154 lines
3.4 KiB
Go
154 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
_ "embed"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"runtime/pprof"
|
|
"strings"
|
|
"time"
|
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
"codexis/db"
|
|
"codexis/indexer"
|
|
)
|
|
|
|
|
|
//go:embed db/schema.sql
|
|
var schemaSQL string
|
|
|
|
const dbDir = ".codexis"
|
|
const dbFileName = "index.db"
|
|
|
|
func main() {
|
|
force := flag.Bool("force", false, "Force full re-index (ignore file hashes)")
|
|
output := flag.String("o", "", "Output database path (default: <root>/.codexis/index.db)")
|
|
cpuprofile := flag.String("cpuprofile", "", "Write CPU profile to file")
|
|
flag.Parse()
|
|
|
|
// CPU profiling
|
|
if *cpuprofile != "" {
|
|
f, err := os.Create(*cpuprofile)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error creating profile: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
pprof.StartCPUProfile(f)
|
|
|
|
// Flush profile on interrupt
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, os.Interrupt)
|
|
go func() {
|
|
<-sigCh
|
|
fmt.Fprintf(os.Stderr, "\nInterrupted, flushing CPU profile...\n")
|
|
pprof.StopCPUProfile()
|
|
f.Close()
|
|
os.Exit(1)
|
|
}()
|
|
|
|
defer func() {
|
|
pprof.StopCPUProfile()
|
|
f.Close()
|
|
}()
|
|
}
|
|
|
|
root := "."
|
|
if flag.NArg() > 0 {
|
|
root = flag.Arg(0)
|
|
}
|
|
|
|
absRoot, err := filepath.Abs(root)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
dbDirPath := filepath.Join(absRoot, dbDir)
|
|
if err := os.MkdirAll(dbDirPath, 0755); err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
dbPath := filepath.Join(dbDirPath, dbFileName)
|
|
if *output != "" {
|
|
dbPath = *output
|
|
}
|
|
|
|
if err := run(absRoot, dbPath, *force); err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run(root, dbPath string, force bool) error {
|
|
ctx := context.Background()
|
|
|
|
sqlDB, err := sql.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)")
|
|
if err != nil {
|
|
return fmt.Errorf("opening database: %w", err)
|
|
}
|
|
defer sqlDB.Close()
|
|
|
|
// Create schema
|
|
if err := createSchema(ctx, sqlDB); err != nil {
|
|
return fmt.Errorf("creating schema: %w", err)
|
|
}
|
|
|
|
queries := db.New(sqlDB)
|
|
idx := indexer.New(sqlDB, queries, root, force)
|
|
|
|
isTTY := fileIsTTY(os.Stderr)
|
|
idx.OnProgress = func(current, total int, path string) {
|
|
if !isTTY {
|
|
return
|
|
}
|
|
pct := current * 100 / total
|
|
barWidth := 30
|
|
filled := barWidth * current / total
|
|
bar := strings.Repeat("█", filled) + strings.Repeat("░", barWidth-filled)
|
|
display := path
|
|
if len(display) > 40 {
|
|
display = "..." + display[len(display)-37:]
|
|
}
|
|
fmt.Fprintf(os.Stderr, "\r %s %3d%% (%d/%d) %s\033[K", bar, pct, current, total, display)
|
|
if current == total {
|
|
fmt.Fprintf(os.Stderr, "\r\033[K")
|
|
}
|
|
}
|
|
|
|
start := time.Now()
|
|
fmt.Fprintf(os.Stderr, "Indexing %s...\n", root)
|
|
|
|
stats, err := idx.Index(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("indexing: %w", err)
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
fmt.Fprintf(os.Stderr, "Done in %s\n", elapsed.Round(time.Millisecond))
|
|
fmt.Fprintf(os.Stderr, " Files: %d total, %d indexed, %d unchanged\n",
|
|
stats.FilesTotal, stats.FilesIndexed, stats.FilesSkipped)
|
|
fmt.Fprintf(os.Stderr, " Symbols: %d\n", stats.SymbolsTotal)
|
|
fmt.Fprintf(os.Stderr, " Output: %s\n", dbPath)
|
|
|
|
return nil
|
|
}
|
|
|
|
func fileIsTTY(f *os.File) bool {
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return fi.Mode()&os.ModeCharDevice != 0
|
|
}
|
|
|
|
func createSchema(ctx context.Context, sqlDB *sql.DB) error {
|
|
_, err := sqlDB.ExecContext(ctx, schemaSQL)
|
|
return err
|
|
}
|