brut 0.18.2 → 0.19.1
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/lib/brut/cli/apps/build_assets.rb +62 -31
- data/lib/brut/cli/apps/db.rb +198 -78
- data/lib/brut/cli/apps/deploy.rb +216 -140
- data/lib/brut/cli/apps/new/app.rb +127 -41
- data/lib/brut/cli/apps/scaffold.rb +108 -105
- data/lib/brut/cli/apps/test.rb +45 -26
- data/lib/brut/cli/commands/base_command.rb +58 -22
- data/lib/brut/cli/commands/compound_command.rb +2 -4
- data/lib/brut/cli/commands/execution_context.rb +17 -10
- data/lib/brut/cli/commands/help.rb +112 -6
- data/lib/brut/cli/commands/output_error.rb +1 -1
- data/lib/brut/cli/execute_result.rb +4 -0
- data/lib/brut/cli/executor.rb +7 -7
- data/lib/brut/cli/logger.rb +122 -0
- data/lib/brut/cli/output.rb +9 -45
- data/lib/brut/cli/parsed_command_line.rb +33 -10
- data/lib/brut/cli/runner.rb +37 -8
- data/lib/brut/cli/terminal.rb +74 -0
- data/lib/brut/cli/terminal_theme.rb +131 -0
- data/lib/brut/cli.rb +7 -3
- data/lib/brut/framework/mcp.rb +4 -3
- data/lib/brut/front_end/asset_metadata.rb +9 -5
- data/lib/brut/spec_support/cli_command_support.rb +9 -3
- data/lib/brut/spec_support/e2e_test_server.rb +3 -3
- data/lib/brut/tui/script.rb +1 -1
- data/lib/brut/version.rb +1 -1
- metadata +18 -4
- data/lib/brut/cli/apps/deploy_base.rb +0 -86
- data/lib/brut/cli/apps/heroku_container_based_deploy.rb +0 -226
- data/templates/segments/Heroku/bin/deploy +0 -11
|
@@ -19,15 +19,27 @@ class Brut::CLI::Commands::BaseCommand
|
|
|
19
19
|
self.run
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def delegate_to_command(command,execution_context=:use_ivar)
|
|
23
|
+
execution_context = if execution_context == :use_ivar
|
|
24
|
+
@execution_context
|
|
25
|
+
else
|
|
26
|
+
execution_context
|
|
27
|
+
end
|
|
28
|
+
if execution_context.nil?
|
|
29
|
+
raise ArgumentError, "No execution context provided and none set on this command"
|
|
30
|
+
end
|
|
31
|
+
command.execute(execution_context)
|
|
32
|
+
end
|
|
33
|
+
|
|
22
34
|
# True if the command requires Brut to fully bootstrap and start itself up. Bootstrapping isn't running a web server but it will
|
|
23
35
|
# do everything else, including connecting too all databases. Your command should return true for this if it needs to access a database
|
|
24
36
|
# or make API calls outside `Brut::CLI`. If this returns false, Brut's configuration options will still be available.
|
|
25
37
|
#
|
|
26
|
-
# By default, this returns
|
|
38
|
+
# By default, this returns false
|
|
27
39
|
#
|
|
28
40
|
# @return [true|false] True if Brut should be fully bootstrap and connect to all database servers (e.g.). False if Brut should only
|
|
29
41
|
# set up its configuration options.
|
|
30
|
-
def bootstrap? =
|
|
42
|
+
def bootstrap? = false
|
|
31
43
|
|
|
32
44
|
# The default `RACK_ENV` to use for this command. This value is used when no `RACK_ENV` is present in the UNIX environment
|
|
33
45
|
# and when `--env` has not been used on the command line. Do note that setting this in an app or parent command does
|
|
@@ -35,11 +47,15 @@ class Brut::CLI::Commands::BaseCommand
|
|
|
35
47
|
#
|
|
36
48
|
# @return [String|nil] If nil, Brut configuration will not be loaded and the command will run more or less as if it were a plain
|
|
37
49
|
# Ruby script. If a `String`, this value will be set as the `RACK_ENV` if it's not been otherwise specified.
|
|
38
|
-
def default_rack_env =
|
|
50
|
+
def default_rack_env = nil
|
|
39
51
|
|
|
40
52
|
# @return [String] description of this command for use in help output
|
|
41
53
|
def description = ""
|
|
42
54
|
|
|
55
|
+
# Returns a more detaile description of the command. This can includes paragraphs which will be maintained, however
|
|
56
|
+
# any additional formatting is not rendered or used.
|
|
57
|
+
def detailed_description = nil
|
|
58
|
+
|
|
43
59
|
# @return [String] description of the arguments this command accepts. Used for documentation only.
|
|
44
60
|
def args_description = nil
|
|
45
61
|
|
|
@@ -85,13 +101,6 @@ class Brut::CLI::Commands::BaseCommand
|
|
|
85
101
|
# where index 0 is the class and index 1 is a proc to convert the command line argument to the class's type.
|
|
86
102
|
def accepts = []
|
|
87
103
|
|
|
88
|
-
# Command to run if none provided on the command line.
|
|
89
|
-
# @return [Brut::CLI::Commands::BaseCommand|nil] if `nil`, it is an error to run this command without a subcommand. If not-`nil`,
|
|
90
|
-
# the returned command will be executed.
|
|
91
|
-
def default_command = self.commands.detect { it.class == default_command_class }
|
|
92
|
-
|
|
93
|
-
def default_command_class = nil
|
|
94
|
-
|
|
95
104
|
# Returns a list of commands that represent the subcommands available to this command. By default, this
|
|
96
105
|
# will return all commands that are inner classes of this command.
|
|
97
106
|
#
|
|
@@ -104,6 +113,11 @@ class Brut::CLI::Commands::BaseCommand
|
|
|
104
113
|
}.map(&:new)
|
|
105
114
|
end
|
|
106
115
|
|
|
116
|
+
def theme
|
|
117
|
+
@theme = Brut::CLI::TerminalTheme.new(terminal:)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
|
|
107
121
|
private
|
|
108
122
|
|
|
109
123
|
# Provides access the `Brut::CLI::Commands::ExecutionContext` used the last time `#execute` was called.
|
|
@@ -121,25 +135,48 @@ private
|
|
|
121
135
|
# Convienience methods to defer to `Brut::CLI::Commands::ExecutionContext`'s `Brut::CLI::Executor#system!`.
|
|
122
136
|
# @!visibility public
|
|
123
137
|
def system!(*args,&block)
|
|
124
|
-
|
|
138
|
+
output = ""
|
|
139
|
+
block ||= ->(output_chunk) {
|
|
140
|
+
output << output_chunk
|
|
141
|
+
}
|
|
142
|
+
begin
|
|
143
|
+
self.execution_context.executor.system!(*args,&block)
|
|
144
|
+
ensure
|
|
145
|
+
if output.length > 0
|
|
146
|
+
progname = args.detect { it.kind_of?(String) }.split(" ")
|
|
147
|
+
output.lines.each do |line|
|
|
148
|
+
self.execution_context.logger.add(Logger::INFO, line.chomp, progname)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
125
152
|
end
|
|
126
153
|
|
|
127
154
|
# Convienience methods to defer to `Brut::CLI::Commands::ExecutionContext#stdout`'s `puts`.
|
|
128
155
|
# @!visibility public
|
|
129
156
|
def puts(*args)
|
|
130
|
-
|
|
157
|
+
if !options.quiet?
|
|
158
|
+
self.execution_context.stdout.puts(*args)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
def print(*args)
|
|
162
|
+
if !options.quiet?
|
|
163
|
+
self.execution_context.stdout.print(*args)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def debug(message) = self.execution_context.logger.debug(message)
|
|
168
|
+
def info(message) = self.execution_context.logger.info(message)
|
|
169
|
+
def warn(message) = self.execution_context.logger.warn(message)
|
|
170
|
+
def error(message) = self.execution_context.logger.error(message)
|
|
171
|
+
def fatal(message) = self.execution_context.logger.fatal(message)
|
|
172
|
+
|
|
173
|
+
def terminal
|
|
174
|
+
@terminal ||= Brut::CLI::Terminal.new
|
|
131
175
|
end
|
|
132
176
|
|
|
133
|
-
# Convienience methods to defer to `Brut::CLI::Commands::ExecutionContext#stderr`. You should use this over `STDERR` or `$stderr`.
|
|
134
|
-
# @!visibility public
|
|
135
|
-
def stderr = self.execution_context.stderr
|
|
136
177
|
# Convienience methods to defer to `Brut::CLI::Commands::ExecutionContext#stdin`. You should use this over `STDIN` of `$stdin`.
|
|
137
178
|
# @!visibility public
|
|
138
179
|
def stdin = self.execution_context.stdin
|
|
139
|
-
# Convienience methods to defer to `Brut::CLI::Commands::ExecutionContext#stdout`. You should use this over `STDOUT` of `$stdout`. Note
|
|
140
|
-
# that if you just want to output a string, use `puts`.
|
|
141
|
-
# @!visibility public
|
|
142
|
-
def stdout = self.execution_context.stdout
|
|
143
180
|
# Convienience methods to defer to `Brut::CLI::Commands::ExecutionContext#options`.
|
|
144
181
|
# @!visibility public
|
|
145
182
|
def options = self.execution_context.options
|
|
@@ -163,12 +200,11 @@ private
|
|
|
163
200
|
# @!visibility public
|
|
164
201
|
def run
|
|
165
202
|
if argv[0]
|
|
166
|
-
|
|
203
|
+
puts theme.error.render("No such command '#{argv[0]}'")
|
|
167
204
|
return 1
|
|
168
205
|
end
|
|
169
206
|
|
|
170
|
-
|
|
171
|
-
stderr.puts "Command is required"
|
|
207
|
+
puts theme.error.render("Command is required")
|
|
172
208
|
1
|
|
173
209
|
end
|
|
174
210
|
end
|
|
@@ -16,12 +16,10 @@ class Brut::CLI::Commands::CompoundCommand < Brut::CLI::Commands::BaseCommand
|
|
|
16
16
|
def execute(execution_context)
|
|
17
17
|
@commands.each do |command|
|
|
18
18
|
execute_result = Brut::CLI::ExecuteResult.new do
|
|
19
|
-
command
|
|
19
|
+
delegate_to_command(command,execution_context)
|
|
20
20
|
end
|
|
21
21
|
if execute_result.failed?
|
|
22
|
-
return execute_result.
|
|
23
|
-
@stderr.puts error_message
|
|
24
|
-
end
|
|
22
|
+
return execute_result.actual_result
|
|
25
23
|
end
|
|
26
24
|
end
|
|
27
25
|
0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The context in which a command's execution is run. This holds essentially all values that are input to
|
|
2
2
|
# the command, including parsed command line options, unparsed arguments, the UNIX environment, and
|
|
3
|
-
# the three
|
|
3
|
+
# the three I/O streams (stderr, stdout, stdin).
|
|
4
4
|
class Brut::CLI::Commands::ExecutionContext
|
|
5
5
|
|
|
6
6
|
def initialize(
|
|
@@ -10,15 +10,21 @@ class Brut::CLI::Commands::ExecutionContext
|
|
|
10
10
|
stdout: :default,
|
|
11
11
|
stderr: :default,
|
|
12
12
|
stdin: :default,
|
|
13
|
-
executor: :default
|
|
13
|
+
executor: :default,
|
|
14
|
+
logger: :default
|
|
14
15
|
)
|
|
15
|
-
@argv
|
|
16
|
-
@options =
|
|
17
|
-
@env =
|
|
18
|
-
@stdout =
|
|
19
|
-
@stderr =
|
|
20
|
-
@stdin =
|
|
21
|
-
@
|
|
16
|
+
@argv = argv == :default ? [] : argv
|
|
17
|
+
@options = options == :default ? Brut::CLI::Options.new({}) : options
|
|
18
|
+
@env = env == :default ? {} : env
|
|
19
|
+
@stdout = stdout == :default ? $stdout : stdout
|
|
20
|
+
@stderr = stderr == :default ? $stderr : stderr
|
|
21
|
+
@stdin = stdin == :default ? $stdin : stdin
|
|
22
|
+
@logger = logger == :default ? Brut::CLI::Logger.new(app_name: $0, stdout: @stdout, stderr: @stderr) : logger
|
|
23
|
+
@executor = executor == :default ? Brut::CLI::Executor.new(logger: @logger, out: self.stdout, err: self.stderr) : executor
|
|
24
|
+
|
|
25
|
+
@logger.level = @options.log_level
|
|
26
|
+
@logger.log_file = @options.log_file
|
|
27
|
+
@logger.log_to_stdout = @options.log_stdout?
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
attr_reader :argv,
|
|
@@ -27,6 +33,7 @@ class Brut::CLI::Commands::ExecutionContext
|
|
|
27
33
|
:stdout,
|
|
28
34
|
:stderr,
|
|
29
35
|
:stdin,
|
|
30
|
-
:executor
|
|
36
|
+
:executor,
|
|
37
|
+
:logger
|
|
31
38
|
|
|
32
39
|
end
|
|
@@ -9,18 +9,124 @@ class Brut::CLI::Commands::Help < Brut::CLI::Commands::BaseCommand
|
|
|
9
9
|
|
|
10
10
|
def commands = []
|
|
11
11
|
|
|
12
|
+
class DescriptionList
|
|
13
|
+
def initialize(term_align: :right, padding_bottom: 1)
|
|
14
|
+
@term_align = term_align
|
|
15
|
+
@padding_bottom = padding_bottom
|
|
16
|
+
@rows = []
|
|
17
|
+
end
|
|
18
|
+
def <<(row)
|
|
19
|
+
@rows << row
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def lipgloss_table(theme:, terminal:)
|
|
23
|
+
indent = " "
|
|
24
|
+
term_width = @rows.map { |r| r[0].length }.max
|
|
25
|
+
description_width = terminal.cols - indent.length - term_width - 2
|
|
26
|
+
formatted_rows = @rows.map { |row|
|
|
27
|
+
[
|
|
28
|
+
row[0],
|
|
29
|
+
theme.wrap(row[1], indent: 0, max_width: description_width).strip,
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
Lipgloss::Table.new.
|
|
33
|
+
border(:hidden).
|
|
34
|
+
rows(formatted_rows).
|
|
35
|
+
style_func(rows: formatted_rows.length, columns: 2) { |row,column|
|
|
36
|
+
if column == 0
|
|
37
|
+
Lipgloss::Style.new.inherit(theme.subheader).padding_bottom(@padding_bottom).align(@term_align)
|
|
38
|
+
else
|
|
39
|
+
Lipgloss::Style.new.padding_bottom(@padding_bottom)
|
|
40
|
+
end
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
12
46
|
def run
|
|
13
|
-
|
|
47
|
+
cli = [@command.name ]
|
|
48
|
+
cmd = @command
|
|
49
|
+
while cmd.parent_command
|
|
50
|
+
cmd = cmd.parent_command
|
|
51
|
+
cli.unshift cmd.name
|
|
52
|
+
end
|
|
53
|
+
invocation = cli.join(" ")
|
|
54
|
+
puts theme.code.render(invocation) + " - " + theme.title.render(theme.wrap(@command.description, indent: invocation.length + 3, max_width: 65).strip)
|
|
55
|
+
puts
|
|
56
|
+
usage = theme.code.render(invocation)
|
|
57
|
+
options = @option_parser.top.list
|
|
58
|
+
if options.size > 0
|
|
59
|
+
usage << theme.weak.render(" [options]")
|
|
60
|
+
end
|
|
14
61
|
if @command.commands.any?
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@command.
|
|
19
|
-
|
|
62
|
+
usage << theme.code.render(" command")
|
|
63
|
+
end
|
|
64
|
+
if @command.args_description
|
|
65
|
+
usage << " #{@command.args_description}"
|
|
66
|
+
end
|
|
67
|
+
puts theme.header.render("USAGE")
|
|
68
|
+
puts
|
|
69
|
+
puts " " + usage
|
|
70
|
+
puts
|
|
71
|
+
if @command.detailed_description
|
|
72
|
+
puts theme.header.render("DESCRIPTION")
|
|
73
|
+
puts
|
|
74
|
+
puts theme.wrap(@command.detailed_description.strip, indent: 2, max_width: 65)
|
|
75
|
+
puts
|
|
76
|
+
end
|
|
77
|
+
if options.size > 0
|
|
78
|
+
puts theme.header.render("OPTIONS")
|
|
79
|
+
list = DescriptionList.new
|
|
80
|
+
options.each do |option|
|
|
81
|
+
switches = option.long.map { |switch|
|
|
82
|
+
if option.arg
|
|
83
|
+
if option.arg[0] == "="
|
|
84
|
+
"#{switch.strip}#{theme.weak.render(option.arg.strip)}"
|
|
85
|
+
else
|
|
86
|
+
"#{switch.strip}=#{theme.weak.render(option.arg.strip)}"
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
switch
|
|
90
|
+
end
|
|
91
|
+
} + option.short.map { |switch|
|
|
92
|
+
if option.arg
|
|
93
|
+
"#{switch} #{theme.weak.render(option.arg)}"
|
|
94
|
+
else
|
|
95
|
+
switch
|
|
96
|
+
end
|
|
97
|
+
}
|
|
98
|
+
list << [
|
|
99
|
+
switches.join("\n"),
|
|
100
|
+
option.desc.join(" "),
|
|
101
|
+
]
|
|
102
|
+
end
|
|
103
|
+
puts list.lipgloss_table(theme:, terminal:).render
|
|
104
|
+
end
|
|
105
|
+
if @command.env_vars.any?
|
|
106
|
+
puts theme.header.render("ENVIRONMENT VARIABLES")
|
|
107
|
+
list = DescriptionList.new
|
|
108
|
+
@command.env_vars.sort_by(&:first).each do |env_var|
|
|
109
|
+
list << [
|
|
110
|
+
env_var[0],
|
|
111
|
+
env_var[1],
|
|
112
|
+
]
|
|
20
113
|
end
|
|
114
|
+
puts list.lipgloss_table(theme:, terminal:).render
|
|
115
|
+
end
|
|
116
|
+
if @command.commands.any?
|
|
117
|
+
puts theme.header.render("COMMANDS")
|
|
118
|
+
list = DescriptionList.new(term_align: :left, padding_bottom: 0)
|
|
119
|
+
@command.commands.sort_by(&:name).each do |command|
|
|
120
|
+
list << [
|
|
121
|
+
command.name,
|
|
122
|
+
command.description,
|
|
123
|
+
]
|
|
124
|
+
end
|
|
125
|
+
puts list.lipgloss_table(theme:, terminal:).render
|
|
21
126
|
end
|
|
22
127
|
0
|
|
23
128
|
end
|
|
129
|
+
|
|
24
130
|
def bootstrap? = false
|
|
25
131
|
def default_rack_env = nil
|
|
26
132
|
end
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# Wraps the result of calling `Brut::CLI::Commands::BaseCommand#execute` and
|
|
2
2
|
# interpreting it as an exit code
|
|
3
3
|
class Brut::CLI::ExecuteResult
|
|
4
|
+
attr_reader :actual_result
|
|
4
5
|
def initialize(&block)
|
|
5
6
|
@actual_result = begin
|
|
6
7
|
block.()
|
|
7
8
|
rescue Brut::CLI::Error => ex
|
|
8
9
|
ex
|
|
10
|
+
rescue => ex2
|
|
11
|
+
raise
|
|
12
|
+
|
|
9
13
|
end
|
|
10
14
|
end
|
|
11
15
|
|
data/lib/brut/cli/executor.rb
CHANGED
|
@@ -9,11 +9,10 @@ require "open3"
|
|
|
9
9
|
class Brut::CLI::Executor
|
|
10
10
|
# Create the executor
|
|
11
11
|
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@
|
|
16
|
-
@err = err
|
|
12
|
+
def initialize(out:,err:,logger:)
|
|
13
|
+
@out = out
|
|
14
|
+
@err = err
|
|
15
|
+
@logger = logger
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
# Execute a command, logging it to the standard output and outputing the
|
|
@@ -57,7 +56,7 @@ class Brut::CLI::Executor
|
|
|
57
56
|
@out.flush
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
@
|
|
59
|
+
@logger.info "Executing #{args}"
|
|
61
60
|
wait_thread = Open3.popen3(*args) do |_stdin,stdout,stderr,wait_thread|
|
|
62
61
|
o = stdout.read_nonblock(10, exception: false)
|
|
63
62
|
e = stderr.read_nonblock(10, exception: false)
|
|
@@ -79,8 +78,9 @@ class Brut::CLI::Executor
|
|
|
79
78
|
wait_thread
|
|
80
79
|
end
|
|
81
80
|
if wait_thread.value.success?
|
|
82
|
-
@
|
|
81
|
+
@logger.info "#{args.length == 1 ? args[0] : args} succeeded"
|
|
83
82
|
else
|
|
83
|
+
@logger.info "'#{args.length == 1 ? args[0] : args}' failed with exit status #{wait_thread.value.exitstatus}"
|
|
84
84
|
raise Brut::CLI::SystemExecError.new(args,wait_thread.value.exitstatus)
|
|
85
85
|
end
|
|
86
86
|
0
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require "logger"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
5
|
+
class Brut::CLI::Logger < SimpleDelegator
|
|
6
|
+
def initialize(app_name:, stdout:, stderr:, theme: nil)
|
|
7
|
+
@app_name = app_name
|
|
8
|
+
@stdout = stdout
|
|
9
|
+
@logger = ::Logger.new("/dev/null")
|
|
10
|
+
|
|
11
|
+
super(@logger)
|
|
12
|
+
|
|
13
|
+
@simple_formatter = ->(severity, _time, progname, msg) {
|
|
14
|
+
formatted_severity = if theme
|
|
15
|
+
case severity
|
|
16
|
+
when "DEBUG"
|
|
17
|
+
theme.weak.render("DEBUG")
|
|
18
|
+
when "INFO"
|
|
19
|
+
theme.header.render("INFO")
|
|
20
|
+
when "WARN"
|
|
21
|
+
theme.warning.render("WARN")
|
|
22
|
+
when "ERROR"
|
|
23
|
+
theme.error.render("ERROR")
|
|
24
|
+
when "FATAL"
|
|
25
|
+
theme.error.render("FATAL")
|
|
26
|
+
else
|
|
27
|
+
severity
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
severity
|
|
31
|
+
end
|
|
32
|
+
[
|
|
33
|
+
theme ? theme.weak.render("[ #{progname} ]") : "[ #{progname} ]",
|
|
34
|
+
"{#{formatted_severity}}",
|
|
35
|
+
msg,
|
|
36
|
+
].join(" ") + "\n"
|
|
37
|
+
}
|
|
38
|
+
@file_formatter = ->(severity, time, progname, msg) {
|
|
39
|
+
"#{time} - [ #{progname} ]{#{severity}} #{msg}\n"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@stdout_logger = ::Logger.new("/dev/null")
|
|
43
|
+
@stderr_logger = if stderr.nil?
|
|
44
|
+
::Logger.new("/dev/null")
|
|
45
|
+
else
|
|
46
|
+
::Logger.new(stderr, progname: @app_name, formatter: @simple_formatter)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@log_file = nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def level=(level)
|
|
53
|
+
if level
|
|
54
|
+
@logger.level = level
|
|
55
|
+
@stdout_logger.level = level
|
|
56
|
+
@stderr_logger.level = level
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def log_file=(log_file)
|
|
61
|
+
@log_file = log_file
|
|
62
|
+
if log_file
|
|
63
|
+
log_dir = log_file.dirname
|
|
64
|
+
if !log_dir.exist?
|
|
65
|
+
FileUtils.mkdir_p(log_dir)
|
|
66
|
+
end
|
|
67
|
+
@logger = ::Logger.new(@log_file, formatter: @file_formatter, progname: @app_name, level: @logger.level)
|
|
68
|
+
__setobj__(@logger)
|
|
69
|
+
if @logger.level == ::Logger::DEBUG
|
|
70
|
+
@stdout.puts "Logging to file #{@log_file}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def log_to_stdout=(log_to_stdout)
|
|
76
|
+
@log_to_stdout = log_to_stdout
|
|
77
|
+
if @log_to_stdout
|
|
78
|
+
@stdout_logger = ::Logger.new(@stdout, formatter: @simple_formatter, progname: @app_name, level: @logger.level)
|
|
79
|
+
else
|
|
80
|
+
@stdout_logger = ::Logger.new("/dev/null")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
def debug(message=nil,&block)
|
|
84
|
+
@logger.debug(message,&block)
|
|
85
|
+
@stdout_logger.debug(message,&block)
|
|
86
|
+
end
|
|
87
|
+
def info(message=nil,&block)
|
|
88
|
+
@logger.info(message,&block)
|
|
89
|
+
@stdout_logger.info(message,&block)
|
|
90
|
+
end
|
|
91
|
+
def warn(message=nil,&block)
|
|
92
|
+
@logger.warn(message,&block)
|
|
93
|
+
@stdout_logger.warn(message,&block)
|
|
94
|
+
@stderr_logger.warn(message,&block)
|
|
95
|
+
end
|
|
96
|
+
def error(message=nil,&block)
|
|
97
|
+
@logger.error(message,&block)
|
|
98
|
+
@stdout_logger.error(message,&block)
|
|
99
|
+
@stderr_logger.error(message,&block)
|
|
100
|
+
end
|
|
101
|
+
def fatal(message=nil,&block)
|
|
102
|
+
@logger.fatal(message,&block)
|
|
103
|
+
@stdout_logger.fatal(message,&block)
|
|
104
|
+
@stderr_logger.fatal(message,&block)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def add(severity, message=nil, progname=nil, &block)
|
|
108
|
+
@logger.add(severity, message, progname, &block)
|
|
109
|
+
@stdout_logger.add(severity, message, progname, &block)
|
|
110
|
+
if severity >= ::Logger::WARN
|
|
111
|
+
@stderr_logger.add(severity, message, progname, &block)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def without_stderr
|
|
116
|
+
logger = self.class.new(app_name: @app_name, stdout: @stdout, stderr: nil)
|
|
117
|
+
logger.level = @logger.level
|
|
118
|
+
logger.log_file = @log_file
|
|
119
|
+
logger.log_to_stdout = @log_to_stdout
|
|
120
|
+
logger
|
|
121
|
+
end
|
|
122
|
+
end
|
data/lib/brut/cli/output.rb
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
# An `IO`-like class that provides user-helpful output, to be used in place of `puts
|
|
1
|
+
# An `IO`-like class that provides user-helpful output, to be used in place of `puts` and a logger. This is not replaceable for an IO.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# ```
|
|
8
|
-
# > bin/my_app doit
|
|
9
|
-
# [ bin/my_app ] About to do sometohing
|
|
10
|
-
# Cannot connect to house
|
|
11
|
-
# [ bin/my_app ] Problem connecting
|
|
12
|
-
# ```
|
|
13
|
-
#
|
|
14
|
-
# The prefix allows the human to know what output was generated by the app they ran and what not. This can be extremely helpful for
|
|
15
|
-
# debugging or just understanding what is going on.
|
|
3
|
+
# The problem this solves is allowing your CLI app to be clutter-free in the user's terminal, but to not hide information
|
|
4
|
+
# unnecessarily or make it unclear where the output is coming from. Your CLI should use this class (or the methods that proxy
|
|
5
|
+
# to it, see `Brut::CLI::Commands::BaseCommand`) for all output and logging.
|
|
16
6
|
class Brut::CLI::Output
|
|
17
7
|
|
|
18
8
|
def self.from_io(io_or_output)
|
|
@@ -20,7 +10,7 @@ class Brut::CLI::Output
|
|
|
20
10
|
in Brut::CLI::Output
|
|
21
11
|
io_or_output
|
|
22
12
|
else
|
|
23
|
-
self.new(io: io_or_output,
|
|
13
|
+
self.new(io: io_or_output, app_name: $0)
|
|
24
14
|
end
|
|
25
15
|
end
|
|
26
16
|
|
|
@@ -30,55 +20,29 @@ class Brut::CLI::Output
|
|
|
30
20
|
# Create a wrapper for the given IO that will use the given prefix
|
|
31
21
|
#
|
|
32
22
|
# @param [IO] io an IO where output should be sent, for example `$stdout`.
|
|
33
|
-
# @param [String]
|
|
34
|
-
def initialize(io:,
|
|
23
|
+
# @param [String] app_name the name of the app that would be using this to generate output.
|
|
24
|
+
def initialize(io:, app_name:)
|
|
35
25
|
@io = io
|
|
36
|
-
@
|
|
26
|
+
@app_name = app_name
|
|
37
27
|
@sync_status = @io.sync
|
|
38
28
|
end
|
|
39
29
|
|
|
40
|
-
# Calls `puts` without the prefix. This is useful if the output is going to be obvious to the human user (e.g. CLI help), or if
|
|
41
|
-
# it's intended to be piped into another app.
|
|
42
|
-
#
|
|
43
|
-
# @see https://ruby-doc.org/3.3.6/Kernel.html#method-i-puts
|
|
44
|
-
def puts_no_prefix(*objects)
|
|
45
|
-
@io.puts(*objects)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Calls `puts`, adding a prefix to each of the objects in `*objects`. This is useful for sending messages that a human may want to
|
|
49
|
-
# read.
|
|
50
|
-
#
|
|
51
30
|
# @see https://ruby-doc.org/3.3.6/Kernel.html#method-i-puts
|
|
52
31
|
def puts(*objects)
|
|
53
32
|
if objects.empty?
|
|
54
33
|
objects << ""
|
|
55
34
|
end
|
|
56
35
|
objects.each do |object|
|
|
57
|
-
@io.puts(
|
|
36
|
+
@io.puts(object)
|
|
58
37
|
end
|
|
59
38
|
nil
|
|
60
39
|
end
|
|
61
40
|
|
|
62
|
-
# Calls `print` without any prefix. In theory, this is the escape hatch to sending arbitrary data to the underlying `IO` without
|
|
63
|
-
# expose said `IO`
|
|
64
|
-
#
|
|
65
|
-
# @see https://ruby-doc.org/3.3.6/Kernel.html#method-i-print
|
|
66
|
-
def print(*objects)
|
|
67
|
-
@io.print(*objects)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
41
|
# Prints a string via `printf`, using the prefix. This is useful for communciating to a human, but you need more power for
|
|
71
42
|
# formatting than is afforded by {#puts}.
|
|
72
43
|
#
|
|
73
44
|
# @see https://ruby-doc.org/3.3.6/Kernel.html#method-i-printf
|
|
74
45
|
def printf(format_string,*objects)
|
|
75
|
-
@io.printf(@prefix + format_string,*objects)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Prints a string via `printf`, without the prefix.
|
|
79
|
-
#
|
|
80
|
-
# @see https://ruby-doc.org/3.3.6/Kernel.html#method-i-printf
|
|
81
|
-
def printf_no_prefix(format_string,*objects)
|
|
82
46
|
@io.printf(format_string,*objects)
|
|
83
47
|
end
|
|
84
48
|
|