github.com/clysto/awgo@v0.15.0/magic.go (about)

     1  //
     2  // Copyright (c) 2016 Dean Jackson <deanishe@deanishe.net>
     3  //
     4  // MIT Licence. See http://opensource.org/licenses/MIT
     5  //
     6  // Created on 2016-11-05
     7  //
     8  
     9  package aw
    10  
    11  import (
    12  	"fmt"
    13  	"log"
    14  	"os"
    15  	"os/exec"
    16  	"strings"
    17  )
    18  
    19  // defaultMagicActions creates a MagicActions with the default actions
    20  // already registered.
    21  func defaultMagicActions(wf *Workflow) *MagicActions {
    22  	ma := &MagicActions{
    23  		actions: map[string]MagicAction{},
    24  		wf:      wf,
    25  	}
    26  	ma.Register(
    27  		logMA{wf},
    28  		cacheMA{wf},
    29  		clearCacheMA{wf},
    30  		dataMA{wf},
    31  		clearDataMA{wf},
    32  		resetMA{wf},
    33  	)
    34  	return ma
    35  }
    36  
    37  /*
    38  MagicAction is a command that is called directly by AwGo (i.e.  your workflow
    39  code is not run) if its keyword is passed in a user query.
    40  
    41  To use Magic Actions, it's imperative that your workflow call
    42  Args()/Workflow.Args().
    43  
    44  Calls to Args()/Workflow.Args() check the workflow's arguments (os.Args[1:])
    45  for the magic prefix ("workflow:" by default), and hijack control
    46  of the workflow if found.
    47  
    48  If an exact keyword match is found (e.g. "workflow:log"), the corresponding
    49  action is executed, and the workflow exits.
    50  
    51  If no exact match is found, AwGo runs a Script Filter for the user to
    52  select an action. Hitting TAB or RETURN on an item will run it.
    53  
    54  Magic Actions are mainly aimed at making debugging and supporting users easier
    55  (via the built-in actions), but they also provide a simple way to integrate
    56  your own commands that don't need a "real" UI.
    57  
    58  For example, setting an Updater on Workflow adds an "update" command that
    59  checks for & installs a new version of the workflow.
    60  
    61  
    62  Defaults
    63  
    64  There are several built-in magic actions, which are registered by
    65  default:
    66  
    67  	<prefix>log         Open workflow's log file in the default app (usually
    68  	                    Console).
    69  	<prefix>data        Open workflow's data directory in the default app
    70  	                    (usually Finder).
    71  	<prefix>cache       Open workflow's data directory in the default app
    72  	                    (usually Finder).
    73  	<prefix>deldata     Delete everything in the workflow's data directory.
    74  	<prefix>delcache    Delete everything in the workflow's cache directory.
    75  	<prefix>reset       Delete everything in the workflow's data and cache directories.
    76  	<prefix>help        Open help URL in default browser.
    77  	                    Only registered if you have set a HelpURL.
    78  	<prefix>update      Check for updates and install a newer version of the
    79  	                    workflow if available.
    80  	                    Only registered if you have configured an Updater.
    81  
    82  
    83  Custom Actions
    84  
    85  To add custom MagicActions, you must register them with your Workflow
    86  *before* you call Workflow.Args()
    87  
    88  To do this, pass MagicAction implementors to Workflow.MagicActions.Register()
    89  
    90  */
    91  type MagicAction interface {
    92  	// Keyword is what the user must enter to run the action after
    93  	// AwGo has recognised the magic prefix. So if the prefix is
    94  	// "workflow:" (the default), a user must enter the query
    95  	// "workflow:<keyword>" to execute this action.
    96  	Keyword() string
    97  
    98  	// Description is shown when a user has entered "magic" mode, but
    99  	// the query does not yet match a keyword.
   100  	Description() string
   101  
   102  	// RunText is sent to Alfred and written to the log file &
   103  	// debugger when the action is run.
   104  	RunText() string
   105  
   106  	// Run is called when the Magic Action is triggered.
   107  	Run() error
   108  }
   109  
   110  // MagicActions contains the registered magic actions. See the MagicAction
   111  // interface for full documentation.
   112  type MagicActions struct {
   113  	actions map[string]MagicAction
   114  	wf      *Workflow
   115  }
   116  
   117  // Register adds a MagicAction to the mapping. Previous entries are overwritten.
   118  func (ma *MagicActions) Register(actions ...MagicAction) {
   119  	for _, action := range actions {
   120  		ma.actions[action.Keyword()] = action
   121  	}
   122  }
   123  
   124  // Unregister removes a MagicAction from the mapping (based on its keyword).
   125  func (ma *MagicActions) Unregister(actions ...MagicAction) {
   126  	for _, action := range actions {
   127  		delete(ma.actions, action.Keyword())
   128  	}
   129  }
   130  
   131  // Args runs a magic action or returns command-line arguments.
   132  // It parses args for magic actions. If it finds one, it takes
   133  // control of your workflow and runs the action. Control is
   134  // not returned to your code.
   135  //
   136  // If no magic actions are found, it returns args.
   137  func (ma *MagicActions) Args(args []string, prefix string) []string {
   138  
   139  	args, handled := ma.handleArgs(args, prefix)
   140  
   141  	if handled {
   142  		finishLog(false)
   143  		os.Exit(0)
   144  	}
   145  
   146  	return args
   147  
   148  }
   149  
   150  // handleArgs checks args for the magic prefix. Returns args and true if
   151  // it found and handled a magic argument.
   152  func (ma *MagicActions) handleArgs(args []string, prefix string) ([]string, bool) {
   153  
   154  	var handled bool
   155  
   156  	for _, arg := range args {
   157  
   158  		arg = strings.TrimSpace(arg)
   159  
   160  		if strings.HasPrefix(arg, prefix) {
   161  
   162  			query := arg[len(prefix):]
   163  			action := ma.actions[query]
   164  
   165  			if action != nil {
   166  
   167  				log.Printf(action.RunText())
   168  
   169  				ma.wf.NewItem(action.RunText()).
   170  					Icon(IconInfo).
   171  					Valid(false)
   172  
   173  				ma.wf.SendFeedback()
   174  
   175  				if err := action.Run(); err != nil {
   176  					log.Printf("Error running magic arg `%s`: %s", action.Description(), err)
   177  					finishLog(true)
   178  				}
   179  
   180  				handled = true
   181  
   182  			} else {
   183  				for kw, action := range ma.actions {
   184  
   185  					ma.wf.NewItem(action.Keyword()).
   186  						Subtitle(action.Description()).
   187  						Valid(false).
   188  						Icon(IconInfo).
   189  						UID(action.Description()).
   190  						Autocomplete(prefix + kw).
   191  						Match(fmt.Sprintf("%s %s", action.Keyword(), action.Description()))
   192  				}
   193  
   194  				ma.wf.Filter(query)
   195  				ma.wf.WarnEmpty("No matching action", "Try another query?")
   196  				ma.wf.SendFeedback()
   197  
   198  				handled = true
   199  			}
   200  		}
   201  	}
   202  
   203  	return args, handled
   204  }
   205  
   206  // Opens workflow's log file.
   207  type logMA struct {
   208  	wf *Workflow
   209  }
   210  
   211  func (a logMA) Keyword() string     { return "log" }
   212  func (a logMA) Description() string { return "Open workflow's log file" }
   213  func (a logMA) RunText() string     { return "Opening log file…" }
   214  func (a logMA) Run() error          { return a.wf.OpenLog() }
   215  
   216  // Opens workflow's data directory.
   217  type dataMA struct {
   218  	wf *Workflow
   219  }
   220  
   221  func (a dataMA) Keyword() string     { return "data" }
   222  func (a dataMA) Description() string { return "Open workflow's data directory" }
   223  func (a dataMA) RunText() string     { return "Opening data directory…" }
   224  func (a dataMA) Run() error          { return a.wf.OpenData() }
   225  
   226  // Opens workflow's cache directory.
   227  type cacheMA struct {
   228  	wf *Workflow
   229  }
   230  
   231  func (a cacheMA) Keyword() string     { return "cache" }
   232  func (a cacheMA) Description() string { return "Open workflow's cache directory" }
   233  func (a cacheMA) RunText() string     { return "Opening cache directory…" }
   234  func (a cacheMA) Run() error          { return a.wf.OpenCache() }
   235  
   236  // Deletes the contents of the workflow's cache directory.
   237  type clearCacheMA struct {
   238  	wf *Workflow
   239  }
   240  
   241  func (a clearCacheMA) Keyword() string     { return "delcache" }
   242  func (a clearCacheMA) Description() string { return "Delete workflow's cached data" }
   243  func (a clearCacheMA) RunText() string     { return "Deleted workflow's cached data" }
   244  func (a clearCacheMA) Run() error          { return a.wf.ClearCache() }
   245  
   246  // Deletes the contents of the workflow's data directory.
   247  type clearDataMA struct {
   248  	wf *Workflow
   249  }
   250  
   251  func (a clearDataMA) Keyword() string     { return "deldata" }
   252  func (a clearDataMA) Description() string { return "Delete workflow's saved data" }
   253  func (a clearDataMA) RunText() string     { return "Deleted workflow's saved data" }
   254  func (a clearDataMA) Run() error          { return a.wf.ClearData() }
   255  
   256  // Deletes the contents of the workflow's cache & data directories.
   257  type resetMA struct {
   258  	wf *Workflow
   259  }
   260  
   261  func (a resetMA) Keyword() string     { return "reset" }
   262  func (a resetMA) Description() string { return "Delete all saved and cached workflow data" }
   263  func (a resetMA) RunText() string     { return "Deleted workflow saved and cached data" }
   264  func (a resetMA) Run() error          { return a.wf.Reset() }
   265  
   266  // Opens URL in default browser.
   267  type helpMA struct {
   268  	URL string
   269  }
   270  
   271  func (a helpMA) Keyword() string     { return "help" }
   272  func (a helpMA) Description() string { return "Open workflow help URL in default browser" }
   273  func (a helpMA) RunText() string     { return "Opening help in your browser…" }
   274  func (a helpMA) Run() error {
   275  	cmd := exec.Command("open", a.URL)
   276  	return cmd.Run()
   277  }
   278  
   279  // Updates the workflow if a newer release is available.
   280  type updateMA struct {
   281  	updater Updater
   282  }
   283  
   284  func (a updateMA) Keyword() string     { return "update" }
   285  func (a updateMA) Description() string { return "Check for updates, and install if one is available" }
   286  func (a updateMA) RunText() string     { return "Fetching update…" }
   287  func (a updateMA) Run() error {
   288  	if err := a.updater.CheckForUpdate(); err != nil {
   289  		return err
   290  	}
   291  	if a.updater.UpdateAvailable() {
   292  		return a.updater.Install()
   293  	}
   294  	log.Println("No update available")
   295  	return nil
   296  }