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