cuelang.org/go@v0.13.0/internal/cmd/cue-ast/main.go (about) 1 // Copyright 2023 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 15 // cue-ast-print parses a CUE file and prints its syntax tree, for example: 16 // 17 // cue-ast-print file.cue 18 package main 19 20 import ( 21 "flag" 22 "fmt" 23 "io" 24 "log" 25 "os" 26 "slices" 27 28 "cuelang.org/go/cue/ast" 29 "cuelang.org/go/cue/ast/astutil" 30 "cuelang.org/go/cue/errors" 31 "cuelang.org/go/cue/format" 32 "cuelang.org/go/cue/load" 33 "cuelang.org/go/cue/parser" 34 "cuelang.org/go/internal/astinternal" 35 ) 36 37 func main() { 38 flag.Usage = func() { 39 fmt.Fprint(flag.CommandLine.Output(), ` 40 usage of cue-ast: 41 42 cue-ast print [flags] [inputs] 43 44 Write multi-line Go-like representations of CUE syntax trees. 45 46 -omitempty omit empty and invalid values 47 48 cue-ast join [flags] [inputs] 49 50 Join the input package instance as a single file. 51 Joining multiple package instances is not supported yet. 52 53 See 'cue help inputs' as well. 54 `[1:]) 55 } 56 57 if len(os.Args) < 2 { 58 flag.Usage() 59 os.Exit(1) 60 } 61 name, args := os.Args[1], os.Args[2:] 62 switch name { 63 case "print": 64 var cfg astinternal.DebugConfig 65 flag.BoolVar(&cfg.OmitEmpty, "omitempty", false, "") 66 flag.BoolVar(&cfg.IncludeNodeRefs, "refs", false, "") 67 fileFlag := false 68 flag.BoolVar(&fileFlag, "files", false, "") 69 // Note that DebugConfig also has a Filter func, but that doesn't lend itself well 70 // to a command line flag. Perhaps we could provide some commonly used filters, 71 // such as "positions only" or "skip positions". 72 flag.CommandLine.Parse(args) 73 74 if fileFlag { 75 for _, f := range flag.Args() { 76 var data []byte 77 var err error 78 if f == "-" { 79 data, err = io.ReadAll(os.Stdin) 80 } else { 81 data, err = os.ReadFile(f) 82 } 83 if err != nil { 84 log.Fatal(err) 85 } 86 astf, err := parser.ParseFile(f, data, parser.ParseComments) 87 if err != nil { 88 log.Fatal(errors.Details(err, nil)) 89 } 90 out := astinternal.AppendDebug(nil, astf, cfg) 91 os.Stdout.Write(out) 92 } 93 return 94 } 95 // TODO: should we produce output in txtar form for the sake of 96 // more clearly separating the AST for each file? 97 // [ast.File.Filename] already has the full filename, 98 // but as one of the first fields it's not a great separator. 99 insts := load.Instances(flag.Args(), &load.Config{}) 100 for _, inst := range insts { 101 if err := inst.Err; err != nil { 102 log.Fatal(errors.Details(err, nil)) 103 } 104 for _, file := range inst.Files { 105 out := astinternal.AppendDebug(nil, file, cfg) 106 os.Stdout.Write(out) 107 } 108 } 109 case "join": 110 // TODO: add a flag drop comments, which is useful when reducing bug reproducers. 111 flag.CommandLine.Parse(args) 112 113 var jointImports []*ast.ImportSpec 114 var jointFields []ast.Decl 115 insts := load.Instances(flag.Args(), &load.Config{}) 116 if len(insts) != 1 { 117 log.Fatal("joining multiple instances is not possible yet") 118 } 119 inst := insts[0] 120 if err := inst.Err; err != nil { 121 log.Fatal(errors.Details(err, nil)) 122 } 123 for _, file := range inst.Files { 124 jointImports = slices.Concat(jointImports, file.Imports) 125 126 fields := file.Decls[len(file.Preamble()):] 127 jointFields = slices.Concat(jointFields, fields) 128 } 129 // TODO: we should sort and deduplicate imports. 130 joint := &ast.File{Decls: slices.Concat([]ast.Decl{ 131 &ast.ImportDecl{Specs: jointImports}, 132 }, jointFields)} 133 134 // Sanitize the resulting file so that, for example, 135 // multiple packages imported as the same name avoid collisions. 136 if err := astutil.Sanitize(joint); err != nil { 137 log.Fatal(err) 138 } 139 140 out, err := format.Node(joint) 141 if err != nil { 142 log.Fatal(err) 143 } 144 os.Stdout.Write(out) 145 default: 146 flag.Usage() 147 } 148 }