codetool-explore 0.6.0__tar.gz → 0.7.1__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 (69) hide show
  1. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/PKG-INFO +4 -2
  2. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/README.md +3 -1
  3. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/pyproject.toml +1 -1
  4. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/api.py +2 -1
  5. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/explorer.py +21 -1
  6. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/matcher.py +4 -3
  7. codetool_explore-0.7.1/src/codetool_explore/regex.py +14 -0
  8. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_api.py +68 -0
  9. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/uv.lock +1 -1
  10. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/.gitignore +0 -0
  11. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/LICENSE +0 -0
  12. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/benchmarks/benchmark_output_lengths.py +0 -0
  13. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/benchmarks/benchmark_search.py +0 -0
  14. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/hatch_build.py +0 -0
  15. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/Cargo.lock +0 -0
  16. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/Cargo.toml +0 -0
  17. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/app.rs +0 -0
  18. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/case.rs +0 -0
  19. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/config.rs +0 -0
  20. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/constants.rs +0 -0
  21. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/file_search.rs +0 -0
  22. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/ignore_rules.rs +0 -0
  23. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/literal.rs +0 -0
  24. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/main.rs +0 -0
  25. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/matcher.rs +0 -0
  26. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/models.rs +0 -0
  27. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/output.rs +0 -0
  28. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/path_utils.rs +0 -0
  29. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/ranking.rs +0 -0
  30. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/regex_search.rs +0 -0
  31. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/search.rs +0 -0
  32. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/text.rs +0 -0
  33. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/rust/src/walker.rs +0 -0
  34. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/scripts/package_rust_binary.py +0 -0
  35. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/scripts/update_readme_benchmarks.py +0 -0
  36. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/__init__.py +0 -0
  37. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/cli.py +0 -0
  38. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/compression.py +0 -0
  39. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/cursor.py +0 -0
  40. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/errors.py +0 -0
  41. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/ignore.py +0 -0
  42. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/py.typed +0 -0
  43. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/__init__.py +0 -0
  44. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/case.py +0 -0
  45. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/config.py +0 -0
  46. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/constants.py +0 -0
  47. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/file_search.py +0 -0
  48. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/ignore_rules.py +0 -0
  49. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/literal.py +0 -0
  50. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/models.py +0 -0
  51. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/output.py +0 -0
  52. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/regex_search.py +0 -0
  53. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/search.py +0 -0
  54. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/text.py +0 -0
  55. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/python_backend/walker.py +0 -0
  56. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/ranking.py +0 -0
  57. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/roots.py +0 -0
  58. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/rust_backend.py +0 -0
  59. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/src/codetool_explore/text_output.py +0 -0
  60. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test___init__.py +0 -0
  61. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_cli.py +0 -0
  62. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_cursor.py +0 -0
  63. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_hatch_build.py +0 -0
  64. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_ignore.py +0 -0
  65. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_packaged_binary.py +0 -0
  66. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_python_backend.py +0 -0
  67. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_ranking.py +0 -0
  68. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_rust_backend.py +0 -0
  69. {codetool_explore-0.6.0 → codetool_explore-0.7.1}/tests/test_rust_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codetool-explore
3
- Version: 0.6.0
3
+ Version: 0.7.1
4
4
  Summary: Fast, dependency-free workspace search, read, and list exploration for coding-agent tools with Rust backend
5
5
  Project-URL: Homepage, https://github.com/pbi-agent/codetool-explore
6
6
  Project-URL: Repository, https://github.com/pbi-agent/codetool-explore
@@ -114,7 +114,9 @@ than one file is read, each file is prefixed by a compact `path:` header. Use
114
114
  `start_line` and `limit` to cap the returned line range; if more lines remain,
115
115
  text output starts with `-- more: cursor=N`. CSV files are read as ordinary
116
116
  text. Binary-looking, missing, unreadable, or directory paths fail with
117
- controlled `ExploreError` subclasses.
117
+ controlled `ExploreError` subclasses. If `root` is already a file,
118
+ `target="read"` reads that file directly, so accidental search-like `pattern`
119
+ values from tool calls still produce the requested line range.
118
120
 
119
121
  `target="list"` treats `pattern` as one file/directory path and returns one
120
122
  directory level under each root. Text output uses the same compact tree display
@@ -84,7 +84,9 @@ than one file is read, each file is prefixed by a compact `path:` header. Use
84
84
  `start_line` and `limit` to cap the returned line range; if more lines remain,
85
85
  text output starts with `-- more: cursor=N`. CSV files are read as ordinary
