-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
+ Use optionparser instead of flag + Remove `help` and `version` commands; make those flags only + Add `sync` subcommand: it runs update and clone in that order since (presumably) you don't need to update repos that you have just cloned
- Loading branch information
1 parent
d6ffc0c
commit 3308afa
Showing
19 changed files
with
871 additions
and
346 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,5 +7,5 @@ import ( | |
) | ||
|
||
func main() { | ||
os.Exit(cli.CmdGitmirror(os.Args[1:])) | ||
os.Exit(cli.CmdGitmirror(os.Args)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,5 @@ | ||
module github.com/telemachus/gitmirror | ||
|
||
go 1.23 | ||
go 1.23.4 | ||
|
||
require ( | ||
github.com/MakeNowJust/heredoc v1.0.0 | ||
github.com/google/go-cmp v0.6.0 | ||
) | ||
require github.com/google/go-cmp v0.6.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,2 @@ | ||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= | ||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= | ||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,119 +1,47 @@ | ||
// Package cli creates and runs a command line interface. | ||
package cli | ||
|
||
import ( | ||
"encoding/json" | ||
"flag" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"slices" | ||
"strings" | ||
) | ||
|
||
const ( | ||
defaultConfig = ".gitmirror.json" | ||
defaultStorage = ".local/share/gitmirror" | ||
exitSuccess = 0 | ||
exitFailure = 1 | ||
) | ||
|
||
// App stores information about the application's state. | ||
type App struct { | ||
HomeDir string | ||
CmdName string | ||
Usage string | ||
ExitValue int | ||
HelpWanted bool | ||
QuietWanted bool | ||
type appEnv struct { | ||
cmd string | ||
subCmd string | ||
cfgFile string | ||
homeDir string | ||
storageDir string | ||
exitValue int | ||
quiet bool | ||
} | ||
|
||
// NoOp determines whether an App should bail out. | ||
func (app *App) NoOp() bool { | ||
return app.ExitValue != exitSuccess | ||
func (app *appEnv) noOp() bool { | ||
return app.exitValue != exitSuccess | ||
} | ||
|
||
// NewApp returns a new App pointer. | ||
func NewApp(cmdUsage string) *App { | ||
func newAppEnv(cfg *cfg) *appEnv { | ||
homeDir, err := os.UserHomeDir() | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "%s: %s\n", gitmirrorName, err) | ||
return &App{ExitValue: exitFailure} | ||
} | ||
return &App{ | ||
CmdName: gitmirrorName, | ||
Usage: gitmirrorUsage, | ||
ExitValue: exitSuccess, | ||
HomeDir: homeDir, | ||
} | ||
} | ||
|
||
// Repo stores information about a git repository. | ||
type Repo struct { | ||
URL string | ||
Name string | ||
} | ||
|
||
// Flags handles flags and options in my finicky way. | ||
func (app *App) Flags(args []string) (string, bool) { | ||
if app.NoOp() { | ||
return "", false | ||
fmt.Fprintf(os.Stderr, "%s %s: %s\n", cfg.cmd, cfg.subCmd, err) | ||
return &appEnv{exitValue: exitFailure} | ||
} | ||
cmdFlags := flag.NewFlagSet("gitmirror", flag.ContinueOnError) | ||
cmdFlags.SetOutput(io.Discard) | ||
var configFile string | ||
var configIsDefault bool | ||
cmdFlags.BoolVar(&app.HelpWanted, "help", false, "") | ||
cmdFlags.BoolVar(&app.HelpWanted, "h", false, "") | ||
cmdFlags.BoolVar(&app.QuietWanted, "quiet", false, "") | ||
cmdFlags.BoolVar(&app.QuietWanted, "q", false, "") | ||
cmdFlags.StringVar(&configFile, "config", "", "") | ||
cmdFlags.StringVar(&configFile, "c", "", "") | ||
err := cmdFlags.Parse(args) | ||
switch { | ||
// This must precede all other checks. | ||
case err != nil: | ||
fmt.Fprintf(os.Stderr, "%s: %s\n%s", app.CmdName, err, app.Usage) | ||
app.ExitValue = exitFailure | ||
case app.HelpWanted: | ||
fmt.Fprintf(os.Stderr, "%s: use 'help' not '-help' or '-h'\n", app.CmdName) | ||
fmt.Fprint(os.Stderr, app.Usage) | ||
app.ExitValue = exitFailure | ||
case configFile == "": | ||
configFile = defaultConfig | ||
configIsDefault = true | ||
if cfg.defaultCfgFile { | ||
cfg.cfgFile = filepath.Join(homeDir, cfg.cfgFile) | ||
} | ||
return configFile, configIsDefault | ||
} | ||
|
||
// Unmarshal reads a configuration file and returns a slice of Repo. | ||
func (app *App) Unmarshal(configFile string, configIsDefault bool) []Repo { | ||
if app.NoOp() { | ||
return nil | ||
} | ||
if configIsDefault { | ||
configFile = filepath.Join(app.HomeDir, configFile) | ||
} | ||
blob, err := os.ReadFile(configFile) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "%s: %s\n", app.CmdName, err) | ||
app.ExitValue = exitFailure | ||
return nil | ||
} | ||
repos := make([]Repo, 0, 20) | ||
err = json.Unmarshal(blob, &repos) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "%s: %s\n", app.CmdName, err) | ||
app.ExitValue = exitFailure | ||
return nil | ||
return &appEnv{ | ||
cmd: cfg.cmd, | ||
subCmd: cfg.subCmd, | ||
cfgFile: cfg.cfgFile, | ||
homeDir: homeDir, | ||
storageDir: filepath.Join(homeDir, ".local/share/gitmirror"), | ||
quiet: cfg.quiet, | ||
exitValue: exitSuccess, | ||
} | ||
// Every repository must have a URL and a directory name. | ||
return slices.DeleteFunc(repos, func(repo Repo) bool { | ||
return repo.URL == "" || repo.Name == "" | ||
}) | ||
} | ||
|
||
// PrettyPath replaces a user's home directory with ~ in a string. | ||
func (app *App) PrettyPath(s string) string { | ||
return strings.Replace(s, app.HomeDir, "~", 1) | ||
func (app *appEnv) prettyPath(s string) string { | ||
return strings.Replace(s, app.homeDir, "~", 1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package cli | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
) | ||
|
||
func makeRepos() []Repo { | ||
return []Repo{ | ||
{URL: "https://github.com/foo/foo.git", Name: "foo.git"}, | ||
{URL: "https://github.com/bar/bar.git", Name: "bar.git"}, | ||
{URL: "https://example.com/buzz/fizz.git", Name: "random.git"}, | ||
} | ||
} | ||
|
||
func makeNewAppEnv(cfgFile string) *appEnv { | ||
cfg := &cfg{ | ||
cmd: "test", | ||
subCmd: "testing", | ||
cfgFile: cfgFile, | ||
defaultCfgFile: false, | ||
} | ||
return newAppEnv(cfg) | ||
} | ||
|
||
func TestGetReposSuccess(t *testing.T) { | ||
expected := makeRepos() | ||
app := makeNewAppEnv("testdata/backups.json") | ||
actual := app.getRepos() | ||
if app.exitValue != exitSuccess { | ||
t.Fatal("app.exitValue != exitSuccess") | ||
} | ||
if diff := cmp.Diff(expected, actual); diff != "" { | ||
t.Errorf("app.getRepos(\"testdata/backups.json\") failure (-want +got)\n%s", diff) | ||
} | ||
} | ||
|
||
func TestGetReposFailure(t *testing.T) { | ||
app := makeNewAppEnv("testdata/nope.json") | ||
app.getRepos() | ||
if app.exitValue != exitFailure { | ||
t.Errorf("app.getRepos(\"testdata/nope.json\") exit value: %d; expected %d", app.exitValue, exitFailure) | ||
} | ||
} | ||
|
||
func TestRepoChecks(t *testing.T) { | ||
app := makeNewAppEnv("testdata/repo-checks.json") | ||
actual := app.getRepos() | ||
if len(actual) != 0 { | ||
t.Errorf("app.getRepos(\"testdata/repo-checks.json\") expected len(repos) = 0; actual: %d", len(actual)) | ||
} | ||
} |
Oops, something went wrong.