OR-Tools  9.6
path.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include "ortools/base/path.h"
15 
16 #include <cstring>
17 #include <string>
18 
19 #include "absl/strings/str_cat.h"
20 
21 namespace file {
22 
23 // 40% of the time in JoinPath() is from calls with 2 arguments, so we
24 // specialize that case.
25 std::string JoinPath(absl::string_view path1, absl::string_view path2) {
26  if (path1.empty()) return std::string(path2);
27  if (path2.empty()) return std::string(path1);
28  if (path1.back() == '/') {
29  if (path2.front() == '/')
30  return absl::StrCat(path1, absl::ClippedSubstr(path2, 1));
31  } else {
32  if (path2.front() != '/') return absl::StrCat(path1, "/", path2);
33  }
34  return absl::StrCat(path1, path2);
35 }
36 
37 namespace internal {
38 
39 // Given a collection of file paths, append them all together,
40 // ensuring that the proper path separators are inserted between them.
41 std::string JoinPathImpl(bool honor_abs,
42  std::initializer_list<absl::string_view> paths) {
43  std::string result;
44 
45  if (paths.size() != 0) {
46  // This size calculation is worst-case: it assumes one extra "/" for every
47  // path other than the first.
48  size_t total_size = paths.size() - 1;
49  for (const absl::string_view path : paths) total_size += path.size();
50  result.resize(total_size);
51 
52  auto begin = result.begin();
53  auto out = begin;
54  bool trailing_slash = false;
55  for (absl::string_view path : paths) {
56  if (path.empty()) continue;
57  if (path.front() == '/') {
58  if (honor_abs) {
59  out = begin; // wipe out whatever we've built up so far.
60  } else if (trailing_slash) {
61  path.remove_prefix(1);
62  }
63  } else {
64  if (!trailing_slash && out != begin) *out++ = '/';
65  }
66  const size_t this_size = path.size();
67  memcpy(&*out, path.data(), this_size);
68  out += this_size;
69  trailing_slash = out[-1] == '/';
70  }
71  result.erase(out - begin);
72  }
73  return result;
74 }
75 
76 // Return the parts of the basename of path, split on the final ".".
77 // If there is no "." in the basename or "." is the final character in the
78 // basename, the second value will be empty.
79 std::pair<absl::string_view, absl::string_view> SplitBasename(
80  absl::string_view path) {
81  path = Basename(path);
82 
83  size_t pos = path.find_last_of('.');
84  if (pos == absl::string_view::npos)
85  return std::make_pair(path, absl::ClippedSubstr(path, path.size(), 0));
86  return std::make_pair(path.substr(0, pos),
87  absl::ClippedSubstr(path, pos + 1));
88 }
89 
90 } // namespace internal
91 
92 bool IsAbsolutePath(absl::string_view path) {
93  return !path.empty() && path[0] == '/';
94 }
95 
96 std::string AddSlash(absl::string_view path) {
97  size_t length = path.size();
98  if (length && path[length - 1] != '/') {
99  return absl::StrCat(path, "/");
100  } else {
101  return std::string(path);
102  }
103 }
104 
105 absl::string_view Dirname(absl::string_view path) {
106  return SplitPath(path).first;
107 }
108 
109 absl::string_view Basename(absl::string_view path) {
110  return SplitPath(path).second;
111 }
112 
113 std::pair<absl::string_view, absl::string_view> SplitPath(
114  absl::string_view path) {
115  size_t pos = path.find_last_of('/');
116 
117  // Handle the case with no '/' in 'path'.
118  if (pos == absl::string_view::npos)
119  return std::make_pair(path.substr(0, 0), path);
120 
121  // Handle the case with a single leading '/' in 'path'.
122  if (pos == 0)
123  return std::make_pair(path.substr(0, 1), absl::ClippedSubstr(path, 1));
124 
125  return std::make_pair(path.substr(0, pos),
126  absl::ClippedSubstr(path, pos + 1));
127 }
128 
129 absl::string_view Stem(absl::string_view path) {
130  return internal::SplitBasename(path).first;
131 }
132 
133 absl::string_view Extension(absl::string_view path) {
134  return internal::SplitBasename(path).second;
135 }
136 
137 std::string CleanPath(const absl::string_view unclean_path) {
138  std::string path = std::string(unclean_path);
139  const char* src = path.c_str();
140  std::string::iterator dst = path.begin();
141 
142  // Check for absolute path and determine initial backtrack limit.
143  const bool is_absolute_path = *src == '/';
144  if (is_absolute_path) {
145  *dst++ = *src++;
146  while (*src == '/') ++src;
147  }
148  std::string::const_iterator backtrack_limit = dst;
149 
150  // Process all parts
151  while (*src) {
152  bool parsed = false;
153 
154  if (src[0] == '.') {
155  // 1dot ".<whateverisnext>", check for END or SEP.
156  if (src[1] == '/' || !src[1]) {
157  if (*++src) {
158  ++src;
159  }
160  parsed = true;
161  } else if (src[1] == '.' && (src[2] == '/' || !src[2])) {
162  // 2dot END or SEP (".." | "../<whateverisnext>").
163  src += 2;
164  if (dst != backtrack_limit) {
165  // We can backtrack the previous part
166  for (--dst; dst != backtrack_limit && dst[-1] != '/'; --dst) {
167  // Empty.
168  }
169  } else if (!is_absolute_path) {
170  // Failed to backtrack and we can't skip it either. Rewind and copy.
171  src -= 2;
172  *dst++ = *src++;
173  *dst++ = *src++;
174  if (*src) {
175  *dst++ = *src;
176  }
177  // We can never backtrack over a copied "../" part so set new limit.
178  backtrack_limit = dst;
179  }
180  if (*src) {
181  ++src;
182  }
183  parsed = true;
184  }
185  }
186 
187  // If not parsed, copy entire part until the next SEP or EOS.
188  if (!parsed) {
189  while (*src && *src != '/') {
190  *dst++ = *src++;
191  }
192  if (*src) {
193  *dst++ = *src++;
194  }
195  }
196 
197  // Skip consecutive SEP occurrences
198  while (*src == '/') {
199  ++src;
200  }
201  }
202 
203  // Calculate and check the length of the cleaned path.
204  int path_length = dst - path.begin();
205  if (path_length != 0) {
206  // Remove trailing '/' except if it is root path ("/" ==> path_length := 1)
207  if (path_length > 1 && path[path_length - 1] == '/') {
208  --path_length;
209  }
210  path.resize(path_length);
211  } else {
212  // The cleaned path is empty; assign "." as per the spec.
213  path.assign(1, '.');
214  }
215  return path;
216 }
217 
218 std::string CollapseSlashes(absl::string_view path) {
219  std::string ret;
220  ret.reserve(path.size());
221  bool prev_was_slash = false;
222  for (char c : path) {
223  if (c == '/') {
224  if (prev_was_slash) {
225  continue;
226  }
227  prev_was_slash = true;
228  } else {
229  prev_was_slash = false;
230  }
231  ret.push_back(c);
232  }
233  return ret;
234 }
235 
236 } // namespace file
std::string JoinPathImpl(bool honor_abs, std::initializer_list< absl::string_view > paths)
Definition: path.cc:41
std::pair< absl::string_view, absl::string_view > SplitBasename(absl::string_view path)
Definition: path.cc:79
absl::string_view Dirname(absl::string_view path)
Definition: path.cc:105
absl::string_view Stem(absl::string_view path)
Definition: path.cc:129
std::string CleanPath(const absl::string_view unclean_path)
Definition: path.cc:137
std::string AddSlash(absl::string_view path)
Definition: path.cc:96
std::string CollapseSlashes(absl::string_view path)
Definition: path.cc:218
std::pair< absl::string_view, absl::string_view > SplitPath(absl::string_view path)
Definition: path.cc:113
std::string JoinPath(absl::string_view path1, absl::string_view path2)
Definition: path.cc:25
bool IsAbsolutePath(absl::string_view path)
Definition: path.cc:92
absl::string_view Basename(absl::string_view path)
Definition: path.cc:109
absl::string_view Extension(absl::string_view path)
Definition: path.cc:133