Skip to content
Merged
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
52 changes: 7 additions & 45 deletions packages/go/driver/cmd/fmtkit-go/main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Command fmtkit-go is the standalone Go formatter CLI. Its command surface
// lives in internal/app (app.GoCLI); this entrypoint only carries the version
// stamped in by -X main.version and the signal handling.
package main

import (
"context"
"fmt"
"io"
"os"
"os/signal"
"syscall"

"go.ollin.sh/fmtkit/driver/internal/cli"
"go.ollin.sh/fmtkit/driver/internal/app"
)

var version = "dev"
Expand All @@ -18,49 +19,10 @@ func main() {

// os.Exit skips deferred calls, so release the signal handler explicitly
// before exiting with the captured code.
code := run(ctx, os.Args[1:], os.Stdout, os.Stderr)
code := app.
GoCLI(version, os.Stdout, os.Stderr).
Dispatch(ctx, os.Args[1:])

stop()
os.Exit(code)
}

func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
printUsage(stderr)

return 1
}

switch args[0] {
case "check":
return cli.
NewRunner(stdout, stderr).
Run(ctx, cli.CheckMode, args[1:])
case "format":
return cli.
NewRunner(stdout, stderr).
Run(ctx, cli.FormatMode, args[1:])
case "sources":
return cli.RunSources(ctx, args[1:], stdout, stderr)
case "version", "--version", "-version":
_, _ = fmt.Fprintf(stdout, "fmtkit %s\n", version)

return 0
case "help", "--help", "-h":
printUsage(stderr)

return 0
default:
_, _ = fmt.Fprintf(stderr, "unknown subcommand - {%q}\n\n", args[0])

printUsage(stderr)

return 1
}
}

func printUsage(w io.Writer) {
_, _ = fmt.Fprintf(w, "fmtkit check [paths...]\n\n")
_, _ = fmt.Fprintf(w, "fmtkit format [paths...]\n\n")
_, _ = fmt.Fprintf(w, "fmtkit sources [--include-declarations] [paths...]\n\n")
}
3 changes: 2 additions & 1 deletion packages/go/driver/cmd/fmtkit-go/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"testing"

"go.ollin.sh/fmtkit/driver/internal/app"
"go.ollin.sh/fmtkit/driver/testutil"
)

Expand Down Expand Up @@ -492,7 +493,7 @@ func runCLI(t *testing.T, workdir string, args ...string) (int, string, string)
var stdout strings.Builder

var stderr strings.Builder
exitCode := run(context.Background(), args, &stdout, &stderr)
exitCode := app.GoCLI("dev", &stdout, &stderr).Dispatch(context.Background(), args)

