Overview

Here's the core syntax of smh:

smh <command> [input]

  • <command> is a string specifiyng what smh should do with the input string.
  • If the input string is not given, smh gets input from stdin.

<command> string

The command string consists of two parts, separated by the pipe symbol "|""

The first part is a list of focusers, separated by the dot symbol "."

The second part is an action.

The idea is that we use focusers to nail down on the part of the string that we want to access, and then the action that we want to perform on it.

For example: smh 'words|get' "hello world" - 'words' focuser focuses on all words within the string, the get action prints them to the screen.

How do focusers work?

Each focuser in the focuser list can be thought of as a function that takes in a focus and returns a list of new focuses. Each focuser represents a focusing step. During each step, every active focus gets fed into the current focuser and all the new focuses that are produces go to the next step.

What is a focus?

Focuses can be devided into two groups:

  • Updatable focuses - represent some part of the input string, these can be updated through actions, to get an updated input string.

  • Non-updatable focuses - these represent read-only data, obtained from the input string, and thus can't be updated, only read.

Focuses are further divided into two types:

  • Strings - represent string data.
  • Lists - represent lists of sub-focuses.

For example the "words" focuser takes in a String focus and produces Updatable String focuses for each word in it.

At the very start we have one active Updatable String focus which represents the whole input string.

For the description of available focusers visit Focuser documentation

What is an action?

Action is the part of the command string that goes after the pipe symbol. Actions can be used to display parts of input, or change them.

For the description of available actions visitAction documentation

Focuser documentation

id (focuser)

"id" is the simplest focuser possible, it returns the same focus that was given to it. It can be useful if you want to focus on the whole input string.

$ smh 'id|get' "hello world"
hello world

each

"each" focuses on each element of a List, or every character in a String.

$ smh 'each|get' "hello"
h
e
l
l
o

words

"words" focuses on each word within a String.

$ smh 'words|get' "hello world"
hello
world

lines

"lines" focuses on every line within the input string.

$ printf "hello\nworld" | smh 'lines|get'
hello
world

ws

"ws" focuses on consequetive strings of whitespace characters.

$ smh 'ws.len|get' "a b c d e"
4

collecting (%)

You can collect all the focuses, produced by a focuser into one List focus, that includes all of them, by using "%"

$ smh '%words|over reverse' "hello world my name is Dani"
Dani is name my world hello

cols

"cols" focuses on every column within the input string. Columns are represented as Lists of words.

$ smh '%cols.[0]|get' "hello my\nname is Dani"
hello
name

slice (focuser)

"slice" ({}) focuses on a part of a String or a List, using a python-like syntax.

$ smh '{1:3}|get' "hello"
el
$ smh '{2:}|get' "hello"
llo
$ smh '{:-2}|get' "hello"
hel
$ smh '{:2,3:}|get' "hello"
helo

index ([n])

"index" focuses on the nth element of a List or a String.

$ smh '[1]|get' "hello"
e

sortedLexBy

"sortedLexBy" focuses on the lexicographically sorted version of a List or a String, using another focuser as a key.

$ smh 'sortedLexBy id|get' "hello"
ehllo

Note:
"sortedLexBy" parses the next focuser
non-greedily , i.e.
"sortedLexBy focuser1.focuser2 == (sortedLexBy focuser1).focuser2"

To use multiple focusers, use parenthesis "sortedLexBy (focuser1.focuser2)"

sortedLex

A shorthand for "sortedLexBy id"

minLexBy

"minLexBy" focuses on the lexicographically smallest element in a List or a String, using another focuser as a key.

$ smh '%words.minLexBy id|get' "hello my name is Dani"
Dani

Note:
"minLexBy" parses the next focuser
non-greedily , i.e.
"minLexBy focuser1.focuser2 == (minLexBy focuser1).focuser2"

To use multiple focusers, use parenthesis "minLexBy (focuser1.focuser2)"

maxLexBy

"maxLexBy" focuses on the lexicographically largest element in a List or a String, using another focuser as a key.

