]> git.tdb.fi Git - libs/core.git/blob - source/fs/path.cpp
Add move semantics to Variant
[libs/core.git] / source / fs / path.cpp
1 #include <stdexcept>
2 #include <msp/strings/utils.h>
3 #include "path.h"
4 #include "utils.h"
5
6 using namespace std;
7
8 namespace {
9
10 #ifdef _WIN32
11 inline bool is_windows_drive(const string &p)
12 { return (p.size()==2 && ((p[0]>='A' && p[0]<='Z') || (p[0]>='a' && p[0]<='z')) && p[1]==':'); }
13 #endif
14
15 }
16
17 namespace Msp {
18 namespace FS {
19
20 Path::Path(const string &p)
21 {
22         if(p.empty())
23                 return;
24         string::size_type start = 0;
25         while(start<p.size())
26         {
27                 string::size_type slash = p.find_first_of("/\\", start);
28                 if(slash>start || start==0)
29                         add_component(p.substr(start, max<string::size_type>(slash-start, 1U)));
30                 if(slash==string::npos)
31                         break;
32                 start = slash+1;
33         }
34 }
35
36 unsigned Path::size() const
37 {
38         if(path.empty())
39                 return 0;
40         if(path.size()==1 && path[0]==DIRSEP)
41                 return 1;
42
43         return separators.size()+1;
44 }
45
46 bool Path::is_absolute() const
47 {
48 #ifdef _WIN32
49         if(!empty() && is_windows_drive((*this)[0]))
50                 return true;
51 #endif
52         return path[0]==DIRSEP;
53 }
54
55 Path Path::subpath(unsigned start, unsigned count) const
56 {
57         Path result;
58         auto i = begin();
59         for(unsigned j=0; (j<start && i!=end()); ++j)
60                 ++i;
61         for(unsigned j=0; (j<count && i!=end()); ++j)
62         {
63                 result /= *i;
64                 ++i;
65         }
66         return result;
67 }
68
69 Path Path::operator/(const Path &p) const
70 {
71         Path a = *this;
72         a /= p;
73         return a;
74 }
75
76 Path &Path::operator/=(const Path &p)
77 {
78         if(p.is_absolute())
79                 *this = p;
80         else
81         {
82                 for(const string &c: p)
83                         add_component(c);
84         }
85         return *this;
86 }
87
88 void Path::add_component(const string &comp)
89 {
90         if(comp.size()==1 && (comp[0]=='/' || comp[0]=='\\'))
91         {
92                 // Replace the path with the root directory
93 #ifdef _WIN32
94                 string::size_type slash = (separators.empty() ? string::npos : separators.front());
95                 if(is_windows_drive(path.substr(0, slash)))
96                 {
97                         path = path.substr(0, 2);
98                         separators.clear();
99                 }
100                 else
101 #endif
102                 {
103                         path.assign(1, DIRSEP);
104                         separators.clear();
105                         separators.push_back(0);
106                 }
107         }
108 #ifdef _WIN32
109         else if(is_windows_drive(comp))
110         {
111                 path = comp;
112                 separators.clear();
113         }
114 #endif
115         else if(comp=="..")
116         {
117                 if(path.empty() || path==".")
118                         path = comp;
119                 // .. in root directory is a no-op
120                 else if(path.size()==1 && path[0]==DIRSEP)
121                         ;
122 #ifdef _WIN32
123                 else if(is_windows_drive(path))
124                         ;
125 #endif
126                 else
127                 {
128                         string::size_type start = (separators.empty() ? 0 : separators.back()+1);
129                         if(!path.compare(start, string::npos, ".."))
130                         {
131                                 // If the last component already is a .., add another
132                                 separators.push_back(path.size());
133                                 path += DIRSEP;
134                                 path += comp;
135                         }
136                         else if(start==0)
137                         {
138                                 /* Removing the last component of a relative path results in the
139                                 current directory */
140                                 path = ".";
141                         }
142                         else if(start==1)
143                         {
144                                 /* Removing the last component of an absolute path results in the
145                                 root directory */
146                                 path.erase(start, string::npos);
147                         }
148                         else
149                         {
150                                 // Otherwise, erase the last component and its separator
151                                 path.erase(separators.back(), string::npos);
152                                 separators.pop_back();
153                         }
154                 }
155         }
156         else if(comp!="." || path.empty())
157         {
158                 if(comp!="." && path.empty())
159                         path = ".";
160                 if(path.size()>1 || (path.size()==1 && path[0]!=DIRSEP))
161                 {
162                         separators.push_back(path.size());
163                         path += DIRSEP;
164                 }
165                 path += comp;
166         }
167 }
168
169 string Path::operator[](int n) const
170 {
171         if(n>=0)
172         {
173                 for(auto i=begin(); i!=end(); ++i, --n)
174                         if(!n)
175                                 return *i;
176         }
177         else
178         {
179                 for(auto i=end(); i!=begin();)
180                 {
181                         --i;
182                         if(!++n)
183                                 return *i;
184                 }
185         }
186
187         throw invalid_argument("Path::operator[]");
188 }
189
190 bool Path::operator==(const Path &other) const
191 {
192 #ifdef _WIN32
193         return strcasecmp(path, other.path)==0;
194 #else
195         return path==other.path;
196 #endif
197 }
198
199 bool Path::operator<(const Path &other) const
200 {
201 #ifdef _WIN32
202         return strcasecmp(path, other.path)<0;
203 #else
204         return path<other.path;
205 #endif
206 }
207
208 bool Path::operator>(const Path &other) const
209 {
210 #ifdef _WIN32
211         return strcasecmp(path, other.path)>0;
212 #else
213         return path>other.path;
214 #endif
215 }
216
217
218 Path::Iterator::Iterator(const Path &p, bool e):
219         path(&p),
220         iter(e ? path->separators.end() : path->separators.begin()),
221         end(e || path->path.empty())
222 {
223         update();
224 }
225
226 Path::Iterator &Path::Iterator::operator++()
227 {
228         if(iter==path->separators.end())
229                 end = true;
230         else
231         {
232                 ++iter;
233                 if(path->path.size()==1 && path->separators.size()==1)
234                         end = true;
235         }
236         update();
237         return *this;
238 }
239
240 Path::Iterator &Path::Iterator::operator--()
241 {
242         if(end)
243         {
244                 end = false;
245                 if(path->path.size()==1 && path->separators.size()==1)
246                         --iter;
247         }
248         else if(iter!=path->separators.begin())
249                 --iter;
250         update();
251         return *this;
252 }
253
254 void Path::Iterator::update()
255 {
256         if(end)
257         {
258                 current.clear();
259                 return;
260         }
261
262         string::size_type start = 0;
263         if(iter!=path->separators.begin())
264                 start = *prev(iter)+1;
265
266         string::size_type slash = string::npos;
267         if(iter!=path->separators.end())
268                 slash = *iter;
269
270         if(slash==0)
271                 current = path->path.substr(start, 1);
272         else
273                 current = path->path.substr(start, slash-start);
274 }
275
276 } // namespace FS
277 } // namespace Msp