Finder and PathFinder selection paths from JavaScript for Automation

I notice that an earlier post was much viewed:

as a JavaScript footnote, here are two JavaScript for Automation snippets (followed, for reference, by AppleScript translations) for getting the:

  • full posix path
  • path of the folder
  • file name (if the selection is a file)

from each item selected in CocoaTech Path Finder and OS X Finder. (For the latter, it will usually be better to use the a Keyboard Maestro For Each action)

(Two separate snippets, because their scripting interfaces differ)

Path Finder version in JavaScript for Automation:

// JAVASCRIPT Version for CocoaTech PATH FINDER

// for each selected item returns a triple of:
// - full path
// - folder path
// - file name (or "" if the selection is a folder)   

function run() {

    var pfSeln = Application("Path Finder").selection();

    return pfSeln ? pfSeln.map(pathFolderFile) : [];

}

function pathFolderFile(x) {
    var strPath = x.posixPath(),
        lstParts = strPath.split(/\//),
        blnFolder = x.kind() === 'Folder',
        strFile = blnFolder ? '' : lstParts.pop();

    return [strPath + (blnFolder ? '/' : ''), lstParts.join('/') + '/', strFile];
}

OS X Finder version in JavaScript for Automation

// JAVASCRIPT Version for OS X FINDER
// for each selected item returns a triple of:
// - full path
// - folder path
// - file name (or "" if the selection is a folder)   

function run() {

    var lstSeln = Application("Finder").selection();

    return lstSeln.length ? lstSeln.map(pathFolderFile) : [];
}

function pathFolderFile(x) {
    var strPath = decodeURI(x.url()).slice(7),
        lstParts = strPath.split(/\//),
        blnFolder = strPath[-1] === '/',
        strFile = blnFolder ? '' : lstParts.pop();

    return [strPath, lstParts.join('/') + '/', strFile];
}

APPLESCRIPT TRANSLATIONS (for reference - these are fairly direct translations, and could be briefer)

Path Finder:

-- APPLESCRIPT Version for CocaTech PATH FINDER

-- (translation of the JavaScript version)

-- for each selected item returns a triple of:
-- * full path
-- * folder path
-- * file name (or "" if the selection is a folder)

on run {}
    tell application ("Path Finder") to set pfSeln to selection
    
    ternary(pfSeln is not missing value, map(pfSeln, posixPath), {})
end run

-- fsItem -> String
on posixPath(x)
    using terms from application "Path Finder"
        set strPath to POSIX path of x
        set blnFolder to kind of x = "Folder"
    end using terms from
    
    set lstParts to splitOn("/", strPath)
    set strFile to ternary(blnFolder, "", item -1 of lstParts)
    set strFolder to ternary(blnFolder, strPath, intercalate("/", items 1 thru -2 of lstParts))
    
    return {strPath & ternary(blnFolder, "/", ""), strFolder & "/", strFile}
end posixPath


-- General library functions:

-- Bool -> a
on ternary(blnTest, x, y)
    if blnTest then
        x
    else
        y
    end if
end ternary

-- [a] -> (a -> b) -> [b]
on map(xs, f)
    script mf
        property call : f
    end script
    
    set lst to {}
    set lng to length of xs
    repeat with i from 1 to lng
        set end of lst to mf's call(item i of xs, i, xs)
    end repeat
    return lst
end map

-- Text -> Text -> [Text]
on splitOn(strDelim, strMain)
    set {dlm, my text item delimiters} to {my text item delimiters, strDelim}
    set lstParts to text items of strMain
    set my text item delimiters to dlm
    return lstParts
end splitOn

-- Text -> [Text] -> Text
on intercalate(strText, lstText)
    set {dlm, my text item delimiters} to {my text item delimiters, strText}
    set strJoined to lstText as text
    set my text item delimiters to dlm
    return strJoined
end intercalate

OS X Finder:

-- APPLESCRIPT Version for OS X FINDER

-- (translation of the JavaScript version)

-- for each selected item returns a triple of:
-- * full path
-- * folder path
-- * file name (or "" if the selection is a folder)

on run {}
    tell application "Finder" to set lstSeln to selection
    
    ternary(length of lstSeln > 0, pathsFoldersFiles(map(lstSeln, encodedPosixPath)), {})
end run

-- fsItem -> String
on encodedPosixPath(x)
    using terms from application "Finder"
        URL of x
    end using terms from
end encodedPosixPath

-- String -> [String]
on splitOnCommas(x)
    splitOn(",", x)
end splitOnCommas

-- [String -> String]
on pathsFoldersFiles(lstCoded)
    return map(splitOn(return, do shell script "
osascript -l JavaScript <<JXA_END 2>/dev/null
    ['" & intercalate("', '", lstCoded) & "'].map(function (x) {
        var strPath = decodeURI(x).slice(7),
            lstParts = strPath.split(/\\//),
            blnFolder = strPath[-1] === '/',
            strFile = blnFolder ? '' : lstParts.pop();
        return [strPath, lstParts.join('/') + '/', strFile]; 
    }).join('\\n');
JXA_END
"), splitOnCommas)
end pathsFoldersFiles

-- general library functions

-- Bool -> a
on ternary(blnTest, x, y)
    if blnTest then
        x
    else
        y
    end if
end ternary

-- [a] -> (a -> b) -> [b]
on map(xs, f)
    script mf
        property call : f
    end script
    
    set lst to {}
    set lng to length of xs
    repeat with i from 1 to lng
        set end of lst to mf's call(item i of xs, i, xs)
    end repeat
    return lst
end map

-- Text -> Text -> [Text]
on splitOn(strDelim, strMain)
    set {dlm, my text item delimiters} to {my text item delimiters, strDelim}
    set lstParts to text items of strMain
    set my text item delimiters to dlm
    return lstParts
end splitOn

-- Text -> [Text] -> Text
on intercalate(strText, lstText)
    set {dlm, my text item delimiters} to {my text item delimiters, strText}
    set strJoined to lstText as text
    set my text item delimiters to dlm
    return strJoined
end intercalate

3 Likes

Thanks for sharing, Rob.
Clipped to Quiver for future reference.

Hi there, I am using PathFinder instead Finder everyday, I really wanna the selected file action works on PathFinder.

Due to my very little to no coding knowledge, I just pasted the above script, it didn't work out. :stuck_out_tongue:

Anyone can help a bit?Thanks in advance.
macOSX 10.13.4 / PF 7.6.2 / KM 8.2

Hey @shruru,

Right. If you read Rob’s script is says

That means you get back a mess of data that isn’t suitable for use in a move-to action without some processing.

See this thread. Automating Path Finder for a turnkey means of working with Path Finder items.

-Chris

@shruru, actually the return is not a "mess", but a well-organized set of data about the selection. By changing just two lines of the JXA script, it now returns ONLY a text list of the full path of each item selected in PathFinder.

However, you also need to change your "For Each" Action to use "Lines in Variable Collection".

image

Revised JXA Script

var ptyScriptName   = "Get Items in PathFinder Selection"
var ptyScriptVer     = "2.0"  // Simplify to return only item paths
var ptyScriptDate   = "2018-04-25"
var ptyScriptAuthor = "ComplexPoint"  //JMTX mods

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PURPOSE:
  • Get Items in PathFinder Selection
  
RETURNS:  Text List of POSIX Path of Items in PathFinder Selection

REQUIRED:
  1.  macOS 10.11.6+
  2.  Mac Applications
      • Keyboard Maestro 8.2+
      • Path Finder 7.6.2+
      
TAGS:  

REF:  The following were used in some way in the writing of this script.

  1.  2016-02-03, ComplexPoint, Keyboard Maestro Discourse
      Finder and PathFinder selection paths from JavaScript for Automation
      https://forum.keyboardmaestro.com/t/finder-and-pathfinder-selection-paths-from-javascript-for-automation/2871?u=jmichaeltx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
// JAVASCRIPT Version for CocoaTech PATH FINDER

// for each selected item returns a triple of:
// - full path  -- //JMTX simplified to return only this

//--- NOT RETURNED ---
// - folder path
// - file name (or "" if the selection is a folder)   

function run() {

    var pfSeln = Application("Path Finder").selection();

    //return pfSeln ? pfSeln.map(pathFolderFile) : [];
    var selectionList = pfSeln ? pfSeln.map(pathFolderFile) : [];  //JMTX Add
    return selectionList.join("\n");  //JMTX ADD

}

function pathFolderFile(x) {
    var strPath = x.posixPath(),
        lstParts = strPath.split(/\//),
        blnFolder = x.kind() === 'Folder',
        strFile = blnFolder ? '' : lstParts.pop();

    //return [strPath + (blnFolder ? '/' : ''), lstParts.join('/') + '/', strFile];
    return strPath;  //JMTX ADD
}

Example Output

~/Documents/TEST/Test_File_Tags.txt
~/Documents/TEST/test_send_file.txt

Here's the full macro, which you may need to adjust for your needs:

updated 2018-05-05 13:10 GMT-0500

  • Fixed bug in last Action to use variable token

MACRO:   Move List of Items Selected in PathFinder

~~~ VER: 1.1    2018-05-05 ~~~

DOWNLOAD:

Get Text List of Items Selected in PathFinder.kmmacros (6.4 KB)
Note: This Macro was uploaded in a DISABLED state. You must enable before it can be triggered.


image


Questions?

Depends upon how you define mess.  :stuck_out_tongue_winking_eye:

An unorganized, random set of data.
Chris, you know full well that a JXA array is the same as an AppleScript List, both of which you sometimes want return the entire list, and sometimes you just want one part. :wink:

If it were a “mess” it would have taken much more than two simple changes to return the data of interest.

Hey Jim,

Mess — “mess of data” as in “mess of grits…” – e.g. a lot.

-Chris

Thanks for the input. However It still doesn't work out. What I did, select a file in Path Finder, then Trigger the Macro, the error info pop-up and also got a pup-up win. including the file path.

This suggest that the path ~/DATA does not exist. Can you confirm?
Try selecting the folder using the KM Action Folder button.

thanks , its correct.

@ccstone 's macro works perfectly with PF.

Sorry. My bad. I forgot to use a token for the variable in the last Action.
It should be:

image

I tested it this time, and it works on my system.

Just updated my post above.