Full Code of alloy/kicker for AI

master 5dcd9a9e91d3 cached
48 files
82.6 KB
23.7k tokens
155 symbols
1 requests
Download .txt
Repository: alloy/kicker
Branch: master
Commit: 5dcd9a9e91d3
Files: 48
Total size: 82.6 KB

Directory structure:
gitextract_s68j9c9x/

├── .gitignore
├── .kick
├── .travis.yml
├── Gemfile
├── LICENSE
├── README.rdoc
├── Rakefile
├── TODO.rdoc
├── bin/
│   └── kicker
├── kicker.gemspec
├── lib/
│   ├── kicker/
│   │   ├── callback_chain.rb
│   │   ├── core_ext.rb
│   │   ├── fsevents.rb
│   │   ├── job.rb
│   │   ├── notification.rb
│   │   ├── options.rb
│   │   ├── recipes/
│   │   │   ├── could_not_handle_file.rb
│   │   │   ├── dot_kick.rb
│   │   │   ├── execute_cli_command.rb
│   │   │   ├── ignore.rb
│   │   │   ├── jstest.rb
│   │   │   ├── rails.rb
│   │   │   └── ruby.rb
│   │   ├── recipes.rb
│   │   ├── utils.rb
│   │   └── version.rb
│   └── kicker.rb
├── rakelib/
│   └── gem_release.rake
└── spec/
    ├── callback_chain_spec.rb
    ├── core_ext_spec.rb
    ├── filesystem_change_spec.rb
    ├── fixtures/
    │   └── a_file_thats_reloaded.rb
    ├── fsevents_spec.rb
    ├── initialization_spec.rb
    ├── job_spec.rb
    ├── kicker_spec.rb
    ├── notification_spec.rb
    ├── options_spec.rb
    ├── recipes/
    │   ├── could_not_handle_file_spec.rb
    │   ├── dot_kick_spec.rb
    │   ├── execute_cli_command_spec.rb
    │   ├── ignore_spec.rb
    │   ├── jstest_spec.rb
    │   ├── rails_spec.rb
    │   └── ruby_spec.rb
    ├── recipes_spec.rb
    ├── spec_helper.rb
    └── utils_spec.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.*.sw?
*.gem
/.rbenv-version
.DS_Store
/coverage
/rdoc
/pkg
/html
/Gemfile.lock
/tmp/
/.idea


================================================
FILE: .kick
================================================
recipe :ignore
recipe :ruby

Kicker::Recipes::Ruby.runner_bin = 'bacon'

process do |files|
  test_files = files.take_and_map do |file|
    case file
    when %r{^lib/kicker(\.rb|/validate\.rb|/growl\.rb)$}
      ["spec/initialization_spec.rb", ("spec/filesystem_change_spec.rb" if $1 == '.rb')]
    when %r{^lib/kicker/(.+)\.rb$}
      "spec/#{$1}_spec.rb"
    end
  end
  
  Kicker::Recipes::Ruby.run_tests test_files
end

process do |files|
  execute("rake docs:generate && open -a Safari html/index.html") if files.delete("README.rdoc")
end

startup do
  log "Good choice mate!"
end

# process do
#   execute "ls -l" do |status|
#     if status.before?
#       status.stdout? ? "Here we go!: #{status.command}" : "Here we go! GROWL"
#     elsif status.after?
#       if status.success?
#         status.stdout? ? "Nice!\n\n#{status.output}" : "Nice!"
#       else
#         status.stdout? ? "Damn brow!\n\n#{status.output}" : "Damn bro!"
#       end
#     end
#   end
# end


================================================
FILE: .travis.yml
================================================
rvm:
  - 2.1.3
  - 2.0.0
  - 1.9.3

script: "rake"


================================================
FILE: Gemfile
================================================
source "https://rubygems.org"
gemspec

gem 'rake'

platforms :mri_18 do
  gem 'rdoc'
end


================================================
FILE: LICENSE
================================================
Kicker:

Copyright (c) 2009 Eloy Duran <eloy.de.enige@gmail.com>

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.

======================================================================

Rucola: http://github.com/alloy/rucola/tree/master

Copyright (c) 2008 Eloy Duran <eloy.de.enige@gmail.com>

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.

======================================================================

growlnotifier: http://github.com/psychs/growlnotifier/tree/master

Copyright (c) 2007-2008 Satoshi Nakagawa <psychs@limechat.net>, Eloy Duran <e.duran@superalloy.nl>
You can redistribute it and/or modify it under the same terms as Ruby.

================================================
FILE: README.rdoc
================================================
= Kicker

