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