require 'temple' class LiterateTest < Temple::Engine class Parser < Temple::Parser def call(lines) stack = [[:multi]] until lines.empty? case lines.shift when /\A(#+)\s*(.*)\Z/ stack.pop(stack.size - $1.size) block = [:multi] stack.last << [:section, $2, block] stack << block when /\A~{3,}\s*(\w+)\s*\Z/ lang = $1 code = [] until lines.empty? case lines.shift when /\A~{3,}\s*\Z/ break when /\A.*\Z/ code << $& end end stack.last << [lang.to_sym, code.join("\n")] when /\A\s*\Z/ when /\A\s*(.*?)\s*Z/ stack.last << [:comment, $1] end end stack.first end end class Compiler < Temple::Filter def call(exp) @opts, @in_testcase = {}, false "require 'helper'\n\n#{compile(exp)}" end def on_section(title, body) old_opts = @opts.dup raise Temple::FilterError, 'New section between slim and html block' if @in_testcase "describe #{title.inspect} do\n #{compile(body).gsub("\n", "\n ")}\nend\n" ensure @opts = old_opts end def on_multi(*exps) exps.map {|exp| compile(exp) }.join("\n") end def on_comment(text) "#{@in_testcase ? ' ' : ''}# #{text}" end def on_slim(code) raise Temple::FilterError, 'Slim block must be followed by html block' if @in_testcase @in_testcase = true "it 'should render' do\n slim = #{code.inspect}" end def on_html(code) raise Temple::FilterError, 'Html block must be preceded by slim block' unless @in_testcase @in_testcase = false result = " html = #{code.inspect}\n" if @opts.empty? result << " render(slim).must_equal html\nend\n" else result << " options = #{@opts.inspect}\n render(slim, options).must_equal html\nend\n" end end def on_options(code) raise Temple::FilterError, 'Options set inside test case' if @in_testcase @opts.update(eval("{#{code}}")) "# #{code.gsub("\n", "\n# ")}" end def on(*exp) raise Temple::InvalidExpression, exp end end use Parser use Compiler use(:Evaluator) {|code| eval(code) } end Dir.glob(File.join(File.dirname(__FILE__), '*.md')) do |file| LiterateTest.new.call(File.readlines(file)) end