aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino/main.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-11 14:47:10 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-11 15:01:57 +0200
commit42b02c47be9b285099203e44a2570636d4ca6f03 (patch)
tree82bcb9e19bd886f36e1ca7b2d94a204c988f1fd1 /cmd/krino/main.go
downloadkrino-42b02c47be9b285099203e44a2570636d4ca6f03.tar.gz
krino-42b02c47be9b285099203e44a2570636d4ca6f03.zip
krino: foundation — sexp reader, config language, init/new/check
Diffstat (limited to 'cmd/krino/main.go')
-rw-r--r--cmd/krino/main.go105
1 files changed, 105 insertions, 0 deletions
diff --git a/cmd/krino/main.go b/cmd/krino/main.go
new file mode 100644
index 0000000..0048098
--- /dev/null
+++ b/cmd/krino/main.go
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Command krino sorts files in configured directories by rules.
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+)
+
+// version is stamped by the Makefile with -ldflags "-X main.version=...".
+var version = "dev"
+
+const usage = `usage: krino [-y | -n] [-v] [--json] [-c FILE] [NAME...]
+ krino init
+ krino new NAME PATH
+ krino check [NAME...]
+ krino explain FILE
+ krino log [-n N]
+ krino undo [RUN]
+
+Sort the files in the directories listed in krino.conf by their rules.
+
+ -y apply without asking
+ -n dry run: show the plan, change nothing
+ -v also list unmatched, ignored and busy files
+ --json with -n: print the plan as JSON
+ -c FILE use FILE instead of ~/.config/krino/krino.conf
+ -h, --help show this help
+ --version print the version
+`
+
+// globals holds the flags that may appear before or after a subcommand.
+type globals struct {
+ yes, dry, verbose, json bool
+ conf string
+}
+
+// command is a subcommand: it gets the parsed globals and its own arguments.
+type command func(g *globals, args []string, stdout, stderr io.Writer) int
+
+// commands maps subcommand names to their functions; each cmd file adds itself.
+var commands = map[string]command{}
+
+func main() {
+ os.Exit(run(os.Args[1:], os.Stdout, os.Stderr))
+}
+
+// run is main without the process exit, so tests can drive it.
+func run(args []string, stdout, stderr io.Writer) int {
+ g := &globals{}
+ fs := flagSet("krino", g)
+ fs.BoolVar(&g.yes, "y", false, "")
+ fs.BoolVar(&g.dry, "n", false, "")
+ fs.BoolVar(&g.verbose, "v", false, "")
+ fs.BoolVar(&g.json, "json", false, "")
+ showVersion := fs.Bool("version", false, "")
+ if code, ok := parse(fs, args, stdout, stderr); !ok {
+ return code
+ }
+ if *showVersion {
+ fmt.Fprintf(stdout, "krino %s\n", version)
+ return 0
+ }
+ rest := fs.Args()
+ if len(rest) > 0 {
+ if cmd, ok := commands[rest[0]]; ok {
+ return cmd(g, rest[1:], stdout, stderr)
+ }
+ }
+ return cmdSort(g, rest, stdout, stderr)
+}
+
+// flagSet returns a silent flag set with -c bound to g, shared by every command.
+func flagSet(name string, g *globals) *flag.FlagSet {
+ fs := flag.NewFlagSet(name, flag.ContinueOnError)
+ fs.SetOutput(io.Discard)
+ fs.StringVar(&g.conf, "c", g.conf, "")
+ return fs
+}
+
+// parse parses args into fs. On -h it prints the usage and returns (0, false);
+// on a bad flag it reports it and returns (2, false).
+func parse(fs *flag.FlagSet, args []string, stdout, stderr io.Writer) (int, bool) {
+ err := fs.Parse(args)
+ switch {
+ case err == nil:
+ return 0, true
+ case errors.Is(err, flag.ErrHelp):
+ fmt.Fprint(stdout, usage)
+ return 0, false
+ default:
+ fmt.Fprintf(stderr, "krino: %v\nrun 'krino -h' for help\n", err)
+ return 2, false
+ }
+}
+
+// cmdSort plans and applies the included directories. It arrives in plan 3.
+func cmdSort(g *globals, names []string, stdout, stderr io.Writer) int {
+ fmt.Fprintln(stderr, "krino: sorting is not implemented yet; try 'krino check'")
+ return 2
+}