Compare commits

...

128 commits

Author SHA1 Message Date
9107b058f5 Traits are no longer covered explicitly 2024-12-27 23:58:49 -05:00
f066aa6649 Method coverage no longer works in PHPUnit 11 2024-12-27 23:39:44 -05:00
2aa4fc9215 Convert method coverage 2024-12-27 21:43:44 -05:00
ba4d3f497b Replace PHPUnit annotations with attributes 2024-12-27 20:28:38 -05:00
ba42b7b0c1 Upgrade to PHPUnit 11
Deprecated annotations still need to be addressed
2024-12-27 19:58:07 -05:00
835f6fb998 Update Daux to latest version 2024-12-27 16:04:41 -05:00
7966947022 Update changelog 2024-12-27 13:59:38 -05:00
7f38369935 Address problem with MySQL 9 2024-12-27 13:36:54 -05:00
9e5c841377 Adjust MySQL for PHP 8.4 2024-12-27 13:12:34 -05:00
b306f0fc29 Crash was related to xdebug 2024-12-27 09:45:22 -05:00
9c25b7b6e6 Test cleanup 2024-12-25 10:11:28 -05:00
1f137ba710 Work around segmentation fault 2024-12-25 08:24:12 -05:00
93c322bdfa Correct most remaining test bugs 2024-12-24 22:58:58 -05:00
f1d3055f4c Convert remaining usage of Phony to Phake 2024-12-24 14:50:09 -05:00
1cd3f29fe3 Merge branch 'master' into phake 2024-12-15 22:38:22 -05:00
2d7b2dde39 Fix-up laminas-xml and docopt
Now there's only Phony, which will be replaced with Phake.
2024-12-15 21:53:20 -05:00
e8be7d0f38 Address our own deprecations in PHP 8.4 2024-12-15 16:31:57 -05:00
1ae3c33344 Be more specific about coverage of Database class
This has uncovered a bug with nextFetch calculation, it seems. Fixing it is deferred for now.
2024-07-10 16:28:19 -04:00
aed3749da8 Shore up coverage 2024-07-09 14:58:31 -04:00
b68a68b500 Update changelog 2024-07-06 12:28:41 -04:00
ade1c79361 Fix language-loading loop by applying incrementally 2024-07-06 12:24:16 -04:00
0170ec19c7 Fix language-loading infinite loop when throwing exceptions
This loop has existed since the very beginning, and was only ever papered over instead of actually fixing it.
2024-07-06 11:01:00 -04:00
8834a65e4c Address case where user is already arsse 2024-01-10 11:00:08 -05:00
b1154359e4 Prepare release 2024-01-09 19:04:28 -05:00
3838cdc0af Merge branch 'arch2' 2024-01-09 18:42:55 -05:00
1f9beb85a5 Fix socket path 2024-01-09 18:13:36 -05:00
2670ed4ab8 Typo 2024-01-07 12:33:58 -05:00
4df97eefcb Oops 2024-01-07 12:30:40 -05:00
82f94d1dae Remove obsolete systemd patching 2024-01-07 12:28:47 -05:00
7f6b36d4da Remove references to Pandoc 2024-01-07 11:23:46 -05:00
06ec67816a Update arsse-git PKGBUILD
This includes both adaptations for php-legacy as well as for the new mdoc manual
2024-01-05 11:20:56 -05:00
e647d79e1e Update dependencies 2024-01-05 09:17:08 -05:00
f853851568 Run with the correct executable name visible to PHP 2024-01-05 09:16:57 -05:00
4131531bff Tweaks
- Add new files to PKGBUILD backup
- Adjust Robo caller to use ARSSE_PHP variable; Windows will need adjusting later
2023-12-29 19:42:33 -05:00
9086a5d9b1 Add interpreter adaptation for service, with documentation 2023-12-24 14:39:42 -05:00
cce48878e7 Add pool for php-legacy 2023-12-22 16:35:09 -05:00
185ae88ba3 Correct outdated documentation 2023-12-22 13:47:26 -05:00
29fb134633 First steps in supporting php-legacy in Arc
This sorts out HTTP servers, hopefully. Adaptation for systemd and documentation are still required
2023-12-22 13:27:02 -05:00
72dd21686e Convert user manager tests to Phake 2023-11-20 16:39:58 -05:00
9d92c1661d More Phake conversion 2023-11-11 17:22:13 -05:00
7e58dd800d Remove list uses of PHPUnit mocks 2023-11-09 23:35:04 -05:00
ece19494d7 Fix errors and failures 2023-11-09 23:31:22 -05:00
1a981bf267 Start on conversion back to Phake 2023-11-09 22:15:11 -05:00
9951b44932 Update changelog 2023-11-01 12:48:30 -04:00
692af39768 Use mdoc manual directly without any preprocessing 2023-10-29 22:20:14 -04:00
73446887f5 Editorial tweak 2023-10-28 08:06:49 -04:00
e20937f98f The rest of the mdoc manual 2023-10-27 23:23:15 -04:00
2ff3286aa9 More work on mdoc manual 2023-10-27 20:44:01 -04:00
174ed544b2 Start on an mdoc-format manual 2023-10-27 12:28:22 -04:00
5579144fee Complete native groff manual 2023-10-26 18:15:20 -04:00
7613142221 Start on a native groff manual 2023-10-25 17:24:54 -04:00
d9b90390e7 Update style rules 2023-06-17 11:09:03 -04:00
59a9329032 Upgrade dependencies where possible with PHP 7.3 2023-03-24 10:28:20 -04:00
be3adf7026 Document RoboFile better 2023-03-23 11:31:33 -04:00
eb371b75fe Fix documentation errors 2023-03-22 23:45:56 -04:00
1b80ad37bc Merge branch 'csfixer3'
Code style cleanup to mostly conform to PSR-12
2023-03-22 23:33:05 -04:00
3c83fc9139 Update php-cs-fixer rules 2023-03-22 23:14:19 -04:00
711f87aad8 Housekeeping
- Update Docopt
- Switch toin-house Picofeed branch for now
- Update composer-bin
- Update php-cs-fixer

