Class: Chitin::Session

Inherits:
Object show all
Defined in:
lib/chitin/session.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = nil) ⇒ Session

Returns a new instance of Session.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/chitin/session.rb', line 12

def initialize(config=nil)
  @config  = config
  @out     = STDOUT
  @sandbox = Sandbox.new # give it its own private space to work

  # include the config and builtins
  (class << @sandbox; self; end).send :include, @config

  @editor  = Coolline.new do |c|
    # Remove the default of '-' and add support for strings
    # starting after parentheses.
    c.word_boundaries = [' ', "\t", "(", ")", '[', ']', '`', '@', '$', '>',
                         '<', '=', ';', '|', '&', '{', '}']
    c.history_file    = File.join ENV['HOME'], '.chitin_history'

    # Make sure we don't kill the shell accidentally when we're trying to
    # kill a file. That's what the default does, so we're overriding that
    # here.
    c.bind(?\C-c) {}

    c.transform_proc = proc do
      CodeRay.scan(c.line, :ruby).term
    end
  
    c.completion_proc = Proc.new do
      line = c.completed_word
  
      # expand ~ to homedir
      if line.start_with? '~'
        line = ENV['HOME'] + line[1..-1]
      end
  
      # if there's a quote, remove it. we'll add it back in later, but it ruins
      # searching so we need it removed for now.
      unquoted_line = ['"', '\''].include?(line[0, 1]) ? line[1..-1] : line

      Dir[unquoted_line + '*'].map do |w|
        slash_it = File.directory?(w) and line[-1] != '/' and w[-1] != '/'
  
        "\"#{w}#{slash_it ? '/' : ''}"
      end
    end
  end

  if @sandbox.completion_proc
    @editor.completion_proc = @sandbox.completion_proc
  end
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



8
9
10
# File 'lib/chitin/session.rb', line 8

def config
  @config
end

#editorObject (readonly)

Returns the value of attribute editor.



9
10
11
# File 'lib/chitin/session.rb', line 9

def editor
  @editor
end

#last_elapsedObject (readonly)

Returns the value of attribute last_elapsed.



10
11
12
# File 'lib/chitin/session.rb', line 10

def last_elapsed
  @last_elapsed
end

#outObject

Returns the value of attribute out.



6
7
8
# File 'lib/chitin/session.rb', line 6

def out
  @out
end

#sandboxObject

Returns the value of attribute sandbox.



5
6
7
# File 'lib/chitin/session.rb', line 5

def sandbox
  @sandbox
end

Instance Method Details

#all_not_ruby?(res) ⇒ Boolean Also known as: all_shell?

Returns:

  • (Boolean)


135
136
137
138
139
140
141
# File 'lib/chitin/session.rb', line 135

def all_not_ruby?(res)
  if Array === res
     !res.empty? && res.map {|r| not returning_ruby? r }.all?
  else
    not returning_ruby? res
  end
end

#display(res) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/chitin/session.rb', line 144

def display(res)
  # The reason that this is here instead of in #evaluate is that
  # pipes could, in fact, have NO display output, but that isn't for
  # #evaluate to decide: rather, it is for #display
  if all_shell? res
    res = [res] unless Array === res
  
    res.each do |res|
      res[:run]
      res[:wait] unless res[:bg]
    end
  
  else # else it's a standard ruby type (or a pipe returning as such)
  
    if Pipe === res || StringMethod === res
      val = res[:raw_run]
    else
      val = res
    end

    # if the input is ruby, then this is redundant
    # if the input is a pipe that returns ruby,
    # then this is needed because while the expression
    # returns a pipe, when we run it it returns a ruby
    # value. we want to remember the ruby value.
    @sandbox.previous = val
  
    txt = @config.post_processing[:color].call val.inspect
    puts " => #{txt}"
  end
  
  # Run all the post_processing stuff
  # Not sure where I should really put this or what arguments it should have
  @config.post_processing[:default].each {|b| b.call }
end

#evaluate(val) ⇒ Object

we need to save the frame or something i think. could use fibers to make the whole thing a generator so that original frame would be saved. why did i write those lines. that makes no sense. AH now i remember. we need to save a frame and use that frame as a sandbox.



116
117
118
119
120
121
# File 'lib/chitin/session.rb', line 116

def evaluate(val)
  val.strip!
  @config.pre_processing[:default].each {|p| val = p.call val }
  
  @sandbox.evaluate val
end

#inspectObject



188
189
190
# File 'lib/chitin/session.rb', line 188

def inspect
  "#<Chitin::Session #{object_id}>"
end


184
185
186
# File 'lib/chitin/session.rb', line 184

def print(*args)
  @out.print *args
end

#puts(*args) ⇒ Object



180
181
182
# File 'lib/chitin/session.rb', line 180

def puts(*args)
  @out.puts *args
end

#readObject

THIS METHOD WILL POSSIBLY RETURN NIL!!!!! So it can return a string or nil. Remember that, folks.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/chitin/session.rb', line 91

def read
  # find out the column (1-indexed) that will next be printed
  # if it's NOT 1, then something was printed, and we want to insert
  # a newline
  a = nil
  STDIN.raw do
    print "\e[6n"
    a = STDIN.gets 'R'
  end

  if a.chomp('R').split(';').last != '1'
    puts
    puts "(no trailing newline)"
  end

  inp = @editor.readline @sandbox.prompt
  
  
  inp ? inp.chomp : nil # return nil so that the while loop in #start can end
end

#returning_ruby?(res) ⇒ Boolean

Returns:

  • (Boolean)


123
124
125
126
127
128
129
130
131
132
133
# File 'lib/chitin/session.rb', line 123

def returning_ruby?(res)
  # We have to use #stat here because reopened pipes retain the file
  # descriptor of the original pipe. Example:
  #   r, w = IO.pipe; r.reopen STDIN; r == STDIN # => false
  # Thus, we have to use #stat (or, more lamely, #inspect).
  return true unless Runnable === res

  res[:returning] == :ruby &&
    (res[:out] == nil ||
    res[:out].stat == STDOUT.stat)
end

#startObject

Read Evaluate Print (display) Loop



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/chitin/session.rb', line 65

def start
  while (val = read)
    next if val.empty?
  
    # a little bit of infrastructure for timing purposes
    timing = Time.now

    begin
      res = evaluate val
      display res unless val.lstrip[0, 1] == '#'
    rescue StandardError, ScriptError, Interrupt => e
      @config.post_processing[e.class].call e, val
  
      print e.backtrace.first, ': '
      puts "#{e.message} (#{e.class})"
      e.backtrace[1..-1].each {|l| puts "\t#{l}" }
      nil
    ensure
      @last_elapsed = Time.now - timing
    end
  
  end
end