otto 1.0.2 → 1.1.0.pre.alpha2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d33bafcf0fe1f9d883228b47ead7a87fe489ce46dace419a2192fd01b29724ba
4
- data.tar.gz: e1c72eca01acae97e35910750884ef9c15c4af53d40167fadaa4ab6ec56865d3
3
+ metadata.gz: 23a54df729acf5a96e337f0d096aee18376e579771e85e0f3f02d9355b049baf
4
+ data.tar.gz: 871c1bebc912b19fda8f3730f905d29077f6aa5e88ab9a087a95be3ccf29d312
5
5
  SHA512:
6
- metadata.gz: 976fc4b60c7db36d6a9ee0fd2e73d5b677f824af90a30d6f5d3d19173e3e41b0b381e6fa4d4a4da17948e0612d60c33ca59ed66925cf957bc5b47ecfbb5af0bd
7
- data.tar.gz: 77d740c93c30f7bf63d711711ae350711c48807b2f8521167b532e9246eb8abde19235ffac97a3ee79f3142f273f8edc32b891c67b441db8bb50ea68672a0ca6
6
+ metadata.gz: 903a3264f15fdcf38b523c7c380dc3b6897f9670a1573c9a9a22edfc1e8e6d61f3f30f89fbb0e78b3db11dee2e824d423444d98d3010f03530e577410bade70c
7
+ data.tar.gz: c096b04cf99f7bf7d18c6441c625f4a304b1b98d8c04d83ee58f2a71f047196ea2dacae75abcfe04ad97324b40d169a952a1c1f4555e388c9c426e2d628e53a9
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ .DS_Store
2
+ .bundle
3
+ .byebug*
4
+ .history
5
+ .devcontainer
6
+ .vscode
7
+ *.env
8
+ *.log
9
+ *.md
10
+ *.txt
11
+ !LICENSE.txt
12
+ .ruby-version
13
+ appendonlydir
14
+ etc/config
15
+ log
16
+ tmp
17
+ vendor
18
+ *.gem
data/.rubocop.yml ADDED
@@ -0,0 +1,71 @@
1
+ ##
2
+ # This is the RuboCop configuration file.
3
+ # It contains the rules and settings for the RuboCop linter.
4
+ #
5
+ # Enable/disable the cops individually. For more information,
6
+ # refer to the RuboCop documentation:
7
+ # https://docs.rubocop.org/rubocop/cops.html
8
+ #
9
+ # Running `rubocop --regenerate-todo` will update the todo file
10
+ # with the latest state of the onion (using the same options
11
+ # as those documented at the top of the todo file). This is
12
+ # useful for a gradual migration of the codebase.
13
+ #
14
+ inherit_from: .rubocop_todo.yml
15
+
16
+ require:
17
+ - rubocop-performance
18
+ - rubocop-thread_safety
19
+
20
+ AllCops:
21
+ NewCops: enable
22
+ UseCache: true
23
+ MaxFilesInCache: 100
24
+ TargetRubyVersion: 3.0
25
+ Exclude:
26
+ - 'migrate/**/*.rb'
27
+ - 'migrate/*.rb'
28
+ - 'try/**/*'
29
+ - 'try/*.rb'
30
+ - 'vendor/**/*'
31
+
32
+ Gemspec/DeprecatedAttributeAssignment:
33
+ Enabled: true
34
+
35
+ Gemspec/DevelopmentDependencies:
36
+ Enabled: true
37
+
38
+ Layout/HashAlignment:
39
+ Enabled: false
40
+
41
+ Lint/Void:
42
+ Enabled: false
43
+
44
+ Metrics/AbcSize:
45
+ Enabled: false
46
+ Max: 20
47
+
48
+ Metrics/ClassLength:
49
+ Enabled: true
50
+ Max: 200
51
+
52
+ Metrics/CyclomaticComplexity:
53
+ Enabled: false
54
+
55
+ Metrics/MethodLength:
56
+ Enabled: true
57
+ Max: 40
58
+ CountAsOne: ['method_call']
59
+
60
+ Metrics/ModuleLength:
61
+ Enabled: true
62
+ Max: 250
63
+ CountAsOne: ['method_call']
64
+
65
+ Performance/Size:
66
+ Enabled: true
67
+ Exclude:
68
+ # - lib/example.rb
69
+
70
+ Style/NegatedIfElseCondition:
71
+ Enabled: true
data/CHANGES.txt ADDED
@@ -0,0 +1,35 @@
1
+ OTTO, CHANGES
2
+
3
+ #### 0.4.1 (2015-04-07) ###############################
4
+
5
+ * FIXED: Resolved error when ACCEPT-LANGUAGES header doesn't exist
6
+
7
+ #### 0.4.0 (2015-04-06) ###############################
8
+
9
+ * ADDED: Locale support via env['rack.locale']
10
+
11
+ #### 0.3.2 (2013-01-27) ###############################
12
+
13
+ * CHANGE: send_cookie doesn't set domain
14
+
15
+ #### 0.3.1 (2012-12-17) ###############################
16
+
17
+ * ADDED: Otto.debug (set w/ Otto.debug= or env variable OTTO_DEBUG)
18
+ * ADDED: RequestHelpers#ajax?
19
+ * CHANGE: Added internal subnets to RequestHelpers#local?
20
+
21
+
22
+ #### 0.3.0 (2011-12-17) ###############################
23
+
24
+ * ADDED: Example app, better docs in readme
25
+ * CHANGE: No default value for user agent
26
+
27
+ #### 0.2.1 (2011-07-07) ###############################
28
+
29
+ * ADDED: Otto#add_static_path
30
+
31
+ #### 0.2.0 (2011-07-06) ###############################
32
+
33
+ Initial public release
34
+
35
+
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ group "development" do
8
+ gem "pry-byebug"
9
+ gem "rubocop"
10
+ gem "tryouts"
11
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,66 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ addressable (2.8.6)
5
+ public_suffix (>= 2.0.2, < 6.0)
6
+ ast (2.4.2)
7
+ byebug (11.1.3)
8
+ coderay (1.1.3)
9
+ drydock (0.6.9)
10
+ json (2.7.2)
11
+ language_server-protocol (3.17.0.3)
12
+ method_source (1.0.0)
13
+ parallel (1.24.0)
14
+ parser (3.3.0.5)
15
+ ast (~> 2.4.1)
16
+ racc
17
+ pry (0.14.2)
18
+ coderay (~> 1.1)
19
+ method_source (~> 1.0)
20
+ pry-byebug (3.10.1)
21
+ byebug (~> 11.0)
22
+ pry (>= 0.13, < 0.15)
23
+ public_suffix (5.0.5)
24
+ racc (1.7.3)
25
+ rack (3.0.10)
26
+ rainbow (3.1.1)
27
+ regexp_parser (2.9.0)
28
+ rexml (3.2.6)
29
+ rubocop (1.62.1)
30
+ json (~> 2.3)
31
+ language_server-protocol (>= 3.17.0)
32
+ parallel (~> 1.10)
33
+ parser (>= 3.3.0.2)
34
+ rainbow (>= 2.2.2, < 4.0)
35
+ regexp_parser (>= 1.8, < 3.0)
36
+ rexml (>= 3.2.5, < 4.0)
37
+ rubocop-ast (>= 1.31.1, < 2.0)
38
+ ruby-progressbar (~> 1.7)
39
+ unicode-display_width (>= 2.4.0, < 3.0)
40
+ rubocop-ast (1.31.2)
41
+ parser (>= 3.3.0.4)
42
+ ruby-progressbar (1.13.0)
43
+ storable (0.10.0)
44
+ sysinfo (0.10.0)
45
+ drydock (< 1.0)
46
+ storable (~> 0.10)
47
+ tryouts (2.2.0)
48
+ sysinfo (~> 0.10)
49
+ unicode-display_width (2.5.0)
50
+
51
+ PLATFORMS
52
+ arm64-darwin-22
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ addressable
57
+ pry-byebug
58
+ rack
59
+ rubocop
60
+ tryouts
61
+
62
+ RUBY VERSION
63
+ ruby 3.2.0p0
64
+
65
+ BUNDLED WITH
66
+ 2.5.7
data/VERSION.yml CHANGED
@@ -1,3 +1,4 @@
1
1
  :MAJOR: 1
