Reorganize GSP module layout
This commit is contained in:
293
toolkit/cmd/gsp/main.go
Normal file
293
toolkit/cmd/gsp/main.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gsp.toolkit/internal/gsp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(os.Args[1:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(args []string) error {
|
||||
if len(args) == 0 {
|
||||
printHelp()
|
||||
return nil
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "validate":
|
||||
return runValidate(args[1:])
|
||||
case "index":
|
||||
return runIndex(args[1:])
|
||||
case "trace":
|
||||
return runTrace(args[1:])
|
||||
case "flatten":
|
||||
return runFlatten(args[1:])
|
||||
case "pack":
|
||||
return runPack(args[1:])
|
||||
case "graph":
|
||||
return runGraph(args[1:])
|
||||
case "stage-check":
|
||||
return runStageCheck(args[1:])
|
||||
case "help", "-h", "--help":
|
||||
printHelp()
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown command %q", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Print(`GSP Toolkit
|
||||
|
||||
Usage:
|
||||
gsp validate [--root .] [--out report.json]
|
||||
gsp index [--root .] [--out index.json]
|
||||
gsp trace <id> [--root .] [--depth 3] [--out trace.json]
|
||||
gsp flatten <id> [--root .] [--depth 3] [--include-type a,b] [--exclude-type a,b] [--out flattened.json]
|
||||
gsp pack <id> [--root .] [--depth 3] [--budget 12000] [--out context-pack.json]
|
||||
gsp graph [id] [--root .] [--depth 3] [--format json|mermaid] [--out graph.json]
|
||||
gsp stage-check --stage implement [--root .] [--out stage-report.json]
|
||||
`)
|
||||
}
|
||||
|
||||
func commonRoot(fs *flag.FlagSet) *string {
|
||||
return fs.String("root", ".", "project root to scan")
|
||||
}
|
||||
|
||||
func commonOut(fs *flag.FlagSet) *string {
|
||||
return fs.String("out", "", "optional output file")
|
||||
}
|
||||
|
||||
func runValidate(args []string) error {
|
||||
fs := flag.NewFlagSet("validate", flag.ContinueOnError)
|
||||
root := commonRoot(fs)
|
||||
out := commonOut(fs)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
project, err := gsp.LoadProject(*root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
report := project.Validate("")
|
||||
return writeReport(*out, report)
|
||||
}
|
||||
|
||||
func runIndex(args []string) error {
|
||||
fs := flag.NewFlagSet("index", flag.ContinueOnError)
|
||||
root := commonRoot(fs)
|
||||
out := commonOut(fs)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
project, err := gsp.LoadProject(*root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeJSON(*out, project.Index())
|
||||
}
|
||||
|
||||
func runTrace(args []string) error {
|
||||
fs := flag.NewFlagSet("trace", flag.ContinueOnError)
|
||||
root := commonRoot(fs)
|
||||
out := commonOut(fs)
|
||||
depth := fs.Int("depth", 3, "maximum relation depth; -1 means unlimited")
|
||||
if err := fs.Parse(normalizeFlagArgs(args)); err != nil {
|
||||
return err
|
||||
}
|
||||
if fs.NArg() != 1 {
|
||||
return fmt.Errorf("trace requires one GSP id")
|
||||
}
|
||||
project, err := gsp.LoadProject(*root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result := project.Trace(fs.Arg(0), *depth, gsp.Filter{})
|
||||
return writeJSON(*out, result)
|
||||
}
|
||||
|
||||
func runFlatten(args []string) error {
|
||||
fs := flag.NewFlagSet("flatten", flag.ContinueOnError)
|
||||
root := commonRoot(fs)
|
||||
out := commonOut(fs)
|
||||
depth := fs.Int("depth", 3, "maximum relation depth; -1 means unlimited")
|
||||
includeType := fs.String("include-type", "", "comma-separated type allow-list")
|
||||
excludeType := fs.String("exclude-type", "", "comma-separated type deny-list")
|
||||
if err := fs.Parse(normalizeFlagArgs(args)); err != nil {
|
||||
return err
|
||||
}
|
||||
if fs.NArg() != 1 {
|
||||
return fmt.Errorf("flatten requires one GSP id")
|
||||
}
|
||||
project, err := gsp.LoadProject(*root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result := project.Flatten(fs.Arg(0), *depth, gsp.Filter{
|
||||
IncludeTypes: splitCSV(*includeType),
|
||||
ExcludeTypes: splitCSV(*excludeType),
|
||||
})
|
||||
return writeJSON(*out, result)
|
||||
}
|
||||
|
||||
func runPack(args []string) error {
|
||||
fs := flag.NewFlagSet("pack", flag.ContinueOnError)
|
||||
root := commonRoot(fs)
|
||||
out := commonOut(fs)
|
||||
depth := fs.Int("depth", 3, "maximum relation depth; -1 means unlimited")
|
||||
budget := fs.Int("budget", 0, "approximate JSON character budget; 0 means unlimited")
|
||||
includeType := fs.String("include-type", "", "comma-separated type allow-list")
|
||||
excludeType := fs.String("exclude-type", "", "comma-separated type deny-list")
|
||||
if err := fs.Parse(normalizeFlagArgs(args)); err != nil {
|
||||
return err
|
||||
}
|
||||
if fs.NArg() != 1 {
|
||||
return fmt.Errorf("pack requires one GSP id")
|
||||
}
|
||||
project, err := gsp.LoadProject(*root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result := project.Pack(fs.Arg(0), *depth, *budget, gsp.Filter{
|
||||
IncludeTypes: splitCSV(*includeType),
|
||||
ExcludeTypes: splitCSV(*excludeType),
|
||||
})
|
||||
return writeJSON(*out, result)
|
||||
}
|
||||
|
||||
func runGraph(args []string) error {
|
||||
fs := flag.NewFlagSet("graph", flag.ContinueOnError)
|
||||
root := commonRoot(fs)
|
||||
out := commonOut(fs)
|
||||
depth := fs.Int("depth", 3, "maximum relation depth when id is provided; -1 means unlimited")
|
||||
format := fs.String("format", "json", "json or mermaid")
|
||||
if err := fs.Parse(normalizeFlagArgs(args)); err != nil {
|
||||
return err
|
||||
}
|
||||
id := ""
|
||||
if fs.NArg() > 1 {
|
||||
return fmt.Errorf("graph accepts zero or one GSP id")
|
||||
}
|
||||
if fs.NArg() == 1 {
|
||||
id = fs.Arg(0)
|
||||
}
|
||||
project, err := gsp.LoadProject(*root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
graph := project.Graph(id, *depth)
|
||||
if *format == "mermaid" {
|
||||
return writeText(*out, graph.Mermaid())
|
||||
}
|
||||
if *format != "json" {
|
||||
return fmt.Errorf("unsupported graph format %q", *format)
|
||||
}
|
||||
return writeJSON(*out, graph)
|
||||
}
|
||||
|
||||
func runStageCheck(args []string) error {
|
||||
fs := flag.NewFlagSet("stage-check", flag.ContinueOnError)
|
||||
root := commonRoot(fs)
|
||||
out := commonOut(fs)
|
||||
stage := fs.String("stage", "", "design, integrate, implement, bind, or release")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if *stage == "" {
|
||||
return fmt.Errorf("stage-check requires --stage")
|
||||
}
|
||||
project, err := gsp.LoadProject(*root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
report := project.StageCheck(*stage)
|
||||
return writeReport(*out, report)
|
||||
}
|
||||
|
||||
func splitCSV(value string) map[string]bool {
|
||||
result := map[string]bool{}
|
||||
for _, part := range strings.Split(value, ",") {
|
||||
part = strings.TrimSpace(part)
|
||||
if part != "" {
|
||||
result[part] = true
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeFlagArgs(args []string) []string {
|
||||
valueFlags := map[string]bool{
|
||||
"-root": true, "--root": true,
|
||||
"-out": true, "--out": true,
|
||||
"-depth": true, "--depth": true,
|
||||
"-include-type": true, "--include-type": true,
|
||||
"-exclude-type": true, "--exclude-type": true,
|
||||
"-budget": true, "--budget": true,
|
||||
"-format": true, "--format": true,
|
||||
"-stage": true, "--stage": true,
|
||||
}
|
||||
var flags []string
|
||||
var positionals []string
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
if !strings.HasPrefix(arg, "-") || arg == "-" {
|
||||
positionals = append(positionals, arg)
|
||||
continue
|
||||
}
|
||||
flags = append(flags, arg)
|
||||
name := arg
|
||||
if index := strings.Index(arg, "="); index >= 0 {
|
||||
name = arg[:index]
|
||||
}
|
||||
if valueFlags[name] && !strings.Contains(arg, "=") && i+1 < len(args) {
|
||||
flags = append(flags, args[i+1])
|
||||
i++
|
||||
}
|
||||
}
|
||||
return append(flags, positionals...)
|
||||
}
|
||||
|
||||
func writeJSON(path string, value any) error {
|
||||
data, err := json.MarshalIndent(value, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = append(data, '\n')
|
||||
return writeBytes(path, data)
|
||||
}
|
||||
|
||||
func writeReport(path string, report gsp.Report) error {
|
||||
if err := writeJSON(path, report); err != nil {
|
||||
return err
|
||||
}
|
||||
if !report.OK {
|
||||
return fmt.Errorf("GSP check failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeText(path string, value string) error {
|
||||
return writeBytes(path, []byte(value))
|
||||
}
|
||||
|
||||
func writeBytes(path string, data []byte) error {
|
||||
if path == "" {
|
||||
_, err := os.Stdout.Write(data)
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
Reference in New Issue
Block a user