diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 7d80b17b..6985e617 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -9,8 +9,8 @@ on: - "master" env: - MIN_COVERED_MSI: 93 - MIN_MSI: 89 + MIN_COVERED_MSI: 95 + MIN_MSI: 91 REQUIRED_PHP_EXTENSIONS: "mbstring" jobs: diff --git a/.php_cs b/.php_cs index bdcbd01a..313d7a88 100644 --- a/.php_cs +++ b/.php_cs @@ -14,31 +14,19 @@ declare(strict_types=1); use Ergebnis\License; use Ergebnis\PhpCsFixer\Config; -$range = License\Range::since( - License\Year::fromString('2020'), - new \DateTimeZone('UTC') -); - -$holder = License\Holder::fromString('Andreas Möller'); - -$file = License\File::create( +$license = License\Type\MIT::markdown( __DIR__ . '/LICENSE.md', - License\Template::fromFile(__DIR__ . '/resource/license/MIT.md'), - $range, - $holder -); - -$file->save(); - -$header = License\Header::create( - License\Template::fromFile(__DIR__ . '/resource/header.txt'), - $range, - $holder, - $file, + License\Range::since( + License\Year::fromString('2020'), + new \DateTimeZone('UTC') + ), + License\Holder::fromString('Andreas Möller'), License\Url::fromString('https://github.com/ergebnis/license') ); -$config = Config\Factory::fromRuleSet(new Config\RuleSet\Php71($header->toString())); +$license->save(); + +$config = Config\Factory::fromRuleSet(new Config\RuleSet\Php71($license->header())); $config->getFinder() ->ignoreDotFiles(false) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80cb780a..8b443f73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ For a full diff see [`675601b...master`][675601b...master]. * Added `Template` ([#20]), by [@localheinz] * Added `File` ([#33]), by [@localheinz] * Added `Header` ([#34]), by [@localheinz] +* Added `MIT` ([#38]), by [@localheinz] [675601b...master]: https://github.com/ergebnis/license/compare/675601b...master @@ -27,5 +28,6 @@ For a full diff see [`675601b...master`][675601b...master]. [#20]: https://github.com/ergebnis/license/pull/20 [#33]: https://github.com/ergebnis/license/pull/33 [#34]: https://github.com/ergebnis/license/pull/34 +[#38]: https://github.com/ergebnis/license/pull/38 [@localheinz]: https://github.com/localheinz diff --git a/Makefile b/Makefile index 402454c5..92a68799 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -MIN_COVERED_MSI:=93 -MIN_MSI:=89 +MIN_COVERED_MSI:=95 +MIN_MSI:=91 .PHONY: it it: coding-standards dependency-analysis static-code-analysis tests ## Runs the coding-standards, dependency-analysis, static-code-analysis, and tests targets diff --git a/README.md b/README.md index 727b8f08..35ef5003 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,138 @@ [![Latest Stable Version](https://poser.pugx.org/ergebnis/license/v/stable)](https://packagist.org/packages/ergebnis/license) [![Total Downloads](https://poser.pugx.org/ergebnis/license/downloads)](https://packagist.org/packages/ergebnis/license) -## Installation +Provides an abstraction of an open-source license. -:bulb: This is a great place for showing how to install the package, see below: +## Installation Run ``` -$ composer require ergebnis/license +$ composer require --dev ergebnis/license ``` ## Usage -:bulb: This is a great place for showing a few usage examples! +Sometimes open source maintainers complain about the burden of managing an open-source project. Sometimes they argue that contributors opening pull requests to update license years unnecessarily increase their workload. + +Of course, all of this can be automated, can't it? + +### Configuration for `friendsofphp/php-cs-fixer` + +With [`friendsofphp/php-cs-fixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer) you can use the configuration file `.php_cs` to + +* save the license to a file, e.g. `LICENSE` or `LICENSE.md` +* specify a file-level header using the `header_comment` fixer that will be replaced in PHP files + +Here's an example of a `.php_cs` file: + +```php +save(); + +$finder = Finder::create()->in(__DIR__); + +return Config::create() + ->setRules([ + 'header_comment' => [ + 'comment_type' => 'PHPDoc', + 'header' => $license->header(), + 'location' => 'after_declare_strict', + 'separate' => 'both', + ], + ]) + ->setFinder($finder); +``` + +:bulb: Also take a look at [`.php_cs`](.php_cs) of this project. + +### GitHub Actions + +When using [GitHub Actions](https://github.com/features/actions), you can set up a scheduled workflow that opens a pull request to the license year automatically on January 1st: + +```yaml +name: "License" + +on: + schedule: + - cron: "1 0 1 1 *" + +jobs: + license: + name: "License" + + runs-on: "ubuntu-latest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2.0.0" + + - name: "Install dependencies with composer" + run: "composer install --no-interaction --no-progress --no-suggest" + + - name: "Run friendsofphp/php-cs-fixer" + run: "vendor/bin/php-cs-fixer fix --config=.php_cs --diff --diff-format=udiff --dry-run --verbose" + + - name: "Open pull request updating license year" + uses: "gr2m/create-or-update-pull-request-action@v1.2.9" + with: + author: "Andreas Möller " + branch: "feature/license-year" + body: | + This PR + + * [x] updates the license year + commit-message: "Enhancement: Update license year" + path: "." + title: "Enhancement: Update license year" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" +``` + +:bulb: See [`crontab.guru`](https://crontab.guru) if you need help scheduling the workflow. + +Note that pull requests opened or commits pushed by GitHub Actions will not trigger a build. As an alternative, you can set up a bot user: + +```diff + - name: "Open pull request updating license year" + uses: "gr2m/create-or-update-pull-request-action@v1.2.9" + with: +- author: "Andreas Möller " ++ author: "ergebnis-bot " + branch: "feature/license-year" + body: | + This PR + + * [x] updates the license year + commit-message: "Enhancement: Update license year" + path: "." + title: "Enhancement: Update license year" + env: +- GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" ++ GITHUB_TOKEN: "${{ secrets.ERGEBNIS_BOT_TOKEN }}" +``` +## Types + +The following license types are currently available: + +* [`Ergebnis\License\Type\MIT`](src/Type/MIT.php) + +:bulb: Need a different license type? Feel free to open a pull request! ## Changelog diff --git a/src/Type/MIT.php b/src/Type/MIT.php new file mode 100644 index 00000000..1f2a5acc --- /dev/null +++ b/src/Type/MIT.php @@ -0,0 +1,89 @@ +file = $file; + $this->header = $header; + } + + public static function markdown(string $name, Period $period, Holder $holder, Url $url): self + { + return new self( + $name, + Template::fromFile(__DIR__ . '/../../resource/license/MIT.md'), + Template::fromFile(__DIR__ . '/../../resource/header.txt'), + $period, + $holder, + $url + ); + } + + public static function text(string $name, Period $period, Holder $holder, Url $url): self + { + return new self( + $name, + Template::fromFile(__DIR__ . '/../../resource/license/MIT.txt'), + Template::fromFile(__DIR__ . '/../../resource/header.txt'), + $period, + $holder, + $url + ); + } + + public function save(): void + { + $this->file->save(); + } + + public function header(): string + { + return $this->header->toString(); + } +} diff --git a/test/Unit/Type/MITTest.php b/test/Unit/Type/MITTest.php new file mode 100644 index 00000000..054d81dc --- /dev/null +++ b/test/Unit/Type/MITTest.php @@ -0,0 +1,197 @@ +mkdir(self::workspaceDirectory()); + } + + protected function tearDown(): void + { + $filesystem = new Filesystem\Filesystem(); + + $filesystem->remove(self::workspaceDirectory()); + } + + public function testHeaderReturnsHeaderForMarkdownLicense(): void + { + $faker = self::faker(); + + $name = \sprintf( + '%s/%s.txt', + self::workspaceDirectory(), + $faker->slug + ); + $range = Range::since( + Year::fromString($faker->year), + new \DateTimeZone($faker->timezone) + ); + $holder = Holder::fromString($faker->name); + $url = Url::fromString($faker->url); + + $license = MIT::markdown( + $name, + $range, + $holder, + $url + ); + + $expected = Template::fromFile(__DIR__ . '/../../../resource/header.txt')->toString([ + '' => \basename($name), + '' => $holder->toString(), + '' => $range->toString(), + '' => $url->toString(), + ]); + + self::assertSame($expected, $license->header()); + } + + public function testHeaderReturnsHeaderForTextLicense(): void + { + $faker = self::faker(); + + $name = \sprintf( + '%s/%s.txt', + self::workspaceDirectory(), + $faker->slug + ); + $range = Range::since( + Year::fromString($faker->year), + new \DateTimeZone($faker->timezone) + ); + $holder = Holder::fromString($faker->name); + $url = Url::fromString($faker->url); + + $license = MIT::text( + $name, + $range, + $holder, + $url + ); + + $expected = Template::fromFile(__DIR__ . '/../../../resource/header.txt')->toString([ + '' => \basename($name), + '' => $holder->toString(), + '' => $range->toString(), + '' => $url->toString(), + ]); + + self::assertSame($expected, $license->header()); + } + + public function testSaveSavesMarkdownToFileForMarkdownLicense(): void + { + $faker = self::faker(); + + $name = \sprintf( + '%s/%s.md', + self::workspaceDirectory(), + $faker->slug + ); + $range = Range::since( + Year::fromString($faker->year), + new \DateTimeZone($faker->timezone) + ); + $holder = Holder::fromString($faker->name); + $url = Url::fromString($faker->url); + + $license = MIT::markdown( + $name, + $range, + $holder, + $url + ); + + $license->save(); + + self::assertFileExists($name); + + $expected = Template::fromFile(__DIR__ . '/../../../resource/license/MIT.md')->toString([ + '' => $holder->toString(), + '' => $range->toString(), + ]); + + self::assertSame($expected, \file_get_contents($name)); + } + + public function testSaveSavesTextToFileForTextLicense(): void + { + $faker = self::faker(); + + $name = \sprintf( + '%s/%s.txt', + self::workspaceDirectory(), + $faker->slug + ); + $range = Range::since( + Year::fromString($faker->year), + new \DateTimeZone($faker->timezone) + ); + $holder = Holder::fromString($faker->name); + $url = Url::fromString($faker->url); + + $license = MIT::text( + $name, + $range, + $holder, + $url + ); + + $license->save(); + + self::assertFileExists($name); + + $expected = Template::fromFile(__DIR__ . '/../../../resource/license/MIT.txt')->toString([ + '' => $holder->toString(), + '' => $range->toString(), + ]); + + self::assertSame($expected, \file_get_contents($name)); + } + + private static function workspaceDirectory(): string + { + return __DIR__ . '/../../../.build/test'; + } +}