utopia 1.0.11 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.travis.yml +1 -0
  4. data/bin/utopia +1 -1
  5. data/lib/utopia/content.rb +10 -12
  6. data/lib/utopia/content/link.rb +8 -11
  7. data/lib/utopia/content/links.rb +1 -1
  8. data/lib/utopia/content/node.rb +8 -7
  9. data/lib/utopia/controller.rb +40 -39
  10. data/lib/utopia/controller/action.rb +14 -12
  11. data/lib/utopia/controller/base.rb +31 -32
  12. data/lib/utopia/controller/rewrite.rb +104 -0
  13. data/lib/utopia/exception_handler.rb +1 -1
  14. data/lib/utopia/extensions/rack.rb +21 -21
  15. data/lib/utopia/http.rb +14 -9
  16. data/lib/utopia/localization.rb +1 -1
  17. data/lib/utopia/middleware.rb +3 -0
  18. data/lib/utopia/path.rb +81 -25
  19. data/lib/utopia/path/matcher.rb +94 -0
  20. data/lib/utopia/redirector.rb +4 -4
  21. data/lib/utopia/session/encrypted_cookie.rb +1 -1
  22. data/lib/utopia/static.rb +1 -1
  23. data/lib/utopia/tags/node.rb +1 -1
  24. data/lib/utopia/version.rb +1 -1
  25. data/setup/site/config.ru +1 -1
  26. data/spec/utopia/content/link_spec.rb +8 -8
  27. data/spec/utopia/content/node_spec.rb +1 -1
  28. data/spec/utopia/content_spec.rb +3 -3
  29. data/spec/utopia/content_spec.ru +1 -1
  30. data/spec/utopia/controller/action_spec.rb +61 -0
  31. data/spec/utopia/controller/middleware_spec.rb +71 -0
  32. data/spec/utopia/controller/middleware_spec.ru +4 -0
  33. data/spec/utopia/controller/middleware_spec/controller/controller.rb +24 -0
  34. data/spec/utopia/{pages → controller/middleware_spec}/controller/index.xnode +0 -0
  35. data/spec/utopia/{pages → controller/middleware_spec}/controller/nested/controller.rb +0 -0
  36. data/spec/utopia/controller/rewrite_spec.rb +66 -0
  37. data/spec/utopia/{controller_spec.rb → controller/sequence_spec.rb} +11 -84
  38. data/spec/utopia/exception_handler_spec.rb +3 -3
  39. data/spec/utopia/exception_handler_spec.ru +2 -2
  40. data/spec/utopia/exception_handler_spec/controller.rb +15 -0
  41. data/spec/utopia/path/matcher_spec.rb +65 -0
  42. data/spec/utopia/rack_spec.rb +0 -12
  43. data/utopia.gemspec +1 -1
  44. metadata +27 -14
  45. data/spec/utopia/controller_spec.ru +0 -4
  46. data/spec/utopia/pages/controller/controller.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb9c01f8a43932f752a9588dd957deb2afec170c
4
- data.tar.gz: 85654a0ecc710fb663616fd7afda1a3da995790e
3
+ metadata.gz: 965466abaa777fe6b33b71c24253718da3544bd1
4
+ data.tar.gz: 1a53312d2f58949a2a6027292dd5d6a66e0c56ab
5
5
  SHA512:
6
- metadata.gz: 5a1de4ccb2fa9a111cefce7ff9c19c065b8551e1cbf6bf8a919d7db6c38a31b5b5b8c4edd7fe23fce192aa17635a74f0c170749c9cb03b78c068c872bf1db8fa
7
- data.tar.gz: 6aa5e5815944530c84c53d14929993036cc09b3f0d4911ed68c6a7dbf2a1db495ec3d4eccb43edd2e9632d5003a262b58cd6c11e379407b27d8b86af8ae4537e
6
+ metadata.gz: a24b35e08ccdbb23e1a85236e25b5166235a39d04d08fa2b5d8b5f00f9feb7ae07a43956bf6629ef4e2e98eaf00ac32b95638efa5b5e8df5cb7eb9d33a1fc002
7
+ data.tar.gz: 56ec05c1856d72c0bf9305f6c0ce821348c83f9d8c2d90a4cc3c53586755533b67c2b89fb1d50c7a4b0fa865e747b25d0d8b94d64983e23210717766325ddb81
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.travis.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
3
4
  - "2.0"
4
5
  - "2.1"
