orange-core 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/README.markdown +145 -0
  2. data/lib/orange-core.rb +8 -0
  3. data/lib/orange-core/application.rb +132 -0
  4. data/lib/orange-core/assets/css/exceptions.css +50 -0
  5. data/lib/orange-core/assets/js/exceptions.js +44 -0
  6. data/lib/orange-core/carton.rb +178 -0
  7. data/lib/orange-core/core.rb +266 -0
  8. data/lib/orange-core/magick.rb +270 -0
  9. data/lib/orange-core/middleware/base.rb +96 -0
  10. data/lib/orange-core/middleware/database.rb +45 -0
  11. data/lib/orange-core/middleware/four_oh_four.rb +45 -0
  12. data/lib/orange-core/middleware/globals.rb +17 -0
  13. data/lib/orange-core/middleware/loader.rb +13 -0
  14. data/lib/orange-core/middleware/rerouter.rb +53 -0
  15. data/lib/orange-core/middleware/restful_router.rb +99 -0
  16. data/lib/orange-core/middleware/route_context.rb +39 -0
  17. data/lib/orange-core/middleware/route_site.rb +51 -0
  18. data/lib/orange-core/middleware/show_exceptions.rb +80 -0
  19. data/lib/orange-core/middleware/static.rb +67 -0
  20. data/lib/orange-core/middleware/static_file.rb +32 -0
  21. data/lib/orange-core/middleware/template.rb +60 -0
  22. data/lib/orange-core/packet.rb +232 -0
  23. data/lib/orange-core/plugin.rb +172 -0
  24. data/lib/orange-core/resource.rb +96 -0
  25. data/lib/orange-core/resources/mapper.rb +36 -0
  26. data/lib/orange-core/resources/model_resource.rb +228 -0
  27. data/lib/orange-core/resources/not_found.rb +10 -0
  28. data/lib/orange-core/resources/page_parts.rb +68 -0
  29. data/lib/orange-core/resources/parser.rb +113 -0
  30. data/lib/orange-core/resources/routable_resource.rb +16 -0
  31. data/lib/orange-core/resources/scaffold.rb +106 -0
  32. data/lib/orange-core/stack.rb +226 -0
  33. data/lib/orange-core/templates/exceptions.haml +111 -0
  34. data/lib/orange-core/views/default_resource/create.haml +4 -0
  35. data/lib/orange-core/views/default_resource/edit.haml +9 -0
  36. data/lib/orange-core/views/default_resource/list.haml +10 -0
  37. data/lib/orange-core/views/default_resource/show.haml +4 -0
  38. data/lib/orange-core/views/default_resource/table_row.haml +7 -0
  39. data/lib/orange-core/views/not_found/404.haml +2 -0
  40. data/spec/orange-core/application_spec.rb +183 -0
  41. data/spec/orange-core/carton_spec.rb +136 -0
  42. data/spec/orange-core/core_spec.rb +248 -0
  43. data/spec/orange-core/magick_spec.rb +96 -0
  44. data/spec/orange-core/middleware/base_spec.rb +38 -0
  45. data/spec/orange-core/middleware/globals_spec.rb +3 -0
  46. data/spec/orange-core/middleware/rerouter_spec.rb +3 -0
  47. data/spec/orange-core/middleware/restful_router_spec.rb +3 -0
  48. data/spec/orange-core/middleware/route_context_spec.rb +3 -0
  49. data/spec/orange-core/middleware/route_site_spec.rb +3 -0
  50. data/spec/orange-core/middleware/show_exceptions_spec.rb +3 -0
  51. data/spec/orange-core/middleware/static_file_spec.rb +3 -0
  52. data/spec/orange-core/middleware/static_spec.rb +3 -0
  53. data/spec/orange-core/mock/mock_app.rb +16 -0
  54. data/spec/orange-core/mock/mock_carton.rb +43 -0
  55. data/spec/orange-core/mock/mock_core.rb +2 -0
  56. data/spec/orange-core/mock/mock_middleware.rb +25 -0
  57. data/spec/orange-core/mock/mock_mixins.rb +19 -0
  58. data/spec/orange-core/mock/mock_model_resource.rb +47 -0
  59. data/spec/orange-core/mock/mock_pulp.rb +24 -0
  60. data/spec/orange-core/mock/mock_resource.rb +26 -0
  61. data/spec/orange-core/mock/mock_router.rb +10 -0
  62. data/spec/orange-core/orange_spec.rb +19 -0
  63. data/spec/orange-core/packet_spec.rb +203 -0
  64. data/spec/orange-core/resource_spec.rb +96 -0
  65. data/spec/orange-core/resources/mapper_spec.rb +5 -0
  66. data/spec/orange-core/resources/model_resource_spec.rb +246 -0
  67. data/spec/orange-core/resources/parser_spec.rb +5 -0
  68. data/spec/orange-core/resources/routable_resource_spec.rb +5 -0
  69. data/spec/orange-core/spec_helper.rb +53 -0
  70. data/spec/orange-core/stack_spec.rb +232 -0
  71. data/spec/stats.rb +182 -0
  72. metadata +227 -0