86
86
  text. Binary-looking, missing, unreadable, or directory paths fail with
87
- controlled `ExploreError` subclasses.
87
+ controlled `ExploreError` subclasses. If `root` is already a file,
88
+ `target="read"` reads that file directly, so accidental search-like `pattern`
89
+ values from tool calls still produce the requested line range.
88
90
 
89
91
  `target="list"` treats `pattern` as one file/directory path and returns one
90
92
  directory level under each root. Text output uses the same compact tree display
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codetool-explore"
3
- version = "0.6.0"
3
+ version = "0.7.1"
4
4
  description = "Fast, dependency-free workspace search, read, and list exploration for coding-agent tools with Rust backend"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -10,6 +10,7 @@ from .compression import compress_result
10
10
  from .errors import ExploreArgumentError, ExploreBackendError, ExplorePatternError
11
11
  from .explorer import list_path_target, read_file_target
12
12
  from .python_backend import resolve_case, search_python
13
+ from .regex import compile_regex
13
14
  from .roots import RootInput
14
15
  from .rust_backend import RustBackendUnavailable, find_rust_binary, search_rust
15
16
  from .text_output import format_text_result
@@ -68,7 +69,7 @@ def _regex_can_produce_empty_match(pattern: str, *, case: str) -> bool:
68
69
  _, case_sensitive = resolve_case(requested_case, pattern)
69
70
  flags = 0 if case_sensitive else re.IGNORECASE
70
71
  try:
71
- compiled = re.compile(pattern, flags)
72
+ compiled = compile_regex(pattern, flags)
72
73
  except re.error as exc:
73
74
  raise ExplorePatternError(f"invalid regex: {exc}") from exc
