├── CHANGELOG.md ├── CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── bin ├── crap └── crap.php ├── composer.json └── src ├── Command ├── AliasCommand.php ├── BaseCommand.php ├── BaseComposerCommand.php ├── InfoCommand.php ├── ListAliasesCommand.php ├── ProjectCommand.php ├── RemoveCommand.php ├── RequireCommand.php ├── UnaliasCommand.php └── UpdateCommand.php ├── Crap.php ├── CrapException.php ├── CrapHelper.php ├── CrapProvider.php └── ExceptionHandler.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `geekish/crap` will be documented in this file and follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 4 | 5 | ## 1.1.0 - 2018-06-13 6 | 7 | ### Added 8 | 9 | - New command `project`; proxy to `composer create-project`. 10 | 11 | ## 1.0.1 - 2018-06-05 12 | 13 | ### Changed 14 | 15 | - Multiple alias arguments now supported in `unalias` command. 16 | 17 | ## 1.0.0 - 2017-02-15 18 | 19 | ### Changed 20 | 21 | - Nothing; stable version bump 22 | 23 | ## 1.0.0-beta.4 - 2017-02-11 24 | 25 | ### Added 26 | 27 | - Add `—dry-run` option to `alias`, `unalias` for testing. 28 | 29 | ### Changed 30 | 31 | - Interaction on `alias` command when the second argument is an existing alias. 32 | 33 | ### Fixed 34 | 35 | - Improved code coverage 36 | 37 | ## 1.0.0-beta.3 - 2017-01-08 38 | 39 | ### Added 40 | 41 | - New command `info`; tells you what an alias is set to. 42 | 43 | ### Changed 44 | 45 | - Improved dialog on `alias` command when overriding an existing alias. 46 | - Interactive input on `alias` command when arguments are missing/swapped. 47 | 48 | ### Fixed 49 | 50 | - Bumped PHPUnit version constraint to fix build failures. 51 | 52 | ### Removed 53 | 54 | - Unnecessary command aliases: `list-aliases` (to `aliases`) and `define` (to `alias`). 55 | 56 | ## 1.0.0-beta.2 - 2016-12-03 57 | 58 | ### Fixed 59 | 60 | - Composer commands were timing out after 60 seconds. 61 | 62 | ## 1.0.0-beta - 2016-11-23 63 | 64 | Initial release. 65 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at `hannahwarmbier@gmail.com`. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/geekish/crap). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - Check the code style with ``$ composer checkcs`` and fix it with ``$ composer fixcs``. 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ composer test 29 | ``` 30 | 31 | **Happy coding**! 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 Hannah Warmbier 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 13 | > all 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 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crap: Composer Require Aliased Packages 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE.md) 5 | [![Build Status][ico-travis]][link-travis] 6 | [![Coverage Status][ico-coveralls]][link-coveralls] 7 | [![Quality Score][ico-code-quality]][link-code-quality] 8 | [![Total Downloads][ico-downloads]][link-downloads] 9 | 10 | Define aliases for your favorite packages, making usage as simple as `crap require monolog`. 11 | 12 | ![](example.gif) 13 | 14 | ## Install 15 | 16 | PHP 5.6+ or 7.0+ required. 17 | 18 | Because using multiple global Composer packages can create dependency conflicts, I recommend using [`consolidation/cgr`](//github.com/consolidation/cgr) over `composer global require`: 19 | 20 | ``` bash 21 | cgr geekish/crap 22 | ``` 23 | 24 | If you _don't_ mind descending into global dependency hell: 25 | 26 | ``` bash 27 | composer global require geekish/crap 28 | ``` 29 | 30 | ## Usage 31 | 32 | To define your aliases (version constraints are optional): 33 | 34 | ``` bash 35 | crap alias monolog monolog/monolog:^1.21 36 | ``` 37 | 38 | To use your aliases: 39 | 40 | ``` bash 41 | crap require monolog 42 | crap update monolog 43 | crap remove monolog 44 | ``` 45 | 46 | To remove an alias: 47 | 48 | ``` bash 49 | crap unalias monolog 50 | ``` 51 | 52 | View a list of defined alias: 53 | 54 | ``` bash 55 | crap list 56 | ``` 57 | 58 | These commands simply find your aliases, and call the Composer commands with the real package names. 59 | If you provided a version constraint when defining your alias, `crap` will use this by default. 60 | You may provide a version constraint on the require and update commands; this will override any default. 61 | 62 | The commands `project`, `require`, `update`, and `remove` all accept the same options/flags as the Composer commands that they call. 63 | For reference, see Composer's [Commands documentation][link-composer-docs]. 64 | 65 | ## Why? 66 | 67 | I'm lazy. 68 | I tried to create global shell aliases for packages that I used often. 69 | It worked, but if I typed out the full package name, I would get `monolog/monolog/monolog/monolog`. 70 | I also tried to write a Composer plugin, but there was no way to override the incoming package argument. 71 | So, I wrote `crap`. 72 | 73 | Oh, the name? 74 | I was going to call it `cra`, and call the main class `Crapp`. 75 | Then I thought of the acronym, and it was just too good not to use. 76 | 77 | Yes, it is totally inspired by Spongebob's [POOP]. 78 | 79 | ## Change log 80 | 81 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 82 | 83 | ## Testing 84 | 85 | After a composer install, to run the unit tests via `phpunit`: 86 | 87 | ``` bash 88 | composer test 89 | ``` 90 | 91 | ## Contributing 92 | 93 | Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details. 94 | 95 | ## Security 96 | 97 | If you discover any security related issues, please email hannahwarmbier@gmail.com instead of using the issue tracker. 98 | 99 | ## Credits 100 | 101 | - [asciinema] & [asciinema2gif] - for the terminal recording 102 | - [consolidation/cgr] - for the inspiration to write this. 103 | - [Contributors] 104 | 105 | ## License 106 | 107 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 108 | 109 | [ico-version]: https://img.shields.io/packagist/v/geekish/crap.svg?style=flat-square 110 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 111 | [ico-travis]: https://img.shields.io/travis/geekish/crap/master.svg?style=flat-square 112 | [ico-coveralls]: https://coveralls.io/repos/github/geekish/crap/badge.svg 113 | [ico-code-quality]: https://img.shields.io/scrutinizer/g/geekish/crap.svg?style=flat-square 114 | [ico-downloads]: https://img.shields.io/packagist/dt/geekish/crap.svg?style=flat-square 115 | 116 | [link-packagist]: https://packagist.org/packages/geekish/crap 117 | [link-travis]: //travis-ci.org/geekish/crap 118 | [link-coveralls]: //coveralls.io/github/geekish/crap 119 | [link-code-quality]: //scrutinizer-ci.com/g/geekish/crap 120 | [link-downloads]: https://packagist.org/packages/geekish/crap 121 | 122 | [link-composer-docs]: //getcomposer.org/doc/03-cli.md 123 | 124 | [contributors]: ../../contributors 125 | 126 | [asciinema]: //asciinema.org/ 127 | [asciinema2gif]: //github.com/tav/asciinema2gif 128 | [consolidation/cgr]: //github.com/consolidation/cgr 129 | [poop]: //poop.urbanup.com/340199#.WC3kAiMiBbo.twitter 130 | -------------------------------------------------------------------------------- /bin/crap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new CrapProvider($home)); 27 | $container = $factory->createContainer(); 28 | 29 | $crap = $container->get(Crap::class); 30 | $result = $container->call([$crap, 'run']); 31 | 32 | exit($result); 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geekish/crap", 3 | "type": "library", 4 | "description": "Define aliases for your favorite Composer packages.", 5 | "keywords": [ 6 | "geekish", 7 | "crap" 8 | ], 9 | "homepage": "https://github.com/geekish/crap", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Hannah Warmbier", 14 | "email": "hannahwarmbier@gmail.com", 15 | "homepage": "https://github.com/geekish", 16 | "role": "Developer" 17 | } 18 | ], 19 | "require": { 20 | "php": "^5.6|^7.0", 21 | "composer/composer": "^1.2", 22 | "mindplay/unbox": "^2.0", 23 | "symfony/console": "^2.8|^3.0", 24 | "symfony/process": "^2.5|^3.0", 25 | "webmozart/json": "^1.2", 26 | "webmozart/key-value-store": "^1.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^5.7|^6.0", 30 | "phpunit/php-code-coverage": "^5.2.3", 31 | "satooshi/php-coveralls": "^1.0", 32 | "squizlabs/php_codesniffer": "^2.7" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Geekish\\Crap\\": "src" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Geekish\\Crap\\": "tests" 42 | } 43 | }, 44 | "scripts": { 45 | "test": "./vendor/bin/phpunit", 46 | "checkcs": "./vendor/bin/phpcs", 47 | "fixcs": "./vendor/bin/phpcbf" 48 | }, 49 | "bin": [ 50 | "bin/crap" 51 | ], 52 | "config": { 53 | "sort-packages": true 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Command/AliasCommand.php: -------------------------------------------------------------------------------- 1 | setName('alias'); 25 | $this->setDescription('Defines an alias for a package to be used by crap.'); 26 | $this->addArgument('alias', InputArgument::REQUIRED, 'Package alias'); 27 | $this->addArgument('package', InputArgument::REQUIRED, 'Package'); 28 | $this->addOption( 29 | 'dry-run', 30 | null, 31 | InputOption::VALUE_NONE, 32 | 'Run command without writing to your `crap.json`, useful for testing.' 33 | ); 34 | } 35 | 36 | /** 37 | * @inheritDoc 38 | */ 39 | protected function execute(InputInterface $input, OutputInterface $output) 40 | { 41 | $alias = $input->getArgument('alias'); 42 | $package = $input->getArgument('package'); 43 | 44 | if (!$this->helper->validateAlias($alias)) { 45 | throw CrapException::create( 46 | 'The alias `%s` is invalid, it should be lowercase, and match: [a-z0-9_.-]+', 47 | $alias 48 | ); 49 | } 50 | 51 | if (!$this->helper->validatePackage($package)) { 52 | throw CrapException::create( 53 | 'The package `%s` is invalid, it should match: [a-z0-9_.-]+/[a-z0-9_.-]+', 54 | $input->getArgument('package') 55 | ); 56 | } 57 | 58 | $override = false; 59 | 60 | if ($this->helper->hasAlias($alias)) { 61 | $current = $this->helper->getAlias($alias); 62 | 63 | if ($current == $package) { 64 | $output->writeln(sprintf( 65 | 'Alias `%s` to package `%s` already exists, silly.', 66 | $alias, 67 | $package 68 | )); 69 | return 0; 70 | } 71 | 72 | $helper = $this->getHelper('question'); 73 | 74 | $output->writeln(sprintf( 75 | 'Alias `%s` exists and is set to `%s`.', 76 | $alias, 77 | $current 78 | )); 79 | 80 | $ask = sprintf('Override alias `%s` with `%s`? (y/n) ', $alias, $package); 81 | $question = new ConfirmationQuestion($ask, false); 82 | 83 | if (!$helper->ask($input, $output, $question)) { 84 | return 0; 85 | } 86 | 87 | $override = true; 88 | } 89 | 90 | if ($input->getOption('dry-run') !== true) { 91 | $this->helper->setAlias($alias, $package); 92 | } 93 | 94 | $output->writeln(sprintf( 95 | 'Alias `%s` to package `%s` successfully %s.', 96 | $alias, 97 | $package, 98 | $override ? 'updated' : 'added' 99 | )); 100 | 101 | return 0; 102 | } 103 | 104 | /** 105 | * @inheritDoc 106 | */ 107 | protected function interact(InputInterface $input, OutputInterface $output) 108 | { 109 | $args = array_values($input->getArguments()); 110 | 111 | $helper = $this->getHelper('question'); 112 | 113 | if ($args[2] == null && $this->helper->validatePackage($args[1]) === true) { 114 | $package = $args[1]; 115 | 116 | $output->writeln('You provided the package but no alias!'); 117 | 118 | $message = sprintf('What do you want to use an an alias for `%s`?', $package); 119 | $question = new Question($message . PHP_EOL, false); 120 | 121 | $alias = $helper->ask($input, $output, $question); 122 | 123 | $input->setArgument('alias', $alias); 124 | $input->setArgument('package', $package); 125 | } elseif ($args[2] == null && $this->helper->validateAlias($args[1])) { 126 | $alias = $args[1]; 127 | 128 | $output->writeln('You provided the alias but no package!'); 129 | 130 | $message = sprintf('What package do you want to alias `%s` to?', $alias); 131 | $question = new Question($message . PHP_EOL, false); 132 | 133 | $package = $helper->ask($input, $output, $question); 134 | 135 | $input->setArgument('package', $package); 136 | } elseif ($this->helper->validateAlias($args[2]) && $this->helper->validatePackage($args[1])) { 137 | $output->writeln('It looks like you swapped the package and alias.'); 138 | 139 | $message = sprintf( 140 | 'Did you mean to alias `%s` to package `%s`? (y/n) ', 141 | $args[2], 142 | $args[1] 143 | ); 144 | 145 | $question = new ConfirmationQuestion($message, false); 146 | 147 | if ($helper->ask($input, $output, $question)) { 148 | $input->setArgument('alias', $args[2]); 149 | $input->setArgument('package', $args[1]); 150 | } 151 | } elseif ($this->helper->validateAlias($args[1]) && $this->helper->hasAlias($args[2])) { 152 | $output->writeln('You provided an existing alias instead of a package.'); 153 | 154 | $existing = $this->helper->getAlias($args[2]); 155 | 156 | $message = sprintf( 157 | 'Do you want to alias `%s` to `%s`? (y/n) ', 158 | $args[1], 159 | $existing 160 | ); 161 | 162 | $question = new ConfirmationQuestion($message, false); 163 | 164 | if ($helper->ask($input, $output, $question)) { 165 | $input->setArgument('package', $existing); 166 | } 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/Command/BaseCommand.php: -------------------------------------------------------------------------------- 1 | helper = $helper; 23 | 24 | parent::__construct(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Command/BaseComposerCommand.php: -------------------------------------------------------------------------------- 1 | getOptions(); 27 | $options = []; 28 | 29 | foreach ($this->getDefinition()->getOptions() as $option) { 30 | $name = $option->getName(); 31 | if ($inputOptions[$name] !== $option->getDefault()) { 32 | $options[] = $name; 33 | } 34 | } 35 | 36 | if ($decorated) { 37 | $options[] = 'ansi'; 38 | } 39 | 40 | return $options; 41 | } 42 | 43 | /** 44 | * Create Process for Composer command 45 | * @codeCoverageIgnore 46 | * 47 | * @param $command 48 | * @param array $packages 49 | * @param array $options 50 | * @return Process 51 | */ 52 | protected function createProcess($command, array $packages, array $options) 53 | { 54 | $arguments = []; 55 | 56 | $arguments[] = $command; 57 | 58 | if (count($options) > 0) { 59 | $options = array_map(function ($value) { 60 | return str_pad($value, strlen($value) + 2, '-', STR_PAD_LEFT); 61 | }, $options); 62 | 63 | array_push($arguments, ...$options); 64 | } 65 | 66 | array_push($arguments, ...$packages); 67 | 68 | return (new ProcessBuilder) 69 | ->setPrefix('composer') 70 | ->setArguments($arguments) 71 | ->setTimeout(null) 72 | ->getProcess(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Command/InfoCommand.php: -------------------------------------------------------------------------------- 1 | setName('info'); 21 | $this->setDescription('Get a single alias.'); 22 | $this->addArgument('alias', InputArgument::REQUIRED, 'Package alias'); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | protected function execute(InputInterface $input, OutputInterface $output) 29 | { 30 | $alias = $input->getArgument('alias'); 31 | 32 | if (!$this->helper->hasAlias($alias)) { 33 | $output->writeln(sprintf( 34 | 'Alias `%s` does not exist.', 35 | $alias 36 | )); 37 | 38 | return 1; 39 | } 40 | 41 | $package = $this->helper->getAlias($alias); 42 | 43 | $output->writeln(sprintf('Alias `%s` is set to: %s', $alias, $package)); 44 | 45 | return 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Command/ListAliasesCommand.php: -------------------------------------------------------------------------------- 1 | setName('aliases'); 20 | $this->setDescription('List currently defined aliases'); 21 | } 22 | 23 | /** 24 | * @inheritDoc 25 | * @codeCoverageIgnore 26 | */ 27 | protected function execute(InputInterface $input, OutputInterface $output) 28 | { 29 | $aliases = $this->helper->getAliases(); 30 | 31 | if (count($aliases) > 0) { 32 | $pad = max(array_map('strlen', $aliases)) + 3; 33 | 34 | foreach ($aliases as $alias) { 35 | $package = $this->helper->getAlias($alias); 36 | $output->writeln(sprintf( 37 | '%s %s', 38 | str_pad($alias, $pad, ' '), 39 | $package 40 | )); 41 | } 42 | 43 | return 0; 44 | } 45 | 46 | $output->writeln('No aliases defined.'); 47 | 48 | return 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Command/ProjectCommand.php: -------------------------------------------------------------------------------- 1 | setName('project'); 23 | $this->setDescription('Gets package name and version by alias, calls `composer create-project`'); 24 | 25 | $this->addArgument('alias', InputArgument::REQUIRED, 'Package alias'); 26 | 27 | $command = new CreateProjectCommand; 28 | $definition = $command->getDefinition(); 29 | 30 | $directory = $definition->getArgument('directory'); 31 | $version = $definition->getArgument('version'); 32 | 33 | $this->getDefinition()->addArgument($directory); 34 | $this->getDefinition()->addArgument($version); 35 | 36 | foreach ($command->getDefinition()->getOptions() as $option) { 37 | $this->getDefinition()->addOption($option); 38 | } 39 | } 40 | 41 | /** 42 | * @inheritDoc 43 | * @codeCoverageIgnore 44 | */ 45 | protected function execute(InputInterface $input, OutputInterface $output) 46 | { 47 | $alias = $input->getArgument('alias'); 48 | $directory = $input->getArgument('directory'); 49 | $version = $input->getArgument('version'); 50 | 51 | $alias = is_null($version) ? $alias : $alias . ':' . $version; 52 | $package = $this->helper->parseArguments([$alias], true); 53 | 54 | $args = [$package[0], $directory, $version]; 55 | 56 | $options = $this->getOptions($input, $output->isDecorated()); 57 | $helper = $this->getHelper('process'); 58 | $process = $this->createProcess('create-project', $args, $options); 59 | 60 | $helper->run($output, $process, 'Command failed.', function ($type, $data) use ($output) { 61 | $output->write($data, false); 62 | }); 63 | 64 | return $process->getExitCode(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Command/RemoveCommand.php: -------------------------------------------------------------------------------- 1 | setName('remove'); 22 | $this->setDescription('Gets package name and version by alias, calls `composer remove`'); 23 | $this->addArgument('aliases', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package aliases'); 24 | 25 | $command = new ComposerRemoveCommand; 26 | 27 | foreach ($command->getDefinition()->getOptions() as $option) { 28 | $this->getDefinition()->addOption($option); 29 | } 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | * @codeCoverageIgnore 35 | */ 36 | protected function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $packages = $this->helper->parseArguments($input->getArgument('aliases'), true); 39 | 40 | $options = $this->getOptions($input, $output->isDecorated()); 41 | $helper = $this->getHelper('process'); 42 | $process = $this->createProcess('remove', $packages, $options); 43 | 44 | $helper->run($output, $process, 'Command failed.', function ($type, $data) use ($output) { 45 | $output->write($data, false); 46 | }); 47 | 48 | return $process->getExitCode(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Command/RequireCommand.php: -------------------------------------------------------------------------------- 1 | setName('require'); 22 | $this->setDescription('Gets package name and version by alias, calls `composer require`'); 23 | $this->addArgument('aliases', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package aliases'); 24 | 25 | $command = new ComposerRequireCommand; 26 | 27 | foreach ($command->getDefinition()->getOptions() as $option) { 28 | $this->getDefinition()->addOption($option); 29 | } 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | * @codeCoverageIgnore 35 | */ 36 | protected function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $packages = $this->helper->parseArguments($input->getArgument('aliases')); 39 | 40 | $options = $this->getOptions($input, $output->isDecorated()); 41 | $helper = $this->getHelper('process'); 42 | $process = $this->createProcess('require', $packages, $options); 43 | 44 | $helper->run($output, $process, 'Command failed.', function ($type, $data) use ($output) { 45 | $output->write($data, false); 46 | }); 47 | 48 | return $process->getExitCode(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Command/UnaliasCommand.php: -------------------------------------------------------------------------------- 1 | setName('unalias'); 22 | $this->setDescription('Unset an existing crap alias.'); 23 | $this->addArgument('alias', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package alias(es)'); 24 | $this->addOption( 25 | 'dry-run', 26 | null, 27 | InputOption::VALUE_NONE, 28 | 'Run command without writing to your `crap.json`, useful for testing.' 29 | ); 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | protected function execute(InputInterface $input, OutputInterface $output) 36 | { 37 | $aliases = $input->getArgument('alias'); 38 | 39 | foreach ($aliases as $alias) { 40 | if (!$this->helper->hasAlias($alias)) { 41 | $output->writeln(sprintf( 42 | 'Alias `%s` does not exist. Skipping.', 43 | $alias 44 | )); 45 | continue; 46 | } 47 | 48 | if ($input->getOption('dry-run') !== true) { 49 | $this->helper->unsetAlias($alias); 50 | } 51 | 52 | $output->writeln(sprintf( 53 | 'Alias `%s` successfully removed.', 54 | $alias 55 | )); 56 | } 57 | 58 | return 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Command/UpdateCommand.php: -------------------------------------------------------------------------------- 1 | setName('update'); 22 | $this->setDescription('Gets package name and version by alias, calls `composer update`'); 23 | $this->addArgument('aliases', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package aliases'); 24 | 25 | $command = new ComposerUpdateCommand; 26 | 27 | foreach ($command->getDefinition()->getOptions() as $option) { 28 | $this->getDefinition()->addOption($option); 29 | } 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | * @codeCoverageIgnore 35 | */ 36 | protected function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $packages = $this->helper->parseArguments($input->getArgument('aliases')); 39 | 40 | $options = $this->getOptions($input, $output->isDecorated()); 41 | $helper = $this->getHelper('process'); 42 | $process = $this->createProcess('update', $packages, $options); 43 | 44 | $helper->run($output, $process, 'Command failed.', function ($type, $data) use ($output) { 45 | $output->write($data, false); 46 | }); 47 | 48 | return $process->getExitCode(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Crap.php: -------------------------------------------------------------------------------- 1 | setCatchExceptions(false); 53 | $this->setAutoExit(false); 54 | 55 | set_exception_handler(new ExceptionHandler($output)); 56 | 57 | return $this->doRun($input, $output); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/CrapException.php: -------------------------------------------------------------------------------- 1 | file = $file; 30 | $this->parser = $parser; 31 | } 32 | 33 | /** 34 | * Get JsonFileStore 35 | * 36 | * @return JsonFileStore 37 | */ 38 | public function getFile() 39 | { 40 | return $this->file; 41 | } 42 | 43 | /** 44 | * Set JsonFileStore 45 | * 46 | * @param JsonFileStore $file 47 | */ 48 | public function setFile(JsonFileStore $file) 49 | { 50 | $this->file = $file; 51 | } 52 | 53 | /** 54 | * Get VersionParser 55 | * 56 | * @return VersionParser 57 | */ 58 | public function getVersionParser() 59 | { 60 | return $this->parser; 61 | } 62 | 63 | /** 64 | * Get aliases from FileStore 65 | * 66 | * @return array 67 | */ 68 | public function getAliases() 69 | { 70 | return $this->file->keys(); 71 | } 72 | 73 | /** 74 | * Get alias from FileStore 75 | * 76 | * @param string $alias 77 | * @throws Exception If alias is not found in JSON file. 78 | * @return string 79 | */ 80 | public function getAlias($alias) 81 | { 82 | return $this->file->get($alias); 83 | } 84 | 85 | /** 86 | * Set alias in FileStore 87 | * 88 | * @param string $alias 89 | * @param $package 90 | * @return void 91 | */ 92 | public function setAlias($alias, $package) 93 | { 94 | $this->file->set($alias, $package); 95 | $this->file->sort(); 96 | } 97 | 98 | /** 99 | * Remove alias from FileStore 100 | * 101 | * @param string $alias 102 | * @return bool 103 | */ 104 | public function unsetAlias($alias) 105 | { 106 | return $this->file->remove($alias); 107 | } 108 | 109 | /** 110 | * Check alias exists in FileStore 111 | * 112 | * @param string $alias 113 | * @return bool 114 | */ 115 | public function hasAlias($alias) 116 | { 117 | return $this->file->exists($alias); 118 | } 119 | 120 | /** 121 | * Check that alias is valid 122 | * 123 | * @param string $alias 124 | * @return bool 125 | */ 126 | public function validateAlias($alias) 127 | { 128 | return (bool) preg_match('{^[a-z0-9_.-]+$}', $alias); 129 | } 130 | 131 | /** 132 | * Make sure provided package string is valid 133 | * 134 | * @param string $input 135 | * @return boolean 136 | */ 137 | public function validatePackage($input) 138 | { 139 | if (empty($input)) { 140 | return false; 141 | } 142 | 143 | list($package, $version) = $this->parsePackageToArray($input); 144 | 145 | if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}', $package)) { 146 | return false; 147 | } 148 | 149 | if ($version !== null) { 150 | try { 151 | $this->parser->parseConstraints($version); 152 | } catch (Exception $e) { 153 | return false; 154 | } 155 | } 156 | 157 | return true; 158 | } 159 | 160 | /** 161 | * Parse package argument array to string 162 | * Version is null if no constraint is provided 163 | * 164 | * @param array $arguments 165 | * @param bool $excludeVersions 166 | * @return array 167 | * 168 | * @throws CrapException If the alias is not set with crap 169 | */ 170 | public function parseArguments(array $arguments, $excludeVersions = false) 171 | { 172 | return array_map(function ($arg) use ($excludeVersions) { 173 | if ($this->validatePackage($arg)) { 174 | return $arg; 175 | } 176 | 177 | list($alias, $argVersion) = $this->parsePackageToArray($arg); 178 | 179 | if (!$this->hasAlias($alias)) { 180 | throw CrapException::create('No record found for alias `%s`.', $alias); 181 | } 182 | 183 | list($package, $packageVersion) = $this->parsePackageToArray($this->getAlias($alias)); 184 | 185 | $version = null; 186 | 187 | if (!$excludeVersions) { 188 | $version = $argVersion ?: $packageVersion; 189 | } 190 | 191 | return (is_null($version)) ? $package : sprintf('%s:%s', $package, $version); 192 | }, $arguments); 193 | } 194 | 195 | /** 196 | * Parse package argument string to array[package, version] 197 | * Version is null if no constraint is provided 198 | * 199 | * @param string $input 200 | * @return array 201 | */ 202 | protected function parsePackageToArray($input) 203 | { 204 | $result = $this->parser->parseNameVersionPairs([$input])[0]; 205 | 206 | if (!isset($result['version'])) { 207 | return [$result['name'], null]; 208 | } 209 | 210 | return [$result['name'], $result['version']]; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/CrapProvider.php: -------------------------------------------------------------------------------- 1 | composerHome = $composerHome; 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | public function register(ContainerFactory $factory) 36 | { 37 | $composerHome = $this->composerHome; 38 | 39 | $factory->register(ArgvInput::class); 40 | $factory->register(ConsoleOutput::class); 41 | 42 | $factory->alias(InputInterface::class, ArgvInput::class); 43 | $factory->alias(OutputInterface::class, ConsoleOutput::class); 44 | 45 | $factory->configure( 46 | ConsoleOutput::class, 47 | function (ConsoleOutput $output) { 48 | $output->getFormatter()->setStyle('error', new OutputFormatterStyle('red', null, [])); 49 | $output->getFormatter()->setStyle('success', new OutputFormatterStyle('green', null, [])); 50 | return $output; 51 | } 52 | ); 53 | 54 | $factory->set( 55 | Helper\HelperSet::class, 56 | new Helper\HelperSet([ 57 | new Helper\FormatterHelper(), 58 | new Helper\DebugFormatterHelper(), 59 | new Helper\ProcessHelper(), 60 | new Helper\QuestionHelper(), 61 | ]) 62 | ); 63 | 64 | $factory->register(VersionParser::class); 65 | $factory->register(CrapHelper::class); 66 | $factory->register(Crap::class); 67 | 68 | $factory->register( 69 | JsonFileStore::class, 70 | function () use ($composerHome) { 71 | $file = sprintf('%s/%s', $composerHome, Crap::FILENAME); 72 | $flags = JsonFileStore::NO_SERIALIZE_STRINGS 73 | | JsonFileStore::PRETTY_PRINT 74 | | JsonFileStore::NO_ESCAPE_SLASH; 75 | return new JsonFileStore($file, $flags); 76 | } 77 | ); 78 | 79 | $factory->configure( 80 | Crap::class, 81 | function (Crap $crap, CrapHelper $helper) { 82 | $crap->addCommands([ 83 | new Command\ListAliasesCommand($helper), 84 | new Command\AliasCommand($helper), 85 | new Command\InfoCommand($helper), 86 | new Command\UnaliasCommand($helper), 87 | new Command\RequireCommand($helper), 88 | new Command\UpdateCommand($helper), 89 | new Command\RemoveCommand($helper), 90 | new Command\ProjectCommand($helper), 91 | ]); 92 | return $crap; 93 | } 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/ExceptionHandler.php: -------------------------------------------------------------------------------- 1 | output = $output; 28 | } 29 | 30 | /** 31 | * Handle the Exception/Throwable 32 | * We avoid type hinting to support both 5.6 & 7.x 33 | * 34 | * @param Exception|Throwable $e 35 | * 36 | * @param bool $exit 37 | * @return int|void 38 | */ 39 | public function __invoke($e, $exit = true) 40 | { 41 | $this->output->writeln(sprintf('%s', $e->getMessage())); 42 | 43 | return $exit ? exit(1) : 1; 44 | } 45 | } 46 | --------------------------------------------------------------------------------