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 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,12 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/exocora'
4
+ #require '/home/aphyr/exocora/lib/exocora'
5
+
6
+ class HelloWorld < Exocora::Sheet
7
+ def process
8
+ {:body => "Hello, world"}
9
+ end
10
+ end
11
+
12
+ HelloWorld.run
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <head>
3
+ <title>Test</title>
4
+ </head>
5
+ <body>
6
+ <h1><%= @body %></h1>
7
+ </body>
8
+ </html>
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/exocora'
4
+ #require '/home/aphyr/exocora/lib/exocora'
5
+
6
+ class Request < Exocora::Sheet
7
+ def process
8
+ {:r => request}
9
+ end
10
+ end
11
+
12
+ Request.run
@@ -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,4 @@
1
+ module Exocora
2
+ module Support
3
+ end
4
+ 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
@@ -0,0 +1,8 @@
1
+ module Exocora
2
+ APP_NAME = 'exocora'
3
+ APP_VERSION = '0.1.0'
4
+ APP_AUTHOR = 'aphyr'
5
+ APP_EMAIL = 'aphyr@aphyr.com'
6
+ APP_URL = 'http://aphyr.com/'
7
+ APP_COPYRIGHT = 'Copyright (c) 2008 aphyr <aphyr@aphyr.com>. All rights reserved.'
8
+ 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: