spinebox 0.0.1
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.
- data/.gitignore +17 -0
- data/Gemfile +11 -0
- data/LICENSE +22 -0
- data/README.textile +26 -0
- data/Rakefile +2 -0
- data/bin/spinebox +32 -0
- data/lib/spinebox/base.rb +10 -0
- data/lib/spinebox/command.rb +53 -0
- data/lib/spinebox/compiler.rb +10 -0
- data/lib/spinebox/config.rb +22 -0
- data/lib/spinebox/generator.rb +24 -0
- data/lib/spinebox/routes.rb +41 -0
- data/lib/spinebox/templates/Gemfile +3 -0
- data/lib/spinebox/templates/Rakefile +0 -0
- data/lib/spinebox/templates/app/assets/images/.gitignore +0 -0
- data/lib/spinebox/templates/app/assets/javascripts/app/controllers/.gitignore +0 -0
- data/lib/spinebox/templates/app/assets/javascripts/app/index.js.coffee +6 -0
- data/lib/spinebox/templates/app/assets/javascripts/app/models/.gitignore +0 -0
- data/lib/spinebox/templates/app/assets/javascripts/app/views/.gitignore +0 -0
- data/lib/spinebox/templates/app/assets/javascripts/application.js +14 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/jquery.js +4 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/json2.js +485 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/ajax.coffee +208 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/list.coffee +43 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/local.coffee +16 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/manager.coffee +83 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/relation.coffee +144 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/route.coffee +145 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/spine.coffee +537 -0
- data/lib/spinebox/templates/app/assets/stylesheets/application.css.scss +13 -0
- data/lib/spinebox/templates/config/config.rb +7 -0
- data/lib/spinebox/templates/config/routes.rb +13 -0
- data/lib/spinebox/templates/config.ru +3 -0
- data/lib/spinebox/templates/public/index.html +14 -0
- data/lib/spinebox/version.rb +3 -0
- data/lib/spinebox.rb +19 -0
- data/spec/base_spec.rb +9 -0
- data/spec/command_spec.rb +23 -0
- data/spec/config_spec.rb +29 -0
- data/spec/generator_spec.rb +25 -0
- data/spec/helpers.rb +9 -0
- data/spec/routes_spec.rb +14 -0
- data/spinebox.gemspec +17 -0
- metadata +97 -0
@@ -0,0 +1,537 @@
|
|
1
|
+
Events =
|
2
|
+
bind: (ev, callback) ->
|
3
|
+
evs = ev.split(' ')
|
4
|
+
calls = @hasOwnProperty('_callbacks') and @_callbacks or= {}
|
5
|
+
|
6
|
+
for name in evs
|
7
|
+
calls[name] or= []
|
8
|
+
calls[name].push(callback)
|
9
|
+
this
|
10
|
+
|
11
|
+
one: (ev, callback) ->
|
12
|
+
@bind ev, ->
|
13
|
+
@unbind(ev, arguments.callee)
|
14
|
+
callback.apply(@, arguments)
|
15
|
+
|
16
|
+
trigger: (args...) ->
|
17
|
+
ev = args.shift()
|
18
|
+
|
19
|
+
list = @hasOwnProperty('_callbacks') and @_callbacks?[ev]
|
20
|
+
return unless list
|
21
|
+
|
22
|
+
for callback in list
|
23
|
+
if callback.apply(@, args) is false
|
24
|
+
break
|
25
|
+
true
|
26
|
+
|
27
|
+
unbind: (ev, callback) ->
|
28
|
+
unless ev
|
29
|
+
@_callbacks = {}
|
30
|
+
return this
|
31
|
+
|
32
|
+
list = @_callbacks?[ev]
|
33
|
+
return this unless list
|
34
|
+
|
35
|
+
unless callback
|
36
|
+
delete @_callbacks[ev]
|
37
|
+
return this
|
38
|
+
|
39
|
+
for cb, i in list when cb is callback
|
40
|
+
list = list.slice()
|
41
|
+
list.splice(i, 1)
|
42
|
+
@_callbacks[ev] = list
|
43
|
+
break
|
44
|
+
this
|
45
|
+
|
46
|
+
Log =
|
47
|
+
trace: true
|
48
|
+
|
49
|
+
logPrefix: '(App)'
|
50
|
+
|
51
|
+
log: (args...) ->
|
52
|
+
return unless @trace
|
53
|
+
if @logPrefix then args.unshift(@logPrefix)
|
54
|
+
console?.log?(args...)
|
55
|
+
this
|
56
|
+
|
57
|
+
moduleKeywords = ['included', 'extended']
|
58
|
+
|
59
|
+
class Module
|
60
|
+
@include: (obj) ->
|
61
|
+
throw('include(obj) requires obj') unless obj
|
62
|
+
for key, value of obj when key not in moduleKeywords
|
63
|
+
@::[key] = value
|
64
|
+
obj.included?.apply(@)
|
65
|
+
this
|
66
|
+
|
67
|
+
@extend: (obj) ->
|
68
|
+
throw('extend(obj) requires obj') unless obj
|
69
|
+
for key, value of obj when key not in moduleKeywords
|
70
|
+
@[key] = value
|
71
|
+
obj.extended?.apply(@)
|
72
|
+
this
|
73
|
+
|
74
|
+
@proxy: (func) ->
|
75
|
+
=> func.apply(@, arguments)
|
76
|
+
|
77
|
+
proxy: (func) ->
|
78
|
+
=> func.apply(@, arguments)
|
79
|
+
|
80
|
+
constructor: ->
|
81
|
+
@init?(arguments...)
|
82
|
+
|
83
|
+
class Model extends Module
|
84
|
+
@extend Events
|
85
|
+
|
86
|
+
@records: {}
|
87
|
+
@crecords: {}
|
88
|
+
@attributes: []
|
89
|
+
|
90
|
+
@configure: (name, attributes...) ->
|
91
|
+
@className = name
|
92
|
+
@records = {}
|
93
|
+
@crecords = {}
|
94
|
+
@attributes = attributes if attributes.length
|
95
|
+
@attributes and= makeArray(@attributes)
|
96
|
+
@attributes or= []
|
97
|
+
@unbind()
|
98
|
+
this
|
99
|
+
|
100
|
+
@toString: -> "#{@className}(#{@attributes.join(", ")})"
|
101
|
+
|
102
|
+
@find: (id) ->
|
103
|
+
record = @records[id]
|
104
|
+
if !record and ("#{id}").match(/c-\d+/)
|
105
|
+
return @findCID(id)
|
106
|
+
throw('Unknown record') unless record
|
107
|
+
record.clone()
|
108
|
+
|
109
|
+
@findCID: (cid) ->
|
110
|
+
record = @crecords[cid]
|
111
|
+
throw('Unknown record') unless record
|
112
|
+
record.clone()
|
113
|
+
|
114
|
+
@exists: (id) ->
|
115
|
+
try
|
116
|
+
return @find(id)
|
117
|
+
catch e
|
118
|
+
return false
|
119
|
+
|
120
|
+
@refresh: (values, options = {}) ->
|
121
|
+
if options.clear
|
122
|
+
@records = {}
|
123
|
+
@crecords = {}
|
124
|
+
|
125
|
+
records = @fromJSON(values)
|
126
|
+
records = [records] unless isArray(records)
|
127
|
+
|
128
|
+
for record in records
|
129
|
+
record.id or= record.cid
|
130
|
+
@records[record.id] = record
|
131
|
+
@crecords[record.cid] = record
|
132
|
+
|
133
|
+
@resetIdCounter()
|
134
|
+
|
135
|
+
@trigger('refresh', @cloneArray(records))
|
136
|
+
this
|
137
|
+
|
138
|
+
@select: (callback) ->
|
139
|
+
result = (record for id, record of @records when callback(record))
|
140
|
+
@cloneArray(result)
|
141
|
+
|
142
|
+
@findByAttribute: (name, value) ->
|
143
|
+
for id, record of @records
|
144
|
+
if record[name] is value
|
145
|
+
return record.clone()
|
146
|
+
null
|
147
|
+
|
148
|
+
@findAllByAttribute: (name, value) ->
|
149
|
+
@select (item) ->
|
150
|
+
item[name] is value
|
151
|
+
|
152
|
+
@each: (callback) ->
|
153
|
+
for key, value of @records
|
154
|
+
callback(value.clone())
|
155
|
+
|
156
|
+
@all: ->
|
157
|
+
@cloneArray(@recordsValues())
|
158
|
+
|
159
|
+
@first: ->
|
160
|
+
record = @recordsValues()[0]
|
161
|
+
record?.clone()
|
162
|
+
|
163
|
+
@last: ->
|
164
|
+
values = @recordsValues()
|
165
|
+
record = values[values.length - 1]
|
166
|
+
record?.clone()
|
167
|
+
|
168
|
+
@count: ->
|
169
|
+
@recordsValues().length
|
170
|
+
|
171
|
+
@deleteAll: ->
|
172
|
+
for key, value of @records
|
173
|
+
delete @records[key]
|
174
|
+
|
175
|
+
@destroyAll: ->
|
176
|
+
for key, value of @records
|
177
|
+
@records[key].destroy()
|
178
|
+
|
179
|
+
@update: (id, atts, options) ->
|
180
|
+
@find(id).updateAttributes(atts, options)
|
181
|
+
|
182
|
+
@create: (atts, options) ->
|
183
|
+
record = new @(atts)
|
184
|
+
record.save(options)
|
185
|
+
|
186
|
+
@destroy: (id, options) ->
|
187
|
+
@find(id).destroy(options)
|
188
|
+
|
189
|
+
@change: (callbackOrParams) ->
|
190
|
+
if typeof callbackOrParams is 'function'
|
191
|
+
@bind('change', callbackOrParams)
|
192
|
+
else
|
193
|
+
@trigger('change', callbackOrParams)
|
194
|
+
|
195
|
+
@fetch: (callbackOrParams) ->
|
196
|
+
if typeof callbackOrParams is 'function'
|
197
|
+
@bind('fetch', callbackOrParams)
|
198
|
+
else
|
199
|
+
@trigger('fetch', callbackOrParams)
|
200
|
+
|
201
|
+
@toJSON: ->
|
202
|
+
@recordsValues()
|
203
|
+
|
204
|
+
@fromJSON: (objects) ->
|
205
|
+
return unless objects
|
206
|
+
if typeof objects is 'string'
|
207
|
+
objects = JSON.parse(objects)
|
208
|
+
if isArray(objects)
|
209
|
+
(new @(value) for value in objects)
|
210
|
+
else
|
211
|
+
new @(objects)
|
212
|
+
|
213
|
+
@fromForm: ->
|
214
|
+
(new this).fromForm(arguments...)
|
215
|
+
|
216
|
+
# Private
|
217
|
+
|
218
|
+
@recordsValues: ->
|
219
|
+
result = []
|
220
|
+
for key, value of @records
|
221
|
+
result.push(value)
|
222
|
+
result
|
223
|
+
|
224
|
+
@cloneArray: (array) ->
|
225
|
+
(value.clone() for value in array)
|
226
|
+
|
227
|
+
@idCounter: 0
|
228
|
+
|
229
|
+
@resetIdCounter: ->
|
230
|
+
ids = (model.id for model in @all()).sort((a, b) -> a > b)
|
231
|
+
lastID = ids[ids.length - 1]
|
232
|
+
lastID = lastID?.replace?(/^c-/, '') or lastID
|
233
|
+
lastID = parseInt(lastID, 10)
|
234
|
+
@idCounter = (lastID + 1) or 0
|
235
|
+
|
236
|
+
@uid: (prefix = '') ->
|
237
|
+
prefix + @idCounter++
|
238
|
+
|
239
|
+
# Instance
|
240
|
+
|
241
|
+
constructor: (atts) ->
|
242
|
+
super
|
243
|
+
@load atts if atts
|
244
|
+
@cid or= @constructor.uid('c-')
|
245
|
+
|
246
|
+
isNew: ->
|
247
|
+
not @exists()
|
248
|
+
|
249
|
+
isValid: ->
|
250
|
+
not @validate()
|
251
|
+
|
252
|
+
validate: ->
|
253
|
+
|
254
|
+
load: (atts) ->
|
255
|
+
for key, value of atts
|
256
|
+
if typeof @[key] is 'function'
|
257
|
+
@[key](value)
|
258
|
+
else
|
259
|
+
@[key] = value
|
260
|
+
this
|
261
|
+
|
262
|
+
attributes: ->
|
263
|
+
result = {}
|
264
|
+
for key in @constructor.attributes when key of this
|
265
|
+
if typeof @[key] is 'function'
|
266
|
+
result[key] = @[key]()
|
267
|
+
else
|
268
|
+
result[key] = @[key]
|
269
|
+
result.id = @id if @id
|
270
|
+
result
|
271
|
+
|
272
|
+
eql: (rec) ->
|
273
|
+
!!(rec and rec.constructor is @constructor and
|
274
|
+
(rec.cid is @cid) or (rec.id and rec.id is @id))
|
275
|
+
|
276
|
+
save: (options = {}) ->
|
277
|
+
unless options.validate is false
|
278
|
+
error = @validate()
|
279
|
+
if error
|
280
|
+
@trigger('error', error)
|
281
|
+
return false
|
282
|
+
|
283
|
+
@trigger('beforeSave', options)
|
284
|
+
record = if @isNew() then @create(options) else @update(options)
|
285
|
+
@trigger('save', options)
|
286
|
+
record
|
287
|
+
|
288
|
+
updateAttribute: (name, value, options) ->
|
289
|
+
@[name] = value
|
290
|
+
@save(options)
|
291
|
+
|
292
|
+
updateAttributes: (atts, options) ->
|
293
|
+
@load(atts)
|
294
|
+
@save(options)
|
295
|
+
|
296
|
+
changeID: (id) ->
|
297
|
+
records = @constructor.records
|
298
|
+
records[id] = records[@id]
|
299
|
+
delete records[@id]
|
300
|
+
@id = id
|
301
|
+
@save()
|
302
|
+
|
303
|
+
destroy: (options = {}) ->
|
304
|
+
@trigger('beforeDestroy', options)
|
305
|
+
delete @constructor.records[@id]
|
306
|
+
delete @constructor.crecords[@cid]
|
307
|
+
@destroyed = true
|
308
|
+
@trigger('destroy', options)
|
309
|
+
@trigger('change', 'destroy', options)
|
310
|
+
@unbind()
|
311
|
+
this
|
312
|
+
|
313
|
+
dup: (newRecord) ->
|
314
|
+
result = new @constructor(@attributes())
|
315
|
+
if newRecord is false
|
316
|
+
result.cid = @cid
|
317
|
+
else
|
318
|
+
delete result.id
|
319
|
+
result
|
320
|
+
|
321
|
+
clone: ->
|
322
|
+
createObject(@)
|
323
|
+
|
324
|
+
reload: ->
|
325
|
+
return this if @isNew()
|
326
|
+
original = @constructor.find(@id)
|
327
|
+
@load(original.attributes())
|
328
|
+
original
|
329
|
+
|
330
|
+
toJSON: ->
|
331
|
+
@attributes()
|
332
|
+
|
333
|
+
toString: ->
|
334
|
+
"<#{@constructor.className} (#{JSON.stringify(@)})>"
|
335
|
+
|
336
|
+
fromForm: (form) ->
|
337
|
+
result = {}
|
338
|
+
for key in $(form).serializeArray()
|
339
|
+
result[key.name] = key.value
|
340
|
+
@load(result)
|
341
|
+
|
342
|
+
exists: ->
|
343
|
+
@id && @id of @constructor.records
|
344
|
+
|
345
|
+
# Private
|
346
|
+
|
347
|
+
update: (options) ->
|
348
|
+
@trigger('beforeUpdate', options)
|
349
|
+
records = @constructor.records
|
350
|
+
records[@id].load @attributes()
|
351
|
+
clone = records[@id].clone()
|
352
|
+
clone.trigger('update', options)
|
353
|
+
clone.trigger('change', 'update', options)
|
354
|
+
clone
|
355
|
+
|
356
|
+
create: (options) ->
|
357
|
+
@trigger('beforeCreate', options)
|
358
|
+
@id = @cid unless @id
|
359
|
+
|
360
|
+
record = @dup(false)
|
361
|
+
@constructor.records[@id] = record
|
362
|
+
@constructor.crecords[@cid] = record
|
363
|
+
|
364
|
+
clone = record.clone()
|
365
|
+
clone.trigger('create', options)
|
366
|
+
clone.trigger('change', 'create', options)
|
367
|
+
clone
|
368
|
+
|
369
|
+
bind: (events, callback) ->
|
370
|
+
@constructor.bind events, binder = (record) =>
|
371
|
+
if record && @eql(record)
|
372
|
+
callback.apply(@, arguments)
|
373
|
+
@constructor.bind 'unbind', unbinder = (record) =>
|
374
|
+
if record && @eql(record)
|
375
|
+
@constructor.unbind(events, binder)
|
376
|
+
@constructor.unbind('unbind', unbinder)
|
377
|
+
binder
|
378
|
+
|
379
|
+
one: (events, callback) ->
|
380
|
+
binder = @bind events, =>
|
381
|
+
@constructor.unbind(events, binder)
|
382
|
+
callback.apply(@, arguments)
|
383
|
+
|
384
|
+
trigger: (args...) ->
|
385
|
+
args.splice(1, 0, @)
|
386
|
+
@constructor.trigger(args...)
|
387
|
+
|
388
|
+
unbind: ->
|
389
|
+
@trigger('unbind')
|
390
|
+
|
391
|
+
class Controller extends Module
|
392
|
+
@include Events
|
393
|
+
@include Log
|
394
|
+
|
395
|
+
eventSplitter: /^(\S+)\s*(.*)$/
|
396
|
+
tag: 'div'
|
397
|
+
|
398
|
+
constructor: (options) ->
|
399
|
+
@options = options
|
400
|
+
|
401
|
+
for key, value of @options
|
402
|
+
@[key] = value
|
403
|
+
|
404
|
+
@el = document.createElement(@tag) unless @el
|
405
|
+
@el = $(@el)
|
406
|
+
|
407
|
+
@el.addClass(@className) if @className
|
408
|
+
@el.attr(@attributes) if @attributes
|
409
|
+
|
410
|
+
@events = @constructor.events unless @events
|
411
|
+
@elements = @constructor.elements unless @elements
|
412
|
+
|
413
|
+
@delegateEvents(@events) if @events
|
414
|
+
@refreshElements() if @elements
|
415
|
+
|
416
|
+
super
|
417
|
+
|
418
|
+
release: =>
|
419
|
+
@el.remove()
|
420
|
+
@unbind()
|
421
|
+
@trigger 'release'
|
422
|
+
|
423
|
+
$: (selector) -> $(selector, @el)
|
424
|
+
|
425
|
+
delegateEvents: (events) ->
|
426
|
+
for key, method of events
|
427
|
+
|
428
|
+
unless typeof(method) is 'function'
|
429
|
+
# Always return true from event handlers
|
430
|
+
method = do (method) => =>
|
431
|
+
@[method].apply(this, arguments)
|
432
|
+
true
|
433
|
+
|
434
|
+
match = key.match(@eventSplitter)
|
435
|
+
eventName = match[1]
|
436
|
+
selector = match[2]
|
437
|
+
|
438
|
+
if selector is ''
|
439
|
+
@el.bind(eventName, method)
|
440
|
+
else
|
441
|
+
@el.delegate(selector, eventName, method)
|
442
|
+
|
443
|
+
refreshElements: ->
|
444
|
+
for key, value of @elements
|
445
|
+
@[value] = @$(key)
|
446
|
+
|
447
|
+
delay: (func, timeout) ->
|
448
|
+
setTimeout(@proxy(func), timeout || 0)
|
449
|
+
|
450
|
+
html: (element) ->
|
451
|
+
@el.html(element.el or element)
|
452
|
+
@refreshElements()
|
453
|
+
@el
|
454
|
+
|
455
|
+
append: (elements...) ->
|
456
|
+
elements = (e.el or e for e in elements)
|
457
|
+
@el.append(elements...)
|
458
|
+
@refreshElements()
|
459
|
+
@el
|
460
|
+
|
461
|
+
appendTo: (element) ->
|
462
|
+
@el.appendTo(element.el or element)
|
463
|
+
@refreshElements()
|
464
|
+
@el
|
465
|
+
|
466
|
+
prepend: (elements...) ->
|
467
|
+
elements = (e.el or e for e in elements)
|
468
|
+
@el.prepend(elements...)
|
469
|
+
@refreshElements()
|
470
|
+
@el
|
471
|
+
|
472
|
+
replace: (element) ->
|
473
|
+
[previous, @el] = [@el, $(element.el or element)]
|
474
|
+
previous.replaceWith(@el)
|
475
|
+
@delegateEvents(@events)
|
476
|
+
@refreshElements()
|
477
|
+
@el
|
478
|
+
|
479
|
+
# Utilities & Shims
|
480
|
+
|
481
|
+
$ = window?.jQuery or window?.Zepto or (element) -> element
|
482
|
+
|
483
|
+
createObject = Object.create or (o) ->
|
484
|
+
Func = ->
|
485
|
+
Func.prototype = o
|
486
|
+
new Func()
|
487
|
+
|
488
|
+
isArray = (value) ->
|
489
|
+
Object::toString.call(value) is '[object Array]'
|
490
|
+
|
491
|
+
isBlank = (value) ->
|
492
|
+
return true unless value
|
493
|
+
return false for key of value
|
494
|
+
true
|
495
|
+
|
496
|
+
makeArray = (args) ->
|
497
|
+
Array::slice.call(args, 0)
|
498
|
+
|
499
|
+
# Globals
|
500
|
+
|
501
|
+
Spine = @Spine = {}
|
502
|
+
module?.exports = Spine
|
503
|
+
|
504
|
+
Spine.version = '1.0.6'
|
505
|
+
Spine.isArray = isArray
|
506
|
+
Spine.isBlank = isBlank
|
507
|
+
Spine.$ = $
|
508
|
+
Spine.Events = Events
|
509
|
+
Spine.Log = Log
|
510
|
+
Spine.Module = Module
|
511
|
+
Spine.Controller = Controller
|
512
|
+
Spine.Model = Model
|
513
|
+
|
514
|
+
# Global events
|
515
|
+
|
516
|
+
Module.extend.call(Spine, Events)
|
517
|
+
|
518
|
+
# JavaScript compatability
|
519
|
+
|
520
|
+
Module.create = Module.sub =
|
521
|
+
Controller.create = Controller.sub =
|
522
|
+
Model.sub = (instances, statics) ->
|
523
|
+
class result extends this
|
524
|
+
result.include(instances) if instances
|
525
|
+
result.extend(statics) if statics
|
526
|
+
result.unbind?()
|
527
|
+
result
|
528
|
+
|
529
|
+
Model.setup = (name, attributes = []) ->
|
530
|
+
class Instance extends this
|
531
|
+
Instance.configure(name, attributes...)
|
532
|
+
Instance
|
533
|
+
|
534
|
+
Module.init = Controller.init = Model.init = (a1, a2, a3, a4, a5) ->
|
535
|
+
new this(a1, a2, a3, a4, a5)
|
536
|
+
|
537
|
+
Spine.Class = Module
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Spinebox</title>
|
5
|
+
<link href="/assets/application.css" media="all" rel="stylesheet" type="text/css">
|
6
|
+
<script src="/assets/application.js" type="text/javascript"></script>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
|
10
|
+
<h1>Spinebox</h1>
|
11
|
+
<p>Is working!</p>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/lib/spinebox.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "sprockets"
|
2
|
+
require "rack"
|
3
|
+
require 'coffee_script'
|
4
|
+
require 'eco'
|
5
|
+
require 'sass'
|
6
|
+
require "ostruct"
|
7
|
+
|
8
|
+
require "spinebox/version"
|
9
|
+
require "spinebox/base"
|
10
|
+
require "spinebox/command"
|
11
|
+
require "spinebox/config"
|
12
|
+
require "spinebox/routes"
|
13
|
+
require "spinebox/generator"
|
14
|
+
|
15
|
+
module Spinebox
|
16
|
+
class << self
|
17
|
+
include Spinebox::Base
|
18
|
+
end
|
19
|
+
end
|
data/spec/base_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative "./helpers"
|
2
|
+
|
3
|
+
describe Spinebox::Command do
|
4
|
+
|
5
|
+
it "should store commands" do
|
6
|
+
Spinebox::Command.dispatch do
|
7
|
+
on "--test, -t", "A test case", :if => proc { true } do
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
on "--test2, -t2", "A test case 2", :if => proc { true } do
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Spinebox::Command.commands.should have(2).command
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should execute commands" do
|
20
|
+
Spinebox::Command.execute
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative "./helpers"
|
2
|
+
|
3
|
+
describe Spinebox do
|
4
|
+
|
5
|
+
it "should offer a block style config method" do
|
6
|
+
Spinebox.config do |config|
|
7
|
+
config.should be_a OpenStruct
|
8
|
+
config.assets.should be_a Sprockets::Environment
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should offer a normal configuration" do
|
13
|
+
Spinebox.config.should be_a OpenStruct
|
14
|
+
Spinebox.config.assets.should be_a Sprockets::Environment
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should offer straight access to the assets" do
|
18
|
+
Spinebox.assets.should be_a Sprockets::Environment
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should load the config from the default path" do
|
22
|
+
Dir.chdir "#{Spinebox.root}/templates"
|
23
|
+
|
24
|
+
Spinebox.config.assets.paths.should have(0).paths
|
25
|
+
Spinebox.load_config!
|
26
|
+
Spinebox.config.assets.paths.should have_at_least(2).paths
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "./helpers"
|
2
|
+
|
3
|
+
describe Spinebox::Generator do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
Dir.chdir(File.dirname(__FILE__))
|
7
|
+
FileUtils.rm_rf("tmp")
|
8
|
+
FileUtils.mkdir("tmp")
|
9
|
+
Dir.chdir "tmp"
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:each) do
|
13
|
+
Dir.chdir ".."
|
14
|
+
FileUtils.rm_rf("tmp")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should generate app with name" do
|
18
|
+
File.directory?("box").should_not be_true
|
19
|
+
|
20
|
+
generator = Spinebox::Generator.new "box"
|
21
|
+
File.directory?(generator.root).should be_true
|
22
|
+
Dir["#{generator.root}/**/*"].should have_at_least(5).files
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|