package main import ( "errors" "flag" "fmt" "io" "os" "sort" "strings" "xlsdoc/internal/docfill" ) type varFlags map[string]string func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } func run(args []string, stdout, stderr io.Writer) int { if len(args) == 0 { writeCodedError(stderr, docfill.CodeCLIUsage, "missing command: run", "Use: docfill run -c task.json", nil) return 2 } if args[0] != "run" { writeCodedError(stderr, docfill.CodeCLIUsage, "unknown command: "+args[0], "Use: docfill run -c task.json", nil) return 2 } result, code := runFill(args[1:], stderr) if code != 0 { return code } fmt.Fprintf(stdout, "generated %d file(s)\n", len(result.Files)) for _, file := range result.Files { fmt.Fprintf(stdout, "- %s\n", file) } return 0 } func runFill(args []string, stderr io.Writer) (docfill.RunResult, int) { fs := flag.NewFlagSet("docfill run", flag.ContinueOnError) fs.SetOutput(stderr) var configPath string var verbose bool var vars varFlags = make(map[string]string) fs.SetOutput(io.Discard) fs.StringVar(&configPath, "config", "", "path to task json") fs.StringVar(&configPath, "c", "", "path to task json") fs.BoolVar(&verbose, "verbose", false, "print diagnostic logs to stderr") fs.Var(vars, "var", "template variable as key=value; repeatable") if err := fs.Parse(args); err != nil { writeCodedError(stderr, docfill.CodeCLIUsage, err.Error(), "Check command flags and use key=value for --var.", nil) return docfill.RunResult{}, 2 } if fs.NArg() > 0 { writeCodedError(stderr, docfill.CodeCLIUsage, "unexpected argument: "+fs.Arg(0), "Remove extra positional arguments.", nil) return docfill.RunResult{}, 2 } if vars == nil { vars = make(map[string]string) } if configPath == "" { writeCodedError(stderr, docfill.CodeCLIUsage, "missing --config", "Use -c task.json or --config task.json.", nil) fs.SetOutput(stderr) fs.Usage() return docfill.RunResult{}, 2 } var logf func(format string, args ...any) if verbose { logf = func(format string, args ...any) { fmt.Fprintf(stderr, "log: "+format+"\n", args...) } } result, err := docfill.RunWithOptions(configPath, docfill.RunOptions{Vars: vars, Logf: logf}) if err != nil { writeExecutionError(stderr, err) return docfill.RunResult{}, 1 } return result, 0 } func writeExecutionError(stderr io.Writer, err error) { var appErr *docfill.AppError if errors.As(err, &appErr) { writeCodedError(stderr, appErr.Code, err.Error(), appErr.Hint, appErr.Context) return } writeCodedError(stderr, docfill.CodeInternal, err.Error(), "Run with --verbose and inspect inputs if this repeats.", nil) } func writeCodedError(stderr io.Writer, code docfill.ErrorCode, message, hint string, context map[string]string) { fmt.Fprintf(stderr, "docfill: [%s] %s\n", code, message) if hint != "" { fmt.Fprintf(stderr, "hint: %s\n", hint) } if len(context) == 0 { return } keys := make([]string, 0, len(context)) for key := range context { keys = append(keys, key) } sort.Strings(keys) parts := make([]string, 0, len(keys)) for _, key := range keys { parts = append(parts, key+"="+context[key]) } fmt.Fprintf(stderr, "context: %s\n", strings.Join(parts, " ")) } func (v varFlags) String() string { if len(v) == 0 { return "" } parts := make([]string, 0, len(v)) for key, value := range v { parts = append(parts, key+"="+value) } return strings.Join(parts, ",") } func (v varFlags) Set(value string) error { key, val, ok := strings.Cut(value, "=") if !ok || strings.TrimSpace(key) == "" { return fmt.Errorf("invalid --var %q, want key=value", value) } v[strings.TrimSpace(key)] = val return nil }