console-mux 2.0.2

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.
@@ -0,0 +1,288 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'eventmachine'
22
+ require 'log4r'
23
+ require 'ripl/readline/em'
24
+
25
+ require 'console/mux/buffer_outputter'
26
+ require 'console/mux/command'
27
+ require 'console/mux/command_set'
28
+ require 'console/mux/console_outputter'
29
+ require 'console/mux/color_formatter'
30
+ require 'console/mux/pty_handler'
31
+ require 'console/mux/shell'
32
+
33
+
34
+ module Console
35
+ module Mux
36
+ class Console
37
+ include Log4r
38
+
39
+ MARK_PERIOD = 60 # seconds
40
+
41
+ BUFFER_LINES = 10000
42
+
43
+ attr_reader :commands, :default_options, :formatter, :buffer, :logger
44
+
45
+ def initialize(options={})
46
+ @commands = CommandSet.new
47
+ @default_options = Hash.new
48
+ @base_dir = '.'
49
+
50
+ @formatter = ColorFormatter.new
51
+
52
+ @logger = Logger.new('process')
53
+ logger.add ConsoleOutputter.new('console',
54
+ self,
55
+ :formatter => formatter)
56
+
57
+ @buffer = BufferOutputter.new('buffer',
58
+ BUFFER_LINES,
59
+ :formatter => formatter)
60
+ logger.add @buffer
61
+
62
+ EventMachine.run do
63
+ logger.info { 'Initializing' }
64
+
65
+ EventMachine.add_periodic_timer(MARK_PERIOD) do
66
+ now = Time.now.strftime('%Y-%m-%d %H:%M')
67
+ logger.info { "#{now} have #{commands.count}" }
68
+ end
69
+
70
+ EventMachine.next_tick do
71
+ load(options[:init_file]) if options[:init_file]
72
+ end
73
+
74
+ @shell = Shell.new(self)
75
+ Ripl.start :binding => @shell.instance_eval { binding }
76
+ EventMachine.watch_stdin(Ripl::Readline::EmInput,
77
+ :on_exit => proc { shutdown })
78
+ end
79
+ end
80
+
81
+ def load(file)
82
+ old_base = @base_dir
83
+ @base_dir = File.expand_path(File.dirname(file))
84
+ begin
85
+ @shell.instance_eval(File.read(file), file)
86
+ @last_file = file
87
+ ensure
88
+ @base_dir = old_base
89
+ end
90
+ end
91
+
92
+ def run_commands()
93
+ next_command = commands.command_q.shift
94
+ if (!next_command.nil?)
95
+ commands.startCommand( next_command )
96
+
97
+ # if next_command.opts[:blocking]
98
+ # EventMachine.next_tick do
99
+ # check_command(next_command)
100
+ # end
101
+ # else
102
+ # end
103
+
104
+ if (!(commands.command_q.empty?))
105
+ EventMachine.next_tick do
106
+ run_commands()
107
+ end
108
+ end
109
+ end
110
+
111
+ # EM::Iterator requires the beta version of eventmachine
112
+ # EM::Iterator.new(commands.command_q).each do |command,iter|
113
+ # commands.start( commands.command_q.shift )
114
+ # iter.next
115
+ # end
116
+ end
117
+
118
+
119
+ # Using set_ rather than '=' style accessor so config file needn't
120
+ # use self.default_options =.
121
+ def set_default_options(opts)
122
+ @default_options = opts
123
+ end
124
+
125
+ # Run a single command, a sequence of commands, or a sequence of
126
+ # single and parallel commands with default shell argument
127
+ # expansion.
128
+ #
129
+ # Each options hash is merged with the default options and then
130
+ # passed to +Command.new+.
131
+ #
132
+ # If multiple command options are given, run them sequentially
133
+ # one-at-a-time. Only the final command will remain in the
134
+ # active command list; the others will be removed as they
135
+ # complete and exit.
136
+ #
137
+ # If an array of command options is given, they will be run in
138
+ # parallel, even if part of sequential sequence. Thus you can
139
+ # specify +run(c1, c2, [c3,c4,c5], c6)+ which will run commands
140
+ # 1 and 2 sequentially, then 3, 4 and 5 in parallel, then
141
+ # finally command 6 only after all previous commands complete.
142
+ #
143
+ # In the following example, the first +ls+ is run, and when it
144
+ # exits the next two +ls+ instances are spawned in parallel.
145
+ # When those two exit, the final +ls+ is run.
146
+ #
147
+ # run({:command => 'ls'},
148
+ # [{:command => 'ls'}, {:command => 'ls'}],
149
+ # {:command => 'ls'})
150
+ #
151
+ # @param [Hash] *opts one or more option hashes passed to +Command.new+.
152
+ def run(*optses)
153
+ names = optses.map do |opts|
154
+ if opts.kind_of? Array
155
+ opts.map { |o| make_command_and_add(o) }
156
+ else
157
+ make_command_and_add(opts)
158
+ end
159
+ end
160
+
161
+ seq_names(names.compact)
162
+ end
163
+
164
+ def make_command(opts)
165
+ opts = @default_options.merge(opts)
166
+ return if opts[:noop]
167
+
168
+ opts[:base_dir] = @base_dir
169
+
170
+ begin
171
+ Command.new(opts)
172
+ rescue => e
173
+ # optimistically assume console-mux errors are uninteresting
174
+ first_relevant = e.backtrace.find { |line| !(line =~ %r{lib/console/mux}) }
175
+ # logger.error e.backtrace.join("\n\t")
176
+ logger.error { "#{opts[:command]}: #{e.message} at #{first_relevant}" }
177
+ nil
178
+ end
179
+ end
180
+ private :make_command
181
+
182
+ # @return [String, nil] the name of the command or nil if
183
+ # command was noop or if there was an error
184
+ def make_command_and_add(opts)
185
+ if c = make_command(opts)
186
+ commands.add(c)
187
+ end
188
+ end
189
+ private :make_command_and_add
190
+
191
+ def seq_names(names)
192
+ return unless names.size > 0
193
+
194
+ name_or_ary = names.shift
195
+ EventMachine.next_tick do
196
+ # name may be a single name or array of names, so we
197
+ # normalize everything to an array
198
+ to_start = [name_or_ary].flatten
199
+ procs = to_start.map { |n| start(n) }
200
+
201
+ CommandSet.join(procs) do
202
+ if names.size > 0
203
+ to_start.each { |n| commands.remove(n) }
204
+ seq_names(names)
205
+ end
206
+ end
207
+ end
208
+ end
209
+ private :seq_names
210
+
211
+ def status
212
+ puts commands.status
213
+ end
214
+
215
+ # Stop all commands in the command set, then destroy the command
216
+ # set, starting with an empty one.
217
+ def reset(&block)
218
+ old_commands = commands
219
+ @commands = CommandSet.new
220
+ old_commands.join(&block)
221
+ end
222
+
223
+ def stop(name)
224
+ logger.info { "Stopping #{name}" }
225
+ commands.stop(name)
226
+ end
227
+
228
+ def start(name)
229
+ logger.info { "Starting #{name}" }
230
+ commands.start(name)
231
+ end
232
+
233
+ def restart(name)
234
+ logger.info { "Restarting #{name}" }
235
+ commands.restart(name)
236
+ end
237
+
238
+ def shutdown
239
+ if commands.stopped?
240
+ EventMachine.stop_event_loop
241
+ else
242
+ timer = EventMachine.add_timer(30) do
243
+ logger.error { "could not halt all processes; giving up :(" }
244
+ EventMachine.stop_event_loop
245
+ end
246
+
247
+ commands.on(:stopped) do
248
+ EventMachine.cancel_timer(timer)
249
+ EventMachine.stop_event_loop
250
+ end
251
+
252
+ commands.stop_all
253
+ end
254
+ end
255
+
256
+ def lastlog(arg=//)
257
+ regex = case arg
258
+ when String
259
+ if arg.eql?(arg.downcase)
260
+ /#{arg}/i
261
+ else
262
+ /#{arg}/
263
+ end
264
+ when Regexp
265
+ arg
266
+ else
267
+ raise ArgumentError, 'need string or regexp'
268
+ end
269
+
270
+ @buffer.each do |msg|
271
+ puts msg if msg =~ regex
272
+ end
273
+ end
274
+
275
+ def puts(message = '')
276
+ # See http://stackoverflow.com/questions/1512028/gnu-readline-how-do-clear-the-input-line
277
+ print "\b \b" * Readline.line_buffer.size
278
+ print "\r"
279
+ begin
280
+ $stdout.puts message
281
+ $stdout.flush
282
+ ensure
283
+ ::Readline.forced_update_display
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,60 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require "log4r/outputter/outputter"
22
+ require "log4r/staticlogger"
23
+
24
+ module Console
25
+
26
+ module Mux
27
+
28
+ ##
29
+ # IO Outputter invokes print then flush on the wrapped IO
30
+ # object. If the IO stream dies, IOOutputter sets itself to OFF
31
+ # and the system continues on its merry way.
32
+ #
33
+ # To find out why an IO stream died, create a logger named 'log4r'
34
+ # and look at the output.
35
+
36
+ class ConsoleOutputter < Log4r::Outputter
37
+
38
+ def initialize(_name, console, hash={})
39
+ @console = console
40
+ super(_name, hash)
41
+ end
42
+
43
+ private
44
+
45
+ # perform the write (copied from Log4r::IOOutputter but uses console.puts)
46
+ def write(data)
47
+ begin
48
+ @console.puts data
49
+ rescue => e # recover from this instead of crash
50
+ Log4r::Logger.log_internal {"IOError in Outputter '#{@name}'!"}
51
+ Log4r::Logger.log_internal {e}
52
+ raise e
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,45 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ class << ENV
22
+ # Merge the values from `hash` into the global ENV; yield the block;
23
+ # restore the original values of ENV.
24
+ #
25
+ # @param [Hash] hash
26
+ def with_merged(hash)
27
+ ENV.restore do
28
+ ENV.update(hash)
29
+ yield
30
+ end
31
+ end
32
+
33
+ # Not threadsafe. Save the current ENV, yield to the block, then restore the original ENV.
34
+ def restore
35
+ # save original values
36
+ orig = ENV.to_hash
37
+
38
+ begin
39
+ yield
40
+ ensure
41
+ ENV.clear
42
+ ENV.update(orig)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,41 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module Console
22
+ module Mux
23
+ module Events
24
+ def event_handlers
25
+ @event_handlers ||= Hash.new { |h,k| h[k] = Array.new }
26
+ end
27
+
28
+ def on(event, &handler)
29
+ event_handlers[event] << handler
30
+ end
31
+
32
+ protected
33
+
34
+ def fire(event, *args)
35
+ event_handlers[event].each do |handler|
36
+ handler.call(event, self, *args)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,32 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ class Object
22
+ # For each symbol in `syms`, call the corresponding method with
23
+ # `*args`, then call subsequent methods with the return value of the
24
+ # previous call.
25
+ #
26
+ # @return [Object] the value of the last method called
27
+ def compose(syms, *args)
28
+ syms.reduce(args) do |args, sym|
29
+ method(sym).call(*args)
30
+ end
31
+ end
32
+ end