chitin 1.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.
- data/ANNOUNCEMENT +67 -0
- data/README +414 -0
- data/TODO +32 -0
- data/bin/chitin +26 -0
- data/chitin.gemspec +31 -0
- data/chitinrc +86 -0
- data/lib/chitin/commands/builtins.rb +224 -0
- data/lib/chitin/commands/executable.rb +139 -0
- data/lib/chitin/commands/pipe.rb +112 -0
- data/lib/chitin/commands/ruby.rb +152 -0
- data/lib/chitin/commands/runnable.rb +84 -0
- data/lib/chitin/core_ext/coolline.rb +104 -0
- data/lib/chitin/core_ext/io.rb +7 -0
- data/lib/chitin/core_ext/object.rb +21 -0
- data/lib/chitin/core_ext/string.rb +35 -0
- data/lib/chitin/core_ext/symbol.rb +6 -0
- data/lib/chitin/file.rb +163 -0
- data/lib/chitin/sandbox.rb +18 -0
- data/lib/chitin/session.rb +167 -0
- data/lib/chitin/support.rb +51 -0
- data/lib/chitin/tools/csv.rb +0 -0
- data/lib/chitin/tools/replay.rb +45 -0
- data/lib/chitin/tools/string_methods.rb +10 -0
- data/lib/chitin/version.rb +4 -0
- data/lib/chitin.rb +26 -0
- data/sample.txt +187 -0
- metadata +126 -0
@@ -0,0 +1,224 @@
|
|
1
|
+
# Things are contained in modules or functions so that they play well with
|
2
|
+
# code folding.
|
3
|
+
|
4
|
+
require 'etc'
|
5
|
+
ENV['USER'] ||= Etc.getlogin
|
6
|
+
ENV['HOME'] ||= Etc.getpwent(ENV['USER']).dir
|
7
|
+
|
8
|
+
module Chitin
|
9
|
+
module Builtins
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# interface to Coolline
|
13
|
+
def bind(key, &action)
|
14
|
+
cool = defined?(SESSION) ? SESSION : Coolline
|
15
|
+
cool.bind(key, &action)
|
16
|
+
end
|
17
|
+
|
18
|
+
def completion_proc(&block)
|
19
|
+
@completion_proc = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def pre_processing
|
23
|
+
@pre_processing ||= {:default => []}
|
24
|
+
end
|
25
|
+
|
26
|
+
def pre_process(label=:default, &block)
|
27
|
+
if label == :default
|
28
|
+
pre_processing[label] << block
|
29
|
+
else
|
30
|
+
pre_processing[label] = block
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def post_processing
|
35
|
+
@post_processing ||= {:default => []}
|
36
|
+
@post_processing.default = Proc.new {|*args| args.size > 1 ? args : args.first }
|
37
|
+
@post_processing
|
38
|
+
end
|
39
|
+
|
40
|
+
def post_process(label=:default, &block)
|
41
|
+
if label == :default
|
42
|
+
post_processing[label] << block
|
43
|
+
else
|
44
|
+
post_processing[label] = block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# L for Lambda. Creates a proc as a RubyMethod
|
49
|
+
def L(&block)
|
50
|
+
StringMethod.new :bottle, &block
|
51
|
+
end
|
52
|
+
|
53
|
+
# Place the last argument on the line
|
54
|
+
# This is a total hack. It would need proper parsing to accomodate for
|
55
|
+
# strings with spaces in them.
|
56
|
+
bind "\e." do |c|
|
57
|
+
# Make it a proper string, first
|
58
|
+
last_line = proper_string c.history[-2]
|
59
|
+
last_arg = shellsplit_keep_quotes(c.history[-2]).last
|
60
|
+
c.line << last_arg
|
61
|
+
c.pos = c.line.length # go one beyond the last character position
|
62
|
+
end
|
63
|
+
|
64
|
+
# Quote the word around or before the cursor.
|
65
|
+
# Useful for when you finish typing something and think "damn i'd like this
|
66
|
+
# in quotes"
|
67
|
+
bind ?\C-q do |c|
|
68
|
+
if beg = c.word_beginning_before(c.pos) and last = c.word_end_before(c.pos) and
|
69
|
+
not (["'", '"'].include?(c.line[beg..last][0, 1]) &&
|
70
|
+
["'", '"'].include?(c.line[beg..last][-1, 1]))
|
71
|
+
c.line[beg..last] = '"' + c.line[beg..last] + '"'
|
72
|
+
c.pos = last + 3 # 2 for the quotes and 1 for the cursor
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Clear the line, don't add it to history
|
77
|
+
bind ?\C-g do |c|
|
78
|
+
c.line.clear
|
79
|
+
c.pos = 0
|
80
|
+
end
|
81
|
+
|
82
|
+
# Clear the line, add it to history
|
83
|
+
bind ?\C-d do |c|
|
84
|
+
# Save the current line
|
85
|
+
c.history[-1] = c.line if c.history.size != 0
|
86
|
+
c.history.index = c.history.size
|
87
|
+
c.history.save_line
|
88
|
+
|
89
|
+
# And prep the new one
|
90
|
+
c.instance_variable_set :@line, ""
|
91
|
+
c.pos = 0
|
92
|
+
c.history.index = c.history.size - 1
|
93
|
+
c.history << c.line
|
94
|
+
end
|
95
|
+
|
96
|
+
module Prompts
|
97
|
+
# The standard prompt. Must return a string. Override this to provide
|
98
|
+
# a custom prompt.
|
99
|
+
def big_prompt
|
100
|
+
"+-#{'-' * ENV['USER'].length}--#{'-' * short_pwd.length}-+\n" +
|
101
|
+
"| #{ENV['USER']}: #{short_pwd} |\n" +
|
102
|
+
"+-#{'-' * ENV['USER'].length}--#{'-' * short_pwd.length}-+ "
|
103
|
+
end
|
104
|
+
|
105
|
+
def small_prompt
|
106
|
+
"#{ENV['USER']}: #{short_pwd} % "
|
107
|
+
end
|
108
|
+
|
109
|
+
def prompt
|
110
|
+
big_prompt
|
111
|
+
end
|
112
|
+
end
|
113
|
+
include Prompts
|
114
|
+
|
115
|
+
# I'm putting these in their own module so that you can compress them easily
|
116
|
+
# using code folding. Sinatra-style, baby.
|
117
|
+
module Aliases
|
118
|
+
# Basic helper commands for shell usability
|
119
|
+
def cd(path=ENV['HOME']); Dir.chdir path; pwd; end
|
120
|
+
def pwd; Dir.pwd; end
|
121
|
+
def short_pwd
|
122
|
+
home = ENV['HOME']
|
123
|
+
pwd.start_with?(home) ? pwd.sub(home, '~') : pwd
|
124
|
+
end
|
125
|
+
def ll(*a); ls '-hGl', *a; end
|
126
|
+
def la(*a); ls '-haGl', *a; end
|
127
|
+
def exeunt; puts 'Fare thee well...'; exit 0; end
|
128
|
+
def x; exeunt; end
|
129
|
+
def c; clear; end
|
130
|
+
|
131
|
+
def gem(*a); raw_exec("gem", *a); end
|
132
|
+
def all; D('.'); end
|
133
|
+
end
|
134
|
+
include Aliases
|
135
|
+
|
136
|
+
# Fixnum math is now floating-point math
|
137
|
+
class ::Fixnum
|
138
|
+
alias_method :old_div, :/
|
139
|
+
|
140
|
+
def /(other)
|
141
|
+
self.to_f / other.to_f
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
module ExecutableBinaries
|
146
|
+
# Executable files
|
147
|
+
# Do them lazily because otherwise we could have a SHITTONNE of methods lying
|
148
|
+
# around in the objectspace
|
149
|
+
COMMANDS = {}
|
150
|
+
PRIORITY_METHODS = []
|
151
|
+
def path_for_exec(name)
|
152
|
+
# poor man's caching
|
153
|
+
return COMMANDS[name] if COMMANDS[name]
|
154
|
+
|
155
|
+
ENV['PATH'].split(':').each do |p|
|
156
|
+
if File.exist? File.join(p, name.to_s)
|
157
|
+
COMMANDS[name] = File.join(p, name.to_s)
|
158
|
+
return COMMANDS[name]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
false
|
163
|
+
end
|
164
|
+
|
165
|
+
def method_missing(name, *args, &block)
|
166
|
+
if PRIORITY_METHODS.include? name
|
167
|
+
return StringMethod.new(name, *args, &block)
|
168
|
+
end
|
169
|
+
|
170
|
+
if path_for_exec(name)
|
171
|
+
return Executable.new(path_for_exec(name), *args)
|
172
|
+
end
|
173
|
+
|
174
|
+
if "".public_methods(false).include? name
|
175
|
+
return StringMethod.new(name, *args, &block)
|
176
|
+
end
|
177
|
+
|
178
|
+
# If we made it this far, there is no executable to be had. Super it up.
|
179
|
+
super
|
180
|
+
end
|
181
|
+
|
182
|
+
def raw_exec(path, *args)
|
183
|
+
Executable.new path, *args
|
184
|
+
end
|
185
|
+
alias_method :here, :raw_exec
|
186
|
+
end
|
187
|
+
include ExecutableBinaries
|
188
|
+
|
189
|
+
pre_process do |val|
|
190
|
+
proper_string val
|
191
|
+
# if there is an unclosed string, close it and run it again.
|
192
|
+
# smart compilers are bad... but this ain't a compiler
|
193
|
+
#
|
194
|
+
# option: make it ask for confirmation first
|
195
|
+
# settable in chitinrc, perjaps?
|
196
|
+
if (e = syntax_error_for(val)) &&
|
197
|
+
e.message =~ /unterminated string meets end of file/
|
198
|
+
|
199
|
+
if syntax_error_for(val + '\'')
|
200
|
+
unless syntax_error_for(val + '"')
|
201
|
+
val << '"'
|
202
|
+
end
|
203
|
+
else
|
204
|
+
val << '\''
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
val
|
210
|
+
end
|
211
|
+
|
212
|
+
# You can use error classes as a name and if the error comes up,
|
213
|
+
# block of code will be run.
|
214
|
+
#
|
215
|
+
# post_process SyntaxError do |e, val|
|
216
|
+
# # sample
|
217
|
+
# end
|
218
|
+
|
219
|
+
post_process :color do |val|
|
220
|
+
Wirble::Colorize.colorize val
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Chitin
|
2
|
+
class Executable
|
3
|
+
include Runnable
|
4
|
+
|
5
|
+
def initialize(path, *args)
|
6
|
+
@path = path
|
7
|
+
@args = process_args(args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def |(other)
|
11
|
+
Pipe.new self, other
|
12
|
+
end
|
13
|
+
alias_method :<=>, :|
|
14
|
+
|
15
|
+
# EVERYTHING will be sent to the command. ERRTHANG!!!!
|
16
|
+
def method_missing(name, *arr, &blk)
|
17
|
+
setup name.to_s, *process_args(arr)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_accessor :args
|
24
|
+
attr_accessor :path
|
25
|
+
attr_accessor :pid
|
26
|
+
|
27
|
+
def process_args(args)
|
28
|
+
processed = args.map do |arg|
|
29
|
+
if arg.is_a? Hash
|
30
|
+
arg.map do |k, v|
|
31
|
+
if k.is_a? Symbol
|
32
|
+
["#{k.to_s.size > 1 ? '--' : '-'}#{k}", v.to_s]
|
33
|
+
else
|
34
|
+
[k.to_s, v.to_s]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
else
|
38
|
+
arg.to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
processed.flatten
|
43
|
+
end
|
44
|
+
|
45
|
+
def args
|
46
|
+
@args ||= []
|
47
|
+
end
|
48
|
+
|
49
|
+
# prep the executable to run with the arguments +subc+ and +args+
|
50
|
+
def setup(subc, *arr)
|
51
|
+
# we have to use +@args+ here because otherwise we'd have to use the +=
|
52
|
+
# method which would trigger an explosion of +method_missing+
|
53
|
+
@args += [subc.to_s]
|
54
|
+
@args += arr.map {|a| a.to_s }
|
55
|
+
self # need to return this so that the shell can just run it
|
56
|
+
end
|
57
|
+
|
58
|
+
# TODO fix it so that self.err isn't alone in the whole `if self.err`
|
59
|
+
# bit. figure out a good way to make it default or something. i dunno.
|
60
|
+
# just do it.
|
61
|
+
def run
|
62
|
+
child = fork do
|
63
|
+
# Like the comments in +open_pipes+, the user writes to +@in+.
|
64
|
+
# Then our executable now will read from +@in+. OH HEY NO WAY
|
65
|
+
# since executables normally read from STDIN, we can link them.
|
66
|
+
IO.open(0).reopen self[:in] # STDIN to the program comes from
|
67
|
+
IO.open(1).reopen self[:out]
|
68
|
+
IO.open(2).reopen self[:err] if self[:err]
|
69
|
+
|
70
|
+
self[:in].close # These can be closed
|
71
|
+
self[:out].close # because there is already an opening
|
72
|
+
self[:err].close if self[:err] # via IO 0, 1, and 2.
|
73
|
+
# And it is important that they be closed, otherwise
|
74
|
+
# we'll have a hanging pipe that will hold everything up.
|
75
|
+
# In the future, we'll use `close_all` to make it simple.
|
76
|
+
|
77
|
+
exec path, *args
|
78
|
+
end
|
79
|
+
|
80
|
+
close_all
|
81
|
+
|
82
|
+
reset
|
83
|
+
@pid = child
|
84
|
+
end
|
85
|
+
|
86
|
+
def wait
|
87
|
+
Process.wait @pid
|
88
|
+
rescue Interrupt => e
|
89
|
+
Process.kill "HUP", @pid
|
90
|
+
end
|
91
|
+
|
92
|
+
def reset
|
93
|
+
@opened = false
|
94
|
+
self.in, self.out, self.err = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def each_line
|
98
|
+
proc do |inp|
|
99
|
+
# since out is only open for writing (since it is supposed to be used
|
100
|
+
# by the process itself, like with puts and print), we need to make our
|
101
|
+
# OWN pipe
|
102
|
+
r, w = IO.pipe
|
103
|
+
inp > self > w
|
104
|
+
run # since we let it run in the background, we're not gonna call +#wait+
|
105
|
+
|
106
|
+
# lil bits at a time
|
107
|
+
while (chunk = r.gets)
|
108
|
+
yield chunk
|
109
|
+
end
|
110
|
+
|
111
|
+
self
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def inspect
|
116
|
+
"#<Chitin::Executable @path=#{@path.inspect} @args=#{@args.inspect}>"
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_s
|
120
|
+
arr = args.map do |arg|
|
121
|
+
if arg.is_a? Hash
|
122
|
+
arg.map do |k, v|
|
123
|
+
if k.is_a? Symbol
|
124
|
+
["#{k.to_s.size > 1 ? '--' : '-'}#{k}", v.to_s]
|
125
|
+
else
|
126
|
+
[k.to_s, v.to_s]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
else
|
130
|
+
arg.to_s
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
[path, *arr.flatten].join ' '
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Chitin
|
2
|
+
class Pipe
|
3
|
+
include Runnable
|
4
|
+
|
5
|
+
attr_accessor :parts
|
6
|
+
attr_accessor :pids
|
7
|
+
|
8
|
+
def initialize(*parts)
|
9
|
+
@parts = parts
|
10
|
+
|
11
|
+
link_all
|
12
|
+
end
|
13
|
+
|
14
|
+
# link everything together
|
15
|
+
def link_all
|
16
|
+
# move from left to right through the list
|
17
|
+
# link the left one to the right one
|
18
|
+
parts[1..-1].inject parts.first do |left, right|
|
19
|
+
link left, right
|
20
|
+
# return this, and shift the whole process one to the right
|
21
|
+
right
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def link(left, right)
|
26
|
+
# The pipe that we use to connect the dots
|
27
|
+
r, w = IO.pipe
|
28
|
+
|
29
|
+
# since left will want to write to STDOUT, we have to give it
|
30
|
+
# something else it can write to.
|
31
|
+
left > w
|
32
|
+
|
33
|
+
# same thing for right, but with reading
|
34
|
+
r > right
|
35
|
+
|
36
|
+
# right will try to read from r until w is closed.
|
37
|
+
# when w is closed, right will finish processing.
|
38
|
+
# if we don't close this, right will never return
|
39
|
+
w.close
|
40
|
+
r.close # and we close r so that we don't have any open useless handles
|
41
|
+
end
|
42
|
+
|
43
|
+
# Like raw_run, but meant to be used in chaining.
|
44
|
+
def run
|
45
|
+
parts.each {|part| part[:run] }
|
46
|
+
reset
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Run them all but let the last run return its value
|
52
|
+
def raw_run
|
53
|
+
parts[0..-2].each {|part| part[:run] }
|
54
|
+
result = parts.last[:raw_run]
|
55
|
+
reset
|
56
|
+
|
57
|
+
result
|
58
|
+
end
|
59
|
+
|
60
|
+
def wait
|
61
|
+
parts.each {|part| part[:wait] }
|
62
|
+
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def reset
|
67
|
+
parts.map {|part| part[:reset] }
|
68
|
+
link_all # we have to refresh the pipes connecting everything
|
69
|
+
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def |(other)
|
74
|
+
raise "#{other.class} needs to include Runnable" unless Runnable === other
|
75
|
+
|
76
|
+
link parts.last, other
|
77
|
+
parts << other
|
78
|
+
|
79
|
+
self
|
80
|
+
end
|
81
|
+
alias_method :<=>, :|
|
82
|
+
|
83
|
+
def in
|
84
|
+
@parts.first[:in]
|
85
|
+
end
|
86
|
+
def in=(other); @parts.first[:set_in, other]; end
|
87
|
+
|
88
|
+
def out
|
89
|
+
@parts.last[:out]
|
90
|
+
end
|
91
|
+
def out=(other); @parts.last[:set_out, other]; end
|
92
|
+
|
93
|
+
def err
|
94
|
+
@parts.last[:err]
|
95
|
+
end
|
96
|
+
def err=(other); @parts.last[:set_err, other]; end
|
97
|
+
|
98
|
+
def to_s
|
99
|
+
"#{self.in} > #{parts.map {|p| p[:to_s] }.join ' | '} > #{self.out}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def returning
|
103
|
+
if [StringMethod, Proc].include?(@parts.last[:class])
|
104
|
+
:ruby
|
105
|
+
else
|
106
|
+
:io
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Chitin
|
2
|
+
class StringMethod
|
3
|
+
include Runnable
|
4
|
+
|
5
|
+
attr_reader :chains
|
6
|
+
attr_reader :pid
|
7
|
+
|
8
|
+
def initialize(*arr, &block)
|
9
|
+
raise "Need at least a method name" unless [String, Symbol].include? arr.first.class
|
10
|
+
@chains = []
|
11
|
+
latest = [*arr]
|
12
|
+
latest << block if block
|
13
|
+
@chains << latest
|
14
|
+
end
|
15
|
+
|
16
|
+
def |(other)
|
17
|
+
Pipe.new self, other
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(*arr, &block)
|
21
|
+
latest = [*arr]
|
22
|
+
latest << block if block
|
23
|
+
@chains << latest
|
24
|
+
self # chainable
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def result
|
30
|
+
val = self[:in].read
|
31
|
+
@chains.each do |arr|
|
32
|
+
val = if arr.last.is_a? Proc
|
33
|
+
val.send *arr[0..-2], &arr[-1]
|
34
|
+
else
|
35
|
+
val.send *arr
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
val
|
40
|
+
end
|
41
|
+
|
42
|
+
# this is runs the method on the input
|
43
|
+
# and then writes the output to self.out
|
44
|
+
# used in piping
|
45
|
+
def run
|
46
|
+
child = fork do
|
47
|
+
self[:out].write result
|
48
|
+
|
49
|
+
close_all
|
50
|
+
end
|
51
|
+
|
52
|
+
close_all
|
53
|
+
reset
|
54
|
+
|
55
|
+
@pid = child
|
56
|
+
end
|
57
|
+
|
58
|
+
# like #run, except instead of writing the output
|
59
|
+
# we simply return it
|
60
|
+
# used when a StringMethod is the LAST item in a pipe
|
61
|
+
def raw_run
|
62
|
+
res = result
|
63
|
+
close_all
|
64
|
+
reset
|
65
|
+
|
66
|
+
res
|
67
|
+
end
|
68
|
+
|
69
|
+
def wait
|
70
|
+
Process.wait @pid
|
71
|
+
end
|
72
|
+
|
73
|
+
def reset
|
74
|
+
self.in, self.out, self.err = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
@chains.map do |arr|
|
79
|
+
"#{arr[0]}(#{arr[1..-1].map {|a| a.is_a?(Proc) ? "&block" : a.inspect }.join ', '})"
|
80
|
+
end.join '.'
|
81
|
+
end
|
82
|
+
|
83
|
+
def inspect
|
84
|
+
"#<StringMethod #{to_s}>"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Proc
|
90
|
+
# So let's quick chat about what including Runnable to a proc means.
|
91
|
+
# It means that it can be chained with pipes.
|
92
|
+
# It also means that you can run private methods with #[].
|
93
|
+
# The important thing isn't that you can run private methods. You could
|
94
|
+
# do that since the beginning of time.
|
95
|
+
# The take-away is that it OVERRIDES #[]. In the words of Joe Biden,
|
96
|
+
# this is a big fucking deal.
|
97
|
+
#
|
98
|
+
# whatdo.jpg
|
99
|
+
#
|
100
|
+
# This means that you cannot, in the environment of Chitin, use
|
101
|
+
# Proc#[] for anything other than accessing private methods. This
|
102
|
+
# means it does not play well with others. This is what we in the biz
|
103
|
+
# call bad.
|
104
|
+
undef_method :[]
|
105
|
+
include Chitin::Runnable
|
106
|
+
|
107
|
+
# # access private methods
|
108
|
+
# def [](*args)
|
109
|
+
# if method(args.first)
|
110
|
+
# method(args.first).call *args[1..-1]
|
111
|
+
# else
|
112
|
+
# raise NoMethodError.new("undefined method" +
|
113
|
+
# "`#{args.first}' for #{self}:#{self.class}")
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
|
117
|
+
def |(other)
|
118
|
+
Pipe.new self, other
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def run
|
124
|
+
child = fork do
|
125
|
+
self.out.puts call(self.in)
|
126
|
+
|
127
|
+
close_all
|
128
|
+
end
|
129
|
+
|
130
|
+
close_all
|
131
|
+
reset
|
132
|
+
|
133
|
+
@pid = child
|
134
|
+
end
|
135
|
+
|
136
|
+
def raw_run
|
137
|
+
val = call self.in
|
138
|
+
|
139
|
+
close_all
|
140
|
+
reset
|
141
|
+
val
|
142
|
+
end
|
143
|
+
|
144
|
+
def wait
|
145
|
+
Process.wait @pid
|
146
|
+
end
|
147
|
+
|
148
|
+
def reset
|
149
|
+
self.in, self.out, self.err = nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Chitin
|
2
|
+
module Runnable
|
3
|
+
|
4
|
+
def >(io)
|
5
|
+
case io
|
6
|
+
when IO, File
|
7
|
+
self[:set_out, io]
|
8
|
+
when String, FileObject
|
9
|
+
f = File.open io, 'w'
|
10
|
+
self[:set_out, f]
|
11
|
+
f.close
|
12
|
+
else
|
13
|
+
raise "Unknown piping type: #{io.class}"
|
14
|
+
end
|
15
|
+
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# access private methods
|
20
|
+
def [](*args)
|
21
|
+
if method(args.first)
|
22
|
+
method(args.first).call *args[1..-1]
|
23
|
+
else
|
24
|
+
raise NoMethodError.new("undefined method" +
|
25
|
+
"`#{args.first}' for #{self}:#{self.class}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :bg
|
32
|
+
attr_accessor :in
|
33
|
+
attr_accessor :out
|
34
|
+
attr_accessor :err
|
35
|
+
|
36
|
+
# Generally the same as +run+, except for the ruby commands
|
37
|
+
# they return real ruby that can be given back to the user.
|
38
|
+
def raw_run
|
39
|
+
run
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_in(other)
|
43
|
+
unless self[:in]
|
44
|
+
r, w = IO.pipe
|
45
|
+
w.close
|
46
|
+
self[:in=, r]
|
47
|
+
end
|
48
|
+
self[:in].reopen other
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_out(other)
|
52
|
+
unless self[:out]
|
53
|
+
r, w = IO.pipe
|
54
|
+
r.close
|
55
|
+
self[:out=, w]
|
56
|
+
end
|
57
|
+
self[:out].reopen other
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_err(other)
|
61
|
+
unless self[:err]
|
62
|
+
r, w = IO.pipe
|
63
|
+
r.close
|
64
|
+
self[:err=, w]
|
65
|
+
end
|
66
|
+
self[:err].reopen other
|
67
|
+
end
|
68
|
+
|
69
|
+
# code smell...
|
70
|
+
def close_all
|
71
|
+
self[:in] && self[:in].close
|
72
|
+
self[:out] && self[:out].close
|
73
|
+
self[:err] && self[:err].close
|
74
|
+
end
|
75
|
+
|
76
|
+
# Methods to be done by those including this:
|
77
|
+
# wait
|
78
|
+
# reset
|
79
|
+
# run
|
80
|
+
# |(other) though this is easily done with a Pipe.new(self, other)
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|