diff options
Diffstat (limited to 'internal/cli')
| -rw-r--r-- | internal/cli/cli.go | 10 | ||||
| -rw-r--r-- | internal/cli/corpus.go | 69 | ||||
| -rw-r--r-- | internal/cli/corpus_test.go | 41 |
3 files changed, 120 insertions, 0 deletions
diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 65be274..eb01039 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -51,6 +51,9 @@ Flags: -L, --liturgy print the computed liturgical day (offline, no network) and exit --cal-new NAME scaffold a custom calendar layer ~/.config/lectio/calendars/NAME.ini --cal-check NAME validate a custom calendar layer NAME.ini and exit + --corpus-check X validate a bible corpus (code, e.g. drb, or a path to + a <code>.tsv) and exit; errors fail, coverage gaps warn + --json with --corpus-check, print the report as JSON --rand, --rand-v print a random verse and exit (uses default_version or -b VER; bt has no corpus, so it falls back to a corpus version) --rand-ch print a random chapter and exit @@ -124,6 +127,8 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { var ref string var liturgy bool var calNew, calCheck string + var corpusCheck string + var jsonOut bool fs := flag.NewFlagSet("lectio", flag.ContinueOnError) fs.SetOutput(stderr) @@ -172,6 +177,8 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { fs.BoolVar(&liturgy, "liturgy", false, "print the computed liturgical day (offline calendar engine) and exit") fs.StringVar(&calNew, "cal-new", "", "scaffold a new calendar-layer file NAME.ini and exit") fs.StringVar(&calCheck, "cal-check", "", "validate the calendar-layer file NAME.ini and exit") + fs.StringVar(&corpusCheck, "corpus-check", "", "validate a bible corpus (code or path to <code>.tsv) and exit") + fs.BoolVar(&jsonOut, "json", false, "with --corpus-check, print the report as JSON") if err := fs.Parse(rest); err != nil { return 2 @@ -203,6 +210,9 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { if calCheck != "" { return runCalCheck(calCheck, stdout, stderr) } + if corpusCheck != "" { + return runCorpusCheck(corpusCheck, jsonOut, stdout, stderr) + } cfg, err := config.Load() if err != nil { diff --git a/internal/cli/corpus.go b/internal/cli/corpus.go new file mode 100644 index 0000000..15a140c --- /dev/null +++ b/internal/cli/corpus.go @@ -0,0 +1,69 @@ +package cli + +import ( + "encoding/json" + "fmt" + "io" + "path/filepath" + "strings" + + "github.com/lukaszkasprzak/lectio/internal/bible" + "github.com/lukaszkasprzak/lectio/internal/config" +) + +// runCorpusCheck handles --corpus-check <code|path>: validates a bible +// corpus's sidecar + text (bible.CheckCorpus) and prints the report, plain or +// (with asJSON) as JSON. It exits 1 if the report has any errors, 0 +// otherwise -- coverage warnings never fail the check. +// +// arg is either a bare corpus code, resolved against the user corpora dir +// (config.CorporaDir(), which overrides the embedded built-ins the same way +// normal rendering does), or a path to a <code>.tsv file -- its directory +// becomes the user corpora dir for this run and its basename (minus ".tsv") +// the code. The path form lets a corpus be validated before it is copied +// into internal/bible/corpora/ (see scripts/corpus-validate.sh, make +// add-corpus). +func runCorpusCheck(arg string, asJSON bool, stdout, stderr io.Writer) int { + code := arg + if strings.Contains(arg, "/") || strings.HasSuffix(arg, ".tsv") { + dir := filepath.Dir(arg) + code = strings.TrimSuffix(filepath.Base(arg), ".tsv") + bible.SetUserCorporaDir(dir) + } else if dir, err := config.CorporaDir(); err == nil { + bible.SetUserCorporaDir(dir) + } + + report := bible.CheckCorpus(code) + + if asJSON { + enc := json.NewEncoder(stdout) + enc.SetIndent("", " ") + if err := enc.Encode(report); err != nil { + fmt.Fprintln(stderr, "lectio:", err) + return 1 + } + } else { + printCorpusReport(stdout, report) + } + + if !report.OK() { + return 1 + } + return 0 +} + +// printCorpusReport renders a CorpusReport as plain text: one line per +// error/warning, then a summary line. +func printCorpusReport(w io.Writer, r bible.CorpusReport) { + for _, e := range r.Errors { + fmt.Fprintf(w, "ERROR: %s\n", e) + } + for _, wm := range r.Warnings { + fmt.Fprintf(w, "WARNING: %s\n", wm) + } + if r.OK() { + fmt.Fprintf(w, "ok: %s (%d warning(s))\n", r.Code, len(r.Warnings)) + } else { + fmt.Fprintf(w, "FAIL: %s (%d error(s), %d warning(s))\n", r.Code, len(r.Errors), len(r.Warnings)) + } +} diff --git a/internal/cli/corpus_test.go b/internal/cli/corpus_test.go new file mode 100644 index 0000000..0ed17bc --- /dev/null +++ b/internal/cli/corpus_test.go @@ -0,0 +1,41 @@ +package cli + +import ( + "bytes" + "strings" + "testing" + + "github.com/lukaszkasprzak/lectio/internal/bible" +) + +func TestRunCorpusCheckPathArg(t *testing.T) { + t.Cleanup(func() { bible.SetUserCorporaDir("") }) + + var buf bytes.Buffer + if code := runCorpusCheck("../bible/testdata/corpora/badbook.tsv", false, &buf, &buf); code != 1 { + t.Fatalf("badbook: exit %d, want 1 (out=%s)", code, buf.String()) + } + if !strings.Contains(buf.String(), "ERROR:") { + t.Errorf("expected an ERROR: line, got %s", buf.String()) + } + + buf.Reset() + if code := runCorpusCheck("../bible/testdata/corpora/good.tsv", false, &buf, &buf); code != 0 { + t.Fatalf("good: exit %d, want 0 (out=%s)", code, buf.String()) + } + if !strings.Contains(buf.String(), "ok:") { + t.Errorf("expected an ok: summary, got %s", buf.String()) + } +} + +func TestRunCorpusCheckJSON(t *testing.T) { + t.Cleanup(func() { bible.SetUserCorporaDir("") }) + + var buf bytes.Buffer + if code := runCorpusCheck("../bible/testdata/corpora/badbook.tsv", true, &buf, &buf); code != 1 { + t.Fatalf("badbook --json: exit %d, want 1 (out=%s)", code, buf.String()) + } + if !strings.Contains(buf.String(), `"Errors"`) { + t.Errorf("expected a JSON report, got %s", buf.String()) + } +} |
