chaffjs 0.6.0 → 0.8.0
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.
- package/README.md +11 -4
- package/dist/adapter-load.d.ts +12 -1
- package/dist/adapter-load.d.ts.map +1 -1
- package/dist/adapter-load.js +37 -13
- package/dist/adapter-load.js.map +1 -1
- package/dist/cli-args.d.ts +8 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +8 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-text.d.ts +26 -0
- package/dist/cli-text.d.ts.map +1 -0
- package/dist/cli-text.js +151 -0
- package/dist/cli-text.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +83 -86
- package/dist/cli.js.map +1 -1
- package/dist/commands/cite.d.ts +2 -1
- package/dist/commands/cite.d.ts.map +1 -1
- package/dist/commands/cite.js +39 -18
- package/dist/commands/cite.js.map +1 -1
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +1 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/skill.d.ts +22 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +68 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/tree.d.ts +11 -1
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +18 -6
- package/dist/commands/tree.js.map +1 -1
- package/dist/config/load.d.ts +9 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +27 -1
- package/dist/config/load.js.map +1 -1
- package/dist/config/rule-problems.d.ts +11 -0
- package/dist/config/rule-problems.d.ts.map +1 -0
- package/dist/config/rule-problems.js +31 -0
- package/dist/config/rule-problems.js.map +1 -0
- package/dist/config/write.d.ts.map +1 -1
- package/dist/config/write.js +29 -9
- package/dist/config/write.js.map +1 -1
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +3 -0
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/orthography.d.ts +9 -0
- package/dist/detectors/orthography.d.ts.map +1 -0
- package/dist/detectors/orthography.js +51 -0
- package/dist/detectors/orthography.js.map +1 -0
- package/dist/document.d.ts +3 -0
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +13 -3
- package/dist/document.js.map +1 -1
- package/dist/eval.d.ts +5 -1
- package/dist/eval.d.ts.map +1 -1
- package/dist/eval.js +10 -5
- package/dist/eval.js.map +1 -1
- package/dist/genre.d.ts +1 -1
- package/dist/genre.d.ts.map +1 -1
- package/dist/genre.js +3 -3
- package/dist/genre.js.map +1 -1
- package/dist/heading-text.d.ts +14 -0
- package/dist/heading-text.d.ts.map +1 -0
- package/dist/heading-text.js +28 -0
- package/dist/heading-text.js.map +1 -0
- package/dist/init.d.ts +2 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +62 -13
- package/dist/init.js.map +1 -1
- package/dist/orthography.d.ts +21 -0
- package/dist/orthography.d.ts.map +1 -0
- package/dist/orthography.js +110 -0
- package/dist/orthography.js.map +1 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/reasons.d.ts +15 -0
- package/dist/reasons.d.ts.map +1 -0
- package/dist/reasons.js +29 -0
- package/dist/reasons.js.map +1 -0
- package/dist/render/explain.d.ts.map +1 -1
- package/dist/render/explain.js +36 -12
- package/dist/render/explain.js.map +1 -1
- package/dist/render/friendly.d.ts.map +1 -1
- package/dist/render/friendly.js +32 -9
- package/dist/render/friendly.js.map +1 -1
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +57 -27
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/summary.d.ts +3 -2
- package/dist/render/summary.d.ts.map +1 -1
- package/dist/render/summary.js +24 -9
- package/dist/render/summary.js.map +1 -1
- package/dist/render/suppressions.d.ts +2 -1
- package/dist/render/suppressions.d.ts.map +1 -1
- package/dist/render/suppressions.js +34 -10
- package/dist/render/suppressions.js.map +1 -1
- package/dist/run.d.ts +3 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +22 -13
- package/dist/run.js.map +1 -1
- package/dist/structure/build.d.ts.map +1 -1
- package/dist/structure/build.js +19 -10
- package/dist/structure/build.js.map +1 -1
- package/dist/structure/cite.d.ts.map +1 -1
- package/dist/structure/cite.js +3 -1
- package/dist/structure/cite.js.map +1 -1
- package/dist/structure/issues.d.ts +5 -2
- package/dist/structure/issues.d.ts.map +1 -1
- package/dist/structure/issues.js +27 -9
- package/dist/structure/issues.js.map +1 -1
- package/dist/structure/numbering.d.ts.map +1 -1
- package/dist/structure/numbering.js +11 -6
- package/dist/structure/numbering.js.map +1 -1
- package/dist/ui.d.ts +14 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +14 -0
- package/dist/ui.js.map +1 -0
- package/dist/watch.d.ts +1 -1
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +2 -2
- package/dist/watch.js.map +1 -1
- package/package.json +3 -2
- package/rules/latin-spacing.yaml +27 -0
- package/rules/preferred-term.yaml +27 -0
- package/skills/chaff/SKILL.md +61 -0
- package/src/adapter-load.ts +41 -10
- package/src/cli-args.ts +9 -0
- package/src/cli-text.ts +182 -0
- package/src/cli.ts +100 -89
- package/src/commands/cite.ts +51 -18
- package/src/commands/eval.ts +1 -0
- package/src/commands/skill.ts +74 -0
- package/src/commands/tree.ts +27 -7
- package/src/config/load.ts +43 -1
- package/src/config/rule-problems.ts +43 -0
- package/src/config/write.ts +33 -9
- package/src/detectors/index.ts +3 -0
- package/src/detectors/orthography.ts +55 -0
- package/src/document.ts +23 -4
- package/src/eval.ts +16 -5
- package/src/genre.ts +4 -4
- package/src/heading-text.ts +26 -0
- package/src/init.ts +74 -15
- package/src/orthography.ts +115 -0
- package/src/plugin.ts +6 -0
- package/src/reasons.ts +44 -0
- package/src/render/explain.ts +46 -12
- package/src/render/friendly.ts +42 -10
- package/src/render/rules-json.ts +45 -10
- package/src/render/summary.ts +34 -9
- package/src/render/suppressions.ts +45 -10
- package/src/run.ts +35 -12
- package/src/structure/build.ts +20 -10
- package/src/structure/cite.ts +3 -1
- package/src/structure/issues.ts +33 -14
- package/src/structure/numbering.ts +12 -7
- package/src/ui.ts +23 -0
- package/src/watch.ts +2 -2
package/src/cli.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { runEval } from "./commands/eval.ts";
|
|
|
16
16
|
import { runTest } from "./commands/test.ts";
|
|
17
17
|
import { frontMatterGenre, guessGenre } from "./genre.ts";
|
|
18
18
|
import { GENRES, runInit } from "./init.ts";
|
|
19
|
+
import { targetsOf } from "./cli-args.ts";
|
|
19
20
|
import { loadRules } from "./rule-load.ts";
|
|
20
21
|
import { renderCompact } from "./render/compact.ts";
|
|
21
22
|
import { renderExplain } from "./render/explain.ts";
|
|
@@ -23,62 +24,50 @@ import { renderFriendly } from "./render/friendly.ts";
|
|
|
23
24
|
import { rulesJson } from "./render/rules-json.ts";
|
|
24
25
|
import { renderSarif } from "./render/sarif.ts";
|
|
25
26
|
import { VERSION } from "./version.ts";
|
|
26
|
-
import { runTree, treeTargets } from "./commands/tree.ts";
|
|
27
|
+
import { runTree, treeTargets, type TreeContext } from "./commands/tree.ts";
|
|
27
28
|
import { citeTargets, runCite } from "./commands/cite.ts";
|
|
29
|
+
import { runSkill } from "./commands/skill.ts";
|
|
30
|
+
import { homedir } from "node:os";
|
|
31
|
+
import { ruleProblems } from "./config/rule-problems.ts";
|
|
28
32
|
import { renderSummary, type FileOutcome } from "./render/summary.ts";
|
|
29
33
|
import { neededBy, runRules } from "./run.ts";
|
|
30
34
|
import type { Level, RuleDefinition } from "./plugin.ts";
|
|
35
|
+
import { CLI_TEXT, type CliText, type GenreSource } from "./cli-text.ts";
|
|
36
|
+
import { hostLanguage, uiLanguageOf, type UiLanguage } from "./ui.ts";
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
chaff <file|dir|glob>... 検査する(設定も API key も要りません)
|
|
35
|
-
chaff . この場所の Markdown を全部
|
|
36
|
-
chaff test <file|dir>... 意味を読む検査も動かす(API key が要ります)
|
|
37
|
-
判定役は chaff.yaml の ai_backend で選びます
|
|
38
|
-
chaff init chaff.yaml を作る
|
|
39
|
-
chaff eval <dir> 手元の文書で閾値を測り直す
|
|
40
|
-
chaff explain <rule> そのルールの意図と根拠を読む
|
|
41
|
-
chaff genres ジャンルの一覧
|
|
42
|
-
chaff tree <file> [--format sexp|json] 文書を番地の付いた木にする(条・項・定義・参照)
|
|
43
|
-
chaff cite <原文> <引用.json> 回答の引用(番地と引用文)が原文にあるかを確かめる
|
|
44
|
-
chaff rules --json いまの設定を JSON で出す(AI に渡す用)
|
|
45
|
-
chaff baseline <dir> いまある指摘を棚上げする(既存の repo に入れるとき)
|
|
46
|
-
chaff suppressions <dir> stet で黙らせている指摘を数える
|
|
47
|
-
chaff relax|strict|off <rule> [--why "理由"]
|
|
48
|
-
|
|
49
|
-
--compact エンジニア向けの 1 行形式
|
|
50
|
-
--experimental 試験中の rule も動かす
|
|
51
|
-
--show-baseline 棚上げした分も含めて全部見る
|
|
52
|
-
--watch 保存のたびに見直し、変わったところだけ出す
|
|
53
|
-
--dry-run test で、何を AI に送るかだけを見る(API を呼びません)
|
|
54
|
-
--sarif <path> 指摘を SARIF で書き出す(GitHub の PR の行に出すため)
|
|
55
|
-
|
|
56
|
-
この箇所だけ黙らせる: <!-- stet: rule-id — 理由 -->
|
|
57
|
-
|
|
58
|
-
値は strict / normal / relaxed / off の 4 つから選びます。
|
|
59
|
-
`;
|
|
38
|
+
/** Text for output that is not about one document. */
|
|
39
|
+
const hostText = (config: Config): CliText => CLI_TEXT[hostLanguage(config.language, process.env)];
|
|
60
40
|
|
|
61
41
|
const readConfig = (): Config => (existsSync(join(process.cwd(), CONFIG_FILE)) ? loadConfig(join(process.cwd(), CONFIG_FILE)) : EMPTY);
|
|
62
42
|
|
|
63
|
-
const resolveGenre = (path: string, source: string, config: Config): { genre: string; from:
|
|
64
|
-
//
|
|
43
|
+
const resolveGenre = (path: string, source: string, config: Config, cliGenre?: string): { genre: string; from: GenreSource } => {
|
|
44
|
+
// コマンドで指定したものが最優先。その実行だけの指定だから、設定より強い。
|
|
45
|
+
if (cliGenre !== undefined) return { genre: cliGenre, from: "--genre" };
|
|
46
|
+
// パスごとの上書きが次。「全体はこう、ここだけは違う」を書けるようにする。
|
|
65
47
|
const override = applyByPath(config.byPath, config.baseDir, path);
|
|
66
|
-
if (override.genre !== undefined) return { genre: override.genre, from:
|
|
67
|
-
if (config.genre !== undefined) return { genre: config.genre, from:
|
|
48
|
+
if (override.genre !== undefined) return { genre: override.genre, from: "by_path" };
|
|
49
|
+
if (config.genre !== undefined) return { genre: config.genre, from: "config" };
|
|
68
50
|
const guess = guessGenre(path, source, frontMatterGenre(source));
|
|
69
|
-
return guess === undefined ? { genre: "blog/tech", from: "
|
|
51
|
+
return guess === undefined ? { genre: "blog/tech", from: "default" } : { genre: guess.genre, from: guess.from };
|
|
70
52
|
};
|
|
71
53
|
|
|
54
|
+
/** resolveGenre with this run's --genre, for the commands that take it as a dependency. */
|
|
55
|
+
const genreFrom =
|
|
56
|
+
(argv: readonly string[]) =>
|
|
57
|
+
(path: string, source: string, config: Config): { genre: string; from: GenreSource } =>
|
|
58
|
+
resolveGenre(path, source, config, flag(argv, "--genre"));
|
|
59
|
+
|
|
72
60
|
const findRule = (rules: readonly RuleDefinition[], id: string | undefined): RuleDefinition | undefined => rules.find((rule) => rule.id === id);
|
|
73
61
|
|
|
74
62
|
const changeSetting = (command: Level, ruleId: string | undefined, why: string | undefined): number => {
|
|
75
63
|
const config = readConfig();
|
|
76
|
-
const
|
|
64
|
+
const language = config.language ?? hostLanguage(undefined, process.env);
|
|
65
|
+
const rule = findRule(loadRules(language), ruleId);
|
|
77
66
|
if (rule === undefined) {
|
|
78
|
-
console.error(
|
|
67
|
+
console.error(hostText(config).unknownRule(ruleId ?? hostText(config).unnamed));
|
|
79
68
|
return 1;
|
|
80
69
|
}
|
|
81
|
-
const outcome = applyLevel(join(process.cwd(), CONFIG_FILE), rule, command, why,
|
|
70
|
+
const outcome = applyLevel(join(process.cwd(), CONFIG_FILE), rule, command, why, language, process.env["USER"] ?? "unknown");
|
|
82
71
|
console.log(outcome.message);
|
|
83
72
|
return outcome.ok ? 0 : 1;
|
|
84
73
|
};
|
|
@@ -98,22 +87,22 @@ type Inspected = {
|
|
|
98
87
|
readonly all: readonly string[];
|
|
99
88
|
};
|
|
100
89
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
return
|
|
90
|
+
/** The first line for a file, in the file's language. */
|
|
91
|
+
const headerFor = (path: string, genre: string, from: GenreSource, language: string, shelved: number, hushed: number): string => {
|
|
92
|
+
const text = CLI_TEXT[uiLanguageOf(language)];
|
|
93
|
+
return text.header(path, genre, text.languageName(language), text.genreSource[from], shelved, hushed);
|
|
105
94
|
};
|
|
106
95
|
|
|
107
96
|
const inspect = async (path: string, config: Config, argv: readonly string[]): Promise<Inspected> => {
|
|
108
97
|
const source = await readFile(path, "utf8");
|
|
109
98
|
const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
|
|
110
99
|
const adapter = await loadAdapter(language);
|
|
111
|
-
const { genre, from } = resolveGenre(path, source, config);
|
|
100
|
+
const { genre, from } = resolveGenre(path, source, config, flag(argv, "--genre"));
|
|
112
101
|
const rules = loadRules(language);
|
|
113
102
|
const experimental = config.experimental || argv.includes("--experimental");
|
|
114
103
|
await adapter.prepare?.(neededBy(rules, config.rules, experimental, genre, language));
|
|
115
104
|
const doc = buildDocument(path, source, adapter, teamRules(config));
|
|
116
|
-
const raw = runRules(doc, rules, config.rules, experimental, genre);
|
|
105
|
+
const raw = runRules(doc, rules, config.rules, experimental, genre, config.limits);
|
|
117
106
|
// 応答は 3 つ。stet で黙らせたものは、ここで落とす。
|
|
118
107
|
const applied = applySuppressions(
|
|
119
108
|
source,
|
|
@@ -148,26 +137,42 @@ const writeSarif = (results: readonly Inspected[], argv: readonly string[]): voi
|
|
|
148
137
|
);
|
|
149
138
|
mkdirSync(dirname(path), { recursive: true });
|
|
150
139
|
writeFileSync(path, renderSarif(located, VERSION), "utf8");
|
|
151
|
-
console.log(
|
|
140
|
+
console.log(hostText(readConfig()).sarifWritten(path, located.length));
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** 効いていない設定は、結果の前に一度だけ言う。標準エラーに出すので、JSON や SARIF の出力は汚さない。 */
|
|
144
|
+
const warnRuleProblems = (config: Config, language: string): void => {
|
|
145
|
+
ruleProblems(config, loadRules(language), hostLanguage(config.language, process.env)).forEach((problem) => console.error(`chaff: ${problem}`));
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Several files end with one summary: in their language when they share one, else the host's. */
|
|
149
|
+
const summaryLanguage = (results: readonly Inspected[], config: Config): UiLanguage => {
|
|
150
|
+
const languages = new Set(results.map((result) => uiLanguageOf(result.language)));
|
|
151
|
+
const [only] = [...languages];
|
|
152
|
+
return languages.size === 1 && only !== undefined ? only : hostLanguage(config.language, process.env);
|
|
152
153
|
};
|
|
153
154
|
|
|
154
155
|
const lint = async (targets: readonly string[], argv: readonly string[]): Promise<number> => {
|
|
155
156
|
const paths = collectTargets(targets);
|
|
157
|
+
const config = readConfig();
|
|
156
158
|
if (paths.length === 0) {
|
|
157
159
|
// 0 件を成功にすると「CI は通っているが何も検証していない」状態が続く。§14。
|
|
158
|
-
console.error(
|
|
160
|
+
console.error(hostText(config).noMarkdown(targets.join(", ")));
|
|
159
161
|
return 1;
|
|
160
162
|
}
|
|
161
|
-
const config = readConfig();
|
|
162
163
|
const language = config.language ?? "ja";
|
|
163
164
|
if (packageFor(language) === undefined) {
|
|
164
|
-
console.error(
|
|
165
|
+
console.error(hostText(config).noAdapter(language));
|
|
165
166
|
return 1;
|
|
166
167
|
}
|
|
168
|
+
warnRuleProblems(config, language);
|
|
167
169
|
const results = await Promise.all(paths.map((path) => inspect(path, config, argv)));
|
|
168
170
|
writeSarif(results, argv);
|
|
169
171
|
results.filter((result) => result.outcome.findings.length > 0 || paths.length === 1).forEach((result) => console.log(result.text));
|
|
170
|
-
renderSummary(
|
|
172
|
+
renderSummary(
|
|
173
|
+
results.map((result) => result.outcome),
|
|
174
|
+
summaryLanguage(results, config),
|
|
175
|
+
).forEach((line) => console.log(line));
|
|
171
176
|
return results.some((result) => result.outcome.findings.some((finding) => finding.severity === "error")) ? 1 : 0;
|
|
172
177
|
};
|
|
173
178
|
|
|
@@ -177,11 +182,13 @@ const lint = async (targets: readonly string[], argv: readonly string[]): Promis
|
|
|
177
182
|
*/
|
|
178
183
|
const runWatch = async (targets: readonly string[], argv: readonly string[]): Promise<number> => {
|
|
179
184
|
const paths = collectTargets(targets);
|
|
185
|
+
const config = readConfig();
|
|
186
|
+
const text = hostText(config);
|
|
180
187
|
if (paths.length === 0) {
|
|
181
|
-
console.error(
|
|
188
|
+
console.error(text.noMarkdown(targets.join(", ")));
|
|
182
189
|
return 1;
|
|
183
190
|
}
|
|
184
|
-
|
|
191
|
+
warnRuleProblems(config, config.language ?? "ja");
|
|
185
192
|
const seen = new Map<string, Snapshot>();
|
|
186
193
|
const once = async (path: string, first: boolean): Promise<void> => {
|
|
187
194
|
const result = await inspect(path, config, argv);
|
|
@@ -189,13 +196,13 @@ const runWatch = async (targets: readonly string[], argv: readonly string[]): Pr
|
|
|
189
196
|
const before = seen.get(path) ?? {};
|
|
190
197
|
seen.set(path, after);
|
|
191
198
|
if (first) return;
|
|
192
|
-
const change = describeChange(before, after);
|
|
193
|
-
console.log(`${clock()} ${path} ${change ??
|
|
199
|
+
const change = describeChange(before, after, text.findingsUnit);
|
|
200
|
+
console.log(`${clock()} ${path} ${change ?? text.unchanged}`);
|
|
194
201
|
};
|
|
195
202
|
await Promise.all(paths.map((path) => once(path, true)));
|
|
196
203
|
const totals = [...seen.values()].reduce((sum, snapshot) => sum + Object.values(snapshot).reduce((inner, count) => inner + count, 0), 0);
|
|
197
|
-
console.log(
|
|
198
|
-
console.log(
|
|
204
|
+
console.log(text.watching(paths.length, totals));
|
|
205
|
+
console.log(text.watchHint);
|
|
199
206
|
const stop = watchPaths(paths, (path) => void once(path, false));
|
|
200
207
|
process.on("SIGINT", () => {
|
|
201
208
|
stop();
|
|
@@ -206,49 +213,33 @@ const runWatch = async (targets: readonly string[], argv: readonly string[]): Pr
|
|
|
206
213
|
|
|
207
214
|
const explain = (ruleId: string | undefined): number => {
|
|
208
215
|
const config = readConfig();
|
|
209
|
-
|
|
216
|
+
// The rule's limits differ by language (characters for Japanese, words for English): explain in the one being written.
|
|
217
|
+
const language = config.language ?? hostLanguage(undefined, process.env);
|
|
218
|
+
const text = CLI_TEXT[uiLanguageOf(language)];
|
|
210
219
|
const rules = loadRules(language);
|
|
211
220
|
const rule = findRule(rules, ruleId);
|
|
212
221
|
if (rule === undefined) {
|
|
213
222
|
const list = rules.map((entry) => ` ${entry.id}`).join("\n");
|
|
214
|
-
console.error(
|
|
223
|
+
console.error(text.unknownRuleWithList(ruleId ?? text.unnamed, list));
|
|
215
224
|
return 1;
|
|
216
225
|
}
|
|
217
226
|
const current = config.rules[rule.id] ?? (rule.status === "experimental" && !config.experimental ? "off" : "normal");
|
|
218
|
-
console.log(renderExplain(rule, current, language,
|
|
227
|
+
console.log(renderExplain(rule, current, language, text.unit(rule.id, language), config.genre));
|
|
219
228
|
return 0;
|
|
220
229
|
};
|
|
221
230
|
|
|
222
|
-
/** 閾値の単位。explain で「4 とは何の 4 か」を示すため。 */
|
|
223
|
-
const unitOf = (ruleId: string, language: string): string => {
|
|
224
|
-
if (ruleId === "bold-density") return "1000 字あたりの箇所数";
|
|
225
|
-
if (ruleId !== "max-sentence-length") return "回";
|
|
226
|
-
return language === "ja" ? "文字" : "語";
|
|
227
|
-
};
|
|
228
|
-
|
|
229
231
|
const runBaseline = async (targets: readonly string[], argv: readonly string[]): Promise<number> => {
|
|
230
232
|
const paths = collectTargets(targets.length > 0 ? targets : ["."]);
|
|
233
|
+
const config = readConfig();
|
|
231
234
|
if (paths.length === 0) {
|
|
232
|
-
console.error(
|
|
235
|
+
console.error(hostText(config).noMarkdownHere);
|
|
233
236
|
return 1;
|
|
234
237
|
}
|
|
235
|
-
const config = readConfig();
|
|
236
238
|
const results = await Promise.all(paths.map((path) => inspect(path, config, [...argv, "--show-baseline"])));
|
|
237
239
|
const entries = results.flatMap((result) => result.all);
|
|
238
240
|
const file = join(process.cwd(), BASELINE_FILE);
|
|
239
241
|
writeBaseline(file, entries);
|
|
240
|
-
console.log(
|
|
241
|
-
[
|
|
242
|
-
"",
|
|
243
|
-
` ${paths.length} ファイルを走査しました。`,
|
|
244
|
-
"",
|
|
245
|
-
` ${entries.length} 件の指摘を ${BASELINE_FILE} に記録しました。`,
|
|
246
|
-
" 以後、これらは報告されません。新しく増えたものだけが出ます。",
|
|
247
|
-
"",
|
|
248
|
-
` ${BASELINE_FILE} を commit してください。`,
|
|
249
|
-
"",
|
|
250
|
-
].join("\n"),
|
|
251
|
-
);
|
|
242
|
+
console.log(hostText(config).baselineDone(paths.length, entries.length, BASELINE_FILE).join("\n"));
|
|
252
243
|
return 0;
|
|
253
244
|
};
|
|
254
245
|
|
|
@@ -256,55 +247,75 @@ const runSuppressions = async (targets: readonly string[], argv: readonly string
|
|
|
256
247
|
const paths = collectTargets(targets.length > 0 ? targets : ["."]);
|
|
257
248
|
const config = readConfig();
|
|
258
249
|
const results = await Promise.all(paths.map((path) => inspect(path, config, [...argv, "--show-baseline"])));
|
|
259
|
-
console.log(
|
|
250
|
+
console.log(
|
|
251
|
+
renderSuppressions(
|
|
252
|
+
results.map((result) => result.perFile),
|
|
253
|
+
hostLanguage(config.language, process.env),
|
|
254
|
+
),
|
|
255
|
+
);
|
|
260
256
|
return 0;
|
|
261
257
|
};
|
|
262
258
|
|
|
263
259
|
type Handler = (argv: readonly string[]) => number | Promise<number>;
|
|
264
260
|
|
|
265
261
|
/** `--` で始まらない引数。対象のパス。 */
|
|
266
|
-
const positional = (argv: readonly string[]): string[] => argv.slice(1)
|
|
262
|
+
const positional = (argv: readonly string[]): string[] => targetsOf(argv.slice(1));
|
|
267
263
|
|
|
268
264
|
const showRules = (): number => {
|
|
269
265
|
const config = readConfig();
|
|
270
|
-
const language = config.language ??
|
|
266
|
+
const language = config.language ?? hostLanguage(undefined, process.env);
|
|
267
|
+
warnRuleProblems(config, language);
|
|
271
268
|
console.log(rulesJson(loadRules(language), config, language, config.genre ?? "blog/tech"));
|
|
272
269
|
return 0;
|
|
273
270
|
};
|
|
274
271
|
|
|
275
272
|
const showGenres = (): number => {
|
|
276
|
-
console.log(
|
|
273
|
+
console.log(hostText(readConfig()).genres(GENRES));
|
|
277
274
|
return 0;
|
|
278
275
|
};
|
|
279
276
|
|
|
277
|
+
const treeContext = (): TreeContext => {
|
|
278
|
+
const config = readConfig();
|
|
279
|
+
return { config, flag, ui: hostLanguage(config.language, process.env) };
|
|
280
|
+
};
|
|
281
|
+
|
|
280
282
|
/** 分岐を数珠つなぎにせず表にする。足すときに main を太らせない。 */
|
|
281
283
|
const HANDLERS: Readonly<Record<string, Handler>> = {
|
|
282
284
|
init: (argv) => {
|
|
283
|
-
runInit(process.cwd(), flag(argv, "--genre") ?? "blog/tech").forEach((line) => console.log(line));
|
|
285
|
+
runInit(process.cwd(), flag(argv, "--genre") ?? "blog/tech", hostLanguage(readConfig().language, process.env)).forEach((line) => console.log(line));
|
|
284
286
|
return 0;
|
|
285
287
|
},
|
|
286
288
|
genres: showGenres,
|
|
287
289
|
rules: showRules,
|
|
288
290
|
explain: (argv) => explain(argv[1]),
|
|
289
|
-
eval: (argv) => runEval(positional(argv), argv, { config: readConfig(), resolveGenre, flag }),
|
|
290
|
-
tree: (argv) => runTree(treeTargets(argv), argv,
|
|
291
|
-
cite: (argv) => runCite(citeTargets(argv), argv,
|
|
292
|
-
test: (argv) => runTest(positional(argv), argv, { config: readConfig(), resolveGenre, inspect }),
|
|
291
|
+
eval: (argv) => runEval(positional(argv), argv, { config: readConfig(), resolveGenre: genreFrom(argv), flag }),
|
|
292
|
+
tree: (argv) => runTree(treeTargets(argv), argv, treeContext()),
|
|
293
|
+
cite: (argv) => runCite(citeTargets(argv), argv, treeContext()),
|
|
294
|
+
test: (argv) => runTest(positional(argv), argv, { config: readConfig(), resolveGenre: genreFrom(argv), inspect }),
|
|
293
295
|
baseline: (argv) => runBaseline(positional(argv), argv),
|
|
294
296
|
suppressions: (argv) => runSuppressions(positional(argv), argv),
|
|
295
297
|
relax: (argv) => changeSetting("relaxed", argv[1], flag(argv, "--why")),
|
|
296
298
|
strict: (argv) => changeSetting("strict", argv[1], flag(argv, "--why")),
|
|
297
299
|
off: (argv) => changeSetting("off", argv[1], flag(argv, "--why")),
|
|
300
|
+
skill: (argv) => runSkill(argv, { cwd: process.cwd(), home: homedir(), ui: hostLanguage(readConfig().language, process.env) }),
|
|
298
301
|
};
|
|
299
302
|
|
|
303
|
+
/** Every subcommand. Anything else on the command line is a file to check. */
|
|
304
|
+
export const COMMANDS: readonly string[] = Object.keys(HANDLERS);
|
|
305
|
+
|
|
300
306
|
export const main = async (argv: readonly string[]): Promise<number> => {
|
|
301
307
|
const first = argv[0];
|
|
302
308
|
if (first === undefined || first === "--help" || first === "-h") {
|
|
303
|
-
console.log(
|
|
309
|
+
console.log(hostText(readConfig()).usage);
|
|
304
310
|
return first === undefined ? 1 : 0;
|
|
305
311
|
}
|
|
312
|
+
const cliGenre = flag(argv, "--genre");
|
|
313
|
+
if (cliGenre !== undefined && !GENRES.includes(cliGenre)) {
|
|
314
|
+
console.error(hostText(readConfig()).unknownGenre(cliGenre));
|
|
315
|
+
return 1;
|
|
316
|
+
}
|
|
306
317
|
const handler = HANDLERS[first];
|
|
307
318
|
if (handler !== undefined) return handler(argv);
|
|
308
|
-
const targets = (first === "lint" ? argv.slice(1) : argv)
|
|
319
|
+
const targets = targetsOf(first === "lint" ? argv.slice(1) : argv);
|
|
309
320
|
return argv.includes("--watch") ? runWatch(targets, argv) : lint(targets, argv);
|
|
310
321
|
};
|
package/src/commands/cite.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { checkCitations, type Citation, type CitationResult } from "../structure/cite.ts";
|
|
2
2
|
import { readSource, readTree, type TreeContext } from "./tree.ts";
|
|
3
|
+
import type { Texts, UiLanguage } from "../ui.ts";
|
|
3
4
|
|
|
4
|
-
const USAGE = "使い方: chaff cite <原文> <引用.json> [--format text|json] [--language ja|en|…]";
|
|
5
5
|
const FORMATS = ["text", "json"];
|
|
6
6
|
const QUOTE_WIDTH = 40;
|
|
7
7
|
const VALUED = ["--format", "--language"];
|
|
@@ -15,37 +15,70 @@ const isCitation = (value: unknown): value is Citation => isRecord(value) && typ
|
|
|
15
15
|
|
|
16
16
|
export type ParsedCitations = { readonly citations: readonly Citation[] } | { readonly error: string };
|
|
17
17
|
|
|
18
|
-
const parseJson = (text: string): { readonly value: unknown } | { readonly error: string } => {
|
|
18
|
+
const parseJson = (text: string, ui: UiLanguage): { readonly value: unknown } | { readonly error: string } => {
|
|
19
19
|
try {
|
|
20
20
|
return { value: JSON.parse(text) };
|
|
21
21
|
} catch (err) {
|
|
22
|
-
return { error:
|
|
22
|
+
return { error: TEXT[ui].notJson(err instanceof Error ? err.message : String(err)) };
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/** 引用の一覧。[{ address, quote }] の配列だけを受け取る。形が違えば理由を返す。 */
|
|
27
|
-
export const parseCitations = (text: string): ParsedCitations => {
|
|
28
|
-
const parsed = parseJson(text);
|
|
27
|
+
export const parseCitations = (text: string, ui: UiLanguage = "ja"): ParsedCitations => {
|
|
28
|
+
const parsed = parseJson(text, ui);
|
|
29
29
|
if ("error" in parsed) return parsed;
|
|
30
|
-
if (!Array.isArray(parsed.value)) return { error:
|
|
30
|
+
if (!Array.isArray(parsed.value)) return { error: TEXT[ui].notArray };
|
|
31
31
|
const entries: readonly unknown[] = parsed.value;
|
|
32
32
|
const bad = entries.findIndex((entry) => !isCitation(entry));
|
|
33
|
-
if (bad !== -1) return { error:
|
|
33
|
+
if (bad !== -1) return { error: TEXT[ui].badEntry(bad + 1) };
|
|
34
34
|
return { citations: entries.filter(isCitation).map((entry) => ({ address: entry.address, quote: entry.quote })) };
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
type CiteText = {
|
|
38
|
+
readonly usage: string;
|
|
39
|
+
readonly quoted: (text: string) => string;
|
|
40
|
+
readonly notJson: (why: string) => string;
|
|
41
|
+
readonly notArray: string;
|
|
42
|
+
readonly badEntry: (n: number) => string;
|
|
43
|
+
readonly results: Readonly<Record<CitationResult["status"], (result: CitationResult) => string>>;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
|
-
const
|
|
46
|
+
const TEXT: Texts<CiteText> = {
|
|
47
|
+
ja: {
|
|
48
|
+
quoted: (text) => `「${text}」`,
|
|
49
|
+
usage: "使い方: chaff cite <原文> <引用.json> [--format text|json] [--language ja|en|…]",
|
|
50
|
+
notJson: (why) => `JSON として読めません: ${why}`,
|
|
51
|
+
notArray: '引用は配列で渡してください: [{ "address": "3.2", "quote": "…" }]',
|
|
52
|
+
badEntry: (n) => `${String(n)} 件目に address(文字列)と quote(文字列)がありません`,
|
|
53
|
+
results: {
|
|
54
|
+
ok: () => "一致",
|
|
55
|
+
"missing-address": (result) => `番地 ${result.citation.address} は原文にありません`,
|
|
56
|
+
"quote-elsewhere": (result) =>
|
|
57
|
+
`引用文は ${result.citation.address} ではなく ${result.foundAt ?? "番地の外"}(${String(result.line ?? "?")} 行目)にあります`,
|
|
58
|
+
"quote-not-found": () => "引用文が原文のどこにもありません",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
en: {
|
|
62
|
+
quoted: (text) => ` "${text}"`,
|
|
63
|
+
usage: "usage: chaff cite <source> <quotes.json> [--format text|json] [--language ja|en|…]",
|
|
64
|
+
notJson: (why) => `Not valid JSON: ${why}`,
|
|
65
|
+
notArray: 'Give the quotations as an array: [{ "address": "3.2", "quote": "…" }]',
|
|
66
|
+
badEntry: (n) => `Entry ${String(n)} needs address (a string) and quote (a string)`,
|
|
67
|
+
results: {
|
|
68
|
+
ok: () => "matches",
|
|
69
|
+
"missing-address": (result) => `address ${result.citation.address} is not in the source`,
|
|
70
|
+
"quote-elsewhere": (result) =>
|
|
71
|
+
`the quotation is not at ${result.citation.address} but at ${result.foundAt ?? "no address"} (line ${String(result.line ?? "?")})`,
|
|
72
|
+
"quote-not-found": () => "the quotation is nowhere in the source",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const describe = (result: CitationResult, text: CiteText): string => {
|
|
45
78
|
const mark = result.status === "ok" ? "✓" : "✗";
|
|
46
79
|
const oneLine = result.citation.quote.replace(/\s+/gu, " ").trim();
|
|
47
80
|
const quote = oneLine.length > QUOTE_WIDTH ? `${oneLine.slice(0, QUOTE_WIDTH)}…` : oneLine;
|
|
48
|
-
return `${mark} ${result.citation.address}
|
|
81
|
+
return `${mark} ${result.citation.address}${text.quoted(quote)}: ${text.results[result.status](result)}`;
|
|
49
82
|
};
|
|
50
83
|
|
|
51
84
|
/**
|
|
@@ -56,12 +89,12 @@ export const runCite = async (targets: readonly string[], argv: readonly string[
|
|
|
56
89
|
const [sourcePath, citationsPath] = targets;
|
|
57
90
|
const format = context.flag(argv, "--format") ?? "text";
|
|
58
91
|
if (sourcePath === undefined || citationsPath === undefined || targets.length !== 2 || !FORMATS.includes(format)) {
|
|
59
|
-
console.error(
|
|
92
|
+
console.error(TEXT[context.ui ?? "ja"].usage);
|
|
60
93
|
return 1;
|
|
61
94
|
}
|
|
62
|
-
const citationsText = await readSource(citationsPath);
|
|
95
|
+
const citationsText = await readSource(citationsPath, context);
|
|
63
96
|
if (citationsText === undefined) return 1;
|
|
64
|
-
const parsed = parseCitations(citationsText);
|
|
97
|
+
const parsed = parseCitations(citationsText, context.ui ?? "ja");
|
|
65
98
|
if ("error" in parsed) {
|
|
66
99
|
console.error(`${citationsPath}: ${parsed.error}`);
|
|
67
100
|
return 1;
|
|
@@ -69,6 +102,6 @@ export const runCite = async (targets: readonly string[], argv: readonly string[
|
|
|
69
102
|
const read = await readTree(sourcePath, argv, context);
|
|
70
103
|
if (read === undefined) return 1;
|
|
71
104
|
const results = checkCitations(read.source, read.tree, parsed.citations);
|
|
72
|
-
console.log(format === "json" ? JSON.stringify(results, null, 2) : results.map(describe).join("\n"));
|
|
105
|
+
console.log(format === "json" ? JSON.stringify(results, null, 2) : results.map((result) => describe(result, TEXT[context.ui ?? "ja"])).join("\n"));
|
|
73
106
|
return results.every((result) => result.status === "ok") ? 0 : 1;
|
|
74
107
|
};
|
package/src/commands/eval.ts
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { Texts, UiLanguage } from "../ui.ts";
|
|
5
|
+
|
|
6
|
+
/** The skill ships in the package, beside rules/. Two levels up from src/commands or dist/commands. */
|
|
7
|
+
export const SKILL_SOURCE = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "skills", "chaff", "SKILL.md");
|
|
8
|
+
|
|
9
|
+
/** Where Claude Code reads a skill: this folder's .claude/skills, or the user's. */
|
|
10
|
+
export const skillTarget = (root: string): string => join(root, ".claude", "skills", "chaff", "SKILL.md");
|
|
11
|
+
|
|
12
|
+
export type SkillOutcome = { readonly status: "written" | "updated" | "same" | "kept"; readonly path: string };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Writes the skill. A file that is already there and differs may have been edited by hand, so it is replaced
|
|
16
|
+
* only with `force`; the same file is left alone.
|
|
17
|
+
*/
|
|
18
|
+
export const installSkill = (skill: string, target: string, force: boolean): SkillOutcome => {
|
|
19
|
+
if (existsSync(target)) {
|
|
20
|
+
const current = readFileSync(target, "utf8");
|
|
21
|
+
if (current === skill) return { status: "same", path: target };
|
|
22
|
+
if (!force) return { status: "kept", path: target };
|
|
23
|
+
writeFileSync(target, skill, "utf8");
|
|
24
|
+
return { status: "updated", path: target };
|
|
25
|
+
}
|
|
26
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
27
|
+
writeFileSync(target, skill, "utf8");
|
|
28
|
+
return { status: "written", path: target };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const TEXT: Texts<Readonly<Record<SkillOutcome["status"], (path: string) => string>>> = {
|
|
32
|
+
ja: {
|
|
33
|
+
written: (path) => `Claude Code の skill を書きました: ${path}\nClaude Code を開き直すと /chaff で使えます。`,
|
|
34
|
+
updated: (path) => `skill を新しい版に置き換えました: ${path}`,
|
|
35
|
+
same: (path) => `skill は既に最新です: ${path}`,
|
|
36
|
+
kept: (path) => `${path} は手で直されているかもしれないので、置き換えていません。\n置き換えるときは --force を付けてください。`,
|
|
37
|
+
},
|
|
38
|
+
en: {
|
|
39
|
+
written: (path) => `Wrote the Claude Code skill: ${path}\nReopen Claude Code and it is available as /chaff.`,
|
|
40
|
+
updated: (path) => `Replaced the skill with the new version: ${path}`,
|
|
41
|
+
same: (path) => `The skill is up to date: ${path}`,
|
|
42
|
+
kept: (path) => `${path} may have been edited by hand, so it was not replaced.\nAdd --force to replace it.`,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const FAILED: Texts<(path: string, why: string) => string> = {
|
|
47
|
+
ja: (path, why) => `skill を ${path} に書けませんでした: ${why}`,
|
|
48
|
+
en: (path, why) => `Could not write the skill to ${path}: ${why}`,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** The outcome, or why the file system refused. */
|
|
52
|
+
const attempt = (install: () => SkillOutcome): SkillOutcome | { readonly failure: string } => {
|
|
53
|
+
try {
|
|
54
|
+
return install();
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return { failure: err instanceof Error ? err.message : String(err) };
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type SkillContext = { readonly cwd: string; readonly home: string; readonly ui: UiLanguage };
|
|
61
|
+
|
|
62
|
+
/** `chaff skill [--global] [--force]`: installs or updates the skill. Exit 1 when a differing file was kept. */
|
|
63
|
+
export const runSkill = (argv: readonly string[], context: SkillContext): number => {
|
|
64
|
+
const target = skillTarget(argv.includes("--global") ? context.home : context.cwd);
|
|
65
|
+
const outcome = attempt(() => installSkill(readFileSync(SKILL_SOURCE, "utf8"), target, argv.includes("--force")));
|
|
66
|
+
if ("failure" in outcome) {
|
|
67
|
+
console.error(FAILED[context.ui](target, outcome.failure));
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
const say = TEXT[context.ui][outcome.status](outcome.path);
|
|
71
|
+
if (outcome.status === "kept") console.error(say);
|
|
72
|
+
else console.log(say);
|
|
73
|
+
return outcome.status === "kept" ? 1 : 0;
|
|
74
|
+
};
|
package/src/commands/tree.ts
CHANGED
|
@@ -7,15 +7,35 @@ import { buildStructure } from "../structure/of.ts";
|
|
|
7
7
|
import { toSexp } from "../structure/sexp.ts";
|
|
8
8
|
import type { Config } from "../config/load.ts";
|
|
9
9
|
import type { StructureNode } from "../plugin.ts";
|
|
10
|
+
import type { Texts, UiLanguage } from "../ui.ts";
|
|
10
11
|
|
|
11
12
|
export type TreeContext = {
|
|
12
13
|
readonly config: Config;
|
|
13
14
|
readonly flag: (argv: readonly string[], name: string) => string | undefined;
|
|
15
|
+
/** The language chaff speaks in here. Japanese when left out. */
|
|
16
|
+
readonly ui?: UiLanguage;
|
|
14
17
|
};
|
|
15
18
|
|
|
16
|
-
const
|
|
19
|
+
const TEXT: Texts<{
|
|
20
|
+
readonly usage: string;
|
|
21
|
+
readonly unreadable: (path: string, why: string) => string;
|
|
22
|
+
readonly noStructure: (path: string, language: string) => string;
|
|
23
|
+
}> = {
|
|
24
|
+
ja: {
|
|
25
|
+
usage: "使い方: chaff tree <file>... [--format sexp|json] [--language ja|en|…]",
|
|
26
|
+
unreadable: (path, why) => `${path} を読めませんでした: ${why}`,
|
|
27
|
+
noStructure: (path, language) => `${path}: 言語 ${language} のパッケージは文書の構造を読めません(structure がありません)`,
|
|
28
|
+
},
|
|
29
|
+
en: {
|
|
30
|
+
usage: "usage: chaff tree <file>... [--format sexp|json] [--language ja|en|…]",
|
|
31
|
+
unreadable: (path, why) => `Could not read ${path}: ${why}`,
|
|
32
|
+
noStructure: (path, language) => `${path}: the ${language} package cannot read a document's structure (it has no structure)`,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
17
35
|
|
|
18
|
-
const
|
|
36
|
+
export const treeText = (context: TreeContext): (typeof TEXT)["ja"] => TEXT[context.ui ?? "ja"];
|
|
37
|
+
|
|
38
|
+
const FORMATS = ["sexp", "json"];
|
|
19
39
|
|
|
20
40
|
/** 値を取るフラグ。その次の引数は値で、対象のファイルではない。 */
|
|
21
41
|
const VALUED = ["--format", "--language"];
|
|
@@ -23,11 +43,11 @@ const VALUED = ["--format", "--language"];
|
|
|
23
43
|
export const treeTargets = (argv: readonly string[]): string[] =>
|
|
24
44
|
argv.slice(1).filter((arg, index, all) => !arg.startsWith("--") && !VALUED.includes(all[index - 1] ?? ""));
|
|
25
45
|
|
|
26
|
-
export const readSource = async (path: string): Promise<string | undefined> => {
|
|
46
|
+
export const readSource = async (path: string, context: TreeContext): Promise<string | undefined> => {
|
|
27
47
|
try {
|
|
28
48
|
return await readFile(path, "utf8");
|
|
29
49
|
} catch (err) {
|
|
30
|
-
console.error(
|
|
50
|
+
console.error(treeText(context).unreadable(path, err instanceof Error ? err.message : String(err)));
|
|
31
51
|
return undefined;
|
|
32
52
|
}
|
|
33
53
|
};
|
|
@@ -49,12 +69,12 @@ export type SourceTree = { readonly source: string; readonly tree: StructureNode
|
|
|
49
69
|
* chaff tree と chaff cite が同じ木を見るように、ここを一つにしておく。
|
|
50
70
|
*/
|
|
51
71
|
export const readTree = async (path: string, argv: readonly string[], context: TreeContext): Promise<SourceTree | undefined> => {
|
|
52
|
-
const source = await readSource(path);
|
|
72
|
+
const source = await readSource(path, context);
|
|
53
73
|
if (source === undefined) return undefined;
|
|
54
74
|
const language = treeLanguage(path, source, argv, context);
|
|
55
75
|
const adapter = await loadAdapter(language);
|
|
56
76
|
if (adapter.structure === undefined) {
|
|
57
|
-
console.error(
|
|
77
|
+
console.error(treeText(context).noStructure(path, language));
|
|
58
78
|
return undefined;
|
|
59
79
|
}
|
|
60
80
|
// 日本語は形態素で数量と日付を読む。解析器が無ければ単位の表で読むので、木は作れる。
|
|
@@ -86,7 +106,7 @@ export const inOrder = async (paths: readonly string[], each: (path: string) =>
|
|
|
86
106
|
export const runTree = async (paths: readonly string[], argv: readonly string[], context: TreeContext): Promise<number> => {
|
|
87
107
|
const format = context.flag(argv, "--format") ?? "sexp";
|
|
88
108
|
if (paths.length === 0 || !FORMATS.includes(format)) {
|
|
89
|
-
console.error(
|
|
109
|
+
console.error(treeText(context).usage);
|
|
90
110
|
return 1;
|
|
91
111
|
}
|
|
92
112
|
const printed = await inOrder(paths, (path) => printTree(path, argv, context));
|