rubish 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,251 @@
1
+ class Rubish::Executable
2
+
3
+ class ExecutableIO
4
+ class << self
5
+ # transactionally open a bunch of ios
6
+ # if one fails to open, close all others.
7
+ def ios(*specs)
8
+ success = false
9
+ begin
10
+ exe_ios = []
11
+ specs.each do |(spec,mode)|
12
+ exe_ios << prepare_io(spec,mode)
13
+ end
14
+ success =true
15
+ return exe_ios
16
+ ensure
17
+ unless success
18
+ exe_ios.each { |exe_io|
19
+ exe_io.close
20
+ }
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+ # sorry, this is pretty hairy. This method
27
+ # instructs how exec should handle IO. (whether
28
+ # IO is done in a thread. whether it needs to be
29
+ # closed. (so on))
30
+ #
31
+ # an io could be
32
+ # String: interpreted as file name
33
+ # Number: file descriptor
34
+ # IO: Ruby IO object
35
+ # Block: executed in a thread, and a pipe connects the executable and the thread.
36
+ def prepare_io(io,mode)
37
+ # if the io given is a block, we execute it in a thread (passing it a pipe)
38
+ raise "invalid io mode: #{mode}" unless mode == "w" || mode == "r"
39
+ result =
40
+ case io
41
+ when $stdin, $stdout, $stderr
42
+ [io, false, nil]
43
+ when String
44
+ path = File.expand_path(io)
45
+ raise "path is a directory" if File.directory?(path)
46
+ [File.new(path,mode), true, nil]
47
+ when IO
48
+ [io, false,nil]
49
+ when Proc
50
+ proc = io
51
+ r,w = IO.pipe
52
+ # if we want to use a block to
53
+ # (1) input into executable
54
+ # - return "r" end from prepare_io, and
55
+ # the executable use this and standard
56
+ # input.
57
+ # - let the thread block writes to the "w" end
58
+ # (2) read from executable
59
+ # - return "w" from prepare_io
60
+ # - let thread reads from "r"
61
+ return_io, thread_io =
62
+ case mode
63
+ # case 1
64
+ when "r"
65
+ [r,w]
66
+ when "w"
67
+ # case 2
68
+ [w,r]
69
+ end
70
+ thread = Thread.new do
71
+ begin
72
+ proc.call(thread_io)
73
+ ensure
74
+ thread_io.close
75
+ end
76
+ end
77
+ [return_io, true, thread]
78
+ else
79
+ raise "not a valid input: #{io}"
80
+ end
81
+ return self.new(*result)
82
+ end
83
+
84
+ end
85
+
86
+ attr_reader :thread
87
+ attr_reader :io
88
+ attr_reader :auto_close
89
+
90
+ def initialize(io,auto_close,thread)
91
+ @io = io
92
+ @auto_close = auto_close
93
+ @thread = thread
94
+ end
95
+
96
+ def close
97
+ if auto_close
98
+ io.close
99
+ else
100
+ #io.flush if io.stat.writable?
101
+ #io.flush rescue true # try flushing
102
+ end
103
+ if thread
104
+ begin
105
+ thread.join
106
+ ensure
107
+ if thread.alive?
108
+ thread.kill
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+
116
+
117
+ attr_reader :working_directory
118
+
119
+ # only changes working directory for this executable
120
+ def cd(dir)
121
+ @working_directory = dir
122
+ self
123
+ end
124
+
125
+ def awk(a=nil,b=nil,&block)
126
+ if block
127
+ Rubish::Awk.new(self).act(a,b,&block)
128
+ else
129
+ Rubish::Awk.new(self)
130
+ end
131
+ end
132
+
133
+ def sed(a=nil,b=nil,&block)
134
+ if block
135
+ Rubish::Sed.new(self).act(a,b,&block)
136
+ else
137
+ Rubish::Sed.new(self)
138
+ end
139
+ end
140
+
141
+ def exec
142
+ raise "abstract"
143
+ end
144
+
145
+ def exec!
146
+ raise "abstract"
147
+ end
148
+
149
+ # methods for io redirection
150
+ def i(io=nil,&block)
151
+ return @__io_in unless io || block
152
+ @__io_in = io || block
153
+ self
154
+ end
155
+
156
+ def o(io=nil,&block)
157
+ return @__io_out unless io || block
158
+ @__io_out = io || block
159
+ self
160
+ end
161
+
162
+ def err(io=nil,&block)
163
+ return @__io_err unless io || block
164
+ @__io_err = io || block
165
+ self
166
+ end
167
+
168
+ def io(i=nil,o=nil)
169
+ i(i); o(o)
170
+ self
171
+ end
172
+
173
+ def i=(io)
174
+ @__io_in = io
175
+ end
176
+
177
+ def o=(io)
178
+ @__io_out = io
179
+ end
180
+
181
+ def err=(io)
182
+ @__io_err = io
183
+ end
184
+
185
+ def each!
186
+ self.o do |pipe|
187
+ pipe.each_line do |line|
188
+ line.chomp!
189
+ yield(line)
190
+ end
191
+ end
192
+ job = self.exec!
193
+ return job
194
+ end
195
+
196
+ def each(&block)
197
+ job = self.each! &block
198
+ job.wait
199
+ end
200
+
201
+ # acc is the accumulator passed in by reference, and updated destructively.
202
+ def map!(acc,&block)
203
+ acc = Array.new unless acc
204
+ job = self.each! do |l|
205
+ acc << (block.nil? ? l : block.call(l))
206
+ end
207
+ return job
208
+ end
209
+
210
+ def map(&block)
211
+ acc = []
212
+ job = self.map!(acc,&block)
213
+ job.wait
214
+ return acc
215
+ end
216
+
217
+ def head(n=1,&block)
218
+ raise "n should be greater than 0: #{n}" unless n > 0
219
+ self.map do |l|
220
+ if n == 0
221
+ break
222
+ else
223
+ n -= 1
224
+ block ? block.call(l) : l
225
+ end
226
+ end
227
+ end
228
+
229
+ def tail(n=1,&block)
230
+ raise "n should be greater than 0: #{n}" unless n > 0
231
+ acc = []
232
+ self.each do |l|
233
+ acc << (block ? block.call(l) : l)
234
+ if acc.size > n
235
+ acc.shift
236
+ end
237
+ end
238
+ return acc
239
+ end
240
+
241
+ def first
242
+ head(1).first
243
+ end
244
+
245
+ def last
246
+ tail(1).first
247
+ end
248
+
249
+
250
+
251
+ end
data/lib/rubish/job.rb ADDED
@@ -0,0 +1,90 @@
1
+
2
+ # a job is not necessarily registered with job control.
3
+ class Rubish::Job
4
+ class Failure < Rubish::Error
5
+ attr_reader :job, :reason
6
+ def initialize(job,reason=nil)
7
+ raise "failure reason should be an Exception" unless reason.is_a?(Exception)
8
+ @job = job
9
+ @reason = reason
10
+ set_backtrace(reason.backtrace)
11
+ end
12
+
13
+ def to_s
14
+ @reason.to_s
15
+ end
16
+ end
17
+
18
+ attr_accessor :result
19
+ attr_reader :job_control
20
+
21
+ # subclass initializer MUST call __start
22
+ def initialize(*args)
23
+ raise "abstract"
24
+ __start
25
+ end
26
+
27
+ def __start
28
+ @result = nil
29
+ @done = false
30
+ # when wait is called, the job control may or
31
+ # may not be the current job_control.
32
+ @job_control = Rubish::JobControl.current
33
+ @job_control.submit(self)
34
+ self
35
+ end
36
+
37
+ def __finish
38
+ @job_control.remove(self)
39
+ @done = true
40
+ self
41
+ end
42
+
43
+ # MUST call __finish in wait
44
+ # should return Job#result
45
+ def wait
46
+ raise "abstract"
47
+ __finish
48
+ end
49
+
50
+ # MUST result in calling __finish
51
+ def stop
52
+ raise "abstract"
53
+ end
54
+
55
+ def done?
56
+ @done
57
+ end
58
+
59
+ end
60
+
61
+ # carry out some computation in a thread.
62
+ class Rubish::Job::ThreadJob < Rubish::Job
63
+ attr_reader :thread
64
+ def initialize(&block)
65
+ # run block in a thread
66
+ @thread = ::Thread.new {
67
+ block.call
68
+ }
69
+ __start
70
+ end
71
+
72
+ def wait
73
+ # wait thread to completeggg64
74
+ begin
75
+ @thread.join
76
+ @result = @thread.value
77
+ return self.result
78
+ rescue => e
79
+ raise Rubish::Job::Failure.new(self,e)
80
+ ensure
81
+ __finish
82
+ end
83
+ end
84
+
85
+ def stop
86
+ @thread.kill
87
+ wait
88
+ end
89
+
90
+ end
@@ -0,0 +1,62 @@
1
+
2
+ # A job is not necessarily registered with job
3
+ # control.
4
+
5
+ # TODO extend this for bg/fg (maybe?)
6
+ # Assume that job control is used by a single thread...
7
+
8
+ require 'thread'
9
+ class Rubish::JobControl
10
+
11
+ class << self
12
+ def current
13
+ Rubish::Context.current.job_control
14
+ end
15
+ end
16
+
17
+ def initialize
18
+ @mutex = Mutex.new
19
+ @jobs = { }
20
+ end
21
+
22
+ def jobs
23
+ @jobs.values
24
+ end
25
+
26
+ # need to synchronize access to the jobs hash
27
+ def submit(job)
28
+ raise "expects a Rubish::JobControl::Job" unless job.is_a?(Rubish::Job)
29
+ @mutex.synchronize {
30
+ @jobs[job.object_id] = job
31
+ }
32
+ end
33
+
34
+ def remove(job)
35
+ raise "expects a Rubish::JobControl::Job" unless job.is_a?(Rubish::Job)
36
+ raise Rubish::Error.new("Job not found: #{job}") unless @jobs.include?(job.object_id)
37
+ @mutex.synchronize {
38
+ @jobs.delete(job.object_id)
39
+ }
40
+ end
41
+
42
+ def wait(*jobs)
43
+ rss = jobs.map do |job|
44
+ job.wait
45
+ if block_given?
46
+ yield(job)
47
+ else
48
+ job
49
+ end
50
+ end
51
+ return *rss
52
+ end
53
+
54
+ # TODO handle interrupt
55
+ def waitall(&block)
56
+ wait(*@jobs.values,&block)
57
+ end
58
+
59
+
60
+ end
61
+
62
+
@@ -0,0 +1,68 @@
1
+
2
+ class Rubish::Pipe < Rubish::UnixExecutable
3
+ attr_reader :cmds
4
+
5
+ class << self
6
+ def build(workspace=nil,&block)
7
+ workspace ||= Rubish::Context.current.workspace
8
+ cmds = []
9
+ workspace.command_factory_hook = Proc.new { |cmd| cmds << cmd; cmd}
10
+ workspace.eval &block
11
+ self.new(cmds)
12
+ end
13
+ end
14
+
15
+
16
+ def initialize(cmds)
17
+ # dun wanna handle special case for now
18
+ raise "pipe length less than 2" if cmds.length < 2
19
+ raise "should build pipe only from Rubish::Command instances" unless cmds.all? { |c| c.is_a?(Rubish::Command)}
20
+ @cmds = cmds
21
+ end
22
+
23
+ def exec_with(pipe_in,pipe_out,pipe_err)
24
+ @cmds.each do |cmd|
25
+ cmd.normalize_args!
26
+ if cmd.i || cmd.o || cmd.err
27
+ raise "It's weird to redirect stdioe for command in a pipeline. Don't."
28
+ end
29
+ end
30
+ # pipes == [i0,o1,i1,o2,i2...in,o0]
31
+ # i0 == $stdin
32
+ # o0 == $stdout
33
+ pipe = nil # [r, w]
34
+ pids = []
35
+ @cmds.each_index do |index|
36
+ tail = index == (@cmds.length - 1) # tail
37
+ head = index == 0 # head
38
+ if head
39
+ i = pipe_in
40
+ pipe = IO.pipe
41
+ o = pipe[1] # w
42
+ elsif tail
43
+ i = pipe[0]
44
+ o = pipe_out
45
+ else # middle
46
+ i = pipe[0] # r
47
+ pipe = IO.pipe
48
+ o = pipe[1]
49
+ end
50
+
51
+ cmd = @cmds[index]
52
+ if child = fork # children
53
+ #parent
54
+ pids << child
55
+ # it's important to close the pipes held
56
+ # by spawning parent, otherwise the pipes
57
+ # would not close after a program ends.
58
+ i.close unless head
59
+ o.close unless tail
60
+ else
61
+ # Rubish.set_stdioe((cmd.i || i),(cmd.o || o),(cmd.err || pipe_err))
62
+ cmd.system_exec(i,o,pipe_err)
63
+ end
64
+ end
65
+ return pids
66
+ end
67
+
68
+ end