data/bin/utopia CHANGED
@@ -37,7 +37,7 @@ Rake::TaskManager.record_task_metadata = true
37
37
  verbose(false)
38
38
 
39
39
  module Setup
40
- BASE = File.expand_path("../../setup", __FILE__)
40
+ BASE = File.expand_path("../setup", __dir__)
41
41
 
42
42
  module Site
43
43
  CONFIGURATION_FILES = ['config.ru', 'Gemfile', 'Rakefile']
@@ -28,7 +28,9 @@ require 'trenni/template'
28
28
 
29
29
  module Utopia
30
30
  class Content
31
- def initialize(app, options = {})
31
+ INDEX = 'index'.freeze
32
+
33
+ def initialize(app, **options)
32
34
  @app = app
33
35
 
34
36
  @root = File.expand_path(options[:root] || Utopia::default_root)
@@ -57,7 +59,7 @@ module Utopia
57
59
  return @tags[name]
58
60
  end
59
61
 
60
- if String === name && name.index("/")
62
+ if String === name && name.index('/')
61
63
  name = Path.create(name)
62
64
  end
63
65
 
@@ -77,7 +79,7 @@ module Utopia
77
79
  end
78
80
 
79
81
  if String === name_path
80
- tag_path = File.join(root, dir.components, "_" + name_path)
82
+ tag_path = File.join(root, dir.components, '_' + name_path)
81
83
 
82
84
  if File.exist? tag_path
83
85
  return Node.new(self, dir + name, parent_path + name, tag_path)
@@ -112,9 +114,9 @@ module Utopia
112
114
 
113
115
  # If the request for /foo/bar{extensions} is actually a directory, rewrite it to /foo/bar/index{extensions}:
114
116
  if File.directory? directory_path
115
- index_path = [basename.name, basename.rename("index")]
117
+ index_path = [basename.name, basename.rename(INDEX)]
116
118
 
117
- return [307, {"Location" => path.dirname.join(index_path).to_s}, []]
119
+ return [307, {HTTP::LOCATION => path.dirname.join(index_path).to_s}, []]
118
120
  end
119
121
 
120
122
  locale = env[Localization::CURRENT_LOCALE_KEY]
@@ -122,17 +124,13 @@ module Utopia
122
124
  if link.path and node = lookup_node(link.path)
123
125
  response = Rack::Response.new
124
126
 
125
- attributes = nil
126
-
127
- if request.respond_to?(:controller)
128
- attributes = request.controller
129
- end
127
+ attributes = request.env.fetch(VARIABLES_KEY, {}).to_hash
130
128
 
131
- node.process!(request, response, (attributes || {}).to_hash)
129
+ node.process!(request, response, attributes)
132
130
 
133
131
  return response.finish
134
132
  elsif redirect_uri = link[:uri]
135
- return [307, {"Location" => redirect_uri}, []]
133
+ return [307, {HTTP::LOCATION => redirect_uri}, []]
136
134
  end
137
135
  end
138
136
 
@@ -38,7 +38,7 @@ module Utopia
38
38
  @name, @variant = path.last.split('.', 2)
39
39
  @path = path
40
40
  when :directory
41
- # raise ArgumentError unless path.last.start_with? 'index'
41
+ # raise ArgumentError unless path.last.start_with? INDEX
42
42
 
43
43
  @name = path.dirname.last
44
44
  @variant = path.last.split('.', 2)[1]
@@ -85,7 +85,7 @@ module Utopia
85
85
  @info.fetch(:title, @title)
86
86
  end
87
87
 
88
- def to_href(options = {})
88
+ def to_href(**options)
89
89
  Trenni::Builder.fragment(options[:builder]) do |builder|
90
90
  if href?
91
91
  relative_href(options[:base])
@@ -101,19 +101,16 @@ module Utopia
101
101
  end
102
102
  end
103
103
 
104
+ def to_s
105
+ "\#<#{self.class}(#{self.kind}) title=#{title.inspect} href=#{href.inspect}>"
106
+ end
107
+
104
108
  def eql? other
105
- if other && self.class == other.class
106
- return kind.eql?(other.kind) &&
107
- name.eql?(other.name) &&
108
- path.eql?(other.path) &&
109
- info.eql?(other.info)
110
- else
111
- return false
112
- end
109
+ self.class.eql?(other.class) and kind.eql?(other.kind) and name.eql?(other.name) and path.eql?(other.path) and info.eql?(other.info)
113
110
  end
114
111
 
