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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6049045c2830e61e0ea865aeb4905e0f4464cdd4b837ee0e7923667e8aae7900
4
- data.tar.gz: 2ca9a6fceac250538239f8ff3b075d4d2813b02892f7dad411dfad0a8dbfd1e7
3
+ metadata.gz: 42ce008456bb9f4bd37bea823f9d0886483c647e76ff265c571810c0e72963bf
4
+ data.tar.gz: cd0ee20f0fb67c4d48eb1571ae90679fc310ffc2cfc0b7b980f0beeae28e086c
5
5
  SHA512:
6
- metadata.gz: 809023e14ecf8fc382ce25f107eeeba053573e190fac4030641a496e74306d3f480ef6b4555f5ffb71b9e412bf4f95778c78ab7bdf27efa7a517f92c1239c69c
7
- data.tar.gz: 6db15aa50cb2ee5d60ee85404a109b00f8106aad9491b993c6aed289f2f23fd838d65124501a617672cf3e9fa06360aef525177199fedaa1b47362622b6a673c
6
+ metadata.gz: 7c50ff0378d2e1519a595eed19988e49be241c9d437e0d4338f7be8761eb468e3656162ebe78504d36decdc57dad3c52472d47c1ad6a330a98cb45d05ce4ded5
7
+ data.tar.gz: 6abc73549c5699ff2ae5355a475f1cffafcf0b584461764f6aa249c86df97e2f74602ce30fb9ffca3db865307917c56360caab44606c2b33775500ac51934ab8
data/ChangeLog.md CHANGED
@@ -1,4 +1,238 @@
1
- ### 0.1.0 / 2015-02-24
1
+ == Release v0.7.0 (2020-02-18) ==
2
2
 
