1
// (C) Copyright Gennadiy Rozental 2005-2008.
2
// Use, modification, and distribution are subject to the
3
// Boost Software License, Version 1.0. (See accompanying file
4
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6
// See http://www.boost.org/libs/test for the library home page.
10
// Version : $Revision: 54633 $
12
// Description : flexible configuration file iterator implementation
13
// ***************************************************************************
15
// Boost.Runtime.Parameter
16
#include <boost/test/utils/runtime/config.hpp>
18
#include <boost/test/utils/runtime/file/config_file_iterator.hpp>
19
#include <boost/test/utils/runtime/validation.hpp>
22
#include <boost/test/utils/runtime/env/environment.hpp>
27
#include <boost/utility.hpp>
28
#include <boost/scoped_array.hpp>
29
#include <boost/bind.hpp>
32
#include <boost/test/utils/basic_cstring/compare.hpp>
33
#include <boost/test/utils/algorithm.hpp>
34
#include <boost/test/utils/iterator/token_iterator.hpp>
35
#include <boost/test/utils/assign_op.hpp>
48
namespace BOOST_RT_PARAM_NAMESPACE {
52
// ************************************************************************** //
53
// ************** symbol_to_value_map ************** //
54
// ************************************************************************** //
56
template<typename ValueType>
57
struct symbol_to_value_map : std::map<cstring, ValueType> {
58
template<typename ParamType>
59
void add( cstring name, ParamType const& value )
61
using namespace unit_test;
63
m_name_store.push_back( dstring() );
65
assign_op( m_name_store.back(), name, 0 );
66
assign_op( (*this)[m_name_store.back()], value, 0 );
68
void remove( cstring name )
70
std::list<dstring>::iterator it = std::find( m_name_store.begin(), m_name_store.end(), name );
72
m_name_store.erase( it );
78
std::list<dstring> m_name_store;
81
// ************************************************************************** //
82
// ************** symbol_table_t ************** //
83
// ************************************************************************** //
85
typedef symbol_to_value_map<dstring> symbol_table_t;
87
// ************************************************************************** //
88
// ************** command_handler_map ************** //
89
// ************************************************************************** //
91
typedef symbol_to_value_map<config_file_iterator::command_handler> command_handler_map;
93
// ************************************************************************** //
94
// ************** is_valid_identifier ************** //
95
// ************************************************************************** //
98
is_valid_identifier( cstring const& source )
100
if( source.is_empty() )
103
cstring::const_iterator it = source.begin();
105
if( !std::isalpha( *it ) )
108
while( ++it < source.end() ) {
109
if( !std::isalnum( *it ) && *it != BOOST_RT_PARAM_LITERAL( '_' ) && *it != BOOST_RT_PARAM_LITERAL( '-' ) )
116
// ************************************************************************** //
117
// ************** include_level ************** //
118
// ************************************************************************** //
120
struct include_level;
121
typedef std::auto_ptr<include_level> include_level_ptr;
123
struct include_level : noncopyable
126
explicit include_level( cstring file_name, cstring path_separators, include_level* parent = 0 );
129
std::ifstream m_stream;
130
location m_curr_location;
131
include_level_ptr m_parent;
134
//____________________________________________________________________________//
136
include_level::include_level( cstring file_name, cstring path_separators, include_level* parent_ )
137
: m_parent( parent_ )
139
if( file_name.is_empty() )
142
assign_op( m_curr_location.first, file_name, 0 );
143
m_curr_location.second = 0;
145
m_stream.open( m_curr_location.first.c_str() );
147
if( !m_stream.is_open() && !!m_parent.get() ) {
148
cstring parent_path = m_parent->m_curr_location.first;
149
cstring::iterator it = unit_test::find_last_of( parent_path.begin(), parent_path.end(),
150
path_separators.begin(),
151
path_separators.end() );
153
if( it != parent_path.end() ) {
154
assign_op( m_curr_location.first, cstring( parent_path.begin(), it+1 ), 0 );
155
m_curr_location.first.append( file_name.begin(), file_name.end() );
157
m_stream.open( m_curr_location.first.c_str() );
161
BOOST_RT_PARAM_VALIDATE_LOGIC( m_stream.is_open(), BOOST_RT_PARAM_LITERAL( "can't open file " ) << file_name );
164
//____________________________________________________________________________//
166
// ************************************************************************** //
167
// ************** config_file_iterator::Impl ************** //
168
// ************************************************************************** //
170
struct config_file_iterator::Impl : noncopyable {
174
bool get_next_line( cstring& next_line );
176
void process_command_line( cstring line );
177
void process_include( cstring line );
178
void process_define( cstring line );
179
void process_undef( cstring line );
180
void process_ifdef( cstring line );
181
void process_ifndef( cstring line );
182
void process_else( cstring line );
183
void process_endif( cstring line );
185
boost::optional<cstring>
186
get_macro_value( cstring macro_name, bool ignore_missing = true );
187
void substitute_macros( cstring& where );
189
bool is_active_line() { return m_inactive_ifdef_level == 0; }
191
static bool match_front( cstring str, cstring pattern )
193
return str.size() >= pattern.size() && str.substr( 0, pattern.size() ) == pattern;
195
static bool match_back( cstring str, cstring pattern )
197
return str.size() >= pattern.size() && str.substr( str.size() - pattern.size() ) == pattern;
200
// Configurable parameters
201
dstring m_path_separators;
202
char_type m_line_delimeter;
203
dstring m_sl_comment_delimeter;
204
dstring m_command_delimeter;
206
dstring m_macro_ref_begin;
207
dstring m_macro_ref_end;
209
dstring m_include_kw;
217
std::size_t m_buffer_size;
219
bool m_trim_trailing_spaces;
220
bool m_trim_leading_spaces;
221
bool m_skip_empty_lines;
222
bool m_detect_missing_macro;
225
dstring m_post_subst_line;
226
scoped_array<char> m_buffer;
227
include_level_ptr m_curr_level;
228
symbol_table_t m_symbols_table;
229
std::vector<bool> m_conditional_states;
230
std::size_t m_inactive_ifdef_level;
231
command_handler_map m_command_handler_map;
234
//____________________________________________________________________________//
236
config_file_iterator::Impl::Impl()
237
: m_path_separators( BOOST_RT_PARAM_LITERAL( "/\\" ) )
238
, m_line_delimeter( BOOST_RT_PARAM_LITERAL( '\n' ) )
239
, m_sl_comment_delimeter( BOOST_RT_PARAM_LITERAL( "#" ) )
240
, m_command_delimeter( BOOST_RT_PARAM_LITERAL( "$" ) )
241
, m_line_beak( BOOST_RT_PARAM_LITERAL( "\\" ) )
242
, m_macro_ref_begin( BOOST_RT_PARAM_LITERAL( "$" ) )
243
, m_macro_ref_end( BOOST_RT_PARAM_LITERAL( "$" ) )
245
, m_include_kw( BOOST_RT_PARAM_LITERAL( "include" ) )
246
, m_define_kw( BOOST_RT_PARAM_LITERAL( "define" ) )
247
, m_undef_kw( BOOST_RT_PARAM_LITERAL( "undef" ) )
248
, m_ifdef_kw( BOOST_RT_PARAM_LITERAL( "ifdef" ) )
249
, m_ifndef_kw( BOOST_RT_PARAM_LITERAL( "ifndef" ) )
250
, m_else_kw( BOOST_RT_PARAM_LITERAL( "else" ) )
251
, m_endif_kw( BOOST_RT_PARAM_LITERAL( "endif" ) )
253
, m_buffer_size( 8192 )
255
, m_trim_trailing_spaces( true )
256
, m_trim_leading_spaces( false )
257
, m_skip_empty_lines( true )
258
, m_detect_missing_macro( true )
260
, m_inactive_ifdef_level( 0 )
263
//____________________________________________________________________________//
266
config_file_iterator::Impl::get_next_line( cstring& line )
268
bool broken_line = false;
272
while( !m_curr_level->m_stream.eof() || !!m_curr_level->m_parent.get() ) {
273
// 10. Switch to upper include level if current one is finished
274
// 20. Read/append next file line
275
// 30. Increment line number
276
// 40. Remove comments
277
// 50. Remove trailing and leading spaces
278
// 60. Skip empty string
279
// 70. Concatenate broken lines if needed. Put the result into line
280
// 80. If line is not completed, try to finish it by reading the next line
281
// 90. Process command line
282
// 100. Substitute macros references with their definitions
283
// 110. Next line found.
285
if( m_curr_level->m_stream.eof() ) { // 10 //
286
m_curr_level = m_curr_level->m_parent;
290
std::ifstream& input = m_curr_level->m_stream;
291
char_type* buffer_insert_pos = broken_line ? m_buffer.get() + line.size() : m_buffer.get();
293
input.getline( buffer_insert_pos, (std::streamsize)(m_buffer_size - line.size()), // 20 //
296
cstring next_line( buffer_insert_pos,
298
? buffer_insert_pos + (input.eof() ? input.gcount() : (input.gcount()-1))
299
: buffer_insert_pos );
302
m_curr_level->m_curr_location.second++; // 30 //
304
cstring::size_type comment_pos = next_line.find( m_sl_comment_delimeter );
305
if( comment_pos != cstring::npos )
306
next_line.trim_right( next_line.begin()+comment_pos ); // 40 //
308
if( m_trim_trailing_spaces ) // 50 //
309
next_line.trim_right();
310
if( m_trim_leading_spaces && !broken_line )
311
next_line.trim_left();
313
if( next_line.is_empty() ) { // 60 //
314
if( m_skip_empty_lines )
317
next_line.assign( buffer_insert_pos, buffer_insert_pos );
320
line = broken_line ? cstring( line.begin(), next_line.end() ) : next_line; // 70 //
322
broken_line = match_back( line, m_line_beak );
323
if( broken_line ) { // 80 //
324
line.trim_right( 1 );
328
if( match_front( line, m_command_delimeter ) ) { // 90 //
329
process_command_line( line );
333
if( !is_active_line() )
336
substitute_macros( line ); // 100 //
338
return true; // 110 //
341
BOOST_RT_PARAM_VALIDATE_LOGIC( !broken_line, BOOST_RT_PARAM_LITERAL( "broken line is not completed" ) );
342
BOOST_RT_PARAM_VALIDATE_LOGIC( m_conditional_states.size() == 0,
343
BOOST_RT_PARAM_LITERAL( "matching endif command is missing" ) );
348
//____________________________________________________________________________//
350
boost::optional<cstring>
351
config_file_iterator::Impl::get_macro_value( cstring macro_name, bool ignore_missing )
353
symbol_table_t::const_iterator it = m_symbols_table.find( macro_name );
355
if( it == m_symbols_table.end() ) {
356
boost::optional<cstring> macro_value; // !! variable actually may have different type
359
env::get( macro_name, macro_value );
362
BOOST_RT_PARAM_VALIDATE_LOGIC( macro_value || ignore_missing || !m_detect_missing_macro,
363
BOOST_RT_PARAM_LITERAL( "Unknown macro \"" ) << macro_name << BOOST_RT_PARAM_LITERAL( "\"" ) );
366
if( !ignore_missing )
367
macro_value = cstring();
370
m_symbols_table.add( macro_name, *macro_value );
375
return boost::optional<cstring>( cstring( it->second ) );
378
//____________________________________________________________________________//
381
config_file_iterator::Impl::process_command_line( cstring line )
383
line.trim_left( m_command_delimeter.size() );
385
unit_test::string_token_iterator tit( line, unit_test::max_tokens = 2 );
387
command_handler_map::const_iterator it = m_command_handler_map.find( *tit );
389
BOOST_RT_PARAM_VALIDATE_LOGIC( it != m_command_handler_map.end(), BOOST_RT_PARAM_LITERAL( "Invalid command " ) << *tit );
393
(it->second)( *tit );
396
//____________________________________________________________________________//
399
config_file_iterator::Impl::process_include( cstring line )
401
using namespace unit_test;
403
if( !is_active_line() )
406
string_token_iterator tit( line, kept_delimeters = dt_none );
408
BOOST_RT_PARAM_VALIDATE_LOGIC( tit != string_token_iterator(),
409
BOOST_RT_PARAM_LITERAL( "include file name missing" ) );
411
cstring include_file_name = *tit;
413
BOOST_RT_PARAM_VALIDATE_LOGIC( ++tit == string_token_iterator(),
414
BOOST_RT_PARAM_LITERAL( "unexpected tokens at the end of include command" ) );
416
substitute_macros( include_file_name );
418
m_curr_level.reset( new include_level( include_file_name, m_path_separators, m_curr_level.release() ) );
421
//____________________________________________________________________________//
424
config_file_iterator::Impl::process_define( cstring line )
426
using namespace unit_test;
428
if( !is_active_line() )
431
string_token_iterator tit( line, (kept_delimeters = dt_none, max_tokens = 2 ));
433
cstring macro_name = *tit;
434
BOOST_RT_PARAM_VALIDATE_LOGIC( is_valid_identifier( macro_name ),
435
BOOST_RT_PARAM_LITERAL( "invalid macro name" ) );
437
cstring macro_value = *(++tit);
438
substitute_macros( macro_value );
440
m_symbols_table.add( macro_name, macro_value );
443
//____________________________________________________________________________//
446
config_file_iterator::Impl::process_undef( cstring line )
448
if( !is_active_line() )
451
cstring macro_name = line;
452
BOOST_RT_PARAM_VALIDATE_LOGIC( is_valid_identifier( macro_name ),
453
BOOST_RT_PARAM_LITERAL( "invalid macro name" ) );
455
m_symbols_table.remove( macro_name );
458
//____________________________________________________________________________//
461
config_file_iterator::Impl::process_ifdef( cstring line )
463
m_conditional_states.push_back( true );
464
if( !is_active_line() )
467
cstring macro_name = line;
468
BOOST_RT_PARAM_VALIDATE_LOGIC( is_valid_identifier( macro_name ),
469
BOOST_RT_PARAM_LITERAL( "invalid macro name" ) );
471
if( !get_macro_value( macro_name ) )
472
m_inactive_ifdef_level = m_conditional_states.size();
475
//____________________________________________________________________________//
478
config_file_iterator::Impl::process_ifndef( cstring line )
480
m_conditional_states.push_back( true );
481
if( !is_active_line() )
484
cstring macro_name = line;
485
BOOST_RT_PARAM_VALIDATE_LOGIC( is_valid_identifier( macro_name ),
486
BOOST_RT_PARAM_LITERAL( "invalid macro name" ) );
488
if( get_macro_value( macro_name ) )
489
m_inactive_ifdef_level = m_conditional_states.size();
492
//____________________________________________________________________________//
495
config_file_iterator::Impl::process_else( cstring line )
497
BOOST_RT_PARAM_VALIDATE_LOGIC( m_conditional_states.size() > 0 && m_conditional_states.back(),
498
BOOST_RT_PARAM_LITERAL( "else without matching if" ) );
500
m_inactive_ifdef_level = m_conditional_states.size() == m_inactive_ifdef_level ? 0 : m_conditional_states.size();
502
BOOST_RT_PARAM_VALIDATE_LOGIC( line.is_empty(), BOOST_RT_PARAM_LITERAL( "unexpected tokens at the end of else command" ) );
505
//____________________________________________________________________________//
508
config_file_iterator::Impl::process_endif( cstring line )
510
BOOST_RT_PARAM_VALIDATE_LOGIC( m_conditional_states.size() > 0, BOOST_RT_PARAM_LITERAL( "endif without matching if" ) );
512
if( m_conditional_states.size() == m_inactive_ifdef_level )
513
m_inactive_ifdef_level = 0;
515
m_conditional_states.pop_back();
516
BOOST_RT_PARAM_VALIDATE_LOGIC( line.is_empty(), BOOST_RT_PARAM_LITERAL( "unexpected tokens at the end of endif command" ) );
519
//____________________________________________________________________________//
522
config_file_iterator::Impl::substitute_macros( cstring& where )
524
m_post_subst_line.clear();
525
cstring::size_type pos;
527
while( (pos = where.find( m_macro_ref_begin )) != cstring::npos ) {
528
m_post_subst_line.append( where.begin(), pos );
530
where.trim_left( where.begin() + pos + m_macro_ref_begin.size() );
532
pos = where.find( m_macro_ref_end );
534
BOOST_RT_PARAM_VALIDATE_LOGIC( pos != cstring::npos, BOOST_RT_PARAM_LITERAL( "incomplete macro reference" ) );
536
cstring value = *get_macro_value( where.substr( 0, pos ), false );
537
m_post_subst_line.append( value.begin(), value.size() );
539
where.trim_left( where.begin() + pos + m_macro_ref_end.size() );
542
if( !m_post_subst_line.empty() ) {
543
m_post_subst_line.append( where.begin(), where.size() );
544
where = m_post_subst_line;
548
//____________________________________________________________________________//
550
// ************************************************************************** //
551
// ************** runtime::file::config_file_iterator ************** //
552
// ************************************************************************** //
555
config_file_iterator::construct()
557
m_pimpl.reset( new Impl );
560
//____________________________________________________________________________//
563
config_file_iterator::load( cstring file_name )
565
m_pimpl->m_curr_level.reset( new include_level( file_name, m_pimpl->m_path_separators ) );
566
m_pimpl->m_buffer.reset( new char[m_pimpl->m_buffer_size] );
568
register_command_handler( m_pimpl->m_include_kw, bind( &Impl::process_include, m_pimpl.get(), _1 ) );
569
register_command_handler( m_pimpl->m_define_kw, bind( &Impl::process_define, m_pimpl.get(), _1 ) );
570
register_command_handler( m_pimpl->m_undef_kw, bind( &Impl::process_undef, m_pimpl.get(), _1 ) );
571
register_command_handler( m_pimpl->m_ifdef_kw, bind( &Impl::process_ifdef, m_pimpl.get(), _1 ) );
572
register_command_handler( m_pimpl->m_ifndef_kw, bind( &Impl::process_ifndef, m_pimpl.get(), _1 ) );
573
register_command_handler( m_pimpl->m_else_kw, bind( &Impl::process_else, m_pimpl.get(), _1 ) );
574
register_command_handler( m_pimpl->m_endif_kw, bind( &Impl::process_endif, m_pimpl.get(), _1 ) );
579
//____________________________________________________________________________//
582
config_file_iterator::curr_location()
584
return m_pimpl->m_curr_level->m_curr_location;
587
//____________________________________________________________________________//
590
config_file_iterator::register_command_handler( cstring command_kw, command_handler const& ch )
592
m_pimpl->m_command_handler_map.add( command_kw, ch );
595
//____________________________________________________________________________//
598
config_file_iterator::get()
600
return m_pimpl->get_next_line( m_value );
603
//____________________________________________________________________________//
606
config_file_iterator::set_parameter( rtti::id_t id, cstring value )
608
BOOST_RTTI_SWITCH( id ) {
609
BOOST_RTTI_CASE( cfg_detail::path_separators_t )
610
assign_op( m_pimpl->m_path_separators , value, 0 );
611
BOOST_RTTI_CASE( cfg_detail::sl_comment_delimeter_t )
612
assign_op( m_pimpl->m_sl_comment_delimeter , value, 0 );
613
BOOST_RTTI_CASE( cfg_detail::command_delimeter_t )
614
assign_op( m_pimpl->m_command_delimeter , value, 0 );
615
BOOST_RTTI_CASE( cfg_detail::line_beak_t )
616
assign_op( m_pimpl->m_line_beak , value, 0 );
617
BOOST_RTTI_CASE( cfg_detail::macro_ref_begin_t )
618
assign_op( m_pimpl->m_macro_ref_begin , value, 0 );
619
BOOST_RTTI_CASE( cfg_detail::macro_ref_end_t )
620
assign_op( m_pimpl->m_macro_ref_end , value, 0 );
621
BOOST_RTTI_CASE( cfg_detail::include_kw_t )
622
assign_op( m_pimpl->m_include_kw , value, 0 );
623
BOOST_RTTI_CASE( cfg_detail::define_kw_t )
624
assign_op( m_pimpl->m_define_kw , value, 0 );
625
BOOST_RTTI_CASE( cfg_detail::undef_kw_t )
626
assign_op( m_pimpl->m_undef_kw , value, 0 );
627
BOOST_RTTI_CASE( cfg_detail::ifdef_kw_t )
628
assign_op( m_pimpl->m_ifdef_kw , value, 0 );
629
BOOST_RTTI_CASE( cfg_detail::ifndef_kw_t )
630
assign_op( m_pimpl->m_ifndef_kw , value, 0 );
631
BOOST_RTTI_CASE( cfg_detail::else_kw_t )
632
assign_op( m_pimpl->m_else_kw , value, 0 );
633
BOOST_RTTI_CASE( cfg_detail::endif_kw_t )
634
assign_op( m_pimpl->m_endif_kw , value, 0 );
638
//____________________________________________________________________________//
641
config_file_iterator::set_parameter( rtti::id_t id, bool value )
643
BOOST_RTTI_SWITCH( id ) {
644
BOOST_RTTI_CASE( cfg_detail::trim_leading_spaces_t )
645
m_pimpl->m_trim_leading_spaces = value;
646
BOOST_RTTI_CASE( cfg_detail::trim_trailing_spaces_t )
647
m_pimpl->m_trim_trailing_spaces = value;
648
BOOST_RTTI_CASE( cfg_detail::skip_empty_lines_t )
649
m_pimpl->m_skip_empty_lines = value;
650
BOOST_RTTI_CASE( cfg_detail::detect_missing_macro_t )
651
m_pimpl->m_detect_missing_macro = value;
655
//____________________________________________________________________________//
658
config_file_iterator::set_parameter( rtti::id_t id, char_type value )
660
BOOST_RTTI_SWITCH( id ) {
661
BOOST_RTTI_CASE( cfg_detail::line_delimeter_t )
662
m_pimpl->m_line_delimeter = value;
666
//____________________________________________________________________________//
669
config_file_iterator::set_parameter( rtti::id_t id, std::size_t value )
671
BOOST_RTTI_SWITCH( id ) {
672
BOOST_RTTI_CASE( cfg_detail::buffer_size_t )
673
m_pimpl->m_buffer_size = value;
677
//____________________________________________________________________________//
681
} // namespace BOOST_RT_PARAM_NAMESPACE