├── .github
├── renovate.json
└── workflows
│ ├── release-github.yaml
│ ├── release-mvn.yml
│ ├── release-npm.yml
│ ├── release-perl.yml
│ ├── release-php.yaml
│ ├── release-pypi.yaml
│ ├── release-rubygem.yml
│ ├── stryker-javascript.yml
│ ├── test-go.yml
│ ├── test-java.yml
│ ├── test-javascript.yml
│ ├── test-perl.yml
│ ├── test-php.yml
│ ├── test-python.yml
│ └── test-ruby.yml
├── .gitignore
├── ARCHITECTURE.md
├── CHANGELOG.md
├── LICENSE
├── README.md
├── RELEASING.md
├── go
├── .gitignore
├── Makefile
├── README.md
├── errors_test.go
├── evaluations_test.go
├── go.mod
├── go.sum
├── parser.go
├── parsing_test.go
└── stack.go
├── java
├── .gitignore
├── README.md
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── io
│ │ └── cucumber
│ │ └── tagexpressions
│ │ ├── Expression.java
│ │ ├── TagExpressionException.java
│ │ └── TagExpressionParser.java
│ └── test
│ └── java
│ └── io
│ └── cucumber
│ └── tagexpressions
│ ├── ErrorsTest.java
│ ├── EvaluationsTest.java
│ └── ParsingTest.java
├── javascript
├── .gitignore
├── .mocharc.json
├── .prettierrc.json
├── CONTRIBUTING.md
├── README.md
├── eslint.config.mjs
├── package-lock.json
├── package.cjs.json
├── package.json
├── src
│ └── index.ts
├── stryker.conf.json
├── test
│ ├── errors.test.ts
│ ├── evaluations.test.ts
│ ├── parsing.test.ts
│ └── testDataDir.ts
├── tsconfig.build-cjs.json
├── tsconfig.build-esm.json
├── tsconfig.build.json
└── tsconfig.json
├── perl
├── .gitignore
├── Makefile
├── VERSION
├── cpanfile
├── default.mk
├── dist.ini
├── lib
│ └── Cucumber
│ │ ├── TagExpressions.pm
│ │ └── TagExpressions
│ │ └── Node.pm
└── t
│ ├── 02-evaluate.t
│ └── 03-shared-tests.t
├── php
├── LICENSE
├── README.md
├── composer.json
└── src
│ ├── Associativity.php
│ ├── Expression.php
│ ├── Expression
│ ├── AndExpression.php
│ ├── LiteralExpression.php
│ ├── NotExpression.php
│ ├── OrExpression.php
│ └── TrueExpression.php
│ ├── TagExpressionException.php
│ ├── TagExpressionParser.php
│ └── TokenType.php
├── python
├── .coveragerc
├── .editorconfig
├── .envrc
├── .envrc.use_venv
├── .gitignore
├── .pylintrc
├── DEVELOPMENT.rst
├── MANIFEST.in
├── Makefile
├── README.rst
├── cucumber_tag_expressions
│ ├── __init__.py
│ ├── model.py
│ └── parser.py
├── default.mk
├── invoke.yaml
├── py.requirements
│ ├── README.txt
│ ├── all.txt
│ ├── basic.txt
│ ├── ci.github.testing.txt
│ ├── develop.txt
│ ├── packaging.txt
│ └── testing.txt
├── pyproject.toml
├── pytest.ini
├── scripts
│ ├── ensurepip_python27.sh
│ ├── pytest_cmd.py
│ └── toxcmd.py
├── tasks
│ ├── __init__.py
│ ├── __main__.py
│ ├── _compat_shutil.py
│ ├── _dry_run.py
│ ├── _path.py
│ ├── _setup.py
│ ├── invoke_cleanup.py
│ ├── py.requirements.txt
│ └── test.py
├── tests
│ ├── data
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── test_errors.py
│ │ ├── test_evaluations.py
│ │ └── test_parsing.py
│ ├── functional
│ │ ├── __init__.py
│ │ └── test_tag_expression.py
│ └── unit
│ │ ├── __init__.py
│ │ ├── test_model.py
│ │ └── test_parser.py
└── tox.ini
├── ruby
├── .gitignore
├── .rspec
├── .rubocop.yml
├── .rubocop_todo.yml
├── Gemfile
├── README.md
├── Rakefile
├── VERSION
├── cucumber-tag-expressions.gemspec
├── lib
│ └── cucumber
│ │ ├── tag_expressions.rb
│ │ └── tag_expressions
│ │ ├── expressions.rb
│ │ └── parser.rb
└── spec
│ ├── errors_spec.rb
│ ├── evaluations_spec.rb
│ ├── parsing_spec.rb
│ └── spec_helper.rb
└── testdata
├── errors.yml
├── evaluations.yml
└── parsing.yml
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "github>cucumber/renovate-config"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.github/workflows/release-github.yaml:
--------------------------------------------------------------------------------
1 | name: Release GitHub
2 |
3 | on:
4 | push:
5 | branches: [release/*]
6 |
7 | jobs:
8 | create-github-release:
9 | name: Create GitHub Release and Git tag
10 | runs-on: ubuntu-latest
11 | environment: Release
12 | permissions:
13 | contents: write
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: cucumber/action-create-github-release@v1.1.1
17 | with:
18 | github-token: ${{ secrets.GITHUB_TOKEN }}
19 |
--------------------------------------------------------------------------------
/.github/workflows/release-mvn.yml:
--------------------------------------------------------------------------------
1 | name: Release Maven
2 |
3 | on:
4 | push:
5 | branches: [release/*]
6 |
7 | jobs:
8 | publish-mvn:
9 | name: Publish Maven Package
10 | runs-on: ubuntu-latest
11 | environment: Release
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-java@v4
15 | with:
16 | distribution: 'temurin'
17 | java-version: '17'
18 | cache: 'maven'
19 | - uses: cucumber/action-publish-mvn@v3.0.0
20 | with:
21 | gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
22 | gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
23 | nexus-username: ${{ secrets.SONATYPE_USERNAME }}
24 | nexus-password: ${{ secrets.SONATYPE_PASSWORD }}
25 | working-directory: java
26 |
--------------------------------------------------------------------------------
/.github/workflows/release-npm.yml:
--------------------------------------------------------------------------------
1 | name: Release NPM
2 |
3 | on:
4 | push:
5 | branches: [release/*]
6 |
7 | jobs:
8 | publish-npm:
9 | name: Publish NPM module
10 | runs-on: ubuntu-latest
11 | environment: Release
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: '22.x'
17 | cache: 'npm'
18 | cache-dependency-path: javascript/package-lock.json
19 | - run: npm install-test
20 | working-directory: javascript
21 | - uses: cucumber/action-publish-npm@v1.1.1
22 | with:
23 | npm-token: ${{ secrets.NPM_TOKEN }}
24 | working-directory: javascript
25 |
--------------------------------------------------------------------------------
/.github/workflows/release-perl.yml:
--------------------------------------------------------------------------------
1 | name: Publish to CPAN
2 |
3 | on:
4 | push:
5 | branches: [release/*]
6 |
7 | jobs:
8 | publish-ui:
9 | name: Publish to CPAN
10 | runs-on: ubuntu-latest
11 | environment: Release
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: cucumber/action-publish-cpan@v1.0.1
15 | with:
16 | cpan-user: ${{ secrets.CPAN_USER }}
17 | cpan-password: ${{ secrets.CPAN_PASSWORD }}
18 | working-directory: "perl"
19 |
--------------------------------------------------------------------------------
/.github/workflows/release-php.yaml:
--------------------------------------------------------------------------------
1 | name: Release PHP
2 |
3 | on:
4 | push:
5 | branches: [release/*]
6 |
7 | jobs:
8 | publish-php-subrepo:
9 | name: Publish to cucumber/tag-expressions-php subrepo
10 | runs-on: ubuntu-latest
11 | environment: Release
12 | permissions:
13 | contents: write
14 | steps:
15 | - uses: actions/checkout@v4
16 | with:
17 | fetch-depth: '0'
18 | - uses: cucumber/action-publish-subrepo@v1.1.1
19 | with:
20 | working-directory: php
21 | github-token: ${{ secrets.CUKEBOT_GITHUB_TOKEN }}
22 |
--------------------------------------------------------------------------------
/.github/workflows/release-pypi.yaml:
--------------------------------------------------------------------------------
1 | name: Release Python
2 |
3 | on:
4 | push:
5 | branches: [release/*]
6 |
7 | jobs:
8 | release:
9 | name: Release
10 | runs-on: ubuntu-latest
11 | environment: Release
12 | permissions:
13 | id-token: write
14 | defaults:
15 | run:
16 | working-directory: python
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v4
20 | - name: Set up Python 3.10
21 | uses: actions/setup-python@v5
22 | with:
23 | python-version: "3.13"
24 | - name: Show Python version
25 | run: python --version
26 |
27 | - name: Build package
28 | run: |
29 | python -m pip install build twine
30 | python -m build
31 | twine check --strict dist/*
32 |
33 | - name: Publish package distributions to PyPI
34 | uses: pypa/gh-action-pypi-publish@release/v1
35 | with:
36 | packages-dir: python/dist
37 |
--------------------------------------------------------------------------------
/.github/workflows/release-rubygem.yml:
--------------------------------------------------------------------------------
1 | name: Release RubyGems
2 |
3 | on:
4 | push:
5 | branches: [release/*]
6 |
7 | jobs:
8 | publish-rubygem:
9 | name: Publish Ruby Gem
10 | runs-on: ubuntu-latest
11 | environment: Release
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: ruby/setup-ruby@v1
15 | with:
16 | ruby-version: '3.4'
17 | bundler-cache: true
18 | - uses: cucumber/action-publish-rubygem@v1.0.0
19 | with:
20 | rubygems_api_key: ${{ secrets.RUBYGEMS_API_KEY }}
21 | working_directory: ruby
22 |
--------------------------------------------------------------------------------
/.github/workflows/stryker-javascript.yml:
--------------------------------------------------------------------------------
1 | name: stryker-javascript
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | - renovate/**
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | stryker-javascript:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-node@v4
17 | with:
18 | node-version: '22.x'
19 | cache: 'npm'
20 | cache-dependency-path: javascript/package-lock.json
21 | - run: npm install
22 | working-directory: javascript
23 | - run: npm run stryker
24 | working-directory: javascript
25 | - uses: actions/upload-artifact@v4
26 | with:
27 | name: stryker-report
28 | path: |
29 | javascript/reports/mutation/html/index.html
30 | javascript/reports/mutation/mutation.json
31 |
--------------------------------------------------------------------------------
/.github/workflows/test-go.yml:
--------------------------------------------------------------------------------
1 | name: test-go
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - renovate/**
8 | paths:
9 | - go/**
10 | - testdata/**
11 | - .github/**
12 | pull_request:
13 | branches:
14 | - main
15 | - renovate/**
16 | paths:
17 | - go/**
18 | - testdata/**
19 | - .github/**
20 | workflow_call:
21 |
22 | jobs:
23 | test-go:
24 | runs-on: ${{ matrix.os }}
25 | strategy:
26 | fail-fast: false
27 | matrix:
28 | os:
29 | - ubuntu-latest
30 | go-version: ['1.20.x', '1.21.x']
31 | include:
32 | - os: windows-latest
33 | go-version: '1.21.x'
34 | - os: macos-latest
35 | go-version: '1.21.x'
36 | steps:
37 | - uses: actions/checkout@v4
38 | - name: Set up Go
39 | uses: actions/setup-go@v5
40 | with:
41 | go-version: ${{ matrix.go-version }}
42 | - name: lint
43 | working-directory: go
44 | run: gofmt -w .
45 | - name: test
46 | working-directory: go
47 | run: go test ./...
48 |
--------------------------------------------------------------------------------
/.github/workflows/test-java.yml:
--------------------------------------------------------------------------------
1 | name: test-java
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - renovate/**
8 | paths:
9 | - java/**
10 | - testdata/**
11 | - .github/**
12 | pull_request:
13 | branches:
14 | - main
15 | - renovate/**
16 | paths:
17 | - java/**
18 | - testdata/**
19 | - .github/**
20 | workflow_call:
21 |
22 | jobs:
23 | test-java:
24 | runs-on: ${{ matrix.os }}
25 | strategy:
26 | fail-fast: false
27 | matrix:
28 | os:
29 | - ubuntu-latest
30 | java: ['11', '17']
31 | include:
32 | - os: windows-latest
33 | java: '17'
34 | - os: macos-latest
35 | java: '17'
36 | steps:
37 | - uses: actions/checkout@v4
38 | - uses: actions/setup-java@v4
39 | with:
40 | distribution: 'temurin'
41 | java-version: ${{ matrix.java }}
42 | cache: 'maven'
43 | - run: mvn install
44 | working-directory: java
45 | - run: mvn test
46 | working-directory: java
47 |
--------------------------------------------------------------------------------
/.github/workflows/test-javascript.yml:
--------------------------------------------------------------------------------
1 | name: test-javascript
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - renovate/**
8 | paths:
9 | - javascript/**
10 | - testdata/**
11 | - .github/**
12 | pull_request:
13 | branches:
14 | - main
15 | - renovate/**
16 | paths:
17 | - javascript/**
18 | - testdata/**
19 | - .github/**
20 | workflow_call:
21 |
22 | jobs:
23 | test-javascript:
24 | runs-on: ${{ matrix.os }}
25 | strategy:
26 | fail-fast: false
27 | matrix:
28 | os:
29 | - ubuntu-latest
30 | node-version: ['18.x', '20.x', '22.x', '23.x']
31 | include:
32 | - os: windows-latest
33 | node-version: '22.x'
34 | - os: macos-latest
35 | node-version: '22.x'
36 | steps:
37 | - name: set git core.autocrlf to 'input'
38 | run: git config --global core.autocrlf input
39 | - uses: actions/checkout@v4
40 | - name: with Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
41 | uses: actions/setup-node@v4
42 | with:
43 | node-version: ${{ matrix.node-version }}
44 | cache: 'npm'
45 | cache-dependency-path: javascript/package-lock.json
46 | - run: npm install-test
47 | working-directory: javascript
48 | - run: npm run lint
49 | working-directory: javascript
50 |
--------------------------------------------------------------------------------
/.github/workflows/test-perl.yml:
--------------------------------------------------------------------------------
1 | name: test-perl
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - renovate/**
8 | paths:
9 | - perl/**
10 | - testdata/**
11 | - .github/**
12 | pull_request:
13 | branches:
14 | - main
15 | - renovate/**
16 | paths:
17 | - perl/**
18 | - testdata/**
19 | - .github/**
20 | workflow_call:
21 |
22 | jobs:
23 | test-perl:
24 | runs-on: ubuntu-latest
25 | strategy:
26 | matrix:
27 | perl: [ "5.14", "5.20", "5.26", "5.32" ]
28 | steps:
29 | - name: Check out repository
30 | uses: actions/checkout@v4
31 | - name: Setup Perl environment
32 | uses: shogo82148/actions-setup-perl@v1
33 | with:
34 | perl-version: ${{ matrix.perl }}
35 | working-directory: perl
36 | - name: Run tests
37 | working-directory: perl
38 | run: make test
39 | # Run author tests second so as to not 'contaminate' the environment
40 | # with dependencies listed as author deps when they should have been
41 | # listed as general deps
42 | - name: Run author tests
43 | working-directory: perl
44 | run: make authortest
45 |
--------------------------------------------------------------------------------
/.github/workflows/test-php.yml:
--------------------------------------------------------------------------------
1 | name: test-php
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - renovate/**
8 | paths:
9 | - php/**
10 | - testdata/**
11 | - .github/**
12 | pull_request:
13 | branches:
14 | - main
15 | paths:
16 | - php/**
17 | - testdata/**
18 | - .github/**
19 | workflow_call:
20 |
21 | permissions:
22 | contents: read
23 |
24 | jobs:
25 | test-php:
26 | runs-on: ubuntu-latest
27 | strategy:
28 | matrix:
29 | php: ['8.1', '8.2', '8.3', '8.4']
30 | composer-mode: ['high-deps']
31 | include:
32 | - php: '8.1'
33 | composer-mode: 'low-deps'
34 |
35 |
36 | steps:
37 | - uses: actions/checkout@v4
38 |
39 | - name: Set up PHP
40 | uses: shivammathur/setup-php@v2
41 | with:
42 | php-version: "${{ matrix.php }}"
43 | env:
44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 |
46 | - name: Discover composer cache directory
47 | id: composer-cache
48 | run: echo "dir=$(composer config cache-dir)" >> $GITHUB_OUTPUT
49 | working-directory: php
50 |
51 | - name: Cache composer
52 | uses: actions/cache@v4
53 | with:
54 | path: "${{ steps.composer-cache.outputs.dir }}"
55 | key: composer
56 |
57 | - name: Install dependencies
58 | id: install
59 | working-directory: php
60 | run: |
61 | if [[ "${{ matrix.composer-mode }}" = "low-deps" ]]; then
62 | composer update --prefer-lowest
63 | else
64 | composer update
65 | fi
66 |
67 | - name: Lint coding standards
68 | if: always() && steps.install.outcome == 'success'
69 | working-directory: php
70 | run: vendor/bin/php-cs-fixer check --diff --show-progress=none
71 | env:
72 | PHP_CS_FIXER_IGNORE_ENV: '1'
73 |
74 | - name: Run tests
75 | if: always() && steps.install.outcome == 'success'
76 | working-directory: php
77 | run: vendor/bin/phpunit
78 |
79 | - name: Run static analysis
80 | if: always() && steps.install.outcome == 'success'
81 | working-directory: php
82 | run: vendor/bin/phpstan analyze
83 |
--------------------------------------------------------------------------------
/.github/workflows/test-python.yml:
--------------------------------------------------------------------------------
1 | name: test-python
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - renovate/**
8 | paths:
9 | - python/**
10 | - testdata/**
11 | - .github/**
12 | pull_request:
13 | branches:
14 | - main
15 | - renovate/**
16 | paths:
17 | - python/**
18 | - testdata/**
19 | - .github/**
20 | workflow_call:
21 |
22 | jobs:
23 | test-python:
24 | runs-on: ${{ matrix.os }}
25 | strategy:
26 | fail-fast: false
27 | matrix:
28 | os:
29 | - ubuntu-latest
30 | python-version: ["3.x","pypy-3.10"]
31 | defaults:
32 | run:
33 | working-directory: python
34 | steps:
35 | - uses: actions/checkout@v4
36 | - name: Set up Python
37 | uses: actions/setup-python@v5
38 | with:
39 | python-version: ${{ matrix.python-version }}
40 | architecture: x64
41 | - name: Show Python version
42 | run: python --version
43 | - name: Install Python package dependencies
44 | run: |
45 | python -m pip install pip setuptools wheel build twine
46 | pip install -U -r py.requirements/ci.github.testing.txt
47 | pip install -e .
48 | - name: Run tests
49 | run: pytest
50 | - name: Build package
51 | run: |
52 | python -m build
53 | twine check --strict dist/*
54 |
--------------------------------------------------------------------------------
/.github/workflows/test-ruby.yml:
--------------------------------------------------------------------------------
1 | name: test-ruby
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - renovate/**
8 | paths:
9 | - ruby/**
10 | - testdata/**
11 | - .github/**
12 | pull_request:
13 | branches:
14 | - main
15 | - renovate/**
16 | paths:
17 | - ruby/**
18 | - testdata/**
19 | - .github/**
20 | workflow_call:
21 |
22 | jobs:
23 | test-ruby:
24 | runs-on: ${{ matrix.os }}
25 | strategy:
26 | fail-fast: false
27 | matrix:
28 | os:
29 | - ubuntu-latest
30 | ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4']
31 | include:
32 | - os: windows-latest
33 | ruby: '3.3'
34 | - os: macos-latest
35 | ruby: '3.3'
36 | steps:
37 | - uses: actions/checkout@v4
38 | - uses: ruby/setup-ruby@v1
39 | with:
40 | ruby-version: ${{ matrix.ruby }}
41 | bundler-cache: true
42 | rubygems: '3.0.8'
43 | working-directory: ruby
44 | - run: bundle exec rspec
45 | working-directory: ruby
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
--------------------------------------------------------------------------------
/ARCHITECTURE.md:
--------------------------------------------------------------------------------
1 | # Architecture
2 |
3 | The implementation is based on a modified version of Edsger Dijkstra's
4 | [Shunting Yard algorithm](https://en.wikipedia.org/wiki/Shunting-yard_algorithm)
5 | that produces an expression tree instead of a postfix notation.
6 |
7 | For example this expression:
8 |
9 | expression = "not @a or @b and not @c or not @d or @e and @f"
10 |
11 | Would parse into this expression tree:
12 |
13 | # Get the root of the tree - an Expression object.
14 | expressionNode = parser.parse(expression)
15 |
16 | or
17 | / \
18 | or and
19 | / \ / \
20 | or not @e @f
21 | / \ \
22 | not and @d
23 | / / \
24 | @a @b not
25 | \
26 | @c
27 |
28 | The root node of tree can then be evaluated for different combinations of tags.
29 | For example:
30 |
31 | result = expressionNode.evaluate(["@a", "@c", "@d"]) # => false
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Cucumber Ltd and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/cucumber/tag-expressions/actions/workflows/test-go.yml)
2 | [](https://github.com/cucumber/tag-expressions/actions/workflows/test-java.yml)
3 | [](https://github.com/cucumber/tag-expressions/actions/workflows/test-javascript.yml)
4 | [](https://github.com/cucumber/tag-expressions/actions/workflows/test-perl.yml)
5 | [](https://github.com/cucumber/tag-expressions/actions/workflows/test-python.yml)
6 | [](https://github.com/cucumber/tag-expressions/actions/workflows/test-ruby.yml)
7 |
8 | # Tag Expressions
9 |
10 | Tag Expressions is a simple query language for tags. The simplest tag expression is
11 | simply a single tag, for example:
12 |
13 | @smoke
14 |
15 | A slightly more elaborate expression may combine tags, for example:
16 |
17 | @smoke and not @ui
18 |
19 | Tag Expressions are used for two purposes:
20 |
21 | 1. Run a subset of scenarios (using the `--tags expression` option of the [command line](https://cucumber.io/docs/cucumber/api/#running-cucumber))
22 | 2. Specify that a hook should only run for a subset of scenarios (using [conditional hooks](https://cucumber.io/docs/cucumber/api/#hooks))
23 |
24 | Tag Expressions are [boolean expressions](https://en.wikipedia.org/wiki/Boolean_expression)
25 | of tags with the logical operators `and`, `or` and `not`.
26 |
27 | For more complex Tag Expressions you can use parenthesis for clarity, or to change operator precedence:
28 |
29 | (@smoke or @ui) and (not @slow)
30 |
31 | ## Escaping
32 |
33 | If you need to use one of the reserved characters `(`, `)`, `\` or ` ` (whitespace) in a tag,
34 | you can escape it with a `\`. Examples:
35 |
36 | | Gherkin Tag | Escaped Tag Expression |
37 | | ------------- | ---------------------- |
38 | | @x(y) | @x\\(y\\) |
39 | | @x\y | @x\\\\y |
40 |
41 | ## Migrating from old style tags
42 |
43 | Older versions of Cucumber used a different syntax for tags. The list below
44 | provides some examples illustrating how to migrate to tag expressions.
45 |
46 | | Old style command line | Cucumber Expressions style command line |
47 | | ----------------------------- | --------------------------------------- |
48 | | --tags @dev | --tags @dev |
49 | | --tags ~@dev | --tags "not @dev" |
50 | | --tags @foo,@bar | --tags "@foo or @bar" |
51 | | --tags @foo --tags @bar | --tags "@foo and bar" |
52 | | --tags ~@foo --tags @bar,@zap | --tags "not @foo and (@bar or @zap)" |
53 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | See [.github/RELEASING](https://github.com/cucumber/.github/blob/main/RELEASING.md).
2 |
--------------------------------------------------------------------------------
/go/.gitignore:
--------------------------------------------------------------------------------
1 | .built
2 | .compared
3 | .deps
4 | .dist
5 | .dist-compressed
6 | .go-get
7 | .gofmt
8 | .linted
9 | .tested*
10 | acceptance/
11 | bin/
12 | dist/
13 | dist_compressed/
14 | *.bin
15 | *.iml
16 | # upx dist/cucumber-gherkin-openbsd-386 fails with a core dump
17 | core.*.!usr!bin!upx-ucl
18 | .idea
19 |
--------------------------------------------------------------------------------
/go/Makefile:
--------------------------------------------------------------------------------
1 | GO_SOURCE_FILES := $(wildcard *.go)
2 | TEST_FILES := $(wildcard ../testdata/*.yml)
3 |
4 | default: .linted .tested
5 | .PHONY: default
6 |
7 | .linted: $(GO_SOURCE_FILES)
8 | gofmt -w $^
9 | touch $@
10 |
11 | .tested: $(GO_SOURCE_FILES) $(TEST_FILES)
12 | go test ./...
13 | touch $@
14 |
15 | update-dependencies:
16 | go get -u && go mod tidy
17 | .PHONY: update-dependencies
18 |
19 | clean:
20 | rm -rf .linted .tested
21 | .PHONY: clean
22 |
--------------------------------------------------------------------------------
/go/README.md:
--------------------------------------------------------------------------------
1 | # Cucumber Tag Expressions for Go
2 |
3 |
4 | [The docs are here](https://cucumber.io/docs/cucumber/api/#tag-expressions).
5 |
--------------------------------------------------------------------------------
/go/errors_test.go:
--------------------------------------------------------------------------------
1 | package tagexpressions
2 |
3 | import (
4 | "fmt"
5 | "gopkg.in/yaml.v3"
6 | "io/ioutil"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | type ErrorTest struct {
13 | Expression string `yaml:"expression"`
14 | Error string `yaml:"error"`
15 | }
16 |
17 | func TestErrors(t *testing.T) {
18 | contents, err := ioutil.ReadFile("../testdata/errors.yml")
19 | require.NoError(t, err)
20 | var tests []ErrorTest
21 | err = yaml.Unmarshal(contents, &tests)
22 | require.NoError(t, err)
23 |
24 | for _, test := range tests {
25 | name := fmt.Sprintf("fails to parse \"%s\" with \"%s\"", test.Expression, test.Error)
26 | t.Run(name, func(t *testing.T) {
27 | _, err := Parse(test.Expression)
28 | require.Error(t, err)
29 | require.Equal(t, test.Error, err.Error())
30 | })
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/go/evaluations_test.go:
--------------------------------------------------------------------------------
1 | package tagexpressions
2 |
3 | import (
4 | "fmt"
5 | "gopkg.in/yaml.v3"
6 | "io/ioutil"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | type Evaluation struct {
14 | Expression string `yaml:"expression"`
15 | Tests []Test `yaml:"tests"`
16 | }
17 |
18 | type Test struct {
19 | Variables []string `yaml:"variables"`
20 | Result bool `yaml:"result"`
21 | }
22 |
23 | func TestEvaluations(t *testing.T) {
24 | contents, err := ioutil.ReadFile("../testdata/evaluations.yml")
25 | require.NoError(t, err)
26 | var evaluations []Evaluation
27 | err = yaml.Unmarshal(contents, &evaluations)
28 | require.NoError(t, err)
29 |
30 | for _, evaluation := range evaluations {
31 | for _, test := range evaluation.Tests {
32 | variables := strings.Join(test.Variables, ", ")
33 | name := fmt.Sprintf("evaluates [%s] to %t", variables, test.Result)
34 | t.Run(name, func(t *testing.T) {
35 | expression, err := Parse(evaluation.Expression)
36 | require.NoError(t, err)
37 |
38 | result := expression.Evaluate(test.Variables)
39 | require.Equal(t, test.Result, result)
40 | })
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/go/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/cucumber/tag-expressions/go/v6
2 |
3 | require (
4 | github.com/stretchr/testify v1.10.0
5 | gopkg.in/yaml.v3 v3.0.1
6 | )
7 |
8 | require (
9 | github.com/davecgh/go-spew v1.1.1 // indirect
10 | github.com/kr/pretty v0.2.0 // indirect
11 | github.com/pmezard/go-difflib v1.0.0 // indirect
12 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
13 | )
14 |
15 | go 1.19
16 |
--------------------------------------------------------------------------------
/go/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
5 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
6 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
7 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
8 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
11 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
12 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
13 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
14 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
15 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
16 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
17 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
18 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
19 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
20 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
21 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
22 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
23 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
24 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
25 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
26 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
27 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
28 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
29 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
30 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
31 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
32 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
33 |
--------------------------------------------------------------------------------
/go/parser.go:
--------------------------------------------------------------------------------
1 | package tagexpressions
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strings"
7 | "unicode"
8 | )
9 |
10 | const OPERAND = "operand"
11 | const OPERATOR = "operator"
12 |
13 | type Evaluatable interface {
14 | Evaluate(variables []string) bool
15 | ToString() string
16 | }
17 |
18 | func Parse(infix string) (Evaluatable, error) {
19 | tokens, err := tokenize(infix)
20 | if err != nil {
21 | return nil, err
22 | }
23 | if len(tokens) == 0 {
24 | return &trueExpr{}, nil
25 | }
26 | expressions := &EvaluatableStack{}
27 | operators := &StringStack{}
28 | expectedTokenType := OPERAND
29 |
30 | for _, token := range tokens {
31 | if isUnary(token) {
32 | if err := check(infix, expectedTokenType, OPERAND); err != nil {
33 | return nil, err
34 | }
35 | operators.Push(token)
36 | expectedTokenType = OPERAND
37 | } else if isBinary(token) {
38 | if err := check(infix, expectedTokenType, OPERATOR); err != nil {
39 | return nil, err
40 | }
41 | for operators.Len() > 0 &&
42 | isOp(operators.Peek()) &&
43 | ((ASSOC[token] == "left" && PREC[token] <= PREC[operators.Peek()]) ||
44 | (ASSOC[token] == "right" && PREC[token] < PREC[operators.Peek()])) {
45 | pushExpr(operators.Pop(), expressions)
46 | }
47 | operators.Push(token)
48 | expectedTokenType = OPERAND
49 | } else if "(" == token {
50 | if err := check(infix, expectedTokenType, OPERAND); err != nil {
51 | return nil, err
52 | }
53 | operators.Push(token)
54 | expectedTokenType = OPERAND
55 | } else if ")" == token {
56 | if err := check(infix, expectedTokenType, OPERATOR); err != nil {
57 | return nil, err
58 | }
59 | for operators.Len() > 0 && operators.Peek() != "(" {
60 | pushExpr(operators.Pop(), expressions)
61 | }
62 | if operators.Len() == 0 {
63 | return nil, fmt.Errorf("Tag expression \"%s\" could not be parsed because of syntax error: Unmatched ).", infix)
64 | }
65 | if operators.Peek() == "(" {
66 | operators.Pop()
67 | }
68 | expectedTokenType = OPERATOR
69 | } else {
70 | if err := check(infix, expectedTokenType, OPERAND); err != nil {
71 | return nil, err
72 | }
73 | pushExpr(token, expressions)
74 | expectedTokenType = OPERATOR
75 | }
76 | }
77 |
78 | for operators.Len() > 0 {
79 | if operators.Peek() == "(" {
80 | return nil, fmt.Errorf("Tag expression \"%s\" could not be parsed because of syntax error: Unmatched (.", infix)
81 | }
82 | pushExpr(operators.Pop(), expressions)
83 | }
84 |
85 | return expressions.Pop(), nil
86 | }
87 |
88 | var ASSOC = map[string]string{
89 | "or": "left",
90 | "and": "left",
91 | "not": "right",
92 | }
93 |
94 | var PREC = map[string]int{
95 | "(": -2,
96 | ")": -1,
97 | "or": 0,
98 | "and": 1,
99 | "not": 2,
100 | }
101 |
102 | func tokenize(expr string) ([]string, error) {
103 | var tokens []string
104 | var token bytes.Buffer
105 |
106 | escaped := false
107 | for _, c := range expr {
108 | if escaped {
109 | if c == '(' || c == ')' || c == '\\' || unicode.IsSpace(c) {
110 | token.WriteRune(c)
111 | escaped = false
112 | } else {
113 | return nil, fmt.Errorf("Tag expression \"%s\" could not be parsed because of syntax error: Illegal escape before \"%s\".", expr, string(c))
114 | }
115 | } else if c == '\\' {
116 | escaped = true
117 | } else if c == '(' || c == ')' || unicode.IsSpace(c) {
118 | if token.Len() > 0 {
119 | tokens = append(tokens, token.String())
120 | token.Reset()
121 | }
122 | if !unicode.IsSpace(c) {
123 | tokens = append(tokens, string(c))
124 | }
125 | } else {
126 | token.WriteRune(c)
127 | }
128 | }
129 | if token.Len() > 0 {
130 | tokens = append(tokens, token.String())
131 | }
132 |
133 | return tokens, nil
134 | }
135 |
136 | func isUnary(token string) bool {
137 | return "not" == token
138 | }
139 |
140 | func isBinary(token string) bool {
141 | return "or" == token || "and" == token
142 | }
143 |
144 | func isOp(token string) bool {
145 | _, ok := ASSOC[token]
146 | return ok
147 | }
148 |
149 | func check(infix, expectedTokenType, tokenType string) error {
150 | if expectedTokenType != tokenType {
151 | return fmt.Errorf("Tag expression \"%s\" could not be parsed because of syntax error: Expected %s.", infix, expectedTokenType)
152 | }
153 | return nil
154 | }
155 |
156 | func pushExpr(token string, stack *EvaluatableStack) {
157 | if token == "and" {
158 | rightAndExpr := stack.Pop()
159 | stack.Push(&andExpr{
160 | leftExpr: stack.Pop(),
161 | rightExpr: rightAndExpr,
162 | })
163 | } else if token == "or" {
164 | rightOrExpr := stack.Pop()
165 | stack.Push(&orExpr{
166 | leftExpr: stack.Pop(),
167 | rightExpr: rightOrExpr,
168 | })
169 | } else if token == "not" {
170 | stack.Push(¬Expr{expr: stack.Pop()})
171 | } else {
172 | stack.Push(&literalExpr{value: token})
173 | }
174 | }
175 |
176 | type literalExpr struct {
177 | value string
178 | }
179 |
180 | func (l *literalExpr) Evaluate(variables []string) bool {
181 | for _, variable := range variables {
182 | if variable == l.value {
183 | return true
184 | }
185 | }
186 | return false
187 | }
188 |
189 | func (l *literalExpr) ToString() string {
190 | s1 := l.value
191 | s2 := strings.Replace(s1, "\\", "\\\\", -1)
192 | s3 := strings.Replace(s2, "(", "\\(", -1)
193 | s4 := strings.Replace(s3, ")", "\\)", -1)
194 | return strings.Replace(s4, " ", "\\ ", -1)
195 | }
196 |
197 | type orExpr struct {
198 | leftExpr Evaluatable
199 | rightExpr Evaluatable
200 | }
201 |
202 | func (o *orExpr) Evaluate(variables []string) bool {
203 | return o.leftExpr.Evaluate(variables) || o.rightExpr.Evaluate(variables)
204 | }
205 |
206 | func (o *orExpr) ToString() string {
207 | return fmt.Sprintf("( %s or %s )", o.leftExpr.ToString(), o.rightExpr.ToString())
208 | }
209 |
210 | type andExpr struct {
211 | leftExpr Evaluatable
212 | rightExpr Evaluatable
213 | }
214 |
215 | func (a *andExpr) Evaluate(variables []string) bool {
216 | return a.leftExpr.Evaluate(variables) && a.rightExpr.Evaluate(variables)
217 | }
218 |
219 | func (a *andExpr) ToString() string {
220 | return fmt.Sprintf("( %s and %s )", a.leftExpr.ToString(), a.rightExpr.ToString())
221 | }
222 |
223 | func isBinaryOperator(e Evaluatable) bool {
224 | _, isBinaryAnd := e.(*andExpr)
225 | _, isBinaryOr := e.(*orExpr)
226 | return isBinaryAnd || isBinaryOr
227 | }
228 |
229 | type notExpr struct {
230 | expr Evaluatable
231 | }
232 |
233 | func (n *notExpr) Evaluate(variables []string) bool {
234 | return !n.expr.Evaluate(variables)
235 | }
236 |
237 | func (n *notExpr) ToString() string {
238 | if isBinaryOperator(n.expr) {
239 | // -- HINT: Binary Operators already have already '( ... )'.
240 | return fmt.Sprintf("not %s", n.expr.ToString())
241 | }
242 | return fmt.Sprintf("not ( %s )", n.expr.ToString())
243 | }
244 |
245 | type trueExpr struct{}
246 |
247 | func (t *trueExpr) Evaluate(variables []string) bool {
248 | return true
249 | }
250 |
251 | func (t *trueExpr) ToString() string {
252 | return "true"
253 | }
254 |
--------------------------------------------------------------------------------
/go/parsing_test.go:
--------------------------------------------------------------------------------
1 | package tagexpressions
2 |
3 | import (
4 | "fmt"
5 | "gopkg.in/yaml.v3"
6 | "io/ioutil"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | type ParsingTest struct {
13 | Expression string `yaml:"expression"`
14 | Formatted string `yaml:"formatted"`
15 | }
16 |
17 | func TestParsing(t *testing.T) {
18 | contents, err := ioutil.ReadFile("../testdata/parsing.yml")
19 | require.NoError(t, err)
20 | var tests []ParsingTest
21 | err = yaml.Unmarshal(contents, &tests)
22 | require.NoError(t, err)
23 |
24 | for _, test := range tests {
25 | name := fmt.Sprintf("parses \"%s\" into \"%s\"", test.Expression, test.Formatted)
26 | t.Run(name, func(t *testing.T) {
27 | expression, err := Parse(test.Expression)
28 | require.NoError(t, err)
29 |
30 | require.Equal(t, test.Formatted, expression.ToString())
31 |
32 | expressionAgain, err := Parse(expression.ToString())
33 | require.NoError(t, err)
34 |
35 | require.Equal(t, test.Formatted, expressionAgain.ToString())
36 | })
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/go/stack.go:
--------------------------------------------------------------------------------
1 | package tagexpressions
2 |
3 | type InterfaceStack struct {
4 | elements []interface{}
5 | }
6 |
7 | func (i *InterfaceStack) Len() int {
8 | return len(i.elements)
9 | }
10 |
11 | func (i *InterfaceStack) Peek() interface{} {
12 | if i.Len() == 0 {
13 | panic("cannot peek")
14 | }
15 | return i.elements[i.Len()-1]
16 | }
17 |
18 | func (i *InterfaceStack) Pop() interface{} {
19 | if i.Len() == 0 {
20 | panic("cannot pop")
21 | }
22 | value := i.elements[i.Len()-1]
23 | i.elements = i.elements[:i.Len()-1]
24 | return value
25 | }
26 |
27 | func (i *InterfaceStack) Push(value interface{}) {
28 | i.elements = append(i.elements, value)
29 | }
30 |
31 | type StringStack struct {
32 | interfaceStack InterfaceStack
33 | }
34 |
35 | func (s *StringStack) Len() int {
36 | return s.interfaceStack.Len()
37 | }
38 |
39 | func (s *StringStack) Peek() string {
40 | return s.interfaceStack.Peek().(string)
41 | }
42 |
43 | func (s *StringStack) Pop() string {
44 | return s.interfaceStack.Pop().(string)
45 | }
46 |
47 | func (s *StringStack) Push(value string) {
48 | s.interfaceStack.Push(value)
49 | }
50 |
51 | type EvaluatableStack struct {
52 | interfaceStack InterfaceStack
53 | }
54 |
55 | func (e *EvaluatableStack) Len() int {
56 | return e.interfaceStack.Len()
57 | }
58 |
59 | func (e *EvaluatableStack) Peek() Evaluatable {
60 | return e.interfaceStack.Peek().(Evaluatable)
61 | }
62 |
63 | func (e *EvaluatableStack) Pop() Evaluatable {
64 | return e.interfaceStack.Pop().(Evaluatable)
65 | }
66 |
67 | func (e *EvaluatableStack) Push(value Evaluatable) {
68 | e.interfaceStack.Push(value)
69 | }
70 |
--------------------------------------------------------------------------------
/java/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | *.iml
3 | target/
4 | release.properties
5 | pom.xml.releaseBackup
6 | pom.xml.versionsBackup
7 | dependency-reduced-pom.xml
8 | .classpath
9 | .deps
10 | .project
11 | .settings/
12 | .tested*
13 | .compared
14 | .built
15 | # Approval tests
16 | acceptance/
17 |
--------------------------------------------------------------------------------
/java/README.md:
--------------------------------------------------------------------------------
1 | [](https://search.maven.org/search?q=g:%22io.cucumber%22%20AND%20a:%22tag-expressions%22)
2 |
3 | # Cucumber Tag Expressions for Java
4 |
5 | [The docs are here](https://cucumber.io/docs/cucumber/api/#tag-expressions).
6 |
--------------------------------------------------------------------------------
/java/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | io.cucumber
7 | cucumber-parent
8 | 4.3.7
9 |
10 |
11 | tag-expressions
12 | 6.2.1-SNAPSHOT
13 | jar
14 | Cucumber Tag Expressions
15 | Parses boolean infix expressions
16 | https://github.com/cucumber/tag-expressions
17 |
18 |
19 | 1748176122
20 | io.cucumber.tagexpressions
21 |
22 |
23 |
24 | scm:git:git://github.com/cucumber/tag-expressions.git
25 | scm:git:git@github.com:cucumber/tag-expressions.git
26 | git://github.com/cucumber/tag-expressions.git
27 | HEAD
28 |
29 |
30 |
31 |
32 |
33 |
34 | org.junit
35 | junit-bom
36 | 5.13.1
37 | pom
38 | import
39 |
40 |
41 |
42 |
43 |
44 |
45 | org.hamcrest
46 | hamcrest
47 | 3.0
48 | test
49 |
50 |
51 | org.junit.jupiter
52 | junit-jupiter-engine
53 | test
54 |
55 |
56 | org.junit.jupiter
57 | junit-jupiter-params
58 | test
59 |
60 |
61 | org.yaml
62 | snakeyaml
63 | 2.4
64 | test
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/tagexpressions/Expression.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.tagexpressions;
2 |
3 | import java.util.List;
4 |
5 | public interface Expression {
6 | boolean evaluate(List variables);
7 | }
8 |
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/tagexpressions/TagExpressionException.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.tagexpressions;
2 |
3 | public final class TagExpressionException extends RuntimeException {
4 | TagExpressionException(String message, Object... args) {
5 | super(String.format(message, args));
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/java/src/test/java/io/cucumber/tagexpressions/ErrorsTest.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.tagexpressions;
2 |
3 | import org.junit.jupiter.params.ParameterizedTest;
4 | import org.junit.jupiter.params.provider.MethodSource;
5 | import org.yaml.snakeyaml.Yaml;
6 |
7 | import java.io.IOException;
8 | import java.nio.file.Paths;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | import static java.nio.file.Files.newInputStream;
13 | import static org.junit.jupiter.api.Assertions.assertEquals;
14 | import static org.junit.jupiter.api.Assertions.assertThrows;
15 |
16 | class ErrorsTest {
17 |
18 | private static List