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

As in (ignoring other associations) third column (and thus up to Nth column) ?

This one uses a fixed-width for field 1 of the table including the . leaders.

Field 2 is flexible but must begin with ‘Working’ or ‘Meeting’.

-Chris


Make Two Column Table with . Leader (Perl) v1.00.kmmacros (7.3 KB)

2 Likes

If you can accept having SPACE filled text, then the simple bash column command will do.
But if you want exactly what you have posted, then you will need a couple of RegEx Search/Replace Actions to replaces the SPACES with dots. Maybe one of the Shell Script gurus will know how to make the bash column command use a different fill character.

Example Output

image

Below is just an example written in response to your request. You will need to use as an example and/or change to meet your workflow automation needs.

Please let us know if it meets your needs.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

MACRO:   Create Text Table from TAB Delimited Text [Example]

-~~~ VER: 1.0    2021-02-10 ~~~
Requires: KM 8.2.4+   macOS 10.11 (El Capitan)+
(Macro was written & tested using KM 9.0+ on macOS 10.14.5 (Mojave))

DOWNLOAD Macro File:

Create Text Table from TAB Delimited Text [Example].kmmacros
Note: This Macro was uploaded in a DISABLED state. You must enable before it can be triggered.


ReleaseNotes

Author.@JMichaelTX -- based on script by @ccstone

PURPOSE:

  • Create Text Table from TAB Delimited Text [Example]

HOW TO USE

  1. First, make sure you have followed instructions in the Macro Setup below.
  2. Change Source String to your string.
    • Do NOT use any TAB characters
    • You may have one or more spaces between the last am/pm and the description (Working/Meeting).
  3. Trigger this macro.

MACRO SETUP

  • Carefully review the Release Notes and the Macro Actions
    • Make sure you understand what the Macro will do.
    • You are responsible for running the Macro, not me. ??
      .
      .
      Make These Changes to this Macro
  1. Assign a Trigger to this macro.
  2. Move this macro to a Macro Group that is only Active when you need this Macro.
  3. ENABLE this Macro, and the Macro Group it is in.
    .
  • REVIEW/CHANGE THE FOLLOWING MACRO ACTIONS:
    (all shown in the magenta color)
    • SET Source String
      • Do NOT use any TAB characters
      • You may have one or more spaces between the last am/pm and the description (Working/Meeting).

REQUIRES:

  1. KM 9.0+ (may work in KM 8.2+ in some cases)
  2. macOS 10.11.6 (El Capitan)+

TAGS: @TextTable @Regex @ShellScript @Example

USER SETTINGS:

  • Any Action in magenta color is designed to be changed by end-user

ACTION COLOR CODES

  • To facilitate the reading, customizing, and maintenance of this macro,
    key Actions are colored as follows:
  • GREEN -- Key Comments designed to highlight main sections of macro
  • MAGENTA -- Actions designed to be customized by user
  • YELLOW -- Primary Actions (usually the main purpose of the macro)
  • ORANGE -- Actions that permanently destroy Variables or Clipboards,
    OR IF/THEN and PAUSE Actions

==USE AT YOUR OWN RISK==

  • While I have given this a modest amount of testing, and to the best of my knowledge will do no harm, I cannot guarantee it.
  • If you have any doubts or questions:
    • Ask first
    • Turn on the KM Debugger from the KM Status Menu, and step through the macro, making sure you understand what it is doing with each Action.

1 Like

Depends.

Do you still want the Working and Meeting to line up and let the extra stuff after that trail off to various lengths? If so, just add (.*) to the end of the regular expression and add the 3rd captured group to the end of the output, i.e.,

#!/usr/bin/python3

import re
import fileinput
   
entry = re.compile(r'^(.+)(Meeting|Working)(.*)$')

for line in fileinput.input():
    m = entry.match(line)
    if m:
        print(f'{m.group(1):.<21s} {m.group(2)}{m.group(3)}')

That turns

