Skip to content

Commit

Permalink
feat(generators): add skeleton go generator (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
plastikfan committed Sep 11, 2023
1 parent e07e44e commit f55c50b
Show file tree
Hide file tree
Showing 18 changed files with 828 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# vendor/

dist/
generators/gola/out/

# go-task intermediate files like checksums
#
Expand Down
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"gocritic",
"gocyclo",
"gofmt",
"gogen",
"goimports",
"gola",
"gomnd",
"gosec",
"gosimple",
Expand All @@ -36,6 +38,7 @@
"psname",
"rebinder",
"refl",
"repotoken",
"Selectf",
"staticcheck",
"structcheck",
Expand Down
77 changes: 75 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
version: '3'
version: "3"
silent: true

dotenv: ['.env']
dotenv: [".env"]

vars:
FORMAT: json
GEN_BINARY_NAME: cobrass-gen
GEN_DIR: ./generators/gola/gen
GEN_TEST_OUTPUT_DIR: ./generators/gola/out/assistant
DIST_DIR: ./dist

tasks:
b:
Expand Down Expand Up @@ -57,3 +64,69 @@ tasks:
cover:
cmds:
- goveralls -repotoken {{.COVERALLS_TOKEN}}

# === code generator =========================================
#
# NB: go generate can't evaluate variables, but we need to
# distinguish between a test run and a real run. For this reason
# we only invoke go generate for a real run and for a test run
# we invoke the generator directly without go generate, passing
# in the test flag.

ov-gen-t:
cmds:
- mkdir -p {{.GEN_TEST_OUTPUT_DIR}}
- cobrass-gen -test -cwd ./

ov-gen:
cmds:
- go generate ./...

# === build/deploy code generator ===========================

b-gen-linux:
cmds:
- task: build-generic
vars: { TARGET_OS: linux, TARGET_ARCH: amd64 }

build-generic:
vars:
APPLICATION_ENTRY: ./generators/gola/gen
cmds:
- echo "cross compiling generator from {{OS}} to {{.TARGET_OS}}"
- GOOS={{.TARGET_OS}} GOARCH={{.TARGET_ARCH}} go build -o {{.DIST_DIR}}/{{.TARGET_OS}}/{{.GEN_BINARY_NAME}} -v {{.APPLICATION_ENTRY}}

sources:
- ./generators/gola/*.go

generates:
- "{{.DIST_DIR}}/{{.TARGET_OS}}/{{.GEN_BINARY_NAME}}"

d:
cmds:
- task: deploy

# currently, this is hardcoded for linux
#
deploy:
vars:
TARGET_OS: linux
DEPLOY_BINARY: "{{.DIST_DIR}}/{{.TARGET_OS}}/{{.GEN_BINARY_NAME}}"

cmds:
- echo "deploying to location (.env) DEPLOY_TO ==> '$DEPLOY_TO'"
- /bin/cp -f {{.DEPLOY_BINARY}} $DEPLOY_TO

generates:
- $DEPLOY_TO/{{.DEPLOY_BINARY}}
- $DEPLOY_TO/{{.ACTIVE_US}}

preconditions:
- test $DEPLOY_TO
- test -f {{.DEPLOY_BINARY}}

tbd:
cmds:
- task: t
- task: b-gen-linux
- task: d
3 changes: 3 additions & 0 deletions generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package cobrass

//go:generate cobrass-gen -cwd ./
109 changes: 109 additions & 0 deletions generators/gola/gen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package main

import (
"flag"
"fmt"
"os"
"path/filepath"

"github.com/samber/lo"
"github.com/snivilised/cobrass/generators/gola"
"github.com/snivilised/cobrass/generators/gola/internal/utils"
)

const (
appName = "cobrass-gen"
outputPathNotFoundExitCode = 2
)

var (
testFlag = flag.Bool("test", false, "generate code in test location?")
cwdFlag = flag.String("cwd", "", "current working directory")
testPath = filepath.Join("generators", "gola", "out", "assistant")
sourcePath = filepath.Join("src", "assistant")
outputPathNotFound = "Output path '%v', not found"
)

func Usage() {
fmt.Fprintf(os.Stderr, "Use of %v:\n", appName)
fmt.Fprintf(os.Stderr, "run the command from the root of the repo ...\n")
fmt.Fprintf(os.Stderr, "\t%v [Flags]\n", appName)
fmt.Fprintf(os.Stderr, "Flags:\n")
flag.PrintDefaults()
}

func fail(reason string, callback ...func()) {
if len(callback) > 0 {
callback[0]()
}

fmt.Fprintf(os.Stderr, "🔥 Failed: '%v'\n", reason)
flag.Usage()
os.Exit(outputPathNotFoundExitCode)
}

// ???
// https://askgolang.com/how-to-get-current-directory-in-golang/

func main() {
flag.Usage = Usage
flag.Parse()

outputPath := lo.Ternary(*testFlag, testPath, sourcePath)

if *cwdFlag == "" {
fail("🔥 current working directory not specified")
}

absolutePath, _ := filepath.Abs(*cwdFlag)
absolutePath = filepath.Join(absolutePath, outputPath)

if !utils.FileExists(absolutePath) {
callback := func() {
fmt.Printf("💥 ---> CWD: '%v' \n", *cwdFlag)
fmt.Printf("💥 ---> OUTPUT: '%v' \n", outputPath)
fmt.Printf("💥 ---> RESOLVED: '%v' \n", absolutePath)
}
fail(fmt.Sprintf(outputPathNotFound, absolutePath), callback)

return
}

sourceCode := gola.NewSourceCodeContainer()
mode := lo.Ternary(*testFlag, "🧪 Test", "🎁 Source")

fmt.Printf("☑️ ---> CWD: '%v' \n", *cwdFlag)
fmt.Printf("☑️ ---> OUTPUT: '%v' \n", outputPath)
fmt.Printf("☑️ ---> RESOLVED: '%v' \n", absolutePath)
fmt.Printf("---> 🐲 cobrass generator (%v, to: %v)\n", mode, absolutePath)

if !*testFlag {
if sourceCode.AnyMissing(absolutePath) {
sourceCode.Verify(absolutePath, func(entry *gola.SourceCodeData) {
exists := entry.Exists(absolutePath)
indicator := lo.Ternary(exists, "✔️", "❌")
status := lo.Ternary(exists, "exists", "missing")
path := entry.FullPath(absolutePath)
message := fmt.Sprintf("%v source file: '%v' %v", indicator, path, status)

fmt.Printf("%v\n", message)
})
} else {
fmt.Printf("✅ ---> ALL-PRESENT-AT: '%v' \n", absolutePath)
}
}

sourceCode.Generator().Run()

logicalEnum := gola.LogicalType{
TypeName: "Enum",
GoType: "string",
DisplayType: "enum",
UnderlyingTypeName: "String",
FlagName: "Format",
Short: "f",
Def: "xml",
}

fmt.Printf("---> 🐲 cobrass generator (enum: %+v)\n", logicalEnum)
}
13 changes: 13 additions & 0 deletions generators/gola/gola_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package gola_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestGola(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Gola Suite")
}
72 changes: 72 additions & 0 deletions generators/gola/gomega-matchers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package gola_test

import (
"fmt"
"strings"

"github.com/samber/lo"
"github.com/snivilised/cobrass/generators/gola"

. "github.com/onsi/gomega/types"
)

type (
AreAllSourceCodeFilesPresentMatcher struct {
directory string
}
)

func ContainAllSourceCodeFilesAt(directory string) GomegaMatcher {
return &AreAllSourceCodeFilesPresentMatcher{
directory: directory,
}
}

func (m *AreAllSourceCodeFilesPresentMatcher) Match(actual interface{}) (bool, error) {
sourceCode, ok := actual.(*gola.SourceCodeContainer)
if !ok {
return false, fmt.Errorf("matcher expected a SourceCodeContainer value (actual: '%v')", actual)
}

return !sourceCode.AnyMissing(m.directory), nil
}

func (m *AreAllSourceCodeFilesPresentMatcher) report(
negated bool,
sourceCode *gola.SourceCodeContainer,
) string {
builder := strings.Builder{}
not := lo.Ternary(negated, "NOT ", " ")
builder.WriteString(
fmt.Sprintf("🔥 Expected all source code files %vto be present\n", not),
)

sourceCode.ReportAll(func(entry *gola.SourceCodeData) {
exists := entry.Exists(m.directory)
indicator := lo.Ternary(exists, "✔️", "❌")
status := lo.Ternary(exists, "exists", "missing")
path := entry.FullPath(m.directory)
message := fmt.Sprintf("%v source file: '%v' %v\n", indicator, path, status)
builder.WriteString(message)
})

return builder.String()
}

func (m *AreAllSourceCodeFilesPresentMatcher) FailureMessage(actual interface{}) string {
sourceCode, ok := actual.(*gola.SourceCodeContainer)
if !ok {
return fmt.Sprintf("matcher expected a SourceCodeContainer value (actual: '%v')", actual)
}

return m.report(false, sourceCode)
}

func (m *AreAllSourceCodeFilesPresentMatcher) NegatedFailureMessage(actual interface{}) string {
sourceCode, ok := actual.(*gola.SourceCodeContainer)
if !ok {
return fmt.Sprintf("matcher expected a SourceCodeContainer value (actual: '%v')", actual)
}

return m.report(true, sourceCode)
}
14 changes: 14 additions & 0 deletions generators/gola/internal/utils/fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package utils

import "os"

// FileExists provides a simple way to determine whether the item identified by a
// path actually exists as a file
func FileExists(path string) bool {
result := false
if info, err := os.Lstat(path); err == nil {
result = !info.IsDir()
}

return result
}
43 changes: 43 additions & 0 deletions generators/gola/logical-type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package gola

type PsCaseEntry struct { // this needs a better name, not Ps
AssertFn string
}

type TestCaseEntry struct {
}

type BhTest struct { // binder helper
}

type BhTestCollection map[string]*BhTest

type LogicalType struct {
TypeName string
GoType string
DisplayType string
UnderlyingTypeName string
FlagName string
Short string
Def any
Assign string
Setup string
BindTo string
Assert string
QuoteExpect string
Equate string
Validatable bool
ForeignValidatorFn bool
GenerateSlice bool
SliceFlagName string
SliceShort string
DefSliceVal string
ExpectSlice string
SliceValue string
OptionValue string
TcEntry PsCaseEntry
BindDoc string
BindValidatedDoc string
Containable bool
BhTests BhTestCollection
}
Loading

0 comments on commit f55c50b

Please sign in to comment.