From 1793dd649f920192662876ac5b713e298a70a717 Mon Sep 17 00:00:00 2001
From: Iwan Eising
Date: Wed, 5 Aug 2026 15:19:05 +0400
Subject: [PATCH 1/2] docs(gherkin-to-asciidoc): document the indexing property
and the includeSubDirs/groupByFeature default changes
PR #99 shipped the indexing DSL property and flipped includeSubDirs/
groupByFeature to default to true, but never touched the plugin's own
README - it still showed the old false defaults everywhere and had no
mention of indexing at all.
Updates the Groovy/Kotlin DSL samples, the configuration properties
table, and the validation notes for the new defaults and the new
property. Adds a "Numbering Features and Scenarios" section describing
all four indexing values, the file processing/numbering order, and a
before/after example, linking to the new indexing example. Updates
"Sample Output"/"Grouping by Feature" to show the new default (grouped)
output first, with groupByFeature = false now documented as the
opt-out.
Co-Authored-By: Claude Sonnet 5
---
gherkin-to-asciidoc/README.adoc | 170 ++++++++++++++++++++++++++++----
1 file changed, 150 insertions(+), 20 deletions(-)
diff --git a/gherkin-to-asciidoc/README.adoc b/gherkin-to-asciidoc/README.adoc
index d786275..b7d62dc 100644
--- a/gherkin-to-asciidoc/README.adoc
+++ b/gherkin-to-asciidoc/README.adoc
@@ -67,10 +67,12 @@ All properties have sensible defaults and do not need to be set for a typical pr
.Groovy DSL
[source,groovy]
----
+import com.arc_e_tect.gradle.gherkin.indexing.IndexingMode // only needed if you set `indexing`
+
gherkinToAsciidoc {
- // One or more directories containing .feature files to scan (scanned non-recursively
- // by default). Default: 'src/test/resources/features' relative to the project directory,
- // used only when sourceDirs is left empty and sourceFile is not set.
+ // One or more directories containing .feature files to scan (scanned recursively by
+ // default - see includeSubDirs below). Default: 'src/test/resources/features' relative
+ // to the project directory, used only when sourceDirs is left empty and sourceFile is not set.
// Mutually exclusive with sourceFile.
sourceDirs.from('src/test/resources/features')
// Add more directories by calling .from(...) again, or in the same call:
@@ -81,9 +83,9 @@ gherkinToAsciidoc {
// sourceFile = file('src/test/resources/features/login.feature')
// When true, recursively scan all sub-directories of every directory in sourceDirs.
- // Cannot be used when sourceFile is set.
- // Default: false
- includeSubDirs = false
+ // Cannot be used when sourceFile is set - explicitly set this to false when using sourceFile.
+ // Default: true
+ includeSubDirs = true
// Directory where the generated AsciiDoc file will be written.
// Default: build/generated-docs
@@ -107,8 +109,8 @@ gherkinToAsciidoc {
// When true, groups scenarios under their enclosing Feature instead of a flat list
// (see "Grouping by Feature" below). Forced to true whenever trackProgress is true.
- // Default: false
- groupByFeature = false
+ // Default: true
+ groupByFeature = true
// Directory that the listed.adoc/defined.adoc/implemented.adoc report snippets are written
// to when trackProgress is true (see "Report Snippets and Custom Templates" below).
@@ -125,23 +127,32 @@ gherkinToAsciidoc {
// top of the generated document (see "System Under Test Version" below).
// Default: the project's own `version`
// systemUnderTestVersion = 'v1.0.0'
+
+ // Numbers Feature/Scenario titles directly in the source .feature files (see "Numbering
+ // Features and Scenarios" below). Requires includeSubDirs = true; FEATURE and ALL also
+ // require groupByFeature = true.
+ // Default: IndexingMode.OFF
+ indexing = IndexingMode.OFF
}
----
.Kotlin DSL (`build.gradle.kts`)
[source,kotlin]
----
+import com.arc_e_tect.gradle.gherkin.indexing.IndexingMode // only needed if you set `indexing`
+
gherkinToAsciidoc {
sourceDirs.from("src/test/resources/features")
- includeSubDirs.set(false)
+ includeSubDirs.set(true)
outputDir.set(layout.buildDirectory.dir("generated-docs"))
outputFileName.set("features.adoc")
trackProgress.set(false)
// glueCodeDirs.from("src/test/java/com/example/steps")
- groupByFeature.set(false)
+ groupByFeature.set(true)
// snippetDir.set(layout.buildDirectory.dir("generated-docs/features/snippets"))
// template.set(file("templates/report.mustache"))
// systemUnderTestVersion.set("v1.0.0")
+ indexing.set(IndexingMode.OFF)
}
----
@@ -154,21 +165,23 @@ gherkinToAsciidoc {
Mutually exclusive with `sourceFile`.
| `sourceFile` | File | — | Single `.feature` file to parse.
Mutually exclusive with `sourceDirs` and `includeSubDirs`.
-| `includeSubDirs`| Boolean | `false` | When `true`, recursively scans all sub-directories of every directory in `sourceDirs`.
-Cannot be combined with `sourceFile`.
+| `includeSubDirs`| Boolean | `true` | When `true`, recursively scans all sub-directories of every directory in `sourceDirs`.
+Cannot be combined with `sourceFile` - explicitly set this to `false` when using `sourceFile`.
| `outputDir` | Directory | `build/generated-docs` | Directory where the generated AsciiDoc file is written.
| `outputFileName`| String | `features.adoc` | Name of the generated AsciiDoc file.
| `trackProgress` | Boolean | `false` | When `true`, classifies every scenario as `listed`, `defined`, or `implemented` and adds a progress summary to the generated document.
Requires `sourceDirs` and `glueCodeDirs`; implies `includeSubDirs = true`.
| `glueCodeDirs` | File collection | — | One or more directories containing the Cucumber-JVM glue code, each scanned recursively for step definitions.
Required when `trackProgress` is `true`.
-| `groupByFeature`| Boolean | `false` | When `true`, groups scenarios under their enclosing `Feature` instead of a flat list.
+| `groupByFeature`| Boolean | `true` | When `true`, groups scenarios under their enclosing `Feature` instead of a flat list.
Forced to `true` whenever `trackProgress` is `true`.
| `snippetDir` | Directory | `build/generated-docs/features/snippets` | Directory that the `listed.adoc`/`defined.adoc`/`implemented.adoc` report snippets are written to.
Only used when `trackProgress` is `true`.
| `template` | File | — | Optional Mustache template used to render the report so it references the snippets via `include::` directives instead of embedding their content verbatim.
Only consulted when `trackProgress` is `true`.
| `systemUnderTestVersion` | String | the project's `version` | Version of the system under test that the reported scenarios exercise, printed near the top of the generated document (see "System Under Test Version" below).
+| `indexing` | `IndexingMode` (`OFF`/`FEATURE`/`SCENARIO`/`ALL`) | `OFF` | Numbers `Feature`/`Scenario` titles directly in the source `.feature` files (see "Numbering Features and Scenarios" below).
+Requires `includeSubDirs = true`; `FEATURE` and `ALL` additionally require `groupByFeature = true`.
|===
[IMPORTANT]
@@ -176,13 +189,19 @@ Only consulted when `trackProgress` is `true`.
`sourceDirs` and `sourceFile` are mutually exclusive.
Setting both will cause the `generateFeatureDocs` task to fail with a descriptive error.
-`includeSubDirs` cannot be combined with `sourceFile`.
+`includeSubDirs` cannot be combined with `sourceFile`. Since `includeSubDirs` now defaults to `true`,
+a project using `sourceFile` must explicitly set `includeSubDirs = false` - otherwise
+`generateFeatureDocs` fails with a descriptive error.
`trackProgress` can only be enabled when `sourceDirs` is configured, and requires `glueCodeDirs` to be set.
Enabling it implies `includeSubDirs = true` and `groupByFeature = true`, regardless of those properties' own
configured values.
`snippetDir` and `template` are only consulted when `trackProgress` is `true`; both are ignored otherwise.
+
+`indexing` can only be set to something other than `OFF` when `includeSubDirs` is `true`. `FEATURE` and `ALL`
+additionally require `groupByFeature` to be `true`; with `groupByFeature = false`, only `OFF` and `SCENARIO`
+are allowed. Setting an invalid combination fails `generateFeatureDocs` with a descriptive error.
====
== Running the Task
@@ -234,6 +253,9 @@ sub-project that inherits or sets `trackProgress = true` still needs its own `gl
| Inherited from the root project by default (subject to the usual `trackProgress` implication).
| `groupByFeature`
| Inherited from the root project by default (subject to the usual `trackProgress` implication).
+| `indexing`
+| Inherited from the root project by default; a sub-project can override it independently, subject to
+the usual `includeSubDirs`/`groupByFeature` validation constraints for whichever value is in effect.
| `outputFileName`
| Inherited from the root project by default.
| `template`
@@ -330,7 +352,7 @@ Feature: User authentication
| bob | wrong | failure |
----
-The plugin generates:
+By default (`groupByFeature = true`), the plugin generates:
[source,asciidoc]
----
@@ -342,15 +364,16 @@ System Under Test version: 1.0.0
This document lists every `Scenario` and `Scenario Outline` found under the configured feature file directories.
+== User authentication
+
* Scenario: User logs in successfully
* Scenario Outline: User logs in with different credential sets
----
== Grouping by Feature
-By default, scenarios are listed as one flat list regardless of which `.feature` file they came from. Setting
-`groupByFeature = true` groups them under their enclosing `Feature` instead — useful once `sourceDirs` spans
-more than one feature area. Given two feature files:
+By default, scenarios are grouped under their enclosing `Feature`, as shown above — useful once `sourceDirs`
+spans more than one feature area, as with two feature files:
[source,gherkin]
----
@@ -365,8 +388,6 @@ Feature: Invoice payment
Given an outstanding invoice
----
-with `groupByFeature = true`, the plugin generates:
-
[source,asciidoc]
----
= Feature Scenarios
@@ -386,6 +407,115 @@ This document lists every `Scenario` and `Scenario Outline` found under the conf
* Scenario: User pays an invoice
----
+Setting `groupByFeature = false` instead lists every scenario as one flat list, regardless of which
+`.feature` file it came from:
+
+[source,asciidoc]
+----
+= Feature Scenarios
+:toc:
+:toclevels: 2
+
+System Under Test version: 1.0.0
+
+This document lists every `Scenario` and `Scenario Outline` found under the configured feature file directories.
+
+* Scenario: User logs in successfully
+* Scenario: User pays an invoice
+----
+
+== Numbering Features and Scenarios
+
+Setting `indexing` numbers `Feature`/`Scenario` titles directly in the source `.feature` files - not just in
+the generated report - so the numbering is visible wherever the feature file itself is read (in an editor, in
+a Cucumber test run, ...), not only in `generateFeatureDocs`'s own output.
+
+[cols="1,3",options="header"]
+|===
+| Value | Effect
+
+| `OFF` | Nothing is numbered (default). Any numbering left over from a previous run is removed.
+| `FEATURE` | Every feature is numbered, e.g. `Feature: 1 - User authentication`. Scenario titles are untouched.
+| `SCENARIO` | Every scenario is numbered continuously across all feature files, e.g. `Scenario: 1 - User logs in`. Feature titles are untouched.
+| `ALL` | Both are numbered; scenarios are numbered per feature as `.`, e.g. `Scenario: 1.1 - User logs in` within `Feature: 1 - User authentication`.
+|===
+
+Feature files are processed - and numbered - in the same order the generated report lists them in: for each
+source directory (directories themselves ordered alphabetically by path when more than one is configured),
+that directory's own feature files first, alphabetically by file name, and only then its sub-directories'
+files, each sub-directory visited the same way, alphabetically by name. Scenario numbers additionally follow
+document order within each file.
+
+Given two feature files, `features-auth/authentication.feature`:
+
+[source,gherkin]
+----
+Feature: User authentication
+
+ Scenario: User requests a password reset
+
+ Scenario: User logs in successfully
+ Given the login page is open
+----
+
+and `features-billing/invoice.feature`:
+
+[source,gherkin]
+----
+Feature: Invoice payment
+
+ Scenario: User pays an invoice
+ Given an outstanding invoice
+----
+
+with `indexing = IndexingMode.ALL`, the plugin rewrites both files in place:
+
+[source,gherkin]
+----
+Feature: 1 - User authentication
+
+ Scenario: 1.1 - User requests a password reset
+
+ Scenario: 1.2 - User logs in successfully
+ Given the login page is open
+----
+
+[source,gherkin]
+----
+Feature: 2 - Invoice payment
+
+ Scenario: 2.1 - User pays an invoice
+ Given an outstanding invoice
+----
+
+and the generated report reflects the same numbering, since it's parsed from the now-rewritten source files:
+
+[source,asciidoc]
+----
+== 1 - User authentication
+
+* Scenario: 1.1 - User requests a password reset
+* Scenario: 1.2 - User logs in successfully
+
+== 2 - Invoice payment
+
+* Scenario: 2.1 - User pays an invoice
+----
+
+[IMPORTANT]
+====
+Changing `indexing` - including setting it back to `OFF` - rewrites the source `.feature` files on the next
+`generateFeatureDocs` run: any numbering left over from a previous run is stripped first, then fresh
+numbering is applied for the new mode. This makes the operation idempotent (re-running with the same mode is
+a no-op once the files are already correctly numbered), but it does mean the task mutates files that are also
+its own inputs - commit the resulting numbered `.feature` files like any other source change.
+====
+
+See the
+link:../examples/gherkin-to-asciidoc/indexing/README.adoc[indexing example]
+for all four modes applied to the same feature files side by side, with the exact before/after content and
+generated reports for each.
+
== Tracking Implementation Progress
Setting `trackProgress = true` (with `sourceDirs` and `glueCodeDirs` configured) switches the generated document
From f26987b71fc50f3ea9c687fb8b07943d6afb289e Mon Sep 17 00:00:00 2001
From: Iwan Eising
Date: Wed, 5 Aug 2026 15:49:18 +0400
Subject: [PATCH 2/2] feat(gherkin-to-asciidoc): add ci indexing value and a
CLI override for the whole build
Adds IndexingMode.CI: unlike OFF, it skips indexing entirely - the
source .feature files aren't touched at all, not even to strip prior
numbering. Adds the -PgherkinToAsciidoc.indexing= project
property, which overrides indexing for every project in the build
regardless of what each project's own gherkinToAsciidoc { } block
configures, matched against IndexingMode case-insensitively. Intended
usage is forcing `ci` in a CI pipeline so generateFeatureDocs never
mutates source files there, without changing any build script.
Documents both in the plugin README (new "Overriding indexing from
the Command Line" subsection, updated tables/validation notes) and
adds a fifth `ci` sub-project to the indexing example, whose feature
files start out already numbered specifically to prove CI leaves
that numbering untouched, plus a walkthrough of the CLI override
forcing all five sub-projects to skip indexing for a single run.
Also fixes a pre-existing bug in the example (introduced when it was
first added): the feature/scenario/all sub-projects' committed
.feature files were left numbered from local verification instead of
the clean baseline the README describes.
Pins the example to gherkin-to-asciidoc = 2.1.0, the version this
purely additive change is expected to release as; verified locally
against the plugin's own source (via a temporary includeBuild, since
removed) before pinning.
Co-Authored-By: Claude Sonnet 5
---
.../gherkin-to-asciidoc/indexing/README.adoc | 130 +++++++++++++++---
.../features-auth/authentication.feature | 8 +-
.../features-billing/invoice.feature | 4 +-
.../gherkin-to-asciidoc/indexing/build.gradle | 12 +-
.../indexing/ci/build.gradle | 16 +++
.../features-auth/authentication.feature | 19 +++
.../features-billing/invoice.feature | 6 +
.../features-auth/authentication.feature | 2 +-
.../features-billing/invoice.feature | 2 +-
.../indexing/gradle/libs.versions.toml | 2 +-
.../features-auth/authentication.feature | 6 +-
.../features-billing/invoice.feature | 2 +-
.../indexing/settings.gradle | 2 +-
gherkin-to-asciidoc/README.adoc | 46 +++++--
.../gherkin/GenerateFeatureDocsTask.java | 21 ++-
.../gherkin/GherkinToAsciidocExtension.java | 28 +++-
.../gherkin/GherkinToAsciidocPlugin.java | 33 ++++-
.../gherkin/indexing/FeatureIndexer.java | 4 +
.../gradle/gherkin/indexing/IndexingMode.java | 12 +-
.../gherkin/GherkinToAsciidocPluginTest.java | 95 ++++++++++++-
20 files changed, 388 insertions(+), 62 deletions(-)
create mode 100644 examples/gherkin-to-asciidoc/indexing/ci/build.gradle
create mode 100644 examples/gherkin-to-asciidoc/indexing/ci/src/test/resources/features-auth/authentication.feature
create mode 100644 examples/gherkin-to-asciidoc/indexing/ci/src/test/resources/features-billing/invoice.feature
diff --git a/examples/gherkin-to-asciidoc/indexing/README.adoc b/examples/gherkin-to-asciidoc/indexing/README.adoc
index 38290e0..43f1631 100644
--- a/examples/gherkin-to-asciidoc/indexing/README.adoc
+++ b/examples/gherkin-to-asciidoc/indexing/README.adoc
@@ -4,18 +4,20 @@
== Description
This example applies the Gherkin to AsciiDoc plugin to a Gradle multi-project build made up of a root
-project and four sub-projects - `off`, `feature`, `scenario`, and `all` - one per value of the `indexing`
-DSL property. Every sub-project starts out with byte-for-byte identical copies of the same two
-`.feature` files; the *only* difference between them is the `indexing` value each one configures.
+project and five sub-projects - `off`, `feature`, `scenario`, `all`, and `ci` - one per value of the
+`indexing` DSL property. `off`, `feature`, `scenario`, and `all` start out with byte-for-byte identical
+copies of the same two `.feature` files, differing only in the `indexing` value each one configures;
+`ci`'s copies start out *already numbered* instead (see "Intent" below).
== Intent
This example shows:
-* What each of the four `indexing` values actually does to the source `.feature` files:
- `off` (nothing), `feature` (numbers `Feature` titles only), `scenario` (numbers `Scenario`/
- `Scenario Outline` titles only, continuously across every feature file), and `all` (numbers both,
- scenarios as `.`).
+* What each of the five `indexing` values actually does to the source `.feature` files:
+ `off` (nothing, but strips any prior numbering), `feature` (numbers `Feature` titles only),
+ `scenario` (numbers `Scenario`/`Scenario Outline` titles only, continuously across every feature
+ file), `all` (numbers both, scenarios as `.`), and `ci` (skips
+ indexing entirely - unlike `off`, not even to strip prior numbering).
* That indexing numbers features/scenarios alphabetically by feature file name -
`authentication.feature` is processed before `invoice.feature` - not in whatever order the
filesystem happens to list them.
@@ -24,6 +26,9 @@ This example shows:
source files.
* That running the build a second time is a no-op: indexing strips any numbering left over from a
previous run before reapplying it, so already-correctly-numbered files are left untouched.
+* That the `-PgherkinToAsciidoc.indexing=` command-line override forces the same `indexing`
+ value onto every sub-project at once, regardless of what each one's own `build.gradle` configures -
+ typically used to force `ci` for the whole build in a CI pipeline.
== Project Layout
@@ -42,9 +47,12 @@ indexing/
├── scenario/
│ ├── build.gradle (indexing = IndexingMode.SCENARIO)
│ └── src/test/resources/... (same two files, byte-for-byte identical to off/)
-└── all/
- ├── build.gradle (indexing = IndexingMode.ALL)
- └── src/test/resources/... (same two files, byte-for-byte identical to off/)
+├── all/
+│ ├── build.gradle (indexing = IndexingMode.ALL)
+│ └── src/test/resources/... (same two files, byte-for-byte identical to off/)
+└── ci/
+ ├── build.gradle (indexing = IndexingMode.CI)
+ └── src/test/resources/... (same two files, but already numbered - see "Intent" above)
----
== Configuration
@@ -104,8 +112,19 @@ gherkinToAsciidoc {
}
----
+.`ci/build.gradle`
+[source,groovy]
+----
+import com.arc_e_tect.gradle.gherkin.indexing.IndexingMode
+
+gherkinToAsciidoc {
+ indexing = IndexingMode.CI
+}
+----
+
`feature` and `all` both require `groupByFeature = true`, which - like `includeSubDirs = true` - is
-already the plugin's default, so neither sub-project needs to configure it explicitly.
+already the plugin's default, so neither sub-project needs to configure it explicitly. `ci` has no such
+requirement - `IndexingMode.CI` is always allowed, regardless of `includeSubDirs`/`groupByFeature`.
== Build And Run
@@ -116,24 +135,44 @@ cd examples/gherkin-to-asciidoc/indexing
----
Running `generateFeatureDocs` from the root project without a project path runs it in *every* project
-that has it - root, `off`, `feature`, `scenario`, and `all` - in one command.
+that has it - root, `off`, `feature`, `scenario`, `all`, and `ci` - in one command.
[IMPORTANT]
====
This rewrites the `.feature` files under `off/`, `feature/`, `scenario/`, and `all/` *in place* - that
-is the whole point of the `indexing` property. Running it again afterwards is a no-op (the files are
-already correctly numbered for their sub-project's mode), but the working tree will show the four
-sub-projects' `.feature` files as modified the first time you run it. Run `git diff` afterwards to see
-exactly what each mode changed - or `git checkout -- off feature scenario all` to put the four pristine,
-identically-worded copies back if you want to try it again from a clean baseline.
+is the whole point of the `indexing` property (`ci/`'s files are never touched, by design). Running it
+again afterwards is a no-op (the files are already correctly numbered for their sub-project's mode), but
+the working tree will show `off/`, `feature/`, `scenario/`, and `all/`'s `.feature` files as modified the
+first time you run it. Run `git diff` afterwards to see exactly what each mode changed - or
+`git checkout -- off feature scenario all` to put the four pristine, identically-worded copies back if you
+want to try it again from a clean baseline.
====
+=== Overriding indexing from the command line
+
+To see the `-PgherkinToAsciidoc.indexing` override in action, run this *before* the plain command above,
+against a clean checkout:
+
+[source,bash]
+----
+./gradlew generateFeatureDocs -PgherkinToAsciidoc.indexing=ci
+----
+
+This forces `indexing = IndexingMode.CI` onto all five sub-projects for this run alone, regardless of what
+each one's own `build.gradle` configures - so even though `feature/`, `scenario/`, and `all/` are
+configured to number their files, none of the five sub-projects' `.feature` files are touched. `git status`
+afterwards shows a clean working tree; only the five generated reports (still produced normally, just from
+each project's pristine, un-numbered starting content) land under each sub-project's own `build/` directory.
+Run the plain `./gradlew generateFeatureDocs` afterwards to see `off/`, `feature/`, `scenario/`, and `all/`
+get numbered as normal - proving the override only affects the run it's passed to.
+
== What To Expect
Before the build runs, `off/`, `feature/`, `scenario/`, and `all/` all contain the exact same
`authentication.feature` (three scenarios: a title-only scenario, a fully-worded scenario, and a
`Scenario Outline`) and `invoice.feature` (one scenario) - reproduced here from `off/`, which stays
-this way after the build since its `indexing` is `off`:
+this way after the build since its `indexing` is `off`. `ci/`'s copies of the same two files start out
+*already* numbered instead (see "Ci" below) - and stay that way, untouched, after the build too.
.`off/src/test/resources/features-auth/authentication.feature` (unchanged by any mode)
[source,gherkin]
@@ -231,9 +270,58 @@ the scenario count for each feature, unlike `scenario` mode's continuous count.
* Scenario: 2.1 - User pays an invoice
----
+=== Ci — indexing skipped entirely
+
+Unlike the other four, `ci/`'s copies of the two feature files start out *already* numbered - as `all`
+mode would leave them - specifically to prove that `IndexingMode.CI` leaves that numbering completely
+untouched, rather than stripping it the way `off` would:
+
+.`ci/src/test/resources/features-auth/authentication.feature` (before *and* after the build - byte-for-byte identical)
+[source,gherkin]
+----
+Feature: 1 - User authentication
+
+ Scenario: 1.1 - User requests a password reset
+ # Not yet fleshed out - title only, no steps yet.
+
+ Scenario: 1.2 - User logs in successfully
+ Given the login page is open
+ When the user submits valid credentials
+ Then the dashboard is displayed
+
+ Scenario Outline: 1.3 - User logs in with different credential sets
+ Given the login page is open
+ When the user submits "" and ""
+ Then the result is ""
+
+ Examples:
+ | username | password | outcome |
+ | alice | secret | success |
+ | bob | wrong | failure |
+----
+
+`ci/build/generated-docs/features.adoc` is still generated normally - reflecting whatever numbering the
+source files already happen to have, since `generateFeatureDocs` always parses and reports on the current
+state of the source files, indexing or not:
+
+[source,asciidoc]
+----
+== 1 - User authentication
+
+* Scenario: 1.1 - User requests a password reset
+* Scenario: 1.2 - User logs in successfully
+* Scenario Outline: 1.3 - User logs in with different credential sets
+
+== 2 - Invoice payment
+
+* Scenario: 2.1 - User pays an invoice
+----
+
[NOTE]
====
-This example is pinned to `gherkin-to-asciidoc = "2.0.0"` in `gradle/libs.versions.toml` - the version
-`indexing` (and the breaking `includeSubDirs`/`groupByFeature` default changes it ships alongside) was
-released as, resolved from the Gradle Plugin Portal like any other example in this repository.
+This example is pinned to `gherkin-to-asciidoc = "2.1.0"` in `gradle/libs.versions.toml` - the version the
+`ci` indexing value and the `-PgherkinToAsciidoc.indexing` command-line override are expected to release
+as (a purely additive change on top of `indexing`, itself released as `2.0.0`). Verified locally against
+the plugin's own source (via a temporary `includeBuild`, since removed) before pinning; won't build
+against the Gradle Plugin Portal until `2.1.0` is actually released.
====
diff --git a/examples/gherkin-to-asciidoc/indexing/all/src/test/resources/features-auth/authentication.feature b/examples/gherkin-to-asciidoc/indexing/all/src/test/resources/features-auth/authentication.feature
index ba1de52..c25f9e8 100644
--- a/examples/gherkin-to-asciidoc/indexing/all/src/test/resources/features-auth/authentication.feature
+++ b/examples/gherkin-to-asciidoc/indexing/all/src/test/resources/features-auth/authentication.feature
@@ -1,14 +1,14 @@
-Feature: 1 - User authentication
+Feature: User authentication
- Scenario: 1.1 - User requests a password reset
+ Scenario: User requests a password reset
# Not yet fleshed out - title only, no steps yet.
- Scenario: 1.2 - User logs in successfully
+ Scenario: User logs in successfully
Given the login page is open
When the user submits valid credentials
Then the dashboard is displayed
- Scenario Outline: 1.3 - User logs in with different credential sets
+ Scenario Outline: User logs in with different credential sets
Given the login page is open
When the user submits "" and ""
Then the result is ""
diff --git a/examples/gherkin-to-asciidoc/indexing/all/src/test/resources/features-billing/invoice.feature b/examples/gherkin-to-asciidoc/indexing/all/src/test/resources/features-billing/invoice.feature
index 7716ca1..b62bf83 100644
--- a/examples/gherkin-to-asciidoc/indexing/all/src/test/resources/features-billing/invoice.feature
+++ b/examples/gherkin-to-asciidoc/indexing/all/src/test/resources/features-billing/invoice.feature
@@ -1,6 +1,6 @@
-Feature: 2 - Invoice payment
+Feature: Invoice payment
- Scenario: 2.1 - User pays an invoice
+ Scenario: User pays an invoice
Given an outstanding invoice
When the user pays the invoice
Then the invoice is marked as paid
diff --git a/examples/gherkin-to-asciidoc/indexing/build.gradle b/examples/gherkin-to-asciidoc/indexing/build.gradle
index 0bfbe2f..edd4980 100644
--- a/examples/gherkin-to-asciidoc/indexing/build.gradle
+++ b/examples/gherkin-to-asciidoc/indexing/build.gradle
@@ -2,7 +2,7 @@ plugins {
alias(libs.plugins.gherkin.to.asciidoc)
}
-// Configuration here applies to every project below (root and all four sub-projects) unless a
+// Configuration here applies to every project below (root and all five sub-projects) unless a
// sub-project's own build.gradle overrides a given property for itself. Every sub-project below
// overrides only the one property this example is about: indexing.
gherkinToAsciidoc {
@@ -10,16 +10,18 @@ gherkinToAsciidoc {
// None of the sub-projects configure sourceDirs themselves, so each one resolves these same
// relative paths against its own directory, e.g. off/src/test/resources/features-auth and
- // off/src/test/resources/features-billing. Every sub-project's features-auth/authentication.feature
+ // off/src/test/resources/features-billing. off/feature/scenario/all's features-auth/authentication.feature
// and features-billing/invoice.feature start out byte-for-byte identical - only the indexing
- // property differs, so the four generated reports (and the numbering baked into the four copies
- // of the source files) are directly comparable.
+ // property differs, so those four generated reports (and the numbering baked into those four
+ // copies of the source files) are directly comparable. ci/'s copies start out already numbered
+ // (as ALL mode would leave them) instead, to demonstrate that indexing = IndexingMode.CI leaves
+ // that numbering completely untouched rather than stripping it the way OFF would.
sourceDirs.from(
'src/test/resources/features-auth',
'src/test/resources/features-billing')
}
-// Applies the plugin to off/feature/scenario/all too, reusing the version already resolved above.
+// Applies the plugin to off/feature/scenario/all/ci too, reusing the version already resolved above.
// Each sub-project gets its own generateFeatureDocs task and its own generated report.
subprojects {
apply plugin: 'com.arc-e-tect.gherkin-to-asciidoc'
diff --git a/examples/gherkin-to-asciidoc/indexing/ci/build.gradle b/examples/gherkin-to-asciidoc/indexing/ci/build.gradle
new file mode 100644
index 0000000..313484c
--- /dev/null
+++ b/examples/gherkin-to-asciidoc/indexing/ci/build.gradle
@@ -0,0 +1,16 @@
+import com.arc_e_tect.gradle.gherkin.indexing.IndexingMode
+
+// The plugin is already applied to this project by the root project's `subprojects { }` block.
+// This sub-project overrides indexing = IndexingMode.CI: indexing is skipped entirely, so the
+// source .feature files are never touched - not even to strip numbering. Unlike the other three
+// sub-projects, this one's feature files start out ALREADY numbered (as ALL mode would have left
+// them), specifically to prove that CI mode leaves that numbering completely untouched rather
+// than stripping it the way OFF would.
+//
+// This value is also - and more commonly - set from the command line for the whole build at
+// once, without editing any sub-project's build.gradle, e.g.:
+// ./gradlew generateFeatureDocs -PgherkinToAsciidoc.indexing=ci
+// See the root README for that in action against the feature/scenario/all sub-projects too.
+gherkinToAsciidoc {
+ indexing = IndexingMode.CI
+}
diff --git a/examples/gherkin-to-asciidoc/indexing/ci/src/test/resources/features-auth/authentication.feature b/examples/gherkin-to-asciidoc/indexing/ci/src/test/resources/features-auth/authentication.feature
new file mode 100644
index 0000000..ba1de52
--- /dev/null
+++ b/examples/gherkin-to-asciidoc/indexing/ci/src/test/resources/features-auth/authentication.feature
@@ -0,0 +1,19 @@
+Feature: 1 - User authentication
+
+ Scenario: 1.1 - User requests a password reset
+ # Not yet fleshed out - title only, no steps yet.
+
+ Scenario: 1.2 - User logs in successfully
+ Given the login page is open
+ When the user submits valid credentials
+ Then the dashboard is displayed
+
+ Scenario Outline: 1.3 - User logs in with different credential sets
+ Given the login page is open
+ When the user submits "" and ""
+ Then the result is ""
+
+ Examples:
+ | username | password | outcome |
+ | alice | secret | success |
+ | bob | wrong | failure |
diff --git a/examples/gherkin-to-asciidoc/indexing/ci/src/test/resources/features-billing/invoice.feature b/examples/gherkin-to-asciidoc/indexing/ci/src/test/resources/features-billing/invoice.feature
new file mode 100644
index 0000000..7716ca1
--- /dev/null
+++ b/examples/gherkin-to-asciidoc/indexing/ci/src/test/resources/features-billing/invoice.feature
@@ -0,0 +1,6 @@
+Feature: 2 - Invoice payment
+
+ Scenario: 2.1 - User pays an invoice
+ Given an outstanding invoice
+ When the user pays the invoice
+ Then the invoice is marked as paid
diff --git a/examples/gherkin-to-asciidoc/indexing/feature/src/test/resources/features-auth/authentication.feature b/examples/gherkin-to-asciidoc/indexing/feature/src/test/resources/features-auth/authentication.feature
index 4ffa363..c25f9e8 100644
--- a/examples/gherkin-to-asciidoc/indexing/feature/src/test/resources/features-auth/authentication.feature
+++ b/examples/gherkin-to-asciidoc/indexing/feature/src/test/resources/features-auth/authentication.feature
@@ -1,4 +1,4 @@
-Feature: 1 - User authentication
+Feature: User authentication
Scenario: User requests a password reset
# Not yet fleshed out - title only, no steps yet.
diff --git a/examples/gherkin-to-asciidoc/indexing/feature/src/test/resources/features-billing/invoice.feature b/examples/gherkin-to-asciidoc/indexing/feature/src/test/resources/features-billing/invoice.feature
index 1042c02..b62bf83 100644
--- a/examples/gherkin-to-asciidoc/indexing/feature/src/test/resources/features-billing/invoice.feature
+++ b/examples/gherkin-to-asciidoc/indexing/feature/src/test/resources/features-billing/invoice.feature
@@ -1,4 +1,4 @@
-Feature: 2 - Invoice payment
+Feature: Invoice payment
Scenario: User pays an invoice
Given an outstanding invoice
diff --git a/examples/gherkin-to-asciidoc/indexing/gradle/libs.versions.toml b/examples/gherkin-to-asciidoc/indexing/gradle/libs.versions.toml
index e7d92f4..7d9b697 100644
--- a/examples/gherkin-to-asciidoc/indexing/gradle/libs.versions.toml
+++ b/examples/gherkin-to-asciidoc/indexing/gradle/libs.versions.toml
@@ -6,4 +6,4 @@ gherkin-to-asciidoc = { id = "com.arc-e-tect.gherkin-to-asciidoc", version.ref =
[versions]
-gherkin-to-asciidoc = "2.0.0"
+gherkin-to-asciidoc = "2.1.0"
diff --git a/examples/gherkin-to-asciidoc/indexing/scenario/src/test/resources/features-auth/authentication.feature b/examples/gherkin-to-asciidoc/indexing/scenario/src/test/resources/features-auth/authentication.feature
index c8afd84..c25f9e8 100644
--- a/examples/gherkin-to-asciidoc/indexing/scenario/src/test/resources/features-auth/authentication.feature
+++ b/examples/gherkin-to-asciidoc/indexing/scenario/src/test/resources/features-auth/authentication.feature
@@ -1,14 +1,14 @@
Feature: User authentication
- Scenario: 1 - User requests a password reset
+ Scenario: User requests a password reset
# Not yet fleshed out - title only, no steps yet.
- Scenario: 2 - User logs in successfully
+ Scenario: User logs in successfully
Given the login page is open
When the user submits valid credentials
Then the dashboard is displayed
- Scenario Outline: 3 - User logs in with different credential sets
+ Scenario Outline: User logs in with different credential sets
Given the login page is open
When the user submits "" and ""
Then the result is ""
diff --git a/examples/gherkin-to-asciidoc/indexing/scenario/src/test/resources/features-billing/invoice.feature b/examples/gherkin-to-asciidoc/indexing/scenario/src/test/resources/features-billing/invoice.feature
index a91987d..b62bf83 100644
--- a/examples/gherkin-to-asciidoc/indexing/scenario/src/test/resources/features-billing/invoice.feature
+++ b/examples/gherkin-to-asciidoc/indexing/scenario/src/test/resources/features-billing/invoice.feature
@@ -1,6 +1,6 @@
Feature: Invoice payment
- Scenario: 4 - User pays an invoice
+ Scenario: User pays an invoice
Given an outstanding invoice
When the user pays the invoice
Then the invoice is marked as paid
diff --git a/examples/gherkin-to-asciidoc/indexing/settings.gradle b/examples/gherkin-to-asciidoc/indexing/settings.gradle
index 153e349..19d1cac 100644
--- a/examples/gherkin-to-asciidoc/indexing/settings.gradle
+++ b/examples/gherkin-to-asciidoc/indexing/settings.gradle
@@ -17,4 +17,4 @@ refreshVersions {
rootProject.name = 'gherkin-to-asciidoc-indexing-example'
-include 'off', 'feature', 'scenario', 'all'
+include 'off', 'feature', 'scenario', 'all', 'ci'
diff --git a/gherkin-to-asciidoc/README.adoc b/gherkin-to-asciidoc/README.adoc
index b7d62dc..cad3fbb 100644
--- a/gherkin-to-asciidoc/README.adoc
+++ b/gherkin-to-asciidoc/README.adoc
@@ -180,8 +180,8 @@ Only used when `trackProgress` is `true`.
| `template` | File | — | Optional Mustache template used to render the report so it references the snippets via `include::` directives instead of embedding their content verbatim.
Only consulted when `trackProgress` is `true`.
| `systemUnderTestVersion` | String | the project's `version` | Version of the system under test that the reported scenarios exercise, printed near the top of the generated document (see "System Under Test Version" below).
-| `indexing` | `IndexingMode` (`OFF`/`FEATURE`/`SCENARIO`/`ALL`) | `OFF` | Numbers `Feature`/`Scenario` titles directly in the source `.feature` files (see "Numbering Features and Scenarios" below).
-Requires `includeSubDirs = true`; `FEATURE` and `ALL` additionally require `groupByFeature = true`.
+| `indexing` | `IndexingMode` (`OFF`/`FEATURE`/`SCENARIO`/`ALL`/`CI`) | `OFF` | Numbers `Feature`/`Scenario` titles directly in the source `.feature` files (see "Numbering Features and Scenarios" below).
+`FEATURE`/`SCENARIO`/`ALL` require `includeSubDirs = true`; `FEATURE` and `ALL` additionally require `groupByFeature = true`. `CI` skips indexing entirely and is always allowed; overridable for the whole build via the `-PgherkinToAsciidoc.indexing` project property.
|===
[IMPORTANT]
@@ -199,9 +199,15 @@ configured values.
`snippetDir` and `template` are only consulted when `trackProgress` is `true`; both are ignored otherwise.
-`indexing` can only be set to something other than `OFF` when `includeSubDirs` is `true`. `FEATURE` and `ALL`
-additionally require `groupByFeature` to be `true`; with `groupByFeature = false`, only `OFF` and `SCENARIO`
-are allowed. Setting an invalid combination fails `generateFeatureDocs` with a descriptive error.
+`OFF` and `CI` are always allowed for `indexing`. `FEATURE`, `SCENARIO`, and `ALL` require `includeSubDirs`
+to be `true`; `FEATURE` and `ALL` additionally require `groupByFeature` to be `true` - with
+`groupByFeature = false`, only `SCENARIO` of those three is allowed. Setting an invalid combination fails
+`generateFeatureDocs` with a descriptive error.
+
+The `-PgherkinToAsciidoc.indexing=` project property, when set, overrides `indexing` for every
+project in the build regardless of what any project's own `gherkinToAsciidoc { }` block configures - see
+"Overriding `indexing` from the Command Line" below. An unrecognised value fails `generateFeatureDocs` with
+a descriptive error too.
====
== Running the Task
@@ -255,7 +261,9 @@ sub-project that inherits or sets `trackProgress = true` still needs its own `gl
| Inherited from the root project by default (subject to the usual `trackProgress` implication).
| `indexing`
| Inherited from the root project by default; a sub-project can override it independently, subject to
-the usual `includeSubDirs`/`groupByFeature` validation constraints for whichever value is in effect.
+the usual `includeSubDirs`/`groupByFeature` validation constraints for whichever value is in effect. The
+`-PgherkinToAsciidoc.indexing` command-line override (see "Overriding `indexing` from the Command Line"
+above) takes precedence over both, for every project in the build at once.
| `outputFileName`
| Inherited from the root project by default.
| `template`
@@ -438,6 +446,7 @@ a Cucumber test run, ...), not only in `generateFeatureDocs`'s own output.
| `FEATURE` | Every feature is numbered, e.g. `Feature: 1 - User authentication`. Scenario titles are untouched.
| `SCENARIO` | Every scenario is numbered continuously across all feature files, e.g. `Scenario: 1 - User logs in`. Feature titles are untouched.
| `ALL` | Both are numbered; scenarios are numbered per feature as `.`, e.g. `Scenario: 1.1 - User logs in` within `Feature: 1 - User authentication`.
+| `CI` | Indexing is skipped entirely: the source files aren't touched at all - unlike `OFF`, not even to strip prior numbering. See "Overriding `indexing` from the Command Line" below.
|===
Feature files are processed - and numbered - in the same order the generated report lists them in: for each
@@ -511,10 +520,31 @@ a no-op once the files are already correctly numbered), but it does mean the tas
its own inputs - commit the resulting numbered `.feature` files like any other source change.
====
+=== Overriding `indexing` from the Command Line
+
+A CI pipeline typically shouldn't have `generateFeatureDocs` rewrite committed `.feature` files during a
+build - a fresh checkout has no numbering to add, and a build that mutates its own source tree can leave a
+CI job with unexpected local changes for no benefit, since nothing there will ever commit them. Rather than
+requiring every project's build script to special-case CI, override `indexing` from the command line
+instead:
+
+[source,bash]
+----
+./gradlew generateFeatureDocs -PgherkinToAsciidoc.indexing=ci
+----
+
+This sets `indexing` to `IndexingMode.CI` for every project in the build - root and every sub-project alike
+- regardless of what each project's own `gherkinToAsciidoc { }` block configures. A project can keep
+`indexing = IndexingMode.FEATURE` (or any other value) for local/day-to-day use and still build cleanly in
+CI without any source file ever being mutated there, with no change to the build script itself. The
+property's value is matched against `IndexingMode` case-insensitively and accepts any of the five values
+(`off`, `feature`, `scenario`, `all`, `ci`), not just `ci` - useful for e.g. temporarily forcing a specific
+mode across an entire multi-project build without editing every sub-project's configuration.
+
See the
link:../examples/gherkin-to-asciidoc/indexing/README.adoc[indexing example]
-for all four modes applied to the same feature files side by side, with the exact before/after content and
-generated reports for each.
+for all five modes applied to the same feature files side by side, with the exact before/after content and
+generated reports for each, plus the command-line override in action.
== Tracking Implementation Progress
diff --git a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GenerateFeatureDocsTask.java b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GenerateFeatureDocsTask.java
index 8427040..9138a63 100644
--- a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GenerateFeatureDocsTask.java
+++ b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GenerateFeatureDocsTask.java
@@ -161,9 +161,11 @@ public abstract class GenerateFeatureDocsTask extends DefaultTask {
/**
* Whether - and how - to number {@code Feature}/{@code Scenario} titles directly in the
- * source {@code .feature} files. Only allowed when {@link #getIncludeSubDirs()} is
- * {@code true}; when {@link #getGroupByFeature()} is {@code false}, only
- * {@link IndexingMode#OFF} and {@link IndexingMode#SCENARIO} are allowed.
+ * source {@code .feature} files. {@link IndexingMode#OFF} and {@link IndexingMode#CI} are
+ * always allowed; {@link IndexingMode#FEATURE}, {@link IndexingMode#SCENARIO}, and
+ * {@link IndexingMode#ALL} are only allowed when {@link #getIncludeSubDirs()} is
+ * {@code true}, and when {@link #getGroupByFeature()} is {@code false}, only
+ * {@link IndexingMode#SCENARIO} of those three is allowed.
*
* @return mutable property for the indexing mode
*/
@@ -224,21 +226,26 @@ public void generate() {
IndexingMode indexing = getIndexing().get();
boolean groupByFeature = getGroupByFeature().get();
- if (indexing != IndexingMode.OFF && !getIncludeSubDirs().get()) {
+ boolean indexingActive = indexing != IndexingMode.OFF && indexing != IndexingMode.CI;
+ if (indexingActive && !getIncludeSubDirs().get()) {
throw new GradleException(
"gherkinToAsciidoc: indexing can only be used when includeSubDirs is true.");
}
- if (indexing != IndexingMode.OFF && indexing != IndexingMode.SCENARIO && !groupByFeature) {
+ if (indexingActive && indexing != IndexingMode.SCENARIO && !groupByFeature) {
throw new GradleException(
"gherkinToAsciidoc: when groupByFeature is false, indexing can only be "
- + "'off' or 'scenario'.");
+ + "'off', 'ci', or 'scenario'.");
}
// trackProgress implies recursive scanning, regardless of includeSubDirs's own value.
boolean recursive = trackProgress || getIncludeSubDirs().get();
List featureFiles = collectFeatureFiles(sourceDirsSet, sourceFileSet, recursive);
- new FeatureIndexer().reindex(featureFiles, indexing);
+ // CI skips indexing entirely - the feature files are left completely untouched, not even
+ // to strip numbering left over from a previous run, unlike OFF.
+ if (indexing != IndexingMode.CI) {
+ new FeatureIndexer().reindex(featureFiles, indexing);
+ }
List scenarios = new ArrayList<>();
FeatureParser featureParser = new FeatureParser();
diff --git a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocExtension.java b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocExtension.java
index c9718f0..b8544ac 100644
--- a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocExtension.java
+++ b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocExtension.java
@@ -24,6 +24,9 @@
* indexing = IndexingMode.OFF // default; requires includeSubDirs = true
* }
*
+ *
+ * {@code indexing} can be overridden for the whole build from the command line, e.g.
+ * {@code -PgherkinToAsciidoc.indexing=ci} - see {@link #getIndexing()}.
*/
public abstract class GherkinToAsciidocExtension {
@@ -42,6 +45,14 @@ public GherkinToAsciidocExtension() {}
/** Default relative path of the directory report snippets are written to. */
public static final String DEFAULT_SNIPPET_DIR = "generated-docs/features/snippets";
+ /**
+ * Name of the Gradle project property that overrides {@link #getIndexing()} from the command
+ * line for every project in the build, e.g. {@code -PgherkinToAsciidoc.indexing=ci}. Takes
+ * precedence over any project's own configured {@code indexing} value. The value is matched
+ * against {@link IndexingMode} enum constant names case-insensitively.
+ */
+ public static final String INDEXING_OVERRIDE_PROPERTY = "gherkinToAsciidoc.indexing";
+
/**
* Source directories that contain the {@code .feature} files to process. One or more
* directories may be configured, e.g. via {@code sourceDirs.from(file('a'), file('b'))}.
@@ -163,6 +174,9 @@ public GherkinToAsciidocExtension() {}
* feature files, e.g. {@code Scenario: 1 - User logs in}.
* {@link IndexingMode#ALL} - both are numbered, scenarios as
* {@code .}, e.g. {@code Scenario: 1.1 - User logs in}.
+ * {@link IndexingMode#CI} - indexing is skipped entirely; unlike {@link IndexingMode#OFF},
+ * the source files aren't even stripped of prior numbering. See
+ * {@value #INDEXING_OVERRIDE_PROPERTY} below.
*
*
* Feature files are processed in the same order the generated report lists them in: for each
@@ -175,9 +189,17 @@ public GherkinToAsciidocExtension() {}
* fresh numbering is applied for the new mode - including removing all numbering when set back
* to {@link IndexingMode#OFF}.
*
- * Only allowed when {@link #getIncludeSubDirs()} is {@code true}. When
- * {@link #getGroupByFeature()} is {@code false}, only {@link IndexingMode#OFF} and
- * {@link IndexingMode#SCENARIO} are allowed.
+ * {@link IndexingMode#OFF} and {@link IndexingMode#CI} are always allowed.
+ * {@link IndexingMode#FEATURE}, {@link IndexingMode#SCENARIO}, and {@link IndexingMode#ALL} are
+ * only allowed when {@link #getIncludeSubDirs()} is {@code true}; when
+ * {@link #getGroupByFeature()} is {@code false}, only {@link IndexingMode#SCENARIO} of those
+ * three is allowed.
+ *
+ * The {@value #INDEXING_OVERRIDE_PROPERTY} project property, when set (e.g.
+ * {@code -PgherkinToAsciidoc.indexing=ci}), overrides this property for every project in the
+ * build regardless of what any project configures here - typically used to force
+ * {@link IndexingMode#CI} in a CI pipeline so {@code generateFeatureDocs} never mutates source
+ * files there, without having to change the build script itself.
*
* @return mutable property for the indexing mode
*/
diff --git a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocPlugin.java b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocPlugin.java
index 9f19617..69668e0 100644
--- a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocPlugin.java
+++ b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocPlugin.java
@@ -1,6 +1,7 @@
package com.arc_e_tect.gradle.gherkin;
import com.arc_e_tect.gradle.gherkin.indexing.IndexingMode;
+import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
@@ -10,6 +11,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
/**
* Gradle plugin that registers the {@code generateFeatureDocs} task and wires
@@ -57,6 +59,12 @@
* regardless of what the root project configures, so that every project's report lands in its
* own build output rather than colliding with another project's. Set these explicitly on a
* specific project to relocate that project's report.
+ *
+ * Overriding {@code indexing} from the command line
+ * The {@code -PgherkinToAsciidoc.indexing=<value>} project property overrides {@code indexing}
+ * for every project in the build, regardless of what any project's own {@code gherkinToAsciidoc { }}
+ * block configures - typically set to {@code ci} in a CI pipeline so {@code generateFeatureDocs} never
+ * mutates source {@code .feature} files there, without having to change the build script itself.
*/
public class GherkinToAsciidocPlugin implements Plugin {
@@ -110,6 +118,13 @@ public void apply(Project project) {
Project rootProject = project.getRootProject();
+ // The -PgherkinToAsciidoc.indexing= project property, when set, overrides indexing
+ // for every project in the build - regardless of what any project's own extension
+ // configures - typically used to force `ci` in a CI pipeline without touching build scripts.
+ Provider indexingCliOverride = project.getProviders()
+ .gradleProperty(GherkinToAsciidocExtension.INDEXING_OVERRIDE_PROPERTY)
+ .map(GherkinToAsciidocPlugin::parseIndexingMode);
+
project.getTasks().register(TASK_NAME, GenerateFeatureDocsTask.class, task -> {
wireSourceLocation(project, rootProject, ext, rootExt, task);
task.getIncludeSubDirs().set(ext.getIncludeSubDirs());
@@ -121,11 +136,27 @@ public void apply(Project project) {
task.getSnippetDir().set(ext.getSnippetDir());
task.getTemplate().set(ext.getTemplate());
task.getSystemUnderTestVersion().set(ext.getSystemUnderTestVersion());
- task.getIndexing().set(ext.getIndexing());
+ task.getIndexing().set(indexingCliOverride.orElse(ext.getIndexing()));
task.getProjectDirectory().set(project.getLayout().getProjectDirectory());
});
}
+ /**
+ * Parses the {@code -PgherkinToAsciidoc.indexing=} project property's value, matching
+ * {@link IndexingMode} enum constant names case-insensitively (e.g. {@code ci} ->
+ * {@link IndexingMode#CI}).
+ */
+ private static IndexingMode parseIndexingMode(String value) {
+ try {
+ return IndexingMode.valueOf(value.trim().toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException e) {
+ throw new GradleException(
+ "gherkinToAsciidoc: invalid value '" + value + "' for -P"
+ + GherkinToAsciidocExtension.INDEXING_OVERRIDE_PROPERTY
+ + "; expected one of: off, feature, scenario, all, ci");
+ }
+ }
+
/**
* Wires the task's {@code sourceDirs}/{@code sourceFile} from this project's own extension, or -
* when neither is configured locally and a root extension exists - from the root project's own
diff --git a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/indexing/FeatureIndexer.java b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/indexing/FeatureIndexer.java
index 90f17e8..ab9d5ee 100644
--- a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/indexing/FeatureIndexer.java
+++ b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/indexing/FeatureIndexer.java
@@ -20,6 +20,10 @@
* {@link IndexingMode#OFF} - applies fresh numbering. This makes the operation idempotent and
* makes switching between modes (including back to {@code OFF}) simply undo the previous
* numbering rather than requiring any state to be tracked between runs.
+ *
+ * Never called with {@link IndexingMode#CI}: the caller skips invoking this class entirely for
+ * that mode, since {@code CI} means the feature files must be left completely untouched, not even
+ * to strip prior numbering the way {@link IndexingMode#OFF} does.
*/
public class FeatureIndexer {
diff --git a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/indexing/IndexingMode.java b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/indexing/IndexingMode.java
index 6eb88e8..0b42267 100644
--- a/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/indexing/IndexingMode.java
+++ b/gherkin-to-asciidoc/src/main/java/com/arc_e_tect/gradle/gherkin/indexing/IndexingMode.java
@@ -27,5 +27,15 @@ public enum IndexingMode {
* {@code .}, e.g. {@code Scenario: 1.1 - User logs in}
* within {@code Feature: 1 - User authentication}.
*/
- ALL
+ ALL,
+
+ /**
+ * Indexing is skipped entirely: the source {@code .feature} files are left completely
+ * untouched, not even to strip numbering left over from a previous run - unlike {@link #OFF},
+ * which does strip it. Intended to be set via the {@code -PgherkinToAsciidoc.indexing=ci}
+ * command-line override (see {@code GherkinToAsciidocExtension#getIndexing()}) so a CI
+ * pipeline never mutates source files, regardless of the {@code indexing} value configured in
+ * the build script - but it can also be configured directly like any other value.
+ */
+ CI
}
diff --git a/gherkin-to-asciidoc/src/test/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocPluginTest.java b/gherkin-to-asciidoc/src/test/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocPluginTest.java
index 9699166..e443461 100644
--- a/gherkin-to-asciidoc/src/test/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocPluginTest.java
+++ b/gherkin-to-asciidoc/src/test/java/com/arc_e_tect/gradle/gherkin/GherkinToAsciidocPluginTest.java
@@ -15,6 +15,7 @@
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DisplayName("GherkinToAsciidocPlugin")
@@ -786,7 +787,7 @@ void throwsWhenIndexingFeatureAndGroupByFeatureFalse() throws IOException {
assertThatThrownBy(task::generate)
.isInstanceOf(org.gradle.api.GradleException.class)
- .hasMessageContaining("when groupByFeature is false, indexing can only be 'off' or 'scenario'");
+ .hasMessageContaining("when groupByFeature is false, indexing can only be 'off', 'ci', or 'scenario'");
}
@Test
@@ -806,7 +807,7 @@ void throwsWhenIndexingAllAndGroupByFeatureFalse() throws IOException {
assertThatThrownBy(task::generate)
.isInstanceOf(org.gradle.api.GradleException.class)
- .hasMessageContaining("when groupByFeature is false, indexing can only be 'off' or 'scenario'");
+ .hasMessageContaining("when groupByFeature is false, indexing can only be 'off', 'ci', or 'scenario'");
}
@Test
@@ -929,6 +930,96 @@ void changingIndexingToOffRemovesNumberingOnNextRun() throws IOException {
.doesNotContain("1.1 -");
}
+ @Test
+ @DisplayName("indexing CI leaves feature files completely untouched, even numbering left over from a previous run")
+ void indexingCiLeavesFilesCompletelyUntouched() throws IOException {
+ Project project = projectWithPlugin();
+ File featuresDir = new File(tempDir.toFile(), "features");
+ featuresDir.mkdirs();
+ // Simulates numbering left over from an earlier ALL-mode run.
+ writeFeatureFile(featuresDir, "sample.feature",
+ "Feature: 1 - Sample\n\n Scenario: 1.1 - A scenario\n Given g\n");
+ String before = Files.readString(featuresDir.toPath().resolve("sample.feature"));
+ File outputDir = new File(tempDir.toFile(), "output");
+
+ GenerateFeatureDocsTask task = task(project);
+ task.getSourceDirs().from(featuresDir);
+ task.getIndexing().set(IndexingMode.CI);
+ task.getOutputDir().set(outputDir);
+ task.getProjectDirectory().set(project.getLayout().getProjectDirectory());
+ task.generate();
+
+ // Unlike OFF, CI does not even strip prior numbering - the file is byte-for-byte unchanged.
+ assertThat(Files.readString(featuresDir.toPath().resolve("sample.feature"))).isEqualTo(before);
+ String content = Files.readString(new File(outputDir, "features.adoc").toPath());
+ assertThat(content).contains("* Scenario: 1.1 - A scenario");
+ }
+
+ @Test
+ @DisplayName("indexing CI is allowed even when includeSubDirs and groupByFeature are both false")
+ void indexingCiAllowedRegardlessOfIncludeSubDirsAndGroupByFeature() throws IOException {
+ Project project = projectWithPlugin();
+ File featuresDir = new File(tempDir.toFile(), "features");
+ featuresDir.mkdirs();
+ writeFeatureFile(featuresDir, "sample.feature", "Feature: Sample\n\n Scenario: A scenario\n Given g\n");
+
+ GenerateFeatureDocsTask task = task(project);
+ task.getSourceDirs().from(featuresDir);
+ task.getIncludeSubDirs().set(false);
+ task.getGroupByFeature().set(false);
+ task.getIndexing().set(IndexingMode.CI);
+ task.getOutputDir().set(new File(tempDir.toFile(), "output"));
+ task.getProjectDirectory().set(project.getLayout().getProjectDirectory());
+
+ assertThatCode(task::generate).doesNotThrowAnyException();
+ }
+
+ @Test
+ @DisplayName("the -PgherkinToAsciidoc.indexing project property overrides indexing regardless of the configured value")
+ void cliPropertyOverridesConfiguredIndexing() throws IOException {
+ Files.writeString(tempDir.resolve("gradle.properties"), "gherkinToAsciidoc.indexing=ci\n");
+ Project project = projectWithPlugin();
+ File featuresDir = new File(tempDir.toFile(), "features");
+ featuresDir.mkdirs();
+ writeFeatureFile(featuresDir, "sample.feature",
+ "Feature: 1 - Sample\n\n Scenario: 1.1 - A scenario\n Given g\n");
+ String before = Files.readString(featuresDir.toPath().resolve("sample.feature"));
+
+ GenerateFeatureDocsTask task = task(project);
+ task.getSourceDirs().from(featuresDir);
+ // Configured to ALL, but the CLI override must win.
+ extension(project).getIndexing().set(IndexingMode.ALL);
+ task.getOutputDir().set(new File(tempDir.toFile(), "output"));
+ task.getProjectDirectory().set(project.getLayout().getProjectDirectory());
+ task.generate();
+
+ assertThat(task.getIndexing().get()).isEqualTo(IndexingMode.CI);
+ assertThat(Files.readString(featuresDir.toPath().resolve("sample.feature"))).isEqualTo(before);
+ }
+
+ @Test
+ @DisplayName("an invalid -PgherkinToAsciidoc.indexing value throws a descriptive GradleException")
+ void cliPropertyInvalidValueThrowsDescriptiveError() throws IOException {
+ Files.writeString(tempDir.resolve("gradle.properties"), "gherkinToAsciidoc.indexing=bogus\n");
+ Project project = projectWithPlugin();
+ File featuresDir = new File(tempDir.toFile(), "features");
+ featuresDir.mkdirs();
+ writeFeatureFile(featuresDir, "sample.feature", "Feature: Sample\n\n Scenario: A scenario\n Given g\n");
+
+ GenerateFeatureDocsTask task = task(project);
+ task.getSourceDirs().from(featuresDir);
+ task.getOutputDir().set(new File(tempDir.toFile(), "output"));
+ task.getProjectDirectory().set(project.getLayout().getProjectDirectory());
+
+ // Gradle wraps the exception thrown while lazily resolving the "indexing" property's
+ // value (the -P override is parsed lazily via a Provider) in a PropertyQueryException;
+ // the GradleException with the actual descriptive message is its root cause.
+ assertThatThrownBy(task::generate)
+ .hasRootCauseInstanceOf(org.gradle.api.GradleException.class)
+ .hasRootCauseMessage("gherkinToAsciidoc: invalid value 'bogus' for -PgherkinToAsciidoc.indexing; "
+ + "expected one of: off, feature, scenario, all, ci");
+ }
+
// --- helpers ---
private Project projectWithPlugin() {