diff --git a/.claude/skills/adding-a-language-port/SKILL.md b/.claude/skills/adding-a-language-port/SKILL.md index 58e0d182..464ebc14 100644 --- a/.claude/skills/adding-a-language-port/SKILL.md +++ b/.claude/skills/adding-a-language-port/SKILL.md @@ -52,8 +52,8 @@ not four: | Package | Depends on | Owns | Never touches | |---|---|---|---| | `-core` (e.g. `var-core` / `var_core`) | nothing runtime-ish | pure pipeline: parse → match → plan → execute, diffs, drift, conformance projections | filesystem, network, globals, time, test-framework types | -| `` facade (e.g. `@oselvar/var` / `var`) | `-core` | author API only: `defineState`/`define_state` (context/action/sensor), `registry` glue subpath | pipeline internals directly (goes through core) | -| `-config` (e.g. `@oselvar/var-config` / `var_config`) | nothing (pure) | the `var.config.json` reader — strict, fail-loud; its own conformance corpus | filesystem beyond reading the one config file | +| `` facade (e.g. `@varar/varar` / `var`) | `-core` | author API only: `defineState`/`define_state` (context/action/sensor), `registry` glue subpath | pipeline internals directly (goes through core) | +| `-config` (e.g. `@varar/config` / `var_config`) | nothing (pure) | the `varar.config.json` reader — strict, fail-loud; its own conformance corpus | filesystem beyond reading the one config file | | `-runner` (e.g. `var-runner`) | facade + config + core | imperative shell: spec/step discovery (globs), `load_steps`, `run_spec`/`plan_spec`, failure rendering, the filesystem `BaselineStore` (drift) | any one test framework's types | | `-` (e.g. `var-vitest`, `var-pytest`) | `-runner` | one test-framework binding: collection (one test item per example), fixture/DI bridging, reporting, the drift gate | pipeline logic (delegates to runner/core) | @@ -119,21 +119,21 @@ gated milestone: `BaselineStore` port. Drift re-identifies examples by Jaccard word-similarity (`DRIFT_SIMILARITY_THRESHOLD = 0.5`, ported byte-identically), flags a paragraph that *was* an example and now matches zero steps, reports on the - Diagnostic rail (code `drift`), and persists a `var.lock.json` baseline. + Diagnostic rail (code `drift`), and persists a `varar.lock.json` baseline. **This stage has no conformance golden** (bundles carry no baseline), so it is the one core feature proven by **translating the unit tests** (`hash.test.ts`, `drift.test.ts`) rather than reproducing goldens. Note - `var.lock.json` uses its *own* serializer — `JSON.stringify(_, null, 2) + + `varar.lock.json` uses its *own* serializer — `JSON.stringify(_, null, 2) + "\n"` with spec paths sorted but **insertion-order keys otherwise** (`version, specs`; per spec `sourceHash, examples`; per example `name, line`) — NOT the recursive alphabetical key-sort of `canonical_json`. Drift is already ported to TS, Python, and the JVM; follow the closest precedent - (`python/packages/var-core/src/var_core/{hash,drift}.py`, Java + (`python/packages/core/src/varar_core/{hash,drift}.py`, Java `Drift.java`/`Hash.java`). The first three stages each have a named projection function (`toVarDocArtifact`, `toRegistryArtifact`, `toPlanArtifact`) in -`typescript/packages/var-core/src/conformance.ts` — port each exactly; it +`typescript/packages/core/src/conformance.ts` — port each exactly; it defines the wire shape the goldens were generated from. The trace stage has no separate `toTraceArtifact`; it's built inline inside `runConformance` in the same file (the executor's recorded events projected directly) — look @@ -201,12 +201,12 @@ module-scope accumulator. (`hash` + `drift` are the drift feature — see stage Each module's TS source file **and** its `*.test.ts` are the authoritative spec — translate the test first (watch it fail), then the implementation. -Not everything under `typescript/packages/var-core/src/` belongs on this +Not everything under `typescript/packages/core/src/` belongs on this list: files like `config.ts` and `find-files.ts` are `var-config`/runner concerns, and `ports.ts` declares the port interfaces (`TestSink`, `Reporter`, `BaselineStore`) that adapters implement. If a `.ts` file in that directory isn't in the list above and doesn't have a -`python/packages/var-core/src/var_core/*.py` counterpart, don't assume it needs +`python/packages/core/src/varar_core/*.py` counterpart, don't assume it needs porting for v1 — confirm against the design docs first. (`hash.ts` *used* to be on the "skip for v1" list; drift promoted it to required.) @@ -266,14 +266,14 @@ TestEngine (JUnit), a `pytest_collect_file` hook (pytest), a generated - **Collection**: one test item per *example* (not per file), independently selectable/reportable, with the item's location pointing at the `.md` source line, not adapter internals. -- **Discovery/config**: one `var.config.json` per workspace root, shared +- **Discovery/config**: one `varar.config.json` per workspace root, shared verbatim across every port — canonical keys `docs: {include, exclude}` (globs; no special file extension, a file is a spec iff its path matches the `docs` globs), `steps` (a glob array), `snippets`, and `scannerPlugins` (plugin name strings, resolved to functions per-language via a name - registry). The schema lives at `conformance/config/var.config.schema.json`. + registry). The schema lives at `conformance/config/varar.config.schema.json`. Each port reads the same JSON with its own small config package - (`@oselvar/var-config` in TypeScript, `var_config` in Python, `var-config` + (`@varar/config` in TypeScript, `var_config` in Python, `var-config` in Java) — do not invent an ecosystem-idiomatic surface (no `[tool.var]` table, no per-language field names); a new port's reader must reproduce the shared conformance corpus at `conformance/config/cases/*/golden.json` @@ -291,12 +291,12 @@ TestEngine (JUnit), a `pytest_collect_file` hook (pytest), a generated re-derive failure text from scratch in the adapter. - **Async**: if the language has an async/coroutine convention, the executor should drive it transparently; the adapter needs no special casing. -- **Drift gate**: each adapter reconciles every spec against `var.lock.json` +- **Drift gate**: each adapter reconciles every spec against `varar.lock.json` via the runner's filesystem `BaselineStore` + `reconcileDrift`, surfaces a `drift` diagnostic on the same Diagnostic rail as `ambiguous-match` (a drifted example fails the suite), writes the baseline on a clean run, and honours an `--update`/acknowledgment path (ADR 0002 — never silently accept drift). Add - a per-adapter drift test with a `var.lock.json` fixture (precedent: + a per-adapter drift test with a `varar.lock.json` fixture (precedent: `var-pytest`/`var-unittest` `tests/test_drift.py`, `var-kotest`'s `kotest-drift/` resources). @@ -324,13 +324,13 @@ suite: - **Standalone `examples/-/` consumer projects** — one per adapter, **not** workspace members: they depend on the released (or locally-installed) artifacts exactly like a user's project, carry their own - `var.config.json`, and implement the feature-covering subset (`hello-var`, + `varar.config.json`, and implement the feature-covering subset (`hello-var`, `deep-thought`, `tables-and-docstrings`, `yahtzee`, `roman-numerals`). Their `.md` specs are symlinks to the `typescript-vitest` originals (the release sync dereferences them). Add rows to `examples/README.md`. - **`release/targets/NN-.sh`** publishing the port's packages to its registry (npm / PyPI / Maven Central / RubyGems), plus adding the port to the - release channels. The `oselvar/var-examples` sync (`60-var-examples.sh`) picks + release channels. The `oselvar/varar-examples` sync (`70-varar-examples.sh`) picks up new `examples/-*` projects, but its **version-pinning rewrite is per-ecosystem** — add a pin block (and any lockfile exclusion) for a new registry, mirroring the npm/PyPI/Maven/RubyGems ones. Also extend the @@ -356,7 +356,7 @@ suite: asserts at build time that every port in `languages.json` has a code tab, so a missing one is a hard build error (message names the language). It's caught in the PR gate because `make typescript` / the CI `test` job build the website - (`pnpm --filter @oselvar/website... build`); run either to surface what you owe. + (`pnpm --filter @varar/website... build`); run either to surface what you owe. - **CodeMirror editor highlighting**: add the language's syntax highlighter to `CM_LANGUAGE` in `typescript/packages/website/src/lib/cm-languages.ts` — an official `@codemirror/lang-` (Lezer, like ts/java/python) if one exists, @@ -367,7 +367,7 @@ suite: itself isn't type-checked in CI, so that test — not tsc — is the enforcement). - **Tree-sitter dialect** (the LSP/editor authoring surface — a *required* deliverable now, not deferred): create - `typescript/packages/var-language/src/tree-sitter-dialects/.ts` + `typescript/packages/language/src/tree-sitter-dialects/.ts` (a `LanguageSpec`: step-def + parameter-type queries, `decodeString`, `extractHandlerParams`, `resolveRegexp`) with queries **verified empirically** against the real grammar's node shapes, then wire it into @@ -391,7 +391,7 @@ lists the language. Run it (or `make typescript`) to find what you still owe. ## Config conformance corpus (a distinct byte-for-byte gate) `var-config` has its own corpus at `conformance/config/cases/*/`, separate from -`conformance/bundles/`. Each case holds a `var.config.json` plus either a +`conformance/bundles/`. Each case holds a `varar.config.json` plus either a `golden.json` (parse succeeds → project to the canonical shape, serialize with your `canonical_json`, byte-compare) or an `expect-error.txt` marker (loading must **raise** — the txt is human-only, not asserted). Reproduce all cases @@ -434,12 +434,12 @@ how many languages exist; a new port does not touch them: | What does the task-by-task TDD execution look like? | `doc/superpowers/plans/2026-06-30-python-core-port.md` | | How is core/facade split, and why? | `doc/superpowers/plans/2026-06-30-python-core-split.md` | | How does the runner + test-framework adapter fit together? | `doc/superpowers/specs/2026-06-30-var-pytest-plugin-design.md` | -| Reference implementation (engine) | `typescript/packages/var-core/src/*.ts`, completed mirror at `python/packages/var-core/src/var_core/*.py` | -| Reference implementation (facade) | `typescript/packages/var/src/{index,internal,registry}.ts`, `python/packages/var/src/var/{__init__,internal,registry}.py` | -| Reference implementation (runner) | `typescript/packages/var-runner/src/*.ts`, `python/packages/var-runner/src/var_runner/*.py` | -| Reference implementation (test-framework adapter) | `typescript/packages/var-vitest/src/*.ts`, `python/packages/var-pytest/src/var_pytest/*.py` | -| Reference implementation (drift, unit-gated) | `typescript/packages/var-core/src/{drift,hash}.ts` + `tests/{drift,hash}.test.ts`; mirror at `python/packages/var-core/src/var_core/{drift,hash}.py`; `java/var-core/.../{Drift,Hash}.java` | -| The conformance corpus + goldens | `conformance/bundles/*/{example.md, *.steps.{ts,py,kt,rb}, *Steps.java, golden/*.json}` — 15 bundles, four artifacts each; **plus** the config corpus `conformance/config/cases/*/{var.config.json, golden.json|expect-error.txt}` | +| Reference implementation (engine) | `typescript/packages/core/src/*.ts`, completed mirror at `python/packages/core/src/varar_core/*.py` | +| Reference implementation (facade) | `typescript/packages/varar/src/{index,internal,registry}.ts`, `python/packages/varar/src/varar/{__init__,internal,registry}.py` | +| Reference implementation (runner) | `typescript/packages/runner/src/*.ts`, `python/packages/runner/src/varar_runner/*.py` | +| Reference implementation (test-framework adapter) | `typescript/packages/vitest/src/*.ts`, `python/packages/pytest/src/varar_pytest/*.py` | +| Reference implementation (drift, unit-gated) | `typescript/packages/core/src/{drift,hash}.ts` + `tests/{drift,hash}.test.ts`; mirror at `python/packages/core/src/varar_core/{drift,hash}.py`; `java/core/.../{Drift,Hash}.java` | +| The conformance corpus + goldens | `conformance/bundles/*/{example.md, *.steps.{ts,py,kt,rb}, *Steps.java, golden/*.json}` — 15 bundles, four artifacts each; **plus** the config corpus `conformance/config/cases/*/{varar.config.json, golden.json|expect-error.txt}` | ## Common mistakes @@ -471,7 +471,7 @@ Some ports don't re-port the pipeline at all. The rule, now settled: - A new language that **shares a runtime with an existing port** can be a thin **facade over that port's engine** — no second pipeline, no full four-artifact conformance run. **Kotlin did exactly this over Java**: `var-kotlin` - (`com.oselvar.varkt`) is an author-facade + Kotest adapter sitting on the + (`dev.varar.kotlin`) is an author-facade + Kotest adapter sitting on the compiled Java `var-core`; both are JVM bytecode. Its conformance scope is the **registry stage only** (its `*.steps.kt` fixtures prove registration); parse/plan/trace stay proven by the Java engine's already-green corpus. diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml index bb4c2a25..122b57bc 100644 --- a/.github/workflows/typescript.yml +++ b/.github/workflows/typescript.yml @@ -7,14 +7,14 @@ on: - 'typescript/**' - 'conformance/**' - 'examples/**' - - 'var.config.json' + - 'varar.config.json' - '.github/workflows/typescript.yml' pull_request: paths: - 'typescript/**' - 'conformance/**' - 'examples/**' - - 'var.config.json' + - 'varar.config.json' - '.github/workflows/typescript.yml' workflow_dispatch: @@ -47,7 +47,7 @@ jobs: # its components assert at build time that every port in # languages.json has example files, so this is where a forgotten port's # missing examples become a red build instead of surfacing only at deploy. - - run: pnpm --filter @oselvar/website... build + - run: pnpm --filter @varar/website... build deploy-website: needs: test @@ -74,7 +74,7 @@ jobs: - run: pnpm install --frozen-lockfile - - run: pnpm --filter @oselvar/website... build + - run: pnpm --filter @varar/website... build - uses: cloudflare/wrangler-action@v4 with: diff --git a/.gitignore b/.gitignore index 317104a0..60294c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,15 +7,15 @@ coverage .superpowers .var/ -# var.lock.json baselines written by var-cli integration tests (temp fixtures) -typescript/packages/var-cli/tests/fixtures/run-basic/var.lock.json -typescript/packages/var-cli/tests/fixtures/drift-tmp-*/ -# var.lock.json written by the Python conformance dogfood command -python/var.lock.json -# var.lock.json written into the Kotest fixture roots on every test run +# varar.lock.json baselines written by var-cli integration tests (temp fixtures) +typescript/packages/cli/tests/fixtures/run-basic/varar.lock.json +typescript/packages/cli/tests/fixtures/drift-tmp-*/ +# varar.lock.json written by the Python conformance dogfood command +python/varar.lock.json +# varar.lock.json written into the Kotest fixture roots on every test run # (the drift fixture's baseline IS committed and is not listed here) -java/var-kotest/src/test/resources/kotest-smoke/var.lock.json -java/var-kotest/src/test/resources/kotest-failing/var.lock.json +java/kotest/src/test/resources/kotest-smoke/varar.lock.json +java/kotest/src/test/resources/kotest-failing/varar.lock.json .claude/* !.claude/skills/ diff --git a/CLAUDE.md b/CLAUDE.md index 191839b6..8d5d205c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,10 +6,10 @@ Guidance for AI assistants working in this repo. This is a multi-language monorepo (ADR 0001). Top level: -- `typescript/` — the pnpm workspace (pure core `@oselvar/var`, runtime, vitest +- `typescript/` — the pnpm workspace (pure core `@varar/varar`, runtime, vitest adapter, **and** the shared authoring/LSP/VS Code/website platform). **Run all pnpm / vitest / tsc commands from `typescript/`.** Package paths in this file - (e.g. `packages/var/src/...`) are relative to `typescript/`. + (e.g. `packages/varar/src/...`) are relative to `typescript/`. - `python/` — the uv workspace for the Python port (core, runner, pytest + unittest adapters). - `java/` — the Maven multi-module workspace for the Java port (JDK 21, pinned in @@ -18,8 +18,8 @@ This is a multi-language monorepo (ADR 0001). Top level: golden/*.json}`) read by every language's conformance harness. - `doc/` — shared design docs (ADRs, specs, plans, ARCHITECTURE). - `examples/` — one standalone sample project per language/test-framework - combo, mirroring the `oselvar/var-examples` repo 1:1 (synced there on every - release by `release/targets/60-var-examples.sh`). The `.md` specs sit at + combo, mirroring the `oselvar/varar-examples` repo 1:1 (synced there on every + release by `release/targets/70-varar-examples.sh`). The `.md` specs sit at each project's root; `typescript-vitest` holds the originals, the other projects symlink their subset (the sync dereferences symlinks). @@ -32,7 +32,7 @@ Decide the quadrant before writing and don't mix them in one page. - User-facing docs live on the website: `typescript/packages/website/src/content/docs/{tutorials,how-to,reference,explanation}`. - Published to https://var.oselvar.com. + Published to https://varar.dev. - Internal/design docs (ADRs, specs, plans, ARCHITECTURE) stay in `doc/` at the repo root — they are not part of the Diátaxis structure. @@ -40,18 +40,18 @@ Decide the quadrant before writing and don't mix them in one page. - **Immutable types.** All data types are `readonly` — no mutable fields, no in-place mutation. Use `ReadonlyArray` and `ReadonlyMap`. Updates produce a new value. - **Pure functions everywhere they're possible.** Parsing, matching, planning, snippet generation, diagnostics: all pure. Given the same input, return the same output, with no side effects. -- **Functional core, imperative shell.** The core (`@oselvar/var`) is pure functions over immutable data. The shell — file I/O, module loading, test-runner integration, CLI prompts, terminal output — lives in the adapter packages (`var-vitest`, `var-node`, `var-bun`, `var-cli`) and is the *only* place side effects are allowed. +- **Functional core, imperative shell.** The core (`@varar/varar`) is pure functions over immutable data. The shell — file I/O, module loading, test-runner integration, CLI prompts, terminal output — lives in the adapter packages (`var-vitest`, `var-node`, `var-bun`, `var-cli`) and is the *only* place side effects are allowed. - **Hexagonal architecture.** The core defines ports (interfaces it depends on); adapters implement them. The core never imports from `node:fs`, `vitest`, `bun:test`, etc. — those are wired in at the edges. Concretely: | Layer | Lives in | May do | May NOT do | |-------------|-----------------------------------|-----------------------------------------|-------------------------------------| -| Core domain | `packages/var/src/*` | pure transformations over immutable AST | filesystem, network, globals, time | -| Ports | `packages/var/src/ports.ts` | declare interfaces | implement them | +| Core domain | `packages/varar/src/*` | pure transformations over immutable AST | filesystem, network, globals, time | +| Ports | `packages/varar/src/ports.ts` | declare interfaces | implement them | | Adapters | `packages/var-*/src/*` | implement ports; talk to runtime APIs | leak runtime types into the core | -If a function in `packages/var/src/` needs to read a file, it doesn't — it takes the bytes as an argument. If the matcher needs the current time, it doesn't — the caller passes it in. +If a function in `packages/varar/src/` needs to read a file, it doesn't — it takes the bytes as an argument. If the matcher needs the current time, it doesn't — the caller passes it in. ## Stack @@ -65,10 +65,10 @@ pnpm workspace · biome · vitest (for the core's own tests) · knip · jscpd · `.github/workflows/` (`typescript.yml`, `python.yml`, `java.yml`, `ruby.yml` — all also trigger on `conformance/**`). - **Trunk-based development.** We commit small, working increments straight to `main` — no long-lived feature branches. Keep each commit self-contained and green (build + tests pass), so trunk is always releasable. -- **Type-check is a separate gate.** vitest runs source through esbuild/tsx, which strips types without checking them — a fully green suite can still fail `tsc`. Run `pnpm -r build` (exit 0) before calling any change done, especially after touching a shared type, an AST node, or a package's public exports (new required fields and new exports are the usual culprits). Note `pnpm build` and `pnpm check` both exclude the website packages — the Starlight website is built separately via `pnpm --filter @oselvar/website... build`, run in two CI places: the `test` job (a PR gate — its `` components assert every port in `languages.json` has example files, so a forgotten port fails the build) and the `deploy-website` job (which also deploys to https://var.oselvar.com). The legacy `packages/website` is never built. To check the website locally: `pnpm --filter @oselvar/website build`. +- **Type-check is a separate gate.** vitest runs source through esbuild/tsx, which strips types without checking them — a fully green suite can still fail `tsc`. Run `pnpm -r build` (exit 0) before calling any change done, especially after touching a shared type, an AST node, or a package's public exports (new required fields and new exports are the usual culprits). Note `pnpm build` and `pnpm check` both exclude the website packages — the Starlight website is built separately via `pnpm --filter @varar/website... build`, run in two CI places: the `test` job (a PR gate — its `` components assert every port in `languages.json` has example files, so a forgotten port fails the build) and the `deploy-website` job (which also deploys to https://varar.dev). The legacy `packages/website` is never built. To check the website locally: `pnpm --filter @varar/website build`. - `pnpm -r build` only type-checks each package's `src/` (its `tsconfig.json` emits with `rootDir: src`). **Test files (`tests/**`) are type-checked by `pnpm typecheck`** (root `tsconfig.tests.json`, `noEmit`, covers every non-website package's `tests/`). It's part of `pnpm check`, so run `pnpm check` (or `pnpm typecheck` alone) after touching tests — a green vitest run does *not* mean the tests type-check. Note `expectTypeOf` assertions are validated here by `tsc`, not by vitest (we don't run `vitest --typecheck`). - **Dogfood specs**: `examples/typescript-vitest` (package - `@oselvar/example-typescript-vitest`, a pnpm workspace member via + `@varar/example-typescript-vitest`, a pnpm workspace member via `../examples/typescript-vitest`, workspace deps, never released) holds the original `.md` specs at its root plus their `steps/*.steps.ts`, and runs in the root vitest workspace. The JVM samples consume the SNAPSHOT installed by @@ -90,7 +90,7 @@ on everything since the last release tag): - **Scope names the consumer.** `feat`/`fix`/`perf` (and anything breaking) must be scoped `ts`, `py`, `java`, `ruby`, `vscode`, or `spec`, optionally `/package`: `feat(ts/var-vitest): …`, `fix(py/var-core): …`, - `refactor(java/var-junit)!: …`. The scope decides which changelog section + `refactor(java/junit)!: …`. The scope decides which changelog section the entry lands in (npm / PyPI / Maven Central / RubyGems / VS Code / all ports). Work that ships nothing to a consumer — website, CI, tooling — is a `chore(website): …` or similar, never a `feat`: it would bump the version @@ -98,7 +98,7 @@ on everything since the last release tag): - **The subject is the changelog line, verbatim.** Write it for the consumer reading release notes, not the reviewer reading the diff: what changed for *them*, not how. "generated modules import runtime helpers from - @oselvar/var-vitest/runtime", not "refactor virtual module codegen". + @varar/vitest/runtime", not "refactor virtual module codegen". - **Breaking changes:** append `!` to the type and add a `BREAKING CHANGE: ` footer — the note is rendered in the changelog under the entry. @@ -117,16 +117,16 @@ on everything since the last release tag): - Test files in the project's own test suite: `*.test.ts` (vitest). - BDD example files (dogfood + docs): plain `*.md`. There is no special `.var.md` - extension — a file is a spec iff its path matches the `docs` globs in `var.config.json`. + extension — a file is a spec iff its path matches the `docs` globs in `varar.config.json`. `docs` is `{ include, exclude }` (canonical shape — no array shorthand); both are plain globs, no `!` prefix. `include` has no default (empty discovers nothing); `exclude` removes matches (e.g. a not-implemented tutorial exercise). That config is the single source of truth for "what is a spec", consulted by the runner, the LSP, and the vitest plugin alike — the plugin drives vitest's own `include`/`exclude` from it. - Step definition files: `*.steps.ts`. -- Config: every example project in `examples/` carries its own `var.config.json` +- Config: every example project in `examples/` carries its own `varar.config.json` (docs `*.md` at the project root, steps per language). The repo-root - `var.config.json` mirrors `examples/typescript-vitest` for the LSP when the + `varar.config.json` mirrors `examples/typescript-vitest` for the LSP when the repo root is the workspace folder. ## Return-based comparison diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 040df85e..852aa8e1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,9 +73,9 @@ issue, as above. ## Security -Vár is a development tool — a testing framework that runs on developer +Varar is a development tool — a testing framework that runs on developer machines and CI, not in production. Its threat model is correspondingly -simple: a bug in Vár means it might fail to catch a bug in the software you +simple: a bug in Varar means it might fail to catch a bug in the software you test with it. That's it. So there is no embargoed disclosure process — report security-relevant bugs (a matcher that passes when it should fail, a comparison that silently skips) as ordinary GitHub issues, like any other diff --git a/IDEA.md b/IDEA.md index 905745fc..7adce15e 100644 --- a/IDEA.md +++ b/IDEA.md @@ -72,7 +72,7 @@ using inspiration from https://diataxis.fr/ ## Name -We'll call the tool Vár (@oselvar/var). It's a tongue in cheek because clueless Cucumber adopters would often call the examples or feature files "BDDs". I've written lots of BDDs, which is a sign they didn't quitre get the concept. So we pick this ironic name since it's fun. +We'll call the tool Varar (@varar/varar). It's a tongue in cheek because clueless Cucumber adopters would often call the examples or feature files "BDDs". I've written lots of BDDs, which is a sign they didn't quitre get the concept. So we pick this ironic name since it's fun. ## Speed diff --git a/Makefile b/Makefile index 838c3ccf..d8ee0ce3 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ commits: release/lint-commits.sh typescript: - cd typescript && pnpm install && pnpm build && pnpm check && pnpm --filter @oselvar/website... build + cd typescript && pnpm install && pnpm build && pnpm check && pnpm --filter @varar/website... build python: # Drop any .venv left pointing at an old checkout path (e.g. after a repo diff --git a/README.md b/README.md index 2d6bda8f..86dcdf90 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# Vár +# Varar **Executable Markdown documentation for humans and agents — turn your docs into tests.** -Vár lets you write plain-Markdown examples that read like prose, then runs them +Varar lets you write plain-Markdown examples that read like prose, then runs them as tests: if the code stops doing what the documentation says, the test fails. It closes the gap where code, tests, and docs quietly drift apart — so a programmer or coding agent that breaks the oath gets caught, every time. 📖 **Full documentation, tutorials, and a live browser playground: -[var.oselvar.com](https://var.oselvar.com)** +[varar.dev](https://varar.dev)** -Vár is a multi-language project with the same behaviour across five ports — +Varar is a multi-language project with the same behaviour across five ports — TypeScript, Java, Kotlin, Python, and Ruby — verified by a shared, language-neutral [conformance](conformance/) corpus. A sixth port, Rust, is in progress. @@ -24,11 +24,11 @@ workflow on `main`; coverage is distilled from each tool's native report into | Port | Build | Line coverage | Branch coverage | | --- | --- | --- | --- | -| TypeScript | [![Build](https://github.com/oselvar/var/actions/workflows/typescript.yml/badge.svg?branch=main)](https://github.com/oselvar/var/actions/workflows/typescript.yml) | ![82.5%](https://img.shields.io/badge/coverage-82.5%25-green) | ![71.4%](https://img.shields.io/badge/coverage-71.4%25-yellowgreen) | -| Java / Kotlin | [![Build](https://github.com/oselvar/var/actions/workflows/java.yml/badge.svg?branch=main)](https://github.com/oselvar/var/actions/workflows/java.yml) | ![86.2%](https://img.shields.io/badge/coverage-86.2%25-green) | ![77.2%](https://img.shields.io/badge/coverage-77.2%25-yellowgreen) | -| Python | [![Build](https://github.com/oselvar/var/actions/workflows/python.yml/badge.svg?branch=main)](https://github.com/oselvar/var/actions/workflows/python.yml) | ![65.7%](https://img.shields.io/badge/coverage-65.7%25-yellow) | ![n/a](https://img.shields.io/badge/coverage-n%2Fa-lightgrey) | -| Ruby | [![Build](https://github.com/oselvar/var/actions/workflows/ruby.yml/badge.svg?branch=main)](https://github.com/oselvar/var/actions/workflows/ruby.yml) | ![90.9%](https://img.shields.io/badge/coverage-90.9%25-brightgreen) | ![n/a](https://img.shields.io/badge/coverage-n%2Fa-lightgrey) | -| Rust | [![Build](https://github.com/oselvar/var/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/oselvar/var/actions/workflows/rust.yml) | ![n/a](https://img.shields.io/badge/coverage-n%2Fa-lightgrey) | ![n/a](https://img.shields.io/badge/coverage-n%2Fa-lightgrey) | +| TypeScript | [![Build](https://github.com/oselvar/varar/actions/workflows/typescript.yml/badge.svg?branch=main)](https://github.com/oselvar/varar/actions/workflows/typescript.yml) | ![82.5%](https://img.shields.io/badge/coverage-82.5%25-green) | ![71.4%](https://img.shields.io/badge/coverage-71.4%25-yellowgreen) | +| Java / Kotlin | [![Build](https://github.com/oselvar/varar/actions/workflows/java.yml/badge.svg?branch=main)](https://github.com/oselvar/varar/actions/workflows/java.yml) | ![86.2%](https://img.shields.io/badge/coverage-86.2%25-green) | ![77.2%](https://img.shields.io/badge/coverage-77.2%25-yellowgreen) | +| Python | [![Build](https://github.com/oselvar/varar/actions/workflows/python.yml/badge.svg?branch=main)](https://github.com/oselvar/varar/actions/workflows/python.yml) | ![65.7%](https://img.shields.io/badge/coverage-65.7%25-yellow) | ![n/a](https://img.shields.io/badge/coverage-n%2Fa-lightgrey) | +| Ruby | [![Build](https://github.com/oselvar/varar/actions/workflows/ruby.yml/badge.svg?branch=main)](https://github.com/oselvar/varar/actions/workflows/ruby.yml) | ![90.9%](https://img.shields.io/badge/coverage-90.9%25-brightgreen) | ![n/a](https://img.shields.io/badge/coverage-n%2Fa-lightgrey) | +| Rust | [![Build](https://github.com/oselvar/varar/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/oselvar/varar/actions/workflows/rust.yml) | ![n/a](https://img.shields.io/badge/coverage-n%2Fa-lightgrey) | ![n/a](https://img.shields.io/badge/coverage-n%2Fa-lightgrey) | ## Development diff --git a/TODO.md b/TODO.md index 999e27fc..02f917e2 100644 --- a/TODO.md +++ b/TODO.md @@ -55,18 +55,18 @@ ## Runtime adapters & CI -- [ ] `@oselvar/var-bun` adapter (parallel to `var-vitest`). -- [ ] `@oselvar/var-deno` adapter. +- [ ] `@varar/varar-bun` adapter (parallel to `var-vitest`). +- [ ] `@varar/varar-deno` adapter. - [ ] CI matrix: node + bun + deno. ## CLI -- [ ] `var lint` should load step files (via `buildWorkspaceIndex`) so it can +- [ ] `varar lint` should load step files (via `buildWorkspaceIndex`) so it can detect `ambiguous-match` end-to-end, not just `orphan-attachment`. -- [ ] `var lint` async `glob` crashes on symlinks (Node 22 bug). Run.ts already +- [ ] `varar lint` async `glob` crashes on symlinks (Node 22 bug). Run.ts already switched to `globSync`; lint.ts and the LSP store still use the buggy `node:fs/promises.glob`. Hoist together with the findFiles cleanup. -- [ ] `var run` Phase 2 polish: colors, file grouping summary, `--quiet`. +- [ ] `varar run` Phase 2 polish: colors, file grouping summary, `--quiet`. - [ ] CellMismatchError: arg 1: expected "Hello, world!" but was Hello, worlsd! - [ ] Quotes - [ ] arg 1 @@ -80,8 +80,8 @@ ## Code quality - [x] Hoist the `findFiles` helper (duplicated across - `packages/var-vitest/src/plugin.ts`, `packages/var-cli/src/lint.ts`, - `packages/var-cli/src/run.ts`, and `packages/var-lsp/src/store.ts`) + `packages/var-vitest/src/plugin.ts`, `packages/cli/src/lint.ts`, + `packages/cli/src/run.ts`, and `packages/lsp/src/store.ts`) into a shared utility — and standardise on `globSync`. - [ ] Move tests next to source - [ ] Move packages/var/tests/conformance.test.ts @@ -97,7 +97,7 @@ ## Runner - [x] Vitest runner -- [x] CLI runner (`var run`): ~0.74 s wall on the cucumber sample, ~2× faster +- [x] CLI runner (`varar run`): ~0.74 s wall on the cucumber sample, ~2× faster than the vitest path (~1.5 s) and slightly faster than cucumber-js (~0.85 s). See `packages/cucumber/README.md`. - [x] Cucumber.js comparison documented in `packages/cucumber/README.md` diff --git a/cliff.toml b/cliff.toml index 4e920844..6b0037eb 100644 --- a/cliff.toml +++ b/cliff.toml @@ -75,7 +75,7 @@ body = """ # The hand-written first release predates the commit convention and is kept # verbatim (generation starts at v0.1.0, see `make changelog`). footer = """ -{{ "\n## [0.1.0]\n\n### Added\n\n- First public release of var: Markdown-native BDD for TypeScript (npm), Python (PyPI), and Java/Kotlin (Maven Central), plus the Vár VS Code extension (Marketplace and Open VSX).\n" }} +{{ "\n## [0.1.0]\n\n### Added\n\n- First public release of var: Markdown-native BDD for TypeScript (npm), Python (PyPI), and Java/Kotlin (Maven Central), plus the Varar VS Code extension (Marketplace and Open VSX).\n" }} """ trim = true diff --git a/conformance/bundles/01-roman-numerals/NumeralsSteps.java b/conformance/bundles/01-roman-numerals/NumeralsSteps.java index 31b524e1..42ad2f54 100644 --- a/conformance/bundles/01-roman-numerals/NumeralsSteps.java +++ b/conformance/bundles/01-roman-numerals/NumeralsSteps.java @@ -1,14 +1,14 @@ -package com.oselvar.var.conformance.bundle01; +package dev.varar.conformance.bundle01; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.Map; /** * Java sibling of {@code numerals.steps.ts} / {@code numerals.steps.py} (bundle - * {@code 01-roman-numerals}). See {@code java/var/src/test/java/com/oselvar/var/ + * {@code 01-roman-numerals}). See {@code java/varar/src/test/java/dev/varar/ * AuthorApiTest.java}'s {@code RomanNumeralSteps} for the hand-authored prototype this * fixture is adapted from — this is the real, conformance-harness-loaded copy. * @@ -16,7 +16,7 @@ * conformance corpus (a sibling of every {@code *.steps.ts}/{@code *.steps.py} in this * directory), not under {@code var}'s {@code src/}. It reaches the test compile * classpath via {@code build-helper-maven-plugin}'s {@code add-test-source} goal - * configured in {@code java/var/pom.xml}, which adds {@code conformance/bundles} + * configured in {@code java/varar/pom.xml}, which adds {@code conformance/bundles} * as an additional test-source root — Maven's compiler plugin does not require a * source file's directory to match its package declaration, only that the directory be * a configured source root. diff --git a/conformance/bundles/01-roman-numerals/numerals.steps.kt b/conformance/bundles/01-roman-numerals/numerals.steps.kt index 542195f0..cc7defbe 100644 --- a/conformance/bundles/01-roman-numerals/numerals.steps.kt +++ b/conformance/bundles/01-roman-numerals/numerals.steps.kt @@ -6,11 +6,11 @@ // plain extension-stripping) — Kotlin has no file-name/class-name coupling, so // no PascalCase workaround is needed; @file:JvmName pins the facade class the // harness loads instead. -package com.oselvar.varkt.conformance.bundle01 +package dev.varar.kotlin.conformance.bundle01 -import com.oselvar.varkt.stimulus -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.stimulus +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor data class Ctx(val result: String? = null) diff --git a/conformance/bundles/01-roman-numerals/numerals.steps.py b/conformance/bundles/01-roman-numerals/numerals.steps.py index 9a473dda..5e777f6b 100644 --- a/conformance/bundles/01-roman-numerals/numerals.steps.py +++ b/conformance/bundles/01-roman-numerals/numerals.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/01-roman-numerals/numerals.steps.rb b/conformance/bundles/01-roman-numerals/numerals.steps.rb index be513455..2975e928 100644 --- a/conformance/bundles/01-roman-numerals/numerals.steps.rb +++ b/conformance/bundles/01-roman-numerals/numerals.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" roman = { 1 => "I", 4 => "IV", 9 => "IX", 40 => "XL" } diff --git a/conformance/bundles/01-roman-numerals/numerals.steps.rs b/conformance/bundles/01-roman-numerals/numerals.steps.rs index 39b8c657..1c0ca956 100644 --- a/conformance/bundles/01-roman-numerals/numerals.steps.rs +++ b/conformance/bundles/01-roman-numerals/numerals.steps.rs @@ -3,7 +3,7 @@ //! Full-replacement state (ADR 0006): the `{result}` map is the whole state. use std::collections::BTreeMap; -use var::{HandlerError, Registry, Steps, Value}; +use varar::{HandlerError, Registry, Steps, Value}; fn roman(n: i64) -> Option<&'static str> { match n { diff --git a/conformance/bundles/01-roman-numerals/numerals.steps.ts b/conformance/bundles/01-roman-numerals/numerals.steps.ts index 365462e6..88349c43 100644 --- a/conformance/bundles/01-roman-numerals/numerals.steps.ts +++ b/conformance/bundles/01-roman-numerals/numerals.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus, sensor } = steps<{ result?: string }>(() => ({})) diff --git a/conformance/bundles/02-context-isolation/CounterSteps.java b/conformance/bundles/02-context-isolation/CounterSteps.java index e682c633..128c2fdf 100644 --- a/conformance/bundles/02-context-isolation/CounterSteps.java +++ b/conformance/bundles/02-context-isolation/CounterSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle02; +package dev.varar.conformance.bundle02; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** Java sibling of {@code counter.steps.ts} / {@code counter.steps.py} (bundle {@code 02-context-isolation}). */ public final class CounterSteps implements StepDefinitions { diff --git a/conformance/bundles/02-context-isolation/counter.steps.kt b/conformance/bundles/02-context-isolation/counter.steps.kt index 2e4e2c2b..7fad74db 100644 --- a/conformance/bundles/02-context-isolation/counter.steps.kt +++ b/conformance/bundles/02-context-isolation/counter.steps.kt @@ -2,11 +2,11 @@ // Kotlin sibling of counter.steps.ts / counter.steps.py / CounterSteps.java // (bundle 02-context-isolation). -package com.oselvar.varkt.conformance.bundle02 +package dev.varar.kotlin.conformance.bundle02 -import com.oselvar.varkt.stimulus -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.stimulus +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor data class Ctx(val count: Int = 0) diff --git a/conformance/bundles/02-context-isolation/counter.steps.py b/conformance/bundles/02-context-isolation/counter.steps.py index 9d2be0c5..108aeba8 100644 --- a/conformance/bundles/02-context-isolation/counter.steps.py +++ b/conformance/bundles/02-context-isolation/counter.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"count": 0}) diff --git a/conformance/bundles/02-context-isolation/counter.steps.rb b/conformance/bundles/02-context-isolation/counter.steps.rb index 0fbc4c81..10d1ca86 100644 --- a/conformance/bundles/02-context-isolation/counter.steps.rb +++ b/conformance/bundles/02-context-isolation/counter.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps(count: 0) do stimulus("I increment") { |state| { count: state[:count] + 1 } } diff --git a/conformance/bundles/02-context-isolation/counter.steps.rs b/conformance/bundles/02-context-isolation/counter.steps.rs index 9665c8ec..922f5507 100644 --- a/conformance/bundles/02-context-isolation/counter.steps.rs +++ b/conformance/bundles/02-context-isolation/counter.steps.rs @@ -1,7 +1,7 @@ //! Rust sibling of `counter.steps.ts` (bundle `02-context-isolation`). use std::collections::BTreeMap; -use var::{HandlerError, Registry, Steps, Value}; +use varar::{HandlerError, Registry, Steps, Value}; fn count_of(state: &Value) -> i64 { match state { diff --git a/conformance/bundles/02-context-isolation/counter.steps.ts b/conformance/bundles/02-context-isolation/counter.steps.ts index d35d5d9d..495c6152 100644 --- a/conformance/bundles/02-context-isolation/counter.steps.ts +++ b/conformance/bundles/02-context-isolation/counter.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus, sensor } = steps<{ count: number }>(() => ({ count: 0 })) diff --git a/conformance/bundles/03-expected-failure/DivisionSteps.java b/conformance/bundles/03-expected-failure/DivisionSteps.java index 03a000d9..a2b82633 100644 --- a/conformance/bundles/03-expected-failure/DivisionSteps.java +++ b/conformance/bundles/03-expected-failure/DivisionSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle03; +package dev.varar.conformance.bundle03; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** Java sibling of {@code division.steps.ts} / {@code division.steps.py} (bundle {@code 03-expected-failure}). */ public final class DivisionSteps implements StepDefinitions { diff --git a/conformance/bundles/03-expected-failure/division.steps.kt b/conformance/bundles/03-expected-failure/division.steps.kt index 38bab923..add34c2c 100644 --- a/conformance/bundles/03-expected-failure/division.steps.kt +++ b/conformance/bundles/03-expected-failure/division.steps.kt @@ -2,10 +2,10 @@ // Kotlin sibling of division.steps.ts / division.steps.py / DivisionSteps.java // (bundle 03-expected-failure). -package com.oselvar.varkt.conformance.bundle03 +package dev.varar.kotlin.conformance.bundle03 -import com.oselvar.varkt.stimulus -import com.oselvar.varkt.steps +import dev.varar.kotlin.stimulus +import dev.varar.kotlin.steps class Ctx diff --git a/conformance/bundles/03-expected-failure/division.steps.py b/conformance/bundles/03-expected-failure/division.steps.py index 29be6d35..ee06c7d2 100644 --- a/conformance/bundles/03-expected-failure/division.steps.py +++ b/conformance/bundles/03-expected-failure/division.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/03-expected-failure/division.steps.rb b/conformance/bundles/03-expected-failure/division.steps.rb index cb767d28..a243b0a8 100644 --- a/conformance/bundles/03-expected-failure/division.steps.rb +++ b/conformance/bundles/03-expected-failure/division.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do stimulus("I divide {int} by {int}") do |_state, _a, b| diff --git a/conformance/bundles/03-expected-failure/division.steps.rs b/conformance/bundles/03-expected-failure/division.steps.rs index 8f44998a..1554fbe6 100644 --- a/conformance/bundles/03-expected-failure/division.steps.rs +++ b/conformance/bundles/03-expected-failure/division.steps.rs @@ -1,6 +1,6 @@ //! Rust sibling of `division.steps.ts` (bundle `03-expected-failure`). -use var::{HandlerError, Registry, Steps, Value}; +use varar::{HandlerError, Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/03-expected-failure/division.steps.ts b/conformance/bundles/03-expected-failure/division.steps.ts index fa775bfe..db03cad1 100644 --- a/conformance/bundles/03-expected-failure/division.steps.ts +++ b/conformance/bundles/03-expected-failure/division.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus } = steps(() => ({})) diff --git a/conformance/bundles/04-tables-and-docstrings/EchoSteps.java b/conformance/bundles/04-tables-and-docstrings/EchoSteps.java index 822d3811..ef34f490 100644 --- a/conformance/bundles/04-tables-and-docstrings/EchoSteps.java +++ b/conformance/bundles/04-tables-and-docstrings/EchoSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle04; +package dev.varar.conformance.bundle04; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** * Java sibling of {@code echo.steps.ts} / {@code echo.steps.py} (bundle {@code diff --git a/conformance/bundles/04-tables-and-docstrings/echo.steps.kt b/conformance/bundles/04-tables-and-docstrings/echo.steps.kt index df24ca1c..75f681bf 100644 --- a/conformance/bundles/04-tables-and-docstrings/echo.steps.kt +++ b/conformance/bundles/04-tables-and-docstrings/echo.steps.kt @@ -4,10 +4,10 @@ // 04-tables-and-docstrings): the doc string arrives as the trailing handler // argument after the expression's own captures (here: none) and is echoed back // for the core's doc-string comparison. -package com.oselvar.varkt.conformance.bundle04 +package dev.varar.kotlin.conformance.bundle04 -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor class Ctx diff --git a/conformance/bundles/04-tables-and-docstrings/echo.steps.py b/conformance/bundles/04-tables-and-docstrings/echo.steps.py index 9b92e211..c732004d 100644 --- a/conformance/bundles/04-tables-and-docstrings/echo.steps.py +++ b/conformance/bundles/04-tables-and-docstrings/echo.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/04-tables-and-docstrings/echo.steps.rb b/conformance/bundles/04-tables-and-docstrings/echo.steps.rb index e78ab659..c3ed25c7 100644 --- a/conformance/bundles/04-tables-and-docstrings/echo.steps.rb +++ b/conformance/bundles/04-tables-and-docstrings/echo.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do sensor("I echo the following:") { |_state, doc| doc } diff --git a/conformance/bundles/04-tables-and-docstrings/echo.steps.rs b/conformance/bundles/04-tables-and-docstrings/echo.steps.rs index e9200a6d..b9065122 100644 --- a/conformance/bundles/04-tables-and-docstrings/echo.steps.rs +++ b/conformance/bundles/04-tables-and-docstrings/echo.steps.rs @@ -1,6 +1,6 @@ //! Rust sibling of `echo.steps.ts` (bundle `04-tables-and-docstrings`). -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/04-tables-and-docstrings/echo.steps.ts b/conformance/bundles/04-tables-and-docstrings/echo.steps.ts index 26c241c2..73af73f7 100644 --- a/conformance/bundles/04-tables-and-docstrings/echo.steps.ts +++ b/conformance/bundles/04-tables-and-docstrings/echo.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { sensor } = steps(() => ({})) diff --git a/conformance/bundles/05-ambiguous-match/CukesSteps.java b/conformance/bundles/05-ambiguous-match/CukesSteps.java index d1db781f..d28a25ae 100644 --- a/conformance/bundles/05-ambiguous-match/CukesSteps.java +++ b/conformance/bundles/05-ambiguous-match/CukesSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle05; +package dev.varar.conformance.bundle05; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** * Java sibling of {@code cukes.steps.ts} / {@code cukes.steps.py} (bundle {@code diff --git a/conformance/bundles/05-ambiguous-match/cukes.steps.kt b/conformance/bundles/05-ambiguous-match/cukes.steps.kt index 727527e4..b145b676 100644 --- a/conformance/bundles/05-ambiguous-match/cukes.steps.kt +++ b/conformance/bundles/05-ambiguous-match/cukes.steps.kt @@ -3,10 +3,10 @@ // Kotlin sibling of cukes.steps.ts / cukes.steps.py / CukesSteps.java (bundle // 05-ambiguous-match): both expressions match "I have 5 cukes" -> ambiguous- // match diagnostic at the plan stage; this stage only needs both registered. -package com.oselvar.varkt.conformance.bundle05 +package dev.varar.kotlin.conformance.bundle05 -import com.oselvar.varkt.stimulus -import com.oselvar.varkt.steps +import dev.varar.kotlin.stimulus +import dev.varar.kotlin.steps class Ctx diff --git a/conformance/bundles/05-ambiguous-match/cukes.steps.py b/conformance/bundles/05-ambiguous-match/cukes.steps.py index 1be4ce19..2c7e056d 100644 --- a/conformance/bundles/05-ambiguous-match/cukes.steps.py +++ b/conformance/bundles/05-ambiguous-match/cukes.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/05-ambiguous-match/cukes.steps.rb b/conformance/bundles/05-ambiguous-match/cukes.steps.rb index bc55438f..bc5c9461 100644 --- a/conformance/bundles/05-ambiguous-match/cukes.steps.rb +++ b/conformance/bundles/05-ambiguous-match/cukes.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do stimulus("I have {int} cukes") { |_state, _n| } diff --git a/conformance/bundles/05-ambiguous-match/cukes.steps.rs b/conformance/bundles/05-ambiguous-match/cukes.steps.rs index 8b83e99f..5fbdcebd 100644 --- a/conformance/bundles/05-ambiguous-match/cukes.steps.rs +++ b/conformance/bundles/05-ambiguous-match/cukes.steps.rs @@ -1,6 +1,6 @@ //! Rust sibling of `cukes.steps.ts` (bundle `05-ambiguous-match`). -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/05-ambiguous-match/cukes.steps.ts b/conformance/bundles/05-ambiguous-match/cukes.steps.ts index db8174f3..8ab73d61 100644 --- a/conformance/bundles/05-ambiguous-match/cukes.steps.ts +++ b/conformance/bundles/05-ambiguous-match/cukes.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus } = steps(() => ({})) diff --git a/conformance/bundles/06-doc-string-mismatch/EchoSteps.java b/conformance/bundles/06-doc-string-mismatch/EchoSteps.java index 00623f73..738e1dc4 100644 --- a/conformance/bundles/06-doc-string-mismatch/EchoSteps.java +++ b/conformance/bundles/06-doc-string-mismatch/EchoSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle06; +package dev.varar.conformance.bundle06; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** * Java sibling of {@code echo.steps.ts} / {@code echo.steps.py} (bundle {@code diff --git a/conformance/bundles/06-doc-string-mismatch/echo.steps.kt b/conformance/bundles/06-doc-string-mismatch/echo.steps.kt index 65a31433..d539c48d 100644 --- a/conformance/bundles/06-doc-string-mismatch/echo.steps.kt +++ b/conformance/bundles/06-doc-string-mismatch/echo.steps.kt @@ -3,10 +3,10 @@ // Kotlin sibling of echo.steps.ts / echo.steps.py / EchoSteps.java (bundle // 06-doc-string-mismatch): deliberately returns the WRONG string so the core's // doc-string comparison fails at the trace stage. -package com.oselvar.varkt.conformance.bundle06 +package dev.varar.kotlin.conformance.bundle06 -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor class Ctx diff --git a/conformance/bundles/06-doc-string-mismatch/echo.steps.py b/conformance/bundles/06-doc-string-mismatch/echo.steps.py index 7502d856..5eb1e6b7 100644 --- a/conformance/bundles/06-doc-string-mismatch/echo.steps.py +++ b/conformance/bundles/06-doc-string-mismatch/echo.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/06-doc-string-mismatch/echo.steps.rb b/conformance/bundles/06-doc-string-mismatch/echo.steps.rb index efda213d..37b6e213 100644 --- a/conformance/bundles/06-doc-string-mismatch/echo.steps.rb +++ b/conformance/bundles/06-doc-string-mismatch/echo.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do sensor("I echo the following:") { |_state, _doc| "goodbye" } diff --git a/conformance/bundles/06-doc-string-mismatch/echo.steps.rs b/conformance/bundles/06-doc-string-mismatch/echo.steps.rs index 277186ca..dad12bbe 100644 --- a/conformance/bundles/06-doc-string-mismatch/echo.steps.rs +++ b/conformance/bundles/06-doc-string-mismatch/echo.steps.rs @@ -1,6 +1,6 @@ //! Rust sibling of `echo.steps.ts` (bundle `06-doc-string-mismatch`). -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/06-doc-string-mismatch/echo.steps.ts b/conformance/bundles/06-doc-string-mismatch/echo.steps.ts index 6b7ab1a3..987c6aed 100644 --- a/conformance/bundles/06-doc-string-mismatch/echo.steps.ts +++ b/conformance/bundles/06-doc-string-mismatch/echo.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { sensor } = steps(() => ({})) diff --git a/conformance/bundles/07-row-check-mismatch/ReportSteps.java b/conformance/bundles/07-row-check-mismatch/ReportSteps.java index 69c3ff30..0dd46967 100644 --- a/conformance/bundles/07-row-check-mismatch/ReportSteps.java +++ b/conformance/bundles/07-row-check-mismatch/ReportSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle07; +package dev.varar.conformance.bundle07; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.Map; /** diff --git a/conformance/bundles/07-row-check-mismatch/report.steps.kt b/conformance/bundles/07-row-check-mismatch/report.steps.kt index 987b2047..16f22ee4 100644 --- a/conformance/bundles/07-row-check-mismatch/report.steps.kt +++ b/conformance/bundles/07-row-check-mismatch/report.steps.kt @@ -4,10 +4,10 @@ // (bundle 07-row-check-mismatch): header-bound row step — receives the current // row (Map keyed by header cell) as the trailing argument and returns hardcoded // (wrong) columns, producing a cell mismatch at the trace stage. -package com.oselvar.varkt.conformance.bundle07 +package dev.varar.kotlin.conformance.bundle07 -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor class Ctx diff --git a/conformance/bundles/07-row-check-mismatch/report.steps.py b/conformance/bundles/07-row-check-mismatch/report.steps.py index 82207a80..9a0ff4ef 100644 --- a/conformance/bundles/07-row-check-mismatch/report.steps.py +++ b/conformance/bundles/07-row-check-mismatch/report.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/07-row-check-mismatch/report.steps.rb b/conformance/bundles/07-row-check-mismatch/report.steps.rb index a6f28694..1e0b85ec 100644 --- a/conformance/bundles/07-row-check-mismatch/report.steps.rb +++ b/conformance/bundles/07-row-check-mismatch/report.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do sensor("I report the score and grade") { |_state, _row = nil| { "score" => "99", "grade" => "A" } } diff --git a/conformance/bundles/07-row-check-mismatch/report.steps.rs b/conformance/bundles/07-row-check-mismatch/report.steps.rs index 6e4e884c..8ca36aee 100644 --- a/conformance/bundles/07-row-check-mismatch/report.steps.rs +++ b/conformance/bundles/07-row-check-mismatch/report.steps.rs @@ -1,7 +1,7 @@ //! Rust sibling of `report.steps.ts` (bundle `07-row-check-mismatch`). use std::collections::BTreeMap; -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/07-row-check-mismatch/report.steps.ts b/conformance/bundles/07-row-check-mismatch/report.steps.ts index e7a7fcc6..b86a5eb5 100644 --- a/conformance/bundles/07-row-check-mismatch/report.steps.ts +++ b/conformance/bundles/07-row-check-mismatch/report.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { sensor } = steps(() => ({})) diff --git a/conformance/bundles/08-string-capture/GreetSteps.java b/conformance/bundles/08-string-capture/GreetSteps.java index d1089058..eb3b3d23 100644 --- a/conformance/bundles/08-string-capture/GreetSteps.java +++ b/conformance/bundles/08-string-capture/GreetSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle08; +package dev.varar.conformance.bundle08; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** Java sibling of {@code greet.steps.ts} / {@code greet.steps.py} (bundle {@code 08-string-capture}). */ public final class GreetSteps implements StepDefinitions { diff --git a/conformance/bundles/08-string-capture/greet.steps.kt b/conformance/bundles/08-string-capture/greet.steps.kt index 39850c4d..6f7cbf8a 100644 --- a/conformance/bundles/08-string-capture/greet.steps.kt +++ b/conformance/bundles/08-string-capture/greet.steps.kt @@ -2,10 +2,10 @@ // Kotlin sibling of greet.steps.ts / greet.steps.py / GreetSteps.java (bundle // 08-string-capture). -package com.oselvar.varkt.conformance.bundle08 +package dev.varar.kotlin.conformance.bundle08 -import com.oselvar.varkt.stimulus -import com.oselvar.varkt.steps +import dev.varar.kotlin.stimulus +import dev.varar.kotlin.steps class Ctx diff --git a/conformance/bundles/08-string-capture/greet.steps.py b/conformance/bundles/08-string-capture/greet.steps.py index 56ba68f8..c7d8b3fa 100644 --- a/conformance/bundles/08-string-capture/greet.steps.py +++ b/conformance/bundles/08-string-capture/greet.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/08-string-capture/greet.steps.rb b/conformance/bundles/08-string-capture/greet.steps.rb index 397a8ac8..36e2b525 100644 --- a/conformance/bundles/08-string-capture/greet.steps.rb +++ b/conformance/bundles/08-string-capture/greet.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do stimulus("I greet {string}") { |_state, _s| } diff --git a/conformance/bundles/08-string-capture/greet.steps.rs b/conformance/bundles/08-string-capture/greet.steps.rs index cc67231e..a997246a 100644 --- a/conformance/bundles/08-string-capture/greet.steps.rs +++ b/conformance/bundles/08-string-capture/greet.steps.rs @@ -1,6 +1,6 @@ //! Rust sibling of `greet.steps.ts` (bundle `08-string-capture`). -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/08-string-capture/greet.steps.ts b/conformance/bundles/08-string-capture/greet.steps.ts index e30ee66e..97282c89 100644 --- a/conformance/bundles/08-string-capture/greet.steps.ts +++ b/conformance/bundles/08-string-capture/greet.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus } = steps(() => ({})) diff --git a/conformance/bundles/09-expected-message-mismatch/BoomSteps.java b/conformance/bundles/09-expected-message-mismatch/BoomSteps.java index 02a229dd..1223f5d4 100644 --- a/conformance/bundles/09-expected-message-mismatch/BoomSteps.java +++ b/conformance/bundles/09-expected-message-mismatch/BoomSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle09; +package dev.varar.conformance.bundle09; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** * Java sibling of {@code boom.steps.ts} / {@code boom.steps.py} (bundle {@code diff --git a/conformance/bundles/09-expected-message-mismatch/boom.steps.kt b/conformance/bundles/09-expected-message-mismatch/boom.steps.kt index 4e6b5b92..121abdd7 100644 --- a/conformance/bundles/09-expected-message-mismatch/boom.steps.kt +++ b/conformance/bundles/09-expected-message-mismatch/boom.steps.kt @@ -3,10 +3,10 @@ // Kotlin sibling of boom.steps.ts / boom.steps.py / BoomSteps.java (bundle // 09-expected-message-mismatch): throws a message NOT containing the expected // substring, so the error fence is not satisfied at the trace stage. -package com.oselvar.varkt.conformance.bundle09 +package dev.varar.kotlin.conformance.bundle09 -import com.oselvar.varkt.stimulus -import com.oselvar.varkt.steps +import dev.varar.kotlin.stimulus +import dev.varar.kotlin.steps class Ctx diff --git a/conformance/bundles/09-expected-message-mismatch/boom.steps.py b/conformance/bundles/09-expected-message-mismatch/boom.steps.py index 40c78d9f..8f4a1268 100644 --- a/conformance/bundles/09-expected-message-mismatch/boom.steps.py +++ b/conformance/bundles/09-expected-message-mismatch/boom.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/09-expected-message-mismatch/boom.steps.rb b/conformance/bundles/09-expected-message-mismatch/boom.steps.rb index 4b651fce..8d6d851c 100644 --- a/conformance/bundles/09-expected-message-mismatch/boom.steps.rb +++ b/conformance/bundles/09-expected-message-mismatch/boom.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do stimulus("I always boom") { |_state| raise "actual different error" } diff --git a/conformance/bundles/09-expected-message-mismatch/boom.steps.rs b/conformance/bundles/09-expected-message-mismatch/boom.steps.rs index acd31970..13103416 100644 --- a/conformance/bundles/09-expected-message-mismatch/boom.steps.rs +++ b/conformance/bundles/09-expected-message-mismatch/boom.steps.rs @@ -1,6 +1,6 @@ //! Rust sibling of `boom.steps.ts` (bundle `09-expected-message-mismatch`). -use var::{HandlerError, Registry, Steps, Value}; +use varar::{HandlerError, Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/09-expected-message-mismatch/boom.steps.ts b/conformance/bundles/09-expected-message-mismatch/boom.steps.ts index 05dcb1df..bea73512 100644 --- a/conformance/bundles/09-expected-message-mismatch/boom.steps.ts +++ b/conformance/bundles/09-expected-message-mismatch/boom.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus } = steps(() => ({})) diff --git a/conformance/bundles/10-error-fence-without-step/CukesSteps.java b/conformance/bundles/10-error-fence-without-step/CukesSteps.java index 36188e41..2a2ffc5f 100644 --- a/conformance/bundles/10-error-fence-without-step/CukesSteps.java +++ b/conformance/bundles/10-error-fence-without-step/CukesSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle10; +package dev.varar.conformance.bundle10; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** * Java sibling of {@code cukes.steps.ts} / {@code cukes.steps.py} (bundle {@code diff --git a/conformance/bundles/10-error-fence-without-step/cukes.steps.kt b/conformance/bundles/10-error-fence-without-step/cukes.steps.kt index d6742a59..f2080773 100644 --- a/conformance/bundles/10-error-fence-without-step/cukes.steps.kt +++ b/conformance/bundles/10-error-fence-without-step/cukes.steps.kt @@ -4,10 +4,10 @@ // 10-error-fence-without-step): the example's prose matches no step, so the // error fence has nothing to run — a plan-stage diagnostic; this stage only // needs the one step registered. -package com.oselvar.varkt.conformance.bundle10 +package dev.varar.kotlin.conformance.bundle10 -import com.oselvar.varkt.stimulus -import com.oselvar.varkt.steps +import dev.varar.kotlin.stimulus +import dev.varar.kotlin.steps class Ctx diff --git a/conformance/bundles/10-error-fence-without-step/cukes.steps.py b/conformance/bundles/10-error-fence-without-step/cukes.steps.py index 4ab4ed49..a769f826 100644 --- a/conformance/bundles/10-error-fence-without-step/cukes.steps.py +++ b/conformance/bundles/10-error-fence-without-step/cukes.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/10-error-fence-without-step/cukes.steps.rb b/conformance/bundles/10-error-fence-without-step/cukes.steps.rb index c3e5e7d0..a786c7c9 100644 --- a/conformance/bundles/10-error-fence-without-step/cukes.steps.rb +++ b/conformance/bundles/10-error-fence-without-step/cukes.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do stimulus("I have {int} cukes") { |_state, _n| } diff --git a/conformance/bundles/10-error-fence-without-step/cukes.steps.rs b/conformance/bundles/10-error-fence-without-step/cukes.steps.rs index 482e1ec7..d0c16c36 100644 --- a/conformance/bundles/10-error-fence-without-step/cukes.steps.rs +++ b/conformance/bundles/10-error-fence-without-step/cukes.steps.rs @@ -1,6 +1,6 @@ //! Rust sibling of `cukes.steps.ts` (bundle `10-error-fence-without-step`). -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/10-error-fence-without-step/cukes.steps.ts b/conformance/bundles/10-error-fence-without-step/cukes.steps.ts index 3071d61f..4b77558c 100644 --- a/conformance/bundles/10-error-fence-without-step/cukes.steps.ts +++ b/conformance/bundles/10-error-fence-without-step/cukes.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus } = steps(() => ({})) diff --git a/conformance/bundles/11-emoji-offsets/GreetSteps.java b/conformance/bundles/11-emoji-offsets/GreetSteps.java index 3df2c561..6ffd5ff2 100644 --- a/conformance/bundles/11-emoji-offsets/GreetSteps.java +++ b/conformance/bundles/11-emoji-offsets/GreetSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle11; +package dev.varar.conformance.bundle11; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.List; /** diff --git a/conformance/bundles/11-emoji-offsets/greet.steps.kt b/conformance/bundles/11-emoji-offsets/greet.steps.kt index f9c60d27..6ccb7999 100644 --- a/conformance/bundles/11-emoji-offsets/greet.steps.kt +++ b/conformance/bundles/11-emoji-offsets/greet.steps.kt @@ -4,10 +4,10 @@ // 11-emoji-offsets): the example's non-header-bound trailing table arrives as // the trailing argument after the {string} capture; the null return skips // every comparison (mirrors TS's `() => undefined`). -package com.oselvar.varkt.conformance.bundle11 +package dev.varar.kotlin.conformance.bundle11 -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor class Ctx diff --git a/conformance/bundles/11-emoji-offsets/greet.steps.py b/conformance/bundles/11-emoji-offsets/greet.steps.py index 27c4d194..cd8d1bc1 100644 --- a/conformance/bundles/11-emoji-offsets/greet.steps.py +++ b/conformance/bundles/11-emoji-offsets/greet.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/11-emoji-offsets/greet.steps.rb b/conformance/bundles/11-emoji-offsets/greet.steps.rb index c478e744..4621177a 100644 --- a/conformance/bundles/11-emoji-offsets/greet.steps.rb +++ b/conformance/bundles/11-emoji-offsets/greet.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do sensor("I greet {string}") { |_state, _s, *_extra| nil } diff --git a/conformance/bundles/11-emoji-offsets/greet.steps.rs b/conformance/bundles/11-emoji-offsets/greet.steps.rs index 6d72727a..aafc0465 100644 --- a/conformance/bundles/11-emoji-offsets/greet.steps.rs +++ b/conformance/bundles/11-emoji-offsets/greet.steps.rs @@ -1,6 +1,6 @@ //! Rust sibling of `greet.steps.ts` (bundle `11-emoji-offsets`). -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/11-emoji-offsets/greet.steps.ts b/conformance/bundles/11-emoji-offsets/greet.steps.ts index b958c823..24de86d9 100644 --- a/conformance/bundles/11-emoji-offsets/greet.steps.ts +++ b/conformance/bundles/11-emoji-offsets/greet.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { sensor } = steps>(() => ({})) sensor('I greet {string}', () => undefined) diff --git a/conformance/bundles/12-combining-marks/GreetSteps.java b/conformance/bundles/12-combining-marks/GreetSteps.java index c7da868c..f13eaf2b 100644 --- a/conformance/bundles/12-combining-marks/GreetSteps.java +++ b/conformance/bundles/12-combining-marks/GreetSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle12; +package dev.varar.conformance.bundle12; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** * Java sibling of {@code greet.steps.ts} / {@code greet.steps.py} (bundle {@code diff --git a/conformance/bundles/12-combining-marks/greet.steps.kt b/conformance/bundles/12-combining-marks/greet.steps.kt index 2bb14c94..7ad21148 100644 --- a/conformance/bundles/12-combining-marks/greet.steps.kt +++ b/conformance/bundles/12-combining-marks/greet.steps.kt @@ -2,10 +2,10 @@ // Kotlin sibling of greet.steps.ts / greet.steps.py / GreetSteps.java (bundle // 12-combining-marks). -package com.oselvar.varkt.conformance.bundle12 +package dev.varar.kotlin.conformance.bundle12 -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor class Ctx diff --git a/conformance/bundles/12-combining-marks/greet.steps.py b/conformance/bundles/12-combining-marks/greet.steps.py index 57076494..a246021e 100644 --- a/conformance/bundles/12-combining-marks/greet.steps.py +++ b/conformance/bundles/12-combining-marks/greet.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) diff --git a/conformance/bundles/12-combining-marks/greet.steps.rb b/conformance/bundles/12-combining-marks/greet.steps.rb index cf6d7844..47bab695 100644 --- a/conformance/bundles/12-combining-marks/greet.steps.rb +++ b/conformance/bundles/12-combining-marks/greet.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do sensor("I greet {string}") { |_state, _s| nil } diff --git a/conformance/bundles/12-combining-marks/greet.steps.rs b/conformance/bundles/12-combining-marks/greet.steps.rs index 598d400f..7119718d 100644 --- a/conformance/bundles/12-combining-marks/greet.steps.rs +++ b/conformance/bundles/12-combining-marks/greet.steps.rs @@ -1,6 +1,6 @@ //! Rust sibling of `greet.steps.ts` (bundle `12-combining-marks`). -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/12-combining-marks/greet.steps.ts b/conformance/bundles/12-combining-marks/greet.steps.ts index b958c823..24de86d9 100644 --- a/conformance/bundles/12-combining-marks/greet.steps.ts +++ b/conformance/bundles/12-combining-marks/greet.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { sensor } = steps>(() => ({})) sensor('I greet {string}', () => undefined) diff --git a/conformance/bundles/13-custom-parameter-type/AirportsSteps.java b/conformance/bundles/13-custom-parameter-type/AirportsSteps.java index 4949d5b0..dc8fd3b1 100644 --- a/conformance/bundles/13-custom-parameter-type/AirportsSteps.java +++ b/conformance/bundles/13-custom-parameter-type/AirportsSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle13; +package dev.varar.conformance.bundle13; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.Locale; import java.util.regex.Pattern; diff --git a/conformance/bundles/13-custom-parameter-type/airports.steps.kt b/conformance/bundles/13-custom-parameter-type/airports.steps.kt index a36210e3..e04da952 100644 --- a/conformance/bundles/13-custom-parameter-type/airports.steps.kt +++ b/conformance/bundles/13-custom-parameter-type/airports.steps.kt @@ -6,11 +6,11 @@ // lowercasing is asserted by the sensor (the .md says "lhr"), so an identity // parse fails this bundle. param MUST precede the steps — // expressions compile eagerly. -package com.oselvar.varkt.conformance.bundle13 +package dev.varar.kotlin.conformance.bundle13 -import com.oselvar.varkt.stimulus -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.stimulus +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor data class Ctx(val dest: String? = null) diff --git a/conformance/bundles/13-custom-parameter-type/airports.steps.py b/conformance/bundles/13-custom-parameter-type/airports.steps.py index abfbc32b..93df0302 100644 --- a/conformance/bundles/13-custom-parameter-type/airports.steps.py +++ b/conformance/bundles/13-custom-parameter-type/airports.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps # Custom {airport} parameter type: IATA code, lowercased by the parse function. # The lowercasing is asserted by the sensor (the .md says "lhr"), so an diff --git a/conformance/bundles/13-custom-parameter-type/airports.steps.rb b/conformance/bundles/13-custom-parameter-type/airports.steps.rb index c782d682..ff2a8c05 100644 --- a/conformance/bundles/13-custom-parameter-type/airports.steps.rb +++ b/conformance/bundles/13-custom-parameter-type/airports.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" steps do # Custom {airport} parameter type: IATA code, lowercased by the parse function. diff --git a/conformance/bundles/13-custom-parameter-type/airports.steps.rs b/conformance/bundles/13-custom-parameter-type/airports.steps.rs index e2c19a3a..ffcca6d5 100644 --- a/conformance/bundles/13-custom-parameter-type/airports.steps.rs +++ b/conformance/bundles/13-custom-parameter-type/airports.steps.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::rc::Rc; -use var::{HandlerError, ParseFn, Registry, Steps, Value}; +use varar::{HandlerError, ParseFn, Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/13-custom-parameter-type/airports.steps.ts b/conformance/bundles/13-custom-parameter-type/airports.steps.ts index 37d874eb..c3006052 100644 --- a/conformance/bundles/13-custom-parameter-type/airports.steps.ts +++ b/conformance/bundles/13-custom-parameter-type/airports.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' // Custom {airport} parameter type: IATA code, lowercased by the parse function. // The lowercasing is asserted by the sensor (the .md says "lhr"), so an diff --git a/conformance/bundles/14-stateless-steps/SquaresSteps.java b/conformance/bundles/14-stateless-steps/SquaresSteps.java index f7ec8a1c..b72cd910 100644 --- a/conformance/bundles/14-stateless-steps/SquaresSteps.java +++ b/conformance/bundles/14-stateless-steps/SquaresSteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle14; +package dev.varar.conformance.bundle14; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.List; /** diff --git a/conformance/bundles/14-stateless-steps/squares.steps.kt b/conformance/bundles/14-stateless-steps/squares.steps.kt index 2f7b4327..0e1711b0 100644 --- a/conformance/bundles/14-stateless-steps/squares.steps.kt +++ b/conformance/bundles/14-stateless-steps/squares.steps.kt @@ -3,10 +3,10 @@ // Kotlin sibling of squares.steps.ts / squares.steps.py / SquaresSteps.java // (bundle 14-stateless-steps): no state factory — these steps are pure, so // steps is called without one and handlers run against Unit. -package com.oselvar.varkt.conformance.bundle14 +package dev.varar.kotlin.conformance.bundle14 -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor val steps = steps { stimulus("I warm up my mental math") {} diff --git a/conformance/bundles/14-stateless-steps/squares.steps.py b/conformance/bundles/14-stateless-steps/squares.steps.py index bb84936a..2394a3e0 100644 --- a/conformance/bundles/14-stateless-steps/squares.steps.py +++ b/conformance/bundles/14-stateless-steps/squares.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps # No state factory: these steps are pure, so steps() is called bare # and handlers get an empty dict as state. diff --git a/conformance/bundles/14-stateless-steps/squares.steps.rb b/conformance/bundles/14-stateless-steps/squares.steps.rb index 385c9683..d08aa00b 100644 --- a/conformance/bundles/14-stateless-steps/squares.steps.rb +++ b/conformance/bundles/14-stateless-steps/squares.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" # No initial state: these steps are pure, so steps is called without state and # handlers get an empty hash. diff --git a/conformance/bundles/14-stateless-steps/squares.steps.rs b/conformance/bundles/14-stateless-steps/squares.steps.rs index 3ee209d6..1aee1f58 100644 --- a/conformance/bundles/14-stateless-steps/squares.steps.rs +++ b/conformance/bundles/14-stateless-steps/squares.steps.rs @@ -3,7 +3,7 @@ //! Pure steps — nothing to arrange or evolve — so `state()` is the bare //! [`Value::Null`] every handler ignores. -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/14-stateless-steps/squares.steps.ts b/conformance/bundles/14-stateless-steps/squares.steps.ts index 76e14f7d..a6297deb 100644 --- a/conformance/bundles/14-stateless-steps/squares.steps.ts +++ b/conformance/bundles/14-stateless-steps/squares.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' // No state factory: these steps are pure — nothing to arrange, nothing to // evolve — so steps() is called bare and handlers get an empty state. diff --git a/conformance/bundles/15-custom-parameter-format/MoneySteps.java b/conformance/bundles/15-custom-parameter-format/MoneySteps.java index 38c53c2d..df220ddb 100644 --- a/conformance/bundles/15-custom-parameter-format/MoneySteps.java +++ b/conformance/bundles/15-custom-parameter-format/MoneySteps.java @@ -1,9 +1,9 @@ -package com.oselvar.var.conformance.bundle15; +package dev.varar.conformance.bundle15; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.Locale; import java.util.Map; import java.util.function.Function; diff --git a/conformance/bundles/15-custom-parameter-format/money.steps.kt b/conformance/bundles/15-custom-parameter-format/money.steps.kt index 3da02ecb..5cbbe9f7 100644 --- a/conformance/bundles/15-custom-parameter-format/money.steps.kt +++ b/conformance/bundles/15-custom-parameter-format/money.steps.kt @@ -8,10 +8,10 @@ // parameter mismatches through `format` identically. Without a format this // actual would be each port's native object rendering, which is deliberately // outside conformance. -package com.oselvar.varkt.conformance.bundle15 +package dev.varar.kotlin.conformance.bundle15 -import com.oselvar.varkt.steps -import com.oselvar.varkt.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.sensor import java.util.Locale val steps = steps { diff --git a/conformance/bundles/15-custom-parameter-format/money.steps.py b/conformance/bundles/15-custom-parameter-format/money.steps.py index 4413851e..691c8ae2 100644 --- a/conformance/bundles/15-custom-parameter-format/money.steps.py +++ b/conformance/bundles/15-custom-parameter-format/money.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps # Custom {money} parameter type with a `format` — the inverse of `parse`, # rendering a value back in the document's notation. The sensor returns the diff --git a/conformance/bundles/15-custom-parameter-format/money.steps.rb b/conformance/bundles/15-custom-parameter-format/money.steps.rb index 9ae32478..6189ed5e 100644 --- a/conformance/bundles/15-custom-parameter-format/money.steps.rb +++ b/conformance/bundles/15-custom-parameter-format/money.steps.rb @@ -1,4 +1,4 @@ -require "oselvar/var" +require "varar" # Custom {money} parameter type with a `format` — the inverse of `parse`, # rendering a value back in the document's notation. The sensor returns the diff --git a/conformance/bundles/15-custom-parameter-format/money.steps.rs b/conformance/bundles/15-custom-parameter-format/money.steps.rs index 777013c1..6ae0cdb7 100644 --- a/conformance/bundles/15-custom-parameter-format/money.steps.rs +++ b/conformance/bundles/15-custom-parameter-format/money.steps.rs @@ -4,7 +4,7 @@ //! back in document notation, so the pinned mismatch reads `£2.60` / `£2.55`. use std::rc::Rc; -use var::{FormatFn, ParseFn, Registry, Steps, Value}; +use varar::{FormatFn, ParseFn, Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/conformance/bundles/15-custom-parameter-format/money.steps.ts b/conformance/bundles/15-custom-parameter-format/money.steps.ts index 81626a22..d46da3e0 100644 --- a/conformance/bundles/15-custom-parameter-format/money.steps.ts +++ b/conformance/bundles/15-custom-parameter-format/money.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' // Custom {money} parameter type with a `format` — the inverse of `parse`, // rendering a value back in the document's notation. The sensor diff --git a/conformance/config/README.md b/conformance/config/README.md index 681a0c42..e38502b1 100644 --- a/conformance/config/README.md +++ b/conformance/config/README.md @@ -1,19 +1,19 @@ # Config conformance corpus -Language-neutral fixtures for `var.config.json` readers. Every port's config +Language-neutral fixtures for `varar.config.json` readers. Every port's config package must implement the same harness rule over `cases/`: -- If a case directory contains `expect-error.txt`, loading `var.config.json` +- If a case directory contains `expect-error.txt`, loading `varar.config.json` from that directory MUST fail (any error type; the txt file documents why for humans and is not asserted against). -- Otherwise, load the config (a missing `var.config.json` — see +- Otherwise, load the config (a missing `varar.config.json` — see `no-config-file/` — is legal and yields the empty config), project it to `{ docs: { include, exclude }, steps, snippets, scannerPlugins }` with scanner-plugin NAMES (strings, never resolved functions), serialize with the port's canonical-JSON helper, and compare byte-for-byte against `golden.json`. -`var.config.schema.json` is the machine-readable schema (reference it from a +`varar.config.schema.json` is the machine-readable schema (reference it from a config file via `"$schema"` for editor validation). Readers enforce the same rules in code: unknown keys, wrong types, and malformed JSON fail loudly with the file path and reason; all keys are optional and default to empty. diff --git a/conformance/config/cases/empty-object/var.config.json b/conformance/config/cases/empty-object/varar.config.json similarity index 100% rename from conformance/config/cases/empty-object/var.config.json rename to conformance/config/cases/empty-object/varar.config.json diff --git a/conformance/config/cases/full/var.config.json b/conformance/config/cases/full/varar.config.json similarity index 86% rename from conformance/config/cases/full/var.config.json rename to conformance/config/cases/full/varar.config.json index 29b7c2dd..02415ddd 100644 --- a/conformance/config/cases/full/var.config.json +++ b/conformance/config/cases/full/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../var.config.schema.json", + "$schema": "../../varar.config.schema.json", "docs": { "include": ["specs/**/*.md", "docs/**/*.md"], "exclude": ["specs/wip/**"] }, "steps": ["steps/**/*.steps.ts", "steps/**/*_steps.py"], "snippets": { "typescript": "// {{expression}}", "python": "# {{expression}}" }, diff --git a/conformance/config/cases/invalid-json/var.config.json b/conformance/config/cases/invalid-json/varar.config.json similarity index 100% rename from conformance/config/cases/invalid-json/var.config.json rename to conformance/config/cases/invalid-json/varar.config.json diff --git a/conformance/config/cases/minimal/var.config.json b/conformance/config/cases/minimal/varar.config.json similarity index 100% rename from conformance/config/cases/minimal/var.config.json rename to conformance/config/cases/minimal/varar.config.json diff --git a/conformance/config/cases/null-values/var.config.json b/conformance/config/cases/null-values/varar.config.json similarity index 100% rename from conformance/config/cases/null-values/var.config.json rename to conformance/config/cases/null-values/varar.config.json diff --git a/conformance/config/cases/unknown-key/var.config.json b/conformance/config/cases/unknown-key/varar.config.json similarity index 100% rename from conformance/config/cases/unknown-key/var.config.json rename to conformance/config/cases/unknown-key/varar.config.json diff --git a/conformance/config/cases/wrong-type/var.config.json b/conformance/config/cases/wrong-type/varar.config.json similarity index 100% rename from conformance/config/cases/wrong-type/var.config.json rename to conformance/config/cases/wrong-type/varar.config.json diff --git a/conformance/config/var.config.schema.json b/conformance/config/varar.config.schema.json similarity index 86% rename from conformance/config/var.config.schema.json rename to conformance/config/varar.config.schema.json index 5cf127ee..3cf0d4f6 100644 --- a/conformance/config/var.config.schema.json +++ b/conformance/config/varar.config.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://oselvar.com/var.config.schema.json", - "title": "Var configuration (var.config.json)", + "$id": "https://varar.dev/varar.config.schema.json", + "title": "Var configuration (varar.config.json)", "type": "object", "additionalProperties": false, "properties": { diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md index d146a330..0c463ab5 100644 --- a/doc/ARCHITECTURE.md +++ b/doc/ARCHITECTURE.md @@ -1,6 +1,6 @@ -# Vár architecture (target state) +# Varar architecture (target state) -This describes how Vár **should** be structured to support many languages — not +This describes how Varar **should** be structured to support many languages — not how it is laid out today. It is the destination for the prefactoring work that precedes the first non-TypeScript language (Python). @@ -31,7 +31,7 @@ obtain the step-definition registry, and what they do with the result. | Step files are | **parsed** (tree-sitter), never executed | **executed** in their native runtime | | Handlers are | absent (matching only) | real callables | | Produces | a workspace index → matches + diagnostics | an `ExecutionPlan` → test-runner items | -| Powers | LSP, VSCode, `lint` | the vitest/pytest adapters, `var run` | +| Powers | LSP, VSCode, `lint` | the vitest/pytest adapters, `varar run` | | Side effects | none | importing user modules | ```mermaid diff --git a/doc/RELEASING.md b/doc/RELEASING.md index 4c1a161b..7820a01a 100644 --- a/doc/RELEASING.md +++ b/doc/RELEASING.md @@ -47,7 +47,7 @@ deploy comes **last**: parked; see below.) 4. **Maven Central** — slow (GPG-signed, atomic multi-module deploy); runs unattended at the end. -5. **var-examples** — a quick git sync of `examples/` to the `oselvar/var-examples` +5. **varar-examples** — a quick git sync of `examples/` to the `oselvar/varar-examples` repo, pinned to the just-published versions. Idempotent: if a publish fails, fix the cause and re-run `make release` — it @@ -69,7 +69,7 @@ VS Code Marketplace — **npm, PyPI, Maven Central and Open VSX publish**. ## Credentials -All secrets live in the 1Password vault **`Vár`** (account `my.1password.com`), +All secrets live in the 1Password vault **`Varar`** (account `my.1password.com`), injected via `op run` from the references in `release/release.env` (the vault is referenced by ID because `op://` URIs reject non-ASCII names). One-time setup is complete — these notes are for rotating a token or rebuilding a @@ -90,10 +90,10 @@ and `npm install -g @vscode/vsce ovsx`. Sign in: `op signin`, `gh auth login`. wait (hours) and re-run; published packages are skipped. - **Sonatype Central Portal (Maven Central)** — user token from central.sonatype.com (Account → Generate User Token); namespace - `com.oselvar` is DNS-verified (2026-07-04) on that account. If it ever needs + `dev.varar` is DNS-verified (2026-07-04) on that account. If it ever needs re-verifying, the portal issues a fresh code to publish as a TXT record on oselvar.com — a deploy from an account without the verified namespace fails - with "Namespace 'com.oselvar' is not allowed". → `sonatype-central`, fields + with "Namespace 'dev.varar' is not allowed". → `sonatype-central`, fields `username` and `password` (both halves of the generated user token). - **GPG** — ed25519 signing key for Oselvar Ltd, public key on keyserver.ubuntu.com. → `maven-gpg`, field `passphrase`, with an armored diff --git a/doc/RENAME-VARAR.md b/doc/RENAME-VARAR.md new file mode 100644 index 00000000..ac890354 --- /dev/null +++ b/doc/RENAME-VARAR.md @@ -0,0 +1,283 @@ +# Rename: `oselvar`/`var` → `varar` + +Migration plan for renaming the project from **Vár / `@oselvar/var`** to +**Varar**. Status: planned 2026-07-18. Branch: `rename-to-varar`. + +## Why + +- More unique, searchable name (`var` is un-Googleable; `varar` is not). +- A domain was available and registered: **varar.dev**. +- Free namespaces in package managers: the **`@varar`** npm org is owned; other + registries have `varar*` free. + +The mascot — the Norse goddess **Vár** (accented, guardian of oaths) — **stays**. +The rename actually *strengthens* the brand: **_varar_ = "oaths" in Old Norse**, +so "Vár guards your _varar_" is a tighter story than before. The mythology copy +on the website should be reframed to lean into this, not deleted. + +## The three naming axes + +The codebase tangles three separable tokens. All three change except the mascot: + +| Axis | Today | Becomes | +|------|-------|---------| +| Org / namespace | `oselvar` (npm `@oselvar`, Maven `com.oselvar`, gem/PyPI `oselvar-` prefix, VS Code publisher `oselvar`) | `varar` (npm `@varar`, Maven `dev.varar`, gem/PyPI/crate `varar` base) | +| Product / base name | `var` (CLI `var`, `var.config.json`, facade `@oselvar/var`, import roots `var_core` / `Oselvar::Var` / `com.oselvar.var` / crate `var`) | `varar` | +| Mascot / brand prose | `Vár` (goddess of oaths) | **unchanged** | + +## Decisions (locked via interview) + +1. **Full rename**: `var` → `varar` everywhere it stands as its own token. +2. **Drop the redundant prefix inside scoped/reverse-DNS namespaces** + (Cucumber-style): `@varar/varar` facade + `@varar/core`; `dev.varar:varar` + + `dev.varar:core`. Unscoped ecosystems keep a prefix: `varar`, `varar-core`. +3. **Docs at the apex**: `https://varar.dev` (replaces `var.oselvar.com`). +4. **Rename GitHub repos** under the (retained) `oselvar` org: + `oselvar/var` → `oselvar/varar`, `oselvar/var-examples` → `oselvar/varar-examples`. + (The `varar` GitHub org is squatted; revisit later.) +5. **Kotlin package leaf** `com.oselvar.varkt` → `dev.varar.kotlin`. +6. **Keep** `aslak@oselvar.com` (mailbox, not brand) and **keep** `Oselvar Ltd` + in LICENSE (legal copyright holder). +7. **Deprecate** old published coordinates pointing at the new `@varar` names; + treat `varar` as a fresh 0.x line. +8. Executed on a **branch/PR**, not straight to trunk. + +## Target naming — full coordinate table + +### npm (`@varar` scope, prefix dropped; bins keep the product name) + +| Dir | Old name | New name | Notes | +|-----|----------|----------|-------| +| `packages/var` | `@oselvar/var` | `@varar/varar` | facade; subpath `./registry` | +| `packages/var-core` | `@oselvar/var-core` | `@varar/core` | | +| `packages/var-config` | `@oselvar/var-config` | `@varar/config` | | +| `packages/var-language` | `@oselvar/var-language` | `@varar/language` | | +| `packages/var-runner` | `@oselvar/var-runner` | `@varar/runner` | | +| `packages/var-vitest` | `@oselvar/var-vitest` | `@varar/vitest` | subpaths `./runtime`, `./reporter` | +| `packages/var-cli` | `@oselvar/var-cli` | `@varar/cli` | **bin `varar`** | +| `packages/var-lsp` | `@oselvar/var-lsp` | `@varar/lsp` | **bin `varar-lsp`**, subpath `./protocol` | +| `packages/var-vscode` | `oselvar-var` | `varar` | publisher `varar`, id `varar.varar` | +| `packages/cucumber` | `@oselvar/cucumber` | `@varar/cucumber` | private | +| `packages/website` | `@oselvar/website` | `@varar/website` | private | +| (root) | `oselvar-var` | `varar-monorepo` | private root | +| `examples/typescript-vitest` | `@oselvar/example-typescript-vitest` | `@varar/example-typescript-vitest` | private | + +Directory renames are **optional** (name field is decoupled from the dir), but +recommended for hygiene: `packages/var-core` → `packages/core`, etc., and +`packages/var` → `packages/varar`. Deferred to a follow-up if it complicates the +diff — the plan below keeps directory names and only changes the `name` fields to +minimize churn, then renames dirs as an optional final phase. + +### PyPI + +| Dir | Old dist | New dist | Old import pkg | New import pkg | +|-----|----------|----------|----------------|----------------| +| `packages/var` | `oselvar-var` | `varar` | `var` | `varar` | +| `packages/var-core` | `oselvar-var-core` | `varar-core` | `var_core` | `varar_core` | +| `packages/var-config` | `oselvar-var-config` | `varar-config` | `var_config` | `varar_config` | +| `packages/var-runner` | `oselvar-var-runner` | `varar-runner` | `var_runner` | `varar_runner` | +| `packages/var-unittest` | `oselvar-var-unittest` | `varar-unittest` | `var_unittest` | `varar_unittest` | +| `packages/var-pytest` | `pytest-var` | `pytest-varar` | `var_pytest` | `varar_pytest` | + +Import-package dirs under `src/` are renamed; `[tool.uv.sources]` and the pinned +inter-package deps (`oselvar-var==x` → `varar==x`) update in lockstep. + +### Maven (`dev.varar` group — domain-verified via varar.dev) + +| Module | Old artifact | New artifact | Old package | New package | +|--------|--------------|--------------|-------------|-------------| +| parent | `com.oselvar:var-parent` | `dev.varar:parent` | — | — | +| var | `com.oselvar:var` | `dev.varar:varar` | `com.oselvar.var` | `dev.varar` | +| var-core | `com.oselvar:var-core` | `dev.varar:core` | `com.oselvar.var.core` | `dev.varar.core` | +| var-config | `com.oselvar:var-config` | `dev.varar:config` | `com.oselvar.var.config` | `dev.varar.config` | +| var-runner | `com.oselvar:var-runner` | `dev.varar:runner` | `com.oselvar.var.runner` | `dev.varar.runner` | +| var-junit | `com.oselvar:var-junit` | `dev.varar:junit` | `com.oselvar.var.junit` | `dev.varar.junit` | +| var-kotlin | `com.oselvar:var-kotlin` | `dev.varar:kotlin` | `com.oselvar.varkt` | `dev.varar.kotlin` | +| var-kotest | `com.oselvar:var-kotest` | `dev.varar:kotest` | `com.oselvar.varkt.kotest` | `dev.varar.kotest` | + +Source trees move `src/main/java/com/oselvar/…` → `…/dev/varar/…` (and +`src/main/kotlin/com/oselvar/varkt/…` → `…/dev/varar/kotlin/…`), plus the mirrored +`src/test/…`. Fixtures sub-packages (`…runner.fixtures`, `…junit.fixtures`, +`…kotest.fixtures`, `…crosspkg`) follow their parent. + +### RubyGems + +| Dir | Old gem | New gem | Old load path | New load path | +|-----|---------|---------|---------------|---------------| +| `packages/var` | `oselvar-var` | `varar` | `oselvar/var` | `varar` | +| `packages/var-core` | `oselvar-var-core` | `varar-core` | `oselvar/var/core` | `varar/core` | +| `packages/var-config` | `oselvar-var-config` | `varar-config` | `oselvar/var/config` | `varar/config` | +| `packages/var-runner` | `oselvar-var-runner` | `varar-runner` | `oselvar/var/runner` | `varar/runner` | +| `packages/var-rspec` | `oselvar-var-rspec` | `varar-rspec` | `oselvar/var/rspec` | `varar/rspec` | +| `packages/var-minitest` | `oselvar-var-minitest` | `varar-minitest` | `oselvar/var/minitest` | `varar/minitest` | + +Module nesting `Oselvar::Var::{Core,Config,Runner,Internal,RSpec,Minitest,RegistryGlue}` +collapses to top-level `Varar::{…}`. Files move `lib/oselvar/var/**` → `lib/varar/**`; +gemspec files renamed `oselvar-var-*.gemspec` → `varar-*.gemspec`; every `require` +updates. Homepage `var.oselvar.com` → `varar.dev`. + +### Rust (all `publish = false` today — no live crates.io names yet) + +| Dir | Old crate | New crate | Old lib | New lib | +|-----|-----------|-----------|---------|---------| +| `rust/var` | `var` | `varar` | `var` | `varar` | +| `rust/var-core` | `var-core` | `varar-core` | `var_core` | `varar_core` | +| `rust/var-config` | `var-config` | `varar-config` | `var_config` | `varar_config` | +| `rust/var-runner` | `var-runner` | `varar-runner` | `var_runner` | `varar_runner` | +| `rust/var-cargotest` | `var-cargotest` | `varar-cargotest` | `var_cargotest` | `varar_cargotest` | + +crates.io publish (`65-crates-io.sh`) currently parked because `var` was taken; +the planned name `oselvar-var` becomes **`varar`** (verify free). Enabling +publish is out of scope for this rename — just correct the intended names. + +### CLI / product tokens + +- CLI command `var` → `varar` (`varar run|lint|init|help`); all hardcoded help/ + error text (`'var — markdown-native BDD'`, `` `var: unknown command` ``). +- `var.config.json` → `varar.config.json` (runner, LSP, vitest plugin, CLI, + website, root, every `examples/*`, conformance; VS Code activation event + `workspaceContains:**/varar.config.json`). +- `var.lock.json` → `varar.lock.json`. +- `var.config.schema.json` → `varar.config.schema.json`; `$id` + `https://oselvar.com/var.config.schema.json` → `https://varar.dev/varar.config.schema.json`. +- Website localStorage keys / custom elements (`var-lang`, `var-palette`, + `var-palette-select`, …) → `varar-*` (cosmetic, product-scoped). + +### Subpath export specifiers (update at every import site) + +- `@oselvar/var/registry` → `@varar/varar/registry` +- `@oselvar/var-vitest/runtime` → `@varar/vitest/runtime` +- `@oselvar/var-vitest/reporter` → `@varar/vitest/reporter` +- `@oselvar/var-lsp/protocol` → `@varar/lsp/protocol` +- vitest plugin `name`, `resolve.dedupe` list, and the **generated virtual-module + source string** it emits. +- `var-cli init` scaffolds `import { steps } from '@oselvar/var'` → `'@varar/varar'` + (a user-visible generated string). + +### Domain / URL / GitHub + +- `var.oselvar.com` → `varar.dev` (astro `site`, wrangler `route` + worker `name` + `var-website` → `varar-website`, all READMEs, gemspec homepages). +- `github.com/oselvar/var` → `github.com/oselvar/varar`; `oselvar/var-examples` → + `oselvar/varar-examples` (badges, pom ``/``, Starlight `editLink`, + ADR issue links, `70-var-examples.sh`). +- Schema `$id` host + website URLs → `varar.dev`. + +### VS Code / Open VSX + +- Publisher `oselvar` → `varar`; extension `oselvar-var` → `varar`; + displayName **`Vár` → `Varar`** (no accent — the accented `Vár` is reserved + for the goddess references in the docs only); commands + `oselvar-var.generateStepDefinition` → + `varar.generateStepDefinition`; LanguageClient id `oselvar-var` → `varar`. +- Marketplace id `oselvar.oselvar-var` → `varar.varar`; Open VSX `oselvar/oselvar-var` + → `varar/varar`. Dev install script (`scripts/install-vscode.mjs`) name/UUIDs. + +## Manual prerequisites (owner action — must land before publishing) + +These are **external** and block *publishing*, not the code rename: + +- [ ] npm: `@varar` org exists (✅ owned); ensure automation token has publish rights. +- [ ] PyPI: reserve/first-publish `varar`, `varar-core`, `varar-config`, + `varar-runner`, `varar-unittest`, `pytest-varar`. +- [ ] RubyGems: confirm `varar`, `varar-core`, `varar-config`, `varar-runner`, + `varar-rspec`, `varar-minitest` free; push owner. +- [ ] crates.io: confirm `varar`, `varar-core`, … free (publishing stays parked). +- [ ] Maven Central: register namespace **`dev.varar`** on the Central Portal; + verify via a DNS TXT record on **varar.dev**. +- [ ] VS Code Marketplace: create publisher **`varar`** (VSCE_PAT). +- [ ] Open VSX: create namespace **`varar`** (OVSX_PAT). +- [ ] Cloudflare: add **varar.dev** zone, wire the Worker custom-domain route. +- [ ] GitHub: rename `oselvar/var` → `oselvar/varar` and `oselvar/var-examples` + → `oselvar/varar-examples` (301 redirects preserve old URLs). +- [ ] 1Password (vault `Vár`): new `@varar` npm token, PyPI token; update the + `op://` refs in `release/release.env` if item names change. + +## Progress — COMPLETE (8 commits on `rename-to-varar`) + +All phases done, every port's gate re-run green (807 files changed): + +- **Coordinates**: npm `@varar/*` · PyPI `varar*` · Maven `dev.varar:*` · + RubyGems `varar*` · Rust `varar*` crates. TS build/check/test, Python + pytest, `mvn install` + all JVM samples, `rake` + Ruby samples, and `cargo` + all green. +- **Product tokens**: `varar.config.json`, `varar.lock.json`, + `varar.config.schema.json` (`$id` → varar.dev); CLI command **`varar`** + (bin `varar`, LSP bin `varar-lsp`); scaffold folder `varar-examples/`. +- **Tooling/branding**: release scripts, `languages.json`, Makefile, CI, + badges retargeted; `70-varar-examples.sh` sync; front page + README reframed + around *varar = oaths* (goddess **Vár** kept). +- Commit-lint (`release/lint-commits.sh`) passes over the branch. + +### Intentionally NOT changed +- **GitHub org** `oselvar/varar` (varar org is squatted); **email** + `aslak@oselvar.com`; **LICENSE** `Oselvar Ltd`; **1Password vault** `Vár`. +- ~~Package directory names~~ — **also renamed** (drop the `var-` prefix: + `packages/core`, `packages/config`, …, facade `packages/varar`); published + names/coordinates are unchanged, only the on-disk dirs moved. `make check` + green after the move. +- **Internal code identifiers** (`VarConfig`, `loadVarConfig`, `parseVarLock`, + the website `var-lang`/`var-palette` localStorage keys) — not user-facing + coordinates; left to avoid churn/risk. +- **Test data** `Vár`/`vár` in specs, step files, and conformance goldens + (golden-compared — not branding). +- The dated **`doc/superpowers` + `doc/adr` archive** and `CHANGELOG.md` — + historical records. + +### Still owner-only (external — blocks *publishing*, see prerequisites above) +Registry namespace reservations, Maven `dev.varar` DNS verification, VS Code / +Open VSX publishers, Cloudflare varar.dev, the two GitHub repo renames, and the +new npm/PyPI tokens in 1Password. + +## Execution phases (on `rename-to-varar`) + +Ordered so each port stays independently build-green. `make ` gates each. + +1. **TypeScript** — `packages/*` `name` + deps + bins + subpaths + imports + + plugin/virtual-module strings + `knip.json`; scaffolding string in + `var-cli init`; website config (site/route/editLink/worker); root + `package.json` + Makefile filters. Gate: `pnpm -r build && pnpm check && pnpm test`. +2. **Python** — dist names, import-pkg dir renames, `uv.sources`, pinned deps, + source refs. Gate: `make python`. +3. **Java** — groupId, artifactIds, `package` decls + source-tree moves (Java & + Kotlin leaf), poms ``/``, fixtures. Gate: `make java`. +4. **Ruby** — gem names, gemspec files, `lib/oselvar/var` → `lib/varar` moves, + `require`s, `Oselvar::Var` → `Varar` module collapse, homepages. Gate: `make ruby`. +5. **Rust** — crate + lib names, workspace members, doc-comment refs. Gate: + `cargo build && cargo test` (via `make` / `rust.yml` commands). +6. **Product tokens** — `var.config.json`/`var.lock.json`/`var.config.schema.json` + renames + every reference (root, conformance, examples, `languages.json` + `stepsGlob`), CLI command/help strings, VS Code activation + commands. +7. **Conformance corpus** — per-language step-file import/package coordinates; + schema `$id`; goldens unaffected (byte-for-byte identical output — verify). +8. **Examples** — per-project deps/imports/READMEs; `70-var-examples.sh` target + repo, DEST, dep-pin regex, commit message; examples READ ME + CI badges. +9. **Release tooling** — `release/lib.sh`, `stamp_python.py`, `targets/20`,`40`, + `50`,`60`,`65`,`70`; `release.env` op refs. +10. **Docs & brand** — README, website `index.mdx` + `oaths.md` (reframe the + Vár↔varar/oaths etymology), `cliff.toml` seed, CLAUDE.md, ADRs (issue links), + ANNOUNCEMENT/CONTRIBUTING/IDEA/TODO. +11. **Deprecations** — script/checklist to `npm deprecate` old `@oselvar/*`, + deprecate old gems/PyPI dists pointing at `@varar` equivalents. +12. **(Optional) directory renames** — `packages/var-*` → `packages/*` etc., last, + as a mechanical follow-up once names are green. + +Full root gate at the end: `make check` (builds+tests all ports; runs +`release/lint-commits.sh`). Website: `pnpm --filter @varar/website build`. + +## Watch-outs + +- **Conformance goldens must stay byte-for-byte identical** — the rename must not + alter any runtime output the goldens capture. If a golden references a package + name in an error/snippet, that's a real change to review. +- **Ruby load path is load-bearing**: `oselvar/var/**` under `lib/` is required by + path across every gem; the directory move and every `require` must move together. +- **Maven `dev.varar` needs DNS verification** on varar.dev before Central accepts + the namespace — do this early; it can take time to propagate. +- **`var` is a keyword-ish token**: mechanical `s/var/varar/` is unsafe (it will + hit the JS/Java `var` keyword, `variable` words, etc.). Rename by *coordinate* + (package names, import paths, config filenames), never blanket text. +- **Mascot spelling `Vár`** (accented) must survive — don't fold it into `varar`. +- Commit messages must follow Conventional Commits; scope these as the ports they + touch (mostly `chore`/`refactor`/`docs` — a rename ships no consumer feature, + though the *first* publish under `@varar` is itself the release event). diff --git a/examples/.github/workflows/java-junit-gradle.yml b/examples/.github/workflows/java-junit-gradle.yml index 3b5a4e9d..d9be6c3f 100644 --- a/examples/.github/workflows/java-junit-gradle.yml +++ b/examples/.github/workflows/java-junit-gradle.yml @@ -1,4 +1,4 @@ -# Runs in the oselvar/var-examples repo, where examples/ is synced on every +# Runs in the oselvar/varar-examples repo, where examples/ is synced on every # release of oselvar/var. In the monorepo this file is inert (GitHub only # reads workflows from the repo root); the monorepo runs the projects against # the local build via .github/workflows/*.yml instead. diff --git a/examples/.github/workflows/java-junit-maven.yml b/examples/.github/workflows/java-junit-maven.yml index 54d63fe8..8266a802 100644 --- a/examples/.github/workflows/java-junit-maven.yml +++ b/examples/.github/workflows/java-junit-maven.yml @@ -1,4 +1,4 @@ -# Runs in the oselvar/var-examples repo, where examples/ is synced on every +# Runs in the oselvar/varar-examples repo, where examples/ is synced on every # release of oselvar/var. In the monorepo this file is inert (GitHub only # reads workflows from the repo root); the monorepo runs the projects against # the local build via .github/workflows/*.yml instead. diff --git a/examples/.github/workflows/kotlin-junit.yml b/examples/.github/workflows/kotlin-junit.yml index e05ec7b2..d39e9d93 100644 --- a/examples/.github/workflows/kotlin-junit.yml +++ b/examples/.github/workflows/kotlin-junit.yml @@ -1,4 +1,4 @@ -# Runs in the oselvar/var-examples repo, where examples/ is synced on every +# Runs in the oselvar/varar-examples repo, where examples/ is synced on every # release of oselvar/var. In the monorepo this file is inert (GitHub only # reads workflows from the repo root); the monorepo runs the projects against # the local build via .github/workflows/*.yml instead. diff --git a/examples/.github/workflows/kotlin-kotest.yml b/examples/.github/workflows/kotlin-kotest.yml index 2a0ad620..b2fdd041 100644 --- a/examples/.github/workflows/kotlin-kotest.yml +++ b/examples/.github/workflows/kotlin-kotest.yml @@ -1,4 +1,4 @@ -# Runs in the oselvar/var-examples repo, where examples/ is synced on every +# Runs in the oselvar/varar-examples repo, where examples/ is synced on every # release of oselvar/var. In the monorepo this file is inert (GitHub only # reads workflows from the repo root); the monorepo runs the projects against # the local build via .github/workflows/*.yml instead. diff --git a/examples/.github/workflows/python-pytest.yml b/examples/.github/workflows/python-pytest.yml index 70e5d4d0..3caabe32 100644 --- a/examples/.github/workflows/python-pytest.yml +++ b/examples/.github/workflows/python-pytest.yml @@ -1,4 +1,4 @@ -# Runs in the oselvar/var-examples repo, where examples/ is synced on every +# Runs in the oselvar/varar-examples repo, where examples/ is synced on every # release of oselvar/var. In the monorepo this file is inert (GitHub only # reads workflows from the repo root); the monorepo runs the projects against # the local build via .github/workflows/*.yml instead. diff --git a/examples/.github/workflows/python-unittest.yml b/examples/.github/workflows/python-unittest.yml index e18f4436..cc96c71c 100644 --- a/examples/.github/workflows/python-unittest.yml +++ b/examples/.github/workflows/python-unittest.yml @@ -1,4 +1,4 @@ -# Runs in the oselvar/var-examples repo, where examples/ is synced on every +# Runs in the oselvar/varar-examples repo, where examples/ is synced on every # release of oselvar/var. In the monorepo this file is inert (GitHub only # reads workflows from the repo root); the monorepo runs the projects against # the local build via .github/workflows/*.yml instead. diff --git a/examples/.github/workflows/typescript-vitest.yml b/examples/.github/workflows/typescript-vitest.yml index ab64ac9f..0938f930 100644 --- a/examples/.github/workflows/typescript-vitest.yml +++ b/examples/.github/workflows/typescript-vitest.yml @@ -1,4 +1,4 @@ -# Runs in the oselvar/var-examples repo, where examples/ is synced on every +# Runs in the oselvar/varar-examples repo, where examples/ is synced on every # release of oselvar/var. In the monorepo this file is inert (GitHub only # reads workflows from the repo root); the monorepo runs the projects against # the local build via .github/workflows/*.yml instead. diff --git a/examples/README.md b/examples/README.md index 1ad9fc28..ebe1794e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,7 +1,7 @@ -# Vár examples +# Varar examples Small, standalone sample projects that run Markdown specs as tests with -[Vár](https://var.oselvar.com) — one project per language/test-framework +[Varar](https://varar.dev) — one project per language/test-framework combination. Each is a complete project you can copy as the starting point for your own. @@ -11,16 +11,16 @@ whole team, and checked against the code on every test run. | Project | Stack | Run with | CI | | --- | --- | --- | --- | -| [`typescript-vitest`](typescript-vitest) | TypeScript + vitest | `pnpm test` | [![typescript-vitest](https://github.com/oselvar/var-examples/actions/workflows/typescript-vitest.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/typescript-vitest.yml) | -| [`kotlin-junit`](kotlin-junit) | Kotlin + JUnit + Gradle | `./gradlew test` | [![kotlin-junit](https://github.com/oselvar/var-examples/actions/workflows/kotlin-junit.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/kotlin-junit.yml) | -| [`kotlin-kotest`](kotlin-kotest) | Kotlin + Kotest + Gradle | `./gradlew test` | [![kotlin-kotest](https://github.com/oselvar/var-examples/actions/workflows/kotlin-kotest.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/kotlin-kotest.yml) | -| [`java-junit-maven`](java-junit-maven) | Java + JUnit + Maven | `mvn test` | [![java-junit-maven](https://github.com/oselvar/var-examples/actions/workflows/java-junit-maven.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/java-junit-maven.yml) | -| [`java-junit-gradle`](java-junit-gradle) | Java + JUnit + Gradle | `./gradlew test` | [![java-junit-gradle](https://github.com/oselvar/var-examples/actions/workflows/java-junit-gradle.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/java-junit-gradle.yml) | -| [`python-pytest`](python-pytest) | Python + pytest | `uv run pytest` | [![python-pytest](https://github.com/oselvar/var-examples/actions/workflows/python-pytest.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/python-pytest.yml) | -| [`python-unittest`](python-unittest) | Python + unittest | `uv run python -m unittest` | [![python-unittest](https://github.com/oselvar/var-examples/actions/workflows/python-unittest.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/python-unittest.yml) | -| [`ruby-rspec`](ruby-rspec) | Ruby + RSpec | `bundle exec rspec` | [![ruby-rspec](https://github.com/oselvar/var-examples/actions/workflows/ruby-rspec.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/ruby-rspec.yml) | -| [`ruby-minitest`](ruby-minitest) | Ruby + Minitest | `bundle exec rake test` | [![ruby-minitest](https://github.com/oselvar/var-examples/actions/workflows/ruby-minitest.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/ruby-minitest.yml) | -| [`rust-cargotest`](rust-cargotest) | Rust + cargo test | `cargo test` | [![rust-cargotest](https://github.com/oselvar/var-examples/actions/workflows/rust-cargotest.yml/badge.svg)](https://github.com/oselvar/var-examples/actions/workflows/rust-cargotest.yml) | +| [`typescript-vitest`](typescript-vitest) | TypeScript + vitest | `pnpm test` | [![typescript-vitest](https://github.com/oselvar/vararar-examples/actions/workflows/typescript-vitest.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/typescript-vitest.yml) | +| [`kotlin-junit`](kotlin-junit) | Kotlin + JUnit + Gradle | `./gradlew test` | [![kotlin-junit](https://github.com/oselvar/vararar-examples/actions/workflows/kotlin-junit.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/kotlin-junit.yml) | +| [`kotlin-kotest`](kotlin-kotest) | Kotlin + Kotest + Gradle | `./gradlew test` | [![kotlin-kotest](https://github.com/oselvar/vararar-examples/actions/workflows/kotlin-kotest.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/kotlin-kotest.yml) | +| [`java-junit-maven`](java-junit-maven) | Java + JUnit + Maven | `mvn test` | [![java-junit-maven](https://github.com/oselvar/vararar-examples/actions/workflows/java-junit-maven.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/java-junit-maven.yml) | +| [`java-junit-gradle`](java-junit-gradle) | Java + JUnit + Gradle | `./gradlew test` | [![java-junit-gradle](https://github.com/oselvar/vararar-examples/actions/workflows/java-junit-gradle.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/java-junit-gradle.yml) | +| [`python-pytest`](python-pytest) | Python + pytest | `uv run pytest` | [![python-pytest](https://github.com/oselvar/vararar-examples/actions/workflows/python-pytest.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/python-pytest.yml) | +| [`python-unittest`](python-unittest) | Python + unittest | `uv run python -m unittest` | [![python-unittest](https://github.com/oselvar/vararar-examples/actions/workflows/python-unittest.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/python-unittest.yml) | +| [`ruby-rspec`](ruby-rspec) | Ruby + RSpec | `bundle exec rspec` | [![ruby-rspec](https://github.com/oselvar/vararar-examples/actions/workflows/ruby-rspec.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/ruby-rspec.yml) | +| [`ruby-minitest`](ruby-minitest) | Ruby + Minitest | `bundle exec rake test` | [![ruby-minitest](https://github.com/oselvar/vararar-examples/actions/workflows/ruby-minitest.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/ruby-minitest.yml) | +| [`rust-cargotest`](rust-cargotest) | Rust + cargo test | `cargo test` | [![rust-cargotest](https://github.com/oselvar/vararar-examples/actions/workflows/rust-cargotest.yml/badge.svg)](https://github.com/oselvar/vararar-examples/actions/workflows/rust-cargotest.yml) | `typescript-vitest` implements the full example set; the other projects implement a feature-covering subset — `hello-var` (basic steps), @@ -32,10 +32,10 @@ emphasised title where the markup *is* the parameter). ## Where these files live -The source of truth is the [`oselvar/var`](https://github.com/oselvar/var) +The source of truth is the [`oselvar/var`](https://github.com/oselvar/varar) monorepo's `examples/` directory, where the projects run against the local build on every push (in there, the subset projects' `.md` files are symlinks to the `typescript-vitest` originals). On every release they are synced — symlinks resolved, versions pinned to the release — to -[`oselvar/var-examples`](https://github.com/oselvar/var-examples). Send +[`oselvar/varar-examples`](https://github.com/oselvar/vararar-examples). Send changes to `oselvar/var`. diff --git a/examples/java-junit-gradle/README.md b/examples/java-junit-gradle/README.md index ad6ca455..5e7c3df2 100644 --- a/examples/java-junit-gradle/README.md +++ b/examples/java-junit-gradle/README.md @@ -1,7 +1,7 @@ # Vár sample: Java + JUnit + Gradle A small, standalone sample project that runs Markdown specs as tests with -[Vár](https://var.oselvar.com), using the Java author API and the JUnit +[Vár](https://varar.dev), using the Java author API and the JUnit Platform engine (`var-junit`). Copy it as the starting point for your own project. @@ -17,7 +17,7 @@ Each example in the Markdown specs becomes one JUnit test. ## How it fits together -- **`var.config.json`** is the single source of truth: `docs.include` globs +- **`varar.config.json`** is the single source of truth: `docs.include` globs the Markdown specs, and `steps` lists the fully-qualified step-definition classes. - **`src/test/java/examples/*Steps.java`** implement `StepDefinitions`: a @@ -32,5 +32,5 @@ Each example in the Markdown specs becomes one JUnit test. In the `oselvar/var` monorepo `varVersion` is the SNAPSHOT that `mvn install` (run from `java/`) puts into the local Maven repository, so the sample gates -trunk; in `oselvar/var-examples` the release sync pins it to the released +trunk; in `oselvar/varar-examples` the release sync pins it to the released Maven Central artifacts. diff --git a/examples/java-junit-gradle/build.gradle.kts b/examples/java-junit-gradle/build.gradle.kts index cde4ec1c..bc877961 100644 --- a/examples/java-junit-gradle/build.gradle.kts +++ b/examples/java-junit-gradle/build.gradle.kts @@ -13,7 +13,7 @@ repositories { } dependencies { - testImplementation("com.oselvar:var-junit:$varVersion") + testImplementation("dev.varar:junit:$varVersion") testImplementation(platform("org.junit:junit-bom:6.1.1")) // Gradle only discovers class-based tests, so the sample uses a JUnit // @Suite (see RunVarSpecsTest) to hand the spec corpus to the "var" engine. diff --git a/examples/java-junit-gradle/settings.gradle.kts b/examples/java-junit-gradle/settings.gradle.kts index 1f2de144..f1445b41 100644 --- a/examples/java-junit-gradle/settings.gradle.kts +++ b/examples/java-junit-gradle/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "var-examples-java-junit-gradle" +rootProject.name = "varar-examples-java-junit-gradle" diff --git a/examples/java-junit-gradle/src/test/java/examples/DeepThoughtSteps.java b/examples/java-junit-gradle/src/test/java/examples/DeepThoughtSteps.java index 7535ffe5..55d810b9 100644 --- a/examples/java-junit-gradle/src/test/java/examples/DeepThoughtSteps.java +++ b/examples/java-junit-gradle/src/test/java/examples/DeepThoughtSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; public final class DeepThoughtSteps implements StepDefinitions { diff --git a/examples/java-junit-gradle/src/test/java/examples/HelloVarSteps.java b/examples/java-junit-gradle/src/test/java/examples/HelloVarSteps.java index 09670bf0..c6e3822c 100644 --- a/examples/java-junit-gradle/src/test/java/examples/HelloVarSteps.java +++ b/examples/java-junit-gradle/src/test/java/examples/HelloVarSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; public final class HelloVarSteps implements StepDefinitions { diff --git a/examples/java-junit-gradle/src/test/java/examples/LibrarySteps.java b/examples/java-junit-gradle/src/test/java/examples/LibrarySteps.java index 41c3744f..a2a725df 100644 --- a/examples/java-junit-gradle/src/test/java/examples/LibrarySteps.java +++ b/examples/java-junit-gradle/src/test/java/examples/LibrarySteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.List; diff --git a/examples/java-junit-gradle/src/test/java/examples/RomanNumeralsSteps.java b/examples/java-junit-gradle/src/test/java/examples/RomanNumeralsSteps.java index ad126df6..977f04f8 100644 --- a/examples/java-junit-gradle/src/test/java/examples/RomanNumeralsSteps.java +++ b/examples/java-junit-gradle/src/test/java/examples/RomanNumeralsSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.Map; public final class RomanNumeralsSteps implements StepDefinitions { diff --git a/examples/java-junit-gradle/src/test/java/examples/RunVarSpecsTest.java b/examples/java-junit-gradle/src/test/java/examples/RunVarSpecsTest.java index 5aac29a1..5ef34036 100644 --- a/examples/java-junit-gradle/src/test/java/examples/RunVarSpecsTest.java +++ b/examples/java-junit-gradle/src/test/java/examples/RunVarSpecsTest.java @@ -7,7 +7,7 @@ /** * Maven Surefire and Gradle only discover class-based tests, so this {@code @Suite} is the * bridge that asks the JUnit Platform to run the {@code "var"} engine over the spec corpus. - * var.config.json (in this project's root, the test working directory) decides which .md + * varar.config.json (in this project's root, the test working directory) decides which .md * files are specs and which classes define the steps. The {@code *Test} suffix matters: * Surefire only scans classes matching its naming convention. */ diff --git a/examples/java-junit-gradle/src/test/java/examples/TablesAndDocStringsSteps.java b/examples/java-junit-gradle/src/test/java/examples/TablesAndDocStringsSteps.java index 75e14034..b5b329c6 100644 --- a/examples/java-junit-gradle/src/test/java/examples/TablesAndDocStringsSteps.java +++ b/examples/java-junit-gradle/src/test/java/examples/TablesAndDocStringsSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.ArrayList; import java.util.List; import java.util.Locale; diff --git a/examples/java-junit-gradle/src/test/java/examples/YahtzeeSteps.java b/examples/java-junit-gradle/src/test/java/examples/YahtzeeSteps.java index bcebc712..29611612 100644 --- a/examples/java-junit-gradle/src/test/java/examples/YahtzeeSteps.java +++ b/examples/java-junit-gradle/src/test/java/examples/YahtzeeSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.Arrays; import java.util.List; import java.util.Map; diff --git a/examples/kotlin-kotest/var.config.json b/examples/java-junit-gradle/varar.config.json similarity index 82% rename from examples/kotlin-kotest/var.config.json rename to examples/java-junit-gradle/varar.config.json index c681849d..8e7f7d50 100644 --- a/examples/kotlin-kotest/var.config.json +++ b/examples/java-junit-gradle/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": [ "*.md" diff --git a/examples/java-junit-gradle/var.lock.json b/examples/java-junit-gradle/varar.lock.json similarity index 100% rename from examples/java-junit-gradle/var.lock.json rename to examples/java-junit-gradle/varar.lock.json diff --git a/examples/java-junit-maven/README.md b/examples/java-junit-maven/README.md index 540dd7c0..fd2bdf6e 100644 --- a/examples/java-junit-maven/README.md +++ b/examples/java-junit-maven/README.md @@ -1,7 +1,7 @@ # Vár sample: Java + JUnit + Maven A small, standalone sample project that runs Markdown specs as tests with -[Vár](https://var.oselvar.com), using the Java author API and the JUnit +[Vár](https://varar.dev), using the Java author API and the JUnit Platform engine (`var-junit`). Copy it as the starting point for your own project. @@ -17,7 +17,7 @@ Each example in the Markdown specs becomes one JUnit test. ## How it fits together -- **`var.config.json`** is the single source of truth: `docs.include` globs +- **`varar.config.json`** is the single source of truth: `docs.include` globs the Markdown specs, and `steps` lists the fully-qualified step-definition classes. - **`src/test/java/examples/*Steps.java`** implement `StepDefinitions`: a @@ -34,5 +34,5 @@ Each example in the Markdown specs becomes one JUnit test. In the `oselvar/var` monorepo `` is the SNAPSHOT that `mvn install` (run from `java/`) puts into the local Maven repository, so the -sample gates trunk; in `oselvar/var-examples` the release sync pins it to the +sample gates trunk; in `oselvar/varar-examples` the release sync pins it to the released Maven Central artifacts. diff --git a/examples/java-junit-maven/pom.xml b/examples/java-junit-maven/pom.xml index 3930fc5e..78098ef3 100644 --- a/examples/java-junit-maven/pom.xml +++ b/examples/java-junit-maven/pom.xml @@ -5,10 +5,10 @@ 4.0.0 com.example - var-examples-java-junit-maven + varar-examples-java-junit-maven 0.0.1-SNAPSHOT jar @@ -23,8 +23,8 @@ - com.oselvar - var-junit + dev.varar + junit ${var.version} test diff --git a/examples/java-junit-maven/src/test/java/examples/DeepThoughtSteps.java b/examples/java-junit-maven/src/test/java/examples/DeepThoughtSteps.java index 7535ffe5..55d810b9 100644 --- a/examples/java-junit-maven/src/test/java/examples/DeepThoughtSteps.java +++ b/examples/java-junit-maven/src/test/java/examples/DeepThoughtSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; public final class DeepThoughtSteps implements StepDefinitions { diff --git a/examples/java-junit-maven/src/test/java/examples/HelloVarSteps.java b/examples/java-junit-maven/src/test/java/examples/HelloVarSteps.java index 09670bf0..c6e3822c 100644 --- a/examples/java-junit-maven/src/test/java/examples/HelloVarSteps.java +++ b/examples/java-junit-maven/src/test/java/examples/HelloVarSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; public final class HelloVarSteps implements StepDefinitions { diff --git a/examples/java-junit-maven/src/test/java/examples/LibrarySteps.java b/examples/java-junit-maven/src/test/java/examples/LibrarySteps.java index 41c3744f..a2a725df 100644 --- a/examples/java-junit-maven/src/test/java/examples/LibrarySteps.java +++ b/examples/java-junit-maven/src/test/java/examples/LibrarySteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.List; diff --git a/examples/java-junit-maven/src/test/java/examples/RomanNumeralsSteps.java b/examples/java-junit-maven/src/test/java/examples/RomanNumeralsSteps.java index ad126df6..977f04f8 100644 --- a/examples/java-junit-maven/src/test/java/examples/RomanNumeralsSteps.java +++ b/examples/java-junit-maven/src/test/java/examples/RomanNumeralsSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.Map; public final class RomanNumeralsSteps implements StepDefinitions { diff --git a/examples/java-junit-maven/src/test/java/examples/RunVarSpecsTest.java b/examples/java-junit-maven/src/test/java/examples/RunVarSpecsTest.java index 5aac29a1..5ef34036 100644 --- a/examples/java-junit-maven/src/test/java/examples/RunVarSpecsTest.java +++ b/examples/java-junit-maven/src/test/java/examples/RunVarSpecsTest.java @@ -7,7 +7,7 @@ /** * Maven Surefire and Gradle only discover class-based tests, so this {@code @Suite} is the * bridge that asks the JUnit Platform to run the {@code "var"} engine over the spec corpus. - * var.config.json (in this project's root, the test working directory) decides which .md + * varar.config.json (in this project's root, the test working directory) decides which .md * files are specs and which classes define the steps. The {@code *Test} suffix matters: * Surefire only scans classes matching its naming convention. */ diff --git a/examples/java-junit-maven/src/test/java/examples/TablesAndDocStringsSteps.java b/examples/java-junit-maven/src/test/java/examples/TablesAndDocStringsSteps.java index 75e14034..b5b329c6 100644 --- a/examples/java-junit-maven/src/test/java/examples/TablesAndDocStringsSteps.java +++ b/examples/java-junit-maven/src/test/java/examples/TablesAndDocStringsSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.ArrayList; import java.util.List; import java.util.Locale; diff --git a/examples/java-junit-maven/src/test/java/examples/YahtzeeSteps.java b/examples/java-junit-maven/src/test/java/examples/YahtzeeSteps.java index bcebc712..29611612 100644 --- a/examples/java-junit-maven/src/test/java/examples/YahtzeeSteps.java +++ b/examples/java-junit-maven/src/test/java/examples/YahtzeeSteps.java @@ -1,9 +1,9 @@ package examples; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; import java.util.Arrays; import java.util.List; import java.util.Map; diff --git a/examples/kotlin-junit/var.config.json b/examples/java-junit-maven/varar.config.json similarity index 82% rename from examples/kotlin-junit/var.config.json rename to examples/java-junit-maven/varar.config.json index c681849d..8e7f7d50 100644 --- a/examples/kotlin-junit/var.config.json +++ b/examples/java-junit-maven/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": [ "*.md" diff --git a/examples/java-junit-maven/var.lock.json b/examples/java-junit-maven/varar.lock.json similarity index 100% rename from examples/java-junit-maven/var.lock.json rename to examples/java-junit-maven/varar.lock.json diff --git a/examples/kotlin-junit/README.md b/examples/kotlin-junit/README.md index 761c9512..57e2714f 100644 --- a/examples/kotlin-junit/README.md +++ b/examples/kotlin-junit/README.md @@ -1,7 +1,7 @@ # Vár sample: Kotlin + JUnit + Gradle A small, standalone sample project that runs Markdown specs as tests with -[Vár](https://var.oselvar.com), using the Kotlin DSL (`var-kotlin`) and the +[Vár](https://varar.dev), using the Kotlin DSL (`var-kotlin`) and the JUnit Platform engine (`var-junit`). Copy it as the starting point for your own project. @@ -17,7 +17,7 @@ Each example in the Markdown specs becomes one JUnit test. ## How it fits together -- **`var.config.json`** is the single source of truth: `docs.include` globs +- **`varar.config.json`** is the single source of truth: `docs.include` globs the Markdown specs, and `steps` lists the fully-qualified step-definition classes. For a Kotlin file with a top-level `val steps = steps(...)`, that's the file-facade class pinned by `@file:JvmName(...)`. @@ -34,5 +34,5 @@ Each example in the Markdown specs becomes one JUnit test. In the `oselvar/var` monorepo `varVersion` is the SNAPSHOT that `mvn install` (run from `java/`) puts into the local Maven repository, so the sample gates -trunk; in `oselvar/var-examples` the release sync pins it to the released +trunk; in `oselvar/varar-examples` the release sync pins it to the released Maven Central artifacts. diff --git a/examples/kotlin-junit/build.gradle.kts b/examples/kotlin-junit/build.gradle.kts index 6510cf62..534f148f 100644 --- a/examples/kotlin-junit/build.gradle.kts +++ b/examples/kotlin-junit/build.gradle.kts @@ -13,8 +13,8 @@ repositories { } dependencies { - testImplementation("com.oselvar:var-kotlin:$varVersion") - testImplementation("com.oselvar:var-junit:$varVersion") + testImplementation("dev.varar:kotlin:$varVersion") + testImplementation("dev.varar:junit:$varVersion") testImplementation(platform("org.junit:junit-bom:6.1.1")) testImplementation("org.junit.platform:junit-platform-suite") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/examples/kotlin-junit/settings.gradle.kts b/examples/kotlin-junit/settings.gradle.kts index c77c78fa..829275cc 100644 --- a/examples/kotlin-junit/settings.gradle.kts +++ b/examples/kotlin-junit/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "var-examples-kotlin-junit" +rootProject.name = "varar-examples-kotlin-junit" diff --git a/examples/kotlin-junit/src/test/kotlin/examples/RunVarSpecsTest.kt b/examples/kotlin-junit/src/test/kotlin/examples/RunVarSpecsTest.kt index 4dbc62a2..a13474f8 100644 --- a/examples/kotlin-junit/src/test/kotlin/examples/RunVarSpecsTest.kt +++ b/examples/kotlin-junit/src/test/kotlin/examples/RunVarSpecsTest.kt @@ -6,7 +6,7 @@ import org.junit.platform.suite.api.Suite // Maven Surefire and Gradle only discover class-based tests, so this @Suite is // the bridge that asks the JUnit Platform to run the "var" engine over the spec -// corpus (the .md files in this project). var.config.json decides which .md +// corpus (the .md files in this project). varar.config.json decides which .md // files are specs and which classes define the steps. The // *Test suffix matters under Maven: Surefire only scans classes matching its // naming convention. diff --git a/examples/kotlin-junit/src/test/kotlin/examples/deep-thought.steps.kt b/examples/kotlin-junit/src/test/kotlin/examples/deep-thought.steps.kt index 49fcd251..e0da71e8 100644 --- a/examples/kotlin-junit/src/test/kotlin/examples/deep-thought.steps.kt +++ b/examples/kotlin-junit/src/test/kotlin/examples/deep-thought.steps.kt @@ -2,8 +2,8 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps val deepThoughtSteps = steps { sensor("life, the universe and everything is {int}") { _: Int -> 42 } diff --git a/examples/kotlin-junit/src/test/kotlin/examples/hello-var.steps.kt b/examples/kotlin-junit/src/test/kotlin/examples/hello-var.steps.kt index 86f6cb34..d7a20334 100644 --- a/examples/kotlin-junit/src/test/kotlin/examples/hello-var.steps.kt +++ b/examples/kotlin-junit/src/test/kotlin/examples/hello-var.steps.kt @@ -2,9 +2,9 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps -import com.oselvar.varkt.stimulus +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.stimulus data class HelloCtx(val greeting: String = "", val result: Int = 0) diff --git a/examples/kotlin-junit/src/test/kotlin/examples/library.steps.kt b/examples/kotlin-junit/src/test/kotlin/examples/library.steps.kt index 860f3446..cc180a8a 100644 --- a/examples/kotlin-junit/src/test/kotlin/examples/library.steps.kt +++ b/examples/kotlin-junit/src/test/kotlin/examples/library.steps.kt @@ -2,9 +2,9 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps -import com.oselvar.varkt.stimulus +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.stimulus import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.Locale diff --git a/examples/kotlin-junit/src/test/kotlin/examples/roman-numerals.steps.kt b/examples/kotlin-junit/src/test/kotlin/examples/roman-numerals.steps.kt index 2a3e565a..98cef6ad 100644 --- a/examples/kotlin-junit/src/test/kotlin/examples/roman-numerals.steps.kt +++ b/examples/kotlin-junit/src/test/kotlin/examples/roman-numerals.steps.kt @@ -2,8 +2,8 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps val romanNumeralsSteps = steps { sensor("a decimal and a roman number") { row: Map -> diff --git a/examples/kotlin-junit/src/test/kotlin/examples/tables-and-docstrings.steps.kt b/examples/kotlin-junit/src/test/kotlin/examples/tables-and-docstrings.steps.kt index 46abcd43..64e2a4ab 100644 --- a/examples/kotlin-junit/src/test/kotlin/examples/tables-and-docstrings.steps.kt +++ b/examples/kotlin-junit/src/test/kotlin/examples/tables-and-docstrings.steps.kt @@ -2,8 +2,8 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps val tablesAndDocStringsSteps = steps { // Whole-table mode: the table arrives as List> (header row diff --git a/examples/kotlin-junit/src/test/kotlin/examples/yahtzee.steps.kt b/examples/kotlin-junit/src/test/kotlin/examples/yahtzee.steps.kt index de2dd0b2..4a5405da 100644 --- a/examples/kotlin-junit/src/test/kotlin/examples/yahtzee.steps.kt +++ b/examples/kotlin-junit/src/test/kotlin/examples/yahtzee.steps.kt @@ -2,8 +2,8 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps val yahtzeeSteps = steps { // Header-bound table: the paragraph names every header cell (dice, diff --git a/examples/java-junit-gradle/var.config.json b/examples/kotlin-junit/varar.config.json similarity index 82% rename from examples/java-junit-gradle/var.config.json rename to examples/kotlin-junit/varar.config.json index c681849d..8e7f7d50 100644 --- a/examples/java-junit-gradle/var.config.json +++ b/examples/kotlin-junit/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": [ "*.md" diff --git a/examples/kotlin-junit/var.lock.json b/examples/kotlin-junit/varar.lock.json similarity index 100% rename from examples/kotlin-junit/var.lock.json rename to examples/kotlin-junit/varar.lock.json diff --git a/examples/kotlin-kotest/README.md b/examples/kotlin-kotest/README.md index 90f44beb..53a42579 100644 --- a/examples/kotlin-kotest/README.md +++ b/examples/kotlin-kotest/README.md @@ -1,7 +1,7 @@ # Vár sample: Kotlin + Kotest + Gradle A small, standalone sample project that runs Markdown specs as tests with -[Vár](https://var.oselvar.com), using the Kotlin DSL (`var-kotlin`) and the +[Vár](https://varar.dev), using the Kotlin DSL (`var-kotlin`) and the Kotest adapter (`var-kotest`). Copy it as the starting point for your own project. @@ -17,7 +17,7 @@ Each example in the Markdown specs becomes one Kotest test. ## How it fits together -- **`var.config.json`** is the single source of truth: `docs.include` globs +- **`varar.config.json`** is the single source of truth: `docs.include` globs the Markdown specs, and `steps` lists the fully-qualified step-definition classes. For a Kotlin file with a top-level `val steps = steps(...)`, that's the file-facade class pinned by `@file:JvmName(...)`. @@ -26,7 +26,7 @@ Each example in the Markdown specs becomes one Kotest test. stimulus returns the next state (`copy(...)`), a sensor returns a value for Vár to compare against what the Markdown says. - **`ExamplesSpec.kt`** extends `VarSpec`, a Kotest `FunSpec` that loads - `var.config.json` (from the test working directory by default) and registers + `varar.config.json` (from the test working directory by default) and registers one test per planned example. Because it's an ordinary Kotest spec class, no discovery workarounds are needed. @@ -34,5 +34,5 @@ Each example in the Markdown specs becomes one Kotest test. In the `oselvar/var` monorepo `varVersion` is the SNAPSHOT that `mvn install` (run from `java/`) puts into the local Maven repository, so the sample gates -trunk; in `oselvar/var-examples` the release sync pins it to the released +trunk; in `oselvar/varar-examples` the release sync pins it to the released Maven Central artifacts. diff --git a/examples/kotlin-kotest/build.gradle.kts b/examples/kotlin-kotest/build.gradle.kts index f1321fc1..ddd9e8d8 100644 --- a/examples/kotlin-kotest/build.gradle.kts +++ b/examples/kotlin-kotest/build.gradle.kts @@ -13,9 +13,9 @@ repositories { } dependencies { - testImplementation("com.oselvar:var-kotlin:$varVersion") + testImplementation("dev.varar:kotlin:$varVersion") // Brings the Kotest JUnit Platform runner transitively (VarSpec extends FunSpec). - testImplementation("com.oselvar:var-kotest:$varVersion") + testImplementation("dev.varar:kotest:$varVersion") testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.1.1") } diff --git a/examples/kotlin-kotest/settings.gradle.kts b/examples/kotlin-kotest/settings.gradle.kts index 0962ecdc..c8784041 100644 --- a/examples/kotlin-kotest/settings.gradle.kts +++ b/examples/kotlin-kotest/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "var-examples-kotlin-kotest" +rootProject.name = "varar-examples-kotlin-kotest" diff --git a/examples/kotlin-kotest/src/test/kotlin/examples/ExamplesSpec.kt b/examples/kotlin-kotest/src/test/kotlin/examples/ExamplesSpec.kt index db01989b..ea57824f 100644 --- a/examples/kotlin-kotest/src/test/kotlin/examples/ExamplesSpec.kt +++ b/examples/kotlin-kotest/src/test/kotlin/examples/ExamplesSpec.kt @@ -1,8 +1,8 @@ package examples -import com.oselvar.varkt.kotest.VarSpec +import dev.varar.kotest.VarSpec -// VarSpec is a Kotest FunSpec: it loads var.config.json from the given root +// VarSpec is a Kotest FunSpec: it loads varar.config.json from the given root // (default: the test working directory), plans every matching Markdown spec, // and registers one Kotest test per example. Being a plain class, it needs no // discovery workarounds — Gradle finds it like any other Kotest spec. diff --git a/examples/kotlin-kotest/src/test/kotlin/examples/deep-thought.steps.kt b/examples/kotlin-kotest/src/test/kotlin/examples/deep-thought.steps.kt index 49fcd251..e0da71e8 100644 --- a/examples/kotlin-kotest/src/test/kotlin/examples/deep-thought.steps.kt +++ b/examples/kotlin-kotest/src/test/kotlin/examples/deep-thought.steps.kt @@ -2,8 +2,8 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps val deepThoughtSteps = steps { sensor("life, the universe and everything is {int}") { _: Int -> 42 } diff --git a/examples/kotlin-kotest/src/test/kotlin/examples/hello-var.steps.kt b/examples/kotlin-kotest/src/test/kotlin/examples/hello-var.steps.kt index 86f6cb34..d7a20334 100644 --- a/examples/kotlin-kotest/src/test/kotlin/examples/hello-var.steps.kt +++ b/examples/kotlin-kotest/src/test/kotlin/examples/hello-var.steps.kt @@ -2,9 +2,9 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps -import com.oselvar.varkt.stimulus +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.stimulus data class HelloCtx(val greeting: String = "", val result: Int = 0) diff --git a/examples/kotlin-kotest/src/test/kotlin/examples/library.steps.kt b/examples/kotlin-kotest/src/test/kotlin/examples/library.steps.kt index 860f3446..cc180a8a 100644 --- a/examples/kotlin-kotest/src/test/kotlin/examples/library.steps.kt +++ b/examples/kotlin-kotest/src/test/kotlin/examples/library.steps.kt @@ -2,9 +2,9 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps -import com.oselvar.varkt.stimulus +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.stimulus import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.Locale diff --git a/examples/kotlin-kotest/src/test/kotlin/examples/roman-numerals.steps.kt b/examples/kotlin-kotest/src/test/kotlin/examples/roman-numerals.steps.kt index 2a3e565a..98cef6ad 100644 --- a/examples/kotlin-kotest/src/test/kotlin/examples/roman-numerals.steps.kt +++ b/examples/kotlin-kotest/src/test/kotlin/examples/roman-numerals.steps.kt @@ -2,8 +2,8 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps val romanNumeralsSteps = steps { sensor("a decimal and a roman number") { row: Map -> diff --git a/examples/kotlin-kotest/src/test/kotlin/examples/tables-and-docstrings.steps.kt b/examples/kotlin-kotest/src/test/kotlin/examples/tables-and-docstrings.steps.kt index 46abcd43..64e2a4ab 100644 --- a/examples/kotlin-kotest/src/test/kotlin/examples/tables-and-docstrings.steps.kt +++ b/examples/kotlin-kotest/src/test/kotlin/examples/tables-and-docstrings.steps.kt @@ -2,8 +2,8 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps val tablesAndDocStringsSteps = steps { // Whole-table mode: the table arrives as List> (header row diff --git a/examples/kotlin-kotest/src/test/kotlin/examples/yahtzee.steps.kt b/examples/kotlin-kotest/src/test/kotlin/examples/yahtzee.steps.kt index de2dd0b2..4a5405da 100644 --- a/examples/kotlin-kotest/src/test/kotlin/examples/yahtzee.steps.kt +++ b/examples/kotlin-kotest/src/test/kotlin/examples/yahtzee.steps.kt @@ -2,8 +2,8 @@ package examples -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps val yahtzeeSteps = steps { // Header-bound table: the paragraph names every header cell (dice, diff --git a/examples/java-junit-maven/var.config.json b/examples/kotlin-kotest/varar.config.json similarity index 82% rename from examples/java-junit-maven/var.config.json rename to examples/kotlin-kotest/varar.config.json index c681849d..8e7f7d50 100644 --- a/examples/java-junit-maven/var.config.json +++ b/examples/kotlin-kotest/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": [ "*.md" diff --git a/examples/kotlin-kotest/var.lock.json b/examples/kotlin-kotest/varar.lock.json similarity index 100% rename from examples/kotlin-kotest/var.lock.json rename to examples/kotlin-kotest/varar.lock.json diff --git a/examples/python-pytest/.gitignore b/examples/python-pytest/.gitignore index c29438bb..34a05d08 100644 --- a/examples/python-pytest/.gitignore +++ b/examples/python-pytest/.gitignore @@ -2,6 +2,6 @@ __pycache__/ .pytest_cache/ # Artifact of `uv run` here: the lock pins path/git sources that differ -# between the oselvar/var monorepo and the var-examples repo. In your own +# between the oselvar/varar monorepo and the varar-examples repo. In your own # project, DO commit your lockfile. uv.lock diff --git a/examples/python-pytest/README.md b/examples/python-pytest/README.md index c06fef23..99184d75 100644 --- a/examples/python-pytest/README.md +++ b/examples/python-pytest/README.md @@ -1,7 +1,7 @@ # Vár sample: Python + pytest A small, standalone sample project that runs Markdown specs as tests with -[Vár](https://var.oselvar.com), using the `pytest-var` plugin. Copy it as the +[Vár](https://varar.dev), using the `pytest-varar` plugin. Copy it as the starting point for your own project. The `.md` files at the project root are the specs — they run as tests. @@ -13,12 +13,12 @@ uv run pytest ``` Each example in the Markdown specs becomes one pytest test. No conftest.py -and no test files are needed — installing `pytest-var` is the entire +and no test files are needed — installing `pytest-varar` is the entire integration. ## How it fits together -- **`var.config.json`** is the single source of truth: `docs.include` globs +- **`varar.config.json`** is the single source of truth: `docs.include` globs the Markdown specs and `steps` globs the step-definition files. - **`steps/*.steps.py`** define the steps with `steps` + `@stimulus`/`@sensor`. A stimulus returns the next state, a sensor returns @@ -28,9 +28,9 @@ integration. ## Versioning note -In the [oselvar/var](https://github.com/oselvar/var) monorepo this sample +In the [oselvar/varar](https://github.com/oselvar/varar) monorepo this sample resolves the Vár packages from `[tool.uv.sources]` path sources, gating trunk against the local build. The release sync to -[oselvar/var-examples](https://github.com/oselvar/var-examples) deletes +[oselvar/varar-examples](https://github.com/oselvar/varar-examples) deletes that table and pins the released PyPI version — there, the plain -`pytest-var` dependency is all a real project needs. +`pytest-varar` dependency is all a real project needs. diff --git a/examples/python-pytest/pyproject.toml b/examples/python-pytest/pyproject.toml index 2112673e..55295999 100644 --- a/examples/python-pytest/pyproject.toml +++ b/examples/python-pytest/pyproject.toml @@ -7,20 +7,20 @@ name = "var-example-python-pytest" version = "0.0.1" description = "Standalone sample: run Markdown specs as pytest tests with Vár" requires-python = ">=3.12" -dependencies = ["pytest>=8", "pytest-var"] +dependencies = ["pytest>=8", "pytest-varar"] # In this monorepo the Vár packages resolve from source (editable path # sources), so the sample gates trunk against the local build. The release -# sync to oselvar/var-examples deletes this whole table and pins the released +# sync to oselvar/varar-examples deletes this whole table and pins the released # PyPI version — the plain dependency above is all a real project needs. The -# transitive set is listed because pytest-var pins its internal dependencies +# transitive set is listed because pytest-varar pins its internal dependencies # exactly (==). [tool.uv.sources] -pytest-var = { path = "../../python/packages/var-pytest", editable = true } -oselvar-var = { path = "../../python/packages/var", editable = true } -oselvar-var-core = { path = "../../python/packages/var-core", editable = true } -oselvar-var-config = { path = "../../python/packages/var-config", editable = true } -oselvar-var-runner = { path = "../../python/packages/var-runner", editable = true } +pytest-varar = { path = "../../python/packages/pytest", editable = true } +varar = { path = "../../python/packages/varar", editable = true } +varar-core = { path = "../../python/packages/core", editable = true } +varar-config = { path = "../../python/packages/config", editable = true } +varar-runner = { path = "../../python/packages/runner", editable = true } [tool.hatch.build.targets.wheel] packages = ["src/library_example", "src/roman_numerals_example", "src/yahtzee_example"] diff --git a/examples/python-pytest/steps/deep_thought.steps.py b/examples/python-pytest/steps/deep_thought.steps.py index c2314c2b..e1bee0c8 100644 --- a/examples/python-pytest/steps/deep_thought.steps.py +++ b/examples/python-pytest/steps/deep_thought.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps() diff --git a/examples/python-pytest/steps/hello_var.steps.py b/examples/python-pytest/steps/hello_var.steps.py index 8d83b453..19dfc8f5 100644 --- a/examples/python-pytest/steps/hello_var.steps.py +++ b/examples/python-pytest/steps/hello_var.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"greeting": "", "result": 0}) diff --git a/examples/python-pytest/steps/library.steps.py b/examples/python-pytest/steps/library.steps.py index 6e6dbabd..801140ec 100644 --- a/examples/python-pytest/steps/library.steps.py +++ b/examples/python-pytest/steps/library.steps.py @@ -1,7 +1,7 @@ from datetime import datetime from library_example import FEE_PER_DAY, add_money, gbp, late_fee, may_borrow -from var import steps +from varar import steps def to_date(raw): diff --git a/examples/python-pytest/steps/roman_numerals.steps.py b/examples/python-pytest/steps/roman_numerals.steps.py index 50ab41e1..6077ad5b 100644 --- a/examples/python-pytest/steps/roman_numerals.steps.py +++ b/examples/python-pytest/steps/roman_numerals.steps.py @@ -1,5 +1,5 @@ from roman_numerals_example import to_roman -from var import steps +from varar import steps param, stimulus, sensor = steps() diff --git a/examples/python-pytest/steps/tables_and_docstrings.steps.py b/examples/python-pytest/steps/tables_and_docstrings.steps.py index 9adf1303..ec444d66 100644 --- a/examples/python-pytest/steps/tables_and_docstrings.steps.py +++ b/examples/python-pytest/steps/tables_and_docstrings.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps() diff --git a/examples/python-pytest/steps/yahtzee.steps.py b/examples/python-pytest/steps/yahtzee.steps.py index d16bde7c..ef9fd0f6 100644 --- a/examples/python-pytest/steps/yahtzee.steps.py +++ b/examples/python-pytest/steps/yahtzee.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps from yahtzee_example import score param, stimulus, sensor = steps() diff --git a/examples/python-pytest/var.config.json b/examples/python-pytest/varar.config.json similarity index 67% rename from examples/python-pytest/var.config.json rename to examples/python-pytest/varar.config.json index 5525feca..8ca611cd 100644 --- a/examples/python-pytest/var.config.json +++ b/examples/python-pytest/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": [ "*.md" diff --git a/examples/python-pytest/var.lock.json b/examples/python-pytest/varar.lock.json similarity index 100% rename from examples/python-pytest/var.lock.json rename to examples/python-pytest/varar.lock.json diff --git a/examples/python-unittest/.gitignore b/examples/python-unittest/.gitignore index f85be887..41984351 100644 --- a/examples/python-unittest/.gitignore +++ b/examples/python-unittest/.gitignore @@ -1,6 +1,6 @@ .venv/ __pycache__/ # Artifact of `uv run` here: the lock pins path/git sources that differ -# between the oselvar/var monorepo and the var-examples repo. In your own +# between the oselvar/varar monorepo and the varar-examples repo. In your own # project, DO commit your lockfile. uv.lock diff --git a/examples/python-unittest/README.md b/examples/python-unittest/README.md index 3311ecc6..e93650b5 100644 --- a/examples/python-unittest/README.md +++ b/examples/python-unittest/README.md @@ -1,7 +1,7 @@ # Vár sample: Python + unittest A small, standalone sample project that runs Markdown specs as tests with -[Vár](https://var.oselvar.com), using the `oselvar-var-unittest` adapter — +[Vár](https://varar.dev), using the `varar-unittest` adapter — nothing but the standard library's test runner. Copy it as the starting point for your own project. @@ -20,7 +20,7 @@ that generates one `TestCase` per spec, which plain `python -m unittest` ## How it fits together -- **`var.config.json`** is the single source of truth: `docs.include` globs +- **`varar.config.json`** is the single source of truth: `docs.include` globs the Markdown specs and `steps` globs the step-definition files. - **`steps/*.steps.py`** define the steps with `steps` + `@stimulus`/`@sensor`. A stimulus returns the next state, a sensor returns @@ -30,9 +30,9 @@ that generates one `TestCase` per spec, which plain `python -m unittest` ## Versioning note -In the [oselvar/var](https://github.com/oselvar/var) monorepo this sample +In the [oselvar/varar](https://github.com/oselvar/varar) monorepo this sample resolves the Vár packages from `[tool.uv.sources]` path sources, gating trunk against the local build. The release sync to -[oselvar/var-examples](https://github.com/oselvar/var-examples) deletes +[oselvar/varar-examples](https://github.com/oselvar/varar-examples) deletes that table and pins the released PyPI version — there, the plain -`oselvar-var-unittest` dependency is all a real project needs. +`varar-unittest` dependency is all a real project needs. diff --git a/examples/python-unittest/pyproject.toml b/examples/python-unittest/pyproject.toml index 92445c4f..5c9ff22c 100644 --- a/examples/python-unittest/pyproject.toml +++ b/examples/python-unittest/pyproject.toml @@ -7,20 +7,20 @@ name = "var-example-python-unittest" version = "0.0.1" description = "Standalone sample: run Markdown specs as unittest tests with Vár" requires-python = ">=3.12" -dependencies = ["oselvar-var-unittest"] +dependencies = ["varar-unittest"] # In this monorepo the Vár packages resolve from source (editable path # sources), so the sample gates trunk against the local build. The release -# sync to oselvar/var-examples deletes this whole table and pins the released +# sync to oselvar/varar-examples deletes this whole table and pins the released # PyPI version — the plain dependency above is all a real project needs. The -# transitive set is listed because oselvar-var-unittest pins its internal +# transitive set is listed because varar-unittest pins its internal # dependencies exactly (==). [tool.uv.sources] -oselvar-var-unittest = { path = "../../python/packages/var-unittest", editable = true } -oselvar-var = { path = "../../python/packages/var", editable = true } -oselvar-var-core = { path = "../../python/packages/var-core", editable = true } -oselvar-var-config = { path = "../../python/packages/var-config", editable = true } -oselvar-var-runner = { path = "../../python/packages/var-runner", editable = true } +varar-unittest = { path = "../../python/packages/unittest", editable = true } +varar = { path = "../../python/packages/varar", editable = true } +varar-core = { path = "../../python/packages/core", editable = true } +varar-config = { path = "../../python/packages/config", editable = true } +varar-runner = { path = "../../python/packages/runner", editable = true } [tool.hatch.build.targets.wheel] packages = ["src/library_example", "src/roman_numerals_example", "src/yahtzee_example"] diff --git a/examples/python-unittest/steps/deep_thought.steps.py b/examples/python-unittest/steps/deep_thought.steps.py index c2314c2b..e1bee0c8 100644 --- a/examples/python-unittest/steps/deep_thought.steps.py +++ b/examples/python-unittest/steps/deep_thought.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps() diff --git a/examples/python-unittest/steps/hello_var.steps.py b/examples/python-unittest/steps/hello_var.steps.py index 8d83b453..19dfc8f5 100644 --- a/examples/python-unittest/steps/hello_var.steps.py +++ b/examples/python-unittest/steps/hello_var.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"greeting": "", "result": 0}) diff --git a/examples/python-unittest/steps/library.steps.py b/examples/python-unittest/steps/library.steps.py index 6e6dbabd..801140ec 100644 --- a/examples/python-unittest/steps/library.steps.py +++ b/examples/python-unittest/steps/library.steps.py @@ -1,7 +1,7 @@ from datetime import datetime from library_example import FEE_PER_DAY, add_money, gbp, late_fee, may_borrow -from var import steps +from varar import steps def to_date(raw): diff --git a/examples/python-unittest/steps/roman_numerals.steps.py b/examples/python-unittest/steps/roman_numerals.steps.py index 50ab41e1..6077ad5b 100644 --- a/examples/python-unittest/steps/roman_numerals.steps.py +++ b/examples/python-unittest/steps/roman_numerals.steps.py @@ -1,5 +1,5 @@ from roman_numerals_example import to_roman -from var import steps +from varar import steps param, stimulus, sensor = steps() diff --git a/examples/python-unittest/steps/tables_and_docstrings.steps.py b/examples/python-unittest/steps/tables_and_docstrings.steps.py index 9adf1303..ec444d66 100644 --- a/examples/python-unittest/steps/tables_and_docstrings.steps.py +++ b/examples/python-unittest/steps/tables_and_docstrings.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps param, stimulus, sensor = steps() diff --git a/examples/python-unittest/steps/yahtzee.steps.py b/examples/python-unittest/steps/yahtzee.steps.py index d16bde7c..ef9fd0f6 100644 --- a/examples/python-unittest/steps/yahtzee.steps.py +++ b/examples/python-unittest/steps/yahtzee.steps.py @@ -1,4 +1,4 @@ -from var import steps +from varar import steps from yahtzee_example import score param, stimulus, sensor = steps() diff --git a/examples/python-unittest/test_var.py b/examples/python-unittest/test_var.py index 0e88efc9..35785b5e 100644 --- a/examples/python-unittest/test_var.py +++ b/examples/python-unittest/test_var.py @@ -1,5 +1,5 @@ -"""Turns every Markdown spec matched by var.config.json into unittest tests.""" +"""Turns every Markdown spec matched by varar.config.json into unittest tests.""" -from var_unittest import generate_tests +from varar_unittest import generate_tests generate_tests(globals()) diff --git a/examples/python-unittest/var.config.json b/examples/python-unittest/varar.config.json similarity index 67% rename from examples/python-unittest/var.config.json rename to examples/python-unittest/varar.config.json index 5525feca..8ca611cd 100644 --- a/examples/python-unittest/var.config.json +++ b/examples/python-unittest/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": [ "*.md" diff --git a/examples/python-unittest/var.lock.json b/examples/python-unittest/varar.lock.json similarity index 100% rename from examples/python-unittest/var.lock.json rename to examples/python-unittest/varar.lock.json diff --git a/examples/ruby-minitest/Gemfile b/examples/ruby-minitest/Gemfile index e3df8d77..0af0be32 100644 --- a/examples/ruby-minitest/Gemfile +++ b/examples/ruby-minitest/Gemfile @@ -3,13 +3,13 @@ source "https://rubygems.org" # During development these resolve against the local monorepo build. The release -# sync (release/targets/60-var-examples.sh) rewrites them to the published -# versions, e.g. gem "oselvar-var-minitest", "~> 0.3". -gem "oselvar-var-core", path: "../../ruby/packages/var-core" -gem "oselvar-var", path: "../../ruby/packages/var" -gem "oselvar-var-config", path: "../../ruby/packages/var-config" -gem "oselvar-var-runner", path: "../../ruby/packages/var-runner" -gem "oselvar-var-minitest", path: "../../ruby/packages/var-minitest" +# sync (release/targets/70-varar-examples.sh) rewrites them to the published +# versions, e.g. gem "varar-minitest", "~> 0.3". +gem "varar-core", path: "../../ruby/packages/core" +gem "varar", path: "../../ruby/packages/varar" +gem "varar-config", path: "../../ruby/packages/config" +gem "varar-runner", path: "../../ruby/packages/runner" +gem "varar-minitest", path: "../../ruby/packages/minitest" gem "minitest", "~> 6.0" gem "rake", "~> 13.0" diff --git a/examples/ruby-minitest/README.md b/examples/ruby-minitest/README.md index c50d4a17..09e575d1 100644 --- a/examples/ruby-minitest/README.md +++ b/examples/ruby-minitest/README.md @@ -1,12 +1,12 @@ # Vár + Ruby + Minitest A standalone sample project that runs Markdown specs as Minitest tests with -[Vár](https://var.oselvar.com). +[Vár](https://varar.dev). The `*.md` files at the project root are the specs — plain Markdown prose that runs as tests. `steps/*.steps.rb` bind the sentences to Ruby inside a `steps(...) do … end` block with `stimulus`/`sensor` (and `param` for custom -types). `var.config.json` says which files are specs (`docs`) and where the +types). `varar.config.json` says which files are specs (`docs`) and where the step definitions live (`steps`). ## Run @@ -16,8 +16,8 @@ bundle install bundle exec rake test ``` -`test/var_test.rb` calls `Oselvar::Var::Minitest.generate_tests`, which injects +`test/var_test.rb` calls `Varar::Minitest.generate_tests`, which injects one `Minitest::Test` subclass per spec with one test method per Markdown example (header-bound table rows are separate methods). A paragraph that used to match a step and no longer does fails as **drift**; re-run with `VAR_UPDATE=1` to accept -it. The committed `var.lock.json` is that drift baseline. +it. The committed `varar.lock.json` is that drift baseline. diff --git a/examples/ruby-minitest/test/var_test.rb b/examples/ruby-minitest/test/var_test.rb index ae0479d1..99b4a541 100644 --- a/examples/ruby-minitest/test/var_test.rb +++ b/examples/ruby-minitest/test/var_test.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true require 'minitest/autorun' -require 'oselvar/var/minitest' +require 'varar/minitest' -# Turn every Markdown spec matched by var.config.json into Minitest tests — +# Turn every Markdown spec matched by varar.config.json into Minitest tests — # one Test subclass per spec, one test method per Markdown example. -# var.config.json lives at the project root (the parent of test/). -Oselvar::Var::Minitest.generate_tests(Object, root: File.expand_path('..', __dir__)) +# varar.config.json lives at the project root (the parent of test/). +Varar::Minitest.generate_tests(Object, root: File.expand_path('..', __dir__)) diff --git a/examples/ruby-minitest/var.config.json b/examples/ruby-minitest/varar.config.json similarity index 67% rename from examples/ruby-minitest/var.config.json rename to examples/ruby-minitest/varar.config.json index de618022..a6aec2a7 100644 --- a/examples/ruby-minitest/var.config.json +++ b/examples/ruby-minitest/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": [ "*.md" diff --git a/examples/ruby-minitest/var.lock.json b/examples/ruby-minitest/varar.lock.json similarity index 100% rename from examples/ruby-minitest/var.lock.json rename to examples/ruby-minitest/varar.lock.json diff --git a/examples/ruby-rspec/Gemfile b/examples/ruby-rspec/Gemfile index 1f663b07..37155f3c 100644 --- a/examples/ruby-rspec/Gemfile +++ b/examples/ruby-rspec/Gemfile @@ -3,12 +3,12 @@ source "https://rubygems.org" # During development these resolve against the local monorepo build. The release -# sync (release/targets/60-var-examples.sh) rewrites them to the published -# versions, e.g. gem "oselvar-var-rspec", "~> 0.3". -gem "oselvar-var-core", path: "../../ruby/packages/var-core" -gem "oselvar-var", path: "../../ruby/packages/var" -gem "oselvar-var-config", path: "../../ruby/packages/var-config" -gem "oselvar-var-runner", path: "../../ruby/packages/var-runner" -gem "oselvar-var-rspec", path: "../../ruby/packages/var-rspec" +# sync (release/targets/70-varar-examples.sh) rewrites them to the published +# versions, e.g. gem "varar-rspec", "~> 0.3". +gem "varar-core", path: "../../ruby/packages/core" +gem "varar", path: "../../ruby/packages/varar" +gem "varar-config", path: "../../ruby/packages/config" +gem "varar-runner", path: "../../ruby/packages/runner" +gem "varar-rspec", path: "../../ruby/packages/rspec" gem "rspec", "~> 3.13" diff --git a/examples/ruby-rspec/README.md b/examples/ruby-rspec/README.md index 921b972e..01218ea4 100644 --- a/examples/ruby-rspec/README.md +++ b/examples/ruby-rspec/README.md @@ -1,12 +1,12 @@ # Vár + Ruby + RSpec A standalone sample project that runs Markdown specs as RSpec examples with -[Vár](https://var.oselvar.com). +[Vár](https://varar.dev). The `*.md` files at the project root are the specs — plain Markdown prose that runs as tests. `steps/*.steps.rb` bind the sentences to Ruby inside a `steps(...) do … end` block with `stimulus`/`sensor` (and `param` for custom -types). `var.config.json` says which files are specs (`docs`) and where the +types). `varar.config.json` says which files are specs (`docs`) and where the step definitions live (`steps`). ## Run @@ -16,8 +16,8 @@ bundle install bundle exec rspec ``` -`spec/var_spec.rb` calls `Oselvar::Var::RSpec.generate`, which turns every +`spec/var_spec.rb` calls `Varar::RSpec.generate`, which turns every matched spec into one RSpec example group with one `it` per Markdown example (header-bound table rows are separate examples). A paragraph that used to match a step and no longer does fails as **drift**; re-run with `VAR_UPDATE=1` to -accept it. The committed `var.lock.json` is that drift baseline. +accept it. The committed `varar.lock.json` is that drift baseline. diff --git a/examples/ruby-rspec/spec/var_spec.rb b/examples/ruby-rspec/spec/var_spec.rb index 01d5642e..bd94ce70 100644 --- a/examples/ruby-rspec/spec/var_spec.rb +++ b/examples/ruby-rspec/spec/var_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -# Turn every Markdown spec matched by var.config.json into RSpec examples — +# Turn every Markdown spec matched by varar.config.json into RSpec examples — # one `it` per Markdown example, discovered when this file loads. -require 'oselvar/var/rspec' +require 'varar/rspec' -# var.config.json lives at the project root (the parent of spec/). -Oselvar::Var::RSpec.generate(root: File.expand_path('..', __dir__)) +# varar.config.json lives at the project root (the parent of spec/). +Varar::RSpec.generate(root: File.expand_path('..', __dir__)) diff --git a/examples/ruby-rspec/steps/deep_thought.steps.rb b/examples/ruby-rspec/steps/deep_thought.steps.rb index da90822e..3135d51f 100644 --- a/examples/ruby-rspec/steps/deep_thought.steps.rb +++ b/examples/ruby-rspec/steps/deep_thought.steps.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'oselvar/var' +require 'varar' steps do sensor('life, the universe and everything is {int}') { 42 } diff --git a/examples/ruby-rspec/steps/hello_var.steps.rb b/examples/ruby-rspec/steps/hello_var.steps.rb index 6e709a75..519e735f 100644 --- a/examples/ruby-rspec/steps/hello_var.steps.rb +++ b/examples/ruby-rspec/steps/hello_var.steps.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'oselvar/var' +require 'varar' steps(greeting: '', result: 0) do stimulus('I greet {string}') { |_state, name| { greeting: "Hello, #{name}!" } } diff --git a/examples/ruby-rspec/steps/library.steps.rb b/examples/ruby-rspec/steps/library.steps.rb index 3dc93976..31236dae 100644 --- a/examples/ruby-rspec/steps/library.steps.rb +++ b/examples/ruby-rspec/steps/library.steps.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'date' -require 'oselvar/var' +require 'varar' require_relative '../lib/library' # June 6, 2026 → Date; and the inverse (no day-padding flags — not portable). diff --git a/examples/ruby-rspec/steps/roman_numerals.steps.rb b/examples/ruby-rspec/steps/roman_numerals.steps.rb index b99001f5..ac1d3708 100644 --- a/examples/ruby-rspec/steps/roman_numerals.steps.rb +++ b/examples/ruby-rspec/steps/roman_numerals.steps.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'oselvar/var' +require 'varar' require_relative '../lib/roman_numerals' steps do diff --git a/examples/ruby-rspec/steps/tables_and_docstrings.steps.rb b/examples/ruby-rspec/steps/tables_and_docstrings.steps.rb index f9d48a9c..fd36220b 100644 --- a/examples/ruby-rspec/steps/tables_and_docstrings.steps.rb +++ b/examples/ruby-rspec/steps/tables_and_docstrings.steps.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'oselvar/var' +require 'varar' steps do # Whole-table mode: the table arrives as an array of rows (header row first). diff --git a/examples/ruby-rspec/steps/yahtzee.steps.rb b/examples/ruby-rspec/steps/yahtzee.steps.rb index a8ff44a6..05d7f47f 100644 --- a/examples/ruby-rspec/steps/yahtzee.steps.rb +++ b/examples/ruby-rspec/steps/yahtzee.steps.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'oselvar/var' +require 'varar' require_relative '../lib/yahtzee' steps do diff --git a/examples/ruby-rspec/var.config.json b/examples/ruby-rspec/varar.config.json similarity index 67% rename from examples/ruby-rspec/var.config.json rename to examples/ruby-rspec/varar.config.json index de618022..a6aec2a7 100644 --- a/examples/ruby-rspec/var.config.json +++ b/examples/ruby-rspec/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": [ "*.md" diff --git a/examples/ruby-rspec/var.lock.json b/examples/ruby-rspec/varar.lock.json similarity index 100% rename from examples/ruby-rspec/var.lock.json rename to examples/ruby-rspec/varar.lock.json diff --git a/examples/rust-cargotest/Cargo.toml b/examples/rust-cargotest/Cargo.toml index 80df31b0..8361e342 100644 --- a/examples/rust-cargotest/Cargo.toml +++ b/examples/rust-cargotest/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "var-example-rust-cargotest" +name = "varar-example-rust-cargotest" version = "0.0.1" edition = "2024" publish = false @@ -10,21 +10,21 @@ description = "Standalone sample: run Markdown specs as `cargo test` tests with # crates instead. [dependencies] # The engine (steps are authored against it) and the cargo-test adapter. -var-core = { path = "../../rust/var-core" } -var-cargotest = { path = "../../rust/var-cargotest" } -# The ergonomic author facade (`var::Steps`), used by the step definitions. -var = { path = "../../rust/var" } +varar-core = { path = "../../rust/core" } +varar-cargotest = { path = "../../rust/cargotest" } +# The ergonomic author facade (`varar::Steps`), used by the step definitions. +varar = { path = "../../rust/varar" } [dev-dependencies] # The `unit` test drives discovery and a single example directly. -var-config = { path = "../../rust/var-config" } -var-runner = { path = "../../rust/var-runner" } +varar-config = { path = "../../rust/config" } +varar-runner = { path = "../../rust/runner" } [lib] name = "example" path = "src/lib.rs" -# The specs run through the var-cargotest adapter, which owns the libtest +# The specs run through the varar-cargotest adapter, which owns the libtest # harness — hence `harness = false` and a `main` in tests/specs.rs. [[test]] name = "specs" diff --git a/examples/rust-cargotest/README.md b/examples/rust-cargotest/README.md index 48e89443..7363e06d 100644 --- a/examples/rust-cargotest/README.md +++ b/examples/rust-cargotest/README.md @@ -1,7 +1,7 @@ # Vár sample: Rust + cargo test A small, standalone sample project that runs Markdown specs as tests with -[Vár](https://var.oselvar.com), driven by `cargo test`. Copy it as the starting +[Vár](https://varar.dev), driven by `cargo test`. Copy it as the starting point for your own project. The `.md` files at the project root are the specs — they run as tests. @@ -16,42 +16,42 @@ cargo test --test specs yahtzee # run a single spec Each Markdown spec becomes one `cargo test` test; every example in it is run and printed as `spec.md::name`, mirroring `pytest -v` / `python -m unittest -v` -in the sibling Python samples. (Because var-core is single-threaded — `Rc`, not +in the sibling Python samples. (Because varar-core is single-threaded — `Rc`, not `Send` — the samples group examples per spec rather than emitting one libtest item per example.) ## How it fits together -- **`var.config.json`** is the single source of truth: `docs.include` globs the +- **`varar.config.json`** is the single source of truth: `docs.include` globs the Markdown specs. (`steps` is carried for parity with the other ports; Rust compiles its step files in, so there is nothing to glob at runtime.) - **`src/steps/*.rs`** define the steps. Rust has no import-for-side-effect, so — like the Java/Kotlin ports and unlike TypeScript/Python — each file exposes a `register(Registry) -> Registry` that adds its steps explicitly, and `steps::build_registry` chains them. The threaded state is a **full - replacement** value (var-core's model): a stimulus returns the whole next + replacement** value (varar-core's model): a stimulus returns the whole next state; a sensor returns a value for Vár to compare against what the Markdown says. - **`src/*_example.rs`** are the sample's domain code — ordinary modules the steps call, just like your production code. - **`src/runner.rs`** is the small imperative shell (read config, glob specs, plan/run each example, render failures). In a full port this would be a - shared `var-runner` crate; here it lives in the sample to keep it to a single - crate depending only on `var-core`. + shared `varar-runner` crate; here it lives in the sample to keep it to a single + crate depending only on `varar-core`. ## Notes for the Rust port -- var-core's dynamic `Value` is a **closed enum**, so — unlike the Python/Java +- varar-core's dynamic `Value` is a **closed enum**, so — unlike the Python/Java ports, which hold a `Money`/`date` object in the threaded state — `library` encodes money as pennies (`Value::Int`) and a date as a `{year, month, day}` map, with `parse`/`format` custom parameter types converting at the edge. - The `money` parameter type uses a lookahead-free regexp - (`£\d+(?:\.\d+)?|\d+p`): var-core's matcher compiles with the `regex` crate, + (`£\d+(?:\.\d+)?|\d+p`): varar-core's matcher compiles with the `regex` crate, which has no lookahead, so it drops the empty-match guards of the Python pattern (the covered corpus is identical). ## Versioning note -In the [oselvar/var](https://github.com/oselvar/var) monorepo this sample -resolves `var-core` from a `path` dependency, gating trunk against the local +In the [oselvar/varar](https://github.com/oselvar/varar) monorepo this sample +resolves `varar-core` from a `path` dependency, gating trunk against the local build. A released project would depend on the published crate instead. diff --git a/examples/rust-cargotest/src/lib.rs b/examples/rust-cargotest/src/lib.rs index 1555f251..115d60f2 100644 --- a/examples/rust-cargotest/src/lib.rs +++ b/examples/rust-cargotest/src/lib.rs @@ -3,7 +3,7 @@ //! - the domain modules (`*_example`) are the code under test; //! - `steps` holds the step definitions plus the registry/context glue. //! -//! `tests/specs.rs` wires it into `cargo test` via the `var-cargotest` +//! `tests/specs.rs` wires it into `cargo test` via the `varar-cargotest` //! adapter — one libtest item per Markdown example. Discovery, planning, //! running, rendering, and drift all live in the shared `var-*` crates now, so //! the sample carries no runner of its own. diff --git a/examples/rust-cargotest/src/library_example.rs b/examples/rust-cargotest/src/library_example.rs index bfcc1941..9b2b1f7b 100644 --- a/examples/rust-cargotest/src/library_example.rs +++ b/examples/rust-cargotest/src/library_example.rs @@ -2,7 +2,7 @@ //! borrow rule. A port of `examples/python-pytest/src/library_example`. //! //! Money is carried as whole **pennies** (`i64`) rather than a `Money` value -//! type: var-core's dynamic [`Value`](var_core::value::Value) is a closed enum, +//! type: varar-core's dynamic [`Value`](varar_core::value::Value) is a closed enum, //! so — unlike the Python/Java ports, which hold a `Money`/`date` object in the //! threaded state — the Rust steps encode money as an integer and dates as a //! `{year, month, day}` map. The GBP currency is implicit. diff --git a/examples/rust-cargotest/src/steps/deep_thought.rs b/examples/rust-cargotest/src/steps/deep_thought.rs index f19dc918..4641ed8c 100644 --- a/examples/rust-cargotest/src/steps/deep_thought.rs +++ b/examples/rust-cargotest/src/steps/deep_thought.rs @@ -1,6 +1,6 @@ //! Steps for `deep-thought.md`. -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/examples/rust-cargotest/src/steps/hello_var.rs b/examples/rust-cargotest/src/steps/hello_var.rs index 8ead32bd..0374f7ef 100644 --- a/examples/rust-cargotest/src/steps/hello_var.rs +++ b/examples/rust-cargotest/src/steps/hello_var.rs @@ -1,7 +1,7 @@ //! Steps for `hello-var.md`. use super::{as_int, as_str, smap}; -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/examples/rust-cargotest/src/steps/library.rs b/examples/rust-cargotest/src/steps/library.rs index b6d3999d..346ba0bc 100644 --- a/examples/rust-cargotest/src/steps/library.rs +++ b/examples/rust-cargotest/src/steps/library.rs @@ -10,7 +10,7 @@ use crate::library_example::{ Date, FEE_PER_DAY, format_date, format_money, late_fee, may_borrow, parse_date, parse_money, }; use std::rc::Rc; -use var::{FormatFn, ParseFn, Registry, Steps, Value}; +use varar::{FormatFn, ParseFn, Registry, Steps, Value}; fn date_value(d: Date) -> Value { vmap(vec![ @@ -53,7 +53,7 @@ pub fn register(r: Registry) -> Registry { date_format, ); - // £2.50 and 50p, both as pennies. var-core's matcher compiles with the + // £2.50 and 50p, both as pennies. varar-core's matcher compiles with the // `regex` crate, which has no lookahead — so this is the corpus-covering // subset of cucumber-expressions' float regexp (no scientific notation, no // empty-match guards), not the exact Python pattern. diff --git a/examples/rust-cargotest/src/steps/mod.rs b/examples/rust-cargotest/src/steps/mod.rs index 59223554..7a79f3bb 100644 --- a/examples/rust-cargotest/src/steps/mod.rs +++ b/examples/rust-cargotest/src/steps/mod.rs @@ -4,11 +4,11 @@ //! and unlike TypeScript/Python's module-scope accumulator — each step file //! exposes a `register(Registry) -> Registry` that adds its steps explicitly, //! and [`build_registry`] chains them. The threaded state is a **full -//! replacement** value (var-core's model), not a shallow-merged partial: a +//! replacement** value (varar-core's model), not a shallow-merged partial: a //! `stimulus` returns the whole next state. use std::collections::BTreeMap; -use var_core::value::Value; +use varar_core::value::Value; pub mod deep_thought; pub mod hello_var; @@ -17,7 +17,7 @@ pub mod roman_numerals; pub mod tables_and_docstrings; pub mod yahtzee; -use var_core::registry::{Registry, create_registry}; +use varar_core::registry::{Registry, create_registry}; /// The combined registry for all specs. pub fn build_registry() -> Registry { @@ -30,7 +30,7 @@ pub fn build_registry() -> Registry { library::register(r) } -/// Fresh initial state per step file (var-core keys context by a step's source +/// Fresh initial state per step file (varar-core keys context by a step's source /// file — the path captured at each `stimulus`/`sensor` call site). Matched by /// filename suffix so it's independent of the path prefix `#[track_caller]` /// reports. Files whose steps are pure return [`Value::Null`]. A plain `fn` diff --git a/examples/rust-cargotest/src/steps/roman_numerals.rs b/examples/rust-cargotest/src/steps/roman_numerals.rs index 4d611f2a..8bbfe854 100644 --- a/examples/rust-cargotest/src/steps/roman_numerals.rs +++ b/examples/rust-cargotest/src/steps/roman_numerals.rs @@ -2,7 +2,7 @@ use super::{as_str, smap, vmap}; use crate::roman_numerals_example::to_roman; -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/examples/rust-cargotest/src/steps/tables_and_docstrings.rs b/examples/rust-cargotest/src/steps/tables_and_docstrings.rs index 3f53d9cd..faba6969 100644 --- a/examples/rust-cargotest/src/steps/tables_and_docstrings.rs +++ b/examples/rust-cargotest/src/steps/tables_and_docstrings.rs @@ -1,7 +1,7 @@ //! Steps for `tables-and-docstrings.md`. use super::{as_str, vmap}; -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/examples/rust-cargotest/src/steps/yahtzee.rs b/examples/rust-cargotest/src/steps/yahtzee.rs index 6c7f91dd..ac56060a 100644 --- a/examples/rust-cargotest/src/steps/yahtzee.rs +++ b/examples/rust-cargotest/src/steps/yahtzee.rs @@ -2,7 +2,7 @@ use super::{as_str, smap, vmap}; use crate::yahtzee_example::score; -use var::{Registry, Steps, Value}; +use varar::{Registry, Steps, Value}; pub fn register(r: Registry) -> Registry { let mut s = Steps::from_registry(r); diff --git a/examples/rust-cargotest/tests/specs.rs b/examples/rust-cargotest/tests/specs.rs index 9e360c94..bcb98936 100644 --- a/examples/rust-cargotest/tests/specs.rs +++ b/examples/rust-cargotest/tests/specs.rs @@ -1,5 +1,5 @@ -//! Runs every Markdown spec matched by `var.config.json` as `cargo test` tests -//! — one libtest item per example — through the `var-cargotest` adapter. +//! Runs every Markdown spec matched by `varar.config.json` as `cargo test` tests +//! — one libtest item per example — through the `varar-cargotest` adapter. //! //! `cargo test` reports each as `spec.md::name`; `cargo test ` //! selects, `--list` enumerates. Set `VAR_UPDATE=1` to accept drift. @@ -7,7 +7,7 @@ use std::path::Path; fn main() { - var_cargotest::run( + varar_cargotest::run( Path::new(env!("CARGO_MANIFEST_DIR")), example::steps::build_registry, example::steps::context_value, diff --git a/examples/rust-cargotest/tests/unit.rs b/examples/rust-cargotest/tests/unit.rs index 477b9bba..e75fa505 100644 --- a/examples/rust-cargotest/tests/unit.rs +++ b/examples/rust-cargotest/tests/unit.rs @@ -4,9 +4,9 @@ use example::steps::{build_registry, context_value}; use std::path::Path; -use var_cargotest::run_one; -use var_config::read_var_config; -use var_runner::find_specs; +use varar_cargotest::run_one; +use varar_config::read_var_config; +use varar_runner::find_specs; fn root() -> &'static Path { Path::new(env!("CARGO_MANIFEST_DIR")) diff --git a/examples/rust-cargotest/var.config.json b/examples/rust-cargotest/varar.config.json similarity index 67% rename from examples/rust-cargotest/var.config.json rename to examples/rust-cargotest/varar.config.json index d1c507f3..376d1fea 100644 --- a/examples/rust-cargotest/var.config.json +++ b/examples/rust-cargotest/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": [ "*.md" diff --git a/examples/rust-cargotest/var.lock.json b/examples/rust-cargotest/varar.lock.json similarity index 100% rename from examples/rust-cargotest/var.lock.json rename to examples/rust-cargotest/varar.lock.json diff --git a/examples/typescript-vitest/.gitignore b/examples/typescript-vitest/.gitignore index a6a4ecab..68fa20c1 100644 --- a/examples/typescript-vitest/.gitignore +++ b/examples/typescript-vitest/.gitignore @@ -1,7 +1,7 @@ node_modules/ .var/ -# Artifacts of a standalone `pnpm install` in the var-examples repo (in the -# oselvar/var monorepo this project is a workspace member and the lockfile +# Artifacts of a standalone `pnpm install` in the varar-examples repo (in the +# varar monorepo this project is a workspace member and the lockfile # lives at typescript/pnpm-lock.yaml). In your own project, DO commit your # lockfile. pnpm-lock.yaml diff --git a/examples/typescript-vitest/README.md b/examples/typescript-vitest/README.md index 50b278fb..0a9fc783 100644 --- a/examples/typescript-vitest/README.md +++ b/examples/typescript-vitest/README.md @@ -1,7 +1,7 @@ # Vár sample: TypeScript + vitest A small, standalone sample project that runs Markdown specs as tests with -[Vár](https://var.oselvar.com), using the vitest plugin (`@oselvar/var-vitest`). +[Vár](https://varar.dev), using the vitest plugin (`@varar/vitest`). Copy it as the starting point for your own project. The `.md` files at the project root are the specs — they run as tests. @@ -17,7 +17,7 @@ Each example in the Markdown specs becomes one vitest test. ## How it fits together -- **`var.config.json`** is the single source of truth: `docs.include` globs +- **`varar.config.json`** is the single source of truth: `docs.include` globs the Markdown specs and `steps` globs the step-definition files. The vitest plugin drives vitest's own include/exclude from it. - **`steps/*.steps.ts`** define the steps with `steps` + @@ -28,6 +28,6 @@ Each example in the Markdown specs becomes one vitest test. ## Versioning note -In the `oselvar/var` monorepo this project uses `workspace:*` dependencies -(it is the dogfood suite, gating trunk); in `oselvar/var-examples` the +In the `oselvar/varar` monorepo this project uses `workspace:*` dependencies +(it is the dogfood suite, gating trunk); in `varar-examples` the release sync pins them to the released npm packages. diff --git a/examples/typescript-vitest/package.json b/examples/typescript-vitest/package.json index beb66032..dcca3a7e 100644 --- a/examples/typescript-vitest/package.json +++ b/examples/typescript-vitest/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/example-typescript-vitest", + "name": "@varar/example-typescript-vitest", "version": "0.3.0", "private": true, "type": "module", @@ -8,9 +8,9 @@ "test": "vitest run" }, "devDependencies": { - "@oselvar/var": "workspace:*", - "@oselvar/var-core": "workspace:*", - "@oselvar/var-vitest": "workspace:*", + "@varar/varar": "workspace:*", + "@varar/core": "workspace:*", + "@varar/vitest": "workspace:*", "@types/node": "^26.1.0", "typescript": "^6.0.3", "vitest": "^4.1.10" @@ -18,7 +18,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", + "url": "git+https://github.com/oselvar/varar.git", "directory": "examples/typescript-vitest" } } diff --git a/examples/typescript-vitest/steps/airport.steps.ts b/examples/typescript-vitest/steps/airport.steps.ts index 252477f1..83b33cb4 100644 --- a/examples/typescript-vitest/steps/airport.steps.ts +++ b/examples/typescript-vitest/steps/airport.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' // The custom `{airport}` parameter type is declared with a chained `.param()` // call, so Vár can infer the captured args: the parse function returns string, diff --git a/examples/typescript-vitest/steps/deep-thought.steps.ts b/examples/typescript-vitest/steps/deep-thought.steps.ts index eb13020c..41ab0207 100644 --- a/examples/typescript-vitest/steps/deep-thought.steps.ts +++ b/examples/typescript-vitest/steps/deep-thought.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { sensor } = steps() diff --git a/examples/typescript-vitest/steps/hello-var.steps.ts b/examples/typescript-vitest/steps/hello-var.steps.ts index 85ce74e2..9312e99a 100644 --- a/examples/typescript-vitest/steps/hello-var.steps.ts +++ b/examples/typescript-vitest/steps/hello-var.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus, sensor } = steps(() => ({ greeting: '', result: 0 })) diff --git a/examples/typescript-vitest/steps/library.steps.ts b/examples/typescript-vitest/steps/library.steps.ts index 1b4ec812..36fc8625 100644 --- a/examples/typescript-vitest/steps/library.steps.ts +++ b/examples/typescript-vitest/steps/library.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' import { addMoney, FEE_PER_DAY, GBP, type Loan, lateFee, type Money, mayBorrow } from './library' // Custom parameter types are declared with chained `.param()` calls so their diff --git a/examples/typescript-vitest/steps/return-sensor.steps.ts b/examples/typescript-vitest/steps/return-sensor.steps.ts index fe307a6b..a7dcafff 100644 --- a/examples/typescript-vitest/steps/return-sensor.steps.ts +++ b/examples/typescript-vitest/steps/return-sensor.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { sensor } = steps() diff --git a/examples/typescript-vitest/steps/roman-numerals.steps.ts b/examples/typescript-vitest/steps/roman-numerals.steps.ts index bde50578..b9cdfc48 100644 --- a/examples/typescript-vitest/steps/roman-numerals.steps.ts +++ b/examples/typescript-vitest/steps/roman-numerals.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' import { toRoman } from './roman-numerals' const { sensor } = steps() diff --git a/examples/typescript-vitest/steps/tables-and-docstrings.steps.ts b/examples/typescript-vitest/steps/tables-and-docstrings.steps.ts index e7d7fa9b..c47219d6 100644 --- a/examples/typescript-vitest/steps/tables-and-docstrings.steps.ts +++ b/examples/typescript-vitest/steps/tables-and-docstrings.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { sensor } = steps() diff --git a/examples/typescript-vitest/steps/yahtzee.steps.ts b/examples/typescript-vitest/steps/yahtzee.steps.ts index 4a1e272f..30adf9d4 100644 --- a/examples/typescript-vitest/steps/yahtzee.steps.ts +++ b/examples/typescript-vitest/steps/yahtzee.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' import { score } from './yahtzee' const { sensor } = steps() diff --git a/examples/typescript-vitest/var.config.json b/examples/typescript-vitest/varar.config.json similarity index 61% rename from examples/typescript-vitest/var.config.json rename to examples/typescript-vitest/varar.config.json index 740fe84d..df0f995e 100644 --- a/examples/typescript-vitest/var.config.json +++ b/examples/typescript-vitest/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../conformance/config/var.config.schema.json", + "$schema": "../../conformance/config/varar.config.schema.json", "docs": { "include": ["*.md"], "exclude": ["README.md"] diff --git a/examples/typescript-vitest/vitest.config.ts b/examples/typescript-vitest/vitest.config.ts index d3ae0f44..e174f310 100644 --- a/examples/typescript-vitest/vitest.config.ts +++ b/examples/typescript-vitest/vitest.config.ts @@ -1,8 +1,8 @@ -import varPlugin from '@oselvar/var-vitest' +import varPlugin from '@varar/vitest' import { defineConfig } from 'vitest/config' -// The var plugin reads this project's var.config.json and drives vitest's -// include/exclude from its globs — var.config.json is the single source of +// The var plugin reads this project's varar.config.json and drives vitest's +// include/exclude from its globs — varar.config.json is the single source of // truth for which `.md` files are specs and where the steps live. const projectDir = new URL('.', import.meta.url).pathname @@ -10,6 +10,6 @@ export default defineConfig({ plugins: [varPlugin({ cwd: projectDir })], test: { // Inline the var packages so the plugin and runtime are transformed by vite. - server: { deps: { inline: [/^@oselvar\//] } }, + server: { deps: { inline: [/^@varar\//] } }, }, }) diff --git a/java/var-config/pom.xml b/java/config/pom.xml similarity index 71% rename from java/var-config/pom.xml rename to java/config/pom.xml index bf3a6f5b..51f23c88 100644 --- a/java/var-config/pom.xml +++ b/java/config/pom.xml @@ -5,16 +5,16 @@ 4.0.0 - com.oselvar - var-parent + dev.varar + parent 0.4.3-SNAPSHOT - var-config + config jar - var-config (Java) — var.config.json reader + var-config (Java) — varar.config.json reader - Reads var.config.json, the shared config file for all var tools (see + Reads varar.config.json, the shared config file for all var tools (see conformance/config/README.md and doc/superpowers/specs/2026-07-02-multi-language-authoring-design.md). Zero runtime dependencies: JSON parsing is hand-rolled, mirroring @@ -23,8 +23,8 @@ - com.oselvar - var-core + dev.varar + core ${project.version} test diff --git a/java/var-config/src/main/java/com/oselvar/var/config/Json.java b/java/config/src/main/java/dev/varar/config/Json.java similarity index 98% rename from java/var-config/src/main/java/com/oselvar/var/config/Json.java rename to java/config/src/main/java/dev/varar/config/Json.java index 442cd3cd..6eb994ae 100644 --- a/java/var-config/src/main/java/com/oselvar/var/config/Json.java +++ b/java/config/src/main/java/dev/varar/config/Json.java @@ -1,4 +1,4 @@ -package com.oselvar.var.config; +package dev.varar.config; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -8,7 +8,7 @@ /** * Minimal recursive-descent JSON parser: the reading twin of var-core's * hand-rolled {@code CanonicalJson} writer. The repo deliberately has zero - * JSON library dependencies; var.config.json files are tiny, so a ~150-line + * JSON library dependencies; varar.config.json files are tiny, so a ~150-line * strict parser (objects, arrays, strings with escapes, numbers, booleans, * null — no extensions, no comments, duplicate keys rejected) beats pulling * in Jackson for one file format. diff --git a/java/var-config/src/main/java/com/oselvar/var/config/VarConfig.java b/java/config/src/main/java/dev/varar/config/VarConfig.java similarity index 95% rename from java/var-config/src/main/java/com/oselvar/var/config/VarConfig.java rename to java/config/src/main/java/dev/varar/config/VarConfig.java index a12b3340..44e56456 100644 --- a/java/var-config/src/main/java/com/oselvar/var/config/VarConfig.java +++ b/java/config/src/main/java/dev/varar/config/VarConfig.java @@ -1,4 +1,4 @@ -package com.oselvar.var.config; +package dev.varar.config; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -12,7 +12,7 @@ import java.util.TreeSet; /** - * The parsed var.config.json — the single shared config file for all var + * The parsed varar.config.json — the single shared config file for all var * tools across every language port. Same field semantics everywhere: * {@code docs.include} has no default (empty discovers nothing), * {@code docs.exclude} removes matches, both are plain globs (no {@code !} @@ -45,9 +45,9 @@ public static VarConfig empty() { return new VarConfig(List.of(), List.of(), List.of(), Map.of(), List.of()); } - /** Reads {@code /var.config.json}; a missing file is the empty config. */ + /** Reads {@code /varar.config.json}; a missing file is the empty config. */ public static VarConfig load(Path root) { - Path path = root.resolve("var.config.json"); + Path path = root.resolve("varar.config.json"); if (!Files.isRegularFile(path)) return empty(); String text; try { diff --git a/java/var-config/src/test/java/com/oselvar/var/config/ConfigConformanceTest.java b/java/config/src/test/java/dev/varar/config/ConfigConformanceTest.java similarity index 93% rename from java/var-config/src/test/java/com/oselvar/var/config/ConfigConformanceTest.java rename to java/config/src/test/java/dev/varar/config/ConfigConformanceTest.java index 40605811..a4c6f6d5 100644 --- a/java/var-config/src/test/java/com/oselvar/var/config/ConfigConformanceTest.java +++ b/java/config/src/test/java/dev/varar/config/ConfigConformanceTest.java @@ -1,10 +1,10 @@ -package com.oselvar.var.config; +package dev.varar.config; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.oselvar.var.core.CanonicalJson; +import dev.varar.core.CanonicalJson; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -20,7 +20,7 @@ /** Shared config-conformance harness — see conformance/config/README.md. */ class ConfigConformanceTest { - // Maven runs with java/var-config/ as the working directory; the corpus + // Maven runs with java/config/ as the working directory; the corpus // is a repo-root sibling of java/, two levels up. private static final Path CASES_DIR = Paths.get("..", "..", "conformance", "config", "cases"); diff --git a/java/var-config/src/test/java/com/oselvar/var/config/JsonTest.java b/java/config/src/test/java/dev/varar/config/JsonTest.java similarity index 98% rename from java/var-config/src/test/java/com/oselvar/var/config/JsonTest.java rename to java/config/src/test/java/dev/varar/config/JsonTest.java index 1b69d7fe..4e05e609 100644 --- a/java/var-config/src/test/java/com/oselvar/var/config/JsonTest.java +++ b/java/config/src/test/java/dev/varar/config/JsonTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.config; +package dev.varar.config; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/java/var-config/src/test/java/com/oselvar/var/config/VarConfigTest.java b/java/config/src/test/java/dev/varar/config/VarConfigTest.java similarity index 79% rename from java/var-config/src/test/java/com/oselvar/var/config/VarConfigTest.java rename to java/config/src/test/java/dev/varar/config/VarConfigTest.java index 9bcef16f..81cf8d0a 100644 --- a/java/var-config/src/test/java/com/oselvar/var/config/VarConfigTest.java +++ b/java/config/src/test/java/dev/varar/config/VarConfigTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.config; +package dev.varar.config; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -24,7 +24,7 @@ void parsesAllKeys() { "snippets": { "java": "J" }, "scannerPlugins": ["gherkinTables"] } - """, "var.config.json"); + """, "varar.config.json"); assertEquals(List.of("specs/**/*.md"), config.docsInclude()); assertEquals(List.of("specs/wip/**"), config.docsExclude()); assertEquals(List.of("**/*Steps.java"), config.steps()); @@ -34,30 +34,33 @@ void parsesAllKeys() { @Test void allKeysOptionalAndSchemaKeyIgnored() { - assertEquals(VarConfig.empty(), VarConfig.parse("{ \"$schema\": \"x\" }", "var.config.json")); + assertEquals(VarConfig.empty(), VarConfig.parse("{ \"$schema\": \"x\" }", "varar.config.json")); } @Test void unknownKeyIsRejected() { IllegalArgumentException e = assertThrows( - IllegalArgumentException.class, () -> VarConfig.parse("{ \"vars\": {} }", "var.config.json")); + IllegalArgumentException.class, () -> VarConfig.parse("{ \"vars\": {} }", "varar.config.json")); assertTrue(e.getMessage().contains("unknown key"), e.getMessage()); - assertTrue(e.getMessage().startsWith("var.config.json"), e.getMessage()); + assertTrue(e.getMessage().startsWith("varar.config.json"), e.getMessage()); } @Test void wrongTypeIsRejected() { - assertThrows(IllegalArgumentException.class, () -> VarConfig.parse("{ \"steps\": \"x\" }", "var.config.json")); + assertThrows( + IllegalArgumentException.class, () -> VarConfig.parse("{ \"steps\": \"x\" }", "varar.config.json")); assertThrows( IllegalArgumentException.class, - () -> VarConfig.parse("{ \"snippets\": { \"java\": 1 } }", "var.config.json")); + () -> VarConfig.parse("{ \"snippets\": { \"java\": 1 } }", "varar.config.json")); } @Test void loadReadsFileAndMissingFileIsEmpty(@TempDir Path dir) throws IOException { assertEquals(VarConfig.empty(), VarConfig.load(dir)); Files.writeString( - dir.resolve("var.config.json"), "{ \"docs\": { \"include\": [\"**/*.md\"] } }", StandardCharsets.UTF_8); + dir.resolve("varar.config.json"), + "{ \"docs\": { \"include\": [\"**/*.md\"] } }", + StandardCharsets.UTF_8); assertEquals(List.of("**/*.md"), VarConfig.load(dir).docsInclude()); } diff --git a/java/var-core/pom.xml b/java/core/pom.xml similarity index 88% rename from java/var-core/pom.xml rename to java/core/pom.xml index dcb058a2..9bd73c9e 100644 --- a/java/var-core/pom.xml +++ b/java/core/pom.xml @@ -5,12 +5,12 @@ 4.0.0 - com.oselvar - var-parent + dev.varar + parent 0.4.3-SNAPSHOT - var-core + core jar var-core (Java) — pure functional core diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Ast.java b/java/core/src/main/java/dev/varar/core/Ast.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/Ast.java rename to java/core/src/main/java/dev/varar/core/Ast.java index 2d60b304..e4953dc9 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Ast.java +++ b/java/core/src/main/java/dev/varar/core/Ast.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.List; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/CanonicalJson.java b/java/core/src/main/java/dev/varar/core/CanonicalJson.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/CanonicalJson.java rename to java/core/src/main/java/dev/varar/core/CanonicalJson.java index 4d3d1c79..638a482a 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/CanonicalJson.java +++ b/java/core/src/main/java/dev/varar/core/CanonicalJson.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.List; import java.util.Map; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/CellDiff.java b/java/core/src/main/java/dev/varar/core/CellDiff.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/CellDiff.java rename to java/core/src/main/java/dev/varar/core/CellDiff.java index 982fb4c4..a06de59e 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/CellDiff.java +++ b/java/core/src/main/java/dev/varar/core/CellDiff.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.ArrayList; import java.util.List; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Conformance.java b/java/core/src/main/java/dev/varar/core/Conformance.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/Conformance.java rename to java/core/src/main/java/dev/varar/core/Conformance.java index 347b2eae..da4fb7ae 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Conformance.java +++ b/java/core/src/main/java/dev/varar/core/Conformance.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import io.cucumber.cucumberexpressions.CucumberExpressionParser; import io.cucumber.cucumberexpressions.Node; @@ -207,7 +207,7 @@ private static Map kindLineAnchor(String kind, int line, Map{@code contextFactory} is typed {@code Supplier}, not {@code - * Supplier}: this package ({@code var-core}) has zero compile-time + * Supplier}: this package ({@code var-core}) has zero compile-time * dependency on the {@code var} facade's {@code State} marker interface — the same * hexagonal boundary {@link Execute}'s own {@code createContext} port already respects by * returning plain {@code Object}. diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Diagnostics.java b/java/core/src/main/java/dev/varar/core/Diagnostics.java similarity index 98% rename from java/var-core/src/main/java/com/oselvar/var/core/Diagnostics.java rename to java/core/src/main/java/dev/varar/core/Diagnostics.java index 370f0bb1..cd2cfe15 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Diagnostics.java +++ b/java/core/src/main/java/dev/varar/core/Diagnostics.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; /** * Diagnostics — port of the subset of {@code var-core/src/diagnostics.ts} that {@link Plan} diff --git a/java/var-core/src/main/java/com/oselvar/var/core/DocStringDiff.java b/java/core/src/main/java/dev/varar/core/DocStringDiff.java similarity index 98% rename from java/var-core/src/main/java/com/oselvar/var/core/DocStringDiff.java rename to java/core/src/main/java/dev/varar/core/DocStringDiff.java index f98b51bc..7d72e6c2 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/DocStringDiff.java +++ b/java/core/src/main/java/dev/varar/core/DocStringDiff.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; /** * Doc-string comparison — port of {@code var-core/src/doc-string-diff.ts}. diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Drift.java b/java/core/src/main/java/dev/varar/core/Drift.java similarity index 96% rename from java/var-core/src/main/java/com/oselvar/var/core/Drift.java rename to java/core/src/main/java/dev/varar/core/Drift.java index 8e08848a..185b7b99 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Drift.java +++ b/java/core/src/main/java/dev/varar/core/Drift.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.ArrayList; import java.util.Collections; @@ -14,10 +14,10 @@ /** * Spec drift detection — port of {@code var-core/src/drift.ts}. * - *

A paragraph the committed {@code var.lock.json} baseline recorded as an example that now + *

A paragraph the committed {@code varar.lock.json} baseline recorded as an example that now * matches no step. Pure over the existing {@link Ast.VarDoc} + {@link Plan.ExecutionPlan}, and * byte-identical to the TypeScript and Python ports so a baseline written by one runs green under - * the others: the same FNV-1a fingerprint ({@link Hash}), the same {@code var.lock.json} bytes + * the others: the same FNV-1a fingerprint ({@link Hash}), the same {@code varar.lock.json} bytes * (insertion-ordered keys, sorted spec paths, raw non-ASCII), and the same similarity semantics. */ public final class Drift { @@ -42,7 +42,7 @@ public record SpecBaseline(String sourceHash, List examples) { } } - /** The whole {@code var.lock.json}: every spec keyed by its POSIX path. */ + /** The whole {@code varar.lock.json}: every spec keyed by its POSIX path. */ public record VarLock(int version, Map specs) { public VarLock { specs = Collections.unmodifiableMap(new LinkedHashMap<>(specs)); @@ -53,7 +53,7 @@ public record VarLock(int version, Map specs) { public record Drifted(String name, int line, Span span) {} /** - * Persistence port for {@code var.lock.json}. The core owns the format; adapters move only raw + * Persistence port for {@code varar.lock.json}. The core owns the format; adapters move only raw * text (a filesystem store on disk, an in-memory store). */ public interface BaselineStore { @@ -188,7 +188,7 @@ public static List reconcileDrift( // ---- serialize (byte-identical to JSON.stringify(...,null,2)+"\n") ------ /** - * Serializes {@code var.lock.json} deterministically: {@code version} then {@code specs} (spec + * Serializes {@code varar.lock.json} deterministically: {@code version} then {@code specs} (spec * paths sorted), examples in document order, two-space indent, trailing newline, non-ASCII * raw. NOT {@link CanonicalJson} (which sorts every key) — the lockfile keeps insertion order. */ @@ -261,7 +261,7 @@ private static void writeString(StringBuilder sb, String s) { // ---- parse (a minimal JSON reader; no library in the project) ---------- - /** Parses {@code var.lock.json}; {@code null} on malformed input (treated as no baseline). */ + /** Parses {@code varar.lock.json}; {@code null} on malformed input (treated as no baseline). */ public static VarLock parseVarLock(String text) { Object parsed; try { @@ -296,7 +296,7 @@ private static SpecBaseline parseSpecBaseline(Object value) { return new SpecBaseline(sourceHash, examples); } - /** A tiny recursive-descent JSON reader — enough for var.lock.json, throws on malformed. */ + /** A tiny recursive-descent JSON reader — enough for varar.lock.json, throws on malformed. */ private static final class JsonReader { private final String s; private int i; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Execute.java b/java/core/src/main/java/dev/varar/core/Execute.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/Execute.java rename to java/core/src/main/java/dev/varar/core/Execute.java index c24daf45..23c198ae 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Execute.java +++ b/java/core/src/main/java/dev/varar/core/Execute.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -26,7 +26,7 @@ * otherwise silently possible and needs to be defended against at runtime. * *

Java needs no equivalent and none is ported here. Task 11 committed to a - * full-replacement {@code record}-based state model ({@code com.oselvar.var.State}): + * full-replacement {@code record}-based state model ({@code dev.varar.State}): * authors declare {@code record Ctx(...) implements State}, and every {@code * stimulus} handler returns a brand new, complete {@code Ctx} value — * there is no partial merge and no in-place mutation path to guard against. A Java @@ -87,7 +87,7 @@ * Registry.StepRegistration#handler()} is plain {@link Object}. This executor invokes it * purely via reflection, matched by arity (state + captured args + at most one trailing * table/doc-string argument) against the handler's single non-{@code Object} method — - * works for any functional interface shape, not just the ones {@code com.oselvar.var} + * works for any functional interface shape, not just the ones {@code dev.varar} * happens to define today. * *

Stack injection for {@link Failure#toFailure}

@@ -418,7 +418,7 @@ private static Object invokeHandler(Object handler, Object state, List a * Finds {@code handlerClass}'s single abstract method with {@code paramCount} * parameters — the functional interface's SAM, whatever it's called and whichever * interface it belongs to (see class javadoc: {@code var-core} never imports {@code - * com.oselvar.var}'s {@code Context0/1/2}/{@code Sensor0/1/2}). + * dev.varar}'s {@code Context0/1/2}/{@code Sensor0/1/2}). */ private static Method samMethod(Class handlerClass, int paramCount) { Method candidate = null; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Failure.java b/java/core/src/main/java/dev/varar/core/Failure.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/Failure.java rename to java/core/src/main/java/dev/varar/core/Failure.java index f93af719..38537789 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Failure.java +++ b/java/core/src/main/java/dev/varar/core/Failure.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/FailureAnchor.java b/java/core/src/main/java/dev/varar/core/FailureAnchor.java similarity index 97% rename from java/var-core/src/main/java/com/oselvar/var/core/FailureAnchor.java rename to java/core/src/main/java/dev/varar/core/FailureAnchor.java index a3aefbb3..bcd138b6 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/FailureAnchor.java +++ b/java/core/src/main/java/dev/varar/core/FailureAnchor.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; /** * Where a failure POINTS in the {@code .md}: a mismatch anchors at its first failing span (the diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Hash.java b/java/core/src/main/java/dev/varar/core/Hash.java similarity index 89% rename from java/var-core/src/main/java/com/oselvar/var/core/Hash.java rename to java/core/src/main/java/dev/varar/core/Hash.java index c8193681..32cb3453 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Hash.java +++ b/java/core/src/main/java/dev/varar/core/Hash.java @@ -1,9 +1,9 @@ -package com.oselvar.var.core; +package dev.varar.core; /** * FNV-1a (32-bit) change-detector over UTF-16 code units. Port of {@code * var-core/src/hash.ts}; byte-identical to the TypeScript and Python ports so - * {@code var.lock.json} fingerprints match across every language. Java {@code + * {@code varar.lock.json} fingerprints match across every language. Java {@code * char} is already a UTF-16 code unit (like JS {@code charCodeAt}), and {@code * int} arithmetic wraps mod 2^32, so this is a direct transliteration. The * {@code fnv1a:} prefix namespaces the algorithm. diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Matcher.java b/java/core/src/main/java/dev/varar/core/Matcher.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/Matcher.java rename to java/core/src/main/java/dev/varar/core/Matcher.java index 2fb43901..c7b3a04a 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Matcher.java +++ b/java/core/src/main/java/dev/varar/core/Matcher.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import io.cucumber.cucumberexpressions.Argument; import java.util.ArrayList; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/ParamDiff.java b/java/core/src/main/java/dev/varar/core/ParamDiff.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/ParamDiff.java rename to java/core/src/main/java/dev/varar/core/ParamDiff.java index 18f6ad41..f39e96ca 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/ParamDiff.java +++ b/java/core/src/main/java/dev/varar/core/ParamDiff.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.ArrayList; import java.util.List; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Parse.java b/java/core/src/main/java/dev/varar/core/Parse.java similarity index 95% rename from java/var-core/src/main/java/com/oselvar/var/core/Parse.java rename to java/core/src/main/java/dev/varar/core/Parse.java index 1ae70ff8..6b609c46 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Parse.java +++ b/java/core/src/main/java/dev/varar/core/Parse.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; /** * Top-level entry point of the pure core: {@code scan} the source into blocks, then {@code diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Plan.java b/java/core/src/main/java/dev/varar/core/Plan.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/Plan.java rename to java/core/src/main/java/dev/varar/core/Plan.java index 935ced13..3fdd63f2 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Plan.java +++ b/java/core/src/main/java/dev/varar/core/Plan.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.ArrayList; import java.util.Collections; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Registry.java b/java/core/src/main/java/dev/varar/core/Registry.java similarity index 98% rename from java/var-core/src/main/java/com/oselvar/var/core/Registry.java rename to java/core/src/main/java/dev/varar/core/Registry.java index f777bcd0..1bbbcf28 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Registry.java +++ b/java/core/src/main/java/dev/varar/core/Registry.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import io.cucumber.cucumberexpressions.CaptureGroupTransformer; import io.cucumber.cucumberexpressions.Expression; @@ -23,7 +23,7 @@ * constructor. The Java library's {@code CucumberExpression(String, * ParameterTypeRegistry)} constructor is package-private — {@code javap} prints it with * no access modifier, and the class is otherwise unreachable for direct construction - * from {@code com.oselvar.var.core}. The library's public entry point is instead {@code + * from {@code dev.varar.core}. The library's public entry point is instead {@code * ExpressionFactory(ParameterTypeRegistry).createExpression(String)}, which returns the * public {@code Expression} interface (dispatching internally to a {@code * CucumberExpression} unless the source string is itself an anchored/regex-literal form diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Result.java b/java/core/src/main/java/dev/varar/core/Result.java similarity index 98% rename from java/var-core/src/main/java/com/oselvar/var/core/Result.java rename to java/core/src/main/java/dev/varar/core/Result.java index e75392fc..177b42f4 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Result.java +++ b/java/core/src/main/java/dev/varar/core/Result.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.List; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Scanner.java b/java/core/src/main/java/dev/varar/core/Scanner.java similarity index 98% rename from java/var-core/src/main/java/com/oselvar/var/core/Scanner.java rename to java/core/src/main/java/dev/varar/core/Scanner.java index 079be42e..143711c5 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Scanner.java +++ b/java/core/src/main/java/dev/varar/core/Scanner.java @@ -1,9 +1,9 @@ -package com.oselvar.var.core; +package dev.varar.core; -import com.oselvar.var.core.Ast.Block; -import com.oselvar.var.core.Ast.Row; -import com.oselvar.var.core.Ast.SegmentOffset; -import com.oselvar.var.core.TableCells.RowCells; +import dev.varar.core.Ast.Block; +import dev.varar.core.Ast.Row; +import dev.varar.core.Ast.SegmentOffset; +import dev.varar.core.TableCells.RowCells; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Sentences.java b/java/core/src/main/java/dev/varar/core/Sentences.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/Sentences.java rename to java/core/src/main/java/dev/varar/core/Sentences.java index 6f9c2b53..4e1e4d57 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Sentences.java +++ b/java/core/src/main/java/dev/varar/core/Sentences.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.ArrayList; import java.util.List; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Span.java b/java/core/src/main/java/dev/varar/core/Span.java similarity index 98% rename from java/var-core/src/main/java/com/oselvar/var/core/Span.java rename to java/core/src/main/java/dev/varar/core/Span.java index 9fe89dcd..54cabf1a 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Span.java +++ b/java/core/src/main/java/dev/varar/core/Span.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; /** * A source position/range, anchored to UTF-16 code-unit offsets into a source diff --git a/java/var-core/src/main/java/com/oselvar/var/core/StepKind.java b/java/core/src/main/java/dev/varar/core/StepKind.java similarity index 87% rename from java/var-core/src/main/java/com/oselvar/var/core/StepKind.java rename to java/core/src/main/java/dev/varar/core/StepKind.java index fbbf4bb3..7e401d30 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/StepKind.java +++ b/java/core/src/main/java/dev/varar/core/StepKind.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; /** * The role a step definition plays, mirroring {@code concepts/sensors-and-actuators.md} @@ -14,7 +14,7 @@ *

The concepts arrange/act (given/when) remain useful narration in a document, but * they share one mechanism: a stimulus evolves state, a sensor observes it. * - *

Hoisted here from {@code com.oselvar.var} (Task 11's provisional home) per the + *

Hoisted here from {@code dev.varar} (Task 11's provisional home) per the * design doc's module map: step-role/registry logic belongs in {@code var-core} (the * engine), alongside {@link StepRole} and {@link Registry}. */ diff --git a/java/var-core/src/main/java/com/oselvar/var/core/StepRole.java b/java/core/src/main/java/dev/varar/core/StepRole.java similarity index 97% rename from java/var-core/src/main/java/com/oselvar/var/core/StepRole.java rename to java/core/src/main/java/dev/varar/core/StepRole.java index e0fec2ba..e70d8675 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/StepRole.java +++ b/java/core/src/main/java/dev/varar/core/StepRole.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.List; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/Structurer.java b/java/core/src/main/java/dev/varar/core/Structurer.java similarity index 99% rename from java/var-core/src/main/java/com/oselvar/var/core/Structurer.java rename to java/core/src/main/java/dev/varar/core/Structurer.java index 66b082cc..7ca83e43 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/Structurer.java +++ b/java/core/src/main/java/dev/varar/core/Structurer.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.ArrayList; import java.util.List; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/TableCells.java b/java/core/src/main/java/dev/varar/core/TableCells.java similarity index 98% rename from java/var-core/src/main/java/com/oselvar/var/core/TableCells.java rename to java/core/src/main/java/dev/varar/core/TableCells.java index 72ac64f5..c332ca4f 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/TableCells.java +++ b/java/core/src/main/java/dev/varar/core/TableCells.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import java.util.ArrayList; import java.util.List; diff --git a/java/var-core/src/main/java/com/oselvar/var/core/package-info.java b/java/core/src/main/java/dev/varar/core/package-info.java similarity index 87% rename from java/var-core/src/main/java/com/oselvar/var/core/package-info.java rename to java/core/src/main/java/dev/varar/core/package-info.java index 0528eef5..1178fb70 100644 --- a/java/var-core/src/main/java/com/oselvar/var/core/package-info.java +++ b/java/core/src/main/java/dev/varar/core/package-info.java @@ -3,4 +3,4 @@ * diffs, and conformance projections. No filesystem, network, time, or * test-framework dependency belongs in this package. */ -package com.oselvar.var.core; +package dev.varar.core; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/AstTest.java b/java/core/src/test/java/dev/varar/core/AstTest.java similarity index 98% rename from java/var-core/src/test/java/com/oselvar/var/core/AstTest.java rename to java/core/src/test/java/dev/varar/core/AstTest.java index 07a8a193..26e71534 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/AstTest.java +++ b/java/core/src/test/java/dev/varar/core/AstTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -9,7 +9,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; -/** Port of typescript/packages/var-core/src/ast.ts (type definitions only, no logic there). */ +/** Port of typescript/packages/core/src/ast.ts (type definitions only, no logic there). */ class AstTest { private static final Span SPAN = new Span(0, 5, 1, 1, 1, 6); diff --git a/java/var-core/src/test/java/com/oselvar/var/core/CanonicalJsonTest.java b/java/core/src/test/java/dev/varar/core/CanonicalJsonTest.java similarity index 99% rename from java/var-core/src/test/java/com/oselvar/var/core/CanonicalJsonTest.java rename to java/core/src/test/java/dev/varar/core/CanonicalJsonTest.java index 3b29eaea..e64837d8 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/CanonicalJsonTest.java +++ b/java/core/src/test/java/dev/varar/core/CanonicalJsonTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/CellDiffTest.java b/java/core/src/test/java/dev/varar/core/CellDiffTest.java similarity index 99% rename from java/var-core/src/test/java/com/oselvar/var/core/CellDiffTest.java rename to java/core/src/test/java/dev/varar/core/CellDiffTest.java index 02271992..87ea7eb4 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/CellDiffTest.java +++ b/java/core/src/test/java/dev/varar/core/CellDiffTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/ConformanceTest.java b/java/core/src/test/java/dev/varar/core/ConformanceTest.java similarity index 94% rename from java/var-core/src/test/java/com/oselvar/var/core/ConformanceTest.java rename to java/core/src/test/java/dev/varar/core/ConformanceTest.java index 76ea170b..b384e5f6 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/ConformanceTest.java +++ b/java/core/src/test/java/dev/varar/core/ConformanceTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -23,13 +23,13 @@ * CanonicalJson#canonicalStringify(Object)}, and asserts byte-for-byte equality with the * committed {@code golden/var-doc.json}. * - *

Port of the var-doc stage of {@code typescript/packages/var/tests/conformance.test.ts} - * and {@code python/packages/var/tests/test_conformance.py::test_var_doc_matches_golden}. + *

Port of the var-doc stage of {@code typescript/packages/varar/tests/conformance.test.ts} + * and {@code python/packages/varar/tests/test_conformance.py::test_var_doc_matches_golden}. * Plan/trace stages are later tasks (Milestones 3-4) — this class's golden-driven harness * only exercises var-doc; it also carries unit-level (non-golden) coverage of {@link * Conformance#toRegistryArtifact}/{@link Conformance#parameterTypeNames}, ported from * {@code conformance.test.ts}'s equivalent unit tests — the registry stage's own - * golden-driven gate lives in {@code com.oselvar.var.ConformanceTest} (the {@code var} + * golden-driven gate lives in {@code dev.varar.ConformanceTest} (the {@code var} * module), since it needs a real Java step-definition fixture per bundle, authored against * the {@code var} module's {@code Registrar}/{@code StepDefinitions} API. * @@ -38,7 +38,7 @@ */ class ConformanceTest { - // Maven runs tests with the module directory (java/var-core/) as the working + // Maven runs tests with the module directory (java/core/) as the working // directory, so the shared corpus — a sibling of java/, typescript/, python/ at the // repo root — is two levels up. Verified empirically: BUNDLES_DIR.toAbsolutePath() // resolves to .../conformance/bundles and bundleDirs() finds all 13 bundles. diff --git a/java/var-core/src/test/java/com/oselvar/var/core/DiagnosticsTest.java b/java/core/src/test/java/dev/varar/core/DiagnosticsTest.java similarity index 97% rename from java/var-core/src/test/java/com/oselvar/var/core/DiagnosticsTest.java rename to java/core/src/test/java/dev/varar/core/DiagnosticsTest.java index 69a5dd1a..e9ff64c9 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/DiagnosticsTest.java +++ b/java/core/src/test/java/dev/varar/core/DiagnosticsTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/DocStringDiffTest.java b/java/core/src/test/java/dev/varar/core/DocStringDiffTest.java similarity index 98% rename from java/var-core/src/test/java/com/oselvar/var/core/DocStringDiffTest.java rename to java/core/src/test/java/dev/varar/core/DocStringDiffTest.java index cde63804..d12bdd6a 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/DocStringDiffTest.java +++ b/java/core/src/test/java/dev/varar/core/DocStringDiffTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/DriftTest.java b/java/core/src/test/java/dev/varar/core/DriftTest.java similarity index 99% rename from java/var-core/src/test/java/com/oselvar/var/core/DriftTest.java rename to java/core/src/test/java/dev/varar/core/DriftTest.java index f134241e..30d4ba79 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/DriftTest.java +++ b/java/core/src/test/java/dev/varar/core/DriftTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/ExecuteTest.java b/java/core/src/test/java/dev/varar/core/ExecuteTest.java similarity index 99% rename from java/var-core/src/test/java/com/oselvar/var/core/ExecuteTest.java rename to java/core/src/test/java/dev/varar/core/ExecuteTest.java index defcd043..fffb849c 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/ExecuteTest.java +++ b/java/core/src/test/java/dev/varar/core/ExecuteTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,7 +34,7 @@ class ExecuteTest { /** - * Minimal test-local functional interfaces shaped like {@code com.oselvar.var.StateBinder}'s + * Minimal test-local functional interfaces shaped like {@code dev.varar.StateBinder}'s * {@code Context0/1/2}/{@code Sensor0/1/2} — WITHOUT importing them. {@code var-core} has no * dependency on the {@code var} module (hexagonal architecture: the core never imports the * facade), and {@link Execute} invokes a handler purely via reflection matched by arity, diff --git a/java/var-core/src/test/java/com/oselvar/var/core/FailureTest.java b/java/core/src/test/java/dev/varar/core/FailureTest.java similarity index 99% rename from java/var-core/src/test/java/com/oselvar/var/core/FailureTest.java rename to java/core/src/test/java/dev/varar/core/FailureTest.java index d5663a8c..d9813837 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/FailureTest.java +++ b/java/core/src/test/java/dev/varar/core/FailureTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/MatcherTest.java b/java/core/src/test/java/dev/varar/core/MatcherTest.java similarity index 99% rename from java/var-core/src/test/java/com/oselvar/var/core/MatcherTest.java rename to java/core/src/test/java/dev/varar/core/MatcherTest.java index 3479981a..517ad44e 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/MatcherTest.java +++ b/java/core/src/test/java/dev/varar/core/MatcherTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/ParamDiffTest.java b/java/core/src/test/java/dev/varar/core/ParamDiffTest.java similarity index 98% rename from java/var-core/src/test/java/com/oselvar/var/core/ParamDiffTest.java rename to java/core/src/test/java/dev/varar/core/ParamDiffTest.java index fa3f22fc..ca5eb5d8 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/ParamDiffTest.java +++ b/java/core/src/test/java/dev/varar/core/ParamDiffTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/ParseTest.java b/java/core/src/test/java/dev/varar/core/ParseTest.java similarity index 83% rename from java/var-core/src/test/java/com/oselvar/var/core/ParseTest.java rename to java/core/src/test/java/dev/varar/core/ParseTest.java index b9b3acec..d9b285ea 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/ParseTest.java +++ b/java/core/src/test/java/dev/varar/core/ParseTest.java @@ -1,12 +1,12 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; -import com.oselvar.var.core.Ast.VarDoc; +import dev.varar.core.Ast.VarDoc; import java.util.List; import org.junit.jupiter.api.Test; -/** Port of {@code typescript/packages/var-core/tests/parse.test.ts}. */ +/** Port of {@code typescript/packages/core/tests/parse.test.ts}. */ class ParseTest { @Test diff --git a/java/var-core/src/test/java/com/oselvar/var/core/PlanTest.java b/java/core/src/test/java/dev/varar/core/PlanTest.java similarity index 99% rename from java/var-core/src/test/java/com/oselvar/var/core/PlanTest.java rename to java/core/src/test/java/dev/varar/core/PlanTest.java index 3beca105..50279388 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/PlanTest.java +++ b/java/core/src/test/java/dev/varar/core/PlanTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/RegistryTest.java b/java/core/src/test/java/dev/varar/core/RegistryTest.java similarity index 99% rename from java/var-core/src/test/java/com/oselvar/var/core/RegistryTest.java rename to java/core/src/test/java/dev/varar/core/RegistryTest.java index 62a061cb..794af775 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/RegistryTest.java +++ b/java/core/src/test/java/dev/varar/core/RegistryTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/ScannerTest.java b/java/core/src/test/java/dev/varar/core/ScannerTest.java similarity index 95% rename from java/var-core/src/test/java/com/oselvar/var/core/ScannerTest.java rename to java/core/src/test/java/dev/varar/core/ScannerTest.java index c4bc5518..4b50bcf6 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/ScannerTest.java +++ b/java/core/src/test/java/dev/varar/core/ScannerTest.java @@ -1,19 +1,19 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.oselvar.var.core.Ast.Block; -import com.oselvar.var.core.Ast.Blockquote; -import com.oselvar.var.core.Ast.Fence; -import com.oselvar.var.core.Ast.Heading; -import com.oselvar.var.core.Ast.ListItem; -import com.oselvar.var.core.Ast.Paragraph; -import com.oselvar.var.core.Ast.SegmentOffset; -import com.oselvar.var.core.Ast.Table; -import com.oselvar.var.core.Ast.ThematicBreak; +import dev.varar.core.Ast.Block; +import dev.varar.core.Ast.Blockquote; +import dev.varar.core.Ast.Fence; +import dev.varar.core.Ast.Heading; +import dev.varar.core.Ast.ListItem; +import dev.varar.core.Ast.Paragraph; +import dev.varar.core.Ast.SegmentOffset; +import dev.varar.core.Ast.Table; +import dev.varar.core.Ast.ThematicBreak; import java.util.List; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; @@ -21,8 +21,8 @@ import org.junit.jupiter.params.provider.ValueSource; /** - * Port of {@code typescript/packages/var-core/tests/scanner.test.ts}, cross-checked against - * {@code python/packages/var-core/tests/test_scanner.py}. + * Port of {@code typescript/packages/core/tests/scanner.test.ts}, cross-checked against + * {@code python/packages/core/tests/test_scanner.py}. * *

The two TS cases that assert on {@code cellSpans} via {@code parse(...)} (table-cell source * spans) are translated against {@link Scanner#scan} directly instead: {@code Parse.java} does not diff --git a/java/var-core/src/test/java/com/oselvar/var/core/SentencesTest.java b/java/core/src/test/java/dev/varar/core/SentencesTest.java similarity index 98% rename from java/var-core/src/test/java/com/oselvar/var/core/SentencesTest.java rename to java/core/src/test/java/dev/varar/core/SentencesTest.java index 67f7daab..ce0c997d 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/SentencesTest.java +++ b/java/core/src/test/java/dev/varar/core/SentencesTest.java @@ -1,9 +1,9 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.oselvar.var.core.Sentences.Sentence; +import dev.varar.core.Sentences.Sentence; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/SmokeTest.java b/java/core/src/test/java/dev/varar/core/SmokeTest.java similarity index 90% rename from java/var-core/src/test/java/com/oselvar/var/core/SmokeTest.java rename to java/core/src/test/java/dev/varar/core/SmokeTest.java index 75005a63..27c5562e 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/SmokeTest.java +++ b/java/core/src/test/java/dev/varar/core/SmokeTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/SpanTest.java b/java/core/src/test/java/dev/varar/core/SpanTest.java similarity index 93% rename from java/var-core/src/test/java/com/oselvar/var/core/SpanTest.java rename to java/core/src/test/java/dev/varar/core/SpanTest.java index 600d24ca..a386d4b7 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/SpanTest.java +++ b/java/core/src/test/java/dev/varar/core/SpanTest.java @@ -1,10 +1,10 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; -/** Port of typescript/packages/var-core/tests/span.test.ts. */ +/** Port of typescript/packages/core/tests/span.test.ts. */ class SpanTest { @Test diff --git a/java/var-core/src/test/java/com/oselvar/var/core/StepRoleTest.java b/java/core/src/test/java/dev/varar/core/StepRoleTest.java similarity index 97% rename from java/var-core/src/test/java/com/oselvar/var/core/StepRoleTest.java rename to java/core/src/test/java/dev/varar/core/StepRoleTest.java index be08bf88..e49050aa 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/StepRoleTest.java +++ b/java/core/src/test/java/dev/varar/core/StepRoleTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/java/var-core/src/test/java/com/oselvar/var/core/StructurerTest.java b/java/core/src/test/java/dev/varar/core/StructurerTest.java similarity index 94% rename from java/var-core/src/test/java/com/oselvar/var/core/StructurerTest.java rename to java/core/src/test/java/dev/varar/core/StructurerTest.java index c2a04184..9001536e 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/StructurerTest.java +++ b/java/core/src/test/java/dev/varar/core/StructurerTest.java @@ -1,17 +1,17 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.oselvar.var.core.Ast.Example; -import com.oselvar.var.core.Ast.VarDoc; +import dev.varar.core.Ast.Example; +import dev.varar.core.Ast.VarDoc; import java.util.List; import org.junit.jupiter.api.Test; /** - * Port of {@code typescript/packages/var-core/tests/structurer.test.ts}, cross-checked against - * {@code python/packages/var-core/tests/test_structurer.py}. + * Port of {@code typescript/packages/core/tests/structurer.test.ts}, cross-checked against + * {@code python/packages/core/tests/test_structurer.py}. */ class StructurerTest { diff --git a/java/var-core/src/test/java/com/oselvar/var/core/TableCellsTest.java b/java/core/src/test/java/dev/varar/core/TableCellsTest.java similarity index 96% rename from java/var-core/src/test/java/com/oselvar/var/core/TableCellsTest.java rename to java/core/src/test/java/dev/varar/core/TableCellsTest.java index 0b79e12b..9da8498c 100644 --- a/java/var-core/src/test/java/com/oselvar/var/core/TableCellsTest.java +++ b/java/core/src/test/java/dev/varar/core/TableCellsTest.java @@ -1,15 +1,15 @@ -package com.oselvar.var.core; +package dev.varar.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.oselvar.var.core.TableCells.RowCells; +import dev.varar.core.TableCells.RowCells; import java.util.List; import org.junit.jupiter.api.Test; /** * Port of the table-row-cell-parsing behavior of {@code table-cells.ts}, cross-checked against - * {@code python/packages/var-core/tests/test_table_cells.py} (which in turn ports the table-cell + * {@code python/packages/core/tests/test_table_cells.py} (which in turn ports the table-cell * span cases of {@code var-core/tests/scanner.test.ts}). */ class TableCellsTest { diff --git a/java/var-junit/pom.xml b/java/junit/pom.xml similarity index 91% rename from java/var-junit/pom.xml rename to java/junit/pom.xml index b0af2437..36b50900 100644 --- a/java/var-junit/pom.xml +++ b/java/junit/pom.xml @@ -5,12 +5,12 @@ 4.0.0 - com.oselvar - var-parent + dev.varar + parent 0.4.3-SNAPSHOT - var-junit + junit jar var-junit — JUnit Platform TestEngine @@ -22,13 +22,13 @@ - com.oselvar - var-runner + dev.varar + runner ${project.version} - com.oselvar - var-config + dev.varar + config ${project.version} @@ -57,7 +57,7 @@ ${project.basedir}/../../conformance/bundles diff --git a/java/var-kotlin/src/main/kotlin/com/oselvar/varkt/DefineState.kt b/java/kotlin/src/main/kotlin/dev/varar/kotlin/DefineState.kt similarity index 98% rename from java/var-kotlin/src/main/kotlin/com/oselvar/varkt/DefineState.kt rename to java/kotlin/src/main/kotlin/dev/varar/kotlin/DefineState.kt index 6854fda4..96647826 100644 --- a/java/var-kotlin/src/main/kotlin/com/oselvar/varkt/DefineState.kt +++ b/java/kotlin/src/main/kotlin/dev/varar/kotlin/DefineState.kt @@ -5,12 +5,12 @@ // call site, exactly as the @RegistrarGlue on StepsScope does for the members. @file:RegistrarGlue -package com.oselvar.varkt +package dev.varar.kotlin -import com.oselvar.`var`.RegistrarGlue -import com.oselvar.`var`.State -import com.oselvar.`var`.StateBinder -import com.oselvar.`var`.StepDefinitions +import dev.varar.RegistrarGlue +import dev.varar.State +import dev.varar.StateBinder +import dev.varar.StepDefinitions import java.util.function.Function import java.util.function.Supplier import kotlinx.coroutines.runBlocking diff --git a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/ConformanceTest.kt b/java/kotlin/src/test/kotlin/dev/varar/kotlin/ConformanceTest.kt similarity index 71% rename from java/var-kotlin/src/test/kotlin/com/oselvar/varkt/ConformanceTest.kt rename to java/kotlin/src/test/kotlin/dev/varar/kotlin/ConformanceTest.kt index 34ed6e31..eb66c26d 100644 --- a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/ConformanceTest.kt +++ b/java/kotlin/src/test/kotlin/dev/varar/kotlin/ConformanceTest.kt @@ -1,24 +1,24 @@ -package com.oselvar.varkt +package dev.varar.kotlin -import com.oselvar.`var`.RegistryRegistrar -import com.oselvar.`var`.StepDefinitions -import com.oselvar.`var`.core.CanonicalJson -import com.oselvar.`var`.core.Conformance -import com.oselvar.varkt.conformance.bundle01.steps as bundle01Steps -import com.oselvar.varkt.conformance.bundle02.steps as bundle02Steps -import com.oselvar.varkt.conformance.bundle03.steps as bundle03Steps -import com.oselvar.varkt.conformance.bundle04.steps as bundle04Steps -import com.oselvar.varkt.conformance.bundle05.steps as bundle05Steps -import com.oselvar.varkt.conformance.bundle06.steps as bundle06Steps -import com.oselvar.varkt.conformance.bundle07.steps as bundle07Steps -import com.oselvar.varkt.conformance.bundle08.steps as bundle08Steps -import com.oselvar.varkt.conformance.bundle09.steps as bundle09Steps -import com.oselvar.varkt.conformance.bundle10.steps as bundle10Steps -import com.oselvar.varkt.conformance.bundle11.steps as bundle11Steps -import com.oselvar.varkt.conformance.bundle12.steps as bundle12Steps -import com.oselvar.varkt.conformance.bundle13.steps as bundle13Steps -import com.oselvar.varkt.conformance.bundle14.steps as bundle14Steps -import com.oselvar.varkt.conformance.bundle15.steps as bundle15Steps +import dev.varar.RegistryRegistrar +import dev.varar.StepDefinitions +import dev.varar.core.CanonicalJson +import dev.varar.core.Conformance +import dev.varar.kotlin.conformance.bundle01.steps as bundle01Steps +import dev.varar.kotlin.conformance.bundle02.steps as bundle02Steps +import dev.varar.kotlin.conformance.bundle03.steps as bundle03Steps +import dev.varar.kotlin.conformance.bundle04.steps as bundle04Steps +import dev.varar.kotlin.conformance.bundle05.steps as bundle05Steps +import dev.varar.kotlin.conformance.bundle06.steps as bundle06Steps +import dev.varar.kotlin.conformance.bundle07.steps as bundle07Steps +import dev.varar.kotlin.conformance.bundle08.steps as bundle08Steps +import dev.varar.kotlin.conformance.bundle09.steps as bundle09Steps +import dev.varar.kotlin.conformance.bundle10.steps as bundle10Steps +import dev.varar.kotlin.conformance.bundle11.steps as bundle11Steps +import dev.varar.kotlin.conformance.bundle12.steps as bundle12Steps +import dev.varar.kotlin.conformance.bundle13.steps as bundle13Steps +import dev.varar.kotlin.conformance.bundle14.steps as bundle14Steps +import dev.varar.kotlin.conformance.bundle15.steps as bundle15Steps import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path @@ -55,7 +55,7 @@ class ConformanceTest { } companion object { - // Maven runs tests with the module directory (java/var-kotlin/) as the + // Maven runs tests with the module directory (java/kotlin/) as the // working directory; the shared corpus is two levels up, same as // java/var's own ConformanceTest. private val BUNDLES_DIR: Path = Paths.get("..", "..", "conformance", "bundles") diff --git a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/DefineStateTest.kt b/java/kotlin/src/test/kotlin/dev/varar/kotlin/DefineStateTest.kt similarity index 97% rename from java/var-kotlin/src/test/kotlin/com/oselvar/varkt/DefineStateTest.kt rename to java/kotlin/src/test/kotlin/dev/varar/kotlin/DefineStateTest.kt index 02010d8e..f03dc874 100644 --- a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/DefineStateTest.kt +++ b/java/kotlin/src/test/kotlin/dev/varar/kotlin/DefineStateTest.kt @@ -1,8 +1,8 @@ -package com.oselvar.varkt +package dev.varar.kotlin -import com.oselvar.`var`.RegistryRegistrar -import com.oselvar.`var`.StepDefinitions -import com.oselvar.`var`.core.StepKind +import dev.varar.RegistryRegistrar +import dev.varar.StepDefinitions +import dev.varar.core.StepKind import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertTrue diff --git a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/ExecuteIntegrationTest.kt b/java/kotlin/src/test/kotlin/dev/varar/kotlin/ExecuteIntegrationTest.kt similarity index 89% rename from java/var-kotlin/src/test/kotlin/com/oselvar/varkt/ExecuteIntegrationTest.kt rename to java/kotlin/src/test/kotlin/dev/varar/kotlin/ExecuteIntegrationTest.kt index 155a4c02..277e3cf8 100644 --- a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/ExecuteIntegrationTest.kt +++ b/java/kotlin/src/test/kotlin/dev/varar/kotlin/ExecuteIntegrationTest.kt @@ -1,10 +1,10 @@ -package com.oselvar.varkt +package dev.varar.kotlin -import com.oselvar.`var`.RegistryRegistrar -import com.oselvar.`var`.core.CellDiff -import com.oselvar.`var`.core.Execute -import com.oselvar.`var`.core.Parse -import com.oselvar.`var`.core.Plan +import dev.varar.RegistryRegistrar +import dev.varar.core.CellDiff +import dev.varar.core.Execute +import dev.varar.core.Parse +import dev.varar.core.Plan import java.util.function.Function import kotlinx.coroutines.delay import org.junit.jupiter.api.Assertions.assertThrows diff --git a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/JUnitEngineSmokeTest.kt b/java/kotlin/src/test/kotlin/dev/varar/kotlin/JUnitEngineSmokeTest.kt similarity index 93% rename from java/var-kotlin/src/test/kotlin/com/oselvar/varkt/JUnitEngineSmokeTest.kt rename to java/kotlin/src/test/kotlin/dev/varar/kotlin/JUnitEngineSmokeTest.kt index a05bb3b6..35218529 100644 --- a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/JUnitEngineSmokeTest.kt +++ b/java/kotlin/src/test/kotlin/dev/varar/kotlin/JUnitEngineSmokeTest.kt @@ -1,6 +1,6 @@ -package com.oselvar.varkt +package dev.varar.kotlin -import com.oselvar.`var`.junit.ConfigBridge +import dev.varar.junit.ConfigBridge import java.nio.file.Files import java.nio.file.Path import org.junit.jupiter.api.Assertions.assertEquals @@ -25,11 +25,11 @@ class JUnitEngineSmokeTest { private fun runSpec(dir: Path, body: String) = Files.writeString(dir.resolve("cukes.md"), body).let { spec -> Files.writeString( - dir.resolve("var.config.json"), + dir.resolve("varar.config.json"), """ { "docs": { "include": ["cukes.md"], "exclude": [] }, - "steps": ["com.oselvar.varkt.fixtures.CukeSteps"] + "steps": ["dev.varar.kotlin.fixtures.CukeSteps"] } """ .trimIndent(), diff --git a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/ParameterTypeTest.kt b/java/kotlin/src/test/kotlin/dev/varar/kotlin/ParameterTypeTest.kt similarity index 91% rename from java/var-kotlin/src/test/kotlin/com/oselvar/varkt/ParameterTypeTest.kt rename to java/kotlin/src/test/kotlin/dev/varar/kotlin/ParameterTypeTest.kt index 473c60f3..2c3b83ca 100644 --- a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/ParameterTypeTest.kt +++ b/java/kotlin/src/test/kotlin/dev/varar/kotlin/ParameterTypeTest.kt @@ -1,8 +1,8 @@ -package com.oselvar.varkt +package dev.varar.kotlin -import com.oselvar.`var`.RegistryRegistrar -import com.oselvar.`var`.core.Parse -import com.oselvar.`var`.core.Plan +import dev.varar.RegistryRegistrar +import dev.varar.core.Parse +import dev.varar.core.Plan import io.cucumber.cucumberexpressions.UndefinedParameterTypeException import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows diff --git a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/StepLoaderKotlinTest.kt b/java/kotlin/src/test/kotlin/dev/varar/kotlin/StepLoaderKotlinTest.kt similarity index 87% rename from java/var-kotlin/src/test/kotlin/com/oselvar/varkt/StepLoaderKotlinTest.kt rename to java/kotlin/src/test/kotlin/dev/varar/kotlin/StepLoaderKotlinTest.kt index 2ee51f4e..25fd1107 100644 --- a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/StepLoaderKotlinTest.kt +++ b/java/kotlin/src/test/kotlin/dev/varar/kotlin/StepLoaderKotlinTest.kt @@ -1,6 +1,6 @@ -package com.oselvar.varkt +package dev.varar.kotlin -import com.oselvar.`var`.runner.StepLoader +import dev.varar.runner.StepLoader import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertTrue @@ -12,7 +12,7 @@ class StepLoaderKotlinTest { fun `loads a top-level val steps via the file facade class`() { val loaded = StepLoader.loadSteps( - listOf("com.oselvar.varkt.fixtures.CukeSteps"), + listOf("dev.varar.kotlin.fixtures.CukeSteps"), javaClass.classLoader, ) diff --git a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/crosspkg/CrossPackageTest.kt b/java/kotlin/src/test/kotlin/dev/varar/kotlin/crosspkg/CrossPackageTest.kt similarity index 84% rename from java/var-kotlin/src/test/kotlin/com/oselvar/varkt/crosspkg/CrossPackageTest.kt rename to java/kotlin/src/test/kotlin/dev/varar/kotlin/crosspkg/CrossPackageTest.kt index 4fa3bb79..26d1c3d8 100644 --- a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/crosspkg/CrossPackageTest.kt +++ b/java/kotlin/src/test/kotlin/dev/varar/kotlin/crosspkg/CrossPackageTest.kt @@ -1,14 +1,14 @@ -package com.oselvar.varkt.crosspkg +package dev.varar.kotlin.crosspkg -import com.oselvar.`var`.RegistryRegistrar -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps -import com.oselvar.varkt.stimulus +import dev.varar.RegistryRegistrar +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.stimulus import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test /** - * Proves the approved author API resolves from OUTSIDE `com.oselvar.varkt` — the situation every + * Proves the approved author API resolves from OUTSIDE `dev.varar.kotlin` — the situation every * real `.steps.kt` file is in. The zero-parameter overloads are `StepsScope` members (no import * beyond `steps`), but the capturing arities are top-level extension functions, so an author's file * needs the four imports above (IDE auto-import adds them). `DefineStateTest` lives in the DSL's diff --git a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/fixtures/cukes.steps.kt b/java/kotlin/src/test/kotlin/dev/varar/kotlin/fixtures/cukes.steps.kt similarity index 73% rename from java/var-kotlin/src/test/kotlin/com/oselvar/varkt/fixtures/cukes.steps.kt rename to java/kotlin/src/test/kotlin/dev/varar/kotlin/fixtures/cukes.steps.kt index eb2d0cf6..6e20bac1 100644 --- a/java/var-kotlin/src/test/kotlin/com/oselvar/varkt/fixtures/cukes.steps.kt +++ b/java/kotlin/src/test/kotlin/dev/varar/kotlin/fixtures/cukes.steps.kt @@ -1,10 +1,10 @@ @file:JvmName("CukeSteps") -package com.oselvar.varkt.fixtures +package dev.varar.kotlin.fixtures -import com.oselvar.varkt.sensor -import com.oselvar.varkt.steps -import com.oselvar.varkt.stimulus +import dev.varar.kotlin.sensor +import dev.varar.kotlin.steps +import dev.varar.kotlin.stimulus data class CukeCtx(val cukes: Int = 0) diff --git a/java/pom.xml b/java/pom.xml index 0fd110da..c332d8fa 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -4,8 +4,8 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.oselvar - var-parent + dev.varar + parent 0.4.3-SNAPSHOT pom @@ -16,7 +16,7 @@ typescript/ (reference implementation) and python/ (second port). - https://github.com/oselvar/var + https://github.com/oselvar/varar MIT License @@ -32,20 +32,20 @@ - scm:git:https://github.com/oselvar/var.git - scm:git:git@github.com:oselvar/var.git - https://github.com/oselvar/var + scm:git:https://github.com/oselvar/varar.git + scm:git:git@github.com:oselvar/varar.git + https://github.com/oselvar/varar HEAD - var-core - var-config - var - var-runner - var-junit - var-kotlin - var-kotest + core + config + varar + runner + junit + kotlin + kotest @@ -123,7 +123,7 @@ The JVM sample projects in ../examples are covered too, via the examples -> ../examples symlink next to this pom: spotless can't scan outside a module's basedir, and the samples' own builds must stay - formatter-free (they sync 1:1 to the public var-examples repo). The + formatter-free (they sync 1:1 to the public varar-examples repo). The examples/*/src includes only match here in the parent — the modules inherit them but have no examples/ dir, so they're a no-op there. --> diff --git a/java/var-runner/pom.xml b/java/runner/pom.xml similarity index 82% rename from java/var-runner/pom.xml rename to java/runner/pom.xml index 7a9d0244..542c2e6d 100644 --- a/java/var-runner/pom.xml +++ b/java/runner/pom.xml @@ -5,12 +5,12 @@ 4.0.0 - com.oselvar - var-parent + dev.varar + parent 0.4.3-SNAPSHOT - var-runner + runner jar var-runner (Java) — shared imperative shell @@ -22,8 +22,8 @@ - com.oselvar - var + dev.varar + varar ${project.version} diff --git a/java/var-runner/src/main/java/com/oselvar/var/runner/BaselineStores.java b/java/runner/src/main/java/dev/varar/runner/BaselineStores.java similarity index 78% rename from java/var-runner/src/main/java/com/oselvar/var/runner/BaselineStores.java rename to java/runner/src/main/java/dev/varar/runner/BaselineStores.java index e6222e88..5eb21302 100644 --- a/java/var-runner/src/main/java/com/oselvar/var/runner/BaselineStores.java +++ b/java/runner/src/main/java/dev/varar/runner/BaselineStores.java @@ -1,6 +1,6 @@ -package com.oselvar.var.runner; +package dev.varar.runner; -import com.oselvar.var.core.Drift; +import dev.varar.core.Drift; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; @@ -8,16 +8,16 @@ /** * The Node/CLI-equivalent filesystem {@link Drift.BaselineStore}: the committed drift baseline - * lives at the project root as {@code var.lock.json}. The core owns the format; this adapter only + * lives at the project root as {@code varar.lock.json}. The core owns the format; this adapter only * reads and writes the raw text. */ public final class BaselineStores { private BaselineStores() {} - /** A store backed by {@code /var.lock.json}. */ + /** A store backed by {@code /varar.lock.json}. */ public static Drift.BaselineStore file(Path root) { - Path path = root.resolve("var.lock.json"); + Path path = root.resolve("varar.lock.json"); return new Drift.BaselineStore() { @Override public String read() { diff --git a/java/var-runner/src/main/java/com/oselvar/var/runner/Discovery.java b/java/runner/src/main/java/dev/varar/runner/Discovery.java similarity index 98% rename from java/var-runner/src/main/java/com/oselvar/var/runner/Discovery.java rename to java/runner/src/main/java/dev/varar/runner/Discovery.java index e719df92..2c136000 100644 --- a/java/var-runner/src/main/java/com/oselvar/var/runner/Discovery.java +++ b/java/runner/src/main/java/dev/varar/runner/Discovery.java @@ -1,4 +1,4 @@ -package com.oselvar.var.runner; +package dev.varar.runner; import java.io.IOException; import java.io.UncheckedIOException; @@ -15,7 +15,7 @@ *

{@link #matchSpec} and {@link #findSpecs} share a single glob-to-regex compiler * ({@link #globToRegex}) so the two never independently reimplement (and silently drift from * each other on) the same matching rules — same discipline as {@link - * com.oselvar.var.core.CanonicalJson CanonicalJson}'s + * dev.varar.core.CanonicalJson CanonicalJson}'s * hand-rolled-not-library decision: Java has no {@code Path.full_match} * (Python 3.13's {@code pathlib.Path.full_match}/PEP 428) and {@code * FileSystem.getPathMatcher("glob:...")}'s {@code **} semantics differ from this project's diff --git a/java/var-runner/src/main/java/com/oselvar/var/runner/Render.java b/java/runner/src/main/java/dev/varar/runner/Render.java similarity index 96% rename from java/var-runner/src/main/java/com/oselvar/var/runner/Render.java rename to java/runner/src/main/java/dev/varar/runner/Render.java index 15ddb39f..889fd18e 100644 --- a/java/var-runner/src/main/java/com/oselvar/var/runner/Render.java +++ b/java/runner/src/main/java/dev/varar/runner/Render.java @@ -1,7 +1,7 @@ -package com.oselvar.var.runner; +package dev.varar.runner; -import com.oselvar.var.core.Failure; -import com.oselvar.var.core.Result; +import dev.varar.core.Failure; +import dev.varar.core.Result; import java.util.stream.Collectors; /** diff --git a/java/var-runner/src/main/java/com/oselvar/var/runner/Run.java b/java/runner/src/main/java/dev/varar/runner/Run.java similarity index 93% rename from java/var-runner/src/main/java/com/oselvar/var/runner/Run.java rename to java/runner/src/main/java/dev/varar/runner/Run.java index 8c72098a..fbf89929 100644 --- a/java/var-runner/src/main/java/com/oselvar/var/runner/Run.java +++ b/java/runner/src/main/java/dev/varar/runner/Run.java @@ -1,10 +1,10 @@ -package com.oselvar.var.runner; +package dev.varar.runner; -import com.oselvar.var.core.Diagnostics; -import com.oselvar.var.core.Execute; -import com.oselvar.var.core.Parse; -import com.oselvar.var.core.Plan; -import com.oselvar.var.core.Registry; +import dev.varar.core.Diagnostics; +import dev.varar.core.Execute; +import dev.varar.core.Parse; +import dev.varar.core.Plan; +import dev.varar.core.Registry; import java.util.ArrayList; import java.util.List; import java.util.function.Function; diff --git a/java/var-runner/src/main/java/com/oselvar/var/runner/StepLoader.java b/java/runner/src/main/java/dev/varar/runner/StepLoader.java similarity index 97% rename from java/var-runner/src/main/java/com/oselvar/var/runner/StepLoader.java rename to java/runner/src/main/java/dev/varar/runner/StepLoader.java index 14b64658..025ff3f3 100644 --- a/java/var-runner/src/main/java/com/oselvar/var/runner/StepLoader.java +++ b/java/runner/src/main/java/dev/varar/runner/StepLoader.java @@ -1,9 +1,9 @@ -package com.oselvar.var.runner; +package dev.varar.runner; -import com.oselvar.var.RegistryRegistrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; -import com.oselvar.var.core.Registry; +import dev.varar.RegistryRegistrar; +import dev.varar.State; +import dev.varar.StepDefinitions; +import dev.varar.core.Registry; import io.cucumber.cucumberexpressions.ParameterTypeRegistry; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -20,7 +20,7 @@ /** * Reflectively loads a run's {@link StepDefinitions} classes, merges each one's own * {@link Registry} into a single shared one, and builds the per-file {@code - * createContext} function {@link com.oselvar.var.core.Execute} expects. + * createContext} function {@link dev.varar.core.Execute} expects. * *

One {@code RegistryRegistrar} per {@code StepDefinitions} class

* @@ -68,7 +68,7 @@ * custom parameter type if that type were re-registered on the accumulator first. But * {@code ParameterTypeRegistry.getParameterTypes()} — confirmed via {@code javap} — is * package-private in {@code io.cucumber.cucumberexpressions}, so there is no accessible - * way from {@code com.oselvar.var.runner} to enumerate a source registry's custom + * way from {@code dev.varar.runner} to enumerate a source registry's custom * parameter types in order to re-register them. Recompiling is unnecessary anyway: each * {@link Registry.StepRegistration#compiled} is already a fully-compiled {@code * Expression}, self-contained and independent of any registry object once built (it was @@ -80,7 +80,7 @@ * expression is a genuine authoring bug this merge should still catch. {@code * customParameterTypes()} is merged the same way — plain concatenation, no * recompilation — since it exists purely to be projected into the registry - * conformance artifact ({@link com.oselvar.var.core.Conformance#toRegistryArtifact}), + * conformance artifact ({@link dev.varar.core.Conformance#toRegistryArtifact}), * never consulted to compile anything itself. Duplicate custom parameter-type names are * rejected (two classes accidentally registering the same name is a genuine authoring * bug this merge should catch). diff --git a/java/var-runner/src/main/java/com/oselvar/var/runner/package-info.java b/java/runner/src/main/java/dev/varar/runner/package-info.java similarity index 91% rename from java/var-runner/src/main/java/com/oselvar/var/runner/package-info.java rename to java/runner/src/main/java/dev/varar/runner/package-info.java index d85d006d..02655bd8 100644 --- a/java/var-runner/src/main/java/com/oselvar/var/runner/package-info.java +++ b/java/runner/src/main/java/dev/varar/runner/package-info.java @@ -4,4 +4,4 @@ * an adapter (e.g. var-junit) that touches the filesystem/classpath. Deliberately * free of any JUnit-Platform dependency so it can be reused by future adapters. */ -package com.oselvar.var.runner; +package dev.varar.runner; diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/DiscoveryTest.java b/java/runner/src/test/java/dev/varar/runner/DiscoveryTest.java similarity index 98% rename from java/var-runner/src/test/java/com/oselvar/var/runner/DiscoveryTest.java rename to java/runner/src/test/java/dev/varar/runner/DiscoveryTest.java index 9cb4595c..bec91058 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/DiscoveryTest.java +++ b/java/runner/src/test/java/dev/varar/runner/DiscoveryTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.runner; +package dev.varar.runner; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -12,7 +12,7 @@ import org.junit.jupiter.api.io.TempDir; /** - * Translation of {@code python/packages/var-runner/tests/test_discovery.py} — same + * Translation of {@code python/packages/runner/tests/test_discovery.py} — same * glob-matching semantics ({@code /**\/}, leading {@code **\/}, bare {@code **}, {@code *}, * {@code ?}), same {@link Discovery#matchSpec}/{@link Discovery#findSpecs} split. See * {@code discovery.py}'s {@code _glob_to_regex} docstring for the semantics being ported. diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/DuplicateStateSteps.java b/java/runner/src/test/java/dev/varar/runner/DuplicateStateSteps.java similarity index 86% rename from java/var-runner/src/test/java/com/oselvar/var/runner/DuplicateStateSteps.java rename to java/runner/src/test/java/dev/varar/runner/DuplicateStateSteps.java index c7f9905d..64a17710 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/DuplicateStateSteps.java +++ b/java/runner/src/test/java/dev/varar/runner/DuplicateStateSteps.java @@ -1,8 +1,8 @@ -package com.oselvar.var.runner; +package dev.varar.runner; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** * Two static factories in ONE source file: both load units' steps report the diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/RenderTest.java b/java/runner/src/test/java/dev/varar/runner/RenderTest.java similarity index 91% rename from java/var-runner/src/test/java/com/oselvar/var/runner/RenderTest.java rename to java/runner/src/test/java/dev/varar/runner/RenderTest.java index b74fb430..a338be09 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/RenderTest.java +++ b/java/runner/src/test/java/dev/varar/runner/RenderTest.java @@ -1,21 +1,21 @@ -package com.oselvar.var.runner; +package dev.varar.runner; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.oselvar.var.core.CellDiff; -import com.oselvar.var.core.DocStringDiff; -import com.oselvar.var.core.Plan; -import com.oselvar.var.runner.StepLoader.LoadedSteps; -import com.oselvar.var.runner.fixtures.BoomSteps; -import com.oselvar.var.runner.fixtures.GreetingSteps; -import com.oselvar.var.runner.fixtures.WidgetSteps; +import dev.varar.core.CellDiff; +import dev.varar.core.DocStringDiff; +import dev.varar.core.Plan; +import dev.varar.runner.StepLoader.LoadedSteps; +import dev.varar.runner.fixtures.BoomSteps; +import dev.varar.runner.fixtures.GreetingSteps; +import dev.varar.runner.fixtures.WidgetSteps; import java.util.List; import org.junit.jupiter.api.Test; /** * Confirms {@link Render#renderFailure} is a pure formatter over {@link - * com.oselvar.var.core.Failure#toFailure}'s {@link com.oselvar.var.core.Result.ExampleFailure} + * dev.varar.core.Failure#toFailure}'s {@link dev.varar.core.Result.ExampleFailure} * payload — driven by REAL exceptions the core pipeline produces via {@link Run#planSpec} * + {@link Run#examplesWithRuns} (same standard as {@code RunTest}), not hand-built * {@code Result.ExampleFailure} values. diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/RunTest.java b/java/runner/src/test/java/dev/varar/runner/RunTest.java similarity index 93% rename from java/var-runner/src/test/java/com/oselvar/var/runner/RunTest.java rename to java/runner/src/test/java/dev/varar/runner/RunTest.java index d6084346..94d8575a 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/RunTest.java +++ b/java/runner/src/test/java/dev/varar/runner/RunTest.java @@ -1,16 +1,16 @@ -package com.oselvar.var.runner; +package dev.varar.runner; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.oselvar.var.core.CellDiff; -import com.oselvar.var.core.Diagnostics; -import com.oselvar.var.core.Plan; -import com.oselvar.var.core.Registry; -import com.oselvar.var.core.StepKind; -import com.oselvar.var.runner.StepLoader.LoadedSteps; -import com.oselvar.var.runner.fixtures.WidgetSteps; +import dev.varar.core.CellDiff; +import dev.varar.core.Diagnostics; +import dev.varar.core.Plan; +import dev.varar.core.Registry; +import dev.varar.core.StepKind; +import dev.varar.runner.StepLoader.LoadedSteps; +import dev.varar.runner.fixtures.WidgetSteps; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/SmokeTest.java b/java/runner/src/test/java/dev/varar/runner/SmokeTest.java similarity index 90% rename from java/var-runner/src/test/java/com/oselvar/var/runner/SmokeTest.java rename to java/runner/src/test/java/dev/varar/runner/SmokeTest.java index 6201c4d3..d7cc63fd 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/SmokeTest.java +++ b/java/runner/src/test/java/dev/varar/runner/SmokeTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.runner; +package dev.varar.runner; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/StaticFactorySteps.java b/java/runner/src/test/java/dev/varar/runner/StaticFactorySteps.java similarity index 82% rename from java/var-runner/src/test/java/com/oselvar/var/runner/StaticFactorySteps.java rename to java/runner/src/test/java/dev/varar/runner/StaticFactorySteps.java index 706cd540..62ce1e7b 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/StaticFactorySteps.java +++ b/java/runner/src/test/java/dev/varar/runner/StaticFactorySteps.java @@ -1,8 +1,8 @@ -package com.oselvar.var.runner; +package dev.varar.runner; -import com.oselvar.var.State; -import com.oselvar.var.StateBinder; -import com.oselvar.var.StepDefinitions; +import dev.varar.State; +import dev.varar.StateBinder; +import dev.varar.StepDefinitions; /** * Fixture for StepLoader's static-factory path: does NOT implement diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/StepLoaderStaticFactoryTest.java b/java/runner/src/test/java/dev/varar/runner/StepLoaderStaticFactoryTest.java similarity index 85% rename from java/var-runner/src/test/java/com/oselvar/var/runner/StepLoaderStaticFactoryTest.java rename to java/runner/src/test/java/dev/varar/runner/StepLoaderStaticFactoryTest.java index c4d92172..7db9a7d1 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/StepLoaderStaticFactoryTest.java +++ b/java/runner/src/test/java/dev/varar/runner/StepLoaderStaticFactoryTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var.runner; +package dev.varar.runner; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -14,8 +14,7 @@ class StepLoaderStaticFactoryTest { @Test void loadsAClassExposingAStaticStepDefinitionsFactory() { - StepLoader.LoadedSteps loaded = - StepLoader.loadSteps(List.of("com.oselvar.var.runner.StaticFactorySteps"), LOADER); + StepLoader.LoadedSteps loaded = StepLoader.loadSteps(List.of("dev.varar.runner.StaticFactorySteps"), LOADER); assertEquals(1, loaded.registry().steps().size()); assertEquals( @@ -36,7 +35,7 @@ void rejectsAClassThatIsNeitherImplementorNorFactory() { void rejectsTwoDefineStateRegistrationsSharingOneSourceFile() { IllegalArgumentException e = assertThrows( IllegalArgumentException.class, - () -> StepLoader.loadSteps(List.of("com.oselvar.var.runner.DuplicateStateSteps"), LOADER)); + () -> StepLoader.loadSteps(List.of("dev.varar.runner.DuplicateStateSteps"), LOADER)); assertTrue(e.getMessage().contains("DuplicateStateSteps.java"), e.getMessage()); assertTrue(e.getMessage().contains("one steps"), e.getMessage()); } diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/StepLoaderTest.java b/java/runner/src/test/java/dev/varar/runner/StepLoaderTest.java similarity index 90% rename from java/var-runner/src/test/java/com/oselvar/var/runner/StepLoaderTest.java rename to java/runner/src/test/java/dev/varar/runner/StepLoaderTest.java index 28b849e1..1c28c43a 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/StepLoaderTest.java +++ b/java/runner/src/test/java/dev/varar/runner/StepLoaderTest.java @@ -1,26 +1,26 @@ -package com.oselvar.var.runner; +package dev.varar.runner; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.oselvar.var.core.Registry; -import com.oselvar.var.runner.StepLoader.LoadedSteps; -import com.oselvar.var.runner.fixtures.AlphaSteps; -import com.oselvar.var.runner.fixtures.BetaSteps; -import com.oselvar.var.runner.fixtures.ContextOnlySteps; -import com.oselvar.var.runner.fixtures.DeltaSteps; -import com.oselvar.var.runner.fixtures.EpsilonSteps; -import com.oselvar.var.runner.fixtures.GammaSteps; +import dev.varar.core.Registry; +import dev.varar.runner.StepLoader.LoadedSteps; +import dev.varar.runner.fixtures.AlphaSteps; +import dev.varar.runner.fixtures.BetaSteps; +import dev.varar.runner.fixtures.ContextOnlySteps; +import dev.varar.runner.fixtures.DeltaSteps; +import dev.varar.runner.fixtures.EpsilonSteps; +import dev.varar.runner.fixtures.GammaSteps; import java.util.List; import org.junit.jupiter.api.Test; /** * Confirms {@link StepLoader#loadSteps} reflectively loads real {@link - * com.oselvar.var.StepDefinitions} classes (not mocks), merges their registries into + * dev.varar.StepDefinitions} classes (not mocks), merges their registries into * one, and builds a {@code createContext} function keyed EXACTLY the way {@link - * com.oselvar.var.core.Execute#collectExamples} looks it up — by {@code + * dev.varar.core.Execute#collectExamples} looks it up — by {@code * Registry.StepRegistration#expressionSourceFile()} (confirmed by reading {@code * Execute.runExample}, which does {@code createContext.apply(step.stepDef() * .expressionSourceFile())}) — with no cross-wiring between two different files' state. @@ -108,7 +108,7 @@ void classNotImplementingStepDefinitionsThrowsClearly() { void unknownClassNameThrowsClearly() { assertThrows( IllegalArgumentException.class, - () -> StepLoader.loadSteps(List.of("com.oselvar.var.runner.NoSuchStepsClass"), LOADER)); + () -> StepLoader.loadSteps(List.of("dev.varar.runner.NoSuchStepsClass"), LOADER)); } @Test diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/AlphaSteps.java b/java/runner/src/test/java/dev/varar/runner/fixtures/AlphaSteps.java similarity index 83% rename from java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/AlphaSteps.java rename to java/runner/src/test/java/dev/varar/runner/fixtures/AlphaSteps.java index b4b9d101..c40081bc 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/AlphaSteps.java +++ b/java/runner/src/test/java/dev/varar/runner/fixtures/AlphaSteps.java @@ -1,8 +1,8 @@ -package com.oselvar.var.runner.fixtures; +package dev.varar.runner.fixtures; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StepDefinitions; /** * A standalone (top-level, own-file) {@link StepDefinitions} fixture for {@code @@ -13,7 +13,7 @@ * — a nested test class would collapse both to the enclosing test file's name (see * {@code RegistryRegistrarTest}, whose nested {@code RomanNumeralSteps} fixture reports * {@code "RegistryRegistrarTest.java"}), which would defeat the point of this test: - * proving {@link com.oselvar.var.runner.StepLoader}'s per-file context-key resolution + * proving {@link dev.varar.runner.StepLoader}'s per-file context-key resolution * doesn't cross-wire two different files' state. */ public final class AlphaSteps implements StepDefinitions { diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/BetaSteps.java b/java/runner/src/test/java/dev/varar/runner/fixtures/BetaSteps.java similarity index 75% rename from java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/BetaSteps.java rename to java/runner/src/test/java/dev/varar/runner/fixtures/BetaSteps.java index 34e0b80c..db8e0ee3 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/BetaSteps.java +++ b/java/runner/src/test/java/dev/varar/runner/fixtures/BetaSteps.java @@ -1,14 +1,14 @@ -package com.oselvar.var.runner.fixtures; +package dev.varar.runner.fixtures; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StepDefinitions; /** * See {@link AlphaSteps}' javadoc — this fixture's role is to be a genuinely separate * step-definition file (own top-level class, own {@code .java} file) with its own * {@link State} type and its own {@code steps} call, so {@code StepLoaderTest} - * can prove {@link com.oselvar.var.runner.StepLoader} doesn't cross-wire this file's + * can prove {@link dev.varar.runner.StepLoader} doesn't cross-wire this file's * state factory with {@link AlphaSteps}'. */ public final class BetaSteps implements StepDefinitions { diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/BoomSteps.java b/java/runner/src/test/java/dev/varar/runner/fixtures/BoomSteps.java similarity index 83% rename from java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/BoomSteps.java rename to java/runner/src/test/java/dev/varar/runner/fixtures/BoomSteps.java index 9c32f6a3..b7d05697 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/BoomSteps.java +++ b/java/runner/src/test/java/dev/varar/runner/fixtures/BoomSteps.java @@ -1,8 +1,8 @@ -package com.oselvar.var.runner.fixtures; +package dev.varar.runner.fixtures; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StepDefinitions; /** * A standalone (top-level, own-file) {@link StepDefinitions} fixture for {@code diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/ContextOnlySteps.java b/java/runner/src/test/java/dev/varar/runner/fixtures/ContextOnlySteps.java similarity index 75% rename from java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/ContextOnlySteps.java rename to java/runner/src/test/java/dev/varar/runner/fixtures/ContextOnlySteps.java index 64a260b7..559d676c 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/ContextOnlySteps.java +++ b/java/runner/src/test/java/dev/varar/runner/fixtures/ContextOnlySteps.java @@ -1,13 +1,13 @@ -package com.oselvar.var.runner.fixtures; +package dev.varar.runner.fixtures; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StepDefinitions; /** * A {@link StepDefinitions} fixture that calls {@code steps} but registers zero * {@code context}/{@code action}/{@code sensor} steps — the edge case {@code - * StepLoaderTest} uses to prove {@link com.oselvar.var.runner.StepLoader} skips (rather + * StepLoaderTest} uses to prove {@link dev.varar.runner.StepLoader} skips (rather * than crashes on) a class with a {@code stateFactory} but no {@code * expressionSourceFile} to key it by. No real {@code .md} spec would exercise a file * like this at runtime (there being no step to invoke {@code createContext.apply} for diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/DeltaSteps.java b/java/runner/src/test/java/dev/varar/runner/fixtures/DeltaSteps.java similarity index 81% rename from java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/DeltaSteps.java rename to java/runner/src/test/java/dev/varar/runner/fixtures/DeltaSteps.java index 356d7a33..c31a425c 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/DeltaSteps.java +++ b/java/runner/src/test/java/dev/varar/runner/fixtures/DeltaSteps.java @@ -1,8 +1,8 @@ -package com.oselvar.var.runner.fixtures; +package dev.varar.runner.fixtures; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StepDefinitions; import java.util.regex.Pattern; /** diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/EpsilonSteps.java b/java/runner/src/test/java/dev/varar/runner/fixtures/EpsilonSteps.java similarity index 82% rename from java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/EpsilonSteps.java rename to java/runner/src/test/java/dev/varar/runner/fixtures/EpsilonSteps.java index 32e21000..89ae3ddc 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/EpsilonSteps.java +++ b/java/runner/src/test/java/dev/varar/runner/fixtures/EpsilonSteps.java @@ -1,8 +1,8 @@ -package com.oselvar.var.runner.fixtures; +package dev.varar.runner.fixtures; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StepDefinitions; import java.util.regex.Pattern; /** diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/GammaSteps.java b/java/runner/src/test/java/dev/varar/runner/fixtures/GammaSteps.java similarity index 80% rename from java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/GammaSteps.java rename to java/runner/src/test/java/dev/varar/runner/fixtures/GammaSteps.java index 70987c77..89b35e4b 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/GammaSteps.java +++ b/java/runner/src/test/java/dev/varar/runner/fixtures/GammaSteps.java @@ -1,8 +1,8 @@ -package com.oselvar.var.runner.fixtures; +package dev.varar.runner.fixtures; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StepDefinitions; import java.util.regex.Pattern; /** diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/GreetingSteps.java b/java/runner/src/test/java/dev/varar/runner/fixtures/GreetingSteps.java similarity index 84% rename from java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/GreetingSteps.java rename to java/runner/src/test/java/dev/varar/runner/fixtures/GreetingSteps.java index 54f33fa2..53bf91f2 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/GreetingSteps.java +++ b/java/runner/src/test/java/dev/varar/runner/fixtures/GreetingSteps.java @@ -1,8 +1,8 @@ -package com.oselvar.var.runner.fixtures; +package dev.varar.runner.fixtures; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StepDefinitions; /** * A standalone (top-level, own-file) {@link StepDefinitions} fixture for {@code diff --git a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/WidgetSteps.java b/java/runner/src/test/java/dev/varar/runner/fixtures/WidgetSteps.java similarity index 79% rename from java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/WidgetSteps.java rename to java/runner/src/test/java/dev/varar/runner/fixtures/WidgetSteps.java index 2a86a60c..ac87b17c 100644 --- a/java/var-runner/src/test/java/com/oselvar/var/runner/fixtures/WidgetSteps.java +++ b/java/runner/src/test/java/dev/varar/runner/fixtures/WidgetSteps.java @@ -1,14 +1,14 @@ -package com.oselvar.var.runner.fixtures; +package dev.varar.runner.fixtures; -import com.oselvar.var.Registrar; -import com.oselvar.var.State; -import com.oselvar.var.StepDefinitions; +import dev.varar.Registrar; +import dev.varar.State; +import dev.varar.StepDefinitions; /** * A small standalone (top-level, own-file) {@link StepDefinitions} fixture for {@code * RunTest} — registers one {@code context} step that sets a widget count and one {@code * sensor} step that reports it, so a real spec can be planned and run end to end - * through {@link com.oselvar.var.runner.Run#examplesWithRuns}. Deliberately its own + * through {@link dev.varar.runner.Run#examplesWithRuns}. Deliberately its own * top-level file (see {@code AlphaSteps}' javadoc for why: {@code RegistryRegistrar}'s * {@code StackWalker}-captured {@code expressionSourceFile} must be this file's own * name, not the enclosing test class's). diff --git a/java/var-junit/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/java/var-junit/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine deleted file mode 100644 index 6b843fd7..00000000 --- a/java/var-junit/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine +++ /dev/null @@ -1 +0,0 @@ -com.oselvar.var.junit.VarTestEngine diff --git a/java/var/pom.xml b/java/varar/pom.xml similarity index 91% rename from java/var/pom.xml rename to java/varar/pom.xml index 3ac9379e..af13d450 100644 --- a/java/var/pom.xml +++ b/java/varar/pom.xml @@ -5,12 +5,12 @@ 4.0.0 - com.oselvar - var-parent + dev.varar + parent 0.4.3-SNAPSHOT - var + varar jar var (Java) — author facade @@ -20,8 +20,8 @@ - com.oselvar - var-core + dev.varar + core ${project.version} @@ -40,7 +40,7 @@ `package` statement. build-helper-maven-plugin's add-test-source goal adds an ADDITIONAL test-source root (on top of the conventional src/test/java), so conformance/bundles/** fixture files (package - com.oselvar.var.conformance.bundleNN, physically under e.g. + dev.varar.conformance.bundleNN, physically under e.g. 01-roman-numerals/) compile and land on this module's test classpath unmodified. @@ -59,7 +59,7 @@ Empirically verified: `mvn -f java/pom.xml -pl var -am test-compile` succeeds and javac places NumeralsSteps.class (and its 11 siblings) - under var/target/test-classes/com/oselvar/var/conformance/bundle01/ + under var/target/test-classes/dev/varar/conformance/bundle01/ despite the hyphenated, digit-leading source directory name. --> org.codehaus.mojo diff --git a/java/var/src/main/java/com/oselvar/var/Registrar.java b/java/varar/src/main/java/dev/varar/Registrar.java similarity index 99% rename from java/var/src/main/java/com/oselvar/var/Registrar.java rename to java/varar/src/main/java/dev/varar/Registrar.java index c16a8dff..cbf75209 100644 --- a/java/var/src/main/java/com/oselvar/var/Registrar.java +++ b/java/varar/src/main/java/dev/varar/Registrar.java @@ -1,4 +1,4 @@ -package com.oselvar.var; +package dev.varar; import java.util.function.Supplier; diff --git a/java/var/src/main/java/com/oselvar/var/RegistrarGlue.java b/java/varar/src/main/java/dev/varar/RegistrarGlue.java similarity index 96% rename from java/var/src/main/java/com/oselvar/var/RegistrarGlue.java rename to java/varar/src/main/java/dev/varar/RegistrarGlue.java index 5419b6ee..af54fb32 100644 --- a/java/var/src/main/java/com/oselvar/var/RegistrarGlue.java +++ b/java/varar/src/main/java/dev/varar/RegistrarGlue.java @@ -1,4 +1,4 @@ -package com.oselvar.var; +package dev.varar; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/java/var/src/main/java/com/oselvar/var/RegistryRegistrar.java b/java/varar/src/main/java/dev/varar/RegistryRegistrar.java similarity index 98% rename from java/var/src/main/java/com/oselvar/var/RegistryRegistrar.java rename to java/varar/src/main/java/dev/varar/RegistryRegistrar.java index 337efbaf..754565bc 100644 --- a/java/var/src/main/java/com/oselvar/var/RegistryRegistrar.java +++ b/java/varar/src/main/java/dev/varar/RegistryRegistrar.java @@ -1,7 +1,7 @@ -package com.oselvar.var; +package dev.varar; -import com.oselvar.var.core.Registry; -import com.oselvar.var.core.StepKind; +import dev.varar.core.Registry; +import dev.varar.core.StepKind; import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Pattern; diff --git a/java/var/src/main/java/com/oselvar/var/State.java b/java/varar/src/main/java/dev/varar/State.java similarity index 97% rename from java/var/src/main/java/com/oselvar/var/State.java rename to java/varar/src/main/java/dev/varar/State.java index 31a4ae9d..20a2bb68 100644 --- a/java/var/src/main/java/com/oselvar/var/State.java +++ b/java/varar/src/main/java/dev/varar/State.java @@ -1,4 +1,4 @@ -package com.oselvar.var; +package dev.varar; /** * Marker for a step-definition class's evolving context state. diff --git a/java/var/src/main/java/com/oselvar/var/StateBinder.java b/java/varar/src/main/java/dev/varar/StateBinder.java similarity index 99% rename from java/var/src/main/java/com/oselvar/var/StateBinder.java rename to java/varar/src/main/java/dev/varar/StateBinder.java index e9368474..e398f69b 100644 --- a/java/var/src/main/java/com/oselvar/var/StateBinder.java +++ b/java/varar/src/main/java/dev/varar/StateBinder.java @@ -1,4 +1,4 @@ -package com.oselvar.var; +package dev.varar; import java.util.function.Function; import java.util.regex.Pattern; diff --git a/java/var/src/main/java/com/oselvar/var/StepDefinitions.java b/java/varar/src/main/java/dev/varar/StepDefinitions.java similarity index 95% rename from java/var/src/main/java/com/oselvar/var/StepDefinitions.java rename to java/varar/src/main/java/dev/varar/StepDefinitions.java index 7fb3c50e..f8e50036 100644 --- a/java/var/src/main/java/com/oselvar/var/StepDefinitions.java +++ b/java/varar/src/main/java/dev/varar/StepDefinitions.java @@ -1,4 +1,4 @@ -package com.oselvar.var; +package dev.varar; /** * Implemented by a step-definition class to register its steps. The runner discovers the diff --git a/java/var/src/main/java/com/oselvar/var/package-info.java b/java/varar/src/main/java/dev/varar/package-info.java similarity index 64% rename from java/var/src/main/java/com/oselvar/var/package-info.java rename to java/varar/src/main/java/dev/varar/package-info.java index 395083ff..ca53e7d3 100644 --- a/java/var/src/main/java/com/oselvar/var/package-info.java +++ b/java/varar/src/main/java/dev/varar/package-info.java @@ -1,6 +1,6 @@ /** - * Author-facing facade over {@code com.oselvar.var.core}: the + * Author-facing facade over {@code dev.varar.core}: the * context/action/sensor registration API a step-definition class uses (see * doc/superpowers/specs/2026-07-01-java-core-port-design.md#author-api). */ -package com.oselvar.var; +package dev.varar; diff --git a/java/var/src/test/java/com/oselvar/var/AuthorApiTest.java b/java/varar/src/test/java/dev/varar/AuthorApiTest.java similarity index 98% rename from java/var/src/test/java/com/oselvar/var/AuthorApiTest.java rename to java/varar/src/test/java/dev/varar/AuthorApiTest.java index 5b6a7f9f..d077d953 100644 --- a/java/var/src/test/java/com/oselvar/var/AuthorApiTest.java +++ b/java/varar/src/test/java/dev/varar/AuthorApiTest.java @@ -1,9 +1,9 @@ -package com.oselvar.var; +package dev.varar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.oselvar.var.core.StepKind; +import dev.varar.core.StepKind; import java.util.Map; import org.junit.jupiter.api.Test; diff --git a/java/var/src/test/java/com/oselvar/var/ConformanceTest.java b/java/varar/src/test/java/dev/varar/ConformanceTest.java similarity index 79% rename from java/var/src/test/java/com/oselvar/var/ConformanceTest.java rename to java/varar/src/test/java/dev/varar/ConformanceTest.java index 3f5fb27a..807e5ec4 100644 --- a/java/var/src/test/java/com/oselvar/var/ConformanceTest.java +++ b/java/varar/src/test/java/dev/varar/ConformanceTest.java @@ -1,14 +1,14 @@ -package com.oselvar.var; +package dev.varar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.oselvar.var.core.Ast; -import com.oselvar.var.core.CanonicalJson; -import com.oselvar.var.core.Conformance; -import com.oselvar.var.core.Parse; -import com.oselvar.var.core.Plan; -import com.oselvar.var.core.Registry; +import dev.varar.core.Ast; +import dev.varar.core.CanonicalJson; +import dev.varar.core.Conformance; +import dev.varar.core.Parse; +import dev.varar.core.Plan; +import dev.varar.core.Registry; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -28,8 +28,8 @@ * serializes with {@link CanonicalJson#canonicalStringify(Object)}, and asserts * byte-for-byte equality with the committed {@code golden/registry.json}. * - *

Port of the registry stage of {@code typescript/packages/var/tests/ - * conformance.test.ts} and {@code python/packages/var/tests/ + *

Port of the registry stage of {@code typescript/packages/varar/tests/ + * conformance.test.ts} and {@code python/packages/varar/tests/ * test_conformance.py::test_registry_matches_golden}. This lives in the {@code var} * module (not {@code var-core}'s {@code ConformanceTest}, which only covers the * var-doc stage from Task 10): the registry stage needs both {@code var-core}'s @@ -42,11 +42,11 @@ * "The projects in the reactor contain a cyclic reference". {@code var}'s test scope * already depends on {@code var-core} (its own main dependency) with no such cycle. * - *

Fixture-layout solution (see {@code java/var/pom.xml}'s {@code + *

Fixture-layout solution (see {@code java/varar/pom.xml}'s {@code * build-helper-maven-plugin} config): every bundle directory under {@code * conformance/bundles/} (e.g. {@code 01-roman-numerals}) is not a valid Java package * segment (leading digit, hyphen), so each bundle's fixture file declares its own - * valid package instead — {@code com.oselvar.var.conformance.bundleNN} (zero-padded + * valid package instead — {@code dev.varar.conformance.bundleNN} (zero-padded * two-digit bundle number) — while physically living alongside the bundle's existing * {@code *.steps.ts}/{@code *.steps.py}. Maven's compiler plugin does not require a * source file's directory to match its {@code package} declaration, only that the @@ -62,7 +62,7 @@ */ class ConformanceTest { - // Maven runs tests with the module directory (java/var/) as the working directory, + // Maven runs tests with the module directory (java/varar/) as the working directory, // so the shared corpus — a sibling of java/, typescript/, python/ at the repo root — // is two levels up, same as var-core's own ConformanceTest. private static final Path BUNDLES_DIR = Paths.get("..", "..", "conformance", "bundles"); @@ -88,21 +88,21 @@ static Stream> bundleDirs() throws IOException { */ private static StepDefinitions loadFixture(String bundleName) { return switch (bundleName) { - case "01-roman-numerals" -> new com.oselvar.var.conformance.bundle01.NumeralsSteps(); - case "02-context-isolation" -> new com.oselvar.var.conformance.bundle02.CounterSteps(); - case "03-expected-failure" -> new com.oselvar.var.conformance.bundle03.DivisionSteps(); - case "04-tables-and-docstrings" -> new com.oselvar.var.conformance.bundle04.EchoSteps(); - case "05-ambiguous-match" -> new com.oselvar.var.conformance.bundle05.CukesSteps(); - case "06-doc-string-mismatch" -> new com.oselvar.var.conformance.bundle06.EchoSteps(); - case "07-row-check-mismatch" -> new com.oselvar.var.conformance.bundle07.ReportSteps(); - case "08-string-capture" -> new com.oselvar.var.conformance.bundle08.GreetSteps(); - case "09-expected-message-mismatch" -> new com.oselvar.var.conformance.bundle09.BoomSteps(); - case "10-error-fence-without-step" -> new com.oselvar.var.conformance.bundle10.CukesSteps(); - case "11-emoji-offsets" -> new com.oselvar.var.conformance.bundle11.GreetSteps(); - case "12-combining-marks" -> new com.oselvar.var.conformance.bundle12.GreetSteps(); - case "13-custom-parameter-type" -> new com.oselvar.var.conformance.bundle13.AirportsSteps(); - case "14-stateless-steps" -> new com.oselvar.var.conformance.bundle14.SquaresSteps(); - case "15-custom-parameter-format" -> new com.oselvar.var.conformance.bundle15.MoneySteps(); + case "01-roman-numerals" -> new dev.varar.conformance.bundle01.NumeralsSteps(); + case "02-context-isolation" -> new dev.varar.conformance.bundle02.CounterSteps(); + case "03-expected-failure" -> new dev.varar.conformance.bundle03.DivisionSteps(); + case "04-tables-and-docstrings" -> new dev.varar.conformance.bundle04.EchoSteps(); + case "05-ambiguous-match" -> new dev.varar.conformance.bundle05.CukesSteps(); + case "06-doc-string-mismatch" -> new dev.varar.conformance.bundle06.EchoSteps(); + case "07-row-check-mismatch" -> new dev.varar.conformance.bundle07.ReportSteps(); + case "08-string-capture" -> new dev.varar.conformance.bundle08.GreetSteps(); + case "09-expected-message-mismatch" -> new dev.varar.conformance.bundle09.BoomSteps(); + case "10-error-fence-without-step" -> new dev.varar.conformance.bundle10.CukesSteps(); + case "11-emoji-offsets" -> new dev.varar.conformance.bundle11.GreetSteps(); + case "12-combining-marks" -> new dev.varar.conformance.bundle12.GreetSteps(); + case "13-custom-parameter-type" -> new dev.varar.conformance.bundle13.AirportsSteps(); + case "14-stateless-steps" -> new dev.varar.conformance.bundle14.SquaresSteps(); + case "15-custom-parameter-format" -> new dev.varar.conformance.bundle15.MoneySteps(); default -> throw new IllegalStateException("No Java step fixture registered for bundle " + bundleName); }; } @@ -129,7 +129,7 @@ void registryMatchesGolden(Path bundle) throws IOException { * does), plans the two together via {@link Plan#plan}, projects the resulting {@link * Plan.ExecutionPlan} via {@link Conformance#toPlanArtifact}, and asserts byte-for-byte * equality with the committed {@code golden/plan.json}. Port of the plan stage of {@code - * typescript/packages/var/tests/conformance.test.ts} and {@code python/packages/var/tests/ + * typescript/packages/varar/tests/conformance.test.ts} and {@code python/packages/varar/tests/ * test_conformance.py::test_plan_matches_golden}. */ @ParameterizedTest(name = "{0}") @@ -159,7 +159,7 @@ void planMatchesGolden(Path bundle) throws IOException { * RegistryRegistrar#stateFactory()}), runs the whole plan via {@link * Conformance#runConformance}, and asserts byte-for-byte equality of the {@code trace} * artifact with the committed {@code golden/trace.json}. Port of the trace stage of {@code - * typescript/packages/var/tests/conformance.test.ts} and {@code python/packages/var/tests/ + * typescript/packages/varar/tests/conformance.test.ts} and {@code python/packages/varar/tests/ * test_conformance.py::test_trace_matches_golden}. * *

Kept as its own separately reported stage (mirroring the Python port and this class's diff --git a/java/var/src/test/java/com/oselvar/var/GlueForwarder.java b/java/varar/src/test/java/dev/varar/GlueForwarder.java similarity index 95% rename from java/var/src/test/java/com/oselvar/var/GlueForwarder.java rename to java/varar/src/test/java/dev/varar/GlueForwarder.java index fd9b26f8..f44861aa 100644 --- a/java/var/src/test/java/com/oselvar/var/GlueForwarder.java +++ b/java/varar/src/test/java/dev/varar/GlueForwarder.java @@ -1,4 +1,4 @@ -package com.oselvar.var; +package dev.varar; /** * Test double for a registration-forwarding layer (what var-kotlin's StepsScope diff --git a/java/var/src/test/java/com/oselvar/var/RecordingRegistrar.java b/java/varar/src/test/java/dev/varar/RecordingRegistrar.java similarity index 98% rename from java/var/src/test/java/com/oselvar/var/RecordingRegistrar.java rename to java/varar/src/test/java/dev/varar/RecordingRegistrar.java index f3090aa7..b5497169 100644 --- a/java/var/src/test/java/com/oselvar/var/RecordingRegistrar.java +++ b/java/varar/src/test/java/dev/varar/RecordingRegistrar.java @@ -1,6 +1,6 @@ -package com.oselvar.var; +package dev.varar; -import com.oselvar.var.core.StepKind; +import dev.varar.core.StepKind; import java.util.ArrayList; import java.util.List; import java.util.function.Function; diff --git a/java/var/src/test/java/com/oselvar/var/RegistrarGlueTest.java b/java/varar/src/test/java/dev/varar/RegistrarGlueTest.java similarity index 91% rename from java/var/src/test/java/com/oselvar/var/RegistrarGlueTest.java rename to java/varar/src/test/java/dev/varar/RegistrarGlueTest.java index 96dfb87a..0d90966e 100644 --- a/java/var/src/test/java/com/oselvar/var/RegistrarGlueTest.java +++ b/java/varar/src/test/java/dev/varar/RegistrarGlueTest.java @@ -1,8 +1,8 @@ -package com.oselvar.var; +package dev.varar; import static org.junit.jupiter.api.Assertions.assertEquals; -import com.oselvar.var.core.Registry; +import dev.varar.core.Registry; import org.junit.jupiter.api.Test; class RegistrarGlueTest { diff --git a/java/var/src/test/java/com/oselvar/var/RegistryRegistrarTest.java b/java/varar/src/test/java/dev/varar/RegistryRegistrarTest.java similarity index 97% rename from java/var/src/test/java/com/oselvar/var/RegistryRegistrarTest.java rename to java/varar/src/test/java/dev/varar/RegistryRegistrarTest.java index 7136820c..27f0dd74 100644 --- a/java/var/src/test/java/com/oselvar/var/RegistryRegistrarTest.java +++ b/java/varar/src/test/java/dev/varar/RegistryRegistrarTest.java @@ -1,12 +1,12 @@ -package com.oselvar.var; +package dev.varar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.oselvar.var.core.Registry; -import com.oselvar.var.core.StepKind; +import dev.varar.core.Registry; +import dev.varar.core.StepKind; import java.util.Map; import java.util.regex.Pattern; import org.junit.jupiter.api.Test; diff --git a/java/var/src/test/java/com/oselvar/var/SmokeTest.java b/java/varar/src/test/java/dev/varar/SmokeTest.java similarity index 92% rename from java/var/src/test/java/com/oselvar/var/SmokeTest.java rename to java/varar/src/test/java/dev/varar/SmokeTest.java index b2330fc5..bbb3c5ee 100644 --- a/java/var/src/test/java/com/oselvar/var/SmokeTest.java +++ b/java/varar/src/test/java/dev/varar/SmokeTest.java @@ -1,4 +1,4 @@ -package com.oselvar.var; +package dev.varar; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/languages.json b/languages.json index f595cb13..ff73ecb1 100644 --- a/languages.json +++ b/languages.json @@ -4,22 +4,22 @@ "label": "TypeScript", "icon": "seti:typescript", "ext": ".ts", - "stepsGlob": "var-examples/**/*.steps.ts", + "stepsGlob": "varar-examples/**/*.steps.ts", "hasCli": true, - "install": { "lang": "bash", "code": "pnpm add -D @oselvar/var @oselvar/var-cli" }, - "scaffold": { "lang": "bash", "code": "pnpm exec var init" }, - "run": { "lang": "bash", "code": "pnpm exec var run" } + "install": { "lang": "bash", "code": "pnpm add -D @varar/varar @varar/cli" }, + "scaffold": { "lang": "bash", "code": "pnpm exec varar init" }, + "run": { "lang": "bash", "code": "pnpm exec varar run" } }, { "id": "java", "label": "Java", "icon": "seti:java", "ext": ".java", - "stepsGlob": "var-examples/**/*Steps.java", + "stepsGlob": "varar-examples/**/*Steps.java", "hasCli": false, "install": { "lang": "xml", - "code": "\n\n com.oselvar\n var-junit\n {{version}}\n test\n" + "code": "\n\n dev.varar\n junit\n {{version}}\n test\n" }, "scaffold": null, "run": { "lang": "bash", "code": "mvn test" } @@ -29,11 +29,11 @@ "label": "Kotlin", "icon": "seti:kotlin", "ext": ".kt", - "stepsGlob": "var-examples/**/*.steps.kt", + "stepsGlob": "varar-examples/**/*.steps.kt", "hasCli": false, "install": { "lang": "kotlin", - "code": "// build.gradle.kts — use the latest release from Maven Central\ntestImplementation(\"com.oselvar:var-kotlin:{{version}}\")\ntestImplementation(\"com.oselvar:var-junit:{{version}}\")" + "code": "// build.gradle.kts — use the latest release from Maven Central\ntestImplementation(\"dev.varar:kotlin:{{version}}\")\ntestImplementation(\"dev.varar:junit:{{version}}\")" }, "scaffold": null, "run": { "lang": "bash", "code": "./gradlew test" } @@ -43,10 +43,10 @@ "label": "Python", "icon": "seti:python", "ext": ".py", - "stepsGlob": "var-examples/**/*.steps.py", + "stepsGlob": "varar-examples/**/*.steps.py", "hasCli": true, - "install": { "lang": "bash", "code": "uv add --dev pytest-var" }, - "scaffold": { "lang": "bash", "code": "uv run var init" }, + "install": { "lang": "bash", "code": "uv add --dev pytest-varar" }, + "scaffold": { "lang": "bash", "code": "uv run varar init" }, "run": { "lang": "bash", "code": "uv run pytest" } }, { @@ -54,10 +54,10 @@ "label": "Ruby", "icon": "seti:ruby", "ext": ".rb", - "stepsGlob": "var-examples/**/*.steps.rb", + "stepsGlob": "varar-examples/**/*.steps.rb", "hasCli": true, - "install": { "lang": "bash", "code": "bundle add oselvar-var-rspec" }, - "scaffold": { "lang": "bash", "code": "bundle exec var init" }, + "install": { "lang": "bash", "code": "bundle add varar-rspec" }, + "scaffold": { "lang": "bash", "code": "bundle exec varar init" }, "run": { "lang": "bash", "code": "bundle exec rspec" } }, { @@ -65,9 +65,9 @@ "label": "Rust", "icon": "seti:rust", "ext": ".rs", - "stepsGlob": "var-examples/**/*.steps.rs", + "stepsGlob": "varar-examples/**/*.steps.rs", "hasCli": false, - "install": { "lang": "bash", "code": "cargo add var-cargotest --dev" }, + "install": { "lang": "bash", "code": "cargo add varar-cargotest --dev" }, "scaffold": null, "run": { "lang": "bash", "code": "cargo test" } } diff --git a/python/README.md b/python/README.md index 80f6d638..92189261 100644 --- a/python/README.md +++ b/python/README.md @@ -14,17 +14,17 @@ uv run ruff check | Package (dist / import) | Layer | |---|---| -| `oselvar-var-core` / `var_core` | pure functional core: parse → plan → execute, matcher, diffs, conformance | -| `oselvar-var` / `var` | author facade: `steps` (+ `internal`, `registry` glue) | -| `oselvar-var-config` / `var_config` | reads `var.config.json` — the shared config file for all var tools | -| `oselvar-var-runner` / `var_runner` | shared imperative shell: discovery, step loading, run orchestration, failure rendering | -| `pytest-var` / `var_pytest` | pytest plugin: `.md` specs as first-class tests | -| `oselvar-var-unittest` / `var_unittest` | unittest adapter: `generate_tests(globals())` in one test module | +| `varar-core` / `varar_core` | pure functional core: parse → plan → execute, matcher, diffs, conformance | +| `varar` / `var` | author facade: `steps` (+ `internal`, `registry` glue) | +| `varar-config` / `varar_config` | reads `varar.config.json` — the shared config file for all var tools | +| `varar-runner` / `varar_runner` | shared imperative shell: discovery, step loading, run orchestration, failure rendering | +| `pytest-varar` / `varar_pytest` | pytest plugin: `.md` specs as first-class tests | +| `varar-unittest` / `varar_unittest` | unittest adapter: `generate_tests(globals())` in one test module | ## Run Markdown specs as live var tests (dogfood) -The `pytest-var` plugin turns a `.md` file into pytest tests (one item per -example). `var.config.json` points it at a **collision-free +The `pytest-varar` plugin turns a `.md` file into pytest tests (one item per +example). `varar.config.json` points it at a **collision-free subset** of the shared `conformance/bundles/` (the bundles reuse some expressions across bundles — e.g. `I echo…`, `I have {int} cukes`, `I greet {string}` — and the plugin builds one global step registry, so it can't @@ -44,11 +44,11 @@ uv run pytest --rootdir=. ../conformance/bundles **Want to edit a spec/step and watch it flip?** Do NOT edit files under `conformance/bundles/` — they are the shared golden corpus, and changing them breaks the conformance suite (Python *and* TypeScript). Instead, copy a bundle to -a scratch location you own, point `var.config.json` at it, and edit freely: +a scratch location you own, point `varar.config.json` at it, and edit freely: ```sh cp -r ../conformance/bundles/01-roman-numerals /tmp/myspec -# add "/tmp/myspec/*.md" to var.config.json's docs.include and +# add "/tmp/myspec/*.md" to varar.config.json's docs.include and # "/tmp/myspec/*.steps.py" to steps uv run pytest --rootdir=. /tmp/myspec # green # now change a number in /tmp/myspec/example.md or break a handler → red diff --git a/python/packages/var-config/pyproject.toml b/python/packages/config/pyproject.toml similarity index 58% rename from python/packages/var-config/pyproject.toml rename to python/packages/config/pyproject.toml index e561466d..f34a0298 100644 --- a/python/packages/var-config/pyproject.toml +++ b/python/packages/config/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "oselvar-var-config" +name = "varar-config" version = "0.4.2" -description = "Reads var.config.json — the shared config file for all var tools" +description = "Reads varar.config.json — the shared config file for all var tools" license = "MIT" requires-python = ">=3.11" dependencies = [] @@ -11,4 +11,4 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/var_config"] +packages = ["src/varar_config"] diff --git a/python/packages/config/src/varar_config/__init__.py b/python/packages/config/src/varar_config/__init__.py new file mode 100644 index 00000000..41849c27 --- /dev/null +++ b/python/packages/config/src/varar_config/__init__.py @@ -0,0 +1,3 @@ +from varar_config.config import VarConfig, read_varar_config + +__all__ = ["VarConfig", "read_varar_config"] diff --git a/python/packages/var-config/src/var_config/config.py b/python/packages/config/src/varar_config/config.py similarity index 94% rename from python/packages/var-config/src/var_config/config.py rename to python/packages/config/src/varar_config/config.py index caec85d7..59073abc 100644 --- a/python/packages/var-config/src/var_config/config.py +++ b/python/packages/config/src/varar_config/config.py @@ -26,15 +26,15 @@ def _string_tuple(value: object, key: str, path: Path) -> tuple[str, ...]: return tuple(value) -def read_var_config(root: str | Path) -> VarConfig: - """Read ``/var.config.json``. +def read_varar_config(root: str | Path) -> VarConfig: + """Read ``/varar.config.json``. Missing file -> empty config (tools no-op; matches every other port). Malformed JSON, wrong types, or unknown keys -> ``ValueError`` starting with the file path — a typo'd config must fail loudly, never silently discover nothing. See conformance/config/README.md for the shared rules. """ - path = Path(root) / "var.config.json" + path = Path(root) / "varar.config.json" if not path.is_file(): return VarConfig() try: diff --git a/python/packages/var-config/tests/test_config.py b/python/packages/config/tests/test_config.py similarity index 73% rename from python/packages/var-config/tests/test_config.py rename to python/packages/config/tests/test_config.py index 7398362c..b0646cb8 100644 --- a/python/packages/var-config/tests/test_config.py +++ b/python/packages/config/tests/test_config.py @@ -1,10 +1,10 @@ import pytest -from var_config import VarConfig, read_var_config +from varar_config import VarConfig, read_varar_config def _write(tmp_path, body: str): - (tmp_path / "var.config.json").write_text(body, encoding="utf-8") + (tmp_path / "varar.config.json").write_text(body, encoding="utf-8") return tmp_path @@ -15,7 +15,7 @@ def test_reads_all_keys(tmp_path): ' "steps": ["**/*_steps.py"], "snippets": {"python": "P"},' ' "scannerPlugins": ["gherkinTables"]}', ) - cfg = read_var_config(root) + cfg = read_varar_config(root) assert cfg.docs_include == ("a/**/*.md",) assert cfg.docs_exclude == ("a/wip/**",) assert cfg.steps == ("**/*_steps.py",) @@ -24,39 +24,39 @@ def test_reads_all_keys(tmp_path): def test_missing_file_is_empty_config(tmp_path): - assert read_var_config(tmp_path / "nowhere") == VarConfig() + assert read_varar_config(tmp_path / "nowhere") == VarConfig() def test_all_keys_optional_and_schema_key_ignored(tmp_path): root = _write(tmp_path, '{"$schema": "https://x/y.json"}') - assert read_var_config(root) == VarConfig() + assert read_varar_config(root) == VarConfig() def test_malformed_json_raises_with_path(tmp_path): root = _write(tmp_path, "{ nope") - with pytest.raises(ValueError, match=r"var\.config\.json.*invalid JSON"): - read_var_config(root) + with pytest.raises(ValueError, match=r"varar\.config\.json.*invalid JSON"): + read_varar_config(root) def test_unknown_key_raises(tmp_path): root = _write(tmp_path, '{"vars": {}}') with pytest.raises(ValueError, match="unknown key"): - read_var_config(root) + read_varar_config(root) def test_wrong_type_raises(tmp_path): root = _write(tmp_path, '{"steps": "x"}') with pytest.raises(ValueError, match="steps"): - read_var_config(root) + read_varar_config(root) def test_falsy_wrong_type_docs_raises(tmp_path): root = _write(tmp_path, '{"docs": false}') with pytest.raises(ValueError, match="docs"): - read_var_config(root) + read_varar_config(root) def test_falsy_wrong_type_snippets_raises(tmp_path): root = _write(tmp_path, '{"snippets": []}') with pytest.raises(ValueError, match="snippets"): - read_var_config(root) + read_varar_config(root) diff --git a/python/packages/var-config/tests/test_conformance.py b/python/packages/config/tests/test_conformance.py similarity index 78% rename from python/packages/var-config/tests/test_conformance.py rename to python/packages/config/tests/test_conformance.py index d9a4fe3c..d303370c 100644 --- a/python/packages/var-config/tests/test_conformance.py +++ b/python/packages/config/tests/test_conformance.py @@ -5,11 +5,11 @@ from pathlib import Path import pytest -from var_core.canonical_json import canonical_stringify +from varar_core.canonical_json import canonical_stringify -from var_config import read_var_config +from varar_config import read_varar_config -# python/packages/var-config/tests -> parents[4] = repo root +# python/packages/config/tests -> parents[4] = repo root CASES_DIR = Path(__file__).resolve().parents[4] / "conformance" / "config" / "cases" CASES = sorted(p for p in CASES_DIR.iterdir() if p.is_dir()) @@ -27,8 +27,8 @@ def _artifact(cfg) -> dict: def test_config_case(case: Path) -> None: if (case / "expect-error.txt").exists(): with pytest.raises(ValueError): - read_var_config(case) + read_varar_config(case) else: - actual = canonical_stringify(_artifact(read_var_config(case))) + actual = canonical_stringify(_artifact(read_varar_config(case))) expected = (case / "golden.json").read_text(encoding="utf-8") assert actual == expected diff --git a/python/packages/var-core/pyproject.toml b/python/packages/core/pyproject.toml similarity index 84% rename from python/packages/var-core/pyproject.toml rename to python/packages/core/pyproject.toml index 15222a89..e732e928 100644 --- a/python/packages/var-core/pyproject.toml +++ b/python/packages/core/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "oselvar-var-core" +name = "varar-core" version = "0.4.2" description = "Markdown-native BDD — pure functional core engine" requires-python = ">=3.11" @@ -11,4 +11,4 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/var_core"] +packages = ["src/varar_core"] diff --git a/python/packages/var-core/src/var_core/__init__.py b/python/packages/core/src/varar_core/__init__.py similarity index 100% rename from python/packages/var-core/src/var_core/__init__.py rename to python/packages/core/src/varar_core/__init__.py diff --git a/python/packages/var-core/src/var_core/ast.py b/python/packages/core/src/varar_core/ast.py similarity index 99% rename from python/packages/var-core/src/var_core/ast.py rename to python/packages/core/src/varar_core/ast.py index 0c001f30..15f0a640 100644 --- a/python/packages/var-core/src/var_core/ast.py +++ b/python/packages/core/src/varar_core/ast.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from typing import Union -from var_core.span import Span +from varar_core.span import Span # Maps a block-text offset to its source offset. Block text is the raw diff --git a/python/packages/var-core/src/var_core/canonical_json.py b/python/packages/core/src/varar_core/canonical_json.py similarity index 100% rename from python/packages/var-core/src/var_core/canonical_json.py rename to python/packages/core/src/varar_core/canonical_json.py diff --git a/python/packages/var-core/src/var_core/cell_diff.py b/python/packages/core/src/varar_core/cell_diff.py similarity index 97% rename from python/packages/var-core/src/var_core/cell_diff.py rename to python/packages/core/src/varar_core/cell_diff.py index bd72a5c7..a573dbac 100644 --- a/python/packages/var-core/src/var_core/cell_diff.py +++ b/python/packages/core/src/varar_core/cell_diff.py @@ -1,4 +1,4 @@ -"""cell_diff.py — port of typescript/packages/var-core/src/cell-diff.ts. +"""cell_diff.py — port of typescript/packages/core/src/cell-diff.ts. Pure functions and immutable types for comparing row/table step returns against the authored Markdown cells. @@ -8,8 +8,8 @@ from dataclasses import dataclass from typing import Any, Sequence -from var_core.ast import Table -from var_core.span import Span +from varar_core.ast import Table +from varar_core.span import Span @dataclass(frozen=True, slots=True) diff --git a/python/packages/var-core/src/var_core/conformance.py b/python/packages/core/src/varar_core/conformance.py similarity index 95% rename from python/packages/var-core/src/var_core/conformance.py rename to python/packages/core/src/varar_core/conformance.py index a0fc6886..62d8556d 100644 --- a/python/packages/var-core/src/var_core/conformance.py +++ b/python/packages/core/src/varar_core/conformance.py @@ -1,7 +1,7 @@ """conformance.py — var-doc, registry, plan, and trace artifact projections. Port of toVarDocArtifact, toRegistryArtifact, toPlanArtifact, toFailureArtifact, -and runConformance from typescript/packages/var-core/src/conformance.ts. +and runConformance from typescript/packages/core/src/conformance.ts. Serializes a VarDoc AST / Registry / ExecutionPlan / trace to the camelCase wire dicts expected by the conformance golden files. """ @@ -14,7 +14,7 @@ from cucumber_expressions.expression import CucumberExpression -from var_core.ast import ( +from varar_core.ast import ( Blockquote, Example, Fence, @@ -27,14 +27,14 @@ ThematicBreak, VarDoc, ) -from var_core.cell_diff import ReturnShapeError, is_cell_mismatch_error -from var_core.doc_string_diff import is_doc_string_mismatch_error -from var_core.execute import CollectPorts, StepObservation, collect_examples, is_unexpected_pass_error -from var_core.failure_anchor import failure_anchor -from var_core.plan import ExecutionPlan -from var_core.plan import plan as build_plan -from var_core.registry import Registry -from var_core.span import Span, utf16_slice +from varar_core.cell_diff import ReturnShapeError, is_cell_mismatch_error +from varar_core.doc_string_diff import is_doc_string_mismatch_error +from varar_core.execute import CollectPorts, StepObservation, collect_examples, is_unexpected_pass_error +from varar_core.failure_anchor import failure_anchor +from varar_core.plan import ExecutionPlan +from varar_core.plan import plan as build_plan +from varar_core.registry import Registry +from varar_core.span import Span, utf16_slice @dataclass(frozen=True, slots=True) diff --git a/python/packages/var-core/src/var_core/deep_freeze.py b/python/packages/core/src/varar_core/deep_freeze.py similarity index 100% rename from python/packages/var-core/src/var_core/deep_freeze.py rename to python/packages/core/src/varar_core/deep_freeze.py diff --git a/python/packages/var-core/src/var_core/diagnostics.py b/python/packages/core/src/varar_core/diagnostics.py similarity index 98% rename from python/packages/var-core/src/var_core/diagnostics.py rename to python/packages/core/src/varar_core/diagnostics.py index cd5ab11f..b5fd9697 100644 --- a/python/packages/var-core/src/var_core/diagnostics.py +++ b/python/packages/core/src/varar_core/diagnostics.py @@ -9,7 +9,7 @@ from dataclasses import dataclass from typing import Literal -from var_core.span import Span +from varar_core.span import Span Severity = Literal["error", "warning"] DiagnosticCode = Literal["ambiguous-match", "error-fence-without-step", "drift"] diff --git a/python/packages/var-core/src/var_core/doc_string_diff.py b/python/packages/core/src/varar_core/doc_string_diff.py similarity index 90% rename from python/packages/var-core/src/var_core/doc_string_diff.py rename to python/packages/core/src/varar_core/doc_string_diff.py index ced8b3ae..e1d8ec82 100644 --- a/python/packages/var-core/src/var_core/doc_string_diff.py +++ b/python/packages/core/src/varar_core/doc_string_diff.py @@ -1,4 +1,4 @@ -"""doc_string_diff.py — port of typescript/packages/var-core/src/doc-string-diff.ts. +"""doc_string_diff.py — port of typescript/packages/core/src/doc-string-diff.ts. Pure comparison of a doc-string step's return value against the fence body. """ @@ -7,8 +7,8 @@ from dataclasses import dataclass from typing import Any -from var_core.cell_diff import ReturnShapeError -from var_core.span import Span +from varar_core.cell_diff import ReturnShapeError +from varar_core.span import Span @dataclass(frozen=True, slots=True) diff --git a/python/packages/var-core/src/var_core/drift.py b/python/packages/core/src/varar_core/drift.py similarity index 91% rename from python/packages/var-core/src/var_core/drift.py rename to python/packages/core/src/varar_core/drift.py index fdfd736d..48905d1b 100644 --- a/python/packages/var-core/src/var_core/drift.py +++ b/python/packages/core/src/varar_core/drift.py @@ -1,8 +1,8 @@ -"""drift.py — port of typescript/packages/var-core/src/drift.ts. +"""drift.py — port of typescript/packages/core/src/drift.ts. -Spec drift detection: a paragraph the committed var.lock.json baseline recorded +Spec drift detection: a paragraph the committed varar.lock.json baseline recorded as an example that now matches no step. Pure over the existing VarDoc + -ExecutionPlan, byte-identical to the TypeScript port so var.lock.json is shared +ExecutionPlan, byte-identical to the TypeScript port so varar.lock.json is shared across languages. """ from __future__ import annotations @@ -12,11 +12,11 @@ from dataclasses import dataclass from typing import Protocol -from var_core.ast import VarDoc -from var_core.diagnostics import Diagnostic, drift_detected -from var_core.hash import hash_source -from var_core.plan import ExecutionPlan, derive_example_name -from var_core.span import Span +from varar_core.ast import VarDoc +from varar_core.diagnostics import Diagnostic, drift_detected +from varar_core.hash import hash_source +from varar_core.plan import ExecutionPlan, derive_example_name +from varar_core.span import Span # A baseline example is re-identified in the edited source by text: an exact # name match, else the most word-similar paragraph at or above this threshold. @@ -44,7 +44,7 @@ class SpecBaseline: @dataclass(frozen=True, slots=True) class VarLock: - """The whole var.lock.json: every spec keyed by its POSIX path.""" + """The whole varar.lock.json: every spec keyed by its POSIX path.""" version: int # always 1 specs: dict[str, SpecBaseline] @@ -60,7 +60,7 @@ class Drift: class BaselineStore(Protocol): - """Persistence port for var.lock.json. The core owns the format; adapters + """Persistence port for varar.lock.json. The core owns the format; adapters move only raw text (a filesystem store on disk, an in-memory store).""" def read(self) -> str | None: ... @@ -216,7 +216,7 @@ def _parse_spec_baseline(value: object) -> SpecBaseline | None: def parse_var_lock(text: str) -> VarLock | None: - """Parse var.lock.json; None on malformed input (treated as no baseline).""" + """Parse varar.lock.json; None on malformed input (treated as no baseline).""" try: parsed = json.loads(text) except (ValueError, TypeError): @@ -236,7 +236,7 @@ def parse_var_lock(text: str) -> VarLock | None: def stringify_var_lock(lock: VarLock) -> str: - """Serialize var.lock.json deterministically: spec paths sorted, examples in + """Serialize varar.lock.json deterministically: spec paths sorted, examples in document order, two-space indent, trailing newline. Byte-identical to the TypeScript serializer (camelCase keys, non-ASCII kept raw).""" specs = { diff --git a/python/packages/var-core/src/var_core/execute.py b/python/packages/core/src/varar_core/execute.py similarity index 97% rename from python/packages/var-core/src/var_core/execute.py rename to python/packages/core/src/varar_core/execute.py index 8679941f..05695907 100644 --- a/python/packages/var-core/src/var_core/execute.py +++ b/python/packages/core/src/varar_core/execute.py @@ -15,18 +15,18 @@ from dataclasses import dataclass, field from typing import Any, Callable, Literal, Optional -from var_core.cell_diff import ( +from varar_core.cell_diff import ( CellMismatchError, ReturnShapeError, compare_row, compare_table, ) -from var_core.deep_freeze import deep_freeze -from var_core.doc_string_diff import DocStringMismatchError, compare_doc_string -from var_core.failure_anchor import failure_anchor -from var_core.param_diff import compare_params -from var_core.plan import ExecutionPlan, PlannedStep -from var_core.span import utf16_slice +from varar_core.deep_freeze import deep_freeze +from varar_core.doc_string_diff import DocStringMismatchError, compare_doc_string +from varar_core.failure_anchor import failure_anchor +from varar_core.param_diff import compare_params +from varar_core.plan import ExecutionPlan, PlannedStep +from varar_core.span import utf16_slice # --------------------------------------------------------------------------- diff --git a/python/packages/var-core/src/var_core/failure.py b/python/packages/core/src/varar_core/failure.py similarity index 89% rename from python/packages/var-core/src/var_core/failure.py rename to python/packages/core/src/varar_core/failure.py index ec48e45e..92d06c45 100644 --- a/python/packages/var-core/src/var_core/failure.py +++ b/python/packages/core/src/varar_core/failure.py @@ -1,4 +1,4 @@ -"""failure.py — port of typescript/packages/var-core/src/failure.ts. +"""failure.py — port of typescript/packages/core/src/failure.ts. Converts a thrown step error into the structured ExampleFailure payload. Shared by every producer so failures are byte-identical. @@ -8,9 +8,9 @@ import re from typing import Any -from var_core.cell_diff import is_cell_mismatch_error -from var_core.doc_string_diff import is_doc_string_mismatch_error -from var_core.result import CellFailure, ExampleFailure +from varar_core.cell_diff import is_cell_mismatch_error +from varar_core.doc_string_diff import is_doc_string_mismatch_error +from varar_core.result import CellFailure, ExampleFailure def _failing_line(stack: str, spec_path: str) -> int | None: diff --git a/python/packages/var-core/src/var_core/failure_anchor.py b/python/packages/core/src/varar_core/failure_anchor.py similarity index 76% rename from python/packages/var-core/src/var_core/failure_anchor.py rename to python/packages/core/src/varar_core/failure_anchor.py index 430fcfc5..3d13003c 100644 --- a/python/packages/var-core/src/var_core/failure_anchor.py +++ b/python/packages/core/src/varar_core/failure_anchor.py @@ -1,6 +1,6 @@ """failure_anchor.py — where a failure points in the .md source. -Port of failureAnchor from typescript/packages/var-core/src/failure-anchor.ts. +Port of failureAnchor from typescript/packages/core/src/failure-anchor.ts. A mismatch anchors at its first failing span (the cell, the doc string fence body), anything else at the fallback — the step's match start. This rule is the single source of truth for failure locations: the executor's stack @@ -10,9 +10,9 @@ from __future__ import annotations -from var_core.cell_diff import is_cell_mismatch_error -from var_core.doc_string_diff import is_doc_string_mismatch_error -from var_core.span import Span +from varar_core.cell_diff import is_cell_mismatch_error +from varar_core.doc_string_diff import is_doc_string_mismatch_error +from varar_core.span import Span def failure_anchor(error: object, fallback: Span) -> Span: diff --git a/python/packages/var-core/src/var_core/hash.py b/python/packages/core/src/varar_core/hash.py similarity index 81% rename from python/packages/var-core/src/var_core/hash.py rename to python/packages/core/src/varar_core/hash.py index 1fede493..d4161905 100644 --- a/python/packages/var-core/src/var_core/hash.py +++ b/python/packages/core/src/varar_core/hash.py @@ -1,8 +1,8 @@ -"""hash.py — port of typescript/packages/var-core/src/hash.ts. +"""hash.py — port of typescript/packages/core/src/hash.ts. FNV-1a (32-bit) change-detector over UTF-16 code units. Not a security hash: tiny, dependency-free, and byte-identical to the TypeScript (and future JVM) -implementations so ``var.lock.json`` fingerprints match across every port. The +implementations so ``varar.lock.json`` fingerprints match across every port. The ``fnv1a:`` prefix namespaces the algorithm. """ from __future__ import annotations diff --git a/python/packages/var-core/src/var_core/matcher.py b/python/packages/core/src/varar_core/matcher.py similarity index 98% rename from python/packages/var-core/src/var_core/matcher.py rename to python/packages/core/src/varar_core/matcher.py index d83b5b9c..704f2963 100644 --- a/python/packages/var-core/src/var_core/matcher.py +++ b/python/packages/core/src/varar_core/matcher.py @@ -18,8 +18,8 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, Literal -from var_core.registry import Registry, StepRegistration -from var_core.span import to_utf16_offset +from varar_core.registry import Registry, StepRegistration +from varar_core.span import to_utf16_offset if TYPE_CHECKING: pass diff --git a/python/packages/var-core/src/var_core/param_diff.py b/python/packages/core/src/varar_core/param_diff.py similarity index 91% rename from python/packages/var-core/src/var_core/param_diff.py rename to python/packages/core/src/varar_core/param_diff.py index ee619810..4e5ac02c 100644 --- a/python/packages/var-core/src/var_core/param_diff.py +++ b/python/packages/core/src/varar_core/param_diff.py @@ -1,4 +1,4 @@ -"""param_diff.py — port of typescript/packages/var-core/src/param-diff.ts. +"""param_diff.py — port of typescript/packages/core/src/param-diff.ts. Compare a sensor's returned inline actuals against captured document values. """ @@ -6,9 +6,9 @@ from typing import Any, Optional, Sequence -from var_core.cell_diff import CellDiff, render_cell_value -from var_core.registry import ParameterFormat -from var_core.span import Span +from varar_core.cell_diff import CellDiff, render_cell_value +from varar_core.registry import ParameterFormat +from varar_core.span import Span def _render_param_value(value: Any, format: Optional[ParameterFormat]) -> tuple[str, bool]: diff --git a/python/packages/var-core/src/var_core/parse.py b/python/packages/core/src/varar_core/parse.py similarity index 64% rename from python/packages/var-core/src/var_core/parse.py rename to python/packages/core/src/varar_core/parse.py index 9b3c3816..904d28aa 100644 --- a/python/packages/var-core/src/var_core/parse.py +++ b/python/packages/core/src/varar_core/parse.py @@ -1,13 +1,13 @@ -"""parse.py — port of typescript/packages/var-core/src/parse.ts. +"""parse.py — port of typescript/packages/core/src/parse.ts. Combines scan + structure into the top-level parse function. """ from __future__ import annotations -from var_core.ast import VarDoc -from var_core.scanner import ScannerPlugin, scan -from var_core.structurer import structure +from varar_core.ast import VarDoc +from varar_core.scanner import ScannerPlugin, scan +from varar_core.structurer import structure def parse( diff --git a/python/packages/var-core/src/var_core/plan.py b/python/packages/core/src/varar_core/plan.py similarity index 97% rename from python/packages/var-core/src/var_core/plan.py rename to python/packages/core/src/varar_core/plan.py index 26279408..796bb8bc 100644 --- a/python/packages/var-core/src/var_core/plan.py +++ b/python/packages/core/src/varar_core/plan.py @@ -11,19 +11,19 @@ from dataclasses import dataclass from typing import Literal -from var_core.ast import Block, Example, Fence, SegmentOffset, Table, VarDoc -from var_core.cell_diff import RowCheck -from var_core.diagnostics import ( +from varar_core.ast import Block, Example, Fence, SegmentOffset, Table, VarDoc +from varar_core.cell_diff import RowCheck +from varar_core.diagnostics import ( AmbiguousInput, Candidate, Diagnostic, ambiguous_match, error_fence_without_step, ) -from var_core.matcher import Hit, find_hits, resolve_hits -from var_core.registry import Registry, StepRegistration -from var_core.sentences import split_sentences -from var_core.span import Span, span_from_offsets, to_utf16_offset, utf16_len +from varar_core.matcher import Hit, find_hits, resolve_hits +from varar_core.registry import Registry, StepRegistration +from varar_core.sentences import split_sentences +from varar_core.span import Span, span_from_offsets, to_utf16_offset, utf16_len # --------------------------------------------------------------------------- diff --git a/python/packages/var-core/src/var_core/registry.py b/python/packages/core/src/varar_core/registry.py similarity index 99% rename from python/packages/var-core/src/var_core/registry.py rename to python/packages/core/src/varar_core/registry.py index e34a1b13..93ee0806 100644 --- a/python/packages/var-core/src/var_core/registry.py +++ b/python/packages/core/src/varar_core/registry.py @@ -9,7 +9,7 @@ from cucumber_expressions.parameter_type import ParameterType from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry -from var_core.step_role import StepKind +from varar_core.step_role import StepKind # A step handler: receives the context state plus any matched arguments StepHandler = Callable[..., Any] diff --git a/python/packages/var-core/src/var_core/result.py b/python/packages/core/src/varar_core/result.py similarity index 95% rename from python/packages/var-core/src/var_core/result.py rename to python/packages/core/src/varar_core/result.py index c5ec6cfd..5a071ceb 100644 --- a/python/packages/var-core/src/var_core/result.py +++ b/python/packages/core/src/varar_core/result.py @@ -1,4 +1,4 @@ -"""result.py — port of typescript/packages/var-core/src/result.ts. +"""result.py — port of typescript/packages/core/src/result.ts. Immutable dataclasses for run results (CellFailure, ExampleResult, SpecResults). """ diff --git a/python/packages/var-core/src/var_core/scanner.py b/python/packages/core/src/varar_core/scanner.py similarity index 98% rename from python/packages/var-core/src/var_core/scanner.py rename to python/packages/core/src/varar_core/scanner.py index 56af17ca..8cac44dc 100644 --- a/python/packages/var-core/src/var_core/scanner.py +++ b/python/packages/core/src/varar_core/scanner.py @@ -1,6 +1,6 @@ """scanner.py — markdown block scanner. -Port of typescript/packages/var-core/src/scanner.ts. +Port of typescript/packages/core/src/scanner.ts. UTF-16 rule: all offsets (start_offset/end_offset in RawLine and Span) count UTF-16 code units, matching TypeScript's String.charCodeAt / String.length @@ -16,7 +16,7 @@ from dataclasses import dataclass from typing import Protocol -from var_core.ast import ( +from varar_core.ast import ( Block, Blockquote, Fence, @@ -28,8 +28,8 @@ Table, ThematicBreak, ) -from var_core.span import span_from_offsets, to_utf16_offset, utf16_len, utf16_slice -from var_core.table_cells import parse_row_cells +from varar_core.span import span_from_offsets, to_utf16_offset, utf16_len, utf16_slice +from varar_core.table_cells import parse_row_cells # ── Public types ────────────────────────────────────────────────────────────── diff --git a/python/packages/var-core/src/var_core/sentences.py b/python/packages/core/src/varar_core/sentences.py similarity index 98% rename from python/packages/var-core/src/var_core/sentences.py rename to python/packages/core/src/varar_core/sentences.py index 742a78e9..2314f925 100644 --- a/python/packages/var-core/src/var_core/sentences.py +++ b/python/packages/core/src/varar_core/sentences.py @@ -1,4 +1,4 @@ -"""sentences.py — port of typescript/packages/var-core/src/sentences.ts. +"""sentences.py — port of typescript/packages/core/src/sentences.ts. Splits a block of plain text into sentences on . ! ? and \\n, skipping terminators inside backtick spans and double-quoted strings and treating diff --git a/python/packages/var-core/src/var_core/span.py b/python/packages/core/src/varar_core/span.py similarity index 100% rename from python/packages/var-core/src/var_core/span.py rename to python/packages/core/src/varar_core/span.py diff --git a/python/packages/var-core/src/var_core/step_role.py b/python/packages/core/src/varar_core/step_role.py similarity index 100% rename from python/packages/var-core/src/var_core/step_role.py rename to python/packages/core/src/varar_core/step_role.py diff --git a/python/packages/var-core/src/var_core/structurer.py b/python/packages/core/src/varar_core/structurer.py similarity index 95% rename from python/packages/var-core/src/var_core/structurer.py rename to python/packages/core/src/varar_core/structurer.py index 6c336a84..755ef370 100644 --- a/python/packages/var-core/src/var_core/structurer.py +++ b/python/packages/core/src/varar_core/structurer.py @@ -1,4 +1,4 @@ -"""structurer.py — port of typescript/packages/var-core/src/structurer.ts. +"""structurer.py — port of typescript/packages/core/src/structurer.ts. Groups scanned blocks into Examples, tracking heading scope and orphan attachments. """ @@ -7,14 +7,14 @@ import re -from var_core.ast import ( +from varar_core.ast import ( Block, Example, Fence, Table, VarDoc, ) -from var_core.span import span_from_offsets, utf16_slice +from varar_core.span import span_from_offsets, utf16_slice def structure(path: str, source: str, blocks: tuple[Block, ...]) -> VarDoc: diff --git a/python/packages/var-core/src/var_core/table_cells.py b/python/packages/core/src/varar_core/table_cells.py similarity index 95% rename from python/packages/var-core/src/var_core/table_cells.py rename to python/packages/core/src/varar_core/table_cells.py index d6d47f45..696c1498 100644 --- a/python/packages/var-core/src/var_core/table_cells.py +++ b/python/packages/core/src/varar_core/table_cells.py @@ -1,14 +1,14 @@ """ Parse a Markdown/Gherkin table row into trimmed cells and per-cell source spans. -Port of: typescript/packages/var-core/src/table-cells.ts +Port of: typescript/packages/core/src/table-cells.ts UTF-16 rule: all offsets (including line_start_offset and emitted Span offsets) count UTF-16 code units, matching JavaScript's native string indexing so that span values are identical between the TS and Python implementations. """ -from var_core.span import Span, span_from_offsets, utf16_len, to_utf16_offset +from varar_core.span import Span, span_from_offsets, utf16_len, to_utf16_offset def parse_row_cells( diff --git a/python/packages/var-core/tests/test_ast.py b/python/packages/core/tests/test_ast.py similarity index 84% rename from python/packages/var-core/tests/test_ast.py rename to python/packages/core/tests/test_ast.py index 5c0899d5..2ec02986 100644 --- a/python/packages/var-core/tests/test_ast.py +++ b/python/packages/core/tests/test_ast.py @@ -1,7 +1,7 @@ import pytest from dataclasses import FrozenInstanceError -from var_core.span import span_from_offsets -from var_core.ast import Paragraph, SegmentOffset, VarDoc +from varar_core.span import span_from_offsets +from varar_core.ast import Paragraph, SegmentOffset, VarDoc def test_nodes_construct_and_are_frozen(): diff --git a/python/packages/var-core/tests/test_canonical_json.py b/python/packages/core/tests/test_canonical_json.py similarity index 88% rename from python/packages/var-core/tests/test_canonical_json.py rename to python/packages/core/tests/test_canonical_json.py index 3d9d9b30..2c65e790 100644 --- a/python/packages/var-core/tests/test_canonical_json.py +++ b/python/packages/core/tests/test_canonical_json.py @@ -1,4 +1,4 @@ -from var_core.canonical_json import canonical_stringify +from varar_core.canonical_json import canonical_stringify def test_sorts_keys_indents_and_trailing_newline(): diff --git a/python/packages/var-core/tests/test_cell_diff.py b/python/packages/core/tests/test_cell_diff.py similarity index 95% rename from python/packages/var-core/tests/test_cell_diff.py rename to python/packages/core/tests/test_cell_diff.py index 8db4de42..61e1dfa2 100644 --- a/python/packages/var-core/tests/test_cell_diff.py +++ b/python/packages/core/tests/test_cell_diff.py @@ -1,10 +1,10 @@ -"""test_cell_diff.py — port of typescript/packages/var-core/tests/cell-diff.test.ts""" +"""test_cell_diff.py — port of typescript/packages/core/tests/cell-diff.test.ts""" from __future__ import annotations import pytest -from var_core.ast import Table -from var_core.cell_diff import ( +from varar_core.ast import Table +from varar_core.cell_diff import ( CellDiff, CellMismatchError, ReturnShapeError, @@ -13,8 +13,8 @@ compare_table, is_cell_mismatch_error, ) -from var_core.parse import parse -from var_core.span import Span +from varar_core.parse import parse +from varar_core.span import Span _span = Span(start_offset=0, end_offset=1, start_line=1, start_col=1, end_line=1, end_col=2) _checks: tuple[RowCheck, ...] = ( diff --git a/python/packages/var-core/tests/test_conformance.py b/python/packages/core/tests/test_conformance.py similarity index 93% rename from python/packages/var-core/tests/test_conformance.py rename to python/packages/core/tests/test_conformance.py index cef12084..b7af50d0 100644 --- a/python/packages/var-core/tests/test_conformance.py +++ b/python/packages/core/tests/test_conformance.py @@ -1,26 +1,26 @@ -"""test_conformance.py — unit tests for var_core.conformance projections. +"""test_conformance.py — unit tests for varar_core.conformance projections. -Port of typescript/packages/var-core/tests/conformance.test.ts. +Port of typescript/packages/core/tests/conformance.test.ts. Exercises the projection functions and run_conformance directly using create_registry()+add_step() — no facade dependency. """ from __future__ import annotations -from var_core.canonical_json import canonical_stringify -from var_core.cell_diff import CellDiff, CellMismatchError, ReturnShapeError -from var_core.conformance import ( +from varar_core.canonical_json import canonical_stringify +from varar_core.cell_diff import CellDiff, CellMismatchError, ReturnShapeError +from varar_core.conformance import ( run_conformance, to_failure_artifact, to_plan_artifact, to_registry_artifact, to_var_doc_artifact, ) -from var_core.doc_string_diff import DocStringDiff, DocStringMismatchError -from var_core.execute import UnexpectedPassError -from var_core.parse import parse -from var_core.plan import plan -from var_core.registry import add_step, create_registry, define_parameter_type -from var_core.span import Span +from varar_core.doc_string_diff import DocStringDiff, DocStringMismatchError +from varar_core.execute import UnexpectedPassError +from varar_core.parse import parse +from varar_core.plan import plan +from varar_core.registry import add_step, create_registry, define_parameter_type +from varar_core.span import Span # --------------------------------------------------------------------------- diff --git a/python/packages/var-core/tests/test_deep_freeze.py b/python/packages/core/tests/test_deep_freeze.py similarity index 98% rename from python/packages/var-core/tests/test_deep_freeze.py rename to python/packages/core/tests/test_deep_freeze.py index c51d49df..fb6429b1 100644 --- a/python/packages/var-core/tests/test_deep_freeze.py +++ b/python/packages/core/tests/test_deep_freeze.py @@ -6,7 +6,7 @@ import pytest -from var_core.deep_freeze import deep_freeze +from varar_core.deep_freeze import deep_freeze def test_deep_freeze_freezes_nested_objects_and_arrays() -> None: diff --git a/python/packages/var-core/tests/test_doc_string_diff.py b/python/packages/core/tests/test_doc_string_diff.py similarity index 81% rename from python/packages/var-core/tests/test_doc_string_diff.py rename to python/packages/core/tests/test_doc_string_diff.py index 4d4d1c86..01ec18e3 100644 --- a/python/packages/var-core/tests/test_doc_string_diff.py +++ b/python/packages/core/tests/test_doc_string_diff.py @@ -1,15 +1,15 @@ -"""test_doc_string_diff.py — port of typescript/packages/var-core/tests/doc-string-diff.test.ts""" +"""test_doc_string_diff.py — port of typescript/packages/core/tests/doc-string-diff.test.ts""" from __future__ import annotations import pytest -from var_core.cell_diff import ReturnShapeError -from var_core.doc_string_diff import ( +from varar_core.cell_diff import ReturnShapeError +from varar_core.doc_string_diff import ( DocStringMismatchError, compare_doc_string, is_doc_string_mismatch_error, ) -from var_core.span import Span +from varar_core.span import Span _span = Span(start_offset=0, end_offset=6, start_line=1, start_col=1, end_line=1, end_col=6) @@ -36,7 +36,7 @@ def test_non_string_return_raises_return_shape_error() -> None: def test_doc_string_mismatch_error_carries_diff_and_is_detectable() -> None: - from var_core.doc_string_diff import DocStringDiff + from varar_core.doc_string_diff import DocStringDiff diff = DocStringDiff(span=_span, expected="hello\n", actual="bye\n") err = DocStringMismatchError(diff) diff --git a/python/packages/var-core/tests/test_drift.py b/python/packages/core/tests/test_drift.py similarity index 96% rename from python/packages/var-core/tests/test_drift.py rename to python/packages/core/tests/test_drift.py index d2b906da..c69986b0 100644 --- a/python/packages/var-core/tests/test_drift.py +++ b/python/packages/core/tests/test_drift.py @@ -1,7 +1,7 @@ -"""test_drift.py — port of typescript/packages/var-core/tests/drift.test.ts.""" +"""test_drift.py — port of typescript/packages/core/tests/drift.test.ts.""" from __future__ import annotations -from var_core.drift import ( +from varar_core.drift import ( BaselineExample, SpecBaseline, VarLock, @@ -13,10 +13,10 @@ reconcile_drift, stringify_var_lock, ) -from var_core.hash import hash_source -from var_core.parse import parse -from var_core.plan import plan -from var_core.registry import add_step, create_registry +from varar_core.hash import hash_source +from varar_core.parse import parse +from varar_core.plan import plan +from varar_core.registry import add_step, create_registry def _noop(*_args: object, **_kwargs: object) -> None: diff --git a/python/packages/var-core/tests/test_execute.py b/python/packages/core/tests/test_execute.py similarity index 99% rename from python/packages/var-core/tests/test_execute.py rename to python/packages/core/tests/test_execute.py index f333d3b7..94ef660a 100644 --- a/python/packages/var-core/tests/test_execute.py +++ b/python/packages/core/tests/test_execute.py @@ -5,17 +5,17 @@ import pytest -from var_core.cell_diff import ReturnShapeError, is_cell_mismatch_error -from var_core.doc_string_diff import is_doc_string_mismatch_error -from var_core.execute import ( +from varar_core.cell_diff import ReturnShapeError, is_cell_mismatch_error +from varar_core.doc_string_diff import is_doc_string_mismatch_error +from varar_core.execute import ( ExecutePorts, StepObservation, UnexpectedPassError, execute_plan, ) -from var_core.parse import parse -from var_core.plan import plan -from var_core.registry import Registry, add_step, create_registry, define_parameter_type +from varar_core.parse import parse +from varar_core.plan import plan +from varar_core.registry import Registry, add_step, create_registry, define_parameter_type # --------------------------------------------------------------------------- diff --git a/python/packages/var-core/tests/test_failure.py b/python/packages/core/tests/test_failure.py similarity index 84% rename from python/packages/var-core/tests/test_failure.py rename to python/packages/core/tests/test_failure.py index 5eab7e4e..6564052c 100644 --- a/python/packages/var-core/tests/test_failure.py +++ b/python/packages/core/tests/test_failure.py @@ -1,11 +1,11 @@ -"""test_failure.py — port of typescript/packages/var-core/tests/failure.test.ts""" +"""test_failure.py — port of typescript/packages/core/tests/failure.test.ts""" from __future__ import annotations -from var_core.cell_diff import CellDiff, CellMismatchError, ReturnShapeError -from var_core.doc_string_diff import DocStringDiff, DocStringMismatchError -from var_core.failure import to_failure -from var_core.result import CellFailure -from var_core.span import span_from_offsets +from varar_core.cell_diff import CellDiff, CellMismatchError, ReturnShapeError +from varar_core.doc_string_diff import DocStringDiff, DocStringMismatchError +from varar_core.failure import to_failure +from varar_core.result import CellFailure +from varar_core.span import span_from_offsets def test_to_failure_extracts_cells_from_cell_mismatch_error() -> None: diff --git a/python/packages/var-core/tests/test_hash.py b/python/packages/core/tests/test_hash.py similarity index 94% rename from python/packages/var-core/tests/test_hash.py rename to python/packages/core/tests/test_hash.py index a195aa4f..1d05f6c9 100644 --- a/python/packages/var-core/tests/test_hash.py +++ b/python/packages/core/tests/test_hash.py @@ -1,4 +1,4 @@ -from var_core.hash import hash_source +from varar_core.hash import hash_source def test_deterministic() -> None: diff --git a/python/packages/var-core/tests/test_matcher.py b/python/packages/core/tests/test_matcher.py similarity index 97% rename from python/packages/var-core/tests/test_matcher.py rename to python/packages/core/tests/test_matcher.py index 9b4014ad..798dc876 100644 --- a/python/packages/var-core/tests/test_matcher.py +++ b/python/packages/core/tests/test_matcher.py @@ -1,8 +1,8 @@ """Tests for var.matcher — port of var-core/tests/matcher.test.ts.""" from __future__ import annotations -from var_core.matcher import ParamSpan, find_hits, resolve_hits -from var_core.registry import Registry, add_step, create_registry +from varar_core.matcher import ParamSpan, find_hits, resolve_hits +from varar_core.registry import Registry, add_step, create_registry def _noop(*_args: object, **_kwargs: object) -> None: diff --git a/python/packages/var-core/tests/test_param_diff.py b/python/packages/core/tests/test_param_diff.py similarity index 85% rename from python/packages/var-core/tests/test_param_diff.py rename to python/packages/core/tests/test_param_diff.py index 0e25c372..91b15057 100644 --- a/python/packages/var-core/tests/test_param_diff.py +++ b/python/packages/core/tests/test_param_diff.py @@ -1,8 +1,8 @@ -"""test_param_diff.py — port of typescript/packages/var-core/tests/param-diff.test.ts""" +"""test_param_diff.py — port of typescript/packages/core/tests/param-diff.test.ts""" from __future__ import annotations -from var_core.param_diff import compare_params -from var_core.span import span_from_offsets +from varar_core.param_diff import compare_params +from varar_core.span import span_from_offsets _SOURCE = "I should have 3 cukes in my big belly" diff --git a/python/packages/var-core/tests/test_parse.py b/python/packages/core/tests/test_parse.py similarity index 79% rename from python/packages/var-core/tests/test_parse.py rename to python/packages/core/tests/test_parse.py index 090e11c6..e1ba3cda 100644 --- a/python/packages/var-core/tests/test_parse.py +++ b/python/packages/core/tests/test_parse.py @@ -1,7 +1,7 @@ -"""test_parse.py — port of typescript/packages/var-core/tests/parse.test.ts.""" +"""test_parse.py — port of typescript/packages/core/tests/parse.test.ts.""" from __future__ import annotations -from var_core.parse import parse +from varar_core.parse import parse def test_parse_returns_var_doc_whose_examples_come_from_paragraphs_and_carry_heading_stack() -> None: diff --git a/python/packages/var-core/tests/test_plan.py b/python/packages/core/tests/test_plan.py similarity index 98% rename from python/packages/var-core/tests/test_plan.py rename to python/packages/core/tests/test_plan.py index 91845920..911b18e7 100644 --- a/python/packages/var-core/tests/test_plan.py +++ b/python/packages/core/tests/test_plan.py @@ -1,9 +1,9 @@ -"""test_plan.py — port of typescript/packages/var-core/tests/plan.test.ts.""" +"""test_plan.py — port of typescript/packages/core/tests/plan.test.ts.""" from __future__ import annotations -from var_core.parse import parse -from var_core.plan import plan -from var_core.registry import add_step, create_registry +from varar_core.parse import parse +from varar_core.plan import plan +from varar_core.registry import add_step, create_registry def _noop(*_args: object, **_kwargs: object) -> None: diff --git a/python/packages/var-core/tests/test_registry.py b/python/packages/core/tests/test_registry.py similarity index 97% rename from python/packages/var-core/tests/test_registry.py rename to python/packages/core/tests/test_registry.py index 3293199e..e27f269c 100644 --- a/python/packages/var-core/tests/test_registry.py +++ b/python/packages/core/tests/test_registry.py @@ -7,7 +7,7 @@ from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry -from var_core.registry import add_step, create_registry, define_parameter_type +from varar_core.registry import add_step, create_registry, define_parameter_type def test_create_registry_returns_empty_registry_with_default_parameter_types() -> None: diff --git a/python/packages/var-core/tests/test_scanner.py b/python/packages/core/tests/test_scanner.py similarity index 97% rename from python/packages/var-core/tests/test_scanner.py rename to python/packages/core/tests/test_scanner.py index 4bf218c7..ea0126da 100644 --- a/python/packages/var-core/tests/test_scanner.py +++ b/python/packages/core/tests/test_scanner.py @@ -1,6 +1,6 @@ """test_scanner.py — block scanner tests. -Ported from typescript/packages/var-core/tests/scanner.test.ts. +Ported from typescript/packages/core/tests/scanner.test.ts. UTF-16 rule: span offsets count UTF-16 code units. """ @@ -8,9 +8,9 @@ import pytest -from var_core.ast import Blockquote, Fence, Paragraph, SegmentOffset -from var_core.scanner import scan -from var_core.span import Span +from varar_core.ast import Blockquote, Fence, Paragraph, SegmentOffset +from varar_core.scanner import scan +from varar_core.span import Span # ── Heading tests ──────────────────────────────────────────────────────────── diff --git a/python/packages/var-core/tests/test_sentences.py b/python/packages/core/tests/test_sentences.py similarity index 95% rename from python/packages/var-core/tests/test_sentences.py rename to python/packages/core/tests/test_sentences.py index 39a0bc6a..2f979287 100644 --- a/python/packages/var-core/tests/test_sentences.py +++ b/python/packages/core/tests/test_sentences.py @@ -1,7 +1,7 @@ -"""Port of typescript/packages/var-core/tests/sentences.test.ts (plus the +"""Port of typescript/packages/core/tests/sentences.test.ts (plus the astral-offset case the Java/Rust ports added).""" -from var_core.sentences import Sentence, split_sentences +from varar_core.sentences import Sentence, split_sentences def _texts(sentences): diff --git a/python/packages/var-core/tests/test_span.py b/python/packages/core/tests/test_span.py similarity index 92% rename from python/packages/var-core/tests/test_span.py rename to python/packages/core/tests/test_span.py index 1b43cc34..6cb45329 100644 --- a/python/packages/var-core/tests/test_span.py +++ b/python/packages/core/tests/test_span.py @@ -1,5 +1,5 @@ # test_span.py -from var_core.span import utf16_len, to_utf16_offset, utf16_slice, line_col, span_from_offsets +from varar_core.span import utf16_len, to_utf16_offset, utf16_slice, line_col, span_from_offsets def test_utf16_len_ascii_and_astral(): assert utf16_len("abc") == 3 diff --git a/python/packages/var-core/tests/test_step_role.py b/python/packages/core/tests/test_step_role.py similarity index 85% rename from python/packages/var-core/tests/test_step_role.py rename to python/packages/core/tests/test_step_role.py index 04fb9ad2..037ce4f8 100644 --- a/python/packages/var-core/tests/test_step_role.py +++ b/python/packages/core/tests/test_step_role.py @@ -1,10 +1,10 @@ -"""Port of typescript/packages/var-core/tests/step-role.test.ts. +"""Port of typescript/packages/core/tests/step-role.test.ts. `infer_step_role` is purely structural: a step with nothing after it is the observation (sensor); anything followed by other steps is driving the software (stimulus).""" -from var_core.step_role import infer_step_role +from varar_core.step_role import infer_step_role def test_nothing_after_means_sensor_expectation_last(): diff --git a/python/packages/var-core/tests/test_structurer.py b/python/packages/core/tests/test_structurer.py similarity index 95% rename from python/packages/var-core/tests/test_structurer.py rename to python/packages/core/tests/test_structurer.py index 69eab96e..b483a8c6 100644 --- a/python/packages/var-core/tests/test_structurer.py +++ b/python/packages/core/tests/test_structurer.py @@ -1,8 +1,8 @@ -"""test_structurer.py — port of typescript/packages/var-core/tests/structurer.test.ts.""" +"""test_structurer.py — port of typescript/packages/core/tests/structurer.test.ts.""" from __future__ import annotations -from var_core.scanner import scan -from var_core.structurer import structure +from varar_core.scanner import scan +from varar_core.structurer import structure def test_every_paragraph_becomes_a_candidate_example_scoped_by_headings_above() -> None: diff --git a/python/packages/var-core/tests/test_table_cells.py b/python/packages/core/tests/test_table_cells.py similarity index 97% rename from python/packages/var-core/tests/test_table_cells.py rename to python/packages/core/tests/test_table_cells.py index 4b791718..59ebdc56 100644 --- a/python/packages/var-core/tests/test_table_cells.py +++ b/python/packages/core/tests/test_table_cells.py @@ -3,8 +3,8 @@ (the table-cell span cases) and from table-cells.ts behaviour directly. """ -from var_core.span import utf16_slice -from var_core.table_cells import parse_row_cells +from varar_core.span import utf16_slice +from varar_core.table_cells import parse_row_cells def test_basic_row_returns_trimmed_cells() -> None: diff --git a/python/packages/var-pytest/pyproject.toml b/python/packages/pytest/pyproject.toml similarity index 55% rename from python/packages/var-pytest/pyproject.toml rename to python/packages/pytest/pyproject.toml index 165124ed..064ee2d9 100644 --- a/python/packages/var-pytest/pyproject.toml +++ b/python/packages/pytest/pyproject.toml @@ -1,17 +1,17 @@ [project] -name = "pytest-var" +name = "pytest-varar" version = "0.4.2" description = "pytest plugin for Markdown-native BDD" requires-python = ">=3.11" license = "MIT" -dependencies = ["oselvar-var==0.4.2", "oselvar-var-config==0.4.2", "oselvar-var-core==0.4.2", "oselvar-var-runner==0.4.2", "pytest>=8"] +dependencies = ["varar==0.4.2", "varar-config==0.4.2", "varar-core==0.4.2", "varar-runner==0.4.2", "pytest>=8"] [project.entry-points.pytest11] -var = "var_pytest.plugin" +var = "varar_pytest.plugin" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/var_pytest"] +packages = ["src/varar_pytest"] diff --git a/python/packages/var-pytest/src/var_pytest/__init__.py b/python/packages/pytest/src/varar_pytest/__init__.py similarity index 100% rename from python/packages/var-pytest/src/var_pytest/__init__.py rename to python/packages/pytest/src/varar_pytest/__init__.py diff --git a/python/packages/var-pytest/src/var_pytest/fixtures.py b/python/packages/pytest/src/varar_pytest/fixtures.py similarity index 98% rename from python/packages/var-pytest/src/var_pytest/fixtures.py rename to python/packages/pytest/src/varar_pytest/fixtures.py index df4fc134..fc34872e 100644 --- a/python/packages/var-pytest/src/var_pytest/fixtures.py +++ b/python/packages/pytest/src/varar_pytest/fixtures.py @@ -14,7 +14,7 @@ from dataclasses import replace from typing import Any, Callable -from var_core.registry import Registry +from varar_core.registry import Registry # --------------------------------------------------------------------------- # Per-item active request contextvar diff --git a/python/packages/var-pytest/src/var_pytest/plugin.py b/python/packages/pytest/src/varar_pytest/plugin.py similarity index 86% rename from python/packages/var-pytest/src/var_pytest/plugin.py rename to python/packages/pytest/src/varar_pytest/plugin.py index e4081816..945f7f39 100644 --- a/python/packages/var-pytest/src/var_pytest/plugin.py +++ b/python/packages/pytest/src/varar_pytest/plugin.py @@ -6,14 +6,14 @@ import pytest -from var_config import read_var_config -from var_core.diagnostics import drift_detected -from var_core.drift import reconcile_drift -from var_runner.baseline_store import create_file_baseline_store -from var_runner.discovery import match_spec -from var_runner.run import RecordingReporter, examples_with_runs, plan_spec -from var_runner.steps import load_steps -from var_pytest.fixtures import _active_request, get_active_request, wrap_registry_for_fixtures +from varar_config import read_varar_config +from varar_core.diagnostics import drift_detected +from varar_core.drift import reconcile_drift +from varar_runner.baseline_store import create_file_baseline_store +from varar_runner.discovery import match_spec +from varar_runner.run import RecordingReporter, examples_with_runs, plan_spec +from varar_runner.steps import load_steps +from varar_pytest.fixtures import _active_request, get_active_request, wrap_registry_for_fixtures _STASH: dict = {} # keyed by config id → (VarConfig, LoadedSteps, root, store) @@ -23,7 +23,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: "--var-update", action="store_true", default=False, - help="Accept drift and re-record var.lock.json (also via VAR_UPDATE=1).", + help="Accept drift and re-record varar.lock.json (also via VAR_UPDATE=1).", ) @@ -35,7 +35,7 @@ def _update_mode(config: pytest.Config) -> bool: def pytest_configure(config: pytest.Config) -> None: root = Path(config.rootpath) - cfg = read_var_config(root) + cfg = read_varar_config(root) loaded = load_steps(cfg.steps, root) wrapped_registry = wrap_registry_for_fixtures(loaded.registry, get_active_request) loaded = dataclasses.replace(loaded, registry=wrapped_registry) @@ -72,7 +72,7 @@ def collect(self): name = base if idx == 0 else f"{base}[{idx}]" yield VarItem.from_parent(self, name=name, example=example, run=run, source=source) - # Reconcile drift against var.lock.json: a clean run records/updates the + # Reconcile drift against varar.lock.json: a clean run records/updates the # baseline; a paragraph that was an example and no longer matches any # step yields a failing item (unless --var-update / VAR_UPDATE accepts). try: @@ -142,7 +142,7 @@ def teardown(self) -> None: self._token = None def repr_failure(self, excinfo: object) -> str: - from var_runner.render import render_failure + from varar_runner.render import render_failure return render_failure(excinfo.value, self._source, str(self.path)) # type: ignore[union-attr] diff --git a/python/packages/var-pytest/tests/test_async.py b/python/packages/pytest/tests/test_async.py similarity index 95% rename from python/packages/var-pytest/tests/test_async.py rename to python/packages/pytest/tests/test_async.py index 345783fd..737151e4 100644 --- a/python/packages/var-pytest/tests/test_async.py +++ b/python/packages/pytest/tests/test_async.py @@ -13,7 +13,7 @@ # A sensor that is async def, returns its single slot bare so the core can compare it. ASYNC_STEPS = """\ import asyncio -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"value": 0}) @@ -51,7 +51,7 @@ async def _(state, expected): def _write_fixture(pytester, spec_content: str, steps_content: str) -> None: - (pytester.path / "var.config.json").write_text(VAR_CONFIG, encoding="utf-8") + (pytester.path / "varar.config.json").write_text(VAR_CONFIG, encoding="utf-8") (pytester.path / "steps").mkdir(exist_ok=True) (pytester.path / "steps" / "async_calc.steps.py").write_text( steps_content.strip(), encoding="utf-8" diff --git a/python/packages/var-pytest/tests/test_collection.py b/python/packages/pytest/tests/test_collection.py similarity index 90% rename from python/packages/var-pytest/tests/test_collection.py rename to python/packages/pytest/tests/test_collection.py index 6e008016..6825b79a 100644 --- a/python/packages/var-pytest/tests/test_collection.py +++ b/python/packages/pytest/tests/test_collection.py @@ -1,5 +1,5 @@ STEPS = ''' -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"n": 0}) @stimulus("I add {int}") def _(state, n): @@ -17,8 +17,8 @@ def _(state, total): SPEC = "# Calc\n\n## adds two\n\nI add 2\nthe total is 2\n\n## adds wrong\n\nI add 2\nthe total is 9\n" -def _write_var_config(pytester): - (pytester.path / "var.config.json").write_text(VAR_CONFIG, encoding="utf-8") +def _write_varar_config(pytester): + (pytester.path / "varar.config.json").write_text(VAR_CONFIG, encoding="utf-8") def _write_steps(pytester): @@ -27,7 +27,7 @@ def _write_steps(pytester): def test_one_item_per_example_pass_and_fail(pytester): - _write_var_config(pytester) + _write_varar_config(pytester) _write_steps(pytester) (pytester.path / "features").mkdir() (pytester.path / "features/calc.md").write_text(SPEC, encoding="utf-8") @@ -37,7 +37,7 @@ def test_one_item_per_example_pass_and_fail(pytester): def test_k_selection(pytester): - _write_var_config(pytester) + _write_varar_config(pytester) _write_steps(pytester) (pytester.path / "features").mkdir() (pytester.path / "features/calc.md").write_text(SPEC, encoding="utf-8") @@ -59,7 +59,7 @@ def test_duplicate_heading_items_get_unique_node_ids(pytester): "## same heading\n\n" "I add 2\nthe total is 9\n" ) - _write_var_config(pytester) + _write_varar_config(pytester) _write_steps(pytester) (pytester.path / "features").mkdir() (pytester.path / "features/dup.md").write_text(spec, encoding="utf-8") @@ -71,7 +71,7 @@ def test_duplicate_heading_items_get_unique_node_ids(pytester): def test_non_matching_md_is_ignored(pytester): - _write_var_config(pytester) + _write_varar_config(pytester) (pytester.path / "README.md").write_text("# not a spec\n", encoding="utf-8") result = pytester.runpytest() result.assert_outcomes() # nothing collected, no error diff --git a/python/packages/var-pytest/tests/test_dogfood_bundles.py b/python/packages/pytest/tests/test_dogfood_bundles.py similarity index 95% rename from python/packages/var-pytest/tests/test_dogfood_bundles.py rename to python/packages/pytest/tests/test_dogfood_bundles.py index 0fcdfdb7..f15234ea 100644 --- a/python/packages/var-pytest/tests/test_dogfood_bundles.py +++ b/python/packages/pytest/tests/test_dogfood_bundles.py @@ -1,7 +1,7 @@ """test_dogfood_bundles.py — integration tests running real conformance bundles. Each test copies a bundle's example.md and .steps.py into the pytester -tree, runs pytest with a var.config.json pointing at them, and asserts the +tree, runs pytest with a varar.config.json pointing at them, and asserts the outcome matches the bundle's intent as documented by its golden trace.json. Bundles exercised: @@ -13,7 +13,7 @@ markdown-anchored cell-mismatch message Bundles directory is resolved robustly relative to this file: - python/packages/var-pytest/tests/ → parents[4] → repo root + python/packages/pytest/tests/ → parents[4] → repo root """ from __future__ import annotations @@ -39,7 +39,7 @@ def _setup_bundle(pytester, bundle_name: str, steps_filename: str) -> None: example_md = (bundle_dir / "example.md").read_text(encoding="utf-8") steps_py = (bundle_dir / steps_filename).read_text(encoding="utf-8") - (pytester.path / "var.config.json").write_text( + (pytester.path / "varar.config.json").write_text( '{"docs": {"include": ["specs/**/*.md"], "exclude": []},' ' "steps": ["steps/**/*.steps.py"]}', encoding="utf-8", diff --git a/python/packages/var-pytest/tests/test_drift.py b/python/packages/pytest/tests/test_drift.py similarity index 82% rename from python/packages/var-pytest/tests/test_drift.py rename to python/packages/pytest/tests/test_drift.py index c0f0630b..8c28ef50 100644 --- a/python/packages/var-pytest/tests/test_drift.py +++ b/python/packages/pytest/tests/test_drift.py @@ -2,7 +2,7 @@ import json STEPS = ''' -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"n": 0}) @stimulus("I add {int}") def _(state, n): @@ -16,7 +16,7 @@ def _(state, n): def _project(pytester): - (pytester.path / "var.config.json").write_text(VAR_CONFIG, encoding="utf-8") + (pytester.path / "varar.config.json").write_text(VAR_CONFIG, encoding="utf-8") (pytester.path / "steps").mkdir(exist_ok=True) (pytester.path / "steps/calc.steps.py").write_text(STEPS.strip(), encoding="utf-8") (pytester.path / "features").mkdir(exist_ok=True) @@ -27,11 +27,11 @@ def _write_baseline(pytester, examples): "version": 1, "specs": {"features/vault.md": {"sourceHash": "fnv1a:0", "examples": examples}}, } - (pytester.path / "var.lock.json").write_text(json.dumps(lock), encoding="utf-8") + (pytester.path / "varar.lock.json").write_text(json.dumps(lock), encoding="utf-8") def _lock(pytester): - return json.loads((pytester.path / "var.lock.json").read_text(encoding="utf-8")) + return json.loads((pytester.path / "varar.lock.json").read_text(encoding="utf-8")) def test_first_run_records_the_baseline_and_passes(pytester): @@ -48,12 +48,12 @@ def test_a_paragraph_that_stopped_matching_drifts_and_fails(pytester): # Prose now; the baseline says it was an example. (pytester.path / "features/vault.md").write_text("The vault is sealed.\n", encoding="utf-8") _write_baseline(pytester, [{"name": "The vault is sealed", "line": 1}]) - before = (pytester.path / "var.lock.json").read_text(encoding="utf-8") + before = (pytester.path / "varar.lock.json").read_text(encoding="utf-8") result = pytester.runpytest("-v") result.assert_outcomes(failed=1) result.stdout.fnmatch_lines(["*var:drift*"]) # Unacknowledged drift leaves the baseline untouched. - assert (pytester.path / "var.lock.json").read_text(encoding="utf-8") == before + assert (pytester.path / "varar.lock.json").read_text(encoding="utf-8") == before def test_var_update_accepts_drift(pytester): diff --git a/python/packages/var-pytest/tests/test_failures.py b/python/packages/pytest/tests/test_failures.py similarity index 96% rename from python/packages/var-pytest/tests/test_failures.py rename to python/packages/pytest/tests/test_failures.py index 5788cad5..7e9cd0ac 100644 --- a/python/packages/var-pytest/tests/test_failures.py +++ b/python/packages/pytest/tests/test_failures.py @@ -27,7 +27,7 @@ """ CELL_MISMATCH_STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) @sensor("I report the score and grade") @@ -66,7 +66,7 @@ def _(state, row=None): """ PROSE_STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) @sensor("I report the score and grade") @@ -76,7 +76,7 @@ def _(state, row=None): # Steps that register NO defs at all. EMPTY_STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) """ @@ -87,7 +87,7 @@ def _(state, row=None): def _write_fixture(pytester, spec_content: str, steps_content: str) -> None: - (pytester.path / "var.config.json").write_text(VAR_CONFIG, encoding="utf-8") + (pytester.path / "varar.config.json").write_text(VAR_CONFIG, encoding="utf-8") (pytester.path / "steps").mkdir(exist_ok=True) (pytester.path / "steps" / "spec.steps.py").write_text( steps_content.strip(), encoding="utf-8" diff --git a/python/packages/var-pytest/tests/test_fixtures.py b/python/packages/pytest/tests/test_fixtures.py similarity index 95% rename from python/packages/var-pytest/tests/test_fixtures.py rename to python/packages/pytest/tests/test_fixtures.py index fd89a0bb..b3686c29 100644 --- a/python/packages/var-pytest/tests/test_fixtures.py +++ b/python/packages/pytest/tests/test_fixtures.py @@ -20,7 +20,7 @@ # --------------------------------------------------------------------------- STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) @stimulus("I save {int}") @@ -43,7 +43,7 @@ def _(state, count, db): """ def _write_fixture(pytester, spec_content: str, steps_content: str) -> None: - (pytester.path / "var.config.json").write_text(VAR_CONFIG, encoding="utf-8") + (pytester.path / "varar.config.json").write_text(VAR_CONFIG, encoding="utf-8") (pytester.path / "steps").mkdir(exist_ok=True) (pytester.path / "steps" / "spec.steps.py").write_text( steps_content.strip(), encoding="utf-8" @@ -76,7 +76,7 @@ def test_fixtures_injected_into_step_handlers(pytester): # --------------------------------------------------------------------------- CLASSIFICATION_STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) @stimulus("I store {int} in db") diff --git a/python/packages/var-runner/pyproject.toml b/python/packages/runner/pyproject.toml similarity index 61% rename from python/packages/var-runner/pyproject.toml rename to python/packages/runner/pyproject.toml index a361aafa..63d41a8a 100644 --- a/python/packages/var-runner/pyproject.toml +++ b/python/packages/runner/pyproject.toml @@ -1,17 +1,17 @@ [project] -name = "oselvar-var-runner" +name = "varar-runner" version = "0.4.2" -description = "Shared spec discovery + run orchestration for var runners" +description = "Shared spec discovery + run orchestration for varar runners" requires-python = ">=3.11" license = "MIT" -dependencies = ["oselvar-var==0.4.2", "oselvar-var-config==0.4.2"] +dependencies = ["varar==0.4.2", "varar-config==0.4.2"] [project.scripts] -var = "var_runner.cli:main" +varar = "varar_runner.cli:main" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/var_runner"] +packages = ["src/varar_runner"] diff --git a/python/packages/var-runner/src/var_runner/__init__.py b/python/packages/runner/src/varar_runner/__init__.py similarity index 52% rename from python/packages/var-runner/src/var_runner/__init__.py rename to python/packages/runner/src/varar_runner/__init__.py index 1f0852d9..73605fb2 100644 --- a/python/packages/var-runner/src/var_runner/__init__.py +++ b/python/packages/runner/src/varar_runner/__init__.py @@ -2,10 +2,10 @@ __version__ = "0.0.0" -from var_runner.discovery import find_specs, match_spec -from var_runner.render import render_failure -from var_runner.run import RecordingReporter, examples_with_runs, plan_spec -from var_runner.steps import LoadedSteps, load_steps +from varar_runner.discovery import find_specs, match_spec +from varar_runner.render import render_failure +from varar_runner.run import RecordingReporter, examples_with_runs, plan_spec +from varar_runner.steps import LoadedSteps, load_steps __all__ = [ "find_specs", diff --git a/python/packages/var-runner/src/var_runner/baseline_store.py b/python/packages/runner/src/varar_runner/baseline_store.py similarity index 82% rename from python/packages/var-runner/src/var_runner/baseline_store.py rename to python/packages/runner/src/varar_runner/baseline_store.py index 1268a6b7..5caede79 100644 --- a/python/packages/var-runner/src/var_runner/baseline_store.py +++ b/python/packages/runner/src/varar_runner/baseline_store.py @@ -1,6 +1,6 @@ """baseline_store.py — the Node/CLI-equivalent filesystem BaselineStore. -The committed drift baseline lives at the project root as var.lock.json. The +The committed drift baseline lives at the project root as varar.lock.json. The core owns the format; this adapter only reads and writes the raw text. """ from __future__ import annotations @@ -10,7 +10,7 @@ class FileBaselineStore: def __init__(self, root: Path | str) -> None: - self._path = Path(root) / "var.lock.json" + self._path = Path(root) / "varar.lock.json" def read(self) -> str | None: return self._path.read_text(encoding="utf-8") if self._path.exists() else None diff --git a/python/packages/var-runner/src/var_runner/cli.py b/python/packages/runner/src/varar_runner/cli.py similarity index 63% rename from python/packages/var-runner/src/var_runner/cli.py rename to python/packages/runner/src/varar_runner/cli.py index bc4c4ad4..7572b289 100644 --- a/python/packages/var-runner/src/var_runner/cli.py +++ b/python/packages/runner/src/varar_runner/cli.py @@ -1,12 +1,12 @@ """The `var` command-line entry point. -Today it offers a single sub-command, `var init`, which scaffolds a new -project: a `var.config.json`, one Markdown spec, and its step definitions. -Specs then run through pytest (`pytest-var`) or unittest — there is no -`var run` in the Python port; the test framework is the runner. +Today it offers a single sub-command, `varar init`, which scaffolds a new +project: a `varar.config.json`, one Markdown spec, and its step definitions. +Specs then run through pytest (`pytest-varar`) or unittest — there is no +`varar run` in the Python port; the test framework is the runner. -The scaffold mirrors the TypeScript CLI (`@oselvar/var-cli`) byte-for-byte -except for the steps file, so a project started with `var init` looks the same +The scaffold mirrors the TypeScript CLI (`@varar/varar-cli`) byte-for-byte +except for the steps file, so a project started with `varar init` looks the same in every language. The `01-hello.md` spec is language-neutral. """ @@ -17,8 +17,8 @@ from typing import Callable _CONFIG = """{ - "docs": { "include": ["var-examples/**/*.md"], "exclude": [] }, - "steps": ["var-examples/**/*.steps.py"] + "docs": { "include": ["varar-examples/**/*.md"], "exclude": [] }, + "steps": ["varar-examples/**/*.steps.py"] } """ @@ -28,7 +28,7 @@ Then the greeting is "Hello, world!" """ -_EXAMPLE_STEPS = '''from var import steps +_EXAMPLE_STEPS = '''from varar import steps param, stimulus, sensor = steps(lambda: {"greeting": ""}) @@ -44,15 +44,15 @@ def _(state, expected): ''' _FILES: tuple[tuple[str, str], ...] = ( - ("var.config.json", _CONFIG), - ("var-examples/01-hello.md", _EXAMPLE_MD), - ("var-examples/steps/01-hello.steps.py", _EXAMPLE_STEPS), + ("varar.config.json", _CONFIG), + ("varar-examples/01-hello.md", _EXAMPLE_MD), + ("varar-examples/steps/01-hello.steps.py", _EXAMPLE_STEPS), ) -_USAGE = """var — scaffold and run Markdown specs +_USAGE = """varar — scaffold and run Markdown specs Usage: - var init scaffold a new project + varar init scaffold a new project """ diff --git a/python/packages/var-runner/src/var_runner/discovery.py b/python/packages/runner/src/varar_runner/discovery.py similarity index 100% rename from python/packages/var-runner/src/var_runner/discovery.py rename to python/packages/runner/src/varar_runner/discovery.py diff --git a/python/packages/var-runner/src/var_runner/render.py b/python/packages/runner/src/varar_runner/render.py similarity index 91% rename from python/packages/var-runner/src/var_runner/render.py rename to python/packages/runner/src/varar_runner/render.py index d27467d4..438a6c97 100644 --- a/python/packages/var-runner/src/var_runner/render.py +++ b/python/packages/runner/src/varar_runner/render.py @@ -4,8 +4,8 @@ """ from __future__ import annotations -from var_core.cell_diff import ReturnShapeError, is_cell_mismatch_error -from var_core.doc_string_diff import is_doc_string_mismatch_error +from varar_core.cell_diff import ReturnShapeError, is_cell_mismatch_error +from varar_core.doc_string_diff import is_doc_string_mismatch_error def render_failure(error: BaseException, source: str, path: str) -> str: # noqa: ARG001 diff --git a/python/packages/var-runner/src/var_runner/run.py b/python/packages/runner/src/varar_runner/run.py similarity index 80% rename from python/packages/var-runner/src/var_runner/run.py rename to python/packages/runner/src/varar_runner/run.py index 10d76f9e..6ed16d3f 100644 --- a/python/packages/var-runner/src/var_runner/run.py +++ b/python/packages/runner/src/varar_runner/run.py @@ -1,10 +1,10 @@ from __future__ import annotations from collections.abc import Callable from typing import Any -from var_core.execute import CollectPorts, collect_examples -from var_core.parse import parse -from var_core.plan import ExecutionPlan, PlannedExample, plan -from var_core.registry import Registry +from varar_core.execute import CollectPorts, collect_examples +from varar_core.parse import parse +from varar_core.plan import ExecutionPlan, PlannedExample, plan +from varar_core.registry import Registry class RecordingReporter: def __init__(self) -> None: diff --git a/python/packages/var-runner/src/var_runner/steps.py b/python/packages/runner/src/varar_runner/steps.py similarity index 89% rename from python/packages/var-runner/src/var_runner/steps.py rename to python/packages/runner/src/varar_runner/steps.py index b718d530..da9ae67c 100644 --- a/python/packages/var-runner/src/var_runner/steps.py +++ b/python/packages/runner/src/varar_runner/steps.py @@ -4,8 +4,8 @@ from dataclasses import dataclass from pathlib import Path from typing import Any -from var.registry import _reset_builder, build_registry, context_factory -from var_core.registry import Registry +from varar.registry import _reset_builder, build_registry, context_factory +from varar_core.registry import Registry @dataclass(frozen=True, slots=True) diff --git a/python/packages/var-runner/tests/test_cli.py b/python/packages/runner/tests/test_cli.py similarity index 50% rename from python/packages/var-runner/tests/test_cli.py rename to python/packages/runner/tests/test_cli.py index a26c4c4a..41314d5d 100644 --- a/python/packages/var-runner/tests/test_cli.py +++ b/python/packages/runner/tests/test_cli.py @@ -1,6 +1,6 @@ from pathlib import Path -from var_runner.cli import run_init +from varar_runner.cli import run_init def _capture() -> tuple[list[str], object]: @@ -13,19 +13,19 @@ def test_init_scaffolds_the_three_files(tmp_path: Path) -> None: exit_code = run_init(tmp_path, write) assert exit_code == 0 - assert (tmp_path / "var.config.json").exists() - assert (tmp_path / "var-examples/01-hello.md").exists() - steps = (tmp_path / "var-examples/steps/01-hello.steps.py").read_text(encoding="utf-8") - assert "from var import steps" in steps + assert (tmp_path / "varar.config.json").exists() + assert (tmp_path / "varar-examples/01-hello.md").exists() + steps = (tmp_path / "varar-examples/steps/01-hello.steps.py").read_text(encoding="utf-8") + assert "from varar import steps" in steps assert "@stimulus" in steps and "@sensor" in steps assert all(line.startswith("created ") for line in lines) def test_init_skips_existing_files(tmp_path: Path) -> None: - (tmp_path / "var.config.json").write_text("{}\n", encoding="utf-8") + (tmp_path / "varar.config.json").write_text("{}\n", encoding="utf-8") lines, write = _capture() run_init(tmp_path, write) - assert (tmp_path / "var.config.json").read_text(encoding="utf-8") == "{}\n" - assert any("skipped var.config.json" in line for line in lines) + assert (tmp_path / "varar.config.json").read_text(encoding="utf-8") == "{}\n" + assert any("skipped varar.config.json" in line for line in lines) diff --git a/python/packages/var-runner/tests/test_discovery.py b/python/packages/runner/tests/test_discovery.py similarity index 96% rename from python/packages/var-runner/tests/test_discovery.py rename to python/packages/runner/tests/test_discovery.py index 80ef3c9f..7b0f61bb 100644 --- a/python/packages/var-runner/tests/test_discovery.py +++ b/python/packages/runner/tests/test_discovery.py @@ -1,6 +1,6 @@ from pathlib import Path -from var_runner.discovery import find_specs, match_spec +from varar_runner.discovery import find_specs, match_spec def _touch(root: Path, rel: str) -> Path: @@ -74,7 +74,7 @@ def test_single_star_does_not_cross_slash(tmp_path: Path) -> None: def test_specs_outside_root_via_parent_glob(tmp_path: Path) -> None: """A spec in a SIBLING of the config root is reachable via a ``../`` glob. - This backs pointing ``var.config.json`` at a shared corpus that lives outside the + This backs pointing ``varar.config.json`` at a shared corpus that lives outside the package (e.g. ``../conformance/bundles``): ``relative_to(..., walk_up=True)`` yields a ``../corpus/...`` path that matches a ``../corpus/**`` glob. """ diff --git a/python/packages/var-runner/tests/test_public_api.py b/python/packages/runner/tests/test_public_api.py similarity index 95% rename from python/packages/var-runner/tests/test_public_api.py rename to python/packages/runner/tests/test_public_api.py index 89bb519f..a21a4100 100644 --- a/python/packages/var-runner/tests/test_public_api.py +++ b/python/packages/runner/tests/test_public_api.py @@ -1,5 +1,5 @@ """Smoke-test: every public symbol is importable from the top-level package.""" -from var_runner import ( +from varar_runner import ( find_specs, match_spec, load_steps, diff --git a/python/packages/var-runner/tests/test_render.py b/python/packages/runner/tests/test_render.py similarity index 92% rename from python/packages/var-runner/tests/test_render.py rename to python/packages/runner/tests/test_render.py index 513e7bf7..2b1e321a 100644 --- a/python/packages/var-runner/tests/test_render.py +++ b/python/packages/runner/tests/test_render.py @@ -1,9 +1,9 @@ """Tests for render_failure — pure human-readable rendering of diff errors.""" -from var_core.cell_diff import CellDiff, CellMismatchError, ReturnShapeError -from var_core.doc_string_diff import DocStringDiff, DocStringMismatchError -from var_core.span import Span +from varar_core.cell_diff import CellDiff, CellMismatchError, ReturnShapeError +from varar_core.doc_string_diff import DocStringDiff, DocStringMismatchError +from varar_core.span import Span -from var_runner.render import render_failure +from varar_runner.render import render_failure def _span(line: int) -> Span: diff --git a/python/packages/var-runner/tests/test_run.py b/python/packages/runner/tests/test_run.py similarity index 87% rename from python/packages/var-runner/tests/test_run.py rename to python/packages/runner/tests/test_run.py index 0cc8707d..4119c543 100644 --- a/python/packages/var-runner/tests/test_run.py +++ b/python/packages/runner/tests/test_run.py @@ -1,8 +1,8 @@ -from var_runner.steps import load_steps -from var_runner.run import plan_spec, examples_with_runs, RecordingReporter +from varar_runner.steps import load_steps +from varar_runner.run import plan_spec, examples_with_runs, RecordingReporter STEPS = ''' -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"n": 0}) @stimulus("I add {int}") def _(state, n): diff --git a/python/packages/var-runner/tests/test_steps.py b/python/packages/runner/tests/test_steps.py similarity index 93% rename from python/packages/var-runner/tests/test_steps.py rename to python/packages/runner/tests/test_steps.py index 61b9856b..17ee4e99 100644 --- a/python/packages/var-runner/tests/test_steps.py +++ b/python/packages/runner/tests/test_steps.py @@ -1,7 +1,7 @@ -from var_runner.steps import load_steps +from varar_runner.steps import load_steps STEPS = ''' -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"n": 0}) @stimulus("I add {int}") def _(state, n): diff --git a/python/packages/var-unittest/pyproject.toml b/python/packages/unittest/pyproject.toml similarity index 56% rename from python/packages/var-unittest/pyproject.toml rename to python/packages/unittest/pyproject.toml index d7311f6f..1f5b2268 100644 --- a/python/packages/var-unittest/pyproject.toml +++ b/python/packages/unittest/pyproject.toml @@ -1,14 +1,14 @@ [project] -name = "oselvar-var-unittest" +name = "varar-unittest" version = "0.4.2" description = "unittest adapter for Markdown-native BDD" requires-python = ">=3.11" license = "MIT" -dependencies = ["oselvar-var==0.4.2", "oselvar-var-config==0.4.2", "oselvar-var-core==0.4.2", "oselvar-var-runner==0.4.2"] +dependencies = ["varar==0.4.2", "varar-config==0.4.2", "varar-core==0.4.2", "varar-runner==0.4.2"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/var_unittest"] +packages = ["src/varar_unittest"] diff --git a/python/packages/var-unittest/src/var_unittest/__init__.py b/python/packages/unittest/src/varar_unittest/__init__.py similarity index 85% rename from python/packages/var-unittest/src/var_unittest/__init__.py rename to python/packages/unittest/src/varar_unittest/__init__.py index 80f9bb61..71892d1a 100644 --- a/python/packages/var-unittest/src/var_unittest/__init__.py +++ b/python/packages/unittest/src/varar_unittest/__init__.py @@ -1,11 +1,11 @@ """unittest adapter for var. -One call in a test module turns every spec matched by var.config.json into +One call in a test module turns every spec matched by varar.config.json into generated ``unittest.TestCase`` classes — one class per spec file, one test method per example:: # test_var.py - from var_unittest import generate_tests + from varar_unittest import generate_tests generate_tests(globals()) @@ -22,17 +22,17 @@ from pathlib import Path from typing import Any, Callable -from var_config import read_var_config -from var_core.cell_diff import ReturnShapeError, is_cell_mismatch_error -from var_core.diagnostics import drift_detected -from var_core.doc_string_diff import is_doc_string_mismatch_error -from var_core.drift import reconcile_drift -from var_core.execute import is_unexpected_pass_error -from var_runner.baseline_store import create_file_baseline_store -from var_runner.discovery import find_specs -from var_runner.render import render_failure -from var_runner.run import RecordingReporter, examples_with_runs, plan_spec -from var_runner.steps import LoadedSteps, load_steps +from varar_config import read_varar_config +from varar_core.cell_diff import ReturnShapeError, is_cell_mismatch_error +from varar_core.diagnostics import drift_detected +from varar_core.doc_string_diff import is_doc_string_mismatch_error +from varar_core.drift import reconcile_drift +from varar_core.execute import is_unexpected_pass_error +from varar_runner.baseline_store import create_file_baseline_store +from varar_runner.discovery import find_specs +from varar_runner.render import render_failure +from varar_runner.run import RecordingReporter, examples_with_runs, plan_spec +from varar_runner.steps import LoadedSteps, load_steps __version__ = "0.0.0" @@ -40,7 +40,7 @@ def generate_tests(namespace: dict[str, Any], root: str | Path | None = None) -> None: """Generate unittest test cases for every spec into *namespace*. - Reads ``var.config.json`` from *root* (default: the directory of the + Reads ``varar.config.json`` from *root* (default: the directory of the module *namespace* belongs to, via its ``__file__``), loads the step definition files it globs, and assigns one ``unittest.TestCase`` subclass per matched spec file into *namespace* — one ``test_*`` method per @@ -49,7 +49,7 @@ def generate_tests(namespace: dict[str, Any], root: str | Path | None = None) -> if root is None: root = Path(namespace["__file__"]).parent root = Path(os.path.abspath(root)) - cfg = read_var_config(root) + cfg = read_varar_config(root) loaded = load_steps(cfg.steps, root) store = create_file_baseline_store(root) module_name = namespace.get("__name__") diff --git a/python/packages/var-unittest/tests/conftest.py b/python/packages/unittest/tests/conftest.py similarity index 97% rename from python/packages/var-unittest/tests/conftest.py rename to python/packages/unittest/tests/conftest.py index 98481351..88b6f709 100644 --- a/python/packages/var-unittest/tests/conftest.py +++ b/python/packages/unittest/tests/conftest.py @@ -14,7 +14,7 @@ import pytest -from var_unittest import generate_tests +from varar_unittest import generate_tests class Harness: diff --git a/python/packages/var-unittest/tests/test_async.py b/python/packages/unittest/tests/test_async.py similarity index 95% rename from python/packages/var-unittest/tests/test_async.py rename to python/packages/unittest/tests/test_async.py index baf25554..d4b78246 100644 --- a/python/packages/var-unittest/tests/test_async.py +++ b/python/packages/unittest/tests/test_async.py @@ -12,7 +12,7 @@ ASYNC_STEPS = """\ import asyncio -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"value": 0}) @@ -50,7 +50,7 @@ async def _(state, expected): def _write(harness, spec: str) -> None: - harness.write("var.config.json", VAR_CONFIG) + harness.write("varar.config.json", VAR_CONFIG) harness.write("steps/async_calc.steps.py", ASYNC_STEPS) harness.write("features/async.md", spec) diff --git a/python/packages/var-unittest/tests/test_collection.py b/python/packages/unittest/tests/test_collection.py similarity index 95% rename from python/packages/var-unittest/tests/test_collection.py rename to python/packages/unittest/tests/test_collection.py index d08f2732..a166c310 100644 --- a/python/packages/var-unittest/tests/test_collection.py +++ b/python/packages/unittest/tests/test_collection.py @@ -4,7 +4,7 @@ import unittest STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"n": 0}) @stimulus("I add {int}") def _(state, n): @@ -25,7 +25,7 @@ def _(state, total): def _write_calc(harness, spec: str = SPEC) -> None: - harness.write("var.config.json", VAR_CONFIG) + harness.write("varar.config.json", VAR_CONFIG) harness.write("steps/calc.steps.py", STEPS) harness.write("features/calc.md", spec) @@ -82,7 +82,7 @@ def test_duplicate_heading_methods_get_unique_names(harness): def test_non_matching_md_is_ignored(harness): - harness.write("var.config.json", VAR_CONFIG) + harness.write("varar.config.json", VAR_CONFIG) harness.write("README.md", "# not a spec\n") ns = harness.generate() assert not [ diff --git a/python/packages/var-unittest/tests/test_dogfood_bundles.py b/python/packages/unittest/tests/test_dogfood_bundles.py similarity index 97% rename from python/packages/var-unittest/tests/test_dogfood_bundles.py rename to python/packages/unittest/tests/test_dogfood_bundles.py index 8f43bb9e..41d514ac 100644 --- a/python/packages/var-unittest/tests/test_dogfood_bundles.py +++ b/python/packages/unittest/tests/test_dogfood_bundles.py @@ -26,7 +26,7 @@ def _setup_bundle(harness, bundle_name: str, steps_filename: str) -> None: bundle_dir = _BUNDLES / bundle_name - harness.write("var.config.json", VAR_CONFIG) + harness.write("varar.config.json", VAR_CONFIG) harness.write("specs/example.md", (bundle_dir / "example.md").read_text(encoding="utf-8")) harness.write( f"steps/{steps_filename}", (bundle_dir / steps_filename).read_text(encoding="utf-8") diff --git a/python/packages/var-unittest/tests/test_drift.py b/python/packages/unittest/tests/test_drift.py similarity index 82% rename from python/packages/var-unittest/tests/test_drift.py rename to python/packages/unittest/tests/test_drift.py index 5bcd9ba1..a5671886 100644 --- a/python/packages/var-unittest/tests/test_drift.py +++ b/python/packages/unittest/tests/test_drift.py @@ -4,7 +4,7 @@ import json STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {"n": 0}) @stimulus("I add {int}") def _(state, n): @@ -18,18 +18,18 @@ def _(state, n): def _project(harness, spec_rel: str, spec: str) -> None: - harness.write("var.config.json", VAR_CONFIG) + harness.write("varar.config.json", VAR_CONFIG) harness.write("steps/calc.steps.py", STEPS) harness.write(spec_rel, spec) def _baseline(harness, spec_key: str, examples) -> None: lock = {"version": 1, "specs": {spec_key: {"sourceHash": "fnv1a:0", "examples": examples}}} - harness.write("var.lock.json", json.dumps(lock)) + harness.write("varar.lock.json", json.dumps(lock)) def _lock(harness): - return json.loads((harness.root / "var.lock.json").read_text(encoding="utf-8")) + return json.loads((harness.root / "varar.lock.json").read_text(encoding="utf-8")) def test_first_run_records_the_baseline(harness): @@ -44,12 +44,12 @@ def test_first_run_records_the_baseline(harness): def test_a_paragraph_that_stopped_matching_drifts_and_fails(harness): _project(harness, "features/vault.md", "The vault is sealed.\n") _baseline(harness, "features/vault.md", [{"name": "The vault is sealed", "line": 1}]) - before = (harness.root / "var.lock.json").read_text(encoding="utf-8") + before = (harness.root / "varar.lock.json").read_text(encoding="utf-8") result, output = harness.generate_and_run() assert not result.wasSuccessful() assert "The vault is sealed" in output # Unacknowledged drift leaves the baseline untouched. - assert (harness.root / "var.lock.json").read_text(encoding="utf-8") == before + assert (harness.root / "varar.lock.json").read_text(encoding="utf-8") == before def test_var_update_accepts_drift(harness, monkeypatch): diff --git a/python/packages/var-unittest/tests/test_failures.py b/python/packages/unittest/tests/test_failures.py similarity index 92% rename from python/packages/var-unittest/tests/test_failures.py rename to python/packages/unittest/tests/test_failures.py index 78687ab3..47149f39 100644 --- a/python/packages/var-unittest/tests/test_failures.py +++ b/python/packages/unittest/tests/test_failures.py @@ -26,7 +26,7 @@ """ CELL_MISMATCH_STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) @sensor("I report the score and grade") @@ -43,7 +43,7 @@ def _(state, row=None): """ EMPTY_STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) """ @@ -56,7 +56,7 @@ def _(state, row=None): """ RAISING_STEPS = """\ -from var import steps +from varar import steps param, stimulus, sensor = steps(lambda: {}) @stimulus("I explode") @@ -66,7 +66,7 @@ def _(state): def test_cell_mismatch_is_a_failure_with_rendered_diff(harness): - harness.write("var.config.json", VAR_CONFIG) + harness.write("varar.config.json", VAR_CONFIG) harness.write("steps/spec.steps.py", CELL_MISMATCH_STEPS) harness.write("features/spec.md", CELL_MISMATCH_SPEC) result, _output = harness.generate_and_run() @@ -82,7 +82,7 @@ def test_cell_mismatch_is_a_failure_with_rendered_diff(harness): def test_step_exception_is_an_error_not_a_failure(harness): - harness.write("var.config.json", VAR_CONFIG) + harness.write("varar.config.json", VAR_CONFIG) harness.write("steps/spec.steps.py", RAISING_STEPS) harness.write("features/spec.md", RAISING_SPEC) result, _output = harness.generate_and_run() @@ -96,7 +96,7 @@ def test_step_exception_is_an_error_not_a_failure(harness): def test_prose_paragraph_with_no_matching_steps_is_silently_ignored(harness): - harness.write("var.config.json", VAR_CONFIG) + harness.write("varar.config.json", VAR_CONFIG) harness.write("steps/spec.steps.py", EMPTY_STEPS) harness.write("features/spec.md", PROSE_ONLY_SPEC) result, _output = harness.generate_and_run() diff --git a/python/packages/var-config/src/var_config/__init__.py b/python/packages/var-config/src/var_config/__init__.py deleted file mode 100644 index a515ede2..00000000 --- a/python/packages/var-config/src/var_config/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from var_config.config import VarConfig, read_var_config - -__all__ = ["VarConfig", "read_var_config"] diff --git a/python/packages/var/tests/test_smoke.py b/python/packages/var/tests/test_smoke.py deleted file mode 100644 index 9cffec20..00000000 --- a/python/packages/var/tests/test_smoke.py +++ /dev/null @@ -1,5 +0,0 @@ -import var - - -def test_version(): - assert var.__version__ == "0.0.0" diff --git a/python/packages/var/pyproject.toml b/python/packages/varar/pyproject.toml similarity index 76% rename from python/packages/var/pyproject.toml rename to python/packages/varar/pyproject.toml index 5fce61f2..1d362d64 100644 --- a/python/packages/var/pyproject.toml +++ b/python/packages/varar/pyproject.toml @@ -1,12 +1,12 @@ [project] -name = "oselvar-var" +name = "varar" version = "0.4.2" -description = "Markdown-native BDD — pure Python core (port of @oselvar/var)" +description = "Markdown-native BDD — pure Python core (port of @varar/varar)" requires-python = ">=3.11" license = "MIT" dependencies = [ "cucumber-expressions==20.0.0", - "oselvar-var-core==0.4.2", + "varar-core==0.4.2", ] [build-system] @@ -14,4 +14,4 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/var"] +packages = ["src/varar"] diff --git a/python/packages/var/src/var/__init__.py b/python/packages/varar/src/varar/__init__.py similarity index 77% rename from python/packages/var/src/var/__init__.py rename to python/packages/varar/src/varar/__init__.py index e050e217..a868f5de 100644 --- a/python/packages/var/src/var/__init__.py +++ b/python/packages/varar/src/varar/__init__.py @@ -2,6 +2,6 @@ __version__ = "0.0.0" -from var.internal import steps +from varar.internal import steps __all__ = ["steps"] diff --git a/python/packages/var/src/var/internal.py b/python/packages/varar/src/varar/internal.py similarity index 97% rename from python/packages/var/src/var/internal.py rename to python/packages/varar/src/varar/internal.py index cb264036..2e6b7a28 100644 --- a/python/packages/var/src/var/internal.py +++ b/python/packages/varar/src/varar/internal.py @@ -5,8 +5,8 @@ from re import Pattern from typing import Any, Callable, Optional -from var_core.registry import Registry, add_step, create_registry, define_parameter_type -from var_core.step_role import StepKind +from varar_core.registry import Registry, add_step, create_registry, define_parameter_type +from varar_core.step_role import StepKind # --------------------------------------------------------------------------- # Module-level mutable builder state (mirrors the module-scope vars in internal.ts) diff --git a/python/packages/var/src/var/registry.py b/python/packages/varar/src/varar/registry.py similarity index 53% rename from python/packages/var/src/var/registry.py rename to python/packages/varar/src/varar/registry.py index 4cf7c557..a12a18bf 100644 --- a/python/packages/var/src/var/registry.py +++ b/python/packages/varar/src/varar/registry.py @@ -1,6 +1,6 @@ -"""Adapter-only glue (mirrors @oselvar/var/registry): build the registry and +"""Adapter-only glue (mirrors @varar/varar/registry): build the registry and context factory from the module-scope accumulator, and reset it between runs.""" -from var.internal import _custom_parameter_types, _reset_builder, build_registry, context_factory +from varar.internal import _custom_parameter_types, _reset_builder, build_registry, context_factory __all__ = ["build_registry", "context_factory", "_reset_builder", "_custom_parameter_types"] diff --git a/python/packages/var/tests/test_conformance.py b/python/packages/varar/tests/test_conformance.py similarity index 91% rename from python/packages/var/tests/test_conformance.py rename to python/packages/varar/tests/test_conformance.py index 41344f93..7be6a76c 100644 --- a/python/packages/var/tests/test_conformance.py +++ b/python/packages/varar/tests/test_conformance.py @@ -21,13 +21,13 @@ import pytest -from var_core.canonical_json import canonical_stringify -from var_core.conformance import run_conformance, to_plan_artifact, to_registry_artifact, to_var_doc_artifact -from var.registry import _custom_parameter_types, _reset_builder, build_registry, context_factory -from var_core.parse import parse -from var_core.plan import plan as build_plan +from varar_core.canonical_json import canonical_stringify +from varar_core.conformance import run_conformance, to_plan_artifact, to_registry_artifact, to_var_doc_artifact +from varar.registry import _custom_parameter_types, _reset_builder, build_registry, context_factory +from varar_core.parse import parse +from varar_core.plan import plan as build_plan -# python/packages/var/tests/ -> parents[4] = repo root +# python/packages/varar/tests/ -> parents[4] = repo root BUNDLES_DIR = Path(__file__).resolve().parents[4] / "conformance" / "bundles" BUNDLES = sorted(p for p in BUNDLES_DIR.iterdir() if p.is_dir()) diff --git a/python/packages/var/tests/test_custom_parameter_types.py b/python/packages/varar/tests/test_custom_parameter_types.py similarity index 90% rename from python/packages/var/tests/test_custom_parameter_types.py rename to python/packages/varar/tests/test_custom_parameter_types.py index 9faff5e3..461b858e 100644 --- a/python/packages/var/tests/test_custom_parameter_types.py +++ b/python/packages/varar/tests/test_custom_parameter_types.py @@ -2,8 +2,8 @@ import pytest -from var import steps -from var.registry import _custom_parameter_types, _reset_builder +from varar import steps +from varar.registry import _custom_parameter_types, _reset_builder def test_projects_name_and_pattern_source(): diff --git a/python/packages/varar/tests/test_smoke.py b/python/packages/varar/tests/test_smoke.py new file mode 100644 index 00000000..04146f9b --- /dev/null +++ b/python/packages/varar/tests/test_smoke.py @@ -0,0 +1,5 @@ +import varar + + +def test_version(): + assert varar.__version__ == "0.0.0" diff --git a/python/packages/var/tests/test_steps.py b/python/packages/varar/tests/test_steps.py similarity index 97% rename from python/packages/var/tests/test_steps.py rename to python/packages/varar/tests/test_steps.py index d5bc514d..a153b227 100644 --- a/python/packages/var/tests/test_steps.py +++ b/python/packages/varar/tests/test_steps.py @@ -3,8 +3,8 @@ import pytest -from var.registry import _reset_builder, build_registry, context_factory -from var import steps +from varar.registry import _reset_builder, build_registry, context_factory +from varar import steps def test_two_decorators_register_with_correct_kinds() -> None: diff --git a/python/pyproject.toml b/python/pyproject.toml index 56f789d1..594abbd5 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -2,10 +2,10 @@ members = ["packages/*"] [tool.uv.sources] -oselvar-var = { workspace = true } -oselvar-var-config = { workspace = true } -oselvar-var-core = { workspace = true } -oselvar-var-runner = { workspace = true } +varar = { workspace = true } +varar-config = { workspace = true } +varar-core = { workspace = true } +varar-runner = { workspace = true } [dependency-groups] dev = [ @@ -25,7 +25,7 @@ addopts = "--import-mode=importlib" # conformance bundles intentionally reuse expressions across bundles # (`I echo…`, `I have {int} cukes`, `I greet {string}`). So this is a # COLLISION-FREE subset — a mix of passing bundles and two that fail by design -# (07 row-check-mismatch, 09 expected-message-mismatch). See var.config.json. +# (07 row-check-mismatch, 09 expected-message-mismatch). See varar.config.json. [tool.ruff] line-length = 100 @@ -36,13 +36,13 @@ target-version = "py311" # Text summary on stdout; HTML in htmlcov/; lcov in coverage.lcov for # editor/CI integrations. Mirrors typescript's `pnpm test:coverage`. [tool.coverage.run] -source_pkgs = ["var", "var_config", "var_core", "var_pytest", "var_runner", "var_unittest"] +source_pkgs = ["varar", "varar_config", "varar_core", "varar_pytest", "varar_runner", "varar_unittest"] [tool.coverage.report] show_missing = true # Ratchet: ~2 points under the level measured when the threshold was # introduced (2026-07-03: 66%). Raise as coverage grows; never lower. -# NOTE var_pytest's own plugin modules load before pytest-cov starts +# NOTE varar_pytest's own plugin modules load before pytest-cov starts # measuring, so their coverage is understated — the real level is higher. fail_under = 64 diff --git a/python/uv.lock b/python/uv.lock index 251afc22..78ae150b 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -4,12 +4,12 @@ requires-python = ">=3.11" [manifest] members = [ - "oselvar-var", - "oselvar-var-config", - "oselvar-var-core", - "oselvar-var-runner", - "oselvar-var-unittest", - "pytest-var", + "pytest-varar", + "varar", + "varar-config", + "varar-core", + "varar-runner", + "varar-unittest", ] [manifest.dependency-groups] @@ -135,71 +135,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] -[[package]] -name = "oselvar-var" -version = "0.4.2" -source = { editable = "packages/var" } -dependencies = [ - { name = "cucumber-expressions" }, - { name = "oselvar-var-core" }, -] - -[package.metadata] -requires-dist = [ - { name = "cucumber-expressions", specifier = "==20.0.0" }, - { name = "oselvar-var-core", editable = "packages/var-core" }, -] - -[[package]] -name = "oselvar-var-config" -version = "0.4.2" -source = { editable = "packages/var-config" } - -[[package]] -name = "oselvar-var-core" -version = "0.4.2" -source = { editable = "packages/var-core" } -dependencies = [ - { name = "cucumber-expressions" }, -] - -[package.metadata] -requires-dist = [{ name = "cucumber-expressions", specifier = "==20.0.0" }] - -[[package]] -name = "oselvar-var-runner" -version = "0.4.2" -source = { editable = "packages/var-runner" } -dependencies = [ - { name = "oselvar-var" }, - { name = "oselvar-var-config" }, -] - -[package.metadata] -requires-dist = [ - { name = "oselvar-var", editable = "packages/var" }, - { name = "oselvar-var-config", editable = "packages/var-config" }, -] - -[[package]] -name = "oselvar-var-unittest" -version = "0.4.2" -source = { editable = "packages/var-unittest" } -dependencies = [ - { name = "oselvar-var" }, - { name = "oselvar-var-config" }, - { name = "oselvar-var-core" }, - { name = "oselvar-var-runner" }, -] - -[package.metadata] -requires-dist = [ - { name = "oselvar-var", editable = "packages/var" }, - { name = "oselvar-var-config", editable = "packages/var-config" }, - { name = "oselvar-var-core", editable = "packages/var-core" }, - { name = "oselvar-var-runner", editable = "packages/var-runner" }, -] - [[package]] name = "packaging" version = "26.2" @@ -258,24 +193,24 @@ wheels = [ ] [[package]] -name = "pytest-var" +name = "pytest-varar" version = "0.4.2" -source = { editable = "packages/var-pytest" } +source = { editable = "packages/pytest" } dependencies = [ - { name = "oselvar-var" }, - { name = "oselvar-var-config" }, - { name = "oselvar-var-core" }, - { name = "oselvar-var-runner" }, { name = "pytest" }, + { name = "varar" }, + { name = "varar-config" }, + { name = "varar-core" }, + { name = "varar-runner" }, ] [package.metadata] requires-dist = [ - { name = "oselvar-var", editable = "packages/var" }, - { name = "oselvar-var-config", editable = "packages/var-config" }, - { name = "oselvar-var-core", editable = "packages/var-core" }, - { name = "oselvar-var-runner", editable = "packages/var-runner" }, { name = "pytest", specifier = ">=8" }, + { name = "varar", editable = "packages/varar" }, + { name = "varar-config", editable = "packages/config" }, + { name = "varar-core", editable = "packages/core" }, + { name = "varar-runner", editable = "packages/runner" }, ] [[package]] @@ -356,3 +291,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] + +[[package]] +name = "varar" +version = "0.4.2" +source = { editable = "packages/varar" } +dependencies = [ + { name = "cucumber-expressions" }, + { name = "varar-core" }, +] + +[package.metadata] +requires-dist = [ + { name = "cucumber-expressions", specifier = "==20.0.0" }, + { name = "varar-core", editable = "packages/core" }, +] + +[[package]] +name = "varar-config" +version = "0.4.2" +source = { editable = "packages/config" } + +[[package]] +name = "varar-core" +version = "0.4.2" +source = { editable = "packages/core" } +dependencies = [ + { name = "cucumber-expressions" }, +] + +[package.metadata] +requires-dist = [{ name = "cucumber-expressions", specifier = "==20.0.0" }] + +[[package]] +name = "varar-runner" +version = "0.4.2" +source = { editable = "packages/runner" } +dependencies = [ + { name = "varar" }, + { name = "varar-config" }, +] + +[package.metadata] +requires-dist = [ + { name = "varar", editable = "packages/varar" }, + { name = "varar-config", editable = "packages/config" }, +] + +[[package]] +name = "varar-unittest" +version = "0.4.2" +source = { editable = "packages/unittest" } +dependencies = [ + { name = "varar" }, + { name = "varar-config" }, + { name = "varar-core" }, + { name = "varar-runner" }, +] + +[package.metadata] +requires-dist = [ + { name = "varar", editable = "packages/varar" }, + { name = "varar-config", editable = "packages/config" }, + { name = "varar-core", editable = "packages/core" }, + { name = "varar-runner", editable = "packages/runner" }, +] diff --git a/python/var.config.json b/python/varar.config.json similarity index 92% rename from python/var.config.json rename to python/varar.config.json index 9266eca0..d0e37ac5 100644 --- a/python/var.config.json +++ b/python/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../conformance/config/var.config.schema.json", + "$schema": "../conformance/config/varar.config.schema.json", "docs": { "include": [ "../conformance/bundles/01-roman-numerals/*.md", diff --git a/release/lib.sh b/release/lib.sh index 08229358..408a2744 100755 --- a/release/lib.sh +++ b/release/lib.sh @@ -6,8 +6,8 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # Single source of truth for whether the Rust port ships to crates.io. While it # is 0 (parked), two targets stay in lock-step: 65-crates-io.sh reports OK -# without publishing, AND 70-var-examples.sh omits the rust-* samples (their -# `var-core` path dependency can't resolve in var-examples until the crates are +# without publishing, AND 70-varar-examples.sh omits the rust-* samples (their +# `var-core` path dependency can't resolve in varar-examples until the crates are # on crates.io — pinning it to an unpublished version would ship a broken # sample). Flip to 1 only once the crates are publishable — see the go-live # checklist in release/targets/65-crates-io.sh. @@ -38,13 +38,13 @@ stamp_java_samples() { } # Stamp into every Ruby workspace gem: the gemspec version, the -# gemspec's internal (oselvar-*) dependency pins, the VERSION constants, and the +# gemspec's internal (varar-*) dependency pins, the VERSION constants, and the # lockfile. External dep pins (cucumber, minitest, rspec, ...) are left alone. # perl -pi, not sed -i: BSD/GNU-portable in-place. stamp_ruby() { local version="$1" f perl -pi -e "s/^(\s*s\.version\s*=\s*)'[^']*'/\${1}'$version'/" ruby/packages/*/*.gemspec - perl -pi -e "s/(add_dependency\s+'oselvar-[a-z0-9-]+',\s*)'[^']*'/\${1}'$version'/" ruby/packages/*/*.gemspec + perl -pi -e "s/(add_dependency\s+'varar-[a-z0-9-]+',\s*)'[^']*'/\${1}'$version'/" ruby/packages/*/*.gemspec while IFS= read -r f; do perl -pi -e "s/(VERSION\s*=\s*)'[^']*'/\${1}'$version'/" "$f" done < <(grep -rlE "VERSION\s*=\s*'" ruby/packages/*/lib) @@ -80,14 +80,14 @@ changelog_body() { # Prints the .vsix path on stdout (all build noise goes to stderr). build_vsix() { local version="$1" - local vsix="$REPO_ROOT/release/dist/oselvar-var-$version-$(git -C "$REPO_ROOT" rev-parse --short HEAD).vsix" + local vsix="$REPO_ROOT/release/dist/varar-$version-$(git -C "$REPO_ROOT" rev-parse --short HEAD).vsix" [[ -f "$vsix" ]] && { echo "$vsix"; return 0; } local manifest_version - manifest_version="$(jq -r .version "$REPO_ROOT/typescript/packages/var-vscode/package.json")" + manifest_version="$(jq -r .version "$REPO_ROOT/typescript/packages/vscode/package.json")" [[ "$manifest_version" == "$version" ]] || die "var-vscode/package.json is at $manifest_version, not $version — stamp has not run" mkdir -p "$REPO_ROOT/release/dist" - (cd "$REPO_ROOT/typescript" && pnpm install --frozen-lockfile >&2 && pnpm --filter oselvar-var build >&2) - (cd "$REPO_ROOT/typescript/packages/var-vscode" && vsce package --no-dependencies -o "$vsix" >&2) + (cd "$REPO_ROOT/typescript" && pnpm install --frozen-lockfile >&2 && pnpm --filter varar build >&2) + (cd "$REPO_ROOT/typescript/packages/vscode" && vsce package --no-dependencies -o "$vsix" >&2) echo "$vsix" } diff --git a/release/release.env b/release/release.env index 79b7606a..ec9842bd 100644 --- a/release/release.env +++ b/release/release.env @@ -2,8 +2,8 @@ # The vault is "Vár" in the my.1password.com account, referenced by ID because # op:// URIs reject non-ASCII vault names. release.sh exports OP_ACCOUNT. # op vault get qtfavpq3rme5n4jbzphovovov4 --account my.1password.com -NPM_TOKEN=op://qtfavpq3rme5n4jbzphovovov4/npm-oselvar/token -UV_PUBLISH_TOKEN=op://qtfavpq3rme5n4jbzphovovov4/pypi-oselvar/token +NPM_TOKEN=op://qtfavpq3rme5n4jbzphovovov4/npm-varar/token +UV_PUBLISH_TOKEN=op://qtfavpq3rme5n4jbzphovovov4/pypi-varar/token CENTRAL_USERNAME=op://qtfavpq3rme5n4jbzphovovov4/sonatype-central/username CENTRAL_PASSWORD=op://qtfavpq3rme5n4jbzphovovov4/sonatype-central/password MAVEN_GPG_PASSPHRASE=op://qtfavpq3rme5n4jbzphovovov4/maven-gpg/passphrase diff --git a/release/release.sh b/release/release.sh index 707fb6bb..53417758 100755 --- a/release/release.sh +++ b/release/release.sh @@ -33,9 +33,9 @@ git fetch origin main --tags die "local main and origin/main differ — run release right after prepare (git pull if needed)" # The prepared version is whatever prepare stamped into the manifests. -VERSION="$(jq -r .version typescript/packages/var/package.json)" +VERSION="$(jq -r .version typescript/packages/varar/package.json)" is_semver "$VERSION" || - die "typescript/packages/var/package.json version '$VERSION' is not semver — did prepare run?" + die "typescript/packages/varar/package.json version '$VERSION' is not semver — did prepare run?" TAG="v$VERSION" git log -1 --pretty=%s | grep -qx "Release $TAG" || diff --git a/release/stamp_python.py b/release/stamp_python.py index 35325102..c104ed82 100644 --- a/release/stamp_python.py +++ b/release/stamp_python.py @@ -11,12 +11,12 @@ VERSION = sys.argv[1] INTERNAL = { - "oselvar-var", - "oselvar-var-config", - "oselvar-var-core", - "oselvar-var-runner", - "pytest-var", - "oselvar-var-unittest", + "varar", + "varar-config", + "varar-core", + "varar-runner", + "pytest-varar", + "varar-unittest", } diff --git a/release/targets/20-rubygems.sh b/release/targets/20-rubygems.sh index cc1c67b6..06a4d2f6 100755 --- a/release/targets/20-rubygems.sh +++ b/release/targets/20-rubygems.sh @@ -23,14 +23,19 @@ cd "$REPO_ROOT/ruby" # Publish in dependency order so a gem's deps exist when it is pushed. gems=( - oselvar-var-core - oselvar-var-config - oselvar-var - oselvar-var-runner - oselvar-var-rspec - oselvar-var-minitest + varar-core + varar-config + varar + varar-runner + varar-rspec + varar-minitest ) +# The gem name no longer shares a prefix with its package directory (gem +# `varar-core` lives in `packages/var-core`), so locate each package by its +# gemspec rather than by stripping a name prefix. +gem_dir() { dirname "$(ls "$REPO_ROOT"/ruby/packages/*/"$1.gemspec")"; } + trap 'rm -f "$REPO_ROOT"/ruby/packages/*/*.gem' EXIT # Which gems still need publishing? (RubyGems returns 200 for a published @@ -60,7 +65,7 @@ fi # Build every pending gem up front — no credentials needed, so this keeps the # OTP-guarded pushes back-to-back and inside one code's validity window. for name in "${pending[@]}"; do - (cd "packages/${name#oselvar-}" && gem build "$name.gemspec" -o "$name-$VERSION.gem" >/dev/null) + (cd "$(gem_dir "$name")" && gem build "$name.gemspec" -o "$name-$VERSION.gem" >/dev/null) done # One OTP for the whole batch. op run pipes our stdio to mask secrets, so read @@ -75,7 +80,7 @@ export GEM_HOST_OTP_CODE published=0 for name in "${pending[@]}"; do - (cd "packages/${name#oselvar-}" && gem push "$name-$VERSION.gem") + (cd "$(gem_dir "$name")" && gem push "$name-$VERSION.gem") log "rubygems: published $name $VERSION" published=$((published + 1)) done diff --git a/release/targets/40-open-vsx.sh b/release/targets/40-open-vsx.sh index 46ea1a9d..db233218 100755 --- a/release/targets/40-open-vsx.sh +++ b/release/targets/40-open-vsx.sh @@ -4,8 +4,8 @@ set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/../lib.sh" VERSION="$1" -if http_ok "https://open-vsx.org/api/oselvar/oselvar-var/$VERSION"; then - log "open-vsx: oselvar.oselvar-var $VERSION already published" +if http_ok "https://open-vsx.org/api/varar/varar/$VERSION"; then + log "open-vsx: varar.varar $VERSION already published" exit 0 fi if [[ "${DRY_RUN:-0}" == "1" ]]; then @@ -15,9 +15,9 @@ fi # The namespace must exist before the first publish (ovsx errors with # "Unknown publisher" otherwise). Creating it is a one-time act; probe first # so re-runs stay quiet. ovsx reads the token from $OVSX_PAT. -if ! http_ok "https://open-vsx.org/api/oselvar"; then - ovsx create-namespace oselvar - log "open-vsx: created namespace oselvar" +if ! http_ok "https://open-vsx.org/api/varar"; then + ovsx create-namespace varar + log "open-vsx: created namespace varar" fi vsix="$(build_vsix "$VERSION")" ovsx publish "$vsix" diff --git a/release/targets/50-vscode-marketplace.sh b/release/targets/50-vscode-marketplace.sh index d041283c..1cad3eb7 100755 --- a/release/targets/50-vscode-marketplace.sh +++ b/release/targets/50-vscode-marketplace.sh @@ -12,9 +12,9 @@ if [[ "$DISABLED" == "1" ]]; then exit 0 fi -listing="$(vsce show oselvar.oselvar-var --json 2>/dev/null || true)" +listing="$(vsce show varar.varar --json 2>/dev/null || true)" if [[ -n "$listing" ]] && jq -e --arg v "$VERSION" '[.versions[]?.version] | index($v) != null' >/dev/null 2>&1 <<<"$listing"; then - log "marketplace: oselvar.oselvar-var $VERSION already published" + log "marketplace: varar.varar $VERSION already published" exit 0 fi if [[ "${DRY_RUN:-0}" == "1" ]]; then diff --git a/release/targets/60-maven-central.sh b/release/targets/60-maven-central.sh index c07dfe90..218bbe2a 100755 --- a/release/targets/60-maven-central.sh +++ b/release/targets/60-maven-central.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Publish all com.oselvar artifacts to Maven Central. The Central Portal +# Publish all dev.varar artifacts to Maven Central. The Central Portal # treats a multi-module deploy as one atomic bundle, so this either deploys # everything or skips everything; a partial state means a manual mess on the # portal and gets a hard error. @@ -25,7 +25,7 @@ AUTH="Authorization: Bearer $(printf '%s:%s' "$CENTRAL_USERNAME" "$CENTRAL_PASSW central_published() { local body status body="$(curl -sS -w '\n%{http_code}' -H "$AUTH" \ - "https://central.sonatype.com/api/v1/publisher/published?namespace=com.oselvar&name=$1&version=$VERSION")" + "https://central.sonatype.com/api/v1/publisher/published?namespace=dev.varar&name=$1&version=$VERSION")" status="${body##*$'\n'}" case "$status" in 401 | 403) die "maven: Central Portal rejected the credentials (HTTP $status) — check the sonatype-central item in 1Password (doc/RELEASING.md §3)" ;; @@ -35,14 +35,14 @@ central_published() { printf '%s' "${body%$'\n'*}" | jq -e '.published == true' >/dev/null } -artifacts=(var-parent var-core var-config var var-runner var-junit var-kotlin var-kotest) +artifacts=(parent core config varar runner junit kotlin kotest) missing=() for artifact in "${artifacts[@]}"; do central_published "$artifact" || missing+=("$artifact") done if [[ ${#missing[@]} -eq 0 ]]; then - log "maven: com.oselvar:*:$VERSION already published" + log "maven: dev.varar:*:$VERSION already published" exit 0 fi if [[ ${#missing[@]} -lt ${#artifacts[@]} ]]; then @@ -56,4 +56,4 @@ if [[ "${DRY_RUN:-0}" == "1" ]]; then fi mvn --batch-mode -s "$REPO_ROOT/release/maven-settings.xml" -Prelease -DskipTests deploy -log "maven: deployed com.oselvar:*:$VERSION (waitUntil=published confirmed by the portal)" +log "maven: deployed dev.varar:*:$VERSION (waitUntil=published confirmed by the portal)" diff --git a/release/targets/65-crates-io.sh b/release/targets/65-crates-io.sh index c49666a5..fff044ae 100755 --- a/release/targets/65-crates-io.sh +++ b/release/targets/65-crates-io.sh @@ -2,11 +2,11 @@ # Publish every Rust workspace crate to crates.io. Idempotent per crate. # # PARKED until the Rust port is ready to ship (gated by CRATES_IO_ENABLED in -# release/lib.sh, which keeps this target and the 70-var-examples.sh rust pin in +# release/lib.sh, which keeps this target and the 70-varar-examples.sh rust pin in # lock-step). While parked this simply reports OK. Go-live checklist: -# 1. Rename the facade crate — `var` is already TAKEN on crates.io (only -# var-core/var-config/var-runner/var-cargotest are free). Pick e.g. -# `oselvar-var` and update rust/var/Cargo.toml + the `crates` list below. +# 1. Rename the facade crate — `var` was already TAKEN on crates.io, so the facade ships as `varar` +# (verify `varar`/`varar-*` are free before first publish); the crate +# names below and rust/varar/Cargo.toml already reflect this. # 2. Flip each crate's `publish = false` to publishable and give them real # versions — the release stamper does not version the Rust port yet, so # wire that (they sit at 0.0.0 today). @@ -14,7 +14,7 @@ # 4. Add the CARGO_REGISTRY_TOKEN reference to release/release.env (already # done — points at the 1Password `crates` item's `token`). # 5. Set CRATES_IO_ENABLED=1 in release/lib.sh (un-parks this target AND the -# var-examples rust pin together). +# varar-examples rust pin together). set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/../lib.sh" VERSION="$1" @@ -30,11 +30,11 @@ cd "$REPO_ROOT/rust" # pushed. crates.io indexes each publish before the next `cargo publish` can # resolve it, so a brief wait between crates may be needed. crates=( - var-core - var-config - var - var-runner - var-cargotest + varar-core + varar-config + varar + varar-runner + varar-cargotest ) for name in "${crates[@]}"; do diff --git a/release/targets/70-var-examples.sh b/release/targets/70-varar-examples.sh similarity index 76% rename from release/targets/70-var-examples.sh rename to release/targets/70-varar-examples.sh index 27784a0c..80567347 100755 --- a/release/targets/70-var-examples.sh +++ b/release/targets/70-varar-examples.sh @@ -1,37 +1,37 @@ #!/usr/bin/env bash -# Sync examples/ to the oselvar/var-examples repo, pinned to the release. +# Sync examples/ to the oselvar/varar-examples repo, pinned to the release. # -# The monorepo's examples/ directory IS the var-examples repo layout: this +# The monorepo's examples/ directory IS the varar-examples repo layout: this # target wipes the destination (everything but .git), copies examples/ over # with symlinks dereferenced (the subset projects' .md specs are symlinks to # the typescript-vitest originals here, plain files there), rewrites the # local/SNAPSHOT references to the released coordinates, pushes, and tags the -# var-examples repo with the same v tag as the release. +# varar-examples repo with the same v tag as the release. # -# Override the checkout location with VAR_EXAMPLES_DIR (default: a sibling -# clone at ../var-examples; cloned via gh if missing). +# Override the checkout location with VARAR_EXAMPLES_DIR (default: a sibling +# clone at ../varar-examples; cloned via gh if missing). set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/../lib.sh" VERSION="$1" TAG="v$VERSION" cd "$REPO_ROOT" -DEST="${VAR_EXAMPLES_DIR:-$REPO_ROOT/../var-examples}" +DEST="${VARAR_EXAMPLES_DIR:-$REPO_ROOT/../varar-examples}" if [[ "${DRY_RUN:-0}" == "1" ]]; then - log "var-examples: dry-run — would sync examples/ -> $DEST pinned to $TAG, push, and tag $TAG" + log "varar-examples: dry-run — would sync examples/ -> $DEST pinned to $TAG, push, and tag $TAG" exit 0 fi if [[ ! -d "$DEST/.git" ]]; then - log "var-examples: cloning oselvar/var-examples to $DEST" - gh repo clone oselvar/var-examples "$DEST" -- --quiet || die "var-examples: clone failed" + log "varar-examples: cloning oselvar/varar-examples to $DEST" + gh repo clone oselvar/varar-examples "$DEST" -- --quiet || die "varar-examples: clone failed" fi -[[ -z "$(git -C "$DEST" status --porcelain)" ]] || die "var-examples: working tree at $DEST not clean" +[[ -z "$(git -C "$DEST" status --porcelain)" ]] || die "varar-examples: working tree at $DEST not clean" default_branch="$(git -C "$DEST" symbolic-ref --short HEAD)" git -C "$DEST" pull --ff-only --quiet || true # empty repo has no upstream yet -# Everything in var-examples comes from examples/ — remove all tracked and +# Everything in varar-examples comes from examples/ — remove all tracked and # untracked content (except .git) so deletions here propagate there. find "$DEST" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} + @@ -40,7 +40,7 @@ find "$DEST" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} + # Keep this list in step with the examples/**/.gitignore files — anything a # project ignores must not be synced. pnpm-workspace.yaml is monorepo-only # plumbing (lets `pnpm test` run inside typescript-vitest, see the comment -# there); in var-examples the deps are real versions and pnpm's defaults work. +# there); in varar-examples the deps are real versions and pnpm's defaults work. # While crates.io publishing is parked, omit the rust-* samples: var-core isn't # on crates.io, so a synced rust sample couldn't resolve it (its path source is # monorepo-only). CRATES_IO_ENABLED (lib.sh) flips this and the pin block below @@ -72,9 +72,9 @@ perl -pi -e "s/^val varVersion = \".*\"/val varVersion = \"$VERSION\"/" "$DEST"/ perl -pi -e "s|[^<]*|$VERSION|" \ "$DEST"/java-junit-maven/pom.xml perl -ni -e 'print unless /^\s*mavenLocal\(\)\s*$/' "$DEST"/*/build.gradle.kts -perl -0pi -e 's|// On trunk this is the SNAPSHOT that `mvn install` \(run from java/\) puts into\n// mavenLocal, so the sample always tests the code in this repo\. In your own\n// project: pin the latest release and drop the mavenLocal\(\) repository\.|// The released Vár version from Maven Central.|' \ +perl -0pi -e 's|// On trunk this is the SNAPSHOT that `mvn install` \(run from java/\) puts into\n// mavenLocal, so the sample always tests the code in this repo\. In your own\n// project: pin the latest release and drop the mavenLocal\(\) repository\.|// The released Varar version from Maven Central.|' \ "$DEST"/*/build.gradle.kts -perl -0pi -e 's|||' \ +perl -0pi -e 's|||' \ "$DEST"/java-junit-maven/pom.xml # Pin the TypeScript sample to the released npm packages. @@ -83,10 +83,10 @@ perl -pi -e "s/\"workspace:\\*\"/\"^$VERSION\"/g" "$DEST"/typescript-vitest/pack # Pin the Python samples to the released PyPI version: delete the # [tool.uv.sources] path-source table (with its comment block) and pin the # adapter dependency. Never rewrite to git sources — this monorepo is -# private, so anonymous CI in var-examples cannot fetch git+tag pins. +# private, so anonymous CI in varar-examples cannot fetch git+tag pins. perl -0pi -e 's|(#[^\n]*\n)+\[tool\.uv\.sources\]\n([\w.-]+ = \{ path = [^\n]+\n)+\n||' \ "$DEST"/python-*/pyproject.toml -perl -pi -e "s/\"(pytest-var|oselvar-var[\\w-]*)\"/\"\$1==$VERSION\"/" \ +perl -pi -e "s/\"(pytest-varar|varar[\\w-]*)\"/\"\$1==$VERSION\"/" \ "$DEST"/python-*/pyproject.toml # Pin the Ruby samples to the released RubyGems version: swap each path source @@ -95,22 +95,22 @@ perl -pi -e "s/\"(pytest-var|oselvar-var[\\w-]*)\"/\"\$1==$VERSION\"/" \ perl -pi -e "s|, path: \"\\.\\./\\.\\./ruby/packages/[\\w-]+\"|, \"$VERSION\"|" \ "$DEST"/ruby-*/Gemfile -# Pin the Rust sample to the released crates.io version: swap the var-core path +# Pin the Rust sample to the released crates.io version: swap the varar-core path # dependency for a version constraint. Only runs once crates.io publishing is # live (CRATES_IO_ENABLED=1); while parked the rust-* samples aren't synced at # all (see the rsync exclude above), so this would have nothing to rewrite. if [[ "$CRATES_IO_ENABLED" == "1" ]]; then - perl -pi -e "s|var-core = \{ path = \"\\.\\./\\.\\./rust/var-core\" \}|var-core = \"$VERSION\"|" \ + perl -pi -e "s|varar-core = \{ path = \"\\.\\./\\.\\./rust/core\" \}|varar-core = \"$VERSION\"|" \ "$DEST"/rust-*/Cargo.toml fi git -C "$DEST" add -A if git -C "$DEST" diff --cached --quiet; then - log "var-examples: already in sync with $TAG" + log "varar-examples: already in sync with $TAG" else - git -C "$DEST" commit --quiet -m "Sync examples from oselvar/var $TAG" + git -C "$DEST" commit --quiet -m "Sync examples from oselvar/varar $TAG" git -C "$DEST" push --quiet origin "$default_branch" - log "var-examples: pushed sync for $TAG" + log "varar-examples: pushed sync for $TAG" fi # Tag the synced state with the release version (outside the commit branch so @@ -119,4 +119,4 @@ if ! git -C "$DEST" rev-parse --quiet --verify "refs/tags/$TAG" >/dev/null; then git -C "$DEST" tag "$TAG" fi git -C "$DEST" push --quiet origin "refs/tags/$TAG" -log "var-examples: tagged $TAG" +log "varar-examples: tagged $TAG" diff --git a/ruby/Gemfile b/ruby/Gemfile index 9d9a4281..eb0968cb 100644 --- a/ruby/Gemfile +++ b/ruby/Gemfile @@ -4,12 +4,12 @@ source 'https://rubygems.org' # The six workspace gems, wired via path so they resolve against the local # source (the release sync rewrites these to published versions). -gem 'oselvar-var', path: 'packages/var' -gem 'oselvar-var-config', path: 'packages/var-config' -gem 'oselvar-var-core', path: 'packages/var-core' -gem 'oselvar-var-minitest', path: 'packages/var-minitest' -gem 'oselvar-var-rspec', path: 'packages/var-rspec' -gem 'oselvar-var-runner', path: 'packages/var-runner' +gem 'varar', path: 'packages/varar' +gem 'varar-config', path: 'packages/config' +gem 'varar-core', path: 'packages/core' +gem 'varar-minitest', path: 'packages/minitest' +gem 'varar-rspec', path: 'packages/rspec' +gem 'varar-runner', path: 'packages/runner' group :development, :test do gem 'minitest', '~> 6.0' diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock index 79d68190..8b68d24f 100644 --- a/ruby/Gemfile.lock +++ b/ruby/Gemfile.lock @@ -1,41 +1,41 @@ PATH - remote: packages/var-config + remote: packages/config specs: - oselvar-var-config (0.4.2) + varar-config (0.4.2) PATH - remote: packages/var-core + remote: packages/core specs: - oselvar-var-core (0.4.2) + varar-core (0.4.2) cucumber-cucumber-expressions (= 20.0.0) PATH - remote: packages/var-minitest + remote: packages/minitest specs: - oselvar-var-minitest (0.4.2) + varar-minitest (0.4.2) minitest (~> 6.0) - oselvar-var-runner (= 0.4.2) + varar-runner (= 0.4.2) PATH - remote: packages/var-rspec + remote: packages/rspec specs: - oselvar-var-rspec (0.4.2) - oselvar-var-runner (= 0.4.2) + varar-rspec (0.4.2) rspec-core (~> 3.13) + varar-runner (= 0.4.2) PATH - remote: packages/var-runner + remote: packages/runner specs: - oselvar-var-runner (0.4.2) - oselvar-var (= 0.4.2) - oselvar-var-config (= 0.4.2) + varar-runner (0.4.2) + varar (= 0.4.2) + varar-config (= 0.4.2) PATH - remote: packages/var + remote: packages/varar specs: - oselvar-var (0.4.2) + varar (0.4.2) cucumber-cucumber-expressions (= 20.0.0) - oselvar-var-core (= 0.4.2) + varar-core (= 0.4.2) GEM remote: https://rubygems.org/ @@ -108,12 +108,6 @@ PLATFORMS DEPENDENCIES minitest (~> 6.0) - oselvar-var! - oselvar-var-config! - oselvar-var-core! - oselvar-var-minitest! - oselvar-var-rspec! - oselvar-var-runner! rake rspec (~> 3.13) rubocop (~> 1.60) @@ -121,6 +115,12 @@ DEPENDENCIES rubocop-rspec simplecov (~> 1.0) simplecov-lcov (~> 0.8) + varar! + varar-config! + varar-core! + varar-minitest! + varar-rspec! + varar-runner! BUNDLED WITH 2.4.10 diff --git a/ruby/packages/config/lib/varar/config.rb b/ruby/packages/config/lib/varar/config.rb new file mode 100644 index 00000000..053f5ca6 --- /dev/null +++ b/ruby/packages/config/lib/varar/config.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'json' + +module Varar + # Strict, fail-loud reader for the shared varar.config.json format. Missing + # file → empty config; malformed JSON, wrong types, or unknown keys → an + # error starting with the file path. See conformance/config/README.md. + module Config + VERSION = '0.4.2' + + # The parsed config. All fields default to empty. + VarConfig = Data.define(:docs_include, :docs_exclude, :steps, :snippets, :scanner_plugins) do + def initialize(docs_include: [], docs_exclude: [], steps: [], snippets: {}, scanner_plugins: []) + super + end + end + + KNOWN_KEYS = %w[$schema docs steps snippets scannerPlugins].freeze + KNOWN_DOCS_KEYS = %w[include exclude].freeze + + module_function + + def read_var_config(root) + path = File.join(root.to_s, 'varar.config.json') + return VarConfig.new unless File.file?(path) + + data = begin + JSON.parse(File.read(path, encoding: 'UTF-8')) + rescue JSON::ParserError => e + raise ArgumentError, "#{path}: invalid JSON: #{e.message}" + end + raise ArgumentError, "#{path}: top level must be an object" unless data.is_a?(::Hash) + + unknown = data.keys - KNOWN_KEYS + raise ArgumentError, "#{path}: unknown key(s): #{unknown.sort.join(', ')}" unless unknown.empty? + + docs = data['docs'] || {} + raise ArgumentError, "#{path}: 'docs' must be an object" unless docs.is_a?(::Hash) + + unknown_docs = docs.keys - KNOWN_DOCS_KEYS + raise ArgumentError, "#{path}: unknown docs key(s): #{unknown_docs.sort.join(', ')}" unless unknown_docs.empty? + + snippets = data['snippets'] || {} + unless snippets.is_a?(::Hash) && snippets.all? { |k, v| k.is_a?(String) && v.is_a?(String) } + raise ArgumentError, "#{path}: 'snippets' must be an object of strings" + end + + VarConfig.new( + docs_include: string_array(docs['include'], 'docs.include', path), + docs_exclude: string_array(docs['exclude'], 'docs.exclude', path), + steps: string_array(data['steps'], 'steps', path), + snippets: snippets, + scanner_plugins: string_array(data['scannerPlugins'], 'scannerPlugins', path) + ) + end + + def string_array(value, key, path) + return [] if value.nil? + unless value.is_a?(Array) && value.all?(String) + raise ArgumentError, "#{path}: '#{key}' must be an array of strings" + end + + value + end + end +end diff --git a/ruby/packages/config/spec/conformance/config_conformance_spec.rb b/ruby/packages/config/spec/conformance/config_conformance_spec.rb new file mode 100644 index 00000000..1ef9fe45 --- /dev/null +++ b/ruby/packages/config/spec/conformance/config_conformance_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'varar/config' +require 'varar/core' # for CanonicalJson (test-only) + +module Varar + # Reproduces the shared config corpus byte-for-byte: each case parses to its + # golden.json, or (with an expect-error.txt marker) must fail to load. See + # conformance/config/README.md. + ::RSpec.describe 'config conformance' do + def self.cases_dir + dir = __dir__ + dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'config', 'cases')) || dir == '/' + File.join(dir, 'conformance', 'config', 'cases') + end + + def self.artifact(cfg) + { + 'docs' => { 'include' => cfg.docs_include, 'exclude' => cfg.docs_exclude }, + 'steps' => cfg.steps, + 'snippets' => cfg.snippets, + 'scannerPlugins' => cfg.scanner_plugins + } + end + + cases = cases_dir + + Dir.children(cases).sort.each do |name| + case_dir = File.join(cases, name) + next unless File.directory?(case_dir) + + if File.exist?(File.join(case_dir, 'expect-error.txt')) + it "#{name} — loading fails" do + expect { Config.read_var_config(case_dir) }.to raise_error(StandardError) + end + else + it "#{name} — matches golden" do + actual = Core::CanonicalJson.canonical_stringify(self.class.artifact(Config.read_var_config(case_dir))) + expect(actual).to eq(File.read(File.join(case_dir, 'golden.json'), encoding: 'UTF-8')) + end + end + end + end +end diff --git a/ruby/packages/var-config/oselvar-var-config.gemspec b/ruby/packages/config/varar-config.gemspec similarity index 59% rename from ruby/packages/var-config/oselvar-var-config.gemspec rename to ruby/packages/config/varar-config.gemspec index 1fd2d7a2..8103d395 100644 --- a/ruby/packages/var-config/oselvar-var-config.gemspec +++ b/ruby/packages/config/varar-config.gemspec @@ -1,13 +1,13 @@ # frozen_string_literal: true Gem::Specification.new do |s| - s.name = 'oselvar-var-config' + s.name = 'varar-config' s.version = '0.4.2' - s.summary = 'Markdown-native BDD — var.config.json reader' - s.description = 'Strict, fail-loud reader for the shared var.config.json format.' + s.summary = 'Markdown-native BDD — varar.config.json reader' + s.description = 'Strict, fail-loud reader for the shared varar.config.json format.' s.authors = ['Aslak Hellesøy'] s.email = ['aslak@oselvar.com'] - s.homepage = 'https://var.oselvar.com' + s.homepage = 'https://varar.dev' s.license = 'MIT' s.required_ruby_version = '>= 3.2' s.files = Dir['lib/**/*.rb'] diff --git a/ruby/packages/core/lib/varar/core.rb b/ruby/packages/core/lib/varar/core.rb new file mode 100644 index 00000000..f92dba6e --- /dev/null +++ b/ruby/packages/core/lib/varar/core.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Varar + # The pure functional core: parse, match, plan, execute, diffs, drift, and + # the conformance projections. No filesystem, network, globals, or time. + module Core + VERSION = '0.4.2' + end +end + +require 'varar/core/span' +require 'varar/core/ast' +require 'varar/core/table_cells' +require 'varar/core/scanner' +require 'varar/core/structurer' +require 'varar/core/parse' +require 'varar/core/step_role' +require 'varar/core/registry' +require 'varar/core/sentences' +require 'varar/core/diagnostics' +require 'varar/core/cell_diff' +require 'varar/core/matcher' +require 'varar/core/plan' +require 'varar/core/deep_freeze' +require 'varar/core/doc_string_diff' +require 'varar/core/param_diff' +require 'varar/core/failure_anchor' +require 'varar/core/execute' +require 'varar/core/hash' +require 'varar/core/drift' +require 'varar/core/canonical_json' +require 'varar/core/conformance' diff --git a/ruby/packages/core/lib/varar/core/ast.rb b/ruby/packages/core/lib/varar/core/ast.rb new file mode 100644 index 00000000..31a194dc --- /dev/null +++ b/ruby/packages/core/lib/varar/core/ast.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'varar/core/span' + +module Varar + module Core + # Maps a block-text offset to its source offset. Block text is the raw + # source minus BLOCK markers only (list bullets, blockquote `>` prefixes); + # inline markup is never stripped. A paragraph/list item has a single + # entry; a blockquote one entry per quoted line. + SegmentOffset = Data.define(:text_offset, :source_offset) + + Heading = Data.define(:level, :text, :span) do + def kind = 'heading' + end + + Paragraph = Data.define(:text, :span, :segment_map) do + def kind = 'paragraph' + end + + ListItem = Data.define(:text, :span, :segment_map, :ordered, :marker_span) do + def kind = 'list_item' + end + + Blockquote = Data.define(:text, :span, :segment_map) do + def kind = 'blockquote' + end + + Row = Data.define(:cells, :cell_spans, :span) + + Table = Data.define(:span, :header, :rows) do + def kind = 'table' + end + + Fence = Data.define(:span, :info, :body, :body_span) do + def kind = 'fence' + end + + ThematicBreak = Data.define(:span) do + def kind = 'thematic_break' + end + + Example = Data.define(:scope_stack, :span, :body) + + VarDoc = Data.define(:path, :source, :examples, :orphan_attachments) + end +end diff --git a/ruby/packages/core/lib/varar/core/canonical_json.rb b/ruby/packages/core/lib/varar/core/canonical_json.rb new file mode 100644 index 00000000..b483aeed --- /dev/null +++ b/ruby/packages/core/lib/varar/core/canonical_json.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'json' + +module Varar + module Core + # JSON serializers byte-for-byte compatible with JS `JSON.stringify(v, null, 2)`: + # 2-space indent, LF, trailing newline, non-ASCII raw, empty containers as + # {}/[]. `canonical_stringify` recursively sorts object keys (the goldens); + # `ordered_stringify` preserves insertion order (varar.lock.json). + # + # The container layout is hand-rolled because Ruby's JSON.pretty_generate + # renders empty arrays/objects as "[\n\n]". Scalar encoding is delegated to + # the stdlib, which matches JS (escapes " \ control chars, keeps non-ASCII raw). + module CanonicalJson + module_function + + def canonical_stringify(value) + "#{encode(value, '', sort_keys: true)}\n" + end + + def ordered_stringify(value) + "#{encode(value, '', sort_keys: false)}\n" + end + + def encode(value, indent, sort_keys:) + case value + when Hash + return '{}' if value.empty? + + keys = sort_keys ? value.keys.sort : value.keys + inner = "#{indent} " + items = keys.map { |key| "#{inner}#{key.to_s.to_json}: #{encode(value[key], inner, sort_keys: sort_keys)}" } + "{\n#{items.join(",\n")}\n#{indent}}" + when Array + return '[]' if value.empty? + + inner = "#{indent} " + items = value.map { |element| "#{inner}#{encode(element, inner, sort_keys: sort_keys)}" } + "[\n#{items.join(",\n")}\n#{indent}]" + else + value.to_json + end + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/cell_diff.rb b/ruby/packages/core/lib/varar/core/cell_diff.rb new file mode 100644 index 00000000..ed5af14f --- /dev/null +++ b/ruby/packages/core/lib/varar/core/cell_diff.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +module Varar + module Core + # One checked column of one header-bound row: the cell text and its span. + RowCheck = Data.define(:column, :value, :span) + + # The verdict for one checked column after comparing against the table. + # expected_value/actual_value/formatted are adapter-facing, never serialized. + CellDiff = Data.define(:column, :span, :expected, :actual, :ok, + :expected_value, :actual_value, :formatted) do + def initialize(column:, span:, expected:, actual:, ok:, + expected_value: nil, actual_value: nil, formatted: false) + super + end + end + + # The step returned the wrong type/shape — an author mistake, not a value diff. + class ReturnShapeError < StandardError; end + + # Raised when a header-bound row's / a table's returned columns don't match. + class CellMismatchError < StandardError + attr_reader :cells + + def initialize(cells) + @cells = cells + super(cells.map { |c| "#{c.column}: expected #{c.expected} but was #{c.actual}" }.join('; ')) + end + end + + # Pure comparison of row/table step returns against the authored cells. + # Port of cell-diff.ts. + module CellDiffs + module_function + + # Display rules 2-4 of the mismatch-rendering chain (rule 1, the + # parameter type's `format`, is applied in param_diff). A string renders + # as-is, other primitives via to_s, anything else via inspect. The + # inspect fallback is port-native and deliberately outside conformance. + def render_cell_value(value) + return value if value.is_a?(String) + return value.to_s if value.nil? || value == true || value == false || + value.is_a?(Integer) || value.is_a?(Float) + + value.inspect + end + + # Compare a row step's returned Hash against the row's cells. Only columns + # present on +returned+ are checked; a non-Hash return checks nothing. + def compare_row(returned, checks) + return [] unless returned.is_a?(Hash) + + checks.filter_map do |check| + next unless returned.key?(check.column) + + actual = render_cell_value(returned[check.column]) + CellDiff.new(column: check.column, span: check.span, expected: check.value, + actual: actual, ok: actual == check.value) + end + end + + # Compare a whole-table step's returned table against the input table. + # +returned+: nil (no checks), Array of Arrays (positional), or Array of + # Hashes (keyed by header). Cells compare as exact strings. + def compare_table(returned, input_table) + return [] if returned.nil? + raise ReturnShapeError, "expected a table (array of rows), got #{returned.class}" unless returned.is_a?(Array) + + columns = input_table.header.cells + data_rows = input_table.rows + if returned.length != data_rows.length + raise ReturnShapeError, "expected #{data_rows.length} row(s), got #{returned.length}" + end + + all_arrays = returned.all?(Array) + all_records = returned.all?(Hash) + raise ReturnShapeError, 'table rows must be all arrays or all objects' if !all_arrays && !all_records + + diffs = [] + data_rows.each_with_index do |row, i| + ret = returned[i] + if all_arrays && ret.length != columns.length + raise ReturnShapeError, "row #{i}: expected #{columns.length} column(s), got #{ret.length}" + end + + columns.each_with_index do |column, j| + if all_arrays + actual_value = ret[j] + else + raise ReturnShapeError, "row #{i}: missing column \"#{column}\"" unless ret.key?(column) + + actual_value = ret[column] + end + expected = j < row.cells.length ? row.cells[j] : '' + actual = render_cell_value(actual_value) + span = j < row.cell_spans.length ? row.cell_spans[j] : row.span + diffs << CellDiff.new(column: column, span: span, expected: expected, actual: actual, + ok: actual == expected) + end + end + diffs + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/conformance.rb b/ruby/packages/core/lib/varar/core/conformance.rb new file mode 100644 index 00000000..61384bb8 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/conformance.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +require 'varar/core/ast' +require 'varar/core/plan' +require 'varar/core/execute' +require 'varar/core/failure_anchor' + +module Varar + module Core + # Projections from the internal pipeline values to the camelCase wire + # dicts compared against golden/*.json. Port of conformance.ts. (var-doc + # stage; registry/plan/trace projections are added in later stages.) + module Conformance + module_function + + def span_hash(span) + { + 'startOffset' => span.start_offset, + 'endOffset' => span.end_offset, + 'startLine' => span.start_line, + 'startCol' => span.start_col, + 'endLine' => span.end_line, + 'endCol' => span.end_col + } + end + + def segment_hash(segment_offset) + { + 'textOffset' => segment_offset.text_offset, + 'sourceOffset' => segment_offset.source_offset + } + end + + def row_hash(row) + { + 'cells' => row.cells, + 'cellSpans' => row.cell_spans.map { |cs| span_hash(cs) }, + 'span' => span_hash(row.span) + } + end + + def block_hash(block) + case block.kind + when 'paragraph', 'blockquote' + { + 'kind' => block.kind, + 'text' => block.text, + 'span' => span_hash(block.span), + 'segmentMap' => block.segment_map.map { |so| segment_hash(so) } + } + when 'heading' + { + 'kind' => block.kind, + 'level' => block.level, + 'text' => block.text, + 'span' => span_hash(block.span) + } + when 'list_item' + { + 'kind' => block.kind, + 'text' => block.text, + 'span' => span_hash(block.span), + 'segmentMap' => block.segment_map.map { |so| segment_hash(so) }, + 'ordered' => block.ordered, + 'markerSpan' => span_hash(block.marker_span) + } + when 'table' + { + 'kind' => block.kind, + 'span' => span_hash(block.span), + 'header' => row_hash(block.header), + 'rows' => block.rows.map { |r| row_hash(r) } + } + when 'fence' + { + 'kind' => block.kind, + 'span' => span_hash(block.span), + 'info' => block.info, + 'body' => block.body, + 'bodySpan' => span_hash(block.body_span) + } + when 'thematic_break' + { + 'kind' => block.kind, + 'span' => span_hash(block.span) + } + else + raise "Unknown block kind: #{block.kind}" + end + end + + def example_hash(example) + { + 'scopeStack' => example.scope_stack, + 'span' => span_hash(example.span), + 'body' => example.body.map { |b| block_hash(b) } + } + end + + # Project a VarDoc to the wire dict for the var-doc artifact. + def to_var_doc_artifact(doc) + { + 'path' => doc.path, + 'examples' => doc.examples.map { |ex| example_hash(ex) }, + 'orphanAttachments' => doc.orphan_attachments.map { |b| block_hash(b) } + } + end + + # Parameter-type names in source order from a compiled CucumberExpression. + # The Ruby gem populates @parameter_types in source order during + # construction (it has no public reader), mirroring the TS AST walk. + def parameter_type_names(compiled) + compiled.instance_variable_get(:@parameter_types).map(&:name) + end + + # Project a Registry to the wire dict for the registry artifact. + # +parameter_types+ is the custom-type list ({"name","regexp"}). + def to_registry_artifact(registry, parameter_types = []) + { + 'steps' => registry.steps.map do |s| + { 'expression' => s.expression, 'parameterTypeNames' => parameter_type_names(s.compiled) } + end, + 'parameterTypes' => parameter_types.map do |p| + { 'name' => p['name'], 'regexp' => p['regexp'] } + end + } + end + + def doc_string_hash(doc_string) + { + 'content' => doc_string.content, + 'contentType' => doc_string.content_type, + 'span' => span_hash(doc_string.span) + } + end + + # Project an ExecutionPlan to the wire dict for the plan artifact. + def to_plan_artifact(plan) + source = plan.var_doc.source + { + 'examples' => plan.examples.map { |ex| planned_example_hash(ex, source) }, + 'diagnostics' => plan.diagnostics.map do |d| + { 'code' => d.code, 'severity' => d.severity, 'span' => span_hash(d.span) } + end + } + end + + def planned_example_hash(example, source) + result = { + 'name' => example.name, + 'scopeStack' => example.scope_stack, + 'span' => span_hash(example.span), + 'expectedOutcome' => example.expected_outcome || 'pass' + } + result['expectedErrorMessage'] = example.expected_error_message if example.expected_error_message + result['steps'] = example.steps.map { |s| planned_step_hash(s, source) } + result + end + + def planned_step_hash(step, source) + step_names = parameter_type_names(step.step_def.compiled) + result = { + 'text' => step.text, + 'matchSpan' => span_hash(step.match_span), + 'paramSpans' => step.param_spans.map { |s| span_hash(s) }, + 'matchedExpression' => step.step_def.expression, + 'args' => step.param_spans.each_with_index.map do |s, i| + { + 'value' => Offsets.utf16_slice(source, s.start_offset, s.end_offset), + 'parameterType' => i < step_names.length ? step_names[i] : nil + } + end + } + result['dataTable'] = block_hash(step.data_table) if step.data_table + result['docString'] = doc_string_hash(step.doc_string) if step.doc_string + result + end + + # Return the file stem: "path/to/foo.steps.rb" -> "foo.steps". + def file_stem(path) + File.basename(path, '.*') + end + + # Project an execution error to a FailureArtifact dict. line and anchor + # are deterministic source positions (never scraped from a backtrace). + def to_failure_artifact(error, match_span) + line = match_span.start_line + anchor = span_hash(FailureAnchor.failure_anchor(error, match_span)) + case error + when CellMismatchError + { + 'kind' => 'cell-mismatch', 'line' => line, 'anchor' => anchor, + 'cells' => error.cells.reject(&:ok).map do |c| + { 'column' => c.column, 'expected' => c.expected, 'actual' => c.actual, 'span' => span_hash(c.span) } + end + } + when DocStringMismatchError + { + 'kind' => 'doc-string-mismatch', 'line' => line, 'anchor' => anchor, + 'diff' => { + 'expected' => error.diff.expected, + 'actual' => error.diff.actual, + 'span' => span_hash(error.diff.span) + } + } + when ReturnShapeError + { 'kind' => 'return-shape', 'line' => line, 'anchor' => anchor } + when UnexpectedPassError + { 'kind' => 'unexpected-pass', 'line' => line, 'anchor' => anchor } + else + { 'kind' => 'thrown', 'line' => line, 'anchor' => anchor } + end + end + + # Run all examples and return the four-artifact bundle. Port of runConformance. + def run_conformance(var_doc, registry, create_context, parameter_types = []) + execution = Plan.plan(var_doc, registry) + observed = Hash.new { |h, k| h[k] = [] } + observer = ->(o) { observed[o.example_index] << o } + queue = Execute.collect_examples(execution, create_context: create_context, observer: observer) + + trace_examples = queue.each_with_index.map do |queued, k| + outcome = 'pass' + begin + queued.run.call + rescue StandardError + outcome = 'fail' + end + + planned = execution.examples[k] + obs_list = observed[k] + steps = planned.steps.each_with_index.map do |step, i| + ordinal = i + 1 + matches = obs_list.select { |x| x.ordinal == ordinal } + observation = matches.find { |m| m.outcome == 'fail' } || matches.last + step_outcome = observation ? observation.outcome : 'skipped' + step_dict = { + 'exampleName' => queued.name, + 'ordinal' => ordinal, + 'stepText' => step.text, + 'matchedExpression' => step.step_def.expression, + 'contextKey' => { 'exampleName' => queued.name, + 'stepFile' => file_stem(step.step_def.expression_source_file) }, + 'outcome' => step_outcome + } + step_dict['failure'] = to_failure_artifact(observation&.error, step.match_span) if step_outcome == 'fail' + step_dict + end + + { 'name' => queued.name, 'outcome' => outcome, 'steps' => steps } + end + + { + var_doc: to_var_doc_artifact(var_doc), + registry: to_registry_artifact(registry, parameter_types), + plan: to_plan_artifact(execution), + trace: { 'examples' => trace_examples } + } + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/deep_freeze.rb b/ruby/packages/core/lib/varar/core/deep_freeze.rb new file mode 100644 index 00000000..396eab10 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/deep_freeze.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Varar + module Core + # Recursively freeze plain Hash/Array so handler code mutating state raises + # FrozenError. Other objects (class instances, primitives, nil) pass + # through. Assumes acyclic input. Port of deep-freeze.ts. + module DeepFreeze + module_function + + def deep_freeze(value) + case value + when Hash + value.each_value { |v| deep_freeze(v) } + value.freeze + when Array + value.each { |v| deep_freeze(v) } + value.freeze + else + value + end + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/diagnostics.rb b/ruby/packages/core/lib/varar/core/diagnostics.rb new file mode 100644 index 00000000..5235917b --- /dev/null +++ b/ruby/packages/core/lib/varar/core/diagnostics.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Varar + module Core + # A planning/run diagnostic on the shared rail. code is one of + # "ambiguous-match", "error-fence-without-step", "drift". Port of + # diagnostics.ts. + Diagnostic = Data.define(:code, :severity, :message, :span) + Candidate = Data.define(:expression, :source_file, :source_line) + AmbiguousInput = Data.define(:text, :span, :candidates) + + module Diagnostics + module_function + + def ambiguous_match(input) + lines = input.candidates.map do |c| + " '#{c.expression}' at #{c.source_file}:#{c.source_line}" + end.join("\n") + Diagnostic.new( + severity: 'error', + code: 'ambiguous-match', + message: "Ambiguous step: \"#{input.text}\"\nMatched by:\n#{lines}", + span: input.span + ) + end + + def drift_detected(name, span) + Diagnostic.new( + severity: 'error', + code: 'drift', + message: "This paragraph was an example and no longer matches any step (drift): \"#{name}\".\n" \ + 'Fix the step so it matches again, or accept it as prose (run in update mode).', + span: span + ) + end + + def error_fence_without_step(span) + Diagnostic.new( + severity: 'error', + code: 'error-fence-without-step', + message: 'This `error` fence marks the example as expected-to-fail, ' \ + 'but the example has no step to run.', + span: span + ) + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/doc_string_diff.rb b/ruby/packages/core/lib/varar/core/doc_string_diff.rb new file mode 100644 index 00000000..f8172040 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/doc_string_diff.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'varar/core/cell_diff' + +module Varar + module Core + # A doc-string content difference: fence body span, expected, actual. + DocStringDiff = Data.define(:span, :expected, :actual) + + # Raised when a doc-string step's returned string differs from the content. + class DocStringMismatchError < StandardError + attr_reader :diff + + def initialize(diff) + @diff = diff + super("doc string: expected #{diff.expected.inspect} but was #{diff.actual.inspect}") + end + end + + # Pure comparison of a doc-string step's return against the fence body. + # Port of doc-string-diff.ts. + module DocStringDiffs + module_function + + # nil → no check; equal string → nil (pass); unequal → DocStringDiff; + # non-string → ReturnShapeError. + def compare_doc_string(returned, content, span) + return nil if returned.nil? + raise ReturnShapeError, "expected a doc string (string), got #{returned.class}" unless returned.is_a?(String) + return nil if returned == content + + DocStringDiff.new(span: span, expected: content, actual: returned) + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/drift.rb b/ruby/packages/core/lib/varar/core/drift.rb new file mode 100644 index 00000000..791de293 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/drift.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +require 'json' +require 'varar/core/hash' +require 'varar/core/diagnostics' +require 'varar/core/plan' +require 'varar/core/canonical_json' + +module Varar + module Core + # One example-producing paragraph, as recorded in the baseline. + BaselineExample = Data.define(:name, :line) + # The committed baseline for one spec file. + SpecBaseline = Data.define(:source_hash, :examples) + # The whole varar.lock.json: every spec keyed by its POSIX path. + VarLock = Data.define(:version, :specs) + # A paragraph the baseline says was an example and now matches no step. + Drift = Data.define(:name, :line, :span) + + # Spec drift detection: a paragraph the committed varar.lock.json baseline + # recorded as an example that now matches no step. Pure, byte-identical to + # the TS port so varar.lock.json is shared across languages. Port of drift.ts. + # + # BaselineStore is a duck-typed port: #read -> String|nil, #write(contents). + module Drifts + # A paragraph may be moved anywhere and reworded up to ~half its words + # and still be recognized; edit it past this and it reads as remove+add, + # not drift. Ported byte-identically. + SIMILARITY_THRESHOLD = 0.5 + TOKEN_RE = /[[:alnum:]]+/ + + module_function + + def within?(inner, outer) + inner.start_offset >= outer.start_offset && inner.end_offset <= outer.end_offset + end + + def live?(candidate_span, plan) + plan.examples.any? { |pe| within?(pe.span, candidate_span) } + end + + # Lower-cased word tokens (letters/digits) — the unit of similarity. + def tokenize(text) + text.downcase.scan(TOKEN_RE).to_set + end + + # Jaccard overlap |A∩B| / |A∪B|. 1 identical, 0 disjoint; two empty = 1. + def similarity(set_a, set_b) + return 1.0 if set_a.empty? && set_b.empty? + + intersection = (set_a & set_b).size + union = set_a.size + set_b.size - intersection + union.zero? ? 0.0 : intersection.to_f / union + end + + # The current example-producing paragraphs, in document order. + def live_examples(var_doc, plan) + var_doc.examples.filter_map do |candidate| + next unless live?(candidate.span, plan) + + BaselineExample.new(name: Plan.derive_example_name(candidate.body), line: candidate.span.start_line) + end + end + + def derive_spec_baseline(source, var_doc, plan) + SpecBaseline.new(source_hash: Hash32.hash_source(source), examples: live_examples(var_doc, plan)) + end + + # Paragraphs the baseline recorded as examples that now match zero steps. + # Each re-identified by the most word-similar current paragraph at/above + # the threshold (exact name scores 1; ties break toward the nearest line). + def detect_drift(baseline, var_doc, plan) + return [] if baseline.nil? + + candidates = var_doc.examples + tokens = candidates.map { |c| tokenize(Plan.derive_example_name(c.body)) } + live = candidates.map { |c| live?(c.span, plan) } + + baseline.examples.filter_map do |b| + b_tokens = tokenize(b.name) + best_idx = -1 + best_score = 0.0 + candidates.each_with_index do |candidate, i| + score = similarity(b_tokens, tokens[i]) + next if score < SIMILARITY_THRESHOLD + + line = candidate.span.start_line + best_line = best_idx >= 0 ? candidates[best_idx].span.start_line : 0 + next unless best_idx.negative? || score > best_score || + (score == best_score && (line - b.line).abs < (best_line - b.line).abs) + + best_idx = i + best_score = score + end + next if best_idx.negative? + next if live[best_idx] + + Drift.new(name: b.name, line: candidates[best_idx].span.start_line, span: candidates[best_idx].span) + end + end + + def drift_diagnostics(drifts) + drifts.map { |d| Diagnostics.drift_detected(d.name, d.span) } + end + + # One spec's baseline reconciliation against a BaselineStore. In update + # mode, accept all drift (re-record, report nothing); otherwise detect + # drift and rewrite the baseline only on a clean run, so an unacknowledged + # drift keeps its old entry (and stays red). + def reconcile_drift(store, spec_path, source, var_doc, plan, update: false) + text = store.read + lock = text ? parse_var_lock(text) : nil + baseline = lock ? lock.specs[spec_path] : nil + drifts = update ? [] : detect_drift(baseline, var_doc, plan) + if update || drifts.empty? + specs = lock ? lock.specs.dup : {} + specs[spec_path] = derive_spec_baseline(source, var_doc, plan) + store.write(stringify_var_lock(VarLock.new(version: 1, specs: specs))) + end + drifts + end + + def parse_var_lock(text) + parsed = JSON.parse(text) + return nil unless parsed.is_a?(::Hash) && parsed['version'] == 1 + + specs_raw = parsed['specs'] + return nil unless specs_raw.is_a?(::Hash) + + specs = {} + specs_raw.each do |path, value| + baseline = parse_spec_baseline(value) + return nil if baseline.nil? + + specs[path] = baseline + end + VarLock.new(version: 1, specs: specs) + rescue JSON::ParserError, TypeError + nil + end + + def parse_spec_baseline(value) + return nil unless value.is_a?(::Hash) + + source_hash = value['sourceHash'] + examples_raw = value['examples'] + return nil unless source_hash.is_a?(String) && examples_raw.is_a?(Array) + + examples = [] + examples_raw.each do |item| + parsed = parse_baseline_example(item) + return nil if parsed.nil? + + examples << parsed + end + SpecBaseline.new(source_hash: source_hash, examples: examples) + end + + def parse_baseline_example(value) + return nil unless value.is_a?(::Hash) + + name = value['name'] + line = value['line'] + return nil unless name.is_a?(String) && line.is_a?(Integer) + + BaselineExample.new(name: name, line: line) + end + + # Serialize varar.lock.json deterministically: spec paths sorted, examples + # in document order, insertion-order keys otherwise (version, specs; + # sourceHash, examples; name, line) — NOT canonical JSON's key sort. + def stringify_var_lock(lock) + specs = {} + lock.specs.keys.sort.each do |path| + baseline = lock.specs[path] + specs[path] = { + 'sourceHash' => baseline.source_hash, + 'examples' => baseline.examples.map { |e| { 'name' => e.name, 'line' => e.line } } + } + end + CanonicalJson.ordered_stringify({ 'version' => 1, 'specs' => specs }) + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/execute.rb b/ruby/packages/core/lib/varar/core/execute.rb new file mode 100644 index 00000000..42ac5e01 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/execute.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +require 'varar/core/span' +require 'varar/core/deep_freeze' +require 'varar/core/cell_diff' +require 'varar/core/doc_string_diff' +require 'varar/core/param_diff' +require 'varar/core/failure_anchor' + +module Varar + module Core + # Raised when an expected-to-fail example passes unexpectedly. + class UnexpectedPassError < StandardError + def initialize(message = 'expected the example to fail, but it passed') + super + end + end + + # Per-step outcome emitted to the optional observer. + StepObservation = Data.define(:example_name, :example_index, :ordinal, :step_file, :outcome, :error) do + def initialize(example_name:, example_index:, ordinal:, step_file:, outcome:, error: nil) + super + end + end + + # A named, runnable example returned by collect_examples. + QueuedExample = Data.define(:name, :run) + + # Execute an ExecutionPlan: route stimulus/sensor returns, merge immutable + # state, compare sensor returns via the diff helpers, invert expected + # failures. Handlers are user callbacks (sync). Port of execute.ts. + module Execute + module_function + + # Collect all examples into an ordered Array of QueuedExamples. + def collect_examples(plan, create_context:, observer: nil, reporter: nil) + queue = [] + sink = ->(name, run, _info) { queue << QueuedExample.new(name: name, run: run) } + execute_plan(plan, sink: sink, create_context: create_context, observer: observer, reporter: reporter) + queue + end + + def execute_plan(plan, sink:, create_context:, observer: nil, reporter: nil) + plan.diagnostics.each { |d| reporter.call(d) } if reporter + create_ctx = create_context || ->(_file) { {} } + var_path = plan.var_doc.path + + plan.examples.each_with_index do |ex, example_index| + seen_lines = {} + ex.steps.each { |s| seen_lines[s.match_span.start_line] = true } + info = { lines: seen_lines.keys } + sink.call(ex.name, build_run(plan, ex, example_index, create_ctx, observer, var_path), info) + end + end + + def build_run(plan, ex, example_index, create_ctx, observer, var_path) + lambda do + state_by_file = {} + last_return = nil + thrown = nil + + ex.steps.each_with_index do |step, i| + file = step.step_def.expression_source_file + state_by_file[file] = DeepFreeze.deep_freeze(create_ctx.call(file)) unless state_by_file.key?(file) + state = state_by_file[file] + + extra = [] + if step.data_table + extra << ([step.data_table.header.cells] + step.data_table.rows.map(&:cells)) + elsif step.doc_string + extra << step.doc_string.content + end + + begin + returned = step.step_def.handler.call(state, *step.args, *extra) + last_return = returned + case step.step_def.kind + when 'stimulus' + unless returned.nil? + unless returned.is_a?(Hash) + raise ReturnShapeError, + 'a stimulus must return a partial state object or nothing' + end + + state = DeepFreeze.deep_freeze(state.merge(returned)) + state_by_file[file] = state + end + when 'sensor' + compare_sensor_return(plan, ex, step, returned, extra) if ex.row_checks.nil? && !returned.nil? + else + raise ReturnShapeError, "unknown step kind: #{step.step_def.kind}" + end + rescue StandardError => e + augmented = augment_stack(e, step, var_path) + observer&.call(observation(ex, example_index, i + 1, file, 'fail', augmented)) + thrown = augmented + break + end + + observer&.call(observation(ex, example_index, i + 1, file, 'pass')) + end + + # Header-bound row checks (after all steps). + if thrown.nil? && ex.row_checks && !ex.row_checks.empty? + bad = CellDiffs.compare_row(last_return, ex.row_checks).reject(&:ok) + unless bad.empty? + last_step = ex.steps.last + augmented = augment_stack(CellMismatchError.new(bad), last_step, var_path) + observer&.call(observation(ex, example_index, ex.steps.length, + last_step.step_def.expression_source_file, 'fail', augmented)) + thrown = augmented + end + end + + # Expected-failure inversion. + if ex.expected_outcome == 'fail' + if thrown.nil? + error = UnexpectedPassError.new + last = ex.steps.last + raise(last ? augment_stack(error, last, var_path) : error) + end + raise thrown if ex.expected_error_message && !thrown.message.include?(ex.expected_error_message) + + return # satisfied expected-failure → pass + end + + raise thrown if thrown + end + end + + # Sensor slot contract: zero slots + a return is a mistake; one slot IS + # the return; two+ is a positional array. Raises the appropriate diff error. + def compare_sensor_return(plan, _ex, step, returned, extra) + slot_count = step.args.length + extra.length + if slot_count.zero? + raise ReturnShapeError, 'this sensor has no parameters, data table or doc string — ' \ + 'nothing to compare a return value against (raise to fail, return nothing to pass)' + end + + if slot_count == 1 + slots = [returned] + else + unless returned.is_a?(Array) + raise ReturnShapeError, + "a sensor with #{slot_count} parameters must return a list of " \ + "#{slot_count} values, got #{returned.class}" + end + unless returned.length == slot_count + raise ReturnShapeError, + "sensor return must have #{slot_count} element(s), got #{returned.length}" + end + + slots = returned + end + + inline_returned = slots[0...step.args.length] + source_texts = step.param_spans.map do |s| + Offsets.utf16_slice(plan.var_doc.source, s.start_offset, s.end_offset) + end + param_diffs = ParamDiff.compare_params(inline_returned, step.args, step.param_spans, source_texts, + step.formats).reject(&:ok) + raise CellMismatchError, param_diffs unless param_diffs.empty? + + if step.data_table + bad = CellDiffs.compare_table(slots[step.args.length], step.data_table).reject(&:ok) + raise CellMismatchError, bad unless bad.empty? + elsif step.doc_string + diff = DocStringDiffs.compare_doc_string(slots[step.args.length], step.doc_string.content, + step.doc_string.span) + raise DocStringMismatchError, diff unless diff.nil? + end + end + + def observation(ex, example_index, ordinal, file, outcome, error = nil) + StepObservation.new(example_name: ex.name, example_index: example_index, ordinal: ordinal, + step_file: file, outcome: outcome, error: error) + end + + # In TS this injects a synthetic `at (path:line:col)` frame for + # editor navigation; the conformance trace derives the anchor separately + # via failure_anchor, so here it is a no-op that returns the error. + def augment_stack(error, _step, _var_path) + error + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/failure_anchor.rb b/ruby/packages/core/lib/varar/core/failure_anchor.rb new file mode 100644 index 00000000..781ef618 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/failure_anchor.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'varar/core/cell_diff' +require 'varar/core/doc_string_diff' + +module Varar + module Core + # Where a failure points in the .md source: a mismatch anchors at its first + # failing span (cell / doc-string body), anything else at the fallback (the + # step's match span). The single source of truth for failure locations, + # pinned as failure.anchor in the conformance trace. Port of failure-anchor.ts. + module FailureAnchor + module_function + + def failure_anchor(error, fallback) + case error + when CellMismatchError + failing = error.cells.find { |c| !c.ok } + failing ? failing.span : fallback + when DocStringMismatchError + error.diff.span + else + fallback + end + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/hash.rb b/ruby/packages/core/lib/varar/core/hash.rb new file mode 100644 index 00000000..4b9009eb --- /dev/null +++ b/ruby/packages/core/lib/varar/core/hash.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Varar + module Core + # FNV-1a (32-bit) change-detector over UTF-16 code units. Not a security + # hash: tiny and byte-identical to the TS/Python/JVM ports so varar.lock.json + # fingerprints match everywhere. The "fnv1a:" prefix namespaces the algorithm. + # Port of hash.ts. + module Hash32 + FNV_OFFSET = 0x811c9dc5 + FNV_PRIME = 0x01000193 + MASK = 0xffffffff + + module_function + + def hash_source(source) + h = FNV_OFFSET + data = source.encode('UTF-16LE').bytes + (0...data.length).step(2) do |i| + unit = data[i] | (data[i + 1] << 8) + h = ((h ^ unit) * FNV_PRIME) & MASK + end + format('fnv1a:%08x', h) + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/matcher.rb b/ruby/packages/core/lib/varar/core/matcher.rb new file mode 100644 index 00000000..e417f634 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/matcher.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require 'varar/core/span' + +module Varar + module Core + # UTF-16 start/end of one captured parameter within a sentence. + ParamSpan = Data.define(:start, :end) + # One successful expression match inside a sentence. Offsets are UTF-16. + Hit = Data.define(:expression, :step_def, :match_start, :match_end, :args, :param_spans, :formats) do + def initialize(expression:, step_def:, match_start:, match_end:, args:, param_spans:, formats: []) + super + end + end + # Two or more hits starting at the same position with equal length. + AmbiguityCollision = Data.define(:match_start, :match_end, :candidates) + # Tagged result of resolve_hits: kind "ok" (steps) or "ambiguous" (collisions). + ResolvedSteps = Data.define(:kind, :steps, :collisions) do + def initialize(kind:, steps: [], collisions: []) + super + end + end + + # Cucumber-expression matching. Port of matcher.ts. cucumber-expressions' + # regexps are anchored (^...$) and its group offsets are code-point based, + # so we strip anchors for substring search and convert offsets to UTF-16. + module Matcher + module_function + + # A compiled, un-anchored pattern from the step's CucumberExpression. + def unanchored_pattern(step) + regexp = step.compiled.instance_variable_get(:@tree_regexp).regexp + source = regexp.source + source = source[1..] if source.start_with?('^') + source = source[0...-1] if source.end_with?('$') + Regexp.new(source, regexp.options) + end + + # Every expression match found anywhere in +sentence+. + def find_hits(sentence, registry) + hits = [] + registry.steps.each do |step| + pattern = unanchored_pattern(step) + pos = 0 + while pos <= sentence.length + m = pattern.match(sentence, pos) + break if m.nil? + + matched_text = m[0] + arguments = step.compiled.match(matched_text) || [] + args = arguments.map { |arg| arg.value(nil) } + formats = arguments.map { |arg| registry.formats[arg.parameter_type.name] } + + # group.start/.end are code-point offsets within matched_text; add + # m.begin(0) for the sentence-absolute code-point index, then to UTF-16. + param_spans = arguments.filter_map do |arg| + g = arg.group + next unless g.start.is_a?(Integer) && g.end.is_a?(Integer) + + ParamSpan.new( + start: Offsets.to_utf16_offset(sentence, m.begin(0) + g.start), + end: Offsets.to_utf16_offset(sentence, m.begin(0) + g.end) + ) + end + + hits << Hit.new( + expression: step.expression, + step_def: step, + match_start: Offsets.to_utf16_offset(sentence, m.begin(0)), + match_end: Offsets.to_utf16_offset(sentence, m.end(0)), + args: args, + param_spans: param_spans, + formats: formats + ) + + pos = matched_text.empty? ? m.begin(0) + 1 : m.end(0) + end + end + hits + end + + # Select the best non-overlapping hits, or report ambiguities. + def resolve_hits(hits) + return ResolvedSteps.new(kind: 'ok') if hits.empty? + + # Stable sort by (match_start asc, length desc); the original index + # breaks ties so equal-key order follows registration order (Ruby's + # sort_by is not stable, Python's sorted is). + sorted = hits.each_with_index.sort_by do |h, i| + [h.match_start, -(h.match_end - h.match_start), i] + end.map(&:first) + + collisions = [] + i = 0 + while i < sorted.length + here = sorted[i] + here_len = here.match_end - here.match_start + tied = [here] + j = i + 1 + while j < sorted.length + candidate = sorted[j] + if candidate.match_start == here.match_start && + candidate.match_end - candidate.match_start == here_len + tied << candidate + j += 1 + else + break + end + end + if tied.length > 1 + collisions << AmbiguityCollision.new( + match_start: here.match_start, match_end: here.match_end, candidates: tied + ) + end + i = j + end + + return ResolvedSteps.new(kind: 'ambiguous', collisions: collisions) unless collisions.empty? + + steps = [] + cursor = -1 + sorted.each do |hit| + next if hit.match_start < cursor + + steps << hit + cursor = hit.match_end + end + ResolvedSteps.new(kind: 'ok', steps: steps) + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/param_diff.rb b/ruby/packages/core/lib/varar/core/param_diff.rb new file mode 100644 index 00000000..877b2fbc --- /dev/null +++ b/ruby/packages/core/lib/varar/core/param_diff.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'varar/core/cell_diff' + +module Varar + module Core + # Compare a sensor's returned inline actuals against captured document + # values. Port of param-diff.ts. + module ParamDiff + module_function + + # Render one side of a parameter diff as [text, via_format]. The + # parameter type's format wins (document notation), else the shared + # string/primitive/inspect chain; a raising formatter falls through. + def render_param_value(value, format) + if format + begin + return [format.call(value), true] + rescue StandardError + # fall through to the native rendering + end + end + [CellDiffs.render_cell_value(value), false] + end + + # Compare returned actuals against expected document values. Arrays align + # 1:1; structural equality (==) compares by value across references. + def compare_params(returned, expected, param_spans, source_texts, formats = nil) + expected.each_index.map do |i| + ok = returned[i] == expected[i] + format = formats && i < formats.length ? formats[i] : nil + actual_text, via_format = render_param_value(returned[i], format) + expected_text = if i < source_texts.length + source_texts[i] + else + render_param_value(expected[i], format)[0] + end + CellDiff.new( + column: "arg #{i + 1}", + span: param_spans[i], + expected: expected_text, + actual: actual_text, + ok: ok, + expected_value: expected[i], + actual_value: returned[i], + formatted: via_format + ) + end + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/parse.rb b/ruby/packages/core/lib/varar/core/parse.rb new file mode 100644 index 00000000..6ef04ff4 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/parse.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'varar/core/scanner' +require 'varar/core/structurer' + +module Varar + module Core + # Parse +source+ into a VarDoc: scan blocks, then group into Examples. + # Port of parse.ts. + module Parse + module_function + + def parse(path, source, plugins = []) + Structurer.structure(path, source, Scanner.scan(source, plugins)) + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/plan.rb b/ruby/packages/core/lib/varar/core/plan.rb new file mode 100644 index 00000000..7fab5490 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/plan.rb @@ -0,0 +1,286 @@ +# frozen_string_literal: true + +require 'varar/core/span' +require 'varar/core/ast' +require 'varar/core/cell_diff' +require 'varar/core/diagnostics' +require 'varar/core/matcher' +require 'varar/core/sentences' + +module Varar + module Core + DocString = Data.define(:content, :content_type, :span) + + PlannedStep = Data.define(:text, :match_span, :param_spans, :step_def, :args, :formats, :data_table, + :doc_string) do + def initialize(text:, match_span:, param_spans:, step_def:, args:, formats: [], data_table: nil, + doc_string: nil) + super + end + end + + HeaderBinding = Data.define(:match_span, :param_spans, :step_def) + + PlannedExample = Data.define(:name, :scope_stack, :span, :steps, :header_binding, :row_checks, + :expected_outcome, :expected_error_message) do + def initialize(name:, scope_stack:, span:, steps:, header_binding: nil, row_checks: nil, + expected_outcome: nil, expected_error_message: nil) + super + end + end + + ExecutionPlan = Data.define(:var_doc, :examples, :diagnostics) + + # Produce an ExecutionPlan from a VarDoc + Registry: match step expressions + # against every text block, attach trailing tables/fences, detect + # header-bound tables, and collect diagnostics. Port of plan.ts. + module Plan + BlockPlan = Data.define(:steps, :ambiguities) + Ambiguity = Data.define(:match_start, :match_end, :candidates) + + module_function + + def plan(var_doc, registry) + examples = [] + diagnostics = [] + + var_doc.examples.each do |ex| + had_ambiguous = false + steps_by_block = {} + + # Pass 1: plan each text-bearing block. + ex.body.each_with_index do |block, idx| + next unless %w[paragraph list_item blockquote].include?(block.kind) + + result = plan_block(block.text, registry) + + result.ambiguities.each do |collision| + span = lift_span(var_doc.source, block, collision.match_start, collision.match_end) + cp_start = Offsets.cp_index_for_utf16(block.text, collision.match_start) + cp_end = Offsets.cp_index_for_utf16(block.text, collision.match_end) + diagnostics << Diagnostics.ambiguous_match( + AmbiguousInput.new( + text: block.text[cp_start...cp_end], + span: span, + candidates: collision.candidates.map do |c| + Candidate.new( + expression: c.expression, + source_file: c.step_def.expression_source_file, + source_line: c.step_def.expression_source_line + ) + end + ) + ) + had_ambiguous = true + end + + next unless !had_ambiguous && !result.steps.empty? + + steps_by_block[idx] = result.steps.map do |hit| + PlannedStep.new( + text: Offsets.utf16_slice(block.text, hit.match_start, hit.match_end), + match_span: lift_span(var_doc.source, block, hit.match_start, hit.match_end), + param_spans: hit.param_spans.map { |p| lift_span(var_doc.source, block, p.start, p.end) }, + step_def: hit.step_def, + args: hit.args, + formats: hit.formats + ) + end + end + + # Header-bound table detection. + bound = had_ambiguous ? nil : detect_header_bound(ex, steps_by_block, var_doc.source) + if bound + table, binding_step, header_spans = bound + header_binding = HeaderBinding.new( + match_span: binding_step.match_span, + param_spans: header_spans, + step_def: binding_step.step_def + ) + table.rows.each do |row| + row_object = {} + table.header.cells.each_with_index do |cell_name, i| + row_object[cell_name] = i < row.cells.length ? row.cells[i] : '' + end + row_step = PlannedStep.new( + text: binding_step.text, + match_span: row.span, + param_spans: binding_step.param_spans, + step_def: binding_step.step_def, + args: binding_step.args + [row_object], + formats: binding_step.formats + ) + row_checks = table.header.cells.each_with_index.map do |cell_name, i| + RowCheck.new( + column: cell_name, + value: i < row.cells.length ? row.cells[i] : '', + span: i < row.cell_spans.length ? row.cell_spans[i] : row.span + ) + end + examples << PlannedExample.new( + name: row.cells.join(' / '), + scope_stack: ex.scope_stack + [binding_step.text], + span: row.span, + steps: [row_step], + header_binding: header_binding, + row_checks: row_checks + ) + end + next + end + + # Error fence detection. + error_fence = ex.body.find { |b| b.kind == 'fence' && b.info == 'error' } + + # Pass 2: attach trailing table / fence to the last step of a block. + attachments = {} + (1...ex.body.length).each do |idx| + here = ex.body[idx] + if here.kind == 'table' && steps_by_block.key?(idx - 1) + _prev_data, prev_doc = attachments[idx - 1] || [nil, nil] + attachments[idx - 1] = [here, prev_doc] + elsif here.kind == 'fence' && here.info != 'error' && steps_by_block.key?(idx - 1) + prev_data, = attachments[idx - 1] || [nil, nil] + attachments[idx - 1] = [ + prev_data, + DocString.new(content: here.body, content_type: here.info, span: here.body_span) + ] + end + end + + # Pass 3: rebuild the final step list, applying attachments. + final_steps = [] + (0...ex.body.length).each do |idx| + block_steps = steps_by_block[idx] || [] + attach = attachments[idx] + block_steps.each_with_index do |step, s_idx| + if s_idx == block_steps.length - 1 && attach + data_table, doc_string = attach + final_steps << PlannedStep.new( + text: step.text, match_span: step.match_span, param_spans: step.param_spans, + step_def: step.step_def, args: step.args, formats: step.formats, + data_table: data_table, doc_string: doc_string + ) + else + final_steps << step + end + end + end + + runnable_steps = had_ambiguous ? [] : final_steps + + diagnostics << Diagnostics.error_fence_without_step(error_fence.span) if error_fence && runnable_steps.empty? + + next if final_steps.empty? && !had_ambiguous + + expected_outcome = nil + expected_error_message = nil + if error_fence + expected_outcome = 'fail' + msg = error_fence.body.strip + expected_error_message = msg unless msg.empty? + end + + examples << PlannedExample.new( + name: derive_example_name(ex.body), + scope_stack: ex.scope_stack, + span: ex.span, + steps: runnable_steps, + expected_outcome: expected_outcome, + expected_error_message: expected_error_message + ) + end + + ExecutionPlan.new(var_doc: var_doc, examples: examples, diagnostics: diagnostics) + end + + def plan_block(text, registry) + all_steps = [] + all_ambiguities = [] + + Sentences.split_sentences(text).each do |sentence| + hits = Matcher.find_hits(sentence.text, registry) + adjusted = hits.map do |h| + Hit.new( + expression: h.expression, + step_def: h.step_def, + match_start: h.match_start + sentence.start_offset, + match_end: h.match_end + sentence.start_offset, + args: h.args, + param_spans: h.param_spans.map do |p| + ParamSpan.new(start: p.start + sentence.start_offset, end: p.end + sentence.start_offset) + end, + formats: h.formats + ) + end + resolved = Matcher.resolve_hits(adjusted) + if resolved.kind == 'ambiguous' + resolved.collisions.each do |c| + all_ambiguities << Ambiguity.new(match_start: c.match_start, match_end: c.match_end, + candidates: c.candidates) + end + elsif !resolved.steps.empty? + all_steps.concat(resolved.steps) + end + end + + BlockPlan.new(steps: all_steps, ambiguities: all_ambiguities) + end + + # Whole-word, case-sensitive start index of +word+ in +haystack+, or nil. + def word_offset(haystack, word) + m = /(?(*groups) { groups[0] } + # `type` is return-type metadata only (used by snippet generation, never + # by matching, transformation, or any conformance artifact). The Ruby + # gem rejects a nil type (Python's accepts None), so pass Object. + pt = Cucumber::CucumberExpressions::ParameterType.new( + name, regexps, Object, transformer, use_for_snippets, prefer_for_regexp_match + ) + registry.parameter_types.define_parameter_type(pt) + return registry if format.nil? + + registry.with(formats: registry.formats.merge(name => format)) + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/scanner.rb b/ruby/packages/core/lib/varar/core/scanner.rb new file mode 100644 index 00000000..8a7253f5 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/scanner.rb @@ -0,0 +1,346 @@ +# frozen_string_literal: true + +require 'varar/core/span' +require 'varar/core/ast' +require 'varar/core/table_cells' + +module Varar + module Core + # Markdown block scanner. Port of scanner.ts. All offsets count UTF-16 + # code units; split_lines advances by utf16_len(line) + 1 per newline, and + # code-point indices from String#index are converted to UTF-16 before use. + module Scanner + RawLine = Data.define(:text, :start_offset, :end_offset) + + # Regexes — verbatim ports of the TS constants (`#` escaped as `\#` so + # Ruby does not read `#{...}` as interpolation). + THEMATIC_RE = /^\s*([-*_])(\s*\1){2,}\s*$/ + UL_RE = /^(\s*)([-*+])\s+(.*)$/ + OL_RE = /^(\s*)(\d+)([.)])\s+(.*)$/ + BQ_RE = /^>\s?(.*)$/ + FENCE_RE = /^(`{3,})\s*(\S*)\s*$/ + ROW_RE = /^\|(.+)\|\s*$/ + DELIM_RE = /^\|\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|\s*$/ + HEADING_RE = /^(\#{1,6})\s+(.*?)(?:\s+\#+)?\s*$/ + PARA_HEADING_RE = /^\#{1,6}\s+/ + + module_function + + # Scan +source+ into an immutable Array of Block nodes. + def scan(source, plugins = []) + blocks = [] + lines = split_lines(source) + + i = 0 + while i < lines.length + line = lines[i] + if line.text.strip.empty? + i += 1 + next + end + + matched = run_plugins(source, lines, i, plugins) + if matched + blocks << matched[0] + i = matched[1] + next + end + + fence_result = try_fence(source, lines, i) + if fence_result + blocks << fence_result[0] + i = fence_result[1] + next + end + + table_result = try_table(source, lines, i) + if table_result + blocks << table_result[0] + i = table_result[1] + next + end + + thematic = try_thematic(source, line) + if thematic + blocks << thematic + i += 1 + next + end + + bq_result = try_blockquote(source, lines, i) + if bq_result + blocks << bq_result[0] + i = bq_result[1] + next + end + + heading = try_heading(source, line) + if heading + blocks << heading + i += 1 + next + end + + list_item = try_list_item(source, line) + if list_item + blocks << list_item + i += 1 + next + end + + paragraph, next_i = consume_paragraph(source, lines, i, plugins) + blocks << paragraph + i = next_i + end + + blocks + end + + def run_plugins(source, lines, start_idx, plugins) + plugins.each do |p| + r = p.try_scan(source: source, lines: lines, start_idx: start_idx) + return r if r + end + nil + end + + # Split +source+ into RawLines with UTF-16 start/end offsets. + def split_lines(source) + out = [] + start_u16 = 0 + current_u16 = 0 + start_cp = 0 + + source.each_char.with_index do |ch, cp_i| + if ch == "\n" + out << RawLine.new(text: source[start_cp...cp_i], start_offset: start_u16, end_offset: current_u16) + start_u16 = current_u16 + 1 # '\n' is BMP → 1 UTF-16 unit + start_cp = cp_i + 1 + end + current_u16 += ch.ord > 0xFFFF ? 2 : 1 + end + + out << RawLine.new(text: source[start_cp..] || '', start_offset: start_u16, end_offset: current_u16) + out + end + + def try_thematic(source, line) + return nil unless THEMATIC_RE.match?(line.text) + + ThematicBreak.new(span: Offsets.span_from_offsets(source, line.start_offset, line.end_offset)) + end + + def try_heading(source, line) + m = HEADING_RE.match(line.text) + return nil unless m + + Heading.new( + level: m[1].length, + text: (m[2] || '').strip, + span: Offsets.span_from_offsets(source, line.start_offset, line.end_offset) + ) + end + + def try_list_item(source, line) + if (ul = UL_RE.match(line.text)) + text = ul[3] || '' + marker_start = line.start_offset + Offsets.utf16_len(ul[1] || '') + marker_end = marker_start + Offsets.utf16_len(ul[2] || '') + cp_idx = line.text.index(text) + text_start = line.start_offset + Offsets.to_utf16_offset(line.text, cp_idx) + return ListItem.new( + text: text, + span: Offsets.span_from_offsets(source, line.start_offset, line.end_offset), + segment_map: [SegmentOffset.new(text_offset: 0, source_offset: text_start)], + ordered: false, + marker_span: Offsets.span_from_offsets(source, marker_start, marker_end) + ) + end + + if (ol = OL_RE.match(line.text)) + text = ol[4] || '' + marker_start = line.start_offset + Offsets.utf16_len(ol[1] || '') + marker_end = marker_start + Offsets.utf16_len(ol[2] || '') + Offsets.utf16_len(ol[3] || '') + cp_idx = line.text.index(text) + text_start = line.start_offset + Offsets.to_utf16_offset(line.text, cp_idx) + return ListItem.new( + text: text, + span: Offsets.span_from_offsets(source, line.start_offset, line.end_offset), + segment_map: [SegmentOffset.new(text_offset: 0, source_offset: text_start)], + ordered: true, + marker_span: Offsets.span_from_offsets(source, marker_start, marker_end) + ) + end + + nil + end + + def try_blockquote(source, lines, start_idx) + return nil if start_idx >= lines.length + + first = lines[start_idx] + m = BQ_RE.match(first.text) + return nil unless m + + first_segment = m[1] || '' + cp_idx = first.text.index(first_segment) + segments = [first_segment] + segment_map = [ + SegmentOffset.new( + text_offset: 0, + source_offset: first.start_offset + Offsets.to_utf16_offset(first.text, cp_idx) + ) + ] + joined_text_offset = Offsets.utf16_len(first_segment) + + i = start_idx + 1 + end_offset = first.end_offset + while i < lines.length + ln = lines[i] + next_m = BQ_RE.match(ln.text) + break unless next_m + + segment = next_m[1] || '' + cp_idx2 = ln.text.index(segment) + joined_text_offset += 1 # newline separator + segment_map << SegmentOffset.new( + text_offset: joined_text_offset, + source_offset: ln.start_offset + Offsets.to_utf16_offset(ln.text, cp_idx2) + ) + segments << segment + joined_text_offset += Offsets.utf16_len(segment) + end_offset = ln.end_offset + i += 1 + end + + [ + Blockquote.new( + text: segments.join("\n"), + span: Offsets.span_from_offsets(source, first.start_offset, end_offset), + segment_map: segment_map + ), + i + ] + end + + def consume_paragraph(source, lines, start_idx, plugins) + raise 'invariant: start_idx out of range' if start_idx >= lines.length + + first = lines[start_idx] + end_idx = start_idx + while end_idx + 1 < lines.length + candidate_idx = end_idx + 1 + candidate = lines[candidate_idx] + break if candidate.text.strip.empty? + break if PARA_HEADING_RE.match?(candidate.text) + break if UL_RE.match?(candidate.text) + break if OL_RE.match?(candidate.text) + break if BQ_RE.match?(candidate.text) + break if FENCE_RE.match?(candidate.text) + break if ROW_RE.match?(candidate.text) + break if THEMATIC_RE.match?(candidate.text) + break if run_plugins(source, lines, candidate_idx, plugins) + + end_idx += 1 + end + + last = lines[end_idx] + start_offset = first.start_offset + end_offset = last.end_offset + [ + Paragraph.new( + text: Offsets.utf16_slice(source, start_offset, end_offset), + span: Offsets.span_from_offsets(source, start_offset, end_offset), + segment_map: [SegmentOffset.new(text_offset: 0, source_offset: start_offset)] + ), + end_idx + 1 + ] + end + + def try_fence(source, lines, start_idx) + return nil if start_idx >= lines.length + + start = lines[start_idx] + open_m = FENCE_RE.match(start.text) + return nil unless open_m + + fence_marker = open_m[1] || '' + info = (open_m[2] || '').strip + + i = start_idx + 1 + body_start = nil + body_end = nil + end_offset = start.end_offset + + while i < lines.length + ln = lines[i] + close_m = FENCE_RE.match(ln.text) + if close_m && (close_m[1] || '').length >= fence_marker.length + end_offset = ln.end_offset + break + end + body_start = ln.start_offset if body_start.nil? + body_end = ln.end_offset + 1 # +1 to include the '\n' after this line + i += 1 + end + + body = body_start.nil? || body_end.nil? ? '' : Offsets.utf16_slice(source, body_start, body_end) + + fallback = start.end_offset + body_span = Offsets.span_from_offsets(source, body_start || fallback, body_end || fallback) + [ + Fence.new( + info: info, + body: body, + body_span: body_span, + span: Offsets.span_from_offsets(source, start.start_offset, end_offset) + ), + i + 1 + ] + end + + def try_table(source, lines, start_idx) + return nil if start_idx + 1 >= lines.length + + header_line = lines[start_idx] + delim_line = lines[start_idx + 1] + return nil unless ROW_RE.match?(header_line.text) + return nil unless DELIM_RE.match?(delim_line.text) + + header_cells, header_cell_spans = TableCells.parse_row_cells(header_line.text, header_line.start_offset, + source) + header = Row.new( + cells: header_cells, + cell_spans: header_cell_spans, + span: Offsets.span_from_offsets(source, header_line.start_offset, header_line.end_offset) + ) + + rows = [] + i = start_idx + 2 + while i < lines.length + ln = lines[i] + break unless ROW_RE.match?(ln.text) + + cells, cell_spans = TableCells.parse_row_cells(ln.text, ln.start_offset, source) + rows << Row.new( + cells: cells, + cell_spans: cell_spans, + span: Offsets.span_from_offsets(source, ln.start_offset, ln.end_offset) + ) + i += 1 + end + + last_row = rows.last + end_offset = last_row ? last_row.span.end_offset : delim_line.end_offset + [ + Table.new( + span: Offsets.span_from_offsets(source, header_line.start_offset, end_offset), + header: header, + rows: rows + ), + i + ] + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/sentences.rb b/ruby/packages/core/lib/varar/core/sentences.rb new file mode 100644 index 00000000..162c3fc7 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/sentences.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +module Varar + module Core + # Split a block of plain text into sentences on . ! ? and \n, skipping + # terminators inside backtick spans and double-quoted strings and treating + # common abbreviations as non-breaking. All offsets are UTF-16 code units + # into the block text. Port of sentences.ts. + Sentence = Data.define(:text, :start_offset, :end_offset) + + module Sentences + ABBREVIATIONS = Set.new(['e.g.', 'i.e.', 'etc.', 'cf.', 'vs.']).freeze + + module_function + + def split_sentences(text) + cp_to_u16 = build_cp_to_u16(text) + n = text.length + out = [] + + # Mark no-split zones (backtick spans, double-quoted strings). + skip = Array.new(n, false) + j = 0 + while j < n + c = text[j] + if ['`', '"'].include?(c) + close = text.index(c, j + 1) + break if close.nil? + + (j..close).each { |k| skip[k] = true } + j = close + 1 + next + end + j += 1 + end + + i = 0 + segment_start = 0 + while i < n + if skip[i] + i += 1 + next + end + ch = text[i] + if ["\n", '.', '!', '?'].include?(ch) + if ch == '.' && inside_number_or_abbrev?(text, i) + i += 1 + next + end + stop = i + 1 + push_segment(out, text, segment_start, stop, cp_to_u16) + i = stop + i += 1 while i < n && [' ', "\n"].include?(text[i]) + segment_start = i + next + end + i += 1 + end + + push_segment(out, text, segment_start, n, cp_to_u16) + out + end + + # cp_to_u16[cp_i] is the UTF-16 offset of text[cp_i]. + def build_cp_to_u16(text) + result = Array.new(text.length + 1, 0) + u16 = 0 + text.each_char.with_index do |ch, i| + result[i] = u16 + u16 += ch.ord > 0xFFFF ? 2 : 1 + end + result[text.length] = u16 + result + end + + def inside_number_or_abbrev?(text, dot_pos) + prev = dot_pos.positive? ? text[dot_pos - 1] : '' + nxt = dot_pos + 1 < text.length ? text[dot_pos + 1] : '' + return true if digit?(prev) && digit?(nxt) + + ABBREVIATIONS.each do |abbrev| + start = [0, dot_pos + 1 - abbrev.length].max + return true if text[start...(dot_pos + 1)] == abbrev + end + lower?(nxt) + end + + def push_segment(out, text, start_cp, end_cp, cp_to_u16) + return if end_cp <= start_cp + + raw = text[start_cp...end_cp] + stripped = raw.strip + return if stripped.empty? + + lead = raw.length - raw.lstrip.length + trail = raw.length - raw.rstrip.length + out << Sentence.new( + text: stripped, + start_offset: cp_to_u16[start_cp + lead], + end_offset: cp_to_u16[end_cp - trail] + ) + end + + def digit?(ch) + !ch.empty? && ch.match?(/[0-9]/) + end + + # Unicode-aware "is a lowercase letter": has case and is already lower. + def lower?(ch) + !ch.empty? && ch != ch.upcase && ch == ch.downcase + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/span.rb b/ruby/packages/core/lib/varar/core/span.rb new file mode 100644 index 00000000..6b35512f --- /dev/null +++ b/ruby/packages/core/lib/varar/core/span.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module Varar + module Core + # A source span. Offsets and columns are **UTF-16 code units** (an astral + # character like 😀 counts as 2), matching the goldens and LSP's default + # position encoding. Lines/cols are 1-based. + Span = Data.define( + :start_offset, :end_offset, + :start_line, :start_col, + :end_line, :end_col + ) + + # UTF-16 offset conversion. Ruby strings are code-point indexed, so the + # whole core converts to/from UTF-16 code units here (the single riskiest + # part of the port — mirrors Python's span.py). Reused by the matcher and + # by hash.rb. + module Offsets + module_function + + # UTF-16 code-unit length of a string (astral chars count as 2). + def utf16_len(str) + n = 0 + str.each_char { |ch| n += ch.ord > 0xFFFF ? 2 : 1 } + n + end + + # UTF-16 offset of the code-point index `cp_index` in `source`. + def to_utf16_offset(source, cp_index) + utf16_len(source[0...cp_index]) + end + + # Inverse of to_utf16_offset: the code-point index at a UTF-16 offset. + def cp_index_for_utf16(source, u16) + count = 0 + source.each_char.with_index do |ch, i| + return i if count >= u16 + + count += ch.ord > 0xFFFF ? 2 : 1 + end + source.length + end + + # Slice `source` by UTF-16 offsets, returning the covered substring. + def utf16_slice(source, start_u16, end_u16) + a = cp_index_for_utf16(source, start_u16) + b = cp_index_for_utf16(source, end_u16) + source[a...b] + end + + # 1-based [line, col] at a UTF-16 offset; col counts UTF-16 units and + # resets to 1 after each newline (mirrors span.ts's lineCol). + def line_col(source, offset_u16) + line = 1 + col = 1 + count = 0 + source.each_char do |ch| + break if count >= offset_u16 + + width = ch.ord > 0xFFFF ? 2 : 1 + if ch == "\n" + line += 1 + col = 1 + else + col += width + end + count += width + end + [line, col] + end + + # Build a Span from UTF-16 start/end offsets. + def span_from_offsets(source, start_u16, end_u16) + start_line, start_col = line_col(source, start_u16) + end_line, end_col = line_col(source, end_u16) + Span.new( + start_offset: start_u16, end_offset: end_u16, + start_line: start_line, start_col: start_col, + end_line: end_line, end_col: end_col + ) + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/step_role.rb b/ruby/packages/core/lib/varar/core/step_role.rb new file mode 100644 index 00000000..f3ab828d --- /dev/null +++ b/ruby/packages/core/lib/varar/core/step_role.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Varar + module Core + # The role a step definition plays: + # "stimulus" — drives the software (arranges and acts on state) + # "sensor" — the read-only assertion (the only role that returns for + # comparison) + # Purely structural — never inspects sentence words (no Given/When/Then + # heuristics). Port of step-role.ts. + module StepRole + module_function + + # Guess a step's role from its document-order neighbours. A step with + # nothing after it is most likely the observation; anything followed by + # other steps is most likely driving the software. + def infer_step_role(neighbours) + after = neighbours[:after] || neighbours['after'] || [] + after.empty? ? 'sensor' : 'stimulus' + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/structurer.rb b/ruby/packages/core/lib/varar/core/structurer.rb new file mode 100644 index 00000000..3a531f34 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/structurer.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'varar/core/span' +require 'varar/core/ast' + +module Varar + module Core + # Group scanned blocks into Examples, tracking heading scope and orphan + # attachments. Port of structurer.ts. + module Structurer + module_function + + def structure(path, source, blocks) + examples = [] + orphan_attachments = [] + scope_stack = [] # [[level, text], ...] + last_example_idx = -1 + attachment_open = false + + blocks.each do |block| + case block.kind + when 'heading' + # Pop deeper-or-equal-level entries before pushing the new heading. + scope_stack.pop while !scope_stack.empty? && scope_stack.last[0] >= block.level + scope_stack << [block.level, block.text] + attachment_open = false + + when 'paragraph', 'list_item', 'blockquote' + # Merge a block into the previous example when that example's last + # block is an attachment (table/fence) with no blank line between. + if attachment_open && last_example_idx >= 0 + prev = examples[last_example_idx] + prev_last = prev.body.last + last_is_attachment = !prev_last.nil? && %w[table fence].include?(prev_last.kind) + if last_is_attachment + between = Offsets.utf16_slice(source, prev.span.end_offset, block.span.start_offset) + unless between.match?(/\n\s*\n/) + new_span = Offsets.span_from_offsets(source, prev.span.start_offset, block.span.end_offset) + examples[last_example_idx] = Example.new( + scope_stack: prev.scope_stack, + span: new_span, + body: prev.body + [block] + ) + next + end + end + end + + examples << Example.new( + scope_stack: scope_stack.map { |(_, text)| text }, + span: block.span, + body: [block] + ) + last_example_idx = examples.length - 1 + attachment_open = true + + when 'table', 'fence' + if attachment_open && last_example_idx >= 0 + prev = examples[last_example_idx] + new_span = Offsets.span_from_offsets(source, prev.span.start_offset, block.span.end_offset) + examples[last_example_idx] = Example.new( + scope_stack: prev.scope_stack, + span: new_span, + body: prev.body + [block] + ) + else + orphan_attachments << block + end + + when 'thematic_break' + attachment_open = false + end + end + + VarDoc.new( + path: path, + source: source, + examples: examples, + orphan_attachments: orphan_attachments + ) + end + end + end +end diff --git a/ruby/packages/core/lib/varar/core/table_cells.rb b/ruby/packages/core/lib/varar/core/table_cells.rb new file mode 100644 index 00000000..32b3a4f3 --- /dev/null +++ b/ruby/packages/core/lib/varar/core/table_cells.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'varar/core/span' + +module Varar + module Core + # Parse a Markdown/Gherkin table row into trimmed cells and per-cell source + # spans. Port of table-cells.ts. All offsets count UTF-16 code units. + module TableCells + module_function + + # Split a `| a | b |` row into [cells, cell_spans]. +line_start_offset+ + # is the UTF-16 offset of the row's first character within +source+. + def parse_row_cells(line_text, line_start_offset, source) + first_cp = line_text.index('|') + last_cp = line_text.rindex('|') + return [[], []] if first_cp.nil? || last_cp.nil? || last_cp <= first_cp + + # Pipe positions: convert code-point index to UTF-16 (matters when + # astral chars precede the pipe). '|' is ASCII → 1 UTF-16 unit. + first_u16 = Offsets.to_utf16_offset(line_text, first_cp) + inner_start_u16 = first_u16 + 1 + + inner = line_text[(first_cp + 1)...last_cp] + + cells = [] + cell_spans = [] + cursor = 0 # running UTF-16 position within inner + + # split(-1) keeps trailing empty segments, matching JS/Python split. + inner.split('|', -1).each do |seg| + trimmed = seg.strip + leading = Offsets.utf16_len(seg) - Offsets.utf16_len(seg.lstrip) + abs_start = line_start_offset + inner_start_u16 + cursor + leading + cells << trimmed + cell_spans << Offsets.span_from_offsets(source, abs_start, abs_start + Offsets.utf16_len(trimmed)) + cursor += Offsets.utf16_len(seg) + 1 # +1 for the '|' delimiter + end + + [cells, cell_spans] + end + end + end +end diff --git a/ruby/packages/core/spec/conformance/var_doc_conformance_spec.rb b/ruby/packages/core/spec/conformance/var_doc_conformance_spec.rb new file mode 100644 index 00000000..997d9ce4 --- /dev/null +++ b/ruby/packages/core/spec/conformance/var_doc_conformance_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'varar/core' + +module Varar + module Core + # Reproduces the shared conformance corpus' var-doc.json goldens + # byte-for-byte (parse stage). Mirrors var/tests/conformance.test.ts. + ::RSpec.describe 'var-doc conformance' do + def self.corpus_dir + dir = __dir__ + dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' + File.join(dir, 'conformance', 'bundles') + end + + corpus = corpus_dir + + Dir.children(corpus).sort.each do |bundle| + golden = File.join(corpus, bundle, 'golden', 'var-doc.json') + next unless File.exist?(golden) + + it "#{bundle} — var-doc.json matches golden" do + source = File.read(File.join(corpus, bundle, 'example.md'), encoding: 'UTF-8') + doc = Parse.parse('example.md', source) + actual = CanonicalJson.canonical_stringify(Conformance.to_var_doc_artifact(doc)) + expect(actual).to eq(File.read(golden, encoding: 'UTF-8')) + end + end + end + end +end diff --git a/ruby/packages/core/spec/varar/core/canonical_json_spec.rb b/ruby/packages/core/spec/varar/core/canonical_json_spec.rb new file mode 100644 index 00000000..96ca14f5 --- /dev/null +++ b/ruby/packages/core/spec/varar/core/canonical_json_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'varar/core' + +module Varar + module Core + ::RSpec.describe CanonicalJson do + subject(:stringify) { described_class.method(:canonical_stringify) } + + it 'sorts object keys recursively' do + expect(stringify.call({ 'b' => 1, 'a' => { 'd' => 2, 'c' => 3 } })) + .to eq(%({\n "a": {\n "c": 3,\n "d": 2\n },\n "b": 1\n}\n)) + end + + it "renders empty containers as {} and [] (not Ruby's [\\n\\n])" do + expect(stringify.call({ 'items' => [], 'meta' => {} })) + .to eq(%({\n "items": [],\n "meta": {}\n}\n)) + end + + it 'indents arrays with two spaces per level' do + expect(stringify.call([1, 2])).to eq("[\n 1,\n 2\n]\n") + end + + it 'keeps non-ASCII raw and escapes control characters like JS' do + expect(stringify.call({ 's' => "café 😀\n\t\"x\"" })) + .to eq(%({\n "s": "café 😀\\n\\t\\"x\\""\n}\n)) + end + + it 'appends a single trailing newline' do + expect(stringify.call(true)).to eq("true\n") + end + end + end +end diff --git a/ruby/packages/core/spec/varar/core/drift_spec.rb b/ruby/packages/core/spec/varar/core/drift_spec.rb new file mode 100644 index 00000000..f9019565 --- /dev/null +++ b/ruby/packages/core/spec/varar/core/drift_spec.rb @@ -0,0 +1,234 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'varar/core' + +module Varar + module Core + # A tiny in-memory BaselineStore for the reconcile tests. + class MemoryStore + attr_accessor :contents + + def initialize(initial = nil) + @contents = initial + end + + def read = @contents + def write(contents) = (@contents = contents) + end + + # Translated from drift.test.ts / test_drift.py. Drift has no conformance + # golden (bundles carry no baseline), so it is proven by these unit tests. + ::RSpec.describe Drifts do + def noop = ->(*_args) {} + + def reg(with_step: true) + r = Registries.create_registry + if with_step + r = Registries.add_step(r, expression: 'I withdraw {int}', expression_source_file: 'steps.rb', + expression_source_line: 1, handler: noop, kind: 'stimulus') + end + r + end + + def roman_reg(with_step: true) + r = Registries.create_registry + if with_step + r = Registries.add_step(r, expression: 'a decimal and a roman number', expression_source_file: 'steps.rb', + expression_source_line: 1, handler: noop, kind: 'sensor') + end + r + end + + def plan_for(source, registry) + var_doc = Parse.parse('w.md', source) + [var_doc, Plan.plan(var_doc, registry)] + end + + def bare(drifts) = drifts.map { |d| [d.name, d.line] } + + it 'records one entry per example-producing paragraph' do + var_doc, plan = plan_for('I withdraw 40.', reg) + expect(described_class.live_examples(var_doc, + plan)).to eq([BaselineExample.new(name: 'I withdraw 40', line: 1)]) + end + + it 'does not record a never-matched paragraph' do + var_doc, plan = plan_for('Just some prose.', reg) + expect(described_class.live_examples(var_doc, plan)).to eq([]) + end + + it 'derive_spec_baseline carries the source fingerprint' do + source = 'I withdraw 40.' + var_doc, plan = plan_for(source, reg) + baseline = described_class.derive_spec_baseline(source, var_doc, plan) + expect(baseline.source_hash).to eq(Hash32.hash_source(source)) + expect(baseline.examples).to eq([BaselineExample.new(name: 'I withdraw 40', line: 1)]) + end + + it 'no baseline means no drift' do + var_doc, plan = plan_for('I withdraw 40.', reg) + expect(described_class.detect_drift(nil, var_doc, plan)).to eq([]) + end + + it 'an unchanged spec and steps have no drift' do + source = 'I withdraw 40.' + var_doc, plan = plan_for(source, reg) + baseline = described_class.derive_spec_baseline(source, var_doc, plan) + expect(described_class.detect_drift(baseline, var_doc, plan)).to eq([]) + end + + it 'a renamed step drifts (matched by name)' do + source = 'I withdraw 40.' + var_doc, plan_with = plan_for(source, reg) + baseline = described_class.derive_spec_baseline(source, var_doc, plan_with) + _doc, plan_without = plan_for(source, reg(with_step: false)) + expect(bare(described_class.detect_drift(baseline, var_doc, plan_without))).to eq([['I withdraw 40', 1]]) + end + + it 'an in-place typo drifts (matched by line)' do + before_doc, before_plan = plan_for('I withdraw 40.', reg) + baseline = described_class.derive_spec_baseline('I withdraw 40.', before_doc, before_plan) + after_doc, after_plan = plan_for('I withdrraw 40.', reg) + expect(bare(described_class.detect_drift(baseline, after_doc, after_plan))).to eq([['I withdraw 40', 1]]) + end + + it 'a deleted paragraph is not drift' do + before_doc, before_plan = plan_for('I withdraw 40.', reg) + baseline = described_class.derive_spec_baseline('I withdraw 40.', before_doc, before_plan) + after_doc, after_plan = plan_for('', reg) + expect(described_class.detect_drift(baseline, after_doc, after_plan)).to eq([]) + end + + it 'moving and rewording a still-matching example does not drift' do + before = "I withdraw 40.\n\nI withdraw 10." + before_doc, before_plan = plan_for(before, reg) + baseline = described_class.derive_spec_baseline(before, before_doc, before_plan) + after_doc, after_plan = plan_for("I withdraw 11.\n\nI withdraw 40.", reg) + expect(described_class.detect_drift(baseline, after_doc, after_plan)).to eq([]) + end + + it 'move + reword + prose on the old line does not false-positive' do + before_doc, before_plan = plan_for('I withdraw 40.', reg) + baseline = described_class.derive_spec_baseline('I withdraw 40.', before_doc, before_plan) + after_doc, after_plan = plan_for("Just some notes.\n\nI withdraw 41.", reg) + expect(described_class.detect_drift(baseline, after_doc, after_plan)).to eq([]) + end + + it 'a paragraph rewritten past recognition is remove+add, not drift' do + before_doc, before_plan = plan_for('I withdraw 40.', reg) + baseline = described_class.derive_spec_baseline('I withdraw 40.', before_doc, before_plan) + after_doc, after_plan = plan_for('The branch closed years ago.', reg) + expect(described_class.detect_drift(baseline, after_doc, after_plan)).to eq([]) + end + + roman = "Each row gives a decimal and a roman number:\n\n" \ + "| decimal | roman |\n| ------: | :---- |\n| 3 | III |\n| 9 | IX |\n" + + it 'header-bound table records its binding paragraph once' do + var_doc, plan = plan_for(roman, roman_reg) + expect(described_class.live_examples(var_doc, plan)) + .to eq([BaselineExample.new(name: 'Each row gives a decimal and a roman number:', line: 1)]) + end + + it 'a header-bound binding paragraph that stops matching drifts' do + var_doc, plan_with = plan_for(roman, roman_reg) + baseline = described_class.derive_spec_baseline(roman, var_doc, plan_with) + _doc, plan_without = plan_for(roman, roman_reg(with_step: false)) + expect(bare(described_class.detect_drift(baseline, var_doc, plan_without))) + .to eq([['Each row gives a decimal and a roman number:', 1]]) + end + + it 'drift diagnostics are error severity' do + source = 'I withdraw 40.' + var_doc, plan_with = plan_for(source, reg) + baseline = described_class.derive_spec_baseline(source, var_doc, plan_with) + _doc, plan_without = plan_for(source, reg(with_step: false)) + diags = described_class.drift_diagnostics(described_class.detect_drift(baseline, var_doc, plan_without)) + expect(diags.length).to eq(1) + expect(diags[0].severity).to eq('error') + expect(diags[0].code).to eq('drift') + expect(diags[0].message).to include('I withdraw 40') + end + + it 'reconcile records on first run, then reports and preserves on drift' do + source = 'I withdraw 40.' + var_doc, plan_with = plan_for(source, reg) + store = MemoryStore.new + expect(described_class.reconcile_drift(store, 'w.md', source, var_doc, plan_with)).to eq([]) + before = store.contents + _doc, plan_without = plan_for(source, reg(with_step: false)) + drift = described_class.reconcile_drift(store, 'w.md', source, var_doc, plan_without) + expect(bare(drift)).to eq([['I withdraw 40', 1]]) + expect(store.contents).to eq(before) + end + + it 'reconcile update mode accepts drift' do + source = 'I withdraw 40.' + var_doc, plan_with = plan_for(source, reg) + store = MemoryStore.new + described_class.reconcile_drift(store, 'w.md', source, var_doc, plan_with) + _doc, plan_without = plan_for(source, reg(with_step: false)) + drift = described_class.reconcile_drift(store, 'w.md', source, var_doc, plan_without, update: true) + expect(drift).to eq([]) + lock = described_class.parse_var_lock(store.contents) + expect(lock.specs['w.md'].examples).to eq([]) + end + + expected_lock = <<~JSON + { + "version": 1, + "specs": { + "library.md": { + "sourceHash": "fnv1a:1a2b3c4d", + "examples": [ + { + "name": "I check out", + "line": 7 + } + ] + } + } + } + JSON + + it 'stringify matches the TypeScript serializer byte-for-byte' do + lock = VarLock.new( + version: 1, + specs: { 'library.md' => SpecBaseline.new(source_hash: 'fnv1a:1a2b3c4d', + examples: [BaselineExample.new(name: 'I check out', line: 7)]) } + ) + expect(described_class.stringify_var_lock(lock)).to eq(expected_lock) + end + + it 'parse round-trips a valid lock' do + lock = VarLock.new( + version: 1, + specs: { 'library.md' => SpecBaseline.new(source_hash: 'fnv1a:1a2b3c4d', + examples: [BaselineExample.new(name: 'I check out', line: 7)]) } + ) + expect(described_class.parse_var_lock(described_class.stringify_var_lock(lock))).to eq(lock) + end + + it 'stringify sorts spec paths' do + lock = VarLock.new( + version: 1, + specs: { + 'zebra.md' => SpecBaseline.new(source_hash: 'fnv1a:00000001', examples: []), + 'alpha.md' => SpecBaseline.new(source_hash: 'fnv1a:00000002', examples: []) + } + ) + text = described_class.stringify_var_lock(lock) + expect(text.index('alpha.md')).to be < text.index('zebra.md') + expect(text).to end_with("}\n") + end + + it 'parse rejects malformed input' do + expect(described_class.parse_var_lock('not json')).to be_nil + expect(described_class.parse_var_lock('{}')).to be_nil + expect(described_class.parse_var_lock('{"version":2,"specs":{}}')).to be_nil + expect(described_class.parse_var_lock('{"version":1,"specs":{"a.md":{"examples":[]}}}')).to be_nil + end + end + end +end diff --git a/ruby/packages/core/spec/varar/core/hash_spec.rb b/ruby/packages/core/spec/varar/core/hash_spec.rb new file mode 100644 index 00000000..bcfc6e68 --- /dev/null +++ b/ruby/packages/core/spec/varar/core/hash_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'varar/core' + +module Varar + module Core + # Translated from hash.test.ts / test_hash.py. + ::RSpec.describe Hash32 do + def hash_source(source) = described_class.hash_source(source) + + it 'is deterministic' do + expect(hash_source('abc')).to eq(hash_source('abc')) + end + + it 'changes for a one-character difference' do + expect(hash_source('abc')).not_to eq(hash_source('abd')) + end + + it 'is namespaced with the algorithm prefix' do + expect(hash_source('abc')).to start_with('fnv1a:') + end + + it 'matches the TypeScript vectors' do + expect(hash_source('hello')).to eq('fnv1a:4f9f2cab') + expect(hash_source('abc')).to eq('fnv1a:1a47e90b') + expect(hash_source("# Title\n")).to eq('fnv1a:4eace75e') + end + end + end +end diff --git a/ruby/packages/core/spec/varar/core/span_spec.rb b/ruby/packages/core/spec/varar/core/span_spec.rb new file mode 100644 index 00000000..272401bf --- /dev/null +++ b/ruby/packages/core/spec/varar/core/span_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'varar/core' + +module Varar + module Core + # Translated from typescript/packages/core/tests/span.test.ts and + # python/packages/core/tests/test_span.py. + ::RSpec.describe Offsets do + describe '.utf16_len' do + it 'counts ASCII, BMP, and astral characters in UTF-16 units' do + expect(described_class.utf16_len('abc')).to eq(3) + expect(described_class.utf16_len('é')).to eq(1) # BMP: 1 code unit + expect(described_class.utf16_len('😀')).to eq(2) # astral: surrogate pair + expect(described_class.utf16_len('a😀b')).to eq(4) + end + end + + describe '.to_utf16_offset' do + it 'counts UTF-16 units before a code-point index' do + s = 'a😀b' # cp indices: a=0 😀=1 b=2 + expect(described_class.to_utf16_offset(s, 0)).to eq(0) + expect(described_class.to_utf16_offset(s, 1)).to eq(1) # after "a" + expect(described_class.to_utf16_offset(s, 2)).to eq(3) # after "a😀" (1+2) + end + end + + describe '.utf16_slice' do + it 'round-trips through UTF-16 units' do + s = 'x😀y' # u16: x=0 😀=1..3 y=3 + expect(described_class.utf16_slice(s, 0, 1)).to eq('x') + expect(described_class.utf16_slice(s, 1, 3)).to eq('😀') + expect(described_class.utf16_slice(s, 3, 4)).to eq('y') + end + end + + describe '.line_col' do + it 'counts UTF-16 units, resetting column after a newline' do + s = "ab\n😀x" # u16 offsets: a0 b1 \n2 😀3-4 x5 + expect(described_class.line_col(s, 1)).to eq([1, 2]) + expect(described_class.line_col(s, 5)).to eq([2, 3]) # astral counts as 2 + end + end + + describe '.span_from_offsets' do + it 'computes line and column for a single-line source' do + span = described_class.span_from_offsets('hello world', 6, 11) + expect(span).to eq(Span.new( + start_offset: 6, end_offset: 11, + start_line: 1, start_col: 7, + end_line: 1, end_col: 12 + )) + end + + it 'handles multi-line sources' do + source = "line one\nline two\nline three" + span = described_class.span_from_offsets(source, 14, 17) # 'two' + expect(span).to eq(Span.new( + start_offset: 14, end_offset: 17, + start_line: 2, start_col: 6, + end_line: 2, end_col: 9 + )) + end + + it 'handles a range crossing a newline' do + span = described_class.span_from_offsets("ab\ncd", 1, 4) # 'b'..'d' + expect(span).to eq(Span.new( + start_offset: 1, end_offset: 4, + start_line: 1, start_col: 2, + end_line: 2, end_col: 2 + )) + end + end + end + end +end diff --git a/ruby/packages/var-core/oselvar-var-core.gemspec b/ruby/packages/core/varar-core.gemspec similarity index 91% rename from ruby/packages/var-core/oselvar-var-core.gemspec rename to ruby/packages/core/varar-core.gemspec index 93e4f80f..53d74ba3 100644 --- a/ruby/packages/var-core/oselvar-var-core.gemspec +++ b/ruby/packages/core/varar-core.gemspec @@ -1,13 +1,13 @@ # frozen_string_literal: true Gem::Specification.new do |s| - s.name = 'oselvar-var-core' + s.name = 'varar-core' s.version = '0.4.2' s.summary = 'Markdown-native BDD — pure functional core engine' s.description = 'The pure functional pipeline (parse, match, plan, execute, drift) behind Vár.' s.authors = ['Aslak Hellesøy'] s.email = ['aslak@oselvar.com'] - s.homepage = 'https://var.oselvar.com' + s.homepage = 'https://varar.dev' s.license = 'MIT' s.required_ruby_version = '>= 3.2' s.files = Dir['lib/**/*.rb'] diff --git a/ruby/packages/minitest/lib/varar/minitest.rb b/ruby/packages/minitest/lib/varar/minitest.rb new file mode 100644 index 00000000..b03f8e27 --- /dev/null +++ b/ruby/packages/minitest/lib/varar/minitest.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'minitest' +require 'varar/runner' + +module Varar + # Minitest adapter. One call turns every spec matched by varar.config.json into + # a generated Minitest::Test subclass — one class per spec file, one test + # method per example. Mirrors var-unittest. + # + # # test/var_test.rb + # require "varar/minitest" + # Varar::Minitest.generate_tests + module Minitest + VERSION = '0.4.2' + + module_function + + def generate_tests(namespace = Object, root: nil) + root ||= File.dirname(caller_locations(1, 1).first.path) + root = File.expand_path(root) + cfg = Config.read_var_config(root) + loaded = Runner.load_steps(cfg.steps, root) + store = Runner.create_file_baseline_store(root) + update = %w[1 true].include?(ENV.fetch('VAR_UPDATE', nil)) + + Runner.find_specs(cfg.docs_include, cfg.docs_exclude, root).each do |spec_path| + klass = build_test_case(spec_path, root, loaded, store, update) + namespace.const_set("Var_#{identifier(Runner.rel_posix(spec_path, root))}", klass) + end + end + + def build_test_case(spec_path, root, loaded, store, update) + rel = Runner.rel_posix(spec_path, root) + source = File.read(spec_path, encoding: 'UTF-8') + plan = Runner.plan_spec(File.basename(spec_path), source, loaded.registry) + pairs = Runner.examples_with_runs(plan, loaded.create_context, Runner::RecordingReporter.new) + + klass = Class.new(::Minitest::Test) + seen = Hash.new(0) + pairs.each do |example, run| + base = example.scope_stack.last || example.name + stem = identifier(base) + idx = seen[stem] + seen[stem] += 1 + method_name = idx.zero? ? "test_#{stem}" : "test_#{stem}_#{idx}" + klass.define_method(method_name) do + run.call + rescue StandardError => e + raise ::Minitest::Assertion, Runner.render_failure(e, source, rel) if Minitest.var_diff_error?(e) + + raise + end + end + + Core::Drifts.reconcile_drift(store, rel, source, plan.var_doc, plan, update: update).each do |drift| + message = Core::Diagnostics.drift_detected(drift.name, drift.span).message + klass.define_method("test_var_drift_#{drift.line}") { raise ::Minitest::Assertion, message } + end + + klass + end + + # A markdown/return mismatch is a test failure (Minitest::Assertion); any + # other exception propagates as an error. + def var_diff_error?(error) + error.is_a?(Core::CellMismatchError) || error.is_a?(Core::DocStringMismatchError) || + error.is_a?(Core::ReturnShapeError) || error.is_a?(Core::UnexpectedPassError) + end + + # Project arbitrary text onto a valid identifier fragment. + def identifier(text) + ident = text.gsub(/\W+/, '_').gsub(/\A_+|_+\z/, '') + ident = 'example' if ident.empty? + ident = "_#{ident}" if ident.match?(/\A\d/) + ident + end + end +end diff --git a/ruby/packages/minitest/spec/varar/minitest_spec.rb b/ruby/packages/minitest/spec/varar/minitest_spec.rb new file mode 100644 index 00000000..78304a30 --- /dev/null +++ b/ruby/packages/minitest/spec/varar/minitest_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'tmpdir' +require 'fileutils' +require 'varar/minitest' + +module Varar + ::RSpec.describe Minitest do + def corpus_dir + dir = __dir__ + dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' + File.join(dir, 'conformance', 'bundles') + end + + # Build a throwaway project from a conformance bundle (its example.md + + # *.steps.rb) with a matching varar.config.json. + def project_from_bundle(tmp, bundle, spec_name) + src = File.join(corpus_dir, bundle) + FileUtils.mkdir_p(File.join(tmp, 'steps')) + FileUtils.cp(File.join(src, 'example.md'), File.join(tmp, spec_name)) + FileUtils.cp(Dir.glob(File.join(src, '*.steps.rb')).first, File.join(tmp, 'steps')) + File.write(File.join(tmp, 'varar.config.json'), + '{"docs":{"include":["*.md"]},"steps":["steps/*.steps.rb"]}') + end + + it 'generates one Test subclass per spec with a passing method for a passing example' do + Dir.mktmpdir do |tmp| + project_from_bundle(tmp, '01-roman-numerals', 'pass.md') + namespace = Module.new + described_class.generate_tests(namespace, root: tmp) + + klass = namespace.constants.map { |c| namespace.const_get(c) }.first + expect(klass.ancestors).to include(::Minitest::Test) + methods = klass.instance_methods(false).grep(/^test_/) + expect(methods).not_to be_empty + methods.each do |m| + expect { klass.new(m.to_s).public_send(m) }.not_to raise_error + end + end + end + + it 'a doc-string mismatch surfaces as a Minitest::Assertion (a failure)' do + Dir.mktmpdir do |tmp| + project_from_bundle(tmp, '06-doc-string-mismatch', 'fail.md') + namespace = Module.new + described_class.generate_tests(namespace, root: tmp) + + klass = namespace.constants.map { |c| namespace.const_get(c) }.first + method = klass.instance_methods(false).grep(/^test_/).first + expect { klass.new(method.to_s).public_send(method) }.to raise_error(::Minitest::Assertion) + end + end + end +end diff --git a/ruby/packages/var-minitest/oselvar-var-minitest.gemspec b/ruby/packages/minitest/varar-minitest.gemspec similarity index 81% rename from ruby/packages/var-minitest/oselvar-var-minitest.gemspec rename to ruby/packages/minitest/varar-minitest.gemspec index 628e331c..3cadf934 100644 --- a/ruby/packages/var-minitest/oselvar-var-minitest.gemspec +++ b/ruby/packages/minitest/varar-minitest.gemspec @@ -1,19 +1,19 @@ # frozen_string_literal: true Gem::Specification.new do |s| - s.name = 'oselvar-var-minitest' + s.name = 'varar-minitest' s.version = '0.4.2' s.summary = 'Markdown-native BDD — run Markdown specs as Minitest tests' s.description = 'Minitest adapter: one selectable test per Markdown example, with a drift gate.' s.authors = ['Aslak Hellesøy'] s.email = ['aslak@oselvar.com'] - s.homepage = 'https://var.oselvar.com' + s.homepage = 'https://varar.dev' s.license = 'MIT' s.required_ruby_version = '>= 3.2' s.files = Dir['lib/**/*.rb'] s.require_paths = ['lib'] s.add_dependency 'minitest', '~> 6.0' - s.add_dependency 'oselvar-var-runner', '0.4.2' + s.add_dependency 'varar-runner', '0.4.2' s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/ruby/packages/rspec/lib/varar/rspec.rb b/ruby/packages/rspec/lib/varar/rspec.rb new file mode 100644 index 00000000..cc2b1dcd --- /dev/null +++ b/ruby/packages/rspec/lib/varar/rspec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'rspec/core' +require 'varar/runner' + +module Varar + # RSpec adapter. One call defines an RSpec example group per spec matched by + # varar.config.json, with one `it` per Markdown example (header-bound rows are + # separate examples) and a drift gate. See ADR 0005. + # + # # spec/var_spec.rb + # require "varar/rspec" + # Varar::RSpec.generate + module RSpec + VERSION = '0.4.2' + + module_function + + def generate(root: nil) + root ||= File.dirname(caller_locations(1, 1).first.path) + root = File.expand_path(root) + cfg = Config.read_var_config(root) + loaded = Runner.load_steps(cfg.steps, root) + store = Runner.create_file_baseline_store(root) + update = %w[1 true].include?(ENV.fetch('VAR_UPDATE', nil)) + + Runner.find_specs(cfg.docs_include, cfg.docs_exclude, root).each do |spec_path| + define_group(spec_path, root, loaded, store, update) + end + end + + def define_group(spec_path, root, loaded, store, update) + rel = Runner.rel_posix(spec_path, root) + source = File.read(spec_path, encoding: 'UTF-8') + plan = Runner.plan_spec(File.basename(spec_path), source, loaded.registry) + pairs = Runner.examples_with_runs(plan, loaded.create_context, Runner::RecordingReporter.new) + drifts = Core::Drifts.reconcile_drift(store, rel, source, plan.var_doc, plan, update: update) + + ::RSpec.describe(rel) do + pairs.each do |example, run| + # A var diff surfaces as a failure carrying the span-anchored render; + # any other exception propagates. RSpec reports both as failures. + it(example.name) do + run.call + rescue StandardError => e + raise Runner.render_failure(e, source, rel) if RSpec.var_diff_error?(e) + + raise + end + end + + drifts.each do |drift| + message = Core::Diagnostics.drift_detected(drift.name, drift.span).message + it("var drift at line #{drift.line}") { raise message } + end + end + end + + def var_diff_error?(error) + error.is_a?(Core::CellMismatchError) || error.is_a?(Core::DocStringMismatchError) || + error.is_a?(Core::ReturnShapeError) || error.is_a?(Core::UnexpectedPassError) + end + end +end diff --git a/ruby/packages/rspec/spec/varar/rspec_spec.rb b/ruby/packages/rspec/spec/varar/rspec_spec.rb new file mode 100644 index 00000000..edebd12a --- /dev/null +++ b/ruby/packages/rspec/spec/varar/rspec_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'tmpdir' +require 'varar/rspec' + +module Varar + # The RSpec adapter is exercised end-to-end by examples/ruby-rspec (run via + # the real `rspec` binary). Here we unit-test the failure classification and + # that generate no-ops cleanly on a project with no specs. + ::RSpec.describe RSpec do + describe '.var_diff_error?' do + it 'classifies var diff/shape errors as failures' do + expect(described_class.var_diff_error?(Core::ReturnShapeError.new('x'))).to be(true) + expect(described_class.var_diff_error?(Core::UnexpectedPassError.new)).to be(true) + expect(described_class.var_diff_error?(Core::CellMismatchError.new([]))).to be(true) + end + + it 'does not classify arbitrary errors as failures' do + expect(described_class.var_diff_error?(RuntimeError.new('boom'))).to be(false) + end + end + + it 'generate no-ops when the project has no specs' do + Dir.mktmpdir do |tmp| + File.write(File.join(tmp, 'varar.config.json'), + '{"docs":{"include":["*.md"]},"steps":["steps/*.steps.rb"]}') + expect { described_class.generate(root: tmp) }.not_to raise_error + end + end + end +end diff --git a/ruby/packages/var-rspec/oselvar-var-rspec.gemspec b/ruby/packages/rspec/varar-rspec.gemspec similarity index 81% rename from ruby/packages/var-rspec/oselvar-var-rspec.gemspec rename to ruby/packages/rspec/varar-rspec.gemspec index f86bc957..7f815d6c 100644 --- a/ruby/packages/var-rspec/oselvar-var-rspec.gemspec +++ b/ruby/packages/rspec/varar-rspec.gemspec @@ -1,19 +1,19 @@ # frozen_string_literal: true Gem::Specification.new do |s| - s.name = 'oselvar-var-rspec' + s.name = 'varar-rspec' s.version = '0.4.2' s.summary = 'Markdown-native BDD — run Markdown specs as RSpec examples' s.description = 'RSpec adapter: one selectable example per Markdown example, with a drift gate.' s.authors = ['Aslak Hellesøy'] s.email = ['aslak@oselvar.com'] - s.homepage = 'https://var.oselvar.com' + s.homepage = 'https://varar.dev' s.license = 'MIT' s.required_ruby_version = '>= 3.2' s.files = Dir['lib/**/*.rb'] s.require_paths = ['lib'] - s.add_dependency 'oselvar-var-runner', '0.4.2' s.add_dependency 'rspec-core', '~> 3.13' + s.add_dependency 'varar-runner', '0.4.2' s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/ruby/packages/runner/exe/varar b/ruby/packages/runner/exe/varar new file mode 100755 index 00000000..599553e5 --- /dev/null +++ b/ruby/packages/runner/exe/varar @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'varar/runner/cli' + +exit Varar::Runner::CLI.main(ARGV) diff --git a/ruby/packages/runner/lib/varar/runner.rb b/ruby/packages/runner/lib/varar/runner.rb new file mode 100644 index 00000000..57210047 --- /dev/null +++ b/ruby/packages/runner/lib/varar/runner.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'varar' +require 'varar/config' +require 'varar/core' + +module Varar + # The imperative shell: discovery, step loading, planning, failure + # rendering, and the filesystem drift baseline store. Depends on the facade + # and config; never on a test framework. Port of var-runner. + module Runner + VERSION = '0.4.2' + end +end + +require 'varar/runner/discovery' +require 'varar/runner/steps' +require 'varar/runner/run' +require 'varar/runner/render' +require 'varar/runner/baseline_store' diff --git a/ruby/packages/runner/lib/varar/runner/baseline_store.rb b/ruby/packages/runner/lib/varar/runner/baseline_store.rb new file mode 100644 index 00000000..efaefa96 --- /dev/null +++ b/ruby/packages/runner/lib/varar/runner/baseline_store.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Varar + module Runner + # The filesystem BaselineStore: the committed drift baseline lives at the + # project root as varar.lock.json. The core owns the format; this adapter + # only moves raw text. Port of baseline_store.py. + class FileBaselineStore + def initialize(root) + @path = File.join(root.to_s, 'varar.lock.json') + end + + def read + File.exist?(@path) ? File.read(@path, encoding: 'UTF-8') : nil + end + + def write(contents) + File.write(@path, contents) + end + end + + module_function + + def create_file_baseline_store(root) + FileBaselineStore.new(root) + end + end +end diff --git a/ruby/packages/runner/lib/varar/runner/cli.rb b/ruby/packages/runner/lib/varar/runner/cli.rb new file mode 100644 index 00000000..0f04f7b0 --- /dev/null +++ b/ruby/packages/runner/lib/varar/runner/cli.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'fileutils' + +module Varar + module Runner + # The `var` command-line entry point (exposed by the `exe/var` + # executable). Today it offers a single sub-command, `varar init`, which + # scaffolds a new project: a `varar.config.json`, one Markdown spec, its + # step definitions, and a framework bridge that turns the specs into + # RSpec examples or Minitest tests. + # + # The config, spec and steps mirror the TypeScript CLI (`@varar/cli`) + # so a project started with `varar init` looks the same in every language; + # only the bridge is Ruby-specific, because RSpec/Minitest — unlike + # pytest — need an explicit generator call to discover the specs. + module CLI + CONFIG = <<~JSON + { + "docs": { "include": ["varar-examples/**/*.md"], "exclude": [] }, + "steps": ["varar-examples/**/*.steps.rb"] + } + JSON + + EXAMPLE_MD = <<~MARKDOWN + # Hello, BDD + + Given I greet "world" + Then the greeting is "Hello, world!" + MARKDOWN + + EXAMPLE_STEPS = <<~RUBY + # frozen_string_literal: true + + require 'varar' + + steps(greeting: '') do + stimulus('I greet {string}') { |_state, name| { greeting: "Hello, \#{name}!" } } + sensor('the greeting is {string}') { |state, _expected| state[:greeting] } + end + RUBY + + RSPEC_BRIDGE = <<~RUBY + # frozen_string_literal: true + + # Turn every Markdown spec matched by varar.config.json into RSpec examples — + # one `it` per Markdown example, discovered when this file loads. + require 'varar/rspec' + + # varar.config.json lives at the project root (the parent of spec/). + Varar::RSpec.generate(root: File.expand_path('..', __dir__)) + RUBY + + MINITEST_BRIDGE = <<~RUBY + # frozen_string_literal: true + + require 'minitest/autorun' + require 'varar/minitest' + + # Turn every Markdown spec matched by varar.config.json into Minitest tests — + # varar.config.json lives at the project root (the parent of test/). + Varar::Minitest.generate_tests(Object, root: File.expand_path('..', __dir__)) + RUBY + + USAGE = <<~TEXT + varar — scaffold and run Markdown specs + + Usage: + varar init scaffold a new project + TEXT + + def self.main(argv, cwd: Dir.pwd, out: $stdout) + case argv.first + when 'init' + run_init(cwd, out) + else + out.print(USAGE) + argv.empty? || %w[help -h --help].include?(argv.first) ? 0 : 1 + end + end + + # Write the scaffold into +cwd+, skipping any file that already exists. + # The framework bridge matches whichever adapter gem is installed + # (RSpec by default). + def self.run_init(cwd, out, framework: detect_framework) + files = [ + ['varar.config.json', CONFIG], + ['varar-examples/01-hello.md', EXAMPLE_MD], + ['varar-examples/steps/01-hello.steps.rb', EXAMPLE_STEPS] + ] + files << if framework == :minitest + ['test/var_test.rb', MINITEST_BRIDGE] + else + ['spec/var_spec.rb', RSPEC_BRIDGE] + end + + files.each do |rel, content| + target = File.join(cwd, rel) + if File.exist?(target) + out.puts "skipped #{rel} (already exists)" + next + end + FileUtils.mkdir_p(File.dirname(target)) + File.write(target, content) + out.puts "created #{rel}" + end + 0 + end + + # RSpec when its adapter is installed, Minitest when only that one is, + # RSpec as the fallback (matching the tutorial's default track). + def self.detect_framework + return :rspec if gem_present?('varar-rspec') + return :minitest if gem_present?('varar-minitest') + + :rspec + end + + def self.gem_present?(name) + Gem::Specification.find_all_by_name(name).any? + rescue StandardError + false + end + end + end +end diff --git a/ruby/packages/runner/lib/varar/runner/discovery.rb b/ruby/packages/runner/lib/varar/runner/discovery.rb new file mode 100644 index 00000000..5ec242bf --- /dev/null +++ b/ruby/packages/runner/lib/varar/runner/discovery.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'pathname' + +module Varar + module Runner + module_function + + # Translate a glob with **, *, ? to an anchored regex (PEP 428 / pathlib + # full_match semantics), matching the other ports' hand-rolled compiler + # rather than Ruby's Dir glob. Port of _glob_to_regex. + def glob_to_regex(pattern) + result = +'' + i = 0 + n = pattern.length + while i < n + c = pattern[i] + if c == '/' && pattern[i, 4] == '/**/' + result << '/(?:.+/)?' + i += 4 + elsif c == '/' && pattern[i, 3] == '/**' && i + 3 == n + result << '(?:/.*)?' + i += 3 + elsif c == '*' && pattern[i, 3] == '**/' + result << '(?:.*/)?' + i += 3 + elsif c == '*' && pattern[i, 2] == '**' + result << '.*' + i += 2 + elsif c == '*' + result << '[^/]*' + i += 1 + elsif c == '?' + result << '[^/]' + i += 1 + else + result << Regexp.escape(c) + i += 1 + end + end + /\A#{result}\z/ + end + + # Relative POSIX path of +path+ within +root+, without dereferencing + # symlinks; yields a ../ prefix when +path+ is outside +root+. + def rel_posix(path, root) + Pathname.new(File.expand_path(path)) + .relative_path_from(Pathname.new(File.expand_path(root))).to_s + end + + def matches_any?(rel, globs) + globs.any? { |g| glob_to_regex(g).match?(rel) } + end + + # True iff +path+ matches an include glob and no exclude glob. + def match_spec?(path, include, exclude, root) + rel = rel_posix(path, root) + matches_any?(rel, include) && !matches_any?(rel, exclude) + end + + # Existing files under +root+ matching any include glob, minus excludes; sorted. + def find_specs(include, exclude, root) + out = [] + include.each do |g| + out.concat(Dir.glob(g, base: root).map { |rel| File.join(root, rel) }) + end + out = out.select { |p| File.file?(p) }.uniq + out.reject { |p| matches_any?(rel_posix(p, root), exclude) }.sort + end + end +end diff --git a/ruby/packages/runner/lib/varar/runner/render.rb b/ruby/packages/runner/lib/varar/runner/render.rb new file mode 100644 index 00000000..1b291437 --- /dev/null +++ b/ruby/packages/runner/lib/varar/runner/render.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'varar/core' + +module Varar + module Runner + module_function + + # Render a step failure as a human-readable, markdown-anchored string, + # dispatching on the concrete error type. Port of render.py. + def render_failure(error, _source, path) + case error + when Core::CellMismatchError + lines = ["Cell mismatch in #{path}:"] + failing = error.cells.reject(&:ok) + lines << ' (no failing cells)' if failing.empty? + failing.each do |cell| + lines << " line #{cell.span.start_line} | column '#{cell.column}' — " \ + "expected: #{cell.expected.inspect}, actual: #{cell.actual.inspect}" + end + lines.join("\n") + when Core::DocStringMismatchError + diff = error.diff + "Doc string mismatch at line #{diff.span.start_line}:\n " \ + "expected: #{diff.expected.inspect}\n actual: #{diff.actual.inspect}" + when Core::ReturnShapeError + error.message + else + "#{error.class}: #{error.message}" + end + end + end +end diff --git a/ruby/packages/runner/lib/varar/runner/run.rb b/ruby/packages/runner/lib/varar/runner/run.rb new file mode 100644 index 00000000..219d569d --- /dev/null +++ b/ruby/packages/runner/lib/varar/runner/run.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'varar/core' + +module Varar + module Runner + # Collects diagnostics emitted during planning/execution. + class RecordingReporter + attr_reader :diagnostics + + def initialize + @diagnostics = [] + end + + def diagnostic(diagnostic) + @diagnostics << diagnostic + end + end + + module_function + + def plan_spec(path, source, registry) + Core::Plan.plan(Core::Parse.parse(path, source), registry) + end + + # Pair each PlannedExample with its lazy run closure, in plan order. + def examples_with_runs(execution_plan, create_context, reporter) + reporter_cb = ->(d) { reporter.diagnostic(d) } + queue = Core::Execute.collect_examples(execution_plan, create_context: create_context, reporter: reporter_cb) + execution_plan.examples.zip(queue).map { |example, queued| [example, queued.run] } + end + end +end diff --git a/ruby/packages/runner/lib/varar/runner/steps.rb b/ruby/packages/runner/lib/varar/runner/steps.rb new file mode 100644 index 00000000..77cf10d7 --- /dev/null +++ b/ruby/packages/runner/lib/varar/runner/steps.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'varar' +require 'varar/registry' + +module Varar + module Runner + # The registry + per-file context factory built from loaded step files. + LoadedSteps = Data.define(:registry, :create_context) + + module_function + + # Reset the accumulator, load (execute) every step file matching + # +step_globs+ under +root+, and build the registry + context factory. + def load_steps(step_globs, root) + RegistryGlue.reset_builder + files = [] + step_globs.each do |g| + files.concat(Dir.glob(g, base: root).map { |rel| File.join(root, rel) }) + end + files.select { |p| File.file?(p) }.uniq.sort.each { |path| load path } + LoadedSteps.new(registry: RegistryGlue.build_registry, create_context: RegistryGlue.context_factory) + end + end +end diff --git a/ruby/packages/runner/spec/varar/runner/cli_spec.rb b/ruby/packages/runner/spec/varar/runner/cli_spec.rb new file mode 100644 index 00000000..978aa985 --- /dev/null +++ b/ruby/packages/runner/spec/varar/runner/cli_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'tmpdir' +require 'stringio' +require 'varar/runner/cli' + +module Varar + module Runner + ::RSpec.describe CLI do + describe '.run_init' do + it 'scaffolds the config, spec, steps and an RSpec bridge' do + Dir.mktmpdir do |dir| + out = StringIO.new + code = described_class.run_init(dir, out, framework: :rspec) + + expect(code).to eq(0) + expect(File.exist?(File.join(dir, 'varar.config.json'))).to be(true) + expect(File.exist?(File.join(dir, 'varar-examples/01-hello.md'))).to be(true) + steps = File.read(File.join(dir, 'varar-examples/steps/01-hello.steps.rb')) + expect(steps).to include('steps(greeting: \'\') do', 'stimulus(', 'sensor(') + expect(File.read(File.join(dir, 'spec/var_spec.rb'))).to include('Varar::RSpec.generate') + expect(out.string).to include('created varar.config.json') + end + end + + it 'writes a Minitest bridge when that framework is selected' do + Dir.mktmpdir do |dir| + described_class.run_init(dir, StringIO.new, framework: :minitest) + expect(File.exist?(File.join(dir, 'test/var_test.rb'))).to be(true) + expect(File.exist?(File.join(dir, 'spec/var_spec.rb'))).to be(false) + end + end + + it 'skips files that already exist' do + Dir.mktmpdir do |dir| + File.write(File.join(dir, 'varar.config.json'), "{}\n") + out = StringIO.new + described_class.run_init(dir, out, framework: :rspec) + + expect(File.read(File.join(dir, 'varar.config.json'))).to eq("{}\n") + expect(out.string).to include('skipped varar.config.json (already exists)') + end + end + end + end + end +end diff --git a/ruby/packages/runner/spec/varar/runner_spec.rb b/ruby/packages/runner/spec/varar/runner_spec.rb new file mode 100644 index 00000000..d5c7fc46 --- /dev/null +++ b/ruby/packages/runner/spec/varar/runner_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'json' +require 'varar/runner' + +module Varar + ::RSpec.describe Runner do + describe '.glob_to_regex / .match_spec?' do + it 'matches * within a segment but not across /' do + expect(described_class.glob_to_regex('*.md').match?('a.md')).to be(true) + expect(described_class.glob_to_regex('*.md').match?('dir/a.md')).to be(false) + end + + it 'matches **/ across nested segments (leading)' do + rx = described_class.glob_to_regex('**/*.steps.rb') + expect(rx.match?('a.steps.rb')).to be(true) + expect(rx.match?('steps/a.steps.rb')).to be(true) + expect(rx.match?('a/b/c.steps.rb')).to be(true) + end + + it 'honours excludes' do + root = Dir.pwd + expect(described_class.match_spec?(File.join(root, 'a.md'), ['*.md'], [], root)).to be(true) + expect(described_class.match_spec?(File.join(root, 'README.md'), ['*.md'], ['README.md'], root)).to be(false) + end + end + + describe 'dogfood: bundle outcomes match trace.json' do + def self.corpus_dir + dir = __dir__ + dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' + File.join(dir, 'conformance', 'bundles') + end + + corpus = corpus_dir + + Dir.children(corpus).sort.each do |bundle| + bundle_dir = File.join(corpus, bundle) + trace_json = File.join(bundle_dir, 'golden', 'trace.json') + steps_rb = Dir.glob(File.join(bundle_dir, '*.steps.rb')).first + next unless File.exist?(trace_json) && steps_rb + + it "#{bundle} — runner outcomes agree with the trace goldens" do + loaded = described_class.load_steps(['*.steps.rb'], bundle_dir) + source = File.read(File.join(bundle_dir, 'example.md'), encoding: 'UTF-8') + plan = described_class.plan_spec('example.md', source, loaded.registry) + pairs = described_class.examples_with_runs(plan, loaded.create_context, Runner::RecordingReporter.new) + + actual = pairs.map do |example, run| + outcome = 'pass' + begin + run.call + rescue StandardError + outcome = 'fail' + end + [example.name, outcome] + end + + trace = JSON.parse(File.read(trace_json, encoding: 'UTF-8')) + expected = trace['examples'].map { |e| [e['name'], e['outcome']] } + expect(actual).to eq(expected) + end + end + end + end +end diff --git a/ruby/packages/var-runner/oselvar-var-runner.gemspec b/ruby/packages/runner/varar-runner.gemspec similarity index 74% rename from ruby/packages/var-runner/oselvar-var-runner.gemspec rename to ruby/packages/runner/varar-runner.gemspec index 566524ec..9626673c 100644 --- a/ruby/packages/var-runner/oselvar-var-runner.gemspec +++ b/ruby/packages/runner/varar-runner.gemspec @@ -1,21 +1,21 @@ # frozen_string_literal: true Gem::Specification.new do |s| - s.name = 'oselvar-var-runner' + s.name = 'varar-runner' s.version = '0.4.2' s.summary = 'Markdown-native BDD — imperative shell (discovery, loading, drift)' s.description = 'Spec/step discovery, step loading, planning, failure rendering, and the drift baseline store.' s.authors = ['Aslak Hellesøy'] s.email = ['aslak@oselvar.com'] - s.homepage = 'https://var.oselvar.com' + s.homepage = 'https://varar.dev' s.license = 'MIT' s.required_ruby_version = '>= 3.2' s.files = Dir['lib/**/*.rb'] + Dir['exe/*'] s.bindir = 'exe' - s.executables = ['var'] + s.executables = ['varar'] s.require_paths = ['lib'] - s.add_dependency 'oselvar-var', '0.4.2' - s.add_dependency 'oselvar-var-config', '0.4.2' + s.add_dependency 'varar', '0.4.2' + s.add_dependency 'varar-config', '0.4.2' s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/ruby/packages/var-config/lib/oselvar/var/config.rb b/ruby/packages/var-config/lib/oselvar/var/config.rb deleted file mode 100644 index 81c50a16..00000000 --- a/ruby/packages/var-config/lib/oselvar/var/config.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -require 'json' - -module Oselvar - module Var - # Strict, fail-loud reader for the shared var.config.json format. Missing - # file → empty config; malformed JSON, wrong types, or unknown keys → an - # error starting with the file path. See conformance/config/README.md. - module Config - VERSION = '0.4.2' - - # The parsed config. All fields default to empty. - VarConfig = Data.define(:docs_include, :docs_exclude, :steps, :snippets, :scanner_plugins) do - def initialize(docs_include: [], docs_exclude: [], steps: [], snippets: {}, scanner_plugins: []) - super - end - end - - KNOWN_KEYS = %w[$schema docs steps snippets scannerPlugins].freeze - KNOWN_DOCS_KEYS = %w[include exclude].freeze - - module_function - - def read_var_config(root) - path = File.join(root.to_s, 'var.config.json') - return VarConfig.new unless File.file?(path) - - data = begin - JSON.parse(File.read(path, encoding: 'UTF-8')) - rescue JSON::ParserError => e - raise ArgumentError, "#{path}: invalid JSON: #{e.message}" - end - raise ArgumentError, "#{path}: top level must be an object" unless data.is_a?(::Hash) - - unknown = data.keys - KNOWN_KEYS - raise ArgumentError, "#{path}: unknown key(s): #{unknown.sort.join(', ')}" unless unknown.empty? - - docs = data['docs'] || {} - raise ArgumentError, "#{path}: 'docs' must be an object" unless docs.is_a?(::Hash) - - unknown_docs = docs.keys - KNOWN_DOCS_KEYS - raise ArgumentError, "#{path}: unknown docs key(s): #{unknown_docs.sort.join(', ')}" unless unknown_docs.empty? - - snippets = data['snippets'] || {} - unless snippets.is_a?(::Hash) && snippets.all? { |k, v| k.is_a?(String) && v.is_a?(String) } - raise ArgumentError, "#{path}: 'snippets' must be an object of strings" - end - - VarConfig.new( - docs_include: string_array(docs['include'], 'docs.include', path), - docs_exclude: string_array(docs['exclude'], 'docs.exclude', path), - steps: string_array(data['steps'], 'steps', path), - snippets: snippets, - scanner_plugins: string_array(data['scannerPlugins'], 'scannerPlugins', path) - ) - end - - def string_array(value, key, path) - return [] if value.nil? - unless value.is_a?(Array) && value.all?(String) - raise ArgumentError, "#{path}: '#{key}' must be an array of strings" - end - - value - end - end - end -end diff --git a/ruby/packages/var-config/spec/conformance/config_conformance_spec.rb b/ruby/packages/var-config/spec/conformance/config_conformance_spec.rb deleted file mode 100644 index 861ca5f5..00000000 --- a/ruby/packages/var-config/spec/conformance/config_conformance_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'oselvar/var/config' -require 'oselvar/var/core' # for CanonicalJson (test-only) - -module Oselvar - module Var - # Reproduces the shared config corpus byte-for-byte: each case parses to its - # golden.json, or (with an expect-error.txt marker) must fail to load. See - # conformance/config/README.md. - ::RSpec.describe 'config conformance' do - def self.cases_dir - dir = __dir__ - dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'config', 'cases')) || dir == '/' - File.join(dir, 'conformance', 'config', 'cases') - end - - def self.artifact(cfg) - { - 'docs' => { 'include' => cfg.docs_include, 'exclude' => cfg.docs_exclude }, - 'steps' => cfg.steps, - 'snippets' => cfg.snippets, - 'scannerPlugins' => cfg.scanner_plugins - } - end - - cases = cases_dir - - Dir.children(cases).sort.each do |name| - case_dir = File.join(cases, name) - next unless File.directory?(case_dir) - - if File.exist?(File.join(case_dir, 'expect-error.txt')) - it "#{name} — loading fails" do - expect { Config.read_var_config(case_dir) }.to raise_error(StandardError) - end - else - it "#{name} — matches golden" do - actual = Core::CanonicalJson.canonical_stringify(self.class.artifact(Config.read_var_config(case_dir))) - expect(actual).to eq(File.read(File.join(case_dir, 'golden.json'), encoding: 'UTF-8')) - end - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core.rb b/ruby/packages/var-core/lib/oselvar/var/core.rb deleted file mode 100644 index 28fc036d..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Oselvar - module Var - # The pure functional core: parse, match, plan, execute, diffs, drift, and - # the conformance projections. No filesystem, network, globals, or time. - module Core - VERSION = '0.4.2' - end - end -end - -require 'oselvar/var/core/span' -require 'oselvar/var/core/ast' -require 'oselvar/var/core/table_cells' -require 'oselvar/var/core/scanner' -require 'oselvar/var/core/structurer' -require 'oselvar/var/core/parse' -require 'oselvar/var/core/step_role' -require 'oselvar/var/core/registry' -require 'oselvar/var/core/sentences' -require 'oselvar/var/core/diagnostics' -require 'oselvar/var/core/cell_diff' -require 'oselvar/var/core/matcher' -require 'oselvar/var/core/plan' -require 'oselvar/var/core/deep_freeze' -require 'oselvar/var/core/doc_string_diff' -require 'oselvar/var/core/param_diff' -require 'oselvar/var/core/failure_anchor' -require 'oselvar/var/core/execute' -require 'oselvar/var/core/hash' -require 'oselvar/var/core/drift' -require 'oselvar/var/core/canonical_json' -require 'oselvar/var/core/conformance' diff --git a/ruby/packages/var-core/lib/oselvar/var/core/ast.rb b/ruby/packages/var-core/lib/oselvar/var/core/ast.rb deleted file mode 100644 index d395c046..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/ast.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/span' - -module Oselvar - module Var - module Core - # Maps a block-text offset to its source offset. Block text is the raw - # source minus BLOCK markers only (list bullets, blockquote `>` prefixes); - # inline markup is never stripped. A paragraph/list item has a single - # entry; a blockquote one entry per quoted line. - SegmentOffset = Data.define(:text_offset, :source_offset) - - Heading = Data.define(:level, :text, :span) do - def kind = 'heading' - end - - Paragraph = Data.define(:text, :span, :segment_map) do - def kind = 'paragraph' - end - - ListItem = Data.define(:text, :span, :segment_map, :ordered, :marker_span) do - def kind = 'list_item' - end - - Blockquote = Data.define(:text, :span, :segment_map) do - def kind = 'blockquote' - end - - Row = Data.define(:cells, :cell_spans, :span) - - Table = Data.define(:span, :header, :rows) do - def kind = 'table' - end - - Fence = Data.define(:span, :info, :body, :body_span) do - def kind = 'fence' - end - - ThematicBreak = Data.define(:span) do - def kind = 'thematic_break' - end - - Example = Data.define(:scope_stack, :span, :body) - - VarDoc = Data.define(:path, :source, :examples, :orphan_attachments) - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/canonical_json.rb b/ruby/packages/var-core/lib/oselvar/var/core/canonical_json.rb deleted file mode 100644 index 39b61db5..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/canonical_json.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'json' - -module Oselvar - module Var - module Core - # JSON serializers byte-for-byte compatible with JS `JSON.stringify(v, null, 2)`: - # 2-space indent, LF, trailing newline, non-ASCII raw, empty containers as - # {}/[]. `canonical_stringify` recursively sorts object keys (the goldens); - # `ordered_stringify` preserves insertion order (var.lock.json). - # - # The container layout is hand-rolled because Ruby's JSON.pretty_generate - # renders empty arrays/objects as "[\n\n]". Scalar encoding is delegated to - # the stdlib, which matches JS (escapes " \ control chars, keeps non-ASCII raw). - module CanonicalJson - module_function - - def canonical_stringify(value) - "#{encode(value, '', sort_keys: true)}\n" - end - - def ordered_stringify(value) - "#{encode(value, '', sort_keys: false)}\n" - end - - def encode(value, indent, sort_keys:) - case value - when Hash - return '{}' if value.empty? - - keys = sort_keys ? value.keys.sort : value.keys - inner = "#{indent} " - items = keys.map { |key| "#{inner}#{key.to_s.to_json}: #{encode(value[key], inner, sort_keys: sort_keys)}" } - "{\n#{items.join(",\n")}\n#{indent}}" - when Array - return '[]' if value.empty? - - inner = "#{indent} " - items = value.map { |element| "#{inner}#{encode(element, inner, sort_keys: sort_keys)}" } - "[\n#{items.join(",\n")}\n#{indent}]" - else - value.to_json - end - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/cell_diff.rb b/ruby/packages/var-core/lib/oselvar/var/core/cell_diff.rb deleted file mode 100644 index e4b3dca0..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/cell_diff.rb +++ /dev/null @@ -1,107 +0,0 @@ -# frozen_string_literal: true - -module Oselvar - module Var - module Core - # One checked column of one header-bound row: the cell text and its span. - RowCheck = Data.define(:column, :value, :span) - - # The verdict for one checked column after comparing against the table. - # expected_value/actual_value/formatted are adapter-facing, never serialized. - CellDiff = Data.define(:column, :span, :expected, :actual, :ok, - :expected_value, :actual_value, :formatted) do - def initialize(column:, span:, expected:, actual:, ok:, - expected_value: nil, actual_value: nil, formatted: false) - super - end - end - - # The step returned the wrong type/shape — an author mistake, not a value diff. - class ReturnShapeError < StandardError; end - - # Raised when a header-bound row's / a table's returned columns don't match. - class CellMismatchError < StandardError - attr_reader :cells - - def initialize(cells) - @cells = cells - super(cells.map { |c| "#{c.column}: expected #{c.expected} but was #{c.actual}" }.join('; ')) - end - end - - # Pure comparison of row/table step returns against the authored cells. - # Port of cell-diff.ts. - module CellDiffs - module_function - - # Display rules 2-4 of the mismatch-rendering chain (rule 1, the - # parameter type's `format`, is applied in param_diff). A string renders - # as-is, other primitives via to_s, anything else via inspect. The - # inspect fallback is port-native and deliberately outside conformance. - def render_cell_value(value) - return value if value.is_a?(String) - return value.to_s if value.nil? || value == true || value == false || - value.is_a?(Integer) || value.is_a?(Float) - - value.inspect - end - - # Compare a row step's returned Hash against the row's cells. Only columns - # present on +returned+ are checked; a non-Hash return checks nothing. - def compare_row(returned, checks) - return [] unless returned.is_a?(Hash) - - checks.filter_map do |check| - next unless returned.key?(check.column) - - actual = render_cell_value(returned[check.column]) - CellDiff.new(column: check.column, span: check.span, expected: check.value, - actual: actual, ok: actual == check.value) - end - end - - # Compare a whole-table step's returned table against the input table. - # +returned+: nil (no checks), Array of Arrays (positional), or Array of - # Hashes (keyed by header). Cells compare as exact strings. - def compare_table(returned, input_table) - return [] if returned.nil? - raise ReturnShapeError, "expected a table (array of rows), got #{returned.class}" unless returned.is_a?(Array) - - columns = input_table.header.cells - data_rows = input_table.rows - if returned.length != data_rows.length - raise ReturnShapeError, "expected #{data_rows.length} row(s), got #{returned.length}" - end - - all_arrays = returned.all?(Array) - all_records = returned.all?(Hash) - raise ReturnShapeError, 'table rows must be all arrays or all objects' if !all_arrays && !all_records - - diffs = [] - data_rows.each_with_index do |row, i| - ret = returned[i] - if all_arrays && ret.length != columns.length - raise ReturnShapeError, "row #{i}: expected #{columns.length} column(s), got #{ret.length}" - end - - columns.each_with_index do |column, j| - if all_arrays - actual_value = ret[j] - else - raise ReturnShapeError, "row #{i}: missing column \"#{column}\"" unless ret.key?(column) - - actual_value = ret[column] - end - expected = j < row.cells.length ? row.cells[j] : '' - actual = render_cell_value(actual_value) - span = j < row.cell_spans.length ? row.cell_spans[j] : row.span - diffs << CellDiff.new(column: column, span: span, expected: expected, actual: actual, - ok: actual == expected) - end - end - diffs - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/conformance.rb b/ruby/packages/var-core/lib/oselvar/var/core/conformance.rb deleted file mode 100644 index d0996ca9..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/conformance.rb +++ /dev/null @@ -1,264 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/ast' -require 'oselvar/var/core/plan' -require 'oselvar/var/core/execute' -require 'oselvar/var/core/failure_anchor' - -module Oselvar - module Var - module Core - # Projections from the internal pipeline values to the camelCase wire - # dicts compared against golden/*.json. Port of conformance.ts. (var-doc - # stage; registry/plan/trace projections are added in later stages.) - module Conformance - module_function - - def span_hash(span) - { - 'startOffset' => span.start_offset, - 'endOffset' => span.end_offset, - 'startLine' => span.start_line, - 'startCol' => span.start_col, - 'endLine' => span.end_line, - 'endCol' => span.end_col - } - end - - def segment_hash(segment_offset) - { - 'textOffset' => segment_offset.text_offset, - 'sourceOffset' => segment_offset.source_offset - } - end - - def row_hash(row) - { - 'cells' => row.cells, - 'cellSpans' => row.cell_spans.map { |cs| span_hash(cs) }, - 'span' => span_hash(row.span) - } - end - - def block_hash(block) - case block.kind - when 'paragraph', 'blockquote' - { - 'kind' => block.kind, - 'text' => block.text, - 'span' => span_hash(block.span), - 'segmentMap' => block.segment_map.map { |so| segment_hash(so) } - } - when 'heading' - { - 'kind' => block.kind, - 'level' => block.level, - 'text' => block.text, - 'span' => span_hash(block.span) - } - when 'list_item' - { - 'kind' => block.kind, - 'text' => block.text, - 'span' => span_hash(block.span), - 'segmentMap' => block.segment_map.map { |so| segment_hash(so) }, - 'ordered' => block.ordered, - 'markerSpan' => span_hash(block.marker_span) - } - when 'table' - { - 'kind' => block.kind, - 'span' => span_hash(block.span), - 'header' => row_hash(block.header), - 'rows' => block.rows.map { |r| row_hash(r) } - } - when 'fence' - { - 'kind' => block.kind, - 'span' => span_hash(block.span), - 'info' => block.info, - 'body' => block.body, - 'bodySpan' => span_hash(block.body_span) - } - when 'thematic_break' - { - 'kind' => block.kind, - 'span' => span_hash(block.span) - } - else - raise "Unknown block kind: #{block.kind}" - end - end - - def example_hash(example) - { - 'scopeStack' => example.scope_stack, - 'span' => span_hash(example.span), - 'body' => example.body.map { |b| block_hash(b) } - } - end - - # Project a VarDoc to the wire dict for the var-doc artifact. - def to_var_doc_artifact(doc) - { - 'path' => doc.path, - 'examples' => doc.examples.map { |ex| example_hash(ex) }, - 'orphanAttachments' => doc.orphan_attachments.map { |b| block_hash(b) } - } - end - - # Parameter-type names in source order from a compiled CucumberExpression. - # The Ruby gem populates @parameter_types in source order during - # construction (it has no public reader), mirroring the TS AST walk. - def parameter_type_names(compiled) - compiled.instance_variable_get(:@parameter_types).map(&:name) - end - - # Project a Registry to the wire dict for the registry artifact. - # +parameter_types+ is the custom-type list ({"name","regexp"}). - def to_registry_artifact(registry, parameter_types = []) - { - 'steps' => registry.steps.map do |s| - { 'expression' => s.expression, 'parameterTypeNames' => parameter_type_names(s.compiled) } - end, - 'parameterTypes' => parameter_types.map do |p| - { 'name' => p['name'], 'regexp' => p['regexp'] } - end - } - end - - def doc_string_hash(doc_string) - { - 'content' => doc_string.content, - 'contentType' => doc_string.content_type, - 'span' => span_hash(doc_string.span) - } - end - - # Project an ExecutionPlan to the wire dict for the plan artifact. - def to_plan_artifact(plan) - source = plan.var_doc.source - { - 'examples' => plan.examples.map { |ex| planned_example_hash(ex, source) }, - 'diagnostics' => plan.diagnostics.map do |d| - { 'code' => d.code, 'severity' => d.severity, 'span' => span_hash(d.span) } - end - } - end - - def planned_example_hash(example, source) - result = { - 'name' => example.name, - 'scopeStack' => example.scope_stack, - 'span' => span_hash(example.span), - 'expectedOutcome' => example.expected_outcome || 'pass' - } - result['expectedErrorMessage'] = example.expected_error_message if example.expected_error_message - result['steps'] = example.steps.map { |s| planned_step_hash(s, source) } - result - end - - def planned_step_hash(step, source) - step_names = parameter_type_names(step.step_def.compiled) - result = { - 'text' => step.text, - 'matchSpan' => span_hash(step.match_span), - 'paramSpans' => step.param_spans.map { |s| span_hash(s) }, - 'matchedExpression' => step.step_def.expression, - 'args' => step.param_spans.each_with_index.map do |s, i| - { - 'value' => Offsets.utf16_slice(source, s.start_offset, s.end_offset), - 'parameterType' => i < step_names.length ? step_names[i] : nil - } - end - } - result['dataTable'] = block_hash(step.data_table) if step.data_table - result['docString'] = doc_string_hash(step.doc_string) if step.doc_string - result - end - - # Return the file stem: "path/to/foo.steps.rb" -> "foo.steps". - def file_stem(path) - File.basename(path, '.*') - end - - # Project an execution error to a FailureArtifact dict. line and anchor - # are deterministic source positions (never scraped from a backtrace). - def to_failure_artifact(error, match_span) - line = match_span.start_line - anchor = span_hash(FailureAnchor.failure_anchor(error, match_span)) - case error - when CellMismatchError - { - 'kind' => 'cell-mismatch', 'line' => line, 'anchor' => anchor, - 'cells' => error.cells.reject(&:ok).map do |c| - { 'column' => c.column, 'expected' => c.expected, 'actual' => c.actual, 'span' => span_hash(c.span) } - end - } - when DocStringMismatchError - { - 'kind' => 'doc-string-mismatch', 'line' => line, 'anchor' => anchor, - 'diff' => { - 'expected' => error.diff.expected, - 'actual' => error.diff.actual, - 'span' => span_hash(error.diff.span) - } - } - when ReturnShapeError - { 'kind' => 'return-shape', 'line' => line, 'anchor' => anchor } - when UnexpectedPassError - { 'kind' => 'unexpected-pass', 'line' => line, 'anchor' => anchor } - else - { 'kind' => 'thrown', 'line' => line, 'anchor' => anchor } - end - end - - # Run all examples and return the four-artifact bundle. Port of runConformance. - def run_conformance(var_doc, registry, create_context, parameter_types = []) - execution = Plan.plan(var_doc, registry) - observed = Hash.new { |h, k| h[k] = [] } - observer = ->(o) { observed[o.example_index] << o } - queue = Execute.collect_examples(execution, create_context: create_context, observer: observer) - - trace_examples = queue.each_with_index.map do |queued, k| - outcome = 'pass' - begin - queued.run.call - rescue StandardError - outcome = 'fail' - end - - planned = execution.examples[k] - obs_list = observed[k] - steps = planned.steps.each_with_index.map do |step, i| - ordinal = i + 1 - matches = obs_list.select { |x| x.ordinal == ordinal } - observation = matches.find { |m| m.outcome == 'fail' } || matches.last - step_outcome = observation ? observation.outcome : 'skipped' - step_dict = { - 'exampleName' => queued.name, - 'ordinal' => ordinal, - 'stepText' => step.text, - 'matchedExpression' => step.step_def.expression, - 'contextKey' => { 'exampleName' => queued.name, - 'stepFile' => file_stem(step.step_def.expression_source_file) }, - 'outcome' => step_outcome - } - step_dict['failure'] = to_failure_artifact(observation&.error, step.match_span) if step_outcome == 'fail' - step_dict - end - - { 'name' => queued.name, 'outcome' => outcome, 'steps' => steps } - end - - { - var_doc: to_var_doc_artifact(var_doc), - registry: to_registry_artifact(registry, parameter_types), - plan: to_plan_artifact(execution), - trace: { 'examples' => trace_examples } - } - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/deep_freeze.rb b/ruby/packages/var-core/lib/oselvar/var/core/deep_freeze.rb deleted file mode 100644 index 28927993..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/deep_freeze.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Oselvar - module Var - module Core - # Recursively freeze plain Hash/Array so handler code mutating state raises - # FrozenError. Other objects (class instances, primitives, nil) pass - # through. Assumes acyclic input. Port of deep-freeze.ts. - module DeepFreeze - module_function - - def deep_freeze(value) - case value - when Hash - value.each_value { |v| deep_freeze(v) } - value.freeze - when Array - value.each { |v| deep_freeze(v) } - value.freeze - else - value - end - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/diagnostics.rb b/ruby/packages/var-core/lib/oselvar/var/core/diagnostics.rb deleted file mode 100644 index 771e3b25..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/diagnostics.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module Oselvar - module Var - module Core - # A planning/run diagnostic on the shared rail. code is one of - # "ambiguous-match", "error-fence-without-step", "drift". Port of - # diagnostics.ts. - Diagnostic = Data.define(:code, :severity, :message, :span) - Candidate = Data.define(:expression, :source_file, :source_line) - AmbiguousInput = Data.define(:text, :span, :candidates) - - module Diagnostics - module_function - - def ambiguous_match(input) - lines = input.candidates.map do |c| - " '#{c.expression}' at #{c.source_file}:#{c.source_line}" - end.join("\n") - Diagnostic.new( - severity: 'error', - code: 'ambiguous-match', - message: "Ambiguous step: \"#{input.text}\"\nMatched by:\n#{lines}", - span: input.span - ) - end - - def drift_detected(name, span) - Diagnostic.new( - severity: 'error', - code: 'drift', - message: "This paragraph was an example and no longer matches any step (drift): \"#{name}\".\n" \ - 'Fix the step so it matches again, or accept it as prose (run in update mode).', - span: span - ) - end - - def error_fence_without_step(span) - Diagnostic.new( - severity: 'error', - code: 'error-fence-without-step', - message: 'This `error` fence marks the example as expected-to-fail, ' \ - 'but the example has no step to run.', - span: span - ) - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/doc_string_diff.rb b/ruby/packages/var-core/lib/oselvar/var/core/doc_string_diff.rb deleted file mode 100644 index ed802b42..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/doc_string_diff.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/cell_diff' - -module Oselvar - module Var - module Core - # A doc-string content difference: fence body span, expected, actual. - DocStringDiff = Data.define(:span, :expected, :actual) - - # Raised when a doc-string step's returned string differs from the content. - class DocStringMismatchError < StandardError - attr_reader :diff - - def initialize(diff) - @diff = diff - super("doc string: expected #{diff.expected.inspect} but was #{diff.actual.inspect}") - end - end - - # Pure comparison of a doc-string step's return against the fence body. - # Port of doc-string-diff.ts. - module DocStringDiffs - module_function - - # nil → no check; equal string → nil (pass); unequal → DocStringDiff; - # non-string → ReturnShapeError. - def compare_doc_string(returned, content, span) - return nil if returned.nil? - raise ReturnShapeError, "expected a doc string (string), got #{returned.class}" unless returned.is_a?(String) - return nil if returned == content - - DocStringDiff.new(span: span, expected: content, actual: returned) - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/drift.rb b/ruby/packages/var-core/lib/oselvar/var/core/drift.rb deleted file mode 100644 index 784ee17b..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/drift.rb +++ /dev/null @@ -1,187 +0,0 @@ -# frozen_string_literal: true - -require 'json' -require 'oselvar/var/core/hash' -require 'oselvar/var/core/diagnostics' -require 'oselvar/var/core/plan' -require 'oselvar/var/core/canonical_json' - -module Oselvar - module Var - module Core - # One example-producing paragraph, as recorded in the baseline. - BaselineExample = Data.define(:name, :line) - # The committed baseline for one spec file. - SpecBaseline = Data.define(:source_hash, :examples) - # The whole var.lock.json: every spec keyed by its POSIX path. - VarLock = Data.define(:version, :specs) - # A paragraph the baseline says was an example and now matches no step. - Drift = Data.define(:name, :line, :span) - - # Spec drift detection: a paragraph the committed var.lock.json baseline - # recorded as an example that now matches no step. Pure, byte-identical to - # the TS port so var.lock.json is shared across languages. Port of drift.ts. - # - # BaselineStore is a duck-typed port: #read -> String|nil, #write(contents). - module Drifts - # A paragraph may be moved anywhere and reworded up to ~half its words - # and still be recognized; edit it past this and it reads as remove+add, - # not drift. Ported byte-identically. - SIMILARITY_THRESHOLD = 0.5 - TOKEN_RE = /[[:alnum:]]+/ - - module_function - - def within?(inner, outer) - inner.start_offset >= outer.start_offset && inner.end_offset <= outer.end_offset - end - - def live?(candidate_span, plan) - plan.examples.any? { |pe| within?(pe.span, candidate_span) } - end - - # Lower-cased word tokens (letters/digits) — the unit of similarity. - def tokenize(text) - text.downcase.scan(TOKEN_RE).to_set - end - - # Jaccard overlap |A∩B| / |A∪B|. 1 identical, 0 disjoint; two empty = 1. - def similarity(set_a, set_b) - return 1.0 if set_a.empty? && set_b.empty? - - intersection = (set_a & set_b).size - union = set_a.size + set_b.size - intersection - union.zero? ? 0.0 : intersection.to_f / union - end - - # The current example-producing paragraphs, in document order. - def live_examples(var_doc, plan) - var_doc.examples.filter_map do |candidate| - next unless live?(candidate.span, plan) - - BaselineExample.new(name: Plan.derive_example_name(candidate.body), line: candidate.span.start_line) - end - end - - def derive_spec_baseline(source, var_doc, plan) - SpecBaseline.new(source_hash: Hash32.hash_source(source), examples: live_examples(var_doc, plan)) - end - - # Paragraphs the baseline recorded as examples that now match zero steps. - # Each re-identified by the most word-similar current paragraph at/above - # the threshold (exact name scores 1; ties break toward the nearest line). - def detect_drift(baseline, var_doc, plan) - return [] if baseline.nil? - - candidates = var_doc.examples - tokens = candidates.map { |c| tokenize(Plan.derive_example_name(c.body)) } - live = candidates.map { |c| live?(c.span, plan) } - - baseline.examples.filter_map do |b| - b_tokens = tokenize(b.name) - best_idx = -1 - best_score = 0.0 - candidates.each_with_index do |candidate, i| - score = similarity(b_tokens, tokens[i]) - next if score < SIMILARITY_THRESHOLD - - line = candidate.span.start_line - best_line = best_idx >= 0 ? candidates[best_idx].span.start_line : 0 - next unless best_idx.negative? || score > best_score || - (score == best_score && (line - b.line).abs < (best_line - b.line).abs) - - best_idx = i - best_score = score - end - next if best_idx.negative? - next if live[best_idx] - - Drift.new(name: b.name, line: candidates[best_idx].span.start_line, span: candidates[best_idx].span) - end - end - - def drift_diagnostics(drifts) - drifts.map { |d| Diagnostics.drift_detected(d.name, d.span) } - end - - # One spec's baseline reconciliation against a BaselineStore. In update - # mode, accept all drift (re-record, report nothing); otherwise detect - # drift and rewrite the baseline only on a clean run, so an unacknowledged - # drift keeps its old entry (and stays red). - def reconcile_drift(store, spec_path, source, var_doc, plan, update: false) - text = store.read - lock = text ? parse_var_lock(text) : nil - baseline = lock ? lock.specs[spec_path] : nil - drifts = update ? [] : detect_drift(baseline, var_doc, plan) - if update || drifts.empty? - specs = lock ? lock.specs.dup : {} - specs[spec_path] = derive_spec_baseline(source, var_doc, plan) - store.write(stringify_var_lock(VarLock.new(version: 1, specs: specs))) - end - drifts - end - - def parse_var_lock(text) - parsed = JSON.parse(text) - return nil unless parsed.is_a?(::Hash) && parsed['version'] == 1 - - specs_raw = parsed['specs'] - return nil unless specs_raw.is_a?(::Hash) - - specs = {} - specs_raw.each do |path, value| - baseline = parse_spec_baseline(value) - return nil if baseline.nil? - - specs[path] = baseline - end - VarLock.new(version: 1, specs: specs) - rescue JSON::ParserError, TypeError - nil - end - - def parse_spec_baseline(value) - return nil unless value.is_a?(::Hash) - - source_hash = value['sourceHash'] - examples_raw = value['examples'] - return nil unless source_hash.is_a?(String) && examples_raw.is_a?(Array) - - examples = [] - examples_raw.each do |item| - parsed = parse_baseline_example(item) - return nil if parsed.nil? - - examples << parsed - end - SpecBaseline.new(source_hash: source_hash, examples: examples) - end - - def parse_baseline_example(value) - return nil unless value.is_a?(::Hash) - - name = value['name'] - line = value['line'] - return nil unless name.is_a?(String) && line.is_a?(Integer) - - BaselineExample.new(name: name, line: line) - end - - # Serialize var.lock.json deterministically: spec paths sorted, examples - # in document order, insertion-order keys otherwise (version, specs; - # sourceHash, examples; name, line) — NOT canonical JSON's key sort. - def stringify_var_lock(lock) - specs = {} - lock.specs.keys.sort.each do |path| - baseline = lock.specs[path] - specs[path] = { - 'sourceHash' => baseline.source_hash, - 'examples' => baseline.examples.map { |e| { 'name' => e.name, 'line' => e.line } } - } - end - CanonicalJson.ordered_stringify({ 'version' => 1, 'specs' => specs }) - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/execute.rb b/ruby/packages/var-core/lib/oselvar/var/core/execute.rb deleted file mode 100644 index 09e287ca..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/execute.rb +++ /dev/null @@ -1,189 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/span' -require 'oselvar/var/core/deep_freeze' -require 'oselvar/var/core/cell_diff' -require 'oselvar/var/core/doc_string_diff' -require 'oselvar/var/core/param_diff' -require 'oselvar/var/core/failure_anchor' - -module Oselvar - module Var - module Core - # Raised when an expected-to-fail example passes unexpectedly. - class UnexpectedPassError < StandardError - def initialize(message = 'expected the example to fail, but it passed') - super - end - end - - # Per-step outcome emitted to the optional observer. - StepObservation = Data.define(:example_name, :example_index, :ordinal, :step_file, :outcome, :error) do - def initialize(example_name:, example_index:, ordinal:, step_file:, outcome:, error: nil) - super - end - end - - # A named, runnable example returned by collect_examples. - QueuedExample = Data.define(:name, :run) - - # Execute an ExecutionPlan: route stimulus/sensor returns, merge immutable - # state, compare sensor returns via the diff helpers, invert expected - # failures. Handlers are user callbacks (sync). Port of execute.ts. - module Execute - module_function - - # Collect all examples into an ordered Array of QueuedExamples. - def collect_examples(plan, create_context:, observer: nil, reporter: nil) - queue = [] - sink = ->(name, run, _info) { queue << QueuedExample.new(name: name, run: run) } - execute_plan(plan, sink: sink, create_context: create_context, observer: observer, reporter: reporter) - queue - end - - def execute_plan(plan, sink:, create_context:, observer: nil, reporter: nil) - plan.diagnostics.each { |d| reporter.call(d) } if reporter - create_ctx = create_context || ->(_file) { {} } - var_path = plan.var_doc.path - - plan.examples.each_with_index do |ex, example_index| - seen_lines = {} - ex.steps.each { |s| seen_lines[s.match_span.start_line] = true } - info = { lines: seen_lines.keys } - sink.call(ex.name, build_run(plan, ex, example_index, create_ctx, observer, var_path), info) - end - end - - def build_run(plan, ex, example_index, create_ctx, observer, var_path) - lambda do - state_by_file = {} - last_return = nil - thrown = nil - - ex.steps.each_with_index do |step, i| - file = step.step_def.expression_source_file - state_by_file[file] = DeepFreeze.deep_freeze(create_ctx.call(file)) unless state_by_file.key?(file) - state = state_by_file[file] - - extra = [] - if step.data_table - extra << ([step.data_table.header.cells] + step.data_table.rows.map(&:cells)) - elsif step.doc_string - extra << step.doc_string.content - end - - begin - returned = step.step_def.handler.call(state, *step.args, *extra) - last_return = returned - case step.step_def.kind - when 'stimulus' - unless returned.nil? - unless returned.is_a?(Hash) - raise ReturnShapeError, - 'a stimulus must return a partial state object or nothing' - end - - state = DeepFreeze.deep_freeze(state.merge(returned)) - state_by_file[file] = state - end - when 'sensor' - compare_sensor_return(plan, ex, step, returned, extra) if ex.row_checks.nil? && !returned.nil? - else - raise ReturnShapeError, "unknown step kind: #{step.step_def.kind}" - end - rescue StandardError => e - augmented = augment_stack(e, step, var_path) - observer&.call(observation(ex, example_index, i + 1, file, 'fail', augmented)) - thrown = augmented - break - end - - observer&.call(observation(ex, example_index, i + 1, file, 'pass')) - end - - # Header-bound row checks (after all steps). - if thrown.nil? && ex.row_checks && !ex.row_checks.empty? - bad = CellDiffs.compare_row(last_return, ex.row_checks).reject(&:ok) - unless bad.empty? - last_step = ex.steps.last - augmented = augment_stack(CellMismatchError.new(bad), last_step, var_path) - observer&.call(observation(ex, example_index, ex.steps.length, - last_step.step_def.expression_source_file, 'fail', augmented)) - thrown = augmented - end - end - - # Expected-failure inversion. - if ex.expected_outcome == 'fail' - if thrown.nil? - error = UnexpectedPassError.new - last = ex.steps.last - raise(last ? augment_stack(error, last, var_path) : error) - end - raise thrown if ex.expected_error_message && !thrown.message.include?(ex.expected_error_message) - - return # satisfied expected-failure → pass - end - - raise thrown if thrown - end - end - - # Sensor slot contract: zero slots + a return is a mistake; one slot IS - # the return; two+ is a positional array. Raises the appropriate diff error. - def compare_sensor_return(plan, _ex, step, returned, extra) - slot_count = step.args.length + extra.length - if slot_count.zero? - raise ReturnShapeError, 'this sensor has no parameters, data table or doc string — ' \ - 'nothing to compare a return value against (raise to fail, return nothing to pass)' - end - - if slot_count == 1 - slots = [returned] - else - unless returned.is_a?(Array) - raise ReturnShapeError, - "a sensor with #{slot_count} parameters must return a list of " \ - "#{slot_count} values, got #{returned.class}" - end - unless returned.length == slot_count - raise ReturnShapeError, - "sensor return must have #{slot_count} element(s), got #{returned.length}" - end - - slots = returned - end - - inline_returned = slots[0...step.args.length] - source_texts = step.param_spans.map do |s| - Offsets.utf16_slice(plan.var_doc.source, s.start_offset, s.end_offset) - end - param_diffs = ParamDiff.compare_params(inline_returned, step.args, step.param_spans, source_texts, - step.formats).reject(&:ok) - raise CellMismatchError, param_diffs unless param_diffs.empty? - - if step.data_table - bad = CellDiffs.compare_table(slots[step.args.length], step.data_table).reject(&:ok) - raise CellMismatchError, bad unless bad.empty? - elsif step.doc_string - diff = DocStringDiffs.compare_doc_string(slots[step.args.length], step.doc_string.content, - step.doc_string.span) - raise DocStringMismatchError, diff unless diff.nil? - end - end - - def observation(ex, example_index, ordinal, file, outcome, error = nil) - StepObservation.new(example_name: ex.name, example_index: example_index, ordinal: ordinal, - step_file: file, outcome: outcome, error: error) - end - - # In TS this injects a synthetic `at (path:line:col)` frame for - # editor navigation; the conformance trace derives the anchor separately - # via failure_anchor, so here it is a no-op that returns the error. - def augment_stack(error, _step, _var_path) - error - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/failure_anchor.rb b/ruby/packages/var-core/lib/oselvar/var/core/failure_anchor.rb deleted file mode 100644 index 870b299d..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/failure_anchor.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/cell_diff' -require 'oselvar/var/core/doc_string_diff' - -module Oselvar - module Var - module Core - # Where a failure points in the .md source: a mismatch anchors at its first - # failing span (cell / doc-string body), anything else at the fallback (the - # step's match span). The single source of truth for failure locations, - # pinned as failure.anchor in the conformance trace. Port of failure-anchor.ts. - module FailureAnchor - module_function - - def failure_anchor(error, fallback) - case error - when CellMismatchError - failing = error.cells.find { |c| !c.ok } - failing ? failing.span : fallback - when DocStringMismatchError - error.diff.span - else - fallback - end - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/hash.rb b/ruby/packages/var-core/lib/oselvar/var/core/hash.rb deleted file mode 100644 index 61c6fcaa..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/hash.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Oselvar - module Var - module Core - # FNV-1a (32-bit) change-detector over UTF-16 code units. Not a security - # hash: tiny and byte-identical to the TS/Python/JVM ports so var.lock.json - # fingerprints match everywhere. The "fnv1a:" prefix namespaces the algorithm. - # Port of hash.ts. - module Hash32 - FNV_OFFSET = 0x811c9dc5 - FNV_PRIME = 0x01000193 - MASK = 0xffffffff - - module_function - - def hash_source(source) - h = FNV_OFFSET - data = source.encode('UTF-16LE').bytes - (0...data.length).step(2) do |i| - unit = data[i] | (data[i + 1] << 8) - h = ((h ^ unit) * FNV_PRIME) & MASK - end - format('fnv1a:%08x', h) - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/matcher.rb b/ruby/packages/var-core/lib/oselvar/var/core/matcher.rb deleted file mode 100644 index dfc5cf62..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/matcher.rb +++ /dev/null @@ -1,134 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/span' - -module Oselvar - module Var - module Core - # UTF-16 start/end of one captured parameter within a sentence. - ParamSpan = Data.define(:start, :end) - # One successful expression match inside a sentence. Offsets are UTF-16. - Hit = Data.define(:expression, :step_def, :match_start, :match_end, :args, :param_spans, :formats) do - def initialize(expression:, step_def:, match_start:, match_end:, args:, param_spans:, formats: []) - super - end - end - # Two or more hits starting at the same position with equal length. - AmbiguityCollision = Data.define(:match_start, :match_end, :candidates) - # Tagged result of resolve_hits: kind "ok" (steps) or "ambiguous" (collisions). - ResolvedSteps = Data.define(:kind, :steps, :collisions) do - def initialize(kind:, steps: [], collisions: []) - super - end - end - - # Cucumber-expression matching. Port of matcher.ts. cucumber-expressions' - # regexps are anchored (^...$) and its group offsets are code-point based, - # so we strip anchors for substring search and convert offsets to UTF-16. - module Matcher - module_function - - # A compiled, un-anchored pattern from the step's CucumberExpression. - def unanchored_pattern(step) - regexp = step.compiled.instance_variable_get(:@tree_regexp).regexp - source = regexp.source - source = source[1..] if source.start_with?('^') - source = source[0...-1] if source.end_with?('$') - Regexp.new(source, regexp.options) - end - - # Every expression match found anywhere in +sentence+. - def find_hits(sentence, registry) - hits = [] - registry.steps.each do |step| - pattern = unanchored_pattern(step) - pos = 0 - while pos <= sentence.length - m = pattern.match(sentence, pos) - break if m.nil? - - matched_text = m[0] - arguments = step.compiled.match(matched_text) || [] - args = arguments.map { |arg| arg.value(nil) } - formats = arguments.map { |arg| registry.formats[arg.parameter_type.name] } - - # group.start/.end are code-point offsets within matched_text; add - # m.begin(0) for the sentence-absolute code-point index, then to UTF-16. - param_spans = arguments.filter_map do |arg| - g = arg.group - next unless g.start.is_a?(Integer) && g.end.is_a?(Integer) - - ParamSpan.new( - start: Offsets.to_utf16_offset(sentence, m.begin(0) + g.start), - end: Offsets.to_utf16_offset(sentence, m.begin(0) + g.end) - ) - end - - hits << Hit.new( - expression: step.expression, - step_def: step, - match_start: Offsets.to_utf16_offset(sentence, m.begin(0)), - match_end: Offsets.to_utf16_offset(sentence, m.end(0)), - args: args, - param_spans: param_spans, - formats: formats - ) - - pos = matched_text.empty? ? m.begin(0) + 1 : m.end(0) - end - end - hits - end - - # Select the best non-overlapping hits, or report ambiguities. - def resolve_hits(hits) - return ResolvedSteps.new(kind: 'ok') if hits.empty? - - # Stable sort by (match_start asc, length desc); the original index - # breaks ties so equal-key order follows registration order (Ruby's - # sort_by is not stable, Python's sorted is). - sorted = hits.each_with_index.sort_by do |h, i| - [h.match_start, -(h.match_end - h.match_start), i] - end.map(&:first) - - collisions = [] - i = 0 - while i < sorted.length - here = sorted[i] - here_len = here.match_end - here.match_start - tied = [here] - j = i + 1 - while j < sorted.length - candidate = sorted[j] - if candidate.match_start == here.match_start && - candidate.match_end - candidate.match_start == here_len - tied << candidate - j += 1 - else - break - end - end - if tied.length > 1 - collisions << AmbiguityCollision.new( - match_start: here.match_start, match_end: here.match_end, candidates: tied - ) - end - i = j - end - - return ResolvedSteps.new(kind: 'ambiguous', collisions: collisions) unless collisions.empty? - - steps = [] - cursor = -1 - sorted.each do |hit| - next if hit.match_start < cursor - - steps << hit - cursor = hit.match_end - end - ResolvedSteps.new(kind: 'ok', steps: steps) - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/param_diff.rb b/ruby/packages/var-core/lib/oselvar/var/core/param_diff.rb deleted file mode 100644 index 6c616a4f..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/param_diff.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/cell_diff' - -module Oselvar - module Var - module Core - # Compare a sensor's returned inline actuals against captured document - # values. Port of param-diff.ts. - module ParamDiff - module_function - - # Render one side of a parameter diff as [text, via_format]. The - # parameter type's format wins (document notation), else the shared - # string/primitive/inspect chain; a raising formatter falls through. - def render_param_value(value, format) - if format - begin - return [format.call(value), true] - rescue StandardError - # fall through to the native rendering - end - end - [CellDiffs.render_cell_value(value), false] - end - - # Compare returned actuals against expected document values. Arrays align - # 1:1; structural equality (==) compares by value across references. - def compare_params(returned, expected, param_spans, source_texts, formats = nil) - expected.each_index.map do |i| - ok = returned[i] == expected[i] - format = formats && i < formats.length ? formats[i] : nil - actual_text, via_format = render_param_value(returned[i], format) - expected_text = if i < source_texts.length - source_texts[i] - else - render_param_value(expected[i], format)[0] - end - CellDiff.new( - column: "arg #{i + 1}", - span: param_spans[i], - expected: expected_text, - actual: actual_text, - ok: ok, - expected_value: expected[i], - actual_value: returned[i], - formatted: via_format - ) - end - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/parse.rb b/ruby/packages/var-core/lib/oselvar/var/core/parse.rb deleted file mode 100644 index dcaa75ed..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/parse.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/scanner' -require 'oselvar/var/core/structurer' - -module Oselvar - module Var - module Core - # Parse +source+ into a VarDoc: scan blocks, then group into Examples. - # Port of parse.ts. - module Parse - module_function - - def parse(path, source, plugins = []) - Structurer.structure(path, source, Scanner.scan(source, plugins)) - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/plan.rb b/ruby/packages/var-core/lib/oselvar/var/core/plan.rb deleted file mode 100644 index 0d23fd42..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/plan.rb +++ /dev/null @@ -1,290 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/span' -require 'oselvar/var/core/ast' -require 'oselvar/var/core/cell_diff' -require 'oselvar/var/core/diagnostics' -require 'oselvar/var/core/matcher' -require 'oselvar/var/core/sentences' - -module Oselvar - module Var - module Core - DocString = Data.define(:content, :content_type, :span) - - PlannedStep = Data.define(:text, :match_span, :param_spans, :step_def, :args, :formats, :data_table, - :doc_string) do - def initialize(text:, match_span:, param_spans:, step_def:, args:, formats: [], data_table: nil, - doc_string: nil) - super - end - end - - HeaderBinding = Data.define(:match_span, :param_spans, :step_def) - - PlannedExample = Data.define(:name, :scope_stack, :span, :steps, :header_binding, :row_checks, - :expected_outcome, :expected_error_message) do - def initialize(name:, scope_stack:, span:, steps:, header_binding: nil, row_checks: nil, - expected_outcome: nil, expected_error_message: nil) - super - end - end - - ExecutionPlan = Data.define(:var_doc, :examples, :diagnostics) - - # Produce an ExecutionPlan from a VarDoc + Registry: match step expressions - # against every text block, attach trailing tables/fences, detect - # header-bound tables, and collect diagnostics. Port of plan.ts. - module Plan - BlockPlan = Data.define(:steps, :ambiguities) - Ambiguity = Data.define(:match_start, :match_end, :candidates) - - module_function - - def plan(var_doc, registry) - examples = [] - diagnostics = [] - - var_doc.examples.each do |ex| - had_ambiguous = false - steps_by_block = {} - - # Pass 1: plan each text-bearing block. - ex.body.each_with_index do |block, idx| - next unless %w[paragraph list_item blockquote].include?(block.kind) - - result = plan_block(block.text, registry) - - result.ambiguities.each do |collision| - span = lift_span(var_doc.source, block, collision.match_start, collision.match_end) - cp_start = Offsets.cp_index_for_utf16(block.text, collision.match_start) - cp_end = Offsets.cp_index_for_utf16(block.text, collision.match_end) - diagnostics << Diagnostics.ambiguous_match( - AmbiguousInput.new( - text: block.text[cp_start...cp_end], - span: span, - candidates: collision.candidates.map do |c| - Candidate.new( - expression: c.expression, - source_file: c.step_def.expression_source_file, - source_line: c.step_def.expression_source_line - ) - end - ) - ) - had_ambiguous = true - end - - next unless !had_ambiguous && !result.steps.empty? - - steps_by_block[idx] = result.steps.map do |hit| - PlannedStep.new( - text: Offsets.utf16_slice(block.text, hit.match_start, hit.match_end), - match_span: lift_span(var_doc.source, block, hit.match_start, hit.match_end), - param_spans: hit.param_spans.map { |p| lift_span(var_doc.source, block, p.start, p.end) }, - step_def: hit.step_def, - args: hit.args, - formats: hit.formats - ) - end - end - - # Header-bound table detection. - bound = had_ambiguous ? nil : detect_header_bound(ex, steps_by_block, var_doc.source) - if bound - table, binding_step, header_spans = bound - header_binding = HeaderBinding.new( - match_span: binding_step.match_span, - param_spans: header_spans, - step_def: binding_step.step_def - ) - table.rows.each do |row| - row_object = {} - table.header.cells.each_with_index do |cell_name, i| - row_object[cell_name] = i < row.cells.length ? row.cells[i] : '' - end - row_step = PlannedStep.new( - text: binding_step.text, - match_span: row.span, - param_spans: binding_step.param_spans, - step_def: binding_step.step_def, - args: binding_step.args + [row_object], - formats: binding_step.formats - ) - row_checks = table.header.cells.each_with_index.map do |cell_name, i| - RowCheck.new( - column: cell_name, - value: i < row.cells.length ? row.cells[i] : '', - span: i < row.cell_spans.length ? row.cell_spans[i] : row.span - ) - end - examples << PlannedExample.new( - name: row.cells.join(' / '), - scope_stack: ex.scope_stack + [binding_step.text], - span: row.span, - steps: [row_step], - header_binding: header_binding, - row_checks: row_checks - ) - end - next - end - - # Error fence detection. - error_fence = ex.body.find { |b| b.kind == 'fence' && b.info == 'error' } - - # Pass 2: attach trailing table / fence to the last step of a block. - attachments = {} - (1...ex.body.length).each do |idx| - here = ex.body[idx] - if here.kind == 'table' && steps_by_block.key?(idx - 1) - _prev_data, prev_doc = attachments[idx - 1] || [nil, nil] - attachments[idx - 1] = [here, prev_doc] - elsif here.kind == 'fence' && here.info != 'error' && steps_by_block.key?(idx - 1) - prev_data, = attachments[idx - 1] || [nil, nil] - attachments[idx - 1] = [ - prev_data, - DocString.new(content: here.body, content_type: here.info, span: here.body_span) - ] - end - end - - # Pass 3: rebuild the final step list, applying attachments. - final_steps = [] - (0...ex.body.length).each do |idx| - block_steps = steps_by_block[idx] || [] - attach = attachments[idx] - block_steps.each_with_index do |step, s_idx| - if s_idx == block_steps.length - 1 && attach - data_table, doc_string = attach - final_steps << PlannedStep.new( - text: step.text, match_span: step.match_span, param_spans: step.param_spans, - step_def: step.step_def, args: step.args, formats: step.formats, - data_table: data_table, doc_string: doc_string - ) - else - final_steps << step - end - end - end - - runnable_steps = had_ambiguous ? [] : final_steps - - if error_fence && runnable_steps.empty? - diagnostics << Diagnostics.error_fence_without_step(error_fence.span) - end - - next if final_steps.empty? && !had_ambiguous - - expected_outcome = nil - expected_error_message = nil - if error_fence - expected_outcome = 'fail' - msg = error_fence.body.strip - expected_error_message = msg unless msg.empty? - end - - examples << PlannedExample.new( - name: derive_example_name(ex.body), - scope_stack: ex.scope_stack, - span: ex.span, - steps: runnable_steps, - expected_outcome: expected_outcome, - expected_error_message: expected_error_message - ) - end - - ExecutionPlan.new(var_doc: var_doc, examples: examples, diagnostics: diagnostics) - end - - def plan_block(text, registry) - all_steps = [] - all_ambiguities = [] - - Sentences.split_sentences(text).each do |sentence| - hits = Matcher.find_hits(sentence.text, registry) - adjusted = hits.map do |h| - Hit.new( - expression: h.expression, - step_def: h.step_def, - match_start: h.match_start + sentence.start_offset, - match_end: h.match_end + sentence.start_offset, - args: h.args, - param_spans: h.param_spans.map do |p| - ParamSpan.new(start: p.start + sentence.start_offset, end: p.end + sentence.start_offset) - end, - formats: h.formats - ) - end - resolved = Matcher.resolve_hits(adjusted) - if resolved.kind == 'ambiguous' - resolved.collisions.each do |c| - all_ambiguities << Ambiguity.new(match_start: c.match_start, match_end: c.match_end, - candidates: c.candidates) - end - elsif !resolved.steps.empty? - all_steps.concat(resolved.steps) - end - end - - BlockPlan.new(steps: all_steps, ambiguities: all_ambiguities) - end - - # Whole-word, case-sensitive start index of +word+ in +haystack+, or nil. - def word_offset(haystack, word) - m = /(?(*groups) { groups[0] } - # `type` is return-type metadata only (used by snippet generation, never - # by matching, transformation, or any conformance artifact). The Ruby - # gem rejects a nil type (Python's accepts None), so pass Object. - pt = Cucumber::CucumberExpressions::ParameterType.new( - name, regexps, Object, transformer, use_for_snippets, prefer_for_regexp_match - ) - registry.parameter_types.define_parameter_type(pt) - return registry if format.nil? - - registry.with(formats: registry.formats.merge(name => format)) - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/scanner.rb b/ruby/packages/var-core/lib/oselvar/var/core/scanner.rb deleted file mode 100644 index 1d03ce68..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/scanner.rb +++ /dev/null @@ -1,348 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/span' -require 'oselvar/var/core/ast' -require 'oselvar/var/core/table_cells' - -module Oselvar - module Var - module Core - # Markdown block scanner. Port of scanner.ts. All offsets count UTF-16 - # code units; split_lines advances by utf16_len(line) + 1 per newline, and - # code-point indices from String#index are converted to UTF-16 before use. - module Scanner - RawLine = Data.define(:text, :start_offset, :end_offset) - - # Regexes — verbatim ports of the TS constants (`#` escaped as `\#` so - # Ruby does not read `#{...}` as interpolation). - THEMATIC_RE = /^\s*([-*_])(\s*\1){2,}\s*$/ - UL_RE = /^(\s*)([-*+])\s+(.*)$/ - OL_RE = /^(\s*)(\d+)([.)])\s+(.*)$/ - BQ_RE = /^>\s?(.*)$/ - FENCE_RE = /^(`{3,})\s*(\S*)\s*$/ - ROW_RE = /^\|(.+)\|\s*$/ - DELIM_RE = /^\|\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|\s*$/ - HEADING_RE = /^(\#{1,6})\s+(.*?)(?:\s+\#+)?\s*$/ - PARA_HEADING_RE = /^\#{1,6}\s+/ - - module_function - - # Scan +source+ into an immutable Array of Block nodes. - def scan(source, plugins = []) - blocks = [] - lines = split_lines(source) - - i = 0 - while i < lines.length - line = lines[i] - if line.text.strip.empty? - i += 1 - next - end - - matched = run_plugins(source, lines, i, plugins) - if matched - blocks << matched[0] - i = matched[1] - next - end - - fence_result = try_fence(source, lines, i) - if fence_result - blocks << fence_result[0] - i = fence_result[1] - next - end - - table_result = try_table(source, lines, i) - if table_result - blocks << table_result[0] - i = table_result[1] - next - end - - thematic = try_thematic(source, line) - if thematic - blocks << thematic - i += 1 - next - end - - bq_result = try_blockquote(source, lines, i) - if bq_result - blocks << bq_result[0] - i = bq_result[1] - next - end - - heading = try_heading(source, line) - if heading - blocks << heading - i += 1 - next - end - - list_item = try_list_item(source, line) - if list_item - blocks << list_item - i += 1 - next - end - - paragraph, next_i = consume_paragraph(source, lines, i, plugins) - blocks << paragraph - i = next_i - end - - blocks - end - - def run_plugins(source, lines, start_idx, plugins) - plugins.each do |p| - r = p.try_scan(source: source, lines: lines, start_idx: start_idx) - return r if r - end - nil - end - - # Split +source+ into RawLines with UTF-16 start/end offsets. - def split_lines(source) - out = [] - start_u16 = 0 - current_u16 = 0 - start_cp = 0 - - source.each_char.with_index do |ch, cp_i| - if ch == "\n" - out << RawLine.new(text: source[start_cp...cp_i], start_offset: start_u16, end_offset: current_u16) - start_u16 = current_u16 + 1 # '\n' is BMP → 1 UTF-16 unit - start_cp = cp_i + 1 - end - current_u16 += ch.ord > 0xFFFF ? 2 : 1 - end - - out << RawLine.new(text: source[start_cp..] || '', start_offset: start_u16, end_offset: current_u16) - out - end - - def try_thematic(source, line) - return nil unless THEMATIC_RE.match?(line.text) - - ThematicBreak.new(span: Offsets.span_from_offsets(source, line.start_offset, line.end_offset)) - end - - def try_heading(source, line) - m = HEADING_RE.match(line.text) - return nil unless m - - Heading.new( - level: m[1].length, - text: (m[2] || '').strip, - span: Offsets.span_from_offsets(source, line.start_offset, line.end_offset) - ) - end - - def try_list_item(source, line) - if (ul = UL_RE.match(line.text)) - text = ul[3] || '' - marker_start = line.start_offset + Offsets.utf16_len(ul[1] || '') - marker_end = marker_start + Offsets.utf16_len(ul[2] || '') - cp_idx = line.text.index(text) - text_start = line.start_offset + Offsets.to_utf16_offset(line.text, cp_idx) - return ListItem.new( - text: text, - span: Offsets.span_from_offsets(source, line.start_offset, line.end_offset), - segment_map: [SegmentOffset.new(text_offset: 0, source_offset: text_start)], - ordered: false, - marker_span: Offsets.span_from_offsets(source, marker_start, marker_end) - ) - end - - if (ol = OL_RE.match(line.text)) - text = ol[4] || '' - marker_start = line.start_offset + Offsets.utf16_len(ol[1] || '') - marker_end = marker_start + Offsets.utf16_len(ol[2] || '') + Offsets.utf16_len(ol[3] || '') - cp_idx = line.text.index(text) - text_start = line.start_offset + Offsets.to_utf16_offset(line.text, cp_idx) - return ListItem.new( - text: text, - span: Offsets.span_from_offsets(source, line.start_offset, line.end_offset), - segment_map: [SegmentOffset.new(text_offset: 0, source_offset: text_start)], - ordered: true, - marker_span: Offsets.span_from_offsets(source, marker_start, marker_end) - ) - end - - nil - end - - def try_blockquote(source, lines, start_idx) - return nil if start_idx >= lines.length - - first = lines[start_idx] - m = BQ_RE.match(first.text) - return nil unless m - - first_segment = m[1] || '' - cp_idx = first.text.index(first_segment) - segments = [first_segment] - segment_map = [ - SegmentOffset.new( - text_offset: 0, - source_offset: first.start_offset + Offsets.to_utf16_offset(first.text, cp_idx) - ) - ] - joined_text_offset = Offsets.utf16_len(first_segment) - - i = start_idx + 1 - end_offset = first.end_offset - while i < lines.length - ln = lines[i] - next_m = BQ_RE.match(ln.text) - break unless next_m - - segment = next_m[1] || '' - cp_idx2 = ln.text.index(segment) - joined_text_offset += 1 # newline separator - segment_map << SegmentOffset.new( - text_offset: joined_text_offset, - source_offset: ln.start_offset + Offsets.to_utf16_offset(ln.text, cp_idx2) - ) - segments << segment - joined_text_offset += Offsets.utf16_len(segment) - end_offset = ln.end_offset - i += 1 - end - - [ - Blockquote.new( - text: segments.join("\n"), - span: Offsets.span_from_offsets(source, first.start_offset, end_offset), - segment_map: segment_map - ), - i - ] - end - - def consume_paragraph(source, lines, start_idx, plugins) - raise 'invariant: start_idx out of range' if start_idx >= lines.length - - first = lines[start_idx] - end_idx = start_idx - while end_idx + 1 < lines.length - candidate_idx = end_idx + 1 - candidate = lines[candidate_idx] - break if candidate.text.strip.empty? - break if PARA_HEADING_RE.match?(candidate.text) - break if UL_RE.match?(candidate.text) - break if OL_RE.match?(candidate.text) - break if BQ_RE.match?(candidate.text) - break if FENCE_RE.match?(candidate.text) - break if ROW_RE.match?(candidate.text) - break if THEMATIC_RE.match?(candidate.text) - break if run_plugins(source, lines, candidate_idx, plugins) - - end_idx += 1 - end - - last = lines[end_idx] - start_offset = first.start_offset - end_offset = last.end_offset - [ - Paragraph.new( - text: Offsets.utf16_slice(source, start_offset, end_offset), - span: Offsets.span_from_offsets(source, start_offset, end_offset), - segment_map: [SegmentOffset.new(text_offset: 0, source_offset: start_offset)] - ), - end_idx + 1 - ] - end - - def try_fence(source, lines, start_idx) - return nil if start_idx >= lines.length - - start = lines[start_idx] - open_m = FENCE_RE.match(start.text) - return nil unless open_m - - fence_marker = open_m[1] || '' - info = (open_m[2] || '').strip - - i = start_idx + 1 - body_start = nil - body_end = nil - end_offset = start.end_offset - - while i < lines.length - ln = lines[i] - close_m = FENCE_RE.match(ln.text) - if close_m && (close_m[1] || '').length >= fence_marker.length - end_offset = ln.end_offset - break - end - body_start = ln.start_offset if body_start.nil? - body_end = ln.end_offset + 1 # +1 to include the '\n' after this line - i += 1 - end - - body = body_start.nil? || body_end.nil? ? '' : Offsets.utf16_slice(source, body_start, body_end) - - fallback = start.end_offset - body_span = Offsets.span_from_offsets(source, body_start || fallback, body_end || fallback) - [ - Fence.new( - info: info, - body: body, - body_span: body_span, - span: Offsets.span_from_offsets(source, start.start_offset, end_offset) - ), - i + 1 - ] - end - - def try_table(source, lines, start_idx) - return nil if start_idx + 1 >= lines.length - - header_line = lines[start_idx] - delim_line = lines[start_idx + 1] - return nil unless ROW_RE.match?(header_line.text) - return nil unless DELIM_RE.match?(delim_line.text) - - header_cells, header_cell_spans = TableCells.parse_row_cells(header_line.text, header_line.start_offset, - source) - header = Row.new( - cells: header_cells, - cell_spans: header_cell_spans, - span: Offsets.span_from_offsets(source, header_line.start_offset, header_line.end_offset) - ) - - rows = [] - i = start_idx + 2 - while i < lines.length - ln = lines[i] - break unless ROW_RE.match?(ln.text) - - cells, cell_spans = TableCells.parse_row_cells(ln.text, ln.start_offset, source) - rows << Row.new( - cells: cells, - cell_spans: cell_spans, - span: Offsets.span_from_offsets(source, ln.start_offset, ln.end_offset) - ) - i += 1 - end - - last_row = rows.last - end_offset = last_row ? last_row.span.end_offset : delim_line.end_offset - [ - Table.new( - span: Offsets.span_from_offsets(source, header_line.start_offset, end_offset), - header: header, - rows: rows - ), - i - ] - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/sentences.rb b/ruby/packages/var-core/lib/oselvar/var/core/sentences.rb deleted file mode 100644 index 27d386f4..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/sentences.rb +++ /dev/null @@ -1,116 +0,0 @@ -# frozen_string_literal: true - -module Oselvar - module Var - module Core - # Split a block of plain text into sentences on . ! ? and \n, skipping - # terminators inside backtick spans and double-quoted strings and treating - # common abbreviations as non-breaking. All offsets are UTF-16 code units - # into the block text. Port of sentences.ts. - Sentence = Data.define(:text, :start_offset, :end_offset) - - module Sentences - ABBREVIATIONS = Set.new(['e.g.', 'i.e.', 'etc.', 'cf.', 'vs.']).freeze - - module_function - - def split_sentences(text) - cp_to_u16 = build_cp_to_u16(text) - n = text.length - out = [] - - # Mark no-split zones (backtick spans, double-quoted strings). - skip = Array.new(n, false) - j = 0 - while j < n - c = text[j] - if ['`', '"'].include?(c) - close = text.index(c, j + 1) - break if close.nil? - - (j..close).each { |k| skip[k] = true } - j = close + 1 - next - end - j += 1 - end - - i = 0 - segment_start = 0 - while i < n - if skip[i] - i += 1 - next - end - ch = text[i] - if ["\n", '.', '!', '?'].include?(ch) - if ch == '.' && inside_number_or_abbrev?(text, i) - i += 1 - next - end - stop = i + 1 - push_segment(out, text, segment_start, stop, cp_to_u16) - i = stop - i += 1 while i < n && [' ', "\n"].include?(text[i]) - segment_start = i - next - end - i += 1 - end - - push_segment(out, text, segment_start, n, cp_to_u16) - out - end - - # cp_to_u16[cp_i] is the UTF-16 offset of text[cp_i]. - def build_cp_to_u16(text) - result = Array.new(text.length + 1, 0) - u16 = 0 - text.each_char.with_index do |ch, i| - result[i] = u16 - u16 += ch.ord > 0xFFFF ? 2 : 1 - end - result[text.length] = u16 - result - end - - def inside_number_or_abbrev?(text, dot_pos) - prev = dot_pos.positive? ? text[dot_pos - 1] : '' - nxt = dot_pos + 1 < text.length ? text[dot_pos + 1] : '' - return true if digit?(prev) && digit?(nxt) - - ABBREVIATIONS.each do |abbrev| - start = [0, dot_pos + 1 - abbrev.length].max - return true if text[start...(dot_pos + 1)] == abbrev - end - lower?(nxt) - end - - def push_segment(out, text, start_cp, end_cp, cp_to_u16) - return if end_cp <= start_cp - - raw = text[start_cp...end_cp] - stripped = raw.strip - return if stripped.empty? - - lead = raw.length - raw.lstrip.length - trail = raw.length - raw.rstrip.length - out << Sentence.new( - text: stripped, - start_offset: cp_to_u16[start_cp + lead], - end_offset: cp_to_u16[end_cp - trail] - ) - end - - def digit?(ch) - !ch.empty? && ch.match?(/[0-9]/) - end - - # Unicode-aware "is a lowercase letter": has case and is already lower. - def lower?(ch) - !ch.empty? && ch != ch.upcase && ch == ch.downcase - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/span.rb b/ruby/packages/var-core/lib/oselvar/var/core/span.rb deleted file mode 100644 index 13589a86..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/span.rb +++ /dev/null @@ -1,86 +0,0 @@ -# frozen_string_literal: true - -module Oselvar - module Var - module Core - # A source span. Offsets and columns are **UTF-16 code units** (an astral - # character like 😀 counts as 2), matching the goldens and LSP's default - # position encoding. Lines/cols are 1-based. - Span = Data.define( - :start_offset, :end_offset, - :start_line, :start_col, - :end_line, :end_col - ) - - # UTF-16 offset conversion. Ruby strings are code-point indexed, so the - # whole core converts to/from UTF-16 code units here (the single riskiest - # part of the port — mirrors Python's span.py). Reused by the matcher and - # by hash.rb. - module Offsets - module_function - - # UTF-16 code-unit length of a string (astral chars count as 2). - def utf16_len(str) - n = 0 - str.each_char { |ch| n += ch.ord > 0xFFFF ? 2 : 1 } - n - end - - # UTF-16 offset of the code-point index `cp_index` in `source`. - def to_utf16_offset(source, cp_index) - utf16_len(source[0...cp_index]) - end - - # Inverse of to_utf16_offset: the code-point index at a UTF-16 offset. - def cp_index_for_utf16(source, u16) - count = 0 - source.each_char.with_index do |ch, i| - return i if count >= u16 - - count += ch.ord > 0xFFFF ? 2 : 1 - end - source.length - end - - # Slice `source` by UTF-16 offsets, returning the covered substring. - def utf16_slice(source, start_u16, end_u16) - a = cp_index_for_utf16(source, start_u16) - b = cp_index_for_utf16(source, end_u16) - source[a...b] - end - - # 1-based [line, col] at a UTF-16 offset; col counts UTF-16 units and - # resets to 1 after each newline (mirrors span.ts's lineCol). - def line_col(source, offset_u16) - line = 1 - col = 1 - count = 0 - source.each_char do |ch| - break if count >= offset_u16 - - width = ch.ord > 0xFFFF ? 2 : 1 - if ch == "\n" - line += 1 - col = 1 - else - col += width - end - count += width - end - [line, col] - end - - # Build a Span from UTF-16 start/end offsets. - def span_from_offsets(source, start_u16, end_u16) - start_line, start_col = line_col(source, start_u16) - end_line, end_col = line_col(source, end_u16) - Span.new( - start_offset: start_u16, end_offset: end_u16, - start_line: start_line, start_col: start_col, - end_line: end_line, end_col: end_col - ) - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/step_role.rb b/ruby/packages/var-core/lib/oselvar/var/core/step_role.rb deleted file mode 100644 index 1370675b..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/step_role.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module Oselvar - module Var - module Core - # The role a step definition plays: - # "stimulus" — drives the software (arranges and acts on state) - # "sensor" — the read-only assertion (the only role that returns for - # comparison) - # Purely structural — never inspects sentence words (no Given/When/Then - # heuristics). Port of step-role.ts. - module StepRole - module_function - - # Guess a step's role from its document-order neighbours. A step with - # nothing after it is most likely the observation; anything followed by - # other steps is most likely driving the software. - def infer_step_role(neighbours) - after = neighbours[:after] || neighbours['after'] || [] - after.empty? ? 'sensor' : 'stimulus' - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/structurer.rb b/ruby/packages/var-core/lib/oselvar/var/core/structurer.rb deleted file mode 100644 index 833a5442..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/structurer.rb +++ /dev/null @@ -1,86 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/span' -require 'oselvar/var/core/ast' - -module Oselvar - module Var - module Core - # Group scanned blocks into Examples, tracking heading scope and orphan - # attachments. Port of structurer.ts. - module Structurer - module_function - - def structure(path, source, blocks) - examples = [] - orphan_attachments = [] - scope_stack = [] # [[level, text], ...] - last_example_idx = -1 - attachment_open = false - - blocks.each do |block| - case block.kind - when 'heading' - # Pop deeper-or-equal-level entries before pushing the new heading. - scope_stack.pop while !scope_stack.empty? && scope_stack.last[0] >= block.level - scope_stack << [block.level, block.text] - attachment_open = false - - when 'paragraph', 'list_item', 'blockquote' - # Merge a block into the previous example when that example's last - # block is an attachment (table/fence) with no blank line between. - if attachment_open && last_example_idx >= 0 - prev = examples[last_example_idx] - prev_last = prev.body.last - last_is_attachment = !prev_last.nil? && %w[table fence].include?(prev_last.kind) - if last_is_attachment - between = Offsets.utf16_slice(source, prev.span.end_offset, block.span.start_offset) - unless between.match?(/\n\s*\n/) - new_span = Offsets.span_from_offsets(source, prev.span.start_offset, block.span.end_offset) - examples[last_example_idx] = Example.new( - scope_stack: prev.scope_stack, - span: new_span, - body: prev.body + [block] - ) - next - end - end - end - - examples << Example.new( - scope_stack: scope_stack.map { |(_, text)| text }, - span: block.span, - body: [block] - ) - last_example_idx = examples.length - 1 - attachment_open = true - - when 'table', 'fence' - if attachment_open && last_example_idx >= 0 - prev = examples[last_example_idx] - new_span = Offsets.span_from_offsets(source, prev.span.start_offset, block.span.end_offset) - examples[last_example_idx] = Example.new( - scope_stack: prev.scope_stack, - span: new_span, - body: prev.body + [block] - ) - else - orphan_attachments << block - end - - when 'thematic_break' - attachment_open = false - end - end - - VarDoc.new( - path: path, - source: source, - examples: examples, - orphan_attachments: orphan_attachments - ) - end - end - end - end -end diff --git a/ruby/packages/var-core/lib/oselvar/var/core/table_cells.rb b/ruby/packages/var-core/lib/oselvar/var/core/table_cells.rb deleted file mode 100644 index fdb2013c..00000000 --- a/ruby/packages/var-core/lib/oselvar/var/core/table_cells.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core/span' - -module Oselvar - module Var - module Core - # Parse a Markdown/Gherkin table row into trimmed cells and per-cell source - # spans. Port of table-cells.ts. All offsets count UTF-16 code units. - module TableCells - module_function - - # Split a `| a | b |` row into [cells, cell_spans]. +line_start_offset+ - # is the UTF-16 offset of the row's first character within +source+. - def parse_row_cells(line_text, line_start_offset, source) - first_cp = line_text.index('|') - last_cp = line_text.rindex('|') - return [[], []] if first_cp.nil? || last_cp.nil? || last_cp <= first_cp - - # Pipe positions: convert code-point index to UTF-16 (matters when - # astral chars precede the pipe). '|' is ASCII → 1 UTF-16 unit. - first_u16 = Offsets.to_utf16_offset(line_text, first_cp) - inner_start_u16 = first_u16 + 1 - - inner = line_text[(first_cp + 1)...last_cp] - - cells = [] - cell_spans = [] - cursor = 0 # running UTF-16 position within inner - - # split(-1) keeps trailing empty segments, matching JS/Python split. - inner.split('|', -1).each do |seg| - trimmed = seg.strip - leading = Offsets.utf16_len(seg) - Offsets.utf16_len(seg.lstrip) - abs_start = line_start_offset + inner_start_u16 + cursor + leading - cells << trimmed - cell_spans << Offsets.span_from_offsets(source, abs_start, abs_start + Offsets.utf16_len(trimmed)) - cursor += Offsets.utf16_len(seg) + 1 # +1 for the '|' delimiter - end - - [cells, cell_spans] - end - end - end - end -end diff --git a/ruby/packages/var-core/spec/conformance/var_doc_conformance_spec.rb b/ruby/packages/var-core/spec/conformance/var_doc_conformance_spec.rb deleted file mode 100644 index 3b735741..00000000 --- a/ruby/packages/var-core/spec/conformance/var_doc_conformance_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'oselvar/var/core' - -module Oselvar - module Var - module Core - # Reproduces the shared conformance corpus' var-doc.json goldens - # byte-for-byte (parse stage). Mirrors var/tests/conformance.test.ts. - ::RSpec.describe 'var-doc conformance' do - def self.corpus_dir - dir = __dir__ - dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' - File.join(dir, 'conformance', 'bundles') - end - - corpus = corpus_dir - - Dir.children(corpus).sort.each do |bundle| - golden = File.join(corpus, bundle, 'golden', 'var-doc.json') - next unless File.exist?(golden) - - it "#{bundle} — var-doc.json matches golden" do - source = File.read(File.join(corpus, bundle, 'example.md'), encoding: 'UTF-8') - doc = Parse.parse('example.md', source) - actual = CanonicalJson.canonical_stringify(Conformance.to_var_doc_artifact(doc)) - expect(actual).to eq(File.read(golden, encoding: 'UTF-8')) - end - end - end - end - end -end diff --git a/ruby/packages/var-core/spec/oselvar/var/core/canonical_json_spec.rb b/ruby/packages/var-core/spec/oselvar/var/core/canonical_json_spec.rb deleted file mode 100644 index dff0f5df..00000000 --- a/ruby/packages/var-core/spec/oselvar/var/core/canonical_json_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'oselvar/var/core' - -module Oselvar - module Var - module Core - ::RSpec.describe CanonicalJson do - subject(:stringify) { described_class.method(:canonical_stringify) } - - it 'sorts object keys recursively' do - expect(stringify.call({ 'b' => 1, 'a' => { 'd' => 2, 'c' => 3 } })) - .to eq(%({\n "a": {\n "c": 3,\n "d": 2\n },\n "b": 1\n}\n)) - end - - it "renders empty containers as {} and [] (not Ruby's [\\n\\n])" do - expect(stringify.call({ 'items' => [], 'meta' => {} })) - .to eq(%({\n "items": [],\n "meta": {}\n}\n)) - end - - it 'indents arrays with two spaces per level' do - expect(stringify.call([1, 2])).to eq("[\n 1,\n 2\n]\n") - end - - it 'keeps non-ASCII raw and escapes control characters like JS' do - expect(stringify.call({ 's' => "café 😀\n\t\"x\"" })) - .to eq(%({\n "s": "café 😀\\n\\t\\"x\\""\n}\n)) - end - - it 'appends a single trailing newline' do - expect(stringify.call(true)).to eq("true\n") - end - end - end - end -end diff --git a/ruby/packages/var-core/spec/oselvar/var/core/drift_spec.rb b/ruby/packages/var-core/spec/oselvar/var/core/drift_spec.rb deleted file mode 100644 index 61cec3b4..00000000 --- a/ruby/packages/var-core/spec/oselvar/var/core/drift_spec.rb +++ /dev/null @@ -1,236 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'oselvar/var/core' - -module Oselvar - module Var - module Core - # A tiny in-memory BaselineStore for the reconcile tests. - class MemoryStore - attr_accessor :contents - - def initialize(initial = nil) - @contents = initial - end - - def read = @contents - def write(contents) = (@contents = contents) - end - - # Translated from drift.test.ts / test_drift.py. Drift has no conformance - # golden (bundles carry no baseline), so it is proven by these unit tests. - ::RSpec.describe Drifts do - def noop = ->(*_args) {} - - def reg(with_step: true) - r = Registries.create_registry - if with_step - r = Registries.add_step(r, expression: 'I withdraw {int}', expression_source_file: 'steps.rb', - expression_source_line: 1, handler: noop, kind: 'stimulus') - end - r - end - - def roman_reg(with_step: true) - r = Registries.create_registry - if with_step - r = Registries.add_step(r, expression: 'a decimal and a roman number', expression_source_file: 'steps.rb', - expression_source_line: 1, handler: noop, kind: 'sensor') - end - r - end - - def plan_for(source, registry) - var_doc = Parse.parse('w.md', source) - [var_doc, Plan.plan(var_doc, registry)] - end - - def bare(drifts) = drifts.map { |d| [d.name, d.line] } - - it 'records one entry per example-producing paragraph' do - var_doc, plan = plan_for('I withdraw 40.', reg) - expect(described_class.live_examples(var_doc, - plan)).to eq([BaselineExample.new(name: 'I withdraw 40', line: 1)]) - end - - it 'does not record a never-matched paragraph' do - var_doc, plan = plan_for('Just some prose.', reg) - expect(described_class.live_examples(var_doc, plan)).to eq([]) - end - - it 'derive_spec_baseline carries the source fingerprint' do - source = 'I withdraw 40.' - var_doc, plan = plan_for(source, reg) - baseline = described_class.derive_spec_baseline(source, var_doc, plan) - expect(baseline.source_hash).to eq(Hash32.hash_source(source)) - expect(baseline.examples).to eq([BaselineExample.new(name: 'I withdraw 40', line: 1)]) - end - - it 'no baseline means no drift' do - var_doc, plan = plan_for('I withdraw 40.', reg) - expect(described_class.detect_drift(nil, var_doc, plan)).to eq([]) - end - - it 'an unchanged spec and steps have no drift' do - source = 'I withdraw 40.' - var_doc, plan = plan_for(source, reg) - baseline = described_class.derive_spec_baseline(source, var_doc, plan) - expect(described_class.detect_drift(baseline, var_doc, plan)).to eq([]) - end - - it 'a renamed step drifts (matched by name)' do - source = 'I withdraw 40.' - var_doc, plan_with = plan_for(source, reg) - baseline = described_class.derive_spec_baseline(source, var_doc, plan_with) - _doc, plan_without = plan_for(source, reg(with_step: false)) - expect(bare(described_class.detect_drift(baseline, var_doc, plan_without))).to eq([['I withdraw 40', 1]]) - end - - it 'an in-place typo drifts (matched by line)' do - before_doc, before_plan = plan_for('I withdraw 40.', reg) - baseline = described_class.derive_spec_baseline('I withdraw 40.', before_doc, before_plan) - after_doc, after_plan = plan_for('I withdrraw 40.', reg) - expect(bare(described_class.detect_drift(baseline, after_doc, after_plan))).to eq([['I withdraw 40', 1]]) - end - - it 'a deleted paragraph is not drift' do - before_doc, before_plan = plan_for('I withdraw 40.', reg) - baseline = described_class.derive_spec_baseline('I withdraw 40.', before_doc, before_plan) - after_doc, after_plan = plan_for('', reg) - expect(described_class.detect_drift(baseline, after_doc, after_plan)).to eq([]) - end - - it 'moving and rewording a still-matching example does not drift' do - before = "I withdraw 40.\n\nI withdraw 10." - before_doc, before_plan = plan_for(before, reg) - baseline = described_class.derive_spec_baseline(before, before_doc, before_plan) - after_doc, after_plan = plan_for("I withdraw 11.\n\nI withdraw 40.", reg) - expect(described_class.detect_drift(baseline, after_doc, after_plan)).to eq([]) - end - - it 'move + reword + prose on the old line does not false-positive' do - before_doc, before_plan = plan_for('I withdraw 40.', reg) - baseline = described_class.derive_spec_baseline('I withdraw 40.', before_doc, before_plan) - after_doc, after_plan = plan_for("Just some notes.\n\nI withdraw 41.", reg) - expect(described_class.detect_drift(baseline, after_doc, after_plan)).to eq([]) - end - - it 'a paragraph rewritten past recognition is remove+add, not drift' do - before_doc, before_plan = plan_for('I withdraw 40.', reg) - baseline = described_class.derive_spec_baseline('I withdraw 40.', before_doc, before_plan) - after_doc, after_plan = plan_for('The branch closed years ago.', reg) - expect(described_class.detect_drift(baseline, after_doc, after_plan)).to eq([]) - end - - roman = "Each row gives a decimal and a roman number:\n\n" \ - "| decimal | roman |\n| ------: | :---- |\n| 3 | III |\n| 9 | IX |\n" - - it 'header-bound table records its binding paragraph once' do - var_doc, plan = plan_for(roman, roman_reg) - expect(described_class.live_examples(var_doc, plan)) - .to eq([BaselineExample.new(name: 'Each row gives a decimal and a roman number:', line: 1)]) - end - - it 'a header-bound binding paragraph that stops matching drifts' do - var_doc, plan_with = plan_for(roman, roman_reg) - baseline = described_class.derive_spec_baseline(roman, var_doc, plan_with) - _doc, plan_without = plan_for(roman, roman_reg(with_step: false)) - expect(bare(described_class.detect_drift(baseline, var_doc, plan_without))) - .to eq([['Each row gives a decimal and a roman number:', 1]]) - end - - it 'drift diagnostics are error severity' do - source = 'I withdraw 40.' - var_doc, plan_with = plan_for(source, reg) - baseline = described_class.derive_spec_baseline(source, var_doc, plan_with) - _doc, plan_without = plan_for(source, reg(with_step: false)) - diags = described_class.drift_diagnostics(described_class.detect_drift(baseline, var_doc, plan_without)) - expect(diags.length).to eq(1) - expect(diags[0].severity).to eq('error') - expect(diags[0].code).to eq('drift') - expect(diags[0].message).to include('I withdraw 40') - end - - it 'reconcile records on first run, then reports and preserves on drift' do - source = 'I withdraw 40.' - var_doc, plan_with = plan_for(source, reg) - store = MemoryStore.new - expect(described_class.reconcile_drift(store, 'w.md', source, var_doc, plan_with)).to eq([]) - before = store.contents - _doc, plan_without = plan_for(source, reg(with_step: false)) - drift = described_class.reconcile_drift(store, 'w.md', source, var_doc, plan_without) - expect(bare(drift)).to eq([['I withdraw 40', 1]]) - expect(store.contents).to eq(before) - end - - it 'reconcile update mode accepts drift' do - source = 'I withdraw 40.' - var_doc, plan_with = plan_for(source, reg) - store = MemoryStore.new - described_class.reconcile_drift(store, 'w.md', source, var_doc, plan_with) - _doc, plan_without = plan_for(source, reg(with_step: false)) - drift = described_class.reconcile_drift(store, 'w.md', source, var_doc, plan_without, update: true) - expect(drift).to eq([]) - lock = described_class.parse_var_lock(store.contents) - expect(lock.specs['w.md'].examples).to eq([]) - end - - expected_lock = <<~JSON - { - "version": 1, - "specs": { - "library.md": { - "sourceHash": "fnv1a:1a2b3c4d", - "examples": [ - { - "name": "I check out", - "line": 7 - } - ] - } - } - } - JSON - - it 'stringify matches the TypeScript serializer byte-for-byte' do - lock = VarLock.new( - version: 1, - specs: { 'library.md' => SpecBaseline.new(source_hash: 'fnv1a:1a2b3c4d', - examples: [BaselineExample.new(name: 'I check out', line: 7)]) } - ) - expect(described_class.stringify_var_lock(lock)).to eq(expected_lock) - end - - it 'parse round-trips a valid lock' do - lock = VarLock.new( - version: 1, - specs: { 'library.md' => SpecBaseline.new(source_hash: 'fnv1a:1a2b3c4d', - examples: [BaselineExample.new(name: 'I check out', line: 7)]) } - ) - expect(described_class.parse_var_lock(described_class.stringify_var_lock(lock))).to eq(lock) - end - - it 'stringify sorts spec paths' do - lock = VarLock.new( - version: 1, - specs: { - 'zebra.md' => SpecBaseline.new(source_hash: 'fnv1a:00000001', examples: []), - 'alpha.md' => SpecBaseline.new(source_hash: 'fnv1a:00000002', examples: []) - } - ) - text = described_class.stringify_var_lock(lock) - expect(text.index('alpha.md')).to be < text.index('zebra.md') - expect(text).to end_with("}\n") - end - - it 'parse rejects malformed input' do - expect(described_class.parse_var_lock('not json')).to be_nil - expect(described_class.parse_var_lock('{}')).to be_nil - expect(described_class.parse_var_lock('{"version":2,"specs":{}}')).to be_nil - expect(described_class.parse_var_lock('{"version":1,"specs":{"a.md":{"examples":[]}}}')).to be_nil - end - end - end - end -end diff --git a/ruby/packages/var-core/spec/oselvar/var/core/hash_spec.rb b/ruby/packages/var-core/spec/oselvar/var/core/hash_spec.rb deleted file mode 100644 index 1ece6adc..00000000 --- a/ruby/packages/var-core/spec/oselvar/var/core/hash_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'oselvar/var/core' - -module Oselvar - module Var - module Core - # Translated from hash.test.ts / test_hash.py. - ::RSpec.describe Hash32 do - def hash_source(source) = described_class.hash_source(source) - - it 'is deterministic' do - expect(hash_source('abc')).to eq(hash_source('abc')) - end - - it 'changes for a one-character difference' do - expect(hash_source('abc')).not_to eq(hash_source('abd')) - end - - it 'is namespaced with the algorithm prefix' do - expect(hash_source('abc')).to start_with('fnv1a:') - end - - it 'matches the TypeScript vectors' do - expect(hash_source('hello')).to eq('fnv1a:4f9f2cab') - expect(hash_source('abc')).to eq('fnv1a:1a47e90b') - expect(hash_source("# Title\n")).to eq('fnv1a:4eace75e') - end - end - end - end -end diff --git a/ruby/packages/var-core/spec/oselvar/var/core/span_spec.rb b/ruby/packages/var-core/spec/oselvar/var/core/span_spec.rb deleted file mode 100644 index e352b936..00000000 --- a/ruby/packages/var-core/spec/oselvar/var/core/span_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'oselvar/var/core' - -module Oselvar - module Var - module Core - # Translated from typescript/packages/var-core/tests/span.test.ts and - # python/packages/var-core/tests/test_span.py. - ::RSpec.describe Offsets do - describe '.utf16_len' do - it 'counts ASCII, BMP, and astral characters in UTF-16 units' do - expect(described_class.utf16_len('abc')).to eq(3) - expect(described_class.utf16_len('é')).to eq(1) # BMP: 1 code unit - expect(described_class.utf16_len('😀')).to eq(2) # astral: surrogate pair - expect(described_class.utf16_len('a😀b')).to eq(4) - end - end - - describe '.to_utf16_offset' do - it 'counts UTF-16 units before a code-point index' do - s = 'a😀b' # cp indices: a=0 😀=1 b=2 - expect(described_class.to_utf16_offset(s, 0)).to eq(0) - expect(described_class.to_utf16_offset(s, 1)).to eq(1) # after "a" - expect(described_class.to_utf16_offset(s, 2)).to eq(3) # after "a😀" (1+2) - end - end - - describe '.utf16_slice' do - it 'round-trips through UTF-16 units' do - s = 'x😀y' # u16: x=0 😀=1..3 y=3 - expect(described_class.utf16_slice(s, 0, 1)).to eq('x') - expect(described_class.utf16_slice(s, 1, 3)).to eq('😀') - expect(described_class.utf16_slice(s, 3, 4)).to eq('y') - end - end - - describe '.line_col' do - it 'counts UTF-16 units, resetting column after a newline' do - s = "ab\n😀x" # u16 offsets: a0 b1 \n2 😀3-4 x5 - expect(described_class.line_col(s, 1)).to eq([1, 2]) - expect(described_class.line_col(s, 5)).to eq([2, 3]) # astral counts as 2 - end - end - - describe '.span_from_offsets' do - it 'computes line and column for a single-line source' do - span = described_class.span_from_offsets('hello world', 6, 11) - expect(span).to eq(Span.new( - start_offset: 6, end_offset: 11, - start_line: 1, start_col: 7, - end_line: 1, end_col: 12 - )) - end - - it 'handles multi-line sources' do - source = "line one\nline two\nline three" - span = described_class.span_from_offsets(source, 14, 17) # 'two' - expect(span).to eq(Span.new( - start_offset: 14, end_offset: 17, - start_line: 2, start_col: 6, - end_line: 2, end_col: 9 - )) - end - - it 'handles a range crossing a newline' do - span = described_class.span_from_offsets("ab\ncd", 1, 4) # 'b'..'d' - expect(span).to eq(Span.new( - start_offset: 1, end_offset: 4, - start_line: 1, start_col: 2, - end_line: 2, end_col: 2 - )) - end - end - end - end - end -end diff --git a/ruby/packages/var-minitest/lib/oselvar/var/minitest.rb b/ruby/packages/var-minitest/lib/oselvar/var/minitest.rb deleted file mode 100644 index 5ba147db..00000000 --- a/ruby/packages/var-minitest/lib/oselvar/var/minitest.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -require 'minitest' -require 'oselvar/var/runner' - -module Oselvar - module Var - # Minitest adapter. One call turns every spec matched by var.config.json into - # a generated Minitest::Test subclass — one class per spec file, one test - # method per example. Mirrors var-unittest. - # - # # test/var_test.rb - # require "oselvar/var/minitest" - # Oselvar::Var::Minitest.generate_tests - module Minitest - VERSION = '0.4.2' - - module_function - - def generate_tests(namespace = Object, root: nil) - root ||= File.dirname(caller_locations(1, 1).first.path) - root = File.expand_path(root) - cfg = Config.read_var_config(root) - loaded = Runner.load_steps(cfg.steps, root) - store = Runner.create_file_baseline_store(root) - update = %w[1 true].include?(ENV.fetch('VAR_UPDATE', nil)) - - Runner.find_specs(cfg.docs_include, cfg.docs_exclude, root).each do |spec_path| - klass = build_test_case(spec_path, root, loaded, store, update) - namespace.const_set("Var_#{identifier(Runner.rel_posix(spec_path, root))}", klass) - end - end - - def build_test_case(spec_path, root, loaded, store, update) - rel = Runner.rel_posix(spec_path, root) - source = File.read(spec_path, encoding: 'UTF-8') - plan = Runner.plan_spec(File.basename(spec_path), source, loaded.registry) - pairs = Runner.examples_with_runs(plan, loaded.create_context, Runner::RecordingReporter.new) - - klass = Class.new(::Minitest::Test) - seen = Hash.new(0) - pairs.each do |example, run| - base = example.scope_stack.last || example.name - stem = identifier(base) - idx = seen[stem] - seen[stem] += 1 - method_name = idx.zero? ? "test_#{stem}" : "test_#{stem}_#{idx}" - klass.define_method(method_name) do - run.call - rescue StandardError => e - raise ::Minitest::Assertion, Runner.render_failure(e, source, rel) if Minitest.var_diff_error?(e) - - raise - end - end - - Core::Drifts.reconcile_drift(store, rel, source, plan.var_doc, plan, update: update).each do |drift| - message = Core::Diagnostics.drift_detected(drift.name, drift.span).message - klass.define_method("test_var_drift_#{drift.line}") { raise ::Minitest::Assertion, message } - end - - klass - end - - # A markdown/return mismatch is a test failure (Minitest::Assertion); any - # other exception propagates as an error. - def var_diff_error?(error) - error.is_a?(Core::CellMismatchError) || error.is_a?(Core::DocStringMismatchError) || - error.is_a?(Core::ReturnShapeError) || error.is_a?(Core::UnexpectedPassError) - end - - # Project arbitrary text onto a valid identifier fragment. - def identifier(text) - ident = text.gsub(/\W+/, '_').gsub(/\A_+|_+\z/, '') - ident = 'example' if ident.empty? - ident = "_#{ident}" if ident.match?(/\A\d/) - ident - end - end - end -end diff --git a/ruby/packages/var-minitest/spec/oselvar/var/minitest_spec.rb b/ruby/packages/var-minitest/spec/oselvar/var/minitest_spec.rb deleted file mode 100644 index 25b83044..00000000 --- a/ruby/packages/var-minitest/spec/oselvar/var/minitest_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'tmpdir' -require 'fileutils' -require 'oselvar/var/minitest' - -module Oselvar - module Var - ::RSpec.describe Minitest do - def corpus_dir - dir = __dir__ - dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' - File.join(dir, 'conformance', 'bundles') - end - - # Build a throwaway project from a conformance bundle (its example.md + - # *.steps.rb) with a matching var.config.json. - def project_from_bundle(tmp, bundle, spec_name) - src = File.join(corpus_dir, bundle) - FileUtils.mkdir_p(File.join(tmp, 'steps')) - FileUtils.cp(File.join(src, 'example.md'), File.join(tmp, spec_name)) - FileUtils.cp(Dir.glob(File.join(src, '*.steps.rb')).first, File.join(tmp, 'steps')) - File.write(File.join(tmp, 'var.config.json'), - '{"docs":{"include":["*.md"]},"steps":["steps/*.steps.rb"]}') - end - - it 'generates one Test subclass per spec with a passing method for a passing example' do - Dir.mktmpdir do |tmp| - project_from_bundle(tmp, '01-roman-numerals', 'pass.md') - namespace = Module.new - described_class.generate_tests(namespace, root: tmp) - - klass = namespace.constants.map { |c| namespace.const_get(c) }.first - expect(klass.ancestors).to include(::Minitest::Test) - methods = klass.instance_methods(false).grep(/^test_/) - expect(methods).not_to be_empty - methods.each do |m| - expect { klass.new(m.to_s).public_send(m) }.not_to raise_error - end - end - end - - it 'a doc-string mismatch surfaces as a Minitest::Assertion (a failure)' do - Dir.mktmpdir do |tmp| - project_from_bundle(tmp, '06-doc-string-mismatch', 'fail.md') - namespace = Module.new - described_class.generate_tests(namespace, root: tmp) - - klass = namespace.constants.map { |c| namespace.const_get(c) }.first - method = klass.instance_methods(false).grep(/^test_/).first - expect { klass.new(method.to_s).public_send(method) }.to raise_error(::Minitest::Assertion) - end - end - end - end -end diff --git a/ruby/packages/var-rspec/lib/oselvar/var/rspec.rb b/ruby/packages/var-rspec/lib/oselvar/var/rspec.rb deleted file mode 100644 index 32a472d7..00000000 --- a/ruby/packages/var-rspec/lib/oselvar/var/rspec.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -require 'rspec/core' -require 'oselvar/var/runner' - -module Oselvar - module Var - # RSpec adapter. One call defines an RSpec example group per spec matched by - # var.config.json, with one `it` per Markdown example (header-bound rows are - # separate examples) and a drift gate. See ADR 0005. - # - # # spec/var_spec.rb - # require "oselvar/var/rspec" - # Oselvar::Var::RSpec.generate - module RSpec - VERSION = '0.4.2' - - module_function - - def generate(root: nil) - root ||= File.dirname(caller_locations(1, 1).first.path) - root = File.expand_path(root) - cfg = Config.read_var_config(root) - loaded = Runner.load_steps(cfg.steps, root) - store = Runner.create_file_baseline_store(root) - update = %w[1 true].include?(ENV.fetch('VAR_UPDATE', nil)) - - Runner.find_specs(cfg.docs_include, cfg.docs_exclude, root).each do |spec_path| - define_group(spec_path, root, loaded, store, update) - end - end - - def define_group(spec_path, root, loaded, store, update) - rel = Runner.rel_posix(spec_path, root) - source = File.read(spec_path, encoding: 'UTF-8') - plan = Runner.plan_spec(File.basename(spec_path), source, loaded.registry) - pairs = Runner.examples_with_runs(plan, loaded.create_context, Runner::RecordingReporter.new) - drifts = Core::Drifts.reconcile_drift(store, rel, source, plan.var_doc, plan, update: update) - - ::RSpec.describe(rel) do - pairs.each do |example, run| - # A var diff surfaces as a failure carrying the span-anchored render; - # any other exception propagates. RSpec reports both as failures. - it(example.name) do - run.call - rescue StandardError => e - raise Runner.render_failure(e, source, rel) if RSpec.var_diff_error?(e) - - raise - end - end - - drifts.each do |drift| - message = Core::Diagnostics.drift_detected(drift.name, drift.span).message - it("var drift at line #{drift.line}") { raise message } - end - end - end - - def var_diff_error?(error) - error.is_a?(Core::CellMismatchError) || error.is_a?(Core::DocStringMismatchError) || - error.is_a?(Core::ReturnShapeError) || error.is_a?(Core::UnexpectedPassError) - end - end - end -end diff --git a/ruby/packages/var-rspec/spec/oselvar/var/rspec_spec.rb b/ruby/packages/var-rspec/spec/oselvar/var/rspec_spec.rb deleted file mode 100644 index f70ba1d6..00000000 --- a/ruby/packages/var-rspec/spec/oselvar/var/rspec_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'tmpdir' -require 'oselvar/var/rspec' - -module Oselvar - module Var - # The RSpec adapter is exercised end-to-end by examples/ruby-rspec (run via - # the real `rspec` binary). Here we unit-test the failure classification and - # that generate no-ops cleanly on a project with no specs. - ::RSpec.describe RSpec do - describe '.var_diff_error?' do - it 'classifies var diff/shape errors as failures' do - expect(described_class.var_diff_error?(Core::ReturnShapeError.new('x'))).to be(true) - expect(described_class.var_diff_error?(Core::UnexpectedPassError.new)).to be(true) - expect(described_class.var_diff_error?(Core::CellMismatchError.new([]))).to be(true) - end - - it 'does not classify arbitrary errors as failures' do - expect(described_class.var_diff_error?(RuntimeError.new('boom'))).to be(false) - end - end - - it 'generate no-ops when the project has no specs' do - Dir.mktmpdir do |tmp| - File.write(File.join(tmp, 'var.config.json'), - '{"docs":{"include":["*.md"]},"steps":["steps/*.steps.rb"]}') - expect { described_class.generate(root: tmp) }.not_to raise_error - end - end - end - end -end diff --git a/ruby/packages/var-runner/exe/var b/ruby/packages/var-runner/exe/var deleted file mode 100755 index bed5b73d..00000000 --- a/ruby/packages/var-runner/exe/var +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'oselvar/var/runner/cli' - -exit Oselvar::Var::Runner::CLI.main(ARGV) diff --git a/ruby/packages/var-runner/lib/oselvar/var/runner.rb b/ruby/packages/var-runner/lib/oselvar/var/runner.rb deleted file mode 100644 index f56d570c..00000000 --- a/ruby/packages/var-runner/lib/oselvar/var/runner.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var' -require 'oselvar/var/config' -require 'oselvar/var/core' - -module Oselvar - module Var - # The imperative shell: discovery, step loading, planning, failure - # rendering, and the filesystem drift baseline store. Depends on the facade - # and config; never on a test framework. Port of var-runner. - module Runner - VERSION = '0.4.2' - end - end -end - -require 'oselvar/var/runner/discovery' -require 'oselvar/var/runner/steps' -require 'oselvar/var/runner/run' -require 'oselvar/var/runner/render' -require 'oselvar/var/runner/baseline_store' diff --git a/ruby/packages/var-runner/lib/oselvar/var/runner/baseline_store.rb b/ruby/packages/var-runner/lib/oselvar/var/runner/baseline_store.rb deleted file mode 100644 index 1b547c11..00000000 --- a/ruby/packages/var-runner/lib/oselvar/var/runner/baseline_store.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Oselvar - module Var - module Runner - # The filesystem BaselineStore: the committed drift baseline lives at the - # project root as var.lock.json. The core owns the format; this adapter - # only moves raw text. Port of baseline_store.py. - class FileBaselineStore - def initialize(root) - @path = File.join(root.to_s, 'var.lock.json') - end - - def read - File.exist?(@path) ? File.read(@path, encoding: 'UTF-8') : nil - end - - def write(contents) - File.write(@path, contents) - end - end - - module_function - - def create_file_baseline_store(root) - FileBaselineStore.new(root) - end - end - end -end diff --git a/ruby/packages/var-runner/lib/oselvar/var/runner/cli.rb b/ruby/packages/var-runner/lib/oselvar/var/runner/cli.rb deleted file mode 100644 index a4e8dc86..00000000 --- a/ruby/packages/var-runner/lib/oselvar/var/runner/cli.rb +++ /dev/null @@ -1,128 +0,0 @@ -# frozen_string_literal: true - -require 'fileutils' - -module Oselvar - module Var - module Runner - # The `var` command-line entry point (exposed by the `exe/var` - # executable). Today it offers a single sub-command, `var init`, which - # scaffolds a new project: a `var.config.json`, one Markdown spec, its - # step definitions, and a framework bridge that turns the specs into - # RSpec examples or Minitest tests. - # - # The config, spec and steps mirror the TypeScript CLI (`@oselvar/var-cli`) - # so a project started with `var init` looks the same in every language; - # only the bridge is Ruby-specific, because RSpec/Minitest — unlike - # pytest — need an explicit generator call to discover the specs. - module CLI - CONFIG = <<~JSON - { - "docs": { "include": ["var-examples/**/*.md"], "exclude": [] }, - "steps": ["var-examples/**/*.steps.rb"] - } - JSON - - EXAMPLE_MD = <<~MARKDOWN - # Hello, BDD - - Given I greet "world" - Then the greeting is "Hello, world!" - MARKDOWN - - EXAMPLE_STEPS = <<~RUBY - # frozen_string_literal: true - - require 'oselvar/var' - - steps(greeting: '') do - stimulus('I greet {string}') { |_state, name| { greeting: "Hello, \#{name}!" } } - sensor('the greeting is {string}') { |state, _expected| state[:greeting] } - end - RUBY - - RSPEC_BRIDGE = <<~RUBY - # frozen_string_literal: true - - # Turn every Markdown spec matched by var.config.json into RSpec examples — - # one `it` per Markdown example, discovered when this file loads. - require 'oselvar/var/rspec' - - # var.config.json lives at the project root (the parent of spec/). - Oselvar::Var::RSpec.generate(root: File.expand_path('..', __dir__)) - RUBY - - MINITEST_BRIDGE = <<~RUBY - # frozen_string_literal: true - - require 'minitest/autorun' - require 'oselvar/var/minitest' - - # Turn every Markdown spec matched by var.config.json into Minitest tests — - # var.config.json lives at the project root (the parent of test/). - Oselvar::Var::Minitest.generate_tests(Object, root: File.expand_path('..', __dir__)) - RUBY - - USAGE = <<~TEXT - var — scaffold and run Markdown specs - - Usage: - var init scaffold a new project - TEXT - - def self.main(argv, cwd: Dir.pwd, out: $stdout) - case argv.first - when 'init' - run_init(cwd, out) - else - out.print(USAGE) - argv.empty? || %w[help -h --help].include?(argv.first) ? 0 : 1 - end - end - - # Write the scaffold into +cwd+, skipping any file that already exists. - # The framework bridge matches whichever adapter gem is installed - # (RSpec by default). - def self.run_init(cwd, out, framework: detect_framework) - files = [ - ['var.config.json', CONFIG], - ['var-examples/01-hello.md', EXAMPLE_MD], - ['var-examples/steps/01-hello.steps.rb', EXAMPLE_STEPS] - ] - files << if framework == :minitest - ['test/var_test.rb', MINITEST_BRIDGE] - else - ['spec/var_spec.rb', RSPEC_BRIDGE] - end - - files.each do |rel, content| - target = File.join(cwd, rel) - if File.exist?(target) - out.puts "skipped #{rel} (already exists)" - next - end - FileUtils.mkdir_p(File.dirname(target)) - File.write(target, content) - out.puts "created #{rel}" - end - 0 - end - - # RSpec when its adapter is installed, Minitest when only that one is, - # RSpec as the fallback (matching the tutorial's default track). - def self.detect_framework - return :rspec if gem_present?('oselvar-var-rspec') - return :minitest if gem_present?('oselvar-var-minitest') - - :rspec - end - - def self.gem_present?(name) - Gem::Specification.find_all_by_name(name).any? - rescue StandardError - false - end - end - end - end -end diff --git a/ruby/packages/var-runner/lib/oselvar/var/runner/discovery.rb b/ruby/packages/var-runner/lib/oselvar/var/runner/discovery.rb deleted file mode 100644 index e580f5ae..00000000 --- a/ruby/packages/var-runner/lib/oselvar/var/runner/discovery.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -require 'pathname' - -module Oselvar - module Var - module Runner - module_function - - # Translate a glob with **, *, ? to an anchored regex (PEP 428 / pathlib - # full_match semantics), matching the other ports' hand-rolled compiler - # rather than Ruby's Dir glob. Port of _glob_to_regex. - def glob_to_regex(pattern) - result = +'' - i = 0 - n = pattern.length - while i < n - c = pattern[i] - if c == '/' && pattern[i, 4] == '/**/' - result << '/(?:.+/)?' - i += 4 - elsif c == '/' && pattern[i, 3] == '/**' && i + 3 == n - result << '(?:/.*)?' - i += 3 - elsif c == '*' && pattern[i, 3] == '**/' - result << '(?:.*/)?' - i += 3 - elsif c == '*' && pattern[i, 2] == '**' - result << '.*' - i += 2 - elsif c == '*' - result << '[^/]*' - i += 1 - elsif c == '?' - result << '[^/]' - i += 1 - else - result << Regexp.escape(c) - i += 1 - end - end - /\A#{result}\z/ - end - - # Relative POSIX path of +path+ within +root+, without dereferencing - # symlinks; yields a ../ prefix when +path+ is outside +root+. - def rel_posix(path, root) - Pathname.new(File.expand_path(path)) - .relative_path_from(Pathname.new(File.expand_path(root))).to_s - end - - def matches_any?(rel, globs) - globs.any? { |g| glob_to_regex(g).match?(rel) } - end - - # True iff +path+ matches an include glob and no exclude glob. - def match_spec?(path, include, exclude, root) - rel = rel_posix(path, root) - matches_any?(rel, include) && !matches_any?(rel, exclude) - end - - # Existing files under +root+ matching any include glob, minus excludes; sorted. - def find_specs(include, exclude, root) - out = [] - include.each do |g| - out.concat(Dir.glob(g, base: root).map { |rel| File.join(root, rel) }) - end - out = out.select { |p| File.file?(p) }.uniq - out.reject { |p| matches_any?(rel_posix(p, root), exclude) }.sort - end - end - end -end diff --git a/ruby/packages/var-runner/lib/oselvar/var/runner/render.rb b/ruby/packages/var-runner/lib/oselvar/var/runner/render.rb deleted file mode 100644 index 4718d977..00000000 --- a/ruby/packages/var-runner/lib/oselvar/var/runner/render.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core' - -module Oselvar - module Var - module Runner - module_function - - # Render a step failure as a human-readable, markdown-anchored string, - # dispatching on the concrete error type. Port of render.py. - def render_failure(error, _source, path) - case error - when Core::CellMismatchError - lines = ["Cell mismatch in #{path}:"] - failing = error.cells.reject(&:ok) - lines << ' (no failing cells)' if failing.empty? - failing.each do |cell| - lines << " line #{cell.span.start_line} | column '#{cell.column}' — " \ - "expected: #{cell.expected.inspect}, actual: #{cell.actual.inspect}" - end - lines.join("\n") - when Core::DocStringMismatchError - diff = error.diff - "Doc string mismatch at line #{diff.span.start_line}:\n " \ - "expected: #{diff.expected.inspect}\n actual: #{diff.actual.inspect}" - when Core::ReturnShapeError - error.message - else - "#{error.class}: #{error.message}" - end - end - end - end -end diff --git a/ruby/packages/var-runner/lib/oselvar/var/runner/run.rb b/ruby/packages/var-runner/lib/oselvar/var/runner/run.rb deleted file mode 100644 index add92d1b..00000000 --- a/ruby/packages/var-runner/lib/oselvar/var/runner/run.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core' - -module Oselvar - module Var - module Runner - # Collects diagnostics emitted during planning/execution. - class RecordingReporter - attr_reader :diagnostics - - def initialize - @diagnostics = [] - end - - def diagnostic(diagnostic) - @diagnostics << diagnostic - end - end - - module_function - - def plan_spec(path, source, registry) - Core::Plan.plan(Core::Parse.parse(path, source), registry) - end - - # Pair each PlannedExample with its lazy run closure, in plan order. - def examples_with_runs(execution_plan, create_context, reporter) - reporter_cb = ->(d) { reporter.diagnostic(d) } - queue = Core::Execute.collect_examples(execution_plan, create_context: create_context, reporter: reporter_cb) - execution_plan.examples.zip(queue).map { |example, queued| [example, queued.run] } - end - end - end -end diff --git a/ruby/packages/var-runner/lib/oselvar/var/runner/steps.rb b/ruby/packages/var-runner/lib/oselvar/var/runner/steps.rb deleted file mode 100644 index 7ce69220..00000000 --- a/ruby/packages/var-runner/lib/oselvar/var/runner/steps.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var' -require 'oselvar/var/registry' - -module Oselvar - module Var - module Runner - # The registry + per-file context factory built from loaded step files. - LoadedSteps = Data.define(:registry, :create_context) - - module_function - - # Reset the accumulator, load (execute) every step file matching - # +step_globs+ under +root+, and build the registry + context factory. - def load_steps(step_globs, root) - RegistryGlue.reset_builder - files = [] - step_globs.each do |g| - files.concat(Dir.glob(g, base: root).map { |rel| File.join(root, rel) }) - end - files.select { |p| File.file?(p) }.uniq.sort.each { |path| load path } - LoadedSteps.new(registry: RegistryGlue.build_registry, create_context: RegistryGlue.context_factory) - end - end - end -end diff --git a/ruby/packages/var-runner/spec/oselvar/var/runner/cli_spec.rb b/ruby/packages/var-runner/spec/oselvar/var/runner/cli_spec.rb deleted file mode 100644 index 93d49da1..00000000 --- a/ruby/packages/var-runner/spec/oselvar/var/runner/cli_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'tmpdir' -require 'stringio' -require 'oselvar/var/runner/cli' - -module Oselvar - module Var - module Runner - ::RSpec.describe CLI do - describe '.run_init' do - it 'scaffolds the config, spec, steps and an RSpec bridge' do - Dir.mktmpdir do |dir| - out = StringIO.new - code = described_class.run_init(dir, out, framework: :rspec) - - expect(code).to eq(0) - expect(File.exist?(File.join(dir, 'var.config.json'))).to be(true) - expect(File.exist?(File.join(dir, 'var-examples/01-hello.md'))).to be(true) - steps = File.read(File.join(dir, 'var-examples/steps/01-hello.steps.rb')) - expect(steps).to include('steps(greeting: \'\') do', 'stimulus(', 'sensor(') - expect(File.read(File.join(dir, 'spec/var_spec.rb'))).to include('Oselvar::Var::RSpec.generate') - expect(out.string).to include('created var.config.json') - end - end - - it 'writes a Minitest bridge when that framework is selected' do - Dir.mktmpdir do |dir| - described_class.run_init(dir, StringIO.new, framework: :minitest) - expect(File.exist?(File.join(dir, 'test/var_test.rb'))).to be(true) - expect(File.exist?(File.join(dir, 'spec/var_spec.rb'))).to be(false) - end - end - - it 'skips files that already exist' do - Dir.mktmpdir do |dir| - File.write(File.join(dir, 'var.config.json'), "{}\n") - out = StringIO.new - described_class.run_init(dir, out, framework: :rspec) - - expect(File.read(File.join(dir, 'var.config.json'))).to eq("{}\n") - expect(out.string).to include('skipped var.config.json (already exists)') - end - end - end - end - end - end -end diff --git a/ruby/packages/var-runner/spec/oselvar/var/runner_spec.rb b/ruby/packages/var-runner/spec/oselvar/var/runner_spec.rb deleted file mode 100644 index 4ccd5e58..00000000 --- a/ruby/packages/var-runner/spec/oselvar/var/runner_spec.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'json' -require 'oselvar/var/runner' - -module Oselvar - module Var - ::RSpec.describe Runner do - describe '.glob_to_regex / .match_spec?' do - it 'matches * within a segment but not across /' do - expect(described_class.glob_to_regex('*.md').match?('a.md')).to be(true) - expect(described_class.glob_to_regex('*.md').match?('dir/a.md')).to be(false) - end - - it 'matches **/ across nested segments (leading)' do - rx = described_class.glob_to_regex('**/*.steps.rb') - expect(rx.match?('a.steps.rb')).to be(true) - expect(rx.match?('steps/a.steps.rb')).to be(true) - expect(rx.match?('a/b/c.steps.rb')).to be(true) - end - - it 'honours excludes' do - root = Dir.pwd - expect(described_class.match_spec?(File.join(root, 'a.md'), ['*.md'], [], root)).to be(true) - expect(described_class.match_spec?(File.join(root, 'README.md'), ['*.md'], ['README.md'], root)).to be(false) - end - end - - describe 'dogfood: bundle outcomes match trace.json' do - def self.corpus_dir - dir = __dir__ - dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' - File.join(dir, 'conformance', 'bundles') - end - - corpus = corpus_dir - - Dir.children(corpus).sort.each do |bundle| - bundle_dir = File.join(corpus, bundle) - trace_json = File.join(bundle_dir, 'golden', 'trace.json') - steps_rb = Dir.glob(File.join(bundle_dir, '*.steps.rb')).first - next unless File.exist?(trace_json) && steps_rb - - it "#{bundle} — runner outcomes agree with the trace goldens" do - loaded = described_class.load_steps(['*.steps.rb'], bundle_dir) - source = File.read(File.join(bundle_dir, 'example.md'), encoding: 'UTF-8') - plan = described_class.plan_spec('example.md', source, loaded.registry) - pairs = described_class.examples_with_runs(plan, loaded.create_context, Runner::RecordingReporter.new) - - actual = pairs.map do |example, run| - outcome = 'pass' - begin - run.call - rescue StandardError - outcome = 'fail' - end - [example.name, outcome] - end - - trace = JSON.parse(File.read(trace_json, encoding: 'UTF-8')) - expected = trace['examples'].map { |e| [e['name'], e['outcome']] } - expect(actual).to eq(expected) - end - end - end - end - end -end diff --git a/ruby/packages/var/lib/oselvar/var/internal.rb b/ruby/packages/var/lib/oselvar/var/internal.rb deleted file mode 100644 index d5077cdf..00000000 --- a/ruby/packages/var/lib/oselvar/var/internal.rb +++ /dev/null @@ -1,118 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/core' - -module Oselvar - module Var - # The module-scope step-registration accumulator behind the block DSL - # `steps(...) do stimulus(...); sensor(...) end`. Mirrors @oselvar/var's - # internal.ts. A step file, when loaded, calls steps() once; the Builder its - # block registers into these accumulators. The runner/harness then reads - # them via build_registry / context_factory. - module Internal - @steps = [] - @context_factories_by_file = {} - @custom_types = [] - - class << self - # Register a file's state factory and return a Builder whose - # stimulus/sensor/param methods accumulate that file's steps. +factory+ - # is a callable (or nil for empty state); +source_file+ keys the - # per-file context factory. Raises if called twice for one file. - def register(factory, source_file) - raise "steps() called more than once in #{source_file}" if @context_factories_by_file.key?(source_file) - - @context_factories_by_file[source_file] = factory || -> { {} } - Builder.new - end - - # Accumulate one step. The handler's source_location anchors it to the - # line the block is written on. - def add_step(expression, handler, kind) - file, line = handler.source_location - @steps << { - expression: expression, source_file: file, source_line: line, - handler: handler, kind: kind - } - nil - end - - # Accumulate one custom parameter type. - def add_custom_type(name, regexp, parse, format) - @custom_types << { name: name, regexp: regexp, parse: parse, format: format } - nil - end - - # (step_file) -> state: invoke the file's factory, or {} if none. - def context_factory - factories = @context_factories_by_file.dup - lambda do |step_file| - factory = factories[step_file] - factory ? factory.call : {} - end - end - - # Build a Core::Registry: custom parameter types first (so expressions - # can reference them), then steps in registration order. - def build_registry - registry = Core::Registries.create_registry - @custom_types.each do |type| - registry = Core::Registries.define_parameter_type( - registry, name: type[:name], regexp: type[:regexp], parse: type[:parse], format: type[:format] - ) - end - @steps.each do |step| - registry = Core::Registries.add_step( - registry, - expression: step[:expression], - expression_source_file: step[:source_file], - expression_source_line: step[:source_line], - handler: step[:handler], - kind: step[:kind] - ) - end - registry - end - - # Clear all accumulated state (between isolated runs / harness bundles). - def reset_builder - @steps = [] - @context_factories_by_file = {} - @custom_types = [] - end - - # Conformance-harness accessor: custom parameter types projected to the - # {"name","regexp"} wire shape. `regexp` is the bare source (Regexp#source - # or the string as authored) — the cross-port convention. - def custom_parameter_types - @custom_types.map do |type| - regexp = type[:regexp] - regexp = regexp.source if regexp.is_a?(Regexp) - unless regexp.is_a?(String) - raise "parameter type #{type[:name].inspect}: regexp arrays are not supported " \ - 'by the conformance projection yet' - end - { 'name' => type[:name], 'regexp' => regexp } - end - end - end - - # The block-scoped authoring DSL. A Builder is `instance_eval`-ed with the - # `steps` block, so authors write bare `stimulus`/`sensor`/`param` calls; - # each delegates to the accumulator above. - class Builder - def stimulus(expression, &handler) - Internal.add_step(expression, handler, 'stimulus') - end - - def sensor(expression, &handler) - Internal.add_step(expression, handler, 'sensor') - end - - def param(name, regexp, parse: nil, format: nil) - Internal.add_custom_type(name, regexp, parse, format) - end - end - end - end -end diff --git a/ruby/packages/var/lib/oselvar/var/registry.rb b/ruby/packages/var/lib/oselvar/var/registry.rb deleted file mode 100644 index bc849a6b..00000000 --- a/ruby/packages/var/lib/oselvar/var/registry.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require 'oselvar/var/internal' - -module Oselvar - module Var - # Adapter/harness glue — mirrors the `@oselvar/var/registry` subpath. - # Authors import only `steps` (via oselvar/var); runners and the conformance - # harness reach the accumulator through here. - module RegistryGlue - module_function - - def reset_builder - Internal.reset_builder - end - - def build_registry - Internal.build_registry - end - - def context_factory - Internal.context_factory - end - - def custom_parameter_types - Internal.custom_parameter_types - end - end - end -end diff --git a/ruby/packages/var/spec/conformance/plan_conformance_spec.rb b/ruby/packages/var/spec/conformance/plan_conformance_spec.rb deleted file mode 100644 index 42e87fac..00000000 --- a/ruby/packages/var/spec/conformance/plan_conformance_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'oselvar/var' -require 'oselvar/var/registry' - -module Oselvar - module Var - # Reproduces the shared conformance corpus' plan.json goldens byte-for-byte - # (match + plan stage). - ::RSpec.describe 'plan conformance' do - def self.corpus_dir - dir = __dir__ - dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' - File.join(dir, 'conformance', 'bundles') - end - - corpus = corpus_dir - - Dir.children(corpus).sort.each do |bundle| - golden = File.join(corpus, bundle, 'golden', 'plan.json') - steps_rb = Dir.glob(File.join(corpus, bundle, '*.steps.rb')).first - next unless File.exist?(golden) && steps_rb - - it "#{bundle} — plan.json matches golden" do - RegistryGlue.reset_builder - load steps_rb - registry = RegistryGlue.build_registry - source = File.read(File.join(corpus, bundle, 'example.md'), encoding: 'UTF-8') - var_doc = Core::Parse.parse('example.md', source) - plan = Core::Plan.plan(var_doc, registry) - actual = Core::CanonicalJson.canonical_stringify(Core::Conformance.to_plan_artifact(plan)) - expect(actual).to eq(File.read(golden, encoding: 'UTF-8')) - end - end - end - end -end diff --git a/ruby/packages/var/spec/conformance/registry_conformance_spec.rb b/ruby/packages/var/spec/conformance/registry_conformance_spec.rb deleted file mode 100644 index 4c158aa2..00000000 --- a/ruby/packages/var/spec/conformance/registry_conformance_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'oselvar/var' -require 'oselvar/var/registry' - -module Oselvar - module Var - # Reproduces the shared conformance corpus' registry.json goldens - # byte-for-byte (registration stage). Loads each bundle's *.steps.rb via the - # facade, then projects the built registry. Mirrors var/tests/conformance. - ::RSpec.describe 'registry conformance' do - def self.corpus_dir - dir = __dir__ - dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' - File.join(dir, 'conformance', 'bundles') - end - - corpus = corpus_dir - - Dir.children(corpus).sort.each do |bundle| - golden = File.join(corpus, bundle, 'golden', 'registry.json') - steps_rb = Dir.glob(File.join(corpus, bundle, '*.steps.rb')).first - next unless File.exist?(golden) && steps_rb - - it "#{bundle} — registry.json matches golden" do - RegistryGlue.reset_builder - load steps_rb - registry = RegistryGlue.build_registry - actual = Core::CanonicalJson.canonical_stringify( - Core::Conformance.to_registry_artifact(registry, RegistryGlue.custom_parameter_types) - ) - expect(actual).to eq(File.read(golden, encoding: 'UTF-8')) - end - end - end - end -end diff --git a/ruby/packages/var/spec/conformance/trace_conformance_spec.rb b/ruby/packages/var/spec/conformance/trace_conformance_spec.rb deleted file mode 100644 index f005092e..00000000 --- a/ruby/packages/var/spec/conformance/trace_conformance_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'oselvar/var' -require 'oselvar/var/registry' - -module Oselvar - module Var - # Reproduces the shared conformance corpus' trace.json goldens byte-for-byte - # (execution stage). - ::RSpec.describe 'trace conformance' do - def self.corpus_dir - dir = __dir__ - dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' - File.join(dir, 'conformance', 'bundles') - end - - corpus = corpus_dir - - Dir.children(corpus).sort.each do |bundle| - golden = File.join(corpus, bundle, 'golden', 'trace.json') - steps_rb = Dir.glob(File.join(corpus, bundle, '*.steps.rb')).first - next unless File.exist?(golden) && steps_rb - - it "#{bundle} — trace.json matches golden" do - RegistryGlue.reset_builder - load steps_rb - registry = RegistryGlue.build_registry - create_context = RegistryGlue.context_factory - source = File.read(File.join(corpus, bundle, 'example.md'), encoding: 'UTF-8') - var_doc = Core::Parse.parse('example.md', source) - artifacts = Core::Conformance.run_conformance( - var_doc, registry, create_context, RegistryGlue.custom_parameter_types - ) - actual = Core::CanonicalJson.canonical_stringify(artifacts[:trace]) - expect(actual).to eq(File.read(golden, encoding: 'UTF-8')) - end - end - end - end -end diff --git a/ruby/packages/var/lib/oselvar/var.rb b/ruby/packages/varar/lib/varar.rb similarity index 54% rename from ruby/packages/var/lib/oselvar/var.rb rename to ruby/packages/varar/lib/varar.rb index 6e07721f..f7646719 100644 --- a/ruby/packages/var/lib/oselvar/var.rb +++ b/ruby/packages/varar/lib/varar.rb @@ -1,13 +1,11 @@ # frozen_string_literal: true -require 'oselvar/var/core' -require 'oselvar/var/internal' -require 'oselvar/var/dsl' +require 'varar/core' +require 'varar/internal' +require 'varar/dsl' -module Oselvar +module Varar # The author facade: `steps` (top-level DSL) → [param, stimulus, sensor], # backed by the module-scope accumulator in Internal. - module Var - VERSION = '0.4.2' - end + VERSION = '0.4.2' end diff --git a/ruby/packages/var/lib/oselvar/var/dsl.rb b/ruby/packages/varar/lib/varar/dsl.rb similarity index 83% rename from ruby/packages/var/lib/oselvar/var/dsl.rb rename to ruby/packages/varar/lib/varar/dsl.rb index bc717996..23f6bd9a 100644 --- a/ruby/packages/var/lib/oselvar/var/dsl.rb +++ b/ruby/packages/varar/lib/varar/dsl.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'oselvar/var/internal' +require 'varar/internal' # `steps` as a top-level method, available in any step file after -# `require "oselvar/var"` (idiomatic for BDD step DSLs, like Cucumber-Ruby's +# `require "varar"` (idiomatic for BDD step DSLs, like Cucumber-Ruby's # Given/When/Then). It takes a block in which bare `stimulus`, `sensor` and # `param` register the file's steps: # @@ -29,7 +29,7 @@ def steps(state = nil, **kwstate, &block) state || {} end factory = initial.is_a?(Proc) ? initial : -> { initial } - builder = Oselvar::Var::Internal.register(factory, caller_locations(1, 1).first.path) + builder = Varar::Internal.register(factory, caller_locations(1, 1).first.path) builder.instance_eval(&block) if block nil end diff --git a/ruby/packages/varar/lib/varar/internal.rb b/ruby/packages/varar/lib/varar/internal.rb new file mode 100644 index 00000000..bff89381 --- /dev/null +++ b/ruby/packages/varar/lib/varar/internal.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +require 'varar/core' + +module Varar + # The module-scope step-registration accumulator behind the block DSL + # `steps(...) do stimulus(...); sensor(...) end`. Mirrors @varar/varar's + # internal.ts. A step file, when loaded, calls steps() once; the Builder its + # block registers into these accumulators. The runner/harness then reads + # them via build_registry / context_factory. + module Internal + @steps = [] + @context_factories_by_file = {} + @custom_types = [] + + class << self + # Register a file's state factory and return a Builder whose + # stimulus/sensor/param methods accumulate that file's steps. +factory+ + # is a callable (or nil for empty state); +source_file+ keys the + # per-file context factory. Raises if called twice for one file. + def register(factory, source_file) + raise "steps() called more than once in #{source_file}" if @context_factories_by_file.key?(source_file) + + @context_factories_by_file[source_file] = factory || -> { {} } + Builder.new + end + + # Accumulate one step. The handler's source_location anchors it to the + # line the block is written on. + def add_step(expression, handler, kind) + file, line = handler.source_location + @steps << { + expression: expression, source_file: file, source_line: line, + handler: handler, kind: kind + } + nil + end + + # Accumulate one custom parameter type. + def add_custom_type(name, regexp, parse, format) + @custom_types << { name: name, regexp: regexp, parse: parse, format: format } + nil + end + + # (step_file) -> state: invoke the file's factory, or {} if none. + def context_factory + factories = @context_factories_by_file.dup + lambda do |step_file| + factory = factories[step_file] + factory ? factory.call : {} + end + end + + # Build a Core::Registry: custom parameter types first (so expressions + # can reference them), then steps in registration order. + def build_registry + registry = Core::Registries.create_registry + @custom_types.each do |type| + registry = Core::Registries.define_parameter_type( + registry, name: type[:name], regexp: type[:regexp], parse: type[:parse], format: type[:format] + ) + end + @steps.each do |step| + registry = Core::Registries.add_step( + registry, + expression: step[:expression], + expression_source_file: step[:source_file], + expression_source_line: step[:source_line], + handler: step[:handler], + kind: step[:kind] + ) + end + registry + end + + # Clear all accumulated state (between isolated runs / harness bundles). + def reset_builder + @steps = [] + @context_factories_by_file = {} + @custom_types = [] + end + + # Conformance-harness accessor: custom parameter types projected to the + # {"name","regexp"} wire shape. `regexp` is the bare source (Regexp#source + # or the string as authored) — the cross-port convention. + def custom_parameter_types + @custom_types.map do |type| + regexp = type[:regexp] + regexp = regexp.source if regexp.is_a?(Regexp) + unless regexp.is_a?(String) + raise "parameter type #{type[:name].inspect}: regexp arrays are not supported " \ + 'by the conformance projection yet' + end + { 'name' => type[:name], 'regexp' => regexp } + end + end + end + + # The block-scoped authoring DSL. A Builder is `instance_eval`-ed with the + # `steps` block, so authors write bare `stimulus`/`sensor`/`param` calls; + # each delegates to the accumulator above. + class Builder + def stimulus(expression, &handler) + Internal.add_step(expression, handler, 'stimulus') + end + + def sensor(expression, &handler) + Internal.add_step(expression, handler, 'sensor') + end + + def param(name, regexp, parse: nil, format: nil) + Internal.add_custom_type(name, regexp, parse, format) + end + end + end +end diff --git a/ruby/packages/varar/lib/varar/registry.rb b/ruby/packages/varar/lib/varar/registry.rb new file mode 100644 index 00000000..bf99ffab --- /dev/null +++ b/ruby/packages/varar/lib/varar/registry.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'varar/internal' + +module Varar + # Adapter/harness glue — mirrors the `@varar/varar/registry` subpath. + # Authors import only `steps` (via varar); runners and the conformance + # harness reach the accumulator through here. + module RegistryGlue + module_function + + def reset_builder + Internal.reset_builder + end + + def build_registry + Internal.build_registry + end + + def context_factory + Internal.context_factory + end + + def custom_parameter_types + Internal.custom_parameter_types + end + end +end diff --git a/ruby/packages/varar/spec/conformance/plan_conformance_spec.rb b/ruby/packages/varar/spec/conformance/plan_conformance_spec.rb new file mode 100644 index 00000000..0ecc3d61 --- /dev/null +++ b/ruby/packages/varar/spec/conformance/plan_conformance_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'varar' +require 'varar/registry' + +module Varar + # Reproduces the shared conformance corpus' plan.json goldens byte-for-byte + # (match + plan stage). + ::RSpec.describe 'plan conformance' do + def self.corpus_dir + dir = __dir__ + dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' + File.join(dir, 'conformance', 'bundles') + end + + corpus = corpus_dir + + Dir.children(corpus).sort.each do |bundle| + golden = File.join(corpus, bundle, 'golden', 'plan.json') + steps_rb = Dir.glob(File.join(corpus, bundle, '*.steps.rb')).first + next unless File.exist?(golden) && steps_rb + + it "#{bundle} — plan.json matches golden" do + RegistryGlue.reset_builder + load steps_rb + registry = RegistryGlue.build_registry + source = File.read(File.join(corpus, bundle, 'example.md'), encoding: 'UTF-8') + var_doc = Core::Parse.parse('example.md', source) + plan = Core::Plan.plan(var_doc, registry) + actual = Core::CanonicalJson.canonical_stringify(Core::Conformance.to_plan_artifact(plan)) + expect(actual).to eq(File.read(golden, encoding: 'UTF-8')) + end + end + end +end diff --git a/ruby/packages/varar/spec/conformance/registry_conformance_spec.rb b/ruby/packages/varar/spec/conformance/registry_conformance_spec.rb new file mode 100644 index 00000000..b36cd416 --- /dev/null +++ b/ruby/packages/varar/spec/conformance/registry_conformance_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'varar' +require 'varar/registry' + +module Varar + # Reproduces the shared conformance corpus' registry.json goldens + # byte-for-byte (registration stage). Loads each bundle's *.steps.rb via the + # facade, then projects the built registry. Mirrors var/tests/conformance. + ::RSpec.describe 'registry conformance' do + def self.corpus_dir + dir = __dir__ + dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' + File.join(dir, 'conformance', 'bundles') + end + + corpus = corpus_dir + + Dir.children(corpus).sort.each do |bundle| + golden = File.join(corpus, bundle, 'golden', 'registry.json') + steps_rb = Dir.glob(File.join(corpus, bundle, '*.steps.rb')).first + next unless File.exist?(golden) && steps_rb + + it "#{bundle} — registry.json matches golden" do + RegistryGlue.reset_builder + load steps_rb + registry = RegistryGlue.build_registry + actual = Core::CanonicalJson.canonical_stringify( + Core::Conformance.to_registry_artifact(registry, RegistryGlue.custom_parameter_types) + ) + expect(actual).to eq(File.read(golden, encoding: 'UTF-8')) + end + end + end +end diff --git a/ruby/packages/varar/spec/conformance/trace_conformance_spec.rb b/ruby/packages/varar/spec/conformance/trace_conformance_spec.rb new file mode 100644 index 00000000..8afba041 --- /dev/null +++ b/ruby/packages/varar/spec/conformance/trace_conformance_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'varar' +require 'varar/registry' + +module Varar + # Reproduces the shared conformance corpus' trace.json goldens byte-for-byte + # (execution stage). + ::RSpec.describe 'trace conformance' do + def self.corpus_dir + dir = __dir__ + dir = File.dirname(dir) until File.directory?(File.join(dir, 'conformance', 'bundles')) || dir == '/' + File.join(dir, 'conformance', 'bundles') + end + + corpus = corpus_dir + + Dir.children(corpus).sort.each do |bundle| + golden = File.join(corpus, bundle, 'golden', 'trace.json') + steps_rb = Dir.glob(File.join(corpus, bundle, '*.steps.rb')).first + next unless File.exist?(golden) && steps_rb + + it "#{bundle} — trace.json matches golden" do + RegistryGlue.reset_builder + load steps_rb + registry = RegistryGlue.build_registry + create_context = RegistryGlue.context_factory + source = File.read(File.join(corpus, bundle, 'example.md'), encoding: 'UTF-8') + var_doc = Core::Parse.parse('example.md', source) + artifacts = Core::Conformance.run_conformance( + var_doc, registry, create_context, RegistryGlue.custom_parameter_types + ) + actual = Core::CanonicalJson.canonical_stringify(artifacts[:trace]) + expect(actual).to eq(File.read(golden, encoding: 'UTF-8')) + end + end + end +end diff --git a/ruby/packages/var/oselvar-var.gemspec b/ruby/packages/varar/varar.gemspec similarity index 82% rename from ruby/packages/var/oselvar-var.gemspec rename to ruby/packages/varar/varar.gemspec index 38fb2258..044b9283 100644 --- a/ruby/packages/var/oselvar-var.gemspec +++ b/ruby/packages/varar/varar.gemspec @@ -1,19 +1,19 @@ # frozen_string_literal: true Gem::Specification.new do |s| - s.name = 'oselvar-var' + s.name = 'varar' s.version = '0.4.2' s.summary = 'Markdown-native BDD — author API (define_state)' s.description = 'The Vár author facade: define_state and the step-registration accumulator.' s.authors = ['Aslak Hellesøy'] s.email = ['aslak@oselvar.com'] - s.homepage = 'https://var.oselvar.com' + s.homepage = 'https://varar.dev' s.license = 'MIT' s.required_ruby_version = '>= 3.2' s.files = Dir['lib/**/*.rb'] s.require_paths = ['lib'] s.add_dependency 'cucumber-cucumber-expressions', '20.0.0' - s.add_dependency 'oselvar-var-core', '0.4.2' + s.add_dependency 'varar-core', '0.4.2' s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/ruby/scripts/lint_no_reexports.rb b/ruby/scripts/lint_no_reexports.rb index 90c63428..54792c54 100644 --- a/ruby/scripts/lint_no_reexports.rb +++ b/ruby/scripts/lint_no_reexports.rb @@ -6,14 +6,14 @@ require 'pathname' ROOT = Pathname.new(__dir__).join('..').expand_path -CORE_LIB = ROOT.join('packages/var-core/lib') +CORE_LIB = ROOT.join('packages/core/lib') FORBIDDEN = [ - %r{require\s+["']oselvar/var["']}, # the facade - %r{require\s+["']oselvar/var/config["']}, - %r{require\s+["']oselvar/var/runner["']}, - %r{require\s+["']oselvar/var/rspec["']}, - %r{require\s+["']oselvar/var/minitest["']} + /require\s+["']varar["']/, # the facade + %r{require\s+["']varar/config["']}, + %r{require\s+["']varar/runner["']}, + %r{require\s+["']varar/rspec["']}, + %r{require\s+["']varar/minitest["']} ].freeze violations = [] diff --git a/rust/Cargo.lock b/rust/Cargo.lock index cccc57d0..c33b1a2a 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -390,32 +390,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "var" +name = "varar" version = "0.0.0" dependencies = [ - "var-core", + "varar-core", ] [[package]] -name = "var-cargotest" +name = "varar-cargotest" version = "0.0.0" dependencies = [ "libtest-mimic", - "var-config", - "var-core", - "var-runner", + "varar-config", + "varar-core", + "varar-runner", ] [[package]] -name = "var-config" +name = "varar-config" version = "0.0.0" dependencies = [ "serde_json", - "var-core", + "varar-core", ] [[package]] -name = "var-core" +name = "varar-core" version = "0.0.0" dependencies = [ "cucumber-expressions", @@ -423,12 +423,12 @@ dependencies = [ ] [[package]] -name = "var-runner" +name = "varar-runner" version = "0.0.0" dependencies = [ "regex", - "var-config", - "var-core", + "varar-config", + "varar-core", ] [[package]] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7d8faf04..1d8e383c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "3" -members = ["var-core", "var", "var-config", "var-runner", "var-cargotest"] +members = ["core", "varar", "config", "runner", "cargotest"] diff --git a/rust/cargotest/Cargo.toml b/rust/cargotest/Cargo.toml new file mode 100644 index 00000000..7f64363a --- /dev/null +++ b/rust/cargotest/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "varar-cargotest" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies] +varar-core = { path = "../core" } +varar-config = { path = "../config" } +varar-runner = { path = "../runner" } +libtest-mimic = "0.8" + +[lib] +name = "varar_cargotest" +path = "src/lib.rs" diff --git a/rust/var-cargotest/src/lib.rs b/rust/cargotest/src/lib.rs similarity index 87% rename from rust/var-cargotest/src/lib.rs rename to rust/cargotest/src/lib.rs index 371e2bdb..863c71a0 100644 --- a/rust/var-cargotest/src/lib.rs +++ b/rust/cargotest/src/lib.rs @@ -1,8 +1,8 @@ -//! `var-cargotest` — the `cargo test` adapter (ADR 0007). +//! `varar-cargotest` — the `cargo test` adapter (ADR 0007). //! -//! Turns every Markdown example matched by `var.config.json` into one +//! Turns every Markdown example matched by `varar.config.json` into one //! `libtest-mimic` test, reported/filtered/listed by `cargo test` like a native -//! `#[test]`. var-core is single-threaded (`Rc`, not `Send`), so each test body +//! `#[test]`. varar-core is single-threaded (`Rc`, not `Send`), so each test body //! captures only owned `Send` data — the spec path/source plus `fn` pointers to //! the step registry + context factory — and **re-derives its one example //! thread-locally** (re-parse, re-plan, run index `i`). No `Rc` value crosses a @@ -11,7 +11,7 @@ //! Usage from a consumer's `tests/specs.rs` (with `harness = false`): //! ```ignore //! fn main() { -//! var_cargotest::run( +//! varar_cargotest::run( //! std::path::Path::new(env!("CARGO_MANIFEST_DIR")), //! my_steps::build_registry, // fn() -> Registry //! my_steps::context_value, // fn(&str) -> Value @@ -23,11 +23,11 @@ use std::path::Path; use libtest_mimic::{Arguments, Failed, Trial}; -use var_core::drift::{self, reconcile_drift}; -use var_core::parse::parse; -use var_core::registry::Registry; -use var_core::value::Value; -use var_runner::{ +use varar_core::drift::{self, reconcile_drift}; +use varar_core::parse::parse; +use varar_core::registry::Registry; +use varar_core::value::Value; +use varar_runner::{ FileBaselineStore, example_names, find_specs, plan_spec, render_failure, run_example, }; @@ -54,7 +54,7 @@ pub fn run_one( } /// Enumerate every example (and any drift) as `libtest-mimic` trials. Drift is -/// reconciled here, on the main thread: a clean run rewrites `var.lock.json`; +/// reconciled here, on the main thread: a clean run rewrites `varar.lock.json`; /// `VAR_UPDATE=1` accepts drift instead of failing. pub fn trials(root: &Path, build_registry: BuildRegistry, context: ContextFactory) -> Vec { let config = read_config(root); @@ -106,6 +106,6 @@ pub fn run(root: &Path, build_registry: BuildRegistry, context: ContextFactory) libtest_mimic::run(&args, trials(root, build_registry, context)).exit(); } -fn read_config(root: &Path) -> var_config::VarConfig { - var_config::read_var_config(root).unwrap_or_else(|e| panic!("{e}")) +fn read_config(root: &Path) -> varar_config::VarConfig { + varar_config::read_var_config(root).unwrap_or_else(|e| panic!("{e}")) } diff --git a/rust/var-cargotest/tests/adapter.rs b/rust/cargotest/tests/adapter.rs similarity index 83% rename from rust/var-cargotest/tests/adapter.rs rename to rust/cargotest/tests/adapter.rs index 5f1d3b66..b1c3164a 100644 --- a/rust/var-cargotest/tests/adapter.rs +++ b/rust/cargotest/tests/adapter.rs @@ -1,11 +1,11 @@ //! Unit tests for the adapter's per-example runner (the libtest binding itself //! is exercised end-to-end by the sample project in examples/rust-cargotest). -use var_cargotest::run_one; -use var_core::handler::Handler; -use var_core::registry::{Registry, add_step, create_registry}; -use var_core::step_kind::StepKind; -use var_core::value::Value; +use varar_cargotest::run_one; +use varar_core::handler::Handler; +use varar_core::registry::{Registry, add_step, create_registry}; +use varar_core::step_kind::StepKind; +use varar_core::value::Value; fn build_registry() -> Registry { add_step( diff --git a/rust/var-config/Cargo.toml b/rust/config/Cargo.toml similarity index 62% rename from rust/var-config/Cargo.toml rename to rust/config/Cargo.toml index cdd450c3..bb38f516 100644 --- a/rust/var-config/Cargo.toml +++ b/rust/config/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "var-config" +name = "varar-config" version = "0.0.0" edition = "2024" publish = false @@ -7,13 +7,13 @@ publish = false [dependencies] serde_json = "1" -# Test-only: the conformance corpus is compared through var-core's canonical +# Test-only: the conformance corpus is compared through varar-core's canonical # JSON. The reader itself stays pure (serde_json only). [dev-dependencies] -var-core = { path = "../var-core" } +varar-core = { path = "../core" } [lib] -name = "var_config" +name = "varar_config" path = "src/lib.rs" [[test]] diff --git a/rust/var-config/src/lib.rs b/rust/config/src/lib.rs similarity index 93% rename from rust/var-config/src/lib.rs rename to rust/config/src/lib.rs index 03f33734..4c31b0ba 100644 --- a/rust/var-config/src/lib.rs +++ b/rust/config/src/lib.rs @@ -1,6 +1,6 @@ -//! `var-config` — the strict, fail-loud reader for `var.config.json`. +//! `varar-config` — the strict, fail-loud reader for `varar.config.json`. //! -//! Port of `@oselvar/var-config` / Python `var_config`. The canonical shape is +//! Port of `@varar/config` / Python `var_config`. The canonical shape is //! `{ docs: { include, exclude }, steps, snippets, scannerPlugins }`; every key //! is optional and defaults to empty. A missing file yields the empty config //! (tools no-op); malformed JSON, wrong types, or unknown keys fail loudly with @@ -22,10 +22,10 @@ pub struct VarConfig { pub scanner_plugins: Vec, } -/// Read `/var.config.json`. Missing file → empty config. Any malformed +/// Read `/varar.config.json`. Missing file → empty config. Any malformed /// input → `Err(message)` beginning with the file path. pub fn read_var_config(root: &Path) -> Result { - let path = root.join("var.config.json"); + let path = root.join("varar.config.json"); let loc = path.display(); if !path.is_file() { return Ok(VarConfig::default()); diff --git a/rust/var-config/tests/conformance.rs b/rust/config/tests/conformance.rs similarity index 91% rename from rust/var-config/tests/conformance.rs rename to rust/config/tests/conformance.rs index d4d5a699..da11ffae 100644 --- a/rust/var-config/tests/conformance.rs +++ b/rust/config/tests/conformance.rs @@ -1,13 +1,13 @@ //! Config conformance gate: reproduce `conformance/config/cases/*` byte-for-byte. //! A case with `expect-error.txt` must fail to load; otherwise the projected -//! config, serialized with var-core's canonical JSON, must equal `golden.json`. +//! config, serialized with varar-core's canonical JSON, must equal `golden.json`. use std::fs; use std::path::{Path, PathBuf}; -use var_config::{VarConfig, read_var_config}; -use var_core::canonical_json::canonical_stringify; -use var_core::value::Value; +use varar_config::{VarConfig, read_var_config}; +use varar_core::canonical_json::canonical_stringify; +use varar_core::value::Value; fn cases_dir() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../../conformance/config/cases") diff --git a/rust/var-core/Cargo.toml b/rust/core/Cargo.toml similarity index 82% rename from rust/var-core/Cargo.toml rename to rust/core/Cargo.toml index f78a22d1..6d7668c4 100644 --- a/rust/var-core/Cargo.toml +++ b/rust/core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "var-core" +name = "varar-core" version = "0.0.0" edition = "2024" publish = false @@ -9,5 +9,5 @@ regex = "1" cucumber-expressions = { version = "0.5", features = ["into-regex"] } [lib] -name = "var_core" +name = "varar_core" path = "src/lib.rs" diff --git a/rust/var-core/src/ast.rs b/rust/core/src/ast.rs similarity index 100% rename from rust/var-core/src/ast.rs rename to rust/core/src/ast.rs diff --git a/rust/var-core/src/canonical_json.rs b/rust/core/src/canonical_json.rs similarity index 100% rename from rust/var-core/src/canonical_json.rs rename to rust/core/src/canonical_json.rs diff --git a/rust/var-core/src/cell_diff.rs b/rust/core/src/cell_diff.rs similarity index 100% rename from rust/var-core/src/cell_diff.rs rename to rust/core/src/cell_diff.rs diff --git a/rust/var-core/src/conformance.rs b/rust/core/src/conformance.rs similarity index 100% rename from rust/var-core/src/conformance.rs rename to rust/core/src/conformance.rs diff --git a/rust/var-core/src/diagnostics.rs b/rust/core/src/diagnostics.rs similarity index 100% rename from rust/var-core/src/diagnostics.rs rename to rust/core/src/diagnostics.rs diff --git a/rust/var-core/src/doc_string_diff.rs b/rust/core/src/doc_string_diff.rs similarity index 100% rename from rust/var-core/src/doc_string_diff.rs rename to rust/core/src/doc_string_diff.rs diff --git a/rust/var-core/src/drift.rs b/rust/core/src/drift.rs similarity index 96% rename from rust/var-core/src/drift.rs rename to rust/core/src/drift.rs index bfd8e35e..e2fb09ac 100644 --- a/rust/var-core/src/drift.rs +++ b/rust/core/src/drift.rs @@ -1,5 +1,5 @@ //! Spec drift detection — port of `drift.ts` / `Drift.java`. A paragraph the -//! committed `var.lock.json` baseline recorded as an example that now matches no +//! committed `varar.lock.json` baseline recorded as an example that now matches no //! step. Byte-identical to the other ports (FNV-1a fingerprint, insertion-ordered //! lockfile serializer, Jaccard word-similarity re-identification). @@ -29,7 +29,7 @@ pub struct SpecBaseline { pub examples: Vec, } -/// The whole `var.lock.json`: every spec keyed by its POSIX path. +/// The whole `varar.lock.json`: every spec keyed by its POSIX path. #[derive(Clone, Debug, PartialEq, Eq)] pub struct VarLock { pub version: u32, @@ -44,7 +44,7 @@ pub struct Drifted { pub span: Span, } -/// Persistence port for `var.lock.json`. The core owns the format; adapters move +/// Persistence port for `varar.lock.json`. The core owns the format; adapters move /// only raw text. pub trait BaselineStore { /// The whole lockfile's contents, or `None` when there is no baseline yet. @@ -196,7 +196,7 @@ pub fn reconcile_drift( drifts } -/// Serializes `var.lock.json` deterministically (fixed field order, sorted spec +/// Serializes `varar.lock.json` deterministically (fixed field order, sorted spec /// paths, two-space indent, trailing newline) — NOT [`crate::canonical_json`]. pub fn stringify_var_lock(lock: &VarLock) -> String { let mut sb = String::new(); @@ -264,7 +264,7 @@ fn write_json_string(sb: &mut String, s: &str) { sb.push('"'); } -/// Parses `var.lock.json`; `None` on malformed input (treated as no baseline). +/// Parses `varar.lock.json`; `None` on malformed input (treated as no baseline). pub fn parse_var_lock(text: &str) -> Option { let parsed = JsonReader::new(text).parse_whole()?; let Value::Map(obj) = parsed else { return None }; @@ -309,7 +309,7 @@ fn parse_spec_baseline(value: &Value) -> Option { }) } -/// A tiny recursive-descent JSON reader — enough for `var.lock.json`, returning +/// A tiny recursive-descent JSON reader — enough for `varar.lock.json`, returning /// `None` on malformed input (Java's caught-exception → null). struct JsonReader { chars: Vec, diff --git a/rust/var-core/src/error.rs b/rust/core/src/error.rs similarity index 100% rename from rust/var-core/src/error.rs rename to rust/core/src/error.rs diff --git a/rust/var-core/src/execute.rs b/rust/core/src/execute.rs similarity index 100% rename from rust/var-core/src/execute.rs rename to rust/core/src/execute.rs diff --git a/rust/var-core/src/expression.rs b/rust/core/src/expression.rs similarity index 100% rename from rust/var-core/src/expression.rs rename to rust/core/src/expression.rs diff --git a/rust/var-core/src/failure.rs b/rust/core/src/failure.rs similarity index 100% rename from rust/var-core/src/failure.rs rename to rust/core/src/failure.rs diff --git a/rust/var-core/src/failure_anchor.rs b/rust/core/src/failure_anchor.rs similarity index 100% rename from rust/var-core/src/failure_anchor.rs rename to rust/core/src/failure_anchor.rs diff --git a/rust/var-core/src/handler.rs b/rust/core/src/handler.rs similarity index 100% rename from rust/var-core/src/handler.rs rename to rust/core/src/handler.rs diff --git a/rust/var-core/src/hash.rs b/rust/core/src/hash.rs similarity index 87% rename from rust/var-core/src/hash.rs rename to rust/core/src/hash.rs index 3076c6ab..6523130d 100644 --- a/rust/var-core/src/hash.rs +++ b/rust/core/src/hash.rs @@ -1,5 +1,5 @@ //! FNV-1a (32-bit) change-detector over UTF-16 code units — port of `hash.ts` / -//! `Hash.java`. Byte-identical across every port so `var.lock.json` fingerprints +//! `Hash.java`. Byte-identical across every port so `varar.lock.json` fingerprints //! match. The `fnv1a:` prefix namespaces the algorithm. const FNV_OFFSET: u32 = 0x811c_9dc5; diff --git a/rust/var-core/src/lib.rs b/rust/core/src/lib.rs similarity index 90% rename from rust/var-core/src/lib.rs rename to rust/core/src/lib.rs index 108b57f9..43ce203d 100644 --- a/rust/var-core/src/lib.rs +++ b/rust/core/src/lib.rs @@ -1,5 +1,5 @@ -//! `var-core` — the pure functional core of var, ported from the Java module -//! `com.oselvar.var.core`: parse → match → plan → execute, diffs, drift/hash, +//! `varar-core` — the pure functional core of var, ported from the Java module +//! `dev.varar.core`: parse → match → plan → execute, diffs, drift/hash, //! canonical JSON, and the conformance projections. No filesystem, network, //! time, or test-framework dependencies. //! diff --git a/rust/var-core/src/matcher.rs b/rust/core/src/matcher.rs similarity index 100% rename from rust/var-core/src/matcher.rs rename to rust/core/src/matcher.rs diff --git a/rust/var-core/src/offsets.rs b/rust/core/src/offsets.rs similarity index 100% rename from rust/var-core/src/offsets.rs rename to rust/core/src/offsets.rs diff --git a/rust/var-core/src/param_diff.rs b/rust/core/src/param_diff.rs similarity index 100% rename from rust/var-core/src/param_diff.rs rename to rust/core/src/param_diff.rs diff --git a/rust/var-core/src/parse.rs b/rust/core/src/parse.rs similarity index 100% rename from rust/var-core/src/parse.rs rename to rust/core/src/parse.rs diff --git a/rust/var-core/src/plan.rs b/rust/core/src/plan.rs similarity index 100% rename from rust/var-core/src/plan.rs rename to rust/core/src/plan.rs diff --git a/rust/var-core/src/registry.rs b/rust/core/src/registry.rs similarity index 100% rename from rust/var-core/src/registry.rs rename to rust/core/src/registry.rs diff --git a/rust/var-core/src/result.rs b/rust/core/src/result.rs similarity index 100% rename from rust/var-core/src/result.rs rename to rust/core/src/result.rs diff --git a/rust/var-core/src/scanner.rs b/rust/core/src/scanner.rs similarity index 99% rename from rust/var-core/src/scanner.rs rename to rust/core/src/scanner.rs index c4a6ce52..b4f63929 100644 --- a/rust/var-core/src/scanner.rs +++ b/rust/core/src/scanner.rs @@ -6,7 +6,7 @@ //! The `plugins` parameter carried by `scanner.ts`'s (and the Python port's) //! `scan` signature is intentionally out of scope, following `Scanner.java` — //! no scanner plugin is needed by this port yet; [`scan`] takes no plugins -//! parameter at all. A `var.config.json` naming `scannerPlugins` therefore has +//! parameter at all. A `varar.config.json` naming `scannerPlugins` therefore has //! no core hook here until this is ported. use crate::ast::{ diff --git a/rust/var-core/src/sentences.rs b/rust/core/src/sentences.rs similarity index 100% rename from rust/var-core/src/sentences.rs rename to rust/core/src/sentences.rs diff --git a/rust/var-core/src/span.rs b/rust/core/src/span.rs similarity index 96% rename from rust/var-core/src/span.rs rename to rust/core/src/span.rs index bdfa22ed..fc20cfd9 100644 --- a/rust/var-core/src/span.rs +++ b/rust/core/src/span.rs @@ -1,5 +1,5 @@ //! Source positions/ranges anchored to UTF-16 code-unit offsets (1-based -//! line/column). Port of `var-core/src/span.ts` / `Span.java`. +//! line/column). Port of `varar-core/src/span.ts` / `Span.java`. /// A source range `[start_offset, end_offset)` in UTF-16 code units, with /// 1-based line/column at each end. diff --git a/rust/var-core/src/step_kind.rs b/rust/core/src/step_kind.rs similarity index 100% rename from rust/var-core/src/step_kind.rs rename to rust/core/src/step_kind.rs diff --git a/rust/var-core/src/step_role.rs b/rust/core/src/step_role.rs similarity index 100% rename from rust/var-core/src/step_role.rs rename to rust/core/src/step_role.rs diff --git a/rust/var-core/src/structurer.rs b/rust/core/src/structurer.rs similarity index 100% rename from rust/var-core/src/structurer.rs rename to rust/core/src/structurer.rs diff --git a/rust/var-core/src/table_cells.rs b/rust/core/src/table_cells.rs similarity index 100% rename from rust/var-core/src/table_cells.rs rename to rust/core/src/table_cells.rs diff --git a/rust/var-core/src/value.rs b/rust/core/src/value.rs similarity index 100% rename from rust/var-core/src/value.rs rename to rust/core/src/value.rs diff --git a/rust/var-core/tests/ast_test.rs b/rust/core/tests/ast_test.rs similarity index 99% rename from rust/var-core/tests/ast_test.rs rename to rust/core/tests/ast_test.rs index f787bbe4..50e8ce29 100644 --- a/rust/var-core/tests/ast_test.rs +++ b/rust/core/tests/ast_test.rs @@ -4,11 +4,11 @@ //! (`blockPermitsExactlySevenVariants` / `tableOrFencePermitsExactlyTableAndFence`) //! are dropped: the Rust enums *are* the compiler-enforced closed sets. -use var_core::ast::{ +use varar_core::ast::{ Block, Blockquote, Example, Fence, Heading, ListItem, Paragraph, Row, SegmentOffset, Table, TableOrFence, ThematicBreak, VarDoc, }; -use var_core::span::Span; +use varar_core::span::Span; const SPAN: Span = Span { start_offset: 0, diff --git a/rust/var-core/tests/canonical_json_test.rs b/rust/core/tests/canonical_json_test.rs similarity index 97% rename from rust/var-core/tests/canonical_json_test.rs rename to rust/core/tests/canonical_json_test.rs index de029d25..83d03414 100644 --- a/rust/var-core/tests/canonical_json_test.rs +++ b/rust/core/tests/canonical_json_test.rs @@ -3,8 +3,8 @@ mod common; use common::vmap; -use var_core::canonical_json::canonical_stringify; -use var_core::value::Value; +use varar_core::canonical_json::canonical_stringify; +use varar_core::value::Value; #[test] fn sorts_keys_indents_and_trailing_newline() { diff --git a/rust/var-core/tests/cell_diff_test.rs b/rust/core/tests/cell_diff_test.rs similarity index 94% rename from rust/var-core/tests/cell_diff_test.rs rename to rust/core/tests/cell_diff_test.rs index 4387f79e..135f8284 100644 --- a/rust/var-core/tests/cell_diff_test.rs +++ b/rust/core/tests/cell_diff_test.rs @@ -3,13 +3,13 @@ mod common; use common::{vlist, vmap}; -use var_core::ast::{Block, Table}; -use var_core::cell_diff::{CellDiff, RowCheck, compare_row, compare_table}; -use var_core::error::StepError; -use var_core::offsets::utf16_slice; -use var_core::parse::parse; -use var_core::span::Span; -use var_core::value::Value; +use varar_core::ast::{Block, Table}; +use varar_core::cell_diff::{CellDiff, RowCheck, compare_row, compare_table}; +use varar_core::error::StepError; +use varar_core::offsets::utf16_slice; +use varar_core::parse::parse; +use varar_core::span::Span; +use varar_core::value::Value; const SPAN: Span = Span { start_offset: 0, @@ -85,7 +85,7 @@ fn cell_mismatch_carries_the_cells_and_is_detectable() { let err = StepError::CellMismatch(vec![CellDiff::new("score", SPAN, "9", "6", false)]); assert!(err.as_cell_mismatch().is_some()); assert!( - StepError::Handler(var_core::error::HandlerError::new("x")) + StepError::Handler(varar_core::error::HandlerError::new("x")) .as_cell_mismatch() .is_none() ); diff --git a/rust/var-core/tests/common/mod.rs b/rust/core/tests/common/mod.rs similarity index 94% rename from rust/var-core/tests/common/mod.rs rename to rust/core/tests/common/mod.rs index 18ed5e8d..baf1993a 100644 --- a/rust/var-core/tests/common/mod.rs +++ b/rust/core/tests/common/mod.rs @@ -2,7 +2,7 @@ #![allow(dead_code)] use std::collections::BTreeMap; -use var_core::value::Value; +use varar_core::value::Value; /// Builds a [`Value::Map`] from `(key, value)` pairs (test ergonomics for Java's /// `Map.of(...)`). diff --git a/rust/var-core/tests/conformance_test.rs b/rust/core/tests/conformance_test.rs similarity index 89% rename from rust/var-core/tests/conformance_test.rs rename to rust/core/tests/conformance_test.rs index 9b82a0ec..e8498598 100644 --- a/rust/var-core/tests/conformance_test.rs +++ b/rust/core/tests/conformance_test.rs @@ -1,4 +1,4 @@ -//! Port of `ConformanceTest.java` (the var-core half): the var-doc golden gate +//! Port of `ConformanceTest.java` (the varar-core half): the var-doc golden gate //! over every bundle in the shared corpus, plus the registry-projection unit //! tests. The registry/plan/trace golden gates need per-bundle Rust step //! fixtures and belong to a future `var` facade crate (as in Java, where they @@ -6,13 +6,13 @@ use std::fs; use std::path::{Path, PathBuf}; -use var_core::canonical_json::canonical_stringify; -use var_core::conformance::{parameter_type_names, to_registry_artifact, to_var_doc_artifact}; -use var_core::handler::Handler; -use var_core::parse::parse; -use var_core::registry::{add_step, create_registry, define_parameter_type}; -use var_core::step_kind::StepKind; -use var_core::value::Value; +use varar_core::canonical_json::canonical_stringify; +use varar_core::conformance::{parameter_type_names, to_registry_artifact, to_var_doc_artifact}; +use varar_core::handler::Handler; +use varar_core::parse::parse; +use varar_core::registry::{add_step, create_registry, define_parameter_type}; +use varar_core::step_kind::StepKind; +use varar_core::value::Value; fn bundles_dir() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../../conformance/bundles") diff --git a/rust/var-core/tests/diagnostics_test.rs b/rust/core/tests/diagnostics_test.rs similarity index 85% rename from rust/var-core/tests/diagnostics_test.rs rename to rust/core/tests/diagnostics_test.rs index 93a03708..60ff30ef 100644 --- a/rust/var-core/tests/diagnostics_test.rs +++ b/rust/core/tests/diagnostics_test.rs @@ -1,8 +1,10 @@ //! Port of `DiagnosticsTest.java`. Java's `assertSame` (identity) becomes value //! equality — [`Span`] is a `Copy` value type. -use var_core::diagnostics::{DiagnosticCode, Severity, ambiguous_match, error_fence_without_step}; -use var_core::span::Span; +use varar_core::diagnostics::{ + DiagnosticCode, Severity, ambiguous_match, error_fence_without_step, +}; +use varar_core::span::Span; const SPAN: Span = Span { start_offset: 0, diff --git a/rust/var-core/tests/doc_string_diff_test.rs b/rust/core/tests/doc_string_diff_test.rs similarity index 85% rename from rust/var-core/tests/doc_string_diff_test.rs rename to rust/core/tests/doc_string_diff_test.rs index 13a9a6e8..a74a44af 100644 --- a/rust/var-core/tests/doc_string_diff_test.rs +++ b/rust/core/tests/doc_string_diff_test.rs @@ -1,9 +1,9 @@ //! Port of `DocStringDiffTest.java` / `doc-string-diff.test.ts`. -use var_core::doc_string_diff::{DocStringDiff, compare_doc_string}; -use var_core::error::StepError; -use var_core::span::Span; -use var_core::value::Value; +use varar_core::doc_string_diff::{DocStringDiff, compare_doc_string}; +use varar_core::error::StepError; +use varar_core::span::Span; +use varar_core::value::Value; const SPAN: Span = Span { start_offset: 0, @@ -48,7 +48,7 @@ fn doc_string_mismatch_carries_the_diff_and_is_detectable() { let err = StepError::DocStringMismatch(DocStringDiff::new(SPAN, "hello\n", "bye\n")); assert!(err.as_doc_string_mismatch().is_some()); assert!( - StepError::Handler(var_core::error::HandlerError::new("x")) + StepError::Handler(varar_core::error::HandlerError::new("x")) .as_doc_string_mismatch() .is_none() ); diff --git a/rust/var-core/tests/drift_test.rs b/rust/core/tests/drift_test.rs similarity index 96% rename from rust/var-core/tests/drift_test.rs rename to rust/core/tests/drift_test.rs index 989ff448..027a7067 100644 --- a/rust/var-core/tests/drift_test.rs +++ b/rust/core/tests/drift_test.rs @@ -1,17 +1,17 @@ //! Port of `DriftTest.java` / `drift.test.ts` (unit-gated; drift has no golden). use std::collections::BTreeMap; -use var_core::drift::{ +use varar_core::drift::{ BaselineExample, BaselineStore, Drifted, SpecBaseline, VarLock, derive_spec_baseline, detect_drift, live_examples, message, parse_var_lock, reconcile_drift, stringify_var_lock, }; -use var_core::handler::Handler; -use var_core::hash::hash_source; -use var_core::parse::parse; -use var_core::plan::{ExecutionPlan, plan}; -use var_core::registry::{Registry, add_step, create_registry}; -use var_core::span::Span; -use var_core::step_kind::StepKind; +use varar_core::handler::Handler; +use varar_core::hash::hash_source; +use varar_core::parse::parse; +use varar_core::plan::{ExecutionPlan, plan}; +use varar_core::registry::{Registry, add_step, create_registry}; +use varar_core::span::Span; +use varar_core::step_kind::StepKind; fn reg(with_step: bool) -> Registry { let r = create_registry(); diff --git a/rust/var-core/tests/execute_test.rs b/rust/core/tests/execute_test.rs similarity index 98% rename from rust/var-core/tests/execute_test.rs rename to rust/core/tests/execute_test.rs index 9d598d1d..874dc411 100644 --- a/rust/var-core/tests/execute_test.rs +++ b/rust/core/tests/execute_test.rs @@ -14,19 +14,19 @@ use std::future::Future; use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; -use var_core::diagnostics::{Diagnostic, DiagnosticCode}; -use var_core::error::{HandlerError, StepError}; -use var_core::execute::{ +use varar_core::diagnostics::{Diagnostic, DiagnosticCode}; +use varar_core::error::{HandlerError, StepError}; +use varar_core::execute::{ ExecutePorts, StepObservation, StepOutcome, collect_examples, execute_plan, }; -use var_core::failure::to_failure; -use var_core::handler::{Handler, HandlerReturn}; -use var_core::offsets::utf16_slice; -use var_core::parse::parse; -use var_core::plan::{ExecutionPlan, plan}; -use var_core::registry::{Registry, add_step, create_registry}; -use var_core::step_kind::StepKind; -use var_core::value::Value; +use varar_core::failure::to_failure; +use varar_core::handler::{Handler, HandlerReturn}; +use varar_core::offsets::utf16_slice; +use varar_core::parse::parse; +use varar_core::plan::{ExecutionPlan, plan}; +use varar_core::registry::{Registry, add_step, create_registry}; +use varar_core::step_kind::StepKind; +use varar_core::value::Value; fn int_of(v: &Value) -> i64 { match v { diff --git a/rust/var-core/tests/failure_test.rs b/rust/core/tests/failure_test.rs similarity index 90% rename from rust/var-core/tests/failure_test.rs rename to rust/core/tests/failure_test.rs index 8d826d8d..f3a719a8 100644 --- a/rust/var-core/tests/failure_test.rs +++ b/rust/core/tests/failure_test.rs @@ -3,12 +3,12 @@ //! regex-escape case becomes an exact path-match check. The "message/stack is a //! String" type assertions are dropped (type-level in Rust). -use var_core::cell_diff::CellDiff; -use var_core::doc_string_diff::DocStringDiff; -use var_core::error::{FailureLocation, HandlerError, StepError, StepFailure}; -use var_core::failure::to_failure; -use var_core::result::CellFailure; -use var_core::span::Span; +use varar_core::cell_diff::CellDiff; +use varar_core::doc_string_diff::DocStringDiff; +use varar_core::error::{FailureLocation, HandlerError, StepError, StepFailure}; +use varar_core::failure::to_failure; +use varar_core::result::CellFailure; +use varar_core::span::Span; fn located(error: StepError, path: &str, line: usize) -> StepFailure { StepFailure { diff --git a/rust/var-core/tests/hash_test.rs b/rust/core/tests/hash_test.rs similarity index 89% rename from rust/var-core/tests/hash_test.rs rename to rust/core/tests/hash_test.rs index ebdbce48..c8d9fb2b 100644 --- a/rust/var-core/tests/hash_test.rs +++ b/rust/core/tests/hash_test.rs @@ -1,6 +1,6 @@ //! Port of the FNV-1a vectors from `DriftTest.java` / `hash.test.ts`. -use var_core::hash::hash_source; +use varar_core::hash::hash_source; #[test] fn hash_matches_the_typescript_vectors() { diff --git a/rust/var-core/tests/matcher_test.rs b/rust/core/tests/matcher_test.rs similarity index 93% rename from rust/var-core/tests/matcher_test.rs rename to rust/core/tests/matcher_test.rs index a6bc27d9..8c854fe7 100644 --- a/rust/var-core/tests/matcher_test.rs +++ b/rust/core/tests/matcher_test.rs @@ -1,10 +1,10 @@ //! Port of `MatcherTest.java` / `matcher.test.ts`. -use var_core::handler::Handler; -use var_core::matcher::{ResolvedSteps, find_hits, resolve_hits}; -use var_core::offsets::{utf16_index, utf16_len, utf16_slice}; -use var_core::registry::{Registry, add_step, create_registry}; -use var_core::value::Value; +use varar_core::handler::Handler; +use varar_core::matcher::{ResolvedSteps, find_hits, resolve_hits}; +use varar_core::offsets::{utf16_index, utf16_len, utf16_slice}; +use varar_core::registry::{Registry, add_step, create_registry}; +use varar_core::value::Value; fn reg() -> Registry { let r = create_registry(); @@ -133,7 +133,7 @@ fn param_spans_use_utf16_offsets_across_an_astral_character_no_manual_conversion #[test] fn a_custom_type_with_capture_groups_passes_each_group_to_parse() { use std::rc::Rc; - use var_core::registry::define_parameter_type; + use varar_core::registry::define_parameter_type; let r = define_parameter_type( &create_registry(), "range", @@ -154,7 +154,7 @@ fn a_custom_type_with_capture_groups_passes_each_group_to_parse() { #[test] fn a_custom_type_without_groups_still_receives_the_whole_match() { use std::rc::Rc; - use var_core::registry::define_parameter_type; + use varar_core::registry::define_parameter_type; let r = define_parameter_type( &create_registry(), "airport", diff --git a/rust/var-core/tests/offsets_test.rs b/rust/core/tests/offsets_test.rs similarity index 95% rename from rust/var-core/tests/offsets_test.rs rename to rust/core/tests/offsets_test.rs index 66e1c30d..25415dd6 100644 --- a/rust/var-core/tests/offsets_test.rs +++ b/rust/core/tests/offsets_test.rs @@ -2,7 +2,7 @@ //! the Python port needed. Not a 1:1 of a Java test file; these pin the helpers //! the astral conformance cases (bundles 11/12) depend on. -use var_core::offsets::{byte_index, utf16_index, utf16_len, utf16_slice}; +use varar_core::offsets::{byte_index, utf16_index, utf16_len, utf16_slice}; #[test] fn utf16_len_counts_code_units_ascii_and_astral() { diff --git a/rust/var-core/tests/param_diff_test.rs b/rust/core/tests/param_diff_test.rs similarity index 93% rename from rust/var-core/tests/param_diff_test.rs rename to rust/core/tests/param_diff_test.rs index 8f7da6b3..c5290d67 100644 --- a/rust/var-core/tests/param_diff_test.rs +++ b/rust/core/tests/param_diff_test.rs @@ -3,9 +3,9 @@ mod common; use common::vmap; -use var_core::param_diff::compare_params; -use var_core::span::Span; -use var_core::value::Value; +use varar_core::param_diff::compare_params; +use varar_core::span::Span; +use varar_core::value::Value; const SOURCE: &str = "I should have 3 cukes in my big belly"; diff --git a/rust/var-core/tests/parse_test.rs b/rust/core/tests/parse_test.rs similarity index 93% rename from rust/var-core/tests/parse_test.rs rename to rust/core/tests/parse_test.rs index 99790dfb..471b2a56 100644 --- a/rust/var-core/tests/parse_test.rs +++ b/rust/core/tests/parse_test.rs @@ -1,6 +1,6 @@ //! Port of `ParseTest.java` / `parse.test.ts`. -use var_core::parse::parse; +use varar_core::parse::parse; #[test] fn parse_returns_a_var_doc_whose_examples_come_from_paragraphs_and_carry_the_heading_stack() { diff --git a/rust/var-core/tests/plan_test.rs b/rust/core/tests/plan_test.rs similarity index 97% rename from rust/var-core/tests/plan_test.rs rename to rust/core/tests/plan_test.rs index 27c04e01..938bf932 100644 --- a/rust/var-core/tests/plan_test.rs +++ b/rust/core/tests/plan_test.rs @@ -3,15 +3,15 @@ mod common; use common::vmap; -use var_core::cell_diff::RowCheck; -use var_core::diagnostics::DiagnosticCode; -use var_core::handler::Handler; -use var_core::offsets::utf16_slice; -use var_core::parse::parse; -use var_core::plan::plan; -use var_core::registry::{Registry, add_step, create_registry}; -use var_core::step_kind::StepKind; -use var_core::value::Value; +use varar_core::cell_diff::RowCheck; +use varar_core::diagnostics::DiagnosticCode; +use varar_core::handler::Handler; +use varar_core::offsets::utf16_slice; +use varar_core::parse::parse; +use varar_core::plan::plan; +use varar_core::registry::{Registry, add_step, create_registry}; +use varar_core::step_kind::StepKind; +use varar_core::value::Value; fn reg() -> Registry { let r = create_registry(); @@ -56,7 +56,7 @@ fn step(r: &Registry, expr: &str, file: &str, line: usize) -> Registry { .unwrap() } -fn step_texts(ex: &var_core::plan::PlannedExample) -> Vec { +fn step_texts(ex: &varar_core::plan::PlannedExample) -> Vec { ex.steps.iter().map(|s| s.text.clone()).collect() } diff --git a/rust/var-core/tests/registry_test.rs b/rust/core/tests/registry_test.rs similarity index 94% rename from rust/var-core/tests/registry_test.rs rename to rust/core/tests/registry_test.rs index 4b30cf19..99487a40 100644 --- a/rust/var-core/tests/registry_test.rs +++ b/rust/core/tests/registry_test.rs @@ -3,11 +3,11 @@ //! immutability clause is dropped (Rust values are immutable). use std::rc::Rc; -use var_core::error::RegistryError; -use var_core::handler::Handler; -use var_core::registry::{CustomParameterType, add_step, create_registry, define_parameter_type}; -use var_core::step_kind::StepKind; -use var_core::value::Value; +use varar_core::error::RegistryError; +use varar_core::handler::Handler; +use varar_core::registry::{CustomParameterType, add_step, create_registry, define_parameter_type}; +use varar_core::step_kind::StepKind; +use varar_core::value::Value; #[test] fn create_registry_returns_an_empty_registry_with_default_parameter_types() { diff --git a/rust/var-core/tests/scanner_test.rs b/rust/core/tests/scanner_test.rs similarity index 95% rename from rust/var-core/tests/scanner_test.rs rename to rust/core/tests/scanner_test.rs index c76f809d..86a96b80 100644 --- a/rust/var-core/tests/scanner_test.rs +++ b/rust/core/tests/scanner_test.rs @@ -1,10 +1,10 @@ //! Port of `ScannerTest.java` / `scanner.test.ts`. -use var_core::ast::Block; -use var_core::ast::SegmentOffset; -use var_core::offsets::{utf16_len, utf16_slice}; -use var_core::scanner::scan; -use var_core::span::Span; +use varar_core::ast::Block; +use varar_core::ast::SegmentOffset; +use varar_core::offsets::{utf16_len, utf16_slice}; +use varar_core::scanner::scan; +use varar_core::span::Span; fn kind_of(b: &Block) -> &'static str { match b { @@ -26,7 +26,7 @@ fn slice(source: &str, span: Span) -> &str { utf16_slice(source, span.start_offset, span.end_offset) } -fn first_paragraph(blocks: &[Block]) -> &var_core::ast::Paragraph { +fn first_paragraph(blocks: &[Block]) -> &varar_core::ast::Paragraph { blocks .iter() .find_map(|b| { @@ -39,7 +39,7 @@ fn first_paragraph(blocks: &[Block]) -> &var_core::ast::Paragraph { .unwrap() } -fn first_table(blocks: &[Block]) -> &var_core::ast::Table { +fn first_table(blocks: &[Block]) -> &varar_core::ast::Table { blocks .iter() .find_map(|b| { @@ -52,7 +52,7 @@ fn first_table(blocks: &[Block]) -> &var_core::ast::Table { .unwrap() } -fn first_fence(blocks: &[Block]) -> &var_core::ast::Fence { +fn first_fence(blocks: &[Block]) -> &varar_core::ast::Fence { blocks .iter() .find_map(|b| { @@ -127,7 +127,7 @@ fn scan_strips_the_optional_trailing_hash_marker() { fn scan_groups_consecutive_non_blank_lines_into_a_single_paragraph() { let source = "first line\nsecond line\n\nthird line"; let blocks = scan(source); - let paragraphs: Vec<&var_core::ast::Paragraph> = blocks + let paragraphs: Vec<&varar_core::ast::Paragraph> = blocks .iter() .filter_map(|b| { if let Block::Paragraph(p) = b { diff --git a/rust/var-core/tests/sentences_test.rs b/rust/core/tests/sentences_test.rs similarity index 94% rename from rust/var-core/tests/sentences_test.rs rename to rust/core/tests/sentences_test.rs index cca6c5d8..45b03b75 100644 --- a/rust/var-core/tests/sentences_test.rs +++ b/rust/core/tests/sentences_test.rs @@ -1,8 +1,8 @@ //! Port of `SentencesTest.java` / `sentences.test.ts`. The `resultListIsImmutable` //! case is dropped (Rust `Vec` is owned/immutable). -use var_core::offsets::utf16_len; -use var_core::sentences::{Sentence, split_sentences}; +use varar_core::offsets::utf16_len; +use varar_core::sentences::{Sentence, split_sentences}; fn texts(sentences: &[Sentence]) -> Vec { sentences.iter().map(|s| s.text.clone()).collect() @@ -99,6 +99,6 @@ fn astral_character_keeps_offsets_correct() { assert_eq!(utf16_len("Party time 🎉!"), first.end_offset); assert_eq!( first.text, - var_core::offsets::utf16_slice(text, first.start_offset, first.end_offset) + varar_core::offsets::utf16_slice(text, first.start_offset, first.end_offset) ); } diff --git a/rust/var-core/tests/smoke_test.rs b/rust/core/tests/smoke_test.rs similarity index 100% rename from rust/var-core/tests/smoke_test.rs rename to rust/core/tests/smoke_test.rs diff --git a/rust/var-core/tests/span_test.rs b/rust/core/tests/span_test.rs similarity index 96% rename from rust/var-core/tests/span_test.rs rename to rust/core/tests/span_test.rs index d9054d48..71ec03ac 100644 --- a/rust/var-core/tests/span_test.rs +++ b/rust/core/tests/span_test.rs @@ -1,7 +1,7 @@ //! Port of `SpanTest.java` / `span.test.ts`. -use var_core::offsets::utf16_len; -use var_core::span::Span; +use varar_core::offsets::utf16_len; +use varar_core::span::Span; #[test] fn span_from_offsets_computes_line_and_column_for_a_single_line_source() { diff --git a/rust/var-core/tests/step_role_test.rs b/rust/core/tests/step_role_test.rs similarity index 90% rename from rust/var-core/tests/step_role_test.rs rename to rust/core/tests/step_role_test.rs index 337d9046..8be946d2 100644 --- a/rust/var-core/tests/step_role_test.rs +++ b/rust/core/tests/step_role_test.rs @@ -1,7 +1,7 @@ //! Port of `StepRoleTest.java` / `step-role.test.ts`. -use var_core::step_kind::StepKind; -use var_core::step_role::{Neighbours, infer_step_role}; +use varar_core::step_kind::StepKind; +use varar_core::step_role::{Neighbours, infer_step_role}; #[test] fn no_step_after_the_selection_means_sensor_expectation_last() { diff --git a/rust/var-core/tests/structurer_test.rs b/rust/core/tests/structurer_test.rs similarity index 96% rename from rust/var-core/tests/structurer_test.rs rename to rust/core/tests/structurer_test.rs index 1e0aeb69..3b6c4780 100644 --- a/rust/var-core/tests/structurer_test.rs +++ b/rust/core/tests/structurer_test.rs @@ -1,8 +1,8 @@ //! Port of `StructurerTest.java` / `structurer.test.ts`. -use var_core::ast::Block; -use var_core::scanner::scan; -use var_core::structurer::structure; +use varar_core::ast::Block; +use varar_core::scanner::scan; +use varar_core::structurer::structure; #[test] fn every_paragraph_becomes_a_candidate_example_scoped_by_the_headings_above_it() { @@ -81,7 +81,7 @@ fn orphan_tables_and_fences_are_recorded_on_the_var_doc() { assert_eq!(1, var_doc.orphan_attachments.len()); assert!(matches!( var_doc.orphan_attachments[0], - var_core::ast::TableOrFence::Table(_) + varar_core::ast::TableOrFence::Table(_) )); } diff --git a/rust/var-core/tests/table_cells_test.rs b/rust/core/tests/table_cells_test.rs similarity index 96% rename from rust/var-core/tests/table_cells_test.rs rename to rust/core/tests/table_cells_test.rs index 9464f061..90a7ab14 100644 --- a/rust/var-core/tests/table_cells_test.rs +++ b/rust/core/tests/table_cells_test.rs @@ -2,9 +2,9 @@ //! The `cellsListIsImmutable` case is dropped (Rust `Vec` is owned/immutable by //! construction). -use var_core::offsets::utf16_slice; -use var_core::span::Span; -use var_core::table_cells::parse_row_cells; +use varar_core::offsets::utf16_slice; +use varar_core::span::Span; +use varar_core::table_cells::parse_row_cells; fn slice(source: &str, span: Span) -> &str { utf16_slice(source, span.start_offset, span.end_offset) diff --git a/rust/var-runner/Cargo.toml b/rust/runner/Cargo.toml similarity index 50% rename from rust/var-runner/Cargo.toml rename to rust/runner/Cargo.toml index 243961f1..708aecd3 100644 --- a/rust/var-runner/Cargo.toml +++ b/rust/runner/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "var-runner" +name = "varar-runner" version = "0.0.0" edition = "2024" publish = false [dependencies] -var-core = { path = "../var-core" } -var-config = { path = "../var-config" } +varar-core = { path = "../core" } +varar-config = { path = "../config" } regex = "1" [lib] -name = "var_runner" +name = "varar_runner" path = "src/lib.rs" diff --git a/rust/var-runner/src/baseline_store.rs b/rust/runner/src/baseline_store.rs similarity index 76% rename from rust/var-runner/src/baseline_store.rs rename to rust/runner/src/baseline_store.rs index b8723831..0bff03e3 100644 --- a/rust/var-runner/src/baseline_store.rs +++ b/rust/runner/src/baseline_store.rs @@ -1,9 +1,9 @@ //! The filesystem `BaselineStore`: the committed drift baseline lives at the -//! project root as `var.lock.json`. The core owns the format; this only reads +//! project root as `varar.lock.json`. The core owns the format; this only reads //! and writes the raw text. use std::path::{Path, PathBuf}; -use var_core::drift::BaselineStore; +use varar_core::drift::BaselineStore; pub struct FileBaselineStore { path: PathBuf, @@ -12,7 +12,7 @@ pub struct FileBaselineStore { impl FileBaselineStore { pub fn new(root: &Path) -> FileBaselineStore { FileBaselineStore { - path: root.join("var.lock.json"), + path: root.join("varar.lock.json"), } } } diff --git a/rust/var-runner/src/discovery.rs b/rust/runner/src/discovery.rs similarity index 97% rename from rust/var-runner/src/discovery.rs rename to rust/runner/src/discovery.rs index 36d34960..69be79ae 100644 --- a/rust/var-runner/src/discovery.rs +++ b/rust/runner/src/discovery.rs @@ -3,10 +3,10 @@ use regex::Regex; use std::path::{Path, PathBuf}; -use var_config::VarConfig; +use varar_config::VarConfig; /// Translate a glob (`/**/`, `/**`, `**/`, `**`, `*`, `?`) to an anchored regex. -/// Port of `var_runner.discovery._glob_to_regex`. +/// Port of `varar_runner.discovery._glob_to_regex`. pub fn glob_to_regex(pattern: &str) -> Regex { let chars: Vec = pattern.chars().collect(); let n = chars.len(); diff --git a/rust/var-runner/src/lib.rs b/rust/runner/src/lib.rs similarity index 64% rename from rust/var-runner/src/lib.rs rename to rust/runner/src/lib.rs index 8cb37679..edd995b7 100644 --- a/rust/var-runner/src/lib.rs +++ b/rust/runner/src/lib.rs @@ -1,12 +1,12 @@ -//! `var-runner` — the imperative shell shared by var test-runner adapters. +//! `varar-runner` — the imperative shell shared by var test-runner adapters. //! //! Spec discovery (the shared glob semantics), planning/running examples, -//! failure rendering, and the filesystem `var.lock.json` baseline store for -//! drift. Contains no pipeline logic — it delegates to `var-core`. Steps are +//! failure rendering, and the filesystem `varar.lock.json` baseline store for +//! drift. Contains no pipeline logic — it delegates to `varar-core`. Steps are //! supplied by the caller (Rust compiles step files in; there is no dynamic //! `load_steps`), as a `Registry` plus a context factory. //! -// `run_example` surfaces var-core's `StepFailure` by value, matching that +// `run_example` surfaces varar-core's `StepFailure` by value, matching that // crate's own `#![allow(clippy::result_large_err)]` public-API choice. #![allow(clippy::result_large_err)] diff --git a/rust/var-runner/src/render.rs b/rust/runner/src/render.rs similarity index 89% rename from rust/var-runner/src/render.rs rename to rust/runner/src/render.rs index 354e0a9d..4e6c7550 100644 --- a/rust/var-runner/src/render.rs +++ b/rust/runner/src/render.rs @@ -1,7 +1,7 @@ //! Pure human-readable rendering of a step failure, anchored to the `.md`. -//! Port of `var_runner.render.render_failure`; reuses the core diff payloads. +//! Port of `varar_runner.render.render_failure`; reuses the core diff payloads. -use var_core::error::StepFailure; +use varar_core::error::StepFailure; pub fn render_failure(failure: &StepFailure, _source: &str, path: &str) -> String { let error = &failure.error; diff --git a/rust/var-runner/src/run.rs b/rust/runner/src/run.rs similarity index 86% rename from rust/var-runner/src/run.rs rename to rust/runner/src/run.rs index a3d7f7a6..82d807a3 100644 --- a/rust/var-runner/src/run.rs +++ b/rust/runner/src/run.rs @@ -1,12 +1,12 @@ //! Planning and running examples, plus the adapter display-name rule. use std::collections::HashMap; -use var_core::error::StepFailure; -use var_core::execute::{ExecutePorts, collect_examples}; -use var_core::parse::parse; -use var_core::plan::{ExecutionPlan, plan}; -use var_core::registry::Registry; -use var_core::value::Value; +use varar_core::error::StepFailure; +use varar_core::execute::{ExecutePorts, collect_examples}; +use varar_core::parse::parse; +use varar_core::plan::{ExecutionPlan, plan}; +use varar_core::registry::Registry; +use varar_core::value::Value; /// Parse + plan one spec. pub fn plan_spec(name: &str, source: &str, registry: &Registry) -> ExecutionPlan { diff --git a/rust/var-runner/tests/runner.rs b/rust/runner/tests/runner.rs similarity index 84% rename from rust/var-runner/tests/runner.rs rename to rust/runner/tests/runner.rs index 0cac2e7b..a8b18792 100644 --- a/rust/var-runner/tests/runner.rs +++ b/rust/runner/tests/runner.rs @@ -2,15 +2,15 @@ //! filesystem baseline store driving drift reconciliation. use std::path::PathBuf; -use var_config::VarConfig; -use var_core::drift::{BaselineStore, reconcile_drift}; -use var_core::handler::Handler; -use var_core::parse::parse; -use var_core::plan::plan; -use var_core::registry::{add_step, create_registry}; -use var_core::step_kind::StepKind; -use var_runner::discovery::glob_to_regex; -use var_runner::{FileBaselineStore, find_specs}; +use varar_config::VarConfig; +use varar_core::drift::{BaselineStore, reconcile_drift}; +use varar_core::handler::Handler; +use varar_core::parse::parse; +use varar_core::plan::plan; +use varar_core::registry::{add_step, create_registry}; +use varar_core::step_kind::StepKind; +use varar_runner::discovery::glob_to_regex; +use varar_runner::{FileBaselineStore, find_specs}; fn tmp(name: &str) -> PathBuf { let dir = std::env::temp_dir().join(format!("var-runner-{}-{name}", std::process::id())); @@ -92,6 +92,6 @@ fn baseline_store_round_trips_and_reconcile_writes_lock() { // Clean run: no drift, and the baseline is written. let drifts = reconcile_drift(&mut store, "hi.md", source, &doc, &execution, false); assert!(drifts.is_empty()); - assert!(store.read().is_some(), "var.lock.json should be written"); - assert!(root.join("var.lock.json").is_file()); + assert!(store.read().is_some(), "varar.lock.json should be written"); + assert!(root.join("varar.lock.json").is_file()); } diff --git a/rust/var-cargotest/Cargo.toml b/rust/var-cargotest/Cargo.toml deleted file mode 100644 index 60d26df0..00000000 --- a/rust/var-cargotest/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "var-cargotest" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -var-core = { path = "../var-core" } -var-config = { path = "../var-config" } -var-runner = { path = "../var-runner" } -libtest-mimic = "0.8" - -[lib] -name = "var_cargotest" -path = "src/lib.rs" diff --git a/rust/var/Cargo.toml b/rust/varar/Cargo.toml similarity index 72% rename from rust/var/Cargo.toml rename to rust/varar/Cargo.toml index 07e4a665..1509518d 100644 --- a/rust/var/Cargo.toml +++ b/rust/varar/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "var" +name = "varar" version = "0.0.0" edition = "2024" publish = false [dependencies] -var-core = { path = "../var-core" } +varar-core = { path = "../core" } [lib] -name = "var" +name = "varar" path = "src/lib.rs" [[test]] diff --git a/rust/var/src/lib.rs b/rust/varar/src/lib.rs similarity index 62% rename from rust/var/src/lib.rs rename to rust/varar/src/lib.rs index b1dcb0f9..ded7049c 100644 --- a/rust/var/src/lib.rs +++ b/rust/varar/src/lib.rs @@ -1,23 +1,23 @@ -//! `var` — the author facade over [`var_core`]. +//! `varar` — the author facade over [`varar_core`]. //! //! Rust uses the **injected-Registrar** author model (ADR 0006): a step file //! exposes a `register(Registry) -> Registry` that adds its steps explicitly, //! rather than the module-scope accumulator TypeScript/Python use. There is //! therefore no `defineState`/`steps()` side-effecting global here — the -//! "author API" is `var_core::registry` plus the handler/value types, curated +//! "author API" is `varar_core::registry` plus the handler/value types, curated //! into a single import surface. This crate is also where the //! registry/plan/trace conformance gates live (see `tests/conformance.rs`), -//! mirroring the Java `var` module: they need both `var-core`'s pipeline and +//! mirroring the Java `var` module: they need both `varar-core`'s pipeline and //! the author surface every bundle fixture is written against. mod steps; pub use steps::{IntoHandler, Steps}; -pub use var_core::error::HandlerError; -pub use var_core::handler::{Handler, HandlerReturn, StepReturn}; -pub use var_core::registry::{ +pub use varar_core::error::HandlerError; +pub use varar_core::handler::{Handler, HandlerReturn, StepReturn}; +pub use varar_core::registry::{ CustomParameterType, FormatFn, ParseFn, Registry, StepRegistration, add_step, create_registry, define_parameter_type, define_parameter_type_with_format, }; -pub use var_core::step_kind::StepKind; -pub use var_core::value::Value; +pub use varar_core::step_kind::StepKind; +pub use varar_core::value::Value; diff --git a/rust/var/src/steps.rs b/rust/varar/src/steps.rs similarity index 95% rename from rust/var/src/steps.rs rename to rust/varar/src/steps.rs index 41d5bad7..722f464f 100644 --- a/rust/var/src/steps.rs +++ b/rust/varar/src/steps.rs @@ -1,18 +1,18 @@ -//! The ergonomic author API: a `Steps` builder over `var-core`'s registry, so +//! The ergonomic author API: a `Steps` builder over `varar-core`'s registry, so //! step definitions read as `s.stimulus(expr, …)` / `s.sensor(expr, …)` — the //! call name IS the kind, matching every other port (and what the LSP/ //! tree-sitter dialect extracts). Mirrors the JVM `StateBinder`. //! -//! The builder owns a `Registry` and folds each definition in with `var-core`'s +//! The builder owns a `Registry` and folds each definition in with `varar-core`'s //! pure `add_step` / `define_parameter_type*`; nothing global is mutated. -use var_core::handler::{Handler, HandlerReturn}; -use var_core::registry::{ +use varar_core::handler::{Handler, HandlerReturn}; +use varar_core::registry::{ FormatFn, ParseFn, Registry, add_step, create_registry, define_parameter_type, define_parameter_type_with_format, }; -use var_core::step_kind::StepKind; -use var_core::value::Value; +use varar_core::step_kind::StepKind; +use varar_core::value::Value; /// Converts an author's bare closure into a [`Handler`], inferring the arity — /// and thus each `Value` parameter — from the closure itself. This is what lets diff --git a/rust/var/tests/conformance.rs b/rust/varar/tests/conformance.rs similarity index 94% rename from rust/var/tests/conformance.rs rename to rust/varar/tests/conformance.rs index 4fb968c2..207ff46c 100644 --- a/rust/var/tests/conformance.rs +++ b/rust/varar/tests/conformance.rs @@ -1,5 +1,5 @@ //! Registry / plan / trace conformance gates — the three stages deferred from -//! `var-core` (which gates only var-doc). Mirrors the Java `var` module's +//! `varar-core` (which gates only var-doc). Mirrors the Java `var` module's //! `ConformanceTest`: for every bundle in the shared corpus, load its Rust step //! fixture, build the registry, and assert the registry/plan/trace artifacts //! byte-for-byte against the committed goldens. @@ -13,12 +13,12 @@ use std::fs; use std::path::{Path, PathBuf}; -use var::{Registry, create_registry}; -use var_core::canonical_json::canonical_stringify; -use var_core::conformance::{run_conformance, to_plan_artifact, to_registry_artifact}; -use var_core::parse::parse; -use var_core::plan::plan; -use var_core::value::Value; +use varar::{Registry, create_registry}; +use varar_core::canonical_json::canonical_stringify; +use varar_core::conformance::{run_conformance, to_plan_artifact, to_registry_artifact}; +use varar_core::parse::parse; +use varar_core::plan::plan; +use varar_core::value::Value; // Fixtures live in the shared corpus (siblings of every `*.steps.ts`), pulled // in by path. Declared at the test's top level so the path base is diff --git a/scripts/coverage-summary.sh b/scripts/coverage-summary.sh index 044d43d1..e7a95f35 100755 --- a/scripts/coverage-summary.sh +++ b/scripts/coverage-summary.sh @@ -133,7 +133,7 @@ build_badge() { rust) wf=rust ;; *) wf="$1" ;; esac - printf '[![Build](https://github.com/oselvar/var/actions/workflows/%s.yml/badge.svg?branch=main)](https://github.com/oselvar/var/actions/workflows/%s.yml)' "$wf" "$wf" + printf '[![Build](https://github.com/oselvar/varar/actions/workflows/%s.yml/badge.svg?branch=main)](https://github.com/oselvar/varar/actions/workflows/%s.yml)' "$wf" "$wf" } TABLE=$(mktemp) diff --git a/typescript/knip.json b/typescript/knip.json index 6c880e9c..145c9883 100644 --- a/typescript/knip.json +++ b/typescript/knip.json @@ -3,28 +3,28 @@ "ignoreExportsUsedInFile": true, "ignoreWorkspaces": ["packages/cucumber"], "workspaces": { - "packages/var-config": { + "packages/config": { "project": ["src/**/*.ts", "tests/**/*.ts"] }, - "packages/var-core": { + "packages/core": { "project": ["src/**/*.ts", "tests/**/*.ts"] }, - "packages/var-cli": { + "packages/cli": { "project": ["src/**/*.ts", "tests/**/*.ts"], - "ignoreDependencies": ["@oselvar/var"] + "ignoreDependencies": ["@varar/varar"] }, - "packages/var-runner": { + "packages/runner": { "project": ["src/**/*.ts", "tests/**/*.ts"] }, - "packages/var-vitest": { + "packages/vitest": { "project": ["src/**/*.ts", "tests/**/*.ts"], "ignoreDependencies": ["tree-sitter-typescript"] }, - "packages/var": { + "packages/varar": { "entry": ["tests/**/*.test.ts", "../../../conformance/bundles/**/*.steps.ts"], "project": ["src/**/*.ts", "tests/**/*.ts", "../../../conformance/bundles/**/*.ts"] }, - "packages/var-language": { + "packages/language": { "project": ["src/**/*.ts", "tests/**/*.ts"], "ignoreDependencies": [ "@tree-sitter-grammars/tree-sitter-kotlin", @@ -35,7 +35,7 @@ "tree-sitter-typescript" ] }, - "packages/var-lsp": { + "packages/lsp": { "project": ["src/**/*.ts", "tests/**/*.ts"], "ignoreDependencies": [ "@tree-sitter-grammars/tree-sitter-kotlin", @@ -46,13 +46,13 @@ "tree-sitter-typescript" ] }, - "packages/var-vscode": { + "packages/vscode": { "project": ["src/**/*.ts"] }, "../examples/typescript-vitest": { "entry": ["steps/*.steps.ts"], "project": ["**/*.ts"], - "ignoreDependencies": ["@oselvar/var-core"] + "ignoreDependencies": ["@varar/core"] } }, "ignore": ["**/tests/fixtures/**", "packages/website/drafts/**"] diff --git a/typescript/package.json b/typescript/package.json index dd498e7f..b83b7926 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -1,5 +1,5 @@ { - "name": "oselvar-var", + "name": "varar-monorepo", "version": "0.0.0", "private": true, "type": "module", @@ -8,7 +8,7 @@ }, "packageManager": "pnpm@9.15.9", "scripts": { - "build": "pnpm -r --filter '!@oselvar/website' --filter '!@oselvar/website' build", + "build": "pnpm -r --filter '!@varar/website' --filter '!@varar/website' build", "typecheck": "tsc -p tsconfig.tests.json", "test": "vitest run", "test:coverage": "vitest run --coverage", @@ -18,7 +18,7 @@ "format": "biome format --write .", "knip": "knip", "jscpd": "jscpd packages", - "install:vscode": "pnpm -r --filter @oselvar/var-lsp --filter oselvar-var build && node scripts/install-vscode.mjs", + "install:vscode": "pnpm -r --filter @varar/lsp --filter varar build && node scripts/install-vscode.mjs", "check": "pnpm lint:fix && pnpm typecheck && pnpm test:coverage && pnpm knip && pnpm jscpd" }, "devDependencies": { diff --git a/typescript/packages/cli/README.md b/typescript/packages/cli/README.md new file mode 100644 index 00000000..3d1f004c --- /dev/null +++ b/typescript/packages/cli/README.md @@ -0,0 +1,4 @@ +# @varar/cli + +The `var` command-line runner for Varar specs: `varar run`, `varar lint`, and `varar init`. +The imperative shell around `@varar/core`. diff --git a/typescript/packages/var-cli/package.json b/typescript/packages/cli/package.json similarity index 68% rename from typescript/packages/var-cli/package.json rename to typescript/packages/cli/package.json index e680f8e9..0660674c 100644 --- a/typescript/packages/var-cli/package.json +++ b/typescript/packages/cli/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/var-cli", + "name": "@varar/cli", "version": "0.4.2", "type": "module", "exports": { @@ -11,7 +11,7 @@ "main": "./src/index.ts", "types": "./src/index.ts", "bin": { - "var": "./dist/bin.js" + "varar": "./dist/bin.js" }, "files": [ "dist", @@ -22,12 +22,12 @@ "test": "vitest run" }, "dependencies": { - "@oselvar/var-config": "workspace:*", - "@oselvar/var-core": "workspace:*", - "@oselvar/var-runner": "workspace:*" + "@varar/config": "workspace:*", + "@varar/core": "workspace:*", + "@varar/runner": "workspace:*" }, "devDependencies": { - "@oselvar/var": "workspace:*" + "@varar/varar": "workspace:*" }, "publishConfig": { "exports": { @@ -42,7 +42,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", - "directory": "typescript/packages/var-cli" + "url": "git+https://github.com/oselvar/varar.git", + "directory": "typescript/packages/cli" } } diff --git a/typescript/packages/var-cli/src/argv.ts b/typescript/packages/cli/src/argv.ts similarity index 100% rename from typescript/packages/var-cli/src/argv.ts rename to typescript/packages/cli/src/argv.ts diff --git a/typescript/packages/var-cli/src/bin.ts b/typescript/packages/cli/src/bin.ts similarity index 70% rename from typescript/packages/var-cli/src/bin.ts rename to typescript/packages/cli/src/bin.ts index 83024a1d..d063ff40 100644 --- a/typescript/packages/var-cli/src/bin.ts +++ b/typescript/packages/cli/src/bin.ts @@ -20,13 +20,13 @@ async function main(): Promise { case '-h': process.stdout.write( [ - 'var — markdown-native BDD', + 'varar — markdown-native BDD', '', 'Usage:', - ' var run [globs] run markdown spec examples (no test runner)', - ' var run --update accept drift and re-record var.lock.json', - ' var lint [globs] check for missing/ambiguous/orphan steps', - ' var init scaffold a new project', + ' varar run [globs] run markdown spec examples (no test runner)', + ' varar run --update accept drift and re-record varar.lock.json', + ' varar lint [globs] check for missing/ambiguous/orphan steps', + ' varar init scaffold a new project', '', ].join('\n'), ) @@ -47,12 +47,12 @@ async function main(): Promise { break } default: - process.stderr.write(`var: unknown command "${parsed.command}". Try \`var help\`.\n`) + process.stderr.write(`varar: unknown command "${parsed.command}". Try \`varar help\`.\n`) process.exitCode = 1 } } main().catch((err: unknown) => { - process.stderr.write(`var: ${err instanceof Error ? err.message : String(err)}\n`) + process.stderr.write(`varar: ${err instanceof Error ? err.message : String(err)}\n`) process.exitCode = 1 }) diff --git a/typescript/packages/var-cli/src/index.ts b/typescript/packages/cli/src/index.ts similarity index 100% rename from typescript/packages/var-cli/src/index.ts rename to typescript/packages/cli/src/index.ts diff --git a/typescript/packages/var-cli/src/init.ts b/typescript/packages/cli/src/init.ts similarity index 73% rename from typescript/packages/var-cli/src/init.ts rename to typescript/packages/cli/src/init.ts index fee396f7..8d8970fd 100644 --- a/typescript/packages/var-cli/src/init.ts +++ b/typescript/packages/cli/src/init.ts @@ -2,8 +2,8 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' const CONFIG = `{ - "docs": { "include": ["var-examples/**/*.md"], "exclude": [] }, - "steps": ["var-examples/**/*.steps.ts"] + "docs": { "include": ["varar-examples/**/*.md"], "exclude": [] }, + "steps": ["varar-examples/**/*.steps.ts"] } ` @@ -16,7 +16,7 @@ The answer to the great question of life, the universe and everything is 42. It was a tough assignment. ` -const EXAMPLE_STEPS = `import { steps } from '@oselvar/var' +const EXAMPLE_STEPS = `import { steps } from '@varar/varar' const { sensor } = steps() @@ -32,9 +32,9 @@ export type InitResult = { readonly exitCode: number } export async function runInit(opts: InitOptions): Promise { const files: Array<{ readonly relPath: string; readonly content: string }> = [ - { relPath: 'var.config.json', content: CONFIG }, - { relPath: 'var-examples/deep-thought.md', content: EXAMPLE_MD }, - { relPath: 'var-examples/steps/deep-thought.steps.ts', content: EXAMPLE_STEPS }, + { relPath: 'varar.config.json', content: CONFIG }, + { relPath: 'varar-examples/deep-thought.md', content: EXAMPLE_MD }, + { relPath: 'varar-examples/steps/deep-thought.steps.ts', content: EXAMPLE_STEPS }, ] for (const f of files) { const target = join(opts.cwd, f.relPath) diff --git a/typescript/packages/var-cli/src/lint.ts b/typescript/packages/cli/src/lint.ts similarity index 92% rename from typescript/packages/var-cli/src/lint.ts rename to typescript/packages/cli/src/lint.ts index 39bb5d01..d743921a 100644 --- a/typescript/packages/var-cli/src/lint.ts +++ b/typescript/packages/cli/src/lint.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'node:fs' -import { findFiles, loadVarConfig } from '@oselvar/var-config' -import { createRegistry, parse, plan } from '@oselvar/var-core' +import { findFiles, loadVarConfig } from '@varar/config' +import { createRegistry, parse, plan } from '@varar/core' export type LintOptions = { readonly cwd: string @@ -22,7 +22,7 @@ type Item = { export async function runLint(opts: LintOptions): Promise { const cfg = await loadVarConfig(opts.cwd) - // A CLI `--globs` override is include-only; excludes live in var.config.json. + // A CLI `--globs` override is include-only; excludes live in varar.config.json. const varGlobs = opts.globs && opts.globs.length > 0 ? { include: opts.globs, exclude: [] } : cfg.docs const files = findFiles(opts.cwd, varGlobs.include, varGlobs.exclude) diff --git a/typescript/packages/var-cli/src/run.ts b/typescript/packages/cli/src/run.ts similarity index 94% rename from typescript/packages/var-cli/src/run.ts rename to typescript/packages/cli/src/run.ts index c53155b6..315dd5e8 100644 --- a/typescript/packages/var-cli/src/run.ts +++ b/typescript/packages/cli/src/run.ts @@ -1,8 +1,8 @@ import { readFileSync } from 'node:fs' import { relative, sep } from 'node:path' -import { findFiles, loadVarConfig } from '@oselvar/var-config' -import { type Diagnostic, driftDiagnostics, reconcileDrift } from '@oselvar/var-core' -import { createFileBaselineStore, examplesWithRuns, loadSteps, planSpec } from '@oselvar/var-runner' +import { findFiles, loadVarConfig } from '@varar/config' +import { type Diagnostic, driftDiagnostics, reconcileDrift } from '@varar/core' +import { createFileBaselineStore, examplesWithRuns, loadSteps, planSpec } from '@varar/runner' export type RunOptions = { readonly cwd: string @@ -18,7 +18,7 @@ export type RunResult = { readonly exitCode: number } export async function runRun(opts: RunOptions): Promise { const cfg = await loadVarConfig(opts.cwd) - // A CLI `--globs` override is include-only; excludes live in var.config.json. + // A CLI `--globs` override is include-only; excludes live in varar.config.json. const varGlobs = opts.globs && opts.globs.length > 0 ? { include: opts.globs, exclude: [] } : cfg.docs const varFiles = findFiles(opts.cwd, varGlobs.include, varGlobs.exclude) @@ -62,7 +62,7 @@ export async function runRun(opts: RunOptions): Promise { } // Reconcile drift against the committed baseline. On a clean run this - // records/updates var.lock.json; an unacknowledged drift is reported as an + // records/updates varar.lock.json; an unacknowledged drift is reported as an // error diagnostic (non-zero exit) and leaves the baseline untouched. const specPath = rel.split(sep).join('/') const drifts = await reconcileDrift({ diff --git a/typescript/packages/var-cli/tests/argv.test.ts b/typescript/packages/cli/tests/argv.test.ts similarity index 100% rename from typescript/packages/var-cli/tests/argv.test.ts rename to typescript/packages/cli/tests/argv.test.ts diff --git a/typescript/packages/var-cli/tests/drift.test.ts b/typescript/packages/cli/tests/drift.test.ts similarity index 87% rename from typescript/packages/var-cli/tests/drift.test.ts rename to typescript/packages/cli/tests/drift.test.ts index a3b7bd3d..28ae4c71 100644 --- a/typescript/packages/var-cli/tests/drift.test.ts +++ b/typescript/packages/cli/tests/drift.test.ts @@ -8,14 +8,14 @@ const HERE = dirname(fileURLToPath(import.meta.url)) const BIN_TS = resolve(HERE, '..', 'src', 'bin.ts') // The temp project lives INSIDE the workspace (under tests/fixtures) so its -// steps file can `import { steps } from '@oselvar/var'` — Node resolves +// steps file can `import { steps } from '@varar/varar'` — Node resolves // that up the tree to the workspace's node_modules. A temp dir in the OS tmp // root could not. let dir: string beforeEach(() => { dir = mkdtempSync(join(HERE, 'fixtures', 'drift-tmp-')) writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), JSON.stringify({ docs: { include: ['*.md'], exclude: [] }, steps: ['*.steps.ts'], @@ -24,7 +24,7 @@ beforeEach(() => { // A step that matches "I open the vault" but NOT "The vault is sealed". writeFileSync( join(dir, 'vault.steps.ts'), - "import { steps } from '@oselvar/var'\n" + + "import { steps } from '@varar/varar'\n" + 'const { stimulus } = steps(() => ({}))\n' + "stimulus('I open the vault', () => ({}))\n", ) @@ -39,11 +39,11 @@ function run(args: ReadonlyArray) { function writeBaseline(examples: ReadonlyArray<{ name: string; line: number }>) { const lock = { version: 1, specs: { 'vault.md': { sourceHash: 'fnv1a:00000000', examples } } } - writeFileSync(join(dir, 'var.lock.json'), `${JSON.stringify(lock, null, 2)}\n`) + writeFileSync(join(dir, 'varar.lock.json'), `${JSON.stringify(lock, null, 2)}\n`) } function lock(): { specs: Record } { - return JSON.parse(readFileSync(join(dir, 'var.lock.json'), 'utf8')) + return JSON.parse(readFileSync(join(dir, 'varar.lock.json'), 'utf8')) } test('a first run records the baseline and exits 0', () => { @@ -57,13 +57,13 @@ test('a paragraph that stopped matching drifts: exits 1, baseline preserved', () // The baseline says this paragraph was an example; now it matches no step. writeFileSync(join(dir, 'vault.md'), 'The vault is sealed.\n') writeBaseline([{ name: 'The vault is sealed', line: 1 }]) - const before = readFileSync(join(dir, 'var.lock.json'), 'utf8') + const before = readFileSync(join(dir, 'varar.lock.json'), 'utf8') const r = run(['run']) expect(r.status).toBe(1) expect(r.stderr).toContain('drift') expect(r.stderr).toContain('The vault is sealed') // Unacknowledged drift leaves the baseline untouched (stays red). - expect(readFileSync(join(dir, 'var.lock.json'), 'utf8')).toBe(before) + expect(readFileSync(join(dir, 'varar.lock.json'), 'utf8')).toBe(before) }) test('--update accepts the drift and re-records the baseline', () => { diff --git a/typescript/packages/var-cli/tests/e2e.test.ts b/typescript/packages/cli/tests/e2e.test.ts similarity index 95% rename from typescript/packages/var-cli/tests/e2e.test.ts rename to typescript/packages/cli/tests/e2e.test.ts index 3a535efc..a2b9e48d 100644 --- a/typescript/packages/var-cli/tests/e2e.test.ts +++ b/typescript/packages/cli/tests/e2e.test.ts @@ -22,7 +22,7 @@ describe('var CLI (source)', () => { try { const r = run(['init'], dir) expect(r.status).toBe(0) - expect(readFileSync(join(dir, 'var.config.json'), 'utf8')).toContain('docs') + expect(readFileSync(join(dir, 'varar.config.json'), 'utf8')).toContain('docs') } finally { rmSync(dir, { recursive: true, force: true }) } diff --git a/typescript/packages/var-cli/tests/fixtures/run-basic/hello.md b/typescript/packages/cli/tests/fixtures/run-basic/hello.md similarity index 100% rename from typescript/packages/var-cli/tests/fixtures/run-basic/hello.md rename to typescript/packages/cli/tests/fixtures/run-basic/hello.md diff --git a/typescript/packages/var-cli/tests/fixtures/run-basic/hello.steps.ts b/typescript/packages/cli/tests/fixtures/run-basic/hello.steps.ts similarity index 86% rename from typescript/packages/var-cli/tests/fixtures/run-basic/hello.steps.ts rename to typescript/packages/cli/tests/fixtures/run-basic/hello.steps.ts index c9fafb3f..3f93f97f 100644 --- a/typescript/packages/var-cli/tests/fixtures/run-basic/hello.steps.ts +++ b/typescript/packages/cli/tests/fixtures/run-basic/hello.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus, sensor } = steps(() => ({ greeting: '' })) diff --git a/typescript/packages/cli/tests/fixtures/run-basic/var.lock.json b/typescript/packages/cli/tests/fixtures/run-basic/var.lock.json new file mode 100644 index 00000000..30e4b032 --- /dev/null +++ b/typescript/packages/cli/tests/fixtures/run-basic/var.lock.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "specs": { + "hello.md": { + "sourceHash": "fnv1a:c95384a6", + "examples": [ + { + "name": "When I greet \"Aslak\". Then the greeting is \"Hello, Aslak!\"", + "line": 3 + }, + { + "name": "When I greet \"world\". Then the greeting is \"wrong\"", + "line": 5 + } + ] + } + } +} diff --git a/typescript/packages/var-cli/tests/fixtures/run-basic/var.config.json b/typescript/packages/cli/tests/fixtures/run-basic/varar.config.json similarity index 100% rename from typescript/packages/var-cli/tests/fixtures/run-basic/var.config.json rename to typescript/packages/cli/tests/fixtures/run-basic/varar.config.json diff --git a/typescript/packages/var-cli/tests/init.test.ts b/typescript/packages/cli/tests/init.test.ts similarity index 65% rename from typescript/packages/var-cli/tests/init.test.ts rename to typescript/packages/cli/tests/init.test.ts index 54e96c35..a62aee9d 100644 --- a/typescript/packages/var-cli/tests/init.test.ts +++ b/typescript/packages/cli/tests/init.test.ts @@ -5,18 +5,18 @@ import { expect, test } from 'vitest' import languages from '../../../../languages.json' with { type: 'json' } import { runInit } from '../src/init.ts' -test('scaffolds var.config.json and an example .md + steps file', async () => { +test('scaffolds varar.config.json and an example .md + steps file', async () => { const dir = mkdtempSync(join(tmpdir(), 'var-init-')) try { const result = await runInit({ cwd: dir, writeStdout: () => {} }) expect(result.exitCode).toBe(0) - expect(existsSync(join(dir, 'var.config.json'))).toBe(true) - expect(existsSync(join(dir, 'var-examples/deep-thought.md'))).toBe(true) - expect(existsSync(join(dir, 'var-examples/steps/deep-thought.steps.ts'))).toBe(true) - const exampleMd = readFileSync(join(dir, 'var-examples/deep-thought.md'), 'utf8') + expect(existsSync(join(dir, 'varar.config.json'))).toBe(true) + expect(existsSync(join(dir, 'varar-examples/deep-thought.md'))).toBe(true) + expect(existsSync(join(dir, 'varar-examples/steps/deep-thought.steps.ts'))).toBe(true) + const exampleMd = readFileSync(join(dir, 'varar-examples/deep-thought.md'), 'utf8') // The scaffolded spec is plain prose — no Given/When/Then keyword ceremony. expect(exampleMd).not.toMatch(/^\s*(Given|When|Then)\b/m) - const stepsTs = readFileSync(join(dir, 'var-examples/steps/deep-thought.steps.ts'), 'utf8') + const stepsTs = readFileSync(join(dir, 'varar-examples/steps/deep-thought.steps.ts'), 'utf8') expect(stepsTs).toContain('steps') expect(stepsTs).toContain('sensor(') expect(stepsTs).toContain('=> 42') @@ -33,7 +33,7 @@ test('the scaffolded config uses the steps glob declared for TypeScript in langu const dir = mkdtempSync(join(tmpdir(), 'var-init-manifest-')) try { await runInit({ cwd: dir, writeStdout: () => {} }) - const config = JSON.parse(readFileSync(join(dir, 'var.config.json'), 'utf8')) + const config = JSON.parse(readFileSync(join(dir, 'varar.config.json'), 'utf8')) expect(config.steps).toContain(ts?.stepsGlob) expect(ts?.stepsGlob.endsWith(ts.ext)).toBe(true) } finally { @@ -41,16 +41,18 @@ test('the scaffolded config uses the steps glob declared for TypeScript in langu } }) -test('refuses to overwrite an existing var.config.json; reports which files were skipped', async () => { +test('refuses to overwrite an existing varar.config.json; reports which files were skipped', async () => { const dir = mkdtempSync(join(tmpdir(), 'var-init-conflict-')) try { - writeFileSync(join(dir, 'var.config.json'), '{ "docs": { "include": [] } }') + writeFileSync(join(dir, 'varar.config.json'), '{ "docs": { "include": [] } }') const captured: string[] = [] const result = await runInit({ cwd: dir, writeStdout: (s) => captured.push(s) }) expect(result.exitCode).toBe(0) - expect(readFileSync(join(dir, 'var.config.json'), 'utf8')).toBe('{ "docs": { "include": [] } }') + expect(readFileSync(join(dir, 'varar.config.json'), 'utf8')).toBe( + '{ "docs": { "include": [] } }', + ) expect(captured.join('')).toContain('skipped') - expect(captured.join('')).toContain('var.config.json') + expect(captured.join('')).toContain('varar.config.json') } finally { rmSync(dir, { recursive: true, force: true }) } diff --git a/typescript/packages/var-cli/tests/lint.test.ts b/typescript/packages/cli/tests/lint.test.ts similarity index 94% rename from typescript/packages/var-cli/tests/lint.test.ts rename to typescript/packages/cli/tests/lint.test.ts index 97b47e84..16725acd 100644 --- a/typescript/packages/var-cli/tests/lint.test.ts +++ b/typescript/packages/cli/tests/lint.test.ts @@ -22,7 +22,7 @@ test('exit code 0 when no diagnostics found', async () => { }) test('a standalone table or fenced code block is not a lint error', async () => { // Tables and fenced code blocks that do not attach to a step are valid - // Markdown content, not mistakes — `var lint` stays quiet about them. + // Markdown content, not mistakes — `varar lint` stays quiet about them. const dir = mkdtempSync(join(tmpdir(), 'var-lint-text-')) try { writeFileSync(join(dir, 'a.md'), '# A\n\n```js\nx=1\n```\n\n| a | b |\n|---|---|\n| 1 | 2 |\n') diff --git a/typescript/packages/var-cli/tests/run.test.ts b/typescript/packages/cli/tests/run.test.ts similarity index 95% rename from typescript/packages/var-cli/tests/run.test.ts rename to typescript/packages/cli/tests/run.test.ts index 6ee2d5fe..1e5a43ec 100644 --- a/typescript/packages/var-cli/tests/run.test.ts +++ b/typescript/packages/cli/tests/run.test.ts @@ -10,7 +10,7 @@ const FIXTURES = resolve(HERE, 'fixtures') function run(args: ReadonlyArray, cwd: string) { // Node runs the TS source directly via native type stripping. Filter stderr // of Node's one-time `ExperimentalWarning: globSync` notice (emitted by - // @oselvar/var-config's file finder) so the assertions below test the + // @varar/config's file finder) so the assertions below test the // CLI's own output, not engine warnings. return spawnSync(process.execPath, [BIN_TS, ...args], { cwd, encoding: 'utf8' }) } @@ -23,7 +23,7 @@ function filterWarnings(stderr: string): string { .trim() } -describe('var run', () => { +describe('varar run', () => { test('runs passing and failing examples, reports counts, exits 1 on failure', () => { const cwd = resolve(FIXTURES, 'run-basic') const r = run(['run'], cwd) diff --git a/typescript/packages/var-cli/tests/smoke.test.ts b/typescript/packages/cli/tests/smoke.test.ts similarity index 100% rename from typescript/packages/var-cli/tests/smoke.test.ts rename to typescript/packages/cli/tests/smoke.test.ts diff --git a/typescript/packages/var-cli/tsconfig.json b/typescript/packages/cli/tsconfig.json similarity index 100% rename from typescript/packages/var-cli/tsconfig.json rename to typescript/packages/cli/tsconfig.json diff --git a/typescript/packages/var-cli/vitest.config.ts b/typescript/packages/cli/vitest.config.ts similarity index 79% rename from typescript/packages/var-cli/vitest.config.ts rename to typescript/packages/cli/vitest.config.ts index 25806da3..160d00d4 100644 --- a/typescript/packages/var-cli/vitest.config.ts +++ b/typescript/packages/cli/vitest.config.ts @@ -4,6 +4,6 @@ export default defineConfig({ test: { include: ['tests/**/*.test.ts'], // Inline workspace packages so vite transforms them from source. - server: { deps: { inline: [/^@oselvar\//] } }, + server: { deps: { inline: [/^@varar\//] } }, }, }) diff --git a/typescript/packages/var-config/package.json b/typescript/packages/config/package.json similarity index 78% rename from typescript/packages/var-config/package.json rename to typescript/packages/config/package.json index 5a182080..d6970dd8 100644 --- a/typescript/packages/var-config/package.json +++ b/typescript/packages/config/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/var-config", + "name": "@varar/config", "version": "0.4.2", "type": "module", "exports": { @@ -19,7 +19,7 @@ "test": "vitest run" }, "dependencies": { - "@oselvar/var-core": "workspace:*" + "@varar/core": "workspace:*" }, "publishConfig": { "exports": { @@ -34,7 +34,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", - "directory": "typescript/packages/var-config" + "url": "git+https://github.com/oselvar/varar.git", + "directory": "typescript/packages/config" } } diff --git a/typescript/packages/var-config/src/config-types.ts b/typescript/packages/config/src/config-types.ts similarity index 89% rename from typescript/packages/var-config/src/config-types.ts rename to typescript/packages/config/src/config-types.ts index c3aca6b2..6974ae53 100644 --- a/typescript/packages/var-config/src/config-types.ts +++ b/typescript/packages/config/src/config-types.ts @@ -1,4 +1,4 @@ -import type { ScannerPlugin } from '@oselvar/var-core' +import type { ScannerPlugin } from '@varar/core' // Spec-doc discovery globs. `include` is globbed; anything also matching // `exclude` is dropped. Both are plain globs — no `!` prefix semantics. @@ -7,7 +7,7 @@ export type VarGlobs = { readonly exclude: ReadonlyArray } -// The parsed, unresolved shape of var.config.json — pure data, shared +// The parsed, unresolved shape of varar.config.json — pure data, shared // byte-for-byte with the Python/Java/Kotlin readers (see // conformance/config/README.md). Scanner plugins are NAMES here. export type ParsedVarConfig = { diff --git a/typescript/packages/var-config/src/config.ts b/typescript/packages/config/src/config.ts similarity index 92% rename from typescript/packages/var-config/src/config.ts rename to typescript/packages/config/src/config.ts index 348dc51d..ba7d6f33 100644 --- a/typescript/packages/var-config/src/config.ts +++ b/typescript/packages/config/src/config.ts @@ -1,6 +1,6 @@ import { existsSync, readFileSync } from 'node:fs' import { resolve } from 'node:path' -import { resolveScannerPlugins } from '@oselvar/var-core' +import { resolveScannerPlugins } from '@varar/core' import type { ParsedVarConfig, VarConfig, VarGlobs } from './config-types.ts' export type { ParsedVarConfig, VarConfig, VarGlobs } from './config-types.ts' @@ -8,7 +8,7 @@ export type { ParsedVarConfig, VarConfig, VarGlobs } from './config-types.ts' const EMPTY_PARSED: ParsedVarConfig = { // No default docs OR steps globs: a repo must declare both explicitly. // (The old TS-only `**/*.steps.ts` steps default died with the TS-only - // format — var.config.json is shared with the Python/Java/Kotlin ports.) + // format — varar.config.json is shared with the Python/Java/Kotlin ports.) docs: { include: [], exclude: [] }, steps: [], snippets: {}, @@ -30,7 +30,7 @@ function stringArray(value: unknown, key: string, sourcePath: string): ReadonlyA return value } -// Pure. Parses the var.config.json TEXT (no filesystem) so the conformance +// Pure. Parses the varar.config.json TEXT (no filesystem) so the conformance // harness and loadVarConfig share one implementation. Fails loudly — a // typo'd config that silently discovers nothing is the failure mode this // refuses (see the design spec's error-handling section). @@ -81,7 +81,7 @@ export function parseVarConfig(jsonText: string, sourcePath: string): ParsedVarC } export async function loadVarConfig(cwd: string): Promise { - const path = resolve(cwd, 'var.config.json') + const path = resolve(cwd, 'varar.config.json') const parsed = existsSync(path) ? parseVarConfig(readFileSync(path, 'utf8'), path) : EMPTY_PARSED return { docs: parsed.docs, diff --git a/typescript/packages/var-config/src/find-files.ts b/typescript/packages/config/src/find-files.ts similarity index 100% rename from typescript/packages/var-config/src/find-files.ts rename to typescript/packages/config/src/find-files.ts diff --git a/typescript/packages/var-config/src/index.ts b/typescript/packages/config/src/index.ts similarity index 100% rename from typescript/packages/var-config/src/index.ts rename to typescript/packages/config/src/index.ts diff --git a/typescript/packages/var-config/tests/config-conformance.test.ts b/typescript/packages/config/tests/config-conformance.test.ts similarity index 93% rename from typescript/packages/var-config/tests/config-conformance.test.ts rename to typescript/packages/config/tests/config-conformance.test.ts index fe57eb6f..98e40b11 100644 --- a/typescript/packages/var-config/tests/config-conformance.test.ts +++ b/typescript/packages/config/tests/config-conformance.test.ts @@ -1,7 +1,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs' import { join } from 'node:path' import { fileURLToPath } from 'node:url' -import { canonicalStringify } from '@oselvar/var-core' +import { canonicalStringify } from '@varar/core' import { expect, test } from 'vitest' import { parseVarConfig } from '../src/config.ts' @@ -13,7 +13,7 @@ const EMPTY = { docs: { include: [], exclude: [] }, steps: [], snippets: {}, sca for (const name of readdirSync(CASES_DIR).sort()) { const dir = join(CASES_DIR, name) - const configPath = join(dir, 'var.config.json') + const configPath = join(dir, 'varar.config.json') if (existsSync(join(dir, 'expect-error.txt'))) { test(`config conformance: ${name} fails to parse`, () => { expect(() => parseVarConfig(readFileSync(configPath, 'utf8'), configPath)).toThrowError() diff --git a/typescript/packages/var-config/tests/config.test.ts b/typescript/packages/config/tests/config.test.ts similarity index 84% rename from typescript/packages/var-config/tests/config.test.ts rename to typescript/packages/config/tests/config.test.ts index e309278f..705761e7 100644 --- a/typescript/packages/var-config/tests/config.test.ts +++ b/typescript/packages/config/tests/config.test.ts @@ -13,7 +13,7 @@ test('parseVarConfig reads all four keys', () => { "snippets": { "typescript": "T" }, "scannerPlugins": ["gherkinTables"] }`, - 'var.config.json', + 'varar.config.json', ) expect(parsed).toEqual({ docs: { include: ['specs/**/*.md'], exclude: ['specs/wip/**'] }, @@ -24,7 +24,7 @@ test('parseVarConfig reads all four keys', () => { }) test('all keys are optional and default to empty; $schema is ignored', () => { - const parsed = parseVarConfig('{ "$schema": "https://x/y.json" }', 'var.config.json') + const parsed = parseVarConfig('{ "$schema": "https://x/y.json" }', 'varar.config.json') expect(parsed).toEqual({ docs: { include: [], exclude: [] }, steps: [], @@ -36,7 +36,7 @@ test('all keys are optional and default to empty; $schema is ignored', () => { test('null values are treated as absent, not errors', () => { const parsed = parseVarConfig( '{ "docs": { "include": null, "exclude": null }, "steps": null, "snippets": null, "scannerPlugins": null }', - 'var.config.json', + 'varar.config.json', ) expect(parsed).toEqual({ docs: { include: [], exclude: [] }, @@ -47,22 +47,22 @@ test('null values are treated as absent, not errors', () => { }) test('malformed JSON throws with the source path in the message', () => { - expect(() => parseVarConfig('{ nope', '/w/var.config.json')).toThrowError( - /^\/w\/var\.config\.json/, + expect(() => parseVarConfig('{ nope', '/w/varar.config.json')).toThrowError( + /^\/w\/varar\.config\.json/, ) }) test('an unknown top-level key throws (migration tripwire for the old "vars" key)', () => { - expect(() => parseVarConfig('{ "vars": {} }', 'var.config.json')).toThrowError( + expect(() => parseVarConfig('{ "vars": {} }', 'varar.config.json')).toThrowError( /unknown key.*"vars"/i, ) }) test('a wrong-typed value throws naming the key', () => { - expect(() => parseVarConfig('{ "steps": "x" }', 'var.config.json')).toThrowError(/steps/) - expect(() => parseVarConfig('{ "docs": [] }', 'var.config.json')).toThrowError(/docs/) + expect(() => parseVarConfig('{ "steps": "x" }', 'varar.config.json')).toThrowError(/steps/) + expect(() => parseVarConfig('{ "docs": [] }', 'varar.config.json')).toThrowError(/docs/) expect(() => - parseVarConfig('{ "snippets": { "typescript": 1 } }', 'var.config.json'), + parseVarConfig('{ "snippets": { "typescript": 1 } }', 'varar.config.json'), ).toThrowError(/snippets/) }) @@ -70,7 +70,7 @@ test('loadVarConfig resolves plugin names and keeps the names', async () => { const dir = mkdtempSync(join(tmpdir(), 'var-cfg-')) try { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"] }, "scannerPlugins": ["gherkinTables"] }\n', ) const cfg = await loadVarConfig(dir) @@ -83,7 +83,7 @@ test('loadVarConfig resolves plugin names and keeps the names', async () => { } }) -test('missing var.config.json yields the empty config (no default steps glob)', async () => { +test('missing varar.config.json yields the empty config (no default steps glob)', async () => { const dir = mkdtempSync(join(tmpdir(), 'var-cfg-none-')) try { const cfg = await loadVarConfig(dir) @@ -100,7 +100,7 @@ test('missing var.config.json yields the empty config (no default steps glob)', test('loadVarConfig rejects an unknown plugin name', async () => { const dir = mkdtempSync(join(tmpdir(), 'var-cfg-badplugin-')) try { - writeFileSync(join(dir, 'var.config.json'), '{ "scannerPlugins": ["nope"] }\n') + writeFileSync(join(dir, 'varar.config.json'), '{ "scannerPlugins": ["nope"] }\n') await expect(loadVarConfig(dir)).rejects.toThrowError(/unknown scanner plugin "nope"/i) } finally { rmSync(dir, { recursive: true, force: true }) diff --git a/typescript/packages/var-config/tsconfig.json b/typescript/packages/config/tsconfig.json similarity index 100% rename from typescript/packages/var-config/tsconfig.json rename to typescript/packages/config/tsconfig.json diff --git a/typescript/packages/var-config/vitest.config.ts b/typescript/packages/config/vitest.config.ts similarity index 79% rename from typescript/packages/var-config/vitest.config.ts rename to typescript/packages/config/vitest.config.ts index 25806da3..160d00d4 100644 --- a/typescript/packages/var-config/vitest.config.ts +++ b/typescript/packages/config/vitest.config.ts @@ -4,6 +4,6 @@ export default defineConfig({ test: { include: ['tests/**/*.test.ts'], // Inline workspace packages so vite transforms them from source. - server: { deps: { inline: [/^@oselvar\//] } }, + server: { deps: { inline: [/^@varar\//] } }, }, }) diff --git a/typescript/packages/core/README.md b/typescript/packages/core/README.md new file mode 100644 index 00000000..7f437065 --- /dev/null +++ b/typescript/packages/core/README.md @@ -0,0 +1,9 @@ +# @varar/core + +The pure functional core of Varar: parser, matcher, planner, executor, AST, diagnostics, +and the return-based comparison engine. Pure functions over immutable data — no +globals, no I/O, no side effects. + +**Internal.** Do not depend on this package directly. Write step definitions against +`@varar/varar`; integrate with a test runner via an adapter such as +`@varar/vitest`. This package's surface is broad and may change without notice. diff --git a/typescript/packages/var-core/package.json b/typescript/packages/core/package.json similarity index 83% rename from typescript/packages/var-core/package.json rename to typescript/packages/core/package.json index 09631145..5a85c2c4 100644 --- a/typescript/packages/var-core/package.json +++ b/typescript/packages/core/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/var-core", + "name": "@varar/core", "version": "0.4.2", "type": "module", "exports": { @@ -34,7 +34,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", - "directory": "typescript/packages/var-core" + "url": "git+https://github.com/oselvar/varar.git", + "directory": "typescript/packages/core" } } diff --git a/typescript/packages/var-core/src/ast.ts b/typescript/packages/core/src/ast.ts similarity index 100% rename from typescript/packages/var-core/src/ast.ts rename to typescript/packages/core/src/ast.ts diff --git a/typescript/packages/var-core/src/cell-diff.ts b/typescript/packages/core/src/cell-diff.ts similarity index 100% rename from typescript/packages/var-core/src/cell-diff.ts rename to typescript/packages/core/src/cell-diff.ts diff --git a/typescript/packages/var-core/src/conformance.ts b/typescript/packages/core/src/conformance.ts similarity index 100% rename from typescript/packages/var-core/src/conformance.ts rename to typescript/packages/core/src/conformance.ts diff --git a/typescript/packages/var-core/src/deep-equal.ts b/typescript/packages/core/src/deep-equal.ts similarity index 100% rename from typescript/packages/var-core/src/deep-equal.ts rename to typescript/packages/core/src/deep-equal.ts diff --git a/typescript/packages/var-core/src/deep-freeze.ts b/typescript/packages/core/src/deep-freeze.ts similarity index 100% rename from typescript/packages/var-core/src/deep-freeze.ts rename to typescript/packages/core/src/deep-freeze.ts diff --git a/typescript/packages/var-core/src/diagnostics.ts b/typescript/packages/core/src/diagnostics.ts similarity index 100% rename from typescript/packages/var-core/src/diagnostics.ts rename to typescript/packages/core/src/diagnostics.ts diff --git a/typescript/packages/var-core/src/doc-string-diff.ts b/typescript/packages/core/src/doc-string-diff.ts similarity index 100% rename from typescript/packages/var-core/src/doc-string-diff.ts rename to typescript/packages/core/src/doc-string-diff.ts diff --git a/typescript/packages/var-core/src/drift.ts b/typescript/packages/core/src/drift.ts similarity index 96% rename from typescript/packages/var-core/src/drift.ts rename to typescript/packages/core/src/drift.ts index 321fdac6..1f0628de 100644 --- a/typescript/packages/var-core/src/drift.ts +++ b/typescript/packages/core/src/drift.ts @@ -7,8 +7,8 @@ import type { Span } from './span.ts' // A baseline example is re-identified in the edited source by text: an exact // name match, else the most word-similar paragraph at or above this threshold. -// So you may move a paragraph anywhere and reword up to ~half its words and Vár -// still recognizes it; edit it past this point and Vár treats it as a fresh +// So you may move a paragraph anywhere and reword up to ~half its words and Varar +// still recognizes it; edit it past this point and Varar treats it as a fresh // paragraph (remove + add), not drift. Tune here — a single number, ported // byte-identically to every language. export const DRIFT_SIMILARITY_THRESHOLD = 0.5 @@ -30,7 +30,7 @@ export type SpecBaseline = { readonly examples: ReadonlyArray } -// The whole `var.lock.json`: every spec keyed by its POSIX path relative to the +// The whole `varar.lock.json`: every spec keyed by its POSIX path relative to the // project root. export type VarLock = { readonly version: 1 @@ -201,7 +201,7 @@ function isSpecBaseline(v: unknown): v is SpecBaseline { ) } -// Parse `var.lock.json`. Returns null on malformed input (treated as "no +// Parse `varar.lock.json`. Returns null on malformed input (treated as "no // baseline yet"), mirroring the LSP's tolerant result ingestion. export function parseVarLock(text: string): VarLock | null { let parsed: unknown @@ -221,7 +221,7 @@ export function parseVarLock(text: string): VarLock | null { return { version: 1, specs } } -// Serialize a `var.lock.json` deterministically: spec paths sorted, examples in +// Serialize a `varar.lock.json` deterministically: spec paths sorted, examples in // document order, two-space indent, trailing newline. Byte-stable across runs // so a clean re-run produces no git diff. export function stringifyVarLock(lock: VarLock): string { diff --git a/typescript/packages/var-core/src/execute.ts b/typescript/packages/core/src/execute.ts similarity index 100% rename from typescript/packages/var-core/src/execute.ts rename to typescript/packages/core/src/execute.ts diff --git a/typescript/packages/var-core/src/expression-segments.ts b/typescript/packages/core/src/expression-segments.ts similarity index 100% rename from typescript/packages/var-core/src/expression-segments.ts rename to typescript/packages/core/src/expression-segments.ts diff --git a/typescript/packages/var-core/src/failure-anchor.ts b/typescript/packages/core/src/failure-anchor.ts similarity index 100% rename from typescript/packages/var-core/src/failure-anchor.ts rename to typescript/packages/core/src/failure-anchor.ts diff --git a/typescript/packages/var-core/src/failure.ts b/typescript/packages/core/src/failure.ts similarity index 100% rename from typescript/packages/var-core/src/failure.ts rename to typescript/packages/core/src/failure.ts diff --git a/typescript/packages/var-core/src/hash.ts b/typescript/packages/core/src/hash.ts similarity index 100% rename from typescript/packages/var-core/src/hash.ts rename to typescript/packages/core/src/hash.ts diff --git a/typescript/packages/var-core/src/index.ts b/typescript/packages/core/src/index.ts similarity index 100% rename from typescript/packages/var-core/src/index.ts rename to typescript/packages/core/src/index.ts diff --git a/typescript/packages/var-core/src/matcher.ts b/typescript/packages/core/src/matcher.ts similarity index 100% rename from typescript/packages/var-core/src/matcher.ts rename to typescript/packages/core/src/matcher.ts diff --git a/typescript/packages/var-core/src/param-diff.ts b/typescript/packages/core/src/param-diff.ts similarity index 100% rename from typescript/packages/var-core/src/param-diff.ts rename to typescript/packages/core/src/param-diff.ts diff --git a/typescript/packages/var-core/src/parse.ts b/typescript/packages/core/src/parse.ts similarity index 100% rename from typescript/packages/var-core/src/parse.ts rename to typescript/packages/core/src/parse.ts diff --git a/typescript/packages/var-core/src/plan.ts b/typescript/packages/core/src/plan.ts similarity index 100% rename from typescript/packages/var-core/src/plan.ts rename to typescript/packages/core/src/plan.ts diff --git a/typescript/packages/var-core/src/plugins/gherkin/doc-strings.ts b/typescript/packages/core/src/plugins/gherkin/doc-strings.ts similarity index 100% rename from typescript/packages/var-core/src/plugins/gherkin/doc-strings.ts rename to typescript/packages/core/src/plugins/gherkin/doc-strings.ts diff --git a/typescript/packages/var-core/src/plugins/gherkin/index.ts b/typescript/packages/core/src/plugins/gherkin/index.ts similarity index 100% rename from typescript/packages/var-core/src/plugins/gherkin/index.ts rename to typescript/packages/core/src/plugins/gherkin/index.ts diff --git a/typescript/packages/var-core/src/plugins/gherkin/tables.ts b/typescript/packages/core/src/plugins/gherkin/tables.ts similarity index 100% rename from typescript/packages/var-core/src/plugins/gherkin/tables.ts rename to typescript/packages/core/src/plugins/gherkin/tables.ts diff --git a/typescript/packages/var-core/src/plugins/registry.ts b/typescript/packages/core/src/plugins/registry.ts similarity index 92% rename from typescript/packages/var-core/src/plugins/registry.ts rename to typescript/packages/core/src/plugins/registry.ts index 7b871fcb..292ec8a4 100644 --- a/typescript/packages/var-core/src/plugins/registry.ts +++ b/typescript/packages/core/src/plugins/registry.ts @@ -1,7 +1,7 @@ import type { ScannerPlugin } from '../scanner.ts' import { gherkinDocStrings, gherkinTables } from './gherkin/index.ts' -// var.config.json carries scanner plugins as NAME STRINGS (the config is +// varar.config.json carries scanner plugins as NAME STRINGS (the config is // shared with the Python/Java/Kotlin ports, which resolve the same names // against their own implementations). This is the TypeScript resolution // table. Fixed to the built-ins for now; third-party plugins are out of diff --git a/typescript/packages/var-core/src/ports.ts b/typescript/packages/core/src/ports.ts similarity index 90% rename from typescript/packages/var-core/src/ports.ts rename to typescript/packages/core/src/ports.ts index 5935e41f..b3ff95d9 100644 --- a/typescript/packages/var-core/src/ports.ts +++ b/typescript/packages/core/src/ports.ts @@ -12,7 +12,7 @@ export interface Reporter { diagnostic(d: Diagnostic): void } -// Persistence port for the drift baseline (`var.lock.json`). The core owns the +// Persistence port for the drift baseline (`varar.lock.json`). The core owns the // format (parseVarLock / stringifyVarLock) and reads/writes raw text through // this port, so adapters stay dumb I/O: a filesystem store on Node (CLI, // vitest), an in-memory store in the browser. `read` returns the whole diff --git a/typescript/packages/var-core/src/registry.ts b/typescript/packages/core/src/registry.ts similarity index 100% rename from typescript/packages/var-core/src/registry.ts rename to typescript/packages/core/src/registry.ts diff --git a/typescript/packages/var-core/src/result.ts b/typescript/packages/core/src/result.ts similarity index 100% rename from typescript/packages/var-core/src/result.ts rename to typescript/packages/core/src/result.ts diff --git a/typescript/packages/var-core/src/run-diagnostics.ts b/typescript/packages/core/src/run-diagnostics.ts similarity index 100% rename from typescript/packages/var-core/src/run-diagnostics.ts rename to typescript/packages/core/src/run-diagnostics.ts diff --git a/typescript/packages/var-core/src/scanner.ts b/typescript/packages/core/src/scanner.ts similarity index 100% rename from typescript/packages/var-core/src/scanner.ts rename to typescript/packages/core/src/scanner.ts diff --git a/typescript/packages/var-core/src/sentences.ts b/typescript/packages/core/src/sentences.ts similarity index 100% rename from typescript/packages/var-core/src/sentences.ts rename to typescript/packages/core/src/sentences.ts diff --git a/typescript/packages/var-core/src/span.ts b/typescript/packages/core/src/span.ts similarity index 100% rename from typescript/packages/var-core/src/span.ts rename to typescript/packages/core/src/span.ts diff --git a/typescript/packages/var-core/src/step-role.ts b/typescript/packages/core/src/step-role.ts similarity index 100% rename from typescript/packages/var-core/src/step-role.ts rename to typescript/packages/core/src/step-role.ts diff --git a/typescript/packages/var-core/src/structurer.ts b/typescript/packages/core/src/structurer.ts similarity index 100% rename from typescript/packages/var-core/src/structurer.ts rename to typescript/packages/core/src/structurer.ts diff --git a/typescript/packages/var-core/src/table-cells.ts b/typescript/packages/core/src/table-cells.ts similarity index 100% rename from typescript/packages/var-core/src/table-cells.ts rename to typescript/packages/core/src/table-cells.ts diff --git a/typescript/packages/var-core/tests/ast-extended.test.ts b/typescript/packages/core/tests/ast-extended.test.ts similarity index 100% rename from typescript/packages/var-core/tests/ast-extended.test.ts rename to typescript/packages/core/tests/ast-extended.test.ts diff --git a/typescript/packages/var-core/tests/ast.test.ts b/typescript/packages/core/tests/ast.test.ts similarity index 100% rename from typescript/packages/var-core/tests/ast.test.ts rename to typescript/packages/core/tests/ast.test.ts diff --git a/typescript/packages/var-core/tests/cell-diff.test.ts b/typescript/packages/core/tests/cell-diff.test.ts similarity index 100% rename from typescript/packages/var-core/tests/cell-diff.test.ts rename to typescript/packages/core/tests/cell-diff.test.ts diff --git a/typescript/packages/var-core/tests/conformance.test.ts b/typescript/packages/core/tests/conformance.test.ts similarity index 100% rename from typescript/packages/var-core/tests/conformance.test.ts rename to typescript/packages/core/tests/conformance.test.ts diff --git a/typescript/packages/var-core/tests/deep-equal.test.ts b/typescript/packages/core/tests/deep-equal.test.ts similarity index 100% rename from typescript/packages/var-core/tests/deep-equal.test.ts rename to typescript/packages/core/tests/deep-equal.test.ts diff --git a/typescript/packages/var-core/tests/deep-freeze.test.ts b/typescript/packages/core/tests/deep-freeze.test.ts similarity index 100% rename from typescript/packages/var-core/tests/deep-freeze.test.ts rename to typescript/packages/core/tests/deep-freeze.test.ts diff --git a/typescript/packages/var-core/tests/diagnostics.test.ts b/typescript/packages/core/tests/diagnostics.test.ts similarity index 100% rename from typescript/packages/var-core/tests/diagnostics.test.ts rename to typescript/packages/core/tests/diagnostics.test.ts diff --git a/typescript/packages/var-core/tests/doc-string-diff.test.ts b/typescript/packages/core/tests/doc-string-diff.test.ts similarity index 100% rename from typescript/packages/var-core/tests/doc-string-diff.test.ts rename to typescript/packages/core/tests/doc-string-diff.test.ts diff --git a/typescript/packages/var-core/tests/drift.test.ts b/typescript/packages/core/tests/drift.test.ts similarity index 100% rename from typescript/packages/var-core/tests/drift.test.ts rename to typescript/packages/core/tests/drift.test.ts diff --git a/typescript/packages/var-core/tests/e2e.test.ts b/typescript/packages/core/tests/e2e.test.ts similarity index 100% rename from typescript/packages/var-core/tests/e2e.test.ts rename to typescript/packages/core/tests/e2e.test.ts diff --git a/typescript/packages/var-core/tests/execute-roles.test.ts b/typescript/packages/core/tests/execute-roles.test.ts similarity index 100% rename from typescript/packages/var-core/tests/execute-roles.test.ts rename to typescript/packages/core/tests/execute-roles.test.ts diff --git a/typescript/packages/var-core/tests/execute-state.test.ts b/typescript/packages/core/tests/execute-state.test.ts similarity index 100% rename from typescript/packages/var-core/tests/execute-state.test.ts rename to typescript/packages/core/tests/execute-state.test.ts diff --git a/typescript/packages/var-core/tests/execute.test.ts b/typescript/packages/core/tests/execute.test.ts similarity index 100% rename from typescript/packages/var-core/tests/execute.test.ts rename to typescript/packages/core/tests/execute.test.ts diff --git a/typescript/packages/var-core/tests/expression-segments.test.ts b/typescript/packages/core/tests/expression-segments.test.ts similarity index 100% rename from typescript/packages/var-core/tests/expression-segments.test.ts rename to typescript/packages/core/tests/expression-segments.test.ts diff --git a/typescript/packages/var-core/tests/failure.test.ts b/typescript/packages/core/tests/failure.test.ts similarity index 100% rename from typescript/packages/var-core/tests/failure.test.ts rename to typescript/packages/core/tests/failure.test.ts diff --git a/typescript/packages/var-core/tests/gherkin-plugins.test.ts b/typescript/packages/core/tests/gherkin-plugins.test.ts similarity index 100% rename from typescript/packages/var-core/tests/gherkin-plugins.test.ts rename to typescript/packages/core/tests/gherkin-plugins.test.ts diff --git a/typescript/packages/var-core/tests/hash.test.ts b/typescript/packages/core/tests/hash.test.ts similarity index 100% rename from typescript/packages/var-core/tests/hash.test.ts rename to typescript/packages/core/tests/hash.test.ts diff --git a/typescript/packages/var-core/tests/index.test.ts b/typescript/packages/core/tests/index.test.ts similarity index 100% rename from typescript/packages/var-core/tests/index.test.ts rename to typescript/packages/core/tests/index.test.ts diff --git a/typescript/packages/var-core/tests/matcher.test.ts b/typescript/packages/core/tests/matcher.test.ts similarity index 100% rename from typescript/packages/var-core/tests/matcher.test.ts rename to typescript/packages/core/tests/matcher.test.ts diff --git a/typescript/packages/var-core/tests/param-diff.test.ts b/typescript/packages/core/tests/param-diff.test.ts similarity index 100% rename from typescript/packages/var-core/tests/param-diff.test.ts rename to typescript/packages/core/tests/param-diff.test.ts diff --git a/typescript/packages/var-core/tests/parse.test.ts b/typescript/packages/core/tests/parse.test.ts similarity index 100% rename from typescript/packages/var-core/tests/parse.test.ts rename to typescript/packages/core/tests/parse.test.ts diff --git a/typescript/packages/var-core/tests/plan.test.ts b/typescript/packages/core/tests/plan.test.ts similarity index 100% rename from typescript/packages/var-core/tests/plan.test.ts rename to typescript/packages/core/tests/plan.test.ts diff --git a/typescript/packages/var-core/tests/plugin-registry.test.ts b/typescript/packages/core/tests/plugin-registry.test.ts similarity index 100% rename from typescript/packages/var-core/tests/plugin-registry.test.ts rename to typescript/packages/core/tests/plugin-registry.test.ts diff --git a/typescript/packages/var-core/tests/ports.test.ts b/typescript/packages/core/tests/ports.test.ts similarity index 100% rename from typescript/packages/var-core/tests/ports.test.ts rename to typescript/packages/core/tests/ports.test.ts diff --git a/typescript/packages/var-core/tests/registry.test.ts b/typescript/packages/core/tests/registry.test.ts similarity index 100% rename from typescript/packages/var-core/tests/registry.test.ts rename to typescript/packages/core/tests/registry.test.ts diff --git a/typescript/packages/var-core/tests/run-diagnostics.test.ts b/typescript/packages/core/tests/run-diagnostics.test.ts similarity index 100% rename from typescript/packages/var-core/tests/run-diagnostics.test.ts rename to typescript/packages/core/tests/run-diagnostics.test.ts diff --git a/typescript/packages/var-core/tests/scanner.test.ts b/typescript/packages/core/tests/scanner.test.ts similarity index 100% rename from typescript/packages/var-core/tests/scanner.test.ts rename to typescript/packages/core/tests/scanner.test.ts diff --git a/typescript/packages/var-core/tests/sentences.test.ts b/typescript/packages/core/tests/sentences.test.ts similarity index 100% rename from typescript/packages/var-core/tests/sentences.test.ts rename to typescript/packages/core/tests/sentences.test.ts diff --git a/typescript/packages/var-core/tests/smoke.test.ts b/typescript/packages/core/tests/smoke.test.ts similarity index 100% rename from typescript/packages/var-core/tests/smoke.test.ts rename to typescript/packages/core/tests/smoke.test.ts diff --git a/typescript/packages/var-core/tests/span.test.ts b/typescript/packages/core/tests/span.test.ts similarity index 100% rename from typescript/packages/var-core/tests/span.test.ts rename to typescript/packages/core/tests/span.test.ts diff --git a/typescript/packages/var-core/tests/step-role.test.ts b/typescript/packages/core/tests/step-role.test.ts similarity index 100% rename from typescript/packages/var-core/tests/step-role.test.ts rename to typescript/packages/core/tests/step-role.test.ts diff --git a/typescript/packages/var-core/tests/structurer.test.ts b/typescript/packages/core/tests/structurer.test.ts similarity index 100% rename from typescript/packages/var-core/tests/structurer.test.ts rename to typescript/packages/core/tests/structurer.test.ts diff --git a/typescript/packages/var-core/tsconfig.json b/typescript/packages/core/tsconfig.json similarity index 100% rename from typescript/packages/var-core/tsconfig.json rename to typescript/packages/core/tsconfig.json diff --git a/typescript/packages/var-core/vitest.config.ts b/typescript/packages/core/vitest.config.ts similarity index 100% rename from typescript/packages/var-core/vitest.config.ts rename to typescript/packages/core/vitest.config.ts diff --git a/typescript/packages/cucumber/README.md b/typescript/packages/cucumber/README.md index b1676495..5c315048 100644 --- a/typescript/packages/cucumber/README.md +++ b/typescript/packages/cucumber/README.md @@ -1,12 +1,12 @@ -# @oselvar/cucumber +# @varar/cucumber A migration-verification sandbox: one Gherkin feature file, two step-definition implementations, three test runners — proving that the same business behavior -runs under both `cucumber-js` and `@oselvar/var`. +runs under both `cucumber-js` and `@varar/varar`. This package is **private** and never published. It exists to answer a single question while we shape the public API: *can a project move from -cucumber-js to oselvar/var by porting only the step definitions?* +cucumber-js to varar by porting only the step definitions?* ## Layout @@ -35,7 +35,7 @@ intact through the loader, so var sees a `.md` file whose contents are Gherkin. To make the var scanner understand Gherkin tables and doc strings the package -opts into two scanner plugins in `var.config.json`: +opts into two scanner plugins in `varar.config.json`: ```json { @@ -45,7 +45,7 @@ opts into two scanner plugins in `var.config.json`: } ``` -Plugins are off by default in `@oselvar/var`; ordinary Markdown-native +Plugins are off by default in `@varar/varar`; ordinary Markdown-native `.md` files do not need them. ## Three runners @@ -53,8 +53,8 @@ Plugins are off by default in `@oselvar/var`; ordinary Markdown-native | Script | Runner | What it does | |---|---|---| | `pnpm test:cucumber` | cucumber-js | Loads `cucumber/steps/library.steps.ts`, runs `library.feature` | -| `pnpm test:var` | `@oselvar/var-cli` (`var run`) | Loads `steps/library.steps.ts`, runs `library.feature.md` | -| `pnpm test:var-vitest` | vitest + `@oselvar/var-vitest` plugin | Same .md, executed through vitest's runner | +| `pnpm test:var` | `@varar/cli` (`varar run`) | Loads `steps/library.steps.ts`, runs `library.feature.md` | +| `pnpm test:var-vitest` | vitest + `@varar/vitest` plugin | Same .md, executed through vitest's runner | | `pnpm test` | all three in sequence | full sweep | All three run the same scenario green. @@ -66,7 +66,7 @@ Locally, one scenario / three steps, Node 22: | Runner | Wall clock (mean of 3) | |---|---| | cucumber-js | ~0.85 s | -| `var run` (CLI) | ~0.74 s | +| `varar run` (CLI) | ~0.74 s | | `var` via vitest | ~1.5 s | The vitest path pays the cost of spinning up vite's transform + worker @@ -77,11 +77,11 @@ directly, with no test runner in the way — which is most of the gap. 1. Symlink (or rename) `.feature` to `.md`. 2. Add `"gherkinTables"` and `"gherkinDocStrings"` to `scannerPlugins` in - `var.config.json` so the existing Gherkin syntax parses unchanged. + `varar.config.json` so the existing Gherkin syntax parses unchanged. 3. Re-write the step file: replace `Given('expr', fn)` / `When(...)` / `Then(...)` with the role function that matches what each step does — `context('expr', fn)` to set up state, `action('expr', fn)` to perform an - action, `sensor('expr', fn)` to return a value Vár checks — and replace + action, `sensor('expr', fn)` to return a value Varar checks — and replace `World` + `Before`/`After` with a `steps(() => ({...}))` factory whose return value flows into each handler as the first argument. 4. Data tables arrive as `ReadonlyArray>` (header row diff --git a/typescript/packages/cucumber/package.json b/typescript/packages/cucumber/package.json index 086f76bc..475bfcc9 100644 --- a/typescript/packages/cucumber/package.json +++ b/typescript/packages/cucumber/package.json @@ -1,27 +1,27 @@ { - "name": "@oselvar/cucumber", + "name": "@varar/cucumber", "version": "0.4.2", "private": true, "type": "module", "scripts": { "test": "cucumber-js && pnpm test:var && pnpm test:var-vitest", "test:cucumber": "cucumber-js", - "test:var": "node ../var-cli/src/bin.ts run", + "test:var": "node ../cli/src/bin.ts run", "test:var-vitest": "vitest run" }, "devDependencies": { "@cucumber/cucumber": "^13.0.0", - "@oselvar/var": "workspace:*", - "@oselvar/var-cli": "workspace:*", - "@oselvar/var-core": "workspace:*", - "@oselvar/var-vitest": "workspace:*", + "@varar/varar": "workspace:*", + "@varar/cli": "workspace:*", + "@varar/core": "workspace:*", + "@varar/vitest": "workspace:*", "@types/node": "^26.1.0", "vitest": "^4.1.10" }, "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", + "url": "git+https://github.com/oselvar/varar.git", "directory": "typescript/packages/cucumber" } } diff --git a/typescript/packages/cucumber/steps/library.steps.ts b/typescript/packages/cucumber/steps/library.steps.ts index dcd7bb7f..8b2d346c 100644 --- a/typescript/packages/cucumber/steps/library.steps.ts +++ b/typescript/packages/cucumber/steps/library.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' import { expect } from 'vitest' import { type Book, type BorrowError, Library, type Receipt } from '../src/library.ts' diff --git a/typescript/packages/cucumber/tsconfig.json b/typescript/packages/cucumber/tsconfig.json index 7fc99c77..a9f206dc 100644 --- a/typescript/packages/cucumber/tsconfig.json +++ b/typescript/packages/cucumber/tsconfig.json @@ -1,4 +1,4 @@ { - "extends": "../var-core/tsconfig.json", + "extends": "../core/tsconfig.json", "include": ["src/**/*", "steps/**/*", "cucumber/steps/**/*"] } diff --git a/typescript/packages/cucumber/var.config.json b/typescript/packages/cucumber/varar.config.json similarity index 70% rename from typescript/packages/cucumber/var.config.json rename to typescript/packages/cucumber/varar.config.json index 3da67cba..99a09dd6 100644 --- a/typescript/packages/cucumber/var.config.json +++ b/typescript/packages/cucumber/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "../../../conformance/config/var.config.schema.json", + "$schema": "../../../conformance/config/varar.config.schema.json", "docs": { "include": ["features/**/*.feature"], "exclude": [] }, "steps": ["steps/**/*.steps.ts"], "scannerPlugins": ["gherkinTables", "gherkinDocStrings"] diff --git a/typescript/packages/cucumber/varar.lock.json b/typescript/packages/cucumber/varar.lock.json new file mode 100644 index 00000000..25d6f276 --- /dev/null +++ b/typescript/packages/cucumber/varar.lock.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "specs": { + "features/library.feature": { + "sourceHash": "fnv1a:de46f92c", + "examples": [ + { + "name": "Scenario: An available book is borrowed and a receipt comes back Given the library has these books:", + "line": 3 + } + ] + } + } +} diff --git a/typescript/packages/cucumber/vitest.config.ts b/typescript/packages/cucumber/vitest.config.ts index 7ced6949..996d92f8 100644 --- a/typescript/packages/cucumber/vitest.config.ts +++ b/typescript/packages/cucumber/vitest.config.ts @@ -1,16 +1,16 @@ -import varPlugin from '@oselvar/var-vitest' +import varPlugin from '@varar/vitest' import { defineConfig } from 'vitest/config' export default defineConfig({ - // Point the plugin at THIS package's var.config.json (not the repo-root one + // Point the plugin at THIS package's varar.config.json (not the repo-root one // which is scoped to the tutorial). plugins: [varPlugin({ cwd: new URL('.', import.meta.url).pathname })], - // Force a single `@oselvar/var` instance so the steps registered via - // `steps` (author side) and the registry glue (`@oselvar/var/registry`) + // Force a single `@varar/varar` instance so the steps registered via + // `steps` (author side) and the registry glue (`@varar/varar/registry`) // share one module — otherwise the registry splits and no steps are seen. - resolve: { dedupe: ['@oselvar/var'] }, + resolve: { dedupe: ['@varar/varar'] }, test: { include: ['**/*.feature'], - server: { deps: { inline: [/^@oselvar\//] } }, + server: { deps: { inline: [/^@varar\//] } }, }, }) diff --git a/typescript/packages/language/README.md b/typescript/packages/language/README.md new file mode 100644 index 00000000..4b93eb96 --- /dev/null +++ b/typescript/packages/language/README.md @@ -0,0 +1,5 @@ +# @varar/language + +Static analysis for Varar step definitions and specs: a TypeScript-based scanner that +discovers step definitions and custom parameter types, and a workspace indexer that +matches specs to step definitions. Used by `@varar/lsp` and the website. diff --git a/typescript/packages/var-language/package.json b/typescript/packages/language/package.json similarity index 83% rename from typescript/packages/var-language/package.json rename to typescript/packages/language/package.json index 60a057f3..5709bed3 100644 --- a/typescript/packages/var-language/package.json +++ b/typescript/packages/language/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/var-language", + "name": "@varar/language", "version": "0.4.2", "type": "module", "exports": { @@ -20,7 +20,8 @@ }, "dependencies": { "@cucumber/cucumber-expressions": "^20.0.0", - "@oselvar/var-core": "workspace:*", + "@varar/core": "workspace:*", + "typescript": "^6.0.3", "web-tree-sitter": "^0.26.10" }, "devDependencies": { @@ -44,7 +45,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", - "directory": "typescript/packages/var-language" + "url": "git+https://github.com/oselvar/varar.git", + "directory": "typescript/packages/language" } } diff --git a/typescript/packages/var-language/src/grammar-loader.ts b/typescript/packages/language/src/grammar-loader.ts similarity index 100% rename from typescript/packages/var-language/src/grammar-loader.ts rename to typescript/packages/language/src/grammar-loader.ts diff --git a/typescript/packages/var-language/src/index-workspace.ts b/typescript/packages/language/src/index-workspace.ts similarity index 98% rename from typescript/packages/var-language/src/index-workspace.ts rename to typescript/packages/language/src/index-workspace.ts index e4733137..d858b447 100644 --- a/typescript/packages/var-language/src/index-workspace.ts +++ b/typescript/packages/language/src/index-workspace.ts @@ -6,7 +6,7 @@ import { plan, type Registry, type ScannerPlugin, -} from '@oselvar/var-core' +} from '@varar/core' import type { StepDefScanner } from './scanner.ts' import type { Range, StepDef } from './step-defs.ts' @@ -14,7 +14,7 @@ export type WorkspaceInput = { readonly stepFiles: ReadonlyArray<{ readonly path: string; readonly source: string }> readonly varFiles: ReadonlyArray<{ readonly path: string; readonly source: string }> // Optional: opt-in scanner extensions (e.g. Gherkin tables, Gherkin doc - // strings) sourced from var.config.json. Empty/omitted = pure markdown. + // strings) sourced from varar.config.json. Empty/omitted = pure markdown. readonly scannerPlugins?: ReadonlyArray // The step-def scanner. Always the tree-sitter scanner // (createTreeSitterScanner); callers build it at their async shell edge with diff --git a/typescript/packages/var-language/src/index.ts b/typescript/packages/language/src/index.ts similarity index 100% rename from typescript/packages/var-language/src/index.ts rename to typescript/packages/language/src/index.ts diff --git a/typescript/packages/var-language/src/scanner.test.ts b/typescript/packages/language/src/scanner.test.ts similarity index 100% rename from typescript/packages/var-language/src/scanner.test.ts rename to typescript/packages/language/src/scanner.test.ts diff --git a/typescript/packages/var-language/src/scanner.ts b/typescript/packages/language/src/scanner.ts similarity index 100% rename from typescript/packages/var-language/src/scanner.ts rename to typescript/packages/language/src/scanner.ts diff --git a/typescript/packages/var-language/src/snippet-emitter.ts b/typescript/packages/language/src/snippet-emitter.ts similarity index 100% rename from typescript/packages/var-language/src/snippet-emitter.ts rename to typescript/packages/language/src/snippet-emitter.ts diff --git a/typescript/packages/var-language/src/snippet-template.ts b/typescript/packages/language/src/snippet-template.ts similarity index 100% rename from typescript/packages/var-language/src/snippet-template.ts rename to typescript/packages/language/src/snippet-template.ts diff --git a/typescript/packages/var-language/src/snippet.ts b/typescript/packages/language/src/snippet.ts similarity index 97% rename from typescript/packages/var-language/src/snippet.ts rename to typescript/packages/language/src/snippet.ts index 0b0c241b..52264951 100644 --- a/typescript/packages/var-language/src/snippet.ts +++ b/typescript/packages/language/src/snippet.ts @@ -1,5 +1,5 @@ import { CucumberExpressionGenerator } from '@cucumber/cucumber-expressions' -import type { Registry, StepKind } from '@oselvar/var-core' +import type { Registry, StepKind } from '@varar/core' import { createTypeScriptSnippetEmitter, type SnippetEmitter } from './snippet-emitter.ts' import { renderTemplate } from './template.ts' diff --git a/typescript/packages/var-language/src/step-defs.ts b/typescript/packages/language/src/step-defs.ts similarity index 96% rename from typescript/packages/var-language/src/step-defs.ts rename to typescript/packages/language/src/step-defs.ts index 347f37ac..55d6ea89 100644 --- a/typescript/packages/var-language/src/step-defs.ts +++ b/typescript/packages/language/src/step-defs.ts @@ -1,4 +1,4 @@ -import type { StepKind } from '@oselvar/var-core' +import type { StepKind } from '@varar/core' // Shared, language-neutral shapes produced by every StepDefScanner (all // tree-sitter-backed — see tree-sitter-scanner.ts). This module is types only: diff --git a/typescript/packages/var-language/src/template.ts b/typescript/packages/language/src/template.ts similarity index 100% rename from typescript/packages/var-language/src/template.ts rename to typescript/packages/language/src/template.ts diff --git a/typescript/packages/var-language/src/tree-sitter-dialects/escape-decode.ts b/typescript/packages/language/src/tree-sitter-dialects/escape-decode.ts similarity index 100% rename from typescript/packages/var-language/src/tree-sitter-dialects/escape-decode.ts rename to typescript/packages/language/src/tree-sitter-dialects/escape-decode.ts diff --git a/typescript/packages/var-language/src/tree-sitter-dialects/java.ts b/typescript/packages/language/src/tree-sitter-dialects/java.ts similarity index 100% rename from typescript/packages/var-language/src/tree-sitter-dialects/java.ts rename to typescript/packages/language/src/tree-sitter-dialects/java.ts diff --git a/typescript/packages/var-language/src/tree-sitter-dialects/kotlin.ts b/typescript/packages/language/src/tree-sitter-dialects/kotlin.ts similarity index 100% rename from typescript/packages/var-language/src/tree-sitter-dialects/kotlin.ts rename to typescript/packages/language/src/tree-sitter-dialects/kotlin.ts diff --git a/typescript/packages/var-language/src/tree-sitter-dialects/python.ts b/typescript/packages/language/src/tree-sitter-dialects/python.ts similarity index 100% rename from typescript/packages/var-language/src/tree-sitter-dialects/python.ts rename to typescript/packages/language/src/tree-sitter-dialects/python.ts diff --git a/typescript/packages/var-language/src/tree-sitter-dialects/ruby.ts b/typescript/packages/language/src/tree-sitter-dialects/ruby.ts similarity index 100% rename from typescript/packages/var-language/src/tree-sitter-dialects/ruby.ts rename to typescript/packages/language/src/tree-sitter-dialects/ruby.ts diff --git a/typescript/packages/var-language/src/tree-sitter-dialects/rust.ts b/typescript/packages/language/src/tree-sitter-dialects/rust.ts similarity index 100% rename from typescript/packages/var-language/src/tree-sitter-dialects/rust.ts rename to typescript/packages/language/src/tree-sitter-dialects/rust.ts diff --git a/typescript/packages/var-language/src/tree-sitter-dialects/types.ts b/typescript/packages/language/src/tree-sitter-dialects/types.ts similarity index 100% rename from typescript/packages/var-language/src/tree-sitter-dialects/types.ts rename to typescript/packages/language/src/tree-sitter-dialects/types.ts diff --git a/typescript/packages/var-language/src/tree-sitter-dialects/typescript.ts b/typescript/packages/language/src/tree-sitter-dialects/typescript.ts similarity index 100% rename from typescript/packages/var-language/src/tree-sitter-dialects/typescript.ts rename to typescript/packages/language/src/tree-sitter-dialects/typescript.ts diff --git a/typescript/packages/var-language/src/tree-sitter-scanner.ts b/typescript/packages/language/src/tree-sitter-scanner.ts similarity index 99% rename from typescript/packages/var-language/src/tree-sitter-scanner.ts rename to typescript/packages/language/src/tree-sitter-scanner.ts index e85a1d02..0f3b4f6b 100644 --- a/typescript/packages/var-language/src/tree-sitter-scanner.ts +++ b/typescript/packages/language/src/tree-sitter-scanner.ts @@ -1,4 +1,4 @@ -import type { StepKind } from '@oselvar/var-core' +import type { StepKind } from '@varar/core' import { Language, type Node, Parser, Query, type QueryMatch } from 'web-tree-sitter' import type { GrammarLoader } from './grammar-loader.ts' import type { StepDefScanner } from './scanner.ts' diff --git a/typescript/packages/var-language/tests/bundle-fixtures.ts b/typescript/packages/language/tests/bundle-fixtures.ts similarity index 100% rename from typescript/packages/var-language/tests/bundle-fixtures.ts rename to typescript/packages/language/tests/bundle-fixtures.ts diff --git a/typescript/packages/var-language/tests/extraction-conformance.test.ts b/typescript/packages/language/tests/extraction-conformance.test.ts similarity index 100% rename from typescript/packages/var-language/tests/extraction-conformance.test.ts rename to typescript/packages/language/tests/extraction-conformance.test.ts diff --git a/typescript/packages/var-language/tests/index-workspace.test.ts b/typescript/packages/language/tests/index-workspace.test.ts similarity index 100% rename from typescript/packages/var-language/tests/index-workspace.test.ts rename to typescript/packages/language/tests/index-workspace.test.ts diff --git a/typescript/packages/var-language/tests/language-coverage.test.ts b/typescript/packages/language/tests/language-coverage.test.ts similarity index 96% rename from typescript/packages/var-language/tests/language-coverage.test.ts rename to typescript/packages/language/tests/language-coverage.test.ts index 5a91f36c..110458f7 100644 --- a/typescript/packages/var-language/tests/language-coverage.test.ts +++ b/typescript/packages/language/tests/language-coverage.test.ts @@ -59,9 +59,9 @@ describe('language coverage (drift gate)', () => { }) test('the grammar loaders and the VS Code bundler list the same grammars', () => { - const nodeLoader = grammarWasms('../../var-lsp/src/node-grammar-loader.ts') + const nodeLoader = grammarWasms('../../lsp/src/node-grammar-loader.ts') const testLoader = grammarWasms('./test-grammar-loader.ts') - const vscodeBundler = grammarWasms('../../var-vscode/esbuild.mjs') + const vscodeBundler = grammarWasms('../../vscode/esbuild.mjs') expect(testLoader).toEqual(nodeLoader) expect(vscodeBundler).toEqual(nodeLoader) }) diff --git a/typescript/packages/var-language/tests/smoke.test.ts b/typescript/packages/language/tests/smoke.test.ts similarity index 100% rename from typescript/packages/var-language/tests/smoke.test.ts rename to typescript/packages/language/tests/smoke.test.ts diff --git a/typescript/packages/var-language/tests/snippet-emitter.test.ts b/typescript/packages/language/tests/snippet-emitter.test.ts similarity index 100% rename from typescript/packages/var-language/tests/snippet-emitter.test.ts rename to typescript/packages/language/tests/snippet-emitter.test.ts diff --git a/typescript/packages/var-language/tests/snippet-languages.test.ts b/typescript/packages/language/tests/snippet-languages.test.ts similarity index 97% rename from typescript/packages/var-language/tests/snippet-languages.test.ts rename to typescript/packages/language/tests/snippet-languages.test.ts index 97527802..1bad55ae 100644 --- a/typescript/packages/var-language/tests/snippet-languages.test.ts +++ b/typescript/packages/language/tests/snippet-languages.test.ts @@ -1,4 +1,4 @@ -import { createRegistry } from '@oselvar/var-core' +import { createRegistry } from '@varar/core' import { expect, test } from 'vitest' import { generateSnippet } from '../src/snippet.ts' import { diff --git a/typescript/packages/var-language/tests/snippet.test.ts b/typescript/packages/language/tests/snippet.test.ts similarity index 98% rename from typescript/packages/var-language/tests/snippet.test.ts rename to typescript/packages/language/tests/snippet.test.ts index ad29e0bc..6af23c28 100644 --- a/typescript/packages/var-language/tests/snippet.test.ts +++ b/typescript/packages/language/tests/snippet.test.ts @@ -1,5 +1,5 @@ import { ParameterType } from '@cucumber/cucumber-expressions' -import { createRegistry } from '@oselvar/var-core' +import { createRegistry } from '@varar/core' import { expect, test } from 'vitest' import { generateSnippet } from '../src/snippet.ts' diff --git a/typescript/packages/var-language/tests/step-defs.test.ts b/typescript/packages/language/tests/step-defs.test.ts similarity index 95% rename from typescript/packages/var-language/tests/step-defs.test.ts rename to typescript/packages/language/tests/step-defs.test.ts index 4eb3954e..994d6d83 100644 --- a/typescript/packages/var-language/tests/step-defs.test.ts +++ b/typescript/packages/language/tests/step-defs.test.ts @@ -11,7 +11,7 @@ describe('tree-sitter scanner', () => { }) test('discovers a single step call with its source range', () => { - const source = `import { stimulus } from '@oselvar/var' + const source = `import { stimulus } from '@varar/varar' stimulus('I have {int} cukes', (ctx, n) => {}) ` const defs = scanner.discoverStepDefs('steps.ts', source) @@ -24,7 +24,7 @@ stimulus('I have {int} cukes', (ctx, n) => {}) }) test('discovers multiple step calls across the file', () => { - const source = `import { stimulus, sensor } from '@oselvar/var' + const source = `import { stimulus, sensor } from '@varar/varar' stimulus('first', () => {}) stimulus('second', () => {}) sensor('third', () => {}) @@ -35,7 +35,7 @@ sensor('third', () => {}) }) test('handles the destructured-role pattern: const { stimulus } = steps(...)', () => { - const source = `import { steps } from '@oselvar/var' + const source = `import { steps } from '@varar/varar' const { stimulus } = steps(() => ({})) stimulus('I greet {string}', (ctx, name: string) => {}) ` @@ -60,7 +60,7 @@ const obj = { stimulus: 1 } }) test('discovers a paramType from a .param() call with a regexp literal', () => { - const source = `import { steps } from '@oselvar/var' + const source = `import { steps } from '@varar/varar' const { stimulus } = steps(() => ({})).param('airport', /[A-Z]{3}/, (r) => r) ` const defs = scanner.discoverParameterTypes('p.ts', source) diff --git a/typescript/packages/var-language/tests/template.test.ts b/typescript/packages/language/tests/template.test.ts similarity index 100% rename from typescript/packages/var-language/tests/template.test.ts rename to typescript/packages/language/tests/template.test.ts diff --git a/typescript/packages/var-language/tests/test-grammar-loader.ts b/typescript/packages/language/tests/test-grammar-loader.ts similarity index 100% rename from typescript/packages/var-language/tests/test-grammar-loader.ts rename to typescript/packages/language/tests/test-grammar-loader.ts diff --git a/typescript/packages/var-language/tests/tree-sitter-scanner-java.test.ts b/typescript/packages/language/tests/tree-sitter-scanner-java.test.ts similarity index 100% rename from typescript/packages/var-language/tests/tree-sitter-scanner-java.test.ts rename to typescript/packages/language/tests/tree-sitter-scanner-java.test.ts diff --git a/typescript/packages/var-language/tests/tree-sitter-scanner-kotlin.test.ts b/typescript/packages/language/tests/tree-sitter-scanner-kotlin.test.ts similarity index 100% rename from typescript/packages/var-language/tests/tree-sitter-scanner-kotlin.test.ts rename to typescript/packages/language/tests/tree-sitter-scanner-kotlin.test.ts diff --git a/typescript/packages/var-language/tests/tree-sitter-scanner-python.test.ts b/typescript/packages/language/tests/tree-sitter-scanner-python.test.ts similarity index 100% rename from typescript/packages/var-language/tests/tree-sitter-scanner-python.test.ts rename to typescript/packages/language/tests/tree-sitter-scanner-python.test.ts diff --git a/typescript/packages/var-language/tests/tree-sitter-scanner-ruby.test.ts b/typescript/packages/language/tests/tree-sitter-scanner-ruby.test.ts similarity index 100% rename from typescript/packages/var-language/tests/tree-sitter-scanner-ruby.test.ts rename to typescript/packages/language/tests/tree-sitter-scanner-ruby.test.ts diff --git a/typescript/packages/var-language/tests/tree-sitter-scanner-rust.test.ts b/typescript/packages/language/tests/tree-sitter-scanner-rust.test.ts similarity index 100% rename from typescript/packages/var-language/tests/tree-sitter-scanner-rust.test.ts rename to typescript/packages/language/tests/tree-sitter-scanner-rust.test.ts diff --git a/typescript/packages/var-language/tests/tree-sitter-scanner.test.ts b/typescript/packages/language/tests/tree-sitter-scanner.test.ts similarity index 100% rename from typescript/packages/var-language/tests/tree-sitter-scanner.test.ts rename to typescript/packages/language/tests/tree-sitter-scanner.test.ts diff --git a/typescript/packages/var-language/tsconfig.json b/typescript/packages/language/tsconfig.json similarity index 100% rename from typescript/packages/var-language/tsconfig.json rename to typescript/packages/language/tsconfig.json diff --git a/typescript/packages/var-language/vitest.config.ts b/typescript/packages/language/vitest.config.ts similarity index 100% rename from typescript/packages/var-language/vitest.config.ts rename to typescript/packages/language/vitest.config.ts diff --git a/typescript/packages/lsp/README.md b/typescript/packages/lsp/README.md new file mode 100644 index 00000000..b3d0f8c2 --- /dev/null +++ b/typescript/packages/lsp/README.md @@ -0,0 +1,5 @@ +# @varar/lsp + +The Language Server Protocol server for Varar — diagnostics, semantic tokens, and rename +support for `.md` specs and their step definitions. Consumed by editor extensions +such as `@varar/varar-vscode`. diff --git a/typescript/packages/var-lsp/package.json b/typescript/packages/lsp/package.json similarity index 80% rename from typescript/packages/var-lsp/package.json rename to typescript/packages/lsp/package.json index d1991466..03af35ce 100644 --- a/typescript/packages/var-lsp/package.json +++ b/typescript/packages/lsp/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/var-lsp", + "name": "@varar/lsp", "version": "0.4.2", "type": "module", "exports": { @@ -15,7 +15,7 @@ "main": "./src/server.ts", "types": "./src/server.ts", "bin": { - "var-lsp": "./dist/bin.js" + "varar-lsp": "./dist/bin.js" }, "files": [ "dist", @@ -26,9 +26,9 @@ "test": "vitest run" }, "dependencies": { - "@oselvar/var-config": "workspace:*", - "@oselvar/var-core": "workspace:*", - "@oselvar/var-language": "workspace:*", + "@varar/config": "workspace:*", + "@varar/core": "workspace:*", + "@varar/language": "workspace:*", "@tree-sitter-grammars/tree-sitter-kotlin": "^1.1.0", "tree-sitter-java": "^0.23.5", "tree-sitter-python": "^0.25.0", @@ -55,7 +55,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", - "directory": "typescript/packages/var-lsp" + "url": "git+https://github.com/oselvar/varar.git", + "directory": "typescript/packages/lsp" } } diff --git a/typescript/packages/var-lsp/src/bin.ts b/typescript/packages/lsp/src/bin.ts similarity index 92% rename from typescript/packages/var-lsp/src/bin.ts rename to typescript/packages/lsp/src/bin.ts index 33e155b6..c78de23e 100644 --- a/typescript/packages/var-lsp/src/bin.ts +++ b/typescript/packages/lsp/src/bin.ts @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { loadVarConfig } from '@oselvar/var-config' +import { loadVarConfig } from '@varar/config' import { createConnection, ProposedFeatures } from 'vscode-languageserver/node' import { createNodeFileSystem } from './node-file-system.ts' import { createNodeGrammarLoader } from './node-grammar-loader.ts' diff --git a/typescript/packages/var-lsp/src/file-system.ts b/typescript/packages/lsp/src/file-system.ts similarity index 89% rename from typescript/packages/var-lsp/src/file-system.ts rename to typescript/packages/lsp/src/file-system.ts index 804688fe..98fd46a0 100644 --- a/typescript/packages/var-lsp/src/file-system.ts +++ b/typescript/packages/lsp/src/file-system.ts @@ -1,4 +1,4 @@ -import type { VarGlobs } from '@oselvar/var-config' +import type { VarGlobs } from '@varar/config' export interface FileSystem { list(globs: VarGlobs): Promise diff --git a/typescript/packages/var-lsp/src/handlers.ts b/typescript/packages/lsp/src/handlers.ts similarity index 99% rename from typescript/packages/var-lsp/src/handlers.ts rename to typescript/packages/lsp/src/handlers.ts index 45d91c53..027ef266 100644 --- a/typescript/packages/var-lsp/src/handlers.ts +++ b/typescript/packages/lsp/src/handlers.ts @@ -4,7 +4,7 @@ import { inferStepRole, renderExpression, type StepKind, -} from '@oselvar/var-core' +} from '@varar/core' import { createTypeScriptSnippetEmitter, emitterForLanguage, @@ -12,7 +12,7 @@ import { languageIdForPath, type MatchRef, type SnippetEmitter, -} from '@oselvar/var-language' +} from '@varar/language' import type { GenerateSnippetResult, HandlerSync, diff --git a/typescript/packages/var-lsp/src/node-file-system.ts b/typescript/packages/lsp/src/node-file-system.ts similarity index 100% rename from typescript/packages/var-lsp/src/node-file-system.ts rename to typescript/packages/lsp/src/node-file-system.ts diff --git a/typescript/packages/var-lsp/src/node-grammar-loader.ts b/typescript/packages/lsp/src/node-grammar-loader.ts similarity index 96% rename from typescript/packages/var-lsp/src/node-grammar-loader.ts rename to typescript/packages/lsp/src/node-grammar-loader.ts index 448b9b5e..c545cdbd 100644 --- a/typescript/packages/var-lsp/src/node-grammar-loader.ts +++ b/typescript/packages/lsp/src/node-grammar-loader.ts @@ -1,7 +1,7 @@ import { readFile } from 'node:fs/promises' import { basename, join } from 'node:path' import { fileURLToPath } from 'node:url' -import type { GrammarLoader } from '@oselvar/var-language' +import type { GrammarLoader } from '@varar/language' const GRAMMAR_FILES: Readonly> = { typescript: 'tree-sitter-typescript/tree-sitter-typescript.wasm', diff --git a/typescript/packages/var-lsp/src/protocol.ts b/typescript/packages/lsp/src/protocol.ts similarity index 100% rename from typescript/packages/var-lsp/src/protocol.ts rename to typescript/packages/lsp/src/protocol.ts diff --git a/typescript/packages/var-lsp/src/run-results.test.ts b/typescript/packages/lsp/src/run-results.test.ts similarity index 97% rename from typescript/packages/var-lsp/src/run-results.test.ts rename to typescript/packages/lsp/src/run-results.test.ts index 42d16ba4..2d26f276 100644 --- a/typescript/packages/var-lsp/src/run-results.test.ts +++ b/typescript/packages/lsp/src/run-results.test.ts @@ -1,4 +1,4 @@ -import { hashSource, type SpecResults } from '@oselvar/var-core' +import { hashSource, type SpecResults } from '@varar/core' import { describe, expect, it } from 'vitest' import { createRunResultsStore, runLspDiagnostics } from './run-results.ts' diff --git a/typescript/packages/var-lsp/src/run-results.ts b/typescript/packages/lsp/src/run-results.ts similarity index 99% rename from typescript/packages/var-lsp/src/run-results.ts rename to typescript/packages/lsp/src/run-results.ts index 4badd22a..9d20172c 100644 --- a/typescript/packages/var-lsp/src/run-results.ts +++ b/typescript/packages/lsp/src/run-results.ts @@ -1,4 +1,4 @@ -import { runResultDiagnostics, type SpecResults, spanFromOffsets } from '@oselvar/var-core' +import { runResultDiagnostics, type SpecResults, spanFromOffsets } from '@varar/core' export type LspPosition = { readonly line: number; readonly character: number } export type LspDiagnostic = { diff --git a/typescript/packages/var-lsp/src/semantic-tokens.test.ts b/typescript/packages/lsp/src/semantic-tokens.test.ts similarity index 97% rename from typescript/packages/var-lsp/src/semantic-tokens.test.ts rename to typescript/packages/lsp/src/semantic-tokens.test.ts index c2c43c88..6fd09434 100644 --- a/typescript/packages/var-lsp/src/semantic-tokens.test.ts +++ b/typescript/packages/lsp/src/semantic-tokens.test.ts @@ -1,4 +1,4 @@ -import type { MatchRef } from '@oselvar/var-language' +import type { MatchRef } from '@varar/language' import { describe, expect, it } from 'vitest' import { SEMANTIC_LEGEND, semanticTokenData } from './semantic-tokens.ts' diff --git a/typescript/packages/var-lsp/src/semantic-tokens.ts b/typescript/packages/lsp/src/semantic-tokens.ts similarity index 97% rename from typescript/packages/var-lsp/src/semantic-tokens.ts rename to typescript/packages/lsp/src/semantic-tokens.ts index 2e45b73a..639e5942 100644 --- a/typescript/packages/var-lsp/src/semantic-tokens.ts +++ b/typescript/packages/lsp/src/semantic-tokens.ts @@ -1,4 +1,4 @@ -import type { MatchRef } from '@oselvar/var-language' +import type { MatchRef } from '@varar/language' export const SEMANTIC_LEGEND = { tokenTypes: ['function', 'parameter'] as const, diff --git a/typescript/packages/var-lsp/src/server.ts b/typescript/packages/lsp/src/server.ts similarity index 100% rename from typescript/packages/var-lsp/src/server.ts rename to typescript/packages/lsp/src/server.ts diff --git a/typescript/packages/var-lsp/src/store.test.ts b/typescript/packages/lsp/src/store.test.ts similarity index 95% rename from typescript/packages/var-lsp/src/store.test.ts rename to typescript/packages/lsp/src/store.test.ts index fbd25089..c6b01d2a 100644 --- a/typescript/packages/var-lsp/src/store.test.ts +++ b/typescript/packages/lsp/src/store.test.ts @@ -1,4 +1,4 @@ -import { DEFAULT_SNIPPET_TEMPLATE } from '@oselvar/var-language' +import { DEFAULT_SNIPPET_TEMPLATE } from '@varar/language' import { describe, expect, it } from 'vitest' import { createNodeGrammarLoader } from './node-grammar-loader.ts' import { createStore, type FileSystem } from './store.ts' @@ -82,7 +82,7 @@ describe('createStore over a FileSystem', () => { const fs = fakeFs({ '/s.steps.ts': `stimulus('I open the vault', () => {})\n`, '/vault.md': 'The vault is sealed.\n', - '/var.lock.json': JSON.stringify(lock), + '/varar.lock.json': JSON.stringify(lock), }) const store = createStore({ fs, config, grammarLoader }) await store.reindex() @@ -103,7 +103,7 @@ describe('createStore over a FileSystem', () => { const fs = fakeFs({ '/s.steps.ts': `stimulus('I open the vault', () => {})\n`, '/vault.md': 'The vault is sealed.\n', - '/var.lock.json': JSON.stringify(lock), + '/varar.lock.json': JSON.stringify(lock), }) const store = createStore({ fs, config, grammarLoader }) await store.reindex() @@ -112,7 +112,7 @@ describe('createStore over a FileSystem', () => { await store.reindex() expect(store.index().diagnostics.filter((d) => d.code === 'drift')).toHaveLength(0) // The now-prose paragraph is gone from the persisted baseline. - const written = JSON.parse(await fs.read('/var.lock.json')) + const written = JSON.parse(await fs.read('/varar.lock.json')) expect(written.specs['vault.md'].examples).toEqual([]) }) @@ -126,7 +126,7 @@ describe('createStore over a FileSystem', () => { const fs = fakeFs({ '/s.steps.ts': `stimulus('I open the vault', () => {})\n`, '/vault.md': 'I open the vault.\n', - '/var.lock.json': JSON.stringify(lock), + '/varar.lock.json': JSON.stringify(lock), }) const store = createStore({ fs, config, grammarLoader }) await store.reindex() diff --git a/typescript/packages/var-lsp/src/store.ts b/typescript/packages/lsp/src/store.ts similarity index 89% rename from typescript/packages/var-lsp/src/store.ts rename to typescript/packages/lsp/src/store.ts index e794f8f4..787cbb87 100644 --- a/typescript/packages/var-lsp/src/store.ts +++ b/typescript/packages/lsp/src/store.ts @@ -1,4 +1,4 @@ -import type { VarConfig } from '@oselvar/var-config' +import type { VarConfig } from '@varar/config' import { createRegistry, deriveSpecBaseline, @@ -10,7 +10,7 @@ import { type Registry, stringifyVarLock, type VarLock, -} from '@oselvar/var-core' +} from '@varar/core' import { buildWorkspaceIndex, createTreeSitterScanner, @@ -19,7 +19,7 @@ import { languageIdForPath, type StepDefScanner, type WorkspaceIndex, -} from '@oselvar/var-language' +} from '@varar/language' import type { FileSystem } from './file-system.ts' export type { FileSystem } from './file-system.ts' @@ -46,24 +46,24 @@ export type Store = { // Whether a file is a var spec — i.e. it was discovered by the `docs` globs. // There is no `.md` extension to key off of; the config defines specs. isVarDoc(path: string): boolean - // Accept drift for one spec: re-record its var.lock.json baseline to the + // Accept drift for one spec: re-record its varar.lock.json baseline to the // current live examples, so a now-prose paragraph is no longer flagged. The // caller reindexes afterwards to clear the squiggle. acceptDrift(varPath: string): Promise fs(): FileSystem } -// Drift diagnostics for the workspace: for each spec with a var.lock.json +// Drift diagnostics for the workspace: for each spec with a varar.lock.json // baseline entry, a paragraph that was an example and now matches no step. // Returns [] when there is no baseline (e.g. the browser, whose memory FS has -// no var.lock.json — drift there is shown via the run pipeline, not the LSP). +// no varar.lock.json — drift there is shown via the run pipeline, not the LSP). async function driftDiagnosticRefs( fs: FileSystem, config: VarConfig, varFiles: ReadonlyArray<{ readonly path: string; readonly source: string }>, registry: Registry, ): Promise { - const [lockAbs] = await fs.list({ include: ['var.lock.json'], exclude: [] }) + const [lockAbs] = await fs.list({ include: ['varar.lock.json'], exclude: [] }) if (!lockAbs) return [] let lockText: string try { @@ -73,9 +73,9 @@ async function driftDiagnosticRefs( } const lock = parseVarLock(lockText) if (!lock) return [] - // var.lock.json sits at the workspace root; trim it to get the root prefix + // varar.lock.json sits at the workspace root; trim it to get the root prefix // (string-only, so this stays free of node:path for the browser build). - const root = lockAbs.slice(0, lockAbs.length - 'var.lock.json'.length).replace(/[/\\]+$/, '') + const root = lockAbs.slice(0, lockAbs.length - 'varar.lock.json'.length).replace(/[/\\]+$/, '') const refs: DiagnosticRef[] = [] for (const vf of varFiles) { const specPath = toSpecPath(root, vf.path) @@ -161,7 +161,7 @@ export function createStore(deps: StoreDeps): Store { scanner, }) // Drift is a run-result concern, but the LSP surfaces it live: a - // paragraph the committed var.lock.json recorded as an example that now + // paragraph the committed varar.lock.json recorded as an example that now // matches no step gets a warning squiggle. Additive to the index's own // parse/plan diagnostics. const drift = await driftDiagnosticRefs(fs, config, varFiles, current.registry) @@ -177,11 +177,13 @@ export function createStore(deps: StoreDeps): Store { // disk-backed index can't see) are still recognised as spec docs. isVarDoc: (path) => fs.matches(path, config.docs), async acceptDrift(varPath) { - const [lockAbs] = await fs.list({ include: ['var.lock.json'], exclude: [] }) + const [lockAbs] = await fs.list({ include: ['varar.lock.json'], exclude: [] }) // No baseline file yet → nothing has been recorded, so nothing to accept. if (!lockAbs) return const existing = parseVarLock(await fs.read(lockAbs).catch(() => '')) - const root = lockAbs.slice(0, lockAbs.length - 'var.lock.json'.length).replace(/[/\\]+$/, '') + const root = lockAbs + .slice(0, lockAbs.length - 'varar.lock.json'.length) + .replace(/[/\\]+$/, '') const specPath = toSpecPath(root, varPath) const source = await fs.read(varPath) const varDoc = parse(varPath, source, config.scannerPlugins) diff --git a/typescript/packages/var-lsp/src/uri.ts b/typescript/packages/lsp/src/uri.ts similarity index 100% rename from typescript/packages/var-lsp/src/uri.ts rename to typescript/packages/lsp/src/uri.ts diff --git a/typescript/packages/var-lsp/tests/handlers.test.ts b/typescript/packages/lsp/tests/handlers.test.ts similarity index 96% rename from typescript/packages/var-lsp/tests/handlers.test.ts rename to typescript/packages/lsp/tests/handlers.test.ts index c0c4f74d..dae4b7ed 100644 --- a/typescript/packages/var-lsp/tests/handlers.test.ts +++ b/typescript/packages/lsp/tests/handlers.test.ts @@ -1,7 +1,7 @@ import { mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { loadVarConfig } from '@oselvar/var-config' +import { loadVarConfig } from '@varar/config' import { expect, test } from 'vitest' import { buildHandlers } from '../src/handlers.ts' import { createNodeFileSystem } from '../src/node-file-system.ts' @@ -25,7 +25,7 @@ async function makeStore(dir: string) { test('hoverOnMd returns the matching step def expression and source location', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -54,7 +54,7 @@ test('hoverOnMd returns the matching step def expression and source location', a test('definitionFromMd returns the steps.ts location for a matched step', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -90,7 +90,7 @@ test('definitionFromMd returns the steps.ts location for a matched step', async test('hover on the second of two adjacent steps returns the second step (off-by-one regression)', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -122,7 +122,7 @@ sensor('the greeting is {string}', () => {}) test('stepAt resolves the step from a .md match and returns every matched site with values', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -156,7 +156,7 @@ test('stepAt resolves the step from a .md match and returns every matched site w test('stepAt resolves the step from a .ts cucumber-expression literal position', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -186,7 +186,7 @@ test('stepAt resolves the step from a .ts cucumber-expression literal position', test('stepAt returns null when the cursor is on plain prose, outside any step', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync(join(dir, 'a.md'), '# A\n\nThis is just prose, no step here.\n') @@ -207,7 +207,7 @@ test('stepAt returns null when the cursor is on plain prose, outside any step', test('renameStep (literal-only) produces a cascade across the step def + every match site', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -243,7 +243,7 @@ test('renameStep (literal-only) produces a cascade across the step def + every m test('planRename returns added/removed fates so the client can prompt', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -285,7 +285,7 @@ test('planRename returns added/removed fates so the client can prompt', async () test('planRename surfaces a type change as kept + nameUnchanged:false (the client prompts for the new value)', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -324,7 +324,7 @@ stimulus('I fly to {string}', () => {}) test('planRename emits a handlerSync that adds a new typed arg when a parameter is added', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -355,7 +355,7 @@ test('planRename emits a handlerSync that adds a new typed arg when a parameter test('planRename emits a handlerSync that drops a removed arg', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -384,7 +384,7 @@ test('planRename emits a handlerSync that drops a removed arg', async () => { test('planRename emits a handlerSync that swaps the TS type when a param type changes', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -420,7 +420,7 @@ test('renaming a .py step syncs the def parameters in python shape', async () => // expression is used verbatim — mirrors the .ts sibling test's call shape. const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.py"] }\n', ) writeFileSync( @@ -452,7 +452,7 @@ test('renaming a .py step syncs the def parameters in python shape', async () => test('renderExpressionText rebuilds a sentence from an expression + captured values', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -479,7 +479,7 @@ test('renderExpressionText rebuilds a sentence from an expression + captured val test('renameStep refuses when a parameter is added (Phase 4 territory)', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -508,7 +508,7 @@ test('renameStep refuses when a parameter is added (Phase 4 territory)', async ( test('renameStep from a .md uses CucumberExpressionGenerator on the new sentence', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -539,7 +539,7 @@ test('renameStep from a .md uses CucumberExpressionGenerator on the new sentence test('completions: returns a snippet item per registered step, replacing from line start when no keyword is present', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -573,7 +573,7 @@ stimulus('I greet {string}', () => {}) test('completions: replace range starts at the first non-whitespace of the line (no keyword sniffing)', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -605,7 +605,7 @@ test('completions: replace range starts at the first non-whitespace of the line test('completions: a custom {airport} type uses its name as the placeholder', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -633,7 +633,7 @@ stimulus('I fly to {airport}', () => {}) test('completions: returns nothing for non-.md files', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync( @@ -659,7 +659,7 @@ test('completions: returns nothing for non-.md files', async () => { test('generateSnippet turns selected text into a step-definition stub (verbatim, no keyword strip)', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) }) @@ -680,7 +680,7 @@ test('generateSnippet infers stimulus role when position is before a sensor and // before=[], after=['sensor'] → inferStepRole → 'stimulus' const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync(join(dir, 'a.steps.ts'), `sensor('the greeting is {string}', () => {})\n`) @@ -705,7 +705,7 @@ test('generateSnippet infers sensor role when position is after all matched step // before=['stimulus'], after=[] → inferStepRole → 'sensor' const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync(join(dir, 'a.steps.ts'), `stimulus('I greet {string}', () => {})\n`) @@ -729,7 +729,7 @@ test('generateSnippet infers sensor role when position is after all matched step test('generateSnippet picks python when it is the only configured step language', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.py"] }\n', ) writeFileSync(join(dir, 'a.steps.py'), '@stimulus("existing")\ndef _(state):\n pass\n') @@ -750,7 +750,7 @@ test('generateSnippet picks python when it is the only configured step language' test('generateSnippet resolves multi-language by file count, ties by config order', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts", "**/*.steps.py"] }\n', ) // Two python files vs one typescript file: python wins on count. @@ -771,7 +771,7 @@ test('generateSnippet resolves multi-language by file count, ties by config orde test('generateSnippet tie-breaks to the first language in config.steps order', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.py", "**/*.steps.ts"] }\n', ) writeFileSync(join(dir, 'a.steps.ts'), `stimulus('x', () => {})\n`) @@ -790,7 +790,7 @@ test('generateSnippet tie-breaks to the first language in config.steps order', a test('generateSnippet honors a config snippets template override for the picked language', async () => { const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.py"], "snippets": { "python": "PY:{{expression}}" } }\n', ) writeFileSync(join(dir, 'a.steps.py'), '@stimulus("existing")\ndef _(state):\n pass\n') @@ -809,7 +809,7 @@ test('diagnosticsFor does NOT emit anything for a keyword-led but unmatched sent // No Given/When/Then heuristic — step-def generation is selection-driven. const { dir, cleanup } = tempWorkspace((dir) => { writeFileSync( - join(dir, 'var.config.json'), + join(dir, 'varar.config.json'), '{ "docs": { "include": ["**/*.md"], "exclude": [] }, "steps": ["**/*.steps.ts"] }\n', ) writeFileSync(join(dir, 'b.md'), '# B\n\nGiven I have 5 cukes') diff --git a/typescript/packages/var-lsp/tests/smoke.test.ts b/typescript/packages/lsp/tests/smoke.test.ts similarity index 100% rename from typescript/packages/var-lsp/tests/smoke.test.ts rename to typescript/packages/lsp/tests/smoke.test.ts diff --git a/typescript/packages/var-lsp/tsconfig.json b/typescript/packages/lsp/tsconfig.json similarity index 100% rename from typescript/packages/var-lsp/tsconfig.json rename to typescript/packages/lsp/tsconfig.json diff --git a/typescript/packages/var-lsp/vitest.config.ts b/typescript/packages/lsp/vitest.config.ts similarity index 100% rename from typescript/packages/var-lsp/vitest.config.ts rename to typescript/packages/lsp/vitest.config.ts diff --git a/typescript/packages/var-runner/package.json b/typescript/packages/runner/package.json similarity index 71% rename from typescript/packages/var-runner/package.json rename to typescript/packages/runner/package.json index 734d3a6b..ddb292bc 100644 --- a/typescript/packages/var-runner/package.json +++ b/typescript/packages/runner/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/var-runner", + "name": "@varar/runner", "version": "0.4.2", "type": "module", "exports": { @@ -19,9 +19,9 @@ "test": "vitest run" }, "dependencies": { - "@oselvar/var": "workspace:*", - "@oselvar/var-config": "workspace:*", - "@oselvar/var-core": "workspace:*" + "@varar/varar": "workspace:*", + "@varar/config": "workspace:*", + "@varar/core": "workspace:*" }, "publishConfig": { "exports": { @@ -36,7 +36,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", - "directory": "typescript/packages/var-runner" + "url": "git+https://github.com/oselvar/varar.git", + "directory": "typescript/packages/runner" } } diff --git a/typescript/packages/var-runner/src/baseline-store.ts b/typescript/packages/runner/src/baseline-store.ts similarity index 77% rename from typescript/packages/var-runner/src/baseline-store.ts rename to typescript/packages/runner/src/baseline-store.ts index 0ae07c73..09646e99 100644 --- a/typescript/packages/var-runner/src/baseline-store.ts +++ b/typescript/packages/runner/src/baseline-store.ts @@ -1,13 +1,13 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' -import type { BaselineStore } from '@oselvar/var-core' +import type { BaselineStore } from '@varar/core' -// The committed drift baseline lives at the project root as var.lock.json. +// The committed drift baseline lives at the project root as varar.lock.json. export function varLockPath(cwd: string): string { - return join(cwd, 'var.lock.json') + return join(cwd, 'varar.lock.json') } -// The Node BaselineStore: var.lock.json on disk. The core owns the format; +// The Node BaselineStore: varar.lock.json on disk. The core owns the format; // this adapter only reads and writes the raw text. export function createFileBaselineStore(cwd: string): BaselineStore { const path = varLockPath(cwd) diff --git a/typescript/packages/var-runner/src/index.ts b/typescript/packages/runner/src/index.ts similarity index 100% rename from typescript/packages/var-runner/src/index.ts rename to typescript/packages/runner/src/index.ts diff --git a/typescript/packages/var-runner/src/render.ts b/typescript/packages/runner/src/render.ts similarity index 97% rename from typescript/packages/var-runner/src/render.ts rename to typescript/packages/runner/src/render.ts index df3addf0..315c3f3c 100644 --- a/typescript/packages/var-runner/src/render.ts +++ b/typescript/packages/runner/src/render.ts @@ -1,4 +1,4 @@ -import { isCellMismatchError, isDocStringMismatchError, ReturnShapeError } from '@oselvar/var-core' +import { isCellMismatchError, isDocStringMismatchError, ReturnShapeError } from '@varar/core' /** * Render a step failure as a human-readable string, anchored to the source `.md` diff --git a/typescript/packages/var-runner/src/run.ts b/typescript/packages/runner/src/run.ts similarity index 97% rename from typescript/packages/var-runner/src/run.ts rename to typescript/packages/runner/src/run.ts index c658ac11..17f47349 100644 --- a/typescript/packages/var-runner/src/run.ts +++ b/typescript/packages/runner/src/run.ts @@ -9,7 +9,7 @@ import { type Registry, type Reporter, type ScannerPlugin, -} from '@oselvar/var-core' +} from '@varar/core' export function examplesWithRuns( executionPlan: ExecutionPlan, diff --git a/typescript/packages/var-runner/src/steps.ts b/typescript/packages/runner/src/steps.ts similarity index 72% rename from typescript/packages/var-runner/src/steps.ts rename to typescript/packages/runner/src/steps.ts index d2f5379c..18aaa638 100644 --- a/typescript/packages/var-runner/src/steps.ts +++ b/typescript/packages/runner/src/steps.ts @@ -1,7 +1,7 @@ import { pathToFileURL } from 'node:url' -import { _resetBuilder, buildRegistry, contextFactory } from '@oselvar/var/registry' -import { findFiles } from '@oselvar/var-config' -import type { Registry } from '@oselvar/var-core' +import { findFiles } from '@varar/config' +import type { Registry } from '@varar/core' +import { _resetBuilder, buildRegistry, contextFactory } from '@varar/varar/registry' export type LoadedSteps = { readonly registry: Registry diff --git a/typescript/packages/var-runner/tests/baseline-store.test.ts b/typescript/packages/runner/tests/baseline-store.test.ts similarity index 89% rename from typescript/packages/var-runner/tests/baseline-store.test.ts rename to typescript/packages/runner/tests/baseline-store.test.ts index 6e01baf8..e6ebc521 100644 --- a/typescript/packages/var-runner/tests/baseline-store.test.ts +++ b/typescript/packages/runner/tests/baseline-store.test.ts @@ -12,7 +12,7 @@ afterEach(() => { rmSync(dir, { recursive: true, force: true }) }) -test('read returns null when var.lock.json is absent', () => { +test('read returns null when varar.lock.json is absent', () => { expect(createFileBaselineStore(dir).read()).toBeNull() }) @@ -20,7 +20,7 @@ test('write then read round-trips the raw contents', () => { const store = createFileBaselineStore(dir) store.write('{"version":1,"specs":{}}\n') expect(store.read()).toBe('{"version":1,"specs":{}}\n') - // Written to var.lock.json at the project root. + // Written to varar.lock.json at the project root. expect(readFileSync(varLockPath(dir), 'utf8')).toBe('{"version":1,"specs":{}}\n') }) diff --git a/typescript/packages/var-runner/tests/fixtures/calc.steps.ts b/typescript/packages/runner/tests/fixtures/calc.steps.ts similarity index 75% rename from typescript/packages/var-runner/tests/fixtures/calc.steps.ts rename to typescript/packages/runner/tests/fixtures/calc.steps.ts index 6833cfed..1e48bedd 100644 --- a/typescript/packages/var-runner/tests/fixtures/calc.steps.ts +++ b/typescript/packages/runner/tests/fixtures/calc.steps.ts @@ -1,4 +1,4 @@ -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus } = steps(() => ({ count: 0 })) diff --git a/typescript/packages/var-runner/tests/render.test.ts b/typescript/packages/runner/tests/render.test.ts similarity index 97% rename from typescript/packages/var-runner/tests/render.test.ts rename to typescript/packages/runner/tests/render.test.ts index 8f732288..7728736e 100644 --- a/typescript/packages/var-runner/tests/render.test.ts +++ b/typescript/packages/runner/tests/render.test.ts @@ -1,5 +1,5 @@ -import type { CellDiff, DocStringDiff } from '@oselvar/var-core' -import { CellMismatchError, DocStringMismatchError, ReturnShapeError } from '@oselvar/var-core' +import type { CellDiff, DocStringDiff } from '@varar/core' +import { CellMismatchError, DocStringMismatchError, ReturnShapeError } from '@varar/core' import { expect, test } from 'vitest' import { renderFailure } from '../src/render.ts' diff --git a/typescript/packages/var-runner/tests/run.test.ts b/typescript/packages/runner/tests/run.test.ts similarity index 98% rename from typescript/packages/var-runner/tests/run.test.ts rename to typescript/packages/runner/tests/run.test.ts index 0dfa8160..8a767f01 100644 --- a/typescript/packages/var-runner/tests/run.test.ts +++ b/typescript/packages/runner/tests/run.test.ts @@ -1,4 +1,4 @@ -import { addStep, createRegistry, type Diagnostic } from '@oselvar/var-core' +import { addStep, createRegistry, type Diagnostic } from '@varar/core' import { expect, test } from 'vitest' import { examplesWithRuns, planSpec, RecordingReporter } from '../src/run.ts' diff --git a/typescript/packages/var-runner/tests/steps.test.ts b/typescript/packages/runner/tests/steps.test.ts similarity index 95% rename from typescript/packages/var-runner/tests/steps.test.ts rename to typescript/packages/runner/tests/steps.test.ts index 46420f7a..8f966386 100644 --- a/typescript/packages/var-runner/tests/steps.test.ts +++ b/typescript/packages/runner/tests/steps.test.ts @@ -4,7 +4,7 @@ import { expect, test } from 'vitest' import { loadSteps } from '../src/steps.ts' // Fixture step files live within the package directory so Node can resolve -// @oselvar/var from this package's own node_modules. +// @varar/varar from this package's own node_modules. const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), 'fixtures') test('loadSteps builds registry with registered steps', async () => { diff --git a/typescript/packages/var-runner/tsconfig.json b/typescript/packages/runner/tsconfig.json similarity index 100% rename from typescript/packages/var-runner/tsconfig.json rename to typescript/packages/runner/tsconfig.json diff --git a/typescript/packages/var-runner/vitest.config.ts b/typescript/packages/runner/vitest.config.ts similarity index 79% rename from typescript/packages/var-runner/vitest.config.ts rename to typescript/packages/runner/vitest.config.ts index 25806da3..160d00d4 100644 --- a/typescript/packages/var-runner/vitest.config.ts +++ b/typescript/packages/runner/vitest.config.ts @@ -4,6 +4,6 @@ export default defineConfig({ test: { include: ['tests/**/*.test.ts'], // Inline workspace packages so vite transforms them from source. - server: { deps: { inline: [/^@oselvar\//] } }, + server: { deps: { inline: [/^@varar\//] } }, }, }) diff --git a/typescript/packages/var-cli/README.md b/typescript/packages/var-cli/README.md deleted file mode 100644 index 69a9ada8..00000000 --- a/typescript/packages/var-cli/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# @oselvar/var-cli - -The `var` command-line runner for Vár specs: `var run`, `var lint`, and `var init`. -The imperative shell around `@oselvar/var-core`. diff --git a/typescript/packages/var-core/README.md b/typescript/packages/var-core/README.md deleted file mode 100644 index d1b3f288..00000000 --- a/typescript/packages/var-core/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# @oselvar/var-core - -The pure functional core of Vár: parser, matcher, planner, executor, AST, diagnostics, -and the return-based comparison engine. Pure functions over immutable data — no -globals, no I/O, no side effects. - -**Internal.** Do not depend on this package directly. Write step definitions against -`@oselvar/var`; integrate with a test runner via an adapter such as -`@oselvar/var-vitest`. This package's surface is broad and may change without notice. diff --git a/typescript/packages/var-language/README.md b/typescript/packages/var-language/README.md deleted file mode 100644 index 9494352d..00000000 --- a/typescript/packages/var-language/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @oselvar/var-language - -Static analysis for Vár step definitions and specs: a TypeScript-based scanner that -discovers step definitions and custom parameter types, and a workspace indexer that -matches specs to step definitions. Used by `@oselvar/var-lsp` and the website. diff --git a/typescript/packages/var-lsp/README.md b/typescript/packages/var-lsp/README.md deleted file mode 100644 index 3c2357ae..00000000 --- a/typescript/packages/var-lsp/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @oselvar/var-lsp - -The Language Server Protocol server for Vár — diagnostics, semantic tokens, and rename -support for `.md` specs and their step definitions. Consumed by editor extensions -such as `@oselvar/var-vscode`. diff --git a/typescript/packages/var-vitest/README.md b/typescript/packages/var-vitest/README.md deleted file mode 100644 index b6164dbe..00000000 --- a/typescript/packages/var-vitest/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# @oselvar/var-vitest - -The vitest adapter for Vár. Wire the plugin into your `vitest.config.ts` so `.md` -files run as tests, and add the results reporter: - -```ts -import varPlugin from '@oselvar/var-vitest' -import { VarResultsReporter } from '@oselvar/var-vitest/reporter' - -export default { plugins: [varPlugin()], test: { reporters: ['default', new VarResultsReporter()] } } -``` - -Write your step definitions against `@oselvar/var`, not this package. diff --git a/typescript/packages/var-vscode/README.md b/typescript/packages/var-vscode/README.md deleted file mode 100644 index 94715acb..00000000 --- a/typescript/packages/var-vscode/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Vár for VS Code - -The VS Code extension for Vár. Bundles the `@oselvar/var-lsp` language server to -provide diagnostics, highlighting, and rename support for `.md` files. diff --git a/typescript/packages/var/README.md b/typescript/packages/varar/README.md similarity index 70% rename from typescript/packages/var/README.md rename to typescript/packages/varar/README.md index 3fc36e0a..5d6afa0d 100644 --- a/typescript/packages/var/README.md +++ b/typescript/packages/varar/README.md @@ -1,17 +1,17 @@ -# @oselvar/var +# @varar/varar The package you write step definitions against. Import `steps`, give it a factory for your scenario state (and optionally custom parameter types), and use the returned `stimulus` / `sensor` functions to bind Cucumber-expression steps. ```ts -import { steps } from '@oselvar/var' +import { steps } from '@varar/varar' const { stimulus, sensor } = steps(() => ({ greeting: '' })) stimulus('I greet {string}', (_state, name) => ({ greeting: `Hello, ${name}!` })) sensor('the greeting is {string}', (state) => state.greeting) ``` -This is a thin stateful shell over the pure `@oselvar/var-core`. Adapters use the -`@oselvar/var/registry` subpath for the registry-building glue; step authors never +This is a thin stateful shell over the pure `@varar/core`. Adapters use the +`@varar/varar/registry` subpath for the registry-building glue; step authors never need it. diff --git a/typescript/packages/var/package.json b/typescript/packages/varar/package.json similarity index 84% rename from typescript/packages/var/package.json rename to typescript/packages/varar/package.json index c54f9382..428a3e48 100644 --- a/typescript/packages/var/package.json +++ b/typescript/packages/varar/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/var", + "name": "@varar/varar", "version": "0.4.2", "type": "module", "exports": { @@ -23,7 +23,7 @@ "test": "vitest run" }, "dependencies": { - "@oselvar/var-core": "workspace:*" + "@varar/core": "workspace:*" }, "devDependencies": { "vitest": "^4.1.10" @@ -45,7 +45,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", - "directory": "typescript/packages/var" + "url": "git+https://github.com/oselvar/varar.git", + "directory": "typescript/packages/varar" } } diff --git a/typescript/packages/var/src/index.ts b/typescript/packages/varar/src/index.ts similarity index 72% rename from typescript/packages/var/src/index.ts rename to typescript/packages/varar/src/index.ts index 9d06b4fa..e08696c2 100644 --- a/typescript/packages/var/src/index.ts +++ b/typescript/packages/varar/src/index.ts @@ -1,4 +1,4 @@ // Public entry point for step authors. Intentionally minimal — only `steps` // (and its `Steps` type). The stateful implementation and the adapter glue live -// in ./internal.js; the glue is exposed separately via @oselvar/var/registry. +// in ./internal.js; the glue is exposed separately via @varar/varar/registry. export { type Steps, steps } from './internal.ts' diff --git a/typescript/packages/var/src/internal.ts b/typescript/packages/varar/src/internal.ts similarity index 99% rename from typescript/packages/var/src/internal.ts rename to typescript/packages/varar/src/internal.ts index 94dfa9e6..35dc1e72 100644 --- a/typescript/packages/var/src/internal.ts +++ b/typescript/packages/varar/src/internal.ts @@ -5,7 +5,7 @@ import { type Registry, type StepHandler, type StepKind, -} from '@oselvar/var-core' +} from '@varar/core' type Entry = { readonly expression: string @@ -252,7 +252,7 @@ export function buildRegistry(): Registry { // wire shape toRegistryArtifact serializes. regexp is the bare pattern // source (RegExp.source — no flags/delimiters), the cross-port convention // every language's registry golden uses. Internal-only: exported via -// @oselvar/var/registry beside _resetBuilder, never from the package root. +// @varar/varar/registry beside _resetBuilder, never from the package root. export function _customParameterTypes(): ReadonlyArray<{ readonly name: string readonly regexp: string diff --git a/typescript/packages/var/src/registry.ts b/typescript/packages/varar/src/registry.ts similarity index 100% rename from typescript/packages/var/src/registry.ts rename to typescript/packages/varar/src/registry.ts diff --git a/typescript/packages/var/tests/api.test.ts b/typescript/packages/varar/tests/api.test.ts similarity index 100% rename from typescript/packages/var/tests/api.test.ts rename to typescript/packages/varar/tests/api.test.ts diff --git a/typescript/packages/var/tests/caller-location.test.ts b/typescript/packages/varar/tests/caller-location.test.ts similarity index 92% rename from typescript/packages/var/tests/caller-location.test.ts rename to typescript/packages/varar/tests/caller-location.test.ts index 1b0c6a24..075418a5 100644 --- a/typescript/packages/var/tests/caller-location.test.ts +++ b/typescript/packages/varar/tests/caller-location.test.ts @@ -65,9 +65,9 @@ test('Firefox bundled (no Error header): both depths resolve to the steps file', test('unbundled dist (V8): skips internal frames, returns the caller module', () => { const stack = [ 'Error: locate', - ' at callerLocation (/repo/typescript/packages/var/dist/internal.js:400:15)', - ' at registerStep (/repo/typescript/packages/var/dist/internal.js:30:20)', - ' at Object.stimulus (/repo/typescript/packages/var/dist/internal.js:250:10)', + ' at callerLocation (/repo/typescript/packages/varar/dist/internal.js:400:15)', + ' at registerStep (/repo/typescript/packages/varar/dist/internal.js:30:20)', + ' at Object.stimulus (/repo/typescript/packages/varar/dist/internal.js:250:10)', ' at /repo/app/tests/library.steps.ts:45:3', ].join('\n') expect(_callerLocationFromStack(stack)).toEqual({ diff --git a/typescript/packages/var/tests/conformance-param-types.test.ts b/typescript/packages/varar/tests/conformance-param-types.test.ts similarity index 100% rename from typescript/packages/var/tests/conformance-param-types.test.ts rename to typescript/packages/varar/tests/conformance-param-types.test.ts diff --git a/typescript/packages/var/tests/conformance.test.ts b/typescript/packages/varar/tests/conformance.test.ts similarity index 94% rename from typescript/packages/var/tests/conformance.test.ts rename to typescript/packages/varar/tests/conformance.test.ts index 02b56b37..1c57a27c 100644 --- a/typescript/packages/var/tests/conformance.test.ts +++ b/typescript/packages/varar/tests/conformance.test.ts @@ -1,7 +1,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs' import { resolve } from 'node:path' import { pathToFileURL } from 'node:url' -import { canonicalStringify, parse, runConformance } from '@oselvar/var-core' +import { canonicalStringify, parse, runConformance } from '@varar/core' import { describe, expect, test } from 'vitest' import { _customParameterTypes, @@ -21,7 +21,7 @@ const ARTIFACTS = [ ['trace', 'trace'], ] as const -// NOTE: these tests share @oselvar/var module-scope state, so they must +// NOTE: these tests share @varar/varar module-scope state, so they must // run sequentially within this file. Do NOT mark them `test.concurrent`. // Run with `vitest run` (one-shot), NOT watch mode: bundles are loaded via // dynamic import(), which is cached, so a watch re-run would re-clear the diff --git a/typescript/packages/var-vitest/tsconfig.json b/typescript/packages/varar/tsconfig.json similarity index 100% rename from typescript/packages/var-vitest/tsconfig.json rename to typescript/packages/varar/tsconfig.json diff --git a/typescript/packages/var/vitest.config.ts b/typescript/packages/varar/vitest.config.ts similarity index 78% rename from typescript/packages/var/vitest.config.ts rename to typescript/packages/varar/vitest.config.ts index 7da75d71..f8325aae 100644 --- a/typescript/packages/var/vitest.config.ts +++ b/typescript/packages/varar/vitest.config.ts @@ -5,13 +5,13 @@ export default defineConfig({ resolve: { alias: [ { - find: /^@oselvar\/var$/, + find: /^@varar\/varar$/, replacement: fileURLToPath(new URL('./src/index.ts', import.meta.url)), }, ], }, test: { include: ['tests/**/*.test.ts'], - server: { deps: { inline: [/^@oselvar\//] } }, + server: { deps: { inline: [/^@varar\//] } }, }, }) diff --git a/typescript/packages/vitest/README.md b/typescript/packages/vitest/README.md new file mode 100644 index 00000000..aeea9328 --- /dev/null +++ b/typescript/packages/vitest/README.md @@ -0,0 +1,13 @@ +# @varar/vitest + +The vitest adapter for Varar. Wire the plugin into your `vitest.config.ts` so `.md` +files run as tests, and add the results reporter: + +```ts +import varPlugin from '@varar/vitest' +import { VarResultsReporter } from '@varar/vitest/reporter' + +export default { plugins: [varPlugin()], test: { reporters: ['default', new VarResultsReporter()] } } +``` + +Write your step definitions against `@varar/varar`, not this package. diff --git a/typescript/packages/var-vitest/package.json b/typescript/packages/vitest/package.json similarity index 77% rename from typescript/packages/var-vitest/package.json rename to typescript/packages/vitest/package.json index b47ddab5..2173150f 100644 --- a/typescript/packages/var-vitest/package.json +++ b/typescript/packages/vitest/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/var-vitest", + "name": "@varar/vitest", "version": "0.4.2", "type": "module", "exports": { @@ -27,11 +27,11 @@ "test": "vitest run" }, "dependencies": { - "@oselvar/var": "workspace:*", - "@oselvar/var-config": "workspace:*", - "@oselvar/var-core": "workspace:*", - "@oselvar/var-language": "workspace:*", - "@oselvar/var-runner": "workspace:*", + "@varar/varar": "workspace:*", + "@varar/config": "workspace:*", + "@varar/core": "workspace:*", + "@varar/language": "workspace:*", + "@varar/runner": "workspace:*", "tree-sitter-typescript": "^0.23.2" }, "peerDependencies": { @@ -59,7 +59,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", - "directory": "typescript/packages/var-vitest" + "url": "git+https://github.com/oselvar/varar.git", + "directory": "typescript/packages/vitest" } } diff --git a/typescript/packages/var-vitest/src/index.ts b/typescript/packages/vitest/src/index.ts similarity index 100% rename from typescript/packages/var-vitest/src/index.ts rename to typescript/packages/vitest/src/index.ts diff --git a/typescript/packages/var-vitest/src/node-grammar-loader.ts b/typescript/packages/vitest/src/node-grammar-loader.ts similarity index 94% rename from typescript/packages/var-vitest/src/node-grammar-loader.ts rename to typescript/packages/vitest/src/node-grammar-loader.ts index 029769b5..c18b8fe6 100644 --- a/typescript/packages/var-vitest/src/node-grammar-loader.ts +++ b/typescript/packages/vitest/src/node-grammar-loader.ts @@ -1,6 +1,6 @@ import { readFile } from 'node:fs/promises' import { fileURLToPath } from 'node:url' -import type { GrammarLoader } from '@oselvar/var-language' +import type { GrammarLoader } from '@varar/language' // The vitest adapter only ever scans `.steps.ts` / `.steps.tsx` files, so it // needs just the TypeScript grammars — not the full per-language set the LSP diff --git a/typescript/packages/var-vitest/src/plugin.ts b/typescript/packages/vitest/src/plugin.ts similarity index 85% rename from typescript/packages/var-vitest/src/plugin.ts rename to typescript/packages/vitest/src/plugin.ts index f1050942..4d1c0c8a 100644 --- a/typescript/packages/var-vitest/src/plugin.ts +++ b/typescript/packages/vitest/src/plugin.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from 'node:fs' import { relative, resolve, sep } from 'node:path' -import { findFiles, loadVarConfig } from '@oselvar/var-config' -import { parseVarLock, type ScannerPlugin, type SpecBaseline } from '@oselvar/var-core' +import { findFiles, loadVarConfig } from '@varar/config' +import { parseVarLock, type ScannerPlugin, type SpecBaseline } from '@varar/core' import type { Plugin } from 'vite' import { configDefaults } from 'vitest/config' import { discoverStaticExamples, type StaticExample } from './static-examples.ts' @@ -25,22 +25,22 @@ export function varVitestPlugin(options: VarVitestPluginOptions = {}): Plugin { // hook transforms only these into virtual test modules — there is no longer // a `.md` extension to key off of. let specFiles: ReadonlySet = new Set() - // Scanner plugins from var.config.json, in both forms: the resolved + // Scanner plugins from varar.config.json, in both forms: the resolved // instances feed the static planner in this process, and the names are // inlined into the generated virtual module so it can re-resolve them via // var-core's registry (functions can't be serialized into generated // source, names can). let scannerPlugins: ReadonlyArray = [] let pluginNames: ReadonlyArray = [] - // Absolute path to var.config.json when one exists — watched so a config + // Absolute path to varar.config.json when one exists — watched so a config // edit re-transforms specs in watch mode. let configJsonPath: string | undefined - // Absolute path to the committed drift baseline (var.lock.json). - const lockPath = resolve(cwd, 'var.lock.json') + // Absolute path to the committed drift baseline (varar.lock.json). + const lockPath = resolve(cwd, 'varar.lock.json') return { - name: '@oselvar/var-vitest', + name: '@varar/vitest', async config() { - // var.config.json is the single source of truth for which files are specs. + // varar.config.json is the single source of truth for which files are specs. // Drive vitest's collection from it so an excluded `.md` is never handed // to vite as a raw-markdown "script" (which fails to parse). Globs are // made absolute against `cwd`; setting `test.exclude` *replaces* vitest's @@ -49,13 +49,13 @@ export function varVitestPlugin(options: VarVitestPluginOptions = {}): Plugin { const cfg = await loadVarConfig(cwd) const abs = (g: string) => resolve(cwd, g) return { - // Force a single @oselvar/var (and @oselvar/var-core) module instance. + // Force a single @varar/varar (and @varar/core) module instance. // The authoring API (steps) and the registry glue - // (@oselvar/var/registry, used by runtime.ts) MUST share one module so + // (@varar/varar/registry, used by runtime.ts) MUST share one module so // buildRegistry() sees the steps registered via steps(). Under // resolve.preserveSymlinks these can split into two instances, leaving // an empty registry and zero steps run with no error — so we dedupe. - resolve: { dedupe: ['@oselvar/var', '@oselvar/var-core'] }, + resolve: { dedupe: ['@varar/varar', '@varar/core'] }, test: { include: cfg.docs.include.map(abs), exclude: [...configDefaults.exclude, ...cfg.docs.exclude.map(abs)], @@ -68,7 +68,7 @@ export function varVitestPlugin(options: VarVitestPluginOptions = {}): Plugin { specFiles = new Set(findFiles(cwd, cfg.docs.include, cfg.docs.exclude)) scannerPlugins = cfg.scannerPlugins pluginNames = cfg.scannerPluginNames - const abs = resolve(cwd, 'var.config.json') + const abs = resolve(cwd, 'varar.config.json') configJsonPath = existsSync(abs) ? abs : undefined }, async load(id) { @@ -88,7 +88,7 @@ export function varVitestPlugin(options: VarVitestPluginOptions = {}): Plugin { stepFiles: stepFiles.map((path) => ({ path, source: readFileSync(path, 'utf8') })), scannerPlugins, }) - // This spec's baseline entry from var.lock.json (POSIX path, relative to + // This spec's baseline entry from varar.lock.json (POSIX path, relative to // cwd), injected so the runtime can run the read-only drift gate. const specPath = relative(cwd, varPath).split(sep).join('/') const lock = existsSync(lockPath) ? parseVarLock(readFileSync(lockPath, 'utf8')) : null @@ -109,7 +109,7 @@ export type GenerateInput = { readonly varPath: string readonly stepImports: ReadonlyArray readonly source?: string - // Scanner-plugin NAMES from var.config.json. The generated module passes + // Scanner-plugin NAMES from varar.config.json. The generated module passes // them to collectVarExamples, which resolves them against var-core's // registry — functions can't be serialized into generated source, names can. readonly scannerPluginNames: ReadonlyArray @@ -117,7 +117,7 @@ export type GenerateInput = { // becomes a `test("literal name", ...)` call placed at its own markdown // line/column. readonly examples?: ReadonlyArray - // This spec's drift baseline from var.lock.json (or null when unbaselined), + // This spec's drift baseline from varar.lock.json (or null when unbaselined), // inlined so the runtime can run the read-only drift gate. readonly baseline?: SpecBaseline | null } @@ -136,12 +136,12 @@ export function generateVirtualModule(input: GenerateInput): string { const examples = input.examples ?? [] const header: string[] = [ "import { test } from 'vitest'", - // Everything the generated module needs comes from @oselvar/var-vitest — + // Everything the generated module needs comes from @varar/vitest — // the one package the consumer directly depends on. Importing e.g. - // @oselvar/var-core here would fail under pnpm's strict node_modules + // @varar/core here would fail under pnpm's strict node_modules // layout, because the module id (the spec path) resolves in the // consumer's project, where transitive deps are not visible. - "import { collectVarExamples, varTestBody } from '@oselvar/var-vitest/runtime'", + "import { collectVarExamples, varTestBody } from '@varar/vitest/runtime'", ...input.stepImports.map((p) => `import ${JSON.stringify(p)}`), `const PATH = ${pathJson}`, // Diagnostics and the stale-transform guard register their tests inside diff --git a/typescript/packages/var-vitest/src/reporter.ts b/typescript/packages/vitest/src/reporter.ts similarity index 99% rename from typescript/packages/var-vitest/src/reporter.ts rename to typescript/packages/vitest/src/reporter.ts index 1fd1fa47..86ef5f08 100644 --- a/typescript/packages/var-vitest/src/reporter.ts +++ b/typescript/packages/vitest/src/reporter.ts @@ -1,6 +1,6 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { dirname, isAbsolute, join, relative, sep } from 'node:path' -import { type ExampleResult, hashSource, type SpecResults } from '@oselvar/var-core' +import { type ExampleResult, hashSource, type SpecResults } from '@varar/core' import type { Reporter, TestModule } from 'vitest/node' // Structural shape of the slice of vitest's TestModule API the collector reads. diff --git a/typescript/packages/var-vitest/src/runtime.ts b/typescript/packages/vitest/src/runtime.ts similarity index 92% rename from typescript/packages/var-vitest/src/runtime.ts rename to typescript/packages/vitest/src/runtime.ts index ac95e4b2..05f2c6f9 100644 --- a/typescript/packages/var-vitest/src/runtime.ts +++ b/typescript/packages/vitest/src/runtime.ts @@ -1,4 +1,3 @@ -import { buildRegistry, contextFactory } from '@oselvar/var/registry' import { type CellDiff, detectDrift, @@ -9,8 +8,9 @@ import { resolveScannerPlugins, type SpecBaseline, toFailure, -} from '@oselvar/var-core' -import { examplesWithRuns, planSpec } from '@oselvar/var-runner' +} from '@varar/core' +import { examplesWithRuns, planSpec } from '@varar/runner' +import { buildRegistry, contextFactory } from '@varar/varar/registry' import { test } from 'vitest' export type CollectPorts = { @@ -20,20 +20,20 @@ export type CollectPorts = { // `test(...)` callsite — only the real per-example ones. readonly reporter?: Reporter // Opt-in scanner-plugin NAMES (e.g. 'gherkinTables') that the var-vitest - // plugin forwards from var.config.json. Resolved here against var-core's + // plugin forwards from varar.config.json. Resolved here against var-core's // registry: the generated virtual module resolves in the CONSUMER's // project, where pnpm's strict layout only sees direct dependencies — so - // it may import @oselvar/var-vitest but never @oselvar/var-core. + // it may import @varar/vitest but never @varar/core. readonly scannerPlugins?: ReadonlyArray // The number of examples the build-time static plan produced. When the // runtime plan disagrees (a step definition the static scanner could not // see appeared or vanished), a failing guard test is registered instead of // letting the suites silently diverge. readonly expectedCount?: number - // This spec's committed drift baseline (from var.lock.json), injected by the + // This spec's committed drift baseline (from varar.lock.json), injected by the // plugin. When present, drift is detected and reported as a diagnostic (a // failing `var:diagnostic:drift` test) — a read-only gate. The baseline is - // written only by `var run`; VAR_UPDATE=1 skips the gate so you can + // written only by `varar run`; VAR_UPDATE=1 skips the gate so you can // re-record it there without vitest going red first. readonly baseline?: SpecBaseline | null } @@ -70,7 +70,7 @@ export function collectVarExamples( ) // Read-only drift gate: a paragraph the baseline recorded as an example that // now matches no step surfaces as a drift diagnostic (a failing test) unless - // VAR_UPDATE is set (then re-record via `var run --update`). + // VAR_UPDATE is set (then re-record via `varar run --update`). if (ports.baseline) { const update = process.env.VAR_UPDATE === '1' || process.env.VAR_UPDATE === 'true' if (!update) { diff --git a/typescript/packages/var-vitest/src/static-examples.ts b/typescript/packages/vitest/src/static-examples.ts similarity index 90% rename from typescript/packages/var-vitest/src/static-examples.ts rename to typescript/packages/vitest/src/static-examples.ts index 79be7ba7..a789bd89 100644 --- a/typescript/packages/var-vitest/src/static-examples.ts +++ b/typescript/packages/vitest/src/static-examples.ts @@ -1,10 +1,6 @@ -import type { ScannerPlugin } from '@oselvar/var-core' -import { - buildWorkspaceIndex, - createTreeSitterScanner, - type StepDefScanner, -} from '@oselvar/var-language' -import { planSpec } from '@oselvar/var-runner' +import type { ScannerPlugin } from '@varar/core' +import { buildWorkspaceIndex, createTreeSitterScanner, type StepDefScanner } from '@varar/language' +import { planSpec } from '@varar/runner' import { createNodeGrammarLoader } from './node-grammar-loader.ts' // The tree-sitter scanner is created once and reused across every spec the diff --git a/typescript/packages/var-vitest/tests/plugin.test.ts b/typescript/packages/vitest/tests/plugin.test.ts similarity index 92% rename from typescript/packages/var-vitest/tests/plugin.test.ts rename to typescript/packages/vitest/tests/plugin.test.ts index 1c125459..746e317d 100644 --- a/typescript/packages/var-vitest/tests/plugin.test.ts +++ b/typescript/packages/vitest/tests/plugin.test.ts @@ -31,12 +31,12 @@ describe('generateVirtualModule', () => { // mapping holds for every following line. expect(lines[0]).toContain("import { test } from 'vitest'") // Generated code may only import from packages the CONSUMER directly - // depends on (@oselvar/var-vitest, vitest) — a bare '@oselvar/var-core' + // depends on (@varar/vitest, vitest) — a bare '@varar/core' // would not resolve from the spec's path under pnpm's strict layout. expect(lines[0]).toContain( - "import { collectVarExamples, varTestBody } from '@oselvar/var-vitest/runtime'", + "import { collectVarExamples, varTestBody } from '@varar/vitest/runtime'", ) - expect(lines[0]).not.toContain("from '@oselvar/var-core'") + expect(lines[0]).not.toContain("from '@varar/core'") expect(lines[0]).toContain('import "/abs/account.steps.ts"') expect(lines[0]).toContain('const PATH = "/abs/foo.md"') expect(lines[0]).toContain('scannerPlugins: []') @@ -102,7 +102,7 @@ describe('generateVirtualModule', () => { expect(out).toContain('baseline:') }) - test('inlines a null baseline when the spec is not yet in var.lock.json', () => { + test('inlines a null baseline when the spec is not yet in varar.lock.json', () => { const out = generateVirtualModule({ varPath: '/abs/foo.md', stepImports: [], @@ -126,7 +126,7 @@ describe('generateVirtualModule', () => { describe('varVitestPlugin', () => { test('returns a vite plugin object with name and load hook', () => { const plugin = varVitestPlugin() - expect(plugin.name).toBe('@oselvar/var-vitest') + expect(plugin.name).toBe('@varar/vitest') expect(typeof plugin.load).toBe('function') }) }) diff --git a/typescript/packages/var-vitest/tests/reporter.test.ts b/typescript/packages/vitest/tests/reporter.test.ts similarity index 97% rename from typescript/packages/var-vitest/tests/reporter.test.ts rename to typescript/packages/vitest/tests/reporter.test.ts index ca1e40d2..f816b4cb 100644 --- a/typescript/packages/var-vitest/tests/reporter.test.ts +++ b/typescript/packages/vitest/tests/reporter.test.ts @@ -1,5 +1,5 @@ import { join } from 'node:path' -import { hashSource } from '@oselvar/var-core' +import { hashSource } from '@varar/core' import { describe, expect, test } from 'vitest' import { buildSpecResults, diff --git a/typescript/packages/var-vitest/tests/runtime.test.ts b/typescript/packages/vitest/tests/runtime.test.ts similarity index 98% rename from typescript/packages/var-vitest/tests/runtime.test.ts rename to typescript/packages/vitest/tests/runtime.test.ts index fffdef83..2448a5b0 100644 --- a/typescript/packages/var-vitest/tests/runtime.test.ts +++ b/typescript/packages/vitest/tests/runtime.test.ts @@ -1,12 +1,12 @@ -import { steps } from '@oselvar/var' -import { _resetBuilder } from '@oselvar/var/registry' import { type CellDiff, CellMismatchError, type Diagnostic, DocStringMismatchError, type SpecBaseline, -} from '@oselvar/var-core' +} from '@varar/core' +import { steps } from '@varar/varar' +import { _resetBuilder } from '@varar/varar/registry' import { afterEach, beforeEach, expect, test } from 'vitest' import { collectVarExamples, varTestBody } from '../src/runtime.ts' diff --git a/typescript/packages/var-vitest/tests/smoke.test.ts b/typescript/packages/vitest/tests/smoke.test.ts similarity index 100% rename from typescript/packages/var-vitest/tests/smoke.test.ts rename to typescript/packages/vitest/tests/smoke.test.ts diff --git a/typescript/packages/var-vitest/tests/static-examples.test.ts b/typescript/packages/vitest/tests/static-examples.test.ts similarity index 92% rename from typescript/packages/var-vitest/tests/static-examples.test.ts rename to typescript/packages/vitest/tests/static-examples.test.ts index baa5b2e4..af81ee27 100644 --- a/typescript/packages/var-vitest/tests/static-examples.test.ts +++ b/typescript/packages/vitest/tests/static-examples.test.ts @@ -1,7 +1,7 @@ import { expect, test } from 'vitest' import { discoverStaticExamples } from '../src/static-examples.ts' -const STEPS = `import { steps } from '@oselvar/var' +const STEPS = `import { steps } from '@varar/varar' const { sensor } = steps(() => ({})) sensor('the answer is {int}', () => 42) ` @@ -17,7 +17,7 @@ test('discovers only examples with matched steps, named by the whole paragraph', }) test('matches expressions that use a custom parameter type', async () => { - const stepSource = `import { steps } from '@oselvar/var' + const stepSource = `import { steps } from '@varar/varar' const { sensor } = steps(() => ({})).param('color', /red|green/) sensor('the light is {color}', () => 'green') ` diff --git a/typescript/packages/var-vscode/tsconfig.json b/typescript/packages/vitest/tsconfig.json similarity index 100% rename from typescript/packages/var-vscode/tsconfig.json rename to typescript/packages/vitest/tsconfig.json diff --git a/typescript/packages/var-vitest/vitest.config.ts b/typescript/packages/vitest/vitest.config.ts similarity index 81% rename from typescript/packages/var-vitest/vitest.config.ts rename to typescript/packages/vitest/vitest.config.ts index 84771d19..ec2307f2 100644 --- a/typescript/packages/var-vitest/vitest.config.ts +++ b/typescript/packages/vitest/vitest.config.ts @@ -2,15 +2,15 @@ import { defineConfig } from 'vitest/config' import { stripTypescriptSourcemap } from '../../vitest.plugins.js' export default defineConfig({ - // static-examples.ts pulls in @oselvar/var-language, which imports + // static-examples.ts pulls in @varar/language, which imports // `typescript` — inlined below, so vite transforms typescript.js and needs // its dangling sourcemap comment stripped (see vitest.plugins.ts). plugins: [stripTypescriptSourcemap()], test: { include: ['tests/**/*.test.ts'], // Vite by default treats workspace packages as node_modules and skips its - // TS transform. Inline them so cross-package imports (e.g. `@oselvar/var`) + // TS transform. Inline them so cross-package imports (e.g. `@varar/varar`) // resolve `./foo.js` → `./foo.ts` via vite's resolver, no build required. - server: { deps: { inline: [/^@oselvar\//] } }, + server: { deps: { inline: [/^@varar\//] } }, }, }) diff --git a/typescript/packages/var-vscode/.vscodeignore b/typescript/packages/vscode/.vscodeignore similarity index 100% rename from typescript/packages/var-vscode/.vscodeignore rename to typescript/packages/vscode/.vscodeignore diff --git a/typescript/packages/var-vscode/LICENSE b/typescript/packages/vscode/LICENSE similarity index 100% rename from typescript/packages/var-vscode/LICENSE rename to typescript/packages/vscode/LICENSE diff --git a/typescript/packages/vscode/README.md b/typescript/packages/vscode/README.md new file mode 100644 index 00000000..2836505e --- /dev/null +++ b/typescript/packages/vscode/README.md @@ -0,0 +1,4 @@ +# Varar for VS Code + +The VS Code extension for Varar. Bundles the `@varar/lsp` language server to +provide diagnostics, highlighting, and rename support for `.md` files. diff --git a/typescript/packages/var-vscode/esbuild.mjs b/typescript/packages/vscode/esbuild.mjs similarity index 94% rename from typescript/packages/var-vscode/esbuild.mjs rename to typescript/packages/vscode/esbuild.mjs index f9efa31f..2169849e 100644 --- a/typescript/packages/var-vscode/esbuild.mjs +++ b/typescript/packages/vscode/esbuild.mjs @@ -33,7 +33,7 @@ await build({ await build({ ...shared, format: 'esm', - entryPoints: ['../var-lsp/src/bin.ts'], + entryPoints: ['../lsp/src/bin.ts'], outfile: 'dist/server.mjs', banner: { js: [ @@ -54,7 +54,7 @@ await build({ // direct dependency on them — and copy them flat next to the bundle // (basenames are unique across the grammar packages; this mirrors // node-grammar-loader.ts's GRAMMAR_FILES map, one entry per language). -const requireFromLsp = createRequire(resolve('../var-lsp/package.json')) +const requireFromLsp = createRequire(resolve('../lsp/package.json')) for (const specifier of [ 'tree-sitter-typescript/tree-sitter-typescript.wasm', 'tree-sitter-typescript/tree-sitter-tsx.wasm', @@ -73,7 +73,7 @@ for (const specifier of [ // above), which points at dist/server.mjs, so the file must sit next to it. // var-vscode has no direct dependency on web-tree-sitter — resolve it via // var-language's, which does. -const requireFromLanguage = createRequire(resolve('../var-language/package.json')) +const requireFromLanguage = createRequire(resolve('../language/package.json')) await copyFile( requireFromLanguage.resolve('web-tree-sitter/web-tree-sitter.wasm'), 'dist/web-tree-sitter.wasm', diff --git a/typescript/packages/var-vscode/package.json b/typescript/packages/vscode/package.json similarity index 73% rename from typescript/packages/var-vscode/package.json rename to typescript/packages/vscode/package.json index 9f66969c..c70c5551 100644 --- a/typescript/packages/var-vscode/package.json +++ b/typescript/packages/vscode/package.json @@ -1,8 +1,8 @@ { - "name": "oselvar-var", - "displayName": "Vár", + "name": "varar", + "displayName": "Varar", "description": "Markdown-native BDD: highlight matched steps, go-to step definitions, missing-step diagnostics.", - "publisher": "oselvar", + "publisher": "varar", "version": "0.4.2", "type": "module", "engines": { @@ -21,12 +21,12 @@ "onLanguage:python", "onLanguage:java", "onLanguage:kotlin", - "workspaceContains:**/var.config.json" + "workspaceContains:**/varar.config.json" ], "contributes": { "commands": [ { - "command": "oselvar-var.generateStepDefinition", + "command": "varar.generateStepDefinition", "title": "BDD: Generate Step Definition from Selection", "category": "BDD" } @@ -34,14 +34,14 @@ "menus": { "editor/context": [ { - "command": "oselvar-var.generateStepDefinition", + "command": "varar.generateStepDefinition", "when": "editorHasSelection && resourceExtname == .md", "group": "var@1" } ], "commandPalette": [ { - "command": "oselvar-var.generateStepDefinition", + "command": "varar.generateStepDefinition", "when": "editorHasSelection" } ] @@ -51,7 +51,7 @@ "build": "tsc -p tsconfig.json && node esbuild.mjs" }, "dependencies": { - "@oselvar/var-lsp": "workspace:*", + "@varar/lsp": "workspace:*", "vscode-languageclient": "^10.1.0" }, "devDependencies": { @@ -61,8 +61,8 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", - "directory": "typescript/packages/var-vscode" + "url": "git+https://github.com/oselvar/varar.git", + "directory": "typescript/packages/vscode" }, "private": true } diff --git a/typescript/packages/var-vscode/src/extension.ts b/typescript/packages/vscode/src/extension.ts similarity index 96% rename from typescript/packages/var-vscode/src/extension.ts rename to typescript/packages/vscode/src/extension.ts index 9a13f3ee..15b56e43 100644 --- a/typescript/packages/var-vscode/src/extension.ts +++ b/typescript/packages/vscode/src/extension.ts @@ -9,7 +9,7 @@ import type { RenderTextResult, StepAtResult, StepGlob, -} from '@oselvar/var-lsp/protocol' +} from '@varar/lsp/protocol' import { CodeAction, type CodeActionContext, @@ -37,16 +37,16 @@ import { let client: LanguageClient | undefined export function activate(context: ExtensionContext): void { - // The symlink installer (T8) mirrors `packages/var-vscode/` into + // The symlink installer (T8) mirrors `packages/vscode/` into // ~/.vscode/extensions/. Resolve the symlink before walking `..` so we land // at the real `packages/` directory. When the sibling var-lsp checkout // exists we are in dev: run the live LSP sources through tsx. Otherwise we // are a packaged .vsix: use the bundled server next to the extension. const extReal = realpathSync(context.extensionPath) - const devServer = resolve(extReal, '..', 'var-lsp', 'dist', 'bin.js') + const devServer = resolve(extReal, '..', 'lsp', 'dist', 'bin.js') let serverOptions: ServerOptions if (existsSync(devServer)) { - // `@oselvar/var`'s `exports.import` points at `src/index.ts` so we can run + // `@varar/varar`'s `exports.import` points at `src/index.ts` so we can run // tests without a build step. The LSP server reaches the core through that // same entry, so we need tsx to load `.ts` files at runtime. const tsxLoader = resolve(extReal, '..', '..', 'node_modules', 'tsx', 'dist', 'loader.mjs') @@ -79,7 +79,7 @@ export function activate(context: ExtensionContext): void { fileEvents: workspace.createFileSystemWatcher('**/.var/**/*.json'), }, } - client = new LanguageClient('oselvar-var', 'Vár', serverOptions, clientOptions) + client = new LanguageClient('varar', 'Varar', serverOptions, clientOptions) const started = client.start() registerGenerateStepDefinition(context, client, started) registerGenerateCodeAction(context) @@ -105,7 +105,7 @@ function registerGenerateCodeAction(context: ExtensionContext): void { CodeActionKind.RefactorExtract, ) action.command = { - command: 'oselvar-var.generateStepDefinition', + command: 'varar.generateStepDefinition', title: 'Generate Step Definition', } return [action] @@ -123,7 +123,7 @@ function registerGenerateStepDefinition( lspClient: LanguageClient, started: Promise, ): void { - const cmd = commands.registerCommand('oselvar-var.generateStepDefinition', async () => { + const cmd = commands.registerCommand('varar.generateStepDefinition', async () => { const editor = window.activeTextEditor if (!editor) { void window.showInformationMessage('No active editor.') diff --git a/typescript/packages/var/tsconfig.json b/typescript/packages/vscode/tsconfig.json similarity index 100% rename from typescript/packages/var/tsconfig.json rename to typescript/packages/vscode/tsconfig.json diff --git a/typescript/packages/website/astro.config.mjs b/typescript/packages/website/astro.config.mjs index 0f87976c..a92a6e5a 100644 --- a/typescript/packages/website/astro.config.mjs +++ b/typescript/packages/website/astro.config.mjs @@ -40,11 +40,11 @@ const restorePrefs = () => ({ // https://astro.build/config export default defineConfig({ - site: 'https://var.oselvar.com', + site: 'https://varar.dev', integrations: [ restorePrefs(), starlight({ - title: 'Vár', + title: 'Varar', tableOfContents: true, customCss: [ './src/styles/tailwind.css', @@ -56,7 +56,7 @@ export default defineConfig({ components: { ThemeSelect: './src/components/ThemeSelect.astro', }, - social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/oselvar/var' }], + social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/oselvar/vararar' }], sidebar: [ { label: 'Start here', @@ -83,7 +83,7 @@ export default defineConfig({ ], }, { - label: 'Understanding Vár', + label: 'Understanding Varar', items: [ 'explanation/oaths', 'explanation/test-anatomy', @@ -94,7 +94,7 @@ export default defineConfig({ }, ], editLink: { - baseUrl: 'https://github.com/oselvar/var/edit/main/typescript/packages/website/', + baseUrl: 'https://github.com/oselvar/vararar/edit/main/typescript/packages/website/', }, }), ], diff --git a/typescript/packages/website/drafts/doc-strings.mdx b/typescript/packages/website/drafts/doc-strings.mdx index 8fa4fa68..8f5dea8a 100644 --- a/typescript/packages/website/drafts/doc-strings.mdx +++ b/typescript/packages/website/drafts/doc-strings.mdx @@ -1,6 +1,6 @@ --- title: Doc strings -description: How a fenced code block attaches to a step in a Vár spec — passed in as text, and checked against the step's returned string. +description: How a fenced code block attaches to a step in a Varar spec — passed in as text, and checked against the step's returned string. area: reference order: 3 --- @@ -27,7 +27,7 @@ stimulus('The rendered greeting is:', (state, body) => { ## Returning a doc string -If the step **returns a string**, Vár compares it against the doc-string content — **exactly**, byte for byte. This turns the fenced block into an assertion: the prose shows the expected output, and the step produces it. +If the step **returns a string**, Varar compares it against the doc-string content — **exactly**, byte for byte. This turns the fenced block into an assertion: the prose shows the expected output, and the step produces it. ```ts sensor('Greet {word}:', (state, name, body) => { diff --git a/typescript/packages/website/drafts/drive-features-with-var-and-an-agent.md b/typescript/packages/website/drafts/drive-features-with-var-and-an-agent.md index 1d5e0154..d29c8842 100644 --- a/typescript/packages/website/drafts/drive-features-with-var-and-an-agent.md +++ b/typescript/packages/website/drafts/drive-features-with-var-and-an-agent.md @@ -1,17 +1,17 @@ --- -title: Drive a feature with Vár and an AI agent +title: Drive a feature with Varar and an AI agent description: The per-feature loop once your agent is wired up — talk in customer language, let the agent specify, then iterate on the spec. area: guides order: 2 --- -# Drive a feature with Vár and an AI agent +# Drive a feature with Varar and an AI agent -This is the per-feature working loop once your agent is wired up to use Vár (see [Wire Vár into your AI agent's instructions](wire-var-into-agent-instructions)). +This is the per-feature working loop once your agent is wired up to use Varar (see [Wire Varar into your AI agent's instructions](wire-var-into-agent-instructions)). ## Before you start -- Vár installed in the repo. +- Varar installed in the repo. - Agent instructions in place — the agent must already know to write specs first. - A clear idea of *what* the feature is, even if the *how* is open. @@ -31,9 +31,9 @@ A correctly instructed agent will create or extend a `*.md` file with a concrete If the spec doesn't match what you meant, push back now, not later. "The spec doesn't say what happens when the name is whitespace-only" is a much cheaper conversation than "the code is wrong in production". -### 3. Let the agent run Vár and read the failures +### 3. Let the agent run Varar and read the failures -The agent should run the Vár suite (via vitest), see the new example fail, and start implementing. You don't need to watch each step. What you do need to watch: +The agent should run the Varar suite (via vitest), see the new example fail, and start implementing. You don't need to watch each step. What you do need to watch: - Is the agent editing the spec to make the failure go away? Stop it. That breaks the contract. - Is the agent silently changing other specs that previously passed? Stop it. Ask why. diff --git a/typescript/packages/website/drafts/examples-and-drift.mdx b/typescript/packages/website/drafts/examples-and-drift.mdx index c89a8b17..47b0ca2b 100644 --- a/typescript/packages/website/drafts/examples-and-drift.mdx +++ b/typescript/packages/website/drafts/examples-and-drift.mdx @@ -1,18 +1,18 @@ --- title: Examples and drift -description: When a paragraph is an example, when it is just prose, and how Vár refuses to let an example quietly stop being one. +description: When a paragraph is an example, when it is just prose, and how Varar refuses to let an example quietly stop being one. area: reference order: 5 --- -In Vár your Markdown is the source of truth, and not every paragraph is a test. +In Varar your Markdown is the source of truth, and not every paragraph is a test. A paragraph becomes an **example** only when at least one of its sentences matches a step definition. A paragraph that matches nothing is just **prose** — -documentation that Vár reads past. +documentation that Varar reads past. ## A paragraph is an example only if a step matches -Vár takes each paragraph, splits it into sentences, and matches those sentences +Varar takes each paragraph, splits it into sentences, and matches those sentences against your registered step definitions (`stimulus` / `sensor`). - **At least one sentence matches** → the paragraph is an example. Its matching @@ -32,20 +32,20 @@ typo creeps into the Markdown — and a paragraph that used to be tested silentl becomes prose. The suite stays green while testing less than it did. That is the worst kind of false confidence: coverage decaying without a single failing test. -Vár treats this transition — **was an example, now matches nothing** — as +Varar treats this transition — **was an example, now matches nothing** — as **drift**, and refuses to let it pass unnoticed. -To detect it, Vár records a fingerprint of each spec's source alongside its run +To detect it, Varar records a fingerprint of each spec's source alongside its run results: an [FNV-1a](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) hash, written as `fnv1a:…`. It is a tiny, dependency-free change-detector, -identical across every Vár runtime, so drift is recognised the same way no +identical across every Varar runtime, so drift is recognised the same way no matter which language you run in. When the source changes such that a previously -matching paragraph no longer matches, Vár sees the fingerprint move and flags the +matching paragraph no longer matches, Varar sees the fingerprint move and flags the drift. ## Drift must be explicitly acknowledged -Drift is never resolved silently. Vár will not drop the example for you (losing +Drift is never resolved silently. Varar will not drop the example for you (losing coverage), and it will not let the run pass as if nothing happened. Instead you must **explicitly mark the drift as OK** — confirming that yes, this paragraph is *intentionally* no longer an example. diff --git a/typescript/packages/website/drafts/hello-var-your-first-spec.mdx b/typescript/packages/website/drafts/hello-var-your-first-spec.mdx index 137092cc..2f213b8c 100644 --- a/typescript/packages/website/drafts/hello-var-your-first-spec.mdx +++ b/typescript/packages/website/drafts/hello-var-your-first-spec.mdx @@ -1,20 +1,20 @@ --- -title: 'Hello Vár: your first spec' -description: Write your first Vár spec — a plain Markdown file describing a behaviour with a concrete example. +title: 'Hello Varar: your first spec' +description: Write your first Varar spec — a plain Markdown file describing a behaviour with a concrete example. area: start-here order: 1 --- import Editor from '$components/Editor.astro' -import helloSteps from '../../../../../var-examples/hello-var/hello-var.steps.ts?raw' +import helloSteps from '../../../../../varar-examples/hello-var/hello-var.steps.ts?raw' -# Hello Vár: your first punishment +# Hello Varar: your first punishment -The best way to understand Vár is to see something *not* working as expected. +The best way to understand Varar is to see something *not* working as expected. Create a new Markdown file with the following text: {` -# Hello, Vár +# Hello, Varar I hope this works! `} @@ -25,25 +25,25 @@ Then run var: var hello.md ``` -Vár is not good! Because you haven't made any promises at all as far as she can tell. +Varar is not good! Because you haven't made any promises at all as far as she can tell. There are no *examples*. Let's make one. Select the text `this works` in the editor below. -A Vár spec is just a Markdown file. You create a file ending in `.md` and +A Varar spec is just a Markdown file. You create a file ending in `.md` and write what you expect to happen in plain English, with concrete examples inline. There is no special syntax to learn: you describe a behaviour the way you would explain it to a -colleague, and Vár turns the examples into runnable tests. Create the file, add some +colleague, and Varar turns the examples into runnable tests. Create the file, add some content, and you have a spec. Add this to a new file: {` -# Hello, Vár +# Hello, Varar Run \`pnpm test\` and watch this file run as tests. First I greet "world" okay? I think the greeting should be "Hello, world!" -Try changing to "Hello, Vár!" and watch the test fail. +Try changing to "Hello, Varar!" and watch the test fail. ## Another example diff --git a/typescript/packages/website/drafts/install-var.md b/typescript/packages/website/drafts/install-var.md index 5df4a509..1652b10a 100644 --- a/typescript/packages/website/drafts/install-var.md +++ b/typescript/packages/website/drafts/install-var.md @@ -1,52 +1,52 @@ --- -title: Install Vár -description: Install and configure Vár +title: Install Varar +description: Install and configure Varar area: guides order: 1 --- -# Install Vár +# Install Varar -This guide covers the TypeScript package, `@oselvar/var`. (A Python port, -`pytest-var`, also exists — dual-language tabs for this guide are coming.) +This guide covers the TypeScript package, `@varar/varar`. (A Python port, +`pytest-varar`, also exists — dual-language tabs for this guide are coming.) You need Node ≥ 22 LTS. It doesn't matter whether you're installing into an existing project or starting one from scratch. ## Install -Open a terminal and add Vár as a dev dependency: +Open a terminal and add Varar as a dev dependency: ```bash -pnpm add -D @oselvar/var +pnpm add -D @varar/varar ``` ## Scaffold a project ```bash -pnpm exec var init +pnpm exec varar init ``` This creates a config file and a first example, side by side: ``` -created var.config.json -created var-examples/hello-var/hello-var.md -created var-examples/hello-var/hello-var.steps.ts +created varar.config.json +created varar-examples/hello-var/hello-var.md +created varar-examples/hello-var/hello-var.steps.ts ``` -`var.config.json` says which files are specs and which files bind their steps: +`varar.config.json` says which files are specs and which files bind their steps: ```json { - "docs": { "include": ["var-examples/**/*.md"], "exclude": [] }, - "steps": ["var-examples/**/*.steps.ts"] + "docs": { "include": ["varar-examples/**/*.md"], "exclude": [] }, + "steps": ["varar-examples/**/*.steps.ts"] } ``` And `hello-var.md` is the spec itself — plain prose with one concrete example: ```markdown -# Hello, Vár +# Hello, Varar I greet "world". The greeting should be "Hello, world!". ``` @@ -54,14 +54,14 @@ I greet "world". The greeting should be "Hello, world!". ## Run it ```bash -pnpm exec var run +pnpm exec varar run ``` The freshly scaffolded example passes: ``` -var-examples/hello-var/hello-var.md - ✓ Hello, Vár +varar-examples/hello-var/hello-var.md + ✓ Hello, Varar 1 example, 1 passed ``` @@ -70,7 +70,7 @@ var-examples/hello-var/hello-var.md [Never trust a test you haven't seen fail.](/var/docs/concepts/the-oaths-of-var/) A passing example you've never seen go red might be testing nothing at all. -Open `var-examples/hello-var/hello-var.steps.ts` and change the greeting it +Open `varar-examples/hello-var/hello-var.steps.ts` and change the greeting it produces: ```ts @@ -80,15 +80,15 @@ stimulus('I greet {string}', (_state, name) => ({ greeting: `Hi, ${name}!` })) Run var again: ```bash -pnpm exec var run +pnpm exec varar run ``` Now the oath is *broken* — the spec still says `"Hello, world!"`, but the step produces something else: ``` -var-examples/hello-var/hello-var.md - ✗ Hello, Vár +varar-examples/hello-var/hello-var.md + ✗ Hello, Varar expected "Hello, world!", actual "Hi, world!" 1 example, 0 passed, 1 failed @@ -102,5 +102,5 @@ Revert the change and run once more. The oath is *kept* again: ## Next -- [Hello Vár: your first spec](/var/docs/start-here/hello-var-your-first-spec/) walks through writing a spec from a blank file. -- [Wire Vár into your AI agent's instructions](/var/docs/guides/wire-var-into-agent-instructions/) so an agent writes specs first. +- [Hello Varar: your first spec](/var/docs/start-here/hello-var-your-first-spec/) walks through writing a spec from a blank file. +- [Wire Varar into your AI agent's instructions](/var/docs/guides/wire-var-into-agent-instructions/) so an agent writes specs first. diff --git a/typescript/packages/website/drafts/rules-without-a-cause.md b/typescript/packages/website/drafts/rules-without-a-cause.md index 757dd544..cb2d6c15 100644 --- a/typescript/packages/website/drafts/rules-without-a-cause.md +++ b/typescript/packages/website/drafts/rules-without-a-cause.md @@ -23,7 +23,7 @@ a 2 a.m. deploy that went sideways. The rule is the scar tissue. The cause is th wound. And when teams keep the scar but forget the wound, the rule hardens into ceremony: followed because it's followed, defended because it's written down. -Vár is built on the opposite move. The artifact isn't the rule — it's the +Varar is built on the opposite move. The artifact isn't the rule — it's the **example**: > First I greet "world". The greeting should be "Hello, world!" @@ -38,7 +38,7 @@ narration — they help you *read* an example, not match it. A rule that polices shape of your prose is a rule that lost its cause somewhere around the third retro. -It's also why Vár only ever reports **presence**, never absence. We can tell you a +It's also why Varar only ever reports **presence**, never absence. We can tell you a sentence *matches* a step — that's a fact, grounded in an example you wrote. We can't tell you a step is *missing*, because "missing" isn't something you can observe; it's something you'd have to assume. A diagnostic that flags absence is a diff --git a/typescript/packages/website/drafts/sensors-and-actuators.md b/typescript/packages/website/drafts/sensors-and-actuators.md index 9aa3d44c..79dd4add 100644 --- a/typescript/packages/website/drafts/sensors-and-actuators.md +++ b/typescript/packages/website/drafts/sensors-and-actuators.md @@ -16,12 +16,12 @@ order: 2 > Particularly powerful when they produce signals that are optimised for LLM consumption, e.g. custom linter > messages that include instructions for the self-correction - a positive kind of prompt injection. -With Vár, the markdown document is both the guide and the sensor. +With Varar, the markdown document is both the guide and the sensor. -The guide aspect happens outside of Vár - that's just the agent reading the markdown document and +The guide aspect happens outside of Varar - that's just the agent reading the markdown document and using it to *guide* or *steer* its activities. -The sensor aspect happens when Vár runs the markdown as an automated acceptance test. +The sensor aspect happens when Varar runs the markdown as an automated acceptance test. The sensor is implemented as an *assertion* - a comparison between an expected value (in the markdown) and an actual value (from the software). Like a good physical sensor, a good assertion is read-only: it observes the software without disturbing it. diff --git a/typescript/packages/website/drafts/state.mdx b/typescript/packages/website/drafts/state.mdx index c3cde3eb..62b678f0 100644 --- a/typescript/packages/website/drafts/state.mdx +++ b/typescript/packages/website/drafts/state.mdx @@ -37,7 +37,7 @@ Handlers may still perform side effects (call the system under test); only the ## sensor: observe, never change -A `sensor` reads state and returns values for Vár to compare against the +A `sensor` reads state and returns values for Varar to compare against the Markdown. A sensor never changes state. ```ts diff --git a/typescript/packages/website/drafts/step-arguments.mdx b/typescript/packages/website/drafts/step-arguments.mdx index b30b52d2..070bf2c5 100644 --- a/typescript/packages/website/drafts/step-arguments.mdx +++ b/typescript/packages/website/drafts/step-arguments.mdx @@ -6,13 +6,13 @@ order: 1 --- import Editor from '$components/Editor.astro' -import helloSteps from '../../../../../var-examples/hello-var/hello-var.steps.ts?raw' +import helloSteps from '../../../../../varar-examples/hello-var/hello-var.steps.ts?raw' # Step argument types A step handler is `(state, ...args) => …`. The first parameter, `state`, is your state — its type comes from `defineState`. Everything after it is captured from -the `{…}` **parameters** in the cucumber expression, and Vár infers those types +the `{…}` **parameters** in the cucumber expression, and Varar infers those types directly from the expression string. **You don't annotate them.** ```ts @@ -42,7 +42,7 @@ handler `(state, a, b)` where both `a` and `b` are `number`. Here is the whole its spec: {` -# Hello, Vár +# Hello, Varar First I greet "world" okay? I think the greeting should be "Hello, world!" diff --git a/typescript/packages/website/drafts/tables.mdx b/typescript/packages/website/drafts/tables.mdx index 5f202eeb..bccd5a39 100644 --- a/typescript/packages/website/drafts/tables.mdx +++ b/typescript/packages/website/drafts/tables.mdx @@ -1,18 +1,18 @@ --- title: Tables -description: How Markdown tables attach to steps in a Vár spec — whole-table mode, and header-bound row iteration. +description: How Markdown tables attach to steps in a Varar spec — whole-table mode, and header-bound row iteration. area: reference order: 2 --- import Editor from '$components/Editor.astro' -import yahtzeeSteps from '../../../../../var-examples/yahtzee/yahtzee.steps.ts?raw' -import yahtzeeLogic from '../../../../../var-examples/yahtzee/yahtzee.ts?raw' +import yahtzeeSteps from '../../../../../varar-examples/yahtzee/yahtzee.steps.ts?raw' +import yahtzeeLogic from '../../../../../varar-examples/yahtzee/yahtzee.ts?raw' # Tables A Markdown table attaches to the step matched in the paragraph **immediately -above it**. There are two ways that table reaches your step definition, and Vár +above it**. There are two ways that table reaches your step definition, and Varar chooses between them based on what the paragraph says. ## Whole-table mode (the default) @@ -35,7 +35,7 @@ sensor('These users exist:', (state, rows) => { }) ``` -The return is **load-bearing**: if a whole-table step returns a table, Vár +The return is **load-bearing**: if a whole-table step returns a table, Varar compares it against the input table cell by cell and fails on any difference. Return the full table (every column of every row) — as an array of rows (`string[][]`, data rows only) or an array of objects keyed by header. A step diff --git a/typescript/packages/website/drafts/the-decline-of-bdd-and-cucumber.md b/typescript/packages/website/drafts/the-decline-of-bdd-and-cucumber.md index 694a1dd2..6f56d3f3 100644 --- a/typescript/packages/website/drafts/the-decline-of-bdd-and-cucumber.md +++ b/typescript/packages/website/drafts/the-decline-of-bdd-and-cucumber.md @@ -96,4 +96,4 @@ That suggests a different shape than 2010s Cucumber ever had: conversation → examples → executable spec → tests → documentation ``` -with the agent doing the binding, and the natural-language spec serving as the *input* to implementation rather than a reporting veneer bolted onto Selenium. That's a meaningfully different value proposition. It's also [why Vár exists](/var/docs/concepts/the-oaths-of-var/). +with the agent doing the binding, and the natural-language spec serving as the *input* to implementation rather than a reporting veneer bolted onto Selenium. That's a meaningfully different value proposition. It's also [why Varar exists](/var/docs/concepts/the-oaths-of-var/). diff --git a/typescript/packages/website/drafts/the-oaths-of-var.md b/typescript/packages/website/drafts/the-oaths-of-var.md index bd1e9f62..d89a74dd 100644 --- a/typescript/packages/website/drafts/the-oaths-of-var.md +++ b/typescript/packages/website/drafts/the-oaths-of-var.md @@ -1,27 +1,27 @@ --- -title: The oaths of Vár -description: Cucumber didn't fail by accident — it failed in specific, repeatable ways. Vár is a set of oaths sworn against each one. +title: The oaths of Varar +description: Cucumber didn't fail by accident — it failed in specific, repeatable ways. Varar is a set of oaths sworn against each one. area: concepts order: 3 --- -# The oaths of Vár +# The oaths of Varar [BDD and Cucumber declined for specific reasons](/var/docs/concepts/the-decline-of-bdd-and-cucumber/) — not bad luck, but the same handful of failure modes, over and over. Feature files the business never read. Gherkin that rotted into test code in disguise. Slow, flaky suites wired to the UI. A brittle glue layer maintained by hand. -Vár is named for the Norse goddess of oaths and agreements. She harkens to the vows people make, and takes vengeance on those who break them. +Varar is named for the Norse goddess of oaths and agreements. She harkens to the vows people make, and takes vengeance on those who break them. -> Níunda Vár, hon hlýðir á eiða manna ok einkamál, er veita sín á milli konur ok karlar. Því heita þau mál várar. Hon hefnir ok þeim, er brigða. +> Níunda Varar, hon hlýðir á eiða manna ok einkamál, er veita sín á milli konur ok karlar. Því heita þau mál várar. Hon hefnir ok þeim, er brigða. -> The ninth is Vár: she harkens to the oaths and compacts made between men and women; wherefore such covenants are called 'vows.' She also takes vengeance on those who perjure themselves. +> The ninth is Varar: she harkens to the oaths and compacts made between men and women; wherefore such covenants are called 'vows.' She also takes vengeance on those who perjure themselves. -That conceit is not decoration. Every design decision in Vár is an oath sworn against one of the ways Cucumber died. Here they are. +That conceit is not decoration. Every design decision in Varar is an oath sworn against one of the ways Cucumber died. Here they are. ## The first oath: the spec is just Markdown Cucumber asked you to learn a dialect. Gherkin is a DSL — its own grammar, its own files, its own tooling — and a dialect is something the business has to be taught before it can read, let alone write. Most never were. -A Vár spec is a Markdown file ending in `.md`. Prose, with concrete examples written inline. There is no `Feature:`, no `Scenario:`, no indentation grammar to get wrong. If you can write a paragraph that names a context, an action, and an expected outcome, you've written a spec. +A Varar spec is a Markdown file ending in `.md`. Prose, with concrete examples written inline. There is no `Feature:`, no `Scenario:`, no indentation grammar to get wrong. If you can write a paragraph that names a context, an action, and an expected outcome, you've written a spec. The point is that the document is *already* the document. It reads as reference documentation — a place people go to understand what the system does — because that's literally what it is. There's no separate business-readable artifact to keep in sync with the executable one. They're the same file. @@ -29,7 +29,7 @@ The point is that the document is *already* the document. It reads as reference This is where Gherkin rotted fastest. `Given`/`When`/`Then` were structural keywords, so authors started writing *to* the structure — and the structure pulled them toward `Given I click… When I wait 3 seconds… Then the modal appears`. Test code in a prose costume. -Vár has no `Given`, `When`, or `Then` exports. Step definitions are written with three role functions — `context`, `action`, and `sensor` — chosen by what a step *does* (set up state, perform an action, observe a result), never by a keyword in the prose. Keywords, if you use them at all, are narration for the human reader — they are never matched, never parsed, never load-bearing. A step binds to a *sentence*, a paragraph of ordinary prose, not to a clause that begins with a magic word. +Varar has no `Given`, `When`, or `Then` exports. Step definitions are written with three role functions — `context`, `action`, and `sensor` — chosen by what a step *does* (set up state, perform an action, observe a result), never by a keyword in the prose. Keywords, if you use them at all, are narration for the human reader — they are never matched, never parsed, never load-bearing. A step binds to a *sentence*, a paragraph of ordinary prose, not to a clause that begins with a magic word. When the keyword carries no mechanical weight, the incentive to write robotic click-by-click scenarios disappears. You describe behaviour, because describing behaviour is the only thing the tool rewards. @@ -37,18 +37,18 @@ When the keyword carries no mechanical weight, the incentive to write robotic cl Cucumber's worst suites lived next to the browser — thousands of imperative UI scripts, slow and flaky, each a reworded sentence away from breaking. The tool didn't force that, but its shape encouraged it. -Vár matches an example from a paragraph and hands the matched spans to your step definition. The example expresses a business rule with concrete names, dates, and numbers — not a sequence of UI gestures. Where you bind that example is your choice, and the cheapest, most stable place is almost never the UI. Tables make this sharper still: write a header-bound table and the step runs once per row, each row an independently passing or failing example, with no new syntax to learn. (See [Tables](/var/docs/reference/tables/).) +Varar matches an example from a paragraph and hands the matched spans to your step definition. The example expresses a business rule with concrete names, dates, and numbers — not a sequence of UI gestures. Where you bind that example is your choice, and the cheapest, most stable place is almost never the UI. Tables make this sharper still: write a header-bound table and the step runs once per row, each row an independently passing or failing example, with no new syntax to learn. (See [Tables](/var/docs/reference/tables/).) -The unit of a Vár spec is an example, not an interaction. Examples are durable. Interactions are not. +The unit of a Varar spec is an example, not an interaction. Examples are durable. Interactions are not. ## The fourth oath: no ceremony to rot Cucumber accreted machinery — tags, hooks, a Gherkin AST, `cucumber-messages`, a whole protocol. Every piece is something that has to be learned, configured, and maintained, and machinery left unmaintained is just future flakiness. -Vár leaves it out on purpose: +Varar leaves it out on purpose: - **No tags.** Not in v1, by design. -- **No lifecycle hooks in the BDD layer.** Use your test runner's native `beforeEach`/`afterEach` — Vár doesn't reinvent them. +- **No lifecycle hooks in the BDD layer.** Use your test runner's native `beforeEach`/`afterEach` — Varar doesn't reinvent them. - **No Gherkin AST, no `cucumber-messages`.** The parser emits its own minimal, immutable AST and nothing more. Underneath, the core is pure functions over immutable data — parsing, matching, planning, snippet generation, diagnostics, all deterministic, all side-effect-free, with file I/O and runner integration pushed out to the adapters. An honest engine for a tool about honesty. The less there is, the less there is to drift. @@ -57,7 +57,7 @@ Underneath, the core is pure functions over immutable data — parsing, matching The original promise of executable specs was that the document couldn't lie, because it ran. That promise was real; teams just couldn't afford to keep it, because the glue that connected prose to code was brittle and maintained by hand. -A Vár spec is a *guide* and a *sensor* at once. It reads like documentation, and it runs like a test. When the documented behaviour and the actual behaviour drift apart, the suite goes red. The lie surfaces immediately, instead of quietly accumulating until the docs are a museum of things that used to be true. +A Varar spec is a *guide* and a *sensor* at once. It reads like documentation, and it runs like a test. When the documented behaviour and the actual behaviour drift apart, the suite goes red. The lie surfaces immediately, instead of quietly accumulating until the docs are a museum of things that used to be true. This is the vengeance the goddess takes. You don't want her knocking — so you keep your spec true, and she keeps it true for you. @@ -71,7 +71,7 @@ That flips the value proposition. Under agentic development, the natural-languag ## The oath she demands from you -Vár swears all of the above. In return she asks for exactly one vow, and it is load-bearing: +Varar swears all of the above. In return she asks for exactly one vow, and it is load-bearing: > **Never edit the spec to make a failing test pass.** @@ -83,6 +83,6 @@ There's a corollary, and it's an old one: A spec is only a sensor if it can actually detect drift. A green example that has never once gone red might be proving your behaviour — or it might be wired to nothing, passing for the wrong reason. So watch it fail before you trust it to pass. Break the code on purpose, confirm the example catches you, then fix it. An oath you can't see enforced isn't an oath; it's a wish. -Keep both, and Vár keeps the rest. +Keep both, and Varar keeps the rest. diff --git a/typescript/packages/website/drafts/why-var-with-ai-agents.md b/typescript/packages/website/drafts/why-var-with-ai-agents.md index 3f7793de..33b97def 100644 --- a/typescript/packages/website/drafts/why-var-with-ai-agents.md +++ b/typescript/packages/website/drafts/why-var-with-ai-agents.md @@ -1,19 +1,19 @@ --- -title: Why Vár pairs well with AI coding agents +title: Why Varar pairs well with AI coding agents description: ATDD is the deterministic counterweight to non-deterministic AI. The spec is the contract; the code is regeneratable. area: concepts order: 1 --- -# Why Vár pairs well with AI coding agents +# Why Varar pairs well with AI coding agents AI coding agents are powerful but non-deterministic. Ask the same agent to implement the same feature twice and you'll get two different implementations — sometimes equivalent, sometimes not. Plain natural-language instructions drift between runs because there is nothing to hold them in place. -Vár specs are the thing that holds them in place. +Varar specs are the thing that holds them in place. ## The spec is the contract -When you run an agent against a Vár spec, the spec is the contract. The agent's job is to satisfy the executable examples; whatever code it produces is incidental. You can throw the code away, run the agent again, and the result is judged the same way: against the same set of examples. The specs survive across implementations. The code is regeneratable. +When you run an agent against a Varar spec, the spec is the contract. The agent's job is to satisfy the executable examples; whatever code it produces is incidental. You can throw the code away, run the agent again, and the result is judged the same way: against the same set of examples. The specs survive across implementations. The code is regeneratable. This is the same shift that happened when high-level languages took over from assembly. Most of us no longer read the assembler our compilers emit because we've tested the higher-level program and trust the outcome. Generated code from an agent deserves the same treatment — *if* the tests around it are good enough. @@ -23,14 +23,14 @@ This is the same shift that happened when high-level languages took over from as ## Specs as a fitness function -Once your acceptance criteria are precise enough to execute, an agent can use them as a fitness function. It writes code, runs the Vár suite, reads the failures, iterates. The loop closes itself. You don't have to babysit each diff because the criteria already encode what "done" means. +Once your acceptance criteria are precise enough to execute, an agent can use them as a fitness function. It writes code, runs the Varar suite, reads the failures, iterates. The loop closes itself. You don't have to babysit each diff because the criteria already encode what "done" means. This is the loop that works: -1. Write the example in plain language as a Vár spec. +1. Write the example in plain language as a Varar spec. 2. Hand the spec to the agent. 3. The agent writes step definitions and production code. -4. Vár runs. Failures come back as feedback. +4. Varar runs. Failures come back as feedback. 5. The agent iterates until the suite is green. What used to be a human review loop becomes a test-driven loop the agent runs against itself. @@ -49,11 +49,11 @@ A good prompt looks like a good specification. A good specification looks like a The pattern that emerges is double-loop TDD: -- **Outer loop** — Vár specs describe the behaviour the system should exhibit. The agent (or you) writes them with the customer in mind. +- **Outer loop** — Varar specs describe the behaviour the system should exhibit. The agent (or you) writes them with the customer in mind. - **Inner loop** — the agent works in small steps, writing unit tests and production code together, running them after each step. The outer loop pins down "are we building the right thing?". The inner loop pins down "are we building the thing right?". Neither alone is enough under agentic development; both together give you something you can trust. ## Where this came from -The framing here is shaped by a conversation between Dave Farley (Continuous Delivery, *Modern Software Engineering* YouTube channel), Stefan Ellisdorfer (Smarter Software, author of *The Effective Software Engineer*), and Christian Gassel (Rohde & Schwarz). They've been using ATDD with agentic assistants to build real systems for real customers. Vár exists to make their workflow easier to adopt without ceremony. +The framing here is shaped by a conversation between Dave Farley (Continuous Delivery, *Modern Software Engineering* YouTube channel), Stefan Ellisdorfer (Smarter Software, author of *The Effective Software Engineer*), and Christian Gassel (Rohde & Schwarz). They've been using ATDD with agentic assistants to build real systems for real customers. Varar exists to make their workflow easier to adopt without ceremony. diff --git a/typescript/packages/website/drafts/wire-var-into-agent-instructions.md b/typescript/packages/website/drafts/wire-var-into-agent-instructions.md index ac2356c3..d324dc96 100644 --- a/typescript/packages/website/drafts/wire-var-into-agent-instructions.md +++ b/typescript/packages/website/drafts/wire-var-into-agent-instructions.md @@ -1,17 +1,17 @@ --- -title: Wire Vár into your AI agent's instructions -description: One-time setup so your coding agent defaults to writing a Vár spec before any production code. +title: Wire Varar into your AI agent's instructions +description: One-time setup so your coding agent defaults to writing a Varar spec before any production code. area: guides order: 3 --- -# Wire Vár into your AI agent's instructions +# Wire Varar into your AI agent's instructions -You want your AI coding agent — Claude Code, Cursor, Aider, Copilot agents, anything that reads project-level instructions — to default to writing a Vár spec *before* it writes code. This is a one-time setup per repo. +You want your AI coding agent — Claude Code, Cursor, Aider, Copilot agents, anything that reads project-level instructions — to default to writing a Varar spec *before* it writes code. This is a one-time setup per repo. ## Before you start -- A repo with Vár installed. +- A repo with Varar installed. - An agent that reads a persistent instruction file. Common names: `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, `.github/copilot-instructions.md`. Most modern agents read at least one of these. ## Steps @@ -27,14 +27,14 @@ Paste this block in. Edit the language to match your house style; the substance ```markdown ## How we work -We use Vár for behaviour-driven development. When you implement a feature +We use Varar for behaviour-driven development. When you implement a feature or fix a bug, you must: 1. Write or update a `*.md` spec under the relevant package's `tests/` directory before touching production code. The spec describes the behaviour in plain English with concrete examples. 2. Write or update the matching `*.steps.ts` step definitions. -3. Run the Vár suite (via vitest) and read the failures. +3. Run the Varar suite (via vitest) and read the failures. 4. Implement the production code in small steps, running the suite after each step. 5. When the suite is green and you believe the feature is complete, stop @@ -60,7 +60,7 @@ pnpm test ```bash git add AGENTS.md -git commit -m "docs: instruct agents to use Vár spec-first" +git commit -m "docs: instruct agents to use Varar spec-first" ``` ## How to tell it worked @@ -75,6 +75,6 @@ If the agent skips straight to production code, your instruction file isn't bein ## Anti-patterns -- **Don't** also paste your full Vár syntax reference into the instruction file. The agent can read the package's own README and `*.md` files in the repo. Keep instructions to *how to work*, not *what Vár is*. +- **Don't** also paste your full Varar syntax reference into the instruction file. The agent can read the package's own README and `*.md` files in the repo. Keep instructions to *how to work*, not *what Varar is*. - **Don't** tell the agent to "write tests where appropriate". Vague guidance gets ignored. Be specific: spec first, every time. - **Don't** let the agent edit the spec to make a failing test pass. That defeats the entire mechanism. The "spec is the contract" line above is load-bearing. diff --git a/typescript/packages/website/drafts/your-docs-are-your-source.md b/typescript/packages/website/drafts/your-docs-are-your-source.md index 1ac0e063..a1079c78 100644 --- a/typescript/packages/website/drafts/your-docs-are-your-source.md +++ b/typescript/packages/website/drafts/your-docs-are-your-source.md @@ -46,11 +46,11 @@ It's usually quiet, but wakes up whenever you mess something up. Like drift between the documentation and the code. You might as well discard it if it's just a bunch of lies. -Vár ensures you have no drift. She is a norse goddess of oaths and agreements. +Varar ensures you have no drift. She is a norse goddess of oaths and agreements. -> Níunda Vár, hon hlýðir á eiða manna ok einkamál, er veita sín á milli konur ok karlar. Því heita þau mál várar. Hon hefnir ok þeim, er brigða. +> Níunda Varar, hon hlýðir á eiða manna ok einkamál, er veita sín á milli konur ok karlar. Því heita þau mál várar. Hon hefnir ok þeim, er brigða. -> The ninth is Vár: she harkens to the oaths and compacts made between men and women; wherefore such covenants are called 'vows.' She also takes vengeance on those who perjure themselves. +> The ninth is Varar: she harkens to the oaths and compacts made between men and women; wherefore such covenants are called 'vows.' She also takes vengeance on those who perjure themselves. You get the picture. You don't want her knocking on your door, trust me. diff --git a/typescript/packages/website/package.json b/typescript/packages/website/package.json index 6126f964..4de36093 100644 --- a/typescript/packages/website/package.json +++ b/typescript/packages/website/package.json @@ -1,5 +1,5 @@ { - "name": "@oselvar/website", + "name": "@varar/website", "version": "0.4.2", "private": true, "type": "module", @@ -30,10 +30,10 @@ "@fontsource/jetbrains-mono": "^5.2.8", "@fontsource/stix-two-text": "^5.2.8", "@lezer/highlight": "^1.2.3", - "@oselvar/var": "workspace:^", - "@oselvar/var-core": "workspace:^", - "@oselvar/var-language": "workspace:^", - "@oselvar/var-lsp": "workspace:^", + "@varar/varar": "workspace:^", + "@varar/core": "workspace:^", + "@varar/language": "workspace:^", + "@varar/lsp": "workspace:^", "@tailwindcss/vite": "^4.3.2", "astro": "^7.0.6", "codemirror": "^6.0.2", @@ -51,7 +51,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/oselvar/var.git", + "url": "git+https://github.com/oselvar/varar.git", "directory": "typescript/packages/website" } } diff --git a/typescript/packages/website/public/diagrams/c4-component.d2 b/typescript/packages/website/public/diagrams/c4-component.d2 index fb010dac..7f9444e6 100644 --- a/typescript/packages/website/public/diagrams/c4-component.d2 +++ b/typescript/packages/website/public/diagrams/c4-component.d2 @@ -1,10 +1,10 @@ -# C4 component diagram (level 3) of the Vár TypeScript workspace. +# C4 component diagram (level 3) of the Varar TypeScript workspace. # One component per published package in typescript/packages/*; private # packages (the cucumber compat shim, the dogfood examples) and the website # are omitted. Every arrow is a real `workspace:` dependency taken from that # package's package.json — but dependencies already implied by a drawn path # are omitted for readability: most packages also declare a direct dep on -# @oselvar/var-core (its types are imported directly, and pnpm's strict +# @varar/core (its types are imported directly, and pnpm's strict # layout requires declaring everything you import), and var-vitest / var-cli # likewise declare var-config (and var-vitest var). See each package.json # for the full list. @@ -18,7 +18,7 @@ direction: down -title: "Vár — TypeScript workspace [C4 Component]" { +title: "Varar — TypeScript workspace [C4 Component]" { near: top-center shape: text style: { @@ -85,15 +85,15 @@ vscode: "VS Code\n[External system]\n\nEditor" { core: "Functional core — pure functions over immutable data, no I/O" { class: boundary - var-core: "@oselvar/var-core\n[Component: npm package]\n\nPure core: parser, matcher, planner,\nexecutor, AST, diagnostics,\nreturn-based comparison" { + var-core: "@varar/core\n[Component: npm package]\n\nPure core: parser, matcher, planner,\nexecutor, AST, diagnostics,\nreturn-based comparison" { class: ported } - var: "@oselvar/var\n[Component: npm package]\n\nAuthoring API: defineState with\ncontext / action / sensor role functions" { + var: "@varar/varar\n[Component: npm package]\n\nAuthoring API: defineState with\ncontext / action / sensor role functions" { class: ported } - var-config: "@oselvar/var-config\n[Component: npm package]\n\nvar.config.ts schema and\nspec-glob resolution" { + var-config: "@varar/config\n[Component: npm package]\n\nvar.config.ts schema and\nspec-glob resolution" { class: ported } } @@ -101,7 +101,7 @@ core: "Functional core — pure functions over immutable data, no I/O" { analysis: "Static analysis — pure given sources and an injected grammar loader" { class: boundary - var-language: "@oselvar/var-language\n[Component: npm package]\n\nStatic analysis: step-definition scanner,\nworkspace indexer, snippet emitter" { + var-language: "@varar/language\n[Component: npm package]\n\nStatic analysis: step-definition scanner,\nworkspace indexer, snippet emitter" { class: component } } @@ -109,15 +109,15 @@ analysis: "Static analysis — pure given sources and an injected grammar loader shell: "Imperative shell — adapters, the only place side effects live" { class: boundary - var-runner: "@oselvar/var-runner\n[Component: npm package]\n\nRunner shell: loads spec and step files,\nexecutes plans" { + var-runner: "@varar/runner\n[Component: npm package]\n\nRunner shell: loads spec and step files,\nexecutes plans" { class: ported } - var-vitest: "@oselvar/var-vitest\n[Component: npm package]\n\nVitest adapter: drives vitest's\ninclude/exclude from var.config.ts" { + var-vitest: "@varar/vitest\n[Component: npm package]\n\nVitest adapter: drives vitest's\ninclude/exclude from var.config.ts" { class: ported } - var-cli: "@oselvar/var-cli\n[Component: npm package]\n\nCommand-line interface" { + var-cli: "@varar/cli\n[Component: npm package]\n\nCommand-line interface" { class: component } } @@ -125,11 +125,11 @@ shell: "Imperative shell — adapters, the only place side effects live" { tooling: "Editor tooling" { class: boundary - var-lsp: "@oselvar/var-lsp\n[Component: npm package]\n\nLanguage server: diagnostics,\ngo-to-definition, completions" { + var-lsp: "@varar/lsp\n[Component: npm package]\n\nLanguage server: diagnostics,\ngo-to-definition, completions" { class: component } - var-vscode: "oselvar-var\n[Component: VS Code extension]\n\nHighlights matched steps,\nmissing-step diagnostics" { + var-vscode: "varar\n[Component: VS Code extension]\n\nHighlights matched steps,\nmissing-step diagnostics" { class: component } } diff --git a/typescript/packages/website/public/diagrams/stimulus-sensor.d2 b/typescript/packages/website/public/diagrams/stimulus-sensor.d2 index 705d767e..ff86a7ec 100644 --- a/typescript/packages/website/public/diagrams/stimulus-sensor.d2 +++ b/typescript/packages/website/public/diagrams/stimulus-sensor.d2 @@ -1,7 +1,7 @@ "Agent" -> "Code": Writes {style.animated: true} "Agent" -> "Markdown": Guided by {style.animated: true} -"Agent" -> "Vár": Runs {style.animated: true} -"Agent" -> "Vár": Sensors {style.animated: true} +"Agent" -> "Varar": Runs {style.animated: true} +"Agent" -> "Varar": Sensors {style.animated: true} "Steps" -> "Code": Calls {style.animated: true} "Var" -> "Steps": Runs {style.animated: true} "Var" -> "Markdown": Reads {style.animated: true} diff --git a/typescript/packages/website/src/content/docs/explanation/markup-is-yours.md b/typescript/packages/website/src/content/docs/explanation/markup-is-yours.md index ba1f6c8a..91c5b5c7 100644 --- a/typescript/packages/website/src/content/docs/explanation/markup-is-yours.md +++ b/typescript/packages/website/src/content/docs/explanation/markup-is-yours.md @@ -1,10 +1,10 @@ --- title: The markup is yours -description: Why Vár never edits inline text, and why document formats are block-structure plugins. +description: Why Varar never edits inline text, and why document formats are block-structure plugins. --- -Vár executes prose. That only stays trustworthy if the prose the matcher -sees is exactly the prose you wrote — so Vár follows one rule: +Varar executes prose. That only stays trustworthy if the prose the matcher +sees is exactly the prose you wrote — so Varar follows one rule: **Format plugins own block structure. Nobody touches inline text.** @@ -28,7 +28,7 @@ title: { ``` The markers are notation, no different from the `£` in `£2.50`. `parse` -takes the notation apart, `format` puts it back, and neither Vár's core nor +takes the notation apart, `format` puts it back, and neither Varar's core nor your handlers ever see markup they didn't ask for. An earlier design stripped emphasis before matching, so `*Emma*` invisibly diff --git a/typescript/packages/website/src/content/docs/explanation/test-anatomy.md b/typescript/packages/website/src/content/docs/explanation/test-anatomy.md index 921c1c58..060393ab 100644 --- a/typescript/packages/website/src/content/docs/explanation/test-anatomy.md +++ b/typescript/packages/website/src/content/docs/explanation/test-anatomy.md @@ -1,6 +1,6 @@ --- title: Test anatomy -description: Every example has a context, an action, and an outcome — and in Vár those map onto two step kinds, not three keywords. +description: Every example has a context, an action, and an outcome — and in Varar those map onto two step kinds, not three keywords. --- Every good example has three parts: the state the software rests in, the one @@ -17,7 +17,7 @@ That's how you *think* about an example, whichever vocabulary you reach for. (If you came from BDD you'll recognise the same shape as *given–when–then* — those are just the three parts wearing keywords.) -Vár's mechanism, though, has only **two** kinds. Arranging state and acting on +Varar's mechanism, though, has only **two** kinds. Arranging state and acting on it both *evolve state*, so they collapse into a single step kind: | The part | Mechanism | @@ -26,7 +26,7 @@ it both *evolve state*, so they collapse into a single step kind: | Act / Action | [`stimulus`](/reference/stimuli/) | | Assert / Outcome | [`sensor`](/reference/sensors/) | -Vár never matches keywords: a step is a stimulus or a sensor by what it *does*, +Varar never matches keywords: a step is a stimulus or a sensor by what it *does*, not by how the sentence begins. You may write the words `Given`, `When`, `Then` in your Markdown if they read well — but they're narration for the human, never load-bearing. @@ -54,7 +54,7 @@ These questions work best with mixed perspectives in the room: developers spot branching the implementation will need, testers anticipate failure modes, domain experts know which "obvious" rules aren't. -## Not every example becomes a Vár example +## Not every example becomes a Varar example The conversation will surface far more examples than belong in your document — and that's the point of the conversation, not a quota for the spec. A document @@ -65,7 +65,7 @@ Keep the document to the examples that *illustrate* — the ones a reader needs to understand the behaviour. Push the rest down: the combinatorial edge cases, the exhaustive boundary values, the fifth variation on the same rule all belong in ordinary unit tests, close to the code. A healthy codebase has many -more unit tests than Vár examples. +more unit tests than Varar examples. A discovered example that doesn't make it into the document still did its job: it changed what you build and what you test. diff --git a/typescript/packages/website/src/content/docs/explanation/thin-steps.md b/typescript/packages/website/src/content/docs/explanation/thin-steps.md index 48dc2e7d..21d0e88c 100644 --- a/typescript/packages/website/src/content/docs/explanation/thin-steps.md +++ b/typescript/packages/website/src/content/docs/explanation/thin-steps.md @@ -3,7 +3,7 @@ title: Thin steps description: Let the steps guide your software design --- -The recommended way to work with Vár is to write the documentation *first* and let it *guide* the +The recommended way to work with Varar is to write the documentation *first* and let it *guide* the implementation of the software design. The documentation is the result of a *conversation* between people and/or agents. diff --git a/typescript/packages/website/src/content/docs/explanation/var-for-cucumber-users.md b/typescript/packages/website/src/content/docs/explanation/var-for-cucumber-users.md index a9bfe89a..538c11e2 100644 --- a/typescript/packages/website/src/content/docs/explanation/var-for-cucumber-users.md +++ b/typescript/packages/website/src/content/docs/explanation/var-for-cucumber-users.md @@ -1,9 +1,9 @@ --- -title: Vár for Cucumber users -description: What Vár keeps from Cucumber, what it drops, and why. +title: Varar for Cucumber users +description: What Varar keeps from Cucumber, what it drops, and why. --- -Vár is created by Aslak Hellesøy, who also created Cucumber in 2008. +Varar is created by Aslak Hellesøy, who also created Cucumber in 2008. The goal is to keep only the good parts, and align it with agentic coding. If you've used Cucumber before — whether you @@ -20,22 +20,22 @@ bound by matching phrases in the text. ## What changed -| Cucumber | Vár | +| Cucumber | Varar | | --- | --- | | `.feature` files in Gherkin | Plain Markdown. No new dialect — a file is a spec iff it matches the globs in `var.config.ts`. | | `Given` / `When` / `Then` step types | Two roles — `stimulus` and `sensor` — chosen by what a step *does*, not by a keyword. Keywords in prose are narration for the reader; they're never matched. | -| Assertions inside step bodies | Steps *return* what the software did; Vár compares it against what the document claims, and failures are anchored to the exact span in the source. | +| Assertions inside step bodies | Steps *return* what the software did; Varar compares it against what the document claims, and failures are anchored to the exact span in the source. | | `DataTable` and doc-string APIs | Native Markdown tables and fenced code blocks, checked by [return-based comparison](/how-to/tables-and-doc-strings/). | | `World` and untyped state | `steps` — a typed state factory per spec; every example starts fresh. | -| `Before` / `After` hooks | None in Vár. Use your test runner's own `beforeEach` / `afterEach`. | +| `Before` / `After` hooks | None in Varar. Use your test runner's own `beforeEach` / `afterEach`. | | Tags | Not in v1. | | A separate test-run artefact | The document *is* the test. There is no report that drifts from the docs, because the docs are what ran. | ## Migration -Our goal is to make Vár capable of running existing `.feature` files without any change to them. +Our goal is to make Varar capable of running existing `.feature` files without any change to them. There will be an adapter API for Cucumber step definitions so that all you need to do is to change -`import` statements from Cucumber to Vár. +`import` statements from Cucumber to Varar. TODO: Finish the adapter implementation and test it extensively @@ -55,8 +55,8 @@ The usual complaints, taken seriously: - **"Regex glue and mystery state."** Steps bind with Cucumber Expressions and a typed state you declare once with `steps`. No `this`, no untyped `World`. -- **"Extra layer of indirection."** Vár still has that layer (step definitions). - Only write a *few* tests in Vár - the ones that *really* matter. Use unit testing tools for the rest. +- **"Extra layer of indirection."** Varar still has that layer (step definitions). + Only write a *few* tests in Varar - the ones that *really* matter. Use unit testing tools for the rest. - **"Step definitions became a second implementation."** Steps that return values stay thin — a couple of lines delegating to your domain (see [Thin steps](/explanation/thin-steps/)). The assertion lives in the document, @@ -66,4 +66,4 @@ The usual complaints, taken seriously: ## Next -See it in two minutes: [Try Vár in your browser](/tutorials/try-var/). +See it in two minutes: [Try Varar in your browser](/tutorials/try-var/). diff --git a/typescript/packages/website/src/content/docs/explanation/var-overview.md b/typescript/packages/website/src/content/docs/explanation/var-overview.md index c137adfb..db729a92 100644 --- a/typescript/packages/website/src/content/docs/explanation/var-overview.md +++ b/typescript/packages/website/src/content/docs/explanation/var-overview.md @@ -1,9 +1,9 @@ --- -title: Vár -description: Vár overview. +title: Varar +description: Varar overview. --- -Vár is an I/O layer that sits between your documentation and your software. +Varar is an I/O layer that sits between your documentation and your software. It connects the two with *cells* (like spreadsheet cells). A cell can be a number, date, name etc. It can be in a paragraph of text, or in a table. diff --git a/typescript/packages/website/src/content/docs/how-to/agent-instructions.md b/typescript/packages/website/src/content/docs/how-to/agent-instructions.md index c2a048b8..180ebfe2 100644 --- a/typescript/packages/website/src/content/docs/how-to/agent-instructions.md +++ b/typescript/packages/website/src/content/docs/how-to/agent-instructions.md @@ -1,15 +1,15 @@ --- -title: Wire Vár into your AI agent's instructions -description: One-time setup so your coding agent writes a Vár spec before any production code. +title: Wire Varar into your AI agent's instructions +description: One-time setup so your coding agent writes a Varar spec before any production code. --- This guide shows you how to make an AI coding agent — Claude Code, Cursor, Copilot, anything that reads project-level instructions — default to writing a -Vár spec *before* it writes code. One-time setup per repo. +Varar spec *before* it writes code. One-time setup per repo. ## Before you start -- A repo with Vár installed. +- A repo with Varar installed. - An agent that reads a persistent instruction file. Common names: `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, `.github/copilot-instructions.md`. @@ -26,13 +26,13 @@ is what matters. ```markdown ## How we work -We use Vár for behaviour-driven development. When you implement a feature +We use Varar for behaviour-driven development. When you implement a feature or fix a bug, you must: 1. Write or update a `*.md` spec before touching production code. The spec describes the behaviour in plain English with concrete examples. 2. Write or update the matching `*.steps.ts` step definitions. -3. Run the Vár suite and read the failures. +3. Run the Varar suite and read the failures. 4. Implement the production code in small steps, running the suite after each step. 5. When the suite is green and you believe the feature is complete, stop @@ -64,9 +64,9 @@ instruction file isn't being read — check the file name your agent expects. ## Anti-patterns -- **Don't** paste a Vár syntax reference into the instruction file. The agent +- **Don't** paste a Varar syntax reference into the instruction file. The agent can read the repo's own specs and READMEs. Instructions are for *how to - work*, not *what Vár is*. + work*, not *what Varar is*. - **Don't** write "add tests where appropriate". Vague guidance gets ignored. Spec first, every time. - **Don't** let the agent edit a spec to make a failing test pass. The "spec is @@ -75,5 +75,5 @@ instruction file isn't being read — check the file name your agent expects. ## Next With the instructions in place, see -[Drive a feature with Vár and an agent](/how-to/drive-a-feature-with-an-agent/) +[Drive a feature with Varar and an agent](/how-to/drive-a-feature-with-an-agent/) for the per-feature working loop. diff --git a/typescript/packages/website/src/content/docs/how-to/drive-a-feature-with-an-agent.md b/typescript/packages/website/src/content/docs/how-to/drive-a-feature-with-an-agent.md index 5085e7fa..d1636137 100644 --- a/typescript/packages/website/src/content/docs/how-to/drive-a-feature-with-an-agent.md +++ b/typescript/packages/website/src/content/docs/how-to/drive-a-feature-with-an-agent.md @@ -1,11 +1,11 @@ --- -title: Drive a feature with Vár and an agent +title: Drive a feature with Varar and an agent description: The per-feature loop — talk in customer language, let the agent specify, then iterate on the spec, not the code. --- This guide shows you the working loop for building one feature with an AI agent -once Vár is wired into its instructions (see -[Wire Vár into your AI agent's instructions](/how-to/agent-instructions/)). +once Varar is wired into its instructions (see +[Wire Varar into your AI agent's instructions](/how-to/agent-instructions/)). ## 1. Brief the agent in customer language @@ -27,7 +27,7 @@ If the spec doesn't say what you meant, push back now. "The spec doesn't cover a whitespace-only name" is a much cheaper conversation than "the code is wrong in production". -## 3. Let the agent run Vár and implement +## 3. Let the agent run Varar and implement The agent runs the suite, sees the new example fail, and implements. You don't need to watch every step — but do watch for: diff --git a/typescript/packages/website/src/content/docs/how-to/run-with-vitest.md b/typescript/packages/website/src/content/docs/how-to/run-with-vitest.md index f60a05f0..fb294995 100644 --- a/typescript/packages/website/src/content/docs/how-to/run-with-vitest.md +++ b/typescript/packages/website/src/content/docs/how-to/run-with-vitest.md @@ -1,29 +1,29 @@ --- title: Run specs through vitest -description: Wire the Vár plugin into vitest so your Markdown specs run inside your existing test suite. +description: Wire the Varar plugin into vitest so your Markdown specs run inside your existing test suite. --- -This guide shows you how to run Vár specs as part of a vitest suite instead of +This guide shows you how to run Varar specs as part of a vitest suite instead of (or alongside) the `var` CLI — one runner, one watch mode, one CI job. -It assumes Vár is already set up in your repo. If not, start with +It assumes Varar is already set up in your repo. If not, start with [Get started on your computer](/tutorials/get-started/). ## 1. Install the adapter ```bash -pnpm add -D @oselvar/var-vitest vitest +pnpm add -D @varar/vitest vitest ``` -Your step definitions keep importing `@oselvar/var` — never the adapter. +Your step definitions keep importing `@varar/varar` — never the adapter. ## 2. Wire the plugin into vitest In `vitest.config.ts`: ```ts -import varPlugin from '@oselvar/var-vitest' -import { VarResultsReporter } from '@oselvar/var-vitest/reporter' +import varPlugin from '@varar/vitest' +import { VarResultsReporter } from '@varar/vitest/reporter' import { defineConfig } from 'vitest/config' export default defineConfig({ @@ -34,18 +34,18 @@ export default defineConfig({ }) ``` -## 3. Let var.config.json decide what is a spec +## 3. Let varar.config.json decide what is a spec -The plugin reads `var.config.json` and drives vitest's own `include`/`exclude` +The plugin reads `varar.config.json` and drives vitest's own `include`/`exclude` from it — you don't repeat the globs in the vitest config: ```json { "docs": { - "include": ["var-examples/**/*.md"], - "exclude": ["var-examples/drafts/**"] + "include": ["varar-examples/**/*.md"], + "exclude": ["varar-examples/drafts/**"] }, - "steps": ["var-examples/**/*.steps.ts"] + "steps": ["varar-examples/**/*.steps.ts"] } ``` @@ -64,6 +64,6 @@ CI reporting, next to your ordinary `*.test.ts` files. ## Set-up and tear-down -Vár has no lifecycle hooks of its own. Use vitest's native `beforeEach` / +Varar has no lifecycle hooks of its own. Use vitest's native `beforeEach` / `afterEach` in a regular test-setup file for anything the specs need around them (databases, servers, fixtures). diff --git a/typescript/packages/website/src/content/docs/how-to/tables-and-doc-strings.mdx b/typescript/packages/website/src/content/docs/how-to/tables-and-doc-strings.mdx index e6b64a78..8dadf316 100644 --- a/typescript/packages/website/src/content/docs/how-to/tables-and-doc-strings.mdx +++ b/typescript/packages/website/src/content/docs/how-to/tables-and-doc-strings.mdx @@ -1,12 +1,12 @@ --- title: Check tables and doc strings -description: Return computed values from a step and let Vár compare them against the Markdown, cell by cell. +description: Return computed values from a step and let Varar compare them against the Markdown, cell by cell. --- import { Tabs, TabItem } from '@astrojs/starlight/components'; -This guide shows you how to check tabular and multi-line expectations. In Vár a -step may `return` a value; Vár compares it against what the Markdown claims and +This guide shows you how to check tabular and multi-line expectations. In Varar a +step may `return` a value; Varar compares it against what the Markdown claims and anchors any mismatch to the exact cell or character span. There are three shapes. @@ -174,7 +174,7 @@ Uppercase each one: -The table is this sensor's only comparable value, so it is returned bare. Vár +The table is this sensor's only comparable value, so it is returned bare. Varar compares every cell of the returned table against the source, as exact strings. ## Check a doc string diff --git a/typescript/packages/website/src/content/docs/index.mdx b/typescript/packages/website/src/content/docs/index.mdx index c18abad7..1388e328 100644 --- a/typescript/packages/website/src/content/docs/index.mdx +++ b/typescript/packages/website/src/content/docs/index.mdx @@ -1,17 +1,17 @@ --- -title: Vár +title: Varar description: Executable markdown documentation for humans and agents. Turn your docs into tests. template: splash # Remove or comment out this line to display the site sidebar on this page. hero: title: Be honest - tagline: Vár ensures your code does what your docs say.
Your oaths, enforced. + tagline: Varar ensures your code does what your docs say.
Your oaths, enforced. image: file: ../../assets/var-logo.webp actions: - text: Get started link: /tutorials/get-started/ icon: rocket - - text: Try Vár in your browser + - text: Try Varar in your browser link: /tutorials/try-var/ icon: laptop variant: minimal @@ -23,12 +23,12 @@ import LibraryEditor from '$components/examples/LibraryEditor.astro'; import RomanNumeralsEditor from '$components/examples/RomanNumeralsEditor.astro'; Entropy is everywhere. Code and tests tend to drift apart from documentation and specifications in subtle ways that can be hard to discover. -Vár closes that gap by making your documentation *executable*. -When a programmer or coding agent breaks the oath, Vár catches it. Every time. +Varar closes that gap by making your documentation *executable*. +When a programmer or coding agent breaks the oath, Varar catches it. Every time. ## Examples -The examples below give a taste of how Vár works. If the markdown disagrees with the code, your tests will fail. +The examples below give a taste of how Varar works. If the markdown disagrees with the code, your tests will fail. @@ -39,7 +39,7 @@ The examples below give a taste of how Vár works. If the markdown disagrees wit Each paragraph runs as its own example with fresh state — arrange, act and assert in plain prose. See [stimuli](/reference/stimuli/) for how state evolves. - Cycle the version button to **Reword (drift)**: the *Ben* paragraph is reworded so it no longer matches any step. Instead of silently dropping to prose (losing that test), Vár flags it as **drift** — an amber marker on the paragraph — and offers **Accept as prose** to acknowledge it. See [Examples › Drift detection](/reference/examples/#drift-detection). + Cycle the version button to **Reword (drift)**: the *Ben* paragraph is reworded so it no longer matches any step. Instead of silently dropping to prose (losing that test), Varar flags it as **drift** — an amber marker on the paragraph — and offers **Accept as prose** to acknowledge it. See [Examples › Drift detection](/reference/examples/#drift-detection). @@ -47,9 +47,11 @@ The examples below give a taste of how Vár works. If the markdown disagrees wit -## Who is Vár? +## Why "Varar"? -In Norse mythology, Vár is a goddess who listens to people's oaths and private agreements. -She punishes those who break them.[^1] +**Varar** is Old Norse for *oaths* — and **Vár** is the goddess who guards them. +In Norse mythology, Vár listens to people's oaths and private agreements, and +punishes those who break them.[^1] That is exactly what this tool does: your +documentation is the oath, and Varar makes sure your code keeps it. [^1]: [Vár on Wikipedia](https://en.wikipedia.org/wiki/V%C3%A1r) \ No newline at end of file diff --git a/typescript/packages/website/src/content/docs/reference/editor-support.mdx b/typescript/packages/website/src/content/docs/reference/editor-support.mdx index f33ba591..374efbdc 100644 --- a/typescript/packages/website/src/content/docs/reference/editor-support.mdx +++ b/typescript/packages/website/src/content/docs/reference/editor-support.mdx @@ -1,26 +1,26 @@ --- title: Editor support -description: The Vár VS Code extension and its language server — matched-step highlighting, go-to step definition, hover, completion, drift and mismatch diagnostics, Generate Step Definition, and cross-file Rename. +description: The Varar VS Code extension and its language server — matched-step highlighting, go-to step definition, hover, completion, drift and mismatch diagnostics, Generate Step Definition, and cross-file Rename. --- import { Aside } from '@astrojs/starlight/components'; -Vár ships an editor integration so that a spec — plain Markdown that happens to +Varar ships an editor integration so that a spec — plain Markdown that happens to be executable — reads and edits like real code: matched steps are highlighted, you can jump from a sentence to the function that runs it, and the editor tells you when a paragraph has stopped being an example. It comes in two pieces: -- **`@oselvar/var-lsp`** — a standalone [Language Server +- **`@varar/lsp`** — a standalone [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) server. It holds all the intelligence: parsing, step matching, diagnostics, snippet generation, and rename planning. Because it speaks LSP, any editor with an LSP client can drive it. -- **The Vár VS Code extension** (`oselvar.oselvar-var`) — a thin client that +- **The Varar VS Code extension** (`varar.varar`) — a thin client that bundles the language server and wires its features into VS Code menus, commands, and the editor surface. Everything on this page is powered by the language server, so an editor other -than VS Code that points an LSP client at `var-lsp` gets the same feature set. +than VS Code that points an LSP client at `varar-lsp` gets the same feature set. ## Install @@ -28,12 +28,12 @@ The extension is published to the [Open VSX Registry](https://open-vsx.org), so it installs in VS Code and every VS Code–compatible editor — VSCodium, Cursor, Windsurf — that resolves extensions from Open VSX: -- **In-editor:** open the Extensions view, search for **Vár**, and install - `oselvar.oselvar-var`. -- **From the CLI:** `code --install-extension oselvar.oselvar-var` (or the +- **In-editor:** open the Extensions view, search for **Varar**, and install + `varar.varar`. +- **From the CLI:** `code --install-extension varar.varar` (or the equivalent `codium` / `cursor` command). -The extension activates when a workspace contains a `var.config.json`, or when +The extension activates when a workspace contains a `varar.config.json`, or when you open a Markdown, TypeScript, Python, Java, or Kotlin file. It needs that config to know which files are specs and where your step definitions live — see the [Examples reference](/reference/examples/) for the `docs` globs that decide @@ -87,7 +87,7 @@ squiggles. Beyond parse errors, it ingests the results of the last run (the `.var/*.json` files the runner writes) and reflects them back onto the source: - **Drift** — a paragraph that *used to be* an example and now matches nothing. - This is the dangerous case Vár refuses to let pass silently; see + This is the dangerous case Varar refuses to let pass silently; see [Drift detection](/reference/examples/#drift-detection). - **Missing steps** — a sentence that looks like it should run but has no matching step definition. @@ -114,7 +114,7 @@ working step. ### Cross-file Rename Press **F2** on a matched step — either on the sentence in the Markdown or on the -`stimulus()` / `sensor()` expression in a steps file — and Vár renames the step +`stimulus()` / `sensor()` expression in a steps file — and Varar renames the step everywhere at once: it rewrites the step-definition expression and its handler, and re-renders every matching sentence across every spec to the new wording. If the new form adds a parameter or changes a parameter's type, the editor prompts @@ -123,7 +123,7 @@ through the rename. ## Using the language server in other editors -`@oselvar/var-lsp` is a plain LSP server with a `var-lsp` executable. Any editor +`@varar/lsp` is a plain LSP server with a `varar-lsp` executable. Any editor that can launch a language server for Markdown and your step-definition files can consume it directly and get the same highlighting, navigation, diagnostics, and rename features described above — the VS Code extension is just the reference diff --git a/typescript/packages/website/src/content/docs/reference/example-projects.mdx b/typescript/packages/website/src/content/docs/reference/example-projects.mdx index 31d06b08..64e49f2c 100644 --- a/typescript/packages/website/src/content/docs/reference/example-projects.mdx +++ b/typescript/packages/website/src/content/docs/reference/example-projects.mdx @@ -1,11 +1,11 @@ --- title: Example projects -description: Standalone sample projects — one per language and test framework — all running the same Markdown specs with Vár. +description: Standalone sample projects — one per language and test framework — all running the same Markdown specs with Varar. --- import { Card, CardGrid } from '@astrojs/starlight/components'; -The [oselvar/var-examples](https://github.com/oselvar/var-examples) repository +The [oselvar/varar-examples](https://github.com/oselvar/vararar-examples) repository holds one small, standalone project per language/test-framework combination. Each is a complete project you can copy as the starting point for your own. @@ -19,44 +19,44 @@ whole team, the language is an implementation detail. The full example set, and the project the interactive editors on this site mirror. Run with `pnpm test`. - [Browse typescript-vitest →](https://github.com/oselvar/var-examples/tree/main/typescript-vitest) + [Browse typescript-vitest →](https://github.com/oselvar/vararar-examples/tree/main/typescript-vitest) Step definitions as `StepDefinitions` classes, run by the JUnit Platform `var` engine. Run with `mvn test`. - [Browse java-junit-maven →](https://github.com/oselvar/var-examples/tree/main/java-junit-maven) + [Browse java-junit-maven →](https://github.com/oselvar/vararar-examples/tree/main/java-junit-maven) The same Java step definitions as the Maven project, built with Gradle. Run with `./gradlew test`. - [Browse java-junit-gradle →](https://github.com/oselvar/var-examples/tree/main/java-junit-gradle) + [Browse java-junit-gradle →](https://github.com/oselvar/vararar-examples/tree/main/java-junit-gradle) Step definitions in the Kotlin DSL — top-level `steps` with state-receiver lambdas. Run with `./gradlew test`. - [Browse kotlin-junit →](https://github.com/oselvar/var-examples/tree/main/kotlin-junit) + [Browse kotlin-junit →](https://github.com/oselvar/vararar-examples/tree/main/kotlin-junit) The same Kotlin step definitions, registered as a Kotest `VarSpec` instead of the JUnit suite. Run with `./gradlew test`. - [Browse kotlin-kotest →](https://github.com/oselvar/var-examples/tree/main/kotlin-kotest) + [Browse kotlin-kotest →](https://github.com/oselvar/vararar-examples/tree/main/kotlin-kotest) - Step definitions as decorated functions, collected by the pytest-var + Step definitions as decorated functions, collected by the pytest-varar plugin. Run with `uv run pytest`. - [Browse python-pytest →](https://github.com/oselvar/var-examples/tree/main/python-pytest) + [Browse python-pytest →](https://github.com/oselvar/vararar-examples/tree/main/python-pytest) The same Python step definitions, generated into standard-library - `TestCase` classes by oselvar-var-unittest. Run with + `TestCase` classes by varar-unittest. Run with `uv run python -m unittest`. - [Browse python-unittest →](https://github.com/oselvar/var-examples/tree/main/python-unittest) + [Browse python-unittest →](https://github.com/oselvar/vararar-examples/tree/main/python-unittest) @@ -75,6 +75,6 @@ carries the full set; the other projects share a feature-covering subset: | `tables-and-docstrings.md` | whole-table and doc-string checks | The projects are synced from the -[oselvar/var](https://github.com/oselvar/var) monorepo's `examples/` directory +[oselvar/var](https://github.com/oselvar/vararar) monorepo's `examples/` directory on every release, pinned to the released package versions — so send changes -there, not to var-examples. +there, not to varar-examples. diff --git a/typescript/packages/website/src/content/docs/reference/examples.mdx b/typescript/packages/website/src/content/docs/reference/examples.mdx index e2629316..89d342af 100644 --- a/typescript/packages/website/src/content/docs/reference/examples.mdx +++ b/typescript/packages/website/src/content/docs/reference/examples.mdx @@ -1,19 +1,19 @@ --- title: Examples -description: How Vár identifies an example in a Markdown spec — where one begins and ends, how tables, doc strings, headings, and prose fit in, and how drift is detected when an example stops being one. +description: How Varar identifies an example in a Markdown spec — where one begins and ends, how tables, doc strings, headings, and prose fit in, and how drift is detected when an example stops being one. --- -In Vár the Markdown *is* the test. A spec file is ordinary Markdown — prose, -headings, lists, tables, code blocks — and Vár runs the parts of it that match +In Varar the Markdown *is* the test. A spec file is ordinary Markdown — prose, +headings, lists, tables, code blocks — and Varar runs the parts of it that match your step definitions. This page is the reference for the unit it runs: the **example**. It covers exactly what becomes an example, where one begins and -ends, how tables and doc strings attach, what role headings play, and how Vár +ends, how tables and doc strings attach, what role headings play, and how Varar notices when something that used to be an example no longer is. ## What is a spec A file is a spec if — and only if — its path matches the `docs` globs in -`var.config.json`. That config is the single source of truth, consulted by the +`varar.config.json`. That config is the single source of truth, consulted by the runner, the LSP, and the vitest plugin alike: ```json @@ -29,18 +29,18 @@ Both `include` and `exclude` are plain globs (no `!` prefix). `include` has no default — an empty list discovers nothing; `exclude` removes matches from it. There is no special file extension: a plain `.md` file is a spec purely because the globs select it. Any Markdown file the globs don't select is invisible to -Vár. +Varar. ## What is an example Within a spec, the candidate unit is a **paragraph** — or a list item, or a -blockquote. Vár splits each candidate into sentences and matches those sentences +blockquote. Varar splits each candidate into sentences and matches those sentences against your registered [stimuli](/reference/stimuli/) and [sensos](/reference/sensors/). - **At least one sentence matches a step** → the paragraph is an **example**. Its matching sentences run as steps, in order. -- **No sentence matches** → the paragraph is **prose**. Vár reads past it: no +- **No sentence matches** → the paragraph is **prose**. Varar reads past it: no test, no failure. This is what lets a single file interleave narrative explanation with executable @@ -50,7 +50,7 @@ run. A paragraph that matches at least one step but not every sentence is still one example. The unmatched sentences are narration that travels *with* the test — -they even become part of its name (see [Naming](#naming)). Vár never guesses +they even become part of its name (see [Naming](#naming)). Varar never guesses that an unmatched sentence "should have" been a step; there is no keyword sniffing, no "missing step" inference from sentence shape. @@ -182,7 +182,7 @@ terminators inside the sentence (a quoted string, `i.e.`) are left alone. ## Sentences, emphasis, and formatting -Vár splits a paragraph into sentences on `.`, `!`, `?`, and hard line breaks, +Varar splits a paragraph into sentences on `.`, `!`, `?`, and hard line breaks, with a few guards so real prose doesn't fragment: - Terminators **inside backtick code spans or double-quoted strings** don't @@ -197,7 +197,7 @@ matches only an expression that says so. Block markers are different: they are structure, not text, so a list item's `- ` bullet and a blockquote's `> ` prefix never reach the matcher. If a marked-up run is data, make it a [custom parameter](/reference/custom-parameters/) whose regexp includes the -markers and whose `parse` strips them — markup is notation, and Vár never +markers and whose `parse` strips them — markup is notation, and Varar never edits your prose behind your back. ## Expected-to-fail examples @@ -226,19 +226,19 @@ creeps into the Markdown — and it silently reverts to prose. The suite stays green while testing *less* than it did: coverage decaying without a single failing test. -Vár treats this transition — **was an example, now matches nothing** — as +Varar treats this transition — **was an example, now matches nothing** — as **drift**, and refuses to let it pass unnoticed. -To recognise it, Vár records a fingerprint of each spec's source alongside its +To recognise it, Varar records a fingerprint of each spec's source alongside its run results: an [FNV-1a](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) hash written as `fnv1a:…`. It is a tiny, dependency-free change-detector, -computed identically in every Vár runtime, so drift is recognised the same way +computed identically in every Varar runtime, so drift is recognised the same way whatever language you run in. When a spec changes such that a previously matching -paragraph no longer matches any step, Vár compares the new source against the +paragraph no longer matches any step, Varar compares the new source against the recorded baseline and reports the drifted example by name and line. -Drift is never resolved silently. Vár will not drop the example for you (losing +Drift is never resolved silently. Varar will not drop the example for you (losing coverage), and it will not let the run pass as if nothing happened. The run **fails** until you **explicitly acknowledge** the drift — confirming that yes, this paragraph is *intentionally* no longer an example — much as you accept an @@ -252,7 +252,7 @@ The distinction is deliberate: ### Why it's important -The whole promise of Vár is that the Markdown is the test. If an example could +The whole promise of Varar is that the Markdown is the test. If an example could quietly stop being a test, that promise leaks: a document that reads like a passing specification would actually be verifying nothing, and no one would know. Silent coverage loss is the worst failure mode a test system has, because @@ -260,7 +260,7 @@ it looks exactly like success — a green suite testing less than it claims. A plain "missing step" warning in an editor doesn't close the gap: a developer who never opens the editor never sees it, and CI never gates on it. Drift -detection lives in the runner, so the same `vitest` / `pytest` / `var run` that +detection lives in the runner, so the same `vitest` / `pytest` / `varar run` that proves your examples pass also proves that your examples are *still examples*. Distinguishing intentional prose (never matched) from a regression (stopped matching) needs history, which is exactly what the fingerprinted baseline diff --git a/typescript/packages/website/src/content/docs/reference/sensors.mdx b/typescript/packages/website/src/content/docs/reference/sensors.mdx index 446da68f..88626482 100644 --- a/typescript/packages/website/src/content/docs/reference/sensors.mdx +++ b/typescript/packages/website/src/content/docs/reference/sensors.mdx @@ -6,9 +6,9 @@ description: The sensor return-value contract — slots, bare values, positional import { Tabs, TabItem } from '@astrojs/starlight/components'; A sensor is a read-only observation. It reads state, returns what the software -actually produced, and Vár compares that against what the Markdown claims. A +actually produced, and Varar compares that against what the Markdown claims. A sensor never changes state, and you never write an assertion — the document is -the assertion. It is one of Vár's two step kinds — the other is the +the assertion. It is one of Varar's two step kinds — the other is the [stimulus](/reference/stimuli/), which drives the software. The word comes from Birgitta Böckeler's @@ -106,7 +106,7 @@ Kotlin, Python, Ruby). ## Slots -A sensor's **slots** are the values Vár will compare, in order: +A sensor's **slots** are the values Varar will compare, in order: 1. each parameter captured by the expression (`{int}`, `{word}`, `{string}`, [custom types](/reference/custom-parameters/)), left to right; @@ -521,7 +521,7 @@ failing characters in the Markdown and show the actual value in place. ## Why an array at all? -A sensor with several slots has several independent claims to check, and Vár +A sensor with several slots has several independent claims to check, and Varar needs to know which returned value belongs to which claim. Positional mapping — same order as the sentence — does that without naming ceremony. When there is only one claim, the position is unambiguous, so the wrapper would be pure diff --git a/typescript/packages/website/src/content/docs/reference/stimuli.mdx b/typescript/packages/website/src/content/docs/reference/stimuli.mdx index a8408e68..8fd61520 100644 --- a/typescript/packages/website/src/content/docs/reference/stimuli.mdx +++ b/typescript/packages/website/src/content/docs/reference/stimuli.mdx @@ -6,7 +6,7 @@ description: Step functions that mutate state and drive the system under test. import { Tabs, TabItem } from '@astrojs/starlight/components'; A *stimulus* drives the software: it arranges the state an example starts from -and acts on it. It is one of Vár's two kinds of step functions. The other is +and acts on it. It is one of Varar's two kinds of step functions. The other is [sensors](/reference/sensors/), the read-only observations. The names are a hardware analogy: you put a stimulus into the system, and you read its response @@ -91,7 +91,7 @@ with sensors. In your prose the stimulus covers both the *context* (arrange) and the *action* -(act) — Vár never matches keywords; see +(act) — Varar never matches keywords; see [Test anatomy](/explanation/test-anatomy/) for why the concepts and the mechanism are decoupled. @@ -99,7 +99,7 @@ mechanism are decoupled. A stimulus receives the current state as its first argument (deeply readonly) followed by the values the expression captured. In TypeScript and Python it -evolves state by **returning a partial state object**, which Vár shallow-merges +evolves state by **returning a partial state object**, which Varar shallow-merges onto the current state and re-freezes. In Java and Kotlin, where state is an immutable record/data class, it returns the **complete new state value** — full replacement, same principle: new value out, never mutation. diff --git a/typescript/packages/website/src/content/docs/tutorials/first-spec.mdx b/typescript/packages/website/src/content/docs/tutorials/first-spec.mdx index aec098a3..b16e3c83 100644 --- a/typescript/packages/website/src/content/docs/tutorials/first-spec.mdx +++ b/typescript/packages/website/src/content/docs/tutorials/first-spec.mdx @@ -1,6 +1,6 @@ --- title: Your first spec from scratch -description: Write a Vár spec in a blank Markdown file and bind its steps. +description: Write a Varar spec in a blank Markdown file and bind its steps. --- import { Steps } from '@astrojs/starlight/components'; @@ -17,7 +17,7 @@ This tutorial continues in the project you set up in 1. Describe the behaviour - Create `var-examples/calculator.md`: + Create `varar-examples/calculator.md`: ```markdown # Calculator @@ -32,10 +32,10 @@ This tutorial continues in the project you set up in 2. Bind the steps Nothing runs yet — no step matches our sentence. Create - `var-examples/steps/calculator.steps.ts`: + `varar-examples/steps/calculator.steps.ts`: ```ts - import { steps } from '@oselvar/var' + import { steps } from '@varar/varar' const { stimulus, sensor } = steps(() => ({ result: 0 })) @@ -51,7 +51,7 @@ This tutorial continues in the project you set up in - The **`stimulus`** drives the software. It matches `` expression `1+1` `` in the prose, computes, and returns a patch to the state. - The **`sensor`** is the read-only observation. It returns what the software - actually produced — `state.result` — and Vár compares that against the `2` + actually produced — `state.result` — and Varar compares that against the `2` written in the Markdown. You never write an assertion; the document *is* the assertion. @@ -62,13 +62,13 @@ This tutorial continues in the project you set up in 3. Run it ```bash - pnpm exec var run + pnpm exec varar run ``` ``` - var-examples/calculator.md + varar-examples/calculator.md ✓ Calculator (0ms) - var-examples/deep-thought.md + varar-examples/deep-thought.md ✓ Deep Thought (1ms) 2 examples, 2 passed, 0 failed @@ -81,7 +81,7 @@ This tutorial continues in the project you set up in Make it a habit: every new example should be seen failing once. This time, break the *document* — claim in `calculator.md` that `1+1` evaluates to `3`. - Run Vár again. The document demands 3, the software answers 2, and the failure + Run Varar again. The document demands 3, the software answers 2, and the failure points at the `3` in your Markdown. Revert it, run once more, and you're green. @@ -91,7 +91,7 @@ This tutorial continues in the project you set up in - Steps come in roles chosen by what they *do*: an **action** stimulates the system, a **sensor** observes it. (There's also **context**, for setting up the starting state — you'll meet it in bigger specs.) -- A sensor *returns* the observed value instead of asserting; Vár does the +- A sensor *returns* the observed value instead of asserting; Varar does the comparison and anchors failures to the document. ## Next diff --git a/typescript/packages/website/src/content/docs/tutorials/get-started.mdx b/typescript/packages/website/src/content/docs/tutorials/get-started.mdx index 0fb4122c..38bcbfbd 100644 --- a/typescript/packages/website/src/content/docs/tutorials/get-started.mdx +++ b/typescript/packages/website/src/content/docs/tutorials/get-started.mdx @@ -1,12 +1,12 @@ --- title: Get started on your computer -description: Install Vár, scaffold a first spec, run it — and watch it fail on purpose. In TypeScript, Python, Java, Kotlin or Ruby. +description: Install Varar, scaffold a first spec, run it — and watch it fail on purpose. In TypeScript, Python, Java, Kotlin or Ruby. --- import { Tabs, TabItem, Aside, Steps } from '@astrojs/starlight/components'; import LangCommand from '$components/LangCommand.astro'; -In this tutorial we'll install Vár into a project, scaffold a working example, run +In this tutorial we'll install Varar into a project, scaffold a working example, run it, and then deliberately break it. By the end you will have seen an [oath](/explanation/oaths/) pass and fail as a test on your own machine. @@ -19,7 +19,7 @@ Every command and code sample below follows that choice. - This adds Vár's author API (the package your step definitions import) and the + This adds Varar's author API (the package your step definitions import) and the adapter that runs specs in your test framework. 2. Scaffold a first spec @@ -27,20 +27,20 @@ Every command and code sample below follows that choice. ``` - created var.config.json - created var-examples/deep-thought.md - created var-examples/steps/deep-thought.steps. + created varar.config.json + created varar-examples/deep-thought.md + created varar-examples/steps/deep-thought.steps. ``` (`` is your language's step-file extension — `ts`, `py`, `rb`, and so on.) - `var.config.json` is the single source of truth for which files are specs and + `varar.config.json` is the single source of truth for which files are specs and which files bind their steps: ```json { - "docs": { "include": ["var-examples/**/*.md"], "exclude": [] }, - "steps": ["var-examples/**/*.steps."] + "docs": { "include": ["varar-examples/**/*.md"], "exclude": [] }, + "steps": ["varar-examples/**/*.steps."] } ``` @@ -57,17 +57,17 @@ Every command and code sample below follows that choice. ``` Notice there are no keywords and no special syntax — it's ordinary Markdown - prose. One sentence makes a checkable claim; Vár matches that phrase, and + prose. One sentence makes a checkable claim; Varar matches that phrase, and everything around it is just documentation for the reader. The steps file binds that sentence to code. A **sensor** reads the software and - returns what it actually produced, for Vár to compare against the number in the + returns what it actually produced, for Varar to compare against the number in the Markdown: ```ts - import { steps } from '@oselvar/var' + import { steps } from '@varar/varar' const { sensor } = steps() @@ -78,10 +78,10 @@ Every command and code sample below follows that choice. ```java package examples; - import com.oselvar.var.Registrar; - import com.oselvar.var.State; - import com.oselvar.var.StateBinder; - import com.oselvar.var.StepDefinitions; + import dev.varar.Registrar; + import dev.varar.State; + import dev.varar.StateBinder; + import dev.varar.StepDefinitions; public final class DeepThoughtSteps implements StepDefinitions { record Ctx() implements State {} @@ -100,8 +100,8 @@ Every command and code sample below follows that choice. package examples - import com.oselvar.varkt.sensor - import com.oselvar.varkt.steps + import dev.varar.kotlin.sensor + import dev.varar.kotlin.steps val deepThoughtSteps = steps { @@ -123,7 +123,7 @@ Every command and code sample below follows that choice. ```ruby - require 'oselvar/var' + require 'varar' steps do sensor('life, the universe and everything is {int}') { 42 } @@ -149,10 +149,10 @@ Every command and code sample below follows that choice.

@@ -170,10 +170,10 @@ Every command and code sample below follows that choice. - Vár reports one example passing: + Varar reports one example passing: ``` - var-examples/deep-thought.md + varar-examples/deep-thought.md ✓ Deep Thought 1 example, 1 passed, 0 failed @@ -227,7 +227,7 @@ Every command and code sample below follows that choice. - Run Vár again. The example now fails: the spec still says `42`, but the sensor + Run Varar again. The example now fails: the spec still says `42`, but the sensor observed `43`. The output shows both values and points at the exact `42` in the Markdown where the promise broke. diff --git a/typescript/packages/website/src/content/docs/tutorials/try-var.mdx b/typescript/packages/website/src/content/docs/tutorials/try-var.mdx index 3d89b320..2430f8f8 100644 --- a/typescript/packages/website/src/content/docs/tutorials/try-var.mdx +++ b/typescript/packages/website/src/content/docs/tutorials/try-var.mdx @@ -1,5 +1,5 @@ --- -title: Try Vár in your browser +title: Try Varar in your browser description: Break an executable spec, watch it fail, and mend it — nothing to install. --- @@ -34,19 +34,19 @@ You can select a different language in the top-right corner of the page, but you Change `42` to `41` in the **deep-thought.steps.ts** file. Now the oath is broken from the other side. -This is the whole idea of Vár: the document and the code check each other. When +This is the whole idea of Varar: the document and the code check each other. When they disagree, your tests fail, and the failure points at the words. ## What you just learned -- A Vár spec is plain Markdown. You read it like documentation, because it *is* +- A Varar spec is plain Markdown. You read it like documentation, because it *is* documentation. -- A **sensor** returns what the software actually does; Vár compares that +- A **sensor** returns what the software actually does; Varar compares that against what the document claims. - When document and code disagree — no matter which side drifted — the failure is anchored to the exact span in the document. ## Next -Ready to run Vár on your own machine? +Ready to run Varar on your own machine? [Get started on your computer](/tutorials/get-started/) — install, scaffold your first oath, and watch it fail on purpose. diff --git a/typescript/packages/website/src/lib/browser-grammar-loader.ts b/typescript/packages/website/src/lib/browser-grammar-loader.ts index 9a7228e7..b3faef62 100644 --- a/typescript/packages/website/src/lib/browser-grammar-loader.ts +++ b/typescript/packages/website/src/lib/browser-grammar-loader.ts @@ -1,4 +1,4 @@ -import type { GrammarLoader } from '@oselvar/var-language' +import type { GrammarLoader } from '@varar/language' import tsxGrammarUrl from 'tree-sitter-typescript/tree-sitter-tsx.wasm?url' import tsGrammarUrl from 'tree-sitter-typescript/tree-sitter-typescript.wasm?url' // Vite rewrites each `?url` import to the hashed asset URL it emits for the diff --git a/typescript/packages/website/src/lib/cm-run.ts b/typescript/packages/website/src/lib/cm-run.ts index fea0786c..bfa1adce 100644 --- a/typescript/packages/website/src/lib/cm-run.ts +++ b/typescript/packages/website/src/lib/cm-run.ts @@ -1,7 +1,7 @@ import { type Diagnostic, linter } from '@codemirror/lint' import { type Extension, RangeSetBuilder, StateEffect, StateField } from '@codemirror/state' import { Decoration, type DecorationSet, EditorView } from '@codemirror/view' -import { type Drift, runResultDiagnostics, type SpecResults } from '@oselvar/var-core' +import { type Drift, runResultDiagnostics, type SpecResults } from '@varar/core' // Effect carrying the latest run results (null clears them). export const setRunResults = StateEffect.define() diff --git a/typescript/packages/website/src/lib/memory-baseline-store.ts b/typescript/packages/website/src/lib/memory-baseline-store.ts index 4ffb7315..52af3daa 100644 --- a/typescript/packages/website/src/lib/memory-baseline-store.ts +++ b/typescript/packages/website/src/lib/memory-baseline-store.ts @@ -1,6 +1,6 @@ -import type { BaselineStore } from '@oselvar/var-core' +import type { BaselineStore } from '@varar/core' -// The browser's BaselineStore: the drift baseline (var.lock.json) held in a +// The browser's BaselineStore: the drift baseline (varar.lock.json) held in a // single string in memory. There is no filesystem to commit to in the browser, // so a fresh page load starts from no baseline — the first run of each spec // records it, and editing takes it from there. The core owns the format diff --git a/typescript/packages/website/src/lib/memory-file-system.ts b/typescript/packages/website/src/lib/memory-file-system.ts index 4fa33fdb..590c6dd1 100644 --- a/typescript/packages/website/src/lib/memory-file-system.ts +++ b/typescript/packages/website/src/lib/memory-file-system.ts @@ -1,4 +1,4 @@ -import type { FileSystem } from '@oselvar/var-lsp' +import type { FileSystem } from '@varar/lsp' // Fresh on every worker start (i.e. every page load) — this is a demo site, // not a persistent coding environment, so there's no cross-reload storage to diff --git a/typescript/packages/website/src/lib/run-client.ts b/typescript/packages/website/src/lib/run-client.ts index bbfecc05..1479a57f 100644 --- a/typescript/packages/website/src/lib/run-client.ts +++ b/typescript/packages/website/src/lib/run-client.ts @@ -1,4 +1,4 @@ -import type { Drift, SpecResults } from '@oselvar/var-core' +import type { Drift, SpecResults } from '@varar/core' export type RunInput = { varPath: string diff --git a/typescript/packages/website/src/lib/run-spec.ts b/typescript/packages/website/src/lib/run-spec.ts index a13dc2b2..e2fdb824 100644 --- a/typescript/packages/website/src/lib/run-spec.ts +++ b/typescript/packages/website/src/lib/run-spec.ts @@ -1,4 +1,3 @@ -import { buildRegistry, contextFactory } from '@oselvar/var/registry' import { type BaselineStore, type Drift, @@ -11,7 +10,8 @@ import { type SpecResults, type TestSink, toFailure, -} from '@oselvar/var-core' +} from '@varar/core' +import { buildRegistry, contextFactory } from '@varar/varar/registry' export type RunOutcome = { readonly results: SpecResults diff --git a/typescript/packages/website/src/lib/run-worker.ts b/typescript/packages/website/src/lib/run-worker.ts index 9d72d217..2b2ef3af 100644 --- a/typescript/packages/website/src/lib/run-worker.ts +++ b/typescript/packages/website/src/lib/run-worker.ts @@ -1,7 +1,7 @@ -import * as varRuntime from '@oselvar/var' -import { _resetBuilder } from '@oselvar/var/registry' -import * as varCore from '@oselvar/var-core' -import { type Drift, hashSource, type SpecResults } from '@oselvar/var-core' +import * as varCore from '@varar/core' +import { type Drift, hashSource, type SpecResults } from '@varar/core' +import * as varRuntime from '@varar/varar' +import { _resetBuilder } from '@varar/varar/registry' import * as ts from 'typescript' import { createMemoryBaselineStore } from './memory-baseline-store.ts' import { runRegisteredSpec } from './run-spec.ts' @@ -14,7 +14,7 @@ type RunInput = { update?: boolean } -// One baseline store for the whole page (all specs keyed inside var.lock.json), +// One baseline store for the whole page (all specs keyed inside varar.lock.json), // living as long as the worker. Drift is measured against it across edits. const baselineStore = createMemoryBaselineStore() // Mirrors run-client.ts's WorkerRequest/WorkerResponse — the requestId lets @@ -58,21 +58,21 @@ function createModuleLoader(files: ReadonlyArray) { fileName: file.path, }).outputText const require = (spec: string): unknown => { - if (spec === '@oselvar/var' || spec === '@oselvar/var-vitest') return varRuntime - if (spec === '@oselvar/var-core') return varCore + if (spec === '@varar/varar' || spec === '@varar/vitest') return varRuntime + if (spec === '@varar/core') return varCore if (spec.startsWith('.')) { const target = resolveRelative(spec, file.path) if (target) return load(target) } throw new Error( - `Cannot import "${spec}" in the browser runner — import steps() from "@oselvar/var", or add the imported file to this editor.`, + `Cannot import "${spec}" in the browser runner — import steps() from "@varar/varar", or add the imported file to this editor.`, ) } const mod = { exports: {} as Record } // Registered before execution so import cycles see the partial exports // instead of recursing forever. cache.set(file.path, mod.exports) - // `//# sourceURL` makes @oselvar/var's stack-based callerLocation see the real path. + // `//# sourceURL` makes @varar/varar's stack-based callerLocation see the real path. new Function('require', 'exports', 'module', `${js}\n//# sourceURL=${file.path}`)( require, mod.exports, diff --git a/typescript/packages/website/src/lib/ts-diagnostics.ts b/typescript/packages/website/src/lib/ts-diagnostics.ts index b94be5a9..f55c312f 100644 --- a/typescript/packages/website/src/lib/ts-diagnostics.ts +++ b/typescript/packages/website/src/lib/ts-diagnostics.ts @@ -13,17 +13,17 @@ for (const [p, text] of Object.entries(libModules)) { if (base) LIB.set(base, text) } -// The REAL `@oselvar/var` typings: the package's exports point at its +// The REAL `@varar/varar` typings: the package's exports point at its // TypeScript source (`./src/index.ts`), so the editor type-checks against the // same files authors install — no hand-maintained ambient copy to drift when -// the API changes. `internal.ts`'s own imports from @oselvar/var-core stay +// the API changes. `internal.ts`'s own imports from @varar/core stay // unresolved in here; that only degrades types INSIDE internal.ts (whose // diagnostics are never requested) — steps's public type closure is // self-contained. -import varIndexSource from '../../../var/src/index.ts?raw' -import varInternalSource from '../../../var/src/internal.ts?raw' +import varIndexSource from '../../../varar/src/index.ts?raw' +import varInternalSource from '../../../varar/src/internal.ts?raw' -const VAR_PACKAGE_DIR = '/oselvar-var' +const VAR_PACKAGE_DIR = '/varar' const VAR_ENTRY = `${VAR_PACKAGE_DIR}/index.ts` const VAR_SOURCES: ReadonlyArray = [ [VAR_ENTRY, varIndexSource], @@ -38,7 +38,7 @@ const OPTIONS: ts.CompilerOptions = { // workspace does (Node runs the sources natively). allowImportingTsExtensions: true, baseUrl: '/', - paths: { '@oselvar/var': [VAR_ENTRY] }, + paths: { '@varar/varar': [VAR_ENTRY] }, noEmit: true, strict: false, skipLibCheck: true, diff --git a/typescript/packages/website/src/lib/var-worker.ts b/typescript/packages/website/src/lib/var-worker.ts index 34edb7a5..a8e97100 100644 --- a/typescript/packages/website/src/lib/var-worker.ts +++ b/typescript/packages/website/src/lib/var-worker.ts @@ -1,5 +1,5 @@ -import { DEFAULT_SNIPPET_TEMPLATE } from '@oselvar/var-language' -import { registerHandlers } from '@oselvar/var-lsp' +import { DEFAULT_SNIPPET_TEMPLATE } from '@varar/language' +import { registerHandlers } from '@varar/lsp' import { BrowserMessageReader, BrowserMessageWriter, diff --git a/typescript/packages/website/src/scripts/editor-mount.ts b/typescript/packages/website/src/scripts/editor-mount.ts index 6fd0c4ad..b444e33a 100644 --- a/typescript/packages/website/src/scripts/editor-mount.ts +++ b/typescript/packages/website/src/scripts/editor-mount.ts @@ -2,7 +2,7 @@ import { foldGutter } from '@codemirror/language' import { LSPClient, languageServerExtensions } from '@codemirror/lsp-client' import { Annotation, EditorSelection, EditorState, type Extension } from '@codemirror/state' import { lineNumbers } from '@codemirror/view' -import { hashSource } from '@oselvar/var-core' +import { hashSource } from '@varar/core' import { basicSetup, EditorView, minimalSetup } from 'codemirror' import { flashExtension, type GenerateSnippet, stepGenAffordance } from '../lib/cm-generate-step.ts' import { CM_LANGUAGE, markdownHighlight } from '../lib/cm-languages.ts' diff --git a/typescript/packages/website/src/styles/custom.css b/typescript/packages/website/src/styles/custom.css index d47c0374..6e43d2cd 100644 --- a/typescript/packages/website/src/styles/custom.css +++ b/typescript/packages/website/src/styles/custom.css @@ -69,7 +69,7 @@ h6 { } /* - * Vár earthy palette, ported from + * Varar earthy palette, ported from * doc/superpowers/specs/2026-06-26-earthy-color-scheme-design.md * onto Starlight's own theming variables. Dark is the default `:root` * (Starlight's convention); light overrides live under diff --git a/typescript/packages/website/tests/cm-run-drift.test.ts b/typescript/packages/website/tests/cm-run-drift.test.ts index 06000e80..1b20bd0a 100644 --- a/typescript/packages/website/tests/cm-run-drift.test.ts +++ b/typescript/packages/website/tests/cm-run-drift.test.ts @@ -1,4 +1,4 @@ -import type { Drift } from '@oselvar/var-core' +import type { Drift } from '@varar/core' import { expect, test } from 'vitest' import { driftDiagnostics } from '../src/lib/cm-run.ts' diff --git a/typescript/packages/website/tests/ts-diagnostics.test.ts b/typescript/packages/website/tests/ts-diagnostics.test.ts index d4bc2ae9..e653abf1 100644 --- a/typescript/packages/website/tests/ts-diagnostics.test.ts +++ b/typescript/packages/website/tests/ts-diagnostics.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest' // The real dogfood sample the front-page editor shows — if the editor's -// virtual module drifts from the actual @oselvar/var API, this file stops +// virtual module drifts from the actual @varar/varar API, this file stops // type-checking cleanly and the first test fails, exactly like the front // page would. import librarySteps from '../../../../examples/typescript-vitest/steps/library.steps.ts?raw' @@ -16,13 +16,13 @@ function realProblems(tsd: ReturnType, name: string, return tsd.diagnostics(name).filter((d) => !d.message.includes("'./library'")) } -test('the front-page library sample type-checks against the real @oselvar/var types', () => { +test('the front-page library sample type-checks against the real @varar/varar types', () => { const problems = realProblems(createTsDiagnostics(), 'library_steps.ts', librarySteps) expect(problems).toEqual([]) }) test('stimulus and sensor are the destructurable names steps returns', () => { - const source = `import { steps } from '@oselvar/var' + const source = `import { steps } from '@varar/varar' const { stimulus, sensor } = steps(() => ({ total: 0 })) stimulus('I add {int}', (state, n) => ({ total: state.total + n })) sensor('the total is {int}', (state) => state.total) @@ -32,7 +32,7 @@ sensor('the total is {int}', (state) => state.total) }) test('the stale pre-rename API names are rejected', () => { - const source = `import { steps } from '@oselvar/var' + const source = `import { steps } from '@varar/varar' const { context, action } = steps(() => ({})) ` const tsd = createTsDiagnostics() @@ -42,7 +42,7 @@ const { context, action } = steps(() => ({})) }) test('a format whose parameter contradicts its parse return is a type error', () => { - const source = `import { steps } from '@oselvar/var' + const source = `import { steps } from '@varar/varar' const { sensor } = steps(() => ({})).param( 'money', /£\\d+\\.\\d{2}/, diff --git a/typescript/packages/website/wrangler.jsonc b/typescript/packages/website/wrangler.jsonc index 6ba83568..89218dc6 100644 --- a/typescript/packages/website/wrangler.jsonc +++ b/typescript/packages/website/wrangler.jsonc @@ -5,5 +5,5 @@ "directory": "./dist", "not_found_handling": "404-page" }, - "routes": [{ "pattern": "var.oselvar.com", "custom_domain": true }] + "routes": [{ "pattern": "varar.dev", "custom_domain": true }] } diff --git a/typescript/pnpm-lock.yaml b/typescript/pnpm-lock.yaml index 7aecb821..d167ce7e 100644 --- a/typescript/pnpm-lock.yaml +++ b/typescript/pnpm-lock.yaml @@ -35,95 +35,88 @@ importers: ../examples/typescript-vitest: devDependencies: - '@oselvar/var': - specifier: workspace:* - version: link:../../typescript/packages/var - '@oselvar/var-core': - specifier: workspace:* - version: link:../../typescript/packages/var-core - '@oselvar/var-vitest': - specifier: workspace:* - version: link:../../typescript/packages/var-vitest '@types/node': specifier: ^26.1.0 version: 26.1.1 - typescript: - specifier: ^6.0.3 - version: 6.0.3 - vitest: - specifier: ^4.1.10 - version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) - - packages/cucumber: - devDependencies: - '@cucumber/cucumber': - specifier: ^13.0.0 - version: 13.1.1 - '@oselvar/var': - specifier: workspace:* - version: link:../var - '@oselvar/var-cli': - specifier: workspace:* - version: link:../var-cli - '@oselvar/var-core': + '@varar/core': specifier: workspace:* - version: link:../var-core - '@oselvar/var-vitest': + version: link:../../typescript/packages/core + '@varar/varar': specifier: workspace:* - version: link:../var-vitest - '@types/node': - specifier: ^26.1.0 - version: 26.1.1 - vitest: - specifier: ^4.1.10 - version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) - - packages/var: - dependencies: - '@oselvar/var-core': + version: link:../../typescript/packages/varar + '@varar/vitest': specifier: workspace:* - version: link:../var-core - devDependencies: + version: link:../../typescript/packages/vitest + typescript: + specifier: ^6.0.3 + version: 6.0.3 vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) - packages/var-cli: + packages/cli: dependencies: - '@oselvar/var-config': + '@varar/config': specifier: workspace:* - version: link:../var-config - '@oselvar/var-core': + version: link:../config + '@varar/core': specifier: workspace:* - version: link:../var-core - '@oselvar/var-runner': + version: link:../core + '@varar/runner': specifier: workspace:* - version: link:../var-runner + version: link:../runner devDependencies: - '@oselvar/var': + '@varar/varar': specifier: workspace:* - version: link:../var + version: link:../varar - packages/var-config: + packages/config: dependencies: - '@oselvar/var-core': + '@varar/core': specifier: workspace:* - version: link:../var-core + version: link:../core - packages/var-core: + packages/core: dependencies: '@cucumber/cucumber-expressions': specifier: ^20.0.0 version: 20.0.0 - packages/var-language: + packages/cucumber: + devDependencies: + '@cucumber/cucumber': + specifier: ^13.0.0 + version: 13.1.1 + '@types/node': + specifier: ^26.1.0 + version: 26.1.1 + '@varar/cli': + specifier: workspace:* + version: link:../cli + '@varar/core': + specifier: workspace:* + version: link:../core + '@varar/varar': + specifier: workspace:* + version: link:../varar + '@varar/vitest': + specifier: workspace:* + version: link:../vitest + vitest: + specifier: ^4.1.10 + version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + + packages/language: dependencies: '@cucumber/cucumber-expressions': specifier: ^20.0.0 version: 20.0.0 - '@oselvar/var-core': + '@varar/core': specifier: workspace:* - version: link:../var-core + version: link:../core + typescript: + specifier: ^6.0.3 + version: 6.0.3 web-tree-sitter: specifier: ^0.26.10 version: 0.26.11 @@ -147,20 +140,20 @@ importers: specifier: ^0.23.2 version: 0.23.2 - packages/var-lsp: + packages/lsp: dependencies: - '@oselvar/var-config': - specifier: workspace:* - version: link:../var-config - '@oselvar/var-core': - specifier: workspace:* - version: link:../var-core - '@oselvar/var-language': - specifier: workspace:* - version: link:../var-language '@tree-sitter-grammars/tree-sitter-kotlin': specifier: ^1.1.0 version: 1.1.0 + '@varar/config': + specifier: workspace:* + version: link:../config + '@varar/core': + specifier: workspace:* + version: link:../core + '@varar/language': + specifier: workspace:* + version: link:../language tree-sitter-java: specifier: ^0.23.5 version: 0.23.5 @@ -183,35 +176,45 @@ importers: specifier: ^1.0.12 version: 1.0.12 - packages/var-runner: + packages/runner: dependencies: - '@oselvar/var': + '@varar/config': + specifier: workspace:* + version: link:../config + '@varar/core': specifier: workspace:* - version: link:../var - '@oselvar/var-config': + version: link:../core + '@varar/varar': specifier: workspace:* - version: link:../var-config - '@oselvar/var-core': + version: link:../varar + + packages/varar: + dependencies: + '@varar/core': specifier: workspace:* - version: link:../var-core + version: link:../core + devDependencies: + vitest: + specifier: ^4.1.10 + version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) - packages/var-vitest: + packages/vitest: dependencies: - '@oselvar/var': + '@varar/config': specifier: workspace:* - version: link:../var - '@oselvar/var-config': + version: link:../config + '@varar/core': specifier: workspace:* - version: link:../var-config - '@oselvar/var-core': + version: link:../core + '@varar/language': specifier: workspace:* - version: link:../var-core - '@oselvar/var-language': + version: link:../language + '@varar/runner': specifier: workspace:* - version: link:../var-language - '@oselvar/var-runner': + version: link:../runner + '@varar/varar': specifier: workspace:* - version: link:../var-runner + version: link:../varar tree-sitter-typescript: specifier: ^0.23.2 version: 0.23.2 @@ -222,11 +225,11 @@ importers: specifier: ^4.0.0 version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) - packages/var-vscode: + packages/vscode: dependencies: - '@oselvar/var-lsp': + '@varar/lsp': specifier: workspace:* - version: link:../var-lsp + version: link:../lsp vscode-languageclient: specifier: ^10.1.0 version: 10.1.0 @@ -294,21 +297,21 @@ importers: '@lezer/highlight': specifier: ^1.2.3 version: 1.2.3 - '@oselvar/var': + '@tailwindcss/vite': + specifier: ^4.3.2 + version: 4.3.3(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + '@varar/core': specifier: workspace:^ - version: link:../var - '@oselvar/var-core': + version: link:../core + '@varar/language': specifier: workspace:^ - version: link:../var-core - '@oselvar/var-language': + version: link:../language + '@varar/lsp': specifier: workspace:^ - version: link:../var-language - '@oselvar/var-lsp': + version: link:../lsp + '@varar/varar': specifier: workspace:^ - version: link:../var-lsp - '@tailwindcss/vite': - specifier: ^4.3.2 - version: 4.3.3(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + version: link:../varar astro: specifier: ^7.0.6 version: 7.1.1(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@26.1.1)(jiti@2.7.0)(yaml@2.9.0) diff --git a/typescript/scripts/install-vscode.mjs b/typescript/scripts/install-vscode.mjs index 6f1233f1..74384534 100755 --- a/typescript/scripts/install-vscode.mjs +++ b/typescript/scripts/install-vscode.mjs @@ -14,7 +14,7 @@ import { fileURLToPath } from 'node:url' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const SRC = join(ROOT, 'packages', 'var-vscode') -const NAME = 'oselvar.oselvar-var-0.0.0' +const NAME = 'varar.varar-0.0.0' const TARGETS = [ join(homedir(), '.vscode', 'extensions', NAME), join(homedir(), '.cursor', 'extensions', NAME), @@ -62,7 +62,7 @@ function registerExtension(jsonPath, dstPath, name) { } if (!Array.isArray(data)) return } - const id = 'oselvar.oselvar-var' + const id = 'varar.varar' const filtered = data.filter((e) => e?.identifier?.id !== id) filtered.push({ identifier: { id, uuid: EXTENSION_UUID }, @@ -72,7 +72,7 @@ function registerExtension(jsonPath, dstPath, name) { metadata: { id: EXTENSION_UUID, publisherId: PUBLISHER_UUID, - publisherDisplayName: 'oselvar', + publisherDisplayName: 'varar', targetPlatform: 'undefined', updated: false, isPreReleaseVersion: false, diff --git a/typescript/scripts/lint-no-reexports.mjs b/typescript/scripts/lint-no-reexports.mjs index 99ac3824..00a641d6 100644 --- a/typescript/scripts/lint-no-reexports.mjs +++ b/typescript/scripts/lint-no-reexports.mjs @@ -11,7 +11,7 @@ // // Relative re-exports (export { x } from './x.js') are the normal way a // package assembles its own entry point and are allowed. A package's own -// subpaths (e.g. '@oselvar/var-vitest/runtime' from inside var-vitest) count +// subpaths (e.g. '@varar/vitest/runtime' from inside var-vitest) count // as self, not cross-package. import { readdirSync, readFileSync } from 'node:fs' import { dirname, join, relative, resolve } from 'node:path' diff --git a/typescript/tsconfig.tests.json b/typescript/tsconfig.tests.json index 437d3fa5..ad5c1220 100644 --- a/typescript/tsconfig.tests.json +++ b/typescript/tsconfig.tests.json @@ -3,7 +3,7 @@ // every package's tests/ (the builds only emit their own src/), plus the // root/project config files (vitest.config.ts, the vitest plugin helper) — // none of which belong to any package's src/. noEmit: this is a check, not - // a build. var.config.json needs no type-checking. The website's src is + // a build. varar.config.json needs no type-checking. The website's src is // excluded — it has its own Astro/browser toolchain (astro/tsconfigs/strict, // DOM libs) — but its plain vitest.config.ts is cheap to check and included // below. @@ -15,13 +15,13 @@ "sourceMap": false }, "include": [ - "packages/var-config/tests", - "packages/var-core/tests", - "packages/var-cli/tests", - "packages/var-language/tests", - "packages/var-lsp/tests", - "packages/var/tests", - "packages/var-vitest/tests", + "packages/config/tests", + "packages/core/tests", + "packages/cli/tests", + "packages/language/tests", + "packages/lsp/tests", + "packages/varar/tests", + "packages/vitest/tests", "vitest.config.ts", "vitest.plugins.ts", "packages/*/vitest.config.ts" diff --git a/typescript/vitest.config.ts b/typescript/vitest.config.ts index 725bd81c..97839b22 100644 --- a/typescript/vitest.config.ts +++ b/typescript/vitest.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'vitest/config' -import { VarResultsReporter } from './packages/var-vitest/src/reporter.js' +import { VarResultsReporter } from './packages/vitest/src/reporter.js' -// The reporter's cwd is the REPO root (not typescript/): var.config.json lives +// The reporter's cwd is the REPO root (not typescript/): varar.config.json lives // there, and spec paths in .var/ results must stay relative to it (no `..` // segments) now that the spec corpus is doc/examples/ at the repo root. const repoRoot = new URL('..', import.meta.url).pathname diff --git a/typescript/vitest.plugins.ts b/typescript/vitest.plugins.ts index 4fef59c3..64a897eb 100644 --- a/typescript/vitest.plugins.ts +++ b/typescript/vitest.plugins.ts @@ -31,7 +31,7 @@ export function defineSourceTestConfig() { plugins: [stripTypescriptSourcemap()], test: { include: ['{src,tests}/**/*.test.ts'], - server: { deps: { inline: [/^@oselvar\//] } }, + server: { deps: { inline: [/^@varar\//] } }, }, }) } diff --git a/var.config.json b/varar.config.json similarity index 75% rename from var.config.json rename to varar.config.json index 4857bc0f..9893bc17 100644 --- a/var.config.json +++ b/varar.config.json @@ -1,5 +1,5 @@ { - "$schema": "conformance/config/var.config.schema.json", + "$schema": "conformance/config/varar.config.schema.json", "docs": { "include": ["examples/typescript-vitest/*.md"], "exclude": ["examples/typescript-vitest/README.md"]