diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d2f4f21 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 LinkORB B.V. + +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..be5b3df --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +CodeSpace +====== + +Manages your local development projects and code repositories. + +When developing projects, especially micro-services, we often end up with hundres of code repositories on our dev machines. CodeSpace helps to manage these repositories and do batch operations on them easily. +You can use it as a stand-alone tool. There is no need to integrate into any project. + +CodeSpace is inspired by the [LinkORB Projex](https://github.com/linkorb/projex) +## Features + +### Project scanner + +Recursively scans the giving directory to find projects/repositories. +Scan the code repositories and show output to the console `bin/codespace scan [--path=~/git]` + +### Export projects +1. Export to HTML `bin/codespace export:html /path/to/the/target.html [--path=~/git]` +2. Export to CSV `bin/codespace export:csv /path/to/the/target.csv [--path=~/git]` + +### Do git fetch on all projects +When the `--pull` option is used, the `git pull` command is executed instead of the `git fetch` command. +``` +bin/codespace git fetch [--path=~/git] [--pull] +``` + +### Auto-update your favorite IDE's project manager +Scan the repositories and make them available to your IDE's project manager plugins. Now __Atom__ and __VSCode__ are supported. +The project managers are: + + https://atom.io/packages/project-manager + https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager + +Commands: +Without specifying the `--ide=` option, both __Atom__ and __VSCode__ are updated. +``` +bin/codespace ide:pm [--ide=atom] [--path=~/git] +``` + +## Installation: +Use the source code: +``` +composer install +``` +Use the phar: +``` +php code-space.phar +``` + +## Use +Base command: +``` +`bin/codespace` +# or +php code-space.phar +``` +You can view all available commands by running the base command. + + +## License + +Please refer to the included LICENSE.md file diff --git a/bin/codespace b/bin/codespace new file mode 100755 index 0000000..cd721ab --- /dev/null +++ b/bin/codespace @@ -0,0 +1,29 @@ +#!/usr/bin/env php +setCatchExceptions(true); +$application->add(new \Wispiring\CodeSpace\Command\ScanCommand()); +$application->add(new \Wispiring\CodeSpace\Command\GitFetchCommand()); +$application->add(new \Wispiring\CodeSpace\Command\AtomUpdateCommand()); +$application->add(new \Wispiring\CodeSpace\Command\IdeProjectManagerUpdateCommand()); +$application->add(new \Wispiring\CodeSpace\Command\ExportCommand()); +$application->add(new \Wispiring\CodeSpace\Command\HtmlExportCommand()); +$application->run(); diff --git a/code-space.phar b/code-space.phar new file mode 100755 index 0000000..9b17358 Binary files /dev/null and b/code-space.phar differ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b862564 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "h-wang/code-space", + "description": "Managing your code - projects and repositories", + "homepage": "https://github.com/h-wang/code-space", + "keywords": ["project", "git", "project", "manager", "management", "code", "repository", "space", "hongliang", "wispiring"], + "type": "application", + "authors": [ + { + "name": "Hongliang", + "email": "code@hongliang.nl", + "role": "Development" + } + ], + "bin": ["bin/codespace"], + "require": { + "symfony/console": "~3.2" + }, + "autoload": { + "psr-4": { + "Wispiring\\CodeSpace\\": "src/" + } + }, + "license": "MIT" +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..8fac37f --- /dev/null +++ b/composer.lock @@ -0,0 +1,249 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "cc2a77ff5ef2216e43b1c3e0598040d3", + "packages": [ + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, + { + "name": "symfony/console", + "version": "v3.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf", + "reference": "d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-04-03T05:22:50+00:00" + }, + { + "name": "symfony/debug", + "version": "v4.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "5961d02d48828671f5d8a7805e06579d692f6ede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/5961d02d48828671f5d8a7805e06579d692f6ede", + "reference": "5961d02d48828671f5d8a7805e06579d692f6ede", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": "<3.4" + }, + "require-dev": { + "symfony/http-kernel": "~3.4|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2018-04-03T05:24:00+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/src/Command/AtomUpdateCommand.php b/src/Command/AtomUpdateCommand.php new file mode 100644 index 0000000..441d70f --- /dev/null +++ b/src/Command/AtomUpdateCommand.php @@ -0,0 +1,42 @@ +setName('atom:update') + ->setDescription('Updates your Atom project manager configuration file') + ->addOption( + 'path', + null, + InputOption::VALUE_OPTIONAL, + 'The path to scan' + ) + ; + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $spaces = $this->scanPath($input); + + $exporter = new AtomProjectsCsonExporter($spaces); + + $o = $this->getStyler($input, $output); + $o->title('CodeSpace: Generating '.$exporter->getConfigFilePath()); + $o->section("\xF0\x9F\x93\x81 : [".$this->scanPath."]"); + $exporter->export(); + + $o->success("\xF0\x9F\x93\xA3 ".count($spaces)." Atom projects added. \xF0\x9F\x8E\x89"); + } +} diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php new file mode 100644 index 0000000..57512b8 --- /dev/null +++ b/src/Command/BaseCommand.php @@ -0,0 +1,68 @@ +styler) { + $this->styler = new SymfonyStyle($input, $output); + } + + return $this->styler; + } + + protected function setOutputStyle(OutputInterface &$output) + { + $output->getFormatter()->setStyle( + 'header', + new OutputFormatterStyle('white', 'green', array('bold')) + ); + } + + protected function getHomeDirectory() + { + return getenv("HOME"); + } + + protected function getScanPath(InputInterface $input) + { + if (!$this->scanPath) { + $home = $this->getHomeDirectory(); + $path = $input->getOption('path'); + if ($path) { + if (0 === strpos($path, '~/')) { + $path = $home.substr($path, 1); + } + } else { + $path = $home.'/code'; + if (!is_dir($path)) { + $path = $home.'/git'; + } + } + $this->scanPath = rtrim($path, '/'); + } + + + return $this->scanPath; + } + + protected function scanPath(InputInterface $input) + { + $scanner = new Scanner(); + + return $scanner->scan($this->getScanPath($input)); + } +} diff --git a/src/Command/ExportCommand.php b/src/Command/ExportCommand.php new file mode 100644 index 0000000..fcf842d --- /dev/null +++ b/src/Command/ExportCommand.php @@ -0,0 +1,50 @@ +setName('export:csv') + ->setDescription('Export code repositories') + ->addArgument( + 'target_path', + InputArgument::REQUIRED, + 'Target path to put the exported file' + ) + ->addOption( + 'path', + null, + InputOption::VALUE_OPTIONAL, + 'The path to scan' + ) + ; + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $spaces = $this->scanPath($input); + $targetPath = $input->getArgument('target_path'); + + $o = $this->getStyler($input, $output); + $o->title('CodeSpace: Exporting repositories'); + $o->section("\xF0\x9F\x93\x81 : [".$this->scanPath."]"); + $o->section("\xF0\x9F\x93\x84 : [".$targetPath."]"); + + $exporter = new CsvExporter($spaces); + $exporter->exportToFile($targetPath); + + $o->success("\xF0\x9F\x93\xA3 ".count($spaces)." repositories exported. \xF0\x9F\x8E\x89"); + } +} diff --git a/src/Command/GitFetchCommand.php b/src/Command/GitFetchCommand.php new file mode 100644 index 0000000..cee2e03 --- /dev/null +++ b/src/Command/GitFetchCommand.php @@ -0,0 +1,66 @@ +setName('git:fetch') + ->setDescription('Scan code repositories and fetch from origin') + ->addOption( + 'path', + null, + InputOption::VALUE_OPTIONAL, + 'The path to scan' + ) + ->addOption( + 'pull', + null, + InputOption::VALUE_NONE, + 'Direct pull if the repo is on master' + ) + ; + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $pullIfMaster = $input->getOption('pull'); + + $spaces = $this->scanPath($input); + + $o = $this->getStyler($input, $output); + $o->title('CodeSpace: Updating code repositories with Git'); + $o->section("\xF0\x9F\x93\x81 : [".$this->scanPath."]"); + + $o->progressStart(count($spaces)); + foreach ($spaces as $project) { + $o->newLine(); + $o->text('Starting '.$project->getPath().' ...'); + $done = false; + if ($pullIfMaster) { + if (trim(shell_exec('cd '.$project->getPath().' && git rev-parse --abbrev-ref HEAD')) == 'master') { + $o->text('Directly pulling to master ... '.shell_exec('cd '.$project->getPath().' && git pull').' Done!'); + $done = true; + } + } + + if (!$done) { + $o->text('Fetching origin ... '.shell_exec('cd '.$project->getPath().' && git fetch origin').' Done!'); + } + $o->progressAdvance(); + // $o->newLine(); + } + $o->progressFinish(); + $o->success("\xF0\x9F\x93\xA3 ".count($spaces)." code repositories fetched. \xF0\x9F\x8E\x89"); + } +} diff --git a/src/Command/HtmlExportCommand.php b/src/Command/HtmlExportCommand.php new file mode 100644 index 0000000..d2b50f6 --- /dev/null +++ b/src/Command/HtmlExportCommand.php @@ -0,0 +1,49 @@ +setName('export:html') + ->setDescription('Export code repositories') + ->addArgument( + 'target_path', + InputArgument::REQUIRED, + 'Target path to put the exported file' + ) + ->addOption( + 'path', + null, + InputOption::VALUE_OPTIONAL, + 'The path to scan' + ) + ; + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $spaces = $this->scanPath($input); + $targetPath = $input->getArgument('target_path'); + + $o = $this->getStyler($input, $output); + $o->title('CodeSpace: Exporting repositories'); + $o->section("\xF0\x9F\x93\x81 : [".$this->scanPath."]"); + $o->section("\xF0\x9F\x93\x84 : [".$targetPath."]"); + + $exporter = new HtmlExporter($spaces); + $exporter->exportToFile($targetPath); + + $o->success("\xF0\x9F\x93\xA3 ".count($spaces)." repositories exported. \xF0\x9F\x8E\x89"); + } +} diff --git a/src/Command/IdeProjectManagerUpdateCommand.php b/src/Command/IdeProjectManagerUpdateCommand.php new file mode 100644 index 0000000..e00b2e2 --- /dev/null +++ b/src/Command/IdeProjectManagerUpdateCommand.php @@ -0,0 +1,64 @@ +setName('ide:pm') + ->setDescription('Updates IDE\'s project manager configuration file. Supports Atom and VSCode.') + ->addOption( + 'path', + null, + InputOption::VALUE_OPTIONAL, + 'The path to scan' + ) + ->addOption( + 'ide', + null, + InputOption::VALUE_OPTIONAL, + 'The name of the IDE, only "Atom" and "VSCode" are supported for now' + ) + ; + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $spaces = $this->scanPath($input); + $ideName = $input->getOption('ide'); + + $o = $this->getStyler($input, $output); + $o->title("CodeSpace: Scanning project in \xF0\x9F\x93\x81 : [".$this->scanPath."]"); + + switch (strtolower($ideName)) { + case 'atom': + $this->export('Atom', (new AtomProjectsCsonExporter($spaces)), $o, count($spaces)); + break; + case 'vscode': + $this->export('VSCode', (new VscodeProjectJsonExporter($spaces)), $o, count($spaces)); + break; + default: + $this->export('Atom', (new AtomProjectsCsonExporter($spaces)), $o, count($spaces)); + $this->export('VSCode', (new VscodeProjectJsonExporter($spaces)), $o, count($spaces)); + break; + } + } + + protected function export($ideName, $exporter, $styler, $projectCount) + { + $exporter->export(); + $styler->section($ideName.' - ['.$exporter->getConfigFilePath().']'); + $styler->success("\xF0\x9F\x93\xA3 $projectCount $ideName projects added. \xF0\x9F\x8E\x89"); + } +} diff --git a/src/Command/ScanCommand.php b/src/Command/ScanCommand.php new file mode 100644 index 0000000..7b0e0f8 --- /dev/null +++ b/src/Command/ScanCommand.php @@ -0,0 +1,40 @@ +setName('scan') + ->setDescription('Scans your directory for code repositories.') + ->addOption( + 'path', + null, + InputOption::VALUE_OPTIONAL, + 'The path to scan. Optional, default ~/code' + ); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $spaces = $this->scanPath($input); + + $o = $this->getStyler($input, $output); + $o->title('CodeSpace: Code repository scan'); + $o->section("\xF0\x9F\x93\x81 : [".$this->scanPath."]"); + $o->listing( + array_map( + function ($c) { + return ''.$c->getGroup().'/'.$c->getShortName().' '.$c->getPath(); + }, + $spaces + ) + ); + $o->success("\xF0\x9F\x93\xA3 ".count($spaces)." code repositories found. \xF0\x9F\x8E\x89"); + } +} diff --git a/src/Exporter/AtomProjectsCsonExporter.php b/src/Exporter/AtomProjectsCsonExporter.php new file mode 100644 index 0000000..bb6f59b --- /dev/null +++ b/src/Exporter/AtomProjectsCsonExporter.php @@ -0,0 +1,46 @@ +repositories = $repositories; + } + + public function getConfigFilePath() + { + switch (strtoupper(substr(PHP_OS, 0, 3))) { + case 'DAR': + case 'LIN': + return getenv('HOME').'/.atom/projects.cson'; + case 'WIN': + default: + throw new \Exception('Win OS is not supported yet', 1); + } + } + + public function generateConfig() + { + $o = ''; + foreach ($this->repositories as $r) { + $o .= PHP_EOL.' {'.PHP_EOL; + // $o .= ' title: "'.$r->getShortName().'"'.PHP_EOL; + $o .= ' title: "'.$r->getName().'"'.PHP_EOL; + $o .= ' group: "'.$r->getGroup().'"'.PHP_EOL; + $o .= ' paths: ['.PHP_EOL.' "'.$r->getPath().'"'.PHP_EOL.' ]'.PHP_EOL; + $o .= ' icon: "icon-repo"'.PHP_EOL; + $o .= ' }'; + } + + return '['.$o.PHP_EOL.']'; + } + + public function export() + { + return file_put_contents($this->getConfigFilePath(), $this->generateConfig()); + } +} diff --git a/src/Exporter/CsvExporter.php b/src/Exporter/CsvExporter.php new file mode 100644 index 0000000..59f37c2 --- /dev/null +++ b/src/Exporter/CsvExporter.php @@ -0,0 +1,30 @@ +repositories = $repositories; + } + + public function export() + { + $del = ','; + $ln = "\r\n"; + $csv = 'Short name'.$del.'Name'.$del.'Group'.$del.'Path'.$ln; + foreach ($this->repositories as $r) { + $csv .= $r->getShortName().$del.$r->getName().$del.$r->getGroup().$del.$r->getPath().$ln; + } + + return $csv; + } + + public function exportToFile($path) + { + return file_put_contents($path, $this->export()); + } +} diff --git a/src/Exporter/HtmlExporter.php b/src/Exporter/HtmlExporter.php new file mode 100644 index 0000000..f5ca1ab --- /dev/null +++ b/src/Exporter/HtmlExporter.php @@ -0,0 +1,80 @@ +repositories = $repositories; + } + + public function export() + { + $html = ' + + + HtmlExport + + + + + + +
+

Your Program List You can click the Short name to read README.md

+
+
+
+
    +
  • Short name
  • +
  • Name
  • +
  • Group
  • +
  • Path
  • +
+
+
'; + foreach ($this->repositories as $i=>$r) { + $readmeContent = ''; + $filePath = $r->getPath().'/README.md'; + if (file_exists($filePath)) { + $readmeContent = file_get_contents($filePath); + } + $html .= ' +
+ +
+
'.htmlspecialchars($readmeContent).'
+
+
+ '; + } + $html.= '
'; + $html.=' + '; + return $html; + } + + public function exportToFile($path) + { + return file_put_contents($path, $this->export()); + } +} diff --git a/src/Exporter/VscodeProjectJsonExporter.php b/src/Exporter/VscodeProjectJsonExporter.php new file mode 100644 index 0000000..a61a610 --- /dev/null +++ b/src/Exporter/VscodeProjectJsonExporter.php @@ -0,0 +1,46 @@ +repositories = $repositories; + } + + public function getConfigFilePath() + { + switch (strtoupper(substr(PHP_OS, 0, 3))) { + case 'DAR': + return getenv('HOME').'/Library/Application Support/Code/User/projects.json'; + case 'WIN': + return '%APPDATA%\Code\User\projects.json'; + case 'LIN': + default: + return getenv('HOME').'/.config/Code/User/projects.json'; + } + } + + public function generateConfig() + { + $o = ''; + foreach ($this->repositories as $r) { + $o .= PHP_EOL.' {'.PHP_EOL; + $o .= ' "name": "'.$r->getName().'",'.PHP_EOL; + $o .= ' "rootPath": "'.$r->getPath().'",'.PHP_EOL; + $o .= ' "paths": "[]",'.PHP_EOL; + $o .= ' "group": ""'.PHP_EOL; + $o .= ' },'; + } + + return '['.rtrim($o, ',').PHP_EOL.']'.PHP_EOL; + } + + public function export() + { + return file_put_contents($this->getConfigFilePath(), $this->generateConfig()); + } +} diff --git a/src/Project.php b/src/Project.php new file mode 100644 index 0000000..1f62bf7 --- /dev/null +++ b/src/Project.php @@ -0,0 +1,62 @@ +name; + } + + public function setName($name) + { + $this->name = $name; + + return $this; + } + + protected $shortName; + + public function getShortName() + { + return $this->shortName; + } + + public function setShortName($shortName) + { + $this->shortName = $shortName; + + return $this; + } + + private $group; + + public function getGroup() + { + return $this->group; + } + + public function setGroup($group) + { + $this->group = $group; + + return $this; + } + + private $path; + + public function getPath() + { + return $this->path; + } + + public function setPath($path) + { + $this->path = $path; + + return $this; + } +} diff --git a/src/Scanner.php b/src/Scanner.php new file mode 100644 index 0000000..1f5d01a --- /dev/null +++ b/src/Scanner.php @@ -0,0 +1,52 @@ +scanRecursive($path, $dirs); + + $projects= array(); + foreach ($dirs as $dir) { + $project = new Project(); + $projects []= $project + ->setShortName(basename($dir)) + ->setGroup(basename(dirname($dir))) + ->setName(basename(dirname($dir)).'/'.basename($dir)) + ->setPath($dir); + } + + return $projects; + } + + private function scanRecursive($path, &$dirs) + { + $files = scandir($path); + foreach ($files as $filename) { + $skip = false; + switch ($filename) { + case '.': + case '..': + case '.git': + case 'vendor': + case 'node_modules': + $skip = true; + break; + } + + if (!$skip) { + if (is_dir($path.'/'.$filename)) { + if (file_exists($path.'/'.$filename.'/.git/HEAD')) { + // Found a .git repository, add it to the dirs list + $dirs []= $path.'/'.$filename; + } else { + $this->scanRecursive($path.'/'.$filename, $dirs); + } + } + } + } + } +}