[QUIZ] Literate Ruby (#102)

Discussion in 'Ruby' started by Ruby Quiz, Nov 17, 2006.

  1. Ruby Quiz

    Ruby Quiz Guest

    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
     
    Ruby Quiz, Nov 17, 2006
    #1
    1. Advertising

  2. Ruby Quiz

    Ken Bloom Guest

    On Fri, 17 Nov 2006 22:56:55 +0900, Ruby Quiz wrote:
    > 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

    --
    Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
    Department of Computer Science. Illinois Institute of Technology.
    http://www.iit.edu/~kbloom1/
     
    Ken Bloom, Nov 17, 2006
    #2
    1. Advertising

  3. On Nov 17, 2006, at 10:25 AM, Ken Bloom wrote:

    > On Fri, 17 Nov 2006 22:56:55 +0900, Ruby Quiz wrote:
    >> 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
     
    James Edward Gray II, Nov 17, 2006
    #3
  4. Ruby Quiz

    Ken Bloom Guest

    On Fri, 17 Nov 2006 22:56:55 +0900, Ruby Quiz wrote:
    > 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}


    Suddenly, I'm reminded of this winner of the 2000 International Obfuscated
    C Code Contest:

    http://www0.us.ioccc.org/2000/tomx.c
    http://www0.us.ioccc.org/2000/tomx.hint

    --Ken

    --
    Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
    Department of Computer Science. Illinois Institute of Technology.
    http://www.iit.edu/~kbloom1/
     
    Ken Bloom, Nov 19, 2006
    #4
  5. #!/usr/bin/env ruby
    #
    # RLit (0.1)
    # Author: Louis J. Scoras <>
    # 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)
     
    Louis J Scoras, Nov 19, 2006
    #5
  6. --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 = 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

    > 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] += 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.

    > end


    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)--
     
    Vincent Fourmond, Nov 19, 2006
    #6
  7. Ruby Quiz

    Ken Bloom Guest

    Re: [QUIZ][SOLUTION] Literate Ruby (#102)

    On Fri, 17 Nov 2006 22:56:55 +0900, Ruby Quiz wrote:
    > "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



    --
    Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
    Department of Computer Science. Illinois Institute of Technology.
    http://www.iit.edu/~kbloom1/
     
    Ken Bloom, Nov 19, 2006
    #7
  8. Ruby Quiz

    Ken Bloom Guest

    Re: [QUIZ][SOLUTION] Literate Ruby (#102)

    On Fri, 17 Nov 2006 22:56:55 +0900, Ruby Quiz wrote:
    > "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

    --
    Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
    Department of Computer Science. Illinois Institute of Technology.
    http://www.iit.edu/~kbloom1/
     
    Ken Bloom, Nov 19, 2006
    #8
  9. Ruby Quiz

    Cameron Pope Guest

    Re: Literate Ruby (#102)

    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
     
    Cameron Pope, Nov 20, 2006
    #9
  10. Ruby Quiz

    Adam Shelly Guest

    On 11/17/06, Ruby Quiz <> wrote:
    > 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
     
    Adam Shelly, Nov 22, 2006
    #10
  11. On Nov 22, 2006, at 9:23 AM, Adam Shelly wrote:

    > On 11/17/06, Ruby Quiz <> wrote:
    >> 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.


    Very clever interpretation of the problem. Too cool.

    James Edward Gray II
     
    James Edward Gray II, Nov 22, 2006
    #11
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Mike Maxwell

    XMLMind and Literate Programming

    Mike Maxwell, Nov 2, 2004, in forum: XML
    Replies:
    2
    Views:
    365
    Mike Maxwell
    Nov 3, 2004
  2. How to literate a const char*

    , Jul 14, 2007, in forum: C Programming
    Replies:
    2
    Views:
    328
    Christopher Benson-Manica
    Jul 15, 2007
  3. Paul Miller

    Literate programs in Python

    Paul Miller, May 13, 2008, in forum: Python
    Replies:
    6
    Views:
    302
    Marc 'BlackJack' Rintsch
    May 14, 2008
  4. Hans Georg Schaathun

    Literate Programming

    Hans Georg Schaathun, Apr 7, 2011, in forum: Python
    Replies:
    10
    Views:
    482
    Tim Arnold
    Apr 11, 2011
  5. Ruby Quiz

    [SUMMARY] Literate Ruby (#102)

    Ruby Quiz, Nov 24, 2006, in forum: Ruby
    Replies:
    0
    Views:
    98
    Ruby Quiz
    Nov 24, 2006
Loading...

Share This Page