{<img src="https://travis-ci.org/alloy/kicker.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/alloy/kicker]

A lean, agnostic, flexible file-change watcher.

== Installation

  $ gem install kicker -s http://gemcutter.org

== The short version

  Usage: ./bin/kicker [options] [paths to watch]

    Available recipes: ignore, jstest, rails, ruby.

      -s, --silent                     Keep output to a minimum.
      -q, --quiet                      Quiet output. Don't print timestamps when logging.
      -c, --clear                      Clear console before each run.
      -l, --latency [FLOAT]            The time to collect file change events before acting on them. Defaults to 1 second.
      -r, --recipe [NAME]              A named recipe to load.
      -e, --execute [COMMAND]          The command to execute.
      -b, --ruby [PATH]                Use an alternate Ruby binary for spawned test runners. (Default is `ruby')


== The long version

=== Execute a shell command

Show all files, whenever a change occurs in the current work directory:

  $ kicker -e "ls -l" .

Show all files, whenever a change occurs to a specific file:

  $ kicker -e "ls -l" foo.txt

Or use it as a ghetto-autotest, running tests whenever files change:

  $ kicker -e "ruby test/test_case.rb" test/test_case.rb lib/file.rb

Et cetera.

=== Using recipes

A recipe is a predefined handler. You can use as many as you like, by
specifying them with the <tt>--recipe</tt> (<tt>-r</tt>) option.

For instance, when in the root of a typical Ruby on Rails application, using
the <tt>rails</tt> recipe will map models, concerns, controllers, helpers, and
views to their respective test files. These will then all be ran with Ruby.

A few recipes come shipped with Kicker:
* Typical Ruby library.
* Ruby on Rails, as aforementioned.
* JavaScript tests, to run it needs
  HeadlessSquirrel[http://github.com/Fingertips/Headless-squirrel].
* Ignore, ignores logs, tmp, and svn and git files.

Add your own shared recipes to <tt>~/.kick</tt> folder or
current working directory <tt>.kick</tt>.

=== Project specific handlers

Most of the time, you’ll want to create handlers specific to the project at
hand. This can be done by adding your handlers to a <tt>.kick</tt> file and
running Kicker from the directory containing it.

This file is reloaded once saved. No need to stop Kicker.

== Writing handlers

Whenever file-change events occur, Kicker will go through a chain of handlers
until that the files list is empty, or the end of the chain is reached.

Handlers are objects that respond to <tt>#call</tt>. These are typically Proc
objects. (If you know Rack, you’re familiar with this concept.) Every handler
gets passed a list of changed files and can decide whether or not to act on
them. Normally when handling a file, you should remove it from the files list,
unless you want to let the file fall through to another handler. In the same
way, one can add files to handler to the files list.

==== Time for a simple example

  process do |files|
    execute("rake docs:generate && open -a Safari html/index.html") if files.delete("README.rdoc")
  end

A handler is defined by passing a block to <tt>process</tt>. Which is one of
three possible callback chains to add your handlers to, the others being:
<tt>pre_process</tt> and <tt>post_process</tt>. See Kernel for more info.

Then <tt>README.rdoc</tt> is deleted from the files array. If it did exist in
the array and was deleted, a shell command is executed which runs a rake task
to generate rdoc and open the docs with Safari.

==== Something more elaborate.

Consider a Rails application with a mailer. Since the naming convention of
mailer views tend to be fairly application specific, a specific handler has to
be added:

  process do |files|
    test_files = files.take_and_map do |file|
      if path =~ %r{^app/views/mailer/\w+\.erb$}
        'test/unit/mailer_test.rb'

      # elsif ... handle more app specific stuff
      end
    end

    Ruby.run_tests test_files
  end

The files list is iterated over with the Array#take_and_map method, which both
removes and maps the results. This is an easy way to do a common thing in
recipes. See Kicker::ArrayExt for details.

The handler then checks if the file is a mailer view and if so runs the
mailers test case. Ruby.run_tests runs them with something like the following
command:

  execute "ruby -r #{test_files.join(' -r ')} -e ''" unless test_files.empty?

See Kernel for more info on the utility methods.

To load recipes from your <tt>~/.kick</tt> file:

  recipe :ignore
  ignore(/^data\//)

That’s basically it, just remember that the order of specifying handlers _can_
be important in your decision on where to specify handlers.

== Notifiers

For platform specific notifications we use the notify gem. For supported
backends see: https://github.com/jugyo/notify#feature.

You select the notify backend by setting the NOTIFY environment variable.

  gem install terminal-notifier
  env NOTIFY=terminal-notifier kicker

== Contributors

* Manfred Stienstra (@manfred)
* Cristi Balan (@evilchelu)
* Damir Zekic (@sidonath)
* Adam Keys (@therealadam)


================================================
FILE: Rakefile
================================================
require 'rdoc/task'

desc "Run specs"
task :spec do
  # shuffle to ensure that tests are run in different order
  files = FileList['spec/**/*_spec.rb'].shuffle
  sh "bundle exec bacon #{files.map { |file| "'#{file}'" }.join(' ')}"
end

namespace :docs do
  RDoc::Task.new('generate') do |t|
    t.main = "README.rdoc"
    t.rdoc_files.include("README.rdoc", "lib/**/*.rb")
    t.options << '--charset=utf8'
  end
end

task :docs => 'docs:generate' do
  FileUtils.cp_r('images', 'html')
end

task :default => :spec


================================================
FILE: TODO.rdoc
================================================
* Move larger parts of README to the GitHub wiki so the README is to the point.
* Add a recipe which implements the basic autotest mapping API.
* Make the loggers, stdout and growl, work in a chain so one can add others.
  This should improve portability as well, as people can easily insert growl
  alternatives for their platform.

================================================
FILE: bin/kicker
================================================
#!/usr/bin/env ruby

if $0 == __FILE__
  $:.unshift File.expand_path('../../lib', __FILE__)
  $:.unshift File.expand_path('../../vendor', __FILE__)
  require 'rubygems'
end

require 'kicker'
Kicker.run


================================================
FILE: kicker.gemspec
================================================
# -*- encoding: utf-8 -*-

$:.unshift File.expand_path('../lib', __FILE__)
require 'kicker/version'
require 'date'

Gem::Specification.new do |s|
  s.name     = "kicker"
  s.version  = Kicker::VERSION
  s.date     = Time.new
  s.license  = 'MIT'

  s.summary  = "A lean, agnostic, flexible file-change watcher."
  s.description = "Allows you to fire specific command on file-system change."
  s.authors  = ["Eloy Duran", "Manfred Stienstra"]
  s.homepage = "http://github.com/alloy/kicker"
  s.email    = %w{ eloy.de.enige@gmail.com manfred@fngtps.com }

  s.executables      = %w{ kicker }
  s.require_paths    = %w{ lib vendor }
  s.files            = Dir['bin/kicker',
                           'lib/**/*.rb',
                           'README.rdoc',
                           'LICENSE',
                           'html/images/kikker.jpg']
  s.extra_rdoc_files = %w{ LICENSE README.rdoc }

  s.add_runtime_dependency("listen", '~> 2.7.9')
  s.add_runtime_dependency("notify", '~> 0.5.2')

  s.add_development_dependency("bacon")
  s.add_development_dependency("mocha-on-bacon")
  s.add_development_dependency("activesupport")
  s.add_development_dependency("fakefs", '>= 0.5')
end



================================================
FILE: lib/kicker/callback_chain.rb
================================================
class Kicker
  class CallbackChain < Array #:nodoc:
    alias_method :append_callback,  :push
    alias_method :prepend_callback, :unshift

    def call(files, stop_when_empty = true)
      each do |callback|
        break if stop_when_empty and files.empty?
        callback.call(files)
      end
    end
  end

  class << self
    attr_writer :startup_chain
    def startup_chain
      @startup_chain ||= CallbackChain.new
    end

    attr_writer :pre_process_chain
    def pre_process_chain
      @pre_process_chain ||= CallbackChain.new
    end

    attr_writer :process_chain
    def process_chain
      @process_chain ||= CallbackChain.new
    end

    attr_writer :post_process_chain
    def post_process_chain
      @post_process_chain ||= CallbackChain.new
    end

    attr_writer :full_chain
    def full_chain
      @full_chain ||= CallbackChain.new([pre_process_chain, process_chain, post_process_chain])
    end
  end

  def startup_chain
    self.class.startup_chain
  end

  def pre_process_chain
    self.class.pre_process_chain
  end

  def process_chain
    self.class.process_chain
  end

  def post_process_chain
    self.class.post_process_chain
  end

  def full_chain
    self.class.full_chain
  end
end

module Kernel
  # Adds a handler to the startup chain. This chain is ran once Kicker is done
  # loading _before_ starting the normal operations. Note that an empty files
  # array is given to the callback.
  #
  # Takes a +callback+ object that responds to <tt>#call</tt>, or a block.
  def startup(callback = nil, &block)
    Kicker.startup_chain.append_callback(block ? block : callback)
  end

  # Adds a handler to the pre_process chain. This chain is ran before the
  # process chain and is processed from first to last.
  #
  # Takes a +callback+ object that responds to <tt>#call</tt>, or a block.
  def pre_process(callback = nil, &block)
    Kicker.pre_process_chain.append_callback(block ? block : callback)
  end

  # Adds a handler to the process chain. This chain is ran in between the
  # pre_process and post_process chains. It is processed from first to last.
  #
  # Takes a +callback+ object that responds to <tt>#call</tt>, or a block.
  def process(callback = nil, &block)
    Kicker.process_chain.append_callback(block ? block : callback)
  end

  # Adds a handler to the post_process chain. This chain is ran after the
  # process chain and is processed from last to first.
  #
  # Takes a +callback+ object that responds to <tt>#call</tt>, or a block.
  def post_process(callback = nil, &block)
    Kicker.post_process_chain.prepend_callback(block ? block : callback)
  end
end


================================================
FILE: lib/kicker/core_ext.rb
================================================
class Kicker
  module ArrayExt
    # Deletes elements from self for which the block evaluates to +true+. A new
    # array is returned with those values the block returned. So basically, a
    # combination of reject! and map.
    #
    #   a = [1,2,3]
    #   b = a.take_and_map { |x| x * 2 if x == 2 }
    #   b # => [4]
    #   a # => [1, 3]
    #
    # If +pattern+ is specified then files matching the pattern will be taken.
    #
    #   a = [ 'bar', 'foo/bar' ]
    #   b = a.take_and_map('*/bar') { |x| x }
    #   b # => ['foo/bar']
    #   a # => ['bar']
    #
    # If +flatten_and_compact+ is +true+, the result array will be flattened
    # and compacted. The default is +true+.
    def take_and_map(pattern = nil, flatten_and_compact = true)
      took = []
      reject! do |x|
        next if pattern and !File.fnmatch?(pattern, x)
        if result = yield(x)
          took << result
        end
      end
      if flatten_and_compact
        took.flatten!
        took.compact!
      end
      took
    end
  end
end

Array.send(:include, Kicker::ArrayExt)


================================================
FILE: lib/kicker/fsevents.rb
================================================
# encoding: utf-8

require 'listen'

class Kicker
  class FSEvents
    class FSEvent
      attr_reader :path

      def initialize(path)
        @path = path
      end

      def files
        Dir.glob("#{File.expand_path(path)}/*").map do |filename|
          begin
            [File.mtime(filename), filename]
          rescue Errno::ENOENT
            nil
          end
        end.compact.sort.reverse.map { |_, filename| filename }
      end
    end

    def self.start_watching(paths, options={}, &block)
      listener = Listen.to(*(paths.dup << options)) do |modified, added, removed|
        files = modified + added + removed
        directories = files.map { |file| File.dirname(file) }.uniq
        yield directories.map { |directory| Kicker::FSEvents::FSEvent.new(directory) }
      end
      listener.start
      listener
    end
  end
end


================================================
FILE: lib/kicker/job.rb
================================================
class Kicker
  class Job
    def self.attr_with_default(name, merge_hash = false, &default)
      # If `nil` this returns the `default`, unless explicitely set to `nil` by
      # the user.
      define_method(name) do
        if instance_variable_get("@#{name}_assigned")
          if assigned_value = instance_variable_get("@#{name}")
            merge_hash ? instance_eval(&default).merge(assigned_value) : assigned_value
          end
        else
          instance_eval(&default)
        end
      end
      define_method("#{name}=") do |value|
        instance_variable_set("@#{name}_assigned", true)
        instance_variable_set("@#{name}", value)
      end
    end

    attr_accessor :command, :exit_code, :output

    def initialize(attributes)
      @exit_code = 0
      @output = ''
      attributes.each { |k,v| send("#{k}=", v) }
    end

    def success?
      exit_code == 0
    end

    attr_with_default(:print_before) do
      "Executing: #{command}"
    end

    attr_with_default(:print_after) do
      # Show all output if it wasn't shown before and the command fails.
      "\n#{output}\n\n" if Kicker.silent? && !success?
    end

    # TODO default titles??

    attr_with_default(:notify_before, true) do
      { :title => "Kicker: Executing", :message => command }
    end

    attr_with_default(:notify_after, true)  do
      message = Kicker.silent? ? "" : output
      if success?
        { :title => "Kicker: Success", :message => message }
      else
        { :title => "Kicker: Failed (#{exit_code})", :message => message }
      end
    end
  end
end


================================================
FILE: lib/kicker/notification.rb
================================================
require 'notify'

class Kicker
  module Notification #:nodoc:
    TITLE = 'Kicker'

    class << self
      attr_accessor :use, :app_bundle_identifier
      alias_method :use?, :use

      def notify(options)
        return unless use?

        unless message = options.delete(:message)
          raise "A notification requires a `:message'"
        end

        options = {
          :group    => Dir.pwd,
          :activate => app_bundle_identifier
        }.merge(options)

        Notify.notify(TITLE, message, options)
      end
    end
  end

  Notification.use = ENV['NOTIFY'].to_s != ''
  Notification.app_bundle_identifier = 'com.apple.Terminal'
end



================================================
FILE: lib/kicker/options.rb
================================================
require 'optparse'

class Kicker
  class << self
    attr_accessor :latency, :paths, :silent, :quiet, :clear_console

    def silent?
      @silent
    end

    def quiet?
      @quiet
    end

    def clear_console?
      @clear_console
    end


    def osx?
      RUBY_PLATFORM.downcase.include?("darwin")
    end
  end

  self.latency = 1
  self.paths = %w{ . }
  self.silent = false
  self.quiet = false
  self.clear_console = false

  module Options #:nodoc:
    DONT_SHOW_RECIPES = %w{ could_not_handle_file execute_cli_command dot_kick }

    def self.recipes_for_display
      Kicker::Recipes.recipe_files.map { |f| File.basename(f, '.rb') } - DONT_SHOW_RECIPES
    end

    def self.parser
      @parser ||= OptionParser.new do |opt|
        opt.banner =  "Usage: #{$0} [options] [paths to watch]"
        opt.separator " "
        opt.separator "  Available recipes: #{recipes_for_display.join(", ")}."
        opt.separator " "

        opt.on('-v', 'Print the Kicker version') do
          puts Kicker::VERSION
          exit
        end

        opt.on('-s', '--silent', 'Keep output to a minimum.') do |silent|
          Kicker.silent = true
        end

        opt.on('-q', '--quiet', "Quiet output. Don't print timestamps when logging.") do |quiet|
          Kicker.silent = Kicker.quiet = true
        end

        opt.on('-c', '--clear', "Clear console before each run.") do |clear|
          Kicker.clear_console = true
        end


        opt.on('--[no-]notification', 'Whether or not to send user notifications (on Mac OS X). Defaults to enabled.') do |notifications|
          Notification.use = notifications
        end

        if Kicker.osx?
          opt.on('--activate-app [BUNDLE ID]', "The application to activate when a notification is clicked. Defaults to `com.apple.Terminal'.") do |bundle_id|
            Kicker::Notification.app_bundle_identifier = bundle_id
          end
        end

        opt.on('-l', '--latency [FLOAT]', "The time to collect file change events before acting on them. Defaults to #{Kicker.latency} second.") do |latency|
          Kicker.latency = Float(latency)
        end

        opt.on('-r', '--recipe [NAME]', 'A named recipe to load.') do |name|
          recipe(name)
        end
      end
    end

    def self.parse(argv)
      parser.parse!(argv)
      Kicker.paths = argv unless argv.empty?
    end
  end
end

module Kernel
  # Returns the global OptionParser instance that recipes can use to add
  # options.
  def options
    Kicker::Options.parser
  end
end


================================================
FILE: lib/kicker/recipes/could_not_handle_file.rb
================================================
post_process do |files|
  unless Kicker.silent?
    log('')
    log("Could not handle: #{files.join(', ')}")
    log('')
  end
end


================================================
FILE: lib/kicker/recipes/dot_kick.rb
================================================
module ReloadDotKick #:nodoc
  class << self
    def save_state
      @features_before_dot_kick = $LOADED_FEATURES.dup
      @chains_before_dot_kick = Kicker.full_chain.map { |c| c.dup }
    end

    def call(files)
      reset! if files.delete('.kick')
    end

    def use?
      File.exist?('.kick')
    end

    def load!
      load '.kick'
    end

    def reset!
      remove_loaded_features!
      reset_chains!
      load!
    end

    def reset_chains!
      Kicker.full_chain = nil

      chains = @chains_before_dot_kick.map { |c| c.dup }
      Kicker.pre_process_chain, Kicker.process_chain, Kicker.post_process_chain = *chains
    end

    def remove_loaded_features!
      ($LOADED_FEATURES - @features_before_dot_kick).each do |feat|
        $LOADED_FEATURES.delete(feat)
      end
    end
  end
end

if ReloadDotKick.use?
  startup do
    pre_process ReloadDotKick
    ReloadDotKick.save_state
    ReloadDotKick.load!
  end
end


================================================
FILE: lib/kicker/recipes/execute_cli_command.rb
================================================
options.on('-e', '--execute [COMMAND]', 'The command to execute.') do |command|
  callback = lambda do |files|
    files.clear
    execute "sh -c #{command.inspect}"
  end

  startup callback
  pre_process callback
end


================================================
FILE: lib/kicker/recipes/ignore.rb
================================================
# A recipe which removes files from the files array, thus “ignoring” them.
#
# By default ignores logs, tmp, and svn and git files.
#
# See Kernel#ignore for info on how to ignore files.
module Ignore
  def self.call(files) #:nodoc:
    files.reject! { |file| ignores.any? { |ignore| file =~ ignore } }
  end

  def self.ignores #:nodoc:
    @ignores ||= []
  end

  def self.ignore(regexp_or_string) #:nodoc:
    ignores << (regexp_or_string.is_a?(Regexp) ? regexp_or_string : /^#{regexp_or_string}$/)
  end
end

module Kernel
  # Adds +regexp_or_string+ as an ignore rule.
  #
  #   require 'ignore'
  #
  #   ignore /^data\//
  #   ignore 'Rakefile'
  #
  # <em>Only available if the `ignore' recipe is required.</em>
  def ignore(regexp_or_string)
    Ignore.ignore(regexp_or_string)
  end
end

recipe :ignore do
  pre_process Ignore

  ignore("tmp")
  ignore(/\w+\.log/)
  ignore(/\.(svn|git)\//)
  ignore("svn-commit.tmp")
end


================================================
FILE: lib/kicker/recipes/jstest.rb
================================================
recipe :jstest do
  process do |files|
    test_files = files.take_and_map do |file|
      if file =~ %r{^(test|public)/javascripts/(\w+?)(_test)*\.(js|html)$}
        "test/javascripts/#{$2}_test.html"
      end
    end
    execute "jstest #{test_files.join(' ')}" unless test_files.empty?
  end
end


================================================
FILE: lib/kicker/recipes/rails.rb
================================================
recipe :ruby

class Kicker::Recipes::Rails < Kicker::Recipes::Ruby
  class << self
    # Call these options on the Ruby class which takes the cli options.
    %w{ test_type runner_bin test_cases_root test_options }.each do |delegate|
      define_method(delegate) { Kicker::Recipes::Ruby.send(delegate) }
    end

    # Maps +type+, for instance `models', to a test directory.
    def type_to_test_dir(type)
      if test_type == 'test'
        case type
        when "models"
          "unit"
        when "concerns"
          "unit/concerns"
        when "controllers", "views"
          "functional"
        when "helpers"
          "unit/helpers"
        end
      elsif test_type == 'spec'
        case type
        when "models"
          "models"
        when "concerns"
          "models/concerns"
        when "controllers", "views"
          "controllers"
        when "helpers"
          "helpers"
        end
      end
    end

    # Returns an array consiting of all controller tests.
    def all_controller_tests
      if test_type == 'test'
        Dir.glob("#{test_cases_root}/functional/**/*_test.rb")
      else
        Dir.glob("#{test_cases_root}/controllers/**/*_spec.rb")
      end
    end
  end

  # Returns an array of all tests related to the given model.
  def tests_for_model(model)
    if test_type == 'test'
      %W{
        unit/#{ActiveSupport::Inflector.singularize(model)}
        unit/helpers/#{ActiveSupport::Inflector.pluralize(model)}_helper
        functional/#{ActiveSupport::Inflector.pluralize(model)}_controller
      }
    else
      %W{
        models/#{ActiveSupport::Inflector.singularize(model)}
        helpers/#{ActiveSupport::Inflector.pluralize(model)}_helper
        controllers/#{ActiveSupport::Inflector.pluralize(model)}_controller
      }
    end.map { |f| test_file f }
  end

  def handle!
    @tests.concat(@files.take_and_map do |file|
      case file
      # Run all functional tests when routes.rb is saved
      when 'config/routes.rb'
        Kicker::Recipes::Rails.all_controller_tests

      # Match lib/*
      when /^(lib\/.+)\.rb$/
        test_file($1)

      # Map fixtures to their related tests
      when %r{^#{test_cases_root}/fixtures/(\w+)\.yml$}
        tests_for_model($1)

      # Match any file in app/ and map it to a test file
      when %r{^app/(\w+)([\w/]*)/([\w\.]+)\.\w+$}
        type, namespace, file = $1, $2, $3

        if dir = Kicker::Recipes::Rails.type_to_test_dir(type)
          if type == "views"
            namespace = namespace.split('/')[1..-1]
            file = "#{namespace.pop}_controller"
          end

          test_file File.join(dir, namespace, file)
        end
      end
    end)

    # And let the Ruby handler match other stuff.
    super
  end
end

recipe :rails do
  require 'rubygems' rescue LoadError
  require 'active_support/inflector'

  process Kicker::Recipes::Rails

  # When changing the schema, prepare the test database.
  process do |files|
    execute 'rake db:test:prepare' if files.delete('db/schema.rb')
  end
end


================================================
FILE: lib/kicker/recipes/ruby.rb
================================================
class Kicker::Recipes::Ruby
  class << self
    # Assigns the type of tests to run. Eg: `test' or `spec'.
    attr_writer :test_type

    # Returns the type of tests to run. Eg: `test' or `spec'.
    #
    # Defaults to `test' if no `spec' directory exists.
    def test_type
      @test_type ||= File.exist?('spec') ? 'spec' : 'test'
    end

    # Assigns the ruby command to run the tests with. Eg: `ruby19' or `specrb'.
    #
    # This can be set from the command line with the `-b' or `--ruby' options.
    attr_writer :runner_bin

    # Returns the ruby command to run the tests with. Eg: `ruby' or `spec'.
    #
    # Defaults to `ruby' if test_type is `test' and `spec' if test_type is
    # `spec'.
    def runner_bin
      @runner_bin ||= test_type == 'test' ? 'ruby' : 'rspec'
    end

    # Assigns the root directory of where test cases will be looked up.
    attr_writer :test_cases_root

    # Returns the root directory of where test cases will be looked up.
    #
    # Defaults to the value of test_type. Eg: `test' or `spec'.
    def test_cases_root
      @test_cases_root ||= test_type
    end

    attr_writer :test_options #:nodoc:

    # Assigns extra options that are to be passed on to the runner_bin.
    #
    #   Ruby.test_options << '-I ./lib/foo'
    def test_options
      @test_options ||= []
    end

    def reset!
      @test_type = nil
      @runner_bin = nil
      @test_cases_root = nil
      @test_options = nil
    end

    def runner_command(*parts)
      parts.map do |part|
        case part
        when Array
          part.empty? ? nil : part.join(' ')
        else
          part.to_s
        end
      end.compact.join(' ')
    end

    # Runs the given tests, if there are any, with the method defined by
    # test_type. If test_type is `test' the run_with_test_runner method is
    # used. The same applies when test_type is `spec'.
    def run_tests(tests)
      send("run_with_#{test_type}_runner", tests) unless tests.empty?
    end

    def test_runner_command(tests)
      tests_without_ext = tests.map { |f| f[0,f.size-3] }
      runner_command(runner_bin, %w{ -I. } + test_options, '-r', tests_without_ext.join(' -r '), "-e ''")
    end

    # Runs the given tests with `ruby' as unit-test tests.
    def run_with_test_runner(tests)
      execute(test_runner_command(tests))
    end

    def spec_runner_command(tests)
      runner_command(runner_bin, test_options, tests)
    end

    # Runs the given tests with `spec' as RSpec tests.
    def run_with_spec_runner(tests)
      execute(spec_runner_command(tests))
    end
  end

  def self.call(files) #:nodoc:
    handler = new(files)
    handler.handle!
    run_tests(handler.tests)
  end

  # The list of collected tests.
  attr_reader :tests

  def initialize(files) #:nodoc:
    @files = files
    @tests = []
  end

  # A shortcut to Ruby.test_type.
  def test_type
    self.class.test_type
  end

  # A shortcut to Ruby.runner_bin.
  def runner_bin
    self.class.runner_bin
  end

  # A shortcut to Ruby.test_cases_root.
  def test_cases_root
    self.class.test_cases_root
  end

  # Returns the file for +name+ if it exists.
  #
  #   test_file('foo') # => "test/foo_test.rb"
  #   test_file('foo/bar') # => "test/foo/bar_test.rb"
  #   test_file('does/not/exist') # => nil
  def test_file(name)
    file = File.join(test_cases_root, "#{name}_#{test_type}.rb")
    file if File.exist?(file)
  end

  # This method is called to collect tests. Override this if you're subclassing
  # and make sure to call +super+.
  def handle!
    @tests.concat(@files.take_and_map do |file|
      case file
      # Match any ruby test file
      when /^#{test_cases_root}\/.+_#{test_type}\.rb$/
        file

      # A file such as ./lib/namespace/foo.rb is mapped to:
      # * ./test/namespace/foo_test.rb
      # * ./test/foo_test.rb
      when /^lib\/(.+)\.rb$/
        if namespaced = test_file($1)
          namespaced
        elsif in_test_root = test_file(File.basename(file, '.rb'))
          in_test_root
        end
      end
    end)
  end
end

options.on('-b', '--ruby [PATH]', "Use an alternate Ruby binary for spawned test runners. (Default is `ruby')") do |command|
  Kicker::Recipes::Ruby.runner_bin = command
end

recipe :ruby do
  process Kicker::Recipes::Ruby

  # When changing the Gemfile, install dependencies
  process do |files|
    execute 'bundle install' if files.delete('Gemfile')
  end
end


================================================
FILE: lib/kicker/recipes.rb
================================================
module Kernel
  # If only given a <tt>name</tt>, the specified recipe will be loaded. For
  # instance, the following, in a <tt>.kick</tt> file, will load the Rails
  # recipe:
  #
  #   recipe :rails
  #
  # However, this same method is used to define a callback that is called _if_
  # the recipe is loaded. For instance, the following, in a recipe file, will
  # be called if the recipe is actually used:
  #
  #   recipe :rails do
  #     # Load anything needed for the recipe.
  #     process do
  #       # ...
  #     end
  #   end
  def recipe(name, &block)
    Kicker::Recipes.recipe(name, &block)
  end
end

class Kicker
  module Recipes #:nodoc:
    RECIPES_DIR      = Pathname.new('../recipes').expand_path(__FILE__)
    USER_RECIPES_DIR = Pathname.new('~/.kick').expand_path
    CURRENT_RECIPES_DIR = Pathname.pwd.join('.kick').expand_path

    RECIPES_DIRS = [RECIPES_DIR, USER_RECIPES_DIR, CURRENT_RECIPES_DIR]

    class << self
      def reset!
        @recipes = nil
        # Always load all the base recipes
        load_recipe :execute_cli_command
        load_recipe :could_not_handle_file
        load_recipe :dot_kick
      end

      def recipes
        @recipes ||= {}
      end

      def recipe_filename(name)
        [
          USER_RECIPES_DIR,
          RECIPES_DIR
        ].each do |directory|
          filename = directory.join("#{name}.rb")
          return filename if filename.exist?
        end
      end

      def recipe_names
        recipe_files.map { |filename| filename.basename('.rb').to_s.to_sym }
      end

      def recipe_files
        RECIPES_DIRS.map{|dir| Pathname.glob(dir.join('*.rb')) }.flatten.uniq.map(&:expand_path)
      end

      def define_recipe(name, &block)
        recipes[name] = block
      end

      def load_recipe(name)
        if recipe_names.include?(name)
          load recipe_filename(name)
        else
          raise LoadError, "Can't load recipe `#{name}', it doesn't exist on disk. Loadable recipes are: #{recipe_names[0..-2].join(', ')}, and #{recipe_names[-1]}"
        end
      end

      def activate_recipe(name)
        unless recipes.has_key?(name)
          load_recipe(name)
        end
        if recipe = recipes[name]
          recipe.call
        else
          raise ArgumentError, "Can't activate the recipe `#{name}' because it hasn't been defined yet."
        end
      end

      # See Kernel#recipe for more information about the usage.
      def recipe(name, &block)
        name = name.to_sym
        if block_given?
          define_recipe(name, &block)
        else
          activate_recipe(name)
        end
      end
    end

    reset!
  end
end


================================================
FILE: lib/kicker/utils.rb
================================================
require 'shellwords' if RUBY_VERSION >= "1.9"

class Kicker
  module Utils #:nodoc:
    extend self

    attr_accessor :should_clear_screen
    alias_method :should_clear_screen?, :should_clear_screen

    def perform_work(command_or_options)
      if command_or_options.is_a?(Hash)
        options = command_or_options
      elsif command_or_options.is_a?(String)
        options = { :command => command_or_options }
      else
        raise ArgumentError, "Should be a string or a hash."
      end
      job = Job.new(options)
      will_execute_command(job)
      yield job
      did_execute_command(job)
      job
    end

    def execute(command_or_options)
      perform_work(command_or_options) do |job|
        _execute(job)
        yield job if block_given?
      end
    end

    def log(message)
      if Kicker.quiet
        puts message
      else
        now = Time.now
        puts "#{now.strftime('%H:%M:%S')}.#{now.usec.to_s[0,2]} | #{message}"
      end
    end

    def clear_console!
      puts(CLEAR) if Kicker.clear_console?
    end

    private

    CLEAR = "\e[H\e[2J"

    def _execute(job)
      silent = Kicker.silent?
      unless silent
        puts
        sync_before, $stdout.sync = $stdout.sync, true
      end
      output = ""
      popen(job.command) do |io|
        while str = io.read(1)
          output << str
          $stdout.print str unless silent
        end
      end
      job.output = output.strip
      job.exit_code = $?.exitstatus
      job
    ensure
      unless silent
        $stdout.sync = sync_before
        puts("\n\n")
      end
    end

    def popen(command, &block)
      if RUBY_VERSION >= "1.9"
        args = Shellwords.shellsplit(command)
        args << { :err => [:child, :out] }
        IO.popen(args, &block)
      else
        IO.popen("#{command} 2>&1", &block)
      end
    end

    def will_execute_command(job)
      puts(CLEAR) if Kicker.clear_console? && should_clear_screen?
      @should_clear_screen = false

      if message = job.print_before
        log(message)
      end

      if notification = job.notify_before
        Notification.notify(notification)
      end
    end

    def did_execute_command(job)
      if message = job.print_after
        puts(message)
      end

      log(job.success? ? "Success" : "Failed (#{job.exit_code})")

      if notification = job.notify_after
        Notification.notify(notification)
      end
    end
  end
end

module Kernel
  # Prints a +message+ with timestamp to stdout.
  def log(message)
    Kicker::Utils.log(message)
  end

  # When you perform some work (like shelling out a command to run without
  # using +execute+) you need to call this method, with a block in which you
  # perform your work, which will take care of logging the work appropriately.
  def perform_work(command, &block)
    Kicker::Utils.perform_work(command, &block)
  end

  # Executes the +command+, logs the output, and optionally sends user
  # notifications on Mac OS X (10.8 or higher).
  def execute(command, &block)
    Kicker::Utils.execute(command, &block)
  end
end


================================================
FILE: lib/kicker/version.rb
================================================
class Kicker
  VERSION = "3.0.0"
end


================================================
FILE: lib/kicker.rb
================================================
require 'kicker/version'
require 'kicker/fsevents'
require 'kicker/callback_chain'
require 'kicker/core_ext'
require 'kicker/job'
require 'kicker/notification'
require 'kicker/options'
require 'kicker/utils'
require 'kicker/recipes'

class Kicker #:nodoc:
  def self.run(argv = ARGV)
    Kicker::Options.parse(argv)
    new.start.loop!
  end

  attr_reader :last_event_processed_at

  def initialize
    finished_processing!
  end

  def paths
    @paths ||= Kicker.paths.map { |path| File.expand_path(path) }
  end

  def start
    validate_options!

    log "Watching for changes on: #{paths.join(', ')}"
    log ''

    run_startup_chain
    run_watch_dog!

    self
  end

  def loop!
    (Thread.list - [Thread.current, Thread.main]).each(&:join)
  end

  private

  def validate_options!
    validate_paths_and_command!
    validate_paths_exist!
  end

  def validate_paths_and_command!
    if startup_chain.empty? && process_chain.empty? && pre_process_chain.empty?
      puts Kicker::Options.parser.help
      exit
    end
  end

  def validate_paths_exist!
    paths.each do |path|
      unless File.exist?(path)
        puts "The given path `#{path}' does not exist"
        exit 1
      end
    end
  end

  def run_watch_dog!
    dirs = @paths.map { |path| File.directory?(path) ? path : File.dirname(path) }
    Kicker::FSEvents.start_watching(dirs, :latency => self.class.latency) do |events|
      process events
    end
    trap('INT') do
      log 'Exiting ...'
      exit
    end
  end

  def run_startup_chain
    startup_chain.call([], false)
  end

  def finished_processing!
    @last_event_processed_at = Time.now
  end

  def process(events)
    unless (files = changed_files(events)).empty?
      Utils.should_clear_screen = true
      full_chain.call(files)
      finished_processing!
    end
  end

  def changed_files(events)
    make_paths_relative(events.map do |event|
      files_in_directory(event.path).select { |file| file_changed_since_last_event? file }
    end.flatten.uniq.sort)
  end

  def files_in_directory(dir)
    Dir.entries(dir).sort[2..-1].map { |f| File.join(dir, f) }
  rescue Errno::ENOENT
    []
  end

  def file_changed_since_last_event?(file)
    File.mtime(file) > @last_event_processed_at
  rescue Errno::ENOENT
    false
  end

  def make_paths_relative(files)
    return files if files.empty?
    wd = Dir.pwd
    files.map do |file|
      if file[0..wd.length-1] == wd
        file[wd.length+1..-1]
      else
        file
      end
    end
  end
end


================================================
FILE: rakelib/gem_release.rake
================================================
NAME = 'Kicker'
LOWERCASE_NAME = NAME.downcase
GEM_NAME = LOWERCASE_NAME

def gem_version
  require File.expand_path("../../lib/#{LOWERCASE_NAME}/version", __FILE__)
  Object.const_get(NAME).const_get('VERSION')
end

def gem_file
  "#{GEM_NAME}-#{gem_version}.gem"
end

desc "Build gem"
task :build do
  sh "gem build #{GEM_NAME}.gemspec"
end

desc "Clean gems"
task :clean do
  sh "rm -f *.gem"
end

desc "Install gem"
task :install => :build do
  sh "gem install #{gem_file}"
end

desc "Clean, build, install, and push gem to rubygems.org"
task :release => [:clean, :install] do
  sh "git tag -a #{gem_version} -m 'Release #{gem_version}'"
  sh "git push --tags"
  sh "gem push #{gem_file}"
end


================================================
FILE: spec/callback_chain_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)

describe "Kicker, concerning its callback chains" do
  before do
    @chains = [:startup_chain, :pre_process_chain, :process_chain, :post_process_chain, :full_chain]
  end

  it "should return the callback chain instances" do
    @chains.each do |chain|
      Kicker.send(chain).should.be.instance_of Kicker::CallbackChain
    end
  end

  it "should be accessible by an instance" do
    kicker = Kicker.new

    @chains.each do |chain|
      kicker.send(chain).should == Kicker.send(chain)
    end
  end

  it "should provide a shortcut method which appends a callback to the startup chain" do
    Kicker.startup_chain.expects(:append_callback).with do |callback|
      callback.call == :from_callback
    end

    startup { :from_callback }
  end

  it "should provide a shortcut method which appends a callback to the pre-process chain" do
    Kicker.pre_process_chain.expects(:append_callback).with do |callback|
      callback.call == :from_callback
    end

    pre_process { :from_callback }
  end

  it "should provide a shortcut method which appends a callback to the process chain" do
    Kicker.process_chain.expects(:append_callback).with do |callback|
      callback.call == :from_callback
    end

    process { :from_callback }
  end

  it "should provide a shortcut method which prepends a callback to the post-process chain" do
    Kicker.post_process_chain.expects(:prepend_callback).with do |callback|
      callback.call == :from_callback
    end

    post_process { :from_callback }
  end

  it "should have assigned the chains to the `full_chain' (except startup_chain)" do
    Kicker.full_chain.length.should == 3
    Kicker.full_chain.each_with_index do |chain, index|
      chain.should == Kicker.send(@chains[index + 1])
    end
  end
end

describe "Kicker::CallbackChain" do
  it "should be a subclass of Array" do
    Kicker::CallbackChain.superclass.should == Array
  end
end

describe "An instance of Kicker::CallbackChain, concerning it's API" do
  before do
    @chain = Kicker::CallbackChain.new

    @callback1 = lambda {}
    @callback2 = lambda {}
  end

  it "should append a callback" do
    @chain << @callback1
    @chain.append_callback(@callback2)

    @chain.should == [@callback1, @callback2]
  end

  it "should prepend a callback" do
    @chain << @callback1
    @chain.prepend_callback(@callback2)

    @chain.should == [@callback2, @callback1]
  end
end

describe "An instance of Kicker::CallbackChain, when calling the chain" do
  before do
    @chain = Kicker::CallbackChain.new
    @result = []
  end

  it "should call the callbacks from first to last" do
    @chain.append_callback lambda { |files| @result << 1 }
    @chain.append_callback lambda { |files| @result << 2 }
    @chain.call(%w{ file })
    @result.should == [1, 2]
  end

  it "should pass the files array given to #call to each callback in the chain" do
    array = %w{ /file/1 }

    @chain.append_callback lambda { |files|
      files.should == array
      files.concat(%w{ /file/2 })
    }

    @chain.append_callback lambda { |files|
      files.should == array
      @result.concat(files)
    }

    @chain.call(array)
    @result.should == %w{ /file/1 /file/2 }
  end

  it "should halt the callback chain once the given array is empty" do
    @chain.append_callback lambda { |files| @result << 1; files.clear }
    @chain.append_callback lambda { |files| @result << 2 }
    @chain.call(%w{ /file/1 /file/2 })
    @result.should == [1]
  end

  it "should not halt the chain if the array is empty if specified" do
    @chain.append_callback lambda { |files| @result << 1; files.clear }
    @chain.append_callback lambda { |files| @result << 2 }
    @chain.call(%w{ /file/1 /file/2 }, false)
    @result.should == [1, 2]
  end

  it "should not call any callback if the given array is empty" do
    @chain.append_callback lambda { |files| @result << 1 }
    @chain.call([])
    @result.should == []
  end

  it "should work with a chain of chains as well" do
    array = %w{ file }

    kicker_and_files = lambda do |kicker, files|
      kicker.should.be @kicker
      files.should.be array
    end

    chain1 = Kicker::CallbackChain.new([
      lambda { |files| files.should == array; @result << 1 },
      lambda { |files| files.should == array; @result << 2 }
    ])

    chain2 = Kicker::CallbackChain.new([
      lambda { |files| files.should == array; @result << 3 },
      lambda { |files| files.should == array; @result << 4 }
    ])

    @chain.append_callback chain1
    @chain.append_callback chain2

    @chain.call(array)
    @result.should == [1, 2, 3, 4]
  end
end


================================================
FILE: spec/core_ext_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)

describe "Array#take_and_map" do
  before do
    @array = %w{ foo bar baz foo/bar.baz foo/bar/baz }
  end

  it "should remove elements from the array for which the block evaluates to true" do
    @array.take_and_map { |x| x =~ /^ba/ }
    @array.should == %w{ foo foo/bar.baz foo/bar/baz }
  end

  it "should return a new array of the return values of each block call that evaluates to true" do
    @array.take_and_map { |x| $1 if x =~ /^ba(\w)/ }.should == %w{ r z }
  end

  it "should flatten and compact the result array" do
    @array.take_and_map do |x|
      x =~ /^ba/ ? %w{ f o o } : [nil]
    end.should == %w{ f o o f o o }
  end

  it "should not flatten and compact the result array if specified" do
    @array.take_and_map(nil, false) do |x|
      x =~ /^ba/ ? %w{ f o o } : [nil]
    end.should == [[nil], %w{ f o o }, %w{ f o o }, [nil], [nil]]
  end

  it "should take only files matching the pattern" do
    @array.take_and_map('**/*') { |x| x.reverse }.should ==
      %w{ foo/bar.baz foo/bar/baz }.map { |s| s.reverse }
  end

  it "should not remove files not matching the pattern" do
    @array.take_and_map('**/*') { |x| x }
    @array.should == %w{ foo bar baz }
  end
end


================================================
FILE: spec/filesystem_change_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)

require 'stringio'
describe "Kicker, when a change occurs" do
  def silent(&block)
    stdout = $stdout
    $stdout = StringIO.new
    yield
  ensure
    $stdout = stdout
  end
  def touch(file)
    file = "/tmp/kicker_test_tmp_#{file}"
    `touch #{file}`
    file
  end

  def event(*files)
    event = stub('FSEvent')
    event.stubs(:path).returns('/tmp')
    event
  end

  def remove_tmp_files!
    Dir.glob("/tmp/kicker_test_tmp_*").each { |f| File.delete(f) }
  end

  before do
    remove_tmp_files!

    Kicker::Notification.stubs(:`)

    Kicker.any_instance.stubs(:last_command_succeeded?).returns(true)
    Kicker.any_instance.stubs(:log)
    @kicker = Kicker.new
  end

  it "should store the current time as when the last change occurred" do
    now = Time.now
    Time.stubs(:now).returns(now)

    @kicker.send(:finished_processing!)
    @kicker.last_event_processed_at.should == now
  end

  it "should return an array of files that have changed since the last event" do
    file1 = touch('1')
    file2 = touch('2')
    file3 = touch('3')
    file4 = touch('4')
    @kicker.send(:finished_processing!)

    events = [event(file1, file2), event(file3, file4)]

    @kicker.send(:changed_files, events).should == []
    @kicker.send(:finished_processing!)

    sleep(1)
    touch('2')

    @kicker.send(:changed_files, events).should == [file2]
    @kicker.send(:finished_processing!)

    sleep(1)
    touch('1')
    touch('3')

    @kicker.send(:changed_files, events).should == [file1, file3]
  end

  it "should return an empty array when a directory doesn't exist while collecting the files in it" do
    @kicker.send(:files_in_directory, '/does/not/exist').should == []
  end

  it "should not break when determining changed files from events with missing files" do
    file1 = touch('1')
    file2 = touch('2')
    @kicker.send(:finished_processing!)
    sleep(1)
    touch('2')

    events = [event(file1, file2), event('/does/not/exist')]
    @kicker.send(:changed_files, events).should == [file2]
  end

  it "should return relative file paths if the path is relative to the current work dir" do
    sleep(1)
    file = touch('1')

    Dir.stubs(:pwd).returns('/tmp')
    @kicker.send(:changed_files, [event(file)]).should == [File.basename(file)]
  end

  it "should call the full_chain with all changed files" do
    files = %w{ /file/1 /file/2 }
    events = [event('/file/1'), event('/file/2')]

    @kicker.expects(:changed_files).with(events).returns(files)
    @kicker.full_chain.expects(:call).with(files)
    @kicker.expects(:finished_processing!)

    @kicker.send(:process, events)
  end

  it "should not call the full_chain if there were no changed files" do
    @kicker.stubs(:changed_files).returns([])
    @kicker.full_chain.expects(:call).never
    @kicker.expects(:finished_processing!).never

    @kicker.send(:process, [event()])
  end

  it "should not break when directory entries are not sorted" do
    sleep(1)
    file = touch('1')

    Dir.stubs(:entries).returns([File.basename(file), ".", ".."])
    @kicker.send(:changed_files, [event(file)]).should == [file]
  end

  it "clears the console only once during running the chain" do
    Kicker.clear_console = true
    Kicker.silent = true
    Kicker::Utils.stubs(:log)
    Kicker::Utils.expects(:puts).with("\e[H\e[2J").once

    files = %w{ /file/1 /file/2 }
    @kicker.stubs(:changed_files).returns(files)

    @kicker.process_chain.append_callback lambda { |files| Kicker::Utils.perform_work('ls -l') {} }
    @kicker.process_chain.append_callback lambda { |files| Kicker::Utils.perform_work('ls -l') {} }

    silent do
      @kicker.send(:process, files.map { |f| event(f) })
    end
  end

  it "does not clear the console if no work is ever performed" do
    Kicker.clear_console = true
    Kicker::Utils.expects(:puts).with("\e[H\e[2J").never

    files = %w{ /file/1 /file/2 }
    @kicker.stubs(:changed_files).returns(files)
    @kicker.full_chain.stubs(:call)
    @kicker.send(:process, files.map { |f| event(f) })
  end
end


================================================
FILE: spec/fixtures/a_file_thats_reloaded.rb
================================================
$FROM_RELOADED_FILE ||= 0
$FROM_RELOADED_FILE += 1


================================================
FILE: spec/fsevents_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)

class FakeListener
  def initialize(paths, options={})
    @paths = paths
  end

  def change(&block)
    @block = block
    self
  end

  def start blocking=true
    self
  end

  def fake_event(paths)
    @block.call(paths, [], [])
  end
end

describe "Kicker::FSEvents" do
  it "calls the provided block with changed directories wrapped in an event instance" do
    tmp = Pathname.new('tmp').join('test')
    test = tmp.join('what')
    test.mkpath

    FileUtils.touch(tmp.join('file'))

    watch_dog = Kicker::FSEvents.start_watching([tmp.to_s]) { |e| events = e }
    Kicker::FSEvents::FSEvent.expects(:new).with(File.expand_path(test.to_s))

    FileUtils.touch(test.join('file'))

    sleep 1
  end
end

describe "Kicker::FSEvents::FSEvent" do
  it "returns the files from the changed directory ordered by mtime and filename" do
    fsevent = Kicker::FSEvents::FSEvent.new(File.expand_path('../fixtures', __FILE__))
    fsevent.files.should == [File.expand_path('../fixtures/a_file_thats_reloaded.rb', __FILE__)]
  end
end


================================================
FILE: spec/initialization_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)

module ReloadDotKick; end

describe "Kicker" do
  before do
    Kicker.any_instance.stubs(:start)
  end

  it "should return the default paths to watch" do
    Kicker.paths.should == %w{ . }
  end

  it "should default the FSEvents latency to 1" do
    Kicker.latency.should == 1
  end
end

describe "Kicker, when initializing" do
  after do
    Kicker.paths = %w{ . }
  end

  it "should return the extended paths to watch" do
    Kicker.paths = %w{ /some/dir a/relative/path }
    Kicker.new.paths.should == ['/some/dir', File.expand_path('a/relative/path')]
  end

  it "should have assigned the current time to last_event_processed_at" do
    now = Time.now; Time.stubs(:now).returns(now)
    Kicker.new.last_event_processed_at.should == now
  end

  it "should use the default paths if no paths were given" do
    Kicker.new.paths.should == [File.expand_path('.')]
  end
end

describe "Kicker, when starting" do
  before do
    Kicker.paths = %w{ /some/file.rb }
    @kicker = Kicker.new
    @kicker.stubs(:log)
    @kicker.startup_chain.stubs(:call)
    Kicker::FSEvents.stubs(:start_watching)
  end

  after do
    Kicker.latency = 1
    Kicker.paths = %w{ . }
  end

  it "should show the usage banner and exit when there are no callbacks defined at all" do
    @kicker.stubs(:validate_paths_exist!)
    Kicker.stubs(:startup_chain).returns(Kicker::CallbackChain.new)
    Kicker.stubs(:process_chain).returns(Kicker::CallbackChain.new)
    Kicker.stubs(:pre_process_chain).returns(Kicker::CallbackChain.new)

    Kicker::Options.stubs(:parser).returns(mock('OptionParser', :help => 'help'))
    @kicker.expects(:puts).with("help")
    @kicker.expects(:exit)

    @kicker.start
  end

  it "should warn the user and exit if any of the given paths doesn't exist" do
    @kicker.stubs(:validate_paths_and_command!)

    @kicker.expects(:puts).with("The given path `/some/file.rb' does not exist")
    @kicker.expects(:exit).with(1)

    @kicker.start
  end

  it "should start a FSEvents stream with the assigned latency" do
    @kicker.stubs(:validate_options!)

    Kicker.latency = 2.34
    Kicker::FSEvents.expects(:start_watching).with(['/some'], :latency => 2.34)
    @kicker.start
  end

  it "should start a FSEvents stream which watches all paths, but the dirnames of paths if they're files" do
    @kicker.stubs(:validate_options!)
    File.stubs(:directory?).with('/some/file.rb').returns(false)

    Kicker::FSEvents.expects(:start_watching).with(['/some'], :latency => Kicker.latency)
    @kicker.start
  end

  it "should start a FSEvents stream with a block which calls #process with any generated events" do
    @kicker.stubs(:validate_options!)

    Kicker::FSEvents.expects(:start_watching).yields(['event'])
    @kicker.expects(:process).with(['event'])

    @kicker.start
  end

  it "should setup a signal handler for `INT' which stops the FSEvents stream and exits" do
    @kicker.stubs(:validate_options!)

    Kicker::FSEvents.stubs(:start_watching).returns(stub)

    @kicker.expects(:trap).with('INT').yields
    @kicker.expects(:exit)

    @kicker.start
  end

  it "should call the startup chain" do
    @kicker.stubs(:validate_options!)

    @kicker.startup_chain.expects(:call).with([], false)
    @kicker.start
  end
end


================================================
FILE: spec/job_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)

describe "Kicker::Job" do
  before do
    @job = Kicker::Job.new(:command => 'ls -l', :exit_code => 0, :output => "line 1\nline2")
  end

  after do
    Kicker.silent = true
  end

  it "initializes with an options hash" do
    @job.command.should == 'ls -l'
    @job.exit_code.should == 0
    @job.output.should == "line 1\nline2"
  end

  it "returns wether or not the job was a success" do
    @job.should.be.success
    @job.exit_code = 123
    @job.should.not.be.success
  end

  describe "concerning the default print and notification messages" do
    describe "for before a command is executed" do
      it "returns what command will be executed (for print)" do
        @job.print_before.should == 'Executing: ls -l'
      end

      it "returns what command will be executed (for notification)" do
        Kicker.silent = false
        @job.notify_before.should == { :title => 'Kicker: Executing', :message => 'ls -l' }
      end

      # TODO what if the user *does* want to send a notification?
      #it "does not send a notification about what command will be executed if Kicker is silent" do
        #Kicker.silent = true
        #@job.notify_before.should == nil
      #end
    end

    describe "for after a command is executed" do
      describe "for print" do
        it "does not return the output if the output has already been logged" do
          Kicker.silent = false
          @job.exit_code = 123
          @job.print_after.should == nil
        end

        it "does not return the output if the command succeeded" do
          Kicker.silent = true
          @job.exit_code = 0
          @job.print_after.should == nil
        end

        it "returns all output if it wasn't printed before and the command failed" do
          Kicker.silent = true
          @job.exit_code = 123
          @job.print_after.should == "\nline 1\nline2\n\n"
        end
      end

      describe "for notification" do
        it "returns the status of the command and its output" do
          Kicker.silent = false
          @job.exit_code = 0
          @job.notify_after.should == { :title => 'Kicker: Success', :message => "line 1\nline2" }
          @job.exit_code = 123
          @job.notify_after.should == { :title => 'Kicker: Failed (123)', :message => "line 1\nline2" }
        end

        it "never returns the output if Kicker is silent" do
          Kicker.silent = true
          @job.exit_code = 0
          @job.notify_after.should == { :title => 'Kicker: Success', :message => '' }
          @job.exit_code = 123
          @job.notify_after.should == { :title => 'Kicker: Failed (123)', :message => '' }
        end
      end
    end
  end

  describe "concerning explicit print and notification messages" do
    before { Kicker.silent = false }

    it "returns `nil' if that was explicitely assigned" do
      %w{ print_before print_after notify_before notify_after }.each do |attr|
        @job.send("#{attr}=", nil)
        @job.send(attr).should == nil
      end
    end

    it "returns the assigned message when explicitely assigned" do
      @job.print_before = 'BEFORE'
      @job.print_before.should == 'BEFORE'
      @job.print_after = 'AFTER'
      @job.print_after.should == 'AFTER'
    end

    it "merges the assigned notification options with the default ones" do
      @job.notify_before = { :message => 'Checking file list' }
      @job.notify_before.should == { :title => 'Kicker: Executing', :message => 'Checking file list' }
      @job.notify_after = { :title => 'OMG' }
      @job.notify_after.should ==  {:title => 'OMG', :message => "line 1\nline2" }
    end
  end
end


================================================
FILE: spec/kicker_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)
require 'stringio'

describe "Kicker" do
  before { @stdout = $stdout }
  after { $stdout = @stdout }

  it "should start" do
    $stdout = StringIO.new
    thread = Thread.new { Kicker.run([]) }
    thread.abort_on_exception = true
    sleep 5
    thread.alive?.should == true
    thread.exit
  end
end


================================================
FILE: spec/notification_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)

