icoa-cli 2.19.456 → 2.19.457

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.
@@ -9,7 +9,16 @@ export interface SyntaxEntry {
9
9
  export declare const SYNTAX_TABLE: SyntaxEntry[];
10
10
  /** Flat index of primary keys — what `/syntax` with no argument shows. */
11
11
  export declare const SYNTAX_INDEX: string[];
12
- /** Exact-then-substring match against the curated table. */
12
+ /**
13
+ * Exact-then-token match against the curated table.
14
+ *
15
+ * 🔴 Single-word keys match WHOLE TOKENS ONLY — never as a substring. A plain
16
+ * `q.includes(k)` let the 2-char key `in` (the set entry) fire inside
17
+ * "IndexError", so `/syntax IndexError` answered with set operations instead of
18
+ * falling through to pydoc. Caught by the live arena PTY walk, not by the unit
19
+ * test — the table is exactly where a wrong-but-confident answer hurts most.
20
+ * Multi-word keys ("2d list", "fast io") still match as a phrase.
21
+ */
13
22
  export declare function lookupLocal(query: string): SyntaxEntry | null;
14
23
  /** Nearest primary keys for a miss — suggestions, never a model. */
15
24
  export declare function nearKeys(query: string, limit?: number): string[];
@@ -1 +1 @@
1
- import{spawn as e}from"node:child_process";import chalk from"chalk";export const SYNTAX_TABLE=[{keys:["del","delete","remove index"],en:"Statement, not a method: deletes without returning. Only `del` can delete a slice.",zh:"语句(不是方法):删掉但不返回。只有 del 能删切片。",demo:["l = [0, 1, 2, 3, 4, 5]","del l[1] # → [0, 2, 3, 4, 5]","del l[1:3] # → [0, 4, 5] slice: pop cannot","del l[:] # → [] same as l.clear()","del l[9] # IndexError; del l[9:99] is silently fine"]},{keys:["pop","list.pop"],en:"Method: removes AND returns. O(1) at the end, O(n) anywhere else.",zh:"方法:删掉并返回。尾部 O(1),其他位置 O(n)。",demo:["l = [10, 20, 30]","l.pop() # → 30 l is [10, 20]","l.pop(0) # → 10 O(n): shifts everything left","[].pop() # IndexError: pop from empty list","d.pop(k, None) # dict: default avoids KeyError"]},{keys:["slice","slicing","sublist"],en:"l[start:stop:step] — stop is EXCLUSIVE, out-of-range is silently clamped.",zh:"l[起:止:步] — 止是开区间,越界静默截断不报错。",demo:["l = [0, 1, 2, 3, 4]","l[1:3] # → [1, 2] stop excluded","l[:2] l[2:] # → [0,1] [2,3,4]","l[-2:] # → [3, 4] last two","l[::-1] # → [4,3,2,1,0] reversed copy","l[9:99] # → [] no IndexError"]},{keys:["fstring","f-string","format","interpolate"],en:'f"..." embeds expressions; :.2f rounds, := width pads, {x=} prints name+value.',zh:'f"..." 内嵌表达式;:.2f 保留小数,:>5 补宽,{x=} 连名字一起打。',demo:["n, x = 7, 3.14159",'f"{n} items" # → "7 items"','f"{x:.2f}" # → "3.14"','f"{n:>5}" # → " 7" right-pad to 5','f"{n=}" # → "n=7" debug form']},{keys:["comprehension","listcomp","dictcomp","setcomp"],en:"Build a list/dict/set in one expression; the `if` filters, and it is faster than append in a loop.",zh:"一行构造 list/dict/set;if 做过滤,比循环里 append 快。",demo:["[x * x for x in range(5)] # → [0,1,4,9,16]","[x for x in l if x % 2 == 0] # filter","{k: len(k) for k in words} # dict","{x % 3 for x in l} # set (dedups)","[(i, c) for i, c in enumerate(s)] # pairs"]},{keys:["sort","sorted","key","sort by"],en:"sorted() returns a new list; l.sort() edits in place and returns None. key= is the sort field.",zh:"sorted() 返回新表;l.sort() 原地改并返回 None。key= 指定按什么排。",demo:["sorted(l) # new list, ascending","sorted(l, reverse=True) # descending","sorted(ps, key=lambda p: p[1]) # by 2nd field","sorted(ps, key=lambda p: (-p[1], p[0])) # desc, then asc tiebreak","l.sort() # in place — returns None!"]},{keys:["stdin","input","read input","fast io"],en:"input() per line; for big input read all of stdin at once — it is far faster.",zh:"input() 一行一行;数据量大时一次读完整个 stdin,快得多。",demo:["n = int(input()) # one number","a = list(map(int, input().split())) # a line of numbers","import sys","data = sys.stdin.read().split() # everything, fastest","for line in sys.stdin: # stream line by line"]},{keys:["print","output","sep","end"],en:"print joins args with a space and adds a newline; sep=/end= change both.",zh:"print 用空格连接参数并换行;sep=/end= 可改。",demo:['print(a, b) # "1 2\\n"',"print(*l) # unpack: each item, space-separated",'print(" ".join(map(str, l))) # explicit, no trailing space','print(x, end="") # no newline','print(a, b, sep=",") # "1,2"']},{keys:["2d list","grid","matrix","2d array"],en:"TRAP: [[0]*m]*n shares ONE row — every row changes together. Build rows in a comprehension.",zh:"陷阱:[[0]*m]*n 是同一行的 n 个引用,改一行全变。用推导式逐行造。",demo:["g = [[0] * 3 for _ in range(2)] # CORRECT","g[0][0] = 9 # → [[9,0,0], [0,0,0]]","","bad = [[0] * 3] * 2 # WRONG — aliased","bad[0][0] = 9 # → [[9,0,0], [9,0,0]] both rows!"]},{keys:["copy","deepcopy","alias","reference"],en:"b = a does NOT copy a list — both names point at one object. Slice or list() for a shallow copy.",zh:"b = a 不是拷贝,两个名字指同一个对象。浅拷贝用切片或 list()。",demo:["b = a # same object — a changes too","b = a[:] # shallow copy (or list(a))","import copy","b = copy.deepcopy(a) # nested lists too"]},{keys:["recursionerror","recursion limit","setrecursionlimit"],en:"CPython caps nesting near 1000 calls. Raise the limit, or rewrite as a loop (usually the right answer).",zh:"CPython 嵌套上限约 1000 层。可以抬上限,但改成循环通常才是对的。",demo:["import sys","sys.setrecursionlimit(300000) # buys depth, not speed","# depth grows with n → prefer iteration:","total = 0","for i in range(1, n + 1): total += i"]},{keys:["dict get","keyerror","default","setdefault"],en:"d[k] raises KeyError; d.get(k, default) does not. Counting? use a Counter.",zh:"d[k] 缺键报 KeyError;d.get(k, 默认) 不报。计数用 Counter。",demo:["d.get(k) # → None if missing","d.get(k, 0) + 1 # count safely","d.setdefault(k, []).append(v) # group into lists","from collections import Counter, defaultdict","Counter(l).most_common(3) # top 3 by frequency"]},{keys:["divide","division","modulo","floor","divmod"],en:"/ is always float; // floors (toward -inf, watch negatives); % follows the divisor sign.",zh:"/ 一定是浮点;// 向下取整(负数会咬人);% 的符号跟除数。",demo:["7 / 2 # → 3.5 float, even for ints","7 // 2 # → 3 floor","-7 // 2 # → -4 NOT -3","-7 % 2 # → 1 sign follows divisor","divmod(7, 2) # → (3, 1)"]},{keys:["try","except","finally","raise"],en:"Catch the narrowest type you expect; finally always runs; bare except hides real bugs.",zh:"只抓你预期的最窄类型;finally 一定执行;裸 except 会藏真 bug。",demo:["try:"," x = int(s)","except ValueError:"," x = 0","finally:"," ... # always runs"]},{keys:["unpack","star","args","kwargs","swap"],en:"* spreads an iterable, ** spreads a dict; a, *rest = l splits head from tail.",zh:"* 展开可迭代,** 展开字典;a, *rest = l 拆头尾。",demo:["a, b = b, a # swap, no temp","a, *rest = [1, 2, 3] # a=1 rest=[2,3]","f(*l) # positional spread","f(**d) # keyword spread","x, y = map(int, input().split())"]},{keys:["enumerate","zip","index in loop"],en:"enumerate gives (index, item); zip walks lists in parallel and stops at the shortest.",zh:"enumerate 给 (下标, 元素);zip 并行遍历,以最短的为准。",demo:["for i, c in enumerate(s): ...","for i, c in enumerate(s, 1): ... # start at 1","for a, b in zip(xs, ys): ...","list(zip(*rows)) # transpose a grid"]},{keys:["string methods","split","join","strip","replace"],en:"Strings are immutable — every method returns a NEW string.",zh:"字符串不可变 —— 每个方法都返回新字符串。",demo:["s.split() # on any whitespace → list",'s.split(",") # on a comma','"-".join(parts) # list → string (parts must be str)',"s.strip() # trim both ends","s.replace(a, b) # returns new; s unchanged"]},{keys:["ternary","inline if","conditional expression"],en:"Value-position if: `a if cond else b` — one expression, always needs the else.",zh:"值位置的 if:a if 条件 else b —— 是表达式,else 不能省。",demo:["x = 1 if ok else -1",'print("yes" if n > 0 else "no")',"l = [f(x) if x else 0 for x in xs]"]},{keys:["set","dedup","union","intersection","in"],en:"set() dedups and makes `in` O(1) instead of O(n) — the single biggest easy speedup.",zh:"set() 去重,并让 in 从 O(n) 变 O(1) —— 最容易拿到的一次加速。",demo:["seen = set(); seen.add(x)","if x in seen: ... # O(1); on a list this is O(n)","a | b a & b a - b # union / intersect / difference","len(set(l)) # distinct count"]},{keys:["walrus",":=","assignment expression"],en:"Assign inside an expression — mostly for while-read loops.",zh:"在表达式里赋值 —— 主要用在 while 读输入。",demo:["while (line := sys.stdin.readline()):"," ...","if (m := len(l)) > 3: print(m)"]},{keys:["lambda","anonymous function"],en:"One-expression function, no return keyword. Mostly for key= / sort.",zh:"单表达式函数,不写 return。主要给 key= / 排序用。",demo:["key=lambda p: p[1]","key=lambda p: (-p[1], p[0]) # desc then asc","f = lambda x: x * 2"]},{keys:["range","loop n times","countdown"],en:"range(stop) / range(start, stop[, step]) — stop is EXCLUSIVE.",zh:"range(止) / range(起, 止[, 步]) —— 止是开区间。",demo:["range(5) # 0 1 2 3 4","range(1, 6) # 1..5","range(10, 0, -1) # 10 down to 1","range(0, 10, 2) # 0 2 4 6 8"]},{keys:["float","rounding","precision","isclose"],en:"Binary floats are inexact: 0.1+0.2 != 0.3. Compare with a tolerance, never ==.",zh:"二进制浮点不精确:0.1+0.2 != 0.3。比较要给容差,别用 ==。",demo:["0.1 + 0.2 == 0.3 # → False","abs(a - b) < 1e-9 # compare like this","round(2.675, 2) # → 2.67 (not 2.68)",'f"{x:.6f}" # print fixed decimals']},{keys:["truthy","falsy","empty check","none"],en:'Empty containers, 0 and "" are falsy. Test None with `is None`, not ==.',zh:'空容器、0、"" 都是假。判 None 用 is None,不用 ==。',demo:["if not l: ... # empty list/str/dict","if x is None: ... # None specifically","if l: ... # non-empty","# careful: 0 and [] are both falsy but not equal"]}];export const SYNTAX_INDEX=SYNTAX_TABLE.map(e=>e.keys[0]);function t(e){return n(e).toLowerCase()}function n(e){return e.trim().replace(/^\/+/,"").replace(/[?。?]+$/,"").replace(/\s+/g," ")}export function lookupLocal(e){const n=t(e);if(!n)return null;for(const e of SYNTAX_TABLE)if(e.keys.some(e=>e===n))return e;const s=n.split(" ").filter(e=>e.length>=2);let o=null;for(const e of SYNTAX_TABLE){let t=0;for(const o of e.keys)n.includes(o)?t+=2*o.length:s.includes(o)&&(t+=o.length);t>0&&(!o||t>o.score)&&(o={e:e,score:t})}return o?o.e:null}export function nearKeys(e,n=6){const s=t(e).split(" ").filter(e=>e.length>=3),o=SYNTAX_INDEX.filter(e=>s.some(t=>e.includes(t)||t.includes(e)));return(o.length?o:SYNTAX_INDEX).slice(0,n)}export function lookupPydoc(t,s,o=14){const i=n(s);return i.length>60||!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*){0,2}$/.test(i)?Promise.resolve(null):new Promise(n=>{let s="",r=!1;const l=e=>{r||(r=!0,n(e))};let a;try{a=e(t,["-c","\nimport io, sys, contextlib, pydoc\nq = sys.argv[1] if len(sys.argv) > 1 else ''\ncands = [q] + ([] if '.' in q else ['list.' + q, 'str.' + q, 'dict.' + q, 'set.' + q])\nfor c in cands:\n try:\n txt = pydoc.render_doc(c, renderer=pydoc.plaintext)\n except Exception:\n continue\n print('OBJ ' + c); print(txt); sys.exit(0)\nbuf = io.StringIO()\ntry:\n with contextlib.redirect_stdout(buf):\n pydoc.Helper(output=buf).help(q.upper())\n out = buf.getvalue()\nexcept Exception:\n out = ''\nif out.strip() and 'no python documentation' not in out.lower():\n print('TOPIC ' + q.upper()); print(out); sys.exit(0)\nsys.exit(1)\n",i],{stdio:["ignore","pipe","ignore"]})}catch{return void l(null)}const d=setTimeout(()=>{try{a.kill("SIGKILL")}catch{}l(null)},4e3);a.stdout?.on("data",e=>{s+=String(e)}),a.on("error",()=>{clearTimeout(d),l(null)}),a.on("close",e=>{if(clearTimeout(d),0!==e||!s.trim())return void l(null);const t=s.split("\n"),n=(t[0]??"").replace(/^(OBJ|TOPIC)\s+/,"").trim(),i=/^\s*\|?\s*(Method resolution order|Methods? (defined here|inherited)|Data (descriptors|and other attributes)|Static methods|Class methods|Readonly properties|-{3,}|_{2})/,r=[];for(const e of t.slice(1)){if(/^Python Library Documentation/.test(e))continue;if(i.test(e))break;const t=e.replace(/^\s*\|\s?/," ");(t.trim()||r.length&&r[r.length-1].trim())&&r.push(t)}for(;r.length&&!r[0].trim();)r.shift();for(;r.length&&!r[r.length-1].trim();)r.pop();l({title:n,lines:r.slice(0,o)})})})}export function renderSyntax(e,t){console.log(),console.log(chalk.bold.white(` ${e.keys[0]}`)+chalk.gray(t?" (语法速查)":" (syntax lookup)")),console.log(chalk.gray(` ${t?e.zh:e.en}`)),console.log();for(const t of e.demo)console.log(t.trim()?chalk.cyan(` ${t}`):"");console.log()}export function renderPydoc(e,t){console.log(),console.log(chalk.bold.white(` ${e.title}`)+chalk.gray(t?" (你本机 Python 的官方文档)":" (your own Python's official doc)")),console.log();for(const t of e.lines)console.log(t.trim()?chalk.white(` ${t}`):"");console.log()}
1
+ import{spawn as e}from"node:child_process";import chalk from"chalk";export const SYNTAX_TABLE=[{keys:["del","delete","remove index"],en:"Statement, not a method: deletes without returning. Only `del` can delete a slice.",zh:"语句(不是方法):删掉但不返回。只有 del 能删切片。",demo:["l = [0, 1, 2, 3, 4, 5]","del l[1] # → [0, 2, 3, 4, 5]","del l[1:3] # → [0, 4, 5] slice: pop cannot","del l[:] # → [] same as l.clear()","del l[9] # IndexError; del l[9:99] is silently fine"]},{keys:["pop","list.pop"],en:"Method: removes AND returns. O(1) at the end, O(n) anywhere else.",zh:"方法:删掉并返回。尾部 O(1),其他位置 O(n)。",demo:["l = [10, 20, 30]","l.pop() # → 30 l is [10, 20]","l.pop(0) # → 10 O(n): shifts everything left","[].pop() # IndexError: pop from empty list","d.pop(k, None) # dict: default avoids KeyError"]},{keys:["slice","slicing","sublist"],en:"l[start:stop:step] — stop is EXCLUSIVE, out-of-range is silently clamped.",zh:"l[起:止:步] — 止是开区间,越界静默截断不报错。",demo:["l = [0, 1, 2, 3, 4]","l[1:3] # → [1, 2] stop excluded","l[:2] l[2:] # → [0,1] [2,3,4]","l[-2:] # → [3, 4] last two","l[::-1] # → [4,3,2,1,0] reversed copy","l[9:99] # → [] no IndexError"]},{keys:["fstring","f-string","format","interpolate"],en:'f"..." embeds expressions; :.2f rounds, := width pads, {x=} prints name+value.',zh:'f"..." 内嵌表达式;:.2f 保留小数,:>5 补宽,{x=} 连名字一起打。',demo:["n, x = 7, 3.14159",'f"{n} items" # → "7 items"','f"{x:.2f}" # → "3.14"','f"{n:>5}" # → " 7" right-pad to 5','f"{n=}" # → "n=7" debug form']},{keys:["comprehension","listcomp","dictcomp","setcomp"],en:"Build a list/dict/set in one expression; the `if` filters, and it is faster than append in a loop.",zh:"一行构造 list/dict/set;if 做过滤,比循环里 append 快。",demo:["[x * x for x in range(5)] # → [0,1,4,9,16]","[x for x in l if x % 2 == 0] # filter","{k: len(k) for k in words} # dict","{x % 3 for x in l} # set (dedups)","[(i, c) for i, c in enumerate(s)] # pairs"]},{keys:["sort","sorted","key","sort by"],en:"sorted() returns a new list; l.sort() edits in place and returns None. key= is the sort field.",zh:"sorted() 返回新表;l.sort() 原地改并返回 None。key= 指定按什么排。",demo:["sorted(l) # new list, ascending","sorted(l, reverse=True) # descending","sorted(ps, key=lambda p: p[1]) # by 2nd field","sorted(ps, key=lambda p: (-p[1], p[0])) # desc, then asc tiebreak","l.sort() # in place — returns None!"]},{keys:["stdin","input","read input","fast io"],en:"input() per line; for big input read all of stdin at once — it is far faster.",zh:"input() 一行一行;数据量大时一次读完整个 stdin,快得多。",demo:["n = int(input()) # one number","a = list(map(int, input().split())) # a line of numbers","import sys","data = sys.stdin.read().split() # everything, fastest","for line in sys.stdin: # stream line by line"]},{keys:["print","output","sep","end"],en:"print joins args with a space and adds a newline; sep=/end= change both.",zh:"print 用空格连接参数并换行;sep=/end= 可改。",demo:['print(a, b) # "1 2\\n"',"print(*l) # unpack: each item, space-separated",'print(" ".join(map(str, l))) # explicit, no trailing space','print(x, end="") # no newline','print(a, b, sep=",") # "1,2"']},{keys:["2d list","grid","matrix","2d array"],en:"TRAP: [[0]*m]*n shares ONE row — every row changes together. Build rows in a comprehension.",zh:"陷阱:[[0]*m]*n 是同一行的 n 个引用,改一行全变。用推导式逐行造。",demo:["g = [[0] * 3 for _ in range(2)] # CORRECT","g[0][0] = 9 # → [[9,0,0], [0,0,0]]","","bad = [[0] * 3] * 2 # WRONG — aliased","bad[0][0] = 9 # → [[9,0,0], [9,0,0]] both rows!"]},{keys:["copy","deepcopy","alias","reference"],en:"b = a does NOT copy a list — both names point at one object. Slice or list() for a shallow copy.",zh:"b = a 不是拷贝,两个名字指同一个对象。浅拷贝用切片或 list()。",demo:["b = a # same object — a changes too","b = a[:] # shallow copy (or list(a))","import copy","b = copy.deepcopy(a) # nested lists too"]},{keys:["recursionerror","recursion limit","setrecursionlimit"],en:"CPython caps nesting near 1000 calls. Raise the limit, or rewrite as a loop (usually the right answer).",zh:"CPython 嵌套上限约 1000 层。可以抬上限,但改成循环通常才是对的。",demo:["import sys","sys.setrecursionlimit(300000) # buys depth, not speed","# depth grows with n → prefer iteration:","total = 0","for i in range(1, n + 1): total += i"]},{keys:["dict get","keyerror","default","setdefault"],en:"d[k] raises KeyError; d.get(k, default) does not. Counting? use a Counter.",zh:"d[k] 缺键报 KeyError;d.get(k, 默认) 不报。计数用 Counter。",demo:["d.get(k) # → None if missing","d.get(k, 0) + 1 # count safely","d.setdefault(k, []).append(v) # group into lists","from collections import Counter, defaultdict","Counter(l).most_common(3) # top 3 by frequency"]},{keys:["divide","division","modulo","floor","divmod"],en:"/ is always float; // floors (toward -inf, watch negatives); % follows the divisor sign.",zh:"/ 一定是浮点;// 向下取整(负数会咬人);% 的符号跟除数。",demo:["7 / 2 # → 3.5 float, even for ints","7 // 2 # → 3 floor","-7 // 2 # → -4 NOT -3","-7 % 2 # → 1 sign follows divisor","divmod(7, 2) # → (3, 1)"]},{keys:["try","except","finally","raise"],en:"Catch the narrowest type you expect; finally always runs; bare except hides real bugs.",zh:"只抓你预期的最窄类型;finally 一定执行;裸 except 会藏真 bug。",demo:["try:"," x = int(s)","except ValueError:"," x = 0","finally:"," ... # always runs"]},{keys:["unpack","star","args","kwargs","swap"],en:"* spreads an iterable, ** spreads a dict; a, *rest = l splits head from tail.",zh:"* 展开可迭代,** 展开字典;a, *rest = l 拆头尾。",demo:["a, b = b, a # swap, no temp","a, *rest = [1, 2, 3] # a=1 rest=[2,3]","f(*l) # positional spread","f(**d) # keyword spread","x, y = map(int, input().split())"]},{keys:["enumerate","zip","index in loop"],en:"enumerate gives (index, item); zip walks lists in parallel and stops at the shortest.",zh:"enumerate 给 (下标, 元素);zip 并行遍历,以最短的为准。",demo:["for i, c in enumerate(s): ...","for i, c in enumerate(s, 1): ... # start at 1","for a, b in zip(xs, ys): ...","list(zip(*rows)) # transpose a grid"]},{keys:["string methods","split","join","strip","replace"],en:"Strings are immutable — every method returns a NEW string.",zh:"字符串不可变 —— 每个方法都返回新字符串。",demo:["s.split() # on any whitespace → list",'s.split(",") # on a comma','"-".join(parts) # list → string (parts must be str)',"s.strip() # trim both ends","s.replace(a, b) # returns new; s unchanged"]},{keys:["ternary","inline if","conditional expression"],en:"Value-position if: `a if cond else b` — one expression, always needs the else.",zh:"值位置的 if:a if 条件 else b —— 是表达式,else 不能省。",demo:["x = 1 if ok else -1",'print("yes" if n > 0 else "no")',"l = [f(x) if x else 0 for x in xs]"]},{keys:["set","dedup","union","intersection","in"],en:"set() dedups and makes `in` O(1) instead of O(n) — the single biggest easy speedup.",zh:"set() 去重,并让 in 从 O(n) 变 O(1) —— 最容易拿到的一次加速。",demo:["seen = set(); seen.add(x)","if x in seen: ... # O(1); on a list this is O(n)","a | b a & b a - b # union / intersect / difference","len(set(l)) # distinct count"]},{keys:["walrus",":=","assignment expression"],en:"Assign inside an expression — mostly for while-read loops.",zh:"在表达式里赋值 —— 主要用在 while 读输入。",demo:["while (line := sys.stdin.readline()):"," ...","if (m := len(l)) > 3: print(m)"]},{keys:["lambda","anonymous function"],en:"One-expression function, no return keyword. Mostly for key= / sort.",zh:"单表达式函数,不写 return。主要给 key= / 排序用。",demo:["key=lambda p: p[1]","key=lambda p: (-p[1], p[0]) # desc then asc","f = lambda x: x * 2"]},{keys:["range","loop n times","countdown"],en:"range(stop) / range(start, stop[, step]) — stop is EXCLUSIVE.",zh:"range(止) / range(起, 止[, 步]) —— 止是开区间。",demo:["range(5) # 0 1 2 3 4","range(1, 6) # 1..5","range(10, 0, -1) # 10 down to 1","range(0, 10, 2) # 0 2 4 6 8"]},{keys:["float","rounding","precision","isclose"],en:"Binary floats are inexact: 0.1+0.2 != 0.3. Compare with a tolerance, never ==.",zh:"二进制浮点不精确:0.1+0.2 != 0.3。比较要给容差,别用 ==。",demo:["0.1 + 0.2 == 0.3 # → False","abs(a - b) < 1e-9 # compare like this","round(2.675, 2) # → 2.67 (not 2.68)",'f"{x:.6f}" # print fixed decimals']},{keys:["truthy","falsy","empty check","none"],en:'Empty containers, 0 and "" are falsy. Test None with `is None`, not ==.',zh:'空容器、0、"" 都是假。判 None 用 is None,不用 ==。',demo:["if not l: ... # empty list/str/dict","if x is None: ... # None specifically","if l: ... # non-empty","# careful: 0 and [] are both falsy but not equal"]}];export const SYNTAX_INDEX=SYNTAX_TABLE.map(e=>e.keys[0]);function t(e){return n(e).toLowerCase()}function n(e){return e.trim().replace(/^\/+/,"").replace(/[?。?]+$/,"").replace(/\s+/g," ")}export function lookupLocal(e){const n=t(e);if(!n)return null;for(const e of SYNTAX_TABLE)if(e.keys.some(e=>e===n))return e;const s=n.split(" ").filter(Boolean);let o=null;for(const e of SYNTAX_TABLE){let t=0;for(const o of e.keys)o.includes(" ")?n.includes(o)&&(t+=2*o.length):s.includes(o)&&(t+=o.length);t>0&&(!o||t>o.score)&&(o={e:e,score:t})}return o?o.e:null}export function nearKeys(e,n=6){const s=t(e).split(" ").filter(e=>e.length>=3),o=SYNTAX_INDEX.filter(e=>s.some(t=>e.includes(t)||t.includes(e)));return(o.length?o:SYNTAX_INDEX).slice(0,n)}export function lookupPydoc(t,s,o=14){const i=n(s);return i.length>60||!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*){0,2}$/.test(i)?Promise.resolve(null):new Promise(n=>{let s="",r=!1;const l=e=>{r||(r=!0,n(e))};let a;try{a=e(t,["-c","\nimport io, sys, contextlib, pydoc\nq = sys.argv[1] if len(sys.argv) > 1 else ''\ncands = [q] + ([] if '.' in q else ['list.' + q, 'str.' + q, 'dict.' + q, 'set.' + q])\nfor c in cands:\n try:\n txt = pydoc.render_doc(c, renderer=pydoc.plaintext)\n except Exception:\n continue\n print('OBJ ' + c); print(txt); sys.exit(0)\nbuf = io.StringIO()\ntry:\n with contextlib.redirect_stdout(buf):\n pydoc.Helper(output=buf).help(q.upper())\n out = buf.getvalue()\nexcept Exception:\n out = ''\nif out.strip() and 'no python documentation' not in out.lower():\n print('TOPIC ' + q.upper()); print(out); sys.exit(0)\nsys.exit(1)\n",i],{stdio:["ignore","pipe","ignore"]})}catch{return void l(null)}const d=setTimeout(()=>{try{a.kill("SIGKILL")}catch{}l(null)},4e3);a.stdout?.on("data",e=>{s+=String(e)}),a.on("error",()=>{clearTimeout(d),l(null)}),a.on("close",e=>{if(clearTimeout(d),0!==e||!s.trim())return void l(null);const t=s.split("\n"),n=(t[0]??"").replace(/^(OBJ|TOPIC)\s+/,"").trim(),i=/^\s*\|?\s*(Method resolution order|Methods? (defined here|inherited)|Data (descriptors|and other attributes)|Static methods|Class methods|Readonly properties|-{3,}|_{2})/,r=[];for(const e of t.slice(1)){if(/^Python Library Documentation/.test(e))continue;if(i.test(e))break;const t=e.replace(/^\s*\|\s?/," ");(t.trim()||r.length&&r[r.length-1].trim())&&r.push(t)}for(;r.length&&!r[0].trim();)r.shift();for(;r.length&&!r[r.length-1].trim();)r.pop();l({title:n,lines:r.slice(0,o)})})})}export function renderSyntax(e,t){console.log(),console.log(chalk.bold.white(` ${e.keys[0]}`)+chalk.gray(t?" (语法速查)":" (syntax lookup)")),console.log(chalk.gray(` ${t?e.zh:e.en}`)),console.log();for(const t of e.demo)console.log(t.trim()?chalk.cyan(` ${t}`):"");console.log()}export function renderPydoc(e,t){console.log(),console.log(chalk.bold.white(` ${e.title}`)+chalk.gray(t?" (你本机 Python 的官方文档)":" (your own Python's official doc)")),console.log();for(const t of e.lines)console.log(t.trim()?chalk.white(` ${t}`):"");console.log()}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.456",
3
+ "version": "2.19.457",
4
4
  "description": "ICOA CLI — The world's first CLI-native cyber & AI security olympiad terminal: AI4CTF (Day 1), CTF4AI (Day 2), VLA4CTF (Pioneer Round — embodied AI)",
5
5
  "type": "module",
6
6
  "bin": {