Daux has been left as-is for now even though we're using an old version
2023-03-22 22:57:58 -04:00
0a8d19d37d Require PHP 7.3
This addresses the last of the deprecation warnings in PHP 8.2
2023-01-28 12:43:42 -05:00
fe06ffc176 Avoid dynamic property creation with PicoFeed
This only leaves the Laminas XML deprecated behaviour to handle
2023-01-28 11:18:14 -05:00
0d6f8d2921 Avoid most deprecation warnings
The Feed class sets dynamic properties on Picofeed classes; this will
need to be completely rewritten. Version 1.2 of the Laminas XML class
also uses a deprecated function, but upgrading it to 1.3 will require
PHP 7.3.
2023-01-27 15:33:34 -05:00
92b1a840a1 Support PHP 8.2 properly 2023-01-24 15:12:21 -05:00
a25e777ec6 Version bump 2022-09-14 08:06:22 -04:00
44e2c9c13e Update documentation 2022-09-13 19:56:01 -04:00
866800dcc5 Finish last Guzzle-related tests 2022-09-13 19:52:29 -04:00
136d3782e3 Update changelog 2022-08-07 20:16:27 -04:00
3be3f43bab Start on tests for response wrappers 2022-08-06 22:59:25 -04:00
d2f3f19128 Fix failures 2022-08-06 16:16:18 -04:00
459e44e041 Address remaining errors
Still many failures to fix
2022-08-06 16:03:50 -04:00
56f015bfb9 More Guzzle conversion 2022-08-06 13:40:02 -04:00
64ec3f6ae4 Use unused variable 2022-08-05 22:10:36 -04:00
4d18bf27e2 Adjust most uses of Diactoros to Guzzle PSR-7 2022-08-05 22:08:36 -04:00
e588a52e88 Replace ServerRequestFactory 2022-08-04 22:15:43 -04:00
6c0183faea Replace instances of Diactoros' EmptyResponse 2022-08-04 22:04:39 -04:00
560d4db139 Remove Diactoros in favour of Guzzle PSR-7
For now this only adds convenience wrappers around Guzzle to somewhat
emulate Diactoros (albeit with a different API). Code and tests will be
adjusted in due course.
2022-08-04 09:26:17 -04:00
2557c22410 Update dependencies 2022-06-22 15:33:17 -04:00
4ca7b65a65 Update dependencies 2022-06-09 21:21:17 -04:00
4d37ae30ae Update dependencies
Addresses a Guzzle vulnerability, though it does not affect The Arsse
2022-06-05 21:38:08 -04:00
d1da6fbe5e Use cases rather than casting bools to int in SQL 2022-05-30 17:29:34 -04:00
d54733ad98 Update link to Nextcloud News documentation again
The URL has changed again since it was updated two weeks ago
2022-05-02 10:48:52 -04:00
a0c31fac5d Merge branch 'reader' 2022-04-30 17:34:29 -04:00
59358ec35b More PHP 7 fixes 2022-04-30 17:11:18 -04:00
90b66241b3 Fixes for PHP 7 2022-04-30 13:50:35 -04:00
761b3d5333 Return removed articles correctly in Miniflux 2022-04-29 23:28:47 -04:00
d64dc751f9 Tests for query filters 2022-04-29 20:53:05 -04:00
f51acb4264 Build exceptions correctly in Miniflux for clarity 2022-04-29 19:10:11 -04:00
300225439c Fix trivial error in Miniflux
This is not a bug as the behaviour that should have been implemented was
not being relied upon
2022-04-29 19:04:08 -04:00
c6cc2a1a42 Restore coverage for Query class 2022-04-29 17:23:41 -04:00
a44fe103d8 Prototype for nesting query filters 2022-04-29 16:37:16 -04:00
630536d789 Tests for union context 2022-04-29 16:35:46 -04:00
206c5c0012 Fill in union context 2022-04-28 22:32:10 -04:00
0c8f33c37c Remove setCTE and pushCTE from query builder 2022-04-28 21:24:57 -04:00
26e431b1a5 Simplify more queries 2022-04-28 17:57:31 -04:00
336207741d Add missing API documentation 2022-04-28 17:37:10 -04:00
6863c182d7 Update reference to the "Reeder" client 2022-04-27 10:22:43 -04:00
f2aad7188c Update links to TT-RSS documentation 2022-04-27 10:16:20 -04:00
65b1bb4fcd Allow multiple dates in TT-RSS searches 2022-04-26 17:13:16 -04:00
2c5b9a6768 Fix missing TTRSS coverage 2022-04-26 12:13:15 -04:00
17832ac63e Allow timezone in TT-RSS search queries
Does not quite work yet
2022-04-25 22:28:16 -04:00
e65069885b Clean up obsolete FIXMEs 2022-04-25 18:30:13 -04:00
7e5d8494c4 Tests for selecting arrays of ranges 2022-04-25 14:33:19 -04:00
e6505a5fda Work around possible MySQL bug 2022-04-25 09:56:13 -04:00
2acacd2647 Implement handling for arrays of ranges
Multiple ranges of articles or editions were not implemented, but the
functionality is generic and could be extended if later needed.
2022-04-24 20:13:08 -04:00
f6799e2ab1 Tests for date ranges in contexts 2022-04-24 12:25:37 -04:00
33a3478a58 Avoid use of PHP 7.4 feature 2022-04-23 17:24:25 -04:00
2489743d0f Further simplifications 2022-04-23 13:21:52 -04:00
0bd01849bb Remove unnecessary in() clause 2022-04-23 11:51:53 -04:00
895c045c9b Simplify folder selection in article queries 2022-04-23 11:15:57 -04:00
fe02613214 Fix coverage 2022-04-22 22:46:13 -04:00
427bddd3b7 Allow multiple date ranges 2022-04-22 20:09:07 -04:00
53ba591720 Finish up article selection refactor 2022-04-22 19:22:50 -04:00
97dfef3267 Fix typos 2022-04-21 23:30:19 -04:00
396ca86482 Start on removal of conditional CTEs
This breaks the code for now, but will make clearer queries once done
2022-04-21 23:19:19 -04:00
4a87926dd5 Fix up context tests 2022-04-21 14:37:28 -04:00
6f1332c559 Start to shore up testing 2022-04-20 19:11:04 -04:00
308b592b18 Clean up coontext classes 2022-04-19 23:20:20 -04:00
983fa58ec8 Convert article and edition ranges to atomic
Unit tests for ranges are still missing
2022-04-19 22:53:36 -04:00
2c2bb4a856 Retrofits dates to use ranges
Article and edition ranges still need work
2022-04-19 20:19:51 -04:00
c993168002 Update URL of Nextcloud News documentation 2022-04-19 19:33:35 -04:00
73497688fc Break contexts up into traits
This will make their expansion easier and will also be useful for using
typed properties later
2022-04-18 22:04:48 -04:00
1b0256d6ce Abandon automation of binary packaging for now 2022-04-04 14:19:53 -04:00
144a41e061 Prepare new version 2022-04-04 14:05:04 -04:00
60b4002329 Revert "Document that we actually emulate Miniflux 2.0.29"
This reverts commit d379aa2253.
2022-04-04 13:43:20 -04:00
f24ec8b00b Address security vulnerability in Guzzle's PSR-7
implementation, used by PicoFeed
2022-04-04 13:40:39 -04:00
d379aa2253 Document that we actually emulate Miniflux 2.0.29
At the time The Arsse 0.9.0 was released the new feature in 2.0.29 was
already implemented, but that version of Miniflux had not been released.
2022-03-14 13:16:46 -04:00
4080b2d09d Apply new rules 2021-04-14 11:17:01 -04:00
73731fa9db Fix up CS config file 2021-04-14 11:16:36 -04:00
18d296dcd6 Clean up CS fixer rules 2021-04-14 10:10:04 -04:00
275 changed files with 9599 additions and 8047 deletions

