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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178 |
// Command patchwork publishes revision feeds for living pages.
package main
import (
"context"
"errors"
"flag"
"fmt"
"io"
"log/slog"
"os"
"os/signal"
"git.pablomurad.com/pablo/patchwork/internal/config"
"git.pablomurad.com/pablo/patchwork/internal/engine"
"git.pablomurad.com/pablo/patchwork/internal/source/folder"
"git.pablomurad.com/pablo/patchwork/internal/store"
)
// version is set at build time with -ldflags "-X main.version=...".
var version = "dev"
const usage = `patchwork publishes revision feeds for living pages.
Usage:
patchwork <command> [flags]
Commands:
init write a commented patchwork.yaml
scan scan the content folder once and apply the publish rules
watch keep scanning as files change
serve watch, and serve the API and feeds over HTTP
export write the API and feeds to a directory
log show a page's public history
diff show what changed in a revision
retract hide a published revision (or bring it back with -undo)
amend rewrite a published revision's note or kind
snapshot import an older copy of a page, to build history
rebuild-diffs recompute cached diffs
backup copy the database
doctor check the config, the content and the database
health check that a running serve answers (for containers)
version print the version
Every command except init and version reads patchwork.yaml from the
current directory, or the file named by --config or PATCHWORK_CONFIG.
Run "patchwork <command> -h" for a command's flags.
`
func main() {
if len(os.Args) < 2 {
fmt.Fprint(os.Stderr, usage)
os.Exit(2)
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
cmd, args := os.Args[1], os.Args[2:]
var err error
switch cmd {
case "init":
err = cmdInit(args)
case "scan":
err = cmdScan(ctx, args)
case "watch":
err = cmdWatch(ctx, args)
case "log":
err = cmdLog(ctx, args)
case "diff":
err = cmdDiff(ctx, args)
case "doctor":
err = cmdDoctor(ctx, args)
case "serve":
err = cmdServe(ctx, args)
case "export":
err = cmdExport(ctx, args)
case "retract":
err = cmdRetract(ctx, args)
case "amend":
err = cmdAmend(ctx, args)
case "snapshot":
err = cmdSnapshot(ctx, args)
case "rebuild-diffs":
err = cmdRebuildDiffs(ctx, args)
case "backup":
err = cmdBackup(ctx, args)
case "health":
err = cmdHealth(args)
case "version", "-v", "--version":
fmt.Println("patchwork", version)
case "help", "-h", "--help":
fmt.Print(usage)
default:
fmt.Fprintf(os.Stderr, "patchwork: unknown command %q\n\n%s", cmd, usage)
os.Exit(2)
}
if err != nil {
if errors.Is(err, flag.ErrHelp) {
os.Exit(2)
}
if errors.Is(err, errProblems) {
os.Exit(1)
}
fmt.Fprintln(os.Stderr, "patchwork:", err)
os.Exit(1)
}
}
// errProblems makes a command exit 1 after it has printed its own report.
var errProblems = errors.New("problems found")
func newFlags(name, args string) *flag.FlagSet {
fs := flag.NewFlagSet(name, flag.ContinueOnError)
fs.Usage = func() {
fmt.Fprintf(fs.Output(), "Usage: patchwork %s %s\n", name, args)
fs.PrintDefaults()
}
return fs
}
func configFlag(fs *flag.FlagSet) *string {
def := os.Getenv("PATCHWORK_CONFIG")
if def == "" {
def = "patchwork.yaml"
}
return fs.String("config", def, "path to patchwork.yaml")
}
// app is what most commands need: config, store and engine.
type app struct {
cfg *config.Config
store *store.Store
engine *engine.Engine
log *slog.Logger
}
func openApp(configPath string) (*app, error) {
cfg, err := config.Load(configPath)
if err != nil {
return nil, err
}
st, err := store.Open(cfg.DB)
if err != nil {
return nil, err
}
e, err := engine.New(st, cfg)
if err != nil {
st.Close()
return nil, err
}
return &app{cfg: cfg, store: st, engine: e, log: slog.New(slog.NewTextHandler(os.Stderr, nil))}, nil
}
func (a *app) Close() error { return a.store.Close() }
func (a *app) runner() *folder.Runner {
m := folder.Matcher{Include: a.cfg.Source.Include, Exclude: a.cfg.Source.Exclude}
return folder.NewRunner(a.cfg.Source.Path, m, a.engine, a.log)
}
func cmdInit(args []string) error {
fs := newFlags("init", "[-o patchwork.yaml] [-force]")
out := fs.String("o", "patchwork.yaml", "file to write")
force := fs.Bool("force", false, "overwrite an existing file")
if err := fs.Parse(args); err != nil {
return err
}
if _, err := os.Stat(*out); err == nil && !*force {
return fmt.Errorf("%s already exists (use -force to overwrite)", *out)
}
if err := os.WriteFile(*out, config.Example, 0o644); err != nil {
return err
}
fmt.Printf("wrote %s; set site.base_url and source.path, then run: patchwork doctor\n", *out)
return nil
}
var stdout io.Writer = os.Stdout
|