Files

107 lines
3.0 KiB
Go

package cli
import (
"bytes"
"errors"
"fmt"
"go/token"
"os"
"path/filepath"
"strings"
"unicode"
"github.com/spf13/cobra"
)
func newGenerateCmd(opts *options) *cobra.Command {
var (
out string
pkg string
check bool
excludeFieldDoc bool
)
cmd := &cobra.Command{
Use: "generate",
Short: "Generate Go types from the config",
Long: `Generate fetches the schemas and extensions named in the config and writes
the Go types to --out. Use --out - to write to stdout.
With --check, nothing is written; the command fails if the output file is
missing or differs from what would be generated. Use this in CI to catch
generated code that is stale or was edited by hand.`,
Example: ` ocdsgen generate -c extensions.json -o ocds_gen.go -p tenders
ocdsgen generate --check --strict
//go:generate go tool ocdsgen generate -c extensions.json -o ocds_gen.go -p tenders`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if pkg == "" {
var err error
if pkg, err = packageFromPath(out); err != nil {
return err
}
}
cfg, g, err := opts.setup(cmd)
if err != nil {
return err
}
src, err := g.Run(cfg, pkg, filepath.Base(opts.config), excludeFieldDoc)
if err != nil {
return err
}
switch {
case out == "-":
if _, err := cmd.OutOrStdout().Write(src); err != nil {
return err
}
case check:
existing, err := os.ReadFile(out)
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("%s does not exist; run ocdsgen generate", out)
} else if err != nil {
return err
}
if !bytes.Equal(existing, src) {
return fmt.Errorf("%s is out of date; run ocdsgen generate", out)
}
fmt.Fprintf(cmd.ErrOrStderr(), "%s is up to date\n", out)
default:
if err := os.WriteFile(out, src, 0o644); err != nil {
return err
}
}
return opts.finish(g)
},
}
f := cmd.Flags()
f.StringVarP(&out, "out", "o", "ocds_gen.go", `output Go file, or "-" for stdout`)
f.StringVarP(&pkg, "package", "p", "", "Go package name (default: name of the output file's directory)")
f.BoolVar(&check, "check", false, "verify the output file is up to date instead of writing it")
f.BoolVar(&excludeFieldDoc, "exclude-field-doc", false, `exclude documentation for definition fields`)
return cmd
}
// packageFromPath derives a package name from the directory the output file
// will be written to, e.g. internal/tenders/ocds_gen.go -> tenders.
func packageFromPath(out string) (string, error) {
if out == "-" {
return "", errors.New("--package is required when writing to stdout")
}
abs, err := filepath.Abs(out)
if err != nil {
return "", err
}
dir := filepath.Base(filepath.Dir(abs))
name := strings.Map(func(r rune) rune {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
return unicode.ToLower(r)
}
return -1
}, dir)
if !token.IsIdentifier(name) || token.IsKeyword(name) {
return "", fmt.Errorf("can't derive a package name from directory %q; pass --package", dir)
}
return name, nil
}