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)