describe "Kicker::Notification" do
  it "sends a notification, grouped by the project (identified by the working dir)" do
    Kicker::Notification.stubs(:use?).returns(true)
    Notify.expects(:notify)
    Kicker::Notification.notify(:title => 'Kicker: Executing', :message => 'ls -l')
  end

  it "does not send a notification if notifying is disabled" do
    Kicker::Notification.stubs(:use?).returns(false)
    Notify.expects(:notify).never
    Kicker::Notification.notify(:title => 'Kicker: Executing', :message => 'ls -l')
  end
end


================================================
FILE: spec/options_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)

describe "Kicker::Options.parse" do
  after do
    Kicker.latency = 1
    Kicker.paths = %w{ . }
    Kicker.silent = false
    Kicker.quiet = false
    Kicker.clear_console = false
    Kicker::Notification.use = true
    Kicker::Notification.app_bundle_identifier = 'com.apple.Terminal'
  end

  it "should parse the paths" do
    Kicker::Options.parse([])
    Kicker.paths.should == %w{ . }

    Kicker::Options.parse(%w{ /some/file.rb })
    Kicker.paths.should == %w{ /some/file.rb }

    Kicker::Options.parse(%w{ /some/file.rb /a/dir /and/some/other/file.rb })
    Kicker.paths.should == %w{ /some/file.rb /a/dir /and/some/other/file.rb }
  end

  it "parses wether or not user notifications should be used" do
    Kicker::Options.parse([])
    Kicker::Notification.should.use

    Kicker::Options.parse(%w{ --no-notification })
    Kicker::Notification.should.not.use
  end

  it "should parse if we should keep output to a minimum" do
    Kicker::Options.parse([])
    Kicker.should.not.be.silent

    Kicker::Options.parse(%w{ -s })
    Kicker.should.be.silent
  end

  it 'should parse whether or not to run in quiet mode and enable silent mode if quiet' do
    Kicker::Options.parse([])
    Kicker.should.not.be.quiet
    Kicker.should.not.be.silent

    Kicker::Options.parse(%w{ --quiet })
    Kicker.should.be.quiet
    Kicker.should.be.silent
  end

  it "should parse whether or not to clear the console before running" do
    Kicker::Options.parse([])
    Kicker.should.not.clear_console

    Kicker::Options.parse(%w{ --clear })
    Kicker.should.clear_console
  end

  if Kicker.osx?
    it "parses the application to activate when a user notification is clicked" do
      Kicker::Options.parse(%w{ --activate-app com.apple.Safari })
      Kicker::Notification.app_bundle_identifier.should == 'com.apple.Safari'
    end
  end

  it "should parse the latency to pass to FSEvents" do
    Kicker::Options.parse(%w{ -l 2.5 })
    Kicker.latency.should == 2.5

    Kicker::Options.parse(%w{ --latency 3.5 })
    Kicker.latency.should == 3.5
  end

  it "should parse recipe requires" do
    Kicker::Recipes.expects(:recipe).with('rails')
    Kicker::Recipes.expects(:recipe).with('jstest')
    Kicker::Options.parse(%w{ -r rails --recipe jstest })
  end
