relaunch 0.0.1 → 0.15.0
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.
- checksums.yaml +4 -4
- data/Changelog.md +15 -0
- data/Fork.md +40 -0
- data/LICENSE +34 -0
- data/README.md +488 -0
- data/Rakefile +75 -0
- data/bin/relaunch +3 -0
- data/bin/relaunch.bat +3 -0
- data/bin/rerun +22 -0
- data/bin/rerun.bat +3 -0
- data/icons/rails_grn_sml.png +0 -0
- data/icons/rails_red_sml.png +0 -0
- data/lib/relaunch.rb +1 -1
- data/lib/rerun/glob.rb +85 -0
- data/lib/rerun/notification.rb +83 -0
- data/lib/rerun/options.rb +148 -0
- data/lib/rerun/runner.rb +376 -0
- data/lib/rerun/system.rb +22 -0
- data/lib/rerun/watcher.rb +134 -0
- data/lib/rerun.rb +17 -0
- data/relaunch.gemspec +44 -0
- metadata +58 -12
data/bin/rerun
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}/lib"
|
|
5
|
+
$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
|
|
6
|
+
|
|
7
|
+
require 'rerun'
|
|
8
|
+
require 'optparse'
|
|
9
|
+
|
|
10
|
+
options = Rerun::Options.parse config_file: ".rerun"
|
|
11
|
+
|
|
12
|
+
if options and options[:verbose]
|
|
13
|
+
puts "\nrerun options:\n\t#{options}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
exit if options.nil? or options[:cmd].nil? or options[:cmd].empty?
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
Rerun::Runner.keep_running(options[:cmd], options)
|
|
20
|
+
rescue Rerun::ExitException
|
|
21
|
+
exit 0
|
|
22
|
+
end
|
data/bin/rerun.bat
ADDED
|
Binary file
|
|
Binary file
|
data/lib/relaunch.rb
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
require 'rerun'
|
data/lib/rerun/glob.rb
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# based on http://cpan.uwinnipeg.ca/htdocs/Text-Glob/Text/Glob.pm.html#glob_to_regex_string-
|
|
2
|
+
|
|
3
|
+
# todo: release as separate gem
|
|
4
|
+
#
|
|
5
|
+
module Rerun
|
|
6
|
+
class Glob
|
|
7
|
+
NO_LEADING_DOT = '(?=[^\.])' # todo
|
|
8
|
+
START_OF_FILENAME = '(\A|\/)' # beginning of string or a slash
|
|
9
|
+
END_OF_STRING = '\z'
|
|
10
|
+
|
|
11
|
+
def initialize glob_string
|
|
12
|
+
@glob_string = glob_string
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_regexp_string
|
|
16
|
+
chars = @glob_string.split('')
|
|
17
|
+
|
|
18
|
+
chars = smoosh(chars)
|
|
19
|
+
|
|
20
|
+
curlies = 0
|
|
21
|
+
escaping = false
|
|
22
|
+
string = chars.map do |char|
|
|
23
|
+
if escaping
|
|
24
|
+
escaping = false
|
|
25
|
+
char
|
|
26
|
+
else
|
|
27
|
+
case char
|
|
28
|
+
when '**'
|
|
29
|
+
"([^/]+/)*"
|
|
30
|
+
when '*'
|
|
31
|
+
".*"
|
|
32
|
+
when "?"
|
|
33
|
+
"."
|
|
34
|
+
when "."
|
|
35
|
+
"\\."
|
|
36
|
+
|
|
37
|
+
when "{"
|
|
38
|
+
curlies += 1
|
|
39
|
+
"("
|
|
40
|
+
when "}"
|
|
41
|
+
if curlies > 0
|
|
42
|
+
curlies -= 1
|
|
43
|
+
")"
|
|
44
|
+
else
|
|
45
|
+
char
|
|
46
|
+
end
|
|
47
|
+
when ","
|
|
48
|
+
if curlies > 0
|
|
49
|
+
"|"
|
|
50
|
+
else
|
|
51
|
+
char
|
|
52
|
+
end
|
|
53
|
+
when "\\"
|
|
54
|
+
escaping = true
|
|
55
|
+
"\\"
|
|
56
|
+
|
|
57
|
+
else
|
|
58
|
+
char
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end.join
|
|
63
|
+
START_OF_FILENAME + string + END_OF_STRING
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def to_regexp
|
|
67
|
+
Regexp.new(to_regexp_string)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def smoosh chars
|
|
71
|
+
out = []
|
|
72
|
+
until chars.empty?
|
|
73
|
+
char = chars.shift
|
|
74
|
+
if char == "*" and chars.first == "*"
|
|
75
|
+
chars.shift
|
|
76
|
+
chars.shift if chars.first == "/"
|
|
77
|
+
out.push("**")
|
|
78
|
+
else
|
|
79
|
+
out.push(char)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
out
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# todo: unit tests
|
|
2
|
+
|
|
3
|
+
module Rerun
|
|
4
|
+
class Notification
|
|
5
|
+
include System
|
|
6
|
+
|
|
7
|
+
attr_reader :title, :body, :options
|
|
8
|
+
|
|
9
|
+
def initialize(title, body, options = Options::DEFAULTS.dup)
|
|
10
|
+
@title = title
|
|
11
|
+
@body = body
|
|
12
|
+
@options = options
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def command
|
|
16
|
+
# todo: strategy or subclass
|
|
17
|
+
|
|
18
|
+
s = nil
|
|
19
|
+
|
|
20
|
+
if options[:notify] == true or options[:notify] == "growl"
|
|
21
|
+
if (cmd = command_named("growlnotify"))
|
|
22
|
+
# todo: check version of growlnotify and warn if it's too old
|
|
23
|
+
icon_str = ("--image \"#{icon}\"" if icon)
|
|
24
|
+
s = "#{cmd} -n \"#{app_name}\" -m \"#{body}\" \"#{app_name} #{title}\" #{icon_str}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if s.nil? and options[:notify] == true or options[:notify] == "osx"
|
|
29
|
+
if (cmd = command_named("terminal-notifier"))
|
|
30
|
+
icon_str = ("-appIcon \"#{icon}\"" if icon)
|
|
31
|
+
s = "#{cmd} -title \"#{app_name}\" -message \"#{body}\" \"#{app_name} #{title}\" #{icon_str}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if s.nil? and options[:notify] == true or options[:notify] == "notify-send"
|
|
36
|
+
if (cmd = command_named('notify-send'))
|
|
37
|
+
icon_str = "--icon #{icon}" if icon
|
|
38
|
+
s = "#{cmd} -t 500 --hint=int:transient:1 #{icon_str} \"#{app_name}: #{title}\" \"#{body}\""
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def command_named(name)
|
|
46
|
+
which_command = windows? ? 'where.exe %{cmd}' : 'which %{cmd} 2> /dev/null'
|
|
47
|
+
# TODO: remove 'INFO' error message
|
|
48
|
+
path = `#{which_command % {cmd: name}}`.chomp
|
|
49
|
+
path.empty? ? nil : path
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def send(background = true)
|
|
53
|
+
return unless command
|
|
54
|
+
with_clean_env do
|
|
55
|
+
`#{command}#{" &" if background}`
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def app_name
|
|
60
|
+
options[:name]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def icon
|
|
64
|
+
"#{icon_dir}/rails_red_sml.png" if rails?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def icon_dir
|
|
68
|
+
here = File.expand_path(File.dirname(__FILE__))
|
|
69
|
+
File.expand_path("#{here}/../../icons")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def with_clean_env
|
|
73
|
+
return yield unless defined?(Bundler)
|
|
74
|
+
|
|
75
|
+
if Bundler.respond_to?(:with_unbundled_env)
|
|
76
|
+
Bundler.with_unbundled_env { yield }
|
|
77
|
+
else
|
|
78
|
+
# Deprecated on Bundler 2.1
|
|
79
|
+
Bundler.with_clean_env { yield }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
require 'optparse'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
require 'rerun/watcher'
|
|
4
|
+
require 'rerun/system'
|
|
5
|
+
|
|
6
|
+
libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}"
|
|
7
|
+
|
|
8
|
+
$spec = Gem::Specification.load(File.join(libdir, "..", "relaunch.gemspec"))
|
|
9
|
+
|
|
10
|
+
module Rerun
|
|
11
|
+
class Options
|
|
12
|
+
|
|
13
|
+
extend Rerun::System
|
|
14
|
+
|
|
15
|
+
# If you change the default pattern, please update the README.md file -- the list appears twice therein, which at the time of this comment are lines 17 and 119
|
|
16
|
+
DEFAULT_PATTERN = "**/*.{rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h}"
|
|
17
|
+
DEFAULT_DIRS = ["."]
|
|
18
|
+
|
|
19
|
+
DEFAULTS = {
|
|
20
|
+
:background => false,
|
|
21
|
+
:dir => DEFAULT_DIRS,
|
|
22
|
+
:force_polling => false,
|
|
23
|
+
:ignore => [],
|
|
24
|
+
:ignore_dotfiles => true,
|
|
25
|
+
:name => Pathname.getwd.basename.to_s.capitalize,
|
|
26
|
+
:notify => true,
|
|
27
|
+
:pattern => DEFAULT_PATTERN,
|
|
28
|
+
:quiet => false,
|
|
29
|
+
:signal => (windows? ? "TERM,KILL" : "TERM,INT,KILL"),
|
|
30
|
+
:verbose => false,
|
|
31
|
+
:wait => 2,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def self.parse args: ARGV, config_file: nil
|
|
35
|
+
|
|
36
|
+
default_options = DEFAULTS.dup
|
|
37
|
+
options = {
|
|
38
|
+
ignore: []
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if config_file && File.exist?(config_file)
|
|
42
|
+
require 'shellwords'
|
|
43
|
+
config_args = File.read(config_file).shellsplit
|
|
44
|
+
args = config_args + args
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
option_parser = OptionParser.new("", 24, ' ') do |o|
|
|
48
|
+
o.banner = "Usage: rerun [options] [--] cmd"
|
|
49
|
+
|
|
50
|
+
o.separator ""
|
|
51
|
+
o.separator "Launches an app, and restarts it when the filesystem changes."
|
|
52
|
+
o.separator "See http://github.com/alexch/rerun for more info."
|
|
53
|
+
o.separator "Version: #{$spec.version}"
|
|
54
|
+
o.separator ""
|
|
55
|
+
o.separator "Options:"
|
|
56
|
+
|
|
57
|
+
o.on("-d dir", "--dir dir", "directory to watch, default = \"#{DEFAULT_DIRS}\". Specify multiple paths with ',' or separate '-d dir' option pairs.") do |dir|
|
|
58
|
+
elements = dir.split(",")
|
|
59
|
+
options[:dir] = (options[:dir] || []) + elements
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# todo: rename to "--watch"
|
|
63
|
+
o.on("-p pattern", "--pattern pattern", "file glob to watch, default = \"#{DEFAULTS[:pattern]}\"") do |pattern|
|
|
64
|
+
options[:pattern] = pattern
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
o.on("-i pattern", "--ignore pattern", "file glob(s) to ignore. Can be set many times. To ignore a directory, you must append '/*' e.g. --ignore 'coverage/*' . Globs do not match dotfiles by default.") do |pattern|
|
|
68
|
+
options[:ignore] += [pattern]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
o.on("--[no-]ignore-dotfiles", "by default, file globs do not match files that begin with a dot. Setting --no-ignore-dotfiles allows you to monitor a relevant file like .env, but you may also have to explicitly --ignore more dotfiles and dotdirs.") do |value|
|
|
72
|
+
options[:ignore_dotfiles] = value
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
o.on("-s signal", "--signal signal", "terminate process using this signal. To try several signals in series, use a comma-delimited list. Default: \"#{DEFAULTS[:signal]}\"") do |signal|
|
|
76
|
+
options[:signal] = signal
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
o.on("-w sec", "--wait sec", "after asking the process to terminate, wait this long (in seconds) before either aborting, or trying the next signal in series. Default: #{DEFAULTS[:wait]} sec") do |value|
|
|
80
|
+
default_options[:wait] = value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
o.on("-r", "--restart", "expect process to restart itself, so just send a signal and continue watching. Sends the HUP signal unless overridden using --signal") do |signal|
|
|
84
|
+
options[:restart] = true
|
|
85
|
+
default_options[:signal] = "HUP"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
o.on("-x", "--exit", "expect the program to exit. With this option, rerun checks the return value; without it, rerun checks that the process is running.") do |value|
|
|
89
|
+
options[:exit] = value
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
o.on("-c", "--clear", "clear screen before each run") do |value|
|
|
93
|
+
options[:clear] = value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
o.on("-b", "--background", "disable on-the-fly keypress commands, allowing the process to be backgrounded") do |value|
|
|
97
|
+
options[:background] = value
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
o.on("-n name", "--name name", "name of app used in logs and notifications, default = \"#{DEFAULTS[:name]}\"") do |name|
|
|
101
|
+
options[:name] = name
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
o.on("--[no-]force-polling", "use polling instead of a native filesystem scan (useful for Vagrant)") do |value|
|
|
105
|
+
options[:force_polling] = value
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
o.on("--no-growl", "don't use growl [OBSOLETE]") do
|
|
109
|
+
options[:growl] = false
|
|
110
|
+
$stderr.puts "--no-growl is obsolete; use --no-notify instead"
|
|
111
|
+
return
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
o.on("--[no-]notify [notifier]", "send messages through a desktop notification application. Supports growl (requires growlnotify), osx (requires terminal-notifier gem), and notify-send on GNU/Linux (notify-send must be installed)") do |notifier|
|
|
115
|
+
notifier = true if notifier.nil?
|
|
116
|
+
options[:notify] = notifier
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
o.on("-q", "--[no-]quiet", "don't output any logs") do |value|
|
|
120
|
+
options[:quiet] = value
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
o.on("--[no-]verbose", "log extra stuff like PIDs (unless you also specified `--quiet`") do |value|
|
|
124
|
+
options[:verbose] = value
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
o.on_tail("-h", "--help", "--usage", "show this message and immediately exit") do
|
|
128
|
+
puts o
|
|
129
|
+
return
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
o.on_tail("--version", "show version and immediately exit") do
|
|
133
|
+
puts $spec.version
|
|
134
|
+
return
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
puts option_parser if args.empty?
|
|
140
|
+
option_parser.parse! args
|
|
141
|
+
options = default_options.merge(options)
|
|
142
|
+
options[:cmd] = args.join(" ").strip # todo: better arg word handling
|
|
143
|
+
|
|
144
|
+
options
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|