spiderfw 0.6.23 → 0.6.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/CHANGELOG +10 -1
  2. data/README.rdoc +1 -1
  3. data/VERSION +1 -1
  4. data/apps/config_editor/_init.rb +1 -2
  5. data/apps/config_editor/controllers/config_editor_controller.rb +1 -7
  6. data/apps/core/admin/controllers/admin_controller.rb +1 -1
  7. data/apps/core/admin/public/css/sass/admin.css +35 -31
  8. data/apps/core/admin/public/sass/admin.scss +6 -1
  9. data/apps/core/components/widgets/crud/crud.shtml +2 -2
  10. data/apps/core/components/widgets/table/table.rb +5 -5
  11. data/apps/core/forms/tags/element_row.erb +15 -10
  12. data/apps/core/forms/widgets/form/form.rb +35 -22
  13. data/apps/core/forms/widgets/inputs/checkbox/checkbox.shtml +2 -2
  14. data/apps/core/forms/widgets/inputs/date_time/date_time.shtml +2 -2
  15. data/apps/core/forms/widgets/inputs/file_input/file_input.shtml +2 -2
  16. data/apps/core/forms/widgets/inputs/html_area/html_area.shtml +2 -2
  17. data/apps/core/forms/widgets/inputs/input/input.shtml +2 -2
  18. data/apps/core/forms/widgets/inputs/password/password.shtml +2 -2
  19. data/apps/core/forms/widgets/inputs/search_select/search_select.shtml +1 -1
  20. data/apps/core/forms/widgets/inputs/select/select.shtml +2 -2
  21. data/apps/core/forms/widgets/inputs/text/text.shtml +2 -2
  22. data/apps/core/forms/widgets/inputs/text_area/text_area.shtml +2 -2
  23. data/apps/core/forms/widgets/inputs/time_span/time_span.shtml +1 -1
  24. data/blueprints/home/config.ru +8 -0
  25. data/lib/spiderfw/app.rb +416 -224
  26. data/lib/spiderfw/cmd/commands/app.rb +243 -239
  27. data/lib/spiderfw/cmd/commands/cert.rb +421 -417
  28. data/lib/spiderfw/cmd/commands/config.rb +85 -82
  29. data/lib/spiderfw/cmd/commands/console.rb +64 -40
  30. data/lib/spiderfw/cmd/commands/content.rb +29 -25
  31. data/lib/spiderfw/cmd/commands/create.rb +58 -54
  32. data/lib/spiderfw/cmd/commands/model.rb +118 -114
  33. data/lib/spiderfw/cmd/commands/setup.rb +55 -51
  34. data/lib/spiderfw/cmd/commands/test.rb +63 -59
  35. data/lib/spiderfw/cmd/commands/webserver.rb +56 -51
  36. data/lib/spiderfw/config/options/spider.rb +4 -3
  37. data/lib/spiderfw/controller/controller.rb +2 -0
  38. data/lib/spiderfw/controller/http_controller.rb +1 -2
  39. data/lib/spiderfw/controller/mixins/static_content.rb +3 -3
  40. data/lib/spiderfw/controller/mixins/visual.rb +30 -15
  41. data/lib/spiderfw/controller/response.rb +84 -0
  42. data/lib/spiderfw/controller/session/file_session.rb +2 -2
  43. data/lib/spiderfw/http/adapters/rack.rb +12 -13
  44. data/lib/spiderfw/http/server.rb +80 -46
  45. data/lib/spiderfw/i18n/cldr.rb +6 -9
  46. data/lib/spiderfw/model/base_model.rb +103 -23
  47. data/lib/spiderfw/model/condition.rb +110 -25
  48. data/lib/spiderfw/model/mappers/db_mapper.rb +14 -6
  49. data/lib/spiderfw/model/mappers/mapper.rb +440 -197
  50. data/lib/spiderfw/model/model.rb +105 -21
  51. data/lib/spiderfw/model/model_hash.rb +9 -1
  52. data/lib/spiderfw/model/query.rb +50 -9
  53. data/lib/spiderfw/model/query_set.rb +211 -44
  54. data/lib/spiderfw/model/request.rb +28 -21
  55. data/lib/spiderfw/model/storage/base_storage.rb +125 -10
  56. data/lib/spiderfw/model/storage/db/db_storage.rb +7 -4
  57. data/lib/spiderfw/model/storage.rb +8 -1
  58. data/lib/spiderfw/setup/spider_setup_wizard.rb +9 -7
  59. data/lib/spiderfw/spider.rb +270 -43
  60. data/lib/spiderfw/templates/layout.rb +9 -4
  61. data/lib/spiderfw/templates/resources/sass.rb +3 -2
  62. data/lib/spiderfw/templates/template.rb +1 -0
  63. data/lib/spiderfw/utils/annotations.rb +3 -1
  64. data/lib/spiderfw/utils/logger.rb +1 -1
  65. data/lib/spiderfw/utils/monkey/symbol.rb +4 -2
  66. data/lib/spiderfw/utils/shared_store/file_shared_store.rb +2 -2
  67. data/lib/spiderfw/utils/thread_out.rb +3 -1
  68. data/public/css/error_page.css +83 -0
  69. data/public/js/error_page.js +5 -0
  70. data/spider.gemspec +4 -1
  71. data/templates/email/error.erb +9 -0
  72. metadata +28 -12
  73. data/apps/config_editor/widgets/edit_bool/edit_bool.rb +0 -8
  74. data/apps/config_editor/widgets/edit_bool/edit_bool.shtml +0 -5
