blob: 15a57b683745740c3186019317809d9312964016 [file] [log] [blame]
/*****************************************************************************
* ___ _ _ ___ ___
* | _|| | | | | _ \ _ \ CLIPP - command line interfaces for modern C++
* | |_ | |_ | | | _/ _/ version 1.2.3
* |___||___||_| |_| |_| https://github.com/muellan/clipp
*
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
* Copyright (c) 2017-2018 André Müller <foss@andremueller-online.de>
*
* ---------------------------------------------------------------------------
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
/**
* NOTE: this is just the formatting_ostream class from the original clipp header
*/
#ifndef AM_CLIPP_H__
#define AM_CLIPP_H__
#include <cstring>
#include <string>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <memory>
#include <vector>
#include <limits>
#include <stack>
#include <algorithm>
#include <sstream>
#include <utility>
#include <iterator>
#include <functional>
/*************************************************************************//**
*
* @brief primary namespace
*
*****************************************************************************/
namespace clipp {
/*****************************************************************************
*
* basic constants and datatype definitions
*
*****************************************************************************/
using arg_index = int;
using arg_string = std::string;
using doc_string = std::string;
using arg_list = std::vector<arg_string>;
/*************************************************************************//**
*
* @brief stream decorator
* that applies formatting like line wrapping
*
*****************************************************************************/
template<class OStream = std::ostream, class StringT = doc_string>
class formatting_ostream
{
public:
using string_type = StringT;
using size_type = typename string_type::size_type;
using char_type = typename string_type::value_type;
formatting_ostream(OStream& os):
os_(os),
curCol_{0}, firstCol_{0}, lastCol_{100},
hangingIndent_{0}, paragraphSpacing_{0}, paragraphSpacingThreshold_{2},
curBlankLines_{0}, curParagraphLines_{1},
totalNonBlankLines_{0},
ignoreInputNls_{false}
{}
//---------------------------------------------------------------
const OStream& base() const noexcept { return os_; }
OStream& base() noexcept { return os_; }
bool good() const { return os_.good(); }
//---------------------------------------------------------------
/** @brief determines the leftmost border of the text body */
formatting_ostream& first_column(int c) {
firstCol_ = c < 0 ? 0 : c;
return *this;
}
int first_column() const noexcept { return firstCol_; }
/** @brief determines the rightmost border of the text body */
formatting_ostream& last_column(int c) {
lastCol_ = c < 0 ? 0 : c;
return *this;
}
int last_column() const noexcept { return lastCol_; }
int text_width() const noexcept {
return lastCol_ - firstCol_;
}
/** @brief additional indentation for the 2nd, 3rd, ... line of
a paragraph (sequence of soft-wrapped lines) */
formatting_ostream& hanging_indent(int amount) {
hangingIndent_ = amount;
return *this;
}
int hanging_indent() const noexcept {
return hangingIndent_;
}
/** @brief amount of blank lines between paragraphs */
formatting_ostream& paragraph_spacing(int lines) {
paragraphSpacing_ = lines;
return *this;
}
int paragraph_spacing() const noexcept {
return paragraphSpacing_;
}
/** @brief insert paragraph spacing
if paragraph is at least 'lines' lines long */
formatting_ostream& min_paragraph_lines_for_spacing(int lines) {
paragraphSpacingThreshold_ = lines;
return *this;
}
int min_paragraph_lines_for_spacing() const noexcept {
return paragraphSpacingThreshold_;
}
/** @brief if set to true, newline characters will be ignored */
formatting_ostream& ignore_newline_chars(bool yes) {
ignoreInputNls_ = yes;
return *this;
}
bool ignore_newline_chars() const noexcept {
return ignoreInputNls_;
}
//---------------------------------------------------------------
/* @brief insert 'n' spaces */
void write_spaces(int n) {
if(n < 1) return;
os_ << string_type(size_type(n), ' ');
curCol_ += n;
}
/* @brief go to new line, but continue current paragraph */
void wrap_soft(int times = 1) {
if(times < 1) return;
if(times > 1) {
os_ << string_type(size_type(times), '\n');
} else {
os_ << '\n';
}
curCol_ = 0;
++curParagraphLines_;
}
/* @brief go to new line, and start a new paragraph */
void wrap_hard(int times = 1) {
if(times < 1) return;
if(paragraph_spacing() > 0 &&
paragraph_lines() >= min_paragraph_lines_for_spacing())
{
times = paragraph_spacing() + 1;
}
if(times > 1) {
os_ << string_type(size_type(times), '\n');
curBlankLines_ += times - 1;
} else {
os_ << '\n';
}
if(at_begin_of_line()) {
++curBlankLines_;
}
curCol_ = 0;
curParagraphLines_ = 1;
}
//---------------------------------------------------------------
bool at_begin_of_line() const noexcept {
return curCol_ <= current_line_begin();
}
int current_line_begin() const noexcept {
return in_hanging_part_of_paragraph()
? firstCol_ + hangingIndent_
: firstCol_;
}
int current_column() const noexcept {
return curCol_;
}
int total_non_blank_lines() const noexcept {
return totalNonBlankLines_;
}
int paragraph_lines() const noexcept {
return curParagraphLines_;
}
int blank_lines_before_paragraph() const noexcept {
return curBlankLines_;
}
//---------------------------------------------------------------
template<class T>
friend formatting_ostream&
operator << (formatting_ostream& os, const T& x) {
os.write(x);
return os;
}
void flush() {
os_.flush();
}
private:
bool in_hanging_part_of_paragraph() const noexcept {
return hanging_indent() > 0 && paragraph_lines() > 1;
}
bool current_line_empty() const noexcept {
return curCol_ < 1;
}
bool left_of_text_area() const noexcept {
return curCol_ < current_line_begin();
}
bool right_of_text_area() const noexcept {
return curCol_ > lastCol_;
}
int columns_left_in_line() const noexcept {
return lastCol_ - std::max(current_line_begin(), curCol_);
}
void fix_indent() {
if(left_of_text_area()) {
const auto fst = current_line_begin();
write_spaces(fst - curCol_);
curCol_ = fst;
}
}
template<class Iter>
bool only_whitespace(Iter first, Iter last) const {
return last == std::find_if_not(first, last,
[](char_type c) { return std::isspace(c); });
}
/** @brief write any object */
template<class T>
void write(const T& x) {
std::ostringstream ss;
ss << x;
write(std::move(ss).str());
}
/** @brief write a stringstream */
void write(const std::ostringstream& s) {
write(s.str());
}
/** @brief write a string */
void write(const string_type& s) {
write(s.begin(), s.end());
}
/** @brief partition output into lines */
template<class Iter>
void write(Iter first, Iter last)
{
if(first == last) return;
if(*first == '\n') {
if(!ignore_newline_chars()) wrap_hard();
++first;
if(first == last) return;
}
auto i = std::find(first, last, '\n');
if(i != last) {
if(ignore_newline_chars()) ++i;
if(i != last) {
write_line(first, i);
write(i, last);
}
}
else {
write_line(first, last);
}
}
/** @brief handle line wrapping due to column constraints */
template<class Iter>
void write_line(Iter first, Iter last)
{
if(first == last) return;
if(only_whitespace(first, last)) return;
if(right_of_text_area()) wrap_soft();
if(at_begin_of_line()) {
//discard whitespace, it we start a new line
first = std::find_if(first, last,
[](char_type c) { return !std::isspace(c); });
if(first == last) return;
}
const auto n = int(std::distance(first,last));
const auto m = columns_left_in_line();
//if text to be printed is too long for one line -> wrap
if(n > m) {
//break before word, if break is mid-word
auto breakat = first + m;
while(breakat > first && !std::isspace(*breakat)) --breakat;
//could not find whitespace before word -> try after the word
if(!std::isspace(*breakat) && breakat == first) {
breakat = std::find_if(first+m, last,
[](char_type c) { return std::isspace(c); });
}
if(breakat > first) {
if(curCol_ < 1) ++totalNonBlankLines_;
fix_indent();
std::copy(first, breakat, std::ostream_iterator<char_type>(os_));
curBlankLines_ = 0;
}
if(breakat < last) {
wrap_soft();
write_line(breakat, last);
}
}
else {
if(curCol_ < 1) ++totalNonBlankLines_;
fix_indent();
std::copy(first, last, std::ostream_iterator<char_type>(os_));
curCol_ += n;
curBlankLines_ = 0;
}
}
/** @brief write a single character */
void write(char_type c)
{
if(c == '\n') {
if(!ignore_newline_chars()) wrap_hard();
}
else {
if(at_begin_of_line()) ++totalNonBlankLines_;
fix_indent();
os_ << c;
++curCol_;
}
}
OStream& os_;
int curCol_;
int firstCol_;
int lastCol_;
int hangingIndent_;
int paragraphSpacing_;
int paragraphSpacingThreshold_;
int curBlankLines_;
int curParagraphLines_;
int totalNonBlankLines_;
bool ignoreInputNls_;
};
} //namespace clipp
#endif