moco 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|