1// Copyright 2018 The 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 15package cmd 16 17import ( 18 "errors" 19 "fmt" 20 "io/ioutil" 21 "os" 22 23 "github.com/spf13/cobra" 24 25 "cuelang.org/go/cue/format" 26 "cuelang.org/go/cue/load" 27 "cuelang.org/go/internal/diff" 28 "cuelang.org/go/tools/trim" 29) 30 31// TODO: 32// - remove the limitations mentioned in the documentation 33// - implement verification post-processing as extra safety 34 35// newTrimCmd creates a trim command 36func newTrimCmd(c *Command) *cobra.Command { 37 cmd := &cobra.Command{ 38 Use: "trim", 39 Short: "remove superfluous fields", 40 Long: `trim removes fields from structs that can be inferred from constraints 41 42A field, struct, or list is removed if it is implied by a constraint, such 43as from an optional field matching a required field, a list type value, 44a comprehension or any other implied content. It will modify the files in place. 45 46 47Limitations 48 49Removal is on a best effort basis. Some caveats: 50- Fields in implied content may refer to fields within the struct in which 51 they are included, but are only resolved on a best-effort basis. 52- Disjunctions that contain structs in implied content cannot be used to 53 remove fields. 54- There is currently no verification step: manual verification is required. 55 56Examples: 57 58 $ cat <<EOF > foo.cue 59 light: [string]: { 60 room: string 61 brightnessOff: *0.0 | >=0 & <=100.0 62 brightnessOn: *100.0 | >=0 & <=100.0 63 } 64 65 light: ceiling50: { 66 room: "MasterBedroom" 67 brightnessOff: 0.0 // this line 68 brightnessOn: 100.0 // and this line will be removed 69 } 70 EOF 71 72 $ cue trim foo.cue 73 $ cat foo.cue 74 light: [string]: { 75 room: string 76 brightnessOff: *0.0 | >=0 & <=100.0 77 brightnessOn: *100.0 | >=0 & <=100.0 78 } 79 80 light: ceiling50: { 81 room: "MasterBedroom" 82 } 83 84It is guaranteed that the resulting files give the same output as before the 85removal. 86`, 87 RunE: mkRunE(c, runTrim), 88 } 89 90 addOutFlags(cmd.Flags(), false) 91 92 return cmd 93} 94 95func runTrim(cmd *Command, args []string) error { 96 binst := loadFromArgs(cmd, args, nil) 97 if binst == nil { 98 return nil 99 } 100 instances := buildInstances(cmd, binst) 101 102 dst := flagOutFile.String(cmd) 103 if dst != "" && dst != "-" && !flagForce.Bool(cmd) { 104 switch _, err := os.Stat(dst); { 105 case os.IsNotExist(err): 106 default: 107 return fmt.Errorf("error writing %q: file already exists", dst) 108 } 109 } 110 111 overlay := map[string]load.Source{} 112 113 for i, inst := range binst { 114 root := instances[i] 115 err := trim.Files(inst.Files, root, &trim.Config{ 116 Trace: flagTrace.Bool(cmd), 117 }) 118 if err != nil { 119 return err 120 } 121 122 for _, f := range inst.Files { 123 overlay[f.Filename] = load.FromFile(f) 124 } 125 126 } 127 128 cfg := *defaultConfig.loadCfg 129 cfg.Overlay = overlay 130 tinsts := buildInstances(cmd, load.Instances(args, &cfg)) 131 if len(tinsts) != len(binst) { 132 return errors.New("unexpected number of new instances") 133 } 134 if !flagIgnore.Bool(cmd) { 135 for i, p := range instances { 136 k, script := diff.Final.Diff(p.Value(), tinsts[i].Value()) 137 if k != diff.Identity { 138 diff.Print(os.Stdout, script) 139 fmt.Println("Aborting trim, output differs after trimming. This is a bug! Use -i to force trim.") 140 fmt.Println("You can file a bug here: https://github.com/cuelang/cue/issues/new?assignees=&labels=NeedsInvestigation&template=bug_report.md&title=") 141 os.Exit(1) 142 } 143 } 144 } 145 146 if flagDryrun.Bool(cmd) { 147 return nil 148 } 149 150 for _, inst := range binst { 151 for _, f := range inst.Files { 152 filename := f.Filename 153 154 opts := []format.Option{} 155 if flagSimplify.Bool(cmd) { 156 opts = append(opts, format.Simplify()) 157 } 158 159 b, err := format.Node(f, opts...) 160 if err != nil { 161 return fmt.Errorf("error formatting file: %v", err) 162 } 163 164 if dst == "-" { 165 _, err := cmd.OutOrStdout().Write(b) 166 if err != nil { 167 return err 168 } 169 continue 170 } else if dst != "" { 171 filename = dst 172 } 173 174 err = ioutil.WriteFile(filename, b, 0644) 175 if err != nil { 176 return err 177 } 178 } 179 } 180 return nil 181} 182