JSON from plist path SUBROUTINE

A subroutine – example of use below – for obtaining a JSON representation of a given .plist file (supplied as a file path).

(The .plist may be a serialization of an Array or a Dict)

JSON from plist path SUBROUTINE.kmmacros (5.0 KB)

Expand disclosure triangle to view JS source
// MAIN :: IO ()
const main = () =>
    either(
        alert("JSON from Plist Path")
    )(
        jsObject => JSON.stringify(jsObject, null, 2)
    )(
        jsoFromPlistPathLR(kmvar.local_Plist_Path)
    );


// ----------------------- JXA -----------------------

// alert :: String => String -> IO String
const alert = title =>
    s => {
        const sa = Object.assign(
            Application("System Events"), {
            includeStandardAdditions: true
        });

        return (
            sa.activate(),
            sa.displayDialog(s, {
                withTitle: title,
                buttons: ["OK"],
                defaultButton: "OK"
            }),
            s
        );
    };

// jsoFromPlistPathLR :: FilePath -> Either String JSObject 
const jsoFromPlistPathLR = fp =>
    either(
        () => either(
            () => Left(
                `Not legible as plist Dict or Array:\n\n\t${fp}`
            )
        )(Right)(
            arrayFromPlistPathLR(fp)
        )
    )(Right)(
        dictFromPlistPathLR(fp)
    );


// dictFromPlistPathLR :: FilePath -> Either String Dict
const dictFromPlistPathLR = fp => {
    const
        maybeDict = $.NSDictionary
            .dictionaryWithContentsOfFile(
                $(fp).stringByStandardizingPath
            );

    return maybeDict.isNil()
        ? Left(`Not legible as plist Dict:\n\t${fp}`)
        : Right(ObjC.deepUnwrap(maybeDict));
};


// arrayFromPlistPathLR :: FilePath -> Either String Array
const arrayFromPlistPathLR = fp => {
    const
        maybeArray = $.NSMutableArray
            .arrayWithContentsOfFile(
                $(fp).stringByStandardizingPath
            );

    return maybeArray.isNil()
        ? Left(`Not legible as plist Array:\n\t${fp}`)
        : Right(ObjC.deepUnwrap(maybeArray));
};



// --------------------- GENERIC ---------------------

// Left :: a -> Either a b
const Left = x => ({
    type: "Either",
    Left: x
});


// Right :: b -> Either a b
const Right = x => ({
    type: "Either",
    Right: x
});


// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
    // Application of the function fl to the
    // contents of any Left value in e, or
    // the application of fr to its Right value.
    fr => e => "Left" in e
        ? fl(e.Left)
        : fr(e.Right);


// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = lr =>
    // Bind operator for the Either option type.
    // If lr has a Left value then lr unchanged,
    // otherwise the function mf applied to the
    // Right value in lr.
    mf => "Left" in lr
        ? lr
        : mf(lr.Right);

// ---
return main();

Example

JSON from plist path TEST.kmmacros (3.0 KB)

2 Likes

Further example – obtaining the name of the first application in a Moom snapshot specified by title:

(as in Restore a Moom layout while hiding apps without windows in that layout )

JSON from plist path (TEST 2 -- First App in named Moom snapshot).kmmacros (7.9 KB)


Expand disclosure triangle to view JS source
// MAIN :: IO ()
const main = () =>
    either(
        alert("First app in snapshot")
    )(
        appName => appName
    )(
        bindLR(
            jsonParseLR(kmvar.local_JSON)
        )(
            appNameForMoomshotTitleLR(
                kmvar.local_Snapshot_Name
            )
        )
    );


// ------------------- MOOM PLIST DATA -------------------

// appNameForMoomshotTitleLR :: String -> Dict -> Either String String
const appNameForMoomshotTitleLR = title =>
    dict => {
        const
            snapshots = Object.keys(dict).flatMap(
                k => k.startsWith("Custom Controls")
                    ? dict[k].filter(
                        subDict => title === subDict.Title
                    )
                    : []
            );

        return bindLR(
            0 < snapshots.length
                ? Right(snapshots[0])
                : Left(`Snapshot not found: "${title}"`)
        )(
            firstAppNamedInShotLR
        );
    };


// firstAppNamedInShotLR :: Dict -> Either String String
function firstAppNamedInShotLR(snapDict) {
    const
        snapShot = snapDict.Snapshot, i = snapShot.findIndex(
            shot => shot["Application Name"]
        );

    return -1 !== i
        ? Right(snapShot[i]["Application Name"])
        : Left(`No application named in snapshot: "${title}"`);
}


// ------------------------- JXA -------------------------

// alert :: String => String -> IO String
const alert = title =>
    s => {
        const sa = Object.assign(
            Application("System Events"), {
            includeStandardAdditions: true
        });

        return (
            sa.activate(),
            sa.displayDialog(s, {
                withTitle: title,
                buttons: ["OK"],
                defaultButton: "OK"
            }),
            s
        );
    };



// ----------------------- GENERIC -----------------------

// Left :: a -> Either a b
const Left = x => ({
    type: "Either",
    Left: x
});


// Right :: b -> Either a b
const Right = x => ({
    type: "Either",
    Right: x
});


// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = lr =>
    // Bind operator for the Either option type.
    // If lr has a Left value then lr unchanged,
    // otherwise the function mf applied to the
    // Right value in lr.
    mf => "Left" in lr
        ? lr
        : mf(lr.Right);


// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
    // Application of the function fl to the
    // contents of any Left value in e, or
    // the application of fr to its Right value.
    fr => e => "Left" in e
        ? fl(e.Left)
        : fr(e.Right);


// jsonParseLR :: String -> Either String a
const jsonParseLR = s => {
    try {
        return Right(JSON.parse(s));
    } catch (e) {
        return Left(
            [
                e.message,
                `(line:${e.line} col:${e.column})`
            ].join("\n")
        );
    }
};

return main()
2 Likes