Skip to content

Commit

Permalink
feat: first working version
Browse files Browse the repository at this point in the history
  • Loading branch information
gorillamoe committed Jul 21, 2024
1 parent ce9e38b commit fa1cbd6
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 37 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ build-and-install-local-debug:

run:
go run -ldflags "-X 'github.com/mistweaverco/kulala-fmt/cmd/kulalafmt.VERSION=development'" main.go

run-check:
go run -ldflags "-X 'github.com/mistweaverco/kulala-fmt/cmd/kulalafmt.VERSION=development'" main.go --check

run-check-verbose:
go run -ldflags "-X 'github.com/mistweaverco/kulala-fmt/cmd/kulalafmt.VERSION=development'" main.go --check --verbose
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,24 @@ Format all `.http` and `.rest` files in the current directory and its subdirecto
kulala-fmt
```

Format all `.http` and `.rest` files in the current directory and its subdirectories and
prints the written output to the console:

```sh
kulala-fmt --verbose
```


Check if all `.http` and `.rest` files in the current directory and its subdirectories are formatted:

```sh
kulala-fmt --check
```

Check if all `.http` and `.rest` files in the current directory and
its subdirectories are formatted and
prints the desired output to the console:

```sh
kulala-fmt --check --verbose
```
4 changes: 1 addition & 3 deletions cmd/kulalafmt/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package kulalafmt
import (
"os"

"github.com/charmbracelet/log"
"github.com/mistweaverco/kulala-fmt/internal/config"
"github.com/mistweaverco/kulala-fmt/internal/parser"
"github.com/spf13/cobra"
Expand All @@ -17,7 +16,6 @@ var rootCmd = &cobra.Command{
Long: "Formats and lints .http and .rest files in the current directory and subdirectories.",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
log.Info("Starting kulala-fmt 🦄", "version", VERSION)
parser.Start(cfg.GetConfigFlags())
}
},
Expand All @@ -31,6 +29,6 @@ func Execute() {
}

func init() {
rootCmd.PersistentFlags().BoolVar(&cfg.Flags.DryRun, "dry-run", false, "dry run")
rootCmd.PersistentFlags().BoolVar(&cfg.Flags.Check, "check", false, "check")
rootCmd.PersistentFlags().BoolVar(&cfg.Flags.Verbose, "verbose", false, "verbose")
}
4 changes: 2 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package config

type ConfigFlags struct {
DryRun bool
Check bool
Check bool
Verbose bool
}

type Config struct {
Expand Down
182 changes: 150 additions & 32 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
@@ -1,57 +1,175 @@
package parser

import (
"os"
"strings"

"github.com/charmbracelet/log"
"github.com/mistweaverco/kulala-fmt/internal/config"
"github.com/mistweaverco/kulala-fmt/internal/filewalker"
)

func containsBothDotEnvAndHttpClientEnvJson(filepaths []string) bool {
var hasDotEnv, hasHttpClientEnvJson bool
type Section struct {
Comments []string
Method string
URL string
Version string
Headers []string
Metadata []string
Body string
}

for _, file := range filepaths {
if strings.HasSuffix(file, ".env") {
hasDotEnv = true
}
if strings.HasSuffix(file, "http-client.env.json") {
hasHttpClientEnvJson = true
}
if hasDotEnv && hasHttpClientEnvJson {
return true
type Document struct {
Variables []string
Sections []Section
Valid bool
}

func isRequestLine(line string) bool {
return strings.HasPrefix(line, "GET") || strings.HasPrefix(line, "POST") || strings.HasPrefix(line, "PUT") || strings.HasPrefix(line, "DELETE")
}

func parseSection(section string, document *Document) Section {
in_request := true
in_header := false
in_body := false
parsedSection := Section{
Comments: []string{},
Method: "",
URL: "",
Version: "",
Headers: []string{},
Metadata: []string{},
Body: "",
}
lines := strings.Split(section, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
if in_request == false && in_body == false {

Check failure on line 48 in internal/parser/parser.go

View workflow job for this annotation

GitHub Actions / lint

S1002: should omit comparison to bool constant, can be simplified to `!in_request` (gosimple)
in_header = false
in_body = true
}
continue
} else if strings.HasPrefix(line, "@") {
document.Variables = append(document.Variables, line)
continue
} else if strings.HasPrefix(line, "# @") {
parsedSection.Metadata = append(parsedSection.Metadata, line)
continue
} else if strings.HasPrefix(line, "#") {
parsedSection.Comments = append(parsedSection.Comments, line)
continue
} else if isRequestLine(line) {
splits := strings.Split(line, " ")
parsedSection.Method = splits[0]
parsedSection.URL = splits[1]
if len(splits) > 2 {
parsedSection.Version = splits[2]
}
in_request = false
in_header = true
continue
} else if in_header {
if strings.Contains(line, ":") {
parsedSection.Headers = append(parsedSection.Headers, line)
}
} else if in_body {
parsedSection.Body += line
}
}

return false
return parsedSection
}

func containsBothHttpAndRest(filepaths []string) bool {
var hasDotEnv, hasHttpClientEnvJson bool
func validateSection(section Section, filepath string, document Document) {
if section.Method == "" {
log.Error("Section is missing method", "file", filepath)
document.Valid = false
}
if section.URL == "" {
log.Error("Section is missing URL", "file", filepath)
document.Valid = false
}
}

for _, file := range filepaths {
if strings.HasSuffix(file, ".rest") {
hasDotEnv = true
func documentToString(document Document) string {
documentString := ""
variableLength := len(document.Variables)
for idx, variable := range document.Variables {
documentString += variable + "\n"
if idx == variableLength-1 {
documentString += "\n"
}
if strings.HasSuffix(file, ".http") {
hasHttpClientEnvJson = true
}
sectionLength := len(document.Sections)
for idx, section := range document.Sections {
for _, comment := range section.Comments {
documentString += comment + "\n"
}
documentString += section.Method + " " + section.URL
if section.Version != "" {
documentString += " " + section.Version
}
documentString += "\n"
for _, header := range section.Headers {
documentString += header + "\n"
}
if hasDotEnv && hasHttpClientEnvJson {
return true
for _, metadata := range section.Metadata {
documentString += metadata + "\n"
}
if section.Body != "" {
documentString += "\n" + section.Body + "\n"
}
if idx != sectionLength-1 {
documentString += "\n###\n\n"
}
}

return false
return documentString
}

func Start(flags config.ConfigFlags) {
filepath_pkgs, _ := filewalker.GetFiles()
for _, filepath_pkg := range filepath_pkgs {
if containsBothDotEnvAndHttpClientEnvJson(filepath_pkg) {
log.Warn("You have both .env and http-client.env.json files in the same directory. This is not recommended.")
func parser(filepath string, flags config.ConfigFlags) {
document := Document{
Valid: true,
Variables: []string{},
Sections: []Section{},
}
fileContents, err := os.ReadFile(filepath)
if err != nil {
log.Fatal("Error reading file", "error", err)
}
sections := strings.Split(string(fileContents), "###")
for _, section := range sections {
section = strings.TrimSpace(section)
if section == "" {
continue
}
parsedSection := parseSection(section, &document)
document.Sections = append(document.Sections, parsedSection)
validateSection(parsedSection, filepath, document)
}
if !document.Valid {
log.Error("File is not valid, can't fix, skipping.", "file", filepath)
return
}
fileContentsAsString := string(fileContents)
documentString := documentToString(document)
if !flags.Check {
if fileContentsAsString != documentString {
log.Warn("File is not formatted correctly, fixing now.", "file", filepath)
if flags.Verbose {
log.Info("Writing", filepath, documentString)
}
err := os.WriteFile(filepath, []byte(documentString), 0644)
if err != nil {
log.Fatal("Error writing file", "error", err)
}
}
if containsBothHttpAndRest(filepath_pkg) {
log.Warn("You have both .rest and .http files in the same directory. This is not recommended.")
} else {
if fileContentsAsString != documentString {
log.Warn("File is not formatted correctly", "file", filepath)
if flags.Verbose {
log.Info("Expected output", filepath, documentString)
}
}
}

}
39 changes: 39 additions & 0 deletions internal/parser/start.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package parser

import (
"strings"

"github.com/charmbracelet/log"
"github.com/mistweaverco/kulala-fmt/internal/config"
"github.com/mistweaverco/kulala-fmt/internal/filewalker"
)

func isDotEnv(filepath string) bool {
return strings.HasSuffix(filepath, ".env")
}

func isHttpClientEnvJson(filepath string) bool {
return strings.HasSuffix(filepath, "http-client.env.json")
}

func Start(flags config.ConfigFlags) {
filepath_pkgs, _ := filewalker.GetFiles()
for _, filepath_pkg := range filepath_pkgs {
dirpath := getDirectoryPath(filepath_pkg[0])
if containsBothDotEnvAndHttpClientEnvJson(filepath_pkg) {
log.Warn("You have both .env and http-client.env.json files in the same directory. This is not recommended.", "directory", dirpath)
}
if containsBothHttpAndRest(filepath_pkg) {
log.Warn("You have both .rest and .http files in the same directory. This is not recommended.", "directory", dirpath)
}
for _, filepath := range filepath_pkg {
if isDotEnv(filepath) {
continue
}
if isHttpClientEnvJson(filepath) {
continue
}
parser(filepath, flags)
}
}
}
44 changes: 44 additions & 0 deletions internal/parser/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package parser

import (
"path/filepath"
"strings"
)

func getDirectoryPath(path string) string {
return filepath.Dir(path)
}

func containsBothDotEnvAndHttpClientEnvJson(filepaths []string) bool {
var hasDotEnv, hasHttpClientEnvJson bool

for _, file := range filepaths {
if strings.HasSuffix(file, ".env") {
hasDotEnv = true
}
if strings.HasSuffix(file, "http-client.env.json") {
hasHttpClientEnvJson = true
}
if hasDotEnv && hasHttpClientEnvJson {
return true
}
}
return false
}

func containsBothHttpAndRest(filepaths []string) bool {
var hasDotEnv, hasHttpClientEnvJson bool

for _, file := range filepaths {
if strings.HasSuffix(file, ".rest") {
hasDotEnv = true
}
if strings.HasSuffix(file, ".http") {
hasHttpClientEnvJson = true
}
if hasDotEnv && hasHttpClientEnvJson {
return true
}
}
return false
}

0 comments on commit fa1cbd6

Please sign in to comment.