Skip to content

Commit 19c92f4

Browse files
committed
feat(cartographer): add ReplaceContent and ExtractContent FFI bindings
- ReplaceOptions/ReplaceResult/FileChange/DiffLine types for sed-like replace - ExtractOptions/ExtractResult/ExtractMatch/CountEntry types for awk-like extract - ReplaceContent() and ExtractContent() in real bridge + stubs - Matches cartographer v1.8.0 FFI surface
1 parent 2ef86ed commit 19c92f4

3 files changed

Lines changed: 155 additions & 1 deletion

File tree

internal/cartographer/bridge.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,72 @@ func GetModuleContext(path, moduleID string, depth uint32) (*ModuleContext, erro
408408
}
409409
return &ctx, nil
410410
}
411+
412+
// ReplaceContent performs a regex find-and-replace across project files.
413+
// replacement supports $0 (whole match) and $1/$2 (capture groups).
414+
// When opts.DryRun is true, no files are written.
415+
func ReplaceContent(path, pattern, replacement string, opts *ReplaceOptions) (*ReplaceResult, error) {
416+
cPath := C.CString(path)
417+
defer C.free(unsafe.Pointer(cPath))
418+
419+
cPattern := C.CString(pattern)
420+
defer C.free(unsafe.Pointer(cPattern))
421+
422+
cReplacement := C.CString(replacement)
423+
defer C.free(unsafe.Pointer(cReplacement))
424+
425+
var cOpts *C.char
426+
if opts != nil {
427+
b, err := json.Marshal(opts)
428+
if err != nil {
429+
return nil, &CartographerError{err.Error()}
430+
}
431+
cOpts = C.CString(string(b))
432+
defer C.free(unsafe.Pointer(cOpts))
433+
}
434+
435+
resp, err := callFFI(func() *C.char {
436+
return C.cartographer_replace_content(cPath, cPattern, cReplacement, cOpts)
437+
})
438+
if err != nil {
439+
return nil, err
440+
}
441+
442+
var result ReplaceResult
443+
if err := json.Unmarshal(resp.Data, &result); err != nil {
444+
return nil, &CartographerError{err.Error()}
445+
}
446+
return &result, nil
447+
}
448+
449+
// ExtractContent extracts capture-group values from regex matches across project files.
450+
func ExtractContent(path, pattern string, opts *ExtractOptions) (*ExtractResult, error) {
451+
cPath := C.CString(path)
452+
defer C.free(unsafe.Pointer(cPath))
453+
454+
cPattern := C.CString(pattern)
455+
defer C.free(unsafe.Pointer(cPattern))
456+
457+
var cOpts *C.char
458+
if opts != nil {
459+
b, err := json.Marshal(opts)
460+
if err != nil {
461+
return nil, &CartographerError{err.Error()}
462+
}
463+
cOpts = C.CString(string(b))
464+
defer C.free(unsafe.Pointer(cOpts))
465+
}
466+
467+
resp, err := callFFI(func() *C.char {
468+
return C.cartographer_extract_content(cPath, cPattern, cOpts)
469+
})
470+
if err != nil {
471+
return nil, err
472+
}
473+
474+
var result ExtractResult
475+
if err := json.Unmarshal(resp.Data, &result); err != nil {
476+
return nil, &CartographerError{err.Error()}
477+
}
478+
return &result, nil
479+
}

internal/cartographer/bridge_stub.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ func Semidiff(_, _, _ string) ([]SemidiffFile, error)
2727
func RankedSkeleton(_ string, _ []string, _ uint32) (*RankedSkeletonResult, error) { return nil, ErrUnavailable }
2828
func UnreferencedSymbols(_ string) (*UnreferencedSymbolsResult, error) { return nil, ErrUnavailable }
2929
func SearchContent(_, _ string, _ *SearchContentOptions) (*SearchResult, error) { return nil, ErrUnavailable }
30-
func FindFiles(_, _ string, _ uint32, _ *FindOptions) (*FindResult, error) { return nil, ErrUnavailable }
30+
func FindFiles(_, _ string, _ uint32, _ *FindOptions) (*FindResult, error) { return nil, ErrUnavailable }
31+
func ReplaceContent(_, _, _ string, _ *ReplaceOptions) (*ReplaceResult, error) { return nil, ErrUnavailable }
32+
func ExtractContent(_, _ string, _ *ExtractOptions) (*ExtractResult, error) { return nil, ErrUnavailable }

