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
|
|