--Boundary_(ID_9gkU1IyKGTVurzshKHmIBg)
Content-type: text/plain; charset=ISO-8859-1
Content-transfer-encoding: 7BIT
Ruby said:
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*.
Here is my solution. It features basically both methods to mark that
were proposed (with > end \begin{code} \end{code}). It should be able
to run as is irb output (practical to test directly from an email) and
it features a small hack to require literate ruby files.
There are five attached files:
* rweb.rb, the actual interpreter;
* small_test.lrb and required.lrb, a test file and the file included
from it (to demonstrate the require feature);
* rweb2tex.lrb, a literate program converting a literate program into
"appropriately" formatted LaTeX file (that definitely could be improved)
* rweb2tex.tex,the result of rweb2tex.lrb ran on itself. (I personally
don't like its look so much, but, well, I don't like literate
programming so much anyway ;-)...)
Hope you appreciate it !
Vince
--
Vincent Fourmond, PhD student
http://vincent.fourmond.neuf.fr/
--Boundary_(ID_9gkU1IyKGTVurzshKHmIBg)
Content-type: text/plain; name=rweb.rb
Content-transfer-encoding: 7BIT
Content-disposition: inline; filename=rweb.rb
#!/usr/bin/ruby
module RWeb
# Escapes a whole line if it starts with this regular expression: the
# rest of the line is fed as is to the current output (text or code)
# without interpretation.
ESCAPE = /^\s*@@/
# Inline code
INLINE = /^\s*>+/
# Beginning of a code block
B_o_CODE = /^\s*\\begin\{code\}\s*$/
# End of a code block
E_o_CODE = /^\s*\\end\{code\}\s*$/
# Takes an array of lines, and returns code lines and text lines
# separately, optionnally including code in text
def self.unliterate_lines(lines, include_code = false)
text = []
code = []
current = text
for line in lines
case line
when ESCAPE # Escaping
current << $'
when INLINE
code << $'
text << $' if include_code
when B_o_CODE
if current == code
current << line
else
current = code
end
text << line if include_code
when E_o_CODE
if current == text
current << line
else
text << line if include_code
current = text
end
else
current << line
end
end
return [code, text]
end
# Unliterates a file
def self.unliterate_file(file, include_code = false)
return unliterate_lines(File.open(file).readlines, include_code)
end
# Runs the unliterated code
def self.run_code(code, bnd = TOPLEVEL_BINDING)
eval(code.join, bnd)
end
# Runs a file.
def self.run_file(file)
run_code(unliterate_file(file).first)
end
end
# Here, we hack our way through require so that we can include
# .lrb files and understand them as literate ruby.
module Kernel
alias
ld_kernel_require :require
undef :require
def require(file)
# if file doesn't have an extension, we look for it
# as a .lrb file.
if file =~ /\.[^\/]*$/
old_kernel_require(file)
else
found = false
for path in ($
.map {|x| File.join(x, file + ".lrb") }
if File.readable?(path)
found = true
RWeb::run_code(RWeb::unliterate_file(path).first,
self.send
binding))
break
end
end
old_kernel_require(file) unless found
end
end
end
# We remove the first element of ARGV so that the script believes
# it is called on its own
file = ARGV.shift
$0 = file
RWeb::run_file(file)
--Boundary_(ID_9gkU1IyKGTVurzshKHmIBg)
Content-type: text/plain; name=small_test.lrb
Content-transfer-encoding: 7BIT
Content-disposition: inline; filename=small_test.lrb
This file contains a small test for the literate ruby
quiz.
It is meant to be run from the command line with some arguments.
Here, we simply begin with displaying the command-line arguments
Who are we ??
Then, just to show, we require the file required.lrb
We complain if there is not arguments on the command-line
puts "It will be more interesting if "+
"you actually provide #$0 with command-line arguments" if ARGV.empty?
Then, I thought it would be interesting to make a small report about
letters used in the command-line arguments:
\begin{code}
letters = {}
for letter in ARGV.join('').split('')
if letters.has_key?(letter)
letters[letter] += 1
else
letters[letter] = 1
end
end
for letter in letters.keys.sort
puts "Letter #{letter} used #{letters[letter]} times"
end
\end{code}
And just for fun, we will show that we can use \end{code}
right in the middle of some code:
\begin{code}
puts <<'EOT'
You see that we can use
@@\end{code}
even alone on its line !!
EOT
\end{code}
I believe this should suffice as a demonstration.
--Boundary_(ID_9gkU1IyKGTVurzshKHmIBg)
Content-type: text/plain; name=required.lrb
Content-transfer-encoding: 7BIT
Content-disposition: inline; filename=required.lrb
Just a test file to show that code can be required fine
with rweb:
\begin{code}
puts "This is required literate code !"
\end{code}
--Boundary_(ID_9gkU1IyKGTVurzshKHmIBg)
Content-type: text/plain; name=rweb2tex.lrb
Content-transfer-encoding: 7BIT
Content-disposition: inline; filename=rweb2tex.lrb
Now that we have a \verb|rweb.rb| file that does the correct job
of executing the literate Ruby code given, the next step in literate
programming is to actually provide a nice display of the program.
\verb|rweb2text.rb| converts the text and the code of a literate Ruby
program into (hopefully) nicely formatted LaTeX.
The \verb|RWebBeautifier| is the main class.
Now, we copy the regular expressions to parse the literate programs
straight from \verb|rweb|
\begin{code}
# Escapes a whole line if it starts with this regular expression: the
# rest of the line is fed as is to the current output (text or code)
# without interpretation.
ESCAPE = /^\s*@@/
# Inline code
INLINE = /^\s*>+/
# Beginning of a code block
B_o_CODE = /^\s*\\begin\{code\}\s*$/
# End of a code block
E_o_CODE = /^\s*\\end\{code\}\s*$/
\end{code}
Initialization; as I don't provide many hooks, this is rather simple:
\verb|cls| is the document class, \verb|code_env| is the name of
the environment used to display code and \verb|packages|
a list of packages to be included.
\begin{code}
def initialize(cls = 'article',
code_env = 'verbatim',
packages = ['verbatim'])
@document_class = cls
@code_env = code_env
@packages = packages
end
\end{code}
The \verb|literate_lines| function is a rewrite of
\verb|unliterate_lines| from \verb|rweb.rb| --- a rewrite is indeed
needed as special formatting is required for included code, and
we don't really care about getting only code.
\begin{code}
def literate_lines(lines)
text = []
code = []
\end{code}
This time, \verb|code| holds a different meaning: it is the current code
block, not all the code read so far.
\begin{code}
current = text
for line in lines
case line
when ESCAPE # Escaping
current << $'
when INLINE
code << $'
when B_o_CODE
if current == code
current << line
else
current = code
end
when E_o_CODE
if current == text
current << line
else
current = text
end
else
\end{code}
Now come the real difference: if we are in text mode, we need to flush first
the code which hasn't been written yet.
\begin{code}
if (current == text) and (not code.empty?)
current << "\\begin{#{@code_env}}\n"
\end{code}
Here, I had first coded using a simple \verb|+=|, but that miserably
fails to work, because after it, \verb|current| is neither \verb|text|
nor \verb|code|, and the code is lost. The solution is
\begin{code}
current.concat(code)
current << "\\end{#{@code_env}}\n"
code.clear
end
current << line
end
end
return text
end
\end{code}
Now, a simple function that wraps the appropriate
\verb|literate_lines| call for a file. I find it self-explanatory.
\begin{code}
def literate_file(file)
output_file = file.sub(/(\.lrb)?$/, '.tex')
out = File.open(output_file, 'w')
out.puts "\\documentclass{#{@document_class}}"
@packages.each do |p|
out.puts "\\usepackage{#{p}}"
end
out.puts "\\begin{document}"
out.puts(literate_lines(File.open(file).readlines))
out.puts "\\end{document}"
out.close
end
\end{code}
The end of the class.
Now, what is left is some wrapper call; we first create an instance of
\verb|RWebBeautifier|
rweb = RWebBeautifier.new
And we use it on all command-line arguments:
ARGV.each do |file|
rweb.literate_file(file)
end
And that's all !
--Boundary_(ID_9gkU1IyKGTVurzshKHmIBg)
Content-type: application/x-tex; name=rweb2tex.tex
Content-transfer-encoding: 7bit
Content-disposition: inline; filename=rweb2tex.tex
\documentclass{article}
\usepackage{verbatim}
\begin{document}
Now that we have a \verb|rweb.rb| file that does the correct job
of executing the literate Ruby code given, the next step in literate
programming is to actually provide a nice display of the program.
\verb|rweb2text.rb| converts the text and the code of a literate Ruby
program into (hopefully) nicely formatted LaTeX.
The \verb|RWebBeautifier| is the main class.
\begin{verbatim}
class RWebBeautifier
\end{verbatim}
Now, we copy the regular expressions to parse the literate programs
straight from \verb|rweb|
\begin{verbatim}
# Escapes a whole line if it starts with this regular expression: the
# rest of the line is fed as is to the current output (text or code)
# without interpretation.
ESCAPE = /^\s*@@/
# Inline code
INLINE = /^\s*>+/
# Beginning of a code block
B_o_CODE = /^\s*\\begin\{code\}\s*$/
# End of a code block
E_o_CODE = /^\s*\\end\{code\}\s*$/
\end{verbatim}
Initialization; as I don't provide many hooks, this is rather simple:
\verb|cls| is the document class, \verb|code_env| is the name of
the environment used to display code and \verb|packages|
a list of packages to be included.
\begin{verbatim}
def initialize(cls = 'article',
code_env = 'verbatim',
packages = ['verbatim'])
@document_class = cls
@code_env = code_env
@packages = packages
end
\end{verbatim}
The \verb|literate_lines| function is a rewrite of
\verb|unliterate_lines| from \verb|rweb.rb| --- a rewrite is indeed
needed as special formatting is required for included code, and
we don't really care about getting only code.
\begin{verbatim}
def literate_lines(lines)
text = []
code = []
\end{verbatim}
This time, \verb|code| holds a different meaning: it is the current code
block, not all the code read so far.
\begin{verbatim}
current = text
for line in lines
case line
when ESCAPE # Escaping
current << $'
when INLINE
code << $'
when B_o_CODE
if current == code
current << line
else
current = code
end
when E_o_CODE
if current == text
current << line
else
current = text
end
else
\end{verbatim}
Now come the real difference: if we are in text mode, we need to flush first
the code which hasn't been written yet.
\begin{verbatim}
if (current == text) and (not code.empty?)
current << "\\begin{#{@code_env}}\n"
\end{verbatim}
Here, I had first coded using a simple \verb|+=|, but that miserably
fails to work, because after it, \verb|current| is neither \verb|text|
nor \verb|code|, and the code is lost. The solution is
\begin{verbatim}
current.concat(code)
current << "\\end{#{@code_env}}\n"
code.clear
end
current << line
end
end
return text
end
\end{verbatim}
Now, a simple function that wraps the appropriate
\verb|literate_lines| call for a file. I find it self-explanatory.
\begin{verbatim}
def literate_file(file)
output_file = file.sub(/(\.lrb)?$/, '.tex')
out = File.open(output_file, 'w')
out.puts "\\documentclass{#{@document_class}}"
@packages.each do |p|
out.puts "\\usepackage{#{p}}"
end
out.puts "\\begin{document}"
out.puts(literate_lines(File.open(file).readlines))
out.puts "\\end{document}"
out.close
end
\end{verbatim}
The end of the class.
\begin{verbatim}
end
\end{verbatim}
Now, what is left is some wrapper call; we first create an instance of
\verb|RWebBeautifier|
\begin{verbatim}
rweb = RWebBeautifier.new
\end{verbatim}
And we use it on all command-line arguments:
\begin{verbatim}
ARGV.each do |file|
rweb.literate_file(file)
end
\end{verbatim}
And that's all !
\end{document}
--Boundary_(ID_9gkU1IyKGTVurzshKHmIBg)--