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