Index: src/OFString+PathAdditions.m ================================================================== --- src/OFString+PathAdditions.m +++ src/OFString+PathAdditions.m @@ -19,8 +19,10 @@ #import "platform.h" #if defined(OF_WINDOWS) || defined(OF_MSDOS) # import "OFString+PathAdditions_DOS.m" +#elif defined(OF_AMIGAOS) +# import "OFString+PathAdditions_AmigaOS.m" #else # import "OFString+PathAdditions_UNIX.m" #endif ADDED src/OFString+PathAdditions_AmigaOS.m Index: src/OFString+PathAdditions_AmigaOS.m ================================================================== --- src/OFString+PathAdditions_AmigaOS.m +++ src/OFString+PathAdditions_AmigaOS.m @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFString+PathAdditions.h" +#import "OFArray.h" + +#import "OFOutOfRangeException.h" + +int _OFString_PathAdditions_reference; + +@implementation OFString (PathAdditions) ++ (OFString *)pathWithComponents: (OFArray *)components +{ + OFMutableString *ret = [OFMutableString string]; + void *pool = objc_autoreleasePoolPush(); + bool firstAfterDevice = true; + + for (OFString *component in components) { + if ([component length] == 0) + continue; + + if (!firstAfterDevice) + [ret appendString: @"/"]; + + [ret appendString: component]; + + if (![component hasSuffix: @":"]) + firstAfterDevice = false; + } + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (bool)isAbsolutePath +{ + return [self containsString: @":"]; +} + +- (OFArray *)pathComponents +{ + OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + const char *cString = [self UTF8String]; + size_t i, last = 0, pathCStringLength = [self UTF8StringLength]; + + if (pathCStringLength == 0) { + objc_autoreleasePoolPop(pool); + return ret; + } + + for (i = 0; i < pathCStringLength; i++) { + if (cString[i] == '/') { + if (i - last != 0) + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last]]; + else + [ret addObject: @"/"]; + + last = i + 1; + } else if (cString[i] == ':') { + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last + 1]]; + + last = i + 1; + } + } + if (i - last != 0) + [ret addObject: [OFString stringWithUTF8String: cString + last + length: i - last]]; + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (OFString *)lastPathComponent +{ + /* + * AmigaOS needs the full parsing to determine the last path component. + * This could be optimized by not creating the temporary objects, + * though. + */ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components = [self pathComponents]; + OFString *ret = [components lastObject]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)pathExtension +{ + void *pool = objc_autoreleasePoolPush(); + OFString *ret, *fileName; + size_t pos; + + fileName = [self lastPathComponent]; + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + ret = [fileName substringWithRange: + of_range(pos + 1, [fileName length] - pos - 1)]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByDeletingLastPathComponent +{ + /* + * AmigaOS needs the full parsing to delete the last path component. + * This could be optimized, though. + */ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components = [self pathComponents]; + size_t count = [components count]; + OFString *ret; + + if (count < 2) { + if ([[components firstObject] hasSuffix: @":"]) { + ret = [[components firstObject] retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; + } + + objc_autoreleasePoolPop(pool); + return @""; + } + + components = [components objectsInRange: + of_range(0, [components count] - 1)]; + ret = [OFString pathWithComponents: components]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByDeletingPathExtension +{ + void *pool; + OFMutableArray OF_GENERIC(OFString *) *components; + OFString *ret, *fileName; + size_t pos; + + if ([self length] == 0) + return [[self copy] autorelease]; + + pool = objc_autoreleasePoolPush(); + components = [[[self pathComponents] mutableCopy] autorelease]; + fileName = [components lastObject]; + + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + fileName = [fileName substringWithRange: of_range(0, pos)]; + [components replaceObjectAtIndex: [components count] - 1 + withObject: fileName]; + + ret = [OFString pathWithComponents: components]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByStandardizingPath +{ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components; + OFMutableArray OF_GENERIC(OFString *) *array; + OFString *ret; + bool done = false; + + if ([self length] == 0) + return @""; + + components = [self pathComponents]; + + if ([components count] == 1) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + array = [[components mutableCopy] autorelease]; + + while (!done) { + size_t length = [array count]; + + done = true; + + for (size_t i = 0; i < length; i++) { + OFString *component = [array objectAtIndex: i]; + OFString *parent = + (i > 0 ? [array objectAtIndex: i - 1] : 0); + + if ([component length] == 0) { + [array removeObjectAtIndex: i]; + + done = false; + break; + } + + if ([component isEqual: @"/"] && + parent != nil && ![parent isEqual: @"/"]) { + [array removeObjectsInRange: + of_range(i - 1, 2)]; + + done = false; + break; + } + } + } + + ret = [OFString pathWithComponents: array]; + + if ([self hasSuffix: @"/"]) + ret = [ret stringByAppendingString: @"/"]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByAppendingPathComponent: (OFString *)component +{ + if ([self hasSuffix: @"/"] || [self hasSuffix: @":"]) + return [self stringByAppendingString: component]; + else { + OFMutableString *ret = [[self mutableCopy] autorelease]; + + [ret appendString: @"/"]; + [ret appendString: component]; + + [ret makeImmutable]; + + return ret; + } +} +@end Index: src/OFString+PathAdditions_DOS.m ================================================================== --- src/OFString+PathAdditions_DOS.m +++ src/OFString+PathAdditions_DOS.m @@ -236,19 +236,27 @@ } - (OFString *)stringByStandardizingPath { void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC(OFString *) *components = [self pathComponents]; + OFArray OF_GENERIC(OFString *) *components; OFMutableArray OF_GENERIC(OFString *) *array; OFString *ret; - bool done = false, endsWithEmpty; + bool done = false; + + if ([self length] == 0) + return @""; + + components = [self pathComponents]; + + if ([components count] == 1) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } array = [[components mutableCopy] autorelease]; - endsWithEmpty = [[array lastObject] isEqual: @""]; - while (!done) { size_t length = [array count]; done = true; @@ -274,11 +282,11 @@ break; } } } - if (endsWithEmpty) + if ([self hasSuffix: @"\\"] || [self hasSuffix: @"/"]) [array addObject: @""]; ret = [[array componentsJoinedByString: @"\\"] retain]; objc_autoreleasePoolPop(pool); Index: src/OFString+PathAdditions_UNIX.m ================================================================== --- src/OFString+PathAdditions_UNIX.m +++ src/OFString+PathAdditions_UNIX.m @@ -235,28 +235,25 @@ - (OFString *)stringByStandardizingPath { void *pool = objc_autoreleasePoolPush(); OFArray OF_GENERIC(OFString *) *components; OFMutableArray OF_GENERIC(OFString *) *array; - OFString *firstComponent, *ret; - bool done = false, startsWithSlash, endsWithEmpty; + OFString *ret; + bool done = false, startsWithSlash; if ([self length] == 0) return @""; components = [self pathComponents]; if ([components count] == 1) { objc_autoreleasePoolPop(pool); - return self; + return [[self copy] autorelease]; } array = [[components mutableCopy] autorelease]; - firstComponent = [array firstObject]; - startsWithSlash = - ([firstComponent isEqual: @"/"] || [firstComponent length] == 0); - endsWithEmpty = ([[array lastObject] length] == 0); + startsWithSlash = [self hasPrefix: @"/"]; if (startsWithSlash) [array removeObjectAtIndex: 0]; while (!done) { @@ -290,11 +287,11 @@ if (startsWithSlash) [array insertObject: @"" atIndex: 0]; - if (endsWithEmpty) + if ([self hasSuffix: @"/"]) [array addObject: @""]; ret = [[array componentsJoinedByString: @"/"] retain]; objc_autoreleasePoolPop(pool); Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -58,10 +58,81 @@ @interface OFCharacterSet_URLQueryOrFragmentAllowed: OFCharacterSet_URLAllowedBase + (OFCharacterSet *)URLQueryOrFragmentAllowedCharacterSet; @end + +#ifdef OF_HAVE_FILES +static OFString * +pathToURLPath(OFString *path) +{ +# if defined(OF_WINDOWS) || defined(OF_MSDOS) + path = [path stringByReplacingOccurrencesOfString: @"\\" + withString: @"/"]; + path = [path stringByPrependingString: @"/"]; +# elif defined(OF_AMIGAOS) + OFArray OF_GENERIC(OFString *) *components = [path pathComponents]; + OFMutableString *ret = [OFMutableString string]; + + for (OFString *component in components) { + if ([component length] == 0) + continue; + + if ([component isEqual: @"/"]) + [ret appendString: @"/.."]; + else { + [ret appendString: @"/"]; + [ret appendString: component]; + } + } + + [ret makeImmutable]; + + return ret; +# else + return path; +# endif +} + +static OFString * +URLPathToPath(OFString *path) +{ +# if defined(OF_WINDOWS) || defined(OF_MSDOS) + path = [path substringWithRange: of_range(1, [path length] - 1)]; + path = [path stringByReplacingOccurrencesOfString: @"/" + withString: @"\\"]; +# elif defined(OF_AMIGAOS) + OFMutableArray OF_GENERIC(OFString *) *components; + size_t count; + + path = [path substringWithRange: of_range(1, [path length] - 1)]; + components = [[[path + componentsSeparatedByString: @"/"] mutableCopy] autorelease]; + count = [components count]; + + for (size_t i = 0; i < count; i++) { + OFString *component = [components objectAtIndex: i]; + + if ([component isEqual: @"."]) { + [components removeObjectAtIndex: i]; + count--; + + i--; + continue; + } + + if ([component isEqual: @".."]) + [components replaceObjectAtIndex: i + withObject: @"/"]; + } + + return [OFString pathWithComponents: components]; +# else + return path; +# endif +} +#endif @interface OFCharacterSet_invertedSetWithPercent: OFCharacterSet { OFCharacterSet *_characterSet; bool (*_characterIsMember)(id, SEL, of_unichar_t); @@ -607,10 +678,14 @@ #if defined(OF_WINDOWS) || defined(OF_MSDOS) isDirectory = ([path hasSuffix: @"\\"] || [path hasSuffix: @"/"] || [OFURLHandler_file of_directoryExistsAtPath: path]); +#elif defined(OF_AMIGAOS) + isDirectory = ([path hasSuffix: @"/"] || + [path hasSuffix: @":"] || + [OFURLHandler_file of_directoryExistsAtPath: path]); #else isDirectory = ([path hasSuffix: @"/"] || [OFURLHandler_file of_directoryExistsAtPath: path]); #endif @@ -641,15 +716,11 @@ path = [currentDirectoryPath stringByAppendingPathComponent: path]; path = [path stringByStandardizingPath]; } -# if defined(OF_WINDOWS) || defined(OF_DJGPP) - path = [path stringByReplacingOccurrencesOfString: @"\\" - withString: @"/"]; - path = [path stringByPrependingString: @"/"]; -# endif + path = pathToURLPath(path); if (isDirectory && ![path hasSuffix: @"/"]) path = [path stringByAppendingString: @"/"]; _URLEncodedScheme = @"file"; @@ -962,15 +1033,11 @@ if ([path hasSuffix: @"/"]) path = [path substringWithRange: of_range(0, [path length] - 1)]; -#if defined(OF_WINDOWS) || defined(OF_MSDOS) - path = [path substringWithRange: of_range(1, [path length] - 1)]; - path = [path stringByReplacingOccurrencesOfString: @"/" - withString: @"\\"]; -#endif + path = URLPathToPath(path); [path retain]; objc_autoreleasePoolPop(pool); Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -554,10 +554,14 @@ #ifdef OF_HAVE_FILES # if defined(OF_WINDOWS) || defined(OF_MSDOS) TEST(@"-[isAbsolutePath]", [C(@"C:\\foo") isAbsolutePath] && [C(@"a:/foo") isAbsolutePath] && ![C(@"foo") isAbsolutePath] && ![C(@"b:foo") isAbsolutePath]) +# elif defined(OF_AMIGAOS) + TEST(@"-[isAbsolutePath]", + [C(@"dh0:foo") isAbsolutePath] && [C(@"dh0:a/b") isAbsolutePath] && + ![C(@"foo/bar") isAbsolutePath] && ![C(@"foo") isAbsolutePath]) # else TEST(@"-[isAbsolutePath]", [C(@"/foo") isAbsolutePath] && [C(@"/foo/bar") isAbsolutePath] && ![C(@"foo/bar") isAbsolutePath] && ![C(@"foo") isAbsolutePath]) # endif @@ -628,10 +632,22 @@ [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo/", @"bar\\", @"", @"baz", @"\\", nil]] isEqual: @"foo/bar\\baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo", nil]] isEqual: @"foo"]) +# elif defined(OF_AMIGAOS) + TEST(@"+[pathWithComponents:]", + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"dh0:", @"foo", @"bar", @"baz", nil]] + isEqual: @"dh0:foo/bar/baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo", @"bar", @"baz", nil]] isEqual: @"foo/bar/baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo/", @"bar", @"", @"baz", @"/", nil]] + isEqual: @"foo//bar/baz//"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo", nil]] isEqual: @"foo"]) # else TEST(@"+[pathWithComponents:]", [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"/", @"foo", @"bar", @"baz", nil]] isEqual: @"/foo/bar/baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @@ -659,18 +675,46 @@ /* foo\bar */ (a = [C(@"foo\\bar") pathComponents]) && [a count] == 2 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[a objectAtIndex: 1] isEqual: @"bar"] && /* foo\bar/baz/ */ - (a = [C(@"foo\\bar/baz") pathComponents]) && [a count] == 3 && + (a = [C(@"foo\\bar/baz/") pathComponents]) && [a count] == 3 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[a objectAtIndex: 1] isEqual: @"bar"] && [[a objectAtIndex: 2] isEqual: @"baz"] && /* foo\/ */ (a = [C(@"foo\\/") pathComponents]) && [a count] == 1 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[C(@"") pathComponents] count] == 0) +# elif defined(OF_AMIGAOS) + TEST(@"-[pathComponents]", + /* dh0:tmp */ + (a = [C(@"dh0:tmp") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"dh0:"] && + [[a objectAtIndex: 1] isEqual: @"tmp"] && + /* dh0:tmp/ */ + (a = [C(@"dh0:tmp/") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"dh0:"] && + [[a objectAtIndex: 1] isEqual: @"tmp"] && + /* dh0: */ + (a = [C(@"dh0:/") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"dh0:"] && + [[a objectAtIndex: 1] isEqual: @"/"] && + /* foo/bar */ + (a = [C(@"foo/bar") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"foo"] && + [[a objectAtIndex: 1] isEqual: @"bar"] && + /* foo/bar/baz/ */ + (a = [C(@"foo/bar/baz/") pathComponents]) && [a count] == 3 && + [[a objectAtIndex: 0] isEqual: @"foo"] && + [[a objectAtIndex: 1] isEqual: @"bar"] && + [[a objectAtIndex: 2] isEqual: @"baz"] && + /* foo// */ + (a = [C(@"foo//") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"foo"] && + [[a objectAtIndex: 1] isEqual: @"/"] && + [[C(@"") pathComponents] count] == 0) # else TEST(@"-[pathComponents]", /* /tmp */ (a = [C(@"/tmp") pathComponents]) && [a count] == 2 && [[a objectAtIndex: 0] isEqual: @"/"] && @@ -685,36 +729,45 @@ /* foo/bar */ (a = [C(@"foo/bar") pathComponents]) && [a count] == 2 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[a objectAtIndex: 1] isEqual: @"bar"] && /* foo/bar/baz/ */ - (a = [C(@"foo/bar/baz") pathComponents]) && [a count] == 3 && + (a = [C(@"foo/bar/baz/") pathComponents]) && [a count] == 3 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[a objectAtIndex: 1] isEqual: @"bar"] && [[a objectAtIndex: 2] isEqual: @"baz"] && /* foo// */ (a = [C(@"foo//") pathComponents]) && [a count] == 1 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[C(@"") pathComponents] count] == 0) # endif -# if !defined(OF_WINDOWS) && !defined(OF_MSDOS) - TEST(@"-[lastPathComponent]", - [[C(@"/tmp") lastPathComponent] isEqual: @"tmp"] && - [[C(@"/tmp/") lastPathComponent] isEqual: @"tmp"] && - [[C(@"/") lastPathComponent] isEqual: @"/"] && - [[C(@"foo") lastPathComponent] isEqual: @"foo"] && - [[C(@"foo/bar") lastPathComponent] isEqual: @"bar"] && - [[C(@"foo/bar/baz/") lastPathComponent] isEqual: @"baz"]) -# else +# if defined(OF_WINDOWS) || defined(OF_MSDOS) TEST(@"-[lastPathComponent]", [[C(@"c:/tmp") lastPathComponent] isEqual: @"tmp"] && [[C(@"c:\\tmp\\") lastPathComponent] isEqual: @"tmp"] && [[C(@"\\") lastPathComponent] isEqual: @""] && [[C(@"foo") lastPathComponent] isEqual: @"foo"] && [[C(@"foo\\bar") lastPathComponent] isEqual: @"bar"] && [[C(@"foo/bar/baz/") lastPathComponent] isEqual: @"baz"]) +# elif defined(OF_AMIGAOS) + TEST(@"-[lastPathComponent]", + [[C(@"dh0:tmp") lastPathComponent] isEqual: @"tmp"] && + [[C(@"dh0:tmp/") lastPathComponent] isEqual: @"tmp"] && + [[C(@"dh0:/") lastPathComponent] isEqual: @"/"] && + [[C(@"dh0:") lastPathComponent] isEqual: @"dh0:"] && + [[C(@"foo") lastPathComponent] isEqual: @"foo"] && + [[C(@"foo/bar") lastPathComponent] isEqual: @"bar"] && + [[C(@"foo/bar/baz/") lastPathComponent] isEqual: @"baz"]) +# else + TEST(@"-[lastPathComponent]", + [[C(@"/tmp") lastPathComponent] isEqual: @"tmp"] && + [[C(@"/tmp/") lastPathComponent] isEqual: @"tmp"] && + [[C(@"/") lastPathComponent] isEqual: @"/"] && + [[C(@"foo") lastPathComponent] isEqual: @"foo"] && + [[C(@"foo/bar") lastPathComponent] isEqual: @"bar"] && + [[C(@"foo/bar/baz/") lastPathComponent] isEqual: @"baz"]) # endif TEST(@"-[pathExtension]", [[C(@"foo.bar") pathExtension] isEqual: @"bar"] && [[C(@"foo/.bar") pathExtension] isEqual: @""] && @@ -729,10 +782,24 @@ isEqual: @"c:\\tmp"] && [[C(@"foo\\bar") stringByDeletingLastPathComponent] isEqual: @"foo"] && [[C(@"\\") stringByDeletingLastPathComponent] isEqual: @""] && [[C(@"foo") stringByDeletingLastPathComponent] isEqual: @"."]) +# elif defined(OF_AMIGAOS) + TEST(@"-[stringByDeletingLastPathComponent]", + [[C(@"dh0:") stringByDeletingLastPathComponent] isEqual: @"dh0:"] && + [[C(@"dh0:tmp") stringByDeletingLastPathComponent] + isEqual: @"dh0:"] && + [[C(@"dh0:tmp/") stringByDeletingLastPathComponent] + isEqual: @"dh0:"] && + [[C(@"dh0:/") stringByDeletingLastPathComponent] + isEqual: @"dh0:"] && + [[C(@"dh0:tmp/foo/") stringByDeletingLastPathComponent] + isEqual: @"dh0:tmp"] && + [[C(@"foo/bar") stringByDeletingLastPathComponent] + isEqual: @"foo"] && + [[C(@"foo") stringByDeletingLastPathComponent] isEqual: @""]) # else TEST(@"-[stringByDeletingLastPathComponent]", [[C(@"/tmp") stringByDeletingLastPathComponent] isEqual: @"/"] && [[C(@"/tmp/") stringByDeletingLastPathComponent] isEqual: @"/"] && [[C(@"/tmp/foo/") stringByDeletingLastPathComponent] @@ -752,10 +819,25 @@ [[C(@"c:\\foo./bar.baz") stringByDeletingPathExtension] isEqual: @"c:\\foo.\\bar"] && [[C(@"foo.bar/") stringByDeletingPathExtension] isEqual: @"foo"] && [[C(@".foo") stringByDeletingPathExtension] isEqual: @".foo"] && [[C(@".foo.bar") stringByDeletingPathExtension] isEqual: @".foo"]) +# elif defined(OF_AMIGAOS) + TEST(@"-[stringByDeletingPathExtension]", + [[C(@"foo.bar") stringByDeletingPathExtension] isEqual: @"foo"] && + [[C(@"foo..bar") stringByDeletingPathExtension] isEqual: @"foo."] && + [[C(@"dh0:foo.bar") stringByDeletingPathExtension] + isEqual: @"dh0:foo"] && + [[C(@"dh0:foo./bar") stringByDeletingPathExtension] + isEqual: @"dh0:foo./bar"] && + [[C(@"dh0:foo./bar.baz") stringByDeletingPathExtension] + isEqual: @"dh0:foo./bar"] && + [[C(@"foo.bar/") stringByDeletingPathExtension] isEqual: @"foo"] && + [[C(@".foo") stringByDeletingPathExtension] isEqual: @".foo"] && + [[C(@".foo\\bar") stringByDeletingPathExtension] + isEqual: @".foo\\bar"] && + [[C(@".foo.bar") stringByDeletingPathExtension] isEqual: @".foo"]) # else TEST(@"-[stringByDeletingPathExtension]", [[C(@"foo.bar") stringByDeletingPathExtension] isEqual: @"foo"] && [[C(@"foo..bar") stringByDeletingPathExtension] isEqual: @"foo."] && [[C(@"/foo./bar") stringByDeletingPathExtension]