Mercurial > projects > ddmd
annotate dmd/FileName.d @ 34:544b922227c7
update to work with dmd 2.048
author | korDen |
---|---|
date | Sat, 21 Aug 2010 05:46:08 +0400 |
parents | 5c9b78899f5d |
children | ccbc1e0bb3f0 |
rev | line source |
---|---|
0 | 1 module dmd.FileName; |
2 | |
3 import dmd.String; | |
4 import dmd.Array; | |
5 import dmd.OutBuffer; | |
6 import dmd.File; | |
7 | |
4 | 8 import core.memory; |
2 | 9 |
5
63623152e82a
Fixed memory corruption bug which was introduced when attempting to restore GC functionality
dkoroskin <>
parents:
4
diff
changeset
|
10 import core.stdc.stdlib : malloc, alloca; |
0 | 11 import core.stdc.string : memcpy, strlen; |
12 import core.stdc.ctype : isspace; | |
13 | |
34 | 14 import std.exception : assumeUnique; |
0 | 15 import std.string : cmp, icmp; |
16 import std.file : mkdirRecurse; | |
17 | |
14
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
18 version (Windows) |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
19 { |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
20 import core.sys.windows.windows; |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
21 } |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
22 |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
23 version (POSIX) |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
24 { |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
25 import core.stdc.stdlib; |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
26 import core.sys.posix.sys.stat; |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
27 import std.conv; |
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
28 } |
0 | 29 |
30 class FileName : String | |
31 { | |
32 this(string str) | |
33 { | |
34 super(str); | |
35 } | |
36 | |
37 this(string path, string name) | |
38 { | |
39 super(combine(path, name)); | |
40 } | |
41 | |
42 hash_t hashCode() | |
43 { | |
44 version (_WIN32) { | |
45 // We need a different hashCode because it must be case-insensitive | |
46 size_t len = str.length; | |
47 hash_t hash = 0; | |
48 ubyte* s = cast(ubyte*)str.ptr; | |
49 | |
50 for (;;) | |
51 { | |
52 switch (len) | |
53 { | |
54 case 0: | |
55 return hash; | |
56 | |
57 case 1: | |
58 hash *= 37; | |
59 hash += *(cast(ubyte*)s) | 0x20; | |
60 return hash; | |
61 | |
62 case 2: | |
63 hash *= 37; | |
64 hash += *(cast(ushort*)s) | 0x2020; | |
65 return hash; | |
66 | |
67 case 3: | |
68 hash *= 37; | |
69 hash += (*cast(ushort*)s) << 8 + | |
70 ((cast(ubyte*)s)[2]) | 0x202020; | |
71 break; | |
72 | |
73 default: | |
74 hash *= 37; | |
75 hash += (*cast(int*)s) | 0x20202020; | |
76 s += 4; | |
77 len -= 4; | |
78 break; | |
79 } | |
80 } | |
81 } else { | |
82 // darwin HFS is case insensitive, though... | |
83 return super.hashCode(); | |
84 } | |
85 } | |
86 | |
87 bool opEquals(Object obj) | |
88 { | |
89 return opCmp(obj) == 0; | |
90 } | |
91 | |
92 static int equals(string name1, string name2) | |
93 { | |
94 return compare(name1, name2) == 0; | |
95 } | |
96 | |
97 int opCmp(Object obj) | |
98 { | |
99 return compare(str, (cast(FileName)obj).str); | |
100 } | |
101 | |
102 static int compare(string name1, string name2) | |
103 { | |
104 version (_WIN32) { | |
105 return icmp(name1, name2); | |
106 } else { | |
107 return cmp(name1, name2); | |
108 } | |
109 } | |
110 | |
111 static bool absolute(string name) | |
112 { | |
113 version (_WIN32) { | |
114 return (*name == '\\') || | |
115 (*name == '/') || | |
116 (*name && name[1] == ':'); | |
117 } else version (POSIX) { | |
118 return (*name == '/'); | |
119 } else { | |
120 static assert(false); | |
121 } | |
122 } | |
123 | |
124 /******************************** | |
125 * Return filename extension (read-only). | |
126 * Points past '.' of extension. | |
127 * If there isn't one, return NULL. | |
128 */ | |
129 static string ext(string str) | |
130 { | |
131 foreach_reverse (i, c; str) | |
132 { | |
133 switch (c) | |
134 { | |
135 case '.': | |
136 return str[i+1..$]; | |
137 version (POSIX) { | |
138 case '/': | |
139 return null; | |
140 } | |
141 version (_WIN32) { | |
142 case '\\': | |
143 case ':': | |
144 case '/': | |
145 return null; | |
146 | |
147 } default: | |
148 break; | |
149 } | |
150 } | |
151 | |
152 return null; | |
153 } | |
154 | |
155 string ext() | |
156 { | |
157 return ext(str); | |
158 } | |
159 | |
160 /******************************** | |
161 * Return filename with extension removed. | |
162 */ | |
163 | |
164 static string removeExt(string str) | |
165 { | |
166 string e = ext(str); | |
167 if (e !is null) | |
168 { | |
169 size_t len = (e.ptr - str.ptr - 1); | |
170 return str[0..len]; | |
171 } | |
172 | |
173 return str; | |
174 } | |
175 | |
176 /******************************** | |
177 * Return filename name excluding path (read-only). | |
178 */ | |
179 | |
180 static string name(string str) | |
181 { | |
182 foreach_reverse(i, c; str) | |
183 { | |
184 switch (c) | |
185 { | |
186 version (POSIX) { | |
187 case '/': | |
188 return str[i+1..$]; | |
189 } | |
190 version (_WIN32) { | |
191 case '/': | |
192 case '\\': | |
193 return str[i+1..$]; | |
194 case ':': | |
195 /* The ':' is a drive letter only if it is the second | |
196 * character or the last character, | |
197 * otherwise it is an ADS (Alternate Data Stream) separator. | |
198 * Consider ADS separators as part of the file name. | |
199 */ | |
200 if (i == 1 || i == str.length - 1) | |
201 return str[i+1..$]; | |
202 } | |
203 default: | |
204 break; | |
205 } | |
206 } | |
207 | |
208 return str; | |
209 } | |
210 | |
211 string name() | |
212 { | |
213 return name(str); | |
214 } | |
215 | |
216 /************************************** | |
217 * Return path portion of str. | |
218 * Path will does not include trailing path separator. | |
219 */ | |
220 | |
221 static string path(string str) | |
222 { | |
223 auto n = name(str).ptr; | |
224 | |
225 if (n > str.ptr) | |
226 { | |
227 auto p = n - 1; | |
228 version (POSIX) { | |
229 if (*p == '/') | |
230 n--; | |
231 } else version (_WIN32) { | |
232 if (*p == '\\' || *p == '/') | |
233 n--; | |
234 } else { | |
235 static assert(false); | |
236 } | |
237 } | |
238 | |
239 size_t pathlen = n - str.ptr; | |
240 return str[0..pathlen]; | |
241 } | |
242 | |
243 /************************************** | |
244 * Replace filename portion of path. | |
245 */ | |
246 | |
247 static string replaceName(string path, string name) | |
248 { | |
249 if (absolute(name)) | |
250 return name; | |
251 | |
252 string n = FileName.name(path); | |
253 if (n is path) | |
254 return name; | |
255 | |
256 size_t pathlen = n.ptr - path.ptr; | |
257 size_t namelen = name.length; | |
258 | |
2 | 259 char* f = cast(char*)GC.malloc(pathlen + 1 + namelen + 1); |
0 | 260 memcpy(f, path.ptr, pathlen); |
261 version (POSIX) { | |
262 if (path[pathlen - 1] != '/') | |
263 { | |
264 f[pathlen] = '/'; | |
265 pathlen++; | |
266 } | |
267 } else version (_WIN32) { | |
268 if (path[pathlen - 1] != '\\' && | |
269 path[pathlen - 1] != '/' && | |
270 path[pathlen - 1] != ':') | |
271 { | |
272 f[pathlen] = '\\'; | |
273 pathlen++; | |
274 } | |
275 } else { | |
276 static assert(false); | |
277 } | |
278 memcpy(f + pathlen, name.ptr, namelen + 1); | |
279 | |
280 return assumeUnique(f[0..pathlen+namelen]); | |
281 } | |
282 | |
283 static string combine(string path, string name) | |
284 { | |
285 size_t pathlen; | |
286 size_t namelen; | |
287 | |
288 if (path.length == 0) | |
289 return name; | |
290 | |
291 pathlen = path.length; | |
292 namelen = name.length; | |
293 | |
2 | 294 char* f = cast(char*)GC.malloc(pathlen + 1 + namelen + 1); |
0 | 295 |
296 memcpy(f, path.ptr, pathlen); | |
297 | |
298 version (POSIX) { | |
299 if (path[pathlen - 1] != '/') | |
300 { | |
301 f[pathlen] = '/'; | |
302 pathlen++; | |
303 } | |
304 } else version (_WIN32) { | |
305 if (path[pathlen - 1] != '\\' && | |
306 path[pathlen - 1] != '/' && | |
307 path[pathlen - 1] != ':') | |
308 { | |
309 f[pathlen] = '\\'; | |
310 pathlen++; | |
311 } | |
312 } else { | |
313 static assert(0); | |
314 } | |
315 memcpy(f + pathlen, name.ptr, namelen + 1); | |
316 | |
317 return assumeUnique(f[0..pathlen+namelen]); | |
318 } | |
319 | |
320 static string[] splitPath(const(char)[] spath) | |
321 { | |
322 char c = 0; // unnecessary initializer is for VC /W4 | |
323 | |
324 scope OutBuffer buf = new OutBuffer(); | |
325 string[] array; | |
326 | |
327 if (spath !is null) | |
328 { | |
329 const(char)* p = spath.ptr; | |
330 int len = spath.length; | |
331 do | |
332 { | |
333 char instring = 0; | |
334 | |
335 while (len > 0 && isspace(*p)) { // skip leading whitespace | |
336 p++; | |
337 --len; | |
338 } | |
339 | |
340 buf.reserve(len + 1); // guess size of path | |
341 for (; len; p++, len--) | |
342 { | |
343 c = *p; | |
344 switch (c) | |
345 { | |
346 case '"': | |
347 instring ^= 1; // toggle inside/outside of string | |
348 continue; | |
349 | |
350 version (MACINTOSH) { | |
351 case ',': | |
352 } | |
353 version (_WIN32) { | |
354 case ';': | |
355 } | |
356 version (POSIX) { | |
357 case ':': | |
358 } | |
359 p++; | |
360 break; // note that ; cannot appear as part | |
361 // of a path, quotes won't protect it | |
362 | |
363 case 0x1A: // ^Z means end of file | |
364 //case 0: | |
365 break; | |
366 | |
367 case '\r': | |
368 continue; // ignore carriage returns | |
369 | |
370 version (POSIX) { | |
371 case '~': | |
14
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
372 buf.writestring(to!string(getenv("HOME"))); |
0 | 373 continue; |
374 } | |
375 | |
376 version (disabled) { | |
377 case ' ': | |
378 case '\t': // tabs in filenames? | |
379 if (!instring) // if not in string | |
380 break; // treat as end of path | |
381 } | |
382 default: | |
383 buf.writeByte(c); | |
384 continue; | |
385 } | |
386 break; | |
387 } | |
388 if (buf.offset) // if path is not empty | |
389 { | |
390 //buf.writeByte(0); // to asciiz | |
391 array ~= buf.extractString(); | |
392 } | |
393 } while (len > 0); | |
394 } | |
395 | |
396 return array; | |
397 } | |
398 | |
399 static FileName defaultExt(string name, string ext) | |
400 { | |
401 string e = FileName.ext(name); | |
402 if (e !is null) { | |
403 // if already has an extension | |
404 return new FileName(name); | |
405 } | |
406 | |
407 size_t len = name.length; | |
408 size_t extlen = ext.length; | |
2 | 409 char* s = cast(char*)GC.malloc(len + 1 + extlen + 1); |
0 | 410 memcpy(s, name.ptr, len); |
411 s[len] = '.'; | |
412 memcpy(s + len + 1, ext.ptr, extlen + 1); | |
413 | |
414 return new FileName(assumeUnique(s[0..len+1+extlen])); | |
415 } | |
416 | |
417 static FileName forceExt(string name, string ext) | |
418 { | |
419 string e = FileName.ext(name); | |
420 if (e !is null) // if already has an extension | |
421 { | |
422 size_t len = e.ptr - name.ptr; | |
423 size_t extlen = ext.length; | |
424 | |
5
63623152e82a
Fixed memory corruption bug which was introduced when attempting to restore GC functionality
dkoroskin <>
parents:
4
diff
changeset
|
425 char* s = cast(char*)malloc(len + extlen + 1); /// ! |
0 | 426 memcpy(s, name.ptr, len); |
427 memcpy(s + len, ext.ptr, extlen + 1); | |
428 return new FileName(assumeUnique(s[0..len+extlen])); | |
429 } | |
430 | |
431 return defaultExt(name, ext); // doesn't have one | |
432 } | |
433 | |
434 /****************************** | |
435 * Return true if extensions match. | |
436 */ | |
437 | |
438 bool equalsExt(string ext) | |
439 { | |
440 string e = FileName.ext(); | |
441 if (e.length == 0 && ext.length == 0) | |
442 return true; | |
443 | |
444 if (e.length == 0 || ext.length == 0) | |
445 return false; | |
446 | |
447 version (POSIX) { | |
448 return cmp(e,ext) == 0; | |
449 } else version (_WIN32) { | |
450 return icmp(e,ext) == 0; | |
451 } else { | |
452 static assert(0); | |
453 } | |
454 } | |
455 | |
456 /************************************* | |
457 * Copy file from this to to. | |
458 */ | |
459 | |
460 void CopyTo(FileName to) | |
461 { | |
462 scope File file = new File(this); | |
463 | |
464 version (_WIN32) { | |
2 | 465 file.touchtime = GC.malloc(WIN32_FIND_DATA.sizeof); // keep same file time |
0 | 466 } else version (POSIX) { |
14
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
467 file.touchtime = GC.malloc(stat_t.sizeof); // keep same file time |
0 | 468 } else { |
469 static assert(0); | |
470 } | |
471 file.readv(); | |
472 file.name = to; | |
473 file.writev(); | |
474 } | |
475 | |
476 /************************************* | |
477 * Search Path for file. | |
478 * Input: | |
479 * cwd if true, search current directory before searching path | |
480 */ | |
481 | |
482 static string searchPath(Array path, string name, bool cwd) | |
483 { | |
484 if (absolute(name)) { | |
485 return exists(name) ? name : null; | |
486 } | |
487 | |
488 if (cwd) { | |
489 if (exists(name)) { | |
490 return name; | |
491 } | |
492 } | |
493 | |
494 if (path !is null) { | |
495 foreach (i; 0..path.dim) | |
496 { | |
497 String p = cast(String)path.data[i]; | |
498 string n = combine(p.str, name); | |
499 | |
500 if (exists(n)) | |
501 return n; | |
502 } | |
503 } | |
504 | |
505 return null; | |
506 } | |
16
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
507 |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
508 static string searchPath(string[] path, string name, bool cwd) |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
509 { |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
510 if (absolute(name)) { |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
511 return exists(name) ? name : null; |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
512 } |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
513 |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
514 if (cwd) { |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
515 if (exists(name)) { |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
516 return name; |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
517 } |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
518 } |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
519 |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
520 if (path !is null) { |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
521 foreach (i, p; path) |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
522 { |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
523 string n = combine(p, name); |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
524 |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
525 if (exists(n)) |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
526 return n; |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
527 } |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
528 } |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
529 |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
530 return null; |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
531 } |
5c9b78899f5d
Implemented methods for Tuples, fixed some linking issues.
Robert Clipsham <robert@octarineparrot.com>
parents:
14
diff
changeset
|
532 |
0 | 533 static int exists(string name) |
534 { | |
535 version (POSIX) { | |
14
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
536 stat_t st; |
0 | 537 |
14
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
538 if (stat(toStringz(name), &st) < 0) |
0 | 539 return 0; |
540 if (S_ISDIR(st.st_mode)) | |
541 return 2; | |
542 return 1; | |
543 } else version (_WIN32) { | |
544 HANDLE h = CreateFileA(toStringz(name), GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, HANDLE.init); /// | |
545 if (h == INVALID_HANDLE_VALUE) { | |
546 return 0; | |
547 } | |
548 | |
549 CloseHandle(h); | |
550 | |
551 DWORD dw = GetFileAttributesA(name.ptr); /// CARE! | |
552 if (dw == -1L) { | |
553 assert(false); | |
554 return 0; | |
555 } else if (dw & FILE_ATTRIBUTE_DIRECTORY) { | |
556 return 2; | |
557 } else { | |
558 return 1; | |
559 } | |
560 } else { | |
561 static assert(0); | |
562 } | |
563 } | |
564 | |
565 static void ensurePathExists(string path) | |
566 { | |
567 try { | |
568 mkdirRecurse(path); | |
569 } catch { | |
570 } | |
571 } | |
14
2cc604139636
Implemented Linux support for ddmd. Some parts are a bit hacky to just "get it working", that said, druntime and phobos compile, and unittests pass.
Robert Clipsham <robert@octarineparrot.com>
parents:
5
diff
changeset
|
572 } |