rubish 0.0.1

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