moco 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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.txt +67 -0
  4. data/Rakefile +15 -0
  5. data/bin/moco +6 -0
  6. data/lib/moco/ansi_escape.rb +37 -0
  7. data/lib/moco/application.rb +109 -0
  8. data/lib/moco/browser.rb +64 -0
  9. data/lib/moco/browser_error.rb +105 -0
  10. data/lib/moco/compile_error.rb +66 -0
  11. data/lib/moco/compiler.rb +151 -0
  12. data/lib/moco/compiler_option.rb +60 -0
  13. data/lib/moco/compiler_register.rb +29 -0
  14. data/lib/moco/compilers/coffee_compiler.rb +70 -0
  15. data/lib/moco/compilers/haml_compiler.rb +21 -0
  16. data/lib/moco/compilers/less_compiler.rb +15 -0
  17. data/lib/moco/compilers/markdown_compiler.rb +139 -0
  18. data/lib/moco/compilers/sass_compiler.rb +25 -0
  19. data/lib/moco/file_util.rb +54 -0
  20. data/lib/moco/log.rb +108 -0
  21. data/lib/moco/monitor.rb +83 -0
  22. data/lib/moco/options.rb +336 -0
  23. data/lib/moco/source_map.rb +22 -0
  24. data/lib/moco/support/error/error.css +22 -0
  25. data/lib/moco/support/error/error.html +7 -0
  26. data/lib/moco/support/error/error.js +58 -0
  27. data/lib/moco/support/reload.scpt +0 -0
  28. data/lib/moco.rb +44 -0
  29. data/moco.gemspec +35 -0
  30. data/moco.rb +35 -0
  31. data/src/error/error.coffee +49 -0
  32. data/src/reload.applescript +135 -0
  33. data/test/ansi_escape_test.rb +52 -0
  34. data/test/application_test.rb +40 -0
  35. data/test/browser_error_test.rb +101 -0
  36. data/test/browser_test.rb +29 -0
  37. data/test/compile_error_test.rb +82 -0
  38. data/test/compiler_option_test.rb +121 -0
  39. data/test/compiler_register_test.rb +41 -0
  40. data/test/compiler_test.rb +243 -0
  41. data/test/compilers/coffee_compiler_test.rb +117 -0
  42. data/test/compilers/haml_compiler_test.rb +86 -0
  43. data/test/compilers/less_compiler_test.rb +72 -0
  44. data/test/compilers/markdown_compiler_test.rb +211 -0
  45. data/test/compilers/sass_compiler_test.rb +84 -0
  46. data/test/file_util_test.rb +37 -0
  47. data/test/fixtures/_color.scss +1 -0
  48. data/test/fixtures/color.less +1 -0
  49. data/test/fixtures/css_lib.rb +2 -0
  50. data/test/fixtures/html_lib.rb +2 -0
  51. data/test/fixtures/js_lib.rb +2 -0
  52. data/test/fixtures/layout.html +13 -0
  53. data/test/fixtures/moco.rb +8 -0
  54. data/test/fixtures/options_lib.rb +2 -0
  55. data/test/fixtures/source.txt +1 -0
  56. data/test/monitor_test.rb +68 -0
  57. data/test/options_test.rb +177 -0
  58. data/test/test_helper.rb +57 -0
  59. metadata +270 -0
