intar 2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/intar +145 -0
- data/lib/intar.rb +564 -0
- data/lib/intar/prompt.rb +128 -0
- data/lib/intar/redirect.rb +67 -0
- data/lib/intar/version.rb +10 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ebb1f8448fc1b1975a50bbc99cac9e601dfc5c8c949fc9c6bb3e2e9ccec5d556
|
4
|
+
data.tar.gz: ac7e7ac5915357dd60db8e958a4c58fc15050c3b04d52746a0d3e3d05d971d98
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5e983cbc3b2773a4f3fc8297407f59aa56fd5156adaa5af2c2e7657e5e3523f8b16bfa193e5f088e1a5d4b259838f63444b7dea35a564c8133e85969ec055205
|
7
|
+
data.tar.gz: bfeb03f9854a50f9f5c7d1ef7c658bd871a4f0896448009236c27f1125e2c6517e563c84b1c6a294c5289a7588e5f180e348af04b89e2949bcacc721be7fddcc
|
data/bin/intar
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# intar -- Interactive Ruby evaluation
|
5
|
+
#
|
6
|
+
|
7
|
+
require "intar"
|
8
|
+
require "appl"
|
9
|
+
|
10
|
+
|
11
|
+
class Intar::Appl < Application
|
12
|
+
|
13
|
+
NAME = "intar"
|
14
|
+
VERSION = Intar::VERSION
|
15
|
+
SUMMARY = "Interactive Ruby"
|
16
|
+
COPYRIGHT = "(C) 2008-2020 Bertram Scharpf <software@bertram-scharpf.de>"
|
17
|
+
LICENSE = "BSD"
|
18
|
+
AUTHOR = "Bertram Scharpf <software@bertram-scharpf.de>"
|
19
|
+
|
20
|
+
DESCRIPTION = <<~EOT
|
21
|
+
Prompt for Ruby statements, evaluate them. This is a replacement
|
22
|
+
for "irb". The underlying library may be entered from inside any
|
23
|
+
Ruby program.
|
24
|
+
|
25
|
+
Example:
|
26
|
+
|
27
|
+
$ intar -p '%(33 1)c%t%c%> '
|
28
|
+
|
29
|
+
EOT
|
30
|
+
|
31
|
+
attr_writer :show, :prompt, :histmax
|
32
|
+
def histfile= f ; @histfile = nil_if_none f ; end
|
33
|
+
def configfile= f ; @configfile = nil_if_none f ; end
|
34
|
+
attr_bang :quiet, :bw, :catch_exit, :histall
|
35
|
+
|
36
|
+
define_option "p", :prompt=, "STR", "prompt - see source code for % escapes"
|
37
|
+
alias_option "p", "prompt"
|
38
|
+
|
39
|
+
define_option "q", :quiet!, "don't show results"
|
40
|
+
alias_option "q", "quiet"
|
41
|
+
|
42
|
+
define_option "s", :show=, "N", "show result line limit (0=all)"
|
43
|
+
alias_option "s", "show"
|
44
|
+
|
45
|
+
define_option "r", :require, "FILE", "Ruby require"
|
46
|
+
alias_option "r", "require"
|
47
|
+
|
48
|
+
define_option "bw", :bw!, "black & white"
|
49
|
+
|
50
|
+
define_option "c", :configfile=, "FILE", ".intarrc",
|
51
|
+
"config file, NONE means none"
|
52
|
+
alias_option "c", "configfile"
|
53
|
+
|
54
|
+
define_option "H", :histfile=, "FILE", ".intar_history",
|
55
|
+
"history file, NONE means none"
|
56
|
+
alias_option "H", "histfile"
|
57
|
+
|
58
|
+
define_option "m", :histmax=, "NUM",
|
59
|
+
"maximum history entries to save"
|
60
|
+
alias_option "m", "histmax"
|
61
|
+
|
62
|
+
define_option "A", :histall!,
|
63
|
+
"pass lines starting with blanks to history"
|
64
|
+
alias_option "A", "histall"
|
65
|
+
|
66
|
+
define_option "E", :encoding=, "ENC", "set encoding (like ruby -E)"
|
67
|
+
alias_option "E", "encoding"
|
68
|
+
|
69
|
+
define_option "x", :catch_exit!, "Don't quit on SystemExit exception"
|
70
|
+
alias_option "x", "catchexit"
|
71
|
+
alias_option "x", "catch-exit"
|
72
|
+
|
73
|
+
define_option "h", :help, "show options"
|
74
|
+
alias_option "h", "help"
|
75
|
+
define_option "V", :version, "show version"
|
76
|
+
alias_option "V", "version"
|
77
|
+
|
78
|
+
@params = {}
|
79
|
+
class <<self
|
80
|
+
attr_reader :params
|
81
|
+
def set **kwargs
|
82
|
+
@params.update **kwargs
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def run
|
87
|
+
# @debug = true # Only development.
|
88
|
+
read_cfg
|
89
|
+
Intar.open **params do |i|
|
90
|
+
i.execute @args.shift while @args.notempty?
|
91
|
+
i.run
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def params
|
98
|
+
p = self.class.params
|
99
|
+
if @quiet then
|
100
|
+
p[ :show] = 0
|
101
|
+
elsif @show then
|
102
|
+
p[ :show] = Integer @show
|
103
|
+
end
|
104
|
+
p[ :prompt ] = @prompt if @prompt
|
105
|
+
p[ :color ] = false if @bw
|
106
|
+
p[ :catch_exit] = true if @catch_exit
|
107
|
+
p[ :histhid ] = false if @histall
|
108
|
+
p[ :histfile ] = @histfile if @histfile
|
109
|
+
p[ :histmax ] = Integer @histmax if @histmax
|
110
|
+
p
|
111
|
+
end
|
112
|
+
|
113
|
+
def encoding= ei
|
114
|
+
e, i = ei.split ":"
|
115
|
+
Encoding.default_external = e if e and not e.empty?
|
116
|
+
Encoding.default_internal = i if i and not i.empty?
|
117
|
+
[ $stdin, $stdout, $stderr].each do |io|
|
118
|
+
io.set_encoding e, i
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def read_cfg
|
123
|
+
return unless @configfile
|
124
|
+
c = File.expand_path @configfile, "~"
|
125
|
+
return unless File.exists? c
|
126
|
+
load c
|
127
|
+
rescue Exception
|
128
|
+
$@.pop 2
|
129
|
+
e = $@.shift
|
130
|
+
puts "#{e}: #$! (#{$!.class})"
|
131
|
+
$@.each { |l| puts "\t#{l}" }
|
132
|
+
raise "Error in config file #{c}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def nil_if_none var
|
136
|
+
case var
|
137
|
+
when "", "NONE" then nil
|
138
|
+
else var
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
Intar::Appl.run
|
145
|
+
|
data/lib/intar.rb
ADDED
@@ -0,0 +1,564 @@
|
|
1
|
+
#
|
2
|
+
# intar.rb -- Interactive Ruby evaluation
|
3
|
+
#
|
4
|
+
|
5
|
+
require "supplement"
|
6
|
+
require "supplement/terminal"
|
7
|
+
require "intar/version"
|
8
|
+
require "intar/prompt"
|
9
|
+
require "intar/redirect"
|
10
|
+
|
11
|
+
|
12
|
+
=begin rdoc
|
13
|
+
|
14
|
+
This could be opened not only by the Intar executable but also
|
15
|
+
everywhere inside your Ruby program.
|
16
|
+
|
17
|
+
= Example 1
|
18
|
+
|
19
|
+
require "intar"
|
20
|
+
Intar.run
|
21
|
+
|
22
|
+
|
23
|
+
= Example 2
|
24
|
+
|
25
|
+
require "intar"
|
26
|
+
a = "hello"
|
27
|
+
Intar.run a, prompt: "str(%(length)i):%03n%> "
|
28
|
+
|
29
|
+
|
30
|
+
= Example 3
|
31
|
+
|
32
|
+
require "intar"
|
33
|
+
Intar.open show: 3, histfile: ".intar_history-example" do |i|
|
34
|
+
i.execute "puts inspect"
|
35
|
+
i.run
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
=end
|
40
|
+
|
41
|
+
|
42
|
+
class Object
|
43
|
+
def intar_binding
|
44
|
+
binding
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
class Intar
|
50
|
+
|
51
|
+
class <<self
|
52
|
+
|
53
|
+
def open obj = nil, **params
|
54
|
+
i = new obj, **params
|
55
|
+
yield i
|
56
|
+
end
|
57
|
+
|
58
|
+
def run obj = nil, **params
|
59
|
+
open obj, **params do |i| i.run end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
DEFAULTS = {
|
66
|
+
prompt: "%(32)c%i%c:%1c%03n%c%> ",
|
67
|
+
color: true,
|
68
|
+
show: 1,
|
69
|
+
shownil: false,
|
70
|
+
pager: nil,
|
71
|
+
catch_exit: false,
|
72
|
+
histhid: true,
|
73
|
+
histfile: nil,
|
74
|
+
histmax: 500,
|
75
|
+
}
|
76
|
+
|
77
|
+
def initialize obj = nil, **params
|
78
|
+
@obj = obj.nil? ? (eval "self", TOPLEVEL_BINDING) : obj
|
79
|
+
@params = DEFAULTS.dup.update params
|
80
|
+
@binding = @obj.intar_binding
|
81
|
+
@n = 0
|
82
|
+
@prompt = Prompt.new
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
class Quit < Exception ; end
|
87
|
+
class Failed < Exception ; end
|
88
|
+
|
89
|
+
def run
|
90
|
+
prompt_load_history
|
91
|
+
oldset = eval OLDSET, @binding
|
92
|
+
while l = readline do
|
93
|
+
begin
|
94
|
+
@redir = find_redirect l
|
95
|
+
r = if l =~ /\A\\(\w+|.)\s*(.*?)\s*\Z/ then
|
96
|
+
m = get_metacommand $1
|
97
|
+
send m.method, (eval_param $2)
|
98
|
+
else
|
99
|
+
begin
|
100
|
+
@redir.redirect_output do eval l, @binding, @file end
|
101
|
+
rescue SyntaxError
|
102
|
+
raise if l.end_with? $/
|
103
|
+
@previous = l
|
104
|
+
next
|
105
|
+
end
|
106
|
+
end
|
107
|
+
rescue Quit
|
108
|
+
break
|
109
|
+
rescue Failed
|
110
|
+
switchcolor 31, 1
|
111
|
+
puts $!
|
112
|
+
switchcolor
|
113
|
+
r = $!
|
114
|
+
rescue Exception
|
115
|
+
raise if SystemExit === $! and not @params[ :catch_exit]
|
116
|
+
show_exception
|
117
|
+
r = $!
|
118
|
+
else
|
119
|
+
display r
|
120
|
+
end
|
121
|
+
oldset.call r, @n
|
122
|
+
end
|
123
|
+
prompt_save_history
|
124
|
+
end
|
125
|
+
|
126
|
+
def execute code
|
127
|
+
eval code, @binding, "#{self.class}/execute"
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def find_redirect line
|
134
|
+
RedirectPipe.detect line, @params[ :pager] or
|
135
|
+
RedirectFile.detect line, @params[ :output] or
|
136
|
+
RedirectNone.new
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
OLDSET = <<~EOT
|
141
|
+
_, __, ___ = nil, nil, nil
|
142
|
+
proc { |r,n|
|
143
|
+
Array === __ or __ = []
|
144
|
+
Hash === ___ or ___ = {}
|
145
|
+
unless r.nil? or r.equal? __ or r.equal? ___ then
|
146
|
+
_ = r
|
147
|
+
__.unshift r
|
148
|
+
___[ n] = r
|
149
|
+
end
|
150
|
+
}
|
151
|
+
EOT
|
152
|
+
|
153
|
+
autoload :Etc, "etc"
|
154
|
+
autoload :Socket, "socket"
|
155
|
+
|
156
|
+
def cur_prompt prev
|
157
|
+
p = @params[ :prompt].to_s
|
158
|
+
p.gsub /%(?:
|
159
|
+
\(([^\)]+)?\)
|
160
|
+
|
|
161
|
+
([+-]?[0-9]+(?:\.[0-9]+)?)
|
162
|
+
)?(.)/nx do
|
163
|
+
case $3
|
164
|
+
when "s" then @obj.to_s
|
165
|
+
when "i" then $1 ? (@obj.send $1) : @obj.inspect
|
166
|
+
when "n" then "%#$2d" % @n
|
167
|
+
when "t" then Time.now.strftime $1||"%X"
|
168
|
+
when "u" then Etc.getpwuid.name
|
169
|
+
when "h" then Socket.gethostname
|
170
|
+
when "w" then cwd_short
|
171
|
+
when "W" then File.basename cwd_short
|
172
|
+
when "c" then color *($1 || $2 || "").split.map { |x| x.to_i }
|
173
|
+
when ">" then prev ? "." : Process.uid == 0 ? "#" : ">"
|
174
|
+
when "%" then $3
|
175
|
+
else $&
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def color *c
|
181
|
+
if @params[ :color] then
|
182
|
+
s = c.map { |i| "%d" % i }.join ";"
|
183
|
+
"\e[#{s}m"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def switchcolor *c
|
188
|
+
s = color *c
|
189
|
+
print s
|
190
|
+
end
|
191
|
+
|
192
|
+
def cwd_short
|
193
|
+
r = Dir.pwd
|
194
|
+
h = Etc.getpwuid.dir
|
195
|
+
r[ 0, h.length] == h and r[ 0, h.length] = "~"
|
196
|
+
r
|
197
|
+
end
|
198
|
+
|
199
|
+
def readline
|
200
|
+
r, @previous = @previous, nil
|
201
|
+
r or @n += 1
|
202
|
+
begin
|
203
|
+
cp = cur_prompt r
|
204
|
+
l = @prompt.ask cp
|
205
|
+
return if l.nil?
|
206
|
+
@prompt.push l unless !r and @params[ :histhid] and l =~ /\A[ \t]+/
|
207
|
+
if r then
|
208
|
+
r << $/ << l
|
209
|
+
else
|
210
|
+
r = l unless l.empty?
|
211
|
+
end
|
212
|
+
cp.strip!
|
213
|
+
cp.gsub! /\e\[[0-9]*(;[0-9]*)*m/, ""
|
214
|
+
@file = "#{self.class}/#{cp}"
|
215
|
+
end until r
|
216
|
+
switchcolor
|
217
|
+
r
|
218
|
+
end
|
219
|
+
|
220
|
+
# :stopdoc:
|
221
|
+
ARROW = "=> "
|
222
|
+
ELLIPSIS = "..."
|
223
|
+
# :startdoc:
|
224
|
+
|
225
|
+
def display r
|
226
|
+
return if r.nil? and not @params[ :shownil]
|
227
|
+
s = @params[ :show]
|
228
|
+
s or return
|
229
|
+
s = s.to_i rescue 0
|
230
|
+
i = ARROW.dup
|
231
|
+
i << r.inspect
|
232
|
+
if s > 0 then
|
233
|
+
siz, = $stdout.wingeom
|
234
|
+
siz *= s
|
235
|
+
if i.length > siz then
|
236
|
+
i.cut! siz-ELLIPSIS.length
|
237
|
+
i << ELLIPSIS
|
238
|
+
end
|
239
|
+
end
|
240
|
+
puts i
|
241
|
+
end
|
242
|
+
|
243
|
+
def show_exception
|
244
|
+
unless $!.to_s.empty? then
|
245
|
+
switchcolor 31, 1
|
246
|
+
print $!
|
247
|
+
print " " unless $!.to_s =~ /\s\z/
|
248
|
+
end
|
249
|
+
switchcolor 31, 22
|
250
|
+
puts "(#{$!.class})"
|
251
|
+
switchcolor 33
|
252
|
+
$@.each { |b|
|
253
|
+
r = b.starts_with? __FILE__
|
254
|
+
break if r and (b.rest r) =~ /\A:\d+:/
|
255
|
+
puts b
|
256
|
+
}
|
257
|
+
switchcolor
|
258
|
+
end
|
259
|
+
|
260
|
+
def eval_param l
|
261
|
+
eot = "EOT0001"
|
262
|
+
eot.succ! while l[ eot]
|
263
|
+
l = eval "<<#{eot}\n#{l}\n#{eot}", @binding, @file
|
264
|
+
l.strip!
|
265
|
+
l.notempty?
|
266
|
+
end
|
267
|
+
|
268
|
+
def prompt_load_history
|
269
|
+
@prompt.load_history @params[ :histfile]
|
270
|
+
end
|
271
|
+
def prompt_save_history
|
272
|
+
@prompt.save_history @params[ :histfile], @params[ :histmax]
|
273
|
+
end
|
274
|
+
def prompt_scan_history
|
275
|
+
@prompt.scan_history do |l|
|
276
|
+
next if l =~ /\A\\/
|
277
|
+
yield l
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
|
282
|
+
|
283
|
+
Metacmds = Struct[ :method, :summary, :description]
|
284
|
+
@metacmds = {}
|
285
|
+
class <<self
|
286
|
+
attr_reader :metacmds
|
287
|
+
private
|
288
|
+
def method_added sym
|
289
|
+
if @mcd then
|
290
|
+
names, summary, desc = *@mcd
|
291
|
+
m = Metacmds[ sym, summary, desc]
|
292
|
+
names.each { |n|
|
293
|
+
@metacmds[ n] = m
|
294
|
+
}
|
295
|
+
@mcd = nil
|
296
|
+
end
|
297
|
+
end
|
298
|
+
def metacmd names, summary, desc
|
299
|
+
@mcd = [ names, summary, desc]
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def get_metacommand name
|
304
|
+
self.class.metacmds[ name] or raise Failed, "Unknown Metacommand: #{name}"
|
305
|
+
end
|
306
|
+
|
307
|
+
metacmd %w(? h help), "Help for metacommands", <<~EOT
|
308
|
+
List of Metacommands or help on a specific command, if given.
|
309
|
+
EOT
|
310
|
+
def cmd_help x
|
311
|
+
@redir.redirect_output do
|
312
|
+
if x then
|
313
|
+
mc = get_metacommand x
|
314
|
+
names = cmds_list[ mc]
|
315
|
+
puts "Metacommand: #{names.join ' '}"
|
316
|
+
puts "Summary: #{mc.summary}"
|
317
|
+
puts "Description:"
|
318
|
+
puts mc.description
|
319
|
+
else
|
320
|
+
l = cmds_list.map { |k,v| [v,k] }
|
321
|
+
puts "Metacommands:"
|
322
|
+
l.each { |names,mc|
|
323
|
+
puts " %-20s %s" % [ (names.join " "), mc.summary]
|
324
|
+
}
|
325
|
+
end
|
326
|
+
end
|
327
|
+
nil
|
328
|
+
end
|
329
|
+
def cmds_list
|
330
|
+
l = Hash.new { |h,k| h[k] = [] }
|
331
|
+
self.class.metacmds.each_pair { |k,v|
|
332
|
+
l[ v].push k
|
333
|
+
}
|
334
|
+
l
|
335
|
+
end
|
336
|
+
|
337
|
+
metacmd %w(v version), "Version information", <<~EOT
|
338
|
+
Print version number.
|
339
|
+
EOT
|
340
|
+
def cmd_version x
|
341
|
+
@redir.redirect_output do
|
342
|
+
puts "#{self.class} #{VERSION}"
|
343
|
+
VERSION
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
metacmd %w(q x quit exit), "Quit Intar", <<~EOT
|
348
|
+
Leave Intar.
|
349
|
+
EOT
|
350
|
+
def cmd_quit x
|
351
|
+
raise Quit
|
352
|
+
end
|
353
|
+
|
354
|
+
metacmd %w(cd), "Change directory", <<~EOT
|
355
|
+
Switch to a different working directory.
|
356
|
+
Former directories will be kept in a stack.
|
357
|
+
|
358
|
+
%[N] exchange with stack item #N
|
359
|
+
=[N] drop and set to stack item #N
|
360
|
+
-[N] drop stack item #N
|
361
|
+
|
362
|
+
Default N is 1.
|
363
|
+
EOT
|
364
|
+
def cmd_cd x
|
365
|
+
@wds ||= []
|
366
|
+
y = Dir.getwd
|
367
|
+
if x =~ /\A([%=-])?(\d+)?\z/ then
|
368
|
+
x = $2 ? (@wds.delete_at -$2.to_i) : @wds.pop
|
369
|
+
x or raise Failed, ($2 ? "No directory ##$2." : "No last directory.")
|
370
|
+
case $1
|
371
|
+
when "-" then x = nil
|
372
|
+
when "=" then y = nil
|
373
|
+
end
|
374
|
+
end
|
375
|
+
if x then
|
376
|
+
x = File.expand_path x
|
377
|
+
Dir.chdir x rescue raise Failed, $!.to_s
|
378
|
+
@wds.push y if y
|
379
|
+
y = Dir.getwd
|
380
|
+
@wds.delete y
|
381
|
+
end
|
382
|
+
@redir.redirect_output do
|
383
|
+
i = @wds.length
|
384
|
+
@wds.each { |d|
|
385
|
+
puts "%2d %s" % [ i, d]
|
386
|
+
i -= 1
|
387
|
+
}
|
388
|
+
end
|
389
|
+
y
|
390
|
+
end
|
391
|
+
|
392
|
+
metacmd %w($ env), "Set environment variable", <<~EOT
|
393
|
+
Set or display an environment variable.
|
394
|
+
EOT
|
395
|
+
def cmd_env x
|
396
|
+
if x then
|
397
|
+
cmds_split_assign x do |n,v|
|
398
|
+
if v then
|
399
|
+
v =~ /\A"((?:[^\\"]|\\.)*)"\z/ and v = ($1.gsub /\\(.)/, "\\1")
|
400
|
+
ENV[ n] = v
|
401
|
+
else
|
402
|
+
ENV[ n]
|
403
|
+
end
|
404
|
+
end
|
405
|
+
else
|
406
|
+
@redir.redirect_output do
|
407
|
+
ENV.keys.sort.each { |n|
|
408
|
+
puts "#{n}=#{ENV[ n]}"
|
409
|
+
}
|
410
|
+
nil
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
def cmds_split_assign x
|
415
|
+
n, v = x.split /\s*=\s*|\s+/, 2
|
416
|
+
yield n, v.notempty?
|
417
|
+
end
|
418
|
+
|
419
|
+
metacmd %w(! sh shell), "Run shell command", <<~EOT
|
420
|
+
Run a shell command or a subshell.
|
421
|
+
EOT
|
422
|
+
def cmd_shell x
|
423
|
+
@redir.redirect_output do
|
424
|
+
system x||ENV[ "SHELL"]||"/bin/sh" or
|
425
|
+
raise Failed, "Exit code: #{$?.exitstatus}"
|
426
|
+
end
|
427
|
+
nil
|
428
|
+
end
|
429
|
+
|
430
|
+
metacmd %w(p param), "Set parameter", <<~EOT
|
431
|
+
Set or display a parameter
|
432
|
+
EOT
|
433
|
+
def cmd_param x
|
434
|
+
if x then
|
435
|
+
cmds_split_assign x do |n,v|
|
436
|
+
if v then
|
437
|
+
@params[ n.to_sym] = parse_param v
|
438
|
+
else
|
439
|
+
@params[ n.to_sym]
|
440
|
+
end
|
441
|
+
end
|
442
|
+
else
|
443
|
+
@redir.redirect_output do
|
444
|
+
@params.keys.sort.each { |n|
|
445
|
+
puts "#{n}=#{@params[n].inspect}"
|
446
|
+
}
|
447
|
+
nil
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
def parse_param v
|
452
|
+
case v
|
453
|
+
when /\At(?:rue)?\z/i, /\Ayes\z/i, /\Aon\z/i then true
|
454
|
+
when /\Af(?:alse)?\z/i, /\Ano\z/i, /\Aoff\z/i then false
|
455
|
+
when /\A(?:nil|none|-)\z/i then nil
|
456
|
+
when /\A(?:[+-])?\d+\z/i then $&.to_i
|
457
|
+
when /\A"((?:[^\\"]|\\.)*)"\z/ then $1.gsub /\\(.)/, "\\1"
|
458
|
+
else v
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
metacmd %w(< i input), "Load Ruby file", <<~EOT
|
463
|
+
Load a Ruby file and eval its contents.
|
464
|
+
EOT
|
465
|
+
def cmd_input x
|
466
|
+
x or raise Failed, "No input file given."
|
467
|
+
l = File.read x rescue raise Failed, $!.to_s
|
468
|
+
@redir.redirect_output do
|
469
|
+
eval l, @binding, x
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
metacmd %w(> o output), "Output to file", <<~EOT
|
474
|
+
Append output to a file.
|
475
|
+
EOT
|
476
|
+
def cmd_output x
|
477
|
+
if x then
|
478
|
+
File.open x, "w" do end rescue raise Failed, "File error: #$!"
|
479
|
+
end
|
480
|
+
@params[ :output] = x
|
481
|
+
end
|
482
|
+
|
483
|
+
metacmd %w(e edit), "Edit last command", <<~EOT
|
484
|
+
Take last command line from the history and open it in an editor.
|
485
|
+
Then execute the edited line.
|
486
|
+
EOT
|
487
|
+
def cmd_edit x
|
488
|
+
fn = tempname
|
489
|
+
x = Regexp.new x if x
|
490
|
+
p = prompt_scan_history { |l| break l if not x or l =~ x }
|
491
|
+
File.open fn, "w" do |f| f.write p end
|
492
|
+
begin
|
493
|
+
system ENV[ "EDITOR"]||ENV[ "VISUAL"]||"vi", fn or
|
494
|
+
raise Failed, "Executing editor failed: #{$?.exitstatus}"
|
495
|
+
p = File.read fn
|
496
|
+
p.strip!
|
497
|
+
@prompt.push p
|
498
|
+
@redir.redirect_output do eval p, @binding, @file end
|
499
|
+
ensure
|
500
|
+
File.unlink fn
|
501
|
+
end
|
502
|
+
end
|
503
|
+
def tempname
|
504
|
+
t = Time.now.strftime "%Y%m%d-%H%M%S"
|
505
|
+
File.expand_path "intar-#{t}-#$$.rb", ENV[ "TMPDR"]||"/tmp"
|
506
|
+
end
|
507
|
+
|
508
|
+
metacmd %w(^ hist history), "Manage history", <<~EOT
|
509
|
+
l load Load history
|
510
|
+
s save Save history
|
511
|
+
/ Search in history
|
512
|
+
[N [M]] Show last N items (skip M)
|
513
|
+
EOT
|
514
|
+
def cmd_history x
|
515
|
+
case x
|
516
|
+
when "l", "load" then prompt_load_history
|
517
|
+
when "s", "save" then prompt_save_history
|
518
|
+
when %r(\A(\d+)?/\s*) then search_history $1, $'
|
519
|
+
when %r(\A(\d+)(?:\s+(\d+))?\s*\z) then show_history $1.to_i, $2.to_i
|
520
|
+
when nil then show_history 5, 0
|
521
|
+
else raise Failed, "Unknown history command: #{x}"
|
522
|
+
end
|
523
|
+
end
|
524
|
+
def search_history num, pat
|
525
|
+
r = Regexp.new pat
|
526
|
+
num ||= 1
|
527
|
+
num = num.to_i.nonzero?
|
528
|
+
extract_from_history { |l|
|
529
|
+
if l =~ r then
|
530
|
+
if num then
|
531
|
+
break unless num > 0
|
532
|
+
num -= 1
|
533
|
+
end
|
534
|
+
next l
|
535
|
+
end
|
536
|
+
}
|
537
|
+
end
|
538
|
+
def show_history n, m
|
539
|
+
i, j = 0, 0
|
540
|
+
extract_from_history { |l|
|
541
|
+
i += 1
|
542
|
+
if i > m then
|
543
|
+
j += 1
|
544
|
+
break if j > n
|
545
|
+
next l
|
546
|
+
end
|
547
|
+
}
|
548
|
+
end
|
549
|
+
def extract_from_history
|
550
|
+
a = []
|
551
|
+
prompt_scan_history do |l|
|
552
|
+
n = yield l
|
553
|
+
a.push n if n
|
554
|
+
end
|
555
|
+
ensure
|
556
|
+
@redir.redirect_output do
|
557
|
+
while (p = a.pop) do
|
558
|
+
puts p
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
end
|
564
|
+
|
data/lib/intar/prompt.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
#
|
2
|
+
# intar/prompt.rb -- Prompt for Intar
|
3
|
+
#
|
4
|
+
|
5
|
+
require "supplement"
|
6
|
+
require "readline"
|
7
|
+
|
8
|
+
|
9
|
+
class Intar
|
10
|
+
|
11
|
+
class Prompt
|
12
|
+
|
13
|
+
def initialize histfile: nil, limit: nil
|
14
|
+
@limit = limit.nonzero?
|
15
|
+
Readline::HISTORY.clear
|
16
|
+
@new = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def ask prompt
|
20
|
+
l = Readline.readline prompt
|
21
|
+
rescue Interrupt
|
22
|
+
puts "^C -- #{$!.inspect}"
|
23
|
+
retry
|
24
|
+
end
|
25
|
+
|
26
|
+
def last
|
27
|
+
Readline::HISTORY[-1] unless Readline::HISTORY.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def scan_history
|
31
|
+
i = Readline::HISTORY.length
|
32
|
+
while i > 0 do
|
33
|
+
i -= 1
|
34
|
+
l = Readline::HISTORY[i]
|
35
|
+
yield l
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def push item
|
40
|
+
item.empty? and return
|
41
|
+
last != item or return
|
42
|
+
Readline::HISTORY.push item
|
43
|
+
@new += 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def load_history filepath
|
47
|
+
with_filepath filepath do |p|
|
48
|
+
read_file_if p do |f|
|
49
|
+
h = []
|
50
|
+
@new.times { h.push Readline::HISTORY.pop }
|
51
|
+
Readline::HISTORY.clear
|
52
|
+
f.each_line { |l|
|
53
|
+
l.chomp!
|
54
|
+
l.sub! "\r", "\n"
|
55
|
+
Readline::HISTORY.push l
|
56
|
+
}
|
57
|
+
Readline::HISTORY.push h.pop while h.any?
|
58
|
+
end
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def save_history filepath, maxsize
|
64
|
+
with_filepath filepath do |p|
|
65
|
+
lock_histfile p do
|
66
|
+
old, m = [], maxsize-@new
|
67
|
+
read_file_if p do |f|
|
68
|
+
f.each_line { |l|
|
69
|
+
old.size >= m and old.shift
|
70
|
+
old.push l
|
71
|
+
}
|
72
|
+
end
|
73
|
+
File.open p, "w" do |f|
|
74
|
+
old.each { |l| f.puts l }
|
75
|
+
i = Readline::HISTORY.length - @new
|
76
|
+
while i < Readline::HISTORY.length do
|
77
|
+
l = Readline::HISTORY[ i].sub "\n", "\r"
|
78
|
+
f.puts l
|
79
|
+
i += 1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def limit_history max
|
88
|
+
n = Readline::HISTORY.length - max
|
89
|
+
n.times { Readline::HISTORY.shift }
|
90
|
+
@new > max and @new = max
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def with_filepath filepath
|
97
|
+
if filepath then
|
98
|
+
p = File.expand_path filepath, "~"
|
99
|
+
yield p
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def read_file_if filepath
|
104
|
+
if File.exist? filepath then
|
105
|
+
File.open filepath do |f|
|
106
|
+
yield f
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def lock_histfile filepath
|
112
|
+
l = "#{filepath}.lock"
|
113
|
+
loop do
|
114
|
+
File.open l, File::CREAT|File::EXCL do end
|
115
|
+
break
|
116
|
+
rescue Errno::EEXIST
|
117
|
+
puts "Lockfile #{l} exists."
|
118
|
+
sleep 1
|
119
|
+
end
|
120
|
+
yield
|
121
|
+
ensure
|
122
|
+
File.unlink l
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#
|
2
|
+
# intar/redirect.rb -- Output redirection for Intar
|
3
|
+
#
|
4
|
+
|
5
|
+
require "supplement"
|
6
|
+
|
7
|
+
|
8
|
+
class Intar
|
9
|
+
|
10
|
+
class RedirectNone
|
11
|
+
def redirect_output
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Redirect
|
17
|
+
def redirect_output
|
18
|
+
out = outfile
|
19
|
+
stdin, stdout = $stdin.dup, $stdout.dup
|
20
|
+
$stdin .reopen "/dev/null"
|
21
|
+
$stdout.reopen out
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
$stdin .reopen stdin
|
25
|
+
$stdout.reopen stdout
|
26
|
+
out.close
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class RedirectPipe < Redirect
|
31
|
+
class <<self
|
32
|
+
def detect line, pager
|
33
|
+
if line.slice! /\s+\|\z/ then
|
34
|
+
new pager
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
def initialize pager
|
39
|
+
@pager = pager||ENV[ "PAGER"]||"more"
|
40
|
+
end
|
41
|
+
def outfile
|
42
|
+
IO.popen @pager.to_s, "w" rescue raise Failed, "Pipe error: #$!"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class RedirectFile < Redirect
|
47
|
+
class <<self
|
48
|
+
def detect line, outfile
|
49
|
+
if line.slice! /\s+>(>)?(\S+|"((?:[^\\"]|\\.)*)")\z/ then
|
50
|
+
p = $3 ? ($3.gsub /\\(.)/, "\\1") : $2
|
51
|
+
append = true if $1
|
52
|
+
new p, append
|
53
|
+
elsif outfile then
|
54
|
+
new outfile.to_s, true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
def initialize path, append
|
59
|
+
@path, @append = path, append
|
60
|
+
end
|
61
|
+
def outfile
|
62
|
+
File.open @path, (@append ? "a" : "w") rescue raise Failed, "File error: #$!"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: intar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '2.0'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bertram Scharpf
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: appl
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: supplement
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2'
|
41
|
+
description: 'This is a lean replacement for Irb.
|
42
|
+
|
43
|
+
'
|
44
|
+
email: "<software@bertram-scharpf.de>"
|
45
|
+
executables:
|
46
|
+
- intar
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- bin/intar
|
51
|
+
- lib/intar.rb
|
52
|
+
- lib/intar/prompt.rb
|
53
|
+
- lib/intar/redirect.rb
|
54
|
+
- lib/intar/version.rb
|
55
|
+
homepage: http://www.bertram-scharpf.de
|
56
|
+
licenses:
|
57
|
+
- BSD-2-Clause
|
58
|
+
metadata: {}
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements:
|
74
|
+
- Ruby and some small Gems; Readline
|
75
|
+
rubygems_version: 3.0.8
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: Interactive Ruby
|
79
|
+
test_files: []
|