nginxconfig
C++libraryforparsingandprintingnginx.conf
|
00001 00011 #include <nginxconfig/config.hpp> 00012 00013 #ifndef NGINXCONFIG_DEBUG 00014 # define NGINXCONFIG_DEBUG 0 00015 #endif 00016 00022 #ifndef NGINXCONFIG_USE_BOOST_REGEX 00023 # define NGINXCONFIG_USE_BOOST_REGEX 0 00024 #endif 00025 00031 #ifndef NGINXCONFIG_CHECK_REGEX_IMPLEMENTATION 00032 # define NGINXCONFIG_CHECK_REGEX_IMPLEMENTATION 1 00033 #endif 00034 00035 #include <nginxconfig/ast.hpp> 00036 #include <nginxconfig/parse.hpp> 00037 #include <nginxconfig/parse_types.hpp> 00038 00039 #include <algorithm> 00040 #include <cassert> 00041 #include <fstream> 00042 #include <sstream> 00043 00044 #if NGINXCONFIG_DEBUG 00045 # include <iostream> 00046 # define NGINXCONFIG_DEBUG_PRINT(x) std::cerr << x << std::endl; 00047 #else 00048 # define NGINXCONFIG_DEBUG_PRINT(x) 00049 #endif 00050 00051 #if NGINXCONFIG_USE_BOOST_REGEX 00052 # include <boost/regex.hpp> 00053 #else 00054 # if NGINXCONFIG_CHECK_REGEX_IMPLEMENTATION 00055 # ifdef __GNUC__ 00056 # if (__GNUC__ == 4) && (__GNUC_MINOR__ < 9) 00057 # error "Cannot use Standard Library regex implementation with GCC < 4.9! Please compile with NGINXCONFIG_USE_BOOST_REGEX=1 (or disable this check with NGINXCONFIG_CHECK_REGEX_IMPLEMENTATION=0)." 00058 # endif 00059 # endif 00060 # endif 00061 # include <regex> 00062 #endif 00063 00064 namespace nginxconfig 00065 { 00066 00068 // parse_error // 00070 00071 static std::string parse_error_what(parse_error::size_type line, 00072 parse_error::size_type column, 00073 parse_error::size_type character, 00074 const std::string& message 00075 ) 00076 { 00077 std::ostringstream stream; 00078 stream << "On line " << line; 00079 if (column != parse_error::no_column) 00080 stream << " column " << column; 00081 00082 stream << " (char " << character << "): " 00083 << message; 00084 return stream.str(); 00085 } 00086 00087 parse_error::parse_error(size_type line_, size_type column_, size_type character_, std::string message_) : 00088 std::runtime_error(parse_error_what(line_, column_, character_, message_)), 00089 _line(line_), 00090 _column(column_), 00091 _character(character_), 00092 _message(std::move(message_)) 00093 { } 00094 00095 parse_error::~parse_error() noexcept = default; 00096 00098 // Parser Implementation // 00100 00101 namespace parser 00102 { 00103 00104 bool context::next() 00105 { 00106 character_no = character_no_next; 00107 if (std::getline(input, current)) 00108 { 00109 NGINXCONFIG_DEBUG_PRINT("LINE:\t" << current); 00110 character_no_next = character_no + current.size(); 00111 ++line_no; 00112 return true; 00113 } 00114 else 00115 { 00116 return false; 00117 } 00118 } 00119 00120 ast_entry::attribute_list split_attributes(const std::string& attrs) 00121 { 00122 ast_entry::attribute_list out; 00123 std::string::size_type pos = 0; 00124 while ((pos = attrs.find_first_not_of(" \t", pos)) != std::string::npos) 00125 { 00126 auto pos_end = attrs.find_first_of(" \t", pos); 00127 out.emplace_back(attrs.substr(pos, pos_end - pos)); 00128 pos = pos_end; 00129 } 00130 return out; 00131 } 00132 00133 line_components line_components::create_from_line(const std::string& line) 00134 { 00135 #if NGINXCONFIG_USE_BOOST_REGEX 00136 using regex = boost::regex; 00137 using smatch = boost::smatch; 00138 using boost::regex_match; 00139 namespace regex_constants = boost::regex_constants; 00140 #else 00141 using regex = std::regex; 00142 using smatch = std::smatch; 00143 using std::regex_match; 00144 namespace regex_constants = std::regex_constants; 00145 #endif 00146 00147 // All lines follow the same basic format: 00148 // 0) whole match 00149 // 1) name 00150 // 2) attributes 00151 // 3) either {, } or ; 00152 // 4) comment including # 00153 // 5) comment not including # 00154 static const regex line_regex(R"([ \t]*([A-Za-z_][A-Za-z0-9_]*)?[ \t]*([^{};#]*)([{};]?)[ \t]*(#(.*))?)", 00155 regex_constants::syntax_option_type(regex_constants::ECMAScript | regex_constants::optimize) 00156 ); 00157 00158 smatch results; 00159 line_components out; 00160 if (regex_match(line, results, line_regex)) 00161 { 00162 #if NGINXCONFIG_DEBUG 00163 NGINXCONFIG_DEBUG_PRINT("REGEX MATCH sz=" << results.size()); 00164 for (const auto& x : results) 00165 { 00166 NGINXCONFIG_DEBUG_PRINT("\t|" << x << '|'); 00167 } 00168 #endif 00169 assert(results.size() == 6); 00170 out.name = results[1]; 00171 out.attributes = split_attributes(results[2]); 00172 const std::string& tok = results[3]; 00173 out.category = tok.size() == 0 ? line_kind::comment 00174 : tok[0] == '{' ? line_kind::complex_start 00175 : tok[0] == '}' ? line_kind::complex_end 00176 : tok[0] == ';' ? line_kind::simple 00177 : line_kind::unknown; 00178 out.comment = results[5]; 00179 NGINXCONFIG_DEBUG_PRINT(out.comment); 00180 } 00181 return out; 00182 } 00183 00184 bool parse_generic(context& cxt, ast_entry& owner) 00185 { 00186 while (cxt.next()) 00187 { 00188 line_components components = line_components::create_from_line(cxt.current); 00189 switch (components.category) 00190 { 00191 case line_kind::comment: 00192 owner.children().emplace_back(ast_entry::make_comment(std::move(components.comment))); 00193 break; 00194 case line_kind::simple: 00195 owner.children().emplace_back(ast_entry::make_simple(std::move(components.name), 00196 std::move(components.attributes), 00197 std::move(components.comment) 00198 ) 00199 ); 00200 break; 00201 case line_kind::complex_start: 00202 { 00203 ast_entry child = ast_entry::make_complex(std::move(components.name), 00204 std::move(components.attributes) 00205 ); 00206 while (parse_generic(cxt, child)) 00207 { 00208 // do nothing 00209 } 00210 owner.children().emplace_back(std::move(child)); 00211 break; 00212 } 00213 case line_kind::complex_end: 00214 if (owner.kind() == ast_entry_kind::document) 00215 throw cxt.create_parse_error(parse_error::no_column, "Unmatched end of nested entry"); 00216 else 00217 return false; 00218 case line_kind::unknown: 00219 default: 00220 throw cxt.create_parse_error(parse_error::no_column, "Indecipherable line: \"", cxt.current, '\"'); 00221 } 00222 } 00223 if (owner.kind() == ast_entry_kind::complex) 00224 throw cxt.create_parse_error(parse_error::no_column, "EOF reached while inside nested entry"); 00225 else 00226 return false; 00227 } 00228 00229 } 00230 00232 // Entry Points // 00234 00235 ast_entry parse(std::istream& input) 00236 { 00237 parser::context cxt(input); 00238 auto out = ast_entry::make_document({}); 00239 parser::parse_generic(cxt, out); 00240 return out; 00241 } 00242 00244 ast_entry parse_file(const std::string& filename) 00245 { 00246 std::ifstream file(filename.c_str()); 00247 return parse(file); 00248 } 00249 00250 }