dirsql 0.4.57__tar.gz → 0.4.58__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.
Files changed (251) hide show
  1. {dirsql-0.4.57 → dirsql-0.4.58}/Cargo.lock +9 -2
  2. {dirsql-0.4.57 → dirsql-0.4.58}/PKG-INFO +1 -1
  3. {dirsql-0.4.57/packages/python → dirsql-0.4.58}/docs/reference/path-tables.md +6 -0
  4. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/Cargo.toml +1 -1
  5. {dirsql-0.4.57 → dirsql-0.4.58/packages/python}/docs/reference/path-tables.md +6 -0
  6. dirsql-0.4.58/packages/python/e2e-attestations/claude-1185-win-abs-table-names.json +7 -0
  7. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/Cargo.toml +2 -1
  8. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/reference/path-tables.md +6 -0
  9. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/path_table.rs +316 -35
  10. {dirsql-0.4.57 → dirsql-0.4.58}/Cargo.toml +0 -0
  11. {dirsql-0.4.57 → dirsql-0.4.58}/README.md +0 -0
  12. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/__init__.py +0 -0
  13. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/_async.py +0 -0
  14. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/_dirsql.pyi +0 -0
  15. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/cli/__init__.py +0 -0
  16. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/cli/discover_plugins/__init__.py +0 -0
  17. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  18. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  19. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  20. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
  21. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  22. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/cli/main.py +0 -0
  23. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/cli/resolve_config_extensions.py +0 -0
  24. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/plan_entry_path.py +0 -0
  25. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/py.typed +0 -0
  26. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/read_config.py +0 -0
  27. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/resolve_configs_extension_specs.py +0 -0
  28. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/resolve_extension.py +0 -0
  29. {dirsql-0.4.57 → dirsql-0.4.58}/dirsql/resolve_package.py +0 -0
  30. {dirsql-0.4.57 → dirsql-0.4.58}/docs/.claude/CLAUDE.md +0 -0
  31. {dirsql-0.4.57 → dirsql-0.4.58}/docs/.vitepress/config.ts +0 -0
  32. {dirsql-0.4.57 → dirsql-0.4.58}/docs/.vitepress/theme/index.ts +0 -0
  33. {dirsql-0.4.57 → dirsql-0.4.58}/docs/.vitepress/theme/lang.ts +0 -0
  34. {dirsql-0.4.57 → dirsql-0.4.58}/docs/AGENTS.md +0 -0
  35. {dirsql-0.4.57 → dirsql-0.4.58}/docs/explanation.md +0 -0
  36. {dirsql-0.4.57 → dirsql-0.4.58}/docs/getting-started.md +0 -0
  37. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/columns-from-paths.md +0 -0
  38. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/define-tables.md +0 -0
  39. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/embed.md +0 -0
  40. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/extract-from-contents.md +0 -0
  41. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/load-extension.md +0 -0
  42. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/parse-files-into-columns.md +0 -0
  43. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/persist.md +0 -0
  44. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/query-json.md +0 -0
  45. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/query-without-config.md +0 -0
  46. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/react-to-changes.md +0 -0
  47. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/search-by-meaning.md +0 -0
  48. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/search-indexes.md +0 -0
  49. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/skip-files.md +0 -0
  50. {dirsql-0.4.57 → dirsql-0.4.58}/docs/howto/write-a-plugin.md +0 -0
  51. {dirsql-0.4.57 → dirsql-0.4.58}/docs/index.md +0 -0
  52. {dirsql-0.4.57 → dirsql-0.4.58}/docs/package.json +0 -0
  53. {dirsql-0.4.57 → dirsql-0.4.58}/docs/plugins.md +0 -0
  54. {dirsql-0.4.57 → dirsql-0.4.58}/docs/pnpm-lock.yaml +0 -0
  55. {dirsql-0.4.57 → dirsql-0.4.58}/docs/pnpm-workspace.yaml +0 -0
  56. {dirsql-0.4.57 → dirsql-0.4.58}/docs/reference/cli.md +0 -0
  57. {dirsql-0.4.57 → dirsql-0.4.58}/docs/reference/columns.md +0 -0
  58. {dirsql-0.4.57 → dirsql-0.4.58}/docs/reference/config.md +0 -0
  59. {dirsql-0.4.57 → dirsql-0.4.58}/docs/reference/hooks.md +0 -0
  60. {dirsql-0.4.57 → dirsql-0.4.58}/docs/reference/http-api.md +0 -0
  61. {dirsql-0.4.57 → dirsql-0.4.58}/docs/reference/sdk.md +0 -0
  62. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/CHANGELOG.md +0 -0
  63. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/MIGRATIONS.md +0 -0
  64. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/README.md +0 -0
  65. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  66. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  67. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  68. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  69. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  70. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  71. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  72. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  73. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  74. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
  75. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
  76. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
  77. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
  78. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
  79. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
  80. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
  81. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
  82. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
  83. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
  84. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
  85. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
  86. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
  87. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
  88. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
  89. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-17-extension-resolution-via-core.md +0 -0
  90. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-18-export-scan-failure.md +0 -0
  91. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-18-flatten-row-event-in-core.md +0 -0
  92. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-18-programmatic-extension-plan.md +0 -0
  93. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
  94. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/2026-09-24-query-rows-in-projection-order.md +0 -0
  95. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/changelog.d/README.md +0 -0
  96. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/conftest.py +0 -0
  97. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/.claude/CLAUDE.md +0 -0
  98. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/.vitepress/config.ts +0 -0
  99. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  100. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  101. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/AGENTS.md +0 -0
  102. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/explanation.md +0 -0
  103. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/getting-started.md +0 -0
  104. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/columns-from-paths.md +0 -0
  105. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/define-tables.md +0 -0
  106. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/embed.md +0 -0
  107. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/extract-from-contents.md +0 -0
  108. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/load-extension.md +0 -0
  109. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
  110. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/persist.md +0 -0
  111. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/query-json.md +0 -0
  112. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/query-without-config.md +0 -0
  113. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/react-to-changes.md +0 -0
  114. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/search-by-meaning.md +0 -0
  115. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/search-indexes.md +0 -0
  116. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/skip-files.md +0 -0
  117. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/howto/write-a-plugin.md +0 -0
  118. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/index.md +0 -0
  119. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/package.json +0 -0
  120. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/plugins.md +0 -0
  121. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/pnpm-lock.yaml +0 -0
  122. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/pnpm-workspace.yaml +0 -0
  123. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/reference/cli.md +0 -0
  124. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/reference/columns.md +0 -0
  125. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/reference/config.md +0 -0
  126. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/reference/hooks.md +0 -0
  127. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/reference/http-api.md +0 -0
  128. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/docs/reference/sdk.md +0 -0
  129. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
  130. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
  131. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
  132. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
  133. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
  134. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +0 -0
  135. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1081-indicatif-progress.json +0 -0
  136. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
  137. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +0 -0
  138. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1119-ext-plan.json +0 -0
  139. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1120-py-ext.json +0 -0
  140. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1122-ext-cleanup.json +0 -0
  141. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1130-codec-crates.json +0 -0
  142. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1131-flatten-row-event.json +0 -0
  143. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1139-sse-flatten.json +0 -0
  144. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1140-scan-failure-export.json +0 -0
  145. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1148-clippy-all-targets.json +0 -0
  146. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1164-sigint-fatal.json +0 -0
  147. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1170-projection-order.json +0 -0
  148. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1172-on-file-path.json +0 -0
  149. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-1173-py-projection-order.json +0 -0
  150. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
  151. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
  152. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
  153. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
  154. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
  155. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
  156. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
  157. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
  158. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
  159. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
  160. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
  161. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
  162. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
  163. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
  164. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
  165. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  166. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  167. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  168. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  169. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  170. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  171. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  172. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
  173. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
  174. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
  175. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
  176. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/2026-09-24-query-rows-in-projection-order.md +0 -0
  177. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/migrations.d/README.md +0 -0
  178. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/src/lib.rs +0 -0
  179. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/testing-conventions.toml +0 -0
  180. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/tests/__init__.py +0 -0
  181. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/tests/conftest.py +0 -0
  182. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/tests/e2e/__init__.py +0 -0
  183. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  184. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  185. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/tests/e2e/speed_of_light.py +0 -0
  186. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/tests/integration/__init__.py +0 -0
  187. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/tests/integration/binding/__init__.py +0 -0
  188. {dirsql-0.4.57 → dirsql-0.4.58}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  189. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/CHANGELOG.md +0 -0
  190. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/MIGRATIONS.md +0 -0
  191. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/README.md +0 -0
  192. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/benches/db_bench.rs +0 -0
  193. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/benches/differ_bench.rs +0 -0
  194. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/benches/matcher_bench.rs +0 -0
  195. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/benches/scanner_bench.rs +0 -0
  196. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/explanation.md +0 -0
  197. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/getting-started.md +0 -0
  198. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  199. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/define-tables.md +0 -0
  200. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/embed.md +0 -0
  201. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  202. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/load-extension.md +0 -0
  203. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
  204. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/persist.md +0 -0
  205. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/query-json.md +0 -0
  206. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/query-without-config.md +0 -0
  207. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/react-to-changes.md +0 -0
  208. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  209. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/search-indexes.md +0 -0
  210. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/skip-files.md +0 -0
  211. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  212. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/index.md +0 -0
  213. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/plugins.md +0 -0
  214. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/reference/cli.md +0 -0
  215. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/reference/columns.md +0 -0
  216. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/reference/config.md +0 -0
  217. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/reference/hooks.md +0 -0
  218. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/reference/http-api.md +0 -0
  219. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/docs/reference/sdk.md +0 -0
  220. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/bin/dirsql.rs +0 -0
  221. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/cli/execute.rs +0 -0
  222. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/cli/init.rs +0 -0
  223. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/cli/mod.rs +0 -0
  224. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/cli/repl.rs +0 -0
  225. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/cli/router.rs +0 -0
  226. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/cli/run.rs +0 -0
  227. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/cli/serialize.rs +0 -0
  228. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/cli/server.rs +0 -0
  229. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/cli/table.rs +0 -0
  230. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/command.rs +0 -0
  231. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/config.rs +0 -0
  232. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/db.rs +0 -0
  233. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/default_config.toml +0 -0
  234. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/differ.rs +0 -0
  235. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/extension_resolution.rs +0 -0
  236. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/functions.rs +0 -0
  237. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/infer.rs +0 -0
  238. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/launcher.rs +0 -0
  239. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/lib.rs +0 -0
  240. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/matcher.rs +0 -0
  241. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/parsed_cache.rs +0 -0
  242. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/parsed_vtab.rs +0 -0
  243. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/persist.rs +0 -0
  244. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/progress.rs +0 -0
  245. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/row_event_flat.rs +0 -0
  246. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/scanner.rs +0 -0
  247. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/sql_literal.rs +0 -0
  248. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/vtab.rs +0 -0
  249. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/vtab_scaffold.rs +0 -0
  250. {dirsql-0.4.57 → dirsql-0.4.58}/packages/rust/src/watcher.rs +0 -0
  251. {dirsql-0.4.57 → dirsql-0.4.58}/pyproject.toml +0 -0
