watchful 0.0.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ pkg/*
3
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2010, Kyle E. Mitchell (http://kemitchell.com)
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ Watchful
2
+ ========
3
+
4
+ A shell script for busy web developers
5
+
6
+ What?
7
+ -----
8
+
9
+ Watchful is a lightweight tool that runs command line tools on updated files. It was built by a web developer who got seriously tired of managing a whole range of CLI minification, translation, and compression tools on his CSS and Javascript.
10
+
11
+ You can think of Watchful as kind of recursive, specialized [Rake](http://rake.rubyforge.org/): you tell it which tools to run on which files, then fire it off in your current project directory and go back to your text editor. When you save a change to a relevant file under that directory, Watchful will run the proper tool on it. If you are on OS X and have [Growl](http://growl.info/) installed, you’ll get a notification.
12
+
13
+ Where?
14
+ ------
15
+
16
+ Watchful is currently hosted on [GitHub](http://github.com):
17
+
18
+ [http://github.com/kemitchell/watchful](http://github.com/kemitchell/watchful)
19
+
20
+ Please feel free to fork me!
21
+
22
+ Eventually, Watchful will be available as a Ruby Gem:
23
+
24
+ sudo gem install watchful
25
+
26
+ How?
27
+ ----
28
+
29
+ All configuration is done in pure Ruby. Configurations are created by subclassing `Watchful::Configuration` in “watchful” files. An example follows:
30
+
31
+ require 'watchful/configuration'
32
+
33
+ class MyConfiguration < Watchful::Configuration
34
+
35
+ description 'LESS CSS and YUI Compressor'
36
+
37
+ # actions are applied in order, hence Less is defined before YUI
38
+
39
+ action :name => "Less CSS",
40
+ :in => ".less", # The extension of files to use as input
41
+ :out => ".css", # Extension to replace the above for output files
42
+ :command => "lessc %s %s", # The command to create output files from input files
43
+ # Currently the command is built using sprintf.
44
+ # In the future all of the above will also take blocks.
45
+ # For now, the first %s is the full input path,
46
+ # and the second the full output path.
47
+ :dependencies => ['less'] # If “less” isn’t found in PATH, this action will be disabled.
48
+
49
+ action :name => 'YUI Compressor',
50
+ :in => '.css',
51
+ :out => '.min.css', # Notice that Watchful computers extensions differently than basename
52
+ # Namely, the extension is anything in the file name from the
53
+ # first period onward.
54
+ :command => 'java -jar /usr/local/utils/yuicompressor-2.4.2.jar --type css --charset utf-8 %s -o %s',
55
+ :dependencies => ['java', '/usr/local/utils/yuicompressor-2.4.2.jar']
56
+ # Dependencies can be programs (e.g. “java”), or full file paths.
57
+
58
+ end
59
+
60
+ When Watchful is started, it searches in the following locations (in the following order) for files called “watchful” and loads them:
61
+
62
+ 1. In the directory Watchful is told to monitor
63
+ 2. In the current user’s home directory
64
+
65
+ If no such files are found, Watchful will use the default configuration, which searches your PATH for tools it recognizes. If your tool isn't listed in [http://github.com/kemitchell/watchful/blob/master/lib/watchful/defaultconfiguration.rb](defaultconfiguration.rb) and you would like it to be, please fork the project on github or send me a patch. I would be happy to include any tools with current stable or Beta releases.
data/Rakefile ADDED
@@ -0,0 +1,68 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "watchful"
8
+ gem.summary = 'Watchful, a tool for busy web devs'
9
+ gem.description = 'Applies intermediary tools to modified files'
10
+ gem.email = "kyleevanmitchell@gmail.com"
11
+ gem.homepage = "http://github.com/kemitchell/watchful"
12
+ gem.authors = ["KEM"]
13
+ gem.add_development_dependency "thoughtbot-shoulda"
14
+ gem.add_dependency('commandline')
15
+ gem.add_dependency('extensions')
16
+ gem.add_dependency('open4')
17
+ gem.rubyforge_project = 'watchful'
18
+ end
19
+ Jeweler::RubyforgeTasks.new do |rubyforge|
20
+ rubyforge.doc_task = 'watchful'
21
+ end
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
24
+ end
25
+
26
+ Jeweler::RubyforgeTasks.new do |rf|
27
+ rf.doc_task = 'rdoc'
28
+ end
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/*_test.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ begin
39
+ require 'rcov/rcovtask'
40
+ Rcov::RcovTask.new do |test|
41
+ test.libs << 'test'
42
+ test.pattern = 'test/**/*_test.rb'
43
+ test.verbose = true
44
+ end
45
+ rescue LoadError
46
+ task :rcov do
47
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
48
+ end
49
+ end
50
+
51
+ task :test => :check_dependencies
52
+
53
+ task :default => [:test, :build]
54
+
55
+ require 'rake/rdoctask'
56
+
57
+ Rake::RDocTask.new do |rdoc|
58
+ if File.exist?('VERSION')
59
+ version = File.read('VERSION')
60
+ else
61
+ version = ""
62
+ end
63
+
64
+ rdoc.rdoc_dir = 'rdoc'
65
+ rdoc.title = "watchful #{version}"
66
+ rdoc.rdoc_files.include('README*')
67
+ rdoc.rdoc_files.include('lib/**/*.rb')
68
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0.pre1
data/bin/watchful ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + "/../lib"
4
+
5
+ require 'rubygems'
6
+ require 'commandline'
7
+ require 'watchful'
8
+
9
+ begin
10
+ require 'growl' # OS X notifications
11
+ rescue LoadError
12
+ HAVE_GROWL = false
13
+ else
14
+ if (Growl.installed? rescue false)
15
+ HAVE_GROWL = true
16
+ else
17
+ HAVE_GROWL = false
18
+ end
19
+ end
20
+
21
+ class App < CommandLine::Application
22
+ def initialize
23
+ version Watchful::version
24
+ author 'KEM'
25
+ copyright '2010, Kyle Mitchell'
26
+ short_description 'runs tools on updated files'
27
+ long_description <<-eos
28
+ A configurable deamon for running source code translation and minification tools–
29
+ such as CSS and Javascript minification tools and generators—on updated files.
30
+ eos
31
+ synopsis "[-dhv] path"
32
+ option :help
33
+ option :debug
34
+ option :version
35
+ option :names => %w{--use-default-configuration --default},
36
+ :opt_found => get_args,
37
+ :opt_description => 'Use the default configuration even if a local or user-level configuration is found.'
38
+ expected_args :path
39
+ end
40
+ def main
41
+ Watchful::Configuration.active_configuration = Watchful::DefaultConfiguration
42
+ # todo: check dependencies of actions of active configuration
43
+
44
+ path = File.expand_path(args[0])
45
+ fail "No such directory: #{path} " unless File.directory? path
46
+
47
+ Watchful::load_configuration(path, opt['--use-default-configuration'])
48
+
49
+ # show the active config
50
+ debug "Config: " + Watchful::Configuration.active_configuration._description
51
+
52
+ # list active actions
53
+ Watchful::Configuration.active_configuration.actions.each do |a|
54
+ debug "Action: \t#{a.name} (#{a.in} -> #{a.out})"
55
+ end
56
+
57
+ puts "Watching #{Dir.pwd}..."
58
+ Watchful::watch_files(path)
59
+ end
60
+ end
@@ -0,0 +1,51 @@
1
+
2
+ # The name MyConfig should be accessible from the command line, e.g.
3
+ #
4
+ # watchful MyConfiguration
5
+ #
6
+ # By default (without a named config) watchful should use:
7
+ #
8
+ # 1) The configuration found in Watchful.rb, which should search PATH
9
+ # for known tools
10
+ #
11
+ # or failing that
12
+ #
13
+ # 2) The default configuration in the gem
14
+ #
15
+
16
+ class MyConfiguration < Watchful::Configuration
17
+
18
+ # hash style action description
19
+
20
+ action :name => 'CSS Minifier' # for messages
21
+ :in => ".css",
22
+ :out => ".min.css",
23
+ :command => "minify $IN $OUT",
24
+ :dependencies => ['minify']
25
+
26
+ # block style action description
27
+
28
+ action 'CSS Minifier' do |a|
29
+
30
+ # file matchers should also support blocks
31
+
32
+ a.filter do |path|
33
+ true if /.+\.css/ =~ path
34
+ end
35
+
36
+ # or regexps
37
+
38
+ a.in = /.+\.css/
39
+
40
+ # without $IN assume standard input
41
+
42
+ a.command = 'minify -o $OUT' # => "cat $IN | minify -o $OUT"
43
+
44
+ a.command = 'minify' # => "cat $IN | minify > $OUT"
45
+ # => a.dependencies << 'minify'
46
+
47
+ a.dependencies = ['minify']
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,78 @@
1
+ require 'commandline' # for debug
2
+
3
+ module Watchful
4
+
5
+ class Action
6
+
7
+ # todo: support blocks for @in, @out, and @command
8
+
9
+ PROPERTIES = [:name, :dependencies, :command, :in, :out]
10
+
11
+ PROPERTIES.each { |k| attr_accessor k }
12
+
13
+ @enabled = true
14
+ def enabled?; @enabled ;end
15
+
16
+
17
+ def initialize(options = {})
18
+
19
+ options[:dependencies] ||= []
20
+
21
+ PROPERTIES.each do |key|
22
+ self.instance_variable_set('@' + key.to_s, options[key])
23
+ end
24
+
25
+ @dependencies = [@dependencies] if @dependencies.kind_of? String
26
+
27
+ raise "Dependencies option must be a string or array" if not @dependencies.kind_of? Array
28
+
29
+ [@in, @out].each do |i|
30
+ @enabled = false unless i and i.instance_of? String and i.starts_with? '.'
31
+ end
32
+
33
+ @enabled = @enabled && self.has_dependencies?
34
+
35
+ end
36
+
37
+ def has_dependencies?
38
+ return true if @dependencies.empty?
39
+ have_all = @dependencies.any? do |d|
40
+ (Action.have_command?(d)) || (File.exists?(File.expand_path(d)))
41
+ end
42
+ # todo: more detailed messages about missing dependencies
43
+ debug "Missing dependencies for action \"#{@name}\"" unless have_all
44
+ return have_all
45
+ end
46
+
47
+ def command_string(input_path)
48
+ Kernel.sprintf(@command, input_path, self.output_path_for(input_path))
49
+ end
50
+
51
+ # todo: better have_command?
52
+ def Action.have_command?(cmd)
53
+ raise 'Argument must be a string' if not cmd.kind_of? String
54
+ raise 'Argument cannot be an empty string' if cmd.empty?
55
+
56
+ ENV['PATH'].split(':').each do |dir|
57
+ return true if File.exists?("#{dir}/#{cmd}")
58
+ end
59
+ false
60
+ end
61
+
62
+ def output_path_for(source_path)
63
+ return File.dirname(source_path) + '/' + File.basename(source_path, @in) + @out
64
+ end
65
+
66
+ # does the given path look like a path to which this action might write output?
67
+ def output_file?(path)
68
+ Watchful::compound_extension_of(path) == @in
69
+ end
70
+
71
+ # does the given path look like a path to which this action could be applied?
72
+ def input_file?(path)
73
+ Watchful::compound_extension_of(path) == @in
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,42 @@
1
+ require 'watchful/action'
2
+
3
+ module Watchful
4
+
5
+ class Configuration
6
+
7
+ @@active_configuration = nil
8
+
9
+ def self.active_configuration; @@active_configuration; end
10
+ def self.active_configuration=(x)
11
+ unless x.is_a? Class and x.ancestors.include? Watchful::Configuration
12
+ throw "Not a valid configuration class"
13
+ end
14
+ @@active_configuration = x
15
+ end
16
+
17
+ class << self
18
+
19
+ attr_reader :actions
20
+
21
+ def description(s); @description = s; end
22
+
23
+ def _description; @description; end
24
+
25
+ def action(args)
26
+ # set the description in case the user doesn’t
27
+ @description ||= 'Custom Configuration'
28
+
29
+ # set to active configuration
30
+ @@active_configuration = self
31
+
32
+ # todo: support block-passing style
33
+ @actions ||= []
34
+ a = Watchful::Action.new(args)
35
+ @actions << a
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,34 @@
1
+ require 'watchful/configuration'
2
+
3
+ module Watchful
4
+
5
+ class DefaultConfiguration < Configuration
6
+
7
+ description "Default Configuration"
8
+
9
+ # LESS CSS, if installed
10
+
11
+ begin
12
+ gem "less"
13
+ action :name => 'LESS CSS',
14
+ :in => '.less',
15
+ :out => '.css',
16
+ :command => 'lessc %s %s',
17
+ :dependencies => ['less']
18
+ rescue GEM::LoadError
19
+ # pass
20
+ end
21
+
22
+ # SASS, if installed
23
+
24
+ if Action.have_command? 'sass'
25
+ action :name => 'SASS',
26
+ :in => '.sass',
27
+ :out => '.min.css',
28
+ :command => 'sass --style compressed %s %s',
29
+ :dependencies => 'sass'
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,20 @@
1
+ require 'extensions/string'
2
+
3
+ module Watchful
4
+
5
+ def self.younger_than(a, b)
6
+ if (not File.exists? a) or (not File.exists? b)
7
+ raise Exception, "cannot compare modification times of non-existant files"
8
+ end
9
+ return (File.stat(a).mtime > File.stat(b).mtime)
10
+ end
11
+
12
+ def self.compound_extension_of(str)
13
+ return nil if str.empty?
14
+ basename = File.basename(str)
15
+ periodpos = basename.index('.')
16
+ return nil if not periodpos or periodpos < 1
17
+ return basename[basename.index('.')..-1]
18
+ end
19
+
20
+ end
@@ -0,0 +1,106 @@
1
+ require 'find'
2
+ require 'open4'
3
+ require 'watchful/defaultconfiguration'
4
+ require "commandline"
5
+
6
+ module Watchful
7
+
8
+ def self.each_source_file_in_dir(path = Dir.pwd)
9
+ Find.find(path) do |filepath|
10
+ next if FileTest.directory?(filepath)
11
+ yield(filepath)
12
+ end
13
+ end
14
+
15
+ def self.check_for_interrupts
16
+ begin
17
+ sleep 1
18
+ rescue Interrupt
19
+ puts # new line
20
+ exit 0
21
+ end
22
+ end
23
+
24
+ CONFIGURATION_FILE_NAME = 'watchful'
25
+
26
+ def self.load_configuration(path, use_default = false)
27
+
28
+ # todo: more efficient loading of configurations
29
+ # todo: check that relative paths work on Windows
30
+
31
+ local = File.expand_path("#{path}/#{CONFIGURATION_FILE_NAME}")
32
+ user = File.expand_path("~/#{CONFIGURATION_FILE_NAME}")
33
+
34
+ if File.exists? local
35
+ debug "Loading #{local}"
36
+ load local
37
+ elsif File.exists? user
38
+ debug "Loading #{user}"
39
+ load user
40
+ end
41
+
42
+ end
43
+
44
+ @files_with_errors = Hash.new # {:path => '', :mtime => ### }
45
+
46
+ def self.watch_files(path)
47
+ @output_file_extensions = Configuration.active_configuration.actions.collect do |a|
48
+ a.out
49
+ end
50
+ loop do
51
+ check_for_interrupts
52
+ each_source_file_in_dir(path) do |file_path|
53
+ extension = compound_extension_of(file_path)
54
+
55
+ action = Configuration.active_configuration.actions.find { |a| a.input_file? file_path}
56
+
57
+ next if action.nil?
58
+
59
+ output_path = action.output_path_for(file_path)
60
+
61
+ if (!File.exists?(output_path)) or younger_than(file_path, output_path)
62
+ error_mtime = @files_with_errors[file_path]
63
+ next if error_mtime && error_mtime >= File.stat(file_path).mtime
64
+ do_action(file_path, action)
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+
71
+ def self.do_action(source_file, action)
72
+
73
+ puts "#{action.name}: #{source_file}"
74
+
75
+ command = action.command_string(source_file)
76
+
77
+ debug "\t#{command}"
78
+
79
+ proc_pid, proc_stdin, proc_stdout, proc_stderr = Open4::popen4(command)
80
+ proc_ignored, proc_status = Process::waitpid2(proc_pid)
81
+
82
+ error = proc_stderr.gets
83
+
84
+ if proc_status && !error
85
+ if HAVE_GROWL
86
+ growl = Growl.new
87
+ growl.title = action.name
88
+ growl.message = "#{source_file}"
89
+ growl.run
90
+ end
91
+ @files_with_errors.delete(source_file)
92
+ else
93
+ $stderr.puts("ERROR -- #{action.name} -- #{source_file}")
94
+ $stderr.puts(error)
95
+ if HAVE_GROWL
96
+ growl = Growl.new
97
+ growl.title = "ERROR: #{action.name}"
98
+ growl.message = error
99
+ growl.run
100
+ end
101
+ @files_with_errors[source_file] = File.stat(source_file).mtime
102
+ end
103
+
104
+ end
105
+
106
+ end
data/lib/watchful.rb ADDED
@@ -0,0 +1,17 @@
1
+ # Author:: K E Mitchell (mailto:kyleevanmitchell@gmail.com)
2
+ # License:: ISC License (http://www.isc.org/software/license)
3
+
4
+ WATCHFUL_ROOT = File.expand_path(File.dirname(__FILE__))
5
+
6
+ $:.unshift File.dirname(__FILE__)
7
+
8
+ require 'watchful/paths'
9
+ require 'watchful/watch'
10
+ require 'watchful/action'
11
+ require 'watchful/defaultconfiguration'
12
+
13
+ module Watchful
14
+ def self.version
15
+ File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip
16
+ end
17
+ end
@@ -0,0 +1,159 @@
1
+ require 'test_helper'
2
+
3
+ class WatchfulTest < Test::Unit::TestCase
4
+
5
+ context 'Action class' do
6
+
7
+ should 'throw an exception with incomplete constructor parameters' do
8
+ begin
9
+ Watchful::Action.new(:name => 'Incomplete Action')
10
+ flunk
11
+ rescue Exception
12
+ assert true
13
+ end
14
+ end
15
+
16
+ should 'properly construct with a complete set of options' do
17
+ a = Watchful::Action.new(
18
+ :name => 'Test Action',
19
+ :dependencies => ['cat'], :command => 'cat %s %s',
20
+ :in => '.txt', :out => '.copy'
21
+ )
22
+ assert a.instance_of? Watchful::Action
23
+ assert_equal a.name, 'Test Action'
24
+ end
25
+
26
+ should 'throw an exception when a non-string, non-array value is passed as dependencies' do
27
+ begin
28
+ a = Watchful::Action.new(
29
+ :name => 'Test Action',
30
+ :dependencies => Date.new, :command => 'cat %s %s',
31
+ :in => '.txt', :out => '.copy'
32
+ )
33
+ flunk
34
+ rescue Exception
35
+ assert true
36
+ end
37
+ end
38
+
39
+ context 'output_path_for' do
40
+
41
+ setup do
42
+ a = Watchful::Action.new(
43
+ :name => 'Test Action',
44
+ :dependencies => ['cat'], :command => 'cat %s %s',
45
+ :in => '.txt', :out => '.copy'
46
+ )
47
+ end
48
+
49
+ should 'produce correct path' do
50
+ a = Watchful::Action.new(
51
+ :name => 'Test Action',
52
+ :dependencies => ['cat'], :command => 'cat %s %s',
53
+ :in => '.txt', :out => '.copy'
54
+ )
55
+ assert_equal a.output_path_for('/tmp/test.txt'), '/tmp/test.copy'
56
+ end
57
+
58
+ should 'produce correct path with complex extensions' do
59
+ a = Watchful::Action.new(
60
+ :name => 'Test Action',
61
+ :dependencies => ['cat'], :command => 'cat %s %s',
62
+ :in => '.max.txt', :out => '.min.copy'
63
+ )
64
+ assert_equal a.output_path_for('/tmp/test.max.txt'), '/tmp/test.min.copy'
65
+ end
66
+
67
+ should 'produce correct with blank output extension' do
68
+ a = Watchful::Action.new(
69
+ :name => 'Test Action',
70
+ :dependencies => ['cat'], :command => 'cat %s %s',
71
+ :in => '', :out => '.copy'
72
+ )
73
+ assert_equal a.output_path_for('/tmp/test.txt'), '/tmp/test.txt.copy'
74
+ end
75
+
76
+ should 'produce correct with blank source extension' do
77
+ a = Watchful::Action.new(
78
+ :name => 'Test Action',
79
+ :dependencies => ['cat'], :command => 'cat %s %s',
80
+ :in => '.txt', :out => ''
81
+ )
82
+ assert_equal a.output_path_for('/tmp/test.txt'), '/tmp/test'
83
+ end
84
+
85
+ should 'produce correct with blank extensions' do
86
+ a = Watchful::Action.new(
87
+ :name => 'Test Action',
88
+ :dependencies => ['cat'], :command => 'cat %s %s',
89
+ :in => '', :out => ''
90
+ )
91
+ assert_equal a.output_path_for('/tmp/test'), '/tmp/test'
92
+ end
93
+
94
+ end
95
+
96
+ context 'dependencies' do
97
+
98
+ context 'have_command?' do
99
+
100
+ should 'raise an exception for an empty string' do
101
+ begin
102
+ Watchful::Action.have_command?('')
103
+ flunk
104
+ rescue Exception
105
+ assert true
106
+ end
107
+ end
108
+
109
+ should 'raise an exception for a non-string argument' do
110
+ begin
111
+ Watchful::Action.have_command?(nil)
112
+ flunk
113
+ rescue Exception
114
+ assert true
115
+ end
116
+ end
117
+
118
+ should 'return false for missing commands' do
119
+ assert_equal Watchful::Action.have_command?('Y0CbR4CEP2XY'), false
120
+ end
121
+
122
+ should 'return true for available commands' do
123
+ assert_equal Watchful::Action.have_command?('cat'), true
124
+ end
125
+
126
+ end
127
+
128
+ should 'confirm valid dependencies' do
129
+ a = Watchful::Action.new(
130
+ :name => 'Test Action',
131
+ :dependencies => ['cat', 'echo'], :command => 'cat %s %s',
132
+ :in => '.txt', :out => '.copy'
133
+ )
134
+ assert_equal a.has_dependencies?, true
135
+ end
136
+
137
+ should 'detect a missing binary dependency' do
138
+ a = Watchful::Action.new(
139
+ :name => 'Test Action',
140
+ :dependencies => ['Y0CbR4CEP2XY'], :command => 'Y0CbR4CEP2XY %s %s',
141
+ :in => '.txt', :out => '.copy'
142
+ )
143
+ assert_equal a.has_dependencies?, false
144
+ end
145
+
146
+ should 'confirm availability of no dependencies' do
147
+ a = Watchful::Action.new(
148
+ :name => 'Test Action',
149
+ :dependencies => nil, :command => '',
150
+ :in => '.txt', :out => '.copy'
151
+ )
152
+ assert_equal a.has_dependencies?, true
153
+ end
154
+
155
+ end
156
+
157
+ end
158
+
159
+ end
@@ -0,0 +1,80 @@
1
+ require 'test_helper'
2
+
3
+ require "watchful/configuration"
4
+
5
+ class FirstTestConfiguration < Watchful::Configuration
6
+
7
+ action :name => 'Placeholder Action'
8
+
9
+ end
10
+
11
+ puts "FirstTestConfiguration is a Configuration?".kind_of? Watchful::Configuration
12
+
13
+ class WatchfulTest < Test::Unit::TestCase
14
+
15
+ context 'Configuration class' do
16
+
17
+ context 'dependencies' do
18
+
19
+ context 'have_command?' do
20
+
21
+ should 'raise an exception for an empty string' do
22
+ begin
23
+ Watchful::Action.have_command?('')
24
+ flunk
25
+ rescue Exception
26
+ assert true
27
+ end
28
+ end
29
+
30
+ should 'raise an exception for a non-string argument' do
31
+ begin
32
+ Watchful::Action.have_command?(nil)
33
+ flunk
34
+ rescue Exception
35
+ assert true
36
+ end
37
+ end
38
+
39
+ should 'return false for missing commands' do
40
+ assert_equal Watchful::Action.have_command?('Y0CbR4CEP2XY'), false
41
+ end
42
+
43
+ should 'return true for available commands' do
44
+ assert_equal Watchful::Action.have_command?('cat'), true
45
+ end
46
+
47
+ end
48
+
49
+ should 'confirm valid dependencies' do
50
+ a = Watchful::Action.new(
51
+ :name => 'Test Action',
52
+ :dependencies => ['cat', 'echo'], :command => 'cat %s %s',
53
+ :in => '.txt', :out => '.copy'
54
+ )
55
+ assert_equal a.has_dependencies?, true
56
+ end
57
+
58
+ should 'detect a missing binary dependency' do
59
+ a = Watchful::Action.new(
60
+ :name => 'Test Action',
61
+ :dependencies => ['Y0CbR4CEP2XY'], :command => 'Y0CbR4CEP2XY %s %s',
62
+ :in => '.txt', :out => '.copy'
63
+ )
64
+ assert_equal a.has_dependencies?, false
65
+ end
66
+
67
+ should 'confirm availability of no dependencies' do
68
+ a = Watchful::Action.new(
69
+ :name => 'Test Action',
70
+ :dependencies => nil, :command => '',
71
+ :in => '.txt', :out => '.copy'
72
+ )
73
+ assert_equal a.has_dependencies?, true
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,20 @@
1
+ require 'test_helper'
2
+ require 'fileutils'
3
+
4
+ class WatchfulTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ ['/tmp/first', '/tmp/second'].each do |f|
8
+ FileUtils.touch(f)
9
+ end
10
+ end
11
+
12
+ class FirstTestConfiguration < Watchful::Configuration
13
+ action :name => 'Placeholder Action'
14
+ end
15
+
16
+ should 'correctly compile list of actions' do
17
+ assert_equal FirstTestConfiguration.actions.length, 1
18
+ end
19
+
20
+ end
@@ -0,0 +1,63 @@
1
+ require 'test_helper'
2
+ require 'fileutils'
3
+
4
+ class WatchfulTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ ['/tmp/first', '/tmp/second'].each do |f|
8
+ FileUtils.touch(f)
9
+ end
10
+ end
11
+
12
+ context 'younger_than' do
13
+
14
+ should 'correctly compare mtimes' do
15
+
16
+ sleep 1
17
+ FileUtils.touch('/tmp/first')
18
+ assert Watchful.younger_than('/tmp/first', '/tmp/second')
19
+
20
+ sleep 1
21
+ FileUtils.touch('/tmp/second')
22
+ assert Watchful.younger_than('/tmp/second', '/tmp/first')
23
+
24
+ end
25
+
26
+ should 'raise exception when comparing nonexistant files' do
27
+ begin
28
+ Watchful.younger_than('/tmp/does/not/exist', @files[1])
29
+ flunk
30
+ rescue Exception
31
+ assert true
32
+ end
33
+ begin
34
+ Watchful.younger_than(@files[0], '/tmp/does/not/exist')
35
+ flunk
36
+ rescue Exception
37
+ assert true
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ context 'compound_extension_of' do
44
+
45
+ should "detect simple extensions" do
46
+ assert_equal Watchful.compound_extension_of('a_long_file_name.css'), '.css'
47
+ end
48
+
49
+ should "detect compound extensions" do
50
+ assert_equal Watchful.compound_extension_of('test.min.css'), '.min.css'
51
+ end
52
+
53
+ should "return nil for files without extensions" do
54
+ assert_nil Watchful.compound_extension_of('test_file')
55
+ end
56
+
57
+ should 'return nil for files without basenames' do
58
+ assert_nil Watchful.compound_extension_of('.extension')
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ # todo: move to raw Test::Unit
6
+
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+
10
+ require 'watchful'
11
+
12
+ class Test::Unit::Watchful
13
+ end
data/watchful.gemspec ADDED
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{watchful}
8
+ s.version = "0.0.0.pre1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["KEM"]
12
+ s.date = %q{2010-04-22}
13
+ s.default_executable = %q{watchful}
14
+ s.description = %q{Applies intermediary tools to modified files}
15
+ s.email = %q{kyleevanmitchell@gmail.com}
16
+ s.executables = ["watchful"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.md"
20
+ ]
21
+ s.files = [
22
+ ".gitignore",
23
+ "LICENSE",
24
+ "README.md",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "bin/watchful",
28
+ "doc/DSL-Specification.rb",
29
+ "lib/watchful.rb",
30
+ "lib/watchful/action.rb",
31
+ "lib/watchful/configuration.rb",
32
+ "lib/watchful/defaultconfiguration.rb",
33
+ "lib/watchful/paths.rb",
34
+ "lib/watchful/watch.rb",
35
+ "test/action_test.rb",
36
+ "test/configuration_test.rb",
37
+ "test/customconfiguration_test.rb",
38
+ "test/paths_test.rb",
39
+ "test/test_helper.rb",
40
+ "watchful.gemspec"
41
+ ]
42
+ s.homepage = %q{http://github.com/kemitchell/watchful}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubyforge_project = %q{watchful}
46
+ s.rubygems_version = %q{1.3.6}
47
+ s.summary = %q{Watchful, a tool for busy web devs}
48
+ s.test_files = [
49
+ "test/action_test.rb",
50
+ "test/configuration_test.rb",
51
+ "test/customconfiguration_test.rb",
52
+ "test/paths_test.rb",
53
+ "test/test_helper.rb"
54
+ ]
55
+
56
+ if s.respond_to? :specification_version then
57
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
58
+ s.specification_version = 3
59
+
60
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
61
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
62
+ s.add_runtime_dependency(%q<commandline>, [">= 0"])
63
+ s.add_runtime_dependency(%q<extensions>, [">= 0"])
64
+ s.add_runtime_dependency(%q<open4>, [">= 0"])
65
+ else
66
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
67
+ s.add_dependency(%q<commandline>, [">= 0"])
68
+ s.add_dependency(%q<extensions>, [">= 0"])
69
+ s.add_dependency(%q<open4>, [">= 0"])
70
+ end
71
+ else
72
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
73
+ s.add_dependency(%q<commandline>, [">= 0"])
74
+ s.add_dependency(%q<extensions>, [">= 0"])
75
+ s.add_dependency(%q<open4>, [">= 0"])
76
+ end
77
+ end
78
+
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: watchful
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: true
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 0
9
+ - pre1
10
+ version: 0.0.0.pre1
11
+ platform: ruby
12
+ authors:
13
+ - KEM
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-04-22 00:00:00 -05:00
19
+ default_executable: watchful
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: thoughtbot-shoulda
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: commandline
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ version: "0"
43
+ type: :runtime
44
+ version_requirements: *id002
45
+ - !ruby/object:Gem::Dependency
46
+ name: extensions
47
+ prerelease: false
48
+ requirement: &id003 !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ type: :runtime
56
+ version_requirements: *id003
57
+ - !ruby/object:Gem::Dependency
58
+ name: open4
59
+ prerelease: false
60
+ requirement: &id004 !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ type: :runtime
68
+ version_requirements: *id004
69
+ description: Applies intermediary tools to modified files
70
+ email: kyleevanmitchell@gmail.com
71
+ executables:
72
+ - watchful
73
+ extensions: []
74
+
75
+ extra_rdoc_files:
76
+ - LICENSE
77
+ - README.md
78
+ files:
79
+ - .gitignore
80
+ - LICENSE
81
+ - README.md
82
+ - Rakefile
83
+ - VERSION
84
+ - bin/watchful
85
+ - doc/DSL-Specification.rb
86
+ - lib/watchful.rb
87
+ - lib/watchful/action.rb
88
+ - lib/watchful/configuration.rb
89
+ - lib/watchful/defaultconfiguration.rb
90
+ - lib/watchful/paths.rb
91
+ - lib/watchful/watch.rb
92
+ - test/action_test.rb
93
+ - test/configuration_test.rb
94
+ - test/customconfiguration_test.rb
95
+ - test/paths_test.rb
96
+ - test/test_helper.rb
97
+ - watchful.gemspec
98
+ has_rdoc: true
99
+ homepage: http://github.com/kemitchell/watchful
100
+ licenses: []
101
+
102
+ post_install_message:
103
+ rdoc_options:
104
+ - --charset=UTF-8
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">"
117
+ - !ruby/object:Gem::Version
118
+ segments:
119
+ - 1
120
+ - 3
121
+ - 1
122
+ version: 1.3.1
123
+ requirements: []
124
+
125
+ rubyforge_project: watchful
126
+ rubygems_version: 1.3.6
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: Watchful, a tool for busy web devs
130
+ test_files:
131
+ - test/action_test.rb
132
+ - test/configuration_test.rb
133
+ - test/customconfiguration_test.rb
134
+ - test/paths_test.rb
135
+ - test/test_helper.rb