mjs 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +89 -0
- data/Rakefile +55 -0
- data/TODO +15 -0
- data/app/controllers/application.rb +5 -0
- data/app/controllers/main.rb +7 -0
- data/app/helpers/application_helper.rb +63 -0
- data/app/views/layout/mjs.html.erb +16 -0
- data/app/views/main/index.html.erb +1 -0
- data/lib/mjs/helper.rb +165 -0
- data/lib/mjs/java_script_context.rb +486 -0
- data/lib/mjs/merbtasks.rb +103 -0
- data/lib/mjs/page_object.rb +16 -0
- data/lib/mjs/slicetasks.rb +20 -0
- data/lib/mjs/spectasks.rb +53 -0
- data/lib/mjs/utils.rb +13 -0
- data/lib/mjs.rb +86 -0
- data/public/javascripts/master.js +0 -0
- data/public/stylesheets/master.css +2 -0
- data/spec/mjs_spec.rb +20 -0
- data/spec/requests/main_spec.rb +30 -0
- data/spec/spec_helper.rb +58 -0
- data/stubs/app/controllers/application.rb +2 -0
- data/stubs/app/controllers/main.rb +2 -0
- metadata +89 -0
@@ -0,0 +1,486 @@
|
|
1
|
+
require 'mjs/utils'
|
2
|
+
|
3
|
+
#
|
4
|
+
# This file is almost derived from prototype_helper.rb of RoR
|
5
|
+
#
|
6
|
+
module Mjs
|
7
|
+
class JavaScriptContext #:nodoc:
|
8
|
+
|
9
|
+
######################################################################
|
10
|
+
### define :each method for Rack::Response
|
11
|
+
### because Merb::Rack::StreamWrapper can't create response body correctly
|
12
|
+
|
13
|
+
def each(&callback)
|
14
|
+
callback.call(to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@lines = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
javascript = @lines * $/
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a element reference by finding it through +id+ in the DOM. This element can then be
|
26
|
+
# used for further method calls. Examples:
|
27
|
+
#
|
28
|
+
# page['blank_slate'] # => $('blank_slate');
|
29
|
+
# page['blank_slate'].show # => $('blank_slate').show();
|
30
|
+
# page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
|
31
|
+
#
|
32
|
+
# You can also pass in a record, which will use ActionController::RecordIdentifier.dom_id to lookup
|
33
|
+
# the correct id:
|
34
|
+
#
|
35
|
+
# page[@post] # => $('post_45')
|
36
|
+
# page[Post.new] # => $('new_post')
|
37
|
+
def [](id)
|
38
|
+
case id
|
39
|
+
when Symbol
|
40
|
+
JavaScriptElementProxy.new(self, "##{id}")
|
41
|
+
when String, NilClass
|
42
|
+
JavaScriptElementProxy.new(self, id)
|
43
|
+
else
|
44
|
+
raise NotImplementedError, "[MJS] RecordIdentifier.dom_id(id)"
|
45
|
+
JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
|
50
|
+
# expression as an argument to another JavaScriptGenerator method.
|
51
|
+
def literal(code)
|
52
|
+
raise NotImplementedError, "[MJS] ActiveSupport::JSON::Variable.new(code.to_s)"
|
53
|
+
ActiveSupport::JSON::Variable.new(code.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
|
57
|
+
# used for further method calls. Examples:
|
58
|
+
#
|
59
|
+
# page.select('p') # => $$('p');
|
60
|
+
# page.select('p.welcome b').first # => $$('p.welcome b').first();
|
61
|
+
# page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
|
62
|
+
#
|
63
|
+
# You can also use prototype enumerations with the collection. Observe:
|
64
|
+
#
|
65
|
+
# # Generates: $$('#items li').each(function(value) { value.hide(); });
|
66
|
+
# page.select('#items li').each do |value|
|
67
|
+
# value.hide
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# Though you can call the block param anything you want, they are always rendered in the
|
71
|
+
# javascript as 'value, index.' Other enumerations, like collect() return the last statement:
|
72
|
+
#
|
73
|
+
# # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
|
74
|
+
# page.select('#items li').collect('hidden') do |item|
|
75
|
+
# item.hide
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
def select(pattern)
|
79
|
+
JavaScriptElementCollectionProxy.new(self, pattern)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Inserts HTML at the specified +position+ relative to the DOM element
|
83
|
+
# identified by the given +id+.
|
84
|
+
#
|
85
|
+
# +position+ may be one of:
|
86
|
+
#
|
87
|
+
# <tt>:top</tt>:: HTML is inserted inside the element, before the
|
88
|
+
# element's existing content.
|
89
|
+
# <tt>:bottom</tt>:: HTML is inserted inside the element, after the
|
90
|
+
# element's existing content.
|
91
|
+
# <tt>:before</tt>:: HTML is inserted immediately preceding the element.
|
92
|
+
# <tt>:after</tt>:: HTML is inserted immediately following the element.
|
93
|
+
#
|
94
|
+
# +options_for_render+ may be either a string of HTML to insert, or a hash
|
95
|
+
# of options to be passed to ActionView::Base#render. For example:
|
96
|
+
#
|
97
|
+
# # Insert the rendered 'navigation' partial just before the DOM
|
98
|
+
# # element with ID 'content'.
|
99
|
+
# # Generates: Element.insert("content", { before: "-- Contents of 'navigation' partial --" });
|
100
|
+
# page.insert_html :before, 'content', :partial => 'navigation'
|
101
|
+
#
|
102
|
+
# # Add a list item to the bottom of the <ul> with ID 'list'.
|
103
|
+
# # Generates: Element.insert("list", { bottom: "<li>Last item</li>" });
|
104
|
+
# page.insert_html :bottom, 'list', '<li>Last item</li>'
|
105
|
+
#
|
106
|
+
def insert_html(position, id, *options_for_render)
|
107
|
+
content = javascript_object_for(render(*options_for_render))
|
108
|
+
record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Replaces the inner HTML of the DOM element with the given +id+.
|
112
|
+
#
|
113
|
+
# +options_for_render+ may be either a string of HTML to insert, or a hash
|
114
|
+
# of options to be passed to ActionView::Base#render. For example:
|
115
|
+
#
|
116
|
+
# # Replace the HTML of the DOM element having ID 'person-45' with the
|
117
|
+
# # 'person' partial for the appropriate object.
|
118
|
+
# # Generates: Element.update("person-45", "-- Contents of 'person' partial --");
|
119
|
+
# page.replace_html 'person-45', :partial => 'person', :object => @person
|
120
|
+
#
|
121
|
+
def replace_html(id, *options_for_render)
|
122
|
+
call 'Element.update', id, render(*options_for_render)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Replaces the "outer HTML" (i.e., the entire element, not just its
|
126
|
+
# contents) of the DOM element with the given +id+.
|
127
|
+
#
|
128
|
+
# +options_for_render+ may be either a string of HTML to insert, or a hash
|
129
|
+
# of options to be passed to ActionView::Base#render. For example:
|
130
|
+
#
|
131
|
+
# # Replace the DOM element having ID 'person-45' with the
|
132
|
+
# # 'person' partial for the appropriate object.
|
133
|
+
# page.replace 'person-45', :partial => 'person', :object => @person
|
134
|
+
#
|
135
|
+
# This allows the same partial that is used for the +insert_html+ to
|
136
|
+
# be also used for the input to +replace+ without resorting to
|
137
|
+
# the use of wrapper elements.
|
138
|
+
#
|
139
|
+
# Examples:
|
140
|
+
#
|
141
|
+
# <div id="people">
|
142
|
+
# <%= render :partial => 'person', :collection => @people %>
|
143
|
+
# </div>
|
144
|
+
#
|
145
|
+
# # Insert a new person
|
146
|
+
# #
|
147
|
+
# # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
|
148
|
+
# page.insert_html :bottom, :partial => 'person', :object => @person
|
149
|
+
#
|
150
|
+
# # Replace an existing person
|
151
|
+
#
|
152
|
+
# # Generates: Element.replace("person_45", "-- Contents of partial --");
|
153
|
+
# page.replace 'person_45', :partial => 'person', :object => @person
|
154
|
+
#
|
155
|
+
def replace(id, *options_for_render)
|
156
|
+
call 'Element.replace', id, render(*options_for_render)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Removes the DOM elements with the given +ids+ from the page.
|
160
|
+
#
|
161
|
+
# Example:
|
162
|
+
#
|
163
|
+
# # Remove a few people
|
164
|
+
# # Generates: ["person_23", "person_9", "person_2"].each(Element.remove);
|
165
|
+
# page.remove 'person_23', 'person_9', 'person_2'
|
166
|
+
#
|
167
|
+
def remove(*ids)
|
168
|
+
loop_on_multiple_args 'Element.remove', ids
|
169
|
+
end
|
170
|
+
|
171
|
+
# Shows hidden DOM elements with the given +ids+.
|
172
|
+
#
|
173
|
+
# Example:
|
174
|
+
#
|
175
|
+
# # Show a few people
|
176
|
+
# # Generates: ["person_6", "person_13", "person_223"].each(Element.show);
|
177
|
+
# page.show 'person_6', 'person_13', 'person_223'
|
178
|
+
#
|
179
|
+
def show(*ids)
|
180
|
+
loop_on_multiple_args 'Element.show', ids
|
181
|
+
end
|
182
|
+
|
183
|
+
# Hides the visible DOM elements with the given +ids+.
|
184
|
+
#
|
185
|
+
# Example:
|
186
|
+
#
|
187
|
+
# # Hide a few people
|
188
|
+
# # Generates: ["person_29", "person_9", "person_0"].each(Element.hide);
|
189
|
+
# page.hide 'person_29', 'person_9', 'person_0'
|
190
|
+
#
|
191
|
+
def hide(*ids)
|
192
|
+
loop_on_multiple_args 'Element.hide', ids
|
193
|
+
end
|
194
|
+
|
195
|
+
# Toggles the visibility of the DOM elements with the given +ids+.
|
196
|
+
# Example:
|
197
|
+
#
|
198
|
+
# # Show a few people
|
199
|
+
# # Generates: ["person_14", "person_12", "person_23"].each(Element.toggle);
|
200
|
+
# page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
|
201
|
+
# page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
|
202
|
+
#
|
203
|
+
def toggle(*ids)
|
204
|
+
loop_on_multiple_args 'Element.toggle', ids
|
205
|
+
end
|
206
|
+
|
207
|
+
# Displays an alert dialog with the given +message+.
|
208
|
+
#
|
209
|
+
# Example:
|
210
|
+
#
|
211
|
+
# # Generates: alert('This message is from Rails!')
|
212
|
+
# page.alert('This message is from Rails!')
|
213
|
+
def alert(message)
|
214
|
+
call 'alert', message
|
215
|
+
end
|
216
|
+
|
217
|
+
# Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
|
218
|
+
#
|
219
|
+
# Examples:
|
220
|
+
#
|
221
|
+
# # Generates: window.location.href = "/mycontroller";
|
222
|
+
# page.redirect_to(:action => 'index')
|
223
|
+
#
|
224
|
+
# # Generates: window.location.href = "/account/signup";
|
225
|
+
# page.redirect_to(:controller => 'account', :action => 'signup')
|
226
|
+
def redirect_to(location)
|
227
|
+
url = location.is_a?(String) ? location : @context.url_for(location)
|
228
|
+
record "window.location.href = #{url.inspect}"
|
229
|
+
end
|
230
|
+
|
231
|
+
# Reloads the browser's current +location+ using JavaScript
|
232
|
+
#
|
233
|
+
# Examples:
|
234
|
+
#
|
235
|
+
# # Generates: window.location.reload();
|
236
|
+
# page.reload
|
237
|
+
def reload
|
238
|
+
record 'window.location.reload()'
|
239
|
+
end
|
240
|
+
|
241
|
+
# Calls the JavaScript +function+, optionally with the given +arguments+.
|
242
|
+
#
|
243
|
+
# If a block is given, the block will be passed to a new JavaScriptGenerator;
|
244
|
+
# the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
|
245
|
+
# and passed as the called function's final argument.
|
246
|
+
#
|
247
|
+
# Examples:
|
248
|
+
#
|
249
|
+
# # Generates: Element.replace(my_element, "My content to replace with.")
|
250
|
+
# page.call 'Element.replace', 'my_element', "My content to replace with."
|
251
|
+
#
|
252
|
+
# # Generates: alert('My message!')
|
253
|
+
# page.call 'alert', 'My message!'
|
254
|
+
#
|
255
|
+
# # Generates:
|
256
|
+
# # my_method(function() {
|
257
|
+
# # $("one").show();
|
258
|
+
# # $("two").hide();
|
259
|
+
# # });
|
260
|
+
# page.call(:my_method) do |p|
|
261
|
+
# p[:one].show
|
262
|
+
# p[:two].hide
|
263
|
+
# end
|
264
|
+
def call(function, *arguments, &block)
|
265
|
+
record "#{function}(#{arguments_for_call(arguments, block)})"
|
266
|
+
end
|
267
|
+
|
268
|
+
# Assigns the JavaScript +variable+ the given +value+.
|
269
|
+
#
|
270
|
+
# Examples:
|
271
|
+
#
|
272
|
+
# # Generates: my_string = "This is mine!";
|
273
|
+
# page.assign 'my_string', 'This is mine!'
|
274
|
+
#
|
275
|
+
# # Generates: record_count = 33;
|
276
|
+
# page.assign 'record_count', 33
|
277
|
+
#
|
278
|
+
# # Generates: tabulated_total = 47
|
279
|
+
# page.assign 'tabulated_total', @total_from_cart
|
280
|
+
#
|
281
|
+
def assign(variable, value)
|
282
|
+
record "#{variable} = #{javascript_object_for(value)}"
|
283
|
+
end
|
284
|
+
|
285
|
+
# Writes raw JavaScript to the page.
|
286
|
+
#
|
287
|
+
# Example:
|
288
|
+
#
|
289
|
+
# page << "alert('JavaScript with Prototype.');"
|
290
|
+
def <<(javascript)
|
291
|
+
@lines << javascript
|
292
|
+
end
|
293
|
+
|
294
|
+
# Executes the content of the block after a delay of +seconds+. Example:
|
295
|
+
#
|
296
|
+
# # Generates:
|
297
|
+
# # setTimeout(function() {
|
298
|
+
# # ;
|
299
|
+
# # new Effect.Fade("notice",{});
|
300
|
+
# # }, 20000);
|
301
|
+
# page.delay(20) do
|
302
|
+
# page.visual_effect :fade, 'notice'
|
303
|
+
# end
|
304
|
+
def delay(seconds = 1)
|
305
|
+
record "setTimeout(function() {\n\n"
|
306
|
+
yield
|
307
|
+
record "}, #{(seconds * 1000).to_i})"
|
308
|
+
end
|
309
|
+
|
310
|
+
# Starts a script.aculo.us visual effect. See
|
311
|
+
# ActionView::Helpers::ScriptaculousHelper for more information.
|
312
|
+
def visual_effect(name, id = nil, options = {})
|
313
|
+
record @context.send(:visual_effect, name, id, options)
|
314
|
+
end
|
315
|
+
|
316
|
+
# Creates a script.aculo.us sortable element. Useful
|
317
|
+
# to recreate sortable elements after items get added
|
318
|
+
# or deleted.
|
319
|
+
# See ActionView::Helpers::ScriptaculousHelper for more information.
|
320
|
+
def sortable(id, options = {})
|
321
|
+
record @context.send(:sortable_element_js, id, options)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Creates a script.aculo.us draggable element.
|
325
|
+
# See ActionView::Helpers::ScriptaculousHelper for more information.
|
326
|
+
def draggable(id, options = {})
|
327
|
+
record @context.send(:draggable_element_js, id, options)
|
328
|
+
end
|
329
|
+
|
330
|
+
# Creates a script.aculo.us drop receiving element.
|
331
|
+
# See ActionView::Helpers::ScriptaculousHelper for more information.
|
332
|
+
def drop_receiving(id, options = {})
|
333
|
+
record @context.send(:drop_receiving_element_js, id, options)
|
334
|
+
end
|
335
|
+
|
336
|
+
private
|
337
|
+
def loop_on_multiple_args(method, ids)
|
338
|
+
record(ids.size>1 ?
|
339
|
+
"#{javascript_object_for(ids)}.each(#{method})" :
|
340
|
+
"#{method}(#{ids.first.to_json})")
|
341
|
+
end
|
342
|
+
|
343
|
+
def page
|
344
|
+
self
|
345
|
+
end
|
346
|
+
|
347
|
+
def record(line)
|
348
|
+
returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
|
349
|
+
self << line
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def render(*options_for_render)
|
354
|
+
old_format = @context && @context.template_format
|
355
|
+
@context.template_format = :html if @context
|
356
|
+
Hash === options_for_render.first ?
|
357
|
+
@context.render(*options_for_render) :
|
358
|
+
options_for_render.first.to_s
|
359
|
+
ensure
|
360
|
+
@context.template_format = old_format if @context
|
361
|
+
end
|
362
|
+
|
363
|
+
def javascript_object_for(object)
|
364
|
+
object.respond_to?(:to_json) ? object.to_json : object.inspect
|
365
|
+
|
366
|
+
# TODO: to_json is too buggy!
|
367
|
+
rescue JSON::GeneratorError
|
368
|
+
if object.is_a?(String)
|
369
|
+
object.inspect
|
370
|
+
else
|
371
|
+
raise
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def arguments_for_call(arguments, block = nil)
|
376
|
+
arguments << block_to_function(block) if block
|
377
|
+
arguments.map { |argument| javascript_object_for(argument) }.join ', '
|
378
|
+
end
|
379
|
+
|
380
|
+
def block_to_function(block)
|
381
|
+
generator = self.class.new(@context, &block)
|
382
|
+
literal("function() { #{generator.to_s} }")
|
383
|
+
end
|
384
|
+
|
385
|
+
def method_missing(method, *arguments)
|
386
|
+
JavaScriptProxy.new(self, Mjs::Utils.camelize(method))
|
387
|
+
end
|
388
|
+
end # class JavaScriptGenerator
|
389
|
+
|
390
|
+
# class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc:
|
391
|
+
# [TODO] BlackSlate is not supported yet
|
392
|
+
class JavaScriptProxy
|
393
|
+
|
394
|
+
def initialize(generator, root = nil)
|
395
|
+
@generator = generator
|
396
|
+
@generator << root if root
|
397
|
+
end
|
398
|
+
|
399
|
+
private
|
400
|
+
def method_missing(method, *arguments, &block)
|
401
|
+
if method.to_s =~ /(.*)=$/
|
402
|
+
assign($1, arguments.first)
|
403
|
+
else
|
404
|
+
call("#{Mjs::Utils.camelize(method, :lower)}", *arguments, &block)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def call(function, *arguments, &block)
|
409
|
+
append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
|
410
|
+
self
|
411
|
+
end
|
412
|
+
|
413
|
+
def assign(variable, value)
|
414
|
+
append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
|
415
|
+
end
|
416
|
+
|
417
|
+
def function_chain
|
418
|
+
@function_chain ||= @generator.instance_variable_get(:@lines)
|
419
|
+
end
|
420
|
+
|
421
|
+
def append_to_function_chain!(call)
|
422
|
+
function_chain[-1].chomp!(';')
|
423
|
+
function_chain[-1] += ".#{call};"
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
|
428
|
+
def initialize(generator, id)
|
429
|
+
@id = id
|
430
|
+
super(generator, "$(#{id.to_json})")
|
431
|
+
end
|
432
|
+
|
433
|
+
# Allows access of element attributes through +attribute+. Examples:
|
434
|
+
#
|
435
|
+
# page['foo']['style'] # => $('foo').style;
|
436
|
+
# page['foo']['style']['color'] # => $('blank_slate').style.color;
|
437
|
+
# page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
|
438
|
+
# page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
|
439
|
+
def [](attribute)
|
440
|
+
append_to_function_chain!(attribute)
|
441
|
+
self
|
442
|
+
end
|
443
|
+
|
444
|
+
def []=(variable, value)
|
445
|
+
assign(variable, value)
|
446
|
+
end
|
447
|
+
|
448
|
+
def replace_html(*options_for_render)
|
449
|
+
call 'update', @generator.send(:render, *options_for_render)
|
450
|
+
end
|
451
|
+
|
452
|
+
def replace(*options_for_render)
|
453
|
+
call 'replace', @generator.send(:render, *options_for_render)
|
454
|
+
end
|
455
|
+
|
456
|
+
def reload(options_for_replace = {})
|
457
|
+
replace(options_for_replace.merge({ :partial => @id.to_s }))
|
458
|
+
end
|
459
|
+
|
460
|
+
end
|
461
|
+
|
462
|
+
class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
|
463
|
+
def initialize(generator, variable)
|
464
|
+
@variable = variable
|
465
|
+
@empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
|
466
|
+
super(generator)
|
467
|
+
end
|
468
|
+
|
469
|
+
# The JSON Encoder calls this to check for the +to_json+ method
|
470
|
+
# Since it's a blank slate object, I suppose it responds to anything.
|
471
|
+
def respond_to?(method)
|
472
|
+
true
|
473
|
+
end
|
474
|
+
|
475
|
+
def to_json(options = nil)
|
476
|
+
@variable
|
477
|
+
end
|
478
|
+
|
479
|
+
private
|
480
|
+
def append_to_function_chain!(call)
|
481
|
+
@generator << @variable if @empty
|
482
|
+
@empty = false
|
483
|
+
super
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
namespace :slices do
|
2
|
+
namespace :mjs do
|
3
|
+
|
4
|
+
desc "Install Mjs"
|
5
|
+
task :install => [:preflight, :setup_directories, :copy_assets, :migrate]
|
6
|
+
|
7
|
+
desc "Test for any dependencies"
|
8
|
+
task :preflight do # see slicetasks.rb
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Setup directories"
|
12
|
+
task :setup_directories do
|
13
|
+
puts "Creating directories for host application"
|
14
|
+
Mjs.mirrored_components.each do |type|
|
15
|
+
if File.directory?(Mjs.dir_for(type))
|
16
|
+
if !File.directory?(dst_path = Mjs.app_dir_for(type))
|
17
|
+
relative_path = dst_path.relative_path_from(Merb.root)
|
18
|
+
puts "- creating directory :#{type} #{File.basename(Merb.root) / relative_path}"
|
19
|
+
mkdir_p(dst_path)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# desc "Copy stub files to host application"
|
26
|
+
# task :stubs do
|
27
|
+
# puts "Copying stubs for Mjs - resolves any collisions"
|
28
|
+
# copied, preserved = Mjs.mirror_stubs!
|
29
|
+
# puts "- no files to copy" if copied.empty? && preserved.empty?
|
30
|
+
# copied.each { |f| puts "- copied #{f}" }
|
31
|
+
# preserved.each { |f| puts "! preserved override as #{f}" }
|
32
|
+
# end
|
33
|
+
|
34
|
+
# desc "Copy stub files and views to host application"
|
35
|
+
# task :patch => [ "stubs", "freeze:views" ]
|
36
|
+
|
37
|
+
desc "Copy public assets to host application"
|
38
|
+
task :copy_assets do
|
39
|
+
puts "Copying assets for Mjs - resolves any collisions"
|
40
|
+
copied, preserved = Mjs.mirror_public!
|
41
|
+
puts "- no files to copy" if copied.empty? && preserved.empty?
|
42
|
+
copied.each { |f| puts "- copied #{f}" }
|
43
|
+
preserved.each { |f| puts "! preserved override as #{f}" }
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Migrate the database"
|
47
|
+
task :migrate do # see slicetasks.rb
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Freeze Mjs into your app (only mjs/app)"
|
51
|
+
task :freeze => [ "freeze:app" ]
|
52
|
+
|
53
|
+
namespace :freeze do
|
54
|
+
|
55
|
+
# desc "Freezes Mjs by installing the gem into application/gems"
|
56
|
+
# task :gem do
|
57
|
+
# ENV["GEM"] ||= "mjs"
|
58
|
+
# Rake::Task['slices:install_as_gem'].invoke
|
59
|
+
# end
|
60
|
+
|
61
|
+
desc "Freezes Mjs by copying all files from mjs/app to your application"
|
62
|
+
task :app do
|
63
|
+
puts "Copying all mjs/app files to your application - resolves any collisions"
|
64
|
+
copied, preserved = Mjs.mirror_app!
|
65
|
+
puts "- no files to copy" if copied.empty? && preserved.empty?
|
66
|
+
copied.each { |f| puts "- copied #{f}" }
|
67
|
+
preserved.each { |f| puts "! preserved override as #{f}" }
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "Freeze all views into your application for easy modification"
|
71
|
+
task :views do
|
72
|
+
puts "Copying all view templates to your application - resolves any collisions"
|
73
|
+
copied, preserved = Mjs.mirror_files_for :view
|
74
|
+
puts "- no files to copy" if copied.empty? && preserved.empty?
|
75
|
+
copied.each { |f| puts "- copied #{f}" }
|
76
|
+
preserved.each { |f| puts "! preserved override as #{f}" }
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "Freeze all models into your application for easy modification"
|
80
|
+
task :models do
|
81
|
+
puts "Copying all models to your application - resolves any collisions"
|
82
|
+
copied, preserved = Mjs.mirror_files_for :model
|
83
|
+
puts "- no files to copy" if copied.empty? && preserved.empty?
|
84
|
+
copied.each { |f| puts "- copied #{f}" }
|
85
|
+
preserved.each { |f| puts "! preserved override as #{f}" }
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "Freezes Mjs as a gem and copies over mjs/app"
|
89
|
+
task :app_with_gem => [:gem, :app]
|
90
|
+
|
91
|
+
desc "Freezes Mjs by unpacking all files into your application"
|
92
|
+
task :unpack do
|
93
|
+
puts "Unpacking Mjs files to your application - resolves any collisions"
|
94
|
+
copied, preserved = Mjs.unpack_slice!
|
95
|
+
puts "- no files to copy" if copied.empty? && preserved.empty?
|
96
|
+
copied.each { |f| puts "- copied #{f}" }
|
97
|
+
preserved.each { |f| puts "! preserved override as #{f}" }
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
namespace :slices do
|
2
|
+
namespace :mjs do
|
3
|
+
|
4
|
+
# add your own mjs tasks here
|
5
|
+
|
6
|
+
# # Uncomment the following lines and edit the pre defined tasks
|
7
|
+
#
|
8
|
+
# # implement this to test for structural/code dependencies
|
9
|
+
# # like certain directories or availability of other files
|
10
|
+
# desc "Test for any dependencies"
|
11
|
+
# task :preflight do
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # implement this to perform any database related setup steps
|
15
|
+
# desc "Migrate the database"
|
16
|
+
# task :migrate do
|
17
|
+
# end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
namespace :slices do
|
2
|
+
namespace :mjs do
|
3
|
+
|
4
|
+
desc "Run slice specs within the host application context"
|
5
|
+
task :spec => [ "spec:explain", "spec:default" ]
|
6
|
+
|
7
|
+
namespace :spec do
|
8
|
+
|
9
|
+
slice_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
10
|
+
|
11
|
+
task :explain do
|
12
|
+
puts "\nNote: By running Mjs specs inside the application context any\n" +
|
13
|
+
"overrides could break existing specs. This isn't always a problem,\n" +
|
14
|
+
"especially in the case of views. Use these spec tasks to check how\n" +
|
15
|
+
"well your application conforms to the original slice implementation."
|
16
|
+
end
|
17
|
+
|
18
|
+
Spec::Rake::SpecTask.new('default') do |t|
|
19
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
20
|
+
t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Run all model specs, run a spec for a specific Model with MODEL=MyModel"
|
24
|
+
Spec::Rake::SpecTask.new('model') do |t|
|
25
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
26
|
+
if(ENV['MODEL'])
|
27
|
+
t.spec_files = Dir["#{slice_root}/spec/models/**/#{ENV['MODEL']}_spec.rb"].sort
|
28
|
+
else
|
29
|
+
t.spec_files = Dir["#{slice_root}/spec/models/**/*_spec.rb"].sort
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Run all request specs, run a spec for a specific request with REQUEST=MyRequest"
|
34
|
+
Spec::Rake::SpecTask.new('request') do |t|
|
35
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
36
|
+
if(ENV['REQUEST'])
|
37
|
+
t.spec_files = Dir["#{slice_root}/spec/requests/**/#{ENV['REQUEST']}_spec.rb"].sort
|
38
|
+
else
|
39
|
+
t.spec_files = Dir["#{slice_root}/spec/requests/**/*_spec.rb"].sort
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Run all specs and output the result in html"
|
44
|
+
Spec::Rake::SpecTask.new('html') do |t|
|
45
|
+
t.spec_opts = ["--format", "html"]
|
46
|
+
t.libs = ['lib', 'server/lib' ]
|
47
|
+
t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
data/lib/mjs/utils.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Mjs
|
2
|
+
module Utils
|
3
|
+
def self.camelize(lower_case_and_underscored_word, first_letter = :upper)
|
4
|
+
lower_case_and_underscored_word = lower_case_and_underscored_word.to_s
|
5
|
+
first_letter_in_uppercase = (first_letter == :upper)
|
6
|
+
if first_letter_in_uppercase
|
7
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
8
|
+
else
|
9
|
+
lower_case_and_underscored_word[0,1].downcase + camelize(lower_case_and_underscored_word)[1..-1]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|