seaweed 0.1.0

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/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