Index: src/OFString+PathAdditions_DOS.m ================================================================== --- src/OFString+PathAdditions_DOS.m +++ src/OFString+PathAdditions_DOS.m @@ -25,11 +25,32 @@ int _OFString_PathAdditions_reference; @implementation OFString (PathAdditions) + (OFString *)pathWithComponents: (OFArray *)components { - return [components componentsJoinedByString: @"\\"]; + OFMutableString *ret = [OFMutableString string]; + void *pool = objc_autoreleasePoolPush(); + bool first = true; + + for (OFString *component in components) { + if ([component length] == 0) + continue; + + if ([component isEqual: @"\\"] || [component isEqual: @"/"]) + continue; + + if (!first && ![ret hasSuffix: @"\\"] && ![ret hasSuffix: @"/"]) + [ret appendString: @"\\"]; + + [ret appendString: component]; + + first = false; + } + + objc_autoreleasePoolPop(pool); + + return ret; } - (bool)isAbsolutePath { void *pool = objc_autoreleasePoolPush(); @@ -53,24 +74,23 @@ if (pathCStringLength == 0) { objc_autoreleasePoolPop(pool); return ret; } - if (cString[pathCStringLength - 1] == '\\' || - cString[pathCStringLength - 1] == '/') - pathCStringLength--; - for (i = 0; i < pathCStringLength; i++) { if (cString[i] == '\\' || cString[i] == '/') { - [ret addObject: - [OFString stringWithUTF8String: cString + last - length: i - last]]; + if (i - last != 0) + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last]]; + last = i + 1; } } - [ret addObject: [OFString stringWithUTF8String: cString + last - length: i - last]]; + if (i - last != 0) + [ret addObject: [OFString stringWithUTF8String: cString + last + length: i - last]]; [ret makeImmutable]; objc_autoreleasePoolPop(pool); Index: src/OFString+PathAdditions_UNIX.m ================================================================== --- src/OFString+PathAdditions_UNIX.m +++ src/OFString+PathAdditions_UNIX.m @@ -25,28 +25,32 @@ int _OFString_PathAdditions_reference; @implementation OFString (PathAdditions) + (OFString *)pathWithComponents: (OFArray *)components { + OFMutableString *ret = [OFMutableString string]; void *pool = objc_autoreleasePoolPush(); - OFString *ret; - - if ([[components firstObject] isEqual: @"/"]) { - OFMutableArray OF_GENERIC(OFString *) *mutableComponents = - [[components mutableCopy] autorelease]; - - [mutableComponents replaceObjectAtIndex: 0 - withObject: @""]; - - components = mutableComponents; - } - - ret = [components componentsJoinedByString: @"/"]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; + bool first = true; + + for (OFString *component in components) { + if ([component length] == 0) + continue; + + if (!first && [component isEqual: @"/"]) + continue; + + if (!first && ![ret hasSuffix: @"/"]) + [ret appendString: @"/"]; + + [ret appendString: component]; + + first = false; + } + + objc_autoreleasePoolPop(pool); + + return ret; } - (bool)isAbsolutePath { return [self hasPrefix: @"/"]; @@ -62,27 +66,25 @@ if (pathCStringLength == 0) { objc_autoreleasePoolPop(pool); return ret; } - if (cString[pathCStringLength - 1] == '/') - pathCStringLength--; - for (i = 0; i < pathCStringLength; i++) { if (cString[i] == '/') { - [ret addObject: - [OFString stringWithUTF8String: cString + last - length: i - last]]; + if (i == 0) + [ret addObject: @"/"]; + else if (i - last != 0) + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last]]; + last = i + 1; } } - [ret addObject: [OFString stringWithUTF8String: cString + last - length: i - last]]; - - if ([[ret firstObject] isEqual: @""]) - [ret replaceObjectAtIndex: 0 - withObject: @"/"]; + if (i - last != 0) + [ret addObject: [OFString stringWithUTF8String: cString + last + length: i - last]]; [ret makeImmutable]; objc_autoreleasePoolPop(pool); Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -623,48 +623,53 @@ #ifdef OF_HAVE_FILES # if defined(OF_WINDOWS) || defined(OF_MSDOS) TEST(@"+[pathWithComponents:]", [[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: @"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"]) # endif # if defined(OF_WINDOWS) || defined(OF_MSDOS) TEST(@"-[pathComponents]", - /* /tmp */ + /* c:/tmp */ (a = [C(@"c:/tmp") pathComponents]) && [a count] == 2 && [[a objectAtIndex: 0] isEqual: @"c:"] && [[a objectAtIndex: 1] isEqual: @"tmp"] && - /* /tmp/ */ + /* c:\tmp\ */ (a = [C(@"c:\\tmp\\") pathComponents]) && [a count] == 2 && [[a objectAtIndex: 0] isEqual: @"c:"] && [[a objectAtIndex: 1] isEqual: @"tmp"] && - /* / */ + /* c:/ */ (a = [C(@"c:/") pathComponents]) && [a count] == 1 && [[a objectAtIndex: 0] isEqual: @"c:"] && - /* foo/bar */ + /* foo\bar */ (a = [C(@"foo\\bar") pathComponents]) && [a count] == 2 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[a objectAtIndex: 1] isEqual: @"bar"] && - /* foo/bar/baz/ */ + /* 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 && + /* foo\/ */ + (a = [C(@"foo\\/") pathComponents]) && [a count] == 1 && [[a objectAtIndex: 0] isEqual: @"foo"] && - [[a objectAtIndex: 1] isEqual: @""] && [[C(@"") pathComponents] count] == 0) # else TEST(@"-[pathComponents]", /* /tmp */ (a = [C(@"/tmp") pathComponents]) && [a count] == 2 && @@ -685,13 +690,12 @@ (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 = [C(@"foo//") pathComponents]) && [a count] == 1 && [[a objectAtIndex: 0] isEqual: @"foo"] && - [[a objectAtIndex: 1] isEqual: @""] && [[C(@"") pathComponents] count] == 0) # endif # if !defined(OF_WINDOWS) && !defined(OF_MSDOS) TEST(@"-[lastPathComponent]", @@ -715,48 +719,57 @@ [[C(@"foo.bar") pathExtension] isEqual: @"bar"] && [[C(@"foo/.bar") pathExtension] isEqual: @""] && [[C(@"foo/.bar.baz") pathExtension] isEqual: @"baz"] && [[C(@"foo/bar.baz/") pathExtension] isEqual: @"baz"]) -# if !defined(OF_WINDOWS) && !defined(OF_MSDOS) - TEST(@"-[stringByDeletingLastPathComponent]", - [[C(@"/tmp") stringByDeletingLastPathComponent] isEqual: @"/"] && - [[C(@"/tmp/") stringByDeletingLastPathComponent] isEqual: @"/"] && - [[C(@"/tmp/foo/") stringByDeletingLastPathComponent] - isEqual: @"/tmp"] && - [[C(@"foo/bar") stringByDeletingLastPathComponent] - isEqual: @"foo"] && - [[C(@"/") stringByDeletingLastPathComponent] isEqual: @"/"] && - [[C(@"foo") stringByDeletingLastPathComponent] isEqual: @"."]) -# else +# if defined(OF_WINDOWS) || defined(OF_MSDOS) TEST(@"-[stringByDeletingLastPathComponent]", [[C(@"\\tmp") stringByDeletingLastPathComponent] isEqual: @""] && [[C(@"/tmp/") stringByDeletingLastPathComponent] isEqual: @""] && [[C(@"c:\\tmp/foo/") stringByDeletingLastPathComponent] isEqual: @"c:\\tmp"] && [[C(@"foo\\bar") stringByDeletingLastPathComponent] isEqual: @"foo"] && [[C(@"\\") stringByDeletingLastPathComponent] isEqual: @""] && [[C(@"foo") stringByDeletingLastPathComponent] isEqual: @"."]) +# else + TEST(@"-[stringByDeletingLastPathComponent]", + [[C(@"/tmp") stringByDeletingLastPathComponent] isEqual: @"/"] && + [[C(@"/tmp/") stringByDeletingLastPathComponent] isEqual: @"/"] && + [[C(@"/tmp/foo/") stringByDeletingLastPathComponent] + isEqual: @"/tmp"] && + [[C(@"foo/bar") stringByDeletingLastPathComponent] + isEqual: @"foo"] && + [[C(@"/") stringByDeletingLastPathComponent] isEqual: @"/"] && + [[C(@"foo") stringByDeletingLastPathComponent] isEqual: @"."]) # endif -# if !defined(OF_WINDOWS) && !defined(OF_MSDOS) -# define EXPECTED @"/foo./bar" +# if defined(OF_WINDOWS) || defined(OF_MSDOS) + TEST(@"-[stringByDeletingPathExtension]", + [[C(@"foo.bar") stringByDeletingPathExtension] isEqual: @"foo"] && + [[C(@"foo..bar") stringByDeletingPathExtension] isEqual: @"foo."] && + [[C(@"c:/foo.\\bar") stringByDeletingPathExtension] + isEqual: @"c:/foo.\\bar"] && + [[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"]) # else -# define EXPECTED @"\\foo.\\bar" -# endif TEST(@"-[stringByDeletingPathExtension]", [[C(@"foo.bar") stringByDeletingPathExtension] isEqual: @"foo"] && [[C(@"foo..bar") stringByDeletingPathExtension] isEqual: @"foo."] && [[C(@"/foo./bar") stringByDeletingPathExtension] isEqual: @"/foo./bar"] && [[C(@"/foo./bar.baz") stringByDeletingPathExtension] - isEqual: EXPECTED] && + isEqual: @"/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"]) -# undef EXPECTED +# endif #endif TEST(@"-[decimalValue]", [C(@"1234") decimalValue] == 1234 && [C(@"\r\n+123 ") decimalValue] == 123 &&