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 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