1// Copyright 2010 The Kyua Authors.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9//   notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright
11//   notice, this list of conditions and the following disclaimer in the
12//   documentation and/or other materials provided with the distribution.
13// * Neither the name of Google Inc. nor the names of its contributors
14//   may be used to endorse or promote products derived from this software
15//   without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29#include "utils/fs/path.hpp"
30
31#include "utils/fs/exceptions.hpp"
32#include "utils/fs/operations.hpp"
33#include "utils/sanity.hpp"
34
35namespace fs = utils::fs;
36
37
38namespace {
39
40
41/// Normalizes an input string to a valid path.
42///
43/// A normalized path cannot have empty components; i.e. there can be at most
44/// one consecutive separator (/).
45///
46/// \param in The string to normalize.
47///
48/// \return The normalized string, representing a path.
49///
50/// \throw utils::fs::invalid_path_error If the path is empty.
51static std::string
52normalize(const std::string& in)
53{
54    if (in.empty())
55        throw fs::invalid_path_error(in, "Cannot be empty");
56
57    std::string out;
58
59    std::string::size_type pos = 0;
60    do {
61        const std::string::size_type next_pos = in.find('/', pos);
62
63        const std::string component = in.substr(pos, next_pos - pos);
64        if (!component.empty()) {
65            if (pos == 0)
66                out += component;
67            else if (component != ".")
68                out += "/" + component;
69        }
70
71        if (next_pos == std::string::npos)
72            pos = next_pos;
73        else
74            pos = next_pos + 1;
75    } while (pos != std::string::npos);
76
77    return out.empty() ? "/" : out;
78}
79
80
81}  // anonymous namespace
82
83
84/// Creates a new path object from a textual representation of a path.
85///
86/// \param text A valid representation of a path in textual form.
87///
88/// \throw utils::fs::invalid_path_error If the input text does not represent a
89///     valid path.
90fs::path::path(const std::string& text) :
91    _repr(normalize(text))
92{
93}
94
95
96/// Gets a view of the path as an array of characters.
97///
98/// \return A \code const char* \endcode representation for the object.
99const char*
100fs::path::c_str(void) const
101{
102    return _repr.c_str();
103}
104
105
106/// Gets a view of the path as a std::string.
107///
108/// \return A \code std::string& \endcode representation for the object.
109const std::string&
110fs::path::str(void) const
111{
112    return _repr;
113}
114
115
116/// Gets the branch path (directory name) of the path.
117///
118/// The branch path of a path with just one component (no separators) is ".".
119///
120/// \return A new path representing the branch path.
121fs::path
122fs::path::branch_path(void) const
123{
124    const std::string::size_type end_pos = _repr.rfind('/');
125    if (end_pos == std::string::npos)
126        return fs::path(".");
127    else if (end_pos == 0)
128        return fs::path("/");
129    else
130        return fs::path(_repr.substr(0, end_pos));
131}
132
133
134/// Gets the leaf name (base name) of the path.
135///
136/// \return A new string representing the leaf name.
137std::string
138fs::path::leaf_name(void) const
139{
140    const std::string::size_type beg_pos = _repr.rfind('/');
141
142    if (beg_pos == std::string::npos)
143        return _repr;
144    else
145        return _repr.substr(beg_pos + 1);
146}
147
148
149/// Converts a relative path in the current directory to an absolute path.
150///
151/// \pre The path is relative.
152///
153/// \return The absolute representation of the relative path.
154fs::path
155fs::path::to_absolute(void) const
156{
157    PRE(!is_absolute());
158    return fs::current_path() / *this;
159}
160
161
162/// \return True if the representation of the path is absolute.
163bool
164fs::path::is_absolute(void) const
165{
166    return _repr[0] == '/';
167}
168
169
170/// Checks whether the path is a parent of another path.
171///
172/// A path is considered to be a parent of itself.
173///
174/// \return True if this path is a parent of p.
175bool
176fs::path::is_parent_of(path p) const
177{
178    do {
179        if ((*this) == p)
180            return true;
181        p = p.branch_path();
182    } while (p != fs::path(".") && p != fs::path("/"));
183    return false;
184}
185
186
187/// Counts the number of components in the path.
188///
189/// \return The number of components.
190int
191fs::path::ncomponents(void) const
192{
193    int count = 0;
194    if (_repr == "/")
195        return 1;
196    else {
197        for (std::string::const_iterator iter = _repr.begin();
198             iter != _repr.end(); ++iter) {
199            if (*iter == '/')
200                count++;
201        }
202        return count + 1;
203    }
204}
205
206
207/// Less-than comparator for paths.
208///
209/// This is provided to make identifiers useful as map keys.
210///
211/// \param p The path to compare to.
212///
213/// \return True if this identifier sorts before the other identifier; false
214///     otherwise.
215bool
216fs::path::operator<(const fs::path& p) const
217{
218    return _repr < p._repr;
219}
220
221
222/// Compares two paths for equality.
223///
224/// Given that the paths are internally normalized, input paths such as
225/// ///foo/bar and /foo///bar are exactly the same.  However, this does NOT
226/// check for true equality: i.e. this does not access the file system to check
227/// if the paths actually point to the same object my means of links.
228///
229/// \param p The path to compare to.
230///
231/// \returns A boolean indicating whether the paths are equal.
232bool
233fs::path::operator==(const fs::path& p) const
234{
235    return _repr == p._repr;
236}
237
238
239/// Compares two paths for inequality.
240///
241/// See the description of operator==() for more details on the comparison
242/// performed.
243///
244/// \param p The path to compare to.
245///
246/// \returns A boolean indicating whether the paths are different.
247bool
248fs::path::operator!=(const fs::path& p) const
249{
250    return _repr != p._repr;
251}
252
253
254/// Concatenates this path with one or more components.
255///
256/// \param components The new components to concatenate to the path.  These are
257///     normalized because, in general, they may come from user input.  These
258///     components cannot represent an absolute path.
259///
260/// \return A new path containing the concatenation of this path and the
261///     provided components.
262///
263/// \throw utils::fs::invalid_path_error If components does not represent a
264///     valid path.
265/// \throw utils::fs::join_error If the join operation is invalid because the
266///     two paths are incompatible.
267fs::path
268fs::path::operator/(const std::string& components) const
269{
270    return (*this) / fs::path(components);
271}
272
273
274/// Concatenates this path with another path.
275///
276/// \param rest The path to concatenate to this one.  Cannot be absolute.
277///
278/// \return A new path containing the concatenation of this path and the other
279///     path.
280///
281/// \throw utils::fs::join_error If the join operation is invalid because the
282///     two paths are incompatible.
283fs::path
284fs::path::operator/(const fs::path& rest) const
285{
286    if (rest.is_absolute())
287        throw fs::join_error(_repr, rest._repr,
288                             "Cannot concatenate a path to an absolute path");
289    return fs::path(_repr + '/' + rest._repr);
290}
291
292
293/// Formats a path for insertion on a stream.
294///
295/// \param os The output stream.
296/// \param p The path to inject to the stream.
297///
298/// \return The output stream os.
299std::ostream&
300fs::operator<<(std::ostream& os, const fs::path& p)
301{
302    return (os << p.str());
303}
304