coyote 1.1.0 → 1.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/Gemfile +8 -0
  2. data/Guardfile +21 -0
  3. data/Manifest +73 -8
  4. data/README.md +110 -1
  5. data/Rakefile +15 -5
  6. data/bin/coyote +2 -1
  7. data/coyote.gemspec +12 -12
  8. data/lib/coyote.rb +7 -45
  9. data/lib/coyote/assets.rb +24 -0
  10. data/lib/coyote/assets/base.rb +33 -0
  11. data/lib/coyote/assets/coffeescript.rb +10 -16
  12. data/lib/coyote/assets/javascript.rb +4 -4
  13. data/lib/coyote/assets/less.rb +21 -16
  14. data/lib/coyote/bundles.rb +22 -0
  15. data/lib/coyote/bundles/base.rb +112 -0
  16. data/lib/coyote/bundles/css.rb +19 -0
  17. data/lib/coyote/bundles/javascript.rb +21 -0
  18. data/lib/coyote/cli.rb +4 -5
  19. data/lib/coyote/compiler.rb +46 -0
  20. data/lib/coyote/dsl.rb +29 -0
  21. data/lib/coyote/fs_listeners.rb +42 -0
  22. data/lib/coyote/fs_listeners/base.rb +46 -0
  23. data/lib/coyote/fs_listeners/darwin.rb +4 -4
  24. data/lib/coyote/fs_listeners/linux.rb +4 -4
  25. data/lib/coyote/fs_listeners/polling.rb +2 -2
  26. data/lib/coyote/fs_listeners/windows.rb +3 -3
  27. data/lib/coyote/notifier.rb +43 -0
  28. data/lib/coyote/rake.rb +15 -36
  29. data/lib/coyote/version.rb +3 -0
  30. data/spec/assets/asset/script.coffee +0 -0
  31. data/spec/assets/asset/script.js +0 -0
  32. data/spec/assets/asset/stylesheet.less +0 -0
  33. data/spec/assets/asset/weird.asdf +0 -0
  34. data/spec/assets/assets/coffeescript/script1.coffee +4 -0
  35. data/spec/assets/assets/coffeescript/script2.coffee +1 -0
  36. data/spec/assets/assets/coffeescript/script3.js +1 -0
  37. data/spec/assets/assets/coffeescript/script4.coffee +1 -0
  38. data/spec/assets/assets/javascript/script1.js +3 -0
  39. data/spec/assets/assets/javascript/script2.js +3 -0
  40. data/spec/assets/assets/javascript/script3.js +1 -0
  41. data/spec/assets/assets/javascript/script4.js +0 -0
  42. data/spec/assets/assets/javascript/script5.js +1 -0
  43. data/spec/assets/assets/javascript/scripts/script6.js +0 -0
  44. data/spec/assets/assets/javascript/scripts/script7.js +1 -0
  45. data/spec/assets/assets/javascript/scripts2/script8.js +0 -0
  46. data/spec/assets/assets/less/stylesheet1.less +5 -0
  47. data/spec/assets/assets/less/stylesheet2.less +3 -0
  48. data/spec/assets/assets/less/stylesheet3.less +0 -0
  49. data/spec/assets/assets/less/stylesheet4.less +0 -0
  50. data/spec/assets/assets/less/stylesheet5.css +0 -0
  51. data/spec/assets/bundle/script.js +0 -0
  52. data/spec/assets/bundle/stylesheet.css +0 -0
  53. data/spec/assets/bundle/weird.asdf +0 -0
  54. data/spec/assets/bundles/base/directory1/directory1_script.js +4 -0
  55. data/spec/assets/bundles/base/directory1/directory2/directory2_script.js +0 -0
  56. data/spec/assets/bundles/base/jquery.js +1 -0
  57. data/spec/assets/bundles/base/output.js +3 -0
  58. data/spec/assets/bundles/base/script1.js +3 -0
  59. data/spec/assets/bundles/base/script2.js +3 -0
  60. data/spec/assets/bundles/base/script3.js +1 -0
  61. data/spec/assets/bundles/base/script4.js +0 -0
  62. data/spec/assets/bundles/base/script5.js +3 -0
  63. data/spec/assets/bundles/base/script6.js +3 -0
  64. data/spec/assets/bundles/css/stylesheet1.css +0 -0
  65. data/spec/assets/bundles/css/weird_file.asdf +1 -0
  66. data/spec/assets/bundles/javascript/script1.js +0 -0
  67. data/spec/assets/bundles/javascript/weird_file.asdf +1 -0
  68. data/spec/assets/compiler/javascript/output.js +3 -0
  69. data/spec/assets/compiler/javascript/script3.js +3 -0
  70. data/spec/assets/compiler/javascript/script4.js +1 -0
  71. data/spec/assets/integration/javascript/output.js +3 -0
  72. data/spec/assets/integration/javascript/script1.js +3 -0
  73. data/spec/assets/integration/javascript/script2.js +1 -0
  74. data/spec/coyote/assets/base_spec.rb +54 -0
  75. data/spec/coyote/assets/coffeescript_spec.rb +20 -0
  76. data/spec/coyote/assets/less_spec.rb +22 -0
  77. data/spec/coyote/assets_spec.rb +27 -0
  78. data/spec/coyote/bundles/base_spec.rb +123 -0
  79. data/spec/coyote/bundles/css_spec.rb +17 -0
  80. data/spec/coyote/bundles/javascript_spec.rb +17 -0
  81. data/spec/coyote/bundles_spec.rb +26 -0
  82. data/spec/coyote/cli_spec.rb +1 -0
  83. data/spec/coyote/compiler_spec.rb +113 -0
  84. data/spec/coyote/dsl_spec.rb +21 -0
  85. data/spec/coyote/fs_listeners_spec.rb +38 -0
  86. data/spec/coyote/notifier_spec.rb +25 -0
  87. data/spec/coyote_spec.rb +21 -0
  88. data/spec/spec_helper.rb +6 -0
  89. metadata +108 -29
  90. data/test/scripts/input1.js +0 -1
  91. data/test/scripts/input2.js +0 -1
  92. data/test/scripts/input3.js +0 -1
  93. data/test/scripts/input4.js +0 -1
  94. data/test/scripts/lib/input5.coffee +0 -3
  95. data/test/scripts/lib/input6.js +0 -1
  96. data/test/scripts/lib/keystone/input7.js +0 -2
  97. data/test/scripts/vendor/input8.coffee +0 -1
