dirsql 0.4.58__tar.gz → 0.4.60__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.58 → dirsql-0.4.60}/Cargo.lock +2 -2
- {dirsql-0.4.58 → dirsql-0.4.60}/PKG-INFO +1 -1
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/Cargo.toml +1 -1
- dirsql-0.4.60/packages/python/e2e-attestations/claude-1184-win-rel-paths.json +7 -0
- dirsql-0.4.60/packages/python/e2e-attestations/claude-1186-win-test-harness.json +7 -0
- dirsql-0.4.60/packages/python/e2e-attestations/claude-1207-win-argv-quote.json +7 -0
- dirsql-0.4.60/packages/python/e2e-attestations/claude-1208-win-nonverbatim-paths.json +7 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/Cargo.toml +1 -1
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/command.rs +254 -2
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/db.rs +1 -1
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/extension_resolution.rs +5 -1
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/lib.rs +37 -27
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/parsed_vtab.rs +12 -9
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/scanner.rs +36 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/vtab.rs +14 -4
- {dirsql-0.4.58 → dirsql-0.4.60}/Cargo.toml +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/README.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/__init__.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/_async.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/plan_entry_path.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/py.typed +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/read_config.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/resolve_configs_extension_specs.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/dirsql/resolve_package.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/AGENTS.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/explanation.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/getting-started.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/embed.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/persist.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/index.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/package.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/plugins.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/reference/cli.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/reference/columns.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/reference/config.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/reference/hooks.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/README.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-17-extension-resolution-via-core.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-18-export-scan-failure.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-18-flatten-row-event-in-core.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-18-programmatic-extension-plan.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/2026-09-24-query-rows-in-projection-order.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/conftest.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1081-indicatif-progress.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1119-ext-plan.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1120-py-ext.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1122-ext-cleanup.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1130-codec-crates.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1131-flatten-row-event.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1139-sse-flatten.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1140-scan-failure-export.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1148-clippy-all-targets.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1164-sigint-fatal.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1170-projection-order.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1172-on-file-path.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1173-py-projection-order.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-1185-win-abs-table-names.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/2026-09-24-query-rows-in-projection-order.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/testing-conventions.toml +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/tests/e2e/speed_of_light.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/README.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/functions.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/launcher.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/parsed_cache.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/progress.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/row_event_flat.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/vtab_scaffold.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.58 → dirsql-0.4.60}/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.60"
|
|
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.60"
|
|
605
605
|
dependencies = [
|
|
606
606
|
"dirsql",
|
|
607
607
|
"pyo3",
|
|
@@ -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.60"
|
|
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
|
|
@@ -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.60"
|
|
10
10
|
edition.workspace = true
|
|
11
11
|
# Literal `license` (not `license.workspace = true`) because
|
|
12
12
|
# putitoutthere's preflight check reads `[package].license` directly
|
|
@@ -49,6 +49,63 @@ impl Placeholder {
|
|
|
49
49
|
value: value.into(),
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/// A placeholder whose value is a filesystem path, handed to the child in
|
|
54
|
+
/// its plain (non-verbatim) form; see [`non_verbatim`].
|
|
55
|
+
pub fn path(name: impl Into<String>, path: &str) -> Self {
|
|
56
|
+
Self::new(name, non_verbatim(path))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// The plain form of a Windows verbatim path: `\\?\C:\x` becomes `C:\x` and
|
|
61
|
+
/// `\\?\UNC\server\share\x` becomes `\\server\share\x`. Anything else, and any
|
|
62
|
+
/// verbatim path whose plain spelling would name a different file, is returned
|
|
63
|
+
/// unchanged.
|
|
64
|
+
pub(crate) fn non_verbatim(path: &str) -> String {
|
|
65
|
+
plain_form(path).unwrap_or_else(|| path.to_string())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn plain_form(path: &str) -> Option<String> {
|
|
69
|
+
let rest = path.strip_prefix(r"\\?\")?;
|
|
70
|
+
let (plain, tail) = if let Some(unc) = rest.strip_prefix(r"UNC\") {
|
|
71
|
+
if unc.split('\\').count() < 2 {
|
|
72
|
+
return None;
|
|
73
|
+
}
|
|
74
|
+
(format!(r"\\{unc}"), unc)
|
|
75
|
+
} else {
|
|
76
|
+
let drive = rest.as_bytes();
|
|
77
|
+
let is_drive = drive.len() >= 3
|
|
78
|
+
&& drive[0].is_ascii_alphabetic()
|
|
79
|
+
&& drive[1] == b':'
|
|
80
|
+
&& drive[2] == b'\\';
|
|
81
|
+
if !is_drive {
|
|
82
|
+
return None;
|
|
83
|
+
}
|
|
84
|
+
(rest.to_string(), &rest[3..])
|
|
85
|
+
};
|
|
86
|
+
// Win32 rejects or rewrites these, so the plain spelling would not name
|
|
87
|
+
// the same file; MAX_PATH counts UTF-16 units including the terminator.
|
|
88
|
+
let same_file = (tail.is_empty() || tail.split('\\').all(is_plain_component))
|
|
89
|
+
&& plain.encode_utf16().count() < 260;
|
|
90
|
+
same_file.then_some(plain)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn is_plain_component(name: &str) -> bool {
|
|
94
|
+
let invalid_char = |c: char| c < ' ' || r#"/<>:"|?*"#.contains(c);
|
|
95
|
+
let stem = name
|
|
96
|
+
.split_once('.')
|
|
97
|
+
.map_or(name, |(stem, _)| stem)
|
|
98
|
+
.trim_end_matches(' ');
|
|
99
|
+
let reserved = matches!(
|
|
100
|
+
stem.to_ascii_uppercase().as_bytes(),
|
|
101
|
+
b"CON"
|
|
102
|
+
| b"PRN"
|
|
103
|
+
| b"AUX"
|
|
104
|
+
| b"NUL"
|
|
105
|
+
| [b'C', b'O', b'M', b'1'..=b'9']
|
|
106
|
+
| [b'L', b'P', b'T', b'1'..=b'9']
|
|
107
|
+
);
|
|
108
|
+
!name.is_empty() && !name.ends_with(['.', ' ']) && !name.contains(invalid_char) && !reserved
|
|
52
109
|
}
|
|
53
110
|
|
|
54
111
|
/// A successful command run.
|
|
@@ -119,8 +176,8 @@ pub fn run_command(
|
|
|
119
176
|
let argv = build_argv(command, placeholders)?;
|
|
120
177
|
// `build_argv` guarantees a non-empty argv.
|
|
121
178
|
let mut cmd = Command::new(&argv[0]);
|
|
122
|
-
|
|
123
|
-
|
|
179
|
+
push_args(&mut cmd, &argv[1..]);
|
|
180
|
+
cmd.current_dir(cwd)
|
|
124
181
|
.stdout(Stdio::piped())
|
|
125
182
|
.stderr(Stdio::piped())
|
|
126
183
|
.stdin(if stdin_payload.is_some() {
|
|
@@ -193,6 +250,20 @@ pub fn run_command(
|
|
|
193
250
|
}
|
|
194
251
|
}
|
|
195
252
|
|
|
253
|
+
// One fn with cfg blocks rather than two cfg'd fns: cargo-mutants mutates
|
|
254
|
+
// the uncompiled twin too, and no test on a Linux gate can kill that mutant.
|
|
255
|
+
fn push_args(cmd: &mut Command, args: &[String]) {
|
|
256
|
+
#[cfg(windows)]
|
|
257
|
+
{
|
|
258
|
+
use std::os::windows::process::CommandExt;
|
|
259
|
+
for arg in args {
|
|
260
|
+
cmd.raw_arg(quote_windows_arg(arg));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
#[cfg(not(windows))]
|
|
264
|
+
cmd.args(args);
|
|
265
|
+
}
|
|
266
|
+
|
|
196
267
|
fn spawn_error(program: &str, source: std::io::Error, cwd: &Path) -> CommandError {
|
|
197
268
|
// A missing cwd fails the child's chdir with `NotFound` too.
|
|
198
269
|
if !searched_path(program, source.kind()) || !cwd.is_dir() {
|
|
@@ -244,6 +315,35 @@ pub(crate) fn build_argv(
|
|
|
244
315
|
Ok(argv)
|
|
245
316
|
}
|
|
246
317
|
|
|
318
|
+
/// Quote `arg` for a Windows command line by the MS C-runtime rules, always
|
|
319
|
+
/// wrapped in double quotes: an MSYS/Cygwin child globs, brace-expands and
|
|
320
|
+
/// unescapes any unquoted word, while an MSVCRT child parses the quoted form
|
|
321
|
+
/// back to `arg` exactly.
|
|
322
|
+
#[cfg_attr(not(windows), allow(dead_code))]
|
|
323
|
+
pub(crate) fn quote_windows_arg(arg: &str) -> String {
|
|
324
|
+
let mut out = String::with_capacity(arg.len() + 2);
|
|
325
|
+
out.push('"');
|
|
326
|
+
let mut backslashes = 0;
|
|
327
|
+
for ch in arg.chars() {
|
|
328
|
+
match ch {
|
|
329
|
+
'\\' => backslashes += 1,
|
|
330
|
+
'"' => {
|
|
331
|
+
out.extend(std::iter::repeat_n('\\', backslashes * 2 + 1));
|
|
332
|
+
out.push('"');
|
|
333
|
+
backslashes = 0;
|
|
334
|
+
}
|
|
335
|
+
_ => {
|
|
336
|
+
out.extend(std::iter::repeat_n('\\', backslashes));
|
|
337
|
+
out.push(ch);
|
|
338
|
+
backslashes = 0;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
out.extend(std::iter::repeat_n('\\', backslashes * 2));
|
|
343
|
+
out.push('"');
|
|
344
|
+
out
|
|
345
|
+
}
|
|
346
|
+
|
|
247
347
|
/// Replace every `{name}` in `token` with its placeholder value in a single
|
|
248
348
|
/// left-to-right pass. Injected values are never re-scanned, so an untrusted
|
|
249
349
|
/// value containing `{…}` is inert. Unknown `{…}` sequences are left literal.
|
|
@@ -315,6 +415,117 @@ mod tests {
|
|
|
315
415
|
build_argv(command, placeholders).expect("valid command")
|
|
316
416
|
}
|
|
317
417
|
|
|
418
|
+
fn unchanged(path: &str) {
|
|
419
|
+
assert_eq!(non_verbatim(path), path);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
#[test]
|
|
423
|
+
fn non_verbatim_strips_a_verbatim_drive_path() {
|
|
424
|
+
assert_eq!(
|
|
425
|
+
non_verbatim(r"\\?\C:\Users\runner\in_cwd.txt"),
|
|
426
|
+
r"C:\Users\runner\in_cwd.txt"
|
|
427
|
+
);
|
|
428
|
+
assert_eq!(non_verbatim(r"\\?\d:\x"), r"d:\x");
|
|
429
|
+
assert_eq!(non_verbatim(r"\\?\C:\my file.txt"), r"C:\my file.txt");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
#[test]
|
|
433
|
+
fn non_verbatim_strips_a_verbatim_drive_root() {
|
|
434
|
+
assert_eq!(non_verbatim(r"\\?\C:\"), r"C:\");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#[test]
|
|
438
|
+
fn non_verbatim_rewrites_a_verbatim_unc_path() {
|
|
439
|
+
assert_eq!(
|
|
440
|
+
non_verbatim(r"\\?\UNC\server\share\dir\f.txt"),
|
|
441
|
+
r"\\server\share\dir\f.txt"
|
|
442
|
+
);
|
|
443
|
+
assert_eq!(non_verbatim(r"\\?\UNC\server\share"), r"\\server\share");
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
#[test]
|
|
447
|
+
fn non_verbatim_leaves_plain_paths_unchanged() {
|
|
448
|
+
unchanged(r"C:\Users\runner\f.txt");
|
|
449
|
+
unchanged(r"\\server\share\f.txt");
|
|
450
|
+
unchanged("/tmp/root/f.txt");
|
|
451
|
+
unchanged("relative/f.txt");
|
|
452
|
+
unchanged("");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
#[test]
|
|
456
|
+
fn non_verbatim_leaves_verbatim_forms_without_a_drive_or_unc_unchanged() {
|
|
457
|
+
unchanged(r"\\?\GLOBALROOT\Device\HarddiskVolume1\f.txt");
|
|
458
|
+
unchanged(r"\\?\Volume{0b1c2d3e-0000-0000-0000-100000000000}\f.txt");
|
|
459
|
+
unchanged(r"\\?\C:");
|
|
460
|
+
unchanged(r"\\?\C:x");
|
|
461
|
+
unchanged(r"\\?\1:\x");
|
|
462
|
+
unchanged(r"\\?\");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
#[test]
|
|
466
|
+
fn non_verbatim_leaves_an_incomplete_unc_unchanged() {
|
|
467
|
+
unchanged(r"\\?\UNC\");
|
|
468
|
+
unchanged(r"\\?\UNC\server");
|
|
469
|
+
unchanged(r"\\?\UNC\server\");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
#[test]
|
|
473
|
+
fn non_verbatim_leaves_components_win32_would_reinterpret_unchanged() {
|
|
474
|
+
unchanged(r"\\?\C:\a\..\b");
|
|
475
|
+
unchanged(r"\\?\C:\a\.\b");
|
|
476
|
+
unchanged(r"\\?\C:\a\\b");
|
|
477
|
+
unchanged(r"\\?\C:\a\");
|
|
478
|
+
unchanged(r"\\?\C:\trailing.");
|
|
479
|
+
unchanged(r"\\?\C:\trailing ");
|
|
480
|
+
unchanged(r"\\?\C:\a/b");
|
|
481
|
+
unchanged(r"\\?\C:\a?b");
|
|
482
|
+
unchanged(r"\\?\C:\a*b");
|
|
483
|
+
unchanged(r"\\?\C:\a:b");
|
|
484
|
+
unchanged(r#"\\?\C:\a"b"#);
|
|
485
|
+
unchanged(r"\\?\C:\a<b");
|
|
486
|
+
unchanged(r"\\?\C:\a>b");
|
|
487
|
+
unchanged(r"\\?\C:\a|b");
|
|
488
|
+
unchanged("\\\\?\\C:\\a\u{1}b");
|
|
489
|
+
unchanged(r"\\?\UNC\server\share\..\x");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
#[test]
|
|
493
|
+
fn non_verbatim_leaves_reserved_device_names_unchanged() {
|
|
494
|
+
for name in [
|
|
495
|
+
"CON", "con", "PRN", "AUX", "NUL", "nul.txt", "NUL .txt", "COM1", "COM9", "LPT1",
|
|
496
|
+
"lpt9.log",
|
|
497
|
+
] {
|
|
498
|
+
unchanged(&format!(r"\\?\C:\dir\{name}"));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#[test]
|
|
503
|
+
fn non_verbatim_strips_names_that_only_resemble_device_names() {
|
|
504
|
+
for name in [
|
|
505
|
+
"CONSOLE", "NULL.txt", "COM0", "COM10", "LPT", "xCON", "AUXa",
|
|
506
|
+
] {
|
|
507
|
+
assert_eq!(
|
|
508
|
+
non_verbatim(&format!(r"\\?\C:\dir\{name}")),
|
|
509
|
+
format!(r"C:\dir\{name}")
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
#[test]
|
|
515
|
+
fn non_verbatim_strips_up_to_max_path_and_no_further() {
|
|
516
|
+
let fits = format!(r"C:\{}", "a".repeat(256));
|
|
517
|
+
assert_eq!(fits.encode_utf16().count(), 259);
|
|
518
|
+
assert_eq!(non_verbatim(&format!(r"\\?\{fits}")), fits);
|
|
519
|
+
unchanged(&format!(r"\\?\C:\{}", "a".repeat(257)));
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
#[test]
|
|
523
|
+
fn path_placeholder_carries_the_non_verbatim_value() {
|
|
524
|
+
let placeholder = Placeholder::path("path", r"\\?\C:\a\f.txt");
|
|
525
|
+
assert_eq!(placeholder.name, "path");
|
|
526
|
+
assert_eq!(placeholder.value, r"C:\a\f.txt");
|
|
527
|
+
}
|
|
528
|
+
|
|
318
529
|
#[test]
|
|
319
530
|
fn splits_on_whitespace_without_placeholders() {
|
|
320
531
|
assert_eq!(argv("echo hello world", &[]), ["echo", "hello", "world"]);
|
|
@@ -402,6 +613,47 @@ mod tests {
|
|
|
402
613
|
assert_eq!(argv("run", &[Placeholder::new("path", "/tmp/x")]), ["run"]);
|
|
403
614
|
}
|
|
404
615
|
|
|
616
|
+
#[test]
|
|
617
|
+
fn windows_quoting_wraps_a_plain_arg_in_quotes() {
|
|
618
|
+
assert_eq!(quote_windows_arg("hello"), r#""hello""#);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
#[test]
|
|
622
|
+
fn windows_quoting_keeps_an_empty_arg_as_an_empty_quoted_word() {
|
|
623
|
+
assert_eq!(quote_windows_arg(""), r#""""#);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
#[test]
|
|
627
|
+
fn windows_quoting_quotes_glob_and_brace_characters() {
|
|
628
|
+
assert_eq!(quote_windows_arg("[{}]"), r#""[{}]""#);
|
|
629
|
+
assert_eq!(quote_windows_arg("a?b*c"), r#""a?b*c""#);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
#[test]
|
|
633
|
+
fn windows_quoting_leaves_backslashes_not_before_a_quote_alone() {
|
|
634
|
+
assert_eq!(
|
|
635
|
+
quote_windows_arg(r"\\?\C:\Users\x.txt"),
|
|
636
|
+
r#""\\?\C:\Users\x.txt""#
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
#[test]
|
|
641
|
+
fn windows_quoting_doubles_trailing_backslashes_before_the_closing_quote() {
|
|
642
|
+
assert_eq!(quote_windows_arg(r"C:\dir\"), r#""C:\dir\\""#);
|
|
643
|
+
assert_eq!(quote_windows_arg(r"a\\"), r#""a\\\\""#);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
#[test]
|
|
647
|
+
fn windows_quoting_escapes_embedded_quotes() {
|
|
648
|
+
assert_eq!(quote_windows_arg(r#"[{"n":1}]"#), r#""[{\"n\":1}]""#);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
#[test]
|
|
652
|
+
fn windows_quoting_doubles_backslashes_before_an_embedded_quote() {
|
|
653
|
+
assert_eq!(quote_windows_arg(r#"a\"b"#), r#""a\\\"b""#);
|
|
654
|
+
assert_eq!(quote_windows_arg(r#"a\\"b"#), r#""a\\\\\"b""#);
|
|
655
|
+
}
|
|
656
|
+
|
|
405
657
|
#[test]
|
|
406
658
|
fn extract_payload_takes_the_last_non_empty_line() {
|
|
407
659
|
assert_eq!(
|
|
@@ -655,7 +655,7 @@ impl Db {
|
|
|
655
655
|
/// Every distinct `(table_name, file_path)` whose file sits beneath the
|
|
656
656
|
/// directory `dir`, ordered by table then path.
|
|
657
657
|
pub fn files_under(&self, dir: &str) -> Result<Vec<(String, String)>> {
|
|
658
|
-
let prefix = format!("{dir}
|
|
658
|
+
let prefix = format!("{dir}/");
|
|
659
659
|
let mut stmt = self.conn.prepare(
|
|
660
660
|
"SELECT DISTINCT table_name, file_path FROM _dirsql_internal_rows \
|
|
661
661
|
WHERE substr(file_path, 1, length(?1)) = ?1 \
|
|
@@ -512,7 +512,11 @@ mod tests {
|
|
|
512
512
|
);
|
|
513
513
|
assert_eq!(
|
|
514
514
|
err.to_string(),
|
|
515
|
-
|
|
515
|
+
format!(
|
|
516
|
+
"multiple loadable extension files found in package 'vec': {}, {}; disambiguate with a literal path",
|
|
517
|
+
dir.join("a.so").display(),
|
|
518
|
+
dir.join("z.so").display()
|
|
519
|
+
)
|
|
516
520
|
);
|
|
517
521
|
}
|
|
518
522
|
}
|
|
@@ -571,7 +571,7 @@ impl DirSQL {
|
|
|
571
571
|
// unspecified. An `on_file` failure produces an error event for that
|
|
572
572
|
// table only; the other matching tables still process the event.
|
|
573
573
|
let matches = self.inner.matcher.match_all(&rel_path_buf);
|
|
574
|
-
let rel_path = rel_path_buf
|
|
574
|
+
let rel_path = scanner::to_slash(&rel_path_buf);
|
|
575
575
|
|
|
576
576
|
let mut events = Vec::new();
|
|
577
577
|
for m in matches {
|
|
@@ -625,11 +625,7 @@ impl DirSQL {
|
|
|
625
625
|
};
|
|
626
626
|
let mut events = Vec::new();
|
|
627
627
|
for (path, table) in self.inner.fs.scan_subtree(base, dir, &self.inner.matcher) {
|
|
628
|
-
let rel_path = path
|
|
629
|
-
.strip_prefix(base)
|
|
630
|
-
.unwrap_or(&path)
|
|
631
|
-
.to_string_lossy()
|
|
632
|
-
.to_string();
|
|
628
|
+
let rel_path = scanner::to_slash(path.strip_prefix(base).unwrap_or(&path));
|
|
633
629
|
events.extend(self.handle_upsert(&table, &path, &rel_path));
|
|
634
630
|
}
|
|
635
631
|
events
|
|
@@ -1836,10 +1832,7 @@ fn run_channel_loop(db: DirSQL, tx: UnboundedSender<RowEvent>) {
|
|
|
1836
1832
|
}
|
|
1837
1833
|
|
|
1838
1834
|
fn relative_path(root: &Path, path: &Path) -> String {
|
|
1839
|
-
path.strip_prefix(root)
|
|
1840
|
-
.unwrap_or(path)
|
|
1841
|
-
.to_string_lossy()
|
|
1842
|
-
.to_string()
|
|
1835
|
+
scanner::to_slash(path.strip_prefix(root).unwrap_or(path))
|
|
1843
1836
|
}
|
|
1844
1837
|
|
|
1845
1838
|
/// Build [`Table`] objects from a parsed config.
|
|
@@ -1945,8 +1938,8 @@ fn run_on_file(
|
|
|
1945
1938
|
root: &Path,
|
|
1946
1939
|
) -> std::result::Result<Vec<Row>, BoxError> {
|
|
1947
1940
|
let placeholders = [
|
|
1948
|
-
Placeholder::
|
|
1949
|
-
Placeholder::
|
|
1941
|
+
Placeholder::path("path", abs_path),
|
|
1942
|
+
Placeholder::path("root", &root.to_string_lossy()),
|
|
1950
1943
|
];
|
|
1951
1944
|
|
|
1952
1945
|
// Errors travel to the caller rather than being logged and flattened to an
|
|
@@ -2943,7 +2936,12 @@ mod internal_tests {
|
|
|
2943
2936
|
/// so `notify` never sees `.`.
|
|
2944
2937
|
#[test]
|
|
2945
2938
|
fn relative_root_canonicalizes_watch_root_only() {
|
|
2946
|
-
let
|
|
2939
|
+
let canonical = if cfg!(windows) {
|
|
2940
|
+
r"C:\ws\canonical"
|
|
2941
|
+
} else {
|
|
2942
|
+
"/ws/canonical"
|
|
2943
|
+
};
|
|
2944
|
+
let fake = FakeFs::default().with_canonical_root(".", canonical);
|
|
2947
2945
|
let db = DirSQL::with_ignore_and_fs(
|
|
2948
2946
|
".",
|
|
2949
2947
|
vec![Table::new(
|
|
@@ -2963,7 +2961,7 @@ mod internal_tests {
|
|
|
2963
2961
|
"watch_root must be absolute, got {:?}",
|
|
2964
2962
|
db.inner.watch_root
|
|
2965
2963
|
);
|
|
2966
|
-
assert_eq!(db.inner.watch_root, PathBuf::from(
|
|
2964
|
+
assert_eq!(db.inner.watch_root, PathBuf::from(canonical));
|
|
2967
2965
|
}
|
|
2968
2966
|
|
|
2969
2967
|
/// With an absolute root, `process_file_event` strips the `watch_root`
|
|
@@ -4346,15 +4344,13 @@ mod internal_tests {
|
|
|
4346
4344
|
}
|
|
4347
4345
|
|
|
4348
4346
|
/// `{abspath}` is not in the substitution table: it is left literal like any
|
|
4349
|
-
/// unknown `{…}`, so `printf` receives the string `{abspath}` verbatim.
|
|
4350
|
-
/// template references `{path}` so that arg is the real path (and no path is
|
|
4351
|
-
/// appended), isolating the `{abspath}` behavior in the `q` column.
|
|
4347
|
+
/// unknown `{…}`, so `printf` receives the string `{abspath}` verbatim.
|
|
4352
4348
|
#[test]
|
|
4353
4349
|
fn run_on_file_does_not_substitute_abspath() {
|
|
4354
4350
|
let dir = TempDir::new().unwrap();
|
|
4355
4351
|
let abs = dir.path().join("f.txt");
|
|
4356
4352
|
let rows = run_on_file(
|
|
4357
|
-
r#"printf '[{"
|
|
4353
|
+
r#"printf '[{"q":"%s"}]' {abspath}"#,
|
|
4358
4354
|
&abs.to_string_lossy(),
|
|
4359
4355
|
dir.path(),
|
|
4360
4356
|
dir.path(),
|
|
@@ -4365,25 +4361,39 @@ mod internal_tests {
|
|
|
4365
4361
|
}
|
|
4366
4362
|
|
|
4367
4363
|
/// `{path}` interpolates the matched file's **absolute** path (not a
|
|
4368
|
-
/// root-relative one)
|
|
4369
|
-
///
|
|
4370
|
-
/// the
|
|
4371
|
-
/// would have shortened to a bare relative path).
|
|
4364
|
+
/// root-relative one), byte-for-byte, even when the file sits directly
|
|
4365
|
+
/// under `root`. The command writes its argument to a side file rather than
|
|
4366
|
+
/// into the JSON payload, where a Windows path's `\` would need escaping.
|
|
4372
4367
|
#[test]
|
|
4373
4368
|
fn run_on_file_passes_absolute_path_for_path_placeholder() {
|
|
4374
4369
|
let dir = TempDir::new().unwrap();
|
|
4375
4370
|
let abs = dir.path().join("f.txt");
|
|
4376
|
-
|
|
4377
|
-
r#"sh -c
|
|
4371
|
+
run_on_file(
|
|
4372
|
+
r#"sh -c 'printf %s "$1" > seen; echo "[]"' sh {path}"#,
|
|
4378
4373
|
&abs.to_string_lossy(),
|
|
4379
4374
|
dir.path(),
|
|
4380
4375
|
dir.path(),
|
|
4381
4376
|
)
|
|
4382
4377
|
.expect("a well-formed payload parses");
|
|
4383
|
-
assert_eq!(rows.len(), 1);
|
|
4384
4378
|
assert_eq!(
|
|
4385
|
-
|
|
4386
|
-
|
|
4379
|
+
std::fs::read_to_string(dir.path().join("seen")).unwrap(),
|
|
4380
|
+
abs.to_string_lossy()
|
|
4381
|
+
);
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
#[test]
|
|
4385
|
+
fn run_on_file_hands_the_hook_non_verbatim_path_and_root() {
|
|
4386
|
+
let dir = TempDir::new().unwrap();
|
|
4387
|
+
run_on_file(
|
|
4388
|
+
r#"sh -c 'printf "%s|%s" "$1" "$2" > seen; echo "[]"' sh {path} {root}"#,
|
|
4389
|
+
r"\\?\C:\r\f.txt",
|
|
4390
|
+
dir.path(),
|
|
4391
|
+
Path::new(r"\\?\D:\r"),
|
|
4392
|
+
)
|
|
4393
|
+
.expect("a well-formed payload parses");
|
|
4394
|
+
assert_eq!(
|
|
4395
|
+
std::fs::read_to_string(dir.path().join("seen")).unwrap(),
|
|
4396
|
+
r"C:\r\f.txt|D:\r"
|
|
4387
4397
|
);
|
|
4388
4398
|
}
|
|
4389
4399
|
|
|
@@ -36,7 +36,7 @@ use crate::matcher::TableMatcher;
|
|
|
36
36
|
use crate::parsed_cache::{self, CachedParse, Entry, RowCache, SqliteRowCache};
|
|
37
37
|
use crate::path_table;
|
|
38
38
|
use crate::persist::{FileStat, hash_file, now_ns};
|
|
39
|
-
use crate::scanner::scan_glob;
|
|
39
|
+
use crate::scanner::{scan_glob, to_slash};
|
|
40
40
|
use crate::vtab_scaffold::{self, TableSource};
|
|
41
41
|
|
|
42
42
|
/// SQL module name a parsed path-table is created with.
|
|
@@ -161,7 +161,7 @@ fn collect_rows_cached(
|
|
|
161
161
|
|
|
162
162
|
let mut seen: Vec<(String, Parsed)> = Vec::with_capacity(rel_paths.len());
|
|
163
163
|
for rel_path in rel_paths {
|
|
164
|
-
let key = rel_path
|
|
164
|
+
let key = to_slash(rel_path);
|
|
165
165
|
let Some(live) = fs.stat(rel_path) else {
|
|
166
166
|
// The file vanished between the scan and the stat. Nothing to
|
|
167
167
|
// parse and nothing to cache; the next run decides afresh.
|
|
@@ -268,7 +268,7 @@ impl ParsedFs for RootedFs<'_> {
|
|
|
268
268
|
fn command_skip_message(rel_path: &Path, error: &str) -> String {
|
|
269
269
|
format!(
|
|
270
270
|
"dirsql: skipping `{}`: on-file command failed: {error}",
|
|
271
|
-
rel_path
|
|
271
|
+
to_slash(rel_path)
|
|
272
272
|
)
|
|
273
273
|
}
|
|
274
274
|
|
|
@@ -276,7 +276,7 @@ fn command_skip_message(rel_path: &Path, error: &str) -> String {
|
|
|
276
276
|
fn parse_skip_message(rel_path: &Path, message: &str) -> String {
|
|
277
277
|
format!(
|
|
278
278
|
"dirsql: skipping `{}`: on-file output was not a JSON array of rows: {message}",
|
|
279
|
-
rel_path
|
|
279
|
+
to_slash(rel_path)
|
|
280
280
|
)
|
|
281
281
|
}
|
|
282
282
|
|
|
@@ -292,8 +292,8 @@ fn no_rows_message(pattern: &str) -> String {
|
|
|
292
292
|
fn run_parser(command: &str, root: &Path, rel_path: &Path) -> std::result::Result<String, String> {
|
|
293
293
|
let abs_path = root.join(rel_path);
|
|
294
294
|
let placeholders = [
|
|
295
|
-
Placeholder::
|
|
296
|
-
Placeholder::
|
|
295
|
+
Placeholder::path("path", &abs_path.to_string_lossy()),
|
|
296
|
+
Placeholder::path("root", &root.to_string_lossy()),
|
|
297
297
|
];
|
|
298
298
|
|
|
299
299
|
run_command(command, &placeholders, root, None)
|
|
@@ -660,9 +660,12 @@ mod tests {
|
|
|
660
660
|
|
|
661
661
|
#[test]
|
|
662
662
|
fn run_parser_substitutes_the_absolute_path_and_the_root() {
|
|
663
|
-
let
|
|
664
|
-
|
|
665
|
-
assert_eq!(
|
|
663
|
+
let root = std::env::temp_dir();
|
|
664
|
+
let payload = run_parser("echo {path} {root}", &root, Path::new("a.json")).unwrap();
|
|
665
|
+
assert_eq!(
|
|
666
|
+
payload,
|
|
667
|
+
format!("{} {}", root.join("a.json").display(), root.display())
|
|
668
|
+
);
|
|
666
669
|
}
|
|
667
670
|
|
|
668
671
|
#[test]
|
|
@@ -10,6 +10,19 @@ use walkdir::WalkDir;
|
|
|
10
10
|
/// whether persistence is enabled.
|
|
11
11
|
pub const RESERVED_DIR: &str = ".dirsql";
|
|
12
12
|
|
|
13
|
+
/// A root-relative path as dirsql stores and reports it: `/`-separated on
|
|
14
|
+
/// every platform, so globs, `file_path` keys and `path` columns agree.
|
|
15
|
+
pub(crate) fn to_slash(path: &Path) -> String {
|
|
16
|
+
with_slashes(&path.to_string_lossy(), std::path::MAIN_SEPARATOR)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
fn with_slashes(path: &str, native: char) -> String {
|
|
20
|
+
if native == '/' {
|
|
21
|
+
return path.to_owned();
|
|
22
|
+
}
|
|
23
|
+
path.replace(native, "/")
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
/// Walk a directory tree and return all file paths paired with their matching table name.
|
|
14
27
|
/// Ignored paths and directories are skipped. Only files (not directories) are returned.
|
|
15
28
|
///
|
|
@@ -354,6 +367,29 @@ mod tests {
|
|
|
354
367
|
assert!(!is_glob_match(&set, Path::new("a.md")));
|
|
355
368
|
}
|
|
356
369
|
|
|
370
|
+
#[test]
|
|
371
|
+
fn with_slashes_rewrites_a_backslash_native_separator() {
|
|
372
|
+
assert_eq!(
|
|
373
|
+
with_slashes(r"moved\one\mid.txt", '\\'),
|
|
374
|
+
"moved/one/mid.txt"
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
#[test]
|
|
379
|
+
fn with_slashes_keeps_a_mixed_path_slash_separated() {
|
|
380
|
+
assert_eq!(with_slashes(r"a/b\c.txt", '\\'), "a/b/c.txt");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
#[test]
|
|
384
|
+
fn with_slashes_leaves_a_backslash_in_a_unix_file_name() {
|
|
385
|
+
assert_eq!(with_slashes(r"dir/a\b.txt", '/'), r"dir/a\b.txt");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
#[test]
|
|
389
|
+
fn to_slash_keeps_a_native_unix_path() {
|
|
390
|
+
assert_eq!(to_slash(Path::new("docs/nested/a.md")), "docs/nested/a.md");
|
|
391
|
+
}
|
|
392
|
+
|
|
357
393
|
#[test]
|
|
358
394
|
fn compile_glob_rejects_an_invalid_pattern() {
|
|
359
395
|
assert!(compile_glob("[").is_err());
|