├── Copy Paths to Clipboard.alfredworkflow ├── README.md ├── screenshots ├── copypaths_actions.png ├── copypaths_paths.png └── copypaths_s_q_n.png └── src ├── AlFeedback.py ├── CopyAllPaths.py ├── allpaths.applescript ├── copypaths_main.py ├── copypaths_scriptfilter.py ├── fileurl_to_hfs.applescript ├── fileurl_to_posix.applescript ├── posix_to_fileurl.applescript └── posix_to_hfs.applescript /Copy Paths to Clipboard.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzheidl/copy-paths-to-clipboard/5a24065249ea3b96ea783546e7ad7a11e3ded31f/Copy Paths to Clipboard.alfredworkflow -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Copy Paths to Clipboard 2 | 3 | ![Copy Paths to Clipboard Screenshot](screenshots/copypaths_paths.png) 4 | 5 | 6 | Workflow for Alfred 2 and 3 to copy the paths of your current files and Documents to the clipboard in a variety of formats: 7 | 8 | * POSIX, HFS paths and file URLs 9 | * '~/'-shortened POSIX paths 10 | * quoted paths 11 | * comma- or newline-separated. 12 | 13 | All options (except newline-separation) are available as File Actions also. 14 | 15 | ## Usage 16 | 17 | Invoke the scriptfilter by typing **`paths`**. If you are in Finder, the path(s) of your current Finder item(s) or selection will be copied to the clipboard, if you're in an other (document-based) application the path of your frontmost document will be copied. The scriptfilter will give you feedback as to how exactly your path will be copied upon actioning your input in the subtitle. 18 | 19 | ### Options 20 | 21 | ![Copy Paths to Clipboard Screenshot](screenshots/copypaths_s_q_n.png) 22 | 23 | By default and without using any of the options, paths will be copied as as-is slash-delimited POSIX paths: 24 | 25 | /Users/UserName/Documents/File.txt 26 | 27 | **`-hfs`** copies your path as an HFS path. 28 | 29 | Macintosh HD:Users:UserName:Documents:File.txt 30 | 31 | **`-url`** copies url-encoded file urls 32 | 33 | file:///Users/franz/Documents/File.txt 34 | 35 | 36 | **`-s`** '~/'-shortens paths under user home. Applies to POSIX paths only. 37 | 38 | ~/Documents/File.txt 39 | 40 | 41 | **`-q`** copies paths with quotes. 42 | POSIX paths will be quoted using single quotes. 43 | 44 | '/Users/UserName/Documents/File.txt' 45 | 46 | HFS paths will be quoted using double quotes with **`-q`**. 47 | 48 | "Macintosh HD:Users:UserName:Documents:File.txt" 49 | 50 | **`-n`** will put each path on a new line if you have multiple paths. 51 | 52 | /Users/UserName/Documents/File1.txt 53 | /Users/UserName/Documents/File2.txt 54 | 55 | By default, multiple paths will be comma separated. 56 | 57 | /Users/UserName/Documents/File1.txt, /Users/UserName/Documents/File2.txt 58 | 59 | All options can be combined. 60 | The scriptfilter subtitle will update the path as it will be copied as per your current options (if you're using **`-n`**, obviously only the first path will be visible) 61 | 62 | 63 | ### File Actions 64 | 65 | This Workflow also adds File Actions for all the options above except newline-separation. Each of these File Actions accept multiple items (use [alt]-[arrow-up] to add an item to your selection, [alt]-[arrow-right] to display File Actions for your selection of multiple items). 66 | 67 | ![Copy Paths to Clipboard Screenshot](screenshots/copypaths_actions.png) 68 | 69 | 70 | ### Changelog 71 | 72 | ###### ***June 17, 2015*** 73 | 74 | Merged two PRs: 75 | * Add missing leading slash to POSIX paths 76 | * Fix unicode paths 77 | 78 | ###### ***November 15, 2014*** 79 | 80 | Add option to copy as file urls, update icons to be slightly more Yosemite-like, change hfs option to '-hfs' (incl. the leading hyphen) to be consistent with the other options 81 | 82 | ###### ***August 23, 2014*** 83 | 84 | Improve stability for applications other than Finder 85 | 86 | ###### ***June 18, 2013*** 87 | 88 | Add support for paths containing non-ASCII/Unicode characters in the scriptfilter (use quoted option `-q` if you're copying such paths for the Terminal) 89 | 90 | 91 | ## License 92 | 93 | ### The MIT License (MIT) 94 | 95 | 96 | Copyright (c) 2013 - 2014 Franz Heidl 97 | 98 | Permission is hereby granted, free of charge, to any person obtaining a copy 99 | of this software and associated documentation files (the "Software"), to deal 100 | in the Software without restriction, including without limitation the rights 101 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 102 | copies of the Software, and to permit persons to whom the Software is 103 | furnished to do so, subject to the following conditions: 104 | 105 | The above copyright notice and this permission notice shall be included in 106 | all copies or substantial portions of the Software. 107 | 108 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 109 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 110 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 111 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 112 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 113 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 114 | THE SOFTWARE. 115 | 116 | 117 | -------------------------------------------------------------------------------- /screenshots/copypaths_actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzheidl/copy-paths-to-clipboard/5a24065249ea3b96ea783546e7ad7a11e3ded31f/screenshots/copypaths_actions.png -------------------------------------------------------------------------------- /screenshots/copypaths_paths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzheidl/copy-paths-to-clipboard/5a24065249ea3b96ea783546e7ad7a11e3ded31f/screenshots/copypaths_paths.png -------------------------------------------------------------------------------- /screenshots/copypaths_s_q_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzheidl/copy-paths-to-clipboard/5a24065249ea3b96ea783546e7ad7a11e3ded31f/screenshots/copypaths_s_q_n.png -------------------------------------------------------------------------------- /src/AlFeedback.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # AlFeedback 4 | # Simple scriptfilter feedback for Alfred 2. 5 | # Franz Heidl 2013 6 | # http://github.com/franzheidl/ 7 | # MIT license. 8 | 9 | 10 | from xml.etree import ElementTree as eTree 11 | import time 12 | 13 | class Feedback: 14 | 15 | def __init__(self, *items): 16 | self.feedback = eTree.Element('items') 17 | # Add items on init, expects list of dicts representing on item each: 18 | if items: 19 | for item in items: 20 | self.addItem(**item) 21 | 22 | 23 | def addItem(self, **kwargs): 24 | # Create Item 25 | item = eTree.SubElement(self.feedback, 'item') 26 | 27 | # Set Item Attributes 28 | if 'valid' in kwargs.keys(): 29 | item.set('valid', kwargs['valid']) 30 | if 'arg' in kwargs.keys(): 31 | item.set('arg', kwargs['arg']) 32 | if 'autocomplete' in kwargs.keys(): 33 | item.set('autocomplete', kwargs['autocomplete']) 34 | if 'type' in kwargs.keys(): 35 | item.set('type', kwargs['type']) 36 | if 'uid' in kwargs.keys(): 37 | if kwargs.get('uid') == 'timestamp': 38 | item.set('uid', self.timestamp()) 39 | else: 40 | item.set('uid', kwargs['uid']) 41 | 42 | # Create Item Elements 43 | if 'title' in kwargs.keys(): 44 | title = eTree.SubElement(item, 'title') 45 | title.text = kwargs['title'] 46 | if 'subtitle' in kwargs.keys(): 47 | subtitle = eTree.SubElement(item, 'subtitle') 48 | subtitle.text = kwargs['subtitle'] 49 | if 'icon' in kwargs.keys(): 50 | icon = eTree.SubElement(item, 'icon') 51 | icon.text = kwargs['icon'] 52 | if 'icontype' in kwargs.keys(): 53 | icon.set('type', kwargs['icontype']) 54 | 55 | 56 | def timestamp(self): 57 | return time.strftime('%Y-%m-%d-%H%M%S%Z') 58 | 59 | def __repr__(self): 60 | return eTree.tostring(self.feedback, encoding="utf-8") 61 | -------------------------------------------------------------------------------- /src/CopyAllPaths.py: -------------------------------------------------------------------------------- 1 | # Copy All Paths to Clipboard 2 | # Workflow For Alfred 2 3 | # Franz Heidl 2013 - 2014 4 | # MIT license. 5 | 6 | import subprocess 7 | 8 | class CopyAllPaths: 9 | 10 | def __init__(self, p): 11 | self.urls = self.listPaths(p) 12 | self.paths = self.listPaths(self.posixPaths()) 13 | 14 | 15 | def listPaths(self, p): 16 | return (p).split(", ") 17 | 18 | 19 | def strPaths(self): 20 | sp = "" 21 | for a in self.paths: 22 | a.strip("'") 23 | if sp != "": 24 | sp += ", " 25 | sp += a 26 | return sp 27 | 28 | 29 | def strUrls(self): 30 | su = "" 31 | for a in self.urls: 32 | a.strip("'") 33 | if su != "": 34 | su += ", " 35 | su += a 36 | return su 37 | 38 | 39 | def posixPaths(self): 40 | pp = "" 41 | for a in self.urls: 42 | p = subprocess.check_output(["osascript", "fileurl_to_posix.applescript", a]).strip() 43 | if pp != "": 44 | pp += ", " 45 | pp += p 46 | return pp 47 | 48 | 49 | def shortPaths(self): 50 | sp = "" 51 | for a in self.paths: 52 | if a.startswith("Users/"): 53 | a = "/" + a 54 | if a.startswith("/Users/"): 55 | a = a.split("/")[3:] 56 | b = "~/" 57 | for i in range(len(a)): 58 | if i < (len(a) - 1): 59 | a[i] += "/" 60 | b += a[i] 61 | if sp != "": 62 | sp += ", " 63 | sp += b 64 | else: 65 | if sp != "": 66 | sp += ", " 67 | sp += a 68 | return sp 69 | 70 | 71 | def quotedPaths(self, p=False, t=False): 72 | if p: 73 | pl = self.listPaths(p) 74 | else: 75 | pl = self.paths 76 | qp = "" 77 | for p in pl: 78 | if t and t == "hfs": 79 | p = ("\"%s\"" % p) 80 | else: 81 | p = ("'%s'" % p) 82 | if qp != "": 83 | qp += ", " 84 | qp += p 85 | return qp 86 | 87 | 88 | def joinNewlines(self, p=False): 89 | if p: 90 | pl = self.listPaths(p) 91 | else: 92 | pl = self.paths 93 | np = "" 94 | for p in pl: 95 | if np != "": 96 | np += "\n" 97 | np += p 98 | return np 99 | 100 | 101 | def fileUrls(self): 102 | fu = "" 103 | for f in self.urls: 104 | u = subprocess.check_output(["osascript", "posix_to_fileurl.applescript", f]).strip().decode("utf-8") 105 | if fu != "": 106 | fu += ", " 107 | fu += u 108 | return fu 109 | 110 | 111 | 112 | def __repr__(self): 113 | return self.strPaths() 114 | -------------------------------------------------------------------------------- /src/allpaths.applescript: -------------------------------------------------------------------------------- 1 | on run 2 | tell application "System Events" 3 | set theTargets to "" 4 | set theApp to the name of the first process whose frontmost is true 5 | 6 | if theApp is "Finder" then 7 | tell application "Finder" 8 | try 9 | set theSelection to (get selection as alias list) 10 | repeat with anItem in theSelection 11 | tell application "system Events" to set anItem to URL of anItem 12 | if theTargets is "" then 13 | set theTargets to anItem 14 | else 15 | set theTargets to theTargets & ", " & anItem 16 | end if 17 | end repeat 18 | on error 19 | try 20 | tell application "System Events" to set theUrl to URL of (target of window 1 as text) 21 | if theUrl is missing value then 22 | set theTargets to "" 23 | else 24 | set theTargets to theUrl 25 | end if 26 | on error 27 | set theTargets to "" 28 | end try 29 | end try 30 | end tell 31 | 32 | else 33 | tell process theApp 34 | 35 | repeat with x from 1 to (count windows) 36 | try 37 | set theDoc to the value of attribute "AXDocument" of window x 38 | if theDoc is not missing value then 39 | if the length of theTargets is 0 then 40 | set theTargets to theDoc 41 | else 42 | set theTargets to theTargets & ", " & theDoc 43 | end if 44 | end if 45 | end try 46 | end repeat 47 | 48 | end tell 49 | end if 50 | 51 | end tell 52 | return {theTargets} 53 | end run 54 | -------------------------------------------------------------------------------- /src/copypaths_main.py: -------------------------------------------------------------------------------- 1 | # Copy All Paths to Clipboard 2 | # Workflow For Alfred 2 3 | # Franz Heidl 2013 - 2014 4 | # MIT license. 5 | 6 | 7 | import sys 8 | import subprocess 9 | from CopyAllPaths import CopyAllPaths 10 | 11 | def main(q): 12 | options = [] 13 | paths = "" 14 | 15 | if q: 16 | paths = ", ".join(q[0].split("\t")) 17 | 18 | if len(q) > 1: 19 | options = q[1:] 20 | 21 | p = CopyAllPaths(paths) 22 | 23 | if options: 24 | if "-hfs" in options: 25 | paths = ((subprocess.check_output(['osascript', 'posix_to_hfs.applescript', paths])).strip()) 26 | if "-q" in options: 27 | paths = p.quotedPaths(paths, "hfs") 28 | else: 29 | if "-url" in options: 30 | paths = p.fileUrls() 31 | if "-s" in options: 32 | paths = p.shortPaths() 33 | if "-q" in options: 34 | paths = p.quotedPaths(paths) 35 | if "-p" in options: 36 | paths = p.posixPaths() 37 | 38 | if "-n" in options: 39 | paths = p.joinNewlines(paths) 40 | 41 | else: 42 | paths = p.strPaths() 43 | 44 | print paths 45 | 46 | if __name__ =="__main__": 47 | main(sys.argv[1:]) 48 | -------------------------------------------------------------------------------- /src/copypaths_scriptfilter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copy Paths to Clipboard 4 | # Workflow For Alfred 2 5 | # Franz Heidl 2013 - 2014 6 | # http://github.com/franzheidl/copy-paths-to-clipboard 7 | # MIT license. 8 | 9 | 10 | import sys 11 | import subprocess 12 | from CopyAllPaths import CopyAllPaths 13 | from AlFeedback import Feedback 14 | from urllib import unquote 15 | 16 | def main(q=False): 17 | 18 | paths = ((subprocess.check_output(['osascript', 'allpaths.applescript'])).strip()) # returns file urls string 19 | f_icon_name = "copypaths" 20 | 21 | if paths != "": 22 | p = CopyAllPaths(paths) #--> file ulrs array 23 | f_title = "Copy POSIX Paths as:" 24 | posixPaths = ((subprocess.check_output(['osascript', 'fileurl_to_posix.applescript', paths])).strip()) 25 | 26 | 27 | if q: 28 | 29 | if "-url" in q: 30 | f_title = "Copy File URLs as:" 31 | f_icon_name += "_url" 32 | 33 | elif "-hfs" in q: 34 | f_title = "Copy HFS Paths as:" 35 | f_icon_name += "_hfs" 36 | paths = ((subprocess.check_output(['osascript', 'fileurl_to_hfs.applescript', paths])).strip()) 37 | paths = unquote(paths).decode('utf-8') 38 | if "-q" in q: 39 | paths = p.quotedPaths(paths, "hfs") 40 | 41 | else: 42 | f_icon_name += "_posix" 43 | paths = unquote(posixPaths).decode('utf-8') 44 | if "-s" in q: 45 | paths = unquote(p.shortPaths()).decode('utf-8') 46 | f_icon_name += "_short" 47 | 48 | if "-q" in q: 49 | f_icon_name += "_quoted" 50 | paths = p.quotedPaths(paths) 51 | 52 | 53 | if "-n" in q: 54 | paths = p.joinNewlines(paths) 55 | 56 | else: 57 | f_icon_name += "_posix" 58 | paths = unquote(posixPaths).decode('utf-8') 59 | 60 | f_sub = paths 61 | f_valid = "yes" 62 | 63 | else: 64 | f_icon_name += "_error" 65 | f_title = "Copy Paths:" 66 | f_sub = "Couldn't get any path" 67 | f_valid = "no" 68 | f_icon = f_icon_name + ".png" 69 | 70 | feedback = Feedback() 71 | feedback.addItem(title=f_title, subtitle=f_sub, valid=f_valid, arg=paths, icon=f_icon) 72 | print feedback 73 | 74 | if __name__ =="__main__": 75 | main(sys.argv[1:]) 76 | -------------------------------------------------------------------------------- /src/fileurl_to_hfs.applescript: -------------------------------------------------------------------------------- 1 | on run(q) 2 | set theTargets to {} 3 | set oldItemDelimiters to text item delimiters 4 | set text item delimiters to ", " 5 | set qList to every text item of (q as text) 6 | set text item delimiters to oldItemDelimiters 7 | repeat with aTarget in qList 8 | set aTarget to (POSIX file aTarget) 9 | set theTargets to theTargets & (aTarget as text) 10 | end repeat 11 | return (theTargets) 12 | end run -------------------------------------------------------------------------------- /src/fileurl_to_posix.applescript: -------------------------------------------------------------------------------- 1 | -- set q to "file:///Users/franz/Library/Application Support/Alfred 2/Alfred.alfredpreferences/workflows/user.workflow.79AA6A1F-DEB4-44B0-8ADC-837BAA98B9A9/copyallpaths_main.py, file:///Users/franz/Library/Application Support/" 2 | 3 | on run(q) 4 | set theTargets to {} 5 | set oldItemDelimiters to text item delimiters 6 | set text item delimiters to ", " 7 | set qList to every text item of (q as text) 8 | set text item delimiters to oldItemDelimiters 9 | repeat with aTarget in qList 10 | if aTarget starts with "file://localhost" then 11 | set aTarget to my doSed(aTarget, "s|file://localhost\\(.*\\)|\\1|") 12 | else 13 | set aTarget to my doSed(aTarget, "s|file://\\(.*\\)|\\1|") 14 | end if 15 | set theTargets to theTargets & aTarget 16 | end repeat 17 | return (theTargets) 18 | end run 19 | 20 | on doSed(theString, thePattern) 21 | do shell script "echo " & quoted form of theString & " |" & "sed '" & thePattern & "'" 22 | end doSed 23 | -------------------------------------------------------------------------------- /src/posix_to_fileurl.applescript: -------------------------------------------------------------------------------- 1 | on run(q) 2 | set theURLs to "" 3 | set oldItemDelimiters to text item delimiters 4 | set text item delimiters to ", " 5 | set qList to every text item of (q as text) 6 | set text item delimiters to oldItemDelimiters 7 | repeat with aTarget in qList 8 | set aTarget to (POSIX file aTarget as alias) 9 | tell application "System Events" to set aTarget to URL of aTarget 10 | if theURLs is "" then 11 | set theURLs to aTarget 12 | else 13 | set theURLs to theURLs & ", " & aTarget 14 | end if 15 | end repeat 16 | return (theURLs) 17 | end run 18 | -------------------------------------------------------------------------------- /src/posix_to_hfs.applescript: -------------------------------------------------------------------------------- 1 | -- Copy All Paths to Clipboard 2 | -- Workflow For Alfred 2 3 | -- Franz Heidl 2014 4 | -- MIT license. 5 | 6 | on run(q) 7 | set theTargets to {} 8 | set oldItemDelimiters to text item delimiters 9 | set text item delimiters to ", " 10 | set qList to every text item of (q as text) 11 | set text item delimiters to oldItemDelimiters 12 | repeat with aTarget in qList 13 | set aTarget to (POSIX file aTarget as text) 14 | set theTargets to theTargets & aTarget 15 | end repeat 16 | return (theTargets) 17 | end run --------------------------------------------------------------------------------