├── .gitignore ├── LICENSE ├── Makefile ├── Readme.md ├── build-phar.php ├── composer.json ├── composer.lock └── src ├── Application.php ├── CliController.php ├── DockerComposeConfig.php ├── SimpleTemplating.php ├── Utilities.php └── cli.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | apps/ 3 | docker-project 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 webreactor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN_NAME=docker-project 2 | build: vendor 3 | php build-phar.php --bin="$(BIN_NAME)" 4 | chmod a+x $(BIN_NAME) 5 | 6 | vendor: 7 | composer install --no-dev --optimize-autoloader 8 | 9 | clean: 10 | -rm $(BIN_NAME) 11 | 12 | clean-all: clean 13 | -rm -rf vendor 14 | 15 | install: 16 | cp $(BIN_NAME) /usr/local/bin/ -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Docker Project Management 2 | ============= 3 | 4 | Helps to work with multiple git repositories and set custom build image process using docker-compose.yml 5 | 6 | ### Why do I need it? 7 | 8 | Modern services is a swarm of different interconnected micro-services where each micro-service is stored at individual repo. 9 | It can take a lot of time to setup this kind of environment. 10 | Docker-project helps to work with docker app stored in multiple git repositories and set custom build image process via docker-compose.yml 11 | 12 | **Case 1** 13 | 14 | git clone or pull for each microservice is a pain. 15 | 16 | Let's say you have a microservice application which contains 5 microservices. 17 | Each microservice is a small application on it's own, so you have 5 different git repositories for them. 18 | Docker-compose is used tightly to make it all work together. 19 | This means when you start work with the whole application, you will need to do `git clone` 5 times with proper parameters to a proper destination and so on. If you work in a team time to time you will need to do `git pull` 5 times. Git submodules are not convinient thing to use in this case. 20 | 21 | **Case 2** 22 | 23 | Custom build image process 24 | 25 | Building docker appplications sometimes is not just a matter of running `docker build`. There is no need to bring heavy development tools inside of your image in order to build applications. You split the process in two phases - building and packing in the image. You will have bash script or Makefile, so you need to store somewhere a command to build the image and be able to build all microservices in single call. 26 | 27 | 28 | ### How it works 29 | 30 | Using [labels](https://docs.docker.com/compose/compose-file/#labels-1) you can define metadata for any service in docker-compose.yml. 31 | 32 | Where git repo link, branch, build and other commands are specified as labels for a service, docker-project will parse out that information and will be able to git clone or pull for each defined repo and run commands over the repositories. 33 | 34 | `docker-project` is able to run commands only over repos that have project_git label or build path. 35 | 36 | 37 | By default `docker-project` creates apps folder next to docker-compose.yml file. That path can be changes using parameter -a. 38 | 39 | 40 | 41 | ### Specification 42 | 43 | Works with docker-compose.yml version 2+ 44 | 45 | ```yml 46 | version: "2" 47 | services: 48 | users: 49 | image: vendor/users 50 | expose: 51 | - 80 52 | labels: 53 | project.git: https://github.com/vendor/users.git # linking git repo 54 | project.git.branch: cool-feature # custom branch, default is master 55 | project.build: make # defining build command 56 | project.custom1: echo __image__ # defining custom1 command 57 | project.custom2: echo __image__ # defining custom2 command 58 | ``` 59 | 60 | Meta tags can be used in commands definitions as well as extra (`-x`) parameter string: 61 | * `__image__` - image name 62 | * `__service__` - service name 63 | 64 | ### Usage 65 | 66 | ```bash 67 | docker-project update 68 | # first time it will create apps/vendor/users folder 69 | # vendor/users is taken from git link 70 | # unless build: parameters is specified 71 | # apps/ is relative to yml fine and can be changed 72 | # then run 'git clone' the repo localy 73 | 74 | docker-project build -x dev 75 | # runs 'make dev' at apps/vendor/users 76 | # -x add anything to the end of command 77 | 78 | docker-project update 79 | # since repo alredy exists it runs just 'git pull' 80 | 81 | docker-project custom 82 | # runs `echo vendor/users` 83 | 84 | docker-project shell -x git checkout master 85 | # runs `git checkout master` over all registered repos 86 | 87 | ``` 88 | 89 | 90 | ### `docker-project help` output: 91 | 92 | ``` 93 | docker project management tool 0.0.5 94 | 95 | Usage: 96 | docker-project 97 | 98 | Commands: 99 | update - clones or pulls application source 100 | shell - uses extra parameter to run shell command for each app 101 | status - prints current services with repos and their commands 102 | help - prints help 103 | your_command - defined as label for the service (example: labels: project.test: make test) 104 | 105 | Arguments: 106 | Full name | Short | Default | Note 107 | ----------------------------------------------------- 108 | --file -f docker-compose.yml Alternative config file 109 | --apps -a apps Applications sources folder 110 | --extra -x Extra parameters passed to command 111 | ``` 112 | 113 | ### Install binary 114 | 115 | ```bash 116 | curl -O -L https://github.com/webreactor/docker-project/releases/download/0.0.5/docker-project 117 | chmod a+x docker-project 118 | sudo mv docker-project /usr/local/bin/ 119 | ``` 120 | 121 | Depenencies: 122 | 123 | *php-cli 124 | 125 | ### Build and install manually 126 | 127 | ``` 128 | git clone https://github.com/webreactor/docker-project.git 129 | make 130 | sudo make install 131 | ``` 132 | 133 | Depenencies for build: 134 | 135 | * php-cli 136 | * php composer 137 | * make 138 | * php.ini phar.readonly = off 139 | -------------------------------------------------------------------------------- /build-phar.php: -------------------------------------------------------------------------------- 1 | 'src/cli.php', 5 | 'web' => 'src/web.php', 6 | 'bin' => 'application.phar', 7 | ); 8 | $temp_file = 'application.phar'; 9 | 10 | $options = getopt('', array('cli::', 'web::', 'bin::')); 11 | $options = array_merge($defaults, $options); 12 | 13 | @unlink($options['bin']); 14 | @unlink($temp_file); 15 | 16 | $phar = new Phar($temp_file); 17 | $phar->buildFromDirectory(__DIR__, '/^((?!\.git).)*$/'); 18 | $defaultStub = $phar->createDefaultStub($options['cli'], $options['web']); 19 | $defaultStub = "#!/usr/bin/env php\n".$defaultStub; 20 | $phar->setStub($defaultStub); 21 | unset($phar); 22 | chmod($temp_file, 0755); 23 | rename($temp_file, $options['bin']); 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker-project", 3 | "license": "MIT", 4 | "autoload": { 5 | "psr-4": { 6 | "Reactor\\DockerProject\\": "src/" 7 | } 8 | }, 9 | "repositories": [ 10 | { 11 | "type": "vcs", 12 | "url": "https://github.com/webreactor/cli-arguments.git" 13 | } 14 | ], 15 | "require": { 16 | "symfony/yaml": "v2.7.3", 17 | "webreactor/cli-arguments": "dev-master" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "a90fb76770fb45da3cb9a910a1f0de40", 8 | "content-hash": "603cff86e2588ba81fb4becd2bc88813", 9 | "packages": [ 10 | { 11 | "name": "symfony/yaml", 12 | "version": "v2.7.3", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/symfony/Yaml.git", 16 | "reference": "71340e996171474a53f3d29111d046be4ad8a0ff" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/71340e996171474a53f3d29111d046be4ad8a0ff", 21 | "reference": "71340e996171474a53f3d29111d046be4ad8a0ff", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.3.9" 26 | }, 27 | "require-dev": { 28 | "symfony/phpunit-bridge": "~2.7" 29 | }, 30 | "type": "library", 31 | "extra": { 32 | "branch-alias": { 33 | "dev-master": "2.7-dev" 34 | } 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "Symfony\\Component\\Yaml\\": "" 39 | } 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Fabien Potencier", 48 | "email": "fabien@symfony.com" 49 | }, 50 | { 51 | "name": "Symfony Community", 52 | "homepage": "https://symfony.com/contributors" 53 | } 54 | ], 55 | "description": "Symfony Yaml Component", 56 | "homepage": "https://symfony.com", 57 | "time": "2015-07-28 14:07:07" 58 | }, 59 | { 60 | "name": "webreactor/cli-arguments", 61 | "version": "dev-master", 62 | "source": { 63 | "type": "git", 64 | "url": "https://github.com/webreactor/cli-arguments.git", 65 | "reference": "bab7e136ef88f982f6882c4cf12e40b72df38595" 66 | }, 67 | "dist": { 68 | "type": "zip", 69 | "url": "https://api.github.com/repos/webreactor/cli-arguments/zipball/bab7e136ef88f982f6882c4cf12e40b72df38595", 70 | "reference": "bab7e136ef88f982f6882c4cf12e40b72df38595", 71 | "shasum": "" 72 | }, 73 | "type": "library", 74 | "autoload": { 75 | "psr-4": { 76 | "Reactor\\CliArguments\\": "src/" 77 | } 78 | }, 79 | "license": [ 80 | "MIT" 81 | ], 82 | "support": { 83 | "source": "https://github.com/webreactor/cli-arguments/tree/master", 84 | "issues": "https://github.com/webreactor/cli-arguments/issues" 85 | }, 86 | "time": "2016-06-24 03:10:53" 87 | } 88 | ], 89 | "packages-dev": [], 90 | "aliases": [], 91 | "minimum-stability": "stable", 92 | "stability-flags": { 93 | "webreactor/cli-arguments": 20 94 | }, 95 | "prefer-stable": false, 96 | "prefer-lowest": false, 97 | "platform": [], 98 | "platform-dev": [] 99 | } 100 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | tpl = new SimpleTemplating(); 17 | $this->compose = new DockerComposeConfig(); 18 | } 19 | 20 | public function setAppsDir($path) { 21 | $this->apps_dir = $path; 22 | } 23 | 24 | public function loadComposeFile($file) { 25 | $this->compose->openFile($file); 26 | $this->apps = $this->parseAppDirs($this->compose->getServices()); 27 | } 28 | 29 | public function runUpdate($extra) { 30 | if (!is_dir($this->apps_dir)) { 31 | mkdir($this->apps_dir, 0775, true); 32 | } 33 | foreach ($this->apps as $service_name => $app_dir) { 34 | $service = $this->compose->getService($service_name); 35 | if (isset($service['labels']['project.git'])) { 36 | $git_link = $service['labels']['project.git']; 37 | $branch = 'master'; 38 | if (isset($service['labels']['project.git.branch'])) { 39 | $branch = $service['labels']['project.git.branch']; 40 | } 41 | $this->gitUpdate($service_name, $git_link, $branch, $extra); 42 | } 43 | } 44 | } 45 | 46 | public function runShell($command, $extra) { 47 | if (empty($extra)) { 48 | throw new \Exception("--extra is a necessary parameter"); 49 | } 50 | foreach ($this->apps as $service_name => $app_dir) { 51 | $service = $this->compose->getService($service_name); 52 | $this->execForService($extra, $service_name); 53 | } 54 | } 55 | 56 | public function runProjectCommand($command_name, $extra) { 57 | $key = strtolower("project.{$command_name}"); 58 | $command = false; 59 | foreach ($this->compose->getServices() as $service_name => $service) { 60 | $service = $this->compose->getService($service_name); 61 | if (isset($service['labels'][$key])) { 62 | $command = trim($service['labels'][$key].' '.$extra); 63 | $this->execForService($command, $service_name); 64 | } 65 | } 66 | if ($command === false) { 67 | throw new \Exception( 68 | "Not defined command '$command_name'\n". 69 | "Possible fix: Create label project.$command_name with shell command as value." 70 | ); 71 | } 72 | } 73 | 74 | private function parseAppDirs($services) { 75 | $apps = array(); 76 | foreach ($services as $service_name => $service) { 77 | $dir = null; 78 | if (isset($service['labels']['project.git'])) { 79 | $git_link = $service['labels']['project.git']; 80 | preg_match('/\b([\w\-]+\/?[\w\-]+)\.git/', $git_link, $match); 81 | $dir = Utilities::normalizeDir($match[1], $this->apps_dir); 82 | } elseif (isset($service['build'])) { 83 | $dir = Utilities::normalizeDir($service['build'], dirname($service['_source'])); 84 | } 85 | if (!empty($dir)) { 86 | $apps[$service_name] = $dir; 87 | } 88 | } 89 | return $apps; 90 | } 91 | 92 | public function execForService($command, $service_name) { 93 | $service = $this->compose->getService($service_name); 94 | echo "--------------------------------------------\n"; 95 | $path = $this->apps[$service_name]; 96 | echo "Service: {$service['service_name']} at $path\n"; 97 | $this->tpl->data = array( 98 | '__service__' => $service['service_name'], 99 | '__image__' => isset($service['image'])?$service['image']:'', 100 | ); 101 | $command = $this->tpl->process(trim($command)); 102 | Utilities::exec($command, $path); 103 | } 104 | 105 | public function gitUpdate($service_name, $link, $branch, $extra) { 106 | if (!is_dir($this->apps[$service_name].'.git')) { 107 | $this->execForService("git clone $extra -b ".$branch." ".$link." .", $service_name); 108 | } else { 109 | $this->execForService("git pull $extra", $service_name); 110 | } 111 | } 112 | 113 | public function testDependencies() { 114 | $a = exec('git --version', $out, $code); 115 | if ($code > 0) { 116 | throw new \Exception( 117 | "Missing git client tool.\n". 118 | "This might help: sudo apt-get install git" 119 | ); 120 | } 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/CliController.php: -------------------------------------------------------------------------------- 1 | app = $app; 15 | $this->pwd = getcwd().'/'; 16 | } 17 | 18 | public function handle($arguments_container) { 19 | try { 20 | $this->handleLogic($arguments_container); 21 | } catch (\Exception $e) { 22 | echo "Error: ".$e->getMessage()."\n\n"; 23 | exit(1); 24 | } 25 | } 26 | 27 | public function handleLogic($arguments_container) { 28 | $arguments = $this->parseArguments($arguments_container); 29 | $this->app->setAppsDir($arguments['apps-dir']); 30 | switch ($arguments['command']) { 31 | case 'help': 32 | $this->printHelp($arguments_container); 33 | break; 34 | case 'update': 35 | $this->app->loadComposeFile($arguments['composer-file']); 36 | $this->app->runUpdate($arguments['extra']); 37 | break; 38 | case 'shell': 39 | $this->app->loadComposeFile($arguments['composer-file']); 40 | $this->app->runShell( 41 | $arguments['command'], 42 | $arguments['extra'] 43 | ); 44 | break; 45 | case 'status': 46 | $this->app->loadComposeFile($arguments['composer-file']); 47 | $this->printStatus($arguments); 48 | break; 49 | default: 50 | $this->app->loadComposeFile($arguments['composer-file']); 51 | $this->app->runProjectCommand( 52 | $arguments['command'], 53 | $arguments['extra'] 54 | ); 55 | }; 56 | } 57 | 58 | public function parseArguments($arguments_container) { 59 | $this->defineArguments($arguments_container); 60 | $arguments = array(); 61 | $arguments['composer-file'] = Utilities::normalizePath($arguments_container->get('file'), $this->pwd); 62 | $arguments['apps-dir'] = Utilities::normalizeDir($arguments_container->get('apps'), $this->pwd); 63 | 64 | $_cli_words = $arguments_container->get('_words_'); 65 | if (!isset($_cli_words[1])) { 66 | $_cli_words[1] = 'help'; 67 | } 68 | $arguments['command'] = $_cli_words[1]; 69 | $arguments['extra'] = $arguments_container->get('extra'); 70 | return $arguments; 71 | } 72 | 73 | public function defineArguments($arguments) { 74 | $arguments->addDefinition(new ArgumentDefinition('file', 'f', 'docker-compose.yml', false, false, 'Alternative config file')); 75 | $arguments->addDefinition(new ArgumentDefinition('apps', 'a', 'apps', false, false, 'Applications sources folder')); 76 | $arguments->addDefinition(new ArgumentDefinition('extra', 'x', '', false, false, 'Extra parameters passed to command')); 77 | $arguments->addDefinition(new ArgumentDefinition('_words_', '', '', false, true, 'command')); 78 | $arguments->parse(); 79 | return $arguments; 80 | } 81 | 82 | public function printHelp($arguments) { 83 | echo "docker project management tool {$this->app->version}\n"; 84 | echo "\nUsage:\n"; 85 | echo " docker-project \n"; 86 | echo "\nCommands:\n"; 87 | echo " update - clones or pulls application source\n"; 88 | echo " shell - uses extra parameter to run shell command for each app\n"; 89 | echo " status - prints current services with repos and their commands\n"; 90 | echo " help - prints help\n"; 91 | echo " your_command - defined as label for the service (example: labels: project.test: make test)\n"; 92 | echo "\nArguments:\n"; 93 | echo " Full name | Short | Default | Note\n"; 94 | echo "-------------------------------------------------------\n"; 95 | 96 | foreach ($arguments->definitions as $key => $definition) { 97 | if ($key != '_words_') { 98 | echo sprintf(" --%-12s -%-6s %-20s %s\n", 99 | $definition->name, 100 | $definition->short, 101 | $definition->default, 102 | $definition->description 103 | ); 104 | } 105 | } 106 | echo "\n"; 107 | } 108 | 109 | public function printStatus($arguments) { 110 | echo "Compose file: {$arguments['composer-file']}\n"; 111 | echo "Apps folder: {$arguments['apps-dir']}\n"; 112 | if (!empty($arguments['extra'])) { 113 | echo "Extra parameters: {$arguments['extra']}\n"; 114 | } 115 | echo "\nRegistered services\n"; 116 | foreach ($this->app->apps as $service_name => $app_dir) { 117 | echo "$service_name:\n"; 118 | echo " Application folder: {$app_dir}\n"; 119 | $service = $this->app->compose->getService($service_name); 120 | foreach ($service['labels'] as $key => $value) { 121 | if (preg_match('/project.(.+)/', $key, $match)) { 122 | $name = ucfirst($match[1]); 123 | echo " {$name}: $value\n"; 124 | } 125 | } 126 | echo "\n"; 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/DockerComposeConfig.php: -------------------------------------------------------------------------------- 1 | loadFile($file); 12 | } 13 | } 14 | 15 | public function getService($service_name) { 16 | return $this->config['services'][$service_name]; 17 | } 18 | public function getServices() { 19 | return $this->config['services']; 20 | } 21 | 22 | public function openFile($file) { 23 | $this->config = $this->loadFile($file); 24 | } 25 | 26 | public function loadFile($file) { 27 | $config = Utilities::loadYMLFile($file); 28 | if (!(isset($config['version']) && $config['version'] >= 2)) { 29 | throw new \Exception("Supports only docker-compose.yml version 2 or above", 1); 30 | } 31 | $config['_source'] = $file; 32 | if (!isset($config['services'])) { 33 | throw new \Exception("No services are defined in $file", 1); 34 | } 35 | $pwd = dirname($file).'/'; 36 | $config = $this->normalize($config); 37 | $config = $this->resolveExtends($config, $pwd); 38 | return $config; 39 | } 40 | 41 | private function normalize($config) { 42 | foreach ($config['services'] as $service_name => $service) { 43 | $config['services'][$service_name]['service_name'] = $service_name; 44 | } 45 | return $config; 46 | } 47 | 48 | 49 | private function resolveExtends($config, $pwd) { 50 | foreach ($config['services'] as $service_name => $service) { 51 | $config['services'][$service_name]['_source'] = $config['_source']; 52 | if (isset($service['extends'])) { 53 | $extends = $service['extends']; 54 | $extends_file = Utilities::normalizePath($extends['file'], $pwd); 55 | if (is_file($extends_file)) { 56 | $extention_data = $this->loadFile($extends_file); 57 | if (isset($extention_data['services'][$extends['service']])) { 58 | $service_extention = $extention_data['services'][$extends['service']]; 59 | $service_extention['_source'] = $extention_data['_source']; 60 | $config['services'][$service_name] = Utilities::mergeRecursive( 61 | $config['services'][$service_name], 62 | $service_extention 63 | ); 64 | } 65 | } else { 66 | echo "Warning: [$service_name] $extends_file does not exist\n"; 67 | } 68 | } 69 | } 70 | return $config; 71 | } 72 | } -------------------------------------------------------------------------------- /src/SimpleTemplating.php: -------------------------------------------------------------------------------- 1 | data = $data; 11 | } 12 | 13 | public function process($data) { 14 | if (is_array($data)) { 15 | foreach ($data as $key => $value) { 16 | $data[$key] = $this->process($value); 17 | } 18 | } elseif (is_string($data)) { 19 | $data = $this->processStr($data); 20 | } 21 | return $data; 22 | } 23 | 24 | public function processStr($str) { 25 | foreach ($this->data as $key => $value) { 26 | $str = str_replace($key, $value, $str); 27 | } 28 | return $str; 29 | } 30 | 31 | public function set($key, $value) { 32 | $this->data[$key] = $value; 33 | return $this; 34 | } 35 | 36 | public function remove($key) { 37 | unset($this->data[$key]); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/Utilities.php: -------------------------------------------------------------------------------- 1 | $value) { 30 | if (isset($available[$key]) && $value !== null) { 31 | $rez[] = $available[$key] . escapeshellarg($value); 32 | } 33 | } 34 | return implode(' ', $rez); 35 | } 36 | 37 | public static function loadYMLFile($file) { 38 | if (!is_file($file)) { 39 | throw new \Exception("File not found '$file'"); 40 | } 41 | return Utilities::parseYAML(file_get_contents($file), true); 42 | } 43 | 44 | public static function normalizeDir($path, $pwd) { 45 | return self::normalizePath($path, $pwd).'/'; 46 | } 47 | 48 | public static function normalizePath($path, $pwd) { 49 | $path = rtrim($path, '/'); 50 | $pwd = rtrim($pwd, '/').'/'; 51 | if ($path[0] !== '/') { 52 | $path = $pwd.$path; 53 | } 54 | $real = realpath($path); 55 | if ($real !== false) { 56 | return $real; 57 | } 58 | return $path; 59 | } 60 | 61 | public static function mergeRecursive($data1, $data2) { 62 | if (!(is_array($data1) && is_array($data2))) { 63 | return $data2; 64 | } 65 | foreach ($data2 as $key => $value) { 66 | if (isset($data1[$key])) { 67 | if (is_integer($key)) { 68 | $data1[] = $value; 69 | } else { 70 | $data1[$key] = self::mergeRecursive($data1[$key], $value); 71 | } 72 | } else { 73 | $data1[$key] = $value; 74 | } 75 | } 76 | return $data1; 77 | } 78 | 79 | public static function strToClassName($str) { 80 | return str_replace(' ', '', ucwords(str_replace('-', ' ', $str))); 81 | } 82 | 83 | public static function parseYAML($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) { 84 | return Yaml::parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap); 85 | } 86 | 87 | public static function dumpYAML($array, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false) { 88 | return Yaml::dump($array, $inline, $indent, $exceptionOnInvalidType, $objectSupport); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/cli.php: -------------------------------------------------------------------------------- 1 | handle(new ArgumentsParser($GLOBALS['argv'])); 11 | --------------------------------------------------------------------------------