$ smh '%words.maxLexBy id|get' "hello my name is Dani"
name

Note:
"maxLexBy" parses the next focuser
non-greedily , i.e.
"maxLexBy focuser1.focuser2 == (maxLexBy focuser1).focuser2"

To use multiple focusers, use parenthesis "maxLexBy (focuser1.focuser2)"

minLex

A shorthand for "minLexBy id"

maxLex

A shorthand for "maxLexBy id"

sortedBy

"sortedBy" focuses on the sorted version of a List or a String, using another focuser as a key.

$ smh '%words.sortedBy len|get' "hello my name is Dani"
my
is
name
Dani
hello

Note:
"sortedBy" parses the next focuser
non-greedily , i.e.
"sortedBy focuser1.focuser2 == (sortedBy focuser1).focuser2"

To use multiple focusers, use parenthesis "sortedBy (focuser1.focuser2)"

Note:
"sortedBy" compares focuses by trying to parse them as numbers and comparing those.
Focuses which which cannot be parsed as numbers are not sorted.

sorted

A shorthand for "sortedBy id"

minBy

"minBy" focuses on the first smallest element in a List or a String, using another focuser as a key.

$ smh '%words.minBy len|get' "Hello my name is Dani"
my

Note:
"minBy" parses the next focuser
non-greedily , i.e.
"minBy focuser1.focuser2 == (minBy focuser1).focuser2"

To use multiple focusers, use parenthesis "minBy (focuser1.focuser2)"

Note:
"minBy" compares focuses by trying to parse them as numbers and comparing those.
Focuses which which cannot be parsed as numbers are not sorted.

maxBy

"maxBy" focuses on the first largest element in a List or a String, using another focuser as a key.

Note:
"maxBy" parses the next focuser
non-greedily , i.e.
"maxBy focuser1.focuser2 == (maxBy focuser1).focuser2"

To use multiple focusers, use parenthesis "maxBy (focuser1.focuser2)"

Note:
"maxBy" compares focuses by trying to parse them as numbers and comparing those.
Focuses which which cannot be parsed as numbers are not sorted.

$ smh '%words.maxBy len|get' "Hello my name is Dani"
Hello

Note:
"maxBy" parses the next focuser
non-greedily , i.e.
"maxBy focuser1.focuser2 == (maxBy focuser1).focuser2"

To use multiple focusers, use parenthesis "maxBy (focuser1.focuser2)"

min

A shorthand for "minBy id"

$ smh 'min|get' "123"
1

max

A shorthand for "maxBy id"

$ smh 'max|get' "123"
3

len (focuser)

"len" focuses on the number of elements in a List or a String.

$ smh 'words.len|get' "hello"
5

Note:
"len" produces a
Non-updatable focus.

sum

"sum" focuses on the sum of all elements in a List or a String, that can be parsed as numbers.

$ smh '%words.sum|get' "1 2 3"
6

Note:
"sum" produces a
Non-updatable focus.

product

"product" focuses on the product of all elements in a List or a String, that can be parsed as numbers.

$ smh '%words.product|get' "1 2 3"
6

Note:
"product" produces a
Non-updatable focus.

average

"average" focuses on the average of all elements in a List or a String, that can be parsed as numbers.

$ smh '%words.average 0|get' "1 2 3"
2

Note:
"average" produces a
Non-updatable focus.

Note:
"average" takes in a optional parameter which defines the default value when no elements that could be parsed as numbers are found. If no default value is given, it will be 0.

add (focuser)

"add" adds a number to a focus if it can be parsed as a number.

$ smh 'words.add 1|get' "1 2 3 4 5"
2
3
4
5
6

Note:
"add" produces a
Non-updatable focus.

sub (focuser)

"sub" subtracts a number from a focus if it can be parsed as a number.

$ smh 'words.sub 1|get' "1 2 3 4 5"
0
1
2
3
4

Note:
"sub" produces a
Non-updatable focus.

mult (focuser)

"mult" multiplies a number to a focus if it can be parsed as a number.

