dirsql 0.4.39__tar.gz → 0.4.41__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.39 → dirsql-0.4.41}/Cargo.lock +2 -2
- {dirsql-0.4.39 → dirsql-0.4.41}/PKG-INFO +1 -1
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/_dirsql.pyi +2 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/cli/resolve_config_extensions.py +1 -1
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/Cargo.toml +1 -1
- dirsql-0.4.41/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +3 -0
- dirsql-0.4.41/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +7 -0
- dirsql-0.4.41/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +7 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/src/lib.rs +16 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/Cargo.toml +1 -1
- dirsql-0.4.41/packages/rust/src/launcher.rs +168 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/lib.rs +249 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/scanner.rs +30 -6
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/watcher.rs +73 -0
- dirsql-0.4.39/dirsql/cli/config_paths_from_argv.py +0 -28
- {dirsql-0.4.39 → dirsql-0.4.41}/Cargo.toml +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/README.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/__init__.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/_async.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/has_bare_name.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/is_bare_name.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/load_extension_entries.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/platform_patterns.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/py.typed +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/resolve_configs_extension_specs.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/resolve_entries.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/resolve_package.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/dirsql/toml_module.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/AGENTS.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/explanation.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/getting-started.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/embed.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/persist.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/index.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/package.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/plugins.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/reference/cli.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/reference/columns.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/reference/config.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/reference/hooks.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/README.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/conftest.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/testing-conventions.toml +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/README.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/command.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/db.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/functions.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/parsed_cache.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/parsed_vtab.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/progress.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/packages/rust/src/vtab.rs +0 -0
- {dirsql-0.4.39 → dirsql-0.4.41}/pyproject.toml +0 -0
|
@@ -540,7 +540,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
|
|
540
540
|
|
|
541
541
|
[[package]]
|
|
542
542
|
name = "dirsql"
|
|
543
|
-
version = "0.4.
|
|
543
|
+
version = "0.4.41"
|
|
544
544
|
dependencies = [
|
|
545
545
|
"assert_cmd",
|
|
546
546
|
"axum",
|
|
@@ -583,7 +583,7 @@ dependencies = [
|
|
|
583
583
|
|
|
584
584
|
[[package]]
|
|
585
585
|
name = "dirsql-py-ext"
|
|
586
|
-
version = "0.4.
|
|
586
|
+
version = "0.4.41"
|
|
587
587
|
dependencies = [
|
|
588
588
|
"dirsql",
|
|
589
589
|
"pyo3",
|
|
@@ -15,8 +15,8 @@ carries resolved paths.
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
from .._dirsql import config_paths_from_argv
|
|
18
19
|
from ..resolve_configs_extension_specs import resolve_configs_extension_specs
|
|
19
|
-
from .config_paths_from_argv import config_paths_from_argv
|
|
20
20
|
|
|
21
21
|
# Config extensions the binary dispatches to `dirsql interpret`; never
|
|
22
22
|
# pre-resolved here (that path resolves via the handshake).
|
|
@@ -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.41"
|
|
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,3 @@
|
|
|
1
|
+
**Changed**
|
|
2
|
+
|
|
3
|
+
- **The launcher's config-path scan now runs in the shared core.** Before clap sees argv, the pip launcher still walks it for every `--config` / `-c` value (all five spellings) so a bare package-name `[[dirsql.extension]]` entry can be resolved through `importlib` — but the walk itself is now `dirsql::launcher::config_paths_from_argv`, reached through the private `dirsql._dirsql.config_paths_from_argv` binding; the Python copy is deleted. No user-observable change: same flags, same paths, same default of `./.dirsql.toml`. (#1078, #1072)
|
|
@@ -462,6 +462,13 @@ mod python {
|
|
|
462
462
|
py.detach(|| dirsql::cli::run_cli(full))
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
+
/// The config paths a launcher must inspect for package-name extensions;
|
|
466
|
+
/// see [`dirsql::launcher::config_paths_from_argv`].
|
|
467
|
+
#[pyfunction]
|
|
468
|
+
fn config_paths_from_argv(argv: Vec<String>) -> Vec<String> {
|
|
469
|
+
dirsql::launcher::config_paths_from_argv(&argv)
|
|
470
|
+
}
|
|
471
|
+
|
|
465
472
|
#[pymodule]
|
|
466
473
|
#[pyo3(name = "_dirsql")]
|
|
467
474
|
fn py_dirsql_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
@@ -471,6 +478,7 @@ mod python {
|
|
|
471
478
|
m.add_class::<PyRowEvent>()?;
|
|
472
479
|
m.add_class::<PyScanFailure>()?;
|
|
473
480
|
m.add_function(wrap_pyfunction!(run_cli, m)?)?;
|
|
481
|
+
m.add_function(wrap_pyfunction!(config_paths_from_argv, m)?)?;
|
|
474
482
|
Ok(())
|
|
475
483
|
}
|
|
476
484
|
|
|
@@ -553,6 +561,14 @@ mod python {
|
|
|
553
561
|
assert_eq!(OnFileError("bad".to_string()).to_string(), "bad");
|
|
554
562
|
}
|
|
555
563
|
|
|
564
|
+
#[test]
|
|
565
|
+
fn config_paths_from_argv_forwards_to_the_core_scan() {
|
|
566
|
+
assert_eq!(
|
|
567
|
+
config_paths_from_argv(vec!["-c".into(), "a.toml".into()]),
|
|
568
|
+
["a.toml"]
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
556
572
|
#[test]
|
|
557
573
|
fn int_overflow_message_names_the_value() {
|
|
558
574
|
let m = int_overflow_message("9223372036854775808");
|
|
@@ -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.41"
|
|
10
10
|
edition.workspace = true
|
|
11
11
|
# Literal `license` (not `license.workspace = true`) because
|
|
12
12
|
# putitoutthere's preflight check reads `[package].license` directly
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
//! Argv scanning the language launchers share.
|
|
2
|
+
//!
|
|
3
|
+
//! The pip and npm launchers must know which config files an invocation names
|
|
4
|
+
//! *before* the core parses argv: a config's `[[dirsql.extension]]` entry may
|
|
5
|
+
//! name an extension by bare package name, and resolving that needs the
|
|
6
|
+
//! interpreter the launcher has and this crate does not. Everything about
|
|
7
|
+
//! locating those config paths is language-neutral, so it lives here and each
|
|
8
|
+
//! launcher reaches it through its binding rather than reimplementing it.
|
|
9
|
+
|
|
10
|
+
/// The config a launcher inspects when argv names none.
|
|
11
|
+
const DEFAULT_CONFIG_PATH: &str = "./.dirsql.toml";
|
|
12
|
+
|
|
13
|
+
/// Every config value in `argv`, in order (`--config X`, `--config=X`,
|
|
14
|
+
/// `-c X`, `-c=X`, `-cX`), or [`DEFAULT_CONFIG_PATH`] when argv names none.
|
|
15
|
+
///
|
|
16
|
+
/// The scan is deliberately looser than clap: it runs before argv is parsed,
|
|
17
|
+
/// only to find files to read extensions out of, so a bare trailing `-c`
|
|
18
|
+
/// yields an empty string rather than an error. A malformed invocation is
|
|
19
|
+
/// clap's to reject once the launcher hands argv over.
|
|
20
|
+
pub fn config_paths_from_argv(argv: &[String]) -> Vec<String> {
|
|
21
|
+
let mut paths = Vec::new();
|
|
22
|
+
let mut args = argv.iter();
|
|
23
|
+
while let Some(arg) = args.next() {
|
|
24
|
+
if arg == "--config" || arg == "-c" {
|
|
25
|
+
paths.push(args.next().cloned().unwrap_or_default());
|
|
26
|
+
} else if let Some(value) = arg.strip_prefix("--config=") {
|
|
27
|
+
paths.push(value.to_string());
|
|
28
|
+
} else if let Some(value) = arg.strip_prefix("-c") {
|
|
29
|
+
paths.push(value.strip_prefix('=').unwrap_or(value).to_string());
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if paths.is_empty() {
|
|
33
|
+
paths.push(DEFAULT_CONFIG_PATH.to_string());
|
|
34
|
+
}
|
|
35
|
+
paths
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[cfg(test)]
|
|
39
|
+
mod tests {
|
|
40
|
+
use super::config_paths_from_argv;
|
|
41
|
+
|
|
42
|
+
fn scan(argv: &[&str]) -> Vec<String> {
|
|
43
|
+
config_paths_from_argv(&argv.iter().map(|a| (*a).to_string()).collect::<Vec<_>>())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#[test]
|
|
47
|
+
fn reads_the_long_config_form() {
|
|
48
|
+
assert_eq!(
|
|
49
|
+
scan(&["--config", "/cfg/.dirsql.toml"]),
|
|
50
|
+
["/cfg/.dirsql.toml"]
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn reads_the_long_config_equals_form() {
|
|
56
|
+
assert_eq!(scan(&["--config=/c/.dirsql.toml"]), ["/c/.dirsql.toml"]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[test]
|
|
60
|
+
fn reads_the_short_c_form() {
|
|
61
|
+
assert_eq!(scan(&["-c", "/frag/dirsql.toml"]), ["/frag/dirsql.toml"]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
fn reads_the_short_c_equals_form() {
|
|
66
|
+
assert_eq!(scan(&["-c=/frag/dirsql.toml"]), ["/frag/dirsql.toml"]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[test]
|
|
70
|
+
fn reads_the_short_c_attached_form() {
|
|
71
|
+
assert_eq!(scan(&["-c/frag/dirsql.toml"]), ["/frag/dirsql.toml"]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#[test]
|
|
75
|
+
fn collects_every_config_flag_in_argv_order() {
|
|
76
|
+
assert_eq!(
|
|
77
|
+
scan(&[
|
|
78
|
+
"-c",
|
|
79
|
+
"a.toml",
|
|
80
|
+
"--config",
|
|
81
|
+
"b.toml",
|
|
82
|
+
"--config=c.toml",
|
|
83
|
+
"-cd.toml"
|
|
84
|
+
]),
|
|
85
|
+
["a.toml", "b.toml", "c.toml", "d.toml"]
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn reads_the_config_value_at_any_argv_position() {
|
|
91
|
+
assert_eq!(scan(&["-v", "--config", "/x/y", "tail"]), ["/x/y"]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[test]
|
|
95
|
+
fn collects_a_discovery_injected_fragment_rather_than_the_default() {
|
|
96
|
+
assert_eq!(
|
|
97
|
+
scan(&[
|
|
98
|
+
"query",
|
|
99
|
+
"SELECT 1",
|
|
100
|
+
"--include-default",
|
|
101
|
+
"-c",
|
|
102
|
+
"/frag/dirsql.toml"
|
|
103
|
+
]),
|
|
104
|
+
["/frag/dirsql.toml"]
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[test]
|
|
109
|
+
fn consumes_a_config_value_that_looks_like_a_flag() {
|
|
110
|
+
assert_eq!(scan(&["--config", "-cx.toml"]), ["-cx.toml"]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#[test]
|
|
114
|
+
fn consumes_a_config_value_that_is_itself_a_config_flag() {
|
|
115
|
+
assert_eq!(scan(&["-c", "-c", "x"]), ["-c"]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#[test]
|
|
119
|
+
fn does_not_mistake_other_short_flags_for_config() {
|
|
120
|
+
assert_eq!(scan(&["-v", "-x", "val"]), ["./.dirsql.toml"]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#[test]
|
|
124
|
+
fn does_not_mistake_other_long_flags_for_config() {
|
|
125
|
+
assert_eq!(scan(&["--a", "val"]), ["./.dirsql.toml"]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[test]
|
|
129
|
+
fn does_not_mistake_a_config_prefixed_long_flag_for_config() {
|
|
130
|
+
assert_eq!(scan(&["--configuration", "val"]), ["./.dirsql.toml"]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[test]
|
|
134
|
+
fn defaults_when_no_config_flag_is_given() {
|
|
135
|
+
assert_eq!(scan(&["--port", "9000"]), ["./.dirsql.toml"]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#[test]
|
|
139
|
+
fn defaults_for_an_empty_argv() {
|
|
140
|
+
assert_eq!(scan(&[]), ["./.dirsql.toml"]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn treats_a_bare_trailing_long_config_as_an_empty_path() {
|
|
145
|
+
assert_eq!(scan(&["--config"]), [""]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[test]
|
|
149
|
+
fn treats_a_bare_trailing_short_c_as_an_empty_path() {
|
|
150
|
+
assert_eq!(scan(&["-c"]), [""]);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn keeps_a_bare_trailing_short_c_after_an_earlier_config() {
|
|
155
|
+
assert_eq!(scan(&["--config", "a.toml", "-c"]), ["a.toml", ""]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[test]
|
|
159
|
+
fn reads_an_empty_value_from_the_equals_forms() {
|
|
160
|
+
assert_eq!(scan(&["--config="]), [""]);
|
|
161
|
+
assert_eq!(scan(&["-c="]), [""]);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#[test]
|
|
165
|
+
fn does_not_treat_a_double_dash_as_end_of_options() {
|
|
166
|
+
assert_eq!(scan(&["--", "-c", "a.toml"]), ["a.toml"]);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -20,6 +20,8 @@ pub mod functions;
|
|
|
20
20
|
#[doc(hidden)]
|
|
21
21
|
pub mod infer;
|
|
22
22
|
#[doc(hidden)]
|
|
23
|
+
pub mod launcher;
|
|
24
|
+
#[doc(hidden)]
|
|
23
25
|
pub mod matcher;
|
|
24
26
|
#[doc(hidden)]
|
|
25
27
|
pub mod parsed_cache;
|
|
@@ -538,6 +540,18 @@ impl DirSQL {
|
|
|
538
540
|
return Vec::new();
|
|
539
541
|
}
|
|
540
542
|
|
|
543
|
+
// A directory that appears whole (`mkdir`, or a populated directory
|
|
544
|
+
// renamed into the tree) arrives as one event naming the directory and
|
|
545
|
+
// none for the files already inside it, so the subtree is walked here.
|
|
546
|
+
// Only on creation: a metadata change on a directory says nothing
|
|
547
|
+
// about its files, and walking on every one would re-run `on_file`
|
|
548
|
+
// across the subtree for a `chmod`.
|
|
549
|
+
if matches!(event, FileEvent::Created(_))
|
|
550
|
+
&& self.inner.fs.is_dir(&abs_path).unwrap_or(false)
|
|
551
|
+
{
|
|
552
|
+
return self.index_subtree(&abs_path);
|
|
553
|
+
}
|
|
554
|
+
|
|
541
555
|
// Fan-out: dispatch the event to every table whose glob matches, and
|
|
542
556
|
// concatenate the resulting row events. Cross-table event order is
|
|
543
557
|
// unspecified. An `on_file` failure produces an error event for that
|
|
@@ -562,6 +576,27 @@ impl DirSQL {
|
|
|
562
576
|
events
|
|
563
577
|
}
|
|
564
578
|
|
|
579
|
+
/// Index every file beneath `dir` as if each had its own create event,
|
|
580
|
+
/// using the scanner's walk so the watch path and the initial scan share
|
|
581
|
+
/// one definition of what counts as a row.
|
|
582
|
+
fn index_subtree(&self, dir: &Path) -> Vec<RowEvent> {
|
|
583
|
+
let base = if dir.starts_with(&self.inner.watch_root) {
|
|
584
|
+
&self.inner.watch_root
|
|
585
|
+
} else {
|
|
586
|
+
&self.inner.root
|
|
587
|
+
};
|
|
588
|
+
let mut events = Vec::new();
|
|
589
|
+
for (path, table) in self.inner.fs.scan_subtree(base, dir, &self.inner.matcher) {
|
|
590
|
+
let rel_path = path
|
|
591
|
+
.strip_prefix(base)
|
|
592
|
+
.unwrap_or(&path)
|
|
593
|
+
.to_string_lossy()
|
|
594
|
+
.to_string();
|
|
595
|
+
events.extend(self.handle_upsert(&table, &path, &rel_path));
|
|
596
|
+
}
|
|
597
|
+
events
|
|
598
|
+
}
|
|
599
|
+
|
|
565
600
|
fn handle_delete(&self, table: &str, rel_path: &str) -> Vec<RowEvent> {
|
|
566
601
|
// Snapshot the file's rows before deleting them — the Delete events
|
|
567
602
|
// carry the old-row payloads. Read and delete under one lock
|
|
@@ -1571,6 +1606,15 @@ trait FileSystem: Send + Sync {
|
|
|
1571
1606
|
fn stat(&self, path: &Path) -> std::io::Result<FileStat>;
|
|
1572
1607
|
/// Whether `path` is a regular file. `Err(NotFound)` when it doesn't exist.
|
|
1573
1608
|
fn is_file(&self, path: &Path) -> std::io::Result<bool>;
|
|
1609
|
+
/// Whether `path` is a directory. `Err(NotFound)` when it doesn't exist.
|
|
1610
|
+
fn is_dir(&self, path: &Path) -> std::io::Result<bool>;
|
|
1611
|
+
/// The scanner's walk over the subtree at `dir`, matched relative to `root`.
|
|
1612
|
+
fn scan_subtree(
|
|
1613
|
+
&self,
|
|
1614
|
+
root: &Path,
|
|
1615
|
+
dir: &Path,
|
|
1616
|
+
matcher: &TableMatcher,
|
|
1617
|
+
) -> Vec<(PathBuf, String)>;
|
|
1574
1618
|
/// BLAKE3-hash a file's contents.
|
|
1575
1619
|
fn hash(&self, path: &Path) -> std::io::Result<[u8; 32]>;
|
|
1576
1620
|
/// Canonicalize the watch root, falling back to the literal path.
|
|
@@ -1588,6 +1632,19 @@ impl FileSystem for RealFs {
|
|
|
1588
1632
|
std::fs::metadata(path).map(|m| m.is_file())
|
|
1589
1633
|
}
|
|
1590
1634
|
|
|
1635
|
+
fn is_dir(&self, path: &Path) -> std::io::Result<bool> {
|
|
1636
|
+
std::fs::metadata(path).map(|m| m.is_dir())
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
fn scan_subtree(
|
|
1640
|
+
&self,
|
|
1641
|
+
root: &Path,
|
|
1642
|
+
dir: &Path,
|
|
1643
|
+
matcher: &TableMatcher,
|
|
1644
|
+
) -> Vec<(PathBuf, String)> {
|
|
1645
|
+
scanner::scan_subtree(root, dir, matcher)
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1591
1648
|
fn hash(&self, path: &Path) -> std::io::Result<[u8; 32]> {
|
|
1592
1649
|
hash_file(path)
|
|
1593
1650
|
}
|
|
@@ -2213,6 +2270,8 @@ mod internal_tests {
|
|
|
2213
2270
|
hashes: StdHashMap<PathBuf, [u8; 32]>,
|
|
2214
2271
|
canonical_roots: StdHashMap<PathBuf, String>,
|
|
2215
2272
|
dirs: StdHashSet<PathBuf>,
|
|
2273
|
+
subtrees: StdHashMap<PathBuf, Vec<(PathBuf, String)>>,
|
|
2274
|
+
subtree_walks: Mutex<Vec<PathBuf>>,
|
|
2216
2275
|
}
|
|
2217
2276
|
|
|
2218
2277
|
impl FakeFs {
|
|
@@ -2228,6 +2287,22 @@ mod internal_tests {
|
|
|
2228
2287
|
self
|
|
2229
2288
|
}
|
|
2230
2289
|
|
|
2290
|
+
/// Register a directory together with what the scanner's walk of it
|
|
2291
|
+
/// would return; each file is also stat-able as a regular file.
|
|
2292
|
+
fn with_subtree(mut self, dir: impl Into<PathBuf>, found: Vec<(PathBuf, String)>) -> Self {
|
|
2293
|
+
let dir = dir.into();
|
|
2294
|
+
self.dirs.insert(dir.clone());
|
|
2295
|
+
for (path, _) in &found {
|
|
2296
|
+
self.stats.insert(path.clone(), fake_stat());
|
|
2297
|
+
}
|
|
2298
|
+
self.subtrees.insert(dir, found);
|
|
2299
|
+
self
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
fn subtree_walks(&self) -> Vec<PathBuf> {
|
|
2303
|
+
self.subtree_walks.lock().unwrap().clone()
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2231
2306
|
fn set_hash(&mut self, path: impl Into<PathBuf>, hash: [u8; 32]) {
|
|
2232
2307
|
self.hashes.insert(path.into(), hash);
|
|
2233
2308
|
}
|
|
@@ -2263,6 +2338,29 @@ mod internal_tests {
|
|
|
2263
2338
|
))
|
|
2264
2339
|
}
|
|
2265
2340
|
|
|
2341
|
+
fn is_dir(&self, path: &Path) -> std::io::Result<bool> {
|
|
2342
|
+
if self.dirs.contains(path) {
|
|
2343
|
+
return Ok(true);
|
|
2344
|
+
}
|
|
2345
|
+
if self.stats.contains_key(path) {
|
|
2346
|
+
return Ok(false);
|
|
2347
|
+
}
|
|
2348
|
+
Err(std::io::Error::new(
|
|
2349
|
+
std::io::ErrorKind::NotFound,
|
|
2350
|
+
"fake: no such file",
|
|
2351
|
+
))
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
fn scan_subtree(
|
|
2355
|
+
&self,
|
|
2356
|
+
_root: &Path,
|
|
2357
|
+
dir: &Path,
|
|
2358
|
+
_matcher: &TableMatcher,
|
|
2359
|
+
) -> Vec<(PathBuf, String)> {
|
|
2360
|
+
self.subtree_walks.lock().unwrap().push(dir.to_path_buf());
|
|
2361
|
+
self.subtrees.get(dir).cloned().unwrap_or_default()
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2266
2364
|
fn hash(&self, path: &Path) -> std::io::Result<[u8; 32]> {
|
|
2267
2365
|
self.hashes.get(path).copied().ok_or_else(|| {
|
|
2268
2366
|
std::io::Error::new(std::io::ErrorKind::NotFound, "fake: no such file")
|
|
@@ -2517,6 +2615,144 @@ mod internal_tests {
|
|
|
2517
2615
|
assert_eq!(events.len(), 1, "non-ignored path must produce one event");
|
|
2518
2616
|
}
|
|
2519
2617
|
|
|
2618
|
+
fn subtree_db(fake: FakeFs, root: &Path, ignore: Vec<&str>) -> DirSQL {
|
|
2619
|
+
DirSQL::with_ignore_and_fs(
|
|
2620
|
+
root,
|
|
2621
|
+
vec![Table::new(
|
|
2622
|
+
"items",
|
|
2623
|
+
"CREATE TABLE items (name TEXT)",
|
|
2624
|
+
"**/*.txt",
|
|
2625
|
+
|path| {
|
|
2626
|
+
vec![Row::from_iter([(
|
|
2627
|
+
"name".to_string(),
|
|
2628
|
+
Value::Text(path.to_string()),
|
|
2629
|
+
)])]
|
|
2630
|
+
},
|
|
2631
|
+
)],
|
|
2632
|
+
ignore,
|
|
2633
|
+
Arc::new(fake),
|
|
2634
|
+
)
|
|
2635
|
+
.unwrap()
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
/// A created directory is indexed by walking it: every file the scanner
|
|
2639
|
+
/// reports beneath it becomes rows, as if each had its own create event.
|
|
2640
|
+
#[test]
|
|
2641
|
+
fn process_file_event_indexes_the_files_beneath_a_created_directory() {
|
|
2642
|
+
let dir = TempDir::new().unwrap();
|
|
2643
|
+
let moved = dir.path().join("moved");
|
|
2644
|
+
let a = moved.join("a.txt");
|
|
2645
|
+
let deep = moved.join("one").join("deep.txt");
|
|
2646
|
+
let fake = FakeFs::default().with_subtree(
|
|
2647
|
+
moved.clone(),
|
|
2648
|
+
vec![(a.clone(), "items".into()), (deep.clone(), "items".into())],
|
|
2649
|
+
);
|
|
2650
|
+
let db = subtree_db(fake, dir.path(), Vec::new());
|
|
2651
|
+
|
|
2652
|
+
let events = db.process_file_event(FileEvent::Created(moved));
|
|
2653
|
+
|
|
2654
|
+
assert_eq!(events.len(), 2, "one insert per file found: {events:?}");
|
|
2655
|
+
assert!(
|
|
2656
|
+
events.iter().all(|e| matches!(e, RowEvent::Insert { .. })),
|
|
2657
|
+
"got: {events:?}"
|
|
2658
|
+
);
|
|
2659
|
+
let paths: Vec<String> = events
|
|
2660
|
+
.iter()
|
|
2661
|
+
.filter_map(|e| match e {
|
|
2662
|
+
RowEvent::Insert { file_path, .. } => Some(file_path.clone()),
|
|
2663
|
+
_ => None,
|
|
2664
|
+
})
|
|
2665
|
+
.collect();
|
|
2666
|
+
assert_eq!(
|
|
2667
|
+
paths,
|
|
2668
|
+
vec!["moved/a.txt".to_string(), "moved/one/deep.txt".to_string()],
|
|
2669
|
+
"file paths are root-relative"
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
/// The walk is matched against the root the event was stripped with, so
|
|
2674
|
+
/// the rows it writes carry the same relative paths a create event would.
|
|
2675
|
+
#[test]
|
|
2676
|
+
fn process_file_event_walks_a_created_directory_under_the_watch_root() {
|
|
2677
|
+
let root = PathBuf::from("/ws");
|
|
2678
|
+
let moved = root.join("moved");
|
|
2679
|
+
let fake = FakeFs::default()
|
|
2680
|
+
.with_canonical_root(&root, "/ws")
|
|
2681
|
+
.with_subtree(moved.clone(), vec![(moved.join("a.txt"), "items".into())]);
|
|
2682
|
+
let db = subtree_db(fake, &root, Vec::new());
|
|
2683
|
+
|
|
2684
|
+
let events = db.process_file_event(FileEvent::Created(moved.clone()));
|
|
2685
|
+
|
|
2686
|
+
assert!(
|
|
2687
|
+
matches!(&events[..], [RowEvent::Insert { file_path, .. }] if file_path == "moved/a.txt"),
|
|
2688
|
+
"got: {events:?}"
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
/// Only a *created* directory is walked. A metadata change on one says
|
|
2693
|
+
/// nothing about its files, and walking on every such event would re-run
|
|
2694
|
+
/// `on_file` across the whole subtree for a `chmod`.
|
|
2695
|
+
#[test]
|
|
2696
|
+
fn process_file_event_does_not_walk_a_modified_directory() {
|
|
2697
|
+
let dir = TempDir::new().unwrap();
|
|
2698
|
+
let sub = dir.path().join("sub");
|
|
2699
|
+
let fake =
|
|
2700
|
+
FakeFs::default().with_subtree(sub.clone(), vec![(sub.join("a.txt"), "items".into())]);
|
|
2701
|
+
let fake = Arc::new(fake);
|
|
2702
|
+
let db = DirSQL::with_ignore_and_fs(
|
|
2703
|
+
dir.path(),
|
|
2704
|
+
vec![Table::new(
|
|
2705
|
+
"items",
|
|
2706
|
+
"CREATE TABLE items (name TEXT)",
|
|
2707
|
+
"**/*.txt",
|
|
2708
|
+
|_| vec![],
|
|
2709
|
+
)],
|
|
2710
|
+
Vec::<String>::new(),
|
|
2711
|
+
fake.clone(),
|
|
2712
|
+
)
|
|
2713
|
+
.unwrap();
|
|
2714
|
+
|
|
2715
|
+
let events = db.process_file_event(FileEvent::Modified(sub.clone()));
|
|
2716
|
+
|
|
2717
|
+
assert!(events.is_empty(), "got: {events:?}");
|
|
2718
|
+
assert!(
|
|
2719
|
+
fake.subtree_walks().is_empty(),
|
|
2720
|
+
"a modified directory must not be walked; walked: {:?}",
|
|
2721
|
+
fake.subtree_walks()
|
|
2722
|
+
);
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
/// An ignored directory is never walked, matching the initial scan.
|
|
2726
|
+
#[test]
|
|
2727
|
+
fn process_file_event_does_not_walk_an_ignored_created_directory() {
|
|
2728
|
+
let dir = TempDir::new().unwrap();
|
|
2729
|
+
let skip = dir.path().join("skip");
|
|
2730
|
+
let fake = Arc::new(
|
|
2731
|
+
FakeFs::default()
|
|
2732
|
+
.with_subtree(skip.clone(), vec![(skip.join("a.txt"), "items".into())]),
|
|
2733
|
+
);
|
|
2734
|
+
let db = DirSQL::with_ignore_and_fs(
|
|
2735
|
+
dir.path(),
|
|
2736
|
+
vec![Table::new(
|
|
2737
|
+
"items",
|
|
2738
|
+
"CREATE TABLE items (name TEXT)",
|
|
2739
|
+
"**/*.txt",
|
|
2740
|
+
|_| vec![],
|
|
2741
|
+
)],
|
|
2742
|
+
vec!["skip"],
|
|
2743
|
+
fake.clone(),
|
|
2744
|
+
)
|
|
2745
|
+
.unwrap();
|
|
2746
|
+
|
|
2747
|
+
let events = db.process_file_event(FileEvent::Created(skip));
|
|
2748
|
+
|
|
2749
|
+
assert!(events.is_empty(), "got: {events:?}");
|
|
2750
|
+
assert!(
|
|
2751
|
+
fake.subtree_walks().is_empty(),
|
|
2752
|
+
"ignored directory was walked"
|
|
2753
|
+
);
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2520
2756
|
/// Building with a **relative** root canonicalizes `watch_root` to an
|
|
2521
2757
|
/// absolute path while leaving `root` exactly as the caller supplied it,
|
|
2522
2758
|
/// so `notify` never sees `.`.
|
|
@@ -2773,6 +3009,19 @@ mod internal_tests {
|
|
|
2773
3009
|
fs.is_file(&missing).is_err(),
|
|
2774
3010
|
"is_file of a missing path must error"
|
|
2775
3011
|
);
|
|
3012
|
+
assert!(
|
|
3013
|
+
fs.is_dir(dir.path()).unwrap(),
|
|
3014
|
+
"a directory must report is_dir=true"
|
|
3015
|
+
);
|
|
3016
|
+
assert!(
|
|
3017
|
+
fs.is_dir(&missing).is_err(),
|
|
3018
|
+
"is_dir of a missing path must error"
|
|
3019
|
+
);
|
|
3020
|
+
let matcher = TableMatcher::new(&[("**/*", "files")], &[]).unwrap();
|
|
3021
|
+
assert!(
|
|
3022
|
+
fs.scan_subtree(dir.path(), dir.path(), &matcher).is_empty(),
|
|
3023
|
+
"an empty directory walks to nothing"
|
|
3024
|
+
);
|
|
2776
3025
|
// A nonexistent path can't canonicalize, so the literal fallback runs.
|
|
2777
3026
|
assert_eq!(
|
|
2778
3027
|
fs.canonical_root(&missing),
|