chore: prepare docfill project for gitea

This commit is contained in:
赵义仑
2026-06-24 09:45:35 +08:00
parent ca444bd5d0
commit 32e74d0f43
38 changed files with 2915 additions and 162 deletions

View File

@@ -1,10 +1,12 @@
package main
import (
"errors"
"flag"
"fmt"
"io"
"os"
"sort"
"strings"
"xlsdoc/internal/docfill"
@@ -18,11 +20,11 @@ func main() {
func run(args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
fmt.Fprintln(stderr, "docfill: missing command: run")
writeCodedError(stderr, docfill.CodeCLIUsage, "missing command: run", "Use: docfill run -c task.json", nil)
return 2
}
if args[0] != "run" {
fmt.Fprintf(stderr, "docfill: unknown command: %s\n", args[0])
writeCodedError(stderr, docfill.CodeCLIUsage, "unknown command: "+args[0], "Use: docfill run -c task.json", nil)
return 2
}
@@ -43,38 +45,78 @@ func runFill(args []string, stderr io.Writer) (docfill.RunResult, int) {
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 {
fmt.Fprintf(stderr, "docfill: %v\n", err)
writeCodedError(stderr, docfill.CodeCLIUsage, err.Error(), "Check command flags and use key=value for --var.", nil)
return docfill.RunResult{}, 2
}
if fs.NArg() > 0 {
fmt.Fprintf(stderr, "docfill: unexpected argument: %s\n", fs.Arg(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 == "" {
fmt.Fprintln(stderr, "docfill: missing --config")
writeCodedError(stderr, docfill.CodeCLIUsage, "missing --config", "Use -c task.json or --config task.json.", nil)
fs.SetOutput(stderr)
fs.Usage()
return docfill.RunResult{}, 2
}
result, err := docfill.RunWithOptions(configPath, docfill.RunOptions{Vars: vars})
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 {
fmt.Fprintf(stderr, "docfill: %v\n", err)
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 ""

View File

@@ -63,7 +63,7 @@ func TestRunCommandRejectsBadVarSyntax(t *testing.T) {
if exitCode != 2 {
t.Fatalf("exit code = %d, want 2", exitCode)
}
if !strings.Contains(stderr.String(), "docfill:") || !strings.Contains(stderr.String(), "invalid --var") {
if !strings.Contains(stderr.String(), "[CLI_USAGE]") || !strings.Contains(stderr.String(), "invalid --var") {
t.Fatalf("unexpected stderr: %q", stderr.String())
}
}
@@ -84,6 +84,96 @@ func TestRunCommandRejectsUnexpectedArgs(t *testing.T) {
}
}
func TestRunCommandFormatsCodedExecutionErrors(t *testing.T) {
dir := t.TempDir()
dataPath := filepath.Join(dir, "data.xlsx")
templatePath := filepath.Join(dir, "template.xlsx")
configPath := filepath.Join(dir, "task.json")
writeCLIWorkbook(t, dataPath, "Daily", [][]string{
{"name"},
{"张三"},
})
writeCLIWorkbook(t, templatePath, "日报", [][]string{
{"{{name}}", "{{missing}}"},
})
writeCLIJSON(t, configPath, map[string]any{
"data": map[string]any{
"type": "excel",
"path": dataPath,
"sheet": "Daily",
},
"template": map[string]any{
"path": templatePath,
},
"output": map[string]any{
"path": filepath.Join(dir, "out", "日报_{{name}}.xlsx"),
},
})
var stdout bytes.Buffer
var stderr bytes.Buffer
exitCode := run([]string{"run", "-c", configPath}, &stdout, &stderr)
if exitCode != 1 {
t.Fatalf("exit code = %d, want 1", exitCode)
}
if stdout.Len() != 0 {
t.Fatalf("expected empty stdout, got %q", stdout.String())
}
got := stderr.String()
for _, want := range []string{"docfill: [TEMPLATE_FIELD_MISSING]", "hint:", "context:", "field=missing", "template="} {
if !strings.Contains(got, want) {
t.Fatalf("stderr missing %q: %q", want, got)
}
}
}
func TestRunCommandVerbosePrintsStepLogs(t *testing.T) {
dir := t.TempDir()
dataPath := filepath.Join(dir, "data.xlsx")
templatePath := filepath.Join(dir, "template.xlsx")
configPath := filepath.Join(dir, "task.json")
writeCLIWorkbook(t, dataPath, "Daily", [][]string{
{"name", "done"},
{"张三", "完成日报"},
})
writeCLIWorkbook(t, templatePath, "日报", [][]string{
{"{{name}}", "{{done}}"},
})
writeCLIJSON(t, configPath, map[string]any{
"data": map[string]any{
"type": "excel",
"path": dataPath,
"sheet": "Daily",
},
"template": map[string]any{
"path": templatePath,
},
"output": map[string]any{
"path": filepath.Join(dir, "out", "日报_{{name}}.xlsx"),
},
})
var stdout bytes.Buffer
var stderr bytes.Buffer
exitCode := run([]string{"run", "-c", configPath, "--verbose"}, &stdout, &stderr)
if exitCode != 0 {
t.Fatalf("exit code = %d, stderr=%q", exitCode, stderr.String())
}
if !strings.Contains(stdout.String(), "generated 1 file(s)") {
t.Fatalf("unexpected stdout: %q", stdout.String())
}
got := stderr.String()
for _, want := range []string{"log: load config", "log: read data", "log: render row=1"} {
if !strings.Contains(got, want) {
t.Fatalf("stderr missing %q: %q", want, got)
}
}
}
func writeCLIWorkbook(t *testing.T, path, sheet string, rows [][]string) {
t.Helper()