Skip to content

seydar/chitin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

== Quick Intro

Rules of the road:

  * Everything you enter is pure Ruby. When you type, you are typing pure Ruby.
  * When in doubt of what something is: use #inspect by doing command[:inspect]
  * #[] allows you to run methods of Executables, Pipes, and StringMethods
    without accidentally running them.

Sample commands and shizzle:

  ari: ~/src/chitin % ls
  ari: ~/src/chitin % 5 + 4
  ari: ~/src/chitin % ll = ls -:al
  ari: ~/src/chitin % hg.stat
  ari: ~/src/chitin % hg.stat | wc | L {|i| i.size }
  ari: ~/src/chitin % hg.stat | wc | L {|i| i.size } > '/tmp/test.out'
  ari: ~/src/chitin % 44 - 2
  ari: ~/src/chitin % _ + 5
  ari: ~/src/chitin % wget "http://bigbad.evils.in
  ari: ~/src/chitin % _ # run the same command again

Look! Thanks to Mon_Ouie, it even has syntax highlighting on the line as you
type! Hooray! Thank you, monsieur!

For the most part, you can just use chitin as you would bash or any other
shell.

Why should you use Chitin?

  * You can do simple arithmatic on the command line without having to switch
    interfaces.
  * The power of Ruby in a shell
  * The command utility of a shell in Ruby
  * No underlying shell usage
  * A much more programmatic shell
  * Prevents against accidental `rm -rf /usr /local/bin` like that one thing
    we all saw on Reddit.
  * Syntax highlighting while you type
  * Makes for a great Christmas present
  * Text processing is SO. MUCH. EASIER.
  * Great library for calling executable files from your own program without
    shelling your soul or shelling out.

== The Library

Buckle up, kids:

  include Chitin

Real simply, here's how you create a reference to a file with Chitin:

  f = FileObject.new "/bin/ls"
  f = F "/bin/ls"

and a directory:

  d = Directory.new "/bin"
  d = D "/bin"

Which is sweet and all, but you kinda need read and write priveleges in order
to read and write to it. And if you're writing to /bin/ls, you're a bad person.

So we can make it an executable really easily:

  f = Executable.new "/bin/ls"

By default, it reads and writes from and to, respectively, empty pipes.
So to make it work in a standard manner, we need to set it up to use STDIN,
STDOUT, and STDERR.

  f.in.reopen  STDIN  # f.in is the input to f
  f.out.reopen STDOUT # f.out is the output of f
  f.err.reopen STDERR # f.err is the error of f

We can easily run this:

  f.run

SWEET!

A more complicated example of "cat /tmp/cool | wc -l":

  f = Executable.new "/bin/cat", "/tmp/cool"
  g = Executable.new "/bin/wc", "-l"

Now we link them together (something like f | g)

  g.in.reopen f.out

And make the overall input and output visible to us

  f.in.reopen  STDIN
  g.out.reopen STDOUT

and run them

  f.run # since these are asynchronous
  g.run # this seems like an ok thing to do

That's a lot of work, though. Luckily, we can simplify it all:

  STDIN > f | g > STDOUT

What's that? You have another function called h?

  h = Executable.new "/bin/cat"
  pipe = STDIN > f | g | h > STDOUT

And then you can run the pipe simply:

  pipe.run

Easy peasy, right? But what if you want to run the exact same command again?
No worries:

  pipe.run

It automatically resets itself internally after being run.

You can even use ruby code:

  STDIN > f | gsub(/asdf/, "LAWL") > STDOUT

And you can chain it simply:

  STDIN > f | gsub(/asdf/, "LAWL").chomp > STDOUT

You can even set up a pipe to return a random ruby object of your choice:

  STDIN > f | split.size > STDOUT

Although you have to run it with `pipe.raw_run`.

If you have your own method and you want to take the result of something as an
input, you can use a special proc to make it happen:

  STDIN > f | L {|i| my_method(i) } > STDOUT

And let's say that you want it all into a file. No problemo, broski.

  STDIN > f | L {|i| my_method(i) } > '/tmp/lolwut'

Dis ish goes both ways:

  '/tmp/my_input' > f | L {|i| my_method(i) } > '/tmp/lolwut'

SILENCE ALLLLLLLLLLLLLLLLLLLL

  NULLIN > f | L {|i| my_method(i) } > NULLOUT ^ NULLERR

Git on mah level, son.

== The Shell

Chitin is such named because it is the material that comprises the exoskeleton
shells of insects. Yes, I know insects suck, but chitin is a pretty cool word,
especially when you learn that it's pronounced chitin and not chitin.

