this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

pkg/tool/file: support file-related tasks

Updates #39

Change-Id: I0fc36d7414b76e9a741bf3d2616653f96fc462bd
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1925
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>

authored by

Marcel van Lohuizen and committed by
Marcel van Lohuizen
83b33c0b 928bfa36

+263 -41
+1
cmd/cue/cmd/custom.go
··· 33 33 itask "cuelang.org/go/internal/task" 34 34 _ "cuelang.org/go/pkg/tool/cli" // Register tasks 35 35 _ "cuelang.org/go/pkg/tool/exec" 36 + _ "cuelang.org/go/pkg/tool/file" 36 37 _ "cuelang.org/go/pkg/tool/http" 37 38 "github.com/spf13/cobra" 38 39 "golang.org/x/sync/errgroup"
+5 -6
cue/builtins.go
··· 1907 1907 native: []*builtin{{}}, 1908 1908 cue: `{ 1909 1909 Read: { 1910 - _kind: "tool/file.Read" 1910 + kind: "tool/file.Read" 1911 1911 filename: !="" 1912 1912 contents: *bytes | string 1913 1913 } 1914 1914 Create: { 1915 - _kind: "tool/file.Create" 1915 + kind: "tool/file.Create" 1916 1916 filename: !="" 1917 1917 contents: bytes | string 1918 1918 permissions: int | *420 1919 - overwrite: *false | true 1920 1919 } 1921 1920 Append: { 1922 - _kind: "tool/file.Append" 1921 + kind: "tool/file.Append" 1923 1922 filename: !="" 1924 1923 contents: bytes | string 1925 1924 permissions: int | *420 1926 1925 } 1927 1926 Glob: { 1928 - _kind: "tool/file.Glob" 1929 - glob: !="" 1927 + kind: "tool/file.Glob" 1928 + glob: !="" 1930 1929 files: [...string] 1931 1930 } 1932 1931 }`,
+21 -35
pkg/tool/file/file.cue
··· 12 12 // See the License for the specific language governing permissions and 13 13 // limitations under the License. 14 14 15 - package os 16 - 17 - import "tool" 15 + package file 18 16 19 17 // Read reads the contents of a file. 20 - Read: tool.Task & { 21 - _kind: "tool/file.Read" 18 + Read: { 19 + kind: "tool/file.Read" 22 20 23 21 // filename names the file to read. 24 - filename: string 22 + filename: !="" 25 23 26 24 // contents is the read contents. If the contents are constraint to bytes 27 25 // (the default), the file is read as is. If it is constraint to a string, 28 26 // the contents are checked to be valid UTF-8. 29 27 contents: *bytes | string 30 - 31 - // if body is given, the file contents are parsed as JSON and unified with 32 - // the specified CUE value. 33 - body?: _ 34 28 } 35 29 36 - // Create writes contents to the given file. 37 - Create: tool.Task & { 38 - _kind: "tool/file.Create" 30 + // Append writes contents to the given file. 31 + Append: { 32 + kind: "tool/file.Append" 39 33 40 - // filename names the file to write. 41 - filename: string 34 + // filename names the file to append. 35 + filename: !="" 42 36 43 37 // permissions defines the permissions to use if the file does not yet exist. 44 - permissions: int 45 - 46 - // overwrite defines whether an existing file may be overwritten. 47 - overwrite: *false | true 38 + permissions: int | *0o644 48 39 49 40 // contents specifies the bytes to be written. 50 41 contents: bytes | string 51 42 } 52 43 53 - // Append writes contents to the given file. 54 - Append: tool.Task & { 55 - // filename names the file to append. 56 - filename: string 44 + // Create writes contents to the given file. 45 + Create: { 46 + kind: "tool/file.Create" 47 + 48 + // filename names the file to write. 49 + filename: !="" 57 50 58 51 // permissions defines the permissions to use if the file does not yet exist. 59 - permissions: int 52 + permissions: int | *0o644 60 53 61 54 // contents specifies the bytes to be written. 62 55 contents: bytes | string 63 56 } 64 57 65 - Dir: tool.Task & { 66 - _kind: "tool/file.Dir" 67 - 68 - path: string 69 - dir: [...string] 70 - } 71 - 72 - Glob: tool.Task & { 73 - _kind: "tool/file.Glob" 58 + Glob: { 59 + kind: "tool/file.Glob" 74 60 75 - glob: string 76 - files <Filename>: string 61 + glob: !="" 62 + files: [...string] 77 63 }
+98
pkg/tool/file/file.go
··· 1 + // Copyright 2019 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // Package file provides file operations for cue tasks. 16 + package file 17 + 18 + import ( 19 + "io/ioutil" 20 + "os" 21 + "path/filepath" 22 + 23 + "cuelang.org/go/cue" 24 + "cuelang.org/go/internal/task" 25 + ) 26 + 27 + func init() { 28 + task.Register("tool/file.Read", newReadCmd) 29 + task.Register("tool/file.Append", newAppendCmd) 30 + task.Register("tool/file.Create", newCreateCmd) 31 + task.Register("tool/file.Glob", newGlobCmd) 32 + } 33 + 34 + func newReadCmd(v cue.Value) (task.Runner, error) { return &cmdRead{}, nil } 35 + func newAppendCmd(v cue.Value) (task.Runner, error) { return &cmdAppend{}, nil } 36 + func newCreateCmd(v cue.Value) (task.Runner, error) { return &cmdCreate{}, nil } 37 + func newGlobCmd(v cue.Value) (task.Runner, error) { return &cmdGlob{}, nil } 38 + 39 + type cmdRead struct{} 40 + type cmdAppend struct{} 41 + type cmdCreate struct{} 42 + type cmdGlob struct{} 43 + 44 + func lookupStr(v cue.Value, str string) string { 45 + str, _ = v.Lookup(str).String() 46 + return str 47 + } 48 + 49 + func (c *cmdRead) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) { 50 + b, err := ioutil.ReadFile(lookupStr(v, "filename")) 51 + if err != nil { 52 + return nil, err 53 + } 54 + update := map[string]interface{}{"contents": b} 55 + 56 + switch v.Lookup("contents").IncompleteKind() &^ cue.BottomKind { 57 + case cue.BytesKind: 58 + case cue.StringKind: 59 + update["contents"] = string(b) 60 + } 61 + return update, nil 62 + } 63 + 64 + func (c *cmdAppend) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) { 65 + filename := lookupStr(v, "filename") 66 + mode, err := v.Lookup("permissions").Int64() 67 + if err != nil { 68 + return nil, err 69 + } 70 + 71 + f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, os.FileMode(mode)) 72 + if err != nil { 73 + return nil, err 74 + } 75 + defer f.Close() 76 + 77 + b, _ := v.Lookup("contents").Bytes() 78 + if _, err := f.Write(b); err != nil { 79 + return nil, err 80 + } 81 + return nil, nil 82 + } 83 + 84 + func (c *cmdCreate) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) { 85 + filename := lookupStr(v, "filename") 86 + mode, err := v.Lookup("permissions").Int64() 87 + if err != nil { 88 + return nil, err 89 + } 90 + 91 + b, _ := v.Lookup("contents").Bytes() 92 + return nil, ioutil.WriteFile(filename, b, os.FileMode(mode)) 93 + } 94 + 95 + func (c *cmdGlob) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) { 96 + m, err := filepath.Glob(lookupStr(v, "glob")) 97 + return m, err 98 + }
+137
pkg/tool/file/file_test.go
··· 1 + // Copyright 2019 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package file 16 + 17 + import ( 18 + "fmt" 19 + "io/ioutil" 20 + "os" 21 + "reflect" 22 + "testing" 23 + 24 + "cuelang.org/go/cue" 25 + "cuelang.org/go/cue/parser" 26 + "cuelang.org/go/cue/token" 27 + "cuelang.org/go/internal" 28 + ) 29 + 30 + func parse(t *testing.T, kind, expr string) cue.Value { 31 + t.Helper() 32 + fset := token.NewFileSet() 33 + 34 + x, err := parser.ParseExpr(fset, "test", expr) 35 + if err != nil { 36 + t.Fatal(err) 37 + } 38 + i, err := cue.FromExpr(fset, x) 39 + if err != nil { 40 + t.Fatal(err) 41 + } 42 + return internal.UnifyBuiltin(i.Value(), kind).(cue.Value) 43 + } 44 + func TestRead(t *testing.T) { 45 + v := parse(t, "tool/file.Read", `{filename: "testdata/input.foo"}`) 46 + got, err := (*cmdRead).Run(nil, nil, v) 47 + if err != nil { 48 + t.Fatal(err) 49 + } 50 + want := map[string]interface{}{"contents": []byte("This is a test.")} 51 + if !reflect.DeepEqual(got, want) { 52 + t.Errorf("got %v; want %v", got, want) 53 + } 54 + 55 + v = parse(t, "tool/file.Read", `{ 56 + filename: "testdata/input.foo" 57 + contents: string 58 + }`) 59 + got, err = (*cmdRead).Run(nil, nil, v) 60 + if err != nil { 61 + t.Fatal(err) 62 + } 63 + want = map[string]interface{}{"contents": "This is a test."} 64 + if !reflect.DeepEqual(got, want) { 65 + t.Errorf("got %v; want %v", got, want) 66 + } 67 + } 68 + 69 + func TestAppend(t *testing.T) { 70 + f, err := ioutil.TempFile("", "filetest") 71 + if err != nil { 72 + t.Fatal(err) 73 + } 74 + name := f.Name() 75 + defer os.Remove(name) 76 + f.Close() 77 + 78 + v := parse(t, "tool/file.Append", fmt.Sprintf(`{ 79 + filename: "%s" 80 + contents: "This is a test." 81 + }`, name)) 82 + _, err = (*cmdAppend).Run(nil, nil, v) 83 + if err != nil { 84 + t.Fatal(err) 85 + } 86 + 87 + b, err := ioutil.ReadFile(name) 88 + if err != nil { 89 + t.Fatal(err) 90 + } 91 + 92 + if got, want := string(b), "This is a test."; got != want { 93 + t.Errorf("got %v; want %v", got, want) 94 + } 95 + } 96 + 97 + func TestCreate(t *testing.T) { 98 + f, err := ioutil.TempFile("", "filetest") 99 + if err != nil { 100 + t.Fatal(err) 101 + } 102 + name := f.Name() 103 + defer os.Remove(name) 104 + f.Close() 105 + 106 + v := parse(t, "tool/file.Create", fmt.Sprintf(`{ 107 + filename: "%s" 108 + contents: "This is a test." 109 + }`, name)) 110 + _, err = (*cmdCreate).Run(nil, nil, v) 111 + if err != nil { 112 + t.Fatal(err) 113 + } 114 + 115 + b, err := ioutil.ReadFile(name) 116 + if err != nil { 117 + t.Fatal(err) 118 + } 119 + 120 + if got, want := string(b), "This is a test."; got != want { 121 + t.Errorf("got %v; want %v", got, want) 122 + } 123 + } 124 + 125 + func TestGlob(t *testing.T) { 126 + v := parse(t, "tool/file.Glob", fmt.Sprintf(`{ 127 + glob: "testdata/input.*" 128 + }`)) 129 + got, err := (*cmdGlob).Run(nil, nil, v) 130 + if err != nil { 131 + t.Fatal(err) 132 + } 133 + 134 + if want := []string{"testdata/input.foo"}; !reflect.DeepEqual(got, want) { 135 + t.Errorf("got %v; want %v", got, want) 136 + } 137 + }
+1
pkg/tool/file/testdata/input.foo
··· 1 + This is a test.