Skip to content

Feature/for each and subcommands #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ indent_style = tab
tab_width = 4

[*.tmpl]
tab_width = 0
indent_size =

[*.py]
indent_size = 4
37 changes: 33 additions & 4 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
version: "3"

env:
VERSION:
sh: "git describe --tags --always --dirty"
YUTC_LOG_LEVEL: info


tasks:
diagram:
desc: "Generate the diagram"
cmd: |-
poetry run python ./docs/diagram.py docs/diagram.png

download-taskfile:
desc: "Download the latest Taskfile binary"
cmd: |-
Go to https://taskfile.dev/installation to download/upgrade taskfile (a task runner).
If you are running this, you already have taskfile, but this is here for
anyone who looks at the Taskfile.yaml first to see wat it is
anyone who looks at the Taskfile.yaml first to see what it is

build:
build-windows:
desc: "Build the CLI"
sources:
sources: &sources
- "cmd/yutc/*.go"
- "pkg/**/*.go"
- "internal/**/*.go"
- "go.mod"
cmd: "go build -o ./dist ./cmd/yutc"
- "Taskfile.yaml"
cmd: |-
go build -o ./dist/yutc-windows-amd64.exe -ldflags="-X 'main.Version=$VERSION'" ./cmd/yutc

build-linux:
desc: "Build the CLI"
sources: *sources
cmd: |-
go build -o ./dist/yutc-linux-amd64 -ldflags="-X 'main.Version=$VERSION'" ./cmd/yutc

install:
desc: "Install the CLI"
Expand All @@ -33,6 +52,16 @@ tasks:
--data ./docs/_data/README.data.yaml \
./docs/_templates/README.md.tmpl

exec-latest-windows:
desc: "Run the latest build"
deps:
- "build-windows"
cmd: |-
pwsh -NoProfile -Command '
[Environment]::SetEnvironmentVariable("YUTC_LOG_LEVEL", "{{.YUTC_LOG_LEVEL}}") ;
./dist/yutc-windows-amd64.exe {{.CLI_ARGS}} ;
'

run-help:
desc: "Run X"
vars:
Expand Down
50 changes: 50 additions & 0 deletions cmd/yutc/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"bytes"
"fmt"
"github.com/adam-huganir/yutc/internal"
"github.com/pkg/errors"
"io"
"path/filepath"
)

func createWriter(path, outputPath string, overwrite bool) (io.Writer, error) {
outPath := filepath.Join(outputPath, path)
outDir := filepath.Dir(outPath)
exists, err := internal.Exists(outDir)
// we may have a file in the place where our output folder should be, we respect overwrite if there is
outDirIsDir, err := internal.IsDir(outDir)
if !exists && err == nil || !outDirIsDir {
if !overwrite {
return nil, fmt.Errorf("file found where output requires a folder, %s, you must use overwrite to delete existing file(s)", outDir)
}
err = internal.Fs.Remove(outDir)
err = internal.Fs.MkdirAll(outDir, 0755)
if err != nil {
return nil, err
}
}
outWriter, err := internal.Fs.Create(outPath)
if err != nil {
return nil, err
}
return outWriter, nil
}

func evalTemplate(t *internal.YutcTemplate, commonTemplates []*internal.FileData, data any, outWriter io.Writer) (*bytes.Buffer, error) {
var err error
for _, ct := range commonTemplates {
err = t.AddTemplate(ct.ReadWriter.String())
if err != nil {
return nil, errors.Wrapf(err, "error adding common template %s to %s", ct.Path, t.Path())
}
}
result, err := t.Execute(data)
if err != nil {
return nil, errors.Wrapf(err, "error executing template %s (%s)", t.Path(), t.ID())

}
_, err = outWriter.Write(result.Bytes())
return result, nil
}
81 changes: 81 additions & 0 deletions cmd/yutc/forEach.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package main

import (
"github.com/adam-huganir/yutc/internal"
"github.com/spf13/cobra"
"io"
"os"
"path"
"slices"
)

func runForEachCommand(cmd *cobra.Command, args []string) (err error) {
YutcLog.Trace().Msg("yutc.runForEachCommand() called")

runSettings.TemplatePaths = args

err = parseArgs(runSettings)
if err != nil {
return err
}

var datas []any
if len(runSettings.DataFiles) == 1 {
data, _, err := internal.GatherData(runSettings.DataFiles, false, runSettings.BasicAuth, runSettings.BearerToken)
if err != nil {
return err
}
datas = data.([]any)
} else {
for _, dataFile := range runSettings.DataFiles {
data, _, err := internal.GatherData([]string{dataFile}, false, runSettings.BasicAuth, runSettings.BearerToken)
if err != nil {
return err
}
datas = append(datas, data)
}
}

var commonTemplates []*internal.FileData
commonTemplates, err = internal.LoadFiles(runSettings.CommonTemplateFiles, runSettings.BasicAuth, runSettings.BearerToken)
if err != nil {
return err
}

var templates []*internal.YutcTemplate
templates, err = internal.LoadTemplates(runSettings.TemplatePaths, runSettings.BasicAuth, runSettings.BearerToken)
if err != nil {
return err
}

slices.SortFunc(templates, internal.CmpTemplatePathLength) // sort templates by their file path length (shortest first)
templateRootDir := internal.NormalizeFilepath(path.Dir(templates[0].Path())) // because of above we know this is the root dir

// see if we need to dive into these and pull files out of them

var outWriter io.Writer

// Load up our output templates with any common definitions from the shared templates
first := true
for _, t := range templates {
t.SetRelativePath(templateRootDir)
for _, data := range datas {
if runSettings.Output == "-" {
outWriter = os.Stdout
if !first {
_, _ = outWriter.Write([]byte("---\n"))
} else {
first = false
}
} else {
outWriter, err = createWriter(t.RelativePath(), runSettings.Output, runSettings.Overwrite)
if err != nil {
return err
}
}
_, err = evalTemplate(t, commonTemplates, data, outWriter)
}
}

return nil
}
76 changes: 47 additions & 29 deletions cmd/yutc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,58 +20,57 @@ func init() {
}

