├── .version ├── constants.go ├── scripts ├── postinstall-debian.sh └── postinstall-rhel.sh ├── CONTRIBUTING.md ├── .gitignore ├── oui ├── collect_csv_test.go ├── convert.go ├── types.go ├── options_test.go ├── registry.go ├── db_test.go ├── collect_csv.go ├── options.go └── db.go ├── internal ├── logger │ ├── logger_test.go │ └── logger.go └── util │ ├── util.go │ └── util_test.go ├── main.go ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── cmd ├── cli.go └── commands.go ├── .goreleaser.yml ├── LICENSE ├── version.sh ├── go.mod ├── README.md └── go.sum /.version: -------------------------------------------------------------------------------- 1 | 2.0.6 2 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed .version 8 | var Version string 9 | -------------------------------------------------------------------------------- /scripts/postinstall-debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | find_path() { 4 | local paths=($(dpkg -L oui)) 5 | local path=${paths[-1]} 6 | echo $path 7 | return 0 8 | } 9 | 10 | BIN=$(find_path) 11 | 12 | $BIN update 13 | exit 0 14 | -------------------------------------------------------------------------------- /scripts/postinstall-rhel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | find_path() { 4 | local paths=($(rpm -ql oui | grep '/oui$')) 5 | local path=${paths[0]} 6 | echo $path 7 | return 0 8 | } 9 | 10 | BIN=$(find_path) 11 | 12 | $BIN update 13 | exit 0 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Version Updates 2 | 3 | To update the version, run `./version.sh `. This will update the `.version` file (which is imported to the codebase at build time), commit the change, and add a git tag. This should be the last step before pushing changes. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | .DS_Store 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | # Go workspace file 19 | go.work 20 | -------------------------------------------------------------------------------- /oui/collect_csv_test.go: -------------------------------------------------------------------------------- 1 | package oui_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | "github.com/thatmattlove/oui/v2/oui" 9 | ) 10 | 11 | func Test_CollectAll(t *testing.T) { 12 | results, err := oui.CollectAll(nil, nil) 13 | require.NoError(t, err) 14 | assert.IsType(t, []*oui.VendorDef{}, results) 15 | assert.True(t, len(results) > 5) 16 | } 17 | -------------------------------------------------------------------------------- /oui/convert.go: -------------------------------------------------------------------------------- 1 | package oui 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/thatmattlove/go-macaddr" 7 | ) 8 | 9 | type Formats struct { 10 | Hex string 11 | Dotted string 12 | Dashed string 13 | Int int64 14 | Bytes string 15 | } 16 | 17 | func Convert(i string) (fmts *Formats, err error) { 18 | i = strings.Split(i, "/")[0] 19 | mac, err := macaddr.ParseMACAddress(i) 20 | if err != nil { 21 | return nil, err 22 | } 23 | fmts = &Formats{ 24 | Hex: mac.String(), 25 | Dotted: mac.Dots(), 26 | Dashed: mac.Dashes(), 27 | Int: mac.Int(), 28 | Bytes: mac.ByteString(), 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /oui/types.go: -------------------------------------------------------------------------------- 1 | package oui 2 | 3 | type VendorDef struct { 4 | Prefix string 5 | Length int 6 | Org string 7 | Registry string 8 | } 9 | 10 | func (v *VendorDef) PrefixString() string { 11 | if v == nil { 12 | return "" 13 | } 14 | return v.Prefix 15 | } 16 | 17 | type LoggerType interface { 18 | Success(s string, f ...interface{}) 19 | Info(s string, f ...interface{}) 20 | Warn(s string, f ...interface{}) 21 | Error(s string, f ...interface{}) 22 | Err(err error, strs ...string) 23 | } 24 | 25 | const ( 26 | dialectSqlite int = iota 27 | dialectPsql 28 | ) 29 | 30 | const ( 31 | maxVarsSqlite int = 999 32 | maxVarsPsql int = 65535 33 | ) 34 | -------------------------------------------------------------------------------- /internal/logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger_test 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | 7 | "github.com/thatmattlove/oui/v2/internal/logger" 8 | ) 9 | 10 | func Test_Logger(t *testing.T) { 11 | log := logger.New() 12 | log.Success("test success") 13 | log.Success("test success formatting: %s", "this should be bold") 14 | log.Info("test info") 15 | log.Info("test info formatting: %s", "this should be bold") 16 | log.Warn("test warn") 17 | log.Warn("test warn formatting: %s", "this should be bold") 18 | log.Error("test error") 19 | log.Error("test error formatting: %s", "this should be bold") 20 | log.Err(io.EOF) 21 | log.Err(io.EOF, "this should bold") 22 | log.Err(io.EOF, "this should be %s", "formatted") 23 | } 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | 9 | "github.com/thatmattlove/oui/v2/cmd" 10 | ) 11 | 12 | func isPiped() bool { 13 | fileInfo, _ := os.Stdin.Stat() 14 | return fileInfo.Mode()&os.ModeCharDevice == 0 15 | } 16 | 17 | var splitPattern = regexp.MustCompile(`[\n\r\t\s]`) 18 | 19 | func main() { 20 | args := os.Args 21 | if isPiped() { 22 | scanner := bufio.NewScanner(os.Stdin) 23 | for scanner.Scan() { 24 | line := scanner.Text() 25 | parts := splitPattern.Split(line, -1) 26 | for _, part := range parts { 27 | clean := splitPattern.ReplaceAllString(part, "") 28 | args = append(args, clean) 29 | } 30 | } 31 | } 32 | if len(args) == 1 { 33 | args = append(args, "--help") 34 | } 35 | err := cmd.New(Version).Run(args) 36 | if err != nil { 37 | fmt.Println(err) 38 | os.Exit(1) 39 | } 40 | os.Exit(0) 41 | } 42 | -------------------------------------------------------------------------------- /oui/options_test.go: -------------------------------------------------------------------------------- 1 | package oui 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_sanitizeVersion(t *testing.T) { 10 | t.Run("valid", func(t *testing.T) { 11 | t.Parallel() 12 | ver := "db1" 13 | result := sanitizeVersion(ver) 14 | assert.Equal(t, ver, result) 15 | }) 16 | t.Run("starting number", func(t *testing.T) { 17 | t.Parallel() 18 | ver := "1oui" 19 | result := sanitizeVersion(ver) 20 | assert.Equal(t, "oui_1oui", result) 21 | }) 22 | t.Run("extra chars", func(t *testing.T) { 23 | t.Parallel() 24 | ver := "oui&db*123" 25 | result := sanitizeVersion(ver) 26 | assert.Equal(t, "oui__db__123", result) 27 | }) 28 | t.Run("semver", func(t *testing.T) { 29 | t.Parallel() 30 | ver := "2.0.3" 31 | result := sanitizeVersion(ver) 32 | assert.Equal(t, "oui_2__0__3", result) 33 | }) 34 | t.Run("numbers only", func(t *testing.T) { 35 | t.Parallel() 36 | ver := "12345" 37 | result := sanitizeVersion(ver) 38 | assert.Equal(t, "oui_12345", result) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v4 19 | with: 20 | go-version: 1.20.x 21 | 22 | - name: Run GoReleaser 23 | uses: goreleaser/goreleaser-action@v5 24 | with: 25 | version: latest 26 | args: release --clean 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | TAP_GITHUB_TOKEN: ${{ secrets.GORELEASER_PAT }} 30 | FURY_TOKEN: ${{ secrets.FURY_TOKEN }} 31 | 32 | - name: Upload assets 33 | uses: actions/upload-artifact@v3 34 | with: 35 | path: dist/* 36 | -------------------------------------------------------------------------------- /cmd/cli.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Xuanwo/go-locale" 7 | "github.com/jedib0t/go-pretty/table" 8 | "github.com/urfave/cli/v2" 9 | "golang.org/x/text/language" 10 | "golang.org/x/text/message" 11 | ) 12 | 13 | var tableStyle = &table.Style{ 14 | Name: "StyleRounded", 15 | Box: table.StyleBoxRounded, 16 | Color: table.ColorOptionsDefault, 17 | Options: table.OptionsDefault, 18 | Title: table.TitleOptionsDefault, 19 | } 20 | 21 | var debugFlag *cli.BoolFlag = &cli.BoolFlag{Name: "debug", Usage: "Enable debugging", Value: false} 22 | 23 | func withLocale() (p *message.Printer) { 24 | tag, err := locale.Detect() 25 | if err != nil { 26 | tag = language.English 27 | } 28 | p = message.NewPrinter(tag) 29 | return 30 | } 31 | 32 | func versionPrinter(c *cli.Context) { 33 | fmt.Println(c.App.Version) 34 | } 35 | 36 | func New(version string) *cli.App { 37 | subs := []*cli.Command{UpdateCmd(), ConvertCmd(), CountCmd()} 38 | 39 | flags := []cli.Flag{debugFlag} 40 | 41 | cli.VersionPrinter = versionPrinter 42 | 43 | cmd := &cli.App{ 44 | Name: "oui", 45 | Usage: "MAC Address CLI Toolkit", 46 | Action: MainCmd, 47 | Commands: subs, 48 | Flags: flags, 49 | Version: version, 50 | HideVersion: false, 51 | } 52 | 53 | return cmd 54 | } 55 | -------------------------------------------------------------------------------- /internal/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "regexp" 7 | "strings" 8 | "time" 9 | 10 | "github.com/hako/durafmt" 11 | ) 12 | 13 | func RemoveComments(str string) (c string) { 14 | has := regexp.MustCompile(`^[^\\]*#.*$`) 15 | re := regexp.MustCompile(`^([^\\#]+)|(#.*)$`) 16 | if has.MatchString(str) { 17 | p := re.FindStringSubmatch(str) 18 | c = p[1] 19 | } else { 20 | c = str 21 | } 22 | return strings.TrimSpace(c) 23 | } 24 | 25 | func SplitTabs(i string) []string { 26 | p := regexp.MustCompile(`\t+`) 27 | var r []string 28 | for _, e := range p.Split(i, -1) { 29 | if e != "" { 30 | r = append(r, strings.TrimSpace(e)) 31 | } 32 | } 33 | return r 34 | } 35 | 36 | func PathExists(n string) bool { 37 | if _, err := os.Stat(n); errors.Is(err, os.ErrNotExist) { 38 | return false 39 | } 40 | return true 41 | } 42 | 43 | func TimeSince(t time.Time) string { 44 | return durafmt.Parse(time.Since(t)).LimitFirstN(1).String() 45 | } 46 | 47 | func SplitSlice[T any](slice []T, max int) [][]T { 48 | result := make([][]T, 0) 49 | 50 | for i := 0; i < len(slice); i += max { 51 | end := i + max 52 | if end > len(slice) { 53 | end = len(slice) 54 | } 55 | part := slice[i:end] 56 | if part != nil { 57 | result = append(result, part) 58 | } 59 | } 60 | return result 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | go-version: 12 | - 1.20.x 13 | os: [ubuntu-latest] 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | - name: Start Postgresql 21 | run: | 22 | sudo systemctl start postgresql.service 23 | pg_isready 24 | sudo -u postgres createuser -s -d -r -w runner 25 | # See: https://dev.to/lxxxvi/github-action-using-pre-installed-postgres-5144 26 | - name: Setup Postgres 27 | run: | 28 | psql postgres -c 'CREATE DATABASE oui;' 29 | psql postgres -c "CREATE USER oui WITH PASSWORD 'oui';" 30 | psql postgres -c 'ALTER DATABASE oui OWNER TO oui;' 31 | 32 | - name: Go Setup 33 | uses: actions/setup-go@v4 34 | with: 35 | go-version: ${{ matrix.go-version }} 36 | 37 | - name: Run Tests 38 | run: go test -v ./... 39 | env: 40 | POSTGRES_PASSWORD: oui 41 | -------------------------------------------------------------------------------- /oui/registry.go: -------------------------------------------------------------------------------- 1 | package oui 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | const ( 9 | REGISTRY_OUI string = "MA-L" 10 | REGISTRY_OUI36 string = "MA-S" 11 | REGISTRY_OUI28 string = "MA-M" 12 | REGISTRY_IAB string = "IAB" 13 | REGISTRY_CID string = "CID" 14 | ) 15 | 16 | type Registry struct { 17 | Name string 18 | BaseURL string 19 | FilePrefix string 20 | FileExtension string 21 | DefaultPrefixLen uint8 22 | } 23 | 24 | func (reg *Registry) URL() *url.URL { 25 | s := fmt.Sprintf("%s/%s.%s", reg.BaseURL, reg.FilePrefix, reg.FileExtension) 26 | u, err := url.Parse(s) 27 | if err != nil { 28 | panic(err) 29 | } 30 | return u 31 | } 32 | 33 | func (reg *Registry) FileName() string { 34 | return fmt.Sprintf("%s.%s", reg.FilePrefix, reg.FileExtension) 35 | } 36 | 37 | func (reg *Registry) TempFilePattern() string { 38 | return fmt.Sprintf("*-%s.%s", reg.FilePrefix, reg.FileExtension) 39 | } 40 | 41 | func Registries() []*Registry { 42 | return []*Registry{ 43 | {Name: REGISTRY_OUI, BaseURL: "https://standards-oui.ieee.org/oui", FilePrefix: "oui", FileExtension: "csv", DefaultPrefixLen: 24}, 44 | {Name: REGISTRY_CID, BaseURL: "https://standards-oui.ieee.org/cid", FilePrefix: "cid", FileExtension: "csv", DefaultPrefixLen: 24}, 45 | {Name: REGISTRY_OUI28, BaseURL: "https://standards-oui.ieee.org/oui28", FilePrefix: "mam", FileExtension: "csv", DefaultPrefixLen: 28}, 46 | {Name: REGISTRY_OUI36, BaseURL: "https://standards-oui.ieee.org/oui36", FilePrefix: "oui36", FileExtension: "csv", DefaultPrefixLen: 36}, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - darwin 10 | - linux 11 | - freebsd 12 | - windows 13 | goarch: 14 | - amd64 15 | - arm 16 | - arm64 17 | ignore: 18 | - goos: windows 19 | goarch: arm64 20 | - goos: windows 21 | goarch: arm 22 | - goos: freebsd 23 | goarch: arm64 24 | - goos: freebsd 25 | goarch: arm 26 | 27 | changelog: 28 | sort: asc 29 | filters: 30 | exclude: 31 | - '^docs:' 32 | - '^test:' 33 | 34 | brews: 35 | - name: oui 36 | homepage: https://github.com/thatmattlove/oui 37 | description: MAC Address CLI Toolkit 38 | license: BSD-3-Clause-Clear 39 | commit_author: 40 | name: Matt Love 41 | email: matt@stunninglyclear.com 42 | repository: 43 | owner: thatmattlove 44 | name: homebrew-oui 45 | token: '{{ .Env.TAP_GITHUB_TOKEN }}' 46 | 47 | nfpms: 48 | - id: linux_packages 49 | homepage: https://github.com/thatmattlove/oui 50 | maintainer: Matt Love 51 | description: MAC Address CLI Toolkit 52 | license: BSD-3-Clause-Clear 53 | formats: 54 | - deb 55 | - rpm 56 | overrides: 57 | deb: 58 | scripts: 59 | postinstall: 'scripts/postinstall-debian.sh' 60 | rpm: 61 | scripts: 62 | postinstall: 'scripts/postinstall-rhel.sh' 63 | 64 | publishers: 65 | - name: fury.io 66 | ids: 67 | - linux_packages 68 | dir: '{{ dir .ArtifactPath }}' 69 | cmd: curl -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/thatmattlove/ 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Clear BSD License 2 | 3 | Copyright (c) 2022 Matthew Love 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted (subject to the limitations in the disclaimer 8 | below) provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from this 19 | software without specific prior written permission. 20 | 21 | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY 22 | THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 23 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 29 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 30 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Version argument, should be something like '0.0.1'. Will be added to '.version' file. 4 | VER="$1" 5 | # Value to use as the git tag. Will end up being somethig like 'v0.0.1'. 6 | TAG=$VER 7 | # Version file full path. 8 | FILE="$(pwd)/.version" 9 | 10 | CURRENT_TAG=$(git describe --abbrev=0 --tags 2>/dev/null) 11 | 12 | DIFF="$(git --no-pager diff)" 13 | 14 | [ ! -f $FILE ] && touch $FILE 15 | 16 | CURRENT="$(cat $FILE)" 17 | 18 | # Ensure there are no unstaged changes. 19 | if [[ $DIFF != "" ]]; then 20 | echo "There are unstaged changes. Commit or stash any changes and try again." 21 | exit 1 22 | fi 23 | 24 | # Ensure a version is provided before continuing. 25 | if [[ $VER == "" ]]; then 26 | echo "Provide a version" 1>&2 27 | exit 1 28 | fi 29 | 30 | # Remove prepended 'v' from version argument, if it was provided. 31 | if [[ "$VER" == *"v"* ]]; then 32 | ver=$(echo "$VER" | cut -d 'v' -f 2) 33 | VER="$ver" 34 | fi 35 | 36 | # Append 'v' to tag variable, if 'v' was not already prepended. 37 | if [[ "$TAG" != *"v"* ]]; then 38 | TAG="v$TAG" 39 | fi 40 | 41 | if [[ "$CURRENT" == "$VER" ]]; then 42 | echo "Version is already $VER" 43 | exit 1 44 | elif [[ "$CURRENT_TAG" == "$TAG" ]]; then 45 | git_del_tag="$(git tag -d $TAG 1>/dev/null 2>&1)" 46 | 47 | if [[ "$git_del_tag" != "" ]]; then 48 | echo -e "Error deleting git tag $TAG:\n$git_del_tag" 49 | exit 1 50 | fi 51 | fi 52 | 53 | echo $VER >$FILE 54 | echo "Added version $VER to $FILE" 55 | 56 | GIT_ADD_ERR="$(git add $FILE 1>/dev/null 2>&1)" 57 | 58 | # Print any errors from git and exit. 59 | if [[ "$GIT_ADD_ERR" != "" ]]; then 60 | echo -e "git add error:\n$GIT_ADD_ERR" 1>&2 61 | exit 1 62 | fi 63 | 64 | GIT_COMMIT_ERR="$(git commit -m "Release $TAG" 1>/dev/null 2>&1)" 65 | 66 | # Print any errors from git and exit. 67 | if [[ "$GIT_COMMIT_ERR" != "" ]]; then 68 | echo -e "git commit error:\n$GIT_COMMIT_ERR" 1>&2 69 | exit 1 70 | fi 71 | 72 | # Capture stderr from adding git tag. 73 | GIT_TAG_ERR="$(git tag $TAG 2>&1)" 74 | 75 | # Print any errors from git and exit. 76 | if [[ "$GIT_TAG_ERR" != "" ]]; then 77 | echo -e "git tag error:\n$GIT_TAG_ERR" 1>&2 78 | exit 1 79 | else 80 | echo "Added git tag $TAG" 81 | fi 82 | 83 | exit 0 84 | -------------------------------------------------------------------------------- /oui/db_test.go: -------------------------------------------------------------------------------- 1 | package oui_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | "github.com/thatmattlove/oui/v2/oui" 12 | ) 13 | 14 | func Test_New(t *testing.T) { 15 | mac := "00:50:56:00:b3:3f" 16 | prefix := "00:50:56:00:00:00/24" 17 | org := "VMware, Inc." 18 | registry := "MA-L" 19 | 20 | t.Run("postgres", func(t *testing.T) { 21 | t.Parallel() 22 | password := os.Getenv("POSTGRES_PASSWORD") 23 | require.NotEqual(t, "", password, "missing POSTGRES_PASSWORD environment variable") 24 | cs := fmt.Sprintf("postgresql://oui:%s@localhost/oui?sslmode=disable", password) 25 | psql, err := oui.CreatePostgresOption(cs) 26 | require.NoError(t, err) 27 | ouidb, err := oui.New(oui.WithVersion("test"), psql) 28 | require.NoError(t, err) 29 | t.Cleanup(func() { 30 | ouidb.Clear() 31 | }) 32 | t.Run("populate", func(t *testing.T) { 33 | populated, err := ouidb.Populate() 34 | require.NoError(t, err) 35 | assert.NotZero(t, populated) 36 | }) 37 | t.Run("count", func(t *testing.T) { 38 | count, err := ouidb.Count() 39 | require.NoError(t, err) 40 | assert.NotZero(t, count) 41 | }) 42 | t.Run("query", func(t *testing.T) { 43 | matches, err := ouidb.Find(mac) 44 | require.NoError(t, err) 45 | assert.NotZero(t, len(matches)) 46 | match := matches[0] 47 | assert.Equal(t, org, match.Org) 48 | assert.Equal(t, prefix, match.Prefix) 49 | assert.Equal(t, registry, match.Registry) 50 | }) 51 | }) 52 | t.Run("sqlite", func(t *testing.T) { 53 | t.Parallel() 54 | d := t.TempDir() 55 | f := filepath.Join(d, "oui.db") 56 | sqlite, err := oui.CreateSQLiteOption(f) 57 | require.NoError(t, err) 58 | ouidb, err := oui.New(oui.WithVersion("test"), sqlite) 59 | require.NoError(t, err) 60 | t.Run("populate", func(t *testing.T) { 61 | populated, err := ouidb.Populate() 62 | require.NoError(t, err) 63 | assert.NotZero(t, populated) 64 | }) 65 | t.Run("count", func(t *testing.T) { 66 | count, err := ouidb.Count() 67 | require.NoError(t, err) 68 | assert.NotZero(t, count) 69 | }) 70 | t.Run("query", func(t *testing.T) { 71 | matches, err := ouidb.Find(mac) 72 | require.NoError(t, err) 73 | assert.NotZero(t, len(matches)) 74 | match := matches[0] 75 | assert.Equal(t, org, match.Org) 76 | assert.Equal(t, prefix, match.Prefix) 77 | assert.Equal(t, registry, match.Registry) 78 | }) 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/thatmattlove/oui/v2 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/Xuanwo/go-locale v1.1.0 7 | github.com/gookit/gcli/v3 v3.0.1 8 | github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b 9 | github.com/jedib0t/go-pretty v4.3.0+incompatible 10 | github.com/lib/pq v1.10.9 11 | github.com/stretchr/testify v1.7.0 12 | github.com/thatmattlove/go-macaddr v0.0.7 13 | github.com/urfave/cli/v2 v2.3.0 14 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d 15 | golang.org/x/text v0.3.7 16 | modernc.org/sqlite v1.14.5 17 | ) 18 | 19 | require ( 20 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect 21 | github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect 22 | github.com/davecgh/go-spew v1.1.1 // indirect 23 | github.com/go-openapi/errors v0.20.1 // indirect 24 | github.com/go-openapi/strfmt v0.21.1 // indirect 25 | github.com/go-stack/stack v1.8.1 // indirect 26 | github.com/google/uuid v1.3.0 // indirect 27 | github.com/gookit/color v1.5.0 // indirect 28 | github.com/gookit/goutil v0.4.3 // indirect 29 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 30 | github.com/mattn/go-isatty v0.0.14 // indirect 31 | github.com/mattn/go-runewidth v0.0.13 // indirect 32 | github.com/mitchellh/mapstructure v1.4.3 // indirect 33 | github.com/oklog/ulid v1.3.1 // indirect 34 | github.com/pmezard/go-difflib v1.0.0 // indirect 35 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect 36 | github.com/rivo/uniseg v0.2.0 // indirect 37 | github.com/russross/blackfriday/v2 v2.0.1 // indirect 38 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 39 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect 40 | go.mongodb.org/mongo-driver v1.8.2 // indirect 41 | golang.org/x/mod v0.3.0 // indirect 42 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect 43 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect 44 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 45 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect 46 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 47 | lukechampine.com/uint128 v1.1.1 // indirect 48 | modernc.org/cc/v3 v3.35.22 // indirect 49 | modernc.org/ccgo/v3 v3.15.1 // indirect 50 | modernc.org/libc v1.14.1 // indirect 51 | modernc.org/mathutil v1.4.1 // indirect 52 | modernc.org/memory v1.0.5 // indirect 53 | modernc.org/opt v0.1.1 // indirect 54 | modernc.org/strutil v1.1.1 // indirect 55 | modernc.org/token v1.0.0 // indirect 56 | ) 57 | -------------------------------------------------------------------------------- /internal/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Xuanwo/go-locale" 8 | "github.com/jedib0t/go-pretty/text" 9 | "golang.org/x/text/language" 10 | "golang.org/x/text/message" 11 | ) 12 | 13 | type Logger struct { 14 | Accent *text.Colors 15 | Writer *message.Printer 16 | } 17 | 18 | func New() *Logger { 19 | tag, err := locale.Detect() 20 | if err != nil { 21 | tag = language.English 22 | } 23 | logger := &Logger{ 24 | Accent: &text.Colors{text.Bold, text.Underline}, 25 | Writer: message.NewPrinter(tag), 26 | } 27 | return logger 28 | } 29 | 30 | func (l *Logger) WithAccent(color text.Color) *text.Colors { 31 | a := &text.Colors{color} 32 | *a = append(*a, *l.Accent...) 33 | return a 34 | } 35 | 36 | func (l *Logger) Success(s string, f ...interface{}) { 37 | t := &text.Colors{text.FgHiGreen} 38 | var v string 39 | if len(f) == 0 { 40 | v = t.Sprint(s) 41 | } else { 42 | v = t.Sprintf(s, l.Accent.Sprint(l.Writer.Sprint(f...))) 43 | } 44 | l.Writer.Fprintln(os.Stdout, v) 45 | } 46 | 47 | func (l *Logger) Info(s string, f ...interface{}) { 48 | t := &text.Colors{text.FgHiCyan} 49 | var v string 50 | if len(f) == 0 { 51 | v = t.Sprint(s) 52 | } else { 53 | v = t.Sprintf(s, l.Accent.Sprint(l.Writer.Sprint(f...))) 54 | } 55 | l.Writer.Fprintln(os.Stdout, v) 56 | } 57 | 58 | func (l *Logger) Warn(s string, f ...interface{}) { 59 | t := &text.Colors{text.FgHiYellow} 60 | 61 | var v string 62 | if len(f) == 0 { 63 | v = t.Sprint(s) 64 | } else { 65 | v = t.Sprintf(s, l.Accent.Sprint(l.Writer.Sprint(f...))) 66 | } 67 | l.Writer.Fprintln(os.Stdout, v) 68 | } 69 | 70 | func (l *Logger) Error(s string, f ...interface{}) { 71 | t := &text.Colors{text.FgHiRed} 72 | var v string 73 | if len(f) == 0 { 74 | v = t.Sprint(s) 75 | } else { 76 | v = t.Sprintf(s, l.Accent.Sprint(l.Writer.Sprint(f...))) 77 | } 78 | l.Writer.Fprintln(os.Stderr, v) 79 | } 80 | 81 | func (l *Logger) Err(err error, strs ...string) { 82 | t := &text.Colors{text.FgHiRed} 83 | e := err.Error() 84 | var v string 85 | if len(strs) == 0 { 86 | v = t.Sprint(e) 87 | } else if len(strs) == 1 { 88 | v = t.Sprintf("%s: %s", strs[0], e) 89 | } else { 90 | s := strs[0] 91 | f := strs[1:] 92 | fi := []interface{}{} 93 | for _, i := range f { 94 | fi = append(fi, l.Writer.Sprint(i)) 95 | } 96 | wa := l.WithAccent(text.FgHiRed) 97 | ss := wa.Sprintf(s, fi...) 98 | v = fmt.Sprintf("%s: %s", ss, t.Sprint(e)) 99 | } 100 | l.Writer.Fprintln(os.Stderr, v) 101 | } 102 | -------------------------------------------------------------------------------- /internal/util/util_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "os" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/thatmattlove/oui/v2/internal/util" 10 | ) 11 | 12 | func Test_removeComments(t *testing.T) { 13 | t.Run("removeComments - inline", func(t *testing.T) { 14 | assert.Equal(t, "stuff", util.RemoveComments("stuff # with comment")) 15 | }) 16 | t.Run("removeComments - full line", func(t *testing.T) { 17 | assert.Equal(t, "", util.RemoveComments("# line with comment")) 18 | }) 19 | t.Run("removeComments - escaped", func(t *testing.T) { 20 | assert.Equal(t, `escaped \# comment`, util.RemoveComments(`escaped \# comment`)) 21 | }) 22 | t.Run("removeComments - empty line", func(t *testing.T) { 23 | assert.Equal(t, "", util.RemoveComments("#")) 24 | }) 25 | t.Run("removeComments - end comment", func(t *testing.T) { 26 | r := util.RemoveComments(`00:00:D1 Adaptec Adaptec, Inc. # "Nodem" product`) 27 | e := `00:00:D1 Adaptec Adaptec, Inc.` 28 | assert.Equal(t, e, r) 29 | }) 30 | } 31 | 32 | func Test_splitTabs(t *testing.T) { 33 | t.Run("splitTabs 1", func(t *testing.T) { 34 | r := util.SplitTabs(`one two three`) 35 | e := []string{"one", "two", "three"} 36 | assert.Equal(t, e, r) 37 | }) 38 | t.Run("splitTabs 2", func(t *testing.T) { 39 | r := util.SplitTabs(`one two three`) 40 | e := []string{"one", "two", "three"} 41 | assert.Equal(t, e, r) 42 | }) 43 | t.Run("splitTabs 3", func(t *testing.T) { 44 | r := util.SplitTabs(`one two three`) 45 | e := []string{"one", "two", "three"} 46 | assert.Equal(t, e, r) 47 | }) 48 | t.Run("splitTabs 4", func(t *testing.T) { 49 | r := util.SplitTabs(`one two three `) 50 | e := []string{"one", "two", "three"} 51 | assert.Equal(t, e, r) 52 | }) 53 | } 54 | 55 | func Test_pathExists(t *testing.T) { 56 | t.Run("pathExists - not exists", func(t *testing.T) { 57 | r := util.PathExists("/this/path/does/not/exist") 58 | assert.False(t, r) 59 | }) 60 | t.Run("pathExists - exists", func(t *testing.T) { 61 | tf, err := os.CreateTemp(os.TempDir(), "oui-test-*") 62 | if err != nil { 63 | panic(err) 64 | } 65 | defer os.Remove(tf.Name()) 66 | 67 | r := util.PathExists(tf.Name()) 68 | assert.True(t, r) 69 | }) 70 | } 71 | 72 | func Test_SplitSlice(t *testing.T) { 73 | t.Run("equal parts", func(t *testing.T) { 74 | t.Parallel() 75 | original := []int{1, 2, 3, 4, 5, 6} 76 | max := 2 77 | expected := [][]int{ 78 | {1, 2}, 79 | {3, 4}, 80 | {5, 6}, 81 | } 82 | result := util.SplitSlice(original, max) 83 | assert.True(t, reflect.DeepEqual(expected, result)) 84 | }) 85 | t.Run("unequal parts", func(t *testing.T) { 86 | t.Parallel() 87 | original := []int{1, 2, 3, 4, 5, 6} 88 | max := 4 89 | expected := [][]int{ 90 | {1, 2, 3, 4}, 91 | {5, 6}, 92 | } 93 | result := util.SplitSlice(original, max) 94 | assert.True(t, reflect.DeepEqual(expected, result)) 95 | }) 96 | 97 | t.Run("empty", func(t *testing.T) { 98 | t.Parallel() 99 | original := []string{} 100 | max := 3 101 | expected := [][]string{} 102 | result := util.SplitSlice(original, max) 103 | assert.Equal(t, expected, result) 104 | }) 105 | 106 | } 107 | -------------------------------------------------------------------------------- /oui/collect_csv.go: -------------------------------------------------------------------------------- 1 | package oui 2 | 3 | import ( 4 | "encoding/csv" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "strings" 11 | 12 | "github.com/gookit/gcli/v3/progress" 13 | "github.com/thatmattlove/go-macaddr" 14 | ) 15 | 16 | func DownloadCSV(registry *Registry) (fileName string, err error) { 17 | file, err := os.CreateTemp("", registry.TempFilePattern()) 18 | if err != nil { 19 | return 20 | } 21 | defer file.Close() 22 | fileName = file.Name() 23 | res, err := http.Get(registry.URL().String()) 24 | if err != nil { 25 | return 26 | } 27 | defer res.Body.Close() 28 | 29 | b, err := io.ReadAll(res.Body) 30 | if err != nil { 31 | return 32 | } 33 | _, err = file.Write(b) 34 | return 35 | } 36 | 37 | func ReadCSV(registry *Registry, fileName string, logger LoggerType) (results []*VendorDef) { 38 | file, err := os.Open(fileName) 39 | if err != nil { 40 | if logger != nil { 41 | logger.Err(err) 42 | } 43 | panic(err) 44 | } 45 | defer file.Close() 46 | reader := csv.NewReader(file) 47 | reader.LazyQuotes = true 48 | var place int64 49 | for { 50 | var row []string 51 | row, err = reader.Read() 52 | if err == io.EOF { 53 | // Exit loop when file is done being read. 54 | if logger != nil { 55 | logger.Success("finished parsing vendors from %s registry", registry.Name) 56 | } 57 | break 58 | } else if err != nil { 59 | if logger != nil { 60 | logger.Err(err, "failed to read file '%s'", registry.FileName()) 61 | } 62 | } 63 | if place == 0 { 64 | // Ignore header row. 65 | place++ 66 | continue 67 | } 68 | place++ 69 | if len(row) < 3 { 70 | // Ignore rows that don't conform to expected structure. 71 | if logger != nil { 72 | logger.Warn("skipping row %s", row) 73 | } 74 | continue 75 | } 76 | assignment := strings.TrimSpace(row[1]) 77 | if !strings.Contains(assignment, "/") { 78 | assignment += fmt.Sprintf("/%d", registry.DefaultPrefixLen) 79 | } 80 | organization := row[2] 81 | org := strings.TrimSpace(organization) 82 | base, mp, err := macaddr.ParseMACPrefix(assignment) 83 | if err != nil { 84 | if logger != nil { 85 | logger.Err(err, "failed to parse OUI assignment") 86 | } 87 | continue 88 | } 89 | prefixLen := mp.PrefixLen() 90 | prefix := fmt.Sprintf("%s/%d", base.String(), prefixLen) 91 | v := &VendorDef{ 92 | Org: org, 93 | Length: prefixLen, 94 | Prefix: prefix, 95 | Registry: registry.Name, 96 | } 97 | results = append(results, v) 98 | } 99 | return 100 | } 101 | 102 | func CollectAll(p *progress.Progress, logger LoggerType) ([]*VendorDef, error) { 103 | registries := Registries() 104 | defs := make([]*VendorDef, 0) 105 | errs := make([]error, 0) 106 | for _, reg := range registries { 107 | if p != nil { 108 | p.Advance(uint(88 / len(registries))) 109 | } 110 | fileName, err := DownloadCSV(reg) 111 | if err != nil { 112 | errs = append(errs, err) 113 | if logger != nil { 114 | logger.Err(err, "failed to download file '%s'", reg.FileName()) 115 | } 116 | } 117 | defs = append(defs, ReadCSV(reg, fileName, logger)...) 118 | } 119 | err := errors.Join(errs...) 120 | return defs, err 121 | } 122 | -------------------------------------------------------------------------------- /oui/options.go: -------------------------------------------------------------------------------- 1 | package oui 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "regexp" 9 | 10 | "github.com/gookit/gcli/v3/progress" 11 | _ "github.com/lib/pq" 12 | "github.com/thatmattlove/oui/v2/internal/util" 13 | _ "modernc.org/sqlite" 14 | ) 15 | 16 | type Options struct { 17 | Logger *LoggerType 18 | Progress *progress.Progress 19 | Version string 20 | Connection *sql.DB 21 | dialect int 22 | MaxConnections uint 23 | } 24 | 25 | type Option func(*Options) 26 | 27 | func sanitizeVersion(v string) string { 28 | if len(v) == 0 { 29 | return "default" 30 | } 31 | first := regexp.MustCompile(`^[0-9].*`) 32 | if first.MatchString(v) { 33 | v = fmt.Sprintf("oui_%s", v) 34 | } 35 | repl := regexp.MustCompile(`[^A-Za-z0-9_]`) 36 | v = repl.ReplaceAllString(v, "__") 37 | return v 38 | } 39 | 40 | func WithProgress(p *progress.Progress) Option { 41 | return func(opts *Options) { 42 | opts.Progress = p 43 | } 44 | } 45 | 46 | func WithLogging(logger LoggerType) Option { 47 | return func(opts *Options) { 48 | opts.Logger = &logger 49 | } 50 | } 51 | 52 | func WithVersion(version string) Option { 53 | return func(opts *Options) { 54 | opts.Version = sanitizeVersion(version) 55 | } 56 | } 57 | 58 | func WithConnection(conn *sql.DB) Option { 59 | return func(opts *Options) { 60 | opts.Connection = conn 61 | } 62 | } 63 | 64 | func WithMaxConnections(max uint) Option { 65 | return func(opts *Options) { 66 | opts.MaxConnections = max 67 | } 68 | } 69 | 70 | func getOptions(setters ...Option) *Options { 71 | options := &Options{ 72 | Logger: nil, 73 | Progress: nil, 74 | Version: "default", 75 | Connection: nil, 76 | MaxConnections: 0, 77 | } 78 | for _, setter := range setters { 79 | setter(options) 80 | } 81 | return options 82 | } 83 | 84 | func getFileName() (fn string, err error) { 85 | dir, err := os.UserConfigDir() 86 | if err != nil { 87 | return 88 | } 89 | fn = filepath.Join(dir, "oui", "oui.db") 90 | return 91 | } 92 | 93 | func scaffold() (dbf *os.File, dn string, err error) { 94 | fn, err := getFileName() 95 | if err != nil { 96 | return 97 | } 98 | dn = filepath.Dir(fn) 99 | 100 | err = os.RemoveAll(dn) 101 | if err != nil { 102 | return 103 | } 104 | err = os.MkdirAll(dn, 0755) 105 | if err != nil { 106 | return 107 | } 108 | defer dbf.Close() 109 | dbf, err = os.Create(fn) 110 | if err != nil { 111 | return 112 | } 113 | return 114 | } 115 | 116 | func CreateSQLiteOption(optionalFileName ...string) (Option, error) { 117 | var fileName string 118 | if len(optionalFileName) != 0 { 119 | fileName = optionalFileName[0] 120 | } else { 121 | defaultFileName, err := getFileName() 122 | if err != nil { 123 | return nil, err 124 | } 125 | fileName = defaultFileName 126 | } 127 | 128 | var conn *sql.DB 129 | 130 | if !util.PathExists(fileName) { 131 | _, _, err := scaffold() 132 | if err != nil { 133 | return nil, err 134 | } 135 | _conn, err := sql.Open("sqlite", fileName) 136 | if err != nil { 137 | return nil, err 138 | } 139 | conn = _conn 140 | } else { 141 | _conn, err := sql.Open("sqlite", fileName) 142 | if err != nil { 143 | return nil, err 144 | } 145 | conn = _conn 146 | } 147 | opt := func(opts *Options) { 148 | opts.Connection = conn 149 | opts.dialect = dialectSqlite 150 | } 151 | return opt, nil 152 | } 153 | 154 | func CreatePostgresOption(connectionString string) (Option, error) { 155 | conn, err := sql.Open("postgres", connectionString) 156 | if err != nil { 157 | return nil, err 158 | } 159 | opt := func(opts *Options) { 160 | opts.Connection = conn 161 | opts.dialect = dialectPsql 162 | } 163 | return opt, nil 164 | } 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | oui 4 |