$ smh 'words.mult 2|get' "1 2 3 4 5"
2
4
6
8
10

Note:
"mult" produces a
Non-updatable focus.

div (focuser)

"div" divides a focus by a number if the focus can be parsed as a number.

$ smh 'words.div 2|get' "1 2 3 4 5"
0.5
1
1.5
2
2.5

Note:
"div" produces a
Non-updatable focus.

pow (focuser)

"pow" raises a focus to a power if the focus can be parsed as a number.

$ smh 'words.pow 2|get' "1 2 3 4 5"
1
4
9
16
25

Note:
"pow" produces a
Non-updatable focus.

abs (focuser)

"abs" focuses on the absolute value of a focus if it can be parsed as a number.

$ smh 'words.abs|get' "-1 -2 -3 -4 -5"
1
2
3
4
5

Note:
"abs" produces a
Non-updatable focus.

sign (focuser)

"sign" focuses on the sign of a focus if it can be parsed as a number.

$ smh 'words.sign|get' "-1 2 -3 0 -5"
-1
1
-1
0
-1

literal (constant) focusers

Strings in double quotes and numbers in place of focusers are interpreted as a constant focuser, that always produces a single focus, equal to that string or number.

$ smh 'words."const"|get' "hello world my name is Dani"
const
const
const
conct
const
const

if

"if" conditionally focuses on the same focus that it was given, if the provided focuser returns "1"

$ smh 'words.if > len 3|get' "hello world my name is Dani"
hello
world
name
Dani
$ smh 'words.if > 5|get' "3 4 5 6 7"
6
7
$ smh 'words.if && (= abs id) (!= 0)|get' "-1 -2 0 1 2"
1
2
$ smh 'words.if any(each.="h")|get' "hello world my name is Dani"
hello
$ smh 'words. if all (each.isLower)|get' "hello world my name is Dani"
hello
world
my
name
is

logical focusers

Logical focusers produce a Non-updatable focus which is equal to either "1" or "0".
They are used primarily with the "if" or "filter" focusers.

isUpper

"isUpper" produces "1" if the focus is all upper case, and "0" otherwise.

$ smh 'words.isUpper|get' "hello world my name is DANI"
0
0
0
0
0
1

isLower

"isLower" produces "1" if the focus is all lower case, and "0" otherwise.

$ smh 'words.isLower|get' "hello world my name is Dani"
1
1
1
1
1
0

isDigit

"isDigit" produces "1" if the focus is all digits, and "0" otherwise.

$ smh 'words.isDigit|get' "hello world my name is 1234"
0
0
0
0
0
1

isAlpha

"isAlpha" produces "1" if the focus is all letters, and "0" otherwise.

$ smh 'words.isAlpha|get' "hello world my name is 1234"
1
1
1
1
1
0

isAlphaNum

"isAlphaNum" produces "1" if the focus is all letters or digits, and "0" otherwise.

$ smh 'words.isAlphaNum|get' "he11o world my name is ____"
1
1
1
1
1
0

isSpace

"isSpace" produces "1" if the focus is all spaces, and "0" otherwise.

$ smh 'ws.isSpace|get' "hello world my name is 1234"
1
1
1
1
1

isNumber

"isNumber" produces "1" if the focus can be parsed as a number, and "0" otherwise.

$ smh 'words.isNumber|get' "hello world 1.5 12"
0
0
1
1

contains

"contains" produces "1" if the focus contains the given string, and "0" otherwise.

$ smh 'words.contains "l"|get' "hello world my name is Dani"
1
1
0
0
0
0

startsWith

"startsWith" produces "1" if the focus starts with the given string, and "0" otherwise.

$ smh 'words.startsWith "he"|get' "hello world my name is Dani"
1
0
0
0
0
0

endsWith

"endsWith" produces "1" if the focus ends with the given string, and "0" otherwise.

$ smh 'words.endsWith "ani"|get' "hello world my name is Dani"
0
0
0
0
0
1

>, <, >=, <=, ==, !=

These focusers take in two focusers and apply the given operator to their focuses.

