cmd/root.go
changeset 0 5abace724584
child 35 61ed03c3f134
equal deleted inserted replaced
-1:000000000000 0:5abace724584
       
     1 // Copyright © 2017 Mikael Berthe <mikael@lilotux.net>
       
     2 //
       
     3 // Licensed under the MIT license.
       
     4 // Please see the LICENSE file is this directory.
       
     5 
       
     6 package cmd
       
     7 
       
     8 import (
       
     9 	"fmt"
       
    10 	"io/ioutil"
       
    11 	"os"
       
    12 
       
    13 	"github.com/spf13/cobra"
       
    14 	"github.com/spf13/viper"
       
    15 
       
    16 	"github.com/McKael/madon"
       
    17 	"github.com/McKael/madonctl/printer"
       
    18 )
       
    19 
       
    20 // AppName is the CLI application name
       
    21 const AppName = "madonctl"
       
    22 const defaultConfigFile = "$HOME/.config/" + AppName + "/" + AppName + ".yaml"
       
    23 
       
    24 var cfgFile string
       
    25 var safeMode bool
       
    26 var instanceURL, appID, appSecret string
       
    27 var login, password, token string
       
    28 var gClient *madon.Client
       
    29 var verbose bool
       
    30 var outputFormat string
       
    31 var outputTemplate, outputTemplateFile string
       
    32 
       
    33 // RootCmd represents the base command when called without any subcommands
       
    34 var RootCmd = &cobra.Command{
       
    35 	Use:               AppName,
       
    36 	Short:             "A CLI utility for Mastodon API",
       
    37 	PersistentPreRunE: checkOutputFormat,
       
    38 	Long: `madonctl is a CLI tool for the Mastodon REST API.
       
    39 
       
    40 You can use a configuration file to store common options.
       
    41 For example, create ` + defaultConfigFile + ` with the following
       
    42 contents:
       
    43 
       
    44 	---
       
    45 	instance: "INSTANCE"
       
    46 	login: "USERNAME"
       
    47 	password: "USERPASSWORD"
       
    48 	...
       
    49 
       
    50 The simplest way to generate a configuration file is to use the 'config dump'
       
    51 command.
       
    52 
       
    53 (Configuration files in JSON are also accepted.)
       
    54 
       
    55 If you want shell auto-completion (for bash or zsh), you can generate the
       
    56 completion scripts with "madonctl completion $SHELL".
       
    57 For example if you use bash:
       
    58 
       
    59 	madonctl completion bash > _bash_madonctl
       
    60 	source _bash_madonctl
       
    61 
       
    62 Now you should have tab completion for subcommands and flags.
       
    63 
       
    64 Note: Most examples assume the user's credentials are set in the configuration
       
    65 file.
       
    66 `,
       
    67 	Example: `  madonctl instance
       
    68   madonctl toot "Hello, World"
       
    69   madonctl toot --visibility direct "@McKael Hello, You"
       
    70   madonctl toot --visibility private --spoiler CW "The answer was 42"
       
    71   madonctl post --file image.jpg Selfie
       
    72   madonctl --instance INSTANCE --login USERNAME --password PASS timeline
       
    73   madonctl accounts notifications --list --clear
       
    74   madonctl accounts blocked
       
    75   madonctl accounts search Gargron
       
    76   madonctl search --resolve https://mastodon.social/@Gargron
       
    77   madonctl accounts follow --remote Gargron@mastodon.social
       
    78   madonctl accounts --account-id 399 statuses
       
    79   madonctl status --status-id 416671 show
       
    80   madonctl status --status-id 416671 favourite
       
    81   madonctl status --status-id 416671 boost
       
    82   madonctl accounts show
       
    83   madonctl accounts show -o yaml
       
    84   madonctl accounts --account-id 1 followers --template '{{.acct}}{{"\n"}}'
       
    85   madonctl config whoami
       
    86   madonctl timeline :mastodon`,
       
    87 }
       
    88 
       
    89 // Execute adds all child commands to the root command sets flags appropriately.
       
    90 // This is called by main.main(). It only needs to happen once to the rootCmd.
       
    91 func Execute() {
       
    92 	if err := RootCmd.Execute(); err != nil {
       
    93 		errPrint("Error: %s", err.Error())
       
    94 		os.Exit(-1)
       
    95 	}
       
    96 }
       
    97 
       
    98 func init() {
       
    99 	cobra.OnInitialize(initConfig)
       
   100 
       
   101 	// Global flags
       
   102 	RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "",
       
   103 		"config file (default is "+defaultConfigFile+")")
       
   104 	RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Verbose mode")
       
   105 	RootCmd.PersistentFlags().StringVarP(&instanceURL, "instance", "i", "", "Mastodon instance")
       
   106 	RootCmd.PersistentFlags().StringVarP(&login, "login", "L", "", "Instance user login")
       
   107 	RootCmd.PersistentFlags().StringVarP(&password, "password", "P", "", "Instance user password")
       
   108 	RootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "User token")
       
   109 	RootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "plain",
       
   110 		"Output format (plain|json|yaml|template)")
       
   111 	RootCmd.PersistentFlags().StringVar(&outputTemplate, "template", "",
       
   112 		"Go template (for output=template)")
       
   113 	RootCmd.PersistentFlags().StringVar(&outputTemplateFile, "template-file", "",
       
   114 		"Go template file (for output=template)")
       
   115 
       
   116 	// Configuration file bindings
       
   117 	viper.BindPFlag("output", RootCmd.PersistentFlags().Lookup("output"))
       
   118 	viper.BindPFlag("verbose", RootCmd.PersistentFlags().Lookup("verbose"))
       
   119 	// XXX viper.BindPFlag("apiKey", RootCmd.PersistentFlags().Lookup("api-key"))
       
   120 	viper.BindPFlag("instance", RootCmd.PersistentFlags().Lookup("instance"))
       
   121 	viper.BindPFlag("login", RootCmd.PersistentFlags().Lookup("login"))
       
   122 	viper.BindPFlag("password", RootCmd.PersistentFlags().Lookup("password"))
       
   123 	viper.BindPFlag("token", RootCmd.PersistentFlags().Lookup("token"))
       
   124 }
       
   125 
       
   126 func checkOutputFormat(cmd *cobra.Command, args []string) error {
       
   127 	of := viper.GetString("output")
       
   128 	switch of {
       
   129 	case "", "plain", "json", "yaml", "template":
       
   130 		return nil // Accepted
       
   131 	}
       
   132 	return fmt.Errorf("output format '%s' not supported", of)
       
   133 }
       
   134 
       
   135 // initConfig reads in config file and ENV variables if set.
       
   136 func initConfig() {
       
   137 	if cfgFile != "" { // enable ability to specify config file via flag
       
   138 		viper.SetConfigFile(cfgFile)
       
   139 	}
       
   140 
       
   141 	viper.SetConfigName(AppName) // name of config file (without extension)
       
   142 	viper.AddConfigPath("$HOME/.config/" + AppName)
       
   143 	viper.AddConfigPath("$HOME/." + AppName)
       
   144 
       
   145 	// Read in environment variables that match, with a prefix
       
   146 	viper.SetEnvPrefix(AppName)
       
   147 	viper.AutomaticEnv()
       
   148 
       
   149 	// If a config file is found, read it in.
       
   150 	if err := viper.ReadInConfig(); viper.GetBool("verbose") && err == nil {
       
   151 		errPrint("Using config file: %s", viper.ConfigFileUsed())
       
   152 	}
       
   153 }
       
   154 
       
   155 // getOutputFormat return the requested output format, defaulting to "plain".
       
   156 func getOutputFormat() string {
       
   157 	of := viper.GetString("output")
       
   158 	if of == "" {
       
   159 		of = "plain"
       
   160 	}
       
   161 	// Override format if a template is provided
       
   162 	if of == "plain" && (outputTemplate != "" || outputTemplateFile != "") {
       
   163 		// If the format is plain and there is a template option,
       
   164 		// set the format to "template".
       
   165 		of = "template"
       
   166 	}
       
   167 	return of
       
   168 }
       
   169 
       
   170 // getPrinter returns a resource printer for the requested output format.
       
   171 func getPrinter() (printer.ResourcePrinter, error) {
       
   172 	var opt string
       
   173 	of := getOutputFormat()
       
   174 
       
   175 	if of == "template" {
       
   176 		opt = outputTemplate
       
   177 		if outputTemplateFile != "" {
       
   178 			tmpl, err := ioutil.ReadFile(outputTemplateFile)
       
   179 			if err != nil {
       
   180 				return nil, err
       
   181 			}
       
   182 			opt = string(tmpl)
       
   183 		}
       
   184 	}
       
   185 	return printer.NewPrinter(of, opt)
       
   186 }
       
   187 
       
   188 func errPrint(format string, a ...interface{}) (n int, err error) {
       
   189 	return fmt.Fprintf(os.Stderr, format+"\n", a...)
       
   190 }