gosick #1

Merged
DelphicOkami merged 162 commits from gosick into main 2026-03-21 23:08:00 +00:00
3 changed files with 212 additions and 116 deletions
Showing only changes of commit 8174c6a983 - Show all commits

1
go.mod
View File

@@ -12,6 +12,7 @@ require (
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.5 // indirect
github.com/alecthomas/kong v1.12.1 // indirect
github.com/cloudflare/circl v1.6.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect

2
go.sum
View File

@@ -5,6 +5,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/alecthomas/kong v1.12.1 h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0=
github.com/alecthomas/kong v1.12.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=

View File

@@ -1,6 +1,7 @@
package cli
import (
"errors"
"fmt"
"io"
"os"
@@ -8,6 +9,7 @@ import (
"git.hrafn.xyz/aether/gosick/internal/homesick/core"
"git.hrafn.xyz/aether/gosick/internal/homesick/version"
"github.com/alecthomas/kong"
)
func Run(args []string, stdout io.Writer, stderr io.Writer) int {
@@ -17,128 +19,219 @@ func Run(args []string, stdout io.Writer, stderr io.Writer) int {
return 1
}
if len(args) == 0 {
printHelp(stdout)
return 0
}
command := args[0]
switch command {
case "-v", "--version", "version":
if err := app.Version(version.String); err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
case "help", "--help", "-h":
printHelp(stdout)
return 0
case "clone":
if len(args) < 2 {
_, _ = fmt.Fprintln(stderr, "error: clone requires URI")
return 1
}
destination := ""
if len(args) > 2 {
destination = args[2]
}
if err := app.Clone(args[1], destination); err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
case "list":
if err := app.List(); err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
case "show_path":
castle := defaultCastle(args)
if err := app.ShowPath(castle); err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
case "status":
castle := defaultCastle(args)
if err := app.Status(castle); err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
case "diff":
castle := defaultCastle(args)
if err := app.Diff(castle); err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
case "link":
castle := defaultCastle(args)
if err := app.LinkCastle(castle); err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
case "unlink":
castle := defaultCastle(args)
if err := app.Unlink(castle); err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
case "track":
if len(args) < 2 || strings.TrimSpace(args[1]) == "" {
_, _ = fmt.Fprintln(stderr, "error: track requires FILE")
return 1
}
castle := "dotfiles"
if len(args) > 2 && strings.TrimSpace(args[2]) != "" {
castle = args[2]
}
if err := app.Track(args[1], castle); err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
case "pull", "push", "commit", "destroy", "cd", "open", "exec", "exec_all", "rc", "generate":
_, _ = fmt.Fprintf(stderr, "error: %s is not implemented in Go yet\n", command)
return 2
default:
_, _ = fmt.Fprintf(stderr, "error: unknown command %q\n\n", command)
printHelp(stderr)
parser, err := kong.New(
&cliModel{},
kong.Name("homesick"),
kong.Description("Go scaffold"),
kong.Writers(stdout, stderr),
kong.Exit(func(int) {}),
kong.ConfigureHelp(kong.HelpOptions{Compact: true}),
)
if err != nil {
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
}
func defaultCastle(args []string) string {
if len(args) > 1 && strings.TrimSpace(args[1]) != "" {
return args[1]
normalizedArgs := normalizeArgs(args)
ctx, err := parser.Parse(normalizedArgs)
if err != nil {
var parseErr *kong.ParseError
if errors.As(err, &parseErr) {
if parseErr.ExitCode() == 0 || isHelpRequest(normalizedArgs) {
return 0
}
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
if parseErr.Context != nil {
_ = parseErr.Context.PrintUsage(false)
}
return parseErr.ExitCode()
}
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return "dotfiles"
if err := ctx.Run(app); err != nil {
var exitErr *cliExitError
if errors.As(err, &exitErr) {
_, _ = fmt.Fprintf(stderr, "error: %v\n", exitErr.err)
return exitErr.code
}
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
return 1
}
return 0
}
func printHelp(w io.Writer) {
_, _ = fmt.Fprintln(w, "homesick (Go scaffold)")
_, _ = fmt.Fprintln(w, "")
_, _ = fmt.Fprintln(w, "Implemented commands:")
_, _ = fmt.Fprintln(w, " clone URI [CASTLE_NAME]")
_, _ = fmt.Fprintln(w, " list")
_, _ = fmt.Fprintln(w, " show_path [CASTLE]")
_, _ = fmt.Fprintln(w, " status [CASTLE]")
_, _ = fmt.Fprintln(w, " diff [CASTLE]")
_, _ = fmt.Fprintln(w, " link [CASTLE]")
_, _ = fmt.Fprintln(w, " unlink [CASTLE]")
_, _ = fmt.Fprintln(w, " track FILE [CASTLE]")
_, _ = fmt.Fprintln(w, " version | -v | --version")
_, _ = fmt.Fprintln(w, "")
_, _ = fmt.Fprintln(w, "Not implemented yet:")
_, _ = fmt.Fprintln(w, " pull, push, commit, destroy, cd, open, exec, exec_all, rc, generate")
_, _ = fmt.Fprintln(w, "")
_, _ = fmt.Fprintln(w, "Build: go build -o dist/homesick-go ./cmd/homesick")
type cliModel struct {
Clone cloneCmd `cmd:"" help:"Clone a castle."`
List listCmd `cmd:"" help:"List castles."`
ShowPath showPathCmd `cmd:"" name:"show_path" help:"Show the path of a castle."`
Status statusCmd `cmd:"" help:"Show git status for a castle."`
Diff diffCmd `cmd:"" help:"Show git diff for a castle."`
Link linkCmd `cmd:"" help:"Symlink all dotfiles from a castle."`
Unlink unlinkCmd `cmd:"" help:"Unsymlink all dotfiles from a castle."`
Track trackCmd `cmd:"" help:"Track a file in a castle."`
Version versionCmd `cmd:"" help:"Display the current version."`
Pull pullCmd `cmd:"" help:"Pull the specified castle."`
Push pushCmd `cmd:"" help:"Push the specified castle."`
Commit commitCmd `cmd:"" help:"Commit the specified castle's changes."`
Destroy destroyCmd `cmd:"" help:"Destroy a castle."`
Cd cdCmd `cmd:"" help:"Print the path to a castle."`
Open openCmd `cmd:"" help:"Open a castle."`
Exec execCmd `cmd:"" help:"Execute a command in a castle."`
ExecAll execAllCmd `cmd:"" name:"exec_all" help:"Execute a command in every castle."`
Rc rcCmd `cmd:"" help:"Run rc hooks for a castle."`
Generate generateCmd `cmd:"" help:"Generate a castle."`
}
type cloneCmd struct {
URI string `arg:"" name:"URI" help:"Castle URI to clone."`
Destination string `arg:"" optional:"" name:"CASTLE_NAME" help:"Optional local castle name."`
}
func (c *cloneCmd) Run(app *core.App) error {
return app.Clone(c.URI, c.Destination)
}
type listCmd struct{}
func (c *listCmd) Run(app *core.App) error {
return app.List()
}
type showPathCmd struct {
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
}
func (c *showPathCmd) Run(app *core.App) error {
return app.ShowPath(defaultCastle(c.Castle))
}
type statusCmd struct {
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
}
func (c *statusCmd) Run(app *core.App) error {
return app.Status(defaultCastle(c.Castle))
}
type diffCmd struct {
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
}
func (c *diffCmd) Run(app *core.App) error {
return app.Diff(defaultCastle(c.Castle))
}
type linkCmd struct {
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
}
func (c *linkCmd) Run(app *core.App) error {
return app.LinkCastle(defaultCastle(c.Castle))
}
type unlinkCmd struct {
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
}
func (c *unlinkCmd) Run(app *core.App) error {
return app.Unlink(defaultCastle(c.Castle))
}
type trackCmd struct {
File string `arg:"" name:"FILE" help:"File to track."`
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
}
func (c *trackCmd) Run(app *core.App) error {
return app.Track(c.File, defaultCastle(c.Castle))
}
type versionCmd struct{}
func (c *versionCmd) Run(app *core.App) error {
return app.Version(version.String)
}
type pullCmd struct{}
type pushCmd struct{}
type commitCmd struct{}
type destroyCmd struct{}
type cdCmd struct{}
type openCmd struct{}
type execCmd struct{}
type execAllCmd struct{}
type rcCmd struct{}
type generateCmd struct{}
func (c *pullCmd) Run() error { return notImplemented("pull") }
func (c *pushCmd) Run() error { return notImplemented("push") }
func (c *commitCmd) Run() error { return notImplemented("commit") }
func (c *destroyCmd) Run() error { return notImplemented("destroy") }
func (c *cdCmd) Run() error { return notImplemented("cd") }
func (c *openCmd) Run() error { return notImplemented("open") }
func (c *execCmd) Run() error { return notImplemented("exec") }
func (c *execAllCmd) Run() error { return notImplemented("exec_all") }
func (c *rcCmd) Run() error { return notImplemented("rc") }
func (c *generateCmd) Run() error { return notImplemented("generate") }
func defaultCastle(castle string) string {
if strings.TrimSpace(castle) == "" {
return "dotfiles"
}
return castle
}
func normalizeArgs(args []string) []string {
if len(args) == 0 {
return []string{"--help"}
}
switch args[0] {
case "-h", "--help":
return []string{"--help"}
case "help":
if len(args) == 1 {
return []string{"--help"}
}
return append(args[1:], "--help")
case "-v", "--version":
return []string{"version"}
default:
return args
}
}
func isHelpRequest(args []string) bool {
for _, arg := range args {
if arg == "-h" || arg == "--help" {
return true
}
}
return false
}
type cliExitError struct {
code int
err error
}
func (e *cliExitError) Error() string {
return e.err.Error()
}
func notImplemented(command string) error {
return &cliExitError{code: 2, err: fmt.Errorf("%s is not implemented in Go yet", command)}
}
func init() {