data/lib/spiderfw/app.rb CHANGED
@@ -3,249 +3,391 @@ require 'tsort'
3
3
 
4
4
  module Spider
5
5
 
6
+ # The Spider::App module must be included in each apps' main module inside the
7
+ # apps's _init.rb file.
8
+ #
9
+ # Example: apps/my_app/_init.rb
10
+ #
11
+ # module MyApp
12
+ # include Spider::App
13
+ # end
14
+ #
15
+ # It extends the including module, defining several variables and Class methods.
16
+ # All variables are pre-set to defaults, but can be overridden by the including Module
17
+ # (see {ClassMethods}).
18
+ #
19
+ # The app's module can implement several lifecycle hooks (as Module methods):
20
+ # * app_init: called during the framework's init phase
21
+ # * app_startup: called when a server is started
22
+ # * app_shutdown: called when a server is shut down
6
23
  module App
7
24
 
8
- def self.included(mod)
9
- mod.module_eval do
10
-
11
- include Spider::DataTypes
25
+ # Methods set on the App main module.
26
+ # All instance attributes will be set to default values, but can be overridden
27
+ # by the module by setting the corresponding instance variable.
28
+ # Example:
29
+ #
30
+ # module MyApp
31
+ # include Spider::App
32
+ # @controller = :MyController
33
+ # @label = 'My Application'
34
+ # end
35
+ #
36
+ module ClassMethods
37
+
38
+ # @return [String] unique identifier for the app
39
+ attr_reader :id
40
+ # @return [String] filesystem path of the app
41
+ attr_reader :path
42
+ # @return [String] path of the 'public' folder
43
+ attr_reader :pub_path
44
+ # @return [String] path of the 'test' folder
45
+ attr_reader :test_path
46
+ # @return [String] path of the 'setup' folder
47
+ attr_reader :setup_path
48
+ # @return [String] path of the 'widgets' folder
49
+ attr_reader :widgets_path
50
+ # @return [String] path of the 'views' folder
51
+ attr_reader :views_path
52
+ # @return [String] path of the 'tags' folder
53
+ attr_reader :tags_path
54
+ # @return [String] path of the 'models' folder
55
+ attr_reader :models_path
56
+ # @return [String] name, without spaces
57
+ attr_reader :short_name
58
+ # @return [String] url from which the app will be routed
59
+ attr_reader :route_url
60
+ # @return [String] app
61
+ attr_reader :label
62
+ # @return [Gem::Version] app's version
63
+ attr_reader :version
64
+ # @return [String] prefix used to distinguish Database table
65
+ attr_accessor :short_prefix
66
+ # @return [AppSpec] the app's AppSpec
67
+ attr_reader :spec
68
+ # @return [Array] A list of directories to look for translations
69
+ attr_reader :gettext_dirs
70
+ # @return [Array] File extensions to parse for translations
71
+ attr_reader :gettext_extensions
72
+ # @return [Array] Additional GetText parasers to use
73
+ attr_reader :gettext_parsers
74
+
75
+
76
+ # Initializes missing variables to default variables.
77
+ def init
78
+ unless @path
79
+ file = caller[1].split(':')[0]
80
+ dir = File.dirname(file)
81
+ @path = dir
82
+ end
83
+ @path = File.expand_path(@path)
84
+ @short_name ||= Inflector.underscore(self.name).gsub(File::SEPARATOR, '_')
85
+ @dotted_name = Inflector.underscore(self.name).gsub(File::SEPARATOR, '.')
86
+ @pub_path ||= File.join(@path, 'public')
87
+ @test_path ||= File.join(@path, 'test')
88
+ @setup_path ||= File.join(@path, 'setup')
89
+ @models_path ||= File.join(@path, 'models')
90
+ @widgets_path ||= File.join(@path, 'widgets')
91
+ @views_path ||= File.join(@path, '/views')
92
+ @tags_path ||= File.join(@path, 'tags')
93
+ @version = Gem::Version.new(@version.to_s) if @version && !@version.is_a?(Gem::Version)
94
+ spec_path = File.join(@path, "#{@short_name}.appspec")
95
+ load_spec(spec_path) if File.exists?(spec_path)
96
+ @route_url ||= Inflector.underscore(self.name)
97
+ @label ||= @short_name.split('_').each{ |p| p[0] = p[0].chr.upcase }.join(' ')
98
+ @gettext_parsers ||= []
99
+ @gettext_dirs ||= ['lib','bin','controllers','models','views','widgets','public']
100
+ @gettext_extensions ||= ['rb','rhtml','shtml','js']
12
101
 
13
- class << self
14
- attr_reader :id, :path, :pub_path, :test_path, :setup_path, :widgets_path, :views_path, :tags_path, :models_path
15
- attr_reader :short_name, :route_url, :label, :version
16
- attr_accessor :short_prefix
17
- attr_reader :command
18
- attr_reader :spec
19
- attr_reader :gettext_dirs, :gettext_extensions, :gettext_parsers
20
-
21
- def init
22
- unless @path
23
- file = caller[1].split(':')[0]
24
- dir = File.dirname(file)
25
- @path = dir
26
- end
27
- @path = File.expand_path(@path)
28
- @short_name ||= Inflector.underscore(self.name).gsub(File::SEPARATOR, '_')
29
- @dotted_name = Inflector.underscore(self.name).gsub(File::SEPARATOR, '.')
30
- @pub_path ||= File.join(@path, 'public')
31
- @test_path ||= File.join(@path, 'test')
32
- @setup_path ||= File.join(@path, 'setup')
33
- @models_path ||= File.join(@path, 'models')
34
- @widgets_path ||= File.join(@path, 'widgets')
35
- @views_path ||= File.join(@path, '/views')
36
- @tags_path ||= File.join(@path, 'tags')
37
- @version = Gem::Version.new(@version.to_s) if @version && !@version.is_a?(Gem::Version)
38
- spec_path = File.join(@path, "#{@short_name}.appspec")
39
- load_spec(spec_path) if File.exists?(spec_path)
40
- @route_url ||= Inflector.underscore(self.name)
41
- @label ||= @short_name.split('_').each{ |p| p[0] = p[0].chr.upcase }.join(' ')
42
- @gettext_parsers ||= []
43
- @gettext_dirs ||= ['lib','bin','controllers','models','views','widgets','public']
44
- @gettext_extensions ||= ['rb','rhtml','shtml','js']
45
-
46
- find_tags
47
- end
102
+ find_tags
103
+ end
48
104
 
49
- def full_name
50
- @full_name || self.spec.name
51
- end
105
+ # @return [String] The apps' full_name or spec.name
106
+ def full_name
107
+ @full_name || self.spec.name
108
+ end
52
109
 
53
- def description
54
- desc = @description || self.spec.description
55
- desc.blank? ? self.name : desc
56
- end
57
-
58
- def request_url
59
- if u = Spider.conf.get("#{@dotted_name}.url")
60
- return u
61
- end
62
- Spider::ControllerMixins::HTTPMixin.reverse_proxy_mapping('/'+@route_url)
63
- end
64
- alias :url :request_url
65
-
66
- def http_url(action=nil)
67
- if u = Spider.conf.get("#{@dotted_name}.http_url")
68
- if action
69
- u += '/' if u[-1].chr != '/'
70
- u += action.to_s
71
- end
72
- return u
73
- end
74
- return nil unless Spider.site
75
- u = "http://#{Spider.site.domain}"
76
- u += ":#{Spider.site.port}" unless Spider.site.port == 80
77
- u += url
78
- u += "/"+action.to_s if action
79
- u
80
- end
81
-
82
- def https_url
83
- return nil unless Spider.site && Spider.site.ssl?
84
- u = "https://#{Spider.site.domain}"
85
- u += ":#{Spider.site.ssl_port}" unless Spider.site.ssl_port == 443
86
- u += url
87
- u
88
- end
89
-
90
- def http_s_url
91
- return https_url if Spider.site && Spider.site.ssl?
92
- return http_url
93
- end
94
-
95
- def pub_url
96
- if Spider.conf.get('static_content.mode') == 'publish'
97
- Spider::HomeController.pub_url+'/apps/'+self.short_name
98
- else
99
- request_url+'/public'
100
- end
101
- end
102
-
103
- def pub_url!
104
- request_url+'/public'
105
- end
106
-
107
- def controller
108
- #controllers = self.const_get(:Controllers)
109
- if (!@controller || !const_defined?(@controller))
110
- @controller = :AppController
111
- return const_set(@controller, Spider::PageController.clone)
112
-
113
- end
114
- return const_get(@controller)
115
- end
116
-
117
- def models(container=nil)
118
- container ||= self
119
- mods = []
120
- container.constants.each do |c|
121
- begin
122
- mods += get_models(container.const_get(c))
123
- rescue LoadError
124
- end
125
- end
126
- return mods
127
- end
128
-
129
- def get_models(m)
130
- ms = []
131
- if m.respond_to?(:subclass_of?) && m.subclass_of?(Spider::Model::BaseModel)
132
- ms << m
133
- m.constants.each do |c|
134
- sub_mod = m.const_get(c)
135
- next unless sub_mod.is_a?(Module)
136
- next if !sub_mod.subclass_of?(Spider::Model::BaseModel) || sub_mod.app != self
137
- next if sub_mod == m
138
- ms += get_models(sub_mod)
139
- end
140
- elsif (m.is_a?(Module) && !m.is_a?(Class))
141
- return models(m)
142
- end
143
- return ms
144
- end
145
-
146
- def controllers
147
- self.constants.map{ |m| const_get(m) }.select{ |m| m.subclass_of? Spider::Controller }
110
+ # @return [String] description or spec.description or name
111
+ def description
112
+ desc = @description || self.spec.description
113
+ desc.blank? ? self.name : desc
114
+ end
115
+
116
+ # @return [String] The path used to access the application from the browser
117
+ def request_url
118
+ if u = Spider.conf.get("#{@dotted_name}.url")
119
+ return u
120
+ end
121
+ Spider::ControllerMixins::HTTPMixin.reverse_proxy_mapping('/'+@route_url)
122
+ end
123
+ alias :url :request_url
124
+
125
+ # @return [String] The full url used to access the application from the browser
126
+ def http_url(action=nil)
127
+ if u = Spider.conf.get("#{@dotted_name}.http_url")
128
+ if action
129
+ u += '/' if u[-1].chr != '/'
130
+ u += action.to_s
148
131
  end
132
+ return u
133
+ end
134
+ return nil unless Spider.site
135
+ u = "http://#{Spider.site.domain}"
136
+ u += ":#{Spider.site.port}" unless Spider.site.port == 80
137
+ u += url
138
+ u += "/"+action.to_s if action
139
+ u
140
+ end
141
+
142
+ # The full url used to access the application from the browser, prefixed
143
+ # with https
144
+ # @return [String]
145
+ def https_url
146
+ return nil unless Spider.site && Spider.site.ssl?
147
+ u = "https://#{Spider.site.domain}"
148
+ u += ":#{Spider.site.ssl_port}" unless Spider.site.ssl_port == 443
149
+ u += url
150
+ u
151
+ end
152
+
153
+ # @return [String] If the site supports SSL, returns the #https_url; otherwise, the #http_url
154
+ def http_s_url
155
+ return https_url if Spider.site && Spider.site.ssl?
156
+ return http_url
157
+ end
158
+
159
+ # @return [String] The url to the app's public content. If the static_content.mode configuration
160
+ # option is set to 'publish', the app's url inside the home is returned.
161
+ def pub_url
162
+ if Spider.conf.get('static_content.mode') == 'publish'
163
+ Spider::HomeController.pub_url+'/apps/'+self.short_name
164
+ else
165
+ request_url+'/public'
166
+ end
167
+ end
168
+
169
+ # @return [String] The url to the app's public content, inside the app's folder (ignoring publishing mode)
170
+ def pub_url!
171
+ request_url+'/public'
172
+ end
173
+
174
+ # @return [Spider::Controller] The apps' main Controller.
175
+ # If setting the instance variable, use a Symbol
176
+ def controller
177
+ if (!@controller || !const_defined?(@controller))
178
+ @controller = :AppController
179
+ return const_set(@controller, Spider::PageController.clone)
149
180
 