$ smh 'words.if > len 3|get' "Hello world my name is Dani"
Hello
world
name
Dani

Additionally for convenience, the first focuser can be omitted if it's "id". "> f2 == > id f2"

Note:
"==" and "!=" can compare
Strings and Lists, but other ones can only compare Strings.

&&, ||

These focusers take in two logical focusers and apply the given operator to their focuses.

$ smh 'words.if && (> len 2) (< len 5)|get' "Hello world my name is Dani"
name
Dani

all, any

These focusers take in a focuser and check if all/any of their focuses are equal to "1"

$ smh 'words.if any (each.isUpper)|get' "hello woRld my nAme is Dani"
woRld
nAme
Dani

not

"not" flips its focus from "1" to "0" and vice versa.

$ smh 'words.if (any (each.isUpper).not)|get' "hello woRld my nAme is Dani"
hello
my
is

regex

"regex" takes in a Perl style regex and focuses on all substrings of a String focus that match it.

$ smh 'words.regex "[a-z]+"|get' "hello world my name is Dani"
hello
world
my
name
is
ani

filter

"filter" works like "if", but instead of filtering the focus itself, it filters elements of a List or a String and focuses on a sub-list or a sub-string, that contains all the elements that passed.

$ smh '%words.filter > len 3|get' "hello world my name is Dani"
hello
world
name
Dani

to (focuser)

"to" can convert a mapping list into a Non-updatable focuser.

$ smh '%words.to reverse|get' "hello world my name is Dani"
Dani
is
name
my
world
hello

Note:
For more information on mappings, visit Mapping documentation

el

"el" parses a JSON array and focuses on each element.

$ smh 'el|get' "[1,2,3,4,5]"
1
2
3
4
5

kv

"kv" parses a JSON object and focuses on each key-value pair, represented as a two-element List of Strings

$ smh 'kv|get-tree' '{"a":1,"b":2,"c":3}'
[["\"a\"","1"], ["\"b\"","2"], ["\"c\"","3"]]

key

When given a string focus, "key" tries to parse it as a JSON object and focuses on all it's keys.
When given a key value pair (represented as a List), it focuses on the key (first element).

$ smh 'key|get' '{a:1, "b":2, c:3}'
a
b
c
$ smh 'kv.if = key "a".val|get' '{"a":1,"b":2,"c":3}'
1

Note:
Notice that "key" strips the quotes from the key, if it has them.

val

When given a string focus, "val" tries to parse it as a JSON object and focuses on all it's values.
When given a key value pair (represented as a List), it focuses on the value (second element).

$ smh 'val|get' '{a:1, "b":"2", c:3}'
1
"2"
3
$ smh 'kv.if = val "2".key|get' '{"a":1, "b":2, "c":3}'
b

Note:
Notice that "val"
doesn't strip the quotes from the value.

atKey

"atKey" parses it's focus as a JSON object and focuses on values associated with the given key.

$ smh 'atKey "a"|get' '{a:1, "b":2, c:3}'
1

*Note:
atKey "key" == kv.if = key "key".val

atIdx

"atIdx" parses it's focus as a JSON array and focuses on the element at the given index.

$ smh 'atIdx 1|get' '[1,2,3,4,5]'
2

*Note:
atIdx i == %el.[i]

Action documentation

get-tree

"get-tree" displays the list of all focuses active at the end.

$ smh 'words|get-tree' "hello world my name is Dani"
["hello","world","my","name","is","Dani"]

get

"get" displays all strings found in all focuses active at the end, one string per line.

$ smh 'words|get' "hello world my name is Dani"
hello
world
my
name
is
Dani

over

"over" takes in a list of mappings separated by ":", applies them to the focuses and reconstructs the original string.

$ smh 'words|over reverse' "hello world my name is Dani"
olleh dlrow ym eman si inaD

Note:
For more information on mappings, visit Mapping documentation

set

"set" takes in a string and sets every string found in all focuses active at the end to that string.

$ smh 'words|set "hello"' "hello world my name is Dani"
hello hello hello hello hello hello

