wunderbar 0.8.0

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