wunderbar 0.8.5 → 0.8.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|