Imported Upstream version 1.64.0
[platform/upstream/boost.git] / tools / quickbook / src / include_paths.cpp
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 "native_text.hpp"
13 #include "glob.hpp"
14 #include "include_paths.hpp"
15 #include "state.hpp"
16 #include "utils.hpp"
17 #include "quickbook.hpp" // For the include_path global (yuck)
18 #include <boost/foreach.hpp>
19 #include <boost/range/algorithm/replace.hpp>
20 #include <boost/filesystem/operations.hpp>
21 #include <cassert>
22
23 namespace quickbook
24 {
25     //
26     // check_path
27     //
28
29     path_parameter check_path(value const& path, quickbook::state& state)
30     {
31         if (qbk_version_n >= 107u) {
32             std::string path_text = path.get_encoded();
33             if (path_text.empty())
34             {
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(glob_unescape(path_text),
48                             path_parameter::path);
49                 }
50             } catch(glob_error& e) {
51                 detail::outerr(path.get_file(), path.get_position())
52                     << "Invalid path (" << e.what() << "): "
53                     << path_text
54                     << std::endl;
55                 ++state.error_count;
56                 return path_parameter(path_text, path_parameter::invalid);
57             }
58         }
59         else {
60             // Paths are encoded for quickbook 1.6+ and also xmlbase
61             // values (technically xmlbase is a 1.6 feature, but that
62             // isn't enforced as it's backwards compatible).
63             //
64             // Counter-intuitively: encoded == plain text here.
65
66             std::string path_text = qbk_version_n >= 106u || path.is_encoded() ?
67                     path.get_encoded() : detail::to_s(path.get_quickbook());
68
69             if (path_text.empty())
70             {
71                 detail::outerr(path.get_file(), path.get_position())
72                     << "Empty path argument"
73                     << std::endl;
74                 ++state.error_count;
75                 return path_parameter(path_text, path_parameter::invalid);
76             }
77
78             // Check for windows paths, an error in quickbook 1.6
79             // In quickbook 1.7 backslash is used as an escape character
80             // for glob characters.
81             if (path_text.find('\\') != std::string::npos)
82             {
83                 quickbook::detail::ostream* err;
84
85                 if (qbk_version_n >= 106u) {
86                     err = &detail::outerr(path.get_file(), path.get_position());
87                     ++state.error_count;
88                 }
89                 else {
90                     err = &detail::outwarn(path.get_file(), path.get_position());
91                 }
92
93                 *err << "Path isn't portable: '"
94                     << path_text
95                     << "'"
96                     << std::endl;
97
98                 boost::replace(path_text, '\\', '/');
99             }
100
101             return path_parameter(path_text, path_parameter::path);
102         }
103     }
104
105     path_parameter check_xinclude_path(value const& p, quickbook::state& state)
106     {
107         path_parameter parameter = check_path(p, state);
108
109         if (parameter.type == path_parameter::glob) {
110             detail::outerr(p.get_file(), p.get_position())
111                 << "Glob used for xml path."
112                 << std::endl;
113             ++state.error_count;
114             parameter.type = path_parameter::invalid;
115         }
116
117         return parameter;
118     }
119
120     //
121     // Search include path
122     //
123
124     void include_search_glob(std::set<quickbook_path> & result,
125         quickbook_path const& location,
126         std::string path, quickbook::state& state)
127     {
128         std::size_t glob_pos = find_glob_char(path);
129
130         if (glob_pos == std::string::npos)
131         {
132             quickbook_path complete_path = location / glob_unescape(path);
133
134             if (fs::exists(complete_path.file_path))
135             {
136                 state.dependencies.add_glob_match(complete_path.file_path);
137                 result.insert(complete_path);
138             }
139             return;
140         }
141
142         std::size_t prev = path.rfind('/', glob_pos);
143         std::size_t next = path.find('/', glob_pos);
144
145         std::size_t glob_begin = prev == std::string::npos ? 0 : prev + 1;
146         std::size_t glob_end = next == std::string::npos ? path.size() : next;
147
148         quickbook_path new_location = location;
149
150         if (prev != std::string::npos) {
151             new_location /= glob_unescape(path.substr(0, prev));
152         }
153
154         if (next != std::string::npos) ++next;
155
156         quickbook::string_view glob(
157                 path.data() + glob_begin,
158                 glob_end - glob_begin);
159
160         fs::path base_dir = new_location.file_path.empty() ?
161             fs::path(".") : new_location.file_path;
162         if (!fs::is_directory(base_dir)) return;
163
164         // Walk through the dir for matches.
165         for (fs::directory_iterator dir_i(base_dir), dir_e;
166                 dir_i != dir_e; ++dir_i)
167         {
168             fs::path f = dir_i->path().filename();
169             std::string generic_path = detail::path_to_generic(f);
170
171             // Skip if the dir item doesn't match.
172             if (!quickbook::glob(glob, generic_path)) continue;
173
174             // If it's a file we add it to the results.
175             if (next == std::string::npos)
176             {
177                 if (fs::is_regular_file(dir_i->status()))
178                 {
179                     quickbook_path r = new_location / generic_path;
180                     state.dependencies.add_glob_match(r.file_path);
181                     result.insert(r);
182                 }
183             }
184             // If it's a matching dir, we recurse looking for more files.
185             else
186             {
187                 if (!fs::is_regular_file(dir_i->status()))
188                 {
189                     include_search_glob(result, new_location / generic_path,
190                             path.substr(next), state);
191                 }
192             }
193         }
194     }
195
196     std::set<quickbook_path> include_search(path_parameter const& parameter,
197             quickbook::state& state, string_iterator pos)
198     {
199         std::set<quickbook_path> result;
200
201         switch (parameter.type) {
202             case path_parameter::glob:
203             // If the path has some glob match characters
204             // we do a discovery of all the matches..
205             {
206                 fs::path current = state.current_file->path.parent_path();
207
208                 // Search for the current dir accumulating to the result.
209                 state.dependencies.add_glob(current / parameter.value);
210                 include_search_glob(result, state.current_path.parent_path(),
211                         parameter.value, state);
212
213                 // Search the include path dirs accumulating to the result.
214                 unsigned count = 0;
215                 BOOST_FOREACH(fs::path dir, include_path)
216                 {
217                     ++count;
218                     state.dependencies.add_glob(dir / parameter.value);
219                     include_search_glob(result,
220                             quickbook_path(dir, count, fs::path()),
221                             parameter.value, state);
222                 }
223
224                 // Done.
225                 return result;
226             }
227
228             case path_parameter::path:
229             {
230                 fs::path path = detail::generic_to_path(parameter.value);
231
232                 // If the path is relative, try and resolve it.
233                 if (!path.has_root_directory() && !path.has_root_name())
234                 {
235                     quickbook_path path2 =
236                         state.current_path.parent_path() / parameter.value;
237
238                     // See if it can be found locally first.
239                     if (state.dependencies.add_dependency(path2.file_path))
240                     {
241                         result.insert(path2);
242                         return result;
243                     }
244
245                     // Search in each of the include path locations.
246                     unsigned count = 0;
247                     BOOST_FOREACH(fs::path full, include_path)
248                     {
249                         ++count;
250                         full /= path;
251
252                         if (state.dependencies.add_dependency(full))
253                         {
254                             result.insert(quickbook_path(full, count, path));
255                             return result;
256                         }
257                     }
258                 }
259                 else
260                 {
261                     if (state.dependencies.add_dependency(path)) {
262                         result.insert(quickbook_path(path, 0, path));
263                         return result;
264                     }
265                 }
266
267                 detail::outerr(state.current_file, pos)
268                     << "Unable to find file: "
269                     << parameter.value
270                     << std::endl;
271                 ++state.error_count;
272
273                 return result;
274             }
275
276             case path_parameter::invalid:
277                 return result;
278
279             default:
280                 assert(0);
281                 return result;
282         }
283     }
284
285     //
286     // quickbook_path
287     //
288
289     void swap(quickbook_path& x, quickbook_path& y) {
290         boost::swap(x.file_path, y.file_path);
291         boost::swap(x.include_path_offset, y.include_path_offset);
292         boost::swap(x.abstract_file_path, y.abstract_file_path);
293     }
294
295     bool quickbook_path::operator<(quickbook_path const& other) const
296     {
297         // TODO: Is comparing file_path redundant? Surely if quickbook_path
298         // and abstract_file_path are equal, it must also be.
299         // (but not vice-versa)
300         return
301             abstract_file_path != other.abstract_file_path ?
302                 abstract_file_path < other.abstract_file_path :
303             include_path_offset != other.include_path_offset ?
304                 include_path_offset < other.include_path_offset :
305                 file_path < other.file_path;
306     }
307
308     quickbook_path quickbook_path::operator/(quickbook::string_view x) const
309     {
310         return quickbook_path(*this) /= x;
311     }
312
313     quickbook_path& quickbook_path::operator/=(quickbook::string_view x)
314     {
315         fs::path x2 = detail::generic_to_path(x);
316         file_path /= x2;
317         abstract_file_path /= x2;
318         return *this;
319     }
320
321     quickbook_path quickbook_path::parent_path() const
322     {
323         return quickbook_path(file_path.parent_path(), include_path_offset,
324                 abstract_file_path.parent_path());
325     }
326
327     // Not a general purpose normalization function, just
328     // from paths from the root directory. It strips the excess
329     // ".." parts from a path like: "x/../../y", leaving "y".
330     std::vector<fs::path> normalize_path_from_root(fs::path const& path)
331     {
332         assert(!path.has_root_directory() && !path.has_root_name());
333
334         std::vector<fs::path> parts;
335
336         BOOST_FOREACH(fs::path const& part, path)
337         {
338             if (part.empty() || part == ".") {
339             }
340             else if (part == "..") {
341                 if (!parts.empty()) parts.pop_back();
342             }
343             else {
344                 parts.push_back(part);
345             }
346         }
347
348         return parts;
349     }
350
351     // The relative path from base to path
352     fs::path path_difference(fs::path const& base, fs::path const& path)
353     {
354         fs::path
355             absolute_base = fs::absolute(base),
356             absolute_path = fs::absolute(path);
357
358         // Remove '.', '..' and empty parts from the remaining path
359         std::vector<fs::path>
360             base_parts = normalize_path_from_root(absolute_base.relative_path()),
361             path_parts = normalize_path_from_root(absolute_path.relative_path());
362
363         std::vector<fs::path>::iterator
364             base_it = base_parts.begin(),
365             base_end = base_parts.end(),
366             path_it = path_parts.begin(),
367             path_end = path_parts.end();
368
369         // Build up the two paths in these variables, checking for the first
370         // difference.
371         fs::path
372             base_tmp = absolute_base.root_path(),
373             path_tmp = absolute_path.root_path();
374
375         fs::path result;
376
377         // If they have different roots then there's no relative path so
378         // just build an absolute path.
379         if (!fs::equivalent(base_tmp, path_tmp))
380         {
381             result = path_tmp;
382         }
383         else
384         {
385             // Find the point at which the paths differ
386             for(; base_it != base_end && path_it != path_end; ++base_it, ++path_it)
387             {
388                 if(!fs::equivalent(base_tmp /= *base_it, path_tmp /= *path_it))
389                     break;
390             }
391
392             // Build a relative path to that point
393             for(; base_it != base_end; ++base_it) result /= "..";
394         }
395
396         // Build the rest of our path
397         for(; path_it != path_end; ++path_it) result /= *path_it;
398
399         return result;
400     }
401
402     // Convert a Boost.Filesystem path to a URL.
403     //
404     // I'm really not sure about this, as the meaning of root_name and
405     // root_directory are only clear for windows.
406     //
407     // Some info on file URLs at:
408     // https://en.wikipedia.org/wiki/File_URI_scheme
409     std::string file_path_to_url(fs::path const& x)
410     {
411         // TODO: Maybe some kind of error if this doesn't understand the path.
412         // TODO: Might need a special cygwin implementation.
413         // TODO: What if x.has_root_name() && !x.has_root_directory()?
414         // TODO: What does Boost.Filesystem do for '//localhost/c:/path'?
415         //       Is that event allowed by windows?
416
417         if (x.has_root_name()) {
418             std::string root_name = detail::path_to_generic(x.root_name());
419
420             if (root_name.size() > 2 && root_name[0] == '/' && root_name[1] == '/') {
421                 // root_name is a network location.
422                 return "file:" + detail::escape_uri(detail::path_to_generic(x));
423             }
424             else if (root_name.size() >= 2 && root_name[root_name.size() - 1] == ':') {
425                 // root_name is a drive.
426                 return "file:///"
427                     + detail::escape_uri(root_name.substr(0, root_name.size() - 1))
428                     + ":/" // TODO: Or maybe "|/".
429                     + detail::escape_uri(detail::path_to_generic(x.relative_path()));
430             }
431             else {
432                 // Not sure what root_name is.
433                 return detail::escape_uri(detail::path_to_generic(x));
434             }
435         }
436         else if (x.has_root_directory()) {
437             return "file://" + detail::escape_uri(detail::path_to_generic(x));
438         }
439         else {
440             return detail::escape_uri(detail::path_to_generic(x));
441         }
442     }
443
444     std::string dir_path_to_url(fs::path const& x)
445     {
446         return file_path_to_url(x) + "/";
447     }
448
449     quickbook_path resolve_xinclude_path(std::string const& x, quickbook::state& state) {
450         fs::path path = detail::generic_to_path(x);
451         fs::path full_path = path;
452
453         // If the path is relative
454         if (!path.has_root_directory())
455         {
456             // Resolve the path from the current file
457             full_path = state.current_file->path.parent_path() / path;
458
459             // Then calculate relative to the current xinclude_base.
460             path = path_difference(state.xinclude_base, full_path);
461         }
462
463         return quickbook_path(full_path, 0, path);
464     }
465 }