@@ -0,0 +1,266 @@
1
+ require 'dm-core'
2
+ require 'rack'
3
+ require 'rack/builder'
4
+
5
+ module Orange
6
+ # Declare submodules for later use
7
+ module Pulp; end
8
+ module Mixins; end
9
+ module Plugins; end
10
+
11
+ attr_accessor :plugins
12
+
13
+ # Support for plugins
14
+ def self.plugins
15
+ @plugins ||= []
16
+ end
17
+
18
+ # Allows adding plugins
19
+ def self.plugin(plugin)
20
+ self.plugins << plugin if plugin.kind_of?(Orange::Plugins::Base)
21
+ end
22
+
23
+ # Allow mixins directly from Orange
24
+ def self.mixin(inc)
25
+ Core.mixin inc
26
+ end
27
+
28
+ # Allow pulp directly from Orange
29
+ def self.add_pulp(inc)
30
+ Packet.mixin inc
31
+ end
32
+
33
+ # Core is one of two main sources of interaction for Orange Applications
34
+ #
35
+ # All portions of Orange based code have access to the Core upon
36
+ # initialization. Orange allows access to individual resources,
37
+ # and also allows single point for event registration and firing.
38
+ #
39
+ # Functionality of the core can be extended by loading resources,
40
+ # or by mixins that directly affect the Core. Generally, resources
41
+ # are the less convoluted (easier to debug) way to do it.
42
+ class Core
43
+ # Sets the default options for Orange Applications
44
+ DEFAULT_CORE_OPTIONS =
45
+ {
46
+ :contexts => [:live, :admin, :orange],
47
+ :default_context => :live,
48
+ :default_resource => :not_found,
49
+ :default_database => 'sqlite3::memory:'
50
+ } unless defined?(DEFAULT_CORE_OPTIONS)
51
+
52
+ # Args will be set to the @options array.
53
+ # Block DSL style option setting also available:
54
+ #
55
+ # orange = Orange::Core.new(:optional_option => 'foo') do
56
+ # haml true
57
+ # site_name "Banana"
58
+ # custom_router MyRouterClass.new
59
+ # end
60
+ #
61
+ # orange.options[:site_name] #=> "Banana"
62
+ #
63
+ # This method calls afterLoad when it is done. Subclasses can override
64
+ # the afterLoad method for initialization needs.
65
+ def initialize(*args, &block)
66
+ @options = Options.new(*args, &block).hash.with_defaults(DEFAULT_CORE_OPTIONS)
67
+ @resources = {}
68
+ @application = false
69
+ @stack = false
70
+ @middleware = []
71
+ @events = {}
72
+ @file = __FILE__
73
+ load(Orange::Parser.new, :parser)
74
+ load(Orange::Mapper.new, :mapper)
75
+ load(Orange::Scaffold.new, :scaffold)
76
+ load(Orange::PageParts.new, :page_parts)
77
+ Orange.plugins.each{|p| p.resources.each{|args| load(*args)} if p.has_resources?}
78
+ self.register(:stack_loaded) do |s|
79
+ @middleware.each{|m| m.stack_init if m.respond_to?(:stack_init)}
80
+ @application.stack_init if @application
81
+ end
82
+ self.register(:stack_reloading){|s| @middleware = []} # Dump middleware on stack reload
83
+ # load(Orange::AdminResource.new, :admin)
84
+ afterLoad
85
+ self
86
+ end
87
+
88
+ # Returns the orange library directory
89
+ # @return [String] the directory name indicating where the core file is
90
+ # located
91
+ def core_dir(*args)
92
+ if args
93
+ File.join((options[:core_dir] ||= File.dirname(__FILE__)), *args)
94
+ else
95
+ options[:core_dir] ||= File.dirname(__FILE__)
96
+ end
97
+ end
98
+
99
+ # Returns the directory of the currently executing file (using Dir.pwd),
100
+ # can be overriden using the option :app_dir in initialization
101
+ #
102
+ # @return [String] the directory name of the currently running application
103
+ def app_dir(*args)
104
+ if args
105
+ File.join((options[:app_dir] ||= Dir.pwd), *args)
106
+ else
107
+ options[:app_dir] ||= Dir.pwd
108
+ end
109
+ end
110
+
111
+ # Called by initialize after finished loading
112
+ def afterLoad
113
+ true
114
+ end
115
+
116
+ # Returns status of a given resource by short name
117
+ # @param [Symbol] resource_name The short name of the resource
118
+ # @return [Boolean] result of has_key? in the resources list
119
+ def loaded?(resource_name)
120
+ @resources.has_key?(resource_name)
121
+ end
122
+
123
+ # Takes an instance of a Orange::Resource subclass, sets orange
124
+ # then adds it to the orange resources
125
+ #
126
+ # It can be assigned a short name to be used for accessing later
127
+ # on. If no short name is assigned, one will be generated by downcasing
128
+ # the class name and changing it to a symbol
129
+ #
130
+ # Resources must respond to set_orange, which is automatically used to
131
+ # create a link back to the Core, and to notify the resource of its assigned
132
+ # short name.
133
+ #
134
+ # @param [Orange::Resource] resource An instance of Orange::Resource subclass
135
+ # @param [optional, Symbol, String] name A short name to assign as key in Hash
136
+ # list of resources.
137
+ # Doesn't necessarily need to be a symbol, but generally is.
138
+ # Set to the class name lowercase as a symbol by default.
139
+ def load(resource, name = false)
140
+ name = resource.orange_name if(!name)
141
+ name = resource.class.to_s.gsub(/::/, '_').downcase.to_sym if(!name)
142
+ @resources[name] = resource.set_orange(self, name)
143
+ end
144
+
145
+ # Takes an instance of Orange::Middleware::Base subclass and
146
+ # keeps it for later. This way we can provide introspection into the
147
+ # middleware instances (useful for calling stack_init on them)
148
+ def middleware(middle = false)
149
+ @middleware << middle if middle
150
+ @middleware
151
+ end
152
+
153
+ # Takes an instance of Orange::Application and saves it.
154
+ def application(app = false)
155
+ @application = app if app
156
+ @application
157
+ end
158
+
159
+ # Takes an instance of Orange::Stack and saves it.
160
+ def stack(new_stack = false)
161
+ @stack = new_stack if new_stack
162
+ @stack
163
+ end
164
+
165
+ # Takes an instance of Orange::Stack and saves it.
166
+ def stack=(new_stack)
167
+ @stack = new_stack
168
+ end
169
+
170
+ # Convenience self for consistent naming across middleware
171
+ # @return [Orange::Core] self
172
+ def orange; self; end
173
+
174
+ # Registers interest in a callback for a named event.
175
+ #
176
+ # Event registration is stored as a hash list of events and arrays
177
+ # of procs to be executed on each event.
178
+ #
179
+ # @param [Symbol] event the name of the event registered for
180
+ # @param [optional, Integer] position the position to place the event in,
181
+ # by default goes to the front of the list. Doesn't necessarily need
182
+ # to be exact count, empty spaces in array are taken out. Forcing the
183
+ # event to be at 99 or some such position will typically make sure it
184
+ # happens last in the firing process.
185
+ # @param [Block] block The code to be executed upon event firing.
186
+ # Saved to an array of procs that are called when #fire is called.
187
+ # Block must accept one param, which is the intended to be the packet
188
+ # causing the block to fire, unless the event happens in setup.
189
+ def register(event, position = 0, &block)
190
+ if block_given?
191
+ if @events[event]
192
+ @events[event].insert(position, Proc.new)
193
+ else
194
+ @events[event] = Array.new.insert(position, Proc.new)
195
+ end
196
+ end
197
+ end
198
+
199
+ # Fires a callback for a given packet (or other object)
200
+ #
201
+ # @param [Symbol] event name of event something has registered for
202
+ # @param [Orange::Packet, object] packet Object, generally Orange::Packet,
203
+ # causing the fire. This is passed to each Proc registered.
204
+ # @return [Boolean] returns false if nothing has been registered for the
205
+ # event, otherwise true.
206
+ def fire(event, packet, *args)
207
+ return false unless @events[event]
208
+ @events[event].compact!
209
+ for callback in @events[event]
210
+ callback.call(packet, *args)
211
+ end
212
+ true
213
+ end
214
+
215
+ # Returns options of the orange core
216
+ #
217
+ # @return [Hash] Hash of options
218
+ def options
219
+ @options
220
+ end
221
+
222
+
223
+ # Accesses resources array, stored as a hash {:short_name => Resource instance,...}
224
+ #
225
+ # @param [Symbol] name the short name for the requested resource
226
+ # @param [optional, Boolean] ignore Whether to ignore any calls to resource if not found
227
+ # (false is default). This will allow method calls to non-existent resources. Should be
228
+ # used with caution.
229
+ # @return [Orange::Resource] the resource for the given short name
230
+ def [](name, ignore = false)
231
+ if ignore && !loaded?(name)
232
+ Ignore.new
233
+ else
234
+ @resources[name]
235
+ end
236
+ end
237
+
238
+ # Includes module in the Packet class
239
+ # @param [Module] inc module to be included
240
+ def add_pulp(inc)
241
+ self.class.add_pulp inc
242
+ end
243
+
244
+ # Includes module in this class
245
+ # @param [Module] inc module to be included
246
+ def mixin(inc)
247
+ self.class.mixin inc
248
+ end
249
+
250
+ # Includes module in this class
251
+ # @param [Module] inc module to be included
252
+ def self.mixin(inc)
253
+ include inc
254
+ end
255
+
256
+ # Includes module in the Packet class
257
+ # @param [Module] inc module to be included
258
+ def self.add_pulp(inc)
259
+ Packet.mixin inc
260
+ end
261
+
262
+ def inspect
263
+ "#<Orange::Core:0x#{self.object_id.to_s(16)}>"
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,270 @@
1
+ # Monkey Patch the extract_options! stolen from ActiveSupport
2
+ # @private
3
+ class ::Array #:nodoc:
4
+ def extract_options!
5
+ last.is_a?(::Hash) ? pop : {}
6
+ end
7
+ def extract_with_defaults(defaults)
8
+ extract_options!.with_defaults(defaults)
9
+ end
10
+ end
11
+
12
+ # Monkey Patch for merging defaults into a hash
13
+ # @private
14
+ class ::Hash #:nodoc:
15
+ def with_defaults(defaults)
16
+ self.merge(defaults){ |key, old, new| old.nil? ? new : old }
17
+ end
18
+ def with_defaults!(defaults)
19
+ self.merge!(defaults){ |key, old, new| old.nil? ? new : old }
20
+ end
21
+ end
22
+
23
+
24
+
25
+ # Monkey patch for awesome array -> hash conversions
26
+ # use:
27
+ #
28
+ # [:x, :y, :z].inject_hash do |results, letter|
29
+ # results[letter] = rand(100)
30
+ # end
31
+ #
32
+ # # => {:x => 32, :y => 63, :z => 91}
33
+ # @private
34
+ module Enumerable #:nodoc:
35
+ def inject_hash(hash = {})
36
+ inject(hash) {|(h,item)| yield(h,item); h}
37
+ end
38
+ end
39
+
40
+ # Borrowed from Roman2k:
41
+ # http://github.com/Roman2K/class-inheritable-attributes/
42
+ # @private
43
+ module ClassInheritableAttributes
44
+ class << self
45
+ def eval_in_accessor_module(klass, code)
46
+ mod = klass.instance_eval { @_inheritable_attribute_accessors ||= Module.new }
47
+ klass.extend(mod)
48
+ mod.module_eval(code)
49
+ end
50
+
51
+ def fetch_value(klass, attribute)
52
+ each_parent(klass) do |parent|
53
+ if values = registry[parent] and values.key?(attribute)
54
+ return values[attribute]
55
+ elsif parent.instance_variable_defined?("@#{attribute}")
56
+ return parent.instance_variable_get("@#{attribute}")
57
+ end
58
+ end
59
+ return nil
60
+ end
61
+
62
+ def store_value(klass, attribute, value)
63
+ if Thread.current == Thread.main
64
+ if value.nil?
65
+ klass.instance_eval do
66
+ remove_instance_variable("@#{attribute}") if instance_variable_defined?("@#{attribute}")
67
+ end
68
+ else
69
+ klass.instance_variable_set("@#{attribute}", value)
70
+ end
71
+ else
72
+ registry[klass] ||= {}
73
+ if value.nil?
74
+ registry[klass].delete(attribute)
75
+ registry.delete(klass) if registry[klass].empty?
76
+ else
77
+ registry[klass][attribute] = value
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def each_parent(klass)
85
+ while klass < Object
86
+ yield klass
87
+ klass = klass.superclass
88
+ end
89
+ end
90
+
91
+ def registry
92
+ Thread.current[ClassInheritableAttributes.name] ||= {}
93
+ end
94
+ end
95
+
96
+ def cattr_reader(*attributes)
97
+ attributes.each do |attribute|
98
+ ClassInheritableAttributes.eval_in_accessor_module(self, <<-EOS)
99
+ def #{attribute}
100
+ #{ClassInheritableAttributes}.fetch_value(self, :#{attribute})
101
+ end
102
+ EOS
103
+ end
104
+ end
105
+
106
+ def cattr_writer(*attributes)
107
+ attributes.each do |attribute|
108
+ ClassInheritableAttributes.eval_in_accessor_module(self, <<-EOS)
109
+ def #{attribute}=(value)
110
+ #{ClassInheritableAttributes}.store_value(self, :#{attribute}, value)
111
+ end
112
+ EOS
113
+ end
114
+ end
115
+
116
+ def cattr_accessor(*attributes)
117
+ cattr_reader(*attributes)
118
+ cattr_writer(*attributes)
119
+ end
120
+ end
121
+
122
+ module Orange
123
+
124
+ # Class that extends hash so that [] can have an optional second attribute
125
+ class DefaultHash < ::Hash
126
+ def [](key, my_default = nil)
127
+ my_default = self.default if my_default.nil?
128
+ self.has_key?(key) ? super(key) : my_default
129
+ end
130
+ end
131
+
132
+ # This class acts as a simple sink for ignoring messages, it will return itself
133
+ # for any message call. Orange::Core can optionally return this when trying
134
+ # to access resources so that you can make method calls to a resource that
135
+ # might not be really there. It will silently swallow any errors that might arrise,
136
+ # so this should be used with caution.
137
+ class Ignore
138
+ def blank?; true; end
139
+ def method_missing(name, *args, &block)
140
+ return self
141
+ end
142
+ end
143
+
144
+ # Simple class for evaluating options and allowing us to access them.
145
+ class Options
146
+
147
+ def initialize(*options, &block)
148
+ @options = options.extract_options!
149
+ @options ||= {}
150
+ instance_eval(&block) if block_given?
151
+ end
152
+
153
+ def hash
154
+ @options
155
+ end
156
+
157
+ def method_missing(key, *args)
158
+ return (@options[key.to_s.gsub(/\?$/, '').to_sym].eql?(true)) if key.to_s.match(/\?$/)
159
+ if args.empty?
160
+ @options[key.to_sym]
161
+ elsif(key.to_s.match(/\=$/))
162
+ @options[key.to_s.gsub(/\=$/, '').to_sym] = (args.size == 1 ? args.first : args)
163
+ else
164
+ @options[key.to_sym] = (args.size == 1 ? args.first : args)
165
+ end
166
+ end
167
+ end
168
+
169
+ # @private
170
+ module Inflector
171
+ extend self
172
+ # @private
173
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
174
+ if first_letter_in_uppercase
175
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
176
+ else
177
+ lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1]
178
+ end
179
+ end
180
+
181
+ if Module.method(:const_get).arity == 1
182
+ def constantize(camel_cased_word)
183
+ names = camel_cased_word.split('::')
184
+ names.shift if names.empty? || names.first.empty?
185
+
186
+ constant = Object
187
+ names.each do |name|
188
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
189
+ end
190
+ constant
191
+ end
192
+ else
193
+ def constantize(camel_cased_word) #:nodoc:
194
+ names = camel_cased_word.split('::')
195
+ names.shift if names.empty? || names.first.empty?
196
+
197
+ constant = Object
198
+ names.each do |name|
199
+ constant = constant.const_get(name, false) || constant.const_missing(name)
200
+ end
201
+ constant
202
+ end
203
+ end
204
+
205
+ end
206
+
207
+ end
208
+
209
+ # @private
210
+ class Object #:nodoc:
211
+ # An object is blank if it's false, empty, or a whitespace string.
212
+ # For example, "", " ", +nil+, [], and {} are blank.
213
+ #
214
+ # This simplifies
215
+ #
216
+ # if !address.nil? && !address.empty?
217
+ #
218
+ # to
219
+ #
220
+ # if !address.blank?
221
+ def blank?
222
+ respond_to?(:empty?) ? empty? : !self
223
+ end
224
+ end
225
+
226
+ # @private
227
+ class NilClass #:nodoc:
228
+ def blank?
229
+ true
230
+ end
231
+ end
232
+
233
+ # @private
234
+ class FalseClass #:nodoc:
235
+ def blank?
236
+ true
237
+ end
238
+ end
239
+
240
+ # @private
241
+ class TrueClass #:nodoc:
242
+ def blank?
243
+ false
244
+ end
245
+ end
246
+
247
+ # @private
248
+ class Array #:nodoc:
249
+ alias_method :blank?, :empty?
250
+ end
251
+
252
+ # @private
253
+ class Hash #:nodoc:
254
+ alias_method :blank?, :empty?
255
+ end
256
+
257
+ # @private
258
+ class String #:nodoc:
259
+ def blank?
260
+ self !~ /\S/
261
+ end
262
+ end
263
+
264
+ # @private
265
+ class Numeric #:nodoc:
266
+ def blank?
267
+ false
268
+ end
269
+ end
270
+