Created
April 20, 2010 05:52
-
-
Save hayeah/372105 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubygems' | |
require 'rdiscount' | |
require 'nokogiri' | |
$blog = [:blog, | |
[[:timestamps, "2009-03-17T16:04:34-07:00"], | |
[:title, "Does Ruby Dream An Eclectic Shell?"], | |
[:categories, "hack"]], | |
[[:md, | |
[], | |
["# Does Ruby Dream An Eclectic Shell?\n\nWhenever I encounter something that sounds even slightly hard to do in Bash, I'd think: \"hmmm, how do I do this in Bash? Oh, I know, I'll use Ruby.\" So I never actually bothered to learn Bash. Better for my sanity.\n\nBut sometimes in the depth of night, contemplating my eventual existential end, I'd think to myself, hmmm, wouldn't it be nice to have a shell that's consistent, expressive, powerful, concise, and **FUN** to use?\n\nI was motivated by two thoughts:\n\n+ Ruby's syntax makes a **plausible** shell.\n+ Ruby itself makes a **compelling** shell.\n\nSo I wrote a Ruby shell called [Rubish](http://github.com/hayeah/rubish/tree/master). Rubish, like Ruby, **is object oriented** from the ground up, and it only uses Ruby syntax (**it has no metasyntax** of its own). And unlike Bash, Rubish is not rubbish. (Always fun to bash Bash).\n\nIn a series of articles, I'll write about Rubish itself, its design, and what potentials it could have (aside from being yet another attempt at programmatic shell that's merely _quaint_.).\n\nAlong the way, I'll have digressions about the internals of Rubish to demostrate metaprogramming techniques in Ruby. Feel free to skip these, but I hope they would stimulate your hacking muscles as they did mine. For example, what use is a class that has all its methods undefined (I wonder if Ruby classes have castration anxiety)?\n\n"]], | |
"\n\n", | |
[:ruby, | |
[], | |
["class Mu\n self.public_instance_methods.each do |m|\n self.send(:undef_method,m)\n end\nend"]], | |
"\n\n", | |
[:md, | |
[], | |
["For the rest of this article, I'd like to ask you to imagine how you'd design a Ruby shell yourself.\n\n#Now, Imagine!\n\nRuby's syntax is concise enough that in the degenerate case, it's like any other Unix shell.\n"]], | |
"\n\n", | |
[:ruby, [], ["> ls\n> ls :la # i.e. ls -la\n> ls \"-l *\""]], | |
"\n\n", | |
[:md, | |
[], | |
["Command evaluations are just method calls handled by the shell object. Of course, since we are `eval`ing, any Ruby expression is valid.\n"]], | |
"\n\n", | |
[:ruby, [], ["> 1+1\n2\n> \"abcd\" == \"dcba\"\nfalse"]], | |
"\n\n", | |
[:md, | |
[], | |
["Since it's Ruby, it follows that we can abstract unix commands as objects."]], | |
"\n\n", | |
[:ruby, | |
[], | |
["> @cmd = ls ; false\nfalse\n> @cmd.inspect\n\"<#Rubish::Executable @cmd=\"ls\">\""]], | |
"\n\n", | |
[:md, | |
[], | |
["Because commands are objects, it's easy to build extra functionalities for them with Ruby. All these extensions to unix commands are just be _ad hoc_ wrappers that munge lines from a pipe. So unlike [PowerShell(TM)](http://en.wikipedia.org/wiki/Powershell), Rubish assumes no specialized support from the underlying platform.\n\nYou could, for example, imagine a mixin for the `ls` command (I say \"imagine\", because this is not necessarily useful or even pretty. But it illustrates the possibilities).\n"]], | |
"\n\n", | |
[:ruby, | |
[], | |
["# we'll mix this into the an Executable instance\nmodule LsMixin\n def each_file(filter=nil)\n # Executable#each yields to a block each line\n # of the executable's output.\n self.each do |line|\n if filter.nil? or line =~ filter\n f = File.new(line)\n yield(f)\n end\n end\n end\nend"]], | |
"\n\n", | |
[:md, | |
[], | |
["Then you could extend `ls` so you yield to a block each line of its output as a Ruby File object,"]], | |
"\n\n", | |
[:ruby, | |
[], | |
["# use the filter to include only *.rb\n> ls.extend(LsMixin).each_file(/.rb$/) { |f|... }"]], | |
"\n\n", | |
[:md, | |
[], | |
["To summarize again,\n\n+ Ruby's concise syntax makes a **plausible** shell.\n+ Ruby itself makes a **compelling** shell.\n\nIn the next article, I'd like to give you a tour of Rubish. And before we go into Rubish itself, I'd love to know how *YOU* would design a Ruby shell given these ideas. So take a coffee break, and imagine :)\n\nCheck out these shells for inspirations:\n\n* [iPython](http://ipython.scipy.org/moin/) is in Python. It has fairly extensive metasyntax and magic to make it practical. Whereas for Rubish, I chose not to add any metasyntax, because Ruby's own is (arguably) good enough.\n\n* [rush](http://rush.heroku.com) is another ruby shell by [Adam Wiggins](http://adam.blog.heroku.com). I actually wanted to call my shell Rush. But he got there first. Rush is far more Rubyesque than Unixy. And it's more designed for handling multiple machines over network. Rubish tries to fuse better with Unix, but keeping with Ruby's spirit.\n\n* [scsh](http://www.scsh.net/) is a shell in scheme. Being a Lisp weenie, how can I not mention this? Scsh is the work of Olin Shivers, implemented on [Scheme48](http://groups.csail.mit.edu/mac/projects/s48/). Shivers' [acknowledgement](http://www.scsh.net/docu/html/man.html) is worth reading. I couldn't get scsh to work though.\n\n"]], | |
"\n\n\n"]] | |
class Curly | |
def initialize(parse) | |
@parse = parse | |
end | |
def generate | |
build_xml(@parse) | |
end | |
protected | |
def build_xml(element) | |
doc = Nokogiri::XML::Document.new | |
doc.add_child(build_node(doc,element)) | |
doc | |
end | |
def build_node(doc,element) | |
head, attributes, children = element | |
node = create_element(doc,head,attributes) | |
children.each { |child| | |
insert(node,child) | |
} | |
node | |
end | |
def create_element(doc,head,attributes) | |
attributes = attributes.inject({}) { |h,(k,v)| | |
h[k] = v; h | |
} | |
name, id, classes = parse_tag_name(head) | |
node = doc.create_element(name,attributes) | |
if id | |
node["id"] = id | |
end | |
if !classes.empty? | |
node["class"] = classes.join(" ") | |
end | |
node | |
end | |
def insert(node,element) | |
doc = node.document | |
case element | |
when String | |
node << doc.create_text_node(element) | |
when Array | |
head, attributes, body = element | |
case head | |
when %s(!) | |
# do nothing | |
when :cdata | |
node << Nokogiri::XML::CDATA.new(doc, body.to_s) | |
else | |
child = build_node(node.document,element) | |
node << child | |
end | |
end | |
end | |
def parse_tag_name(head) | |
head = head.to_s | |
id = nil | |
head.gsub!(/#([^.]*)/) { |match| | |
id = $1 | |
raise "empty id" if id.empty? | |
"" | |
} | |
tokens = head.split(".") | |
head = tokens.shift | |
classes = tokens | |
raise "empty head" if head.empty? | |
return [head,id,classes] | |
end | |
end | |
def dom | |
Curly.new($blog).generate | |
end | |
def markdown(text) | |
RDiscount.new(text).to_html | |
end | |
def process | |
div = dom | |
################################################## | |
# markdown | |
(div / "md").each do |node| | |
md = markdown(node.content) | |
frag = Nokogiri::XML::DocumentFragment.parse(md) | |
node.replace(frag) | |
end | |
end | |
loop { | |
process | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment