spiderfw 0.6.23 → 0.6.24

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