├── VERSION ├── tests ├── fixtures │ ├── twig │ │ ├── test.twig │ │ ├── docs │ │ │ └── docs.twig │ │ ├── example.twig │ │ └── tests │ │ │ └── test.twig │ ├── example.txt │ ├── example.md │ ├── example.json │ └── expected_example.md ├── bootstrap.php ├── Util │ ├── ArrTest.php │ ├── InflectorTest.php │ ├── GitTest.php │ ├── MetadataTest.php │ ├── ComposerTest.php │ └── PathTest.php └── Generator │ ├── CollisionHandlerTest.php │ └── TwigGeneratorTest.php ├── resources ├── .env.example.twig ├── CHANGELOG.md.twig ├── tests │ ├── bootstrap.php.twig │ └── test.twig ├── package.json.twig ├── .gitignore.twig ├── .editorconfig.twig ├── docs │ ├── PULL_REQUEST_TEMPLATE.md.twig │ ├── ISSUE_TEMPLATE.md.twig │ └── docs.twig ├── phpunit.xml.dist.twig ├── .travis.yml.twig ├── README.md.twig ├── composer.json.twig ├── CONTRIBUTING.md.twig └── LICENSE.twig ├── .github ├── FUNDING.yml └── dependabot.yml ├── .gitignore ├── .editorconfig ├── .travis.yml ├── docs ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── phint.json ├── phpunit.xml.dist ├── src ├── Generator │ ├── CollisionHandlerInterface.php │ ├── GeneratorInterface.php │ ├── CollisionHandler.php │ └── TwigGenerator.php ├── Util │ ├── Arr.php │ ├── Inflector.php │ ├── Git.php │ ├── Composer.php │ ├── Executable.php │ ├── Metadata.php │ └── Path.php └── Console │ ├── ExportCommand.php │ ├── DocsCommand.php │ ├── UpdateCommand.php │ ├── TestCommand.php │ ├── BaseCommand.php │ └── InitCommand.php ├── box.json ├── LICENSE ├── CONTRIBUTING.md ├── composer.json ├── bin └── phint ├── README.md └── CHANGELOG.md /VERSION: -------------------------------------------------------------------------------- 1 | 0.6.1 2 | -------------------------------------------------------------------------------- /tests/fixtures/twig/test.twig: -------------------------------------------------------------------------------- 1 | {{ string }} 2 | -------------------------------------------------------------------------------- /resources/.env.example.twig: -------------------------------------------------------------------------------- 1 | APP_ENV=production 2 | -------------------------------------------------------------------------------- /tests/fixtures/example.txt: -------------------------------------------------------------------------------- 1 | This is example text 2 | -------------------------------------------------------------------------------- /tests/fixtures/twig/docs/docs.twig: -------------------------------------------------------------------------------- 1 | {{ string }} 2 | -------------------------------------------------------------------------------- /tests/fixtures/twig/example.twig: -------------------------------------------------------------------------------- 1 | {{ string }} 2 | -------------------------------------------------------------------------------- /tests/fixtures/twig/tests/test.twig: -------------------------------------------------------------------------------- 1 | {{ string }} 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: adhocore 2 | custom: ['https://paypal.me/ji10'] 3 | -------------------------------------------------------------------------------- /resources/CHANGELOG.md.twig: -------------------------------------------------------------------------------- 1 | ## 0.0.0 {{ gmdate() }} UTC 2 | 3 | - init {{type}} 4 | -------------------------------------------------------------------------------- /resources/tests/bootstrap.php.twig: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | require_once __DIR__ . '/../vendor/autoload.php'; 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; http://editorconfig.org 2 | ; 3 | ; Sublime: https://github.com/sindresorhus/editorconfig-sublime 4 | ; Phpstorm: https://plugins.jetbrains.com/plugin/7294-editorconfig 5 | 6 | root = true 7 | 8 | [*] 9 | indent_style = space 10 | indent_size = 2 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [{*.md,*.php,composer.json,composer.lock}] 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /resources/.editorconfig.twig: -------------------------------------------------------------------------------- 1 | ; http://editorconfig.org 2 | ; 3 | ; Sublime: https://github.com/sindresorhus/editorconfig-sublime 4 | ; Phpstorm: https://plugins.jetbrains.com/plugin/7294-editorconfig 5 | 6 | root = true 7 | 8 | [*] 9 | indent_style = space 10 | indent_size = 4 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [{*.js,*.css,*.scss,*.html}] 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | cache: 2 | directories: 3 | - $HOME/.composer/cache/files 4 | 5 | language: php 6 | 7 | php: 8 | - 7.1 9 | - 7.2 10 | - 7.3 11 | - 7.4 12 | 13 | install: 14 | - composer install --prefer-dist 15 | 16 | before_script: 17 | - for P in src tests; do find $P -type f -name '*.php' -exec php -l {} \;; done 18 | 19 | script: 20 | - composer test:cov 21 | 22 | after_success: 23 | - bash <(curl -s https://codecov.io/bash) 24 | -------------------------------------------------------------------------------- /docs/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | **This is a** 4 | 5 | 6 | - [ ] New feature 7 | - [ ] Fix for ... 8 | - [ ] General improvement 9 | - [ ] Backward incompatible change 10 | 11 | It closes # 12 | 13 | ## Describe the change 14 | 15 | ... 16 | 17 | ## How to use it 18 | 19 | ... 20 | 21 | #### Sample code 22 | 23 | ```php 24 | 25 | ``` 26 | -------------------------------------------------------------------------------- /resources/docs/PULL_REQUEST_TEMPLATE.md.twig: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | **This is a** 4 | 5 | 6 | - [ ] New feature 7 | - [ ] Fix for ... 8 | - [ ] General improvement 9 | - [ ] Backward incompatible change 10 | 11 | It closes # 12 | 13 | ## Describe the change 14 | 15 | ... 16 | 17 | ## How to use it 18 | 19 | ... 20 | 21 | #### Sample code 22 | 23 | ```php 24 | 25 | ``` 26 | -------------------------------------------------------------------------------- /phint.json: -------------------------------------------------------------------------------- 1 | /* 2 | * Surprised to see JSON with comment? 3 | * check: https://github.com/adhocore/json-comment 4 | */ 5 | { 6 | // library 7 | "type": "library", 8 | // root namespace (project name is auto appended). auto inflected to StuldyCas 9 | "namespace": "Ahc", 10 | // vendor's vcs username (github handle) 11 | "username": "adhocore", 12 | // vendor's name 13 | "name": "Jitendra Adhikari", 14 | // vendor's email 15 | "email": "jiten.adhikary@gmail.com", 16 | // min php version 17 | "php": "7.0", 18 | // license (mit/apache/gnulgpl/bsd) 19 | "license": "m" 20 | } 21 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | ./src 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Generator/CollisionHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Generator; 13 | 14 | interface CollisionHandlerInterface 15 | { 16 | /** 17 | * Skip/override/merge existing files in targetFile. 18 | * 19 | * @param string $targetFile 20 | * @param string $newContent 21 | * @param array $parameters 22 | * 23 | * @return bool 24 | */ 25 | public function handle(string $targetFile, string $newContent, array $parameters = null): bool; 26 | } 27 | -------------------------------------------------------------------------------- /resources/phpunit.xml.dist.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | ./src 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "chmod": "0755", 3 | 4 | "check-requirements": false, 5 | 6 | "banner": [ 7 | "Phint is a PHP project scaffolding tool" 8 | ], 9 | 10 | "files": [ 11 | "vendor/twig/twig/lib/Twig/Test.php", 12 | "vendor/twig/twig/lib/Twig/SimpleTest.php", 13 | "vendor/twig/twig/src/TwigTest.php" 14 | ], 15 | 16 | "files-bin": [ 17 | "VERSION", 18 | "resources/.editorconfig.twig", 19 | "resources/.env.example.twig", 20 | "resources/.gitignore.twig", 21 | "resources/.travis.yml.twig" 22 | ], 23 | 24 | "directories-bin": [ 25 | "resources" 26 | ], 27 | 28 | "compression": "GZ", 29 | "datetime": "release-date" 30 | } 31 | -------------------------------------------------------------------------------- /src/Generator/GeneratorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Generator; 13 | 14 | interface GeneratorInterface 15 | { 16 | /** 17 | * Generate basic project files into target path using given parameters. 18 | * 19 | * @param string $targetPath 20 | * @param array $parameters 21 | * @param CollisionHandlerInterface $handler Optional, if not provided files are overwritten. 22 | * 23 | * @return int The count of generated files. 24 | */ 25 | public function generate(string $targetPath, array $parameters, CollisionHandlerInterface $handler = null): int; 26 | } 27 | -------------------------------------------------------------------------------- /resources/.travis.yml.twig: -------------------------------------------------------------------------------- 1 | cache: 2 | directories: 3 | - $HOME/.composer/cache/files 4 | 5 | language: php 6 | 7 | php: 8 | {% if php <= 5.4 %} 9 | - 5.4 10 | {% endif %} 11 | {% if php <= 5.5 %} 12 | - 5.5 13 | {% endif %} 14 | {% if php <= 5.6 %} 15 | - 5.6 16 | {% endif %} 17 | {% if php <= 7.0 %} 18 | - 7.0 19 | {% endif %} 20 | {% if php <= 7.1 %} 21 | - 7.1 22 | {% endif %} 23 | {% if php <= 7.2 %} 24 | - 7.2 25 | {% endif %} 26 | {% if php <= 7.3 %} 27 | - 7.3 28 | {% endif %} 29 | - nightly 30 | 31 | matrix: 32 | allow_failures: 33 | - php: nightly 34 | 35 | install: 36 | - composer install --prefer-dist 37 | 38 | before_script: 39 | - for P in src tests; do find $P -type f -name '*.php' -exec php -l {} \;; done 40 | 41 | script: 42 | - composer test:cov 43 | 44 | {% if codecov %} 45 | after_success: 46 | - bash <(curl -s https://codecov.io/bash) 47 | {% endif %} 48 | -------------------------------------------------------------------------------- /docs/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | **This is a** 4 | 5 | 6 | - [ ] Bug/Issue 7 | - [ ] Feature request 8 | - [ ] General stuff 9 | 10 | 37 | 38 | 43 | 44 | 50 | -------------------------------------------------------------------------------- /resources/docs/ISSUE_TEMPLATE.md.twig: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | **This is a** 4 | 5 | 6 | - [ ] Bug/Issue 7 | - [ ] Feature request 8 | - [ ] General stuff 9 | 10 | 37 | 38 | 43 | 44 | 50 | -------------------------------------------------------------------------------- /src/Util/Arr.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Util; 13 | 14 | class Arr 15 | { 16 | /** 17 | * @see http://php.net/array_merge_recursive#92195 18 | * 19 | * @param array $array1 20 | * @param array $array2 21 | * 22 | * @return array 23 | */ 24 | public static function mergeRecursive(array $array1, array $array2) 25 | { 26 | $merged = $array1; 27 | 28 | foreach ($array2 as $key => &$value) { 29 | if (\is_array($value) && isset($merged[$key]) && \is_array($merged[$key])) { 30 | $merged[$key] = self::mergeRecursive($merged[$key], $value); 31 | } else { 32 | $merged[$key] = $value; 33 | } 34 | } 35 | 36 | return $merged; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Util/ArrTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Test; 13 | 14 | use Ahc\Phint\Util\Arr; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class ArrTest extends TestCase 18 | { 19 | public function arrProvider() 20 | { 21 | return [ 22 | [[6, 7, 8], [2, 3, [4, 5]], [2, 3, [4, 5]]], 23 | [[2, 3, [4, 5]], [2, 3, [4, 5]], [2, 3, [4, 5]]], 24 | [['1', 2, 'a' => 3], ['2', 3, 'a' => 4], ['2', 3, 'a' => 4]], 25 | [['a' => 3, 'b' => [1, 2]], ['a' => [4], 'b' => [0]], ['a' => [4], 'b' => [0, 2]]], 26 | ]; 27 | } 28 | 29 | /** @dataProvider arrProvider */ 30 | public function testMergeRecursive($array1, $array2, $expectedArray) 31 | { 32 | $this->assertEquals($expectedArray, Arr::mergeRecursive($array1, $array2)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Util/InflectorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Test; 13 | 14 | use Ahc\Phint\Util\Inflector; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class InflectorTest extends TestCase 18 | { 19 | public function testStuldyCase() 20 | { 21 | $inflector = new Inflector; 22 | 23 | $this->assertEquals('ThisWillBeUcwordString', $inflector->stuldyCase('this-will-be-ucword-string')); 24 | } 25 | 26 | public function testSnakeCase() 27 | { 28 | $inflector = new Inflector; 29 | 30 | $this->assertEquals('this_will_be_snake_case_string', $inflector->snakeCase('this will be snake case string')); 31 | } 32 | 33 | public function testWords() 34 | { 35 | $inflector = new Inflector; 36 | 37 | $this->assertEquals('This Will Be Ucword String', $inflector->words('this will be ucword string')); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /resources/docs/docs.twig: -------------------------------------------------------------------------------- 1 | 2 | {% for class in docsMetadata %} 3 | ## {{class.name}} 4 | 5 | ```php 6 | use {{class.classFqcn}}; 7 | ``` 8 | {% if class.title %} 9 | 10 | > {{class.title}} 11 | {% endif %} 12 | {% if class.texts %} 13 | 14 | {{class.texts|join("\n")}} 15 | {% endif %} 16 | {% for name, method in class.methods if method.isPublic and not method.maybeMagic %} 17 | 18 | ### {{name}}() 19 | {% if method.title %} 20 | 21 | > {{method.title}} 22 | {% endif %} 23 | 24 |
Details … 25 |
26 | 27 | ```php 28 | {% set params = call('array_column', method.params, 0) %} 29 | {{method.isStatic ? '::' : ''}}{{name}}({{params|join(', ')}}){{method.return ? ': ' ~ method.return[0] : ''}} 30 | ``` 31 | {% if method.texts %} 32 | 33 | {{method.texts|join("\n\n")|replace({'':"```php", '':"```"})|raw|nl2br}} 34 | {% endif %} 35 | {% if method.throws %} 36 | 37 | > _Throws_ **{{method.throws[0]}}** {{ method.throws[1] ? '_' ~ method.throws[1] ~ '_' : '' }} 38 | {% endif %} 39 | 40 |
41 |
42 | {% endfor %} 43 | 44 | **[⬆ back to top](#the-top)** 45 | 46 | {% endfor %} 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2020 Jitendra Adhikari 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 | -------------------------------------------------------------------------------- /src/Util/Inflector.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Util; 13 | 14 | class Inflector 15 | { 16 | /** 17 | * StuldyCase. 18 | * 19 | * @param string $path 20 | * 21 | * @return string 22 | */ 23 | public function stuldyCase(string $string): string 24 | { 25 | return str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $string))); 26 | } 27 | 28 | /** 29 | * Separate words. 30 | * 31 | * @param string $path 32 | * 33 | * @return string 34 | */ 35 | public function words(string $string): string 36 | { 37 | return ucwords(str_replace(['-', '_'], ' ', $string)); 38 | } 39 | 40 | /** 41 | * Snakeize. 42 | * 43 | * @param string $path 44 | * 45 | * @return string 46 | */ 47 | public function snakeCase(string $string): string 48 | { 49 | $string = \str_replace([' ', '-'], '_', $string); 50 | 51 | return \ltrim(\strtolower(\preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '_$0', $string)), '_'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Util/GitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Test; 13 | 14 | use Ahc\Phint\Util\Git; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class GitTest extends TestCase 18 | { 19 | public function testGetConfig() 20 | { 21 | $git = new Git; 22 | $conf = $git->getConfig(); 23 | 24 | $this->assertArrayHasKey('remote.origin.url', $conf); 25 | $this->assertContains('adhocore/phint.git', $conf['remote.origin.url']); 26 | } 27 | 28 | public function testGetConfigOnSpecificKey() 29 | { 30 | $git = new Git; 31 | 32 | $this->assertSame('false', $git->getConfig('core.bare')); 33 | } 34 | 35 | public function testInit() 36 | { 37 | $git = new Git(); 38 | 39 | $this->assertInstanceOf(Git::class, $git->init()); 40 | $this->assertTrue($git->successful()); 41 | } 42 | 43 | public function testAddRemote() 44 | { 45 | $git = new Git(); 46 | 47 | $this->assertInstanceOf(Git::class, $git->addRemote('adhocore', 'phint')); 48 | $this->assertFalse($git->successful()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | 3 | ### Before you start 4 | 5 | Please be considerate that: 6 | 7 | - Commit messages should follow angular standard 8 | 9 | ### Setting up 10 | 11 | You may need to fork this project in [GitHub](https://github.com/adhocore/phint). 12 | 13 | ```sh 14 | git clone git@github.com:adhocore/phint.git 15 | 16 | # OR if you have a fork 17 | # git clone git@github.com:/phint.git 18 | 19 | cd phint 20 | 21 | # Create a new branch 22 | git checkout -b $branch_name 23 | 24 | # Install deps 25 | composer install -o 26 | ``` 27 | 28 | ### Moving forward 29 | 30 | ```sh 31 | # Open phint in IDE 32 | subl phint 33 | 34 | # ... and do the needful 35 | 36 | # Optionally run the lint 37 | for P in src tests; do find $P -type f -name '*.php' -exec php -l {} \;; done 38 | 39 | # ... and phpcs fixer or stuffs like that! 40 | 41 | # Run tests 42 | vendor/bin/phpunit --coverage-text 43 | ``` 44 | 45 | ### Finalizing 46 | 47 | Everything looking good? 48 | 49 | ```sh 50 | # Commit your stuffs 51 | git add $file ...$files 52 | git commit -m "..." 53 | 54 | # Push 'em 55 | git push origin HEAD 56 | ``` 57 | 58 | Now goto [GitHub](https://github.com/adhocore/phint/compare?expand=1), select your branch and create PR. 59 | 60 | ### Getting PR merged 61 | 62 | You have to wait. You have to address change requests. 63 | 64 | Thank you for contribution! 65 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adhocore/phint", 3 | "description": "Initializes new PHP project with sane defaults using templates", 4 | "type": "library", 5 | "keywords": [ 6 | "php-project-scaffolding", "php-project-init", "bootstrap", "template", "init-project", "phint" 7 | ], 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Jitendra Adhikari", 12 | "email": "jiten.adhikary@gmail.com" 13 | } 14 | ], 15 | "autoload": { 16 | "psr-4": { 17 | "Ahc\\Phint\\": "src/" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "Ahc\\Phint\\Test\\": "tests/" 23 | } 24 | }, 25 | "require": { 26 | "php": ">=7.1", 27 | "adhocore/cli": "^0.8.1", 28 | "adhocore/json-comment": "^0.1", 29 | "crazyfactory/docblocks": "^2.2", 30 | "symfony/finder": "^3.3.0", 31 | "twig/twig": "^2.4.0" 32 | }, 33 | "require-dev": { 34 | "phpunit/phpunit": "^6.0" 35 | }, 36 | "bin": ["bin/phint"], 37 | "config": { 38 | "optimize-autoloader": true, 39 | "preferred-install": { 40 | "*": "dist" 41 | } 42 | }, 43 | "scripts": { 44 | "test": "phpunit", 45 | "test:cov": "phpunit --coverage-text --coverage-clover coverage.xml --coverage-html vendor/cov" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bin/phint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new Ahc\Phint\Console\InitCommand, 'i'); 43 | $app->add(new Ahc\Phint\Console\UpdateCommand, 'u'); 44 | $app->add(new Ahc\Phint\Console\TestCommand, 't'); 45 | $app->add(new Ahc\Phint\Console\DocsCommand, 'd'); 46 | $app->add(new Ahc\Phint\Console\ExportCommand, 'e'); 47 | 48 | $app->logo($logo)->handle($_SERVER['argv']); 49 | -------------------------------------------------------------------------------- /src/Generator/CollisionHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Generator; 13 | 14 | use Ahc\Phint\Util\Arr; 15 | use Ahc\Phint\Util\Path; 16 | 17 | class CollisionHandler implements CollisionHandlerInterface 18 | { 19 | public function __construct(Path $pathUtil = null) 20 | { 21 | $this->pathUtil = $pathUtil ?: new Path; 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function handle(string $targetFile, string $newContent, array $parameters = null): bool 28 | { 29 | switch ($this->pathUtil->getExtension($targetFile)) { 30 | case 'json': 31 | return $this->mergeJson($targetFile, $newContent); 32 | 33 | case 'md': 34 | return $this->appendFile($targetFile, "\n---\n" . $newContent); 35 | } 36 | 37 | return false; 38 | } 39 | 40 | protected function mergeJson(string $targetFile, string $newContent): bool 41 | { 42 | $oldJson = $this->pathUtil->readAsJson($targetFile); 43 | $newJson = \json_decode($newContent, true); 44 | $merged = Arr::mergeRecursive($oldJson, $newJson); 45 | 46 | return $this->pathUtil->writeFile($targetFile, $merged); 47 | } 48 | 49 | protected function appendFile(string $targetFile, string $newContent): bool 50 | { 51 | return $this->pathUtil->writeFile($targetFile, $newContent, \FILE_APPEND); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /resources/README.md.twig: -------------------------------------------------------------------------------- 1 | ## {{username}}/{{package}} 2 | 3 | [![Latest Version](https://img.shields.io/github/release/{{username}}/{{project}}.svg?style=flat-square)](https://github.com/{{username}}/{{project}}/releases) 4 | {% if travis %} 5 | [![Travis Build](https://img.shields.io/travis/com/{{username}}/{{project}}.svg?branch=master&style=flat-square)](https://travis-ci.com/{{username}}/{{project}}?branch=master) 6 | {% endif %} 7 | {% if scrutinizer %} 8 | [![Scrutinizer CI](https://img.shields.io/scrutinizer/g/{{username}}/{{project}}.svg?style=flat-square)](https://scrutinizer-ci.com/g/{{username}}/{{project}}/?branch=master) 9 | {% endif %} 10 | {% if codecov %} 11 | [![Codecov branch](https://img.shields.io/codecov/c/github/{{username}}/{{project}}/master.svg?style=flat-square)](https://codecov.io/gh/{{username}}/{{project}}) 12 | {% endif %} 13 | {% if styleci %} 14 | [![StyleCI](https://styleci.io/repos/{styleci}/shield)](https://styleci.io/repos/{styleci}) 15 | {% endif %} 16 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](./LICENSE) 17 | 18 | 19 | ## Installation 20 | ```bash 21 | composer require {{username}}/{{package}} 22 | ``` 23 | 24 | ## Usage 25 | ```php 26 | use {{namespace}}; 27 | 28 | ``` 29 | 30 | ## API 31 | 32 | 33 | 34 | 35 | ## Contributing 36 | 37 | Please check [the guide](./CONTRIBUTING.md) 38 | 39 | ## LICENSE 40 | 41 | {% set L = {'m': 'MIT', 'g': 'GNU LGPL', 'a': 'Apache 2', 'b': 'BSD Simple', 'i': 'ISC'} %} 42 | > © [{{L[license]}}](./LICENSE) | {{year}}, {{name}} 43 | 44 | ### Credits 45 | 46 | This project is bootstrapped by [phint](https://github.com/adhocore/phint). 47 | -------------------------------------------------------------------------------- /resources/composer.json.twig: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{username}}/{{package}}", 3 | "description": "{{descr}}", 4 | "type": "{{type}}", 5 | "keywords": {{keywords|json_encode|raw}}, 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "{{name}}", 10 | "email": "{{email}}" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "{{namespace|replace({'\\': '\\\\'})}}\\": "src/" 16 | }, 17 | "files": [] 18 | }, 19 | "autoload-dev": { 20 | "psr-4": { 21 | "{{namespace|replace({'\\': '\\\\'})}}\\Test\\": "tests/" 22 | } 23 | }, 24 | "require": { 25 | "php": ">={{php}}.0"{% for pkg in req %}, 26 | "{{pkg.name}}": "{{pkg.version}}"{% endfor %} 27 | 28 | }, 29 | "require-dev": { 30 | "phpunit/phpunit": "^5.7 || ^6.5" || ^7.5{% for pkg in dev %}, 31 | "{{pkg.name}}": "{{pkg.version}}"{% endfor %} 32 | 33 | }, 34 | {% if bin %} 35 | "bin": [ 36 | {% set binc = bin|length - 1 %} 37 | {% for i, bn in bin %} 38 | "{{bn}}"{{ i < binc ? ',' : '' }} 39 | {% endfor %} 40 | ], 41 | {% endif %} 42 | "config": { 43 | "optimize-autoloader": true, 44 | "preferred-install": { 45 | "*": "dist" 46 | } 47 | }, 48 | "scripts": { 49 | "post-root-package-install": [ 50 | {%if type == 'project' %} 51 | "@env" 52 | {% endif %} 53 | ], 54 | {%if type == 'project' %} 55 | "@env": "php -r \"is_file('.env') || copy('.env.example', '.env');\"", 56 | {% endif %} 57 | "test": "phpunit", 58 | "test:cov": "phpunit --coverage-text --coverage-clover coverage.xml --coverage-html vendor/cov" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Util/Git.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Util; 13 | 14 | class Git extends Executable 15 | { 16 | /** @var array */ 17 | protected $gitConfig; 18 | 19 | /** @var string The binary executable */ 20 | protected $binary = 'git'; 21 | 22 | /** 23 | * Gets git config. 24 | * 25 | * @param string|null $key 26 | * 27 | * @return mixed 28 | */ 29 | public function getConfig($key = null) 30 | { 31 | if (null === $this->gitConfig) { 32 | $this->loadConfig(); 33 | } 34 | 35 | if (null === $key) { 36 | return $this->gitConfig; 37 | } 38 | 39 | return isset($this->gitConfig[$key]) ? $this->gitConfig[$key] : null; 40 | } 41 | 42 | protected function loadConfig() 43 | { 44 | $gitConfig = []; 45 | 46 | $output = $this->runCommand('config --list'); 47 | $output = explode("\n", str_replace(["\r\n", "\r"], "\n", $output)); 48 | 49 | foreach ($output as $config) { 50 | $parts = array_map('trim', explode('=', $config, 2)) + ['', '']; 51 | 52 | $gitConfig[$parts[0]] = $parts[1]; 53 | } 54 | 55 | $this->gitConfig = $gitConfig; 56 | } 57 | 58 | public function init() 59 | { 60 | $this->runCommand('init'); 61 | 62 | return $this; 63 | } 64 | 65 | public function addRemote($username, $project) 66 | { 67 | $this->runCommand(sprintf('remote add origin git@github.com:%s/%s.git', $username, $project)); 68 | 69 | return $this; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Generator/CollisionHandlerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Test; 13 | 14 | use Ahc\Phint\Generator\CollisionHandler; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class CollisionHandlerTest extends TestCase 18 | { 19 | public function testConstructor() 20 | { 21 | $this->assertInstanceOf(CollisionHandler::class, new CollisionHandler()); 22 | } 23 | 24 | public function testHandleOnJsonFile() 25 | { 26 | $targetjsonFile = __DIR__ . '/../fixtures/example.json'; 27 | $collisionHandler = new CollisionHandler(); 28 | $collisionHandler->handle($targetjsonFile, \json_encode(['key' => 'value'])); 29 | 30 | $content = \json_decode(\file_get_contents($targetjsonFile), true); 31 | 32 | $this->assertArrayHasKey('type', $content); 33 | $this->assertArrayHasKey('key', $content); 34 | } 35 | 36 | public function testHandleOnMarkdownFile() 37 | { 38 | $targetjsonFile = __DIR__ . '/../fixtures/example.md'; 39 | $expectedjsonFile = __DIR__ . '/../fixtures/expected_example.md'; 40 | $collisionHandler = new CollisionHandler(); 41 | $collisionHandler->handle($targetjsonFile, "key\nvalue\n"); 42 | 43 | $content = \file_get_contents($targetjsonFile); 44 | $expectedContent = \file_get_contents($expectedjsonFile); 45 | 46 | $this->assertEquals($expectedContent, $content); 47 | } 48 | 49 | public function testHandleOnUnsupportedFile() 50 | { 51 | $targetjsonFile = __DIR__ . '/../fixtures/example.txt'; 52 | $collisionHandler = new CollisionHandler(); 53 | 54 | $this->assertFalse($collisionHandler->handle($targetjsonFile, "key\nvalue\n")); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Util/MetadataTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Test; 13 | 14 | use Ahc\Phint\Util\Metadata; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class MetadataTest extends TestCase 18 | { 19 | public function testForClass() 20 | { 21 | $metaData = new Metadata; 22 | $result = $metaData->forClass(Metadata::class); 23 | 24 | $this->assertArraySubset([ 25 | 'namespace' => 'Ahc\Phint\Util', 26 | 'classFqcn' => 'Ahc\Phint\Util\Metadata', 27 | 'name' => 'Metadata', 28 | 'className' => 'Metadata', 29 | 'isTrait' => false, 30 | 'isAbstract' => false, 31 | 'isInterface' => false, 32 | 'newable' => true, 33 | 'title' => null, 34 | 'texts' => [], 35 | 'methods' => [], 36 | 'name' => 'Metadata', 37 | ], $result); 38 | } 39 | 40 | public function testForMethod() 41 | { 42 | $metaData = new Metadata; 43 | $result = $metaData->forMethod(Metadata::class, 'forClass'); 44 | 45 | $this->assertSame([ 46 | 'name' => 'forClass', 47 | 'inClass' => 'Ahc\Phint\Util\Metadata', 48 | 'isStatic' => false, 49 | 'isFinal' => false, 50 | 'isPublic' => true, 51 | 'isAbstract' => false, 52 | 'maybeMagic' => false, 53 | 'throws' => [], 54 | 'title' => null, 55 | 'texts' => [], 56 | 'params' => [ 57 | ['string $classFqcn', ''], 58 | ], 59 | 'return' => ['array', ''], 60 | ], $result); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /resources/CONTRIBUTING.md.twig: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | 3 | ### Before you start 4 | 5 | 6 | 7 | 8 | ### Setting up 9 | 10 | You may need to fork this project in [GitHub](https://github.com/{{username}}/{{project}}). 11 | 12 | ```sh 13 | git clone git@github.com:{{username}}/{{project}}.git 14 | 15 | # OR if you have a fork 16 | git clone git@github.com:/{{project}}.git 17 | 18 | # You may also add upstream 19 | git remote add upstream https://github.com/{{username}}/{{project}}.git 20 | 21 | cd {{project}} 22 | 23 | # Create a new branch 24 | git checkout -b $branch_name 25 | 26 | # Install deps 27 | composer install -o 28 | ``` 29 | 30 | ### Moving forward 31 | 32 | ```sh 33 | # Open {{project}} in IDE 34 | subl {{project}} 35 | 36 | # ... and do the needful 37 | 38 | # Optionally run the lint 39 | for P in src tests; do find $P -type f -name '*.php' -exec php -l {} \;; done 40 | 41 | # ... and phpcs fixer or stuffs like that! 42 | 43 | # Run tests 44 | vendor/bin/phpunit --coverage-text 45 | 46 | 47 | # If your feature takes long your dev branch might be out of sync, you may want to 48 | git checkout $branch_name 49 | git pull upstream master # branch could be something else than master 50 | ``` 51 | 52 | ### Finalizing 53 | 54 | Everything looking good? 55 | 56 | ```sh 57 | # Commit your stuffs 58 | git add $file ...$files 59 | git commit -m "..." 60 | 61 | # Push 'em 62 | git push origin HEAD 63 | ``` 64 | 65 | Now goto [GitHub](https://github.com/{{username}}/{{project}}/compare?expand=1), select your branch and create PR. 66 | 67 | ### Getting PR merged 68 | 69 | You have to wait. You have to address change requests. Be patient. 70 | 71 | Thank you for contribution! 72 | 73 | **Lastly** Please be informed that your works will be licensed same as the project [license](./LICENSE) 74 | -------------------------------------------------------------------------------- /src/Util/Composer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Util; 13 | 14 | class Composer extends Executable 15 | { 16 | /** @var array Content of composer.json decoded */ 17 | protected $config = []; 18 | 19 | /** @var string The binary executable */ 20 | protected $binary = 'composer'; 21 | 22 | public function createProject($project, $using) 23 | { 24 | $this->runCommand(sprintf('create-project %s %s', $using, $project)); 25 | 26 | return $this; 27 | } 28 | 29 | public function install() 30 | { 31 | $this->runCommand('install --prefer-dist --optimize-autoloader --no-suggest'); 32 | 33 | return $this; 34 | } 35 | 36 | public function update() 37 | { 38 | $this->runCommand('update --prefer-dist --optimize-autoloader --no-suggest'); 39 | 40 | return $this; 41 | } 42 | 43 | public function dumpAutoload() 44 | { 45 | $this->runCommand('dump-autoload --optimize'); 46 | 47 | return $this; 48 | } 49 | 50 | public function config(string $key, $default = null) 51 | { 52 | if (!$this->config) { 53 | $this->config = (new Path)->readAsJson($this->workDir . '/composer.json'); 54 | } 55 | 56 | $temp = $this->config; 57 | foreach (\explode('.', $key) as $part) { 58 | if (\is_array($temp) && \array_key_exists($part, $temp)) { 59 | $temp = $temp[$part]; 60 | } else { 61 | return $default; 62 | } 63 | } 64 | 65 | return $temp; 66 | } 67 | 68 | public function forceAutoload() 69 | { 70 | $autoloader = $this->workDir . '/vendor/autoload.php'; 71 | 72 | if (\array_search($autoloader, \get_included_files()) === false) { 73 | require_once $autoloader; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /resources/tests/test.twig: -------------------------------------------------------------------------------- 1 | {{'{{className|lcfirst}} = new {{ special ? 'Test' : ''}}{{className}}; 52 | {% endif %} 53 | } 54 | {% endif %} 55 | {% if teardown %} 56 | 57 | public function tearDown() 58 | { 59 | parent::tearDown(); 60 | } 61 | {% endif %} 62 | {% for name, method in methods if method.isPublic and not method.isAbstract and not method.maybeMagic %} 63 | 64 | {% if naming == 't' %} 65 | public function test{{name|ucfirst}}() 66 | {% elseif naming == 'm' %} 67 | public function test_{{name|snake}}() 68 | {% elseif naming == 'i' %} 69 | /** 70 | * @test 71 | */ 72 | public function it_{{name}}() 73 | {% endif %} 74 | { 75 | {% if method.isStatic %} 76 | $actual = {{className}}::{{name}}(); 77 | {% elseif setup %} 78 | $actual = $this->{{className|lcfirst}}->{{name}}(); 79 | {% else %} 80 | ${{className|lcfirst}} = new {{ special ? 'Test' : ''}}{{className}}; 81 | 82 | $actual = ${{className|lcfirst}}->{{name}}(); 83 | {% endif %} 84 | 85 | // $this->assertSame('', $actual); 86 | } 87 | {% endfor %} 88 | } 89 | -------------------------------------------------------------------------------- /src/Console/ExportCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Console; 13 | 14 | class ExportCommand extends BaseCommand 15 | { 16 | /** @var string Command name */ 17 | protected $_name = 'export'; 18 | 19 | /** @var string Command description */ 20 | protected $_desc = 'Export factory templates so you can customize and use them'; 21 | 22 | /** 23 | * Configure the command options/arguments. 24 | * 25 | * @return void 26 | */ 27 | protected function onConstruct() 28 | { 29 | $this 30 | ->option('-t --to ', 'Output directory') 31 | ->option('-o --overwrite', 'Overwrite if target file exists', 'boolval', false) 32 | ->usage( 33 | ' phint export -t . Exports to current dir' . 34 | ' phint e --to ~/myphint Exports to ~/myphint dir' 35 | ); 36 | } 37 | 38 | /** 39 | * Generate test stubs. 40 | * 41 | * @return void 42 | */ 43 | public function execute() 44 | { 45 | $io = $this->app()->io(); 46 | $res = __DIR__ . '/../../resources'; 47 | $dir = $this->_pathUtil->expand($this->to, $this->_workDir); 48 | 49 | $this->_pathUtil->ensureDir($dir); 50 | 51 | $count = 0; 52 | $templates = $this->_pathUtil->findFiles([$res], '.twig', true); 53 | 54 | $io->comment('Exporting ...', true); 55 | 56 | foreach ($templates as $template) { 57 | $target = \str_replace($res, $dir, $template); 58 | 59 | if (\is_file($target) && !$this->overwrite) { 60 | continue; 61 | } 62 | 63 | $content = \file_get_contents($template); 64 | $count += (int) $this->_pathUtil->writeFile($target, $content); 65 | } 66 | 67 | $io->cyan("$count template(s) copied to {$this->to}", true); 68 | if ($count) { 69 | $io->comment('Now you can customize those templates and use like so:', true); 70 | $io->bold(' phint init --template ' . $this->_pathUtil->expand($this->to), true); 71 | } 72 | 73 | $io->ok('Done', true); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Util/ComposerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Test; 13 | 14 | use Ahc\Phint\Util\Composer; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class ComposerTest extends TestCase 18 | { 19 | public function testCreateProjectOnWithoutComposerBinary() 20 | { 21 | $composer = new Composer; 22 | $composer->withWorkDir(sys_get_temp_dir()); 23 | $result = $composer->createProject('project-name', 'app-name'); 24 | 25 | $this->assertInstanceOf(Composer::class, $result); 26 | $this->assertFalse($result->successful()); 27 | } 28 | 29 | public function testInstallOnWithoutComposerBinary() 30 | { 31 | $composer = new Composer; 32 | $composer->withWorkDir(sys_get_temp_dir()); 33 | $result = $composer->install(); 34 | 35 | $this->assertInstanceOf(Composer::class, $result); 36 | $this->assertFalse($result->successful()); 37 | } 38 | 39 | public function testUpdateOnWithoutComposerBinary() 40 | { 41 | $composer = new Composer; 42 | $composer->withWorkDir(sys_get_temp_dir()); 43 | $result = $composer->update(); 44 | 45 | $this->assertInstanceOf(Composer::class, $result); 46 | $this->assertFalse($result->successful()); 47 | } 48 | 49 | public function testDumpAutoloadOnWithoutComposerBinary() 50 | { 51 | $composer = new Composer; 52 | $composer->withWorkDir(sys_get_temp_dir()); 53 | $result = $composer->dumpAutoload(); 54 | 55 | $this->assertInstanceOf(Composer::class, $result); 56 | $this->assertFalse($result->successful()); 57 | } 58 | 59 | public function testConfigOnNullDefaultValue() 60 | { 61 | $composer = new Composer; 62 | $composer->withWorkDir(sys_get_temp_dir()); 63 | 64 | $this->assertNull($composer->config('name')); 65 | } 66 | 67 | public function testConfigOnDefaultValue() 68 | { 69 | copy(__DIR__ . '/../../composer.json', sys_get_temp_dir() . '/composer.json'); 70 | $composer = new Composer; 71 | $composer->withWorkDir(sys_get_temp_dir()); 72 | 73 | $this->assertSame('app/name', $composer->config('name.license', 'app/name')); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Generator/TwigGeneratorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Test; 13 | 14 | use Ahc\Phint\Generator\TwigGenerator; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class TwigGeneratorTest extends TestCase 18 | { 19 | protected $templatePath; 20 | 21 | public function setUp() 22 | { 23 | $path = __DIR__ . '/../fixtures/twig'; 24 | 25 | if (!is_dir($path . '_cache')) { 26 | mkdir($path . '_cache', 0777); 27 | } 28 | 29 | $this->templatePath = realpath($path); 30 | } 31 | 32 | public function testGenerate() 33 | { 34 | $twigGenerator = new TwigGenerator([$this->templatePath], __DIR__ . '/../fixtures/twig_cache'); 35 | 36 | $rand = '_' . rand(); 37 | $this->assertSame(1, $twigGenerator->generate($this->templatePath, ['string' => $rand])); 38 | 39 | $this->assertContains($rand, file_get_contents($this->templatePath . '/example')); 40 | } 41 | 42 | public function testGenerateTestsOnExistedTestTwigFile() 43 | { 44 | $twigGenerator = new TwigGenerator([$this->templatePath], __DIR__ . '/../fixtures/twig_cache'); 45 | $metaData = [ 46 | ['testPath' => __DIR__ . '/../fixtures/twig/test.twig'], 47 | ]; 48 | $parameters = [ 49 | 'string' => 'here', 50 | ]; 51 | 52 | $this->assertSame(0, $twigGenerator->generateTests($metaData, $parameters)); 53 | } 54 | 55 | public function testGenerateTestsOnNonExistedTestTwigFile() 56 | { 57 | $twigGenerator = new TwigGenerator([$this->templatePath], __DIR__ . '/../fixtures/twig_cache'); 58 | $metaData = [ 59 | ['testPath' => __DIR__ . '/../fixtures/test_non_exsited.twig'], 60 | ]; 61 | $parameters = [ 62 | 'string' => 'here', 63 | ]; 64 | 65 | $this->assertSame(1, $twigGenerator->generateTests($metaData, $parameters)); 66 | } 67 | 68 | public function testGeneradteDocs() 69 | { 70 | $twigGenerator = new TwigGenerator([$this->templatePath], __DIR__ . '/../fixtures/twig_cache'); 71 | $metaData = []; 72 | $parameters = [ 73 | 'output' => __DIR__ . '/../fixtures/doc.md', 74 | ]; 75 | 76 | $this->assertSame(1, $twigGenerator->generateDocs($metaData, $parameters)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Console/DocsCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Console; 13 | 14 | use Ahc\Phint\Generator\TwigGenerator; 15 | 16 | class DocsCommand extends BaseCommand 17 | { 18 | /** @var string Command name */ 19 | protected $_name = 'docs'; 20 | 21 | /** @var string Command description */ 22 | protected $_desc = 'Generate basic readme docs from docblocks'; 23 | 24 | /** 25 | * Configure the command options/arguments. 26 | * 27 | * @return void 28 | */ 29 | protected function onConstruct() 30 | { 31 | $this 32 | ->option( 33 | '-o --output', 34 | 'Output file (default README.md). For old project you should use something else' 35 | . "\n(OR mark region with and to inject docs)", 36 | null, 37 | 'README.md' 38 | ) 39 | ->option('-a --with-abstract', 'Create docs for abstract/interface class') 40 | ->option('-x --template', "User supplied template path\nIt has higher precedence than inbuilt templates") 41 | ->usage( 42 | ' phint docs Appends to readme.md' . 43 | ' phint d -o docs/api.md Writes to docs/api.md' 44 | ); 45 | } 46 | 47 | /** 48 | * Generate test stubs. 49 | * 50 | * @return void 51 | */ 52 | public function execute() 53 | { 54 | $io = $this->app()->io(); 55 | 56 | $io->comment('Preparing metadata ...', true); 57 | $docsMetadata = $this->getClassesMetadata(); 58 | 59 | if (empty($docsMetadata)) { 60 | $io->bgGreen('Looks like nothing to do here', true); 61 | 62 | return; 63 | } 64 | 65 | $io->comment('Generating docs ...', true); 66 | $generated = $this->generate($docsMetadata, $this->values()); 67 | 68 | if ($generated) { 69 | $io->cyan("$generated doc(s) generated", true); 70 | } 71 | 72 | $io->ok('Done', true); 73 | } 74 | 75 | protected function generate(array $docsMetadata, array $parameters): int 76 | { 77 | $generator = new TwigGenerator($this->getTemplatePaths($parameters), $this->getCachePath()); 78 | 79 | $parameters['output'] = $this->_pathUtil->expand($parameters['output'], $this->_workDir); 80 | 81 | return $generator->generateDocs($docsMetadata, $parameters); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Util/Executable.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Util; 13 | 14 | use Ahc\Cli\Helper\Shell; 15 | use Ahc\Cli\IO\Interactor; 16 | 17 | abstract class Executable 18 | { 19 | /** @var bool Last command successful? */ 20 | protected $isSuccessful = true; 21 | 22 | /** @var Interactor */ 23 | protected $io; 24 | 25 | /** @var string The binary executable */ 26 | protected $binary; 27 | 28 | /** @var string */ 29 | protected $workDir; 30 | 31 | /** @var string Full path of log file */ 32 | protected $logFile; 33 | 34 | public function __construct($binary = null, string $logFile = '') 35 | { 36 | $this->workDir = \getcwd(); 37 | $this->logFile = $logFile; 38 | $this->binary = $this->findBinary($binary ?? $this->binary); 39 | } 40 | 41 | public function withWorkDir($workDir = null) 42 | { 43 | if (!\is_dir($workDir)) { 44 | throw new \InvalidArgumentException('Not a valid working dir: ' . $workDir); 45 | } 46 | 47 | $this->workDir = $workDir; 48 | 49 | return $this; 50 | } 51 | 52 | public function successful(): bool 53 | { 54 | return $this->isSuccessful; 55 | } 56 | 57 | protected function findBinary(string $binary): string 58 | { 59 | if (\is_executable($binary)) { 60 | return '"' . $binary . '"'; 61 | } 62 | 63 | $isWin = \DIRECTORY_SEPARATOR === '\\'; 64 | 65 | return $isWin ? $this->findWindowsBinary($binary) : '"' . $binary . '"'; 66 | } 67 | 68 | protected function findWindowsBinary(string $binary): string 69 | { 70 | $paths = \explode(\PATH_SEPARATOR, \getenv('PATH') ?: \getenv('Path')); 71 | 72 | foreach ($paths as $path) { 73 | foreach (['.exe', '.bat', '.cmd'] as $ext) { 74 | if (\is_file($file = $path . '\\' . $binary . $ext)) { 75 | return '"' . $file . '"'; 76 | } 77 | } 78 | } 79 | 80 | return '"' . $binary . '"'; 81 | } 82 | 83 | /** 84 | * Runs the command using underlying binary. 85 | * 86 | * @param string $command 87 | * 88 | * @return string|null The output of command. 89 | */ 90 | protected function runCommand($command) 91 | { 92 | $proc = new Shell($this->binary . ' ' . $command); 93 | 94 | $proc->setOptions($this->workDir)->execute(); 95 | 96 | if ($this->logFile && \is_writable(\dirname($this->logFile))) { 97 | $data = $proc->getOutput() . \PHP_EOL . $proc->getErrorOutput(); 98 | 99 | (new Path)->writeFile($this->logFile, $data, \FILE_APPEND); 100 | } 101 | 102 | $this->isSuccessful = 0 === $proc->getExitCode(); 103 | 104 | return $proc->getOutput(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/Util/PathTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Test; 13 | 14 | use Ahc\Phint\Util\Path; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class PathTest extends TestCase 18 | { 19 | public function testIsAbsolute() 20 | { 21 | $path = new Path; 22 | 23 | $this->assertTrue($path->isAbsolute(__DIR__ . '/../fixtures/')); 24 | } 25 | 26 | public function relativePathProvider() 27 | { 28 | return [ 29 | [__DIR__ . '/../fixtures', __DIR__ . '/../fixtures', ''], 30 | [__DIR__ . '/../fixtures', '/../fixtures', __DIR__ . '/../fixtures'], 31 | ]; 32 | } 33 | 34 | /** @dataProvider relativePathProvider */ 35 | public function testGetRelativePath($fullPath, $relativePath, $expectedPath) 36 | { 37 | $path = new Path; 38 | 39 | $this->assertEquals($expectedPath, $path->getRelativePath($fullPath, $relativePath)); 40 | } 41 | 42 | public function testEnsureDir() 43 | { 44 | $path = new Path; 45 | 46 | $this->assertTrue($path->ensureDir(__DIR__ . '/../fixtures/ensure_dir')); 47 | } 48 | 49 | public function testEnsureDirOnNonExistedDir() 50 | { 51 | $path = new Path; 52 | 53 | $this->assertTrue($path->ensureDir(__DIR__ . '/../fixtures/add_dir')); 54 | } 55 | 56 | public function testGetExtension() 57 | { 58 | $path = new Path; 59 | 60 | $this->assertEquals('json', $path->getExtension(__DIR__ . '/../fixtures/example.json')); 61 | } 62 | 63 | public function testReadAsJson() 64 | { 65 | $path = new Path; 66 | 67 | $this->assertArrayHasKey('name', $path->readAsJson(__DIR__ . '/../fixtures/example.json')); 68 | } 69 | 70 | public function testReadDirShouldReturnNull() 71 | { 72 | $path = new Path; 73 | 74 | $this->assertNull($path->read(__DIR__ . '/../fixtures')); 75 | } 76 | 77 | public function testGetPhintPath() 78 | { 79 | $path = new Path; 80 | 81 | $this->assertContains('/home', $path->getPhintPath('/fixtures')); 82 | } 83 | 84 | public function testGetPhintPathOnEmptySubPath() 85 | { 86 | $path = new Path; 87 | 88 | $this->assertContains('', $path->getPhintPath()); 89 | } 90 | 91 | public function testWriteFile() 92 | { 93 | $writeFilePath = __DIR__ . '/../fixtures/write_file.txt'; 94 | $path = new Path; 95 | $path->writeFile($writeFilePath, 'write_file_test'); 96 | 97 | $this->assertEquals('write_file_test', \file_get_contents($writeFilePath)); 98 | } 99 | 100 | public function testJoinOnEmptyPathArray() 101 | { 102 | $path = new Path; 103 | 104 | $this->assertSame('', $path->join([])); 105 | } 106 | 107 | public function testLoadClasses() 108 | { 109 | $path = new Path; 110 | 111 | $this->assertContains('Ahc\Phint\Test\PathTest', $path->loadClasses([__DIR__ . '/../../src'], ['Ahc\Phint\Test\PathTest'])); 112 | } 113 | 114 | public function testExpandOnEmptyPath() 115 | { 116 | $path = new Path; 117 | 118 | $this->assertSame('', $path->expand('.')); 119 | } 120 | 121 | public function testExpandOnContainingHomePath() 122 | { 123 | $path = new Path; 124 | 125 | $this->assertContains('/home', $path->expand('~')); 126 | } 127 | 128 | public function testExpandOnRelativePath() 129 | { 130 | $path = new Path; 131 | 132 | $this->assertSame('../fixtures/.', $path->expand('./', '../fixtures')); 133 | } 134 | 135 | public function testExpandOnAbsolutePath() 136 | { 137 | $path = new Path; 138 | $absolutePath = '/usr/local/bin'; 139 | 140 | $this->assertSame('/usr/local/bin', $path->expand($absolutePath)); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Util/Metadata.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Util; 13 | 14 | use CrazyFactory\DocBlocks\DocBlock; 15 | 16 | class Metadata 17 | { 18 | public function forClass(string $classFqcn): array 19 | { 20 | return $this->forReflectionClass(new \ReflectionClass($classFqcn)); 21 | } 22 | 23 | public function forReflectionClass(\ReflectionClass $class): array 24 | { 25 | $name = $class->name; 26 | $texts = (new DocBlock($class))->texts(); 27 | $title = \array_shift($texts); 28 | 29 | $metadata = [ 30 | 'namespace' => \preg_replace('!\W\w+$!', '', $name), 31 | 'classFqcn' => $name, 32 | 'classPath' => $class->getFileName(), 33 | 'name' => $class->getShortName(), 34 | 'className' => $class->getShortName(), 35 | 'isTrait' => $class->isTrait(), 36 | 'isAbstract' => $class->isAbstract(), 37 | 'isInterface' => $class->isInterface(), 38 | 'newable' => $class->isInstantiable(), 39 | 'title' => $title, 40 | 'texts' => $texts, 41 | 'methods' => [], 42 | ]; 43 | 44 | foreach ($class->getMethods() as $method) { 45 | if ($method->class !== $name) { 46 | continue; 47 | } 48 | 49 | $metadata['methods'][$method->name] = $this->forReflectionMethod($method); 50 | } 51 | 52 | return $metadata; 53 | } 54 | 55 | public function forMethod(string $classFqcn, string $method): array 56 | { 57 | $reflMethod = (new \ReflectionClass($classFqcn))->getMethod($method); 58 | 59 | return $this->forReflectionMethod($reflMethod); 60 | } 61 | 62 | public function forReflectionMethod(\ReflectionMethod $method): array 63 | { 64 | $parser = new DocBlock($method); 65 | $texts = $parser->texts(); 66 | $title = \array_shift($texts); 67 | $throws = $parser->first('throws'); 68 | 69 | $metadata = [ 70 | 'name' => $method->name, 71 | 'inClass' => $method->getDeclaringClass()->name, 72 | 'isStatic' => $method->isStatic(), 73 | 'isFinal' => $method->isFinal(), 74 | 'isPublic' => $method->isPublic(), 75 | 'isAbstract' => $method->isAbstract(), 76 | 'maybeMagic' => \substr($method->name, 0, 2) === '__', 77 | 'throws' => $throws ? \explode(' ', \trim($throws->getValue()), 2) : [], 78 | 'title' => $title, 79 | 'texts' => $texts, 80 | ]; 81 | 82 | $params = []; 83 | foreach ($parser->find('param') as $param) { 84 | if (\preg_match('/(.*)\$(\w+)(.*)/', $param->getValue(), $match)) { 85 | $params[$match[2]] = [\trim($match[1]), \trim($match[3])]; 86 | } 87 | } 88 | 89 | if (null !== $return = $parser->first('return')) { 90 | $return = \explode(' ', \trim($return->getValue()), 2); 91 | } 92 | 93 | return $metadata + $this->getMethodParameters($method, $params, $return ?? []); 94 | } 95 | 96 | protected function getMethodParameters(\ReflectionMethod $method, array $docParams, array $return) 97 | { 98 | $params = []; 99 | $parser = new DocBlock($method); 100 | 101 | foreach ($method->getParameters() as $param) { 102 | $name = $param->name; 103 | if (!$param->hasType()) { 104 | $params[] = [\trim(($docParams[$name][0] ?? '') . " \$$name"), $docParams[$name][1] ?? '']; 105 | 106 | continue; 107 | } 108 | 109 | $params[] = [$this->getRealType($param) . " \$$name", $docParams[$name][1] ?? '']; 110 | } 111 | 112 | if ($returnType = $method->getReturnType()) { 113 | $return = [$this->getRealType($returnType), $return[1] ?? '']; 114 | } 115 | 116 | return \compact('params', 'return'); 117 | } 118 | 119 | protected function getRealType($param): string 120 | { 121 | $type = \method_exists($param, 'getType') 122 | ? $param->getType() 123 | : (string) $param; 124 | 125 | if (\preg_match('/void|null/', $type)) { 126 | return $type; 127 | } 128 | 129 | return $type . ($param->allowsNull() ? '|null' : ''); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Console/UpdateCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Console; 13 | 14 | use Ahc\Cli\Exception\RuntimeException; 15 | use Ahc\Cli\Input\Command; 16 | use Ahc\Phint\Util\Composer; 17 | 18 | /** 19 | * Some ideas related to phar taken from `composer selfupdate`. 20 | */ 21 | class UpdateCommand extends BaseCommand 22 | { 23 | const PHAR_URL = 'https://github.com/adhocore/phint/releases/download/{version}/phint.phar'; 24 | 25 | /** @var string Command name */ 26 | protected $_name = 'update'; 27 | 28 | /** @var string Command description */ 29 | protected $_desc = 'Update Phint to lastest version'; 30 | 31 | protected function onConstruct() 32 | { 33 | $this 34 | ->option('-r --rollback', 'Rollback to earlier version', 'boolval', false) 35 | ->on([$this, 'rollback']) 36 | ->usage( 37 | ' phint update Updates to latest version' . 38 | ' phint u Also updates to latest version' . 39 | ' phint update -r Rolls back to prev version' . 40 | ' phint u --rollback Also rolls back to prev version' 41 | ); 42 | } 43 | 44 | /** 45 | * Execute the command action. 46 | */ 47 | public function execute() 48 | { 49 | $io = $this->app()->io(); 50 | 51 | $io->cyan("Current version {$this->_version}", true); 52 | $io->comment('Fetching latest version ...', true); 53 | 54 | $release = \shell_exec('curl -sSL https://api.github.com/repos/adhocore/phint/releases/latest'); 55 | $release = \json_decode($release); 56 | 57 | if (\JSON_ERROR_NONE !== \json_last_error() || empty($release->assets[0])) { 58 | $io->bgRed('Problem getting latest release', true); 59 | $io->red($release->message ?? 'Unknown error', true); 60 | 61 | return; 62 | } 63 | 64 | $latest = $release->tag_name; 65 | 66 | if (!\version_compare(\str_replace('v', '', $this->_version), \str_replace('v', '', $latest), '<')) { 67 | $io->bgGreen('You seem to have latest version already', true); 68 | } else { 69 | $this->updateTo($latest, $release->assets[0]->size); 70 | 71 | if (\is_file($this->getPharPathFor(null) . '.old')) { 72 | $io->colors('You can run phint update --rollback to revert'); 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * Perform rollback. 79 | */ 80 | public function rollback() 81 | { 82 | $io = $this->app()->io(); 83 | 84 | $io->cyan("Current version {$this->_version}", true); 85 | $io->comment('Rolling back to earlier version ...', true); 86 | 87 | $thisPhar = $this->getPharPathFor(null); 88 | $oldPhar = $thisPhar . '.old'; 89 | 90 | if (!\is_file($oldPhar)) { 91 | throw new RuntimeException('No old version locally available'); 92 | } 93 | 94 | $oldPerms = \fileperms($thisPhar); 95 | 96 | if (@\rename($oldPhar, $thisPhar)) { 97 | $io->ok('Done', true); 98 | } 99 | 100 | @\chmod($thisPhar, $oldPerms); 101 | 102 | $this->emit('_exit'); 103 | } 104 | 105 | protected function updateTo(string $version, int $size) 106 | { 107 | $io = $this->app()->io(); 108 | 109 | $currentPhar = $this->getPharPathFor(null); 110 | $versionPhar = $this->getPharPathFor($version); 111 | $sourceUrl = \str_replace('{version}', $version, static::PHAR_URL); 112 | 113 | $io->comment("Downloading phar $version ...", true); 114 | 115 | // Create new $version phar 116 | $saved = @\file_put_contents($versionPhar, \shell_exec("curl -sSL $sourceUrl")); 117 | 118 | if ($saved < $size) { 119 | @\unlink($versionPhar); 120 | 121 | throw new RuntimeException("Couldnt download the phar for $version"); 122 | } 123 | 124 | $io->comment("Updating to $version ...", true); 125 | 126 | try { 127 | @\chmod($versionPhar, \fileperms($currentPhar)); 128 | 129 | if (!\ini_get('phar.readonly')) { 130 | $phar = new \Phar($versionPhar); 131 | unset($phar); 132 | } 133 | 134 | // Take backup of current 135 | @\copy($currentPhar, $currentPhar . '.old'); 136 | 137 | // Replace current with new $version 138 | @\rename($versionPhar, $currentPhar); 139 | 140 | $io->ok('Done', true); 141 | } catch (\Throwable $e) { 142 | $io->error('Couldnt update to ' . $version, true); 143 | } 144 | } 145 | 146 | protected function getPharPathFor(string $version = null): string 147 | { 148 | if (false === $thisPhint = \realpath($_SERVER['argv'][0])) { 149 | $thisPhint = $this->_pathUtil->getPhintPath('phint.phar'); 150 | } 151 | 152 | if (empty($thisPhint)) { 153 | throw new RuntimeException('Couldnt locate phint path, make sure you have HOME in the env vars'); 154 | } 155 | 156 | if (empty($version)) { 157 | return $thisPhint; 158 | } 159 | 160 | $pathTemplate = '%s.%s.phar'; 161 | 162 | return \sprintf($pathTemplate, \str_replace('.phar', '', $thisPhint), $version); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Console/TestCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Console; 13 | 14 | use Ahc\Cli\IO\Interactor; 15 | use Ahc\Phint\Generator\TwigGenerator; 16 | 17 | class TestCommand extends BaseCommand 18 | { 19 | /** @var string Command name */ 20 | protected $_name = 'test'; 21 | 22 | /** @var string Command description */ 23 | protected $_desc = 'Generate test stubs'; 24 | 25 | /** 26 | * Configure the command options/arguments. 27 | * 28 | * @return void 29 | */ 30 | protected function onConstruct() 31 | { 32 | $this 33 | ->option('-t --no-teardown', 'Dont add teardown method') 34 | ->option('-s --no-setup', 'Dont add setup method') 35 | ->option('-n --naming', "Test method naming format\n(t: testMethod | m: test_method | i: it_tests_)") 36 | ->option('-a --with-abstract', 'Create stub for abstract/interface class') 37 | ->option('-p --phpunit [classFqcn]', 'Base PHPUnit class to extend from') 38 | ->option('-x --template', "User supplied template path\nIt has higher precedence than inbuilt templates") 39 | ->usage( 40 | ' phint test -n i With `it_` naming' . 41 | ' phint t --no-teardown Without `tearDown()`' . 42 | ' phint test -a With stubs for abstract/interface' 43 | ); 44 | } 45 | 46 | public function interact(Interactor $io) 47 | { 48 | $promptConfig = [ 49 | 'naming' => [ 50 | 'choices' => ['t' => 'testMethod', 'i' => 'it_tests_', 'm' => 'test_method'], 51 | 'default' => 't', 52 | ], 53 | 'phpunit' => [ 54 | 'default' => 'PHPUnit\\Framework\\TestCase', 55 | ], 56 | 'template' => false, 57 | ]; 58 | 59 | $this->logging('start'); 60 | $this->promptAll($io, $promptConfig); 61 | } 62 | 63 | /** 64 | * Generate test stubs. 65 | * 66 | * @return void 67 | */ 68 | public function execute() 69 | { 70 | $io = $this->app()->io(); 71 | 72 | $io->comment('Preparing metadata ...', true); 73 | $metadata = $this->prepare(); 74 | 75 | if (empty($metadata)) { 76 | $io->bgGreen('Looks like nothing to do here', true); 77 | 78 | return; 79 | } 80 | 81 | $io->comment('Generating tests ...', true); 82 | $generated = $this->generate($metadata, $this->values()); 83 | 84 | if ($generated) { 85 | $io->cyan("$generated test(s) generated", true); 86 | } 87 | 88 | $io->ok('Done', true); 89 | $this->logging('end'); 90 | } 91 | 92 | protected function prepare(): array 93 | { 94 | // Sorry psr-0! 95 | $namespaces = $this->_composer->config('autoload.psr-4'); 96 | $namespaces += $this->_composer->config('autoload-dev.psr-4'); 97 | 98 | $srcNs = $testNs = []; 99 | 100 | foreach ($namespaces as $ns => $path) { 101 | $ns = \rtrim($ns, '\\') . '\\'; 102 | if (\preg_match('!^(source|src|lib|class)/?!', $path)) { 103 | $path = \rtrim($path, '/\\') . '/'; 104 | $srcNs[] = ['ns' => $ns, 'nsPath' => "{$this->_workDir}/$path"]; 105 | } elseif (\strpos($path, 'test') === 0) { 106 | $path = \rtrim($path, '/\\') . '/'; 107 | $testNs = ['ns' => $ns, 'nsPath' => "{$this->_workDir}/$path"]; 108 | } 109 | } 110 | 111 | if (empty($srcNs) || empty($testNs)) { 112 | throw new \RuntimeException( 113 | 'The composer.json#(autoload.psr-4, autoload-dev.psr-4) contains no `src` or `test` paths' 114 | ); 115 | } 116 | 117 | return $this->getTestMetadata($this->getSourceClasses(), $srcNs, $testNs); 118 | } 119 | 120 | protected function getTestMetadata(array $classes, array $srcNs, array $testNs): array 121 | { 122 | $metadata = []; 123 | 124 | foreach ($classes as $classFqcn) { 125 | if ([] === $meta = $this->getClassMetaData($classFqcn)) { 126 | continue; 127 | } 128 | 129 | $metadata[] = $meta + $this->convertToTest($meta, $srcNs, $testNs); 130 | } 131 | 132 | return $metadata; 133 | } 134 | 135 | private function convertToTest(array $metadata, array $srcNs, array $testNs): array 136 | { 137 | $srcNspcs = \array_column($srcNs, 'ns'); 138 | $testClass = $metadata['className'] . 'Test'; 139 | $testPath = \str_replace(\array_column($srcNs, 'nsPath'), $testNs['nsPath'], $metadata['classPath']); 140 | $testPath = \preg_replace('!\.php$!i', 'Test.php', $testPath); 141 | $testFqcn = \str_replace($srcNspcs, $testNs['ns'], $metadata['classFqcn']) . 'Test'; 142 | 143 | $testNamespace = \trim(\str_replace($srcNspcs, $testNs['ns'], $metadata['namespace'] . '\\'), '\\'); 144 | 145 | return compact('testClass', 'testNamespace', 'testFqcn', 'testPath'); 146 | } 147 | 148 | protected function generate(array $testMetadata, array $parameters): int 149 | { 150 | $generator = new TwigGenerator($this->getTemplatePaths($parameters), $this->getCachePath()); 151 | 152 | return $generator->generateTests($testMetadata, $parameters); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Console/BaseCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Console; 13 | 14 | use Ahc\Cli\Exception\RuntimeException; 15 | use Ahc\Cli\Input\Command; 16 | use Ahc\Cli\IO\Interactor; 17 | use Ahc\Phint\Util\Composer; 18 | use Ahc\Phint\Util\Git; 19 | use Ahc\Phint\Util\Metadata; 20 | use Ahc\Phint\Util\Path; 21 | 22 | abstract class BaseCommand extends Command 23 | { 24 | /** @var string Full path of log file */ 25 | protected $_logFile; 26 | 27 | /** @var string Current working dir */ 28 | protected $_workDir; 29 | 30 | public function __construct() 31 | { 32 | $logFile = @\tempnam(\sys_get_temp_dir(), 'PHT') ?: ''; 33 | 34 | $this->_logFile = $logFile; 35 | $this->_pathUtil = new Path; 36 | $this->_workDir = \realpath(\getcwd()); 37 | $this->_git = new Git(null, $logFile); 38 | $this->_composer = new Composer(null, $logFile); 39 | 40 | $this->defaults(); 41 | $this->onConstruct(); 42 | } 43 | 44 | protected function onConstruct() 45 | { 46 | // ;) 47 | } 48 | 49 | protected function promptAll(Interactor $io, array $promptConfig) 50 | { 51 | $template = ['default' => null, 'choices' => [], 'retry' => 1, 'extra' => '', 'restore' => false]; 52 | 53 | foreach ($this->missingOptions($promptConfig) as $name => $option) { 54 | $config = ($promptConfig[$name] ?? []) + $template; 55 | $default = ($config['default'] ?? $option->default()) ?: null; 56 | 57 | if ($config['choices']) { 58 | $value = $io->choice($option->desc(), $config['choices'], $default); 59 | } else { 60 | $value = $io->prompt($option->desc() . $config['extra'], $default, null, $config['retry']); 61 | } 62 | 63 | if ($config['restore']) { 64 | $value = $config['choices'][$value] ?? $value; 65 | } 66 | 67 | $this->set($name, $value); 68 | } 69 | } 70 | 71 | protected function missingOptions(array $config) 72 | { 73 | return \array_filter($this->userOptions(), function ($name) use ($config) { 74 | return false !== ($config[$name] ?? null) && \in_array($this->$name, [null, []], true); 75 | }, \ARRAY_FILTER_USE_KEY); 76 | } 77 | 78 | protected function getCachePath(): string 79 | { 80 | if (!\Phar::running(false)) { 81 | return __DIR__ . '/../../.cache'; 82 | } 83 | 84 | return $this->_pathUtil->getPhintPath('.cache'); 85 | } 86 | 87 | protected function logging(string $mode = 'start') 88 | { 89 | if (!$logFile = $this->_logFile) { 90 | return; 91 | } 92 | 93 | if ('end' === $mode) { 94 | $this->app()->io()->comment("Check detailed logs at $logFile", true); 95 | } else { 96 | $this->app()->io()->comment("Logging to $logFile", true); 97 | } 98 | } 99 | 100 | protected function getTemplatePaths(array $parameters): array 101 | { 102 | // Phint provided path. 103 | $templatePaths = [__DIR__ . '/../../resources']; 104 | $userPath = $parameters['template'] ?? null; 105 | 106 | if (empty($userPath)) { 107 | return $templatePaths; 108 | } 109 | 110 | $userPath = $this->_pathUtil->expand($userPath, $this->_workDir); 111 | 112 | // User supplied path comes first. 113 | \array_unshift($templatePaths, $userPath); 114 | 115 | return $templatePaths; 116 | } 117 | 118 | protected function getClassesMetadata(): array 119 | { 120 | $metadata = []; 121 | 122 | foreach ($this->getSourceClasses() as $classFqcn) { 123 | if ([] === $meta = $this->getClassMetaData($classFqcn)) { 124 | continue; 125 | } 126 | 127 | $metadata[] = $meta; 128 | } 129 | 130 | return $metadata; 131 | } 132 | 133 | protected function getSourceClasses(): array 134 | { 135 | // Sorry psr-0! 136 | $namespaces = $this->_composer->config('autoload.psr-4'); 137 | 138 | $srcPaths = []; 139 | foreach ($namespaces ?: [] as $ns => $path) { 140 | if (\preg_match('!^(source|src|lib|class)/?!', $path)) { 141 | $srcPaths[] = $this->_pathUtil->join($this->_workDir, $path); 142 | } else { 143 | unset($namespaces[$ns]); 144 | } 145 | } 146 | 147 | if (empty($srcPaths)) { 148 | throw new RuntimeException('Source namespaces not specified in composer.json'); 149 | } 150 | 151 | $this->_composer->forceAutoload(); 152 | 153 | return $this->_pathUtil->loadClasses($srcPaths, \array_keys($namespaces)); 154 | } 155 | 156 | protected function getClassMetaData(string $classFqcn): array 157 | { 158 | $class = new \ReflectionClass($classFqcn); 159 | 160 | if (!$this->shouldGenerateFor($class)) { 161 | return []; 162 | } 163 | 164 | $metadata = (new Metadata)->forReflectionClass($class); 165 | 166 | return empty($metadata['methods']) ? [] : $metadata; 167 | } 168 | 169 | protected function shouldGenerateFor(\ReflectionClass $class): bool 170 | { 171 | if ($class->isSubclassOf(\Throwable::class)) { 172 | return false; 173 | } 174 | 175 | if ($this->abstract) { 176 | return true; 177 | } 178 | 179 | return !$class->isInterface() && !$class->isAbstract(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Util/Path.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Util; 13 | 14 | use Ahc\Json\Comment; 15 | use Symfony\Component\Finder\Finder; 16 | 17 | class Path 18 | { 19 | /** @var string */ 20 | protected $phintPath; 21 | 22 | /** 23 | * Platform agnostic absolute path detection. 24 | * 25 | * @param string $path 26 | * 27 | * @return bool 28 | */ 29 | public function isAbsolute(string $path): bool 30 | { 31 | if (\DIRECTORY_SEPARATOR === '\\') { 32 | return \strpos($path, ':') === 1; 33 | } 34 | 35 | return isset($path[0]) && $path[0] === '/'; 36 | } 37 | 38 | public function getRelativePath(string $fullPath, string ...$basePaths): string 39 | { 40 | foreach ($basePaths as $basePath) { 41 | if (\strpos($fullPath, $basePath) === 0) { 42 | return \substr($fullPath, \strlen($basePath)); 43 | } 44 | } 45 | 46 | // Hmm! 47 | return $fullPath; 48 | } 49 | 50 | public function ensureDir(string $dir, $mode = 0777): bool 51 | { 52 | if (!\is_dir($dir)) { 53 | return \mkdir($dir, $mode, true); 54 | } 55 | 56 | return true; 57 | } 58 | 59 | public function getExtension(string $filePath): string 60 | { 61 | return \pathinfo($filePath, \PATHINFO_EXTENSION); 62 | } 63 | 64 | public function readAsJson(string $filePath, bool $asArray = true) 65 | { 66 | return (new Comment)->decode($this->read($filePath) ?? 'null', $asArray); 67 | } 68 | 69 | public function read(string $filePath): ?string 70 | { 71 | if (\is_file($filePath)) { 72 | return \file_get_contents($filePath); 73 | } 74 | 75 | return null; 76 | } 77 | 78 | public function writeFile(string $file, $content, int $mode = null): bool 79 | { 80 | $this->ensureDir(\dirname($file)); 81 | 82 | if (!\is_string($content)) { 83 | $content = \json_encode($content, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES); 84 | } 85 | 86 | return \file_put_contents($file, $content, $mode) > 0; 87 | } 88 | 89 | public function getPhintPath(string $subpath = ''): string 90 | { 91 | $this->initPhintPath(); 92 | 93 | if ($subpath && $this->phintPath) { 94 | return $this->phintPath . '/' . \ltrim($subpath, '/'); 95 | } 96 | 97 | return $this->phintPath; 98 | } 99 | 100 | public function createBinaries(array $bins, string $basePath) 101 | { 102 | foreach ($bins as $bin) { 103 | $bin = $this->join($basePath, $bin); 104 | 105 | if ($this->writeFile($bin, "#!/usr/bin/env php\n &$path) { 119 | $path = $i === 0 ? \rtrim($path, '\\/') : \trim($path, '\\/'); 120 | } 121 | 122 | return \implode('/', $paths); 123 | } 124 | 125 | public function findFiles(array $inPaths, string $ext, bool $dotfiles = false): array 126 | { 127 | $finder = new Finder; 128 | 129 | if ($ext !== '*') { 130 | $ext = '.' . \ltrim($ext, '.'); 131 | $len = \strlen($ext); 132 | 133 | $finder->filter(function ($file) use ($ext, $len) { 134 | return \substr($file, -$len) === $ext; 135 | }); 136 | } 137 | 138 | foreach ($inPaths as $path) { 139 | $finder->in($path); 140 | } 141 | 142 | $files = []; 143 | foreach ($finder->files()->ignoreDotFiles($dotfiles) as $file) { 144 | $files[] = (string) $file; 145 | } 146 | 147 | return $files; 148 | } 149 | 150 | public function loadClasses(array $inPaths, array $namespaces, string $ext = 'php'): array 151 | { 152 | foreach ($this->findFiles($inPaths, $ext) as $file) { 153 | _require($file); 154 | } 155 | 156 | $namespaces = \implode('\|', $namespaces); 157 | $allClasses = \array_merge(\get_declared_interfaces(), \get_declared_classes(), \get_declared_traits()); 158 | 159 | return \preg_grep('~^' . \preg_quote($namespaces) . '~', $allClasses); 160 | } 161 | 162 | public function expand(string $path, string $from = ''): string 163 | { 164 | if ($path === '.') { 165 | return $from; 166 | } 167 | 168 | if ($path[0] === '~') { 169 | return \str_replace('~', \getenv('HOME'), $path); 170 | } 171 | 172 | if (\strlen($from) > 0 && !$this->isAbsolute($path)) { 173 | return $this->join($from, $path); 174 | } 175 | 176 | return $path; 177 | } 178 | 179 | protected function initPhintPath() 180 | { 181 | if (null !== $this->phintPath) { 182 | return; 183 | } 184 | 185 | $this->phintPath = ''; 186 | 187 | if (false !== $home = ($_SERVER['HOME'] ?? \getenv('HOME'))) { 188 | $path = \rtrim($home, '/') . '/.phint'; 189 | 190 | if ($this->ensureDir($path)) { 191 | $this->phintPath = $path; 192 | } 193 | } 194 | } 195 | } 196 | 197 | /** 198 | * Isolated file require. 199 | * 200 | * @param string $file 201 | * 202 | * @return void 203 | */ 204 | function _require(string $file) 205 | { 206 | require_once $file; 207 | } 208 | -------------------------------------------------------------------------------- /src/Generator/TwigGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Generator; 13 | 14 | use Ahc\Phint\Util\Inflector; 15 | use Ahc\Phint\Util\Path; 16 | 17 | class TwigGenerator implements GeneratorInterface 18 | { 19 | /** @var \Twig_Environment */ 20 | protected $twig; 21 | 22 | /** @var Path */ 23 | protected $pathUtil; 24 | 25 | /** @var Inflector */ 26 | protected $inflector; 27 | 28 | /** @var array */ 29 | protected $templatePaths; 30 | 31 | /** @var string */ 32 | protected $cachePath; 33 | 34 | /** @var array Templates required for type 'project' only */ 35 | protected $projectTemplates = [ 36 | '.env.example' => true, 37 | 'package.json' => true, 38 | ]; 39 | 40 | /** @var array Templates only loaded by some specific commands */ 41 | protected $commandTemplates = [ 42 | 'test' => true, 43 | 'docs' => true, 44 | ]; 45 | 46 | public function __construct(array $templatePaths, string $cachePath) 47 | { 48 | $this->templatePaths = $templatePaths; 49 | $this->cachePath = $cachePath; 50 | $this->pathUtil = new Path; 51 | $this->inflector = new Inflector; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function generate(string $targetPath, array $parameters, CollisionHandlerInterface $handler = null): int 58 | { 59 | $count = 0; 60 | 61 | if (!$this->twig) { 62 | $this->initTwig(); 63 | } 64 | 65 | $processed = []; 66 | $templates = $this->pathUtil->findFiles($this->templatePaths, '.twig', false); 67 | 68 | foreach ($templates as $template) { 69 | $relativePath = $this->pathUtil->getRelativePath($template, ...$this->templatePaths); 70 | 71 | if ($processed[$relativePath] ?? null) { 72 | continue; 73 | } 74 | 75 | $processed[$relativePath] = true; 76 | 77 | if ($this->shouldGenerate($template, $parameters)) { 78 | $count += (int) $this->doGenerate($relativePath, $targetPath, $parameters, $handler); 79 | } 80 | } 81 | 82 | $this->pathUtil->createBinaries($parameters['bin'] ?? [], $parameters['path'] ?? ''); 83 | 84 | return $count; 85 | } 86 | 87 | public function generateTests(array $testMetadata, array $parameters): int 88 | { 89 | if (!$this->twig) { 90 | $this->initTwig(); 91 | } 92 | 93 | $count = 0; 94 | 95 | foreach ($testMetadata as $metadata) { 96 | // Skip existing 97 | if (\is_file($targetFile = $metadata['testPath'])) { 98 | continue; 99 | } 100 | 101 | $content = $this->twig->render('tests/test.twig', $metadata + $parameters); 102 | $count += (int) $this->pathUtil->writeFile($targetFile, $content); 103 | } 104 | 105 | return $count; 106 | } 107 | 108 | public function generateDocs(array $docsMetadata, array $parameters): int 109 | { 110 | if (!$this->twig) { 111 | $this->initTwig(); 112 | } 113 | 114 | $targetFile = $parameters['output'] ?? 'README.md'; 115 | $docContent = $this->twig->render('docs/docs.twig', \compact('docsMetadata') + $parameters); 116 | $docContent = "\n$docContent\n"; 117 | 118 | if (null === $oldContent = \trim($this->pathUtil->read($targetFile))) { 119 | return (int) $this->pathUtil->writeFile($targetFile, $docContent); 120 | } 121 | 122 | if (\strpos($oldContent, '') !== false) { 123 | $docContent = \preg_replace('~.*?~s', $docContent, $oldContent); 124 | 125 | return (int) $this->pathUtil->writeFile($targetFile, $docContent); 126 | } 127 | 128 | return (int) $this->pathUtil->writeFile($targetFile, \trim("$oldContent\n\n$docContent")); 129 | } 130 | 131 | protected function initTwig() 132 | { 133 | $options = [ 134 | 'auto_reload' => true, 135 | 'cache' => false, 136 | ]; 137 | 138 | if ($this->cachePath) { 139 | $this->pathUtil->ensureDir($this->cachePath); 140 | $options['cache'] = $this->cachePath; 141 | } 142 | 143 | $this->twig = new \Twig_Environment( 144 | new \Twig_Loader_Filesystem($this->templatePaths), 145 | $options 146 | ); 147 | 148 | $this->twig->addFilter(new \Twig_SimpleFilter('snake', function ($x) { 149 | return $this->inflector->snakeCase($x); 150 | })); 151 | 152 | $this->twig->addFilter(new \Twig_SimpleFilter('lcfirst', function ($x) { 153 | return \lcfirst($x); 154 | })); 155 | 156 | $this->twig->addFilter(new \Twig_SimpleFilter('ucfirst', function ($x) { 157 | return \ucfirst($x); 158 | })); 159 | 160 | $this->twig->addFunction(new \Twig_Function('gmdate', function ($f = null) { 161 | return \gmdate($f ?? 'Y-m-d H:i:s'); 162 | })); 163 | 164 | $this->twig->addFunction(new \Twig_Function('call', function ($fn) { 165 | return $fn(...\array_slice(\func_get_args(), 1)); 166 | })); 167 | } 168 | 169 | protected function doGenerate(string $relativePath, string $targetPath, array $parameters, CollisionHandlerInterface $handler = null): bool 170 | { 171 | $targetFile = $this->pathUtil->join($targetPath, $this->getRelativeTarget($parameters, $relativePath)); 172 | $fileExists = \is_file($targetFile); 173 | $content = $this->twig->render($relativePath, $parameters); 174 | 175 | if ($handler && $fileExists) { 176 | return $handler->handle($targetFile, $content, $parameters); 177 | } 178 | 179 | if ($this->mayOverride($fileExists, $parameters)) { 180 | return $this->pathUtil->writeFile($targetFile, $content); 181 | } 182 | 183 | return false; 184 | } 185 | 186 | protected function getRelativeTarget(array $parameters, string $relativePath): string 187 | { 188 | $fileName = \basename($relativePath, '.twig'); 189 | $targetFile = \str_replace('.twig', '', $relativePath); 190 | 191 | if (!empty($parameters['ghTemplate']) && \in_array($fileName, ['ISSUE_TEMPLATE.md', 'PULL_REQUEST_TEMPLATE.md'])) { 192 | $targetFile = '.github/' . $fileName; 193 | } 194 | 195 | return $targetFile; 196 | } 197 | 198 | protected function shouldGenerate(string $template, array $parameters) 199 | { 200 | $name = \basename($template, '.twig'); 201 | 202 | if (isset($this->projectTemplates[$name])) { 203 | return $parameters['type'] === 'project'; 204 | } 205 | 206 | if (isset($this->commandTemplates[$name])) { 207 | return false; 208 | } 209 | 210 | if (empty($parameters['travis'])) { 211 | return $name !== '.travis.yml'; 212 | } 213 | 214 | return true; 215 | } 216 | 217 | protected function mayOverride(bool $fileExists, array $parameters) 218 | { 219 | if (!$fileExists) { 220 | return true; 221 | } 222 | 223 | // If using reference package then we dont overwrite! 224 | if (!empty($parameters['using'])) { 225 | return false; 226 | } 227 | 228 | if (!empty($parameters['sync'])) { 229 | return false; 230 | } 231 | 232 | return true; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/Console/InitCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 9 | * Licensed under MIT license. 10 | */ 11 | 12 | namespace Ahc\Phint\Console; 13 | 14 | use Ahc\Cli\Exception\InvalidArgumentException; 15 | use Ahc\Cli\Input\Command; 16 | use Ahc\Cli\IO\Interactor; 17 | use Ahc\Phint\Generator\CollisionHandler; 18 | use Ahc\Phint\Generator\TwigGenerator; 19 | use Ahc\Phint\Util\Inflector; 20 | 21 | class InitCommand extends BaseCommand 22 | { 23 | /** @var string Command name */ 24 | protected $_name = 'init'; 25 | 26 | /** @var string Command description */ 27 | protected $_desc = 'Create and Scaffold a bare new PHP project'; 28 | 29 | /** 30 | * Configure the command options/arguments. 31 | * 32 | * @return void 33 | */ 34 | protected function onConstruct() 35 | { 36 | $this 37 | ->argument('', 'The project name without slashes') 38 | ->option('-T --type', 'Project type (project | library | composer-plugin)') 39 | ->option('-n --name', 'Vendor full name', null, $this->_git->getConfig('user.name')) 40 | ->option('-e --email', 'Vendor email', null, $this->_git->getConfig('user.email')) 41 | ->option('-u --username', 'Vendor handle/username') 42 | ->option('-N --namespace', 'Root namespace (use `/` separator)') 43 | ->option('-P --php', 'Minimum PHP version', 'floatval') 44 | ->option('-p --path', 'The project path (Auto resolved)') 45 | ->option('-S --sync', "Only create missing files\nUse with caution, take backup if needed", null, false) 46 | ->option('-f --force', "Run even if the project exists\nUse with caution, take backup if needed", null, false) 47 | ->option('-g --package', 'Packagist name (Without vendor handle)') 48 | ->option('-x --template', "User supplied template path\nIt has higher precedence than inbuilt templates") 49 | ->option('-d --descr', 'Project description') 50 | ->option('-w --keywords [words...]', 'Project Keywords') 51 | ->option('-y --year', 'License Year', null, date('Y')) 52 | ->option('-b --bin [binaries...]', 'Executable binaries') 53 | ->option('-z --using', 'Reference package (should be known to composer)') 54 | ->option('-C --config', 'JSON filepath to read config from') 55 | ->option('-G --gh-template', "Use `.github/` as template path\nBy default uses `docs/`", null, false) 56 | ->option('-R --req [pkgs...]', 'Required packages') 57 | ->option('-D --dev [pkgs...]', 'Developer packages') 58 | ->option('-t --no-travis', 'Disable travis') 59 | ->option('-c --no-codecov', 'Disable codecov') 60 | ->option('-s --no-scrutinizer', 'Disable scrutinizer') 61 | ->option('-l --no-styleci', 'Disable StyleCI') 62 | ->option('-L --license', 'License (m: MIT, g: GNULGPL, a: Apache2, b: BSDSimple, i: ISC, w: WTFPL)') 63 | ->usage($this->writer()->colorizer()->colors( 64 | '' 65 | . ' phint init ' 66 | . '--force --descr "Awesome project" --name "YourName" --email you@domain.com' 67 | . ' phint init ' 68 | . '--using laravel/lumen --namespace Project/Api --type project --license m' 69 | . ' phint init ' 70 | . '--php 7.0 --config /path/to/json --dev mockery/mockery --req adhocore/cli' 71 | )); 72 | } 73 | 74 | /** 75 | * Execute the command action. 76 | * 77 | * @return void 78 | */ 79 | public function execute() 80 | { 81 | $io = $this->app()->io(); 82 | 83 | if ($using = $this->using) { 84 | $io->colors("Using $using to create project (takes some time)"); 85 | 86 | $this->_composer->createProject($this->path, $this->using); 87 | } 88 | 89 | $io->comment('Generating files ...', true); 90 | $this->generate($this->path, $this->values()); 91 | 92 | $io->colors('Setting up git'); 93 | $this->_git->withWorkDir($this->path)->init()->addRemote($this->username, $this->project); 94 | 95 | $io->colors('Setting up composer (takes some time)'); 96 | if ($using) { 97 | $this->_composer->withWorkDir($this->path)->update(); 98 | } else { 99 | $this->_composer->withWorkDir($this->path)->install(); 100 | } 101 | 102 | $success = $this->_composer->successful(); 103 | 104 | $success ? $io->ok('Done', true) : $io->error('Composer setup failed', true); 105 | 106 | $this->logging('end'); 107 | } 108 | 109 | public function interact(Interactor $io) 110 | { 111 | $project = $this->project; 112 | 113 | if (!\preg_match('/[a-z0-9_-]/i', $project)) { 114 | throw new InvalidArgumentException('Project argument should only contain [a-z0-9_-]'); 115 | } 116 | 117 | $io->okBold('Phint Setup', true); 118 | $this->logging('start'); 119 | 120 | $this->set('path', $path = $this->prepareProjectPath()); 121 | $this->loadConfig($this->config); 122 | 123 | $this->collectMissing($io); 124 | $this->collectPackages($io); 125 | } 126 | 127 | protected function prepareProjectPath(): string 128 | { 129 | $path = $this->project; 130 | $io = $this->app()->io(); 131 | 132 | if (!$this->_pathUtil->isAbsolute($path)) { 133 | $path = $this->_workDir . '/' . $path; 134 | } 135 | 136 | if (\is_dir($path)) { 137 | $this->projectExists($io, $path); 138 | } else { 139 | \mkdir($path, 0777, true); 140 | } 141 | 142 | return $path; 143 | } 144 | 145 | protected function projectExists(Interactor $io, string $path) 146 | { 147 | if (!$this->force && !$this->sync) { 148 | throw new InvalidArgumentException( 149 | \sprintf('Something with the name "%s" already exists!', \basename($path)) 150 | ); 151 | } 152 | 153 | if ($this->force && !$this->sync) { 154 | $io->error('You have set --force flag, existing files will be overwritten', true); 155 | } 156 | } 157 | 158 | protected function loadConfig(string $path = null) 159 | { 160 | if (empty($path)) { 161 | return; 162 | } 163 | 164 | if (!$this->_pathUtil->isAbsolute($path)) { 165 | $path = \getcwd() . '/' . $path; 166 | } 167 | 168 | if (!\is_file($path)) { 169 | $this->app()->io()->error('Invalid path specified for config', true); 170 | 171 | return; 172 | } 173 | 174 | foreach ($this->_pathUtil->readAsJson($path) as $key => $value) { 175 | $this->$key ?? $this->set($key, $value); 176 | } 177 | } 178 | 179 | protected function collectMissing(Interactor $io) 180 | { 181 | $promptConfig = [ 182 | 'type' => [ 183 | 'choices' => ['p' => 'project', 'l' => 'library', 'c' => 'composer-plugin'], 184 | 'default' => 'l', 185 | 'restore' => true, 186 | ], 187 | 'package' => ['default' => $this->project, 'retry' => 0], 188 | 'license' => [ 189 | 'choices' => ['m' => 'MIT', 'g' => 'GNULGPL', 'a' => 'Apache2', 'b' => 'BSDSimple', 'i' => 'ISC', 'w' => 'WTFPL'], 190 | 'default' => 'm', 191 | ], 192 | 'php' => [ 193 | 'choices' => ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3'], 194 | 'default' => '7.0', 195 | ], 196 | 'using' => ['retry' => 0, 'extra' => ' (ENTER to skip)'], 197 | 'keywords' => ['retry' => 0, 'extra' => ' (CSV, ENTER to skip)', 'default' => "php, {$this->project}"], 198 | 'bin' => ['retry' => 0, 'extra' => ' (CSV, ENTER to skip)'], 199 | 200 | // Donot prompt these here! 201 | 'req' => false, 202 | 'dev' => false, 203 | 'config' => false, 204 | 'template' => false, 205 | ]; 206 | 207 | $this->promptAll($io, $promptConfig); 208 | } 209 | 210 | protected function collectPackages(Interactor $io) 211 | { 212 | foreach (['req' => 'Required', 'dev' => 'Developer'] as $key => $label) { 213 | $pkgs = $this->$key ?: $this->promptPackages($label, $io); 214 | 215 | foreach ($pkgs as &$pkg) { 216 | $pkg = \strpos($pkg, ':') === false ? "{$pkg}:@stable" : $pkg; 217 | $pkg = \array_combine(['name', 'version'], \explode(':', $pkg, 2)); 218 | } 219 | 220 | $this->set($key, $pkgs); 221 | } 222 | } 223 | 224 | protected function promptPackages(string $label, Interactor $io): array 225 | { 226 | $pkgs = []; 227 | 228 | do { 229 | if (!$pkg = $io->prompt($label . ' package (ENTER to skip)', null, [$this, 'validatePackage'], 0)) { 230 | break; 231 | } 232 | 233 | $pkgs[] = $pkg; 234 | } while (true); 235 | 236 | return $pkgs; 237 | } 238 | 239 | public function validatePackage(string $pkg): string 240 | { 241 | $pkg = \trim($pkg); 242 | 243 | if ($pkg && \strpos($pkg, '/') === false) { 244 | throw new InvalidArgumentException( 245 | 'Package name format should be vendor/package:version (version can be omitted)' 246 | ); 247 | } 248 | 249 | return $pkg; 250 | } 251 | 252 | protected function generate(string $projectPath, array $parameters) 253 | { 254 | $generator = new TwigGenerator($this->getTemplatePaths($parameters), $this->getCachePath()); 255 | 256 | // Normalize license (default MIT) 257 | $parameters['license'] = \strtolower($parameters['license'][0] ?? 'm'); 258 | $parameters['namespace'] = $this->makeNamespace($parameters['namespace']); 259 | $parameters['keywords'] = $this->makeArray($parameters['keywords'], ['php', $this->project]); 260 | $parameters['bin'] = $this->makeArray($parameters['bin']); 261 | 262 | $generator->generate($projectPath, $parameters, new CollisionHandler); 263 | } 264 | 265 | protected function makeNamespace(string $value): string 266 | { 267 | $in = new Inflector; 268 | 269 | $project = $this->package; 270 | $value = $in->stuldyCase(\str_replace([' ', '/'], '\\', $value)); 271 | $project = $in->stuldyCase(\str_replace([' ', '/', '\\'], '-', $project)); 272 | 273 | if (\stripos($value, $project) === false) { 274 | $value .= '\\' . $project; 275 | } 276 | 277 | return $value; 278 | } 279 | 280 | protected function makeArray($value, array $default = []): array 281 | { 282 | if (empty($value)) { 283 | return $default; 284 | } 285 | 286 | if (\is_string($value)) { 287 | $value = \array_map('trim', \explode(',', $value)); 288 | } 289 | 290 | return \array_values(\array_unique(\array_merge($default, $value))); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /resources/LICENSE.twig: -------------------------------------------------------------------------------- 1 | {# All the license contents are derived from respective official resources #} 2 | {% if license == 'm' %} 3 | MIT License 4 | 5 | Copyright (c) {{year}} {{name}} 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | {% endif %} 25 | {% if license == 'a' %} 26 | Copyright {{year}} {{name}} 27 | 28 | Licensed under the Apache License, Version 2.0 (the "License"); 29 | you may not use this file except in compliance with the License. 30 | You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. 39 | {% endif %} 40 | {% if license == 'g' %} 41 | GNU LESSER GENERAL PUBLIC LICENSE 42 | Version 3, 29 June 2007 43 | 44 | Copyright (C) 2007 Free Software Foundation, Inc. 45 | Everyone is permitted to copy and distribute verbatim copies 46 | of this license document, but changing it is not allowed. 47 | 48 | 49 | This version of the GNU Lesser General Public License incorporates 50 | the terms and conditions of version 3 of the GNU General Public 51 | License, supplemented by the additional permissions listed below. 52 | 53 | 0. Additional Definitions. 54 | 55 | As used herein, "this License" refers to version 3 of the GNU Lesser 56 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 57 | General Public License. 58 | 59 | "The Library" refers to a covered work governed by this License, 60 | other than an Application or a Combined Work as defined below. 61 | 62 | An "Application" is any work that makes use of an interface provided 63 | by the Library, but which is not otherwise based on the Library. 64 | Defining a subclass of a class defined by the Library is deemed a mode 65 | of using an interface provided by the Library. 66 | 67 | A "Combined Work" is a work produced by combining or linking an 68 | Application with the Library. The particular version of the Library 69 | with which the Combined Work was made is also called the "Linked 70 | Version". 71 | 72 | The "Minimal Corresponding Source" for a Combined Work means the 73 | Corresponding Source for the Combined Work, excluding any source code 74 | for portions of the Combined Work that, considered in isolation, are 75 | based on the Application, and not on the Linked Version. 76 | 77 | The "Corresponding Application Code" for a Combined Work means the 78 | object code and/or source code for the Application, including any data 79 | and utility programs needed for reproducing the Combined Work from the 80 | Application, but excluding the System Libraries of the Combined Work. 81 | 82 | 1. Exception to Section 3 of the GNU GPL. 83 | 84 | You may convey a covered work under sections 3 and 4 of this License 85 | without being bound by section 3 of the GNU GPL. 86 | 87 | 2. Conveying Modified Versions. 88 | 89 | If you modify a copy of the Library, and, in your modifications, a 90 | facility refers to a function or data to be supplied by an Application 91 | that uses the facility (other than as an argument passed when the 92 | facility is invoked), then you may convey a copy of the modified 93 | version: 94 | 95 | a) under this License, provided that you make a good faith effort to 96 | ensure that, in the event an Application does not supply the 97 | function or data, the facility still operates, and performs 98 | whatever part of its purpose remains meaningful, or 99 | 100 | b) under the GNU GPL, with none of the additional permissions of 101 | this License applicable to that copy. 102 | 103 | 3. Object Code Incorporating Material from Library Header Files. 104 | 105 | The object code form of an Application may incorporate material from 106 | a header file that is part of the Library. You may convey such object 107 | code under terms of your choice, provided that, if the incorporated 108 | material is not limited to numerical parameters, data structure 109 | layouts and accessors, or small macros, inline functions and templates 110 | (ten or fewer lines in length), you do both of the following: 111 | 112 | a) Give prominent notice with each copy of the object code that the 113 | Library is used in it and that the Library and its use are 114 | covered by this License. 115 | 116 | b) Accompany the object code with a copy of the GNU GPL and this license 117 | document. 118 | 119 | 4. Combined Works. 120 | 121 | You may convey a Combined Work under terms of your choice that, 122 | taken together, effectively do not restrict modification of the 123 | portions of the Library contained in the Combined Work and reverse 124 | engineering for debugging such modifications, if you also do each of 125 | the following: 126 | 127 | a) Give prominent notice with each copy of the Combined Work that 128 | the Library is used in it and that the Library and its use are 129 | covered by this License. 130 | 131 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 132 | document. 133 | 134 | c) For a Combined Work that displays copyright notices during 135 | execution, include the copyright notice for the Library among 136 | these notices, as well as a reference directing the user to the 137 | copies of the GNU GPL and this license document. 138 | 139 | d) Do one of the following: 140 | 141 | 0) Convey the Minimal Corresponding Source under the terms of this 142 | License, and the Corresponding Application Code in a form 143 | suitable for, and under terms that permit, the user to 144 | recombine or relink the Application with a modified version of 145 | the Linked Version to produce a modified Combined Work, in the 146 | manner specified by section 6 of the GNU GPL for conveying 147 | Corresponding Source. 148 | 149 | 1) Use a suitable shared library mechanism for linking with the 150 | Library. A suitable mechanism is one that (a) uses at run time 151 | a copy of the Library already present on the user's computer 152 | system, and (b) will operate properly with a modified version 153 | of the Library that is interface-compatible with the Linked 154 | Version. 155 | 156 | e) Provide Installation Information, but only if you would otherwise 157 | be required to provide such information under section 6 of the 158 | GNU GPL, and only to the extent that such information is 159 | necessary to install and execute a modified version of the 160 | Combined Work produced by recombining or relinking the 161 | Application with a modified version of the Linked Version. (If 162 | you use option 4d0, the Installation Information must accompany 163 | the Minimal Corresponding Source and Corresponding Application 164 | Code. If you use option 4d1, you must provide the Installation 165 | Information in the manner specified by section 6 of the GNU GPL 166 | for conveying Corresponding Source.) 167 | 168 | 5. Combined Libraries. 169 | 170 | You may place library facilities that are a work based on the 171 | Library side by side in a single library together with other library 172 | facilities that are not Applications and are not covered by this 173 | License, and convey such a combined library under terms of your 174 | choice, if you do both of the following: 175 | 176 | a) Accompany the combined library with a copy of the same work based 177 | on the Library, uncombined with any other library facilities, 178 | conveyed under the terms of this License. 179 | 180 | b) Give prominent notice with the combined library that part of it 181 | is a work based on the Library, and explaining where to find the 182 | accompanying uncombined form of the same work. 183 | 184 | 6. Revised Versions of the GNU Lesser General Public License. 185 | 186 | The Free Software Foundation may publish revised and/or new versions 187 | of the GNU Lesser General Public License from time to time. Such new 188 | versions will be similar in spirit to the present version, but may 189 | differ in detail to address new problems or concerns. 190 | 191 | Each version is given a distinguishing version number. If the 192 | Library as you received it specifies that a certain numbered version 193 | of the GNU Lesser General Public License "or any later version" 194 | applies to it, you have the option of following the terms and 195 | conditions either of that published version or of any later version 196 | published by the Free Software Foundation. If the Library as you 197 | received it does not specify a version number of the GNU Lesser 198 | General Public License, you may choose any version of the GNU Lesser 199 | General Public License ever published by the Free Software Foundation. 200 | 201 | If the Library as you received it specifies that a proxy can decide 202 | whether future versions of the GNU Lesser General Public License shall 203 | apply, that proxy's public statement of acceptance of any version is 204 | permanent authorization for you to choose that version for the 205 | Library. 206 | {% endif %} 207 | {% if license == 'b' %} 208 | Copyright (c) {{year}}, {{name}} 209 | All rights reserved. 210 | 211 | Redistribution and use in source and binary forms, with or without 212 | modification, are permitted provided that the following conditions are met: 213 | 214 | 1. Redistributions of source code must retain the above copyright notice, this 215 | list of conditions and the following disclaimer. 216 | 2. Redistributions in binary form must reproduce the above copyright notice, 217 | this list of conditions and the following disclaimer in the documentation 218 | and/or other materials provided with the distribution. 219 | 220 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 221 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 222 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 223 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 224 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 225 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 226 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 227 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 228 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 229 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 230 | 231 | The views and conclusions contained in the software and documentation are those 232 | of the authors and should not be interpreted as representing official policies, 233 | either expressed or implied, of the {{project}} project. 234 | {% elseif license == 'i' %} 235 | Copyright {{year}} {{name}} 236 | 237 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 238 | 239 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 240 | {% elseif licence == 'w' %} 241 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 242 | Version 2, December 2004 243 | 244 | Copyright (C) {{year}} {{name}} <{{email}}> 245 | 246 | Everyone is permitted to copy and distribute verbatim or modified 247 | copies of this license document, and changing it is allowed as long 248 | as the name is changed. 249 | 250 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 251 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 252 | 253 | 0. You just DO WHAT THE FUCK YOU WANT TO. 254 | {% endif %} 255 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## adhocore/phint 2 | 3 | Initializes new PHP project with sane defaults using templates. 4 | It scaffolds PHP library &/or project to boost your productivity and save time. 5 | 6 | For already existing project, run with `--sync` flag to add missing stuffs, see [phint init](#init). 7 | 8 | Once you have files in your `src/` or `lib/` you can run [phint docs](#docs) to generate API like documentation in `.md` format 9 | and [phint test](#test) to generate basic test stubs with all the structures already maintained. 10 | 11 | It helps you be even more lazier! **phint** is continuously evolving and the plan is to make it [big](#todo). 12 | 13 | [![Latest Version](https://img.shields.io/github/release/adhocore/phint.svg?style=flat-square)](https://github.com/adhocore/phint/releases) 14 | [![Travis Build](https://img.shields.io/travis/adhocore/phint/master.svg?style=flat-square)](https://travis-ci.org/adhocore/phint?branch=master) 15 | [![Scrutinizer CI](https://img.shields.io/scrutinizer/g/adhocore/phint.svg?style=flat-square)](https://scrutinizer-ci.com/g/adhocore/phint/?branch=master) 16 | [![Codecov branch](https://img.shields.io/codecov/c/github/adhocore/phint/master.svg?style=flat-square)](https://codecov.io/gh/adhocore/phint) 17 | [![StyleCI](https://styleci.io/repos/108550679/shield)](https://styleci.io/repos/108550679) 18 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 19 | [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Scaffold+new+PHP+project+with+sane+defaults+using+templates&url=https://github.com/adhocore/phint&hashtags=php,template,scaffold,initproject) 20 | [![Support](https://img.shields.io/static/v1?label=Support&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/adhocore) 21 | 24 | 25 | 26 | ![Phint Preview](https://imgur.com/F6PkX9Z.png "Phint Preview") 27 | 28 | [Installation](#installation) · [Features](#features) · [Autocompletion](#autocompletion) · [Usage](#usage) · [phint init](#init) · [phint update](#update) · [phint docs](#docs) · [phint test](#test) · [Templating](#templating) 29 | 30 | > Phint is powered by [adhocore/cli](https://github.com/adhocore/php-cli) 31 | 32 | ## Installation 33 | 34 | > Requires PHP7. 35 | 36 | ### Manual 37 | 38 | Download `phint.phar` from [latest release](https://github.com/adhocore/phint/releases/latest). 39 | And use it like so `php /path/to/phint.phar [opts] [args]`. Hmm not cool. See Command section below. 40 | 41 | ### Command 42 | 43 | ```bash 44 | # get latest version (you need `jq`) 45 | LATEST_PHINT=`curl --silent "https://api.github.com/repos/adhocore/phint/releases/latest" | jq -r .tag_name` 46 | 47 | # download latest phint 48 | curl -sSLo ~/phint.phar "https://github.com/adhocore/phint/releases/download/$LATEST_PHINT/phint.phar" 49 | 50 | # make executable 51 | chmod +x ~/phint.phar 52 | sudo ln -s ~/phint.phar /usr/local/bin/phint 53 | 54 | # check 55 | phint --help 56 | ``` 57 | 58 | ## Features 59 | 60 | - generate dot files the likes of `.gitignore, .travis.yml, . editorconfig` etc 61 | - generate `LICENSE`, `README.md`, `composer.json` 62 | - generate `CHANGELOG.md` stub, `CONTRIBUTING.md` guide, `ISSUE_TEMPLATE.md` and `PULL_REQUEST_TEMPLATE.md` 63 | - generate binaries if any 64 | - git init 65 | - interactively ask and install all the dev and prod deps 66 | - generate `phpunit.xml`, test `bootstrap.php` 67 | - generate test stubs for all classes/methods corresponding to `src` (`phint test`) 68 | - generate docs for all public class/methods 69 | - export templates to chosen path so it can be customized (`phint export`) 70 | - use custom templates from a path specified by user 71 | - update its own self (`phint update`) 72 | 73 | ## Autocompletion 74 | 75 | The phint commands and options can be [autocompleted](https://github.com/adhocore/php-cli#autocompletion) if you use zsh shell with oh-my-zsh. 76 | 77 | Setting up auto complete: 78 | 79 | ```sh 80 | mkdir -p ~/.oh-my-zsh/custom/plugins/ahccli && cd ~/.oh-my-zsh/custom/plugins/ahccli 81 | 82 | [ -f ./ahccli.plugin.zsh ] || curl -sSLo ./ahccli.plugin.zsh https://raw.githubusercontent.com/adhocore/php-cli/master/ahccli.plugin.zsh 83 | 84 | echo compdef _ahccli phint >> ./ahccli.plugin.zsh 85 | 86 | chmod +x ./ahccli.plugin.zsh && source ./ahccli.plugin.zsh && cd - 87 | ``` 88 | 89 | Dont forget to [add](https://github.com/adhocore/php-cli#load-ahccli-plugin) `ahccli` into `plugins=(... ...)` list in `~/.zshrc` file. 90 | 91 | ## Usage 92 | 93 | It can be used to quickly spin off new project containing all basic and default stuffs. The quick steps are as follows: 94 | 95 | ```bash 96 | # See options/arguments 97 | phint init --help 98 | 99 | # OR (shortcut) 100 | phint i -h 101 | 102 | # Below command inits a brand new PHP project in `project-name` folder in current dir 103 | # Missing arguments are interactively collected 104 | phint init project-name 105 | 106 | # You can also use config file (with json) to read option values from 107 | phint init project-name --config phint.json 108 | ``` 109 | 110 | ## Commands 111 | 112 | Each of the commands below should be used like so: 113 | ```sh 114 | cd /path/to/project 115 | phint [--options] [args] 116 | ``` 117 | 118 | ### init 119 | 120 | > alias i 121 | 122 | Create and Scaffold a bare new PHP project. 123 | 124 | ***Parameters:*** 125 | 126 | Dont be intimidated by long list of parameters, you are not required to enter any of them 127 | as arguments as they are interactively collected when required. 128 | 129 | Also check [config](#example-config) on how to create a reusable json config so you can use `phint` like a *pro*. 130 | 131 | ``` 132 | Arguments: 133 | The project name without slashes 134 | 135 | Options: 136 | [-b, --bin...] Executable binaries 137 | [-c, --no-codecov] Disable codecov 138 | [-C, --config] JSON filepath to read config from 139 | [-d, --descr] Project description 140 | [-D, --dev...] Developer packages 141 | [-e, --email] Vendor email 142 | [-f, --force] Run even if the project exists 143 | [-G, --gh-template] Use `.github/` as template path 144 | By default uses `docs/` 145 | [-h, --help] Show help 146 | [-w, --keywords...] Project Keywords 147 | [-L, --license] License (m: MIT, g: GNULGPL, a: Apache2, b: BSDSimple, i: ISC, w: WTFPL) 148 | [-n, --name] Vendor full name 149 | [-N, --namespace] Root namespace (use `/` separator) 150 | [-g, --package] Packagist name (Without vendor handle) 151 | [-p, --path] The project path (Auto resolved) 152 | [-P, --php] Minimum PHP version 153 | [-R, --req...] Required packages 154 | [-s, --no-scrutinizer] Disable scrutinizer 155 | [-l, --no-styleci] Disable StyleCI 156 | [-S, --sync] Only create missing files 157 | Use with caution, take backup if needed 158 | [-t, --no-travis] Disable travis 159 | [-T, --type] Project type 160 | [-u, --username] Vendor handle/username 161 | [-z, --using] Reference package 162 | [-y, --year] License Year 163 | 164 | Usage Examples: 165 | phint init --force --descr "Awesome project" --name "YourName" --email you@domain.com 166 | phint init --using laravel/lumen --namespace Project/Api --type project 167 | phint init --php 7.0 --config /path/to/json --dev mockery/mockery --req adhocore/cli 168 | ``` 169 | 170 | ### Example config 171 | 172 | Parameters sent via command args will have higher precedence than values from config file (`-C --config`). 173 | 174 | What can you put in config? Anything but we suggest you put only known options (check `$ phint init --help`) 175 | 176 | ```json 177 | { 178 | "type": "library", 179 | "namespace": "Ahc", 180 | "username": "adhocore", 181 | "name": "Jitendra Adhikari", 182 | "email": "jiten.adhikary@gmail.com", 183 | "php": "7.0", 184 | "codecov": false, 185 | "...": "..." 186 | } 187 | ``` 188 | 189 | --- 190 | ## update 191 | 192 | > alias u 193 | 194 | Update Phint to lastest version or rollback to earlier locally installed version. 195 | 196 | ***Parameters:*** 197 | 198 | ``` 199 | Options: 200 | [-h, --help] Show help 201 | [-r, --rollback] Rollback to earlier version 202 | 203 | Usage Examples: 204 | phint update Updates to latest version 205 | phint u Also updates to latest version 206 | phint update -r Rolls back to prev version 207 | phint u --rollback Also rolls back to prev version 208 | ``` 209 | 210 | --- 211 | ## docs 212 | 213 | > alias d 214 | 215 | Generate docs (`.md`) for all public classes and methods from their docblocks. 216 | 217 | Ideally you would run it on existing project **or** after you create/update `src/` files. 218 | 219 | ***Parameters:*** 220 | 221 | ``` 222 | Options: 223 | [-a, --with-abstract] Create docs for abstract/interface class 224 | [-h, --help] Show help 225 | [-o, --output] Output file (default README.md). For old project you should use something else 226 | (OR mark region with and to inject docs) 227 | 228 | Usage Examples: 229 | phint docs If there is `` and `` region 230 | Injects new doc in between them otherwise appends to bottom 231 | phint d -o docs/api.md Writes to docs/api.md (Same rule applies regarding inject/append) 232 | ``` 233 | 234 | ### Sample docs 235 | 236 | ***PHP code*** 237 | 238 | ```php 239 | namespace Abc; 240 | 241 | /** 242 | * This is dummy class. 243 | * 244 | * It does nothing as of now. 245 | * Maybe you could fix it? 246 | */ 247 | class Dummy 248 | { 249 | /** 250 | * Alpha beta. 251 | * 252 | * Example: 253 | * 254 | * 255 | * $dummy = new Dummy; 256 | * $dummy->alpha('john', true); 257 | * // '...' 258 | * 259 | * 260 | * @param string $name 261 | * @param bool $flag 262 | * 263 | * @return string|null 264 | */ 265 | public function alpha($name, $flag) 266 | { 267 | // 268 | } 269 | } 270 | ``` 271 | 272 | ***Generated Markdown*** 273 | 274 | ```md 275 | ## Dummy 276 | 277 | ```php 278 | use Abc\Dummy; 279 | \``` 280 | 281 | > This is dummy class. 282 | 283 | It does nothing as of now. 284 | Maybe you could fix it? 285 | 286 | ### alpha() 287 | 288 | > Alpha beta. 289 | 290 | ```php 291 | alpha(string $name, bool $flag): string|null 292 | \``` 293 | 294 | Example: 295 | 296 | ```php 297 | $dummy = new Dummy; 298 | $dummy->alpha('john', true); 299 | // '...' 300 | \``` 301 | ``` 302 | 303 | ***Preview*** 304 | 305 | ## Dummy 306 | 307 | ```php 308 | use Ahc\Dummy; 309 | ``` 310 | 311 | > This is dummy class. 312 | 313 | It does nothing as of now. 314 | Maybe you could fix it? 315 | 316 | ### alpha() 317 | 318 | > Alpha beta. 319 | 320 | ```php 321 | alpha(string $name, bool $flag): string|null 322 | ``` 323 | 324 | Example: 325 | 326 | ```php 327 | $dummy = new Dummy; 328 | $dummy->alpha('john', true); 329 | // '...' 330 | ``` 331 | 332 | --- 333 | ## test 334 | 335 | > alias t 336 | 337 | Generate test files with proper classes and test methods analogous to their source counterparts. 338 | If a test class already exists, it is skipped. In future we may append test stubs for new methods. 339 | 340 | Ideally you would run it on existing project **or** after you create/update `src/` files. 341 | 342 | ***Parameters:*** 343 | 344 | ``` 345 | Options: 346 | [-a, --with-abstract] Create stub for abstract/interface class 347 | [-h, --help] Show help 348 | [-n, --naming] Test method naming format 349 | (t: testMethod | m: test_method | i: it_tests_) 350 | [-p, --phpunit] Base PHPUnit class to extend from 351 | [-s, --no-setup] Dont add setup method 352 | [-t, --no-teardown] Dont add teardown method 353 | 354 | Usage Examples: 355 | phint test -n i With `it_` naming 356 | phint t --no-teardown Without `tearDown()` 357 | phint test -a With stubs for abstract/interface 358 | ``` 359 | 360 | ### Sample test 361 | 362 | Generated `tests/Dummy.php` for [Abc\\Dummy](#sample-docs) above: 363 | 364 | ```php 365 | dummy = new Dummy; 387 | } 388 | 389 | public function testAlpha() 390 | { 391 | $actual = $this->dummy->alpha(); 392 | 393 | // $this->assertSame('', $actual); 394 | } 395 | } 396 | ``` 397 | 398 | --- 399 | ## Templating 400 | 401 | > `phint export --to ~/myphint` 402 | 403 | So you would like to have your own templates and customize `phint` to your taste! 404 | 405 | First you need to create a directory root (of any name, eg: `myphint`) with structure that looks like: 406 | 407 | ```tree 408 | myphint 409 | ├── CHANGELOG.md.twig 410 | ├── composer.json.twig 411 | ├── CONTRIBUTING.md.twig 412 | ├── docs 413 | │   ├── docs.twig 414 | │   ├── ISSUE_TEMPLATE.md.twig 415 | │   └── PULL_REQUEST_TEMPLATE.md.twig 416 | ├── .editorconfig.twig 417 | ├── .env.example.twig 418 | ├── .gitignore.twig 419 | ├── LICENSE.twig 420 | ├── package.json.twig 421 | ├── phpunit.xml.dist.twig 422 | ├── README.md.twig 423 | ├── tests 424 | │   ├── bootstrap.php.twig 425 | │   └── test.twig 426 | └── .travis.yml.twig 427 | ``` 428 | 429 | Note that you dont need to have all the files there in new directory just pick the ones you would like to customize and start hacking. 430 | 431 | Luckily you **dont** have to create these templates yourself, just run `phint export --to ~/myphint`! 432 | 433 | **Pro Tip** 434 | You can actually introduce any new template as long as their extension is `.twig`. 435 | Such templates are *only* used by `phint init` command. Check [Template variables](#template-variables). 436 | 437 | After you are done customizing these templates you can use them in each of the *phint* commands like so 438 | 439 | ```sh 440 | phint init project --template ~/myphint 441 | phint docs --template ~/myphint 442 | phint test --template ~/myphint 443 | ``` 444 | 445 | The short option name for `--template` is `-x`. 446 | 447 | #### Template variables 448 | 449 | Here's what parameters these templates would receive when run: 450 | 451 | - ***[docs/docs.twig](https://github.com/adhocore/phint/blob/master/resources/docs/docs.twig):*** classes [metadata](#class-metadata) and [docs parameters](#docs) 452 | - ***[tests/test.twig](https://github.com/adhocore/phint/blob/master/resources/tests/test.twig):*** class [metadata](#class-metadata) and [test parameters](#test) 453 | - ***Everything else:*** [init parameters](#init) 454 | 455 | ***Metadata*** 456 | 457 | - The `docs` and `test` commands read and use source files metadata. 458 | - The `docs.twig` template recieves metadata collection of all classes at once. 459 | - The `test.twig` template recieves metadata unit of one class at a time. 460 | 461 | ### Class metadata 462 | 463 | Example metadata for [Abc\\Dummy](#sample-docs) above: 464 | 465 | ```php 466 | [ 467 | 'namespace' => 'Abc', 468 | 'classFqcn' => 'Abc\\Dummy', 469 | 'classPath' => '/home/user/projects/src/Dummy.php', 470 | 'name' => 'Dummy', 471 | 'className' => 'Dummy', 472 | 'isTrait' => false, 473 | 'isAbstract' => false, 474 | 'isInterface' => false, 475 | 'newable' => true, 476 | 'title' => 'This is dummy class.', 477 | 'texts' => [ 478 | 'It does nothing as of now.', 479 | 'Maybe you could fix it?', 480 | ], 481 | 'methods' => [ 482 | 'alpha' => [ 483 | 'name' => 'alpha', 484 | 'inClass' => 'Abc\\Dummy', 485 | 'isStatic' => false, 486 | 'isFinal' => false, 487 | 'isPublic' => true, 488 | 'isAbstract' => false, 489 | 'maybeMagic' => false, 490 | 'title' => 'Alpha beta.', 491 | 'texts' => [ 492 | 'Example:', 493 | '', 494 | '$dummy = new Dummy;', 495 | '$dummy->alpha(\'john\', true);', 496 | '// \'...\'', 497 | '', 498 | ], 499 | 'return' => 'string|null', 500 | 'params' => [ 501 | 'string $name', 502 | 'bool $flag', 503 | ], 504 | ], 505 | // more methods ... 506 | ], 507 | ]; 508 | ``` 509 | 510 | ## Todo 511 | 512 | Including but not limited to: 513 | 514 | - [x] README.md/Docs generator 515 | - [x] Test files generator 516 | - [x] Support user templates 517 | - [ ] Test stubs for new methods 518 | 519 | ## License 520 | 521 | > © 2017-2020, [Jitendra Adhikari](https://github.com/adhocore) | [MIT](./LICENSE) 522 | 523 | ### Credits 524 | 525 | This library is release managed by [please](https://github.com/adhocore/please). 526 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.6.1](https://github.com/adhocore/phint/releases/tag/0.6.1) (2020-01-03) 2 | 3 | ### Miscellaneous 4 | - **Composer**: Bump adhocore/cli (Jitendra Adhikari) [_b48e4d3_](https://github.com/adhocore/phint/commit/b48e4d3) 5 | 6 | 7 | ## [0.6.0](https://github.com/adhocore/phint/releases/tag/0.6.0) (2020-01-03) 8 | 9 | ### Miscellaneous 10 | - **Composer**: Bump cli and json-comment (Jitendra Adhikari) [_e872c21_](https://github.com/adhocore/phint/commit/e872c21) 11 | 12 | 13 | ## [v0.5.4](https://github.com/adhocore/phint/releases/tag/v0.5.4) (2020-01-03) 14 | 15 | ### Miscellaneous 16 | - **Composer**: Bump adhocore/cli (Jitendra Adhikari) [_862cf7a_](https://github.com/adhocore/phint/commit/862cf7a) 17 | 18 | ### Documentations 19 | - Add credits, update license year (Jitendra Adhikari) [_2dfdf67_](https://github.com/adhocore/phint/commit/2dfdf67) 20 | 21 | 22 | ## [v0.5.3](https://github.com/adhocore/phint/releases/tag/v0.5.3) (2019-12-31) 23 | 24 | ### Bug Fixes 25 | - **Phpunit**: Rm xml.syntaxCheck (Jitendra Adhikari) [_50b872b_](https://github.com/adhocore/phint/commit/50b872b) 26 | 27 | ### Miscellaneous 28 | - **Template**: Update test stuffs (Jitendra Adhikari) [_d8ccb9b_](https://github.com/adhocore/phint/commit/d8ccb9b) 29 | - **Travis**: Script (Jitendra Adhikari) [_6322753_](https://github.com/adhocore/phint/commit/6322753) 30 | - **Composer**: Tweak script.test (Jitendra Adhikari) [_fd12251_](https://github.com/adhocore/phint/commit/fd12251) 31 | - **Deps**: Bump cli (Jitendra Adhikari) [_c20ca7e_](https://github.com/adhocore/phint/commit/c20ca7e) 32 | 33 | ### Builds 34 | - **Deps**: Update adhocore/cli requirement from ^0.6 to ^0.6 || ^0.7 (dependabot-preview[bot]) [_31b6101_](https://github.com/adhocore/phint/commit/31b6101) 35 | 36 | 37 | ## [v0.5.2](https://github.com/adhocore/phint/releases/tag/v0.5.2) (2019-12-28) 38 | 39 | ### Miscellaneous 40 | - Bump adhocore/cli (Jitendra Adhikari) [_dce602c_](https://github.com/adhocore/phint/commit/dce602c) 41 | 42 | ### Builds 43 | - **Deps**: Update adhocore/cli requirement from ^0.5 to ^0.5 || ^0.6 (dependabot-preview[bot]) [_a35483a_](https://github.com/adhocore/phint/commit/a35483a) 44 | 45 | 46 | ## [v0.5.1](https://github.com/adhocore/phint/releases/tag/v0.5.1) (2019-12-16) 47 | 48 | ### Miscellaneous 49 | - **Template**: Bootstrapped by phint (Jitendra Adhikari) [_7f33932_](https://github.com/adhocore/phint/commit/7f33932) 50 | - Bump adhocore/cli (Jitendra Adhikari) [_05791b3_](https://github.com/adhocore/phint/commit/05791b3) 51 | 52 | ### Builds 53 | - **Deps**: Update adhocore/cli requirement from ^0.3 to ^0.3 || ^0.5 (dependabot-preview[bot]) [_69d2322_](https://github.com/adhocore/phint/commit/69d2322) 54 | 55 | 56 | ## [v0.5.0] 2019-03-05 14:03:22 UTC 57 | 58 | - fix(basecmd): autoload classes (Jitendra Adhikari) 59 | - feat(util.composer): add force autoload (Jitendra Adhikari) 60 | 61 | ## [v0.4.1] 2019-02-09 15:02:20 UTC 62 | 63 | - [f303d13](https://github.com/adhocore/phint/commit/f303d13) build(travis): no nightly (Jitendra Adhikari) 64 | - [b8d3bc3](https://github.com/adhocore/phint/commit/b8d3bc3) build(travis): php 7.3 (Jitendra Adhikari) 65 | - [9fa7a31](https://github.com/adhocore/phint/commit/9fa7a31) chore: also list php 7.3 (Jitendra Adhikari) 66 | 67 | ## [v0.4.0] 2018-09-23 03:09:47 UTC 68 | 69 | - [fce6e4e](https://github.com/adhocore/phint/commit/fce6e4e) fix(util.executable): log if only writable (Jitendra Adhikari) 70 | - [b9d8c09](https://github.com/adhocore/phint/commit/b9d8c09) chore: composer remove unused deps (Jitendra Adhikari) 71 | - [e178127](https://github.com/adhocore/phint/commit/e178127) refactor(util.executable): log output, cleanup unused imports (Jitendra Adhikari) 72 | - [c53e469](https://github.com/adhocore/phint/commit/c53e469) feat(util.executable): add windows binary finder (Jitendra Adhikari) 73 | - [b51feee](https://github.com/adhocore/phint/commit/b51feee) refactor(util.executable): use ahc/cli/shell and findBinary (Jitendra Adhikari) 74 | - [56747a3](https://github.com/adhocore/phint/commit/56747a3) chore: composer deps bump (Jitendra Adhikari) 75 | 76 | ## [v0.3.6] 2018-09-20 15:09:16 UTC 77 | 78 | - [0485057](https://github.com/adhocore/phint/commit/0485057) fix(util.meta): param must be array (Jitendra Adhikari) 79 | - [3f3fc5e](https://github.com/adhocore/phint/commit/3f3fc5e) test: fix meta test (Jitendra Adhikari) 80 | - [bb27989](https://github.com/adhocore/phint/commit/bb27989) test: fix git test (Jitendra Adhikari) 81 | - [ef48c80](https://github.com/adhocore/phint/commit/ef48c80) chore(docs.tmpl): make collaspible. add throws. fix return/param (Jitendra Adhikari) 82 | - [a8898a6](https://github.com/adhocore/phint/commit/a8898a6) refactor(util.metadata): add throws, improve return/params (Jitendra Adhikari) 83 | - [72bc7fa](https://github.com/adhocore/phint/commit/72bc7fa) fix(twig.generator): fix call() fn (Jitendra Adhikari) 84 | - [92230ae](https://github.com/adhocore/phint/commit/92230ae) fix(initcmd): add missing path arg in projectExists() (Jitendra Adhikari) 85 | - [a443885](https://github.com/adhocore/phint/commit/a443885) docs: update autocomplete section (Jitendra Adhikari) 86 | - [79029f9](https://github.com/adhocore/phint/commit/79029f9) Intialize Util Composer, Git and Metadata tests (peter279k) 87 | 88 | ## [v0.3.5] 2018-09-01 12:09:26 UTC 89 | 90 | - [376fea9](https://github.com/adhocore/phint/commit/376fea9) chore: support wtfpl license [closes #52] (Jitendra Adhikari) 91 | - [487681b](https://github.com/adhocore/phint/commit/487681b) chore: add wtfpl license support (Jitendra Adhikari) 92 | - [a0c040b](https://github.com/adhocore/phint/commit/a0c040b) docs: template with anchor and jumps [closes #47] (Jitendra Adhikari) 93 | - [f5f2242](https://github.com/adhocore/phint/commit/f5f2242) Add generateTest and generateDoc tests (peter279k) 94 | 95 | ## [v0.3.4] 2018-08-31 15:08:19 UTC 96 | 97 | - [5003ec4](https://github.com/adhocore/phint/commit/5003ec4) fix(initcmd): donot prompt --template (Jitendra Adhikari) 98 | - [1356bc3](https://github.com/adhocore/phint/commit/1356bc3) fix(basecmd): realpath fails on phar (Jitendra Adhikari) 99 | - [03616f3](https://github.com/adhocore/phint/commit/03616f3) docs: reorder test section, add sample test (Jitendra Adhikari) 100 | 101 | ## [v0.3.3] 2018-08-31 12:08:46 UTC 102 | 103 | - [63fb737](https://github.com/adhocore/phint/commit/63fb737) fix(testcmd): the test namespace for file in root was broken (Jitendra Adhikari) 104 | - [1ece271](https://github.com/adhocore/phint/commit/1ece271) Add more Path tests (peter279k) 105 | 106 | ## [v0.3.2] 2018-08-28 14:08:36 UTC 107 | 108 | - [1afb621](https://github.com/adhocore/phint/commit/1afb621) chore(initcmd): update option desc (Jitendra Adhikari) 109 | - [2f16c43](https://github.com/adhocore/phint/commit/2f16c43) chore(updcmd): show error msg if fetch latest release failed (Jitendra Adhikari) 110 | 111 | ## [v0.3.1] 2018-08-25 14:08:41 UTC 112 | 113 | - [f511845](https://github.com/adhocore/phint/commit/f511845) fix(exportcmd): realpath() fails when in phar (Jitendra Adhikari) 114 | 115 | ## [v0.3.0] 2018-08-25 13:08:23 UTC 116 | 117 | - [06bb162](https://github.com/adhocore/phint/commit/06bb162) feat(util.meta): prefer reflection type over docblocks (Jitendra Adhikari) 118 | - [ec3f440](https://github.com/adhocore/phint/commit/ec3f440) docs(readme): add metadata example (Jitendra Adhikari) 119 | - [b60d817](https://github.com/adhocore/phint/commit/b60d817) docs(readme): add templating, improve other parts (Jitendra Adhikari) 120 | - [4c1dab2](https://github.com/adhocore/phint/commit/4c1dab2) fix(basecmd): typo (Jitendra Adhikari) 121 | - [7537782](https://github.com/adhocore/phint/commit/7537782) feat(bin): reg export cmd (Jitendra Adhikari) 122 | - [c1eba08](https://github.com/adhocore/phint/commit/c1eba08) fix(util.path): expand . to cwd (Jitendra Adhikari) 123 | - [6ce702e](https://github.com/adhocore/phint/commit/6ce702e) feat(exportcmd): add template export for customization (Jitendra Adhikari) 124 | - [34a2e02](https://github.com/adhocore/phint/commit/34a2e02) refactor(util.meta): add class namespace and name (Jitendra Adhikari) 125 | - [7c5767a](https://github.com/adhocore/phint/commit/7c5767a) refactor(twig.gen): generated -> count (Jitendra Adhikari) 126 | - [964ffbe](https://github.com/adhocore/phint/commit/964ffbe) refactor(testcmd): remove dumpautoload, reuse metadata logic from base command (Jitendra Adhikari) 127 | - [dc07912](https://github.com/adhocore/phint/commit/dc07912) fix(tmpl.test): as per new metadata (Jitendra Adhikari) 128 | - [24905ec](https://github.com/adhocore/phint/commit/24905ec) fix(util.meta): class path and fqcn (Jitendra Adhikari) 129 | - [a68b159](https://github.com/adhocore/phint/commit/a68b159) test(fix): generator (Jitendra Adhikari) 130 | - [46f827f](https://github.com/adhocore/phint/commit/46f827f) fix(twig.gen): doGenerate() expects relative path (Jitendra Adhikari) 131 | - [9f099d3](https://github.com/adhocore/phint/commit/9f099d3) refactor(docscmd): metadata logic is now extraxted away (Jitendra Adhikari) 132 | - [813204e](https://github.com/adhocore/phint/commit/813204e) fix(twig.gen): inject old doc was broken for new file (Jitendra Adhikari) 133 | - [25fbc5d](https://github.com/adhocore/phint/commit/25fbc5d) feat(basecmd): add metadata helper (Jitendra Adhikari) 134 | - [0bb087b](https://github.com/adhocore/phint/commit/0bb087b) fix(tmpl.docs): filter out abstract or non public methods (Jitendra Adhikari) 135 | - [fd4dc0c](https://github.com/adhocore/phint/commit/fd4dc0c) feat(util.meta): add method metadata: name, inClass, maybeMagic (Jitendra Adhikari) 136 | - [9314388](https://github.com/adhocore/phint/commit/9314388) refactor(util.meta): remove docblock arg, docblocks are always included (Jitendra Adhikari) 137 | - [5f8dabe](https://github.com/adhocore/phint/commit/5f8dabe) feat(util.meta): add method metadata util (Jitendra Adhikari) 138 | - [9883ab7](https://github.com/adhocore/phint/commit/9883ab7) refactor(util.meta): organize stuffs (Jitendra Adhikari) 139 | - [ba42bd0](https://github.com/adhocore/phint/commit/ba42bd0) feat(util.meta): add class metadata util (Jitendra Adhikari) 140 | - [ef53aba](https://github.com/adhocore/phint/commit/ef53aba) chore: bin/phint php7.1 (Jitendra Adhikari) 141 | - [8aaf410](https://github.com/adhocore/phint/commit/8aaf410) fix(twig.gen): dont generate .travis.yml when travis is disabled (-t) (Jitendra Adhikari) 142 | - [cec7dba](https://github.com/adhocore/phint/commit/cec7dba) refactor(twig.gen): templatePath -> templatePaths, fix: dont generate same template (based on relativePath) twice (Jitendra Adhikari) 143 | - [bc633a2](https://github.com/adhocore/phint/commit/bc633a2) refactor(testcmd): generate() params. dont prompt --template (Jitendra Adhikari) 144 | - [6111638](https://github.com/adhocore/phint/commit/6111638) fix(util.path): make expand() public (Jitendra Adhikari) 145 | - [9af6f4e](https://github.com/adhocore/phint/commit/9af6f4e) feat(util.path): add expand() (Jitendra Adhikari) 146 | - [4ab7e03](https://github.com/adhocore/phint/commit/4ab7e03) feat(testcmd): support user template with higher precedence (Jitendra Adhikari) 147 | - [b55af1c](https://github.com/adhocore/phint/commit/b55af1c) feat(docscmd): support user template with higher precedence (Jitendra Adhikari) 148 | - [3d89fd8](https://github.com/adhocore/phint/commit/3d89fd8) refactor(util.path): get relative path support multiple paths (Jitendra Adhikari) 149 | - [5d66504](https://github.com/adhocore/phint/commit/5d66504) refactor(testcmd): move _workDir to base class, update templatePath (Jitendra Adhikari) 150 | - [4e1f66a](https://github.com/adhocore/phint/commit/4e1f66a) feat(initcmd): add -x,--template, multi templates support, fix option descr, missing io (Jitendra Adhikari) 151 | - [dcd06f6](https://github.com/adhocore/phint/commit/dcd06f6) refactor: move _workDir to base class, update templatePath (Jitendra Adhikari) 152 | - [f1113fb](https://github.com/adhocore/phint/commit/f1113fb) feat(basecmd): add template paths helper (Jitendra Adhikari) 153 | - [b1f3225](https://github.com/adhocore/phint/commit/b1f3225) refactor: move _workDir to base class (Jitendra Adhikari) 154 | - [ac5c27f](https://github.com/adhocore/phint/commit/ac5c27f) refactor(initcmd): update option descriptions (Jitendra Adhikari) 155 | 156 | ## [v0.2.1] 2018-08-23 13:08:23 UTC 157 | 158 | - [ade9409](https://github.com/adhocore/phint/commit/ade9409) fix(twig.gen): docs append (Jitendra Adhikari) 159 | - [f6f8537](https://github.com/adhocore/phint/commit/f6f8537) docs: add sample docs generated by docs (Jitendra Adhikari) 160 | - [243695d](https://github.com/adhocore/phint/commit/243695d) fix(tmpl.docs): spacing (Jitendra Adhikari) 161 | 162 | ## [v0.2.0] 2018-08-23 12:08:54 UTC 163 | 164 | - [9e377b5](https://github.com/adhocore/phint/commit/9e377b5) fix(testcmd): show options for test naming (Jitendra Adhikari) 165 | - [bcf8914](https://github.com/adhocore/phint/commit/bcf8914) docs: add docs for 'docs' command, update some texts (Jitendra Adhikari) 166 | - [39138a2](https://github.com/adhocore/phint/commit/39138a2) chore(tmpl.contrib): add more texts (Jitendra Adhikari) 167 | - [96069d2](https://github.com/adhocore/phint/commit/96069d2) test: we now return count (Jitendra Adhikari) 168 | - [7f79c04](https://github.com/adhocore/phint/commit/7f79c04) fix(docscmd): typo (Jitendra Adhikari) 169 | - [984b11c](https://github.com/adhocore/phint/commit/984b11c) build(travis): cache (Jitendra Adhikari) 170 | - [143fc18](https://github.com/adhocore/phint/commit/143fc18) chore: composer repo (Jitendra Adhikari) 171 | - [733a80d](https://github.com/adhocore/phint/commit/733a80d) chore(build): min php 7.1 (Jitendra Adhikari) 172 | - [63e710d](https://github.com/adhocore/phint/commit/63e710d) fix(docscmd): exclude magic methods (Jitendra Adhikari) 173 | - [76f09a1](https://github.com/adhocore/phint/commit/76f09a1) feat(twig.gen): add docs generator (Jitendra Adhikari) 174 | - [ed84c52](https://github.com/adhocore/phint/commit/ed84c52) feat(path): add read() api (Jitendra Adhikari) 175 | - [ad6ded6](https://github.com/adhocore/phint/commit/ad6ded6) refactor(docscmd): sanitize output path, use default (Jitendra Adhikari) 176 | - [80f00a2](https://github.com/adhocore/phint/commit/80f00a2) chore(tmpl): readme needs License/Contributing link [closes #40] (Jitendra Adhikari) 177 | - [7340457](https://github.com/adhocore/phint/commit/7340457) chore(tmpl): readme to contain docs placeholder (Jitendra Adhikari) 178 | - [9491b13](https://github.com/adhocore/phint/commit/9491b13) fix(tmpl.docs): only use :: for static (Jitendra Adhikari) 179 | - [b30b8ba](https://github.com/adhocore/phint/commit/b30b8ba) fix(tmpl.docs): only use :: for static (Jitendra Adhikari) 180 | - [8c0e3bf](https://github.com/adhocore/phint/commit/8c0e3bf) fix(docscmd): return type meta (Jitendra Adhikari) 181 | - [595db1a](https://github.com/adhocore/phint/commit/595db1a) refactor(docscmd): update msg, rename vars, include class name (Jitendra Adhikari) 182 | - [cde7314](https://github.com/adhocore/phint/commit/cde7314) feat(tmpl.docs): add docs template (Jitendra Adhikari) 183 | - [7e4feb9](https://github.com/adhocore/phint/commit/7e4feb9) chore(tmpl): update gh templates (Jitendra Adhikari) 184 | - [efa54d3](https://github.com/adhocore/phint/commit/efa54d3) refactor(util.path): improve find files, add typehints (Jitendra Adhikari) 185 | - [4041d41](https://github.com/adhocore/phint/commit/4041d41) refactor(generator): typehints and stuffs, cleanup redundant (Jitendra Adhikari) 186 | - [e783059](https://github.com/adhocore/phint/commit/e783059) refactor(collisionhandler): typehints and stuffs (Jitendra Adhikari) 187 | - [20336ca](https://github.com/adhocore/phint/commit/20336ca) feat: integrate docblock to read metadata (Jitendra Adhikari) 188 | - [f46881c](https://github.com/adhocore/phint/commit/f46881c) chore: add docblock lib (Jitendra Adhikari) 189 | - [86b7cfe](https://github.com/adhocore/phint/commit/86b7cfe) chore(tmpl): docs.twig [stub] (Jitendra Adhikari) 190 | - [fb99c9f](https://github.com/adhocore/phint/commit/fb99c9f) feat(docscmd): add docs cmd [wip] (Jitendra Adhikari) 191 | - [a7c85b0](https://github.com/adhocore/phint/commit/a7c85b0) feat(util.path): add load classes (Jitendra Adhikari) 192 | - [e2c4f58](https://github.com/adhocore/phint/commit/e2c4f58) fix(util.path): typo (Jitendra Adhikari) 193 | - [b3f52d1](https://github.com/adhocore/phint/commit/b3f52d1) feat: reg docs cmd (Jitendra Adhikari) 194 | - [6a343f7](https://github.com/adhocore/phint/commit/6a343f7) fix(twig.gen): docs is command template too (Jitendra Adhikari) 195 | - [9495bd6](https://github.com/adhocore/phint/commit/9495bd6) feat(util.path): find files in paths of given ext (Jitendra Adhikari) 196 | - [0725a9e](https://github.com/adhocore/phint/commit/0725a9e) fix(tmpl.test): test stubs template (Jitendra Adhikari) 197 | - [a518bb9](https://github.com/adhocore/phint/commit/a518bb9) feat(twig.generator): add ucfirst and generic fn (Jitendra Adhikari) 198 | 199 | ## [v0.1.1] 2018-08-22 00:08:21 UTC 200 | 201 | - [2cb1a6f](https://github.com/adhocore/phint/commit/2cb1a6f) chore(tmpl.ghtmpl): fix title, and comment (Jitendra Adhikari) 202 | - [db4ab22](https://github.com/adhocore/phint/commit/db4ab22) chore: add contributing (Jitendra Adhikari) 203 | - [34f2bf4](https://github.com/adhocore/phint/commit/34f2bf4) chore: issue/pr template (Jitendra Adhikari) 204 | - [88e47a2](https://github.com/adhocore/phint/commit/88e47a2) chore: issue/pr template (Jitendra Adhikari) 205 | - [286e7a8](https://github.com/adhocore/phint/commit/286e7a8) docs: update init options (Jitendra Adhikari) 206 | - [abcf36e](https://github.com/adhocore/phint/commit/abcf36e) refactor: readme > README (Jitendra Adhikari) 207 | 208 | ## [v0.1.0] 2018-08-21 15:08:03 UTC 209 | 210 | - [5639cce](https://github.com/adhocore/phint/commit/5639cce) refactor(twig.gen): dont override on sync, use empty checks [closes #42] (Jitendra Adhikari) 211 | - [2669328](https://github.com/adhocore/phint/commit/2669328) feat(initcmd): add projectExists handler (Jitendra Adhikari) 212 | - [4569a86](https://github.com/adhocore/phint/commit/4569a86) feat(initcmd): sync mode (Jitendra Adhikari) 213 | - [b79cf7c](https://github.com/adhocore/phint/commit/b79cf7c) refactor(twig.generator): support path override for gh template, extract logic to methods [closes #39] (Jitendra Adhikari) 214 | - [5a8acff](https://github.com/adhocore/phint/commit/5a8acff) feat(initcmd): add gh-template path override opt (Jitendra Adhikari) 215 | - [8f51517](https://github.com/adhocore/phint/commit/8f51517) chore: bump adhocore/cli (Jitendra Adhikari) 216 | - [77cb89c](https://github.com/adhocore/phint/commit/77cb89c) fix(testcmd): skip exceptions [closes #37] (Jitendra Adhikari) 217 | - [eb4d5b1](https://github.com/adhocore/phint/commit/eb4d5b1) build(travis): cache composer [closes #38] (Jitendra Adhikari) 218 | - [fd95a34](https://github.com/adhocore/phint/commit/fd95a34) docs: add `$ phint` preview (Jitendra Adhikari) 219 | 220 | ## [v0.0.13] 2018-08-19 14:08:54 UTC 221 | 222 | - [e112041](https://github.com/adhocore/phint/commit/e112041) fix(tmpl): travis badge (Jitendra Adhikari) 223 | - [9d3cb55](https://github.com/adhocore/phint/commit/9d3cb55) docs: add features (Jitendra Adhikari) 224 | 225 | ## [v0.0.12] 2018-08-18 09:08:03 UTC 226 | 227 | - [3984dac](https://github.com/adhocore/phint/commit/3984dac) feat(tmpl): support PR template [closes #35] 228 | - [cb4b5b7](https://github.com/adhocore/phint/commit/cb4b5b7) feat(tmpl): support issue template 229 | - [64e5ce9](https://github.com/adhocore/phint/commit/64e5ce9) chore: add thank you 230 | - [7c31cab](https://github.com/adhocore/phint/commit/7c31cab) feat: generate contributing.md [closes #36] 231 | 232 | ## [v0.0.11] 2018-08-18 03:08:20 UTC 233 | 234 | - [678a9c5](https://github.com/adhocore/phint/commit/678a9c5) feat(twig.gmdate): add gmdate fn [closes #34] 235 | - [f1f1f7d](https://github.com/adhocore/phint/commit/f1f1f7d) feat(tmpl): add changelog 236 | - [973e37d](https://github.com/adhocore/phint/commit/973e37d) feat(license): support ISC [closes #31] 237 | - [aecafe6](https://github.com/adhocore/phint/commit/aecafe6) chore(tmpl): readme -> README [closes #33] 238 | --------------------------------------------------------------------------------