// SPDX-License-Identifier: MIT
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <iostream>
#include <sstream>
#include <string>
#include <unistd.h>
#include "MException.hh"
#include "MString.hh"
#include "MStringParser.hh"
#ifndef MSTRING_VERSION
#define MSTRING_VERSION "unknown"
#endif
namespace
{
void Help( std::ostream &stream )
{
stream << "usage:" << std::endl
<< " mstring [-h] [-v] [-t] [-w] [-m <module>] [-O <folder>] [-I <folders>]... [<message file>]" << std::endl
<< std::endl
<< " -h display this page" << std::endl
<< " -v display the version" << std::endl
<< " -t trace the parsing (prints every $MESSAGE)" << std::endl
<< " -w do not print warnings" << std::endl
<< " -m <module> the module name of the output files (overrides $MODULE)" << std::endl
<< " -O <folder> write the output files into <folder> instead of the current folder" << std::endl
<< " -I <folders> folders searched for $IMPORT files, ':'-separated; may be repeated" << std::endl
<< std::endl
<< " <message file> the message definition file; standard input when omitted or '-'" << std::endl;
}
void AddFolders( std::vector<std::string> &folders, const std::string &list )
{
std::string::size_type begin = 0;
while ( begin <= list.size( ) )
{
std::string::size_type const end = list.find( ':', begin );
std::string const folder = list.substr( begin, end == std::string::npos ? std::string::npos : end - begin );
if ( ! folder.empty( ) )
{
folders.push_back( folder );
}
if ( end == std::string::npos )
{
break;
}
begin = end + 1;
}
}
void Report( const std::vector<fedem::parser::ParseError> &diagnostics, bool warnings )
{
for ( const auto &e : diagnostics )
{
if ( ! warnings && e.kind == fedem::parser::ParseError::Kind::Warning )
{
continue;
}
std::cerr << ( e.filename.empty( ) ? "mstring" : e.filename );
if ( e.line != 0 )
{
std::cerr << ':' << e.line << ':' << e.col;
}
std::cerr << ": "
<< ( e.kind == fedem::parser::ParseError::Kind::Warning ? "warning" : "error" )
<< ": " << e.message << std::endl;
}
}
}
int main( int argc, char **argv )
try
{
std::string moduleName;
std::string outputFolder;
bool warnings = true;
Module module;
ParseContext context( module );
int c;
opterr = 0;
while ( ( c = getopt( argc, argv, "hvtwm:I:O:" ) ) != -1 )
{
switch ( c )
{
case 'm':
moduleName = optarg;
break;
case 'O':
outputFolder = optarg;
break;
case 'I':
AddFolders( context.importFolders, optarg );
break;
case 'h':
Help( std::cout );
return EXIT_SUCCESS;
case 'v':
std::cout << "mstring " << MSTRING_VERSION << std::endl;
return EXIT_SUCCESS;
case 't':
context.trace = true;
break;
case 'w':
warnings = false;
break;
default:
if ( optopt == 'm' || optopt == 'O' || optopt == 'I' )
std::cerr << "mstring: option -" << static_cast<char>( optopt ) << " needs an argument" << std::endl;
else
std::cerr << "mstring: unknown option -" << static_cast<char>( optopt ) << std::endl;
Help( std::cerr );
return EXIT_FAILURE;
}
}
if ( argc - optind > 1 )
{
std::cerr << "mstring: too many parameters" << std::endl;
Help( std::cerr );
return EXIT_FAILURE;
}
std::string const input = argc - optind == 1 ? argv[optind] : "-";
MStringParser parser( context );
bool const parsed = input == "-" ? parser.parseStream( "<stdin>", std::cin )
: parser.parseFile( input );
if ( parsed )
{
CheckTranslations( context );
}
Report( context.diagnostics, warnings );
if ( ! parsed )
{
return EXIT_FAILURE;
}
if ( ! moduleName.empty( ) )
{
module.Name = moduleName;
}
ValidateModule( module );
if ( ! outputFolder.empty( ) && ! std::filesystem::is_directory( outputFolder ) )
{
throw MException( "output folder '" + outputFolder + "' does not exist" );
}
std::filesystem::path const folder( outputFolder );
std::string const banner = input == "-" ? "" : std::filesystem::path( input ).filename( ).string( );
std::string const header = HeaderFileName( module );
std::string const source = SourceFileName( module );
if ( ! header.empty( ) )
{
std::ostringstream stream;
WriteCppInterface( stream, module, banner );
WriteIfChanged( ( folder / header ).string( ), stream.str( ) );
}
if ( ! source.empty( ) )
{
std::ostringstream stream;
WriteCppImplementation( stream, module, banner );
WriteIfChanged( ( folder / source ).string( ), stream.str( ) );
}
return EXIT_SUCCESS;
}
catch ( const MException &ex )
{
std::cerr << "mstring: error: " << ex.what( ) << std::endl;
return EXIT_FAILURE;
}
catch ( const std::exception &ex )
{
std::cerr << "mstring: error: (std) " << ex.what( ) << std::endl;
return EXIT_FAILURE;
}
catch ( ... )
{
std::cerr << "mstring: error: unexpected exception" << std::endl;
return EXIT_FAILURE;
}