chitin 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|