end


================================================
FILE: spec/recipes/could_not_handle_file_spec.rb
================================================
require File.expand_path('../../spec_helper', __FILE__)

describe "Kicker, concerning the default `could not handle file' callback" do
  before do
    Kicker.silent = false
  end

  it "should log that it could not handle the given files" do
    Kicker::Utils.expects(:log).with('')
    Kicker::Utils.expects(:log).with("Could not handle: /file/1, /file/2")
    Kicker::Utils.expects(:log).with('')

    Kicker.post_process_chain.last.call(%w{ /file/1 /file/2 })
  end

  it "should not log in silent mode" do
    Kicker.silent = true
    Kicker::Utils.expects(:log).never
    Kicker.post_process_chain.last.call(%w{ /file/1 /file/2 })
  end
end


================================================
FILE: spec/recipes/dot_kick_spec.rb
================================================
require File.expand_path('../../spec_helper', __FILE__)

describe "The .kick handler" do
  it "should reset $LOADED_FEATURES and callback chains to state before loading .kick and reload .kick" do
    ReloadDotKick.save_state

    features_before_dot_kick = $LOADED_FEATURES.dup
    chains_before_dot_kick = Kicker.full_chain.map { |c| c.dup }

    ReloadDotKick.expects(:load).with('.kick').twice

    2.times do
      require File.expand_path('../../fixtures/a_file_thats_reloaded', __FILE__)
      process {}
      ReloadDotKick.call(%w{ .kick })
    end

    $FROM_RELOADED_FILE.should == 2
    $LOADED_FEATURES.should == features_before_dot_kick
    Kicker.full_chain.should == chains_before_dot_kick
  end