@@ -552,7 +552,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
552
552
 
553
553
  [[package]]
554
554
  name = "dirsql"
555
- version = "0.4.57"
555
+ version = "0.4.58"
556
556
  dependencies = [
557
557
  "assert_cmd",
558
558
  "axum",
@@ -584,6 +584,7 @@ dependencies = [
584
584
  "tokio",
585
585
  "tokio-stream",
586
586
  "toml",
587
+ "typed-path",
587
588
  "walkdir",
588
589
  ]
589
590
 
@@ -600,7 +601,7 @@ dependencies = [
600
601
 
601
602
  [[package]]
602
603
  name = "dirsql-py-ext"
603
- version = "0.4.57"
604
+ version = "0.4.58"
604
605
  dependencies = [
605
606
  "dirsql",
606
607
  "pyo3",
@@ -2915,6 +2916,12 @@ version = "0.2.5"
2915
2916
  source = "registry+https://github.com/rust-lang/crates.io-index"
2916
2917
  checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
2917
2918
 
2919
+ [[package]]
2920
+ name = "typed-path"
2921
+ version = "0.12.3"
2922
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2923
+ checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
2924
+
2918
2925
  [[package]]
2919
2926
  name = "unicase"
2920
2927
  version = "2.9.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.4.57
3
+ Version: 0.4.58
4
4
  Requires-Dist: bin-shim>=0.1
5
5
  Summary: Ephemeral SQL index over a local directory
6
6
  Keywords: sql,filesystem,directory,sqlite,index
@@ -96,6 +96,12 @@ SELECT path FROM '/var/log/*.log';
96
96
  -- /var/log/syslog
97
97
  ```
98
98
 
99
+ On Windows, the absolute forms also include a drive letter
100
+ (`'C:\logs\*.log'` or `'C:/logs/*.log'`), a UNC share
101
+ (`'\\server\share\*.md'`) and `'~\notes'`. `\` and `/` both separate there,
102
+ and the `path` these report uses `/` throughout (`C:/logs/app.log`), matching
103
+ the `/`-separated relative paths.
104
+
99
105
  On a system with no home directory, a `~/` path-table reports that it cannot
100
106
  resolve rather than guessing.
101
107
 
@@ -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.57"
7
+ version = "0.4.58"
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
@@ -96,6 +96,12 @@ SELECT path FROM '/var/log/*.log';
96
96
  -- /var/log/syslog
97
97
  ```
98
98
 
99
+ On Windows, the absolute forms also include a drive letter
100
+ (`'C:\logs\*.log'` or `'C:/logs/*.log'`), a UNC share
101
+ (`'\\server\share\*.md'`) and `'~\notes'`. `\` and `/` both separate there,
102
+ and the `path` these report uses `/` throughout (`C:/logs/app.log`), matching
103
+ the `/`-separated relative paths.
104
+
99
105
  On a system with no home directory, a `~/` path-table reports that it cannot
100
106
  resolve rather than guessing.
101
107
 
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "just test-e2e",
3
+ "ran_at": 1790604714,
4
+ "exit_code": 0,
5
+ "commit": "f719e49b96e8610e5692f76ebfb564ae3188d04e",
6
+ "branch": "claude/1185-win-abs-table-names"
7
+ }
@@ -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.57"
9
+ version = "0.4.58"
10
10
  edition.workspace = true
11
11
  # Literal `license` (not `license.workspace = true`) because
12
12
  # putitoutthere's preflight check reads `[package].license` directly
@@ -86,6 +86,7 @@ thiserror.workspace = true
86
86
  tokio.workspace = true
87
87
  tokio-stream = { version = "0.1", features = ["sync"], optional = true }
88
88
  toml = "0.8"
89
+ typed-path = "0.12"
89
90
  walkdir = "2"
90
91
 
91
92
  [lints.rust]
@@ -96,6 +96,12 @@ SELECT path FROM '/var/log/*.log';
96
96
  -- /var/log/syslog
97
97
  ```
98
98
 
99
+ On Windows, the absolute forms also include a drive letter
100
+ (`'C:\logs\*.log'` or `'C:/logs/*.log'`), a UNC share
101
+ (`'\\server\share\*.md'`) and `'~\notes'`. `\` and `/` both separate there,
102
+ and the `path` these report uses `/` throughout (`C:/logs/app.log`), matching
103
+ the `/`-separated relative paths.
104
+
99
105
  On a system with no home directory, a `~/` path-table reports that it cannot
100
106
  resolve rather than guessing.
101
107
 
@@ -4,9 +4,15 @@
4
4
  //!
5
5
  //! Every decision here is a pure function of the written string plus one
6
6
  //! filesystem question (*is this a directory?*), which is injected so the
7
- //! rules can be tested without a filesystem.
7
+ //! rules can be tested without a filesystem. The path syntax is a type
8
+ //! parameter, so the Windows rules are testable on any host.
8
9
 
9
- use std::path::{Component, Path, PathBuf};
10
+ use std::path::{Path, PathBuf};
11
+
12
+ use typed_path::{
13
+ Utf8Component, Utf8Encoding, Utf8NativeEncoding, Utf8Path, Utf8PathBuf, Utf8UnixEncoding,
14
+ Utf8WindowsComponent, Utf8WindowsEncoding, Utf8WindowsPath,
15
+ };
10
16
 
11
17
  /// Directories a path-table scan skips, at any depth. They are skipped only
12
18
  /// *beneath* the literal part of the path you write, so naming one explicitly
@@ -43,6 +49,50 @@ pub enum Resolution {
43
49
  NotAPath,
44
50
  }
45
51
 
52
+ /// The per-platform rules for which written names are absolute and how a
53
+ /// resolved scan root is reported.
54
+ trait Syntax: Utf8Encoding + Sized {
55
+ const HOME_PREFIXES: &'static [&'static str];
56
+
57
+ fn is_rooted(name: &str) -> bool;
58
+
59
+ fn reported(path: &Utf8Path<Self>) -> String;
60
+ }
61
+
62
+ impl Syntax for Utf8UnixEncoding {
63
+ const HOME_PREFIXES: &'static [&'static str] = &["~/"];
64
+
65
+ fn is_rooted(name: &str) -> bool {
66
+ name.starts_with('/')
67
+ }
68
+
69
+ fn reported(path: &Utf8Path<Self>) -> String {
70
+ path.as_str().to_string()
71
+ }
72
+ }
73
+
74
+ impl Syntax for Utf8WindowsEncoding {
75
+ const HOME_PREFIXES: &'static [&'static str] = &["~/", "~\\"];
76
+
77
+ fn is_rooted(name: &str) -> bool {
78
+ Utf8WindowsPath::new(name).has_root()
79
+ }
80
+
81
+ /// Reported with `/`, matching the index-root-relative paths. A verbatim
82
+ /// (`\\?\`) path is the exception: there `/` is not a separator.
83
+ fn reported(path: &Utf8Path<Self>) -> String {
84
+ let verbatim = matches!(
85
+ path.components().next(),
86
+ Some(Utf8WindowsComponent::Prefix(prefix)) if prefix.kind().is_verbatim()
87
+ );
88
+ if verbatim {
89
+ path.as_str().to_string()
90
+ } else {
91
+ path.as_str().replace('\\', "/")
92
+ }
93
+ }
94
+ }
95
+
46
96
  /// Whether `name` contains a character that makes it a glob rather than a
47
97
  /// literal path.
48
98
  fn has_glob_metacharacter(name: &str) -> bool {
@@ -58,6 +108,15 @@ pub fn resolve(
58
108
  index_root: &Path,
59
109
  home: Option<&Path>,
60
110
  is_dir: &dyn Fn(&Path) -> bool,
111
+ ) -> Resolution {
112
+ resolve_as::<Utf8NativeEncoding>(name, index_root, home, is_dir)
113
+ }
114
+
115
+ fn resolve_as<S: Syntax>(
116
+ name: &str,
117
+ index_root: &Path,
118
+ home: Option<&Path>,
119
+ is_dir: &dyn Fn(&Path) -> bool,
61
120
  ) -> Resolution {
62
121
  if let Some(rest) = name.strip_prefix("./") {
63
122
  return Resolution::Table(PathTable {
@@ -67,7 +126,7 @@ pub fn resolve(
67
126
  });
68
127
  }
69
128
 
70
- match absolute_target(name, index_root, home) {
129
+ match absolute_target::<S>(name, index_root, home) {
71
130
  Some(Some(target)) => Resolution::Table(split_absolute(&target, is_dir)),
72
131
  Some(None) => Resolution::NoHome,
73
132
  None if has_glob_metacharacter(name) => Resolution::Hint,
@@ -91,19 +150,27 @@ fn relative_glob(rest: &str, is_dir: &dyn Fn(&Path) -> bool) -> String {
91
150
  trimmed.to_string()
92
151
  }
93
152
 
153
+ fn typed<S: Syntax>(path: &Path) -> Utf8PathBuf<S> {
154
+ Utf8PathBuf::from(path.to_string_lossy().into_owned())
155
+ }
156
+
94
157
  /// The absolute path a non-`./` path-table names, with `.` and `..` folded out.
95
158
  ///
96
159
  /// `None` means the name is not a path at all. `Some(None)` means it is a `~/`
97
160
  /// path but no home directory could be found.
98
- fn absolute_target(name: &str, index_root: &Path, home: Option<&Path>) -> Option<Option<PathBuf>> {
99
- if let Some(rest) = name.strip_prefix("~/") {
100
- return Some(home.map(|h| normalize(&h.join(rest))));
161
+ fn absolute_target<S: Syntax>(
162
+ name: &str,
163
+ index_root: &Path,
164
+ home: Option<&Path>,
165
+ ) -> Option<Option<Utf8PathBuf<S>>> {
166
+ if let Some(rest) = S::HOME_PREFIXES.iter().find_map(|p| name.strip_prefix(p)) {
167
+ return Some(home.map(|h| normalize(&typed::<S>(h).join(rest))));
101
168
  }
102
169
  if name.starts_with("../") {
103
- return Some(Some(normalize(&index_root.join(name))));
170
+ return Some(Some(normalize(&typed::<S>(index_root).join(name))));
104
171
  }
105
- if name.starts_with('/') {
106
- return Some(Some(normalize(Path::new(name))));
172
+ if S::is_rooted(name) {
173
+ return Some(Some(normalize(Utf8Path::new(name))));
107
174
  }
108
175
  None
109
176
  }
@@ -111,21 +178,37 @@ fn absolute_target(name: &str, index_root: &Path, home: Option<&Path>) -> Option
111
178
  /// Fold `.` and `..` out of `path` lexically. Purely textual: a `..` is not
112
179
  /// resolved through a symlink, which keeps the answer a function of the string
113
180
  /// the user wrote.
114
- fn normalize(path: &Path) -> PathBuf {
115
- let mut out = PathBuf::new();
181
+ fn normalize<S: Syntax>(path: &Utf8Path<S>) -> Utf8PathBuf<S> {
182
+ let mut kept: Vec<&str> = Vec::new();
183
+ let mut normal = 0;
184
+ let mut rooted = false;
116
185
  for component in path.components() {
117
- match component {
118
- Component::CurDir => {}
186
+ if component.is_current() {
187
+ continue;
188
+ }
189
+ if component.is_parent() {
119
190
  // A `..` that cannot pop is kept on a relative path (it still
120
191
  // means something) but dropped at the filesystem root, which has
121
192
  // no parent to climb to.
122
- Component::ParentDir => {
123
- if !out.pop() && !out.has_root() {
124
- out.push(component);
125
- }
193
+ if normal > 0 {
194
+ kept.pop();
195
+ normal -= 1;
196
+ } else if !rooted {
197
+ kept.push(component.as_str());
126
198
  }
127
- other => out.push(other),
199
+ continue;
200
+ }
201
+ if component.is_normal() {
202
+ normal += 1;
203
+ } else if component.is_root() {
204
+ rooted = true;
128
205
  }
206
+ kept.push(component.as_str());
207
+ }
208
+
209
+ let mut out = Utf8PathBuf::new();
210
+ for component in kept {
211
+ out.push(component);
129
212
  }
130
213
  out
131
214
  }
@@ -133,43 +216,44 @@ fn normalize(path: &Path) -> PathBuf {
133
216
  /// Split an absolute path-table target into the directory to walk and the glob
134
217
  /// to match beneath it. A wholly literal target is a directory (scan it
135
218
  /// recursively) or a single file (match exactly that name).
136
- fn split_absolute(target: &Path, is_dir: &dyn Fn(&Path) -> bool) -> PathTable {
219
+ fn split_absolute<S: Syntax>(target: &Utf8Path<S>, is_dir: &dyn Fn(&Path) -> bool) -> PathTable {
137
220
  let (literal, rest) = split_at_first_glob(target);
138
221
 
139
222
  if !rest.is_empty() {
140
- return table_at(literal, rest);
223
+ return table_at(&literal, rest);
141
224
  }
142
- if is_dir(&literal) {
143
- return table_at(literal, RECURSIVE_GLOB.to_string());
225
+ if is_dir(Path::new(literal.as_str())) {
226
+ return table_at(&literal, RECURSIVE_GLOB.to_string());
144
227
  }
145
228
 
146
229
  let name = literal
147
230
  .file_name()
148
- .map(|n| n.to_string_lossy().into_owned())
231
+ .map(str::to_string)
149
232
  .unwrap_or_else(|| RECURSIVE_GLOB.to_string());
150
- let parent = literal.parent().unwrap_or(&literal).to_path_buf();
233
+ let parent = literal.parent().unwrap_or(&literal);
151
234
  table_at(parent, name)
152
235
  }
153
236
 
154
237
  /// A path-table rooted at `root`, reporting absolute paths.
155
- fn table_at(root: PathBuf, glob: String) -> PathTable {
238
+ fn table_at<S: Syntax>(root: &Utf8Path<S>, glob: String) -> PathTable {
156
239
  PathTable {
157
- path_prefix: root.to_string_lossy().into_owned(),
158
- root,
240
+ path_prefix: S::reported(root),
241
+ root: PathBuf::from(root.as_str()),
159
242
  glob,
160
243
  }
161
244
  }
162
245
 
163
246
  /// Split `target` at its first glob-bearing component: the literal directory
164
247
  /// chain ahead of it, and the rest as a `/`-joined glob.
165
- fn split_at_first_glob(target: &Path) -> (PathBuf, String) {
166
- let mut literal = PathBuf::new();
167
- let mut rest: Vec<String> = Vec::new();
248
+ fn split_at_first_glob<S: Syntax>(target: &Utf8Path<S>) -> (Utf8PathBuf<S>, String) {
249
+ let mut literal = Utf8PathBuf::new();
250
+ let mut rest: Vec<&str> = Vec::new();
168
251
 
169
252
  for component in target.components() {
170
- let text = component.as_os_str().to_string_lossy().into_owned();
171
- if rest.is_empty() && !has_glob_metacharacter(&text) {
172
- literal.push(component);
253
+ let text = component.as_str();
254
+ let is_glob = component.is_normal() && has_glob_metacharacter(text);
255
+ if rest.is_empty() && !is_glob {
256
+ literal.push(text);
173
257
  } else {
174
258
  rest.push(text);
175
259
  }
@@ -388,12 +472,23 @@ mod tests {
388
472
 
389
473
  #[test]
390
474
  fn normalize_drops_current_directory_components() {
391
- assert_eq!(normalize(Path::new("/a/./b")), PathBuf::from("/a/b"));
475
+ assert_eq!(unix_normalize("/a/./b"), "/a/b");
392
476
  }
393
477
 
394
478
  #[test]
395
479
  fn normalize_keeps_a_leading_parent_it_cannot_pop() {
396
- assert_eq!(normalize(Path::new("../a")), PathBuf::from("../a"));
480
+ assert_eq!(unix_normalize("../a"), "../a");
481
+ }
482
+
483
+ #[test]
484
+ fn normalize_does_not_pop_a_parent_it_kept() {
485
+ assert_eq!(unix_normalize("../../a"), "../../a");
486
+ }
487
+
488
+ fn unix_normalize(path: &str) -> String {
489
+ normalize(Utf8Path::<Utf8UnixEncoding>::new(path))
490
+ .as_str()
491
+ .to_string()
397
492
  }
398
493
 
399
494
  #[test]
@@ -413,4 +508,190 @@ mod tests {
413
508
  fn ignore_base_is_the_whole_of_a_literal_glob() {
414
509
  assert_eq!(ignore_base("docs/a.md"), PathBuf::from("docs/a.md"));
415
510
  }
511
+
512
+ mod windows {
513
+ use super::*;
514
+
515
+ const ROOT: &str = r"C:\index";
516
+
517
+ fn resolve_with(name: &str, is_dir: &dyn Fn(&Path) -> bool) -> Resolution {
518
+ resolve_as::<Utf8WindowsEncoding>(
519
+ name,
520
+ Path::new(ROOT),
521
+ Some(Path::new(r"C:\Users\u")),
522
+ is_dir,
523
+ )
524
+ }
525
+
526
+ fn table(name: &str, is_dir: &dyn Fn(&Path) -> bool) -> PathTable {
527
+ match resolve_with(name, is_dir) {
528
+ Resolution::Table(t) => t,
529
+ other => panic!("expected a path-table, got {other:?}"),
530
+ }
531
+ }
532
+
533
+ #[test]
534
+ fn a_drive_letter_glob_roots_at_its_literal_prefix() {
535
+ let t = table(r"C:\var\log\*.log", &nothing_is_a_dir);
536
+ assert_eq!(t.root, Path::new(r"C:\var\log"));
537
+ assert_eq!(t.glob, "*.log");
538
+ assert_eq!(t.path_prefix, "C:/var/log");
539
+ }
540
+
541
+ #[test]
542
+ fn a_drive_letter_path_may_use_forward_slashes() {
543
+ let t = table("C:/var/log/*.log", &nothing_is_a_dir);
544
+ assert_eq!(t.root, Path::new(r"C:\var\log"));
545
+ assert_eq!(t.path_prefix, "C:/var/log");
546
+ }
547
+
548
+ #[test]
549
+ fn a_mixed_separator_path_splits_on_both() {
550
+ let t = table(r"C:\Users\u\Temp/docs/*.md", &nothing_is_a_dir);
551
+ assert_eq!(t.root, Path::new(r"C:\Users\u\Temp\docs"));
552
+ assert_eq!(t.glob, "*.md");
553
+ assert_eq!(t.path_prefix, "C:/Users/u/Temp/docs");
554
+ }
555
+
556
+ #[test]
557
+ fn a_drive_letter_directory_expands_recursively() {
558
+ let t = table(r"C:\var\log", &everything_is_a_dir);
559
+ assert_eq!(t.root, Path::new(r"C:\var\log"));
560
+ assert_eq!(t.glob, "**/*");
561
+ }
562
+
563
+ #[test]
564
+ fn a_drive_letter_single_file_roots_at_its_parent() {
565
+ let t = table(r"C:\var\log\syslog", &nothing_is_a_dir);
566
+ assert_eq!(t.root, Path::new(r"C:\var\log"));
567
+ assert_eq!(t.glob, "syslog");
568
+ }
569
+
570
+ #[test]
571
+ fn a_drive_root_scans_everything() {
572
+ let t = table(r"C:\", &everything_is_a_dir);
573
+ assert_eq!(t.root, Path::new(r"C:\"));
574
+ assert_eq!(t.glob, "**/*");
575
+ assert_eq!(t.path_prefix, "C:/");
576
+ }
577
+
578
+ #[test]
579
+ fn a_drive_relative_name_is_not_a_path() {
580
+ assert_eq!(
581
+ resolve_with("C:notes", &nothing_is_a_dir),
582
+ Resolution::NotAPath
583
+ );
584
+ }
585
+
586
+ #[test]
587
+ fn a_rooted_path_without_a_drive_is_still_absolute() {
588
+ let t = table("/var/log/*.log", &nothing_is_a_dir);
589
+ assert_eq!(t.root, Path::new(r"\var\log"));
590
+ assert_eq!(t.path_prefix, "/var/log");
591
+ }
592
+
593
+ #[test]
594
+ fn a_unc_path_roots_at_its_share() {
595
+ let t = table(r"\\server\share\docs\*.md", &nothing_is_a_dir);
596
+ assert_eq!(t.root, Path::new(r"\\server\share\docs"));
597
+ assert_eq!(t.glob, "*.md");
598
+ assert_eq!(t.path_prefix, "//server/share/docs");
599
+ }
600
+
601
+ #[test]
602
+ fn a_verbatim_path_keeps_its_backslashes() {
603
+ let t = table(r"\\?\C:\var\log\*.log", &nothing_is_a_dir);
604
+ assert_eq!(t.path_prefix, r"\\?\C:\var\log");
605
+ }
606
+
607
+ #[test]
608
+ fn a_parent_relative_path_resolves_against_the_index_root() {
609
+ let t = resolve_as::<Utf8WindowsEncoding>(
610
+ "../../a/*.md",
611
+ Path::new(r"C:\x\y\z"),
612
+ None,
613
+ &nothing_is_a_dir,
614
+ );
615
+ assert_eq!(
616
+ t,
617
+ Resolution::Table(PathTable {
618
+ root: PathBuf::from(r"C:\x\a"),
619
+ glob: "*.md".to_string(),
620
+ path_prefix: "C:/x/a".to_string(),
621
+ })
622
+ );
623
+ }
624
+
625
+ #[test]
626
+ fn a_parent_relative_path_past_the_drive_root_stops_there() {
627
+ let t = resolve_as::<Utf8WindowsEncoding>(
628
+ "../../*.md",
629
+ Path::new(r"C:\x"),
630
+ None,
631
+ &nothing_is_a_dir,
632
+ );
633
+ assert_eq!(
634
+ t,
635
+ Resolution::Table(PathTable {
636
+ root: PathBuf::from(r"C:\"),
637
+ glob: "*.md".to_string(),
638
+ path_prefix: "C:/".to_string(),
639
+ })
640
+ );
641
+ }
642
+
643
+ #[test]
644
+ fn a_home_relative_path_resolves_against_the_home_directory() {
645
+ let t = table("~/notes/*.md", &nothing_is_a_dir);
646
+ assert_eq!(t.root, Path::new(r"C:\Users\u\notes"));
647
+ assert_eq!(t.glob, "*.md");
648
+ assert_eq!(t.path_prefix, "C:/Users/u/notes");
649
+ }
650
+
651
+ #[test]
652
+ fn a_backslash_home_relative_path_resolves_against_the_home_directory() {
653
+ let t = table(r"~\notes\*.md", &nothing_is_a_dir);
654
+ assert_eq!(t.root, Path::new(r"C:\Users\u\notes"));
655
+ assert_eq!(t.glob, "*.md");
656
+ }
657
+
658
+ #[test]
659
+ fn a_home_relative_path_without_a_home_directory_is_unresolvable() {
660
+ assert_eq!(
661
+ resolve_as::<Utf8WindowsEncoding>(
662
+ r"~\notes",
663
+ Path::new(ROOT),
664
+ None,
665
+ &everything_is_a_dir
666
+ ),
667
+ Resolution::NoHome
668
+ );
669
+ }
670
+
671
+ #[test]
672
+ fn a_bare_glob_asks_for_the_dot_slash_form() {
673
+ assert_eq!(resolve_with("**/*.md", &nothing_is_a_dir), Resolution::Hint);
674
+ }
675
+
676
+ #[test]
677
+ fn a_plain_identifier_is_not_a_path() {
678
+ assert_eq!(
679
+ resolve_with("users", &nothing_is_a_dir),
680
+ Resolution::NotAPath
681
+ );
682
+ }
683
+ }
684
+
685
+ #[test]
686
+ fn a_backslash_after_a_tilde_is_not_a_unix_path() {
687
+ assert_eq!(
688
+ resolve_as::<Utf8UnixEncoding>(
689
+ r"~\notes",
690
+ Path::new(ROOT),
691
+ Some(Path::new("/home/u")),
692
+ &nothing_is_a_dir
693
+ ),
694
+ Resolution::NotAPath
695
+ );
696
+ }
416
697
  }
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