1 /*============================================================================= 2 Copyright (c) 2002 2004 2006 Joel de Guzman 3 Copyright (c) 2004 Eric Niebler 4 Copyright (c) 2005 Thomas Guest 5 Copyright (c) 2013 Daniel James 6 7 Use, modification and distribution is subject to the Boost Software 8 License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 9 http://www.boost.org/LICENSE_1_0.txt) 10 =============================================================================*/ 11 12 #include "include_paths.hpp" 13 #include <cassert> 14 #include <boost/filesystem.hpp> 15 #include <boost/range/algorithm/replace.hpp> 16 #include "for.hpp" 17 #include "glob.hpp" 18 #include "path.hpp" 19 #include "quickbook.hpp" // For the include_path global (yuck) 20 #include "state.hpp" 21 #include "stream.hpp" 22 #include "utils.hpp" 23 24 namespace quickbook 25 { 26 // 27 // check_path 28 // 29 check_path(value const & path,quickbook::state & state)30 path_parameter check_path(value const& path, quickbook::state& state) 31 { 32 if (qbk_version_n >= 107u) { 33 std::string path_text = path.get_encoded(); 34 if (path_text.empty()) { 35 detail::outerr(path.get_file(), path.get_position()) 36 << "Empty path argument" 37 << "std::endl"; 38 ++state.error_count; 39 return path_parameter(path_text, path_parameter::invalid); 40 } 41 42 try { 43 if (check_glob(path_text)) { 44 return path_parameter(path_text, path_parameter::glob); 45 } 46 else { 47 return path_parameter( 48 glob_unescape(path_text), path_parameter::path); 49 } 50 } catch (glob_error& e) { 51 detail::outerr(path.get_file(), path.get_position()) 52 << "Invalid path (" << e.what() << "): " << path_text 53 << std::endl; 54 ++state.error_count; 55 return path_parameter(path_text, path_parameter::invalid); 56 } 57 } 58 else { 59 // Paths are encoded for quickbook 1.6+ and also xmlbase 60 // values (technically xmlbase is a 1.6 feature, but that 61 // isn't enforced as it's backwards compatible). 62 // 63 // Counter-intuitively: encoded == plain text here. 64 65 std::string path_text = qbk_version_n >= 106u || path.is_encoded() 66 ? path.get_encoded() 67 : path.get_quickbook().to_s(); 68 69 if (path_text.empty()) { 70 detail::outerr(path.get_file(), path.get_position()) 71 << "Empty path argument" << std::endl; 72 ++state.error_count; 73 return path_parameter(path_text, path_parameter::invalid); 74 } 75 76 // Check for windows paths, an error in quickbook 1.6 77 // In quickbook 1.7 backslash is used as an escape character 78 // for glob characters. 79 if (path_text.find('\\') != std::string::npos) { 80 quickbook::detail::ostream* err; 81 82 if (qbk_version_n >= 106u) { 83 err = &detail::outerr(path.get_file(), path.get_position()); 84 ++state.error_count; 85 } 86 else { 87 err = 88 &detail::outwarn(path.get_file(), path.get_position()); 89 } 90 91 *err << "Path isn't portable: '" << path_text << "'" 92 << std::endl; 93 94 boost::replace(path_text, '\\', '/'); 95 } 96 97 return path_parameter(path_text, path_parameter::path); 98 } 99 } 100 check_xinclude_path(value const & p,quickbook::state & state)101 path_parameter check_xinclude_path(value const& p, quickbook::state& state) 102 { 103 path_parameter parameter = check_path(p, state); 104 105 if (parameter.type == path_parameter::glob) { 106 detail::outerr(p.get_file(), p.get_position()) 107 << "Glob used for xml path." << std::endl; 108 ++state.error_count; 109 parameter.type = path_parameter::invalid; 110 } 111 112 return parameter; 113 } 114 115 // 116 // Search include path 117 // 118 include_search_glob(std::set<quickbook_path> & result,quickbook_path const & location,std::string path,quickbook::state & state)119 void include_search_glob( 120 std::set<quickbook_path>& result, 121 quickbook_path const& location, 122 std::string path, 123 quickbook::state& state) 124 { 125 std::size_t glob_pos = find_glob_char(path); 126 127 if (glob_pos == std::string::npos) { 128 quickbook_path complete_path = location / glob_unescape(path); 129 130 if (fs::exists(complete_path.file_path)) { 131 state.dependencies.add_glob_match(complete_path.file_path); 132 result.insert(complete_path); 133 } 134 return; 135 } 136 137 std::size_t prev = path.rfind('/', glob_pos); 138 std::size_t next = path.find('/', glob_pos); 139 140 std::size_t glob_begin = prev == std::string::npos ? 0 : prev + 1; 141 std::size_t glob_end = next == std::string::npos ? path.size() : next; 142 143 quickbook_path new_location = location; 144 145 if (prev != std::string::npos) { 146 new_location /= glob_unescape(path.substr(0, prev)); 147 } 148 149 if (next != std::string::npos) ++next; 150 151 quickbook::string_view glob( 152 path.data() + glob_begin, glob_end - glob_begin); 153 154 fs::path base_dir = new_location.file_path.empty() 155 ? fs::path(".") 156 : new_location.file_path; 157 if (!fs::is_directory(base_dir)) return; 158 159 // Walk through the dir for matches. 160 for (fs::directory_iterator dir_i(base_dir), dir_e; dir_i != dir_e; 161 ++dir_i) { 162 fs::path f = dir_i->path().filename(); 163 std::string generic_path = detail::path_to_generic(f); 164 165 // Skip if the dir item doesn't match. 166 if (!quickbook::glob(glob, generic_path)) continue; 167 168 // If it's a file we add it to the results. 169 if (next == std::string::npos) { 170 if (fs::is_regular_file(dir_i->status())) { 171 quickbook_path r = new_location / generic_path; 172 state.dependencies.add_glob_match(r.file_path); 173 result.insert(r); 174 } 175 } 176 // If it's a matching dir, we recurse looking for more files. 177 else { 178 if (!fs::is_regular_file(dir_i->status())) { 179 include_search_glob( 180 result, new_location / generic_path, path.substr(next), 181 state); 182 } 183 } 184 } 185 } 186 include_search(path_parameter const & parameter,quickbook::state & state,string_iterator pos)187 std::set<quickbook_path> include_search( 188 path_parameter const& parameter, 189 quickbook::state& state, 190 string_iterator pos) 191 { 192 std::set<quickbook_path> result; 193 194 switch (parameter.type) { 195 case path_parameter::glob: 196 // If the path has some glob match characters 197 // we do a discovery of all the matches.. 198 { 199 fs::path current = state.current_file->path.parent_path(); 200 201 // Search for the current dir accumulating to the result. 202 state.dependencies.add_glob(current / parameter.value); 203 include_search_glob( 204 result, state.current_path.parent_path(), parameter.value, 205 state); 206 207 // Search the include path dirs accumulating to the result. 208 unsigned count = 0; 209 QUICKBOOK_FOR (fs::path dir, include_path) { 210 ++count; 211 state.dependencies.add_glob(dir / parameter.value); 212 include_search_glob( 213 result, quickbook_path(dir, count, fs::path()), 214 parameter.value, state); 215 } 216 217 // Done. 218 return result; 219 } 220 221 case path_parameter::path: { 222 fs::path path = detail::generic_to_path(parameter.value); 223 224 // If the path is relative, try and resolve it. 225 if (!path.has_root_directory() && !path.has_root_name()) { 226 quickbook_path path2 = 227 state.current_path.parent_path() / parameter.value; 228 229 // See if it can be found locally first. 230 if (state.dependencies.add_dependency(path2.file_path)) { 231 result.insert(path2); 232 return result; 233 } 234 235 // Search in each of the include path locations. 236 unsigned count = 0; 237 QUICKBOOK_FOR (fs::path full, include_path) { 238 ++count; 239 full /= path; 240 241 if (state.dependencies.add_dependency(full)) { 242 result.insert(quickbook_path(full, count, path)); 243 return result; 244 } 245 } 246 } 247 else { 248 if (state.dependencies.add_dependency(path)) { 249 result.insert(quickbook_path(path, 0, path)); 250 return result; 251 } 252 } 253 254 detail::outerr(state.current_file, pos) 255 << "Unable to find file: " << parameter.value << std::endl; 256 ++state.error_count; 257 258 return result; 259 } 260 261 case path_parameter::invalid: 262 return result; 263 264 default: 265 assert(0); 266 return result; 267 } 268 } 269 270 // 271 // quickbook_path 272 // 273 swap(quickbook_path & x,quickbook_path & y)274 void swap(quickbook_path& x, quickbook_path& y) 275 { 276 boost::swap(x.file_path, y.file_path); 277 boost::swap(x.include_path_offset, y.include_path_offset); 278 boost::swap(x.abstract_file_path, y.abstract_file_path); 279 } 280 operator <(quickbook_path const & other) const281 bool quickbook_path::operator<(quickbook_path const& other) const 282 { 283 return abstract_file_path != other.abstract_file_path 284 ? abstract_file_path < other.abstract_file_path 285 : include_path_offset != other.include_path_offset 286 ? include_path_offset < other.include_path_offset 287 : file_path < other.file_path; 288 } 289 operator /(quickbook::string_view x) const290 quickbook_path quickbook_path::operator/(quickbook::string_view x) const 291 { 292 return quickbook_path(*this) /= x; 293 } 294 operator /=(quickbook::string_view x)295 quickbook_path& quickbook_path::operator/=(quickbook::string_view x) 296 { 297 fs::path x2 = detail::generic_to_path(x); 298 file_path /= x2; 299 abstract_file_path /= x2; 300 return *this; 301 } 302 parent_path() const303 quickbook_path quickbook_path::parent_path() const 304 { 305 return quickbook_path( 306 file_path.parent_path(), include_path_offset, 307 abstract_file_path.parent_path()); 308 } 309 resolve_xinclude_path(std::string const & x,quickbook::state & state,bool is_file)310 quickbook_path resolve_xinclude_path( 311 std::string const& x, quickbook::state& state, bool is_file) 312 { 313 fs::path path = detail::generic_to_path(x); 314 fs::path full_path = path; 315 316 // If the path is relative 317 if (!path.has_root_directory()) { 318 // Resolve the path from the current file 319 full_path = state.current_file->path.parent_path() / path; 320 321 // Then calculate relative to the current xinclude_base. 322 path = path_difference(state.xinclude_base, full_path, is_file); 323 } 324 325 return quickbook_path(full_path, 0, path); 326 } 327 } 328