end


================================================
FILE: spec/recipes/execute_cli_command_spec.rb
================================================
require File.expand_path('../../spec_helper', __FILE__)

describe "Kicker, concerning the `execute a command-line' callback" do
  it "should parse the command and add the callback" do
    before = Kicker.pre_process_chain.length

    Kicker::Options.parse(%w{ -e ls })
    Kicker.pre_process_chain.length.should == before + 1

    Kicker::Options.parse(%w{ --execute ls })
    Kicker.pre_process_chain.length.should == before + 2
  end

  it "should call execute with the given command" do
    Kicker::Options.parse(%w{ -e ls })

    callback = Kicker.pre_process_chain.last
    callback.should.be.instance_of Proc

    Kicker::Utils.expects(:execute).with('sh -c "ls"')

    callback.call(%w{ /file/1 /file/2 }).should.not.be.instance_of Array
  end

  it "should clear the files array to halt the chain" do
    Kicker::Utils.stubs(:execute)

    files = %w{ /file/1 /file/2 }
    Kicker.pre_process_chain.last.call(files)
    files.should.be.empty
  end

  it "should run the command directly once Kicker is done loading" do
    callback = Kicker.pre_process_chain.last
    Kicker.startup_chain.should.include callback
  end
end


================================================
FILE: spec/recipes/ignore_spec.rb
================================================
require File.expand_path('../../spec_helper', __FILE__)

recipe :ignore

IGNORE = Kicker.pre_process_chain.find{|callback| callback == Ignore }

describe "The Ignore handler" do
  it "should remove files that match the given regexp" do
    ignore(/^fo{2}bar/)

    files = %w{ Rakefile foobar foobarbaz }
    IGNORE.call(files)
    files.should == %w{ Rakefile }
  end

  it "should remove files that match the given string" do
    ignore('bazbla')

    files = %w{ Rakefile bazbla bazblabla }
    IGNORE.call(files)
    files.should == %w{ Rakefile bazblabla }
  end

  it "should ignore a few file types by default" do
    files = %w{ Rakefile foo/bar/dev.log .svn/foo svn-commit.tmp .git/foo tmp }
    IGNORE.call(files)
    files.should == %w{ Rakefile }
  end
end


================================================
FILE: spec/recipes/jstest_spec.rb
================================================
require File.expand_path('../../spec_helper', __FILE__)

before = Kicker.process_chain.dup
recipe :jstest
JSTEST = (Kicker.process_chain - before).first

describe "The HeadlessSquirrel handler" do
  before do
    @files = %w{ Rakefile }
  end

  it "should match any test case files" do
    @files += %w{ test/javascripts/ui_test.html test/javascripts/admin_test.js }

    Kicker::Utils.expects(:execute).
      with("jstest test/javascripts/ui_test.html test/javascripts/admin_test.html")

    JSTEST.call(@files)
    @files.should == %w{ Rakefile }
  end

  it "should map public/javascripts libs to test/javascripts" do
    @files += %w{ public/javascripts/ui.js public/javascripts/admin.js }

    Kicker::Utils.expects(:execute).
      with("jstest test/javascripts/ui_test.html test/javascripts/admin_test.html")

    JSTEST.call(@files)
    @files.should == %w{ Rakefile }
  end
end


================================================
FILE: spec/recipes/rails_spec.rb
================================================
require File.expand_path('../../spec_helper', __FILE__)
recipe :rails

class Kicker::Recipes::Rails
  class << self
    attr_accessor :tests_ran
    def run_tests(tests)
      self.tests_ran ||= []
      self.tests_ran << tests
    end
  end
end

describe "The Rails handler" do
  it "should return all controller tests when test_type is `test'" do
    tests = %w{ test.rb }

    File.use_original_exist = false
    File.existing_files = tests

    Kicker::Recipes::Ruby.test_type = 'test'
    Kicker::Recipes::Ruby.test_cases_root = nil

    Dir.expects(:glob).with("test/functional/**/*_test.rb").returns(tests)
    Kicker::Recipes::Rails.all_controller_tests.should == tests
  end

  it "should return all controller tests when test_type is `spec'" do
    specs = %w{ spec.rb }

    File.use_original_exist = false
    File.existing_files = specs

    Kicker::Recipes::Ruby.test_type = 'spec'
    Kicker::Recipes::Ruby.test_cases_root = nil

    Dir.expects(:glob).with("spec/controllers/**/*_spec.rb").returns(specs)
    Kicker::Recipes::Rails.all_controller_tests.should == specs
  end
end

