wunderbar 0.8.5 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Manifest +3 -1
- data/README +0 -1
- data/demo/wiki.html +69 -0
- data/demo/wiki.rb +200 -0
- data/lib/wunderbar/builder.rb +129 -87
- data/lib/wunderbar/cgi-methods.rb +10 -15
- data/lib/wunderbar/environment.rb +0 -1
- data/lib/wunderbar/html-methods.rb +24 -66
- data/lib/wunderbar/version.rb +1 -1
- data/test/test_builder.rb +18 -6
- data/test/test_html_markup.rb +33 -2
- data/tools/web2script.rb +213 -0
- data/wunderbar.gemspec +3 -3
- metadata +8 -5
    
        data/Manifest
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 1 | 
             
            COPYING
         | 
| 2 2 | 
             
            README
         | 
| 3 3 | 
             
            Rakefile
         | 
| 4 | 
            +
            demo/wiki.html
         | 
| 5 | 
            +
            demo/wiki.rb
         | 
| 4 6 | 
             
            lib/wunderbar.rb
         | 
| 5 7 | 
             
            lib/wunderbar/builder.rb
         | 
| 6 8 | 
             
            lib/wunderbar/cgi-methods.rb
         | 
| @@ -13,5 +15,5 @@ lib/wunderbar/version.rb | |
| 13 15 | 
             
            test/test_builder.rb
         | 
| 14 16 | 
             
            test/test_html_markup.rb
         | 
| 15 17 | 
             
            test/test_logger.rb
         | 
| 16 | 
            -
             | 
| 18 | 
            +
            tools/web2script.rb
         | 
| 17 19 | 
             
            Manifest
         | 
    
        data/README
    CHANGED
    
    | @@ -47,7 +47,6 @@ number of other convenience methods are defined: | |
| 47 47 | 
             
            * $cgi   - Common Gateway Interface
         | 
| 48 48 | 
             
            * $param - Access to parameters (read-only OpenStruct like interface)
         | 
| 49 49 | 
             
            * $env   - Access to environment variables (read-only OpenStruct like interface)
         | 
| 50 | 
            -
            * $x     - XmlBuilder instance
         | 
| 51 50 | 
             
            * $USER  - Host user id
         | 
| 52 51 | 
             
            * $HOME  - Home directory
         | 
| 53 52 | 
             
            * $SERVER- Server name
         | 
    
        data/demo/wiki.html
    ADDED
    
    | @@ -0,0 +1,69 @@ | |
| 1 | 
            +
            <!DOCTYPE html>
         | 
| 2 | 
            +
            <html xmlns="http://www.w3.org/1999/xhtml">
         | 
| 3 | 
            +
              <head>
         | 
| 4 | 
            +
                <meta charset="utf-8"/>
         | 
| 5 | 
            +
                <title>Wunderbar Wiki</title>
         | 
| 6 | 
            +
              </head>
         | 
| 7 | 
            +
              <body>
         | 
| 8 | 
            +
            <h2>Wunderbar Wiki</h2>
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            <p>Note: demo code requires Ruby 1.9.2 or later.  Wunderbar itself works with 
         | 
| 11 | 
            +
            Ruby 1.8.7 or later.</p>
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            <p>Prereqs:</p>
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            <ul>
         | 
| 16 | 
            +
            <li><code>gem install wunderbar rdiscount</code></li>
         | 
| 17 | 
            +
            <li><code>apt-get install git</code></li>
         | 
| 18 | 
            +
            <li><a href="http://jquery.com/">jQuery</a></li>
         | 
| 19 | 
            +
            <li><a href="http://www.showdown.im/">Showdown</a></li>
         | 
| 20 | 
            +
            </ul>
         | 
| 21 | 
            +
             | 
| 22 | 
            +
             | 
| 23 | 
            +
            <p>Create a data directory (one per wiki):</p>
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            <ul>
         | 
| 26 | 
            +
            <li>Must be writeable by your web server</li>
         | 
| 27 | 
            +
            <li>Should not be in the Document Root</li>
         | 
| 28 | 
            +
            </ul>
         | 
| 29 | 
            +
             | 
| 30 | 
            +
             | 
| 31 | 
            +
            <p>Configure git:</p>
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            <ul>
         | 
| 34 | 
            +
            <li>Follow <a
         | 
| 35 | 
            +
            href="http://help.github.com/set-your-user-name-email-and-github-token/]">Step
         | 
| 36 | 
            +
            1</a>.</li>
         | 
| 37 | 
            +
            <li>Note that this needs to be done for the user that the webserver runs the
         | 
| 38 | 
            +
            script under.  If you are not running <a
         | 
| 39 | 
            +
            href="http://httpd.apache.org/docs/2.0/suexec.html">suexec</a>, this is
         | 
| 40 | 
            +
            typically <code>www-data</code> on Unix or <code>www</code> on Mac OS/X.</li>
         | 
| 41 | 
            +
            </ul>
         | 
| 42 | 
            +
             | 
| 43 | 
            +
             | 
| 44 | 
            +
            <p>Configure Apache:</p>
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            <pre><code>    Options +ExecCGI +MultiViews
         | 
| 47 | 
            +
                MultiViewsMatch Any
         | 
| 48 | 
            +
                AddHandler cgi-script .cgi
         | 
| 49 | 
            +
            </code></pre>
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            <p>Install scripts:</p>
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            <ul>
         | 
| 54 | 
            +
            <li>Copy <code>jquery-min.js</code> and <code>showdown.js</code> into your
         | 
| 55 | 
            +
            document root</li>
         | 
| 56 | 
            +
            </ul>
         | 
| 57 | 
            +
             | 
| 58 | 
            +
             | 
| 59 | 
            +
            <p>Install wiki (one per wiki):</p>
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            <pre><code>    ruby wiki.rb --install=/var/www/wikiname.cgi --WIKIDATA="/full/path/to/data/directory"
         | 
| 62 | 
            +
            </code></pre>
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            <p>Access your wiki:</p>
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            <pre><code>    http://localhost/wikiname/
         | 
| 67 | 
            +
            </code>
         | 
| 68 | 
            +
            </body>
         | 
| 69 | 
            +
            </html>
         | 
    
        data/demo/wiki.rb
    ADDED
    
    | @@ -0,0 +1,200 @@ | |
| 1 | 
            +
            #!/usr/bin/ruby
         | 
| 2 | 
            +
            require 'wunderbar'
         | 
| 3 | 
            +
            require 'rdiscount'
         | 
| 4 | 
            +
            require 'shellwords'
         | 
| 5 | 
            +
            require 'digest/md5'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Dir.chdir WIKIDATA
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # parse request
         | 
| 10 | 
            +
            %r{/(?<file>.\w+)((?<flag>/)(?<rev>\w*))?$} =~ $env.PATH_INFO
         | 
| 11 | 
            +
            flag ||= '?' if $env.REQUEST_URI.include? '?'
         | 
| 12 | 
            +
            file ||= 'index'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            Wunderbar.html do
         | 
| 15 | 
            +
              _head do
         | 
| 16 | 
            +
                _title file
         | 
