exocora 0.1.0

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