├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .travis.yml ├── .yardopts ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── guard-coffeescript.gemspec ├── lib └── guard │ ├── coffeescript.rb │ └── coffeescript │ ├── formatter.rb │ ├── inspector.rb │ ├── runner.rb │ ├── templates │ └── Guardfile │ └── version.rb └── spec ├── fixtures └── script_with_pre_processing.coffee ├── guard ├── coffeescript │ ├── formatter_spec.rb │ ├── inspector_spec.rb │ ├── runner_integration_spec.rb │ ├── runner_spec.rb │ └── version_spec.rb └── coffeescript_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | *.gem 3 | *.rbc 4 | .bundle 5 | .yardoc 6 | .idea 7 | doc 8 | Gemfile.lock 9 | .rbx 10 | 11 | ## MAC OS 12 | .DS_Store 13 | .Trashes 14 | .com.apple.timemachine.supported 15 | .fseventsd 16 | Desktop DB 17 | Desktop DF 18 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by `rubocop --auto-gen-config` 2 | # on 2014-12-06 22:01:08 +0100 using RuboCop version 0.27.1. 3 | # The point is for the user to remove these configuration records 4 | # one by one as the offenses are removed from the code base. 5 | # Note that changes in the inspected code, or installation of new 6 | # versions of RuboCop, may require this file to be generated again. 7 | 8 | # Offense count: 3 9 | Metrics/AbcSize: 10 | Max: 21 11 | 12 | # Offense count: 1 13 | Metrics/CyclomaticComplexity: 14 | Max: 7 15 | 16 | # Offense count: 189 17 | # Configuration parameters: AllowURI, URISchemes. 18 | Metrics/LineLength: 19 | Max: 182 20 | 21 | # Offense count: 4 22 | # Configuration parameters: CountComments. 23 | Metrics/MethodLength: 24 | Max: 20 25 | 26 | # Offense count: 1 27 | Metrics/PerceivedComplexity: 28 | Max: 8 29 | 30 | # Offense count: 2 31 | Style/Documentation: 32 | Enabled: false 33 | 34 | # Offense count: 8 35 | # Configuration parameters: MaxSlashes. 36 | Style/RegexpLiteral: 37 | Enabled: false 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | bundler_args: --without development 3 | rvm: 4 | - 1.9.3 5 | - 2.0.0 6 | - 2.1.0 7 | - jruby-19mode 8 | matrix: 9 | allow_failures: 10 | - rvm: rbx 11 | branches: 12 | only: 13 | - master 14 | sudo: false 15 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --readme README.md 2 | --markup markdown 3 | --markup-provider redcarpet 4 | --title 'Guard::CoffeeScript Documentation' 5 | --hide-void-return 6 | --protected 7 | --private 8 | lib/**/*.rb 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.4.0 - Februar 1, 2014 4 | 5 | - [#29](https://github.com/netzpirat/guard-coffeescript/issues/29) Add support for compiling literate coffeescript. ([@oychang][]) 6 | 7 | ## 1.3.4 - September 25, 2013 8 | 9 | - [#12](https://github.com/guard/guard-coffeescript/issues/12) Fix `:hide_success` after a failure. ([@whatcould][]) 10 | 11 | ## 1.3.3 - September 9, 2013 12 | 13 | - [#24](https://github.com/netzpirat/guard-coffeescript/issues/24) Fix selective `bare` option. 14 | 15 | ## 1.3.2 - Mai 30, 2013 16 | 17 | - [#11](https://github.com/guard/guard-coffeescript/issues/11) Improve source map support. ([@jabbawookiees][]) 18 | 19 | ## 1.3.1 - Mai 23, 2013 20 | 21 | - [#22](https://github.com/netzpirat/guard-coffeescript/pull/22) Improve source map support. ([@rogueg][]) 22 | 23 | ## 1.3.0 - March 5, 2013 24 | 25 | - Add `:source_map` option for CoffeeScript. 26 | 27 | ## 1.2.1 - November 1, 2012 28 | 29 | - Remove file creation notification. 30 | 31 | ## 1.2.0 - July 27, 2012 32 | 33 | - [#10](https://github.com/guard/guard-coffeescript/pull/10) Adding a timestamp to the success message. ([@proc][]) 34 | 35 | ## 1.1.0 - July 18, 2012 36 | 37 | - [#9](https://github.com/guard/guard-coffeescript/pull/9) Fix file removal handling. ([@caldwell][]) 38 | 39 | ## 1.0.0 - June 02, 2012 40 | 41 | - Depend on Guard 1.1.0 42 | 43 | ## 0.6.0 - April 23, 2012 44 | 45 | - [#14](https://github.com/netzpirat/guard-coffeescript/pull/14) Add option `:error_to_js`. ([@damin][]) 46 | 47 | ## 0.5.7 - April 7, 2012 48 | 49 | - Catch StandardError instead of ExecJS errors. 50 | - Do not notify other Guards when changed files is empty. 51 | 52 | ## 0.5.6 - April 7, 2012 53 | 54 | - [#12](https://github.com/netzpirat/guard-coffeescript/issues/12) Fix wrong output without `:output` option. 55 | 56 | ## 0.5.5 - March 13, 2012 57 | 58 | - [#11](https://github.com/netzpirat/guard-coffeescript/issues/11) Fix compilation when using a simple watch expression. 59 | 60 | ## 0.5.4 - December 15, 2011 61 | 62 | - Top level directories are empty, so make them simply absolute. 63 | 64 | ## 0.5.3 - December 13, 2011 65 | 66 | - [#8](https://github.com/guard/guard-coffeescript/pull/8) Follow symlinks. ([@jtolds][]) 67 | 68 | ## 0.5.2 - October 1, 2011 69 | 70 | - Depend on Guard 0.8.3 71 | - Enable `:task_has_failed` 72 | 73 | ## 0.5.0 - September 30, 2011 74 | 75 | - Support #run_on_deletion to cleanup generated JavaScript. 76 | - [#8](https://github.com/netzpirat/guard-coffeescript/pull/8) Added option to regenerate all files on startup. ([@jqr][]) 77 | 78 | ## 0.4.2 - September 15, 2011 79 | 80 | - Catch all exceptions from compiling. 81 | 82 | ## 0.4.1 - September 8, 2011 83 | 84 | - Documentation update 85 | 86 | ## 0.4.0 - September 3, 2011 87 | 88 | - Add `:noop` option for use with the Rails 3.1 asset pipeline. 89 | 90 | ## 0.3.3 - September 3, 2011 91 | 92 | - [#4](https://github.com/guard/guard-coffeescript/pull/4) Add `:priority` option. ([@akahigeg][]) 93 | 94 | ## 0.3.2 - July 25, 2011 95 | 96 | - [#3](https://github.com/guard/guard-coffeescript/pull/3) Add `:bare` option. [@jraines][] 97 | 98 | ## 0.3.1 - July 23, 2011 99 | 100 | - Make `:input` match more explicit. 101 | 102 | ## 0.3.0 - June 6, 2011 103 | 104 | - Depend on Guard 0.4 105 | 106 | ## 0.2.4 - May 24, 2011 107 | 108 | - Handle `*.js.coffee` files correct. 109 | - When omitting the output option, it'll be taken from the input option. 110 | 111 | ## 0.2.3 - May 13, 2011 112 | 113 | - [#5](https://github.com/netzpirat/guard-coffeescript/pull/5) Added formatter to colorize the success and error messages. ([@vizjerai][]) 114 | 115 | ## 0.2.2 - May 11, 2011 116 | 117 | - [#4](https://github.com/netzpirat/guard-coffeescript/pull/4) Added result messages to Guard::UI. ([@vizjerai][]) 118 | 119 | ## 0.2.1 - May 8, 2011 120 | 121 | - [#2](https://github.com/guard/guard-coffeescript/pull/2) Add `:hide_success` option. ([@hoverbird][]) 122 | 123 | ## 0.2.0 - April 4, 2011 124 | 125 | - [#2](https://github.com/netzpirat/guard-coffeescript/issues/2) Add a shortcut notation for the Guard configuration. 126 | 127 | ## 0.1.6 - January 9, 2010 128 | 129 | - Case insensitive match of compilation error messages. 130 | 131 | ## 0.1.5 - November 12, 2010 132 | 133 | - Support nested directories. 134 | - Add `:shallow` option. 135 | 136 | ## 0.1.1 - October 28, 2010 137 | 138 | - Better growl messages and print full CoffeeScript output to the console. 139 | 140 | ## 0.1.0 - October 26, 2010 141 | 142 | - Inital release 143 | 144 | [@MyD]: https://github.com/MyD 145 | [@akahigeg]: https://github.com/akahigeg 146 | [@caldwell]: https://github.com/caldwell 147 | [@damin]: https://github.com/damin 148 | [@hoverbird]: https://github.com/hoverbird 149 | [@jabbawookiees]: https://github.com/jabbawookiees 150 | [@jqr]: https://github.com/jqr 151 | [@jraines]: https://github.com/jraines 152 | [@jtolds]: https://github.com/jtolds 153 | [@oychang]: https://github.com/oychang 154 | [@proc]: https://github.com/proc 155 | [@rogueg]: https://github.com/rogueg 156 | [@vizjerai]: https://github.com/vizjerai 157 | [@whatcould]: https://github.com/whatcould 158 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contribute to Guard::Cucumber 2 | ============================= 3 | 4 | File an issue 5 | ------------- 6 | 7 | You can report bugs and feature requests to [GitHub Issues](https://github.com/netzpirat/guard-coffeescript/issues). 8 | 9 | **Please don't ask question in the issue tracker**, instead ask them on at Stack Overflow and use the 10 | [guard](http://stackoverflow.com/questions/tagged/guard) tag. 11 | 12 | Try to figure out where the issue belongs to: Is it an issue with Guard itself or with a Guard plugin you're 13 | using? 14 | 15 | When you file a bug, please try to follow these simple rules if applicable: 16 | 17 | * Make sure you've read the README carefully. 18 | * Make sure you run Guard with `bundle exec` first. 19 | * Add debug information to the issue by running Guard with the `--debug` option. 20 | * Add your `Guardfile` and `Gemfile` to the issue. 21 | * Make sure that the issue is reproducible with your description. 22 | 23 | **It's most likely that your bug gets resolved faster if you provide as much information as possible!** 24 | 25 | Development 26 | ----------- 27 | 28 | * Documentation hosted at [RubyDoc](http://rubydoc.info/github/netzpirat/guard-coffeescript/master/frames). 29 | * Source hosted at [GitHub](https://github.com/netzpirat/guard-coffeescript). 30 | 31 | Pull requests are very welcome! Please try to follow these simple rules if applicable: 32 | 33 | * Please create a topic branch for every separate change you make. 34 | * Make sure your patches are well tested. All specs run with `rake spec:portability` must pass. 35 | * Update the [Yard](http://yardoc.org/) documentation. 36 | * Update the [README](https://github.com/netzpirat/guard-coffeescript/blob/master/README.md). 37 | * Update the [CHANGELOG](https://github.com/netzpirat/guard-coffeescript/blob/master/CHANGELOG.md) for noteworthy changes. 38 | * Please **do not change** the version number. 39 | 40 | For questions please join us in our [Google group](http://groups.google.com/group/guard-dev) or on 41 | `#guard` (irc.freenode.net). 42 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec development_group: :gem_build_tools 4 | 5 | gem 'rake' 6 | 7 | # The JS runtime is needed because ExecJS searches one when the module is loaded. 8 | # This breaks travis builds even when the compiler is stubbed. 9 | platform :jruby do 10 | gem 'therubyrhino' 11 | end 12 | 13 | unless ENV['TRAVIS'] 14 | gem 'redcarpet' 15 | gem 'yard' 16 | end 17 | 18 | platforms :rbx do 19 | gem 'racc' 20 | gem 'rubysl', '~> 2.0' 21 | gem 'psych' 22 | gem 'json' 23 | end 24 | 25 | group :test do 26 | gem 'rspec', '~> 3.1' 27 | end 28 | 29 | group :development do 30 | gem 'guard-rspec' 31 | gem 'rubocop' 32 | end 33 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard 'rspec', cmd: 'bundle exec rspec' do 2 | watch(%r{spec/spec_helper\.rb}) { 'spec' } 3 | watch(%r{spec/.+_spec.rb}) 4 | watch(%r{lib/(.+).rb}) { |m| "spec/#{ m[1] }_spec.rb" } 5 | end 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2012 Michael Kessler 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. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guard::CoffeeScript [![Build Status](https://secure.travis-ci.org/netzpirat/guard-coffeescript.png)](http://travis-ci.org/netzpirat/guard-coffeescript) 2 | 3 | Guard::CoffeeScript compiles or validates your CoffeeScripts automatically when files are modified. 4 | If you're looking for a CoffeeScript merge tool, please checkout [Guard::CoffeeDripper](https://github.com/guard/guard-coffeedripper). 5 | 6 | Tested on MRI Ruby 1.9.3, 2.0.0, 2.1.0 and the latest versions of JRuby & Rubinius. 7 | 8 | If you have any questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard` 9 | (irc.freenode.net). 10 | 11 | # IMPORTANT NOTES! 12 | 13 | Especially if you are upgrading from earlier guard-coffeescript versions, please make sure you have the `:patterns` options set (example take from default Guardfile): 14 | 15 | ```ruby 16 | coffeescript_options = { 17 | input: 'app/assets/javascripts', 18 | output: 'app/assets/javascripts', 19 | patterns: [%r{^app/assets/javascripts/(.+\.(?:coffee|coffee\.md|litcoffee))$}] 20 | } 21 | 22 | guard 'coffeescript', coffeescript_options do 23 | coffeescript_options[:patterns].each { |pattern| watch(pattern) } 24 | end 25 | ``` 26 | 27 | *Please browse through the [issues](https://github.com/guard/guard-coffeescript/issues) if you have trouble, because the docs and examples below may not be up to date. Contributions are more than welcome.* 28 | 29 | 30 | ## Install 31 | 32 | The simplest way to install Guard is to use [Bundler](http://gembundler.com/). 33 | Please make sure to have [Guard](https://github.com/guard/guard) installed. 34 | 35 | Add Guard::Coffeescript to your `Gemfile`: 36 | 37 | ```ruby 38 | group :development do 39 | gem 'guard-coffeescript' 40 | end 41 | ``` 42 | Add the default Guard::Coffeescript template to your `Guardfile` by running: 43 | 44 | ```bash 45 | $ guard init coffeescript 46 | ``` 47 | 48 | ## JSON 49 | 50 | The JSON library is also required but is not explicitly stated as a gem dependency. If you're on Ruby 1.8 you'll need 51 | to install the `json` or `json_pure` gem. On Ruby 1.9, JSON is included in the standard library. 52 | 53 | ## CoffeeScript 54 | 55 | Guard::CoffeeScript uses [Ruby CoffeeScript](https://github.com/josh/ruby-coffee-script/) to compile the CoffeeScripts, 56 | that in turn uses [ExecJS](https://github.com/sstephenson/execjs) to pick the best runtime to evaluate the JavaScript. 57 | 58 | * With CRuby you want to use a V8 JavaScript Engine or Mozilla SpiderMonkey. 59 | * With JRuby you want to use the Mozilla Rhino. 60 | * On Mac OS X you want to use Apple JavaScriptCore. 61 | * On Linux or as a node.js developer you want to use Node.js (V8). 62 | * On Windows you want to use Microsoft Windows Script Host. 63 | 64 | ## JavaScript runtimes 65 | 66 | The following sections gives you a short overview of the available JavaScript runtimes and how to install it. 67 | 68 | ### Node.js (V8) 69 | 70 | You can install [node.js](http://nodejs.org/) and use its V8 engine. On OS X you may want to install it with 71 | [Homebrew](http://mxcl.github.com/homebrew/), on Linux with your package manager and on Windows you have to download and 72 | install the [executable](http://www.nodejs.org/#download). 73 | 74 | ### V8 JavaScript Engine 75 | 76 | To use the [V8 JavaScript Engine](http://code.google.com/p/v8/), simple add `therubyracer` to your `Gemfile`. 77 | The Ruby Racer acts as a bridge between Ruby and the V8 engine, that will be automatically installed by the Ruby Racer. 78 | 79 | ```ruby 80 | group :development do 81 | gem 'therubyracer' 82 | end 83 | ``` 84 | 85 | Another alternative is [Mustang](https://github.com/nu7hatch/mustang), a Ruby proxy library for the awesome Google V8 86 | JavaScript engine. Just add `mustang` to your `Gemfile`: 87 | 88 | ```ruby 89 | group :development do 90 | gem 'mustang' 91 | end 92 | ``` 93 | 94 | ### Mozilla SpiderMonkey 95 | 96 | To use [Mozilla SpiderMonkey](https://developer.mozilla.org/en/SpiderMonkey), simple add `johnson` to your `Gemfile`. 97 | Johnson embeds the Mozilla SpiderMonkey JavaScript runtime as a C extension. 98 | 99 | ```ruby 100 | group :development do 101 | gem 'johnson' 102 | end 103 | ``` 104 | 105 | ### Mozilla Rhino 106 | 107 | If you're using JRuby, you can embed the [Mozilla Rhino](http://www.mozilla.org/rhino/) runtime by adding `therubyrhino` 108 | to your `Gemfile`: 109 | 110 | ```ruby 111 | group :development do 112 | gem 'therubyrhino' 113 | end 114 | ``` 115 | 116 | ### Apple JavaScriptCore 117 | 118 | [JavaScriptCore](http://developer.apple.com/library/mac/#documentation/Carbon/Reference/WebKit_JavaScriptCore_Ref/index.html) 119 | is Safari's Nitro JavaScript Engine and only usable on Mac OS X. You don't have to install anything, because 120 | JavaScriptCore is already packaged with Mac OS X. 121 | 122 | ### Microsoft Windows Script Host 123 | 124 | [Microsoft Windows Script Host](http://msdn.microsoft.com/en-us/library/9bbdkx3k.aspx) is available on any Microsoft 125 | Windows operating systems. 126 | 127 | ## Usage 128 | 129 | Please read the [Guard usage documentation](https://github.com/guard/guard#readme). 130 | 131 | ## Guardfile 132 | 133 | Guard::CoffeeScript can be adapted to all kind of projects. Please read the 134 | [Guard documentation](https://github.com/guard/guard#readme) for more information about the Guardfile DSL. 135 | 136 | ### Ruby project 137 | 138 | In a Ruby project you want to configure your input and output directories. 139 | 140 | ```ruby 141 | guard 'coffeescript', :input => 'coffeescripts', :output => 'javascripts' 142 | ``` 143 | 144 | If your output directory is the same as the input directory, you can simply skip it: 145 | 146 | ```ruby 147 | guard 'coffeescript', :input => 'javascripts' 148 | ``` 149 | 150 | ### Rails app with the asset pipeline 151 | 152 | With the introduction of the [asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html) in Rails 3.1 there is 153 | no need to compile your CoffeeScripts with this Guard. 154 | 155 | However, if you would still like to have feedback on the validation of your CoffeeScripts 156 | (preferably with a Growl notification) directly after you save a change, then you can still 157 | use this Guard and simply skip generation of the output file: 158 | 159 | ```ruby 160 | guard 'coffeescript', :input => 'app/assets/javascripts', :noop => true 161 | ``` 162 | 163 | This give you a faster compilation feedback compared to making a subsequent request to your Rails application. If you 164 | just want to be notified when an error occurs you can hide the success compilation message: 165 | 166 | ```ruby 167 | guard 'coffeescript', :input => 'app/assets/javascripts', :noop => true, :hide_success => true 168 | ``` 169 | 170 | ### Rails app without the asset pipeline 171 | 172 | Without the asset pipeline you just define an input and output directory like within a normal Ruby project: 173 | 174 | ```ruby 175 | guard 'coffeescript', :input => 'app/coffeescripts', :output => 'public/javascripts' 176 | ``` 177 | 178 | ## Options 179 | 180 | There following options can be passed to Guard::CoffeeScript: 181 | 182 | ```ruby 183 | :input => 'coffeescripts' # Relative path to the input directory. 184 | # Files will be added that match a suffix 185 | # of /(.+\.(coffee|coffee\.md|litcoffee)) 186 | # default: nil 187 | 188 | :output => 'javascripts' # Relative path to the output directory. 189 | # default: the path given with the :input option 190 | 191 | :noop => true # No operation: do not write an output file. 192 | # default: false 193 | 194 | :bare => true # Compile without the top-level function wrapper. 195 | # Provide either a boolean value or an Array of 196 | # filenames. 197 | # default: false 198 | 199 | :shallow => true # Do not create nested output directories. 200 | # default: false 201 | 202 | :source_map => true # Do create the source map file. 203 | # default: false 204 | 205 | :source_root => 'coffeescripts' # Root path for coffeescript sources. 206 | # Used in source map to determine root URL for 207 | # all sources 208 | # default: nil (using the `:input` directory) 209 | 210 | :pre_process => Proc.new {|f|...} # Proc or lambda to pre process the script content before compilation. 211 | # Accept one argument which is the file content. 212 | # default: nil 213 | 214 | :hide_success => true # Disable successful compilation messages. 215 | # default: false 216 | 217 | :all_on_start => true # Regenerate all files on startup 218 | # default: false 219 | 220 | :error_to_js => true # Print the Coffeescript error message directly in 221 | # the JavaScript file 222 | # default: false 223 | ``` 224 | 225 | The `:source_map` option needs at least CoffeeScript version 1.6.1. 226 | 227 | ### Output short notation 228 | 229 | In addition to the standard configuration, this Guard has a short notation for configure projects with a single input 230 | and output directory. This notation creates a watcher from the `:input` parameter that matches all CoffeeScript files 231 | under the given directory and you don't have to specify a watch regular expression. 232 | 233 | ```ruby 234 | guard 'coffeescript', :input => 'javascripts' 235 | ``` 236 | 237 | ### Selective bare option 238 | 239 | The `:bare` option can take a boolean value that indicates if all scripts should be compiled without the top-level 240 | function wrapper. 241 | 242 | ```ruby 243 | :bare => true 244 | ``` 245 | 246 | But you can also pass an Array of filenames that should be compiled without the top-level function wrapper. The path of 247 | the file to compile is ignored, so the list of filenames should not contain any path information: 248 | 249 | ```ruby 250 | :bare => %w{ a.coffee b.coffee } 251 | ``` 252 | 253 | In the above example, all `a.coffee` and `b.coffee` files will be compiled with option `:bare => true` and all other 254 | files with option `:bare => false`. 255 | 256 | ### Nested directories 257 | 258 | The Guard detects by default nested directories and creates these within the output directory. The detection is based on 259 | the match of the watch regular expression: 260 | 261 | A file 262 | 263 | ```ruby 264 | /app/coffeescripts/ui/buttons/toggle_button.coffee 265 | ``` 266 | 267 | that has been detected by the watch 268 | 269 | ```ruby 270 | watch(%r{^app/coffeescripts/(.+\.coffee)$}) 271 | ``` 272 | 273 | with an output directory of 274 | 275 | ```ruby 276 | :output => 'public/javascripts/compiled' 277 | ``` 278 | 279 | will be compiled to 280 | 281 | ```ruby 282 | public/javascripts/compiled/ui/buttons/toggle_button.js 283 | ``` 284 | 285 | Note the parenthesis around the `.+\.coffee`. This enables Guard::CoffeeScript to place the full path that was matched 286 | inside the parenthesis into the proper output directory. 287 | 288 | This behavior can be switched off by passing the option `:shallow => true` to the Guard, so that all JavaScripts will be 289 | compiled directly to the output directory. 290 | 291 | ### Multiple source directories 292 | 293 | The Guard short notation 294 | 295 | ```ruby 296 | guard 'coffeescript', :input => 'app/coffeescripts', :output => 'public/javascripts/compiled' 297 | ``` 298 | 299 | will be internally converted into the standard notation by adding `/(.+\.coffee)` to the `input` option string and 300 | create a Watcher that is equivalent to: 301 | 302 | ```ruby 303 | guard 'coffeescript', :output => 'public/javascripts/compiled' do 304 | watch(%r{^app/coffeescripts/(.+\.coffee)$}) 305 | end 306 | ``` 307 | 308 | To add a second source directory that will be compiled to the same output directory, just add another watcher: 309 | 310 | ```ruby 311 | guard 'coffeescript', :input => 'app/coffeescripts', :output => 'public/javascripts/compiled' do 312 | watch(%r{lib/coffeescripts/(.+\.coffee)}) 313 | end 314 | ``` 315 | 316 | which is equivalent to: 317 | 318 | ```ruby 319 | guard 'coffeescript', :output => 'public/javascripts/compiled' do 320 | watch(%r{app/coffeescripts/(.+\.coffee)}) 321 | watch(%r{lib/coffeescripts/(.+\.coffee)}) 322 | end 323 | ``` 324 | 325 | ## Issues 326 | 327 | You can report issues and feature requests to [GitHub Issues](https://github.com/netzpirat/guard-coffeescript/issues). Try to figure out 328 | where the issue belongs to: Is it an issue with Guard itself or with a Guard::Cucumber? Please don't 329 | ask question in the issue tracker, instead join us in our [Google group](http://groups.google.com/group/guard-dev) or on 330 | `#guard` (irc.freenode.net). 331 | 332 | When you file an issue, please try to follow to these simple rules if applicable: 333 | 334 | * Make sure you run Guard with `bundle exec` first. 335 | * Add debug information to the issue by running Guard with the `--debug` option. 336 | * Add your `Guardfile` and `Gemfile` to the issue. 337 | * Make sure that the issue is reproducible with your description. 338 | 339 | ## Development 340 | 341 | - Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/guard-coffeescript/master/frames). 342 | - Source hosted at [GitHub](https://github.com/netzpirat/guard-coffeescript). 343 | 344 | Pull requests are very welcome! Please try to follow these simple rules if applicable: 345 | 346 | * Please create a topic branch for every separate change you make. 347 | * Make sure your patches are well tested. 348 | * Update the [Yard](http://yardoc.org/) documentation. 349 | * Update the README. 350 | * Update the CHANGELOG for noteworthy changes. 351 | * Please **do not change** the version number. 352 | 353 | For questions please join us in our [Google group](http://groups.google.com/group/guard-dev) or on 354 | `#guard` (irc.freenode.net). 355 | 356 | ## Author 357 | 358 | Developed by Michael Kessler, sponsored by [FlinkFinger](http://www.flinkfinger.com). 359 | 360 | If you like Guard::CoffeeScript, you can watch the repository at [GitHub](https://github.com/netzpirat/guard-coffeescript) and 361 | follow [@netzpirat](https://twitter.com/#!/netzpirat) on Twitter for project updates. 362 | 363 | ## Contributors 364 | 365 | See the GitHub list of [contributors](https://github.com/netzpirat/guard-coffeescript/contributors). 366 | 367 | ## Acknowledgment 368 | 369 | * [Jeremy Ashkenas](http://twitter.com/#!/jashkenas) for [CoffeeScript](http://jashkenas.github.com/coffee-script/), 370 | that little language that compiles into JavaScript and makes me enjoy the frontend. 371 | * The [Guard Team](https://github.com/guard/guard/contributors) for giving us such a nice piece of software 372 | that is so easy to extend, one *has* to make a plugin for it! 373 | * All the authors of the numerous [Guards](https://github.com/guard) available for making the Guard ecosystem 374 | so much growing and comprehensive. 375 | 376 | ## License 377 | 378 | (The MIT License) 379 | 380 | Copyright (c) 2010-2013 Michael Kessler 381 | 382 | Permission is hereby granted, free of charge, to any person obtaining 383 | a copy of this software and associated documentation files (the 384 | 'Software'), to deal in the Software without restriction, including 385 | without limitation the rights to use, copy, modify, merge, publish, 386 | distribute, sublicense, and/or sell copies of the Software, and to 387 | permit persons to whom the Software is furnished to do so, subject to 388 | the following conditions: 389 | 390 | The above copyright notice and this permission notice shall be 391 | included in all copies or substantial portions of the Software. 392 | 393 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 394 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 395 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 396 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 397 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 398 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 399 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 400 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | def ci? 4 | ENV['CI'] == 'true' 5 | end 6 | 7 | default_tasks = [] 8 | 9 | require 'rspec/core/rake_task' 10 | default_tasks << RSpec::Core::RakeTask.new(:spec) do |t| 11 | t.verbose = false unless ENV.key?('CI') 12 | end 13 | 14 | unless ci? 15 | require 'rubocop/rake_task' 16 | default_tasks << RuboCop::RakeTask.new(:rubocop) 17 | end 18 | 19 | task default: default_tasks.map(&:name) 20 | 21 | namespace(:spec) do 22 | desc 'Run all specs on multiple ruby versions (requires rvm)' 23 | task(:portability) do 24 | travis_config_file = File.expand_path('../.travis.yml', __FILE__) 25 | begin 26 | travis_options ||= YAML.load_file(travis_config_file) 27 | rescue => ex 28 | puts "Travis config file '#{travis_config_file}' could not be found: #{ex.message}" 29 | return 30 | end 31 | 32 | travis_options['rvm'].each do |version| 33 | system <<-BASH 34 | bash -c 'source ~/.rvm/scripts/rvm; 35 | rvm #{version}; 36 | ruby_version_string_size=`ruby -v | wc -m` 37 | echo; 38 | for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done 39 | echo; 40 | echo "`ruby -v`"; 41 | for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done 42 | echo; 43 | RBXOPT="-Xrbc.db" bundle install; 44 | RBXOPT="-Xrbc.db" bundle exec rspec spec -f doc 2>&1;' 45 | BASH 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /guard-coffeescript.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $LOAD_PATH.push File.expand_path('../lib', __FILE__) 3 | require 'guard/coffeescript/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'guard-coffeescript' 7 | s.version = Guard::CoffeeScriptVersion::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ['Michael Kessler'] 10 | s.email = ['michi@flinkfinger.com'] 11 | s.homepage = 'http://github.com/guard/guard-coffeescript' 12 | s.summary = 'Guard gem for CoffeeScript' 13 | s.description = 'Guard::CoffeeScript automatically generates your JavaScripts from your CoffeeScripts' 14 | s.license = 'MIT' 15 | 16 | s.required_rubygems_version = '>= 1.3.6' 17 | s.rubyforge_project = 'guard-coffeescript' 18 | 19 | s.add_dependency 'guard', '>= 2.1.0' 20 | s.add_dependency 'guard-compat', '~> 1.1' 21 | s.add_dependency 'coffee-script', '>= 2.2.0' 22 | 23 | s.add_development_dependency 'bundler' 24 | 25 | s.files = Dir.glob('{lib}/**/*') + %w(LICENSE README.md) 26 | s.require_path = 'lib' 27 | end 28 | -------------------------------------------------------------------------------- /lib/guard/coffeescript.rb: -------------------------------------------------------------------------------- 1 | require 'guard/compat/plugin' 2 | 3 | module Guard 4 | # The CoffeeScript guard that gets notifications about the following 5 | # Guard events: `start`, `stop`, `reload`, `run_all` and `run_on_change`. 6 | # 7 | class CoffeeScript < Plugin 8 | require 'guard/coffeescript/formatter' 9 | require 'guard/coffeescript/inspector' 10 | require 'guard/coffeescript/runner' 11 | 12 | DEFAULT_OPTIONS = { 13 | bare: false, 14 | shallow: false, 15 | hide_success: false, 16 | noop: false, 17 | error_to_js: false, 18 | all_on_start: false, 19 | source_map: false 20 | } 21 | 22 | # Initialize Guard::CoffeeScript. 23 | # 24 | 25 | # @param [Hash] options the options for the Guard 26 | # @option options [String] :input the input directory 27 | # @option options [String] :output the output directory 28 | # @option options [Array] :watchers the watchers in the Guard block 29 | # @option options [Boolean] :bare do not wrap the output in a top level function 30 | # @option options [Boolean] :shallow do not create nested directories 31 | # @option options [Boolean] :hide_success hide success message notification 32 | # @option options [Boolean] :all_on_start generate all JavaScripts files on start 33 | # @option options [Boolean] :noop do not generate an output file 34 | # @option options [Boolean] :source_map generate the source map files 35 | # 36 | 37 | attr_reader :patterns 38 | 39 | def initialize(options = {}) 40 | defaults = DEFAULT_OPTIONS.clone 41 | 42 | @patterns = options.dup.delete(:patterns) || [] 43 | 44 | msg = 'Invalid :patterns argument. Expected: Array, got %s' 45 | fail ArgumentError, format(msg, @patterns.inspect) unless @patterns.is_a?(Array) 46 | 47 | msg = ':input option not provided (see current template Guardfile)' 48 | fail msg unless options[:input] 49 | 50 | options[:output] = options[:input] unless options[:output] 51 | 52 | super(defaults.merge(options)) 53 | end 54 | 55 | # Gets called once when Guard starts. 56 | # 57 | # @raise [:task_has_failed] when stop has failed 58 | # 59 | def start 60 | run_all if options[:all_on_start] 61 | end 62 | 63 | # Gets called when all files should be regenerated. 64 | # 65 | # @raise [:task_has_failed] when stop has failed 66 | # 67 | def run_all 68 | found = Dir.glob('**{,/*/**}/*.{coffee,coffee.md,litcoffee}') 69 | found.select! do |file| 70 | @patterns.any? do |pattern| 71 | pattern.match(file) 72 | end 73 | end 74 | 75 | run_on_modifications(found) 76 | end 77 | 78 | # Gets called when watched paths and files have changes. 79 | # 80 | # @param [Array] paths the changed paths and files 81 | # @raise [:task_has_failed] when stop has failed 82 | # 83 | def run_on_modifications(paths) 84 | _changed_files, success = Runner.run(Inspector.clean(paths), @patterns, options) 85 | 86 | throw :task_has_failed unless success 87 | end 88 | 89 | # Called on file(s) deletions that the Guard watches. 90 | # 91 | # @param [Array] paths the deleted files or paths 92 | # @raise [:task_has_failed] when run_on_change has failed 93 | # 94 | def run_on_removals(paths) 95 | Runner.remove(Inspector.clean(paths, missing_ok: true), @patterns, options) 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/guard/coffeescript/formatter.rb: -------------------------------------------------------------------------------- 1 | module Guard 2 | class CoffeeScript 3 | # The Guard::CoffeeScript formatter collects console and 4 | # system notification methods and enhances them with 5 | # some color information. 6 | # 7 | module Formatter 8 | class << self 9 | # Print an info message to the console. 10 | # 11 | # @param [String] message the message to print 12 | # @param [Hash] options the output options 13 | # @option options [Boolean] :reset reset the UI 14 | # 15 | def info(message, options = {}) 16 | Compat::UI.info(message, options) 17 | end 18 | 19 | # Print a debug message to the console. 20 | # 21 | # @param [String] message the message to print 22 | # @param [Hash] options the output options 23 | # @option options [Boolean] :reset reset the UI 24 | # 25 | def debug(message, options = {}) 26 | Compat::UI.debug(message, options) 27 | end 28 | 29 | # Print a red error message to the console. 30 | # 31 | # @param [String] message the message to print 32 | # @param [Hash] options the output options 33 | # @option options [Boolean] :reset reset the UI 34 | # 35 | def error(message, options = {}) 36 | Compat::UI.error(color(message, ';31'), options) 37 | end 38 | 39 | # Print a green success message to the console. 40 | # 41 | # @param [String] message the message to print 42 | # @param [Hash] options the output options 43 | # @option options [Boolean] :reset reset the UI 44 | # 45 | def success(message, options = {}) 46 | stamped_message = "#{Time.now.strftime('%r')} #{message}" 47 | Compat::UI.info(color(stamped_message, ';32'), options) 48 | end 49 | 50 | # Outputs a system notification. 51 | # 52 | # @param [String] message the message to print 53 | # @param [Hash] options the output options 54 | # @option options [Symbol, String] :image the image to use, either :failed, :pending or :success, or an image path 55 | # @option options [String] :title the title of the system notification 56 | # 57 | def notify(message, options = {}) 58 | Compat::UI.notify(message, options) 59 | end 60 | 61 | private 62 | 63 | # Print a info message to the console. 64 | # 65 | # @param [String] text the text to colorize 66 | # @param [String] color_code the color code 67 | # 68 | def color(text, color_code) 69 | Compat::UI.send(:color_enabled?) ? "\e[0#{ color_code }m#{ text }\e[0m" : text 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/guard/coffeescript/inspector.rb: -------------------------------------------------------------------------------- 1 | module Guard 2 | class CoffeeScript 3 | # The inspector verifies of the changed paths are valid 4 | # for Guard::CoffeeScript. 5 | # 6 | module Inspector 7 | class << self 8 | # Clean the changed paths and return only valid 9 | # CoffeeScript files. 10 | # 11 | # @param [Array] paths the changed paths 12 | # @param [Hash] options the clean options 13 | # @option options [String] :missing_ok don't remove missing files from list 14 | # @return [Array] the valid spec files 15 | # 16 | def clean(paths, options = {}) 17 | paths.uniq! 18 | paths.compact! 19 | paths.select { |p| coffee_file?(p, options) } 20 | end 21 | 22 | private 23 | 24 | # Tests if the file is valid. 25 | # 26 | # @param [String] path the file 27 | # @param [Hash] options the clean options 28 | # @option options [String] :missing_ok don't remove missing files from list 29 | # @return [Boolean] when the file valid 30 | # 31 | def coffee_file?(path, options) 32 | path =~ /\.(?:coffee|coffee\.md|litcoffee)$/ && (options[:missing_ok] || File.exist?(path)) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/guard/coffeescript/runner.rb: -------------------------------------------------------------------------------- 1 | require 'coffee_script' 2 | 3 | module Guard 4 | class CoffeeScript 5 | module Runner 6 | class << self 7 | attr_accessor :last_run_failed 8 | 9 | # The CoffeeScript runner handles the CoffeeScript compilation, 10 | # creates nested directories and the output file, writes the result 11 | # to the console and triggers optional system notifications. 12 | # 13 | # @param [Array] files the spec files or directories 14 | # @param [Array] patterns the patterns in the block 15 | # @param [Hash] options the options for the execution 16 | # @option options [String] :input the input directory 17 | # @option options [String] :output the output directory 18 | # @option options [Boolean] :bare do not wrap the output in a top level function 19 | # @option options [Boolean] :shallow do not create nested directories 20 | # @option options [Boolean] :hide_success hide success message notification 21 | # @option options [Boolean] :noop do not generate an output file 22 | # @option options [Boolean] :source_map generate the source map files 23 | # @option options [Proc] :pre_process Proc or lambda to run with the file content before compilation 24 | # @return [Array, Boolean>] the result for the compilation run 25 | # 26 | def run(files, patterns, options = {}) 27 | notify_start(files, options) 28 | changed_files, errors = compile_files(files, patterns, options) 29 | notify_result(changed_files, errors, options) 30 | [changed_files, errors.empty?] 31 | end 32 | 33 | # The remove function deals with CoffeeScript file removal by 34 | # locating the output javascript file and removing it. 35 | # 36 | # @param [Array] files the spec files or directories 37 | # @param [Array] patterns the patterns in the block 38 | # @param [Hash] options the options for the removal 39 | # @option options [String] :output the output directory 40 | # @option options [Boolean] :shallow do not create nested directories 41 | # 42 | def remove(files, patterns, options = {}) 43 | removed_files = [] 44 | directories = detect_nested_directories(files, patterns, options) 45 | 46 | directories.each do |directory, scripts| 47 | scripts.each do |file| 48 | javascript = javascript_file_name(file, directory) 49 | if File.exist?(javascript) 50 | FileUtils.remove_file(javascript) 51 | removed_files << javascript 52 | end 53 | end 54 | end 55 | 56 | return unless removed_files.length > 0 57 | 58 | message = "Removed #{ removed_files.join(', ') }" 59 | Formatter.success(message) 60 | Formatter.notify(message, title: 'CoffeeScript results') 61 | end 62 | 63 | private 64 | 65 | # Generates a start compilation notification. 66 | # 67 | # @param [Array] files the generated files 68 | # @param [Hash] options the options for the execution 69 | # @option options [Boolean] :noop do not generate an output file 70 | # 71 | def notify_start(files, options) 72 | message = options[:message] || (options[:noop] ? 'Verify ' : 'Compile ') + files.join(', ') 73 | Formatter.info(message, reset: true) 74 | end 75 | 76 | # Compiles all CoffeeScript files and writes the JavaScript files. 77 | # 78 | # @param [Array] files the files to compile 79 | # @param [Hash] options the options for the execution 80 | # @return [Array, Array] the result for the compilation run 81 | # 82 | def compile_files(files, patterns, options) 83 | errors = [] 84 | changed_files = [] 85 | directories = detect_nested_directories(files, patterns, options) 86 | 87 | directories.each do |directory, scripts| 88 | scripts.each do |file| 89 | begin 90 | js, map = compile(file, options) 91 | changed_files << write_javascript_file(js, map, file, directory, options) 92 | 93 | rescue RuntimeError, ::CoffeeScript::EngineError, ::CoffeeScript::CompilationError => e 94 | error_message = file + ': ' + e.message.to_s 95 | 96 | if options[:error_to_js] 97 | js_error_message = "throw \"#{ error_message }\";" 98 | changed_files << write_javascript_file(js_error_message, nil, file, directory, options) 99 | end 100 | 101 | errors << error_message 102 | Formatter.error(error_message) 103 | end 104 | end 105 | end 106 | 107 | [changed_files.flatten.compact, errors] 108 | end 109 | 110 | # Compile the CoffeeScript and generate the source map. 111 | # 112 | # @param [String] filename the CoffeeScript file n 113 | # @param [Hash] options the options for the execution 114 | # @option options [Boolean] :source_map generate the source map files 115 | # @option options [Proc] :pre_process Proc or lambda to run with the file content before compilation 116 | # @return [Array] the JavaScript source and the source map 117 | # 118 | def compile(filename, options) 119 | file = File.read(filename) 120 | file = options[:pre_process].call(file) if options[:pre_process].is_a? Proc 121 | file_options = options_for_file(filename, options) 122 | 123 | if options[:source_map] 124 | file_options.merge! options_for_source_map(filename, options) 125 | result = ::CoffeeScript.compile(file, file_options) 126 | fail 'CoffeeScript.compile returned nil' if result.nil? 127 | js, map = result['js'], result['v3SourceMap'] 128 | else 129 | js = ::CoffeeScript.compile(file, file_options) 130 | end 131 | 132 | [js, map] 133 | end 134 | 135 | # Gets the CoffeeScript compilation options. 136 | # 137 | # @param [String] file the CoffeeScript file 138 | # @param [Hash] options the options for the execution of all files 139 | # @option options [Boolean] :bare do not wrap the output in a top level function 140 | # @return [Hash] options for a particular file's execution 141 | # 142 | def options_for_file(file, options) 143 | file_options = options.clone 144 | 145 | # if :bare was provided an array of filenames, check for file's inclusion 146 | if file_options[:bare].respond_to? :include? 147 | filename = file[/([^\/]*)\.(?:coffee|coffee\.md|litcoffee)$/] 148 | file_options[:bare] = file_options[:bare].include?(filename) 149 | end 150 | 151 | file_options[:literate] = true if file[/\.(?:coffee\.md|litcoffee)$/] 152 | 153 | file_options 154 | end 155 | 156 | # Gets the CoffeeScript source map options. 157 | # 158 | # @param [String] filename the CoffeeScript filename 159 | # @param [Hash] options the options for the execution 160 | # 161 | def options_for_source_map(filename, options) 162 | # if :input was provided, make all filenames relative to that 163 | filename = Pathname.new(filename).relative_path_from(Pathname.new(options[:input])).to_s if options[:input] 164 | 165 | { 166 | sourceMap: true, 167 | generatedFile: filename.gsub(/((?:js\.)?(?:coffee|coffee\.md|litcoffee))$/, 'js'), 168 | sourceFiles: [filename], 169 | sourceRoot: options[:source_root] || options[:input] || '' 170 | } 171 | end 172 | 173 | # Analyzes the CoffeeScript compilation output and creates the 174 | # nested directories and writes the output file. 175 | # 176 | # @param [String] js the JavaScript content 177 | # @param [String] map the source map content 178 | # @param [String] file the CoffeeScript file name 179 | # @param [String] directory the output directory 180 | # @param [Hash] options the options for the execution 181 | # @option options [Boolean] :noop do not generate an output file 182 | # @return [String] the JavaScript file name 183 | # 184 | def write_javascript_file(js, map, file, directory, options) 185 | directory = Dir.pwd if !directory || directory.empty? 186 | filename = javascript_file_name(file, directory) 187 | 188 | return filename if options[:noop] 189 | 190 | if options[:source_map] 191 | map_name = filename + '.map' 192 | js += "\n//# sourceMappingURL=#{File.basename(map_name)}\n" 193 | end 194 | 195 | FileUtils.mkdir_p(File.expand_path(directory)) unless File.directory?(directory) 196 | File.open(File.expand_path(filename), 'w') { |f| f.write(js) } 197 | 198 | if options[:source_map] 199 | File.open(File.expand_path(map_name), 'w') { |f| f.write(map) } 200 | [filename, map_name] 201 | else 202 | filename 203 | end 204 | end 205 | 206 | # Calculates the output filename from the coffescript filename and 207 | # the output directory 208 | # 209 | # @param [string] file the CoffeeScript file name 210 | # @param [String] directory the output directory 211 | # 212 | def javascript_file_name(file, directory) 213 | File.join(directory, File.basename(file.gsub(/((?:js\.)?(?:coffee|coffee\.md|litcoffee))$/, 'js'))) 214 | end 215 | 216 | # Detects the output directory for each CoffeeScript file. Builds 217 | # the product of all patterns and assigns to each directory 218 | # the files to which it belongs to. 219 | # 220 | # @param [Array] files the CoffeeScript files 221 | # @param [Array] patterns the patterns in the block 222 | # @param [Hash] options the options for the execution 223 | # @option options [String] :output the output directory 224 | # @option options [Boolean] :shallow do not create nested directories 225 | # 226 | def detect_nested_directories(files, patterns, options) 227 | return { options[:output] => files } if options[:shallow] 228 | 229 | directories = {} 230 | 231 | patterns.product(files).each do |pattern, file| 232 | next unless (matches = file.match(pattern)) 233 | 234 | target = matches[1] ? File.join(options[:output], File.dirname(matches[1])).gsub(/\/\.$/, '') : options[:output] || File.dirname(file) 235 | if directories[target] 236 | directories[target] << file 237 | else 238 | directories[target] = [file] 239 | end 240 | end 241 | 242 | directories 243 | end 244 | 245 | # Writes console and system notifications about the result of the compilation. 246 | # 247 | # @param [Array] changed_files the changed JavaScript files 248 | # @param [Array] errors the error messages 249 | # @param [Hash] options the options for the execution 250 | # @option options [Boolean] :hide_success hide success message notification 251 | # @option options [Boolean] :noop do not generate an output file 252 | # 253 | def notify_result(changed_files, errors, options = {}) 254 | if !errors.empty? 255 | self.last_run_failed = true 256 | Formatter.notify(errors.join("\n"), title: 'CoffeeScript results', image: :failed, priority: 2) 257 | elsif !options[:hide_success] || last_run_failed 258 | self.last_run_failed = false 259 | message = "Successfully #{ options[:noop] ? 'verified' : 'generated' } #{ changed_files.join(', ') }" 260 | Formatter.success(message) 261 | Formatter.notify(message, title: 'CoffeeScript results') 262 | end 263 | end 264 | end 265 | end 266 | end 267 | end 268 | -------------------------------------------------------------------------------- /lib/guard/coffeescript/templates/Guardfile: -------------------------------------------------------------------------------- 1 | coffeescript_options = { 2 | input: 'app/assets/javascripts', 3 | output: 'app/assets/javascripts', 4 | patterns: [%r{^app/assets/javascripts/(.+\.(?:coffee|coffee\.md|litcoffee))$}] 5 | } 6 | 7 | guard 'coffeescript', coffeescript_options do 8 | coffeescript_options[:patterns].each { |pattern| watch(pattern) } 9 | end 10 | -------------------------------------------------------------------------------- /lib/guard/coffeescript/version.rb: -------------------------------------------------------------------------------- 1 | module Guard 2 | module CoffeeScriptVersion 3 | VERSION = '2.0.1' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/script_with_pre_processing.coffee: -------------------------------------------------------------------------------- 1 | alert 'raw coffee beans' -------------------------------------------------------------------------------- /spec/guard/coffeescript/formatter_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Guard::CoffeeScript::Formatter do 2 | let(:formatter) { Guard::CoffeeScript::Formatter } 3 | let(:ui) { Guard::Compat::UI } 4 | 5 | describe '.info' do 6 | it 'shows an info message' do 7 | expect(ui).to receive(:info).with('Info message', reset: true) 8 | formatter.info('Info message', reset: true) 9 | end 10 | end 11 | 12 | describe '.debug' do 13 | it 'shows a debug message' do 14 | expect(ui).to receive(:debug).with('Debug message', reset: true) 15 | formatter.debug('Debug message', reset: true) 16 | end 17 | end 18 | 19 | describe '.error' do 20 | it 'shows a colorized error message' do 21 | expect(ui).to receive(:error).with("\e[0;31mError message\e[0m", reset: true) 22 | formatter.error('Error message', reset: true) 23 | end 24 | end 25 | 26 | describe '.success' do 27 | it 'shows a colorized success message with a timestamp' do 28 | expected_success_message = %r{^\e\[0;32m\d{2}:\d{2}:\d{2} (AM|PM) Success message\e\[0m$} 29 | expect(ui).to receive(:info).with(expected_success_message, reset: true) 30 | formatter.success('Success message', reset: true) 31 | end 32 | end 33 | 34 | describe '.notify' do 35 | it 'shows an info message' do 36 | expect(ui).to receive(:notify).with('Notify message', image: :failed) 37 | formatter.notify('Notify message', image: :failed) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/guard/coffeescript/inspector_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Guard::CoffeeScript::Inspector do 2 | let(:inspector) { Guard::CoffeeScript::Inspector } 3 | 4 | describe 'clean' do 5 | it 'removes duplicate files' do 6 | expect(File).to receive(:exist?).with('a.coffee').and_return true 7 | expect(File).to receive(:exist?).with('b.coffee.md').and_return true 8 | expect(File).to receive(:exist?).with('c.litcoffee').and_return true 9 | expect(inspector.clean(['a.coffee', 'a.coffee', 'b.coffee.md', 'b.coffee.md', 'c.litcoffee', 'c.litcoffee'])) 10 | .to eq(['a.coffee', 'b.coffee.md', 'c.litcoffee']) 11 | end 12 | 13 | it 'remove nil files' do 14 | expect(File).to receive(:exist?).with('a.coffee').and_return true 15 | expect(File).to receive(:exist?).with('b.coffee.md').and_return true 16 | expect(File).to receive(:exist?).with('c.litcoffee').and_return true 17 | expect(inspector.clean(['a.coffee', 'b.coffee.md', 'c.litcoffee', nil])) 18 | .to eq(['a.coffee', 'b.coffee.md', 'c.litcoffee']) 19 | end 20 | 21 | describe 'without the :missing_ok option' do 22 | it 'removes non-coffee files that do not exist' do 23 | expect(File).to receive(:exist?).with('a.coffee').and_return true 24 | expect(File).to receive(:exist?).with('c.litcoffee').and_return true 25 | expect(File).to receive(:exist?).with('d.coffee.md').and_return true 26 | expect(File).to receive(:exist?).with('doesntexist.coffee').and_return false 27 | expect(inspector.clean(['a.coffee', 'b.txt', 'doesntexist.coffee', 'c.litcoffee', 'd.coffee.md'])) 28 | .to eq(['a.coffee', 'c.litcoffee', 'd.coffee.md']) 29 | end 30 | end 31 | 32 | describe 'with the :missing_ok options' do 33 | it 'removes non-coffee files' do 34 | expect(inspector.clean(['a.coffee', 'b.txt', 'doesntexist.coffee', 'c.litcoffee', 'd.coffee.md'], missing_ok: true)) 35 | .to eq(['a.coffee', 'doesntexist.coffee', 'c.litcoffee', 'd.coffee.md']) 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/guard/coffeescript/runner_integration_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Guard::CoffeeScript::Runner do 2 | let(:runner) { Guard::CoffeeScript::Runner } 3 | let(:pattern) { %r{spec/fixtures/.+\.coffee$} } 4 | let(:formatter) { Guard::CoffeeScript::Formatter } 5 | 6 | before do 7 | allow(formatter).to receive(:notify) 8 | 9 | Guard::CoffeeScript::Runner.last_run_failed = false 10 | end 11 | after do 12 | FileUtils.rm_rf "#{@project_path}/tmp" if Dir.exist? "#{@project_path}/tmp" 13 | end 14 | 15 | describe 'run script with pre processing instruction' do 16 | before { runner.run(["#{@project_path}/spec/fixtures/script_with_pre_processing.coffee"], [pattern], options) } 17 | context 'no preprocessing option present' do 18 | let(:options) { {output: 'tmp'} } 19 | it do 20 | file = File.read("#{@project_path}/tmp/script_with_pre_processing.js") 21 | expect(file).to match(/alert\('raw coffee beans'\);/) 22 | end 23 | end 24 | context 'with lambda preprocessing' do 25 | let(:options) { {output: 'tmp', pre_process: ->(file) { file.gsub(/raw coffee beans/, 'grinded coffee') }} } 26 | it do 27 | file = File.read("#{@project_path}/tmp/script_with_pre_processing.js") 28 | expect(file).to match(/alert\('grinded coffee'\);/) 29 | end 30 | end 31 | context 'with proc preprocessing' do 32 | let(:options) { {output: 'tmp', pre_process: Proc.new { |file| file.gsub(/raw coffee beans/, 'grinded coffee') }} } 33 | it do 34 | file = File.read("#{@project_path}/tmp/script_with_pre_processing.js") 35 | expect(file).to match(/alert\('grinded coffee'\);/) 36 | end 37 | end 38 | end 39 | 40 | end -------------------------------------------------------------------------------- /spec/guard/coffeescript/runner_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Guard::CoffeeScript::Runner do 2 | let(:runner) { Guard::CoffeeScript::Runner } 3 | let(:pattern) { /^(.+)\.(?:coffee|coffee\.md|litcoffee)$/ } 4 | let(:formatter) { Guard::CoffeeScript::Formatter } 5 | 6 | before do 7 | allow(runner).to receive(:compile).and_return '' 8 | allow(formatter).to receive(:notify) 9 | 10 | allow(FileUtils).to receive(:mkdir_p) 11 | allow(FileUtils).to receive(:remove_file) 12 | allow(File).to receive(:open) 13 | Guard::CoffeeScript::Runner.last_run_failed = false 14 | end 15 | 16 | describe '#run' do 17 | context 'without the :noop option' do 18 | it 'shows a start notification' do 19 | expect(formatter).to receive(:info).once.with('Compile a.coffee, b.coffee.md, c.litcoffee', reset: true) 20 | expect(formatter).to receive(:success).once.with('Successfully generated ') 21 | runner.run(['a.coffee', 'b.coffee.md', 'c.litcoffee'], []) 22 | end 23 | end 24 | 25 | context 'with the :noop option' do 26 | it 'shows a start notification' do 27 | expect(formatter).to receive(:info).once.with('Verify a.coffee, b.coffee.md, c.litcoffee', reset: true) 28 | expect(formatter).to receive(:success).once.with('Successfully verified ') 29 | runner.run(['a.coffee', 'b.coffee.md', 'c.litcoffee'], [], noop: true) 30 | end 31 | end 32 | 33 | context 'without a nested directory' do 34 | let(:pattern) { %r{src/.+\.(?:coffee|coffee\.md|litcoffee)$} } 35 | 36 | context 'without the :noop option' do 37 | it 'compiles the CoffeeScripts to the output and replace .{coffee,coffee.md,litcoffee} with .js' do 38 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/target") 39 | expect(File).to receive(:open).with("#{ @project_path }/target/a.js", 'w') 40 | expect(File).to receive(:open).with("#{ @project_path }/target/b.js", 'w') 41 | expect(File).to receive(:open).with("#{ @project_path }/target/c.js", 'w') 42 | runner.run(['src/a.coffee', 'src/b.coffee.md', 'src/c.litcoffee'], [pattern], output: 'target') 43 | end 44 | 45 | it 'compiles the CoffeeScripts to the output and replace .js.{coffee,coffee.md,litcoffee} with .js' do 46 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/target") 47 | expect(File).to receive(:open).with("#{ @project_path }/target/a.js", 'w') 48 | expect(File).to receive(:open).with("#{ @project_path }/target/b.js", 'w') 49 | expect(File).to receive(:open).with("#{ @project_path }/target/c.js", 'w') 50 | runner.run(['src/a.js.coffee', 'src/b.js.coffee.md', 'src/c.litcoffee'], [pattern], output: 'target') 51 | end 52 | end 53 | 54 | context 'without the :output option' do 55 | it 'compiles the CoffeeScripts to the same dir like the file and replace .{coffee,coffee.md,litcoffee} with .js' do 56 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/src") 57 | expect(File).to receive(:open).with("#{ @project_path }/src/a.js", 'w') 58 | expect(File).to receive(:open).with("#{ @project_path }/src/b.js", 'w') 59 | expect(File).to receive(:open).with("#{ @project_path }/src/c.js", 'w') 60 | runner.run(['src/a.coffee', 'src/b.coffee.md', 'src/c.litcoffee'], [pattern]) 61 | end 62 | 63 | it 'compiles the CoffeeScripts to the same dir like the file and replace .js.{coffee,coffee.md,litcoffee} with .js' do 64 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/src") 65 | expect(File).to receive(:open).with("#{ @project_path }/src/a.js", 'w') 66 | expect(File).to receive(:open).with("#{ @project_path }/src/b.js", 'w') 67 | expect(File).to receive(:open).with("#{ @project_path }/src/c.js", 'w') 68 | runner.run(['src/a.js.coffee', 'src/b.js.coffee.md', 'src/c.js.litcoffee'], [pattern]) 69 | end 70 | end 71 | 72 | context 'with the :noop option' do 73 | it 'does not write the output file' do 74 | expect(FileUtils).not_to receive(:mkdir_p).with("#{ @project_path }/target") 75 | expect(File).not_to receive(:open).with("#{ @project_path }/target/a.js", 'w') 76 | expect(File).not_to receive(:open).with("#{ @project_path }/target/b.js", 'w') 77 | expect(File).not_to receive(:open).with("#{ @project_path }/target/c.js", 'w') 78 | runner.run(['src/a.js.coffee', 'src/b.js.coffee.md', 'src/c.js.litcoffee'], [pattern], output: 'target', noop: true) 79 | end 80 | end 81 | 82 | context 'with the :source_map option' do 83 | it 'compiles the source map to the same dir like the file and replace .{coffee,coffee.md,litcoffee} with .js.map' do 84 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/src") 85 | expect(File).to receive(:open).with("#{ @project_path }/src/a.js.map", 'w') 86 | expect(File).to receive(:open).with("#{ @project_path }/src/b.js.map", 'w') 87 | expect(File).to receive(:open).with("#{ @project_path }/src/c.js.map", 'w') 88 | runner.run(['src/a.coffee', 'src/b.coffee.md', 'src/c.litcoffee'], [pattern], source_map: true) 89 | end 90 | 91 | it 'compiles the source map to the same dir like the file and replace .js.{coffee,coffee.md,litcoffee} with .js.map' do 92 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/src") 93 | expect(File).to receive(:open).with("#{ @project_path }/src/a.js.map", 'w') 94 | expect(File).to receive(:open).with("#{ @project_path }/src/b.js.map", 'w') 95 | expect(File).to receive(:open).with("#{ @project_path }/src/c.js.map", 'w') 96 | runner.run(['src/a.js.coffee', 'src/b.js.coffee.md', 'src/c.js.litcoffee'], [pattern], source_map: true) 97 | end 98 | end 99 | end 100 | 101 | context 'with the :bare option set to an array of filenames' do 102 | let(:pattern) { %r{src/.+\.(?:coffee|coffee\.md|litcoffee)$} } 103 | 104 | before do 105 | allow(runner).to receive(:compile).and_call_original 106 | allow(::CoffeeScript).to receive(:compile) 107 | expect(File).to receive(:read).with('src/a.coffee').and_return 'a = -> 1' 108 | expect(File).to receive(:read).with('src/b.coffee').and_return 'b = -> 2' 109 | end 110 | 111 | it 'should compile files in the list without the outer function wrapper' do 112 | expect(::CoffeeScript).to receive(:compile).with 'a = -> 1', hash_including(bare: true) 113 | runner.run(['src/a.coffee', 'src/b.coffee'], [pattern], output: 'target', bare: ['a.coffee']) 114 | end 115 | 116 | it 'should compile files not in the list with the outer function wrapper' do 117 | expect(::CoffeeScript).to receive(:compile).with 'b = -> 2', hash_including(bare: false) 118 | runner.run(['src/a.coffee', 'src/b.coffee'], [pattern], output: 'target', bare: ['a.coffee']) 119 | end 120 | end 121 | 122 | context 'with the :shallow option set to false' do 123 | let(:pattern) { /^app\/coffeescripts\/(.+)\.(?:coffee|coffee\.md|litcoffee)$/ } 124 | 125 | it 'compiles the CoffeeScripts to the output and creates nested directories' do 126 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/javascripts/x/y") 127 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/x/y/a.js", 'w') 128 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/x/y/b.js", 'w') 129 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/x/y/c.js", 'w') 130 | runner.run(['app/coffeescripts/x/y/a.coffee', 'app/coffeescripts/x/y/b.coffee.md', 'app/coffeescripts/x/y/c.litcoffee'], 131 | [pattern], output: 'javascripts', shallow: false) 132 | end 133 | 134 | context 'with the :source_map option' do 135 | it 'generates the source map to the output and creates nested directories' do 136 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/javascripts/x/y") 137 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/x/y/a.js.map", 'w') 138 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/x/y/b.js.map", 'w') 139 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/x/y/c.js.map", 'w') 140 | runner.run(['app/coffeescripts/x/y/a.coffee', 'app/coffeescripts/x/y/b.coffee.md', 'app/coffeescripts/x/y/c.litcoffee'], 141 | [pattern], output: 'javascripts', shallow: false, source_map: true) 142 | end 143 | end 144 | end 145 | 146 | context 'with the :shallow option set to true' do 147 | let(:pattern) { /^app\/coffeescripts\/(.+)\.(?:coffee|coffee\.md|litcoffee)$/ } 148 | 149 | it 'compiles the CoffeeScripts to the output without creating nested directories' do 150 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/javascripts") 151 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/a.js", 'w') 152 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/b.js", 'w') 153 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/c.js", 'w') 154 | runner.run(['app/coffeescripts/x/y/a.coffee', 'app/coffeescripts/x/y/b.coffee.md', 'app/coffeescripts/x/y/c.litcoffee'], 155 | [pattern], output: 'javascripts', shallow: true) 156 | end 157 | 158 | context 'with the :source_map option' do 159 | it 'generates the source map to the output without creating nested directories' do 160 | expect(FileUtils).to receive(:mkdir_p).with("#{ @project_path }/javascripts") 161 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/a.js.map", 'w') 162 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/b.js.map", 'w') 163 | expect(File).to receive(:open).with("#{ @project_path }/javascripts/c.js.map", 'w') 164 | runner.run(['app/coffeescripts/x/y/a.coffee', 'app/coffeescripts/x/y/b.coffee.md', 'app/coffeescripts/x/y/c.litcoffee'], 165 | [pattern], output: 'javascripts', shallow: true, source_map: true) 166 | end 167 | end 168 | end 169 | 170 | context 'with the :source_map option' do 171 | before do 172 | allow(runner).to receive(:compile).and_call_original 173 | allow(::CoffeeScript).to receive(:compile) 174 | allow(File).to receive(:read) { |file| file } 175 | end 176 | 177 | after do 178 | allow(runner).to receive(:compile).and_return '' 179 | allow(::CoffeeScript).to receive(:compile).and_call_original 180 | end 181 | 182 | it 'compiles with source map file options set' do 183 | expect(::CoffeeScript).to receive(:compile).with 'src/a.coffee', hash_including( 184 | sourceMap: true, 185 | generatedFile: 'a.js', 186 | sourceFiles: ['a.coffee'], 187 | sourceRoot: 'src' 188 | ) 189 | runner.run(['src/a.coffee'], [pattern], output: 'target', source_map: true, input: 'src') 190 | end 191 | 192 | it 'accepts a different source_root' do 193 | expect(::CoffeeScript).to receive(:compile).with 'src/a.coffee', hash_including(sourceRoot: 'foo') 194 | runner.run(['src/a.coffee'], [pattern], output: 'target', source_map: true, source_root: 'foo') 195 | end 196 | end 197 | 198 | context 'with literate coffeescript' do 199 | before do 200 | allow(runner).to receive(:compile).and_call_original 201 | allow(::CoffeeScript).to receive(:compile) 202 | allow(File).to receive(:read) { |file| file } 203 | end 204 | 205 | after do 206 | allow(runner).to receive(:compile).and_return '' 207 | allow(::CoffeeScript).to receive(:compile).and_call_original 208 | end 209 | 210 | it 'compiles with the :literate option set' do 211 | expect(::CoffeeScript).to receive(:compile).with 'a.coffee', hash_not_including(literate: true) 212 | expect(::CoffeeScript).to receive(:compile).with 'b.coffee.md', hash_including(literate: true) 213 | expect(::CoffeeScript).to receive(:compile).with 'c.litcoffee', hash_including(literate: true) 214 | runner.run(['a.coffee', 'b.coffee.md', 'c.litcoffee'], [pattern], output: 'javascripts') 215 | end 216 | end 217 | 218 | context 'with compilation errors' do 219 | context 'without the :noop option' do 220 | it 'shows the error messages' do 221 | expect(runner).to receive(:compile).and_raise ::CoffeeScript::CompilationError.new("Parse error on line 2: Unexpected 'UNARY'") 222 | expect(formatter).to receive(:error).once.with("a.coffee: Parse error on line 2: Unexpected 'UNARY'") 223 | expect(formatter).to receive(:notify).with("a.coffee: Parse error on line 2: Unexpected 'UNARY'", 224 | title: 'CoffeeScript results', 225 | image: :failed, 226 | priority: 2) 227 | runner.run(['a.coffee'], [pattern], output: 'javascripts') 228 | end 229 | end 230 | 231 | context 'with the :noop option' do 232 | it 'shows the error messages' do 233 | expect(runner).to receive(:compile).and_raise ::CoffeeScript::CompilationError.new("Parse error on line 2: Unexpected 'UNARY'") 234 | expect(formatter).to receive(:error).once.with("a.coffee: Parse error on line 2: Unexpected 'UNARY'") 235 | expect(formatter).to receive(:notify).with("a.coffee: Parse error on line 2: Unexpected 'UNARY'", 236 | title: 'CoffeeScript results', 237 | image: :failed, 238 | priority: 2) 239 | runner.run(['a.coffee'], [pattern], output: 'javascripts', noop: true) 240 | end 241 | end 242 | 243 | context 'with the :error_to_js option' do 244 | it 'write the error message as javascript file' do 245 | expect(runner).to receive(:compile).and_raise ::CoffeeScript::CompilationError.new("Parse error on line 2: Unexpected 'UNARY'") 246 | expect(runner).to receive(:write_javascript_file).once.with("throw \"a.coffee: Parse error on line 2: Unexpected 'UNARY'\";", nil, 'a.coffee', 'javascripts', kind_of(Hash)) 247 | runner.run(['a.coffee'], [pattern], output: 'javascripts', error_to_js: true) 248 | end 249 | end 250 | end 251 | 252 | context 'with engine errors' do 253 | context 'without the :noop option' do 254 | it 'shows the error messages' do 255 | expect(runner).to receive(:compile).and_raise ::CoffeeScript::EngineError.new("SyntaxError: [stdin]:1:5: reserved word \"true\" can't be assigned") 256 | expect(formatter).to receive(:error).once.with("a.coffee: SyntaxError: [stdin]:1:5: reserved word \"true\" can't be assigned") 257 | expect(formatter).to receive(:notify).with("a.coffee: SyntaxError: [stdin]:1:5: reserved word \"true\" can't be assigned", 258 | title: 'CoffeeScript results', 259 | image: :failed, 260 | priority: 2) 261 | runner.run(['a.coffee'], [pattern], output: 'javascripts') 262 | end 263 | end 264 | end 265 | 266 | context 'without compilation errors' do 267 | context 'without the :noop option' do 268 | it 'shows a success messages' do 269 | expect(formatter).to receive(:success).once.with('Successfully generated javascripts/a.js') 270 | expect(formatter).to receive(:notify).with('Successfully generated javascripts/a.js', 271 | title: 'CoffeeScript results') 272 | runner.run(['a.coffee'], [pattern], output: 'javascripts') 273 | end 274 | end 275 | 276 | context 'with the :noop option' do 277 | it 'shows a success messages' do 278 | expect(formatter).to receive(:success).once.with('Successfully verified javascripts/a.js') 279 | expect(formatter).to receive(:notify).with('Successfully verified javascripts/a.js', 280 | title: 'CoffeeScript results') 281 | runner.run(['a.coffee'], [pattern], output: 'javascripts', 282 | noop: true) 283 | end 284 | end 285 | 286 | context 'with the :hide_success option set to true' do 287 | let(:pattern) { /^app\/coffeescripts\/.+\.(?:coffee|coffee\.md|litcoffee)$/ } 288 | 289 | it 'does not show the success message' do 290 | expect(formatter).not_to receive(:success).with('Successfully generated javascripts/a.js') 291 | expect(formatter).not_to receive(:notify).with('Successfully generated javascripts/a.js', 292 | title: 'CoffeeScript results') 293 | runner.run(['app/coffeescripts/x/y/a.coffee'], [pattern], output: 'javascripts', 294 | hide_success: true) 295 | end 296 | end 297 | end 298 | 299 | context 'with :hide_success over multiple runs' do 300 | it 'shows the failure message every time' do 301 | expect(runner).to receive(:compile).twice.and_raise ::CoffeeScript::CompilationError.new("Parse error on line 2: Unexpected 'UNARY'") 302 | expect(formatter).to receive(:error).twice.with("a.coffee: Parse error on line 2: Unexpected 'UNARY'") 303 | expect(formatter).to receive(:notify).twice.with("a.coffee: Parse error on line 2: Unexpected 'UNARY'", 304 | title: 'CoffeeScript results', 305 | image: :failed, 306 | priority: 2) 307 | 308 | 2.times { runner.run(['a.coffee'], [pattern], output: 'javascripts') } 309 | end 310 | 311 | it 'shows the success message only when previous attempt was failure' do 312 | expect(runner).to receive(:compile).and_raise ::CoffeeScript::CompilationError.new("Parse error on line 2: Unexpected 'UNARY'") 313 | runner.run(['a.coffee'], [pattern], output: 'javascripts', 314 | hide_success: true) 315 | 316 | allow(runner).to receive(:compile).and_return '' 317 | expect(formatter).to receive(:success).with('Successfully generated javascripts/a.js') 318 | expect(formatter).to receive(:notify).with('Successfully generated javascripts/a.js', 319 | title: 'CoffeeScript results') 320 | runner.run(['a.coffee'], [pattern], output: 'javascripts', 321 | hide_success: true) 322 | end 323 | end 324 | end 325 | 326 | describe '#remove' do 327 | let(:pattern) { %r{src/.+\.(?:coffee|coffee\.md|litcoffee)$} } 328 | 329 | before do 330 | expect(File).to receive(:exist?).with('target/a.js').and_return true 331 | expect(File).to receive(:exist?).with('target/b.js').and_return true 332 | expect(File).to receive(:exist?).with('target/c.js').and_return true 333 | end 334 | 335 | it 'removes the files' do 336 | expect(FileUtils).to receive(:remove_file).with('target/a.js') 337 | expect(FileUtils).to receive(:remove_file).with('target/b.js') 338 | expect(FileUtils).to receive(:remove_file).with('target/c.js') 339 | runner.remove(['src/a.coffee', 'src/b.coffee.md', 'src/c.litcoffee'], [pattern], output: 'target') 340 | end 341 | 342 | it 'shows a notification' do 343 | expect(formatter).to receive(:success).once.with('Removed target/a.js, target/b.js, target/c.js') 344 | expect(formatter).to receive(:notify).with('Removed target/a.js, target/b.js, target/c.js', 345 | title: 'CoffeeScript results') 346 | 347 | runner.remove(['src/a.coffee', 'src/b.coffee.md', 'src/c.litcoffee'], [pattern], output: 'target') 348 | end 349 | end 350 | end 351 | -------------------------------------------------------------------------------- /spec/guard/coffeescript/version_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Guard::CoffeeScriptVersion do 2 | describe 'VERSION' do 3 | it 'defines the version' do 4 | expect(Guard::CoffeeScriptVersion::VERSION).to match(/\d+\.\d+\.\d+/) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/guard/coffeescript_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Guard::CoffeeScript do 2 | let(:input) { 'app/assets/javascripts' } 3 | let(:output) { 'app/assets/javascripts' } 4 | let(:pattern) { %r{^app/assets/javascripts/(.+\.(?:coffee|coffee\.md|litcoffee))$} } 5 | let(:extra_options) { {} } 6 | let(:options) { { input: input, output: output, patterns: [pattern] }.merge(extra_options) } 7 | 8 | subject { described_class.new(options) } 9 | 10 | let(:runner) { described_class::Runner } 11 | let(:inspector) { described_class::Inspector } 12 | 13 | let(:defaults) { described_class::DEFAULT_OPTIONS } 14 | 15 | before do 16 | allow(inspector).to receive(:clean) 17 | allow(runner).to receive(:run) 18 | allow(runner).to receive(:remove) 19 | end 20 | 21 | describe '#initialize' do 22 | context 'when no options are provided' do 23 | specify { expect(subject.options).to include(bare: false) } 24 | specify { expect(subject.options).to include(shallow: false) } 25 | specify { expect(subject.options).to include(hide_success: false) } 26 | specify { expect(subject.options).to include(noop: false) } 27 | specify { expect(subject.options).to include(all_on_start: false) } 28 | specify { expect(subject.options).to include(source_map: false) } 29 | end 30 | 31 | context 'with options besides the defaults' do 32 | let(:extra_options) do 33 | { 34 | output: 'output_folder', 35 | bare: true, 36 | shallow: true, 37 | hide_success: true, 38 | all_on_start: true, 39 | noop: true, 40 | source_map: true 41 | } 42 | end 43 | 44 | specify { expect(subject.options).to include(bare: true) } 45 | specify { expect(subject.options).to include(shallow: true) } 46 | specify { expect(subject.options).to include(hide_success: true) } 47 | specify { expect(subject.options).to include(noop: true) } 48 | specify { expect(subject.options).to include(all_on_start: true) } 49 | specify { expect(subject.options).to include(source_map: true) } 50 | end 51 | 52 | context 'without an input option' do 53 | let(:input) { nil } 54 | specify { expect { subject }.to raise_error(/:input option not provided/) } 55 | end 56 | 57 | context 'with a input option' do 58 | let(:output) { 'app/coffeescripts' } 59 | let(:pattern) { %r{^app/coffeescripts/(.+\.(?:coffee|coffee\.md|litcoffee))$} } 60 | 61 | it 'watches all *.{coffee,coffee.md,litcoffee} files' do 62 | expect(subject.patterns.first).to eql pattern 63 | end 64 | 65 | context 'without an output option' do 66 | let(:input) { 'app/coffeescripts' } 67 | let(:output) { nil } 68 | specify { expect(subject.options).to include(output: 'app/coffeescripts') } 69 | end 70 | 71 | context 'with an output option' do 72 | let(:output) { 'public/javascripts' } 73 | specify { expect(subject.options).to include(output: 'public/javascripts') } 74 | end 75 | end 76 | end 77 | 78 | describe '#start' do 79 | it 'calls #run_all' do 80 | expect(subject).not_to receive(:run_all) 81 | subject.start 82 | end 83 | 84 | context 'with the :all_on_start option' do 85 | let(:extra_options) { { all_on_start: true } } 86 | 87 | it 'calls #run_all' do 88 | expect(subject).to receive(:run_all) 89 | subject.start 90 | end 91 | end 92 | end 93 | 94 | describe '#run_all' do 95 | let(:pattern) { /^x\/.+\.(?:coffee|coffee\.md|litcoffee)$/ } 96 | 97 | before do 98 | allow(Dir).to receive(:glob).and_return ['x/a.coffee', 'x/b.coffee', 'y/c.coffee', 'x/d.coffeeemd', 'x/e.litcoffee'] 99 | end 100 | 101 | it 'runs the run_on_modifications with all watched CoffeeScripts' do 102 | expect(subject).to receive(:run_on_modifications).with(['x/a.coffee', 'x/b.coffee', 'x/e.litcoffee']) 103 | subject.run_all 104 | end 105 | end 106 | 107 | describe '#run_on_modifications' do 108 | it 'throws :task_has_failed when an error occurs' do 109 | expected_opts = defaults.merge( 110 | input: 'app/assets/javascripts', 111 | output: 'app/assets/javascripts', 112 | patterns: [pattern] 113 | ) 114 | expect(inspector).to receive(:clean).with(['a.coffee', 'b.coffee']).and_return ['a.coffee'] 115 | expect(runner).to receive(:run).with(['a.coffee'], [pattern], expected_opts).and_return [[], false] 116 | expect { subject.run_on_modifications(['a.coffee', 'b.coffee']) }.to throw_symbol :task_has_failed 117 | end 118 | 119 | it 'starts the Runner with the cleaned files' do 120 | expected_opts = defaults.merge( 121 | input: 'app/assets/javascripts', 122 | output: 'app/assets/javascripts', 123 | patterns: [pattern] 124 | ) 125 | expect(inspector).to receive(:clean).with(['a.coffee', 'b.coffee']).and_return ['a.coffee'] 126 | expect(runner).to receive(:run).with(['a.coffee'], [pattern], expected_opts).and_return [['a.js'], true] 127 | subject.run_on_modifications(['a.coffee', 'b.coffee']) 128 | end 129 | end 130 | 131 | describe '#run_on_removals' do 132 | it 'cleans the paths accepting missing files' do 133 | expect(inspector).to receive(:clean).with(['a.coffee', 'b.coffee'], missing_ok: true) 134 | subject.run_on_removals(['a.coffee', 'b.coffee']) 135 | end 136 | 137 | it 'removes the files' do 138 | expect(inspector).to receive(:clean).and_return ['a.coffee', 'b.coffee'] 139 | expect(runner).to receive(:remove).with(['a.coffee', 'b.coffee'], subject.patterns, subject.options) 140 | subject.run_on_removals(['a.coffee', 'b.coffee']) 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rake/ext/pathname' 2 | 3 | require 'guard/coffeescript/version' 4 | require 'guard/compat/test/helper' 5 | require 'guard/coffeescript' 6 | 7 | RSpec.configure do |config| 8 | config.expect_with :rspec do |expectations| 9 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 10 | end 11 | 12 | config.mock_with :rspec do |mocks| 13 | mocks.verify_partial_doubles = true 14 | end 15 | 16 | config.filter_run focus: (ENV['CI'] != 'true') 17 | config.run_all_when_everything_filtered = true 18 | 19 | config.disable_monkey_patching! 20 | 21 | config.warnings = true 22 | 23 | config.default_formatter = 'doc' if config.files_to_run.one? 24 | 25 | # config.profile_examples = 10 26 | 27 | config.order = :random 28 | 29 | Kernel.srand config.seed 30 | 31 | config.before(:each) do 32 | @project_path = Pathname.new(File.expand_path('../../', __FILE__)) 33 | 34 | allow(Guard::Compat::UI).to receive(:info) 35 | allow(Guard::Compat::UI).to receive(:debug) 36 | allow(Guard::Compat::UI).to receive(:error) 37 | allow(Guard::Compat::UI).to receive(:warning) 38 | allow(Guard::Compat::UI).to receive(:color_enabled?).and_return(true) 39 | end 40 | end 41 | --------------------------------------------------------------------------------