Home

gen @main - refs - log -
-
https://git.jolheiser.com/gen.git
Generate Go flags and Nix module
gen / main.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
package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/cue/load"
)

func main() {
	if err := maine(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, "gen:", err)
		os.Exit(1)
	}
}

func maine(args []string) error {
	fs := flag.NewFlagSet("gen", flag.ContinueOnError)
	var (
		schemaPathFlag  = fs.String("schema", "schema.cue", "CUE schema file or package directory")
		goOutFlag       = fs.String("go", "", "Go output file")
		goPkgFlag       = fs.String("go-package", "main", "Go package name")
		goTypeFlag      = fs.String("go-type", "cliArgs", "Go name of the root struct type")
		goSuffixFlag    = fs.String("go-type-suffix", "Args", "suffix for nested struct type names")
		initialismsFlag = fs.String("initialisms", "", "extra comma-separated initialisms for Go names")
		nixOutFlag      = fs.String("nix", "", "Nix options output file")
		checkFlag       = fs.Bool("check", false, "report stale outputs instead of writing them")
	)
	if err := fs.Parse(args); err != nil {
		return err
	}
	if *goOutFlag == "" && *nixOutFlag == "" {
		return errors.New("nothing to do: set --go and/or --nix")
	}

	v, err := loadSchema(*schemaPathFlag)
	if err != nil {
		return err
	}
	names := newNamer(strings.Split(*initialismsFlag, ","))
	root, err := buildModel(v, *goTypeFlag, *goSuffixFlag, names)
	if err != nil {
		return err
	}

	schemaName := filepath.Base(*schemaPathFlag)
	var outputs []output
	if *goOutFlag != "" {
		src, err := genGo(root, *goPkgFlag, schemaName)
		if err != nil {
			return err
		}
		outputs = append(outputs, output{path: *goOutFlag, data: src})
	}
	if *nixOutFlag != "" {
		src, err := genNix(root, schemaName)
		if err != nil {
			return err
		}
		outputs = append(outputs, output{path: *nixOutFlag, data: src})
	}

	if *checkFlag {
		return checkOutputs(outputs)
	}
	return writeOutputs(outputs)
}

// output is a generated file
type output struct {
	path string
	data []byte
}

// checkOutputs returns an error listing any outputs that don't match what's on disk
func checkOutputs(outputs []output) error {
	var stale []string
	for _, out := range outputs {
		existing, err := os.ReadFile(out.path)
		if err != nil || !bytes.Equal(existing, out.data) {
			stale = append(stale, out.path)
		}
	}
	if len(stale) > 0 {
		return fmt.Errorf("out of date, regenerate: %s", strings.Join(stale, ", "))
	}
	return nil
}

// writeOutputs writes each output, creating directories as needed
func writeOutputs(outputs []output) error {
	for _, out := range outputs {
		if err := os.MkdirAll(filepath.Dir(out.path), 0o755); err != nil {
			return err
		}
		if err := os.WriteFile(out.path, out.data, 0o644); err != nil {
			return err
		}
	}
	return nil
}

// loadSchema loads a CUE schema from a file, or from a package if path is a directory
func loadSchema(path string) (cue.Value, error) {
	fi, err := os.Stat(path)
	if err != nil {
		return cue.Value{}, err
	}

	cfg := &load.Config{}
	arg := path
	if fi.IsDir() {
		cfg.Dir = path
		arg = "."
	}

	instances := load.Instances([]string{arg}, cfg)
	if len(instances) != 1 {
		return cue.Value{}, fmt.Errorf("%s: expected one CUE instance, got %d", path, len(instances))
	}
	if err := instances[0].Err; err != nil {
		return cue.Value{}, err
	}

	v := cuecontext.New().BuildInstance(instances[0])
	return v, v.Err()
}