describe "The Rails schema handler" do
  before do
    # We assume the Rails schema handler is in the chain after the Rails handler
    # because it's defined in the same recipe
    @handler = Kicker.process_chain[Kicker.process_chain.index(Kicker::Recipes::Rails) + 1]
  end

  it "should prepare the test database if db/schema.rb is modified" do
    Kicker::Utils.expects(:execute).with('rake db:test:prepare')
    @handler.call(%w{ db/schema.rb })
  end

  it "should not prepare the test database if another file than db/schema.rb is modified" do
    Kicker::Utils.expects(:execute).never
    @handler.call(%w{ Rakefile })
  end
end

module SharedRailsHandlerHelper
  def should_match(files, tests, existing_files=nil)
    File.use_original_exist = false
    File.existing_files = existing_files || tests
    @files += files
    Kicker::Recipes::Rails.call(@files)
    @files.should == %w{ Rakefile }
  end
end

describe "An instance of the Rails handler, with test type `test'" do
  extend SharedRailsHandlerHelper

  before do
    Kicker::Recipes::Ruby.reset!
    @files = %w{ Rakefile }
  end

  after do
    File.use_original_exist = true
  end

  it "should map model files to test/unit" do
    should_match %w{ app/models/member.rb     app/models/article.rb },
                 %w{ test/unit/member_test.rb test/unit/article_test.rb }
  end

  it "should map concern files to test/unit/concerns" do
    should_match %w{ app/concerns/authenticate.rb            app/concerns/nested_resource.rb },
                 %w{ test/unit/concerns/authenticate_test.rb test/unit/concerns/nested_resource_test.rb }
  end

  it "should map helper files to test/unit/helpers" do
    should_match %w{ app/helpers/members_helper.rb             app/helpers/articles_helper.rb },
                 %w{ test/unit/helpers/members_helper_test.rb  test/unit/helpers/articles_helper_test.rb }
  end

  it "should map controller files to test/functional" do
    should_match %w{ app/controllers/application_controller.rb      app/controllers/members_controller.rb },
                 %w{ test/functional/application_controller_test.rb test/functional/members_controller_test.rb }
  end

  it "should map view templates to test/functional" do
    should_match %w{ app/views/members/index.html.erb           app/views/admin/articles/show.html.erb },
                 %w{ test/functional/members_controller_test.rb test/functional/admin/articles_controller_test.rb }
  end

  it "should run all functional tests when config/routes.rb is saved" do
    tests = %w{ test/functional/members_controller_test.rb test/functional/admin/articles_controller_test.rb }
    Kicker::Recipes::Rails.expects(:all_controller_tests).returns(tests)
    should_match %w{ config/routes.rb }, tests
  end

  it "should map lib files to test/lib" do
    should_match %w{ lib/money.rb           lib/views/date.rb },
                 %w{ test/lib/money_test.rb test/lib/views/date_test.rb }
  end

  it "should map fixtures to their unit, helper and functional tests if they exist" do
    tests = %w{ test/unit/member_test.rb test/unit/helpers/members_helper_test.rb test/functional/members_controller_test.rb }
    should_match %w{ test/fixtures/members.yml }, tests, []
    Kicker::Recipes::Rails.tests_ran.last.should == []
  end

  it "should map fixtures to their unit, helper and functional tests if they exist" do
    tests = %w{ test/unit/member_test.rb test/unit/helpers/members_helper_test.rb test/functional/members_controller_test.rb }
    should_match %w{ test/fixtures/members.yml }, tests
    Kicker::Recipes::Rails.tests_ran.last.should == tests
  end
end

describe "An instance of the Rails handler, with test type `spec'" do
  extend SharedRailsHandlerHelper

  before do
    Kicker::Recipes::Ruby.reset!
    Kicker::Recipes::Ruby.test_type = 'spec'
    @files = %w{ Rakefile }
  end

  after do
    File.use_original_exist = true
  end

  it "should map model files to spec/models" do
    should_match %w{ app/models/member.rb       app/models/article.rb },
                 %w{ spec/models/member_spec.rb spec/models/article_spec.rb }
  end

  it "should map concern files to spec/models/concerns" do
    should_match %w{ app/concerns/authenticate.rb              app/concerns/nested_resource.rb },
                 %w{ spec/models/concerns/authenticate_spec.rb spec/models/concerns/nested_resource_spec.rb }
  end

  it "should map helper files to spec/helpers" do
    should_match %w{ app/helpers/members_helper.rb       app/helpers/articles_helper.rb },
                 %w{ spec/helpers/members_helper_spec.rb spec/helpers/articles_helper_spec.rb }
  end

  it "should map controller files to spec/controllers" do
    should_match %w{ app/controllers/application_controller.rb       app/controllers/members_controller.rb },
                 %w{ spec/controllers/application_controller_spec.rb spec/controllers/members_controller_spec.rb }
  end

  it "should map view templates to spec/controllers" do
    should_match %w{ app/views/members/index.html.erb            app/views/admin/articles/show.html.erb },
                 %w{ spec/controllers/members_controller_spec.rb spec/controllers/admin/articles_controller_spec.rb }
  end

  it "should run all controller tests when config/routes.rb is saved" do
    specs = %w{ spec/controllers/members_controller_test.rb spec/controllers/admin/articles_controller_test.rb }
    Kicker::Recipes::Rails.expects(:all_controller_tests).returns(specs)
    should_match %w{ config/routes.rb }, specs
  end

  it "should map lib files to spec/lib" do
    should_match %w{ lib/money.rb           lib/views/date.rb },
                 %w{ spec/lib/money_spec.rb spec/lib/views/date_spec.rb }
  end

  it "should map fixtures to their model, helper and controller specs" do
    specs = %w{ spec/models/member_spec.rb spec/helpers/members_helper_spec.rb spec/controllers/members_controller_spec.rb }
    should_match %w{ spec/fixtures/members.yml }, specs
  end

  it "should map fixtures to their model, helper and controller specs if they exist" do
    specs = %w{ spec/models/member_spec.rb spec/helpers/members_helper_spec.rb spec/controllers/members_controller_spec.rb }
    should_match %w{ spec/fixtures/members.yml }, specs
  end
end


================================================
FILE: spec/recipes/ruby_spec.rb
================================================
require File.expand_path('../../spec_helper', __FILE__)
recipe :ruby

class Kicker::Recipes::Ruby
  class << self
    attr_accessor :executed
    attr_accessor :blocks
    def execute(command, &block)
      self.executed ||= []
      self.blocks ||= []

      self.executed << command
      self.blocks << block
    end
  end
end

describe "The Ruby handler" do
  before do
    @handler = Kicker::Recipes::Ruby
    @handler.reset!
    @handler.test_type = 'test'
  end

  after do
    File.use_original_exist = true
  end

  it "should instantiate a handler instance when called" do
    tests = %w{ test/1_test.rb Rakefile test/namespace/2_test.rb }
    instance = @handler.new(tests)
    @handler.expects(:new).with(tests).returns(instance)
    @handler.call(tests)
  end

  it "should discover whether to use `ruby' or `spec' as the test_type" do
    File.use_original_exist = false

    File.existing_files = []
    @handler.test_type.should == 'test'

    @handler.reset!

    File.existing_files = ['spec']
    @handler.test_type.should == 'spec'
  end

  it "should run the given tests with a test-unit runner" do
    @handler.run_tests(%w{ test/1_test.rb test/namespace/2_test.rb })
    @handler.executed.last.should == "ruby -I. -r test/1_test -r test/namespace/2_test -e ''"
  end

  it "should run the given tests with a spec runner" do
    @handler.test_type = 'spec'
    @handler.run_tests(%w{ test/1_test.rb test/namespace/2_test.rb })
    @handler.executed.last.should == "rspec test/1_test.rb test/namespace/2_test.rb"
  end

  it "should not try to run the tests if none were given" do
    @handler.executed = []
    @handler.run_tests([])
    @handler.executed.should.be.empty
  end

  it "should be possible to override the bin path" do
    @handler.runner_bin = '/some/other/runner'
    @handler.run_tests(%w{ test/1_test.rb test/namespace/2_test.rb })
    @handler.executed.last.should == "/some/other/runner -I. -r test/1_test -r test/namespace/2_test -e ''"
  end

  it "should set the alternative ruby bin path" do
    Kicker::Options.parse(%w{ -b /opt/ruby-1.9.2/bin/ruby })
    @handler.runner_bin.should == '/opt/ruby-1.9.2/bin/ruby'

    @handler.reset!

    Kicker::Options.parse(%w{ --ruby /opt/ruby-1.9.2/bin/ruby })
    @handler.runner_bin.should == '/opt/ruby-1.9.2/bin/ruby'
  end

  it "should be possible to add runner options when test_type is `test'" do
    @handler.test_type = 'test'
    @handler.test_options << '-I ./other'
    @handler.run_tests(%w{ test/1_test.rb })
    @handler.executed.last.should == "ruby -I. -I ./other -r test/1_test -e ''"
  end

  it "should be possible to add runner options when test_type is `spec'" do
    @handler.test_type = 'spec'
    @handler.test_options << '-I ./other'
    @handler.run_tests(%w{ spec/1_spec.rb })
    @handler.executed.last.should == "rspec -I ./other spec/1_spec.rb"
  end
end