150
- def find_resource(type, name, cur_path=nil)
151
- Spider.find_resource(type, name, cur_path, self)
181
+ end
182
+ return const_get(@controller)
183
+ end
184
+
185
+ # @return [Array] An array of all the {BaseModel} subclasses defined inside the module
186
+ def models(container=nil)
187
+ container ||= self
188
+ mods = []
189
+ container.constants.each do |c|
190
+ begin
191
+ mods += get_models(container.const_get(c))
192
+ rescue LoadError
152
193
  end
194
+ end
195
+ return mods
196
+ end
197
+
198
+ # @return [Array] An array of all the {Controller} subclasses defined inside the module
199
+ def controllers
200
+ self.constants.map{ |m| const_get(m) }.select{ |m| m.subclass_of? Spider::Controller }
201
+ end
202
+
203
+ # Finds a resource (see {Spider.find_resource})
204
+ # @return [Spider::Resource]
205
+ def find_resource(type, name, cur_path=nil)
206
+ Spider.find_resource(type, name, cur_path, self)
207
+ end
153
208
 
154
- def find_resource_path(type, name, cur_path=nil)
155
- res = Spider.find_resource(type, name, cur_path, self)
156
- return res ? res.path : nil
157
- end
158
-
159
-
160
- def register_tag(tag, obj)
161
- @tags ||= {}
162
- @tags[tag] = obj
163
- end
164
-
165
- def get_tag(tag)
166
- @tags[tag]
167
- end
168
-
169
- def has_tag?(tag)
170
- return false unless @tags
171
- @tags[tag] ? true : false
172
- end
173
-
174
- def route(path, dest=nil, options=nil)
175
- self.controller.route(path, dest, options)
176
- end
177
-
178
- def relative_path
179
- if Spider.paths[:apps] && @path.index(Spider.paths[:apps]) == 0
180
- return @path[Spider.paths[:apps].length+1..-1]
181
- else
182
- return @path[$SPIDER_PATHS[:core_apps].length+1..-1]
183
- end
184
- end
185
-
186
- def find_tags
187
- return unless File.directory?(@tags_path)
188
- Dir.new(@tags_path).each do |entry|
189
- next if entry[0].chr == '.'
190
- next unless File.extname(entry) == '.erb'
191
- name = File.basename(entry, '.erb')
192
- klass = Spider::Tag.new_class(File.join(@tags_path, entry))
193
- const_set(Spider::Inflector.camelize(name).to_sym, klass)
194
- #Spider::Logger.debug("REGISTERED TAG #{name}, #{klass}")
195
- register_tag(name, klass)
196
- end
197
- end
209
+ # Finds the path of the resource (see {Spider#find_resource})
210
+ # @return [String]
211
+ def find_resource_path(type, name, cur_path=nil)
212
+ res = Spider.find_resource(type, name, cur_path, self)
213
+ return res ? res.path : nil
214
+ end
215
+
216
+ # Calls route on the app's controller (see {Dispatcher.route}).
217
+ # @return [nil]
218
+ def route(path, dest=nil, options=nil)
219
+ self.controller.route(path, dest, options)
220
+ end
221
+
222
+ # @return [String] The app's path, relative to its container (the home or the Spider lib)
223
+ def relative_path
224
+ if Spider.paths[:apps] && @path.index(Spider.paths[:apps]) == 0
225
+ return @path[Spider.paths[:apps].length+1..-1]
226
+ else
227
+ return @path[$SPIDER_PATHS[:core_apps].length+1..-1]
228
+ end
229
+ end
198
230
 
