exocora 0.1.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/LICENSE +27 -0
- data/examples/hello_world.cgi +12 -0
- data/examples/hello_world.rhtml +8 -0
- data/examples/request.cgi +12 -0
- data/examples/request.rhtml +35 -0
- data/examples/validation.cgi +20 -0
- data/examples/validation.rhtml +20 -0
- data/lib/array.rb +18 -0
- data/lib/cgi.rb +46 -0
- data/lib/exocora/errors.rb +47 -0
- data/lib/exocora/sheet.rb +205 -0
- data/lib/exocora/support.rb +4 -0
- data/lib/exocora/validation.rb +73 -0
- data/lib/exocora/version.rb +8 -0
- data/lib/exocora.rb +26 -0
- data/lib/string.rb +62 -0
- metadata +70 -0
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2008 Aphyr <aphyr@aphyr.com>.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* Neither the name of the <ORGANIZATION> nor the names of its contributors
|
15
|
+
may be used to endorse or promote products derived from this software
|
16
|
+
without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Request Test</title>
|
4
|
+
</head>
|
5
|
+
<body>
|
6
|
+
<h1>Inferred URI information</h1>
|
7
|
+
<ul>
|
8
|
+
<li>Host URI: <%== @r.host_uri %></li>
|
9
|
+
<li>Relative URI: <%==@r.relative_uri %></li>
|
10
|
+
<li>Full URI: <%== @r.uri %></li>
|
11
|
+
</ul>
|
12
|
+
|
13
|
+
<h1>Request Parameters</h1>
|
14
|
+
<% names = [
|
15
|
+
'AUTH_TYPE', 'HTTP_HOST', 'REMOTE_IDENT',
|
16
|
+
'CONTENT_LENGTH', 'HTTP_NEGOTIATE', 'REMOTE_USER',
|
17
|
+
'CONTENT_TYPE', 'HTTP_PRAGMA', 'REQUEST_METHOD',
|
18
|
+
'GATEWAY_INTERFACE', 'HTTP_REFERER', 'SCRIPT_NAME',
|
19
|
+
'HTTP_ACCEPT', 'HTTP_USER_AGENT', 'SERVER_NAME',
|
20
|
+
'HTTP_ACCEPT_CHARSET', 'PATH_INFO', 'SERVER_PORT',
|
21
|
+
'HTTP_ACCEPT_ENCODING', 'PATH_TRANSLATED', 'SERVER_PROTOCOL',
|
22
|
+
'HTTP_ACCEPT_LANGUAGE', 'QUERY_STRING', 'SERVER_SOFTWARE',
|
23
|
+
'HTTP_CACHE_CONTROL', 'REMOTE_ADDR',
|
24
|
+
'HTTP_FROM', 'REMOTE_HOST'
|
25
|
+
] %>
|
26
|
+
<table>
|
27
|
+
<% names.each do |name| %>
|
28
|
+
<tr>
|
29
|
+
<td><%== name %></td>
|
30
|
+
<td><%== @r.send(name.downcase.sub(/^http_/, '')).inspect %></td>
|
31
|
+
</tr>
|
32
|
+
<% end %>
|
33
|
+
</table>
|
34
|
+
</body>
|
35
|
+
</html>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/../lib/exocora'
|
4
|
+
#require '/home/aphyr/exocora/lib/exocora'
|
5
|
+
|
6
|
+
class Validation < Exocora::Sheet
|
7
|
+
|
8
|
+
validate(:short) do |value|
|
9
|
+
value.length < 4
|
10
|
+
end
|
11
|
+
|
12
|
+
validate(:num, "must be a number").when Numeric
|
13
|
+
validate(:empty).unless(lambda { |s| s.size > 0 }).because("must be empty")
|
14
|
+
|
15
|
+
def process
|
16
|
+
{:params => params, :errors => errors}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Validation.run
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Validation Test</title>
|
4
|
+
</head>
|
5
|
+
<body>
|
6
|
+
<h1>Errors</h1>
|
7
|
+
<ul>
|
8
|
+
<% @errors.each do |name, errors| %>
|
9
|
+
<li><%= name %> <%= errors.to_sentence %>.</li>
|
10
|
+
<% end %>
|
11
|
+
</ul>
|
12
|
+
|
13
|
+
<h1>All Parameters</h1>
|
14
|
+
<ul>
|
15
|
+
<% @params.each do |name, value| %>
|
16
|
+
<li><%= name %>: <%= value.inspect %></li>
|
17
|
+
<% end %>
|
18
|
+
</ul>
|
19
|
+
</body>
|
20
|
+
</html>
|
data/lib/array.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
class Array
|
2
|
+
# Convert an array to a nice sentence. Method is called on each element of the array, and should return a string.
|
3
|
+
def to_sentence(method = :to_s)
|
4
|
+
case size
|
5
|
+
when 0
|
6
|
+
''
|
7
|
+
when 1
|
8
|
+
first.send method
|
9
|
+
when 2
|
10
|
+
first.send(method) + ' and ' + last.send(method)
|
11
|
+
else
|
12
|
+
sentence = self[1..-2].inject(first.send(method)) do |sentence, element|
|
13
|
+
sentence += ', ' + element.send(method)
|
14
|
+
end
|
15
|
+
sentence + ', and ' + last.send(method)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/cgi.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
class CGI
|
2
|
+
HTTP_PORT = 80
|
3
|
+
HTTPS_PORT = 443
|
4
|
+
|
5
|
+
# Returns a full uri for a given uri fragment.
|
6
|
+
# full_uri_for 'foo.cgi' => 'http://localhost/dir/foo.cgi'
|
7
|
+
# full_uri_for '/images/' => 'http://localhost/images/'
|
8
|
+
def full_uri_for(fragment)
|
9
|
+
case fragment
|
10
|
+
when /^http:\/\//
|
11
|
+
# The fragment is a complete URI
|
12
|
+
fragment
|
13
|
+
when /^\//
|
14
|
+
# The fragment is relative to the root of this host
|
15
|
+
host_uri + fragment
|
16
|
+
else
|
17
|
+
# The fragment is relative to this directory
|
18
|
+
relative_uri + fragment
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Try to guess the uri for the host alone (http://host/)
|
23
|
+
def host_uri
|
24
|
+
uri = ''
|
25
|
+
case server_port
|
26
|
+
when HTTP_PORT
|
27
|
+
uri << 'http://' + server_name
|
28
|
+
when HTTPS_PORT
|
29
|
+
uri << 'https://' + server_name
|
30
|
+
else
|
31
|
+
uri << 'https://' + server_name + ':' + server_port
|
32
|
+
end
|
33
|
+
uri
|
34
|
+
end
|
35
|
+
|
36
|
+
# Try to guess the relative path for this request. (http://host/directory/)
|
37
|
+
def relative_uri
|
38
|
+
uri.sub(/[^\/]*$/, '')
|
39
|
+
end
|
40
|
+
|
41
|
+
# Try to guess the full uri for this script (http://host/directory/script.cgi)
|
42
|
+
def uri
|
43
|
+
host_uri.chop + script_name
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Exception
|
2
|
+
def to_html
|
3
|
+
"<p><b>#{self.class}</b>: #{Erubis::XmlHelper::escape_xml(to_s)}</p>" +
|
4
|
+
"<p>Backtrace:" +
|
5
|
+
'<ul>' + backtrace.map { |line|
|
6
|
+
'<li><code>' + Erubis::XmlHelper::escape_xml(line) + '</code></li>'
|
7
|
+
}.join + '</ul></p>'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Exocora
|
12
|
+
# An error encountered when validating data
|
13
|
+
class ValidationError < RuntimeError
|
14
|
+
attr_accessor :value, :message
|
15
|
+
def initialize(value, message = 'is invalid')
|
16
|
+
@value = value
|
17
|
+
@message = message
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
@message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Represents an error encountered in an Exocora sheet.
|
26
|
+
class SheetError < RuntimeError
|
27
|
+
end
|
28
|
+
|
29
|
+
# Represents an error encountered when processing a template.
|
30
|
+
class TemplateError < RuntimeError
|
31
|
+
attr_accessor :message, :exception
|
32
|
+
def initialize(message, exception)
|
33
|
+
@message = message
|
34
|
+
@exception = exception
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_html
|
38
|
+
"<p>" + Erubis::XmlHelper::escape_xml(@message) + "</p>" +
|
39
|
+
"<p>The original exception was:</p>" +
|
40
|
+
if @exception.respond_to? :to_html
|
41
|
+
@exception.to_html
|
42
|
+
else
|
43
|
+
Erubis::XmlHelper::escape_xml(@exception.to_s)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
module Exocora
|
2
|
+
# A single CGI script. Requests are processed through a chain of actions
|
3
|
+
#
|
4
|
+
# validate - extract and sanitize data from the CGI request
|
5
|
+
# process - do stuff
|
6
|
+
# render - render to the specified template.
|
7
|
+
class Sheet
|
8
|
+
# Parameters with validations
|
9
|
+
@@validations = Hash.new
|
10
|
+
|
11
|
+
attr_reader :params, :errors
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@cgi = CGI.new
|
15
|
+
@headers = {
|
16
|
+
}
|
17
|
+
|
18
|
+
@params = {}
|
19
|
+
@errors = {}
|
20
|
+
|
21
|
+
@template = self.class.to_s.module_path
|
22
|
+
@log_file = self.class.to_s.underscore + '.log'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds a validation condition for a parameter.
|
26
|
+
# validate :query { |q| q.size > 0 }
|
27
|
+
# validate :query, "is invalid" { |q|
|
28
|
+
def self.validate(param, message='must be valid', &block)
|
29
|
+
validation = Validation.new(nil, message)
|
30
|
+
if block_given?
|
31
|
+
validation.when block
|
32
|
+
end
|
33
|
+
|
34
|
+
@@validations[param.to_s] ||= []
|
35
|
+
@@validations[param.to_s] << validation
|
36
|
+
|
37
|
+
validation
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates an instance of this sheet, and runs it.
|
41
|
+
def self.run
|
42
|
+
new.run
|
43
|
+
end
|
44
|
+
|
45
|
+
# Casts incoming CGI parameters to nil, numerics, collapses single-element
|
46
|
+
# arrays, and so forth.
|
47
|
+
def cast_params(params)
|
48
|
+
cast = {}
|
49
|
+
params.each do |key, values|
|
50
|
+
cast_values = values.map do |value|
|
51
|
+
if value =~ /^\d+$/
|
52
|
+
value.to_i
|
53
|
+
elsif value =~ /^[\d.]+$/
|
54
|
+
value.to_f
|
55
|
+
else
|
56
|
+
value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if values.empty?
|
61
|
+
cast[key] = nil
|
62
|
+
elsif values.size == 1
|
63
|
+
cast[key] = cast_values.first
|
64
|
+
else
|
65
|
+
cast[key] = cast_values
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
cast
|
70
|
+
end
|
71
|
+
|
72
|
+
# Logs a message to a file.
|
73
|
+
def log(message)
|
74
|
+
File.open(@log_file, 'a') do |file|
|
75
|
+
file.puts message
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Chooses the file for logging.
|
80
|
+
def log_to(file)
|
81
|
+
@log_file = file
|
82
|
+
end
|
83
|
+
|
84
|
+
# Sends output to the client. The first time output is called, it sends an
|
85
|
+
# HTTP header first.
|
86
|
+
def output(string)
|
87
|
+
unless @headers_sent
|
88
|
+
puts @cgi.header(@headers)
|
89
|
+
@headers_sent = true
|
90
|
+
end
|
91
|
+
|
92
|
+
puts string
|
93
|
+
end
|
94
|
+
|
95
|
+
# Process is the meat of the script.
|
96
|
+
# It returns a hash like {:a => 2}, which become instance
|
97
|
+
# variables @a = 2 in the template.
|
98
|
+
def process
|
99
|
+
end
|
100
|
+
|
101
|
+
# Breaks the normal rendering flow, and outputs an HTTP redirect header.
|
102
|
+
def redirect_to(uri_fragment)
|
103
|
+
@headers['Status'] = '302 Moved'
|
104
|
+
@headers['Location'] = @cgi.full_uri_for uri_fragment
|
105
|
+
output
|
106
|
+
end
|
107
|
+
|
108
|
+
# Renders the erubis template for this action. Takes a hash of variables to
|
109
|
+
# render. The default template is determined by underscoring this sheet's
|
110
|
+
# class name, but another template can be specified using #template.
|
111
|
+
def render(context = Erubis::Context.new)
|
112
|
+
# Read template data
|
113
|
+
template_filename = "#{@template}.rhtml"
|
114
|
+
begin
|
115
|
+
template = File.read(template_filename)
|
116
|
+
rescue Errno::ENOENT
|
117
|
+
raise ScriptError.new("Template #{template_filename} does not exist!")
|
118
|
+
end
|
119
|
+
|
120
|
+
# Prepare template and variables
|
121
|
+
eruby = Erubis::Eruby.new template
|
122
|
+
|
123
|
+
# Perform templating
|
124
|
+
begin
|
125
|
+
result = eruby.evaluate context
|
126
|
+
rescue
|
127
|
+
raise TemplateError.new("Encountered error processing template #{template_filename}.", $!)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Output result
|
131
|
+
output result
|
132
|
+
end
|
133
|
+
|
134
|
+
# Accessor for the CGI object
|
135
|
+
def request
|
136
|
+
@cgi
|
137
|
+
end
|
138
|
+
|
139
|
+
# This is the action which initiates processing of the script.
|
140
|
+
def run
|
141
|
+
begin
|
142
|
+
# Cast parameters
|
143
|
+
@params = cast_params @cgi.params
|
144
|
+
|
145
|
+
# Check parameters
|
146
|
+
validate
|
147
|
+
|
148
|
+
# Run process
|
149
|
+
context = process
|
150
|
+
|
151
|
+
# Render output
|
152
|
+
render context
|
153
|
+
rescue
|
154
|
+
# Handle errors
|
155
|
+
output "<html><head><title>Exocora Error</title></head><body>"
|
156
|
+
output "<h1>#{$!.class}</h1>"
|
157
|
+
|
158
|
+
output $!.to_html
|
159
|
+
|
160
|
+
output "</body></html>"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Sets the name of the template to render.
|
165
|
+
def use_template(template)
|
166
|
+
@template = template
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns true if the specified (or all) parameters have no errors.
|
170
|
+
def valid?(param = nil)
|
171
|
+
if param.nil?
|
172
|
+
@errors.empty?
|
173
|
+
else
|
174
|
+
@errors[param].nil? or @errors[param].empty?
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Validates CGI parameters according to @@validations.
|
179
|
+
#
|
180
|
+
# Parameters which fail validation have entries recorderd in @errors.
|
181
|
+
def validate(params = @params)
|
182
|
+
@@validations.each do |param, validations|
|
183
|
+
validations.each do |validation|
|
184
|
+
values = params[param]
|
185
|
+
|
186
|
+
# If there are multiple values, check each one.
|
187
|
+
unless values.kind_of? Array
|
188
|
+
values = [values]
|
189
|
+
end
|
190
|
+
|
191
|
+
values.each do |value|
|
192
|
+
begin
|
193
|
+
validation.validate(value)
|
194
|
+
rescue ValidationError => e
|
195
|
+
@errors[param] ||= []
|
196
|
+
@errors[param] << e
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
params
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Exocora
|
2
|
+
# Validates a parameter. If a block is provided, the block is applied to
|
3
|
+
# values passed to apply; the return value determines if the value is valid.
|
4
|
+
# Otherwise, compares filter === value.
|
5
|
+
|
6
|
+
class Validation
|
7
|
+
attr_accessor :message, :filter, :negate
|
8
|
+
|
9
|
+
def initialize(filter = Object, message = 'must be valid')
|
10
|
+
@filter = filter
|
11
|
+
@message = message
|
12
|
+
@negate = false
|
13
|
+
end
|
14
|
+
|
15
|
+
# Change the message given when this validation fails.
|
16
|
+
def because(message)
|
17
|
+
@message = message
|
18
|
+
self
|
19
|
+
end
|
20
|
+
alias :explain :because
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"#{@message} #{@negate ? 'unless' : 'when'} #{@filter.inspect}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Adds the opposite of this filter
|
27
|
+
def unless(filter, &block)
|
28
|
+
@filter = block_given? ? block : filter
|
29
|
+
@negate = true
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Applies this validation to a valie. Returns true, or raises a
|
34
|
+
# ValidationError.
|
35
|
+
def validate(value)
|
36
|
+
# Treat the filter as a function or a rubric
|
37
|
+
if @filter.respond_to? :call
|
38
|
+
method = :call
|
39
|
+
else
|
40
|
+
method = :===
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check the value
|
44
|
+
begin
|
45
|
+
result = @filter.send method, value
|
46
|
+
rescue
|
47
|
+
result = false
|
48
|
+
end
|
49
|
+
|
50
|
+
# If we're a negative filter, negate the result
|
51
|
+
if negate
|
52
|
+
result = ! result
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return
|
56
|
+
if result
|
57
|
+
value
|
58
|
+
else
|
59
|
+
raise ValidationError.new(value, @message)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Sets the filter used
|
64
|
+
def if(filter, &block)
|
65
|
+
@filter = block_given? ? block : filter
|
66
|
+
@negate = false
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
alias :with :if
|
71
|
+
alias :when :if
|
72
|
+
end
|
73
|
+
end
|
data/lib/exocora.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Exocora is an obscure species of spider, in the sheet weaver family.
|
2
|
+
#
|
3
|
+
# It is also a small web framework for writing CGI scripts, or "sheets". Each
|
4
|
+
# request has a three-stage lifecycle, in which it extracts and checks incoming
|
5
|
+
# data for safety, performs some operations, and renders a templated result.
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'cgi'
|
9
|
+
require 'erubis'
|
10
|
+
|
11
|
+
APPDIR = File.dirname(File.expand_path($0))
|
12
|
+
BASEDIR = File.dirname(File.expand_path(__FILE__))
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift(BASEDIR)
|
15
|
+
$LOAD_PATH.uniq!
|
16
|
+
|
17
|
+
require 'string'
|
18
|
+
require 'array'
|
19
|
+
require 'cgi'
|
20
|
+
require 'exocora/support'
|
21
|
+
require 'exocora/errors'
|
22
|
+
require 'exocora/validation'
|
23
|
+
require 'exocora/sheet'
|
24
|
+
|
25
|
+
module Exocora
|
26
|
+
end
|
data/lib/string.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Provides support methods for string manipulation
|
2
|
+
|
3
|
+
class String
|
4
|
+
# Converts dashes, underscores, and spaces to camelcase
|
5
|
+
#
|
6
|
+
# "foo_bar_baz".camelcase => "FooBarBaz"
|
7
|
+
# "FooBarBaz".camelcase(false) => "fooBarBaz"
|
8
|
+
def camelcase(first_letter_capitalized = true)
|
9
|
+
# Convert separators
|
10
|
+
camelcase = gsub(/[ _-]([^ _-])/) do |match|
|
11
|
+
$1.upcase
|
12
|
+
end
|
13
|
+
|
14
|
+
# Convert first letter
|
15
|
+
if first_letter_capitalized
|
16
|
+
camelcase[0..0].upcase + camelcase[1..-1]
|
17
|
+
else
|
18
|
+
camelcase[0..0].downcase + camelcase[1..-1]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Removes module and class prefixes
|
23
|
+
#
|
24
|
+
# "Foo::Bar::Baz".demodulize => "Baz"
|
25
|
+
def demodulize
|
26
|
+
self[/([^:]+)$/]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Splits the string into module and class components
|
30
|
+
#
|
31
|
+
# "Foo::Bar::Baz".module_split => ["Foo", "Bar", "Baz"]
|
32
|
+
def module_split
|
33
|
+
self.split /::/
|
34
|
+
end
|
35
|
+
|
36
|
+
# Converts the string into a conventional path.
|
37
|
+
def module_path
|
38
|
+
File.join(module_split.map {|e| e.underscore })
|
39
|
+
end
|
40
|
+
|
41
|
+
# Converts dashes, spaces, and capitals to underscore separators.
|
42
|
+
#
|
43
|
+
# "FooBar-Baz Whee".underscore => 'foo_bar_baz_whee'
|
44
|
+
def underscore(force_lower_case = true)
|
45
|
+
# Convert separators
|
46
|
+
underscore = gsub(/[ -]/, '_')
|
47
|
+
|
48
|
+
# Convert capitals
|
49
|
+
underscore.gsub!(/(.)([A-Z])/) do |match|
|
50
|
+
$1 + '_' + $2
|
51
|
+
end
|
52
|
+
|
53
|
+
# Drop double underscores
|
54
|
+
underscore.gsub!(/_+/, '_')
|
55
|
+
|
56
|
+
if force_lower_case
|
57
|
+
underscore.downcase
|
58
|
+
else
|
59
|
+
underscore
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: exocora
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2008-03-27 00:00:00 -05:00
|
8
|
+
summary: A small framework for cgi scripts
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: aphyr@aphyr.com
|
12
|
+
homepage: http://aphyr.com/
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.6
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- aphyr
|
31
|
+
files:
|
32
|
+
- examples/validation.rhtml
|
33
|
+
- examples/hello_world.rhtml
|
34
|
+
- examples/validation.cgi
|
35
|
+
- examples/hello_world.cgi
|
36
|
+
- examples/request.rhtml
|
37
|
+
- examples/request.cgi
|
38
|
+
- lib/cgi.rb
|
39
|
+
- lib/array.rb
|
40
|
+
- lib/exocora.rb
|
41
|
+
- lib/string.rb
|
42
|
+
- lib/exocora
|
43
|
+
- lib/exocora/support.rb
|
44
|
+
- lib/exocora/validation.rb
|
45
|
+
- lib/exocora/sheet.rb
|
46
|
+
- lib/exocora/errors.rb
|
47
|
+
- lib/exocora/version.rb
|
48
|
+
- LICENSE
|
49
|
+
test_files: []
|
50
|
+
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
extra_rdoc_files: []
|
54
|
+
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
dependencies:
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: erubis
|
64
|
+
version_requirement:
|
65
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.5.0
|
70
|
+
version:
|