[QUIZ] Literate Ruby (#102)

R

Ruby Quiz

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
 
K

Ken Bloom

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]

Does this mean I need to accept both the LaTeX syntax and the email-quoted
syntax to make this quiz run as a literate program? Or may I reorganize
the quiz to use my preferred code demarcation?

--Ken
 
J

James Edward Gray II

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]

Does this mean I need to accept both the LaTeX syntax and the email-
quoted
syntax to make this quiz run as a literate program? Or may I
reorganize
the quiz to use my preferred code demarcation?

I doubt I would do both. Would be great to have it configurable though.

James Edward Gray II
 
L

Louis J Scoras

#!/usr/bin/env ruby
#
# RLit (0.1)
# Author: Louis J. Scoras <[email protected]>
# Date: Sat Nov 18 23:29:29 EST 2006
#
# RLit -- a simple script for enabling literate programming in Ruby using erb
# and BlueCloth.
#
# It is licensed under the same terms as ruby.
#
# I think it's proper form to send the code for solutions in the body
of the mail, so
# I'm just sending the regular ruby code to the list. If you want to
see the literate
# version check out it out here:
#
# http://www.ljstech.net/articles/rlit.html
#
# This solution is a pretty simple one, but by leveraging erb and BlueCloth it
# makes getting an html version of the document really easy.
#

require 'rubygems'
require 'bluecloth'
require 'cgi'
require 'erb'
require 'optparse'

module RLit
class Processor
attr_reader :html, :code

def initialize(io, which_chunk)
corpus = io.read
@prog = ERB.new(corpus, nil, '%<>')
@chunks = Hash.new {|h,k| h[k] = ''}
@html, @code = do_html, do_code(which_chunk)
end

def chunk c
r,ch = nil,nil
c.each do |chunk, code|
@chunks[chunk] << code
r = code
ch = chunk
end
"<div class=\"caption\">:#{ch}</div>\n<pre><code>#{CGI.escapeHTML(r)}</code></pre>"
end

def ref c
t = ERB.new(@chunks[c])
t.result(binding)
end

def do_html
doc = @prog.result(binding)
'<link rel="stylesheet" href="style.css" type="text/css" />' +
BlueCloth.new(doc).to_html
end

def do_code(which_chunk)
return unless which_chunk
t = ERB.new(@chunks[which_chunk])
t.result(binding)
end

private :do_html, :do_code

end
end

def usage(opts)
puts opts; exit
end

method, arg = nil, nil

opts = OptionParser.new do |o|
o.banner = "Usage: #{File.basename $0} output_method FILE(s)"
o.separator ''
o.separator "output_method can be either"
o.on('-d','--documentation', 'Output the document') {
method = :html
}
o.on('-c','--code [CHUNK_NAME]', 'Output the code for the
interpreter') {|chunk|
arg = chunk
method = :code
}
o.separator ''
o.separator "Other options"
o.on('-h','--help', 'Print this help message') {
usage(o)
}
end

opts.parse!(ARGV)

unless method
puts "Invalid Arguments: Must specify an output method"
usage(opts)
end

p = RLit::processor.new(ARGF, arg && arg.to_sym) #chunk)
puts p.__send__(method)
 
V

Vincent Fourmond

--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 :eek: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 ??
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] += 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.
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 = 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)--
 
K

Ken Bloom

"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's my solution #1 which uses email style quoting. In my quest for
perfection, I figured out how to do this without polluting the execution
environment (from the perspective of the literate script) with any new
methods, constants, or (more amazingly) local variables. __FILE__ and
__LINE__ work as one would expect, returning the name and location in the
original literate file.

#!/usr/bin/env ruby

#the literate interpreter cannot be implemented as literate code itself
#for obvious reasons. A literate compiler could.

#I chose to use the line prefix "> " because many email clients have
#automatic "Add Quote Chars" functions which can add this to the
#beginning of each line, without affecting wrapping.
line_prefix=/^> /
#Read the code, and get the file name right
if ARGV[0]
filename=ARGV.shift
code=open(filename).readlines
else
code=STDIN.readlines
#this is how ruby itself identifies stdin when that's the source
#of its code
filename="-"
end
#process the code to strip the documentation, and the line prefix
code.map! do |line|
if line=~line_prefix
line.sub(line_prefix,"")
else
#we want __LINE__ to return the correct line number in the
#literate file when we evaluate the file so we don't delete
#documentation lines -- we just replace them with blank lines
"\n"
end
end

#the goal here is to have NO local variables or special
#methods introduced into the execution environment
def __ken_binding
self.class.class_eval {remove_method :__ken_binding}
binding
end

