Skip to content

Commit

Permalink
Add :noop option for use with the Rails 3.1 asset pipeline.
Browse files Browse the repository at this point in the history
  • Loading branch information
netzpirat committed Sep 3, 2011
1 parent f1973d3 commit 87dcc4f
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 57 deletions.
83 changes: 62 additions & 21 deletions README.md
Expand Up @@ -2,11 +2,12 @@

![travis-ci](http://travis-ci.org/netzpirat/guard-coffeescript.png)

Guard::CoffeeScript compiles you CoffeeScripts automatically when files are modified.
Guard::CoffeeScript compiles or validates your CoffeeScripts automatically when files are modified.

Tested on MRI Ruby 1.8.7, 1.9.2 and the latest versions of JRuby & Rubinius.

If you have any questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard` (irc.freenode.net).
If you have any questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard`
(irc.freenode.net).

## Install

Expand Down Expand Up @@ -85,20 +86,44 @@ Please read the [Guard usage documentation](https://github.com/guard/guard#readm
Guard::CoffeeScript can be adapted to all kind of projects. Please read the
[Guard documentation](https://github.com/guard/guard#readme) for more information about the Guardfile DSL.

In addition to the standard configuration, this Guard has a short notation for configure projects with a single input a output
directory. This notation creates a watcher from the `:input` parameter that matches all CoffeeScript files under the given directory
and you don't have to specify a watch regular expression.

### Standard Ruby gem

In a custom Ruby project you want to configure your `:input` and `:output` directories.

```ruby
guard 'coffeescript', :input => 'coffeescripts', :output => 'javascripts'
```

### Rails 3.1 app
If your output directory is the same as the input directory, you can simply skip it:

```ruby
guard 'coffeescript', :input => 'javascripts'
```

### Rails app with the asset pipeline

With the introduction of the [asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html) in Rails 3.1 there is
no need to compile your CoffeeScripts with this Guard. However if you like to have instant validation feedback
(preferably with a Growl notification) directly after you save a change, then you may want to skip the generation
of the output file:

```ruby
guard 'coffeescript', :input => 'app/assets/javascripts', :noop => true
```

This give you a faster compilation feedback compared to making a subsequent request to your Rails application. If you
just want to be notified when an error occurs you can hide the success compilation message:

```ruby
guard 'coffeescript', :input => 'app/assets/javascripts'
guard 'coffeescript', :input => 'app/assets/javascripts', :noop => true, :hide_success => true
```

### Rails app without the asset pipeline

Without the asset pipeline you just define an input and output directory like within a normal Ruby project:

```ruby
guard 'coffeescript', :input => 'app/coffeescripts', :output => 'public/javascripts',
```

## Options
Expand All @@ -113,6 +138,9 @@ There following options can be passed to Guard::CoffeeScript:
:output => 'javascripts' # Relative path to the output directory.
# default: the path given with the :input option

:noop => true # No operation: do not write an output file.
# default: false

:bare => true # Compile without the top-level function wrapper.
# Provide either a boolean value or an Array of filenames.
# default: false
Expand All @@ -124,27 +152,39 @@ There following options can be passed to Guard::CoffeeScript:
# default: false
```

### Output short notation

In addition to the standard configuration, this Guard has a short notation for configure projects with a single input
and output directory. This notation creates a watcher from the `:input` parameter that matches all CoffeeScript files
under the given directory and you don't have to specify a watch regular expression.

```ruby
guard 'coffeescript', :input => 'javascripts'
```

### Selective bare option

The `:bare` option can take a boolean value that indicates if all scripts should be compiled without the top-level function wrapper.
The `:bare` option can take a boolean value that indicates if all scripts should be compiled without the top-level
function wrapper.

```ruby
:bare => true
```

But you can also pass an Array of filenames that should be compiled without the top-level function wrapper. The path of the file to compile is
ignored, so the list of filenames should not contain any path information:
But you can also pass an Array of filenames that should be compiled without the top-level function wrapper. The path of
the file to compile is ignored, so the list of filenames should not contain any path information:

```ruby
:bare => %w{ a.coffee b.coffee }
```

In the above example, all `a.coffee` and `b.coffee` files will be compiled with option `:bare => true` and all other files with option `:bare => false`.
In the above example, all `a.coffee` and `b.coffee` files will be compiled with option `:bare => true` and all other
files with option `:bare => false`.

### Nested directories

The Guard detects by default nested directories and creates these within the output directory. The detection is based on the match
of the watch regular expression:
The Guard detects by default nested directories and creates these within the output directory. The detection is based on
the match of the watch regular expression:

A file

Expand All @@ -170,11 +210,11 @@ will be compiled to
public/javascripts/compiled/ui/buttons/toggle_button.js
```

Note the parenthesis around the `.+\.coffee`. This enables Guard::CoffeeScript to place the full path that was matched inside the
parenthesis into the proper output directory.
Note the parenthesis around the `.+\.coffee`. This enables Guard::CoffeeScript to place the full path that was matched
inside the parenthesis into the proper output directory.

This behavior can be switched off by passing the option `:shallow => true` to the Guard, so that all JavaScripts will be compiled
directly to the output directory.
This behavior can be switched off by passing the option `:shallow => true` to the Guard, so that all JavaScripts will be
compiled directly to the output directory.

### Multiple source directories

Expand All @@ -184,8 +224,8 @@ The Guard short notation
guard 'coffeescript', :input => 'app/coffeescripts', :output => 'public/javascripts/compiled'
```

will be internally converted into the standard notation by adding `/(.+\.coffee)` to the `input` option string and create a Watcher
that is equivalent to:
will be internally converted into the standard notation by adding `/(.+\.coffee)` to the `input` option string and
create a Watcher that is equivalent to:

```ruby
guard 'coffeescript', :output => 'public/javascripts/compiled' do
Expand Down Expand Up @@ -217,7 +257,8 @@ end

Pull requests are very welcome! Make sure your patches are well tested.

For questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard` (irc.freenode.net).
For questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard`
(irc.freenode.net).

## Contributors

Expand Down
3 changes: 2 additions & 1 deletion lib/guard/coffeescript.rb
Expand Up @@ -15,6 +15,7 @@ def initialize(watchers = [], options = {})
:bare => false,
:shallow => false,
:hide_success => false,
:noop => false
}

if options[:input]
Expand All @@ -32,7 +33,7 @@ def run_all
def run_on_change(paths)
changed_files, success = Runner.run(Inspector.clean(paths), watchers, options)
notify changed_files

success
end

Expand Down
12 changes: 6 additions & 6 deletions lib/guard/coffeescript/runner.rb
Expand Up @@ -19,7 +19,7 @@ def run(files, watchers, options = {})
private

def notify_start(files, options)
message = options[:message] || "Compile #{ files.join(', ') }"
message = options[:message] || (options[:noop] ? 'Verify ' : 'Compile ') + files.join(', ')
Formatter.info(message, :reset => true)
end

Expand All @@ -32,7 +32,7 @@ def compile_files(files, options, watchers)
scripts.each do |file|
begin
content = compile(file, options)
changed_files << process_compile_result(content, file, directory)
changed_files << process_compile_result(content, file, directory, options)
rescue ExecJS::ProgramError => e
error_message = file + ': ' + e.message.to_s
errors << error_message
Expand All @@ -58,10 +58,10 @@ def options_for_file(file, options)
file_options
end

def process_compile_result(content, file, directory)
FileUtils.mkdir_p(File.expand_path(directory)) if !File.directory?(directory)
def process_compile_result(content, file, directory, options)
FileUtils.mkdir_p(File.expand_path(directory)) if !File.directory?(directory) && !options[:noop]
filename = File.join(directory, File.basename(file.gsub(/(js\.coffee|coffee)$/, 'js')))
File.open(File.expand_path(filename), 'w') { |f| f.write(content) }
File.open(File.expand_path(filename), 'w') { |f| f.write(content) } if !options[:noop]

filename
end
Expand Down Expand Up @@ -89,7 +89,7 @@ def notify_result(changed_files, errors, options = {})
if !errors.empty?
Formatter.notify(errors.join("\n"), :title => 'CoffeeScript results', :image => :failed, :priority => 2)
elsif !options[:hide_success]
message = "Successfully generated #{ changed_files.join(', ') }"
message = "Successfully #{ options[:noop] ? 'verified' : 'generated' } #{ changed_files.join(', ') }"
Formatter.success(message)
Formatter.notify(message, :title => 'CoffeeScript results')
end
Expand Down
97 changes: 70 additions & 27 deletions spec/guard/coffeescript/runner_spec.rb
Expand Up @@ -11,31 +11,51 @@
File.stub(:open)
end

it 'shows a start notification' do
::Guard::CoffeeScript::Formatter.should_receive(:info).once.with('Compile a.coffee, b.coffee', { :reset => true })
::Guard::CoffeeScript::Formatter.should_receive(:success).once.with('Successfully generated ')
runner.run(['a.coffee', 'b.coffee'], [])
context 'without the :noop option' do
it 'shows a start notification' do
::Guard::CoffeeScript::Formatter.should_receive(:info).once.with('Compile a.coffee, b.coffee', { :reset => true })
::Guard::CoffeeScript::Formatter.should_receive(:success).once.with('Successfully generated ')
runner.run(['a.coffee', 'b.coffee'], [])
end
end

context 'with the :noop option' do
it 'shows a start notification' do
::Guard::CoffeeScript::Formatter.should_receive(:info).once.with('Verify a.coffee, b.coffee', { :reset => true })
::Guard::CoffeeScript::Formatter.should_receive(:success).once.with('Successfully verified ')
runner.run(['a.coffee', 'b.coffee'], [], { :noop => true })
end
end

context 'without a nested directory' do
let(:watcher) { Guard::Watcher.new(%r{src/.+\.coffee}) }

it 'compiles the CoffeeScripts to the output and replace .coffee with .js' do
FileUtils.should_receive(:mkdir_p).with("#{ @project_path }/target")
File.should_receive(:open).with("#{ @project_path }/target/a.js", 'w')
runner.run(['src/a.coffee'], [watcher], { :output => 'target' })
context 'without the :noop option' do
it 'compiles the CoffeeScripts to the output and replace .coffee with .js' do
FileUtils.should_receive(:mkdir_p).with("#{ @project_path }/target")
File.should_receive(:open).with("#{ @project_path }/target/a.js", 'w')
runner.run(['src/a.coffee'], [watcher], { :output => 'target' })
end

it 'compiles the CoffeeScripts to the output and replace .js.coffee with .js' do
FileUtils.should_receive(:mkdir_p).with("#{ @project_path }/target")
File.should_receive(:open).with("#{ @project_path }/target/a.js", 'w')
runner.run(['src/a.js.coffee'], [watcher], { :output => 'target' })
end
end

it 'compiles the CoffeeScripts to the output and replace .js.coffee with .js' do
FileUtils.should_receive(:mkdir_p).with("#{ @project_path }/target")
File.should_receive(:open).with("#{ @project_path }/target/a.js", 'w')
runner.run(['src/a.js.coffee'], [watcher], { :output => 'target' })
context 'with the :noop option' do
it 'does not write the output file' do
FileUtils.should_not_receive(:mkdir_p).with("#{ @project_path }/target")
File.should_not_receive(:open).with("#{ @project_path }/target/a.js", 'w')
runner.run(['src/a.coffee'], [watcher], { :output => 'target', :noop => true })
end
end
end

context 'with the :bare option set to an array of filenames' do
let(:watcher) { Guard::Watcher.new(%r{src/.+\.coffee}) }

before do
runner.unstub(:compile)
::CoffeeScript.stub(:compile)
Expand All @@ -44,16 +64,16 @@

after do
runner.stub(:compile).and_return ''
::CoffeeScript.unstub(:compile)
::CoffeeScript.unstub(:compile)
end

it 'should compile files in the list without the outer function wrapper' do
::CoffeeScript.should_receive(:compile).with 'src/a.coffee', hash_including(:bare => true)
::CoffeeScript.should_receive(:compile).with 'src/a.coffee', hash_including(:bare => true)
runner.run(['src/a.coffee', 'src/b.coffee'], [watcher], { :output => 'target', :bare => ['a.coffee'] })
end

it 'should compile files not in the list with the outer function wrapper' do
::CoffeeScript.should_receive(:compile).with 'src/b.coffee', hash_including(:bare => false)
::CoffeeScript.should_receive(:compile).with 'src/b.coffee', hash_including(:bare => false)
runner.run(['src/a.coffee', 'src/b.coffee'], [watcher], { :output => 'target', :bare => ['a.coffee'] })
end

Expand All @@ -80,21 +100,44 @@
end

context 'with compilation errors' do
it 'shows the error messages' do
runner.should_receive(:compile).and_raise ::CoffeeScript::CompilationError.new("Parse error on line 2: Unexpected 'UNARY'")
::Guard::CoffeeScript::Formatter.should_receive(:error).once.with("a.coffee: Parse error on line 2: Unexpected 'UNARY'")
Guard::Notifier.should_receive(:notify).with("a.coffee: Parse error on line 2: Unexpected 'UNARY'", :title => 'CoffeeScript results', :image => :failed, :priority => 2)
runner.run(['a.coffee'], [watcher], { :output => 'javascripts' })
context 'without the :noop option' do
it 'shows the error messages' do
runner.should_receive(:compile).and_raise ::CoffeeScript::CompilationError.new("Parse error on line 2: Unexpected 'UNARY'")
::Guard::CoffeeScript::Formatter.should_receive(:error).once.with("a.coffee: Parse error on line 2: Unexpected 'UNARY'")
Guard::Notifier.should_receive(:notify).with("a.coffee: Parse error on line 2: Unexpected 'UNARY'", :title => 'CoffeeScript results', :image => :failed, :priority => 2)
runner.run(['a.coffee'], [watcher], { :output => 'javascripts' })
end
end

context 'with the :noop option' do
it 'shows the error messages' do
runner.should_receive(:compile).and_raise ::CoffeeScript::CompilationError.new("Parse error on line 2: Unexpected 'UNARY'")
::Guard::CoffeeScript::Formatter.should_receive(:error).once.with("a.coffee: Parse error on line 2: Unexpected 'UNARY'")
Guard::Notifier.should_receive(:notify).with("a.coffee: Parse error on line 2: Unexpected 'UNARY'", :title => 'CoffeeScript results', :image => :failed, :priority => 2)
runner.run(['a.coffee'], [watcher], { :output => 'javascripts', :noop => true })
end
end
end

context 'without compilation errors' do
it 'shows a success messages' do
runner.should_receive(:compile).with('a.coffee', { :output => 'javascripts' }).and_return ["OK", true]
runner.should_receive(:notify_start).with(['a.coffee'], { :output => 'javascripts' })
::Guard::CoffeeScript::Formatter.should_receive(:success).once.with('Successfully generated javascripts/a.js')
Guard::Notifier.should_receive(:notify).with('Successfully generated javascripts/a.js', :title => 'CoffeeScript results')
runner.run(['a.coffee'], [watcher], { :output => 'javascripts' })
context 'without the :noop option' do
it 'shows a success messages' do
runner.should_receive(:compile).with('a.coffee', { :output => 'javascripts' }).and_return ["OK", true]
runner.should_receive(:notify_start).with(['a.coffee'], { :output => 'javascripts' })
::Guard::CoffeeScript::Formatter.should_receive(:success).once.with('Successfully generated javascripts/a.js')
Guard::Notifier.should_receive(:notify).with('Successfully generated javascripts/a.js', :title => 'CoffeeScript results')
runner.run(['a.coffee'], [watcher], { :output => 'javascripts' })
end
end

context 'with the :noop option' do
it 'shows a success messages' do
runner.should_receive(:compile).with('a.coffee', { :output => 'javascripts', :noop => true }).and_return ["OK", true]
runner.should_receive(:notify_start).with(['a.coffee'], { :output => 'javascripts', :noop => true })
::Guard::CoffeeScript::Formatter.should_receive(:success).once.with('Successfully verified javascripts/a.js')
Guard::Notifier.should_receive(:notify).with('Successfully verified javascripts/a.js', :title => 'CoffeeScript results')
runner.run(['a.coffee'], [watcher], { :output => 'javascripts', :noop => true })
end
end

context 'with the :hide_success option set to true' do
Expand Down
13 changes: 11 additions & 2 deletions spec/guard/coffeescript_spec.rb
Expand Up @@ -22,10 +22,14 @@
it 'sets a default :hide_success option' do
guard.options[:hide_success].should be_false
end

it 'sets a default :noop option' do
guard.options[:noop].should be_false
end
end

context 'with other options than the default ones' do
let(:guard) { Guard::CoffeeScript.new(nil, { :output => 'output_folder', :bare => true, :shallow => true, :hide_success => true }) }
let(:guard) { Guard::CoffeeScript.new(nil, { :output => 'output_folder', :bare => true, :shallow => true, :hide_success => true, :noop => true }) }

it 'sets the provided :bare option' do
guard.options[:bare].should be_true
Expand All @@ -38,6 +42,10 @@
it 'sets the provided :hide_success option' do
guard.options[:hide_success].should be_true
end

it 'sets a provided :noop option' do
guard.options[:noop].should be_true
end
end

context 'with a input option' do
Expand Down Expand Up @@ -97,7 +105,8 @@
Guard::CoffeeScript::Runner.should_receive(:run).with(['a.coffee'], [], {
:bare => false,
:shallow => false,
:hide_success => false }).and_return [['a.js'], true]
:hide_success => false,
:noop => false}).and_return [['a.js'], true]
guard.run_on_change(['a.coffee', 'b.coffee'])
end

Expand Down

0 comments on commit 87dcc4f

Please sign in to comment.