115
112
  def == other
116
- return other && kind == other.kind && name == other.name && path == other.path
113
+ other and kind == other.kind and name == other.name and path == other.path
117
114
  end
118
115
 
119
116
  def default_locale?
@@ -41,7 +41,7 @@ module Utopia
41
41
  :display => :display,
42
42
  }
43
43
 
44
- def self.index(root, path, options = {})
44
+ def self.index(root, path, **options)
45
45
  options = DEFAULT_INDEX_OPTIONS.merge(options)
46
46
 
47
47
  ordered = self.new(root, path, options).ordered
@@ -134,7 +134,7 @@ module Utopia
134
134
 
135
135
  # A helper method for accessing controller variables from view:
136
136
  def controller
137
- @request.controller
137
+ @request.env[VARIABLES_KEY]
138
138
  end
139
139
 
140
140
  def parse_xml(xml_data)
@@ -315,8 +315,9 @@ module Utopia
315
315
  @controller.lookup_node(path)
316
316
  end
317
317
 
318
- def local_path(path = ".", base = nil)
319
- path = Path.create(path)
318
+ def local_path(path = '.', base = nil)
319
+ path = Path[path]
320
+
320
321
  root = Pathname.new(@controller.root)
321
322
 
322
323
  if path.absolute?
@@ -342,8 +343,8 @@ module Utopia
342
343
  uri_path.dirname
343
344
  end
344
345
 
345
- def links(path = ".", options = {}, &block)
346
- path = uri_path.dirname + Path.create(path)
346
+ def links(path = '.', **options, &block)
347
+ path = uri_path.dirname + Path[path]
347
348
  links = Links.index(@controller.root, path, options)
348
349
 
349
350
  if block_given?
@@ -361,14 +362,14 @@ module Utopia
361
362
  def siblings_path
362
363
  name = @uri_path.last.split('.', 2).first
363
364
 
364
- if name == "index"
365
+ if name == INDEX
365
366
  @uri_path.dirname(2)
366
367
  else
367
368
  @uri_path.dirname
368
369
  end
369
370
  end
370
371
 
371
- def sibling_links(options = {})
372
+ def sibling_links(**options)
372
373
  return Links.index(@controller.root, siblings_path, options)
373
374
  end
374
375
 
@@ -20,25 +20,32 @@
20
20
 
21
21
  require_relative 'path'
22
22
 
23
+ require_relative 'middleware'
23
24
  require_relative 'controller/variables'
24
25
  require_relative 'controller/action'
25
26
  require_relative 'controller/base'
26
27
 
27
- class Rack::Request
28
- def controller(&block)
29
- if block_given?
30
- env["utopia.controller"].instance_eval(&block)
31
- else
32
- env["utopia.controller"]
33
- end
34
- end
35
- end
28
+ require_relative 'controller/rewrite'
36
29
 
37
30
  module Utopia
31
+ module Controllers
32
+ def self.class_name_for_controller(controller)
33
+ controller.uri_path.to_a.collect{|_| _.capitalize}.join + "_#{controller.object_id}"
34
+ end
35
+
36
+ def self.define(klass)
37
+ self.const_set(
38
+ class_name_for_controller(klass),
39
+ klass,
40
+ )
41
+ end
42
+ end
43
+
38
44
  class Controller
39
- CONTROLLER_RB = "controller.rb".freeze
40
-
41
- def initialize(app, options = {})
45
+ CONTROLLER_RB = 'controller.rb'.freeze
46
+ PATH_INFO_KEY = 'PATH_INFO'.freeze
47
+
48
+ def initialize(app, **options)
42
49
  @app = app
43
50
  @root = options[:root] || Utopia::default_root
44
51
 
@@ -77,11 +84,10 @@ module Utopia
77
84
 
78
85
  klass.const_set(:CONTROLLER, self)
79
86
 
80
- $LOAD_PATH.unshift(base_path)
81
-
82
87
  klass.class_eval(File.read(controller_path), controller_path)
83
88
 
84
- $LOAD_PATH.delete(base_path)
89
+ # Give the controller a useful name:
90
+ # Controllers.define(klass)
85
91
 
86
92
  return klass.new
87
93
  else
@@ -89,45 +95,40 @@ module Utopia
89
95
  end
90
96
  end
91
97
 
92
- def invoke_controllers(variables, request, done = Set.new)
93
- path = Path.create(request.path_info)
98
+ def invoke_controllers(request)
99
+ relative_path = Path[request.path_info]
94
100
  controller_path = Path.new
