--- /dev/null
+// =========================================================================
+// @author Leonardo Florez-Valencia (florez-l@javeriana.edu.co)
+// =========================================================================
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <queue>
+#include <set>
+#include <sstream>
+#include <streambuf>
+#include <string>
+#include <vector>
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/program_options.hpp>
+#include <boost/tokenizer.hpp>
+#include <mstch/mstch.hpp>
+
+// -------------------------------------------------------------------------
+typedef boost::filesystem::path TPath;
+typedef std::vector< std::string > TStrings;
+typedef std::map< std::string, TStrings > TDefinitions;
+struct TData
+{
+ TPath Input;
+ TPath Output;
+ std::set< TPath > IncludePaths;
+ TStrings InputLines;
+ TStrings CompleteLines;
+ TDefinitions Definitions;
+};
+TData Data;
+
+// -------------------------------------------------------------------------
+bool Arguments( int argc, char* argv[] );
+bool Read( const std::string& input, std::string& buffer );
+bool Lines( const std::string& input, TStrings& lines );
+void Includes( TStrings& new_lines, const TStrings& lines );
+void Definitions( TDefinitions& defs, const TStrings& lines );
+int Parse( );
+void Render( std::ostream& out, const std::string& in, mstch::map& context );
+
+// -------------------------------------------------------------------------
+int main( int argc, char* argv[] )
+{
+ if( Arguments( argc, argv ) )
+ {
+ if( Lines( Data.Input.string( ), Data.InputLines ) )
+ {
+ Includes( Data.CompleteLines, Data.InputLines );
+ Definitions( Data.Definitions, Data.CompleteLines );
+ return( Parse( ) );
+ }
+ else
+ return( 1 );
+ }
+ else
+ return( 1 );
+}
+
+// -------------------------------------------------------------------------
+bool Arguments( int argc, char* argv[] )
+{
+ // Declare the supported options.
+ boost::program_options::options_description desc( "Allowed options" );
+ desc.add_options( )
+ ( "help,h", "produce help message" )
+ (
+ "input,i",
+ boost::program_options::value< std::string >( ),
+ "Input"
+ )
+ (
+ "output,o",
+ boost::program_options::value< std::string >( ),
+ "Output"
+ )
+ (
+ "include,I",
+ boost::program_options::value< TStrings >( ),
+ "Include directory(ies)"
+ )
+ ;
+
+ try
+ {
+ // Parse input arguments
+ boost::program_options::variables_map vm;
+ boost::program_options::store(
+ boost::program_options::parse_command_line( argc, argv, desc ), vm
+ );
+ boost::program_options::notify( vm );
+ if( vm.count( "help" ) )
+ {
+ std::cerr << desc << std::endl;
+ return( false );
+
+ } // fi
+ if( vm.count( "input" ) == 0 || vm.count( "output" ) == 0 )
+ {
+ std::cerr
+ << "Invalid usage: --input and --output are required."
+ << std::endl << desc << std::endl;
+ return( false );
+
+ } // fi
+
+ // Get values
+ Data.Input =
+ boost::filesystem::canonical(
+ TPath( vm[ "input" ].as< std::string >( ) )
+ );
+ Data.Output = TPath( vm[ "output" ].as< std::string >( ) );
+ Data.IncludePaths.clear( );
+ Data.IncludePaths.insert( Data.Input.parent_path( ) );
+ if( vm.count( "include" ) > 0 )
+ for( const std::string& dir: vm[ "include" ].as< TStrings >( ) )
+ Data.IncludePaths.insert( boost::filesystem::canonical( TPath( dir ) ) );
+ return( true );
+ }
+ catch( std::exception& err )
+ {
+ std::cerr << "Error caught: " << err.what( ) << std::endl;
+ std::cerr << desc << std::endl;
+ return( false );
+
+ } // yrt
+}
+
+// -------------------------------------------------------------------------
+bool Read( const std::string& input, std::string& buffer )
+{
+ std::ifstream in( input.c_str( ) );
+ if( !in )
+ {
+ std::cerr
+ << "===============================" << std::endl
+ << "Error caught: " << std::endl
+ << "could not load input file \"" << input << "\"" << std::endl
+ << "===============================" << std::endl
+ << std::endl;
+ return( false );
+
+ } // fi
+ typedef std::istreambuf_iterator< char > _TDIt;
+ std::istringstream str( std::string( ( _TDIt( in ) ), _TDIt( ) ) );
+ buffer = str.str( );
+ in.close( );
+ return( true );
+}
+
+// -------------------------------------------------------------------------
+bool Lines( const std::string& input, TStrings& lines )
+{
+ std::string buffer;
+ if( Read( input, buffer ) )
+ {
+ lines.clear( );
+ std::istringstream str( buffer );
+ std::string line;
+ while( std::getline( str, line ) )
+ lines.push_back( line );
+ return( true );
+ }
+ else
+ return( false );
+}
+
+// -------------------------------------------------------------------------
+void Includes( TStrings& new_lines, const TStrings& lines )
+{
+ bool ok = true;
+ new_lines.clear( );
+ for( const std::string& line: lines )
+ {
+ std::size_t a = line.find_first_of( "$" );
+ std::size_t b = line.find( "$include", a );
+ if( a == b && a != std::string::npos )
+ {
+ std::size_t s = line.find_first_of( "\"" );
+ std::size_t l = line.find_last_of( "\"" );
+ std::string fname = line.substr( s + 1, l - s - 1 );
+ std::set< TPath >::const_iterator pIt = Data.IncludePaths.begin( );
+ bool found = false;
+ while( pIt != Data.IncludePaths.end( ) && !found )
+ {
+ TPath incl = *pIt;
+ incl /= fname;
+ if( boost::filesystem::exists( incl ) )
+ {
+ TStrings incl_lines;
+ if( Lines( incl.string( ), incl_lines ) )
+ {
+ for( const std::string& l: incl_lines )
+ {
+ if( l.find( "$include" ) != std::string::npos )
+ ok = false;
+ new_lines.push_back( l );
+
+ } // rof
+
+ } // fi
+ found = true;
+
+ } // fi
+ pIt++;
+
+ } // elihw
+ }
+ else
+ new_lines.push_back( line );
+
+ } // rof
+
+ if( !ok )
+ {
+ TStrings new_new_lines;
+ Includes( new_new_lines, new_lines );
+ new_lines = new_new_lines;
+
+ } // fi
+}
+
+// -------------------------------------------------------------------------
+void Definitions( TDefinitions& defs, const TStrings& lines )
+{
+ // Tokenizer
+ typedef boost::char_separator< char > _TSep;
+ typedef boost::tokenizer< _TSep > _TTok;
+
+ // Identify definitions
+ for( const std::string& line: lines )
+ {
+ std::size_t a = line.find_first_not_of( " \t" );
+ if( a != std::string::npos )
+ {
+ if( line[ a ] == '$' )
+ {
+ _TTok tokens( line, _TSep( "=;" ) );
+ _TTok::const_iterator t = tokens.begin( );
+ std::string d = *t + "$";
+
+ std::pair< TDefinitions::iterator, bool > i =
+ defs.insert( TDefinitions::value_type( d, TStrings( ) ) );
+ if( i.second )
+ {
+ for( ++t; t != tokens.end( ); ++t )
+ i.first->second.push_back( *t );
+ if( i.first->second.size( ) == 0 )
+ defs.erase( i.first );
+ }
+ else
+ throw std::runtime_error( "Duplicated defition: " + d );
+
+ } // fi
+
+ } // fi
+
+ } // rof
+
+ // Expand definitions
+ bool stop = false;
+ while( !stop )
+ {
+ // Change all possible values
+ for( TDefinitions::value_type& d: defs )
+ {
+ TStrings nValues;
+ for( const std::string& v: d.second )
+ {
+ std::size_t a = v.find_first_of( "$" );
+ if( a != std::string::npos )
+ {
+ std::size_t b = v.find_first_of( "$", a + 1 );
+ std::string c = v.substr( a, b - a + 1 );
+ TDefinitions::const_iterator cIt = defs.find( c );
+ if( cIt != defs.end( ) )
+ {
+ for( std::string cV: cIt->second )
+ {
+ std::string vnew = v;
+ boost::algorithm::replace_all( vnew, c, cV );
+ nValues.push_back( vnew );
+
+ } // rof
+
+ } // fi
+ }
+ else
+ nValues.push_back( v );
+
+ } // rof
+ d.second = nValues;
+
+ } // rof
+
+ // Check stop
+ stop = true;
+ for( TDefinitions::value_type& d: defs )
+ {
+ for( const std::string& v: d.second )
+ stop &= ( v.find_first_of( "$" ) == std::string::npos );
+
+ } // rof
+
+ } // elihw
+
+ // Clear definitions
+ TDefinitions::iterator dIt = defs.begin( );
+ while( dIt != defs.end( ) )
+ {
+ if( dIt->second.size( ) == 0 )
+ {
+ TDefinitions::iterator eIt = dIt;
+ dIt++;
+ defs.erase( eIt );
+ }
+ else
+ dIt++;
+
+ } // elihw
+}
+
+// -------------------------------------------------------------------------
+int Parse( )
+{
+ // Parse lines
+ std::stringstream lines;
+ for( const std::string& line: Data.InputLines )
+ {
+ // Check if a this is a definition line
+ std::size_t a = line.find_first_of( "$" );
+ std::size_t b = line.find_first_of( "=" );
+ bool ok = true;
+ if( a != std::string::npos && b != std::string::npos )
+ {
+ std::string c = line.substr( a, b - a ) + "$";
+ if( Data.Definitions.find( c ) != Data.Definitions.end( ) )
+ ok = false;
+
+ } // fi
+
+ if( a != std::string::npos )
+ {
+ if( a == line.find( "$include", a ) )
+ ok = false;
+
+ } // fi
+
+ if( ok )
+ {
+ std::string t = line;
+
+ std::set< std::string > tags;
+ for( const TDefinitions::value_type& d: Data.Definitions )
+ {
+ if( line.find( d.first ) != std::string::npos )
+ tags.insert( d.first );
+
+ } // rof
+
+ if( tags.size( ) > 0 )
+ {
+ std::stringstream pre, pos;
+ std::set< std::string >::const_iterator tIt = tags.begin( );
+ std::set< std::string >::const_reverse_iterator rtIt = tags.rbegin( );
+ for( ; tIt != tags.end( ); ++tIt, ++rtIt )
+ {
+ pre << "{{#" << tIt->substr( 1, tIt->size( ) - 2 ) << "}}";
+ pos << "{{/" << rtIt->substr( 1, rtIt->size( ) - 2 ) << "}}";
+ boost::algorithm::replace_all(
+ t, *tIt, "{{" + tIt->substr( 1, tIt->size( ) - 2 ) + "}}"
+ );
+
+ } // rof
+ pre << std::endl << t << std::endl << pos.str( );
+ t = pre.str( );
+
+ } // fi
+ lines << t << std::endl;
+
+ } // fi
+
+ } // rof
+
+
+ // Create moustache context
+ typedef std::pair< std::string, std::string > TStringPair;
+ typedef std::pair< std::string, mstch::array > TStringArrayPair;
+ mstch::map context;
+ for( const TDefinitions::value_type& d: Data.Definitions )
+ {
+ mstch::array values;
+ std::string c = d.first.substr( 1, d.first.size( ) - 2 );
+ for( const std::string& v: d.second )
+ {
+ mstch::map def;
+ def.insert( TStringPair( c, v ) );
+ values.push_back( def );
+
+ } // rof
+ context.insert( TStringArrayPair( c, values ) );
+
+ } // rof
+
+ // Parse moustache syntax and save output file
+ std::ofstream out( Data.Output.string( ).c_str( ) );
+ if( !out )
+ {
+ std::cerr
+ << "===============================" << std::endl
+ << "Error caught: " << std::endl
+ << "could not save to output file \"" << Data.Output.string( )
+ << "\"" << std::endl
+ << "===============================" << std::endl
+ << std::endl;
+ return( 1 );
+
+ } // fi
+
+ Render( out, lines.str( ), context );
+ out << std::endl;
+ out.close( );
+ return( 0 );
+}
+
+// -------------------------------------------------------------------------
+void Render( std::ostream& out, const std::string& in, mstch::map& context )
+{
+ std::string s = mstch::render( in, context );
+ boost::algorithm::replace_all( s, "<", "<" );
+ boost::algorithm::replace_all( s, ">", ">" );
+ out << s;
+}
+
+// eof - $RCSfile$