From dc7e1def2914be677e483552c334b77194df2922 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Sun, 18 Jul 2021 17:00:17 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 + LICENSE | 21 +++ README.md | 49 ++++++ app/.gitkeep | 1 + app/Command/Display/DefaultController.php | 182 ++++++++++++++++++++++ app/Command/Help/DefaultController.php | 42 +++++ app/Command/Help/DisplayController.php | 26 ++++ app/Service/GithubService.php | 35 +++++ commit-calendar | 27 ++++ composer.json | 14 ++ 10 files changed, 400 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/.gitkeep create mode 100644 app/Command/Display/DefaultController.php create mode 100644 app/Command/Help/DefaultController.php create mode 100644 app/Command/Help/DisplayController.php create mode 100644 app/Service/GithubService.php create mode 100755 commit-calendar create mode 100644 composer.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..157ff0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +vendor/ +composer.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..14ad34e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 minicli + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac6b2c5 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# commit-calendar + +Display a list of dates and commits from public GitHub repositories. +You will display the list in the terminal, and you can choose to save it to a CSV file (";" separated) and display into a browser (thanks to [itty.bitty](https://itty.bitty.site)) + +Commit Calendar is a CLI utility based on [Minicli](https://github.com/minicli/minicli). + +## Installation + +Requirements: +- `php-cli` >= 7.3 +- Composer + +Installation: + +1. Clone this repository +2. Run `composer install` + +## How to use + +In a terminal, in the project folder: + +```bash +./commit-calendar display owner=OWNER name=NAME [number=NUMBER] +``` + +- OWNER: is the repository owner +- NAME: is the repository name +- NUMBER: the number of commits you want to extract + +For example: from this url https://github.com/dottxado/pico-macro-pad the OWNER is "dottxado" and the name is "pico-macro-pad", resulting in +```bash +./commit-calendar display owner=dottxado name=pico-macro-pad +``` + +While OWNER and NAME are required, and if not given they will be queried by the CLI, NUMBER has a default value of 30. + +## Help +In a terminal, in the project folder: + +```bash +./commit-calendar help +``` +will display the available commands, and + +```bash +./commit-calendar help display +``` +will display the help for the display command. diff --git a/app/.gitkeep b/app/.gitkeep new file mode 100644 index 0000000..9c558e3 --- /dev/null +++ b/app/.gitkeep @@ -0,0 +1 @@ +. diff --git a/app/Command/Display/DefaultController.php b/app/Command/Display/DefaultController.php new file mode 100644 index 0000000..f5fb584 --- /dev/null +++ b/app/Command/Display/DefaultController.php @@ -0,0 +1,182 @@ +numberOfResults = 30; + $this->perPage = 30; + $this->userInput = new Input('CommitCalendar$> '); + } + + public function handle(): void + { + $owner = $this->owner(); + $name = $this->name(); + $this->number(); + + $pages = ceil($this->numberOfResults / $this->perPage); + + $this->getPrinter()->info(sprintf('Your input -> OWNER %s and NAME %s', $owner, $name)); + $this->getPrinter()->info(sprintf('Total number of results to get: %s', $this->numberOfResults)); + + $output = []; + for ($i = 1; $i <= $pages; $i++) { + $numberOfResults = ($this->perPage * $i) <= $this->numberOfResults + ? $this->perPage + : $this->numberOfResults % $this->perPage; + $this->getPrinter()->info(sprintf('Querying GitHub for %s results...', $numberOfResults)); + /** + * @var GithubService + */ + $githubService = $this->getApp()->github; + $output = array_merge($output, $githubService->getCommitList($owner, $name, $i, $numberOfResults) ?? []); + } + + if (is_null($output)) { + $this->getPrinter()->error('Ooooops, something has gone bad'); + exit; + } elseif (empty($output)) { + $this->getPrinter()->error('Ooooops, the result is empty!'); + exit; + } + + $this->displayTable($output); + $this->saveToFile($output); + $this->displayInBrowser($output, $owner, $name); + + $this->getPrinter()->success('Bye!'); + $this->getPrinter()->newline(); + } + + private function owner(): string + { + if ($this->hasParam('owner')) { + $owner = $this->getParam('owner'); + } else { + $this->getPrinter()->info('Provide the owner of the repository:'); + $owner = $this->userInput->read(); + } + return $owner; + } + + private function name(): string + { + if ($this->hasParam('name')) { + $name = $this->getParam('name'); + } else { + $this->getPrinter()->info('Provide the name of the repository:'); + $name = $this->userInput->read(); + } + return $name; + } + + private function number() + { + if ($this->hasParam('number') && ! is_null($this->getParam('number'))) { + $this->numberOfResults = (int)$this->getParam('number'); + } + } + + private function displayTable(array $list): void + { + $table = new TableHelper(); + $table->addHeader(['Date', 'Message']); + foreach ($list as $element) { + $date = $element->commit->author->date ?? ''; + $message = isset($element->commit->message) + ? str_replace("\n", ' ', trim($element->commit->message)) + : ''; + $table->addRow([$date, $message]); + } + $this->getPrinter()->newline(); + $this->getPrinter()->rawOutput($table->getFormattedTable(new ColorOutputFilter())); + $this->getPrinter()->newline(); + } + + private function saveToFile(array $list): void + { + $this->getPrinter()->display('Do you want it saved to a CSV file?[Y/n]', true); + $logToFile = $this->userInput->read(); + if (strtolower($logToFile) === 'y' || $logToFile === '') { + $this->getPrinter()->display('Full path and filename?[./commitcalendar.csv]', true); + $filePath = $this->userInput->read(); + if ($filePath === '') { + $filePath = 'commitcalendar.csv'; + } + $backupOutput = $this->getApp()->getPrinter(); + $this->getApp()->setOutputHandler(new OutputHandler(new FilePrinterAdapter($filePath))); + $content = $this->generateCsv($list); + $this->getPrinter()->rawOutput($content); + $this->getApp()->setOutputHandler($backupOutput); + } + } + + private function displayInBrowser(array $list, string $owner, string $name): void + { + $this->getPrinter()->display('Do you want to see it in a browser? (It may not work if the data are > 2Kb)[Y/n]', true); + $generateHtml = $this->userInput->read(); + if (strtolower($generateHtml) === 'y' || $generateHtml === '') { + $link = shell_exec( + 'echo -n "' + .$this->generateHtml($list, $owner, $name) + .'" | lzma -9 | base64 | xargs -0 printf "https://itty.bitty.site/#/%s\n"' + ); + $this->getPrinter()->info('Copy this link to your browser'); + $this->getPrinter()->rawOutput($link); + } + } + + private function generateHtml(array $list, string $owner, string $name): string + { + $pageTemplate = 'Commit List

