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 =============================================================================*/
14 #include "include_paths.hpp"
18 #include "quickbook.hpp" // For the include_path global (yuck)
19 #include <boost/foreach.hpp>
20 #include <boost/range/algorithm/replace.hpp>
21 #include <boost/filesystem/operations.hpp>
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())
36 detail::outerr(path
.get_file(), path
.get_position())
37 << "Empty path argument"
40 return path_parameter(path_text
, path_parameter::invalid
);
44 if (check_glob(path_text
)) {
45 return path_parameter(path_text
, path_parameter::glob
);
48 return path_parameter(glob_unescape(path_text
),
49 path_parameter::path
);
51 } catch(glob_error
& e
) {
52 detail::outerr(path
.get_file(), path
.get_position())
53 << "Invalid path (" << e
.what() << "): "
57 return path_parameter(path_text
, path_parameter::invalid
);
61 // Paths are encoded for quickbook 1.6+ and also xmlbase
62 // values (technically xmlbase is a 1.6 feature, but that
63 // isn't enforced as it's backwards compatible).
65 // Counter-intuitively: encoded == plain text here.
67 std::string path_text
= qbk_version_n
>= 106u || path
.is_encoded() ?
68 path
.get_encoded() : path
.get_quickbook().to_s();
70 if (path_text
.empty())
72 detail::outerr(path
.get_file(), path
.get_position())
73 << "Empty path argument"
76 return path_parameter(path_text
, path_parameter::invalid
);
79 // Check for windows paths, an error in quickbook 1.6
80 // In quickbook 1.7 backslash is used as an escape character
81 // for glob characters.
82 if (path_text
.find('\\') != std::string::npos
)
84 quickbook::detail::ostream
* err
;
86 if (qbk_version_n
>= 106u) {
87 err
= &detail::outerr(path
.get_file(), path
.get_position());
91 err
= &detail::outwarn(path
.get_file(), path
.get_position());
94 *err
<< "Path isn't portable: '"
99 boost::replace(path_text
, '\\', '/');
102 return path_parameter(path_text
, path_parameter::path
);
106 path_parameter
check_xinclude_path(value
const& p
, quickbook::state
& state
)
108 path_parameter parameter
= check_path(p
, state
);
110 if (parameter
.type
== path_parameter::glob
) {
111 detail::outerr(p
.get_file(), p
.get_position())
112 << "Glob used for xml path."
115 parameter
.type
= path_parameter::invalid
;
122 // Search include path
125 void include_search_glob(std::set
<quickbook_path
> & result
,
126 quickbook_path
const& location
,
127 std::string path
, quickbook::state
& state
)
129 std::size_t glob_pos
= find_glob_char(path
);
131 if (glob_pos
== std::string::npos
)
133 quickbook_path complete_path
= location
/ glob_unescape(path
);
135 if (fs::exists(complete_path
.file_path
))
137 state
.dependencies
.add_glob_match(complete_path
.file_path
);
138 result
.insert(complete_path
);
143 std::size_t prev
= path
.rfind('/', glob_pos
);
144 std::size_t next
= path
.find('/', glob_pos
);
146 std::size_t glob_begin
= prev
== std::string::npos
? 0 : prev
+ 1;
147 std::size_t glob_end
= next
== std::string::npos
? path
.size() : next
;
149 quickbook_path new_location
= location
;
151 if (prev
!= std::string::npos
) {
152 new_location
/= glob_unescape(path
.substr(0, prev
));
155 if (next
!= std::string::npos
) ++next
;
157 quickbook::string_view
glob(
158 path
.data() + glob_begin
,
159 glob_end
- glob_begin
);
161 fs::path base_dir
= new_location
.file_path
.empty() ?
162 fs::path(".") : new_location
.file_path
;
163 if (!fs::is_directory(base_dir
)) return;
165 // Walk through the dir for matches.
166 for (fs::directory_iterator
dir_i(base_dir
), dir_e
;
167 dir_i
!= dir_e
; ++dir_i
)
169 fs::path f
= dir_i
->path().filename();
170 std::string generic_path
= detail::path_to_generic(f
);
172 // Skip if the dir item doesn't match.
173 if (!quickbook::glob(glob
, generic_path
)) continue;
175 // If it's a file we add it to the results.
176 if (next
== std::string::npos
)
178 if (fs::is_regular_file(dir_i
->status()))
180 quickbook_path r
= new_location
/ generic_path
;
181 state
.dependencies
.add_glob_match(r
.file_path
);
185 // If it's a matching dir, we recurse looking for more files.
188 if (!fs::is_regular_file(dir_i
->status()))
190 include_search_glob(result
, new_location
/ generic_path
,
191 path
.substr(next
), state
);
197 std::set
<quickbook_path
> include_search(path_parameter
const& parameter
,
198 quickbook::state
& state
, string_iterator pos
)
200 std::set
<quickbook_path
> result
;
202 switch (parameter
.type
) {
203 case path_parameter::glob
:
204 // If the path has some glob match characters
205 // we do a discovery of all the matches..
207 fs::path current
= state
.current_file
->path
.parent_path();
209 // Search for the current dir accumulating to the result.
210 state
.dependencies
.add_glob(current
/ parameter
.value
);
211 include_search_glob(result
, state
.current_path
.parent_path(),
212 parameter
.value
, state
);
214 // Search the include path dirs accumulating to the result.
216 BOOST_FOREACH(fs::path dir
, include_path
)
219 state
.dependencies
.add_glob(dir
/ parameter
.value
);
220 include_search_glob(result
,
221 quickbook_path(dir
, count
, fs::path()),
222 parameter
.value
, state
);
229 case path_parameter::path
:
231 fs::path path
= detail::generic_to_path(parameter
.value
);
233 // If the path is relative, try and resolve it.
234 if (!path
.has_root_directory() && !path
.has_root_name())
236 quickbook_path path2
=
237 state
.current_path
.parent_path() / parameter
.value
;
239 // See if it can be found locally first.
240 if (state
.dependencies
.add_dependency(path2
.file_path
))
242 result
.insert(path2
);
246 // Search in each of the include path locations.
248 BOOST_FOREACH(fs::path full
, include_path
)
253 if (state
.dependencies
.add_dependency(full
))
255 result
.insert(quickbook_path(full
, count
, path
));
262 if (state
.dependencies
.add_dependency(path
)) {
263 result
.insert(quickbook_path(path
, 0, path
));
268 detail::outerr(state
.current_file
, pos
)
269 << "Unable to find file: "
277 case path_parameter::invalid
:
290 void swap(quickbook_path
& x
, quickbook_path
& y
) {
291 boost::swap(x
.file_path
, y
.file_path
);
292 boost::swap(x
.include_path_offset
, y
.include_path_offset
);
293 boost::swap(x
.abstract_file_path
, y
.abstract_file_path
);
296 bool quickbook_path::operator<(quickbook_path
const& other
) const
299 abstract_file_path
!= other
.abstract_file_path
?
300 abstract_file_path
< other
.abstract_file_path
:
301 include_path_offset
!= other
.include_path_offset
?
302 include_path_offset
< other
.include_path_offset
:
303 file_path
< other
.file_path
;
306 quickbook_path
quickbook_path::operator/(quickbook::string_view x
) const
308 return quickbook_path(*this) /= x
;
311 quickbook_path
& quickbook_path::operator/=(quickbook::string_view x
)
313 fs::path x2
= detail::generic_to_path(x
);
315 abstract_file_path
/= x2
;
319 quickbook_path
quickbook_path::parent_path() const
321 return quickbook_path(file_path
.parent_path(), include_path_offset
,
322 abstract_file_path
.parent_path());
325 quickbook_path
resolve_xinclude_path(std::string
const& x
, quickbook::state
& state
, bool is_file
) {
326 fs::path path
= detail::generic_to_path(x
);
327 fs::path full_path
= path
;
329 // If the path is relative
330 if (!path
.has_root_directory())
332 // Resolve the path from the current file
333 full_path
= state
.current_file
->path
.parent_path() / path
;
335 // Then calculate relative to the current xinclude_base.
336 path
= path_difference(state
.xinclude_base
, full_path
, is_file
);
339 return quickbook_path(full_path
, 0, path
);