Skip to content

Commit c17f3c8

Browse files
Gherkin PHP implementation (#1911)
* Gherkin PHP folder with testdata * Generated parser with correct types * Working tokeniser * Emit Source messages * Emit ASTs * Emit Pickles * Emit Error messages * Update changelog, license, readme, package description * Change GherkinParser::parse API to remove responsibility for file reading * Fix rsync issues * Add local messages repository for Composer This allows us to 'install' messages from local, which is useful when working across project * Support UUID IDs * Removed redundant lines, fixed some incorrect docblocks * Check public-facing parse() receives Source objects * Remove redundant getters * Scan tokens progressively rather than splitting into a big array * Fix some wrong imports * Don't throw logic exceptions during parsing * Stricter return type for AstNode::getSingle * Regex for efficiently splitting table cells Thanks to Gregor Harlan <[email protected]> * Refactor GherkinLIne handling to more idiomatic PHP (and regex for cell parsing) * Rationalise the Makefile setup * Remove unused Token::detach method * Remove local path repository for pre-release * Add parallel PHP gherkin job * Scripts to update the composer dependencies pre and post-release * Add Gherkin-php subrepo * Enforce trailing commas * Various refactorings from code review * Better makefiles
1 parent 597504f commit c17f3c8

File tree

11 files changed

+191
-37
lines changed

11 files changed

+191
-37
lines changed

php/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ composer.lock
44
.php-cs-fixer.cache
55
.codegen
66
.deps
7+
.tested

php/.php-cs-fixer.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
$finder = PhpCsFixer\Finder::create()
3+
->in([
4+
__DIR__ . '/src',
5+
__DIR__ . '/src-generated',
6+
__DIR__ . '/tests',
7+
])
8+
;
9+
10+
$config = new PhpCsFixer\Config();
11+
return $config->setFinder($finder);

php/Makefile

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,20 @@ JSONSCHEMAS = $(shell find ../jsonschema -name "*.json")
44

55
clean: clean-build
66

7-
.codegen: $(JSONSCHEMAS) ../jsonschema/scripts/codegen.rb ../jsonschema/scripts/templates/php.php.erb
7+
.codegen: $(JSONSCHEMAS) ../jsonschema/scripts/codegen.rb ../jsonschema/scripts/templates/php.php.erb build/messages.php
8+
9+
post-release: update-gherkin-dependency
10+
11+
update-gherkin-dependency:
12+
php scripts/update-gherkin-dependency.php ../../gherkin/php/composer.json > gherkin-composer.json
13+
jq --indent 4 < gherkin-composer.json > ../../gherkin/php/composer.json
14+
rm gherkin-composer.json
15+
.PHONY: update-gherkin-dependency
16+
17+
build/messages.php:
818
ruby ../jsonschema/scripts/codegen.rb Php ../jsonschema php.php.erb > build/messages.php
919
ruby ../jsonschema/scripts/codegen.rb Php ../jsonschema php.enum.php.erb >> build/messages.php
1020
csplit --quiet --prefix=build/Generated --suffix-format=%02d.php.tmp --elide-empty-files build/messages.php /^.*[.]php$$/ {*}
11-
rm build/messages.php
1221
rm -rf src-generated/*
1322
for file in build/Generated**; do mkdir -p src-generated/$$(head -n 1 $$file | sed 's/[^/]*.php$$//'); done
1423
for file in build/Generated**; do tail -n +2 $$file > src-generated/$$(head -n 1 $$file); rm $$file; done
@@ -17,10 +26,3 @@ clean: clean-build
1726
clean-build:
1827
rm -rf build/messages.php
1928
rm -rf src-generated/*
20-
21-
.tested: .cs-fixer
22-
23-
.cs-fixer:
24-
vendor/bin/php-cs-fixer --dry-run --diff fix src
25-
vendor/bin/php-cs-fixer --dry-run --diff fix src-generated
26-
vendor/bin/php-cs-fixer --dry-run --diff fix tests

php/composer.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"type": "library",
77
"require": {
88
"php": "^8.1",
9-
"ext-json": "*"
9+
"ext-json": "*",
10+
"ramsey/uuid": "^4.2"
1011
},
1112
"require-dev": {
1213
"vimeo/psalm": "^4.18",
@@ -16,7 +17,19 @@
1617
},
1718
"autoload": {
1819
"psr-4": {
19-
"Cucumber\\Messages\\": ["src","src-generated"]
20+
"Cucumber\\Messages\\": [
21+
"src",
22+
"src-generated"
23+
]
24+
}
25+
},
26+
"autoload-dev": {
27+
"psr-4": {
28+
"Cucumber\\Messages\\": [
29+
"src",
30+
"src-generated",
31+
"tests"
32+
]
2033
}
2134
}
2235
}

php/default.mk

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ PHP_SOURCE_FILES = $(shell find . -name "*.php")
77

88
### COMMON stuff for all platforms
99

10+
BERP_VERSION = 1.3.0
11+
BERP_GRAMMAR = gherkin.berp
12+
13+
define berp-generate-parser =
14+
-! dotnet tool list --tool-path /usr/bin | grep "berp\s*$(BERP_VERSION)" && dotnet tool update Berp --version $(BERP_VERSION) --tool-path /usr/bin
15+
berp -g $(BERP_GRAMMAR) -t $< -o $@ --noBOM
16+
endef
17+
18+
1019
### Common targets for all functionalities implemented on php
1120

1221
default: .tested
@@ -22,6 +31,7 @@ endif
2231
.PHONY: update-version
2332

2433
update-dependencies:
34+
composer update
2535
.PHONY: update-dependencies
2636

2737
publish:
@@ -33,20 +43,21 @@ post-release:
3343
.PHONY: post-release
3444

3545
clean:
46+
rm -rf .tested .deps .codegen
3647
rm -rf vendor composer.lock
3748
.PHONY: clean
3849

39-
.tested: .deps .codegen $(PHP_SOURCE_FILES)
50+
.tested: .deps $(PHP_SOURCE_FILES)
51+
vendor/bin/php-cs-fixer --dry-run --diff fix
52+
vendor/bin/psalm --no-cache
4053
vendor/bin/phpunit
41-
vendor/bin/psalm
42-
.PHONY: .tested
54+
touch $@
4355

44-
.deps: composer.lock
56+
.deps: vendor .codegen
4557
touch $@
4658

4759
.codegen:
4860
touch $@
4961

50-
composer.lock: composer.json
62+
vendor: composer.json
5163
composer install
52-
touch $@
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/**
4+
* Updates the dependency in the Composer repo (outputs unindented string)
5+
*/
6+
7+
if ($argc !== 2) {
8+
fwrite(STDERR, 'Please provide a single JSON file path' . "\n");
9+
exit(1);
10+
}
11+
12+
$composerFile = $argv[1];
13+
14+
if(!$newVersion = getenv('NEW_VERSION')) {
15+
fwrite(STDERR, 'NEW_VERSION env var must be set' . "\n");
16+
exit(1);
17+
}
18+
19+
try {
20+
$json = json_decode(@file_get_contents($composerFile), true, flags: JSON_THROW_ON_ERROR);
21+
}
22+
catch (\Throwable $t)
23+
{
24+
fwrite(STDERR, 'Could not read JSON from ' . $composerFile . "\n");
25+
exit(1);
26+
}
27+
28+
if (!isset($json['require']['cucumber/messages'])) {
29+
fwrite(STDERR, 'Provided JSON does not have a dependency on cucumber/messages' . "\n");
30+
exit(1);
31+
}
32+
33+
$dependencyString = $json['require']['cucumber/messages'];
34+
35+
if (!preg_match('/^(?<major>\\d+)\\.\\d+\\.\\d+$/', $newVersion, $matches)) {
36+
fwrite(STDERR, 'New version was not in the format XX.XX.XX' . "\n");
37+
exit(1);
38+
}
39+
40+
// like ^21.0 (same as >=21.0.0 <22.0.0)
41+
$newDependency = ',^'.$matches['major'].'.0';
42+
43+
if (str_contains($dependencyString, $newDependency)) {
44+
fwrite(STDERR, 'Nothing to update, already depends on ' . $newDependency . "\n");
45+
$newDependency = '';
46+
}
47+
48+
$newDependencyString = $dependencyString . $newDependency;
49+
50+
$json['require']['cucumber/messages'] = $newDependencyString;
51+
52+
echo json_encode($json);

php/src/Id/UuidIdGenerator.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cucumber\Messages\Id;
6+
7+
use Ramsey\Uuid\Uuid;
8+
9+
final class UuidIdGenerator implements IdGenerator
10+
{
11+
public function newId(): string
12+
{
13+
return Uuid::uuid4()->toString();
14+
}
15+
}

php/tests/Cucumber/Messages/Id/IncrementingIdGeneratorTest.php

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Cucumber\Messages\Id;
4+
5+
trait IdGeneratorTestTrait
6+
{
7+
private IdGenerator $idGenerator;
8+
9+
public function testItDoesNotGenerateEmptyIds(): void
10+
{
11+
self::assertNotEquals('', $this->idGenerator->newId());
12+
}
13+
14+
public function testItDoesNotRepeatIds(): void
15+
{
16+
$id1 = $this->idGenerator->newId();
17+
$id2 = $this->idGenerator->newId();
18+
19+
self::assertNotEquals($id1, $id2);
20+
}
21+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cucumber\Messages\Id;
6+
7+
use PHPUnit\Framework\TestCase;
8+
9+
final class IncrementingIdGeneratorTest extends TestCase
10+
{
11+
use IdGeneratorTestTrait;
12+
13+
public function setUp(): void
14+
{
15+
$this->idGenerator = new IncrementingIdGenerator();
16+
}
17+
18+
public function testItIncrementsFromZero(): void
19+
{
20+
self::assertSame('0', $this->idGenerator->newId());
21+
self::assertSame('1', $this->idGenerator->newId());
22+
self::assertSame('2', $this->idGenerator->newId());
23+
self::assertSame('3', $this->idGenerator->newId());
24+
}
25+
}

0 commit comments

Comments
 (0)