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
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
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.