5 |
6 | MAC Address CLI Toolkit 7 |
8 |
9 | 10 | GitHub Workflow Status 11 | 12 | 13 | GitHub release (latest SemVer) 14 | 15 |
16 | 17 | Check Out the Web Version 18 | 19 |
20 | 21 | 22 | ## Installation 23 | 24 | ### macOS 25 | 26 | #### Homebrew 27 | 28 | ```bash 29 | brew tap thatmattlove/oui 30 | brew install oui 31 | ``` 32 | 33 | #### MacPorts 34 | 35 | ```bash 36 | sudo port install oui 37 | ``` 38 | 39 | ### Linux 40 | 41 | #### Debian/Ubuntu (APT) 42 | 43 | ```bash 44 | echo "deb [trusted=yes] https://repo.fury.io/thatmattlove/ /" > /etc/apt/sources.list.d/thatmattlove.fury.list 45 | sudo apt update 46 | sudo apt install oui 47 | ``` 48 | 49 | #### RHEL/CentOS (YUM) 50 | 51 | ```bash 52 | echo -e "[fury-thatmattlove]\nname=thatmattlove\nbaseurl=https://repo.fury.io/thatmattlove/\nenabled=1\ngpgcheck=0" > /etc/yum.repos.d/thatmattlove.fury.repo 53 | sudo yum update 54 | sudo yum install oui 55 | ``` 56 | 57 | ### Windows 58 | 59 | Coming Soon 60 | 61 | ## Usage 62 | 63 | ```console 64 | $ oui --help 65 | NAME: 66 | oui - MAC Address CLI Toolkit 67 | 68 | USAGE: 69 | oui [global options] command [command options] [arguments...] 70 | 71 | VERSION: 72 | 2.0.4 73 | 74 | 75 | COMMANDS: 76 | update, u, up Refresh the MAC address database 77 | convert, c, con Convert a MAC Address to other formats 78 | entires, e, count Show the number of MAC addresses in the database 79 | help, h Shows a list of commands or help for one command 80 | 81 | GLOBAL OPTIONS: 82 | --debug Enable debugging (default: false) 83 | --help, -h show help (default: false) 84 | --version, -v print the version (default: false) 85 | ``` 86 | 87 | ### OUI Lookup 88 | 89 | ```console 90 | $ oui F4:BD:9E:01:23:45 91 | 92 | F4:BD:9E:01:23:45 Results 93 | 94 | ╭──────────────────────┬────────────────────┬─────────────────────────────────────┬──────────╮ 95 | │ Prefix │ Organization │ Range │ Registry │ 96 | ├──────────────────────┼────────────────────┼─────────────────────────────────────┼──────────┤ 97 | │ f4:bd:9e:00:00:00/24 │ Cisco Systems, Inc │ f4:bd:9e:00:00:00-f4:bd:9e:ff:ff:ff │ MA-L │ 98 | ╰──────────────────────┴────────────────────┴─────────────────────────────────────┴──────────╯ 99 | 100 | ``` 101 | 102 | ### Conversion 103 | 104 | ```console 105 | $ oui convert F4:BD:9E:01:23:45 106 | 107 | F4:BD:9E:01:23:45 108 | 109 | ╭─────────────┬───────────────────────╮ 110 | │ Hexadecimal │ f4:bd:9e:01:23:45 │ 111 | │ Dotted │ f4bd.9e01.2345 │ 112 | │ Dashed │ f4-bd-9e-01-23-45 │ 113 | │ Integer │ 269095236870981 │ 114 | │ Bytes │ {244,189,158,1,35,69} │ 115 | ╰─────────────┴───────────────────────╯ 116 | 117 | ``` 118 | 119 | ### Updating the Database 120 | 121 | ``` 122 | $ oui update 123 | 124 | Updating MAC Address Database 125 | ██████████▌░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 20.0% Populating database...finished parsing vendors from MA-L registry 126 | ██████████████████▌░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 37.0% Populating database...finished parsing vendors from CID registry 127 | ███████████████████████████▌░░░░░░░░░░░░░░░░░░░░░░ 54.0% Populating database...finished parsing vendors from IAB registry 128 | ███████████████████████████████████▌░░░░░░░░░░░░░░ 71.0% Populating database...finished parsing vendors from MA-M registry 129 | ████████████████████████████████████████████▌░░░░░ 88.0% Populating database...finished parsing vendors from MA-S registry 130 | ██████████████████████████████████████████████████ 100.0% Completed 131 | Updated MAC Address database (2.0.4) with 49,949 records in 5 seconds 132 | ``` 133 | 134 | ![GitHub](https://img.shields.io/github/license/thatmattlove/oui?style=for-the-badge&color=000000) -------------------------------------------------------------------------------- /cmd/commands.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/gookit/gcli/v3/progress" 9 | "github.com/jedib0t/go-pretty/table" 10 | "github.com/jedib0t/go-pretty/text" 11 | "github.com/thatmattlove/go-macaddr" 12 | "github.com/thatmattlove/oui/v2/internal/logger" 13 | "github.com/thatmattlove/oui/v2/internal/util" 14 | "github.com/thatmattlove/oui/v2/oui" 15 | "github.com/urfave/cli/v2" 16 | "golang.org/x/term" 17 | "golang.org/x/text/language" 18 | "golang.org/x/text/message" 19 | ) 20 | 21 | func resultsTitleDetail(search string) string { 22 | s := &text.Colors{text.FgHiCyan, text.Bold, text.Underline} 23 | t := &text.Colors{text.Bold} 24 | return fmt.Sprintf("\n %s%s\n", s.Sprint(search), t.Sprint(" Results")) 25 | } 26 | 27 | func resultsTitleSummary(num int) string { 28 | s := &text.Colors{text.FgHiCyan, text.Bold, text.Underline} 29 | t := &text.Colors{text.Bold} 30 | return fmt.Sprintf("\n %s%s%s\n", t.Sprint("Results for "), s.Sprint(num), t.Sprint(" Addresses")) 31 | } 32 | 33 | func createTable() (t table.Writer) { 34 | t = table.NewWriter() 35 | t.SetStyle(*tableStyle) 36 | h := &text.Colors{text.FgHiMagenta, text.Bold} 37 | t.AppendHeader(table.Row{h.Sprint("Prefix"), h.Sprint("Organization"), h.Sprint("Range"), h.Sprint("Registry")}) 38 | return 39 | } 40 | 41 | func CountCmd() *cli.Command { 42 | return &cli.Command{ 43 | Name: "entires", 44 | Usage: "Show the number of MAC addresses in the database", 45 | Aliases: []string{"e", "count"}, 46 | Action: func(c *cli.Context) error { 47 | logger := logger.New() 48 | sqlite, err := oui.CreateSQLiteOption() 49 | if err != nil { 50 | return err 51 | } 52 | db, err := oui.New(oui.WithVersion(c.App.Version), oui.WithLogging(logger), sqlite) 53 | if err != nil { 54 | return err 55 | } 56 | count, err := db.Count() 57 | if err != nil { 58 | return err 59 | } 60 | logger.Info("MAC Address database has %s entries", count) 61 | return nil 62 | }, 63 | } 64 | } 65 | 66 | func MainCmd(c *cli.Context) error { 67 | log := logger.New() 68 | 69 | searches := c.Args().Slice() 70 | 71 | sqlite, err := oui.CreateSQLiteOption() 72 | if err != nil { 73 | return err 74 | } 75 | 76 | db, err := oui.New(oui.WithVersion(c.App.Version), oui.WithLogging(log), sqlite) 77 | if err != nil { 78 | return err 79 | } 80 | defer db.Close() 81 | count, err := db.Count() 82 | if err != nil { 83 | return err 84 | } 85 | if count == 0 { 86 | log.Warn("MAC Address database has not been populated.") 87 | err = UpdateCmd().Run(c) 88 | if err != nil { 89 | return err 90 | } 91 | } 92 | 93 | t := createTable() 94 | 95 | title := resultsTitleDetail(strings.Join(searches, ", ")) 96 | 97 | fd := int(os.Stdin.Fd()) 98 | 99 | if term.IsTerminal(fd) { 100 | w, _, err := term.GetSize(fd) 101 | if err == nil && len(title) > w { 102 | title = resultsTitleSummary(len(searches)) 103 | } 104 | } else { 105 | title = resultsTitleSummary(len(searches)) 106 | } 107 | 108 | fmt.Println(title) 109 | 110 | for _, search := range searches { 111 | results, err := db.Find(search) 112 | if err != nil { 113 | return err 114 | } 115 | if len(results) == 0 { 116 | _, mp, err := macaddr.ParseMACPrefix(search) 117 | if err != nil { 118 | return err 119 | } 120 | m := (&text.Colors{text.FgHiGreen, text.Bold}).Sprint(mp.MAC.String()) 121 | p := text.FgHiCyan.Sprintf("/%d", mp.PrefixLen()) 122 | rf := text.FgHiCyan.Sprint(mp.First()) 123 | rl := text.FgHiRed.Sprint(mp.Last()) 124 | r := fmt.Sprintf("%s-%s", rf, rl) 125 | nf := (&text.Colors{text.FgHiYellow, text.Italic}).Sprint("Not Found") 126 | t.AppendRow(table.Row{m + p, nf, r, nf}) 127 | } else { 128 | for _, result := range results { 129 | _, mp, err := macaddr.ParseMACPrefix(result.PrefixString()) 130 | if err != nil { 131 | return err 132 | } 133 | m := (&text.Colors{text.FgHiGreen, text.Bold}).Sprint(mp.MAC.String()) 134 | p := text.FgHiCyan.Sprintf("/%d", mp.PrefixLen()) 135 | rf := text.FgHiCyan.Sprint(mp.First()) 136 | rl := text.FgHiRed.Sprint(mp.Last()) 137 | r := fmt.Sprintf("%s-%s", rf, rl) 138 | t.AppendRow(table.Row{m + p, result.Org, r, result.Registry}) 139 | } 140 | } 141 | } 142 | fmt.Println(t.Render()) 143 | 144 | return nil 145 | } 146 | 147 | func UpdateCmd() *cli.Command { 148 | cmd := &cli.Command{ 149 | Name: "update", 150 | Usage: "Refresh the MAC address database", 151 | Aliases: []string{"u", "up"}, 152 | } 153 | cmd.Action = func(c *cli.Context) error { 154 | statuses := map[int]string{ 155 | 5: "Downloading vendor data...", 156 | 10: "Processing vendor data...", 157 | 99: "Populating database...", 158 | 100: "Completed", 159 | } 160 | 161 | style := progress.BarChars{Completed: '█', Processing: '▌', Remaining: '░'} 162 | b := (&text.Colors{text.Color(808080)}).Sprint("{@bar}") 163 | title := (&text.Colors{text.FgHiCyan, text.Bold}).Sprint("\nUpdating MAC Address Database") 164 | 165 | fmt.Println(title) 166 | 167 | p := progress.New(100). 168 | Config(func(p *progress.Progress) { 169 | p.Format = b + " {@percent:4s}% {@message}" 170 | }). 171 | AddWidget("bar", progress.BarWidget(50, style)). 172 | AddWidget("message", progress.DynamicTextWidget(statuses)) 173 | p.Start() 174 | sqlite, err := oui.CreateSQLiteOption() 175 | if err != nil { 176 | return err 177 | } 178 | db, err := oui.New(oui.WithVersion(c.App.Version), oui.WithLogging(logger.New()), oui.WithProgress(p), sqlite) 179 | if err != nil { 180 | return err 181 | } 182 | defer db.Close() 183 | p.AdvanceTo(3) 184 | num, err := db.Populate() 185 | p.Finish() 186 | if err != nil { 187 | return err 188 | } 189 | message.NewPrinter(language.English) 190 | 191 | dur := util.TimeSince(p.StartedAt()) 192 | v := (&text.Colors{text.FgHiGreen, text.Bold}).Sprint(c.App.Version) 193 | n := (&text.Colors{text.FgHiBlue, text.Bold}).Sprint(withLocale().Sprint(num)) 194 | d := (&text.Colors{text.FgHiRed, text.Bold}).Sprint(dur) 195 | fmt.Printf("Updated MAC Address database (%s) with %s records in %s\n", v, n, d) 196 | return nil 197 | } 198 | 199 | return cmd 200 | } 201 | 202 | func ConvertCmd() *cli.Command { 203 | cmd := &cli.Command{ 204 | Name: "convert", 205 | Usage: "Convert a MAC Address to other formats", 206 | Aliases: []string{"c", "con"}, 207 | ArgsUsage: "MAC Address to Convert", 208 | } 209 | cmd.Action = func(c *cli.Context) error { 210 | i := c.Args().First() 211 | f, err := oui.Convert(i) 212 | if err != nil { 213 | return err 214 | } 215 | t := table.NewWriter() 216 | t.SetStyle(*tableStyle) 217 | h := (&text.Colors{text.FgHiMagenta, text.Bold}) 218 | t.AppendRow(table.Row{h.Sprint("Hexadecimal"), f.Hex}) 219 | t.AppendRow(table.Row{h.Sprint("Dotted"), f.Dotted}) 220 | t.AppendRow(table.Row{h.Sprint("Dashed"), f.Dashed}) 221 | t.AppendRow(table.Row{h.Sprint("Integer"), f.Int}) 222 | t.AppendRow(table.Row{h.Sprint("Bytes"), f.Bytes}) 223 | m := (&text.Colors{text.FgHiCyan, text.Bold, text.Underline}).Sprintf("%s\n", i) 224 | fmt.Println("\n " + m) 225 | fmt.Println(t.Render()) 226 | return nil 227 | } 228 | 229 | return cmd 230 | } 231 | -------------------------------------------------------------------------------- /oui/db.go: -------------------------------------------------------------------------------- 1 | package oui 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/gookit/gcli/v3/progress" 11 | "github.com/thatmattlove/go-macaddr" 12 | "github.com/thatmattlove/oui/v2/internal/util" 13 | ) 14 | 15 | type OUIDB struct { 16 | Connection *sql.DB 17 | Version string 18 | Progress *progress.Progress 19 | Logger *LoggerType 20 | useLogging bool 21 | useProgress bool 22 | dialect int 23 | } 24 | 25 | func tableExists(dialect int, conn *sql.DB, ver string) (bool, error) { 26 | q, err := conn.Prepare(fmt.Sprintf("SELECT name FROM sqlite_master WHERE type='table' AND name='%s';", ver)) 27 | if err != nil { 28 | return false, err 29 | } 30 | rs, err := q.Query() 31 | if err != nil { 32 | return false, nil 33 | } 34 | defer rs.Close() 35 | 36 | var table string 37 | rs.Next() 38 | rs.Scan(&table) 39 | if table != "" { 40 | return true, nil 41 | } 42 | return false, nil 43 | } 44 | 45 | func (ouidb *OUIDB) Clear() (err error) { 46 | err = ouidb.Connection.Ping() 47 | if err != nil { 48 | return 49 | } 50 | query, err := ouidb.Connection.Prepare(fmt.Sprintf("DELETE FROM %s", ouidb.Version)) 51 | if err != nil { 52 | return 53 | } 54 | _, err = query.Exec() 55 | return 56 | } 57 | 58 | func (ouidb *OUIDB) Delete() error { 59 | err := ouidb.Connection.Ping() 60 | if err != nil { 61 | return err 62 | } 63 | query, err := ouidb.Connection.Prepare(fmt.Sprintf("DROP TABLE %s", ouidb.Version)) 64 | if err != nil { 65 | return err 66 | } 67 | _, err = query.Exec() 68 | return err 69 | } 70 | 71 | func (ouidb *OUIDB) Insert(d *VendorDef) (res sql.Result, err error) { 72 | var q string 73 | switch ouidb.dialect { 74 | case dialectSqlite: 75 | q = fmt.Sprintf("INSERT INTO %s(prefix, length, org, registry) values(?,?,?,?)", ouidb.Version) 76 | case dialectPsql: 77 | q = fmt.Sprintf("INSERT INTO %s(prefix, length, org, registry) values($1,$2,$3,$4) ON CONFLICT (prefix, length, registry) DO UPDATE SET prefix = excluded.prefix, length = excluded.length, registry = excluded.registry", ouidb.Version) 78 | } 79 | s, err := ouidb.Connection.Prepare(q) 80 | if err != nil { 81 | return 82 | } 83 | res, err = s.Exec(d.Prefix, d.Length, d.Org, d.Registry) 84 | return 85 | } 86 | 87 | func (ouidb *OUIDB) BulkInsert(defs []*VendorDef) (int64, error) { 88 | 89 | var statement string 90 | var tmpl string 91 | var maxRecords int 92 | switch ouidb.dialect { 93 | case dialectSqlite: 94 | tmpl = "(?,?,?,?)" 95 | statement = "INSERT INTO %s(prefix, length, org, registry) values%s" 96 | maxRecords = maxVarsSqlite 97 | case dialectPsql: 98 | tmpl = "($%d,$%d,$%d,$%d)" 99 | statement = "INSERT INTO %s(prefix, length, org, registry) values%s ON CONFLICT (prefix, length, registry) DO UPDATE SET prefix = excluded.prefix, length = excluded.length, registry = excluded.registry" 100 | maxRecords = maxVarsPsql 101 | } 102 | 103 | splitDefs := util.SplitSlice(defs, maxRecords/4) 104 | inserted := int64(0) 105 | 106 | for _, split := range splitDefs { 107 | placeholders := make([]string, 0, len(split)) 108 | args := make([]interface{}, 0, len(split)*4) 109 | i := 0 110 | for _, def := range split { 111 | def := def 112 | var placeholder string 113 | switch ouidb.dialect { 114 | case dialectSqlite: 115 | placeholder = tmpl 116 | case dialectPsql: 117 | placeholder = fmt.Sprintf(tmpl, i*4+1, i*4+2, i*4+3, i*4+4) 118 | } 119 | placeholders = append(placeholders, placeholder) 120 | args = append(args, def.Prefix, def.Length, def.Org, def.Registry) 121 | i++ 122 | } 123 | q := fmt.Sprintf(statement, ouidb.Version, strings.Join(placeholders, ",")) 124 | res, err := ouidb.Connection.Exec(q, args...) 125 | if err != nil { 126 | return inserted, err 127 | } 128 | rows, err := res.RowsAffected() 129 | if err != nil { 130 | return inserted, err 131 | } 132 | inserted += rows 133 | if err != nil { 134 | return inserted, err 135 | } 136 | } 137 | return inserted, nil 138 | } 139 | 140 | func (ouidb *OUIDB) Populate() (records int64, err error) { 141 | err = ouidb.Clear() 142 | if err != nil { 143 | return 144 | } 145 | var p *progress.Progress = nil 146 | var l LoggerType = nil 147 | if ouidb.useLogging { 148 | l = *ouidb.Logger 149 | } 150 | if ouidb.useProgress { 151 | p = ouidb.Progress 152 | } 153 | defs, err := CollectAll(p, l) 154 | if err != nil { 155 | return 156 | } 157 | records, err = ouidb.BulkInsert(defs) 158 | if err != nil { 159 | return 160 | } 161 | return 162 | } 163 | 164 | func (ouidb *OUIDB) Count() (count int64, err error) { 165 | q := fmt.Sprintf("SELECT COUNT(*) FROM %s", ouidb.Version) 166 | rows, err := ouidb.Connection.Query(q) 167 | if err != nil { 168 | return 169 | } 170 | var countS string 171 | for rows.Next() { 172 | err = rows.Scan(&countS) 173 | if err != nil { 174 | return 175 | } 176 | } 177 | count, err = strconv.ParseInt(countS, 10, 64) 178 | return 179 | } 180 | 181 | func (ouidb *OUIDB) Find(search string) ([]*VendorDef, error) { 182 | mac, err := macaddr.ParseMACAddress(search) 183 | if err != nil { 184 | return nil, err 185 | } 186 | q := fmt.Sprintf("SELECT prefix,length,org,registry FROM %s WHERE prefix LIKE '%s%%'", ouidb.Version, mac.OUI()) 187 | rows, err := ouidb.Connection.Query(q) 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | defer rows.Close() 193 | 194 | errs := make([]error, 0) 195 | matches := make([]*VendorDef, 0) 196 | 197 | for rows.Next() { 198 | var prefix string 199 | var length int 200 | var org string 201 | var reg string 202 | err = rows.Scan(&prefix, &length, &org, ®) 203 | if err != nil { 204 | errs = append(errs, err) 205 | continue 206 | } 207 | def := &VendorDef{Prefix: prefix, Length: length, Org: org, Registry: reg} 208 | _, mp, err := macaddr.ParseMACPrefix(def.PrefixString()) 209 | if err != nil { 210 | errs = append(errs, err) 211 | continue 212 | } 213 | _, failure := mp.Match(search) 214 | if failure == nil { 215 | matches = append(matches, def) 216 | } 217 | } 218 | err = errors.Join(errs...) 219 | if err != nil { 220 | return nil, err 221 | } 222 | return matches, nil 223 | } 224 | 225 | func (ouidb *OUIDB) Close() error { 226 | return ouidb.Connection.Close() 227 | } 228 | 229 | func New(opts ...Option) (*OUIDB, error) { 230 | options := getOptions(opts...) 231 | err := options.Connection.Ping() 232 | if err != nil { 233 | return nil, err 234 | } 235 | if options.dialect == dialectSqlite { 236 | exists, err := tableExists(options.dialect, options.Connection, options.Version) 237 | if err != nil { 238 | return nil, err 239 | } 240 | if !exists { 241 | q := fmt.Sprintf("CREATE TABLE `%s` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `prefix` VARCHAR(32) NOT NULL, `length` INTEGER NOT NULL, `org` VARCHAR(256) NOT NULL, `registry` VARCHAR(32) NOT NULL , UNIQUE(prefix, length, registry) ON CONFLICT REPLACE )", options.Version) 242 | _, err := options.Connection.Exec(q) 243 | if err != nil { 244 | return nil, err 245 | } 246 | } 247 | } else if options.dialect == dialectPsql { 248 | q := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %v ( id SERIAL PRIMARY KEY, prefix VARCHAR(32) NOT NULL, length INT NOT NULL, org VARCHAR(256) NOT NULL, registry VARCHAR(32) NOT NULL, UNIQUE(prefix, length, registry) )", options.Version) 249 | options.Connection.SetMaxOpenConns(int(options.MaxConnections)) 250 | _, err := options.Connection.Exec(q) 251 | if err != nil { 252 | return nil, err 253 | } 254 | } else { 255 | return nil, fmt.Errorf("unknown SQL dialect") 256 | } 257 | 258 | indexQ := fmt.Sprintf("CREATE INDEX IF NOT EXISTS prefix_idx ON %v (prefix)", options.Version) 259 | _, err = options.Connection.Exec(indexQ) 260 | if err != nil { 261 | return nil, err 262 | } 263 | 264 | ouidb := &OUIDB{ 265 | Connection: options.Connection, 266 | Version: options.Version, 267 | Logger: options.Logger, 268 | Progress: options.Progress, 269 | useLogging: options.Logger != nil, 270 | useProgress: options.Progress != nil, 271 | dialect: options.dialect, 272 | } 273 | return ouidb, nil 274 | } 275 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/Xuanwo/go-locale v1.1.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg= 3 | github.com/Xuanwo/go-locale v1.1.0/go.mod h1:UKrHoZB3FPIk9wIG2/tVSobnHgNnceGSH3Y8DY5cASs= 4 | github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 5 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 6 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 8 | github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= 9 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 10 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 15 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 16 | github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= 17 | github.com/go-openapi/errors v0.20.1 h1:j23mMDtRxMwIobkpId7sWh7Ddcx4ivaoqUbfXx5P+a8= 18 | github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= 19 | github.com/go-openapi/strfmt v0.21.1 h1:G6s2t5V5kGCHLVbSdZ/6lI8Wm4OzoPFkc3/cjAsKQrM= 20 | github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= 21 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 22 | github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= 23 | github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= 24 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 25 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 26 | github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= 27 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 28 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 29 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 30 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 31 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 32 | github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= 33 | github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw= 34 | github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= 35 | github.com/gookit/gcli/v3 v3.0.1 h1:L63ZtEwB1fZhX/jplO0NJcCE2JaTrj94vLGsWevUb9s= 36 | github.com/gookit/gcli/v3 v3.0.1/go.mod h1:52ZyS8uixakow3dAG5nFbuHFQxALZtLVrtIsKekZxIA= 37 | github.com/gookit/goutil v0.3.15/go.mod h1:2w7h+/CB6n2m4qzECHj6+TOmMR8q7ierD9+LyybGy3I= 38 | github.com/gookit/goutil v0.4.3 h1:qr7PRWRYL1VGZSiNwgQcyYHGk8W2gNg2Vt/yT8zNE+Q= 39 | github.com/gookit/goutil v0.4.3/go.mod h1:qlGVh0PI+WnWSjYnIocfz/7tkeogxL6+EDNP1mRe+7o= 40 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 41 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 42 | github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= 43 | github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= 44 | github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= 45 | github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= 46 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 47 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 48 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 49 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 50 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 51 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 52 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 53 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 54 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 55 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 56 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 57 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 58 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 59 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 60 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 61 | github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 62 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 63 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 64 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 65 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 66 | github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk= 67 | github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 68 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 69 | github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 70 | github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= 71 | github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 72 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 73 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 74 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 75 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 76 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 77 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 78 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 79 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 80 | github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= 81 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 82 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 83 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 84 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 85 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= 86 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 87 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 88 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 89 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 90 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 91 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 92 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 93 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 94 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 95 | github.com/smartystreets/goconvey v1.6.7 h1:I6tZjLXD2Q1kjvNbIzB1wvQBsXmKXiVrhpRE8ZjP5jY= 96 | github.com/smartystreets/goconvey v1.6.7/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 97 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 98 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 99 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 100 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 101 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 102 | github.com/thatmattlove/go-macaddr v0.0.7 h1:Fv6KnMdyLd0xZKoiDyQhyR+jGCmfLgI6GZJGiFgP/Bk= 103 | github.com/thatmattlove/go-macaddr v0.0.7/go.mod h1:8kuuDZGjrKKv3KXckpLHEOWWfbRJ88hZ2p3u+FDUVcQ= 104 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 105 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 106 | github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= 107 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= 108 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 109 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= 110 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= 111 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= 112 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= 113 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 114 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 115 | go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= 116 | go.mongodb.org/mongo-driver v1.8.2 h1:8ssUXufb90ujcIvR6MyE1SchaNj0SFxsakiZgxIyrMk= 117 | go.mongodb.org/mongo-driver v1.8.2/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= 118 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 119 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 120 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 121 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 122 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 123 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 124 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 125 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 126 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 127 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 128 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 129 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 130 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 131 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 132 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 133 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 134 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 135 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 136 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 137 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 138 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 139 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 140 | golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 142 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 143 | golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 144 | golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 145 | golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 146 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= 147 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 148 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 149 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= 150 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 151 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 152 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 153 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 154 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 155 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 156 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 157 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 158 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 159 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 160 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs= 161 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 162 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 163 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 164 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 165 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 166 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 167 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 168 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 169 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 170 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= 171 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 172 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 173 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 174 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 175 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 176 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 177 | lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= 178 | lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= 179 | modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 180 | modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 181 | modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 182 | modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 183 | modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 184 | modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 185 | modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 186 | modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 187 | modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 188 | modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 189 | modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 190 | modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 191 | modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 192 | modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 193 | modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 194 | modernc.org/cc/v3 v3.35.22 h1:BzShpwCAP7TWzFppM4k2t03RhXhgYqaibROWkrWq7lE= 195 | modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 196 | modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= 197 | modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= 198 | modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= 199 | modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag= 200 | modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw= 201 | modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ= 202 | modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c= 203 | modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo= 204 | modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg= 205 | modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I= 206 | modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs= 207 | modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8= 208 | modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE= 209 | modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk= 210 | modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w= 211 | modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE= 212 | modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8= 213 | modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc= 214 | modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU= 215 | modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE= 216 | modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk= 217 | modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI= 218 | modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE= 219 | modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg= 220 | modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74= 221 | modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU= 222 | modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU= 223 | modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc= 224 | modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM= 225 | modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ= 226 | modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84= 227 | modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ= 228 | modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY= 229 | modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w= 230 | modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU= 231 | modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko= 232 | modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA= 233 | modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4= 234 | modernc.org/ccgo/v3 v3.14.0/go.mod h1:hBrkiBlUwvr5vV/ZH9YzXIp982jKE8Ek8tR1ytoAL6Q= 235 | modernc.org/ccgo/v3 v3.15.1 h1:bagyhO7uFlYWedkh6mfIYf8LZGYnVGPYh2FqXisaOV4= 236 | modernc.org/ccgo/v3 v3.15.1/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0= 237 | modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA= 238 | modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= 239 | modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= 240 | modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= 241 | modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= 242 | modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= 243 | modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= 244 | modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M= 245 | modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU= 246 | modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE= 247 | modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso= 248 | modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8= 249 | modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8= 250 | modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I= 251 | modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk= 252 | modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY= 253 | modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE= 254 | modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg= 255 | modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM= 256 | modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg= 257 | modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo= 258 | modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8= 259 | modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ= 260 | modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA= 261 | modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM= 262 | modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg= 263 | modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE= 264 | modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM= 265 | modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU= 266 | modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw= 267 | modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M= 268 | modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18= 269 | modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8= 270 | modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= 271 | modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0= 272 | modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI= 273 | modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE= 274 | modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY= 275 | modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ= 276 | modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c= 277 | modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI= 278 | modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ= 279 | modernc.org/libc v1.13.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk= 280 | modernc.org/libc v1.13.2/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk= 281 | modernc.org/libc v1.14.1 h1:rwx9uVJU/fEmsmV5ECGRwdAiXgUm6k6tsFA+L8kQb6E= 282 | modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk= 283 | modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 284 | modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 285 | modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 286 | modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= 287 | modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 288 | modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= 289 | modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14= 290 | modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= 291 | modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= 292 | modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 293 | modernc.org/sqlite v1.14.5 h1:bYrrjwH9Y7QUGk1MbchZDhRfmpGuEAs/D45sVjNbfvs= 294 | modernc.org/sqlite v1.14.5/go.mod h1:YyX5Rx0WbXokitdWl2GJIDy4BrPxBP0PwwhpXOHCDLE= 295 | modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= 296 | modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= 297 | modernc.org/tcl v1.10.0 h1:vux2MNFhSXYqD8Kq4Uc9RjWcgv2c7Atx3da3VpLPPEw= 298 | modernc.org/tcl v1.10.0/go.mod h1:WzWapmP/7dHVhFoyPpEaNSVTL8xtewhouN/cqSJ5A2s= 299 | modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= 300 | modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 301 | modernc.org/z v1.2.21/go.mod h1:uXrObx4pGqXWIMliC5MiKuwAyMrltzwpteOFUP1PWCc= 302 | modernc.org/z v1.3.0 h1:4RWULo1Nvaq5ZBhbLe74u8p6tV4Mmm0ZrPBXYPm/xjM= 303 | modernc.org/z v1.3.0/go.mod h1:+mvgLH814oDjtATDdT3rs84JnUIpkvAF5B8AVkNlE2g= 304 | --------------------------------------------------------------------------------