3
- * Initial release:
3
+ * Copyright
4
+ * rsync: Use -zz instead of -z (--new-compress)
5
+ * Fixes for ruby 2.7
6
+ * rsync: add clobber option
7
+ * logger: fix logger.debug {"foo"}
8
+ * sysutils.rb: add exemples
9
+ * Misc bug fixes and doc
10
+ * sh.rb: correct examples
11
+ * Pathname.read!
12
+ * pathname: old_glob -> rel_glob
13
+ * pathname: deprecate glob, it is now a builtin
14
+ * logger: fix output showing twice
15
+ * Bug fix
16
+ * Fix tests
17
+ * Small tweaks
18
+ * logger2.rb is now logger.rb
19
+ * logger2: bug fixes
20
+ * logger2.rb: continue merge
21
+ * logger2: Merge MoreLogger and ColorLogger
22
+ * Rework MoreLogger
23
+ * SH: log_options (for OptParse)
24
+ * Fix tests
25
+ * logger.rb: new class ColorLogger
26
+ * logger: cli_info -> not bold
27
+ * Logger: cli_methods configuration
28
+ * logger: small tweaks
29
+ * tests for logger
30
+ * Logger: cli_*
31
+ * Update Rakefile
32
+ * Update Rakefile
33
+ * logger.rb: add bold
34
+ * logger: color_add
35
+ * severity: debug1=debug and verbose1=verbose
36
+ * logger: verbose* levels
37
+ * Logger#cli_level: helper to set up log level
38
+ * logger: debug1, debug2, debug3
39
+ * run.rb: bug fixes
40
+ * Bug fixes
41
+ * SH.log
42
+ * Sh: use instance_method for run_sudo_loop
43
+ * pathname: add missing require
44
+ * ssh: ssh_env option to pass env to the remote
45
+ * Run#run_success
46
+ * SH.sh: by default only return success
47
+ * Sh.sh: specify argv0
48
+ * Sh.sh: accept a block
49
+ * Sh: sudo_loop
50
+ * Sh#sh_or_proc
51
+ * Bug fixes
52
+
53
+ == Release v0.6.0 (2018-09-07) ==
54
+
55
+ * Bump version to 0.6.0
56
+
57
+ == Release v0.3.0 (2018-09-07) ==
58
+
59
+ * Bump version
60
+ * run+sh: unify command handling
61
+ * logger.rb: add quiet
62
+ * SH.sh: sudo can be a complex command
63
+ * Bug fixes + better logging for sh
64
+ * ShConfig + VirtualFile
65
+ * ShConfig: remove context
66
+ * ShConfig: wrap
67
+ * Sh: ShConfig
68
+ * Excpetions: subclass StandardError
69
+ * Run: bug fix
70
+ * Run.run: add :quiet alias for error: :quiet
71
+ * Run.run: replace status_mode by error_mode to be clearer
72
+ * run: accept environments, wrap into ProcessStatus if needed
73
+ * run.rb: run_simple now calls run
74
+ * sysutils: small improvements
75
+ * Sysutils: partition_infos + bug fixes
76
+ * Pathname#readbin
77
+ * SH.sh: detach vs spawn
78
+ * ssh: use URI::Ssh to be more lenient for host name
79
+ * Rsync exclude: escape
80
+ * Clean up
81
+ * Rsync: exclude
82
+ * utils: refactor
83
+ * rsync: add sshcommand option
84
+ * ssh: :sshkit and :net_ssh modes
85
+ * Pathname: add logging
86
+ * SH.sh: allow mode to be :exec too
87
+ * Utils.ssh: more mode, and split ssh_command
88
+ * Utils.ssh: user
89
+ * Utils.ssh:
90
+ * sysutils: bug fix
91
+ * Pathname#glob: expand to pathname
92
+ * Pathname#rel_path_to: convert the target to a Pathname
93
+ * sysutils: allows mountoptions to be a String
94
+ * Remove LogHelper
95
+ * sh.rb: change execute log level to be :info
96
+ * Utils: eval_shell
97
+ * Bug fix
98
+ * Bug fix
99
+ * make_dir_or_subvolume
100
+ * sysutils: stat_files
101
+ * sysutils: bug fixes
102
+ * sysutils: check devices for name too
103
+ * sysutils: block handling to automatically unmount
104
+ * sysutils: use Pathname#<=>
105
+ * run/sh: add a 'sudo' option
106
+ * sysutils: improve find_devices
107
+ * sysutils: fs_infos return a hash rather than an array
108
+ * sysutils: unify keywords for blkid, lsblk, findmnt
109
+ * sysutils: lsblk and findmnt
110
+ * sysutils: partition types
111
+ * sysutils: commands for sysadmins
112
+ * pathname: chown
113
+ * rsync: more convenient options passing
114
+ * Copyright
115
+ * rsync: correct a typo
116
+ * Wrap chdir
117
+ * SH::Sh: Add DryRun module
118
+ * Pathname: misc utils functions
119
+ * Sh.sh: correctly handle false options
120
+ * docs typos
121
+ * Sh: activates logging by default
122
+ * sh_commands: chomp newline
123
+
124
+ == Release v0.2.0 (2018-02-01) ==
125
+
126
+ * Bump versions
127
+ * rsync: add chown option
128
+ * Pathname#{text?,binary?}: return false on a directory
129
+ * run.rb: improve api
130
+ * Import changes from methadone
131
+ * binary?: Encoding to ascii would lose to many bits
132
+ * Pathname#text? Don't invoke 'file'
133
+ * Pathname#text?, Pathname#glob
134
+ * run_lazy: simplify the implementation
135
+ * run: add run_enum
136
+ * run_pager: can pass arguments to the pager
137
+ * Be explicit about require
138
+ * Add missing options lib
139
+ * Fix require
140
+ * Split utils.rb into utils.rb and export.rb
141
+ * Pathname#hidden? was defined twice
142
+ * Add badges
143
+ * In travis the test should be under 'bundle exec'
144
+ * We need the git version of drain for now
145
+ * Update gemspec
146
+ * Add rake dependency
147
+ * Configure travis and streamline rake and test files
148
+ * rsync: using relative we usually want no-implied-dirs
149
+ * utils: more rsync options
150
+ * import_parse: inline mode
151
+ * utils: add capture_stdout
152
+ * shrun: helper to run system/spawn with the correct options
153
+ * sh: Pass along options
154
+ * logger: progname should affect the error logger too
155
+ * SH.sh: allow to detach
156
+ * bugfix
157
+ * Unquote value
158
+ * utils.rb: put import_parse in the correct Module
159
+ * import_variable: match variable name on \S*
160
+ * test import_parse
161
+ * Import variables
162
+ * Copyright
163
+ * export_value: test if the object respond to :to_a or :to_h
164
+ * test: test_utils.rb (only ShellExport for now)
165
+ * Fixes for ruby 2.4
166
+ * find: handle max_depth
167
+ * utils: when exporting a group/name variable replace '/' with '_'
168
+ * Copyright
169
+ * Add examples
170
+ * Pathname.cd
171
+ * find_files
172
+ * find_file
173
+ * RunSimple: add error: nil
174
+ * Add pathname#split_all
175
+ * Pathname: add may_exist?
176
+ * Pathname: add chattr
177
+ * More documentation on SH.find
178
+ * FileUtils#chmod requires the mode as first parameter
179
+ * rsync: the option is --suffix, not --backup-suffix
180
+ * rsync: Add expected: 23
181
+ * rsync: add option to clean output directory
182
+ * rsync: add :delete option
183
+ * Add rsync helper
184
+ * Rename Sh#commands to Sh#sh_commands
185
+ * Add Sh.commands
186
+ * Add Pathname#copy_entry
187
+ * rmtree should be an alias to rm_rf
188
+ * Add backup mode to filewrite, and mkpath mode ton on_*
189
+ * Add rsync helper
190
+ * Pathname#squel_dir: allow to pass mkpath
191
+ * SH.find now accept a :prune options
192
+ * We should check if CLILogging has @@logger, not Module
193
+ * Move options.rb to another library
194
+ * mv_and_ln.rb: dereference
195
+ * mv_and_ln: options tweaks
196
+ * Add mv_and_ln.rb
197
+ * Pathname: use convert_path in rel_path_to
198
+ * Handle relative_path_from errors
199
+ * Add new mode to not dereference symlink in some actions
200
+ * abs_to_rel.rb
201
+ * A binary using the features of pathname
202
+ * Inner scope
203
+ * Add helper functions
204
+ * find: add a depth first option
205
+ * find: rename depth to max_depth
206
+ * Pathname#squel_dir: we need to use squel inside the find
207
+ * Bugfixes in Pathname and ShUtils.find
208
+ * Pathname#squel_dir
209
+ * Start working on Pathname#squel
210
+ * Pathname: add rel_path_from
211
+ * Use included hooks
212
+ * Put pathname functionality into Modules and Classes
213
+ * Correct constant reference
214
+ * Pathname: fix aliases
215
+ * SH::Pathname: put changes in module so that other class can use them
216
+ * Merge branch 'master-imb'
217
+ * Small bug fixes
218
+ * SH.find: any filter should prevent the yield
219
+ * Pathname: factorize the new name usual method
220
+ * Pathname: new_name
221
+ * Implement SH::Pathname#follow
222
+ * Add Pathname#rel_path
223
+ * Readme: Add warning
224
+ * pathname.rb: raise some Errors if needed
225
+ * Implement a 'magic' rm function
226
+ * Pathname: use FileUtils when possible
227
+ * Add a module LogHelper to help setup logging
228
+ * Add a fallback if SimpleColor is not found
229
+ * Some files/names have changed
230
+ * Rename SH to ShellHelpers
231
+
232
+ == Release v0.1.0 (2015-02-24) ==
233
+
234
+ * Add dependency
235
+ * Description
236
+ * Add library
237
+ * Initial commit.
4
238
 
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright © 2015–2018 Damien Robert
1
+ Copyright © 2015–2020 Damien Robert
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -1,15 +1,5 @@
1
1
  require 'rake'
