├── .env ├── .gitignore ├── LICENCE.txt ├── README.md ├── build-workflow.zsh ├── demo.gif ├── favourites.go ├── favourites_demo.go ├── go.mod ├── go.sum ├── icon.png ├── info.plist ├── main.go ├── modd.conf └── update-available.png /.env: -------------------------------------------------------------------------------- 1 | here="$( dirname "$0" )" 2 | ip="${here}/info.plist" 3 | pl=/usr/libexec/PlistBuddy 4 | 5 | export alfred_workflow_bundleid="$( $pl -c "Print :bundleid" "$ip" )" 6 | export alfred_workflow_name="$( $pl -c "Print :name" "$ip" )" 7 | export alfred_workflow_version="$( $pl -c "Print :version" "$ip" )" 8 | 9 | export alfred_workflow_cache="${HOME}/Library/Caches/com.runningwithcrayons.Alfred/Workflow Data/${alfred_workflow_bundleid}" 10 | export alfred_workflow_data="${HOME}/Library/Application Support/Alfred/Workflow Data/${alfred_workflow_bundleid}" 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | *.alfredworkflow 3 | 4 | # Created by https://www.gitignore.io/api/vim,go,sublimetext 5 | 6 | ### Go ### 7 | # Binaries for programs and plugins 8 | *.exe 9 | *.dll 10 | *.so 11 | *.dylib 12 | /forklift 13 | 14 | # Test binary, build with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 21 | .glide/ 22 | 23 | ### SublimeText ### 24 | # cache files for sublime text 25 | *.tmlanguage.cache 26 | *.tmPreferences.cache 27 | *.stTheme.cache 28 | 29 | # workspace files are user-specific 30 | *.sublime-workspace 31 | 32 | # project files should be checked into the repository, unless a significant 33 | # proportion of contributors will probably not be using SublimeText 34 | # *.sublime-project 35 | 36 | # sftp configuration file 37 | sftp-config.json 38 | 39 | # Package control specific files 40 | Package Control.last-run 41 | Package Control.ca-list 42 | Package Control.ca-bundle 43 | Package Control.system-ca-bundle 44 | Package Control.cache/ 45 | Package Control.ca-certs/ 46 | Package Control.merged-ca-bundle 47 | Package Control.user-ca-bundle 48 | oscrypto-ca-bundle.crt 49 | bh_unicode_properties.cache 50 | 51 | # Sublime-github package stores a github token in this file 52 | # https://packagecontrol.io/packages/sublime-github 53 | GitHub.sublime-settings 54 | 55 | ### Vim ### 56 | # swap 57 | [._]*.s[a-v][a-z] 58 | [._]*.sw[a-p] 59 | [._]s[a-v][a-z] 60 | [._]sw[a-p] 61 | # session 62 | Session.vim 63 | # temporary 64 | .netrwhist 65 | *~ 66 | # auto-generated tag files 67 | tags 68 | 69 | # End of https://www.gitignore.io/api/vim,go,sublimetext 70 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Secure SHell for Alfred. Open SSH connections from Alfred 2. 2 | 3 | The MIT License (MIT) 4 | --------------------- 5 | 6 | Copyright (c) 2016 Dean Jackson 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | ForkLift Favourites for Alfred 6 | ============================== 7 | 8 | Browse, filter and open [ForkLift 3][forklift] favourites from [Alfred 3+][alfred]. 9 | 10 | ![](https://github.com/deanishe/alfred-forklift/raw/master/demo.gif) 11 | 12 | 13 | Download & installation 14 | ----------------------- 15 | 16 | Download the workflow from [GitHub releases][ghr] and double-click the downloaded `ForkLift-Favourites-X.X.alfredworkflow` file. 17 | 18 | 19 | Usage 20 | ----- 21 | 22 | - `ftp []` — List/filter ForkLift favourites 23 | - `↩` — Open favourite in ForkLift 24 | 25 | 26 | Configuration 27 | ------------- 28 | 29 | The workflow has one setting in its configuration sheet (the `[𝑥]` symbol): 30 | 31 | `IGNORE_LOCAL` — Set to `1` or `true` to exclude Local favourites from results; set to `0`, `false` or empty to include Local favourites in results. 32 | 33 | 34 | 35 | Licensing & thanks 36 | ------------------ 37 | 38 | This workflow is released under the [MIT licence][mit]. 39 | 40 | It is based on the [AwGo library][awgo], also released under the MIT licence. 41 | 42 | 43 | [alfred]: https://www.alfredapp.com/ 44 | [awgo]: https://git.deanishe.net/deanishe/awgo/ 45 | [forklift]: http://www.binarynights.com/forklift/ 46 | [ghr]: https://github.com/deanishe/alfred-forklift/releases 47 | [mit]: ./LICENCE.txt 48 | -------------------------------------------------------------------------------- /build-workflow.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | set -e 4 | 5 | here="${${(%):-%x}:A:h}" 6 | source "${here}/.env" 7 | 8 | devmode=true 9 | verbose= 10 | 11 | log() { 12 | echo "$@" > /dev/stderr 13 | } 14 | 15 | usage() { 16 | cat < 2 | // MIT Licence applies http://opensource.org/licenses/MIT 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "path/filepath" 13 | "sort" 14 | 15 | aw "github.com/deanishe/awgo" 16 | "github.com/deanishe/awgo/util" 17 | ) 18 | 19 | // ForkLift's icon directory 20 | const resources = "/Applications/ForkLift.app/Contents/Resources/" 21 | 22 | var ( 23 | // path to favourites file 24 | favesFile = os.ExpandEnv("$HOME/Library/Application Support/ForkLift/Favorites/Favorites.json") 25 | ) 26 | 27 | // Favourite is a ForkLift favourite 28 | type Favourite struct { 29 | UUID string // Favourite UUID 30 | Name string // Name of favourite 31 | Group string // Name of favourite's group 32 | Path string // Favourite's path 33 | Server string // Hostname of favourite 34 | Type string // Type of favourite (SFTP, Local etc.) 35 | } 36 | 37 | // ByName sorts a slice of Favourites by name. 38 | type ByName []Favourite 39 | 40 | func (s ByName) Len() int { return len(s) } 41 | func (s ByName) Less(i, j int) bool { return s[i].Name < s[j].Name } 42 | func (s ByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 43 | 44 | // Icon returns the appropriate icon for the favourite's type 45 | func (f Favourite) Icon() *aw.Icon { 46 | switch f.Type { 47 | case "Local": 48 | return &aw.Icon{Value: f.Path, Type: aw.IconTypeFileIcon} 49 | case "Backblaze": 50 | return &aw.Icon{Value: resources + "ConnectionBackblaze.icns"} 51 | case "FTP": 52 | return &aw.Icon{Value: resources + "ConnectionFTP.icns"} 53 | case "GoogleDrive": 54 | return &aw.Icon{Value: resources + "ConnectionGoogleDrive.icns"} 55 | case "NFS": 56 | return &aw.Icon{Value: resources + "ConnectionNFS.icns"} 57 | case "Rackspace": 58 | return &aw.Icon{Value: resources + "ConnectionRackspace.icns"} 59 | case "S3": 60 | return &aw.Icon{Value: resources + "ConnectionS3.icns"} 61 | case "SFTP": 62 | return &aw.Icon{Value: resources + "ConnectionSFTP.icns"} 63 | case "Search": 64 | return &aw.Icon{Value: resources + "ConnectionSearch.icns"} 65 | case "Sync": 66 | return &aw.Icon{Value: resources + "ConnectionSync.icns"} 67 | case "VNC": 68 | return &aw.Icon{Value: resources + "ConnectionVNC.icns"} 69 | case "WebDAV", "WebDAVHTTPS": 70 | return &aw.Icon{Value: resources + "ConnectionWebDAV.icns"} 71 | case "Workspace": 72 | return &aw.Icon{Value: resources + "ConnectionWorkspace.icns"} 73 | default: 74 | log.Printf("[WARN] unknown type: %s", f.Type) 75 | return iconDefault 76 | } 77 | } 78 | 79 | // loadFavourites reads favourites from JSON file at path. 80 | func loadFavourites(path string) ([]Favourite, error) { 81 | var faves = []Favourite{} 82 | 83 | if !util.PathExists(path) { 84 | return nil, fmt.Errorf("favourites file does not exist: %s", path) 85 | } 86 | data, err := ioutil.ReadFile(path) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | raw := struct { 92 | Groups []struct { 93 | UUID string `json:"UUID"` 94 | Attr struct { 95 | Name string 96 | Path string 97 | Server string 98 | } `json:"attributes"` 99 | Children []struct { 100 | UUID string `json:"UUID"` 101 | Attr struct { 102 | Name string 103 | Path string 104 | Server string 105 | } `json:"attributes"` 106 | Type string `json:"type"` 107 | } `json:"childItems"` 108 | Type string `json:"type"` 109 | } `json:"favorites"` 110 | }{} 111 | if err := json.Unmarshal(data, &raw); err != nil { 112 | return nil, fmt.Errorf("error unmarshalling %s: %s", path, err) 113 | } 114 | log.Printf("%2d group(s)", len(raw.Groups)) 115 | for _, fg := range raw.Groups { 116 | log.Printf("%2d favourite(s) in group %q", len(fg.Children), fg.Attr.Name) 117 | for _, f := range fg.Children { 118 | 119 | fav := Favourite{ 120 | UUID: f.UUID, 121 | Name: f.Attr.Name, 122 | Group: fg.Attr.Name, 123 | Path: f.Attr.Path, 124 | Server: f.Attr.Server, 125 | Type: f.Type, 126 | } 127 | 128 | if ignoreLocal && fav.Type == "Local" { 129 | continue 130 | } 131 | 132 | // Ignore local favourites whose path doesn't exist 133 | if fav.Type == "Local" && !util.PathExists(fav.Path) { 134 | continue 135 | } 136 | 137 | if fav.Name == "" && fav.Path != "" { 138 | fav.Name = filepath.Base(fav.Path) 139 | } 140 | 141 | faves = append(faves, fav) 142 | } 143 | } 144 | 145 | sort.Sort(ByName(faves)) 146 | 147 | return faves, nil 148 | } 149 | -------------------------------------------------------------------------------- /favourites_demo.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Dean Jackson 3 | // 4 | // MIT Licence. See http://opensource.org/licenses/MIT 5 | // 6 | // Created on 2017-07-28 7 | // 8 | 9 | package main 10 | 11 | // demoFavourites returns sample data 12 | func demoFavourites() []Favourite { 13 | return []Favourite{ 14 | { 15 | UUID: "BA892C03-0300-4A56-AB60-A116FBC4B84C", 16 | Name: "Warez", 17 | Group: "Data", 18 | Server: "www.warez.ru", 19 | Type: "FTP", 20 | }, 21 | { 22 | UUID: "87ECFC95-FE2F-4327-839B-CE9C2E43C96C", 23 | Name: "Ubuntu ISOs", 24 | Group: "Data", 25 | Server: "ftp.ubuntu.com", 26 | Type: "FTP", 27 | }, 28 | { 29 | UUID: "16E522BD-1213-4894-8FB1-F86C5C017A0A", 30 | Name: "OpenStreetMap", 31 | Group: "Data", 32 | Server: "ftp5.gwdg.de", 33 | Type: "FTP", 34 | }, 35 | { 36 | UUID: "3DD969C4-773B-4600-8F8F-13416CC68A57", 37 | Name: "Homepage", 38 | Group: "Data", 39 | Server: "webdav.example.com", 40 | Type: "WebDAV", 41 | }, 42 | { 43 | UUID: "711B838F-E565-470E-8086-53ADFFDAA32E", 44 | Name: "NAS", 45 | Group: "Data", 46 | Server: "192.168.0.5", 47 | Type: "Workspace", 48 | }, 49 | { 50 | UUID: "8935EF9D-BBB6-4B0B-87E4-80CCA3E84DE6", 51 | Name: "S3 Bucket", 52 | Group: "Data", 53 | Server: "mybucket.amazon.com", 54 | Type: "S3", 55 | }, 56 | { 57 | UUID: "FA1B24E2-32ED-42BE-B28E-00534223EBFD", 58 | Name: "www.example.com", 59 | Group: "Data", 60 | Server: "server.example.com", 61 | Type: "SFTP", 62 | }, 63 | { 64 | UUID: "751DA871-C5D5-44C6-BA29-B8CF6CBFA0FF", 65 | Name: "demo.example.com", 66 | Group: "Data", 67 | Server: "server.example.com", 68 | Type: "SFTP", 69 | }, 70 | { 71 | UUID: "56AD2BFC-A7CD-4085-BED0-E50E2E5FB6D1", 72 | Name: "reynolds.com", 73 | Group: "Data", 74 | Server: "reynolds.com", 75 | Type: "Workspace", 76 | }, 77 | { 78 | UUID: "1857118C-08C6-4AB1-B2BA-3A6B02318F59", 79 | Name: "ullmann.org", 80 | Group: "Data", 81 | Server: "ullmann.org", 82 | Type: "SFTP", 83 | }, 84 | { 85 | UUID: "B21FBB02-048F-44B5-93D8-0E08E8A8D4D6", 86 | Name: "Server Logs", 87 | Group: "Data", 88 | Server: "fiebig.net", 89 | Type: "Sync", 90 | }, 91 | { 92 | UUID: "45125B47-75BC-4CE3-A864-941B4CE24422", 93 | Name: "iPhone (8080)", 94 | Group: "Data", 95 | Server: "192.168.0.2", 96 | Type: "WebDAV", 97 | }, 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deanishe/alfred-forklift 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/deanishe/awgo v0.20.4 7 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= 2 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/deanishe/awgo v0.20.4 h1:d6PUWriwtOtJr+wxxNwQ58N50fr9lhsf3Z5XhWfmYnI= 5 | github.com/deanishe/awgo v0.20.4/go.mod h1:GU6eQZvFKhxX6Qjlb5Qls/Le/3r5XOs3P0L4F283R+w= 6 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= 7 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 8 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 9 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 10 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 11 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 12 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 13 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 18 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 19 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 20 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 21 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 23 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 24 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 25 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 26 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 27 | howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= 28 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deanishe/alfred-forklift/8469ea72c0d04578196026e510af5cb5a7494597/icon.png -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | net.deanishe.alfred.forklift 7 | connections 8 | 9 | BC70CD57-DACE-4F66-A7F5-758FBEA3A948 10 | 11 | 12 | destinationuid 13 | 0C7F2339-51B3-4C59-A4E6-D134D8F91FD0 14 | modifiers 15 | 0 16 | modifiersubtext 17 | 18 | vitoclose 19 | 20 | 21 | 22 | 23 | createdby 24 | Dean Jackson 25 | description 26 | Browse and open ForkLift favourites 27 | disabled 28 | 29 | name 30 | ForkLift Favourites 31 | objects 32 | 33 | 34 | config 35 | 36 | alfredfiltersresults 37 | 38 | alfredfiltersresultsmatchmode 39 | 0 40 | argumenttrimmode 41 | 0 42 | argumenttype 43 | 1 44 | escaping 45 | 102 46 | keyword 47 | ftp 48 | queuedelaycustom 49 | 3 50 | queuedelayimmediatelyinitially 51 | 52 | queuedelaymode 53 | 0 54 | queuemode 55 | 1 56 | runningsubtext 57 | Loading favourites… 58 | script 59 | ./forklift "$1" 60 | scriptargtype 61 | 1 62 | scriptfile 63 | 64 | subtext 65 | 66 | title 67 | ForkLift Favourites 68 | type 69 | 0 70 | withspace 71 | 72 | 73 | type 74 | alfred.workflow.input.scriptfilter 75 | uid 76 | BC70CD57-DACE-4F66-A7F5-758FBEA3A948 77 | version 78 | 2 79 | 80 | 81 | config 82 | 83 | browser 84 | 85 | spaces 86 | 87 | url 88 | forkliftfav://{query} 89 | utf8 90 | 91 | 92 | type 93 | alfred.workflow.action.openurl 94 | uid 95 | 0C7F2339-51B3-4C59-A4E6-D134D8F91FD0 96 | version 97 | 1 98 | 99 | 100 | readme 101 | ForkLift Favourites 102 | =================== 103 | 104 | Search ForkLift 3 favourites in Alfred 3+. 105 | 106 | Default keyword is "ftp". 107 | 108 | 109 | Configuration 110 | ------------- 111 | 112 | IGNORE_LOCAL 113 | = 1/true -- Don't show local favourites 114 | = 0/false -- Include local favourites in results 115 | uidata 116 | 117 | 0C7F2339-51B3-4C59-A4E6-D134D8F91FD0 118 | 119 | colorindex 120 | 3 121 | xpos 122 | 240 123 | ypos 124 | 40 125 | 126 | BC70CD57-DACE-4F66-A7F5-758FBEA3A948 127 | 128 | colorindex 129 | 3 130 | xpos 131 | 50 132 | ypos 133 | 40 134 | 135 | 136 | variables 137 | 138 | IGNORE_LOCAL 139 | true 140 | 141 | version 142 | 0.1.3 143 | webaddress 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Dean Jackson 3 | // 4 | // MIT Licence. See http://opensource.org/licenses/MIT 5 | // 6 | // Created on 2017-07-20 7 | // 8 | 9 | // alfred-forklift is a Script Filter for Alfred 3+ to open ForkLift favourites. 10 | package main 11 | 12 | import ( 13 | "flag" 14 | "fmt" 15 | "log" 16 | "os" 17 | "os/exec" 18 | 19 | aw "github.com/deanishe/awgo" 20 | "github.com/deanishe/awgo/update" 21 | ) 22 | 23 | var ( 24 | // workflow configuration 25 | repo = "deanishe/alfred-forklift" 26 | helpURL = "https://github.com/deanishe/alfred-forklift/issues" 27 | iconDefault = &aw.Icon{Value: "icon.png"} 28 | iconUpdate = &aw.Icon{Value: "update-available.png"} 29 | wf *aw.Workflow 30 | 31 | // CLI options and workflow settings 32 | cli *flag.FlagSet 33 | query string 34 | demoMode bool 35 | doUpdate bool 36 | ignoreLocal bool 37 | ) 38 | 39 | func init() { 40 | wf = aw.New( 41 | update.GitHub(repo), 42 | aw.HelpURL(helpURL), 43 | ) 44 | 45 | cli = flag.NewFlagSet("forklift", flag.ExitOnError) 46 | cli.Usage = func() { 47 | fmt.Fprintf(os.Stderr, `forklift [options] [] 48 | 49 | Alfred 3+ workflow for searching ForkLift 3 favourites. 50 | 51 | Usage: 52 | forklift [-demo] [] 53 | forklift -update 54 | forklift -h 55 | 56 | Options: 57 | `) 58 | cli.PrintDefaults() 59 | } 60 | 61 | cli.BoolVar(&demoMode, "demo", false, "use demo data instead of real favourites") 62 | cli.BoolVar(&doUpdate, "update", false, "check whether an update is available") 63 | } 64 | 65 | // run starts the workflow 66 | func run() { 67 | if err := cli.Parse(wf.Args()); err != nil { 68 | wf.FatalError(err) 69 | } 70 | query = cli.Arg(0) 71 | ignoreLocal = wf.Config.GetBool("IGNORE_LOCAL", false) 72 | 73 | // Alternate actions ---------------------------------- 74 | if doUpdate { 75 | wf.Configure(aw.TextErrors(true)) 76 | log.Printf("checking for update...") 77 | if err := wf.CheckForUpdate(); err != nil { 78 | wf.FatalError(err) 79 | } 80 | return 81 | } 82 | 83 | // Script Filter ------------------------------------- 84 | log.Printf("query=%q", query) 85 | 86 | // Notify updates 87 | if wf.UpdateCheckDue() && !wf.IsRunning("update") { 88 | log.Printf("checking for update ...") 89 | cmd := exec.Command(os.Args[0], "-update") 90 | if err := wf.RunInBackground("update", cmd); err != nil { 91 | log.Printf("[ERROR] update check failed: %v", err) 92 | } 93 | } 94 | 95 | if query == "" && wf.UpdateAvailable() { 96 | log.Printf("update available") 97 | wf.NewItem("An update is available"). 98 | Subtitle("↩ or ⇥ to install update"). 99 | Valid(false). 100 | Autocomplete("workflow:update"). 101 | Icon(iconUpdate) 102 | wf.Configure(aw.SuppressUIDs(true)) 103 | } 104 | 105 | var ( 106 | faves []Favourite 107 | err error 108 | ) 109 | 110 | if demoMode { 111 | faves = demoFavourites() 112 | } else { 113 | faves, err = loadFavourites(favesFile) 114 | if err != nil { 115 | wf.Fatalf("couldn't load favourites: %v", err) 116 | } 117 | } 118 | log.Printf("%d favourite(s)", len(faves)) 119 | 120 | for _, f := range faves { 121 | log.Printf("%s (%s)", f.Name, f.Type) 122 | wf.NewItem(f.Name). 123 | Subtitle(f.Server). 124 | Arg(f.UUID). 125 | UID(f.UUID). 126 | Match(fmt.Sprintf("%s %s", f.Name, f.Server)). 127 | Icon(f.Icon()). 128 | Valid(true) 129 | } 130 | 131 | if query != "" { 132 | res := wf.Filter(query) 133 | log.Printf("%d favourite(s) match %q", len(res), query) 134 | } 135 | 136 | wf.WarnEmpty("No favourites found", "Try a different query?") 137 | 138 | wf.SendFeedback() 139 | } 140 | 141 | func main() { 142 | wf.Run(run) 143 | } 144 | -------------------------------------------------------------------------------- /modd.conf: -------------------------------------------------------------------------------- 1 | 2 | modd.conf 3 | build-workflow.zsh 4 | **/*.go 5 | !**/*_test.go { 6 | prep: ./build-workflow.zsh -v 7 | } 8 | 9 | **/*_test.go { 10 | prep: go test -v @dirmods 11 | } 12 | 13 | -------------------------------------------------------------------------------- /update-available.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deanishe/alfred-forklift/8469ea72c0d04578196026e510af5cb5a7494597/update-available.png --------------------------------------------------------------------------------