3
.gitignore vendored
View file

@ -7,11 +7,12 @@
/dist/arch/arsse/
/dist/arch/src/
/dist/arch/pkg/
/dist/man/
/arsse.db*
/config.php
/.php_cs.cache
/.php-cs-fixer.cache
/tests/.phpunit.result.cache
/tests/.phpunit.cache
# Dependencies

View file

@ -4,6 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse;
const BASE = __DIR__.DIRECTORY_SEPARATOR;
@ -13,7 +14,10 @@ $paths = [
BASE."arsse.php",
BASE."RoboFile.php",
BASE."lib",
BASE."tests",
BASE."tests/cases",
BASE."tests/lib",
BASE."tests/bootstrap.php",
BASE."tests/server.php",
];
$rules = [
// house rules where PSR series is silent
@ -35,6 +39,7 @@ $rules = [
'no_blank_lines_after_phpdoc' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => true, // this could probably use more configuration
'no_mixed_echo_print' => ['use' => "echo"],
'no_short_bool_cast' => true,
@ -48,30 +53,19 @@ $rules = [
'pow_to_exponentiation' => true,
'set_type_to_cast' => true,
'standardize_not_equals' => true,
'trailing_comma_in_multiline_array' => true,
'trailing_comma_in_multiline' => ['elements' => ["arrays"]],
'unary_operator_spaces' => true,
'yoda_style' => false,
// PSR standard to apply
'@PSR2' => true,
// PSR-12 rules; php-cs-fixer does not yet support PSR-12 natively
'compact_nullable_typehint' => true,
'declare_equal_normalize' => ['space' => "none"],
'function_typehint_space' => true,
'lowercase_cast' => true,
'lowercase_static_reference' => true,
'no_alternative_syntax' => true,
'no_empty_statement' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_whitespace_in_blank_line' => true,
'return_type_declaration' => ['space_before' => "none"],
'single_trait_insert_per_statement' => true,
'short_scalar_cast' => true,
'visibility_required' => ['elements' => ["const", "property", "method"]],
'@PSR12' => true,
// house exceptions to PSR rules
'braces' => ['position_after_functions_and_oop_constructs' => "same"],
'curly_braces_position' => [
'functions_opening_brace' => "same_line",
'classes_opening_brace' => "same_line",
],
'function_declaration' => ['closure_function_spacing' => "none"],
'new_with_braces' => false, // no option to specify absence of braces
'php_unit_attributes' => true,
];
$finder = \PhpCsFixer\Finder::create();
@ -82,4 +76,4 @@ foreach ($paths as $path) {
$finder = $finder->in($path);
}
}
return \PhpCsFixer\Config::create()->setRiskyAllowed(true)->setRules($rules)->setFinder($finder);
return (new \PhpCsFixer\Config)->setRiskyAllowed(true)->setRules($rules)->setFinder($finder);

View file

@ -1,3 +1,44 @@
Version 0.10.6 (2024-12-27)
===========================
Bug fixes:
- Do not hang when language files are missing or corrupted
Changes:
- Support PHP 8.4
- Support MySQL 9.0
Version 0.10.5 (2024-01-10)
===========================
Changes:
- Require PHP 7.3
- Adapt the Arch package to make using alternative PHP interpreters easier
(see manual for details)
- Multiple editorial and stylistic changes to the UNIX manual page
Version 0.10.4 (2023-01-24)
===========================
Changes:
- Support PHP 8.2
Version 0.10.3 (2022-09-14)
===========================
Bug fixes:
- Return all removed articles when multiple statuses are requested in Miniflux
- Allow multiple date ranges in search strings in Tiny Tiny RSS
- Honour user time zone when interpreting search strings in Tiny Tiny RSS
- Perform MySQL table maintenance more reliably
- Address CVE-2022-31090, CVE-2022-31091, CVE-2022-29248, and CVE-2022-31109
Version 0.10.2 (2022-04-04)
===========================
Changes:
- Update Guzzle PSR-7 due to CVE-2022-24775
Version 0.10.1 (2022-01-17)
===========================
@ -50,7 +91,7 @@ Bug fixes:
- Further relax Fever HTTP correctness, to fix more clients
- Use icons specified in Atom feeds when available
- Do not return null as subscription unread count
- Explicitly forbid U+003A COLON and control characters in usernames, for
- Explicitly forbid U+003A COLON and control characters in usernames, for
compatibility with RFC 7617
- Never return 401 in response to an OPTIONS request
- Accept "t" and "f" as booleans in Tiny Tiny RSS

View file

@ -34,13 +34,13 @@ Also necessary to the functioning of the application is the `/vendor/` directory
The `/locale/` and `/sql/` directories contain human-language files and database schemata, both of which are occasionally used by the application in the course of execution. The `/www/` directory serves as a document root for a few static files to be made available to users by a Web server.
The `/dist/` directory, on the other hand, contains general and system-specific build files, and samples of configuration for Web servers and other system integration. These are not used by The Arsse itself, but are used during the process of preparing new releases for supported operating systems.
The `/dist/` directory, on the other hand, contains general and system-specific build files, manual pages, and samples of configuration for Web servers and other system integration. These are not used by The Arsse itself, but are used during the process of preparing new releases for supported operating systems.
## Documentation
The source text for The Arsse's manual can be found in `/docs/`, with pages written in [Markdown](https://spec.commonmark.org/current/) and converted to HTML [with Daux](#building-the-manual). If a static manual is generated its files will appear under `/manual/`.
The Arsse also has a UNIX manual page, also written in Markdown, which can be found under `/manpages/`. [Pandoc](https://pandoc.org/) is needed to convert it to the appropriate format, with the results stored under `/dist/man/`.
The Arsse also has a UNIX manual page written in [mdoc](https://man.archlinux.org/man/extra/mandoc/mandoc_mdoc.7.en) format, which can be found under `/dist/man/`.
In addition to the manuals the files `/README.md` (this file), `/CHANGELOG`, `/UPGRADING`, `/LICENSE`, and `/AUTHORS` also document various things about the software, rather than the software itself.
@ -64,17 +64,17 @@ PHPUnit's configuration can be customized by copying its configuration file to `
The `/vendor-bin/` directory houses the files needed for the tools used in The Arsse's programming environment. These are managed by the Composer ["bin" plugin](https://github.com/bamarni/composer-bin-plugin) and are not used by The Arsse itself. The following files are also related to various programming tools:
| Path | Description |
|-------------------|----------------------------------------------------------|
| `/.gitattributes` | Git settings for handling files |
| `/.gitignore` | Git file exclusion patterns |
| `/.php_cs.dist` | Configuration for [php-cs-fixer](https://cs.symfony.com) |
| `/.php_cs.cache` | Cache for php-cs-fixer |
| `/composer.json` | Configuration for Composer |
| `/composer.lock` | Version synchronization data for Composer |
| `/RoboFile.php` | Task definitions for [Robo](https://robo.li/) |
| `/robo` | Simple wrapper for executing Robo on POSIX systems |
| `/robo.bat` | Simple wrapper for executing Robo on Windows |
| Path | Description |
|---------------------------|----------------------------------------------------------|
| `/.gitattributes` | Git settings for handling files |
| `/.gitignore` | Git file exclusion patterns |
| `/.php-cs-fixer.dist.php` | Configuration for [php-cs-fixer](https://cs.symfony.com) |
| `/.php-cs-fixer.cache` | Cache for php-cs-fixer |
| `/composer.json` | Configuration for Composer |
| `/composer.lock` | Version synchronization data for Composer |
| `/RoboFile.php` | Task definitions for [Robo](https://robo.li/) |
| `/robo` | Simple wrapper for executing Robo on POSIX systems |
| `/robo.bat` | Simple wrapper for executing Robo on Windows |
In addition the files `/package.json` and `/postcss.config.js` as well as the `/node_modules/` directory are used by [Yarn](https://yarnpkg.com/) and [PostCSS](https://postcss.org/) when modifying the stylesheet for The Arsse's manual.
@ -90,11 +90,11 @@ There is also a `test:quick` Robo task which excludes slower tests, and a `test:
### Test coverage
Computing the coverage of tests can be done by running `./robo coverage`, after which an HTML-format coverage report will be written to `/tests/coverage/`. Either [PCOV](https://github.com/krakjoe/pcov), [Xdebug](https://xdebug.org), or [phpdbg](https://php.net/manual/en/book.phpdbg.php) is required for this. PCOV is generally recommended as it is faster than Xdebug; phpdbg is faster still, but less accurate. If using either PCOV or Xdebug, the extension need not be enabled globally; PHPUnit will enable it when needed.
Computing the coverage of tests can be done by running `./robo coverage`, after which an HTML-format coverage report will be written to `/tests/coverage/`. Either [PCOV](https://github.com/krakjoe/pcov) or [Xdebug](https://xdebug.org) is required for this. PCOV is generally recommended as it is faster than Xdebug. Neither extension need be enabled globally; Robo will enable it when needed.
## Enforcing coding style
The [php-cs-fixer](https://cs.symfony.com) tool, executed via `./robo clean`, can be used to rewrite code to adhere to The Arsse's coding style. The style largely follows [PSR-2](https://www.php-fig.org/psr/psr-2/) with some exceptions:
The [php-cs-fixer](https://cs.symfony.com) tool, executed via `./robo clean`, can be used to rewrite code to adhere to The Arsse's coding style. The style largely follows [PSR-12](https://www.php-fig.org/psr/psr-12/) with some exceptions:
- Classes, methods, and functions should have their opening brace on the same line as the signature
- Anonymous functions should have no space before the parameter list
@ -107,20 +107,13 @@ The Arsse's user manual, made using [Daux](https://daux.io/), can be compiled by
The manual employs a custom theme derived from the standard Daux theme. If the standard Daux theme receives improvements, the custom theme can be rebuilt by running `./robo manual:theme`. This requires that [NodeJS](https://nodejs.org) and [Yarn](https://yarnpkg.com/) be installed, but JavaScript tools are not required to modify The Arsse itself, nor the content of the manual.
## Building the man page
The Arsse's UNIX manual page is authored in Markdown, and must be converted to the native roff format using [Pandoc](https://pandoc.org/). This can be done by running `./robo manpage`, which will output appropriate files to `/dist/man/`. The conversion should not be done manually as there is post-processing required for optimal output.
## Packaging a release
Producing release packages is done by running `./robo package`. This performs the following operations:
- Duplicates a [Git](https://git-scm.com/) working tree with the commit (usually a release tag) to package
- Generates UNIX manual pages with [Pandoc](https://pandoc.org/)
- Generates the HTML manual
- Installs runtime Composer dependencies with an optimized autoloader
- Deletes numerous unneeded files
- Exports the default configuration of The Arsse to a file
- Compresses the remaining files into a tarball
- Produces a binary package for Arch Linux, if possible
- Produces source and binary packages for Debian using [pbuilder](https://pbuilder-team.pages.debian.net/pbuilder/), if possible

View file

@ -55,8 +55,8 @@ class RoboFile extends \Robo\Tasks {
* tests/coverage/. Additional reports may be produced by passing
* arguments to this task as one would to PHPUnit.
*
* Robo first tries to use pcov and will fall back first to xdebug then
* phpdbg. Neither pcov nor xdebug need to be enabled to be used; they
* Robo first tries to use pcov and will fall back to xdebug.
* Neither pcov nor xdebug need to be enabled to be used; they
* only need to be present in the extension load path to be used.
*/
public function coverage(array $args): Result {
@ -80,9 +80,9 @@ class RoboFile extends \Robo\Tasks {
return $this->runTests($exec, "typical", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
}
/** Runs the coding standards fixer */
/** Runs the coding-style fixer */
public function clean($opts = ['demo|d' => false]): Result {
$t = $this->taskExec(norm(BASE."vendor/bin/php-cs-fixer"));
$t = $this->taskExec(norm(BASE."vendor-bin/csfixer/vendor/bin/php-cs-fixer"));
$t->arg("fix");
if ($opts['demo']) {
$t->args("--dry-run", "--diff")->option("--diff-format", "udiff");
@ -90,9 +90,10 @@ class RoboFile extends \Robo\Tasks {
return $t->run();
}
/** Finds the first suitable means of computing code coverage, either pcov or xdebug. */
protected function findCoverageEngine(): string {
$dir = rtrim(ini_get("extension_dir"), "/").\DIRECTORY_SEPARATOR;
$ext = IS_WIN ? "dll" : (IS_MAC ? "dylib" : "so");
$ext = IS_WIN ? "dll" : "so";
$php = escapeshellarg(\PHP_BINARY);
$code = escapeshellarg(BASE."lib");
if (extension_loaded("pcov")) {
@ -104,48 +105,61 @@ class RoboFile extends \Robo\Tasks {
} elseif (file_exists($dir."xdebug.$ext")) {
return "$php -d zend_extension=xdebug.$ext -d xdebug.mode=coverage";
} else {
if (IS_WIN) {
$dbg = dirname(\PHP_BINARY)."\\phpdbg.exe";
$dbg = file_exists($dbg) ? $dbg : "";
} else {
$dbg = trim(`which phpdbg 2>/dev/null`);
}
if ($dbg) {
return escapeshellarg($dbg)." -qrr";
} else {
return $php;
}
return $php;
}
}
/** Returns the necessary shell arguments to print error output or all output to the bitbucket
*
* @param bool $all Whether all output (true) or only error output (false) should be suppressed
*/
protected function blackhole(bool $all = false): string {
$hole = IS_WIN ? "nul" : "/dev/null";
return $all ? ">$hole 2>&1" : "2>$hole";
}
/** Executes PHPUnit, used by the test and coverage tasks.
*
* This also executes the built-in PHP Web server, which is required to fetch some newsfeeds during tests
*
* @param string $executor The path to the PHP binary to execute with any required extra arguments. Normally this is either "php" or the result of findCoverageEngine()
* @param string $set The set of tests to run, either "typical" (excludes redundant tests), "quick" (excludes redundant and slow tests), "coverage" (excludes tests not needed for coverage), or "full" (all tests)
* @param array $args Extra arguments passed by Robo from the command line
*/
protected function runTests(string $executor, string $set, array $args): Result {
switch ($set) {
case "typical":
$set = ["--exclude-group", "optional"];
$exc = ["optional"];
break;
case "quick":
$set = ["--exclude-group", "optional,slow"];
$exc = ["optional", "slow"];
break;
case "coverage":
$set = ["--exclude-group", "optional,coverageOptional"];
$exc = ["optional", "coverageOptional"];
break;
case "full":
$set = [];
$exc = [];
break;
default:
throw new \Exception;
}
$extra = ["--display-phpunit-deprecations"];
foreach ($exc as $group) {
$extra[] = "--exclude-group";
$extra[] = $group;
}
$execpath = norm(BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
$confpath = realpath(BASE_TEST."phpunit.dist.xml") ?: norm(BASE_TEST."phpunit.xml");
$this->taskServer(8000)->host("localhost")->dir(BASE_TEST."docroot")->rawArg("-n")->arg(BASE_TEST."server.php")->rawArg($this->blackhole())->background()->run();
return $this->taskExec($executor)->option("-d", "zend.assertions=1")->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
return $this->taskExec($executor)->option("-d", "zend.assertions=1")->arg($execpath)->option("-c", $confpath)->args(array_merge($extra, $args))->run();
}
/** Returns a Git version string for a given Git tree-ish ID
*
* Returns an array containing the tree-ish string and the version string.
*
* @param string|null $commit The tree-ish ID. If not supplied the user will be prompted
*/
protected function commitVersion(?string $commit): array {
$target = $commit ?? $this->askDefault("Reference commit:", "HEAD");
$base = escapeshellarg(BASE);
@ -158,13 +172,11 @@ class RoboFile extends \Robo\Tasks {
return [$target, $version];
}
/** Checks whether all the supplied terminal commands are available in path */
protected function toolExists(string ...$binary): bool {
$blackhole = $this->blackhole(IS_WIN);
foreach ($binary as $bin) {
if (
(IS_WIN && (!exec(escapeshellarg($bin)." --help $blackhole", $junk, $status) || $status))
|| (!IS_WIN && (!exec("which ".escapeshellarg($bin)." $blackhole", $junk, $status) || $status))
) {
if (!exec(escapeshellarg($bin)." --help $blackhole", $junk, $status) || $status) {
return false;
}
}
@ -181,13 +193,16 @@ class RoboFile extends \Robo\Tasks {
* may not be equivalent due to subsequent changes in the exclude list, or because
* of new tooling.
*/
public function packageGeneric(string $commit = null): Result {
if (!$this->toolExists("git", "pandoc")) {
throw new \Exception("Git and Pandoc are required in PATH to produce generic release tarballs");
public function packageGeneric(?string $commit = null): Result {
if (!$this->toolExists("git")) {
throw new \Exception("Git is required in PATH to produce generic release tarballs");
}
// establish which commit to package
[$commit, $version] = $this->commitVersion($commit);
$archVersion = preg_replace('/^([^-]+)-(\d+)-(\w+)$/', "$1.r$2.$3", $version);
preg_match('/^([^-]+)(?:-(\d+)-(\w+))?$/', $version, $m);
$archVersion = $m[1].($m[2] ? ".r$m[2].$m[3]" : "");
$baseVersion = $m[1];
$release = $m[2];
// name the generic release tarball
$tarball = BASE."release/$version/arsse-$version.tar.gz";
// start a collection
@ -200,32 +215,47 @@ class RoboFile extends \Robo\Tasks {
return $result;
}
try {
if (file_exists($dir."dist/debian")) {
// generate the Debian changelog; this also validates our original changelog
$debianChangelog = $this->changelogDebian($this->changelogParse(file_get_contents($dir."CHANGELOG"), $version), $version);
// save the Debian-format changelog
$t->addTask($this->taskWriteToFile($dir."dist/debian/changelog")->text($debianChangelog));
}
// Perform Arch-specific tasks
if (file_exists($dir."dist/arch")) {
// patch the Arch PKGBUILD file with the correct version string
$t->addTask($this->taskReplaceInFile($dir."dist/arch/PKGBUILD")->regex('/^pkgver=.*$/m')->to("pkgver=$archVersion"));
// patch the Arch PKGBUILD file with the correct source file
$t->addTask($this->taskReplaceInFile($dir."dist/arch/PKGBUILD")->regex('/^source=\("arsse-[^"]+"\)$/m')->to('source=("'.basename($tarball).'")'));
}
// perform Debian-specific tasks
if (file_exists($dir."dist/debian")) {
// generate the Debian changelog; this also validates our original changelog
$changelog = $this->changelogParse(file_get_contents($dir."CHANGELOG"), $version);
$debianChangelog = $this->changelogDebian($changelog, $version);
// save the Debian-format changelog
$t->addTask($this->taskWriteToFile($dir."dist/debian/changelog")->text($debianChangelog));
}
// perform RPM-specific tasks
if (file_exists($dir."dist/rpm")) {
// patch the spec file with the correct version and release
$t->addTask($this->taskReplaceInFile($dir."dist/rpm/arsse.spec")->regex('/^Version: .*$/m')->to("Version: $baseVersion"));
$t->addTask($this->taskReplaceInFile($dir."dist/rpm/arsse.spec")->regex('/^Release: .*$/m')->to("Release: $release"));
// patch the spec file with the correct tarball name
$t->addTask($this->taskReplaceInFile($dir."dist/rpm/arsse.spec")->regex('/^Source0: .*$/m')->to("Source0: arsse-$version.tar.gz"));
// append the RPM changelog to the spec file
$t->addTask($this->taskWriteToFile($dir."dist/rpm/arsse.spec")->append(true)->text("\n\n%changelog\n".$this->changelogRPM($changelog, $version)));
}
// save commit description to VERSION file for reference
$t->addTask($this->taskWriteToFile($dir."VERSION")->text($version));
// perform Composer installation in the temp location with dev dependencies
$t->addTask($this->taskComposerInstall()->arg("-q")->dir($dir));
if (file_exists($dir."manpages")) {
// generate manpages
$t->addTask($this->taskExec("./robo manpage")->dir($dir));
}
if (file_exists($dir."docs")) {
// generate the HTML manual
$t->addTask($this->taskExec("./robo manual -q")->dir($dir));
if (file_exists($dir."docs") || file_exists($dir."manpages/en.md")) {
// perform Composer installation in the temp location with dev dependencies to include Robo and Daux
$t->addTask($this->taskExec("composer install")->arg("-q")->dir($dir));
if (file_exists($dir."docs")) {
// generate the HTML manual
$t->addTask($this->taskExec("./robo manual -q")->dir($dir));
}
if (file_exists($dir."manpages/en.md")) {
// generate manpages (NOTE: obsolete process)
$t->addTask($this->taskExec("./robo manpage")->dir($dir));
}
}
// perform Composer installation in the temp location for final output
$t->addTask($this->taskComposerInstall()->dir($dir)->noDev()->optimizeAutoloader()->arg("--no-scripts")->arg("-q"));
$t->addTask($this->taskExec("composer install")->dir($dir)->arg("--no-dev")->arg("-o")->arg("--no-scripts")->arg("-q"));
// delete unwanted files
$t->addTask($this->taskFilesystemStack()->remove([
$dir.".git",
@ -277,86 +307,13 @@ class RoboFile extends \Robo\Tasks {
return $result;
}
/** Packages a given commit of the software into an Arch package
*
* The commit to package may be any Git tree-ish identifier: a tag, a branch,
* or any commit hash. If none is provided on the command line, Robo will prompt
* for a commit to package; the default is "HEAD".
*
* The Arch base-devel group should be installed for this.
*/
public function packageArch(string $commit = null): Result {
if (!$this->toolExists("git", "makepkg", "updpkgsums")) {
throw new \Exception("Git, makepkg, and updpkgsums are required in PATH to produce Arch packages");
}
// establish which commit to package
[$commit, $version] = $this->commitVersion($commit);
$tarball = BASE."release/$version/arsse-$version.tar.gz";
$dir = dirname($tarball).\DIRECTORY_SEPARATOR;
// start a collection
$t = $this->collectionBuilder();
// build the generic release tarball if it doesn't exist
if (!file_exists($tarball)) {
$t->addTask($this->taskExec(BASE."robo package:generic $commit"));
}
// extract the PKGBUILD from the tarball
$t->addCode(function() use ($tarball, $dir) {
// because Robo doesn't support extracting a single file we have to do it ourselves
(new \Archive_Tar($tarball))->extractList("arsse/dist/arch/PKGBUILD", $dir, "arsse/dist/arch/", false);
// perform a do-nothing filesystem operation since we need a Robo task result
return $this->taskFilesystemStack()->chmod($dir."PKGBUILD", 0644)->run();
})->completion($this->taskFilesystemStack()->remove($dir."PKGBUILD"));
// build the package
$t->addTask($this->taskExec("makepkg -Ccf")->dir($dir));
return $t->run();
}
/** Packages a given commit of the software a binary Debian package
*
* The commit to package may be any Git tree-ish identifier: a tag, a branch,
* or any commit hash. If none is provided on the command line, Robo will prompt
* for a commit to package; the default is "HEAD".
*
* The pbuilder tool should be installed for this.
*/
public function packageDeb(string $commit = null): Result {
if (!$this->toolExists("git", "sudo", "pbuilder")) {
throw new \Exception("Git, sudo, and pbuilder are required in PATH to produce Debian packages");
}
// establish which commit to package
[$commit, $version] = $this->commitVersion($commit);
$tarball = BASE."release/$version/arsse-$version.tar.gz";
// define some more variables
$tgz = BASE."release/pbuilder-arsse.tgz";
$bind = dirname($tarball);
$script = BASE."dist/debian/pbuilder.sh";
$user = trim(`id -un`);
$group = trim(`id -gn`);
// start a task collection
$t = $this->collectionBuilder();
// check that the pbuilder base exists and create it if it does not
if (!file_exists($tgz)) {
$t->addTask($this->taskFilesystemStack()->mkdir(BASE."release"));
$t->addTask($this->taskExec('sudo pbuilder create --basetgz '.escapeshellarg($tgz).' --mirror http://ftp.ca.debian.org/debian/ --extrapackages "debhelper devscripts lintian"'));
}
// build the generic release tarball if it doesn't exist
if (!file_exists($tarball)) {
$t->addTask($this->taskExec(BASE."robo package:generic $commit"));
}
// build the packages
$t->addTask($this->taskExec('sudo pbuilder execute --basetgz '.escapeshellarg($tgz).' --bindmounts '.escapeshellarg($bind).' -- '.escapeshellarg($script).' '.escapeshellarg("$bind/".basename($tarball))));
// take ownership of the output files
$t->addTask($this->taskExec("sudo chown -R $user:$group ".escapeshellarg($bind)));
return $t->run();
}
/** Packages a release tarball into a Debian source package
*
* The commit to package may be any Git tree-ish identifier: a tag, a branch,
* or any commit hash. If none is provided on the command line, Robo will prompt
* for a commit to package; the default is "HEAD".
*/
public function packageDebsrc(string $commit = null): Result {
public function packageDebsrc(?string $commit = null): Result {
// establish which commit to package
[$commit, $version] = $this->commitVersion($commit);
$tarball = BASE."release/$version/arsse-$version.tar.gz";
@ -381,7 +338,7 @@ class RoboFile extends \Robo\Tasks {
// re-pack the tarball using a specific name special to Debian
$t->addTask($this->taskPack($dir."arsse_$baseVersion.orig.tar.gz")->addDir("arsse-$baseVersion", $base));
// pack the debian tarball
$t->addTask($this->taskPack($dir."arsse_$debVersion.debian.tar.gz")->addDir("debian", $base."dist"));
$t->addTask($this->taskPack($dir."arsse_$debVersion.debian.tar.gz")->addDir("debian", $base."dist/debian"));
// generate the DSC file
$t->addCode(function() use ($t, $debVersion, $baseVersion, $dir, $base) {
try {
@ -393,57 +350,74 @@ class RoboFile extends \Robo\Tasks {
return $this->taskWriteToFile($dir."arsse_$debVersion.dsc")->text($dsc)->run();
});
// delete any existing files
$t->AddTask($this->taskFilesystemStack()->remove(BASE."release/$version/arsse_$baseVersion.orig.tar.gz")->remove(BASE."release/$version/arsse_$debVersion.debian.tar.gz")->remove(BASE."release/$version/arsse_$debVersion.dsc"));
$t->AddTask($this->taskFilesystemStack()->remove([BASE."release/$version/arsse_$baseVersion.orig.tar.gz", BASE."release/$version/arsse_$debVersion.debian.tar.gz", BASE."release/$version/arsse_$debVersion.dsc"]));
// copy the new files over
$t->addTask($this->taskFilesystemStack()->copy($dir."arsse_$baseVersion.orig.tar.gz", BASE."release/$version/arsse_$baseVersion.orig.tar.gz")->copy($dir."arsse_$debVersion.debian.tar.gz", BASE."release/$version/arsse_$debVersion.debian.tar.gz")->copy($dir."arsse_$debVersion.dsc", BASE."release/$version/arsse_$debVersion.dsc"));
return $t->run();
}
/** Generates all possible package types for a given commit of the software
/** Packages a given commit of the software and produces all relevant release files
*
* The commit to package may be any Git tree-ish identifier: a tag, a branch,
* or any commit hash. If none is provided on the command line, Robo will prompt
* for a commit to package; the default is "HEAD".
*
* Generic release tarballs will always be generated, but distribution-specific
* packages are skipped when the required tools are not available
* In addition to the release tarball, a Debian source package, Arch PKGBUILD,
* and RPM spec file are output as well. These are suitable for use with Open
* Build Service instances and with slight modification the Arch User Repository.
* Use for Launchpad PPAs has not been tested.
*/
public function package(string $commit = null): Result {
public function package(?string $commit = null): Result {
if (!$this->toolExists("git")) {
throw new \Exception("Git is required in PATH to produce packages");
}
[$commit,] = $this->commitVersion($commit);
// determine whether the distribution-specific packages can be built
$dist = [
'Arch' => $this->toolExists("git", "makepkg", "updpkgsums"),
'Deb' => $this->toolExists("git", "sudo", "pbuilder"),
];
[$commit, $version] = $this->commitVersion($commit);
$tarball = BASE."release/$version/arsse-$version.tar.gz";
// build the generic release tarball
$result = $this->taskExec(BASE."robo package:generic $commit")->run();
if (!$result->wasSuccessful()) {
return $result;
}
// if the generic tarball could be built, try to produce Arch, Debian, and RPM files; these might legitimately not exist in old releases
// start by getting the list of files from the tarball
$archive = new \Archive_Tar($tarball);
$filelist = array_flip(array_column($archive->listContent(), "filename"));
// start a collection
$t = $this->collectionBuilder();
// build the generic release tarball
$t->addTask($this->taskExec(BASE."robo package:generic $commit"));
// build other packages
foreach ($dist as $distro => $run) {
if ($run) {
$subcmd = strtolower($distro);
$t->addTask($this->taskExec(BASE."robo package:$subcmd $commit"));
}
// Produce an Arch PKGBUILD if appropriate
if (isset($filelist['arsse/dist/arch/PKGBUILD'])) {
$t->addCode(function() use ($tarball, $archive) {
$dir = dirname($tarball).\DIRECTORY_SEPARATOR;
$archive->extractList("arsse/dist/arch/PKGBUILD", $dir, "arsse/dist/arch/", false);
// update the tarball's checksum
$sums = [
'md5' => hash_file("md5", $tarball),
];
return $this->taskReplaceInFile($dir."PKGBUILD")->regex('/^md5sums=\("SKIP"\)$/m')->to('md5sums=("'.$sums['md5'].'")')->run();
});
}
$out = $t->run();
// note any packages which were not built
foreach ($dist as $distro => $run) {
if (!$run) {
$this->say("Packages for $distro skipped");
}
// Produce a Debian source package if appropriate
if (isset($filelist['arsse/dist/debian/control']) && isset($filelist['arsse/dist/debian/source/format'])) {
$t->addTask($this->taskExec(BASE."robo package:debsrc $commit"));
}
return $out;
// Produce an RPM spec file if appropriate
if (isset($filelist['arsse/dist/rpm/arsse.spec'])) {
$t->addCode(function() use ($tarball, $archive) {
$dir = dirname($tarball).\DIRECTORY_SEPARATOR;
$archive->extractList("arsse/dist/rpm/arsse.spec", $dir, "arsse/dist/rpm/", false);
// perform a do-nothing filesystem operation since we need a Robo task result
return $this->taskFilesystemStack()->chmod($dir."arsse.spec", 0644)->run();
});
}
return $t->run();
}
/** Generates static manual pages in the "manual" directory
/** Generates static HTML manual pages in the "manual" directory
*
* The resultant files are suitable for offline viewing and inclusion into release builds
*/
public function manual(array $args): Result {
$execpath = escapeshellarg(norm(BASE."vendor/bin/daux"));
$execpath = escapeshellarg(norm(BASE."vendor-bin/daux/vendor/bin/daux"));
$t = $this->collectionBuilder();
$t->taskExec($execpath)->arg("generate")->option("-d", BASE."manual")->args($args);
$t->taskDeleteDir(BASE."manual/daux_libraries");
@ -454,7 +428,7 @@ class RoboFile extends \Robo\Tasks {
/** Serves a live view of the manual using the built-in Web server */
public function manualLive(array $args): Result {
$execpath = escapeshellarg(norm(BASE."vendor/bin/daux"));
$execpath = escapeshellarg(norm(BASE."vendor-bin/daux/vendor/bin/daux"));
return $this->taskExec($execpath)->arg("serve")->args($args)->run();
}
@ -485,29 +459,15 @@ class RoboFile extends \Robo\Tasks {
return $t->run();
}
/** Generates the "arsse" command's manual page (UNIX man page)
/** Parses the contents of the CHANGELOG file into an array structure
*
* This requires that the Pandoc document converter be installed and
* available in $PATH.
* This is done line-by-line and tends to be quite strict.
* The parsed output can be used to generate changelogs in other formats,
* such as a Debian changelog or RPM changelog.
*
* @param string $text The text of the CHANGELOG file
* @param string $targetVersion The x.y.z version number of the latest release. This is used to check that version numbers and dates have been updated when preparing a release
*/
public function manpage(): Result {
if (!$this->toolExists("pandoc")) {
throw new \Exception("Pandoc is required in PATH to generate manual pages");
}
$t = $this->collectionBuilder();
$man = [
'en' => "man1/arsse.1",
];
foreach ($man as $src => $out) {
$src = BASE."manpages/$src.md";
$out = BASE."dist/man/$out";
$t->addTask($this->taskFilesystemStack()->mkdir(dirname($out), 0755));
$t->addTask($this->taskExec("pandoc -s -f markdown-smart -t man -o ".escapeshellarg($out)." ".escapeshellarg($src)));
$t->addTask($this->taskReplaceInFile($out)->regex('/\.\n(?!\.)/s')->to(". "));
}
return $t->run();
}
protected function changelogParse(string $text, string $targetVersion): array {
$lines = preg_split('/\r?\n/', $text);
$version = "";
@ -517,7 +477,7 @@ class RoboFile extends \Robo\Tasks {
$expected = ["version"];
for ($a = 0; $a < sizeof($lines);) {
$l = rtrim($lines[$a++]);
if (in_array("version", $expected) && preg_match('/^Version (\d+(?:\.\d+)*) \(([\d\?]{4}-[\d\?]{2}-[\d\?]{2})\)\s*$/D', $l, $m)) {
if (in_array("version", $expected) && preg_match('/^Version ([\d\?]+(?:\.[\d\?]+)*) \(([\d\?]{4}-[\d\?]{2}-[\d\?]{2})\)\s*$/D', $l, $m)) {
$version = $m[1];
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/D', $m[2])) {
// uncertain dates are allowed only for the top version, and only if it does not match the target version (otherwise we have forgotten to set the correct date before tagging)
@ -562,7 +522,7 @@ class RoboFile extends \Robo\Tasks {
} elseif (in_array("item", $expected) && preg_match('/^- (\w.*)$/D', $l, $m)) {
$entry[$section][] = $m[1];
$expected = ["item", "continuation", "blank line"];
} elseif (in_array("continuation", $expected) && preg_match('/^ (\w.*)$/D', $l, $m)) {
} elseif (in_array("continuation", $expected) && preg_match('/^ (\S.*)$/D', $l, $m)) {
$last = sizeof($entry[$section]) - 1;
$entry[$section][$last] .= "\n".$m[1];
} else {
@ -584,7 +544,18 @@ class RoboFile extends \Robo\Tasks {
return $out;
}
/** Produce a Debian changelog from a parsed CHANGELOG file
*
* The Debian changelog format is extremely specific with certain tokens
* having special meaning and leading whitespace also being significant.
* Modifying this function should be done with extreme care.
*
* @param array $log The parsed chaneglog, output by changelogParse()
* @param string $targetVersion The second output of commitVersion()
*/
protected function changelogDebian(array $log, string $targetVersion): string {
$authorName = "J. King";
$authorMail = "jking@jkingweb.ca";
$latest = $log[0]['version'];
$baseVersion = preg_replace('/^(\d+(?:\.\d+)*).*/', "$1", $targetVersion);
if ($baseVersion !== $targetVersion && version_compare($latest, $baseVersion, ">")) {
@ -620,11 +591,20 @@ class RoboFile extends \Robo\Tasks {
$out .= " * ".trim(preg_replace("/^/m", " ", $item))."\n";
}
}
$out .= "\n -- J. King <jking@jkingweb.ca> ".\DateTimeImmutable::createFromFormat("Y-m-d", $entry['date'], new \DateTimeZone("UTC"))->format("D, d M Y")." 00:00:00 +0000\n\n";
$out .= "\n -- $authorName <$authorMail> ".\DateTimeImmutable::createFromFormat("Y-m-d", $entry['date'], new \DateTimeZone("UTC"))->format("D, d M Y")." 00:00:00 +0000\n\n";
}
return $out;
}
/** Produces a Debian "source control" file from various bits of data
*
* As with a Debian changelog, the output is of a very exacting format,
* and this function should be modified with care.
*
* @param string $dir The path to Debian-specific files, with trailing slash
* @param string $version The Debian version string, in the format x.y.z-a
* @param array $tarballs An array of paths to the "orig" and "debian" tarball files
*/
protected function generateDebianSourceControl(string $dir, string $version, array $tarballs): string {
// read in control file
if (!$control = @file_get_contents($dir."control")) {