shell_helpers 0.6.0 → 0.7.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 +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
|