return exitCode, stdout.String(), stderr.String()
}
Expand Down
4 changes: 2 additions & 2 deletions packages/go/driver/cmd/fmtkit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ func main() {
// os.Exit skips deferred calls, so release the signal handler explicitly
// before exiting with the captured code.
code := app.
New(version, os.Stdout, os.Stderr).
Run(ctx, os.Args[1:])
Umbrella(version, os.Stdout, os.Stderr).
Dispatch(ctx, os.Args[1:])

stop()
os.Exit(code)
Expand Down
179 changes: 133 additions & 46 deletions packages/go/driver/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,68 +5,155 @@ import (
"fmt"
"io"

"go.ollin.sh/fmtkit/driver/internal/cli"
"go.ollin.sh/fmtkit/driver/internal/command"
"go.ollin.sh/fmtkit/driver/internal/gotool"
"go.ollin.sh/fmtkit/driver/internal/sourcefiles"
report "go.ollin.sh/fmtkit/driver/report"
)

// App is the fmtkit command surface. The version is injected by the binary so
// release builds keep stamping it through -X main.version.
type App struct {
// deps carries what every command handler needs: the version stamped by the
// binary and the output streams. It is a pointer so the usage printer can be
// wired in after the Set is built.
type deps struct {
version string
stdout io.Writer
stderr io.Writer

// usage prints the enclosing Set's usage text; wired after the Set exists so
// the flag-parsing handlers can reprint it on a bad argument.
usage func(io.Writer)
}

func New(version string, stdout, stderr io.Writer) App {
return App{
version: version,
stdout: stdout,
stderr: stderr,
// umbrellaHeader is the top line of the umbrella usage; the per-command lines
// follow from each Command's Usage.
const umbrellaHeader = "usage: fmtkit <format|format-all|go|ts|lint|check|version|help> [args...]\n"

// Umbrella builds the fmtkit command surface: the pipeline commands plus the
// embedded Go formatter CLI reached through `fmtkit go`.
func Umbrella(version string, stdout, stderr io.Writer) command.Set {
d := &deps{version: version, stdout: stdout, stderr: stderr}

// The Go CLI reached through `fmtkit go` prints "fmtkit go ..." usage and
// adopts the umbrella's exit code for bad subcommands.
goSet := d.goCommandSet("fmtkit go", 2)

set := command.Set{
Name: "fmtkit",
Header: umbrellaHeader,
ErrExit: 2,
Stderr: stderr,
Commands: []command.Command{
{
Name: "format",
Usage: " format [--ts] [--go] [--quiet] [paths...] format changed files (vs HEAD) and untracked ones\n",
Run: d.runFormat,
},
{
Name: "format-all",
Usage: " format-all [--ts] [--go] [--quiet] format every file, against .\n --ts only TS/Vue lint + formatting; --go only Go formatting; default: all\n",
Run: d.runFormatAll,
},
{
Name: "go",
Usage: " go <check|format|sources|version|help> run the Go formatter CLI\n",
Run: goSet.Dispatch,
},
{
Name: "ts",
Usage: " ts [paths...] run TS/Vue formatting support and oxfmt\n",
Run: d.runTS,
},
{
Name: "lint",
Usage: " lint [paths...] lint TS/Vue files with oxlint\n",
Run: d.runLint,
},
{
Name: "check",
Usage: " check [args...] run the Go formatter in check mode\n",
Run: d.runCheck,
},
{
Name: "version",
Aliases: []string{"--version", "-version"},
Usage: " version print the fmtkit version\n",
Run: d.runVersion,
},
},
}

d.usage = set.PrintUsage

return set
}

// Run dispatches a subcommand to its handler; each mode lives in its own file.
func (a App) Run(ctx context.Context, args []string) int {
if len(args) == 0 {
printUsage(a.stderr)
// GoCLI builds the standalone fmtkit-go command surface: check, format,
// sources, version, and help, exiting 1 on a bad subcommand.
func GoCLI(version string, stdout, stderr io.Writer) command.Set {
d := &deps{version: version, stdout: stdout, stderr: stderr}

set := d.goCommandSet("fmtkit", 1)

d.usage = set.PrintUsage

return 2
return set
}

// goCommandSet builds the Go formatter command group. name is the usage prefix
// ("fmtkit" standalone, "fmtkit go" under the umbrella) and errExit is the
// exit code for an empty or unknown subcommand.
func (d *deps) goCommandSet(name string, errExit int) command.Set {
usage := func(sub string) string {
return name + " " + sub + "\n\n"
}

mode := args[0]
rest := args[1:]

switch mode {
case "format":
return a.runFormat(ctx, rest)
case "format-all":
return a.runFormatAll(ctx, rest)
case "ts":
return a.runTS(ctx, rest)
case "lint":
return a.runLint(ctx, rest)
case "go":
return a.runGo(ctx, rest)
case "check":
return cli.
NewRunner(a.stdout, a.stderr).
Run(ctx, cli.CheckMode, rest)
case "version", "--version", "-version":
return a.printVersion()
case "help", "--help", "-h":
printUsage(a.stderr)

return 0
default:
_, _ = fmt.Fprintf(a.stderr, "unknown subcommand - {%q}\n\n", mode)

printUsage(a.stderr)

return 2
return command.Set{
Name: name,
ErrExit: errExit,
Stderr: d.stderr,
Commands: []command.Command{
{
Name: "check",
Usage: usage("check [paths...]"),
Run: d.runCheck,
},
{
Name: "format",
Usage: usage("format [paths...]"),
Run: d.runGoFormat,
},
{
Name: "sources",
Usage: usage("sources [--include-declarations] [paths...]"),
Run: func(ctx context.Context, args []string) int {
return sourcefiles.Run(ctx, args, d.stdout, d.stderr)
},
},
{
Name: "version",
Aliases: []string{"--version", "-version"},
Run: d.runVersion,
},
},
}
}

func (a App) printVersion() int {
_, _ = fmt.Fprintf(a.stdout, "fmtkit %s\n", a.version)
// goRunner is the unscoped Go formatter runner shared by `check` and the
// standalone `format`.
func (d *deps) goRunner() gotool.Runner {
return gotool.Runner{Stdout: d.stdout, Stderr: d.stderr}
}

func (d *deps) runCheck(ctx context.Context, args []string) int {
return d.goRunner().Run(ctx, report.ModeCheck, args)
}

func (d *deps) runGoFormat(ctx context.Context, args []string) int {
return d.goRunner().Run(ctx, report.ModeFormat, args)
}

func (d *deps) runVersion(_ context.Context, _ []string) int {
_, _ = fmt.Fprintf(d.stdout, "fmtkit %s\n", d.version)

return 0
}
2 changes: 1 addition & 1 deletion packages/go/driver/internal/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func runCLI(t *testing.T, workdir string, args ...string) (int, string, string)
var stderr strings.Builder

// "dev" mirrors the unstamped binary: no embedded TS assets.
exitCode := New("dev", &stdout, &stderr).Run(context.Background(), args)
exitCode := Umbrella("dev", &stdout, &stderr).Dispatch(context.Background(), args)

return exitCode, stdout.String(), stderr.String()
}
Expand Down
4 changes: 2 additions & 2 deletions packages/go/driver/internal/app/exit.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// reportError maps a tool failure onto an exit code, propagating the child's
// own code when it already reported the problem itself.
func (a App) reportError(err error) int {
func (d *deps) reportError(err error) int {
if err == nil {
return 0
}
Expand All @@ -19,7 +19,7 @@ func (a App) reportError(err error) int {
return exit.ExitCode()
}

_, _ = fmt.Fprintf(a.stderr, "fmtkit: %v\n", err)
_, _ = fmt.Fprintf(d.stderr, "fmtkit: %v\n", err)

return 1
}
Loading
Loading