Mapping documentation

id (mapping)

"id" is the simplest mapping possible, it returns the same focus that was given to it.

$ smh 'id|over id' "hello world"
hello world

reverse

"reverse" reverses a List or a String.

$ smh '%words|over reverse' "hello world my name is Dani"
Dani is name my world hello

len (mapping)

"len" sets a String focus to its length.

$ smh 'words|over len' "hello world my name is Dani"
5 5 2 4 2 4

map

"map" takes in a mapping and applies it to every element of a List or a String.

Note:
"map" takes in a mapping
non-greedily , i.e.
"map mapping1:mapping2" == "(map mapping1):mapping2"
To use multiple mappings, use parenthesis "map (mapping1:mapping2)"

$ smh '%words|over map len' "hello world my name is Dani"
5 5 2 4 2 4

append

"append" takes in a string, number or a focuser and appends it to a String focus.

$ smh 'words|over append "!"' "hello world my name is Dani"
hello! world! my! name! is! Dani!
$ smh 'words|over append len' "hello world my name is Dani"
hello5 world5 my2 name4 is2 Dani4

prepend

"prepend" takes in a string, number or a focuser and prepends it to a String focus.

$ smh 'words|over prepend "!"' "hello world my name is Dani"
!hello !world !my !name !is !Dani
$ smh 'words|over prepend len' "hello world my name is Dani"
5hello 5world 2my 4name 2is 4Dani

upper

"upper" sets a String focus to its upper case.

$ smh 'words|over upper' "hello world my name is Dani"
HELLO WORLD MY NAME IS DANI

lower

"lower" sets a String focus to its lower case.

$ smh 'words|over lower' "HELLO WORLD MY NAME IS DANI"
hello world my name is dani

add (mapping)

"add" adds a number to a focus if it can be parsed as a number.

$ smh 'words|over add 1' "1 2 3"
2 3 4

sub (mapping)

"sub" subtracts a number from a focus if it can be parsed as a number.

$ smh 'words|over sub 1' "1 2 3"
0 1 2

mult (mapping)

"mult" multiplies a focus by a number if it can be parsed as a number.

$ smh 'words|over mult 2' "1 2 3"
2 4 6

div (mapping)

"div" divides a focus by a number if it can be parsed as a number.

$ smh 'words|over div 2' "1 2 3"
0.5 1 1.5

pow (mapping)

"pow" raises a focus to a power if the focus can be parsed as a number.

$ smh 'words|over pow 2' "1 2 3 4 5"
1 4 9 16 25

abs (mapping)

"abs" sets the focus to its absolute value if it can be parsed as a number.

$ smh 'words|over abs' "-1 -2 -3 -4 -5"
1 2 3 4 5

sign (mapping)

"sign" sets the focus to its sign if it can be parsed as a number.

$ smh 'words|over sign' "1 -2 3 0 -5"
1 -1 1 0 -1

slice ({}) (mapping)

"slice" sets a String focus to its part according to a python-like syntax.

$ smh 'words|over {1:3}' "hello"
el
$ smh 'words|over {2:}' "hello"
llo
$ smh 'words|over {:-2}' "hello"
hel
$ smh 'words|over {:2,3:}' "hello"
helo

sortLexBy

"sortLexBy" sorts a List or a String focus lexicographically using another focuser as a key.

$ smh 'words|over sortLexBy id' "hello my name is Dani"
ehllo my aemn is Dain

sortLex

"sortLex" is a shorthand for "sortLexBy id"

sortBy

"sortBy" sorts a List or a String focus using another focuser as a key.

$ smh '%words|over sortBy len' "hello my name is Dani"
my is name Dani hello

sort

"sort" is a shorthand for "sortBy id"

to (mapping)

"to" produces a mapping from a focuser list that takes in a String focus and produces exactly one String focus.

$ smh 'regex "\[.*?\]"|over to ({1:-1}.%(words.regex "[^,]*").sum)' "[1, 2, 3] [4, 5]"
6 9