| 17 | 
            +
                _style %{
         | 
| 18 | 
            +
                  body {background-color: #{(flag=='?') ? '#E0D8D8' : '#FFF'}}
         | 
| 19 | 
            +
                  .status {border: 2px solid #000; border-radius: 1em; background: #FAFAD2; padding: 0.5em}
         | 
| 20 | 
            +
                  .input, .output {border: 1px solid #888; position: relative; width: 47.5%; height: 400px; overflow: auto}
         | 
| 21 | 
            +
                  .input {float: left; left: 1.5%}
         | 
| 22 | 
            +
                  .output {float: right; right: 1.5%; background-color: #6C6666; color: #FFF}
         | 
| 23 | 
            +
                  .buttons {clear: both; text-align: center; padding-top: 0.5em}
         | 
| 24 | 
            +
                  .message {position: fixed; left: 2%; color: #9400d3}
         | 
| 25 | 
            +
                  h1 {text-align: center; margin: 0}
         | 
| 26 | 
            +
                  form {clear: both}
         | 
| 27 | 
            +
                  .buttons form {display: inline}
         | 
| 28 | 
            +
                  ._stdin:before {content: '$ '}
         | 
| 29 | 
            +
                  ._stdin {color: #9400D3; margin-bottom: 0}
         | 
| 30 | 
            +
                  ._stdout {margin: 0}
         | 
| 31 | 
            +
                  ._stderr {margin: 0; color: red}
         | 
| 32 | 
            +
                }
         | 
| 33 | 
            +
                _script src: '/showdown.js'
         | 
| 34 | 
            +
                _script src: '/jquery.min.js'
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              _body? do
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                # determine markup
         | 
| 40 | 
            +
                if _.post? and @markup
         | 
| 41 | 
            +
                  _header class: 'status' do
         | 
| 42 | 
            +
                    _h1 'Status'
         | 
| 43 | 
            +
                    if File.exist?(file) and Digest::MD5.hexdigest(File.read(file)) != @hash
         | 
| 44 | 
            +
                      _p 'Write conflict'
         | 
| 45 | 
            +
                    else
         | 
| 46 | 
            +
                      File.open(file, 'w') {|fh| fh.write @markup}
         | 
| 47 | 
            +
                      _.system 'git init' unless Dir.exist? '.git'
         | 
| 48 | 
            +
                      if `git status --porcelain #{file}`.empty?
         | 
| 49 | 
            +
                        _p 'Nothing changed'
         | 
| 50 | 
            +
                      else
         | 
| 51 | 
            +
                        _.system "git add #{file}"
         | 
| 52 | 
            +
                        _.system "git commit -m #{@comment.shellescape} #{file}"
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                elsif File.exist? file
         | 
| 58 | 
            +
                  # existing file
         | 
| 59 | 
            +
                  if !rev or rev.empty?
         | 
| 60 | 
            +
                    @markup = File.read(file) 
         | 
| 61 | 
            +
                  else
         | 
| 62 | 
            +
                    @markup = `git show #{rev}:#{file}`
         | 
| 63 | 
            +
                    flag = nil
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                else
         | 
| 67 | 
            +
                  # new file: go directly into edit mode
         | 
| 68 | 
            +
                  @markup = "#{file}\n#{'-'*file.length}\n\nEnter your text here..."
         | 
| 69 | 
            +
                  flag = '?'
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                # produce HTML
         | 
| 73 | 
            +
                if file == '_index'
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  # index
         | 
| 76 | 
            +
                  index = Hash[`git ls-tree HEAD --name-only`.scan(/(\w+)()/)].
         | 
| 77 | 
            +
                    merge Hash[*`git status --porcelain`.scan(/(..) (\w+)/).flatten.reverse]
         | 
| 78 | 
            +
                  _table do
         | 
| 79 | 
            +
                    _tbody do
         | 
| 80 | 
            +
                      index.sort.each do |name, status|
         | 
| 81 | 
            +
                        _tr do
         | 
| 82 | 
            +
                          _td status
         | 
| 83 | 
            +
                          _td {_a name, href: name}
         | 
| 84 | 
            +
                        end
         | 
| 85 | 
            +
                      end
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                elsif flag == '?'
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  # edit mode
         | 
| 92 | 
            +
                  _header do
         | 
| 93 | 
            +
                    _h1 "~~~ #{file} ~~~"
         | 
| 94 | 
            +
                    _span 'Input', style: 'float: left; margin-left: 2em'
         | 
| 95 | 
            +
                    _span 'Output', style: 'float: right; margin-right: 2em'
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  _form action: file, method: 'post' do
         | 
| 99 | 
            +
                    _textarea @markup, name: 'markup', class: 'input'
         | 
| 100 | 
            +
                    _input type: 'hidden', name: 'hash', 
         | 
| 101 | 
            +
                      value: Digest::MD5.hexdigest(@markup)
         | 
| 102 | 
            +
                    _div class: 'output' do
         | 
| 103 | 
            +
                      _ << RDiscount.new(@markup).to_html
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    _div class: 'buttons' do
         | 
| 107 | 
            +
                      _span class: 'message'
         | 
| 108 | 
            +
                      _input name: 'comment', placeholder: 'commit message'
         | 
| 109 | 
            +
                      _input type: 'submit', value: 'save'
         | 
| 110 | 
            +
                    end
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                elsif flag == '/'
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  # revision history
         | 
| 116 | 
            +
                  _h2 "Revision history for #{file}"
         | 
| 117 | 
            +
                  _ul do
         | 
| 118 | 
            +
                    `git log --format="%H|%ai|%an|%s" #{file}`.lines.each do |line|
         | 
| 119 | 
            +
                      hash, date, author, subject = line.split('|')
         | 
| 120 | 
            +
                      _li! {_a date, href: hash; _ " #{subject} by #{author}"}
         | 
| 121 | 
            +
                    end
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                else
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  #display
         | 
| 127 | 
            +
                  _ << RDiscount.new(@markup).to_html
         | 
| 128 | 
            +
                  _div class: 'buttons' do
         | 
| 129 | 
            +
                    if !rev or rev.empty?
         | 
| 130 | 
            +
                      _form action: "#{file}?", method: 'post' do
         | 
| 131 | 
            +
                        _input type: 'submit', value: 'edit'
         | 
| 132 | 
            +
                      end
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
                    _form action: "#{file}/" do
         | 
| 135 | 
            +
                      _input type: 'submit', value: 'history'
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                _script %{
         | 
| 141 | 
            +
                  // autosave every 10 seconds
         | 
| 142 | 
            +
                  var dirty = false;
         | 
| 143 | 
            +
                  setInterval(function() {
         | 
| 144 | 
            +
                    if (!dirty) return;
         | 
| 145 | 
            +
                    dirty = false;
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                    var params = {
         | 
| 148 | 
            +
                      markup: $('textarea[name=markup]').val(),
         | 
| 149 | 
            +
                      hash:   $('input[name=hash]').val()
         | 
| 150 | 
            +
                    };
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    $.getJSON("#{SELF}", params, function(_) {
         | 
| 153 | 
            +
                      $('input[name=hash]').val(_.hash);
         | 
| 154 | 
            +
                      if (_.time) {
         | 
| 155 | 
            +
                        var time = new Date(_.time).toLocaleTimeString();
         | 
| 156 | 
            +
                        $('.message').text("Autosaved at " + time).show().fadeOut(5000);
         | 
| 157 | 
            +
                      } else {
         | 
| 158 | 
            +
                        $('.input').val(_.markup).attr('readonly', 'readonly');
         | 
| 159 | 
            +
                        $('.message').css({'font-weight': 'bold'}).text(_.error).show();
         | 
| 160 | 
            +
                      }
         | 
| 161 | 
            +
                    });
         | 
| 162 | 
            +
                  }, 10000);
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  // regenerate output every 0.5 seconds
         | 
| 165 | 
            +
                  var updated = false;
         | 
| 166 | 
            +
                  setInterval(function() {
         | 
| 167 | 
            +
                    if (!updated) return;
         | 
| 168 | 
            +
                    updated = false;
         | 
| 169 | 
            +
                    $('.output').html(converter.makeHtml($('.input').val()));
         | 
| 170 | 
            +
                  }, 500);
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  // update output pane and mark dirty whenever input changes
         | 
| 173 | 
            +
                  var converter = new Showdown.converter();
         | 
| 174 | 
            +
                  $('.input').bind('input', function() {
         | 
| 175 | 
            +
                    updated = dirty = true;
         | 
| 176 | 
            +
                  }).trigger('input');
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  // resize based on window size
         | 
| 179 | 
            +
                  var reserve = $('header').height() * 3 + $('.buttons').height();
         | 
| 180 | 
            +
                  $(window).resize(function() {
         | 
| 181 | 
            +
                    $('.input,.output').height($(window).height()-reserve);
         | 
| 182 | 
            +
                  }).trigger('resize');
         | 
| 183 | 
            +
                }
         | 
| 184 | 
            +
              end
         | 
| 185 | 
            +
            end
         | 
| 186 | 
            +
             | 
| 187 | 
            +
            # process autosave requests
         | 
| 188 | 
            +
            Wunderbar.json do
         | 
| 189 | 
            +
              hash = Digest::MD5.hexdigest(@markup)
         | 
| 190 | 
            +
              if File.exist?(file) and Digest::MD5.hexdigest(File.read(file)) != @hash
         | 
| 191 | 
            +
                {error: "Write conflict", markup: File.read(file), hash: hash}
         | 
| 192 | 
            +
              else
         | 
| 193 | 
            +
                File.open(file, 'w') {|fh| fh.write @markup} unless @hash == hash
         | 
| 194 | 
            +
                {time: Time.now.to_i*1000, hash: hash}
         | 
| 195 | 
            +
              end
         | 
| 196 | 
            +
            end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
            __END__
         | 
| 199 | 
            +
            # Customize where the wiki data is stored
         | 
| 200 | 
            +
            WIKIDATA = '/full/path/to/data/directory'
         | 
    
        data/lib/wunderbar/builder.rb
    CHANGED
    
    | @@ -1,107 +1,149 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
               | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
                  end
         | 
| 1 | 
            +
            module Wunderbar
         | 
| 2 | 
            +
              # XmlMarkup handles indentation of elements beautifully, this class extends
         | 
| 3 | 
            +
              # that support to text, data, and spacing between elements
         | 
| 4 | 
            +
              class SpacedMarkup < Builder::XmlMarkup
         | 
| 5 | 
            +
                def indented_text!(text)
         | 
| 6 | 
            +
                  indented_data!(text) {|data| text! data}
         | 
| 8 7 | 
             
                end
         | 
| 9 8 |  | 
| 10 | 
            -
                 | 
| 11 | 
            -
                   | 
| 12 | 
            -
                    return if data.strip.length == 0
         | 
| 9 | 
            +
                def indented_data!(data, &block)
         | 
| 10 | 
            +
                  return if data.strip.length == 0
         | 
| 13 11 |  | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 12 | 
            +
                  if @indent > 0
         | 
| 13 | 
            +
                    data.sub! /\n\s*\Z/, ''
         | 
| 14 | 
            +
                    data.sub! /\A\s*\n/, ''
         | 
| 17 15 |  | 
| 18 | 
            -
             | 
| 16 | 
            +
                    unindent = data.sub(/s+\Z/,'').scan(/^ *\S/).map(&:length).min || 1
         | 
| 19 17 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
                    end
         | 
| 18 | 
            +
                    before  = ::Regexp.new('^'.ljust(unindent))
         | 
| 19 | 
            +
                    after   =  " " * (@level * @indent)
         | 
| 20 | 
            +
                    data.gsub! before, after
         | 
| 24 21 |  | 
| 25 | 
            -
                    if  | 
| 26 | 
            -
             | 
| 27 | 
            -
                     | 
| 28 | 
            -
             | 
| 29 | 
            -
                    end
         | 
| 22 | 
            +
                    _newline if @pending_newline and not @first_tag
         | 
| 23 | 
            +
                    @pending_newline = @pending_margin
         | 
| 24 | 
            +
                    @first_tag = @pending_margin = false
         | 
| 25 | 
            +
                  end
         | 
| 30 26 |  | 
| 31 | 
            -
             | 
| 27 | 
            +
                  if block
         | 
| 28 | 
            +
                    block.call(data)
         | 
| 29 | 
            +
                  else
         | 
| 30 | 
            +
                    self << data
         | 
| 32 31 | 
             
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  _newline unless data =~ /\n\Z/
         | 
| 33 34 | 
             
                end
         | 
| 34 35 |  | 
| 35 | 
            -
                 | 
| 36 | 
            -
                   | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
                   | 
| 36 | 
            +
                def disable_indendation!(&block)
         | 
| 37 | 
            +
                  indent, level = @indent, @level
         | 
| 38 | 
            +
                  @indent = @level = 0
         | 
| 39 | 
            +
                  text! " "*indent*level
         | 
| 40 | 
            +
                  block.call
         | 
| 41 | 
            +
                ensure
         | 
| 42 | 
            +
                  text! "\n"
         | 
| 43 | 
            +
                  @indent, @level = indent, level
         | 
| 43 44 | 
             
                end
         | 
| 44 | 
            -
              end
         | 
| 45 | 
            -
            end
         | 
| 46 45 |  | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 46 | 
            +
                def _margin
         | 
| 47 | 
            +
                  _newline unless @first_tag
         | 
| 48 | 
            +
                  @pending_newline = false
         | 
| 49 | 
            +
                  @pending_margin = true
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def _nested_structures(*args)
         | 
| 53 | 
            +
                  pending_newline = @pending_newline
         | 
| 54 | 
            +
                  @pending_newline = false
         | 
| 55 | 
            +
                  @first_tag = true
         | 
| 56 | 
            +
                  super
         | 
| 57 | 
            +
                  @first_tag = @pending_margin = false
         | 
| 58 | 
            +
                  @pending_newline = pending_newline
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def tag!(*args)
         | 
| 62 | 
            +
                  _newline if @pending_newline
         | 
| 63 | 
            +
                  @pending_newline = @pending_margin
         | 
| 64 | 
            +
                  @first_tag = @pending_margin = false
         | 
| 65 | 
            +
                  super
         | 
| 58 66 | 
             
                end
         | 
| 59 67 | 
             
              end
         | 
| 60 68 |  | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 69 | 
            +
              class XmlMarkup
         | 
| 70 | 
            +
                def initialize(*args)
         | 
| 71 | 
            +
                  @x = SpacedMarkup.new(*args)
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                # forward to either Wunderbar or XmlMarkup
         | 
| 75 | 
            +
                def method_missing(method, *args, &block)
         | 
| 76 | 
            +
                  if Wunderbar.respond_to? method
         | 
| 77 | 
            +
                    Wunderbar.send method, *args, &block
         | 
| 78 | 
            +
                  elsif SpacedMarkup.public_instance_methods.include? method
         | 
| 79 | 
            +
                    @x.__send__ method, *args, &block
         | 
| 80 | 
            +
                  elsif SpacedMarkup.public_instance_methods.include? method.to_s
         | 
| 81 | 
            +
                    @x.__send__ method, *args, &block
         | 
| 82 | 
            +
                  else
         | 
| 83 | 
            +
                    super
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                # avoid method_missing overhead for the most common case
         | 
| 88 | 
            +
                def tag!(*args, &block)
         | 
| 89 | 
            +
                  @x.tag! *args, &block
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                # execute a system command, echoing stdin, stdout, and stderr
         | 
| 93 | 
            +
                def system(command, opts={})
         | 
| 94 | 
            +
                  require 'open3'
         | 
| 95 | 
            +
                  tag  = opts[:tag]  || 'pre'
         | 
| 96 | 
            +
                  output_class = opts[:class] || {}
         | 
| 97 | 
            +
                  stdin  = output_class[:stdin]  || '_stdin'
         | 
| 98 | 
            +
                  stdout = output_class[:stdout] || '_stdout'
         | 
| 99 | 
            +
                  stderr = output_class[:stderr] || '_stderr'
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  @x.tag! tag, command, :class=>stdin unless opts[:echo] == false
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  require 'thread'
         | 
| 104 | 
            +
                  semaphore = Mutex.new
         | 
| 105 | 
            +
                  Open3.popen3(command) do |pin, pout, perr|
         | 
| 106 | 
            +
                    [
         | 
| 107 | 
            +
                      Thread.new do
         | 
| 108 | 
            +
                        until pout.eof?
         | 
| 109 | 
            +
                          out_line = pout.readline.chomp
         | 
| 110 | 
            +
                          semaphore.synchronize { @x.tag! tag, out_line, :class=>stdout }
         | 
| 111 | 
            +
                        end
         | 
| 112 | 
            +
                      end,
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                      Thread.new do
         | 
| 115 | 
            +
                        until perr.eof?
         | 
| 116 | 
            +
                          err_line = perr.readline.chomp
         | 
| 117 | 
            +
                          semaphore.synchronize { @x.tag! tag, err_line, :class=>stderr }
         | 
| 118 | 
            +
                        end
         | 
| 119 | 
            +
                      end,
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                      Thread.new do
         | 
| 122 | 
            +
                        if opts[:stdin].respond_to? :read
         | 
| 123 | 
            +
                          require 'fileutils'
         | 
| 124 | 
            +
                          FileUtils.copy_stream opts[:stdin], pin
         | 
| 125 | 
            +
                        elsif opts[:stdin]
         | 
| 126 | 
            +
                          pin.write opts[:stdin].to_s
         | 
| 127 | 
            +
                        end
         | 
| 128 | 
            +
                        pin.close
         | 
| 81 129 | 
             
                      end
         | 
| 82 | 
            -
             | 
| 83 | 
            -
                      _start_tag(sym, attrs)
         | 
| 84 | 
            -
                      _newline
         | 
| 85 | 
            -
                      begin ### Added
         | 
| 86 | 
            -
                        _nested_structures(block)
         | 
| 87 | 
            -
                      ensure ### Added
         | 
| 88 | 
            -
                        _indent
         | 
| 89 | 
            -
                        _end_tag(sym)
         | 
| 90 | 
            -
                        _newline
         | 
| 91 | 
            -
                      end ### Added
         | 
| 92 | 
            -
                    elsif text.nil?
         | 
| 93 | 
            -
                      _indent
         | 
| 94 | 
            -
                      _start_tag(sym, attrs, true)
         | 
| 95 | 
            -
                      _newline
         | 
| 96 | 
            -
                    else
         | 
| 97 | 
            -
                      _indent
         | 
| 98 | 
            -
                      _start_tag(sym, attrs)
         | 
| 99 | 
            -
                      text! text
         | 
| 100 | 
            -
                      _end_tag(sym)
         | 
| 101 | 
            -
                      _newline
         | 
| 102 | 
            -
                    end
         | 
| 103 | 
            -
                    @target
         | 
| 130 | 
            +
                    ].each {|thread| thread.join}
         | 
| 104 131 | 
             
                  end
         | 
| 105 132 | 
             
                end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                # declaration (DOCTYPE, etc)
         | 
| 135 | 
            +
                def declare(*args)
         | 
| 136 | 
            +
                  @x.declare!(*args)
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                # comment
         | 
| 140 | 
            +
                def comment(*args)
         | 
| 141 | 
            +
                  @x.comment! *args
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                # was this invoked via HTTP POST?
         | 
| 145 | 
            +
                def post?
         | 
| 146 | 
            +
                  $HTTP_POST
         | 
| 147 | 
            +
                end
         | 
| 106 148 | 
             
              end
         | 
| 107 149 | 
             
            end
         | 
| @@ -83,28 +83,23 @@ def $cgi.out?(headers, &block) | |
| 83 83 | 
             
            end
         | 
| 84 84 |  | 
| 85 85 | 
             
            # produce html/xhtml
         | 
| 86 | 
            -
            def $cgi.html(&block)
         | 
| 86 | 
            +
            def $cgi.html(*args, &block)
         | 
| 87 87 | 
             
              return if $XHR_JSON or $TEXT
         | 
| 88 | 
            +
              args.push {} if args.empty?
         | 
| 89 | 
            +
              args.first[:xmlns] ||= 'http://www.w3.org/1999/xhtml' if Hash === args.first
         | 
| 90 | 
            +
              mimetype = ($XHTML ? 'application/xhtml+xml' : 'text/html')
         | 
| 88 91 | 
             
              x = HtmlMarkup.new
         | 
| 89 | 
            -
               | 
| 90 | 
            -
                 | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
                  x.html :xmlns => 'http://www.w3.org/1999/xhtml', &block
         | 
| 94 | 
            -
                end
         | 
| 95 | 
            -
              else
         | 
| 96 | 
            -
                $cgi.out? 'type' => 'text/html', 'charset' => 'UTF-8' do
         | 
| 97 | 
            -
                  x._! "\xEF\xBB\xBF"
         | 
| 98 | 
            -
                  x.declare! :DOCTYPE, :html
         | 
| 99 | 
            -
                  x.html &block
         | 
| 100 | 
            -
                end
         | 
| 92 | 
            +
              $cgi.out? 'type' => mimetype, 'charset' => 'UTF-8' do
         | 
| 93 | 
            +
                x._! "\xEF\xBB\xBF"
         | 
| 94 | 
            +
                x._.declare :DOCTYPE, :html
         | 
| 95 | 
            +
                x.html *args, &block
         | 
| 101 96 | 
             
              end
         | 
| 102 97 | 
             
            end
         | 
| 103 98 |  | 
| 104 99 | 
             
            # produce html and quit
         | 
| 105 | 
            -
            def $cgi.html! &block
         | 
| 100 | 
            +
            def $cgi.html! *args, &block
         | 
| 106 101 | 
             
              return if $XHR_JSON or $TEXT
         | 
| 107 | 
            -
              html(&block)
         | 
| 102 | 
            +
              html(*args, &block)
         | 
| 108 103 | 
             
              Process.exit
         | 
| 109 104 | 
             
            end
         | 
| 110 105 |  | 
| @@ -1,49 +1,3 @@ | |
| 1 | 
            -
            # execute a system command, echoing stdin, stdout, and stderr
         | 
| 2 | 
            -
            def $x.system(command, opts={})
         | 
| 3 | 
            -
              ::Kernel.require 'open3'
         | 
| 4 | 
            -
              output_class = opts[:class] || {}
         | 
| 5 | 
            -
              stdin  = output_class[:stdin]  || '_stdin'
         | 
| 6 | 
            -
              stdout = output_class[:stdout] || '_stdout'
         | 
| 7 | 
            -
              stderr = output_class[:stderr] || '_stderr'
         | 
| 8 | 
            -
             | 
| 9 | 
            -
              $x.pre command, :class=>stdin unless opts[:echo] == false
         | 
| 10 | 
            -
             | 
| 11 | 
            -
              ::Kernel.require 'thread'
         | 
| 12 | 
            -
              semaphore = ::Mutex.new
         | 
| 13 | 
            -
              ::Open3.popen3(command) do |pin, pout, perr|
         | 
| 14 | 
            -
                [
         | 
| 15 | 
            -
                  ::Thread.new do
         | 
| 16 | 
            -
                    until pout.eof?
         | 
| 17 | 
            -
                      out_line = pout.readline.chomp
         | 
| 18 | 
            -
                      semaphore.synchronize { $x.pre out_line, :class=>stdout }
         | 
| 19 | 
            -
                    end
         | 
| 20 | 
            -
                  end,
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                  ::Thread.new do
         | 
| 23 | 
            -
                    until perr.eof?
         | 
| 24 | 
            -
                      err_line = perr.readline.chomp
         | 
| 25 | 
            -
                      semaphore.synchronize { $x.pre err_line, :class=>stderr }
         | 
| 26 | 
            -
                    end
         | 
| 27 | 
            -
                  end,
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                  ::Thread.new do
         | 
| 30 | 
            -
                    if opts[:stdin].respond_to? :read
         | 
| 31 | 
            -
                      require 'fileutils'
         | 
| 32 | 
            -
                      FileUtils.copy_stream opts[:stdin], pin
         | 
| 33 | 
            -
                    elsif opts[:stdin]
         | 
| 34 | 
            -
                      pin.write opts[:stdin].to_s
         | 
| 35 | 
            -
                    end
         | 
| 36 | 
            -
                    pin.close
         | 
| 37 | 
            -
                  end
         | 
| 38 | 
            -
                ].each {|thread| thread.join}
         | 
| 39 | 
            -
              end
         | 
| 40 | 
            -
            end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
            # was this invoked via HTTP POST?
         | 
| 43 | 
            -
            def $x.post?
         | 
| 44 | 
            -
              $HTTP_POST
         | 
| 45 | 
            -
            end
         | 
| 46 | 
            -
             | 
| 47 1 | 
             
            # Wrapper class that understands HTML
         | 
| 48 2 | 
             
            class HtmlMarkup
         | 
| 49 3 | 
             
              VOID = %w(
         | 
| @@ -52,14 +6,11 @@ class HtmlMarkup | |
| 52 6 | 
             
              )
         | 
| 53 7 |  | 
| 54 8 | 
             
              def initialize(*args, &block)
         | 
| 55 | 
            -
                 | 
| 56 | 
            -
                # to be deprecated.
         | 
| 57 | 
            -
                $x ||= Builder::XmlMarkup.new :indent => 2
         | 
| 58 | 
            -
                @x = $x
         | 
| 9 | 
            +
                @x = Wunderbar::XmlMarkup.new :indent => 2
         | 
| 59 10 | 
             
              end
         | 
| 60 11 |  | 
| 61 12 | 
             
              def html(*args, &block)
         | 
| 62 | 
            -
                @x.html | 
| 13 | 
            +
                @x.tag! :html, *args do 
         | 
| 63 14 | 
             
                  $param.each do |key,value| 
         | 
| 64 15 | 
             
                    instance_variable_set "@#{key}", value.first if key =~ /^\w+$/
         | 
| 65 16 | 
             
                  end
         | 
| @@ -76,6 +27,10 @@ class HtmlMarkup | |
| 76 27 | 
             
                  raise error
         | 
| 77 28 | 
             
                end
         | 
| 78 29 |  | 
| 30 | 
            +
                if name.sub!(/_$/,'')
         | 
| 31 | 
            +
                  @x._margin
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 79 34 | 
             
                if flag != '!'
         | 
| 80 35 | 
             
                  if %w(script style).include?(name)
         | 
| 81 36 | 
             
                    if String === args.first and not block
         | 
| @@ -104,15 +59,8 @@ class HtmlMarkup | |
| 104 59 | 
             
                end
         | 
| 105 60 |  | 
| 106 61 | 
             
                if flag == '!'
         | 
| 107 | 
            -
                   | 
| 108 | 
            -
                  indent, level = @x.instance_eval { [@indent, @level] }
         | 
| 109 | 
            -
                  begin
         | 
| 110 | 
            -
                    @x.instance_eval { [@indent=0, @level=0] }
         | 
| 111 | 
            -
                    @x.text! " "*indent*level
         | 
| 62 | 
            +
                  @x.disable_indendation! do
         | 
| 112 63 | 
             
                    @x.tag! name, *args, &block
         | 
| 113 | 
            -
                  ensure
         | 
| 114 | 
            -
                    @x.text! "\n"
         | 
| 115 | 
            -
                    @x.instance_eval { [@indent=indent, @level=level] }
         | 
| 116 64 | 
             
                  end
         | 
| 117 65 | 
             
                elsif flag == '?'
         | 
| 118 66 | 
             
                  # capture exceptions, produce filtered tracebacks
         | 
| @@ -135,9 +83,9 @@ class HtmlMarkup | |
| 135 83 | 
             
                      end
         | 
| 136 84 |  | 
| 137 85 | 
             
                      if traceback_class
         | 
| 138 | 
            -
                         | 
| 86 | 
            +
                        @x.tag! :pre, text, :class=>traceback_class
         | 
| 139 87 | 
             
                      else
         | 
| 140 | 
            -
                         | 
| 88 | 
            +
                        @x.tag! :pre, text, :style=>traceback_style
         | 
| 141 89 | 
             
                      end
         | 
| 142 90 | 
             
                    end
         | 
| 143 91 | 
             
                  end
         | 
| @@ -148,11 +96,25 @@ class HtmlMarkup | |
| 148 96 |  | 
| 149 97 | 
             
              def _head(*args, &block)
         | 
| 150 98 | 
             
                @x.tag!('head', *args) do
         | 
| 151 | 
            -
                  @x.meta :charset => 'utf-8' unless $XHTML
         | 
| 99 | 
            +
                  @x.tag! :meta, :charset => 'utf-8' unless $XHTML
         | 
| 152 100 | 
             
                  block.call if block
         | 
| 153 101 | 
             
                end
         | 
| 154 102 | 
             
              end
         | 
| 155 103 |  | 
| 104 | 
            +
              def _svg(*args, &block)
         | 
| 105 | 
            +
                args << {} if args.empty?
         | 
| 106 | 
            +
                args.first['xmlns'] = 'http://www.w3.org/2000/svg' if Hash === args.first
         | 
| 107 | 
            +
                @x.tag! :svg, *args, &block
         | 
| 108 | 
            +
              end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
              def _math(*args, &block)
         | 
| 111 | 
            +
                args << {} if args.empty?
         | 
| 112 | 
            +
                if Hash === args.first
         | 
| 113 | 
            +
                  args.first['xmlns'] = 'http://www.w3.org/1998/Math/MathML'
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
                @x.tag! :math, *args, &block
         | 
| 116 | 
            +
              end
         | 
| 117 | 
            +
             | 
| 156 118 | 
             
              def _(text=nil)
         | 
| 157 119 | 
             
                @x.indented_text! text if text
         | 
| 158 120 | 
             
                @x
         | 
| @@ -163,10 +125,6 @@ class HtmlMarkup | |
| 163 125 | 
             
                @x
         | 
| 164 126 | 
             
              end
         | 
| 165 127 |  | 
| 166 | 
            -
              def declare!(*args)
         | 
| 167 | 
            -
                @x.declare!(*args)
         | 
| 168 | 
            -
              end
         | 
| 169 | 
            -
             | 
| 170 128 | 
             
              def _coffeescript(text)
         | 
| 171 129 | 
             
                require 'coffee-script'
         | 
| 172 130 | 
             
                _script CoffeeScript.compile(text)
         | 
    
        data/lib/wunderbar/version.rb
    CHANGED
    
    
    
        data/test/test_builder.rb
    CHANGED
    
    | @@ -4,20 +4,32 @@ require 'wunderbar' | |
| 4 4 |  | 
| 5 5 | 
             
            class BuilderTest < Test::Unit::TestCase
         | 
| 6 6 | 
             
              def test_empty
         | 
| 7 | 
            -
                x =  | 
| 8 | 
            -
                x.script { x.indented_text! '' }
         | 
| 7 | 
            +
                x = Wunderbar::XmlMarkup.new :indent => 2
         | 
| 8 | 
            +
                x.tag!(:script) { x.indented_text! '' }
         | 
| 9 9 | 
             
                assert_equal %{<script>\n</script>\n}, x.target!
         | 
| 10 10 | 
             
              end
         | 
| 11 11 |  | 
| 12 12 | 
             
              def test_unindented_input
         | 
| 13 | 
            -
                x =  | 
| 14 | 
            -
                x.script { x.indented_text! "{\n  x: 1\n}" }
         | 
| 13 | 
            +
                x = Wunderbar::XmlMarkup.new :indent => 2
         | 
| 14 | 
            +
                x.tag!(:script) { x.indented_text! "{\n  x: 1\n}" }
         | 
| 15 15 | 
             
                assert_equal %{<script>\n  {\n    x: 1\n  }\n</script>\n}, x.target!
         | 
| 16 16 | 
             
              end
         | 
| 17 17 |  | 
| 18 18 | 
             
              def test_indented_input
         | 
| 19 | 
            -
                x =  | 
| 20 | 
            -
                x.script { x.indented_text! "      alert('danger');" }
         | 
| 19 | 
            +
                x = Wunderbar::XmlMarkup.new :indent => 2
         | 
| 20 | 
            +
                x.tag!(:script) { x.indented_text! "      alert('danger');" }
         | 
| 21 21 | 
             
                assert_equal %{<script>\n  alert('danger');\n</script>\n}, x.target!
         | 
| 22 22 | 
             
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def test_exception
         | 
| 25 | 
            +
                x = Wunderbar::XmlMarkup.new :indent => 2
         | 
| 26 | 
            +
                x.tag!(:body) do
         | 
| 27 | 
            +
                  begin
         | 
| 28 | 
            +
                    x.tag!(:p) { raise Exception.new('boom') }
         | 
| 29 | 
            +
                  rescue Exception => e
         | 
| 30 | 
            +
                    x.tag!(:pre, e)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
                assert x.target!.include? '<p>' and x.target!.include? '</p>'
         | 
| 34 | 
            +
              end
         | 
| 23 35 | 
             
            end
         | 
    
        data/test/test_html_markup.rb
    CHANGED
    
    | @@ -4,7 +4,6 @@ require 'wunderbar' | |
| 4 4 |  | 
| 5 5 | 
             
            class HtmlMarkupTest < Test::Unit::TestCase
         | 
| 6 6 | 
             
              def setup
         | 
| 7 | 
            -
                $x = nil # until this hack is removed html-methods.rb
         | 
| 8 7 | 
             
                @original_log_level = Wunderbar.logger.level
         | 
| 9 8 | 
             
                Wunderbar.log_level = :fatal
         | 
| 10 9 | 
             
              end
         | 
| @@ -75,6 +74,20 @@ class HtmlMarkupTest < Test::Unit::TestCase | |
| 75 74 | 
             
                assert_match %r[<div>one <strong>two</strong> three</div>], x.target!
         | 
| 76 75 | 
             
              end
         | 
| 77 76 |  | 
| 77 | 
            +
              def test_spaced_embedded
         | 
| 78 | 
            +
                x = HtmlMarkup.new
         | 
| 79 | 
            +
                x.html {_div {_p 'one'; _hr_; _p 'two'}}
         | 
| 80 | 
            +
                assert_match %r[<div>\n +<p>one</p>\n\n +<hr/>\n\n +<p>two</p>\n +</div>], 
         | 
| 81 | 
            +
                  x.target!
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              def test_spaced_collapsed
         | 
| 85 | 
            +
                x = HtmlMarkup.new
         | 
| 86 | 
            +
                x.html {_div {_p_ 'one'; _hr_; _p_ 'two'}}
         | 
| 87 | 
            +
                assert_match %r[<div>\n +<p>one</p>\n\n +<hr/>\n\n +<p>two</p>\n +</div>], 
         | 
| 88 | 
            +
                  x.target!
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
             | 
| 78 91 | 
             
              def test_traceback
         | 
| 79 92 | 
             
                x = HtmlMarkup.new
         | 
| 80 93 | 
             
                x.html {_body? {boom}}
         | 
| @@ -120,10 +133,28 @@ class HtmlMarkupTest < Test::Unit::TestCase | |
| 120 133 |  | 
| 121 134 | 
             
              def test_declare
         | 
| 122 135 | 
             
                x = HtmlMarkup.new
         | 
| 123 | 
            -
                x.declare | 
| 136 | 
            +
                x._.declare :DOCTYPE, 'html'
         | 
| 124 137 | 
             
                assert_equal %{<!DOCTYPE "html">\n}, x.target!
         | 
| 125 138 | 
             
              end
         | 
| 126 139 |  | 
| 140 | 
            +
              def test_comment
         | 
| 141 | 
            +
                x = HtmlMarkup.new
         | 
| 142 | 
            +
                x._.comment 'foo'
         | 
| 143 | 
            +
                assert_equal %{<!-- foo -->\n}, x.target!
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              def test_svg
         | 
| 147 | 
            +
                x = HtmlMarkup.new
         | 
| 148 | 
            +
                x.html {_svg}
         | 
| 149 | 
            +
                assert_match %r[^  <svg xmlns="http://www.w3.org/2000/svg"/>], x.target!
         | 
| 150 | 
            +
              end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
              def test_math
         | 
| 153 | 
            +
                x = HtmlMarkup.new
         | 
| 154 | 
            +
                x.html {_math}
         | 
| 155 | 
            +
                assert_match %r[^  <math xmlns="http://www.w3.org/1998/Math/MathML"/>], x.target!
         | 
| 156 | 
            +
              end
         | 
| 157 | 
            +
             | 
| 127 158 | 
             
              begin
         | 
| 128 159 | 
             
                require 'coffee-script'
         | 
| 129 160 |  | 
    
        data/tools/web2script.rb
    ADDED
    
    | @@ -0,0 +1,213 @@ | |
| 1 | 
            +
            require 'net/http'
         | 
| 2 | 
            +
            require 'rubygems'
         | 
| 3 | 
            +
            require 'nokogiri'
         | 
| 4 | 
            +
            require 'optparse'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # Convert a webpage to a Wunderbar script
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            OptionParser.new { |opts|
         | 
| 9 | 
            +
              opts.banner = "#{File.basename(__FILE__)} [-o output] [-w width] URLs..."
         | 
| 10 | 
            +
              opts.on '-o', '--output FILE', 'Send Output to FILE' do |file|
         | 
| 11 | 
            +
                $stdout = File.open(file, 'w')
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
              opts.on '-w', '--width WIDTH', Integer, 'Set line width' do |width|
         | 
| 14 | 
            +
                $width = width
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
              opts.on '-g', '--group lines', Integer, 
         | 
| 17 | 
            +
                'Insert blanks lines around blocks larger than this value' do |group|
         | 
| 18 | 
            +
                $group = group
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
              if ''.respond_to? 'encoding'
         | 
| 21 | 
            +
                opts.on '-a', '--ascii', Integer, 'Escape non-ASCII characters' do
         | 
| 22 | 
            +
                  $ascii = true
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            }.parse!
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            # Method to "enquote" a string
         | 
| 28 | 
            +
            class String
         | 
| 29 | 
            +
              def enquote
         | 
| 30 | 
            +
                if $ascii
         | 
| 31 | 
            +
                  inspect.gsub(/[^\x20-\x7f]/) { |c| '\u' + c.ord.to_s(16).rjust(4,'0') }
         | 
| 32 | 
            +
                else
         | 
| 33 | 
            +
                  inspect
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            # queue of lines to be output
         | 
| 39 | 
            +
            $q = []
         | 
| 40 | 
            +
            def q line
         | 
| 41 | 
            +
              $q << line
         | 
| 42 | 
            +
            end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            def flow_text(line, join)
         | 
| 45 | 
            +
              while $width and line.length>$width
         | 
| 46 | 
            +
                line.sub! /(.{1,#{$width-4}})(\s+|\Z)/, "\\1 #{join}"
         | 
| 47 | 
            +
                break unless line.include? "\n"
         | 
| 48 | 
            +
                q line.split("\n").first
         | 
| 49 | 
            +
                line = line[/\n(.*)/,1]
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
              q line
         | 
| 52 | 
            +
            end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            def flow_attrs(line, attributes, indent)
         | 
| 55 | 
            +
              attributes.each do |attribute|
         | 
| 56 | 
            +
                line += ','
         | 
| 57 | 
            +
                if $width and (line+attribute).length > $width-1
         | 
| 58 | 
            +
                  q line
         | 
| 59 | 
            +
                  line = "#{indent} "
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
                line += attribute
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
              q line
         | 
| 64 | 
            +
            end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            def code(element, indent='')
         | 
| 67 | 
            +
              attributes = []
         | 
| 68 | 
            +
              element.attributes.keys.each do |key|
         | 
| 69 | 
            +
                value = element[key]
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                # resolve relative links
         | 
| 72 | 
            +
                if %w(a img link).include? element.name and %w(href src).include? key
         | 
| 73 | 
            +
                  value = ($uri + value).to_s rescue nil
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                if key =~ /^\w+$/
         | 
| 77 | 
            +
                  if key == 'xmlns' and %w(html svg mathml).include? element.name
         | 
| 78 | 
            +
                    # drop xmlns attributes from these elements
         | 
| 79 | 
            +
                  elsif key == 'type' and element.name == 'style' and value == 'text/css'
         | 
| 80 | 
            +
                    # drop type attributes from script elements
         | 
| 81 | 
            +
                  elsif key == 'type' and element.name == 'script' and value == 'text/javascript'
         | 
| 82 | 
            +
                    # drop type attributes from script elements
         | 
| 83 | 
            +
                  elsif RUBY_VERSION =~ /^1\.8/
         | 
| 84 | 
            +
                    attributes << " :#{key} => #{value.enquote}"
         | 
| 85 | 
            +
                  else
         | 
| 86 | 
            +
                    attributes << " #{key}: #{value.enquote}"
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                else
         | 
| 89 | 
            +
                  attributes << " #{key.enquote} => #{value.enquote}"
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              # restore namespaces that Nokogiri::HTML dropped
         | 
| 94 | 
            +
              element_name = element.name
         | 
| 95 | 
            +
              if $namespaced[element.name]
         | 
| 96 | 
            +
                element_name = $namespaced[element.name]
         | 
| 97 | 
            +
                element_name += ',' unless attributes.empty?
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
              line = "#{indent}_#{element_name}#{attributes.join(',')}"
         | 
| 101 | 
            +
              line.sub! /^_/, 'Wunderbar.' if element_name == 'html' and indent.empty?
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              if element.children.empty?
         | 
| 104 | 
            +
                flow_attrs "#{indent}_#{element_name}#{attributes.pop}", attributes, indent
         | 
| 105 | 
            +
             | 
| 106 | 
            +
              # element has children
         | 
| 107 | 
            +
              elsif element.children.any? {|child| child.element?}
         | 
| 108 | 
            +
                # do any of the text nodes need special processing to preserve spacing?
         | 
| 109 | 
            +
                flatten = false
         | 
| 110 | 
            +
                space = true
         | 
| 111 | 
            +
                if element.children.any? {|child| child.text? and !child.text.strip.empty?}
         | 
| 112 | 
            +
                  element.children.each do |child| 
         | 
| 113 | 
            +
                    if child.text? or child.element?
         | 
| 114 | 
            +
                      next if child.text == ''
         | 
| 115 | 
            +
                      flatten = true if not space and not child.text =~ /\A\s/
         | 
| 116 | 
            +
                      space = (child.text =~ /\s\Z/)
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
                line.sub!(/(\w)( |$)/, '\1!\2') if flatten
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                q "#{line} do"
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                start = $q.length
         | 
| 125 | 
            +
                blank = false
         | 
| 126 | 
            +
                first = true
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                # recursively process children
         | 
| 129 | 
            +
                element.children.each do |child|
         | 
| 130 | 
            +
                  if child.text?
         | 
| 131 | 
            +
                    text = child.text.gsub(/\s+/, ' ')
         | 
| 132 | 
            +
                    text = text.strip unless flatten
         | 
| 133 | 
            +
                    next if text.empty?
         | 
| 134 | 
            +
                    flow_text "#{indent}  _ #{text.enquote}", "\" +\n    #{indent}\""
         | 
| 135 | 
            +
                  elsif child.comment?
         | 
| 136 | 
            +
                    flow_text "#{indent}  _.comment #{child.text.strip.enquote}", 
         | 
| 137 | 
            +
                      "\" +\n    #{indent}\""
         | 
| 138 | 
            +
                  else
         | 
| 139 | 
            +
                    code(child, indent + '  ')
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  # insert a blank line if either this or the previous block was large
         | 
| 143 | 
            +
                  if $group and start + $group < $q.length
         | 
| 144 | 
            +
                    $q[start].sub! /^(\s+_\w+) /, '\1_ \2'
         | 
| 145 | 
            +
                    $q.insert(start,'')  if not first
         | 
| 146 | 
            +
                    blank = true
         | 
| 147 | 
            +
                  else
         | 
| 148 | 
            +
                    $q.insert(start,'') if blank
         | 
| 149 | 
            +
                    blank = false
         | 
| 150 | 
            +
                  end
         | 
| 151 | 
            +
                  start = $q.length
         | 
| 152 | 
            +
                  first = false
         | 
| 153 | 
            +
                end
         | 
| 154 | 
            +
                q indent + "end"
         | 
| 155 | 
            +
             | 
| 156 | 
            +
              elsif element.name == 'pre' and element.text.include? "\n"
         | 
| 157 | 
            +
                data = element.text.sub(/\A\n/,'').sub(/\s+\Z/,'')
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                unindent = data.sub(/s+\Z/,'').scan(/^ *\S/).map(&:length).min || 1
         | 
| 160 | 
            +
                before  = Regexp.new('^'.ljust(unindent))
         | 
| 161 | 
            +
                after   =  "#{indent}  "
         | 
| 162 | 
            +
                data.gsub! before, after
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                flow_attrs "#{indent}_pre <<-EOD.gsub(/^\\s{#{after.length}}/,'')", 
         | 
| 165 | 
            +
                  attributes, indent
         | 
| 166 | 
            +
                data.split("\n").each { |line| q line }
         | 
| 167 | 
            +
                q "#{indent}EOD"
         | 
| 168 | 
            +
             | 
| 169 | 
            +
              # element has text but no attributes or children
         | 
| 170 | 
            +
              elsif attributes.empty?
         | 
| 171 | 
            +
                if %w(script style).include? element.name and element.text.include? "\n"
         | 
| 172 | 
            +
                  script = element.text.sub(/\A\n/,'').sub(/\s+\Z/,'')
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  unindent = script.sub(/s+\Z/,'').scan(/^ *\S/).map(&:length).min || 1
         | 
| 175 | 
            +
                  before  = Regexp.new('^'.ljust(unindent))
         | 
| 176 | 
            +
                  after   =  "#{indent}  "
         | 
| 177 | 
            +
                  script.gsub! before, after
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                  q "#{line} %{"
         | 
| 180 | 
            +
                  script.split("\n").each { |line| q line }
         | 
| 181 | 
            +
                  q "#{indent}}"
         | 
| 182 | 
            +
                else
         | 
| 183 | 
            +
                  flow_text "#{line} #{element.text.enquote}", "\" +\n  #{indent}\""
         | 
| 184 | 
            +
                end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
              # element has text and attributes but no children
         | 
| 187 | 
            +
              else
         | 
| 188 | 
            +
                flow_attrs "#{indent}_#{element_name} #{element.text.enquote}",
         | 
| 189 | 
            +
                  attributes, indent
         | 
| 190 | 
            +
              end
         | 
| 191 | 
            +
            end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
            # fetch and convert each web page
         | 
| 194 | 
            +
            ARGV.each do |arg|
         | 
| 195 | 
            +
              $uri = URI.parse arg
         | 
| 196 | 
            +
              doc = Net::HTTP.get($uri)
         | 
| 197 | 
            +
              $namespaced = Hash[doc.scan(/<\/(\w+):(\w+)>/).uniq.
         | 
| 198 | 
            +
                map {|p,n| [n, "#{p} :#{n}"]}]
         | 
| 199 | 
            +
              $namespaced.delete_if {|name, value| doc =~ /<#{name}[ >]/}
         | 
| 200 | 
            +
              code Nokogiri::HTML(doc).root
         | 
| 201 | 
            +
            end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
            # headers
         | 
| 204 | 
            +
            if ''.respond_to? 'encoding'
         | 
| 205 | 
            +
              puts '# encoding: utf-8' if $q.any? {|line| line.match /[^\x20-\x7f]/}
         | 
| 206 | 
            +
            else
         | 
| 207 | 
            +
              puts "require 'rubygems'"
         | 
| 208 | 
            +
            end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
            puts "require 'wunderbar'\n\n"
         | 
| 211 | 
            +
             | 
| 212 | 
            +
            # main output
         | 
| 213 | 
            +
            puts $q.join("\n")
         | 
    
        data/wunderbar.gemspec
    CHANGED
    
    | @@ -2,15 +2,15 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            Gem::Specification.new do |s|
         | 
| 4 4 | 
             
              s.name = "wunderbar"
         | 
| 5 | 
            -
              s.version = "0.8. | 
| 5 | 
            +
              s.version = "0.8.6"
         | 
| 6 6 |  | 
| 7 7 | 
             
              s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
         | 
| 8 8 | 
             
              s.authors = ["Sam Ruby"]
         | 
| 9 | 
            -
              s.date = "2012- | 
| 9 | 
            +
              s.date = "2012-03-17"
         | 
| 10 10 | 
             
              s.description = "    Provides a number of globals, helper methods, and monkey patches which\n    simplify the generation of HTML and the development of CGI scripts.\n"
         | 
| 11 11 | 
             
              s.email = "rubys@intertwingly.net"
         | 
| 12 12 | 
             
              s.extra_rdoc_files = ["COPYING", "README", "lib/wunderbar.rb", "lib/wunderbar/builder.rb", "lib/wunderbar/cgi-methods.rb", "lib/wunderbar/environment.rb", "lib/wunderbar/html-methods.rb", "lib/wunderbar/installation.rb", "lib/wunderbar/job-control.rb", "lib/wunderbar/logger.rb", "lib/wunderbar/version.rb"]
         | 
| 13 | 
            -
              s.files = ["COPYING", "README", "Rakefile", "lib/wunderbar.rb", "lib/wunderbar/builder.rb", "lib/wunderbar/cgi-methods.rb", "lib/wunderbar/environment.rb", "lib/wunderbar/html-methods.rb", "lib/wunderbar/installation.rb", "lib/wunderbar/job-control.rb", "lib/wunderbar/logger.rb", "lib/wunderbar/version.rb", "test/test_builder.rb", "test/test_html_markup.rb", "test/test_logger.rb", " | 
| 13 | 
            +
              s.files = ["COPYING", "README", "Rakefile", "demo/wiki.html", "demo/wiki.rb", "lib/wunderbar.rb", "lib/wunderbar/builder.rb", "lib/wunderbar/cgi-methods.rb", "lib/wunderbar/environment.rb", "lib/wunderbar/html-methods.rb", "lib/wunderbar/installation.rb", "lib/wunderbar/job-control.rb", "lib/wunderbar/logger.rb", "lib/wunderbar/version.rb", "test/test_builder.rb", "test/test_html_markup.rb", "test/test_logger.rb", "tools/web2script.rb", "Manifest", "wunderbar.gemspec"]
         | 
| 14 14 | 
             
              s.homepage = "http://github.com/rubys/wunderbar"
         | 
| 15 15 | 
             
              s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Wunderbar", "--main", "README"]
         | 
| 16 16 | 
             
              s.require_paths = ["lib"]
         | 
    
        metadata
    CHANGED
    
    | @@ -1,13 +1,13 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: wunderbar
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              hash:  | 
| 4 | 
            +
              hash: 51
         | 
| 5 5 | 
             
              prerelease: 
         | 
| 6 6 | 
             
              segments: 
         | 
| 7 7 | 
             
              - 0
         | 
| 8 8 | 
             
              - 8
         | 
| 9 | 
            -
              -  | 
| 10 | 
            -
              version: 0.8. | 
| 9 | 
            +
              - 6
         | 
| 10 | 
            +
              version: 0.8.6
         | 
| 11 11 | 
             
            platform: ruby
         | 
| 12 12 | 
             
            authors: 
         | 
| 13 13 | 
             
            - Sam Ruby
         | 
| @@ -15,7 +15,7 @@ autorequire: | |
| 15 15 | 
             
            bindir: bin
         | 
| 16 16 | 
             
            cert_chain: []
         | 
| 17 17 |  | 
| 18 | 
            -
            date: 2012- | 
| 18 | 
            +
            date: 2012-03-17 00:00:00 Z
         | 
| 19 19 | 
             
            dependencies: 
         | 
| 20 20 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| 21 21 | 
             
              name: builder
         | 
| @@ -67,6 +67,8 @@ files: | |
| 67 67 | 
             
            - COPYING
         | 
| 68 68 | 
             
            - README
         | 
| 69 69 | 
             
            - Rakefile
         | 
| 70 | 
            +
            - demo/wiki.html
         | 
| 71 | 
            +
            - demo/wiki.rb
         | 
| 70 72 | 
             
            - lib/wunderbar.rb
         | 
| 71 73 | 
             
            - lib/wunderbar/builder.rb
         | 
| 72 74 | 
             
            - lib/wunderbar/cgi-methods.rb
         | 
| @@ -79,8 +81,9 @@ files: | |
| 79 81 | 
             
            - test/test_builder.rb
         | 
| 80 82 | 
             
            - test/test_html_markup.rb
         | 
| 81 83 | 
             
            - test/test_logger.rb
         | 
| 82 | 
            -
            -  | 
| 84 | 
            +
            - tools/web2script.rb
         | 
| 83 85 | 
             
            - Manifest
         | 
| 86 | 
            +
            - wunderbar.gemspec
         | 
| 84 87 | 
             
            homepage: http://github.com/rubys/wunderbar
         | 
| 85 88 | 
             
            licenses: []
         | 
| 86 89 |  |