-- n003KyEaXMD4XKovKt1
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
(Apologies if this turns out to be a dupe - I posted this about an hour
ago on the newsgroup and it doesn't seem to be showing up anywhere).
Here's my solution to this quiz. It's basically a non-too-efficient
regex-based solution wrapped in a combination command-line script and
library. The main file (lrb.rb) contains both the library and script
code, while the other three files make up some tests of the
functionality in lrb.rb.
To run the tests, use the lrb script:
$ ./lrb.rb test.lrb
If you like, lrb will output the code it's about to run before running
it if you enable ruby debugging:
$ ./lrb.rb test.lrb -d
The first argument has to be the filename. If the second argument is a
number, it's taken as the index of the code block to run. Following
arguments are passed to the ruby interpreter that replaces the one
running lrb. You can run individual tests using this, but you need to
manually require test/unit. E.g:
$ ./lrb.rb test.lrb 1 -rtest/unit
$ ./lrb.rb test.lrb 2 -rtest/unit
(the numbers are 0-based, but the tests have a 'require test/unit' line
at block 0 which does nothing on it's own). If you need to pass a number
as the first argument to your script, give the block number as -- .
You can also get a dump of the extracted code, rather than running it:
$ ./lrb.rb test.lrb --dump
When used as a library, lrb provides a literate version of eval
(lrb_eval) and an lrb-enabled require (lrb_require) that won't work with
rubygems :(. The library is not auto-required into the interpreter that
runs the lrb code.
It has it's warts (especially, no handling of piped-in code or data, no
nested \begin...\end blocks, and debugging can be a PITA) but generally
it seems to work and is pretty flexible.
Thanks for this quiz - literate programming is something I'm still
finding out about (mostly via Haskell, which I've just started looking
at properly) so this was an interesting quiz topic for me :)
--
Ross Bamford - rosco / roscopeco.REMOVE.co.uk
-- n003KyEaXMD4XKovKt1
Content-Disposition: attachment; filename=lrb.rb
Content-Type: application/x-ruby; name=lrb.rb
Content-Transfer-Encoding: 7bit
#!/usr/bin/env ruby
#
# Literate ruby.
# for Ruby Quiz #102
require 'tempfile'
MAIN_BINDING inding
module Lrb
LRB_RX (?:^\s*>(.*?)$) |
(?:[^\\]\\begin\{code\}
(.*?)
[^\\]\\end\{code\})/mxu
class << self
# Get individual blocks of code
def lrb_blocks(str)
str.scan(LRB_RX).map do |rb|
rb.compact.first.strip
end
end
# Get the whole code
def lrb_code(str)
lrb_blocks(str).join("\n")
end
def lrb_dump(str, blk_idx il)
get_lrb_code(str, blk_idx, false)
end
def lrb_exec(fn, args ], blk_idx il, debug DEBUG)
# Ruby will interpet the -d and set DEBUG in the new ruby
# that replaces this process. However, to get output printed
# now we need to check it manually.
code et_lrb_code(File.read(fn), blk_idx, debug)
# Use a TempFile. We don't get the auto-delete stuff (finalizer
# won't get run after we replace the process) but it's
# convenient for the auto naming, and we can delete it
# later, from the new interpreter.
#
# The added bonus is, if theres a parse error, the tempfile
# gets left behind which helps with debugging...
tf empfile.open(File.basename(fn), '.')
tf << "at_exit { File.unlink(__FILE__) }\n"
tf << code
tf.close(false) # finalizer will never run
exec('ruby', *(args << tf.path))
end
def lrb_eval(code, blk_idx il, fn il, debug DEBUG)
fn || (lrb_eval)'
eval(get_lrb_code(code, blk_idx, debug), MAIN_BINDING,
"#{fn} #{('(#' + blk_idx.to_s + ')') if blk_idx}")
end
def lrb_require(fn, blk_idx il, debug DEBUG)
unless $".include?(fn)
fn + .lrb' if File.extname(fn).empty?
unless File.exists?(fn + blk_idx.to_s) # nil.to_s "" so all good
rfn il
fn :.detect { |dir| File.exists?(rfn ile.join(dir,fn)) }
fn fn if fn
end
if fn
begin
code et_lrb_code(File.read(fn), blk_idx, debug)
rescue
raise LoadError, $!.message
end
eval(code, MAIN_BINDING,
"#{fn} #{('(#' + blk_idx.to_s + ')') if blk_idx}")
$" << fn + blk_idx.to_s
true
else
raise LoadError, "no such file to load - #{fn}"
end
else
false
end
end
private
def get_lrb_code(str, blk_idx il, debug alse)
if blk_idx
code b.lrb_blocks(str)[blk_idx] or raise "LRB: No block ##{blk_idx}"
else
code b.lrb_code(str) or raise "LRB: No code found"
end
if debug
$stderr.puts " ************ LRB: Will execute ************** "
$stderr.puts code
$stderr.puts " ***************** LRB: END ****************** "
end
code
end
end
end
unless ($NO_CORE_LRB || alse)
module Kernel
private
def lrb_exec(fn, args ], blk_idx il, debug DEBUG)
Lrb.lrb_exec(fn, args, blk_idx, debug)
end
def lrb_eval(code, blk_idx il, fn il, debug DEBUG)
Lrb.lrb_eval(code, blk_idx, fn, debug)
end
def lrb_require(fn, blk_idx il, debug DEBUG)
Lrb.lrb_require(fn, blk_idx, debug)
end
end
end
if $0 __FILE__
fn RGV.shift or raise ArgumentError,
"LRB: lrb [--dump | filename [block#] [ruby / program opts...]]"
if (poss_idx RGV.first).to_i.to_s poss_idx
blk_idx RGV.shift.to_i
elsif poss_idx '--'
ARGV.shift
end
if File.extname(fn) '.lrb'
if ARGV.include?('--dump')
puts Lrb.lrb_dump(File.read(fn), blk_idx)
else
lrb_exec(fn, ARGV, blk_idx, ARGV.include?('-d'))
end
else
exec('ruby', *(ARGV << fn))
end
end
-- n003KyEaXMD4XKovKt1
Content-Disposition: attachment; filename=quiz.lrb
Content-Type: text/plain; name=quiz.lrb; charset=utf-8
Content-Transfer-Encoding: 7bit
The three rules of Ruby Quiz:
1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.
2. Support Ruby Quiz by submitting ideas as often as you can:
http://www.rubyquiz.com/
3. Enjoy!
Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.
- - - - - - - - - - - - - - - - - - - -
by Justin Bailey
"Literate Programming"[1] is an idea popularized by Donald Knuth, where the
traditional order of code and comments in a source file is switched. Instead of
using special delimiters to mark comments, special delimiters are used to mark
*code*.
Innocuous as it sounds, this style of programming makes for a great way to post
code snippets, tutorials, or even whole libraries to mailing lists, blogs, and
web pages. It's also an excellent way to develop your CS homework
There are, of course, a variety of ways to make a source file "literate". One
popular method is called "bird notation". Code is delimited by lines starting
with ">":
> puts "The first line of literate Ruby you may have ever seen"
Another method, used in the Haskell language, is borrowed from LateX and makes
it very easy to embed working code into longer papers:
\begin{code}
puts "And here, we have"
puts "the second and third lines of literate Ruby to be produced."
\end{code}
Beyond *how* to represent literate code, a host of issues present themselves.
Can a class, method, or even string span multiple code sections? Can the
different styles of code demarcation be mixed in one file? How do you "escape"
code demarcation? What about inserting the output of code lines into the same
literate file?
Your task is to enable literate Ruby. What that means is up to you. Is literate
programming only available at the file level (e.g. only files ending in ".lrb"
are considered literate)? Or is literate programming supported with
eval/class_eval/module_eval? Would this enable embedded literated here (i.e. <<)
docs?
At the minimum, this quiz should be seen as a literate program, and your code
should be able to run it! [Editor's Note: The indention added to Ruby blocks in
this quiz are a side effect of the Ruby Quiz software. Feel free to remove them
when treating this quiz as literate code. --JEG2]
Justin
> puts "Here's to hoping you enjoyed the quiz!"
[1] http://en.wikipedia.org/wiki/Literate_programming
-- n003KyEaXMD4XKovKt1
Content-Disposition: attachment; filename=test.lrb
Content-Type: text/plain; name=test.lrb; charset=utf-8
Content-Transfer-Encoding: 7bit
This is a literate ruby test file. You can run the whole file
by simply running it through lrb, or individual tests by
supplying the block number to execute. In that case though,
you'll have to
> require 'test/unit'
yourself with a command like:
lrb test.lrb 2 -rtest/unit
Test numbers are 1-based (zero is that require 'test/unit'
above) You can have lrb show the code it's about to execute
using e.g:
lrb test.lrb -d
So, here's the first test.
\begin{code}
class TC_LRB < Test::Unit::TestCase
def test_01
assert true
end
end
\end{code}
We need to make sure we can \begin{code}class TC_LRB < Test::Unit::TestCase
def test_02
assert true
end
end
\end{code} anywhere on a line.
Also, we need to make sure we can escape \\begin{code} raise "Not code!"
\\end{code} both within and at start of a line.
Okay, let's just check that lines starting with > aren't treated as
single line blocks if they're inside a \begin...\end block.
\begin{code}
class TC_LRB < Test::Unit::TestCase
def test_03
s
> This is not a line of code
> And wouldn't compile on it's own
"
e n> This is not a line of code\n> And wouldn't compile on it's own\n"
assert_equal e, s.gsub(/\n\s*/, "\n")
end
end
\end{code}
Make sure lrb_require works properly:
\begin{code}
require 'lrb'
class TC_LRB < Test::Unit::TestCase
def test_04
assert_raise(NoMethodError) { meth_one() }
lrb_require('testreq', 0)
assert_equal 1, meth_one()
assert_raise(NoMethodError) { meth_two() }
lrb_require('testreq', 1)
assert_equal 2, meth_two()
end
end
\end{code}
Finally, make sure lrb_eval works.
\begin{code}
require 'lrb'
class TC_LRB < Test::Unit::TestCase
def test_06
lrb_require('testreq', 2)
# does it's own assertions
meth_three()
end
end
\end{code}
That's it. Now just run this test through lrb.
-- n003KyEaXMD4XKovKt1
Content-Disposition: attachment; filename=testreq.lrb
Content-Type: text/plain; name=testreq.lrb; charset=utf-8
Content-Transfer-Encoding: 7bit
Just test that lrb_require works
\begin{code}
class TC_LRB < Test::Unit::TestCase
def meth_one
1
end
end
\end{code}
\\begin{code} a red herring
\begin{code}
class TC_LRB < Test::Unit::TestCase
def meth_two
2
end
end
\end{code}
But we also need to make sure lrb_eval works too.
\begin{code}
require 'stringio'
class TC_LRB < Test::Unit::TestCase
def meth_three
test_block_0
test_block_1
test_block_2
end
def test_block_0
$stdout s tringIO.new)
lrb_eval(File.read('quiz.lrb'), 0)
assert_equal(
"The first line of literate Ruby you may have ever seen",
s.string.chomp)
$stdout TDOUT
end
def test_block_1
$stdout s tringIO.new)
lrb_eval(File.read('quiz.lrb'), 1)
assert_equal(
"And here, we have\nthe second and third lines of literate Ruby to be produced.",
s.string.chomp)
$stdout TDOUT
end
def test_block_2
$stdout s tringIO.new)
lrb_eval(File.read('quiz.lrb'), 2)
assert_equal "Here's to hoping you enjoyed the quiz!", s.string.chomp
$stdout TDOUT
end
end
\end{code}
The End.
-- n003KyEaXMD4XKovKt1--