#evaluate, setting __FILE__ appropriately
eval code.join, __ken_binding , filename
 
K

Ken Bloom

"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 #2.

#!/usr/bin/env ruby

#This is a variation on my other solution. The same basic mechanisms
#are used for evaluation, but different demarcations are used.

#Code begins at \begin{ruby} or \begin{ruby}[codeword]
#Code ends at \end{ruby} or \end{ruby}[codeword], but only matching
#the original pattern. If a codeword was used to start the block, then
#the same codeword is required to end the block. If no codeword was used
#to start the block, then no codeword may be used at the end of the
#block.

#Of course, if literate blocks don't nest properly in LaTeX, that's
#beyond the scope of the Ruby Quiz ;-).

#Read the code, and get the file name right
if ARGV[0]
filename=ARGV.shift
code=open(filename).readlines
else
code=STDIN.readlines
#this is how ruby itself identifies stdin when that's the source
#of its code
filename="-"
end

#process the code to strip the documentation, and the demarcations
inblock=nil
code.map! do |line|

inblock=nil if inblock and line=~/^\\end\{ruby\}#{Regexp.escape(inblock)}$/

l=line
l="\n" if not inblock

if not inblock and line=~/^\\begin\{ruby\}(\[\w+\])?$/
if Regexp.last_match[1]
inblock=Regexp.last_match[1]
else
inblock=""
end
end

l
end

#the goal here is to have NO local variables or special
#methods introduced into the execution environment
def __ken_binding
self.class.class_eval {remove_method :__ken_binding}
binding
end

#evaluate, setting __FILE__ appropriately
eval code.join, __ken_binding , filename
 
C

Cameron Pope

It's amazing how complete some of these solutions are -- it's clear to
me that you could have a fully functional literate programming
environment in Ruby that supported HTML documentation, re-ordering of
code and fully functional require and evals in a few hundred lines of
code.

My solution doesn't do most of that, but it does generate html and text
documentation using BlueCloth. The code is posted below, the literate
version is here:

http://www.theaboutbox.com/code/lrb/lrb.html

Cheers!
-Cameron

----- lrb.rb
require 'rubygems'
require 'bluecloth'
class LRB
def parse(io, &block)
current_state = :in_text
io.each_line do |line|
if current_state == :in_text
case line
when /^>\s?(.*)/: yield :code, $1 + "\n" if block_given?
when /\\begin\{.*\}\s*.*/: current_state = :in_code
else yield :text, line if block_given?
end
else
case line
when /\\end\{.*\}\s*.*/: current_state = :in_text
else yield :code, line if block_given?
end
end
end
end
def self.to_code(io)
code = String.new
LRB.new.parse(io) do |type, line|
code << line if type == :code
end
return code
end
def self.to_markdown(io)
doc = String.new
LRB.new.parse(io) do |type, line|
case type
when :code: doc << " " << line
when :text: doc << line
end
end
return doc
end
def self.to_html(io)
markdown = self.to_markdown io
doc = BlueCloth::new markdown
doc.to_html
end
end # class LRB
if $0 == __FILE__
opt = ARGV.shift
file = ARGV.shift
case opt
when '-c': puts LRB::to_code(File.new(file))
when '-t': puts LRB::to_markdown(File.new(file))
when '-h': puts LRB::to_html(File.new(file))
when '-e': eval LRB::to_code(File.new(file))
else
usage = <<"ENDING"
Usage:
lrb.rb [option] [file]

Options:
-c: extract code
-t: extract text documentation
-h: extract html documentation
-e: evaluate as Ruby program
ENDING
puts usage
end
end
 
A

Adam Shelly

Your task is to enable literate Ruby. What that means is up to you.

So I enabled literate programming in "Junebug":www.junebugwiki.com, a
small wiki based on
"Camping":http://code.whytheluckystiff.net/camping/wiki.

Redcloth provides all the markup capability, and any code on a page
can be executed in place, or generated to a plain .rb source file.

The modifications were minimal and are described here as a literate program=
:

First, add 2 buttons to the normal page view. In module
Junebug::Views, modify the method #show. Existing code:

module Junebug::Views
def show
#...
_markup @version.body

+Add+ These two lines:

_button 'raw source', R(Raw, @page.title, @version.version),
{:style=3D>'float: left; margin: 5px 0 0 5px;'}
_button 'exec', R(Exec, @page.title, @version.version),
{:style=3D>'float: right; margin: 5px 0 0 5px;'} if logged_in?
#...
end
end

To implement exec, the next step is a controller. The controller
fetches the page data, sets the appropriate version, and calls the
render function. It handles older versions of the page, allowing you
to go back and run prior versions of the code.

