diff options
Diffstat (limited to 'libbuild2/name.cxx')
-rw-r--r-- | libbuild2/name.cxx | 145 |
1 files changed, 126 insertions, 19 deletions
diff --git a/libbuild2/name.cxx b/libbuild2/name.cxx index d956a99..6c48bb3 100644 --- a/libbuild2/name.cxx +++ b/libbuild2/name.cxx @@ -12,9 +12,30 @@ namespace build2 const name empty_name; const names empty_names; + void name:: + canonicalize () + { + // We cannot assume the name part is a valid filesystem name so we will + // have to do the splitting manually. + // + size_t p (path_traits::rfind_separator (value)); + + if (p != string::npos) + { + if (p + 1 == value.size ()) + throw invalid_argument ("empty value"); + + dir /= dir_path (value, p != 0 ? p : 1); // Special case: "/". + + value.erase (0, p + 1); + } + } + string to_string (const name& n) { + assert (!n.pattern); + string r; // Note: similar to to_stream() below. @@ -59,20 +80,76 @@ namespace build2 } ostream& - to_stream (ostream& os, const name& n, bool quote, char pair, bool escape) + to_stream (ostream& os, const name& n, quote_mode q, char pair, bool escape) { - auto write_string = [quote, pair, escape, &os](const string& v) + using pattern_type = name::pattern_type; + + auto write_string = [&os, q, pair, escape] ( + const string& v, + optional<pattern_type> pat = nullopt, + bool curly = false) { - char sc[] = { + // We don't expect the effective quoting mode to be specified for the + // name patterns. + // + assert (q != quote_mode::effective || !pat); + + // Special characters, path pattern characters, and regex pattern + // characters. The latter only need to be quoted in the first position + // and if followed by a non-alphanumeric delimiter. If that's the only + // special character, then we handle it with escaping rather than + // quoting (see the parsing logic for rationale). Additionally, we + // escape leading `+` in the curly braces which is also recognized as a + // path pattern. + // + char nsc[] = { '{', '}', '[', ']', '$', '(', ')', // Token endings. ' ', '\t', '\n', '#', // Spaces. '\\', '"', // Escaping and quoting. '%', // Project name separator. - '*', '?', // Wildcard characters. pair, // Pair separator, if any. '\0'}; - if (quote && v.find ('\'') != string::npos) + char pc[] = { + '*', '?', // Path wildcard characters. + '\0'}; + + auto rc = [] (const string& v) + { + return (v[0] == '~' || v[0] == '^') && v[1] != '\0' && !alnum (v[1]); + }; + + char esc[] = { + '{', '}', '$', '(', // Token endings. + ' ', '\t', '\n', '#', // Spaces. + '"', // Quoting. + pair, // Pair separator, if any. + '\0'}; + + auto ec = [&esc] (const string& v) + { + for (size_t i (0); i < v.size (); ++i) + { + char c (v[i]); + + if (strchr (esc, c) != nullptr || (c == '\\' && v[i + 1] == '\\')) + return true; + } + + return false; + }; + + if (pat) + { + switch (*pat) + { + case pattern_type::path: break; + case pattern_type::regex_pattern: os << '~'; break; + case pattern_type::regex_substitution: os << '^'; break; + } + } + + if (q != quote_mode::none && v.find ('\'') != string::npos) { // Quote the string with the double quotes rather than with the single // one. Escape some of the special characters. @@ -91,7 +168,15 @@ namespace build2 if (escape) os << '\\'; os << '"'; } - else if (quote && v.find_first_of (sc) != string::npos) + // + // Note that a regex pattern does not need to worry about special path + // pattern character but not vice-verse. See the parsing logic for + // details. + // + else if ((q == quote_mode::normal && + (v.find_first_of (nsc) != string::npos || + (!pat && v.find_first_of (pc) != string::npos))) || + (q == quote_mode::effective && ec (v))) { if (escape) os << '\\'; os << '\''; @@ -101,26 +186,43 @@ namespace build2 if (escape) os << '\\'; os << '\''; } + // Note that currently we do not preserve a leading `+` as a pattern + // unless it has other wildcard characters (see the parsing code for + // details). So we escape it both if it's not a pattern or is a path + // pattern. + // + else if (q == quote_mode::normal && + (!pat || *pat == pattern_type::path) && + ((v[0] == '+' && curly) || rc (v))) + { + if (escape) os << '\\'; + os << '\\' << v; + } else os << v; }; uint16_t dv (stream_verb (os).path); // Directory verbosity. - auto write_dir = [dv, quote, &os, &write_string] (const dir_path& d) + auto write_dir = [&os, q, &write_string, dv] ( + const dir_path& d, + optional<pattern_type> pat = nullopt, + bool curly = false) { - if (quote) - write_string (dv < 1 ? diag_relative (d) : d.representation ()); + if (q != quote_mode::none) + write_string (dv < 1 ? diag_relative (d) : d.representation (), + pat, + curly); else os << d; }; - // Note: similar to to_string() below. + // Note: similar to to_string() above. // // If quoted then print empty name as '' rather than {}. // - if (quote && n.empty ()) + if (q != quote_mode::none && n.empty ()) return os << (escape ? "\\'\\'" : "''"); if (n.proj) @@ -147,7 +249,8 @@ namespace build2 if (!pd.empty ()) write_dir (pd); - if (t || (!d && !v)) + bool curly; + if ((curly = t || (!d && !v))) { if (t) write_string (n.type); @@ -156,18 +259,22 @@ namespace build2 } if (v) - write_string (n.value); + write_string (n.value, n.pattern, curly); else if (d) { + // A directory pattern cannot be regex. + // + assert (!n.pattern || *n.pattern == pattern_type::path); + if (rd.empty ()) - write_string (dir_path (".").representation ()); + write_string (dir_path (".").representation (), nullopt, curly); else if (!pd.empty ()) - write_string (rd.leaf ().representation ()); + write_string (rd.leaf ().representation (), n.pattern, curly); else - write_dir (rd); + write_dir (rd, n.pattern, curly); } - if (t || (!d && !v)) + if (curly) os << '}'; return os; @@ -176,7 +283,7 @@ namespace build2 ostream& to_stream (ostream& os, const names_view& ns, - bool quote, + quote_mode q, char pair, bool escape) { @@ -184,7 +291,7 @@ namespace build2 { const name& n (*i); ++i; - to_stream (os, n, quote, pair, escape); + to_stream (os, n, q, pair, escape); if (n.pair) os << n.pair; |