chitin 1.0.1

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