seaweed 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/seaweed ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env jruby
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ require 'celerity'
5
+ require 'watchr'
6
+
7
+ require File.expand_path('../../lib/required_files', __FILE__)
8
+ require File.expand_path('../../server/server', __FILE__)
9
+ require 'net/http'
10
+
11
+ PORT = 4567
12
+
13
+ app = Rack::Builder.app do
14
+ run Sinatra::Application
15
+ end
16
+
17
+ mode = case ARGV.first
18
+ when 's', 'server'
19
+ 'server'
20
+ when 't', 'terminal'
21
+ 'terminal'
22
+ when 'a', 'auto'
23
+ 'auto'
24
+ else
25
+ 'auto'
26
+ end
27
+
28
+ if mode == 'server'
29
+ Rack::Handler.default.run app, :Port => PORT
30
+ else
31
+ Thread.new do
32
+ Rack::Handler.default.run app, :Port => PORT
33
+ end
34
+
35
+ begin
36
+ page = Net::HTTP.get URI.parse("http://localhost:#{PORT}/")
37
+ rescue Errno::ECONNREFUSED
38
+ sleep 1
39
+ retry
40
+ end
41
+
42
+ browser = Celerity::Browser.new
43
+ browser.goto "http://localhost:#{PORT}/#terminal"
44
+ puts browser.text
45
+
46
+ if mode == 'auto'
47
+ script = Watchr::Script.new
48
+ script.watch(/^(lib|spec)\/.*\.coffee$/) do
49
+ browser.refresh
50
+ puts browser.text
51
+ end
52
+ controller = Watchr::Controller.new(script, Watchr.handler.new)
53
+ controller.run
54
+ end
55
+ end
data/lib/Spec.coffee ADDED
@@ -0,0 +1,308 @@
1
+ # Seaweed Coffeescript spec framework
2
+ window.Spec = {
3
+ EnvironmentInitialized: false
4
+
5
+ # Adds   indentation to a string
6
+ Pad: (string, times) ->
7
+ for i in [1..times]
8
+ string = ' ' + string
9
+ string
10
+
11
+ # Escapes text for HTML
12
+ Escape: (string) ->
13
+ $('<div/>').text(String(string)).html()
14
+
15
+ # Executes a test case
16
+ describe: (title, definition) ->
17
+ @initializeEnvironment() unless @EnvironmentInitialized
18
+
19
+ ul = $('<ul></ul>')
20
+ switch @Format
21
+ when 'ul'
22
+ $('.results').append($('<li>' + title + '</li>').append(ul))
23
+ when 'terminal'
24
+ $('.results').append "#{title}<br>"
25
+ ul.depth = 2
26
+
27
+ @testStack = [{
28
+ title: title
29
+ ul: ul
30
+ before: []
31
+ }]
32
+
33
+ definition()
34
+
35
+ # Displays a summary of error rate at the end of testing
36
+ finalize: ->
37
+ summary = "#{@counts.passed} passed, #{@counts.failed} failed, #{@counts.pending} pending, #{@counts.total} total"
38
+ switch @Format
39
+ when 'ul'
40
+ document.title = summary
41
+ if @errors.length
42
+ $('<h3>Errors</h3>').appendTo document.body
43
+ ul = $('<ul></ul>').addClass('errors').appendTo(document.body)
44
+ for error in @errors
45
+ ul.append $('<li>').append($('<span>').html(error.message), ' - ', $('<span>').html(error.title))
46
+ when 'terminal'
47
+ $('.results').append "<br>"
48
+ for error in @errors
49
+ $('.results').append "&#x1b;[31m#{error.message}&#x1b;[0m #{error.title}<br>"
50
+ color = if @counts.failed > 0
51
+ 31
52
+ else if @counts.pending > 0
53
+ 33
54
+ else
55
+ 32
56
+ $('.results').append "&#x1b;[1m&#x1b;[#{color}m#{summary}&#x1b;[0m<br>"
57
+
58
+ # Extends the environment with test methods
59
+ initializeEnvironment: ->
60
+ @EnvironmentInitialized = true
61
+
62
+ @errors = []
63
+ @counts = {
64
+ passed: 0
65
+ failed: 0
66
+ pending: 0
67
+ total: 0
68
+ }
69
+
70
+ @Format = 'ul'
71
+ @Format = 'terminal' if location.hash == '#terminal'
72
+
73
+ # Add results display element to the page
74
+ switch @Format
75
+ when 'ul'
76
+ $('body').append('<ul class="results"></ul>')
77
+ when 'terminal'
78
+ $('body').append('<div class="results"></div>')
79
+
80
+ # Tests for a positive match
81
+ Object.prototype.should = (matcher) ->
82
+ result = matcher(this)
83
+ Spec.fail "expected #{result[1]}" unless result[0]
84
+
85
+ # Tests for a negative match
86
+ Object.prototype.shouldNot = (matcher) ->
87
+ result = matcher(this)
88
+ Spec.fail "expected not #{result[1]}" if result[0]
89
+
90
+ # Sets up an expectation
91
+ window.expectation = (message) ->
92
+ exp = {
93
+ message: message
94
+ meet: -> @met++
95
+ met: 0
96
+ desired: 1
97
+ twice: ->
98
+ @desired = 2
99
+ this
100
+ exactly: (times) ->
101
+ @desired = times
102
+ {times: this}
103
+ timesString: (times) ->
104
+ switch times
105
+ when 0
106
+ 'not at all'
107
+ when 1
108
+ 'once'
109
+ when 2
110
+ 'twice'
111
+ else
112
+ "#{times} times"
113
+ check: ->
114
+ if @met != @desired
115
+ Spec.fail "expected #{message} #{@timesString @desired}, actually received #{@timesString @met}"
116
+ }
117
+ Spec.expectations.push exp
118
+ exp
119
+
120
+ # Creates a stub method with an expectation
121
+ Object.prototype.shouldReceive = (name) ->
122
+ object = this
123
+
124
+ received = expectation "to receive &ldquo;#{name}&rdquo;"
125
+
126
+ passthrough = object[name]
127
+ object[name] = -> received.meet()
128
+
129
+ received.with = (expectArgs...) ->
130
+ object[name] = (args...) ->
131
+ received.meet()
132
+ correct = true
133
+ correct = false if expectArgs.length != args.length
134
+ if correct
135
+ for i in [0..args.length]
136
+ correct = false unless String(expectArgs[i]) == String(args[i])
137
+ unless correct
138
+ Spec.fail "expected ##{name} to be called with arguments &ldquo;#{expectArgs.join ', '}&rdquo;, actual arguments: &ldquo;#{args.join ', '}&rdquo;"
139
+ received
140
+
141
+ received.andReturn = (returnValue) ->
142
+ fn = object[name]
143
+ object[name] = ->
144
+ fn.apply this, arguments
145
+ returnValue
146
+ received
147
+
148
+ received.andPassthrough = ->
149
+ fn = object[name]
150
+ object[name] = ->
151
+ fn.apply this, arguments
152
+ passthrough.apply this, arguments
153
+ received
154
+
155
+ received
156
+
157
+ # Creates a stub method, with an expectation of no calls
158
+ Object.prototype.shouldNotReceive = (name) ->
159
+ @shouldReceive(name).exactly(0).times
160
+
161
+ # Allows an assertion on a non-object value
162
+ window.expect = (object) ->
163
+ {
164
+ to: (matcher) ->
165
+ result = matcher(object)
166
+ Spec.fail "expected #{result[1]}" unless result[0]
167
+ notTo: (matcher) ->
168
+ result = matcher(object)
169
+ Spec.fail "expected not #{result[1]}" if result[0]
170
+ }
171
+
172
+ # Adds a setup step to the current test case
173
+ window.beforeEach = (action) ->
174
+ test = Spec.testStack[Spec.testStack.length - 1]
175
+ test.before.push action
176
+
177
+ # Prepares a sub-test of the current test case
178
+ window.describe = window.context = (title, definition) ->
179
+ parent = Spec.testStack[Spec.testStack.length - 1]
180
+
181
+ ul = $('<ul></ul>')
182
+ switch Spec.Format
183
+ when 'ul'
184
+ parent.ul.append($('<li>' + title + '</li>').append(ul))
185
+ when 'terminal'
186
+ $('.results').append(Spec.Pad(title, parent.ul.depth) + "<br>")
187
+ ul.depth = parent.ul.depth + 2
188
+
189
+ Spec.testStack.push {
190
+ title: title
191
+ ul: ul
192
+ before: []
193
+ }
194
+ definition()
195
+ Spec.testStack.pop()
196
+
197
+ # Creates a specificaition
198
+ window.it = (title, definition) ->
199
+ test = Spec.testStack[Spec.testStack.length - 1]
200
+ status = if definition?
201
+ env = {sandbox: $('<div/>').appendTo document.body}
202
+ for aTest in Spec.testStack
203
+ for action in aTest.before
204
+ action.call env
205
+
206
+ Spec.expectations = []
207
+ Spec.testTitle = title
208
+
209
+ window.onerror = (message) ->
210
+ Spec.fail "Error: #{message}"
211
+
212
+ Spec.passed = true
213
+ try
214
+ definition.call env
215
+ catch e
216
+ Spec.fail 'Error: ' + e
217
+
218
+ for expectation in Spec.expectations
219
+ expectation.check()
220
+
221
+ delete Spec.expectations
222
+ delete Spec.testTitle
223
+ delete window.onerror
224
+
225
+ env.sandbox.empty().remove()
226
+
227
+ if Spec.passed then "passed"; else "failed"
228
+ else
229
+ "pending"
230
+
231
+ switch Spec.Format
232
+ when 'ul'
233
+ li = $('<li>' + title + '</li>')
234
+ li.addClass status
235
+
236
+ test.ul.append li
237
+ when 'terminal'
238
+ s = title
239
+ color = switch status
240
+ when 'passed' then 32
241
+ when 'failed' then 31
242
+ when 'pending' then 33
243
+ $('.results').append Spec.Pad("&#x1b;[#{color}m#{s}&#x1b;[0m<br>", test.ul.depth)
244
+
245
+ Spec.counts[status]++
246
+ Spec.counts.total++
247
+
248
+ # Tests if matched value is a function
249
+ window.beAFunction = (value) ->
250
+ [typeof value is 'function', "to have type &ldquo;function&rdquo;, actual &ldquo;#{typeof value}&rdquo;"]
251
+
252
+ # Tests if matched value === expected value
253
+ window.be = (expected) ->
254
+ (value) ->
255
+ [value is expected, "to be &ldquo;#{Spec.Escape expected}&rdquo;, actual &ldquo;#{Spec.Escape value}&rdquo;"]
256
+
257
+ # Tests if matched value is boolean true
258
+ window.beTrue = (value) ->
259
+ [String(value) == 'true', "to be true, got &ldquo;#{Spec.Escape value}&rdquo;"]
260
+
261
+ # Tests if matched value is boolean false
262
+ window.beFalse = (value) ->
263
+ [String(value) == 'false', "to be false, got &ldquo;#{Spec.Escape value}&rdquo;"]
264
+
265
+ # Tests if matched value is an instance of class
266
+ window.beAnInstanceOf = (klass) ->
267
+ (value) ->
268
+ [value instanceof klass, "to be an instance of &ldquo;#{klass}&rdquo;"]
269
+
270
+ # Tests if matched value == expected value
271
+ window.equal = (expected) ->
272
+ (value) ->
273
+ [String(value) == String(expected), "to equal &ldquo;#{Spec.Escape expected}&rdquo;, actual &ldquo;#{Spec.Escape value}&rdquo;"]
274
+
275
+ # Fails test, with an error message
276
+ fail: (message) ->
277
+ @passed = false
278
+ @error = message
279
+ titles = []
280
+ for item in @testStack
281
+ titles.push item.title
282
+ titles.push @testTitle
283
+ @errors.push {
284
+ title: titles.join ' '
285
+ message: message
286
+ }
287
+
288
+ # Cleans test environment initialized with #initializeEnvironment
289
+ uninitializeEnvironment: ->
290
+ @EnvironmentInitialized = false
291
+
292
+ delete Object.prototype.should
293
+ delete Object.prototype.shouldNot
294
+ delete window.expectation
295
+ delete Object.prototype.shouldReceive
296
+ delete Object.prototype.shouldNotReceive
297
+ delete window.expect
298
+ delete window.beforeEach
299
+ delete window.describe
300
+ delete window.context
301
+ delete window.it
302
+ delete window.beAFunction
303
+ delete window.be
304
+ delete window.beTrue
305
+ delete window.beFalse
306
+ delete window.beAnInstanceOf
307
+ delete window.equal
308
+ }
@@ -0,0 +1,58 @@
1
+ require 'tsort'
2
+
3
+ class RequiredFiles < Hash
4
+ include TSort
5
+
6
+ attr_accessor :paths
7
+
8
+ # Adds a file to required list, and recursively scans and adds all other
9
+ # files required by this file
10
+ def add name
11
+ file_name = full_path name
12
+ if file_name && !self[name.to_sym]
13
+ requirements = find_requirements file_name
14
+ self[name.to_sym] = {
15
+ :file_name => file_name,
16
+ :requires => requirements
17
+ }
18
+ requirements.each { |requirement| add requirement }
19
+ end
20
+ end
21
+
22
+ # Gets an array of file paths sorted in dependency order
23
+ def sorted_files
24
+ tsort.map{ |key| self[key][:file_name] }
25
+ end
26
+
27
+ private
28
+ # Searches for file in path with given name and returns full file path,
29
+ # or nil if not found
30
+ def full_path name
31
+ file_path = @paths.find { |path| File.exists? "#{path}/#{name}.coffee" }
32
+ raise "File not found: #{name}" unless file_path
33
+ file_path && "#{file_path}/#{name}.coffee"
34
+ end
35
+
36
+ # Scans a coffeescript file for requirement directives.
37
+ #
38
+ # Example requirement directives:
39
+ # #require ST
40
+ # #require ST/Model/Index
41
+ def find_requirements name
42
+ requirements = []
43
+ File.open name do |file|
44
+ file.each do |line|
45
+ if line.match /^#require\s+(\S+)\s*$/
46
+ requirements << $1
47
+ end
48
+ end
49
+ end
50
+ requirements
51
+ end
52
+
53
+ # Callback functions for tsort
54
+ alias tsort_each_node each_key
55
+ def tsort_each_child node
56
+ self[node][:requires].each{ |name| yield name.to_sym }
57
+ end
58
+ end
data/lib/spec.rb ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env jruby
2
+ require "rubygems"
3
+ require "bundler/setup"
4
+ require "coffee-script"
5
+ require "celerity"
6
+
7
+ Dir.mkdir 'compiled' unless File.exists? 'compiled'
8
+ raise "Can't create compile directory" unless File.directory? 'compiled'
9
+
10
+ # Compile coffee scripts
11
+ files = Dir['lib/**/*.coffee', 'spec/**/*.coffee']
12
+ longest_name = files.map(&:length).max
13
+ for file in files
14
+ begin
15
+ mtime = File.mtime file
16
+
17
+ outfile = file.sub /^(lib|spec)/, 'compiled'
18
+ outfile = outfile.sub /\.coffee$/, '.js'
19
+
20
+ next if File.exists?(outfile) && File.mtime(outfile) == mtime
21
+
22
+ dir = outfile.sub /\/[^\/]+$/, ''
23
+ Dir.mkdir dir unless File.exists? dir
24
+ File.open outfile, 'w' do |f|
25
+ f.write CoffeeScript.compile(File.read(file))
26
+ end
27
+ File.utime mtime, mtime, outfile
28
+ puts outfile
29
+ rescue CoffeeScript::CompilationError => e
30
+ if e.message.match /^SyntaxError: (.*) on line (\d+)\D*$/
31
+ puts "\e[31m#{file}:#{$2}".ljust(longest_name + 6) + " #{$1}\e[0m"
32
+ elsif e.message.match /^Parse error on line (\d+): (.*)$/
33
+ puts "\e[31m#{file}:#{$1}".ljust(longest_name + 6) + " #{$2}\e[0m"
34
+ else
35
+ puts "#{file} - #{e.message}"
36
+ end
37
+ end
38
+ end
39
+
40
+ browser = Celerity::Browser.new
41
+ browser.goto 'http://localhost/~tobico/SeaTurtle/spec/spec.html#terminal'
42
+ puts browser.text