args-parser 6.3.3
Loading...
Searching...
No Matches
help_printer.hpp
Go to the documentation of this file.
1
31#ifndef ARGS__HELP_PRINTER_HPP__INCLUDED
32#define ARGS__HELP_PRINTER_HPP__INCLUDED
33
34// C++ include.
35#include <vector>
36#include <algorithm>
37#include <functional>
38
39// Args include.
40#include "utils.hpp"
41#include "exceptions.hpp"
42#include "group_iface.hpp"
43#include "groups.hpp"
44#include "command.hpp"
45#include "types.hpp"
47#include "cmd_line.hpp"
48
49
50namespace Args {
51
52class CmdLine;
53class ArgIface;
54
55
56//
57// HelpPrinter
58//
59
64 : public HelpPrinterIface
65{
66public:
68 using ArgPtr = std::unique_ptr< ArgIface, details::Deleter< ArgIface > >;
69
71
72 virtual ~HelpPrinter();
73
75 void print(
77 OutStreamType & to ) override;
78
80 void print(
82 const String & name,
84 OutStreamType & to,
86 Command * parent = nullptr ) override;
87
89 void setExecutable( const String & exe ) override;
90
92 void setAppDescription( const String & desc ) override;
93
95 void setCmdLine( CmdLine * cmd ) override;
96
98 void setLineLength( String::size_type length ) override;
99
101 ArgIface * findArgument( const String & name ) override
102 {
103 return m_cmdLine->findArgument( name );
104 }
105
106private:
108 StringList createUsageString( ArgIface * arg,
109 bool required ) const;
111 StringList splitToWords( const String & s ) const;
113 void printString( OutStreamType & to,
114 const StringList & words,
115 String::size_type currentPos, String::size_type leftMargin,
116 String::size_type rightMargin ) const;
118 void print( ArgIface * arg, OutStreamType & to ) const;
120 void sortArg( const ArgPtr & arg,
121 std::vector< Command* > & commands,
122 std::vector< ArgIface* > & required,
123 std::vector< ArgIface* > & optional,
124 String::size_type & maxFlag,
125 String::size_type & maxName,
126 String::size_type & maxCommand,
127 bool requiredAllOfGroup = false ) const;
129 void printOnlyFor( ArgIface * arg, OutStreamType & to,
130 String::size_type beforeDescription, String::size_type maxFlag ) const;
131
132private:
134
135
136 String m_exeName;
138 String m_appDescription;
140 CmdLine * m_cmdLine;
142 String::size_type m_lineLength;
143}; // class HelpPrinter
144
145static const String c_positional = SL( "[positional]" );
146
147static const String c_commands = SL( "<command>" );
148
149static const String c_options = SL( "<options>" );
150
151
152//
153// HelpPrinter
154//
155
156inline
158 : m_cmdLine( 0 )
159 , m_lineLength( 79 )
160{
161}
162
163inline
167
168static inline void
169printOffset( OutStreamType & to, String::size_type currentPos,
170 String::size_type leftMargin )
171{
172 if( currentPos < leftMargin )
173 to << String( leftMargin - currentPos, ' ' );
174}
175
176static inline void
177calcMaxFlagAndName( ArgIface * arg, String::size_type & maxFlag,
178 String::size_type & maxName )
179{
180 String::size_type f = 1;
181 String::size_type n = ( !arg->argumentName().empty() ?
182 arg->argumentName().length() : ( arg->flag().empty() ?
183 arg->name().length() : 0 ) );
184
185 if( arg->isWithValue() )
186 {
187 if( n > 0 )
188 n += 3 + arg->valueSpecifier().length();
189 else
190 f += 3 + arg->valueSpecifier().length();
191 }
192
193 if( f > maxFlag )
194 maxFlag = f;
195
196 if( n > maxName )
197 maxName = n;
198}
199
200inline void
201HelpPrinter::sortArg( const ArgPtr & arg,
202 std::vector< Command* > & commands,
203 std::vector< ArgIface* > & required,
204 std::vector< ArgIface* > & optional,
205 String::size_type & maxFlag,
206 String::size_type & maxName,
207 String::size_type & maxCommand,
208 bool requiredAllOfGroup ) const
209{
210 GroupIface * g = dynamic_cast< GroupIface* > ( arg.get() );
211
212 if( arg->type() == ArgType::Command )
213 {
214 Command * cmd = static_cast< Command* > ( arg.get() );
215
216 commands.push_back( cmd );
217
218 String::size_type length = cmd->name().length() + ( cmd->isWithValue() ?
219 3 + cmd->valueSpecifier().length() : 0 );
220
221 if( length > maxCommand )
222 maxCommand = length;
223 }
224 else if( g )
225 {
226 if( g->isRequired() && g->type() == ArgType::AllOfGroup )
227 requiredAllOfGroup = true;
228 else
229 requiredAllOfGroup = false;
230
231 auto f = std::bind( &HelpPrinter::sortArg, this, std::placeholders::_1,
232 std::ref( commands ), std::ref( required ),
233 std::ref( optional ), std::ref( maxFlag ),
234 std::ref( maxName ), std::ref( maxCommand ),
235 requiredAllOfGroup );
236
237 std::for_each( g->children().cbegin(),
238 g->children().cend(), f );
239 }
240 else
241 {
242 if( arg->isRequired() || requiredAllOfGroup )
243 required.push_back( arg.get() );
244 else
245 optional.push_back( arg.get() );
246
247 calcMaxFlagAndName( arg.get(), maxFlag, maxName );
248 }
249}
250
251inline void
252HelpPrinter::printOnlyFor( ArgIface * arg, OutStreamType & to,
253 String::size_type beforeDescription, String::size_type ) const
254{
255 String::size_type pos = 0;
256
257 if( !arg->flag().empty() )
258 {
259 to << ' ';
260 ++pos;
261 to << '-' << arg->flag();
262 pos += arg->flag().length() + 1;
263
264 if( !arg->argumentName().empty() )
265 {
266 to << ',';
267 ++pos;
268 }
269 else if( arg->isWithValue() )
270 {
271 to << " <" << arg->valueSpecifier() << '>';
272 pos += arg->valueSpecifier().length() + 3;
273 }
274 }
275 else
276 {
277 printOffset( to, pos, 4 );
278 pos = 4;
279 }
280
281 if( !arg->argumentName().empty() )
282 {
283 to << ' ';
284 ++pos;
285 to << "--" << arg->argumentName();
286 pos += arg->argumentName().length() + 2;
287
288 if( arg->isWithValue() )
289 {
290 to << " <" << arg->valueSpecifier() << '>';
291 pos += arg->valueSpecifier().length() + 3;
292 }
293 }
294 else if( arg->flag().empty() && arg->argumentName().empty() )
295 {
296 to << ' ';
297 ++pos;
298 to << arg->name();
299 pos += arg->name().length();
300
301 if( arg->isWithValue() )
302 {
303 to << " <" << arg->valueSpecifier() << '>';
304 pos += arg->valueSpecifier().length() + 3;
305 }
306 }
307
308 printString( to, splitToWords( arg->description() ), pos,
309 beforeDescription, 0 );
310
311 to << "\n" << "\n";
312
313 to.flush();
314}
315
316namespace details {
317
318template< typename T >
319bool argNameLess( const T & a1, const T & a2 )
320{
321 static const Char dash = SL( '-' );
322
323 if( !a1->name().empty() && !a2->name().empty() )
324 {
325 if( *( a1->name().cbegin() ) != dash &&
326 *( a2->name().cbegin() ) == dash )
327 return true;
328 else if( *( a1->name().cbegin() ) == dash &&
329 *( a2->name().cbegin() ) != dash )
330 return false;
331 else if( a1->argumentName().empty() && !a2->argumentName().empty() )
332 return true;
333 else if( !a1->argumentName().empty() && a2->argumentName().empty() )
334 return false;
335 else
336 return ( a1->name() < a2->name() );
337 }
338 else
339 return ( a1->name() < a2->name() );
340}
341
342} /* namespace details */
343
344inline void
346{
347 std::vector< ArgIface* > required;
348 std::vector< ArgIface* > optional;
349 std::vector< Command* > commands;
350
351 String::size_type maxFlag = 0;
352 String::size_type maxName = 0;
353 String::size_type maxCommand = 0;
354
355 bool requiredAllOfGroup = false;
356
357 auto f = std::bind( &HelpPrinter::sortArg, this, std::placeholders::_1,
358 std::ref( commands ), std::ref( required ),
359 std::ref( optional ), std::ref( maxFlag ),
360 std::ref( maxName ), std::ref( maxCommand ),
361 requiredAllOfGroup );
362
363 std::for_each( m_cmdLine->arguments().cbegin(),
364 m_cmdLine->arguments().cend(), f );
365
366 // Sort arguments by name.
367 std::sort( required.begin(), required.end(),
368 [] ( const auto & a1, const auto & a2 )
369 { return details::argNameLess( a1, a2 ); } );
370
371 std::sort( optional.begin(), optional.end(),
372 [] ( const auto & a1, const auto & a2 )
373 { return details::argNameLess( a1, a2 ); } );
374
375 std::sort( commands.begin(), commands.end(),
376 [] ( const auto & c1, const auto & c2 )
377 { return details::argNameLess( c1, c2 ); } );
378
379 maxFlag += 2;
380 maxName += 2;
381 maxCommand += 2;
382
383 printString( to, splitToWords( m_appDescription ), 0, 0, 0 );
384
385 to << "\n" << "\n";
386
387 if( commands.empty() )
388 {
389 StringList usage;
390
391 usage.push_back( m_exeName );
392
393 bool requiredFlag = true;
394
395 std::function< void ( ArgIface* ) > createUsageAndAppend =
396 [ & ] ( ArgIface * arg )
397 {
398 const StringList words = createUsageString( arg,
399 requiredFlag );
400
401 for( const auto & w : details::asConst( words ) )
402 usage.push_back( w );
403 };
404
405 std::for_each( required.cbegin(), required.cend(),
406 createUsageAndAppend );
407
408 requiredFlag = false;
409
410 std::for_each( optional.cbegin(), optional.cend(),
411 createUsageAndAppend );
412
413 to << "USAGE: ";
414
416 usage.push_back( m_cmdLine->positionalDescription().empty() ? c_positional :
417 m_cmdLine->positionalDescription() );
418
419 printString( to, usage, 7, 7, 1 );
420
421 to << "\n" << "\n";
422 }
423 else
424 {
425 StringList usage;
426
427 usage.push_back( m_exeName );
428 usage.push_back( c_commands );
429
430 if( !optional.empty() || !required.empty() )
431 usage.push_back( c_options );
432
434 usage.push_back( m_cmdLine->positionalDescription().empty() ? c_positional :
435 m_cmdLine->positionalDescription() );
436
437 to << "USAGE: ";
438
439 printString( to, usage, 7, 7, 1 );
440
441 to << "\n" << "\n";
442
443 std::for_each( commands.cbegin(), commands.cend(),
444 [ & ] ( Command * cmd )
445 {
446 String::size_type pos = 2;
447
448 to << " " << cmd->name();
449
450 pos += cmd->name().length();
451
452 if( cmd->isWithValue() )
453 {
454 to << " <" << cmd->valueSpecifier() << ">";
455
456 pos += 3 + cmd->valueSpecifier().length();
457 }
458
459 printString( to, splitToWords( cmd->description() ), pos,
460 maxCommand + 1, 0 );
461
462 to << "\n";
463 } );
464
465 to << "\n";
466 }
467
468 auto printArg = std::bind( &HelpPrinter::printOnlyFor, this,
469 std::placeholders::_1, std::ref( to ),
470 ( maxFlag == 1 ? maxName + 6 : ( maxName + 6 > maxFlag + 1 ?
471 maxName + 6 : maxFlag + 1 ) ),
472 maxFlag );
473
474 if( !required.empty() )
475 {
476 to << "REQUIRED:" << "\n";
477
478 std::for_each( required.cbegin(), required.cend(), printArg );
479 }
480
481 if( !optional.empty() )
482 {
483 to << "OPTIONAL:" << "\n";
484
485 std::for_each( optional.cbegin(), optional.cend(), printArg );
486 }
487
488 to.flush();
489}
490
491inline void
492HelpPrinter::print( const String & name, OutStreamType & to, Command * parent )
493{
494 auto * arg = ( parent ? parent->findChild( name ) : m_cmdLine->findArgument( name ) );
495
496 if( parent && !arg )
497 arg = m_cmdLine->findArgument( name );
498
499 if( arg && arg->type() == ArgType::Command )
500 {
501 Command * cmd = static_cast < Command* > ( arg );
502
503 // Prepare global arguments.
504 std::vector< ArgIface* > grequired;
505 std::vector< ArgIface* > goptional;
506 std::vector< Command* > gcommands;
507
508 String::size_type gmaxFlag = 0;
509 String::size_type gmaxName = 0;
510 String::size_type gmaxCommand = 0;
511
512 bool requiredAllOfGroup = false;
513
514 auto gf = std::bind( &HelpPrinter::sortArg, this, std::placeholders::_1,
515 std::ref( gcommands ), std::ref( grequired ),
516 std::ref( goptional ), std::ref( gmaxFlag ),
517 std::ref( gmaxName ), std::ref( gmaxCommand ),
518 requiredAllOfGroup );
519
520 std::for_each( m_cmdLine->arguments().cbegin(),
521 m_cmdLine->arguments().cend(), gf );
522
523 // Sort global arguments by name.
524 std::sort( grequired.begin(), grequired.end(),
525 [] ( const auto & a1, const auto & a2 )
526 { return details::argNameLess( a1, a2 ); } );
527
528 std::sort( goptional.begin(), goptional.end(),
529 [] ( const auto & a1, const auto & a2 )
530 { return details::argNameLess( a1, a2 ); } );
531
532 gmaxFlag += 2;
533 gmaxName += 2;
534
535 // Prepare arguments of command.
536 std::vector< ArgIface* > required;
537 std::vector< ArgIface* > optional;
538 std::vector< Command* > commands;
539
540 String::size_type maxFlag = 0;
541 String::size_type maxName = 0;
542 String::size_type maxCommand = 0;
543
544 requiredAllOfGroup = false;
545
546 auto f = std::bind( &HelpPrinter::sortArg, this, std::placeholders::_1,
547 std::ref( commands ), std::ref( required ),
548 std::ref( optional ), std::ref( maxFlag ),
549 std::ref( maxName ), std::ref( maxCommand ),
550 requiredAllOfGroup );
551
552 std::for_each( cmd->children().cbegin(),
553 cmd->children().cend(), f );
554
555 // Sort arguments by name.
556 std::sort( required.begin(), required.end(),
557 [] ( const auto & a1, const auto & a2 )
558 { return details::argNameLess( a1, a2 ); } );
559
560 std::sort( optional.begin(), optional.end(),
561 [] ( const auto & a1, const auto & a2 )
562 { return details::argNameLess( a1, a2 ); } );
563
564 std::sort( commands.begin(), commands.end(),
565 [] ( const auto & c1, const auto & c2 )
566 { return details::argNameLess( c1, c2 ); } );
567
568 maxFlag += 2;
569 maxName += 2;
570 maxCommand += 2;
571
572 // Print.
573 printString( to, splitToWords( cmd->longDescription() ), 0, 0, 0 );
574
575 to << "\n\n";
576
577 if( commands.empty() )
578 {
579 to << "USAGE: " << name;
580
581 if( cmd->isWithValue() )
582 to << " <" << cmd->valueSpecifier() << ">";
583
584 if( !grequired.empty() || !goptional.empty() ||
585 !required.empty() || !optional.empty() )
586 to << " <options>";
587
588 to << "\n\n";
589 }
590 else
591 {
592 to << "USAGE: " << name << " <command>";
593
594 if( !optional.empty() || !required.empty() )
595 to << " <options>";
596
597 to << "\n\n";
598
599 std::for_each( commands.cbegin(), commands.cend(),
600 [ & ] ( Command * c )
601 {
602 String::size_type pos = 2;
603
604 to << " " << c->name();
605
606 pos += c->name().length();
607
608 if( c->isWithValue() )
609 {
610 to << " <" << c->valueSpecifier() << ">";
611
612 pos += 3 + c->valueSpecifier().length();
613 }
614
615 printString( to, splitToWords( c->description() ), pos,
616 maxCommand + 1, 0 );
617
618 to << "\n";
619 } );
620
621 to << "\n";
622 }
623
624 // Print command's arguments.
625 auto printArg = std::bind( &HelpPrinter::printOnlyFor, this,
626 std::placeholders::_1, std::ref( to ),
627 ( maxFlag == 1 ? maxName + 6 : ( maxName + 6 > maxFlag + 1 ?
628 maxName + 6 : maxFlag + 1 ) ),
629 maxFlag );
630
631 if( !required.empty() )
632 {
633 to << "REQUIRED:" << "\n";
634
635 std::for_each( required.cbegin(), required.cend(), printArg );
636 }
637
638 if( !optional.empty() )
639 {
640 to << "OPTIONAL:" << "\n";
641
642 std::for_each( optional.cbegin(), optional.cend(), printArg );
643 }
644
645 // Print global arguments.
646 if( !grequired.empty() || !goptional.empty() )
647 {
648 auto printGlobalArg = std::bind( &HelpPrinter::printOnlyFor, this,
649 std::placeholders::_1, std::ref( to ),
650 ( gmaxFlag == 1 ? gmaxName + 6 : ( gmaxName + 6 > gmaxFlag + 1 ?
651 gmaxName + 6 : gmaxFlag + 1 ) ), gmaxFlag );
652
653 if( !grequired.empty() )
654 {
655 to << "GLOBAL REQUIRED:" << "\n";
656
657 std::for_each( grequired.cbegin(), grequired.cend(),
658 printGlobalArg );
659 }
660
661 if( !goptional.empty() )
662 {
663 to << "GLOBAL OPTIONAL:" << "\n";
664
665 std::for_each( goptional.cbegin(), goptional.cend(),
666 printGlobalArg );
667 }
668 }
669
670 to.flush();
671 }
672 else if( arg )
673 print( arg, to );
674 else
675 print( to );
676}
677
678inline void
679HelpPrinter::print( ArgIface * arg, OutStreamType & to ) const
680{
681 StringList usage = createUsageString( arg,
682 arg->isRequired() );
683
684 to << "USAGE: ";
685
686 std::for_each( usage.cbegin(), usage.cend(),
687 [ & ] ( const String & s )
688 { to << s << ' '; }
689 );
690
691 to << "\n" << "\n";
692
693 printString( to, splitToWords( arg->longDescription() ),
694 0, 7, 7 );
695
696 to << "\n" << "\n";
697
698 to.flush();
699}
700
701inline void
702HelpPrinter::setExecutable( const String & exe )
703{
704 m_exeName = exe;
705}
706
707inline void
708HelpPrinter::setAppDescription( const String & desc )
709{
710 m_appDescription = desc;
711}
712
713inline void
714HelpPrinter::setCmdLine( CmdLine * cmd )
715{
716 m_cmdLine = cmd;
717}
718
719inline void
720HelpPrinter::setLineLength( String::size_type length )
721{
722 if( length > 40 )
723 m_lineLength = length;
724 else
725 m_lineLength = 40;
726}
727
728inline StringList
729HelpPrinter::createUsageString( ArgIface * arg, bool required ) const
730{
731 StringList result;
732
733 String usage;
734
735 if( !required )
736 usage.append( SL( "[ " ) );
737
738 if( !arg->flag().empty() )
739 {
740 usage.append( SL( "-" ) );
741 usage.append( arg->flag() );
742
743 if( !arg->argumentName().empty() )
744 {
745 usage.append( SL( "," ) );
746
747 result.push_back( usage );
748
749 usage.clear();
750 }
751 }
752
753 if( !arg->argumentName().empty() )
754 {
755 usage.append( SL( "--" ) );
756 usage.append( arg->argumentName() );
757 }
758 else if( arg->flag().empty() && arg->argumentName().empty() )
759 usage.append( arg->name() );
760
761 if( arg->isWithValue() )
762 {
763 usage.append( SL( " <" ) );
764 usage.append( arg->valueSpecifier() );
765 usage.append( SL( ">" ) );
766 }
767
768 if( !required )
769 usage.append( SL( " ]" ) );
770
771 result.push_back( usage );
772
773 return result;
774}
775
776static inline bool
777isSpaceChar( const Char & c )
778{
779 static const String spaceChars = SL( " \n\t\r" );
780
781 return ( spaceChars.find( c ) != String::npos );
782}
783
784inline StringList
785HelpPrinter::splitToWords( const String & s ) const
786{
787 String word;
788 StringList result;
789
790 std::for_each( s.cbegin(), s.cend(),
791 [ &word, &result ] ( const Char & c )
792 {
793 if( isSpaceChar( c ) )
794 {
795 if( !word.empty() )
796 result.push_back( word );
797
798 word.clear();
799 }
800 else
801 word.append( 1, c );
802 }
803 );
804
805 if( !word.empty() )
806 result.push_back( word );
807
808 return result;
809}
810
811inline void
812HelpPrinter::printString( OutStreamType & to,
813 const StringList & words,
814 String::size_type currentPos, String::size_type leftMargin,
815 String::size_type rightMargin ) const
816{
817 const String::size_type occupied = leftMargin + rightMargin;
818
819 String::size_type maxLineLength = ( occupied < m_lineLength ?
820 m_lineLength - occupied : 0 );
821
822 if( maxLineLength < 20 )
823 {
824 to << "\n\n";
825
826 maxLineLength = m_lineLength - 20;
827
828 currentPos = 0;
829 leftMargin = 20;
830 rightMargin = 0;
831 }
832
833 String::size_type length = 0;
834
835 bool makeOffset = ( currentPos < leftMargin );
836
837 std::for_each( words.cbegin(), words.cend(),
838 [ & ] ( const String & word )
839 {
840 if( makeOffset )
841 {
842 printOffset( to, currentPos, leftMargin );
843
844 makeOffset = false;
845 }
846
847 if( length + word.length() < maxLineLength )
848 {
849 length += word.length();
850
851 to << word;
852
853 if( length < maxLineLength )
854 {
855 ++length;
856 to << ' ';
857 }
858 }
859 else
860 {
861 currentPos = 0;
862
863 length = 0;
864
865 maxLineLength = m_lineLength - leftMargin - rightMargin;
866
867 to << "\n";
868
869 printOffset( to, currentPos, leftMargin );
870
871 to << word;
872
873 length += word.length();
874
875 if( length < maxLineLength )
876 {
877 ++length;
878 to << ' ';
879 }
880 }
881 }
882 );
883}
884
885} /* namespace Args */
886
887#endif // ARGS__HELP_PRINTER_HPP__INCLUDED
Interface for arguments.
Definition arg_iface.hpp:51
virtual ArgIface * findArgument(const String &name)=0
virtual const String & valueSpecifier() const =0
virtual String name() const =0
virtual const String & argumentName() const =0
virtual bool isWithValue() const =0
virtual const String & flag() const =0
CmdLine is class that holds all rguments and parse command line arguments in the correspondence with ...
Definition cmd_line.hpp:123
void clear()
Clear state of the arguments.
Definition cmd_line.hpp:435
@ HandlePositionalArguments
Handle positional arguments.
Definition cmd_line.hpp:137
const String & positionalDescription() const
Definition cmd_line.hpp:216
ArgIface * findArgument(const String &name)
Definition cmd_line.hpp:229
const Arguments & arguments() const
Definition cmd_line.hpp:761
CmdLineOpts parserOptions() const
Definition cmd_line.hpp:167
Command in the command line interface.
Definition command.hpp:53
ArgIface * findChild(const String &name)
Definition command.hpp:309
bool isWithValue() const override
Definition command.hpp:114
const String & longDescription() const override
Definition command.hpp:149
const String & description() const override
Definition command.hpp:138
const String & valueSpecifier() const override
Definition command.hpp:127
const Arguments & children() const
HelpPrinter is a class that prints help.
void setCmdLine(CmdLine *cmd) override
Set command line.
std::unique_ptr< ArgIface, details::Deleter< ArgIface > > ArgPtr
Smart pointer to the argument.
void setExecutable(const String &exe) override
Set executable name.
ArgIface * findArgument(const String &name) override
void setLineLength(String::size_type length) override
Set line length for the help.
void setAppDescription(const String &desc) override
Set description for the application.
void print(OutStreamType &to) override
Print help for all arguments.
Interface for HelpPrinter.
constexpr std::add_const< T >::type & asConst(T &t) noexcept
Adds const to non-const objects.
Definition utils.hpp:72
Definition api.hpp:42
std::string String
String type.
Definition types.hpp:324
std::vector< String > StringList
List of strings.
Definition types.hpp:346
@ AllOfGroup
"All of" group.
@ Command
Command.
std::ostream OutStreamType
Out stream type.
Definition types.hpp:330
String::value_type Char
Char type.
Definition types.hpp:327
#define SL(str)
Definition types.hpp:338
#define DISABLE_COPY(Class)
Macro for disabling copy.
Definition utils.hpp:50