By default, chitin gives you a funky prompt that's big like yo mamma. The
reason the default prompt is so AWESOME is so that it doesn't look identical
to my bash prompt (leading me to get confused) but still provides useful
information. Before we continue our tutorial, let's change that. Add the
following bit of code to ~/.chitinrc

  def prompt
    "#{ENV['USER']}: #{short_pwd} % "
  end

1337. Well done! You've been promoted to tenderfoot. I'm proud of you.

So now let's get going with this thang. Try out some random math equation:

  ari: ~/src/chitin % 4 + 5
   => 9
  ari: ~/src/chitin % (1 + 3 + 4  +65 + 7 +345) / 23
   => 18.4782608695652

NB: integer division is off by default because it sucks and always has sucked.
If I'm cranking out some quick maths, I don't want to deal with the fact that
3/4 = 0. NIQUE LA POLICE WOOT WOOT!!!!!

So ladida, you're doing some math, this could be IRB with a few changes.
HOLY BATMAN! LOOK AT THAT SHIZNIZZLE YOU JUST DID!!! Oh, you mean this bit?

  ari: ~/src/chitin % ls
  NOTES   README  TODO    bin     lib

Yeah, that's right. We just ran a command. And guess what? We didn't have to
start a shell underneath to get it to work! We also didn't use any system
calls because that would be hell to get right for every system. Though it
might be neat for a future version. Note to self...

But that's not all! Let's make ruby do some heavy lifting for us:

  ari: ~/src/chitin % ls | L {|s| s.split }
   => ["NOTES", "README", "TODO", "bin", "lib"]

Chyeah son, it even returned us a ruby object.

What Chitin as a shell does is it automatically does the STDIN > ... > STDOUT
boilerplate that you saw in the first section. It also automagically runs your
pipe, calling #run in the general case and #raw_run if it's supposed to return
ruby.

To earn your second class rank, you need to do a few things. First, you must
demonstrate proficiency with the bowline knot and clove hitch. These are just
good life skillz. Second, you must solve a problem for "your friend Jimmy".

