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.
@@ -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
- # CLILogger {{{
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 < Logger
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, progname = nil, &block) #:nodoc:
64
- return true if severity == QUIET
284
+ def add(severity, message = nil, **opts, &block) #:nodoc:
285
+ severity_lvl = severity_lvl(severity)
65
286
  if @split_logs
66
- unless severity >= @stderr_logger.level
67
- super(severity,message,progname,&block)
287
+ unless severity_lvl >= @stderr_logger.level
288
+ super(severity,message, **opts, &block)
68
289
  end
69
290
  else
70
- super(severity,message,progname,&block)
291
+ super(severity,message,**opts, &block)
71
292
  end
72
- @stderr_logger.add(severity,message,progname,&block)
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
- split_log: :auto)
96
- @stderr_logger = Logger.new(error_device)
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.level = Logger::Severity::INFO
106
- @stderr_logger.level = DEFAULT_ERROR_LEVEL
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.formatter = BLANK_FORMAT if log_device_tty
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 level=(level)
113
- super(level)
328
+ private def adjust_stderr_level
114
329
  #current_error_level = @stderr_logger.level
115
- if (level > DEFAULT_ERROR_LEVEL) && @split_logs
116
- @stderr_logger.level = level
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: Logger::INFO, definee: self, **opts, &block)
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
- logger.add(severity,msg)
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
- QUIET=-1
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('debug')
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
- def debug(progname = nil, &block); logger.debug(progname,&block); end
267
- def info(progname = nil, &block); logger.info(progname,&block); end
268
- def warns(progname = nil, &block); logger.warn(progname,&block); end
269
- def error(progname = nil, &block); logger.error(progname,&block); end
270
- def fatal(progname = nil, &block); logger.fatal(progname,&block); end
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
- def glob(pattern, expand: false)
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
- g=g.map {|f| self+f} if expand
276
- g
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