How to Format a Variable Text, to Produce Uniform Column Table?

@noisneil, thanks for sharing this very useful macro! (Nice example data too. :grinning:)

1 Like

No worries. Here's another approach using tabs instead of spaces.

Align Text as Tabbed Columns.kmmacros (22 KB)

Macro screenshot

One thing I've noticed with the tab version is that if you paste it into TextEdit, for example, and then select a monospaced font, it's perfect.

However, if you write it to a file as styled text with the same font, the tabs occasionally come out a bit squiffy.

Any ideas why that might be?

A lot of shell utilities are going to expect monospaced fonts.

I'm using Menlo, which is monospaced.

Here's a perl version, which @griffman pointed out is sliiiightly snappier:

No idea how this Keyboard Maestro Execute JavaScript for Automation action compares in terms of speed.

Monospaced columns (as defined by delimiter) separated by given gap.kmmacros (5.3 KB)

Expand disclosure triangle to view JS source
(() => {
    "use strict";

    // Columns of Text (as defined by given Delimiter),
    // padded to even width, and separated by given Gap.

    // Rob Trew @2023
    // Ver 0.01

    const main = () => {
        const
            kmVar = kmValue(kmInstance()),
            [txt, delim, gap] = ["Text", "Delimiter", "Gap"]
            .map(k => kmVar(`local_${k}`)),

            rows = lines(txt).map(
                row => row.split(delim).map(k => k.trim())
            ),
            n = Math.max(...rows.map(row => row.length)),
            fullRows = rows.map(row => {
                const m = row.length;

                return n === m
                    ? row
                    : row.concat(
                        Array.from(
                            {length: n - m},
                            () => ""
                        )
                    );
            });

        return transpose(
            transpose(fullRows).map(column => {
                const
                    w = Math.max(
                        ...column.map(cell => cell.length)
                    );

                return column.map(
                    cell => cell.padEnd(w, " ")
                );
            })
        )
        .map(row => row.join(gap))
        .join("\n");
    };


    // ---------------- KEYBOARD MAESTRO -----------------

    // kmValue :: KM Instance -> String -> IO String
    const kmValue = instance =>
        k => Application("Keyboard Maestro Engine")
        .getvariable(k, {instance});


    // kmInstance :: () -> IO String
    const kmInstance = () =>
        ObjC.unwrap(
            $.NSProcessInfo.processInfo.environment
            .objectForKey("KMINSTANCE")
        ) || "";


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

    // lines :: String -> [String]
    const lines = s =>
        // A list of strings derived from a single string
        // which is delimited by \n or by \r\n or \r.
        0 < s.length
            ? s.split(/\r\n|\n|\r/u)
            : [];


    // transpose :: [[a]] -> [[a]]
    const transpose = rows =>
        // The columns of the input transposed
        // into new rows.
        // Simpler version of transpose, assuming input
        // rows of even length.
        0 < rows.length
            ? rows[0].map(
                (_, i) => rows.flatMap(v => v[i])
            )
            : [];

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

This version sets each column to an explicit character position. Useful if you want multiple text blocks to be formatted separately yet identically.

Align Text as Spaced Columns (Explicit Positions).kmmacros (22 KB)

Macro screenshot

1 Like