@@ -1,22 +1,16 @@
1
- module Coyote
2
- class CoffeeScript < Asset
1
+ module Coyote::Assets
2
+ class CoffeeScript < Base
3
+ require_pattern Regexp.new(/#=\s*require\s(.*)$/i)
4
+
3
5
  def update!
4
6
  super
5
7
  compile!
6
8
  end
7
-
8
- protected
9
-
10
- # Defines the regex pattern for scanning the contents of the
11
- # file to look for require directives
12
- def require_pattern
13
- Regexp.new(/#=\s*require\s(.*)$/i) # '#= require a/b/c.coffee' => 'a/b/c.coffee'
14
- end
15
-
16
- private
17
-
9
+
18
10
  def compile!
19
- @contents = `cat #{@absolute_path} | coffee -sc`
11
+ @contents = `cat #{@path} | coffee -sc`
12
+ self
20
13
  end
21
- end
22
- end
14
+
15
+ end
16
+ end
@@ -1,4 +1,4 @@
1
- module Coyote
2
- class JavaScript < Asset
3
- end
4
- end
1
+ module Coyote::Assets
2
+ class JavaScript < Base
3
+ end
4
+ end
@@ -1,22 +1,27 @@
1
- module Coyote
2
- class Less < Asset
3
- def update!
4
- super
5
- compile!
6
- end
1
+ module Coyote::Assets
2
+ class Less < Base
3
+ require_pattern Regexp.new(/@import\s*['|"](.*)['|"]\s*;$/i)
4
+
7
5
 
8
- protected
9
-
10
- # Defines the regex pattern for scanning the contents of the
11
- # file to look for require directives
12
- def require_pattern
13
- Regexp.new(/\/\/=\s*require\s(.*)$/i) # '//= require a/b/c.less' => 'a/b/c.less'
6
+ def find_dependencies
7
+ super
8
+ @dependencies.map! do |file|
9
+ path = File.expand_path(File.join(File.dirname(@relative_path), file))
10
+ if File.exists? path
11
+ file
12
+ elsif File.exists?(path + ".less")
13
+ file + ".less"
14
+ else
15
+ nil
16
+ end
17
+ end.compact!
14
18
  end
15
19
 
16
- private
17
20
 
18
21
  def compile!
19
- @contents = `lessc #{@absolute_path}`
22
+ @contents = `lessc #{@path}`
23
+ self
20
24
  end
21
- end
22
- end
25
+
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ require 'coyote/bundles/base'
2
+ require 'coyote/bundles/javascript'
3
+ require 'coyote/bundles/css'
4
+
5
+ module Coyote
6
+
7
+ module Bundle
8
+ def self.new(path)
9
+ Coyote::Bundles.choose(path)
10
+ end
11
+ end
12
+
13
+ module Bundles
14
+ def self.choose(path)
15
+ case File.extname(path)
16
+ when /\.js/i ; JavaScript.new(path)
17
+ when /\.css/i ; CSS.new(path)
18
+ else ; Base.new(path)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,112 @@
1
+ require 'coyote/assets'
2
+
3
+ module Coyote::Bundles
4
+ class Base
5
+
6
+ class << self
7
+ def filetypes(*args)
8
+ @filetypes ||= args.map { |arg| arg.to_s.gsub('.','') } || []
9
+ end
10
+ end
11
+
12
+ filetypes :js, :coffee, :css, :less
13
+
14
+ attr_accessor :contents
15
+ attr_reader :assets, :target
16
+
17
+ def initialize(target)
18
+ @target = target
19
+ empty!
20
+ end
21
+
22
+ def add(input)
23
+ path = File.expand_path(input)
24
+ if File.directory? path
25
+ add_directory path
26
+ elsif File.exists? path
27
+ add_file path
28
+ else
29
+ notify "Could not find #{path}", :failure
30
+ end
31
+ end
32
+
33
+
34
+ def add_file(path)
35
+ return false unless path_is_directory_or_kosher_file?(path)
36
+ asset = Coyote::Asset.new(path)
37
+ @files.delete(path)
38
+ @files << path
39
+ @assets[path] = asset
40
+ add_dependencies(asset)
41
+ end
42
+
43
+
44
+ def add_directory(dir_path)
45
+ Dir.foreach(dir_path) do |path|
46
+ next if path == '.' or path == '..'
47
+ path = "#{dir_path}/#{path}"
48
+ add path if path_is_directory_or_kosher_file?(path)
49
+ end
50
+ end
51
+
52
+
53
+ def add_dependencies(asset)
54
+ asset.dependencies.each do |dependency_path|
55
+ relative_directory = File.dirname asset.relative_path
56
+ add File.join relative_directory, dependency_path
57
+ end
58
+ end
59
+
60
+
61
+ def path_is_directory_or_kosher_file?(path)
62
+ return true if File.directory?(path)
63
+ self.class.filetypes.include? File.extname(path).gsub('.','')
64
+ end
65
+
66
+
67
+ def empty!
68
+ @assets = {}
69
+ @files = []
70
+ end
71
+
72
+
73
+ def files
74
+ @files ||= []
75
+ end
76
+
77
+
78
+ def contents
79
+ @contents ||= files.reverse.map { |path| assets[path].contents }.join
80
+ end
81
+
82
+
83
+ def update!(changed_files=[])
84
+ reset!
85
+ changed_files.each { |path| assets[path].update! }
86
+ end
87
+
88
+
89
+ def manifest
90
+ files.reverse.map { |path| "+ #{path}" }.join("\n")
91
+ end
92
+
93
+
94
+ def reset!
95
+ @contents = nil
96
+ end
97
+
98
+
99
+ def save!
100
+ File.open target, 'w+' do |file|
101
+ file.write contents
102
+ end
103
+ end
104
+
105
+
106
+ def compress!
107
+ self
108
+ end
109
+
110
+
111
+ end
112
+ end
@@ -0,0 +1,19 @@
1
+ module Coyote::Bundles
2
+ class CSS < Base
3
+ filetypes :css, :less
4
+
5
+ def compress!
6
+ notify "Compressing bundle...", :warning, :timestamp
7
+ tmp = "#{@target}.#{Time.now.to_i}.less"
8
+ File.open(tmp, 'w+') { |f| f.write contents }
9
+ self.contents = `lessc #{tmp} -x`
10
+ File.delete tmp
11
+ end
12
+
13
+ def contents
14
+ @contents ||= @assets[files.first].compile!.contents
15
+ end
16
+
17
+ end
18
+ end
19
+
@@ -0,0 +1,21 @@
1
+ require 'coyote/closure_compiler'
2
+
3
+ module Coyote::Bundles
4
+ class JavaScript < Base
5
+ filetypes :js, :coffee
6
+
7
+ def compress!
8
+ notify "Compressing bundle...", :warning, :timestamp
9
+ compiler = Coyote::ClosureCompiler.new.compile(self.contents)
10
+ if compiler.success?
11
+ self.contents = compiler.compiled_code
12
+ elsif compiler.file_too_big?
13
+ notify "Input code too big for API, creating uncompressed file\n", :failure
14
+ elsif compiler.errors
15
+ notify "Google closure API failed to compile, creating uncompressed file\n", :failure
16
+ notify "Errors:", :failure
17
+ notify "#{compiler.errors.to_s}", :failure
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/coyote/cli.rb CHANGED
@@ -1,18 +1,17 @@
1
- require 'coyote'
2
1
  require 'optparse'
3
2
 
4
3
  options = {}
5
4
  OptionParser.new do |opts|
6
- Coyote::options.each do |name|
5
+ ['compress', 'watch', 'quiet', 'version'].each do |name|
7
6
  opts.on("-#{name[0]}", "--#{name}", "#{name.capitalize}") { |o| options[name.to_sym] = o }
8
7
  end
9
8
  end.parse!
10
9
 
11
10
  if options[:version]
12
- puts "coyote #{Coyote::VERSION}"
11
+ notify "Coyote #{Coyote::VERSION}"
13
12
  exit 0
14
13
  end
15
14
 
16
- input, output = ARGV[0].split(':', 2)
15
+ input, output = ARGV.fetch(0, "").split(':', 2)
17
16
 
18
- Coyote::run(input, output, options)
17
+ Coyote.compile(input, output, options)
@@ -0,0 +1,46 @@
1
+ require 'coyote/bundles'
2
+ require 'coyote/fs_listeners'
3
+
4
+ module Coyote
5
+ class Compiler
6
+ attr_reader :bundle
7
+
8
+ def initialize(input, output, options={})
9
+ @options = options || {}
10
+ @output = output
11
+ @bundle = Coyote::Bundle.new(output)
12
+ @bundle.add(input)
13
+ end
14
+
15
+ def compile!
16
+ save!
17
+ watch if @options.fetch(:watch, false)
18
+ end
19
+
20
+
21
+ def save!
22
+ notify "\n" + @bundle.manifest unless @options.fetch(:quiet, false)
23
+ @bundle.compress! if @options.fetch(:compress, false)
24
+ @bundle.save!
25
+ notify "Saved bundle to #{@output} [#{@bundle.files.length} files]", :timestamp, :success
26
+ end
27
+
28
+
29
+ def watch
30
+ listener = Coyote::FSListener.new
31
+
32
+ listener.on_change do |changed_files|
33
+ changed_files = @bundle.files & changed_files.map {|file| File.expand_path file }
34
+
35
+ if changed_files.length > 0
36
+ notify "Detected change, recompiling...", :warning, :timestamp
37
+ @bundle.update! changed_files
38
+ save!
39
+ end
40
+ end
41
+
42
+ notify "Watching for changes to your bundle. ctrl+c to stop.", :timestamp
43
+ listener.start
44
+ end
45
+ end
46
+ end
data/lib/coyote/dsl.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'coyote'
2
+
3
+ module Coyote
4
+ class DSL
5
+ attr_accessor :input, :output, :options
6
+
7
+ def initialize(&block)
8
+ config = Struct.new(:input, :output, :options).new
9
+ config.options = {}
10
+ yield config if block_given?
11
+ instance_eval(&block)
12
+ @input = config.input
13
+ @output = config.output
14
+ @options = config.options
15
+ end
16
+
17
+ def run
18
+ Coyote.compile(@input, @output, @options)
19
+ end
20
+ end
21
+ end
22
+
23
+
24
+ def coyote(*args, &block)
25
+ runner = Coyote::DSL.new(&block)
26
+ runner.run
27
+ end
28
+
29
+
@@ -0,0 +1,42 @@
1
+ require 'coyote/fs_listeners/base'
2
+ require 'coyote/fs_listeners/darwin'
3
+ require 'coyote/fs_listeners/linux'
4
+ require 'coyote/fs_listeners/windows'
5
+ require 'coyote/fs_listeners/polling'
6
+
7
+
8
+ module Coyote
9
+
10
+ module FSListener
11
+ def self.new
12
+ Coyote::FSListeners.choose
13
+ end
14
+ end
15
+
16
+ module FSListeners
17
+ def self.choose
18
+ if mac? && Darwin.usable?
19
+ Darwin.new
20
+ elsif linux? && Linux.usable?
21
+ Linux.new
22
+ elsif windows? && Windows.usable?
23
+ Windows.new
24
+ else
25
+ notify "Using polling (Please help us to support your system better than that.)", :failure
26
+ Polling.new
27
+ end
28
+ end
29
+
30
+ def self.mac?
31
+ Config::CONFIG['target_os'] =~ /darwin/i
32
+ end
33
+
34
+ def self.linux?
35
+ Config::CONFIG['target_os'] =~ /linux/i
36
+ end
37
+
38
+ def self.windows?
39
+ Config::CONFIG['target_os'] =~ /mswin|mingw/i
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,46 @@
1
+ require 'digest'
2
+
3
+ module Coyote::FSListeners
4
+ class Base
5
+ attr_reader :last_event, :sha1_checksums_hash
6
+
7
+ def initialize
8
+ @sha1_checksums_hash = {}
9
+ update_last_event
10
+ end
11
+
12
+ def update_last_event
13
+ @last_event = Time.now
14
+ end
15
+
16
+ def modified_files(dirs, options = {})
17
+ files = potentially_modified_files(dirs, options).select { |path| File.file?(path) && file_modified?(path) && file_content_modified?(path) }
18
+ files.map! { |file| file.gsub("#{Dir.pwd}/", '') }
19
+ end
20
+
21
+ private
22
+
23
+ def potentially_modified_files(dirs, options = {})
24
+ match = options[:all] ? "**/*" : "*"
25
+ Dir.glob(dirs.map { |dir| "#{dir}#{match}" })
26
+ end
27
+
28
+ def file_modified?(path)
29
+ # Depending on the filesystem, mtime is probably only precise to the second, so round
30
+ # both values down to the second for the comparison.
31
+ File.mtime(path).to_i >= last_event.to_i
32
+ rescue
33
+ false
34
+ end
35
+
36
+ def file_content_modified?(path)
37
+ sha1_checksum = Digest::SHA1.file(path).to_s
38
+ if @sha1_checksums_hash[path] != sha1_checksum
39
+ @sha1_checksums_hash[path] = sha1_checksum
40
+ true
41
+ else
42
+ false
43
+ end
44
+ end
45
+ end
46
+ end