Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 4d3744ff43430bdae9dc4b55264912320872fa9a 0 parents
@thibaudgg thibaudgg authored
11 .gitignore
@@ -0,0 +1,11 @@
+pkg/*
+*.gem
+.bundle
+
+## MAC OS
+.DS_Store
+.Trashes
+.com.apple.timemachine.supported
+.fseventsd
+Desktop DB
+Desktop DF
3  Gemfile
@@ -0,0 +1,3 @@
+source "http://rubygems.org"
+
+gemspec
45 Gemfile.lock
@@ -0,0 +1,45 @@
+PATH
+ remote: .
+ specs:
+ guard (0.1.0.beta.1)
+ growl (~> 1.0.3)
+ libnotify (~> 0.1.3)
+ rb-inotify
+ sys-uname (~> 0.8.4)
+ thor (~> 0.14.2)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ diff-lcs (1.1.2)
+ ffi (0.6.3)
+ rake (>= 0.8.7)
+ growl (1.0.3)
+ libnotify (0.1.4)
+ ffi (>= 0.6.2)
+ rake (0.8.7)
+ rb-inotify (0.8.1)
+ ffi (>= 0.5.0)
+ rspec (2.0.0.beta.19)
+ rspec-core (= 2.0.0.beta.19)
+ rspec-expectations (= 2.0.0.beta.19)
+ rspec-mocks (= 2.0.0.beta.19)
+ rspec-core (2.0.0.beta.19)
+ rspec-expectations (2.0.0.beta.19)
+ diff-lcs (>= 1.1.2)
+ rspec-mocks (2.0.0.beta.19)
+ sys-uname (0.8.4)
+ thor (0.14.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ bundler (~> 1.0.1)
+ growl (~> 1.0.3)
+ guard!
+ libnotify (~> 0.1.3)
+ rb-inotify
+ rspec (~> 2.0.0.beta.22)
+ sys-uname (~> 0.8.4)
+ thor (~> 0.14.2)
25 Guardfile
@@ -0,0 +1,25 @@
+# guard 'spork', :rspec_port => 9010 do
+# watch('^config/initializers/.*')
+# end
+
+def super
+ `say yo`
+end
+
+guard 'rspec', :version => 2 do
+ watch('^spec/(.*)_spec.rb')
+ watch('^lib/(.*).rb') { |m| "spec/#{m[1]}_spec.rb" }
+ watch('^spec/spec_helper.rb') { "spec" }
+ # watch('^spec/spec_helper.rb') { `say hello` }
+ # watch('^spec/(.*)_spec\.rb')
+ # watch('^app/(.*)\.rb') { |m| "spec/#{m[1]}_spec.rb" }
+ # watch('^app/(.*)\.html.erb') { |m| "spec/#{m[1]}_spec.rb" }
+ # watch('^lib/(.*)\.rb') { |m| "spec/lib/#{m[1]}_spec.rb" }
+ # watch('^spec/spec_helper\.rb') { |m| "spec" }
+ # watch('^config/routes\.rb') { |m| "spec/routing" }
+ # watch('^spec/factories\.rb') { |m| "spec/model" }
+ # watch('^app/controllers/application_controller\.rb') { |m| "spec/controllers" }
+end
+
+# guard 'livereload' do
+# end
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Thibaud Guillaume-Gentil
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3  README.rdoc
@@ -0,0 +1,3 @@
+= Guard
+
+Documentation is coming.
6 Rakefile
@@ -0,0 +1,6 @@
+require 'bundler'
+Bundler::GemHelper.install_tasks
+
+require 'rspec/core/rake_task'
+RSpec::Core::RakeTask.new(:spec)
+task :default => :spec
6 bin/guard
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+
+require 'guard'
+require 'guard/cli'
+
+Guard::CLI.start
25 bin/inotify_watch
@@ -0,0 +1,25 @@
+#!/usr/bin/env ruby
+require 'rubygems'
+require 'rb-inotify'
+
+folders = Array.new
+notifier = INotify::Notifier.new
+
+notifier.watch(ARGV.first || '.', :modify, :recursive) do |event|
+ dir = File.expand_path(File.dirname(event.absolute_name)) + '/'
+ if !folders.include?(dir)
+ folders << dir
+ end
+end
+
+while true do
+ notifier.process
+
+ if !folders.empty?
+ $stdout.puts folders.join(' ')
+ $stdout.flush
+ folders.clear
+ end
+
+ sleep(0.1)
+end
18 ext/extconf.rb
@@ -0,0 +1,18 @@
+# Workaround to make Rubygems believe it builds a native gem
+require 'mkmf'
+create_makefile('none')
+
+if `uname -s`.chomp == 'Darwin'
+ gem_root = File.expand_path(File.join('..'))
+ darwin_verion = `uname -r`.to_i
+ sdk_verion = { 9 => '10.5', 10 => '10.6', 11 => '10.7' }[darwin_verion]
+
+ raise "Darwin #{darwin_verion} is not supported" unless sdk_verion
+
+ # Compile the actual fsevent_watch binary
+ system("CFLAGS='-isysroot /Developer/SDKs/MacOSX#{sdk_verion}.sdk -mmacosx-version-min=#{sdk_verion}' /usr/bin/gcc -framework CoreServices -o '#{gem_root}/bin/fsevent_watch' fsevent/fsevent_watch.c")
+
+ unless File.executable?("#{gem_root}/bin/fsevent_watch")
+ raise "Compilation of fsevent_watch failed (see README)"
+ end
+end
44 ext/fsevent/fsevent_watch.c
@@ -0,0 +1,44 @@
+#include <CoreServices/CoreServices.h>
+
+void callback(ConstFSEventStreamRef streamRef,
+ void *clientCallBackInfo,
+ size_t numEvents,
+ void *eventPaths,
+ const FSEventStreamEventFlags eventFlags[],
+ const FSEventStreamEventId eventIds[]
+) {
+ // Print modified dirs
+ int i;
+ char **paths = eventPaths;
+ for (i = 0; i < numEvents; i++) {
+ printf("%s", paths[i]);
+ printf(" ");
+ }
+ printf("\n");
+ fflush(stdout);
+}
+
+int main (int argc, const char * argv[]) {
+ // Create event stream
+ CFStringRef pathToWatch = CFStringCreateWithCString(kCFAllocatorDefault, argv[1], kCFStringEncodingUTF8);
+ CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&pathToWatch, 1, NULL);
+ void *callbackInfo = NULL;
+ FSEventStreamRef stream;
+ CFAbsoluteTime latency = 0.1;
+ stream = FSEventStreamCreate(
+ kCFAllocatorDefault,
+ callback,
+ callbackInfo,
+ pathsToWatch,
+ kFSEventStreamEventIdSinceNow,
+ latency,
+ kFSEventStreamCreateFlagNone
+ );
+
+ // Add stream to run loop
+ FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+ FSEventStreamStart(stream);
+ CFRunLoopRun();
+
+ return 2;
+}
32 guard.gemspec
@@ -0,0 +1,32 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path('../lib', __FILE__)
+require 'guard/version'
+
+Gem::Specification.new do |s|
+ s.name = 'guard'
+ s.version = Guard::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = ['Thibaud Guillaume-Gentil']
+ s.email = ['thibaud@thibaud.me']
+ s.homepage = 'http://rubygems.org/gems/guard'
+ s.summary = 'Guard keep an eye on your files event'
+ s.description = 'Guard is a command line tool to easly manage script launch when your files change'
+
+ s.rubyforge_project = 'guard'
+
+ s.add_development_dependency 'bundler', '~> 1.0.1'
+ s.add_development_dependency 'rspec', '~> 2.0.0.beta.22'
+
+ s.add_dependency 'thor', '~> 0.14.2'
+ s.add_dependency 'sys-uname', '~> 0.8.4'
+ # Mac OS X
+ s.add_dependency 'growl', '~> 1.0.3'
+ # Linux
+ s.add_dependency 'rb-inotify'
+ s.add_dependency 'libnotify', '~> 0.1.3'
+
+ s.files = Dir.glob('{bin,images,lib,ext}/**/*') + %w[LICENSE README.rdoc]
+ s.extensions = ['ext/extconf.rb']
+ s.executable = 'guard'
+ s.require_path = 'lib'
+end
BIN  images/failed.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  images/pending.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  images/success.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 lib/guard.rb
@@ -0,0 +1,49 @@
+module Guard
+
+ autoload :UI, 'guard/ui'
+ autoload :Dsl, 'guard/dsl'
+ autoload :Interactor, 'guard/interactor'
+ autoload :Listener, 'guard/listener'
+ autoload :Watcher, 'guard/watcher'
+ autoload :Notifier, 'guard/notifier'
+
+ class << self
+ attr_accessor :options, :guards, :listener
+
+ def start(options = {})
+ @options = options
+ @listener = Listener.new
+ @guards = []
+
+ Dsl.evaluate_guardfile
+ Interactor.init_signal_traps
+
+ listener.on_change do |files|
+ run do
+ guards.each do |guard|
+ paths = Watcher.match_files(guard, files)
+ guard.run_on_change(paths) unless paths.empty?
+ end
+ end
+ end
+
+ UI.info "Guard is now watching at '#{Dir.pwd}'"
+ guards.each(&:start)
+ listener.start
+ end
+
+ def add_guard(name, watchers = [], options = {})
+ require "guard/#{name.downcase}"
+ guard_class = ObjectSpace.each_object(Class).detect { |c| c.to_s.downcase.match "^guard::#{name.downcase}" }
+ @guards << guard_class.new(watchers, options)
+ end
+
+ def run
+ listener.stop
+ yield
+ listener.start
+ end
+
+ end
+
+end
20 lib/guard/cli.rb
@@ -0,0 +1,20 @@
+require 'thor'
+require 'guard/version'
+
+module Guard
+ class CLI < Thor
+ default_task :start
+
+ desc "start", "Starts guard"
+ method_option :clear, :type => :boolean, :default => false, :aliases => '-c', :banner => "Auto clear shell after each change"
+ def start
+ Guard.start(options)
+ end
+
+ desc "version", "Prints the guard's version information"
+ def version
+ Guard::UI.info "Guard version #{Guard::VERSION}"
+ end
+ map %w(-v --version) => :version
+ end
+end
24 lib/guard/dsl.rb
@@ -0,0 +1,24 @@
+module Guard
+ class Dsl
+
+ def self.evaluate_guardfile
+ guardfile = "#{Dir.pwd}/Guardfile"
+ dsl = new
+ dsl.instance_eval(File.read(guardfile.to_s), guardfile.to_s, 1)
+ rescue
+ UI.error "Guardfile not found or invalid"
+ exit 1
+ end
+
+ def guard(name, options = {}, &definition)
+ @watchers = []
+ definition.call
+ Guard.add_guard(name, @watchers, options)
+ end
+
+ def watch(pattern, &action)
+ @watchers << Guard::Watcher.new(pattern, action)
+ end
+
+ end
+end
34 lib/guard/guard.rb
@@ -0,0 +1,34 @@
+module Guard
+ class Guard
+ attr_accessor :watchers, :options
+
+ def initialize(watchers = [], options = {})
+ @watchers, @options = watchers, options
+ end
+
+ # ================
+ # = Guard method =
+ # ================
+
+ def start
+ true
+ end
+
+ def stop
+ true
+ end
+
+ def reload
+ true
+ end
+
+ def run_all
+ true
+ end
+
+ def run_on_change(paths)
+ true
+ end
+
+ end
+end
32 lib/guard/interactor.rb
@@ -0,0 +1,32 @@
+module Guard
+ module Interactor
+
+ def self.init_signal_traps
+ # Run all (Ctrl-\)
+ Signal.trap('QUIT') do
+ ::Guard.run do
+ ::Guard.guards.each(&:run_all)
+ end
+ end
+
+ # Stop (Ctrl-C)
+ Signal.trap('INT') do
+ ::Guard.listener.stop
+ if ::Guard.guards.all?(&:stop)
+ UI.info "Bye bye...", :reset => true, :clear => false
+ abort("\n")
+ else
+ ::Guard.listener.start
+ end
+ end
+
+ # Reload (Ctrl-Z)
+ Signal.trap('TSTP') do
+ ::Guard.run do
+ ::Guard.guards.each(&:reload)
+ end
+ end
+ end
+
+ end
+end
64 lib/guard/listener.rb
@@ -0,0 +1,64 @@
+require 'sys/uname'
+
+module Guard
+ class Listener
+ attr_reader :last_event, :callback, :pipe
+
+ def initialize
+ update_last_event
+ end
+
+ def on_change(&block)
+ @callback = block
+ end
+
+ def start
+ @pipe = case Sys::Uname.sysname
+ when 'Darwin'
+ IO.popen("#{bin_path}/fsevent_watch .")
+ when 'Linux'
+ IO.popen("#{bin_path}/inotify_watch .")
+ end
+ watch_change
+ end
+
+ def stop
+ Process.kill("HUP", pipe.pid) if pipe
+ end
+
+ private
+
+ def watch_change
+ while !pipe.eof?
+ if line = pipe.readline
+ modified_dirs = line.split(" ")
+ files = modified_files(modified_dirs)
+ update_last_event
+ callback.call(files)
+ end
+ end
+ end
+
+ def modified_files(dirs)
+ files = potentially_modified_files(dirs).select { |file| recent_file?(file) }
+ files.map! { |file| file.gsub("#{Dir.pwd}/", '') }
+ end
+
+ def potentially_modified_files(dirs)
+ Dir.glob(dirs.map { |dir| "#{dir}*" })
+ end
+
+ def recent_file?(file)
+ File.mtime(file) >= last_event
+ end
+
+ def update_last_event
+ @last_event = Time.now
+ end
+
+ def bin_path
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin'))
+ end
+
+ end
+end
45 lib/guard/notifier.rb
@@ -0,0 +1,45 @@
+require 'sys/uname'
+require 'pathname'
+
+case Sys::Uname.sysname
+when 'Darwin'
+ require 'growl'
+when 'Linux'
+ require 'libnotify'
+end
+
+module Guard
+ module Notifier
+
+ def self.notify(message, options = {})
+ unless ENV["GUARD_ENV"] == "test"
+ image = options[:image] || :success
+ title = options[:title] || "Guard"
+ case Sys::Uname.sysname
+ when 'Darwin'
+ Growl.notify message, :title => title, :icon => image_path(image), :name => "Guard"
+ when 'Linux'
+ Libnotify.show :body => message, :summary => title, :icon_path => image_path(image)
+ end
+ end
+ end
+
+ private
+
+ def self.image_path(image)
+ images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
+ case image
+ when :failed
+ images_path.join("failed.png").to_s
+ when :pending
+ images_path.join("pending.png").to_s
+ when :success
+ images_path.join("success.png").to_s
+ else
+ # path given
+ image
+ end
+ end
+
+ end
+end
37 lib/guard/ui.rb
@@ -0,0 +1,37 @@
+module Guard
+ module UI
+ class << self
+
+ def info(message, options = {})
+ unless ENV["GUARD_ENV"] == "test"
+ reset_line if options[:reset]
+ clear if options.key?(:clear) ? options[:clear] : ::Guard.options[:clear]
+ puts reset_color(message) if message != ''
+ end
+ end
+
+ def error(message)
+ puts "ERROR: #{message}"
+ end
+
+ def reset_line
+ print "\r\e "
+ end
+
+ private
+
+ def clear
+ system("clear;")
+ end
+
+ def reset_color(text)
+ color(text, "\e[0m")
+ end
+
+ def color(text, color_code)
+ "#{color_code}#{text}\e[0m"
+ end
+
+ end
+ end
+end
3  lib/guard/version.rb
@@ -0,0 +1,3 @@
+module Guard
+ VERSION = "0.1.0.beta.1"
+end
35 lib/guard/watcher.rb
@@ -0,0 +1,35 @@
+module Guard
+ class Watcher
+ attr_accessor :pattern, :action
+
+ def initialize(pattern, action = nil)
+ @pattern, @action = pattern, action
+ end
+
+ def self.match_files(guard, files)
+ guard.watchers.inject([]) do |paths, watcher|
+ files.each do |file|
+ if matches = file.match(watcher.pattern)
+ if watcher.action
+ begin
+ case watcher.action.arity
+ when -1
+ result = watcher.action.call
+ when 1
+ result = watcher.action.call(matches)
+ end
+ rescue
+ UI.info "Problem with watch action"
+ end
+ paths << result if result.is_a?(String) && result != ''
+ else
+ paths << matches[0]
+ end
+ end
+ end
+ paths
+ end
+ end
+
+ end
+end
25 spec/guard/listener_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Guard::Listener do
+ subject { described_class.new }
+
+ its(:last_event) { should < Time.now }
+
+ describe "start" do
+ let(:pipe_mock) { mock("pipe", :eof? => true) }
+
+ it "should use fsevent_watch on Mac OS X" do
+ Sys::Uname.stub(:sysname).and_return('Darwin')
+ IO.should_receive(:popen).with(/.*\/fsevent_watch\s\./).and_return(pipe_mock)
+ subject.start
+ end
+
+ it "should use inotify_watch on Linux" do
+ Sys::Uname.stub(:sysname).and_return('Linux')
+ IO.should_receive(:popen).with(/.*\/inotify_watch\s\./).and_return(pipe_mock)
+ subject.start
+ end
+
+ end
+
+end
32 spec/guard/notifier_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Guard::Notifier do
+ subject { Guard::Notifier }
+
+ describe "notify" do
+ before(:each) { ENV["GUARD_ENV"] = 'special_test' }
+
+ it "should use Growl on Mac OS X" do
+ Sys::Uname.stub(:sysname).and_return('Darwin')
+ Growl.should_receive(:notify).with("great",
+ :title => "Guard",
+ :icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
+ :name => "Guard"
+ )
+ subject.notify 'great', :title => 'Guard'
+ end
+
+ # it "should use Libnotify on Linux" do
+ # Sys::Uname.stub(:sysname).and_return('Linux')
+ # Libnotify.should_receive(:show).with(
+ # :body => "great",
+ # :summary => 'Guard',
+ # :icon_path => 'image/path'
+ # )
+ # subject.notify 'great', 'Guard', 'image/path'
+ # end
+
+ after(:each) { ENV["GUARD_ENV"] = 'test' }
+ end
+
+end
17 spec/spec_helper.rb
@@ -0,0 +1,17 @@
+require 'rubygems'
+require 'guard'
+require 'rspec'
+
+fixture_path = Pathname.new(File.expand_path('../fixtures/', __FILE__))
+
+RSpec.configure do |config|
+ config.color_enabled = true
+
+ config.before(:each) do
+ ENV["GUARD_ENV"] = 'test'
+ @fixture_path = fixture_path
+ end
+
+ config.after(:all) do
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.