Filter Artist Names from a List of Text Records

How would I write a macro that takes a list like this from a variable...

Europe - Rock The Night 
Three Days Grace - I Am The Weapon 
Sixx-A.M. - Life Is Beautiful 
Warrant - Heaven 
Syteria - Pause For Peace (Silent Minute) 
Girlschool - Don't Mess Around 
Velvet Revolver - Fall to Pieces 
Dokken - Sleepless Nights 
Poison - Talk Dirty to Me 
Last In Line - Ghost Town 
Bon Jovi - Livin' on a Prayer 
Black Stone Cherry - Out Of Pocket 
BulletBoys - Smooth Up In Ya 
Mötley Crüe - Piece of Your Action 
AC/DC - You Shook Me All Night Long 

...and returns only the artist names (before the " - ") like this...

Europe
Three Days Grace
Sixx-A.M.
Warrant
Syteria
Girlschool
Velvet Revolver
Dokken
Poison
Last In Line
Bon Jovi
Black Stone Cherry
BulletBoys
Mötley Crüe
AC/DC

?

1 Like

I would do it with native Keyboard Maestro actions and split each line like this (using " - " as the delimiter to get the first part of each line).

NB
If the delimiter was a comma
Europe,Rock The Night
then Keyboard Maestro could split the line at each comma and get the first part by:
%Variable%LOCAL__Line[1]%

As the delimiter is " - "
Europe - Rock The Night
It is a custom delimiter and the syntax to get the first part is:
%Variable%LOCAL__Line[1] - %

This is a really good thing to know.

EXAMPLE Get first part of line before " - ".kmmacros (4.7 KB)

Click to Show Image of Macro

4 Likes

I wasn't sure how to do the delimiter. That works perfectly. Thank you!

1 Like

From the "more than one way to do it" file...

As well as "getting the text before the -", you could consider this as "delete all the text from - to the end of each line". So a nice and simple regular expression "search for - .* and replace with nothing":

Get First Part.kmmacros (3.4 KB)

Image

1 Like

Ha!

@Nige_S beat me to the punch on the regex one, but I'll post mine anyway due to some variation in the way I preprocess the text and a slightly different regular expression.

Filter a Hyphenated List (RegEx Version) v1.00.kmmacros (7.8 KB)

Macro Image

Keyboard Maestro Export


I also built an AppleScript version just for fun.

Filter a Hyphenated List (AppleScript Version) v1.00.kmmacros (8.3 KB)

Macro Image

Keyboard Maestro Export

1 Like

FWIW, a Swift solution:

Filter artist names.kmmacros (4.6 KB)

Expand disclosure triangle to see "Swift" source
import Foundation

// FUNCTIONS --
func main() -> () {
    let str = ProcessInfo.processInfo.environment["KMVAR_localParameter"]!

    return print(
        (unlines
         • map(strip • takeWhile({x in x != "-"}))
         • lines
        )(str)
    )
}

// FUNCTIONS --
// https://github.com/unlocked2412/Swift-Prelude
// Swift Precedence & Operators --------------------------------
precedencegroup ApplicativePrecedence {
    associativity: left
    higherThan: BitwiseShiftPrecedence
}

infix operator <*> : ApplicativePrecedence

precedencegroup CompositionPrecedence {
	associativity: right
	
	// This is a higher precedence than the exponentiative operators `<<` and `>>`.
	higherThan: BitwiseShiftPrecedence, ApplicativePrecedence
}

// Swift Prelude -----------------------------------------------
// apFn :: (a -> b -> c) -> (a -> b) -> (a -> c)
func <*><A, B, C>(_ f: @escaping (A) -> (B) -> C, _ g: @escaping (A) -> B) -> ((A) -> C) {
    return { x in f(x)(g(x)) }
}

// lines :: String -> [String]
func lines<S>(_ s: S) -> [Substring] where S: StringProtocol, S.SubSequence == Substring {
    return s.split(whereSeparator: \.isNewline)
}

// map :: (a -> b) -> [a] -> [b]
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
    return { xs in xs.map(f) }
}