101
+ variables = request.env[VARIABLES_KEY]
95
102
 
96
- path.descend do |controller_path|
97
- # puts "Invoke controller: #{controller_path}"
103
+ while relative_path.components.any?
104
+ controller_path.components << relative_path.components.shift
105
+
98
106
  if controller = lookup_controller(controller_path)
99
- # We only want to invoke controllers which have not already been invoked:
100
- unless done.include? controller
101
- # If we get throw :rewrite, location, the URL has been rewritten and we need to request again:
102
- location = catch(:rewrite) do
103
- # Invoke the controller and if it returns a result, send it back out:
104
- if result = controller.process!(request, path)
105
- return result
106
- end
107
- end
108
-
109
- if location
110
- # Rewrite relative paths based on the controller's URI:
111
- request.env['PATH_INFO'] = Path[location].expand(controller.class.uri_path).to_s
112
-
113
- return invoke_controllers(variables, request, done)
114
- end
115
-
116
- done << controller
107
+ # Don't modify the original controller:
108
+ controller = controller.clone
109
+
110
+ # Append the controller to the set of controller variables, updates the controller with all current instance variables.
111
+ variables << controller
112
+
113
+ if result = controller.process!(request, relative_path)
114
+ return result
117
115
  end
118
116
  end
119
117
  end
120
118
 
119
+ # The controllers may have rewriten the path so we update the path info:
120
+ request.env[PATH_INFO_KEY] = controller_path.to_s
121
+
121
122
  # No controller gave a useful result:
122
123
  return nil
123
124
  end
124
125
 
125
126
  def call(env)
126
- variables = (env["utopia.controller"] ||= Variables.new)
127
+ variables = (env[VARIABLES_KEY] ||= Variables.new)
127
128
 
128
129
  request = Rack::Request.new(env)
129
130
 
130
- if result = invoke_controllers(variables, request)
131
+ if result = invoke_controllers(request)
131
132
  return result
132
133
  end
133
134
 
@@ -21,8 +21,7 @@
21
21
  module Utopia
22
22
  class Controller
23
23
  class Action < Hash
24
- attr_accessor :callback
25
- attr_accessor :options
24
+ attr_accessor :path, :callback, :options
26
25
 
27
26
  def callback?
28
27
  @callback != nil
@@ -33,15 +32,15 @@ module Utopia
33
32
  end
34
33
 
35
34
  def eql? other
36
- super and self.callback.eql? other.callback and self.options.eql? other.options
35
+ super and @callback.eql? other.callback and @options.eql? other.options and @path.eql? other.path
37
36
  end
38
37
 
39
38
  def hash
40
- [super, callback, options].hash
39
+ [super, callback, options, path].hash
41
40
  end
42
41
 
43
42
  def == other
44
- super and (self.callback == other.callback) and (self.options == other.options)
43
+ super and @callback == other.callback and @options == other.options and @path == other.path
45
44
  end
46
45
 
47
46
  protected
@@ -76,28 +75,31 @@ module Utopia
76
75
  # relative_path = 2014/mr-potato
77
76
  # actions => {:** => A}
78
77
  def select(relative_path)
79
- actions = []
80
-
81
- append(relative_path.reverse, 0, actions)
82
-
83
- return actions
78
+ selection = [].tap do |actions|
79
+ append(relative_path.reverse, 0, actions)
80
+ end
84
81
  end
85
82
 
86
- def define(path, options = {}, &callback)
83
+ def define(path, **options, &callback)
87
84
  current = self
88
85
 
89
86
  path.reverse.each do |name|
90
87
  current = (current[name.to_sym] ||= Action.new)
91
88
  end
92
89
 
90
+ current.path = path
93
91
  current.options = options
94
92
  current.callback = callback
95
93
 
96
94
  return current
97
95
  end
98
96
 
97
+ def arity
98
+ @callback ? @callback.arity : 0
99
+ end
100
+
99
101
  def invoke!(controller, *arguments)
100
- controller.instance_exec(*arguments, &@callback)
102
+ controller.instance_exec(*arguments, self, &@callback)
101
103
  end
102
104
 
103
105
  def inspect
@@ -22,6 +22,8 @@ require_relative '../http'
22
22
 
23
23
  module Utopia
24
24
  class Controller
25
+ EMPTY_BODY = [].freeze
26
+
25
27
  class Base
26
28
  def self.base_path
