otto 1.0.2 → 1.1.0.pre.alpha2

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.
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.