cicada-mcp 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- cicada/_version_hash.py +4 -0
- cicada/cli.py +6 -748
- cicada/commands.py +1255 -0
- cicada/dead_code/__init__.py +1 -0
- cicada/{find_dead_code.py → dead_code/finder.py} +2 -1
- cicada/dependency_analyzer.py +147 -0
- cicada/entry_utils.py +92 -0
- cicada/extractors/base.py +9 -9
- cicada/extractors/call.py +17 -20
- cicada/extractors/common.py +64 -0
- cicada/extractors/dependency.py +117 -235
- cicada/extractors/doc.py +2 -49
- cicada/extractors/function.py +10 -14
- cicada/extractors/keybert.py +228 -0
- cicada/extractors/keyword.py +191 -0
- cicada/extractors/module.py +6 -10
- cicada/extractors/spec.py +8 -56
- cicada/format/__init__.py +20 -0
- cicada/{ascii_art.py → format/ascii_art.py} +1 -1
- cicada/format/formatter.py +1145 -0
- cicada/git_helper.py +134 -7
- cicada/indexer.py +322 -89
- cicada/interactive_setup.py +251 -323
- cicada/interactive_setup_helpers.py +302 -0
- cicada/keyword_expander.py +437 -0
- cicada/keyword_search.py +208 -422
- cicada/keyword_test.py +383 -16
- cicada/mcp/__init__.py +10 -0
- cicada/mcp/entry.py +17 -0
- cicada/mcp/filter_utils.py +107 -0
- cicada/mcp/pattern_utils.py +118 -0
- cicada/{mcp_server.py → mcp/server.py} +819 -73
- cicada/mcp/tools.py +473 -0
- cicada/pr_finder.py +2 -3
- cicada/pr_indexer/indexer.py +3 -2
- cicada/setup.py +167 -35
- cicada/tier.py +225 -0
- cicada/utils/__init__.py +9 -2
- cicada/utils/fuzzy_match.py +54 -0
- cicada/utils/index_utils.py +9 -0
- cicada/utils/path_utils.py +18 -0
- cicada/utils/text_utils.py +52 -1
- cicada/utils/tree_utils.py +47 -0
- cicada/version_check.py +99 -0
- cicada/watch_manager.py +320 -0
- cicada/watcher.py +431 -0
- cicada_mcp-0.3.0.dist-info/METADATA +541 -0
- cicada_mcp-0.3.0.dist-info/RECORD +70 -0
- cicada_mcp-0.3.0.dist-info/entry_points.txt +4 -0
- cicada/formatter.py +0 -864
- cicada/keybert_extractor.py +0 -286
- cicada/lightweight_keyword_extractor.py +0 -290
- cicada/mcp_entry.py +0 -683
- cicada/mcp_tools.py +0 -291
- cicada_mcp-0.2.0.dist-info/METADATA +0 -735
- cicada_mcp-0.2.0.dist-info/RECORD +0 -53
- cicada_mcp-0.2.0.dist-info/entry_points.txt +0 -4
- /cicada/{dead_code_analyzer.py → dead_code/analyzer.py} +0 -0
- /cicada/{colors.py → format/colors.py} +0 -0
- {cicada_mcp-0.2.0.dist-info → cicada_mcp-0.3.0.dist-info}/WHEEL +0 -0
- {cicada_mcp-0.2.0.dist-info → cicada_mcp-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {cicada_mcp-0.2.0.dist-info → cicada_mcp-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper utilities for wildcard and OR pattern handling within MCP tools.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import contextlib
|
|
8
|
+
import fnmatch
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"FunctionPattern",
|
|
15
|
+
"has_wildcards",
|
|
16
|
+
"match_any_pattern",
|
|
17
|
+
"match_wildcard",
|
|
18
|
+
"matches_pattern",
|
|
19
|
+
"parse_function_patterns",
|
|
20
|
+
"split_or_patterns",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def has_wildcards(pattern: str) -> bool:
|
|
25
|
+
"""Return True if the supplied pattern contains wildcard characters."""
|
|
26
|
+
return "*" in pattern or "|" in pattern
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def match_wildcard(pattern: str, text: str) -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Check if text matches a wildcard pattern.
|
|
32
|
+
|
|
33
|
+
Supports * (matches any characters) only, not ?.
|
|
34
|
+
"""
|
|
35
|
+
if "?" in pattern:
|
|
36
|
+
return False
|
|
37
|
+
return fnmatch.fnmatch(text.lower(), pattern.lower())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def matches_pattern(pattern: str | None, text: str) -> bool:
|
|
41
|
+
"""Evaluate whether text satisfies a (possibly wildcard) pattern."""
|
|
42
|
+
if not pattern or pattern == "*":
|
|
43
|
+
return True
|
|
44
|
+
if "*" in pattern:
|
|
45
|
+
return match_wildcard(pattern, text)
|
|
46
|
+
return pattern.lower() == text.lower()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def match_any_pattern(patterns: Iterable[str], text: str) -> bool:
|
|
50
|
+
"""Return True if the text matches any of the provided patterns."""
|
|
51
|
+
return any(matches_pattern(pattern.strip(), text) for pattern in patterns if pattern.strip())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def split_or_patterns(pattern: str) -> list[str]:
|
|
55
|
+
"""Split a pattern by the OR symbol (|)."""
|
|
56
|
+
return [p.strip() for p in pattern.split("|")]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class FunctionPattern:
|
|
61
|
+
"""Structured representation of a parsed function search pattern."""
|
|
62
|
+
|
|
63
|
+
file: str | None = None
|
|
64
|
+
module: str | None = None
|
|
65
|
+
name: str = "*"
|
|
66
|
+
arity: int | None = None
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_string(cls, pattern: str) -> FunctionPattern:
|
|
70
|
+
"""Parse a raw pattern string into a `FunctionPattern` instance."""
|
|
71
|
+
raw = pattern.strip()
|
|
72
|
+
if not raw:
|
|
73
|
+
raw = "*"
|
|
74
|
+
|
|
75
|
+
file_pattern: str | None = None
|
|
76
|
+
module_pattern: str | None = None
|
|
77
|
+
name_pattern = raw
|
|
78
|
+
arity: int | None = None
|
|
79
|
+
|
|
80
|
+
if ":" in name_pattern:
|
|
81
|
+
file_candidate, remainder = name_pattern.split(":", 1)
|
|
82
|
+
if "/" in file_candidate or file_candidate.endswith((".ex", ".exs")):
|
|
83
|
+
file_pattern = file_candidate
|
|
84
|
+
name_pattern = remainder
|
|
85
|
+
|
|
86
|
+
if "." in name_pattern:
|
|
87
|
+
module_pattern, name_pattern = name_pattern.rsplit(".", 1)
|
|
88
|
+
|
|
89
|
+
if "/" in name_pattern:
|
|
90
|
+
name_part, arity_part = name_pattern.rsplit("/", 1)
|
|
91
|
+
with contextlib.suppress(ValueError):
|
|
92
|
+
arity = int(arity_part)
|
|
93
|
+
name_pattern = name_part
|
|
94
|
+
|
|
95
|
+
if not name_pattern:
|
|
96
|
+
name_pattern = "*"
|
|
97
|
+
|
|
98
|
+
return cls(file=file_pattern, module=module_pattern, name=name_pattern, arity=arity)
|
|
99
|
+
|
|
100
|
+
def matches(self, module_name: str, file_path: str, func: dict[str, Any]) -> bool:
|
|
101
|
+
"""Return True if the function entry satisfies this pattern."""
|
|
102
|
+
return (
|
|
103
|
+
matches_pattern(self.module, module_name)
|
|
104
|
+
and matches_pattern(self.file, file_path)
|
|
105
|
+
and matches_pattern(self.name, func["name"])
|
|
106
|
+
and (self.arity is None or func["arity"] == self.arity)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def parse_function_patterns(raw: str | None) -> list[FunctionPattern]:
|
|
111
|
+
"""Split and parse a compound OR pattern string into FunctionPattern objects."""
|
|
112
|
+
if not raw:
|
|
113
|
+
return [FunctionPattern()]
|
|
114
|
+
|
|
115
|
+
patterns = [
|
|
116
|
+
FunctionPattern.from_string(part) for part in split_or_patterns(raw) if part.strip()
|
|
117
|
+
]
|
|
118
|
+
return patterns or [FunctionPattern()]
|