summaryrefslogtreecommitdiffstats
path: root/vespaclient/src/perl/lib/Yahoo/Vespa/ConsoleOutput.pm
diff options
context:
space:
mode:
Diffstat (limited to 'vespaclient/src/perl/lib/Yahoo/Vespa/ConsoleOutput.pm')
-rw-r--r--vespaclient/src/perl/lib/Yahoo/Vespa/ConsoleOutput.pm331
1 files changed, 331 insertions, 0 deletions
diff --git a/vespaclient/src/perl/lib/Yahoo/Vespa/ConsoleOutput.pm b/vespaclient/src/perl/lib/Yahoo/Vespa/ConsoleOutput.pm
new file mode 100644
index 00000000000..73a0a016592
--- /dev/null
+++ b/vespaclient/src/perl/lib/Yahoo/Vespa/ConsoleOutput.pm
@@ -0,0 +1,331 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#
+# Output handler
+#
+# Intentions:
+# - Make it easy for unit tests to redirect output.
+# - Allow programmers to add all sorts of debug information into tools usable
+# for debugging, while hiding it by default for real users.
+# - Allow generic functionality that can be reused by all. For instance color
+# coding of very important information.
+#
+# Ideas for improvement:
+# - Could possibly detect terminal width and do proper line breaking of long
+# lines
+#
+# A note about colors:
+# - This module will detect if terminal supports colors. If not, it will not
+# print any. (Color support can be turned off by giving --nocolors argument
+# through argument parser, by setting a TERM value that does not support
+# colors or programmatically call setUseAnsiColors(0).
+# - Currently only red and grey are used in addition to default. These colors
+# should work well for both light and dark backgrounds.
+#
+
+package Yahoo::Vespa::ConsoleOutput;
+
+use strict;
+use warnings;
+use Yahoo::Vespa::Utils;
+
+BEGIN { # - Define exports for modul
+ use base 'Exporter';
+ our @EXPORT = qw(
+ printResult printError printWarning printInfo printDebug printSpam
+ enableAutomaticLineBreaks
+ COLOR_RESET COLOR_WARN COLOR_ERR COLOR_ANON
+ );
+ our @EXPORT_OK = qw(
+ getTerminalWidth getVerbosity usingAnsiColors ansiColorsSupported
+ setVerbosity
+ );
+}
+
+my %TYPES = (
+ 'result' => 0, # Output from a tool. Expected when app runs successfully.
+ 'error' => 1, # Error found, typically aborting the script with a failure.
+ 'warning' => 2, # An issue that may or may not cause the program to fail.
+ 'info' => 3, # Useful information to get from the script.
+ 'debug' => 4, # Debug information useful to debug script or to see
+ # internals of what is happening.
+ 'spam' => 5, # Spammy information used when large amounts of details is
+ # wanted. Typically to debug some failure.
+);
+my $VERBOSITY; # Current verbosity level
+my $ANSI_COLORS_SUPPORTED; # True if terminal supports colors
+my $ANSI_COLORS; # True if we want to use colors (and support it)
+my %ATTRIBUTE_PREFIX; # Ansi escape prefixes for verbosity levels
+my %ATTRIBUTE_POSTFIX; # Ansi escape postfixes for verbosity levels
+my %OUTPUT_STREAM; # Where to write different verbosity levels (stdout|stderr)
+my $TERMINAL_WIDTH; # With of terminal in columns
+my $COLUMN_POSITION; # Current index of cursor in terminal
+my $ENABLE_AUTO_LINE_BREAKS;
+
+use constant COLOR_RESET => "\e[0m";
+use constant COLOR_ERR => "\e[91m";
+use constant COLOR_WARN => "\e[93m";
+use constant COLOR_ANON => "\e[90m";
+
+&initialize(*STDOUT, *STDERR);
+
+return 1;
+
+########################## Default exported functions ########################
+
+sub printResult { # (Output...)
+ printAtLevel('result', @_);
+}
+sub printError { # (Output...)
+ printAtLevel('error', @_);
+}
+sub printWarning { # (Output...)
+ printAtLevel('warning', @_);
+}
+sub printInfo { # (Output...)
+ printAtLevel('info', @_);
+}
+sub printDebug { # (Output...)
+ printAtLevel('debug', @_);
+}
+sub printSpam { # (Output...)
+ printAtLevel('spam', @_);
+}
+sub enableAutomaticLineBreaks { # (Bool) -> (OldValue)
+ my $oldval = $ENABLE_AUTO_LINE_BREAKS;
+ $ENABLE_AUTO_LINE_BREAKS = ($_[0] ? 1 : 0);
+ return $oldval;
+}
+
+######################## Optionally exported functions #######################
+
+sub getTerminalWidth { # () -> ColumnCount
+ # May be undefined if someone prints before initialized
+ return (defined $TERMINAL_WIDTH ? $TERMINAL_WIDTH : 80);
+}
+sub getVerbosity { # () -> VerbosityLevel
+ return $VERBOSITY;
+}
+sub usingAnsiColors { # () -> Bool
+ return $ANSI_COLORS;
+}
+sub ansiColorsSupported { # () -> Bool
+ return $ANSI_COLORS_SUPPORTED;
+}
+sub setVerbosity { # (VerbosityLevel)
+ $VERBOSITY = $_[0];
+}
+
+################## Functions for unit tests to mock internals ################
+
+sub setTerminalWidth { # (ColumnCount)
+ $TERMINAL_WIDTH = $_[0];
+}
+sub setUseAnsiColors { # (Bool)
+ if ($ANSI_COLORS_SUPPORTED && $_[0]) {
+ $ANSI_COLORS = 1;
+ } else {
+ $ANSI_COLORS = 0;
+ }
+}
+
+############## Utility functions - Not intended for external use #############
+
+sub initialize { # ()
+ my ($stdout, $stderr, $use_colors_by_default) = @_;
+ if (!defined $VERBOSITY) {
+ $VERBOSITY = &getDefaultVerbosity();
+ }
+ $COLUMN_POSITION = 0;
+ $ENABLE_AUTO_LINE_BREAKS = 1;
+ %ATTRIBUTE_PREFIX = map { $_ => '' } keys %TYPES;
+ %ATTRIBUTE_POSTFIX = map { $_ => '' } keys %TYPES;
+ &setAttribute('error', COLOR_ERR, COLOR_RESET);
+ &setAttribute('warning', COLOR_WARN, COLOR_RESET);
+ &setAttribute('debug', COLOR_ANON, COLOR_RESET);
+ &setAttribute('spam', COLOR_ANON, COLOR_RESET);
+ %OUTPUT_STREAM = map { $_ => $stdout } keys %TYPES;
+ $OUTPUT_STREAM{'error'} = $stderr;
+ $OUTPUT_STREAM{'warning'} = $stderr;
+ if (defined $use_colors_by_default) {
+ $ANSI_COLORS_SUPPORTED = $use_colors_by_default;
+ $ANSI_COLORS = $ANSI_COLORS_SUPPORTED;
+ } else {
+ &detectTerminalColorSupport();
+ }
+ if (!defined $TERMINAL_WIDTH) {
+ $TERMINAL_WIDTH = &detectTerminalWidth();
+ }
+}
+sub setAttribute { # (type, prefox, postfix)
+ my ($type, $prefix, $postfix) = @_;
+ $ATTRIBUTE_PREFIX{$type} = $prefix;
+ $ATTRIBUTE_POSTFIX{$type} = $postfix;
+}
+sub stripAnsiEscapes { # (Line) -> (StrippedLine)
+ $_[0] =~ s/\e\[[^m]*m//g;
+ return $_[0];
+}
+sub getDefaultVerbosity { # () -> VerbosityLevel
+ # We can not print at correct verbosity levels before argument parsing has
+ # completed. We try some simple arg parsing here assuming default options
+ # used to set verbosity, such that we likely guess correctly, allowing
+ # correct verbosity from the start.
+ my $default = 3;
+ foreach my $arg (@ARGV) {
+ if ($arg eq '--') { return $default; }
+ if ($arg =~ /^-([^-]+)/) {
+ my $optstring = $1;
+ while ($optstring =~ /^(.)(.*)$/) {
+ my $char = $1;
+ $optstring = $2;
+ if ($char eq 'v') {
+ ++$default;
+ }
+ if ($char eq 's') {
+ if ($default > 0) {
+ --$default;
+ }
+ }
+ }
+ }
+ }
+ return $default;
+}
+sub detectTerminalWidth { #() -> ColumnCount
+ my $cols = &checkConsoleFeature('cols');
+ if (!defined $cols) {
+ printDebug "Assuming terminal width of 80.\n";
+ return 80;
+ }
+ if ($cols =~ /^\d+$/ && $cols > 10 && $cols < 500) {
+ printDebug "Detected terminal width of $cols.\n";
+ return $cols;
+ } else {
+ printDebug "Unexpected terminal width of '$cols' given. "
+ . "Assuming size of 80.\n";
+ return 80;
+ }
+}
+sub detectTerminalColorSupport { # () -> Bool
+ my $colorcount = &checkConsoleFeature('colors');
+ if (!defined $colorcount) {
+ $ANSI_COLORS_SUPPORTED = 0;
+ printDebug "Assuming no color support.\n";
+ return 0;
+ }
+ if ($colorcount =~ /^\d+$/ && $colorcount >= 8) {
+ $ANSI_COLORS_SUPPORTED = 1;
+ if (!defined $ANSI_COLORS) {
+ $ANSI_COLORS = $ANSI_COLORS_SUPPORTED;
+ }
+ printDebug "Color support detected.\n";
+ return 1;
+ }
+}
+sub checkConsoleFeature { # (Feature) -> Bool
+ my ($feature) = @_;
+ # Unit tests must mock. Can't depend on TERM being set.
+ assertNotUnitTest();
+ if (!exists $ENV{'TERM'}) {
+ printDebug "Terminal not set. Unknown.\n";
+ return;
+ }
+ if (-f '/usr/bin/tput') {
+ my ($fh, $result);
+ if (open ($fh, "tput $feature 2>/dev/null |")) {
+ $result = <$fh>;
+ close $fh;
+ } else {
+ printDebug "Failed to open tput pipe.\n";
+ return;
+ }
+ if ($? != 0) {
+ printDebug "Failed tput call to detect feature $feature $!\n";
+ return;
+ }
+ chomp $result;
+ #printSpam "Console feature $feature: '$result'\n";
+ return $result;
+ } else {
+ printDebug "No tput binary. Dont know how to detect feature.\n";
+ return;
+ }
+}
+sub printAtLevel { # (Level, Output...)
+ # Prints an array of data that may contain newlines
+ my $level = shift @_;
+ exists $TYPES{$level} or confess "Unknown print level '$level'.";
+ if ($TYPES{$level} > $VERBOSITY) {
+ return;
+ }
+ my $buffer = '';
+ my $width = &getTerminalWidth();
+ foreach my $printable (@_) {
+ my @lines = split(/\n/, $printable, -1);
+ my $current = 0;
+ for (my $i=0; $i < scalar @lines; ++$i) {
+ if ($i != 0) {
+ $buffer .= "\n";
+ $COLUMN_POSITION = 0;
+ }
+ my $last = ($i + 1 == scalar @lines);
+ printLineAtLevel($level, $lines[$i], \$buffer, $last);
+ }
+ }
+ my $stream = $OUTPUT_STREAM{$level};
+ print $stream $buffer;
+}
+sub printLineAtLevel { # (Level, Line, Buffer, Last)
+ # Prints a single line, which might still have to be broken into multiple
+ # lines
+ my ($level, $data, $buffer, $last) = @_;
+ if (!$ANSI_COLORS) {
+ $data = &stripAnsiEscapes($data);
+ }
+ my $width = &getTerminalWidth();
+ while (1) {
+ my $remaining = $width - $COLUMN_POSITION;
+ if (&prefixLineWithLevel($level)) {
+ $remaining -= (2 + length $level);
+ }
+ if ($ENABLE_AUTO_LINE_BREAKS && $remaining < length $data) {
+ my $min = int (2 * $width / 3) - $COLUMN_POSITION;
+ if ($min < 1) { $min = 1; }
+ if ($data =~ /^(.{$min,$remaining}) (.*?)$/s) {
+ my ($first, $rest) = ($1, $2);
+ &printLinePartAtLevel($level, $first, $buffer);
+ $$buffer .= "\n";
+ $data = $rest;
+ $COLUMN_POSITION = 0;
+ } else {
+ last;
+ }
+ } else {
+ last;
+ }
+ }
+ if (!$last || length $data > 0) {
+ &printLinePartAtLevel($level, $data, $buffer);
+ }
+}
+sub printLinePartAtLevel { # ($Level, Line, Buffer)
+ # Print a single line that should fit on one line
+ my ($level, $data, $buffer) = @_;
+ if ($ANSI_COLORS) {
+ $$buffer .= $ATTRIBUTE_PREFIX{$level};
+ }
+ if (&prefixLineWithLevel($level)) {
+ $$buffer .= $level . ": ";
+ $COLUMN_POSITION = (length $level) + 2;
+ }
+ $$buffer .= $data;
+ $COLUMN_POSITION += length $data;
+ if ($ANSI_COLORS) {
+ $$buffer .= $ATTRIBUTE_POSTFIX{$level};
+ }
+}
+sub prefixLineWithLevel { # (Level) -> Bool
+ my ($level) = @_;
+ return ($TYPES{$level} > 2 && $VERBOSITY >= 4 && $COLUMN_POSITION == 0);
+}
+