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 }