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
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 =============================================================================*/
12 #include "native_text.hpp"
14 #include "include_paths.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>
29 path_parameter
check_path(value
const& path
, quickbook::state
& state
)
31 if (qbk_version_n
>= 107u) {
32 std::string path_text
= path
.get_encoded();
35 if (check_glob(path_text
)) {
36 return path_parameter(path_text
, path_parameter::glob
);
39 return path_parameter(glob_unescape(path_text
),
40 path_parameter::path
);
42 } catch(glob_error
& e
) {
43 detail::outerr(path
.get_file(), path
.get_position())
44 << "Invalid path (" << e
.what() << "): "
48 return path_parameter(path_text
, path_parameter::invalid
);
52 // Paths are encoded for quickbook 1.6+ and also xmlbase
53 // values (technically xmlbase is a 1.6 feature, but that
54 // isn't enforced as it's backwards compatible).
56 // Counter-intuitively: encoded == plain text here.
58 std::string path_text
= qbk_version_n
>= 106u || path
.is_encoded() ?
59 path
.get_encoded() : detail::to_s(path
.get_quickbook());
61 if (path_text
.find('\\') != std::string::npos
)
63 quickbook::detail::ostream
* err
;
65 if (qbk_version_n
>= 106u) {
66 err
= &detail::outerr(path
.get_file(), path
.get_position());
70 err
= &detail::outwarn(path
.get_file(), path
.get_position());
73 *err
<< "Path isn't portable: '"
78 boost::replace(path_text
, '\\', '/');
81 return path_parameter(path_text
, path_parameter::path
);
85 path_parameter
check_xinclude_path(value
const& p
, quickbook::state
& state
)
87 path_parameter parameter
= check_path(p
, state
);
89 if (parameter
.type
== path_parameter::glob
) {
90 detail::outerr(p
.get_file(), p
.get_position())
91 << "Glob used for xml path."
94 parameter
.type
= path_parameter::invalid
;
101 // Search include path
104 void include_search_glob(std::set
<quickbook_path
> & result
,
105 quickbook_path
const& location
,
106 std::string path
, quickbook::state
& state
)
108 std::size_t glob_pos
= find_glob_char(path
);
110 if (glob_pos
== std::string::npos
)
112 quickbook_path complete_path
= location
/ glob_unescape(path
);
114 if (fs::exists(complete_path
.file_path
))
116 state
.dependencies
.add_glob_match(complete_path
.file_path
);
117 result
.insert(complete_path
);
122 std::size_t prev
= path
.rfind('/', glob_pos
);
123 std::size_t next
= path
.find('/', glob_pos
);
125 std::size_t glob_begin
= prev
== std::string::npos
? 0 : prev
+ 1;
126 std::size_t glob_end
= next
== std::string::npos
? path
.size() : next
;
128 quickbook_path new_location
= location
;
130 if (prev
!= std::string::npos
) {
131 new_location
/= glob_unescape(path
.substr(0, prev
));
134 if (next
!= std::string::npos
) ++next
;
136 boost::string_ref
glob(
137 path
.data() + glob_begin
,
138 glob_end
- glob_begin
);
140 fs::path base_dir
= new_location
.file_path
.empty() ?
141 fs::path(".") : new_location
.file_path
;
142 if (!fs::is_directory(base_dir
)) return;
144 // Walk through the dir for matches.
145 for (fs::directory_iterator
dir_i(base_dir
), dir_e
;
146 dir_i
!= dir_e
; ++dir_i
)
148 fs::path f
= dir_i
->path().filename();
149 std::string generic_path
= detail::path_to_generic(f
);
151 // Skip if the dir item doesn't match.
152 if (!quickbook::glob(glob
, generic_path
)) continue;
154 // If it's a file we add it to the results.
155 if (next
== std::string::npos
)
157 if (fs::is_regular_file(dir_i
->status()))
159 quickbook_path r
= new_location
/ generic_path
;
160 state
.dependencies
.add_glob_match(r
.file_path
);
164 // If it's a matching dir, we recurse looking for more files.
167 if (!fs::is_regular_file(dir_i
->status()))
169 include_search_glob(result
, new_location
/ generic_path
,
170 path
.substr(next
), state
);
176 std::set
<quickbook_path
> include_search(path_parameter
const& parameter
,
177 quickbook::state
& state
, string_iterator pos
)
179 std::set
<quickbook_path
> result
;
181 switch (parameter
.type
) {
182 case path_parameter::glob
:
183 // If the path has some glob match characters
184 // we do a discovery of all the matches..
186 fs::path current
= state
.current_file
->path
.parent_path();
188 // Search for the current dir accumulating to the result.
189 state
.dependencies
.add_glob(current
/ parameter
.value
);
190 include_search_glob(result
, state
.current_path
.parent_path(),
191 parameter
.value
, state
);
193 // Search the include path dirs accumulating to the result.
195 BOOST_FOREACH(fs::path dir
, include_path
)
198 state
.dependencies
.add_glob(dir
/ parameter
.value
);
199 include_search_glob(result
,
200 quickbook_path(dir
, count
, fs::path()),
201 parameter
.value
, state
);
208 case path_parameter::path
:
210 fs::path path
= detail::generic_to_path(parameter
.value
);
212 // If the path is relative, try and resolve it.
213 if (!path
.has_root_directory() && !path
.has_root_name())
215 quickbook_path path2
=
216 state
.current_path
.parent_path() / parameter
.value
;
218 // See if it can be found locally first.
219 if (state
.dependencies
.add_dependency(path2
.file_path
))
221 result
.insert(path2
);
225 // Search in each of the include path locations.
227 BOOST_FOREACH(fs::path full
, include_path
)
232 if (state
.dependencies
.add_dependency(full
))
234 result
.insert(quickbook_path(full
, count
, path
));
241 if (state
.dependencies
.add_dependency(path
)) {
242 result
.insert(quickbook_path(path
, 0, path
));
247 detail::outerr(state
.current_file
, pos
)
248 << "Unable to find file: "
256 case path_parameter::invalid
:
269 void swap(quickbook_path
& x
, quickbook_path
& y
) {
270 boost::swap(x
.file_path
, y
.file_path
);
271 boost::swap(x
.include_path_offset
, y
.include_path_offset
);
272 boost::swap(x
.abstract_file_path
, y
.abstract_file_path
);
275 bool quickbook_path::operator<(quickbook_path
const& other
) const
277 // TODO: Is comparing file_path redundant? Surely if quickbook_path
278 // and abstract_file_path are equal, it must also be.
279 // (but not vice-versa)
281 abstract_file_path
!= other
.abstract_file_path
?
282 abstract_file_path
< other
.abstract_file_path
:
283 include_path_offset
!= other
.include_path_offset
?
284 include_path_offset
< other
.include_path_offset
:
285 file_path
< other
.file_path
;
288 quickbook_path
quickbook_path::operator/(boost::string_ref x
) const
290 return quickbook_path(*this) /= x
;
293 quickbook_path
& quickbook_path::operator/=(boost::string_ref x
)
295 fs::path x2
= detail::generic_to_path(x
);
297 abstract_file_path
/= x2
;
301 quickbook_path
quickbook_path::parent_path() const
303 return quickbook_path(file_path
.parent_path(), include_path_offset
,
304 abstract_file_path
.parent_path());
307 // Not a general purpose normalization function, just
308 // from paths from the root directory. It strips the excess
309 // ".." parts from a path like: "x/../../y", leaving "y".
310 std::vector
<fs::path
> normalize_path_from_root(fs::path
const& path
)
312 assert(!path
.has_root_directory() && !path
.has_root_name());
314 std::vector
<fs::path
> parts
;
316 BOOST_FOREACH(fs::path
const& part
, path
)
318 if (part
.empty() || part
== ".") {
320 else if (part
== "..") {
321 if (!parts
.empty()) parts
.pop_back();
324 parts
.push_back(part
);
331 // The relative path from base to path
332 fs::path
path_difference(fs::path
const& base
, fs::path
const& path
)
335 absolute_base
= fs::absolute(base
),
336 absolute_path
= fs::absolute(path
);
338 // Remove '.', '..' and empty parts from the remaining path
339 std::vector
<fs::path
>
340 base_parts
= normalize_path_from_root(absolute_base
.relative_path()),
341 path_parts
= normalize_path_from_root(absolute_path
.relative_path());
343 std::vector
<fs::path
>::iterator
344 base_it
= base_parts
.begin(),
345 base_end
= base_parts
.end(),
346 path_it
= path_parts
.begin(),
347 path_end
= path_parts
.end();
349 // Build up the two paths in these variables, checking for the first
352 base_tmp
= absolute_base
.root_path(),
353 path_tmp
= absolute_path
.root_path();
357 // If they have different roots then there's no relative path so
358 // just build an absolute path.
359 if (!fs::equivalent(base_tmp
, path_tmp
))
365 // Find the point at which the paths differ
366 for(; base_it
!= base_end
&& path_it
!= path_end
; ++base_it
, ++path_it
)
368 if(!fs::equivalent(base_tmp
/= *base_it
, path_tmp
/= *path_it
))
372 // Build a relative path to that point
373 for(; base_it
!= base_end
; ++base_it
) result
/= "..";
376 // Build the rest of our path
377 for(; path_it
!= path_end
; ++path_it
) result
/= *path_it
;
382 // Convert a Boost.Filesystem path to a URL.
384 // I'm really not sure about this, as the meaning of root_name and
385 // root_directory are only clear for windows.
387 // Some info on file URLs at:
388 // https://en.wikipedia.org/wiki/File_URI_scheme
389 std::string
file_path_to_url(fs::path
const& x
)
391 // TODO: Maybe some kind of error if this doesn't understand the path.
392 // TODO: Might need a special cygwin implementation.
393 // TODO: What if x.has_root_name() && !x.has_root_directory()?
394 // TODO: What does Boost.Filesystem do for '//localhost/c:/path'?
395 // Is that event allowed by windows?
397 if (x
.has_root_name()) {
398 std::string root_name
= detail::path_to_generic(x
.root_name());
400 if (root_name
.size() > 2 && root_name
[0] == '/' && root_name
[1] == '/') {
401 // root_name is a network location.
402 return "file:" + detail::escape_uri(detail::path_to_generic(x
));
404 else if (root_name
.size() >= 2 && root_name
[root_name
.size() - 1] == ':') {
405 // root_name is a drive.
407 + detail::escape_uri(root_name
.substr(0, root_name
.size() - 1))
408 + ":/" // TODO: Or maybe "|/".
409 + detail::escape_uri(detail::path_to_generic(x
.relative_path()));
412 // Not sure what root_name is.
413 return detail::escape_uri(detail::path_to_generic(x
));
416 else if (x
.has_root_directory()) {
417 return "file://" + detail::escape_uri(detail::path_to_generic(x
));
420 return detail::escape_uri(detail::path_to_generic(x
));
424 std::string
dir_path_to_url(fs::path
const& x
)
426 return file_path_to_url(x
) + "/";
429 quickbook_path
resolve_xinclude_path(std::string
const& x
, quickbook::state
& state
) {
430 fs::path path
= detail::generic_to_path(x
);
431 fs::path full_path
= path
;
433 // If the path is relative
434 if (!path
.has_root_directory())
436 // Resolve the path from the current file
437 full_path
= state
.current_file
->path
.parent_path() / path
;
439 // Then calculate relative to the current xinclude_base.
440 path
= path_difference(state
.xinclude_base
, full_path
);
443 return quickbook_path(full_path
, 0, path
);