--Boundary_(ID_9gkU1IyKGTVurzshKHmIBg) Content-type: text/plain; charset=ISO-8859-1 Content-transfer-encoding: 7BIT Ruby Quiz wrote: > 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 alse) text ] code ] current ext 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 ode end text << line if include_code when E_o_CODE if current text current << line else text << line if include_code current ext end else current << line end end return [code, text] end # Unliterates a file def self.unliterate_file(file, include_code alse) return unliterate_lines(File.open(file).readlines, include_code) end # Runs the unliterated code def self.run_code(code, bnd OPLEVEL_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 :old_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 alse for path in ($:).map {|x| File.join(x, file + ".lrb") } if File.readable?(path) found rue 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 RGV.shift $0 ile 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 > p ARGV Who are we ?? > puts "Here is #{$0}" Then, just to show, we require the file required.lrb > require 'required' 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] + else letters[letter] 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. > class RWebBeautifier 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 ls @code_env ode_env @packages ackages 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 ext for line in lines case line when ESCAPE # Escaping current << $' when INLINE code << $' when B_o_CODE if current code current << line else current ode end when E_o_CODE if current text current << line else current ext 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 ile.sub(/(\.lrb)?$/, '.tex') out ile.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. > end Now, what is left is some wrapper call; we first create an instance of \verb|RWebBeautifier| > rweb WebBeautifier.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 ls @code_env ode_env @packages ackages 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 ext for line in lines case line when ESCAPE # Escaping current << $' when INLINE code << $' when B_o_CODE if current code current << line else current ode end when E_o_CODE if current text current << line else current ext 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 ile.sub(/(\.lrb)?$/, '.tex') out ile.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 WebBeautifier.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)--