utopia 1.0.11 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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