shell 0.0.1 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: false
2
+ #
3
+ # shell/error.rb -
4
+ # $Release Version: 0.7 $
5
+ # $Revision$
6
+ # by Keiju ISHITSUKA(keiju@ruby-lang.org)
7
+ #
8
+ # --
9
+ #
10
+ #
11
+ #
12
+
13
+ require "e2mmap"
14
+
15
+ class Shell
16
+ module Error
17
+ extend Exception2MessageMapper
18
+ def_e2message TypeError, "wrong argument type %s (expected %s)"
19
+
20
+ def_exception :DirStackEmpty, "Directory stack empty."
21
+ def_exception :CantDefine, "Can't define method(%s, %s)."
22
+ def_exception :CantApplyMethod, "This method(%s) does not apply to this type(%s)."
23
+ def_exception :CommandNotFound, "Command not found(%s)."
24
+ end
25
+ end
26
+
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: false
2
+ #
3
+ # shell/filter.rb -
4
+ # $Release Version: 0.7 $
5
+ # $Revision$
6
+ # by Keiju ISHITSUKA(keiju@ruby-lang.org)
7
+ #
8
+ # --
9
+ #
10
+ #
11
+ #
12
+
13
+ class Shell #:nodoc:
14
+ # Any result of command execution is a Filter.
15
+ #
16
+ # This class includes Enumerable, therefore a Filter object can use all
17
+ # Enumerable
18
+ # facilities.
19
+ #
20
+ class Filter
21
+ include Enumerable
22
+
23
+ def initialize(sh)
24
+ @shell = sh # parent shell
25
+ @input = nil # input filter
26
+ end
27
+
28
+ attr_reader :input
29
+
30
+ def input=(filter)
31
+ @input = filter
32
+ end
33
+
34
+ # call-seq:
35
+ # each(record_separator=nil) { block }
36
+ #
37
+ # Iterates a block for each line.
38
+ def each(rs = nil)
39
+ rs = @shell.record_separator unless rs
40
+ if @input
41
+ @input.each(rs){|l| yield l}
42
+ end
43
+ end
44
+
45
+ # call-seq:
46
+ # < source
47
+ #
48
+ # Inputs from +source+, which is either a string of a file name or an IO
49
+ # object.
50
+ def <(src)
51
+ case src
52
+ when String
53
+ cat = Cat.new(@shell, src)
54
+ cat | self
55
+ when IO
56
+ self.input = src
57
+ self
58
+ else
59
+ Shell.Fail Error::CantApplyMethod, "<", src.class
60
+ end
61
+ end
62
+
63
+ # call-seq:
64
+ # > source
65
+ #
66
+ # Outputs from +source+, which is either a string of a file name or an IO
67
+ # object.
68
+ def >(to)
69
+ case to
70
+ when String
71
+ dst = @shell.open(to, "w")
72
+ begin
73
+ each(){|l| dst << l}
74
+ ensure
75
+ dst.close
76
+ end
77
+ when IO
78
+ each(){|l| to << l}
79
+ else
80
+ Shell.Fail Error::CantApplyMethod, ">", to.class
81
+ end
82
+ self
83
+ end
84
+
85
+ # call-seq:
86
+ # >> source
87
+ #
88
+ # Appends the output to +source+, which is either a string of a file name
89
+ # or an IO object.
90
+ def >>(to)
91
+ begin
92
+ Shell.cd(@shell.pwd).append(to, self)
93
+ rescue CantApplyMethod
94
+ Shell.Fail Error::CantApplyMethod, ">>", to.class
95
+ end
96
+ end
97
+
98
+ # call-seq:
99
+ # | filter
100
+ #
101
+ # Processes a pipeline.
102
+ def |(filter)
103
+ filter.input = self
104
+ if active?
105
+ @shell.process_controller.start_job filter
106
+ end
107
+ filter
108
+ end
109
+
110
+ # call-seq:
111
+ # filter1 + filter2
112
+ #
113
+ # Outputs +filter1+, and then +filter2+ using Join.new
114
+ def +(filter)
115
+ Join.new(@shell, self, filter)
116
+ end
117
+
118
+ def to_a
119
+ ary = []
120
+ each(){|l| ary.push l}
121
+ ary
122
+ end
123
+
124
+ def to_s
125
+ str = ""
126
+ each(){|l| str.concat l}
127
+ str
128
+ end
129
+
130
+ def inspect
131
+ if @shell.debug.kind_of?(Integer) && @shell.debug > 2
132
+ super
133
+ else
134
+ to_s
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,309 @@
1
+ # frozen_string_literal: false
2
+ #
3
+ # shell/process-controller.rb -
4
+ # $Release Version: 0.7 $
5
+ # $Revision$
6
+ # by Keiju ISHITSUKA(keiju@ruby-lang.org)
7
+ #
8
+ # --
9
+ #
10
+ #
11
+ #
12
+ require "forwardable"
13
+ require "sync"
14
+
15
+ class Shell
16
+ class ProcessController
17
+
18
+ @ProcessControllers = {}
19
+ @ProcessControllersMonitor = Thread::Mutex.new
20
+ @ProcessControllersCV = Thread::ConditionVariable.new
21
+
22
+ @BlockOutputMonitor = Thread::Mutex.new
23
+ @BlockOutputCV = Thread::ConditionVariable.new
24
+
25
+ class << self
26
+ extend Forwardable
27
+
28
+ def_delegator("@ProcessControllersMonitor",
29
+ "synchronize", "process_controllers_exclusive")
30
+
31
+ def active_process_controllers
32
+ process_controllers_exclusive do
33
+ @ProcessControllers.dup
34
+ end
35
+ end
36
+
37
+ def activate(pc)
38
+ process_controllers_exclusive do
39
+ @ProcessControllers[pc] ||= 0
40
+ @ProcessControllers[pc] += 1
41
+ end
42
+ end
43
+
44
+ def inactivate(pc)
45
+ process_controllers_exclusive do
46
+ if @ProcessControllers[pc]
47
+ if (@ProcessControllers[pc] -= 1) == 0
48
+ @ProcessControllers.delete(pc)
49
+ @ProcessControllersCV.signal
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def each_active_object
56
+ process_controllers_exclusive do
57
+ for ref in @ProcessControllers.keys
58
+ yield ref
59
+ end
60
+ end
61
+ end
62
+
63
+ def block_output_synchronize(&b)
64
+ @BlockOutputMonitor.synchronize(&b)
65
+ end
66
+
67
+ def wait_to_finish_all_process_controllers
68
+ process_controllers_exclusive do
69
+ while !@ProcessControllers.empty?
70
+ Shell::notify("Process finishing, but active shell exists",
71
+ "You can use Shell#transact or Shell#check_point for more safe execution.")
72
+ if Shell.debug?
73
+ for pc in @ProcessControllers.keys
74
+ Shell::notify(" Not finished jobs in "+pc.shell.to_s)
75
+ for com in pc.jobs
76
+ com.notify(" Jobs: %id")
77
+ end
78
+ end
79
+ end
80
+ @ProcessControllersCV.wait(@ProcessControllersMonitor)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # for shell-command complete finish at this process exit.
87
+ USING_AT_EXIT_WHEN_PROCESS_EXIT = true
88
+ at_exit do
89
+ wait_to_finish_all_process_controllers unless $@
90
+ end
91
+
92
+ def initialize(shell)
93
+ @shell = shell
94
+ @waiting_jobs = []
95
+ @active_jobs = []
96
+ @jobs_sync = Sync.new
97
+
98
+ @job_monitor = Thread::Mutex.new
99
+ @job_condition = Thread::ConditionVariable.new
100
+ end
101
+
102
+ attr_reader :shell
103
+
104
+ def jobs
105
+ jobs = []
106
+ @jobs_sync.synchronize(:SH) do
107
+ jobs.concat @waiting_jobs
108
+ jobs.concat @active_jobs
109
+ end
110
+ jobs
111
+ end
112
+
113
+ def active_jobs
114
+ @active_jobs
115
+ end
116
+
117
+ def waiting_jobs
118
+ @waiting_jobs
119
+ end
120
+
121
+ def jobs_exist?
122
+ @jobs_sync.synchronize(:SH) do
123
+ @active_jobs.empty? or @waiting_jobs.empty?
124
+ end
125
+ end
126
+
127
+ def active_jobs_exist?
128
+ @jobs_sync.synchronize(:SH) do
129
+ @active_jobs.empty?
130
+ end
131
+ end
132
+
133
+ def waiting_jobs_exist?
134
+ @jobs_sync.synchronize(:SH) do
135
+ @waiting_jobs.empty?
136
+ end
137
+ end
138
+
139
+ # schedule a command
140
+ def add_schedule(command)
141
+ @jobs_sync.synchronize(:EX) do
142
+ ProcessController.activate(self)
143
+ if @active_jobs.empty?
144
+ start_job command
145
+ else
146
+ @waiting_jobs.push(command)
147
+ end
148
+ end
149
+ end
150
+
151
+ # start a job
152
+ def start_job(command = nil)
153
+ @jobs_sync.synchronize(:EX) do
154
+ if command
155
+ return if command.active?
156
+ @waiting_jobs.delete command
157
+ else
158
+ command = @waiting_jobs.shift
159
+
160
+ return unless command
161
+ end
162
+ @active_jobs.push command
163
+ command.start
164
+
165
+ # start all jobs that input from the job
166
+ for job in @waiting_jobs.dup
167
+ start_job(job) if job.input == command
168
+ end
169
+ end
170
+ end
171
+
172
+ def waiting_job?(job)
173
+ @jobs_sync.synchronize(:SH) do
174
+ @waiting_jobs.include?(job)
175
+ end
176
+ end
177
+
178
+ def active_job?(job)
179
+ @jobs_sync.synchronize(:SH) do
180
+ @active_jobs.include?(job)
181
+ end
182
+ end
183
+
184
+ # terminate a job
185
+ def terminate_job(command)
186
+ @jobs_sync.synchronize(:EX) do
187
+ @active_jobs.delete command
188
+ ProcessController.inactivate(self)
189
+ if @active_jobs.empty?
190
+ command.notify("start_job in terminate_job(%id)", Shell::debug?)
191
+ start_job
192
+ end
193
+ end
194
+ end
195
+
196
+ # kill a job
197
+ def kill_job(sig, command)
198
+ @jobs_sync.synchronize(:EX) do
199
+ if @waiting_jobs.delete command
200
+ ProcessController.inactivate(self)
201
+ return
202
+ elsif @active_jobs.include?(command)
203
+ begin
204
+ r = command.kill(sig)
205
+ ProcessController.inactivate(self)
206
+ rescue
207
+ print "Shell: Warn: $!\n" if @shell.verbose?
208
+ return nil
209
+ end
210
+ @active_jobs.delete command
211
+ r
212
+ end
213
+ end
214
+ end
215
+
216
+ # wait for all jobs to terminate
217
+ def wait_all_jobs_execution
218
+ @job_monitor.synchronize do
219
+ begin
220
+ while !jobs.empty?
221
+ @job_condition.wait(@job_monitor)
222
+ for job in jobs
223
+ job.notify("waiting job(%id)", Shell::debug?)
224
+ end
225
+ end
226
+ ensure
227
+ redo unless jobs.empty?
228
+ end
229
+ end
230
+ end
231
+
232
+ # simple fork
233
+ def sfork(command)
234
+ pipe_me_in, pipe_peer_out = IO.pipe
235
+ pipe_peer_in, pipe_me_out = IO.pipe
236
+
237
+
238
+ pid = nil
239
+ pid_mutex = Thread::Mutex.new
240
+ pid_cv = Thread::ConditionVariable.new
241
+
242
+ Thread.start do
243
+ ProcessController.block_output_synchronize do
244
+ STDOUT.flush
245
+ ProcessController.each_active_object do |pc|
246
+ for jobs in pc.active_jobs
247
+ jobs.flush
248
+ end
249
+ end
250
+
251
+ pid = fork {
252
+ Thread.list.each do |th|
253
+ th.kill unless Thread.current == th
254
+ end
255
+
256
+ STDIN.reopen(pipe_peer_in)
257
+ STDOUT.reopen(pipe_peer_out)
258
+
259
+ ObjectSpace.each_object(IO) do |io|
260
+ if ![STDIN, STDOUT, STDERR].include?(io)
261
+ io.close
262
+ end
263
+ end
264
+
265
+ yield
266
+ }
267
+ end
268
+ pid_cv.signal
269
+
270
+ pipe_peer_in.close
271
+ pipe_peer_out.close
272
+ command.notify "job(%name:##{pid}) start", @shell.debug?
273
+
274
+ begin
275
+ _pid = nil
276
+ command.notify("job(%id) start to waiting finish.", @shell.debug?)
277
+ _pid = Process.waitpid(pid, nil)
278
+ rescue Errno::ECHILD
279
+ command.notify "warn: job(%id) was done already waitpid."
280
+ _pid = true
281
+ ensure
282
+ command.notify("Job(%id): Wait to finish when Process finished.", @shell.debug?)
283
+ # when the process ends, wait until the command terminates
284
+ if USING_AT_EXIT_WHEN_PROCESS_EXIT or _pid
285
+ else
286
+ command.notify("notice: Process finishing...",
287
+ "wait for Job[%id] to finish.",
288
+ "You can use Shell#transact or Shell#check_point for more safe execution.")
289
+ redo
290
+ end
291
+
292
+ @job_monitor.synchronize do
293
+ terminate_job(command)
294
+ @job_condition.signal
295
+ command.notify "job(%id) finish.", @shell.debug?
296
+ end
297
+ end
298
+ end
299
+
300
+ pid_mutex.synchronize do
301
+ while !pid
302
+ pid_cv.wait(pid_mutex)
303
+ end
304
+ end
305
+
306
+ return pid, pipe_me_in, pipe_me_out
307
+ end
308
+ end
309
+ end