diff -r 000000000000 -r 5abace724584 cmd/root.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/root.go Wed Apr 19 19:08:47 2017 +0200 @@ -0,0 +1,190 @@ +// Copyright © 2017 Mikael Berthe +// +// Licensed under the MIT license. +// Please see the LICENSE file is this directory. + +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/McKael/madon" + "github.com/McKael/madonctl/printer" +) + +// AppName is the CLI application name +const AppName = "madonctl" +const defaultConfigFile = "$HOME/.config/" + AppName + "/" + AppName + ".yaml" + +var cfgFile string +var safeMode bool +var instanceURL, appID, appSecret string +var login, password, token string +var gClient *madon.Client +var verbose bool +var outputFormat string +var outputTemplate, outputTemplateFile string + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: AppName, + Short: "A CLI utility for Mastodon API", + PersistentPreRunE: checkOutputFormat, + Long: `madonctl is a CLI tool for the Mastodon REST API. + +You can use a configuration file to store common options. +For example, create ` + defaultConfigFile + ` with the following +contents: + + --- + instance: "INSTANCE" + login: "USERNAME" + password: "USERPASSWORD" + ... + +The simplest way to generate a configuration file is to use the 'config dump' +command. + +(Configuration files in JSON are also accepted.) + +If you want shell auto-completion (for bash or zsh), you can generate the +completion scripts with "madonctl completion $SHELL". +For example if you use bash: + + madonctl completion bash > _bash_madonctl + source _bash_madonctl + +Now you should have tab completion for subcommands and flags. + +Note: Most examples assume the user's credentials are set in the configuration +file. +`, + Example: ` madonctl instance + madonctl toot "Hello, World" + madonctl toot --visibility direct "@McKael Hello, You" + madonctl toot --visibility private --spoiler CW "The answer was 42" + madonctl post --file image.jpg Selfie + madonctl --instance INSTANCE --login USERNAME --password PASS timeline + madonctl accounts notifications --list --clear + madonctl accounts blocked + madonctl accounts search Gargron + madonctl search --resolve https://mastodon.social/@Gargron + madonctl accounts follow --remote Gargron@mastodon.social + madonctl accounts --account-id 399 statuses + madonctl status --status-id 416671 show + madonctl status --status-id 416671 favourite + madonctl status --status-id 416671 boost + madonctl accounts show + madonctl accounts show -o yaml + madonctl accounts --account-id 1 followers --template '{{.acct}}{{"\n"}}' + madonctl config whoami + madonctl timeline :mastodon`, +} + +// Execute adds all child commands to the root command sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := RootCmd.Execute(); err != nil { + errPrint("Error: %s", err.Error()) + os.Exit(-1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Global flags + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", + "config file (default is "+defaultConfigFile+")") + RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Verbose mode") + RootCmd.PersistentFlags().StringVarP(&instanceURL, "instance", "i", "", "Mastodon instance") + RootCmd.PersistentFlags().StringVarP(&login, "login", "L", "", "Instance user login") + RootCmd.PersistentFlags().StringVarP(&password, "password", "P", "", "Instance user password") + RootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "User token") + RootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "plain", + "Output format (plain|json|yaml|template)") + RootCmd.PersistentFlags().StringVar(&outputTemplate, "template", "", + "Go template (for output=template)") + RootCmd.PersistentFlags().StringVar(&outputTemplateFile, "template-file", "", + "Go template file (for output=template)") + + // Configuration file bindings + viper.BindPFlag("output", RootCmd.PersistentFlags().Lookup("output")) + viper.BindPFlag("verbose", RootCmd.PersistentFlags().Lookup("verbose")) + // XXX viper.BindPFlag("apiKey", RootCmd.PersistentFlags().Lookup("api-key")) + viper.BindPFlag("instance", RootCmd.PersistentFlags().Lookup("instance")) + viper.BindPFlag("login", RootCmd.PersistentFlags().Lookup("login")) + viper.BindPFlag("password", RootCmd.PersistentFlags().Lookup("password")) + viper.BindPFlag("token", RootCmd.PersistentFlags().Lookup("token")) +} + +func checkOutputFormat(cmd *cobra.Command, args []string) error { + of := viper.GetString("output") + switch of { + case "", "plain", "json", "yaml", "template": + return nil // Accepted + } + return fmt.Errorf("output format '%s' not supported", of) +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { // enable ability to specify config file via flag + viper.SetConfigFile(cfgFile) + } + + viper.SetConfigName(AppName) // name of config file (without extension) + viper.AddConfigPath("$HOME/.config/" + AppName) + viper.AddConfigPath("$HOME/." + AppName) + + // Read in environment variables that match, with a prefix + viper.SetEnvPrefix(AppName) + viper.AutomaticEnv() + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); viper.GetBool("verbose") && err == nil { + errPrint("Using config file: %s", viper.ConfigFileUsed()) + } +} + +// getOutputFormat return the requested output format, defaulting to "plain". +func getOutputFormat() string { + of := viper.GetString("output") + if of == "" { + of = "plain" + } + // Override format if a template is provided + if of == "plain" && (outputTemplate != "" || outputTemplateFile != "") { + // If the format is plain and there is a template option, + // set the format to "template". + of = "template" + } + return of +} + +// getPrinter returns a resource printer for the requested output format. +func getPrinter() (printer.ResourcePrinter, error) { + var opt string + of := getOutputFormat() + + if of == "template" { + opt = outputTemplate + if outputTemplateFile != "" { + tmpl, err := ioutil.ReadFile(outputTemplateFile) + if err != nil { + return nil, err + } + opt = string(tmpl) + } + } + return printer.NewPrinter(of, opt) +} + +func errPrint(format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(os.Stderr, format+"\n", a...) +}