cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / config / output.go
output.go 4.5K
1package config23import (4 "errors"5 "fmt"6 "os"7 "path/filepath"8 "slices"9 "strings"10)1112// ParseFlags takes the options that have flags ("--log path" or13// "--log=path") out of args, returning their values by option name and14// the arguments left.15func ParseFlags(args []string) (map[string]string, []string, error) {16 flags := map[string]string{}17 var rest []string18 for i := 0; i < len(args); i++ {19 name, value, hasValue := strings.Cut(args[i], "=")20 o := flagOption(name)21 switch {22 case o == nil:23 rest = append(rest, args[i])24 case hasValue:25 flags[o.Name] = value26 case i+1 < len(args):27 flags[o.Name] = args[i+1]28 i++29 default:30 return nil, nil, errors.New(name + " needs a value")31 }32 }33 return flags, rest, nil34}3536func flagOption(flag string) *Option {37 if !strings.HasPrefix(flag, "--") {38 return nil39 }40 for i := range Options {41 if Options[i].Flag == flag {42 return &Options[i]43 }44 }45 return nil46}4748// FlagUsage lists the flags for a usage line, e.g. "[--log file]".49func FlagUsage() string {50 var parts []string51 for _, o := range Options {52 if o.Flag != "" {53 parts = append(parts, "["+o.Flag+" "+strings.TrimPrefix(o.Flag, "--")+"]")54 }55 }56 return strings.Join(parts, " ")57}5859// Show writes the effective configuration: every option with its value60// and where it came from, values that can hold secrets redacted.61func (c *Config) Show() string {62 var b strings.Builder63 fmt.Fprintf(&b, "# %s\n", c.Path)64 for _, g := range Groups {65 fmt.Fprintf(&b, "\n# %s\n", g)66 for _, o := range Options {67 if o.Group != g {68 continue69 }70 vals := c.List(o.Name)71 if !o.Repeat || len(vals) == 0 {72 vals = []Value{c.Get(o.Name)}73 }74 for _, v := range vals {75 raw := v.Raw76 if o.Redact != nil && raw != "" {77 raw = o.Redact(raw)78 }79 fmt.Fprintf(&b, "%s = %s", o.Name, raw)80 b.WriteString(strings.Repeat(" ", max(1, 40-len(o.Name)-len(raw)-3)))81 fmt.Fprintf(&b, "# %s\n", v.Src)82 }83 }84 }85 for _, w := range c.Warnings {86 fmt.Fprintf(&b, "\n# warning: %s", w)87 }88 if len(c.Warnings) > 0 {89 b.WriteByte('\n')90 }91 return b.String()92}9394// DefaultFile is a config file with every option commented out at its95// default, each with its description: what `012 config default` prints96// and `012 config edit` starts a new file with.97func DefaultFile() string {98 var b strings.Builder99 b.WriteString("# 012 configuration. Lines are key = value; # starts a comment.\n")100 b.WriteString("# Flags and environment variables override this file.\n")101 b.WriteString("# See `012 config` for the values in effect and docs/reference/config.md for more.\n")102 for _, g := range Groups {103 fmt.Fprintf(&b, "\n# ---- %s ----\n", g)104 for _, o := range Options {105 if o.Group != g {106 continue107 }108 b.WriteString("\n")109 for _, l := range wrap(stripTicks(o.Desc), 74) {110 b.WriteString("# " + l + "\n")111 }112 fmt.Fprintf(&b, "# %s = %s\n", o.Name, o.Default)113 }114 }115 return b.String()116}117118func stripTicks(s string) string { return strings.ReplaceAll(s, "`", "") }119120// wrap breaks s into lines of at most w columns at spaces.121func wrap(s string, w int) []string {122 var lines []string123 line := ""124 for word := range strings.FieldsSeq(s) {125 if line != "" && len(line)+1+len(word) > w {126 lines = append(lines, line)127 line = ""128 }129 if line != "" {130 line += " "131 }132 line += word133 }134 if line != "" {135 lines = append(lines, line)136 }137 return lines138}139140// SetInFile sets key to value in the config file at path: the last line141// setting key is replaced, or a line is added at the end. Comments and142// every other line are kept. The file and its directory are created if143// needed.144func SetInFile(path, key, value string) error {145 if _, ok := byName[key]; !ok {146 return errors.New("no option " + key)147 }148 data, err := os.ReadFile(path)149 if err != nil && !errors.Is(err, os.ErrNotExist) {150 return err151 }152 lines := strings.Split(strings.TrimRight(string(data), "\n"), "\n")153 if len(data) == 0 {154 lines = nil155 }156 newLine := key + " = " + value157 at := -1158 for i, l := range lines {159 if k, _, ok := splitLine(l); ok && k == key {160 at = i161 }162 }163 if at >= 0 {164 lines[at] = newLine165 } else {166 lines = append(lines, newLine)167 }168 if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {169 return err170 }171 tmp := path + ".tmp"172 if err := os.WriteFile(tmp, []byte(strings.Join(lines, "\n")+"\n"), 0o600); err != nil {173 return err174 }175 return os.Rename(tmp, path)176}177178// Names is every option name, sorted.179func Names() []string {180 names := make([]string, len(Options))181 for i, o := range Options {182 names[i] = o.Name183 }184 slices.Sort(names)185 return names186}