@@ -0,0 +1,243 @@
1
+ require 'test_helper'
2
+
3
+ module MoCo
4
+
5
+ describe Compiler do
6
+
7
+ describe 'require libraries' do
8
+
9
+ $LOAD_PATH.unshift(fixtures_path)
10
+
11
+ it 'will not require the library at the time of registration' do
12
+ JsCompiler.require_library('js_lib')
13
+ refute defined? JsLib
14
+ end
15
+
16
+ it 'requires the library when the compiler is instantiated' do
17
+ CssCompiler.require_library('css_lib')
18
+ refute defined? CssLib
19
+ CssCompiler.new('')
20
+ assert defined? CssLib
21
+ end
22
+
23
+ it 'requires the superclass libraries' do
24
+ HtmlCompiler.require_library('html_lib')
25
+ refute defined? HtmlLib
26
+ Class.new(Class.new(HtmlCompiler)).new('')
27
+ assert defined? HtmlLib
28
+ end
29
+
30
+ end
31
+
32
+ describe 'require uninstalled libraries' do
33
+
34
+ class CorruptCompiler < HtmlCompiler
35
+ require_library 'not_installed', 'not-installed-gem', '~> 2'
36
+ end
37
+
38
+ it 'raises a LoadError with installation instructions' do
39
+ e = assert_raises(Gem::LoadError) { CorruptCompiler.new('') }
40
+ assert_match "gem install not-installed-gem -v '~> 2'", e.message
41
+ end
42
+
43
+ end
44
+
45
+ describe 'compiler options' do
46
+
47
+ after { reset_options(HtmlCompiler) }
48
+
49
+ it 'sets the option' do
50
+ HtmlCompiler.set_option(:format, :html5)
51
+ assert_equal :html5, HtmlCompiler.options[:format]
52
+ assert_equal :html5, HtmlCompiler.new('').options[:format]
53
+ end
54
+
55
+ specify 'the default value is true' do
56
+ HtmlCompiler.set_option(:ugly)
57
+ assert_equal true, HtmlCompiler.options[:ugly]
58
+ end
59
+
60
+ specify 'updating the options hash directly has no effect' do
61
+ HtmlCompiler.options[:format] = :xhtml
62
+ compiler = HtmlCompiler.new('')
63
+ compiler.options[:format] = :xhtml
64
+ assert_empty HtmlCompiler.options
65
+ assert_empty compiler.options
66
+ end
67
+
68
+ end
69
+
70
+ describe 'the compiled filename' do
71
+
72
+ it 'cannot be equal to the source filename' do
73
+ e = assert_raises(Error) do
74
+ HtmlCompiler.new('index.html', Dir.pwd + '/index.html')
75
+ end
76
+ assert_match 'validate_filenames', e.backtrace.first
77
+ end
78
+
79
+ it 'will keep the compiled filename unchanged, if provided' do
80
+ compiler = HtmlCompiler.new('source.md', 'compiled.xml', 'dir')
81
+ assert_equal 'compiled.xml', compiler.compiled_file
82
+ end
83
+
84
+ end
85
+
86
+ describe 'the compiled filename extension' do
87
+
88
+ def compiled_file(source_file)
89
+ JsCompiler.new(source_file).compiled_file
90
+ end
91
+
92
+ it 'has the compiled extension' do
93
+ assert_equal 'script.js', compiled_file('script.coffee')
94
+ end
95
+
96
+ specify 'the compiler must implement compiled_extension' do
97
+ e = assert_raises(NotImplementedError) { Compiler.new('') }
98
+ assert_match 'compiled_extension', e.backtrace.first
99
+ end
100
+
101
+ describe 'nested source extension' do
102
+
103
+ it 'will not duplicate the compiled extension' do
104
+ assert_equal 'script.js', compiled_file('script.js.coffee')
105
+ end
106
+
107
+ it 'will keep other extensions' do
108
+ assert_equal 'script.min.js', compiled_file('script.min.coffee')
109
+ end
110
+
111
+ specify 'not duplicate extension' do
112
+ assert_equal 'about_js.js', compiled_file('about_js.coffee')
113
+ end
114
+
115
+ end
116
+
117
+ describe 'nested compiled extension' do
118
+
119
+ class Minify < JsCompiler
120
+ define_singleton_method(:compiled_extension) { '.min.js' }
121
+ end
122
+
123
+ specify do
124
+ assert_equal 'script.min.js', Minify.new('script.js').compiled_file
125
+ end
126
+
127
+ end
128
+
129
+ describe 'no extensions' do
130
+
131
+ class Readme < Compiler
132
+ define_singleton_method(:compiled_extension) { '' }
133
+ end
134
+
135
+ specify 'the compiled extension is empty' do
136
+ assert_equal 'README', Readme.new('README.txt').compiled_file
137
+ end
138
+
139
+ specify 'the source file has no extension' do
140
+ assert_equal 'README.html', HtmlCompiler.new('README').compiled_file
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+
147
+ describe 'the compiled filename directory' do
148
+
149
+ def compiled_file(source_file, compiled_dir = nil)
150
+ CssCompiler.new(source_file, nil, compiled_dir).compiled_file
151
+ end
152
+
153
+ it 'has the same directory as the source file by default' do
154
+ assert_equal 'css/style.css', compiled_file('css/style.sass')
155
+ end
156
+
157
+ it 'replaces the directory if one is provided' do
158
+ assert_equal 'css/style.css', compiled_file('style.scss', 'css')
159
+ assert_equal 'css/style.css', compiled_file('/sass/style.scss', 'css/')
160
+ end
161
+
162
+ it 'removes the source directory if compiled_dir is empty' do
163
+ assert_equal 'style.css', compiled_file('templates/style.scss', '')
164
+ end
165
+
166
+ end
167
+
168
+ describe 'compile' do
169
+
170
+ class GoodCompiler < Compiler
171
+ define_method(:compiled_text) { 'The compiled text' }
172
+ end
173
+
174
+ let(:compiler) do
175
+ GoodCompiler.new(fixtures_path('source.txt'), tmp_dir + '/dir/out.txt')
176
+ end
177
+
178
+ it 'reads the source file' do
179
+ assert_equal 'The source text', compiler.source_text
180
+ end
181
+
182
+ it 'makes the necessary directories for the compiled file' do
183
+ compiler.compile
184
+ assert File.directory?(File.dirname(compiler.compiled_file))
185
+ end
186
+
187
+ it 'writes the compiled text to the compiled file' do
188
+ compiler.compile
189
+ assert_equal 'The compiled text', File.read(compiler.compiled_file)
190
+ end
191
+
192
+ end
193
+
194
+ describe 'failed compilation' do
195
+
196
+ class BadCompiler < Compiler
197
+ define_method(:compiled_text) { raise 'The error text' }
198
+ end
199
+
200
+ let(:compiler) { BadCompiler.new('', tmp_dir + '/dir/out.txt') }
201
+
202
+ it 'raises a CompileError' do
203
+ assert_raises(CompileError) { compiler.compile }
204
+ end
205
+
206
+ it 'writes the error message to the compiled file' do
207
+ compiler.compile rescue
208
+ assert_equal 'The error text', File.read(compiler.compiled_file)
209
+ end
210
+
211
+ specify 'the compiler must implement compiled_text' do
212
+ e = assert_raises(NotImplementedError) { JsCompiler.new('').compile }
213
+ assert_match 'compiled_text', e.backtrace.first
214
+ end
215
+
216
+ end
217
+
218
+ describe 'should_compile?' do
219
+
220
+ let(:compiler) { JsCompiler.new(tmp_dir + '/script.coffee') }
221
+
222
+ it 'is true if the compiled file does not exist' do
223
+ touch(compiler.source_file)
224
+ assert compiler.should_compile?
225
+ end
226
+
227
+ it 'is true when the source file is updated' do
228
+ touch(compiler.compiled_file, :mtime => 0)
229
+ touch(compiler.source_file, :mtime => 1)
230
+ assert compiler.should_compile?
231
+ end
232
+
233
+ it 'is false if the compiled file is up to date' do
234
+ touch(compiler.source_file)
235
+ touch(compiler.compiled_file)
236
+ refute compiler.should_compile?
237
+ end
238
+
239
+ end
240
+
241
+ end
242
+
243
+ end
@@ -0,0 +1,117 @@
1
+ require 'test_helper'
2
+
3
+ module MoCo
4
+
5
+ describe CoffeeCompiler do
6
+
7
+ let(:compiler) { mock_compiler(CoffeeCompiler, 'alert yes') }
8
+ let(:compiled_text) { compiler.compiled_text }
9
+
10
+ it 'is registered for coffee files' do
11
+ assert_equal CoffeeCompiler, MoCo.compiler_for('coffee')
12
+ end
13
+
14
+ it 'is a JavaScript compiler' do
15
+ assert CoffeeCompiler < JsCompiler
16
+ assert_equal 'js', CoffeeCompiler.compiled_extension
17
+ end
18
+
19
+ it 'compiles CoffeeScript into JavaScript' do
20
+ assert_match 'alert(true);', compiled_text
21
+ end
22
+
23
+ describe 'options' do
24
+
25
+ after { reset_options(CoffeeCompiler) }
26
+
27
+ it 'has a function wrapper by default' do
28
+ assert compiled_text.start_with?('(function() {')
29
+ assert compiled_text.strip.end_with?('}).call(this);')
30
+ end
31
+
32
+ specify 'the bare option disables the function wrapper' do
33
+ CoffeeCompiler.set_option(:bare)
34
+ assert_equal 'alert(true);', compiled_text.strip
35
+ end
36
+
37
+ specify 'the header includes the version number' do
38
+ CoffeeCompiler.set_option(:header)
39
+ version = CoffeeCompiler.context.run('return CoffeeScript.VERSION;')
40
+ assert_match "// Generated by CoffeeScript #{version}", compiled_text
41
+ end
42
+
43
+ end
44
+
45
+ describe 'source maps' do
46
+
47
+ before { CoffeeCompiler.set_option(:sourceMap) }
48
+ after { reset_options(CoffeeCompiler) }
49
+
50
+ let(:compiler) do
51
+ mock_compiler(CoffeeCompiler, 'alert', 'app.coffee', 'dir/the app.js')
52
+ end
53
+
54
+ it 'produces a source map with relative paths' do
55
+ compiler.compile
56
+ map = JSON.load(compiler.source_map_text)
57
+ assert_equal 3, map['version']
58
+ assert_equal 'the app.js', map['file']
59
+ assert_equal '../app.coffee', map['sources'].first
60
+ assert_match 'AAA', map['mappings']
61
+ end
62
+
63
+ it 'adds a comment, with the source map URI, to the compiled text' do
64
+ assert_match '//@ sourceMappingURL=the%20app.js.map', compiled_text
65
+ end
66
+
67
+ it 'will be saved to the same directory as the compiled file' do
68
+ assert_equal File.dirname(compiler.source_map_file),
69
+ File.dirname(compiler.compiled_file)
70
+ end
71
+
72
+ it 'writes the source map file' do
73
+ compiler = CoffeeCompiler.new('/script.coffee', nil, tmp_dir)
74
+ compiler.define_singleton_method(:source_text) { 'alert' }
75
+ compiler.compile
76
+ assert_equal compiler.source_map_text, File.read(compiler.source_map_file)
77
+ end
78
+
79
+ end
80
+
81
+ describe 'compile error' do
82
+
83
+ let(:compiler) { mock_compiler(CoffeeCompiler, 'evil = (eval) ->') }
84
+ let(:error) { compiler.compile rescue $! }
85
+
86
+ it 'raises a CompileError' do
87
+ assert_raises(CompileError) { compiler.compile }
88
+ end
89
+
90
+ it 'captures the error location' do
91
+ assert_equal 1, error.line
92
+ assert_equal 9, error.column
93
+ end
94
+
95
+ describe 'the error message' do
96
+
97
+ it 'removes the filename and location from the original message' do
98
+ message = error.error.message
99
+ assert message.start_with?('mock.coffee:1:9: error: parameter name')
100
+
101
+ message = error.message
102
+ assert message.start_with?('Parameter name')
103
+ end
104
+
105
+ it 'marks the erroneous code with red (using ANSI escape codes)' do
106
+ message = error.message.split("\n")
107
+ assert_equal "evil = (\e[1;31meval\e[0m) ->", message[-2]
108
+ assert_equal "\e[1;31m ^^^^\e[0m" , message[-1]
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+
115
+ end if run_compiler_tests?(CoffeeCompiler)
116
+
117
+ end
@@ -0,0 +1,86 @@
1
+ require 'test_helper'
2
+
3
+ module MoCo
4
+
5
+ describe HamlCompiler do
6
+
7
+ let(:compiler) { mock_compiler(HamlCompiler, '#haml= 2 + 2') }
8
+
9
+ it 'is registered for haml files' do
10
+ assert_equal HamlCompiler, MoCo.compiler_for('haml')
11
+ end
12
+
13
+ it 'is a Html compiler' do
14
+ assert HamlCompiler < HtmlCompiler
15
+ assert_equal 'html', HamlCompiler.compiled_extension
16
+ end
17
+
18
+ it 'compiles haml into html' do
19
+ assert_equal "<div id='haml'>4</div>", compiler.compiled_text.strip
20
+ end
21
+
22
+ specify 'options are passed on to the haml compiler' do
23
+ HamlCompiler.set_option(:attr_wrapper, '@')
24
+ assert_match '<div id=@haml@>', compiler.compiled_text
25
+ reset_options(HamlCompiler)
26
+ end
27
+
28
+ describe 'compile error' do
29
+
30
+ def compile(haml)
31
+ mock_compiler(HamlCompiler, haml, 'mock.haml').compile
32
+ rescue CompileError => error
33
+ error
34
+ end
35
+
36
+ it 'removes the filename and line number from the error message' do
37
+ error = compile('.haml= [}')
38
+ refute_match 'mock.haml', error.message
39
+ refute_match /[:\d]/, error.message
40
+
41
+ # The original error
42
+ assert_match 'mock.haml:1:', error.error.message
43
+ assert_match 'mock.haml:2:', error.error.message
44
+ end
45
+
46
+ describe 'the line number' do
47
+
48
+ specify 'line numbers fetched from @line start at 1' do
49
+ error = compile(' %p')
50
+ assert_equal 1, error.line
51
+
52
+ # The original error
53
+ assert error.error.line
54
+ end
55
+
56
+ it 'finds the line number in the backtrace' do
57
+ error = compile("\n.time= Date.tomorrow")
58
+ assert_equal 2, error.line
59
+
60
+ # The original error
61
+ refute defined? error.error.line
62
+ assert_match 'mock.haml:2:', error.error.backtrace.first
63
+ end
64
+
65
+ it 'finds the line number in the error message' do
66
+ error = compile("\n\n.unbalanced= [}")
67
+ assert_equal 3, error.line
68
+
69
+ # The original error
70
+ assert_nil error.error.line
71
+ refute_match ':3:', error.error.backtrace.to_s
72
+ assert_match 'mock.haml:3:', error.error.message
73
+ end
74
+
75
+ specify 'line can be nil' do
76
+ error = compile("%p= eval '2 *** 10'")
77
+ assert_nil error.line
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+
84
+ end if run_compiler_tests?(HamlCompiler)
85
+
86
+ end
@@ -0,0 +1,72 @@
1
+ require 'test_helper'
2
+
3
+ module MoCo
4
+
5
+ describe LessCompiler do
6
+
7
+ it 'is registered for less files' do
8
+ assert_equal LessCompiler, MoCo.compiler_for('less')
9
+ end
10
+
11
+ it 'is a CSS compiler' do
12
+ assert LessCompiler < CssCompiler
13
+ assert_equal 'css', LessCompiler.compiled_extension
14
+ end
15
+
16
+ describe 'compile' do
17
+
18
+ css = <<-EOF
19
+ .less {
20
+ color: #d9eef2;
21
+ }
22
+ EOF
23
+
24
+ less = <<-EOF
25
+ @blue: #D9EEF2;
26
+ .less {
27
+ color: @blue;
28
+ }
29
+ EOF
30
+
31
+ import = <<-EOF
32
+ @import 'color';
33
+ .less {
34
+ color: @blue;
35
+ }
36
+ EOF
37
+
38
+ [css, less, import].each { |style| style.gsub!(/^ {8}/, '') }
39
+
40
+ it 'compiles less into css' do
41
+ compiler = mock_compiler(LessCompiler, less)
42
+ assert_equal css, compiler.compiled_text
43
+ end
44
+
45
+ it 'handles the @import directive' do
46
+ LessCompiler.set_option(:paths, [fixtures_path])
47
+ compiler = mock_compiler(LessCompiler, import)
48
+ assert_equal css, compiler.compiled_text
49
+ reset_options(LessCompiler)
50
+ end
51
+
52
+ end
53
+
54
+ describe 'compile error' do
55
+
56
+ let(:compiler) do
57
+ mock_compiler(LessCompiler, '.less { color: @pink; }')
58
+ end
59
+
60
+ it 'raises a CompileError' do
61
+ assert_raises(CompileError) { compiler.compile }
62
+ end
63
+
64
+ it 'has a line number' do
65
+ assert_equal 1, (compiler.compile rescue $!).line
66
+ end
67
+
68
+ end
69
+
70
+ end if run_compiler_tests?(LessCompiler)
71
+
72
+ end