1 /*
2  *  Created by Phil on 18/4/2013.
3  *  Copyright 2013 Two Blue Cubes Ltd. All rights reserved.
4  *
5  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
6  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  */
8 // Only use header guard if we are not using an outer namespace
9 #ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
10 # ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
11 #  ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
12 #   define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
13 #  endif
14 # else
15 #  define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
16 # endif
17 #endif
18 #ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
19 #include <string>
20 #include <vector>
21 #include <sstream>
22 
23 // Use optional outer namespace
24 #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
25 namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE {
26 #endif
27 
28 namespace Tbc {
29 
30 #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH
31     const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH;
32 #else
33     const unsigned int consoleWidth = 80;
34 #endif
35 
36     struct TextAttributes {
TextAttributesTextAttributes37         TextAttributes()
38         :   initialIndent( std::string::npos ),
39             indent( 0 ),
40             width( consoleWidth-1 )
41         {}
42 
setInitialIndentTextAttributes43         TextAttributes& setInitialIndent( std::size_t _value )  { initialIndent = _value; return *this; }
setIndentTextAttributes44         TextAttributes& setIndent( std::size_t _value )         { indent = _value; return *this; }
setWidthTextAttributes45         TextAttributes& setWidth( std::size_t _value )          { width = _value; return *this; }
46 
47         std::size_t initialIndent;  // indent of first line, or npos
48         std::size_t indent;         // indent of subsequent lines, or all if initialIndent is npos
49         std::size_t width;          // maximum width of text, including indent. Longer text will wrap
50     };
51 
52     class Text {
53     public:
54         Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
attr(_attr)55         : attr( _attr )
56         {
57             const std::string wrappableBeforeChars = "[({<\t";
58             const std::string wrappableAfterChars = "])}>-,./|\\";
59             const std::string wrappableInsteadOfChars = " \n\r";
60             std::string indent = _attr.initialIndent != std::string::npos
61                 ? std::string( _attr.initialIndent, ' ' )
62                 : std::string( _attr.indent, ' ' );
63 
64             typedef std::string::const_iterator iterator;
65             iterator it = _str.begin();
66             const iterator strEnd = _str.end();
67 
68             while( it != strEnd ) {
69 
70                 if( lines.size() >= 1000 ) {
71                     lines.push_back( "... message truncated due to excessive size" );
72                     return;
73                 }
74 
75 
76                 std::string suffix;
77                 std::size_t width = (std::min)( static_cast<size_t>( strEnd-it ), _attr.width-static_cast<size_t>( indent.size() ) );
78                 iterator itEnd = it+width;
79                 iterator itNext = _str.end();
80 
81                 iterator itNewLine = std::find( it, itEnd, '\n' );
82                 if( itNewLine != itEnd )
83                     itEnd = itNewLine;
84 
85                 if( itEnd != strEnd  ) {
86                     bool foundWrapPoint = false;
87                     iterator findIt = itEnd;
88                     do {
89                         if( wrappableAfterChars.find( *findIt ) != std::string::npos && findIt != itEnd ) {
90                             itEnd = findIt+1;
91                             itNext = findIt+1;
92                             foundWrapPoint = true;
93                         }
94                         else if( findIt > it && wrappableBeforeChars.find( *findIt ) != std::string::npos ) {
95                             itEnd = findIt;
96                             itNext = findIt;
97                             foundWrapPoint = true;
98                         }
99                         else if( wrappableInsteadOfChars.find( *findIt ) != std::string::npos ) {
100                             itNext = findIt+1;
101                             itEnd = findIt;
102                             foundWrapPoint = true;
103                         }
104                         if( findIt == it )
105                             break;
106                         else
107                             --findIt;
108                     }
109                     while( !foundWrapPoint );
110 
111                     if( !foundWrapPoint ) {
112                         // No good wrap char, so we'll break mid word and add a hyphen
113                         --itEnd;
114                         itNext = itEnd;
115                         suffix = "-";
116                     }
117                     else {
118                         while( itEnd > it && wrappableInsteadOfChars.find( *(itEnd-1) ) != std::string::npos )
119                             --itEnd;
120                     }
121                 }
122                 lines.push_back( indent + std::string( it, itEnd ) + suffix );
123 
124                 if( indent.size() != _attr.indent )
125                     indent = std::string( _attr.indent, ' ' );
126                 it = itNext;
127             }
128         }
129 
130 
131 
132         typedef std::vector<std::string>::const_iterator const_iterator;
133 
begin()134         const_iterator begin() const { return lines.begin(); }
end()135         const_iterator end() const { return lines.end(); }
last()136         std::string const& last() const { return lines.back(); }
size()137         std::size_t size() const { return lines.size(); }
138         std::string const& operator[]( std::size_t _index ) const { return lines[_index]; }
toString()139         std::string toString() const {
140             std::ostringstream oss;
141             oss << *this;
142             return oss.str();
143         }
144 
145         inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
146             for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
147                 it != itEnd; ++it ) {
148                 if( it != _text.begin() )
149                     _stream << "\n";
150                 _stream << *it;
151             }
152             return _stream;
153         }
154 
155 
156     private:
157         std::string str;
158         TextAttributes attr;
159         std::vector<std::string> lines;
160     };
161 
162 } // end namespace Tbc
163 
164 #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
165 } // end outer namespace
166 #endif
167 
168 #endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
169