199
- def app
200
- self
201
- end
202
-
203
- def req(*list)
204
- list.each do |file|
205
- require @path+'/'+file
206
- end
207
- end
208
-
209
- def installed_version_path
210
- File.join(Spider.paths[:var], 'apps', self.name, 'installed_version')
211
- end
212
-
213
- def installed_version
214
- FileUtils.mkpath(File.dirname(installed_version_path))
215
- return unless File.exist?(installed_version_path)
216
- return Gem::Version.new(IO.read(installed_version_path))
217
- end
218
-
219
- def installed_version=(version)
220
- FileUtils.mkpath(File.dirname(installed_version_path))
221
- version = Gem::Version.new(version) unless version.is_a?(Gem::Version)
222
- File.open(installed_version_path, 'w') do |f|
223
- f << version.to_s
224
- end
225
- end
226
-
227
- def load_spec(spec_path=nil)
228
- @spec = AppSpec.load(spec_path)
229
- @spec.app_id = File.basename(spec_path, 'appsec') unless @spec.app_id
230
- @version = @spec.version if @spec.version
231
- end
232
-
233
- def gettext_parsers
234
- @gettext_parsers ||[]
235
- end
236
-
237
-
238
-
231
+
232
+ # Convenience method: since all classes inside the app have an #app method,
233
+ # the App itself has it too
234
+ # @return [self]
235
+ def app
236
+ self
237
+ end
238
+
239
+ # Require files inside the App's path
240
+ # @param [file1,file2...] files to require
241
+ # @return [nil]
242
+ def req(*list)
243
+ list.each do |file|
244
+ require File.join(@path, file)
245
+ end
246
+ end
247
+
248
+
249
+
250
+ # Returns the currently installed version of an app
251
+ # @return [Gem::Version]
252
+ def installed_version
253
+ FileUtils.mkpath(File.dirname(installed_version_path))
254
+ return unless File.exist?(installed_version_path)
255
+ return Gem::Version.new(IO.read(installed_version_path))
256
+ end
257
+
258
+ # Sets the currently installed version of an app
259
+ # @param [String|Gem::Version] version
260
+ # @return [nil]
261
+ def installed_version=(version)
262
+ FileUtils.mkpath(File.dirname(installed_version_path))
263
+ version = Gem::Version.new(version) unless version.is_a?(Gem::Version)
264
+ File.open(installed_version_path, 'w') do |f|
265
+ f << version.to_s
239
266
  end
267
+ end
268
+
269
+ # Loads the app's .spec file
270
+ # @param [String] spec_path
271
+ # @return [AppSpec]
272
+ def load_spec(spec_path=nil)
273
+ @spec = AppSpec.load(spec_path)
274
+ @spec.app_id = File.basename(spec_path, 'appsec') unless @spec.app_id
275
+ @version = @spec.version if @spec.version
276
+ @spec
277
+ end
278
+
279
+ # A list of tettext parsers to use for the app
280
+ # @return [Array]
281
+ def gettext_parsers
282
+ @gettext_parsers || []
283
+ end
240
284
 
285
+ # Register the pointer from a widget tag to the an object
286
+ # @param [String] tag
287
+ # @param [String] object
288
+ # @return [void]
289
+ def register_tag(tag, obj)
290
+ @tags ||= {}
291
+ @tags[tag] = obj
241
292
  end
