diff --git a/.gitignore b/.gitignore index d96ff526..6ee2a7a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /hcloud /dist /bats +/completions +/manpages /cmd/hcloud/hcloud hcloud_cli.p12 coverage.txt diff --git a/.goreleaser.yml b/.goreleaser.yml index 226c3f4d..0c4ebf05 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -2,6 +2,8 @@ version: 2 before: hooks: - go mod tidy + - ./scripts/completions.sh + - go run scripts/manpages.go builds: - <<: &build_defaults @@ -17,13 +19,26 @@ builds: env: - CGO_ENABLED=0 - id: hcloud-build - goos: [freebsd, windows, linux] + id: hcloud-build-linux-main + goos: [linux] + goarch: [amd64, arm64] + + - <<: *build_defaults + id: hcloud-build-linux-other + goos: [ linux ] + goarch: [ arm, "386" ] + goarm: [ "6", "7" ] + + - <<: *build_defaults + id: hcloud-build-freebsd + goos: [ freebsd ] goarch: [amd64, arm, arm64, "386"] goarm: ["6", "7"] - ignore: - - goos: windows - goarch: arm + + - <<: *build_defaults + id: hcloud-build-windows + goos: [ windows ] + goarch: [ amd64, arm64, "386" ] - <<: *build_defaults id: hcloud-build-darwin @@ -37,9 +52,46 @@ builds: - cmd: bash -c 'quill sign-and-notarize "{{ .Path }}" --dry-run={{ .IsSnapshot }} --ad-hoc={{ .IsSnapshot }} || touch sign-and-notarize.error' output: true +nfpms: + - id: default + file_name_template: "{{ .ConventionalFileName }}" + package_name: hcloud-cli + ids: [hcloud-build-linux-main] + provides: [hcloud] + vendor: Hetzner Cloud GmbH + homepage: https://github.com/hetznercloud/cli + maintainer: Hetzner Cloud GmbH + formats: + - deb + - rpm + description: A command-line interface for Hetzner Cloud + license: MIT + + contents: + - src: ./completions/hcloud.bash + dst: /usr/share/bash-completion/completions/hcloud + file_info: + mode: 0644 + - src: ./completions/hcloud.fish + dst: /usr/share/fish/vendor_completions.d/hcloud.fish + file_info: + mode: 0644 + - src: ./completions/hcloud.zsh + dst: /usr/share/zsh/vendor-completions/_hcloud + file_info: + mode: 0644 + - src: ./LICENSE + dst: /usr/share/doc/hcloud/license + file_info: + mode: 0644 + - src: ./manpages/* + dst: /usr/share/man/man1/ + file_info: + mode: 0644 + kos: - id: container-images - build: hcloud-build + build: hcloud-build-linux-main main: ./cmd/hcloud/ repositories: - hetznercloud/cli @@ -71,9 +123,6 @@ signs: - artifacts: all signature: ${artifact}.sig id: hcloud-sign - ids: - - hcloud-build - - hcloud-build-darwin args: - --batch - --local-user=github-bot@hetzner-cloud.de @@ -84,9 +133,6 @@ signs: archives: - id: hcloud-archive - ids: - - hcloud-build - - hcloud-build-darwin name_template: "{{ .Binary }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" format_overrides: diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index 3aac6d82..9e2afcc9 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -2,20 +2,42 @@ package config import ( _ "embed" + "fmt" "github.com/spf13/cobra" "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" ) //go:embed helptext/other.txt var nonPreferenceOptions string +//go:embed helptext/other.md +var nonPreferenceOptionsMd string + //go:embed helptext/preferences.txt var preferenceOptions string +//go:embed helptext/preferences.md +var preferenceOptionsMd string + func NewCommand(s state.State) *cobra.Command { + var ( + nonPreferenceOptions = nonPreferenceOptions + preferenceOptions = preferenceOptions + ) + + useMarkdown, err := config.OptionMarkdownTableFormat.Get(s.Config()) + fmt.Println(useMarkdown) + fmt.Println(err) + + if useMarkdown, err := config.OptionMarkdownTableFormat.Get(s.Config()); err == nil && useMarkdown { + nonPreferenceOptions = nonPreferenceOptionsMd + preferenceOptions = preferenceOptionsMd + } + cmd := &cobra.Command{ Use: "config", Short: "Manage configuration", diff --git a/internal/cmd/config/helptext/generate.go b/internal/cmd/config/helptext/generate.go index 6345c53d..e3df835c 100644 --- a/internal/cmd/config/helptext/generate.go +++ b/internal/cmd/config/helptext/generate.go @@ -17,28 +17,18 @@ import ( func main() { generateTable( - "preferences.txt", + "preferences", config.OptionFlagPreference|config.OptionFlagHidden, config.OptionFlagPreference, table.Row{"sort.", "Default sorting for resource", "string list", "sort.", "HCLOUD_SORT_", ""}, ) - generateTable("other.txt", + generateTable("other", config.OptionFlagPreference|config.OptionFlagHidden, 0, ) } func generateTable(outFile string, mask, filter config.OptionFlag, extraRows ...table.Row) { - f, err := os.OpenFile(outFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) //nolint:gosec - if err != nil { - panic(err) - } - defer func() { - if err := f.Close(); err != nil { - panic(err) - } - }() - t := table.NewWriter() t.SetStyle(table.StyleLight) t.SetColumnConfigs([]table.ColumnConfig{ @@ -49,7 +39,6 @@ func generateTable(outFile string, mask, filter config.OptionFlag, extraRows ... }, }) - t.SetOutputMirror(f) t.AppendHeader(table.Row{"Option", "Description", "Type", "Config key", "Environment variable", "Flag"}) var opts []config.IOption @@ -74,7 +63,15 @@ func generateTable(outFile string, mask, filter config.OptionFlag, extraRows ... t.AppendSeparator() } - t.Render() + err := os.WriteFile(outFile+".txt", []byte(t.Render()+"\n"), 0644) + if err != nil { + panic(err) + } + + err = os.WriteFile(outFile+".md", []byte(t.RenderMarkdown()+"\n"), 0644) + if err != nil { + panic(err) + } } func getTypeName(opt config.IOption) string { diff --git a/internal/cmd/config/helptext/other.md b/internal/cmd/config/helptext/other.md new file mode 100644 index 00000000..63666b62 --- /dev/null +++ b/internal/cmd/config/helptext/other.md @@ -0,0 +1,5 @@ +| Option | Description | Type | Config key | Environment variable | Flag | +| --- | --- | --- | --- | --- | --- | +| config | Config file path (default "~/.config/hcloud/cli.toml") | string | | HCLOUD_CONFIG | --config | +| context | Currently active context | string | active_context | HCLOUD_CONTEXT | --context | +| token | Hetzner Cloud API token | string | token | HCLOUD_TOKEN | | diff --git a/internal/cmd/config/helptext/preferences.md b/internal/cmd/config/helptext/preferences.md new file mode 100644 index 00000000..7a8dc522 --- /dev/null +++ b/internal/cmd/config/helptext/preferences.md @@ -0,0 +1,12 @@ +| Option | Description | Type | Config key | Environment variable | Flag | +| --- | --- | --- | --- | --- | --- | +| debug | Enable debug output | boolean | debug | HCLOUD_DEBUG | --debug | +| debug-file | File to write debug output to | string | debug_file | HCLOUD_DEBUG_FILE | --debug-file | +| default-ssh-keys | Default SSH Keys for new Servers and Storage Boxes | string list | default_ssh_keys | HCLOUD_DEFAULT_SSH_KEYS | | +| endpoint | Hetzner Cloud API endpoint | string | endpoint | HCLOUD_ENDPOINT | --endpoint | +| hetzner-endpoint | Hetzner API endpoint | string | hetzner_endpoint | HETZNER_ENDPOINT | --hetzner-endpoint | +| markdown-table-format | Force table formatting in help outputs to markdown | boolean | markdown_table_format | HCLOUD_MARKDOWN_TABLE_FORMAT | --markdown-table-format | +| no-experimental-warnings | If true, experimental warnings are not shown | boolean | no_experimental_warnings | HCLOUD_NO_EXPERIMENTAL_WARNINGS | --no-experimental-warnings | +| poll-interval | Interval at which to poll information, for example action progress | duration | poll_interval | HCLOUD_POLL_INTERVAL | --poll-interval | +| quiet | If true, only print error messages | boolean | quiet | HCLOUD_QUIET | --quiet | +| sort. | Default sorting for resource | string list | sort. | HCLOUD_SORT_ | | diff --git a/internal/cmd/config/helptext/preferences.txt b/internal/cmd/config/helptext/preferences.txt index 0f6e4cde..6bcae72d 100644 --- a/internal/cmd/config/helptext/preferences.txt +++ b/internal/cmd/config/helptext/preferences.txt @@ -15,6 +15,10 @@ ├──────────────────────────┼──────────────────────┼─────────────┼──────────────────────────┼─────────────────────────────────┼────────────────────────────┤ │ hetzner-endpoint │ Hetzner API endpoint │ string │ hetzner_endpoint │ HETZNER_ENDPOINT │ --hetzner-endpoint │ ├──────────────────────────┼──────────────────────┼─────────────┼──────────────────────────┼─────────────────────────────────┼────────────────────────────┤ +│ markdown-table-format │ Force table │ boolean │ markdown_table_format │ HCLOUD_MARKDOWN_TABLE_FORMAT │ --markdown-table-format │ +│ │ formatting in help │ │ │ │ │ +│ │ outputs to markdown │ │ │ │ │ +├──────────────────────────┼──────────────────────┼─────────────┼──────────────────────────┼─────────────────────────────────┼────────────────────────────┤ │ no-experimental-warnings │ If true, │ boolean │ no_experimental_warnings │ HCLOUD_NO_EXPERIMENTAL_WARNINGS │ --no-experimental-warnings │ │ │ experimental │ │ │ │ │ │ │ warnings are not │ │ │ │ │ diff --git a/internal/scripts/generate_docs.go b/internal/scripts/generate_docs.go index be584a85..143c0fd5 100644 --- a/internal/scripts/generate_docs.go +++ b/internal/scripts/generate_docs.go @@ -40,6 +40,7 @@ func run() error { cmd := cli.NewRootCommand(s) // Generate the docs + config.OptionMarkdownTableFormat.Override(cfg, true) if err := doc.GenMarkdownTree(cmd, dir); err != nil { return fmt.Errorf("error generating docs: %w", err) } diff --git a/internal/state/config/options.go b/internal/state/config/options.go index 7f9c21d6..11177c66 100644 --- a/internal/state/config/options.go +++ b/internal/state/config/options.go @@ -160,6 +160,15 @@ var ( nil, ) + OptionMarkdownTableFormat = newOpt( + "markdown-table-format", + "Force table formatting in help outputs to markdown", + false, + DefaultPreferenceFlags, + nil, + nil, + ) + OptionPollInterval = newOpt( "poll-interval", "Interval at which to poll information, for example action progress", diff --git a/scripts/completions.sh b/scripts/completions.sh new file mode 100755 index 00000000..92af5720 --- /dev/null +++ b/scripts/completions.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -e +rm -rf completions +mkdir completions +for sh in bash zsh fish; do + go run ./cmd/hcloud completion "$sh" >"completions/hcloud.$sh" +done diff --git a/scripts/manpages.go b/scripts/manpages.go new file mode 100644 index 00000000..d201ca7e --- /dev/null +++ b/scripts/manpages.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + "regexp" + + "github.com/spf13/cobra/doc" + + "github.com/hetznercloud/cli/internal/cli" + "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" +) + +const directory = "./manpages" + +func main() { + //nolint:gosec + if err := os.MkdirAll(directory, 0755); err != nil { + log.Fatal(err) + } + + s, _ := state.New(config.New()) + rootCommand := cli.NewRootCommand(s) + + err := doc.GenManTree(rootCommand, nil, directory) + if err != nil { + log.Fatal(err) + } + + files, err := os.ReadDir(directory) + if err != nil { + log.Fatal(err) + } + + generatedOnRegex := regexp.MustCompile(`\n\n\.SH HISTORY\n.* Auto generated by spf13/cobra\n`) + + for _, f := range files { + if f.IsDir() { + continue + } + path := filepath.Join(directory, f.Name()) + bytes, err := os.ReadFile(path) + if err != nil { + log.Fatalf("could not read file at %q: %v", path, err) + } + bytes = generatedOnRegex.ReplaceAll(bytes, nil) + err = os.WriteFile(path, bytes, f.Type()) + if err != nil { + log.Fatalf("could not write file at %q: %w", path, err) + } + } + + fmt.Println("Man pages generated successfully") +}