2
2
 
3
- begin
4
- require 'rubygems/tasks'
5
- Gem::Tasks.new(sign: {checksum: true, pgp: true},
6
- scm: {status: true}) do |tasks|
7
- tasks.console.command = 'pry'
8
- end
9
- rescue LoadError => e
10
- warn e.message
11
- end
12
-
13
3
  require 'rake/testtask'
14
4
  Rake::TestTask.new do |test|
15
5
  test.libs << 'test'
@@ -27,3 +17,10 @@ rescue LoadError => e
27
17
  end
28
18
  task :doc => :yard
29
19
 
20
+ begin
21
+ require 'dr/rake_gems'
22
+ Gem::MyTasks.new
23
+ rescue LoadError => e
24
+ warn e.message
25
+ end
26
+
data/gemspec.yml CHANGED
@@ -9,6 +9,7 @@ homepage: https://github.com/DamienRobert/shell_helpers#readme
9
9
 
10
10
  dependencies:
11
11
  drain: ~> 0.2
12
+ simplecolor: ~> 0.2
12
13
  development_dependencies:
13
14
  minitest: "~> 5.0"
14
15
  rake: "~> 10"
data/lib/shell_helpers.rb CHANGED
@@ -23,37 +23,75 @@ module ShellHelpers
23
23
  include Export #export
24
24
  include Utils #find, run_pager, rsync...
25
25
  include SysUtils #mount, find_devices...
