shell 0.0.1 → 0.7

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,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