// Copyright 2011 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/cmdline/ui.hpp"2930#if defined(HAVE_CONFIG_H)31# include "config.h"32#endif3334extern "C" {35#include <sys/param.h>36#include <sys/ioctl.h>3738#if defined(HAVE_TERMIOS_H)39# include <termios.h>40#endif41#include <unistd.h>42}4344#include <iostream>4546#include "utils/cmdline/globals.hpp"47#include "utils/env.hpp"48#include "utils/format/macros.hpp"49#include "utils/fs/path.hpp"50#include "utils/logging/macros.hpp"51#include "utils/optional.ipp"52#include "utils/text/operations.ipp"53#include "utils/text/table.hpp"5455namespace cmdline = utils::cmdline;56namespace text = utils::text;5758using utils::none;59using utils::optional;606162/// Destructor for the class.63cmdline::ui::~ui(void)64{65}666768/// Writes a single line to stderr.69///70/// The written line is printed as is, without being wrapped to fit within the71/// screen width. If the caller wants to print more than one line, it shall72/// invoke this function once per line.73///74/// \param message The line to print. Should not include a trailing newline75/// character.76/// \param newline Whether to append a newline to the message or not.77void78cmdline::ui::err(const std::string& message, const bool newline)79{80LI(F("stderr: %s") % message);81if (newline)82std::cerr << message << "\n";83else {84std::cerr << message;85std::cerr.flush();86}87}888990/// Writes a single line to stdout.91///92/// The written line is printed as is, without being wrapped to fit within the93/// screen width. If the caller wants to print more than one line, it shall94/// invoke this function once per line.95///96/// \param message The line to print. Should not include a trailing newline97/// character.98/// \param newline Whether to append a newline to the message or not.99void100cmdline::ui::out(const std::string& message, const bool newline)101{102LI(F("stdout: %s") % message);103if (newline)104std::cout << message << "\n";105else {106std::cout << message;107std::cout.flush();108}109}110111112/// Queries the width of the screen.113///114/// This information comes first from the COLUMNS environment variable. If not115/// present or invalid, and if the stdout of the current process is connected to116/// a terminal the width is deduced from the terminal itself. Ultimately, if117/// all fails, none is returned. This function shall not raise any errors.118///119/// Be aware that the results of this query are cached during execution.120/// Subsequent calls to this function will always return the same value even if121/// the terminal size has actually changed.122///123/// \todo Install a signal handler for SIGWINCH so that we can readjust our124/// knowledge of the terminal width when the user resizes the window.125///126/// \return The width of the screen if it was possible to determine it, or none127/// otherwise.128optional< std::size_t >129cmdline::ui::screen_width(void) const130{131static bool done = false;132static optional< std::size_t > width = none;133134if (!done) {135const optional< std::string > columns = utils::getenv("COLUMNS");136if (columns) {137if (columns.get().length() > 0) {138try {139width = utils::make_optional(140utils::text::to_type< std::size_t >(columns.get()));141} catch (const utils::text::value_error& e) {142LD(F("Ignoring invalid value in COLUMNS variable: %s") %143e.what());144}145}146}147if (!width) {148struct ::winsize ws;149if (::ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1)150width = optional< std::size_t >(ws.ws_col);151}152153if (width && width.get() >= 80)154width.get() -= 5;155156done = true;157}158159return width;160}161162163/// Writes a line to stdout.164///165/// The line is wrapped to fit on screen.166///167/// \param message The line to print, without the trailing newline character.168void169cmdline::ui::out_wrap(const std::string& message)170{171const optional< std::size_t > max_width = screen_width();172if (max_width) {173const std::vector< std::string > lines = text::refill(174message, max_width.get());175for (std::vector< std::string >::const_iterator iter = lines.begin();176iter != lines.end(); iter++)177out(*iter);178} else179out(message);180}181182183/// Writes a line to stdout with a leading tag.184///185/// If the line does not fit on the current screen width, the line is broken186/// into pieces and the tag is repeated on every line.187///188/// \param tag The leading line tag.189/// \param message The message to be printed, without the trailing newline190/// character.191/// \param repeat If true, print the tag on every line; otherwise, indent the192/// text of all lines to match the width of the tag on the first line.193void194cmdline::ui::out_tag_wrap(const std::string& tag, const std::string& message,195const bool repeat)196{197const optional< std::size_t > max_width = screen_width();198if (max_width && max_width.get() > tag.length()) {199const std::vector< std::string > lines = text::refill(200message, max_width.get() - tag.length());201for (std::vector< std::string >::const_iterator iter = lines.begin();202iter != lines.end(); iter++) {203if (repeat || iter == lines.begin())204out(F("%s%s") % tag % *iter);205else206out(F("%s%s") % std::string(tag.length(), ' ') % *iter);207}208} else {209out(F("%s%s") % tag % message);210}211}212213214/// Writes a table to stdout.215///216/// \param table The table to write.217/// \param formatter The table formatter to use to convert the table to a218/// console representation.219/// \param prefix Text to prepend to all the lines of the output table.220void221cmdline::ui::out_table(const text::table& table,222text::table_formatter formatter,223const std::string& prefix)224{225if (table.empty())226return;227228const optional< std::size_t > max_width = screen_width();229if (max_width)230formatter.set_table_width(max_width.get() - prefix.length());231232const std::vector< std::string > lines = formatter.format(table);233for (std::vector< std::string >::const_iterator iter = lines.begin();234iter != lines.end(); ++iter)235out(prefix + *iter);236}237238239/// Formats and prints an error message.240///241/// \param ui_ The user interface object used to print the message.242/// \param message The message to print. Should not end with a newline243/// character.244void245cmdline::print_error(ui* ui_, const std::string& message)246{247LE(message);248ui_->err(F("%s: E: %s") % cmdline::progname() % message);249}250251252/// Formats and prints an informational message.253///254/// \param ui_ The user interface object used to print the message.255/// \param message The message to print. Should not end with a newline256/// character.257void258cmdline::print_info(ui* ui_, const std::string& message)259{260LI(message);261ui_->err(F("%s: I: %s") % cmdline::progname() % message);262}263264265/// Formats and prints a warning message.266///267/// \param ui_ The user interface object used to print the message.268/// \param message The message to print. Should not end with a newline269/// character.270void271cmdline::print_warning(ui* ui_, const std::string& message)272{273LW(message);274ui_->err(F("%s: W: %s") % cmdline::progname() % message);275}276277278