chore: remove vendor from git tracking
This commit is contained in:
22
vendor/autoload.php
vendored
22
vendor/autoload.php
vendored
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit5de2040ea43272234872b658d6c40998::getLoader();
|
||||
605
vendor/behat/gherkin/CHANGELOG.md
vendored
605
vendor/behat/gherkin/CHANGELOG.md
vendored
@ -1,605 +0,0 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
This project follows the [Behat release and version support policies]
|
||||
(https://docs.behat.org/en/latest/releases.html).
|
||||
|
||||
# [4.16.1] - 2025-12-08
|
||||
|
||||
### Fixed
|
||||
|
||||
* Reinstate support for tag filter expressions without a leading `@` (e.g. `wip&&~slow` instead of `@wip&&~@slow`).
|
||||
This syntax was never officially supported, but previously worked and was broken by 4.16.0. We have temporarily
|
||||
fixed this, but it is deprecated and will be removed in the next major version.
|
||||
|
||||
# [4.16.0] - 2025-12-05
|
||||
|
||||
### Changed
|
||||
|
||||
* Further improvements to parser parity when the experimental `gherkin-32` compatibility mode is enabled:
|
||||
- Parse descriptions (instead of multiline titles) for all describable nodes by @acoulton in [#361](https://github.com/Behat/Gherkin/pull/361)
|
||||
- Unescape escaped delimiters within doc strings by @stof in [#393](https://github.com/Behat/Gherkin/pull/393)
|
||||
- Retain the `@` prefix when parsing tags by @acoulton in [#400](https://github.com/Behat/Gherkin/pull/400)
|
||||
- Trim unicode padding from table cells by @stof in [#405](https://github.com/Behat/Gherkin/pull/405)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix the implementation of the default dialect for the keywords provider by @stof in [#404](https://github.com/Behat/Gherkin/pull/404)
|
||||
|
||||
### Internal
|
||||
|
||||
* Add `Stringable` to classes implementing __toString() by @acoulton in [#402](https://github.com/Behat/Gherkin/pull/402)
|
||||
* Fix cucumber variant assertions to include inherited properties by @acoulton in [#394](https://github.com/Behat/Gherkin/pull/394)
|
||||
* Update cucumber/gherkin parity tests to 37.0.0 by @behat-gherkin-updater[bot] in [#397](https://github.com/Behat/Gherkin/pull/397) and [#398](https://github.com/Behat/Gherkin/pull/398)
|
||||
* update_cucumber script should not fail on manually created releases by @acoulton in [#396](https://github.com/Behat/Gherkin/pull/396)
|
||||
* Add funding links and information by @acoulton in [#401](https://github.com/Behat/Gherkin/pull/401)
|
||||
|
||||
# [4.15.0] - 2025-11-05
|
||||
|
||||
### Changed
|
||||
|
||||
* Added a new ParserInterface and deprecated extending the core Lexer, Parser and Node classes by @acoulton in [#354](https://github.com/Behat/Gherkin/pull/354)
|
||||
* Deprecate the CucumberNDJsonAstLoader (which was only intended for internal use by our tests) by @stof in [#356](https://github.com/Behat/Gherkin/pull/356)
|
||||
* By default, the parser ignores invalid language tags (e.g. `#language:no-such`) and falls back to the default language
|
||||
(e.g. `en`). Previously, the resultant `FeatureNode::getLanguage()` would return the original invalid value from the
|
||||
feature file - it will now return the language that was actually used for parsing. By @stof in [#350](https://github.com/Behat/Gherkin/pull/350)
|
||||
|
||||
### Added
|
||||
|
||||
* Introduce a DialectProviderInterface matching the modern cucumber API. This will replace the existing Keywords API in
|
||||
a future major release. By @stof in [#350](https://github.com/Behat/Gherkin/pull/350)
|
||||
* Introduce configurable `GherkinCompatibilityMode` to control how gherkin files are parsed. In the default `legacy` mode,
|
||||
there is no change to parsing. In the new **experimental** `gherkin-32` mode, files will in future be parsed
|
||||
consistently with the official cucumber/gherkin parsers. This mode is not yet complete - in this first release:
|
||||
- Whitespace within description nodes will not be trimmed by @acoulton in [#349](https://github.com/Behat/Gherkin/pull/349)
|
||||
- Invalid language tags will cause an exception by @stof in [#357](https://github.com/Behat/Gherkin/pull/357)
|
||||
- Step keywords will not be trimmed by @stof in [#360](https://github.com/Behat/Gherkin/pull/360)
|
||||
- Language tags can include whitespace by @acoulton in [#358](https://github.com/Behat/Gherkin/pull/358)
|
||||
- `\n` literals in table cells will be parsed as newlines by @stof in [#359](https://github.com/Behat/Gherkin/pull/359)
|
||||
and [#391](https://github.com/Behat/Gherkin/pull/391)
|
||||
* Improved translations for `ru` (Russian) and `af` (Afrikaans) from cucumber/gherkin in [#381](https://github.com/Behat/Gherkin/pull/381)
|
||||
and [#386](https://github.com/Behat/Gherkin/pull/386)
|
||||
* Support PHP 8.5 by @acoulton in [#388](https://github.com/Behat/Gherkin/pull/388)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Improve phpdoc / phpstan type-hinting of the lexer and parser by @uuf6429 in [#344](https://github.com/Behat/Gherkin/pull/344)
|
||||
and @stof in [#363](https://github.com/Behat/Gherkin/pull/363)
|
||||
* Handle race conditions when creating cache directory by @uuf6429 in [#373](https://github.com/Behat/Gherkin/pull/373)
|
||||
* Throw if Loader->load() called with unsupported resource by @uuf6429 in [#372](https://github.com/Behat/Gherkin/pull/372)
|
||||
* Use default file cache key if `behat/gherkin` version is unknown by @uuf6429 in [#370](https://github.com/Behat/Gherkin/pull/370)
|
||||
|
||||
### Internal
|
||||
|
||||
* Enable PHPStan level 10 and resolve remaining warnings by @uuf6429 in [#368](https://github.com/Behat/Gherkin/pull/368)
|
||||
* Remove duplication and improve robustness in filesystem operations by @uuf6429 in [#365](https://github.com/Behat/Gherkin/pull/365)
|
||||
and [#367](https://github.com/Behat/Gherkin/pull/367)
|
||||
* Explicitly cover expected departures from cucumber gherkin parsing with tests by @acoulton in [#392](https://github.com/Behat/Gherkin/pull/392)
|
||||
* Update cucumber/gherkin parity tests to v36.0.0 in [#355](https://github.com/Behat/Gherkin/pull/355), [#376](https://github.com/Behat/Gherkin/pull/376)
|
||||
[#378](https://github.com/Behat/Gherkin/pull/378), [#381](https://github.com/Behat/Gherkin/pull/381), [#385](https://github.com/Behat/Gherkin/pull/385)
|
||||
[#386](https://github.com/Behat/Gherkin/pull/386) and [#387](https://github.com/Behat/Gherkin/pull/387)
|
||||
* Fixes and improvements to the cucumber update CI job by @acoulton in [#374](https://github.com/Behat/Gherkin/pull/374),
|
||||
[#375](https://github.com/Behat/Gherkin/pull/375), [#379](https://github.com/Behat/Gherkin/pull/379)
|
||||
and [#380](https://github.com/Behat/Gherkin/pull/380)
|
||||
* Minor coding style fixes by @acoulton in [#377](https://github.com/Behat/Gherkin/pull/377) and [#383](https://github.com/Behat/Gherkin/pull/383)
|
||||
* Minor code improvements to Lexer/Parser implementation by @uuf6429 in [#352](https://github.com/Behat/Gherkin/pull/352)
|
||||
* Minor code improvements to TableNode by @uuf6429 in [#366](https://github.com/Behat/Gherkin/pull/366)
|
||||
* Add native typehints where this does not break BC by @stof in [#353](https://github.com/Behat/Gherkin/pull/353)
|
||||
* Fix typo of a PHPStan alias type by @uuf6429 in [#371](https://github.com/Behat/Gherkin/pull/371)
|
||||
* Fix github actions workflow job name by @uuf6429 in [#369](https://github.com/Behat/Gherkin/pull/369)
|
||||
|
||||
# [4.14.0] - 2025-05-23
|
||||
|
||||
### Changed
|
||||
|
||||
* Throw ParserException if file ends with tags by @acoulton in [#313](https://github.com/Behat/Gherkin/pull/313)
|
||||
* Throw ParserException if Background comes after first Scenario by @acoulton in [#343](https://github.com/Behat/Gherkin/pull/343)
|
||||
* For compatibility with the official cucumber/gherkin parsers, we now accept some gherkin syntax that would previously
|
||||
have triggered a ParserException. Users may wish to consider running a tool like gherkin-lint in CI to detect
|
||||
incomplete feature files or valid-but-unusual gherkin syntax. The specific changes are:
|
||||
- Parse `Scenario` and `Scenario Outline` as synonyms depending on the presence (or not) of an `Examples:` keyword.
|
||||
by @acoulton in [#316](https://github.com/Behat/Gherkin/pull/316) and [#324](https://github.com/Behat/Gherkin/pull/324)
|
||||
- Do not throw on some unexpected Feature / Language tags by @acoulton in [#323](https://github.com/Behat/Gherkin/pull/323)
|
||||
- Do not throw on `.feature` file that does not contain a Feature by @acoulton in [#340](https://github.com/Behat/Gherkin/pull/340)
|
||||
- Ignore content after table right-hand `|` (instead of throwing) by @acoulton in [#341](https://github.com/Behat/Gherkin/pull/341)
|
||||
* Remove the line length from the NewLine token value by @stof in [#338](https://github.com/Behat/Gherkin/pull/338)
|
||||
* Added precise PHPStan type information by @stof in [#332](https://github.com/Behat/Gherkin/pull/332),
|
||||
[#333](https://github.com/Behat/Gherkin/pull/333), [#339](https://github.com/Behat/Gherkin/pull/339)
|
||||
and [#334](https://github.com/Behat/Gherkin/pull/334)
|
||||
|
||||
### Internal
|
||||
|
||||
* Make private props readonly; fix tests by @uuf6429 in [#319](https://github.com/Behat/Gherkin/pull/319)
|
||||
* Use the `Yaml::parseFile` API to handle Yaml files by @stof in [#335](https://github.com/Behat/Gherkin/pull/335)
|
||||
* test: Make CucumberND name reading consistent by @uuf6429 in [#309](https://github.com/Behat/Gherkin/pull/309)
|
||||
* test: Use vfsStream to simplify / improve filesystem-related tests by @uuf6429 in [#298](https://github.com/Behat/Gherkin/pull/298)
|
||||
* test: Handle optional tableHeader when loading NDJson examples by @uuf6429 in [#294](https://github.com/Behat/Gherkin/pull/294)
|
||||
* test: Refactor valid ParserExceptionsTest examples into cucumber/gherkin testdata by @acoulton in [#322](https://github.com/Behat/Gherkin/pull/322)
|
||||
* test: Compare step arguments when checking gherkin parity by @acoulton in [#325](https://github.com/Behat/Gherkin/pull/325)
|
||||
* test: Use a custom object comparator to ignore the keywordType of StepNode by @stof in [#331](https://github.com/Behat/Gherkin/pull/331)
|
||||
* ci: Add conventional title to gherkin update, error on missing asserts by @acoulton in [#314](https://github.com/Behat/Gherkin/pull/314)
|
||||
* Assert that preg_split does not fail when splitting a table row by @stof in [#337](https://github.com/Behat/Gherkin/pull/337)
|
||||
* Add assertions in the parser to reflect the structure of tokens by @stof in [#342](https://github.com/Behat/Gherkin/pull/342)
|
||||
* style: Define and change phpdoc order coding style by @uuf6429 in [#345](https://github.com/Behat/Gherkin/pull/345)
|
||||
|
||||
|
||||
# [4.13.0] - 2025-05-06
|
||||
|
||||
### Changed
|
||||
|
||||
* Files have been moved to flatten paths into a PSR-4 structure (instead of the previous PSR-0). This may affect users
|
||||
who are requiring files directly rather than using the composer autoloader as expected.
|
||||
See the 4.12.0 release for the new `CachedArrayKeywords::withDefaultKeywords()` to use the `i18n.php` file without
|
||||
depending on paths to other files in this repo. By @uuf6429 in [#288](https://github.com/Behat/Gherkin/pull/288)
|
||||
|
||||
### Added
|
||||
|
||||
* ExampleTableNode now implements TaggedNodeInterface. Also refactored node tag handling methods. By @uuf6429 in
|
||||
[#289](https://github.com/Behat/Gherkin/pull/289)
|
||||
* Improve some exceptions thrown when parsing invalid feature files. Also increased test coverage. By @uuf6429 in
|
||||
[#295](https://github.com/Behat/Gherkin/pull/295)
|
||||
* New translations for `amh` (Amharic), `be` (Belarusian) and `ml` (Malayalam) from cucumber/gherkin in [#306](https://github.com/Behat/Gherkin/pull/306)
|
||||
* Improved translations / whitespace for `ga` (Irish), `it` (Italian), `ja` (Japanese), `ka` (Georgian) and `ko` (Korean)
|
||||
from cucumber/gherkin in [#306](https://github.com/Behat/Gherkin/pull/306)
|
||||
|
||||
### Internal
|
||||
|
||||
* Fix & improve automatic CI updates to newer cucumber/gherkin test data and translations. By @acoulton in
|
||||
[#300](https://github.com/Behat/Gherkin/pull/300), [#302](https://github.com/Behat/Gherkin/pull/302),
|
||||
[#304](https://github.com/Behat/Gherkin/pull/304), [#305](https://github.com/Behat/Gherkin/pull/305)
|
||||
* Update code style and resolve PHPStan warnings (up to level 9) in tests and CI scripts. By @uuf6429 in
|
||||
[#296](https://github.com/Behat/Gherkin/pull/296), [#297](https://github.com/Behat/Gherkin/pull/297)
|
||||
and [#307](https://github.com/Behat/Gherkin/pull/307)
|
||||
* Make tests that expect exceptions more explicit by @uuf6429 in [#310](https://github.com/Behat/Gherkin/pull/310)
|
||||
* Improve CI workflows and integrate Codecov reporting by @uuf6429 in [#299](https://github.com/Behat/Gherkin/pull/299)
|
||||
and [#301](https://github.com/Behat/Gherkin/pull/301)
|
||||
* Refactor tag filtering implementation by @uuf6429 in [#308](https://github.com/Behat/Gherkin/pull/308)
|
||||
* Update cucumber/gherkin parity tests to v32.1.1 in [#306](https://github.com/Behat/Gherkin/pull/306)
|
||||
|
||||
# [4.12.0] - 2025-02-26
|
||||
|
||||
### Changed
|
||||
* Gherkin::VERSION is deprecated and will not be updated, use the composer runtime API if you need to identify the
|
||||
running version. This also changes the value used to namespace cached feature files.
|
||||
by @acoulton in [#279](https://github.com/Behat/Gherkin/pull/279)
|
||||
|
||||
### Added
|
||||
|
||||
* Provide `CachedArrayKeywords::withDefaultKeywords()` to create an instance without an external dependency on the path
|
||||
to the `i18n.php` file in this repo. **NOTE** that paths to source files will change in the next Gherkin release -
|
||||
use the new constructor to avoid any impact.
|
||||
by @carlos-granados in [#290](https://github.com/Behat/Gherkin/pull/290)
|
||||
|
||||
### Internal
|
||||
|
||||
* Upgrade to phpunit 10 by @uuf6429 in [#275](https://github.com/Behat/Gherkin/pull/275)
|
||||
* Remove redundant files by @uuf6429 in [#278](https://github.com/Behat/Gherkin/pull/278)
|
||||
* Update documentation by @uuf6429 in [#274](https://github.com/Behat/Gherkin/pull/274)
|
||||
* Adopt PHP CS Fixer and apply code styles by @uuf6429 in [#277](https://github.com/Behat/Gherkin/pull/277)
|
||||
* Add PHPStan and improve / fix docblock annotations and type-safety within methods to achieve level 5 by
|
||||
@uuf6429 in [#276](https://github.com/Behat/Gherkin/pull/276), [#281](https://github.com/Behat/Gherkin/pull/281),
|
||||
[#282](https://github.com/Behat/Gherkin/pull/282), and [#287](https://github.com/Behat/Gherkin/pull/287)
|
||||
|
||||
# [4.11.0] - 2024-12-06
|
||||
|
||||
### Changed
|
||||
|
||||
* Drop support for PHP < 8.1, Symfony < 5.4 and Symfony 6.0 - 6.3. In future we will drop support for PHP and symfony
|
||||
versions as they reach EOL. by @acoulton in [#272](https://github.com/Behat/Gherkin/pull/272)
|
||||
* Deprecated `ExampleNode::getTitle()` and `ScenarioNode::getTitle()` in favour of new methods with clearer meaning.
|
||||
by @uuf6429 in [#271](https://github.com/Behat/Gherkin/pull/271)
|
||||
|
||||
### Added
|
||||
|
||||
* Added `(ExampleNode|ScenarioNode)::getName()` to access human-readable names for examples and scenarios,
|
||||
and `ExampleNode::getExampleText()` for the string content of the example table row.
|
||||
by @uuf6429 in [#271](https://github.com/Behat/Gherkin/pull/271)
|
||||
|
||||
### Internal
|
||||
|
||||
* Enable dependabot for github actions workflows by @jrfnl in [#261](https://github.com/Behat/Gherkin/pull/261)
|
||||
|
||||
# 4.10.0 / 2024-10-19
|
||||
|
||||
### Changed
|
||||
|
||||
- **⚠ Backslashes in feature files must now be escaped**\
|
||||
Gherkin syntax treats `\` as an escape character, which must be escaped (`\\`) to use it as a
|
||||
literal value. Historically, this was not being parsed correctly. This release fixes that bug,
|
||||
but means that if your scenarios currently use unescaped `\` you will need to replace each one
|
||||
with `\\` to achieve the same parsed result.
|
||||
By @everzet in 5a0836d.
|
||||
|
||||
### Added
|
||||
- Symfony 6 and 7 thanks to @tacman in #257
|
||||
- PHP 8.4 support thanks to @heiglandreas in #258 and @jrfnl in #262
|
||||
|
||||
### Fixed
|
||||
- Fix exception when filter string is empty thanks to @magikid in #251
|
||||
|
||||
### Internal
|
||||
- Sync teststuite with Cucumber 24.1.0
|
||||
- Fix PHPUnit 10 deprecation messages
|
||||
- A lot of great CI work by @heiglandreas and @jrfnl
|
||||
|
||||
# 4.9.0 / 2021-10-12
|
||||
|
||||
- Simplify the boolean condition for the tag matching by @stof in https://github.com/Behat/Gherkin/pull/219
|
||||
- Remove symfony phpunit bridge by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/220
|
||||
- Ignore the bin folder in archives by @stof in https://github.com/Behat/Gherkin/pull/226
|
||||
- Cast table node exceptions into ParserExceptions when throwing by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/216
|
||||
- Cucumber changelog in PRs and using correct hash by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/225
|
||||
- Support alternative docstrings format (```) by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/214
|
||||
- Fix DocBlocks (Boolean -> bool) by @simonhammes in https://github.com/Behat/Gherkin/pull/237
|
||||
- Tag parsing by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/215
|
||||
- Remove test - cucumber added an example with Rule which is not supported by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/239
|
||||
- Add PHP 8.1 support by @javer in https://github.com/Behat/Gherkin/pull/242
|
||||
- Fix main branch alias version by @mvorisek in https://github.com/Behat/Gherkin/pull/244
|
||||
|
||||
# 4.8.0 / 2021-02-04
|
||||
|
||||
- Drop support for PHP before version 7.2
|
||||
|
||||
# 4.7.3 / 2021-02-04
|
||||
|
||||
- Refactored comments parsing to avoid Maximum function nesting level errors
|
||||
|
||||
# 4.7.2 / 2021-02-03
|
||||
|
||||
- Issue where Scenario Outline title was not populated into Examples
|
||||
- Updated translations from cucumber 16.0.0
|
||||
|
||||
# 4.7.1 / 2021-01-26
|
||||
|
||||
- Issue parsing comments before scenarios when following an Examples table
|
||||
|
||||
# 4.7.0 / 2021-01-24
|
||||
|
||||
- Provides better messages for TableNode construct errors
|
||||
- Now allows single character steps
|
||||
- Supports multiple Example Tables with tags
|
||||
|
||||
# 4.6.2 / 2020-03-17
|
||||
|
||||
- Fixed issues due to incorrect cache key
|
||||
|
||||
# 4.6.1 / 2020-02-27
|
||||
|
||||
- Fix AZ translations
|
||||
- Correctly filter features, now that the base path is correctly set
|
||||
|
||||
# 4.6.0 / 2019-01-16
|
||||
|
||||
- Updated translations (including 'Example' as synonym for 'Scenario' in `en`)
|
||||
|
||||
# 4.5.1 / 2017-08-30
|
||||
|
||||
- Fix regression in `PathsFilter`
|
||||
|
||||
# 4.5.0 / 2017-08-30
|
||||
|
||||
- Sync i18n with Cucumber Gherkin
|
||||
- Drop support for HHVM tests on Travis
|
||||
- Add `TableNode::fromList()` method (thanks @TravisCarden)
|
||||
- Add `ExampleNode::getOutlineTitle()` method (thanks @duxet)
|
||||
- Use realpath, so the feature receives the cwd prefixed (thanks @glennunipro)
|
||||
- Explicitly handle non-two-dimensional arrays in TableNode (thanks @TravisCarden)
|
||||
- Fix to line/linefilter scenario runs which take relative paths to files (thanks @generalconsensus)
|
||||
|
||||
# 4.4.5 / 2016-10-30
|
||||
|
||||
- Fix partial paths matching in `PathsFilter`
|
||||
|
||||
# 4.4.4 / 2016-09-18
|
||||
|
||||
- Provide clearer exception for non-writeable cache directories
|
||||
|
||||
# 4.4.3 / 2016-09-18
|
||||
|
||||
- Ensure we reset tags between features
|
||||
|
||||
# 4.4.2 / 2016-09-03
|
||||
|
||||
- Sync 18n with gherkin 3
|
||||
|
||||
# 4.4.1 / 2015-12-30
|
||||
|
||||
- Ensure keywords are trimmed when syncing translations
|
||||
- Sync 18n with cucumber
|
||||
|
||||
# 4.4.0 / 2015-09-19
|
||||
|
||||
- Added validation enforcing that all rows of a `TableNode` have the same number of columns
|
||||
- Added `TableNode::getColumn` to get a column from the table
|
||||
- Sync 18n with cucumber
|
||||
|
||||
# 4.3.0 / 2014-06-06
|
||||
|
||||
- Added `setFilters(array)` method to `Gherkin` class
|
||||
- Added `NarrativeFilter` for non-english `RoleFilter` lovers
|
||||
|
||||
# 4.2.1 / 2014-06-06
|
||||
|
||||
- Fix parsing of features without line feed at the end
|
||||
|
||||
# 4.2.0 / 2014-05-27
|
||||
|
||||
- Added `getKeyword()` and `getKeywordType()` methods to `StepNode`, deprecated `getType()`.
|
||||
Thanks to @kibao
|
||||
|
||||
# 4.1.3 / 2014-05-25
|
||||
|
||||
- Properly handle tables with rows terminating in whitespace
|
||||
|
||||
# 4.1.2 / 2014-05-14
|
||||
|
||||
- Handle case where Gherkin cache is broken
|
||||
|
||||
# 4.1.1 / 2014-05-05
|
||||
|
||||
- Fixed the compatibility with PHP 5.6-beta by avoiding to use the broken PHP array function
|
||||
- The YamlFileLoader no longer extend from ArrayLoader but from AbstractFileLoader
|
||||
|
||||
# 4.1.0 / 2014-04-20
|
||||
|
||||
- Fixed scenario tag filtering
|
||||
- Do not allow multiple multiline step arguments
|
||||
- Sync 18n with cucumber
|
||||
|
||||
# 4.0.0 / 2014-01-05
|
||||
|
||||
- Changed the behavior when no loader can be found for the resource. Instead of throwing an exception, the
|
||||
Gherkin class now returns an empty array.
|
||||
|
||||
# 3.1.3 / 2014-01-04
|
||||
|
||||
- Dropped the dependency on the Symfony Finder by using SPL iterators directly
|
||||
- Added testing on HHVM on Travis. HHVM is officially supported (previous release was actually already compatible)
|
||||
|
||||
# 3.1.2 / 2014-01-01
|
||||
|
||||
- All paths passed to PathsFilter are converted using realpath
|
||||
|
||||
# 3.1.1 / 2013-12-31
|
||||
|
||||
- Add `ComplexFilterInterace` that has complex behavior for scenarios and requires to pass
|
||||
feature too
|
||||
- `TagFilter` is an instance of a `ComplexFilterInterace` now
|
||||
|
||||
# 3.1.0 / 2013-12-31
|
||||
|
||||
- Example node is a scenario
|
||||
- Nodes do not have uprefs (memory usage fix)
|
||||
- Scenario filters do not depend on feature nodes
|
||||
|
||||
# 3.0.5 / 2014-01-01
|
||||
|
||||
- All paths passed to PathsFilter are converted using realpath
|
||||
|
||||
# 3.0.4 / 2013-12-31
|
||||
|
||||
- TableNode is now traversable using foreach
|
||||
- All possibly thrown exceptions implement Gherkin\Exception interface
|
||||
- Sync i18n with cucumber
|
||||
|
||||
# 3.0.3 / 2013-09-15
|
||||
|
||||
- Extend ExampleNode with additional methods
|
||||
|
||||
# 3.0.2 / 2013-09-14
|
||||
|
||||
- Extract `KeywordNodeInterface` and `ScenarioLikeInterface`
|
||||
- Add `getIndex()` methods to scenarios, outlines, steps and examples
|
||||
- Throw proper exception for fractured node tree
|
||||
|
||||
# 3.0.1 / 2013-09-14
|
||||
|
||||
- Use versioned subfolder in FileCache
|
||||
|
||||
# 3.0.0 / 2013-09-14
|
||||
|
||||
- A lot of optimizations in Parser and Lexer
|
||||
- Node tree is now immutable by nature (no setters)
|
||||
- Example nodes are now part of the node tree. They are lazily generated by Outline node
|
||||
- Sync with latest cucumber i18n
|
||||
|
||||
# 2.3.4 / 2013-08-11
|
||||
|
||||
- Fix leaks in memory cache
|
||||
|
||||
# 2.3.3 / 2013-08-11
|
||||
|
||||
- Fix encoding bug introduced with previous release
|
||||
- Sync i18n with cucumber
|
||||
|
||||
# 2.3.2 / 2013-08-11
|
||||
|
||||
- Explicitly use utf8 encoding
|
||||
|
||||
# 2.3.1 / 2013-08-10
|
||||
|
||||
- Support `an` prefix with RoleFilter
|
||||
|
||||
# 2.3.0 / 2013-08-04
|
||||
|
||||
- Add RoleFilter
|
||||
- Add PathsFilter
|
||||
- Add MemoryCache
|
||||
|
||||
# 2.2.9 / 2013-03-02
|
||||
|
||||
- Fix dependency version requirement
|
||||
|
||||
# 2.2.8 / 2013-03-02
|
||||
|
||||
- Features filtering behavior change. Now emptified (by filtering) features
|
||||
that do not match filter themselves are removed from resultset.
|
||||
- Small potential bug fix in TableNode
|
||||
|
||||
# 2.2.7 / 2013-01-27
|
||||
|
||||
- Fixed bug in i18n syncing script
|
||||
- Resynced Gherkin i18n
|
||||
|
||||
# 2.2.6 / 2013-01-26
|
||||
|
||||
- Support long row hashes in tables ([see](https://github.com/Behat/Gherkin/issues/40))
|
||||
- Synced Gherkin i18n
|
||||
|
||||
# 2.2.5 / 2012-09-26
|
||||
|
||||
- Fixed issue with loading empty features
|
||||
- Synced Gherkin i18n
|
||||
|
||||
# 2.2.4 / 2012-08-03
|
||||
|
||||
- Fixed exception message for "no loader found"
|
||||
|
||||
# 2.2.3 / 2012-08-03
|
||||
|
||||
- Fixed minor loader bug with empty base path
|
||||
- Synced Gherkin i18n
|
||||
|
||||
# 2.2.2 / 2012-07-01
|
||||
|
||||
- Added ability to filter outline scenarios by line and range filters
|
||||
- Synced Gherkin i18n
|
||||
- Refactored table parser to read row line numbers too
|
||||
|
||||
# 2.2.1 / 2012-05-04
|
||||
|
||||
- Fixed StepNode `getLanguage()` and `getFile()`
|
||||
|
||||
# 2.2.0 / 2012-05-03
|
||||
|
||||
- Features freeze after parsing
|
||||
- Implemented GherkinDumper (@Halleck45)
|
||||
- Synced i18n with Cucumber
|
||||
- Updated inline documentation
|
||||
|
||||
# 2.1.1 / 2012-03-09
|
||||
|
||||
- Fixed caching bug, where `isFresh()` always returned false
|
||||
|
||||
# 2.1.0 / 2012-03-09
|
||||
|
||||
- Added parser caching layer
|
||||
- Added support for table delimiter escaping (use `\|` for that)
|
||||
- Added LineRangeFilter (thanks @headrevision)
|
||||
- Synced i18n dictionary with cucumber/gherkin
|
||||
|
||||
# 2.0.2 / 2012-02-04
|
||||
|
||||
- Synced i18n dictionary with cucumber/gherkin
|
||||
|
||||
# 2.0.1 / 2012-01-26
|
||||
|
||||
- Fixed issue about parsing features without indentation
|
||||
|
||||
# 2.0.0 / 2012-01-19
|
||||
|
||||
- Background titles support
|
||||
- Correct parsing of titles/descriptions (hirarchy lexing)
|
||||
- Migration to the cucumber/gherkin i18n dictionary
|
||||
- Speed optimizations
|
||||
- Refactored KeywordsDumper
|
||||
- New loaders
|
||||
- Bugfixes
|
||||
|
||||
# 1.1.4 / 2012-01-08
|
||||
|
||||
- Read feature description even if it looks like a step
|
||||
|
||||
# 1.1.3 / 2011-12-14
|
||||
|
||||
- Removed file loading routines from Parser (fixes `is_file()` issue on some systems - thanks
|
||||
@flodocteurklein)
|
||||
|
||||
# 1.1.2 / 2011-12-01
|
||||
|
||||
- Updated spanish trasnaltion (@anbotero)
|
||||
- Integration with Composer and Travis CI
|
||||
|
||||
# 1.1.1 / 2011-07-29
|
||||
|
||||
- Updated pt language step types (@danielcsgomes)
|
||||
- Updated vendors
|
||||
|
||||
# 1.1.0 / 2011-07-16
|
||||
|
||||
- Return all tags, including inherited in `Scenario::getTags()`
|
||||
- New `Feature::getOwnTags()` and `Scenario::getOwnTags()` method added,
|
||||
which returns only own tags
|
||||
|
||||
# 1.0.8 / 2011-06-29
|
||||
|
||||
- Fixed comments parsing.
|
||||
You can’t have comments at the end of a line # like this
|
||||
# But you can still have comments at the beginning of a line
|
||||
|
||||
# 1.0.7 / 2011-06-28
|
||||
|
||||
- Added `getRaw()` method to PyStringNode
|
||||
- Updated vendors
|
||||
|
||||
# 1.0.6 / 2011-06-17
|
||||
|
||||
- Updated vendors
|
||||
|
||||
# 1.0.5 / 2011-06-10
|
||||
|
||||
- Fixed bug, introduced with 1.0.4 - hash in PyStrings
|
||||
|
||||
# 1.0.4 / 2011-06-10
|
||||
|
||||
- Fixed inability to comment pystrings
|
||||
|
||||
# 1.0.3 / 2011-04-21
|
||||
|
||||
- Fixed introduced with 1.0.2 pystring parsing bug
|
||||
|
||||
# 1.0.2 / 2011-04-18
|
||||
|
||||
- Fixed bugs in text with comments parsing
|
||||
|
||||
# 1.0.1 / 2011-04-01
|
||||
|
||||
- Updated vendors
|
||||
|
||||
# 1.0.0 / 2011-03-08
|
||||
|
||||
- Updated vendors
|
||||
|
||||
# 1.0.0RC2 / 2011-02-25
|
||||
|
||||
- Windows support
|
||||
- Missing phpunit config
|
||||
|
||||
# 1.0.0RC1 / 2011-02-15
|
||||
|
||||
- Huge optimizations to Lexer & Parser
|
||||
- Additional loaders (Yaml, Array, Directory)
|
||||
- Filters (Tag, Name, Line)
|
||||
- Code refactoring
|
||||
- Nodes optimizations
|
||||
- Additional tests for exceptions and translations
|
||||
- Keywords dumper
|
||||
|
||||
# 0.2.0 / 2011-01-05
|
||||
|
||||
- New Parser & Lexer (based on AST)
|
||||
- New verbose parsing exception handling
|
||||
- New translation mechanics
|
||||
- 47 brand new translations (see i18n)
|
||||
- Full test suite for everything from AST nodes to translations
|
||||
|
||||
[4.16.1]: https://github.com/Behat/Gherkin/compare/v4.16.0...v4.16.1
|
||||
[4.16.0]: https://github.com/Behat/Gherkin/compare/v4.15.0...v4.16.0
|
||||
[4.15.0]: https://github.com/Behat/Gherkin/compare/v4.14.0...v4.15.0
|
||||
[4.14.0]: https://github.com/Behat/Gherkin/compare/v4.13.0...v4.14.0
|
||||
[4.13.0]: https://github.com/Behat/Gherkin/compare/v4.12.0...v4.13.0
|
||||
[4.12.0]: https://github.com/Behat/Gherkin/compare/v4.11.0...v4.12.0
|
||||
[4.11.0]: https://github.com/Behat/Gherkin/compare/v4.10.0...v4.11.0
|
||||
22
vendor/behat/gherkin/LICENSE
vendored
22
vendor/behat/gherkin/LICENSE
vendored
@ -1,22 +0,0 @@
|
||||
Copyright (c) 2011-2013 Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
81
vendor/behat/gherkin/README.md
vendored
81
vendor/behat/gherkin/README.md
vendored
@ -1,81 +0,0 @@
|
||||
# Behat Gherkin Parser
|
||||
|
||||
This is the php Gherkin parser for Behat. It comes bundled with more than 40 native languages (see `i18n.php`) support
|
||||
and clean architecture.
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [Behat Site](https://behat.org)
|
||||
- [Note on Patches/Pull Requests](CONTRIBUTING.md)
|
||||
|
||||
## Usage Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$keywords = new Behat\Gherkin\Keywords\ArrayKeywords(array(
|
||||
'en' => array(
|
||||
'feature' => 'Feature',
|
||||
'background' => 'Background',
|
||||
'scenario' => 'Scenario',
|
||||
'scenario_outline' => 'Scenario Outline|Scenario Template',
|
||||
'examples' => 'Examples|Scenarios',
|
||||
'given' => 'Given',
|
||||
'when' => 'When',
|
||||
'then' => 'Then',
|
||||
'and' => 'And',
|
||||
'but' => 'But'
|
||||
),
|
||||
'en-pirate' => array(
|
||||
'feature' => 'Ahoy matey!',
|
||||
'background' => 'Yo-ho-ho',
|
||||
'scenario' => 'Heave to',
|
||||
'scenario_outline' => 'Shiver me timbers',
|
||||
'examples' => 'Dead men tell no tales',
|
||||
'given' => 'Gangway!',
|
||||
'when' => 'Blimey!',
|
||||
'then' => 'Let go and haul',
|
||||
'and' => 'Aye',
|
||||
'but' => 'Avast!'
|
||||
)
|
||||
));
|
||||
$lexer = new Behat\Gherkin\Lexer($keywords);
|
||||
$parser = new Behat\Gherkin\Parser($lexer);
|
||||
|
||||
$feature = $parser->parse(file_get_contents('some.feature'));
|
||||
```
|
||||
|
||||
## Installing Dependencies
|
||||
|
||||
```shell
|
||||
curl https://getcomposer.org/installer | php
|
||||
php composer.phar update
|
||||
```
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
- Konstantin Kudryashov [everzet](https://github.com/everzet) [original developer]
|
||||
- Andrew Coulton [acoulton](https://github.com/acoulton) [current maintainer]
|
||||
- Carlos Granados [carlos-granados](https://github.com/carlos-granados) [current maintainer]
|
||||
- Christophe Coevoet [stof](https://github.com/stof) [current maintainer]
|
||||
- Other [awesome developers](https://github.com/Behat/Gherkin/graphs/contributors)
|
||||
|
||||
Support the project
|
||||
-------------------
|
||||
|
||||
Behat is free software, maintained by volunteers as a gift for users. If you'd like to see
|
||||
the project continue to thrive, and particularly if you use it for work, we'd encourage you
|
||||
to contribute.
|
||||
|
||||
Contributions of time - whether code, documentation, or support reviewing PRs and triaging
|
||||
issues - are very welcome and valued by the maintainers and the wider Behat community.
|
||||
|
||||
But we also believe that [financial sponsorship is an important part of a healthy Open Source
|
||||
ecosystem](https://opensourcepledge.com/about/). Maintaining a project like Behat requires a
|
||||
significant commitment from the core team: your support will help us to keep making that time
|
||||
available over the long term. Even small contributions make a big difference.
|
||||
|
||||
You can support [@acoulton](https://github.com/acoulton), [@carlos-granados](https://github.com/carlos-granados) and
|
||||
[@stof](https://github.com/stof) on GitHub sponsors. If you'd like to discuss supporting us in a different way, please
|
||||
get in touch!
|
||||
95
vendor/behat/gherkin/composer.json
vendored
95
vendor/behat/gherkin/composer.json
vendored
@ -1,95 +0,0 @@
|
||||
{
|
||||
"name": "behat/gherkin",
|
||||
"description": "Gherkin DSL parser for PHP",
|
||||
"keywords": ["BDD", "parser", "DSL", "Behat", "Gherkin", "Cucumber"],
|
||||
"homepage": "https://behat.org/",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Konstantin Kudryashov",
|
||||
"email": "ever.zet@gmail.com",
|
||||
"homepage": "https://everzet.com"
|
||||
}
|
||||
],
|
||||
|
||||
"require": {
|
||||
"php": ">=8.1 <8.6",
|
||||
"composer-runtime-api": "^2.2"
|
||||
},
|
||||
|
||||
"require-dev": {
|
||||
"symfony/yaml": "^5.4 || ^6.4 || ^7.0",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"cucumber/gherkin-monorepo": "dev-gherkin-v37.0.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.77",
|
||||
"phpstan/phpstan": "^2",
|
||||
"phpstan/extension-installer": "^1",
|
||||
"phpstan/phpstan-phpunit": "^2",
|
||||
"mikey179/vfsstream": "^1.6"
|
||||
},
|
||||
|
||||
"suggest": {
|
||||
"symfony/yaml": "If you want to parse features, represented in YAML files"
|
||||
},
|
||||
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Behat\\Gherkin\\": "src/"
|
||||
}
|
||||
},
|
||||
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\Behat\\Gherkin\\": "tests/"
|
||||
}
|
||||
},
|
||||
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.x-dev"
|
||||
}
|
||||
},
|
||||
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "cucumber/gherkin-monorepo",
|
||||
"version": "dev-gherkin-v37.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cucumber/gherkin.git",
|
||||
"reference": "1e49335524c384694fe9faa843d74b550fb330c5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/cucumber/gherkin/zipball/1e49335524c384694fe9faa843d74b550fb330c5",
|
||||
"reference": "1e49335524c384694fe9faa843d74b550fb330c5"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"scripts": {
|
||||
"lint": [
|
||||
"phpstan analyze --ansi --no-progress --memory-limit=-1",
|
||||
"phpstan analyze bin/update_cucumber --ansi --no-progress --memory-limit=-1",
|
||||
"phpstan analyze bin/update_i18n --ansi --no-progress --memory-limit=-1",
|
||||
"php-cs-fixer check --diff --ansi --show-progress=dots --verbose"
|
||||
],
|
||||
"test": [
|
||||
"phpunit --colors=always"
|
||||
],
|
||||
"fix": [
|
||||
"php-cs-fixer fix --diff --ansi --show-progress=dots"
|
||||
]
|
||||
},
|
||||
|
||||
"config": {
|
||||
"process-timeout": 0,
|
||||
"allow-plugins": {
|
||||
"phpstan/extension-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
1293
vendor/behat/gherkin/i18n.php
vendored
1293
vendor/behat/gherkin/i18n.php
vendored
File diff suppressed because it is too large
Load Diff
3833
vendor/behat/gherkin/resources/gherkin-languages.json
vendored
3833
vendor/behat/gherkin/resources/gherkin-languages.json
vendored
File diff suppressed because it is too large
Load Diff
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Cache;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
|
||||
/**
|
||||
* Parser cache interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface CacheInterface
|
||||
{
|
||||
/**
|
||||
* Checks that cache for feature exists and is fresh.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
* @param int $timestamp The last time feature was updated
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFresh(string $path, int $timestamp);
|
||||
|
||||
/**
|
||||
* Reads feature cache from path.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
*
|
||||
* @return FeatureNode
|
||||
*/
|
||||
public function read(string $path);
|
||||
|
||||
/**
|
||||
* Caches feature node.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function write(string $path, FeatureNode $feature);
|
||||
}
|
||||
136
vendor/behat/gherkin/src/Cache/FileCache.php
vendored
136
vendor/behat/gherkin/src/Cache/FileCache.php
vendored
@ -1,136 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Cache;
|
||||
|
||||
use Behat\Gherkin\Exception\CacheException;
|
||||
use Behat\Gherkin\Exception\FilesystemException;
|
||||
use Behat\Gherkin\Filesystem;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Composer\InstalledVersions;
|
||||
|
||||
/**
|
||||
* File cache.
|
||||
* Caches feature into a file.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class FileCache implements CacheInterface
|
||||
{
|
||||
private readonly string $path;
|
||||
|
||||
/**
|
||||
* Used as part of the cache directory path to invalidate cache if the installed package version changes.
|
||||
*/
|
||||
private static function getGherkinVersionHash(): string
|
||||
{
|
||||
$version = InstalledVersions::getVersion('behat/gherkin') ?? 'unknown';
|
||||
|
||||
// Composer version strings can contain arbitrary content so hash for filesystem safety
|
||||
return md5($version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes file cache.
|
||||
*
|
||||
* @param string $path path to the folder where to store caches
|
||||
*
|
||||
* @throws CacheException
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
$this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . self::getGherkinVersionHash();
|
||||
|
||||
try {
|
||||
Filesystem::ensureDirectoryExists($this->path);
|
||||
} catch (FilesystemException $ex) {
|
||||
throw new CacheException(
|
||||
sprintf(
|
||||
'Cache path "%s" cannot be created or is not a directory: %s',
|
||||
$this->path,
|
||||
$ex->getMessage(),
|
||||
),
|
||||
previous: $ex
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_writable($this->path)) {
|
||||
throw new CacheException(sprintf('Cache path "%s" is not writeable. Check your filesystem permissions or disable Gherkin file cache.', $this->path));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that cache for feature exists and is fresh.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
* @param int $timestamp The last time feature was updated
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFresh(string $path, int $timestamp)
|
||||
{
|
||||
$cachePath = $this->getCachePathFor($path);
|
||||
|
||||
if (!file_exists($cachePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Filesystem::getLastModified($cachePath) > $timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads feature cache from path.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
*
|
||||
* @return FeatureNode
|
||||
*
|
||||
* @throws CacheException
|
||||
*/
|
||||
public function read(string $path)
|
||||
{
|
||||
$cachePath = $this->getCachePathFor($path);
|
||||
try {
|
||||
$feature = unserialize(Filesystem::readFile($cachePath), ['allowed_classes' => true]);
|
||||
} catch (FilesystemException $ex) {
|
||||
throw new CacheException("Can not load cache: {$ex->getMessage()}", previous: $ex);
|
||||
}
|
||||
|
||||
if (!$feature instanceof FeatureNode) {
|
||||
throw new CacheException(sprintf('Can not load cache for a feature "%s" from "%s".', $path, $cachePath));
|
||||
}
|
||||
|
||||
return $feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches feature node.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function write(string $path, FeatureNode $feature)
|
||||
{
|
||||
file_put_contents($this->getCachePathFor($path), serialize($feature));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature cache file path from features path.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getCachePathFor(string $path)
|
||||
{
|
||||
return $this->path . '/' . md5($path) . '.feature.cache';
|
||||
}
|
||||
}
|
||||
73
vendor/behat/gherkin/src/Cache/MemoryCache.php
vendored
73
vendor/behat/gherkin/src/Cache/MemoryCache.php
vendored
@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Cache;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
|
||||
/**
|
||||
* Memory cache.
|
||||
* Caches feature into a memory.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class MemoryCache implements CacheInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, FeatureNode>
|
||||
*/
|
||||
private array $features = [];
|
||||
/**
|
||||
* @var array<string, int>
|
||||
*/
|
||||
private array $timestamps = [];
|
||||
|
||||
/**
|
||||
* Checks that cache for feature exists and is fresh.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
* @param int $timestamp The last time feature was updated
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFresh(string $path, int $timestamp)
|
||||
{
|
||||
if (!isset($this->features[$path])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->timestamps[$path] > $timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads feature cache from path.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
*
|
||||
* @return FeatureNode
|
||||
*/
|
||||
public function read(string $path)
|
||||
{
|
||||
return $this->features[$path];
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches feature node.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function write(string $path, FeatureNode $feature)
|
||||
{
|
||||
$this->features[$path] = $feature;
|
||||
$this->timestamps[$path] = time();
|
||||
}
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Dialect;
|
||||
|
||||
use Behat\Gherkin\Exception\NoSuchLanguageException;
|
||||
use Behat\Gherkin\Filesystem;
|
||||
|
||||
/**
|
||||
* A dialect provider that loads the dialects based on the gherkin-languages.json file copied from the Cucumber project.
|
||||
*
|
||||
* @phpstan-import-type TDialectData from GherkinDialect
|
||||
*/
|
||||
final class CucumberDialectProvider implements DialectProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var non-empty-array<non-empty-string, TDialectData>
|
||||
*/
|
||||
private readonly array $dialects;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
/**
|
||||
* Here we force the type checker to assume the decoded JSON has the correct
|
||||
* structure, rather than validating it. This is safe because it's not dynamic.
|
||||
*
|
||||
* @var non-empty-array<non-empty-string, TDialectData> $data
|
||||
*/
|
||||
$data = Filesystem::readJsonFileHash(__DIR__ . '/../../resources/gherkin-languages.json');
|
||||
$this->dialects = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $language
|
||||
*
|
||||
* @throws NoSuchLanguageException
|
||||
*/
|
||||
public function getDialect(string $language): GherkinDialect
|
||||
{
|
||||
if (!isset($this->dialects[$language])) {
|
||||
throw new NoSuchLanguageException($language);
|
||||
}
|
||||
|
||||
return new GherkinDialect($language, $this->dialects[$language]);
|
||||
}
|
||||
|
||||
public function getDefaultDialect(): GherkinDialect
|
||||
{
|
||||
return $this->getDialect('en');
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Dialect;
|
||||
|
||||
use Behat\Gherkin\Exception\NoSuchLanguageException;
|
||||
|
||||
interface DialectProviderInterface
|
||||
{
|
||||
/**
|
||||
* @param non-empty-string $language
|
||||
*
|
||||
* @throws NoSuchLanguageException when the language is not supported
|
||||
*/
|
||||
public function getDialect(string $language): GherkinDialect;
|
||||
|
||||
public function getDefaultDialect(): GherkinDialect;
|
||||
}
|
||||
159
vendor/behat/gherkin/src/Dialect/GherkinDialect.php
vendored
159
vendor/behat/gherkin/src/Dialect/GherkinDialect.php
vendored
@ -1,159 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Dialect;
|
||||
|
||||
/**
|
||||
* @phpstan-type TDialectData array{
|
||||
* feature: non-empty-list<non-empty-string>,
|
||||
* background: non-empty-list<non-empty-string>,
|
||||
* scenario: non-empty-list<non-empty-string>,
|
||||
* scenarioOutline: non-empty-list<non-empty-string>,
|
||||
* examples: non-empty-list<non-empty-string>,
|
||||
* rule: non-empty-list<non-empty-string>,
|
||||
* given: non-empty-list<non-empty-string>,
|
||||
* when: non-empty-list<non-empty-string>,
|
||||
* then: non-empty-list<non-empty-string>,
|
||||
* and: non-empty-list<non-empty-string>,
|
||||
* but: non-empty-list<non-empty-string>,
|
||||
* }
|
||||
*/
|
||||
final class GherkinDialect
|
||||
{
|
||||
/**
|
||||
* @var non-empty-list<non-empty-string>|null
|
||||
*/
|
||||
private ?array $stepKeywordsCache = null;
|
||||
|
||||
/**
|
||||
* @phpstan-param TDialectData $dialect
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $language,
|
||||
private readonly array $dialect,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getLanguage(): string
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getFeatureKeywords(): array
|
||||
{
|
||||
return $this->dialect['feature'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getBackgroundKeywords(): array
|
||||
{
|
||||
return $this->dialect['background'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getScenarioKeywords(): array
|
||||
{
|
||||
return $this->dialect['scenario'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getScenarioOutlineKeywords(): array
|
||||
{
|
||||
return $this->dialect['scenarioOutline'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getRuleKeywords(): array
|
||||
{
|
||||
return $this->dialect['rule'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getGivenKeywords(): array
|
||||
{
|
||||
return $this->dialect['given'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getWhenKeywords(): array
|
||||
{
|
||||
return $this->dialect['when'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getThenKeywords(): array
|
||||
{
|
||||
return $this->dialect['then'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getAndKeywords(): array
|
||||
{
|
||||
return $this->dialect['and'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getButKeywords(): array
|
||||
{
|
||||
return $this->dialect['but'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getStepKeywords(): array
|
||||
{
|
||||
if ($this->stepKeywordsCache !== null) {
|
||||
return $this->stepKeywordsCache;
|
||||
}
|
||||
|
||||
$stepKeywords = [
|
||||
...$this->getGivenKeywords(),
|
||||
...$this->getWhenKeywords(),
|
||||
...$this->getThenKeywords(),
|
||||
...$this->getAndKeywords(),
|
||||
...$this->getButKeywords(),
|
||||
];
|
||||
|
||||
// Sort longer keywords before shorter keywords being their prefix
|
||||
rsort($stepKeywords);
|
||||
|
||||
return $this->stepKeywordsCache = $stepKeywords;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
public function getExamplesKeywords(): array
|
||||
{
|
||||
return $this->dialect['examples'];
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Dialect;
|
||||
|
||||
use Behat\Gherkin\Exception\NoSuchLanguageException;
|
||||
use Behat\Gherkin\Keywords\ArrayKeywords;
|
||||
use Behat\Gherkin\Keywords\KeywordsInterface;
|
||||
|
||||
/**
|
||||
* Adapter for the legacy keywords interface.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class KeywordsDialectProvider implements DialectProviderInterface
|
||||
{
|
||||
private readonly string $defaultLanguage;
|
||||
|
||||
public function __construct(
|
||||
private readonly KeywordsInterface $keywords,
|
||||
) {
|
||||
// Assume a default dialect of `en` as the KeywordsInterface does not allow reading its language but returns the current data
|
||||
$this->defaultLanguage = $this->keywords instanceof ArrayKeywords ? $this->keywords->getLanguage() : 'en';
|
||||
}
|
||||
|
||||
public function getDialect(string $language): GherkinDialect
|
||||
{
|
||||
// The legacy keywords interface doesn't support detecting whether changing the language worked or no.
|
||||
$this->keywords->setLanguage($language);
|
||||
|
||||
if ($this->keywords instanceof ArrayKeywords && $this->keywords->getLanguage() !== $language) {
|
||||
throw new NoSuchLanguageException($language);
|
||||
}
|
||||
|
||||
return $this->buildDialect($language);
|
||||
}
|
||||
|
||||
public function getDefaultDialect(): GherkinDialect
|
||||
{
|
||||
$this->keywords->setLanguage($this->defaultLanguage);
|
||||
|
||||
return $this->buildDialect($this->defaultLanguage);
|
||||
}
|
||||
|
||||
private function buildDialect(string $language): GherkinDialect
|
||||
{
|
||||
return new GherkinDialect($language, [
|
||||
'feature' => self::parseKeywords($this->keywords->getFeatureKeywords()),
|
||||
'background' => self::parseKeywords($this->keywords->getBackgroundKeywords()),
|
||||
'scenario' => self::parseKeywords($this->keywords->getScenarioKeywords()),
|
||||
'scenarioOutline' => self::parseKeywords($this->keywords->getOutlineKeywords()),
|
||||
'examples' => self::parseKeywords($this->keywords->getExamplesKeywords()),
|
||||
'rule' => ['Rule'], // Hardcoded value as our old keywords interface doesn't support rules.
|
||||
'given' => self::parseStepKeywords($this->keywords->getGivenKeywords()),
|
||||
'when' => self::parseStepKeywords($this->keywords->getWhenKeywords()),
|
||||
'then' => self::parseStepKeywords($this->keywords->getThenKeywords()),
|
||||
'and' => self::parseStepKeywords($this->keywords->getAndKeywords()),
|
||||
'but' => self::parseStepKeywords($this->keywords->getButKeywords()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
private static function parseKeywords(string $keywordString): array
|
||||
{
|
||||
$keywords = array_values(array_filter(explode('|', $keywordString)));
|
||||
|
||||
if ($keywords === []) {
|
||||
throw new \LogicException('A keyword string must contain at least one keyword.');
|
||||
}
|
||||
|
||||
return $keywords;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<non-empty-string>
|
||||
*/
|
||||
private static function parseStepKeywords(string $keywordString): array
|
||||
{
|
||||
$legacyKeywords = explode('|', $keywordString);
|
||||
$keywords = [];
|
||||
|
||||
foreach ($legacyKeywords as $legacyKeyword) {
|
||||
if (\strlen($legacyKeyword) >= 2 && str_ends_with($legacyKeyword, '<')) {
|
||||
$keyword = substr($legacyKeyword, 0, -1);
|
||||
\assert($keyword !== ''); // phpstan is not smart enough to detect that the length check above guarantees this invariant
|
||||
$keywords[] = $keyword;
|
||||
} else {
|
||||
$keywords[] = $legacyKeyword . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return $keywords;
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Cache exception.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class CacheException extends RuntimeException implements Exception
|
||||
{
|
||||
}
|
||||
15
vendor/behat/gherkin/src/Exception/Exception.php
vendored
15
vendor/behat/gherkin/src/Exception/Exception.php
vendored
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
interface Exception
|
||||
{
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class FilesystemException extends RuntimeException implements Exception
|
||||
{
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
class InvalidTagContentException extends ParserException
|
||||
{
|
||||
public function __construct(string $tag, ?string $file)
|
||||
{
|
||||
parent::__construct(
|
||||
sprintf(
|
||||
'Tags cannot include whitespace, found "%s"%s',
|
||||
$tag,
|
||||
is_string($file)
|
||||
? "in file {$file}"
|
||||
: ''
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class LexerException extends RuntimeException implements Exception
|
||||
{
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
final class NoSuchLanguageException extends ParserException
|
||||
{
|
||||
public function __construct(public readonly string $language)
|
||||
{
|
||||
parent::__construct('Language not supported: ' . $language);
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class NodeException extends RuntimeException implements Exception
|
||||
{
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ParserException extends RuntimeException implements Exception
|
||||
{
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
use Behat\Gherkin\Node\NodeInterface;
|
||||
|
||||
class UnexpectedParserNodeException extends ParserException
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $expectation,
|
||||
public readonly string|NodeInterface $node,
|
||||
public readonly ?string $sourceFile,
|
||||
) {
|
||||
parent::__construct(
|
||||
sprintf(
|
||||
'Expected %s, but got %s%s',
|
||||
$expectation,
|
||||
is_string($node)
|
||||
? "text: \"{$node}\""
|
||||
: "{$node->getNodeType()} on line: {$node->getLine()}",
|
||||
$sourceFile ? " in file: {$sourceFile}" : ''
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Exception;
|
||||
|
||||
use Behat\Gherkin\Lexer;
|
||||
|
||||
/**
|
||||
* @phpstan-import-type TToken from Lexer
|
||||
*/
|
||||
class UnexpectedTaggedNodeException extends ParserException
|
||||
{
|
||||
/**
|
||||
* @phpstan-param TToken $taggedToken
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $taggedToken,
|
||||
public readonly ?string $sourceFile,
|
||||
) {
|
||||
$msg = match ($this->taggedToken['type']) {
|
||||
'EOS' => 'Unexpected end of file after tags',
|
||||
default => sprintf(
|
||||
'%s can not be tagged, but it is',
|
||||
$taggedToken['type'],
|
||||
),
|
||||
};
|
||||
|
||||
parent::__construct(
|
||||
sprintf(
|
||||
'%s on line: %d%s',
|
||||
$msg,
|
||||
$taggedToken['line'],
|
||||
$this->sourceFile ? " in file: {$this->sourceFile}" : '',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
184
vendor/behat/gherkin/src/Filesystem.php
vendored
184
vendor/behat/gherkin/src/Filesystem.php
vendored
@ -1,184 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin;
|
||||
|
||||
use Behat\Gherkin\Exception\FilesystemException;
|
||||
use ErrorException;
|
||||
use JsonException;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SplFileInfo;
|
||||
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Filesystem
|
||||
{
|
||||
/**
|
||||
* @throws FilesystemException
|
||||
*/
|
||||
public static function readFile(string $fileName): string
|
||||
{
|
||||
try {
|
||||
$result = self::callSafely(static fn () => file_get_contents($fileName));
|
||||
} catch (ErrorException $e) {
|
||||
throw new FilesystemException(
|
||||
sprintf('File "%s" cannot be read: %s', $fileName, $e->getMessage()),
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
assert($result !== false, 'file_get_contents() should not return false without emitting a PHP warning');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function writeFile(string $fileName, string $content): void
|
||||
{
|
||||
self::ensureDirectoryExists(dirname($fileName));
|
||||
try {
|
||||
$result = self::callSafely(static fn () => file_put_contents($fileName, $content));
|
||||
} catch (ErrorException $e) {
|
||||
throw new FilesystemException(
|
||||
sprintf('File "%s" cannot be written: %s', $fileName, $e->getMessage()),
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
assert($result !== false, 'file_put_contents() should not return false without emitting a PHP warning');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*
|
||||
* @throws JsonException|FilesystemException
|
||||
*/
|
||||
public static function readJsonFileArray(string $fileName): array
|
||||
{
|
||||
$result = json_decode(self::readFile($fileName), true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
assert(is_array($result), 'File must contain JSON with an array or object at its root');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*
|
||||
* @throws JsonException|FilesystemException
|
||||
*/
|
||||
public static function readJsonFileHash(string $fileName): array
|
||||
{
|
||||
$result = self::readJsonFileArray($fileName);
|
||||
assert(
|
||||
$result === array_filter($result, is_string(...), ARRAY_FILTER_USE_KEY),
|
||||
'File must contain a JSON object at its root',
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function findFilesRecursively(string $path, string $pattern): array
|
||||
{
|
||||
/**
|
||||
* @var iterable<string, SplFileInfo> $fileIterator
|
||||
*/
|
||||
$fileIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::CHILD_FIRST);
|
||||
|
||||
$found = [];
|
||||
foreach ($fileIterator as $file) {
|
||||
if ($file->isFile() && fnmatch($pattern, $file->getFilename())) {
|
||||
$found[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
public static function getLastModified(string $fileName): int
|
||||
{
|
||||
try {
|
||||
$result = self::callSafely(static fn () => filemtime($fileName));
|
||||
} catch (ErrorException $e) {
|
||||
throw new FilesystemException(
|
||||
sprintf('Last modification time of file "%s" cannot be found: %s', $fileName, $e->getMessage()),
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
assert($result !== false, 'filemtime() should not return false without emitting a PHP warning');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getRealPath(string $path): string
|
||||
{
|
||||
$result = realpath($path);
|
||||
|
||||
if ($result === false) {
|
||||
throw new FilesystemException("Cannot retrieve the real path of $path");
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function ensureDirectoryExists(string $path): void
|
||||
{
|
||||
if (is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = self::callSafely(static fn () => mkdir($path, 0777, true));
|
||||
|
||||
assert($result !== false, 'mkdir() should not return false without emitting a PHP warning');
|
||||
} catch (ErrorException $e) {
|
||||
// @codeCoverageIgnoreStart
|
||||
if (is_dir($path)) {
|
||||
// Some other concurrent process created the directory.
|
||||
return;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
throw new FilesystemException(
|
||||
sprintf('Path at "%s" cannot be created: %s', $path, $e->getMessage()),
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TResult
|
||||
*
|
||||
* @param (callable(): TResult) $callback
|
||||
*
|
||||
* @return TResult
|
||||
*
|
||||
* @throws ErrorException
|
||||
*/
|
||||
private static function callSafely(callable $callback): mixed
|
||||
{
|
||||
set_error_handler(
|
||||
static fn (int $severity, string $message, string $file, int $line) => throw new ErrorException($message, 0, $severity, $file, $line)
|
||||
);
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Abstract filter class.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
abstract class ComplexFilter implements ComplexFilterInterface
|
||||
{
|
||||
/**
|
||||
* Filters feature according to the filter.
|
||||
*
|
||||
* @return FeatureNode
|
||||
*/
|
||||
public function filterFeature(FeatureNode $feature)
|
||||
{
|
||||
$scenarios = $feature->getScenarios();
|
||||
$filteredScenarios = array_filter(
|
||||
$scenarios,
|
||||
fn (ScenarioInterface $scenario) => $this->isScenarioMatch($feature, $scenario)
|
||||
);
|
||||
|
||||
return $scenarios === $filteredScenarios ? $feature : $feature->withScenarios($filteredScenarios);
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Filter interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface ComplexFilterInterface extends FeatureFilterInterface
|
||||
{
|
||||
/**
|
||||
* Checks if scenario or outline matches specified filter.
|
||||
*
|
||||
* @param FeatureNode $feature Feature node instance
|
||||
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isScenarioMatch(FeatureNode $feature, ScenarioInterface $scenario);
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
|
||||
/**
|
||||
* Feature filter interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface FeatureFilterInterface
|
||||
{
|
||||
/**
|
||||
* Checks if Feature matches specified filter.
|
||||
*
|
||||
* @param FeatureNode $feature Feature instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFeatureMatch(FeatureNode $feature);
|
||||
|
||||
/**
|
||||
* Filters feature according to the filter and returns new one.
|
||||
*
|
||||
* @return FeatureNode
|
||||
*/
|
||||
public function filterFeature(FeatureNode $feature);
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Filter interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface FilterInterface extends FeatureFilterInterface
|
||||
{
|
||||
/**
|
||||
* Checks if scenario or outline matches specified filter.
|
||||
*
|
||||
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isScenarioMatch(ScenarioInterface $scenario);
|
||||
}
|
||||
107
vendor/behat/gherkin/src/Filter/LineFilter.php
vendored
107
vendor/behat/gherkin/src/Filter/LineFilter.php
vendored
@ -1,107 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\OutlineNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Filters scenarios by definition line number.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class LineFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $filterLine;
|
||||
|
||||
/**
|
||||
* Initializes filter.
|
||||
*
|
||||
* @param int|numeric-string $filterLine Line of the scenario to filter on
|
||||
*/
|
||||
public function __construct(int|string $filterLine)
|
||||
{
|
||||
$this->filterLine = (int) $filterLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Feature matches specified filter.
|
||||
*
|
||||
* @param FeatureNode $feature Feature instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFeatureMatch(FeatureNode $feature)
|
||||
{
|
||||
return $this->filterLine === $feature->getLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if scenario or outline matches specified filter.
|
||||
*
|
||||
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||
{
|
||||
if ($this->filterLine === $scenario->getLine()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||
return $this->filterLine === $scenario->getLine()
|
||||
|| in_array($this->filterLine, $scenario->getExampleTable()->getLines());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters feature according to the filter and returns new one.
|
||||
*
|
||||
* @return FeatureNode
|
||||
*/
|
||||
public function filterFeature(FeatureNode $feature)
|
||||
{
|
||||
$scenarios = [];
|
||||
foreach ($feature->getScenarios() as $scenario) {
|
||||
if (!$this->isScenarioMatch($scenario)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||
foreach ($scenario->getExampleTables() as $exampleTable) {
|
||||
$table = $exampleTable->getTable();
|
||||
$lines = array_keys($table);
|
||||
|
||||
if (in_array($this->filterLine, $lines)) {
|
||||
$filteredTable = [$lines[0] => $table[$lines[0]]];
|
||||
|
||||
if ($lines[0] !== $this->filterLine) {
|
||||
$filteredTable[$this->filterLine] = $table[$this->filterLine];
|
||||
}
|
||||
|
||||
$scenario = $scenario->withTables([$exampleTable->withTable($filteredTable)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scenarios[] = $scenario;
|
||||
}
|
||||
|
||||
return $feature->withScenarios($scenarios);
|
||||
}
|
||||
}
|
||||
125
vendor/behat/gherkin/src/Filter/LineRangeFilter.php
vendored
125
vendor/behat/gherkin/src/Filter/LineRangeFilter.php
vendored
@ -1,125 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\OutlineNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Filters scenarios by definition line number range.
|
||||
*
|
||||
* @author Fabian Kiss <headrevision@gmail.com>
|
||||
*/
|
||||
class LineRangeFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $filterMinLine;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $filterMaxLine;
|
||||
|
||||
/**
|
||||
* Initializes filter.
|
||||
*
|
||||
* @param int|numeric-string $filterMinLine Minimum line of a scenario to filter on
|
||||
* @param int|numeric-string|'*' $filterMaxLine Maximum line of a scenario to filter on
|
||||
*/
|
||||
public function __construct(int|string $filterMinLine, int|string $filterMaxLine)
|
||||
{
|
||||
$this->filterMinLine = (int) $filterMinLine;
|
||||
$this->filterMaxLine = $filterMaxLine === '*' ? PHP_INT_MAX : (int) $filterMaxLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Feature matches specified filter.
|
||||
*
|
||||
* @param FeatureNode $feature Feature instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFeatureMatch(FeatureNode $feature)
|
||||
{
|
||||
return $this->filterMinLine <= $feature->getLine()
|
||||
&& $this->filterMaxLine >= $feature->getLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if scenario or outline matches specified filter.
|
||||
*
|
||||
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||
{
|
||||
if ($this->filterMinLine <= $scenario->getLine() && $this->filterMaxLine >= $scenario->getLine()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||
foreach ($scenario->getExampleTable()->getLines() as $line) {
|
||||
if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters feature according to the filter.
|
||||
*
|
||||
* @return FeatureNode
|
||||
*/
|
||||
public function filterFeature(FeatureNode $feature)
|
||||
{
|
||||
$scenarios = [];
|
||||
foreach ($feature->getScenarios() as $scenario) {
|
||||
if (!$this->isScenarioMatch($scenario)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||
// first accumulate examples and then create scenario
|
||||
$exampleTableNodes = [];
|
||||
|
||||
foreach ($scenario->getExampleTables() as $exampleTable) {
|
||||
$table = $exampleTable->getTable();
|
||||
$lines = array_keys($table);
|
||||
|
||||
$filteredTable = [$lines[0] => $table[$lines[0]]];
|
||||
unset($table[$lines[0]]);
|
||||
|
||||
foreach ($table as $line => $row) {
|
||||
if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) {
|
||||
$filteredTable[$line] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($filteredTable) > 1) {
|
||||
$exampleTableNodes[] = $exampleTable->withTable($filteredTable);
|
||||
}
|
||||
}
|
||||
|
||||
$scenario = $scenario->withTables($exampleTableNodes);
|
||||
}
|
||||
|
||||
$scenarios[] = $scenario;
|
||||
}
|
||||
|
||||
return $feature->withScenarios($scenarios);
|
||||
}
|
||||
}
|
||||
87
vendor/behat/gherkin/src/Filter/NameFilter.php
vendored
87
vendor/behat/gherkin/src/Filter/NameFilter.php
vendored
@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\DescribableNodeInterface;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Filters scenarios by feature/scenario name.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class NameFilter extends SimpleFilter
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $filterString;
|
||||
|
||||
public function __construct(string $filterString)
|
||||
{
|
||||
$this->filterString = trim($filterString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Feature matches specified filter.
|
||||
*
|
||||
* @param FeatureNode $feature Feature instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFeatureMatch(FeatureNode $feature)
|
||||
{
|
||||
if ($feature->getTitle() === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->filterString[0] === '/') {
|
||||
return (bool) preg_match($this->filterString, $feature->getTitle());
|
||||
}
|
||||
|
||||
return str_contains($feature->getTitle(), $this->filterString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if scenario or outline matches specified filter.
|
||||
*
|
||||
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||
{
|
||||
// Historically (and in legacy GherkinCompatibilityMode), multiline scenario text was all part of the title.
|
||||
// In new GherkinCompatibilityMode the text will be split into a single-line title & multiline description.
|
||||
// For BC, this filter should continue to match on the complete multiline text value.
|
||||
$textParts = array_filter([
|
||||
$scenario->getTitle(),
|
||||
$scenario instanceof DescribableNodeInterface ? $scenario->getDescription() : null,
|
||||
]);
|
||||
|
||||
if ($textParts === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$textToMatch = implode("\n", $textParts);
|
||||
|
||||
if ($this->filterString[0] === '/' && preg_match($this->filterString, $textToMatch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_contains($textToMatch, $this->filterString)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Filters features by their narrative using regular expression.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class NarrativeFilter extends SimpleFilter
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $regex,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isFeatureMatch(FeatureNode $feature)
|
||||
{
|
||||
return (bool) preg_match($this->regex, $feature->getDescription() ?? '');
|
||||
}
|
||||
|
||||
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||
{
|
||||
// This filter does not apply to scenarios.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
71
vendor/behat/gherkin/src/Filter/PathsFilter.php
vendored
71
vendor/behat/gherkin/src/Filter/PathsFilter.php
vendored
@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Exception\FilesystemException;
|
||||
use Behat\Gherkin\Filesystem;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Filters features by their paths.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class PathsFilter extends SimpleFilter
|
||||
{
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $filterPaths = [];
|
||||
|
||||
/**
|
||||
* Initializes filter.
|
||||
*
|
||||
* @param array<array-key, string> $paths List of approved paths
|
||||
*/
|
||||
public function __construct(array $paths)
|
||||
{
|
||||
foreach ($paths as $path) {
|
||||
try {
|
||||
$realpath = Filesystem::getRealPath($path);
|
||||
} catch (FilesystemException) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->filterPaths[] = rtrim($realpath, DIRECTORY_SEPARATOR)
|
||||
. (is_dir($realpath) ? DIRECTORY_SEPARATOR : '');
|
||||
}
|
||||
}
|
||||
|
||||
public function isFeatureMatch(FeatureNode $feature)
|
||||
{
|
||||
if (($filePath = $feature->getFile()) === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$realFeatureFilePath = Filesystem::getRealPath($filePath);
|
||||
|
||||
foreach ($this->filterPaths as $filterPath) {
|
||||
if (str_starts_with($realFeatureFilePath, $filterPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||
{
|
||||
// This filter does not apply to scenarios.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
59
vendor/behat/gherkin/src/Filter/RoleFilter.php
vendored
59
vendor/behat/gherkin/src/Filter/RoleFilter.php
vendored
@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Filters features by their actors role.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class RoleFilter extends SimpleFilter
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $pattern;
|
||||
|
||||
/**
|
||||
* Initializes filter.
|
||||
*
|
||||
* @param string $role Approved role wildcard
|
||||
*/
|
||||
public function __construct(string $role)
|
||||
{
|
||||
$this->pattern = sprintf(
|
||||
'/as an? %s[$\n]/i',
|
||||
strtr(
|
||||
preg_quote($role, '/'),
|
||||
[
|
||||
'\*' => '.*',
|
||||
'\?' => '.',
|
||||
'\[' => '[',
|
||||
'\]' => ']',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function isFeatureMatch(FeatureNode $feature)
|
||||
{
|
||||
return (bool) preg_match($this->pattern, $feature->getDescription() ?? '');
|
||||
}
|
||||
|
||||
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||
{
|
||||
// This filter does not apply to scenarios.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
40
vendor/behat/gherkin/src/Filter/SimpleFilter.php
vendored
40
vendor/behat/gherkin/src/Filter/SimpleFilter.php
vendored
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
|
||||
/**
|
||||
* Abstract filter class.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
abstract class SimpleFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Filters feature according to the filter.
|
||||
*
|
||||
* @return FeatureNode
|
||||
*/
|
||||
public function filterFeature(FeatureNode $feature)
|
||||
{
|
||||
if ($this->isFeatureMatch($feature)) {
|
||||
return $feature;
|
||||
}
|
||||
|
||||
return $feature->withScenarios(
|
||||
array_filter(
|
||||
$feature->getScenarios(),
|
||||
$this->isScenarioMatch(...)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
184
vendor/behat/gherkin/src/Filter/TagFilter.php
vendored
184
vendor/behat/gherkin/src/Filter/TagFilter.php
vendored
@ -1,184 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Filter;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\OutlineNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
|
||||
/**
|
||||
* Filters scenarios by feature/scenario tag.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class TagFilter extends ComplexFilter
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $filterString;
|
||||
|
||||
public function __construct(string $filterString)
|
||||
{
|
||||
$filterString = trim($filterString);
|
||||
$fixedFilterString = $this->fixLegacyFilterStringWithoutPrefixes($filterString);
|
||||
// @todo trigger a deprecation here $filterString !== $fixedFilterString
|
||||
$this->filterString = $fixedFilterString;
|
||||
|
||||
if (preg_match('/\s/u', $this->filterString)) {
|
||||
trigger_error(
|
||||
'Tags with whitespace are deprecated and may be removed in a future version',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix tag expressions where the filter string does not include the `@` prefixes.
|
||||
*
|
||||
* e.g. `new TagFilter('wip&&~slow')` rather than `new TagFilter('@wip&&~@slow')`. These were historically
|
||||
* supported, although not officially, and have been reinstated to solve a BC issue. This syntax will be deprecated
|
||||
* and removed in future.
|
||||
*/
|
||||
private function fixLegacyFilterStringWithoutPrefixes(string $filterString): string
|
||||
{
|
||||
if ($filterString === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$allParts = [];
|
||||
foreach (explode('&&', $filterString) as $andTags) {
|
||||
$allParts[] = implode(
|
||||
',',
|
||||
array_map(
|
||||
fn (string $tag): string => match (true) {
|
||||
// Valid - tag filter contains the `@` prefix
|
||||
str_starts_with($tag, '@'),
|
||||
str_starts_with($tag, '~@') => $tag,
|
||||
// Invalid / legacy cases - insert the missing `@` prefix in the right place
|
||||
str_starts_with($tag, '~') => '~@' . substr($tag, 1),
|
||||
default => '@' . $tag,
|
||||
},
|
||||
explode(',', $andTags),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return implode('&&', $allParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters feature according to the filter.
|
||||
*
|
||||
* @return FeatureNode
|
||||
*/
|
||||
public function filterFeature(FeatureNode $feature)
|
||||
{
|
||||
$scenarios = [];
|
||||
foreach ($feature->getScenarios() as $scenario) {
|
||||
if (!$this->isScenarioMatch($feature, $scenario)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||
$exampleTables = [];
|
||||
|
||||
foreach ($scenario->getExampleTables() as $exampleTable) {
|
||||
if ($this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags(), $exampleTable->getTags()))) {
|
||||
$exampleTables[] = $exampleTable;
|
||||
}
|
||||
}
|
||||
|
||||
$scenario = $scenario->withTables($exampleTables);
|
||||
}
|
||||
|
||||
$scenarios[] = $scenario;
|
||||
}
|
||||
|
||||
return $feature->withScenarios($scenarios);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Feature matches specified filter.
|
||||
*
|
||||
* @param FeatureNode $feature Feature instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFeatureMatch(FeatureNode $feature)
|
||||
{
|
||||
return $this->isTagsMatchCondition($feature->getTags());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if scenario or outline matches specified filter.
|
||||
*
|
||||
* @param FeatureNode $feature Feature node instance
|
||||
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isScenarioMatch(FeatureNode $feature, ScenarioInterface $scenario)
|
||||
{
|
||||
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||
foreach ($scenario->getExampleTables() as $example) {
|
||||
if ($this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags(), $example->getTags()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that node matches condition.
|
||||
*
|
||||
* @param array<array-key, string> $tags
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isTagsMatchCondition(array $tags)
|
||||
{
|
||||
if ($this->filterString === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the file was parsed in legacy mode, the `@` prefix will have been removed from the individual tags on the
|
||||
// parsed node. The tags in the filter expression still have their @ so we add the prefix back here if required.
|
||||
// This can be removed once legacy parsing mode is removed.
|
||||
$tags = array_map(
|
||||
static fn (string $tag) => str_starts_with($tag, '@') ? $tag : '@' . $tag,
|
||||
$tags
|
||||
);
|
||||
|
||||
foreach (explode('&&', $this->filterString) as $andTags) {
|
||||
$satisfiesComma = false;
|
||||
|
||||
foreach (explode(',', $andTags) as $tag) {
|
||||
if ($tag[0] === '~') {
|
||||
$tag = mb_substr($tag, 1, mb_strlen($tag, 'utf8') - 1, 'utf8');
|
||||
$satisfiesComma = !in_array($tag, $tags, true) || $satisfiesComma;
|
||||
} else {
|
||||
$satisfiesComma = in_array($tag, $tags, true) || $satisfiesComma;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$satisfiesComma) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
160
vendor/behat/gherkin/src/Gherkin.php
vendored
160
vendor/behat/gherkin/src/Gherkin.php
vendored
@ -1,160 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin;
|
||||
|
||||
use Behat\Gherkin\Filter\FeatureFilterInterface;
|
||||
use Behat\Gherkin\Filter\LineFilter;
|
||||
use Behat\Gherkin\Filter\LineRangeFilter;
|
||||
use Behat\Gherkin\Loader\FileLoaderInterface;
|
||||
use Behat\Gherkin\Loader\LoaderInterface;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
|
||||
/**
|
||||
* Gherkin manager.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class Gherkin
|
||||
{
|
||||
/**
|
||||
* @deprecated this constant will not be updated for releases after 4.8.0 and will be removed in the next major.
|
||||
* You can use composer's runtime API to get the behat version if you need it. Note that composer's versions will
|
||||
* not always be simple numeric values.
|
||||
*/
|
||||
public const VERSION = '4.8.0';
|
||||
|
||||
/**
|
||||
* @var list<LoaderInterface<*>>
|
||||
*/
|
||||
protected $loaders = [];
|
||||
/**
|
||||
* @var list<FeatureFilterInterface>
|
||||
*/
|
||||
protected $filters = [];
|
||||
|
||||
/**
|
||||
* Adds loader to manager.
|
||||
*
|
||||
* @param LoaderInterface<*> $loader Feature loader
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addLoader(LoaderInterface $loader)
|
||||
{
|
||||
$this->loaders[] = $loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds filter to manager.
|
||||
*
|
||||
* @param FeatureFilterInterface $filter Feature filter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addFilter(FeatureFilterInterface $filter)
|
||||
{
|
||||
$this->filters[] = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filters to the parser.
|
||||
*
|
||||
* @param array<array-key, FeatureFilterInterface> $filters
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setFilters(array $filters)
|
||||
{
|
||||
$this->filters = [];
|
||||
array_map($this->addFilter(...), $filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets base features path.
|
||||
*
|
||||
* @param string $path Loaders base path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setBasePath(string $path)
|
||||
{
|
||||
foreach ($this->loaders as $loader) {
|
||||
if ($loader instanceof FileLoaderInterface) {
|
||||
$loader->setBasePath($path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads & filters resource with added loaders.
|
||||
*
|
||||
* @param mixed $resource Resource to load
|
||||
* @param array<array-key, FeatureFilterInterface> $filters Additional filters
|
||||
*
|
||||
* @return list<FeatureNode>
|
||||
*/
|
||||
public function load($resource, array $filters = [])
|
||||
{
|
||||
$filters = array_merge($this->filters, $filters);
|
||||
|
||||
$matches = [];
|
||||
if (is_scalar($resource) || $resource instanceof \Stringable) {
|
||||
if (preg_match('/^(.*):(\d+)-(\d+|\*)$/', (string) $resource, $matches)) {
|
||||
$resource = $matches[1];
|
||||
$filters[] = new LineRangeFilter($matches[2], $matches[3]);
|
||||
} elseif (preg_match('/^(.*):(\d+)$/', (string) $resource, $matches)) {
|
||||
$resource = $matches[1];
|
||||
$filters[] = new LineFilter($matches[2]);
|
||||
}
|
||||
}
|
||||
|
||||
$loader = $this->resolveLoader($resource);
|
||||
|
||||
if ($loader === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$features = [];
|
||||
foreach ($loader->load($resource) as $feature) {
|
||||
foreach ($filters as $filter) {
|
||||
$feature = $filter->filterFeature($feature);
|
||||
|
||||
if (!$feature->hasScenarios() && !$filter->isFeatureMatch($feature)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$features[] = $feature;
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves loader by resource.
|
||||
*
|
||||
* @template TResourceType
|
||||
*
|
||||
* @param TResourceType $resource Resource to load
|
||||
*
|
||||
* @return LoaderInterface<TResourceType>|null
|
||||
*/
|
||||
public function resolveLoader(mixed $resource)
|
||||
{
|
||||
foreach ($this->loaders as $loader) {
|
||||
if ($loader->supports($resource)) {
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,124 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin;
|
||||
|
||||
enum GherkinCompatibilityMode: string
|
||||
{
|
||||
case LEGACY = 'legacy';
|
||||
|
||||
/**
|
||||
* Note: The gherkin-32 parsing mode is not yet complete, and further behaviour changes are expected.
|
||||
*
|
||||
* @see https://github.com/Behat/Gherkin/issues?q=is%3Aissue%20state%3Aopen%20label%3Acucumber-parity
|
||||
*/
|
||||
case GHERKIN_32 = 'gherkin-32';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function shouldRemoveStepKeywordSpace(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::LEGACY => true,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function shouldRemoveDescriptionPadding(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::LEGACY => true,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function allowAllNodeDescriptions(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::LEGACY => false,
|
||||
default => true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function shouldUseNewTableCellParsing(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::LEGACY => false,
|
||||
default => true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function shouldUnespaceDocStringDelimiters(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::LEGACY => false,
|
||||
default => true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function shouldIgnoreInvalidLanguage(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::LEGACY => true,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function allowWhitespaceInLanguageTag(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::LEGACY => false,
|
||||
default => true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function shouldRemoveTagPrefixChar(): bool
|
||||
{
|
||||
// Note: When this is removed we can also remove the code in TagFilter that handles tags with no leading @
|
||||
return match ($this) {
|
||||
self::LEGACY => true,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function shouldThrowOnWhitespaceInTag(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
// Note, although we don't throw we have triggered an E_USER_DEPRECATED in Parser::guardTags since v4.9.0
|
||||
self::LEGACY => false,
|
||||
default => true,
|
||||
};
|
||||
}
|
||||
}
|
||||
226
vendor/behat/gherkin/src/Keywords/ArrayKeywords.php
vendored
226
vendor/behat/gherkin/src/Keywords/ArrayKeywords.php
vendored
@ -1,226 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Keywords;
|
||||
|
||||
/**
|
||||
* Array initializable keywords holder.
|
||||
*
|
||||
* ```
|
||||
* $keywords = new Behat\Gherkin\Keywords\ArrayKeywords(array(
|
||||
* 'en' => array(
|
||||
* 'feature' => 'Feature',
|
||||
* 'background' => 'Background',
|
||||
* 'scenario' => 'Scenario',
|
||||
* 'scenario_outline' => 'Scenario Outline|Scenario Template',
|
||||
* 'examples' => 'Examples|Scenarios',
|
||||
* 'given' => 'Given',
|
||||
* 'when' => 'When',
|
||||
* 'then' => 'Then',
|
||||
* 'and' => 'And',
|
||||
* 'but' => 'But'
|
||||
* ),
|
||||
* 'ru' => array(
|
||||
* 'feature' => 'Функционал',
|
||||
* 'background' => 'Предыстория',
|
||||
* 'scenario' => 'Сценарий',
|
||||
* 'scenario_outline' => 'Структура сценария',
|
||||
* 'examples' => 'Примеры',
|
||||
* 'given' => 'Допустим',
|
||||
* 'when' => 'Если',
|
||||
* 'then' => 'То',
|
||||
* 'and' => 'И',
|
||||
* 'but' => 'Но'
|
||||
* )
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @phpstan-type TKeywordsArray array{
|
||||
* feature: string,
|
||||
* background: string,
|
||||
* scenario: string,
|
||||
* scenario_outline: string,
|
||||
* examples: string,
|
||||
* given: string,
|
||||
* when: string,
|
||||
* then: string,
|
||||
* and: string,
|
||||
* but: string,
|
||||
* }
|
||||
* @phpstan-type TMultiLanguageKeywords array<string, TKeywordsArray>
|
||||
*/
|
||||
class ArrayKeywords implements KeywordsInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private array $keywordString = [];
|
||||
private string $language = 'en';
|
||||
|
||||
/**
|
||||
* Initializes holder with keywords.
|
||||
*
|
||||
* @phpstan-param TMultiLanguageKeywords $keywords Keywords array
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $keywords,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets keywords holder language.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setLanguage(string $language)
|
||||
{
|
||||
if (!isset($this->keywords[$language])) {
|
||||
$this->language = 'en';
|
||||
} else {
|
||||
$this->language = $language;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getLanguage(): string
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Feature keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFeatureKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['feature'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Background keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBackgroundKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['background'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Scenario keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getScenarioKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['scenario'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Scenario Outline keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutlineKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['scenario_outline'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Examples keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExamplesKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['examples'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Given keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGivenKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['given'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns When keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWhenKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['when'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Then keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getThenKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['then'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns And keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAndKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['and'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns But keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getButKeywords()
|
||||
{
|
||||
return $this->keywords[$this->language]['but'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all step keywords (Given, When, Then, And, But).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStepKeywords()
|
||||
{
|
||||
if (!isset($this->keywordString[$this->language])) {
|
||||
$keywords = array_merge(
|
||||
explode('|', $this->getGivenKeywords()),
|
||||
explode('|', $this->getWhenKeywords()),
|
||||
explode('|', $this->getThenKeywords()),
|
||||
explode('|', $this->getAndKeywords()),
|
||||
explode('|', $this->getButKeywords())
|
||||
);
|
||||
|
||||
usort($keywords, function ($keyword1, $keyword2) {
|
||||
return mb_strlen($keyword2, 'utf8') - mb_strlen($keyword1, 'utf8');
|
||||
});
|
||||
|
||||
$this->keywordString[$this->language] = implode('|', $keywords);
|
||||
}
|
||||
|
||||
return $this->keywordString[$this->language];
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Keywords;
|
||||
|
||||
/**
|
||||
* File initializable keywords holder.
|
||||
*
|
||||
* $keywords = new Behat\Gherkin\Keywords\CachedArrayKeywords($file);
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class CachedArrayKeywords extends ArrayKeywords
|
||||
{
|
||||
public static function withDefaultKeywords(): self
|
||||
{
|
||||
return new self(__DIR__ . '/../../i18n.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes holder with file.
|
||||
*
|
||||
* @param string $file Cached array path
|
||||
*/
|
||||
public function __construct(string $file)
|
||||
{
|
||||
// @phpstan-ignore argument.type
|
||||
parent::__construct(require $file);
|
||||
}
|
||||
}
|
||||
@ -1,107 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Keywords;
|
||||
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Cucumber-translations reader.
|
||||
*
|
||||
* $keywords = new Behat\Gherkin\Keywords\CucumberKeywords($i18nYmlPath);
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class CucumberKeywords extends ArrayKeywords
|
||||
{
|
||||
/**
|
||||
* Initializes holder with yaml string OR file.
|
||||
*
|
||||
* @param string $yaml Yaml string or file path
|
||||
*/
|
||||
public function __construct(string $yaml)
|
||||
{
|
||||
if (!str_contains($yaml, "\n") && is_file($yaml)) {
|
||||
$content = Yaml::parseFile($yaml);
|
||||
} else {
|
||||
$content = Yaml::parse($yaml);
|
||||
}
|
||||
|
||||
if (!is_array($content)) {
|
||||
throw new ParseException(sprintf('Root element must be an array, but %s found.', get_debug_type($content)));
|
||||
}
|
||||
|
||||
// @phpstan-ignore argument.type
|
||||
parent::__construct($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Feature keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGivenKeywords()
|
||||
{
|
||||
return $this->prepareStepString(parent::getGivenKeywords());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns When keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWhenKeywords()
|
||||
{
|
||||
return $this->prepareStepString(parent::getWhenKeywords());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Then keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getThenKeywords()
|
||||
{
|
||||
return $this->prepareStepString(parent::getThenKeywords());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns And keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAndKeywords()
|
||||
{
|
||||
return $this->prepareStepString(parent::getAndKeywords());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns But keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getButKeywords()
|
||||
{
|
||||
return $this->prepareStepString(parent::getButKeywords());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim *| from the beginning of the list.
|
||||
*/
|
||||
private function prepareStepString(string $keywordsString): string
|
||||
{
|
||||
if (str_starts_with($keywordsString, '*|')) {
|
||||
$keywordsString = mb_substr($keywordsString, 2, mb_strlen($keywordsString, 'utf8') - 2, 'utf8');
|
||||
}
|
||||
|
||||
return $keywordsString;
|
||||
}
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Keywords;
|
||||
|
||||
use Behat\Gherkin\Dialect\DialectProviderInterface;
|
||||
use Behat\Gherkin\Dialect\GherkinDialect;
|
||||
|
||||
/**
|
||||
* An adapter around a DialectProviderInterface to be able to use it with the KeywordsDumper.
|
||||
*
|
||||
* TODO add support for dumping an example feature for a dialect directly instead.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DialectKeywords implements KeywordsInterface
|
||||
{
|
||||
private GherkinDialect $currentDialect;
|
||||
|
||||
public function __construct(
|
||||
private readonly DialectProviderInterface $dialectProvider,
|
||||
) {
|
||||
$this->currentDialect = $this->dialectProvider->getDefaultDialect();
|
||||
}
|
||||
|
||||
public function setLanguage(string $language): void
|
||||
{
|
||||
if ($language === '') {
|
||||
throw new \InvalidArgumentException('Language cannot be empty');
|
||||
}
|
||||
|
||||
$this->currentDialect = $this->dialectProvider->getDialect($language);
|
||||
}
|
||||
|
||||
public function getFeatureKeywords(): string
|
||||
{
|
||||
return $this->getKeywordString($this->currentDialect->getFeatureKeywords());
|
||||
}
|
||||
|
||||
public function getBackgroundKeywords(): string
|
||||
{
|
||||
return $this->getKeywordString($this->currentDialect->getBackgroundKeywords());
|
||||
}
|
||||
|
||||
public function getScenarioKeywords(): string
|
||||
{
|
||||
return $this->getKeywordString($this->currentDialect->getScenarioKeywords());
|
||||
}
|
||||
|
||||
public function getOutlineKeywords(): string
|
||||
{
|
||||
return $this->getKeywordString($this->currentDialect->getScenarioOutlineKeywords());
|
||||
}
|
||||
|
||||
public function getExamplesKeywords(): string
|
||||
{
|
||||
return $this->getKeywordString($this->currentDialect->getExamplesKeywords());
|
||||
}
|
||||
|
||||
public function getGivenKeywords(): string
|
||||
{
|
||||
return $this->getStepKeywordString($this->currentDialect->getGivenKeywords());
|
||||
}
|
||||
|
||||
public function getWhenKeywords(): string
|
||||
{
|
||||
return $this->getStepKeywordString($this->currentDialect->getWhenKeywords());
|
||||
}
|
||||
|
||||
public function getThenKeywords(): string
|
||||
{
|
||||
return $this->getStepKeywordString($this->currentDialect->getThenKeywords());
|
||||
}
|
||||
|
||||
public function getAndKeywords(): string
|
||||
{
|
||||
return $this->getStepKeywordString($this->currentDialect->getAndKeywords());
|
||||
}
|
||||
|
||||
public function getButKeywords(): string
|
||||
{
|
||||
return $this->getStepKeywordString($this->currentDialect->getButKeywords());
|
||||
}
|
||||
|
||||
public function getStepKeywords(): string
|
||||
{
|
||||
return $this->getStepKeywordString($this->currentDialect->getStepKeywords());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $keywords
|
||||
*/
|
||||
private function getKeywordString(array $keywords): string
|
||||
{
|
||||
return implode('|', $keywords);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $keywords
|
||||
*/
|
||||
private function getStepKeywordString(array $keywords): string
|
||||
{
|
||||
$legacyKeywords = [];
|
||||
foreach ($keywords as $keyword) {
|
||||
if (str_ends_with($keyword, ' ')) {
|
||||
$legacyKeywords[] = substr($keyword, 0, -1);
|
||||
} else {
|
||||
$legacyKeywords[] = $keyword . '<';
|
||||
}
|
||||
}
|
||||
|
||||
return implode('|', $legacyKeywords);
|
||||
}
|
||||
}
|
||||
367
vendor/behat/gherkin/src/Keywords/KeywordsDumper.php
vendored
367
vendor/behat/gherkin/src/Keywords/KeywordsDumper.php
vendored
@ -1,367 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Keywords;
|
||||
|
||||
/**
|
||||
* Gherkin keywords dumper.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class KeywordsDumper
|
||||
{
|
||||
/**
|
||||
* @var callable(list<string>, bool): string
|
||||
*/
|
||||
private $keywordsDumper;
|
||||
|
||||
public function __construct(
|
||||
private readonly KeywordsInterface $keywords,
|
||||
) {
|
||||
$this->keywordsDumper = [$this, 'dumpKeywords'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets keywords mapper function.
|
||||
*
|
||||
* Callable should accept 2 arguments (array $keywords and bool $isShort)
|
||||
*
|
||||
* @param callable(list<string>, bool): string $mapper Mapper function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setKeywordsDumperFunction(callable $mapper)
|
||||
{
|
||||
$this->keywordsDumper = $mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults keywords dumper.
|
||||
*
|
||||
* @param list<string> $keywords Keywords list
|
||||
* @param bool $isShort Is short version
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function dumpKeywords(array $keywords, bool $isShort)
|
||||
{
|
||||
if ($isShort) {
|
||||
return count($keywords) > 1
|
||||
? '(' . implode('|', $keywords) . ')'
|
||||
: $keywords[0];
|
||||
}
|
||||
|
||||
return $keywords[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps keyworded feature into string.
|
||||
*
|
||||
* @param string $language Keywords language
|
||||
* @param bool $short Dump short version
|
||||
*
|
||||
* @return string|array String for short version and array of features for extended
|
||||
*
|
||||
* @phpstan-return ($short is true ? string : list<string>)
|
||||
*/
|
||||
public function dump(string $language, bool $short = true, bool $excludeAsterisk = false)
|
||||
{
|
||||
$this->keywords->setLanguage($language);
|
||||
$languageComment = '';
|
||||
if ($language !== 'en') {
|
||||
$languageComment = "# language: $language\n";
|
||||
}
|
||||
|
||||
$keywords = explode('|', $this->keywords->getFeatureKeywords());
|
||||
|
||||
if ($short) {
|
||||
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||
|
||||
return trim($languageComment . $this->dumpFeature($keywords, $short, $excludeAsterisk));
|
||||
}
|
||||
|
||||
$features = [];
|
||||
foreach ($keywords as $keyword) {
|
||||
$keyword = call_user_func($this->keywordsDumper, [$keyword], $short);
|
||||
$features[] = trim($languageComment . $this->dumpFeature($keyword, $short, $excludeAsterisk));
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps feature example.
|
||||
*
|
||||
* @param string $keyword Item keyword
|
||||
* @param bool $short Dump short version?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function dumpFeature(string $keyword, bool $short = true, bool $excludeAsterisk = false)
|
||||
{
|
||||
$dump = <<<GHERKIN
|
||||
{$keyword}: Internal operations
|
||||
In order to stay secret
|
||||
As a secret organization
|
||||
We need to be able to erase past agents' memory
|
||||
|
||||
|
||||
GHERKIN;
|
||||
|
||||
// Background
|
||||
$keywords = explode('|', $this->keywords->getBackgroundKeywords());
|
||||
if ($short) {
|
||||
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||
$dump .= $this->dumpBackground($keywords, $short, $excludeAsterisk);
|
||||
} else {
|
||||
$keyword = call_user_func($this->keywordsDumper, [$keywords[0]], $short);
|
||||
$dump .= $this->dumpBackground($keyword, $short, $excludeAsterisk);
|
||||
}
|
||||
|
||||
// Scenario
|
||||
$keywords = explode('|', $this->keywords->getScenarioKeywords());
|
||||
if ($short) {
|
||||
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||
$dump .= $this->dumpScenario($keywords, $short, $excludeAsterisk);
|
||||
} else {
|
||||
foreach ($keywords as $keyword) {
|
||||
$keyword = call_user_func($this->keywordsDumper, [$keyword], $short);
|
||||
$dump .= $this->dumpScenario($keyword, $short, $excludeAsterisk);
|
||||
}
|
||||
}
|
||||
|
||||
// Outline
|
||||
$keywords = explode('|', $this->keywords->getOutlineKeywords());
|
||||
if ($short) {
|
||||
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||
$dump .= $this->dumpOutline($keywords, $short, $excludeAsterisk);
|
||||
} else {
|
||||
foreach ($keywords as $keyword) {
|
||||
$keyword = call_user_func($this->keywordsDumper, [$keyword], $short);
|
||||
$dump .= $this->dumpOutline($keyword, $short, $excludeAsterisk);
|
||||
}
|
||||
}
|
||||
|
||||
return $dump;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps background example.
|
||||
*
|
||||
* @param string $keyword Item keyword
|
||||
* @param bool $short Dump short version?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function dumpBackground(string $keyword, bool $short = true, bool $excludeAsterisk = false)
|
||||
{
|
||||
$dump = <<<GHERKIN
|
||||
{$keyword}:
|
||||
|
||||
GHERKIN;
|
||||
|
||||
// Given
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getGivenKeywords(),
|
||||
'there is agent A',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
// And
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getAndKeywords(),
|
||||
'there is agent B',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
return $dump . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps scenario example.
|
||||
*
|
||||
* @param string $keyword Item keyword
|
||||
* @param bool $short Dump short version?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function dumpScenario(string $keyword, bool $short = true, bool $excludeAsterisk = false)
|
||||
{
|
||||
$dump = <<<GHERKIN
|
||||
{$keyword}: Erasing agent memory
|
||||
|
||||
GHERKIN;
|
||||
|
||||
// Given
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getGivenKeywords(),
|
||||
'there is agent J',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
// And
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getAndKeywords(),
|
||||
'there is agent K',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
// When
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getWhenKeywords(),
|
||||
'I erase agent K\'s memory',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
// Then
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getThenKeywords(),
|
||||
'there should be agent J',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
// But
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getButKeywords(),
|
||||
'there should not be agent K',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
return $dump . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps outline example.
|
||||
*
|
||||
* @param string $keyword Item keyword
|
||||
* @param bool $short Dump short version?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function dumpOutline(string $keyword, bool $short = true, bool $excludeAsterisk = false)
|
||||
{
|
||||
$dump = <<<GHERKIN
|
||||
{$keyword}: Erasing other agents' memory
|
||||
|
||||
GHERKIN;
|
||||
|
||||
// Given
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getGivenKeywords(),
|
||||
'there is agent <agent1>',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
// And
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getAndKeywords(),
|
||||
'there is agent <agent2>',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
// When
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getWhenKeywords(),
|
||||
'I erase agent <agent2>\'s memory',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
// Then
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getThenKeywords(),
|
||||
'there should be agent <agent1>',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
// But
|
||||
$dump .= $this->dumpStep(
|
||||
$this->keywords->getButKeywords(),
|
||||
'there should not be agent <agent2>',
|
||||
$short,
|
||||
$excludeAsterisk
|
||||
);
|
||||
|
||||
$keywords = explode('|', $this->keywords->getExamplesKeywords());
|
||||
if ($short) {
|
||||
$keyword = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||
} else {
|
||||
$keyword = call_user_func($this->keywordsDumper, [$keywords[0]], $short);
|
||||
}
|
||||
|
||||
$dump .= <<<GHERKIN
|
||||
|
||||
{$keyword}:
|
||||
| agent1 | agent2 |
|
||||
| D | M |
|
||||
|
||||
GHERKIN;
|
||||
|
||||
return $dump . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps step example.
|
||||
*
|
||||
* @param string $keywords Item keyword
|
||||
* @param string $text Step text
|
||||
* @param bool $short Dump short version?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function dumpStep(string $keywords, string $text, bool $short = true, bool $excludeAsterisk = false)
|
||||
{
|
||||
$dump = '';
|
||||
|
||||
$keywords = explode('|', $keywords);
|
||||
if ($short) {
|
||||
$keywords = array_map(
|
||||
function ($keyword) {
|
||||
return str_replace('<', '', $keyword);
|
||||
},
|
||||
$keywords
|
||||
);
|
||||
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||
$dump .= <<<GHERKIN
|
||||
{$keywords} {$text}
|
||||
|
||||
GHERKIN;
|
||||
} else {
|
||||
foreach ($keywords as $keyword) {
|
||||
if ($excludeAsterisk && $keyword === '*') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$indent = ' ';
|
||||
if (str_contains($keyword, '<')) {
|
||||
$keyword = mb_substr($keyword, 0, -1, 'utf8');
|
||||
$indent = '';
|
||||
}
|
||||
$keyword = call_user_func($this->keywordsDumper, [$keyword], $short);
|
||||
$dump .= <<<GHERKIN
|
||||
{$keyword}{$indent}{$text}
|
||||
|
||||
GHERKIN;
|
||||
}
|
||||
}
|
||||
|
||||
return $dump;
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Keywords;
|
||||
|
||||
/**
|
||||
* Keywords holder interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface KeywordsInterface
|
||||
{
|
||||
/**
|
||||
* Sets keywords holder language.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setLanguage(string $language);
|
||||
|
||||
/**
|
||||
* Returns Feature keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFeatureKeywords();
|
||||
|
||||
/**
|
||||
* Returns Background keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBackgroundKeywords();
|
||||
|
||||
/**
|
||||
* Returns Scenario keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getScenarioKeywords();
|
||||
|
||||
/**
|
||||
* Returns Scenario Outline keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutlineKeywords();
|
||||
|
||||
/**
|
||||
* Returns Examples keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExamplesKeywords();
|
||||
|
||||
/**
|
||||
* Returns Given keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGivenKeywords();
|
||||
|
||||
/**
|
||||
* Returns When keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWhenKeywords();
|
||||
|
||||
/**
|
||||
* Returns Then keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getThenKeywords();
|
||||
|
||||
/**
|
||||
* Returns And keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAndKeywords();
|
||||
|
||||
/**
|
||||
* Returns But keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getButKeywords();
|
||||
|
||||
/**
|
||||
* Returns all step keywords (separated by "|").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStepKeywords();
|
||||
}
|
||||
990
vendor/behat/gherkin/src/Lexer.php
vendored
990
vendor/behat/gherkin/src/Lexer.php
vendored
@ -1,990 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin;
|
||||
|
||||
use Behat\Gherkin\Dialect\DialectProviderInterface;
|
||||
use Behat\Gherkin\Dialect\GherkinDialect;
|
||||
use Behat\Gherkin\Dialect\KeywordsDialectProvider;
|
||||
use Behat\Gherkin\Exception\LexerException;
|
||||
use Behat\Gherkin\Exception\NoSuchLanguageException;
|
||||
use Behat\Gherkin\Keywords\KeywordsInterface;
|
||||
use LogicException;
|
||||
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* Gherkin lexer.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*
|
||||
* @phpstan-type TStepKeyword 'Given'|'When'|'Then'|'And'|'But'
|
||||
* @phpstan-type TTitleKeyword 'Feature'|'Background'|'Scenario'|'Outline'|'Examples'
|
||||
* @phpstan-type TTokenType 'Text'|'Comment'|'EOS'|'Newline'|'PyStringOp'|'TableRow'|'Tag'|'Language'|'Step'|TTitleKeyword
|
||||
* @phpstan-type TToken TStringValueToken|TNullValueToken|TTitleToken|TStepToken|TTagToken|TTableRowToken
|
||||
* @phpstan-type TStringValueToken array{type: TTokenType, value: string, line: int, deferred: bool}
|
||||
* @phpstan-type TNullValueToken array{type: TTokenType, value: null, line: int, deferred: bool}
|
||||
* @phpstan-type TTitleToken array{type: TTitleKeyword, value: null|non-empty-string, line: int, deferred: bool, keyword: string, indent: int}
|
||||
* @phpstan-type TStepToken array{type: 'Step', value: string, line: int, deferred: bool, keyword_type: string, text: string}
|
||||
* @phpstan-type TTagToken array{type: 'Tag', value: null, line: int, deferred: bool, tags: list<string>}
|
||||
* @phpstan-type TTableRowToken array{type: 'TableRow', value: null, line: int, deferred: bool, columns: list<string>}
|
||||
* @phpstan-type TDocStringSeparator '"""'|'```'
|
||||
*/
|
||||
class Lexer
|
||||
{
|
||||
/**
|
||||
* Splits a string around | char, only if it's not preceded by an odd number of \.
|
||||
*
|
||||
* @see https://github.com/cucumber/gherkin/blob/679a87e21263699c15ea635159c6cda60f64af3b/php/src/StringGherkinLine.php#L14
|
||||
*/
|
||||
private const CELL_PATTERN = '/(?<!\\\\)(?:\\\\{2})*\K\\|/u';
|
||||
|
||||
private readonly DialectProviderInterface $dialectProvider;
|
||||
private GherkinDialect $currentDialect;
|
||||
private GherkinCompatibilityMode $compatibilityMode = GherkinCompatibilityMode::LEGACY;
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $lines;
|
||||
private int $linesCount;
|
||||
private string $line;
|
||||
private ?string $trimmedLine = null;
|
||||
private int $lineNumber;
|
||||
private bool $eos;
|
||||
/**
|
||||
* A cache of keyword types associated with each keyword.
|
||||
*
|
||||
* @phpstan-var array<string, non-empty-list<TStepKeyword>>|null
|
||||
*/
|
||||
private ?array $stepKeywordTypesCache = null;
|
||||
/**
|
||||
* @phpstan-var list<TToken>
|
||||
*/
|
||||
private array $deferredObjects = [];
|
||||
private int $deferredObjectsCount = 0;
|
||||
/**
|
||||
* @phpstan-var TToken|null
|
||||
*/
|
||||
private ?array $stashedToken = null;
|
||||
private bool $inPyString = false;
|
||||
private int $pyStringSwallow = 0;
|
||||
private bool $allowLanguageTag = true;
|
||||
private bool $allowFeature = true;
|
||||
private bool $allowMultilineArguments = false;
|
||||
private bool $allowExamples = false;
|
||||
private bool $allowSteps = false;
|
||||
/**
|
||||
* @phpstan-var TDocStringSeparator|null
|
||||
*/
|
||||
private ?string $pyStringDelimiter = null;
|
||||
|
||||
public function __construct(
|
||||
DialectProviderInterface|KeywordsInterface $dialectProvider,
|
||||
) {
|
||||
if ($dialectProvider instanceof KeywordsInterface) {
|
||||
// TODO trigger deprecation
|
||||
$dialectProvider = new KeywordsDialectProvider($dialectProvider);
|
||||
}
|
||||
|
||||
$this->dialectProvider = $dialectProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setCompatibilityMode(GherkinCompatibilityMode $compatibilityMode): void
|
||||
{
|
||||
$this->compatibilityMode = $compatibilityMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets lexer input.
|
||||
*
|
||||
* @param string $input Input string
|
||||
* @param string $language Language name
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws LexerException
|
||||
*/
|
||||
public function analyse(string $input, string $language = 'en')
|
||||
{
|
||||
// try to detect unsupported encoding
|
||||
if (mb_detect_encoding($input, 'UTF-8', true) !== 'UTF-8') {
|
||||
throw new LexerException('Feature file is not in UTF8 encoding');
|
||||
}
|
||||
|
||||
$input = strtr($input, ["\r\n" => "\n", "\r" => "\n"]);
|
||||
|
||||
$this->lines = explode("\n", $input);
|
||||
$this->linesCount = count($this->lines);
|
||||
$this->line = $this->lines[0];
|
||||
$this->lineNumber = 1;
|
||||
$this->trimmedLine = null;
|
||||
$this->eos = false;
|
||||
|
||||
$this->deferredObjects = [];
|
||||
$this->deferredObjectsCount = 0;
|
||||
$this->stashedToken = null;
|
||||
$this->inPyString = false;
|
||||
$this->pyStringSwallow = 0;
|
||||
|
||||
$this->allowLanguageTag = true;
|
||||
$this->allowFeature = true;
|
||||
$this->allowMultilineArguments = false;
|
||||
$this->allowSteps = false;
|
||||
$this->allowExamples = false;
|
||||
|
||||
if (\func_num_args() > 1) {
|
||||
// @codeCoverageIgnoreStart
|
||||
\assert($language !== '');
|
||||
// TODO trigger deprecation (the Parser does not use this code path)
|
||||
$this->setLanguage($language);
|
||||
// @codeCoverageIgnoreEnd
|
||||
} else {
|
||||
$this->currentDialect = $this->dialectProvider->getDefaultDialect();
|
||||
$this->stepKeywordTypesCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $language
|
||||
*/
|
||||
private function setLanguage(string $language): void
|
||||
{
|
||||
if (($this->stashedToken !== null) || ($this->deferredObjects !== [])) {
|
||||
// @codeCoverageIgnoreStart
|
||||
// It is not possible to trigger this condition using the public interface of this class.
|
||||
// It may be possible if the end-user has extended the Lexer with custom functionality.
|
||||
throw new LogicException(
|
||||
<<<'STRING'
|
||||
Cannot set gherkin language due to unexpected Lexer state.
|
||||
|
||||
Please open an issue at https://github.com/Behat/Gherkin with a copy of the current
|
||||
feature file. If you are using a Lexer or Parser class that extends the ones provided
|
||||
in behat/gherkin, please also provide details of these.
|
||||
STRING,
|
||||
);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
try {
|
||||
$this->currentDialect = $this->dialectProvider->getDialect($language);
|
||||
} catch (NoSuchLanguageException $e) {
|
||||
if (!$this->compatibilityMode->shouldIgnoreInvalidLanguage()) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
$this->stepKeywordTypesCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current lexer language.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->currentDialect->getLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next token or previously stashed one.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @phpstan-return TToken
|
||||
*/
|
||||
public function getAdvancedToken()
|
||||
{
|
||||
return $this->getStashedToken() ?? $this->getNextToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defers token.
|
||||
*
|
||||
* @phpstan-param TToken $token Token to defer
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deferToken(array $token)
|
||||
{
|
||||
$token['deferred'] = true;
|
||||
$this->deferredObjects[] = $token;
|
||||
++$this->deferredObjectsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicts the upcoming token without passing over it.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @phpstan-return TToken
|
||||
*/
|
||||
public function predictToken()
|
||||
{
|
||||
return $this->stashedToken ??= $this->getNextToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips over the currently-predicted token, if any.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function skipPredictedToken()
|
||||
{
|
||||
$this->stashedToken = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a token with specified parameters.
|
||||
*
|
||||
* @template T of TTokenType
|
||||
*
|
||||
* @param string|null $value Token value
|
||||
*
|
||||
* @phpstan-param T $type Token type
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @phpstan-return ($value is non-empty-string ? array{type: T, value: non-empty-string, line: int, deferred: bool} : array{type: T, value: null, line: int, deferred: bool})
|
||||
*/
|
||||
public function takeToken(string $type, ?string $value = null)
|
||||
{
|
||||
return [
|
||||
'type' => $type,
|
||||
'line' => $this->lineNumber,
|
||||
'value' => $value ?: null,
|
||||
'deferred' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes line from input & increments line counter.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function consumeLine()
|
||||
{
|
||||
++$this->lineNumber;
|
||||
|
||||
if (($this->lineNumber - 1) === $this->linesCount) {
|
||||
$this->eos = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line = $this->lines[$this->lineNumber - 1];
|
||||
$this->trimmedLine = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes first part of line from input without incrementing the line number.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function consumeLineUntil(int $trimmedOffset)
|
||||
{
|
||||
$this->line = mb_substr(ltrim($this->line), $trimmedOffset, null, 'utf-8');
|
||||
$this->trimmedLine = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns trimmed version of line.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getTrimmedLine()
|
||||
{
|
||||
return $this->trimmedLine ??= trim($this->line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stashed token or null if there isn't one.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TToken|null
|
||||
*/
|
||||
protected function getStashedToken()
|
||||
{
|
||||
$stashedToken = $this->stashedToken;
|
||||
$this->stashedToken = null;
|
||||
|
||||
return $stashedToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns deferred token or null if there isn't one.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TToken|null
|
||||
*/
|
||||
protected function getDeferredToken()
|
||||
{
|
||||
if (!$this->deferredObjectsCount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
--$this->deferredObjectsCount;
|
||||
|
||||
return array_shift($this->deferredObjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next token from input.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @phpstan-return TToken
|
||||
*/
|
||||
protected function getNextToken()
|
||||
{
|
||||
return $this->getDeferredToken()
|
||||
?? $this->scanEOS()
|
||||
?? $this->scanLanguage()
|
||||
?? $this->scanComment()
|
||||
?? $this->scanPyStringOp()
|
||||
?? $this->scanPyStringContent()
|
||||
?? $this->scanStep()
|
||||
?? $this->scanScenario()
|
||||
?? $this->scanBackground()
|
||||
?? $this->scanOutline()
|
||||
?? $this->scanExamples()
|
||||
?? $this->scanFeature()
|
||||
?? $this->scanTags()
|
||||
?? $this->scanTableRow()
|
||||
?? $this->scanNewline()
|
||||
?? $this->scanText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans for token with specified regex.
|
||||
*
|
||||
* @param string $regex Regular expression
|
||||
*
|
||||
* @phpstan-param TTokenType $type Expected token type
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TStringValueToken|null
|
||||
*/
|
||||
protected function scanInput(string $regex, string $type)
|
||||
{
|
||||
if (!preg_match($regex, $this->line, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
assert($matches[1] !== '');
|
||||
|
||||
$token = $this->takeToken($type, $matches[1]);
|
||||
$this->consumeLine();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans for token with specified keywords.
|
||||
*
|
||||
* @param string $keywords Keywords (separated by "|")
|
||||
*
|
||||
* @phpstan-param TTitleKeyword $type Expected token type
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TTitleToken|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected function scanInputForKeywords(string $keywords, string $type)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if (!preg_match('/^(\s*)(' . $keywords . '):\s*(.*)/u', $this->line, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = $this->takeToken($type, $matches[3]);
|
||||
$token['keyword'] = $matches[2];
|
||||
$token['indent'] = mb_strlen($matches[1], 'utf8');
|
||||
|
||||
$this->consumeLine();
|
||||
|
||||
// turn off language searching and feature detection
|
||||
if ($type === 'Feature') {
|
||||
$this->allowFeature = false;
|
||||
$this->allowLanguageTag = false;
|
||||
}
|
||||
|
||||
// turn off PyString and Table searching
|
||||
if ($type === 'Feature' || $type === 'Scenario' || $type === 'Outline') {
|
||||
$this->allowMultilineArguments = false;
|
||||
} elseif ($type === 'Examples') {
|
||||
$this->allowMultilineArguments = true;
|
||||
}
|
||||
|
||||
// turn on steps searching
|
||||
if ($type === 'Scenario' || $type === 'Background' || $type === 'Outline') {
|
||||
$this->allowSteps = true;
|
||||
}
|
||||
|
||||
return $token;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $keywords
|
||||
*
|
||||
* @phpstan-param TTitleKeyword $type
|
||||
*
|
||||
* @phpstan-return TTitleToken|null
|
||||
*/
|
||||
private function scanTitleLine(array $keywords, string $type): ?array
|
||||
{
|
||||
$trimmedLine = $this->getTrimmedLine();
|
||||
|
||||
foreach ($keywords as $keyword) {
|
||||
if (str_starts_with($trimmedLine, $keyword . ':')) {
|
||||
$title = trim(mb_substr($trimmedLine, mb_strlen($keyword) + 1));
|
||||
|
||||
$token = $this->takeToken($type, $title);
|
||||
$token['keyword'] = $keyword;
|
||||
$token['indent'] = mb_strlen($this->line, 'utf8') - mb_strlen(ltrim($this->line), 'utf8');
|
||||
|
||||
$this->consumeLine();
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans EOS from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TNullValueToken|null
|
||||
*/
|
||||
protected function scanEOS()
|
||||
{
|
||||
if (!$this->eos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->takeToken('EOS');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a regex matching the keywords for the provided type.
|
||||
*
|
||||
* @phpstan-param 'Step'|TTitleKeyword|TStepKeyword $type Keyword type
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected function getKeywords(string $type)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
$keywords = match ($type) {
|
||||
'Feature' => $this->currentDialect->getFeatureKeywords(),
|
||||
'Background' => $this->currentDialect->getBackgroundKeywords(),
|
||||
'Scenario' => $this->currentDialect->getScenarioKeywords(),
|
||||
'Outline' => $this->currentDialect->getScenarioOutlineKeywords(),
|
||||
'Examples' => $this->currentDialect->getExamplesKeywords(),
|
||||
'Step' => $this->currentDialect->getStepKeywords(),
|
||||
'Given' => $this->currentDialect->getGivenKeywords(),
|
||||
'When' => $this->currentDialect->getWhenKeywords(),
|
||||
'Then' => $this->currentDialect->getThenKeywords(),
|
||||
'And' => $this->currentDialect->getAndKeywords(),
|
||||
'But' => $this->currentDialect->getButKeywords(),
|
||||
default => throw new \InvalidArgumentException(sprintf('Unknown keyword type "%s"', $type)),
|
||||
};
|
||||
|
||||
$keywordsRegex = implode('|', array_map(fn ($keyword) => preg_quote($keyword, '/'), $keywords));
|
||||
|
||||
if ($type === 'Step') {
|
||||
$keywordsRegex = '(?:' . $keywordsRegex . ')\s*';
|
||||
}
|
||||
|
||||
return $keywordsRegex;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Feature from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TTitleToken|null
|
||||
*/
|
||||
protected function scanFeature()
|
||||
{
|
||||
if (!$this->allowFeature) {
|
||||
// The Feature: tag is only allowed once in a file, later in the file it may be part of a description node
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = $this->scanTitleLine($this->currentDialect->getFeatureKeywords(), 'Feature');
|
||||
|
||||
if ($token === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->allowFeature = false;
|
||||
$this->allowLanguageTag = false;
|
||||
$this->allowMultilineArguments = false;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Background from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TTitleToken|null
|
||||
*/
|
||||
protected function scanBackground()
|
||||
{
|
||||
$token = $this->scanTitleLine($this->currentDialect->getBackgroundKeywords(), 'Background');
|
||||
|
||||
if ($token === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->allowSteps = true;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Scenario from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TTitleToken|null
|
||||
*/
|
||||
protected function scanScenario()
|
||||
{
|
||||
$token = $this->scanTitleLine($this->currentDialect->getScenarioKeywords(), 'Scenario');
|
||||
|
||||
if ($token === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->allowMultilineArguments = false;
|
||||
$this->allowSteps = true;
|
||||
$this->allowExamples = true;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Scenario Outline from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TTitleToken|null
|
||||
*/
|
||||
protected function scanOutline()
|
||||
{
|
||||
$token = $this->scanTitleLine($this->currentDialect->getScenarioOutlineKeywords(), 'Outline');
|
||||
|
||||
if ($token === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->allowMultilineArguments = false;
|
||||
$this->allowSteps = true;
|
||||
$this->allowExamples = true;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Scenario Outline Examples from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TTitleToken|null
|
||||
*/
|
||||
protected function scanExamples()
|
||||
{
|
||||
if (!$this->allowExamples) {
|
||||
return null;
|
||||
}
|
||||
$token = $this->scanTitleLine($this->currentDialect->getExamplesKeywords(), 'Examples');
|
||||
|
||||
if ($token === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->allowMultilineArguments = true;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Step from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TStepToken|null
|
||||
*/
|
||||
protected function scanStep()
|
||||
{
|
||||
if (!$this->allowSteps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$trimmedLine = $this->getTrimmedLine();
|
||||
$matchedKeyword = null;
|
||||
|
||||
foreach ($this->currentDialect->getStepKeywords() as $keyword) {
|
||||
if (str_starts_with($trimmedLine, $keyword)) {
|
||||
$matchedKeyword = $keyword;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($matchedKeyword === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$text = ltrim(mb_substr($trimmedLine, mb_strlen($matchedKeyword)));
|
||||
|
||||
$nodeKeyword = $this->compatibilityMode->shouldRemoveStepKeywordSpace() ? trim($matchedKeyword) : $matchedKeyword;
|
||||
assert($nodeKeyword !== '');
|
||||
|
||||
$token = $this->takeToken('Step', $nodeKeyword);
|
||||
$token['keyword_type'] = $this->getStepKeywordType($matchedKeyword);
|
||||
$token['text'] = $text;
|
||||
|
||||
$this->consumeLine();
|
||||
$this->allowMultilineArguments = true;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans PyString from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TNullValueToken|null
|
||||
*/
|
||||
protected function scanPyStringOp()
|
||||
{
|
||||
if (!$this->allowMultilineArguments) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!preg_match('/^\s*(?<delimiter>"""|```)/u', $this->line, $matches, PREG_OFFSET_CAPTURE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
['delimiter' => [0 => $delimiter, 1 => $indent]] = $matches;
|
||||
|
||||
if ($this->inPyString) {
|
||||
if ($this->pyStringDelimiter !== $delimiter) {
|
||||
return null;
|
||||
}
|
||||
$this->pyStringDelimiter = null;
|
||||
} else {
|
||||
$this->pyStringDelimiter = $delimiter;
|
||||
}
|
||||
|
||||
$this->inPyString = !$this->inPyString;
|
||||
$token = $this->takeToken('PyStringOp');
|
||||
$this->pyStringSwallow = $indent;
|
||||
|
||||
$this->consumeLine();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans PyString content.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TStringValueToken|null
|
||||
*/
|
||||
protected function scanPyStringContent()
|
||||
{
|
||||
if (!$this->inPyString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = $this->scanText();
|
||||
// swallow trailing spaces
|
||||
$value = (string) preg_replace('/^\s{0,' . $this->pyStringSwallow . '}/u', '', $token['value'] ?? '');
|
||||
|
||||
if ($this->compatibilityMode->shouldUnespaceDocStringDelimiters()) {
|
||||
\assert($this->pyStringDelimiter !== null);
|
||||
$escapedDelimiter = match ($this->pyStringDelimiter) {
|
||||
'"""' => '\\"\\"\\"',
|
||||
'```' => '\\`\\`\\`',
|
||||
};
|
||||
$value = str_replace($escapedDelimiter, $this->pyStringDelimiter, $value);
|
||||
}
|
||||
|
||||
$token['value'] = $value;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Table Row from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TTableRowToken|null
|
||||
*/
|
||||
protected function scanTableRow()
|
||||
{
|
||||
if (!$this->allowMultilineArguments) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$line = $this->getTrimmedLine();
|
||||
if (!str_starts_with($line, '|')) {
|
||||
// Strictly speaking, a table row only has to begin with a pipe - content to the right
|
||||
// of the final pipe will be ignored after we split the cells.
|
||||
return null;
|
||||
}
|
||||
|
||||
$rawColumns = preg_split(self::CELL_PATTERN, $line);
|
||||
assert($rawColumns !== false);
|
||||
|
||||
// Safely remove elements before the first and last separators
|
||||
array_shift($rawColumns);
|
||||
array_pop($rawColumns);
|
||||
|
||||
$token = $this->takeToken('TableRow');
|
||||
if ($this->compatibilityMode->shouldUseNewTableCellParsing()) {
|
||||
$columns = array_map($this->parseTableCell(...), $rawColumns);
|
||||
} else {
|
||||
$columns = array_map(static fn ($column) => trim(str_replace(['\\|', '\\\\'], ['|', '\\'], $column)), $rawColumns);
|
||||
}
|
||||
$token['columns'] = $columns;
|
||||
|
||||
$this->consumeLine();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
private function parseTableCell(string $cell): string
|
||||
{
|
||||
$trimmedCell = preg_replace('/^[ \\t\\n\\x0B\\f\\r\\x85\\xA0]++|[ \\t\\n\\x0B\\f\\r\\x85\\xA0]++$/u', '', $cell);
|
||||
\assert($trimmedCell !== null);
|
||||
|
||||
$value = preg_replace_callback('/\\\\./', function (array $matches) {
|
||||
return match ($matches[0]) {
|
||||
'\\n' => "\n",
|
||||
'\\\\' => '\\',
|
||||
'\\|' => '|',
|
||||
default => $matches[0],
|
||||
};
|
||||
}, $trimmedCell);
|
||||
|
||||
assert($value !== null);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Tags from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TTagToken|null
|
||||
*/
|
||||
protected function scanTags()
|
||||
{
|
||||
$line = $this->getTrimmedLine();
|
||||
|
||||
if ($line === '' || !str_starts_with($line, '@')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/^(?<line>.*)\s+#.*$/', $line, $matches)) {
|
||||
['line' => $line] = $matches;
|
||||
$this->consumeLineUntil(mb_strlen($line, 'utf-8'));
|
||||
} else {
|
||||
$this->consumeLine();
|
||||
}
|
||||
|
||||
$token = $this->takeToken('Tag');
|
||||
|
||||
if ($this->compatibilityMode->shouldRemoveTagPrefixChar()) {
|
||||
// Legacy behaviour
|
||||
$tags = explode('@', mb_substr($line, 1, mb_strlen($line, 'utf8') - 1, 'utf8'));
|
||||
$tags = array_map(trim(...), $tags);
|
||||
$token['tags'] = $tags;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
$tags = preg_split('/(?=@)/u', $line);
|
||||
assert($tags !== false);
|
||||
// Remove the empty content before the first tag prefix
|
||||
array_shift($tags);
|
||||
|
||||
// Note: checking for whitespace in tags is done in the Parser to fit with existing logic
|
||||
$token['tags'] = array_map(trim(...), $tags);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Language specifier from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TStringValueToken|null
|
||||
*/
|
||||
protected function scanLanguage()
|
||||
{
|
||||
if (!$this->allowLanguageTag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->inPyString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!str_starts_with(ltrim($this->line), '#')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pattern = $this->compatibilityMode->allowWhitespaceInLanguageTag()
|
||||
? '/^\s*#\s*language\s*:\s*([\w_\-]+)\s*$/u'
|
||||
: '/^\s*#\s*language:\s*([\w_\-]+)\s*$/';
|
||||
|
||||
$token = $this->scanInput($pattern, 'Language');
|
||||
|
||||
if ($token) {
|
||||
\assert(\is_string($token['value']));
|
||||
\assert($token['value'] !== ''); // the regex can only match a non-empty value.
|
||||
$this->allowLanguageTag = false;
|
||||
$this->setLanguage($token['value']);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Comment from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TStringValueToken|null
|
||||
*/
|
||||
protected function scanComment()
|
||||
{
|
||||
if ($this->inPyString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$line = $this->getTrimmedLine();
|
||||
if (!str_starts_with($line, '#')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = $this->takeToken('Comment', $line);
|
||||
$this->consumeLine();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans Newline from input & returns it if found.
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TNullValueToken|null
|
||||
*/
|
||||
protected function scanNewline()
|
||||
{
|
||||
if ($this->getTrimmedLine() !== '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = $this->takeToken('Newline');
|
||||
$this->consumeLine();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans text from input & returns it if found.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @phpstan-return TStringValueToken|TNullValueToken
|
||||
*/
|
||||
protected function scanText()
|
||||
{
|
||||
$token = $this->takeToken('Text', $this->line);
|
||||
$this->consumeLine();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns step type keyword (Given, When, Then, etc.).
|
||||
*
|
||||
* @param string $native Step keyword in provided language
|
||||
*
|
||||
* @phpstan-return TStepKeyword
|
||||
*/
|
||||
private function getStepKeywordType(string $native): string
|
||||
{
|
||||
if ($this->stepKeywordTypesCache === null) {
|
||||
$this->stepKeywordTypesCache = [];
|
||||
$this->addStepKeywordTypes($this->currentDialect->getGivenKeywords(), 'Given');
|
||||
$this->addStepKeywordTypes($this->currentDialect->getWhenKeywords(), 'When');
|
||||
$this->addStepKeywordTypes($this->currentDialect->getThenKeywords(), 'Then');
|
||||
$this->addStepKeywordTypes($this->currentDialect->getAndKeywords(), 'And');
|
||||
$this->addStepKeywordTypes($this->currentDialect->getButKeywords(), 'But');
|
||||
}
|
||||
|
||||
if (!isset($this->stepKeywordTypesCache[$native])) { // should not happen when the native keyword belongs to the dialect
|
||||
return 'Given'; // cucumber/gherkin has an UNKNOWN type, but we don't have it.
|
||||
}
|
||||
|
||||
if (\count($this->stepKeywordTypesCache[$native]) === 1) {
|
||||
return $this->stepKeywordTypesCache[$native][0];
|
||||
}
|
||||
|
||||
// Consider ambiguous keywords as AND keywords so that they are normalized to the previous step type.
|
||||
// This happens in English for the `* ` keyword for instance.
|
||||
// cucumber/gherkin returns that as an UNKNOWN type, but we don't have it.
|
||||
return 'And';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $keywords
|
||||
*
|
||||
* @phpstan-param TStepKeyword $type
|
||||
*/
|
||||
private function addStepKeywordTypes(array $keywords, string $type): void
|
||||
{
|
||||
foreach ($keywords as $keyword) {
|
||||
$this->stepKeywordTypesCache[$keyword][] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Loader;
|
||||
|
||||
use Behat\Gherkin\Filesystem;
|
||||
|
||||
/**
|
||||
* Abstract filesystem loader.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @template TResourceType
|
||||
*
|
||||
* @extends AbstractLoader<TResourceType>
|
||||
*
|
||||
* @implements FileLoaderInterface<TResourceType>
|
||||
*/
|
||||
abstract class AbstractFileLoader extends AbstractLoader implements FileLoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $basePath;
|
||||
|
||||
/**
|
||||
* Sets base features path.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setBasePath(string $path)
|
||||
{
|
||||
$this->basePath = Filesystem::getRealPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds relative path for provided absolute (relative to base features path).
|
||||
*
|
||||
* @param string $path Absolute path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function findRelativePath(string $path)
|
||||
{
|
||||
if ($this->basePath !== null) {
|
||||
return strtr($path, [$this->basePath . DIRECTORY_SEPARATOR => '']);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds absolute path for provided relative (relative to base features path).
|
||||
*
|
||||
* @param string $path Relative path
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
protected function findAbsolutePath(string $path)
|
||||
{
|
||||
if (file_exists($path)) {
|
||||
return realpath($path);
|
||||
}
|
||||
|
||||
if ($this->basePath === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file_exists($this->basePath . DIRECTORY_SEPARATOR . $path)) {
|
||||
return realpath($this->basePath . DIRECTORY_SEPARATOR . $path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
final protected function getAbsolutePath(string $path): string
|
||||
{
|
||||
$resolvedPath = $this->findAbsolutePath($path);
|
||||
if ($resolvedPath === false) {
|
||||
throw new \RuntimeException("Unable to locate absolute path of \"$path\"");
|
||||
}
|
||||
|
||||
return $resolvedPath;
|
||||
}
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Loader;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
|
||||
/**
|
||||
* @template TResourceType
|
||||
*
|
||||
* @implements LoaderInterface<TResourceType>
|
||||
*/
|
||||
abstract class AbstractLoader implements LoaderInterface
|
||||
{
|
||||
public function load(mixed $resource)
|
||||
{
|
||||
if (!$this->supports($resource)) {
|
||||
throw new \LogicException(sprintf(
|
||||
'%s::%s() was called with unsupported resource `%s`.',
|
||||
static::class,
|
||||
__FUNCTION__,
|
||||
json_encode($resource)
|
||||
));
|
||||
}
|
||||
|
||||
return $this->doLoad($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TResourceType $resource
|
||||
*
|
||||
* @return list<FeatureNode>
|
||||
*/
|
||||
abstract protected function doLoad(mixed $resource): array;
|
||||
}
|
||||
299
vendor/behat/gherkin/src/Loader/ArrayLoader.php
vendored
299
vendor/behat/gherkin/src/Loader/ArrayLoader.php
vendored
@ -1,299 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Loader;
|
||||
|
||||
use Behat\Gherkin\Node\BackgroundNode;
|
||||
use Behat\Gherkin\Node\ExampleTableNode;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\OutlineNode;
|
||||
use Behat\Gherkin\Node\PyStringNode;
|
||||
use Behat\Gherkin\Node\ScenarioNode;
|
||||
use Behat\Gherkin\Node\StepNode;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
|
||||
/**
|
||||
* From-array loader.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @phpstan-type TFeatureHash array{title?: string|null, description?: string|null, tags?: list<string>, keyword?: string, language?: string, line?: int, background?: TBackgroundHash|null, scenarios?: array<int, TScenarioHash|TOutlineHash>}
|
||||
* @phpstan-type TBackgroundHash array{title?: string|null, keyword?: string, line?: int, steps?: array<int, TStepHash>}
|
||||
* @phpstan-type TScenarioHash array{title?: string|null, tags?: list<string>, keyword?: string, line?: int, steps?: array<int, TStepHash>}
|
||||
* @phpstan-type TOutlineHash array{type: 'outline', title?: string|null, tags?: list<string>, keyword?: string, line?: int, steps?: array<int, TStepHash>, examples?: TExampleTableHash|array<array-key, TExampleHash>}
|
||||
* @phpstan-type TExampleHash array{table: TExampleTableHash, tags?: list<string>}|TExampleTableHash
|
||||
* @phpstan-type TExampleTableHash array<int<1, max>, list<string>>
|
||||
* @phpstan-type TStepHash array{keyword_type?: string, type?: string, text: string, keyword?: string, line?: int, arguments?: array<array-key, TArgumentHash>}
|
||||
* @phpstan-type TArgumentHash array{type: 'table', rows: TTableHash}|TPyStringHash
|
||||
* @phpstan-type TTableHash array<int, list<string>>
|
||||
* @phpstan-type TPyStringHash array{type: 'pystring', line?: int, text: string}
|
||||
* @phpstan-type TArrayResource array{feature: TFeatureHash}|array{features: array<int, TFeatureHash>}
|
||||
*
|
||||
* @phpstan-extends AbstractLoader<TArrayResource>
|
||||
*/
|
||||
class ArrayLoader extends AbstractLoader
|
||||
{
|
||||
public function supports(mixed $resource)
|
||||
{
|
||||
return is_array($resource) && (isset($resource['features']) || isset($resource['feature']));
|
||||
}
|
||||
|
||||
protected function doLoad(mixed $resource): array
|
||||
{
|
||||
$features = [];
|
||||
|
||||
if (isset($resource['features'])) {
|
||||
foreach ($resource['features'] as $iterator => $hash) {
|
||||
$feature = $this->loadFeatureHash($hash, $iterator);
|
||||
$features[] = $feature;
|
||||
}
|
||||
} elseif (isset($resource['feature'])) {
|
||||
$feature = $this->loadFeatureHash($resource['feature']);
|
||||
$features[] = $feature;
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads feature from provided feature hash.
|
||||
*
|
||||
* @phpstan-param TFeatureHash $hash
|
||||
*
|
||||
* @return FeatureNode
|
||||
*/
|
||||
protected function loadFeatureHash(array $hash, int $line = 0)
|
||||
{
|
||||
$hash = array_merge(
|
||||
[
|
||||
'title' => null,
|
||||
'description' => null,
|
||||
'tags' => [],
|
||||
'keyword' => 'Feature',
|
||||
'language' => 'en',
|
||||
'line' => $line,
|
||||
'scenarios' => [],
|
||||
],
|
||||
$hash
|
||||
);
|
||||
$background = isset($hash['background']) ? $this->loadBackgroundHash($hash['background']) : null;
|
||||
|
||||
$scenarios = [];
|
||||
foreach ((array) $hash['scenarios'] as $scenarioIterator => $scenarioHash) {
|
||||
if (isset($scenarioHash['type']) && $scenarioHash['type'] === 'outline') {
|
||||
$scenarios[] = $this->loadOutlineHash($scenarioHash, $scenarioIterator);
|
||||
} else {
|
||||
$scenarios[] = $this->loadScenarioHash($scenarioHash, $scenarioIterator);
|
||||
}
|
||||
}
|
||||
|
||||
return new FeatureNode($hash['title'], $hash['description'], $hash['tags'], $background, $scenarios, $hash['keyword'], $hash['language'], null, $hash['line']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads background from provided hash.
|
||||
*
|
||||
* @phpstan-param TBackgroundHash $hash
|
||||
*
|
||||
* @return BackgroundNode
|
||||
*/
|
||||
protected function loadBackgroundHash(array $hash)
|
||||
{
|
||||
$hash = array_merge(
|
||||
[
|
||||
'title' => null,
|
||||
'keyword' => 'Background',
|
||||
'line' => 0,
|
||||
'steps' => [],
|
||||
],
|
||||
$hash
|
||||
);
|
||||
|
||||
$steps = $this->loadStepsHash($hash['steps']);
|
||||
|
||||
return new BackgroundNode($hash['title'], $steps, $hash['keyword'], $hash['line']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads scenario from provided scenario hash.
|
||||
*
|
||||
* @phpstan-param TScenarioHash $hash
|
||||
*
|
||||
* @return ScenarioNode
|
||||
*/
|
||||
protected function loadScenarioHash(array $hash, int $line = 0)
|
||||
{
|
||||
$hash = array_merge(
|
||||
[
|
||||
'title' => null,
|
||||
'tags' => [],
|
||||
'keyword' => 'Scenario',
|
||||
'line' => $line,
|
||||
'steps' => [],
|
||||
],
|
||||
$hash
|
||||
);
|
||||
|
||||
$steps = $this->loadStepsHash($hash['steps']);
|
||||
|
||||
return new ScenarioNode($hash['title'], $hash['tags'], $steps, $hash['keyword'], $hash['line']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads outline from provided outline hash.
|
||||
*
|
||||
* @phpstan-param TOutlineHash $hash
|
||||
*
|
||||
* @return OutlineNode
|
||||
*/
|
||||
protected function loadOutlineHash(array $hash, int $line = 0)
|
||||
{
|
||||
$hash = array_merge(
|
||||
[
|
||||
'title' => null,
|
||||
'tags' => [],
|
||||
'keyword' => 'Scenario Outline',
|
||||
'line' => $line,
|
||||
'steps' => [],
|
||||
'examples' => [],
|
||||
],
|
||||
$hash
|
||||
);
|
||||
|
||||
$steps = $this->loadStepsHash($hash['steps']);
|
||||
|
||||
if (isset($hash['examples']['keyword'])) {
|
||||
$examplesKeyword = $hash['examples']['keyword'];
|
||||
assert(is_string($examplesKeyword));
|
||||
unset($hash['examples']['keyword']);
|
||||
} else {
|
||||
$examplesKeyword = 'Examples';
|
||||
}
|
||||
|
||||
$examples = $this->loadExamplesHash($hash['examples'], $examplesKeyword);
|
||||
|
||||
return new OutlineNode($hash['title'], $hash['tags'], $steps, $examples, $hash['keyword'], $hash['line']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads steps from provided hash.
|
||||
*
|
||||
* @phpstan-param array<int, TStepHash> $hash
|
||||
*
|
||||
* @return list<StepNode>
|
||||
*/
|
||||
private function loadStepsHash(array $hash)
|
||||
{
|
||||
$steps = [];
|
||||
foreach ($hash as $stepIterator => $stepHash) {
|
||||
$steps[] = $this->loadStepHash($stepHash, $stepIterator);
|
||||
}
|
||||
|
||||
return $steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads step from provided hash.
|
||||
*
|
||||
* @phpstan-param TStepHash $hash
|
||||
*
|
||||
* @return StepNode
|
||||
*/
|
||||
protected function loadStepHash(array $hash, int $line = 0)
|
||||
{
|
||||
$hash = array_merge(
|
||||
[
|
||||
'keyword_type' => 'Given',
|
||||
'type' => 'Given',
|
||||
'text' => null,
|
||||
'keyword' => 'Scenario',
|
||||
'line' => $line,
|
||||
'arguments' => [],
|
||||
],
|
||||
$hash
|
||||
);
|
||||
|
||||
$arguments = [];
|
||||
foreach ($hash['arguments'] as $argumentHash) {
|
||||
if ($argumentHash['type'] === 'table') {
|
||||
$arguments[] = $this->loadTableHash($argumentHash['rows']);
|
||||
} elseif ($argumentHash['type'] === 'pystring') {
|
||||
$arguments[] = $this->loadPyStringHash($argumentHash, $hash['line'] + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return new StepNode($hash['type'], $hash['text'], $arguments, $hash['line'], $hash['keyword_type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads table from provided hash.
|
||||
*
|
||||
* @phpstan-param TTableHash $hash
|
||||
*
|
||||
* @return TableNode
|
||||
*/
|
||||
protected function loadTableHash(array $hash)
|
||||
{
|
||||
return new TableNode($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PyString from provided hash.
|
||||
*
|
||||
* @phpstan-param TPyStringHash $hash
|
||||
*
|
||||
* @return PyStringNode
|
||||
*/
|
||||
protected function loadPyStringHash(array $hash, int $line = 0)
|
||||
{
|
||||
$line = $hash['line'] ?? $line;
|
||||
|
||||
$strings = [];
|
||||
foreach (explode("\n", $hash['text']) as $string) {
|
||||
$strings[] = $string;
|
||||
}
|
||||
|
||||
return new PyStringNode($strings, $line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes cases when examples are in the form of array of arrays
|
||||
* OR in the form of array of objects.
|
||||
*
|
||||
* @phpstan-param TExampleHash|array<array-key, TExampleHash> $examplesHash
|
||||
*
|
||||
* @return list<ExampleTableNode>
|
||||
*/
|
||||
private function loadExamplesHash(array $examplesHash, string $examplesKeyword): array
|
||||
{
|
||||
if (!isset($examplesHash[0])) {
|
||||
// examples as a single table - create a list with the one element
|
||||
// @phpstan-ignore argument.type
|
||||
return [new ExampleTableNode($examplesHash, $examplesKeyword)];
|
||||
}
|
||||
|
||||
$examples = [];
|
||||
|
||||
foreach ($examplesHash as $exampleHash) {
|
||||
if (isset($exampleHash['table'])) {
|
||||
// we have examples as objects, hence there could be tags
|
||||
$exHashTags = $exampleHash['tags'] ?? [];
|
||||
// @phpstan-ignore argument.type,argument.type
|
||||
$examples[] = new ExampleTableNode($exampleHash['table'], $examplesKeyword, $exHashTags);
|
||||
} else {
|
||||
// we have examples as arrays
|
||||
// @phpstan-ignore argument.type
|
||||
$examples[] = new ExampleTableNode($exampleHash, $examplesKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
return $examples;
|
||||
}
|
||||
}
|
||||
@ -1,265 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Loader;
|
||||
|
||||
use Behat\Gherkin\Exception\NodeException;
|
||||
use Behat\Gherkin\Node\ArgumentInterface;
|
||||
use Behat\Gherkin\Node\BackgroundNode;
|
||||
use Behat\Gherkin\Node\ExampleTableNode;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\OutlineNode;
|
||||
use Behat\Gherkin\Node\PyStringNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
use Behat\Gherkin\Node\ScenarioNode;
|
||||
use Behat\Gherkin\Node\StepNode;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Loads a feature from cucumber's messages JSON format.
|
||||
*
|
||||
* Lines in the ndjson file are expected to match the Cucumber Messages JSON schema defined at https://github.com/cucumber/messages/tree/main/jsonschema
|
||||
*
|
||||
* @deprecated This loader is deprecated and will be removed in 5.0
|
||||
*
|
||||
* @phpstan-type TLocation array{line: int, column?: int}
|
||||
* @phpstan-type TBackground array{location: TLocation, keyword: string, name: string, description: string, steps: list<TStep>, id: string}
|
||||
* @phpstan-type TComment array{location: TLocation, text: string}
|
||||
* @phpstan-type TDataTable array{location: TLocation, rows: list<TTableRow>}
|
||||
* @phpstan-type TDocString array{location: TLocation, content: string, delimiter: string, mediaType?: string}
|
||||
* @phpstan-type TExamples array{location: TLocation, tags: list<TTag>, keyword: string, name: string, description: string, tableHeader?: TTableRow, tableBody: list<TTableRow>, id: string}
|
||||
* @phpstan-type TFeature array{location: TLocation, tags: list<TTag>, language: string, keyword: string, name: string, description: string, children: list<TFeatureChild>}
|
||||
* @phpstan-type TFeatureChild array{background?: TBackground, scenario?: TScenario, rule?: TRule}
|
||||
* @phpstan-type TRule array{location: TLocation, tags: list<TTag>, keyword: string, name: string, description: string, children: list<TRuleChild>, id: string}
|
||||
* @phpstan-type TRuleChild array{background?: TBackground, scenario?: TScenario}
|
||||
* @phpstan-type TScenario array{location: TLocation, tags: list<TTag>, keyword: string, name: string, description: string, steps: list<TStep>, examples: list<TExamples>, id: string}
|
||||
* @phpstan-type TStep array{location: TLocation, keyword: string, keywordType?: 'Unknown'|'Context'|'Action'|'Outcome'|'Conjunction', text: string, docString?: TDocString, dataTable?: TDataTable, id: string}
|
||||
* @phpstan-type TTableCell array{location: TLocation, value: string}
|
||||
* @phpstan-type TTableRow array{location: TLocation, cells: list<TTableCell>, id: string}
|
||||
* @phpstan-type TTag array{location: TLocation, name: string, id: string}
|
||||
* @phpstan-type TGherkinDocument array{uri?: string, feature?: TFeature, comments: list<TComment>}
|
||||
* // We only care about the gherkinDocument messages for our use case, so this does not describe the envelope fully
|
||||
* @phpstan-type TEnvelope array{gherkinDocument?: TGherkinDocument, ...}
|
||||
*
|
||||
* @extends AbstractLoader<string>
|
||||
*/
|
||||
class CucumberNDJsonAstLoader extends AbstractLoader
|
||||
{
|
||||
public function supports(mixed $resource)
|
||||
{
|
||||
return is_string($resource);
|
||||
}
|
||||
|
||||
protected function doLoad(mixed $resource): array
|
||||
{
|
||||
return array_values(
|
||||
array_filter(
|
||||
array_map(
|
||||
static function ($line) use ($resource) {
|
||||
// As we load data from the official Cucumber project, we assume the data matches the JSON schema.
|
||||
// @phpstan-ignore argument.type
|
||||
return self::getFeature(json_decode($line, true, 512, \JSON_THROW_ON_ERROR), $resource);
|
||||
},
|
||||
file($resource)
|
||||
?: throw new RuntimeException("Could not load Cucumber json file: $resource."),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param TEnvelope $json
|
||||
*/
|
||||
private static function getFeature(array $json, string $filePath): ?FeatureNode
|
||||
{
|
||||
if (!isset($json['gherkinDocument']['feature'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$featureJson = $json['gherkinDocument']['feature'];
|
||||
|
||||
return new FeatureNode(
|
||||
$featureJson['name'],
|
||||
$featureJson['description'],
|
||||
self::getTags($featureJson),
|
||||
self::getBackground($featureJson),
|
||||
self::getScenarios($featureJson),
|
||||
$featureJson['keyword'],
|
||||
$featureJson['language'],
|
||||
preg_replace('/(?<=\\.feature).*$/', '', $filePath),
|
||||
$featureJson['location']['line']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param array{tags: list<TTag>, ...} $json
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function getTags(array $json): array
|
||||
{
|
||||
return array_map(
|
||||
static fn (array $tag) => preg_replace('/^@/', '', $tag['name']) ?? $tag['name'],
|
||||
$json['tags']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param TFeature $json
|
||||
*
|
||||
* @return list<ScenarioInterface>
|
||||
*/
|
||||
private static function getScenarios(array $json): array
|
||||
{
|
||||
return array_values(
|
||||
array_map(
|
||||
static function ($child) {
|
||||
$tables = self::getTables($child['scenario']['examples']);
|
||||
|
||||
if ($tables) {
|
||||
return new OutlineNode(
|
||||
$child['scenario']['name'],
|
||||
self::getTags($child['scenario']),
|
||||
self::getSteps($child['scenario']['steps']),
|
||||
$tables,
|
||||
$child['scenario']['keyword'],
|
||||
$child['scenario']['location']['line']
|
||||
);
|
||||
}
|
||||
|
||||
return new ScenarioNode(
|
||||
$child['scenario']['name'],
|
||||
self::getTags($child['scenario']),
|
||||
self::getSteps($child['scenario']['steps']),
|
||||
$child['scenario']['keyword'],
|
||||
$child['scenario']['location']['line']
|
||||
);
|
||||
},
|
||||
array_filter(
|
||||
$json['children'],
|
||||
static function ($child) {
|
||||
return isset($child['scenario']);
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param TFeature $json
|
||||
*/
|
||||
private static function getBackground(array $json): ?BackgroundNode
|
||||
{
|
||||
$backgrounds = array_filter(
|
||||
$json['children'],
|
||||
static fn ($child) => isset($child['background']),
|
||||
);
|
||||
|
||||
if (count($backgrounds) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$background = array_shift($backgrounds);
|
||||
|
||||
return new BackgroundNode(
|
||||
$background['background']['name'],
|
||||
self::getSteps($background['background']['steps']),
|
||||
$background['background']['keyword'],
|
||||
$background['background']['location']['line']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param list<TStep> $items
|
||||
*
|
||||
* @return list<StepNode>
|
||||
*/
|
||||
private static function getSteps(array $items): array
|
||||
{
|
||||
return array_map(
|
||||
static fn (array $item) => new StepNode(
|
||||
trim($item['keyword']),
|
||||
$item['text'],
|
||||
self::getStepArguments($item),
|
||||
$item['location']['line'],
|
||||
trim($item['keyword'])
|
||||
),
|
||||
$items
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param TStep $step
|
||||
*
|
||||
* @return list<ArgumentInterface>
|
||||
*/
|
||||
private static function getStepArguments(array $step): array
|
||||
{
|
||||
$args = [];
|
||||
|
||||
if (isset($step['docString'])) {
|
||||
$args[] = new PyStringNode(
|
||||
explode("\n", $step['docString']['content']),
|
||||
$step['docString']['location']['line'],
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($step['dataTable'])) {
|
||||
$table = [];
|
||||
foreach ($step['dataTable']['rows'] as $row) {
|
||||
$table[$row['location']['line']] = array_column($row['cells'], 'value');
|
||||
}
|
||||
$args[] = new TableNode($table);
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param list<TExamples> $items
|
||||
*
|
||||
* @return list<ExampleTableNode>
|
||||
*/
|
||||
private static function getTables(array $items): array
|
||||
{
|
||||
return array_map(
|
||||
static function ($tableJson): ExampleTableNode {
|
||||
$headerRow = $tableJson['tableHeader'] ?? null;
|
||||
$tableBody = $tableJson['tableBody'];
|
||||
|
||||
if ($headerRow === null && ($tableBody !== [])) {
|
||||
throw new NodeException(
|
||||
sprintf(
|
||||
'Table header is required when a table body is provided for the example on line %s.',
|
||||
$tableJson['location']['line'],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$table = [];
|
||||
if ($headerRow !== null) {
|
||||
$table[$headerRow['location']['line']] = array_column($headerRow['cells'], 'value');
|
||||
}
|
||||
|
||||
foreach ($tableBody as $bodyRow) {
|
||||
$table[$bodyRow['location']['line']] = array_column($bodyRow['cells'], 'value');
|
||||
}
|
||||
|
||||
return new ExampleTableNode(
|
||||
$table,
|
||||
$tableJson['keyword'],
|
||||
self::getTags($tableJson)
|
||||
);
|
||||
},
|
||||
$items
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Loader;
|
||||
|
||||
use Behat\Gherkin\Gherkin;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SplFileInfo;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Directory contents loader.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @extends AbstractFileLoader<string>
|
||||
*/
|
||||
class DirectoryLoader extends AbstractFileLoader
|
||||
{
|
||||
/**
|
||||
* @var Gherkin
|
||||
*/
|
||||
protected $gherkin;
|
||||
|
||||
/**
|
||||
* Initializes loader.
|
||||
*/
|
||||
public function __construct(Gherkin $gherkin)
|
||||
{
|
||||
$this->gherkin = $gherkin;
|
||||
}
|
||||
|
||||
public function supports(mixed $resource)
|
||||
{
|
||||
return is_string($resource)
|
||||
&& ($path = $this->findAbsolutePath($resource)) !== false
|
||||
&& is_dir($path);
|
||||
}
|
||||
|
||||
protected function doLoad(mixed $resource): array
|
||||
{
|
||||
$path = $this->getAbsolutePath($resource);
|
||||
/** @var Traversable<SplFileInfo> $iterator */
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS)
|
||||
);
|
||||
$paths = array_map(strval(...), iterator_to_array($iterator));
|
||||
uasort($paths, strnatcasecmp(...));
|
||||
|
||||
$features = [];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$path = (string) $path;
|
||||
$loader = $this->gherkin->resolveLoader($path);
|
||||
|
||||
if ($loader !== null) {
|
||||
array_push($features, ...$loader->load($path));
|
||||
}
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Loader;
|
||||
|
||||
/**
|
||||
* File Loader interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @template TResourceType
|
||||
*
|
||||
* @extends LoaderInterface<TResourceType>
|
||||
*/
|
||||
interface FileLoaderInterface extends LoaderInterface
|
||||
{
|
||||
/**
|
||||
* Sets base features path.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setBasePath(string $path);
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Loader;
|
||||
|
||||
use Behat\Gherkin\Cache\CacheInterface;
|
||||
use Behat\Gherkin\Filesystem;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\ParserInterface;
|
||||
|
||||
/**
|
||||
* Gherkin *.feature files loader.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @extends AbstractFileLoader<string>
|
||||
*/
|
||||
class GherkinFileLoader extends AbstractFileLoader
|
||||
{
|
||||
/**
|
||||
* @var ParserInterface
|
||||
*/
|
||||
protected $parser;
|
||||
/**
|
||||
* @var CacheInterface|null
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
public function __construct(ParserInterface $parser, ?CacheInterface $cache = null)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets cache layer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCache(CacheInterface $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
public function supports(mixed $resource)
|
||||
{
|
||||
return is_string($resource)
|
||||
&& ($path = $this->findAbsolutePath($resource)) !== false
|
||||
&& is_file($path)
|
||||
&& pathinfo($path, PATHINFO_EXTENSION) === 'feature';
|
||||
}
|
||||
|
||||
protected function doLoad(mixed $resource): array
|
||||
{
|
||||
$path = $this->getAbsolutePath($resource);
|
||||
if ($this->cache) {
|
||||
if ($this->cache->isFresh($path, Filesystem::getLastModified($path))) {
|
||||
$feature = $this->cache->read($path);
|
||||
} elseif (null !== $feature = $this->parseFeature($path)) {
|
||||
$this->cache->write($path, $feature);
|
||||
}
|
||||
} else {
|
||||
$feature = $this->parseFeature($path);
|
||||
}
|
||||
|
||||
return $feature !== null ? [$feature] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses feature at provided absolute path.
|
||||
*
|
||||
* @param string $path Feature path
|
||||
*
|
||||
* @return FeatureNode|null
|
||||
*/
|
||||
protected function parseFeature(string $path)
|
||||
{
|
||||
return $this->parser->parseFile($path);
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Loader;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
|
||||
/**
|
||||
* Loader interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @template TResourceType
|
||||
*/
|
||||
interface LoaderInterface
|
||||
{
|
||||
/**
|
||||
* Checks if current loader supports provided resource.
|
||||
*
|
||||
* @template TSupportedResourceType
|
||||
*
|
||||
* @param TSupportedResourceType $resource Resource to load
|
||||
*
|
||||
* @phpstan-assert-if-true =LoaderInterface<TSupportedResourceType> $this
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(mixed $resource);
|
||||
|
||||
/**
|
||||
* Loads features from provided resource.
|
||||
*
|
||||
* @param TResourceType $resource Resource to load
|
||||
*
|
||||
* @return list<FeatureNode>
|
||||
*/
|
||||
public function load(mixed $resource);
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Loader;
|
||||
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Yaml files loader.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @extends AbstractFileLoader<string>
|
||||
*
|
||||
* @phpstan-import-type TArrayResource from ArrayLoader
|
||||
*/
|
||||
class YamlFileLoader extends AbstractFileLoader
|
||||
{
|
||||
/**
|
||||
* @phpstan-param LoaderInterface<TArrayResource> $loader
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly LoaderInterface $loader = new ArrayLoader(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(mixed $resource)
|
||||
{
|
||||
return is_string($resource)
|
||||
&& ($path = $this->findAbsolutePath($resource)) !== false
|
||||
&& is_file($path)
|
||||
&& pathinfo($path, PATHINFO_EXTENSION) === 'yml';
|
||||
}
|
||||
|
||||
protected function doLoad(mixed $resource): array
|
||||
{
|
||||
$path = $this->getAbsolutePath($resource);
|
||||
$hash = Yaml::parseFile($path);
|
||||
|
||||
// @phpstan-ignore argument.type
|
||||
$features = $this->loader->load($hash);
|
||||
|
||||
return array_map(
|
||||
static fn (FeatureNode $feature) => new FeatureNode(
|
||||
$feature->getTitle(),
|
||||
$feature->getDescription(),
|
||||
$feature->getTags(),
|
||||
$feature->getBackground(),
|
||||
$feature->getScenarios(),
|
||||
$feature->getKeyword(),
|
||||
$feature->getLanguage(),
|
||||
$path,
|
||||
$feature->getLine()
|
||||
),
|
||||
$features
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Gherkin arguments interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface ArgumentInterface extends NodeInterface
|
||||
{
|
||||
}
|
||||
98
vendor/behat/gherkin/src/Node/BackgroundNode.php
vendored
98
vendor/behat/gherkin/src/Node/BackgroundNode.php
vendored
@ -1,98 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Represents Gherkin Background.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*/
|
||||
class BackgroundNode implements ScenarioLikeInterface, DescribableNodeInterface
|
||||
{
|
||||
/**
|
||||
* @param StepNode[] $steps
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ?string $title,
|
||||
private readonly array $steps,
|
||||
private readonly string $keyword,
|
||||
private readonly int $line,
|
||||
private readonly ?string $description = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node type string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType()
|
||||
{
|
||||
return 'Background';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns background title.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if background has steps.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSteps()
|
||||
{
|
||||
return (bool) count($this->steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns background steps.
|
||||
*
|
||||
* @return StepNode[]
|
||||
*/
|
||||
public function getSteps()
|
||||
{
|
||||
return $this->steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns background keyword.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyword()
|
||||
{
|
||||
return $this->keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns background declaration line number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
interface DescribableNodeInterface
|
||||
{
|
||||
/**
|
||||
* @return ?string
|
||||
*/
|
||||
public function getDescription();
|
||||
}
|
||||
248
vendor/behat/gherkin/src/Node/ExampleNode.php
vendored
248
vendor/behat/gherkin/src/Node/ExampleNode.php
vendored
@ -1,248 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Represents Gherkin Outline Example.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*/
|
||||
class ExampleNode implements ScenarioInterface, NamedScenarioInterface
|
||||
{
|
||||
use TaggedNodeTrait;
|
||||
|
||||
/**
|
||||
* @var list<StepNode>|null
|
||||
*/
|
||||
private ?array $steps = null;
|
||||
|
||||
/**
|
||||
* @param string $text The entire row as a string, e.g. "| 1 | 2 | 3 |"
|
||||
* @param list<string> $tags
|
||||
* @param array<array-key, StepNode> $outlineSteps
|
||||
* @param array<string, string> $tokens
|
||||
* @param int $line line number within the feature file
|
||||
* @param string|null $outlineTitle original title of the scenario outline
|
||||
* @param int|null $index the 1-based index of the row/example within the scenario outline
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $text,
|
||||
private readonly array $tags,
|
||||
private readonly array $outlineSteps,
|
||||
private readonly array $tokens,
|
||||
private readonly int $line,
|
||||
private readonly ?string $outlineTitle = null,
|
||||
private readonly ?int $index = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node type string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType()
|
||||
{
|
||||
return 'Example';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node keyword.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyword()
|
||||
{
|
||||
return $this->getNodeType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the example row as a single string.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @deprecated you should normally not depend on the original row text, but if you really do, please switch
|
||||
* to {@see self::getExampleText()} as this method will be removed in the next major version
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function getTags()
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if outline has steps.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSteps()
|
||||
{
|
||||
return count($this->outlineSteps) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns outline steps.
|
||||
*
|
||||
* @return list<StepNode>
|
||||
*/
|
||||
public function getSteps()
|
||||
{
|
||||
return $this->steps ??= $this->createExampleSteps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns example tokens.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTokens()
|
||||
{
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns outline declaration line number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns outline title.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOutlineTitle()
|
||||
{
|
||||
return $this->outlineTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Return type should become `string` in 5.0 when the class is actually `final`
|
||||
*
|
||||
* @phpstan-ignore return.unusedType
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return "{$this->replaceTextTokens($this->outlineTitle ?? '')} #{$this->index}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the example row as a single string.
|
||||
*
|
||||
* You should normally not need this, since it is an implementation detail.
|
||||
* If you need the individual example values, use {@see self::getTokens()}.
|
||||
* To get the fully-normalised/expanded title, use {@see self::getName()}.
|
||||
*/
|
||||
public function getExampleText(): string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates steps for this example from abstract outline steps.
|
||||
*
|
||||
* @return list<StepNode>
|
||||
*/
|
||||
protected function createExampleSteps()
|
||||
{
|
||||
$steps = [];
|
||||
foreach ($this->outlineSteps as $outlineStep) {
|
||||
$keyword = $outlineStep->getKeyword();
|
||||
$keywordType = $outlineStep->getKeywordType();
|
||||
$text = $this->replaceTextTokens($outlineStep->getText());
|
||||
$args = $this->replaceArgumentsTokens($outlineStep->getArguments());
|
||||
$line = $outlineStep->getLine();
|
||||
|
||||
$steps[] = new StepNode($keyword, $text, $args, $line, $keywordType);
|
||||
}
|
||||
|
||||
return $steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces tokens in arguments with row values.
|
||||
*
|
||||
* @param array<array-key, ArgumentInterface> $arguments
|
||||
*
|
||||
* @return array<array-key, ArgumentInterface>
|
||||
*/
|
||||
protected function replaceArgumentsTokens(array $arguments)
|
||||
{
|
||||
foreach ($arguments as $num => $argument) {
|
||||
if ($argument instanceof TableNode) {
|
||||
$arguments[$num] = $this->replaceTableArgumentTokens($argument);
|
||||
}
|
||||
if ($argument instanceof PyStringNode) {
|
||||
$arguments[$num] = $this->replacePyStringArgumentTokens($argument);
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces tokens in table with row values.
|
||||
*
|
||||
* @return TableNode
|
||||
*/
|
||||
protected function replaceTableArgumentTokens(TableNode $argument)
|
||||
{
|
||||
$replacedTable = [];
|
||||
foreach ($argument->getTable() as $line => $row) {
|
||||
$replacedRow = [];
|
||||
foreach ($row as $value) {
|
||||
$replacedRow[] = $this->replaceTextTokens($value);
|
||||
}
|
||||
$replacedTable[$line] = $replacedRow;
|
||||
}
|
||||
|
||||
return new TableNode($replacedTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces tokens in PyString with row values.
|
||||
*
|
||||
* @return PyStringNode
|
||||
*/
|
||||
protected function replacePyStringArgumentTokens(PyStringNode $argument)
|
||||
{
|
||||
$strings = $argument->getStrings();
|
||||
foreach ($strings as $line => $string) {
|
||||
$strings[$line] = $this->replaceTextTokens($strings[$line]);
|
||||
}
|
||||
|
||||
return new PyStringNode($strings, $argument->getLine());
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces tokens in text with row values.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function replaceTextTokens(string $text)
|
||||
{
|
||||
foreach ($this->tokens as $key => $val) {
|
||||
$text = str_replace('<' . $key . '>', $val, $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Represents Gherkin Outline Example Table.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*/
|
||||
class ExampleTableNode extends TableNode implements TaggedNodeInterface, DescribableNodeInterface
|
||||
{
|
||||
use TaggedNodeTrait;
|
||||
|
||||
/**
|
||||
* @param array<int, list<string>> $table Table in form of [$rowLineNumber => [$val1, $val2, $val3]]
|
||||
* @param list<string> $tags
|
||||
*/
|
||||
public function __construct(
|
||||
array $table,
|
||||
private readonly string $keyword,
|
||||
private readonly array $tags = [],
|
||||
private readonly ?string $name = null,
|
||||
private readonly ?string $description = null,
|
||||
) {
|
||||
parent::__construct($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node type string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType()
|
||||
{
|
||||
return 'ExampleTable';
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getTags()
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns example table keyword.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyword()
|
||||
{
|
||||
return $this->keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, list<string>> $table Table in form of [$rowLineNumber => [$val1, $val2, $val3]]
|
||||
*/
|
||||
public function withTable(array $table): self
|
||||
{
|
||||
return new self(
|
||||
$table,
|
||||
$this->keyword,
|
||||
$this->tags,
|
||||
$this->name,
|
||||
$this->description,
|
||||
);
|
||||
}
|
||||
}
|
||||
217
vendor/behat/gherkin/src/Node/FeatureNode.php
vendored
217
vendor/behat/gherkin/src/Node/FeatureNode.php
vendored
@ -1,217 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Represents Gherkin Feature.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*/
|
||||
class FeatureNode implements KeywordNodeInterface, TaggedNodeInterface, DescribableNodeInterface
|
||||
{
|
||||
use TaggedNodeTrait;
|
||||
|
||||
/**
|
||||
* @param list<string> $tags
|
||||
* @param ScenarioInterface[] $scenarios
|
||||
* @param string|null $file the absolute path to the feature file
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ?string $title,
|
||||
private readonly ?string $description,
|
||||
private readonly array $tags,
|
||||
private readonly ?BackgroundNode $background,
|
||||
private readonly array $scenarios,
|
||||
private readonly string $keyword,
|
||||
private readonly string $language,
|
||||
private readonly ?string $file,
|
||||
private readonly int $line,
|
||||
) {
|
||||
// Verify that the feature file is an absolute path.
|
||||
if (!empty($file) && !$this->isAbsolutePath($file)) {
|
||||
throw new InvalidArgumentException('The file should be an absolute path.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node type string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType()
|
||||
{
|
||||
return 'Feature';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature title.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if feature has a description.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDescription()
|
||||
{
|
||||
return !empty($this->description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature description.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getTags()
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if feature has background.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasBackground()
|
||||
{
|
||||
return $this->background !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature background.
|
||||
*
|
||||
* @return BackgroundNode|null
|
||||
*/
|
||||
public function getBackground()
|
||||
{
|
||||
return $this->background;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if feature has scenarios.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasScenarios()
|
||||
{
|
||||
return count($this->scenarios) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature scenarios.
|
||||
*
|
||||
* @return ScenarioInterface[]
|
||||
*/
|
||||
public function getScenarios()
|
||||
{
|
||||
return $this->scenarios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature keyword.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyword()
|
||||
{
|
||||
return $this->keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature language.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature file as an absolute path.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature declaration line number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this feature, but with a different set of scenarios.
|
||||
*
|
||||
* @param array<array-key, ScenarioInterface> $scenarios
|
||||
*/
|
||||
public function withScenarios(array $scenarios): self
|
||||
{
|
||||
return new self(
|
||||
$this->title,
|
||||
$this->description,
|
||||
$this->tags,
|
||||
$this->background,
|
||||
array_values($scenarios),
|
||||
$this->keyword,
|
||||
$this->language,
|
||||
$this->file,
|
||||
$this->line,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the file path is an absolute path.
|
||||
*
|
||||
* @param string|null $file A file path
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @see https://github.com/symfony/filesystem/blob/master/Filesystem.php
|
||||
*/
|
||||
protected function isAbsolutePath(?string $file)
|
||||
{
|
||||
if ($file === null) {
|
||||
throw new InvalidArgumentException('The provided file path must not be null.');
|
||||
}
|
||||
|
||||
return strspn($file, '/\\', 0, 1)
|
||||
|| (strlen($file) > 3 && ctype_alpha($file[0])
|
||||
&& $file[1] === ':'
|
||||
&& strspn($file, '/\\', 2, 1)
|
||||
)
|
||||
|| parse_url($file, PHP_URL_SCHEME) !== null;
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Gherkin keyword node interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface KeywordNodeInterface extends NodeInterface
|
||||
{
|
||||
/**
|
||||
* Returns node keyword.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyword();
|
||||
|
||||
/**
|
||||
* Returns node title.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTitle();
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
interface NamedScenarioInterface
|
||||
{
|
||||
/**
|
||||
* Returns the human-readable name of the scenario.
|
||||
*/
|
||||
public function getName(): ?string;
|
||||
}
|
||||
33
vendor/behat/gherkin/src/Node/NodeInterface.php
vendored
33
vendor/behat/gherkin/src/Node/NodeInterface.php
vendored
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Gherkin node interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface NodeInterface
|
||||
{
|
||||
/**
|
||||
* Returns node type string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType();
|
||||
|
||||
/**
|
||||
* Returns feature declaration line number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLine();
|
||||
}
|
||||
219
vendor/behat/gherkin/src/Node/OutlineNode.php
vendored
219
vendor/behat/gherkin/src/Node/OutlineNode.php
vendored
@ -1,219 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Represents Gherkin Outline.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*/
|
||||
class OutlineNode implements ScenarioInterface, DescribableNodeInterface
|
||||
{
|
||||
use TaggedNodeTrait;
|
||||
|
||||
/**
|
||||
* @var array<array-key, ExampleTableNode>
|
||||
*/
|
||||
private array $tables;
|
||||
/**
|
||||
* @var ExampleNode[]
|
||||
*/
|
||||
private array $examples;
|
||||
|
||||
/**
|
||||
* @param list<string> $tags
|
||||
* @param StepNode[] $steps
|
||||
* @param ExampleTableNode|array<array-key, ExampleTableNode> $tables
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ?string $title,
|
||||
private readonly array $tags,
|
||||
private readonly array $steps,
|
||||
array|ExampleTableNode $tables,
|
||||
private readonly string $keyword,
|
||||
private readonly int $line,
|
||||
private readonly ?string $description = null,
|
||||
) {
|
||||
$this->tables = is_array($tables) ? $tables : [$tables];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node type string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType()
|
||||
{
|
||||
return 'Outline';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns outline title.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getTags()
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if outline has steps.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSteps()
|
||||
{
|
||||
return count($this->steps) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns outline steps.
|
||||
*
|
||||
* @return StepNode[]
|
||||
*/
|
||||
public function getSteps()
|
||||
{
|
||||
return $this->steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if outline has examples.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExamples()
|
||||
{
|
||||
return count($this->tables) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns examples table for the outline.
|
||||
*
|
||||
* WARNING: it returns a merged table with tags, names & descriptions lost.
|
||||
*
|
||||
* @return ExampleTableNode
|
||||
*
|
||||
* @deprecated use getExampleTables instead
|
||||
*/
|
||||
public function getExampleTable()
|
||||
{
|
||||
$table = [];
|
||||
foreach ($this->tables[0]->getTable() as $k => $v) {
|
||||
$table[$k] = $v;
|
||||
}
|
||||
|
||||
/** @var ExampleTableNode $exampleTableNode */
|
||||
$exampleTableNode = new ExampleTableNode($table, $this->tables[0]->getKeyword());
|
||||
$tableCount = count($this->tables);
|
||||
for ($i = 1; $i < $tableCount; ++$i) {
|
||||
$exampleTableNode->mergeRowsFromTable($this->tables[$i]);
|
||||
}
|
||||
|
||||
return $exampleTableNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of examples for the outline.
|
||||
*
|
||||
* @return ExampleNode[]
|
||||
*/
|
||||
public function getExamples()
|
||||
{
|
||||
return $this->examples ??= $this->createExamples();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns examples tables array for the outline.
|
||||
*
|
||||
* @return array<array-key, ExampleTableNode>
|
||||
*/
|
||||
public function getExampleTables()
|
||||
{
|
||||
return $this->tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns outline keyword.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyword()
|
||||
{
|
||||
return $this->keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns outline declaration line number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this outline, but with a different table.
|
||||
*
|
||||
* @param array<array-key, ExampleTableNode> $exampleTables
|
||||
*/
|
||||
public function withTables(array $exampleTables): self
|
||||
{
|
||||
return new self(
|
||||
$this->title,
|
||||
$this->tags,
|
||||
$this->steps,
|
||||
$exampleTables,
|
||||
$this->keyword,
|
||||
$this->line,
|
||||
$this->description,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates examples for this outline using examples table.
|
||||
*
|
||||
* @return ExampleNode[]
|
||||
*/
|
||||
protected function createExamples()
|
||||
{
|
||||
$examples = [];
|
||||
|
||||
foreach ($this->getExampleTables() as $exampleTable) {
|
||||
foreach ($exampleTable->getColumnsHash() as $rowNum => $row) {
|
||||
$examples[] = new ExampleNode(
|
||||
$exampleTable->getRowAsString($rowNum + 1),
|
||||
array_merge($this->tags, $exampleTable->getTags()),
|
||||
$this->getSteps(),
|
||||
$row,
|
||||
$exampleTable->getRowLine($rowNum + 1),
|
||||
$this->getTitle(),
|
||||
$rowNum + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $examples;
|
||||
}
|
||||
}
|
||||
83
vendor/behat/gherkin/src/Node/PyStringNode.php
vendored
83
vendor/behat/gherkin/src/Node/PyStringNode.php
vendored
@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* Represents Gherkin PyString argument.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*/
|
||||
class PyStringNode implements Stringable, ArgumentInterface
|
||||
{
|
||||
/**
|
||||
* @param list<string> $strings String in form of [$stringLine]
|
||||
* @param int $line Line number where string been started
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $strings,
|
||||
private readonly int $line,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType()
|
||||
{
|
||||
return 'PyString';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns entire PyString lines set.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getStrings()
|
||||
{
|
||||
return $this->strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns raw string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRaw()
|
||||
{
|
||||
return implode("\n", $this->strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts PyString into string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns line number at which PyString was started.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Gherkin scenario interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface ScenarioInterface extends ScenarioLikeInterface, TaggedNodeInterface
|
||||
{
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Gherkin scenario-like interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface ScenarioLikeInterface extends KeywordNodeInterface, StepContainerInterface
|
||||
{
|
||||
}
|
||||
115
vendor/behat/gherkin/src/Node/ScenarioNode.php
vendored
115
vendor/behat/gherkin/src/Node/ScenarioNode.php
vendored
@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Represents Gherkin Scenario.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*/
|
||||
class ScenarioNode implements ScenarioInterface, NamedScenarioInterface, DescribableNodeInterface
|
||||
{
|
||||
use TaggedNodeTrait;
|
||||
|
||||
/**
|
||||
* @param StepNode[] $steps
|
||||
* @param list<string> $tags
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ?string $title,
|
||||
private readonly array $tags,
|
||||
private readonly array $steps,
|
||||
private readonly string $keyword,
|
||||
private readonly int $line,
|
||||
private readonly ?string $description = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node type string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType()
|
||||
{
|
||||
return 'Scenario';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns scenario title.
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @deprecated you should use {@see self::getName()} instead as this method will be removed in the next
|
||||
* major version
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getTags()
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if scenario has steps.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSteps()
|
||||
{
|
||||
return count($this->steps) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns scenario steps.
|
||||
*
|
||||
* @return StepNode[]
|
||||
*/
|
||||
public function getSteps()
|
||||
{
|
||||
return $this->steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns scenario keyword.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyword()
|
||||
{
|
||||
return $this->keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns scenario declaration line number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Gherkin step container interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface StepContainerInterface extends NodeInterface
|
||||
{
|
||||
/**
|
||||
* Checks if container has steps.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSteps();
|
||||
|
||||
/**
|
||||
* Returns container steps.
|
||||
*
|
||||
* @return list<StepNode>
|
||||
*/
|
||||
public function getSteps();
|
||||
}
|
||||
129
vendor/behat/gherkin/src/Node/StepNode.php
vendored
129
vendor/behat/gherkin/src/Node/StepNode.php
vendored
@ -1,129 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
use Behat\Gherkin\Exception\NodeException;
|
||||
|
||||
/**
|
||||
* Represents Gherkin Step.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*/
|
||||
class StepNode implements NodeInterface
|
||||
{
|
||||
private readonly string $keywordType;
|
||||
|
||||
/**
|
||||
* @param ArgumentInterface[] $arguments
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $keyword,
|
||||
private readonly string $text,
|
||||
private readonly array $arguments,
|
||||
private readonly int $line,
|
||||
?string $keywordType = null,
|
||||
) {
|
||||
if (count($arguments) > 1) {
|
||||
throw new NodeException(sprintf(
|
||||
'Steps could have only one argument, but `%s %s` have %d.',
|
||||
$keyword,
|
||||
$text,
|
||||
count($arguments)
|
||||
));
|
||||
}
|
||||
|
||||
$this->keywordType = $keywordType ?: 'Given';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node type string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType()
|
||||
{
|
||||
return 'Step';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns step keyword in provided language (Given, When, Then, etc.).
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @deprecated use getKeyword() instead
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->getKeyword();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns step keyword in provided language (Given, When, Then, etc.).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyword()
|
||||
{
|
||||
return $this->keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns step type keyword (Given, When, Then, etc.).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeywordType()
|
||||
{
|
||||
return $this->keywordType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns step text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getText()
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if step has arguments.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasArguments()
|
||||
{
|
||||
return (bool) count($this->arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns step arguments.
|
||||
*
|
||||
* @return ArgumentInterface[]
|
||||
*/
|
||||
public function getArguments()
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns step declaration line number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
}
|
||||
378
vendor/behat/gherkin/src/Node/TableNode.php
vendored
378
vendor/behat/gherkin/src/Node/TableNode.php
vendored
@ -1,378 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
use ArrayIterator;
|
||||
use Behat\Gherkin\Exception\NodeException;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use ReturnTypeWillChange;
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* Represents Gherkin Table argument.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @template-implements IteratorAggregate<int, array<string, string>>
|
||||
*/
|
||||
class TableNode implements Stringable, ArgumentInterface, IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var array<array-key, int>
|
||||
*/
|
||||
private array $maxLineLength = [];
|
||||
|
||||
/**
|
||||
* Initializes table.
|
||||
*
|
||||
* @param array<int, list<string>> $table Table in form of [$rowLineNumber => [$val1, $val2, $val3]]
|
||||
*
|
||||
* @throws NodeException If the given table is invalid
|
||||
*/
|
||||
public function __construct(
|
||||
private array $table,
|
||||
) {
|
||||
$columnCount = null;
|
||||
|
||||
foreach ($this->getRows() as $rowIndex => $row) {
|
||||
if (!is_array($row)) {
|
||||
throw new NodeException(sprintf(
|
||||
"Table row '%s' is expected to be array, got %s",
|
||||
$rowIndex,
|
||||
gettype($row)
|
||||
));
|
||||
}
|
||||
|
||||
if ($columnCount === null) {
|
||||
$columnCount = count($row);
|
||||
}
|
||||
|
||||
if (count($row) !== $columnCount) {
|
||||
throw new NodeException(sprintf(
|
||||
"Table row '%s' is expected to have %s columns, got %s",
|
||||
$rowIndex,
|
||||
$columnCount,
|
||||
count($row)
|
||||
));
|
||||
}
|
||||
|
||||
foreach ($row as $columnIndex => $cellValue) {
|
||||
if (!isset($this->maxLineLength[$columnIndex])) {
|
||||
$this->maxLineLength[$columnIndex] = 0;
|
||||
}
|
||||
|
||||
if (!is_scalar($cellValue)) {
|
||||
throw new NodeException(sprintf(
|
||||
"Table cell at row '%s', column '%s' is expected to be scalar, got %s",
|
||||
$rowIndex,
|
||||
$columnIndex,
|
||||
get_debug_type($cellValue)
|
||||
));
|
||||
}
|
||||
|
||||
$this->maxLineLength[$columnIndex] = max($this->maxLineLength[$columnIndex], mb_strlen($cellValue, 'utf8'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table from a given list.
|
||||
*
|
||||
* @param array<int, string> $list One-dimensional array
|
||||
*
|
||||
* @return TableNode
|
||||
*
|
||||
* @throws NodeException If the given list is not a one-dimensional array
|
||||
*/
|
||||
public static function fromList(array $list)
|
||||
{
|
||||
if (count($list) !== count($list, COUNT_RECURSIVE)) {
|
||||
throw new NodeException('List is not a one-dimensional array.');
|
||||
}
|
||||
|
||||
$table = array_map(fn ($item) => [$item], $list);
|
||||
|
||||
return new self($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns node type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNodeType()
|
||||
{
|
||||
return 'Table';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns table hash, formed by columns (ColumnsHash).
|
||||
*
|
||||
* @return list<array<string, string>>
|
||||
*/
|
||||
public function getHash()
|
||||
{
|
||||
return $this->getColumnsHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns table hash, formed by columns.
|
||||
*
|
||||
* @return list<array<string, string>>
|
||||
*/
|
||||
public function getColumnsHash()
|
||||
{
|
||||
$rows = $this->getRows();
|
||||
$keys = array_shift($rows);
|
||||
|
||||
$hash = [];
|
||||
foreach ($rows as $row) {
|
||||
assert($keys !== null); // If there is no first row due to an empty table, we won't enter this loop either.
|
||||
$hash[] = array_combine($keys, $row);
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns table hash, formed by rows.
|
||||
*
|
||||
* @return array<string, string|list<string>>
|
||||
*/
|
||||
public function getRowsHash()
|
||||
{
|
||||
$hash = [];
|
||||
|
||||
foreach ($this->getRows() as $row) {
|
||||
$hash[array_shift($row)] = count($row) === 1 ? $row[0] : $row;
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns numerated table lines.
|
||||
* Line numbers are keys, lines are values.
|
||||
*
|
||||
* @return array<int, list<string>>
|
||||
*/
|
||||
public function getTable()
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns table rows.
|
||||
*
|
||||
* @return list<list<string>>
|
||||
*/
|
||||
public function getRows()
|
||||
{
|
||||
return array_values($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns table definition lines.
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
public function getLines()
|
||||
{
|
||||
return array_keys($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns specific row in a table.
|
||||
*
|
||||
* @param int $index Row number
|
||||
*
|
||||
* @return list<string>
|
||||
*
|
||||
* @throws NodeException If row with specified index does not exist
|
||||
*/
|
||||
public function getRow(int $index)
|
||||
{
|
||||
$rows = $this->getRows();
|
||||
|
||||
if (!isset($rows[$index])) {
|
||||
throw new NodeException(sprintf('Rows #%d does not exist in table.', $index));
|
||||
}
|
||||
|
||||
return $rows[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns specific column in a table.
|
||||
*
|
||||
* @param int $index Column number
|
||||
*
|
||||
* @return list<string>
|
||||
*
|
||||
* @throws NodeException If column with specified index does not exist
|
||||
*/
|
||||
public function getColumn(int $index)
|
||||
{
|
||||
if ($index >= count($this->getRow(0))) {
|
||||
throw new NodeException(sprintf('Column #%d does not exist in table.', $index));
|
||||
}
|
||||
|
||||
$rows = $this->getRows();
|
||||
$column = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$column[] = $row[$index];
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns line number at which specific row was defined.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws NodeException If row with specified index does not exist
|
||||
*/
|
||||
public function getRowLine(int $index)
|
||||
{
|
||||
$lines = array_keys($this->table);
|
||||
|
||||
if (!isset($lines[$index])) {
|
||||
throw new NodeException(sprintf('Rows #%d does not exist in table.', $index));
|
||||
}
|
||||
|
||||
return $lines[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts row into delimited string.
|
||||
*
|
||||
* @param int $rowNum Row number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRowAsString(int $rowNum)
|
||||
{
|
||||
$values = [];
|
||||
foreach ($this->getRow($rowNum) as $column => $value) {
|
||||
$values[] = $this->padRight(' ' . $value . ' ', $this->maxLineLength[$column] + 2);
|
||||
}
|
||||
|
||||
return sprintf('|%s|', implode('|', $values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts row into delimited string.
|
||||
*
|
||||
* @param int $rowNum Row number
|
||||
* @param callable(string, int): string $wrapper Wrapper function
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRowAsStringWithWrappedValues(int $rowNum, callable $wrapper)
|
||||
{
|
||||
$values = [];
|
||||
foreach ($this->getRow($rowNum) as $column => $value) {
|
||||
$value = $this->padRight(' ' . $value . ' ', $this->maxLineLength[$column] + 2);
|
||||
|
||||
$values[] = call_user_func($wrapper, $value, $column);
|
||||
}
|
||||
|
||||
return sprintf('|%s|', implode('|', $values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts entire table into string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTableAsString()
|
||||
{
|
||||
$lines = [];
|
||||
$rowCount = count($this->getRows());
|
||||
for ($i = 0; $i < $rowCount; ++$i) {
|
||||
$lines[] = $this->getRowAsString($i);
|
||||
}
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns line number at which table was started.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
return $this->getRowLine(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts table into string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getTableAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a hash iterator.
|
||||
*
|
||||
* @return Iterator
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->getHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains and adds rows from another table to the current table.
|
||||
* The second table should have the same structure as the current one.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @deprecated remove together with OutlineNode::getExampleTable
|
||||
*/
|
||||
public function mergeRowsFromTable(TableNode $node)
|
||||
{
|
||||
// check structure
|
||||
if ($this->getRow(0) !== $node->getRow(0)) {
|
||||
throw new NodeException('Tables have different structure. Cannot merge one into another');
|
||||
}
|
||||
|
||||
$firstLine = $node->getLine();
|
||||
foreach ($node->getTable() as $line => $value) {
|
||||
if ($line === $firstLine) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->table[$line] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads string right.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function padRight(string $text, int $length)
|
||||
{
|
||||
while ($length > mb_strlen($text, 'utf8')) {
|
||||
$text .= ' ';
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* Gherkin tagged node interface.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface TaggedNodeInterface extends NodeInterface
|
||||
{
|
||||
/**
|
||||
* Checks if node is tagged with tag.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTag(string $tag);
|
||||
|
||||
/**
|
||||
* Checks if node has tags (including any inherited tags e.g. from feature).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTags();
|
||||
|
||||
/**
|
||||
* Returns node tags (including any inherited tags e.g. from feature).
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getTags();
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin\Node;
|
||||
|
||||
/**
|
||||
* This trait partially implements {@see TaggedNodeInterface}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait TaggedNodeTrait
|
||||
{
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
abstract public function getTags();
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTag(string $tag)
|
||||
{
|
||||
return in_array($tag, $this->getTags(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTags()
|
||||
{
|
||||
return $this->getTags() !== [];
|
||||
}
|
||||
}
|
||||
760
vendor/behat/gherkin/src/Parser.php
vendored
760
vendor/behat/gherkin/src/Parser.php
vendored
@ -1,760 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin;
|
||||
|
||||
use Behat\Gherkin\Exception\FilesystemException;
|
||||
use Behat\Gherkin\Exception\InvalidTagContentException;
|
||||
use Behat\Gherkin\Exception\LexerException;
|
||||
use Behat\Gherkin\Exception\NodeException;
|
||||
use Behat\Gherkin\Exception\ParserException;
|
||||
use Behat\Gherkin\Exception\UnexpectedParserNodeException;
|
||||
use Behat\Gherkin\Exception\UnexpectedTaggedNodeException;
|
||||
use Behat\Gherkin\Node\BackgroundNode;
|
||||
use Behat\Gherkin\Node\ExampleTableNode;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\OutlineNode;
|
||||
use Behat\Gherkin\Node\PyStringNode;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
use Behat\Gherkin\Node\ScenarioNode;
|
||||
use Behat\Gherkin\Node\StepNode;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* Gherkin parser.
|
||||
*
|
||||
* ```
|
||||
* $lexer = new Behat\Gherkin\Lexer($keywords);
|
||||
* $parser = new Behat\Gherkin\Parser($lexer);
|
||||
* $featuresArray = $parser->parse('/path/to/feature.feature');
|
||||
* ```
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* @final since 4.15.0
|
||||
*
|
||||
* @phpstan-import-type TTokenType from Lexer
|
||||
* @phpstan-import-type TToken from Lexer
|
||||
* @phpstan-import-type TNullValueToken from Lexer
|
||||
* @phpstan-import-type TStringValueToken from Lexer
|
||||
* @phpstan-import-type TTagToken from Lexer
|
||||
* @phpstan-import-type TStepToken from Lexer
|
||||
* @phpstan-import-type TTitleToken from Lexer
|
||||
* @phpstan-import-type TTableRowToken from Lexer
|
||||
* @phpstan-import-type TTitleKeyword from Lexer
|
||||
*
|
||||
* @phpstan-type TParsedExpressionResult FeatureNode|BackgroundNode|ScenarioNode|OutlineNode|ExampleTableNode|TableNode|PyStringNode|StepNode|string
|
||||
*/
|
||||
class Parser implements ParserInterface
|
||||
{
|
||||
private string $input;
|
||||
private ?string $file = null;
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $tags = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly Lexer $lexer,
|
||||
private GherkinCompatibilityMode $compatibilityMode = GherkinCompatibilityMode::LEGACY,
|
||||
) {
|
||||
}
|
||||
|
||||
public function setGherkinCompatibilityMode(GherkinCompatibilityMode $mode): void
|
||||
{
|
||||
$this->compatibilityMode = $mode;
|
||||
}
|
||||
|
||||
public function parse(string $input, ?string $file = null)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->file = $file;
|
||||
$this->tags = [];
|
||||
$this->lexer->setCompatibilityMode($this->compatibilityMode);
|
||||
|
||||
try {
|
||||
$this->lexer->analyse($this->input);
|
||||
} catch (LexerException $e) {
|
||||
throw new ParserException(
|
||||
sprintf('Lexer exception "%s" thrown for file %s', $e->getMessage(), $file),
|
||||
0,
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
$feature = null;
|
||||
while ($this->predictTokenType() !== 'EOS') {
|
||||
$node = $this->parseExpression();
|
||||
|
||||
if ($node === "\n" || $node === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$feature && $node instanceof FeatureNode) {
|
||||
$feature = $node;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new UnexpectedParserNodeException('Feature', $node, $this->file);
|
||||
}
|
||||
|
||||
return $feature;
|
||||
}
|
||||
|
||||
public function parseFile(string $file): ?FeatureNode
|
||||
{
|
||||
try {
|
||||
return $this->parse(Filesystem::readFile($file), $file);
|
||||
} catch (FilesystemException $ex) {
|
||||
throw new ParserException("Cannot parse file: {$ex->getMessage()}", previous: $ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next token if it's type equals to expected.
|
||||
*
|
||||
* @phpstan-param TTokenType $type
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @phpstan-return (
|
||||
* $type is 'TableRow'
|
||||
* ? TTableRowToken
|
||||
* : ($type is 'Tag'
|
||||
* ? TTagToken
|
||||
* : ($type is 'Step'
|
||||
* ? TStepToken
|
||||
* : ($type is 'Text'
|
||||
* ? TStringValueToken
|
||||
* : ($type is TTitleKeyword
|
||||
* ? TTitleToken
|
||||
* : TNullValueToken|TStringValueToken
|
||||
* )))))
|
||||
*
|
||||
* @throws ParserException
|
||||
*/
|
||||
protected function expectTokenType(string $type)
|
||||
{
|
||||
if ($this->predictTokenType() === $type) {
|
||||
return $this->lexer->getAdvancedToken();
|
||||
}
|
||||
|
||||
$token = $this->lexer->predictToken();
|
||||
|
||||
throw new ParserException(sprintf(
|
||||
'Expected %s token, but got %s on line: %d%s',
|
||||
$type,
|
||||
$this->predictTokenType(),
|
||||
$token['line'],
|
||||
$this->file ? ' in file: ' . $this->file : ''
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next token if it's type equals to expected.
|
||||
*
|
||||
* @param string $type Token type
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @phpstan-return TToken|null
|
||||
*/
|
||||
protected function acceptTokenType(string $type)
|
||||
{
|
||||
if ($type !== $this->predictTokenType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->lexer->getAdvancedToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next token type without real input reading (prediction).
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @phpstan-return TTokenType
|
||||
*/
|
||||
protected function predictTokenType()
|
||||
{
|
||||
$token = $this->lexer->predictToken();
|
||||
|
||||
return $token['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses current expression & returns Node.
|
||||
*
|
||||
* @phpstan-return TParsedExpressionResult
|
||||
*
|
||||
* @throws ParserException
|
||||
*/
|
||||
protected function parseExpression()
|
||||
{
|
||||
$type = $this->predictTokenType();
|
||||
|
||||
while ($type === 'Comment') {
|
||||
$this->expectTokenType('Comment');
|
||||
|
||||
$type = $this->predictTokenType();
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
'Feature' => $this->parseFeature(),
|
||||
'Background' => $this->parseBackground(),
|
||||
'Scenario' => $this->parseScenario(),
|
||||
'Outline' => $this->parseOutline(),
|
||||
'Examples' => $this->parseExamples(),
|
||||
'TableRow' => $this->parseTable(),
|
||||
'PyStringOp' => $this->parsePyString(),
|
||||
'Step' => $this->parseStep(),
|
||||
'Text' => $this->parseText(),
|
||||
'Newline' => $this->parseNewline(),
|
||||
'Tag' => $this->parseTags(),
|
||||
'Language' => $this->parseLanguage(),
|
||||
'EOS' => '',
|
||||
default => throw new ParserException(sprintf('Unknown token type: %s', $type)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses feature token & returns it's node.
|
||||
*
|
||||
* @return FeatureNode
|
||||
*
|
||||
* @throws ParserException
|
||||
*/
|
||||
protected function parseFeature()
|
||||
{
|
||||
$token = $this->expectTokenType('Feature');
|
||||
|
||||
['title' => $title, 'description' => $description] = $this->parseTitleAndDescription($token);
|
||||
$tags = $this->popTags();
|
||||
$background = null;
|
||||
$scenarios = [];
|
||||
$keyword = $token['keyword'];
|
||||
$language = $this->lexer->getLanguage();
|
||||
$file = $this->file;
|
||||
$line = $token['line'];
|
||||
|
||||
// Parse description, background, scenarios & outlines
|
||||
while ($this->predictTokenType() !== 'EOS') {
|
||||
$node = $this->parseExpression();
|
||||
|
||||
if ($node === "\n") {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isBackgroundAllowed = ($background === null && $scenarios === []);
|
||||
|
||||
if ($isBackgroundAllowed && $node instanceof BackgroundNode) {
|
||||
$background = $node;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node instanceof ScenarioInterface) {
|
||||
$scenarios[] = $node;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new UnexpectedParserNodeException(
|
||||
match ($isBackgroundAllowed) {
|
||||
true => 'Background, Scenario or Outline',
|
||||
false => 'Scenario or Outline',
|
||||
},
|
||||
$node,
|
||||
$this->file,
|
||||
);
|
||||
}
|
||||
|
||||
return new FeatureNode(
|
||||
$title,
|
||||
$description,
|
||||
$tags,
|
||||
$background,
|
||||
$scenarios,
|
||||
$keyword,
|
||||
$language,
|
||||
$file,
|
||||
$line
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses background token & returns it's node.
|
||||
*
|
||||
* @return BackgroundNode
|
||||
*
|
||||
* @throws ParserException
|
||||
*/
|
||||
protected function parseBackground()
|
||||
{
|
||||
$token = $this->expectTokenType('Background');
|
||||
|
||||
$keyword = $token['keyword'];
|
||||
$line = $token['line'];
|
||||
['title' => $title, 'description' => $description] = $this->parseTitleAndDescription($token);
|
||||
|
||||
if (count($this->popTags()) !== 0) {
|
||||
// Should not be possible to happen, parseTags should have already picked this up.
|
||||
throw new UnexpectedTaggedNodeException($token, $this->file);
|
||||
}
|
||||
|
||||
// Parse description and steps
|
||||
$steps = [];
|
||||
$allowedTokenTypes = ['Step', 'Newline', 'Text', 'Comment'];
|
||||
while (in_array($this->predictTokenType(), $allowedTokenTypes)) {
|
||||
// NB: Technically, we do not support `Text` inside this loop. However, there is no situation where `Text`
|
||||
// can be a direct child or immediately following a Scenario. Therefore, we consume it here as the most
|
||||
// logical context for throwing an UnexpectedParserNodeException.
|
||||
|
||||
$node = $this->parseExpression();
|
||||
|
||||
if ($node instanceof StepNode) {
|
||||
$steps[] = $this->normalizeStepNodeKeywordType($node, $steps);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node === "\n") {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new UnexpectedParserNodeException('Step', $node, $this->file);
|
||||
}
|
||||
|
||||
return new BackgroundNode($title, $steps, $keyword, $line, $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses scenario token & returns it's node.
|
||||
*
|
||||
* @return OutlineNode|ScenarioNode
|
||||
*
|
||||
* @throws ParserException
|
||||
*/
|
||||
protected function parseScenario()
|
||||
{
|
||||
return $this->parseScenarioOrOutlineBody($this->expectTokenType('Scenario'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses scenario outline token & returns it's node.
|
||||
*
|
||||
* @return OutlineNode|ScenarioNode
|
||||
*
|
||||
* @throws ParserException
|
||||
*/
|
||||
protected function parseOutline()
|
||||
{
|
||||
return $this->parseScenarioOrOutlineBody($this->expectTokenType('Outline'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param TTitleToken $token
|
||||
*/
|
||||
private function parseScenarioOrOutlineBody(array $token): OutlineNode|ScenarioNode
|
||||
{
|
||||
['title' => $title, 'description' => $description] = $this->parseTitleAndDescription($token);
|
||||
$tags = $this->popTags();
|
||||
$keyword = $token['keyword'];
|
||||
|
||||
/** @var list<ExampleTableNode> $examples */
|
||||
$examples = [];
|
||||
$line = $token['line'];
|
||||
$steps = [];
|
||||
|
||||
while (in_array($nextTokenType = $this->predictTokenType(), ['Step', 'Examples', 'Newline', 'Text', 'Comment', 'Tag'])) {
|
||||
// NB: Technically, we do not support `Text` inside this loop. However, there is no situation where `Text`
|
||||
// can be a direct child or immediately following a Scenario. Therefore, we consume it here as the most
|
||||
// logical context for throwing an UnexpectedParserNodeException.
|
||||
|
||||
if ($nextTokenType === 'Comment') {
|
||||
$this->lexer->skipPredictedToken();
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($nextTokenType === 'Tag') {
|
||||
// The only thing inside a Scenario / Scenario Outline that can be tagged is an Examples table
|
||||
// Scan on to see what the tags are attached to - if it's not Examples then we must have reached the
|
||||
// end of this scenario and be about to start a new one.
|
||||
if ($this->validateAndGetNextTaggedNodeType() !== 'Examples') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$node = $this->parseExpression();
|
||||
|
||||
if ($node === "\n") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($examples === [] && $node instanceof StepNode) {
|
||||
// Steps are only allowed before the first Examples table (if any)
|
||||
$steps[] = $this->normalizeStepNodeKeywordType($node, $steps);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node instanceof ExampleTableNode) {
|
||||
// NB: It is valid to have a Scenario with Examples: but no Steps
|
||||
// It is also valid to have an Examples: with no table rows (this produces no actual examples)
|
||||
$examples[] = $node;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new UnexpectedParserNodeException(
|
||||
match ($examples) {
|
||||
[] => 'Step, Examples table, or end of Scenario',
|
||||
default => 'Examples table or end of Scenario',
|
||||
},
|
||||
$node,
|
||||
$this->file,
|
||||
);
|
||||
}
|
||||
|
||||
if ($examples !== []) {
|
||||
return new OutlineNode($title, $tags, $steps, $examples, $keyword, $line, $description);
|
||||
}
|
||||
|
||||
return new ScenarioNode($title, $tags, $steps, $keyword, $line, $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek ahead to find the node that the current tags belong to.
|
||||
*
|
||||
* @throws UnexpectedTaggedNodeException if there is not a taggable node
|
||||
*/
|
||||
private function validateAndGetNextTaggedNodeType(): string
|
||||
{
|
||||
$deferred = [];
|
||||
try {
|
||||
while (true) {
|
||||
$deferred[] = $next = $this->lexer->getAdvancedToken();
|
||||
$nextType = $next['type'];
|
||||
|
||||
if (in_array($nextType, ['Tag', 'Comment', 'Newline'], true)) {
|
||||
// These are the only node types allowed between tag node(s) and the node they are tagging
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($nextType, ['Feature', 'Examples', 'Scenario', 'Outline'], true)) {
|
||||
// These are the only taggable node types
|
||||
return $nextType;
|
||||
}
|
||||
|
||||
throw new UnexpectedTaggedNodeException($next, $this->file);
|
||||
}
|
||||
} finally {
|
||||
// Rewind the lexer back to where it was when we started scanning ahead
|
||||
foreach ($deferred as $token) {
|
||||
$this->lexer->deferToken($token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses step token & returns it's node.
|
||||
*
|
||||
* @return StepNode
|
||||
*/
|
||||
protected function parseStep()
|
||||
{
|
||||
$token = $this->expectTokenType('Step');
|
||||
|
||||
$arguments = [];
|
||||
while (in_array($predicted = $this->predictTokenType(), ['PyStringOp', 'TableRow', 'Newline', 'Comment'])) {
|
||||
if ($predicted === 'Comment' || $predicted === 'Newline') {
|
||||
$this->acceptTokenType($predicted);
|
||||
continue;
|
||||
}
|
||||
|
||||
$node = $this->parseExpression();
|
||||
|
||||
if ($node instanceof PyStringNode || $node instanceof TableNode) {
|
||||
$arguments[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
return new StepNode($token['value'], trim($token['text']), $arguments, $token['line'], $token['keyword_type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses examples table node.
|
||||
*
|
||||
* @return ExampleTableNode
|
||||
*/
|
||||
protected function parseExamples()
|
||||
{
|
||||
$token = $this->expectTokenType('Examples');
|
||||
$keyword = $token['keyword'];
|
||||
$tags = empty($this->tags) ? [] : $this->popTags();
|
||||
['title' => $title, 'description' => $description] = $this->parseTitleAndDescription($token);
|
||||
$table = $this->parseTableRows();
|
||||
|
||||
try {
|
||||
return new ExampleTableNode($table, $keyword, $tags, $title, $description);
|
||||
} catch (NodeException $e) {
|
||||
$this->rethrowNodeException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses table token & returns it's node.
|
||||
*
|
||||
* @return TableNode
|
||||
*/
|
||||
protected function parseTable()
|
||||
{
|
||||
$table = $this->parseTableRows();
|
||||
|
||||
try {
|
||||
return new TableNode($table);
|
||||
} catch (NodeException $e) {
|
||||
$this->rethrowNodeException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses PyString token & returns it's node.
|
||||
*
|
||||
* @return PyStringNode
|
||||
*/
|
||||
protected function parsePyString()
|
||||
{
|
||||
$token = $this->expectTokenType('PyStringOp');
|
||||
|
||||
$line = $token['line'];
|
||||
|
||||
$strings = [];
|
||||
while ('PyStringOp' !== ($predicted = $this->predictTokenType()) && $predicted === 'Text') {
|
||||
$strings[] = $this->expectTokenType('Text')['value'];
|
||||
}
|
||||
|
||||
$this->expectTokenType('PyStringOp');
|
||||
|
||||
return new PyStringNode($strings, $line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses tags.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseTags()
|
||||
{
|
||||
$token = $this->expectTokenType('Tag');
|
||||
|
||||
// Validate that the tags are followed by a node that can be tagged
|
||||
$this->validateAndGetNextTaggedNodeType();
|
||||
|
||||
$this->guardTags($token['tags']);
|
||||
|
||||
$this->tags = array_merge($this->tags, $token['tags']);
|
||||
|
||||
return "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current set of tags and clears tag buffer.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function popTags()
|
||||
{
|
||||
$tags = $this->tags;
|
||||
$this->tags = [];
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the tags fit the required format.
|
||||
*
|
||||
* @param array<array-key, string> $tags
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function guardTags(array $tags)
|
||||
{
|
||||
foreach ($tags as $tag) {
|
||||
if (preg_match('/\s/', $tag)) {
|
||||
if ($this->compatibilityMode->shouldThrowOnWhitespaceInTag()) {
|
||||
throw new InvalidTagContentException($tag, $this->file);
|
||||
}
|
||||
|
||||
trigger_error(
|
||||
sprintf('Whitespace in tags is deprecated, found "%s" in %s', $tag, $this->file ?? 'unknown file'),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses next text line & returns it.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseText()
|
||||
{
|
||||
$token = $this->expectTokenType('Text');
|
||||
\assert(\is_string($token['value']));
|
||||
|
||||
return $token['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses next newline & returns \n.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseNewline()
|
||||
{
|
||||
$this->expectTokenType('Newline');
|
||||
|
||||
return "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips over language tags (they are handled inside the Lexer).
|
||||
*
|
||||
* @phpstan-return TParsedExpressionResult
|
||||
*
|
||||
* @throws ParserException
|
||||
*
|
||||
* @deprecated language tags are handled inside the Lexer, they skipped over (like any other comment) in the Parser
|
||||
*/
|
||||
protected function parseLanguage()
|
||||
{
|
||||
$this->expectTokenType('Language');
|
||||
|
||||
return $this->parseExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the rows of a table.
|
||||
*
|
||||
* @return array<int, list<string>>
|
||||
*/
|
||||
private function parseTableRows(): array
|
||||
{
|
||||
$table = [];
|
||||
while (in_array($predicted = $this->predictTokenType(), ['TableRow', 'Newline', 'Comment'])) {
|
||||
if ($predicted === 'Comment' || $predicted === 'Newline') {
|
||||
$this->acceptTokenType($predicted);
|
||||
continue;
|
||||
}
|
||||
|
||||
$token = $this->expectTokenType('TableRow');
|
||||
|
||||
$table[$token['line']] = $token['columns'];
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TTitleToken $keywordToken
|
||||
*
|
||||
* @return array{title:string|null, description:string|null}
|
||||
*/
|
||||
private function parseTitleAndDescription(array $keywordToken): array
|
||||
{
|
||||
$originalTitle = trim($keywordToken['value'] ?? '');
|
||||
$textLines = [];
|
||||
|
||||
while (in_array($predicted = $this->predictTokenType(), ['Newline', 'Text', 'Comment'])) {
|
||||
$token = $this->expectTokenType($predicted);
|
||||
|
||||
if ($token['type'] === 'Comment') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$text = match ($token['type']) {
|
||||
'Newline' => '',
|
||||
'Text' => $token['value'],
|
||||
default => throw new LogicException('Unexpected token type: ' . $token['type']),
|
||||
};
|
||||
|
||||
// The only time we use $token['value'] is if we got a `Text` token.
|
||||
// ->expectTokenType('Text') is tagged as returning a `TStringValueToken`, where 'value' cannot be null
|
||||
// However PHPStan cannot follow the chain through predictTokenType -> expectTokenType -> $token['type']
|
||||
assert($text !== null, 'Text token value should not be null');
|
||||
|
||||
if ($this->compatibilityMode->shouldRemoveDescriptionPadding()) {
|
||||
$text = preg_replace('/^\s{0,' . ($keywordToken['indent'] + 2) . '}|\s*$/', '', $text);
|
||||
}
|
||||
|
||||
$textLines[] = $text;
|
||||
}
|
||||
|
||||
if ($this->compatibilityMode->allowAllNodeDescriptions()) {
|
||||
// Gherkin-compatible format - title is the original keyword value, description is all following lines.
|
||||
// Blank lines between title and description are removed, as are any after the description.
|
||||
return [
|
||||
'title' => $originalTitle ?: null,
|
||||
'description' => trim(implode("\n", $textLines), "\n") ?: null,
|
||||
];
|
||||
}
|
||||
|
||||
if ($keywordToken['type'] === 'Feature') {
|
||||
// Legacy format always supported a title & description for a Feature
|
||||
// But kept blank lines between title and description as the start of the description.
|
||||
return [
|
||||
'title' => $originalTitle ?: null,
|
||||
'description' => rtrim(implode("\n", $textLines), "\n") ?: null,
|
||||
];
|
||||
}
|
||||
|
||||
// Legacy format for nodes without description support - the full text block (title & description) is parsed
|
||||
// as the title.
|
||||
array_unshift($textLines, $originalTitle);
|
||||
|
||||
return [
|
||||
'title' => rtrim(implode("\n", $textLines)) ?: null,
|
||||
'description' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes step node type for types But, And to type of previous step if it exists else sets to Given.
|
||||
*
|
||||
* @param StepNode[] $steps
|
||||
*/
|
||||
private function normalizeStepNodeKeywordType(StepNode $node, array $steps = []): StepNode
|
||||
{
|
||||
if (!in_array($node->getKeywordType(), ['And', 'But'])) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
if ($prev = end($steps)) {
|
||||
$keywordType = $prev->getKeywordType();
|
||||
} else {
|
||||
$keywordType = 'Given';
|
||||
}
|
||||
|
||||
return new StepNode(
|
||||
$node->getKeyword(),
|
||||
$node->getText(),
|
||||
$node->getArguments(),
|
||||
$node->getLine(),
|
||||
$keywordType
|
||||
);
|
||||
}
|
||||
|
||||
private function rethrowNodeException(NodeException $e): never
|
||||
{
|
||||
throw new ParserException(
|
||||
$e->getMessage() . ($this->file ? ' in file ' . $this->file : ''),
|
||||
0,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
36
vendor/behat/gherkin/src/ParserInterface.php
vendored
36
vendor/behat/gherkin/src/ParserInterface.php
vendored
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Behat Gherkin Parser.
|
||||
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Behat\Gherkin;
|
||||
|
||||
use Behat\Gherkin\Exception\ParserException;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
|
||||
interface ParserInterface
|
||||
{
|
||||
/**
|
||||
* Parses a Gherkin document string and returns feature (or null when none found).
|
||||
*
|
||||
* @param string $input Gherkin string document
|
||||
* @param string|null $file File name
|
||||
*
|
||||
* @return FeatureNode|null
|
||||
*
|
||||
* @throws ParserException
|
||||
*/
|
||||
public function parse(string $input, ?string $file = null);
|
||||
|
||||
/**
|
||||
* Parses a Gherkin file and returns feature (or null when none found).
|
||||
*
|
||||
* @throws ParserException
|
||||
*/
|
||||
public function parseFile(string $file): ?FeatureNode;
|
||||
}
|
||||
119
vendor/bin/codecept
vendored
119
vendor/bin/codecept
vendored
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../codeception/codeception/codecept)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/codeception/codeception/codecept');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/codeception/codeception/codecept';
|
||||
119
vendor/bin/markdown
vendored
119
vendor/bin/markdown
vendored
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../cebe/markdown/bin/markdown)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/cebe/markdown/bin/markdown');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/cebe/markdown/bin/markdown';
|
||||
119
vendor/bin/php-parse
vendored
119
vendor/bin/php-parse
vendored
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../nikic/php-parser/bin/php-parse)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse';
|
||||
122
vendor/bin/phpunit
vendored
122
vendor/bin/phpunit
vendored
@ -1,122 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../phpunit/phpunit/phpunit)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
$GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] = $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'] = array(realpath(__DIR__ . '/..'.'/phpunit/phpunit/phpunit'));
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = 'phpvfscomposer://'.$this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
$data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data);
|
||||
$data = str_replace('__FILE__', var_export($this->realpath, true), $data);
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';
|
||||
119
vendor/bin/psysh
vendored
119
vendor/bin/psysh
vendored
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../psy/psysh/bin/psysh)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/psy/psysh/bin/psysh');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/psy/psysh/bin/psysh';
|
||||
119
vendor/bin/var-dump-server
vendored
119
vendor/bin/var-dump-server
vendored
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../symfony/var-dumper/Resources/bin/var-dump-server)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';
|
||||
119
vendor/bin/yaml-lint
vendored
119
vendor/bin/yaml-lint
vendored
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../symfony/yaml/Resources/bin/yaml-lint)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint';
|
||||
119
vendor/bin/yii
vendored
119
vendor/bin/yii
vendored
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../yiisoft/yii2/yii)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/yiisoft/yii2/yii');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/yiisoft/yii2/yii';
|
||||
6
vendor/cebe/markdown/.gitattributes
vendored
6
vendor/cebe/markdown/.gitattributes
vendored
@ -1,6 +0,0 @@
|
||||
* text=auto
|
||||
|
||||
/.gitattributes
|
||||
/.gitignore
|
||||
/.scrutinizer.yml
|
||||
/.travis.yml
|
||||
4
vendor/cebe/markdown/.gitignore
vendored
4
vendor/cebe/markdown/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/.idea/
|
||||
composer.lock
|
||||
/vendor
|
||||
README.html
|
||||
6
vendor/cebe/markdown/.scrutinizer.yml
vendored
6
vendor/cebe/markdown/.scrutinizer.yml
vendored
@ -1,6 +0,0 @@
|
||||
imports:
|
||||
- php
|
||||
|
||||
tools:
|
||||
external_code_coverage:
|
||||
timeout: 600 # Timeout in seconds.
|
||||
42
vendor/cebe/markdown/.travis.yml
vendored
42
vendor/cebe/markdown/.travis.yml
vendored
@ -1,42 +0,0 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- nightly
|
||||
- hhvm
|
||||
|
||||
# faster builds on new travis setup not using sudo
|
||||
sudo: false
|
||||
|
||||
# travis does not support HHVM on other platforms, choosing trusty
|
||||
dist: trusty
|
||||
|
||||
# cache composer cache
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
# run build against hhvm but allow them to fail
|
||||
# http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
|
||||
install:
|
||||
- composer self-update && composer --version
|
||||
- composer install --prefer-dist
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --verbose --coverage-clover=coverage.clover
|
||||
# test against standard markdown spec
|
||||
# - git clone https://github.com/jgm/stmd && cd stmd && perl runtests.pl spec.txt ../bin/markdown
|
||||
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
||||
104
vendor/cebe/markdown/CHANGELOG.md
vendored
104
vendor/cebe/markdown/CHANGELOG.md
vendored
@ -1,104 +0,0 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
Version 1.2.1 on 26. Mar. 2018
|
||||
------------------------------
|
||||
|
||||
- Improved handling of inline HTML with URL and email tags.
|
||||
- Improved handling of custom syntax with `[[`, references should not use `[` as the first character in the reference name.
|
||||
|
||||
Version 1.2.0 on 14. Mar. 2018
|
||||
------------------------------
|
||||
|
||||
- #50 Do not render empty emphs.
|
||||
- #69 Improve ABSY for tables, make column and row information directly available in absy (@NathanBaulch)
|
||||
- #89 Lists should be separated by a HR (@bieleckim)
|
||||
- #95 Added `TableTrait::composeTable($head, $body)`, for easier overriding of table layout (@maximal, @cebe)
|
||||
- #111 Improve rendering of successive strongs (@wogsland)
|
||||
- #132 Improve detection and rendering of fenced code blocks in lists.
|
||||
- #134 Fix Emph and Strong to allow escaping `*` or `_` inside them.
|
||||
- #135 GithubMarkdown was not parsing inline code when there are square brackets around it.
|
||||
- #151 Fixed table rendering for lines begining with | for GFM (@GenaBitu)
|
||||
- Improved table rendering, allow single column tables.
|
||||
|
||||
Version 1.1.2 on 16. Jul 2017
|
||||
-----------------------------
|
||||
|
||||
- #126 Fixed crash on empty lines that extend a lazy list
|
||||
- #128 Fix table renderer which including default alignment (@tanakahisateru)
|
||||
- #129 Use given encoded URL if decoded URL text looks insecure, e.g. uses broken UTF-8 (@tanakahisateru)
|
||||
- Added a workaround for a [PHP bug](https://bugs.php.net/bug.php?id=45735) which exists in versions `<` 7.0, where `preg_match()` causes a segfault
|
||||
on [catastropic backtracking][] in emph/strong parsing.
|
||||
|
||||
[catastropic backtracking]: http://www.regular-expressions.info/catastrophic.html
|
||||
|
||||
Version 1.1.1 on 14. Sep 2016
|
||||
-----------------------------
|
||||
|
||||
- #112 Fixed parsing for custom self-closing HTML tags
|
||||
- #113 improve extensibility by making `prepareMarkers()` protected and add `parseBlock()` method
|
||||
- #114 better handling of continued inline HTML in paragraphs
|
||||
|
||||
Version 1.1.0 on 06. Mar. 2015
|
||||
------------------------------
|
||||
|
||||
- improve compatibility with github flavored markdown
|
||||
- #64 fixed some rendering issue with emph and strong
|
||||
- #56 trailing and leading spaces in a link are now ignored
|
||||
- fixed various issues with table rendering
|
||||
- #98 Fix PHP fatal error when maximumNestingLevel was reached (@tanakahisateru)
|
||||
- refactored nested and lazy list handling, improved overall list rendering consistency
|
||||
- Lines containing "0" where skipped or considered empty in some cases (@tanakahisateru)
|
||||
- #54 escape characters are now also considered inside of urls
|
||||
|
||||
Version 1.0.1 on 25. Oct. 2014
|
||||
------------------------------
|
||||
|
||||
- Fixed the `bin/markdown` script to work with composer autoloader (c497bada0e15f61873ba6b2e29f4bb8b3ef2a489)
|
||||
- #74 fixed a bug that caused a bunch of broken characters when non-ASCII input was given. Parser now handles UTF-8 input correctly. Other encodings are currently untested, UTF-8 is recommended.
|
||||
|
||||
Version 1.0.0 on 12. Oct. 2014
|
||||
------------------------------
|
||||
|
||||
This is the first stable release of version 1.0 which is incompatible to the 0.9.x branch regarding the internal API which is used when extending the Markdown parser. The external API has no breaking changes. The rendered Markdown however has changed in some edge cases and some rendering issues have been fixed.
|
||||
|
||||
The parser got a bit slower compared to earlier versions but is able to parse Markdown more accurately and uses an abstract syntax tree as the internal representation of the parsed text which allows extensions to work with the parsed Markdown in many ways including rendering as other formats than HTML.
|
||||
|
||||
For more details about the changes see the [release message of 1.0.0-rc](https://github.com/cebe/markdown/releases/tag/1.0.0-rc).
|
||||
|
||||
You can try it out on the website: <http://markdown.cebe.cc/try>
|
||||
|
||||
The parser is now also regsitered on the [Babelmark 2 page](http://johnmacfarlane.net/babelmark2/?normalize=1&text=Hello+**World**!) by [John MacFarlane](http://johnmacfarlane.net/) which you can use to compare Markdown output of different parsers.
|
||||
|
||||
Version 1.0.0-rc on 10. Oct. 2014
|
||||
---------------------------------
|
||||
|
||||
- #21 speed up inline parsing using [strpbrk](http://www.php.net/manual/de/function.strpbrk.php) about 20% speedup compared to parsing before.
|
||||
- #24 CLI script now sends all error output to stderr instead of stdout
|
||||
- #25 Added partial support for the Markdown Extra flavor
|
||||
- #10 GithubMarkdown is now fully supported including tables
|
||||
- #67 All Markdown classes are now composed out of php traits
|
||||
- #67 The way to extend markdown has changed due to the introduction of an abstract syntax tree. See https://github.com/cebe/markdown/commit/dd2d0faa71b630e982d6651476872469b927db6d for how it changes or read the new README.
|
||||
- Introduced an abstract syntax tree as an intermediate representation between parsing steps.
|
||||
This not only fixes some issues with nested block elements but also allows manipulation of the markdown
|
||||
before rendering.
|
||||
- This version also fixes serveral rendering issues.
|
||||
|
||||
Version 0.9.2 on 18. Feb. 2014
|
||||
------------------------------
|
||||
|
||||
- #27 Fixed some rendering problems with block elements not separated by newlines
|
||||
|
||||
Version 0.9.1 on 18. Feb. 2014
|
||||
------------------------------
|
||||
|
||||
Fixed an issue with inline markers that begin with the same character e.g. `[` and `[[`.
|
||||
|
||||
Version 0.9.0 on 18. Feb. 2014
|
||||
------------------------------
|
||||
|
||||
The initial release.
|
||||
|
||||
- Complete implementation of the original Markdown spec
|
||||
- GFM without tables
|
||||
- a command line tool for markdown parsing
|
||||
36
vendor/cebe/markdown/CONTRIBUTING.md
vendored
36
vendor/cebe/markdown/CONTRIBUTING.md
vendored
@ -1,36 +0,0 @@
|
||||
Contributing
|
||||
============
|
||||
|
||||
First of all, **thank you** for contributing, **you are awesome**! :)
|
||||
|
||||
If you have an idea or found a bug, please [open an issue](https://github.com/cebe/markdown/issues/new) on github.
|
||||
|
||||
If you want to contribute code, there a few rules to follow:
|
||||
|
||||
- I am following a code style that is basically [PSR-2](http://www.php-fig.org/psr/2/) but with TABS indentation (yes, I really do that ;) ).
|
||||
I am not going to nit-pick on all the details about the code style but indentation is a must. The important part is that code is readable.
|
||||
Methods should be documented using phpdoc style.
|
||||
|
||||
- All code must be covered by tests so if you fix a bug or add a feature, please include a test case for it. See below on how that works.
|
||||
|
||||
- If you add a feature it should be documented.
|
||||
|
||||
- Also, while creating your Pull Request on GitHub, please write a description
|
||||
which gives the context and/or explains why you are creating it.
|
||||
|
||||
Thank you very much!
|
||||
|
||||
|
||||
Running the tests
|
||||
-----------------
|
||||
|
||||
The Markdown parser classes are tested with [PHPUnit](https://phpunit.de/). For each test case there is a set of files in
|
||||
the subfolders of the `/tests` folder. The result of the parser is tested with an input and an output file respectively
|
||||
where the input file contains the Markdown and the output file contains the expected HTML.
|
||||
|
||||
You can run the tests after initializing the lib with composer(`composer install`) with the following command:
|
||||
|
||||
vendor/bin/phpunit
|
||||
|
||||
To create a new test case, create a `.md` file a`.html` with the same base name in the subfolders of
|
||||
the `/tests` directory. See existing files for examples.
|
||||
114
vendor/cebe/markdown/GithubMarkdown.php
vendored
114
vendor/cebe/markdown/GithubMarkdown.php
vendored
@ -1,114 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2014 Carsten Brandt
|
||||
* @license https://github.com/cebe/markdown/blob/master/LICENSE
|
||||
* @link https://github.com/cebe/markdown#readme
|
||||
*/
|
||||
|
||||
namespace cebe\markdown;
|
||||
|
||||
/**
|
||||
* Markdown parser for github flavored markdown.
|
||||
*
|
||||
* @author Carsten Brandt <mail@cebe.cc>
|
||||
*/
|
||||
class GithubMarkdown extends Markdown
|
||||
{
|
||||
// include block element parsing using traits
|
||||
use block\TableTrait;
|
||||
use block\FencedCodeTrait;
|
||||
|
||||
// include inline element parsing using traits
|
||||
use inline\StrikeoutTrait;
|
||||
use inline\UrlLinkTrait;
|
||||
|
||||
/**
|
||||
* @var boolean whether to interpret newlines as `<br />`-tags.
|
||||
* This feature is useful for comments where newlines are often meant to be real new lines.
|
||||
*/
|
||||
public $enableNewlines = false;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected $escapeCharacters = [
|
||||
// from Markdown
|
||||
'\\', // backslash
|
||||
'`', // backtick
|
||||
'*', // asterisk
|
||||
'_', // underscore
|
||||
'{', '}', // curly braces
|
||||
'[', ']', // square brackets
|
||||
'(', ')', // parentheses
|
||||
'#', // hash mark
|
||||
'+', // plus sign
|
||||
'-', // minus sign (hyphen)
|
||||
'.', // dot
|
||||
'!', // exclamation mark
|
||||
'<', '>',
|
||||
// added by GithubMarkdown
|
||||
':', // colon
|
||||
'|', // pipe
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Consume lines for a paragraph
|
||||
*
|
||||
* Allow headlines, lists and code to break paragraphs
|
||||
*/
|
||||
protected function consumeParagraph($lines, $current)
|
||||
{
|
||||
// consume until newline
|
||||
$content = [];
|
||||
for ($i = $current, $count = count($lines); $i < $count; $i++) {
|
||||
$line = $lines[$i];
|
||||
if ($line === ''
|
||||
|| ltrim($line) === ''
|
||||
|| !ctype_alpha($line[0]) && (
|
||||
$this->identifyQuote($line, $lines, $i) ||
|
||||
$this->identifyFencedCode($line, $lines, $i) ||
|
||||
$this->identifyUl($line, $lines, $i) ||
|
||||
$this->identifyOl($line, $lines, $i) ||
|
||||
$this->identifyHr($line, $lines, $i)
|
||||
)
|
||||
|| $this->identifyHeadline($line, $lines, $i))
|
||||
{
|
||||
break;
|
||||
} elseif ($this->identifyCode($line, $lines, $i)) {
|
||||
// possible beginning of a code block
|
||||
// but check for continued inline HTML
|
||||
// e.g. <img src="file.jpg"
|
||||
// alt="some alt aligned with src attribute" title="some text" />
|
||||
if (preg_match('~<\w+([^>]+)$~s', implode("\n", $content))) {
|
||||
$content[] = $line;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$content[] = $line;
|
||||
}
|
||||
}
|
||||
$block = [
|
||||
'paragraph',
|
||||
'content' => $this->parseInline(implode("\n", $content)),
|
||||
];
|
||||
return [$block, --$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdocs
|
||||
*
|
||||
* Parses a newline indicated by two spaces on the end of a markdown line.
|
||||
*/
|
||||
protected function renderText($text)
|
||||
{
|
||||
if ($this->enableNewlines) {
|
||||
$br = $this->html5 ? "<br>\n" : "<br />\n";
|
||||
return strtr($text[1], [" \n" => $br, "\n" => $br]);
|
||||
} else {
|
||||
return parent::renderText($text);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
vendor/cebe/markdown/LICENSE
vendored
21
vendor/cebe/markdown/LICENSE
vendored
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Carsten Brandt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
128
vendor/cebe/markdown/Markdown.php
vendored
128
vendor/cebe/markdown/Markdown.php
vendored
@ -1,128 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2014 Carsten Brandt
|
||||
* @license https://github.com/cebe/markdown/blob/master/LICENSE
|
||||
* @link https://github.com/cebe/markdown#readme
|
||||
*/
|
||||
|
||||
namespace cebe\markdown;
|
||||
|
||||
/**
|
||||
* Markdown parser for the [initial markdown spec](http://daringfireball.net/projects/markdown/syntax).
|
||||
*
|
||||
* @author Carsten Brandt <mail@cebe.cc>
|
||||
*/
|
||||
class Markdown extends Parser
|
||||
{
|
||||
// include block element parsing using traits
|
||||
use block\CodeTrait;
|
||||
use block\HeadlineTrait;
|
||||
use block\HtmlTrait {
|
||||
parseInlineHtml as private;
|
||||
}
|
||||
use block\ListTrait {
|
||||
// Check Ul List before headline
|
||||
identifyUl as protected identifyBUl;
|
||||
consumeUl as protected consumeBUl;
|
||||
}
|
||||
use block\QuoteTrait;
|
||||
use block\RuleTrait {
|
||||
// Check Hr before checking lists
|
||||
identifyHr as protected identifyAHr;
|
||||
consumeHr as protected consumeAHr;
|
||||
}
|
||||
|
||||
// include inline element parsing using traits
|
||||
use inline\CodeTrait;
|
||||
use inline\EmphStrongTrait;
|
||||
use inline\LinkTrait;
|
||||
|
||||
/**
|
||||
* @var boolean whether to format markup according to HTML5 spec.
|
||||
* Defaults to `false` which means that markup is formatted as HTML4.
|
||||
*/
|
||||
public $html5 = false;
|
||||
|
||||
/**
|
||||
* @var array these are "escapeable" characters. When using one of these prefixed with a
|
||||
* backslash, the character will be outputted without the backslash and is not interpreted
|
||||
* as markdown.
|
||||
*/
|
||||
protected $escapeCharacters = [
|
||||
'\\', // backslash
|
||||
'`', // backtick
|
||||
'*', // asterisk
|
||||
'_', // underscore
|
||||
'{', '}', // curly braces
|
||||
'[', ']', // square brackets
|
||||
'(', ')', // parentheses
|
||||
'#', // hash mark
|
||||
'+', // plus sign
|
||||
'-', // minus sign (hyphen)
|
||||
'.', // dot
|
||||
'!', // exclamation mark
|
||||
'<', '>',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function prepare()
|
||||
{
|
||||
// reset references
|
||||
$this->references = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume lines for a paragraph
|
||||
*
|
||||
* Allow headlines and code to break paragraphs
|
||||
*/
|
||||
protected function consumeParagraph($lines, $current)
|
||||
{
|
||||
// consume until newline
|
||||
$content = [];
|
||||
for ($i = $current, $count = count($lines); $i < $count; $i++) {
|
||||
$line = $lines[$i];
|
||||
|
||||
// a list may break a paragraph when it is inside of a list
|
||||
if (isset($this->context[1]) && $this->context[1] === 'list' && !ctype_alpha($line[0]) && (
|
||||
$this->identifyUl($line, $lines, $i) || $this->identifyOl($line, $lines, $i))) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($line === '' || ltrim($line) === '' || $this->identifyHeadline($line, $lines, $i)) {
|
||||
break;
|
||||
} elseif ($line[0] === "\t" || $line[0] === " " && strncmp($line, ' ', 4) === 0) {
|
||||
// possible beginning of a code block
|
||||
// but check for continued inline HTML
|
||||
// e.g. <img src="file.jpg"
|
||||
// alt="some alt aligned with src attribute" title="some text" />
|
||||
if (preg_match('~<\w+([^>]+)$~s', implode("\n", $content))) {
|
||||
$content[] = $line;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$content[] = $line;
|
||||
}
|
||||
}
|
||||
$block = [
|
||||
'paragraph',
|
||||
'content' => $this->parseInline(implode("\n", $content)),
|
||||
];
|
||||
return [$block, --$i];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritdocs
|
||||
*
|
||||
* Parses a newline indicated by two spaces on the end of a markdown line.
|
||||
*/
|
||||
protected function renderText($text)
|
||||
{
|
||||
return str_replace(" \n", $this->html5 ? "<br>\n" : "<br />\n", $text[1]);
|
||||
}
|
||||
}
|
||||
256
vendor/cebe/markdown/MarkdownExtra.php
vendored
256
vendor/cebe/markdown/MarkdownExtra.php
vendored
@ -1,256 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace cebe\markdown;
|
||||
|
||||
use cebe\markdown\block\TableTrait;
|
||||
|
||||
// work around https://github.com/facebook/hhvm/issues/1120
|
||||
defined('ENT_HTML401') || define('ENT_HTML401', 0);
|
||||
|
||||
/**
|
||||
* Markdown parser for the [markdown extra](http://michelf.ca/projects/php-markdown/extra/) flavor.
|
||||
*
|
||||
* @author Carsten Brandt <mail@cebe.cc>
|
||||
* @license https://github.com/cebe/markdown/blob/master/LICENSE
|
||||
* @link https://github.com/cebe/markdown#readme
|
||||
*/
|
||||
class MarkdownExtra extends Markdown
|
||||
{
|
||||
// include block element parsing using traits
|
||||
use block\TableTrait;
|
||||
use block\FencedCodeTrait;
|
||||
|
||||
// include inline element parsing using traits
|
||||
// TODO
|
||||
|
||||
/**
|
||||
* @var bool whether special attributes on code blocks should be applied on the `<pre>` element.
|
||||
* The default behavior is to put them on the `<code>` element.
|
||||
*/
|
||||
public $codeAttributesOnPre = false;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected $escapeCharacters = [
|
||||
// from Markdown
|
||||
'\\', // backslash
|
||||
'`', // backtick
|
||||
'*', // asterisk
|
||||
'_', // underscore
|
||||
'{', '}', // curly braces
|
||||
'[', ']', // square brackets
|
||||
'(', ')', // parentheses
|
||||
'#', // hash mark
|
||||
'+', // plus sign
|
||||
'-', // minus sign (hyphen)
|
||||
'.', // dot
|
||||
'!', // exclamation mark
|
||||
'<', '>',
|
||||
// added by MarkdownExtra
|
||||
':', // colon
|
||||
'|', // pipe
|
||||
];
|
||||
|
||||
private $_specialAttributesRegex = '\{(([#\.][A-z0-9-_]+\s*)+)\}';
|
||||
|
||||
// TODO allow HTML intended 3 spaces
|
||||
|
||||
// TODO add markdown inside HTML blocks
|
||||
|
||||
// TODO implement definition lists
|
||||
|
||||
// TODO implement footnotes
|
||||
|
||||
// TODO implement Abbreviations
|
||||
|
||||
|
||||
// block parsing
|
||||
|
||||
protected function identifyReference($line)
|
||||
{
|
||||
return ($line[0] === ' ' || $line[0] === '[') && preg_match('/^ {0,3}\[[^\[](.*?)\]:\s*([^\s]+?)(?:\s+[\'"](.+?)[\'"])?\s*('.$this->_specialAttributesRegex.')?\s*$/', $line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume link references
|
||||
*/
|
||||
protected function consumeReference($lines, $current)
|
||||
{
|
||||
while (isset($lines[$current]) && preg_match('/^ {0,3}\[(.+?)\]:\s*(.+?)(?:\s+[\(\'"](.+?)[\)\'"])?\s*('.$this->_specialAttributesRegex.')?\s*$/', $lines[$current], $matches)) {
|
||||
$label = strtolower($matches[1]);
|
||||
|
||||
$this->references[$label] = [
|
||||
'url' => $this->replaceEscape($matches[2]),
|
||||
];
|
||||
if (isset($matches[3])) {
|
||||
$this->references[$label]['title'] = $matches[3];
|
||||
} else {
|
||||
// title may be on the next line
|
||||
if (isset($lines[$current + 1]) && preg_match('/^\s+[\(\'"](.+?)[\)\'"]\s*$/', $lines[$current + 1], $matches)) {
|
||||
$this->references[$label]['title'] = $matches[1];
|
||||
$current++;
|
||||
}
|
||||
}
|
||||
if (isset($matches[5])) {
|
||||
$this->references[$label]['attributes'] = $matches[5];
|
||||
}
|
||||
$current++;
|
||||
}
|
||||
return [false, --$current];
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume lines for a fenced code block
|
||||
*/
|
||||
protected function consumeFencedCode($lines, $current)
|
||||
{
|
||||
// consume until ```
|
||||
$block = [
|
||||
'code',
|
||||
];
|
||||
$line = trim($lines[$current]);
|
||||
if (($pos = strrpos($line, '`')) === false) {
|
||||
$pos = strrpos($line, '~');
|
||||
}
|
||||
$fence = substr($line, 0, $pos + 1);
|
||||
$block['attributes'] = substr($line, $pos);
|
||||
$content = [];
|
||||
for($i = $current + 1, $count = count($lines); $i < $count; $i++) {
|
||||
if (($pos = strpos($line = $lines[$i], $fence)) === false || $pos > 3) {
|
||||
$content[] = $line;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$block['content'] = implode("\n", $content);
|
||||
return [$block, $i];
|
||||
}
|
||||
|
||||
protected function renderCode($block)
|
||||
{
|
||||
$attributes = $this->renderAttributes($block);
|
||||
return ($this->codeAttributesOnPre ? "<pre$attributes><code>" : "<pre><code$attributes>")
|
||||
. htmlspecialchars($block['content'] . "\n", ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8')
|
||||
. "</code></pre>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a headline
|
||||
*/
|
||||
protected function renderHeadline($block)
|
||||
{
|
||||
foreach($block['content'] as $i => $element) {
|
||||
if ($element[0] === 'specialAttributes') {
|
||||
unset($block['content'][$i]);
|
||||
$block['attributes'] = $element[1];
|
||||
}
|
||||
}
|
||||
$tag = 'h' . $block['level'];
|
||||
$attributes = $this->renderAttributes($block);
|
||||
return "<$tag$attributes>" . rtrim($this->renderAbsy($block['content']), "# \t") . "</$tag>\n";
|
||||
}
|
||||
|
||||
protected function renderAttributes($block)
|
||||
{
|
||||
$html = [];
|
||||
if (isset($block['attributes'])) {
|
||||
$attributes = preg_split('/\s+/', $block['attributes'], -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach($attributes as $attribute) {
|
||||
if ($attribute[0] === '#') {
|
||||
$html['id'] = substr($attribute, 1);
|
||||
} else {
|
||||
$html['class'][] = substr($attribute, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
$result = '';
|
||||
foreach($html as $attr => $value) {
|
||||
if (is_array($value)) {
|
||||
$value = trim(implode(' ', $value));
|
||||
}
|
||||
if (!empty($value)) {
|
||||
$result .= " $attr=\"$value\"";
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
// inline parsing
|
||||
|
||||
|
||||
/**
|
||||
* @marker {
|
||||
*/
|
||||
protected function parseSpecialAttributes($text)
|
||||
{
|
||||
if (preg_match("~$this->_specialAttributesRegex~", $text, $matches)) {
|
||||
return [['specialAttributes', $matches[1]], strlen($matches[0])];
|
||||
}
|
||||
return [['text', '{'], 1];
|
||||
}
|
||||
|
||||
protected function renderSpecialAttributes($block)
|
||||
{
|
||||
return '{' . $block[1] . '}';
|
||||
}
|
||||
|
||||
protected function parseInline($text)
|
||||
{
|
||||
$elements = parent::parseInline($text);
|
||||
// merge special attribute elements to links and images as they are not part of the final absy later
|
||||
$relatedElement = null;
|
||||
foreach($elements as $i => $element) {
|
||||
if ($element[0] === 'link' || $element[0] === 'image') {
|
||||
$relatedElement = $i;
|
||||
} elseif ($element[0] === 'specialAttributes') {
|
||||
if ($relatedElement !== null) {
|
||||
$elements[$relatedElement]['attributes'] = $element[1];
|
||||
unset($elements[$i]);
|
||||
}
|
||||
$relatedElement = null;
|
||||
} else {
|
||||
$relatedElement = null;
|
||||
}
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
protected function renderLink($block)
|
||||
{
|
||||
if (isset($block['refkey'])) {
|
||||
if (($ref = $this->lookupReference($block['refkey'])) !== false) {
|
||||
$block = array_merge($block, $ref);
|
||||
} else {
|
||||
if (strncmp($block['orig'], '[', 1) === 0) {
|
||||
return '[' . $this->renderAbsy($this->parseInline(substr($block['orig'], 1)));
|
||||
}
|
||||
return $block['orig'];
|
||||
}
|
||||
}
|
||||
$attributes = $this->renderAttributes($block);
|
||||
return '<a href="' . htmlspecialchars($block['url'], ENT_COMPAT | ENT_HTML401, 'UTF-8') . '"'
|
||||
. (empty($block['title']) ? '' : ' title="' . htmlspecialchars($block['title'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"')
|
||||
. $attributes . '>' . $this->renderAbsy($block['text']) . '</a>';
|
||||
}
|
||||
|
||||
protected function renderImage($block)
|
||||
{
|
||||
if (isset($block['refkey'])) {
|
||||
if (($ref = $this->lookupReference($block['refkey'])) !== false) {
|
||||
$block = array_merge($block, $ref);
|
||||
} else {
|
||||
if (strncmp($block['orig'], '![', 2) === 0) {
|
||||
return '![' . $this->renderAbsy($this->parseInline(substr($block['orig'], 2)));
|
||||
}
|
||||
return $block['orig'];
|
||||
}
|
||||
}
|
||||
$attributes = $this->renderAttributes($block);
|
||||
return '<img src="' . htmlspecialchars($block['url'], ENT_COMPAT | ENT_HTML401, 'UTF-8') . '"'
|
||||
. ' alt="' . htmlspecialchars($block['text'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"'
|
||||
. (empty($block['title']) ? '' : ' title="' . htmlspecialchars($block['title'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"')
|
||||
. $attributes . ($this->html5 ? '>' : ' />');
|
||||
}
|
||||
}
|
||||
389
vendor/cebe/markdown/Parser.php
vendored
389
vendor/cebe/markdown/Parser.php
vendored
@ -1,389 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2014 Carsten Brandt
|
||||
* @license https://github.com/cebe/markdown/blob/master/LICENSE
|
||||
* @link https://github.com/cebe/markdown#readme
|
||||
*/
|
||||
|
||||
namespace cebe\markdown;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* A generic parser for markdown-like languages.
|
||||
*
|
||||
* @author Carsten Brandt <mail@cebe.cc>
|
||||
*/
|
||||
abstract class Parser
|
||||
{
|
||||
/**
|
||||
* @var integer the maximum nesting level for language elements.
|
||||
*/
|
||||
public $maximumNestingLevel = 32;
|
||||
|
||||
/**
|
||||
* @var array the current context the parser is in.
|
||||
* TODO remove in favor of absy
|
||||
*/
|
||||
protected $context = [];
|
||||
/**
|
||||
* @var array these are "escapeable" characters. When using one of these prefixed with a
|
||||
* backslash, the character will be outputted without the backslash and is not interpreted
|
||||
* as markdown.
|
||||
*/
|
||||
protected $escapeCharacters = [
|
||||
'\\', // backslash
|
||||
];
|
||||
|
||||
private $_depth = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Parses the given text considering the full language.
|
||||
*
|
||||
* This includes parsing block elements as well as inline elements.
|
||||
*
|
||||
* @param string $text the text to parse
|
||||
* @return string parsed markup
|
||||
*/
|
||||
public function parse($text)
|
||||
{
|
||||
$this->prepare();
|
||||
|
||||
if (ltrim($text) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$text = str_replace(["\r\n", "\n\r", "\r"], "\n", $text);
|
||||
|
||||
$this->prepareMarkers($text);
|
||||
|
||||
$absy = $this->parseBlocks(explode("\n", $text));
|
||||
$markup = $this->renderAbsy($absy);
|
||||
|
||||
$this->cleanup();
|
||||
return $markup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a paragraph without block elements (block elements are ignored).
|
||||
*
|
||||
* @param string $text the text to parse
|
||||
* @return string parsed markup
|
||||
*/
|
||||
public function parseParagraph($text)
|
||||
{
|
||||
$this->prepare();
|
||||
|
||||
if (ltrim($text) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$text = str_replace(["\r\n", "\n\r", "\r"], "\n", $text);
|
||||
|
||||
$this->prepareMarkers($text);
|
||||
|
||||
$absy = $this->parseInline($text);
|
||||
$markup = $this->renderAbsy($absy);
|
||||
|
||||
$this->cleanup();
|
||||
return $markup;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called before `parse()` and `parseParagraph()`.
|
||||
* You can override it to do some initialization work.
|
||||
*/
|
||||
protected function prepare()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called after `parse()` and `parseParagraph()`.
|
||||
* You can override it to do cleanup.
|
||||
*/
|
||||
protected function cleanup()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// block parsing
|
||||
|
||||
private $_blockTypes;
|
||||
|
||||
/**
|
||||
* @return array a list of block element types available.
|
||||
*/
|
||||
protected function blockTypes()
|
||||
{
|
||||
if ($this->_blockTypes === null) {
|
||||
// detect block types via "identify" functions
|
||||
$reflection = new \ReflectionClass($this);
|
||||
$this->_blockTypes = array_filter(array_map(function($method) {
|
||||
$name = $method->getName();
|
||||
return strncmp($name, 'identify', 8) === 0 ? strtolower(substr($name, 8)) : false;
|
||||
}, $reflection->getMethods(ReflectionMethod::IS_PROTECTED)));
|
||||
|
||||
sort($this->_blockTypes);
|
||||
}
|
||||
return $this->_blockTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of lines and an index of a current line it uses the registed block types to
|
||||
* detect the type of this line.
|
||||
* @param array $lines
|
||||
* @param integer $current
|
||||
* @return string name of the block type in lower case
|
||||
*/
|
||||
protected function detectLineType($lines, $current)
|
||||
{
|
||||
$line = $lines[$current];
|
||||
$blockTypes = $this->blockTypes();
|
||||
foreach($blockTypes as $blockType) {
|
||||
if ($this->{'identify' . $blockType}($line, $lines, $current)) {
|
||||
return $blockType;
|
||||
}
|
||||
}
|
||||
// consider the line a normal paragraph if no other block type matches
|
||||
return 'paragraph';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse block elements by calling `detectLineType()` to identify them
|
||||
* and call consume function afterwards.
|
||||
*/
|
||||
protected function parseBlocks($lines)
|
||||
{
|
||||
if ($this->_depth >= $this->maximumNestingLevel) {
|
||||
// maximum depth is reached, do not parse input
|
||||
return [['text', implode("\n", $lines)]];
|
||||
}
|
||||
$this->_depth++;
|
||||
|
||||
$blocks = [];
|
||||
|
||||
// convert lines to blocks
|
||||
for ($i = 0, $count = count($lines); $i < $count; $i++) {
|
||||
$line = $lines[$i];
|
||||
if ($line !== '' && rtrim($line) !== '') { // skip empty lines
|
||||
// identify a blocks beginning and parse the content
|
||||
list($block, $i) = $this->parseBlock($lines, $i);
|
||||
if ($block !== false) {
|
||||
$blocks[] = $block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_depth--;
|
||||
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the block at current line by identifying the block type and parsing the content
|
||||
* @param $lines
|
||||
* @param $current
|
||||
* @return array Array of two elements, the first element contains the block,
|
||||
* the second contains the next line index to be parsed.
|
||||
*/
|
||||
protected function parseBlock($lines, $current)
|
||||
{
|
||||
// identify block type for this line
|
||||
$blockType = $this->detectLineType($lines, $current);
|
||||
|
||||
// call consume method for the detected block type to consume further lines
|
||||
return $this->{'consume' . $blockType}($lines, $current);
|
||||
}
|
||||
|
||||
protected function renderAbsy($blocks)
|
||||
{
|
||||
$output = '';
|
||||
foreach ($blocks as $block) {
|
||||
array_unshift($this->context, $block[0]);
|
||||
$output .= $this->{'render' . $block[0]}($block);
|
||||
array_shift($this->context);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume lines for a paragraph
|
||||
*
|
||||
* @param $lines
|
||||
* @param $current
|
||||
* @return array
|
||||
*/
|
||||
protected function consumeParagraph($lines, $current)
|
||||
{
|
||||
// consume until newline
|
||||
$content = [];
|
||||
for ($i = $current, $count = count($lines); $i < $count; $i++) {
|
||||
if (ltrim($lines[$i]) !== '') {
|
||||
$content[] = $lines[$i];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$block = [
|
||||
'paragraph',
|
||||
'content' => $this->parseInline(implode("\n", $content)),
|
||||
];
|
||||
return [$block, --$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a paragraph block
|
||||
*
|
||||
* @param $block
|
||||
* @return string
|
||||
*/
|
||||
protected function renderParagraph($block)
|
||||
{
|
||||
return '<p>' . $this->renderAbsy($block['content']) . "</p>\n";
|
||||
}
|
||||
|
||||
|
||||
// inline parsing
|
||||
|
||||
|
||||
/**
|
||||
* @var array the set of inline markers to use in different contexts.
|
||||
*/
|
||||
private $_inlineMarkers = [];
|
||||
|
||||
/**
|
||||
* Returns a map of inline markers to the corresponding parser methods.
|
||||
*
|
||||
* This array defines handler methods for inline markdown markers.
|
||||
* When a marker is found in the text, the handler method is called with the text
|
||||
* starting at the position of the marker.
|
||||
*
|
||||
* Note that markers starting with whitespace may slow down the parser,
|
||||
* you may want to use [[renderText]] to deal with them.
|
||||
*
|
||||
* You may override this method to define a set of markers and parsing methods.
|
||||
* The default implementation looks for protected methods starting with `parse` that
|
||||
* also have an `@marker` annotation in PHPDoc.
|
||||
*
|
||||
* @return array a map of markers to parser methods
|
||||
*/
|
||||
protected function inlineMarkers()
|
||||
{
|
||||
$markers = [];
|
||||
// detect "parse" functions
|
||||
$reflection = new \ReflectionClass($this);
|
||||
foreach($reflection->getMethods(ReflectionMethod::IS_PROTECTED) as $method) {
|
||||
$methodName = $method->getName();
|
||||
if (strncmp($methodName, 'parse', 5) === 0) {
|
||||
preg_match_all('/@marker ([^\s]+)/', $method->getDocComment(), $matches);
|
||||
foreach($matches[1] as $match) {
|
||||
$markers[$match] = $methodName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $markers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare markers that are used in the text to parse
|
||||
*
|
||||
* Add all markers that are present in markdown.
|
||||
* Check is done to avoid iterations in parseInline(), good for huge markdown files
|
||||
* @param string $text
|
||||
*/
|
||||
protected function prepareMarkers($text)
|
||||
{
|
||||
$this->_inlineMarkers = [];
|
||||
foreach ($this->inlineMarkers() as $marker => $method) {
|
||||
if (strpos($text, $marker) !== false) {
|
||||
$m = $marker[0];
|
||||
// put the longest marker first
|
||||
if (isset($this->_inlineMarkers[$m])) {
|
||||
reset($this->_inlineMarkers[$m]);
|
||||
if (strlen($marker) > strlen(key($this->_inlineMarkers[$m]))) {
|
||||
$this->_inlineMarkers[$m] = array_merge([$marker => $method], $this->_inlineMarkers[$m]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$this->_inlineMarkers[$m][$marker] = $method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses inline elements of the language.
|
||||
*
|
||||
* @param string $text the inline text to parse.
|
||||
* @return array
|
||||
*/
|
||||
protected function parseInline($text)
|
||||
{
|
||||
if ($this->_depth >= $this->maximumNestingLevel) {
|
||||
// maximum depth is reached, do not parse input
|
||||
return [['text', $text]];
|
||||
}
|
||||
$this->_depth++;
|
||||
|
||||
$markers = implode('', array_keys($this->_inlineMarkers));
|
||||
|
||||
$paragraph = [];
|
||||
|
||||
while (!empty($markers) && ($found = strpbrk($text, $markers)) !== false) {
|
||||
|
||||
$pos = strpos($text, $found);
|
||||
|
||||
// add the text up to next marker to the paragraph
|
||||
if ($pos !== 0) {
|
||||
$paragraph[] = ['text', substr($text, 0, $pos)];
|
||||
}
|
||||
$text = $found;
|
||||
|
||||
$parsed = false;
|
||||
foreach ($this->_inlineMarkers[$text[0]] as $marker => $method) {
|
||||
if (strncmp($text, $marker, strlen($marker)) === 0) {
|
||||
// parse the marker
|
||||
array_unshift($this->context, $method);
|
||||
list($output, $offset) = $this->$method($text);
|
||||
array_shift($this->context);
|
||||
|
||||
$paragraph[] = $output;
|
||||
$text = substr($text, $offset);
|
||||
$parsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$parsed) {
|
||||
$paragraph[] = ['text', substr($text, 0, 1)];
|
||||
$text = substr($text, 1);
|
||||
}
|
||||
}
|
||||
|
||||
$paragraph[] = ['text', $text];
|
||||
|
||||
$this->_depth--;
|
||||
|
||||
return $paragraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses escaped special characters.
|
||||
* @marker \
|
||||
*/
|
||||
protected function parseEscape($text)
|
||||
{
|
||||
if (isset($text[1]) && in_array($text[1], $this->escapeCharacters)) {
|
||||
return [['text', $text[1]], 2];
|
||||
}
|
||||
return [['text', $text[0]], 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function renders plain text sections in the markdown text.
|
||||
* It can be used to work on normal text sections for example to highlight keywords or
|
||||
* do special escaping.
|
||||
*/
|
||||
protected function renderText($block)
|
||||
{
|
||||
return $block[1];
|
||||
}
|
||||
}
|
||||
521
vendor/cebe/markdown/README.md
vendored
521
vendor/cebe/markdown/README.md
vendored
@ -1,521 +0,0 @@
|
||||
A super fast, highly extensible markdown parser for PHP
|
||||
=======================================================
|
||||
|
||||
[](https://packagist.org/packages/cebe/markdown)
|
||||
[](https://packagist.org/packages/cebe/markdown)
|
||||
[](http://travis-ci.org/cebe/markdown)
|
||||
[](https://scrutinizer-ci.com/g/cebe/markdown/)
|
||||
[](https://scrutinizer-ci.com/g/cebe/markdown/)
|
||||
|
||||
What is this? <a name="what"></a>
|
||||
-------------
|
||||
|
||||
A set of [PHP][] classes, each representing a [Markdown][] flavor, and a command line tool
|
||||
for converting markdown files to HTML files.
|
||||
|
||||
The implementation focus is to be **fast** (see [benchmark][]) and **extensible**.
|
||||
Parsing Markdown to HTML is as simple as calling a single method (see [Usage](#usage)) providing a solid implementation
|
||||
that gives most expected results even in non-trivial edge cases.
|
||||
|
||||
Extending the Markdown language with new elements is as simple as adding a new method to the class that converts the
|
||||
markdown text to the expected output in HTML. This is possible without dealing with complex and error prone regular expressions.
|
||||
It is also possible to hook into the markdown structure and add elements or read meta information using the internal representation
|
||||
of the Markdown text as an abstract syntax tree (see [Extending the language](#extend)).
|
||||
|
||||
Currently the following markdown flavors are supported:
|
||||
|
||||
- **Traditional Markdown** according to <http://daringfireball.net/projects/markdown/syntax> ([try it!](http://markdown.cebe.cc/try?flavor=default)).
|
||||
- **Github flavored Markdown** according to <https://help.github.com/articles/github-flavored-markdown> ([try it!](http://markdown.cebe.cc/try?flavor=gfm)).
|
||||
- **Markdown Extra** according to <http://michelf.ca/projects/php-markdown/extra/> (currently not fully supported WIP see [#25][], [try it!](http://markdown.cebe.cc/try?flavor=extra))
|
||||
- Any mixed Markdown flavor you like because of its highly extensible structure (See documentation below).
|
||||
|
||||
Future plans are to support:
|
||||
|
||||
- Smarty Pants <http://daringfireball.net/projects/smartypants/>
|
||||
- ... (Feel free to [suggest](https://github.com/cebe/markdown/issues/new) further additions!)
|
||||
|
||||
[PHP]: http://php.net/ "PHP is a popular general-purpose scripting language that is especially suited to web development."
|
||||
[Markdown]: http://en.wikipedia.org/wiki/Markdown "Markdown on Wikipedia"
|
||||
[#25]: https://github.com/cebe/markdown/issues/25 "issue #25"
|
||||
[benchmark]: https://github.com/kzykhys/Markbench#readme "kzykhys/Markbench on github"
|
||||
|
||||
### Who is using it?
|
||||
|
||||
- It powers the [API-docs and the definitive guide](http://www.yiiframework.com/doc-2.0/) for the [Yii Framework][] [2.0](https://github.com/yiisoft/yii2).
|
||||
|
||||
[Yii Framework]: http://www.yiiframework.com/ "The Yii PHP Framework"
|
||||
|
||||
|
||||
Installation <a name="installation"></a>
|
||||
------------
|
||||
|
||||
[PHP 5.4 or higher](http://www.php.net/downloads.php) is required to use it.
|
||||
It will also run on facebook's [hhvm](http://hhvm.com/).
|
||||
|
||||
The library uses PHPDoc annotations to determine the markdown elements that should be parsed.
|
||||
So in case you are using PHP `opcache`, make sure
|
||||
[it does not strip comments](http://php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments).
|
||||
|
||||
Installation is recommended to be done via [composer][] by running:
|
||||
|
||||
composer require cebe/markdown "~1.2.0"
|
||||
|
||||
Alternatively you can add the following to the `require` section in your `composer.json` manually:
|
||||
|
||||
```json
|
||||
"cebe/markdown": "~1.2.0"
|
||||
```
|
||||
|
||||
Run `composer update` afterwards.
|
||||
|
||||
[composer]: https://getcomposer.org/ "The PHP package manager"
|
||||
|
||||
> Note: If you have configured PHP with opcache you need to enable the
|
||||
> [opcache.save_comments](http://php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments) option because inline element parsing relies on PHPdoc annotations to find declared elements.
|
||||
|
||||
Usage <a name="usage"></a>
|
||||
-----
|
||||
|
||||
### In your PHP project
|
||||
|
||||
To parse your markdown you need only two lines of code. The first one is to choose the markdown flavor as
|
||||
one of the following:
|
||||
|
||||
- Traditional Markdown: `$parser = new \cebe\markdown\Markdown();`
|
||||
- Github Flavored Markdown: `$parser = new \cebe\markdown\GithubMarkdown();`
|
||||
- Markdown Extra: `$parser = new \cebe\markdown\MarkdownExtra();`
|
||||
|
||||
The next step is to call the `parse()`-method for parsing the text using the full markdown language
|
||||
or calling the `parseParagraph()`-method to parse only inline elements.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
```php
|
||||
// traditional markdown and parse full text
|
||||
$parser = new \cebe\markdown\Markdown();
|
||||
echo $parser->parse($markdown);
|
||||
|
||||
// use github markdown
|
||||
$parser = new \cebe\markdown\GithubMarkdown();
|
||||
echo $parser->parse($markdown);
|
||||
|
||||
// use markdown extra
|
||||
$parser = new \cebe\markdown\MarkdownExtra();
|
||||
echo $parser->parse($markdown);
|
||||
|
||||
// parse only inline elements (useful for one-line descriptions)
|
||||
$parser = new \cebe\markdown\GithubMarkdown();
|
||||
echo $parser->parseParagraph($markdown);
|
||||
```
|
||||
|
||||
You may optionally set one of the following options on the parser object:
|
||||
|
||||
For all Markdown Flavors:
|
||||
|
||||
- `$parser->html5 = true` to enable HTML5 output instead of HTML4.
|
||||
- `$parser->keepListStartNumber = true` to enable keeping the numbers of ordered lists as specified in the markdown.
|
||||
The default behavior is to always start from 1 and increment by one regardless of the number in markdown.
|
||||
|
||||
For GithubMarkdown:
|
||||
|
||||
- `$parser->enableNewlines = true` to convert all newlines to `<br/>`-tags. By default only newlines with two preceding spaces are converted to `<br/>`-tags.
|
||||
|
||||
It is recommended to use UTF-8 encoding for the input strings. Other encodings may work, but are currently untested.
|
||||
|
||||
### The command line script
|
||||
|
||||
You can use it to render this readme:
|
||||
|
||||
bin/markdown README.md > README.html
|
||||
|
||||
Using github flavored markdown:
|
||||
|
||||
bin/markdown --flavor=gfm README.md > README.html
|
||||
|
||||
or convert the original markdown description to html using the unix pipe:
|
||||
|
||||
curl http://daringfireball.net/projects/markdown/syntax.text | bin/markdown > md.html
|
||||
|
||||
Here is the full Help output you will see when running `bin/markdown --help`:
|
||||
|
||||
PHP Markdown to HTML converter
|
||||
------------------------------
|
||||
|
||||
by Carsten Brandt <mail@cebe.cc>
|
||||
|
||||
Usage:
|
||||
bin/markdown [--flavor=<flavor>] [--full] [file.md]
|
||||
|
||||
--flavor specifies the markdown flavor to use. If omitted the original markdown by John Gruber [1] will be used.
|
||||
Available flavors:
|
||||
|
||||
gfm - Github flavored markdown [2]
|
||||
extra - Markdown Extra [3]
|
||||
|
||||
--full ouput a full HTML page with head and body. If not given, only the parsed markdown will be output.
|
||||
|
||||
--help shows this usage information.
|
||||
|
||||
If no file is specified input will be read from STDIN.
|
||||
|
||||
Examples:
|
||||
|
||||
Render a file with original markdown:
|
||||
|
||||
bin/markdown README.md > README.html
|
||||
|
||||
Render a file using gihtub flavored markdown:
|
||||
|
||||
bin/markdown --flavor=gfm README.md > README.html
|
||||
|
||||
Convert the original markdown description to html using STDIN:
|
||||
|
||||
curl http://daringfireball.net/projects/markdown/syntax.text | bin/markdown > md.html
|
||||
|
||||
|
||||
[1] http://daringfireball.net/projects/markdown/syntax
|
||||
[2] https://help.github.com/articles/github-flavored-markdown
|
||||
[3] http://michelf.ca/projects/php-markdown/extra/
|
||||
|
||||
|
||||
Extensions
|
||||
----------
|
||||
|
||||
Here are some extensions to this library:
|
||||
|
||||
- [Bogardo/markdown-codepen](https://github.com/Bogardo/markdown-codepen) - shortcode to embed codepens from http://codepen.io/ in markdown.
|
||||
- [kartik-v/yii2-markdown](https://github.com/kartik-v/yii2-markdown) - Advanced Markdown editing and conversion utilities for Yii Framework 2.0.
|
||||
- [cebe/markdown-latex](https://github.com/cebe/markdown-latex) - Convert Markdown to LaTeX and PDF
|
||||
- [softark/creole](https://github.com/softark/creole) - A creole markup parser
|
||||
- [hyn/frontmatter](https://github.com/hyn/frontmatter) - Frontmatter Metadata Support (JSON, TOML, YAML)
|
||||
- ... [add yours!](https://github.com/cebe/markdown/edit/master/README.md#L186)
|
||||
|
||||
|
||||
Extending the language <a name="extend"></a>
|
||||
----------------------
|
||||
|
||||
Markdown consists of two types of language elements, I'll call them block and inline elements simlar to what you have in
|
||||
HTML with `<div>` and `<span>`. Block elements are normally spreads over several lines and are separated by blank lines.
|
||||
The most basic block element is a paragraph (`<p>`).
|
||||
Inline elements are elements that are added inside of block elements i.e. inside of text.
|
||||
|
||||
This markdown parser allows you to extend the markdown language by changing existing elements behavior and also adding
|
||||
new block and inline elements. You do this by extending from the parser class and adding/overriding class methods and
|
||||
properties. For the different element types there are different ways to extend them as you will see in the following sections.
|
||||
|
||||
### Adding block elements
|
||||
|
||||
The markdown is parsed line by line to identify each non-empty line as one of the block element types.
|
||||
To identify a line as the beginning of a block element it calls all protected class methods who's name begins with `identify`.
|
||||
An identify function returns true if it has identified the block element it is responsible for or false if not.
|
||||
In the following example we will implement support for [fenced code blocks][] which are part of the github flavored markdown.
|
||||
|
||||
[fenced code blocks]: https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks
|
||||
"Fenced code block feature of github flavored markdown"
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class MyMarkdown extends \cebe\markdown\Markdown
|
||||
{
|
||||
protected function identifyFencedCode($line, $lines, $current)
|
||||
{
|
||||
// if a line starts with at least 3 backticks it is identified as a fenced code block
|
||||
if (strncmp($line, '```', 3) === 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In the above, `$line` is a string containing the content of the current line and is equal to `$lines[$current]`.
|
||||
You may use `$lines` and `$current` to check other lines than the current line. In most cases you can ignore these parameters.
|
||||
|
||||
Parsing of a block element is done in two steps:
|
||||
|
||||
1. **Consuming** all the lines belonging to it. In most cases this is iterating over the lines starting from the identified
|
||||
line until a blank line occurs. This step is implemented by a method named `consume{blockName}()` where `{blockName}`
|
||||
is the same name as used for the identify function above. The consume method also takes the lines array
|
||||
and the number of the current line. It will return two arguments: an array representing the block element in the abstract syntax tree
|
||||
of the markdown document and the line number to parse next. In the abstract syntax array the first element refers to the name of
|
||||
the element, all other array elements can be freely defined by yourself.
|
||||
In our example we will implement it like this:
|
||||
|
||||
```php
|
||||
protected function consumeFencedCode($lines, $current)
|
||||
{
|
||||
// create block array
|
||||
$block = [
|
||||
'fencedCode',
|
||||
'content' => [],
|
||||
];
|
||||
$line = rtrim($lines[$current]);
|
||||
|
||||
// detect language and fence length (can be more than 3 backticks)
|
||||
$fence = substr($line, 0, $pos = strrpos($line, '`') + 1);
|
||||
$language = substr($line, $pos);
|
||||
if (!empty($language)) {
|
||||
$block['language'] = $language;
|
||||
}
|
||||
|
||||
// consume all lines until ```
|
||||
for($i = $current + 1, $count = count($lines); $i < $count; $i++) {
|
||||
if (rtrim($line = $lines[$i]) !== $fence) {
|
||||
$block['content'][] = $line;
|
||||
} else {
|
||||
// stop consuming when code block is over
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [$block, $i];
|
||||
}
|
||||
```
|
||||
|
||||
2. **Rendering** the element. After all blocks have been consumed, they are being rendered using the
|
||||
`render{elementName}()`-method where `elementName` refers to the name of the element in the abstract syntax tree:
|
||||
|
||||
```php
|
||||
protected function renderFencedCode($block)
|
||||
{
|
||||
$class = isset($block['language']) ? ' class="language-' . $block['language'] . '"' : '';
|
||||
return "<pre><code$class>" . htmlspecialchars(implode("\n", $block['content']) . "\n", ENT_NOQUOTES, 'UTF-8') . '</code></pre>';
|
||||
}
|
||||
```
|
||||
|
||||
You may also add code highlighting here. In general it would also be possible to render ouput in a different language than
|
||||
HTML for example LaTeX.
|
||||
|
||||
|
||||
### Adding inline elements
|
||||
|
||||
Adding inline elements is different from block elements as they are parsed using markers in the text.
|
||||
An inline element is identified by a marker that marks the beginning of an inline element (e.g. `[` will mark a possible
|
||||
beginning of a link or `` ` `` will mark inline code).
|
||||
|
||||
Parsing methods for inline elements are also protected and identified by the prefix `parse`. Additionally a `@marker` annotation
|
||||
in PHPDoc is needed to register the parse function for one or multiple markers.
|
||||
The method will then be called when a marker is found in the text. As an argument it takes the text starting at the position of the marker.
|
||||
The parser method will return an array containing the element of the abstract sytnax tree and an offset of text it has
|
||||
parsed from the input markdown. All text up to this offset will be removed from the markdown before the next marker will be searched.
|
||||
|
||||
As an example, we will add support for the [strikethrough][] feature of github flavored markdown:
|
||||
|
||||
[strikethrough]: https://help.github.com/articles/github-flavored-markdown#strikethrough "Strikethrough feature of github flavored markdown"
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class MyMarkdown extends \cebe\markdown\Markdown
|
||||
{
|
||||
/**
|
||||
* @marker ~~
|
||||
*/
|
||||
protected function parseStrike($markdown)
|
||||
{
|
||||
// check whether the marker really represents a strikethrough (i.e. there is a closing ~~)
|
||||
if (preg_match('/^~~(.+?)~~/', $markdown, $matches)) {
|
||||
return [
|
||||
// return the parsed tag as an element of the abstract syntax tree and call `parseInline()` to allow
|
||||
// other inline markdown elements inside this tag
|
||||
['strike', $this->parseInline($matches[1])],
|
||||
// return the offset of the parsed text
|
||||
strlen($matches[0])
|
||||
];
|
||||
}
|
||||
// in case we did not find a closing ~~ we just return the marker and skip 2 characters
|
||||
return [['text', '~~'], 2];
|
||||
}
|
||||
|
||||
// rendering is the same as for block elements, we turn the abstract syntax array into a string.
|
||||
protected function renderStrike($element)
|
||||
{
|
||||
return '<del>' . $this->renderAbsy($element[1]) . '</del>';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Composing your own Markdown flavor
|
||||
|
||||
This markdown library is composed of traits so it is very easy to create your own markdown flavor by adding and/or removing
|
||||
the single feature traits.
|
||||
|
||||
Designing your Markdown flavor consists of four steps:
|
||||
|
||||
1. Select a base class
|
||||
2. Select language feature traits
|
||||
3. Define escapeable characters
|
||||
4. Optionally add custom rendering behavior
|
||||
|
||||
#### Select a base class
|
||||
|
||||
If you want to extend from a flavor and only add features you can use one of the existing classes
|
||||
(`Markdown`, `GithubMarkdown` or `MarkdownExtra`) as your flavors base class.
|
||||
|
||||
If you want to define a subset of the markdown language, i.e. remove some of the features, you have to
|
||||
extend your class from `Parser`.
|
||||
|
||||
#### Select language feature traits
|
||||
|
||||
The following shows the trait selection for traditional Markdown.
|
||||
|
||||
```php
|
||||
class MyMarkdown extends Parser
|
||||
{
|
||||
// include block element parsing using traits
|
||||
use block\CodeTrait;
|
||||
use block\HeadlineTrait;
|
||||
use block\HtmlTrait {
|
||||
parseInlineHtml as private;
|
||||
}
|
||||
use block\ListTrait {
|
||||
// Check Ul List before headline
|
||||
identifyUl as protected identifyBUl;
|
||||
consumeUl as protected consumeBUl;
|
||||
}
|
||||
use block\QuoteTrait;
|
||||
use block\RuleTrait {
|
||||
// Check Hr before checking lists
|
||||
identifyHr as protected identifyAHr;
|
||||
consumeHr as protected consumeAHr;
|
||||
}
|
||||
// include inline element parsing using traits
|
||||
use inline\CodeTrait;
|
||||
use inline\EmphStrongTrait;
|
||||
use inline\LinkTrait;
|
||||
|
||||
/**
|
||||
* @var boolean whether to format markup according to HTML5 spec.
|
||||
* Defaults to `false` which means that markup is formatted as HTML4.
|
||||
*/
|
||||
public $html5 = false;
|
||||
|
||||
protected function prepare()
|
||||
{
|
||||
// reset references
|
||||
$this->references = [];
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In general, just adding the trait with `use` is enough, however in some cases some fine tuning is desired
|
||||
to get most expected parsing results. Elements are detected in alphabetical order of their identification
|
||||
function. This means that if a line starting with `-` could be a list or a horizontal rule, the preference has to be set
|
||||
by renaming the identification function. This is what is done with renaming `identifyHr` to `identifyAHr`
|
||||
and `identifyBUl` to `identifyBUl`. The consume function always has to have the same name as the identification function
|
||||
so this has to be renamed too.
|
||||
|
||||
There is also a conflict for parsing of the `<` character. This could either be a link/email enclosed in `<` and `>`
|
||||
or an inline HTML tag. In order to resolve this conflict when adding the `LinkTrait`, we need to hide the `parseInlineHtml`
|
||||
method of the `HtmlTrait`.
|
||||
|
||||
If you use any trait that uses the `$html5` property to adjust its output you also need to define this property.
|
||||
|
||||
If you use the link trait it may be useful to implement `prepare()` as shown above to reset references before
|
||||
parsing to ensure you get a reusable object.
|
||||
|
||||
#### Define escapeable characters
|
||||
|
||||
Depending on the language features you have chosen there is a different set of characters that can be escaped
|
||||
using `\`. The following is the set of escapeable characters for traditional markdown, you can copy it to your class
|
||||
as is.
|
||||
|
||||
```php
|
||||
/**
|
||||
* @var array these are "escapeable" characters. When using one of these prefixed with a
|
||||
* backslash, the character will be outputted without the backslash and is not interpreted
|
||||
* as markdown.
|
||||
*/
|
||||
protected $escapeCharacters = [
|
||||
'\\', // backslash
|
||||
'`', // backtick
|
||||
'*', // asterisk
|
||||
'_', // underscore
|
||||
'{', '}', // curly braces
|
||||
'[', ']', // square brackets
|
||||
'(', ')', // parentheses
|
||||
'#', // hash mark
|
||||
'+', // plus sign
|
||||
'-', // minus sign (hyphen)
|
||||
'.', // dot
|
||||
'!', // exclamation mark
|
||||
'<', '>',
|
||||
];
|
||||
```
|
||||
|
||||
#### Add custom rendering behavior
|
||||
|
||||
Optionally you may also want to adjust rendering behavior by overriding some methods.
|
||||
You may refer to the `consumeParagraph()` method of the `Markdown` and `GithubMarkdown` classes for some inspiration
|
||||
which define different rules for which elements are allowed to interrupt a paragraph.
|
||||
|
||||
|
||||
Acknowledgements <a name="ack"></a>
|
||||
----------------
|
||||
|
||||
I'd like to thank [@erusev][] for creating [Parsedown][] which heavily influenced this work and provided
|
||||
the idea of the line based parsing approach.
|
||||
|
||||
[@erusev]: https://github.com/erusev "Emanuil Rusev"
|
||||
[Parsedown]: http://parsedown.org/ "The Parsedown PHP Markdown parser"
|
||||
|
||||
FAQ <a name="faq"></a>
|
||||
---
|
||||
|
||||
### Why another markdown parser?
|
||||
|
||||
While reviewing PHP markdown parsers for choosing one to use bundled with the [Yii framework 2.0][]
|
||||
I found that most of the implementations use regex to replace patterns instead
|
||||
of doing real parsing. This way extending them with new language elements is quite hard
|
||||
as you have to come up with a complex regex, that matches your addition but does not mess
|
||||
with other elements. Such additions are very common as you see on github which supports referencing
|
||||
issues, users and commits in the comments.
|
||||
A [real parser][] should use context aware methods that walk trough the text and
|
||||
parse the tokens as they find them. The only implentation that I have found that uses
|
||||
this approach is [Parsedown][] which also shows that this implementation is [much faster][benchmark]
|
||||
than the regex way. Parsedown however is an implementation that focuses on speed and implements
|
||||
its own flavor (mainly github flavored markdown) in one class and at the time of this writing was
|
||||
not easily extensible.
|
||||
|
||||
Given the situation above I decided to start my own implementation using the parsing approach
|
||||
from Parsedown and making it extensible creating a class for each markdown flavor that extend each
|
||||
other in the way that also the markdown languages extend each other.
|
||||
This allows you to choose between markdown language flavors and also provides a way to compose your
|
||||
own flavor picking the best things from all.
|
||||
I chose this approach as it is easier to implement and also more intuitive approach compared
|
||||
to using callbacks to inject functionallity into the parser.
|
||||
|
||||
[real parser]: http://en.wikipedia.org/wiki/Parsing#Types_of_parser
|
||||
|
||||
[Parsedown]: http://parsedown.org/ "The Parsedown PHP Markdown parser"
|
||||
|
||||
[Yii framework 2.0]: https://github.com/yiisoft/yii2
|
||||
|
||||
### Where do I report bugs or rendering issues?
|
||||
|
||||
Just [open an issue][] on github, post your markdown code and describe the problem. You may also attach screenshots of the rendered HTML result to describe your problem.
|
||||
|
||||
[open an issue]: https://github.com/cebe/markdown/issues/new
|
||||
|
||||
### How can I contribute to this library?
|
||||
|
||||
Check the [CONTRIBUTING.md](CONTRIBUTING.md) file for more info.
|
||||
|
||||
|
||||
### Am I free to use this?
|
||||
|
||||
This library is open source and licensed under the [MIT License][]. This means that you can do whatever you want
|
||||
with it as long as you mention my name and include the [license file][license]. Check the [license][] for details.
|
||||
|
||||
[MIT License]: http://opensource.org/licenses/MIT
|
||||
|
||||
[license]: https://github.com/cebe/markdown/blob/master/LICENSE
|
||||
|
||||
Contact
|
||||
-------
|
||||
|
||||
Feel free to contact me using [email](mailto:mail@cebe.cc) or [twitter](https://twitter.com/cebe_cc).
|
||||
170
vendor/cebe/markdown/bin/markdown
vendored
170
vendor/cebe/markdown/bin/markdown
vendored
@ -1,170 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2014 Carsten Brandt
|
||||
* @license https://github.com/cebe/markdown/blob/master/LICENSE
|
||||
* @link https://github.com/cebe/markdown#readme
|
||||
*/
|
||||
|
||||
$composerAutoload = [
|
||||
__DIR__ . '/../vendor/autoload.php', // standalone with "composer install" run
|
||||
__DIR__ . '/../../../autoload.php', // script is installed as a composer binary
|
||||
];
|
||||
foreach ($composerAutoload as $autoload) {
|
||||
if (file_exists($autoload)) {
|
||||
require($autoload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Send all errors to stderr
|
||||
ini_set('display_errors', 'stderr');
|
||||
|
||||
$flavor = 'cebe\\markdown\\Markdown';
|
||||
$flavors = [
|
||||
'gfm' => ['cebe\\markdown\\GithubMarkdown', __DIR__ . '/../GithubMarkdown.php'],
|
||||
'extra' => ['cebe\\markdown\\MarkdownExtra', __DIR__ . '/../MarkdownExtra.php'],
|
||||
];
|
||||
|
||||
$full = false;
|
||||
$src = [];
|
||||
foreach($argv as $k => $arg) {
|
||||
if ($k == 0) {
|
||||
continue;
|
||||
}
|
||||
if ($arg[0] == '-') {
|
||||
$arg = explode('=', $arg);
|
||||
switch($arg[0]) {
|
||||
case '--flavor':
|
||||
if (isset($arg[1])) {
|
||||
if (isset($flavors[$arg[1]])) {
|
||||
require($flavors[$arg[1]][1]);
|
||||
$flavor = $flavors[$arg[1]][0];
|
||||
} else {
|
||||
error("Unknown flavor: " . $arg[1], "usage");
|
||||
}
|
||||
} else {
|
||||
error("Incomplete argument --flavor!", "usage");
|
||||
}
|
||||
break;
|
||||
case '--full':
|
||||
$full = true;
|
||||
break;
|
||||
case '-h':
|
||||
case '--help':
|
||||
echo "PHP Markdown to HTML converter\n";
|
||||
echo "------------------------------\n\n";
|
||||
echo "by Carsten Brandt <mail@cebe.cc>\n\n";
|
||||
usage();
|
||||
break;
|
||||
default:
|
||||
error("Unknown argument " . $arg[0], "usage");
|
||||
}
|
||||
} else {
|
||||
$src[] = $arg;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($src)) {
|
||||
$markdown = file_get_contents("php://stdin");
|
||||
} elseif (count($src) == 1) {
|
||||
$file = reset($src);
|
||||
if (!file_exists($file)) {
|
||||
error("File does not exist:" . $file);
|
||||
}
|
||||
$markdown = file_get_contents($file);
|
||||
} else {
|
||||
error("Converting multiple files is not yet supported.", "usage");
|
||||
}
|
||||
|
||||
/** @var cebe\markdown\Parser $md */
|
||||
$md = new $flavor();
|
||||
$markup = $md->parse($markdown);
|
||||
|
||||
if ($full) {
|
||||
echo <<<HTML
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; }
|
||||
code { background: #eeeeff; padding: 2px; }
|
||||
li { margin-bottom: 5px; }
|
||||
img { max-width: 1200px; }
|
||||
table, td, th { border: solid 1px #ccc; border-collapse: collapse; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
$markup
|
||||
</body>
|
||||
</html>
|
||||
HTML;
|
||||
} else {
|
||||
echo $markup;
|
||||
}
|
||||
|
||||
// functions
|
||||
|
||||
/**
|
||||
* Display usage information
|
||||
*/
|
||||
function usage() {
|
||||
global $argv;
|
||||
$cmd = $argv[0];
|
||||
echo <<<EOF
|
||||
Usage:
|
||||
$cmd [--flavor=<flavor>] [--full] [file.md]
|
||||
|
||||
--flavor specifies the markdown flavor to use. If omitted the original markdown by John Gruber [1] will be used.
|
||||
Available flavors:
|
||||
|
||||
gfm - Github flavored markdown [2]
|
||||
extra - Markdown Extra [3]
|
||||
|
||||
--full ouput a full HTML page with head and body. If not given, only the parsed markdown will be output.
|
||||
|
||||
--help shows this usage information.
|
||||
|
||||
If no file is specified input will be read from STDIN.
|
||||
|
||||
Examples:
|
||||
|
||||
Render a file with original markdown:
|
||||
|
||||
$cmd README.md > README.html
|
||||
|
||||
Render a file using gihtub flavored markdown:
|
||||
|
||||
$cmd --flavor=gfm README.md > README.html
|
||||
|
||||
Convert the original markdown description to html using STDIN:
|
||||
|
||||
curl http://daringfireball.net/projects/markdown/syntax.text | $cmd > md.html
|
||||
|
||||
|
||||
[1] http://daringfireball.net/projects/markdown/syntax
|
||||
[2] https://help.github.com/articles/github-flavored-markdown
|
||||
[3] http://michelf.ca/projects/php-markdown/extra/
|
||||
|
||||
EOF;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send custom error message to stderr
|
||||
* @param $message string
|
||||
* @param $callback mixed called before script exit
|
||||
* @return void
|
||||
*/
|
||||
function error($message, $callback = null) {
|
||||
$fe = fopen("php://stderr", "w");
|
||||
fwrite($fe, "Error: " . $message . "\n");
|
||||
|
||||
if (is_callable($callback)) {
|
||||
call_user_func($callback);
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
66
vendor/cebe/markdown/block/CodeTrait.php
vendored
66
vendor/cebe/markdown/block/CodeTrait.php
vendored
@ -1,66 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2014 Carsten Brandt
|
||||
* @license https://github.com/cebe/markdown/blob/master/LICENSE
|
||||
* @link https://github.com/cebe/markdown#readme
|
||||
*/
|
||||
|
||||
namespace cebe\markdown\block;
|
||||
|
||||
/**
|
||||
* Adds the 4 space indented code blocks
|
||||
*/
|
||||
trait CodeTrait
|
||||
{
|
||||
/**
|
||||
* identify a line as the beginning of a code block.
|
||||
*/
|
||||
protected function identifyCode($line)
|
||||
{
|
||||
// indentation >= 4 or one tab is code
|
||||
return ($l = $line[0]) === ' ' && $line[1] === ' ' && $line[2] === ' ' && $line[3] === ' ' || $l === "\t";
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume lines for a code block element
|
||||
*/
|
||||
protected function consumeCode($lines, $current)
|
||||
{
|
||||
// consume until newline
|
||||
|
||||
$content = [];
|
||||
for ($i = $current, $count = count($lines); $i < $count; $i++) {
|
||||
$line = $lines[$i];
|
||||
|
||||
// a line is considered to belong to this code block as long as it is intended by 4 spaces or a tab
|
||||
if (isset($line[0]) && ($line[0] === "\t" || strncmp($line, ' ', 4) === 0)) {
|
||||
$line = $line[0] === "\t" ? substr($line, 1) : substr($line, 4);
|
||||
$content[] = $line;
|
||||
// but also if it is empty and the next line is intended by 4 spaces or a tab
|
||||
} elseif (($line === '' || rtrim($line) === '') && isset($lines[$i + 1][0]) &&
|
||||
($lines[$i + 1][0] === "\t" || strncmp($lines[$i + 1], ' ', 4) === 0)) {
|
||||
if ($line !== '') {
|
||||
$line = $line[0] === "\t" ? substr($line, 1) : substr($line, 4);
|
||||
}
|
||||
$content[] = $line;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$block = [
|
||||
'code',
|
||||
'content' => implode("\n", $content),
|
||||
];
|
||||
return [$block, --$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a code block
|
||||
*/
|
||||
protected function renderCode($block)
|
||||
{
|
||||
$class = isset($block['language']) ? ' class="language-' . $block['language'] . '"' : '';
|
||||
return "<pre><code$class>" . htmlspecialchars($block['content'] . "\n", ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8') . "</code></pre>\n";
|
||||
}
|
||||
}
|
||||
58
vendor/cebe/markdown/block/FencedCodeTrait.php
vendored
58
vendor/cebe/markdown/block/FencedCodeTrait.php
vendored
@ -1,58 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2014 Carsten Brandt
|
||||
* @license https://github.com/cebe/markdown/blob/master/LICENSE
|
||||
* @link https://github.com/cebe/markdown#readme
|
||||
*/
|
||||
|
||||
namespace cebe\markdown\block;
|
||||
|
||||
/**
|
||||
* Adds the fenced code blocks
|
||||
*
|
||||
* automatically included 4 space indented code blocks
|
||||
*/
|
||||
trait FencedCodeTrait
|
||||
{
|
||||
use CodeTrait;
|
||||
|
||||
/**
|
||||
* identify a line as the beginning of a fenced code block.
|
||||
*/
|
||||
protected function identifyFencedCode($line)
|
||||
{
|
||||
return ($line[0] === '`' && strncmp($line, '```', 3) === 0) ||
|
||||
($line[0] === '~' && strncmp($line, '~~~', 3) === 0) ||
|
||||
(isset($line[3]) && (
|
||||
($line[3] === '`' && strncmp(ltrim($line), '```', 3) === 0) ||
|
||||
($line[3] === '~' && strncmp(ltrim($line), '~~~', 3) === 0)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume lines for a fenced code block
|
||||
*/
|
||||
protected function consumeFencedCode($lines, $current)
|
||||
{
|
||||
$line = ltrim($lines[$current]);
|
||||
$fence = substr($line, 0, $pos = strrpos($line, $line[0]) + 1);
|
||||
$language = rtrim(substr($line, $pos));
|
||||
// consume until end fence
|
||||
$content = [];
|
||||
for ($i = $current + 1, $count = count($lines); $i < $count; $i++) {
|
||||
if (($pos = strpos($line = $lines[$i], $fence)) === false || $pos > 3) {
|
||||
$content[] = $line;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$block = [
|
||||
'code',
|
||||
'content' => implode("\n", $content),
|
||||
];
|
||||
if (!empty($language)) {
|
||||
$block['language'] = $language;
|
||||
}
|
||||
return [$block, $i];
|
||||
}
|
||||
}
|
||||
70
vendor/cebe/markdown/block/HeadlineTrait.php
vendored
70
vendor/cebe/markdown/block/HeadlineTrait.php
vendored
@ -1,70 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2014 Carsten Brandt
|
||||
* @license https://github.com/cebe/markdown/blob/master/LICENSE
|
||||
* @link https://github.com/cebe/markdown#readme
|
||||
*/
|
||||
|
||||
namespace cebe\markdown\block;
|
||||
|
||||
/**
|
||||
* Adds the headline blocks
|
||||
*/
|
||||
trait HeadlineTrait
|
||||
{
|
||||
/**
|
||||
* identify a line as a headline
|
||||
*/
|
||||
protected function identifyHeadline($line, $lines, $current)
|
||||
{
|
||||
return (
|
||||
// heading with #
|
||||
$line[0] === '#' && !preg_match('/^#\d+/', $line)
|
||||
||
|
||||
// underlined headline
|
||||
!empty($lines[$current + 1]) &&
|
||||
(($l = $lines[$current + 1][0]) === '=' || $l === '-') &&
|
||||
preg_match('/^(\-+|=+)\s*$/', $lines[$current + 1])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume lines for a headline
|
||||
*/
|
||||
protected function consumeHeadline($lines, $current)
|
||||
{
|
||||
if ($lines[$current][0] === '#') {
|
||||
// ATX headline
|
||||
$level = 1;
|
||||
while (isset($lines[$current][$level]) && $lines[$current][$level] === '#' && $level < 6) {
|
||||
$level++;
|
||||
}
|
||||
$block = [
|
||||
'headline',
|
||||
'content' => $this->parseInline(trim($lines[$current], "# \t")),
|
||||
'level' => $level,
|
||||
];
|
||||
return [$block, $current];
|
||||
} else {
|
||||
// underlined headline
|
||||
$block = [
|
||||
'headline',
|
||||
'content' => $this->parseInline($lines[$current]),
|
||||
'level' => $lines[$current + 1][0] === '=' ? 1 : 2,
|
||||
];
|
||||
return [$block, $current + 1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a headline
|
||||
*/
|
||||
protected function renderHeadline($block)
|
||||
{
|
||||
$tag = 'h' . $block['level'];
|
||||
return "<$tag>" . $this->renderAbsy($block['content']) . "</$tag>\n";
|
||||
}
|
||||
|
||||
abstract protected function parseInline($text);
|
||||
abstract protected function renderAbsy($absy);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user