func initRoot(rootCommand *cobra.Command, settings *internal.YutcSettings) {
//const matchMessage = "Regex patterns to match/exclude from. A `!` prefix will exclude the pattern. Implies a recursive search."

YutcLog.Trace().Msg("yutc.initRoot() called")
rootCommand.Flags().SortFlags = false
rootCommand.Flags().StringArrayVarP(

rootCommand.PersistentFlags().BoolVarP(
&settings.Verbose,
"verbose",
"v",
false,
"Verbose output",
)
rootCommand.Flags().BoolVar(&settings.Version, "version", false, "Print the version and exit")
}

func initCommon(cmd *cobra.Command, settings *internal.YutcSettings) {
YutcLog.Trace().Msg("yutc.initCommon() called")
cmd.Flags().SortFlags = false
cmd.Flags().StringArrayVarP(
&settings.DataFiles,
"data",
"d",
nil,
"Data file to parse and merge. Can be a file or a URL. "+
"Can be specified multiple times and the inputs will be merged.",
)
//rootCommand.Flags().StringArrayVar(&settings.DataMatch, "data-match", nil, matchMessage)
rootCommand.Flags().StringArrayVarP(
cmd.Flags().StringArrayVarP(
&settings.CommonTemplateFiles,
"common-templates",
"c",
nil,
"Templates to be shared across all arguments in template list. Can be a file or a URL. "+
"Can be specified multiple times.",
)
//rootCommand.Flags().StringArrayVar(&settings.CommonTemplateMatch, "common-match", nil, matchMessage)

rootCommand.Flags().StringVarP(&settings.Output, "output", "o", "-", "Output file/directory, defaults to stdout")
cmd.Flags().StringVarP(&settings.Output, "output", "o", "-", "Output file/directory, defaults to stdout")

rootCommand.Flags().BoolVar(&settings.IncludeFilenames, "include-filenames", false, "Exec any filenames with go templates")
rootCommand.Flags().BoolVarP(&settings.Overwrite, "overwrite", "w", false, "Overwrite existing files")
cmd.Flags().BoolVar(&settings.IncludeFilenames, "include-filenames", false, "Exec any filenames with go templates")
cmd.Flags().BoolVarP(&settings.Overwrite, "overwrite", "w", false, "Overwrite existing files")

rootCommand.Flags().StringVar(&settings.BearerToken, "bearer-auth", "", "Bearer token for any URL authentication")
rootCommand.Flags().StringVar(&settings.BasicAuth, "basic-auth", "", "Basic auth for any URL authentication")
// probably a unnecessary feature but still rad. maybe add b64 encoding at some point
cmd.Flags().StringVar(&settings.BearerToken, "bearer-auth", "", "Bearer token for any URL authentication")
cmd.Flags().StringVar(&settings.BasicAuth, "basic-auth", "", "Basic auth for any URL authentication")

//rootCommand.Flags().StringArrayVarP(
// &settings.TemplateMatch,
// "match",
// "m",
// nil,
// "For template arguments input, "+matchMessage,
//)
rootCommand.PersistentFlags().BoolVarP(
&settings.Verbose,
"verbose",
"v",
false,
"Verbose output",
)
rootCommand.Flags().BoolVar(&settings.Version, "version", false, "Print the version and exit")
}

func initForEachCommand(cmd *cobra.Command, settings *internal.YutcSettings) {
cmd.Flags().BoolVar(&settings.Append, "append", false, "Append data to output")
}

func main() {
YutcLog.Trace().Msg("yutc.main() called, executing rootCommand")
rootCommand := newRootCommand()
runSettings = internal.NewCLISettings()
initRoot(rootCommand, runSettings)
rootCommand := initCli()
err := rootCommand.Execute()
if err != nil {
YutcLog.Error().Msg(err.Error())
Expand All @@ -81,3 +80,22 @@ func main() {
}
os.Exit(*internal.ExitCode)
}

func initCli(settings ...*internal.YutcSettings) *cobra.Command {
rootCommand := newRootCommand()
templateCommand := newTemplateCommand()
forEachCommand := newForEachCommand()
rootCommand.AddCommand(templateCommand)
rootCommand.AddCommand(forEachCommand)
if len(settings) == 1 {
runSettings = settings[0]
} else if len(settings) == 0 {
runSettings = internal.NewCLISettings()
} else {
YutcLog.Fatal().Msg("Too many settings passed to initCli")
}
initRoot(rootCommand, runSettings)
initCommon(templateCommand, runSettings)
initCommon(forEachCommand, runSettings)
return rootCommand
}
Loading
Loading