intar 2.0
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.
- 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: []
|