aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2023-12-26 07:42:51 +0100
committerPřemysl Eric Janouch <p@janouch.name>2023-12-26 07:42:51 +0100
commit37292f8897efae517e5a22142d9cafdd93a7891c (patch)
treec5e542a3d441b9a1e67e05c9059adb2dcf5d217d
parent6345d9e18e126b85c7e3f59e1b390084320bec8c (diff)
downloadgallery-37292f8897efae517e5a22142d9cafdd93a7891c.tar.gz
gallery-37292f8897efae517e5a22142d9cafdd93a7891c.tar.xz
gallery-37292f8897efae517e5a22142d9cafdd93a7891c.zip
Command arguments
-rw-r--r--main.go139
1 files changed, 92 insertions, 47 deletions
diff --git a/main.go b/main.go
index 7651525..546e3ff 100644
--- a/main.go
+++ b/main.go
@@ -262,13 +262,16 @@ func parallelize(strings []string, callback parallelFunc) error {
// cmdInit initializes a "gallery directory" that contains gallery.sqlite,
// images, thumbs.
func cmdInit(fs *flag.FlagSet, args []string) error {
- if len(args) != 1 {
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ if fs.NArg() != 1 {
return errWrongUsage
}
-
- if err := openDB(args[0]); err != nil {
+ if err := openDB(fs.Arg(0)); err != nil {
return err
}
+
if _, err := db.Exec(initializeSQL); err != nil {
return err
}
@@ -1076,14 +1079,17 @@ func handleAPISearch(w http.ResponseWriter, r *http.Request) {
// cmdWeb runs a web UI against GD on ADDRESS.
func cmdWeb(fs *flag.FlagSet, args []string) error {
- if len(args) != 2 {
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ if fs.NArg() != 2 {
return errWrongUsage
}
- if err := openDB(args[0]); err != nil {
+ if err := openDB(fs.Arg(0)); err != nil {
return err
}
- address := args[1]
+ address := fs.Arg(1)
// This separation is not strictly necessary,
// but having an elementary level of security doesn't hurt either.
@@ -1683,7 +1689,6 @@ func syncRun(ctx context.Context, tx *sql.Tx, roots []string) error {
// cmdSync ensures the given (sub)roots are accurately reflected
// in the database.
func cmdSync(fs *flag.FlagSet, args []string) error {
- // TODO: Convert the other commands as well.
fullpaths := fs.Bool("fullpaths", false, "don't basename the arguments")
if err := fs.Parse(args); err != nil {
return err
@@ -1691,7 +1696,6 @@ func cmdSync(fs *flag.FlagSet, args []string) error {
if fs.NArg() < 2 {
return errWrongUsage
}
-
if err := openDB(fs.Arg(0)); err != nil {
return err
}
@@ -1750,10 +1754,13 @@ func cmdSync(fs *flag.FlagSet, args []string) error {
// cmdRemove is for manual removal of subtrees from the database.
// Beware that inputs are database, not filesystem paths.
func cmdRemove(fs *flag.FlagSet, args []string) error {
- if len(args) < 2 {
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ if fs.NArg() < 2 {
return errWrongUsage
}
- if err := openDB(args[0]); err != nil {
+ if err := openDB(fs.Arg(0)); err != nil {
return err
}
@@ -1763,7 +1770,7 @@ func cmdRemove(fs *flag.FlagSet, args []string) error {
}
defer tx.Rollback()
- for _, path := range args[1:] {
+ for _, path := range fs.Args()[1:] {
var id sql.NullInt64
for _, name := range decodeWebPath(path) {
if err := tx.QueryRow(`SELECT id FROM node
@@ -1795,18 +1802,21 @@ func cmdRemove(fs *flag.FlagSet, args []string) error {
// cmdTag mass imports tags from data passed on stdin as a TSV
// of SHA1 TAG WEIGHT entries.
func cmdTag(fs *flag.FlagSet, args []string) error {
- if len(args) < 2 || len(args) > 3 {
- return errors.New("usage: GD SPACE [DESCRIPTION]")
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ if fs.NArg() < 2 || fs.NArg() > 3 {
+ return errWrongUsage
}
- if err := openDB(args[0]); err != nil {
+ if err := openDB(fs.Arg(0)); err != nil {
return err
}
- space := args[1]
+ space := fs.Arg(1)
var description sql.NullString
- if len(args) >= 3 {
- description = sql.NullString{String: args[2], Valid: true}
+ if fs.NArg() >= 3 {
+ description = sql.NullString{String: fs.Arg(2), Valid: true}
}
// Note that starting as a write transaction prevents deadlocks.
@@ -1871,7 +1881,6 @@ func cmdTag(fs *flag.FlagSet, args []string) error {
if err := scanner.Err(); err != nil {
return err
}
-
return tx.Commit()
}
@@ -1946,10 +1955,13 @@ func checkFiles(root, suffix string, hashes []string) (bool, []string, error) {
// cmdCheck carries out various database consistency checks.
func cmdCheck(fs *flag.FlagSet, args []string) error {
- if len(args) != 1 {
- return errors.New("usage: GD")
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ if fs.NArg() != 1 {
+ return errWrongUsage
}
- if err := openDB(args[0]); err != nil {
+ if err := openDB(fs.Arg(0)); err != nil {
return err
}
@@ -2063,14 +2075,17 @@ func makeThumbnailFor(sha1 string) (message string, err error) {
// cmdThumbnail generates missing thumbnails, in parallel.
func cmdThumbnail(fs *flag.FlagSet, args []string) error {
- if len(args) < 1 {
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ if fs.NArg() < 1 {
return errWrongUsage
}
- if err := openDB(args[0]); err != nil {
+ if err := openDB(fs.Arg(0)); err != nil {
return err
}
- hexSHA1 := args[1:]
+ hexSHA1 := fs.Args()[1:]
if len(hexSHA1) == 0 {
// Get all unique images in the database with no thumbnail.
var err error
@@ -2216,16 +2231,19 @@ func makeDhashFor(sha1 string) (message string, err error) {
return "", err
}
-// cmdDhash generates perceptual hash from thumbnails.
+// cmdDhash computes perceptual hashes from thumbnails.
func cmdDhash(fs *flag.FlagSet, args []string) error {
- if len(args) < 1 {
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ if fs.NArg() < 1 {
return errWrongUsage
}
- if err := openDB(args[0]); err != nil {
+ if err := openDB(fs.Arg(0)); err != nil {
return err
}
- hexSHA1 := args[1:]
+ hexSHA1 := fs.Args()[1:]
if len(hexSHA1) == 0 {
var err error
hexSHA1, err = dbCollectStrings(`SELECT sha1 FROM image
@@ -2242,40 +2260,67 @@ func cmdDhash(fs *flag.FlagSet, args []string) error {
var errWrongUsage = errors.New("wrong usage")
var commands = map[string]struct {
- handler func(*flag.FlagSet, []string) error
- usage string
+ handler func(*flag.FlagSet, []string) error
+ usage string
+ function string
}{
- "init": {cmdInit, "GD"},
- "web": {cmdWeb, "GD ADDRESS"},
- "tag": {cmdTag, "GD SPACE [DESCRIPTION]"},
- "sync": {cmdSync, "GD ROOT..."},
- "remove": {cmdRemove, "GD PATH..."},
- "check": {cmdCheck, "GD"},
- "thumbnail": {cmdThumbnail, "GD [SHA1...]"},
- "dhash": {cmdDhash, "GD [SHA1...]"},
+ "init": {cmdInit, "GD", "Initialize a database."},
+ "web": {cmdWeb, "GD ADDRESS", "Launch a web interface."},
+ "tag": {cmdTag, "GD SPACE [DESCRIPTION]", "Import tags."},
+ "sync": {cmdSync, "GD ROOT...", "Synchronise with the filesystem."},
+ "remove": {cmdRemove, "GD PATH...", "Remove database subtrees."},
+ "check": {cmdCheck, "GD", "Run consistency checks."},
+ "thumbnail": {cmdThumbnail, "GD [SHA1...]", "Generate thumbnails."},
+ "dhash": {cmdDhash, "GD [SHA1...]", "Compute perceptual hashes."},
+}
+
+func usage() {
+ f := flag.CommandLine.Output()
+ fmt.Fprintf(f, "Usage: %s COMMAND [ARG...]\n", os.Args[0])
+ flag.PrintDefaults()
+
+ // The alphabetic ordering is unfortunate, but tolerable.
+ keys := []string{}
+ for key := range commands {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+
+ fmt.Fprintf(f, "\nCommands:\n")
+ for _, key := range keys {
+ fmt.Fprintf(f, " %s [OPTION...] %s\n \t%s\n",
+ key, commands[key].usage, commands[key].function)
+ }
}
func main() {
- // TODO: Implement a global -h command.
- // The flag package doesn't reorder, unlike GNU, so it's fine.
- if len(os.Args) <= 2 {
- log.Fatalln("Missing arguments")
+ // This implements the -h switch for us by default.
+ // The rest of the handling here closely follows what flag does internally.
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() < 1 {
+ flag.Usage()
+ os.Exit(2)
}
- cmd, ok := commands[os.Args[1]]
+ cmd, ok := commands[flag.Arg(0)]
if !ok {
- log.Fatalln("Unknown command: " + os.Args[1])
+ fmt.Fprintf(flag.CommandLine.Output(),
+ "unknown command: %s\n", flag.Arg(0))
+ flag.Usage()
+ os.Exit(2)
}
- fs := flag.NewFlagSet(os.Args[1], flag.ExitOnError)
+ fs := flag.NewFlagSet(flag.Arg(0), flag.ExitOnError)
fs.Usage = func() {
fmt.Fprintf(fs.Output(),
- "Usage: %s [OPTION...] %s...\n", fs.Name(), cmd.usage)
+ "Usage: %s [OPTION...] %s\n%s\n",
+ fs.Name(), cmd.usage, cmd.function)
fs.PrintDefaults()
}
taskSemaphore = newSemaphore(runtime.NumCPU())
- err := cmd.handler(fs, os.Args[2:])
+ err := cmd.handler(fs, flag.Args()[1:])
// Note that the database object has a closing finalizer,
// we just additionally print any errors coming from there.