pureshellcheck 0.2.0__tar.gz → 0.2.2__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.
- {pureshellcheck-0.2.0/src/pureshellcheck.egg-info → pureshellcheck-0.2.2}/PKG-INFO +12 -11
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/README.md +11 -10
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/pyproject.toml +1 -1
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/__init__.py +1 -1
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/analyzer.py +2 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/cli.py +38 -3
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/parser.py +73 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/shast.py +13 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2/src/pureshellcheck.egg-info}/PKG-INFO +12 -11
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/LICENSE +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/MANIFEST.in +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/setup.cfg +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/astlib.py +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/__init__.py +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/commands.py +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/misc.py +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/quoting.py +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/variables.py +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/varflow.py +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck/varscan.py +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/SOURCES.txt +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/dependency_links.txt +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/entry_points.txt +0 -0
- {pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pureshellcheck
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A pure Python reimplementation of ShellCheck's most common checks
|
|
5
5
|
Author: adam2go
|
|
6
6
|
License: MIT
|
|
@@ -157,24 +157,25 @@ no process spawn, no binary. A one-line snippet checks in **~40 µs**
|
|
|
157
157
|
(~25,000 checks/second); throughput on large scripts is ~57k lines/s. CLI
|
|
158
158
|
time is dominated by CPython interpreter startup (~20 ms).
|
|
159
159
|
|
|
160
|
-
**v0.2.
|
|
160
|
+
**v0.2.x vs v0.1.0** (controlled before/after,
|
|
161
161
|
`python tools/bench_compare.py`: baseline wheel from PyPI vs this tree in
|
|
162
162
|
the same interpreter, 25–200 in-process repeats, outputs verified
|
|
163
163
|
identical on every workload):
|
|
164
164
|
|
|
165
|
-
| workload | v0.1.0 | v0.2.
|
|
165
|
+
| workload | v0.1.0 | v0.2.1 | improvement |
|
|
166
166
|
|---|---|---|---|
|
|
167
|
-
| tiny (1 line) | 0.
|
|
168
|
-
| small (75 lines) | 2.
|
|
169
|
-
| medium (263 lines) |
|
|
170
|
-
| large (1216 lines) |
|
|
167
|
+
| tiny (1 line) | 0.058 ms | 0.036 ms | 1.6× |
|
|
168
|
+
| small (75 lines) | 2.44 ms | 1.21 ms | 2.0× |
|
|
169
|
+
| medium (263 lines) | 8.76 ms | 4.48 ms | 2.0× |
|
|
170
|
+
| large (1216 lines) | 46.2 ms | 20.0 ms | **2.3×** |
|
|
171
171
|
|
|
172
|
-
The
|
|
172
|
+
The speedups came from caching the AST child/parent structure and a
|
|
173
173
|
document-order node table (one traversal instead of dozens), making
|
|
174
174
|
variable states immutable tuples so branch snapshots are plain dict
|
|
175
|
-
copies, a banded Levenshtein for SC2153
|
|
176
|
-
reference implementation on 20,000 random
|
|
177
|
-
word/command resolution.
|
|
175
|
+
copies, a leaf-node fast path, a banded Levenshtein for SC2153
|
|
176
|
+
(fuzz-tested against the reference implementation on 20,000 random
|
|
177
|
+
pairs), and memoizing repeated word/command resolution. Package import is
|
|
178
|
+
3.6 ms; remaining CLI latency is CPython interpreter startup.
|
|
178
179
|
|
|
179
180
|
## Compatibility notes
|
|
180
181
|
|
|
@@ -130,24 +130,25 @@ no process spawn, no binary. A one-line snippet checks in **~40 µs**
|
|
|
130
130
|
(~25,000 checks/second); throughput on large scripts is ~57k lines/s. CLI
|
|
131
131
|
time is dominated by CPython interpreter startup (~20 ms).
|
|
132
132
|
|
|
133
|
-
**v0.2.
|
|
133
|
+
**v0.2.x vs v0.1.0** (controlled before/after,
|
|
134
134
|
`python tools/bench_compare.py`: baseline wheel from PyPI vs this tree in
|
|
135
135
|
the same interpreter, 25–200 in-process repeats, outputs verified
|
|
136
136
|
identical on every workload):
|
|
137
137
|
|
|
138
|
-
| workload | v0.1.0 | v0.2.
|
|
138
|
+
| workload | v0.1.0 | v0.2.1 | improvement |
|
|
139
139
|
|---|---|---|---|
|
|
140
|
-
| tiny (1 line) | 0.
|
|
141
|
-
| small (75 lines) | 2.
|
|
142
|
-
| medium (263 lines) |
|
|
143
|
-
| large (1216 lines) |
|
|
140
|
+
| tiny (1 line) | 0.058 ms | 0.036 ms | 1.6× |
|
|
141
|
+
| small (75 lines) | 2.44 ms | 1.21 ms | 2.0× |
|
|
142
|
+
| medium (263 lines) | 8.76 ms | 4.48 ms | 2.0× |
|
|
143
|
+
| large (1216 lines) | 46.2 ms | 20.0 ms | **2.3×** |
|
|
144
144
|
|
|
145
|
-
The
|
|
145
|
+
The speedups came from caching the AST child/parent structure and a
|
|
146
146
|
document-order node table (one traversal instead of dozens), making
|
|
147
147
|
variable states immutable tuples so branch snapshots are plain dict
|
|
148
|
-
copies, a banded Levenshtein for SC2153
|
|
149
|
-
reference implementation on 20,000 random
|
|
150
|
-
word/command resolution.
|
|
148
|
+
copies, a leaf-node fast path, a banded Levenshtein for SC2153
|
|
149
|
+
(fuzz-tested against the reference implementation on 20,000 random
|
|
150
|
+
pairs), and memoizing repeated word/command resolution. Package import is
|
|
151
|
+
3.6 ms; remaining CLI latency is CPython interpreter startup.
|
|
151
152
|
|
|
152
153
|
## Compatibility notes
|
|
153
154
|
|
|
@@ -382,6 +382,8 @@ def apply_directives(findings, directives, nodes, source, positions):
|
|
|
382
382
|
def run_checks(source, shell=None, include_optional=False,
|
|
383
383
|
filename="<stdin>"):
|
|
384
384
|
"""Parse and analyze a script. Returns (findings, parse_error|None)."""
|
|
385
|
+
if source and source[0] == chr(0xFEFF):
|
|
386
|
+
source = source[1:] # tolerate a UTF-8 BOM
|
|
385
387
|
parser = Parser(source)
|
|
386
388
|
try:
|
|
387
389
|
root = parser.parse()
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Command line interface, modeled on shellcheck's."""
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
-
import json
|
|
5
4
|
import sys
|
|
6
5
|
|
|
7
6
|
from . import __version__, run_checks
|
|
@@ -19,6 +18,38 @@ COLORS = {
|
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
|
|
21
|
+
_TAKES_ONE = {"-s", "--shell", "-f", "--format", "-e", "--exclude",
|
|
22
|
+
"-S", "--severity", "-o", "--enable"}
|
|
23
|
+
_COLOR_VALUES = {"auto", "always", "never"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _reorder(argv):
|
|
27
|
+
"""Let options appear after files, like shellcheck: before Python 3.12,
|
|
28
|
+
argparse stops recognizing options once positionals begin, so
|
|
29
|
+
`pureshellcheck foo.sh -f gcc` would fail there. Deterministically sort
|
|
30
|
+
options to the front."""
|
|
31
|
+
opts, pos = [], []
|
|
32
|
+
i, n = 0, len(argv)
|
|
33
|
+
while i < n:
|
|
34
|
+
a = argv[i]
|
|
35
|
+
if a == "--":
|
|
36
|
+
pos.extend(argv[i + 1:])
|
|
37
|
+
break
|
|
38
|
+
if a.startswith("-") and a != "-":
|
|
39
|
+
take = 2 if a in _TAKES_ONE and i + 1 < n else 1
|
|
40
|
+
if a in ("-C", "--color") and i + 1 < n \
|
|
41
|
+
and argv[i + 1] in _COLOR_VALUES:
|
|
42
|
+
take = 2
|
|
43
|
+
opts.extend(argv[i:i + take])
|
|
44
|
+
i += take
|
|
45
|
+
else:
|
|
46
|
+
pos.append(a)
|
|
47
|
+
i += 1
|
|
48
|
+
if any(p.startswith("-") and p != "-" for p in pos):
|
|
49
|
+
pos.insert(0, "--")
|
|
50
|
+
return opts + pos
|
|
51
|
+
|
|
52
|
+
|
|
22
53
|
def parse_args(argv):
|
|
23
54
|
p = argparse.ArgumentParser(
|
|
24
55
|
prog="pureshellcheck",
|
|
@@ -45,7 +76,7 @@ def parse_args(argv):
|
|
|
45
76
|
help="use color (default: auto)")
|
|
46
77
|
p.add_argument("--version", action="version",
|
|
47
78
|
version="pureshellcheck %s" % __version__)
|
|
48
|
-
return p.parse_args(argv)
|
|
79
|
+
return p.parse_args(_reorder(argv))
|
|
49
80
|
|
|
50
81
|
|
|
51
82
|
def parse_excludes(items):
|
|
@@ -65,7 +96,9 @@ def check_file(path, args, excluded, min_rank):
|
|
|
65
96
|
source = sys.stdin.read()
|
|
66
97
|
name = "-"
|
|
67
98
|
else:
|
|
68
|
-
|
|
99
|
+
# utf-8-sig: tolerate a UTF-8 BOM, which would otherwise break
|
|
100
|
+
# shebang detection
|
|
101
|
+
with open(path, encoding="utf-8-sig", errors="replace") as f:
|
|
69
102
|
source = f.read()
|
|
70
103
|
name = path
|
|
71
104
|
shell = args.shell if args.shell != "busybox" else "ash"
|
|
@@ -148,9 +181,11 @@ def main(argv=None):
|
|
|
148
181
|
else:
|
|
149
182
|
json_items.extend(finding_json(name, f) for f in findings)
|
|
150
183
|
if args.format == "json":
|
|
184
|
+
import json
|
|
151
185
|
json.dump(json_items, out)
|
|
152
186
|
out.write("\n")
|
|
153
187
|
elif args.format == "json1":
|
|
188
|
+
import json
|
|
154
189
|
json.dump({"comments": json_items}, out)
|
|
155
190
|
out.write("\n")
|
|
156
191
|
if had_error:
|
|
@@ -22,6 +22,15 @@ END_KEYWORDS = {
|
|
|
22
22
|
# Characters that terminate an unquoted word.
|
|
23
23
|
METACHARS = " \t\n;&|<>()"
|
|
24
24
|
|
|
25
|
+
# Cap on nested constructs - subshells "(", command substitutions "$(",
|
|
26
|
+
# brace/arithmetic groups - which re-enter read_command or read_arith_primary.
|
|
27
|
+
# Arithmetic nesting is the most frame-hungry (its precedence chain adds ~15
|
|
28
|
+
# interpreter frames per level), so 40 trips at ~600 frames: comfortably under
|
|
29
|
+
# CPython's default 1000 limit on every platform (including Windows' ~1 MB
|
|
30
|
+
# stack) even from a deep call site, yet far above any real script. Deeply
|
|
31
|
+
# nested input like "$( $( $(" or "(((((" raises a clean ParseError.
|
|
32
|
+
MAX_NESTING = 40
|
|
33
|
+
|
|
25
34
|
UNQUOTED_RUN = re.compile(r"""[^\\'"$`*?\[\](){}<>|&;\s!#~]+""")
|
|
26
35
|
DQUOTE_RUN = re.compile(r'[^"\\$`]+')
|
|
27
36
|
SQUOTE_END = re.compile(r"'")
|
|
@@ -81,6 +90,17 @@ class ParseError(Exception):
|
|
|
81
90
|
self.code = code
|
|
82
91
|
|
|
83
92
|
|
|
93
|
+
class _NestingLimit(Exception):
|
|
94
|
+
"""Raised when input nests deeper than MAX_NESTING. Deliberately NOT a
|
|
95
|
+
ParseError so the parser's backtracking (which catches ParseError to try
|
|
96
|
+
alternatives) cannot swallow it; parse() turns it into a ParseError."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, message, pos):
|
|
99
|
+
super().__init__(message)
|
|
100
|
+
self.message = message
|
|
101
|
+
self.pos = pos
|
|
102
|
+
|
|
103
|
+
|
|
84
104
|
class Directive:
|
|
85
105
|
"""A `# shellcheck key=value` comment."""
|
|
86
106
|
|
|
@@ -104,6 +124,11 @@ class Parser:
|
|
|
104
124
|
self.pending_heredocs = []
|
|
105
125
|
self.directives = []
|
|
106
126
|
self.line_had_command = False
|
|
127
|
+
# Smallest index from which a glob-class scan reached EOF with no
|
|
128
|
+
# closing ']' or metachar; from there on every '[' is a literal.
|
|
129
|
+
# Caps the otherwise O(n^2) rescan on input like "[1,[1,[1,...".
|
|
130
|
+
self._glob_eof_from = self.n + 1
|
|
131
|
+
self._nesting = 0 # current compound-command nesting depth
|
|
107
132
|
|
|
108
133
|
# ------------------------------------------------------------------
|
|
109
134
|
# Low-level helpers
|
|
@@ -192,6 +217,13 @@ class Parser:
|
|
|
192
217
|
# Script / lists
|
|
193
218
|
|
|
194
219
|
def parse(self):
|
|
220
|
+
try:
|
|
221
|
+
return self._parse()
|
|
222
|
+
except _NestingLimit as e:
|
|
223
|
+
# Surface the internal depth-limit signal as the public error type.
|
|
224
|
+
raise ParseError(e.message, e.pos) from None
|
|
225
|
+
|
|
226
|
+
def _parse(self):
|
|
195
227
|
shebang = None
|
|
196
228
|
if self.src.startswith("#!"):
|
|
197
229
|
end = self.src.find("\n")
|
|
@@ -340,6 +372,20 @@ class Parser:
|
|
|
340
372
|
# Commands
|
|
341
373
|
|
|
342
374
|
def read_command(self):
|
|
375
|
+
# Every nested compound command - subshell, command substitution,
|
|
376
|
+
# brace/arithmetic group - re-enters here, so this bounds parser
|
|
377
|
+
# recursion and keeps adversarial input from overflowing the stack.
|
|
378
|
+
self._nesting += 1
|
|
379
|
+
if self._nesting > MAX_NESTING:
|
|
380
|
+
self._nesting -= 1
|
|
381
|
+
raise _NestingLimit("commands nested too deeply (over %d levels)"
|
|
382
|
+
% MAX_NESTING, self.i)
|
|
383
|
+
try:
|
|
384
|
+
return self._read_command()
|
|
385
|
+
finally:
|
|
386
|
+
self._nesting -= 1
|
|
387
|
+
|
|
388
|
+
def _read_command(self):
|
|
343
389
|
self.skip_inline_ws()
|
|
344
390
|
if self.at_end():
|
|
345
391
|
return None
|
|
@@ -1366,11 +1412,17 @@ class Parser:
|
|
|
1366
1412
|
"""Parse [...] as a glob character class, else literal '['."""
|
|
1367
1413
|
src, n = self.src, self.n
|
|
1368
1414
|
start = self.i
|
|
1415
|
+
# We already proved the rest of the input has no closing ']' (nor a
|
|
1416
|
+
# metachar) from an earlier '['; this one can't open a class either.
|
|
1417
|
+
if start >= self._glob_eof_from:
|
|
1418
|
+
self.i = start + 1
|
|
1419
|
+
return Node("T_Literal", start, self.i, text="[")
|
|
1369
1420
|
j = self.i + 1
|
|
1370
1421
|
if j < n and src[j] in "!^":
|
|
1371
1422
|
j += 1
|
|
1372
1423
|
if j < n and src[j] == "]":
|
|
1373
1424
|
j += 1
|
|
1425
|
+
jumped = False
|
|
1374
1426
|
while j < n:
|
|
1375
1427
|
c = src[j]
|
|
1376
1428
|
if c == "]":
|
|
@@ -1384,8 +1436,15 @@ class Parser:
|
|
|
1384
1436
|
if end == -1:
|
|
1385
1437
|
break
|
|
1386
1438
|
j = end + 2
|
|
1439
|
+
jumped = True
|
|
1387
1440
|
continue
|
|
1388
1441
|
j += 1
|
|
1442
|
+
else:
|
|
1443
|
+
# Reached EOF with no ']' or metachar. Unless a [:class:] jump
|
|
1444
|
+
# skipped over text, no later '[' can find one either - remember
|
|
1445
|
+
# it so the next '[' bails in O(1).
|
|
1446
|
+
if not jumped:
|
|
1447
|
+
self._glob_eof_from = start
|
|
1389
1448
|
self.i = start + 1
|
|
1390
1449
|
return Node("T_Literal", start, self.i, text="[")
|
|
1391
1450
|
|
|
@@ -1842,6 +1901,20 @@ class Parser:
|
|
|
1842
1901
|
return operand
|
|
1843
1902
|
|
|
1844
1903
|
def read_arith_primary(self):
|
|
1904
|
+
# Nested "(...)" in arithmetic re-enters here, a separate recursion
|
|
1905
|
+
# from read_command, so it shares the same nesting guard to keep
|
|
1906
|
+
# "(((((..." / "$((((..." from overflowing the stack.
|
|
1907
|
+
self._nesting += 1
|
|
1908
|
+
if self._nesting > MAX_NESTING:
|
|
1909
|
+
self._nesting -= 1
|
|
1910
|
+
raise _NestingLimit("arithmetic nested too deeply (over %d levels)"
|
|
1911
|
+
% MAX_NESTING, self.i)
|
|
1912
|
+
try:
|
|
1913
|
+
return self._read_arith_primary()
|
|
1914
|
+
finally:
|
|
1915
|
+
self._nesting -= 1
|
|
1916
|
+
|
|
1917
|
+
def _read_arith_primary(self):
|
|
1845
1918
|
self.skip_arith_ws()
|
|
1846
1919
|
start = self.i
|
|
1847
1920
|
src, n = self.src, self.n
|
|
@@ -82,15 +82,28 @@ def walk(node):
|
|
|
82
82
|
stack.extend(reversed(children))
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
# Node kinds whose fields can never contain child nodes.
|
|
86
|
+
LEAF_KINDS = frozenset({
|
|
87
|
+
"T_Literal", "T_SingleQuoted", "T_DollarSingleQuoted", "T_Glob",
|
|
88
|
+
"TA_Literal", "TA_Empty", "T_IoDuplicate", "TC_Empty",
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
_EMPTY = ()
|
|
92
|
+
|
|
93
|
+
|
|
85
94
|
def set_parents(root):
|
|
86
95
|
"""Link parents, cache children, and return all nodes in doc order."""
|
|
87
96
|
nodes = []
|
|
88
97
|
append = nodes.append
|
|
89
98
|
stack = [root]
|
|
90
99
|
pop = stack.pop
|
|
100
|
+
leaf = LEAF_KINDS
|
|
91
101
|
while stack:
|
|
92
102
|
n = pop()
|
|
93
103
|
append(n)
|
|
104
|
+
if n.kind in leaf:
|
|
105
|
+
n.children = _EMPTY
|
|
106
|
+
continue
|
|
94
107
|
children = []
|
|
95
108
|
add = children.append
|
|
96
109
|
for value in n.fields.values():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pureshellcheck
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A pure Python reimplementation of ShellCheck's most common checks
|
|
5
5
|
Author: adam2go
|
|
6
6
|
License: MIT
|
|
@@ -157,24 +157,25 @@ no process spawn, no binary. A one-line snippet checks in **~40 µs**
|
|
|
157
157
|
(~25,000 checks/second); throughput on large scripts is ~57k lines/s. CLI
|
|
158
158
|
time is dominated by CPython interpreter startup (~20 ms).
|
|
159
159
|
|
|
160
|
-
**v0.2.
|
|
160
|
+
**v0.2.x vs v0.1.0** (controlled before/after,
|
|
161
161
|
`python tools/bench_compare.py`: baseline wheel from PyPI vs this tree in
|
|
162
162
|
the same interpreter, 25–200 in-process repeats, outputs verified
|
|
163
163
|
identical on every workload):
|
|
164
164
|
|
|
165
|
-
| workload | v0.1.0 | v0.2.
|
|
165
|
+
| workload | v0.1.0 | v0.2.1 | improvement |
|
|
166
166
|
|---|---|---|---|
|
|
167
|
-
| tiny (1 line) | 0.
|
|
168
|
-
| small (75 lines) | 2.
|
|
169
|
-
| medium (263 lines) |
|
|
170
|
-
| large (1216 lines) |
|
|
167
|
+
| tiny (1 line) | 0.058 ms | 0.036 ms | 1.6× |
|
|
168
|
+
| small (75 lines) | 2.44 ms | 1.21 ms | 2.0× |
|
|
169
|
+
| medium (263 lines) | 8.76 ms | 4.48 ms | 2.0× |
|
|
170
|
+
| large (1216 lines) | 46.2 ms | 20.0 ms | **2.3×** |
|
|
171
171
|
|
|
172
|
-
The
|
|
172
|
+
The speedups came from caching the AST child/parent structure and a
|
|
173
173
|
document-order node table (one traversal instead of dozens), making
|
|
174
174
|
variable states immutable tuples so branch snapshots are plain dict
|
|
175
|
-
copies, a banded Levenshtein for SC2153
|
|
176
|
-
reference implementation on 20,000 random
|
|
177
|
-
word/command resolution.
|
|
175
|
+
copies, a leaf-node fast path, a banded Levenshtein for SC2153
|
|
176
|
+
(fuzz-tested against the reference implementation on 20,000 random
|
|
177
|
+
pairs), and memoizing repeated word/command resolution. Package import is
|
|
178
|
+
3.6 ms; remaining CLI latency is CPython interpreter startup.
|
|
178
179
|
|
|
179
180
|
## Compatibility notes
|
|
180
181
|
|
|
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
|
{pureshellcheck-0.2.0 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|