From 61e7cadad7f623a2f3252219fbd287023a713b0a Mon Sep 17 00:00:00 2001 From: Michael Andres Date: Thu, 23 Jan 2014 15:29:31 +0100 Subject: [PATCH] Start developing a more generic way of printing lists to tty/xml. --- src/output/Out.cc | 3 + src/output/Out.h | 401 +++++++++++++++++++++++++++++++++++++++++++++--- src/output/OutNormal.cc | 10 +- src/output/OutNormal.h | 6 +- 4 files changed, 389 insertions(+), 31 deletions(-) diff --git a/src/output/Out.cc b/src/output/Out.cc index e02aba2..2f79884 100644 --- a/src/output/Out.cc +++ b/src/output/Out.cc @@ -81,6 +81,9 @@ std::string TermLine::get( unsigned width_r, SplitFlags flags_r, char exp_r ) co // class Out //////////////////////////////////////////////////////////////////////////////// +constexpr Out::Type Out::TYPE_NONE; +constexpr Out::Type Out::TYPE_ALL; + Out::~Out() {} diff --git a/src/output/Out.h b/src/output/Out.h index b21c33c..c7d5282 100644 --- a/src/output/Out.h +++ b/src/output/Out.h @@ -2,8 +2,11 @@ #define OUT_H_ #include +#include #include +#include +#include #include #include #include @@ -16,13 +19,169 @@ #include "utils/prompt.h" #include "output/prompt.h" -using zypp::tribool; -using zypp::indeterminate; -using zypp::ProgressData; +using namespace zypp; class Table; class Zypper; +/////////////////////////////////////////////////////////////////// +namespace out +{ + /** \relates XmlNodeAttrFormater XML NODE ATTRIBUTE representation of types [str::asString] */ + template + std::string asXmlNodeAttr( const _Tp & val_r ) + { using str::asString; return asString( val_r ); } + + /////////////////////////////////////////////////////////////////// + /// \class XmlNodeAttrFormater + /// \brief Default representation of types as XmlNodeAttr [asXmlNodeAttr] + /////////////////////////////////////////////////////////////////// + struct XmlNodeAttrFormater + { + template + std::string operator()( const _Tp & val_r ) const + { return asXmlNodeAttr( val_r ); } + }; + +} // namespace out +/////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////// +namespace out +{ + /** \relates ListFormater NORMAL representation of types in lists [no default] */ + template + std::string asListElement( const _Tp & val_r ); + + /** \relates ListFormater XML representation of types in lists [no default] */ + template + std::string asXmlListElement( const _Tp & val_r ); + + /////////////////////////////////////////////////////////////////// + /// \class ListLayout + /// \brief Basic list layout + /// \todo fix design made in eile + /////////////////////////////////////////////////////////////////// + struct ListLayout + { + ListLayout( bool singleline_r, bool wrapline_r, bool gaped_r, unsigned indent_r ) + : _singleline( singleline_r ) + , _wrapline( wrapline_r ) + , _gaped( gaped_r ) + , _indent( indent_r ) + {} + bool _singleline; ///< one list element per line + bool _wrapline; ///< fold lines longer than \c _linewidth + bool _gaped; ///< add extra NL before element (if singleline) + unsigned _indent; ///< ammount of indent + }; + + namespace detail + { + template + struct ListLayoutInit : public ListLayout { ListLayoutInit() : ListLayout( _Singleline, _Wrapline, _Gaped, _Indent ) {} }; + } + typedef detail::ListLayoutInit XmlListLayout; + typedef detail::ListLayoutInit DefaultListLayout; ///< one element per line, no indent + typedef detail::ListLayoutInit DefaultGapedListLayout; ///< one element per line, no indent, gaped + typedef detail::ListLayoutInit IndentedListLayout; ///< one element per line, indented + typedef detail::ListLayoutInit IndentedGapedListLayout;///< one element per line, indented, gaped + typedef detail::ListLayoutInit CompressedListLayout; ///< multiple elements per line, indented + + /////////////////////////////////////////////////////////////////// + /// \class ListFormater + /// \brief Default representation of types in Lists [asListElement|asXmlListElement] + /////////////////////////////////////////////////////////////////// + struct ListFormater + { + typedef DefaultListLayout ListLayout; //< ListLayout for NORMAL lists + + struct XmlFormater //< XML representation of element + { + template + std::string operator()( const _Tp & val_r ) const + { return asXmlListElement( val_r ); } + }; + + template //< NORMAL representation of element + std::string operator()( const _Tp & val_r ) const + { return asListElement( val_r ); } + + std::string operator()( const std::string & val_r ) const + { return val_r; } + + std::string operator()( const char * val_r ) const + { return val_r; } + }; + +} // namespace out +/////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////// +namespace out +{ + namespace detail + { + // TODO: wrap singlelines; support for atttibuted text; + struct BasicList + { + NON_COPYABLE( BasicList ); + + BasicList( ListLayout layout_r, unsigned linewidth_r ) + : _layout( std::move(layout_r) ) + , _linewidth( linewidth_r ) + , _indent( _layout._indent, ' ' ) + , _cpos( 0U ) + {} + + ~BasicList() + { if ( !_layout._singleline && _cpos ) std::cout << std::endl; } + + void print( const std::string & val_r ) + { + if ( _layout._singleline ) + { + if ( _layout._gaped ) + std::cout << std::endl; + std::cout << _indent << val_r << std::endl; + } + else + { + if ( _cpos != 0 && ! fitsOnLine( 1/*' '*/ + val_r.size() ) ) + endLine(); + + if ( _cpos == 0 ) + { + if ( !_indent.empty() ) + printAndCount( _indent ); + } + else + printAndCount( " " ); + + printAndCount( val_r ); + } + } + + private: + bool fitsOnLine( unsigned val_r ) + { return( !_layout._wrapline || _cpos + val_r <= _linewidth ); } + + void printAndCount( const std::string & val_r ) + { _cpos += val_r.size(); std::cout << val_r; } + + void endLine() + { std::cout << std::endl; _cpos = 0U; } + + private: + const ListLayout _layout; + unsigned _linewidth; ///< desired line width + const std::string _indent; + unsigned _cpos; + }; + } +} // namespace out +/////////////////////////////////////////////////////////////////// + // Too simple on small terminals as esc-sequences may get truncated. // A table like writer for attributed strings is desirable. struct TermLine @@ -114,32 +273,214 @@ public: /** Verbosity levels. */ typedef enum { - /** Only important messages (no progress or status, only the result). */ - QUIET = 0, - /** Default output verbosity level. Progress for important tasks, moderate - * amount of status messages, operation information, result. */ - NORMAL = 1, - /** More detailed description of the operations. */ - HIGH = 2, - /** \todo drop this level in favor of zypper.log? */ - DEBUG = 3 + QUIET = 0, ///< Only important messages (no progress or status, only the result). + NORMAL = 1, ///< Default output verbosity level. Progress for important tasks, moderate + ///< amount of status messages, operation information, result. + HIGH = 2, ///< More detailed description of the operations. + DEBUG = 3 ///< \todo drop this level in favor of zypper.log? } Verbosity; /** Known output types implemented by derived classes. */ - typedef enum + enum TypeBit { - TYPE_NORMAL = 1, - TYPE_XML = 2, - TYPE_ALL = 0xff - } Type; + TYPE_NORMAL = 0x01<<0, ///< plain text output + TYPE_XML = 0x01<<1 ///< xml output + }; + ZYPP_DECLARE_FLAGS(Type,TypeBit); -public: - Out(Type type, Verbosity verbosity = NORMAL) + static constexpr Type TYPE_NONE = TypeBit(0x00); + static constexpr Type TYPE_ALL = TypeBit(0xff); + +protected: + Out(TypeBit type, Verbosity verbosity = NORMAL) : _verbosity(verbosity), _type(type) {} + +public: virtual ~Out(); +protected: + /////////////////////////////////////////////////////////////////// + /// \class ParentOut + /// \brief Convenience base class storing the back reference to Out. + struct ParentOut + { + NON_COPYABLE_BUT_MOVE( ParentOut ); + ParentOut( Out & out_r ) : _out( out_r ) {} + Out & out() { return _out; } + private: + Out & _out; + }; + +public: + /////////////////////////////////////////////////////////////////// + /// \class OnBlockEnd + /// \brief RAII writing out a remembered string when going out of scope + /// Helper class to store an end tag after the start tag was written. + /// As it's usually used in a RAII context the class is not copyable, + /// but it is movable. Use it like a std::ostringstream. + struct OnBlockEnd : protected ParentOut + { + NON_COPYABLE( OnBlockEnd ); + OnBlockEnd( Out & out_r ) : ParentOut( out_r ) {} + OnBlockEnd( OnBlockEnd && rhs ) : ParentOut( rhs.out() ){ if ( rhs._strp ) _strp.swap( rhs._strp ); } + ~OnBlockEnd() { if ( _strp ) std::cout << _strp->str(); } + std::ostream & operator*() { if ( !_strp ) _strp.reset( new std::ostringstream ); return *_strp; } + private: + boost::scoped_ptr _strp; + }; + + /////////////////////////////////////////////////////////////////// + /// \class XmlNode + /// \brief XML only: RAII writing a XML nodes start/end tag + /// \code + /// { + /// XmlNode( "node", { "attr", "val" } ); // + /// ... + /// } // + /// \endcode + struct XmlNode : protected ParentOut + { + /////////////////////////////////////////////////////////////////// + /// \class AttrDefType + /// \brief (Key, Value) string pair of XML node attributes + struct AttrDefType : public std::pair + { + typedef std::pair Pair; + + template + AttrDefType( std::string key_r, _Type && val_r ) + : Pair( std::move(key_r), ::out::asXmlNodeAttr( std::forward<_Type>(val_r) ) ) + {} + }; + /////////////////////////////////////////////////////////////////// + + /** Ctor taking nodename and attribute list. */ + XmlNode( Out & out_r, const std::string & name_r, const std::initializer_list & attrs_r = {} ) + : ParentOut( out_r ), _onend( out_r ) + { + if ( out().typeXML() && ! name_r.empty() ) + { + std::cout << "<" << name_r; + for ( const auto & pair : attrs_r ) + std::cout << " " << pair.first << "=\"" << xml::escape( pair.second ) << "\""; + std::cout << ">" << std::endl; + *_onend << "" << std::endl; + } + } + + /** Convenience ctor for one attribute pair */ + XmlNode( Out & out_r, const std::string & name_r, AttrDefType attr_r ) + : XmlNode( out_r, name_r, { attr_r } ) + {} + + private: + OnBlockEnd _onend; + }; + /////////////////////////////////////////////////////////////////// + + /** XML only: Write a leaf node without PCDATA + * \code + * + * \endcode + */ + void xmlNode( const std::string & name_r, const std::initializer_list & attrs_r = {} ) + { + if ( typeXML() ) + { + std::cout << "<" << name_r; + for ( const auto & pair : attrs_r ) + std::cout << " " << pair.first << "=\"" << xml::escape(pair.second) << "\""; + std::cout << "/>" << std::endl; + } + } + /** \overload for one attribute pair */ + void xmlNode( const std::string & name_r, XmlNode::AttrDefType attr_r ) + { xmlNode( name_r, { attr_r } ); } + + /////////////////////////////////////////////////////////////////// + /// \class TitleNode + /// \brief XmlNode with optional normal text headline (NL appended) + struct TitleNode : public XmlNode + { + TitleNode( XmlNode && node_r, const std::string & title_r = "" ) + : XmlNode( std::move(node_r) ) + { if ( out().typeNORMAL() && ! title_r.empty() ) std::cout << title_r << std::endl; } + }; + + /////////////////////////////////////////////////////////////////// + /// \class List + /// \brief Printing a list + /////////////////////////////////////////////////////////////////// + template + struct List : protected ParentOut + { + List( Out & out_r, _ListFormater formater_r, out::ListLayout layout_r = typename _ListFormater::ListLayout() ) + : ParentOut( out_r ), _formater( std::move(formater_r) ), _base( std::move(layout_r), out_r.termwidth() ) + {} + + template + List & operator<<( _Tp && val_r ) + { _base.print( _formater( std::forward<_Tp>(val_r) ) ); return *this; } + + private: + _ListFormater _formater; + out::detail::BasicList _base; + }; + /////////////////////////////////////////////////////////////////// + public: + /** Write list from iterator pair */ + template + void list( _Iterator begin_r, _Iterator end_r, _ListFormater && formater_r = _ListFormater() ) + { + switch ( type() ) + { + case TYPE_NORMAL: + { + List<_ListFormater> mlist( *this, std::forward<_ListFormater>(formater_r) ); + for_( it, begin_r, end_r ) mlist << ( *it ); + } + break; + case TYPE_XML: + { + typedef typename _ListFormater::XmlFormater XmlFormater; + List mlist( *this, XmlFormater(), out::XmlListLayout() ); + for_( it, begin_r, end_r ) mlist << ( *it ); + } + break; + } + } + + /** Write list from constainer */ + template + void list( const _Container & container_r, _ListFormater && formater_r = _ListFormater() ) + { list( container_r.begin(), container_r.end(), std::forward<_ListFormater>(formater_r) ); } + + /** Write list from iterator pair enclosed by a \ref Node */ + template + void list( XmlNode && node_r, _Iterator begin_r, _Iterator end_r, _ListFormater && formater_r = _ListFormater() ) + { XmlNode guard( std::move(node_r) ); list( begin_r, end_r, std::forward<_ListFormater>(formater_r) ); } + + /** Write list from constainer enclosed by a \ref Node */ + template + void list( XmlNode && node_r, const _Container & container_r, _ListFormater && formater_r = _ListFormater() ) + { XmlNode guard( std::move(node_r) ); list( container_r.begin(), container_r.end(), std::forward<_ListFormater>(formater_r) ); } + + /** Write list from container creating a TitleNode with \c size="nnn" attribue and + * replacing optional \c %1% in \a title_r with size. */ + template + void list( const std::string & nodeName_r, const std::string & title_r, const _Container & container_r, _ListFormater && formater_r = _ListFormater() ) + { + TitleNode guard( XmlNode( *this, nodeName_r, XmlNode::AttrDefType( "size", str::numstring( container_r.size() ) ) ), + (boost::formatNAC( title_r ) % container_r.size()).str() ); + list( container_r, std::forward<_ListFormater>(formater_r) ); + } + +public: + /** NORMAL: An empty line */ + void gap() { if ( type() == TYPE_NORMAL ) std::cout << std::endl; } + /** * Show an info message. * @@ -329,15 +670,25 @@ public: public: /** Get current verbosity. */ - Verbosity verbosity() { return _verbosity; } + Verbosity verbosity() const { return _verbosity; } /** Set current verbosity. */ void setVerbosity(Verbosity verbosity) { _verbosity = verbosity; } /** Return the type of the instance. */ - Type type() { return _type; } + TypeBit type() const { return _type; } + + /** Test for a specific type */ + bool type( TypeBit type_r ) const { return type() == type_r; } + /** \overload test for TYPE_NORMAL */ + bool typeNORMAL() const { return type( TYPE_NORMAL ); } + /** \overload test for TPE_XML */ + bool typeXML() const { return type( TYPE_XML ); } protected: + /** Width for formated output [unlimited]. */ + virtual unsigned termwidth() const { return unsigned(-1); } + /** * Determine whether the output is intended for the particular type. */ @@ -358,9 +709,11 @@ protected: private: Verbosity _verbosity; - Type _type; + const TypeBit _type; }; +ZYPP_DECLARE_OPERATORS_FOR_FLAGS(Out::Type); + /////////////////////////////////////////////////////////////////// /// \class Out::ProgressBar /// \brief Convenience class for progress output. @@ -447,6 +800,10 @@ public: void error( const std::string & label_r ) { _progress.name( label_r ); error( true ); } + /** \overload also change the progress bar label and disambiguate. */ + void error( const char * label_r ) + { _progress.name( label_r ); error( true ); } + /** \overload also change the progress bar label. */ void error( tribool error_r, const std::string & label_r ) { _progress.name( label_r ); error( error_r ); } diff --git a/src/output/OutNormal.cc b/src/output/OutNormal.cc index 066c28c..ded41e2 100644 --- a/src/output/OutNormal.cc +++ b/src/output/OutNormal.cc @@ -420,15 +420,13 @@ void OutNormal::promptHelp(const PromptOptions & poptions) _newline = true; } -unsigned int OutNormal::termwidth() const { - if(!_isatty) - return 10000; - else +unsigned OutNormal::termwidth() const +{ + if ( _isatty ) { struct winsize wns; if (!ioctl(1, TIOCGWINSZ, &wns)) return wns.ws_col; - else - return 10000; } + return Out::termwidth(); // unlimited } diff --git a/src/output/OutNormal.h b/src/output/OutNormal.h index 9c7372c..909d0ef 100644 --- a/src/output/OutNormal.h +++ b/src/output/OutNormal.h @@ -80,13 +80,13 @@ public: protected: virtual bool mine(Type type); + /* Return current terminal width or 'unsigned(-1)' when failed */ + virtual unsigned termwidth() const; + private: bool infoWarningFilter(Verbosity verbosity, Type mask); void displayProgress(const std::string & s, int percent); void displayTick(const std::string & s); - /* Return current terminal width - * or return 10000 when failed */ - unsigned int termwidth() const; bool _use_colors; bool _isatty; -- 2.7.4