%s

%s
DateMessage
'; + $rowTemplate = '%s%s'; + $rows = ''; + foreach ($list as $element) { + $date = $element->commit->author->date ?? ''; + $message = isset($element->commit->message) + ? str_replace("\n", ' ', $element->commit->message) + : ''; + $message = $this->sanitizeMessage($message); + $rows .= sprintf($rowTemplate, $date, $message); + } + + return sprintf($pageTemplate, $owner . '/'.$name, $rows); + } + + private function generateCsv(array $list, string $delimiter = ';'): string + { + $result = 'DATE, MESSAGE'; + foreach ($list as $element) { + $date = $element->commit->author->date ?? ''; + $message = isset($element->commit->message) + ? str_replace("\n", ' ', $element->commit->message) + : ''; + $result .= "\n" . $date . $delimiter . $message; + } + return $result; + } + + private function sanitizeMessage(string $message): string + { + $message = str_replace('`', "'", $message); + $message = htmlentities($message); + return $message; + } +} diff --git a/app/Command/Help/DefaultController.php b/app/Command/Help/DefaultController.php new file mode 100644 index 0000000..0eeb530 --- /dev/null +++ b/app/Command/Help/DefaultController.php @@ -0,0 +1,42 @@ +command_map = $app->command_registry->getCommandMap(); + } + + public function handle() + { + $this->getPrinter()->info('Available Commands'); + + foreach ($this->command_map as $command => $sub) { + + $this->getPrinter()->newline(); + $this->getPrinter()->out($command, 'info_alt'); + + if (is_array($sub)) { + foreach ($sub as $subcommand) { + if ($subcommand !== 'default') { + $this->getPrinter()->newline(); + $this->getPrinter()->out(sprintf('%s%s','└──', $subcommand)); + } + } + } + $this->getPrinter()->newline(); + } + + $this->getPrinter()->newline(); + $this->getPrinter()->newline(); + } +} diff --git a/app/Command/Help/DisplayController.php b/app/Command/Help/DisplayController.php new file mode 100644 index 0000000..628c8e5 --- /dev/null +++ b/app/Command/Help/DisplayController.php @@ -0,0 +1,26 @@ +getPrinter()->info('Display the date and the commit messages of public repositories on GitHub', true); + $this->getPrinter()->info('./commit-calendar display owner=OWNER name=NAME [number=NUMBER]'); + $this->getPrinter()->display('OWNER is the repository owner. If not provided, the user will be queried.'); + $this->getPrinter()->display('NAME is the repository name. If not provided, the user will be queried.'); + $this->getPrinter()->display('NUMBER is the number of commits you want to extract. Default 30.'); + + $this->getPrinter()->info('You will display the commit calendar in the console, and you can choose to save it to a CSV file and display into a browser (thanks to itty.bitty)'); + + $this->getPrinter()->newline(); + $this->getPrinter()->info('You can try...', true); + $this->getPrinter()->info('./commit-calendar display owner=minicli name=minicli'); + $this->getPrinter()->info('./commit-calendar display owner=wordpress name=wordpress number=10'); + $this->getPrinter()->info('./commit-calendar display owner=quarkusio name=quarkus'); + $this->getPrinter()->newline(); + } +} diff --git a/app/Service/GithubService.php b/app/Service/GithubService.php new file mode 100644 index 0000000..32140e5 --- /dev/null +++ b/app/Service/GithubService.php @@ -0,0 +1,35 @@ +baseUrl = 'https://api.github.com/repos/'; + $this->headers[] = 'Accept: application/vnd.github.v3+json'; + } + + public function getCommitList(string $ownerName, string $repoName, int $page = 1, int $perPage = 30): ?array + { + $url = $this->baseUrl.$ownerName.'/'.$repoName.'/commits?page='.$page.'&per_page='.$perPage; + $curlResource = curl_init(); + curl_setopt($curlResource, CURLOPT_URL, $url); + curl_setopt($curlResource, CURLOPT_HEADER, $this->headers); + curl_setopt($curlResource, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curlResource, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)'); + $output = curl_exec($curlResource); + $info = curl_getinfo($curlResource); + curl_close($curlResource); + if (false === $output || $info['http_code'] !== 200) { + return null; + } + return json_decode(substr($output, $info["header_size"])); + } +} diff --git a/commit-calendar b/commit-calendar new file mode 100755 index 0000000..1edcc4a --- /dev/null +++ b/commit-calendar @@ -0,0 +1,27 @@ +#!/usr/bin/php + __DIR__ . '/app/Command', +]); + +$app->setSignature(<<addService('github', new GithubService()); + +$app->runCommand($argv); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..50fd944 --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "dottxado/commit-calendar", + "description": "Display a calendar of commits from public GitHub repositories", + "license": "MIT", + "keywords": ["cli","command-line"], + "autoload": { + "psr-4": { + "App\\": "app/" + } + }, + "require": { + "minicli/minicli": "^2.0" + } +}