Skip to content

Commit

Permalink
Merge e963cee into 58b27d7
Browse files Browse the repository at this point in the history
  • Loading branch information
wenovus committed Nov 8, 2019
2 parents 58b27d7 + e963cee commit ce64a25
Show file tree
Hide file tree
Showing 17 changed files with 1,380 additions and 239 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
language: go
go:
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[![Build Status](https://travis-ci.org/openconfig/ygot.svg?branch=master)](https://travis-ci.org/openconfig/ygot)
[![Coverage Status](https://coveralls.io/repos/github/openconfig/ygot/badge.svg?branch=master)](https://coveralls.io/github/openconfig/ygot?branch=master)
Supports [latest 3 Go releases](https://golang.org/project/#release)

![#ygot](docs/img/ygot.png)

Expand All @@ -16,7 +17,7 @@ Whilst ygot is designed to work with any YANG module, for OpenConfig modules, it

## Getting Started with ygot

`ygot` consists of a number of parts, `generator` which is a binary using the `ygen` library to generate Go code from a set of YANG modules. `ygot` which provides helper methods for the `ygen`-produced structs - for example, rendering to JSON, or gNMI notifications - and `ytypes` which provides validation of the contents of `ygen` structs against the YANG schema.
`ygot` consists of a number of parts, `generator` which is a binary using the `ygen` library to generate Go code from a set of YANG modules. `ygot` which provides helper methods for the `ygen`-produced structs - for example, rendering to JSON, or gNMI notifications - and `ytypes` which provides validation of the contents of `ygen` structs against the YANG schema.

The basic workflow for working with `ygot` is as follows:

Expand All @@ -35,7 +36,7 @@ The generator binary takes a set of YANG modules as input and outputs generated
generator -output_file=<outputpath> -package_name=<pkg> [yangfiles]
```

Will output generated Go code for `yangfiles` (a space separated list of YANG files) to a file at `<outputpath>` with the Go package named `<pkg>`.
Will output generated Go code for `yangfiles` (a space separated list of YANG files) to a file at `<outputpath>` with the Go package named `<pkg>`.

Most YANG modules include other modules. If these included modules are not within the current working directory, the `path` argument is used. The argument to `path` is a comma-separated list of directories which will be recursively searched for included files.

Expand Down
29 changes: 4 additions & 25 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ func writeGoCodeMultipleFiles(dir string, goCode *ygen.GeneratedGoCode) error {
if len(f.contents) == 0 {
continue
}
fh := openFile(filepath.Join(dir, fn))
defer syncFile(fh)
fh := genutil.OpenFile(filepath.Join(dir, fn))
defer genutil.SyncFile(fh)
fmt.Fprintln(fh, goCode.CommonHeader)
if f.oneoffHeader {
fmt.Fprintln(fh, goCode.OneOffHeader)
Expand Down Expand Up @@ -277,8 +277,8 @@ func main() {
default:
// Assign the newly created filehandle to the outfh, and ensure
// that it is synced and closed before exit of main.
outfh = openFile(*outputFile)
defer syncFile(outfh)
outfh = genutil.OpenFile(*outputFile)
defer genutil.SyncFile(outfh)
}

writeGoCodeSingleFile(outfh, generatedGoCode)
Expand All @@ -288,24 +288,3 @@ func main() {
// Write the Go code to a series of output files.
writeGoCodeMultipleFiles(*outputDir, generatedGoCode)
}

// openFile opens a file with the supplied name, logging and exiting if it cannot
// be opened.
func openFile(fn string) *os.File {
fileOut, err := os.Create(fn)
if err != nil {
log.Exitf("Error: could not open output file: %v\n", err)
}
return fileOut
}

// syncFile synchronises the supplied os.File and closes it.
func syncFile(fh *os.File) {
if err := fh.Sync(); err != nil {
log.Exitf("Error: could not sync file output: %v\n", err)
}

if err := fh.Close(); err != nil {
log.Exitf("Error: could not close output file: %v\n", err)
}
}
2 changes: 1 addition & 1 deletion genutil/common.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017 Google Inc.
// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
42 changes: 42 additions & 0 deletions genutil/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package genutil

import (
"os"

log "github.com/golang/glog"
)

// OpenFile opens a file with the supplied name, logging and exiting if it cannot
// be opened.
func OpenFile(fn string) *os.File {
fileOut, err := os.Create(fn)
if err != nil {
log.Exitf("Error: could not open output file: %v\n", err)
}
return fileOut
}

// SyncFile synchronises the supplied os.File and closes it.
func SyncFile(fh *os.File) {
if err := fh.Sync(); err != nil {
log.Exitf("Error: could not sync file output: %v\n", err)
}

if err := fh.Close(); err != nil {
log.Exitf("Error: could not close output file: %v\n", err)
}
}
2 changes: 1 addition & 1 deletion genutil/names.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017 Google Inc.
// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
63 changes: 40 additions & 23 deletions util/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,40 +265,57 @@ func StripModulePrefix(name string) string {
}
}

// PathStringToElements splits the string s, which represents a gNMI string
// path into its constituent elements. It does not parse keys, which are left
// unchanged within the path - but removes escape characters from element
// names. The path returned omits any leading or trailing empty elements when
// splitting on the / character.
func PathStringToElements(path string) []string {
parts := SplitPath(path)
// Remove leading empty element
if len(parts) > 0 && parts[0] == "" {
parts = parts[1:]
}
// Remove trailing empty element
if len(parts) > 0 && path[len(path)-1] == '/' {
parts = parts[:len(parts)-1]
}
return parts
}

// SplitPath splits path across unescaped /.
// Any / inside square brackets are ignored.
func SplitPath(path string) []string {
var prev rune
var out []string
var w bytes.Buffer
skip := false
var parts []string
var buf bytes.Buffer

for _, r := range path {
switch {
case r == '[' && prev != '\\':
skip = true
var inKey, inEscape bool

case r == ']' && prev != '\\':
skip = false
var ch rune
for _, ch = range path {
switch {
case ch == '[' && !inEscape:
inKey = true
case ch == ']' && !inEscape:
inKey = false
case ch == '\\' && !inEscape && !inKey:
inEscape = true
continue
case ch == '/' && !inEscape && !inKey:
parts = append(parts, buf.String())
buf.Reset()
continue
}

if !skip && r == '/' && prev != '\\' {
out = append(out, w.String())
w.Reset()
} else {
w.WriteRune(r)
}
prev = r
buf.WriteRune(ch)
inEscape = false
}

if w.Len() != 0 {
out = append(out, w.String())
}
if prev == '/' {
out = append(out, "")
if buf.Len() != 0 || (len(path) != 1 && ch == '/') {
parts = append(parts, buf.String())
}

return out
return parts
}

// SlicePathToString concatenates a slice of strings into a / separated path. i.e.,
Expand Down
79 changes: 59 additions & 20 deletions util/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -662,41 +662,80 @@ func TestStripModulePrefixesStr(t *testing.T) {

func TestSplitPath(t *testing.T) {
tests := []struct {
desc string
in string
want []string
desc string
in string
want []string
wantIgnoreLeadingTrailing []string
}{
{
desc: "simple",
in: "a/b/c",
want: []string{"a", "b", "c"},
desc: "simple",
in: "a/b/c",
want: []string{"a", "b", "c"},
wantIgnoreLeadingTrailing: []string{"a", "b", "c"},
},
{
desc: "empty",
in: "",
want: nil,
wantIgnoreLeadingTrailing: nil,
},
{
desc: "one slash",
in: "/",
want: []string{""},
wantIgnoreLeadingTrailing: nil,
},
{
desc: "leading slash",
in: "/a",
want: []string{"", "a"},
wantIgnoreLeadingTrailing: []string{"a"},
},
{
desc: "trailing slash",
in: "aa/",
want: []string{"aa", ""},
wantIgnoreLeadingTrailing: []string{"aa"},
},
{
desc: "blank",
in: "a//b",
want: []string{"a", "", "b"},
desc: "blank",
in: "a//b",
want: []string{"a", "", "b"},
wantIgnoreLeadingTrailing: []string{"a", "", "b"},
},
{
desc: "lead trail slash",
in: "/a/b/c/",
want: []string{"", "a", "b", "c", ""},
desc: "lead trail slash",
in: "/a/b/c/",
want: []string{"", "a", "b", "c", ""},
wantIgnoreLeadingTrailing: []string{"a", "b", "c"},
},
{
desc: "escape slash",
in: `a/\/b/c`,
want: []string{"a", `\/b`, "c"},
desc: "double lead trail slash",
in: "//a/b/c//",
want: []string{"", "", "a", "b", "c", "", ""},
wantIgnoreLeadingTrailing: []string{"", "a", "b", "c", ""},
},
{
desc: "internal key slashes",
in: `a/b[key1 = ../x/y key2 = "z"]/c`,
want: []string{"a", `b[key1 = ../x/y key2 = "z"]`, "c"},
desc: "escape slash",
in: `a/\/b/c`,
want: []string{"a", "/b", "c"},
wantIgnoreLeadingTrailing: []string{"a", "/b", "c"},
},
{
desc: "internal key slashes",
in: `a/b[key1 = ../x/y key2 = "z"]/c`,
want: []string{"a", `b[key1 = ../x/y key2 = "z"]`, "c"},
wantIgnoreLeadingTrailing: []string{"a", `b[key1 = ../x/y key2 = "z"]`, "c"},
},
}

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got, want := SplitPath(tt.in), tt.want; !reflect.DeepEqual(got, want) {
t.Errorf("got: %v, want: %v", got, want)
if diff := cmp.Diff(tt.want, SplitPath(tt.in), cmpopts.EquateEmpty()); diff != "" {
t.Errorf("SplitPath (-want, +got):\n%s", diff)
}
if diff := cmp.Diff(tt.wantIgnoreLeadingTrailing, PathStringToElements(tt.in), cmpopts.EquateEmpty()); diff != "" {
t.Errorf("PathStringToElements (-want, +got):\n%s", diff)
}
})
}
Expand Down
3 changes: 3 additions & 0 deletions ygen/gogen.go
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,9 @@ func writeGoHeader(yangFiles, includePaths []string, cfg GeneratorConfig, rootNa
// outputting structs; this is done to allow checks against nil.
func IsScalarField(field *yang.Entry, t *MappedType) bool {
switch {
// A non-leaf has a generated type which are always stored by pointers.
case field.Kind != yang.LeafEntry:
return false
// A union shouldn't be a pointer since its field type is an interface;
case len(t.UnionTypes) >= 2:
return false
Expand Down
45 changes: 2 additions & 43 deletions ygot/pathstrings.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func StringToPath(path string, pathTypes ...PathType) (*gnmipb.Path, error) {
// contents is left unchanged. This implements the legacy string slice path that are
// used in gNMI pre-0.4.0. The specification for these paths is at https://goo.gl/uD6g6z.
func StringToStringSlicePath(path string) (*gnmipb.Path, error) {
parts := pathStringToElements(path)
parts := util.PathStringToElements(path)
gpath := new(gnmipb.Path)
for _, p := range parts {
// Run through extractKV to ensure that the path is valid.
Expand All @@ -212,7 +212,7 @@ func StringToStringSlicePath(path string) (*gnmipb.Path, error) {
// StringToStructuredPath takes a string representing a path, and converts it to
// a gnmi.Path, using the PathElem element message that is defined in gNMI 0.4.0.
func StringToStructuredPath(path string) (*gnmipb.Path, error) {
parts := pathStringToElements(path)
parts := util.PathStringToElements(path)

gpath := &gnmipb.Path{}
for _, p := range parts {
Expand All @@ -228,47 +228,6 @@ func StringToStructuredPath(path string) (*gnmipb.Path, error) {
return gpath, nil
}

// pathStringToElements splits the string s, which represents a gNMI string
// path into its constituent elements. It does not parse keys, which are left
// unchanged within the path - but removes escape characters from element
// names. The path returned omits any leading empty elements when splitting
// on the / character.
func pathStringToElements(s string) []string {
var parts []string
var buf bytes.Buffer

var inKey, inEscape bool

for _, ch := range s {
switch {
case ch == '[' && !inEscape:
inKey = true
case ch == ']' && !inEscape:
inKey = false
case ch == '\\' && !inEscape && !inKey:
inEscape = true
continue
case ch == '/' && !inEscape && !inKey:
parts = append(parts, buf.String())
buf.Reset()
continue
}

buf.WriteRune(ch)
inEscape = false
}

if buf.Len() != 0 {
parts = append(parts, buf.String())
}

if len(parts) > 0 && parts[0] == "" {
parts = parts[1:]
}

return parts
}

// extractKV extracts key value predicates from the input string in. It returns
// the name of the element, a map keyed by key name with values of the predicates
// specified. It removes escape characters from keys and values where they are
Expand Down

0 comments on commit ce64a25

Please sign in to comment.