26
- extend self
27
- #activates debug mode
28
- def self.debug(level=Logger::DEBUG)
29
- #activates logging on Pathname
30
- Pathname.send(:include, CLILogging)
31
- logger.level=(level)
26
+
27
+ module LogHelpers
28
+ #activates debug mode
29
+ def debug(level=true)
30
+ #activates logging on Pathname
31
+ Pathname.send(:include, CLILogging)
32
+ logger.cli_level(level, active: Logger::DEBUG)
33
+ end
34
+
35
+ def log(*args, **kw)
36
+ logger.add(*args, **kw)
37
+ end
38
+
39
+ # add standard log options to an OptParse instance
40
+ def log_options(opt, recipient)
41
+ opt.on("--[no-]color", "Colorize output", "Default to #{recipient[:color]}") do |v|
42
+ recipient[:color]=v
43
+ end
44
+
45
+ opt.on("--debug", "=[level]", "Activate debug informations", "Use `--debug=pry` to launch the pry debugger", "Default to #{recipient[:debug]}") do |v|
46
+ recipient[:debug]=v
47
+ end
48
+
49
+ opt.on("--log", "=[level]", "Set log level", "Default to #{recipient[:loglevel]}.") do |v|
50
+ recipient[:loglevel]=v
51
+ end
52
+
53
+ opt.on("--[no-]verbose", "-v", "Verbose mode", "Similar to --log=verbose") do |v|
54
+ recipient[:loglevel]=:verbose if v
55
+ end
56
+
57
+ opt.on("--vv", "Verbose mode 2", "Similar to --log=verbose2") do |v|
58
+ recipient[:loglevel]=:verbose2 if v
59
+ end
60
+
61
+ opt.on("--vvv", "Verbose mode 3", "Similar to --log=verbose3") do |v|
62
+ recipient[:loglevel]=:verbose3 if v
63
+ end
64
+
65
+ opt.on("--[no-]quiet", "-q", "Quiet mode", "Similar to --log=warn") do |v|
66
+ recipient[:loglevel]=:warn if v
67
+ end
68
+ end
69
+
70
+ def process_log_options(recipient)
71
+ SimpleColor.enabled=recipient[:color] if recipient.key?(:color)
72
+ SH.logger.cli_level(recipient[:loglevel]) if recipient.key?(:loglevel)
73
+ if recipient.key?(:debug)
74
+ debug=recipient[:debug]
75
+ if debug=="pry"
76
+ puts "# Launching pry"
77
+ require 'pry'; binding.pry
78
+ elsif debug
79
+ SH.debug(debug)
80
+ end
81
+ end
82
+ end
32
83
  end
84
+ include LogHelpers
85
+
86
+ extend self
87
+
33
88
  #include SH::FU to add FileUtils
34
89
  module FU
35
90
  include ::FileUtils
36
91
  include ::ShellHelpers
37
92
  extend self
38
93
  end
39
-
40
- # #include LogHelper to set up CLILogging with some convenience facilities
41
- # module LogHelper
42
- # include CLILogging
43
- # CLILogging.logger.progname||=$0
44
- # # #Activates Sh.sh in klass
45
- # # def self.included(klass)
46
- # # klass.const_set(:Sh,ShellHelpers::Sh)
47
- # # end
48
- # end
49
94
  end
50
95
 
51
96
  #for the lazy
52
97
  SH=ShellHelpers
53
-
54
- ## # SHLog.sh to get logging
55
- ## module SHLog
56
- ## include ShellHelpers
57
- ## include ShellHelpers::ShLog
58
- ## end
59
-
@@ -82,13 +82,13 @@ module ShellHelpers
82
82
  #from {ploum: plim} return something like
83
83
  #PLOUM=plim
84
84
  #that can be evaluated by the shell
85
- def export_variables(hash, local: false, export: false, prefix:"",upcase:true)
85
+ def export_variables(hash, inline: false, local: false, export: false, prefix:"",upcase:true)
86
86
  names=hash.keys.map {|s| escape_name(s,prefix:prefix,upcase:upcase)}
87
87
  r=""
88
88
  r+="local #{names.join(" ")}\n" if local
89
- hash.each do |k,v|
90
- r+=export_variable(k,v,prefix:prefix,upcase:upcase)
91
- end
89
+ r+=hash.map do |k,v|
90
+ export_variable(k,v,prefix:prefix,upcase:upcase).chomp
91
+ end.join(inline ? " " : "\n") + (inline ? "" : "\n")
92
92
  r+="export #{names.join(" ")}\n" if export
93
93
  return r
94
94
  end