74
75
  return any(
@@ -252,7 +252,12 @@ def _resolve_explore_paths(
252
252
  raise ExploreArgumentError(f"{target} path must not be empty")
253
253
 
254
254
  return tuple(
255
- _resolve_path_for_search_root(query, search_root=search_root, root_set=root_set)
255
+ _resolve_path_for_search_root(
256
+ query,
257
+ search_root=search_root,
258
+ root_set=root_set,
259
+ target=target,
260
+ )
256
261
  for search_root in root_set.roots
257
262
  )
258
263
 
@@ -262,10 +267,25 @@ def _resolve_path_for_search_root(
262
267
  *,
263
268
  search_root: SearchRoot,
264
269
  root_set: NormalizedRoots,
270
+ target: str,
265
271
  ) -> ResolvedExplorePath:
266
272
  root_abs = search_root.abs_path
267
273
  root_is_file = os.path.isfile(root_abs)
268
274
  root_base = root_abs if os.path.isdir(root_abs) else os.path.dirname(root_abs)
275
+ if target == "read" and root_is_file:
276
+ display_base = root_set.rel_base if root_set.has_multiple else root_base
277
+ display_path = _display_path(root_abs, display_base)
278
+ return ResolvedExplorePath(
279
+ query=query,
280
+ abs_path=root_abs,
281
+ display_path=display_path,
282
+ root_display=root_set.display,
283
+ display_base=display_base,
284
+ root_base=root_base,
285
+ search_root_abs=root_abs,
286
+ root_is_file=True,
287
+ rel_base_abs=root_set.rel_base,
288
+ )
269
289
  if os.path.isabs(query):
270
290
  abs_path = os.path.abspath(query)
271
291
  base = root_set.rel_base if root_set.has_multiple else root_base
@@ -6,6 +6,7 @@ import re
6
6
  from dataclasses import dataclass
7
7
  from typing import TypeAlias
8
8
 
9
+ from ..regex import compile_regex
9
10
  from .literal import LiteralMatcher
10
11
  from .regex_search import RegexLineMatcher
11
12
 
@@ -22,7 +23,7 @@ def build_content_matcher(
22
23
 
23
24
  if regex:
24
25
  flags = 0 if case_sensitive else re.IGNORECASE
25
- return RegexLineMatcher(re.compile(pattern, flags))
26
+ return RegexLineMatcher(compile_regex(pattern, flags))
26
27
  needle = pattern.encode("utf-8", errors="surrogatepass")
27
28
  return LiteralMatcher(needle=needle, case_sensitive=case_sensitive)
28
29
 
@@ -49,7 +50,7 @@ class PathMatcher:
49
50
  compiled = None
50
51
  if regex:
51
52
  flags = 0 if case_sensitive else re.IGNORECASE
52
- compiled = re.compile(pattern, flags)
53
+ compiled = compile_regex(pattern, flags)
53
54
  return cls(
54
55
  pattern=pattern,
55
56
  regex=regex,
@@ -76,4 +77,4 @@ def path_match_subject(rel_path: str, path_scope: str) -> str:
76
77
 
77
78
  if path_scope == "basename":
78
79
  return rel_path.rsplit("/", 1)[-1]
79
- return rel_path
80
+ return rel_path
@@ -0,0 +1,14 @@
1
+ """Shared regex helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import warnings
7
+
8
+
9
+ def compile_regex(pattern: str, flags: int = 0) -> re.Pattern[str]:
10
+ """Compile a user regex without leaking Python's advisory future warnings."""
11
+
12
+ with warnings.catch_warnings():
13
+ warnings.simplefilter("ignore", FutureWarning)
14
+ return re.compile(pattern, flags)
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import warnings
4
5
 
5
6
  import pytest
6
7
 
@@ -265,6 +266,41 @@ def test_read_target_defaults_to_plain_text_line_range(tmp_path):
265
266
  assert "2:" not in result
266
267
 
267
268
 
269
+ def test_read_target_accepts_file_root_with_search_hint_pattern(tmp_path):
270
+ target = tmp_path / "webapp" / "src" / "styles" / "modal.css"
271
+ write(
272
+ target,
273
+ "one\n"
274
+ "two\n"
275
+ ".task-form-dialog {\n"
276
+ " color: red;\n"
277
+ "}\n",
278
+ )
279
+
280
+ text = api.explore(
281
+ "task-form-dialog",
282
+ root=str(target),
283
+ target="read",
284
+ start_line=3,
285
+ limit=2,
286
+ )
287
+ full = api.explore(
288
+ "task-form-dialog",
289
+ root=str(target),
290
+ target="read",
291
+ start_line=3,
292
+ limit=2,
293
+ result_format="full",
294
+ )
295
+
296
+ assert text == "-- more: cursor=5\n.task-form-dialog {\n color: red;"
297
+ assert full["pattern"] == "task-form-dialog"
298
+ assert full["root"] == str(target)
299
+ assert full["path"] == "modal.css"
300
+ assert full["returned"] == 2
301
+ assert full["next_cursor"] == "5"
302
+
303
+
268
304
  def test_read_target_reports_more_and_supports_csv_as_text(tmp_path):
269
305
  write(tmp_path / "data.csv", "name,value\nalpha,1\nbeta,2\n")
270
306
 
@@ -788,6 +824,38 @@ def test_auto_regex_empty_match_uses_python_for_counts(monkeypatch, tmp_path):
788
824
  assert result["matches"] == [{"path": "a.py", "count": 4, "first_line": 1}]
789
825
 
790
826
 
827
+ def test_nested_set_regex_warning_is_suppressed_in_auto(monkeypatch, tmp_path):
828
+ write(tmp_path / "a.py", "[abc]\n")
829
+ monkeypatch.setattr(api, "find_rust_binary", lambda: "/fake/rust")
830
+
831
+ def fake_rust(pattern, **kwargs):
832
+ return {
833
+ "pattern": pattern,
834
+ "backend": "rust",
835
+ "root": kwargs["root"],
836
+ "regex": True,
837
+ "mode": kwargs["mode"],
838
+ "matches": [],
839
+ "returned": 0,
840
+ "total_files": 1,
841
+ "total_matches": 0,
842
+ "count": 0,
843
+ "truncated": False,
844
+ "next_cursor": None,
845
+ "offset": 0,
846
+ "limit": kwargs["limit"],
847
+ }
848
+
849
+ monkeypatch.setattr(api, "search_rust", fake_rust)
850
+
851
+ with warnings.catch_warnings(record=True) as caught:
852
+ warnings.simplefilter("always")
853
+ api.explore("[[abc]]", root=str(tmp_path), result_format="full")
854
+ api.explore("[[abc]]", root=str(tmp_path), backend="python")
855
+
856
+ assert not [item for item in caught if item.category is FutureWarning]
857
+
858
+
791
859
  def test_auto_regex_falls_back_to_python_when_rust_rejects_syntax(
792
860
  monkeypatch, tmp_path
793
861
  ):
@@ -86,7 +86,7 @@ wheels = [
86
86
 
87
87
  [[package]]
88
88
  name = "codetool-explore"
89
- version = "0.6.0"
89
+ version = "0.7.1"
90
90
  source = { editable = "." }
91
91
 
92
92
  [package.dev-dependencies]