From 8082fc319a3c7ef8c297e7447e05b1adee6e1865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Sedl=C3=A1=C4=8Dek?= Date: Fri, 11 Oct 2024 00:08:26 +0200 Subject: [PATCH] Annotations, tests (#9) * annotations, tests * github tests --- .github/workflows/tests.yml | 25 ++++ .gitignore | 2 + _examples/e1.ridl | 16 ++- formatter/processor.go | 55 +++++++- formatter/section.go | 1 + go.mod | 8 ++ go.sum | 10 ++ main.go | 29 +++-- main_test.go | 249 ++++++++++++++++++++++++++++++++++++ 9 files changed, 377 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 go.sum create mode 100644 main_test.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..ea57d8a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,25 @@ +name: Tests + +on: + push: + branches: + - master + pull_request: + +jobs: + test: + name: Test check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.21.0 + + - name: Run tests and coverage + run: | + go test -v -coverprofile=coverage.txt -covermode=atomic ./... diff --git a/.gitignore b/.gitignore index 0d3ae47..a2088ff 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .idea/* bin/* +_examples/e2.ridl +coverage.txt diff --git a/_examples/e1.ridl b/_examples/e1.ridl index d5bdc37..4d0a7fe 100755 --- a/_examples/e1.ridl +++ b/_examples/e1.ridl @@ -1,13 +1,12 @@ -webrpc = v1 # version of webrpc schema format (ridl or json) -name = example # name of your backend app -version = v0.0.1 # version of your schema + webrpc = v1 # version of webrpc schema format (ridl or json) + name = example # name of your backend app + version=v0.0.1#version of your schema # bar enum Intent: string #! foo - - openSession - - closeSession - - validateSession + - openSession + - closeSession enum Kind: uint32 - USER @@ -68,9 +67,12 @@ error 20 UserNotFound "User not found" HTTP 404 error 4 UserTooYoung "" HTTP 404 service ExampleService # oof + @ deprecated : Pong + @ auth : ApiKeyAuth @ who dsa : J W T ## dadsadadsa - Ping() - Status() => (status: bool) + @ internal @ public ## dsada s dsa - Version() => (version: Version) +@public - GetUser ( header : map < string , string > , userID : uint64 ) => ( code : uint32 , user : User ) - FindUser(s :SearchFilter) => (name: string, user: User) ###! last - diff --git a/formatter/processor.go b/formatter/processor.go index c1b5181..e5d6d45 100644 --- a/formatter/processor.go +++ b/formatter/processor.go @@ -86,6 +86,23 @@ func (f *form) formatLine(line string) (string, error) { f.parseSection(line) switch f.section { + case sectionWebRPC: + fallthrough + case sectionName: + fallthrough + case sectionVersion: + f.padding = 0 + line = reduceSpaces(line) + s, c := parseAndDivideInlineComment(line) + parts := strings.Split(s, "=") + if len(parts) != 2 { + return "", fmt.Errorf("unexpected amount of parts=(%d) %s", len(parts), line) + } + + line = fmt.Sprintf("%s = %s", removeSpaces(parts[0]), removeSpaces(parts[1])) + + line = c.appendInlineComment(line) + case sectionComment: f.comments = append(f.comments, parseComment(line)) case sectionEnum: @@ -218,6 +235,34 @@ func (f *form) formatLine(line string) (string, error) { line = fmt.Sprintf("%s%s", strings.Repeat(" ", f.padding), line) line = c.appendInlineComment(line) + case sectionAnnotation: + f.padding = 4 + s, c := parseAndDivideInlineComment(line) + + parts := strings.Split(s, "@") + if len(parts) < 2 { + return "", fmt.Errorf("unexpected amount of parts=(%d)", len(parts)) + } + + var as string + for i := 1; i < len(parts); i++ { + ap := strings.Split(parts[i], ":") + if i > 1 { + as += " " + } + + switch len(ap) { + case 1: + as = fmt.Sprintf("%s@%s", as, removeSpaces(ap[0])) + case 2: + as = fmt.Sprintf("%s@%s:%s", as, removeSpaces(ap[0]), removeSpaces(ap[1])) + default: + return "", fmt.Errorf("unexpected amount of parts for one anotation parts=(%d) %s", len(ap), line) + } + } + + line = fmt.Sprintf("%s%s", strings.Repeat(" ", f.padding), as) + line = c.appendInlineComment(line) default: } @@ -261,6 +306,8 @@ func (f *form) parseSection(line string) { f.section = sectionField case strings.HasPrefix(line, "+"): f.section = sectionTag + case strings.HasPrefix(line, "@"): + f.section = sectionAnnotation default: f.section = sectionUnknown } @@ -330,6 +377,10 @@ func reduceSpaces(input string) string { return pattern.ReplaceAllString(input, " ") } +func removeSpaces(input string) string { + return strings.ReplaceAll(input, " ", "") +} + func formatMethodArguments(s string) (string, error) { content, err := extractFromParenthesis(s) if err != nil { @@ -360,7 +411,7 @@ func extractFromParenthesis(s string) (string, error) { } func splitArguments(s string) []string { - s = strings.ReplaceAll(strings.TrimSpace(s), " ", "") + s = removeSpaces(strings.TrimSpace(s)) var parts []string var ic, im int @@ -378,7 +429,7 @@ func splitArguments(s string) []string { break } } else { - s = strings.ReplaceAll(s, " ", "") + s = removeSpaces(s) c, more := findComma(ic, s) if more { parts = append(parts, s[:c]) diff --git a/formatter/section.go b/formatter/section.go index 16a9538..09ddd62 100644 --- a/formatter/section.go +++ b/formatter/section.go @@ -16,4 +16,5 @@ const ( sectionTag sectionError sectionImport + sectionAnnotation ) diff --git a/go.mod b/go.mod index 3bd93c1..17bd8bc 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module github.com/webrpc/ridlfmt go 1.20 + +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..60ce688 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 4bcc677..13aad63 100644 --- a/main.go +++ b/main.go @@ -12,29 +12,38 @@ import ( ) func main() { - flag.Usage = usage + flagSet := flag.NewFlagSet("ridlfmt", flag.ExitOnError) + if err := runRidlfmt(flagSet, os.Args[1:]); err != nil { + log.Fatalf("Error: %v", err) + } +} - sortErrorsFlag := flag.Bool("s", false, "sort errors by code") - writeFlag := flag.Bool("w", false, "write output to input file (overwrites the file)") - helpFlag := flag.Bool("h", false, "show help") +func runRidlfmt(flagSet *flag.FlagSet, args []string) error { + flag.Usage = usage - flag.Parse() + sortErrorsFlag := flagSet.Bool("s", false, "sort errors by code") + writeFlag := flagSet.Bool("w", false, "write output to input file (overwrites the file)") + helpFlag := flagSet.Bool("h", false, "show help") - args := flag.Args() + if err := flagSet.Parse(args); err != nil { + return fmt.Errorf("parse args: %w", err) + } if *helpFlag { flag.Usage() os.Exit(0) } - if len(args) == 0 && !isInputFromPipe() { + fileArgs := flagSet.Args() + + if len(fileArgs) == 0 && !isInputFromPipe() { fmt.Fprintln(os.Stderr, "error: no input files specified") flag.Usage() os.Exit(1) } if *writeFlag { - for _, fileName := range args { + for _, fileName := range fileArgs { err := formatAndWriteToFile(fileName, *sortErrorsFlag) if err != nil { log.Fatalf("Error processing file %s: %v", fileName, err) @@ -47,7 +56,7 @@ func main() { log.Fatalf("Error processing input from pipe: %v", err) } } else { - for _, fileName := range args { + for _, fileName := range fileArgs { err := formatAndPrintToStdout(fileName, *sortErrorsFlag) if err != nil { log.Fatalf("Error processing file %s: %v", fileName, err) @@ -55,6 +64,8 @@ func main() { } } } + + return nil } func isInputFromPipe() bool { diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..4d31632 --- /dev/null +++ b/main_test.go @@ -0,0 +1,249 @@ +package main + +import ( + "bytes" + "flag" + "io" + "os" + "os/exec" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFormatAndPrintFromPipe(t *testing.T) { + r, w, _ := os.Pipe() + oldStdin := os.Stdin + defer func() { + os.Stdin = oldStdin + }() + + os.Stdin = r + w.Write([]byte(testInput)) + w.Close() + + rOut, wOut, _ := os.Pipe() + oldStdout := os.Stdout + defer func() { + os.Stdout = oldStdout + }() + os.Stdout = wOut + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + + args := []string{"-s"} + err := runRidlfmt(flagSet, args) + require.NoError(t, err) + wOut.Close() + + var out bytes.Buffer + _, err = io.Copy(&out, rOut) + require.NoError(t, err) + + require.Equal(t, strings.TrimSpace(expectedOutput), strings.TrimSpace(out.String())) +} + +func TestFormatAndWriteToFile(t *testing.T) { + tempFile, err := os.CreateTemp("", "ridlfmt_test*.ridl") + require.NoError(t, err) + defer os.Remove(tempFile.Name()) + + _, err = tempFile.WriteString(testInput) + require.NoError(t, err) + tempFile.Close() + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + + args := []string{"-w", "-s", tempFile.Name()} + err = runRidlfmt(flagSet, args) + require.NoError(t, err) + + // Read the output from the temp file + outputBytes, err := os.ReadFile(tempFile.Name()) + require.NoError(t, err) + + require.Equal(t, expectedOutput, string(outputBytes)) +} + +func testHelpFlag(t *testing.T) { + cmd := exec.Command(os.Args[0], "-h") + + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil && err.Error() != "exit status 2" { // exit code 2 is expected for help flag + t.Fatalf("Error running command: %v", err) + } + + expectedHelpText := `usage: ridlfmt [flags] [path...]` + if !strings.Contains(out.String(), expectedHelpText) { + t.Errorf("Expected help message not found. Got: %s", out.String()) + } +} + +const testInput string = ` + webrpc = v1 # version of webrpc schema format (ridl or json) + name = example # name of your backend app + version=v0.0.1#version of your schema + +# bar +enum Intent: string + #! foo + - openSession + - closeSession + +enum Kind: uint32 + - USER +# admin + - ADMIN + +struct Empty + + # struct comment +struct User + - id: uint64 + + json = id + + go.field.name = ID # dsadsa + + go.tag.db = id + + - username: string + + json = USERNAME + + go.tag.db = username #! far away + +#! role? + #! role! + - role: string + + go.tag.db = - + + - kind: Kind + + json = kind + + - intent: Intent + + json = intent ###! dsadasdasds + + go.tag.db = - + +struct Version + - webrpcVersion: string + - schemaVersion: string + - schemaHash: string + +struct ComplexType # dsdas + # https://www.example.com/?first=1&second=12#help + - meta: map + - metaNestedExample: map> + - namesList: []string + - numsList: []int64 + - doubleArray: [][]string + - listOfMaps: []map # dsadasdasdas + - listOfUsers: []User + - mapOfUsers: map + - user: User + +#! +#! Errors +#! +error 2 UserNotFound "User not found" HTTP 404 +error 20 SpaceshipNotFound "Spaceship not found" HTTP 404#comment +error 300 Unsomething "Un what?" HTTP 444 #comment +error 1 IAmFirst "I am first" HTTP 101 # comment + +error 20 UserNotFound "User not found" HTTP 404 +error 4 UserTooYoung "" HTTP 404 + +service ExampleService # oof + @ deprecated : Pong + @ auth : ApiKeyAuth @ who dsa : J W T ## dadsadadsa +- Ping() + - Status() => (status: bool) + @ internal @ public ## dsada s dsa + - Version() => (version: Version) +@public + - GetUser ( header : map < string , string > , userID : uint64 ) => ( code : uint32 , user : User ) + - FindUser(s :SearchFilter) => (name: string, user: User) ###! last + + + + +` + +const expectedOutput string = ` +webrpc = v1 # version of webrpc schema format (ridl or json) +name = example # name of your backend app +version = v0.0.1 # version of your schema + +# bar +enum Intent: string + #! foo + - openSession + - closeSession + +enum Kind: uint32 + - USER + # admin + - ADMIN + +struct Empty + +# struct comment +struct User + - id: uint64 + + json = id + + go.field.name = ID # dsadsa + + go.tag.db = id + + - username: string + + json = USERNAME + + go.tag.db = username #! far away + + #! role? + #! role! + - role: string + + go.tag.db = - + + - kind: Kind + + json = kind + + - intent: Intent + + json = intent ###! dsadasdasds + + go.tag.db = - + +struct Version + - webrpcVersion: string + - schemaVersion: string + - schemaHash: string + +struct ComplexType # dsdas + # https://www.example.com/?first=1&second=12#help + - meta: map + - metaNestedExample: map> + - namesList: []string + - numsList: []int64 + - doubleArray: [][]string + - listOfMaps: []map # dsadasdasdas + - listOfUsers: []User + - mapOfUsers: map + - user: User + +#! +#! Errors +#! +error 1 IAmFirst "I am first" HTTP 101 # comment +error 2 UserNotFound "User not found" HTTP 404 +error 20 SpaceshipNotFound "Spaceship not found" HTTP 404 # comment +error 300 Unsomething "Un what?" HTTP 444 # comment + +error 4 UserTooYoung "" HTTP 404 +error 20 UserNotFound "User not found" HTTP 404 + +service ExampleService # oof + @deprecated:Pong + @auth:ApiKeyAuth @whodsa:JWT ## dadsadadsa + - Ping() + - Status() => (status: bool) + @internal @public ## dsada s dsa + - Version() => (version: Version) + @public + - GetUser(header: map, userID: uint64) => (code: uint32, user: User) + - FindUser(s: SearchFilter) => (name: string, user: User) ###! last +`