appl 1.2
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 +156 -0
- data/doc/demoappl +69 -0
- data/lib/appl.rb +302 -0
- data/lib/intar.rb +451 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c17ebbb3f574a1d27df9610144f9881c3b8ef910
|
4
|
+
data.tar.gz: 6e68ffdb0dd1e1c227dc371f29e9d93fb7a1abef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c16e0eb7be520d7eace0ab904c838d61ab3eaa29b34d3f2fda9d5c23b35f03721f244ffb814389399bb6f1a175ba68cdd39a2b9fefb7f4cc7bb4f84ce8dbeef0
|
7
|
+
data.tar.gz: 98cf8b5bde3d91115a0763e0561718f04a99d560134d52448f68384662af9814cad6fdc44a283352e00897cf323fd450e4f1c9e77de7c052f9a75ab7dbc4ae51
|
data/bin/intar
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# intar -- Interactive Ruby evaluation
|
5
|
+
#
|
6
|
+
|
7
|
+
require "intar"
|
8
|
+
require "appl"
|
9
|
+
|
10
|
+
|
11
|
+
class IntarApp < Application
|
12
|
+
|
13
|
+
NAME = "intar"
|
14
|
+
VERSION = APPL_VERSION
|
15
|
+
SUMMARY = "Interactive Ruby"
|
16
|
+
COPYRIGHT = "(C) 2008-2013 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
|
+
def quiet!
|
32
|
+
Intar.show = nil
|
33
|
+
end
|
34
|
+
def show= s
|
35
|
+
Intar.show = s.to_i
|
36
|
+
end
|
37
|
+
def prompt= p
|
38
|
+
Intar.prompt = p if p
|
39
|
+
end
|
40
|
+
def bw!
|
41
|
+
Intar.colour = false
|
42
|
+
end
|
43
|
+
def catch_exit!
|
44
|
+
Intar.catch_exit = (Intar.catch_exit||0) + 3
|
45
|
+
end
|
46
|
+
def histall!
|
47
|
+
Intar.histhid = false
|
48
|
+
end
|
49
|
+
def histfile= h
|
50
|
+
Intar.histfile = nil_if_none h
|
51
|
+
end
|
52
|
+
def histmax= m
|
53
|
+
Intar.histmax = (Integer m rescue m.to_i)
|
54
|
+
end
|
55
|
+
def configfile= c
|
56
|
+
@configfile = nil_if_none c
|
57
|
+
end
|
58
|
+
|
59
|
+
define_option "p", :prompt=, "STR",
|
60
|
+
"prompt - see source code for % escapes"
|
61
|
+
alias_option "p", "prompt"
|
62
|
+
|
63
|
+
define_option "q", :quiet!, "don't show results"
|
64
|
+
alias_option "q", "quiet"
|
65
|
+
|
66
|
+
define_option "S", :show=, "N", 1, "show result line limit (0=all)"
|
67
|
+
alias_option "S", "show"
|
68
|
+
|
69
|
+
define_option "r", :require, "FILE", "Ruby require"
|
70
|
+
alias_option "r", "require"
|
71
|
+
|
72
|
+
define_option "bw", :bw!, "black & white"
|
73
|
+
|
74
|
+
define_option "c", :configfile=, "FILE", ".intarrc",
|
75
|
+
"config file, NONE means none"
|
76
|
+
alias_option "c", "configfile"
|
77
|
+
|
78
|
+
define_option "H", :histfile=, "FILE", ".intar_history",
|
79
|
+
"history file, NONE means none"
|
80
|
+
alias_option "H", "histfile"
|
81
|
+
|
82
|
+
define_option "m", :histmax=, "NUM",
|
83
|
+
"maximum history entries to save"
|
84
|
+
alias_option "m", "histmax"
|
85
|
+
|
86
|
+
define_option "A", :histall!,
|
87
|
+
"pass lines starting with blanks to history"
|
88
|
+
alias_option "A", "histall"
|
89
|
+
|
90
|
+
define_option "E", :encoding=, "ENC", "set encoding (like ruby -E)"
|
91
|
+
alias_option "E", "encoding"
|
92
|
+
|
93
|
+
define_option "x", :catch_exit!,
|
94
|
+
"On exit exception: Wait 3 seconds for interrupt"
|
95
|
+
alias_option "x", "catchexit"
|
96
|
+
alias_option "x", "catch-exit"
|
97
|
+
|
98
|
+
define_option "h", :help, "show options"
|
99
|
+
alias_option "h", "help"
|
100
|
+
define_option "V", :version, "show version"
|
101
|
+
alias_option "V", "version"
|
102
|
+
|
103
|
+
def run
|
104
|
+
# @debug = true # Only development.
|
105
|
+
read_cfg
|
106
|
+
main = eval "self", TOPLEVEL_BINDING
|
107
|
+
Intar.open main do |i|
|
108
|
+
i.run *(@args.shift @args.length)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def encoding= ei
|
115
|
+
e, i = ei.split ":"
|
116
|
+
Encoding.default_external = e if e and not e.empty?
|
117
|
+
Encoding.default_internal = i if i and not i.empty?
|
118
|
+
[ $stdin, $stdout, $stderr].each do |io|
|
119
|
+
io.set_encoding e, i
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def read_cfg
|
125
|
+
c = @configfile
|
126
|
+
return unless c
|
127
|
+
unless c[ File::SEPARATOR] or (File.exists? c) then
|
128
|
+
c = File.expand_path c, "~"
|
129
|
+
return unless File.exists? c
|
130
|
+
end
|
131
|
+
r = File.read c
|
132
|
+
r.prepend "$SAFE = 1#$/"
|
133
|
+
Thread.new {
|
134
|
+
begin
|
135
|
+
eval r, TOPLEVEL_BINDING
|
136
|
+
rescue Exception
|
137
|
+
$@.pop 2
|
138
|
+
e = $@.shift
|
139
|
+
puts "#{e}: #$! (#{$!.class})"
|
140
|
+
$@.each { |l| puts "\t#{l}" }
|
141
|
+
raise "Error in config file #{c}"
|
142
|
+
end
|
143
|
+
}.value
|
144
|
+
end
|
145
|
+
|
146
|
+
def nil_if_none var
|
147
|
+
case var
|
148
|
+
when "", "NONE" then nil
|
149
|
+
else var
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
IntarApp.run
|
156
|
+
|
data/doc/demoappl
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# demoappl -- Just test the Application class
|
5
|
+
#
|
6
|
+
|
7
|
+
require "appl"
|
8
|
+
|
9
|
+
class DemoAppl < Application
|
10
|
+
|
11
|
+
NAME = "demoappl"
|
12
|
+
VERSION = "1.0"
|
13
|
+
SUMMARY = "Just test the Application class"
|
14
|
+
COPYRIGHT = "(C) 2011 Bertram Scharpf"
|
15
|
+
LICENSE = "For internal use only"
|
16
|
+
AUTHOR = "Bertram Scharpf <software@bertram-scharpf.de>"
|
17
|
+
|
18
|
+
DESCRIPTION = <<EOT
|
19
|
+
This is an example how to define an Application subclass.
|
20
|
+
The program just prints out its parameters.
|
21
|
+
|
22
|
+
EOT
|
23
|
+
|
24
|
+
OPTIONS_ENV = "DEMO_OPTS"
|
25
|
+
|
26
|
+
attr_writer :param, :default_param
|
27
|
+
attr_bang :switch, :raise, :debug
|
28
|
+
|
29
|
+
define_option "s", :switch!, "dummy switch"
|
30
|
+
alias_option "s", "switch"
|
31
|
+
|
32
|
+
define_option "p", :param=, "PARAM", "dummy parameter"
|
33
|
+
alias_option "p", "param"
|
34
|
+
|
35
|
+
define_option "d", :default_param=, "PARAM:dddd",
|
36
|
+
"dummy parameter with default value"
|
37
|
+
alias_option "d", "default-param"
|
38
|
+
|
39
|
+
define_option "r", :raise!, "raise an exception"
|
40
|
+
alias_option "r", "raise"
|
41
|
+
|
42
|
+
define_option "g", :debug!, "lots of ugly debugging information"
|
43
|
+
alias_option "g", "debug"
|
44
|
+
|
45
|
+
define_option "h", :help, "show this options list"
|
46
|
+
alias_option "h", "help"
|
47
|
+
define_option "V", :version, "show version information"
|
48
|
+
alias_option "V", "version"
|
49
|
+
|
50
|
+
UNKNOWN = "Sorry, unknown option"
|
51
|
+
STOPOPT = "no more options"
|
52
|
+
UNPROCA = "Warning: unprocessed arguments left"
|
53
|
+
|
54
|
+
def run
|
55
|
+
puts inspect
|
56
|
+
raise "stop" if @raise
|
57
|
+
ENV[ OPTIONS_ENV] or
|
58
|
+
puts "Try to set the environment variable #{OPTIONS_ENV}."
|
59
|
+
@args.clear
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
if false then
|
65
|
+
DemoAppl.run %w(-s -p rrrr qqqq -x -y -z)
|
66
|
+
else
|
67
|
+
DemoAppl.run
|
68
|
+
end
|
69
|
+
|
data/lib/appl.rb
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
#
|
2
|
+
# appl.rb -- Application class
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
class Application
|
7
|
+
|
8
|
+
APPL_VERSION = "1.2".freeze
|
9
|
+
|
10
|
+
OPTIONS_ENV = nil
|
11
|
+
|
12
|
+
STOPOPT = "stop option processing"
|
13
|
+
UNKNOWN = "Unknown option"
|
14
|
+
UNPROCA = "Warning: unprocessed arguments"
|
15
|
+
|
16
|
+
class OptionError < StandardError ; end
|
17
|
+
class Done < Exception ; end
|
18
|
+
|
19
|
+
def initialize args = nil
|
20
|
+
@args = args||self.class.cmdline_arguments
|
21
|
+
self.class.each_option { |opt,desc,arg,dfl,act|
|
22
|
+
begin
|
23
|
+
send act, dfl if dfl
|
24
|
+
rescue NoMethodError
|
25
|
+
raise OptionError, "Option action `#{act}' is not defined."
|
26
|
+
end
|
27
|
+
}
|
28
|
+
while @args.first =~ /\A-/ do
|
29
|
+
opt = $'
|
30
|
+
@args.shift
|
31
|
+
if opt =~ /\A-/ then
|
32
|
+
break if !$' or $'.empty?
|
33
|
+
opt = $'
|
34
|
+
if opt =~ /^=/ then opt = $` ; @args.unshift $' end
|
35
|
+
act = self.class.option_act @args, opt, nil
|
36
|
+
send *act
|
37
|
+
else
|
38
|
+
until not opt or opt.empty? do
|
39
|
+
c = opt.slice! 0, 1
|
40
|
+
if opt =~ /^=/ then opt = nil ; @args.unshift $' end
|
41
|
+
act = self.class.option_act @args, c, opt
|
42
|
+
send *act
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def help
|
49
|
+
c = self.class
|
50
|
+
puts c.version
|
51
|
+
puts
|
52
|
+
puts c::DESCRIPTION
|
53
|
+
puts
|
54
|
+
c.show_options
|
55
|
+
if block_given? then
|
56
|
+
puts
|
57
|
+
yield
|
58
|
+
end
|
59
|
+
raise Done
|
60
|
+
end
|
61
|
+
|
62
|
+
def version
|
63
|
+
self.class.show_version
|
64
|
+
raise Done
|
65
|
+
end
|
66
|
+
|
67
|
+
VERSION = "0"
|
68
|
+
NAME = "appl"
|
69
|
+
SUMMARY = "Dummy application"
|
70
|
+
DESCRIPTION = <<-EOT
|
71
|
+
This base class does nothing by default.
|
72
|
+
EOT
|
73
|
+
|
74
|
+
def run
|
75
|
+
end
|
76
|
+
|
77
|
+
def execute
|
78
|
+
run
|
79
|
+
if @args.any? then
|
80
|
+
u = @args.join " "
|
81
|
+
puts "#{self.class::UNPROCA}: #{u}"
|
82
|
+
end
|
83
|
+
0
|
84
|
+
rescue SignalException
|
85
|
+
raise if @debug
|
86
|
+
self.class.show_message $!.inspect
|
87
|
+
128 + ($!.signo||2) # Ruby 1.8 returns signo +nil+; assume SIGINT
|
88
|
+
rescue
|
89
|
+
raise if @debug
|
90
|
+
self.class.show_message "Error: #$!", "(#{$!.class})"
|
91
|
+
$!.to_i rescue 1
|
92
|
+
end
|
93
|
+
|
94
|
+
HASH_ORDER = RUBY_VERSION >= "1.9"
|
95
|
+
|
96
|
+
class <<self
|
97
|
+
|
98
|
+
def run args = nil
|
99
|
+
e = execute args
|
100
|
+
exit e
|
101
|
+
end
|
102
|
+
|
103
|
+
def version
|
104
|
+
if self::VERSION =~ %r/\s/ then
|
105
|
+
self::VERSION
|
106
|
+
else
|
107
|
+
"#{self::NAME} #{self::VERSION} -- #{self::SUMMARY}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def execute args = nil
|
114
|
+
i = new args
|
115
|
+
i.execute
|
116
|
+
rescue Done
|
117
|
+
0
|
118
|
+
rescue OptionError
|
119
|
+
show_message $!
|
120
|
+
127
|
121
|
+
end
|
122
|
+
|
123
|
+
def inherited sub
|
124
|
+
sub.instance_eval { @options, @aliases = {}, {} }
|
125
|
+
end
|
126
|
+
|
127
|
+
def attr_bang *syms
|
128
|
+
syms.each { |sym|
|
129
|
+
define_method :"#{sym}!" do
|
130
|
+
instance_variable_set :"@#{sym}", true
|
131
|
+
end
|
132
|
+
}
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
136
|
+
public
|
137
|
+
|
138
|
+
def define_option opt, *param
|
139
|
+
delete_option opt
|
140
|
+
act = param.shift
|
141
|
+
desc = param.pop
|
142
|
+
arg = param.shift
|
143
|
+
if arg then
|
144
|
+
if param.empty? then
|
145
|
+
arg, dfl = arg.split /:/, 2
|
146
|
+
if dfl =~ /\A:/ then
|
147
|
+
dfl = $'.to_sym
|
148
|
+
end
|
149
|
+
else
|
150
|
+
dfl = param.shift
|
151
|
+
end
|
152
|
+
end
|
153
|
+
d = param.map { |x| "#{x}#$/" }.join
|
154
|
+
desc.insert 0, d
|
155
|
+
@options[ opt.to_s] = [ desc, arg, dfl, act]
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
alias def_option define_option
|
159
|
+
|
160
|
+
def alias_option orig, opt
|
161
|
+
unalias_option opt
|
162
|
+
@aliases[ opt.to_s] = orig.to_s
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def delete_option opt
|
167
|
+
self < Application or return
|
168
|
+
superclass.delete_option opt
|
169
|
+
@options.delete opt
|
170
|
+
@aliases.reject! { |k,v| v == opt }
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def unalias_option opt
|
175
|
+
self < Application or return
|
176
|
+
superclass.unalias_option opt
|
177
|
+
@aliases.delete opt
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
|
181
|
+
protected
|
182
|
+
|
183
|
+
def find_option_act opt
|
184
|
+
self < Application or return
|
185
|
+
@options[ opt] || @options[ @aliases[ opt]] ||
|
186
|
+
(superclass.find_option_act opt)
|
187
|
+
end
|
188
|
+
|
189
|
+
def all_options
|
190
|
+
if self < Application then
|
191
|
+
r = superclass.all_options
|
192
|
+
r.update @options
|
193
|
+
else
|
194
|
+
{}
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def all_aliases
|
199
|
+
if self < Application then
|
200
|
+
r = superclass.all_aliases
|
201
|
+
r.update @aliases
|
202
|
+
else
|
203
|
+
{}
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def options_desc &block
|
208
|
+
a = Hash.new do |h,k| h[ k] = [] end
|
209
|
+
all_aliases.each { |k,v|
|
210
|
+
a[ v].push k
|
211
|
+
}
|
212
|
+
a.values.each { |v| v.sort! } unless HASH_ORDER
|
213
|
+
each_option { |opt,desc,arg,dfl,|
|
214
|
+
yield opt, arg, dfl, desc
|
215
|
+
a[ opt].each { |l|
|
216
|
+
yield l, nil, nil, nil
|
217
|
+
}
|
218
|
+
}
|
219
|
+
yield "", nil, nil, self::STOPOPT
|
220
|
+
end
|
221
|
+
|
222
|
+
public
|
223
|
+
|
224
|
+
def each_option
|
225
|
+
o = all_options
|
226
|
+
o = o.sort_by { |k,v| k.swapcase } unless HASH_ORDER
|
227
|
+
o.each { |opt,(desc,arg,dfl,act)|
|
228
|
+
case dfl
|
229
|
+
when Symbol then dfl = const_get dfl
|
230
|
+
end
|
231
|
+
yield opt, desc, arg, dfl, act
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
def option_act args, opt, rest
|
236
|
+
dada = find_option_act opt
|
237
|
+
dada or raise OptionError, "#{self::UNKNOWN}: `#{opt}'."
|
238
|
+
desc, arg, dfl, act = *dada
|
239
|
+
r = [ act]
|
240
|
+
if arg then
|
241
|
+
p = rest.slice! 0, rest.length if rest and not rest.empty?
|
242
|
+
r.push p||args.shift
|
243
|
+
end
|
244
|
+
r
|
245
|
+
end
|
246
|
+
|
247
|
+
def show_options
|
248
|
+
options_desc do |opt,arg,dfl,desc|
|
249
|
+
opt = opt.length == 1 ? "-#{opt}" : "--#{opt}"
|
250
|
+
arg &&= "#{arg}"
|
251
|
+
dfl &&= "[#{dfl}]"
|
252
|
+
arg << dfl if arg && dfl
|
253
|
+
puts " %-10s %-12s %s" % [ opt, arg, desc]
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def show_version
|
258
|
+
puts version
|
259
|
+
puts self::COPYRIGHT if const_defined? :COPYRIGHT
|
260
|
+
puts "License: #{self::LICENSE}" if const_defined? :LICENSE
|
261
|
+
a = []
|
262
|
+
a.push self::AUTHOR if const_defined? :AUTHOR
|
263
|
+
a.concat self::AUTHORS if const_defined? :AUTHORS
|
264
|
+
if a.any? then
|
265
|
+
a.flatten!
|
266
|
+
a.uniq!
|
267
|
+
j = a.join ", "
|
268
|
+
h = a.length == 1 ? "Author" : "Authors"
|
269
|
+
puts "#{h}: #{j}"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def show_message msg, extra = nil
|
274
|
+
if $stderr.tty? then
|
275
|
+
msg = "\e[31;1m#{msg}\e[m"
|
276
|
+
if extra then
|
277
|
+
extra = "\e[31m#{extra}\e[m"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
if extra then
|
281
|
+
msg = [ msg, extra].join " "
|
282
|
+
end
|
283
|
+
$stderr.puts msg
|
284
|
+
end
|
285
|
+
|
286
|
+
def cmdline_arguments
|
287
|
+
r = []
|
288
|
+
oe = self::OPTIONS_ENV
|
289
|
+
eo = ENV[ oe] if oe
|
290
|
+
if eo then
|
291
|
+
eo.scan /"((?:\\.|[^"])*")|[^" \t]+/ do
|
292
|
+
r.push $1 ? (eval $1) : $&
|
293
|
+
end
|
294
|
+
end
|
295
|
+
r.concat $*
|
296
|
+
r
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
|
data/lib/intar.rb
ADDED
@@ -0,0 +1,451 @@
|
|
1
|
+
#
|
2
|
+
# intar.rb -- Interactive Ruby evaluation
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
=begin rdoc
|
7
|
+
|
8
|
+
This could be opened not only by the Intar executable but also
|
9
|
+
everywhere inside your Ruby program.
|
10
|
+
|
11
|
+
= Example 1
|
12
|
+
|
13
|
+
require "intar"
|
14
|
+
|
15
|
+
Intar.prompt = "str(%(length)i):%03n%> "
|
16
|
+
a = "hello"
|
17
|
+
Intar.run a
|
18
|
+
|
19
|
+
|
20
|
+
= Example 2
|
21
|
+
|
22
|
+
require "intar"
|
23
|
+
|
24
|
+
class C
|
25
|
+
end
|
26
|
+
|
27
|
+
class IntarC < Intar
|
28
|
+
@show = 3
|
29
|
+
@prompt = "%(33 1)c%t%c%> "
|
30
|
+
@histfile = ".intarc_history"
|
31
|
+
|
32
|
+
class <<self
|
33
|
+
def open
|
34
|
+
super C.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
IntarC.open do |ia| ia.run end
|
40
|
+
|
41
|
+
=end
|
42
|
+
|
43
|
+
|
44
|
+
require "readline"
|
45
|
+
require "supplement"
|
46
|
+
require "supplement/terminal"
|
47
|
+
|
48
|
+
|
49
|
+
class Object
|
50
|
+
def empty_binding
|
51
|
+
binding
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Intar
|
56
|
+
|
57
|
+
class History
|
58
|
+
|
59
|
+
IND = " "
|
60
|
+
IND_RE = /^#{IND}/
|
61
|
+
|
62
|
+
def initialize filename
|
63
|
+
@filename = filename
|
64
|
+
return unless @filename
|
65
|
+
h = "~" unless @filename.starts_with "./"
|
66
|
+
@filename = File.expand_path @filename, h
|
67
|
+
File.exists? @filename and File.open @filename do |h|
|
68
|
+
c = []
|
69
|
+
h.each { |l|
|
70
|
+
case l
|
71
|
+
when IND_RE then c.push $'
|
72
|
+
else push c.join.chomp ; c.clear
|
73
|
+
end
|
74
|
+
}
|
75
|
+
push c.join.chomp
|
76
|
+
end
|
77
|
+
@num = Readline::HISTORY.length
|
78
|
+
end
|
79
|
+
|
80
|
+
def finish max
|
81
|
+
return unless @filename
|
82
|
+
@num.times { Readline::HISTORY.shift }
|
83
|
+
File.open @filename, "a" do |h|
|
84
|
+
a = []
|
85
|
+
while (c = Readline::HISTORY.shift) do a.push c end
|
86
|
+
if a.any? then
|
87
|
+
h.puts "# #{Time.now}"
|
88
|
+
a.each { |c|
|
89
|
+
c.each_line { |l| h.puts IND + l }
|
90
|
+
h.puts "-"
|
91
|
+
}
|
92
|
+
i = a.length
|
93
|
+
h.puts "# #{i} #{entry_str i} added"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
n = File.open @filename do |h|
|
97
|
+
h.inject 0 do |i,l| i += 1 if l =~ IND_RE ; i end
|
98
|
+
end
|
99
|
+
i = max - n
|
100
|
+
if i < 0 then
|
101
|
+
f = nil
|
102
|
+
File.open @filename do |h|
|
103
|
+
h.each_line { |l|
|
104
|
+
f.push l if f
|
105
|
+
case l
|
106
|
+
when IND_RE then i += 1
|
107
|
+
else f ||= [] if i >= 0
|
108
|
+
end
|
109
|
+
}
|
110
|
+
end
|
111
|
+
f and File.open @filename, "w" do |h| h.puts f end
|
112
|
+
end
|
113
|
+
rescue Errno
|
114
|
+
# Forget it if there isn't enough disk space.
|
115
|
+
end
|
116
|
+
|
117
|
+
def push l
|
118
|
+
Readline::HISTORY.push l unless l.empty?
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def entry_str i
|
124
|
+
i == 1 ? "entry" : "entries"
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
class <<self
|
130
|
+
|
131
|
+
attr_accessor :prompt, :show, :colour
|
132
|
+
|
133
|
+
attr_reader :histfile
|
134
|
+
def histfile= hf
|
135
|
+
@histfile = hf
|
136
|
+
if @history then
|
137
|
+
@history.finish @histmax
|
138
|
+
@history = History.new @histfile
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Maximum number of history entries.
|
143
|
+
attr_accessor :histmax
|
144
|
+
|
145
|
+
# Whether to hide entries starting with whitespace.
|
146
|
+
attr_accessor :histhid
|
147
|
+
|
148
|
+
# Shell prefix and Pipe suffix
|
149
|
+
attr_accessor :sh_pref, :pi_suff
|
150
|
+
|
151
|
+
# Whether <code>Kernel#exit</code> should be caught.
|
152
|
+
attr_accessor :catch_exit
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def inherited sub
|
157
|
+
sub.class_eval {
|
158
|
+
s = superclass
|
159
|
+
@prompt = s.prompt
|
160
|
+
@show = s.show
|
161
|
+
@colour = s.colour
|
162
|
+
@histfile = s.histfile
|
163
|
+
@histmax = s.histmax
|
164
|
+
@histhid = s.histhid
|
165
|
+
@sh_pref = s.sh_pref
|
166
|
+
@pi_suff = s.pi_suff
|
167
|
+
@catch_exit = s.catch_exit
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
def history_file
|
172
|
+
if @history then
|
173
|
+
yield
|
174
|
+
else
|
175
|
+
@history = History.new @histfile
|
176
|
+
begin
|
177
|
+
yield
|
178
|
+
ensure
|
179
|
+
@history.finish @histmax
|
180
|
+
@history = nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
public
|
186
|
+
|
187
|
+
private :new
|
188
|
+
def open obj
|
189
|
+
history_file do
|
190
|
+
i = new obj
|
191
|
+
yield i
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def run obj
|
196
|
+
open obj do |i| i.run end
|
197
|
+
end
|
198
|
+
|
199
|
+
def hist_add l
|
200
|
+
return if @histhid and l == /\A[ \t]+/
|
201
|
+
lst = Readline::HISTORY[-1] if Readline::HISTORY.length > 0
|
202
|
+
@history.push l unless l == lst
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
self.prompt = "%(32)c%i%c:%1c%03n%c%> "
|
208
|
+
self.show = 1
|
209
|
+
self.colour = true
|
210
|
+
self.histfile = nil
|
211
|
+
self.histmax = 500
|
212
|
+
self.histhid = true
|
213
|
+
self.sh_pref = "."
|
214
|
+
self.pi_suff = " |"
|
215
|
+
self.catch_exit = nil
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
def initialize obj
|
220
|
+
@obj = obj
|
221
|
+
@n = 0
|
222
|
+
end
|
223
|
+
|
224
|
+
OLDSET = <<-EOT
|
225
|
+
_, __, ___ = nil, nil, nil
|
226
|
+
proc { |r,n|
|
227
|
+
Array === __ or __ = []
|
228
|
+
Hash === ___ or ___ = {}
|
229
|
+
unless r.nil? or r.equal? __ or r.equal? ___ then
|
230
|
+
_ = r
|
231
|
+
__.delete r rescue nil
|
232
|
+
__.unshift r
|
233
|
+
___[ n] = r
|
234
|
+
end
|
235
|
+
}
|
236
|
+
EOT
|
237
|
+
|
238
|
+
autoload :Etc, "etc"
|
239
|
+
autoload :Socket, "socket"
|
240
|
+
|
241
|
+
def cur_prompt
|
242
|
+
t = Time.now
|
243
|
+
self.class.prompt.gsub /%(?:
|
244
|
+
\(([^\)]*)\)
|
245
|
+
|
|
246
|
+
([+-]?[0-9]+(?:\.[0-9]+)?)
|
247
|
+
)?(.)/nx do
|
248
|
+
case $3
|
249
|
+
when "s" then @obj.to_s
|
250
|
+
when "i" then $1.notempty? ? (@obj.send $1) : @obj.inspect
|
251
|
+
when "n" then "%#$2d" % @n
|
252
|
+
when "t" then t.strftime $1||"%X"
|
253
|
+
when "u" then Etc.getpwuid.name
|
254
|
+
when "h" then Socket.gethostname
|
255
|
+
when "w" then cwd_short
|
256
|
+
when "W" then File.basename cwd_short
|
257
|
+
when "c" then (colour *($1 || $2 || "").split.map { |x| x.to_i }).to_s
|
258
|
+
when ">" then Process.uid == 0 ? "#" : ">"
|
259
|
+
when "%" then $3
|
260
|
+
else $&
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def colour *c
|
266
|
+
if self.class.colour then
|
267
|
+
s = c.map { |i| "%d" % i }.join ";"
|
268
|
+
"\e[#{s}m"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def switchcolour *c
|
273
|
+
s = colour *c
|
274
|
+
print s if s
|
275
|
+
end
|
276
|
+
|
277
|
+
def cwd_short
|
278
|
+
r = Dir.pwd
|
279
|
+
h = Etc.getpwuid.dir
|
280
|
+
r[ 0, h.length] == h and r[ 0, h.length] = "~"
|
281
|
+
r
|
282
|
+
end
|
283
|
+
|
284
|
+
def readline
|
285
|
+
r, @previous = @previous, nil
|
286
|
+
r or @n += 1
|
287
|
+
cp = cur_prompt
|
288
|
+
loop do
|
289
|
+
begin
|
290
|
+
l = Readline.readline r ? "" : cp
|
291
|
+
rescue Interrupt
|
292
|
+
puts "^C -- #{$!.inspect}"
|
293
|
+
retry
|
294
|
+
end
|
295
|
+
if r then
|
296
|
+
break if l.nil?
|
297
|
+
r << $/ << l
|
298
|
+
break if l.empty?
|
299
|
+
else
|
300
|
+
return if l.nil?
|
301
|
+
next unless l =~ /\S/
|
302
|
+
r = l
|
303
|
+
break unless l =~ /\\+\z/ and $&.length % 2 != 0
|
304
|
+
end
|
305
|
+
end
|
306
|
+
cp.strip!
|
307
|
+
cp.gsub! /\e\[[0-9]*(;[0-9]*)*m/, ""
|
308
|
+
@file = "#{self.class}/#{cp}"
|
309
|
+
self.class.hist_add r
|
310
|
+
r
|
311
|
+
end
|
312
|
+
|
313
|
+
# :stopdoc:
|
314
|
+
ARROW = "=> "
|
315
|
+
ELLIPSIS = "..."
|
316
|
+
# :startdoc:
|
317
|
+
|
318
|
+
def display r
|
319
|
+
return if r.nil?
|
320
|
+
show = (self.class.show or return)
|
321
|
+
i = r.inspect
|
322
|
+
if show > 0 then
|
323
|
+
siz, = $stdout.winsize
|
324
|
+
siz *= show
|
325
|
+
siz -= ARROW.length
|
326
|
+
if i.length > siz then
|
327
|
+
i.cut! siz-ELLIPSIS.length
|
328
|
+
i << ELLIPSIS
|
329
|
+
end
|
330
|
+
end
|
331
|
+
i.prepend ARROW
|
332
|
+
puts i
|
333
|
+
end
|
334
|
+
|
335
|
+
def pager doit
|
336
|
+
if doit then
|
337
|
+
IO.popen ENV[ "PAGER"]||"more", "w" do |pg|
|
338
|
+
begin
|
339
|
+
stdout = $stdout.dup
|
340
|
+
$stdout.reopen pg
|
341
|
+
yield
|
342
|
+
ensure
|
343
|
+
$stdout.reopen stdout
|
344
|
+
end
|
345
|
+
end
|
346
|
+
else
|
347
|
+
yield
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
public
|
352
|
+
|
353
|
+
class Exit < Exception ; end
|
354
|
+
class CmdFailed < Exception ; end
|
355
|
+
|
356
|
+
def run *precmds
|
357
|
+
bind = @obj.empty_binding
|
358
|
+
precmds.each { |l| eval l, bind }
|
359
|
+
oldset = eval OLDSET, bind
|
360
|
+
@cl = eval "caller.length", bind
|
361
|
+
while l = readline do
|
362
|
+
re_sh_pref = /\A#{Regexp.quote self.class.sh_pref}/
|
363
|
+
re_pi_suff = /#{Regexp.quote self.class.pi_suff}\z/
|
364
|
+
switchcolour
|
365
|
+
begin
|
366
|
+
pg = l.slice! re_pi_suff
|
367
|
+
r = pager pg do
|
368
|
+
unless l =~ re_sh_pref then
|
369
|
+
eval l, bind, @file
|
370
|
+
else
|
371
|
+
call_system $', bind
|
372
|
+
end
|
373
|
+
end
|
374
|
+
oldset.call r, @n
|
375
|
+
display r
|
376
|
+
rescue Exit
|
377
|
+
wait_exit and break
|
378
|
+
rescue CmdFailed
|
379
|
+
oldset.call $?, @n
|
380
|
+
switchcolour 33
|
381
|
+
puts "Exit code: #{$?.exitstatus}"
|
382
|
+
rescue LoadError
|
383
|
+
oldset.call $!, @n
|
384
|
+
show_exception
|
385
|
+
rescue ScriptError
|
386
|
+
if l[ $/] then
|
387
|
+
switchcolour 33
|
388
|
+
puts $!
|
389
|
+
else
|
390
|
+
@previous = l
|
391
|
+
end
|
392
|
+
rescue SystemExit
|
393
|
+
break if wait_exit
|
394
|
+
oldset.call $!, @n
|
395
|
+
show_exception
|
396
|
+
rescue Exception
|
397
|
+
oldset.call $!, @n
|
398
|
+
show_exception
|
399
|
+
ensure
|
400
|
+
switchcolour
|
401
|
+
end
|
402
|
+
end
|
403
|
+
puts
|
404
|
+
ensure
|
405
|
+
done
|
406
|
+
end
|
407
|
+
|
408
|
+
protected
|
409
|
+
|
410
|
+
def done
|
411
|
+
end
|
412
|
+
|
413
|
+
private
|
414
|
+
|
415
|
+
def wait_exit
|
416
|
+
c = self.class.catch_exit
|
417
|
+
c and c.times { print "." ; $stdout.flush ; sleep 1 }
|
418
|
+
true
|
419
|
+
rescue Interrupt
|
420
|
+
puts
|
421
|
+
end
|
422
|
+
|
423
|
+
def show_exception
|
424
|
+
unless $!.to_s.empty? then
|
425
|
+
switchcolour 31, 1
|
426
|
+
print $!
|
427
|
+
print " " unless $!.to_s =~ /\s\z/
|
428
|
+
end
|
429
|
+
switchcolour 31, 22
|
430
|
+
puts "(#{$!.class})"
|
431
|
+
switchcolour 33
|
432
|
+
bt = $@.dup
|
433
|
+
if bt.length > @cl then
|
434
|
+
bt.pop @cl
|
435
|
+
bt.push bt.pop[ /(.*:\d+):.*/, 1]
|
436
|
+
end
|
437
|
+
puts bt
|
438
|
+
end
|
439
|
+
|
440
|
+
def call_system l, bind
|
441
|
+
l.strip!
|
442
|
+
raise Exit if l.empty?
|
443
|
+
eot = "EOT0001"
|
444
|
+
eot.succ! while l[ eot]
|
445
|
+
l = eval "<<#{eot}\n#{l}\n#{eot}", bind, @file
|
446
|
+
system l or raise CmdFailed
|
447
|
+
nil
|
448
|
+
end
|
449
|
+
|
450
|
+
end
|
451
|
+
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: appl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.2'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bertram Scharpf
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: supplement
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
description: |
|
28
|
+
A base class for command line applications doing options parsing
|
29
|
+
and generating exit codes.
|
30
|
+
email: "<software@bertram-scharpf.de>"
|
31
|
+
executables:
|
32
|
+
- intar
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- bin/intar
|
37
|
+
- doc/demoappl
|
38
|
+
- lib/appl.rb
|
39
|
+
- lib/intar.rb
|
40
|
+
homepage: http://www.bertram-scharpf.de/software/appl
|
41
|
+
licenses:
|
42
|
+
- BSD
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements:
|
59
|
+
- Just Ruby
|
60
|
+
rubyforge_project: NONE
|
61
|
+
rubygems_version: 2.6.4
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: Easy option parsing
|
65
|
+
test_files: []
|