293
+
294
+ # @param [String] tag
295
+ # @return [Object] The object corresponding to a registered tag
296
+ def get_tag(tag)
297
+ @tags[tag]
298
+ end
299
+
300
+ # @param [String] tag
301
+ # @return [bool] Whether the given tag is registered
302
+ def has_tag?(tag)
303
+ return false unless @tags
304
+ @tags[tag] ? true : false
305
+ end
306
+
307
+
308
+ private
309
+
310
+ # @private
311
+ # Path to the file with the currently installed version
312
+ # @return [String]
313
+ def installed_version_path
314
+ File.join(Spider.paths[:var], 'apps', self.name, 'installed_version')
315
+ end
316
+
317
+ # Looks for erb files in the tags_path
318
+ # @return [nil]
319
+ def find_tags
320
+ return unless File.directory?(@tags_path)
321
+ Dir.new(@tags_path).each do |entry|
322
+ next if entry[0].chr == '.'
323
+ next unless File.extname(entry) == '.erb'
324
+ name = File.basename(entry, '.erb')
325
+ klass = Spider::Tag.new_class(File.join(@tags_path, entry))
326
+ const_set(Spider::Inflector.camelize(name).to_sym, klass)
327
+ #Spider::Logger.debug("REGISTERED TAG #{name}, #{klass}")
328
+ register_tag(name, klass)
329
+ end
330
+ end
331
+
332
+ # Collects models ({BaseModel} subclasses) from inside the module m
333
+ # @param [Module] m
334
+ # @return [Array] the models
335
+ def get_models(m)
336
+ ms = []
337
+ if m.respond_to?(:subclass_of?) && m.subclass_of?(Spider::Model::BaseModel)
338
+ ms << m
339
+ m.constants.each do |c|
340
+ sub_mod = m.const_get(c)
341
+ next unless sub_mod.is_a?(Module)
342
+ next if !sub_mod.subclass_of?(Spider::Model::BaseModel) || sub_mod.app != self
343
+ next if sub_mod == m
344
+ ms += get_models(sub_mod)
345
+ end
346
+ elsif (m.is_a?(Module) && !m.is_a?(Class))
347
+ return models(m)
348
+ end
349
+ return ms
350
+ end
351
+
352
+ end
353
+
354
+ def self.included(mod)
355
+ mod.module_eval do
356
+
357
+ include Spider::DataTypes
358
+
359
+ extend ClassMethods
360
+ end
361
+
242
362
  mod.init()
243
363
  Spider::add_app(mod)
244
364
  end
245
365
 
366
+ # The AppSpec class represents an app's .spec file
367
+ # The AppSpec attributes are:
368
+ #
369
+ # * :app_id [String] sunique identifier for the app
370
+ # * :name [String] descriptive name
371
+ # * :description [String]
372
+ # * :git_repo [String] URL of git repository for the app
373
+ # * :git_repo_rw [String] URL of read/write git repository for the app
374
+ # * :authors [Array]
375
+ # * :depends [Array] Apps this app depends on
376
+ # * :depends_optional [Array] Optional dependencies
377
+ # * :load_after [Array] Apps that must be loaded before this one (if present)
378
+ # * :gems [Array] Gems this app depends on
379
+ # * :gems_optional [Array] Optional gem dependencies
380
+ # * :version [Gem::Version] Current app version
381
+ # * :app_server [String] URL for the app server of this app
382
+ # * :auto_update [TrueClass|FalseClass] true by default; set to false if this version can't be auto-updated
383
+
246
384
  class AppSpec
385
+ # @private
247
386
  @@attributes = []
248
387
 
388
+ # @private
389
+ # Helper method to define an attribute on the AppSpec class
390
+ # @return [nil]
249
391
  def self.attribute(name, options={})
250
392
  @@attributes << name
251
393
  str = <<END_OF_EVAL
@@ -260,8 +402,12 @@ END_OF_EVAL
260
402
  str += "\nalias :#{name}? :#{name}\n"
261
403
  end
262
404
  class_eval(str)
405
+ nil
263
406
  end
264
407
 
408
+ # @private
409
+ # Helper method to define an Array attribute on the AppSpec class
410
+ # @return [nil]
265
411
  def self.array_attribute(name, options={})
266
412
  @@attributes << name
267
413
  str = <<END_OF_EVAL
@@ -272,6 +418,7 @@ END_OF_EVAL
272
418
  end
273
419
  END_OF_EVAL
274
420
  class_eval(str)
421
+ nil
275
422
  end
276
423
 
277
424
  attribute :app_id
@@ -283,48 +430,70 @@ END_OF_EVAL
283
430
  array_attribute :depends
284
431
  array_attribute :depends_optional
285
432
  array_attribute :load_after
286
- array_attribute :can_use
287
433
  array_attribute :gems
288
434
  array_attribute :gems_optional
289
435
  attribute :version
290
436
  attribute :app_server
291
437
  attribute :auto_update, :default => true
292
438
 
439
+ # The git branch used for the app
293
440
  attr_accessor :branch
294
441
 
442
+ # Sets or retrieves the AppSpec id
443
+ # @return [String]
295
444
  def id(val=nil)
296
445
  self.app_id(val)
297
446
  end
298
447
 
448
+ # Sets or retrieves the AppSpec version
449
+ # @param [String] val
450
+ # @return [Gem::Version]
299
451
  def version(val=nil)
300
452
  @version = Gem::Version.new(val) if val
301
453
  @version
302
454
  end
303
455
 
456
+ # Sets or retrieves the first AppSpec author
457
+ # @return [String]
304
458
  def author(val = nil)
