Home

ffnix @main - refs - log -
-
https://git.jolheiser.com/ffnix.git
Generate some nix expressions from a flag.FlagSet
ffnix / ffnix.go
- raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package ffnix

import (
	"bytes"
	_ "embed"
	"flag"
	"fmt"
	"reflect"
	"sort"
	"strings"
	"text/template"
)

var (
	//go:embed templates/default.nix.tmpl
	defaultNix string
	Default    = template.Must(template.New("").Parse(defaultNix))
	//go:embed templates/module.nix.tmpl
	moduleNix string
	Module    = template.Must(template.New("").Parse(moduleNix))
	//go:embed templates/pkg.nix.tmpl
	pkgNix string
	Pkg    = template.Must(template.New("").Parse(pkgNix))
)

// FlagSet turns a flag.FlagSet into markdown
func FlagSet(fs *flag.FlagSet) (results, error) {
	return flagSetCommand(fs).Markdown()
}

func flagSetCommand(fs *flag.FlagSet) command {
	a := command{
		Name:  fs.Name(),
		Flags: []appFlag{},
	}
	fs.VisitAll(func(f *flag.Flag) {
		if strings.HasPrefix(f.Usage, "--") {
			return
		}
		def := f.DefValue
		if isZeroValue(f, def) {
			def = ""
		}
		nixType := "str"
		typ := reflect.TypeOf(f.Value).Elem()
		switch typ.Kind() {
		case reflect.Bool:
			nixType = "bool"
		case reflect.Int:
			nixType = "int"
		case reflect.String:
			nixType = "str"
			def = fmt.Sprintf("%q", def)
		default:
			def = fmt.Sprintf("%q", def)
		}
		nameParts := strings.Split(f.Name, "-")
		for idx, part := range nameParts[1:] {
			nameParts[1+idx] = strings.Title(part)
		}
		name := strings.Join(nameParts, "")
		af := appFlag{
			Name:        name,
			Flag:        f.Name,
			Description: f.Usage,
			Default:     def,
			Type:        nixType,
		}
		a.Flags = append(a.Flags, af)
	})
	for idx, f := range a.Flags {
		a.Flags[idx] = f
	}
	sort.Slice(a.Flags, func(i, j int) bool {
		return a.Flags[i].Name < a.Flags[j].Name
	})
	return a
}

type command struct {
	Name  string
	Flags []appFlag
}

type results struct {
	Default string
	Module  string
	Pkg     string
}

func (c command) Markdown() (results, error) {
	var res results

	var bufDefault bytes.Buffer
	if err := Default.Execute(&bufDefault, c); err != nil {
		return res, err
	}
	res.Default = bufDefault.String()

	var bufModule bytes.Buffer
	if err := Module.Execute(&bufModule, c); err != nil {
		return res, err
	}
	res.Module = bufModule.String()

	var bufPkg bytes.Buffer
	if err := Pkg.Execute(&bufPkg, c); err != nil {
		return res, err
	}
	res.Pkg = bufPkg.String()

	return res, nil
}

type appFlag struct {
	Name        string
	Flag        string
	Type        string
	Description string
	Default     string
}

// From stdlib

func isZeroValue(f *flag.Flag, value string) bool {
	// Build a zero value of the flag's Value type, and see if the
	// result of calling its String method equals the value passed in.
	// This works unless the Value type is itself an interface type.
	typ := reflect.TypeOf(f.Value)
	var z reflect.Value
	if typ.Kind() == reflect.Ptr {
		z = reflect.New(typ.Elem())
	} else {
		z = reflect.Zero(typ)
	}
	return value == z.Interface().(flag.Value).String()
}

type boolFlag interface {
	flag.Value
	IsBoolFlag() bool
}