dirsql 0.3.17__tar.gz → 0.3.18__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 (161) hide show
  1. {dirsql-0.3.17 → dirsql-0.3.18}/Cargo.lock +1 -1
  2. {dirsql-0.3.17 → dirsql-0.3.18}/PKG-INFO +1 -1
  3. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/Cargo.toml +1 -1
  4. dirsql-0.3.18/packages/python/tests/integration/__fixtures__/dirsql.config.py +29 -0
  5. dirsql-0.3.18/packages/python/tests/integration/__fixtures__/interpret/data/a/meta.json +1 -0
  6. dirsql-0.3.18/packages/python/tests/integration/__fixtures__/interpret/data/b/meta.json +1 -0
  7. dirsql-0.3.18/packages/python/tests/integration/test_native_config.py +100 -0
  8. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/bin/dirsql.rs +67 -1
  9. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/cli/mod.rs +1 -0
  10. dirsql-0.3.18/packages/rust/src/cli/native_config.rs +694 -0
  11. {dirsql-0.3.17 → dirsql-0.3.18}/Cargo.toml +0 -0
  12. {dirsql-0.3.17 → dirsql-0.3.18}/README.md +0 -0
  13. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/__init__.py +0 -0
  14. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/_async.py +0 -0
  15. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/_dirsql.pyi +0 -0
  16. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/__init__.py +0 -0
  17. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/binary_path.py +0 -0
  18. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/binary_path_test.py +0 -0
  19. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/interpret/__init__.py +0 -0
  20. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/interpret/dispatch_extract.py +0 -0
  21. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/interpret/dispatch_extract_test.py +0 -0
  22. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/interpret/load_app.py +0 -0
  23. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/interpret/load_app_test.py +0 -0
  24. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/interpret/run.py +0 -0
  25. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/interpret/run_test.py +0 -0
  26. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/interpret/write_message.py +0 -0
  27. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/interpret/write_message_test.py +0 -0
  28. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/is_windows.py +0 -0
  29. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/is_windows_test.py +0 -0
  30. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/main.py +0 -0
  31. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/cli/main_test.py +0 -0
  32. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/py.typed +0 -0
  33. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/resolve_config.py +0 -0
  34. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/resolve_config_test.py +0 -0
  35. {dirsql-0.3.17 → dirsql-0.3.18}/dirsql/test_async.py +0 -0
  36. {dirsql-0.3.17 → dirsql-0.3.18}/docs/.claude/CLAUDE.md +0 -0
  37. {dirsql-0.3.17 → dirsql-0.3.18}/docs/.vitepress/config.ts +0 -0
  38. {dirsql-0.3.17 → dirsql-0.3.18}/docs/.vitepress/theme/index.ts +0 -0
  39. {dirsql-0.3.17 → dirsql-0.3.18}/docs/.vitepress/theme/lang.ts +0 -0
  40. {dirsql-0.3.17 → dirsql-0.3.18}/docs/AGENTS.md +0 -0
  41. {dirsql-0.3.17 → dirsql-0.3.18}/docs/api/index.md +0 -0
  42. {dirsql-0.3.17 → dirsql-0.3.18}/docs/cli/config.md +0 -0
  43. {dirsql-0.3.17 → dirsql-0.3.18}/docs/cli/http-api.md +0 -0
  44. {dirsql-0.3.17 → dirsql-0.3.18}/docs/cli/index.md +0 -0
  45. {dirsql-0.3.17 → dirsql-0.3.18}/docs/cli/init.md +0 -0
  46. {dirsql-0.3.17 → dirsql-0.3.18}/docs/cli/server.md +0 -0
  47. {dirsql-0.3.17 → dirsql-0.3.18}/docs/getting-started.md +0 -0
  48. {dirsql-0.3.17 → dirsql-0.3.18}/docs/guide/async.md +0 -0
  49. {dirsql-0.3.17 → dirsql-0.3.18}/docs/guide/crdt.md +0 -0
  50. {dirsql-0.3.17 → dirsql-0.3.18}/docs/guide/persistence.md +0 -0
  51. {dirsql-0.3.17 → dirsql-0.3.18}/docs/guide/querying.md +0 -0
  52. {dirsql-0.3.17 → dirsql-0.3.18}/docs/guide/tables.md +0 -0
  53. {dirsql-0.3.17 → dirsql-0.3.18}/docs/guide/watching.md +0 -0
  54. {dirsql-0.3.17 → dirsql-0.3.18}/docs/index.md +0 -0
  55. {dirsql-0.3.17 → dirsql-0.3.18}/docs/migrations.md +0 -0
  56. {dirsql-0.3.17 → dirsql-0.3.18}/docs/package.json +0 -0
  57. {dirsql-0.3.17 → dirsql-0.3.18}/docs/playwright.config.ts +0 -0
  58. {dirsql-0.3.17 → dirsql-0.3.18}/docs/pnpm-lock.yaml +0 -0
  59. {dirsql-0.3.17 → dirsql-0.3.18}/docs/pnpm-workspace.yaml +0 -0
  60. {dirsql-0.3.17 → dirsql-0.3.18}/docs/tests/integration/home.spec.ts +0 -0
  61. {dirsql-0.3.17 → dirsql-0.3.18}/docs/tests/integration/language-flag.spec.ts +0 -0
  62. {dirsql-0.3.17 → dirsql-0.3.18}/docs/tests/unit/config.test.ts +0 -0
  63. {dirsql-0.3.17 → dirsql-0.3.18}/docs/tests/unit/lang.test.ts +0 -0
  64. {dirsql-0.3.17 → dirsql-0.3.18}/docs/vitest.config.ts +0 -0
  65. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/README.md +0 -0
  66. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/conftest.py +0 -0
  67. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/.claude/CLAUDE.md +0 -0
  68. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/.vitepress/config.ts +0 -0
  69. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  70. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  71. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/AGENTS.md +0 -0
  72. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/api/index.md +0 -0
  73. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/cli/config.md +0 -0
  74. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/cli/http-api.md +0 -0
  75. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/cli/index.md +0 -0
  76. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/cli/init.md +0 -0
  77. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/cli/server.md +0 -0
  78. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/getting-started.md +0 -0
  79. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/guide/async.md +0 -0
  80. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/guide/crdt.md +0 -0
  81. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/guide/persistence.md +0 -0
  82. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/guide/querying.md +0 -0
  83. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/guide/tables.md +0 -0
  84. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/guide/watching.md +0 -0
  85. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/index.md +0 -0
  86. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/migrations.md +0 -0
  87. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/package.json +0 -0
  88. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/playwright.config.ts +0 -0
  89. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/pnpm-lock.yaml +0 -0
  90. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/pnpm-workspace.yaml +0 -0
  91. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  92. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  93. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/tests/unit/config.test.ts +0 -0
  94. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  95. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/docs/vitest.config.ts +0 -0
  96. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/src/lib.rs +0 -0
  97. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/__init__.py +0 -0
  98. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/conftest.py +0 -0
  99. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/e2e/__init__.py +0 -0
  100. {dirsql-0.3.17/packages/python/tests/integration/__fixtures__/interpret → dirsql-0.3.18/packages/python/tests/integration/__fixtures__}/data/a/meta.json +0 -0
  101. {dirsql-0.3.17/packages/python/tests/integration/__fixtures__/interpret → dirsql-0.3.18/packages/python/tests/integration/__fixtures__}/data/b/meta.json +0 -0
  102. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config.py +0 -0
  103. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_no_app.py +0 -0
  104. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_raises.py +0 -0
  105. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/__init__.py +0 -0
  106. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/interpret_subprocess.py +0 -0
  107. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/test_async_dirsql.py +0 -0
  108. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/test_binding.py +0 -0
  109. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/test_dirsql.py +0 -0
  110. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/test_docs_examples.py +0 -0
  111. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/test_docs_gaps.py +0 -0
  112. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/test_from_config.py +0 -0
  113. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/test_interpret.py +0 -0
  114. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/test_persist.py +0 -0
  115. {dirsql-0.3.17 → dirsql-0.3.18}/packages/python/tests/integration/test_serialization.py +0 -0
  116. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/Cargo.toml +0 -0
  117. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/README.md +0 -0
  118. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/benches/db_bench.rs +0 -0
  119. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/benches/differ_bench.rs +0 -0
  120. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/benches/matcher_bench.rs +0 -0
  121. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/benches/scanner_bench.rs +0 -0
  122. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/api/index.md +0 -0
  123. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/cli/config.md +0 -0
  124. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/cli/http-api.md +0 -0
  125. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/cli/index.md +0 -0
  126. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/cli/init.md +0 -0
  127. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/cli/server.md +0 -0
  128. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/getting-started.md +0 -0
  129. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/guide/async.md +0 -0
  130. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/guide/crdt.md +0 -0
  131. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/guide/persistence.md +0 -0
  132. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/guide/querying.md +0 -0
  133. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/guide/tables.md +0 -0
  134. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/guide/watching.md +0 -0
  135. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/index.md +0 -0
  136. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/docs/migrations.md +0 -0
  137. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/cli/init.rs +0 -0
  138. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/cli/router.rs +0 -0
  139. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/cli/serialize.rs +0 -0
  140. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/cli/server.rs +0 -0
  141. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/config.rs +0 -0
  142. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/db.rs +0 -0
  143. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/differ.rs +0 -0
  144. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/lib.rs +0 -0
  145. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/matcher.rs +0 -0
  146. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/persist.rs +0 -0
  147. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/scanner.rs +0 -0
  148. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/src/watcher.rs +0 -0
  149. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/async_sdk.rs +0 -0
  150. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/cli_e2e.rs +0 -0
  151. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/cli_integration.rs +0 -0
  152. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/docs_examples.rs +0 -0
  153. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/docs_gaps.rs +0 -0
  154. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/from_config.rs +0 -0
  155. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/init_e2e.rs +0 -0
  156. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/init_integration.rs +0 -0
  157. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/persist.rs +0 -0
  158. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/readonly_query.rs +0 -0
  159. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/sdk.rs +0 -0
  160. {dirsql-0.3.17 → dirsql-0.3.18}/packages/rust/tests/serialization.rs +0 -0
  161. {dirsql-0.3.17 → dirsql-0.3.18}/pyproject.toml +0 -0
