Permalink
Browse files

* tools/rakehelp.rb: Rake convenience methods ripped from mongrel.

 * lib/markaby: documentation throughout.
 * Rakefile: moving away from the gemspec to a Rakefile.
  • Loading branch information...
1 parent 933403d commit ca39d2f276262cc7b2be3b5adfdf31f7e873f6f1 _why committed Feb 2, 2006
Showing with 414 additions and 23 deletions.
  1. +17 −0 CHANGELOG
  2. +162 −10 README
  3. +15 −0 Rakefile
  4. +19 −1 lib/markaby.rb
  5. +88 −11 lib/markaby/builder.rb
  6. +9 −0 lib/markaby/cssproxy.rb
  7. +6 −1 lib/markaby/helper.rb
  8. +98 −0 tools/rakehelp.rb
View
17 CHANGELOG
@@ -0,0 +1,17 @@
+= 0.3
+=== 02nd February, 2006
+
+* Allow Markaby::Builder.new without args.
+* Rails helper method render_markaby.
+
+= 0.2
+=== 17th January, 2006
+
+* Public announcement.
+* DOCTYPES, head tags.
+* Works with Rails helpers.
+
+= 0.1
+=== 05th January, 2006
+
+* Initial import.
View
172 README
@@ -1,7 +1,12 @@
+= Markaby (Markup as Ruby)
- |- Markaby (Markup as Ruby) -|
+Markaby is a very short bit of code for writing HTML pages in pure Ruby.
+It is an alternative to ERb which weaves the two languages together.
+Also a replacement for templating languages which use primitive languages
+that blend with HTML.
+
+== Using Markaby as a Rails plugin
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Write Rails templates in pure Ruby. Example layout:
html do
@@ -17,21 +22,168 @@ Write Rails templates in pure Ruby. Example layout:
end
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Output defaults to XHTML 1.0 Transitional. To do XHTML 1.0 Strict,
-try this:
+== Using Markaby as a Ruby class
- xhtml_strict do
- # innerds
+Markaby is flaming easy to call from your Ruby classes.
+
+ require 'markaby'
+
+ mab = Markaby::Builder.new
+ mab.html do
+ head { title "Boats.com" }
+ body do
+ h1 "Boats.com has great deals"
+ ul do
+ li "$49 for a canoe"
+ li "$39 for a raft"
+ li "$29 for a huge boot that floats and can fit 5 people"
+ end
+ end
+ end
+ puts mab.to_s
+
+Markaby::Builder.new does take two arguments for passing in variables and
+a helper object. You can also affix the block right on to the class.
+
+See Markaby::Builder for all of that.
+
+= A Note About <tt>instance_eval</tt>
+
+The Markaby::Builder class is different from the normal Builder class,
+since it uses <tt>instance_eval</tt> when running blocks. This cleans
+up the appearance of the Markaby code you write. If <tt>instance_eval</tt>
+was not used, the code would look like this:
+
+ mab = Markaby::Builder.new
+ mab.html do
+ mab.head { mab.title "Boats.com" }
+ mab.body do
+ mab.h1 "Boats.com has great deals"
+ end
+ end
+ puts mab.to_s
+
+So, the advantage is the cleanliness of your code. The disadvantage is that
+the block will run inside the Markaby::Builder object's scope. This means
+that inside these blocks, <tt>self</tt> will be your Markaby::Builder object.
+When you use instance variables in these blocks, they will be instance variables
+of the Markaby::Builder object.
+
+This doesn't effect Rails users, but when used in regular Ruby code, it can
+be a bit disorienting. You are recommended to put your Markaby code in a
+module where it won't mix with anything.
+
+= A Note About Rails Helpers
+
+When used in Rails templates, the Rails helper object is passed into
+Markaby::Builder. When you call helper methods inside Markaby, the output
+from those methods will be output to the stream. This is incredibly
+handy, since most Rails helpers output HTML tags.
+
+ head do
+ javascript_include_tag 'prototype'
+ autodiscovery_link_tag
end
-Classes may be added by hooking methods onto container elements:
+However, some methods are designed to give back a String which you can use
+elsewhere. Call the <tt>@helpers</tt> object with the method and you'll get
+the String back and nothing will be output.
+
+ p "Total is: #{@helper.number_to_human_size @file_bytes}"
+
+Conversely, you may call instance variables from your controller by using
+a method and its value will be returned, nothing will be output.
+
+ # Inside imaginary ProductController
+ def list
+ @products = Product.find :all
+ end
+
+ # Inside app/views/product/list.mab
+ products.each do |product|
+ p product.title
+ end
+
+= A Quick Tour
+
+If you dive right into Markaby, it'll probably make good sense, but you're
+likely to run into a few kinks. Keep these pointers in mind and everything
+will be fine.
+
+== Element Classes
+
+Element classes may be added by hooking methods onto container elements:
div.entry do
h2.entryTitle 'Son of WebPage'
div.entrySection %{by Anthony}
div.entryContent 'Okay, once again, the idea here is ...'
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Markaby by Tim Fletcher and why the lucky stiff
+Which results in:
+
+ <div class="entry">
+ <h2 class="entryTitle">Son of WebPage</h2>
+ <div class="entrySection">by Anthony</div>
+ <div class="entryContent">Okay, once again, the idea here is ...</div>
+ </div>
+
+== Element IDs
+
+IDs may be added by the use of bang methods:
+
+ div.page!
+ div.content!
+ h1 "A Short Short Saintly Dog"
+ end
+ end
+
+Which results in:
+
+ <div id="page">
+ <div id="content">
+ <h1>A Short Short Saintly Dog</h1>
+ </div>
+ </div>
+
+== Markaby assumes XHTML 1.0 Transitional
+
+Output defaults to XHTML 1.0 Transitional. To do XHTML 1.0 Strict,
+try this:
+
+ xhtml_strict do
+ # innerds
+ end
+
+== The <tt>capture</tt> Method
+
+Want to catch a block of HTML as a string and play with it a bit?
+Use the <tt>capture</tt> method.
+
+Commonly used to join HTML blocks together:
+
+ div.menu! \
+ ['5.gets', 'bits', 'cult', 'inspect', '-h'].map do |category|
+ capture { link_to category }
+ end.
+ join( " | " )
+
+== The <tt>tag!</tt> Method
+
+If you need to force a tag at any time, call <tt>tag!</tt> with the
+tag name followed by the possible arguments and block. The CssProxy
+won't work with this technique.
+
+ tag! :select, :id => "country_list" do
+ countries.each do |country|
+ tag! :option, country
+ end
+ end
+
+= Credits
+
+Markaby is a work of immense hope by Tim Fletcher and why the lucky stiff.
+Thankyou for giving it a whirl.
+
+Markaby is inspired by the HTML library within cgi.rb. Hopefully it will
+turn around and take some cues.
View
15 Rakefile
@@ -0,0 +1,15 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/clean'
+require 'rake/gempackagetask'
+require 'rake/rdoctask'
+require 'tools/rakehelp'
+require 'fileutils'
+include FileUtils
+
+setup_tests
+setup_rdoc ['README', 'CHANGELOG', 'lib/*.rb']
+
+summary = "Markup as Ruby, write HTML in your native Ruby tongue"
+test_file = "test/test_markaby.rb"
+setup_gem("markaby", "0.2.2", "Tim Fletcher and _why", summary, [], test_file)
View
20 lib/markaby.rb
@@ -1,7 +1,25 @@
+# = About lib/markany.rb
+#
+# By requiring <tt>lib/markaby</tt>, you can load Markaby's dependency (the Builder library,)
+# as well as the full set of Markaby classes.
+#
+# For a full list of features and instructions, see the README.
$:.unshift File.expand_path(File.dirname(__FILE__))
+# Markaby is a module containing all of the great Markaby classes that
+# do such an excellent job.
+#
+# * Markaby::Builder: the class for actually calling the Ruby methods
+# which write the HTML.
+# * Markaby::CSSProxy: a class which adds element classes and IDs to
+# elements when used within Markaby::Builder.
+# * Markaby::MetAid: metaprogramming helper methods.
+# * Markaby::Tags: lists the roles of various XHTML tags to help Builder
+# use these tags as they are intended.
+# * Markaby::Template: a class for hooking Markaby into Rails as a
+# proper templating language.
module Markaby
- VERSION = '0.2'
+ VERSION = '0.2.2'
end
unless defined?(Builder)
View
99 lib/markaby/builder.rb
@@ -1,8 +1,40 @@
module Markaby
+ # The Markaby::Builder class is the central gear in the system. When using
+ # from Ruby code, this is the only class you need to instantiate directly.
+ #
+ # mab = Markaby::Builder.new
+ # mab.html do
+ # head { title "Boats.com" }
+ # body do
+ # h1 "Boats.com has great deals"
+ # ul do
+ # li "$49 for a canoe"
+ # li "$39 for a raft"
+ # li "$29 for a huge boot that floats and can fit 5 people"
+ # end
+ # end
+ # end
+ # puts mab.to_s
+ #
class Builder
attr_accessor :output_helpers
+ # Create a Markaby builder object. Pass in a hash of variable assignments to
+ # +assigns+ which will be available as instance variables inside tag construction
+ # blocks. If an object is passed in to +helpers+, its methods will be available
+ # from those same blocks.
+ #
+ # Pass in a +block+ to new and the block will be evaluated.
+ #
+ # mab = Markaby::Builder.new {
+ # html do
+ # body do
+ # h1 "Matching Mole"
+ # end
+ # end
+ # }
+ #
def initialize(assigns = {}, helpers = nil, &block)
@stream = []
@assigns = assigns
@@ -33,16 +65,24 @@ def initialize(assigns = {}, helpers = nil, &block)
end
end
+ # Returns a string containing the HTML stream. Internally, the stream is stored as an Array.
def to_s
@builder.target!.join
end
+ # Write a +string+ to the HTML stream without escaping it.
def text(string)
@builder << "#{string}"
nil
end
alias_method :<<, :text
+ # Captures the HTML code built inside the +block+. This is done by creating a new
+ # builder object, running the block and passing back its stream as a string.
+ #
+ # >> Markaby::Builder.new.capture { h1 "TEST"; h2 "CAPTURE ME" }
+ # => "<h1>TITLE</h1>\n<h2>CAPTURE ME</h2>\n"
+ #
def capture(&block)
assigns = instance_variables.inject({}) do |hsh, iv|
unless ['@stream', '@builder', '@assigns', '@helpers'].include?(iv)
@@ -53,10 +93,25 @@ def capture(&block)
self.class.new(assigns, @helpers, &block).to_s
end
+ # Content_for will store the given block in an instance variable for later use
+ # in another template or in the layout.
+ #
+ # The name of the instance variable is content_for_<name> to stay consistent
+ # with @content_for_layout which is used by ActionView's layouts.
+ #
+ # Example:
+ #
+ # content_for("header") do
+ # h1 "Half Shark and Half Lion"
+ # end
+ #
+ # If used several times, the variable will contain all the parts concatenated.
def content_for(name, &block)
eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
end
+ # Create a tag named +tag+. Other than the first argument which is the tag name,
+ # the arguments are the same as the tags implemented via method_missing.
def tag!(tag, *args, &block)
if block
str = capture &block
@@ -65,27 +120,42 @@ def tag!(tag, *args, &block)
@builder.method_missing(tag, *args, &block)
end
- def method_missing(tag, *args, &block)
+ # Create XML markup based on the name of the method +sym+. This method is never
+ # invoked directly, but is called for each markup method in the markup block.
+ #
+ # This method is also used to intercept calls to helper methods and instance
+ # variables. Here is the order of interception:
+ #
+ # * If +sym+ is a recognized HTML tag, the tag is output
+ # or a CssProxy is returned if no arguments are given.
+ # * If +sym+ appears to be a self-closing tag, its block
+ # is ignored, thus outputting a valid self-closing tag.
+ # * If +sym+ is also the name of an instance variable, the
+ # value of the instance variable is returned.
+ # * If +sym+ is a helper method, the helper method is called
+ # and output to the stream.
+ # * Otherwise, +sym+ and its arguments are passed to tag!
+ def method_missing(sym, *args, &block)
args.each do |arg|
@stream.delete_if { |x| x.object_id == arg.object_id }
end
- if TAGS.include?(tag)
+ if TAGS.include?(sym)
if args.empty? and block.nil?
return CssProxy.new do |args, block|
- tag!(tag, *args, &block)
+ tag!(sym, *args, &block)
end
end
- tag!(tag, *args, &block)
- elsif SELF_CLOSING_TAGS.include?(tag)
- tag!(tag, *args)
- elsif instance_variable_get("@#{tag}")
- instance_variable_get("@#{tag}")
- elsif @helpers.respond_to?(tag)
- r = @helpers.send(tag, *args, &block)
+ tag!(sym, *args, &block)
+ elsif SELF_CLOSING_TAGS.include?(sym)
+ tag!(sym, *args)
+ elsif instance_variable_get("@#{sym}")
+ instance_variable_get("@#{sym}")
+ elsif @helpers.respond_to?(sym)
+ r = @helpers.send(sym, *args, &block)
@builder << r if @output_helpers
r
else
- tag!(tag, *args, &block)
+ tag!(sym, *args, &block)
end
end
@@ -95,17 +165,23 @@ def p(*args, &block)
@@default_image_tag_options ||= { :border => '0', :alt => '' }
+ # Builds a image tag. Assumes <tt>:border => '0', :alt => ''</tt>.
def img(opts = {})
tag!(:img, @@default_image_tag_options.merge(opts))
end
+ # Builds a head tag. Adds a <tt>meta</tt> tag inside with Content-Type
+ # set to <tt>text/html; charset=utf-8</tt>.
def head(*args, &block)
tag!(:head, *args) do
tag!(:meta, 'http-equiv' => 'Content-Type', 'content' => 'text/html; charset=utf-8')
instance_eval &block
end
end
+ # Builds an html tag. An XML 1.0 instruction and an XHTML 1.0 Transitional doctype
+ # are prepended. Also assumes <tt>:xmlns => "http://www.w3.org/1999/xhtml",
+ # "xml:lang" => "en", :lang => "en"</tt>.
def html(*args, &block)
if args.empty?
args = ["-//W3C//DTD XHTML 1.0 Transitional//EN", "DTD/xhtml1-transitional.dtd"]
@@ -117,6 +193,7 @@ def html(*args, &block)
end
alias_method :xhtml_transitional, :html
+ # Builds an html tag with XHTML 1.0 Strict doctype instead.
def xhtml_strict(&block)
html("-//W3C//DTD XHTML 1.0 Strict//EN", "DTD/xhtml1-strict.dtd", &block)
end
View
9 lib/markaby/cssproxy.rb
@@ -1,11 +1,20 @@
module Markaby
+ # Class used by Markaby::Builder to store element options. Methods called
+ # against the CssProxy object are added as element classes or IDs.
+ #
+ # See the README for examples.
class CssProxy
+ # Creates a CssProxy object. The +opts+ and +block+ passed in are
+ # stored until the element is created by Builder.tag!
def initialize(opts = {}, &blk)
@opts = opts
@blk = blk
end
+ # Adds attributes to an element. Bang methods set the :id attribute.
+ # Other methods add to the :class attribute. If a block is supplied,
+ # it is executed with a merged hash (@opts + args).
def method_missing(id_or_class, *args, &blk)
idc = id_or_class.to_s
case idc
View
7 lib/markaby/helper.rb
@@ -1,9 +1,14 @@
module Markaby
+ # Markaby helpers for Rails.
module ActionControllerHelper
+ # Returns a string of HTML built from the attached +block+. Any +options+ are
+ # passed into the render method.
+ #
+ # Use this method in your controllers to output Markaby directly from inside.
def render_markaby(options = {}, &block)
render options.merge({
:text => Builder.new(nil, self, &block).to_s
})
end
end
-end
+end
View
98 tools/rakehelp.rb
@@ -0,0 +1,98 @@
+
+def make(makedir)
+ Dir.chdir(makedir) do
+ sh 'make'
+ end
+end
+
+
+def extconf(dir)
+ Dir.chdir(dir) do ruby "extconf.rb" end
+end
+
+
+def setup_tests
+ Rake::TestTask.new do |t|
+ t.libs << "test"
+ t.test_files = FileList['test/test*.rb']
+ t.verbose = true
+ end
+end
+
+
+def setup_clean otherfiles
+ files = ['build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log'] + otherfiles
+ CLEAN.include(files)
+end
+
+
+def setup_rdoc files
+ Rake::RDocTask.new do |rdoc|
+ rdoc.rdoc_dir = 'doc/rdoc'
+ rdoc.options << '--line-numbers'
+ rdoc.rdoc_files.add(files)
+ end
+end
+
+
+def setup_extension(dir, extension)
+ ext = "ext/#{dir}"
+ ext_so = "#{ext}/#{extension}.#{Config::CONFIG['DLEXT']}"
+ ext_files = FileList[
+ "#{ext}/*.c",
+ "#{ext}/*.h",
+ "#{ext}/extconf.rb",
+ "#{ext}/Makefile",
+ "lib"
+ ]
+
+ task "lib" do
+ directory "lib"
+ end
+
+ desc "Builds just the #{extension} extension"
+ task extension.to_sym => ["#{ext}/Makefile", ext_so ]
+
+ file "#{ext}/Makefile" => ["#{ext}/extconf.rb"] do
+ extconf "#{ext}"
+ end
+
+ file ext_so => ext_files do
+ make "#{ext}"
+ cp ext_so, "lib"
+ end
+end
+
+
+def setup_gem(pkg_name, pkg_version, author, summary, files, test_file)
+ pkg_version = pkg_version
+ pkg_name = pkg_name
+ pkg_file_name = "#{pkg_name}-#{pkg_version}"
+
+ spec = Gem::Specification.new do |s|
+ s.name = pkg_name
+ s.version = pkg_version
+ s.platform = Gem::Platform::RUBY
+ s.author = author
+ s.summary = summary
+ s.test_file = test_file
+ s.has_rdoc = true
+ s.extra_rdoc_files = [ "README" ]
+
+ s.files = %w(README Rakefile setup.rb) +
+ Dir.glob("{bin,doc,test,lib}/**/*") +
+ Dir.glob("ext/**/*.{h,c,rb}") +
+ Dir.glob("examples/**/*.rb") +
+ Dir.glob("tools/*.rb")
+
+ s.require_path = "lib"
+ s.extensions = FileList["ext/**/extconf.rb"].to_a
+
+ s.bindir = "bin"
+ end
+
+ Rake::GemPackageTask.new(spec) do |p|
+ p.gem_spec = spec
+ p.need_tar = true
+ end
+end

0 comments on commit ca39d2f

Please sign in to comment.