Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions cli/cmd_export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cli

import (
"os"

"github.com/spf13/cobra"

Check failure on line 6 in cli/cmd_export.go

View workflow job for this annotation

GitHub Actions / govulncheck

could not import github.com/spf13/cobra (invalid package name: "")

Check failure on line 6 in cli/cmd_export.go

View workflow job for this annotation

GitHub Actions / govulncheck

missing go.sum entry for module providing package github.com/spf13/cobra (imported by github.com/tamnd/tiobe-cli/cli); to add:

Check failure on line 6 in cli/cmd_export.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

missing go.sum entry for module providing package github.com/spf13/cobra (imported by github.com/tamnd/tiobe-cli/cli); to add:

Check failure on line 6 in cli/cmd_export.go

View workflow job for this annotation

GitHub Actions / test (macos-latest)

missing go.sum entry for module providing package github.com/spf13/cobra (imported by github.com/tamnd/tiobe-cli/cli); to add:
)

func (a *App) exportCmd() *cobra.Command {
var outFile string
cmd := &cobra.Command{
Use: "export",
Short: "Export all TIOBE index entries as JSONL",
Long: `export fetches the current TIOBE index and writes one JSON record per line.

Examples:
tiobe export > tiobe.jsonl
tiobe export --out tiobe.jsonl
tiobe export -o csv > tiobe.csv`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
entries, err := a.client.Index(cmd.Context())
if err != nil {
return mapFetchErr(err)
}
if outFile != "" {
f, err := os.Create(outFile)
if err != nil {
return codeError(exitError, err)
}
defer f.Close()
r := a.newRendererTo(f)
return r.Render(entries)
}
return a.renderOrEmpty(entries, len(entries))
},
}
cmd.Flags().StringVar(&outFile, "out", "", "write output to FILE instead of stdout")
return cmd
}
24 changes: 24 additions & 0 deletions cli/cmd_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cli

import "github.com/spf13/cobra"

func (a *App) infoCmd() *cobra.Command {
return &cobra.Command{
Use: "info",
Short: "Show TIOBE index statistics",
Long: `info prints aggregate statistics: total languages tracked and the current
top language.

Examples:
tiobe info
tiobe info -o json`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
info, err := a.client.Stats(cmd.Context())
if err != nil {
return mapFetchErr(err)
}
return a.render(info)
},
}
}
32 changes: 32 additions & 0 deletions cli/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cli

import (
"io"

render "github.com/tamnd/tiobe-cli/pkg"

Check failure on line 6 in cli/output.go

View workflow job for this annotation

GitHub Actions / govulncheck

could not import github.com/tamnd/tiobe-cli/pkg (invalid package name: "")

Check failure on line 6 in cli/output.go

View workflow job for this annotation

GitHub Actions / govulncheck

github.com/charmbracelet/fang@v1.0.0: missing go.sum entry for go.mod file; to add it:

Check failure on line 6 in cli/output.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

github.com/charmbracelet/fang@v1.0.0: missing go.sum entry for go.mod file; to add it:

Check failure on line 6 in cli/output.go

View workflow job for this annotation

GitHub Actions / test (macos-latest)

github.com/charmbracelet/fang@v1.0.0: missing go.sum entry for go.mod file; to add it:
)

type Format = render.Format

const (
FormatTable = render.FormatTable
FormatJSON = render.FormatJSON
FormatJSONL = render.FormatJSONL
FormatCSV = render.FormatCSV
FormatTSV = render.FormatTSV
FormatURL = render.FormatURL
FormatRaw = render.FormatRaw
)

func NewRenderer(w io.Writer, format Format, fields []string, noHeader bool, tmpl string) *render.Renderer {
return render.New(w, format, fields, noHeader, tmpl)
}

// newRendererTo builds a renderer writing to w using the App's current settings.
func (a *App) newRendererTo(w io.Writer) *render.Renderer {
format := Format(a.output)
if !format.Valid() {
format = FormatJSONL
}
return NewRenderer(w, format, a.fields, a.noHeader, a.template)
}
107 changes: 97 additions & 10 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,117 @@
package cli