@@ -499,7 +499,7 @@ dependencies = [
499
499
 
500
500
  [[package]]
501
501
  name = "dirsql-py-ext"
502
- version = "0.3.17"
502
+ version = "0.3.18"
503
503
  dependencies = [
504
504
  "dirsql",
505
505
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.17
3
+ Version: 0.3.18
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -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.3.17"
7
+ version = "0.3.18"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
@@ -0,0 +1,29 @@
1
+ """A native Python config file for dirsql.
2
+
3
+ When ``dirsql --config <path-to-this-file>`` runs, the Rust binary
4
+ spawns ``dirsql interpret`` against this file. The helper loads the
5
+ module, takes its module-level ``app``, and dispatches ``extract``
6
+ callbacks over NDJSON.
7
+ """
8
+
9
+ import json
10
+ import os
11
+
12
+ from dirsql import DirSQL, Table
13
+
14
+
15
+ def _extract(path):
16
+ with open(path) as f:
17
+ return [json.load(f)]
18
+
19
+
20
+ app = DirSQL(
21
+ root=os.path.join(os.path.dirname(__file__), "data"),
22
+ tables=[
23
+ Table(
24
+ ddl="CREATE TABLE papers (title TEXT)",
25
+ glob="**/meta.json",
26
+ extract=_extract,
27
+ )
28
+ ],
29
+ )
@@ -0,0 +1,100 @@
1
+ """CLI integration test for native Python config file support.
2
+
3
+ Spawns the bundled Rust binary directly against the
4
+ ``__fixtures__/dirsql.config.py`` fixture and asserts the HTTP server
5
+ serves the ``papers`` table. Bypasses the Python launcher because the
6
+ architectural property under test ("the Rust binary dispatches
7
+ non-TOML configs to ``dirsql interpret``") is the binary's job — the
8
+ launcher is a transparent forwarder.
9
+
10
+ The binary's `dirsql interpret <X>` subprocess resolves via PATH; in
11
+ this dev/CI tree the pip-installed `dirsql` console script (entry
12
+ point ``dirsql.cli.main:main``) is on PATH and handles the
13
+ ``interpret`` subcommand directly.
14
+ """
15
+
16
+ import json
17
+ import os
18
+ import socket
19
+ import subprocess
20
+ import time
21
+ import urllib.error
22
+ import urllib.request
23
+
24
+ FIXTURE_DIR = os.path.join(os.path.dirname(__file__), "__fixtures__")
25
+ CONFIG_PATH = os.path.join(FIXTURE_DIR, "dirsql.config.py")
26
+
27
+ # Workspace root → cargo's target dir. The CI workflow builds the
28
+ # binary with `cargo build --release -p dirsql --features cli` before
29
+ # running integration tests; locally, `cargo build --release` does the
30
+ # same. Fall back to debug if release isn't present.
31
+ _REPO_ROOT = os.path.abspath(
32
+ os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")
33
+ )
34
+ _RELEASE = os.path.join(_REPO_ROOT, "target", "release", "dirsql")
35
+ _DEBUG = os.path.join(_REPO_ROOT, "target", "debug", "dirsql")
36
+ BINARY = _RELEASE if os.path.exists(_RELEASE) else _DEBUG
37
+
38
+
39
+ def _free_port() -> int:
40
+ with socket.socket() as s:
41
+ s.bind(("", 0))
42
+ return s.getsockname()[1]
43
+
44
+
45
+ def _wait_for_server(proc: subprocess.Popen, port: int, timeout: float = 5.0) -> bool:
46
+ """Return True once the server accepts a connection, False if the process
47
+ exits first or the timeout elapses."""
48
+ deadline = time.monotonic() + timeout
49
+ while time.monotonic() < deadline:
50
+ if proc.poll() is not None:
51
+ return False
52
+ try:
53
+ urllib.request.urlopen(f"http://localhost:{port}/query", timeout=0.5)
54
+ return True
55
+ except urllib.error.HTTPError:
56
+ return True # any HTTP response means the server is up
57
+ except Exception:
58
+ time.sleep(0.05)
59
+ return False
60
+
61
+
62
+ def _query(port: int, sql: str) -> list:
63
+ body = json.dumps({"sql": sql}).encode()
64
+ req = urllib.request.Request(
65
+ f"http://localhost:{port}/query",
66
+ data=body,
67
+ headers={"content-type": "application/json"},
68
+ )
69
+ with urllib.request.urlopen(req) as resp:
70
+ return json.loads(resp.read())
71
+
72
+
73
+ def describe_cli_py_config():
74
+ def it_starts_an_http_server_serving_the_config_tables():
75
+ assert os.path.exists(BINARY), (
76
+ f"dirsql binary not built at {BINARY}; "
77
+ "run `cargo build --release -p dirsql --features cli` first"
78
+ )
79
+ port = _free_port()
80
+ proc = subprocess.Popen(
81
+ [
82
+ BINARY,
83
+ "--config",
84
+ CONFIG_PATH,
85
+ "--host",
86
+ "127.0.0.1",
87
+ "--port",
88
+ str(port),
89
+ ],
90
+ stderr=subprocess.PIPE,
91
+ )
92
+ try:
93
+ assert _wait_for_server(proc, port), (
94
+ "dirsql server did not start with --config .py"
95
+ )
96
+ rows = _query(port, "SELECT title FROM papers ORDER BY title")
97
+ assert rows == [{"title": "Alpha"}, {"title": "Beta"}]
98
+ finally:
99
+ proc.terminate()
100
+ proc.wait(timeout=5)
@@ -8,7 +8,12 @@ use std::path::{Path, PathBuf};
8
8
  use std::process::ExitCode;
9
9
 
10
10
  use clap::{Args, Parser, Subcommand};
11
- use dirsql::cli::{AppState, ServerConfig, init::InitOptions, serve_with_state};
11
+ use dirsql::cli::{
12
+ AppState, ServerConfig,
13
+ init::InitOptions,
14
+ native_config::{InterpretHelper, build_dirsql},
15
+ serve_with_state,
16
+ };
12
17
  use dirsql::{DirSQL, Row, Table};
13
18
 
14
19
  #[derive(Debug, Parser)]
@@ -152,12 +157,73 @@ fn load_state(cli: &Cli) -> AppState {
152
157
  }
153
158
  };
154
159
 
160
+ if is_native_config(&resolved) {
161
+ return load_native_state(&resolved);
162
+ }
163
+
155
164
  match DirSQL::from_config_path(&resolved) {
156
165
  Ok(db) => AppState::Ready(db),
157
166
  Err(err) => AppState::Unavailable(format!("failed to load config: {err}")),
158
167
  }
159
168
  }