internal/cartographer/types.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,89 @@ type FindResult struct {
257257
Truncated bool `json:"truncated"`
258258
}
259259

260+
// ---------------------------------------------------------------------------
261+
// Replace types
262+
// ---------------------------------------------------------------------------
263+
264+
// ReplaceOptions controls replace_content behaviour.
265+
type ReplaceOptions struct {
266+
Literal bool `json:"literal,omitempty"`
267+
CaseSensitive *bool `json:"caseSensitive,omitempty"`
268+
WordRegexp bool `json:"wordRegexp,omitempty"`
269+
DryRun bool `json:"dryRun,omitempty"`
270+
Backup bool `json:"backup,omitempty"`
271+
ContextLines *int `json:"contextLines,omitempty"`
272+
FileGlob string `json:"fileGlob,omitempty"`
273+
ExcludeGlob string `json:"excludeGlob,omitempty"`
274+
SearchPath string `json:"searchPath,omitempty"`
275+
NoIgnore bool `json:"noIgnore,omitempty"`
276+
MaxPerFile int `json:"maxPerFile,omitempty"`
277+
}
278+
279+
// DiffLine is one line in a contextual diff produced by ReplaceContent.
280+
type DiffLine struct {
281+
Kind string `json:"kind"` // "context", "removed", "added", "separator"
282+
LineNumber int `json:"lineNumber"`
283+
Content string `json:"content"`
284+
}
285+
286+
// FileChange describes the replacements made (or previewed) in one file.
287+
type FileChange struct {
288+
Path string `json:"path"`
289+
Replacements int `json:"replacements"`
290+
Diff []DiffLine `json:"diff"`
291+
}
292+
293+
// ReplaceResult is returned by ReplaceContent.
294+
type ReplaceResult struct {
295+
FilesChanged int `json:"filesChanged"`
296+
TotalReplacements int `json:"totalReplacements"`
297+
Changes []FileChange `json:"changes"`
298+
DryRun bool `json:"dryRun"`
299+
}
300+
301+
// ---------------------------------------------------------------------------
302+
// Extract types
303+
// ---------------------------------------------------------------------------
304+
305+
// ExtractOptions controls extract_content behaviour.
306+
type ExtractOptions struct {
307+
Groups []int `json:"groups,omitempty"`
308+
Separator string `json:"separator,omitempty"`
309+
Format string `json:"format,omitempty"` // "text", "json", "csv", "tsv"
310+
Count bool `json:"count,omitempty"`
311+
Dedup bool `json:"dedup,omitempty"`
312+
Sort bool `json:"sort,omitempty"`
313+
CaseSensitive *bool `json:"caseSensitive,omitempty"`
314+
FileGlob string `json:"fileGlob,omitempty"`
315+
ExcludeGlob string `json:"excludeGlob,omitempty"`
316+
SearchPath string `json:"searchPath,omitempty"`
317+
NoIgnore bool `json:"noIgnore,omitempty"`
318+
Limit int `json:"limit,omitempty"`
319+
}
320+
321+
// ExtractMatch is one extracted row.
322+
type ExtractMatch struct {
323+
Path string `json:"path"`
324+
LineNumber int `json:"lineNumber"`
325+
Groups []string `json:"groups"`
326+
}
327+
328+
// CountEntry is a frequency entry returned when ExtractOptions.Count is true.
329+
type CountEntry struct {
330+
Value string `json:"value"`
331+
Count int `json:"count"`
332+
}
333+
334+
// ExtractResult is returned by ExtractContent.
335+
type ExtractResult struct {
336+
Matches []ExtractMatch `json:"matches,omitempty"`
337+
Counts []CountEntry `json:"counts,omitempty"`
338+
Total int `json:"total"`
339+
FilesSearched int `json:"filesSearched"`
340+
Truncated bool `json:"truncated"`
341+
}
342+
260343
// CartographerError is returned when a Cartographer FFI call fails.
261344
type CartographerError struct {
262345
Message string

0 commit comments

Comments
 (0)