-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroot.go
More file actions
138 lines (117 loc) · 3.53 KB
/
Copy pathroot.go
File metadata and controls
138 lines (117 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Package cli builds the cryp command tree on top of the cryptostanford library.
package cli
import (
"fmt"
"os"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
"github.com/tamnd/cryptostanford-cli/cryptostanford"
)
// Build metadata, set via -ldflags at release time.
var (
Version = "dev"
Commit = "none"
Date = "unknown"
)
// exit codes
const (
exitError = 1
exitUsage = 2
exitNoData = 3
)
// ExitError carries a process exit code up to main.
type ExitError struct {
Code int
Err error
}
func (e *ExitError) Error() string {
if e.Err != nil {
return e.Err.Error()
}
return fmt.Sprintf("exit %d", e.Code)
}
func (e *ExitError) Unwrap() error { return e.Err }
func codeError(code int, err error) error { return &ExitError{Code: code, Err: err} }
// App holds shared state threaded through every command.
type App struct {
client *cryptostanford.Client
cfg cryptostanford.Config
output string
noHeader bool
template string
limit int
quiet bool
}
// Root builds the root command and its subtree.
func Root() *cobra.Command {
app := &App{cfg: cryptostanford.DefaultConfig()}
root := &cobra.Command{
Use: "cryp",
Short: "Browse Stanford Cryptography course content",
Long: `cryp fetches lecture schedules and materials from Stanford's CS255
Introduction to Cryptography course at https://crypto.stanford.edu/~dabo/cs255/.
It returns records as table, JSON, JSONL, CSV, or TSV. All data is
pulled directly from the public course website; no API key is required.`,
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
return app.setup()
},
}
pf := root.PersistentFlags()
pf.StringVarP(&app.output, "output", "o", "auto", "output format: table|json|jsonl|csv|tsv (auto=table on TTY, jsonl piped)")
pf.BoolVar(&app.noHeader, "no-header", false, "omit the header row in table/csv/tsv")
pf.StringVar(&app.template, "template", "", "Go text/template applied per record")
pf.IntVarP(&app.limit, "limit", "n", 0, "maximum number of records (0 = all)")
pf.BoolVarP(&app.quiet, "quiet", "q", false, "suppress progress messages on stderr")
pf.StringVar(&app.cfg.BaseURL, "base-url", app.cfg.BaseURL, "override the base URL")
pf.DurationVar(&app.cfg.Rate, "delay", app.cfg.Rate, "minimum spacing between requests")
pf.DurationVar(&app.cfg.Timeout, "timeout", app.cfg.Timeout, "per-request timeout")
pf.IntVar(&app.cfg.Retries, "retries", app.cfg.Retries, "retry attempts on 429/5xx")
pf.StringVar(&app.cfg.UserAgent, "user-agent", app.cfg.UserAgent, "User-Agent sent with each request")
root.AddCommand(
app.lecturesCmd(),
app.searchCmd(),
newVersionCmd(),
)
return root
}
func (a *App) setup() error {
if a.output == "" || a.output == "auto" {
if isatty.IsTerminal(os.Stdout.Fd()) {
a.output = "table"
} else {
a.output = "jsonl"
}
}
if !validFormat(a.output) {
return codeError(exitUsage, fmt.Errorf("unknown output format %q", a.output))
}
a.client = cryptostanford.NewClient(a.cfg)
return nil
}
func (a *App) render(records any) error {
r := newRenderer(os.Stdout, a.output, a.noHeader, a.template)
return r.render(records)
}
func (a *App) renderOrEmpty(records any, n int) error {
if err := a.render(records); err != nil {
return err
}
if n == 0 {
return codeError(exitNoData, nil)
}
return nil
}
func (a *App) progressf(format string, args ...any) {
if a.quiet {
return
}
_, _ = fmt.Fprintf(os.Stderr, format+"\n", args...)
}
func (a *App) effectiveLimit(def int) int {
if a.limit > 0 {
return a.limit
}
return def
}