dirsql 0.4.38__tar.gz → 0.4.40__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.38 → dirsql-0.4.40}/Cargo.lock +2 -2
- {dirsql-0.4.38 → dirsql-0.4.40}/PKG-INFO +1 -1
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/Cargo.toml +1 -1
- dirsql-0.4.40/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +7 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/testing-conventions.toml +0 -3
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/Cargo.toml +1 -1
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/lib.rs +247 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/scanner.rs +30 -6
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/watcher.rs +73 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/Cargo.toml +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/README.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/__init__.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/_async.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/config_paths_from_argv.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/has_bare_name.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/is_bare_name.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/load_extension_entries.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/platform_patterns.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/py.typed +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/resolve_configs_extension_specs.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/resolve_entries.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/resolve_package.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/dirsql/toml_module.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/AGENTS.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/explanation.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/getting-started.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/embed.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/persist.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/index.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/package.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/plugins.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/reference/cli.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/reference/columns.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/reference/config.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/reference/hooks.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/README.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/conftest.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/README.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/command.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/db.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/functions.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/parsed_cache.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/parsed_vtab.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/progress.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/packages/rust/src/vtab.rs +0 -0
- {dirsql-0.4.38 → dirsql-0.4.40}/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.40"
|
|
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.40"
|
|
587
587
|
dependencies = [
|
|
588
588
|
"dirsql",
|
|
589
589
|
"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.40"
|
|
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.40"
|
|
10
10
|
edition.workspace = true
|
|
11
11
|
# Literal `license` (not `license.workspace = true`) because
|
|
12
12
|
# putitoutthere's preflight check reads `[package].license` directly
|
|
@@ -538,6 +538,18 @@ impl DirSQL {
|
|
|
538
538
|
return Vec::new();
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
+
// A directory that appears whole (`mkdir`, or a populated directory
|
|
542
|
+
// renamed into the tree) arrives as one event naming the directory and
|
|
543
|
+
// none for the files already inside it, so the subtree is walked here.
|
|
544
|
+
// Only on creation: a metadata change on a directory says nothing
|
|
545
|
+
// about its files, and walking on every one would re-run `on_file`
|
|
546
|
+
// across the subtree for a `chmod`.
|
|
547
|
+
if matches!(event, FileEvent::Created(_))
|
|
548
|
+
&& self.inner.fs.is_dir(&abs_path).unwrap_or(false)
|
|
549
|
+
{
|
|
550
|
+
return self.index_subtree(&abs_path);
|
|
551
|
+
}
|
|
552
|
+
|
|
541
553
|
// Fan-out: dispatch the event to every table whose glob matches, and
|
|
542
554
|
// concatenate the resulting row events. Cross-table event order is
|
|
543
555
|
// unspecified. An `on_file` failure produces an error event for that
|
|
@@ -562,6 +574,27 @@ impl DirSQL {
|
|
|
562
574
|
events
|
|
563
575
|
}
|
|
564
576
|
|
|
577
|
+
/// Index every file beneath `dir` as if each had its own create event,
|
|
578
|
+
/// using the scanner's walk so the watch path and the initial scan share
|
|
579
|
+
/// one definition of what counts as a row.
|
|
580
|
+
fn index_subtree(&self, dir: &Path) -> Vec<RowEvent> {
|
|
581
|
+
let base = if dir.starts_with(&self.inner.watch_root) {
|
|
582
|
+
&self.inner.watch_root
|
|
583
|
+
} else {
|
|
584
|
+
&self.inner.root
|
|
585
|
+
};
|
|
586
|
+
let mut events = Vec::new();
|
|
587
|
+
for (path, table) in self.inner.fs.scan_subtree(base, dir, &self.inner.matcher) {
|
|
588
|
+
let rel_path = path
|
|
589
|
+
.strip_prefix(base)
|
|
590
|
+
.unwrap_or(&path)
|
|
591
|
+
.to_string_lossy()
|
|
592
|
+
.to_string();
|
|
593
|
+
events.extend(self.handle_upsert(&table, &path, &rel_path));
|
|
594
|
+
}
|
|
595
|
+
events
|
|
596
|
+
}
|
|
597
|
+
|
|
565
598
|
fn handle_delete(&self, table: &str, rel_path: &str) -> Vec<RowEvent> {
|
|
566
599
|
// Snapshot the file's rows before deleting them — the Delete events
|
|
567
600
|
// carry the old-row payloads. Read and delete under one lock
|
|
@@ -1571,6 +1604,15 @@ trait FileSystem: Send + Sync {
|
|
|
1571
1604
|
fn stat(&self, path: &Path) -> std::io::Result<FileStat>;
|
|
1572
1605
|
/// Whether `path` is a regular file. `Err(NotFound)` when it doesn't exist.
|
|
1573
1606
|
fn is_file(&self, path: &Path) -> std::io::Result<bool>;
|
|
1607
|
+
/// Whether `path` is a directory. `Err(NotFound)` when it doesn't exist.
|
|
1608
|
+
fn is_dir(&self, path: &Path) -> std::io::Result<bool>;
|
|
1609
|
+
/// The scanner's walk over the subtree at `dir`, matched relative to `root`.
|
|
1610
|
+
fn scan_subtree(
|
|
1611
|
+
&self,
|
|
1612
|
+
root: &Path,
|
|
1613
|
+
dir: &Path,
|
|
1614
|
+
matcher: &TableMatcher,
|
|
1615
|
+
) -> Vec<(PathBuf, String)>;
|
|
1574
1616
|
/// BLAKE3-hash a file's contents.
|
|
1575
1617
|
fn hash(&self, path: &Path) -> std::io::Result<[u8; 32]>;
|
|
1576
1618
|
/// Canonicalize the watch root, falling back to the literal path.
|
|
@@ -1588,6 +1630,19 @@ impl FileSystem for RealFs {
|
|
|
1588
1630
|
std::fs::metadata(path).map(|m| m.is_file())
|
|
1589
1631
|
}
|
|
1590
1632
|
|
|
1633
|
+
fn is_dir(&self, path: &Path) -> std::io::Result<bool> {
|
|
1634
|
+
std::fs::metadata(path).map(|m| m.is_dir())
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
fn scan_subtree(
|
|
1638
|
+
&self,
|
|
1639
|
+
root: &Path,
|
|
1640
|
+
dir: &Path,
|
|
1641
|
+
matcher: &TableMatcher,
|
|
1642
|
+
) -> Vec<(PathBuf, String)> {
|
|
1643
|
+
scanner::scan_subtree(root, dir, matcher)
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1591
1646
|
fn hash(&self, path: &Path) -> std::io::Result<[u8; 32]> {
|
|
1592
1647
|
hash_file(path)
|
|
1593
1648
|
}
|
|
@@ -2213,6 +2268,8 @@ mod internal_tests {
|
|
|
2213
2268
|
hashes: StdHashMap<PathBuf, [u8; 32]>,
|
|
2214
2269
|
canonical_roots: StdHashMap<PathBuf, String>,
|
|
2215
2270
|
dirs: StdHashSet<PathBuf>,
|
|
2271
|
+
subtrees: StdHashMap<PathBuf, Vec<(PathBuf, String)>>,
|
|
2272
|
+
subtree_walks: Mutex<Vec<PathBuf>>,
|
|
2216
2273
|
}
|
|
2217
2274
|
|
|
2218
2275
|
impl FakeFs {
|
|
@@ -2228,6 +2285,22 @@ mod internal_tests {
|
|
|
2228
2285
|
self
|
|
2229
2286
|
}
|
|
2230
2287
|
|
|
2288
|
+
/// Register a directory together with what the scanner's walk of it
|
|
2289
|
+
/// would return; each file is also stat-able as a regular file.
|
|
2290
|
+
fn with_subtree(mut self, dir: impl Into<PathBuf>, found: Vec<(PathBuf, String)>) -> Self {
|
|
2291
|
+
let dir = dir.into();
|
|
2292
|
+
self.dirs.insert(dir.clone());
|
|
2293
|
+
for (path, _) in &found {
|
|
2294
|
+
self.stats.insert(path.clone(), fake_stat());
|
|
2295
|
+
}
|
|
2296
|
+
self.subtrees.insert(dir, found);
|
|
2297
|
+
self
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
fn subtree_walks(&self) -> Vec<PathBuf> {
|
|
2301
|
+
self.subtree_walks.lock().unwrap().clone()
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2231
2304
|
fn set_hash(&mut self, path: impl Into<PathBuf>, hash: [u8; 32]) {
|
|
2232
2305
|
self.hashes.insert(path.into(), hash);
|
|
2233
2306
|
}
|
|
@@ -2263,6 +2336,29 @@ mod internal_tests {
|
|
|
2263
2336
|
))
|
|
2264
2337
|
}
|
|
2265
2338
|
|
|
2339
|
+
fn is_dir(&self, path: &Path) -> std::io::Result<bool> {
|
|
2340
|
+
if self.dirs.contains(path) {
|
|
2341
|
+
return Ok(true);
|
|
2342
|
+
}
|
|
2343
|
+
if self.stats.contains_key(path) {
|
|
2344
|
+
return Ok(false);
|
|
2345
|
+
}
|
|
2346
|
+
Err(std::io::Error::new(
|
|
2347
|
+
std::io::ErrorKind::NotFound,
|
|
2348
|
+
"fake: no such file",
|
|
2349
|
+
))
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
fn scan_subtree(
|
|
2353
|
+
&self,
|
|
2354
|
+
_root: &Path,
|
|
2355
|
+
dir: &Path,
|
|
2356
|
+
_matcher: &TableMatcher,
|
|
2357
|
+
) -> Vec<(PathBuf, String)> {
|
|
2358
|
+
self.subtree_walks.lock().unwrap().push(dir.to_path_buf());
|
|
2359
|
+
self.subtrees.get(dir).cloned().unwrap_or_default()
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2266
2362
|
fn hash(&self, path: &Path) -> std::io::Result<[u8; 32]> {
|
|
2267
2363
|
self.hashes.get(path).copied().ok_or_else(|| {
|
|
2268
2364
|
std::io::Error::new(std::io::ErrorKind::NotFound, "fake: no such file")
|
|
@@ -2517,6 +2613,144 @@ mod internal_tests {
|
|
|
2517
2613
|
assert_eq!(events.len(), 1, "non-ignored path must produce one event");
|
|
2518
2614
|
}
|
|
2519
2615
|
|
|
2616
|
+
fn subtree_db(fake: FakeFs, root: &Path, ignore: Vec<&str>) -> DirSQL {
|
|
2617
|
+
DirSQL::with_ignore_and_fs(
|
|
2618
|
+
root,
|
|
2619
|
+
vec![Table::new(
|
|
2620
|
+
"items",
|
|
2621
|
+
"CREATE TABLE items (name TEXT)",
|
|
2622
|
+
"**/*.txt",
|
|
2623
|
+
|path| {
|
|
2624
|
+
vec![Row::from_iter([(
|
|
2625
|
+
"name".to_string(),
|
|
2626
|
+
Value::Text(path.to_string()),
|
|
2627
|
+
)])]
|
|
2628
|
+
},
|
|
2629
|
+
)],
|
|
2630
|
+
ignore,
|
|
2631
|
+
Arc::new(fake),
|
|
2632
|
+
)
|
|
2633
|
+
.unwrap()
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
/// A created directory is indexed by walking it: every file the scanner
|
|
2637
|
+
/// reports beneath it becomes rows, as if each had its own create event.
|
|
2638
|
+
#[test]
|
|
2639
|
+
fn process_file_event_indexes_the_files_beneath_a_created_directory() {
|
|
2640
|
+
let dir = TempDir::new().unwrap();
|
|
2641
|
+
let moved = dir.path().join("moved");
|
|
2642
|
+
let a = moved.join("a.txt");
|
|
2643
|
+
let deep = moved.join("one").join("deep.txt");
|
|
2644
|
+
let fake = FakeFs::default().with_subtree(
|
|
2645
|
+
moved.clone(),
|
|
2646
|
+
vec![(a.clone(), "items".into()), (deep.clone(), "items".into())],
|
|
2647
|
+
);
|
|
2648
|
+
let db = subtree_db(fake, dir.path(), Vec::new());
|
|
2649
|
+
|
|
2650
|
+
let events = db.process_file_event(FileEvent::Created(moved));
|
|
2651
|
+
|
|
2652
|
+
assert_eq!(events.len(), 2, "one insert per file found: {events:?}");
|
|
2653
|
+
assert!(
|
|
2654
|
+
events.iter().all(|e| matches!(e, RowEvent::Insert { .. })),
|
|
2655
|
+
"got: {events:?}"
|
|
2656
|
+
);
|
|
2657
|
+
let paths: Vec<String> = events
|
|
2658
|
+
.iter()
|
|
2659
|
+
.filter_map(|e| match e {
|
|
2660
|
+
RowEvent::Insert { file_path, .. } => Some(file_path.clone()),
|
|
2661
|
+
_ => None,
|
|
2662
|
+
})
|
|
2663
|
+
.collect();
|
|
2664
|
+
assert_eq!(
|
|
2665
|
+
paths,
|
|
2666
|
+
vec!["moved/a.txt".to_string(), "moved/one/deep.txt".to_string()],
|
|
2667
|
+
"file paths are root-relative"
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
/// The walk is matched against the root the event was stripped with, so
|
|
2672
|
+
/// the rows it writes carry the same relative paths a create event would.
|
|
2673
|
+
#[test]
|
|
2674
|
+
fn process_file_event_walks_a_created_directory_under_the_watch_root() {
|
|
2675
|
+
let root = PathBuf::from("/ws");
|
|
2676
|
+
let moved = root.join("moved");
|
|
2677
|
+
let fake = FakeFs::default()
|
|
2678
|
+
.with_canonical_root(&root, "/ws")
|
|
2679
|
+
.with_subtree(moved.clone(), vec![(moved.join("a.txt"), "items".into())]);
|
|
2680
|
+
let db = subtree_db(fake, &root, Vec::new());
|
|
2681
|
+
|
|
2682
|
+
let events = db.process_file_event(FileEvent::Created(moved.clone()));
|
|
2683
|
+
|
|
2684
|
+
assert!(
|
|
2685
|
+
matches!(&events[..], [RowEvent::Insert { file_path, .. }] if file_path == "moved/a.txt"),
|
|
2686
|
+
"got: {events:?}"
|
|
2687
|
+
);
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
/// Only a *created* directory is walked. A metadata change on one says
|
|
2691
|
+
/// nothing about its files, and walking on every such event would re-run
|
|
2692
|
+
/// `on_file` across the whole subtree for a `chmod`.
|
|
2693
|
+
#[test]
|
|
2694
|
+
fn process_file_event_does_not_walk_a_modified_directory() {
|
|
2695
|
+
let dir = TempDir::new().unwrap();
|
|
2696
|
+
let sub = dir.path().join("sub");
|
|
2697
|
+
let fake =
|
|
2698
|
+
FakeFs::default().with_subtree(sub.clone(), vec![(sub.join("a.txt"), "items".into())]);
|
|
2699
|
+
let fake = Arc::new(fake);
|
|
2700
|
+
let db = DirSQL::with_ignore_and_fs(
|
|
2701
|
+
dir.path(),
|
|
2702
|
+
vec![Table::new(
|
|
2703
|
+
"items",
|
|
2704
|
+
"CREATE TABLE items (name TEXT)",
|
|
2705
|
+
"**/*.txt",
|
|
2706
|
+
|_| vec![],
|
|
2707
|
+
)],
|
|
2708
|
+
Vec::<String>::new(),
|
|
2709
|
+
fake.clone(),
|
|
2710
|
+
)
|
|
2711
|
+
.unwrap();
|
|
2712
|
+
|
|
2713
|
+
let events = db.process_file_event(FileEvent::Modified(sub.clone()));
|
|
2714
|
+
|
|
2715
|
+
assert!(events.is_empty(), "got: {events:?}");
|
|
2716
|
+
assert!(
|
|
2717
|
+
fake.subtree_walks().is_empty(),
|
|
2718
|
+
"a modified directory must not be walked; walked: {:?}",
|
|
2719
|
+
fake.subtree_walks()
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
/// An ignored directory is never walked, matching the initial scan.
|
|
2724
|
+
#[test]
|
|
2725
|
+
fn process_file_event_does_not_walk_an_ignored_created_directory() {
|
|
2726
|
+
let dir = TempDir::new().unwrap();
|
|
2727
|
+
let skip = dir.path().join("skip");
|
|
2728
|
+
let fake = Arc::new(
|
|
2729
|
+
FakeFs::default()
|
|
2730
|
+
.with_subtree(skip.clone(), vec![(skip.join("a.txt"), "items".into())]),
|
|
2731
|
+
);
|
|
2732
|
+
let db = DirSQL::with_ignore_and_fs(
|
|
2733
|
+
dir.path(),
|
|
2734
|
+
vec![Table::new(
|
|
2735
|
+
"items",
|
|
2736
|
+
"CREATE TABLE items (name TEXT)",
|
|
2737
|
+
"**/*.txt",
|
|
2738
|
+
|_| vec![],
|
|
2739
|
+
)],
|
|
2740
|
+
vec!["skip"],
|
|
2741
|
+
fake.clone(),
|
|
2742
|
+
)
|
|
2743
|
+
.unwrap();
|
|
2744
|
+
|
|
2745
|
+
let events = db.process_file_event(FileEvent::Created(skip));
|
|
2746
|
+
|
|
2747
|
+
assert!(events.is_empty(), "got: {events:?}");
|
|
2748
|
+
assert!(
|
|
2749
|
+
fake.subtree_walks().is_empty(),
|
|
2750
|
+
"ignored directory was walked"
|
|
2751
|
+
);
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2520
2754
|
/// Building with a **relative** root canonicalizes `watch_root` to an
|
|
2521
2755
|
/// absolute path while leaving `root` exactly as the caller supplied it,
|
|
2522
2756
|
/// so `notify` never sees `.`.
|
|
@@ -2773,6 +3007,19 @@ mod internal_tests {
|
|
|
2773
3007
|
fs.is_file(&missing).is_err(),
|
|
2774
3008
|
"is_file of a missing path must error"
|
|
2775
3009
|
);
|
|
3010
|
+
assert!(
|
|
3011
|
+
fs.is_dir(dir.path()).unwrap(),
|
|
3012
|
+
"a directory must report is_dir=true"
|
|
3013
|
+
);
|
|
3014
|
+
assert!(
|
|
3015
|
+
fs.is_dir(&missing).is_err(),
|
|
3016
|
+
"is_dir of a missing path must error"
|
|
3017
|
+
);
|
|
3018
|
+
let matcher = TableMatcher::new(&[("**/*", "files")], &[]).unwrap();
|
|
3019
|
+
assert!(
|
|
3020
|
+
fs.scan_subtree(dir.path(), dir.path(), &matcher).is_empty(),
|
|
3021
|
+
"an empty directory walks to nothing"
|
|
3022
|
+
);
|
|
2776
3023
|
// A nonexistent path can't canonicalize, so the literal fallback runs.
|
|
2777
3024
|
assert_eq!(
|
|
2778
3025
|
fs.canonical_root(&missing),
|
|
@@ -34,11 +34,31 @@ pub fn scan_directory_reporting(
|
|
|
34
34
|
root: &Path,
|
|
35
35
|
matcher: &TableMatcher,
|
|
36
36
|
on_file: &mut dyn FnMut(u64),
|
|
37
|
+
) -> Vec<(PathBuf, String)> {
|
|
38
|
+
scan_below(root, root, matcher, on_file)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// [`scan_directory`] restricted to the subtree at `dir`, a directory beneath
|
|
42
|
+
/// `root`. Paths are still matched and ignored relative to `root`, so the
|
|
43
|
+
/// result is exactly the slice of a full scan that falls under `dir`.
|
|
44
|
+
///
|
|
45
|
+
/// This is how the live watcher indexes a directory that appears whole — a
|
|
46
|
+
/// `mkdir` or a rename into the tree — since the OS reports one event for the
|
|
47
|
+
/// directory and none for the files already inside it.
|
|
48
|
+
pub fn scan_subtree(root: &Path, dir: &Path, matcher: &TableMatcher) -> Vec<(PathBuf, String)> {
|
|
49
|
+
scan_below(root, dir, matcher, &mut |_| {})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn scan_below(
|
|
53
|
+
root: &Path,
|
|
54
|
+
start: &Path,
|
|
55
|
+
matcher: &TableMatcher,
|
|
56
|
+
on_file: &mut dyn FnMut(u64),
|
|
37
57
|
) -> Vec<(PathBuf, String)> {
|
|
38
58
|
let mut results = Vec::new();
|
|
39
59
|
let mut seen: u64 = 0;
|
|
40
60
|
|
|
41
|
-
for entry in walk(root, matcher, Path::new(""), false) {
|
|
61
|
+
for entry in walk(root, start, matcher, Path::new(""), false) {
|
|
42
62
|
let path = entry.path();
|
|
43
63
|
|
|
44
64
|
// Match against relative path so globs like "comments/**/*.jsonl" work
|
|
@@ -92,7 +112,7 @@ pub fn scan_glob(
|
|
|
92
112
|
) -> Vec<PathBuf> {
|
|
93
113
|
let mut results = Vec::new();
|
|
94
114
|
|
|
95
|
-
for entry in walk(root, ignore, ignore_base, gitignore) {
|
|
115
|
+
for entry in walk(root, root, ignore, ignore_base, gitignore) {
|
|
96
116
|
if !entry.file_type().is_file() {
|
|
97
117
|
continue;
|
|
98
118
|
}
|
|
@@ -145,6 +165,7 @@ pub(crate) fn parse_gitignore_arg(arg: &str) -> Result<bool, String> {
|
|
|
145
165
|
/// entries a `.gitignore` in force ignores are pruned/skipped too.
|
|
146
166
|
fn walk<'a>(
|
|
147
167
|
root: &Path,
|
|
168
|
+
start: &Path,
|
|
148
169
|
ignore: &'a TableMatcher,
|
|
149
170
|
ignore_base: &'a Path,
|
|
150
171
|
gitignore: bool,
|
|
@@ -154,13 +175,16 @@ fn walk<'a>(
|
|
|
154
175
|
// first. `filter_entry` visits a directory before its children and the
|
|
155
176
|
// children before the next sibling, so a depth-keyed stack tracks scope.
|
|
156
177
|
let mut frames: Vec<GitignoreFrame> = Vec::new();
|
|
157
|
-
WalkDir::new(
|
|
178
|
+
WalkDir::new(start)
|
|
158
179
|
.into_iter()
|
|
159
180
|
.filter_entry(move |entry| {
|
|
160
181
|
let rel_path = entry.path().strip_prefix(&root).unwrap_or(entry.path());
|
|
182
|
+
// Depth below `root`, not below `start`: the reserved-directory
|
|
183
|
+
// rule is about the tree's top level wherever the walk begins.
|
|
184
|
+
let depth = rel_path.components().count();
|
|
161
185
|
let is_dir = entry.file_type().is_dir();
|
|
162
186
|
if !should_descend(
|
|
163
|
-
|
|
187
|
+
depth,
|
|
164
188
|
is_dir,
|
|
165
189
|
entry.file_name(),
|
|
166
190
|
rel_path,
|
|
@@ -172,7 +196,7 @@ fn walk<'a>(
|
|
|
172
196
|
if !gitignore {
|
|
173
197
|
return true;
|
|
174
198
|
}
|
|
175
|
-
while frames.last().is_some_and(|f| f.depth >=
|
|
199
|
+
while frames.last().is_some_and(|f| f.depth >= depth) {
|
|
176
200
|
frames.pop();
|
|
177
201
|
}
|
|
178
202
|
if is_gitignored(&frames, entry.path(), rel_path, is_dir, ignore_base) {
|
|
@@ -180,7 +204,7 @@ fn walk<'a>(
|
|
|
180
204
|
}
|
|
181
205
|
if is_dir && let Some(matcher) = load_gitignore(entry.path()) {
|
|
182
206
|
frames.push(GitignoreFrame {
|
|
183
|
-
depth
|
|
207
|
+
depth,
|
|
184
208
|
dir: rel_path.to_path_buf(),
|
|
185
209
|
matcher,
|
|
186
210
|
});
|
|
@@ -79,6 +79,13 @@ fn translate_event(event: &Event) -> Vec<FileEvent> {
|
|
|
79
79
|
EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
|
|
80
80
|
Some(FileEvent::Deleted(path.clone()))
|
|
81
81
|
}
|
|
82
|
+
// inotify follows a matched `From`/`To` pair with a `Both` naming
|
|
83
|
+
// the same two paths; both sides were already translated above.
|
|
84
|
+
EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => None,
|
|
85
|
+
// A rename INTO the tree puts a path here that did not exist a
|
|
86
|
+
// moment ago — a creation, and for a directory one the watcher must
|
|
87
|
+
// walk, since the OS reports nothing for the files already inside.
|
|
88
|
+
EventKind::Modify(ModifyKind::Name(_)) => Some(FileEvent::Created(path.clone())),
|
|
82
89
|
EventKind::Modify(_) => Some(FileEvent::Modified(path.clone())),
|
|
83
90
|
EventKind::Remove(_) => Some(FileEvent::Deleted(path.clone())),
|
|
84
91
|
_ => None,
|
|
@@ -135,6 +142,39 @@ fn rename_from_event(paths: Vec<PathBuf>) -> Event {
|
|
|
135
142
|
}
|
|
136
143
|
}
|
|
137
144
|
|
|
145
|
+
#[cfg(test)]
|
|
146
|
+
fn rename_to_event(paths: Vec<PathBuf>) -> Event {
|
|
147
|
+
Event {
|
|
148
|
+
kind: EventKind::Modify(notify::event::ModifyKind::Name(
|
|
149
|
+
notify::event::RenameMode::To,
|
|
150
|
+
)),
|
|
151
|
+
paths,
|
|
152
|
+
attrs: Default::default(),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#[cfg(test)]
|
|
157
|
+
fn rename_any_event(paths: Vec<PathBuf>) -> Event {
|
|
158
|
+
Event {
|
|
159
|
+
kind: EventKind::Modify(notify::event::ModifyKind::Name(
|
|
160
|
+
notify::event::RenameMode::Any,
|
|
161
|
+
)),
|
|
162
|
+
paths,
|
|
163
|
+
attrs: Default::default(),
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[cfg(test)]
|
|
168
|
+
fn rename_both_event(paths: Vec<PathBuf>) -> Event {
|
|
169
|
+
Event {
|
|
170
|
+
kind: EventKind::Modify(notify::event::ModifyKind::Name(
|
|
171
|
+
notify::event::RenameMode::Both,
|
|
172
|
+
)),
|
|
173
|
+
paths,
|
|
174
|
+
attrs: Default::default(),
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
138
178
|
#[cfg(test)]
|
|
139
179
|
fn access_event(paths: Vec<PathBuf>) -> Event {
|
|
140
180
|
Event {
|
|
@@ -196,6 +236,39 @@ mod tests {
|
|
|
196
236
|
);
|
|
197
237
|
}
|
|
198
238
|
|
|
239
|
+
#[test]
|
|
240
|
+
fn translate_event_maps_rename_to_as_create() {
|
|
241
|
+
// inotify emits `Modify(Name(To))` when a path is renamed INTO the
|
|
242
|
+
// watched tree; it is a creation so a moved-in directory gets walked.
|
|
243
|
+
let results = translate_event(&rename_to_event(vec![PathBuf::from("/tmp/arrived")]));
|
|
244
|
+
assert_eq!(
|
|
245
|
+
results,
|
|
246
|
+
vec![FileEvent::Created(PathBuf::from("/tmp/arrived"))]
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[test]
|
|
251
|
+
fn translate_event_maps_rename_any_as_create() {
|
|
252
|
+
// kqueue / FSEvents cannot tell which side of a rename they saw; a
|
|
253
|
+
// creation of a path that turns out not to exist is a harmless no-op.
|
|
254
|
+
let results = translate_event(&rename_any_event(vec![PathBuf::from("/tmp/either")]));
|
|
255
|
+
assert_eq!(
|
|
256
|
+
results,
|
|
257
|
+
vec![FileEvent::Created(PathBuf::from("/tmp/either"))]
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
#[test]
|
|
262
|
+
fn translate_event_ignores_rename_both() {
|
|
263
|
+
// `Both` restates a `From`/`To` pair inotify already delivered; acting
|
|
264
|
+
// on it would index the destination twice.
|
|
265
|
+
let results = translate_event(&rename_both_event(vec![
|
|
266
|
+
PathBuf::from("/tmp/old"),
|
|
267
|
+
PathBuf::from("/tmp/new"),
|
|
268
|
+
]));
|
|
269
|
+
assert!(results.is_empty());
|
|
270
|
+
}
|
|
271
|
+
|
|
199
272
|
#[test]
|
|
200
273
|
fn translate_event_multiple_paths() {
|
|
201
274
|
let results = translate_event(&create_event(vec![
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|