Skip to content

Commit a88d7c1

Browse files
committed
Restore extractor docs after workspace merge #1709
1 parent a7e8468 commit a88d7c1

5 files changed

Lines changed: 141 additions & 1 deletion

File tree

DEVELOPER_GUIDE.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3059,6 +3059,54 @@ Cloud セッションは開発ループの中で `dotnet build` にフォール
30593059
- ドキュメント(README, CHANGELOG)は前半英語、後半日本語の構成。
30603060
- 不要な本番パッケージは入れない。test-only package は、テストハーネスの改善に明確に寄与し、`tests/CodeIndex.Tests/` に閉じる限り許容されるが、本番依存ルールを緩めるものではない。
30613061

3062+
## Custom Language Extraction
3063+
3064+
Downstream users can add lightweight language support without rebuilding
3065+
`cdidx`:
3066+
3067+
- extension aliases are read from `~/.config/cdidx/langmap.yaml` and the first
3068+
workspace ancestor `.cdidx-langmap.yaml`; workspace entries override user
3069+
entries;
3070+
- regex-backed symbol patterns are read from `.cdidx/patterns/*.yaml` and
3071+
`~/.config/cdidx/patterns/*.yaml`;
3072+
- `cdidx test-extractor --language <lang> --file <path> --json` runs symbol
3073+
extraction without building an index, and `--expect-symbols <json>` compares
3074+
the extracted JSON to a fixture.
3075+
3076+
Minimal examples:
3077+
3078+
```yaml
3079+
# .cdidx-langmap.yaml
3080+
entries:
3081+
- extension: ".kts.in"
3082+
language: "kotlin"
3083+
```
3084+
3085+
```yaml
3086+
# .cdidx/patterns/toydsl.yaml
3087+
language: "toydsl"
3088+
extensions:
3089+
- extension: ".toy"
3090+
patterns:
3091+
- kind: "class"
3092+
regex: "^entity (?<name>\\w+)"
3093+
```
3094+
3095+
Each configured regex should expose a named `name` capture. If it does not,
3096+
`cdidx` uses the full match text as the symbol name. Invalid sidecar files are
3097+
ignored so a broken local experiment does not prevent indexing.
3098+
3099+
## カスタム言語抽出
3100+
3101+
下流ユーザーは `cdidx` を再ビルドせずに軽量な言語対応を追加できます。
3102+
3103+
- 拡張子 alias は `~/.config/cdidx/langmap.yaml` と、最初に見つかった workspace
3104+
祖先の `.cdidx-langmap.yaml` から読み込まれ、workspace 側が user 側を上書きします。
3105+
- regex ベースのシンボルパターンは `.cdidx/patterns/*.yaml` と
3106+
`~/.config/cdidx/patterns/*.yaml` から読み込まれます。
3107+
- `cdidx test-extractor --language <lang> --file <path> --json` は index を作らずに
3108+
symbol extraction だけを実行し、`--expect-symbols <json>` で fixture JSON と比較できます。
3109+
30623110
## SQLite reader のデバッグ
30633111