2
- :MINOR: 0
3
- :PATCH: 2
2
+ :MINOR: 1
3
+ :PATCH: 0
4
+ :PRE: alpha2
@@ -0,0 +1,61 @@
1
+ class Otto
2
+ module RequestHelpers
3
+ def user_agent
4
+ env['HTTP_USER_AGENT']
5
+ end
6
+ def client_ipaddress
7
+ env['HTTP_X_FORWARDED_FOR'].to_s.split(/,\s*/).first ||
8
+ env['HTTP_X_REAL_IP'] || env['REMOTE_ADDR']
9
+ end
10
+ def request_method
11
+ env['REQUEST_METHOD']
12
+ end
13
+ def current_server
14
+ [current_server_name, env['SERVER_PORT']].join(':')
15
+ end
16
+ def current_server_name
17
+ env['SERVER_NAME']
18
+ end
19
+ def http_host
20
+ env['HTTP_HOST']
21
+ end
22
+ def request_path
23
+ env['REQUEST_PATH']
24
+ end
25
+ def request_uri
26
+ env['REQUEST_URI']
27
+ end
28
+ def root_path
29
+ env['SCRIPT_NAME']
30
+ end
31
+ def absolute_suri host=current_server_name
32
+ prefix = local? ? 'http://' : 'https://'
33
+ [prefix, host, request_path].join
34
+ end
35
+ def local?
36
+ Otto.env?(:dev, :development) &&
37
+ (client_ipaddress == '127.0.0.1' ||
38
+ !client_ipaddress.match(/^10\.0\./).nil? ||
39
+ !client_ipaddress.match(/^192\.168\./).nil?)
40
+ end
41
+ def secure?
42
+ # X-Scheme is set by nginx
43
+ # X-FORWARDED-PROTO is set by elastic load balancer
44
+ (env['HTTP_X_FORWARDED_PROTO'] == 'https' || env['HTTP_X_SCHEME'] == "https")
45
+ end
46
+ # See: http://stackoverflow.com/questions/10013812/how-to-prevent-jquery-ajax-from-following-a-redirect-after-a-post
47
+ def ajax?
48
+ env['HTTP_X_REQUESTED_WITH'].to_s.downcase == 'xmlhttprequest'
49
+ end
50
+ def cookie name
51
+ cookies[name.to_s]
52
+ end
53
+ def cookie? name
54
+ !cookie(name).to_s.empty?
55
+ end
56
+ def current_absolute_uri
57
+ prefix = secure? && !local? ? 'https://' : 'http://'
58
+ [prefix, http_host, request_path].join
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,22 @@
1
+ class Otto
2
+ module ResponseHelpers
3
+ attr_accessor :request
4
+ def send_secure_cookie name, value, ttl
5
+ send_cookie name, value, ttl, true
6
+ end
7
+ def send_cookie name, value, ttl, secure=true
8
+ secure = false if request.local?
9
+ opts = {
10
+ :value => value,
11
+ :path => '/',
12
+ :expires => (Time.now.utc + ttl + 10),
13
+ :secure => secure
14
+ }
15
+ #opts[:domain] = request.env['SERVER_NAME']
16
+ set_cookie name, opts
17
+ end
18
+ def delete_cookie name
19
+ send_cookie name, nil, -1.day
20
+ end
21
+ end
22
+ end
data/lib/otto/route.rb ADDED
@@ -0,0 +1,97 @@
1
+
2
+ class Otto
3
+ # Otto::Route
4
+ #
5
+ # A Route is a definition of a URL path and the method to call when
6
+ # that path is requested. Each route represents a single line in a
7
+ # routes file.
8
+ #
9
+ # e.g.
10
+ #
11
+ # GET /uri/path YourApp.method
12
+ # GET /uri/path2 YourApp#method
13
+ #
14
+ #
15
+ class Route
16
+ module ClassMethods
17
+ attr_accessor :otto
18
+ end
19
+ attr_reader :verb, :path, :pattern, :method, :klass, :name, :definition, :keys, :kind
20
+ attr_accessor :otto
21
+ def initialize verb, path, definition
22
+ @verb, @path, @definition = verb.to_s.upcase.to_sym, path, definition
23
+ @pattern, @keys = *compile(@path)
24
+ if !@definition.index('.').nil?
25
+ @klass, @name = @definition.split('.')
26
+ @kind = :class
27
+ elsif !@definition.index('#').nil?
28
+ @klass, @name = @definition.split('#')
29
+ @kind = :instance
30
+ else
31
+ raise ArgumentError, "Bad definition: #{@definition}"
32
+ end
33
+ @klass = eval(@klass)
34
+ #@method = eval(@klass).method(@name)
35
+ end
36
+ def pattern_regexp
37
+ Regexp.new(@path.gsub(/\/\*/, '/.+'))
38
+ end
39
+ def call(env, extra_params={})
40
+ extra_params ||= {}
41
+ req = Rack::Request.new(env)
42
+ res = Rack::Response.new
43
+ req.extend Otto::RequestHelpers
44
+ res.extend Otto::ResponseHelpers
45
+ res.request = req
46
+ req.params.merge! extra_params
47
+ req.params.replace Otto::Static.indifferent_params(req.params)
48
+ klass.extend Otto::Route::ClassMethods
49
+ klass.otto = self.otto
50
+ Otto.logger.debug "Route class: #{klass}"
51
+ case kind
52
+ when :instance
53
+ inst = klass.new req, res
54
+ inst.send(name)
55
+ when :class
56
+ klass.send(name, req, res)
57
+ else
58
+ raise RuntimeError, "Unsupported kind for #{@definition}: #{kind}"
59
+ end
60
+ res.body = [res.body] unless res.body.respond_to?(:each)
61
+ res.finish
62
+ end
63
+ # Brazenly borrowed from Sinatra::Base:
64
+ # https://github.com/sinatra/sinatra/blob/v1.2.6/lib/sinatra/base.rb#L1156
65
+ def compile(path)
66
+ keys = []
67
+ if path.respond_to? :to_str
68
+ special_chars = %w{. + ( ) $}
69
+ pattern =
70
+ path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
71
+ case match
72
+ when "*"
73
+ keys << 'splat'
74
+ "(.*?)"
75
+ when *special_chars
76
+ Regexp.escape(match)
77
+ else
78
+ keys << $2[1..-1]
79
+ "([^/?#]+)"
80
+ end
81
+ end
82
+ # Wrap the regex in parens so the regex works properly.
83
+ # They can fail when there's an | for example (matching only the last one).
84
+ # Note: this means we also need to remove the first matched value.
85
+ [/\A(#{pattern})\z/, keys]
86
+ elsif path.respond_to?(:keys) && path.respond_to?(:match)
87
+ [path, path.keys]
88
+ elsif path.respond_to?(:names) && path.respond_to?(:match)
89
+ [path, path.names]
90
+ elsif path.respond_to? :match
91
+ [path, keys]
92
+ else
93
+ raise TypeError, path
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,36 @@
1
+
2
+ class Otto
3
+
4
+ module Static
5
+ extend self
6
+ def server_error
7
+ [500, {'Content-Type'=>'text/plain'}, ["Server error"]]
8
+ end
9
+ def not_found
10
+ [404, {'Content-Type'=>'text/plain'}, ["Not Found"]]
11
+ end
12
+ # Enable string or symbol key access to the nested params hash.
13
+ def indifferent_params(params)
14
+ if params.is_a?(Hash)
15
+ params = indifferent_hash.merge(params)
16
+ params.each do |key, value|
17
+ next unless value.is_a?(Hash) || value.is_a?(Array)
18
+ params[key] = indifferent_params(value)
19
+ end
20
+ elsif params.is_a?(Array)
21
+ params.collect! do |value|
22
+ if value.is_a?(Hash) || value.is_a?(Array)
23
+ indifferent_params(value)
24
+ else
25
+ value
26
+ end
27
+ end
28
+ end
29
+ end
30
+ # Creates a Hash with indifferent access.
31
+ def indifferent_hash
32
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,27 @@
1
+ #
2
+
3
+ class Otto
4
+ # Otto::VERSION
5
+ #
6
+ module VERSION
7
+ def self.to_a
8
+ load_config
9
+ [@version[:MAJOR], @version[:MINOR], @version[:PATCH]]
10
+ end
11
+
12
+ def self.to_s
13
+ version = to_a.join('.')
14
+ "#{version}-#{@version[:PRE]}" if @version[:PRE]
15
+ end
16
+
17
+ def self.inspect
18
+ to_s
19
+ end
20
+
21
+ def self.load_config
22
+ return if @version
23
+ require 'yaml'
24
+ @version = YAML.load_file(File.join(__dir__, '..', '..', 'VERSION.yml'))
25
+ end
26
+ end
27
+ end
data/lib/otto.rb CHANGED
@@ -1,45 +1,44 @@
1
+ require 'logger'
1
2
 
2
3
  require 'rack/request'
3
4
  require 'rack/response'
4
5
  require 'rack/utils'
5
6
  require 'addressable/uri'
6
7
 
8
+ require_relative 'otto/route'
9
+ require_relative 'otto/static'
10
+ require_relative 'otto/helpers/request'
11
+ require_relative 'otto/helpers/response'
12
+ require_relative 'otto/version'
13
+
14
+ # Otto is a simple Rack router that allows you to define routes in a file
15
+ #
16
+ #
7
17
  class Otto
8
- @debug = ENV['OTTO_DEBUG'] == 'true'
9
- LIB_HOME = File.expand_path File.dirname(__FILE__) unless defined?(Otto::LIB_HOME)
18
+ LIB_HOME = __dir__ unless defined?(Otto::LIB_HOME)
10
19
 
11
- module VERSION
12
- def self.to_s
13
- load_config
14
- [@version[:MAJOR], @version[:MINOR], @version[:PATCH]].join('.')
15
- end
16
- def self.inspect
17
- to_s
18
- end
19
- def self.load_config
20
- require 'yaml'
21
- @version ||= YAML.load_file(File.join(LIB_HOME, '..', 'VERSION.yml'))
22
- end
23
- end
24
- end
20
+ @debug = ENV['OTTO_DEBUG'] == 'true'
21
+ @logger = Logger.new($stdout, Logger::INFO)
25
22
 
26
- class Otto
27
23
  attr_reader :routes, :routes_literal, :routes_static, :route_definitions
28
24
  attr_reader :option, :static_route
29
25
  attr_accessor :not_found, :server_error
26
+
30
27
  def initialize path=nil, opts={}
31
- @routes_static = { :GET => {} }
32
- @routes = { :GET => [] }
33
- @routes_literal = { :GET => {} }
28
+ @routes_static = { GET: {} }
29
+ @routes = { GET: [] }
30
+ @routes_literal = { GET: {} }
34
31
  @route_definitions = {}
35
32
  @option = opts.merge({
36
- :public => nil,
37
- :locale => 'en'
33
+ public: nil,
34
+ locale: 'en'
38
35
  })
36
+ Otto.logger.debug "new Otto: #{opts}" if Otto.debug
39
37
  load(path) unless path.nil?
40
38
  super()
41
39
  end
42
40
  alias_method :options, :option
41
+
43
42
  def load path
44
43
  path = File.expand_path(path)
45
44
  raise ArgumentError, "Bad path: #{path}" unless File.exist?(path)
@@ -51,13 +50,14 @@ class Otto
51
50
  route.otto = self
52
51
  path_clean = path.gsub /\/$/, ''
53
52
  @route_definitions[route.definition] = route
54
- STDERR.puts "route: #{route.pattern}" if Otto.debug
53
+ Otto.logger.debug "route: #{route.pattern}" if Otto.debug
55
54
  @routes[route.verb] ||= []
56
55
  @routes[route.verb] << route
57
56
  @routes_literal[route.verb] ||= {}
58
57
  @routes_literal[route.verb][path_clean] = route
59
- rescue => ex
60
- STDERR.puts "Bad route in #{path}: #{entry}"
58
+
59
+ rescue StandardError => ex
60
+ Otto.logger.error "Bad route in #{path}: #{entry}"
61
61
  end
62
62
  }
63
63
  self
@@ -79,7 +79,7 @@ class Otto
79
79
  # Files in the root directory can refer to themselves
80
80
  base_path = path if base_path == '/'
81
81
  static_path = File.join(option[:public], base_path)
82
- STDERR.puts "new static route: #{base_path} (#{path})" if Otto.debug
82
+ Otto.logger.debug "new static route: #{base_path} (#{path})" if Otto.debug
83
83
  routes_static[:GET][base_path] = base_path
84
84
  end
85
85
  end
@@ -100,15 +100,15 @@ class Otto
100
100
  literal_routes = routes_literal[http_verb] || {}
101
101
  literal_routes.merge! routes_literal[:GET] if http_verb == :HEAD
102
102
  if static_route && http_verb == :GET && routes_static[:GET].member?(base_path)
103
- #STDERR.puts " request: #{path_info} (static)"
103
+ #Otto.logger.debug " request: #{path_info} (static)"
104
104
  static_route.call(env)
105
105
  elsif literal_routes.has_key?(path_info_clean)
106
106
  route = literal_routes[path_info_clean]
107
- #STDERR.puts " request: #{http_verb} #{path_info} (literal route: #{route.verb} #{route.path})"
107
+ #Otto.logger.debug " request: #{http_verb} #{path_info} (literal route: #{route.verb} #{route.path})"
108
108
  route.call(env)
109
109
  elsif static_route && http_verb == :GET && safe_file?(path_info)
110
110
  static_path = File.join(option[:public], base_path)
111
- STDERR.puts " new static route: #{base_path} (#{path_info})"
111
+ Otto.logger.debug " new static route: #{base_path} (#{path_info})"
112
112
  routes_static[:GET][base_path] = base_path
113
113
  static_route.call(env)
114
114
  else
@@ -117,7 +117,7 @@ class Otto
117
117
  valid_routes = routes[http_verb] || []
118
118
  valid_routes.push *routes[:GET] if http_verb == :HEAD
119
119
  valid_routes.each { |route|
120
- #STDERR.puts " request: #{http_verb} #{path_info} (trying route: #{route.verb} #{route.pattern})"
120
+ #Otto.logger.debug " request: #{http_verb} #{path_info} (trying route: #{route.verb} #{route.pattern})"
121
121
  if (match = route.pattern.match(path_info))
122
122
  values = match.captures.to_a
123
123
  # The first capture returned is the entire matched string b/c
@@ -151,7 +151,8 @@ class Otto
151
151
  end
152
152
  end
153
153
  rescue => ex
154
- STDERR.puts ex.message, ex.backtrace
154
+ Otto.logger.error "#{ex.class}: #{ex.message} #{ex.backtrace.join("\n")}"
155
+
155
156
  if found_route = literal_routes['/500']
156
157
  found_route.call env
157
158
  else
@@ -159,7 +160,6 @@ class Otto
159
160
  end
160
161
  end
161
162
 
162
-
163
163
  # Return the URI path for the given +route_definition+
164
164
  # e.g.
165
165
  #
@@ -199,130 +199,15 @@ class Otto
199
199
  locale
200
200
  }.reverse
201
201
  end
202
- STDERR.puts "locale: #{locales} (#{accept_langs})" if Otto.debug
202
+ Otto.logger.debug "locale: #{locales} (#{accept_langs})" if Otto.debug
203
203
  locales.empty? ? nil : locales
204
204
  end
205
205
 
206
- module Static
207
- extend self
208
- def server_error
209
- [500, {'Content-Type'=>'text/plain'}, ["Server error"]]
210
- end
211
- def not_found
212
- [404, {'Content-Type'=>'text/plain'}, ["Not Found"]]
213
- end
214
- # Enable string or symbol key access to the nested params hash.
215
- def indifferent_params(params)
216
- if params.is_a?(Hash)
217
- params = indifferent_hash.merge(params)
218
- params.each do |key, value|
219
- next unless value.is_a?(Hash) || value.is_a?(Array)
220
- params[key] = indifferent_params(value)
221
- end
222
- elsif params.is_a?(Array)
223
- params.collect! do |value|
224
- if value.is_a?(Hash) || value.is_a?(Array)
225
- indifferent_params(value)
226
- else
227
- value
228
- end
229
- end
230
- end
231
- end
232
- # Creates a Hash with indifferent access.
233
- def indifferent_hash
234
- Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
235
- end
236
- end
237
- #
238
- # e.g.
239
- #
240
- # GET /uri/path YourApp.method
241
- # GET /uri/path2 YourApp#method
242
- #
243
- class Route
244
- module ClassMethods
245
- attr_accessor :otto
246
- end
247
- attr_reader :verb, :path, :pattern, :method, :klass, :name, :definition, :keys, :kind
248
- attr_accessor :otto
249
- def initialize verb, path, definition
250
- @verb, @path, @definition = verb.to_s.upcase.to_sym, path, definition
251
- @pattern, @keys = *compile(@path)
252
- if !@definition.index('.').nil?
253
- @klass, @name = @definition.split('.')
254
- @kind = :class
255
- elsif !@definition.index('#').nil?
256
- @klass, @name = @definition.split('#')
257
- @kind = :instance
258
- else
259
- raise ArgumentError, "Bad definition: #{@definition}"
260
- end
261
- @klass = eval(@klass)
262
- #@method = eval(@klass).method(@name)
263
- end
264
- def pattern_regexp
265
- Regexp.new(@path.gsub(/\/\*/, '/.+'))
266
- end
267
- def call(env, extra_params={})
268
- extra_params ||= {}
269
- req = Rack::Request.new(env)
270
- res = Rack::Response.new
271
- req.extend Otto::RequestHelpers
272
- res.extend Otto::ResponseHelpers
273
- res.request = req
274
- req.params.merge! extra_params
275
- req.params.replace Otto::Static.indifferent_params(req.params)
276
- klass.extend Otto::Route::ClassMethods
277
- klass.otto = self.otto
278
- case kind
279
- when :instance
280
- inst = klass.new req, res
281
- inst.send(name)
282
- when :class
283
- klass.send(name, req, res)
284
- else
285
- raise RuntimeError, "Unsupported kind for #{@definition}: #{kind}"
286
- end
287
- res.body = [res.body] unless res.body.respond_to?(:each)
288
- res.finish
289
- end
290
- # Brazenly borrowed from Sinatra::Base:
291
- # https://github.com/sinatra/sinatra/blob/v1.2.6/lib/sinatra/base.rb#L1156
292
- def compile(path)
293
- keys = []
294
- if path.respond_to? :to_str
295
- special_chars = %w{. + ( ) $}
296
- pattern =
297
- path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
298
- case match
299
- when "*"
300
- keys << 'splat'
301
- "(.*?)"
302
- when *special_chars
303
- Regexp.escape(match)
304
- else
305
- keys << $2[1..-1]
306
- "([^/?#]+)"
307
- end
308
- end
309
- # Wrap the regex in parens so the regex works properly.
310
- # They can fail when there's an | for example (matching only the last one).
311
- # Note: this means we also need to remove the first matched value.
312
- [/\A(#{pattern})\z/, keys]
313
- elsif path.respond_to?(:keys) && path.respond_to?(:match)
314
- [path, path.keys]
315
- elsif path.respond_to?(:names) && path.respond_to?(:match)
316
- [path, path.names]
317
- elsif path.respond_to? :match
318
- [path, keys]
319
- else
320
- raise TypeError, path
321
- end
322
- end
323
- end
324
206
  class << self
325
- attr_accessor :debug
207
+ attr_accessor :debug, :logger
208
+ end
209
+
210
+ module ClassMethods
326
211
  def default
327
212
  @default ||= Otto.new
328
213
  @default
@@ -340,88 +225,5 @@ class Otto
340
225
  !guesses.flatten.select { |n| ENV['RACK_ENV'].to_s == n.to_s }.empty?
341
226
  end
342
227
  end
343
- module RequestHelpers
344
- def user_agent
345
- env['HTTP_USER_AGENT']
346
- end
347
- # HTTP_X_FORWARDED_FOR is from the ELB (non-https only)
348
- # and it can take the form: 74.121.244.2, 10.252.130.147
349
- # HTTP_X_REAL_IP is from nginx
350
- # REMOTE_ADDR is from thin
351
- # There's no way to get the client IP address in HTTPS.
352
- def client_ipaddress
353
- env['HTTP_X_FORWARDED_FOR'].to_s.split(/,\s*/).first ||
354
- env['HTTP_X_REAL_IP'] || env['REMOTE_ADDR']
355
- end
356
- def request_method
357
- env['REQUEST_METHOD']
358
- end
359
- def current_server
360
- [current_server_name, env['SERVER_PORT']].join(':')
361
- end
362
- def current_server_name
363
- env['SERVER_NAME']
364
- end
365
- def http_host
366
- env['HTTP_HOST']
367
- end
368
- def request_path
369
- env['REQUEST_PATH']
370
- end
371
- def request_uri
372
- env['REQUEST_URI']
373
- end
374
- def root_path
375
- env['SCRIPT_NAME']
376
- end
377
- def absolute_suri host=current_server_name
378
- prefix = local? ? 'http://' : 'https://'
379
- [prefix, host, request_path].join
380
- end
381
- def local?
382
- Otto.env?(:dev, :development) &&
383
- (client_ipaddress == '127.0.0.1' ||
384
- !client_ipaddress.match(/^10\.0\./).nil? ||
385
- !client_ipaddress.match(/^192\.168\./).nil?)
386
- end
387
- def secure?
388
- # X-Scheme is set by nginx
389
- # X-FORWARDED-PROTO is set by elastic load balancer
390
- (env['HTTP_X_FORWARDED_PROTO'] == 'https' || env['HTTP_X_SCHEME'] == "https")
391
- end
392
- # See: http://stackoverflow.com/questions/10013812/how-to-prevent-jquery-ajax-from-following-a-redirect-after-a-post
393
- def ajax?
394
- env['HTTP_X_REQUESTED_WITH'].to_s.downcase == 'xmlhttprequest'
395
- end
396
- def cookie name
397
- cookies[name.to_s]
398
- end
399
- def cookie? name
400
- !cookie(name).to_s.empty?
401
- end
402
- def current_absolute_uri
403
- prefix = secure? && !local? ? 'https://' : 'http://'
404
- [prefix, http_host, request_path].join
405
- end
406
- end
407
- module ResponseHelpers
408
- attr_accessor :request
409
- def send_secure_cookie name, value, ttl
410
- send_cookie name, value, ttl, true
411
- end
412
- def send_cookie name, value, ttl, secure=true
413
- secure = false if request.local?
414
- opts = {
415
- :value => value,
416
- :path => '/',
417
- :expires => (Time.now.utc + ttl + 10),
418
- :secure => secure
419
- }
420
- #opts[:domain] = request.env['SERVER_NAME']
421
- set_cookie name, opts
422
- end
423
- def delete_cookie name
424
- send_cookie name, nil, -1.day
425
- end
426
- end
228
+ extend ClassMethods
427
229
  end
data/otto.gemspec CHANGED
@@ -1,31 +1,24 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
+ require_relative 'lib/otto/version'
4
+
3
5
  Gem::Specification.new do |spec|
4
6
  spec.name = "otto"
5
- spec.version = "1.0.2"
7
+ spec.version = Otto::VERSION.to_s
6
8
  spec.summary = "Auto-define your rack-apps in plaintext."
7
9
  spec.description = "Otto: #{spec.summary}"
8
10
  spec.email = "gems@solutious.com"
9
11
  spec.authors = ["Delano Mandelbaum"]
10
12
  spec.license = "MIT"
11
- spec.files = [
12
- "LICENSE.txt",
13
- "README.md",
14
- "VERSION.yml",
15
- "example/app.rb",
16
- "example/config.ru",
17
- "example/public/favicon.ico",
18
- "example/public/img/otto.jpg",
19
- "example/routes",
20
- "lib/otto.rb",
21
- "otto.gemspec"
22
- ]
13
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
14
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ end
23
16
  spec.homepage = "https://github.com/delano/otto"
24
17
  spec.require_paths = ["lib"]
25
- spec.rubygems_version = "3.2.22" # Update to the latest version
18
+ spec.rubygems_version = "3.5.15" # Update to the latest version
26
19
 
27
20
  spec.required_ruby_version = ['>= 2.6.8', '< 4.0']
28
21
 
29
- spec.add_dependency 'addressable', '>= 2.2.6'
30
- spec.add_dependency 'rack', '>= 1.2.1'
22
+ spec.add_runtime_dependency 'addressable', '~> 2.2', '>= 2.2.6'
23
+ spec.add_runtime_dependency 'rack', '~> 1.2', '>= 1.2.1'
31
24
  end
metadata CHANGED
@@ -1,19 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: otto
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0.pre.alpha2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-01 00:00:00.000000000 Z
11
+ date: 2024-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.2'
17
20
  - - ">="
18
21
  - !ruby/object:Gem::Version
19
22
  version: 2.2.6
@@ -21,6 +24,9 @@ dependencies:
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.2'
24
30
  - - ">="
25
31
  - !ruby/object:Gem::Version
26
32
  version: 2.2.6
@@ -28,6 +34,9 @@ dependencies:
28
34
  name: rack
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.2'
31
40
  - - ">="
32
41
  - !ruby/object:Gem::Version
33
42
  version: 1.2.1
@@ -35,6 +44,9 @@ dependencies:
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.2'
38
50
  - - ">="
39
51
  - !ruby/object:Gem::Version
40
52
  version: 1.2.1
@@ -44,6 +56,11 @@ executables: []
44
56
  extensions: []
45
57
  extra_rdoc_files: []
46
58
  files:
59
+ - ".gitignore"
60
+ - ".rubocop.yml"
61
+ - CHANGES.txt
62
+ - Gemfile
63
+ - Gemfile.lock
47
64
  - LICENSE.txt
48
65
  - README.md
49
66
  - VERSION.yml
@@ -53,6 +70,11 @@ files:
53
70
  - example/public/img/otto.jpg
54
71
  - example/routes
55
72
  - lib/otto.rb
73
+ - lib/otto/helpers/request.rb
74
+ - lib/otto/helpers/response.rb
75
+ - lib/otto/route.rb
76
+ - lib/otto/static.rb
77
+ - lib/otto/version.rb
56
78
  - otto.gemspec
57
79
  homepage: https://github.com/delano/otto
58
80
  licenses:
@@ -76,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
98
  - !ruby/object:Gem::Version
77
99
  version: '0'
78
100
  requirements: []
79
- rubygems_version: 3.5.9
101
+ rubygems_version: 3.5.15
80
102
  signing_key:
81
103
  specification_version: 4
82
104
  summary: Auto-define your rack-apps in plaintext.