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.
@@ -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
+