Permalink
Browse files

persist with mongodb; better homepage, styles

  • Loading branch information...
1 parent d51ba1a commit 94eb7bae94ab2293741ad999789825913944debb @mislav committed Aug 22, 2010
Showing with 253 additions and 78 deletions.
  1. +3 −0 Gemfile
  2. +8 −0 Gemfile.lock
  3. +26 −14 app.rb
  4. +80 −13 code.rb
  5. +27 −2 processor.rb
  6. +23 −4 templates/home.mustache
  7. +2 −2 templates/layout.mustache
  8. +1 −1 templates/rocco.mustache
  9. +1 −1 views/_dawn.sass
  10. +6 −5 views/docco.sass
  11. +76 −0 views/explain.sass
  12. +0 −36 views/style.sass
View
3 Gemfile
@@ -8,6 +8,9 @@ gem 'ruby2ruby'
gem 'rocco'
gem 'nokogiri'
gem 'ultraviolet'
+gem 'mongo'
+gem 'mongo_ext'
+gem 'bson_ext'
group :development do
gem 'mongrel'
View
8 Gemfile.lock
@@ -1,11 +1,16 @@
GEM
remote: http://rubygems.org/
specs:
+ bson (1.0.4)
+ bson_ext (1.0.4)
cgi_multipart_eof_fix (2.5.0)
daemons (1.1.0)
fastthread (1.0.7)
gem_plugin (0.2.3)
haml (3.0.16)
+ mongo (1.0.7)
+ bson (>= 1.0.4)
+ mongo_ext (0.19.3)
mongrel (1.1.5)
cgi_multipart_eof_fix (>= 2.4)
daemons (>= 1.0.3)
@@ -42,7 +47,10 @@ PLATFORMS
ruby
DEPENDENCIES
+ bson_ext
haml
+ mongo
+ mongo_ext
mongrel
mustache
nokogiri
View
40 app.rb
@@ -2,6 +2,13 @@
require 'sass'
require 'pp'
require 'code'
+require 'mongo'
+
+configure :development do
+ connection = Mongo::Connection.from_uri 'mongodb://localhost'
+ db = connection.db('explainruby')
+ ExplainRuby::Code.mongo = db.collection('results')
+end
require 'mustache/sinatra'
set :mustache, { :templates => './templates', :views => './views' }
@@ -13,15 +20,8 @@ def email_link(email)
"<a href='mailto:#{email}'>#{email}</a>"
end
- def link_to(object)
- case object
- when Project
- "<a href='#{project_path(object)}'>#{object.name}</a>"
- when Company
- "<a href='#{company_path(object)}'>#{object.name}</a>"
- else
- raise ArgumentError
- end
+ def redirect_to(code)
+ redirect "/#{code.slug}"
end
def image_tag(file, attributes = {})
@@ -69,13 +69,18 @@ def sass_with_caching(name)
mustache :home
end
+get '/url/*:url' do
+ code = ExplainRuby::Code.from_url params[:url]
+ redirect_to code
+end
+
post '/' do
if not params[:url].empty?
code = ExplainRuby::Code.from_url params[:url]
- rocco { code.to_s }
+ redirect_to code
elsif not params[:code].empty?
- code = ExplainRuby::Code.new params[:code]
- rocco { code.to_s }
+ code = ExplainRuby::Code.create params[:code]
+ redirect_to code
else
status "400 Not Chunky"
@message = "Please paste some code or enter a URL"
@@ -94,10 +99,17 @@ def sass_with_caching(name)
code.pretty_inspect
end
-get '/chunky.css' do
- sass_with_caching :style
+get '/explain.css' do
+ sass_with_caching :explain
end
get '/docco.css' do
sass_with_caching :docco
end
+
+get %r!^/([a-z0-9]{3,})$! do
+ code = ExplainRuby::Code.find params[:captures][0]
+ halt 404 unless code
+ etag code.md5
+ rocco { code.to_s }
+end
View
93 code.rb
@@ -1,6 +1,7 @@
require 'nokogiri'
require 'ruby_parser'
require 'processor'
+require 'digest/md5'
require 'net/http'
# TODO: SSL support
# require 'net/https'
@@ -14,7 +15,7 @@ def http?
module ExplainRuby
module FromUrl
def from_url(url)
- code = if raw_url = get_raw_url(url)
+ if raw_url = get_raw_url(url)
get_raw_body(raw_url)
else
response = get_http_response(url)
@@ -24,8 +25,6 @@ def from_url(url)
response.body
end
end
-
- new(code, raw_url || url)
end
def extract_code_from_html(doc)
@@ -75,15 +74,83 @@ def get_raw_url(url)
class Code
extend FromUrl
- def initialize(code, url = nil)
- raise ArgumentError, "no code given" if code.nil? or code.empty?
- @code = code.to_s
- @url = url.nil?? nil : url.to_s
+ attr_reader :attributes
+
+ def initialize(attributes)
+ @attributes = attributes
@reconstructed_code = nil
@explained_code = nil
@sexp = nil
end
+ def [](key)
+ @attributes[key.to_s]
+ end
+
+ def []=(key, value)
+ @attributes[key.to_s] = value
+ end
+
+ def slug() self['slug'] end
+ def md5() self['md5'] end
+ def url() self['url'] end
+
+ class << self
+ attr_accessor :mongo
+ end
+
+ def self.from_url(url)
+ find_or('url' => url) do |params|
+ create(super) { |obj| obj.attributes.update(params) }
+ end
+ end
+
+ def self.create(code)
+ md5 = md5_digest code
+
+ find_or('md5' => md5) do |params|
+ obj = new({'code' => code}.update(params))
+ yield obj if block_given?
+ obj.save
+ end
+ end
+
+ def self.find_or(query)
+ if record = mongo.find_one(query, :fields => 'slug')
+ new(record)
+ else
+ yield query
+ end
+ end
+
+ def self.exists?(query)
+ !!mongo.find_one(query, :fields => [])
+ end
+
+ def self.md5_digest(code)
+ Digest::MD5.hexdigest code.strip
+ end
+
+ def self.find(slug)
+ record = mongo.find_one(:slug => slug) and new(record)
+ end
+
+ SEED = ('a'..'z').to_a
+
+ def self.generate_slug
+ (1..3).map { SEED[rand(SEED.length)] }.join('').tap do |slug|
+ slug << SEED[rand(SEED.length)] while exists?(:slug => slug)
+ end
+ end
+
+ def save
+ self['md5'] ||= self.class.md5_digest(self['code'])
+ self['slug'] ||= self.class.generate_slug
+ self['created_at'] ||= Time.now
+ self.class.mongo.save(@attributes, :safe => true)
+ self
+ end
+
def to_s
@explained_code ||= process
end
@@ -93,7 +160,7 @@ def process
end
def parse
- @sexp ||= self.class.ruby2sexp(@code, @url)
+ @sexp ||= self.class.ruby2sexp(self['code'], self['url'])
end
# delegate pretty printing to sexp
@@ -102,7 +169,7 @@ def pretty_print(io)
end
def reconstruct_code
- @reconstructed_code ||= self.class.ruby2ruby(parse, @url)
+ @reconstructed_code ||= self.class.ruby2ruby(parse, self['url'])
end
EXPLANATIONS_PATH = File.expand_path('../explanations', __FILE__)
@@ -138,7 +205,7 @@ def self.ruby2sexp(io, filename = io.path)
def self.from_test_fixture(name)
File.open(FIXTURES_PATH + "/#{name}.rb") do |file|
- new(file.read, file.path)
+ new('code' => file.read, 'url' => file.path)
end
end
end
@@ -200,18 +267,18 @@ def self.from_test_fixture(name)
describe "#reconstruct_code" do
it "inserts explanation markers" do
- code = described_class.new("class Klass < Main; end")
+ code = described_class.new('code' => "class Klass < Main; end")
code.reconstruct_code.should == ">> class class_inheritance\nclass Klass < Main\nend"
end
it "doesn't insert same marker twice" do
- code = described_class.new("def foo() 1 end; def bar() 2 end")
+ code = described_class.new('code' => "def foo() 1 end; def bar() 2 end")
code.reconstruct_code.should == ">> method\ndef foo\n 1\nend\n\ndef bar\n 2\nend\n"
end
end
it "delegates pretty printing to sexp" do
- code = described_class.new("class Klass; end")
+ code = described_class.new('code' => "class Klass; end")
code.pretty_inspect.should == "s(:class, :Klass, nil, s(:scope))\n"
end
end
View
29 processor.rb
@@ -18,13 +18,26 @@ def mark(*explanations)
@also.clear
explanations.reject! { |e| @explained.include? e }
- if explanations.any?
+ if explanations.any? and context_ok?
@explained += explanations
">> #{explanations.join(' ')}\n"
- else
+ elsif not in_args?
+ # "\n#{context.inspect}\n"
"\n"
+ else
+ ""
end
end
+
+ def context_ok?
+ ( context[1] == :block or
+ context[1, 2] == [:scope, :sclass] ) and
+ not in_args?
+ end
+
+ def in_args?
+ context.include? :args
+ end
def process_block(exp)
super.sub(/\A\s*\n/, '')
@@ -83,6 +96,18 @@ def process_cdecl(exp)
mark(:constant) + super
end
+ def process_lasgn(exp)
+ mark(:variable_local) + super
+ end
+
+ def process_iasgn(exp)
+ mark(:variable_instance) + super
+ end
+
+ def process_cvdecl(exp)
+ mark(:variable_class) + super
+ end
+
CALLS = [:require, :attr_accessor, :attr_reader, :attr_writer, :include, :extend]
def process_call(exp)
View
27 templates/home.mustache
@@ -1,12 +1,31 @@
-<h1><span class="chunky">Explain</span> <span class="bacon">Ruby</span></h1>
+<h1>{{ title }}</h1>
-<p>
- Paste code or a URL ➝ get Ruby syntax explained
-</p>
+<div id="primer">
+ <p>
+ Paste Ruby code or a URL to get Ruby syntax explained.
+ </p>
+
+ <p>
+ <i>Explain Ruby</i> will not be able to explain what the code in question <em>does</em>, i.e. the original author's intent, but it will help newcomers to Ruby to learn how to read code by teaching them programming constructs of the Ruby programming language.
+ </p>
+
+ <p>
+ This is still in experimental phase. Some syntaxes are not yet documented, and there might be formatting bugs in the output.
+ </p>
+
+ <p>
+ Special thanks to Ryan Tomayko for <a href="http://rtomayko.github.com/rocco/">Rocco</a> which is used for final formatting of results on this site.
+ </p>
+
+ <p>
+ Made by <a href="http://twitter.com/mislav">@mislav</a> <strong>in memory of Why The Lucky Stiff</strong>.
+ </p>
+</div>
<form action="/" method="post">
<p><label>Ruby code: <textarea name="code"></textarea></label></p>
<p><label>Alternatively, paste a URL: <input type="url" name="url"></label></p>
+ <p class="note">URL can point to a Gist, Pastie, file on GitHub, a HTML page with code on it, or directly to a Ruby script.</p>
<div class="actions">
<input type="submit" value="Explain">
</div>
View
4 templates/layout.mustache
@@ -2,10 +2,10 @@
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>{{ title }}</title>
- <link rel="stylesheet" href="/chunky.css" type="text/css">
+ <link rel="stylesheet" href="/explain.css" type="text/css">
</head>
-<body class="{{ highlight_style }}">
+<body id="home" class="{{ highlight_style }}">
<div id='container'>
<div id="background"></div>
{{{ yield }}}
View
2 templates/rocco.mustache
@@ -3,7 +3,7 @@
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>{{ title }}</title>
- <link rel="stylesheet" href="/docco.css">
+ <link rel="stylesheet" href="/explain.css" type="text/css">
</head>
<body class="{{ highlight_style }}">
<div id="container">
View
2 views/_dawn.sass
@@ -67,7 +67,7 @@ $dawn_background: desaturate(#f5f5ff, 30%)
.StringRegexp
color: #CF5628
.StringEmbeddedSource
- background-color: #829AC2
+ // background-color: #829AC2
color: #080808
.MarkupLink
color: #234A97
View
11 views/docco.sass
@@ -1,6 +1,7 @@
/* yanked from: http://jashkenas.github.com/docco/resources/docco.css
$width: 575px
+$middle_margin: 50px
body
font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif
@@ -81,10 +82,10 @@ table td
outline: 0
td.docs, th.docs
- max-width: $width - (25 + 50)
- min-width: $width - (25 + 50)
+ max-width: $width - (25 + $middle_margin)
+ min-width: $width - (25 + $middle_margin)
min-height: 5px
- padding: 10px 25px 1px 50px
+ padding: 10px 25px 1px $middle_margin
vertical-align: top
text-align: left
@@ -119,12 +120,12 @@ td
&.docs:hover .octothorpe
opacity: 1
&.code
- padding: 14px 15px 16px 50px
+ padding: 14px 15px 16px $middle_margin
width: 100%
vertical-align: top
th.code
- padding: 14px 15px 16px 50px
+ padding: 14px 15px 16px $middle_margin
width: 100%
vertical-align: top
View
76 views/explain.sass
@@ -0,0 +1,76 @@
+@import docco
+
+=clearing
+ &:after
+ content: " "
+ visibility: hidden
+ height: 0
+ font-size: 0
+ display: block
+ clear: both
+ * html &
+ height: 1px
+ *:first-child + html &
+ overflow: hidden
+
+#home h1
+ margin-left: $middle_margin
+ margin-bottom: 1.2em
+
+#primer
+ margin: 0 25px 0 $middle_margin
+ float: left
+ width: $width - (25 + $middle_margin)
+
+form
+ float: left
+ margin-left: 20px
+ width: 960px - $width - 20px
+ textarea, input[type=url]
+ display: block
+ border: 1px solid silver
+ padding: 4px 6px
+ width: 100%
+ textarea
+ height: 10em
+ font: 11px Monaco, monospace
+ input[type=url]
+ font-size: 15px
+ p.note
+ margin-top: -.5em
+ font-size: 85%
+ font-style: italic
+ color: gray
+
+.docs
+ h2
+ font-size: 1.2em
+ margin-top: 0
+ margin-bottom: .3em
+ code
+ font-size: 80%
+ &:before
+ content: "Also here: "
+ color: gray
+ font-size: 90%
+ font-weight: normal
+ margin-right: .2em
+ .octowrap + h2:before
+ content: ""
+ p code
+ font-size: 80%
+ pre
+ font-size: 14px !important
+ p
+ a:link, a:active
+ color: darkblue !important
+ a:visited
+ color: inherit
+th.docs
+ h1 + p
+ color: gray
+ font: 12px Helvetica, sans-serif
+td.docs
+ padding-bottom: 1em !important
+td.code, th.code
+ padding-left: $middle_margin / 2
View
36 views/style.sass
@@ -1,36 +0,0 @@
-@import docco
-
-=clearing
- &:after
- content: " "
- visibility: hidden
- height: 0
- font-size: 0
- display: block
- clear: both
- * html &
- height: 1px
- *:first-child + html &
- overflow: hidden
-
-h1
- +clearing
- .chunky
- display: block
- width: $width - 10
- padding-right: 10px
- text-align: right
- float: left
- .bacon
- display: block
- float: left
- width: 3em
- padding-left: 10px
-
-form
- margin-left: $width + 10px
- textarea, input[type=url]
- display: block
- width: 30em
- textarea
- height: 5em

0 comments on commit 94eb7ba

Please sign in to comment.