module Junebug::Controllers
class Exec < R '/([\w ]+)/exec', '/([\w ]+)/(\d+)/exec'
def get page_name, version =3D nil
redirect("#{Junebug.config['url']}/login") and return unless logged=
_in?
@page_title =3D "Exec #{page_name}: Results"
@page =3D Page.find_by_title(page_name)
@version =3D (version.nil? or version =3D=3D @page.version.to_s) ?
@page : @page.versions.find_by_version(version)
render :exec
end
end

The raw source button links to a controller for the raw view. It
starts out the same, except there is no redirect to the login page,
since there is no login required.

class Raw < R '/([\w ]+)\raw', '/([\w ]+)/(\d+)/raw'
def get page_name, version =3D nil
@page_title =3D "#{page_name}.rb"
@page =3D Page.find_by_title(page_name)
@version =3D (version.nil? or version =3D=3D @page.version.to_s) ?
@page : @page.versions.find_by_version(version)

But the end is different - instead of calling render, which would wrap
the results in HTML tags, it just returns the raw code directly.

=09@headers['Content-Type'] =3D "text/plain"
=09get_code(@version.body)
end
end
end #module Junebug::Controllers

Next, the exec render function, back among the views:

module Junebug::Views
def exec
_header :show, @page.title
_body {
_markup @version.body
div.formbox {
form do
p {
label 'Execution Results'

Here is the only interesting line. 'execute' is a new helper function
that will extract the code blocks from the markup, and execute the
code.

textarea
_execute(get_code(@version.body,[@page.title])), :rows =3D> 10, :cols =3D>
80
}
br
end
br :clear=3D>'all'
}
_button 'edit', R(Edit, @page.title, @version.version),
{:style=3D>'float: right; margin: 5px 0 0 5px;'} if logged_in? &&
(@version.version =3D=3D @page.version && (! @page.readonly || is_admin?))
_button 'view', R(Show, @page.title, @version.version),
{:style=3D>'float: right; margin: 5px 0 0 5px;'}
br
}
_footer {
text '<b>[readonly]</b> ' if @page.readonly
span.actions {
text "Version #{@version.version} "
text "(current) " if @version.version =3D=3D @page.version
a '=C2=ABolder', :href =3D> R(Exec, @page.title,
@version.version-1) unless @version.version =3D=3D 1
a 'newer=C2=BB', :href =3D> R(Exec, @page.title,
@version.version+1) unless @version.version =3D=3D @page.version
a 'current', :href =3D> R(Exec, @page.title) unless
@version.version =3D=3D @page.version
a 'versions', :href =3D> R(Versions, @page.title)
}
}
end

Here's the function that actually runs the code. It is a wrapper for
the FreakyFreaky Sandbox, which should protect our system from
evildoers. _Note: This part is untested, since Sandbox does not
currently compile on Windows. For internal testing, the body can be
replaced with "eval(code)"._

def _execute code
begin
Sandbox.safe.eval(code)
rescue Sandbox::Exception =3D> show_ex
end
end
end #module Junebug::Views

The only thing left is one helper function to take the page source and
extract anything in a code block.

module Junebug::Helpers
def get_code html,found_list

Here's the extractor. Instead of reparsing the page, let RedCloth do
the work of identifying code:

html =3D RedCloth.new(source).to_html
code =3D html.scan(/$lt;code>.*?<\/code>/m).join("\n").gsub(/<\/?code=
/,'')

And here's a tricky bit. If we find a require statement, first search
the wiki for a page matching that name...

code.gsub!(/require\s*(["'])(.*?)\1/){|match|
name =3D $+
codepage =3D Junebug::Models::page.find_by_title(name)
if !found_list.include?(name) and codepage

and if we find it, replace the statement with the code from that page.
This allows us to put our project over multiple wiki pages. The
found_list tracks the replacements, to prevent us from getting into
require loops. If there is no matching page, the require statement is
left in place for ruby to handle normally.

get_code codepage.body, found_list<< name
end

In a poor use of overloading, a nil found_list argument is a signal
not to do the require expansion. The raw view uses this -trick-
feature. The last step is to unescape any HTML in the code, so that
Ruby gets '>' instead of =3D=3D'&gt;'=3D=3D

} if found_list
GGI.unescapeHTML code
end
end

And that's it.

I ran into a few small issues with RedCloth, which might be a matter
of getting the right settings. My main problem was that some of the
markup gets inserted before the code sections are identified, so
various code symbols, like *'s and +'s are replaced by html tags.

-Adam
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,898
Latest member
BlairH7607

Latest Threads

Top