305
459
  @authors = [val] if val
306
460
  @authors ||= []
307
461
  @authors[0]
308
462
  end
309
463
 
464
+ # Loads attributes from a .spec file
465
+ # @param [String] spec_path
466
+ # @return [self]
310
467
  def load(spec_path)
311
468
  self.eval(File.read(spec_path), spec_path)
312
469
  self
313
470
  end
314
471
 
472
+ # Returns a new AppSpec instance, loading from a .spec file
473
+ # @param [String] spec_path
474
+ # @return [AppSpec]
315
475
  def self.load(spec_path)
316
476
  self.new.load(spec_path)
317
477
  end
318
478
 
479
+
480
+ # Evals the given code in the AppSpec's context
481
+ # @return [AppSpec]
319
482
  def eval(text, path=nil)
320
483
  self.instance_eval(text)
321
484
  self
322
485
  end
323
486
 
487
+ # Returns a new AppSpec instance, evaluating the given code
488
+ # @param [String] text code to evaluate
489
+ # @param [String] path path to the code
490
+ # @return [AppSpec]
324
491
  def self.eval(text, path=nil)
325
492
  self.new.eval(text, path)
326
493
  end
327
494
 
495
+ # Returns all attributes as an Hash
496
+ # @return [Hash]
328
497
  def to_h
329
498
  h = {}
330
499
  @@attributes.each do |a|
@@ -334,10 +503,16 @@ END_OF_EVAL
334
503
  h
335
504
  end
336
505
 
506
+ # Returns the Hash (as in #to_hash) as JSON
507
+ # @param [Hash] opts JSON options
508
+ # @return [String]
337
509
  def to_json(opts=nil)
338
510
  to_h.to_json
339
511
  end
340
512
 
513
+ # Constructs a new AppSpec instance from an Hash of attributes
514
+ # @param [Hash] h
515
+ # @return [AppSpec]
341
516
  def self.parse_hash(h)
342
517
  spec = self.new
343
518
  h.each do |key, value|
@@ -350,21 +525,28 @@ END_OF_EVAL
350
525
  spec
351
526
  end
352
527
 
528
+ # Returns an array of apps needed at runtime
529
+ # @return [Array]
353
530
  def get_runtime_dependencies
354
531
  return self.load_after unless @load_after.blank?
355
532
  return self.depends + self.depends_optional
356
533
  end
357
534
 
535
+ # Returns an Array of gem names for gems this AppSpec depends on
536
+ # @return [Array]
358
537
  def gems_list
359
538
  self.gems.map{ |g| g.is_a?(Array) ? g.first : g }
360
539
  end
361
540
 
541
+ # Returns an Array of optional gem names
542
+ # @return [Array]
362
543
  def gems_optional_list
363
544
  self.gems_optional.map{ |g| g.is_a?(Array) ? g.first : g }
364
545
  end
365
546
 
366
547
  end
367
548
 
549
+ # Helper class to sort the runtime dependencies of an app using TSort.
368
550
  class RuntimeSort
369
551
 
370
552
  def initialize
@@ -372,6 +554,9 @@ END_OF_EVAL
372
554
  @apps_hash = {}
373
555
  end
374
556
 
557
+
558
+ # Adds an app to be sorted
559
+ # @param [AppSpec|String] app the app to add
375
560
  def add(app)
376
561
  @apps << app
377
562
  if app.is_a?(AppSpec)
@@ -381,15 +566,22 @@ END_OF_EVAL
381
566
  end
382
567
  end
383
568
 
569
+ # Runs block on each dependency
570
+ # @param [Proc] block
384
571
  def tsort_each_node(&block)
385
572
  @apps.each(&block)
386
573
  end
387
574
 
575
+ # Runs the given block for each dependency of node
576
+ # @param [AppSpec] node the app to get dependecies for
577
+ # @param [Proc] block
388
578
  def tsort_each_child(node, &block)
389
579
  return unless node.is_a?(AppSpec)
390
580
  node.get_runtime_dependencies.map{ |a| @apps_hash[a] }.each(&block)
391
581
  end
392
582
 
583
+ # Runs tsort
584
+ # @return [Array] an array of sorted App ids
393
585
  def tsort
394
586
  sorted = super
395
587
  sorted.map{ |a| a.is_a?(AppSpec) ? a.app_id : a }