orange-core 0.5.3

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 (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
+