30643112
`Database/DbDebug.cs` は `ExecuteTrackedReader` / `TrackedRead` の最後に流れた SQL、パラメーター、行ごとの状態を記録し、ループ途中で `SqliteException` が発生した場合に再現に十分な文脈を stderr へダンプする。インデックス済みのソースバイトが想定外の経路に漏れないよう、ダンプ経路はゲート制御されている:

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ cdidx validate
5858
cdidx mcp
5959
```
6060

61+
Custom language loops can stay out of tree: put extension aliases in
62+
`.cdidx-langmap.yaml`, put regex symbol patterns in `.cdidx/patterns/*.yaml`,
63+
and run `cdidx test-extractor --language <lang> --file <path> --json` to test
64+
an extractor fixture without building a full index. See
65+
[Custom Language Extraction](DEVELOPER_GUIDE.md#custom-language-extraction).
66+
6167
After the first command, use these cues and follow-up commands:
6268

6369
| Situation | What to expect or run |
@@ -300,6 +306,12 @@ cdidx validate
300306
cdidx mcp
301307
```
302308

309+
カスタム言語の開発ループは out-of-tree で回せます。拡張子 alias は
310+
`.cdidx-langmap.yaml`、regex シンボルパターンは `.cdidx/patterns/*.yaml` に置き、
311+
`cdidx test-extractor --language <lang> --file <path> --json` で full index を作らずに
312+
extractor fixture を確認できます。詳細は
313+
[Custom Language Extraction](DEVELOPER_GUIDE.md#custom-language-extraction) を参照してください。
314+
303315
初回実行後は、次の見方と追加コマンドをよく使います。
304316

305317
| 状況 | 見るもの / 使うもの |

src/CodeIndex/Cli/CdidxConfigFile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ internal static int RunShow(string[] args, JsonSerializerOptions jsonOptions)
349349
var json = args.Contains("--json", StringComparer.Ordinal);
350350
args = args.Where(a => a != "--json").ToArray();
351351
if (args.Length > 0)
352-
return CommandErrorWriter.Write("config show does not accept positional arguments.", CommandExitCodes.UsageError, "run `cdidx config show` from the workspace whose config should be shown.");
352+
return CommandErrorWriter.WriteJsonOrHuman(json, jsonOptions, "config show does not accept positional arguments.", CommandExitCodes.UsageError, "run `cdidx config show` from the workspace whose config should be shown.");
353353

354354
var path = FindConfigFile(Environment.CurrentDirectory);
355355
var active = ActiveWorkspace.Load();

src/CodeIndex/Cli/ProgramRunner.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ internal static int Run(
263263
"workspace" => WorkspaceCommandRunner.Run(subArgs, jsonOptions),
264264
"db" => DbCommandRunner.RunIntegrityCheck(subArgs, jsonOptions),
265265
"report" => ReportCommandRunner.Run(subArgs, jsonOptions, appVersion),
266+
"test-extractor" => RunTestExtractor(subArgs, jsonOptions),
266267
_ when IsProjectPathArg(commandName)
267268
=> IndexCommandRunner.Run(args, jsonOptions),
268269
_ => ShowError(args, $"Unknown command: {commandName}")
@@ -332,6 +333,74 @@ private static bool IsWindowsDrivePath(string arg) =>
332333
&& arg[1] == ':'
333334
&& ((arg[0] >= 'A' && arg[0] <= 'Z') || (arg[0] >= 'a' && arg[0] <= 'z'));
334335

336+
private static int RunTestExtractor(string[] args, JsonSerializerOptions jsonOptions)
337+
{
338+
string? language = null;
339+
string? file = null;
340+
string? expect = null;
341+
var json = false;
342+
for (var i = 0; i < args.Length; i++)
343+
{
344+
var arg = args[i];
345+
if (TryConsumeInlineOrNext(args, ref i, arg, "--language", out var value))
346+
language = value;
347+
else if (TryConsumeInlineOrNext(args, ref i, arg, "--file", out value))
348+
file = value;
349+
else if (TryConsumeInlineOrNext(args, ref i, arg, "--expect-symbols", out value) || TryConsumeInlineOrNext(args, ref i, arg, "--expect", out value))
350+
expect = value;
351+
else if (arg == "--json")
352+
json = true;
353+
else
354+
return CommandErrorWriter.Write($"Unknown test-extractor argument: {arg}", CommandExitCodes.InvalidArgument, "use --language <lang> --file <path> [--expect-symbols <json>] [--json].");
355+
}
356+
357+
if (string.IsNullOrWhiteSpace(language) || string.IsNullOrWhiteSpace(file))
358+
return CommandErrorWriter.Write("test-extractor requires --language and --file.", CommandExitCodes.InvalidArgument, "use --language <lang> --file <path> [--expect-symbols <json>] [--json].");
359+
if (!File.Exists(file))
360+
return CommandErrorWriter.Write($"File not found: {file}", CommandExitCodes.NotFound);
361+
362+
var source = File.ReadAllText(file);
363+
var symbols = Indexer.SymbolExtractor.Extract(1, language, source, file);
364+
if (expect != null)
365+
{
366+
var expected = File.ReadAllText(expect);
367+
var actual = JsonSerializer.Serialize(symbols);
368+
if (!JsonEquivalent(expected, actual))
369+
{
370+
Console.Error.WriteLine("Expected symbols did not match extracted symbols.");
371+
Console.Error.WriteLine(actual);
372+
return CommandExitCodes.InvalidArgument;
373+
}
374+
}
375+
376+
if (json || expect == null)
377+
Console.WriteLine(JsonSerializer.Serialize(symbols));
378+
return CommandExitCodes.Success;
379+
}
380+
381+
private static bool TryConsumeInlineOrNext(string[] args, ref int index, string arg, string flag, out string value)
382+
{
383+
value = string.Empty;
384+
if (arg.StartsWith(flag + "=", StringComparison.Ordinal))
385+
{
386+
value = arg[(flag.Length + 1)..];
387+
return true;
388+
}
389+
390+
if (arg != flag || index + 1 >= args.Length)
391+
return false;
392+
393+
value = args[++index];
394+
return true;
395+
}
396+
397+
private static bool JsonEquivalent(string expected, string actual)
398+
{
399+
using var expectedDoc = JsonDocument.Parse(expected);
400+
using var actualDoc = JsonDocument.Parse(actual);
401+
return JsonSerializer.Serialize(expectedDoc.RootElement) == JsonSerializer.Serialize(actualDoc.RootElement);
402+
}
403+
335404
internal static void EnsureRedirectedStdoutUsesUtf8()
336405
{
337406
if (!Console.IsOutputRedirected || Console.Out is StringWriter || Console.Out.GetType().Assembly != typeof(Console).Assembly)

tests/CodeIndex.Tests/WorkspaceCommandRunnerTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ public void ConfigErrors_HonorJsonFlag()
7878
}
7979
}
8080

81+
[Fact]
82+
public void ConfigShowErrors_HonorJsonFlag()
83+
{
84+
var (exitCode, stdout, stderr) = ConsoleCapture.Capture(() => CdidxConfigFile.RunShow(["extra", "--json"], _jsonOptions));
85+
86+
Assert.Equal(CommandExitCodes.UsageError, exitCode);
87+
Assert.Contains("\"status\":\"error\"", stdout);
88+
Assert.Contains("config show does not accept positional arguments", stdout);
89+
Assert.DoesNotContain("config show does not accept positional arguments", stderr);
90+
}
91+
8192
[Fact]
8293
public void ConfigShow_PrintsPrecedence()
8394
{

0 commit comments

Comments
 (0)