-
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.
feat: update to use optionparser; add linters
1. This version uses optionparser to handle command line parsing. 2. I added revive and staticcheck separately from golangci. When I use these two linters with golangci, the results seem to differ from running them alone. I removed staticcheck from golangci—I was already not using revive with golangci—and I added individual make target for staticcheck, revive, and golangci as well as a lint target that calls all three. I should probably think more about all of this.
- Loading branch information
1 parent
d6ffc0c
commit 34d4edd
Showing
27 changed files
with
955 additions
and
369 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
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,17 @@ | ||
# TODO | ||
|
||
+ Improve the json in the configuration file. First, use a whole json document | ||
rather than just an array. (This will require me to put the `repos` array as | ||
the value of a `repos` key.) Second, add a `storage` key in order to let | ||
users specify where they want the repos to be placed. | ||
+ Currently, I add `GIT_TERMINAL_PROMPT=0` to the environment of the git command | ||
that I call with `os.Exec`. This works for me, but others may want git to | ||
prompt them for their username and password. I should make this configurable, | ||
maybe in the configuration file, maybe on the command line, and maybe both. | ||
(This shows up in clone.go and in update.go.) | ||
+ The clone.go and update.go files share a lot of structure and code. Maybe | ||
I can DRY up these two? | ||
+ Investigate linting more. I probably don't need all the linting options | ||
I have now, and I should simplify the Makefile. I should also study the | ||
options for each linter more so that I make sure to use them as well as | ||
possible. |
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 |
---|---|---|
@@ -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,90 @@ | ||
// Package cli creates and runs a command line interface. | ||
package cli | ||
|
||
import ( | ||
"encoding/json" | ||
"flag" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"slices" | ||
"strings" | ||
) | ||
|
||
const ( | ||
defaultConfig = ".gitmirror.json" | ||
defaultStorage = ".local/share/gitmirror" | ||
exitSuccess = 0 | ||
exitFailure = 1 | ||
"github.com/telemachus/gitmirror/internal/optionparser" | ||
) | ||
|
||
// 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 | ||
config string | ||
home string | ||
storage string | ||
exitVal int | ||
quiet bool | ||
} | ||
|
||
// NoOp determines whether an App should bail out. | ||
func (app *App) NoOp() bool { | ||
return app.ExitValue != exitSuccess | ||
} | ||
func appFrom(args []string) (*appEnv, error) { | ||
app := &appEnv{cmd: cmd, exitVal: exitSuccess} | ||
|
||
// NewApp returns a new App pointer. | ||
func NewApp(cmdUsage string) *App { | ||
homeDir, err := os.UserHomeDir() | ||
op := optionparser.NewOptionParser() | ||
op.On("-c", "--config FILE", "Use FILE as config file (default ~/.gitmirror.json)", &app.config) | ||
op.On("-q", "--quiet", "Print only error messages", &app.quiet) | ||
op.On("--version", "Print version and exit", version) | ||
op.Command("clone", "Clone git repositories using `git clone --mirror`") | ||
op.Command("update|up", "Update git repositories using `git remote update`") | ||
op.Command("sync", "Run both update and clone (in that order)") | ||
op.Start = 24 | ||
op.Banner = "usage: gitmirror [options] <subcommand>" | ||
|
||
// Do not continue if we cannot parse and validate arguments or get the | ||
// user's home directory. | ||
if err := op.ParseFrom(args); err != nil { | ||
return nil, err | ||
} | ||
if err := validate(op.Extra); err != nil { | ||
return nil, err | ||
} | ||
home, err := os.UserHomeDir() | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "%s: %s\n", gitmirrorName, err) | ||
return &App{ExitValue: exitFailure} | ||
return nil, err | ||
} | ||
return &App{ | ||
CmdName: gitmirrorName, | ||
Usage: gitmirrorUsage, | ||
ExitValue: exitSuccess, | ||
HomeDir: homeDir, | ||
|
||
if app.config == "" { | ||
app.config = filepath.Join(home, config) | ||
} | ||
app.storage = filepath.Join(home, storage) | ||
app.subCmd = op.Extra[0] | ||
|
||
return app, nil | ||
} | ||
|
||
// Repo stores information about a git repository. | ||
type Repo struct { | ||
URL string | ||
Name string | ||
func (app *appEnv) noOp() bool { | ||
return app.exitVal != exitSuccess | ||
} | ||
|
||
// Flags handles flags and options in my finicky way. | ||
func (app *App) Flags(args []string) (string, bool) { | ||
if app.NoOp() { | ||
return "", false | ||
} | ||
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 | ||
} | ||
return configFile, configIsDefault | ||
func (app *appEnv) prettyPath(s string) string { | ||
return strings.Replace(s, app.home, "~", 1) | ||
} | ||
|
||
// 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 | ||
func validate(extra []string) error { | ||
if len(extra) != 1 { | ||
return errors.New("one (and only one) subcommand is required") | ||
} | ||
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 | ||
|
||
// The only recognized subcommands are clone, up(date), and sync. | ||
recognized := map[string]struct{}{ | ||
"clone": {}, | ||
"update": {}, | ||
"up": {}, | ||
"sync": {}, | ||
} | ||
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 | ||
if _, ok := recognized[extra[0]]; !ok { | ||
return fmt.Errorf("unrecognized subcommand: %q", extra[0]) | ||
} | ||
// Every repository must have a URL and a directory name. | ||
return slices.DeleteFunc(repos, func(repo Repo) bool { | ||
return repo.URL == "" || repo.Name == "" | ||
}) | ||
|
||
return nil | ||
} | ||
|
||
// 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) | ||
// Quick and dirty, but why be fancy in this case? | ||
func version() { | ||
fmt.Printf("%s %s\n", cmd, cmdVersion) | ||
os.Exit(exitSuccess) | ||
} |
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
Oops, something went wrong.