diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b27546d --- /dev/null +++ b/.gitignore @@ -0,0 +1,67 @@ + +# Created by https://www.gitignore.io/api/vim,go,sublimetext + +### Go ### +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib +/forklift + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +### SublimeText ### +# cache files for sublime text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# workspace files are user-specific +*.sublime-workspace + +# project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using SublimeText +# *.sublime-project + +# sftp configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + +### Vim ### +# swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + +# End of https://www.gitignore.io/api/vim,go,sublimetext diff --git a/ForkLift-Favourites-0.1.alfredworkflow b/ForkLift-Favourites-0.1.alfredworkflow new file mode 100644 index 0000000..edc6a8c Binary files /dev/null and b/ForkLift-Favourites-0.1.alfredworkflow differ diff --git a/LICENCE.txt b/LICENCE.txt new file mode 100644 index 0000000..eab4338 --- /dev/null +++ b/LICENCE.txt @@ -0,0 +1,26 @@ +Secure SHell for Alfred. Open SSH connections from Alfred 2. + +The MIT License (MIT) +--------------------- + +Copyright (c) 2016 Dean Jackson + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe198ca --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +ForkLift Favourites for Alfred +============================== + +Browse, filter and open [ForkLift 3][forklift] favourites from [Alfred][alfred]. + + +Usage +----- + +- `ftp []` — List/filter ForkLift favourites + - `↩` — Open favourite in ForkLift + + +Licensing & thanks +------------------ + +This workflow is released under the [MIT licence][mit]. + +It is based on the [AwGo library][awgo], also released under the MIT licence. + + +[alfred]: https://www.alfredapp.com +[awgo]: https://git.deanishe.net/deanishe/awgo/ +[forklift]: http://www.binarynights.com/forklift/ +[mit]: ./LICENCE.txt diff --git a/build-workflow.sh b/build-workflow.sh new file mode 100755 index 0000000..eff3db6 --- /dev/null +++ b/build-workflow.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +here="$( cd "$( dirname "$0" )"; pwd )" + + +log() { + echo "$@" > /dev/stderr +} + +pushd "$here" &> /dev/null + +log "cleaning ./build ..." +rm -rvf ./build + +log + +log "copying assets to ./build ..." + +mkdir -vp ./build + +cp -v icon.png ./build/ +cp -v info.plist ./build/ +cp -v README.md ./build/ +cp -v LICENCE.txt ./build/ + +log + +log "building executable(s) ..." +go build -v -o ./forklift ./forklift.go +ST_BUILD=$? +if [ "$ST_BUILD" != 0 ]; then + log "error building executable." + rm -rf ./build/ + popd &> /dev/null + exit $ST_BUILD +fi + +chmod 755 ./forklift +cp -v ./forklift ./build/forklift + +# Get the dist filename from the executable +zipfile="$(./forklift --distname 2> /dev/null)" + +log + +if test -e "$zipfile"; then + log "removing existing .alfredworkflow file ..." + rm -rvf "$zipfile" + log +fi + +log "building .alfredworkflow file ..." +pushd ./build/ &> /dev/null +zip -v "../${zipfile}" * +ST_ZIP=$? +if [ "$ST_ZIP" != 0 ]; then + log "error creating .alfredworkflow file." + rm -rf ./build/ + popd &> /dev/null + exit $ST_ZIP +fi +popd &> /dev/null + +log + +log "cleaning up ..." +rm -rvf ./build/ + +popd &> /dev/null +log "all done." + diff --git a/forklift.go b/forklift.go new file mode 100644 index 0000000..b8b6bad --- /dev/null +++ b/forklift.go @@ -0,0 +1,239 @@ +// +// Copyright (c) 2017 Dean Jackson +// +// MIT Licence. See http://opensource.org/licenses/MIT +// +// Created on 2017-07-20 +// + +/* +forklift.go +=========== + +A Script Filter for Alfred 3 to open ForkLift favourites. +*/ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + + aw "git.deanishe.net/deanishe/awgo" + "github.com/docopt/docopt-go" +) + +var ( + usage = `forklift [options] [] + +Filter ForkLift favourites in Alfred 3. + +Usage: + forklift [] + forklift --help | --version + forklift --datadir | --cachedir | --distname | --logfile + +Options: + --datadir Print path to workflow's data directory and exit. + --cachedir Print path to workflow's cache directory and exit. + --logfile Print path to workflow's log file and exit. + --distname Print filename of distributable .alfredworkflow file + (for the build script). + -h, --help Show this message and exit. + +` + favesFile = os.ExpandEnv("$HOME/Library/Application Support/ForkLift/Favorites/Favorites.json") +) + +var ( + connectionTypes = []string{"Local", "SFTP", "NFS", "Rackspace", "S3", "Search", "FTP", "Sync", "VNC", "WebDAV", "Workspace"} + iconDefault = &aw.Icon{Value: "icon.png", Type: aw.IconTypeImageFile} + connectionIcons map[string]*aw.Icon +) + +func init() { + connectionIcons = map[string]*aw.Icon{} + for _, s := range connectionTypes { + path := fmt.Sprintf("/Applications/ForkLift.app/Contents/Resources/Connection%s.icns", s) + connectionIcons[s] = &aw.Icon{Value: path, Type: aw.IconTypeImageFile} + } +} + +type faves struct { + Groups []*faveGroup `json:"favorites"` +} + +type fave struct { + UUID string `json:"UUID"` + Attr *faveAttr `json:"attributes"` + Type string `json:"type"` +} + +type faveGroup struct { + UUID string `json:"UUID"` + Attr *faveAttr `json:"attributes"` + Children []fave `json:"childItems"` + Type string `json:"type"` +} + +type faveAttr struct { + Name string + Path string + Server string +} + +// Favourite is a ForkLift favourite +type Favourite struct { + UUID string // Favourite UUID + Name string // Name of favourite + Group string // Name of favourite's group + Path string // Favourite's path + Server string // Hostname of favourite + Type string // Type of favourite (SFTP, Local etc.) +} + +func (f *Favourite) Icon() *aw.Icon { + if f.Type == "Local" { + return &aw.Icon{Value: f.Path, Type: aw.IconTypeFileIcon} + } + for t, i := range connectionIcons { + if strings.Index(f.Type, t) > -1 { + return i + } + } + return iconDefault +} + +// newIcon creates a new workflow icon from an icon file in ForkLift's +// Resources directory. +func newIcon(filename string) *aw.Icon { + path := fmt.Sprintf("/Applications/ForkLift.app/Contents/Resources/Connection%s.icns", filename) + return &aw.Icon{Value: path, Type: aw.IconTypeImageFile} +} + +// loadFavourites reads favourites from JSON file at path. +func loadFavourites(path string) ([]*Favourite, error) { + var favourites = []*Favourite{} + + if !aw.PathExists(path) { + return nil, fmt.Errorf("file does not exist: %s", path) + } + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + rawFaves := &faves{} + if err := json.Unmarshal(data, rawFaves); err != nil { + return nil, fmt.Errorf("error unmarshalling %s: %s", path, err) + } + log.Printf("%d groups", len(rawFaves.Groups)) + for _, fg := range rawFaves.Groups { + log.Printf("group '%s' contains %d favourites", fg.Attr.Name, len(fg.Children)) + for _, f := range fg.Children { + + fav := &Favourite{ + UUID: f.UUID, + Name: f.Attr.Name, + Group: fg.Attr.Name, + Path: f.Attr.Path, + Server: f.Attr.Server, + Type: f.Type, + } + + // Ignore local favourites whose path doesn't exist + if fav.Type == "Local" && !aw.PathExists(fav.Path) { + continue + } + + if fav.Name == "" && fav.Path != "" { + fav.Name = filepath.Base(fav.Path) + } + + favourites = append(favourites, fav) + } + } + return favourites, nil +} + +// run starts the workflow +func run() { + var query string + + vstr := fmt.Sprintf("%s/%v (awgo/%v)", aw.Name(), aw.Version(), aw.AwGoVersion) + args, err := docopt.Parse(usage, nil, true, vstr, false) + if err != nil { + log.Fatalf("error parsing CLI options: %s", err) + } + + log.Printf("args=%+v", args) + + // ================ Alternate actions ==================== + + if args["--datadir"] == true { + fmt.Println(aw.DataDir()) + return + } + + if args["--cachedir"] == true { + fmt.Println(aw.CacheDir()) + return + } + + if args["--logfile"] == true { + fmt.Println(aw.LogFile()) + return + } + + if args["--distname"] == true { + name := strings.Replace( + fmt.Sprintf("%s-%s.alfredworkflow", aw.Name(), aw.Version()), + " ", "-", -1) + fmt.Println(name) + return + } + + // ================ Script Filter ==================== + + if args[""] != nil { + query = fmt.Sprintf("%v", args[""]) + } + log.Printf("query=%s", query) + + // Load favourites + faves, err := loadFavourites(favesFile) + if err != nil { + panic(fmt.Sprintf("couldn't load favourites: %s", err)) + } + log.Printf("%d favourites", len(faves)) + + for _, f := range faves { + log.Printf("%s (%s)", f.Name, f.Type) + it := aw.NewItem(f.Name). + Subtitle(f.Server). + Arg(f.UUID). + SortKey(fmt.Sprintf("%s %s", f.Name, f.Server)). + Icon(f.Icon()). + Valid(true) + + it.Var("UUID", f.UUID) + } + + if query != "" { + res := aw.Filter(query) + log.Printf("%d favourites match '%s'", len(res), query) + } + + aw.WarnEmpty("No matching favourites", "Try a different query?") + + aw.SendFeedback() +} + +// main calls run via aw.Run() +func main() { + aw.Run(run) +} diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..7a39fe2 Binary files /dev/null and b/icon.png differ diff --git a/info.plist b/info.plist new file mode 100644 index 0000000..1c471e6 --- /dev/null +++ b/info.plist @@ -0,0 +1,122 @@ + + + + + bundleid + net.deanishe.alfred.forklift + connections + + BC70CD57-DACE-4F66-A7F5-758FBEA3A948 + + + destinationuid + 0C7F2339-51B3-4C59-A4E6-D134D8F91FD0 + modifiers + 0 + modifiersubtext + + vitoclose + + + + + createdby + Dean Jackson + description + Browse and open ForkLift favourites + disabled + + name + ForkLift Favourites + objects + + + config + + alfredfiltersresults + + argumenttrimmode + 0 + argumenttype + 1 + escaping + 102 + keyword + ftp + queuedelaycustom + 3 + queuedelayimmediatelyinitially + + queuedelaymode + 0 + queuemode + 1 + runningsubtext + Loading favourites… + script + ./forklift "$1" + scriptargtype + 1 + scriptfile + + subtext + + title + ForkLift Favourites + type + 0 + withspace + + + type + alfred.workflow.input.scriptfilter + uid + BC70CD57-DACE-4F66-A7F5-758FBEA3A948 + version + 2 + + + config + + browser + + spaces + + url + forkliftfav://{query} + utf8 + + + type + alfred.workflow.action.openurl + uid + 0C7F2339-51B3-4C59-A4E6-D134D8F91FD0 + version + 1 + + + readme + + uidata + + 0C7F2339-51B3-4C59-A4E6-D134D8F91FD0 + + xpos + 240 + ypos + 40 + + BC70CD57-DACE-4F66-A7F5-758FBEA3A948 + + xpos + 50 + ypos + 40 + + + version + 0.1 + webaddress + + + diff --git a/modd.conf b/modd.conf new file mode 100644 index 0000000..e589565 --- /dev/null +++ b/modd.conf @@ -0,0 +1,9 @@ + +**/*.go { + prep: go build -v -o ./forklift ./forklift.go +} + +/Users/daj/src/git.deanishe.net/deanishe/awgo/**/*.go { + prep: go build -v -o ./forklift ./forklift.go +} +