Module: Watchful

Defined in:
lib/watchful/defaultconfiguration.rb,
lib/watchful.rb,
lib/watchful/paths.rb,
lib/watchful/watch.rb,
lib/watchful/action.rb,
lib/watchful/configuration.rb

Overview

todo: Don’t require DefaultConfiguration by default

Defined Under Namespace

Classes: Action, Configuration, DefaultConfiguration

Constant Summary collapse

CONFIGURATION_FILE_NAME =
'watchful'

Class Method Summary collapse

Class Method Details

.compound_extension_of(str) ⇒ Object



13
14
15
16
17
18
19
# File 'lib/watchful/paths.rb', line 13

def self.compound_extension_of(str)
	return nil if str.empty?
	basename = File.basename(str)
	periodpos = basename.index('.')
	return nil if not periodpos or periodpos < 1
	return basename[basename.index('.')..-1]
end

.do_action(source_file, action) ⇒ Object

apply an action to a change file and post notifications



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/watchful/watch.rb', line 113

def self.do_action(source_file, action)

	puts "! #{action.name}: #{source_file}"

	command = action.command_string(source_file)
	
	puts "!\t#{command}"
	
	proc_pid, proc_stdin, proc_stdout, proc_stderr = Open4::popen4(command)
	proc_ignored, proc_status = Process::waitpid2(proc_pid)

	error = proc_stderr.gets

	if proc_status && !error
		if HAVE_GROWL
			growl = Growl.new
	        growl.title = action.name
	        growl.message = "#{source_file}"
	        growl.run
		end
		@files_with_errors.delete(source_file)
	else
		$stderr.puts("ERROR -- #{action.name} -- #{source_file}")
		$stderr.puts(error)
		if HAVE_GROWL
			growl = Growl.new
	        growl.title = "ERROR: #{action.name}"
	        growl.message = error
	        growl.run
		end
		@files_with_errors[source_file] = File.stat(source_file).mtime
	end
	
end

.each_source_file_in_dir(path = Dir.pwd) ⇒ Object



8
9
10
11
12
13
# File 'lib/watchful/watch.rb', line 8

def self.each_source_file_in_dir(path = Dir.pwd)
	Find.find(path) do |filepath|
		next if FileTest.directory?(filepath)
		yield(filepath)
	end
end

.load_configuration(path, use_default = false) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/watchful/watch.rb', line 17

def self.load_configuration(path, use_default = false)

	# todo: more efficient loading of configurations
	# todo: check that relative paths work on Windows
	
	local = File.expand_path("#{path}/#{CONFIGURATION_FILE_NAME}")
	user = File.expand_path("~/#{CONFIGURATION_FILE_NAME}")
	
	if File.exists? local
		puts "=> Loading configuration #{local}"
		load local
	elsif File.exists? user
		puts "=> Loading configuration #{user}"
		load user
	end
	
end

.versionObject



26
27
28
# File 'lib/watchful.rb', line 26

def self.version
	File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip
end

.watch_files(path, force_polling = false) ⇒ Object

{:path => ”, :mtime => ### }



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/watchful/watch.rb', line 40

def self.watch_files(path, force_polling = false)
	
	if force_polling
		use_fsevents = false
	elsif RUBY_PLATFORM.include?('darwin') # todo: better check for OS fsevents support
		begin
			require 'fsevents'
			use_fsevents = true
		rescue LoadError
			use_fsevents = false
			puts
			puts "*****************************************************************"
			puts "* Using fsevents significantly improves Watchful’s performance. *"
			puts "*                                                               *"
			puts "*                      Install it with:                         *"
			puts "*                                                               *"
			puts "*                 sudo gem install fsevents                     *"
			puts "*                                                               *"
			puts "*****************************************************************"
			puts
		end
	end
	
	puts "... Watching #{path} ..."
	
	if use_fsevents # OS X filesystem events API
		
		begin
		
			fsevents_stream = FSEvents::Stream.watch(path) do |events|
				events.modified_files.each do |file|
					action = Configuration.active_configuration.action_for_file(file)
					do_action(file, action) unless action.nil?
				end
			end
			fsevents_stream.run
			
		rescue Interrupt
			fsevents_stream.stop
		end
		
	else # stat polling
		
		begin
			loop do
				check_for_interrupts
				each_source_file_in_dir(path) do |file_path|
					
					extension = compound_extension_of(file_path)
					
					action = Configuration.active_configuration.action_for_file(file_path)
			
					next if action.nil?
			
					output_path = action.output_path_for(file_path)
			
					if (!File.exists?(output_path)) or younger_than(file_path, output_path)
						error_mtime = @files_with_errors[file_path]
						next if error_mtime && error_mtime >= File.stat(file_path).mtime
						do_action(file_path, action)
					end
			
				end
			end
		rescue Interrupt
			# pass
		end
		
	end
	
end

.younger_than(a, b) ⇒ Object



6
7
8
9
10
11
# File 'lib/watchful/paths.rb', line 6

def self.younger_than(a, b)
	if (not File.exists? a) or (not File.exists? b)
		raise Exception, "cannot compare modification times of non-existant files"
	end
	return (File.stat(a).mtime > File.stat(b).mtime)
end