rish 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env/ruby
2
+ # -*- ruby -*-
3
+
4
+ # Copyright (c) 2010 by Mikio L. Braun
5
+ # rish is distributed under a BSD-style license. See COPYING
6
+
7
+ require 'rish/shell'
8
+ require 'rish/autoreload'
9
+
10
+ def banner
11
+ puts <<EOS
12
+
13
+ rish - Ruby Interactive Shell
14
+
15
+ an autoreloading interactive shell with
16
+ interactive help, shell-outs and profiling.
17
+
18
+ written by Mikio L. Braun
19
+
20
+ Type 'exit' or hit Ctrl-D on an empty line to exit.
21
+ Type '@h' to get help on built-in commands
22
+
23
+ EOS
24
+ end
25
+
26
+ def usage
27
+ puts <<EOS
28
+ Usage: rish [options] files_to_require.rb ...
29
+
30
+ Options:
31
+ -a dir : load all *.rb files in dir/ on startup
32
+ -w dir : watch all *.rb files in dir/ for changes
33
+ -I dir : add dir/ to load-path
34
+ --irb : use IRB as shell (default)
35
+ --rish : user rish's own shell
36
+ -h, --help : show help (this)
37
+ EOS
38
+ end
39
+
40
+ #
41
+ # Main
42
+ #
43
+
44
+ banner
45
+
46
+ type = :irb
47
+
48
+ until ARGV.empty?
49
+ cmd = ARGV.shift
50
+ case cmd
51
+ when '-I'
52
+ dir = ARGV.shift
53
+ $: << dir
54
+ when '-a'
55
+ dir = ARGV.shift
56
+ puts "Loading all ruby files in #{dir}/"
57
+ Dir.glob("#{dir}/**/*.rb").each {|fn| require fn}
58
+ when '-w'
59
+ dir = ARGV.shift
60
+ Rish::Autoreload.watch_directory dir
61
+ when '-h', '--help'
62
+ usage
63
+ exit
64
+ when '-e'
65
+ cmd = ARGV.shift
66
+ puts eval(cmd)
67
+ exit
68
+ when '--irb'
69
+ type = :irb
70
+ when '--rish'
71
+ type = :rish
72
+ else
73
+ require cmd
74
+ end
75
+ end
76
+
77
+ Rish::Autoreload.check_directories
78
+
79
+ shell = Rish.shell(type)
@@ -0,0 +1,123 @@
1
+ # Copyright (c) 2010 by Mikio L. Braun
2
+ # rish is distributed under a BSD-style license. See COPYING
3
+
4
+ require 'pp'
5
+ require 'set'
6
+
7
+ module Rish
8
+ # This module tracks loaded files and their timestamps and allows to reload
9
+ # files which have changed automatically by calling reload.
10
+ #
11
+ # There is nothing magically happening here. Basically, you can
12
+ # reload a file by removing it from $" (the list of all loaded
13
+ # files) and require'ing it again.
14
+ module Autoreload
15
+
16
+ # stores the normalized filenames and their File.mtime timestamps
17
+ @timestamps = Hash.new
18
+ @notfound = Set.new
19
+ @verbose = false
20
+ @watched_dirs = []
21
+
22
+ # Set verbosity flag. Setting this to true will report each file
23
+ # that has been reloaded.
24
+ def self.verbose=(flag)
25
+ @verbose = flag
26
+ end
27
+
28
+ # Find the full path to a file.
29
+ def self.locate(file)
30
+ return nil if @notfound.include? file
31
+ $:.each do |dir|
32
+ fullpath = File.join(dir, file)
33
+ if File.exists? fullpath
34
+ return fullpath
35
+ elsif File.exists?(fullpath + '.rb')
36
+ return fullpath + '.rb'
37
+ elsif File.exists?(fullpath + '.so')
38
+ return fullpath + '.so'
39
+ end
40
+ end
41
+ # puts "[JML::AutoReload] File #{file} not found!"
42
+ @notfound.add file
43
+ return nil
44
+ end
45
+
46
+ # Store the time stamp of a file.
47
+ def self.timestamp(file)
48
+ path = locate(file)
49
+ if path
50
+ file = normalize(path, file)
51
+ @timestamps[file] = File.mtime(path)
52
+ end
53
+ end
54
+
55
+ # Put the extension on a filename.
56
+ def self.normalize(path, file)
57
+ if File.extname(file) == ""
58
+ return file + File.extname(path)
59
+ else
60
+ return file
61
+ end
62
+ end
63
+
64
+ # Show all stored files and their timestamp.
65
+ def self.dump
66
+ pp @timestamps
67
+ end
68
+
69
+ # Reload a file. With force=true, file is reloaded in
70
+ # any case.
71
+ def self.reload(file, force=false)
72
+ path = locate(file)
73
+ file = normalize(path, file)
74
+
75
+ if force or (path and File.mtime(path) > @timestamps[file])
76
+ puts "[JML::AutoReload] reloading #{file}" if @verbose
77
+
78
+ # delete file from list of loaded modules, and reload
79
+ $".delete file
80
+ require file
81
+ return true
82
+ else
83
+ return false
84
+ end
85
+ end
86
+
87
+ # Reload all files which were required.
88
+ def self.reload_all(force=false)
89
+ @timestamps.each_key do |file|
90
+ self.reload(file, force)
91
+ end
92
+ check_directories
93
+ end
94
+
95
+ # Add directories to be watched.
96
+ def self.watch_directory(dir)
97
+ @watched_dirs << dir
98
+ end
99
+
100
+ # Reload any new files in the watched directories.
101
+ def self.check_directories
102
+ @watched_dirs.each do |dir|
103
+ Dir.glob("#{dir}/**/*.rb").each do |fn|
104
+ if @timestamps.include? fn
105
+ reload(fn)
106
+ else
107
+ require(fn)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ # Overwrite 'require' to register the time stamps instead.
116
+ module Kernel # :nodoc:
117
+ alias old_require require
118
+
119
+ def require(file)
120
+ Rish::Autoreload.timestamp(file)
121
+ old_require(file)
122
+ end
123
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2010 by Mikio L. Braun
2
+ # rish is distributed under a BSD-style license. See COPYING
3
+
4
+ module Rish
5
+ # Collects commands which are available trough extensions.
6
+ module Commands
7
+ module_function
8
+
9
+ # Show help for a command, or just the builtin commands.
10
+ def help(word=nil)
11
+ if word
12
+ puts %x{qri #{word}}
13
+ else
14
+ puts <<EOS
15
+ Rish builtin-commands
16
+
17
+ @p <expr> profile expression (requires --debug on JRuby)
18
+ ?<expr> show help for command expr (doesn't work so well on IRB :( )
19
+ @h show this help
20
+ !<cmd> Run cmd in a shell
21
+ !<cmd> & Run cmd in background
22
+ !&<cmd> Same as above (version for IRB which doesn't like an "&"
23
+ as last element on line)
24
+ EOS
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,63 @@
1
+ # Copyright (c) 2010 by Mikio L. Braun
2
+ # rish is distributed under a BSD-style license. See COPYING
3
+
4
+ require 'profiler'
5
+ require 'rish/autoreload'
6
+ require 'rish/commands'
7
+
8
+ module Rish
9
+ # The main Hook class.
10
+ #
11
+ # This adds the main functionality realized through before
12
+ # and after hooks.
13
+ #
14
+ # If you want to add further capabilities, here is the place to do
15
+ # so.
16
+ class Hook
17
+ @profile = false
18
+
19
+ # Called with each input line.
20
+ #
21
+ # If a special command is recognized, the input line is changed
22
+ # accordingly. For example, "!ls" becomes "%x{ls}"
23
+ def before(line)
24
+ Autoreload::reload_all
25
+ if line[0] == ?!
26
+ if line[1] == ?&
27
+ line = line[2..-1] + "&"
28
+ end
29
+ if line[-1] == ?&
30
+ "Thread.new { %x{#{line[1...-1]}} };"
31
+ else
32
+ "%x{#{line[1..-1]}}"
33
+ end
34
+ elsif line[0] == ?? and line.size > 2
35
+ "Rish::Commands.help \"#{line[1..-1]}\""
36
+ elsif line[0] == ?@
37
+ if line[1] == ?p
38
+ Profiler__::start_profile
39
+ @profile = true
40
+ line = line[2..-1]
41
+ elsif line[1] == ?h
42
+ 'Rish::Commands.help'
43
+ else
44
+ line
45
+ end
46
+ else
47
+ line
48
+ end
49
+ end
50
+
51
+ # Called on the result of an evaluation.
52
+ #
53
+ # Used here mainly to clean up after the profiler.
54
+ def after(result)
55
+ if @profile
56
+ Profiler__::stop_profile
57
+ Profiler__::print_profile($stdout)
58
+ @profile = false
59
+ end
60
+ result
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,188 @@
1
+ # Copyright (c) 2010 by Mikio L. Braun
2
+ # rish is distributed under a BSD-style license. See COPYING
3
+
4
+ require 'readline'
5
+ require 'pp'
6
+
7
+ module Rish
8
+ # Rish's own interactive mode.
9
+ #
10
+ # Similar to IRB, but much smaller. The advantage it has is that it
11
+ # doesn't look for line continuations and works better with rish's
12
+ # built-in commands (well, is this a feature or a bug?)
13
+ #
14
+ # Also, if the line ends in ";", the result is not plotted, just as in
15
+ # matlab.
16
+ class Interactive
17
+ # Start an interactive shell.
18
+ def run
19
+ read_history
20
+
21
+ #binding = Workspace.new.binding
22
+ binding = Object::TOPLEVEL_BINDING
23
+
24
+ Readline.completion_proc = proc do |s|
25
+ if /([a-zA-Z_]+)(::|\.)([^.]*)/ =~ s
26
+ prefix = $1
27
+ sep = $2
28
+ s = $3
29
+
30
+ candidates = get_candidates(binding, prefix + sep)
31
+ else
32
+ prefix = ''
33
+ sep = ''
34
+
35
+ candidates = get_candidates(binding)
36
+ end
37
+
38
+ candidates = candidates.select {|m| starts_with(m, s)}
39
+ candidates.map! {|c| prefix + sep + c}
40
+ end
41
+
42
+ thread = Thread.current
43
+
44
+ state = :input
45
+
46
+ trap('SIGINT') do
47
+ case state
48
+ when :input
49
+ puts "Exiting..."
50
+ thread.raise Interrupt
51
+ exit
52
+ when :calc
53
+ thread.raise Interrupt
54
+ end
55
+ end
56
+
57
+ lineno = 0
58
+ s = 'okay'
59
+ while s
60
+ lineno += 1
61
+ state = :input
62
+ s = Readline.readline("interactive:#{lineno}> ", true)
63
+ #print "interactive:#{lineno}> "
64
+ #s = gets.chomp
65
+ break if (s.nil? or s == 'exit' or s == 'quit')
66
+ begin
67
+ s = @hook.before(s) if @hook
68
+ state = :calc
69
+ result = eval(s, binding, 'interactive', lineno)
70
+ state = :input
71
+ result = @hook.after(result) if @hook
72
+ if s[-1] != ?;
73
+ print_result(result)
74
+ $ans = result
75
+ end
76
+ rescue SyntaxError => e
77
+ puts "SyntaxError: #{e}"
78
+ rescue StandardError, ScriptError => e
79
+ puts "#{e.class}: #{e.to_s[0..1000]}"
80
+ print_backtrace e.backtrace
81
+ rescue SignalException => e
82
+ puts "Caught #{e.class}"
83
+ print_backtrace e.backtrace
84
+ end
85
+ end
86
+
87
+ write_history
88
+ exit
89
+ end
90
+
91
+ # Add a hook which is called before and after each line.
92
+ #
93
+ # The hook object as to have a +before+ and +after+ method.
94
+ # The +before+ method is called with the input line
95
+ # and has to return it or a changed version. Likewise,
96
+ # the +after+ method gets the result of the evaluation and
97
+ # has to return the value or a changed version.
98
+ def hook=(h)
99
+ @hook = h
100
+ end
101
+
102
+ #######
103
+ private
104
+ #######
105
+
106
+ def history_file
107
+ File.join(ENV['HOME'], '.rish_history')
108
+ end
109
+
110
+ def print_result(result)
111
+ puts result.inspect unless result.nil?
112
+ end
113
+
114
+ def read_history
115
+ begin
116
+ open(history_file, 'r').each do |l|
117
+ Readline::HISTORY << l.chomp
118
+ end
119
+ rescue Errno::ENOENT
120
+ # do nothing
121
+ end
122
+ end
123
+
124
+ def write_history
125
+ open(history_file, 'w') do |f|
126
+ Readline::HISTORY.each {|l| f.puts l }
127
+ end
128
+ end
129
+
130
+ # For cleanin up JRuby's stack traces.
131
+ BACKTRACE_HIDE = [ 'sun/', 'java/', 'org/jruby', 'interactive', ':', __FILE__ ]
132
+
133
+ def print_backtrace(bt)
134
+ bt.each do |t|
135
+ unless BACKTRACE_HIDE.inject(false) do |flag,h|
136
+ flag ||= starts_with(t, h)
137
+ end
138
+ puts " #{t}"
139
+ end
140
+ end
141
+ end
142
+
143
+ def starts_with(long, short)
144
+ long[0...short.length] == short
145
+ end
146
+
147
+ def get_candidates(binding, where='')
148
+ candidates = []
149
+ %w(methods constants local_variables).each do |m|
150
+ begin
151
+ candidates += eval(where + m, binding)
152
+ rescue
153
+ end
154
+ end
155
+
156
+ if where == ''
157
+ eval('self.class.ancestors', binding).each do |an|
158
+ %w(methods constants).each do |m|
159
+ begin
160
+ candidates += an.send(m)
161
+ rescue
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ return candidates.sort.uniq
168
+ end
169
+ end
170
+ end
171
+
172
+ if __FILE__ == $0
173
+ include M
174
+
175
+ puts "Interactive with a before and after hook"
176
+ class MyInteractive < Interactive # :nodoc:
177
+ def process_line(line)
178
+ puts "This line is great: #{line}"
179
+ line
180
+ end
181
+
182
+ def process_result(result)
183
+ print " => "
184
+ result
185
+ end
186
+ end
187
+ MyInteractive.new.run
188
+ end
@@ -0,0 +1,104 @@
1
+ # Copyright (c) 2010 by Mikio L. Braun
2
+ # rish is distributed under a BSD-style license. See COPYING
3
+
4
+ require 'irb'
5
+
6
+ module IRB
7
+ # Start a rish-shell.
8
+ #
9
+ # This code is basically the same as IRB.start with the
10
+ # irb object replaced by our own version.
11
+ def IRB.rish_start(ap_path=nil, hook=nil)
12
+ $0 = File::basename(ap_path, ".rb") if ap_path
13
+
14
+ IRB.setup(ap_path)
15
+
16
+ if @CONF[:SCRIPT]
17
+ irb = Rish::Irb::RishIrb.new(nil, @CONF[:SCRIPT])
18
+ else
19
+ irb = Rish::Irb::RishIrb.new
20
+ end
21
+
22
+ irb.hook = hook
23
+
24
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
25
+ @CONF[:MAIN_CONTEXT] = irb.context
26
+
27
+ trap("SIGINT") do
28
+ irb.signal_handle
29
+ end
30
+
31
+ catch(:IRB_EXIT) do
32
+ irb.eval_input
33
+ end
34
+ # print "\n"
35
+ end
36
+ end
37
+
38
+ module Rish
39
+ # Rish's irb extensions.
40
+ #
41
+ # This module defines two classes derived from IRB::Irb and
42
+ # IRB::Context which basically add after and before hooks
43
+ # to the evaluation to implement automatic reloading and
44
+ # profiling.
45
+ module Irb
46
+ # Our version of IRB::Irb which uses a RishContext.
47
+ class RishIrb < IRB::Irb
48
+ # Create new object.
49
+ #
50
+ # Overwrites @context to RishContext.
51
+ def initialize(workspace = nil, input_method = nil, output_method = nil)
52
+ super(workspace, input_method, output_method)
53
+ @context = RishContext.new(self, workspace, input_method, output_method)
54
+ end
55
+
56
+ # Add a hook in the context object.
57
+ def hook=(h)
58
+ @context.hook = h
59
+ end
60
+ end
61
+
62
+ # Our version of IRB::Context which adds before and after hooks
63
+ # to evaluate.
64
+ class RishContext < IRB::Context
65
+ # Create new context object.
66
+
67
+ def initialize(irb, workspace = nil, input_method = nil, output_method = nil)
68
+ super(irb, workspace, input_method, output_method)
69
+ end
70
+
71
+ # Add a hook.
72
+ def hook=(h)
73
+ @hook = h
74
+ end
75
+
76
+ # Overwritten version of evaluate which calls the +before+ and
77
+ # +after+ methods of the hook, if present. +before+ gets the
78
+ # input line and may return a changed version, +after+ gets the
79
+ # result and may return a changed version.
80
+ def evaluate(line, line_no)
81
+ line = @hook.before(line) if @hook
82
+ val = super(line, line_no)
83
+ val = @hook.after(val) if @hook
84
+ val
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ if __FILE__ == $0
91
+ class RishHook # :nodoc:
92
+ def before(line)
93
+ puts "Before"
94
+ line
95
+ end
96
+
97
+ def after(val)
98
+ puts "After"
99
+ val
100
+ end
101
+ end
102
+
103
+ IRB.rish_start(nil, RishHook.new)
104
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2010 by Mikio L. Braun
2
+ # rish is distributed under a BSD-style license. See COPYING
3
+
4
+ require 'profiler'
5
+
6
+ require 'rish/hook'
7
+ require 'rish/irb'
8
+ require 'rish/interactive'
9
+
10
+ # The Rish main module.
11
+ #
12
+ # Call Rish.shell to start a shell.
13
+ module Rish
14
+ module_function
15
+
16
+ # Start a new Rish shell. Supported types are :irb and :rish.
17
+ def shell(type=:irb)
18
+ hook = Hook.new
19
+ case type
20
+ when :irb
21
+ IRB.rish_start(nil, hook)
22
+ when :rish
23
+ i = Interactive.new
24
+ i.hook = hook
25
+ i.run
26
+ end
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rish
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ version: "0.1"
9
+ platform: ruby
10
+ authors:
11
+ - Mikio L. Braun
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-04-14 00:00:00 +02:00
17
+ default_executable:
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: fastri
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 0
28
+ version: "0"
29
+ type: :runtime
30
+ version_requirements: *id001
31
+ description: |
32
+ An interactive Ruby shell with auto-reloading, shell-outs, profiling,
33
+ and integrated help.
34
+
35
+ email: mikiobraun@gmail.com
36
+ executables:
37
+ - rish
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - bin/rish
44
+ - lib/rish/commands.rb
45
+ - lib/rish/autoreload.rb
46
+ - lib/rish/irb.rb
47
+ - lib/rish/hook.rb
48
+ - lib/rish/interactive.rb
49
+ - lib/rish/shell.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/mikiobraun/rish
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options: []
56
+
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.6
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Ruby interactive shell
80
+ test_files: []
81
+