From cc7c02dc0650fc64652f5184f8aa32f1768cb2d3 Mon Sep 17 00:00:00 2001 From: Tony Worm Date: Sat, 16 Oct 2021 14:07:49 -0400 Subject: [PATCH] operator ,@ ; outname ; overwrite ; update pick to use these Signed-off-by: Tony Worm --- cmd/cuetils/cmd/pick.go | 6 +- structural/count.go | 6 ++ structural/input.go | 77 ++++++++++++- structural/output.go | 83 ++++++++++++++ structural/pick.go | 31 ++---- test/cli/cli_test.go | 101 ++++++++++++++++-- test/cli/testdata/bugs/replace_incomplete.txt | 5 +- test/cli/testdata/pick_001.txt | 3 - test/cli/testdata/pick_002.txt | 32 ++++++ test/cli/testdata/pick_003.txt | 27 +++++ test/cli/testdata/pick_004.txt | 30 ++++++ test/cli/testdata/pick_005.txt | 32 ++++++ test/diff/r.cue | 1 + 13 files changed, 391 insertions(+), 43 deletions(-) create mode 100644 test/cli/testdata/pick_002.txt create mode 100644 test/cli/testdata/pick_003.txt create mode 100644 test/cli/testdata/pick_004.txt create mode 100644 test/cli/testdata/pick_005.txt create mode 100644 test/diff/r.cue diff --git a/cmd/cuetils/cmd/pick.go b/cmd/cuetils/cmd/pick.go index 176544e..d411700 100644 --- a/cmd/cuetils/cmd/pick.go +++ b/cmd/cuetils/cmd/pick.go @@ -17,14 +17,12 @@ func PickRun(pick string, globs []string) (err error) { // you can safely comment this print out // fmt.Println("not implemented") - picks, err := structural.Pick(pick, globs, flags.RootPflags) + results, err := structural.Pick(pick, globs, flags.RootPflags) if err != nil { return err } - for _, p := range picks { - fmt.Printf("%s\n----------------------\n%s\n\n", p.Filename, p.Content) - } + err = structural.ProcessOutputs(results, flags.RootPflags) return err } diff --git a/structural/count.go b/structural/count.go index 4d10c3e..e86fda3 100644 --- a/structural/count.go +++ b/structural/count.go @@ -9,6 +9,12 @@ import ( "github.com/hofstadter-io/cuetils/cmd/cuetils/flags" ) +type StatsResult struct { + Filename string + Count int + Depth int +} + type CountResult struct { Filename string Count int diff --git a/structural/input.go b/structural/input.go index 0fc2ae8..9c3e8ea 100644 --- a/structural/input.go +++ b/structural/input.go @@ -6,16 +6,85 @@ import ( "os" "path/filepath" "sort" + "strings" "cuelang.org/go/cue" "cuelang.org/go/cue/load" ) type Input struct { - Filename string - Filetype string // yaml, json, cue... toml? - Expression string // cue expression to select within document - Content []byte + Original string + Entrypoints []string + Filename string + Filetype string // yaml, json, cue... toml? + Expression string // cue expression to select within document + Content []byte + Value cue.Value +} + +func ParseOperator(op string) (Input, error) { + i := Input{ Original: op, Filename: op } + + // does the op look like a file or a CUE value? + + // look for expression + if strings.Contains(i.Filename, "@") { + parts := strings.Split(op, "@") + if len(parts) != 2 { + return i, fmt.Errorf("more than on '@' found for input %q", i.Original) + } + i.Filename, i.Expression = parts[0], parts[1] + } + // look for entrypoints + if strings.Contains(i.Filename, ",") { + i.Entrypoints = strings.Split(i.Filename, ",") + i.Filename = "" + } + + return i, nil +} + +func LoadOperator(i Input, doLoad bool, ctx *cue.Context) (Input, error) { + if doLoad || i.Entrypoints != nil { + // handle entrypoints + if i.Entrypoints == nil { + i.Entrypoints = []string{i.Filename} + } + v, err := LoadInputs(i.Entrypoints, ctx) + if err != nil { + return i, err + } + i.Value = v + } else { + // handle stdin? + + // handle single file + d, err := os.ReadFile(i.Filename) + if err != nil { + return i, err + } + // handle input types + ext := filepath.Ext(i.Filename) + switch ext { + case ".yml", ".yaml": + s := fmt.Sprintf(yamlMod, string(d)) + d = []byte(s) + } + + i.Content = d + + i.Value = ctx.CompileBytes(i.Content, cue.Filename(i.Filename)) + } + + if i.Value.Err() != nil { + return i, i.Value.Err() + } + + if i.Expression != "" { + i.Value = i.Value.LookupPath(cue.ParsePath(i.Expression)) + } + + return i, nil } // Loads the entrypoints using the context provided diff --git a/structural/output.go b/structural/output.go index 7121ca3..0a63dc5 100644 --- a/structural/output.go +++ b/structural/output.go @@ -4,10 +4,15 @@ import ( "bytes" "encoding/json" "fmt" + "os" + "path/filepath" + "strings" "cuelang.org/go/cue" "cuelang.org/go/cue/format" "cuelang.org/go/encoding/yaml" + + "github.com/hofstadter-io/cuetils/cmd/cuetils/flags" ) type GlobResult struct { @@ -16,6 +21,84 @@ type GlobResult struct { Value cue.Value } +func ProcessOutputs(results []GlobResult, rflags flags.RootPflagpole) (err error) { + //if rflags.Accum != "" { + //results, err = AccumOutputs(results, rflags.Accum) + //if err != nil { + //return err + //} + //} + w := os.Stdout + for _, r := range results { + // Format + r.Content, err = FormatOutput(r.Value, rflags.Out) + if err != nil { + return err + } + + // make outname + outname := "" + if rflags.Outname != "" { + outname = rflags.Outname + // look for interpolation syntax + if strings.Contains(outname, "<") { + dir, file := filepath.Split(r.Filename) + ext := filepath.Ext(file) + name := strings.TrimSuffix(file, ext) + + outname = strings.Replace(outname, "", dir, -1) + outname = strings.Replace(outname, "", name, -1) + outname = strings.Replace(outname, "", ext, -1) + outname = strings.Replace(outname, "", file, -1) + outname = strings.Replace(outname, "", r.Filename, -1) + } + if strings.Contains(outname, "\\(") { + o := r.Value.Context().CompileString(outname, cue.Scope(r.Value)) + outname, err = o.String() + if err != nil { + return err + } + } + } + + // are we writing a file? + writeFile := false + if rflags.Overwrite || outname != "" { + writeFile = true + } + // now possibly fill filename + if outname == "" { + outname = r.Filename + } + + // if yes, we need to override w + if writeFile { + _, err = os.Stat(outname) + // if no overwrite and exists, return err + if !rflags.Overwrite && err == nil { + return err + } + w, err = os.Create(outname) + if err != nil { + return err + } + } + + // now do actual writing + if rflags.Headers { + fmt.Fprintf(w, "%s\n----------------------\n%s\n\n", outname, r.Content) + } else { + fmt.Fprintf(w, "%s\n", r.Content) + } + } + + return nil +} + +func AccumOutputs(results []GlobResult, accum string) ([]GlobResult, error) { + return results, nil +} + func FormatOutput(val cue.Value, format string) (string, error) { switch format { case "cue", "CUE": diff --git a/structural/pick.go b/structural/pick.go index c7236d3..0e15de7 100644 --- a/structural/pick.go +++ b/structural/pick.go @@ -20,23 +20,19 @@ val: #P: _ pick: val.pick ` -func Pick(orig string, globs []string, rflags flags.RootPflagpole) ([]PickResult, error) { - // no globs, then stdin - if len(globs) == 0 { - globs = []string{"-"} - } - +func Pick(orig string, globs []string, rflags flags.RootPflagpole) ([]GlobResult, error) { cuest, err := NewCuest([]string{"pick"}, nil) if err != nil { return nil, err } - ov, err := LoadInputs([]string{orig}, cuest.ctx) + operator, err := ParseOperator(orig) if err != nil { return nil, err } - if ov.Err() != nil { - return nil, ov.Err() + operator, err = LoadOperator(operator, rflags.LoadOperands, cuest.ctx) + if err != nil { + return nil, err } inputs, err := ReadGlobs(globs) @@ -53,9 +49,9 @@ func Pick(orig string, globs []string, rflags flags.RootPflagpole) ([]PickResult val := cuest.ctx.CompileString(content, cue.Scope(cuest.orig)) // fill val with the orig value, so we only need to once before loop - val = val.FillPath(cue.ParsePath("val.#P"), ov) + val = val.FillPath(cue.ParsePath("val.#P"), operator.Value) - picks := make([]PickResult, 0) + results := make([]GlobResult, 0) for _, input := range inputs { iv := cuest.ctx.CompileBytes(input.Content, cue.Filename(input.Filename)) @@ -65,19 +61,14 @@ func Pick(orig string, globs []string, rflags flags.RootPflagpole) ([]PickResult result := val.FillPath(cue.ParsePath("val.#X"), iv) - dv := result.LookupPath(cue.ParsePath("pick")) - - out, err := FormatOutput(dv, rflags.Out) - if err != nil { - return nil, err - } + v := result.LookupPath(cue.ParsePath("pick")) - picks = append(picks, PickResult{ + results = append(results, GlobResult{ Filename: input.Filename, - Content: out, + Value: v, }) } - return picks, nil + return results, nil } diff --git a/test/cli/cli_test.go b/test/cli/cli_test.go index 5c5a108..9c9f5f4 100644 --- a/test/cli/cli_test.go +++ b/test/cli/cli_test.go @@ -7,20 +7,101 @@ import ( "github.com/hofstadter-io/hof/script/runtime" ) -func TestCliTests(t *testing.T) { +func TestCliCount(t *testing.T) { yagu.Mkdir(".workdir/tests") runtime.Run(t, runtime.Params{ Dir: "testdata", - Glob: "*.txt", + Glob: "count_*.txt", + WorkdirRoot: ".workdir/tests", + }) +} + +func TestCliDepth(t *testing.T) { + yagu.Mkdir(".workdir/tests") + runtime.Run(t, runtime.Params{ + Dir: "testdata", + Glob: "depth_*.txt", + WorkdirRoot: ".workdir/tests", + }) +} + +func TestCliDiff(t *testing.T) { + yagu.Mkdir(".workdir/tests") + runtime.Run(t, runtime.Params{ + Dir: "testdata", + Glob: "diff_*.txt", + WorkdirRoot: ".workdir/tests", + }) +} + +func TestCliMask(t *testing.T) { + yagu.Mkdir(".workdir/tests") + runtime.Run(t, runtime.Params{ + Dir: "testdata", + Glob: "mask_*.txt", + WorkdirRoot: ".workdir/tests", + }) +} + +func TestCliPatch(t *testing.T) { + yagu.Mkdir(".workdir/tests") + runtime.Run(t, runtime.Params{ + Dir: "testdata", + Glob: "patch_*.txt", + WorkdirRoot: ".workdir/tests", + }) +} + +func TestCliPick(t *testing.T) { + yagu.Mkdir(".workdir/tests") + runtime.Run(t, runtime.Params{ + Dir: "testdata", + Glob: "pick_*.txt", + WorkdirRoot: ".workdir/tests", + }) +} + +func TestCliReplace(t *testing.T) { + yagu.Mkdir(".workdir/tests") + runtime.Run(t, runtime.Params{ + Dir: "testdata", + Glob: "replace_*.txt", + WorkdirRoot: ".workdir/tests", + }) +} + +func TestCliTransform(t *testing.T) { + yagu.Mkdir(".workdir/tests") + runtime.Run(t, runtime.Params{ + Dir: "testdata", + Glob: "transform_*.txt", + WorkdirRoot: ".workdir/tests", + }) +} + +func TestCliUpsert(t *testing.T) { + yagu.Mkdir(".workdir/tests") + runtime.Run(t, runtime.Params{ + Dir: "testdata", + Glob: "upsert_*.txt", WorkdirRoot: ".workdir/tests", }) } -//func TestCliBugs(t *testing.T) { - //yagu.Mkdir(".workdir/bugs") - //runtime.Run(t, runtime.Params{ - //Dir: "testdata/bugs", - //Glob: "*.txt", - //WorkdirRoot: ".workdir/bugs", - //}) -//} +func TestCliValidate(t *testing.T) { + yagu.Mkdir(".workdir/tests") + runtime.Run(t, runtime.Params{ + Dir: "testdata", + Glob: "validate_*.txt", + WorkdirRoot: ".workdir/tests", + }) +} + +func TestCliBugs(t *testing.T) { + yagu.Mkdir(".workdir/bugs") + runtime.Run(t, runtime.Params{ + Dir: "testdata/bugs", + Glob: "*.txt", + WorkdirRoot: ".workdir/bugs", + }) +} diff --git a/test/cli/testdata/bugs/replace_incomplete.txt b/test/cli/testdata/bugs/replace_incomplete.txt index 6193453..be9b8cb 100644 --- a/test/cli/testdata/bugs/replace_incomplete.txt +++ b/test/cli/testdata/bugs/replace_incomplete.txt @@ -1,5 +1,6 @@ -!exec cuetils replace replace.cue a.json -!cmp stdout golden.stdout +skip +exec cuetils replace replace.cue a.json +cmp stdout golden.stdout -- replace.cue -- c: string diff --git a/test/cli/testdata/pick_001.txt b/test/cli/testdata/pick_001.txt index 4f66b7e..35e8652 100644 --- a/test/cli/testdata/pick_001.txt +++ b/test/cli/testdata/pick_001.txt @@ -20,8 +20,6 @@ cmp stdout golden.stdout } -- golden.stdout -- -a.json ----------------------- { a: { b: "B" @@ -29,4 +27,3 @@ a.json c: 2 d: "D" } - diff --git a/test/cli/testdata/pick_002.txt b/test/cli/testdata/pick_002.txt new file mode 100644 index 0000000..fb5c33e --- /dev/null +++ b/test/cli/testdata/pick_002.txt @@ -0,0 +1,32 @@ +exec cuetils pick --headers pick.cue a.json +cmp stdout golden.stdout + +-- pick.cue -- +{ + a: { + b: string + } + c: int + d: "D" +} +-- a.json -- +{ + "a": { + "b": "B" + }, + "b": 1, + "c": 2, + "d": "D" +} + +-- golden.stdout -- +a.json +---------------------- +{ + a: { + b: "B" + } + c: 2 + d: "D" +} + diff --git a/test/cli/testdata/pick_003.txt b/test/cli/testdata/pick_003.txt new file mode 100644 index 0000000..a8b6b49 --- /dev/null +++ b/test/cli/testdata/pick_003.txt @@ -0,0 +1,27 @@ +exec cuetils pick pick.cue@a a.json +cmp stdout golden.stdout + +-- pick.cue -- +{ + a: { + b: string + c: int + } + c: int + d: "D" +} +-- a.json -- +{ + "a": { + "b": "B" + }, + "b": "b", + "c": 2, + "d": "D" +} + +-- golden.stdout -- +{ + b: "b" + c: 2 +} diff --git a/test/cli/testdata/pick_004.txt b/test/cli/testdata/pick_004.txt new file mode 100644 index 0000000..26717d1 --- /dev/null +++ b/test/cli/testdata/pick_004.txt @@ -0,0 +1,30 @@ +exec cuetils pick --outname "\(a.b)" pick.cue a.json +cat B.json +cmp stdout golden.stdout + +-- pick.cue -- +{ + a: { + b: string + } + c: int + d: "D" +} +-- a.json -- +{ + "a": { + "b": "B" + }, + "b": 1, + "c": 2, + "d": "D" +} + +-- golden.stdout -- +{ + a: { + b: "B" + } + c: 2 + d: "D" +} diff --git a/test/cli/testdata/pick_005.txt b/test/cli/testdata/pick_005.txt new file mode 100644 index 0000000..60a12f5 --- /dev/null +++ b/test/cli/testdata/pick_005.txt @@ -0,0 +1,32 @@ +exec cuetils pick a.cue,b.cue a.json +cmp stdout golden.stdout + +-- a.cue -- +{ + a: { + b: string + } +} +-- b.cue -- +{ + c: int + d: "D" +} +-- a.json -- +{ + "a": { + "b": "B" + }, + "b": 1, + "c": 2, + "d": "D" +} + +-- golden.stdout -- +{ + a: { + b: "B" + } + c: 2 + d: "D" +} diff --git a/test/diff/r.cue b/test/diff/r.cue new file mode 100644 index 0000000..6aa0386 --- /dev/null +++ b/test/diff/r.cue @@ -0,0 +1 @@ +a: b: "b"