wunderbar 0.8.0

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/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2009 Sam Ruby <rubys@intertwingly.net>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,13 @@
1
+ COPYING
2
+ README
3
+ Rakefile
4
+ lib/cgi-spa.rb
5
+ lib/wunderbar/builder.rb
6
+ lib/wunderbar/cgi-methods.rb
7
+ lib/wunderbar/environment.rb
8
+ lib/wunderbar/html-methods.rb
9
+ lib/wunderbar/installation.rb
10
+ lib/wunderbar/job-control.rb
11
+ lib/wunderbar/version.rb
12
+ test/test_html_markup.rb
13
+ Manifest
data/README ADDED
@@ -0,0 +1,61 @@
1
+ = cgi-spa: Common Gateway Interface for Single Page Applications
2
+
3
+ CGI-SPA provides a number of globals, helper methods, and monkey patches which
4
+ simplify the development of single page applications in the form of CGI
5
+ scripts.
6
+
7
+ == Globals provided
8
+ * $cgi - Common Gateway Interface
9
+ * $param - Access to parameters (read-only OpenStruct like interface)
10
+ * $env - Access to environment variables (read-only OpenStruct like interface)
11
+ * $x - XmlBuilder instance
12
+ * $USER - Host user id
13
+ * $HOME - Home directory
14
+ * $SERVER- Server name
15
+ * SELF - Request URI
16
+ * SELF? - Request URI with '?' appended (avoids spoiling the cache)
17
+
18
+ * $USER - user
19
+ * $HOME - user's home directory
20
+ * $HOST - server host
21
+
22
+ * $HTTP_GET - request is an HTTP GET
23
+ * $HTTP_POST - request is an HTTP POST
24
+ * $XHR_JSON - request is XmlHttpRequest for JSON
25
+ * $XHTML - user agent accepts XHTML responses
26
+
27
+ == HTML methods
28
+ * style! - argument is indented text/data
29
+ * system! - run command and capture output
30
+ * script! - argument is indented text/data
31
+ * body? - capture exceptions, and produce a stack traceback
32
+
33
+ == CGI methods
34
+ * json - produce JSON output using the block specified
35
+ * json! - produce JSON output using the block specified and exit
36
+ * html - produce HTML output using the block specified
37
+ * html! - produce HTML output using the block specified and exit
38
+ * post - execute block only if method is POST
39
+ * post! - if POST, produce HTML output using the block specified and exit
40
+
41
+ == Helper methods
42
+ * submit: runs command (or block) as a deamon process
43
+
44
+ == OpenStruct methods (for $params and $env)
45
+ * untaint_if_match: untaints value if it matches a regular expression
46
+
47
+ == Builder extensions
48
+ * indented_text: matches text indentation to markup
49
+ * indented_data: useful for script and styles in HTML syntax
50
+ * traceback!: formats an exception traceback
51
+ * method_missing: patched to ensure open tags are closed
52
+
53
+ == Command line options
54
+ When run from the command line, CGI name=value pairs can be specified.
55
+ Additionally, the following options are supported:
56
+ * --html: HTML (HTTP GET) output is expected
57
+ * --post: HTML (HTTP POST) output is expected
58
+ * --json: JSON (XML HTTP Request) output is expected
59
+ * --xhtml: XHTML output is expected
60
+ * --prompt: prompt for key/value pairs using stdin
61
+ * --install=path: produce an suexec-callable wrapper script
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ require File.expand_path(File.dirname(__FILE__) + "/lib/wunderbar/version")
6
+
7
+ Echoe.new('wunderbar', Wunderbar::VERSION::STRING) do |p|
8
+ p.summary = "CGI Builder"
9
+ p.description = <<-EOF
10
+ Provides a number of globals, helper methods, and monkey patches which
11
+ simplify the development of generation of HTML and other artifacts for
12
+ CGI scripts.
13
+ EOF
14
+ p.url = "http://github.com/rubys/wunderbar"
15
+ p.author = "Sam Ruby"
16
+ p.email = "rubys@intertwingly.net"
17
+ p.dependencies = %w(
18
+ builder
19
+ json
20
+ )
21
+ end
22
+
23
+ Rake::TestTask.new do |t|
24
+ t.libs << 'test'
25
+ t.test_files = FileList['test/test*.rb']
26
+ end
data/lib/cgi-spa.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'cgi'
2
+ require 'builder'
3
+ require 'json'
4
+
5
+ require 'cgi-spa/environment'
6
+ require 'cgi-spa/cgi-methods'
7
+ require 'cgi-spa/html-methods'
8
+ require 'cgi-spa/job-control'
9
+ require 'cgi-spa/installation'
10
+ require 'cgi-spa/builder'
11
+
@@ -0,0 +1,107 @@
1
+ # add indented_text!, indented_data! and traceback! methods to builder
2
+ module Builder
3
+ class XmlMarkup
4
+ unless method_defined? :indented_text!
5
+ def indented_text!(text)
6
+ indented_data!(text) {|data| text! data}
7
+ end
8
+ end
9
+
10
+ unless method_defined? :indented_data!
11
+ def indented_data!(data, &block)
12
+ return if data.strip.length == 0
13
+
14
+ if @indent > 0
15
+ data.sub! /\n\s*\Z/, ''
16
+ data.sub! /\A\s*\n/, ''
17
+
18
+ unindent = data.sub(/s+\Z/,'').scan(/^ +/).map(&:length).min || 0
19
+
20
+ before = ::Regexp.new('^'.ljust(unindent+1))
21
+ after = " " * (@level * @indent)
22
+ data.gsub! before, after
23
+ end
24
+
25
+ if block
26
+ block.call(data)
27
+ else
28
+ self << data
29
+ end
30
+
31
+ _newline unless data =~ /\n\Z/
32
+ end
33
+ end
34
+
35
+ unless method_defined? :traceback!
36
+ def traceback!(exception=$!, klass='traceback')
37
+ pre :class=>klass do
38
+ text! exception.inspect
39
+ _newline
40
+ exception.backtrace.each {|frame| text!((' '*@indent)+frame + "\n")}
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # monkey patch to ensure that tags are closed
48
+ test =
49
+ Builder::XmlMarkup.new.html do |x|
50
+ x.body do
51
+ begin
52
+ x.p do
53
+ raise Exception.new('boom')
54
+ end
55
+ rescue Exception => e
56
+ x.pre e
57
+ end
58
+ end
59
+ end
60
+
61
+ if test.index('<p>') and !test.index('</p>')
62
+ module Builder
63
+ class XmlMarkup
64
+ def method_missing(sym, *args, &block)
65
+ text = nil
66
+ attrs = nil
67
+ sym = "#{sym}:#{args.shift}" if args.first.kind_of?(Symbol)
68
+ args.each do |arg|
69
+ case arg
70
+ when Hash
71
+ attrs ||= {}
72
+ attrs.merge!(arg)
73
+ else
74
+ text ||= ''
75
+ text << arg.to_s
76
+ end
77
+ end
78
+ if block
79
+ unless text.nil?
80
+ raise ArgumentError, "XmlMarkup cannot mix a text argument with a block"
81
+ end
82
+ _indent
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
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,117 @@
1
+ # produce json
2
+ def $cgi.json
3
+ return unless $XHR_JSON
4
+ output = yield
5
+ rescue Exception => exception
6
+ Kernel.print "Status: 500 Internal Error\r\n"
7
+ output = {
8
+ :exception => exception.inspect,
9
+ :backtrace => exception.backtrace
10
+ }
11
+ ensure
12
+ if $XHR_JSON
13
+ Kernel.print "Status: 404 Not Found\r\n" unless output
14
+ $cgi.out? 'type' => 'application/json', 'Cache-Control' => 'no-cache' do
15
+ begin
16
+ JSON.pretty_generate(output)+ "\n"
17
+ rescue
18
+ output.to_json + "\n"
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # produce json and quit
25
+ def $cgi.json! &block
26
+ return unless $XHR_JSON
27
+ json(&block)
28
+ Process.exit
29
+ end
30
+
31
+ # produce text
32
+ def $cgi.text &block
33
+ return unless $TEXT
34
+ @output = []
35
+ def $cgi.puts(line='')
36
+ @output << line + "\n"
37
+ end
38
+ def $cgi.print(line=nil)
39
+ @output << line
40
+ end
41
+ self.instance_eval &block
42
+ rescue Exception => exception
43
+ Kernel.print "Status: 500 Internal Error\r\n"
44
+ @output << "\n" unless @output.empty?
45
+ @output << exception.inspect + "\n"
46
+ exception.backtrace.each {|frame| @output << " #{frame}\n"}
47
+ ensure
48
+ class << $cgi
49
+ undef puts
50
+ undef print
51
+ end
52
+ if $TEXT
53
+ Kernel.print "Status: 404 Not Found\r\n" if @output.empty?
54
+ $cgi.out? 'type' => 'text/plain', 'Cache-Control' => 'no-cache' do
55
+ @output.join
56
+ end
57
+ @output = nil
58
+ end
59
+ end
60
+
61
+ # produce text and quit
62
+ def $cgi.text! &block
63
+ return unless $TEXT
64
+ json(&block)
65
+ Process.exit
66
+ end
67
+
68
+ # Conditionally provide output, based on ETAG
69
+ def $cgi.out?(headers, &block)
70
+ content = block.call
71
+ require 'digest/md5'
72
+ etag = Digest::MD5.hexdigest(content)
73
+
74
+ if ENV['HTTP_IF_NONE_MATCH'] == etag.inspect
75
+ Kernel.print "Status: 304 Not Modified\r\n\r\n"
76
+ else
77
+ $cgi.out headers.merge('Etag' => etag.inspect) do
78
+ content
79
+ end
80
+ end
81
+ end
82
+
83
+ # produce html/xhtml
84
+ def $cgi.html(&block)
85
+ return if $XHR_JSON or $TEXT
86
+ x = HtmlMarkup.new
87
+ if $XHTML
88
+ $cgi.out? 'type' => 'application/xhtml+xml', 'charset' => 'UTF-8' do
89
+ x._! "\xEF\xBB\xBF"
90
+ x.declare! :DOCTYPE, :html
91
+ x.html :xmlns => 'http://www.w3.org/1999/xhtml', &block
92
+ end
93
+ else
94
+ $cgi.out? 'type' => 'text/html', 'charset' => 'UTF-8' do
95
+ x._! "\xEF\xBB\xBF"
96
+ x.declare! :DOCTYPE, :html
97
+ x.html &block
98
+ end
99
+ end
100
+ end
101
+
102
+ # produce html and quit
103
+ def $cgi.html! &block
104
+ return if $XHR_JSON or $TEXT
105
+ html(&block)
106
+ Process.exit
107
+ end
108
+
109
+ # post specific logic (doesn't produce output)
110
+ def $cgi.post
111
+ yield if $HTTP_POST
112
+ end
113
+
114
+ # post specific content (produces output)
115
+ def $cgi.post! &block
116
+ html!(&block) if $HTTP_POST
117
+ end
@@ -0,0 +1,108 @@
1
+ # safety first!
2
+ $SAFE = 1 unless $UNSAFE or $SAFE > 1
3
+
4
+ # explicit request types
5
+ $HTTP_GET = ARGV.delete('--html')
6
+ $HTTP_POST = ARGV.delete('--post')
7
+ $XHR_JSON = ARGV.delete('--json')
8
+ $XHTML = ARGV.delete('--xhtml')
9
+ $TEXT = ARGV.delete('--text')
10
+
11
+ # Only prompt if explicitly asked for
12
+ ARGV.push '' if ARGV.empty?
13
+ ARGV.delete('--prompt') or ARGV.delete('--offline')
14
+
15
+ # standard objects
16
+ $cgi = CGI.new
17
+
18
+ def $cgi.validate(validations)
19
+ $SAFE = 1
20
+ $cgi.params.each do |key, values|
21
+ next unless validations.include? key
22
+ values.each do |value|
23
+ unless value =~ validations[key]
24
+ raise SecurityError.new("CGI parameter #{key} doesn't match #{validations[key]}")
25
+ end
26
+ value.untaint
27
+ end
28
+ end
29
+ end
30
+
31
+ $param = $cgi.params
32
+ $x = Builder::XmlMarkup.new :indent => 2
33
+
34
+ # implied request types
35
+ $HTTP_GET ||= ($cgi.request_method == 'GET')
36
+ $HTTP_POST ||= ($cgi.request_method == 'POST')
37
+ $XHR_JSON ||= ($cgi.accept.to_s =~ /json/)
38
+ $XHTML ||= ($cgi.accept.to_s =~ /xhtml/)
39
+ $TEXT ||= ($cgi.accept.to_s =~ /plain/ and $cgi.accept.to_s !~ /html/)
40
+
41
+ # get arguments if CGI couldn't find any...
42
+ $param.merge!(CGI.parse(ARGV.join('&'))) if $param.empty?
43
+
44
+ module Wunderbar
45
+ module Untaint
46
+ def untaint_if_match regexp
47
+ self.untaint if regexp.match(self)
48
+ end
49
+ end
50
+ end
51
+
52
+ # fast path for accessing CGI parameters
53
+ def $param.method_missing(name)
54
+ if has_key? name.to_s
55
+ if self[name.to_s].length == 1
56
+ self[name.to_s].first.extend(Wunderbar::Untaint)
57
+ else
58
+ self[name.to_s].join
59
+ end
60
+ end
61
+ end
62
+
63
+ $env = {}
64
+ def $env.method_missing(name)
65
+ delete name.to_s if ENV[name.to_s] != self[name.to_s]
66
+ if ENV[name.to_s] and not has_key?(name.to_s)
67
+ self[name.to_s]=ENV[name.to_s].dup.extend(Wunderbar::Untaint)
68
+ end
69
+ self[name.to_s]
70
+ end
71
+
72
+ # quick access to request_uri
73
+ SELF = ENV['REQUEST_URI'].to_s
74
+ def SELF?
75
+ if SELF.include? '?'
76
+ SELF
77
+ else
78
+ SELF + "?" # avoids spoiling the cache
79
+ end
80
+ end
81
+
82
+ # environment objects
83
+ $USER = ENV['REMOTE_USER'] || ENV['USER'] ||
84
+ if RUBY_PLATFORM =~ /darwin/
85
+ `dscl . -search /Users UniqueID #{Process.uid}`.split.first
86
+ else
87
+ `getent passwd #{Process.uid}`.split(':').first
88
+ end
89
+
90
+ $USER = $USER.dup.untaint if $USER =~ /^\w+$/
91
+
92
+ ENV['REMOTE_USER'] ||= $USER
93
+
94
+ $HOME = ENV['HOME'] || File.expand_path('~' + $USER)
95
+ $SERVER = ENV['HTTP_HOST'] || `hostname`.chomp
96
+
97
+ # more implied request types
98
+ $XHR_JSON ||= ($env.REQUEST_URI.to_s =~ /\?json$/)
99
+ $TEXT ||= ($env.REQUEST_URI.to_s =~ /\?text$/)
100
+
101
+ # set encoding to UTF-8
102
+ ENV['LANG'] ||= "en_US.UTF-8"
103
+ if defined? Encoding
104
+ Encoding.default_external = Encoding::UTF_8
105
+ Encoding.default_internal = Encoding::UTF_8
106
+ else
107
+ $KCODE = 'U'
108
+ end
@@ -0,0 +1,208 @@
1
+ # smart style, knows that the content is indented text/data
2
+ def $x.style!(text)
3
+ text.slice! /^\n/
4
+ text.slice! /[ ]+\z/
5
+ $x.style :type => "text/css" do
6
+ if $XHTML
7
+ indented_text! text
8
+ else
9
+ indented_data! text
10
+ end
11
+ end
12
+ end
13
+
14
+ # smart script, knows that the content is indented text/data
15
+ def $x.script!(text)
16
+ text.slice! /^\n/
17
+ text.slice! /[ ]+\z/
18
+ $x.script :lang => "text/javascript" do
19
+ if $XHTML
20
+ indented_text! text
21
+ else
22
+ indented_data! text
23
+ end
24
+ end
25
+ end
26
+
27
+ # execute a system command, echoing stdin, stdout, and stderr
28
+ def $x.system!(command, opts={})
29
+ require 'open3'
30
+ output_class = opts[:class] || {}
31
+ stdin = output_class[:stdin] || 'stdin'
32
+ stdout = output_class[:stdout] || 'stdout'
33
+ stderr = output_class[:stderr] || 'stderr'
34
+
35
+ $x.pre command, :class=>stdin unless opts[:echo] == false
36
+
37
+ require 'thread'
38
+ semaphore = Mutex.new
39
+ Open3.popen3(command) do |pin, pout, perr|
40
+ [
41
+ Thread.new do
42
+ until pout.eof?
43
+ out_line = pout.readline.chomp
44
+ semaphore.synchronize { $x.pre out_line, :class=>stdout }
45
+ end
46
+ end,
47
+
48
+ Thread.new do
49
+ until perr.eof?
50
+ err_line = perr.readline.chomp
51
+ semaphore.synchronize { $x.pre err_line, :class=>stderr }
52
+ end
53
+ end,
54
+
55
+ Thread.new do
56
+ if opts[:stdin].respond_to? :read
57
+ require 'fileutils'
58
+ FileUtils.copy_stream opts[:stdin], pin
59
+ elsif opts[:stdin]
60
+ pin.write opts[:stdin].to_s
61
+ end
62
+ pin.close
63
+ end
64
+ ].each {|thread| thread.join}
65
+ end
66
+ end
67
+
68
+ def $x.body? args={}
69
+ traceback_class = args.delete('traceback_class')
70
+ traceback_style = args.delete('traceback_style')
71
+ traceback_style ||= 'background-color:#ff0; margin: 1em 0; padding: 1em; ' +
72
+ 'border: 4px solid red; border-radius: 1em'
73
+ $x.body(args) do
74
+ begin
75
+ yield
76
+ rescue ::Exception => exception
77
+ text = exception.inspect
78
+ exception.backtrace.each {|frame| text += "\n #{frame}"}
79
+
80
+ if traceback_class
81
+ $x.pre text, :class=>traceback_class
82
+ else
83
+ $x.pre text, :style=>traceback_style
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ # Wrapper class that understands HTML
90
+ class HtmlMarkup
91
+ VOID = %w(
92
+ area base br col command embed hr img input keygen
93
+ link meta param source track wbr
94
+ )
95
+
96
+ def initialize(*args, &block)
97
+ # as a migration aide, use the global variable, but consider that
98
+ # to be deprecated.
99
+ $x ||= Builder::XmlMarkup.new :indent => 2
100
+ @x = $x
101
+ end
102
+
103
+ def html(*args, &block)
104
+ @x.html(*args) { instance_exec(@x, &block) }
105
+ end
106
+
107
+ def method_missing(name, *args, &block)
108
+ if name.to_s =~ /^_(\w+)(!|\?|)$/
109
+ name, flag = $1, $2
110
+ else
111
+ error = NameError.new "undefined local variable or method `#{name}'", name
112
+ error.set_backtrace caller
113
+ raise error
114
+ end
115
+
116
+ if flag != '!'
117
+ if %w(script style).include?(name)
118
+ if String === args.first and not block
119
+ text = args.shift
120
+ if $XHTML
121
+ block = Proc.new {@x.indented_text! text}
122
+ else
123
+ block = Proc.new {@x.indented_data! text}
124
+ end
125
+ end
126
+
127
+ args << {} if args.length == 0
128
+ if Hash === args.last
129
+ args.last[:lang] ||= 'text/javascript' if name == 'script'
130
+ args.last[:type] ||= 'text/css' if name == 'style'
131
+ end
132
+ end
133
+
134
+ # ensure that non-void elements are explicitly closed
135
+ if args.length == 0 or (args.length == 1 and Hash === args.first)
136
+ args.unshift '' if not VOID.include?(name) and not block
137
+ end
138
+
139
+ # remove attributes with nil values
140
+ args.last.delete_if {|key, value| value == nil} if Hash === args.last
141
+ end
142
+
143
+ if flag == '!'
144
+ # turn off indentation
145
+ indent, level = @x.instance_eval { [@indent, @level] }
146
+ begin
147
+ @x.instance_eval { [@indent=0, @level=0] }
148
+ @x.text! " "*indent*level
149
+ @x.tag! name, *args, &block
150
+ ensure
151
+ @x.text! "\n"
152
+ @x.instance_eval { [@indent=indent, @level=level] }
153
+ end
154
+ elsif flag == '?'
155
+ # capture exceptions, produce filtered tracebacks
156
+ options = (Hash === args.last)? args.last : {}
157
+ traceback_class = options.delete(:traceback_class)
158
+ traceback_style = options.delete(:traceback_style)
159
+ traceback_style ||= 'background-color:#ff0; margin: 1em 0; ' +
160
+ 'padding: 1em; border: 4px solid red; border-radius: 1em'
161
+ @x.tag!(name, *args) do
162
+ begin
163
+ block.call
164
+ rescue ::Exception => exception
165
+ text = exception.inspect
166
+ exception.backtrace.each do |frame|
167
+ next if frame =~ %r{/cgi-spa/}
168
+ next if frame =~ %r{/gems/.*/builder/}
169
+ text += "\n #{frame}"
170
+ end
171
+
172
+ if traceback_class
173
+ $x.pre text, :class=>traceback_class
174
+ else
175
+ $x.pre text, :style=>traceback_style
176
+ end
177
+ end
178
+ end
179
+ else
180
+ @x.tag! name, *args, &block
181
+ end
182
+ end
183
+
184
+ def _head(*args, &block)
185
+ @x.tag!('head', *args) do
186
+ @x.meta :charset => 'utf-8' unless $XHTML
187
+ block.call if block
188
+ end
189
+ end
190
+
191
+ def _(text=nil)
192
+ @x.indented_text! text if text
193
+ @x
194
+ end
195
+
196
+ def _!(text=nil)
197
+ @x.text! text if text
198
+ @x
199
+ end
200
+
201
+ def declare!(*args)
202
+ @x.declare!(*args)
203
+ end
204
+
205
+ def target!
206
+ @x.target!
207
+ end
208
+ end
@@ -0,0 +1,54 @@
1
+ # option to create an suexec callable wrapper
2
+ install = ARGV.find {|arg| arg =~ /--install=(.*)/}
3
+ if install and ARGV.delete(install)
4
+
5
+ # scope out the situation
6
+ dest = File.expand_path($1)
7
+ main = File.expand_path(caller.last[/^(.*):\d+(:|$)/,1])
8
+
9
+ # if destination is a directory, determine an appropriate file name
10
+ if File.directory?(dest)
11
+ if dest =~ /\/cgi-bin\/?$/
12
+ dest = File.join(dest, File.basename(main))
13
+ else
14
+ dest = File.join(dest, File.basename(main).sub(/\.rb$/,'.cgi'))
15
+ end
16
+ end
17
+
18
+ # prevent accidental overwrite
19
+ if File.exist?(dest) and not ARGV.delete('-f')
20
+ STDERR.puts "File #{dest} already exists. (Specify -f to overwrite)"
21
+ Process.exit
22
+ end
23
+
24
+ # ensure destination directory exists
25
+ destdir = File.dirname(dest)
26
+ if not File.exist?(destdir) or not File.directory?(destdir)
27
+ STDERR.puts "Directory #{destdir} does not exist."
28
+ Process.exit
29
+ end
30
+
31
+ # output wrapper
32
+ open(dest,'w') do |file|
33
+ # she-bang
34
+ file.puts "#!" + File.join(
35
+ RbConfig::CONFIG["bindir"],
36
+ RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"]
37
+ )
38
+
39
+ # Change directory
40
+ file.puts "Dir.chdir #{File.dirname(main).inspect}"
41
+
42
+ # Optional data from the script (after __END__)
43
+ file.puts DATA.read if Object.const_defined? :DATA
44
+
45
+ # Load script
46
+ file.puts "require #{File.basename(main).sub(/\.rb$/,'').inspect}"
47
+ end
48
+
49
+ # Mark wrapper as executable
50
+ File.chmod(0755, dest)
51
+
52
+ # Don't execute the script itself at this time
53
+ Process.exit
54
+ end
@@ -0,0 +1,30 @@
1
+ # run command/block as a background daemon
2
+ def submit(cmd=nil)
3
+ fork do
4
+ # detach from tty
5
+ Process.setsid
6
+ fork and exit
7
+
8
+ # clear working directory and mask
9
+ Dir.chdir '/'
10
+ File.umask 0000
11
+
12
+ # close open files
13
+ STDIN.reopen '/dev/null'
14
+ STDOUT.reopen '/dev/null', 'a'
15
+ STDERR.reopen STDOUT
16
+
17
+ # clear environment of cgi cruft
18
+ ENV.keys.to_a.each do |key|
19
+ ENV.delete(key) if key =~ /HTTP/ or $cgi.respond_to? key.downcase
20
+ end
21
+
22
+ # setup environment
23
+ ENV['USER'] ||= $USER
24
+ ENV['HOME'] ||= $HOME
25
+
26
+ # run cmd and/or block
27
+ system cmd if cmd
28
+ yield if block_given?
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ module Wunderbar
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 8
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end unless defined?(Wunderbar::VERSION)
@@ -0,0 +1,120 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'cgi-spa'
4
+
5
+ class HtmlMarkupTest < Test::Unit::TestCase
6
+ def setup
7
+ $x = nil # until this hack is removed html-methods.rb
8
+ end
9
+
10
+ def test_html
11
+ x = HtmlMarkup.new
12
+ x.html {}
13
+ assert_equal %{<html>\n</html>\n}, x.target!
14
+ end
15
+
16
+ def test_void_element
17
+ x = HtmlMarkup.new
18
+ x.html {_br}
19
+ assert_match %r{<br/>}, x.target!
20
+ end
21
+
22
+ def test_normal_element
23
+ x = HtmlMarkup.new
24
+ x.html {_textarea}
25
+ assert_match %r{<textarea></textarea>}, x.target!
26
+ end
27
+
28
+ def test_script_lang
29
+ x = HtmlMarkup.new
30
+ x.html {_script}
31
+ assert_match %r[<script lang="text/javascript">], x.target!
32
+ end
33
+
34
+ def test_style_type
35
+ x = HtmlMarkup.new
36
+ x.html {_style}
37
+ assert_match %r[<style type="text/css">], x.target!
38
+ end
39
+
40
+ def test_script_plain
41
+ x = HtmlMarkup.new
42
+ x.html {_script! 'alert("foo");'}
43
+ assert_match %r[<script>alert\("foo"\);</script>], x.target!
44
+ end
45
+
46
+ def test_script_indent
47
+ x = HtmlMarkup.new
48
+ x.html {_script "if (i<1) {}"}
49
+ assert_match %r[^ if], x.target!
50
+ end
51
+
52
+ def test_script_html
53
+ $XHTML = false
54
+ x = HtmlMarkup.new
55
+ x.html {_script "if (i<1) {}"}
56
+ assert_match %r[<script.*>\s*if \(i<1\) \{\}\s*</script>], x.target!
57
+ end
58
+
59
+ def test_script_xhtml
60
+ $XHTML = true
61
+ x = HtmlMarkup.new
62
+ x.html {_script "if (i<1) {}"}
63
+ assert_match %r[<script.*>\s*if \(i&lt;1\) \{\}\s*</script>], x.target!
64
+ end
65
+
66
+ def test_disable_indent
67
+ x = HtmlMarkup.new
68
+ x.html {_div! {_ "one "; _strong "two"; _ " three"}}
69
+ assert_match %r[<div>one <strong>two</strong> three</div>], x.target!
70
+ end
71
+
72
+ def test_traceback
73
+ x = HtmlMarkup.new
74
+ x.html {_body? {boom}}
75
+ assert_match %r[<pre.*>#&lt;NameError: .*boom],
76
+ x.target!
77
+ end
78
+
79
+ def test_traceback_default_style
80
+ x = HtmlMarkup.new
81
+ x.html {_body? {boom}}
82
+ assert_match %r[<pre style="background-color:#ff0.*">], x.target!
83
+ end
84
+
85
+ def test_traceback_style_override
86
+ x = HtmlMarkup.new
87
+ x.html {_body?(:traceback_style => 'color:red') {boom}}
88
+ assert_match %r[<pre style="color:red"], x.target!
89
+ end
90
+
91
+ def test_traceback_class_override
92
+ x = HtmlMarkup.new
93
+ x.html {_body?(:traceback_class => 'traceback') {boom}}
94
+ assert_match %r[<pre class="traceback"], x.target!
95
+ end
96
+
97
+ def test_meta_charset
98
+ x = HtmlMarkup.new
99
+ x.html {_head}
100
+ assert_match %r[<head>\s*<meta charset="utf-8"/>\s*</head>], x.target!
101
+ end
102
+
103
+ def test_indented_text
104
+ x = HtmlMarkup.new
105
+ x.html {_div {_ 'text'}}
106
+ assert_match %r[^ <div>\n text\n </div>], x.target!
107
+ end
108
+
109
+ def test_unindented_text
110
+ x = HtmlMarkup.new
111
+ x.html {_div {_! "text\n"}}
112
+ assert_match %r[^ <div>\ntext\n </div>], x.target!
113
+ end
114
+
115
+ def test_declare
116
+ x = HtmlMarkup.new
117
+ x.declare! :DOCTYPE, 'html'
118
+ assert_equal %{<!DOCTYPE "html">\n}, x.target!
119
+ end
120
+ end
data/wunderbar.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "wunderbar"
5
+ s.version = "0.8.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Sam Ruby"]
9
+ s.date = "2012-02-02"
10
+ s.description = " Provides a number of globals, helper methods, and monkey patches which\n simplify the development of generation of HTML and other artifacts for\n CGI scripts.\n"
11
+ s.email = "rubys@intertwingly.net"
12
+ s.extra_rdoc_files = ["COPYING", "README", "lib/cgi-spa.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/version.rb"]
13
+ s.files = ["COPYING", "README", "Rakefile", "lib/cgi-spa.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/version.rb", "test/test_html_markup.rb", "Manifest", "wunderbar.gemspec"]
14
+ s.homepage = "http://github.com/rubys/wunderbar"
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Wunderbar", "--main", "README"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = "wunderbar"
18
+ s.rubygems_version = "1.8.15"
19
+ s.summary = "CGI Builder"
20
+ s.test_files = ["test/test_html_markup.rb"]
21
+
22
+ if s.respond_to? :specification_version then
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<builder>, [">= 0"])
27
+ s.add_runtime_dependency(%q<json>, [">= 0"])
28
+ else
29
+ s.add_dependency(%q<builder>, [">= 0"])
30
+ s.add_dependency(%q<json>, [">= 0"])
31
+ end
32
+ else
33
+ s.add_dependency(%q<builder>, [">= 0"])
34
+ s.add_dependency(%q<json>, [">= 0"])
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wunderbar
3
+ version: !ruby/object:Gem::Version
4
+ hash: 63
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 8
9
+ - 0
10
+ version: 0.8.0
11
+ platform: ruby
12
+ authors:
13
+ - Sam Ruby
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-02-02 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: builder
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: json
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: " Provides a number of globals, helper methods, and monkey patches which\n simplify the development of generation of HTML and other artifacts for\n CGI scripts.\n"
49
+ email: rubys@intertwingly.net
50
+ executables: []
51
+
52
+ extensions: []
53
+
54
+ extra_rdoc_files:
55
+ - COPYING
56
+ - README
57
+ - lib/cgi-spa.rb
58
+ - lib/wunderbar/builder.rb
59
+ - lib/wunderbar/cgi-methods.rb
60
+ - lib/wunderbar/environment.rb
61
+ - lib/wunderbar/html-methods.rb
62
+ - lib/wunderbar/installation.rb
63
+ - lib/wunderbar/job-control.rb
64
+ - lib/wunderbar/version.rb
65
+ files:
66
+ - COPYING
67
+ - README
68
+ - Rakefile
69
+ - lib/cgi-spa.rb
70
+ - lib/wunderbar/builder.rb
71
+ - lib/wunderbar/cgi-methods.rb
72
+ - lib/wunderbar/environment.rb
73
+ - lib/wunderbar/html-methods.rb
74
+ - lib/wunderbar/installation.rb
75
+ - lib/wunderbar/job-control.rb
76
+ - lib/wunderbar/version.rb
77
+ - test/test_html_markup.rb
78
+ - Manifest
79
+ - wunderbar.gemspec
80
+ homepage: http://github.com/rubys/wunderbar
81
+ licenses: []
82
+
83
+ post_install_message:
84
+ rdoc_options:
85
+ - --line-numbers
86
+ - --inline-source
87
+ - --title
88
+ - Wunderbar
89
+ - --main
90
+ - README
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 11
108
+ segments:
109
+ - 1
110
+ - 2
111
+ version: "1.2"
112
+ requirements: []
113
+
114
+ rubyforge_project: wunderbar
115
+ rubygems_version: 1.8.15
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: CGI Builder
119
+ test_files:
120
+ - test/test_html_markup.rb