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  }