the last command you will use
y is a command that if everything goes as planed should end up being a shell, nonetheless it plays nice with other unix commands
what would happen if the shell spoke edn instead of text.
that and commands inspired by python/clojure
there is no stderr
thanks to tagged values in edn errors can flow from command to command going through stdout without affecting the actual data processing, by default if a command receives an error it will pass it along.
let's make an error:
$ y error :status 404 :reason '"not found"'
#y.E {"status" 404 "reason" "not found"}
now let's pipe it to cat:
$ y error :status 404 :reason '"not found"' | y cat
#y.E {"status" 404 "reason" "not found"}
no problem it went by without problem, now let's pipe that to something that does something useful:
$ y error :status 404 :reason '"not found"' | y range
[0 1 2 3 4 5 6 7 8 9]
you read my mind, you mean to the inputs and the command line options right?
well it happens that that's just a convenience, options are tagged values that can be sent at any point in the input.
yes, you are right (I read minds), you can change the options of a command on the fly:
$ y starts-with
asd
false
#y.O{:value "a"}
asd
true
dsa
false
#y.O{:value "x"}
xsdfs
true
$ y starts-with x
asd
false
xxx
true
$ y options x
#y.O {"value" "x"}
$ y options x | y starts-with
xsdffd
true
I'm using HTTP status response codes, I may invent 8xx errors or something like that if I need new ones, but there are some useful ones there and people remember HTTP status codes more than errno ones ;)
nope, I will provide pretty printers and may detect if it's the last command and pretty print it for you.
let commands and computers speak formats they like and humans read formats they like, don't push text to computers or data formats to humans.
is that a compliment? :)
I can imagine it, if it's not broken don't fix it, the shell is ok as it is, that's not unix.
I can imagine the same people telling the same things to K&R when they were developing unix and C (replacing unix and C for what was mainstream at the moment)
a y command is a python module that will be loaded by name, the format is:
y <command> <args>
where command is a name and args can be any valid edn value, an example using our friend echo:
$ y echo :num 1 :bool 2 :symbol asd :keyword :lala
{"symbol" "asd" "num" 1 "bool" 2 "keyword" "lala"}
for now if the command arguments are not a vector, a list, a set or a single value it will be interpreted as a map (may change in the future)
$ y echo [1 2 3]
[1 2 3]
$ y echo "asd"
"asd"
$ y echo asd
"asd"
$ y echo :asd
"asd"
and what can I do with that?
I don't know here are some random commands:
$ y echo '"hi this is some text"' | y title
"Hi This Is Some Text"
$ y echo '"hi this is some text"' | y title | y shuffle
[" " "h" "o" "s" " " "T" " " "i" "e" "H" " " "i" "I" "m" "S" "e" "s" "x" "T" "t"]
$ y echo '"hi this is some text"' | y title | y shuffle | y join
"i THSx e Imtih Tssoe"
$ y echo '"hi this is some text"' | y title | y shuffle | y join | y lower
"xtesi is htehmsoit "
$ y echo '"hi this is some text"' | y slice :start 1 :stop 7
"i this"
$ y echo '"hi this is some text"' | y slice :start 7 :stop 0 :step -1
" siht i"
$ seq 1 10 | y one-list
[1 2 3 4 5 6 7 8 9 10]
$ seq 1 10 | y one-list | y shuffle
[2 5 7 8 3 6 1 9 10 4]
$ seq 1 10 | y one-list | y shuffle | y sort
[1 2 3 4 5 6 7 8 9 10]
$ seq 1 10 | y one-list | y min
1
$ seq 1 10 | y one-list | y max
10
$ y range
[0 1 2 3 4 5 6 7 8 9]
$ y range :start 2
[2 3 4 5 6 7 8 9]
$ y range :start 2 :stop 7
[2 3 4 5 6]
$ y range :start 2 :stop 7 :step 2
[2 4 6]
$ y range :start 2 :stop 7 :step 2 | y reverse
[6 4 2]
all commands read from stdin so you can play with it from there:
$ y cat
hi
hi
hello
hello
^D
first, last, nth:
$ seq 1 10 | y first
1
$ seq 1 10 | y first 3
1
2
3
$ seq 1 10 | y first 5
1
2
3
4
5
$ seq 1 10 | y first 20
1
2
3
4
5
6
7
8
9
10
$ seq 1 10 | y last
10
$ seq 1 10 | y last 3
8
9
10
$ seq 1 10 | y nth 5
5
$ seq 1 10 | y nth 2
2
$ seq 1 10 | y nth
1
$ seq 1 10 | y nth 20
nil
keep-keys, drop-keys:
$ y date-time
{"hours" 8 "second" 11 "year" 2013 "day" 15 "minute" 52 "month" 8}
$ y date-time | y drop-keys [year month day]
{"hours" 8 "second" 31 "minute" 51}
$ y date-time | y keep-keys [year month day]
{"month" 8 "year" 2013 "day" 15}
drop-first, drop-last:
$ seq 1 10 | y drop-first
2
3
4
5
6
7
8
9
10
$ seq 1 10 | y drop-first 3
4
5
6
7
8
9
10
$ seq 1 10 | y drop-last
1
2
3
4
5
6
7
8
9
$ seq 1 10 | y drop-last 3
1
2
3
4
5
6
7
$ seq 1 10 | y drop-last 30
$
not:
$ echo true | y not
false
$ echo false | y not
true
$ echo 1 | y not
false
$ echo 0 | y not
true
$ echo [] | y not
true
$ echo [1] | y not
false
$ y not
true
false
false
true
1
false
0
true
^D
bool:
$ echo [1] | y bool
true
$ echo [] | y bool
false
$ echo 1 | y bool
true
$ echo 0 | y bool
false
$ echo true | y bool
true
$ echo false | y bool
false
$ echo nil | y bool
false
any, all:
$ seq 1 10 | y all
true
$ seq 0 10 | y all
false
$ seq 1 10 | y any
true
$ seq 0 1 | y any
true
count:
$ seq 1 10 | y count
10
$ seq 1 20 | y count
20
$ echo 1 | y count
1
join:
$ seq 1 10 | y join :sep a
"1a2a3a4a5a6a7a8a9a10"
$ seq 1 10 | y join :sep -
"1-2-3-4-5-6-7-8-9-10"
$ seq 1 10 | y join
"12345678910"
replace:
$ echo -e "foo\nbar\nbaz" | y replace :old a :new x
"foo"
"bxr"
"bxz"
reverse:
$ seq 1 10 | y reverse
10
9
8
7
6
5
4
3
2
1
set:
$ echo -e "1\n2\n1" | y set
1
2
shuffle and sort:
$ seq 1 10 | y shuffle
8
5
4
7
6
3
10
9
1
2
$ seq 1 10 | y shuffle | y sort
1
2
3
4
5
6
7
8
9
10
slice (the generated seq is different on each example to keep the output short):
$ seq 1 8 | y slice :start 3
4
5
6
7
8
$ seq 1 5 | y slice
1
2
3
4
5
$ seq 1 20 | y slice :start 3 :stop 10 :step 2
4
6
8
10
$ seq 1 20 | y slice :start 3 :stop 5
4
5
many commands:
$ y ls | y keep-keys [type path size] | y group-by type | y get f | y flatten-1 | y sort-by size | y keep-keys [ path ]
will display a list of all the file paths under the current directory
sorted by size
it could be simpler but the idea is to show how commands compose
filtering:
$ y ls | y keep :is eq? :key type :value f | y p
-------+--------+---------+--------------------------+-----------------------------------------+--------------------------+------+-------
Uid | Gid | Mode | Mtime | Path | Atime | Type | Size
-------+--------+---------+--------------------------+-----------------------------------------+--------------------------+------+-------
ubuntu | ubuntu | 0100664 | Thu Aug 15 17:44:46 2013 | /home/ubuntu/src/yel/README.rest | Thu Aug 15 17:44:46 2013 | File | 8 KBs
ubuntu | ubuntu | 0100664 | Thu Aug 15 18:00:59 2013 | /home/ubuntu/src/yel/yel_utils.pyc | Thu Aug 15 18:01:46 2013 | File | 15 KBs
ubuntu | ubuntu | 0100764 | Thu Aug 15 19:08:05 2013 | /home/ubuntu/src/yel/y | Thu Aug 15 19:08:05 2013 | File | 2 KBs
ubuntu | ubuntu | 0100664 | Thu Aug 15 19:09:34 2013 | /home/ubuntu/src/yel/yel_predicates.py | Thu Aug 15 19:09:34 2013 | File | 1 KBs
ubuntu | ubuntu | 0100664 | Thu Aug 15 17:46:40 2013 | /home/ubuntu/src/yel/yel_utils.py | Thu Aug 15 17:46:40 2013 | File | 9 KBs
ubuntu | ubuntu | 0100664 | Wed Aug 14 20:14:45 2013 | /home/ubuntu/src/yel/yel_status.pyc | Wed Aug 14 20:16:28 2013 | File | 0 KBs
ubuntu | ubuntu | 0100664 | Wed Aug 14 16:13:12 2013 | /home/ubuntu/src/yel/parser.out | Wed Aug 14 14:30:53 2013 | File | 52 KBs
ubuntu | ubuntu | 0100664 | Thu Aug 15 19:09:36 2013 | /home/ubuntu/src/yel/yel_predicates.pyc | Thu Aug 15 19:09:51 2013 | File | 3 KBs
ubuntu | ubuntu | 0100664 | Wed Aug 14 15:21:48 2013 | /home/ubuntu/src/yel/yel_status.py | Thu Aug 15 15:23:14 2013 | File | 0 KBs
ubuntu | ubuntu | 0100664 | Thu Aug 15 11:26:30 2013 | /home/ubuntu/src/yel/test.py | Thu Aug 15 11:26:30 2013 | File | 0 KBs
ubuntu | ubuntu | 0100664 | Wed Aug 14 16:13:12 2013 | /home/ubuntu/src/yel/parsetab.py | Wed Aug 14 16:14:40 2013 | File | 7 KBs
ubuntu | ubuntu | 0100664 | Wed Aug 14 20:04:00 2013 | /home/ubuntu/src/yel/.gitignore | Wed Aug 14 20:04:10 2013 | File | 0 KBs
ubuntu | ubuntu | 0100664 | Wed Aug 14 20:14:45 2013 | /home/ubuntu/src/yel/parsetab.pyc | Wed Aug 14 20:16:28 2013 | File | 7 KBs
$ y ps | y keep-keys [pid username cpu_percent status exe name memory_percent] | y keep pid [lt 200] | y p
$ y ls | y drop type f | y p
-------+--------+--------+--------------------------+---------------------------------+--------------------------+------+------Uid | Gid | Mode | Mtime | Path | Atime | Type | Size -------+--------+--------+--------------------------+---------------------------------+--------------------------+------+------ubuntu | ubuntu | 040775 | Thu Aug 15 18:01:46 2013 | /home/ubuntu/src/yel/edn_format | Thu Aug 15 18:02:38 2013 | Dir | 4 KBs ubuntu | ubuntu | 040755 | Wed Aug 14 15:56:56 2013 | /home/ubuntu/src/yel/pyrfc3339 | Thu Aug 15 15:57:34 2013 | Dir | 4 KBs ubuntu | ubuntu | 040775 | Thu Aug 15 19:12:21 2013 | /home/ubuntu/src/yel/.git | Wed Aug 14 20:04:03 2013 | Dir | 4 KBs ubuntu | ubuntu | 040775 | Thu Aug 15 19:16:33 2013 | /home/ubuntu/src/yel/commands | Thu Aug 15 19:12:19 2013 | Dir | 4 KBs
$ seq 0 1 10 | y any asd? #y.E {"status" 404 "reason" "Invalid predicate 'asd?'"}
$ seq 1 10 | y any zero? false
$ seq 0 10 | y any zero? true
$ seq 0 10 | y all int? true
$ seq 0 10 | y all even? false
$ seq 0 10 | y all false
$ seq 0 10 | y any true
yes, but you can use map to apply a command to each item in a unit if it's a sequence:
$ y ls | y keys
["uid" "gid" "mode" "mtime" "path" "atime" "type" "size"]
["uid" "gid" "mode" "mtime" "path" "atime" "type" "size"]
["uid" "gid" "mode" "mtime" "path" "atime" "type" "size"]
$ y ls | y keys | y map upper
["UID" "GID" "MODE" "MTIME" "PATH" "ATIME" "TYPE" "SIZE" ]
["UID" "GID" "MODE" "MTIME" "PATH" "ATIME" "TYPE" "SIZE" ]
["UID" "GID" "MODE" "MTIME" "PATH" "ATIME" "TYPE" "SIZE" ]
how to differentiate commands that work on collections on each line and the ones that works on the whole input as a collection?
- prefix/suffix?
- option?
- docs?
- reduce
- df
- free
- recursive ls
- pwd
GPL v3 (may change my mind in the future)