rubish 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +24 -0
- data/README.markdown +651 -0
- data/README.textile +651 -0
- data/Rakefile +52 -0
- data/VERSION.yml +4 -0
- data/bin/rubish +5 -0
- data/lib/rubish.rb +24 -0
- data/lib/rubish/awk.rb +54 -0
- data/lib/rubish/batch_executable.rb +27 -0
- data/lib/rubish/command.rb +91 -0
- data/lib/rubish/command_builder.rb +116 -0
- data/lib/rubish/context.rb +75 -0
- data/lib/rubish/executable.rb +251 -0
- data/lib/rubish/job.rb +90 -0
- data/lib/rubish/job_control.rb +62 -0
- data/lib/rubish/pipe.rb +68 -0
- data/lib/rubish/repl.rb +47 -0
- data/lib/rubish/sed.rb +37 -0
- data/lib/rubish/streamer.rb +347 -0
- data/lib/rubish/stub.rb +83 -0
- data/lib/rubish/unix_executable.rb +74 -0
- data/lib/rubish/workspace.rb +211 -0
- data/test/slowcat.rb +5 -0
- data/test/test.rb +896 -0
- data/test/test_dev.rb +50 -0
- metadata +81 -0
data/lib/rubish/repl.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
class Rubish::Repl
|
3
|
+
class << self
|
4
|
+
def repl
|
5
|
+
self.new.repl
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@scanner = RubyLex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def repl
|
14
|
+
raise "$stdin is not a tty device" unless $stdin.tty?
|
15
|
+
raise "readline is not available??" unless defined?(IRB::ReadlineInputMethod)
|
16
|
+
rl = IRB::ReadlineInputMethod.new
|
17
|
+
|
18
|
+
@scanner.set_prompt do |ltype, indent, continue, line_no|
|
19
|
+
# ltype is Delimiter type. In strings that are continued across a line break, %l will display the type of delimiter used to begin the string, so you'll know how to end it. The delimiter will be one of ", ', /, ], or `.
|
20
|
+
if ltype or indent > 0 or continue
|
21
|
+
p = ". "
|
22
|
+
else
|
23
|
+
p = "> "
|
24
|
+
end
|
25
|
+
if indent
|
26
|
+
p << " " * indent
|
27
|
+
end
|
28
|
+
rl.prompt = p
|
29
|
+
end
|
30
|
+
|
31
|
+
@scanner.set_input(rl)
|
32
|
+
|
33
|
+
@scanner.each_top_level_statement do |line,line_no|
|
34
|
+
begin
|
35
|
+
r = Rubish::Context.current.eval(line)
|
36
|
+
if r.is_a?(Rubish::Executable)
|
37
|
+
r.exec
|
38
|
+
elsif r != Rubish::Null
|
39
|
+
pp r
|
40
|
+
end
|
41
|
+
rescue StandardError, ScriptError => e
|
42
|
+
puts e
|
43
|
+
puts e.backtrace
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/rubish/sed.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
class Rubish::Sed < Rubish::Streamer
|
2
|
+
|
3
|
+
def initialize(exe)
|
4
|
+
super(exe)
|
5
|
+
end
|
6
|
+
|
7
|
+
def q
|
8
|
+
self.quit
|
9
|
+
end
|
10
|
+
|
11
|
+
def s(regexp,sub)
|
12
|
+
line.sub!(regexp,sub)
|
13
|
+
return line
|
14
|
+
end
|
15
|
+
|
16
|
+
def gs(regexp,sub)
|
17
|
+
line.gsub!(regexp,sub)
|
18
|
+
return line
|
19
|
+
end
|
20
|
+
|
21
|
+
def p(string=nil)
|
22
|
+
self.puts(string || line)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def stream_begin
|
28
|
+
end
|
29
|
+
|
30
|
+
def init_line
|
31
|
+
end
|
32
|
+
|
33
|
+
def stream_end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,347 @@
|
|
1
|
+
# a Streamer wraps the output of an exectuable
|
2
|
+
#
|
3
|
+
# this implements the streaming abstraction for
|
4
|
+
# Rubish::{Sed,Awk}, corresponding to Unix
|
5
|
+
# Power Fools of similar names.
|
6
|
+
class Rubish::Streamer < Rubish::Executable
|
7
|
+
|
8
|
+
class Trigger
|
9
|
+
attr_accessor :inverted
|
10
|
+
def initialize(streamer,block,a,b,inverted=false)
|
11
|
+
@block = block
|
12
|
+
@streamer = streamer
|
13
|
+
raise "the first pattern can't be null" if a.nil?
|
14
|
+
@a = a
|
15
|
+
@b = b # could be nil. If so, this is a positioned trigger.
|
16
|
+
@inverted = inverted
|
17
|
+
@tripped = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
if @b
|
22
|
+
@streamer.instance_eval(&@block) if range_trigger
|
23
|
+
else
|
24
|
+
@streamer.instance_eval(&@block) if position_trigger
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def range_trigger
|
29
|
+
if @tripped
|
30
|
+
@tripped = !test(@b)
|
31
|
+
true
|
32
|
+
else
|
33
|
+
@tripped = test(@a)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def position_trigger
|
38
|
+
test(@a)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test(trigger)
|
42
|
+
case trigger
|
43
|
+
when :eof, -1
|
44
|
+
@streamer.peek.empty?
|
45
|
+
when :bof, 1
|
46
|
+
@streamer.lineno == 1
|
47
|
+
when Integer
|
48
|
+
@streamer.lineno == trigger
|
49
|
+
when Regexp
|
50
|
+
!(@streamer.line =~ trigger).nil?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
attr_accessor :line
|
55
|
+
attr_reader :output
|
56
|
+
attr_reader :lineno
|
57
|
+
attr_reader :buckets, :bucket_types
|
58
|
+
attr_reader :exe
|
59
|
+
|
60
|
+
def initialize(exe)
|
61
|
+
@exe = exe
|
62
|
+
@acts = []
|
63
|
+
@output = nil # the IO object that puts and pp should write to.
|
64
|
+
@buffer = [] # look ahead buffer for peek(n)
|
65
|
+
@line = nil # the current line ("pattern space" in sed speak)
|
66
|
+
@lineno = 0 # current line number
|
67
|
+
@interrupt = nil # a few methods could interrupt the sed process loop.
|
68
|
+
@buckets = {}
|
69
|
+
@bucket_types = {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def puts(*args)
|
73
|
+
output.puts args
|
74
|
+
end
|
75
|
+
|
76
|
+
# redirect
|
77
|
+
def exec!
|
78
|
+
streamer = self
|
79
|
+
Rubish::Job::ThreadJob.new {
|
80
|
+
begin
|
81
|
+
result = nil
|
82
|
+
old_output = streamer.exe.o
|
83
|
+
output = Rubish::Executable::ExecutableIO.ios([streamer.o || Rubish::Context.current.o,"w"]).first
|
84
|
+
# ask exe to output to a pipe
|
85
|
+
streamer.exe.o { |input|
|
86
|
+
# input to streamer is the output of the executable
|
87
|
+
result = streamer.exec_with(input,output.io,nil)
|
88
|
+
}.exec
|
89
|
+
result
|
90
|
+
ensure
|
91
|
+
streamer.exe.o = old_output # restores the output of the old executable
|
92
|
+
output.close if output
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def exec
|
98
|
+
exec!.wait
|
99
|
+
end
|
100
|
+
|
101
|
+
def pp(obj)
|
102
|
+
output.pp obj
|
103
|
+
end
|
104
|
+
|
105
|
+
##################################################
|
106
|
+
# Line Buffer Handling Stuff
|
107
|
+
|
108
|
+
def exec_with(i,o,_e=nil)
|
109
|
+
raise "error stream shouldn't be used" if _e
|
110
|
+
@output = o
|
111
|
+
@input = i
|
112
|
+
begin
|
113
|
+
stream_begin # abstract
|
114
|
+
while string = get_string
|
115
|
+
@line = string
|
116
|
+
init_line # abstract
|
117
|
+
interrupted = true
|
118
|
+
catch :interrupt do
|
119
|
+
@acts.each do |act|
|
120
|
+
# evaluate in the context of the object that included the Streamer.
|
121
|
+
if act.is_a?(Trigger)
|
122
|
+
act.call
|
123
|
+
else
|
124
|
+
self.instance_eval(&act)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
interrupted = false
|
128
|
+
end
|
129
|
+
if interrupted
|
130
|
+
case @interrupt
|
131
|
+
when :quit
|
132
|
+
break # stop processing
|
133
|
+
when :done
|
134
|
+
next # restart loop, skip other actions
|
135
|
+
else
|
136
|
+
raise "Unknown Sed Interrupt: #{@interrupt}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
ensure
|
141
|
+
result = stream_end # abstract
|
142
|
+
end
|
143
|
+
return result
|
144
|
+
end
|
145
|
+
|
146
|
+
def stream_begin
|
147
|
+
raise "abstract"
|
148
|
+
end
|
149
|
+
|
150
|
+
def init_line
|
151
|
+
raise "abstract"
|
152
|
+
end
|
153
|
+
|
154
|
+
# the return value of this method is taken to be
|
155
|
+
# the return value of process_stream
|
156
|
+
def stream_end
|
157
|
+
raise "abstract"
|
158
|
+
end
|
159
|
+
|
160
|
+
def act(a=nil,b=nil,&block)
|
161
|
+
if a || b
|
162
|
+
@acts << Trigger.new(self,block,a,b)
|
163
|
+
else
|
164
|
+
@acts << block
|
165
|
+
end
|
166
|
+
return self
|
167
|
+
end
|
168
|
+
|
169
|
+
def interrupt(cmd=nil)
|
170
|
+
@interrupt = cmd
|
171
|
+
throw :interrupt
|
172
|
+
end
|
173
|
+
|
174
|
+
# returns line and advances the cursor.
|
175
|
+
# nil if EOF.
|
176
|
+
def get_string
|
177
|
+
# use line in lookahead buffer if there's any
|
178
|
+
if @buffer.empty?
|
179
|
+
r = @input.gets
|
180
|
+
r.chomp! if r
|
181
|
+
else
|
182
|
+
r = @buffer.shift
|
183
|
+
end
|
184
|
+
@lineno += 1 if r # increments lineno iff it's not EOF
|
185
|
+
return r
|
186
|
+
end
|
187
|
+
|
188
|
+
# peek(n) returns n (or less, if we reach EOF)
|
189
|
+
# lines from the cursor without advancing it.
|
190
|
+
def peek(n=1)
|
191
|
+
lines = @buffer[0...n]
|
192
|
+
# return if we have enough lines in buffer to satisfy peek.
|
193
|
+
return lines if lines.length == n
|
194
|
+
# or keep reading from the pipe if we don't.
|
195
|
+
n = n - lines.length
|
196
|
+
n.times do |i|
|
197
|
+
s = @input.gets
|
198
|
+
break if s.nil? # EOF
|
199
|
+
s.chomp!
|
200
|
+
lines << s
|
201
|
+
@buffer << s
|
202
|
+
end
|
203
|
+
return lines
|
204
|
+
end
|
205
|
+
|
206
|
+
# advances cursor by n
|
207
|
+
# returns false if EOF
|
208
|
+
# returns true otherwise.
|
209
|
+
def skip(n=1)
|
210
|
+
n.times do
|
211
|
+
return false unless get_string
|
212
|
+
end
|
213
|
+
return !peek.nil?
|
214
|
+
end
|
215
|
+
|
216
|
+
# skip other actions
|
217
|
+
def done
|
218
|
+
interrupt(:done)
|
219
|
+
end
|
220
|
+
|
221
|
+
def quit
|
222
|
+
interrupt(:quit)
|
223
|
+
end
|
224
|
+
|
225
|
+
##################################################
|
226
|
+
# Bucket Handling Stuff
|
227
|
+
|
228
|
+
|
229
|
+
# common-lisp loopesque helpers
|
230
|
+
def count(name,key=nil)
|
231
|
+
create_bucket(:count,name,0)
|
232
|
+
update_bucket(name,[key,nil]) do |old_c,ignore|
|
233
|
+
old_c + 1
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def pick(name,val,key=nil)
|
238
|
+
create_bucket(:pick,name,nil)
|
239
|
+
update_bucket(name,val,key) do |old_v,new_v|
|
240
|
+
if old_v.nil?
|
241
|
+
new_v
|
242
|
+
else
|
243
|
+
yield(old_v,new_v)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def max(name,val,key=nil)
|
249
|
+
create_bucket(:max,name,nil)
|
250
|
+
update_bucket(name,val,key) do |old,new|
|
251
|
+
if old.nil?
|
252
|
+
new
|
253
|
+
elsif new > old
|
254
|
+
new
|
255
|
+
else
|
256
|
+
old
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def min(name,val,key=nil)
|
262
|
+
create_bucket(:min,name,nil)
|
263
|
+
update_bucket(name,val,key) do |old,new|
|
264
|
+
if old.nil?
|
265
|
+
new
|
266
|
+
elsif new < old
|
267
|
+
new
|
268
|
+
else
|
269
|
+
old
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def collect(name,val,key=nil)
|
275
|
+
# the initial value should be nil, if it's the
|
276
|
+
# empty array, it would be shared among all
|
277
|
+
# the buckets (which is incorrect (since we
|
278
|
+
# are doing destructive append))
|
279
|
+
create_bucket(:collect,name,nil)
|
280
|
+
update_bucket(name,val,key) do |acc,val|
|
281
|
+
if acc.nil?
|
282
|
+
acc = [val]
|
283
|
+
else
|
284
|
+
acc << val
|
285
|
+
end
|
286
|
+
acc
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# size-limited FIFO buffer
|
291
|
+
def hold(name,size,val,key=nil)
|
292
|
+
raise "hold size should be larger than 1" unless size >= 1
|
293
|
+
create_bucket(:hold,name,nil)
|
294
|
+
update_bucket(name,val,key) do |acc,val|
|
295
|
+
if acc.nil?
|
296
|
+
acc = [val]
|
297
|
+
elsif acc.length < size
|
298
|
+
acc << val
|
299
|
+
else
|
300
|
+
acc.shift
|
301
|
+
acc << val
|
302
|
+
end
|
303
|
+
acc
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
private
|
308
|
+
|
309
|
+
# [type] denotes an aggregate of type
|
310
|
+
# type denotes the type itself (non-aggregate)
|
311
|
+
def create_bucket(type,name,init_val)
|
312
|
+
name = name.to_sym
|
313
|
+
if buckets.has_key?(name)
|
314
|
+
raise "conflict bucket types for: #{name}" unless bucket_types[name] == type
|
315
|
+
return false
|
316
|
+
else
|
317
|
+
raise "bucket name conflicts with existing method: #{name}" if self.respond_to?(name)
|
318
|
+
bucket_types[name] = type
|
319
|
+
buckets[name] = Hash.new(init_val)
|
320
|
+
#singleton = class << self; self; end
|
321
|
+
|
322
|
+
defstr = <<-HERE
|
323
|
+
def #{name.to_s}(key=nil)
|
324
|
+
buckets[:#{name.to_s}][key]
|
325
|
+
end
|
326
|
+
HERE
|
327
|
+
self.instance_eval(defstr)
|
328
|
+
|
329
|
+
|
330
|
+
end
|
331
|
+
return true
|
332
|
+
end
|
333
|
+
|
334
|
+
def update_bucket(name,val,key=nil)
|
335
|
+
name = name.to_sym
|
336
|
+
# if a key is given, update the key specific sub-bucket.
|
337
|
+
if key
|
338
|
+
new_val = yield(buckets[name][key],val)
|
339
|
+
buckets[name][key] = new_val
|
340
|
+
end
|
341
|
+
# always update the special nil key.
|
342
|
+
new_val = yield(buckets[name][nil],val)
|
343
|
+
buckets[name][nil] = new_val
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
end
|
data/lib/rubish/stub.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
module Rubish
|
3
|
+
# magic singleton value to supress shell output.
|
4
|
+
module Null
|
5
|
+
end
|
6
|
+
|
7
|
+
class Error < RuntimeError
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def repl
|
12
|
+
Repl.repl
|
13
|
+
end
|
14
|
+
|
15
|
+
# dup2 the given i,o,e to stdin,stdout,stderr
|
16
|
+
# close all other file descriptors.
|
17
|
+
def set_stdioe(i,o,e)
|
18
|
+
$stdin.reopen(i)
|
19
|
+
$stdout.reopen(o)
|
20
|
+
$stderr.reopen(e)
|
21
|
+
ObjectSpace.each_object(IO) do |io|
|
22
|
+
unless io.closed? || [0,1,2].include?(io.fileno)
|
23
|
+
io.close
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def Rubish(&__block)
|
31
|
+
Rubish::Context.global.eval {
|
32
|
+
begin
|
33
|
+
self.eval(&__block)
|
34
|
+
ensure
|
35
|
+
waitall
|
36
|
+
end
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# Rubish::UnixExecutable < Rubish::Executable
|
42
|
+
# Rubish::Command < Rubish::UnixExecutable
|
43
|
+
# Rubish::Pipe < Rubish::UnixExecutable
|
44
|
+
#
|
45
|
+
# Rubish::Streamer < Rubish::Executable
|
46
|
+
# Rubish::Sed < Rubish::Streamer
|
47
|
+
# Rubish::Awk < Rubish::Streamer
|
48
|
+
#
|
49
|
+
# Rubish::BatchExecutable < Rubish::Executable
|
50
|
+
class Rubish::Executable
|
51
|
+
# stub, see: executable.rb
|
52
|
+
def exec
|
53
|
+
raise "implemented in executable.rb"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# This is an object that doesn't respond to anything.
|
58
|
+
#
|
59
|
+
# This provides an empty context for instance_eval (__instance_eval
|
60
|
+
# for the Mu object). It catches all method calls with method_missing.
|
61
|
+
# It is All and Nothing.
|
62
|
+
class Rubish::Mu
|
63
|
+
|
64
|
+
self.public_instance_methods.each do |m|
|
65
|
+
# for consistency's sake, methods already
|
66
|
+
# underscored should also be aliased, but we
|
67
|
+
# don't undefine it.
|
68
|
+
self.send(:alias_method,"__#{m}",m)
|
69
|
+
if m[0..1] != "__"
|
70
|
+
# don't remove special methods (i.e. __id__, __send__)
|
71
|
+
self.send(:undef_method,m)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(*modules,&block)
|
76
|
+
raise "abstract"
|
77
|
+
end
|
78
|
+
|
79
|
+
def method_missing(*args,&block)
|
80
|
+
raise "abstract"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|