ollie/tools/codeintel/main.go

238 lines
4.8 KiB
Go

// Multicall binary for codeintel tools.
// Invoked via symlinks: code_outline, code_query, code_rewrite, code_symbols,
// code_dependencies, codebase_overview.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"ollie/tools/codeintel/internal/codeintel"
)
func main() {
name := filepath.Base(os.Args[0])
switch name {
case "code_outline":
cmdOutline()
case "code_query":
cmdQuery()
case "code_rewrite":
cmdRewrite()
case "code_symbols":
cmdSymbols()
case "code_dependencies":
cmdDependencies()
case "codebase_overview":
cmdOverview()
default:
fmt.Fprintf(os.Stderr, "codeintel: unknown tool %q\n", name)
os.Exit(1)
}
}
func fail(e error) { fmt.Fprintln(os.Stderr, "error:", e); os.Exit(1) }
// flexInt unmarshals from JSON int or string.
type flexInt int
func (f *flexInt) UnmarshalJSON(data []byte) error {
var i int
if err := json.Unmarshal(data, &i); err == nil {
*f = flexInt(i)
return nil
}
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
n, err := strconv.Atoi(s)
if err != nil {
return err
}
*f = flexInt(n)
return nil
}
// --- code_outline ---
func cmdOutline() {
var a struct {
Path string `json:"path"`
Language string `json:"language"`
}
if err := json.NewDecoder(os.Stdin).Decode(&a); err != nil {
fail(err)
}
p, err := filepath.Abs(a.Path)
if err != nil {
fail(err)
}
d, err := codeintel.Parse(p, a.Language)
if err != nil {
fail(err)
}
defer d.Close()
for _, s := range codeintel.Symbols(d) {
fmt.Printf("%s:%d: %s [%s]\n", s.Path, s.Line, s.Name, s.Kind)
}
}
// --- code_query ---
func cmdQuery() {
var a struct {
Path string `json:"path"`
Language string `json:"language"`
Query string `json:"query"`
}
if err := json.NewDecoder(os.Stdin).Decode(&a); err != nil {
fail(err)
}
p, err := filepath.Abs(a.Path)
if err != nil {
fail(err)
}
cs, err := codeintel.Query(p, a.Language, a.Query)
if err != nil {
fail(err)
}
for _, c := range cs {
fmt.Printf("%s:%d: %s = %s\n", c.Name, c.Line, c.Name, c.Text)
}
}
// --- code_rewrite ---
func cmdRewrite() {
var a struct {
Path string `json:"path"`
Language string `json:"language"`
Query string `json:"query"`
Replacement string `json:"replacement"`
DryRun bool `json:"dry_run"`
}
if err := json.NewDecoder(os.Stdin).Decode(&a); err != nil {
fail(err)
}
p, err := filepath.Abs(a.Path)
if err != nil {
fail(err)
}
out, err := codeintel.ReplaceMatches(p, a.Language, a.Query, a.Replacement)
if err != nil {
fail(err)
}
if a.DryRun {
fmt.Print(string(out))
return
}
if err := os.WriteFile(p, out, 0644); err != nil {
fail(err)
}
}
// --- code_symbols ---
func cmdSymbols() {
var a struct {
Path string `json:"path"`
Query string `json:"query"`
}
if err := json.NewDecoder(os.Stdin).Decode(&a); err != nil {
fail(err)
}
root, err := filepath.Abs(a.Path)
if err != nil {
fail(err)
}
ss, err := codeintel.MatchSymbols(root, a.Query)
if err != nil {
fail(err)
}
for _, s := range ss {
fmt.Printf("%s:%d: %s [%s]\n", s.Path, s.Line, s.Name, s.Kind)
}
}
// --- code_dependencies ---
func cmdDependencies() {
var a struct {
Path string `json:"path"`
Language string `json:"language"`
}
if err := json.NewDecoder(os.Stdin).Decode(&a); err != nil {
fail(err)
}
p, err := filepath.Abs(a.Path)
if err != nil {
fail(err)
}
ds, err := codeintel.Dependencies(p, a.Language)
if err != nil {
fail(err)
}
for _, d := range ds {
fmt.Println(d)
}
}
// --- codebase_overview ---
func cmdOverview() {
var a struct {
Path string `json:"path"`
Offset flexInt `json:"offset"`
Limit flexInt `json:"limit"`
}
if err := json.NewDecoder(os.Stdin).Decode(&a); err != nil {
fail(err)
}
root, err := filepath.Abs(a.Path)
if err != nil {
fail(err)
}
files, err := codeintel.Files(root)
if err != nil {
fail(err)
}
start := int(a.Offset)
limit := int(a.Limit)
if start < 0 {
fail(fmt.Errorf("offset must be non-negative"))
}
if limit < 0 {
fail(fmt.Errorf("limit must be non-negative"))
}
if start >= len(files) {
start = len(files)
}
end := len(files)
if limit > 0 && start+limit < end {
end = start + limit
}
fmt.Printf("# Codebase Overview\n\nPath: `%s`\n\n", root)
if len(files) > 0 && (start > 0 || end < len(files)) {
fmt.Printf("Files %d-%d of %d. Next offset: %d\n\n", start+1, end, len(files), end)
}
for _, f := range files[start:end] {
d, err := codeintel.Parse(f.Path, f.Language)
if err != nil {
continue
}
fmt.Printf("## `%s` (%s)\n\n", relPath(root, f.Path), f.Language)
for _, s := range codeintel.Symbols(d) {
fmt.Printf("- %s`%s` — line %d\n", strings.Repeat(" ", min(s.Depth, 4)), s.Name, s.Line)
}
fmt.Println()
d.Close()
}
}
func relPath(root, path string) string { p, _ := filepath.Rel(root, path); return p }