-
Notifications
You must be signed in to change notification settings - Fork 0
start with IPAM DSL grammar #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| version: 2 | ||
| updates: | ||
| - package-ecosystem: "gomod" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "weekly" | ||
| day: "monday" | ||
| time: "04:00" | ||
| timezone: "UTC" | ||
| open-pull-requests-limit: 5 | ||
| labels: ["dependencies", "gomod"] | ||
| commit-message: | ||
| prefix: "chore(deps)" | ||
| groups: | ||
| minor-and-patch: | ||
| update-types: ["minor", "patch"] | ||
|
|
||
| - package-ecosystem: "github-actions" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "weekly" | ||
| day: "monday" | ||
| time: "04:00" | ||
| timezone: "UTC" | ||
| open-pull-requests-limit: 5 | ||
| labels: ["dependencies", "github-actions"] | ||
| commit-message: | ||
| prefix: "chore(actions)" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| name: CI | ||
| on: | ||
| push: | ||
| branches: ["main", "devel"] | ||
| paths: &paths | ||
| - 'go.mod' | ||
| - '**.go' | ||
| - '**.yml' | ||
| - '**.yaml' | ||
| pull_request: | ||
| branches: ["main", "devel"] | ||
| paths: *paths | ||
|
|
||
| # Best Practice: Restrict global permissions to minimum required (Read-Only) | ||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| test: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v5 | ||
| with: | ||
| # Disable credential persisting for enhanced security | ||
| persist-credentials: false | ||
|
|
||
| - uses: actions/setup-go@v6 | ||
| with: | ||
| go-version-file: go.mod | ||
| cache: true # Enable build caching for faster test execution | ||
|
|
||
| - name: Run Tests | ||
| run: go test -v ./... | ||
|
|
||
| linting: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v5 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: actions/setup-go@v6 | ||
| with: | ||
| go-version-file: go.mod | ||
| cache: false # golangci-lint-action handles its own caching | ||
|
|
||
| - name: golangci-lint | ||
| uses: golangci/golangci-lint-action@v9 | ||
| with: | ||
| # Use 'latest' to prevent version mismatch with go.mod | ||
| version: latest | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| version: '2' | ||
| run: | ||
| timeout: 5m | ||
| linters: | ||
| enable: | ||
| - decorder | ||
| - errorlint | ||
| - gocyclo | ||
| - govet | ||
| - gosec | ||
| - gosmopolitan | ||
| - intrange | ||
| - misspell | ||
| - predeclared | ||
| - reassign | ||
| - staticcheck | ||
| - unconvert | ||
| - unparam | ||
| - usestdlibvars | ||
| - wastedassign | ||
| - copyloopvar | ||
| - tparallel | ||
| - gochecknoglobals | ||
| - nolintlint | ||
| - modernize | ||
| - gocritic | ||
| settings: | ||
| govet: | ||
| enable: | ||
| - shadow | ||
| gocyclo: | ||
| min-complexity: 30 | ||
| exclusions: | ||
| generated: lax | ||
| rules: | ||
| - linters: | ||
| - gochecknoglobals | ||
| - gosec | ||
| - gocyclo | ||
| path: _test\.go | ||
| presets: | ||
| - comments | ||
| - common-false-positives | ||
| - legacy | ||
| - std-error-handling | ||
| paths: | ||
| - third_party$ | ||
| - builtin$ | ||
| - examples$ | ||
|
|
||
| formatters: | ||
| enable: | ||
| - gofmt | ||
| - goimports | ||
| exclusions: | ||
| generated: lax | ||
| paths: | ||
| - third_party$ | ||
| - builtin$ | ||
| - examples$ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| module github.com/gaissmai/ipam | ||
|
|
||
| go 1.26 | ||
|
|
||
| require github.com/gaissmai/grammar v0.3.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| github.com/gaissmai/grammar v0.3.0 h1:O90o3Fe5Vaw/3044pip3UEekN4RSl8mExKMKv2WuuGM= | ||
| github.com/gaissmai/grammar v0.3.0/go.mod h1:iAnAryYv9vmVajL9t7zKDnWjeySd8LrTacEwOMbnlvk= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| // Package grammar defines patterns and rules for IPAM domain specific language. | ||
| package grammar | ||
|
|
||
| import ( | ||
| "regexp" | ||
|
|
||
| "github.com/gaissmai/grammar" | ||
| ) | ||
|
|
||
| // g is ready to use after init(). | ||
| // | ||
| //nolint:gochecknoglobals | ||
| var g *grammar.Grammar | ||
|
|
||
| // init compiles the grammar rules. | ||
| func init() { | ||
| g = grammar.New("IPAM") | ||
|
|
||
| for name, rawPattern := range AllRules() { | ||
| if err := g.Add(name, rawPattern); err != nil { | ||
| panic(err) | ||
| } | ||
| } | ||
|
|
||
| if err := g.Compile(); err != nil { | ||
| panic(err) | ||
| } | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // MustRx returns the regexp for rule from the underlying grammar. | ||
| func MustRx(name string) *regexp.Regexp { | ||
| rx, err := g.Rx(name) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| return rx | ||
| } | ||
|
|
||
| // AllRules to build the IPAM tokens. | ||
| func AllRules() map[string]string { | ||
| return map[string]string{ | ||
| "label": label, | ||
| "word": word, | ||
| "cswords": cswords, | ||
| "fqdn": fqdn, | ||
| "ident": ident, | ||
| "comment": comment, | ||
| "scoping": scoping, | ||
| "ip": ip, | ||
| "ipv4": ipv4, | ||
| "ipv6": ipv6, | ||
| } | ||
| } | ||
|
|
||
| // ########################################################################### | ||
| // IPAM GRAMMAR | ||
| // slimmed down version, just some helper regexps for the lexer state machine | ||
| // ########################################################################### | ||
|
|
||
| const ( | ||
|
|
||
| // # comment til EOL | ||
| comment = `^ # .* $` | ||
|
|
||
| scoping = `^ --- -* $` | ||
|
|
||
| // label is a field of a FQDN | ||
| label = `(?: [- \w]+ )` // hyphen and word chars, the rest is done by the parser | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
|
|
||
| // word | ||
| word = `\w [-\w]*` | ||
|
|
||
| // comma-separated words | ||
| cswords = `^ ${word} (?: , ${word} )* $` | ||
|
|
||
| // full qualified domain name, com com. www.google.com. *.foo.bar | ||
| // the length restriction parsing etc. is done by the parser | ||
| // IDNA not supported, just plain ASCII | ||
| fqdn = `^ | ||
| (?: | ||
| (?: \*\. )? // alias can start with wildcard followed by dot | ||
| (?: ${label} \. )+ // one or more (label followed by dot) | ||
| ${label}? [a-zA-Z]+ // TLD ends with at least one ASCII letter | ||
| \.? // last dot optional | ||
| $)` | ||
|
|
||
| // keywords, attributes and flags are uppercase | ||
| ident = `^ (?: [A-Z] [A-Z0-9_]+ )` // missing $ is intended | ||
|
|
||
| // simple and fast patterns for lexing, true parsing with netip.ParseAddr() | ||
| ip = `^ (?: ${ipv4} | ${ipv6} ) ` // missing $ is intended | ||
|
|
||
| ipv4 = `^ (?: | ||
| \d{1,3} \. | ||
| \d{1,3} \. | ||
| \d{1,3} \. | ||
| \d{1,3} | ||
| )` // missing $ is intended | ||
|
|
||
| ipv6 = `^ (?: // xdigits and colons, no dot | ||
| [[:xdigit:] :]+ : | ||
| [[:xdigit:] :]* | ||
| )` // missing $ is intended | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| package grammar_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| g "github.com/gaissmai/ipam/internal/grammar" | ||
| ) | ||
|
|
||
| func TestGrammar(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| for name := range g.AllRules() { | ||
| rx := g.MustRx(name) | ||
| t.Log(name, rx) | ||
| } | ||
| } | ||
|
|
||
| func TestGrammarRules(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| tt := []struct { | ||
| rule string | ||
| input string | ||
| want bool | ||
| }{ | ||
| // ── word ──────────────────────────────────────────────────────────── | ||
| {rule: "word", input: "hello", want: true}, | ||
| {rule: "word", input: "with-hyphen", want: true}, | ||
| {rule: "word", input: "word123", want: true}, | ||
| {rule: "word", input: "_underscore", want: true}, | ||
| {rule: "word", input: "", want: false}, | ||
|
|
||
| // ── cswords ───────────────────────────────────────────────────────── | ||
| {rule: "cswords", input: "single", want: true}, | ||
| {rule: "cswords", input: "std,open", want: true}, | ||
| {rule: "cswords", input: "a,b-c,d", want: true}, | ||
| {rule: "cswords", input: ",leading,comma", want: false}, | ||
| {rule: "cswords", input: "trailing,comma,", want: false}, | ||
| {rule: "cswords", input: "a,,b", want: false}, // double comma | ||
| {rule: "cswords", input: "-starts,with,hyphen", want: false}, | ||
| {rule: "cswords", input: "with space", want: false}, // spaces not allowed | ||
|
|
||
| // ── comment ───────────────────────────────────────────────────────── | ||
| {rule: "comment", input: "# this is a comment", want: true}, | ||
| {rule: "comment", input: "#", want: true}, | ||
| {rule: "comment", input: "# ", want: true}, | ||
| {rule: "comment", input: "not a comment", want: false}, | ||
| {rule: "comment", input: "", want: false}, | ||
| {rule: "comment", input: " # indented", want: false}, // ^ anchors at start | ||
|
|
||
| // ── scoping ───────────────────────────────────────────────────────── | ||
| {rule: "scoping", input: "---", want: true}, | ||
| {rule: "scoping", input: "----", want: true}, | ||
| {rule: "scoping", input: "----------", want: true}, | ||
| {rule: "scoping", input: "--", want: false}, // minimum three dashes | ||
| {rule: "scoping", input: "- -", want: false}, // no spaces | ||
| {rule: "scoping", input: "--- x", want: false}, // nothing after dashes | ||
|
|
||
| // ── fqdn ──────────────────────────────────────────────────────────── | ||
| {rule: "fqdn", input: "www.example.com", want: true}, | ||
| {rule: "fqdn", input: "www.example.com.", want: true}, // trailing dot allowed | ||
| {rule: "fqdn", input: "*.foo.bar", want: true}, // wildcard prefix | ||
| {rule: "fqdn", input: "example.com", want: true}, | ||
| {rule: "fqdn", input: "a.b.c.d.e.tld", want: true}, | ||
| {rule: "fqdn", input: "localhost", want: false}, // bare hostname, no dot | ||
| {rule: "fqdn", input: "192.168.1.1", want: false}, // TLD must end with letter | ||
| {rule: "fqdn", input: "", want: false}, | ||
| {rule: "fqdn", input: "foo bar.com", want: false}, // spaces not allowed in label | ||
|
|
||
| // ── ident (no trailing $, anchored only at start) ─────────────────── | ||
| {rule: "ident", input: "VLAN", want: true}, | ||
| {rule: "ident", input: "VRF", want: true}, | ||
| {rule: "ident", input: "STATUS_OK", want: true}, | ||
| {rule: "ident", input: "VLAN10", want: true}, | ||
| {rule: "ident", input: "A9", want: true}, | ||
| {rule: "ident", input: "A", want: false}, // [A-Z0-9_]+ needs ≥1 more char | ||
| {rule: "ident", input: "lowercase", want: false}, | ||
| {rule: "ident", input: "Mixed", want: false}, | ||
| {rule: "ident", input: "1NVALID", want: false}, // must start with A-Z | ||
|
|
||
| // ── ip (no trailing $, anchored only at start) ─────────────────────── | ||
| {rule: "ip", input: "192.168.1.1", want: true}, | ||
| {rule: "ip", input: "192.168.1.1/24", want: true}, // prefix is fine, no trailing $ | ||
| {rule: "ip", input: "10.0.0.1", want: true}, | ||
| {rule: "ip", input: "2001:db8::1", want: true}, | ||
| {rule: "ip", input: "::1", want: true}, | ||
| {rule: "ip", input: "not-an-ip", want: false}, | ||
| {rule: "ip", input: "", want: false}, | ||
|
|
||
| // ── ipv4 (no trailing $) ───────────────────────────────────────────── | ||
| {rule: "ipv4", input: "192.168.1.1", want: true}, | ||
| {rule: "ipv4", input: "0.0.0.0", want: true}, | ||
| {rule: "ipv4", input: "255.255.255.255", want: true}, | ||
| {rule: "ipv4", input: "192.168.1.1/24", want: true}, // CIDR — no trailing $ intended | ||
| {rule: "ipv4", input: "2001:db8::1", want: false}, // IPv6 must not match ipv4 | ||
| {rule: "ipv4", input: "1.2.3", want: false}, // only three octets | ||
| {rule: "ipv4", input: "not-an-ip", want: false}, | ||
|
|
||
| // ── ipv6 (no trailing $) ───────────────────────────────────────────── | ||
| {rule: "ipv6", input: "2001:db8::1 foo", want: true}, | ||
| {rule: "ipv6", input: "::1", want: true}, | ||
| {rule: "ipv6", input: "fe80::1", want: true}, | ||
| {rule: "ipv6", input: "2001:db8::1/64", want: true}, // prefix — no trailing $ intended | ||
| {rule: "ipv6", input: "192.168.1.1", want: false}, // pure dotted-decimal has no colon | ||
| {rule: "ipv6", input: "not-an-ip", want: false}, | ||
| } | ||
|
|
||
| for _, tc := range tt { | ||
| t.Run(tc.rule+"/"+tc.input, func(t *testing.T) { | ||
| t.Parallel() | ||
| got := g.MustRx(tc.rule).MatchString(tc.input) | ||
| if got != tc.want { | ||
| t.Fatalf("rule=%q input=%q: got %v, want %v", tc.rule, tc.input, got, tc.want) | ||
| } | ||
| }) | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.