Using RegEx to sort text in a variable?

Or again, extending it to Tom's data sample, and proceeding for the sake of variety by splits (my patience for regex-fiddling shortens with advancing years :slight_smile: )

Sorted filenames by splits.kmmacros (23.2 KB)

JavaScript ES6 source (Paste into Babel JS REPL at https://babeljs.io/repl/ to get pre-Sierra ES5 JavaScript source)

(() => {
    'use strict';

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

        // (++) :: [a] -> [a] -> [a]
        const append = (xs, ys) => xs.concat(ys);

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

        // elem :: Eq a => a -> [a] -> Bool
        const elem = (x, xs) => xs.indexOf(x) !== -1;

        // id :: a -> a
        const id = x => x;

        // init :: [a] -> [a]
        const init = xs => xs.length > 0 ? xs.slice(0, -1) : [];

        // intercalate :: String -> [a] -> String
        const intercalate = (s, xs) => xs.join(s);

        // isDigit :: Char -> Bool
        const isDigit = c => {
            const n = ord(c);
            return n >= 48 && n <= 57;
        };

        // last :: [a] -> a
        const last = xs => xs.length ? xs.slice(-1)[0] : undefined;

        // length :: [a] -> Int
        const length = xs => xs.length;

        // lines :: String -> [String]
        const lines = s => s.split(/[\r\n]/);

        // map :: (a -> b) -> [a] -> [b]
        const map = (f, xs) => xs.map(f);

        // mappendComparing :: [(a -> b)] -> (a -> a -> Ordering)
        const mappendComparing = fs => (x, y) =>
            fs.reduce((ord, f) => (ord !== 0) ? (
                ord
            ) : (() => {
                const
                    a = f(x),
                    b = f(y);
                return a < b ? -1 : a > b ? 1 : 0
            })(), 0);

        // ord :: Char -> Int
        const ord = c => c.codePointAt(0);

        // show :: Int -> a -> Indented String
        // show :: a -> String
        const show = (...x) =>
            JSON.stringify.apply(
                null, x.length > 1 ? [x[1], null, x[0]] : x
            );

        // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
        const sortBy = (f, xs) =>
            xs.slice()
            .sort(f);

        // Splitting not on a delimiter, but whenever the relationship between
        // two consecutive items matches a supplied predicate function

        // splitBy :: (a -> a -> Bool) -> [a] -> [[a]]
        const splitBy = (f, ys) => {
            const
                bool = typeof ys === 'string',
                xs = bool ? ys.split('') : ys;
            return (xs.length < 2) ? [xs] : (() => {
                const
                    h = xs[0],
                    lstParts = xs.slice(1)
                    .reduce(([acc, active, prev], x) =>
                        f(prev, x) ? (
                            [acc.concat([active]), [x], x]
                        ) : [acc, active.concat(x), x], [
                            [],
                            [h],
                            h
                        ]);
                return map(
                    (bool ? concat : id),
                    lstParts[0].concat([lstParts[1]])
                );
            })();
        };

        // splitOn :: a -> [a] -> [[a]]
        // splitOn :: String -> String -> [String]
        const splitOn = (needle, haystack) =>
            typeof haystack === 'string' ? (
                haystack.split(needle)
            ) : (function sp_(ndl, hay) {
                const mbi = findIndex(x => ndl === x, hay);
                return mbi.nothing ? (
                    [hay]
                ) : append(
                    [take(mbi.just, hay)],
                    sp_(ndl, drop(mbi.just + 1, hay))
                );
            })(needle, haystack);

        // toLower :: Text -> Text
        const toLower = s => s.toLowerCase();

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


        // COMPARABLE FILE NAME PARTS --------------------------------------------

        // suffixDotSplit :: String -> (String, String)
        const suffixSplit = s =>
            elem('.', s) ? (() => {
                const xs = splitOn('.', s);
                return [intercalate('.', init(xs)), last(xs)];
            })() : [s, ''];

        // stemNumSplit :: String -> (String, String)
        const stemNumSplit = s => {
            const tpl = splitBy(
                (a, b) => isDigit(b) && !isDigit(a),
                suffixSplit(s)[0]
            );
            return length(tpl) > 1 ? tpl : append(tpl, ["0"]);
        };

        const stemPreNum = s => toLower(stemNumSplit(s)[0]);
        const stemNum = s => parseInt(stemNumSplit(s)[1], 10) || 0;
        const suffix = s => suffixSplit(s)[1];

        // TEST ------------------------------------------------------------------
        return unlines(
            sortBy(
                mappendComparing(
                    [stemPreNum, stemNum, suffix]
                ),
                lines(
                    Application('Keyboard Maestro Engine')
                    .getvariable('fileNames')
                )
            )
        );
    })();
2 Likes