diff --git a/cat/cat.go b/cat/cat.go index eca92e5..2b8dfd8 100644 --- a/cat/cat.go +++ b/cat/cat.go @@ -30,7 +30,7 @@ const FILE_LINK_REGEXP = `(?m)` + HR_BLOCK_STRIP + `([0-9]+\.[^\.]*\.*md\n)+\n` var fileLinkRegexp *regexp.Regexp = regexp.MustCompile(FILE_LINK_REGEXP) type options struct { - Filelist flags.Arg + Filelist flags.Glob Base flags.String KeepHeader flags.Bool KeepTitle flags.Bool @@ -40,7 +40,7 @@ type options struct { func initOpts() options { return options{ - flags.Arg{"", "filelist. accepts from stdin if not provided"}, + flags.Glob{nil, ".um filelist. accepts multiple. reads from stdin if not provided"}, flags.String{"--base", "-b", "", "base directory prepended to files in filelist"}, flags.Bool{"--keep-header", "-d", false, "preserve um headers in concatenated file. overrides --keep-title"}, flags.Bool{"--keep-title", "-t", false, "preserve um titles in concatenated file"}, @@ -120,7 +120,8 @@ func Cat(args []string) { } log.Fatalf("um %s: %s", CMD, err) } - files, err := pipe.FileListSplitMaybeStdin(opts.Filelist.Val) + // NOTE: um cat expects .um files, the content of which is assembled: + files, err := pipe.FileListFromGlobOrStdin(opts.Filelist.Val) if err != nil { log.Fatalf("um %s: %s", CMD, err) } diff --git a/flags/flags.go b/flags/flags.go index 84880e4..54ec9bd 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -31,6 +31,11 @@ type Arg struct { Help string } +type Glob struct { + Val []string + Help string +} + type String struct { Long string Short string @@ -69,6 +74,33 @@ func (f *Arg) MaybeIncrement(i int) int { return i } +func (f *Glob) Set(arg string) { + f.Val = append(f.Val, arg) +} + +func (f *Glob) Valid(args []string, i int) bool { + return true +} + +func (f *Glob) Match(arg string, i, j int) bool { + // NOTE: this is the key difference from Arg: by not requiring that the arg and flag positions + // match, a shell-expanded glob of files of any length will populate the value. + return i >= j && !hasDashPrefix(arg) +} + +func (f *Glob) IsSet() bool { + return f.Val != nil && len(f.Val) != 0 +} + +func (f *Glob) IsHelp() bool { + return false +} + +func (f *Glob) MaybeIncrement(i int) int { + // NOTE: because this is only needed where a flag --prefix exists: + return i +} + func (f *String) Set(arg string) { f.Val = arg } @@ -145,16 +177,16 @@ func expandOpts(opts any) ([]Flag, error) { for j := 0; j < v.NumField(); j++ { field := v.Field(j) if field.Kind() != reflect.Struct { - return nil, ParseError{fmt.Sprintf("field must be a struct: %#v", field.Kind())} + return nil, ParseError{fmt.Sprintf("field must be a struct: %v", field.Kind())} } if !field.CanAddr() { - return nil, ParseError{fmt.Sprintf("field must be addressable type: %#v", field.Type())} + return nil, ParseError{fmt.Sprintf("field must be addressable type: %v", field.Type())} } // NOTE: Addr() gets the underlying address: if f, ok := field.Addr().Interface().(Flag); ok { flags = append(flags, f) } else { - return nil, ParseError{fmt.Sprintf("field must be a []Flag interface: %#v", field.Type())} + return nil, ParseError{fmt.Sprintf("field must be a []Flag interface: %v", field.Type())} } } return flags, nil diff --git a/flags/help.go b/flags/help.go index 6408f11..5ec106c 100644 --- a/flags/help.go +++ b/flags/help.go @@ -61,9 +61,12 @@ func (h HelpError) Help(opts any) error { tField := t.Field(i) vField := v.Field(i) switch vField.Interface().(type) { - case Arg: + case Arg, Glob: positional += fmt.Sprintf(" [%s]", strings.ToLower(tField.Name)) - fmt.Fprintf(w, "[%v]\tstring\t%v\n", strings.ToLower(tField.Name), vField.FieldByName("Help")) + fmt.Fprintf(w, "[%v]\t%v\t%v\n", + strings.ToLower(tField.Name), + vField.FieldByName("Val").Type(), + vField.FieldByName("Help")) case String, Bool: if tField.Name != "Help" { positional += fmt.Sprintf(" [%s]", vField.FieldByName("Long")) @@ -75,7 +78,7 @@ func (h HelpError) Help(opts any) error { vField.FieldByName("Help"), ) default: - return ParseError{fmt.Sprintf("needs a []Flag interface: %#v", vField.Type())} + return ParseError{fmt.Sprintf("Help did not recognize the Flag: %v", vField.Type())} } } diff --git a/pipe/pipe.go b/pipe/pipe.go index 47a6af1..7c78345 100644 --- a/pipe/pipe.go +++ b/pipe/pipe.go @@ -29,15 +29,30 @@ func GetStdin() ([]string, error) { return strings.Split(s, Newline), nil } -// reads files from stdin if present, otherwise from the filename: -func FileListSplitMaybeStdin(f string) ([]string, error) { +// reads files from stdin if present, otherwise just returns the glob: +func GlobOrStdin(glob []string) ([]string, error) { filelist, err := GetStdin() if err != nil { - // BORK: don't see a neater way of wrapping these two when we depend on the non-nil in this block: - // if i just return FileListSplit(), we lose the first err - filelist, err2 := FileListSplit(f) - if err2 != nil { - return nil, fmt.Errorf("%w: %w", err, err2) + if glob == nil || len(glob) == 0 { + return nil, fmt.Errorf("glob is empty") + } + return glob, nil + } + return filelist, nil +} + +// reads files from stdin if present, otherwise reads content from files in glob and assembles into a list. +func FileListFromGlobOrStdin(glob []string) ([]string, error) { + filelist, err := GetStdin() + if err != nil { + for _, f := range glob { + // TODO: don't see a neater way of wrapping these two when we depend on the non-nil in this block: + // if i just return FileListSplit(), we lose the first err + fl, err2 := FileListSplit(f) + if err2 != nil { + return nil, fmt.Errorf("%w: %w", err, err2) + } + filelist = append(filelist, fl...) } return filelist, nil } diff --git a/sort/sort.go b/sort/sort.go index 5bdda0a..7f67c1f 100644 --- a/sort/sort.go +++ b/sort/sort.go @@ -19,15 +19,15 @@ const ( ) type options struct { - Source flags.String - Key flags.String - Write flags.Bool - Help flags.Bool + Filelist flags.Glob + Key flags.String + Write flags.Bool + Help flags.Bool } func initOpts() options { return options{ - flags.String{"--source", "-s", "", "path to source list. reads from stdin if omitted."}, + flags.Glob{nil, ".md filelist. accepts multiple. reads from stdin if not provided"}, flags.String{"--key", "-k", "", "path to sort key"}, flags.Bool{"--write", "-w", false, "write sorted list back to --key file"}, flags.Bool{"--help", "-h", false, "show help"}, @@ -84,7 +84,8 @@ func Sort(args []string) { return } - sslice, err := pipe.FileListSplitMaybeStdin(opts.Source.Val) + // NOTE: um sort expects a list of .md files, in contrast to um cat. + sslice, err := pipe.GlobOrStdin(opts.Filelist.Val) if err != nil { log.Fatalf("um %s: %s", CMD, err) }