From e5cd23de484e37af5846bc053175e731303ee703 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 29 May 2026 16:52:02 +0000 Subject: [PATCH] Establish initial Go application skeleton --- .gitignore | 3 +-- README.md | 20 +++++++++++++++ cmd/weatherreporter/main.go | 16 ++++++++++++ docs/cli.md | 23 +++++++++++++++++ go.mod | 3 +++ internal/app/doc.go | 2 ++ internal/cli/root.go | 45 +++++++++++++++++++++++++++++++++ internal/cli/root_test.go | 50 +++++++++++++++++++++++++++++++++++++ internal/config/doc.go | 3 +++ 9 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 cmd/weatherreporter/main.go create mode 100644 docs/cli.md create mode 100644 go.mod create mode 100644 internal/app/doc.go create mode 100644 internal/cli/root.go create mode 100644 internal/cli/root_test.go create mode 100644 internal/config/doc.go diff --git a/.gitignore b/.gitignore index db90548..cc85814 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Compiled application binary -weatherreporter +/weatherreporter # ---> Go # If you prefer the allow list template instead of the deny list, see community template: @@ -71,4 +71,3 @@ Icon Network Trash Folder Temporary Items .apdisk - diff --git a/README.md b/README.md index 3d54a96..e19d1b0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,22 @@ # weatherreporter +`weatherreporter` is a Go application for preparing human-facing weather +reports from normalized forecast data. + +The repository currently contains the application skeleton and a minimal CLI +help command. Report generation, configuration loading, weather API fetching, +and `scriptorium` rendering are tracked in the roadmap and are not implemented +yet. + +## Quickstart + +```sh +weatherreporter --help +``` + +## Documentation + +- [CLI reference](docs/cli.md) +- [Architecture policy](docs/policy/architecture.md) +- [Development policy](docs/policy/development.md) +- [Implementation roadmap](docs/roadmap/initial.md) diff --git a/cmd/weatherreporter/main.go b/cmd/weatherreporter/main.go new file mode 100644 index 0000000..61d4cd4 --- /dev/null +++ b/cmd/weatherreporter/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "context" + "fmt" + "os" + + "gitea.maximumdirect.net/eric/weatherreporter/internal/cli" +) + +func main() { + if err := cli.Run(context.Background(), os.Args[1:], os.Stdout, os.Stderr); err != nil { + fmt.Fprintf(os.Stderr, "weatherreporter: %v\n", err) + os.Exit(1) + } +} diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 0000000..3003049 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,23 @@ +# Weatherreporter CLI + +`weatherreporter` currently exposes only the root help command while the +application skeleton is being established. + +## Shortest Useful Command + +```sh +weatherreporter --help +``` + +## Command Overview + +```text +weatherreporter --help +``` + +Shows the available command-line help without loading configuration or calling +external services. + +## Flags + +- `-h`, `--help`: show help. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b9acf78 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitea.maximumdirect.net/eric/weatherreporter + +go 1.26 diff --git a/internal/app/doc.go b/internal/app/doc.go new file mode 100644 index 0000000..3a5d8cf --- /dev/null +++ b/internal/app/doc.go @@ -0,0 +1,2 @@ +// Package app owns application orchestration and top-level use cases. +package app diff --git a/internal/cli/root.go b/internal/cli/root.go new file mode 100644 index 0000000..5b57826 --- /dev/null +++ b/internal/cli/root.go @@ -0,0 +1,45 @@ +package cli + +import ( + "context" + "flag" + "fmt" + "io" +) + +const helpText = `weatherreporter prepares weather reports from normalized forecast data. + +Usage: + weatherreporter --help + +Options: + -h, --help Show this help message. +` + +// Run parses the root command and executes the selected behavior. +func Run(ctx context.Context, args []string, stdout io.Writer, stderr io.Writer) error { + _ = ctx + _ = stderr + + fs := flag.NewFlagSet("weatherreporter", flag.ContinueOnError) + fs.SetOutput(io.Discard) + + help := fs.Bool("help", false, "show help") + fs.BoolVar(help, "h", false, "show help") + + if err := fs.Parse(args); err != nil { + return err + } + + if *help { + _, err := fmt.Fprint(stdout, helpText) + return err + } + + if fs.NArg() > 0 { + return fmt.Errorf("unknown argument %q", fs.Arg(0)) + } + + _, err := fmt.Fprint(stdout, helpText) + return err +} diff --git a/internal/cli/root_test.go b/internal/cli/root_test.go new file mode 100644 index 0000000..0ed4202 --- /dev/null +++ b/internal/cli/root_test.go @@ -0,0 +1,50 @@ +package cli + +import ( + "bytes" + "context" + "strings" + "testing" +) + +func TestRunHelpLongFlag(t *testing.T) { + var stdout bytes.Buffer + var stderr bytes.Buffer + + err := Run(context.Background(), []string{"--help"}, &stdout, &stderr) + if err != nil { + t.Fatalf("Run() error = %v", err) + } + + if !strings.Contains(stdout.String(), "Usage:") { + t.Fatalf("help output missing usage:\n%s", stdout.String()) + } +} + +func TestRunHelpShortFlag(t *testing.T) { + var stdout bytes.Buffer + var stderr bytes.Buffer + + err := Run(context.Background(), []string{"-h"}, &stdout, &stderr) + if err != nil { + t.Fatalf("Run() error = %v", err) + } + + if !strings.Contains(stdout.String(), "weatherreporter --help") { + t.Fatalf("help output missing root help command:\n%s", stdout.String()) + } +} + +func TestRunUnknownArgument(t *testing.T) { + var stdout bytes.Buffer + var stderr bytes.Buffer + + err := Run(context.Background(), []string{"generate"}, &stdout, &stderr) + if err == nil { + t.Fatal("Run() error = nil, want unknown argument error") + } + + if !strings.Contains(err.Error(), `unknown argument "generate"`) { + t.Fatalf("Run() error = %q, want unknown argument message", err.Error()) + } +} diff --git a/internal/config/doc.go b/internal/config/doc.go new file mode 100644 index 0000000..04a6075 --- /dev/null +++ b/internal/config/doc.go @@ -0,0 +1,3 @@ +// Package config owns application configuration structures, defaults, loading, +// precedence, and validation. +package config