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.



11
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
# File 'lib/chitin/session.rb', line 11

def initialize(config=nil)
  @config  = config
  @out     = STDOUT
  @sandbox = Sandbox.new # give it its own private space to work
  (class << @sandbox; self; end).send :include, @config # include the config and builtins

  @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

#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

#display(res) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/chitin/session.rb', line 104

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
  #
  # Only pipes and executables are standalone. StringMethods need to be
  # chained, and proc is also a general Ruby type, so we don't want to
  # automatically call it.
  if shell_command?(res) || (res.is_a?(Array) && !res.empty? && res.map {|r| shell_command? r }.all?)
  
    res = [res] unless res.is_a?(Array)
  
    res.each do |res|
      # set up the inputs and outputs
      res[:set_in,  STDIN]  unless res[:in]
      res[:set_out, STDOUT] unless res[:out]
      res[:set_err, STDOUT] unless res[:err]
  
      res[:run]
      res[:wait] unless res[:bg]
    end
  
  else # else it's a standard ruby type (or a pipe returning as such)
  
    unless res.is_a? Pipe
      # RUBY values pass through here
      txt = @config.post_processing[:color].call res.inspect
      puts " => #{txt}"
    else
      # set up the inputs and outputs
      res[:set_in,  STDIN]  unless res[:in]
      res[:set_out, STDOUT] unless res[:out]
      res[:set_err, STDOUT] unless res[:err]
  
      # 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).
      if res[:out].stat == STDOUT.stat
        val = res[:raw_run]
        txt = @config.post_processing[:color].call val.inspect
        puts " => #{txt}"
      else
        res[:run]
        res[:wait] unless res[:bg]
      end
    end
  
  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.



93
94
95
96
97
98
# File 'lib/chitin/session.rb', line 93

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


163
164
165
# File 'lib/chitin/session.rb', line 163

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

#puts(*args) ⇒ Object



159
160
161
# File 'lib/chitin/session.rb', line 159

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.



82
83
84
85
86
87
# File 'lib/chitin/session.rb', line 82

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

#shell_command?(res) ⇒ Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/chitin/session.rb', line 100

def shell_command?(res)
  res.is_a?(Executable) || (res.is_a?(Pipe) && res[:returning] != :ruby)
end

#startObject

Read Evaluate Print (display) Loop



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/chitin/session.rb', line 61

def start
  while (val = read)
    next if val.empty?
  
    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
    end
  
  end
end