diff --git a/README.md b/README.md index 01b039f..7e5b075 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Core +![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/Snider/Core?utm_source=oss&utm_medium=github&utm_campaign=Snider%2FCore&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews) + Core is a Web3 Framework, written in Go using Wails.io to replace Electron and the bloat of browsers that, at their core, still live in their mum's basement. More to come, follow us on Discord http://discord.dappco.re diff --git a/Taskfile.yml b/Taskfile.yml index 693b3da..f35896f 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -42,13 +42,3 @@ tasks: cmds: - task: cov - go tool cover -html=coverage.txt - - sync: - desc: "Updates the public API Go files to match the exported interface of the modules." - cmds: - - task: cli:sync - - test-gen: - desc: "Generates tests for the public API." - cmds: - - task: cli:test-gen diff --git a/cmd/core/README.md b/cmd/core/README.md new file mode 100644 index 0000000..1c4d95b --- /dev/null +++ b/cmd/core/README.md @@ -0,0 +1,53 @@ +# Core CLI + +This directory contains the source code for the `core` command-line interface (CLI), a tool for managing and interacting with the Core framework. + +## Purpose + +The CLI provides a set of commands to streamline common development tasks, such as: + +- Building and running applications. +- Generating tests for the public API. +- Synchronizing API definitions. +- Generating missing documentation. + +## Key Packages + +The CLI's user interface is built with the help of several key packages: + +- **`github.com/charmbracelet/lipgloss`**: A library for styling terminal output. We use it to add color and formatting to make the CLI's output more readable and visually appealing. + +- **`github.com/common-nighthawk/go-figure`**: Used to create ASCII art text banners, giving the CLI a distinct and recognizable startup message. + +- **`github.com/leaanthony/clir`**: A simple and lightweight library for creating CLI applications, and our primary framework for specific, simpler command needs. + +## Examples + +```go +package main + +import ( + "fmt" + + "github.com/leaanthony/clir" +) + +func main() { + // Create new cli + cli := clir.NewCli("Flags", "A simple example", "v0.0.1") + + // Name + name := "Anonymous" + cli.StringFlag("name", "Your name", &name) + + // Define action for the command + cli.Action(func() error { + fmt.Printf("Hello %s!\n", name) + return nil + }) + + if err := cli.Run(); err != nil { + fmt.Printf("Error encountered: %v\n", err) + } +} +``` diff --git a/cmd/core/Taskfile.yml b/cmd/core/Taskfile.yml index afb356d..9b931e6 100644 --- a/cmd/core/Taskfile.yml +++ b/cmd/core/Taskfile.yml @@ -2,6 +2,7 @@ version: '3' includes: cmd: "./cmd/Taskfile.yml" + dev: "./cmd/dev/Taskfile.yml" tasks: build: @@ -17,7 +18,7 @@ tasks: run: summary: Builds and runs the core executable cmds: - - task: build + - task: cmd:build - cmd: chmod +x {{.TASKFILE_DIR}}/bin/core platforms: [linux, darwin] - "{{.TASKFILE_DIR}}/bin/core {{.CLI_ARGS}}" @@ -30,10 +31,3 @@ tasks: platforms: [linux, darwin] - "{{.TASKFILE_DIR}}/bin/core dev sync" - test-gen: - summary: Generates tests for the public API - deps: [build] - cmds: - - cmd: chmod +x {{.TASKFILE_DIR}}/bin/core - platforms: [linux, darwin] - - "{{.TASKFILE_DIR}}/bin/core dev test-gen" diff --git a/cmd/core/cmd/Taskfile.yml b/cmd/core/cmd/Taskfile.yml index a8d1c45..1cb66de 100644 --- a/cmd/core/cmd/Taskfile.yml +++ b/cmd/core/cmd/Taskfile.yml @@ -1,6 +1,7 @@ version: '3' tasks: + build: summary: Builds the core executable cmds: diff --git a/cmd/core/cmd/dev/Taskfile.yml b/cmd/core/cmd/dev/Taskfile.yml new file mode 100644 index 0000000..8e0e8b8 --- /dev/null +++ b/cmd/core/cmd/dev/Taskfile.yml @@ -0,0 +1,26 @@ +version: '3' + +tasks: + docgen: + summary: Generates missing docstrings for the public API + deps: [build] + cmds: + - cmd: chmod +x ../../bin/core + platforms: [linux, darwin] + - "../../bin/core dev doc" + + test-gen: + summary: Generates tests for the public API + deps: [ build ] + cmds: + - cmd: chmod +x {{.TASKFILE_DIR}}/bin/core + platforms: [ linux, darwin ] + - "{{.TASKFILE_DIR}}/bin/core dev test-gen" + + sync: + summary: Updates the public API Go files + deps: [ build ] + cmds: + - cmd: chmod +x {{.TASKFILE_DIR}}/bin/core + platforms: [ linux, darwin ] + - "{{.TASKFILE_DIR}}/bin/core dev sync" diff --git a/cmd/core/cmd/api.go b/cmd/core/cmd/dev/api.go similarity index 97% rename from cmd/core/cmd/api.go rename to cmd/core/cmd/dev/api.go index 4302b53..01d8872 100644 --- a/cmd/core/cmd/api.go +++ b/cmd/core/cmd/dev/api.go @@ -1,4 +1,4 @@ -package cmd +package dev import ( "github.com/leaanthony/clir" diff --git a/cmd/core/cmd/dev/doc.go b/cmd/core/cmd/dev/doc.go new file mode 100644 index 0000000..6304c54 --- /dev/null +++ b/cmd/core/cmd/dev/doc.go @@ -0,0 +1,115 @@ +package dev + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" + + "github.com/leaanthony/clir" +) + +// AddDocCommand adds the 'doc' command to the provided clir instance. +func AddDocCommand(parent *clir.Command) { + docCmd := parent.NewSubCommand("doc", "Generate missing docstrings for the public API.") + docCmd.LongDescription(`This command scans the public API of your project and generates placeholder docstrings for any exported functions, types, or methods that are missing them. + +It will also report any values that cannot be determined through reflection, providing a list of file names and line numbers that require manual attention. + +If a comment already exists, it will be preserved.`) + + var basePath string + docCmd.StringFlag("path", "Base path to scan for Go files (defaults to current directory)", &basePath) + + docCmd.Action(func() error { + if basePath == "" { + basePath = "." + } + + reports := []string{} + fset := token.NewFileSet() + + err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() && (info.Name() == "vendor" || strings.HasPrefix(info.Name(), ".")) { + return filepath.SkipDir + } + if !info.IsDir() && strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, "_test.go") { + fileContent, readErr := os.ReadFile(path) + if readErr != nil { + return fmt.Errorf("failed to read file %s: %w", path, readErr) + } + + node, parseErr := parser.ParseFile(fset, path, fileContent, parser.ParseComments) + if parseErr != nil { + return fmt.Errorf("failed to parse file %s: %w", path, parseErr) + } + + for _, decl := range node.Decls { + switch d := decl.(type) { + case *ast.GenDecl: + if d.Doc == nil || len(d.Doc.List) == 0 { + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + if ast.IsExported(s.Name.Name) { + reports = append(reports, fmt.Sprintf("%s:%d: Missing docstring for type %s. Suggested: // %s ...", path, fset.Position(s.Pos()).Line, s.Name.Name, s.Name.Name)) + } + case *ast.ValueSpec: // For var and const + for _, name := range s.Names { + if ast.IsExported(name.Name) { + reports = append(reports, fmt.Sprintf("%s:%d: Missing docstring for %s %s. Suggested: // %s ...", path, fset.Position(name.Pos()).Line, d.Tok.String(), name.Name, name.Name)) + } + } + } + } + } + case *ast.FuncDecl: + if d.Doc == nil || len(d.Doc.List) == 0 { + if d.Name != nil && ast.IsExported(d.Name.Name) { + funcName := d.Name.Name + if d.Recv != nil && len(d.Recv.List) > 0 { + // It's a method + recvType := "" + switch rt := d.Recv.List[0].Type.(type) { + case *ast.Ident: + recvType = rt.Name + case *ast.StarExpr: + if id, ok := rt.X.(*ast.Ident); ok { + recvType = "*" + id.Name + } + } + reports = append(reports, fmt.Sprintf("%s:%d: Missing docstring for method (%s) %s. Suggested: // (%s) %s ...", path, fset.Position(d.Pos()).Line, recvType, funcName, recvType, funcName)) + } else { + // It's a function + reports = append(reports, fmt.Sprintf("%s:%d: Missing docstring for function %s. Suggested: // %s ...", path, fset.Position(d.Pos()).Line, funcName, funcName)) + } + } + } + } + } + } + return nil + }) + + if err != nil { + fmt.Fprintf(os.Stderr, "Error walking through files: %v\n", err) + return err + } + + if len(reports) == 0 { + fmt.Println("No missing docstrings found for exported declarations.") + } else { + fmt.Println("Missing docstrings found:") + for _, r := range reports { + fmt.Println(r) + } + } + return nil + }) +} diff --git a/cmd/core/cmd/build.go b/cmd/core/cmd/dev/pwa-build.go similarity index 84% rename from cmd/core/cmd/build.go rename to cmd/core/cmd/dev/pwa-build.go index 71e9150..0711594 100644 --- a/cmd/core/cmd/build.go +++ b/cmd/core/cmd/dev/pwa-build.go @@ -1,4 +1,4 @@ -package cmd +package dev import ( "embed" @@ -22,8 +22,8 @@ import ( var guiTemplate embed.FS // AddBuildCommand adds the new build command and its subcommands to the clir app. -func AddBuildCommand(app *clir.Cli) { - buildCmd := app.NewSubCommand("build", "Builds a web application into a standalone desktop app.") +func AddBuildCommand(parent *clir.Command) { + buildCmd := parent.NewSubCommand("build", "Builds a web application into a standalone desktop app.") // --- `build from-path` command --- fromPathCmd := buildCmd.NewSubCommand("from-path", "Build from a local directory.") @@ -81,31 +81,31 @@ func downloadPWA(baseURL, destDir string) error { } // Find the manifest URL from the HTML - manifestURL, err := findManifestURL(string(body), baseURL) - if err != nil { - // If no manifest, it's not a PWA, but we can still try to package it as a simple site. - fmt.Println("Warning: no manifest file found. Proceeding with basic site download.") - if err := os.WriteFile(filepath.Join(destDir, "index.html"), body, 0644); err != nil { - return fmt.Errorf("failed to write index.html: %w", err) - } - return nil - } - - fmt.Printf("Found manifest: %s\n", manifestURL) - - // Fetch and parse the manifest - manifest, err := fetchManifest(manifestURL) - if err != nil { - return fmt.Errorf("failed to fetch or parse manifest: %w", err) - } + //manifestURL, err := findManifestURL(string(body), baseURL) + //if err != nil { + // // If no manifest, it's not a PWA, but we can still try to package it as a simple site. + // fmt.Println("Warning: no manifest file found. Proceeding with basic site download.") + // if err := os.WriteFile(filepath.Join(destDir, "index.html"), body, 0644); err != nil { + // return fmt.Errorf("failed to write index.html: %w", err) + // } + // return nil + //} + // + //fmt.Printf("Found manifest: %s\n", manifestURL) + // + //// Fetch and parse the manifest + //manifest, err := fetchManifest(baseURL, manifestURL) + //if err != nil { + // return fmt.Errorf("failed to fetch or parse manifest: %w", err) + //} // Download all assets listed in the manifest - assets := collectAssets(manifest, manifestURL) - for _, assetURL := range assets { - if err := downloadAsset(assetURL, destDir); err != nil { - fmt.Printf("Warning: failed to download asset %s: %v\n", assetURL, err) - } - } + //assets := collectAssets(manifest, manifestURL) + //for _, assetURL := range assets { + // if err := downloadAsset(assetURL, destDir); err != nil { + // fmt.Printf("Warning: failed to download asset %s: %v\n", assetURL, err) + // } + //} // Also save the root index.html if err := os.WriteFile(filepath.Join(destDir, "index.html"), body, 0644); err != nil { @@ -163,18 +163,18 @@ func findManifestURL(htmlContent, baseURL string) (string, error) { return manifestURL.String(), nil } -func fetchManifest(manifestURL string) (map[string]interface{}, error) { +func fetchManifest(manifest map[string]interface{}, manifestURL string) (map[string]interface{}, error) { resp, err := http.Get(manifestURL) if err != nil { return nil, err } defer resp.Body.Close() - var manifest map[string]interface{} - if err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil { + var manifestData map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&manifestData); err != nil { return nil, err } - return manifest, nil + return manifestData, nil } func collectAssets(manifest map[string]interface{}, manifestURL string) []string { diff --git a/cmd/core/cmd/sync.go b/cmd/core/cmd/dev/sync.go similarity index 99% rename from cmd/core/cmd/sync.go rename to cmd/core/cmd/dev/sync.go index 2c9fe24..5a99e9b 100644 --- a/cmd/core/cmd/sync.go +++ b/cmd/core/cmd/dev/sync.go @@ -1,4 +1,4 @@ -package cmd +package dev import ( "bytes" diff --git a/cmd/core/cmd/test_gen.go b/cmd/core/cmd/dev/test_gen.go similarity index 99% rename from cmd/core/cmd/test_gen.go rename to cmd/core/cmd/dev/test_gen.go index c7fc8c2..72ac9d0 100644 --- a/cmd/core/cmd/test_gen.go +++ b/cmd/core/cmd/dev/test_gen.go @@ -1,4 +1,4 @@ -package cmd +package dev import ( "bytes" diff --git a/cmd/core/cmd/tmpl/gui/go.mod.tmpl b/cmd/core/cmd/dev/tmpl/gui/go.mod.tmpl similarity index 100% rename from cmd/core/cmd/tmpl/gui/go.mod.tmpl rename to cmd/core/cmd/dev/tmpl/gui/go.mod.tmpl diff --git a/cmd/core/cmd/tmpl/gui/html/.gitkeep b/cmd/core/cmd/dev/tmpl/gui/html/.gitkeep similarity index 100% rename from cmd/core/cmd/tmpl/gui/html/.gitkeep rename to cmd/core/cmd/dev/tmpl/gui/html/.gitkeep diff --git a/cmd/core/cmd/tmpl/gui/html/.placeholder b/cmd/core/cmd/dev/tmpl/gui/html/.placeholder similarity index 100% rename from cmd/core/cmd/tmpl/gui/html/.placeholder rename to cmd/core/cmd/dev/tmpl/gui/html/.placeholder diff --git a/cmd/core/cmd/tmpl/gui/main.go.tmpl b/cmd/core/cmd/dev/tmpl/gui/main.go.tmpl similarity index 100% rename from cmd/core/cmd/tmpl/gui/main.go.tmpl rename to cmd/core/cmd/dev/tmpl/gui/main.go.tmpl diff --git a/cmd/core/cmd/root.go b/cmd/core/cmd/root.go index cbadbb1..5b0e8a5 100644 --- a/cmd/core/cmd/root.go +++ b/cmd/core/cmd/root.go @@ -2,12 +2,19 @@ package cmd import ( "fmt" + "os" + "strings" "github.com/charmbracelet/lipgloss" "github.com/common-nighthawk/go-figure" "github.com/leaanthony/clir" + + "github.com/Snider/Core/cmd/core/cmd/dev" ) +// IsDevMode indicates if the application is running in development or CI mode. +var IsDevMode bool + // Define some global lipgloss styles for a Tailwind dark theme var ( coreStyle = lipgloss.NewStyle(). @@ -34,6 +41,20 @@ var ( // Execute creates the root CLI application and runs it. func Execute() error { + // Determine if in dev mode + executablePath, err := os.Executable() + if err == nil { + // Check if the executable path contains the build path for dev mode + if strings.Contains(executablePath, "Core/cmd/core/bin/") { + IsDevMode = true + } + } + + // Check for DEV or CI environment variables + if os.Getenv("DEV") != "" || os.Getenv("CI") != "" { + IsDevMode = true + } + // Create a new clir instance, removing the description and version to avoid the default header. app := clir.NewCli("core", "", "") @@ -66,17 +87,20 @@ func Execute() error { // Add the top-level commands devCmd := app.NewSubCommand("dev", "Development tools for Core Framework") - AddAPICommands(devCmd) - AddTestGenCommand(devCmd) - AddSyncCommand(devCmd) - AddBuildCommand(app) - AddTviewCommand(app) + dev.AddAPICommands(devCmd) + dev.AddTestGenCommand(devCmd) + dev.AddSyncCommand(devCmd) + dev.AddDocCommand(devCmd) + dev.AddBuildCommand(devCmd) // Run the application return app.Run() } // showBanner generates and prints the ASCII art banner. func showBanner() { + if !IsDevMode { + return + } coreFig := figure.NewFigure("Core", "big", true) frameworkFig := figure.NewFigure("Framework", "big", true) diff --git a/cmd/core/cmd/tview.go b/cmd/core/cmd/tview.go deleted file mode 100644 index be954d2..0000000 --- a/cmd/core/cmd/tview.go +++ /dev/null @@ -1,26 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/leaanthony/clir" - "github.com/rivo/tview" -) - -// AddTviewCommand adds the tview-example command to the clir app. -func AddTviewCommand(app *clir.Cli) { - tviewCmd := app.NewSubCommand("tview-example", "Runs a tview example to demonstrate its capabilities") - tviewCmd.LongDescription("This command launches a simple tview application to showcase its full-screen terminal UI features.") - tviewCmd.Action(func() error { - app := tview.NewApplication() - box := tview.NewBox(). - SetBorder(true). - SetTitle("Hello from tview!") - if err := app.SetRoot(box, true).Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error running tview app: %v\n", err) - os.Exit(1) - } - return nil - }) -}