github.com/quantumghost/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 }