Index: src/OFString.h ================================================================== --- src/OFString.h +++ src/OFString.h @@ -321,11 +321,11 @@ * @brief Creates a path from the specified path components. * * @param components An array of components for the path * @return A new autoreleased OFString */ -+ (instancetype)pathWithComponents: (OFArray*)components; ++ (OFString*)pathWithComponents: (OFArray*)components; /*! * @brief Initializes an already allocated OFString from a UTF-8 encoded C * string. * @@ -880,10 +880,17 @@ * * @return The directory name of the path */ - (OFString*)stringByDeletingLastPathComponent; +/*! + * @brief Returns a new string with the file extension of the path removed. + * + * @return A new string with the file extension of the path removed + */ +- (OFString*)stringByDeletingPathExtension; + /*! * @brief Returns the path with relative sub paths resolved. * * @return The path with relative sub paths resolved */ Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -624,11 +624,11 @@ { return [[[self alloc] initWithContentsOfURL: URL encoding: encoding] autorelease]; } -+ (instancetype)pathWithComponents: (OFArray*)components ++ (OFString*)pathWithComponents: (OFArray*)components { OFMutableString *ret = [OFMutableString string]; void *pool = objc_autoreleasePoolPush(); OFEnumerator *enumerator = [components objectEnumerator]; OFString *component; @@ -2140,11 +2140,44 @@ return [self substringWithRange: of_range(0, 1)]; } objc_autoreleasePoolPop(pool); - return @"."; + return OF_PATH_CURRENT_DIRECTORY; +} + +- (OFString*)stringByDeletingPathExtension +{ + void *pool; + OFMutableArray *components; + OFString *fileName, *ret; + 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 { return standardize_path([self pathComponents], Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -336,10 +336,21 @@ isEqual: @"/tmp"] && [[@"foo/bar" stringByDeletingLastPathComponent] isEqual: @"foo"] && [[@"/" stringByDeletingLastPathComponent] isEqual: @"/"] && [[@"foo" stringByDeletingLastPathComponent] isEqual: @"."]) + TEST(@"-[stringByDeletingPathExtension]", + [[@"foo.bar" stringByDeletingPathExtension] isEqual: @"foo"] && + [[@"foo..bar" stringByDeletingPathExtension] isEqual: @"foo."] && + [[@"/foo./bar" stringByDeletingPathExtension] + isEqual: @"/foo./bar"] && + [[@"/foo./bar.baz" stringByDeletingPathExtension] + isEqual: @"/foo./bar"] && + [[@"foo.bar/" stringByDeletingPathExtension] isEqual: @"foo"] && + [[@".foo" stringByDeletingPathExtension] isEqual: @".foo"] && + [[@".foo.bar" stringByDeletingPathExtension] isEqual: @".foo"]) + TEST(@"-[decimalValue]", [@"1234" decimalValue] == 1234 && [@"\r\n+123 " decimalValue] == 123 && [@"-500\t" decimalValue] == -500 && [@"\t\t\r\n" decimalValue] == 0)