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 
35 namespace fs = utils::fs;
36 
37 
38 namespace {
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.
51 static std::string
normalize(const std::string & in)52 normalize(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.
path(const std::string & text)90 fs::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 const char*
c_str(void) const98 fs::path::c_str(void) const
99 {
100     return _repr.c_str();
101 }
102 
103 
104 /// Gets a view of the path as a std::string.
105 const std::string&
str(void) const106 fs::path::str(void) const
107 {
108     return _repr;
109 }
110 
111 
112 /// Gets the branch path (directory name) of the path.
113 ///
114 /// The branch path of a path with just one component (no separators) is ".".
115 ///
116 /// \return A new path representing the branch path.
117 fs::path
branch_path(void) const118 fs::path::branch_path(void) const
119 {
120     const std::string::size_type end_pos = _repr.rfind('/');
121     if (end_pos == std::string::npos)
122         return fs::path(".");
123     else if (end_pos == 0)
124         return fs::path("/");
125     else
126         return fs::path(_repr.substr(0, end_pos));
127 }
128 
129 
130 /// Gets the leaf name (base name) of the path.
131 ///
132 /// \return A new string representing the leaf name.
133 std::string
leaf_name(void) const134 fs::path::leaf_name(void) const
135 {
136     const std::string::size_type beg_pos = _repr.rfind('/');
137 
138     if (beg_pos == std::string::npos)
139         return _repr;
140     else
141         return _repr.substr(beg_pos + 1);
142 }
143 
144 
145 /// Converts a relative path in the current directory to an absolute path.
146 ///
147 /// \pre The path is relative.
148 ///
149 /// \return The absolute representation of the relative path.
150 fs::path
to_absolute(void) const151 fs::path::to_absolute(void) const
152 {
153     PRE(!is_absolute());
154     return fs::current_path() / *this;
155 }
156 
157 
158 /// Checks whether the path is absolute.
159 bool
is_absolute(void) const160 fs::path::is_absolute(void) const
161 {
162     return _repr[0] == '/';
163 }
164 
165 
166 /// Checks whether the path is a parent of another path.
167 ///
168 /// A path is considered to be a parent of itself.
169 ///
170 /// \return True if this path is a parent of p.
171 bool
is_parent_of(path p) const172 fs::path::is_parent_of(path p) const
173 {
174     do {
175         if ((*this) == p)
176             return true;
177         p = p.branch_path();
178     } while (p != fs::path(".") && p != fs::path("/"));
179     return false;
180 }
181 
182 
183 /// Counts the number of components in the path.
184 ///
185 /// \return The number of components.
186 int
ncomponents(void) const187 fs::path::ncomponents(void) const
188 {
189     int count = 0;
190     if (_repr == "/")
191         return 1;
192     else {
193         for (std::string::const_iterator iter = _repr.begin();
194              iter != _repr.end(); ++iter) {
195             if (*iter == '/')
196                 count++;
197         }
198         return count + 1;
199     }
200 }
201 
202 
203 /// Less-than comparator for paths.
204 ///
205 /// This is provided to make identifiers useful as map keys.
206 ///
207 /// \param p The path to compare to.
208 ///
209 /// \return True if this identifier sorts before the other identifier; false
210 ///     otherwise.
211 bool
operator <(const fs::path & p) const212 fs::path::operator<(const fs::path& p) const
213 {
214     return _repr < p._repr;
215 }
216 
217 
218 /// Compares two paths for equality.
219 ///
220 /// Given that the paths are internally normalized, input paths such as
221 /// ///foo/bar and /foo///bar are exactly the same.  However, this does NOT
222 /// check for true equality: i.e. this does not access the file system to check
223 /// if the paths actually point to the same object my means of links.
224 ///
225 /// \param p The path to compare to.
226 ///
227 /// \returns A boolean indicating whether the paths are equal.
228 bool
operator ==(const fs::path & p) const229 fs::path::operator==(const fs::path& p) const
230 {
231     return _repr == p._repr;
232 }
233 
234 
235 /// Compares two paths for inequality.
236 ///
237 /// See the description of operator==() for more details on the comparison
238 /// performed.
239 ///
240 /// \param p The path to compare to.
241 ///
242 /// \returns A boolean indicating whether the paths are different.
243 bool
operator !=(const fs::path & p) const244 fs::path::operator!=(const fs::path& p) const
245 {
246     return _repr != p._repr;
247 }
248 
249 
250 /// Concatenates this path with one or more components.
251 ///
252 /// \param components The new components to concatenate to the path.  These are
253 ///     normalized because, in general, they may come from user input.  These
254 ///     components cannot represent an absolute path.
255 ///
256 /// \return A new path containing the concatenation of this path and the
257 ///     provided components.
258 ///
259 /// \throw utils::fs::invalid_path_error If components does not represent a
260 ///     valid path.
261 /// \throw utils::fs::join_error If the join operation is invalid because the
262 ///     two paths are incompatible.
263 fs::path
operator /(const std::string & components) const264 fs::path::operator/(const std::string& components) const
265 {
266     return (*this) / fs::path(components);
267 }
268 
269 
270 /// Concatenates this path with another path.
271 ///
272 /// \param rest The path to concatenate to this one.  Cannot be absolute.
273 ///
274 /// \return A new path containing the concatenation of this path and the other
275 ///     path.
276 ///
277 /// \throw utils::fs::join_error If the join operation is invalid because the
278 ///     two paths are incompatible.
279 fs::path
operator /(const fs::path & rest) const280 fs::path::operator/(const fs::path& rest) const
281 {
282     if (rest.is_absolute())
283         throw fs::join_error(_repr, rest._repr,
284                              "Cannot concatenate a path to an absolute path");
285     return fs::path(_repr + '/' + rest._repr);
286 }
287 
288 
289 /// Formats a path for insertion on a stream.
290 ///
291 /// \param os The output stream.
292 /// \param p The path to inject to the stream.
293 ///
294 /// \return The output stream os.
295 std::ostream&
operator <<(std::ostream & os,const fs::path & p)296 fs::operator<<(std::ostream& os, const fs::path& p)
297 {
298     return (os << p.str());
299 }
300