/
path.h
432 lines (371 loc) · 12.5 KB
/
path.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/** @file path.h Textual path composed of segments.
*
* @author Copyright © 2010-2012 Daniel Swanson <danij@dengine.net>
* @author Copyright © 2010-2012 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* GPL: http://www.gnu.org/licenses/gpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. This program is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details. You should have received a copy of the GNU
* General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/
#ifndef LIBDENG2_PATH_H
#define LIBDENG2_PATH_H
#include <algorithm> // std::swap
#include "../libdeng2.h"
#include "../Error"
#include "../Log"
#include "../ISerializable"
#include "../String"
namespace de {
/**
* A textual path composed of segments. @ingroup data
*
* A path is a case insensitive text string that is broken down into segments.
* Path is a generic class and as such does not provide any interpretation of
* what the path refers to; it just parses the string and splits it into
* segments. The user may choose any character to act as the segment separator.
*
* Paths are used when identifying and organizing structured data. One
* practical example is file system paths.
*
* Internally, the class avoids duplicating the provided path String (i.e.,
* string is not altered), instead relying on the implicit sharing of QString.
*/
class DENG2_PUBLIC Path : public ISerializable, public LogEntry::Arg::Base
{
struct Instance; // needs to be friended by Path::Segment
public:
/// Segment index was out of bounds. @ingroup errors
DENG2_ERROR(OutOfBoundsError);
/// Type used to represent a path segment hash key.
typedef ushort hash_type;
/// Range of a path segment hash key; [0..hash_range)
static hash_type const hash_range;
/**
* Marks a segment in the path. Makes no copy of the segments in the path,
* only stores the location within the path where they begin and end.
*
* Examples:
* - Empty path (as produced by the default constructor) => one empty segment ""
* - Unix-style root directory "/" => two empty segments "", ""
* - Windows-style root directory "c:/" => "c:", ""
* - relative path "some/dir/file.ext" => "some", "dir", file.ext"
* - Unix-style absolute path "/some/file.ext" => "", "some", file.ext"
*
* @see URI paths are composed of "segments":
* http://tools.ietf.org/html/rfc3986#section-3.3
*/
struct DENG2_PUBLIC Segment
{
/**
* Segments are implicitly converted to text strings.
*/
operator String () const;
/**
* Converts the segment to a string.
*/
String toString() const;
/**
* Determines the length of the segment in characters.
* Same as size().
*/
int length() const;
/**
* Determines the length of the segment in characters.
* Same as length().
*/
dsize size() const;
/**
* Returns a somewhat-random number in the range [0..Path::hash_range)
* generated from the segment.
*
* @return The generated hash key.
*/
hash_type hash() const;
/**
* Case and separator insensitive equality test.
*
* Examples:
* - "hello/world" (sep: /) == "HELLO/World" (sep: /)
* - "hello/world" (sep: /) == "Hello|World" (sep: |)
*
* @param other Other segment.
*
* @return @c true, iff the segments are equal.
*/
bool operator == (Segment const &other) const;
bool operator != (Segment const &other) const {
return !(*this == other);
}
/**
* Returns @c true if this segment is lexically less than @a other.
* The test is case and separator insensitive.
*/
bool operator < (Segment const &other) const;
friend class Path;
friend struct Path::Instance;
private:
mutable bool gotHashKey;
mutable hash_type hashKey;
QStringRef range;
};
public:
/**
* Construct an empty Path instance.
*/
Path();
/**
* Construct a path from @a path.
*
* @param path Path to be parsed. The supplied string is used as-is
* (implicit sharing): all white space is included in the path.
* @param sep Character used to separate path segments in @a path.
*/
Path(String const &path, QChar sep = '/');
/**
* Construct a path from @a str with '/' as the segment separator.
*
* @param str String.
*/
Path(QString const &str);
/**
* Construct a path from a UTF-8 C-style string.
*
* @param nullTerminatedCStr Path to be parsed. All white space is included in the path.
* @param sep Character used to separate path segments.
*/
Path(char const *nullTerminatedCStr, char sep);
/**
* Construct a path from a UTF-8 C-style string that uses '/' separators.
*
* @param nullTerminatedCStr Path to be parsed. All white space is included in the path.
*/
Path(char const *nullTerminatedCStr);
/**
* Construct a path by duplicating @a other.
*/
Path(Path const &other);
virtual ~Path();
inline Path &operator = (Path other) {
std::swap(d, other.d);
return *this;
}
/**
* Append a string.
*
* @param str String.
*
* @return Path with @a str added to the end.
*
* @note This is a plain string append, not a path concatenation: use the /
* operator for concatenating paths in a way that takes care of separators
* and path relativity.
*/
Path operator + (QString const &str) const;
/**
* @copydoc operator+
*/
Path operator + (char const *str) const;
/**
* Swaps this Path with @a other.
* @param other Path.
*/
inline void swap(Path &other) {
std::swap(d, other.d);
}
/**
* Determines if this path is equal to @a other. The test is case
* and separator insensitive.
*
* @param other Path.
*
* @return @c true, iff the paths are equal.
*/
bool operator == (Path const &other) const;
/**
* Determines if this path is not equal to @a other. The test is case
* and separator insensitive.
*
* @param other Path.
*
* @return @c true, iff the paths are not equal.
*/
bool operator != (Path const &other) const {
return !(*this == other);
}
/**
* Returns @c true if this path is lexically less than @a other. The test
* is case and separator insensitive.
*/
bool operator < (Path const &other) const;
/**
* Concatenate paths together. This path's separator will be used for
* the resulting path. @see String::concatenatePath()
*
* @param other Path to concatenate after this one.
*
* @return Concatenated path.
*/
Path operator / (Path const &other) const;
/**
* Concatenate paths together. This path's separator will be used for
* the resulting path. @see String::concatenatePath()
*
* @param other Path to concatenate after this one. '/' is used as
* the separator.
*
* @return Concatenated path.
*/
Path operator / (QString other) const;
Path operator / (char const *otherNullTerminatedUtf8) const;
/**
* Convert this path to a text string.
*/
operator String() const {
return toString();
}
/**
* Convert this path to a text string.
*/
String toString() const;
/**
* Returns a reference to the path as a string.
*/
String const &toStringRef() const;
/**
* Returns @c true if the path is empty; otherwise @c false.
*/
bool isEmpty() const;
/// Returns the length of the path.
int length() const;
/// Returns the length of the path.
dsize size() const;
/// Returns the first character of the path.
QChar first() const;
/// Returns the last character of the path.
QChar last() const;
/**
* Clear the path.
*/
Path &clear();
/**
* Assigns a new path with '/' separators.
*
* @param newPath Path where '/' is the segment separator character.
*
* @return This Path.
*/
Path &operator = (String const &newPath);
/**
* Changes the path.
*
* @param newPath New path.
* @param sep Character used to separate path segments in @a path.
*/
Path &set(String const &newPath, QChar sep = '/');
/**
* Returns a copy of the path where all segment separators have been
* replaced with a new character.
*
* @param sep Character used to replace segment separators.
*
* @return Path with new separators.
*/
Path withSeparators(QChar sep = '/') const;
/**
* Returns the character used as segment separator.
*/
QChar separator() const;
/**
* Returns the file name portion of the path, i.e., the last segment.
*/
String fileName() const;
Block toUtf8() const;
/**
* Retrieve a reference to the segment at @a index. In this method the
* segments are indexed left to right, in the same order as they appear in
* the original textual path. There is always at least one segment (index
* 0, the first segment).
*
* @note The zero-length name in UNIX-style absolute paths is also treated
* as a segment. For example, the path "/Users/username" has three
* segments ("", "Users", "username").
*
* @param index Index of the segment to look up. All paths have
* at least one segment (even empty ones) thus index @c 0 will
* always be valid.
*
* @return Referenced segment. Do not keep the returned reference after
* making a change to the path.
*/
Segment const &segment(int index) const;
/**
* Retrieve a reference to the segment at @a reverseIndex. In this method
* the segments are indexed in reverse order (right to left). There is
* always at least one segment (index 0, the last segment).
*
* For example, if the path is "c:/mystuff/myaddon.addon" the corresponding
* segment map is arranged as follows:
* <pre>
* [0:{myaddon.addon}, 1:{mystuff}, 2:{c:}].
* </pre>
*
* @note The zero-length name in UNIX-style absolute paths is also treated
* as a segment. For example, the path "/Users/username" has three
* segments ("username", "Users", "").
*
* @param reverseIndex Reverse-index of the segment to look up. All paths have
* at least one segment (even empty ones) thus index @c 0 will
* always be valid.
*
* @return Referenced segment. Do not keep the returned reference after
* making a change to the path.
*/
Segment const &reverseSegment(int reverseIndex) const;
/**
* @return Total number of segments in the segment map. There is always
* at least one segment.
*/
int segmentCount() const;
/**
* @return First (i.e., left-most) segment in the path. If the path is
* empty, the returned segment is an empty, zero-length segment.
*/
inline Segment const &firstSegment() const {
return segment(0);
}
/**
* @return Last (i.e., right-most) segment in the path. If the path is empty,
* the returned segment is an empty, zero-length segment.
*/
inline Segment const &lastSegment() const {
return segment(segmentCount() - 1);
}
// Implements LogEntry::Arg::Base.
LogEntry::Arg::Type logEntryArgType() const { return LogEntry::Arg::STRING; }
String asText() const {
return toString();
}
// Implements ISerializable.
void operator >> (Writer &to) const;
void operator << (Reader &from);
private:
Instance *d;
};
} // namespace de
namespace std {
// std::swap specialization for de::Path
template <>
inline void swap<de::Path>(de::Path &a, de::Path &b) {
a.swap(b);
}
}
#endif // LIBDENG2_PATH_H