dirsql 0.4.72__tar.gz → 0.4.74__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dirsql-0.4.72 → dirsql-0.4.74}/Cargo.lock +2 -2
- {dirsql-0.4.72 → dirsql-0.4.74}/PKG-INFO +1 -1
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/reference/cli.md +8 -0
- {dirsql-0.4.72/packages/rust → dirsql-0.4.74}/docs/reference/path-tables.md +7 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/Cargo.toml +1 -1
- dirsql-0.4.74/packages/python/changelog.d/2026-10-05-brace-groups-follow-bash.md +6 -0
- {dirsql-0.4.72/packages/rust → dirsql-0.4.74/packages/python}/docs/reference/cli.md +8 -0
- {dirsql-0.4.72 → dirsql-0.4.74/packages/python}/docs/reference/path-tables.md +7 -0
- dirsql-0.4.74/packages/python/e2e-attestations/claude-1262-brace-literal.json +7 -0
- dirsql-0.4.74/packages/python/e2e-attestations/claude-1286-nested-repo.json +7 -0
- dirsql-0.4.74/packages/python/migrations.d/2026-10-05-brace-groups-follow-bash.md +39 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/Cargo.toml +1 -1
- {dirsql-0.4.72/packages/python → dirsql-0.4.74/packages/rust}/docs/reference/cli.md +8 -0
- {dirsql-0.4.72/packages/python → dirsql-0.4.74/packages/rust}/docs/reference/path-tables.md +7 -0
- dirsql-0.4.74/packages/rust/src/brace.rs +425 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/lib.rs +1 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/scanner.rs +86 -33
- {dirsql-0.4.72 → dirsql-0.4.74}/Cargo.toml +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/README.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/__init__.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/_async.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/plan_entry_path.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/py.typed +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/read_config.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/resolve_configs_extension_specs.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/dirsql/resolve_package.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/AGENTS.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/context/.gitattributes +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/context/recipes.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/context/troubleshooting.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/context/usage.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/explanation.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/getting-started.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/embed.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/persist.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/index.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/package.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/plugins.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/reference/columns.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/reference/config.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/reference/hooks.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/README.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-17-extension-resolution-via-core.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-18-export-scan-failure.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-18-flatten-row-event-in-core.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-18-programmatic-extension-plan.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-09-24-query-rows-in-projection-order.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-10-02-readme-docs-host.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-10-03-path-table-directory-one-level.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-10-05-cli-context.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-10-05-gitignore-requires-repo.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-10-05-path-table-node-modules-named.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-10-05-path-table-symlinks.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/2026-10-05-path-table-trailing-slash.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/conftest.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/context/.gitattributes +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/context/recipes.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/context/troubleshooting.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/context/usage.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1081-indicatif-progress.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1119-ext-plan.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1120-py-ext.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1122-ext-cleanup.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1130-codec-crates.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1131-flatten-row-event.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1139-sse-flatten.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1140-scan-failure-export.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1148-clippy-all-targets.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1164-sigint-fatal.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1170-projection-order.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1172-on-file-path.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1173-py-projection-order.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1184-win-rel-paths.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1185-win-abs-table-names.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1186-win-test-harness.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1196-join-index.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1199-glob-prefix.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1201-single-walk.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1204-embed-batch.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1207-win-argv-quote.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1208-win-nonverbatim-paths.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1215-on-file-batch.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1216-on-file-cleanup.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1223-config-glob-rule.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1223-dir-one-level.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1226-stale-docs.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1238-parallel-walk.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1241-content-prefetch.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1243-ingest-perf.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1249-dotfiles.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1256-symlinks.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1257-trailing-slash.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1264-context.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1267-posix-classes.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1269-utf8-chars.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1271-node-modules-named.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1272-ancestor-gitignore.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1273-walk-prune.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-1285-ingest-fast.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-09-24-query-rows-in-projection-order.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-10-02-readme-docs-host.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-10-03-path-table-directory-one-level.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-10-05-cli-context.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-10-05-gitignore-requires-repo.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-10-05-path-table-node-modules-named.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-10-05-path-table-symlinks.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/2026-10-05-path-table-trailing-slash.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/testing-conventions.toml +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/tests/e2e/speed_of_light.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/README.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/context/.gitattributes +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/context/recipes.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/context/troubleshooting.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/context/usage.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/context.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/command.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/db.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/extension_resolution.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/functions.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/launcher.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/on_file.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/parsed_vtab.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/posix_class.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/progress.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/row_event_flat.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/vtab.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/vtab_scaffold.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.72 → dirsql-0.4.74}/pyproject.toml +0 -0
|
@@ -552,7 +552,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
|
|
552
552
|
|
|
553
553
|
[[package]]
|
|
554
554
|
name = "dirsql"
|
|
555
|
-
version = "0.4.
|
|
555
|
+
version = "0.4.74"
|
|
556
556
|
dependencies = [
|
|
557
557
|
"assert_cmd",
|
|
558
558
|
"axum",
|
|
@@ -601,7 +601,7 @@ dependencies = [
|
|
|
601
601
|
|
|
602
602
|
[[package]]
|
|
603
603
|
name = "dirsql-py-ext"
|
|
604
|
-
version = "0.4.
|
|
604
|
+
version = "0.4.74"
|
|
605
605
|
dependencies = [
|
|
606
606
|
"dirsql",
|
|
607
607
|
"pyo3",
|
|
@@ -453,6 +453,14 @@ first line is `dirsql <version>`, then sections on usage, recipes and
|
|
|
453
453
|
troubleshooting. The guide is compiled into the binary, so it describes the
|
|
454
454
|
version that prints it. It takes no flags and exits `0`.
|
|
455
455
|
|
|
456
|
+
The dirsql agent skill tells an agent to run this command before it writes a
|
|
457
|
+
query. Install the skill into Claude Code, Codex, Cursor, Gemini CLI and
|
|
458
|
+
others with:
|
|
459
|
+
|
|
460
|
+
```sh
|
|
461
|
+
npx skills add thekevinscott/dirsql
|
|
462
|
+
```
|
|
463
|
+
|
|
456
464
|
## Plugins
|
|
457
465
|
|
|
458
466
|
A **plugin** is an ordinary Python package that ships a `dirsql.toml` config
|
|
@@ -54,6 +54,13 @@ depth, as in the shell. A trailing `/` is `*` appended, so `'./*/'` is
|
|
|
54
54
|
A path containing `*`, `?`, `[` or `{` is a glob and is used exactly as
|
|
55
55
|
written: `*` matches within a single directory, `**` crosses directories.
|
|
56
56
|
|
|
57
|
+
Braces expand as bash expands them. A group holding a comma is an
|
|
58
|
+
alternation: `'./docs/{a,b}.md'` is `docs/a.md` and `docs/b.md`, and groups
|
|
59
|
+
nest. A group holding a `..` range counts: `'./log/{1..3}.txt'`,
|
|
60
|
+
`'./{a..c}.md'`, `'./{01..10}.md'` (zero-padded) and `'./{1..10..2}.md'`
|
|
61
|
+
(every second value). Any other brace is literal text, so `'./br/{q}.md'`
|
|
62
|
+
names the file `br/{q}.md`, and `\{` is always a literal brace.
|
|
63
|
+
|
|
57
64
|
The scan starts at the last directory named outright before the first glob
|
|
58
65
|
component, and descends only as deep as the pattern can match --
|
|
59
66
|
`'./small/*.md'` lists `small/` and nothing else -- so a query over one
|
|
@@ -4,7 +4,7 @@ name = "dirsql-py-ext"
|
|
|
4
4
|
# pypi/maturin handler can rewrite it via `write-version` before
|
|
5
5
|
# `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
|
|
6
6
|
# and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.74"
|
|
8
8
|
# abi3 note: the `abi3-py310` pyo3 feature (below) builds ONE stable-ABI
|
|
9
9
|
# `cp310-abi3` wheel per platform that loads on every CPython >= 3.10
|
|
10
10
|
# (matching `pyproject.toml`'s `requires-python = ">=3.10"`), instead of
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
**Changed** Braces in a path-table follow bash brace expansion. A group with
|
|
2
|
+
a top-level comma (`{a,b}`, nested allowed) is an alternation, and a `..`
|
|
3
|
+
range (`{1..3}`, `{a..c}`, `{01..10}`, `{1..10..2}`) expands to each value.
|
|
4
|
+
Any other brace is literal text: `'./br/{q}.md'` names the file `br/{q}.md`
|
|
5
|
+
where it used to match `br/q.md`, and `{` or `}` without a partner no longer
|
|
6
|
+
fails to compile. Config `[[table]] glob` is unchanged.
|
|
@@ -453,6 +453,14 @@ first line is `dirsql <version>`, then sections on usage, recipes and
|
|
|
453
453
|
troubleshooting. The guide is compiled into the binary, so it describes the
|
|
454
454
|
version that prints it. It takes no flags and exits `0`.
|
|
455
455
|
|
|
456
|
+
The dirsql agent skill tells an agent to run this command before it writes a
|
|
457
|
+
query. Install the skill into Claude Code, Codex, Cursor, Gemini CLI and
|
|
458
|
+
others with:
|
|
459
|
+
|
|
460
|
+
```sh
|
|
461
|
+
npx skills add thekevinscott/dirsql
|
|
462
|
+
```
|
|
463
|
+
|
|
456
464
|
## Plugins
|
|
457
465
|
|
|
458
466
|
A **plugin** is an ordinary Python package that ships a `dirsql.toml` config
|
|
@@ -54,6 +54,13 @@ depth, as in the shell. A trailing `/` is `*` appended, so `'./*/'` is
|
|
|
54
54
|
A path containing `*`, `?`, `[` or `{` is a glob and is used exactly as
|
|
55
55
|
written: `*` matches within a single directory, `**` crosses directories.
|
|
56
56
|
|
|
57
|
+
Braces expand as bash expands them. A group holding a comma is an
|
|
58
|
+
alternation: `'./docs/{a,b}.md'` is `docs/a.md` and `docs/b.md`, and groups
|
|
59
|
+
nest. A group holding a `..` range counts: `'./log/{1..3}.txt'`,
|
|
60
|
+
`'./{a..c}.md'`, `'./{01..10}.md'` (zero-padded) and `'./{1..10..2}.md'`
|
|
61
|
+
(every second value). Any other brace is literal text, so `'./br/{q}.md'`
|
|
62
|
+
names the file `br/{q}.md`, and `\{` is always a literal brace.
|
|
63
|
+
|
|
57
64
|
The scan starts at the last directory named outright before the first glob
|
|
58
65
|
component, and descends only as deep as the pattern can match --
|
|
59
66
|
`'./small/*.md'` lists `small/` and nothing else -- so a query over one
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
### Core: path-table braces follow bash brace expansion
|
|
2
|
+
|
|
3
|
+
#### Summary
|
|
4
|
+
|
|
5
|
+
A path-table's braces now expand as bash expands them before globbing: a
|
|
6
|
+
group with a top-level comma is an alternation, a `..` range expands to each
|
|
7
|
+
value, and every other brace is literal text. `'./br/{q}.md'` used to match
|
|
8
|
+
`br/q.md` and now matches only a file named `br/{q}.md`; `'./a{1..3}.md'`
|
|
9
|
+
used to match nothing and now matches `a1.md`, `a2.md`, `a3.md`. Comma groups
|
|
10
|
+
behave as before. Every surface that resolves a path-table is affected (CLI,
|
|
11
|
+
REPL, HTTP server, SDKs, `--on-file`); config `[[table]] glob` is not.
|
|
12
|
+
|
|
13
|
+
#### Required changes
|
|
14
|
+
|
|
15
|
+
| Surface | Before | After |
|
|
16
|
+
| ------- | ------ | ----- |
|
|
17
|
+
| One-option group | `SELECT * FROM './br/{q}.md'` (matched `br/q.md`) | `SELECT * FROM './br/q.md'` |
|
|
18
|
+
|
|
19
|
+
#### Deprecations removed
|
|
20
|
+
|
|
21
|
+
_None._
|
|
22
|
+
|
|
23
|
+
#### Behavior changes without code changes
|
|
24
|
+
|
|
25
|
+
- A brace group with neither a top-level comma nor a valid `..` range is
|
|
26
|
+
literal text.
|
|
27
|
+
- `{1..3}`, `{a..c}`, `{01..10}` and `{1..10..2}` expand to their values
|
|
28
|
+
instead of matching the literal text between the braces.
|
|
29
|
+
- An unpaired `{` or `}` is literal text instead of a glob compile error.
|
|
30
|
+
|
|
31
|
+
#### Verification
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
# root holds q.md, {q}.md, a1.md and a2.md
|
|
35
|
+
rows = await DirSQL(root).query("SELECT path FROM './{q}.md'")
|
|
36
|
+
# expected: [{"path": "{q}.md"}]
|
|
37
|
+
rows = await DirSQL(root).query("SELECT path FROM './a{1..2}.md' ORDER BY path")
|
|
38
|
+
# expected: [{"path": "a1.md"}, {"path": "a2.md"}]
|
|
39
|
+
```
|
|
@@ -6,7 +6,7 @@ description = "Ephemeral SQL index over a local directory"
|
|
|
6
6
|
# version stays for the binding crates (publish = false). The literal
|
|
7
7
|
# intentionally lags the published version: putitoutthere rewrites it to
|
|
8
8
|
# the planned release version at build time and never commits it back.
|
|
9
|
-
version = "0.4.
|
|
9
|
+
version = "0.4.74"
|
|
10
10
|
edition.workspace = true
|
|
11
11
|
# Literal `license` (not `license.workspace = true`) because
|
|
12
12
|
# putitoutthere's preflight check reads `[package].license` directly
|
|
@@ -453,6 +453,14 @@ first line is `dirsql <version>`, then sections on usage, recipes and
|
|
|
453
453
|
troubleshooting. The guide is compiled into the binary, so it describes the
|
|
454
454
|
version that prints it. It takes no flags and exits `0`.
|
|
455
455
|
|
|
456
|
+
The dirsql agent skill tells an agent to run this command before it writes a
|
|
457
|
+
query. Install the skill into Claude Code, Codex, Cursor, Gemini CLI and
|
|
458
|
+
others with:
|
|
459
|
+
|
|
460
|
+
```sh
|
|
461
|
+
npx skills add thekevinscott/dirsql
|
|
462
|
+
```
|
|
463
|
+
|
|
456
464
|
## Plugins
|
|
457
465
|
|
|
458
466
|
A **plugin** is an ordinary Python package that ships a `dirsql.toml` config
|
|
@@ -54,6 +54,13 @@ depth, as in the shell. A trailing `/` is `*` appended, so `'./*/'` is
|
|
|
54
54
|
A path containing `*`, `?`, `[` or `{` is a glob and is used exactly as
|
|
55
55
|
written: `*` matches within a single directory, `**` crosses directories.
|
|
56
56
|
|
|
57
|
+
Braces expand as bash expands them. A group holding a comma is an
|
|
58
|
+
alternation: `'./docs/{a,b}.md'` is `docs/a.md` and `docs/b.md`, and groups
|
|
59
|
+
nest. A group holding a `..` range counts: `'./log/{1..3}.txt'`,
|
|
60
|
+
`'./{a..c}.md'`, `'./{01..10}.md'` (zero-padded) and `'./{1..10..2}.md'`
|
|
61
|
+
(every second value). Any other brace is literal text, so `'./br/{q}.md'`
|
|
62
|
+
names the file `br/{q}.md`, and `\{` is always a literal brace.
|
|
63
|
+
|
|
57
64
|
The scan starts at the last directory named outright before the first glob
|
|
58
65
|
component, and descends only as deep as the pattern can match --
|
|
59
66
|
`'./small/*.md'` lists `small/` and nothing else -- so a query over one
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
//! Bash brace expansion for path globs.
|
|
2
|
+
//!
|
|
3
|
+
//! A pattern expands the way bash expands a word before globbing it: a brace
|
|
4
|
+
//! group holding a top-level comma is an alternation, one holding a `..`
|
|
5
|
+
//! sequence is a range, and any other brace is literal text. The expansions
|
|
6
|
+
//! come back as globset patterns with every remaining brace escaped, so
|
|
7
|
+
//! globset never sees an alternation of its own.
|
|
8
|
+
|
|
9
|
+
/// Expand `pattern`'s brace groups as bash does, one globset pattern per word.
|
|
10
|
+
pub(crate) fn expand(pattern: &str) -> Vec<String> {
|
|
11
|
+
let chars: Vec<char> = pattern.chars().collect();
|
|
12
|
+
expand_word(&chars)
|
|
13
|
+
.iter()
|
|
14
|
+
.map(|word| escape_braces(word))
|
|
15
|
+
.collect()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[derive(Clone, Copy, PartialEq)]
|
|
19
|
+
enum Stop {
|
|
20
|
+
Close,
|
|
21
|
+
Comma,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// The characters of `text` from `from` on that no backslash escapes, with
|
|
25
|
+
/// their indices; the backslashes themselves are dropped too.
|
|
26
|
+
fn unescaped(text: &[char], from: usize) -> impl Iterator<Item = (usize, char)> + '_ {
|
|
27
|
+
let mut escaped = false;
|
|
28
|
+
text.iter()
|
|
29
|
+
.copied()
|
|
30
|
+
.enumerate()
|
|
31
|
+
.skip(from)
|
|
32
|
+
.filter(move |&(_, c)| {
|
|
33
|
+
let keep = !escaped && c != '\\';
|
|
34
|
+
escaped = !escaped && c == '\\';
|
|
35
|
+
keep
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Index of the first unescaped `stop` at nesting level zero, scanning from
|
|
40
|
+
/// `from`; `text.len()` when there is none. A `}` only closes once a comma or
|
|
41
|
+
/// a `..` has been seen, which is why `{q}` never closes.
|
|
42
|
+
fn gobble(text: &[char], from: usize, stop: Stop) -> usize {
|
|
43
|
+
let target = match stop {
|
|
44
|
+
Stop::Close => '}',
|
|
45
|
+
Stop::Comma => ',',
|
|
46
|
+
};
|
|
47
|
+
let mut level = 0usize;
|
|
48
|
+
let mut closable = stop != Stop::Close;
|
|
49
|
+
for (i, c) in unescaped(text, from) {
|
|
50
|
+
if c == target && level == 0 && closable {
|
|
51
|
+
return i;
|
|
52
|
+
}
|
|
53
|
+
if c == '{' {
|
|
54
|
+
level += 1;
|
|
55
|
+
} else if c == '}' && level > 0 {
|
|
56
|
+
level -= 1;
|
|
57
|
+
} else if level == 0
|
|
58
|
+
&& (c == ',' || (text[i..].starts_with(&['.', '.']) && text.get(i + 2) != Some(&'}')))
|
|
59
|
+
{
|
|
60
|
+
closable = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
text.len()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Bash never opens a group at a `{` that starts a word-like token and is
|
|
67
|
+
/// followed by whitespace or `}`, so `{}` stays intact for `find -exec`.
|
|
68
|
+
fn is_shell_brace(text: &[char], i: usize) -> bool {
|
|
69
|
+
let after_space = i == 0 || text[i - 1].is_ascii_whitespace();
|
|
70
|
+
let before_space_or_close = text
|
|
71
|
+
.get(i + 1)
|
|
72
|
+
.is_some_and(|c| c.is_ascii_whitespace() || *c == '}');
|
|
73
|
+
after_space && before_space_or_close
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn expand_word(text: &[char]) -> Vec<String> {
|
|
77
|
+
let group = unescaped(text, 0)
|
|
78
|
+
.filter(|&(i, c)| c == '{' && !is_shell_brace(text, i))
|
|
79
|
+
.find_map(|(open, _)| {
|
|
80
|
+
let close = gobble(text, open + 1, Stop::Close);
|
|
81
|
+
(close < text.len()).then_some((open, close))
|
|
82
|
+
});
|
|
83
|
+
let Some((open, close)) = group else {
|
|
84
|
+
return vec![text.iter().collect()];
|
|
85
|
+
};
|
|
86
|
+
let preamble: String = text[..open].iter().collect();
|
|
87
|
+
let amble = &text[open + 1..close];
|
|
88
|
+
let middle = if unescaped(amble, 0).any(|(_, c)| c == ',') {
|
|
89
|
+
expand_alternatives(amble)
|
|
90
|
+
} else {
|
|
91
|
+
let amble: String = amble.iter().collect();
|
|
92
|
+
sequence(&amble).unwrap_or_else(|| vec![format!("{{{amble}}}")])
|
|
93
|
+
};
|
|
94
|
+
let rest = expand_word(&text[close + 1..]);
|
|
95
|
+
middle
|
|
96
|
+
.iter()
|
|
97
|
+
.flat_map(|m| {
|
|
98
|
+
rest.iter()
|
|
99
|
+
.map(|r| format!("{preamble}{m}{r}"))
|
|
100
|
+
.collect::<Vec<_>>()
|
|
101
|
+
})
|
|
102
|
+
.collect()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn expand_alternatives(amble: &[char]) -> Vec<String> {
|
|
106
|
+
let end = gobble(amble, 0, Stop::Comma);
|
|
107
|
+
let mut words = expand_word(&amble[..end]);
|
|
108
|
+
if let Some(tail) = amble.get(end + 1..) {
|
|
109
|
+
words.extend(expand_alternatives(tail));
|
|
110
|
+
}
|
|
111
|
+
words
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// The `..` range bash expands `{lo..hi}` / `{lo..hi..step}` to, or `None`
|
|
115
|
+
/// when the text is not a range bash accepts.
|
|
116
|
+
fn sequence(amble: &str) -> Option<Vec<String>> {
|
|
117
|
+
let (lhs, rhs) = amble.split_once("..")?;
|
|
118
|
+
if lhs.is_empty() || rhs.is_empty() {
|
|
119
|
+
return None;
|
|
120
|
+
}
|
|
121
|
+
let digits = number_prefix_len(rhs);
|
|
122
|
+
let (end, rhs_len) = if let Ok(n) = rhs[..digits].parse() {
|
|
123
|
+
(Term::Int(n), digits)
|
|
124
|
+
} else {
|
|
125
|
+
let c = rhs.as_bytes()[0];
|
|
126
|
+
if !c.is_ascii_alphabetic() || !(rhs.len() == 1 || rhs.as_bytes()[1] == b'.') {
|
|
127
|
+
return None;
|
|
128
|
+
}
|
|
129
|
+
(Term::Char(c), 1)
|
|
130
|
+
};
|
|
131
|
+
let step = match &rhs[rhs_len..] {
|
|
132
|
+
"" => 1,
|
|
133
|
+
tail => tail
|
|
134
|
+
.strip_prefix("..")
|
|
135
|
+
.filter(|s| !s.is_empty())?
|
|
136
|
+
.parse()
|
|
137
|
+
.ok()?,
|
|
138
|
+
};
|
|
139
|
+
let start = if let Ok(n) = lhs.parse::<i64>() {
|
|
140
|
+
Term::Int(n)
|
|
141
|
+
} else if lhs.len() == 1 && lhs.as_bytes()[0].is_ascii_alphabetic() {
|
|
142
|
+
Term::Char(lhs.as_bytes()[0])
|
|
143
|
+
} else {
|
|
144
|
+
return None;
|
|
145
|
+
};
|
|
146
|
+
match (start, end) {
|
|
147
|
+
(Term::Int(lo), Term::Int(hi)) => {
|
|
148
|
+
let width = pad_width(lhs, &rhs[..rhs_len]);
|
|
149
|
+
Some(
|
|
150
|
+
steps(lo, hi, step)?
|
|
151
|
+
.map(|n| format!("{n:0width$}"))
|
|
152
|
+
.collect(),
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
(Term::Char(lo), Term::Char(hi)) => Some(
|
|
156
|
+
steps(i64::from(lo), i64::from(hi), step)?
|
|
157
|
+
.filter_map(|n| u8::try_from(n).ok())
|
|
158
|
+
.map(|b| char::from(b).to_string())
|
|
159
|
+
.collect(),
|
|
160
|
+
),
|
|
161
|
+
_ => None,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
enum Term {
|
|
166
|
+
Int(i64),
|
|
167
|
+
Char(u8),
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn number_prefix_len(s: &str) -> usize {
|
|
171
|
+
let sign = usize::from(matches!(s.as_bytes()[0], b'+' | b'-'));
|
|
172
|
+
sign + s[sign..].bytes().take_while(u8::is_ascii_digit).count()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Bash zero-pads every term to the widest end written with a leading zero.
|
|
176
|
+
fn pad_width(lhs: &str, rhs: &str) -> usize {
|
|
177
|
+
let padded =
|
|
178
|
+
|s: &str| (s.len() > 1 && s.starts_with('0')) || (s.len() > 2 && s.starts_with("-0"));
|
|
179
|
+
if padded(lhs) || padded(rhs) {
|
|
180
|
+
lhs.len().max(rhs.len())
|
|
181
|
+
} else {
|
|
182
|
+
0
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// `lo` to `hi` inclusive in steps of `|step|` (0 counts as 1), walking down
|
|
187
|
+
/// when `hi < lo`. `None` past the element count bash refuses to generate.
|
|
188
|
+
fn steps(lo: i64, hi: i64, step: i64) -> Option<impl Iterator<Item = i128>> {
|
|
189
|
+
let stride = i128::from(step).abs().max(1);
|
|
190
|
+
let distance = i128::from(hi) - i128::from(lo);
|
|
191
|
+
if distance.abs() / stride > i128::from(i32::MAX) - 3 {
|
|
192
|
+
return None;
|
|
193
|
+
}
|
|
194
|
+
let count = distance.abs() / stride + 1;
|
|
195
|
+
let signed = stride * distance.signum();
|
|
196
|
+
Some((0..count).map(move |k| i128::from(lo) + k * signed))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Escape the braces bash left as literal text, so globset matches them
|
|
200
|
+
/// rather than reading an alternation. A complete `[...]` class is copied as
|
|
201
|
+
/// written; an unclosed one ends escaping so globset reports the error.
|
|
202
|
+
fn escape_braces(word: &str) -> String {
|
|
203
|
+
let chars: Vec<char> = word.chars().collect();
|
|
204
|
+
let mut out = String::with_capacity(word.len());
|
|
205
|
+
let mut resume = 0;
|
|
206
|
+
for (i, &c) in chars.iter().enumerate() {
|
|
207
|
+
if i < resume {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
match (c, chars.get(i + 1)) {
|
|
211
|
+
('\\', Some(&next)) => {
|
|
212
|
+
match next {
|
|
213
|
+
'{' => out.push_str("[{]"),
|
|
214
|
+
'}' => out.push_str("[}]"),
|
|
215
|
+
',' => out.push(','),
|
|
216
|
+
_ => out.extend(['\\', next]),
|
|
217
|
+
}
|
|
218
|
+
resume = i + 2;
|
|
219
|
+
}
|
|
220
|
+
('[', _) => match class_end(&chars, i) {
|
|
221
|
+
Some(end) => {
|
|
222
|
+
out.extend(&chars[i..=end]);
|
|
223
|
+
resume = end + 1;
|
|
224
|
+
}
|
|
225
|
+
None => {
|
|
226
|
+
out.extend(&chars[i..]);
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
('{', _) => out.push_str("[{]"),
|
|
231
|
+
('}', _) => out.push_str("[}]"),
|
|
232
|
+
_ => out.push(c),
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
out
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// Index of the `]` closing the class globset opens at `open`, where a `]`
|
|
239
|
+
/// first in the class (after any `!` / `^`) is a member, not the close.
|
|
240
|
+
fn class_end(chars: &[char], open: usize) -> Option<usize> {
|
|
241
|
+
let mut i = open + 1;
|
|
242
|
+
if matches!(chars.get(i), Some('!' | '^')) {
|
|
243
|
+
i += 1;
|
|
244
|
+
}
|
|
245
|
+
if chars.get(i) == Some(&']') {
|
|
246
|
+
i += 1;
|
|
247
|
+
}
|
|
248
|
+
(i..chars.len()).find(|&j| chars[j] == ']')
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
#[cfg(test)]
|
|
252
|
+
mod tests {
|
|
253
|
+
use super::*;
|
|
254
|
+
|
|
255
|
+
fn words(pattern: &str) -> Vec<String> {
|
|
256
|
+
expand_word(&pattern.chars().collect::<Vec<_>>())
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[test]
|
|
260
|
+
fn a_group_without_a_comma_or_sequence_is_literal() {
|
|
261
|
+
for pattern in [
|
|
262
|
+
"x{q}y", "x{}y", "x{a,b", "xa,b}y", "x{}}y", "x{{}}y", "{},a}",
|
|
263
|
+
] {
|
|
264
|
+
assert_eq!(words(pattern), vec![pattern], "{pattern}");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#[test]
|
|
269
|
+
fn a_comma_group_alternates_in_order() {
|
|
270
|
+
assert_eq!(words("x{a,b}y"), vec!["xay", "xby"]);
|
|
271
|
+
assert_eq!(words("{a,b}{c,d}"), vec!["ac", "ad", "bc", "bd"]);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#[test]
|
|
275
|
+
fn empty_alternatives_are_kept() {
|
|
276
|
+
assert_eq!(words("x{,a}y"), vec!["xy", "xay"]);
|
|
277
|
+
assert_eq!(words("x{a,}y"), vec!["xay", "xy"]);
|
|
278
|
+
assert_eq!(words("x{,}y"), vec!["xy", "xy"]);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#[test]
|
|
282
|
+
fn nested_groups_expand_inside_out_as_bash_does() {
|
|
283
|
+
assert_eq!(words("x{a,{b,c}}y"), vec!["xay", "xby", "xcy"]);
|
|
284
|
+
assert_eq!(words("x{a{b,c}}y"), vec!["x{ab}y", "x{ac}y"]);
|
|
285
|
+
assert_eq!(words("x{{a,b}}y"), vec!["x{a}y", "x{b}y"]);
|
|
286
|
+
assert_eq!(words("x{a,{b}}y"), vec!["xay", "x{b}y"]);
|
|
287
|
+
assert_eq!(words("x{{},a}y"), vec!["x{}y", "xay"]);
|
|
288
|
+
assert_eq!(words("x{}a,b}y"), vec!["x}ay", "xby"]);
|
|
289
|
+
assert_eq!(words("x{..{a,b}}y"), vec!["x..ay", "x..by"]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#[test]
|
|
293
|
+
fn stray_braces_beside_a_group_are_literal() {
|
|
294
|
+
assert_eq!(words("x{{a,b}y"), vec!["x{ay", "x{by"]);
|
|
295
|
+
assert_eq!(words("x{a,b}}y"), vec!["xa}y", "xb}y"]);
|
|
296
|
+
assert_eq!(words("x{a,}}y"), vec!["xa}y", "x}y"]);
|
|
297
|
+
assert_eq!(words("xx{a..}y,b}"), vec!["xxa..}y", "xxb"]);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#[test]
|
|
301
|
+
fn an_escaped_brace_or_comma_does_not_count() {
|
|
302
|
+
assert_eq!(words(r"x\{a,b}y"), vec![r"x\{a,b}y"]);
|
|
303
|
+
assert_eq!(words(r"x{a\,b}y"), vec![r"x{a\,b}y"]);
|
|
304
|
+
assert_eq!(words(r"x{a\,b,c}y"), vec![r"xa\,by", "xcy"]);
|
|
305
|
+
assert_eq!(words(r"x{\}a,b}y"), vec![r"x\}ay", "xby"]);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
#[test]
|
|
309
|
+
fn a_brace_before_whitespace_or_a_close_after_whitespace_never_opens() {
|
|
310
|
+
assert_eq!(words("x {},a}y"), vec!["x {},a}y"]);
|
|
311
|
+
assert_eq!(words("{ a,b}"), vec!["{ a,b}"]);
|
|
312
|
+
assert_eq!(words("x{ a,b}"), vec!["x a", "xb"]);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
#[test]
|
|
316
|
+
fn integer_sequences_count_either_way() {
|
|
317
|
+
assert_eq!(words("x{1..3}y"), vec!["x1y", "x2y", "x3y"]);
|
|
318
|
+
assert_eq!(words("x{3..1}"), vec!["x3", "x2", "x1"]);
|
|
319
|
+
assert_eq!(words("{-2..1}"), vec!["-2", "-1", "0", "1"]);
|
|
320
|
+
assert_eq!(words("{+1..3}"), vec!["1", "2", "3"]);
|
|
321
|
+
assert_eq!(words("{1..-1}"), vec!["1", "0", "-1"]);
|
|
322
|
+
assert_eq!(words("{1..1}"), vec!["1"]);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
#[test]
|
|
326
|
+
fn a_step_is_taken_by_magnitude() {
|
|
327
|
+
assert_eq!(words("{1..10..3}"), vec!["1", "4", "7", "10"]);
|
|
328
|
+
assert_eq!(words("{10..1..3}"), vec!["10", "7", "4", "1"]);
|
|
329
|
+
assert_eq!(words("{1..3..-1}"), vec!["1", "2", "3"]);
|
|
330
|
+
assert_eq!(words("{1..3..0}"), vec!["1", "2", "3"]);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#[test]
|
|
334
|
+
fn a_leading_zero_pads_every_term() {
|
|
335
|
+
assert_eq!(words("{08..10}"), vec!["08", "09", "10"]);
|
|
336
|
+
assert_eq!(words("{1..03}"), vec!["01", "02", "03"]);
|
|
337
|
+
assert_eq!(words("{001..2}"), vec!["001", "002"]);
|
|
338
|
+
assert_eq!(words("{-01..1}"), vec!["-01", "000", "001"]);
|
|
339
|
+
assert_eq!(words("{0..10..5}"), vec!["0", "5", "10"]);
|
|
340
|
+
assert_eq!(words("{-0..10..5}"), vec!["0", "5", "10"]);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#[test]
|
|
344
|
+
fn letter_sequences_walk_the_alphabet() {
|
|
345
|
+
assert_eq!(words("{a..c}"), vec!["a", "b", "c"]);
|
|
346
|
+
assert_eq!(words("{e..a..2}"), vec!["e", "c", "a"]);
|
|
347
|
+
assert_eq!(words("{Y..[}"), vec!["{Y..[}"]);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#[test]
|
|
351
|
+
fn a_malformed_sequence_is_literal() {
|
|
352
|
+
for pattern in [
|
|
353
|
+
"{1..a}",
|
|
354
|
+
"{ab..c}",
|
|
355
|
+
"{1...3}",
|
|
356
|
+
"{a..}",
|
|
357
|
+
"{..a}",
|
|
358
|
+
"{1..3..}",
|
|
359
|
+
"{ab..cd}",
|
|
360
|
+
"{1..3...}",
|
|
361
|
+
"{1..3.}",
|
|
362
|
+
"{1..3..x}",
|
|
363
|
+
"{é..f}",
|
|
364
|
+
"{9223372036854775807..9223372036854775808}",
|
|
365
|
+
] {
|
|
366
|
+
assert_eq!(words(pattern), vec![pattern], "{pattern}");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
#[test]
|
|
371
|
+
fn a_malformed_sequence_leaves_later_groups_expanding() {
|
|
372
|
+
assert_eq!(words("x{1..z}{a,b}"), vec!["x{1..z}a", "x{1..z}b"]);
|
|
373
|
+
assert_eq!(words("x{q}{1..2}"), vec!["x{q}1", "x{q}2"]);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#[test]
|
|
377
|
+
fn a_comma_beside_dots_makes_an_alternation_not_a_range() {
|
|
378
|
+
assert_eq!(words("{a..c,d}"), vec!["a..c", "d"]);
|
|
379
|
+
assert_eq!(words("{1..3,}"), vec!["1..3", ""]);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#[test]
|
|
383
|
+
fn a_range_bash_refuses_to_generate_stays_literal() {
|
|
384
|
+
assert!(steps(0, i64::from(i32::MAX), 1).is_none());
|
|
385
|
+
assert!(steps(0, i64::from(i32::MAX) - 2, 1).is_none());
|
|
386
|
+
let widest = steps(0, i64::from(i32::MAX) - 3, 1).unwrap();
|
|
387
|
+
assert_eq!(widest.size_hint().0, i32::MAX as usize - 2);
|
|
388
|
+
assert!(steps(0, i64::from(i32::MAX), 2).is_some());
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
#[test]
|
|
392
|
+
fn a_range_missing_either_end_is_not_a_sequence() {
|
|
393
|
+
assert!(sequence("a..").is_none());
|
|
394
|
+
assert!(sequence("..a").is_none());
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
#[test]
|
|
398
|
+
fn literal_braces_are_escaped_for_globset() {
|
|
399
|
+
assert_eq!(escape_braces("x{q}y"), "x[{]q[}]y");
|
|
400
|
+
assert_eq!(escape_braces(r"x\{a,b\}"), "x[{]a,b[}]");
|
|
401
|
+
assert_eq!(escape_braces(r"a\,b"), "a,b");
|
|
402
|
+
assert_eq!(escape_braces(r"a\*b\"), r"a\*b\");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
#[test]
|
|
406
|
+
fn a_class_is_copied_as_written() {
|
|
407
|
+
assert_eq!(escape_braces("[{}]x"), "[{}]x");
|
|
408
|
+
assert_eq!(escape_braces("[]{]{"), "[]{][{]");
|
|
409
|
+
assert_eq!(escape_braces("[!]{]{"), "[!]{][{]");
|
|
410
|
+
assert_eq!(escape_braces("[^}]}"), "[^}][}]");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
#[test]
|
|
414
|
+
fn an_unclosed_class_ends_escaping() {
|
|
415
|
+
assert_eq!(escape_braces("x[{q}"), "x[{q}");
|
|
416
|
+
assert_eq!(escape_braces("x[]{"), "x[]{");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
#[test]
|
|
420
|
+
fn expand_escapes_every_word() {
|
|
421
|
+
assert_eq!(expand("{a,{b}}.md"), vec!["a.md", "[{]b[}].md"]);
|
|
422
|
+
assert_eq!(expand("{q}"), vec!["[{]q[}]"]);
|
|
423
|
+
assert_eq!(expand("plain/*.md"), vec!["plain/*.md"]);
|
|
424
|
+
}
|
|
425
|
+
}
|