diff --git a/CHANGELOG.md b/CHANGELOG.md index c3feb3e..b98b998 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change log +## [Unreleased] + +### Added +* Add :tag option to add custom tags to command output + ## [v0.10.1] - 2021-02-14 ### Fixed diff --git a/README.md b/README.md index 022717f..e1255b1 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,9 @@ Or install it yourself as: * [2.3. Logging](#23-logging) * [2.3.1. Color](#231-color) * [2.3.2. UUID](#232-uuid) - * [2.3.3. Only output on error](#233-only-output-on-error) - * [2.3.4. Verbose](#234-verbose) + * [2.3.3. Tag](#233-tag) + * [2.3.4. Only output on error](#234-only-output-on-error) + * [2.3.5. Verbose](#235-verbose) * [2.4. Dry run](#24-dry-run) * [2.5. Wait](#25-wait) * [2.6. Test](#26-test) @@ -221,7 +222,25 @@ cmd.run("echo hello", uuid: false) # Finished in 0.003 seconds with exit status 0 (successful) ``` -#### 2.3.3 Only output on error +#### 2.3.3 Tag + +You can add custom tags to command output using the `:tag` option. This is useful for categorizing or identifying a specific instance or run of a command: + +```ruby +cmd = TTY::Command.new(tag: "deploy") +cmd.run("echo hello") + +# or individually per command run: +cmd = TTY::Command.new +cmd.run("echo hello", tag: "deploy") + +# => +# [deploy] Running echo hello +# hello +# [deploy] Finished in 0.003 seconds with exit status 0 (successful) +``` + +#### 2.3.4 Only output on error When using a command that can fail, setting `:only_output_on_error` option to `true` hides the output if the command succeeds: @@ -254,7 +273,7 @@ will also print the output. *Setting this option will cause the output to show at once, at the end of the command.* -#### 2.3.4 Verbose +#### 2.3.5 Verbose By default commands will produce warnings when, for example `pty` option is not supported on a given platform. You can switch off such warnings with `:verbose` option set to `false`. diff --git a/lib/tty/command.rb b/lib/tty/command.rb index 86880d2..c117b56 100644 --- a/lib/tty/command.rb +++ b/lib/tty/command.rb @@ -55,14 +55,18 @@ def initialize(**options) @output = options.fetch(:output) { $stdout } @color = options.fetch(:color) { true } @uuid = options.fetch(:uuid) { true } + @tag = options[:tag] @printer_name = options.fetch(:printer) { :pretty } @dry_run = options.fetch(:dry_run) { false } - @printer = use_printer(@printer_name, color: @color, uuid: @uuid) + @printer = use_printer( + @printer_name, color: @color, uuid: @uuid, tag: @tag + ) @cmd_options = {} @cmd_options[:verbose] = options.fetch(:verbose, true) @cmd_options[:pty] = true if options[:pty] @cmd_options[:binmode] = true if options[:binmode] @cmd_options[:timeout] = options[:timeout] if options[:timeout] + @cmd_options[:tag] = @tag if @tag end # Start external executable in a child process diff --git a/lib/tty/command/cmd.rb b/lib/tty/command/cmd.rb index 6b76723..f8cbf88 100644 --- a/lib/tty/command/cmd.rb +++ b/lib/tty/command/cmd.rb @@ -123,6 +123,12 @@ def group(value) def with_clean_env end + # The command tag + # @api public + def tag + @options[:tag] + end + # Assemble full command # # @api public @@ -140,7 +146,8 @@ def to_hash { command: command, argv: argv, - uuid: uuid + uuid: uuid, + tag: @options[:tag] } end diff --git a/lib/tty/command/printers/pretty.rb b/lib/tty/command/printers/pretty.rb index 0e79b7b..8fb474d 100644 --- a/lib/tty/command/printers/pretty.rb +++ b/lib/tty/command/printers/pretty.rb @@ -9,6 +9,7 @@ class Pretty < Abstract def initialize(*) super @uuid = options.fetch(:uuid, true) + @tag = options[:tag] end def print_command_start(cmd, *args) @@ -46,10 +47,10 @@ def print_command_exit(cmd, status, runtime, *args) def write(cmd, message, data = nil) cmd_set_uuid = cmd.options.fetch(:uuid, true) uuid_needed = cmd.options[:uuid].nil? ? @uuid : cmd_set_uuid + prefix = cmd.tag || @tag || (uuid_needed ? cmd.uuid : nil) + out = [] - if uuid_needed - out << "[#{decorate(cmd.uuid, :green)}] " unless cmd.uuid.nil? - end + out << "[#{decorate(prefix, :green)}] " if prefix out << "#{message}\n" target = (cmd.only_output_on_error && !data.nil?) ? data : output target << out.join diff --git a/spec/unit/run_spec.rb b/spec/unit/run_spec.rb index 90593b5..d5ad849 100644 --- a/spec/unit/run_spec.rb +++ b/spec/unit/run_spec.rb @@ -12,16 +12,14 @@ end it "runs command successfully with logging" do - output = StringIO.new uuid = "xxxx" allow(SecureRandom).to receive(:uuid).and_return(uuid) - command = TTY::Command.new(output: output) - command.run(:echo, "hello") + lines = retrieve_log_lines do |output| + command = TTY::Command.new(output: output) + command.run(:echo, "hello") + end - output.rewind - lines = output.readlines - lines.last.gsub!(/\d+\.\d+/, "x") expect(lines).to eq([ "[\e[32m#{uuid}\e[0m] Running \e[33;1mecho hello\e[0m\n", "[\e[32m#{uuid}\e[0m] \thello\n", @@ -31,16 +29,14 @@ end it "runs command successfully with logging without color" do - output = StringIO.new uuid = "xxxx" allow(SecureRandom).to receive(:uuid).and_return(uuid) - command = TTY::Command.new(output: output, color: false) - command.run(:echo, "hello") + lines = retrieve_log_lines do |output| + command = TTY::Command.new(output: output, color: false) + command.run(:echo, "hello") + end - output.rewind - lines = output.readlines - lines.last.gsub!(/\d+\.\d+/, "x") expect(lines).to eq([ "[#{uuid}] Running echo hello\n", "[#{uuid}] \thello\n", @@ -48,50 +44,16 @@ ]) end - it "runs command successfully with logging without uuid set globally" do - output = StringIO.new - command = TTY::Command.new(output: output, uuid: false) - - command.run(:echo, "hello") - output.rewind - - lines = output.readlines - lines.last.gsub!(/\d+\.\d+/, "x") - expect(lines).to eq([ - "Running \e[33;1mecho hello\e[0m\n", - "\thello\n", - "Finished in x seconds with exit status 0 (\e[32;1msuccessful\e[0m)\n" - ]) - end - - it "runs command successfully with logging without uuid set locally" do - output = StringIO.new - command = TTY::Command.new(output: output) - - command.run(:echo, "hello", uuid: false) - output.rewind - - lines = output.readlines - lines.last.gsub!(/\d+\.\d+/, "x") - expect(lines).to eq([ - "Running \e[33;1mecho hello\e[0m\n", - "\thello\n", - "Finished in x seconds with exit status 0 (\e[32;1msuccessful\e[0m)\n" - ]) - end - it "runs command and fails with logging" do non_zero_exit = fixtures_path("non_zero_exit") - output = StringIO.new uuid = "xxxx" allow(SecureRandom).to receive(:uuid).and_return(uuid) - command = TTY::Command.new(output: output) - command.run!("ruby #{non_zero_exit}") + lines = retrieve_log_lines do |output| + command = TTY::Command.new(output: output) + command.run!("ruby #{non_zero_exit}") + end - output.rewind - lines = output.readlines - lines.last.gsub!(/\d+\.\d+/, "x") expect(lines).to eq([ "[\e[32m#{uuid}\e[0m] Running \e[33;1mruby #{non_zero_exit}\e[0m\n", "[\e[32m#{uuid}\e[0m] \tnooo\n", @@ -145,17 +107,16 @@ phased_output = fixtures_path("phased_output") uuid = "xxxx" allow(SecureRandom).to receive(:uuid).and_return(uuid) - output = StringIO.new - cmd = TTY::Command.new(output: output) - out, err = cmd.run("ruby #{phased_output}") + out = err = nil + lines = retrieve_log_lines do |output| + cmd = TTY::Command.new(output: output) + out, err = cmd.run("ruby #{phased_output}") + end expect(out).to eq("." * 10) expect(err).to eq("") - output.rewind - lines = output.readlines - lines.last.gsub!(/\d+\.\d+/, "x") expect(lines).to eq([ "[\e[32m#{uuid}\e[0m] Running \e[33;1mruby #{phased_output}\e[0m\n", "[\e[32m#{uuid}\e[0m] \t..........\n", @@ -184,4 +145,98 @@ lines = output.readlines expect(lines[0]).to include("Running \e[33;1mecho hello\e[0m\n") end + + context "with uuid option" do + it "runs command successfully with logging without uuid set globally" do + lines = retrieve_log_lines do |output| + command = TTY::Command.new(output: output, uuid: false) + command.run(:echo, "hello") + end + + expect(lines).to eq( + generic_colored_log_lines(prefix: nil) + ) + end + + it "runs command successfully with logging without uuid set locally" do + lines = retrieve_log_lines do |output| + command = TTY::Command.new(output: output) + command.run(:echo, "hello", uuid: false) + end + + expect(lines).to eq( + generic_colored_log_lines(prefix: nil) + ) + end + end + + context "with tag option" do + it "prints the tag set globally" do + tag = "task" + + lines = retrieve_log_lines do |output| + command = TTY::Command.new(output: output, tag: tag) + command.run(:echo, "hello") + end + + expect(lines).to eq( + generic_colored_log_lines(prefix: tag) + ) + end + + it "prints the tag set locally" do + tag = "task" + + lines = retrieve_log_lines do |output| + command = TTY::Command.new(output: output) + command.run(:echo, "hello", tag: tag) + end + + expect(lines).to eq( + generic_colored_log_lines(prefix: tag) + ) + end + + it "prints the tag even if uuid is set to false" do + tag = "task" + + lines = retrieve_log_lines do |output| + command = TTY::Command.new(output: output, tag: tag, uuid: false) + command.run(:echo, "hello") + end + + expect(lines).to eq( + generic_colored_log_lines(prefix: tag) + ) + end + end + + # Retrieves log lines from the output produced within the given block. + # Also replaces the execution time portion in the output with `x`. + def retrieve_log_lines + output = StringIO.new + yield(output) + output.rewind + lines = output.readlines + lines.last.gsub!(/\d+\.\d+/, "x") + lines + end + + # Generates the expected log lines in colored mode, with/without `[prefix]` + def generic_colored_log_lines(prefix: nil) + if prefix + [ + "[\e[32m#{prefix}\e[0m] Running \e[33;1mecho hello\e[0m\n", + "[\e[32m#{prefix}\e[0m] \thello\n", + "[\e[32m#{prefix}\e[0m] Finished in x seconds with exit status 0 " \ + "(\e[32;1msuccessful\e[0m)\n" + ] + else + [ + "Running \e[33;1mecho hello\e[0m\n", + "\thello\n", + "Finished in x seconds with exit status 0 (\e[32;1msuccessful\e[0m)\n" + ] + end + end end