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.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.txt +67 -0
- data/Rakefile +15 -0
- data/bin/moco +6 -0
- data/lib/moco/ansi_escape.rb +37 -0
- data/lib/moco/application.rb +109 -0
- data/lib/moco/browser.rb +64 -0
- data/lib/moco/browser_error.rb +105 -0
- data/lib/moco/compile_error.rb +66 -0
- data/lib/moco/compiler.rb +151 -0
- data/lib/moco/compiler_option.rb +60 -0
- data/lib/moco/compiler_register.rb +29 -0
- data/lib/moco/compilers/coffee_compiler.rb +70 -0
- data/lib/moco/compilers/haml_compiler.rb +21 -0
- data/lib/moco/compilers/less_compiler.rb +15 -0
- data/lib/moco/compilers/markdown_compiler.rb +139 -0
- data/lib/moco/compilers/sass_compiler.rb +25 -0
- data/lib/moco/file_util.rb +54 -0
- data/lib/moco/log.rb +108 -0
- data/lib/moco/monitor.rb +83 -0
- data/lib/moco/options.rb +336 -0
- data/lib/moco/source_map.rb +22 -0
- data/lib/moco/support/error/error.css +22 -0
- data/lib/moco/support/error/error.html +7 -0
- data/lib/moco/support/error/error.js +58 -0
- data/lib/moco/support/reload.scpt +0 -0
- data/lib/moco.rb +44 -0
- data/moco.gemspec +35 -0
- data/moco.rb +35 -0
- data/src/error/error.coffee +49 -0
- data/src/reload.applescript +135 -0
- data/test/ansi_escape_test.rb +52 -0
- data/test/application_test.rb +40 -0
- data/test/browser_error_test.rb +101 -0
- data/test/browser_test.rb +29 -0
- data/test/compile_error_test.rb +82 -0
- data/test/compiler_option_test.rb +121 -0
- data/test/compiler_register_test.rb +41 -0
- data/test/compiler_test.rb +243 -0
- data/test/compilers/coffee_compiler_test.rb +117 -0
- data/test/compilers/haml_compiler_test.rb +86 -0
- data/test/compilers/less_compiler_test.rb +72 -0
- data/test/compilers/markdown_compiler_test.rb +211 -0
- data/test/compilers/sass_compiler_test.rb +84 -0
- data/test/file_util_test.rb +37 -0
- data/test/fixtures/_color.scss +1 -0
- data/test/fixtures/color.less +1 -0
- data/test/fixtures/css_lib.rb +2 -0
- data/test/fixtures/html_lib.rb +2 -0
- data/test/fixtures/js_lib.rb +2 -0
- data/test/fixtures/layout.html +13 -0
- data/test/fixtures/moco.rb +8 -0
- data/test/fixtures/options_lib.rb +2 -0
- data/test/fixtures/source.txt +1 -0
- data/test/monitor_test.rb +68 -0
- data/test/options_test.rb +177 -0
- data/test/test_helper.rb +57 -0
- metadata +270 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1bf216f6a4a3693e7fbe4208e2ce90c6c1381c18
|
4
|
+
data.tar.gz: 4ca98a5e6728548265487bdc3c310009fee3462b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 86b782cc69382beb4f4c2e9ea9408db2bd2d4f870966f83400de7bfe7feb57825abf15e31919d29212e9d8fa9a976a940a1195faecd631bbe36324b8cf720e74
|
7
|
+
data.tar.gz: c61aadfb6a5897fc065b160216fffed557624c2482ff119351c52290e37726f17db56cbfa36854a0baf7bd4abc3c66b297b41165b9c088435cd657651843ac61
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 AS Harbitz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.txt
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
Usage:
|
2
|
+
moco [options] SOURCE ...
|
3
|
+
moco [options] SOURCE:COMPILED ...
|
4
|
+
|
5
|
+
Description:
|
6
|
+
MoCo monitors web templates. On updates the templates are compiled and
|
7
|
+
the browser reloaded. MoCo currently supports CoffeeScript, Sass, LESS,
|
8
|
+
Markdown and Haml.
|
9
|
+
|
10
|
+
Files and directories:
|
11
|
+
The given source files and directories will be monitored for updates.
|
12
|
+
Use the SOURCE:COMPILED format to save the compiled files to another
|
13
|
+
directory or to change the compiled filename:
|
14
|
+
moco .:/www sass:/www/css README.md:/www/index.html
|
15
|
+
|
16
|
+
Options:
|
17
|
+
--monitor Keep running until Ctrl-C is pressed [DEFAULT]
|
18
|
+
--no-monitor Exit after the initial compilation
|
19
|
+
|
20
|
+
-c, --compile Compile all the supported file types [DEFAULT]
|
21
|
+
-c, --compile EXT,EXT Compile the given file types
|
22
|
+
--no-compile Disable compilation
|
23
|
+
moco -c coffee -c sass,scss .
|
24
|
+
|
25
|
+
-f, --force Force recompilation at startup
|
26
|
+
--no-force Do not compile up-to-date files [DEFAULT]
|
27
|
+
|
28
|
+
-m, --source-map Make source maps if the compiler supports it
|
29
|
+
--no-source-map Do not make source maps [DEFAULT]
|
30
|
+
|
31
|
+
-o, --option EXT:KEY:VAL Set a compiler option
|
32
|
+
moco -o coffee:header # header = true
|
33
|
+
-o haml:ugly:false # ugly = false
|
34
|
+
-o haml:format::xhtml # format = :xhtml
|
35
|
+
-o md:layout:md.html # layout = 'md.html'
|
36
|
+
-o less:paths:css: . # paths = ['css']
|
37
|
+
|
38
|
+
-r, --reload Reload after css/html/js file updates [DEFAULT]
|
39
|
+
-r, --reload EXT,EXT Set the file types that triggers reloading
|
40
|
+
--no-reload Disable reloading
|
41
|
+
moco -r rb -r css,html,js .
|
42
|
+
|
43
|
+
-b, --browser BRO,BRO The browsers to reload [all by DEFAULT]
|
44
|
+
moco -b safari -b chrome,canary .
|
45
|
+
|
46
|
+
-u, --url all Reload all active tabs
|
47
|
+
-u, --url localhost Reload active tabs with localhost urls [DEFAULT]
|
48
|
+
-u, --url URL,URL Reload active tabs where the url starts with URL
|
49
|
+
moco -u localhost -u http://app.dev/ .
|
50
|
+
|
51
|
+
--require LIB Require the library
|
52
|
+
moco --require path/to/compiler.rb .
|
53
|
+
|
54
|
+
-q, --quiet Log errors only
|
55
|
+
--no-quiet Log errors and file updates [DEFAULT]
|
56
|
+
|
57
|
+
-l, --list List the supported file types and browsers
|
58
|
+
|
59
|
+
-h, --help Display this message
|
60
|
+
|
61
|
+
The moco file:
|
62
|
+
MoCo looks for files named '.moco' and 'moco.rb' in the working directory
|
63
|
+
and in the home directory. The purpose of these files is to set options
|
64
|
+
and to define new compilers. The command line options have precedence.
|
65
|
+
|
66
|
+
More information:
|
67
|
+
https://github.com/asharbitz/moco#readme
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
|
2
|
+
|
3
|
+
require 'moco'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
Rake::TestTask.new do |test|
|
7
|
+
test.libs << 'test'
|
8
|
+
test.pattern = 'test/*_test.rb'
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'Compile files'
|
12
|
+
task :compile do
|
13
|
+
args = %w[--force --no-monitor --no-reload]
|
14
|
+
MoCo::Application.monitor_compile_and_reload(args)
|
15
|
+
end
|
data/bin/moco
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module MoCo
|
2
|
+
|
3
|
+
module AnsiEscape
|
4
|
+
|
5
|
+
def self.bold(text)
|
6
|
+
escape(text, '1')
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.bold_red(text)
|
10
|
+
escape(text, '1;31')
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.green(text)
|
14
|
+
escape(text, '32')
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.unescape(text)
|
18
|
+
text.gsub(/\e\[[\d;]+m(.*?)\e\[0*m/) do
|
19
|
+
block_given? ? yield($1) : $1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def self.escape(text, code)
|
26
|
+
if $stdout.tty?
|
27
|
+
"\e[#{code}m#{text}\e[0m"
|
28
|
+
else
|
29
|
+
text
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private_class_method :escape
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module MoCo
|
2
|
+
|
3
|
+
class Application
|
4
|
+
|
5
|
+
def self.monitor_compile_and_reload(args)
|
6
|
+
options = parse_options(args)
|
7
|
+
new(options).monitor_compile_and_reload
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def monitor_compile_and_reload
|
15
|
+
monitor = monitor_instance
|
16
|
+
monitor.files.each { |file| compile(file, @options[:force]) }
|
17
|
+
reload
|
18
|
+
if @options[:monitor]
|
19
|
+
Log.monitor
|
20
|
+
monitor.monitor { |file| compile_and_reload(file) }
|
21
|
+
puts
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.parse_options(args)
|
28
|
+
options = Options.parse(args)
|
29
|
+
Log.quiet = options[:quiet]
|
30
|
+
Log.load(Options.moco_files)
|
31
|
+
options
|
32
|
+
rescue OptionError, LoadError => e
|
33
|
+
Log.load(Options.moco_files)
|
34
|
+
abort e.message
|
35
|
+
end
|
36
|
+
|
37
|
+
private_class_method :parse_options
|
38
|
+
|
39
|
+
def monitor_instance
|
40
|
+
exts = []
|
41
|
+
exts += @options[:compile_exts] if @options[:compile]
|
42
|
+
exts += @options[:reload_exts] if @options[:reload]
|
43
|
+
Monitor.new(@options[:source_files], @options[:source_dirs], exts.uniq)
|
44
|
+
end
|
45
|
+
|
46
|
+
def compile_and_reload(file)
|
47
|
+
if compiler = compile(file, true)
|
48
|
+
reload(compiler.compiled_file)
|
49
|
+
end
|
50
|
+
reload(file)
|
51
|
+
end
|
52
|
+
|
53
|
+
def compile(file, force)
|
54
|
+
if @options[:compile]
|
55
|
+
compiler = compiler_for(file)
|
56
|
+
if compiler && (force || compiler.should_compile?)
|
57
|
+
do_compile(compiler)
|
58
|
+
compiler
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def compiler_for(file)
|
64
|
+
if compiler = MoCo.compiler_for(file)
|
65
|
+
compiler.new(file, compiled_file(file), compiled_dir(file))
|
66
|
+
end
|
67
|
+
rescue LoadError => e
|
68
|
+
abort e.message
|
69
|
+
end
|
70
|
+
|
71
|
+
def compiled_file(file)
|
72
|
+
@options[:compiled_files][file]
|
73
|
+
end
|
74
|
+
|
75
|
+
def compiled_dir(file)
|
76
|
+
@options[:compiled_dirs].keys.sort.reverse.each do |source_dir|
|
77
|
+
if file.start_with?(source_dir)
|
78
|
+
compiled_dir = @options[:compiled_dirs][source_dir]
|
79
|
+
if compiled_dir
|
80
|
+
compiled_dir = File.dirname(file).sub(source_dir, compiled_dir)
|
81
|
+
end
|
82
|
+
return compiled_dir
|
83
|
+
end
|
84
|
+
end
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def do_compile(compiler)
|
89
|
+
Log.compile(compiler)
|
90
|
+
compiler.compile
|
91
|
+
Log.update(compiler)
|
92
|
+
rescue CompileError => e
|
93
|
+
Log.error(e)
|
94
|
+
end
|
95
|
+
|
96
|
+
def reload(file = nil)
|
97
|
+
if @options[:reload]
|
98
|
+
@browser ||= browser_instance
|
99
|
+
@browser.reload if file.nil? || @browser.should_reload?(file)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def browser_instance
|
104
|
+
Browser.new(@options[:reload_exts], @options[:browsers], @options[:urls])
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
data/lib/moco/browser.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module MoCo
|
2
|
+
|
3
|
+
class Browser
|
4
|
+
|
5
|
+
def self.extensions
|
6
|
+
%w[css html js]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.browsers
|
10
|
+
%w[Canary Chrome Firefox Opera Safari WebKit]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.localhost
|
14
|
+
%w[
|
15
|
+
file:///
|
16
|
+
file://localhost/
|
17
|
+
http://localhost/
|
18
|
+
http://localhost:
|
19
|
+
http://127.0.0.1/
|
20
|
+
http://127.0.0.1:
|
21
|
+
http://0.0.0.0/
|
22
|
+
http://0.0.0.0:
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(extensions, browsers, urls)
|
27
|
+
@extensions = extensions
|
28
|
+
@args = browsers + urls(urls)
|
29
|
+
@reload = false
|
30
|
+
at_exit { do_reload }
|
31
|
+
end
|
32
|
+
|
33
|
+
def should_reload?(file)
|
34
|
+
@extensions.include?(FileUtil.normalized_extension(file))
|
35
|
+
end
|
36
|
+
|
37
|
+
def reload
|
38
|
+
return if @reload
|
39
|
+
@reload = true
|
40
|
+
Thread.new do
|
41
|
+
sleep 0.2
|
42
|
+
do_reload
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
SCRIPT = File.expand_path('../support/reload.scpt', __FILE__)
|
49
|
+
|
50
|
+
def urls(urls)
|
51
|
+
return [] if urls.include?('all')
|
52
|
+
urls += Browser.localhost if urls.delete('localhost')
|
53
|
+
urls.uniq
|
54
|
+
end
|
55
|
+
|
56
|
+
def do_reload
|
57
|
+
return unless @reload
|
58
|
+
@reload = false
|
59
|
+
system('osascript', SCRIPT, *@args)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module MoCo
|
4
|
+
|
5
|
+
class BrowserError
|
6
|
+
|
7
|
+
def self.message(error)
|
8
|
+
new(error).message
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(error)
|
12
|
+
@message = error.message
|
13
|
+
@file = file(error)
|
14
|
+
@line = error.line
|
15
|
+
@column = error.column
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
escaped_message + on_line + in_file
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def file(error)
|
25
|
+
FileUtil.short_path(error.file)
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_file(file)
|
29
|
+
File.read(File.expand_path('../support/error/' + file, __FILE__))
|
30
|
+
end
|
31
|
+
|
32
|
+
def escaped_message(message = @message)
|
33
|
+
message = remove_ansi_color(message)
|
34
|
+
message = message.gsub('\\') { '\\\\' }.gsub('"', '\"')
|
35
|
+
message + "\n\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove_ansi_color(message)
|
39
|
+
AnsiEscape.unescape(message) do |escaped|
|
40
|
+
html_allowed? ? "<span>#{escaped}</span>" : escaped
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def html_allowed?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_line
|
49
|
+
@line ? "Line: #{@line}\n" : ''
|
50
|
+
end
|
51
|
+
|
52
|
+
def in_file
|
53
|
+
"File: #{File.basename(@file)} (#{edit_link || @file})"
|
54
|
+
end
|
55
|
+
|
56
|
+
def edit_link
|
57
|
+
if html_allowed? && txmt_url_scheme?
|
58
|
+
href = "txmt://open/?url=file://#{@file}"
|
59
|
+
href << "&line=#{@line}" if @line
|
60
|
+
href << "&column=#{@column}" if @column
|
61
|
+
"<a href='#{href}'>#{@file}</a>"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def txmt_url_scheme?
|
66
|
+
return @@txmt if defined? @@txmt
|
67
|
+
@@txmt = `defaults read com.apple.LaunchServices` =~
|
68
|
+
/LSHandlerURLScheme["\s]*=["\s]*txmt["\s]*;/
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
class CssError < BrowserError
|
74
|
+
|
75
|
+
def message
|
76
|
+
read_file('error.css') % super.gsub(/\n/, '\a ')
|
77
|
+
end
|
78
|
+
|
79
|
+
def html_allowed?
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
class JsError < BrowserError
|
86
|
+
|
87
|
+
def message
|
88
|
+
read_file('error.js') % super.gsub(/\n/, '<br>')
|
89
|
+
end
|
90
|
+
|
91
|
+
def escaped_message
|
92
|
+
super(CGI.escapeHTML(@message))
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
class HtmlError < JsError
|
98
|
+
|
99
|
+
def message
|
100
|
+
read_file('error.html') % super
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module MoCo
|
2
|
+
|
3
|
+
class CompileError < Error
|
4
|
+
|
5
|
+
attr_reader :error
|
6
|
+
attr_reader :file
|
7
|
+
attr_reader :line
|
8
|
+
attr_reader :column
|
9
|
+
|
10
|
+
def initialize(error, file)
|
11
|
+
@error = error
|
12
|
+
@file = file
|
13
|
+
@line = get_line
|
14
|
+
@column = get_column
|
15
|
+
super(get_message)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def get_line
|
21
|
+
line = line_from_method || line_from_message || line_from_backtrace
|
22
|
+
line.to_i if line
|
23
|
+
end
|
24
|
+
|
25
|
+
def line_from_method
|
26
|
+
@error.line if @error.respond_to?(:line)
|
27
|
+
end
|
28
|
+
|
29
|
+
def line_from_message
|
30
|
+
@error.message[source_pattern, 1]
|
31
|
+
end
|
32
|
+
|
33
|
+
def line_from_backtrace
|
34
|
+
if @error.backtrace && @error.backtrace[0]
|
35
|
+
@error.backtrace[0][source_pattern, 1]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_column
|
40
|
+
column = column_from_method || column_from_message
|
41
|
+
column.to_i if column
|
42
|
+
end
|
43
|
+
|
44
|
+
def column_from_method
|
45
|
+
@error.column if @error.respond_to?(:column)
|
46
|
+
end
|
47
|
+
|
48
|
+
def column_from_message
|
49
|
+
@error.message[source_pattern, 2]
|
50
|
+
end
|
51
|
+
|
52
|
+
def source_pattern
|
53
|
+
file = Regexp.escape(@file)
|
54
|
+
/^#{file}:(\d+):?(\d+)?[:\s]*/
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_message
|
58
|
+
message = @error.message.gsub(source_pattern, '')
|
59
|
+
message = message.sub(/\Aerror: /i, '')
|
60
|
+
message[0, 1] = message[0, 1].upcase
|
61
|
+
message
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module MoCo
|
2
|
+
|
3
|
+
class Compiler
|
4
|
+
|
5
|
+
def self.register(source_extension)
|
6
|
+
MoCo.register(self, source_extension)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.require_library(lib, gem_name = lib, version = nil)
|
10
|
+
@libraries ||= []
|
11
|
+
@libraries << [lib, gem_name, version]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.set_option(key, value = true)
|
15
|
+
@options ||= {}
|
16
|
+
@options[key] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.options
|
20
|
+
(@options || {}).dup
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.convert_option(key, value)
|
24
|
+
[key.to_sym, CompilerOption.convert(value)]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.compiled_extension
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :source_file
|
32
|
+
attr_reader :compiled_file
|
33
|
+
|
34
|
+
def initialize(source_file, compiled_file = nil, compiled_dir = nil)
|
35
|
+
@source_file = source_file
|
36
|
+
@compiled_file = compiled_file || compiled_filename(compiled_dir)
|
37
|
+
validate_filenames
|
38
|
+
require_libraries
|
39
|
+
end
|
40
|
+
|
41
|
+
def should_compile?
|
42
|
+
! FileUtil.up_to_date?(@compiled_file, @source_file)
|
43
|
+
end
|
44
|
+
|
45
|
+
def compile
|
46
|
+
write_compiled(compiled_text)
|
47
|
+
rescue SyntaxError, StandardError => e
|
48
|
+
error = CompileError.new(e, @source_file)
|
49
|
+
error.set_backtrace(e.backtrace)
|
50
|
+
write_compiled(error_text(error))
|
51
|
+
raise error
|
52
|
+
end
|
53
|
+
|
54
|
+
def options
|
55
|
+
self.class.options
|
56
|
+
end
|
57
|
+
|
58
|
+
def source_text
|
59
|
+
File.read(@source_file)
|
60
|
+
end
|
61
|
+
|
62
|
+
def compiled_text
|
63
|
+
raise NotImplementedError
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def compiled_filename(compiled_dir)
|
69
|
+
compiled_ext = self.class.compiled_extension
|
70
|
+
compiled_file = FileUtil.replace_extension(@source_file, compiled_ext)
|
71
|
+
FileUtil.replace_directory(compiled_file, compiled_dir)
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_filenames
|
75
|
+
if File.expand_path(@source_file) == File.expand_path(@compiled_file)
|
76
|
+
raise Error, 'The source and compiled filenames are identical'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def require_libraries
|
81
|
+
self.class.ancestors.each do |klass|
|
82
|
+
if libs = klass.instance_variable_get(:@libraries)
|
83
|
+
libs.each { |lib| require_library(*lib) }
|
84
|
+
libs.clear
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def require_library(lib, gem_name = lib, version = nil)
|
90
|
+
gem gem_name, version if version
|
91
|
+
require lib
|
92
|
+
rescue LoadError => e
|
93
|
+
if e.message !~ /\b(#{lib}|#{gem_name})\b/
|
94
|
+
raise # Another library failed to load
|
95
|
+
else
|
96
|
+
version = " -v '#{version}'" if version
|
97
|
+
raise e, "#{e.message}\nTry: gem install #{gem_name}#{version}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def write_compiled(text)
|
102
|
+
write_file(@compiled_file, text)
|
103
|
+
end
|
104
|
+
|
105
|
+
def write_file(filename, text)
|
106
|
+
FileUtil.write(filename, text)
|
107
|
+
end
|
108
|
+
|
109
|
+
def error_text(error)
|
110
|
+
error.message
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
class HtmlCompiler < Compiler
|
116
|
+
|
117
|
+
def self.compiled_extension
|
118
|
+
'html'
|
119
|
+
end
|
120
|
+
|
121
|
+
def error_text(error)
|
122
|
+
HtmlError.message(error)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
class CssCompiler < Compiler
|
128
|
+
|
129
|
+
def self.compiled_extension
|
130
|
+
'css'
|
131
|
+
end
|
132
|
+
|
133
|
+
def error_text(error)
|
134
|
+
CssError.message(error)
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
class JsCompiler < Compiler
|
140
|
+
|
141
|
+
def self.compiled_extension
|
142
|
+
'js'
|
143
|
+
end
|
144
|
+
|
145
|
+
def error_text(error)
|
146
|
+
JsError.message(error)
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|