dawn_node: Add idlgen tool
A go WebIDL parser and template generator tool. Will be used to generate the NodeJS bindings for Dawn. We may wish to reimplement this in Python some day. Not today. Bug: dawn:1123 Change-Id: I31c868efcd8ba00084a6c25a1fc0e3ad774dfa53 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/64746 Reviewed-by: Antonio Maiorano <amaiorano@google.com> Reviewed-by: Austin Eng <enga@chromium.org> Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
parent
029d67f2c8
commit
cea792f971
|
@ -0,0 +1,620 @@
|
|||
// Copyright 2021 The Dawn Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
// idlgen is a tool used to generate code from WebIDL files and a golang
|
||||
// template file
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
|
||||
"github.com/ben-clayton/webidlparser/ast"
|
||||
"github.com/ben-clayton/webidlparser/parser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func showUsage() {
|
||||
fmt.Println(`
|
||||
idlgen is a tool used to generate code from WebIDL files and a golang
|
||||
template file
|
||||
|
||||
Usage:
|
||||
idlgen --template=<template-path> --output=<output-path> <idl-file> [<idl-file>...]`)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func run() error {
|
||||
var templatePath string
|
||||
var outputPath string
|
||||
flag.StringVar(&templatePath, "template", "", "the template file run with the parsed WebIDL files")
|
||||
flag.StringVar(&outputPath, "output", "", "the output file")
|
||||
flag.Parse()
|
||||
|
||||
idlFiles := flag.Args()
|
||||
|
||||
// Check all required arguments are provided
|
||||
if templatePath == "" || outputPath == "" || len(idlFiles) == 0 {
|
||||
showUsage()
|
||||
}
|
||||
|
||||
// Open up the output file
|
||||
out := os.Stdout
|
||||
if outputPath != "" {
|
||||
file, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open output file '%v'", outputPath)
|
||||
}
|
||||
out = file
|
||||
defer file.Close()
|
||||
}
|
||||
|
||||
// Read the template file
|
||||
tmpl, err := ioutil.ReadFile(templatePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open template file '%v'", templatePath)
|
||||
}
|
||||
|
||||
// idl is the combination of the parsed idlFiles
|
||||
idl := &ast.File{}
|
||||
|
||||
// Parse each of the WebIDL files and add the declarations to idl
|
||||
for _, path := range idlFiles {
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open file '%v'", path)
|
||||
}
|
||||
fileIDL := parser.Parse(string(content))
|
||||
if numErrs := len(fileIDL.Errors); numErrs != 0 {
|
||||
errs := make([]string, numErrs)
|
||||
for i, e := range fileIDL.Errors {
|
||||
errs[i] = e.Message
|
||||
}
|
||||
return fmt.Errorf("errors found while parsing %v:\n%v", path, strings.Join(errs, "\n"))
|
||||
}
|
||||
idl.Declarations = append(idl.Declarations, fileIDL.Declarations...)
|
||||
}
|
||||
|
||||
// Initialize the generator
|
||||
g := generator{t: template.New(templatePath)}
|
||||
g.workingDir = filepath.Dir(templatePath)
|
||||
g.funcs = map[string]interface{}{
|
||||
// Functions exposed to the template
|
||||
"AttributesOf": attributesOf,
|
||||
"ConstantsOf": constantsOf,
|
||||
"EnumEntryName": enumEntryName,
|
||||
"Eval": g.eval,
|
||||
"Include": g.include,
|
||||
"IsBasicLiteral": is(ast.BasicLiteral{}),
|
||||
"IsConstructor": isConstructor,
|
||||
"IsDefaultDictionaryLiteral": is(ast.DefaultDictionaryLiteral{}),
|
||||
"IsDictionary": is(ast.Dictionary{}),
|
||||
"IsEnum": is(ast.Enum{}),
|
||||
"IsInterface": is(ast.Interface{}),
|
||||
"IsInterfaceOrNamespace": is(ast.Interface{}, ast.Namespace{}),
|
||||
"IsMember": is(ast.Member{}),
|
||||
"IsNamespace": is(ast.Namespace{}),
|
||||
"IsNullableType": is(ast.NullableType{}),
|
||||
"IsParametrizedType": is(ast.ParametrizedType{}),
|
||||
"IsRecordType": is(ast.RecordType{}),
|
||||
"IsSequenceType": is(ast.SequenceType{}),
|
||||
"IsTypedef": is(ast.Typedef{}),
|
||||
"IsTypeName": is(ast.TypeName{}),
|
||||
"IsUndefinedType": isUndefinedType,
|
||||
"IsUnionType": is(ast.UnionType{}),
|
||||
"Lookup": g.lookup,
|
||||
"MethodsOf": methodsOf,
|
||||
"Title": strings.Title,
|
||||
}
|
||||
t, err := g.t.
|
||||
Option("missingkey=invalid").
|
||||
Funcs(g.funcs).
|
||||
Parse(string(tmpl))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse template file '%v': %w", templatePath, err)
|
||||
}
|
||||
|
||||
// simplify the definitions in the WebIDL before passing this to the template
|
||||
idl, declarations := simplify(idl)
|
||||
g.declarations = declarations
|
||||
|
||||
// Write the file header
|
||||
fmt.Fprintf(out, header, strings.Join(os.Args[1:], "\n// "))
|
||||
|
||||
// Execute the template
|
||||
return t.Execute(out, idl)
|
||||
}
|
||||
|
||||
// declarations is a map of WebIDL declaration name to its AST node.
|
||||
type declarations map[string]ast.Decl
|
||||
|
||||
// nameOf returns the name of the AST node n.
|
||||
// Returns an empty string if the node is not named.
|
||||
func nameOf(n ast.Node) string {
|
||||
switch n := n.(type) {
|
||||
case *ast.Namespace:
|
||||
return n.Name
|
||||
case *ast.Interface:
|
||||
return n.Name
|
||||
case *ast.Dictionary:
|
||||
return n.Name
|
||||
case *ast.Enum:
|
||||
return n.Name
|
||||
case *ast.Typedef:
|
||||
return n.Name
|
||||
case *ast.Mixin:
|
||||
return n.Name
|
||||
case *ast.Includes:
|
||||
return ""
|
||||
default:
|
||||
panic(fmt.Errorf("unhandled AST declaration %T", n))
|
||||
}
|
||||
}
|
||||
|
||||
// simplify processes the AST 'in', returning a new AST that:
|
||||
// * Has all partial interfaces merged into a single interface.
|
||||
// * Has all mixins flattened into their place of use.
|
||||
// * Has all the declarations ordered in dependency order (leaf first)
|
||||
// simplify also returns the map of declarations in the AST.
|
||||
func simplify(in *ast.File) (*ast.File, declarations) {
|
||||
s := simplifier{
|
||||
declarations: declarations{},
|
||||
registered: map[string]bool{},
|
||||
out: &ast.File{},
|
||||
}
|
||||
|
||||
// Walk the IDL declarations to merge together partial interfaces and embed
|
||||
// mixins into their uses.
|
||||
{
|
||||
interfaces := map[string]*ast.Interface{}
|
||||
mixins := map[string]*ast.Mixin{}
|
||||
for _, d := range in.Declarations {
|
||||
switch d := d.(type) {
|
||||
case *ast.Interface:
|
||||
if i, ok := interfaces[d.Name]; ok {
|
||||
// Merge partial body into one interface
|
||||
i.Members = append(i.Members, d.Members...)
|
||||
} else {
|
||||
clone := *d
|
||||
d := &clone
|
||||
interfaces[d.Name] = d
|
||||
s.declarations[d.Name] = d
|
||||
}
|
||||
case *ast.Mixin:
|
||||
mixins[d.Name] = d
|
||||
s.declarations[d.Name] = d
|
||||
case *ast.Includes:
|
||||
// Merge mixin into interface
|
||||
i, ok := interfaces[d.Name]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%v includes %v, but %v is not an interface", d.Name, d.Source, d.Name))
|
||||
}
|
||||
m, ok := mixins[d.Source]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%v includes %v, but %v is not an mixin", d.Name, d.Source, d.Source))
|
||||
}
|
||||
// Merge mixin into the interface
|
||||
for _, member := range m.Members {
|
||||
if member, ok := member.(*ast.Member); ok {
|
||||
i.Members = append(i.Members, member)
|
||||
}
|
||||
}
|
||||
default:
|
||||
if name := nameOf(d); name != "" {
|
||||
s.declarations[nameOf(d)] = d
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now traverse the declarations in to produce the dependency-ordered
|
||||
// output `s.out`.
|
||||
for _, d := range in.Declarations {
|
||||
if name := nameOf(d); name != "" {
|
||||
s.visit(s.declarations[nameOf(d)])
|
||||
}
|
||||
}
|
||||
|
||||
return s.out, s.declarations
|
||||
}
|
||||
|
||||
// simplifier holds internal state for simplify()
|
||||
type simplifier struct {
|
||||
// all AST declarations
|
||||
declarations declarations
|
||||
// set of visited declarations
|
||||
registered map[string]bool
|
||||
// the dependency-ordered output
|
||||
out *ast.File
|
||||
}
|
||||
|
||||
// visit traverses the AST declaration 'd' adding all dependent declarations to
|
||||
// s.out.
|
||||
func (s *simplifier) visit(d ast.Decl) {
|
||||
register := func(name string) bool {
|
||||
if s.registered[name] {
|
||||
return true
|
||||
}
|
||||
s.registered[name] = true
|
||||
return false
|
||||
}
|
||||
switch d := d.(type) {
|
||||
case *ast.Namespace:
|
||||
if register(d.Name) {
|
||||
return
|
||||
}
|
||||
for _, m := range d.Members {
|
||||
if m, ok := m.(*ast.Member); ok {
|
||||
s.visitType(m.Type)
|
||||
for _, p := range m.Parameters {
|
||||
s.visitType(p.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.Interface:
|
||||
if register(d.Name) {
|
||||
return
|
||||
}
|
||||
if d, ok := s.declarations[d.Inherits]; ok {
|
||||
s.visit(d)
|
||||
}
|
||||
for _, m := range d.Members {
|
||||
if m, ok := m.(*ast.Member); ok {
|
||||
s.visitType(m.Type)
|
||||
for _, p := range m.Parameters {
|
||||
s.visitType(p.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.Dictionary:
|
||||
if register(d.Name) {
|
||||
return
|
||||
}
|
||||
if d, ok := s.declarations[d.Inherits]; ok {
|
||||
s.visit(d)
|
||||
}
|
||||
for _, m := range d.Members {
|
||||
s.visitType(m.Type)
|
||||
for _, p := range m.Parameters {
|
||||
s.visitType(p.Type)
|
||||
}
|
||||
}
|
||||
case *ast.Typedef:
|
||||
if register(d.Name) {
|
||||
return
|
||||
}
|
||||
s.visitType(d.Type)
|
||||
case *ast.Mixin:
|
||||
if register(d.Name) {
|
||||
return
|
||||
}
|
||||
for _, m := range d.Members {
|
||||
if m, ok := m.(*ast.Member); ok {
|
||||
s.visitType(m.Type)
|
||||
for _, p := range m.Parameters {
|
||||
s.visitType(p.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.Enum:
|
||||
if register(d.Name) {
|
||||
return
|
||||
}
|
||||
case *ast.Includes:
|
||||
if register(d.Name) {
|
||||
return
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("unhandled AST declaration %T", d))
|
||||
}
|
||||
|
||||
s.out.Declarations = append(s.out.Declarations, d)
|
||||
}
|
||||
|
||||
// visitType traverses the AST type 't' adding all dependent declarations to
|
||||
// s.out.
|
||||
func (s *simplifier) visitType(t ast.Type) {
|
||||
switch t := t.(type) {
|
||||
case *ast.TypeName:
|
||||
if d, ok := s.declarations[t.Name]; ok {
|
||||
s.visit(d)
|
||||
}
|
||||
case *ast.UnionType:
|
||||
for _, t := range t.Types {
|
||||
s.visitType(t)
|
||||
}
|
||||
case *ast.ParametrizedType:
|
||||
for _, t := range t.Elems {
|
||||
s.visitType(t)
|
||||
}
|
||||
case *ast.NullableType:
|
||||
s.visitType(t.Type)
|
||||
case *ast.SequenceType:
|
||||
s.visitType(t.Elem)
|
||||
case *ast.RecordType:
|
||||
s.visitType(t.Elem)
|
||||
default:
|
||||
panic(fmt.Errorf("unhandled AST type %T", t))
|
||||
}
|
||||
}
|
||||
|
||||
// generator holds the template generator state
|
||||
type generator struct {
|
||||
// the root template
|
||||
t *template.Template
|
||||
// the working directory
|
||||
workingDir string
|
||||
// map of function name to function exposed to the template executor
|
||||
funcs map[string]interface{}
|
||||
// dependency-sorted declarations
|
||||
declarations declarations
|
||||
}
|
||||
|
||||
// eval executes the sub-template with the given name and arguments, returning
|
||||
// the generated output
|
||||
// args can be a single argument:
|
||||
// arg[0]
|
||||
// or a list of name-value pairs:
|
||||
// (args[0]: name, args[1]: value), (args[2]: name, args[3]: value)...
|
||||
func (g *generator) eval(template string, args ...interface{}) (string, error) {
|
||||
target := g.t.Lookup(template)
|
||||
if target == nil {
|
||||
return "", fmt.Errorf("template '%v' not found", template)
|
||||
}
|
||||
sb := strings.Builder{}
|
||||
var err error
|
||||
if len(args) == 1 {
|
||||
err = target.Execute(&sb, args[0])
|
||||
} else {
|
||||
m := newMap()
|
||||
if len(args)%2 != 0 {
|
||||
return "", fmt.Errorf("Eval expects a single argument or list name-value pairs")
|
||||
}
|
||||
for i := 0; i < len(args); i += 2 {
|
||||
name, ok := args[i].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Eval argument %v is not a string", i)
|
||||
}
|
||||
m.Put(name, args[i+1])
|
||||
}
|
||||
err = target.Execute(&sb, m)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("while evaluating '%v': %v", template, err)
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// lookup returns the declaration with the given name, or nil if not found.
|
||||
func (g *generator) lookup(name string) ast.Decl {
|
||||
return g.declarations[name]
|
||||
}
|
||||
|
||||
// include loads the template with the given path, importing the declarations
|
||||
// into the scope of the current template.
|
||||
func (g *generator) include(path string) (string, error) {
|
||||
t, err := g.t.
|
||||
Option("missingkey=invalid").
|
||||
Funcs(g.funcs).
|
||||
ParseFiles(filepath.Join(g.workingDir, path))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
g.t.AddParseTree(path, t.Tree)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Map is a simple generic key-value map, which can be used in the template
|
||||
type Map map[interface{}]interface{}
|
||||
|
||||
func newMap() Map { return Map{} }
|
||||
|
||||
// Put adds the key-value pair into the map.
|
||||
// Put always returns an empty string so nothing is printed in the template.
|
||||
func (m Map) Put(key, value interface{}) string {
|
||||
m[key] = value
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get looks up and returns the value with the given key. If the map does not
|
||||
// contain the given key, then nil is returned.
|
||||
func (m Map) Get(key interface{}) interface{} {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
// is returns a function that returns true if the value passed to the function
|
||||
// matches any of the types of the objects in 'prototypes'.
|
||||
func is(prototypes ...interface{}) func(interface{}) bool {
|
||||
types := make([]reflect.Type, len(prototypes))
|
||||
for i, p := range prototypes {
|
||||
types[i] = reflect.TypeOf(p)
|
||||
}
|
||||
return func(v interface{}) bool {
|
||||
ty := reflect.TypeOf(v)
|
||||
for _, rty := range types {
|
||||
if ty == rty || ty == reflect.PtrTo(rty) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isConstructor returns true if the object is a constructor ast.Member.
|
||||
func isConstructor(v interface{}) bool {
|
||||
if member, ok := v.(*ast.Member); ok {
|
||||
if ty, ok := member.Type.(*ast.TypeName); ok {
|
||||
return ty.Name == "constructor"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isUndefinedType returns true if the type is 'undefined'
|
||||
func isUndefinedType(ty ast.Type) bool {
|
||||
if ty, ok := ty.(*ast.TypeName); ok {
|
||||
return ty.Name == "undefined"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// enumEntryName formats the enum entry name 's' for use in a C++ enum.
|
||||
func enumEntryName(s string) string {
|
||||
return "k" + strings.ReplaceAll(pascalCase(strings.Trim(s, `"`)), "-", "")
|
||||
}
|
||||
|
||||
// Method describes a WebIDL interface method
|
||||
type Method struct {
|
||||
// Name of the method
|
||||
Name string
|
||||
// The list of overloads of the method
|
||||
Overloads []*ast.Member
|
||||
}
|
||||
|
||||
// methodsOf returns all the methods of the given WebIDL interface.
|
||||
func methodsOf(obj interface{}) []*Method {
|
||||
iface, ok := obj.(*ast.Interface)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
byName := map[string]*Method{}
|
||||
out := []*Method{}
|
||||
for _, member := range iface.Members {
|
||||
member := member.(*ast.Member)
|
||||
if !member.Const && !member.Attribute && !isConstructor(member) {
|
||||
if method, ok := byName[member.Name]; ok {
|
||||
method.Overloads = append(method.Overloads, member)
|
||||
} else {
|
||||
method = &Method{
|
||||
Name: member.Name,
|
||||
Overloads: []*ast.Member{member},
|
||||
}
|
||||
byName[member.Name] = method
|
||||
out = append(out, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// attributesOf returns all the attributes of the given WebIDL interface or
|
||||
// namespace.
|
||||
func attributesOf(obj interface{}) []*ast.Member {
|
||||
out := []*ast.Member{}
|
||||
add := func(m interface{}) {
|
||||
if m := m.(*ast.Member); m.Attribute {
|
||||
out = append(out, m)
|
||||
}
|
||||
}
|
||||
switch obj := obj.(type) {
|
||||
case *ast.Interface:
|
||||
for _, m := range obj.Members {
|
||||
add(m)
|
||||
}
|
||||
case *ast.Namespace:
|
||||
for _, m := range obj.Members {
|
||||
add(m)
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// constantsOf returns all the constant values of the given WebIDL interface or
|
||||
// namespace.
|
||||
func constantsOf(obj interface{}) []*ast.Member {
|
||||
out := []*ast.Member{}
|
||||
add := func(m interface{}) {
|
||||
if m := m.(*ast.Member); m.Const {
|
||||
out = append(out, m)
|
||||
}
|
||||
}
|
||||
switch obj := obj.(type) {
|
||||
case *ast.Interface:
|
||||
for _, m := range obj.Members {
|
||||
add(m)
|
||||
}
|
||||
case *ast.Namespace:
|
||||
for _, m := range obj.Members {
|
||||
add(m)
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// pascalCase returns the snake-case string s transformed into 'PascalCase',
|
||||
// Rules:
|
||||
// * The first letter of the string is capitalized
|
||||
// * Characters following an underscore, hyphen or number are capitalized
|
||||
// * Underscores are removed from the returned string
|
||||
// See: https://en.wikipedia.org/wiki/Camel_case
|
||||
func pascalCase(s string) string {
|
||||
b := strings.Builder{}
|
||||
upper := true
|
||||
for _, r := range s {
|
||||
if r == '_' || r == '-' {
|
||||
upper = true
|
||||
continue
|
||||
}
|
||||
if upper {
|
||||
b.WriteRune(unicode.ToUpper(r))
|
||||
upper = false
|
||||
} else {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
if unicode.IsNumber(r) {
|
||||
upper = true
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
const header = `// Copyright 2021 The Dawn Authors.
|
||||
//
|
||||
// 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.
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// File generated by tools/cmd/idlgen.go, with the arguments:
|
||||
// %v
|
||||
//
|
||||
// Do not modify this file directly
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
`
|
|
@ -0,0 +1,5 @@
|
|||
module dawn.googlesource.com/dawn/src/dawn_node/tools
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/ben-clayton/webidlparser v0.0.0-20210923100217-8ba896ded094
|
|
@ -0,0 +1,26 @@
|
|||
github.com/ben-clayton/webidlparser v0.0.0-20210920164050-74f1f9d55323 h1:z6T07jitSi9kfzvR2cHgRQgp32vJsIPMGJDJiWeShok=
|
||||
github.com/ben-clayton/webidlparser v0.0.0-20210920164050-74f1f9d55323/go.mod h1:bV550SPlMos7UhMprxlm14XTBTpKHSUZ8Q4Id5qQuyw=
|
||||
github.com/ben-clayton/webidlparser v0.0.0-20210923100217-8ba896ded094 h1:CTVJdI6oUCRNucMEmoh3c2U88DesoPtefsxKhoZ1WuQ=
|
||||
github.com/ben-clayton/webidlparser v0.0.0-20210923100217-8ba896ded094/go.mod h1:bV550SPlMos7UhMprxlm14XTBTpKHSUZ8Q4Id5qQuyw=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
Loading…
Reference in New Issue