Choose an ObjC class with predictive typing, and copy its inheritance path

Provides inheritance paths like:

NSObject -> NSAttributedString -> NSMutableAttributedString -> NSTextStorage -> NSSubTextStorage
NSObject -> NSResponder -> NSView -> _NSKeyLoopSplicingContainerView -> NSTabBarViewButton -> NSTabButton
NSObject -> NSBinder -> NSEditorBinder -> NSObjectDetailBinder -> NSArrayDetailBinder -> NSTreeDetailBinder

etc.

for a chosen class name.

To extend or restrict the list of ObjC classes listed, adjust the JavaScript source below (for the first of the two Execute a JXA actions in the macro - the second action derives the inheritance path of the chosen class)

Choose an ObjC class with predictive typing, and copy its inheritance path.kmmacros (29.0 KB)

58

JavaScript for Automation source:

(1 of 2) List of (NS, CF and CG) ObjC classes

(() => {
    'use strict';

    ['AppKit', 'Quartz', 'Carbon']
    .forEach(k => ObjC.import(k));

    // prefixes :: [String]
    const prefixes = ['NS','CF', 'CG'];

    // main :: () -> String
    const main = () => {
        const xs = objcClassNames();
        return unlines(concat(
            groupBy(
                (a, b) => a.slice(0, 2) === b.slice(0, 2),
                xs.sort()
            )
            .filter(x => prefixes.includes(x[0].slice(0, 2)))
        ));
    };

    // OBJC CLASS NAMES -------------------------------------------------------

    // objcClassNames :: () -> [String]
    const objcClassNames = () => {
        // See: https://stackoverflow.com/questions/49263671

        ObjC.bindFunction('objc_getClassList', ['int', ['void**', 'int']]);
        ObjC.bindFunction('class_getName', ['char *', ['void*']]);
        ObjC.bindFunction('malloc', ['void**', ['int']]);

        const
            intClasses = $.objc_getClassList(undefined, 0),
            classes = $.malloc(8 * intClasses);

        return (
            $.objc_getClassList(classes, intClasses),
            Array.from({
                length: intClasses
            }, (_, i) => $.class_getName(classes[i]))
        );
    };

    // GENERICS --------------------------------------------------------------

    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs =>
        xs.length > 0 ? (() => {
            const unit = typeof xs[0] === 'string' ? '' : [];
            return unit.concat.apply(unit, xs);
        })() : [];

    // group :: Eq a => [a] -> [[a]]
    const group = xs => groupBy((a, b) => a === b, xs);

    // Typical usage: groupBy(on(eq, f), xs)
    // groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
    const groupBy = (f, xs) => {
        const dct = xs.slice(1)
            .reduce((a, x) => {
                const h = a.active.length > 0 ? a.active[0] : undefined;
                return h !== undefined && f(h, x) ? {
                    active: a.active.concat([x]),
                    sofar: a.sofar
                } : {
                    active: [x],
                    sofar: a.sofar.concat([a.active])
                };
            }, {
                active: xs.length > 0 ? [xs[0]] : [],
                sofar: []
            });
        return dct.sofar.concat(dct.active.length > 0 ? [dct.active] : []);
    };

    // showJSON :: a -> String
    const showJSON = x => JSON.stringify(x, null, 2);

    // take :: Int -> [a] -> [a]
    const take = (n, xs) => xs.slice(0, n);

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');


    // MAIN ------------------------------------------------------------------
    return main();
})();

(2 of 2) Inheritance path of chosen class

(() => {
    'use strict';

    // main :: () -> String
    const main = () =>
        intercalate(' -> ', classPath(
            Application('Keyboard Maestro Engine')
            .getvariable('classChosen')
        ));

    // OBJC CLASSES ---------------------------------------------------------

    // className :: NSObject -> String
    const className = anyClass =>
        ObjC.unwrap($.NSStringFromClass(anyClass));

    // classPath :: String -> [String]
    const classPath = strClassName =>
        strClassName.includes('NSLeafProxy') ? (
            ['.superClass not defined for NSLeafProxy']
        ) : (() => {
            try {
                const childClass = $.NSClassFromString(strClassName);
                return ObjC.unwrap(childClass) === undefined ? (
                    []
                ) : ['NSObject'].concat(
                    // UNFOLDR builds a list from a seed value.
                    unfoldr(parentClass, childClass)
                );
            } catch (e) {
                return ['.superClass not defined for ' + strClassName];
            }
        })();

    // parentClass :: NSObject  -> Maybe (String, NSObject)
    const parentClass = anyClass => {
        const strName = className(anyClass);
        return 'NSObject' === strName ? (
            Nothing()
        ) : Just(Tuple(strName, anyClass.superclass));
    };

    // GENERIC FUNCTIONS -----------------------------------------------------

    // Just :: a -> Just a
    const Just = x => ({
        type: 'Maybe',
        Nothing: false,
        Just: x
    });

    // Nothing :: () -> Nothing
    const Nothing = () => ({
        type: 'Maybe',
        Nothing: true,
    });

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = (a, b) => ({
        type: 'Tuple',
        '0': a,
        '1': b
    });

    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs =>
        xs.length > 0 ? (() => {
            const unit = typeof xs[0] === 'string' ? '' : [];
            return unit.concat.apply(unit, xs);
        })() : [];

    // intercalate :: [a] -> [[a]] -> [a]
    // intercalate :: String -> [String] -> String
    const intercalate = (sep, xs) =>
        xs.length > 0 && typeof sep === 'string' &&
        typeof xs[0] === 'string' ? (
            xs.join(sep)
        ) : concat(intersperse(sep, xs));

    // intersperse(0, [1,2,3]) -> [1, 0, 2, 0, 3]
    // intersperse :: Char -> String -> String
    // intersperse :: a -> [a] -> [a]
    const intersperse = (sep, xs) => {
        const bool = (typeof xs)[0] === 's';
        return xs.length > 1 ? (
            (bool ? concat : x => x)(
                (bool ? (
                    xs.split('')
                ) : xs)
                .slice(1)
                .reduce((a, x) => a.concat([sep, x]), [xs[0]])
            )) : xs;
    };

    // The 'unfoldr' function is a \`dual\' to 'foldr': while 'foldr'
    // reduces a list to a summary value, 'unfoldr' builds a list from
    // a seed value.  The function takes the element and returns 'Nothing'
    // if it is done producing the list or returns 'Just' @(a,b)@, in which
    // case, @a@ is a prepended to the list and @b@ is used as the next
    // element in a recursive call.
    //
    // unfoldr(b => b === 0 ? Nothing() : Just(Tuple(b, b - 1)), 10);
    // --> [10,9,8,7,6,5,4,3,2,1]

    // (x => Maybe [value, remainder] -> initial value -> values
    // unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
    const unfoldr = (f, v) => {
        let xs = [];
        return (
            until(
                mb => mb.Nothing,
                mb => (
                    xs.push(mb.Just[0]),
                    f(mb.Just[1])
                ), Just(Tuple(v, v))
            ),
            xs.slice(1)
        );
    };

    // until :: (a -> Bool) -> (a -> a) -> a -> a
    const until = (p, f, x) => {
        let v = x;
        while (!p(v)) v = f(v);
        return v;
    };

    // MAIN --------------------------------------------------------------
    return main();
})();
3 Likes

Thanks for sharing. This looks interesting.
But I'm lost on how I might use this tool. Could you please give us a real world example of how to use?

Thanks.