From 5b8a407a3f168b3db4e2f3db702084f4ffe4a273 Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Fri, 9 Aug 2024 17:16:35 +0100 Subject: [PATCH] checkpoint: began to create schema and code generator --- cmd/enbas-cli-generators/main.go | 91 +++++++++++++++++++++++++++ cmd/enbas-cli-generators/schema.go | 65 +++++++++++++++++++ cmd/enbas-cli-generators/templates.go | 36 +++++++++++ internal/internal.go | 2 + schema/enbas_cli_schema.json | 31 +++++++++ 5 files changed, 225 insertions(+) create mode 100644 cmd/enbas-cli-generators/main.go create mode 100644 cmd/enbas-cli-generators/schema.go create mode 100644 cmd/enbas-cli-generators/templates.go create mode 100644 schema/enbas_cli_schema.json diff --git a/cmd/enbas-cli-generators/main.go b/cmd/enbas-cli-generators/main.go new file mode 100644 index 0000000..a766e93 --- /dev/null +++ b/cmd/enbas-cli-generators/main.go @@ -0,0 +1,91 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "os/exec" + "text/template" + "unicode" +) + +func main() { + var ( + enbasCLISchemaFilepath string + executorsFilePath string + ) + + flag.StringVar(&enbasCLISchemaFilepath, "path-to-enbas-cli-schema", "", "The path to the Enbas CLI schema file") + flag.StringVar(&executorsFilePath, "path-to-enbas-executors", "", "The path to the executors Go file") + flag.Parse() + + schema, err := readSchemaFile(enbasCLISchemaFilepath) + if err != nil { + fmt.Printf("ERROR: Unable to read the schema file: %v.\n", err) + } + + if err := generateExecutors(schema, executorsFilePath); err != nil { + fmt.Printf("ERROR: Unable to generate the executors: %v.\n", err) + } + + if err := runGoImports(executorsFilePath); err != nil { + fmt.Printf("ERROR: Unable to run goimports: %v.\n", err) + } +} + +func readSchemaFile(path string) (enbasCLISchema, error) { + file, err := os.Open(path) + if err != nil { + return enbasCLISchema{}, fmt.Errorf("unable to open the schema file: %w", err) + } + defer file.Close() + + var schema enbasCLISchema + + if err := json.NewDecoder(file).Decode(&schema); err != nil { + return enbasCLISchema{}, fmt.Errorf("unable to decode the JSON data: %w", err) + } + + return schema, nil +} + +func generateExecutors(schema enbasCLISchema, output string) error { + funcMap := template.FuncMap{ + "capitalise": capitalise, + "getFlagType": schema.Flags.getType, + "getFlagDescription": schema.Flags.getDescription, + } + + tmpl := template.Must(template.New("executor-template").Funcs(funcMap).Parse(executorsFileTemplate)) + + file, err := os.Create(output) + if err != nil { + return fmt.Errorf("unable to create the output file: %w", err) + } + defer file.Close() + + if err := tmpl.Execute(file, schema.Commands); err != nil { + return fmt.Errorf("unable to generate the code from the template: %w", err) + } + + return nil +} + +func runGoImports(path string) error { + imports := exec.Command("goimports", "-w", path) + + if err := imports.Run(); err != nil { + return fmt.Errorf("received an error after running goimports: %w", err) + } + + return nil +} + +func capitalise(str string) string { + runes := []rune(str) + + runes[0] = unicode.ToUpper(runes[0]) + + return string(runes) +} diff --git a/cmd/enbas-cli-generators/schema.go b/cmd/enbas-cli-generators/schema.go new file mode 100644 index 0000000..6d18c5b --- /dev/null +++ b/cmd/enbas-cli-generators/schema.go @@ -0,0 +1,65 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" +) + +type enbasCLISchema struct { + Flags enbasCLISchemaFlagMap `json:"flags"` + Commands map[string]enbasCLISchemaCommand `json:"commands"` +} + +func newEnbasCLISchemaFromFile(path string) (enbasCLISchema, error) { + file, err := os.Open(path) + if err != nil { + return enbasCLISchema{}, fmt.Errorf("unable to open the schema file: %w", err) + } + defer file.Close() + + var schema enbasCLISchema + + if err := json.NewDecoder(file).Decode(&schema); err != nil { + return enbasCLISchema{}, fmt.Errorf("unable to decode the JSON data: %w", err) + } + + return schema, nil +} + +type enbasCLISchemaFlag struct { + Type string `json:"type"` + Description string `json:"description"` +} + +type enbasCLISchemaFlagMap map[string]enbasCLISchemaFlag + +func (e enbasCLISchemaFlagMap) getType(name string) string { + flag, ok := e[name] + if !ok { + return "UNKNOWN TYPE" + } + + return flag.Type +} + +func (e enbasCLISchemaFlagMap) getDescription(name string) string { + flag, ok := e[name] + if !ok { + return "UNKNOWN DESCRIPTION" + } + + return flag.Description +} + +type enbasCLISchemaCommand struct { + AddPrinter bool `json:"addPrinter"` + AddConfig bool `json:"addConfig"` + Flags []enbasCLISchemaFlagReference `json:"flags"` + Summary string `json:"summary"` +} + +type enbasCLISchemaFlagReference struct { + Flag string `json:"flag"` + Default string `json:"default"` +} diff --git a/cmd/enbas-cli-generators/templates.go b/cmd/enbas-cli-generators/templates.go new file mode 100644 index 0000000..b8596e5 --- /dev/null +++ b/cmd/enbas-cli-generators/templates.go @@ -0,0 +1,36 @@ +package main + +var executorsFileTemplate = `package executor +{{ range $name, $command := . }} +{{- $struct_name := capitalise $name | printf "%sExecutor" -}} +{{- $new_executor_function_name := capitalise $name | printf "New%sExecutor" -}} +{{ print "" }} +// {{ $struct_name }} is the executor for the {{ $name }} command. +type {{ $struct_name }} struct { + *flag.FlagSet + {{- range $flag := $command.Flags -}} + {{ print "" }} + {{ $flag.Flag }} {{ getFlagType $flag.Flag }} + {{- end -}} + {{ print "" }} +} + +func {{ $new_executor_function_name }}() *{{ $struct_name }} { + exe := {{ $struct_name }}{ + FlagSet: flag.NewFlagSet({{ printf "%q" $name }}, flag.ExitOnError), + } + {{ print "" }} + exe.Usage = commandUsageFunc({{ printf "%q" $name }}, {{ printf "%q" $command.Summary }}, showExe.FlagSet) + {{ print "" }} + {{- range $flag := $command.Flags -}} + {{- if eq (getFlagType $flag.Flag) "string" -}} + exe.StringVar(&exe.{{ $flag.Flag }}, {{ printf "%q" $flag.Flag }}, {{ printf "%q" $flag.Default }}, {{ getFlagDescription $flag.Flag | printf "%q" }}) + {{- else if eq (getFlagType $flag.Flag) "bool" -}} + exe.BoolVar(&exe.{{ $flag.Flag }}, {{ printf "%q" $flag.Flag }}, {{ $flag.Default }}, {{ getFlagDescription $flag.Flag | printf "%q" }}) + {{- end -}} + {{- end -}} + {{ print "" }} + return &exe +} +{{ end }} +` diff --git a/internal/internal.go b/internal/internal.go index b47f3df..00bbfb5 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -1,5 +1,7 @@ package internal +//go:generate go run ../cmd/enbas-cli-generators --path-to-enbas-cli-schema ../schema/enbas_cli_schema.json --path-to-enbas-executors ../executors-wip.go + const ( ApplicationName = "enbas" ApplicationWebsite = "https://codeflow.dananglin.me.uk/apollo/enbas" diff --git a/schema/enbas_cli_schema.json b/schema/enbas_cli_schema.json new file mode 100644 index 0000000..30c1d90 --- /dev/null +++ b/schema/enbas_cli_schema.json @@ -0,0 +1,31 @@ +{ + "flags": { + "full": { + "type": "bool", + "description": "Set to true to print the build information in full" + }, + "instance": { + "type": "string", + "description": "The instance that you want to log into" + } + }, + + "commands": { + "login": { + "addPrinter": true, + "addConfig": true, + "flags": [ + { "flag": "instance", "default": "" } + ], + "summary": "Login to an account on GoToSocial" + }, + "version": { + "addPrinter": true, + "addConfig": false, + "summary": "Prints the application's version and build information", + "flags": [ + { "flag": "full", "default": "false" } + ] + } + } +}