shell_helpers 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +236 -2
- data/LICENSE.txt +1 -1
- data/Rakefile +7 -10
- data/gemspec.yml +1 -0
- data/lib/shell_helpers.rb +61 -23
- data/lib/shell_helpers/export.rb +4 -4
- data/lib/shell_helpers/logger.bak.rb +481 -0
- data/lib/shell_helpers/logger.rb +274 -56
- data/lib/shell_helpers/pathname.rb +16 -3
- data/lib/shell_helpers/run.rb +48 -19
- data/lib/shell_helpers/sh.rb +97 -30
- data/lib/shell_helpers/sysutils.rb +71 -8
- data/lib/shell_helpers/utils.rb +12 -8
- data/lib/shell_helpers/version.rb +1 -1
- data/test/test_logger.rb +48 -0
- metadata +20 -5
data/lib/shell_helpers/logger.rb
CHANGED
@@ -1,9 +1,234 @@
|
|
1
1
|
# vim: foldmethod=marker
|
2
2
|
#From methadone (cli_logger.rb, cli_logging.rb, last import: 4626a2bca9b6e54077a06a0f8e11a04fadc6e7ae; 2017-01-19)
|
3
3
|
require 'logger'
|
4
|
+
require 'simplecolor'
|
4
5
|
|
5
6
|
module ShellHelpers
|
6
|
-
|
7
|
+
class ColorFormatter < Logger::Formatter #{{{1
|
8
|
+
CLI_COLORS={
|
9
|
+
mark: [:bold],
|
10
|
+
success: [:green, :bold],
|
11
|
+
important: [:blue, :bold],
|
12
|
+
warn: [:yellow, :bold],
|
13
|
+
error: [:red, :bold],
|
14
|
+
fatal: [:red, :bold]
|
15
|
+
}
|
16
|
+
|
17
|
+
def self.create(type=:default)
|
18
|
+
return type if type.respond_to?(:call)
|
19
|
+
logger=self.new
|
20
|
+
case type
|
21
|
+
when :blank
|
22
|
+
logger.format=BLANK_FORMAT
|
23
|
+
when :color
|
24
|
+
logger.cli_colors=CLI_COLORS
|
25
|
+
logger.format=BLANK_FORMAT
|
26
|
+
when :color_info
|
27
|
+
logger.cli_colors=CLI_COLORS
|
28
|
+
when :none
|
29
|
+
logger.format=""
|
30
|
+
end
|
31
|
+
logger
|
32
|
+
end
|
33
|
+
|
34
|
+
def format_severity(severity)
|
35
|
+
# sev_name = Logger::SEV_LABEL[severity] || 'ANY'
|
36
|
+
sev_name=severity.to_s.upcase
|
37
|
+
sev_short=sev_name[0..0]
|
38
|
+
[sev_name, sev_short]
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_accessor :cli_colors
|
42
|
+
|
43
|
+
BLANK_FORMAT = "%{msg}\n"
|
44
|
+
# "%s, [%s#%d] %5s -- %s: %s\n"
|
45
|
+
DEFAULT_FORMAT = "%{severity_short}, [%{date}#%<pid>d] %<severity>s9 -- %{progname}: %{msg}\n"
|
46
|
+
attr_writer :format
|
47
|
+
def format
|
48
|
+
@format ||= DEFAULT_FORMAT
|
49
|
+
end
|
50
|
+
|
51
|
+
private def get_colors(severity, color: [], **_kwds)
|
52
|
+
if cli_colors.nil? #no colors at all
|
53
|
+
return []
|
54
|
+
end
|
55
|
+
colors=[*color]
|
56
|
+
unless severity.is_a?(Numeric)
|
57
|
+
colors=[*cli_colors[severity.to_sym]]+colors
|
58
|
+
end
|
59
|
+
colors
|
60
|
+
end
|
61
|
+
|
62
|
+
def format_msg(msg_infos, colors: [])
|
63
|
+
msg_infos[:msg]=SimpleColor[msg_infos[:msg], *colors]
|
64
|
+
format % msg_infos
|
65
|
+
end
|
66
|
+
|
67
|
+
def call(severity, time, progname, msg, **kwds)
|
68
|
+
colors=get_colors(severity, **kwds)
|
69
|
+
severity_short, severity_name=format_severity(severity)
|
70
|
+
format_msg( {severity_short: severity_short,
|
71
|
+
date: format_datetime(time),
|
72
|
+
pid: $$,
|
73
|
+
severity: severity_name,
|
74
|
+
progname: progname,
|
75
|
+
msg: msg2str(msg)}, colors: colors)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# like Logger but with more levels
|
80
|
+
class ColorLogger < ::Logger #{{{1
|
81
|
+
ColorLoggerError=Class.new(StandardError)
|
82
|
+
WrongLevel=Class.new(ColorLoggerError)
|
83
|
+
module Levels
|
84
|
+
#note Logger::Severity is included into Logger, so we can access the severity levels directly
|
85
|
+
DEBUG1=0 #=DEBUG
|
86
|
+
DEBUG2=-0.1
|
87
|
+
DEBUG3=-0.2
|
88
|
+
IMPORTANT=1.5 #between warning and info
|
89
|
+
SUCCESS=1.3 #between warning and info
|
90
|
+
VERBOSE=0.9
|
91
|
+
VERBOSE1=0.9
|
92
|
+
VERBOSE2=0.8
|
93
|
+
VERBOSE3=0.7
|
94
|
+
QUIET=-9
|
95
|
+
DEBUG = Logger::DEBUG # 0
|
96
|
+
INFO = Logger::INFO # 1
|
97
|
+
WARN = Logger::WARN # 2
|
98
|
+
ERROR = Logger::ERROR # 3
|
99
|
+
FATAL = Logger::FATAL # 4
|
100
|
+
UNKNOWN = Logger::UNKNOWN # 5
|
101
|
+
end
|
102
|
+
|
103
|
+
LOG_LEVELS=
|
104
|
+
{
|
105
|
+
quiet: Levels::QUIET,
|
106
|
+
debug3: Levels::DEBUG3,
|
107
|
+
debug2: Levels::DEBUG2,
|
108
|
+
debug1: Levels::DEBUG1,
|
109
|
+
debug: Levels::DEBUG, #0
|
110
|
+
verbose: Levels::VERBOSE,
|
111
|
+
verbose1: Levels::VERBOSE1,
|
112
|
+
verbose2: Levels::VERBOSE2,
|
113
|
+
verbose3: Levels::VERBOSE3,
|
114
|
+
info: Levels::INFO, #1
|
115
|
+
mark: :info,
|
116
|
+
success: Levels::SUCCESS,
|
117
|
+
important: Levels::IMPORTANT,
|
118
|
+
warn: Levels::WARN, #2
|
119
|
+
error: Levels::ERROR, #3
|
120
|
+
fatal: Levels::FATAL, #4
|
121
|
+
unknown: Levels::UNKNOWN, #5
|
122
|
+
}
|
123
|
+
|
124
|
+
def log_levels
|
125
|
+
@levels ||= LOG_LEVELS.dup
|
126
|
+
@levels
|
127
|
+
end
|
128
|
+
|
129
|
+
def severity(severity, default_lvl: @default_lvl, quiet_lvl: @quiet_lvl, **_opts)
|
130
|
+
severity ||= :unknown
|
131
|
+
severity=default_lvl if severity == true
|
132
|
+
severity=quiet_lvl if severity == false
|
133
|
+
severity
|
134
|
+
end
|
135
|
+
|
136
|
+
def severity_lvl(severity, **opts)
|
137
|
+
severity=severity(severity, **opts)
|
138
|
+
if severity.is_a?(Numeric)
|
139
|
+
return severity
|
140
|
+
else
|
141
|
+
sev=severity.to_s.downcase.to_sym
|
142
|
+
if log_levels.key?(sev)
|
143
|
+
return severity_lvl(log_levels[sev])
|
144
|
+
else
|
145
|
+
raise WrongLevel.new(severity)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
attr_accessor :default_lvl, :verbose_lvl, :quiet_lvl, :default_formatter
|
151
|
+
|
152
|
+
def initialize(*args, levels: {}, default_lvl: :info, level: default_lvl, verbose_lvl: :verbose, quiet_lvl: :unknown, default_formatter: :color, **kwds)
|
153
|
+
@default_lvl=default_lvl
|
154
|
+
@verbose_lvl=verbose_lvl
|
155
|
+
@quiet_lvl=quiet_lvl
|
156
|
+
super(*args, level: severity_lvl(level), **kwds)
|
157
|
+
@default_formatter = ColorFormatter.create(default_formatter)
|
158
|
+
@level=severity_lvl(@default_lvl)
|
159
|
+
klass=self.singleton_class
|
160
|
+
levels=log_levels.merge!(levels)
|
161
|
+
levels.keys.each do |lvl|
|
162
|
+
klass.define_method(lvl.to_sym) do |msg=nil, **opts, &block|
|
163
|
+
add(lvl.to_sym, msg, **opts, &block)
|
164
|
+
end
|
165
|
+
klass.define_method("#{lvl}?".to_sym) do
|
166
|
+
@level <= severity_lvl(lvl)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
yield self, @default_formatter if block_given?
|
170
|
+
end
|
171
|
+
|
172
|
+
def datetime_format=(datetime_format)
|
173
|
+
@default_formatter.datetime_format = datetime_format if @default_formatter.respond_to?(:datetime_format)
|
174
|
+
@formatter.datetime_format = datetime_format if defined? @formatter and @formatter.respond_to?(:datetime_format)
|
175
|
+
end
|
176
|
+
|
177
|
+
def datetime_format
|
178
|
+
@default_formatter.datetime_format if @default_formatter.respond_to?(:datetime_format)
|
179
|
+
end
|
180
|
+
|
181
|
+
def formatter=(form)
|
182
|
+
if form.nil?
|
183
|
+
super
|
184
|
+
else
|
185
|
+
@formatter=get_formatter(form)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def get_formatter(form=nil)
|
190
|
+
if form.nil?
|
191
|
+
@formatter || @default_formatter
|
192
|
+
else
|
193
|
+
formatter=ColorFormatter.create(form)
|
194
|
+
formatter.datetime_format = @default_formatter.datetime_format if formatter.respond_to?(:datetime_format) and @default_formatter.respond_to?(:datetime_format)
|
195
|
+
formatter
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def format_message(severity, datetime, progname, msg, formatter: nil, **opts)
|
200
|
+
get_formatter(formatter).call(severity, datetime, progname, msg, **opts)
|
201
|
+
end
|
202
|
+
|
203
|
+
# log with given security. Also accepts 'true'
|
204
|
+
def add(severity, message = nil, progname: @progname, callback: nil, format: nil, **opts)
|
205
|
+
severity=severity(severity, **opts)
|
206
|
+
severity_lvl=severity_lvl(severity)
|
207
|
+
if @logdev.nil? or severity_lvl < @level
|
208
|
+
return true
|
209
|
+
end
|
210
|
+
if message.nil?
|
211
|
+
message = yield if block_given?
|
212
|
+
end
|
213
|
+
callback.call(message, progname, severity) if callback
|
214
|
+
@logdev.write(
|
215
|
+
format_message(severity, Time.now, progname, message, formatter: format, caller: self, **opts))
|
216
|
+
true
|
217
|
+
end
|
218
|
+
|
219
|
+
def level=(severity)
|
220
|
+
@level = severity_lvl(severity)
|
221
|
+
end
|
222
|
+
|
223
|
+
# like level= but for clis, so we can pass a default if level=true
|
224
|
+
def cli_level(level, active: @verbose_lvl, disactive: @quiet_lvl)
|
225
|
+
level=active if level==true #for cli
|
226
|
+
level=disactive if level==false #for cli
|
227
|
+
self.level=level
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# CLILogger {{{1
|
7
232
|
# A Logger instance that gives better control of messaging the user and
|
8
233
|
# logging app activity. At it's most basic, you would use <tt>info</tt>
|
9
234
|
# as a replacement for +puts+ and <tt>error</tt> as a replacement for
|
@@ -40,11 +265,7 @@ module ShellHelpers
|
|
40
265
|
# logger = CLILogger.new('logfile.txt')
|
41
266
|
# logger.debug("Starting up") #=> logfile.txt gets this
|
42
267
|
# logger.error("Something went wrong!") # => BOTH logfile.txt AND the standard error get this
|
43
|
-
class CLILogger <
|
44
|
-
BLANK_FORMAT = lambda { |severity,datetime,progname,msg|
|
45
|
-
msg + "\n"
|
46
|
-
}
|
47
|
-
|
268
|
+
class CLILogger < ColorLogger
|
48
269
|
# Helper to proxy methods to the super class AND to the internal error logger
|
49
270
|
# +symbol+:: Symbol for name of the method to proxy
|
50
271
|
def self.proxy_method(symbol) #:nodoc:
|
@@ -60,23 +281,19 @@ module ShellHelpers
|
|
60
281
|
proxy_method :'progname='
|
61
282
|
proxy_method :'datetime_format='
|
62
283
|
|
63
|
-
def add(severity, message = nil,
|
64
|
-
|
284
|
+
def add(severity, message = nil, **opts, &block) #:nodoc:
|
285
|
+
severity_lvl = severity_lvl(severity)
|
65
286
|
if @split_logs
|
66
|
-
unless
|
67
|
-
super(severity,message,
|
287
|
+
unless severity_lvl >= @stderr_logger.level
|
288
|
+
super(severity,message, **opts, &block)
|
68
289
|
end
|
69
290
|
else
|
70
|
-
super(severity,message,
|
291
|
+
super(severity,message,**opts, &block)
|
71
292
|
end
|
72
|
-
|
293
|
+
severity = severity_lvl if severity == true
|
294
|
+
@stderr_logger.add(severity,message,**opts, &block)
|
73
295
|
end
|
74
296
|
|
75
|
-
def quiet(progname = nil, &block)
|
76
|
-
add(QUIET, nil, progname, &block)
|
77
|
-
end
|
78
|
-
|
79
|
-
|
80
297
|
DEFAULT_ERROR_LEVEL = Logger::Severity::WARN
|
81
298
|
|
82
299
|
# A logger that logs error-type messages to a second device; useful for
|
@@ -92,31 +309,44 @@ module ShellHelpers
|
|
92
309
|
# By default, this is Logger::Severity::WARN
|
93
310
|
# +error_device+:: device where all error messages should go.
|
94
311
|
def initialize(log_device=$stdout,error_device=$stderr,
|
95
|
-
|
96
|
-
@stderr_logger =
|
312
|
+
split_log: :auto, default_error_lvl: DEFAULT_ERROR_LEVEL, **kwds)
|
313
|
+
@stderr_logger = ColorLogger.new(error_device, default_lvl: default_error_lvl, **kwds)
|
97
314
|
|
98
|
-
super(log_device)
|
315
|
+
super(log_device, **kwds)
|
99
316
|
|
100
317
|
log_device_tty = tty?(log_device)
|
101
318
|
error_device_tty = tty?(error_device)
|
102
319
|
|
103
320
|
@split_logs = log_device_tty && error_device_tty if split_log==:auto
|
104
321
|
|
105
|
-
self.
|
106
|
-
@stderr_logger.
|
322
|
+
self.default_formatter = ColorFormatter.create(:color) if log_device_tty
|
323
|
+
@stderr_logger.default_formatter = ColorFormatter.create(:color) if error_device_tty
|
107
324
|
|
108
|
-
self
|
109
|
-
@stderr_logger.formatter = BLANK_FORMAT if error_device_tty
|
325
|
+
yield self, @stderr_logger if block_given?
|
110
326
|
end
|
111
327
|
|
112
|
-
def
|
113
|
-
super(level)
|
328
|
+
private def adjust_stderr_level
|
114
329
|
#current_error_level = @stderr_logger.level
|
115
|
-
if
|
116
|
-
|
330
|
+
if @split_logs
|
331
|
+
if (self.level > @stderr_logger.level)
|
332
|
+
@stderr_logger.level = self.level
|
333
|
+
end
|
334
|
+
if (self.level < @stderr_logger.level)
|
335
|
+
@stderr_logger.level = [self.level, @stderr_logger.severity_lvl(@stderr_logger.default_lvl)].min
|
336
|
+
end
|
117
337
|
end
|
118
338
|
end
|
119
339
|
|
340
|
+
def level=(level)
|
341
|
+
super
|
342
|
+
adjust_stderr_level
|
343
|
+
end
|
344
|
+
|
345
|
+
def cli_level(*args)
|
346
|
+
super
|
347
|
+
adjust_stderr_level
|
348
|
+
end
|
349
|
+
|
120
350
|
# Set the threshold for what messages go to the error device. Note
|
121
351
|
# that calling #level= will *not* affect the error logger *unless* both
|
122
352
|
# devices are TTYs.
|
@@ -139,11 +369,11 @@ module ShellHelpers
|
|
139
369
|
|
140
370
|
#log the action and execute it
|
141
371
|
#Severity is Logger:: DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
|
142
|
-
def log_and_do(*args, severity:
|
372
|
+
def log_and_do(*args, severity: INFO, definee: self, **opts, &block)
|
143
373
|
msg="log_and_do #{args} on #{self}"
|
144
374
|
msg+=" with options #{opts}" unless opts.empty?
|
145
375
|
msg+=" with block #{block}" if block
|
146
|
-
|
376
|
+
add(severity,msg)
|
147
377
|
if opts.empty?
|
148
378
|
definee.send(*args, &block)
|
149
379
|
else
|
@@ -151,25 +381,12 @@ module ShellHelpers
|
|
151
381
|
end
|
152
382
|
end
|
153
383
|
|
154
|
-
|
155
|
-
|
156
|
-
def log_levels
|
157
|
-
{
|
158
|
-
'quiet' => QUIET,
|
159
|
-
'debug' => Logger::DEBUG,
|
160
|
-
'info' => Logger::INFO,
|
161
|
-
'warn' => Logger::WARN,
|
162
|
-
'error' => Logger::ERROR,
|
163
|
-
'fatal' => Logger::FATAL,
|
164
|
-
}
|
165
|
-
end
|
166
|
-
|
167
|
-
private def toggle_log_level
|
384
|
+
private def toggle_log_level(toggle='debug')
|
168
385
|
@log_level_original = self.level unless @log_level_toggled
|
169
386
|
logger.level = if @log_level_toggled
|
170
387
|
@log_level_original
|
171
388
|
else
|
172
|
-
log_levels.fetch(
|
389
|
+
log_levels.fetch(toggle)
|
173
390
|
end
|
174
391
|
@log_level_toggled = !@log_level_toggled
|
175
392
|
@log_level = logger.level
|
@@ -186,8 +403,8 @@ module ShellHelpers
|
|
186
403
|
end
|
187
404
|
|
188
405
|
end
|
189
|
-
|
190
|
-
# CLILogging {{{
|
406
|
+
|
407
|
+
# CLILogging {{{1
|
191
408
|
# Provides easier access to a shared DR::CLILogger instance.
|
192
409
|
# Include this module into your class, and #logger provides access to a
|
193
410
|
# shared logger. This is handy if you want all of your clases to have
|
@@ -253,21 +470,22 @@ module ShellHelpers
|
|
253
470
|
logger.setup_toggle_trap(signal)
|
254
471
|
end
|
255
472
|
|
256
|
-
def log_and_do(*args)
|
257
|
-
logger.log_and_do(*args)
|
473
|
+
def log_and_do(*args,**kws)
|
474
|
+
logger.log_and_do(*args,**kws)
|
258
475
|
end
|
259
476
|
|
477
|
+
LOG_LEVELS=logger.log_levels
|
478
|
+
|
260
479
|
#Include this in place of CLILogging if you prefer to use
|
261
480
|
#info directly rather than logger.info
|
262
481
|
module Shortcuts #{{{
|
263
|
-
extend self
|
264
482
|
include CLILogging
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
483
|
+
extend self
|
484
|
+
logger.log_levels.each_key do |lvl|
|
485
|
+
define_method(lvl.to_sym) do |*args, &block|
|
486
|
+
logger.send(lvl.to_sym, *args, &block)
|
487
|
+
end
|
488
|
+
end
|
271
489
|
end
|
272
490
|
#}}}
|
273
491
|
end #}}}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'pathname'
|
2
|
+
require 'forwardable'
|
2
3
|
|
3
4
|
#backports from ruby 2.1
|
4
5
|
class Pathname
|
@@ -97,6 +98,14 @@ module ShellHelpers
|
|
97
98
|
exist? or symlink?
|
98
99
|
end
|
99
100
|
|
101
|
+
#like read, but output nil rather than an exception if the file does
|
102
|
+
#not exist
|
103
|
+
def read!
|
104
|
+
read
|
105
|
+
rescue
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
100
109
|
def filewrite(*args,mode:"w",perm: nil,mkpath: false,backup: false)
|
101
110
|
logger.debug("Write to #{self}"+ (perm ? " (#{perm})" : "")) if respond_to?(:logger)
|
102
111
|
self.dirname.mkpath if mkpath
|
@@ -269,11 +278,15 @@ module ShellHelpers
|
|
269
278
|
Utils.find(self,*args,&b)
|
270
279
|
end
|
271
280
|
|
272
|
-
|
281
|
+
# We now have Pathname#glob
|
282
|
+
def rel_glob(pattern, expand: false)
|
273
283
|
g=[]
|
274
284
|
self.cd { g=Dir.glob(pattern) }
|
275
|
-
|
276
|
-
|
285
|
+
if expand
|
286
|
+
g.map {|f| self+f}
|
287
|
+
else
|
288
|
+
g.map {|f| Pathname.new(f)}
|
289
|
+
end
|
277
290
|
end
|
278
291
|
|
279
292
|
#follow a symlink
|