github.com/openconfig/goyang@v1.4.5/yang.go (about)

     1  // Copyright 2015 Google Inc.
     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  // Program yang parses YANG files, displays errors, and possibly writes
    16  // something related to the input on output.
    17  //
    18  // Usage: yang [--path DIR] [--format FORMAT] [FORMAT OPTIONS] [MODULE] [FILE ...]
    19  //
    20  // If MODULE is specified (an argument that does not end in .yang), it is taken
    21  // as the name of the module to display.  Any FILEs specified are read, and the
    22  // tree for MODULE is displayed.  If MODULE was not defined in FILEs (or no
    23  // files were specified), then the file MODULES.yang is read as well.  An error
    24  // is displayed if no definition for MODULE was found.
    25  //
    26  // If MODULE is missing, then all base modules read from the FILEs are
    27  // displayed.  If there are no arguments then standard input is parsed.
    28  //
    29  // If DIR is specified, it is considered a comma separated list of paths
    30  // to append to the search directory.  If DIR appears as DIR/... then
    31  // DIR and all direct and indirect subdirectories are checked.
    32  //
    33  // FORMAT, which defaults to "tree", specifies the format of output to produce.
    34  // Use "goyang --help" for a list of available formats.
    35  //
    36  // FORMAT OPTIONS are flags that apply to a specific format.  They must follow
    37  // --format.
    38  //
    39  // THIS PROGRAM IS STILL JUST A DEVELOPMENT TOOL.
    40  package main
    41  
    42  import (
    43  	"fmt"
    44  	"io"
    45  	"io/ioutil"
    46  	"os"
    47  	"runtime/trace"
    48  	"sort"
    49  	"strings"
    50  
    51  	"github.com/openconfig/goyang/pkg/indent"
    52  	"github.com/openconfig/goyang/pkg/yang"
    53  	"github.com/pborman/getopt"
    54  )
    55  
    56  // Each format must register a formatter with register.  The function f will
    57  // be called once with the set of yang Entry trees generated.
    58  type formatter struct {
    59  	name  string
    60  	f     func(io.Writer, []*yang.Entry)
    61  	help  string
    62  	flags *getopt.Set
    63  }
    64  
    65  var formatters = map[string]*formatter{}
    66  
    67  func register(f *formatter) {
    68  	formatters[f.name] = f
    69  }
    70  
    71  // exitIfError writes errs to standard error and exits with an exit status of 1.
    72  // If errs is empty then exitIfError does nothing and simply returns.
    73  func exitIfError(errs []error) {
    74  	if len(errs) > 0 {
    75  		for _, err := range errs {
    76  			fmt.Fprintln(os.Stderr, err)
    77  		}
    78  		stop(1)
    79  	}
    80  }
    81  
    82  var stop = os.Exit
    83  
    84  func main() {
    85  	var format string
    86  	formats := make([]string, 0, len(formatters))
    87  	for k := range formatters {
    88  		formats = append(formats, k)
    89  	}
    90  	sort.Strings(formats)
    91  
    92  	var traceP string
    93  	var help bool
    94  	var paths []string
    95  	var ignoreSubmoduleCircularDependencies bool
    96  	getopt.ListVarLong(&paths, "path", 'p', "comma separated list of directories to add to search path", "DIR[,DIR...]")
    97  	getopt.StringVarLong(&format, "format", 'f', "format to display: "+strings.Join(formats, ", "), "FORMAT")
    98  	getopt.StringVarLong(&traceP, "trace", 't', "write trace into to TRACEFILE", "TRACEFILE")
    99  	getopt.BoolVarLong(&help, "help", 'h', "display help")
   100  	getopt.BoolVarLong(&ignoreSubmoduleCircularDependencies, "ignore-circdep", 'g', "ignore circular dependencies between submodules")
   101  	getopt.SetParameters("[FORMAT OPTIONS] [SOURCE] [...]")
   102  
   103  	if err := getopt.Getopt(func(o getopt.Option) bool {
   104  		if o.Name() == "--format" {
   105  			f, ok := formatters[format]
   106  			if !ok {
   107  				fmt.Fprintf(os.Stderr, "%s: invalid format.  Choices are %s\n", format, strings.Join(formats, ", "))
   108  				stop(1)
   109  			}
   110  			if f.flags != nil {
   111  				f.flags.VisitAll(func(o getopt.Option) {
   112  					getopt.AddOption(o)
   113  				})
   114  			}
   115  		}
   116  		return true
   117  	}); err != nil {
   118  		fmt.Fprintln(os.Stderr, err)
   119  		getopt.PrintUsage(os.Stderr)
   120  		os.Exit(1)
   121  	}
   122  
   123  	if traceP != "" {
   124  		fp, err := os.Create(traceP)
   125  		if err != nil {
   126  			fmt.Fprintln(os.Stderr, err)
   127  			os.Exit(1)
   128  		}
   129  		trace.Start(fp)
   130  		stop = func(c int) { trace.Stop(); os.Exit(c) }
   131  		defer func() { trace.Stop() }()
   132  	}
   133  
   134  	if help {
   135  		getopt.CommandLine.PrintUsage(os.Stderr)
   136  		fmt.Fprintf(os.Stderr, `
   137  SOURCE may be a module name or a .yang file.
   138  
   139  Formats:
   140  `)
   141  		for _, fn := range formats {
   142  			f := formatters[fn]
   143  			fmt.Fprintf(os.Stderr, "    %s - %s\n", f.name, f.help)
   144  			if f.flags != nil {
   145  				f.flags.PrintOptions(indent.NewWriter(os.Stderr, "   "))
   146  			}
   147  			fmt.Fprintln(os.Stderr)
   148  		}
   149  		stop(0)
   150  	}
   151  
   152  	ms := yang.NewModules()
   153  	ms.ParseOptions.IgnoreSubmoduleCircularDependencies = ignoreSubmoduleCircularDependencies
   154  
   155  	for _, path := range paths {
   156  		expanded, err := yang.PathsWithModules(path)
   157  		if err != nil {
   158  			fmt.Fprintln(os.Stderr, err)
   159  			continue
   160  		}
   161  		ms.AddPath(expanded...)
   162  	}
   163  
   164  	if format == "" {
   165  		format = "tree"
   166  	}
   167  	if _, ok := formatters[format]; !ok {
   168  		fmt.Fprintf(os.Stderr, "%s: invalid format.  Choices are %s\n", format, strings.Join(formats, ", "))
   169  		stop(1)
   170  
   171  	}
   172  
   173  	files := getopt.Args()
   174  
   175  	if len(files) == 0 {
   176  		data, err := ioutil.ReadAll(os.Stdin)
   177  		if err == nil {
   178  			err = ms.Parse(string(data), "<STDIN>")
   179  		}
   180  		if err != nil {
   181  			fmt.Fprintln(os.Stderr, err)
   182  			stop(1)
   183  		}
   184  	}
   185  
   186  	for _, name := range files {
   187  		if err := ms.Read(name); err != nil {
   188  			fmt.Fprintln(os.Stderr, err)
   189  			continue
   190  		}
   191  	}
   192  
   193  	// Process the read files, exiting if any errors were found.
   194  	exitIfError(ms.Process())
   195  
   196  	// Keep track of the top level modules we read in.
   197  	// Those are the only modules we want to print below.
   198  	mods := map[string]*yang.Module{}
   199  	var names []string
   200  
   201  	for _, m := range ms.Modules {
   202  		if mods[m.Name] == nil {
   203  			mods[m.Name] = m
   204  			names = append(names, m.Name)
   205  		}
   206  	}
   207  	sort.Strings(names)
   208  	entries := make([]*yang.Entry, len(names))
   209  	for x, n := range names {
   210  		entries[x] = yang.ToEntry(mods[n])
   211  	}
   212  
   213  	formatters[format].f(os.Stdout, entries)
   214  }