@@ -0,0 +1,481 @@
1
+ # vim: foldmethod=marker
2
+ #From methadone (cli_logger.rb, cli_logging.rb, last import: 4626a2bca9b6e54077a06a0f8e11a04fadc6e7ae; 2017-01-19)
3
+ require 'logger'
4
+ require 'simplecolor'
5
+
6
+ module ShellHelpers
7
+ # like Logger but with more levels
8
+ class MoreLogger < Logger #{{{1
9
+ class Formatter < Logger::Formatter
10
+ def self.format_severity(severity)
11
+ Logger::SEV_LABEL[severity] || 'ANY'
12
+ end
13
+
14
+ def call(severity, time, progname, msg)
15
+ severity=self.class.format_severity(severity)
16
+ Format % [severity[0..0], format_datetime(time), $$, severity, progname,
17
+ msg2str(msg)]
18
+ end
19
+ end
20
+
21
+ WrongLevel=Class.new(StandardError)
22
+
23
+ module Levels
24
+ #note Logger::Severity is included into Logger, so we can access the severity levels directly
25
+ DEBUG1=0 #=DEBUG
26
+ DEBUG2=-0.1
27
+ DEBUG3=-0.2
28
+ IMPORTANT=1.5 #between warning and info
29
+ SUCCESS=1.3 #between warning and info
30
+ VERBOSE=0.9
31
+ VERBOSE1=0.9
32
+ VERBOSE2=0.8
33
+ VERBOSE3=0.7
34
+ QUIET=-9
35
+ DEBUG = Logger::DEBUG # 0
36
+ INFO = Logger::INFO # 1
37
+ WARN = Logger::WARN # 2
38
+ ERROR = Logger::ERROR # 3
39
+ FATAL = Logger::FATAL # 4
40
+ UNKNOWN = Logger::UNKNOWN # 5
41
+ end
42
+
43
+ LOG_LEVELS=
44
+ {
45
+ 'quiet' => Levels::QUIET,
46
+ 'debug3' => Levels::DEBUG3,
47
+ 'debug2' => Levels::DEBUG2,
48
+ 'debug1' => Levels::DEBUG1,
49
+ 'debug' => Levels::DEBUG, #0
50
+ 'verbose' => Levels::VERBOSE,
51
+ 'verbose1' => Levels::VERBOSE1,
52
+ 'verbose2' => Levels::VERBOSE2,
53
+ 'verbose3' => Levels::VERBOSE3,
54
+ 'info' => Levels::INFO, #1
55
+ 'success' => Levels::SUCCESS,
56
+ 'important' => Levels::IMPORTANT,
57
+ 'warn' => Levels::WARN, #2
58
+ 'error' => Levels::ERROR, #3
59
+ 'fatal' => Levels::FATAL, #4
60
+ 'unknown' => Levels::UNKNOWN, #5
61
+ }
62
+
63
+ def log_levels
64
+ @levels ||= LOG_LEVELS.dup
65
+ @levels
66
+ end
67
+
68
+ attr_accessor :default, :active, :quiet
69
+
70
+ def initialize(*args, levels: {}, default: :info, active: :verbose, quiet: :warn, **kwds)
71
+ @default=default
72
+ @active=active
73
+ @quiet=quiet
74
+ super(*args, **kwds)
75
+ @default_formatter = Formatter.new
76
+ @level=severity_lvl(@default)
77
+ klass=self.singleton_class
78
+ levels=log_levels.merge!(levels)
79
+ levels.keys.each do |lvl, cst|
80
+ klass.define_method(lvl.to_sym) do |progname=nil, **opts, &block|
81
+ add(lvl.to_sym, nil, progname, **opts, &block)
82
+ end
83
+ klass.define_method("#{lvl}?".to_sym) do
84
+ @level <= cst
85
+ end
86
+ end
87
+ end
88
+
89
+ # log with given security. Also accepts 'true'
90
+ def add(severity, message = nil, progname = nil, default: @default, quiet: @quiet, callback: nil)
91
+ severity=severity(severity, default: default, quiet: quiet)
92
+ severity_lvl=severity_lvl(severity)
93
+ if @logdev.nil? or severity_lvl < @level
94
+ return true
95
+ end
96
+ if progname.nil?
97
+ progname = @progname
98
+ end
99
+ if message.nil?
100
+ if block_given?
101
+ message = yield
102
+ else
103
+ message = progname
104
+ progname = @progname
105
+ end
106
+ end
107
+ callback.call(message, progname, severity) if callback
108
+ @logdev.write(
109
+ format_message(format_severity(severity), Time.now, progname, message))
110
+ true
111
+ end
112
+
113
+ def severity(severity, default: @default, quiet: @quiet)
114
+ severity ||= UNKNOWN
115
+ severity=default if severity == true
116
+ severity=quiet if severity == false
117
+ severity
118
+ end
119
+
120
+ def severity_lvl(severity, **opts)
121
+ severity=severity(severity, **opts)
122
+ if severity.is_a?(Numeric)
123
+ return severity
124
+ else
125
+ sev=severity.to_s.downcase
126
+ if log_levels.key?(sev)
127
+ return log_levels[sev]
128
+ else
129
+ raise WrongLevel.new(severity)
130
+ end
131
+ end
132
+ end
133
+
134
+ def level=(severity)
135
+ @level = severity_lvl(severity)
136
+ end
137
+
138
+ # like level= but for clis, so we can pass a default if level=true
139
+ def cli_level(level, active: @active, quiet: @quiet)
140
+ level=active if level==true #for cli
141
+ level=quiet if level==false #for cli
142
+ self.level=level
143
+ end
144
+ end
145
+
146
+ class ColorLogger < MoreLogger #{{{1
147
+ CLI_COLORS_BASE={
148
+ # info: [:bold],
149
+ success: [:green, :bold],
150
+ important: [:blue, :bold],
151
+ warn: [:yellow, :bold],
152
+ error: [:red, :bold],
153
+ fatal: [:red, :bold]
154
+ }
155
+
156
+ CLI_COLORS={
157
+ mark: {lvl: :info, colors: :bold}
158
+ }
159
+
160
+ def cli_colors
161
+ return @cli_colors if defined?(@cli_colors)
162
+ @cli_colors={}
163
+ base_colors=CLI_COLORS_BASE
164
+ base_colors.each do |k,v|
165
+ r={colors: v}
166
+ @cli_colors[k.to_sym]=r
167
+ end
168
+ @cli_colors.merge!(CLI_COLORS)
169
+ @cli_colors
170
+ #mode => {lvl: lvl, colors: colors }
171
+ end
172
+
173
+ def add(severity, message = nil, progname = nil, color: [], raw: @raw, **args)
174
+ severity ||= UNKNOWN
175
+ severity=severity(severity)
176
+ color=[*color]
177
+ unless severity.is_a?(Numeric)
178
+ cli=cli_colors[severity.to_sym]
179
+ if cli
180
+ severity=cli[:lvl] if cli.key?(:lvl)
181
+ if !raw
182
+ color=[*cli[:colors]] + color
183
+ end
184
+ end
185
+ end
186
+ severity_lvl=severity_lvl(severity)
187
+
188
+ if @logdev.nil? or severity_lvl < @level
189
+ return true
190
+ end
191
+ if progname.nil?
192
+ progname = @progname
193
+ end
194
+ if message.nil?
195
+ if block_given?
196
+ message = yield
197
+ else
198
+ message = progname
199
+ progname = @progname
200
+ end
201
+ end
202
+ message = SimpleColor.color(message.to_s, *color)
203
+ super(severity_lvl, message, progname)
204
+ end
205
+
206
+ attr_accessor :raw
207
+
208
+ def initialize(*args, cli: {}, **kwds)
209
+ @raw=false
210
+ super(*args, **kwds)
211
+ klass=self.singleton_class
212
+ cli=cli_colors.merge!(cli)
213
+ (cli.keys - CLI_COLORS_BASE.keys).each do |lvl|
214
+ klass.define_method(lvl.to_sym) do |progname=nil, **opts, &block|
215
+ add(lvl, nil, progname, **opts, &block)
216
+ end
217
+ end
218
+ yield self if block_given?
219
+ end
220
+ end
221
+
222
+ # CLILogger {{{1
223
+ # A Logger instance that gives better control of messaging the user and
224
+ # logging app activity. At it's most basic, you would use <tt>info</tt>
225
+ # as a replacement for +puts+ and <tt>error</tt> as a replacement for
226
+ # <tt>STDERR.puts</tt>. Since this is a logger, however, you can also
227
+ # use #debug, #warn, and #fatal, and you can control the format and
228
+ # "logging level" as such.
229
+ #
230
+ # So, by default:
231
+ # * debug messages do not appear anywhere
232
+ # * info messages appear on the standard output
233
+ # * warn, error, and fatal message appear on the standard error
234
+ # * The default format of messages is simply the message, no logging
235
+ # cruft, however if your output is redirected to a file, a better
236
+ # timestamped logging format is used
237
+ #
238
+ # You can customize this in several ways:
239
+ # * You can override the devices used by passing different devices to the constructor
240
+ # * You can adjust the level of message that goes to the error logger via error_level=
241
+ # * You can adjust the format for messages to the error logger separately via error_formatter=
242
+ #
243
+ # === Example
244
+ #
245
+ # logger = CLILogger.new
246
+ # logger.debug("Starting up") # => only the standard output gets this
247
+ # logger.warn("careful!") # => only the standard error gets this
248
+ # logger.error("Something went wrong!") # => only the standard error gets this
249
+ #
250
+ # logger = CLILogger.new
251
+ # logger.error_level = Logger::ERROR
252
+ # logger.debug("Starting up") # => only the standard output gets this
253
+ # logger.warn("careful!") # => only the standard OUTPUT gets this
254
+ # logger.error("Something went wrong!") # => only the standard error gets this
255
+ #
256
+ # logger = CLILogger.new('logfile.txt')
257
+ # logger.debug("Starting up") #=> logfile.txt gets this
258
+ # logger.error("Something went wrong!") # => BOTH logfile.txt AND the standard error get this
259
+ class CLILogger < ColorLogger
260
+ BLANK_FORMAT = lambda { |severity,datetime,progname,msg|
261
+ msg + "\n"
262
+ }
263
+
264
+ # Helper to proxy methods to the super class AND to the internal error logger
265
+ # +symbol+:: Symbol for name of the method to proxy
266
+ def self.proxy_method(symbol) #:nodoc:
267
+ old_name = "old_#{symbol}".to_sym
268
+ alias_method old_name,symbol
269
+ define_method symbol do |*args,&block|
270
+ send(old_name,*args,&block)
271
+ @stderr_logger.send(symbol,*args,&block)
272
+ end
273
+ end
274
+
275
+ proxy_method :'formatter='
276
+ proxy_method :'progname='
277
+ proxy_method :'datetime_format='
278
+
279
+ def add(severity, message = nil, progname = nil, **opts, &block) #:nodoc:
280
+ severity_lvl = severity_lvl(severity)
281
+ if @split_logs
282
+ unless severity_lvl >= @stderr_logger.level
283
+ super(severity,message,progname, **opts, &block)
284
+ end
285
+ else
286
+ super(severity,message,progname,**opts, &block)
287
+ end
288
+ @stderr_logger.add(severity,message,progname,**opts, &block)
289
+ end
290
+
291
+ DEFAULT_ERROR_LEVEL = Logger::Severity::WARN
292
+
293
+ # A logger that logs error-type messages to a second device; useful for
294
+ # ensuring that error messages go to standard error. This should be
295
+ # pretty smart about doing the right thing. If both log devices are
296
+ # ttys, e.g. one is going to standard error and the other to the
297
+ # standard output, messages only appear once in the overall output
298
+ # stream. In other words, an ERROR logged will show up *only* in the
299
+ # standard error. If either log device is NOT a tty, then all messages
300
+ # go to +log_device+ and only errors go to +error_device+
301
+ #
302
+ # +log_device+:: device where all log messages should go, based on level
303
+ # By default, this is Logger::Severity::WARN
304
+ # +error_device+:: device where all error messages should go.
305
+ def initialize(log_device=$stdout,error_device=$stderr,
306
+ split_log: :auto, default_error: DEFAULT_ERROR_LEVEL, **kwds)
307
+ @stderr_logger = MoreLogger.new(error_device, default: default_error, **kwds)
308
+
309
+ super(log_device, **kwds)
310
+
311
+ log_device_tty = tty?(log_device)
312
+ error_device_tty = tty?(error_device)
313
+
314
+ @split_logs = log_device_tty && error_device_tty if split_log==:auto
315
+
316
+ self.level = Logger::Severity::INFO
317
+ @stderr_logger.level = @stderr_logger.default
318
+
319
+ self.formatter = BLANK_FORMAT if log_device_tty
320
+ @stderr_logger.formatter = BLANK_FORMAT if error_device_tty
321
+ yield self, @stderr_logger if block_given?
322
+ end
323
+
324
+ def level=(level)
325
+ super
326
+ #current_error_level = @stderr_logger.level
327
+ if (self.level > @stderr_logger.default) && @split_logs
328
+ @stderr_logger.level = self.level
329
+ end
330
+ end
331
+
332
+ def cli_level(*args)
333
+ super
334
+ if (self.level > @stderr_logger.default) && @split_logs
335
+ @stderr_logger.level = self.level
336
+ end
337
+ end
338
+
339
+ # Set the threshold for what messages go to the error device. Note
340
+ # that calling #level= will *not* affect the error logger *unless* both
341
+ # devices are TTYs.
342
+ # +level+:: a constant from Logger::Severity for the level of messages that should go to the error logger
343
+ def error_level=(level)
344
+ @stderr_logger.level = level
345
+ end
346
+
347
+ # Overrides the formatter for the error logger. A future call to
348
+ # #formatter= will affect both, so the order of the calls matters.
349
+ # +formatter+:: Proc that handles the formatting, the same as for #formatter=
350
+ def error_formatter=(formatter)
351
+ @stderr_logger.formatter=formatter
352
+ end
353
+
354
+ private def tty?(device_or_string)
355
+ return device_or_string.tty? if device_or_string.respond_to? :tty?
356
+ false
357
+ end
358
+
359
+ #log the action and execute it
360
+ #Severity is Logger:: DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
361
+ def log_and_do(*args, severity: INFO, definee: self, **opts, &block)
362
+ msg="log_and_do #{args} on #{self}"
363
+ msg+=" with options #{opts}" unless opts.empty?
364
+ msg+=" with block #{block}" if block
365
+ add(severity,msg)
366
+ if opts.empty?
367
+ definee.send(*args, &block)
368
+ else
369
+ definee.send(*args, **opts, &block)
370
+ end
371
+ end
372
+
373
+ private def toggle_log_level(toggle='debug')
374
+ @log_level_original = self.level unless @log_level_toggled
375
+ logger.level = if @log_level_toggled
376
+ @log_level_original
377
+ else
378
+ log_levels.fetch(toggle)
379
+ end
380
+ @log_level_toggled = !@log_level_toggled
381
+ @log_level = logger.level
382
+ end
383
+
384
+ #call logger.setup_toggle_trap('USR1') to change the log level to
385
+ #:debug when USR1 is received
386
+ def setup_toggle_trap(signal)
387
+ if signal
388
+ Signal.trap(signal) do
389
+ toggle_log_level
390
+ end
391
+ end
392
+ end
393
+
394
+ end
395
+
396
+ # CLILogging {{{1
397
+ # Provides easier access to a shared DR::CLILogger instance.
398
+ # Include this module into your class, and #logger provides access to a
399
+ # shared logger. This is handy if you want all of your clases to have
400
+ # access to the same logger, but don't want to (or aren't able to) pass
401
+ # it around to each class.
402
+ # This also provides methods for direct logging without going through the
403
+ # #logger
404
+ #
405
+ # === Example
406
+ #
407
+ # class MyClass
408
+ # include DR::CLILogging
409
+ #
410
+ # def doit
411
+ # debug("About to doit!")
412
+ # if results
413
+ # info("We did it!")
414
+ # else
415
+ # error("Something went wrong")
416
+ # end
417
+ # debug("Done doing it")
418
+ # end
419
+ # end
420
+ #
421
+ # Note that every class that mixes this in shares the *same logger
422
+ # instance*, so if you call #change_logger, this will change the logger
423
+ # for all classes that mix this in. This is likely what you want.
424
+ module CLILogging
425
+ extend self
426
+
427
+ # Access the shared logger. All classes that include this module
428
+ # will get the same logger via this method.
429
+ def logger
430
+ unless CLILogging.class_variable_defined?(:@@logger)
431
+ @@logger = CLILogger.new
432
+ @@logger.progname=$0
433
+ end
434
+ @@logger
435
+ end
436
+
437
+ self.logger.progname||=$0
438
+
439
+ # Change the global logger that includers will use. Useful if you
440
+ # don't want the default configured logger. Note that the
441
+ # +change_logger+ version is preferred because Ruby will often parse
442
+ # <tt>logger = Logger.new</tt> as the declaration of, and assignment
443
+ # to, of a local variable. You'd need to do
444
+ # <tt>self.logger=Logger.new</tt> to be sure. This method is a bit
445
+ # easier.
446
+ #
447
+ # +new_logger+:: the new logger. May not be nil and should be a logger of some kind
448
+ def change_logger(new_logger)
449
+ raise ArgumentError,"Logger may not be nil" if new_logger.nil?
450
+ @@logger = new_logger
451
+ @@logger.level = @log_level if defined?(@log_level) && @log_level
452
+ end
453
+
454
+ alias logger= change_logger
455
+
456
+ #call CLILogging.setup_toggle_trap('USR1') to change the log level to
457
+ #:debug when USR1 is received
458
+ def self.setup_toggle_trap(signal)
459
+ logger.setup_toggle_trap(signal)
460
+ end
461
+
462
+ def log_and_do(*args)
463
+ logger.log_and_do(*args)
464
+ end
465
+
466
+ LOG_LEVELS=logger.log_levels
467
+
468
+ #Include this in place of CLILogging if you prefer to use
469
+ #info directly rather than logger.info
470
+ module Shortcuts #{{{
471
+ extend self
472
+ include CLILogging
473
+ LOG_LEVELS.each do |lvl, _cst|
474
+ define_method(lvl.to_sym) do |progname=nil, &block|
475
+ logger.send(lvl.to_sym, progname, &block)
476
+ end
477
+ end
478
+ end
479
+ #}}}
480
+ end #}}}
481
+ end