160
169
 
170
+ /// Native-language config support: `--config X.{py,js,mjs,cjs}` delegates
171
+ /// to `dirsql interpret <X>` (spawned via PATH) for `extract` execution.
172
+ /// The binary still owns SQL, HTTP, and the file watcher.
173
+ fn is_native_config(path: &Path) -> bool {
174
+ matches!(
175
+ path.extension().and_then(|s| s.to_str()),
176
+ Some("py") | Some("js") | Some("mjs") | Some("cjs")
177
+ )
178
+ }
179
+
180
+ fn load_native_state(config_path: &Path) -> AppState {
181
+ let (helper, config) = match spawn_interpret_helper(config_path) {
182
+ Ok(x) => x,
183
+ Err(err) => return AppState::Unavailable(err),
184
+ };
185
+ match build_dirsql(helper, config) {
186
+ Ok(db) => AppState::Ready(db),
187
+ Err(err) => AppState::Unavailable(format!(
188
+ "failed to build DirSQL from {}: {err}",
189
+ config_path.display()
190
+ )),
191
+ }
192
+ }
193
+
194
+ /// Spawn `dirsql interpret <config_path>` via PATH and hand the child
195
+ /// off to [`InterpretHelper::from_child`]. Lives in the CLI binary
196
+ /// (rather than the lib's `cli::native_config` module) because the
197
+ /// `Command::new("dirsql")` plumbing is only meaningfully exercised
198
+ /// end-to-end via the `dirsql --config X.{py,js,mjs,cjs}` integration
199
+ /// path — there's no useful in-process unit test for it.
200
+ fn spawn_interpret_helper(
201
+ config_path: &Path,
202
+ ) -> Result<
203
+ (
204
+ std::sync::Arc<InterpretHelper>,
205
+ dirsql::cli::native_config::NativeConfig,
206
+ ),
207
+ String,
208
+ > {
209
+ use std::process::{Command, Stdio};
210
+ let child = Command::new("dirsql")
211
+ .arg("interpret")
212
+ .arg(config_path)
213
+ .stdin(Stdio::piped())
214
+ .stdout(Stdio::piped())
215
+ .stderr(Stdio::inherit())
216
+ .spawn()
217
+ .map_err(|e| {
218
+ format!(
219
+ "failed to spawn `dirsql interpret`: {e}. \
220
+ Native-language configs require a launcher that implements `interpret` \
221
+ on PATH (install dirsql via pip/uv or npm/npx)."
222
+ )
223
+ })?;
224
+ InterpretHelper::from_child(child)
225
+ }
226
+
161
227
  /// Zero-config fallback. When no `.dirsql.toml` is found, dirsql indexes the
162
228
  /// directory that would have held the config with a single default `files`
163
229
  /// table — one row per file, columns drawn entirely from filesystem facts —
@@ -28,6 +28,7 @@ use tokio::task::JoinHandle;
28
28
  use crate::DirSQL;
29
29
 
30
30
  pub mod init;
31
+ pub mod native_config;
31
32
  pub mod router;
32
33
  pub mod serialize;
33
34
  pub mod server;