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() {