cmd/root.go
changeset 0 5abace724584
child 35 61ed03c3f134
--- /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 <mikael@lilotux.net>
+//
+// 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...)
+}