package next import ( "errors" "fmt" "log" "os" "regexp" "strconv" "strings" "time" "git.bth.moi/bth/um/cmd" "git.bth.moi/bth/um/flags" "git.bth.moi/bth/um/last" ) const ( CMD = cmd.Next SUMMARY = "create the next um file and open with emacsclient" NEXT_NUM_ERROR = "failed to find number in last file" ) const FILE_REGEXP = `(?m)^([0-9]+)\..*\.md$` // TODO: consider supporting a config file, since my preference for dots here is not precisely ISO8601. const DATE_FORMAT = "2006.01.02" var fileRegexp *regexp.Regexp = regexp.MustCompile(FILE_REGEXP) type options struct { Descriptor flags.Arg Tags flags.Arg Help flags.Bool } func initOpts() options { return options{ flags.Arg{"", "midfix file descriptor"}, flags.Arg{"", "tags to add to new file, comma separated. '+' will add file descriptor as tag"}, flags.Bool{"--help", "-h", false, "show help"}, } } // takes the complete last file string // returns the number as string func NumFromLast(l string) (string, error) { res := fileRegexp.FindStringSubmatch(l) num := "" if len(res) < 2 { return num, fmt.Errorf("%s: %s", NEXT_NUM_ERROR, l) } num = res[1] return num, nil } // takes the complete last file string and new descriptor // returns the complete next file string func next(last string, desc string) (string, error) { num, err := NumFromLast(last) if err != nil { return "", err } i, err := strconv.Atoi(num) if err != nil { return "", err } if desc != "" { desc = desc + "." } width := len(num) // whaddya doin bro wahh fmtstr := fmt.Sprintf("%%0%dd.%%smd", width) n := fmt.Sprintf(fmtstr, i+1, desc) return n, nil } // writes the header string to the filename. // // NOTE: we used to call into emacsclient to get config values, but I want separation of concern // betweeen the CLI and the emacs um-mode. // // So I've got Go and and an ancient beloved Lisp machine trying to live together. func writeHeader(f, tags, descriptor string) error { headerFmt := `# %s : %s %s` tagsStr := "" // WARN: strings.Split will return the whole string when no match, even when the input string is // empty. Thus this ugly dance to ensure an empty slice. var tagsX []string if tags != "" { tagsX = strings.Split(tags, ",") } for _, t := range tagsX { if t == "+" { t = descriptor } tagsStr += fmt.Sprintf("+ %s\n", t) } // NOTE: always needs the trailing newline. tagsStr += "\n" header := fmt.Sprintf(headerFmt, f, time.Now().Format(DATE_FORMAT), tagsStr) return os.WriteFile(f, []byte(header), 0644) } func Next(args []string) { opts := initOpts() help := flags.NewHelpError(CMD, SUMMARY) if err := flags.ParseArgs(help, args, &opts); err != nil { if errors.As(err, &help) { fmt.Println(help) return } log.Fatalf("um %s: %s", CMD, err) } l, err := last.GlobLast(last.GLOB) if err != nil { log.Fatalf("um %s: %v", CMD, err) } filename, err := next(l, opts.Descriptor.Val) if err != nil { log.Fatalf("um %s: %v", CMD, err) } if err := writeHeader(filename, opts.Tags.Val, opts.Descriptor.Val); err != nil { log.Fatalf("um %s: %v", CMD, err) } // NOTE: sends filename to stdout for piping fmt.Println(filename) }