// str :: [Char] -> String
func str(_ xs: [Character]) -> String {
    return String(xs)
}

// strip :: String -> String
func strip<S: StringProtocol>(_ s: S) -> String {
    return s.trimmingCharacters(in: .whitespaces)
}

// takeWhile :: (a -> Bool) -> [a] -> [a]
func takeWhile<A>(_ p: @escaping (A) -> Bool) -> (any Sequence<A>) -> (any Sequence<A>) {
    return { xs in xs.prefix(while: p)}
}

func takeWhile<S: StringProtocol>(_ p: @escaping (S.Element) -> Bool) -> (S) -> (S.SubSequence) {
    return { xs in xs.prefix(while: p)}
}

// unlines :: [String] -> String
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
    return xs.joined(separator: "\n")
}

infix operator • : CompositionPrecedence

// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
func • <A, B, C>(f: @escaping (B) -> C, g: @escaping (A) -> B) -> (A) -> C {
    return { x in f(g(x)) }
}

main()

Needs more Led Zeppelin

3 Likes

Fails on my Mojave system. I'm assuming this is because you have a more advanced version of Swift – yes?

Error Text
2023-01-25 14:18:30 Action 12931595 failed: Execute a Swift Script failed with script error: text-script:44:57: error: expected ':' following argument label and parameter name
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                        ^
text-script:44:57: error: expected type
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                        ^
text-script:44:68: error: consecutive statements on a line must be separated by ';'
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                                   ^
                                                                   ;
text-script:44:65: error: use of undeclared type 'any'
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                                ^~~
text-script:45:27: error: use of unresolved identifier 'f'
    return { xs in xs.map(f) }
                          ^
text-script:44:69: error: cannot specialize non-generic type 'Sequence'
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                                    ^       ~~~
                                                                            