8 - 10am Working, saw video of people with Cat filter on on video
7 - 9am Working
9 - 11am Meeting with people I hate
11 - 11:30am Meeting
11:30am - 12:30pm Meeting
12:30pm - 1pm Working
2 - 5pm Working

into

8 - 10am ............ Working, saw video of people with Cat filter on on video
7 - 9am ............. Working
9 - 11am ............ Meeting with people I hate
11 - 11:30am ........ Meeting
11:30am - 12:30pm ... Meeting
12:30pm - 1pm ....... Working
2 - 5pm ............. Working

If that's not the kind of output you want, the answer will probably be more complicated.

(If you put .* after Working and Meeting in the second capture group, there's no need for a third group, and the print line could go back to what it was. I added the third group because I thought it made the code clearer. Now I'm not sure there's any value in it.)

2 Likes

wow
Insert sunglasses reaction meme
thanks everyone!

It was a good question and harvested well, not only because it clearly showed:

  • a sample of input,
  • and a sample of target output

but also, I think, because it touches on some very general and recognisable problems that everyone bumps into:

  • splitting each of several rows into column cells
  • specifying alignment or centering for each column
  • getting the minimum width of each column from column contents
  • choosing a (possibly non-space) padding character for the justification or centering
  • specifying any additional whitespace padding

I wonder if it would be useful to have a (custom or buillt-in) KM action for tabulation of delimited rows ?

1 Like

"Emplace". :slight_smile:

2 Likes

Okay, I'll try again...  :sunglasses:

-Chris

--------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2021/02/12 06:10
# dMod: 2021/02/12 06:37
# Appl: AppleScript
# Task: Create a Fixed-Width Table with Data 03.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @Create, @Fixed_Width @Table
--------------------------------------------------------
set theData to paragraphs 2 thru -2 of "
7 - 9am Working
9 - 11am Meeting
11 - 11:30am Meeting
11:30am - 12:30pm Meeting
12:30pm - 1pm Working
2 - 5pm Working
"
property tableWidth : 29
property dotPad : "........................................"
set AppleScript's text item delimiters to {" "}
repeat with i in theData
   tell i to set contents of i to (its text items 1 thru -2 as text) & space & (text 1 thru (tableWidth - 2 - ((length of (text items 1 thru -2 of i as text)) + (length of (last text item of i)))) of dotPad) & space & (its last text item)
end repeat
set AppleScript's text item delimiters to linefeed
return theData as text
--------------------------------------------------------

If I set the shell script action to display text in a window, everything looks great:

However, if I save the script result to a variable and then use the Display Text in a Window action, I get:

If I paste the misaligned output here, it looks fine:

Local__Test:                    [test1]
--------------------------------------------------------------------
Local__LongTestVariableName:    [test2]
--------------------------------------------------------------------

So what gives?

Change the font of the display text action to a monospaced font.

I typically use Menlo 14.

1 Like

Nice! Thanks Chris! :pray:t3:

1 Like

Here's an evolution of the same concept:

Align Text as Columns.kmmacros (23 KB)

Macro screenshot


Example input:

Instrument: Name: Wife: Years Married

Rhythm Guitar: John: Yoko: 69-80
Bass: Paul: Linda: 67-98
Lead Guitar: George: Pattie: 66-77
Drums: Ringo: Maureen: 65-75

Output:

Instrument       Name      Wife       Years Married   

Rhythm Guitar    John      Yoko       69-80           
Bass             Paul      Linda      67-98           
Lead Guitar      George    Pattie     66-77           
Drums            Ringo     Maureen    65-75

Options:

  • Additional Separation Spaces

Set to 0, a single space will separate columns. Any positive value will add addional spaces.

  • Delimiter

Set the delimiter that will differentiate column bounds.

3 Likes

Thanks @noisneil for creating this macro. I don't need it right now, but I know I will need it one day. :slight_smile:

ChrisQ

1 Like

@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