mirror of https://github.com/mvdan/fdroidcl.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
4.9 KiB
243 lines
4.9 KiB
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> |
|
// See LICENSE for licensing information |
|
|
|
package main |
|
|
|
import ( |
|
"flag" |
|
"fmt" |
|
"os" |
|
"path/filepath" |
|
"strings" |
|
|
|
"github.com/pelletier/go-toml/v2" |
|
) |
|
|
|
const cmdName = "fdroidcl" |
|
|
|
const version = "v0.8.1" |
|
|
|
func subdir(dir, name string) string { |
|
p := filepath.Join(dir, name) |
|
if err := os.MkdirAll(p, 0o755); err != nil { |
|
fmt.Fprintf(os.Stderr, "Could not create dir '%s': %v\n", p, err) |
|
} |
|
return p |
|
} |
|
|
|
func mustCache() string { |
|
dir, err := os.UserCacheDir() |
|
if err != nil { |
|
fmt.Fprintln(os.Stderr, err) |
|
panic("TODO: return an error") |
|
} |
|
return subdir(dir, cmdName) |
|
} |
|
|
|
func mustData() string { |
|
dir, err := os.UserConfigDir() |
|
if err != nil { |
|
fmt.Fprintln(os.Stderr, err) |
|
panic("TODO: return an error") |
|
} |
|
return subdir(dir, cmdName) |
|
} |
|
|
|
func configPath() string { |
|
return filepath.Join(mustData(), "config.toml") |
|
} |
|
|
|
type repo struct { |
|
ID string `toml:"id"` |
|
URL string `toml:"url"` |
|
Enabled bool `toml:"enabled"` |
|
} |
|
|
|
type setup struct { |
|
ID string `toml:"id"` |
|
Apps []string `toml:"apps"` |
|
Repos []string `toml:"repos"` |
|
} |
|
|
|
type userConfig struct { |
|
Repos []repo `toml:"repos"` |
|
Setups []setup `toml:"setups"` |
|
} |
|
|
|
var config = userConfig{ |
|
Repos: []repo{ |
|
{ |
|
ID: "f-droid", |
|
URL: "https://f-droid.org/repo", |
|
Enabled: true, |
|
}, |
|
{ |
|
ID: "f-droid-archive", |
|
URL: "https://f-droid.org/archive", |
|
Enabled: false, |
|
}, |
|
}, |
|
Setups: []setup{}, |
|
} |
|
|
|
func readConfig() error { |
|
f, err := os.Open(configPath()) |
|
if err != nil { |
|
// ignore error, if file does not exist |
|
return nil |
|
} |
|
defer f.Close() |
|
fileConfig := userConfig{} |
|
err = toml.NewDecoder(f).Decode(&fileConfig) |
|
if err != nil { |
|
return err |
|
} |
|
config = fileConfig |
|
return nil |
|
} |
|
|
|
// A Command is an implementation of a go command |
|
// like go build or go fix. |
|
type Command struct { |
|
// Run runs the command. |
|
// The args are the arguments after the command name. |
|
Run func(args []string) error |
|
|
|
// UsageLine is the one-line usage message. |
|
// The first word in the line is taken to be the command name. |
|
UsageLine string |
|
|
|
// Short is the short, single-line description. |
|
Short string |
|
|
|
// Long is an optional longer version of the Short description. |
|
Long string |
|
|
|
Fset flag.FlagSet |
|
} |
|
|
|
// Name returns the command's name: the first word in the usage line. |
|
func (c *Command) Name() string { |
|
name := c.UsageLine |
|
i := strings.Index(name, " ") |
|
if i >= 0 { |
|
name = name[:i] |
|
} |
|
return name |
|
} |
|
|
|
func (c *Command) usage() { |
|
fmt.Fprintf(os.Stderr, "usage: %s %s\n\n", cmdName, c.UsageLine) |
|
if c.Long == "" { |
|
fmt.Fprintf(os.Stderr, "%s.\n", c.Short) |
|
} else { |
|
fmt.Fprint(os.Stderr, c.Long) |
|
} |
|
anyFlags := false |
|
c.Fset.VisitAll(func(f *flag.Flag) { anyFlags = true }) |
|
if anyFlags { |
|
fmt.Fprintf(os.Stderr, "\nAvailable options:\n") |
|
c.Fset.PrintDefaults() |
|
} |
|
} |
|
|
|
func init() { |
|
flag.Usage = func() { |
|
fmt.Fprintf(os.Stderr, "usage: %s [-h] <command> [<args>]\n\n", cmdName) |
|
fmt.Fprintf(os.Stderr, "Available commands:\n") |
|
maxUsageLen := 0 |
|
for _, c := range commands { |
|
if len(c.UsageLine) > maxUsageLen { |
|
maxUsageLen = len(c.UsageLine) |
|
} |
|
} |
|
for _, c := range commands { |
|
fmt.Fprintf(os.Stderr, " %s%s %s\n", c.UsageLine, |
|
strings.Repeat(" ", maxUsageLen-len(c.UsageLine)), c.Short) |
|
} |
|
fmt.Fprintf(os.Stderr, ` |
|
An appid is just an app's unique package name. A specific version of an app can |
|
be selected by following the appid with a colon and the version code. The |
|
'search' and 'show' commands can be used to find these strings. For example: |
|
|
|
$ fdroidcl search redreader |
|
$ fdroidcl show org.quantumbadger.redreader |
|
$ fdroidcl install org.quantumbadger.redreader:85 |
|
`) |
|
fmt.Fprintf(os.Stderr, "\nUse %s <command> -h for more information.\n", cmdName) |
|
} |
|
} |
|
|
|
// Commands lists the available commands. |
|
var commands = []*Command{ |
|
cmdUpdate, |
|
cmdSearch, |
|
cmdShow, |
|
cmdInstall, |
|
cmdUninstall, |
|
cmdDownload, |
|
cmdDevices, |
|
cmdScan, |
|
cmdList, |
|
cmdRepo, |
|
cmdSetup, |
|
cmdClean, |
|
cmdDefaults, |
|
cmdVersion, |
|
} |
|
|
|
var cmdVersion = &Command{ |
|
UsageLine: "version", |
|
Short: "Print version information", |
|
Run: func(args []string) error { |
|
if len(args) > 0 { |
|
return fmt.Errorf("no arguments allowed") |
|
} |
|
fmt.Println(version) |
|
return nil |
|
}, |
|
} |
|
|
|
func main() { |
|
os.Exit(main1()) |
|
} |
|
|
|
func main1() int { |
|
flag.Parse() |
|
args := flag.Args() |
|
|
|
if len(args) < 1 { |
|
flag.Usage() |
|
return 2 |
|
} |
|
|
|
cmdName := args[0] |
|
for _, cmd := range commands { |
|
if cmd.Name() != cmdName { |
|
continue |
|
} |
|
cmd.Fset.Init(cmdName, flag.ContinueOnError) |
|
cmd.Fset.Usage = cmd.usage |
|
if err := cmd.Fset.Parse(args[1:]); err != nil { |
|
if err == flag.ErrHelp { |
|
return 0 |
|
} |
|
return 2 |
|
} |
|
|
|
err := readConfig() |
|
if err != nil { |
|
fmt.Fprintf(os.Stderr, "config %s: %v\n", configPath(), err) |
|
return 1 |
|
} |
|
|
|
if err := cmd.Run(cmd.Fset.Args()); err != nil { |
|
fmt.Fprintf(os.Stderr, "%s: %v\n", cmdName, err) |
|
return 1 |
|
} |
|
return 0 |
|
} |
|
fmt.Fprintf(os.Stderr, "Unrecognised command '%s'\n\n", cmdName) |
|
flag.Usage() |
|
return 2 |
|
}
|
|
|