text-script:8:10: error: use of unresolved identifier 'unlines'
        (unlines
         ^~~~~~~
text-script:9:10: error: use of unresolved operator '•'
         • map(strip • takeWhile({x in x != "-"}))
text-script:9:18: error: use of unresolved identifier 'strip'
         • map(strip • takeWhile({x in x != "-"}))
text-script:9:24: error: use of unresolved operator '•'
         • map(strip • takeWhile({x in x != "-"}))
text-script:9:28: error: use of unresolved identifier 'takeWhile'
         • map(strip • takeWhile({x in x != "-"}))
text-script:10:10: error: use of unresolved operator '•'
         • lines
text-script:40:36: error: extra argument 'whereSeparator' in call
    return s.split(whereSeparator: \.isNewline)
                                   ^~~~~~~~~~~
text-script:59:63: error: expected ':' following argument label and parameter name
func takeWhile<A>(_ p: @escaping (A) -> Bool) -> (any Sequence<A>) -> (any Sequence<A>) {
                                                              ^
text-script:59:63: error: expected type
func takeWhile<A>(_ p: @escaping (A) -> Bool) -> (any Sequence<A>) -> (any Sequence<A>) {
                                                              ^
text-script:59:84: error: expected ':' following argument label and parameter name
func takeWhile<A>(_ p: @escaping (A) -> Bool) -> (any Sequence<A>) -> (any Sequence<A>) {
                                                                                   ^
text-script:59:84: error: expected type
func takeWhile<A>(_ p: @escaping (A) -> Bool) -> (any Sequence<A>) -> (any Sequence<A>) {
                                                                                   ^
text-script:68:43: error: expected ',' separator
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
                                          ^
                                         ,
text-script:68:43: error: unnamed parameters must be written with the empty name '_'
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
                                          ^
                                          _: 
text-script:68:39: error: use of undeclared type 'any'
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
                                      ^~~
text-script:68:43: error: cannot specialize non-generic type 'Sequence'
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
                                          ^       ~~~
                                                  
text-script:60:14: error: contextual closure type '() -> ()' expects 0 arguments, but 1 was used in closure body
    return { xs in xs.prefix(while: p)}
             ^
2023-01-25 14:18:30 Execute a Swift Script failed with script error: text-script:44:57: error: expected ':' following argument label and parameter name
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                        ^
text-script:44:57: error: expected type
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                        ^
text-script:44:68: error: consecutive statements on a line must be separated by ';'
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                                   ^
                                                                   ;
text-script:44:65: error: use of undeclared type 'any'
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                                ^~~
text-script:45:27: error: use of unresolved identifier 'f'
    return { xs in xs.map(f) }
                          ^
text-script:44:69: error: cannot specialize non-generic type 'Sequence'
func map<A, B>(_ f: @escaping (A) -> B) -> (any Sequence<A>) -> any Sequence<B> {
                                                                    ^       ~~~
                                                                            
text-script:8:10: error: use of unresolved identifier 'unlines'
        (unlines
         ^~~~~~~
text-script:9:10: error: use of unresolved operator '•'
         • map(strip • takeWhile({x in x != "-"}))
text-script:9:18: error: use of unresolved identifier 'strip'
         • map(strip • takeWhile({x in x != "-"}))
text-script:9:24: error: use of unresolved operator '•'
         • map(strip • takeWhile({x in x != "-"}))
text-script:9:28: error: use of unresolved identifier 'takeWhile'
         • map(strip • takeWhile({x in x != "-"}))
text-script:10:10: error: use of unresolved operator '•'
         • lines
text-script:40:36: error: extra argument 'whereSeparator' in call
    return s.split(whereSeparator: \.isNewline)
                                   ^~~~~~~~~~~
text-script:59:63: error: expected ':' following argument label and parameter name
func takeWhile<A>(_ p: @escaping (A) -> Bool) -> (any Sequence<A>) -> (any Sequence<A>) {
                                                              ^
text-script:59:63: error: expected type
func takeWhile<A>(_ p: @escaping (A) -> Bool) -> (any Sequence<A>) -> (any Sequence<A>) {
                                                              ^
text-script:59:84: error: expected ':' following argument label and parameter name
func takeWhile<A>(_ p: @escaping (A) -> Bool) -> (any Sequence<A>) -> (any Sequence<A>) {
                                                                                   ^
text-script:59:84: error: expected type
func takeWhile<A>(_ p: @escaping (A) -> Bool) -> (any Sequence<A>) -> (any Sequence<A>) {
                                                                                   ^
text-script:68:43: error: expected ',' separator
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
                                          ^
                                         ,
text-script:68:43: error: unnamed parameters must be written with the empty name '_'
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
                                          ^
                                          _: 
text-script:68:39: error: use of undeclared type 'any'
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
                                      ^~~
text-script:68:43: error: cannot specialize non-generic type 'Sequence'
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
                                          ^       ~~~
                                                  
text-script:60:14: error: contextual closure type '() -> ()' expects 0 arguments, but 1 was used in closure body
    return { xs in xs.prefix(while: p)}
             ^. Macro “Filter artist names” cancelled (while executing Execute Swift Script).
1 Like

Okay, just one more for fun using some terse Perl.

In this one I've introduced some anomalous horizontal and vertical whitespace, which the Perl was able exclude without much effort.

Here's the un-tersified code:

#!/usr/bin/env perl -sw
use v5.010;

while ( my $line = <> ) {
    if ( $line =~ /^\h*(\w.+?)(?=\h-)/ ) { say $1; }
}

Typically I like to avoid loops whenever possible, but this is a very efficient use-case for one.


Filter a Hyphenated List (Perl Version) v1.00.kmmacros (8.3 KB)

Macro Image

Keyboard Maestro Export

Thank you for checking, @ccstone ! I really appreciate it.

If I run:

xcrun swift -version

I get:

Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
Target: arm64-apple-darwin22.2.0

Could you tell me which Swift version do you have ?

One of the issues seems to be:

the any keyword was introduced in Swift 5.6

and I am using it. But I am not sure about your Swift version.

1 Like
Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
Target: x86_64-apple-darwin18.7.0
1 Like

Yes, then you're right. Some of the features I am using feature aren't available on Swift 5.1.3.

1 Like