27
29
  self.const_get(:BASE_PATH)
@@ -36,55 +38,56 @@ module Utopia
36
38
  end
37
39
 
38
40
  class << self
39
- def require_local(path)
40
- require File.join(base_path, path)
41
- end
42
-
43
41
  def direct?(path)
44
42
  path.dirname == uri_path
45
43
  end
46
44
 
45
+ def patterns
46
+ @patterns ||= []
47
+ end
48
+
49
+ def match(pattern, &block)
50
+ patterns << [pattern, block]
51
+ end
52
+
47
53
  def actions
48
54
  @actions ||= Action.new
49
55
  end
50
56
 
51
- def on(path, options = {}, &block)
52
- if Symbol === path
53
- path = ['**', path]
57
+ def on(first, *path, **options, &block)
58
+ if first.is_a? Symbol
59
+ first = ['**', first]
54
60
  end
55
61
 
56
- actions.define(Path.create(path).components, options, &block)
62
+ actions.define(Path.split(first) + path, options, &block)
57
63
  end
58
64
 
59
65
  def lookup(path)
60
- possible_actions = actions.select((path - uri_path).components)
66
+ relative_path = (path - uri_path).to_a
67
+
68
+ possible_actions = actions.select(relative_path)
61
69
  end
62
70
  end
63
71
 
64
72
  # Given a path, look up all matched actions.
65
- def lookup(path)
73
+ def actions_for_request(request, path)
66
74
  self.class.lookup(path)
67
75
  end
68
76
 
69
77
  # Given a request, call associated actions if at least one exists.
70
78
  def passthrough(request, path)
71
- actions = lookup(path)
79
+ actions = actions_for_request(request, path)
72
80
 
73
- if actions.size > 0
74
- variables = request.controller
75
- controller_clone = self.clone
76
-
77
- variables << controller_clone
78
-
81
+ unless actions.empty?
79
82
  response = catch(:response) do
80
83
  # By default give nothing - i.e. keep on processing:
81
84
  actions.each do |action|
82
- action.invoke!(controller_clone, request, path)
85
+ action.invoke!(self, request, path)
83
86
  end and nil
84
87
  end
85
88
 
86
89
  if response
87
- return controller_clone.respond_with(*response)
90
+ return self.respond_with(*response)
88
91
  end
89
92
  end
90
93
 
@@ -111,11 +114,7 @@ module Utopia
111
114
  end
112
115
 
113
116
  def redirect! (target, status = 302)
114
- respond! :redirect => target.to_str, :status => status
115
- end
116
-
117
- def rewrite! location
118
- throw :rewrite, location.to_str
117
+ respond! :redirect => target, :status => status
119
118
  end
120
119
 
121
120
  def fail!(error = :bad_request)
@@ -140,25 +139,26 @@ module Utopia
140
139
  status = options[:status] || status
141
140
  end
142
141
 
143
- status = Utopia::HTTP::STATUS_CODES[status] || status
142
+ status = HTTP::STATUS_CODES[status] || status
144
143
  headers = options[:headers] || {}
145
144
 
146
- if options[:type]
147
- headers['Content-Type'] ||= options[:type]
145
+ if type = options[:type]
146
+ headers[HTTP::CONTENT_TYPE] ||= type
148
147
  end
149
148
 
150
- if options[:redirect]
151
- headers["Location"] = options[:redirect]
149
+ if redirect = options[:redirect]
150
+ headers[HTTP::LOCATION] = redirect.to_s
152
151
  status = 302 if status < 300 || status >= 400
153
152
  end
154
153
 
155
- body = []
156
154
  if options[:body]
157
155
  body = options[:body]
158
156
  elsif options[:content]
159
157
  body = [options[:content]]
160
158
  elsif status >= 300
161
- body = [Utopia::HTTP::STATUS_DESCRIPTIONS[status] || "Status #{status}"]
159
+ body = [HTTP::STATUS_DESCRIPTIONS[status] || "Status #{status}"]
160
+ else
161
+ body = EMPTY_BODY
162
162
  end
163
163
 
164
164
  return [status, headers, body]
@@ -166,7 +166,6 @@ module Utopia
166
166
 
167
167
  # Return nil if this controller didn't do anything. Request will keep on processing. Return a valid rack response if the controller can do so.
168
168
  def process!(request, path)
169
- # puts "process! #{request} #{path}"
170
169
  passthrough(request, path)
171
170
  end
172
171
  end