2017-08-13 22:57:53 +02:00
/*
KLayout Layout Viewer
Copyright ( C ) 2006 - 2017 Matthias Koefferlein
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
# include "tlLog.h"
# include "tlCommandLineParser.h"
# include <QFileInfo>
namespace tl
{
// ------------------------------------------------------------------------
// ArgBase implementation
ArgBase : : ParsedOption : : ParsedOption ( const std : : string & option )
2017-08-19 00:36:45 +02:00
: optional ( false ) , inverted ( false ) , advanced ( false ) , non_advanced ( false ) , repeated ( false )
2017-08-13 22:57:53 +02:00
{
tl : : Extractor ex ( option . c_str ( ) ) ;
2017-08-14 22:42:12 +02:00
2017-08-17 09:26:15 +02:00
while ( ! ex . at_end ( ) ) {
if ( ex . test ( " # " ) ) {
advanced = true ;
} else if ( ex . test ( " / " ) ) {
non_advanced = true ;
2017-08-19 00:36:45 +02:00
} else if ( ex . test ( " * " ) ) {
repeated = true ;
} else if ( ex . test ( " ! " ) ) {
inverted = true ;
} else if ( ex . test ( " ? " ) ) {
optional = true ;
2017-08-17 09:26:15 +02:00
} else if ( ex . test ( " [ " ) ) {
const char * t = ex . get ( ) ;
while ( ! ex . at_end ( ) & & * ex ! = ' ] ' ) {
+ + ex ;
}
group + = std : : string ( t , 0 , ex . get ( ) - t ) ;
ex . test ( " ] " ) ;
} else {
break ;
}
}
2017-08-13 22:57:53 +02:00
while ( ! ex . at_end ( ) ) {
if ( ex . test ( " -- " ) ) {
optional = true ;
ex . read_word ( long_option , " _- " ) ;
if ( ex . test ( " = " ) ) {
2017-08-19 10:23:19 +02:00
ex . read_word_or_quoted ( name ) ;
2017-08-13 22:57:53 +02:00
}
} else if ( ex . test ( " - " ) ) {
optional = true ;
ex . read_word ( short_option , " " ) ;
if ( ex . test ( " = " ) ) {
2017-08-19 10:23:19 +02:00
ex . read_word_or_quoted ( name ) ;
2017-08-13 22:57:53 +02:00
}
} else {
2017-08-19 10:23:19 +02:00
ex . read_word_or_quoted ( name ) ;
2017-08-13 22:57:53 +02:00
}
ex . test ( " | " ) ;
}
}
2017-08-17 09:26:15 +02:00
ArgBase : : ArgBase ( const std : : string & option , const std : : string & brief_doc , const std : : string & long_doc )
2017-08-13 22:57:53 +02:00
: m_option ( option ) , m_brief_doc ( brief_doc ) , m_long_doc ( long_doc )
{
// .. nothing yet ..
}
ArgBase : : ~ ArgBase ( )
{
// .. nothing yet ..
}
bool
ArgBase : : is_option ( ) const
{
return ! m_option . short_option . empty ( ) | | ! m_option . long_option . empty ( ) ;
}
2017-08-16 23:48:59 +02:00
// ------------------------------------------------------------------------
// Internal argument classes to implement info arguments
class HelpArg
: public ArgBase
{
public :
HelpArg ( )
: ArgBase ( " -h|--help " , " Shows the usage and exits " , " " )
{
// .. nothing yet ..
}
ArgBase * clone ( ) const
{
return new HelpArg ( ) ;
}
void action ( CommandLineOptions * options ) const
{
2017-08-17 09:26:15 +02:00
options - > produce_help ( options - > program_name ( ) , false ) ;
throw tl : : CancelException ( ) ;
}
} ;
class AdvancedHelpArg
: public ArgBase
{
public :
AdvancedHelpArg ( )
: ArgBase ( " /--help-all " , " Shows all options (including advanced) and exits " , " " )
{
// .. nothing yet ..
}
ArgBase * clone ( ) const
{
return new AdvancedHelpArg ( ) ;
}
void action ( CommandLineOptions * options ) const
{
options - > produce_help ( options - > program_name ( ) , true ) ;
2017-08-16 23:48:59 +02:00
throw tl : : CancelException ( ) ;
}
} ;
class LicenseArg
: public ArgBase
{
public :
LicenseArg ( )
: ArgBase ( " --license " , " Shows the license and exits " , " " )
{
// .. nothing yet ..
}
ArgBase * clone ( ) const
{
return new LicenseArg ( ) ;
}
void action ( CommandLineOptions * options ) const
{
options - > produce_license ( ) ;
throw tl : : CancelException ( ) ;
}
} ;
class VersionArg
: public ArgBase
{
public :
VersionArg ( )
2017-08-17 09:26:15 +02:00
: ArgBase ( " --version " , " Shows the version and exits " , " " )
2017-08-16 23:48:59 +02:00
{
// .. nothing yet ..
}
ArgBase * clone ( ) const
{
return new VersionArg ( ) ;
}
void action ( CommandLineOptions * options ) const
{
options - > produce_version ( ) ;
throw tl : : CancelException ( ) ;
}
} ;
2017-08-17 09:26:15 +02:00
class VerbosityArg
: public ArgBase
{
public :
VerbosityArg ( )
: ArgBase ( " -d|--debug-level " , " Sets the verbosity level " ,
" The verbosity level is an integer. Typical values are: \n "
" * 0: silent \n "
" * 10: somewhat verbose \n "
" * 11: somewhat verbose plus timing information \n "
" * 20: verbose \n "
" * 21: verbose plus timing information \n "
" ... "
)
{
// .. nothing yet ..
}
ArgBase * clone ( ) const
{
return new VerbosityArg ( ) ;
}
bool wants_value ( ) const
{
return true ;
}
void take_value ( tl : : Extractor & ex )
{
int d = 0 ;
ex . read ( d ) ;
tl : : verbosity ( d ) ;
}
} ;
2017-08-13 22:57:53 +02:00
// ------------------------------------------------------------------------
// CommandLineOptions implementation
2017-08-16 23:48:59 +02:00
std : : string CommandLineOptions : : m_version ;
std : : string CommandLineOptions : : m_license ;
2017-08-13 22:57:53 +02:00
CommandLineOptions : : CommandLineOptions ( )
{
// Populate with the built-in options
2017-08-17 09:26:15 +02:00
* this < < HelpArg ( ) < < AdvancedHelpArg ( ) < < VersionArg ( ) < < LicenseArg ( ) < < VerbosityArg ( ) ;
2017-08-13 22:57:53 +02:00
}
CommandLineOptions : : ~ CommandLineOptions ( )
{
for ( std : : vector < ArgBase * > : : const_iterator a = m_args . begin ( ) ; a ! = m_args . end ( ) ; + + a ) {
delete * a ;
}
m_args . clear ( ) ;
}
CommandLineOptions &
CommandLineOptions : : operator < < ( const ArgBase & a )
{
m_args . push_back ( a . clone ( ) ) ;
return * this ;
}
static void
print_string_formatted ( const std : : string & indent , unsigned int columns , const std : : string & text )
{
tl : : info < < indent < < tl : : noendl ;
unsigned int c = 0 ;
const char * t = text . c_str ( ) ;
while ( * t ) {
const char * tt = t ;
bool at_beginning = ( c = = 0 ) ;
while ( * t & & * t ! = ' ' & & * t ! = ' \n ' ) {
+ + t ;
+ + c ;
if ( c = = columns & & ! at_beginning ) {
tl : : info < < " " ;
tl : : info < < indent < < tl : : noendl ;
2017-08-16 23:48:59 +02:00
c = ( unsigned int ) ( t - tt ) ;
2017-08-13 22:57:53 +02:00
}
}
tl : : info < < std : : string ( tt , 0 , t - tt ) < < tl : : noendl ;
2017-08-16 23:48:59 +02:00
2017-08-13 22:57:53 +02:00
while ( * t = = ' ' ) {
+ + t ;
}
if ( * t = = ' \n ' ) {
+ + t ;
tl : : info < < tl : : endl < < indent < < tl : : noendl ;
c = 0 ;
} else {
if ( c + 1 = = columns ) {
tl : : info < < tl : : endl < < indent < < tl : : noendl ;
c = 0 ;
} else {
tl : : info < < " " < < tl : : noendl ;
c + = 1 ;
}
}
while ( * t = = ' ' ) {
+ + t ;
}
}
tl : : info < < " " ;
}
static std : : string
pad_string ( unsigned int columns , const std : : string & text )
{
std : : string s = text ;
while ( s . size ( ) < size_t ( columns ) ) {
s + = " " ;
}
return s ;
}
2017-08-16 23:48:59 +02:00
struct NameCompare
{
bool operator ( ) ( ArgBase * a , ArgBase * b )
{
if ( a - > is_option ( ) ! = b - > is_option ( ) ) {
return a - > is_option ( ) < b - > is_option ( ) ;
}
if ( ! a - > is_option ( ) ) {
return false ;
}
2017-08-17 09:26:15 +02:00
if ( a - > option ( ) . group ! = b - > option ( ) . group ) {
return a - > option ( ) . group < b - > option ( ) . group ;
}
2017-08-16 23:48:59 +02:00
if ( a - > option ( ) . short_option . empty ( ) ! = b - > option ( ) . short_option . empty ( ) ) {
return a - > option ( ) . short_option . empty ( ) < b - > option ( ) . short_option . empty ( ) ;
}
if ( a - > option ( ) . short_option ! = b - > option ( ) . short_option ) {
return a - > option ( ) . short_option < b - > option ( ) . short_option ;
}
return a - > option ( ) . long_option < b - > option ( ) . long_option ;
}
} ;
2017-08-13 22:57:53 +02:00
void
2017-08-17 09:26:15 +02:00
CommandLineOptions : : produce_help ( const std : : string & program_name , bool advanced )
2017-08-13 22:57:53 +02:00
{
2017-08-16 23:48:59 +02:00
int columns = 80 ;
2017-08-13 22:57:53 +02:00
tl : : info < < " Usage: " < < tl : : endl ;
tl : : info < < " " < < program_name < < " [options] " < < tl : : noendl ;
2017-08-16 23:48:59 +02:00
std : : vector < ArgBase * > sorted_args = m_args ;
std : : stable_sort ( sorted_args . begin ( ) , sorted_args . end ( ) , NameCompare ( ) ) ;
for ( std : : vector < ArgBase * > : : const_iterator a = sorted_args . begin ( ) ; a ! = sorted_args . end ( ) ; + + a ) {
2017-08-15 00:13:57 +02:00
if ( ! ( * a ) - > is_option ( ) ) {
2017-08-13 22:57:53 +02:00
if ( ( * a ) - > option ( ) . optional ) {
tl : : info < < " [< " < < ( * a ) - > option ( ) . name < < " >] " < < tl : : noendl ;
} else {
tl : : info < < " < " < < ( * a ) - > option ( ) . name < < " > " < < tl : : noendl ;
}
}
}
tl : : info < < tl : : endl ;
print_string_formatted ( " " , columns , m_brief ) ;
tl : : info < < tl : : endl ;
unsigned int short_option_width = 0 ;
unsigned int long_option_width = 0 ;
unsigned int name_width = 0 ;
2017-08-16 23:48:59 +02:00
for ( std : : vector < ArgBase * > : : const_iterator a = sorted_args . begin ( ) ; a ! = sorted_args . end ( ) ; + + a ) {
2017-08-13 22:57:53 +02:00
name_width = std : : max ( name_width , ( unsigned int ) ( * a ) - > option ( ) . name . size ( ) ) ;
short_option_width = std : : max ( short_option_width , ( unsigned int ) ( * a ) - > option ( ) . short_option . size ( ) ) ;
long_option_width = std : : max ( long_option_width , ( unsigned int ) ( * a ) - > option ( ) . long_option . size ( ) ) ;
}
tl : : info < < " Arguments: " < < tl : : endl ;
2017-08-16 23:48:59 +02:00
for ( std : : vector < ArgBase * > : : const_iterator a = sorted_args . begin ( ) ; a ! = sorted_args . end ( ) ; + + a ) {
2017-08-13 22:57:53 +02:00
if ( ( * a ) - > is_option ( ) ) {
continue ;
}
std : : string n = " < " + ( * a ) - > option ( ) . name + " > " ;
if ( ( * a ) - > option ( ) . optional ) {
n + = " (optional) " ;
}
tl : : info < < " " < < pad_string ( name_width + 13 , n ) < < ( * a ) - > brief_doc ( ) ;
tl : : info < < " " ;
if ( ! ( * a ) - > long_doc ( ) . empty ( ) ) {
print_string_formatted ( " " , columns , ( * a ) - > long_doc ( ) ) ;
tl : : info < < " " ;
}
}
tl : : info < < " " ;
tl : : info < < " Options: " < < tl : : endl ;
2017-08-15 00:13:57 +02:00
print_string_formatted ( " " , columns ,
" Options can be specified in a short (with one dash) or a long form "
" (with two dashes). If a value is required, it can be specified either "
" as the following argument or added to the option with an equal sign (=). " ) ;
2017-08-17 09:26:15 +02:00
tl : : info < < tl : : endl < < " List of options: " < < tl : : endl ;
std : : string header = pad_string ( short_option_width + 5 , " Short " ) + " "
+ pad_string ( long_option_width + 5 , " Long " ) + " "
+ pad_string ( name_width + 3 , " Value " ) + " " + " Description " ;
2017-08-15 00:13:57 +02:00
2017-08-17 09:26:15 +02:00
tl : : info < < " " < < header < < tl : : endl ;
std : : string prev_group ;
bool hidden = false ;
2017-08-15 00:13:57 +02:00
2017-08-16 23:48:59 +02:00
for ( std : : vector < ArgBase * > : : const_iterator a = sorted_args . begin ( ) ; a ! = sorted_args . end ( ) ; + + a ) {
2017-08-17 09:26:15 +02:00
2017-08-13 22:57:53 +02:00
if ( ! ( * a ) - > is_option ( ) ) {
continue ;
2017-08-17 09:26:15 +02:00
} else if ( ( * a ) - > option ( ) . advanced & & ! advanced ) {
hidden = true ;
continue ;
} else if ( ( * a ) - > option ( ) . non_advanced & & advanced ) {
continue ;
2017-08-13 22:57:53 +02:00
}
2017-08-17 09:26:15 +02:00
if ( ( * a ) - > option ( ) . group ! = prev_group ) {
prev_group = ( * a ) - > option ( ) . group ;
tl : : info < < tl : : endl < < " " < < prev_group < < " : " < < tl : : endl ;
tl : : info < < " " < < header < < tl : : endl ;
}
2017-08-13 22:57:53 +02:00
std : : string name ;
if ( ( * a ) - > wants_value ( ) ) {
name = ( * a ) - > option ( ) . name ;
if ( name . empty ( ) ) {
name = " value " ;
}
}
2017-08-17 09:26:15 +02:00
2017-08-15 00:13:57 +02:00
tl : : info < < " "
2017-08-13 22:57:53 +02:00
< < pad_string ( short_option_width + 5 , ( * a ) - > option ( ) . short_option . empty ( ) ? " " : " - " + ( * a ) - > option ( ) . short_option ) < < " "
< < pad_string ( long_option_width + 5 , ( * a ) - > option ( ) . long_option . empty ( ) ? " " : " -- " + ( * a ) - > option ( ) . long_option ) < < " "
< < pad_string ( name_width + 3 , name ) < < " "
< < ( * a ) - > brief_doc ( ) ;
tl : : info < < " " ;
if ( ! ( * a ) - > long_doc ( ) . empty ( ) ) {
2017-08-15 00:13:57 +02:00
print_string_formatted ( " " , columns , ( * a ) - > long_doc ( ) ) ;
2017-08-13 22:57:53 +02:00
tl : : info < < " " ;
}
2017-08-17 09:26:15 +02:00
}
if ( hidden ) {
tl : : info < < tl : : endl < < " See --help-all for more options. " < < tl : : endl ;
2017-08-13 22:57:53 +02:00
}
}
2017-08-16 23:48:59 +02:00
void
CommandLineOptions : : produce_license ( )
{
tl : : info < < m_license ;
}
void
CommandLineOptions : : produce_version ( )
{
tl : : info < < m_version ;
}
2017-08-13 22:57:53 +02:00
void
CommandLineOptions : : parse ( int argc , char * argv [ ] )
{
2017-08-16 23:48:59 +02:00
m_program_name = tl : : to_string ( QFileInfo ( QString : : fromLocal8Bit ( argv [ 0 ] ) ) . fileName ( ) ) ;
2017-08-13 22:57:53 +02:00
2017-08-17 09:26:15 +02:00
std : : vector < ArgBase * > plain_args ;
std : : map < std : : string , ArgBase * > arg_by_short_option , arg_by_long_option ;
for ( std : : vector < ArgBase * > : : const_iterator i = m_args . begin ( ) ; i ! = m_args . end ( ) ; + + i ) {
if ( ( * i ) - > is_option ( ) ) {
if ( ! ( * i ) - > option ( ) . short_option . empty ( ) ) {
2017-08-17 23:31:05 +02:00
if ( arg_by_short_option . find ( ( * i ) - > option ( ) . short_option ) ! = arg_by_short_option . end ( ) ) {
throw tl : : Exception ( " Command line parser setup: duplicate option - " + ( * i ) - > option ( ) . short_option ) ;
}
2017-08-17 09:26:15 +02:00
arg_by_short_option . insert ( std : : make_pair ( ( * i ) - > option ( ) . short_option , * i ) ) ;
}
if ( ! ( * i ) - > option ( ) . long_option . empty ( ) ) {
2017-08-17 23:31:05 +02:00
if ( arg_by_long_option . find ( ( * i ) - > option ( ) . long_option ) ! = arg_by_long_option . end ( ) ) {
throw tl : : Exception ( " Command line parser setup: duplicate option -- " + ( * i ) - > option ( ) . long_option ) ;
}
2017-08-17 09:26:15 +02:00
arg_by_long_option . insert ( std : : make_pair ( ( * i ) - > option ( ) . long_option , * i ) ) ;
}
} else {
plain_args . push_back ( * i ) ;
}
2017-08-13 22:57:53 +02:00
}
2017-08-17 09:26:15 +02:00
std : : vector < ArgBase * > : : const_iterator next_plain_arg = plain_args . begin ( ) ;
2017-08-13 22:57:53 +02:00
for ( int i = 1 ; i < argc ; + + i ) {
ArgBase * arg = 0 ;
std : : string arg_as_utf8 = tl : : to_string ( QString : : fromLocal8Bit ( argv [ i ] ) ) ;
tl : : Extractor ex ( arg_as_utf8 . c_str ( ) ) ;
if ( ex . test ( " -- " ) ) {
std : : string n ;
2017-08-17 09:26:15 +02:00
ex . read_word ( n , " _- " ) ;
std : : map < std : : string , ArgBase * > : : const_iterator a = arg_by_long_option . find ( n ) ;
if ( a = = arg_by_long_option . end ( ) ) {
2017-08-13 22:57:53 +02:00
throw tl : : Exception ( tl : : to_string ( QObject : : tr ( " Unknown command line option --%1 (use -h for help) " ) . arg ( tl : : to_qstring ( n ) ) ) ) ;
}
2017-08-17 09:26:15 +02:00
arg = a - > second ;
2017-08-13 22:57:53 +02:00
} else if ( ex . test ( " - " ) ) {
std : : string n ;
ex . read_word ( n ) ;
2017-08-17 09:26:15 +02:00
std : : map < std : : string , ArgBase * > : : const_iterator a = arg_by_short_option . find ( n ) ;
if ( a = = arg_by_short_option . end ( ) ) {
throw tl : : Exception ( tl : : to_string ( QObject : : tr ( " Unknown command line option --%1 (use -h for help) " ) . arg ( tl : : to_qstring ( n ) ) ) ) ;
2017-08-13 22:57:53 +02:00
}
2017-08-17 09:26:15 +02:00
arg = a - > second ;
2017-08-13 22:57:53 +02:00
} else {
2017-08-17 09:26:15 +02:00
if ( next_plain_arg = = plain_args . end ( ) ) {
2017-08-13 22:57:53 +02:00
throw tl : : Exception ( tl : : to_string ( QObject : : tr ( " Unknown command line component %1 - no further plain argument expected (use -h for help) " ) . arg ( tl : : to_qstring ( arg_as_utf8 ) ) ) ) ;
}
2017-08-19 00:36:45 +02:00
arg = * next_plain_arg ;
if ( ! arg - > option ( ) . repeated ) {
+ + next_plain_arg ;
}
2017-08-13 22:57:53 +02:00
}
2017-08-19 00:36:45 +02:00
if ( ! arg - > is_option ( ) ) {
2017-08-13 22:57:53 +02:00
2017-08-19 00:36:45 +02:00
arg - > take_value ( ex ) ;
} else if ( arg - > wants_value ( ) ) {
if ( ex . test ( " = " ) ) {
2017-08-13 22:57:53 +02:00
arg - > take_value ( ex ) ;
} else {
if ( ! ex . at_end ( ) ) {
throw tl : : Exception ( tl : : to_string ( QObject : : tr ( " Syntax error in argument at \" ..%1 \" (use -h for help) " ) . arg ( tl : : to_qstring ( ex . get ( ) ) ) ) ) ;
}
+ + i ;
if ( i = = argc ) {
throw tl : : Exception ( tl : : to_string ( QObject : : tr ( " Value missing for last argument (use -h for help) " ) ) ) ;
}
std : : string arg_as_utf8 = tl : : to_string ( QString : : fromLocal8Bit ( argv [ i ] ) ) ;
tl : : Extractor ex_value ( arg_as_utf8 ) ;
arg - > take_value ( ex_value ) ;
}
} else {
2017-08-17 09:26:15 +02:00
2017-08-13 22:57:53 +02:00
if ( ex . test ( " = " ) ) {
arg - > take_value ( ex ) ;
} else {
2017-08-14 22:42:12 +02:00
arg - > mark_present ( arg - > option ( ) . inverted ) ;
2017-08-13 22:57:53 +02:00
}
2017-08-17 09:26:15 +02:00
2017-08-13 22:57:53 +02:00
}
2017-08-19 00:36:45 +02:00
// Execute the action if there is one
2017-08-16 23:48:59 +02:00
arg - > action ( this ) ;
2017-08-13 22:57:53 +02:00
}
2017-08-17 09:26:15 +02:00
if ( next_plain_arg ! = plain_args . end ( ) & & ! ( * next_plain_arg ) - > option ( ) . optional ) {
2017-08-13 22:57:53 +02:00
throw tl : : Exception ( tl : : to_string ( QObject : : tr ( " Additional arguments required (use -h for help) " ) ) ) ;
}
}
}