From fa1cbd6713f76d35642958f8062b776c7c1383c7 Mon Sep 17 00:00:00 2001 From: Marco Kellershoff Date: Sun, 21 Jul 2024 18:43:10 +0200 Subject: [PATCH] feat: first working version --- Makefile | 6 ++ README.md | 16 ++++ cmd/kulalafmt/root.go | 4 +- internal/config/config.go | 4 +- internal/parser/parser.go | 182 +++++++++++++++++++++++++++++++------- internal/parser/start.go | 39 ++++++++ internal/parser/utils.go | 44 +++++++++ 7 files changed, 258 insertions(+), 37 deletions(-) create mode 100644 internal/parser/start.go create mode 100644 internal/parser/utils.go diff --git a/Makefile b/Makefile index fd07d31..f90016b 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 9094de1..f5b41e3 100644 --- a/README.md +++ b/README.md @@ -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 +``` diff --git a/cmd/kulalafmt/root.go b/cmd/kulalafmt/root.go index 19d0624..7d82b92 100644 --- a/cmd/kulalafmt/root.go +++ b/cmd/kulalafmt/root.go @@ -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" @@ -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()) } }, @@ -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") } \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index 8a8bea8..30c774a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,8 +1,8 @@ package config type ConfigFlags struct { - DryRun bool - Check bool + Check bool + Verbose bool } type Config struct { diff --git a/internal/parser/parser.go b/internal/parser/parser.go index b68ee7c..25a77db 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -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 { + 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) + } } } + } \ No newline at end of file diff --git a/internal/parser/start.go b/internal/parser/start.go new file mode 100644 index 0000000..fda5e3e --- /dev/null +++ b/internal/parser/start.go @@ -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) + } + } +} \ No newline at end of file diff --git a/internal/parser/utils.go b/internal/parser/utils.go new file mode 100644 index 0000000..99030ed --- /dev/null +++ b/internal/parser/utils.go @@ -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 +} \ No newline at end of file