import (
"fmt"
"os"

"github.com/mattn/go-isatty"

Check failure on line 8 in cli/root.go

View workflow job for this annotation

GitHub Actions / govulncheck

could not import github.com/mattn/go-isatty (invalid package name: "")

Check failure on line 8 in cli/root.go

View workflow job for this annotation

GitHub Actions / govulncheck

github.com/charmbracelet/fang@v1.0.0: missing go.sum entry for go.mod file; to add it:

Check failure on line 8 in cli/root.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

github.com/charmbracelet/fang@v1.0.0: missing go.sum entry for go.mod file; to add it:

Check failure on line 8 in cli/root.go

View workflow job for this annotation

GitHub Actions / test (macos-latest)

github.com/charmbracelet/fang@v1.0.0: missing go.sum entry for go.mod file; to add it:
"github.com/spf13/cobra"
"github.com/tamnd/tiobe-cli/tiobe"
)

// Build metadata, set via -ldflags at release time.
var (
Version = "dev"
Commit = "none"
Date = "unknown"
)

// Root builds the root command and its subtree.
const (
exitError = 1
exitUsage = 2
exitNoData = 3
)

type ExitError struct {
Code int
Err error
}

func (e *ExitError) Error() string {
if e.Err != nil {
return e.Err.Error()
}
return fmt.Sprintf("exit %d", e.Code)
}

func (e *ExitError) Unwrap() error { return e.Err }

func codeError(code int, err error) error { return &ExitError{Code: code, Err: err} }

type App struct {
client *tiobe.Client
cfg tiobe.Config
output string
fields []string
noHeader bool
template string
limit int
}

func Root() *cobra.Command {
root := &cobra.Command{
Use: "tiobe",
Short: "A command line for tiobe.",
Long: `A command line for tiobe.
app := &App{cfg: tiobe.DefaultConfig()}

This is a fresh scaffold. Add your commands here on top of the tiobe
library package, then wire them into Root with root.AddCommand.`,
root := &cobra.Command{
Use: "tiobe",
Short: "Browse the TIOBE programming language index from the command line",
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
return app.setup()
},
}

root.AddCommand(newVersionCmd())
// TODO: root.AddCommand(newGetCmd()), etc.
pf := root.PersistentFlags()
pf.StringVarP(&app.output, "output", "o", "auto", "output: table|json|jsonl|csv|tsv|url|raw")
pf.StringSliceVar(&app.fields, "fields", nil, "comma-separated columns to include")
pf.BoolVar(&app.noHeader, "no-header", false, "omit header row in table/csv/tsv")
pf.StringVar(&app.template, "template", "", "Go text/template per record")
pf.IntVarP(&app.limit, "limit", "n", 0, "limit number of records (0 = all)")
pf.DurationVar(&app.cfg.Rate, "delay", app.cfg.Rate, "minimum spacing between requests")
pf.DurationVar(&app.cfg.Timeout, "timeout", app.cfg.Timeout, "per-request timeout")
pf.IntVar(&app.cfg.Retries, "retries", app.cfg.Retries, "retry attempts on 429/5xx")

root.AddCommand(
app.indexCmd(),

Check failure on line 75 in cli/root.go

View workflow job for this annotation

GitHub Actions / govulncheck

app.indexCmd undefined (type *App has no field or method indexCmd)

Check failure on line 75 in cli/root.go

View workflow job for this annotation

GitHub Actions / lint

app.indexCmd undefined (type *App has no field or method indexCmd) (typecheck)
app.exportCmd(),
app.infoCmd(),
newVersionCmd(),
)
return root
}

func (a *App) setup() error {
if a.output == "" || a.output == "auto" {
if isatty.IsTerminal(os.Stdout.Fd()) {

Check failure on line 85 in cli/root.go

View workflow job for this annotation

GitHub Actions / govulncheck

undefined: isatty

Check failure on line 85 in cli/root.go

View workflow job for this annotation

GitHub Actions / lint

undefined: isatty (typecheck)
a.output = string(FormatTable)
} else {
a.output = string(FormatJSONL)
}
}
if !Format(a.output).Valid() {
return codeError(exitUsage, fmt.Errorf("unknown output format %q", a.output))
}
a.client = tiobe.NewClient(a.cfg)
return nil
}

func (a *App) render(records any) error {
r := NewRenderer(os.Stdout, Format(a.output), a.fields, a.noHeader, a.template)
return r.Render(records)
}

func (a *App) renderOrEmpty(records any, n int) error {
if err := a.render(records); err != nil {
return err
}
if n == 0 {
return codeError(exitNoData, nil)
}
return nil
}

func mapFetchErr(err error) error {
if err == nil {
return nil
}
return codeError(exitError, err)
}
Loading
Loading