%w{ test spec }.each do |type|
  describe "An instance of the Ruby handler, with test type `#{type}'" do
    before do
      @handler = Kicker::Recipes::Ruby
      @test_type, @test_cases_root = @handler.test_type, @handler.test_cases_root
      @handler.test_type = type
      @handler.test_cases_root = type

      File.use_original_exist = false
      File.existing_files = %W(#{type}/1_#{type}.rb #{type}/namespace/2_#{type}.rb)
    end

    after do
      @handler.test_type, @handler.test_cases_root = @test_type, @test_cases_root
      File.use_original_exist = true
    end

    it "should match any test case files" do
      files = %w(Rakefile) + File.existing_files
      handler = @handler.new(files)
      handler.handle!

      handler.tests.should == File.existing_files
      files.should == %W{ Rakefile }
    end

    it "should match files in ./lib" do
      files = %w(Rakefile) + File.existing_files
      handler = @handler.new(files)
      handler.handle!

      handler.tests.should == File.existing_files
      files.should == %w{ Rakefile }
    end

    it "should match lib tests in the test root as well" do
      File.existing_files = %W(#{type}/1_#{type}.rb #{type}/2_#{type}.rb)

      files = %W{ Rakefile lib/1.rb lib/namespace/2.rb }
      handler = @handler.new(files)
      handler.handle!

      handler.tests.should == %W{ #{type}/1_#{type}.rb #{type}/2_#{type}.rb }
      files.should == %W{ Rakefile }
    end

    it "should check if a different test case root" do
      @handler.test_cases_root = 'test/cases'

      files = %W{ Rakefile test/cases/1_#{type}.rb test/cases/namespace/2_#{type}.rb }
      handler = @handler.new(files)
      handler.handle!

      handler.tests.should == %W{ test/cases/1_#{type}.rb test/cases/namespace/2_#{type}.rb }
      files.should == %W{ Rakefile }
    end
  end
end

describe "The Ruby Bundler handler" do
  before do
    # We assume the Ruby Bundler handler is in the chain after the Ruby handler
    # because it's defined in the same recipe
    @handler = Kicker.process_chain[Kicker.process_chain.index(Kicker::Recipes::Ruby) + 1]
  end

  it "runs `bundle install` whenever the Gemfile changes" do
    Kicker::Utils.expects(:execute).with('bundle install')
    @handler.call(%w{ Gemfile })
  end

  it "does not run `bundle install` when another file is changed" do
    Kicker::Utils.expects(:execute).never
    @handler.call(%w{ Rakefile })
  end
end


================================================
FILE: spec/recipes_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)
require 'fakefs/safe'

module ReloadDotKick; end

describe "Kicker::Recipes" do
  RECIPES_PATH = Pathname.new('../../lib/kicker/recipes/').expand_path(__FILE__)

  def recipe_files
    Kicker::Recipes.recipe_files
  end

  before do
    Kicker::Recipes.reset!
  end

  before do
    FakeFS.activate!
    FakeFS::FileSystem.clone(RECIPES_PATH)
    Dir.chdir(File.absolute_path('../../', __FILE__))
  end

  after do
    FakeFS.deactivate!
    FakeFS::FileSystem.clear
  end

  it "returns a list of recipes" do
    Pathname.glob(RECIPES_PATH.join('**/*.rb')) do |path|
      recipe_files.should.include?(path.expand_path)
    end
  end

  it "loads local recipes" do
    local = Pathname.new('~/.kick').expand_path
    local.mkpath
    recipe = local.join('some-random-recipe.rb')
    FileUtils.touch(recipe)

    recipe_files.should.include?(recipe.expand_path)
  end

  it "loads recipes in current working dir" do
    pwd = Pathname.pwd.join('.kick')
    pwd.mkpath
    recipe = pwd.expand_path.join('cwd-recipe.rb')
    FileUtils.touch(recipe)

    recipe_files.should.include(recipe.expand_path)
  end

  it "returns a list of recipe names" do
    expected = Set.new(%w(could_not_handle_file dot_kick execute_cli_command ignore jstest rails ruby).map { |n| n.to_sym })
    actual = Set.new(Kicker::Recipes.recipe_names)
    actual.should == expected
  end

  # TODO ~/.kick is no longer added to the load path, but files are looked up
  # in lib/kicker/recipes.rb recipe_filename
  #
  #if File.exist?(File.expand_path('~/.kick'))
    #it "should add ~/.kick to the load path" do
      #$:.should.include File.expand_path('~/.kick')
    #end
  #else
    #puts "[!] ~/.kick does not exist, not testing the Kicker directory support."
  #end

  it "should load a recipe" do
    should.not.raise { recipe :ruby }
  end

  it "does not break when a recipe is loaded twice" do
    should.not.raise do
      recipe :ruby
      recipe :ruby
    end
  end

  it "should define a recipe load callback" do
    called = false
    recipe('new_recipe') { called = true }
    called.should == false
    recipe(:new_recipe)
    called.should == true
  end

  it "should raise if a recipe does not exist" do
    begin
      recipe :foobar
    rescue LoadError => e
      e.message.should.start_with "Can't load recipe `foobar', it doesn't exist on disk."
    end
  end
end


================================================
FILE: spec/spec_helper.rb
================================================
require 'bacon'
require 'mocha-on-bacon'

Bacon.summary_at_exit

require 'set'

$:.unshift File.expand_path('../../lib', __FILE__)
require 'kicker'

class File
  class << self
    attr_accessor :existing_files
    attr_accessor :use_original_exist

    alias exist_without_stubbing? exist?
    def exist?(file)
      if use_original_exist
        exist_without_stubbing?(file)
      else
        if existing_files
          existing_files.include?(file)
        else
          raise "Please stub the files you want to exist by setting File.existing_files"
        end
      end
    end
  end
end

File.use_original_exist = true


================================================
FILE: spec/utils_spec.rb
================================================
require File.expand_path('../spec_helper', __FILE__)

class Kicker
  module Utils
    public :will_execute_command, :did_execute_command
  end
end

describe "A Kicker instance, concerning its utility methods" do
  def utils
    Kicker::Utils
  end

  before do
    utils.stubs(:puts)
    Kicker::Notification.use = false
    Kicker::Notification.stubs(:`)
  end

  after do
    Kicker.silent = false
    Kicker::Notification.use = true
  end

  it "should print a log entry with timestamp" do
    now = Time.now
    Time.stubs(:now).returns(now)

    utils.expects(:puts).with("#{now.strftime('%H:%M:%S')}.#{now.usec.to_s[0,2]} | the message")
    utils.send(:log, 'the message')
  end

  it 'should print a log entry with no timestamp in quiet mode' do
    before = Kicker.quiet

    utils.expects(:puts).with('the message')

    Kicker.quiet = true
    utils.send(:log, 'the message')

    Kicker.quiet = before
  end

  it "logs that the command succeeded" do
    utils.stubs(:_execute).with do |job|
      job.output = "line 1\nline 2"
      job.exit_code = 0
    end
    utils.expects(:log).with('Executing: ls')
    utils.expects(:log).with('Success')
    utils.execute('ls')
  end

  it "logs that the command failed" do
    utils.stubs(:_execute).with do |job|
      job.output = "line 1\nline 2"
      job.exit_code = 123
    end
    utils.expects(:log).with('Executing: ls')
    utils.expects(:log).with('Failed (123)')
    utils.execute('ls')
  end

  it "calls the block given to execute and yields the job so the user can transform the output" do
    Kicker.silent = true

    utils.stubs(:_execute).with do |job|
      job.output = "line 1\nline 2"
      job.exit_code = 123
    end

    utils.expects(:log).with('Executing: ls -l')
    utils.expects(:puts).with("\nOhnoes!\n\n")
    utils.expects(:log).with('Failed (123)')

    utils.execute('ls -l') do |job|
      job.output = job.success? ? 'Done!' : 'Ohnoes!'
    end
  end

  before do
    Kicker::Notification.use = true
  end

  it "notifies that a change occurred and shows the command and then the output" do
    utils.stubs(:log)
    utils.stubs(:_execute).with do |job|
      job.output = "line 1\nline 2"
    end
    Kicker::Notification.expects(:notify).with(:title => 'Kicker: Executing', :message => "ls")
    Kicker::Notification.expects(:notify).with(:title => 'Kicker: Success', :message => "line 1\nline 2")
    utils.execute('ls')
  end

  it "does not notify that a change occured in silent mode" do
    Kicker.silent = true
    utils.stubs(:did_execute_command)

    utils.expects(:log)
    Kicker::Notification.expects(:change_occured).never
    utils.execute('ls')
  end

  it "only logs that it has succeeded in silent mode" do
    Kicker.silent = true
    Kicker::Notification.expects(:notify).with(:title => "Kicker: Success", :message => "")

    job = Kicker::Job.new(:command => 'ls -l', :exit_code => 0, :output => "line 1\nline 2")

    utils.expects(:log).with("Success")
    utils.did_execute_command(job)
  end

  it "fully logs that it has failed in silent mode" do
    Kicker.silent = true
    Kicker::Notification.expects(:notify).with(:title => "Kicker: Failed (123)", :message => "")

    utils.expects(:puts).with("\nline 1\nline 2\n\n")
    utils.expects(:log).with('Failed (123)')

    job = Kicker::Job.new(:command => 'ls -l', :exit_code => 123, :output => "line 1\nline 2")
    utils.did_execute_command(job)
  end
end

describe "Kernel utility methods" do
  def utils
    Kicker::Utils
  end

  it "should forward log calls to the Kicker::Utils module" do
    utils.expects(:log).with('the message')
    log 'the message'
  end

  it "should forward execute calls to the Kicker::Utils module" do
    utils.expects(:execute).with('ls')
    execute 'ls'
  end
end
Download .txt
gitextract_s68j9c9x/

├── .gitignore
├── .kick
├── .travis.yml
├── Gemfile
├── LICENSE
├── README.rdoc
├── Rakefile
├── TODO.rdoc
├── bin/
│   └── kicker
├── kicker.gemspec
├── lib/
│   ├── kicker/
│   │   ├── callback_chain.rb
│   │   ├── core_ext.rb
│   │   ├── fsevents.rb
│   │   ├── job.rb
│   │   ├── notification.rb
│   │   ├── options.rb
│   │   ├── recipes/
│   │   │   ├── could_not_handle_file.rb
│   │   │   ├── dot_kick.rb
│   │   │   ├── execute_cli_command.rb
│   │   │   ├── ignore.rb
│   │   │   ├── jstest.rb
│   │   │   ├── rails.rb
│   │   │   └── ruby.rb
│   │   ├── recipes.rb
│   │   ├── utils.rb
│   │   └── version.rb
│   └── kicker.rb
├── rakelib/
│   └── gem_release.rake
└── spec/
    ├── callback_chain_spec.rb
    ├── core_ext_spec.rb
    ├── filesystem_change_spec.rb
    ├── fixtures/
    │   └── a_file_thats_reloaded.rb
    ├── fsevents_spec.rb
    ├── initialization_spec.rb
    ├── job_spec.rb
    ├── kicker_spec.rb
    ├── notification_spec.rb
    ├── options_spec.rb
    ├── recipes/
    │   ├── could_not_handle_file_spec.rb
    │   ├── dot_kick_spec.rb
    │   ├── execute_cli_command_spec.rb
    │   ├── ignore_spec.rb
    │   ├── jstest_spec.rb
    │   ├── rails_spec.rb
    │   └── ruby_spec.rb
    ├── recipes_spec.rb
    ├── spec_helper.rb
    └── utils_spec.rb
Download .txt
SYMBOL INDEX (155 symbols across 23 files)

FILE: lib/kicker.rb
  class Kicker (line 11) | class Kicker #:nodoc:
    method run (line 12) | def self.run(argv = ARGV)
    method initialize (line 19) | def initialize
    method paths (line 23) | def paths
    method start (line 27) | def start
    method loop! (line 39) | def loop!
    method validate_options! (line 45) | def validate_options!
    method validate_paths_and_command! (line 50) | def validate_paths_and_command!
    method validate_paths_exist! (line 57) | def validate_paths_exist!
    method run_watch_dog! (line 66) | def run_watch_dog!
    method run_startup_chain (line 77) | def run_startup_chain
    method finished_processing! (line 81) | def finished_processing!
    method process (line 85) | def process(events)
    method changed_files (line 93) | def changed_files(events)
    method files_in_directory (line 99) | def files_in_directory(dir)
    method file_changed_since_last_event? (line 105) | def file_changed_since_last_event?(file)
    method make_paths_relative (line 111) | def make_paths_relative(files)

FILE: lib/kicker/callback_chain.rb
  class Kicker (line 1) | class Kicker
    class CallbackChain (line 2) | class CallbackChain < Array #:nodoc:
      method call (line 6) | def call(files, stop_when_empty = true)
    method startup_chain (line 16) | def startup_chain
    method pre_process_chain (line 21) | def pre_process_chain
    method process_chain (line 26) | def process_chain
    method post_process_chain (line 31) | def post_process_chain
    method full_chain (line 36) | def full_chain
    method startup_chain (line 41) | def startup_chain
    method pre_process_chain (line 45) | def pre_process_chain
    method process_chain (line 49) | def process_chain
    method post_process_chain (line 53) | def post_process_chain
    method full_chain (line 57) | def full_chain
  type Kernel (line 62) | module Kernel
    function startup (line 68) | def startup(callback = nil, &block)
    function pre_process (line 76) | def pre_process(callback = nil, &block)
    function process (line 84) | def process(callback = nil, &block)
    function post_process (line 92) | def post_process(callback = nil, &block)

FILE: lib/kicker/core_ext.rb
  class Kicker (line 1) | class Kicker
    type ArrayExt (line 2) | module ArrayExt
      function take_and_map (line 21) | def take_and_map(pattern = nil, flatten_and_compact = true)

FILE: lib/kicker/fsevents.rb
  class Kicker (line 5) | class Kicker
    class FSEvents (line 6) | class FSEvents
      class FSEvent (line 7) | class FSEvent
        method initialize (line 10) | def initialize(path)
        method files (line 14) | def files
      method start_watching (line 25) | def self.start_watching(paths, options={}, &block)

FILE: lib/kicker/job.rb
  class Kicker (line 1) | class Kicker
    class Job (line 2) | class Job
      method attr_with_default (line 3) | def self.attr_with_default(name, merge_hash = false, &default)
      method initialize (line 23) | def initialize(attributes)
      method success? (line 29) | def success?

FILE: lib/kicker/notification.rb
  class Kicker (line 3) | class Kicker
    type Notification (line 4) | module Notification #:nodoc:
      function notify (line 11) | def notify(options)

FILE: lib/kicker/options.rb
  class Kicker (line 3) | class Kicker
    method silent? (line 7) | def silent?
    method quiet? (line 11) | def quiet?
    method clear_console? (line 15) | def clear_console?
    method osx? (line 20) | def osx?
    type Options (line 31) | module Options #:nodoc:
      function recipes_for_display (line 34) | def self.recipes_for_display
      function parser (line 38) | def self.parser
      function parse (line 83) | def self.parse(argv)
  type Kernel (line 90) | module Kernel
    function options (line 93) | def options

FILE: lib/kicker/recipes.rb
  type Kernel (line 1) | module Kernel
    function recipe (line 18) | def recipe(name, &block)
  class Kicker (line 23) | class Kicker
    type Recipes (line 24) | module Recipes #:nodoc:
      function reset! (line 32) | def reset!
      function recipes (line 40) | def recipes
      function recipe_filename (line 44) | def recipe_filename(name)
      function recipe_names (line 54) | def recipe_names
      function recipe_files (line 58) | def recipe_files
      function define_recipe (line 62) | def define_recipe(name, &block)
      function load_recipe (line 66) | def load_recipe(name)
      function activate_recipe (line 74) | def activate_recipe(name)
      function recipe (line 86) | def recipe(name, &block)

FILE: lib/kicker/recipes/dot_kick.rb
  type ReloadDotKick (line 1) | module ReloadDotKick #:nodoc
    function save_state (line 3) | def save_state
    function call (line 8) | def call(files)
    function use? (line 12) | def use?
    function load! (line 16) | def load!
    function reset! (line 20) | def reset!
    function reset_chains! (line 26) | def reset_chains!
    function remove_loaded_features! (line 33) | def remove_loaded_features!

FILE: lib/kicker/recipes/ignore.rb
  type Ignore (line 6) | module Ignore
    function call (line 7) | def self.call(files) #:nodoc:
    function ignores (line 11) | def self.ignores #:nodoc:
    function ignore (line 15) | def self.ignore(regexp_or_string) #:nodoc:
  type Kernel (line 20) | module Kernel
    function ignore (line 29) | def ignore(regexp_or_string)

FILE: lib/kicker/recipes/rails.rb
  class Kicker::Recipes::Rails (line 3) | class Kicker::Recipes::Rails < Kicker::Recipes::Ruby
    method type_to_test_dir (line 11) | def type_to_test_dir(type)
    method all_controller_tests (line 38) | def all_controller_tests
    method tests_for_model (line 48) | def tests_for_model(model)
    method handle! (line 64) | def handle!

FILE: lib/kicker/recipes/ruby.rb
  class Kicker::Recipes::Ruby (line 1) | class Kicker::Recipes::Ruby
    method test_type (line 9) | def test_type
    method runner_bin (line 22) | def runner_bin
    method test_cases_root (line 32) | def test_cases_root
    method test_options (line 41) | def test_options
    method reset! (line 45) | def reset!
    method runner_command (line 52) | def runner_command(*parts)
    method run_tests (line 66) | def run_tests(tests)
    method test_runner_command (line 70) | def test_runner_command(tests)
    method run_with_test_runner (line 76) | def run_with_test_runner(tests)
    method spec_runner_command (line 80) | def spec_runner_command(tests)
    method run_with_spec_runner (line 85) | def run_with_spec_runner(tests)
    method call (line 90) | def self.call(files) #:nodoc:
    method initialize (line 99) | def initialize(files) #:nodoc:
    method test_type (line 105) | def test_type
    method runner_bin (line 110) | def runner_bin
    method test_cases_root (line 115) | def test_cases_root
    method test_file (line 124) | def test_file(name)
    method handle! (line 131) | def handle!

FILE: lib/kicker/utils.rb
  class Kicker (line 3) | class Kicker
    type Utils (line 4) | module Utils #:nodoc:
      function perform_work (line 10) | def perform_work(command_or_options)
      function execute (line 25) | def execute(command_or_options)
      function log (line 32) | def log(message)
      function clear_console! (line 41) | def clear_console!
      function _execute (line 49) | def _execute(job)
      function popen (line 72) | def popen(command, &block)
      function will_execute_command (line 82) | def will_execute_command(job)
      function did_execute_command (line 95) | def did_execute_command(job)
  type Kernel (line 109) | module Kernel
    function log (line 111) | def log(message)
    function perform_work (line 118) | def perform_work(command, &block)
    function execute (line 124) | def execute(command, &block)

FILE: lib/kicker/version.rb
  class Kicker (line 1) | class Kicker

FILE: rakelib/gem_release.rake
  function gem_version (line 5) | def gem_version
  function gem_file (line 10) | def gem_file

FILE: spec/filesystem_change_spec.rb
  function silent (line 5) | def silent(&block)
  function touch (line 12) | def touch(file)
  function event (line 18) | def event(*files)
  function remove_tmp_files! (line 24) | def remove_tmp_files!

FILE: spec/fsevents_spec.rb
  class FakeListener (line 3) | class FakeListener
    method initialize (line 4) | def initialize(paths, options={})
    method change (line 8) | def change(&block)
    method start (line 13) | def start blocking=true
    method fake_event (line 17) | def fake_event(paths)

FILE: spec/initialization_spec.rb
  type ReloadDotKick (line 3) | module ReloadDotKick; end

FILE: spec/recipes/rails_spec.rb
  class Kicker::Recipes::Rails (line 4) | class Kicker::Recipes::Rails
    method run_tests (line 7) | def run_tests(tests)
  type SharedRailsHandlerHelper (line 60) | module SharedRailsHandlerHelper
    function should_match (line 61) | def should_match(files, tests, existing_files=nil)

FILE: spec/recipes/ruby_spec.rb
  class Kicker::Recipes::Ruby (line 4) | class Kicker::Recipes::Ruby
    method execute (line 8) | def execute(command, &block)

FILE: spec/recipes_spec.rb
  type ReloadDotKick (line 4) | module ReloadDotKick; end
  function recipe_files (line 9) | def recipe_files

FILE: spec/spec_helper.rb
  class File (line 11) | class File
    method exist? (line 17) | def exist?(file)

FILE: spec/utils_spec.rb
  class Kicker (line 3) | class Kicker
    type Utils (line 4) | module Utils
  function utils (line 10) | def utils
  function utils (line 127) | def utils
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (91K chars).
[
  {
    "path": ".gitignore",
    "chars": 93,
    "preview": ".*.sw?\n*.gem\n/.rbenv-version\n.DS_Store\n/coverage\n/rdoc\n/pkg\n/html\n/Gemfile.lock\n/tmp/\n/.idea\n"
  },
  {
    "path": ".kick",
    "chars": 978,
    "preview": "recipe :ignore\nrecipe :ruby\n\nKicker::Recipes::Ruby.runner_bin = 'bacon'\n\nprocess do |files|\n  test_files = files.take_an"
  },
  {
    "path": ".travis.yml",
    "chars": 51,
    "preview": "rvm:\n  - 2.1.3\n  - 2.0.0\n  - 1.9.3\n\nscript: \"rake\"\n"
  },
  {
    "path": "Gemfile",
    "chars": 89,
    "preview": "source \"https://rubygems.org\"\ngemspec\n\ngem 'rake'\n\nplatforms :mri_18 do\n  gem 'rdoc'\nend\n"
  },
  {
    "path": "LICENSE",
    "chars": 2603,
    "preview": "Kicker:\n\nCopyright (c) 2009 Eloy Duran <eloy.de.enige@gmail.com>\n\nPermission is hereby granted, free of charge, to any p"
  },
  {
    "path": "README.rdoc",
    "chars": 5185,
    "preview": "= Kicker\n\n{<img src=\"https://travis-ci.org/alloy/kicker.svg?branch=master\" alt=\"Build Status\" />}[https://travis-ci.org/"
  },
  {
    "path": "Rakefile",
    "chars": 514,
    "preview": "require 'rdoc/task'\n\ndesc \"Run specs\"\ntask :spec do\n  # shuffle to ensure that tests are run in different order\n  files "
  },
  {
    "path": "TODO.rdoc",
    "chars": 332,
    "preview": "* Move larger parts of README to the GitHub wiki so the README is to the point.\n* Add a recipe which implements the basi"
  },
  {
    "path": "bin/kicker",
    "chars": 202,
    "preview": "#!/usr/bin/env ruby\n\nif $0 == __FILE__\n  $:.unshift File.expand_path('../../lib', __FILE__)\n  $:.unshift File.expand_pat"
  },
  {
    "path": "kicker.gemspec",
    "chars": 1189,
    "preview": "# -*- encoding: utf-8 -*-\n\n$:.unshift File.expand_path('../lib', __FILE__)\nrequire 'kicker/version'\nrequire 'date'\n\nGem:"
  },
  {
    "path": "lib/kicker/callback_chain.rb",
    "chars": 2632,
    "preview": "class Kicker\n  class CallbackChain < Array #:nodoc:\n    alias_method :append_callback,  :push\n    alias_method :prepend_"
  },
  {
    "path": "lib/kicker/core_ext.rb",
    "chars": 1076,
    "preview": "class Kicker\n  module ArrayExt\n    # Deletes elements from self for which the block evaluates to +true+. A new\n    # arr"
  },
  {
    "path": "lib/kicker/fsevents.rb",
    "chars": 854,
    "preview": "# encoding: utf-8\n\nrequire 'listen'\n\nclass Kicker\n  class FSEvents\n    class FSEvent\n      attr_reader :path\n\n      def "
  },
  {
    "path": "lib/kicker/job.rb",
    "chars": 1587,
    "preview": "class Kicker\n  class Job\n    def self.attr_with_default(name, merge_hash = false, &default)\n      # If `nil` this return"
  },
  {
    "path": "lib/kicker/notification.rb",
    "chars": 661,
    "preview": "require 'notify'\n\nclass Kicker\n  module Notification #:nodoc:\n    TITLE = 'Kicker'\n\n    class << self\n      attr_accesso"
  },
  {
    "path": "lib/kicker/options.rb",
    "chars": 2535,
    "preview": "require 'optparse'\n\nclass Kicker\n  class << self\n    attr_accessor :latency, :paths, :silent, :quiet, :clear_console\n\n  "
  },
  {
    "path": "lib/kicker/recipes/could_not_handle_file.rb",
    "chars": 131,
    "preview": "post_process do |files|\n  unless Kicker.silent?\n    log('')\n    log(\"Could not handle: #{files.join(', ')}\")\n    log('')"
  },
  {
    "path": "lib/kicker/recipes/dot_kick.rb",
    "chars": 944,
    "preview": "module ReloadDotKick #:nodoc\n  class << self\n    def save_state\n      @features_before_dot_kick = $LOADED_FEATURES.dup\n "
  },
  {
    "path": "lib/kicker/recipes/execute_cli_command.rb",
    "chars": 219,
    "preview": "options.on('-e', '--execute [COMMAND]', 'The command to execute.') do |command|\n  callback = lambda do |files|\n    files"
  },
  {
    "path": "lib/kicker/recipes/ignore.rb",
    "chars": 933,
    "preview": "# A recipe which removes files from the files array, thus “ignoring” them.\n#\n# By default ignores logs, tmp, and svn and"
  },
  {
    "path": "lib/kicker/recipes/jstest.rb",
    "chars": 301,
    "preview": "recipe :jstest do\n  process do |files|\n    test_files = files.take_and_map do |file|\n      if file =~ %r{^(test|public)/"
  },
  {
    "path": "lib/kicker/recipes/rails.rb",
    "chars": 3050,
    "preview": "recipe :ruby\n\nclass Kicker::Recipes::Rails < Kicker::Recipes::Ruby\n  class << self\n    # Call these options on the Ruby "
  },
  {
    "path": "lib/kicker/recipes/ruby.rb",
    "chars": 4424,
    "preview": "class Kicker::Recipes::Ruby\n  class << self\n    # Assigns the type of tests to run. Eg: `test' or `spec'.\n    attr_write"
  },
  {
    "path": "lib/kicker/recipes.rb",
    "chars": 2660,
    "preview": "module Kernel\n  # If only given a <tt>name</tt>, the specified recipe will be loaded. For\n  # instance, the following, i"
  },
  {
    "path": "lib/kicker/utils.rb",
    "chars": 3087,
    "preview": "require 'shellwords' if RUBY_VERSION >= \"1.9\"\n\nclass Kicker\n  module Utils #:nodoc:\n    extend self\n\n    attr_accessor :"
  },
  {
    "path": "lib/kicker/version.rb",
    "chars": 37,
    "preview": "class Kicker\n  VERSION = \"3.0.0\"\nend\n"
  },
  {
    "path": "lib/kicker.rb",
    "chars": 2511,
    "preview": "require 'kicker/version'\nrequire 'kicker/fsevents'\nrequire 'kicker/callback_chain'\nrequire 'kicker/core_ext'\nrequire 'ki"
  },
  {
    "path": "rakelib/gem_release.rake",
    "chars": 697,
    "preview": "NAME = 'Kicker'\nLOWERCASE_NAME = NAME.downcase\nGEM_NAME = LOWERCASE_NAME\n\ndef gem_version\n  require File.expand_path(\".."
  },
  {
    "path": "spec/callback_chain_spec.rb",
    "chars": 4659,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\n\ndescribe \"Kicker, concerning its callback chains\" do\n  before do\n "
  },
  {
    "path": "spec/core_ext_spec.rb",
    "chars": 1253,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\n\ndescribe \"Array#take_and_map\" do\n  before do\n    @array = %w{ foo "
  },
  {
    "path": "spec/filesystem_change_spec.rb",
    "chars": 4096,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\n\nrequire 'stringio'\ndescribe \"Kicker, when a change occurs\" do\n  de"
  },
  {
    "path": "spec/fixtures/a_file_thats_reloaded.rb",
    "chars": 51,
    "preview": "$FROM_RELOADED_FILE ||= 0\n$FROM_RELOADED_FILE += 1\n"
  },
  {
    "path": "spec/fsevents_spec.rb",
    "chars": 1086,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\n\nclass FakeListener\n  def initialize(paths, options={})\n    @paths "
  },
  {
    "path": "spec/initialization_spec.rb",
    "chars": 3311,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\n\nmodule ReloadDotKick; end\n\ndescribe \"Kicker\" do\n  before do\n    Ki"
  },
  {
    "path": "spec/job_spec.rb",
    "chars": 3670,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\n\ndescribe \"Kicker::Job\" do\n  before do\n    @job = Kicker::Job.new(:"
  },
  {
    "path": "spec/kicker_spec.rb",
    "chars": 357,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\nrequire 'stringio'\n\ndescribe \"Kicker\" do\n  before { @stdout = $stdo"
  },
  {
    "path": "spec/notification_spec.rb",
    "chars": 592,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\n\ndescribe \"Kicker::Notification\" do\n  it \"sends a notification, gro"
  },
  {
    "path": "spec/options_spec.rb",
    "chars": 2325,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\n\ndescribe \"Kicker::Options.parse\" do\n  after do\n    Kicker.latency "
  },
  {
    "path": "spec/recipes/could_not_handle_file_spec.rb",
    "chars": 646,
    "preview": "require File.expand_path('../../spec_helper', __FILE__)\n\ndescribe \"Kicker, concerning the default `could not handle file"
  },
  {
    "path": "spec/recipes/dot_kick_spec.rb",
    "chars": 715,
    "preview": "require File.expand_path('../../spec_helper', __FILE__)\n\ndescribe \"The .kick handler\" do\n  it \"should reset $LOADED_FEAT"
  },
  {
    "path": "spec/recipes/execute_cli_command_spec.rb",
    "chars": 1131,
    "preview": "require File.expand_path('../../spec_helper', __FILE__)\n\ndescribe \"Kicker, concerning the `execute a command-line' callb"
  },
  {
    "path": "spec/recipes/ignore_spec.rb",
    "chars": 769,
    "preview": "require File.expand_path('../../spec_helper', __FILE__)\n\nrecipe :ignore\n\nIGNORE = Kicker.pre_process_chain.find{|callbac"
  },
  {
    "path": "spec/recipes/jstest_spec.rb",
    "chars": 889,
    "preview": "require File.expand_path('../../spec_helper', __FILE__)\n\nbefore = Kicker.process_chain.dup\nrecipe :jstest\nJSTEST = (Kick"
  },
  {
    "path": "spec/recipes/rails_spec.rb",
    "chars": 7353,
    "preview": "require File.expand_path('../../spec_helper', __FILE__)\nrecipe :rails\n\nclass Kicker::Recipes::Rails\n  class << self\n    "
  },
  {
    "path": "spec/recipes/ruby_spec.rb",
    "chars": 5313,
    "preview": "require File.expand_path('../../spec_helper', __FILE__)\nrecipe :ruby\n\nclass Kicker::Recipes::Ruby\n  class << self\n    at"
  },
  {
    "path": "spec/recipes_spec.rb",
    "chars": 2413,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\nrequire 'fakefs/safe'\n\nmodule ReloadDotKick; end\n\ndescribe \"Kicker:"
  },
  {
    "path": "spec/spec_helper.rb",
    "chars": 628,
    "preview": "require 'bacon'\nrequire 'mocha-on-bacon'\n\nBacon.summary_at_exit\n\nrequire 'set'\n\n$:.unshift File.expand_path('../../lib',"
  },
  {
    "path": "spec/utils_spec.rb",
    "chars": 3775,
    "preview": "require File.expand_path('../spec_helper', __FILE__)\n\nclass Kicker\n  module Utils\n    public :will_execute_command, :did"
  }
]

About this extraction

This page contains the full source code of the alloy/kicker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (82.6 KB), approximately 23.7k tokens, and a symbol index with 155 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!