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 "include_paths.hpp"
14 #include <boost/filesystem.hpp>
15 #include <boost/range/algorithm/replace.hpp>
19 #include "quickbook.hpp" // For the include_path global (yuck)
30 path_parameter
check_path(value
const& path
, quickbook::state
& state
)
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"
39 return path_parameter(path_text
, path_parameter::invalid
);
43 if (check_glob(path_text
)) {
44 return path_parameter(path_text
, path_parameter::glob
);
47 return path_parameter(
48 glob_unescape(path_text
), path_parameter::path
);
50 } catch (glob_error
& e
) {
51 detail::outerr(path
.get_file(), path
.get_position())
52 << "Invalid path (" << e
.what() << "): " << path_text
55 return path_parameter(path_text
, path_parameter::invalid
);
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).
63 // Counter-intuitively: encoded == plain text here.
65 std::string path_text
= qbk_version_n
>= 106u || path
.is_encoded()
67 : path
.get_quickbook().to_s();
69 if (path_text
.empty()) {
70 detail::outerr(path
.get_file(), path
.get_position())
71 << "Empty path argument" << std::endl
;
73 return path_parameter(path_text
, path_parameter::invalid
);
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
;
82 if (qbk_version_n
>= 106u) {
83 err
= &detail::outerr(path
.get_file(), path
.get_position());
88 &detail::outwarn(path
.get_file(), path
.get_position());
91 *err
<< "Path isn't portable: '" << path_text
<< "'"
94 boost::replace(path_text
, '\\', '/');
97 return path_parameter(path_text
, path_parameter::path
);
101 path_parameter
check_xinclude_path(value
const& p
, quickbook::state
& state
)
103 path_parameter parameter
= check_path(p
, state
);
105 if (parameter
.type
== path_parameter::glob
) {
106 detail::outerr(p
.get_file(), p
.get_position())
107 << "Glob used for xml path." << std::endl
;
109 parameter
.type
= path_parameter::invalid
;
116 // Search include path
119 void include_search_glob(
120 std::set
<quickbook_path
>& result
,
121 quickbook_path
const& location
,
123 quickbook::state
& state
)
125 std::size_t glob_pos
= find_glob_char(path
);
127 if (glob_pos
== std::string::npos
) {
128 quickbook_path complete_path
= location
/ glob_unescape(path
);
130 if (fs::exists(complete_path
.file_path
)) {
131 state
.dependencies
.add_glob_match(complete_path
.file_path
);
132 result
.insert(complete_path
);
137 std::size_t prev
= path
.rfind('/', glob_pos
);
138 std::size_t next
= path
.find('/', glob_pos
);
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
;
143 quickbook_path new_location
= location
;
145 if (prev
!= std::string::npos
) {
146 new_location
/= glob_unescape(path
.substr(0, prev
));
149 if (next
!= std::string::npos
) ++next
;
151 quickbook::string_view
glob(
152 path
.data() + glob_begin
, glob_end
- glob_begin
);
154 fs::path base_dir
= new_location
.file_path
.empty()
156 : new_location
.file_path
;
157 if (!fs::is_directory(base_dir
)) return;
159 // Walk through the dir for matches.
160 for (fs::directory_iterator
dir_i(base_dir
), dir_e
; dir_i
!= dir_e
;
162 fs::path f
= dir_i
->path().filename();
163 std::string generic_path
= detail::path_to_generic(f
);
165 // Skip if the dir item doesn't match.
166 if (!quickbook::glob(glob
, generic_path
)) continue;
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
);
176 // If it's a matching dir, we recurse looking for more files.
178 if (!fs::is_regular_file(dir_i
->status())) {
180 result
, new_location
/ generic_path
, path
.substr(next
),
187 std::set
<quickbook_path
> include_search(
188 path_parameter
const& parameter
,
189 quickbook::state
& state
,
192 std::set
<quickbook_path
> result
;
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..
199 fs::path current
= state
.current_file
->path
.parent_path();
201 // Search for the current dir accumulating to the result.
202 state
.dependencies
.add_glob(current
/ parameter
.value
);
204 result
, state
.current_path
.parent_path(), parameter
.value
,
207 // Search the include path dirs accumulating to the result.
209 QUICKBOOK_FOR (fs::path dir
, include_path
) {
211 state
.dependencies
.add_glob(dir
/ parameter
.value
);
213 result
, quickbook_path(dir
, count
, fs::path()),
214 parameter
.value
, state
);
221 case path_parameter::path
: {
222 fs::path path
= detail::generic_to_path(parameter
.value
);
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
;
229 // See if it can be found locally first.
230 if (state
.dependencies
.add_dependency(path2
.file_path
)) {
231 result
.insert(path2
);
235 // Search in each of the include path locations.
237 QUICKBOOK_FOR (fs::path full
, include_path
) {
241 if (state
.dependencies
.add_dependency(full
)) {
242 result
.insert(quickbook_path(full
, count
, path
));
248 if (state
.dependencies
.add_dependency(path
)) {
249 result
.insert(quickbook_path(path
, 0, path
));
254 detail::outerr(state
.current_file
, pos
)
255 << "Unable to find file: " << parameter
.value
<< std::endl
;
261 case path_parameter::invalid
:
274 void swap(quickbook_path
& x
, quickbook_path
& y
)
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
);
281 bool quickbook_path::operator<(quickbook_path
const& other
) const
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
;
290 quickbook_path
quickbook_path::operator/(quickbook::string_view x
) const
292 return quickbook_path(*this) /= x
;
295 quickbook_path
& quickbook_path::operator/=(quickbook::string_view x
)
297 fs::path x2
= detail::generic_to_path(x
);
299 abstract_file_path
/= x2
;
303 quickbook_path
quickbook_path::parent_path() const
305 return quickbook_path(
306 file_path
.parent_path(), include_path_offset
,
307 abstract_file_path
.parent_path());
310 quickbook_path
resolve_xinclude_path(
311 std::string
const& x
, quickbook::state
& state
, bool is_file
)
313 fs::path path
= detail::generic_to_path(x
);
314 fs::path full_path
= path
;
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
;
321 // Then calculate relative to the current xinclude_base.
322 path
= path_difference(state
.xinclude_base
, full_path
, is_file
);
325 return quickbook_path(full_path
, 0, path
);