From 18b4f4e49206d7cdc2b8182cb05a43cc79e75d83 Mon Sep 17 00:00:00 2001 From: Mikhail Knyazhev Date: Thu, 17 Apr 2025 00:26:33 +0300 Subject: [PATCH] golang generate --- .github/dependabot.yml | 7 + .github/workflows/ci.yml | 27 +++ .gitignore | 29 ++- .golangci.yml | 211 +++++++++++++++++++++ .lic.yaml | 3 + LICENSE | 2 +- Makefile | 31 +++ go.mod | 3 + go.sum | 0 golang/config.go | 39 ++++ golang/render.go | 29 +++ golang/stmt.go | 352 +++++++++++++++++++++++++++++++++++ golang/stmt_create.go | 64 +++++++ golang/stmt_test.go | 44 +++++ internal/config/config.go | 16 ++ internal/gen/render.go | 75 ++++++++ internal/gen/types.go | 12 ++ internal/models/block.go | 50 +++++ internal/models/comment.go | 28 +++ internal/models/keyword.go | 28 +++ internal/models/letter.go | 20 ++ internal/models/operation.go | 26 +++ internal/models/text.go | 21 +++ types/token.go | 12 ++ 24 files changed, 1111 insertions(+), 18 deletions(-) create mode 100755 .github/dependabot.yml create mode 100755 .github/workflows/ci.yml mode change 100644 => 100755 .gitignore create mode 100755 .golangci.yml create mode 100644 .lic.yaml create mode 100755 Makefile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 golang/config.go create mode 100644 golang/render.go create mode 100644 golang/stmt.go create mode 100644 golang/stmt_create.go create mode 100644 golang/stmt_test.go create mode 100644 internal/config/config.go create mode 100644 internal/gen/render.go create mode 100644 internal/gen/types.go create mode 100644 internal/models/block.go create mode 100644 internal/models/comment.go create mode 100644 internal/models/keyword.go create mode 100644 internal/models/letter.go create mode 100644 internal/models/operation.go create mode 100644 internal/models/text.go create mode 100644 types/token.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100755 index 0000000..da0407c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100755 index 0000000..bea49eb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ + +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + go: [ '1.23.6' ] + steps: + - uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go }} + + - name: Run CI + env: + COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + run: make ci diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 6f72f89..02a3c9f --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,20 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins +.tools/ +bin/ +vendor/ +.idea/ +.vscode/ +coverage.txt +coverage.out *.exe *.exe~ *.dll *.so *.dylib - -# Test binary, built with `go test -c` +*.db +*.db-journal +*.mmdb *.test - -# Output of the go coverage tool, specifically when used with LiteIDE *.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work -go.work.sum - -# env file .env + +build/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100755 index 0000000..c564621 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,211 @@ + +run: + go: "1.22" + concurrency: 4 + timeout: 5m + tests: false + issues-exit-code: 1 + modules-download-mode: readonly + +issues: + exclude-use-default: false + max-issues-per-linter: 100 + max-same-issues: 4 + new: false + exclude-files: + - ".+_test.go" + exclude-dirs: + - "vendor$" + +output: + formats: + - format: line-number + sort-results: true + +linters-settings: + govet: + check-shadowing: true + enable: + - asmdecl + - assign + - atomic + - atomicalign + - bools + - buildtag + - cgocall + - composites + - copylocks + - deepequalerrors + - errorsas + - findcall + - framepointer + - httpresponse + - ifaceassert + - loopclosure + - lostcancel + - nilfunc + - nilness + - printf + - reflectvaluecompare + - shadow + - shift + - sigchanyzer + - sortslice + - stdmethods + - stringintconv + - structtag + - testinggoroutine + - tests + - unmarshal + - unreachable + - unsafeptr + - unusedresult + - unusedwrite + disable: + - fieldalignment + gofmt: + simplify: true + errcheck: + check-type-assertions: true + check-blank: true + gocyclo: + min-complexity: 30 + misspell: + locale: US + prealloc: + simple: true + range-loops: true + for-loops: true + unparam: + check-exported: false + gci: + skip-generated: true + custom-order: false + gosec: + includes: + - G101 # Look for hard coded credentials + - G102 # Bind to all interfaces + - G103 # Audit the use of unsafe block + - G104 # Audit errors not checked + - G106 # Audit the use of ssh.InsecureIgnoreHostKey + - G107 # Url provided to HTTP request as taint input + - G108 # Profiling endpoint automatically exposed on /debug/pprof + - G109 # Potential Integer overflow made by strconv.Atoi result conversion to int16/32 + - G110 # Potential DoS vulnerability via decompression bomb + - G111 # Potential directory traversal + - G112 # Potential slowloris attack + - G113 # Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772) + - G114 # Use of net/http serve function that has no support for setting timeouts + - G201 # SQL query construction using format string + - G202 # SQL query construction using string concatenation + - G203 # Use of unescaped data in HTML templates + - G204 # Audit use of command execution + - G301 # Poor file permissions used when creating a directory + - G302 # Poor file permissions used with chmod + - G303 # Creating tempfile using a predictable path + - G304 # File path provided as taint input + - G305 # File traversal when extracting zip/tar archive + - G306 # Poor file permissions used when writing to a new file + - G307 # Deferring a method which returns an error + - G401 # Detect the usage of DES, RC4, MD5 or SHA1 + - G402 # Look for bad TLS connection settings + - G403 # Ensure minimum RSA key length of 2048 bits + - G404 # Insecure random number source (rand) + - G501 # Import blocklist: crypto/md5 + - G502 # Import blocklist: crypto/des + - G503 # Import blocklist: crypto/rc4 + - G504 # Import blocklist: net/http/cgi + - G505 # Import blocklist: crypto/sha1 + - G601 # Implicit memory aliasing of items from a range statement + excludes: + - G101 # Look for hard coded credentials + - G102 # Bind to all interfaces + - G103 # Audit the use of unsafe block + - G104 # Audit errors not checked + - G106 # Audit the use of ssh.InsecureIgnoreHostKey + - G107 # Url provided to HTTP request as taint input + - G108 # Profiling endpoint automatically exposed on /debug/pprof + - G109 # Potential Integer overflow made by strconv.Atoi result conversion to int16/32 + - G110 # Potential DoS vulnerability via decompression bomb + - G111 # Potential directory traversal + - G112 # Potential slowloris attack + - G113 # Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772) + - G114 # Use of net/http serve function that has no support for setting timeouts + - G201 # SQL query construction using format string + - G202 # SQL query construction using string concatenation + - G203 # Use of unescaped data in HTML templates + - G204 # Audit use of command execution + - G301 # Poor file permissions used when creating a directory + - G302 # Poor file permissions used with chmod + - G303 # Creating tempfile using a predictable path + - G304 # File path provided as taint input + - G305 # File traversal when extracting zip/tar archive + - G306 # Poor file permissions used when writing to a new file + - G307 # Deferring a method which returns an error + - G401 # Detect the usage of DES, RC4, MD5 or SHA1 + - G402 # Look for bad TLS connection settings + - G403 # Ensure minimum RSA key length of 2048 bits + - G404 # Insecure random number source (rand) + - G501 # Import blocklist: crypto/md5 + - G502 # Import blocklist: crypto/des + - G503 # Import blocklist: crypto/rc4 + - G504 # Import blocklist: net/http/cgi + - G505 # Import blocklist: crypto/sha1 + - G601 # Implicit memory aliasing of items from a range statement + exclude-generated: true + severity: medium + confidence: medium + concurrency: 12 + config: + global: + nosec: true + "#nosec": "#my-custom-nosec" + show-ignored: true + audit: true + G101: + pattern: "(?i)passwd|pass|password|pwd|secret|token|pw|apiKey|bearer|cred" + ignore_entropy: false + entropy_threshold: "80.0" + per_char_threshold: "3.0" + truncate: "32" + G104: + fmt: + - Fscanf + G111: + pattern: "http\\.Dir\\(\"\\/\"\\)|http\\.Dir\\('\\/'\\)" + G301: "0750" + G302: "0600" + G306: "0600" + + lll: + line-length: 130 + tab-width: 1 + staticcheck: + go: "1.15" + # SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks + # Default: ["*"] + checks: [ "*", "-SA1019" ] + +linters: + disable-all: true + enable: + - govet + - gofmt + - errcheck + - misspell + - gocyclo + - ineffassign + - goimports + - nakedret + - unparam + - unused + - prealloc + - durationcheck + - staticcheck + - makezero + - nilerr + - errorlint + - bodyclose + - gosec + - lll + fast: false diff --git a/.lic.yaml b/.lic.yaml new file mode 100644 index 0000000..737072f --- /dev/null +++ b/.lic.yaml @@ -0,0 +1,3 @@ +author: Mikhail Knyazhev +lic_short: "BSD 3-Clause" +lic_file: LICENSE diff --git a/LICENSE b/LICENSE index d63bc87..9c7d996 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2025, The OSSPkg Team +Copyright (c) 2025, Mikhail Knyazhev Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..03d4159 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ + +SHELL=/bin/bash + + +.PHONY: install +install: + go install go.osspkg.com/goppy/v2/cmd/goppy@latest + goppy setup-lib + +.PHONY: lint +lint: + goppy lint + +.PHONY: license +license: + goppy license + +.PHONY: build +build: + goppy build --arch=amd64 + +.PHONY: tests +tests: + goppy test + +.PHONY: pre-commit +pre-commit: install license lint tests build + +.PHONY: ci +ci: pre-commit + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..035c4a9 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module go.osspkg.com/gogen + +go 1.23.6 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/golang/config.go b/golang/config.go new file mode 100644 index 0000000..474602e --- /dev/null +++ b/golang/config.go @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package golang + +import ( + config2 "go.osspkg.com/gogen/internal/config" +) + +var _ config2.Config = (*config)(nil) + +type config struct{} + +func (c config) OperationAvailable(op string) bool { + switch op { + case "+", "-", "*", "/", "%", "&", "|", "^", "<<", ">>", "&^", "+=", "-=", "*=", "/=", "%=", + "&=", "|=", "^=", "<<=", ">>=", "&^=", "&&", "||", "<-", "++", "--", "==", "<", ">", "=", "!", "~", "!=", + "<=", ">=", ":=", "...", "(", ")", "[", "]", "{", "}", ",", ".", ";", ":": + return true + default: + return false + } +} + +func (config) CommentSingle() config2.OpenClose { + return config2.OpenClose{ + Open: "//", + Close: "\n", + } +} + +func (config) CommentMulti() config2.OpenClose { + return config2.OpenClose{ + Open: "/*\n", + Close: "\n*/\n", + } +} diff --git a/golang/render.go b/golang/render.go new file mode 100644 index 0000000..2a8be9a --- /dev/null +++ b/golang/render.go @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package golang + +import ( + "bytes" + "go/format" + "io" + + "go.osspkg.com/gogen/types" +) + +func Render(w io.Writer, arg types.Token) error { + buf := bytes.NewBuffer(nil) + if err := arg.Render(buf); err != nil { + return err + } + b, err := format.Source(buf.Bytes()) + if err != nil { + return err + } + if _, err = w.Write(b); err != nil { + return err + } + return nil +} diff --git a/golang/stmt.go b/golang/stmt.go new file mode 100644 index 0000000..481a6c3 --- /dev/null +++ b/golang/stmt.go @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package golang + +import ( + "io" + + "go.osspkg.com/gogen/internal/gen" + "go.osspkg.com/gogen/internal/models" + "go.osspkg.com/gogen/types" +) + +type Tokens []types.Token + +func newStmt() *Tokens { + return &Tokens{} +} + +func (v *Tokens) Render(w io.Writer) error { + for _, token := range *v { + if err := gen.Render(w, token); err != nil { + return err + } + } + return nil +} + +func (v *Tokens) Unwrap() []types.Token { + return *v +} + +func (v *Tokens) _add(args ...types.Token) *Tokens { + t := Tokens(args) + *v = append(*v, &t) + return v +} + +func (v *Tokens) Add(args ...types.Token) *Tokens { + *v = append(*v, args...) + return v +} + +///////////////////////////////////////////////////////////////// + +func (v *Tokens) Package(arg string) *Tokens { + return v._add( + &models.Keyword{D: "package"}, + &models.Keyword{D: arg}, + ) +} + +func (v *Tokens) Import(name, module string) *Tokens { + return v._add( + &models.Keyword{D: "import"}, + &models.Keyword{D: name, Verify: true}, + &models.Text{D: module}, + ) +} + +///////////////////////////////////////////////////////////////// + +func (v *Tokens) Func() *Tokens { + *v = append(*v, &models.Keyword{D: "func"}) + return v +} + +func (v *Tokens) Return() *Tokens { + *v = append(*v, &models.Keyword{D: "return"}) + return v +} + +func (v *Tokens) Params(args ...types.Token) *Tokens { + *v = append(*v, &models.Params{D: args, Brace: true}) + return v +} + +func (v *Tokens) List(args ...types.Token) *Tokens { + *v = append(*v, &models.Params{D: args, Brace: false}) + return v +} + +func (v *Tokens) Call(args ...types.Token) *Tokens { + return v.Params(args...) +} + +func (v *Tokens) Block(args ...types.Token) *Tokens { + *v = append(*v, &models.Block{D: args}) + return v +} + +///////////////////////////////////////////////////////////////// + +func (v *Tokens) Comment(arg string) *Tokens { + *v = append(*v, &models.Comment[config]{D: arg}) + return v +} + +func (v *Tokens) Line() *Tokens { + *v = append(*v, &models.Letter{D: "\n"}) + return v +} + +func (v *Tokens) Text(arg string) *Tokens { + *v = append(*v, &models.Text{D: arg}) + return v +} + +func (v *Tokens) Id(arg string) *Tokens { + *v = append(*v, &models.Keyword{D: arg, Verify: true}) + return v +} + +func (v *Tokens) Nil() *Tokens { + *v = append(*v, &models.Keyword{D: "nil"}) + return v +} + +func (v *Tokens) Type() *Tokens { + *v = append(*v, &models.Keyword{D: "type"}) + return v +} + +func (v *Tokens) Var() *Tokens { + *v = append(*v, &models.Keyword{D: "var"}) + return v +} + +func (v *Tokens) Const() *Tokens { + *v = append(*v, &models.Keyword{D: "const"}) + return v +} + +///////////////////////////////////////////////////////////////// + +func (v *Tokens) Op(arg string) *Tokens { + *v = append(*v, &models.Operation[config]{D: arg}) + return v +} + +///////////////////////////////////////////////////////////////// + +func (v *Tokens) If() *Tokens { + *v = append(*v, &models.Keyword{D: "if"}) + return v +} + +func (v *Tokens) Else() *Tokens { + *v = append(*v, &models.Keyword{D: "else"}) + return v +} + +func (v *Tokens) Default() *Tokens { + *v = append(*v, &models.Keyword{D: "default"}) + return v +} + +func (v *Tokens) Fallthrough() *Tokens { + *v = append(*v, &models.Keyword{D: "fallthrough"}) + return v +} + +func (v *Tokens) Break() *Tokens { + *v = append(*v, &models.Keyword{D: "break"}) + return v +} + +func (v *Tokens) Case() *Tokens { + *v = append(*v, &models.Keyword{D: "case"}) + return v +} + +func (v *Tokens) Continue() *Tokens { + *v = append(*v, &models.Keyword{D: "continue"}) + return v +} + +func (v *Tokens) Goto() *Tokens { + *v = append(*v, &models.Keyword{D: "goto"}) + return v +} + +///////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////// + +func (v *Tokens) Struct() *Tokens { + *v = append(*v, &models.Keyword{D: "struct"}) + return v +} + +func (v *Tokens) Interface() *Tokens { + *v = append(*v, &models.Keyword{D: "interface"}) + return v +} + +func (v *Tokens) Any() *Tokens { + *v = append(*v, &models.Keyword{D: "any"}) + return v +} + +func (v *Tokens) Chan() *Tokens { + *v = append(*v, &models.Keyword{D: "chan"}) + return v +} + +func (v *Tokens) Uint8() *Tokens { + *v = append(*v, &models.Keyword{D: "uint8"}) + return v +} + +func (v *Tokens) Uint16() *Tokens { + *v = append(*v, &models.Keyword{D: "uint16"}) + return v +} + +func (v *Tokens) Uint32() *Tokens { + *v = append(*v, &models.Keyword{D: "uint32"}) + return v +} + +func (v *Tokens) Uint64() *Tokens { + *v = append(*v, &models.Keyword{D: "uint64"}) + return v +} + +func (v *Tokens) Int8() *Tokens { + *v = append(*v, &models.Keyword{D: "int8"}) + return v +} + +func (v *Tokens) Int16() *Tokens { + *v = append(*v, &models.Keyword{D: "int16"}) + return v +} + +func (v *Tokens) Int32() *Tokens { + *v = append(*v, &models.Keyword{D: "int32"}) + return v +} + +func (v *Tokens) Int64() *Tokens { + *v = append(*v, &models.Keyword{D: "int64"}) + return v +} + +func (v *Tokens) Float32() *Tokens { + *v = append(*v, &models.Keyword{D: "float32"}) + return v +} + +func (v *Tokens) Float64() *Tokens { + *v = append(*v, &models.Keyword{D: "float64"}) + return v +} + +func (v *Tokens) Complex64() *Tokens { + *v = append(*v, &models.Keyword{D: "complex64"}) + return v +} + +func (v *Tokens) Complex128() *Tokens { + *v = append(*v, &models.Keyword{D: "complex128"}) + return v +} + +func (v *Tokens) Byte() *Tokens { + *v = append(*v, &models.Keyword{D: "byte"}) + return v +} + +func (v *Tokens) Rune() *Tokens { + *v = append(*v, &models.Keyword{D: "rune"}) + return v +} + +func (v *Tokens) Uint() *Tokens { + *v = append(*v, &models.Keyword{D: "uint"}) + return v +} + +func (v *Tokens) Int() *Tokens { + *v = append(*v, &models.Keyword{D: "int"}) + return v +} + +func (v *Tokens) Uintptr() *Tokens { + *v = append(*v, &models.Keyword{D: "uintptr"}) + return v +} + +func (v *Tokens) String() *Tokens { + *v = append(*v, &models.Keyword{D: "string"}) + return v +} + +func (v *Tokens) Bool() *Tokens { + *v = append(*v, &models.Keyword{D: "bool"}) + return v +} + +func (v *Tokens) Error() *Tokens { + *v = append(*v, &models.Keyword{D: "error"}) + return v +} + +///////////////////////////////////////////////////////////////// + +// +//func (v *Tokens) For() *Tokens { +// *v = append(*v, &models.Keyword{D: "for"}) +// return v +//} + +func (v *Tokens) __defer() *Tokens { + *v = append(*v, &models.Keyword{D: "defer"}) + return v +} + +func (v *Tokens) __go() *Tokens { + *v = append(*v, &models.Keyword{D: "go"}) + return v +} + +// +//func (v *Tokens) Map() *Tokens { +// *v = append(*v, &models.Keyword{D: "map"}) +// return v +//} + +// +//func (v *Tokens) Range() *Tokens { +// *v = append(*v, &models.Keyword{D: "range"}) +// return v +//} + +//func (v *Tokens) Select() *Tokens { +// *v = append(*v, &models.Keyword{D: "select"}) +// return v +//} + +//func (v *Tokens) Switch() *Tokens { +// *v = append(*v, &models.Keyword{D: "switch"}) +// return v +//} + +//func (v *Tokens) Array(size int) *Tokens { +// *v = append(*v, &models.Keyword{D: "bool"}) +// return v +//} diff --git a/golang/stmt_create.go b/golang/stmt_create.go new file mode 100644 index 0000000..759a2eb --- /dev/null +++ b/golang/stmt_create.go @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package golang + +import "go.osspkg.com/gogen/types" + +func Comment(arg string) *Tokens { + return newStmt().Comment(arg) +} + +func Line() *Tokens { + return newStmt().Line() +} + +func Return() *Tokens { + return newStmt().Return() +} + +func Nil() *Tokens { + return newStmt().Nil() +} + +func Var() *Tokens { + return newStmt().Var() +} + +func Text(arg string) *Tokens { + return newStmt().Text(arg) +} + +func Id(arg string) *Tokens { + return newStmt().Id(arg) +} + +func Package(arg string) *Tokens { + return newStmt().Package(arg) +} + +func Import(name, module string) *Tokens { + return newStmt().Import(name, module) +} + +func Func() *Tokens { + return newStmt().Func() +} + +func Params(args ...types.Token) *Tokens { + return newStmt().Params(args...) +} + +func List(args ...types.Token) *Tokens { + return newStmt().List(args...) +} + +func Defer() *Tokens { + return newStmt().__defer() +} + +func Go() *Tokens { + return newStmt().__go() +} diff --git a/golang/stmt_test.go b/golang/stmt_test.go new file mode 100644 index 0000000..a3fae5f --- /dev/null +++ b/golang/stmt_test.go @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package golang_test + +import ( + "bytes" + "fmt" + "testing" + + . "go.osspkg.com/gogen/golang" +) + +func Test_newStmt(t *testing.T) { + buf := bytes.NewBuffer(nil) + + stmt := Comment("Autogenerate code"). + Package("main"). + Add( + Import("fmt", "fmt"). + Import("bytes", "bytes"), + Comment("nolint:errcheck"). + Func().Id("main"). + Params(Id("id").Int64(), Id("name").String()). + Params(Id("err").Error()). + Block( + List(Id("a"), Id("b")).Op(":=").List(Text("123"), Text("321")), + Defer().Func().Params(Id("a").Int()).Block().Call(Id("a")), + Go().Id("A").Call(), + Return().Nil(), + ), + Func().Id("A").Params().Block(), + ) + + if err := Render(buf, stmt); err != nil { + t.Fatal(err) + } + + fmt.Println("______________________________") + fmt.Print(buf.String()) + fmt.Println("______________________________") +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..4e02abd --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package config + +type Config interface { + CommentSingle() OpenClose + CommentMulti() OpenClose + OperationAvailable(op string) bool +} + +type OpenClose struct { + Open, Close string +} diff --git a/internal/gen/render.go b/internal/gen/render.go new file mode 100644 index 0000000..ccbe395 --- /dev/null +++ b/internal/gen/render.go @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package gen + +import ( + "fmt" + "io" + + "go.osspkg.com/gogen/types" +) + +func Render(w io.Writer, args ...any) error { + for _, arg := range args { + if err := drawAny(w, arg); err != nil { + return err + } + } + return nil +} + +func drawAny(w io.Writer, t any) error { + if v, ok := t.(Unwrap); ok { + return drawList(w, v.Unwrap(), true) + } else if v, ok := t.([]types.Token); ok { + return drawList(w, v, false) + } else if v, ok := t.(types.Token); ok { + return v.Render(w) + } else if v, ok := t.(string); ok { + _, err := io.WriteString(w, v) + return err + } else { + fmt.Printf("[X] %T\n", t) + } + return nil +} + +func drawList(w io.Writer, list []types.Token, line bool) error { + count := len(list) - 1 + for i, token := range list { + if vt, ok := token.(Unwrap); ok { + if err := drawList(w, vt.Unwrap(), true); err != nil { + return err + } + continue + } + + if err := token.Render(w); err != nil { + return err + } + if i >= count { + continue + } + if _, err := io.WriteString(w, " "); err != nil { + return err + } + } + if line { + if _, err := io.WriteString(w, "\n"); err != nil { + return err + } + } + return nil +} + +func Params(token types.Token) (out []types.Token) { + if unw, ok := token.(Unwrap); ok { + out = append(out, unw.Unwrap()...) + } else { + out = append(out, token) + } + return +} diff --git a/internal/gen/types.go b/internal/gen/types.go new file mode 100644 index 0000000..bcfdbbb --- /dev/null +++ b/internal/gen/types.go @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package gen + +import "go.osspkg.com/gogen/types" + +type Unwrap interface { + Unwrap() []types.Token +} diff --git a/internal/models/block.go b/internal/models/block.go new file mode 100644 index 0000000..cd0a7b2 --- /dev/null +++ b/internal/models/block.go @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package models + +import ( + "io" + + "go.osspkg.com/gogen/internal/gen" + "go.osspkg.com/gogen/types" +) + +type Block struct { + D []types.Token +} + +func (v *Block) Render(w io.Writer) error { + out := make([]any, 0) + out = append(out, "{\n") + for _, token := range v.D { + out = append(out, token) + } + out = append(out, "}") + return gen.Render(w, out...) +} + +type Params struct { + D []types.Token + Brace bool +} + +func (v *Params) Render(w io.Writer) error { + count := len(v.D) - 1 + out := make([]any, 0, count*2+2) + if v.Brace { + out = append(out, "(") + } + for i, token := range v.D { + out = append(out, gen.Params(token)) + if i < count { + out = append(out, ", ") + } + } + if v.Brace { + out = append(out, ")") + } + return gen.Render(w, out...) +} diff --git a/internal/models/comment.go b/internal/models/comment.go new file mode 100644 index 0000000..831d62d --- /dev/null +++ b/internal/models/comment.go @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package models + +import ( + "io" + "strings" + + "go.osspkg.com/gogen/internal/config" + "go.osspkg.com/gogen/internal/gen" +) + +type Comment[C config.Config] struct { + c C + D string +} + +func (v *Comment[C]) Render(w io.Writer) error { + oc := v.c.CommentSingle() + if strings.Contains(v.D, "\n") { + oc = v.c.CommentMulti() + } + + return gen.Render(w, oc.Open, v.D, oc.Close) +} diff --git a/internal/models/keyword.go b/internal/models/keyword.go new file mode 100644 index 0000000..0581c23 --- /dev/null +++ b/internal/models/keyword.go @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package models + +import ( + "fmt" + "io" + "regexp" + + "go.osspkg.com/gogen/internal/gen" +) + +type Keyword struct { + Verify bool + D string +} + +var rexLetter = regexp.MustCompile(`(?mUi)^[a-z][0-9a-z\_]{0,}$`) + +func (v *Keyword) Render(w io.Writer) error { + if v.Verify && !rexLetter.MatchString(v.D) { + return fmt.Errorf("invalid letter: %s", v.D) + } + return gen.Render(w, v.D) +} diff --git a/internal/models/letter.go b/internal/models/letter.go new file mode 100644 index 0000000..ac7e279 --- /dev/null +++ b/internal/models/letter.go @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package models + +import ( + "io" + + "go.osspkg.com/gogen/internal/gen" +) + +type Letter struct { + D string +} + +func (v *Letter) Render(w io.Writer) error { + return gen.Render(w, v.D) +} diff --git a/internal/models/operation.go b/internal/models/operation.go new file mode 100644 index 0000000..27d2902 --- /dev/null +++ b/internal/models/operation.go @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package models + +import ( + "fmt" + "io" + + "go.osspkg.com/gogen/internal/config" + "go.osspkg.com/gogen/internal/gen" +) + +type Operation[C config.Config] struct { + c C + D string +} + +func (v *Operation[C]) Render(w io.Writer) error { + if !v.c.OperationAvailable(v.D) { + return fmt.Errorf("invalid operation: %s", v.D) + } + return gen.Render(w, v.D) +} diff --git a/internal/models/text.go b/internal/models/text.go new file mode 100644 index 0000000..0824c34 --- /dev/null +++ b/internal/models/text.go @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package models + +import ( + "io" + "strconv" + + "go.osspkg.com/gogen/internal/gen" +) + +type Text struct { + D string +} + +func (v *Text) Render(w io.Writer) error { + return gen.Render(w, strconv.Quote(v.D)) +} diff --git a/types/token.go b/types/token.go new file mode 100644 index 0000000..ed10eab --- /dev/null +++ b/types/token.go @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package types + +import "io" + +type Token interface { + Render(w io.Writer) error +}