Jimmmy has a pokemon folder on his computer that he doesn't want his dad to see.
Unfortunately, Jimmy doesn't know about cryptography and wants to give you
(hah, as if you and Jimmy aren't the same person) a task to solve with Chitin.
Help him hide his pokemon folder from his dad who likes to cd and ls around!

  ari: ~/src/chitin % ls
  NOTES   POKEMON    README  TODO    bin     lib
  ari: ~/src/chitin % ls | L {|s| a = s.gsub /pokemon/i, "digimon"} | cat
  NOTES
  digimon
  README
  TODO
  bin
  lib
  ari: ~/src/chitin % def ls; raw_command('/bin/ls') | L {|s| s.gsub /pokemon/i, "digimon" } | cat; end
   => nil
  ari: ~/src/chitin % ls
  NOTES
  digimon
  README
  TODO
  bin
  lib

Save that in you ~/.chitinrc and your dad won't find out about your pokemon
collection.

Congratulations! You are now a second class scout!

Alright, hotshot. Let's see if you've got what it takes to be first class.
Your boss rolls into your office and asks to speak with you. You know it
couldn't be about your pokemon collection because you fixed that in attaining
second class; now he just thinks you're a nerd for having a 40GB digimon
collection. So bossman rolls in and says "Yo dawg, delete every file and folder
in a directory." Naturally, he decides not to open up a GUI and do that himself
using the wonders of modern technology (the mouse). You look that cat dead in
the eye and say "YES SIR LET'S DO DIS THANG!"

  ari: ~/src/chitin % Dir['pokemon/*'].each {|f| rm f }

But since I'm a sick bastard, I showed you code that DOES NOT WORK. Before I
get into why it doesn't work, lemme show you code that DOES work, first:

  ari: ~/src/chitin % Dir['pokemon/*'].map {|f| rm f}

The difference here is that we used #map instead of #each. Why?

#each is a function that returns self after running the block. Since an
executable is just a Ruby object that is only run when told to, those blocks
will never run the code you want. In order to run the code, we use #map to
return the executables we want to run in an array. Chitin knows that whenever
it sees an array of executables, its job is to execute them.

But let's get back to the original problem, which was removing a buttload of
files:

  ari: ~/src/chitin % rm *Dir['pokemon/*']

The built-in Ruby * (glob) operator enables us to do batch functions as an
afterthought. It is like `rm "file1", "file2", ...`.

Sweet deal brah, you're a second class scout. To become a first class scout,
you need to install something. Luckily, Rosy the Nosy Neighbor from down the
street (guess who just watch "Trapped in the Closet" -- all fucking 23 videos)
wants you to install this C program that she wrote so she can monitor your
keystrokes. Here comes you to the rescue! OH MAN that rhymed. Time to give
up 'gramming and move to lyrical wordsmithing.

First, you gotsta download the codez:

  ari: ~/src/chitin % wget "http://supersecret.com/rosys_stuff.tar.gz

Simple enough, you're just running the `wget` command. Now, unzip it to remove
it from its archived format. Use tab completion to make your life simpler! Or
not...

  ari: ~/src/chitin % tar.zxf "rosys_stuff.tar.gz"

Easy there, cowboy. You have no idea what Rosy the Asshole just sent you.
Let's try it again except THIS time, use the "v" flag for "tar".

  ari: ~/src/chitin % rm -:rf, "rosys_stuff
  ari: ~/src/chitin % tar.zxvf "rosys_stuff.tar.gz"

So here we learned a few things about Chitin and how it plays well with 40
fucking years of bash-style syntax and history. Most obviously, -:rf takes
a symbol and translates it into '-rf'. These three are all equal:

  -:rf == -'rf' == '-rf'

You want two dashes? Sweet deal brah.

  --:rf == --'rf == -'-rf' == '--rf'

Next in the commands, we use tar. But we don't just run tar. We run tar.zxvf.
tar refers to the command itself, and then calling any methods on it send the
method name directly to the executable. Thus, 'tar.zxvf' becomes 'tar zxvf'. We
do this here because tar does NOT want any dashes.

So back to the installation process:

  ari: ~/src/chitin % here 'configure', :prefix => '/Users/ari/local'
  ari: ~/src/chitin % [make, make.install]

`here` is the method we use to refer to an executable file relative to the
current directory. We're using hash notation in configure to automatically
prepend two dashes to 'prefix' ('--prefix'); the whole line is translated
as "./configure --prefix /Users/ari/local". We're also using the #& operator
to do each command only if the previous one succeeds.

In lieu of using `here`, which can understandably get a little tedious, we
can do two other things:

  ari: ~/src/chitin % raw_exec 'configure', :prefix => '/Users/ari/local

And if you didn't want to include any arguments:

  ari: ~/src/chitin % '.'/'configure'

For the second one using String#/, it can't take any arguments because
otherwise it wouldn't be valid Ruby. I'm still trying to figure out a better
way to make use of it.

So now that we have this all, let's try archiving it all up again.

  ari: ~/src/chitin % tar.zcf 'compiled.tar.gz' => 'rosys_stuff'

Much better.

== Color Guard: Working with Flags

Here's a quick synopsis of flag equivalencies from bash-style flags to Chitin.
The first on the left is the bash, and everything else is Chitin:

  -h          == -:h == -'h' == '-h'
  -f file     == :f => file
  -help       == -:help ==  -'help' == '-help'
  --d         == --:h == --'h' == '--h'
  --pref file == :pref => file

You should be able to get the idea after that. The important things to note
are how hashes are treated. If the key is one character long, it gets one dash.
If the key is two or more characters long, it gets two dashes. This makes
dealing with Java programs really fucking annoying.

== String Methods

One of the points of using Ruby in a shell is that you can use Ruby. For
instance:

  ari: ~/src/chitin % ll | split("\n")

However, what if there's an executable on your path entitled "split"? Then
the executable would be the one to be run. However, I really, really, really
want to use a few String methods such as #gsub and #split regardless of whether
there is an executable named as such. So, there's the Kernel::string_method
function that takes a string method and makes it a PRIORITY method. This means
that the Ruby method will take precedence over any executables found.

The string methods which take precedence over executables are:

  gsub
  split
  size
  pack
  unpack

== Globbing: Globlins and Hobgloblins

Globbing is simple in Chitin: there is none. However, that's ok because
globbing is a really really cheap hack. So here's what Chitin offers in its
stead:

  ari: ~/src/chitin % D('.').map {|f| echo f } # D stands for Directory
  ari: ~/src/chitin % all.map {|f| echo f }    # all is an alias to D('.')
  ari: ~/src/chitin % echo *all                # Ruby's natural glob!

Now, all and D('.') will only get the files and directories in the current
directory. What if you wanted to see just how far the rabbit hole went?

  ari: ~/src/chitin % all.down.map {|f| echo f }
  ari: ~/src/chitin % echo *all.down

#down will go from the directory it's called on allllll the way down to
Davey Jones' locker.

== Kewl Features

Did you know?

  * A FULL RUBY INTERPRETER!
  * Tab completion that respects spaces in strings!
  * Bash's ESC-. to place the last argument!
  * Syntax highlighting as you type!
  * ^q quotes the word just behind your cursor
  * I think this train might ACTUALLY get to Chicago on time!

== Thanks

A huge thank you to Roger Pack. You know why.