Skip to content
Browse files

version 0.1.0

  • Loading branch information...
1 parent bc548ba commit 4508be7753ccfb7af0fe898b29b993180fcb1970 @mordaroso committed Aug 10, 2012
View
2 .travis.yml
@@ -0,0 +1,2 @@
+rvm:
+ - 1.9.3
View
4 Gemfile
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in guard-motion.gemspec
gemspec
+
+platforms :ruby do
+ gem 'rb-readline'
+end
View
9 Guardfile
@@ -0,0 +1,9 @@
+# A sample Guardfile
+# More info at https://github.com/guard/guard#readme
+
+guard 'rspec', :version => 2 do
+ watch(%r{^spec/.+_spec\.rb$})
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
+ watch('spec/spec_helper.rb') { "spec" }
+end
+
View
102 README.md
@@ -1,29 +1,99 @@
-# Guard::Motion
+# Guard::Motion [![Build Status](https://secure.travis-ci.org/mordaroso/guard-motion.png?branch=master)](http://travis-ci.org/mordaroso/guard-motion)
-TODO: Write a gem description
+Motion guard allows to automatically & intelligently launch [RubyMotion](http://www.rubymotion.com/) specs when files are modified.
-## Installation
+## Install
-Add this line to your application's Gemfile:
+Please be sure to have [Guard](https://github.com/guard/guard) installed before continue.
- gem 'guard-motion'
+Install the gem:
-And then execute:
+```
+$ gem install guard-motion
+```
- $ bundle
+Add it to your Gemfile (inside development group):
-Or install it yourself as:
+``` ruby
+gem 'guard-motion'
+```
- $ gem install guard-motion
+Add guard definition to your Guardfile by running this command:
+
+```
+$ guard init motion
+```
+
+Make sure Guard::Motion is loaded in your project Rakefile:
+
+```
+require 'guard/motion'
+```
## Usage
-TODO: Write usage instructions here
+Please read [Guard usage doc](https://github.com/guard/guard#readme)
+
+## Guardfile
+
+Motion guard can be really adapted to all kind of project setup.
+
+### Typical RubyMotion App
+
+``` ruby
+guard 'motion' do
+ watch(%r{^spec/.+_spec\.rb$})
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
+end
+```
+
+### Typical RubyMotion library
+
+``` ruby
+guard 'motion' do
+ watch(%r{^spec/.+_spec\.rb$})
+ watch(%r{^lib/[^/]+/(.+)\.rb$}) { |m| "./spec/#{m[1]}_spec.rb" }
+end
+```
+
+Please read [Guard doc](https://github.com/guard/guard#readme) for more information about the Guardfile DSL.
+
+## Options
+
+By default, Guard::Motion will only look for spec files within `spec` in your project root. You can configure Guard::Motion to look in additional paths by using the `:spec_paths` option:
+
+``` ruby
+guard 'motion', :spec_paths => ["spec", "vendor/other_project/spec"] do
+ # ...
+end
+```
+If you have only one path to look, you can configure `:spec_paths` option with a string:
+
+``` ruby
+guard 'motion', :spec_paths => "test" do
+ # ...
+end
+```
+
+### List of available options:
+
+``` ruby
+:bundler => false # use "bundle exec" to run the rake command, default: true
+:binstubs => true # use "bin/rake" to run the rake command (takes precedence over :bundle), default: false
+:notification => false # display Growl (or Libnotify) notification after the specs are done running, default: true
+:all_after_pass => false # run all specs after changed specs pass, default: true
+:all_on_start => false # run all the specs at startup, default: true
+:keep_failed => false # keep failed specs until they pass, default: true
+:spec_paths => ["spec"] # specify an array of paths that contain spec files
+```
+
+You can also use a custom binstubs directory using `:binstubs => 'some-dir'`.
+
+Development
+-----------
-## Contributing
+* Source hosted at [GitHub](https://github.com/mordaroso/guard-motion)
+* Report issues/Questions/Feature requests on [GitHub Issues](https://github.com/mordaroso/guard-motion/issues)
-1. Fork it
-2. Create your feature branch (`git checkout -b my-new-feature`)
-3. Commit your changes (`git commit -am 'Added some feature'`)
-4. Push to the branch (`git push origin my-new-feature`)
-5. Create new Pull Request
+Pull requests are very welcome! Make sure your patches are well tested. Please create a topic branch for every separate change
+you make.
View
5 guard-motion.gemspec
@@ -4,11 +4,9 @@ require File.expand_path('../lib/guard/motion/version', __FILE__)
Gem::Specification.new do |gem|
gem.authors = ["mordaroso"]
gem.email = ["mordaroso@gmail.com"]
- gem.description = %q{TODO: Write a gem description}
- gem.summary = %q{TODO: Write a gem summary}
gem.homepage = 'http://rubygems.org/gems/guard-motion'
gem.summary = 'Guard gem for RubyMotion'
- gem.description = 'Guard::Motion automatically runs RubyMotion specs'
+ gem.description = 'Guard::Motion automatically run your specs (much like autotest).'
gem.files = `git ls-files`.split($\)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -18,6 +16,7 @@ Gem::Specification.new do |gem|
gem.version = Guard::MotionVersion::VERSION
gem.add_dependency 'guard', '>= 1.1.0'
+ gem.add_dependency 'rake', '>= 0.9'
gem.add_development_dependency 'bundler', '~> 1.1.0'
gem.add_development_dependency 'rspec', '~> 2.10'
View
86 lib/guard/motion.rb
@@ -0,0 +1,86 @@
+require 'guard'
+require 'guard/guard'
+
+require 'guard/motion/tasks'
+
+module Guard
+ class Motion < Guard
+ autoload :Runner, 'guard/motion/runner'
+
+ # Initialize a Guard.
+ # @param [Array<Guard::Watcher>] watchers the Guard file watchers
+ # @param [Hash] options the custom Guard options
+ def initialize(watchers = [], options = {})
+ super
+ @options = {
+ :all_after_pass => true,
+ :all_on_start => true,
+ :keep_failed => true,
+ :spec_paths => ["spec"]
+ }.merge(options)
+ @last_failed = false
+ @failed_paths = []
+
+ @runner = Runner.new(@options)
+ end
+
+ # Call once when Guard starts. Please override initialize method to init stuff.
+ # @raise [:task_has_failed] when start has failed
+ def start
+ UI.info "Guard::Motion is running"
+ run_all if @options[:all_on_start]
+ end
+
+ # Called when `reload|r|z + enter` is pressed.
+ # This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
+ # @raise [:task_has_failed] when reload has failed
+ def reload
+ @failed_paths = []
+ end
+
+ # Called when just `enter` is pressed
+ # This method should be principally used for long action like running all specs/tests/...
+ # @raise [:task_has_failed] when run_all has failed
+ def run_all
+ passed = @runner.run
+
+ unless @last_failed = !passed
+ @failed_paths = []
+ else
+ throw :task_has_failed
+ end
+ end
+
+ # Called on file(s) modifications that the Guard watches.
+ # @param [Array<String>] paths the changes files or paths
+ # @raise [:task_has_failed] when run_on_change has failed
+ def run_on_changes(paths)
+ paths += @failed_paths if @options[:keep_failed]
+
+ if passed = @runner.run(paths)
+ remove_failed(paths)
+
+ # run all the specs if the run before this one failed
+ if @last_failed && @options[:all_after_pass]
+ @last_failed = false
+ run_all
+ end
+ else
+ @last_failed = true
+ add_failed(paths)
+
+ throw :task_has_failed
+ end
+ end
+
+ private
+ def remove_failed(paths)
+ @failed_paths -= paths if @options[:keep_failed]
+ end
+
+ def add_failed(paths)
+ @failed_paths += paths if @options[:keep_failed]
+ end
+
+ end
+end
View
93 lib/guard/motion/runner.rb
@@ -0,0 +1,93 @@
+module Guard
+ class Motion
+ class Runner
+
+ def initialize(options = {})
+ @options = {
+ :bundler => true,
+ :binstubs => false,
+ :notification => true,
+ }.merge(options)
+ end
+
+ def run(paths = nil, options = {})
+ if paths.nil?
+ paths = all_spec_paths
+ message = options[:message] || "Running all specs"
+ else
+ message = options[:message] || "Running: #{paths.join(' ')}"
+ end
+
+ UI.info(message, :reset => true)
+
+ run_via_shell rake_command(paths)
+ end
+
+ def rake_executable
+ @rake_executable ||= begin
+ binstubs? ? "#{binstubs}/rake" : 'rake'
+ end
+ end
+
+ def rake_command(paths)
+ cmd_parts = []
+ cmd_parts << "bundle exec" if bundle_exec?
+ cmd_parts << rake_executable
+ cmd_parts << "spec:specific[\"#{paths.join(';')}\"]"
+ cmd_parts.compact.join(' ')
+ end
+
+ def run_via_shell(command)
+ success = system(command)
+
+ if @options[:notification] && !success
+ Notifier.notify("Failed", :title => "Motion spec results", :image => :failed, :priority => 2)
+ end
+
+ success
+ end
+
+ def all_spec_paths
+ @options[:spec_paths].map { |spec_path|
+ Dir.glob("#{spec_path}/**/*_spec.rb")
+ }.flatten
+ end
+
+ def bundler_allowed?
+ if @bundler_allowed.nil?
+ @bundler_allowed = File.exist?("#{Dir.pwd}/Gemfile")
+ else
+ @bundler_allowed
+ end
+ end
+
+ def bundler?
+ if @bundler.nil?
+ @bundler = bundler_allowed? && @options[:bundler]
+ else
+ @bundler
+ end
+ end
+
+ def binstubs?
+ if @binstubs.nil?
+ @binstubs = !!@options[:binstubs]
+ else
+ @binstubs
+ end
+ end
+
+ def binstubs
+ if @options[:binstubs] == true
+ "bin"
+ else
+ @options[:binstubs]
+ end
+ end
+
+ def bundle_exec?
+ bundler? && !binstubs?
+ end
+ end
+ end
+end
View
21 lib/guard/motion/tasks.rb
@@ -0,0 +1,21 @@
+# include rake task spec:specific only if included in a rake file
+if Kernel.const_defined?(:Rake)
+ desc "Run a specific list of motion specs"
+ namespace :spec do
+ task :specific, :files do |task, args|
+ files = args[:files]
+
+ if files.nil? || files.empty?
+ puts "No spec file passed to the task."
+ puts "Please run the task like this: `rake spec:specific[./spec/app_delegate_spec.rb;./spec/other_spec.rb]`"
+ exit 1
+ end
+
+ App.config.spec_mode = true
+ spec_files = App.config.spec_files.select{|file_path| !(file_path =~ /_spec.rb$/)}
+ spec_files += files.split(';')
+ App.config.instance_variable_set("@spec_files", spec_files)
+ Rake::Task["simulator"].invoke
+ end
+ end
+end
View
4 lib/guard/motion/templates/Guardfile
@@ -2,8 +2,8 @@ guard 'motion' do
watch(%r{^spec/.+_spec\.rb$})
# RubyMotion App example
- watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
+ watch(%r{^app/(.+)\.rb$}) { |m| "./spec/#{m[1]}_spec.rb" }
# RubyMotion gem example
- watch(%r{^lib/[^/]+/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
+ watch(%r{^lib/[^/]+/(.+)\.rb$}) { |m| "./spec/#{m[1]}_spec.rb" }
end
View
2 lib/guard/motion/version.rb
@@ -1,5 +1,5 @@
module Guard
module MotionVersion
- VERSION = "0.0.1"
+ VERSION = "0.1.0"
end
end
View
50 lib/motion.rb
@@ -1,50 +0,0 @@
-require 'guard'
-require 'guard/guard'
-
-module Guard
- class Motion < Guard
-
- # Initialize a Guard.
- # @param [Array<Guard::Watcher>] watchers the Guard file watchers
- # @param [Hash] options the custom Guard options
- def initialize(watchers = [], options = {})
- super
- end
-
- # Call once when Guard starts. Please override initialize method to init stuff.
- # @raise [:task_has_failed] when start has failed
- def start
- end
-
- # Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits).
- # @raise [:task_has_failed] when stop has failed
- def stop
- end
-
- # Called when `reload|r|z + enter` is pressed.
- # This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
- # @raise [:task_has_failed] when reload has failed
- def reload
- end
-
- # Called when just `enter` is pressed
- # This method should be principally used for long action like running all specs/tests/...
- # @raise [:task_has_failed] when run_all has failed
- def run_all
- end
-
- # Called on file(s) modifications that the Guard watches.
- # @param [Array<String>] paths the changes files or paths
- # @raise [:task_has_failed] when run_on_change has failed
- def run_on_changes(paths)
- end
-
- # Called on file(s) deletions that the Guard watches.
- # @param [Array<String>] paths the deleted files or paths
- # @raise [:task_has_failed] when run_on_change has failed
- def run_on_removals(paths)
- end
-
- end
- end
-end
View
3 spec/fixtures/bundler/Gemfile
@@ -0,0 +1,3 @@
+source "http://rubygems.org"
+
+gem "rake"
View
140 spec/guard/motion/runner_spec.rb
@@ -0,0 +1,140 @@
+require 'spec_helper'
+
+module Guard
+ class Motion
+
+ describe Runner do
+ subject { described_class.new }
+
+ describe '#run' do
+ context 'when passed an empty paths list' do
+ it 'returns false' do
+ subject.run([]).should be_false
+ end
+ end
+
+ context 'in a folder without Bundler' do
+ before do
+ Dir.stub(:pwd).and_return(@fixture_path.join('empty'))
+ end
+
+ it 'runs without bundler' do
+ subject.should_receive(:system).with(
+ "rake spec:specific[\"something\"]"
+ ).and_return(true)
+
+ subject.run(['something'])
+ end
+ end
+
+ context 'in a folder with Bundler' do
+ before do
+ Dir.stub(:pwd).and_return(@fixture_path.join('bundler'))
+ end
+
+ it 'runs with Bundler' do
+ subject.should_receive(:system).with(
+ "bundle exec rake spec:specific[\"something\"]"
+ ).and_return(true)
+
+ subject.run(['something'])
+ end
+
+ describe 'notification' do
+ it 'notifies when Motion specs fails to execute' do
+ subject.should_receive(:rake_command) { "`exit 1`" }
+ ::Guard::Notifier.should_receive(:notify).with(
+ 'Failed', :title => 'Motion spec results', :image => :failed, :priority => 2
+ )
+
+ subject.run(['spec'])
+ end
+
+ it 'does not notify that Motion specs failed when the specs pass' do
+ subject.should_receive(:rake_command) { "`exit 0`" }
+ ::Guard::Notifier.should_not_receive(:notify)
+
+ subject.run(['spec'])
+ end
+ end
+
+ describe 'options' do
+
+ describe ':bundler' do
+ context ':bundler => false' do
+ subject { described_class.new(:bundler => false) }
+
+ it 'runs without Bundler' do
+ subject.should_receive(:system).with(
+ "rake spec:specific[\"spec\"]"
+ ).and_return(true)
+
+ subject.run(['spec'])
+ end
+ end
+ end
+
+ describe ':binstubs' do
+ context ':bundler => false, :binstubs => true' do
+ subject { described_class.new(:bundler => false, :binstubs => true) }
+
+ it 'runs without Bundler and with binstubs' do
+ subject.should_receive(:system).with(
+ "bin/rake spec:specific[\"spec\"]"
+ ).and_return(true)
+
+ subject.run(['spec'])
+ end
+ end
+
+ context ':bundler => true, :binstubs => true' do
+ subject { described_class.new(:bundler => true, :binstubs => true) }
+
+ it 'runs without Bundler and binstubs' do
+ subject.should_receive(:system).with(
+ "bin/rake spec:specific[\"spec\"]"
+ ).and_return(true)
+
+ subject.run(['spec'])
+ end
+ end
+
+ context ':bundler => true, :binstubs => "dir"' do
+ subject { described_class.new(:bundler => true, :binstubs => 'dir') }
+
+ it 'runs without Bundler and binstubs in custom directory' do
+ subject.should_receive(:system).with(
+ "dir/rake spec:specific[\"spec\"]"
+ ).and_return(true)
+
+ subject.run(['spec'])
+ end
+ end
+ end
+
+ describe ':notification' do
+ context ':notification => false' do
+ subject { described_class.new(:notification => false) }
+
+ it 'runs without notification formatter' do
+ subject.should_receive(:system).with(
+ "bundle exec rake spec:specific[\"spec\"]"
+ ).and_return(true)
+
+ subject.run(['spec'])
+ end
+
+ it "doesn't notify when specs fails" do
+ subject.should_receive(:system) { mock('res', :success? => false, :exitstatus => 2) }
+ ::Guard::Notifier.should_not_receive(:notify)
+
+ subject.run(['spec'])
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
View
145 spec/guard/motion_spec.rb
@@ -0,0 +1,145 @@
+require 'spec_helper'
+
+module Guard
+ describe Motion do
+ let(:default_options) do
+ {
+ :all_after_pass => true, :all_on_start => true, :keep_failed => true,
+ :spec_paths => ['spec']
+ }
+ end
+ subject { described_class.new }
+
+ let(:runner) { mock(described_class::Runner) }
+
+ before do
+ described_class::Runner.stub(:new => runner)
+ end
+
+ shared_examples_for 'clear failed paths' do
+ it 'should clear the previously failed paths' do
+ runner.should_receive(:run).with(['spec/foo']) { false }
+ expect { subject.run_on_changes(['spec/foo']) }.to throw_symbol :task_has_failed
+
+ runner.should_receive(:run) { true }
+ expect { subject.run_all }.to_not throw_symbol # this actually clears the failed paths
+
+ runner.should_receive(:run).with(['spec/bar']) { true }
+ subject.run_on_changes(['spec/bar'])
+ end
+ end
+
+ describe '.initialize' do
+ it 'creates a runner' do
+ described_class::Runner.should_receive(:new).with(default_options.merge(:foo => :bar))
+
+ described_class.new([], :foo => :bar)
+ end
+ end
+
+ describe '#start' do
+ it 'calls #run_all' do
+ subject.should_receive(:run_all)
+ subject.start
+ end
+
+ context ':all_on_start option is false' do
+ let(:subject) { subject = described_class.new([], :all_on_start => false) }
+
+ it "doesn't call #run_all" do
+ subject.should_not_receive(:run_all)
+ subject.start
+ end
+ end
+ end
+
+ describe '#run_all' do
+ it "runs all specs specified by the default 'spec_paths' option" do
+ runner.should_receive(:run) { true }
+
+ subject.run_all
+ end
+
+ it "throws task_has_failed if specs don't passed" do
+ runner.should_receive(:run) { false }
+
+ expect { subject.run_all }.to throw_symbol :task_has_failed
+ end
+
+ it_should_behave_like 'clear failed paths'
+ end
+
+ describe '#reload' do
+ it_should_behave_like 'clear failed paths'
+ end
+
+ describe '#run_on_changes' do
+ it 'runs rspec with paths' do
+ runner.should_receive(:run).with(['spec/foo']) { true }
+
+ subject.run_on_changes(['spec/foo'])
+ end
+
+ # context 'the changed specs pass after failing' do
+ # it 'calls #run_all' do
+ # runner.should_receive(:run).with(['spec/foo']) { false }
+
+ # expect { subject.run_on_changes(['spec/foo']) }.to throw_symbol :task_has_failed
+
+ # runner.should_receive(:run).with(['spec/foo']) { true }
+ # subject.should_receive(:run_all)
+
+ # expect { subject.run_on_changes(['spec/foo']) }.to_not throw_symbol
+ # end
+
+ # context ':all_after_pass option is false' do
+ # subject { described_class.new([], :all_after_pass => false) }
+
+ # it "doesn't call #run_all" do
+ # runner.should_receive(:run).with(['spec/foo']) { false }
+
+ # expect { subject.run_on_changes(['spec/foo']) }.to throw_symbol :task_has_failed
+
+ # runner.should_receive(:run).with(['spec/foo']) { true }
+ # subject.should_not_receive(:run_all)
+
+ # expect { subject.run_on_changes(['spec/foo']) }.to_not throw_symbol
+ # end
+ # end
+ # end
+
+ context 'the changed specs pass without failing' do
+ it "doesn't call #run_all" do
+ runner.should_receive(:run).with(['spec/foo']) { true }
+
+ subject.should_not_receive(:run_all)
+
+ subject.run_on_changes(['spec/foo'])
+ end
+ end
+
+ it 'keeps failed spec and rerun them later' do
+ subject = described_class.new([], :all_after_pass => false)
+
+ runner.should_receive(:run).with(['spec/bar']) { false }
+
+ expect { subject.run_on_changes(['spec/bar']) }.to throw_symbol :task_has_failed
+
+ runner.should_receive(:run).with(['spec/foo', 'spec/bar']) { true }
+
+ subject.run_on_changes(['spec/foo'])
+
+ runner.should_receive(:run).with(['spec/foo']) { true }
+
+ subject.run_on_changes(['spec/foo'])
+ end
+
+ it "throws task_has_failed if specs doesn't pass" do
+ runner.should_receive(:run).with(['spec/foo']) { false }
+
+ expect { subject.run_on_changes(['spec/foo']) }.to throw_symbol :task_has_failed
+ end
+ end
+
+ end
+end
View
17 spec/spec_helper.rb
@@ -0,0 +1,17 @@
+require 'guard/motion'
+require 'rspec'
+
+ENV["GUARD_ENV"] = 'test'
+
+Dir["#{File.expand_path('..', __FILE__)}/support/**/*.rb"].each { |f| require f }
+
+RSpec.configure do |config|
+ config.color_enabled = true
+ config.filter_run :focus => true
+ config.run_all_when_everything_filtered = true
+
+ config.before(:each) do
+ @fixture_path = Pathname.new(File.expand_path('../fixtures/', __FILE__))
+ @lib_path = Pathname.new(File.expand_path('../../lib/', __FILE__))
+ end
+end

0 comments on commit 4508be7

Please sign in to comment.
Something went wrong with that request. Please try again.