Imported Upstream version 1.72.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 "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
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
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
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
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
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
281     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
290     quickbook_path quickbook_path::operator/(quickbook::string_view x) const
291     {
292         return quickbook_path(*this) /= x;
293     }
294
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
303     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
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 }