Index: ObjFW.xcodeproj/project.pbxproj ================================================================== --- ObjFW.xcodeproj/project.pbxproj +++ ObjFW.xcodeproj/project.pbxproj @@ -268,10 +268,14 @@ 4B55A113133AC24600B58A93 /* OFReadFailedException.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55A10D133AC24500B58A93 /* OFReadFailedException.m */; }; 4B55A114133AC24600B58A93 /* OFReadOrWriteFailedException.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B55A10E133AC24500B58A93 /* OFReadOrWriteFailedException.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4B55A115133AC24600B58A93 /* OFReadOrWriteFailedException.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55A10F133AC24500B58A93 /* OFReadOrWriteFailedException.m */; }; 4B55A116133AC24600B58A93 /* OFWriteFailedException.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B55A110133AC24500B58A93 /* OFWriteFailedException.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4B55A117133AC24600B58A93 /* OFWriteFailedException.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55A111133AC24600B58A93 /* OFWriteFailedException.m */; }; + 4B5B02BE18B288A400CE6AE4 /* OFINIFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B5B02BC18B288A400CE6AE4 /* OFINIFile.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4B5B02BF18B288A400CE6AE4 /* OFINIFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B02BD18B288A400CE6AE4 /* OFINIFile.m */; }; + 4B5B02C118B2897500CE6AE4 /* OFINIFileTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B02C018B2897500CE6AE4 /* OFINIFileTests.m */; }; + 4B5B02C418B28A1B00CE6AE4 /* testfile.ini in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B5B02C218B289ED00CE6AE4 /* testfile.ini */; }; 4B5C112F17E9AB3E003C917F /* forwarding.S in Sources */ = {isa = PBXBuildFile; fileRef = 4B5C112C17E9AAED003C917F /* forwarding.S */; }; 4B5CF8F914940BD2007AA324 /* OFString+JSONValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B5CF8F614940BD2007AA324 /* OFString+JSONValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4B5CF8FA14940BD2007AA324 /* OFString+JSONValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B5CF8F714940BD2007AA324 /* OFString+JSONValue.m */; }; 4B62ED1518566FCA0004E0E3 /* OFCopyItemFailedException.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B62ED1318566FCA0004E0E3 /* OFCopyItemFailedException.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4B62ED1618566FCA0004E0E3 /* OFCopyItemFailedException.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B62ED1418566FCA0004E0E3 /* OFCopyItemFailedException.m */; }; @@ -480,10 +484,11 @@ dstPath = ""; dstSubfolderSpec = 16; files = ( 4BAE7354139C508E00F682ED /* serialization.xml in CopyFiles */, 4BF33B4813380D2D0059CEF7 /* testfile.bin in CopyFiles */, + 4B5B02C418B28A1B00CE6AE4 /* testfile.ini in CopyFiles */, 4BF33B4713380CE20059CEF7 /* testfile.txt in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -648,10 +653,14 @@ 4B55A10D133AC24500B58A93 /* OFReadFailedException.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFReadFailedException.m; path = src/exceptions/OFReadFailedException.m; sourceTree = ""; }; 4B55A10E133AC24500B58A93 /* OFReadOrWriteFailedException.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFReadOrWriteFailedException.h; path = src/exceptions/OFReadOrWriteFailedException.h; sourceTree = ""; }; 4B55A10F133AC24500B58A93 /* OFReadOrWriteFailedException.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFReadOrWriteFailedException.m; path = src/exceptions/OFReadOrWriteFailedException.m; sourceTree = ""; }; 4B55A110133AC24500B58A93 /* OFWriteFailedException.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFWriteFailedException.h; path = src/exceptions/OFWriteFailedException.h; sourceTree = ""; }; 4B55A111133AC24600B58A93 /* OFWriteFailedException.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFWriteFailedException.m; path = src/exceptions/OFWriteFailedException.m; sourceTree = ""; }; + 4B5B02BC18B288A400CE6AE4 /* OFINIFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFINIFile.h; path = src/OFINIFile.h; sourceTree = ""; }; + 4B5B02BD18B288A400CE6AE4 /* OFINIFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFINIFile.m; path = src/OFINIFile.m; sourceTree = ""; }; + 4B5B02C018B2897500CE6AE4 /* OFINIFileTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFINIFileTests.m; path = tests/OFINIFileTests.m; sourceTree = ""; }; + 4B5B02C218B289ED00CE6AE4 /* testfile.ini */ = {isa = PBXFileReference; lastKnownFileType = text; name = testfile.ini; path = tests/testfile.ini; sourceTree = ""; }; 4B5C112817E9AAED003C917F /* apple-forwarding-arm.S */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; name = "apple-forwarding-arm.S"; path = "src/forwarding/apple-forwarding-arm.S"; sourceTree = ""; }; 4B5C112917E9AAED003C917F /* apple-forwarding-i386.S */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; name = "apple-forwarding-i386.S"; path = "src/forwarding/apple-forwarding-i386.S"; sourceTree = ""; }; 4B5C112A17E9AAED003C917F /* apple-forwarding-ppc.S */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; name = "apple-forwarding-ppc.S"; path = "src/forwarding/apple-forwarding-ppc.S"; sourceTree = ""; }; 4B5C112B17E9AAED003C917F /* apple-forwarding-x86_64.S */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; name = "apple-forwarding-x86_64.S"; path = "src/forwarding/apple-forwarding-x86_64.S"; sourceTree = ""; }; 4B5C112C17E9AAED003C917F /* forwarding.S */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; name = forwarding.S; path = src/forwarding/forwarding.S; sourceTree = ""; }; @@ -1213,10 +1222,12 @@ 4B99251012E0780000215DBE /* OFHTTPRequest.m */, 4B7161AB17A6FC7600B74970 /* OFHTTPResponse.h */, 4B7161AC17A6FC7600B74970 /* OFHTTPResponse.m */, 4BB4B54116775FF4002A2DCE /* OFHTTPServer.h */, 4BB4B54216775FF4002A2DCE /* OFHTTPServer.m */, + 4B5B02BC18B288A400CE6AE4 /* OFINIFile.h */, + 4B5B02BD18B288A400CE6AE4 /* OFINIFile.m */, 4BA49D8E13DB113B00381CDB /* OFIntrospection.h */, 4BA49D8F13DB113B00381CDB /* OFIntrospection.m */, 4BA02B9F15041F5900002F84 /* OFJSONRepresentation.h */, 4B0EA9181898690E00F573A4 /* OFKernelEventObserver.h */, 4B0EA9191898690E00F573A4 /* OFKernelEventObserver.m */, @@ -1396,10 +1407,11 @@ 4BE5F0E412DF4259005C7A0C /* OFBlockTests.m */, 4B6EF66F1235358D0076B512 /* OFDataArrayTests.m */, 4BE5F0E512DF4259005C7A0C /* OFDateTests.m */, 4B6EF6701235358D0076B512 /* OFDictionaryTests.m */, 4BB4B54916776094002A2DCE /* OFHTTPClientTests.m */, + 4B5B02C018B2897500CE6AE4 /* OFINIFileTests.m */, 4BAA60C714D09699006F068D /* OFJSONTests.m */, 4B6EF6721235358D0076B512 /* OFListTests.m */, 4B6EF6731235358D0076B512 /* OFMD5HashTests.m */, 4B6EF6741235358D0076B512 /* OFNumberTests.m */, 4B6EF6751235358D0076B512 /* OFObjectTests.m */, @@ -1452,10 +1464,11 @@ isa = PBXGroup; children = ( 4B19023D1338D6D5000374C9 /* Makefile */, 4BAE7353139C507F00F682ED /* serialization.xml */, 4BF33B4213380CD40059CEF7 /* testfile.bin */, + 4B5B02C218B289ED00CE6AE4 /* testfile.ini */, 4BF33B4313380CD40059CEF7 /* testfile.txt */, ); name = "Supporting Files"; sourceTree = ""; }; @@ -1516,10 +1529,11 @@ 4B3D23C91337FCB000DD29B8 /* OFHash.h in Headers */, 4BB4B54416775FF4002A2DCE /* OFHTTPClient.h in Headers */, 4B3D23CA1337FCB000DD29B8 /* OFHTTPRequest.h in Headers */, 4B7161AD17A6FC7600B74970 /* OFHTTPResponse.h in Headers */, 4BB4B54616775FF4002A2DCE /* OFHTTPServer.h in Headers */, + 4B5B02BE18B288A400CE6AE4 /* OFINIFile.h in Headers */, 4BA49D9013DB113B00381CDB /* OFIntrospection.h in Headers */, 4BA02BA115041F5900002F84 /* OFJSONRepresentation.h in Headers */, 4B0EA9211898690E00F573A4 /* OFKernelEventObserver.h in Headers */, 4B3D23CB1337FCB000DD29B8 /* OFList.h in Headers */, 4B674402163C395900EB1E59 /* OFLocking.h in Headers */, @@ -1887,10 +1901,11 @@ 4B3D23961337FC0D00DD29B8 /* OFFile.m in Sources */, 4BB4B54516775FF4002A2DCE /* OFHTTPClient.m in Sources */, 4B3D23981337FC0D00DD29B8 /* OFHTTPRequest.m in Sources */, 4B7161AE17A6FC7600B74970 /* OFHTTPResponse.m in Sources */, 4BB4B54716775FF4002A2DCE /* OFHTTPServer.m in Sources */, + 4B5B02BF18B288A400CE6AE4 /* OFINIFile.m in Sources */, 4BA49D9113DB113B00381CDB /* OFIntrospection.m in Sources */, 4B0EA9221898690E00F573A4 /* OFKernelEventObserver.m in Sources */, 4B0EA91C1898690E00F573A4 /* OFKernelEventObserver_kqueue.m in Sources */, 4B0EA91E1898690E00F573A4 /* OFKernelEventObserver_poll.m in Sources */, 4B0EA9201898690E00F573A4 /* OFKernelEventObserver_select.m in Sources */, @@ -2029,10 +2044,11 @@ 4BF33AFD133807A20059CEF7 /* OFBlockTests.m in Sources */, 4BF33AFE133807A20059CEF7 /* OFDataArrayTests.m in Sources */, 4BF33AFF133807A20059CEF7 /* OFDateTests.m in Sources */, 4BF33B00133807A20059CEF7 /* OFDictionaryTests.m in Sources */, 4BB4B54A16776094002A2DCE /* OFHTTPClientTests.m in Sources */, + 4B5B02C118B2897500CE6AE4 /* OFINIFileTests.m in Sources */, 4BAA60C814D09699006F068D /* OFJSONTests.m in Sources */, 4BF33B03133807A20059CEF7 /* OFListTests.m in Sources */, 4BF33B04133807A20059CEF7 /* OFMD5HashTests.m in Sources */, 4BF33B05133807A20059CEF7 /* OFNumberTests.m in Sources */, 4BF33B06133807A20059CEF7 /* OFObjectTests.m in Sources */, Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -75,10 +75,11 @@ ${USE_SRCS_FILES} \ ${USE_SRCS_PLUGINS} \ ${USE_SRCS_SOCKETS} \ ${USE_SRCS_THREADS} SRCS_FILES = OFFile.m \ + OFINIFile.m \ OFZIPArchive.m \ OFZIPArchiveEntry.m SRCS_PLUGINS = OFPlugin.m SRCS_SOCKETS = OFHTTPClient.m \ OFHTTPRequest.m \ ADDED src/OFINIFile.h Index: src/OFINIFile.h ================================================================== --- src/OFINIFile.h +++ src/OFINIFile.h @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 + * 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. + */ + +#import "OFObject.h" + +@class OFString; +@class OFMutableArray; + +/*! + * @brief A class for representing a category of an INI file. + */ +@interface OFINICategory: OFObject +{ + OFString *_name; + OFMutableArray *_lines; +} + +#ifdef OF_HAVE_PROPERTIES +@property (copy) OFString *name; +#endif + +/*! + * @brief Returns the string value for the specified key, or nil if it does not + * exist. + * + * @param key The key for which the string value should be returned + * @return The string value for the specified key, or nil if it does not exist + */ +- (OFString*)stringForKey: (OFString*)key; + +/*! + * @brief Returns the string value for the specified key or the specified + * default value if it does not exist. + * + * @param key The key for which the string value should be returned + * @param defaultValue The value to return if the key does not exist + * @return The string value for the specified key or the specified default + * value if it does not exist + */ +- (OFString*)stringForKey: (OFString*)key + defaultValue: (OFString*)defaultValue; + +/*! + * @brief Returns the integer value for the specified key or the specified + * default value if it does not exist. + * + * @param key The key for which the integer value should be returned + * @param defaultValue The value to return if the key does not exist + * @return The integer value for the specified key or the specified default + * value if it does not exist + */ +- (intmax_t)integerForKey: (OFString*)key + defaultValue: (intmax_t)defaultValue; + +/*! + * @brief Returns the bool value for the specified key or the specified default + * value if it does not exist. + * + * @param key The key for which the bool value should be returned + * @param defaultValue The value to return if the key does not exist + * @return The bool value for the specified key or the specified default value + * if it does not exist + */ +- (bool)boolForKey: (OFString*)key + defaultValue: (bool)defaultValue; + +/*! + * @brief Returns the float value for the specified key or the specified + * default value if it does not exist. + * + * @param key The key for which the float value should be returned + * @param defaultValue The value to return if the key does not exist + * @return The float value for the specified key or the specified default value + * if it does not exist + */ +- (float)floatForKey: (OFString*)key + defaultValue: (float)defaultValue; + +/*! + * @brief Returns the double value for the specified key or the specified + * default value if it does not exist. + * + * @param key The key for which the double value should be returned + * @param defaultValue The value to return if the key does not exist + * @return The double value for the specified key or the specified default + * value if it does not exist + */ +- (double)doubleForKey: (OFString*)key + defaultValue: (double)defaultValue; + +/*! + * @brief Sets the value of the specified key to the specified string. + * + * @param string The string to which the value of the key should be set + * @param key The key for which the new value should be set + */ +- (void)setString: (OFString*)string + forKey: (OFString*)key; + +/*! + * @brief Sets the value of the specified key to the specified integer. + * + * @param integer The integer to which the value of the key should be set + * @param key The key for which the new value should be set + */ +- (void)setInteger: (intmax_t)integer + forKey: (OFString*)key; + +/*! + * @brief Sets the value of the specified key to the specified bool. + * + * @param bool_ The bool to which the value of the key should be set + * @param key The key for which the new value should be set + */ +- (void)setBool: (bool)bool_ + forKey: (OFString*)key; + +/*! + * @brief Sets the value of the specified key to the specified float. + * + * @param float_ The float to which the value of the key should be set + * @param key The key for which the new value should be set + */ +- (void)setFloat: (float)float_ + forKey: (OFString*)key; + +/*! + * @brief Sets the value of the specified key to the specified double. + * + * @param double_ The double to which the value of the key should be set + * @param key The key for which the new value should be set + */ +- (void)setDouble: (double)double_ + forKey: (OFString*)key; + +/*! + * @brief Removes the value for the specified key + * + * @param key The key of the value to remove + */ +- (void)removeValueForKey: (OFString*)key; +@end + +/*! + * @brief A class for reading, creating and modifying INI files. + */ +@interface OFINIFile: OFObject +{ + OFMutableArray *_categories; +} + +/*! + * @brief Creates a new OFINIFile with the contents of the specified file. + * + * @param path The path to the file whose contents the OFINIFile should contain + * + * @return A new, autoreleased OFINIFile with the contents of the specified file + */ ++ (instancetype)fileWithPath: (OFString*)path; + +/*! + * @brief Initializes an already allocated OFINIFile with the contents of the + * specified file. + * + * @param path The path to the file whose contents the OFINIFile should contain + * + * @return An initialized OFINIFile with the contents of the specified file + */ +- initWithPath: (OFString*)path; + +/*! + * @brief Returns an @ref OFINICategory for the category with the specified + * name. + * + * @param name The name of the category for which an @ref OFINICategory should + * be returned + * + * @return An @ref OFINICategory for the category with the specified name + */ +- (OFINICategory*)categoryForName: (OFString*)name; + +/*! + * @brief Writes the contents of the OFINIFile to a file. + * + * @param path The path of the file to write to + */ +- (void)writeToFile: (OFString*)path; +@end ADDED src/OFINIFile.m Index: src/OFINIFile.m ================================================================== --- src/OFINIFile.m +++ src/OFINIFile.m @@ -0,0 +1,530 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 + * 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. + */ + +#import "OFINIFile.h" +#import "OFArray.h" +#import "OFString.h" +#import "OFFile.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" + +#import "autorelease.h" +#import "macros.h" + +@interface OFINIFile (OF_PRIVATE_CATEGORY) +- (void)OF_parseFile: (OFString*)path; +@end + +@interface OFINICategory (OF_PRIVATE_CATEGORY) +- (instancetype)OF_init; +- (void)OF_parseLine: (OFString*)line; +- (bool)OF_writeToStream: (OFStream*)stream + first: (bool)first; +@end + +@interface OFINICategory_Pair: OFObject +{ +@public + OFString *_key, *_value; +} +@end + +@interface OFINICategory_Comment: OFObject +{ +@public + OFString *_comment; +} +@end + +static bool +isWhitespaceLine(OFString *line) +{ + const char *cString = [line UTF8String]; + size_t i, length = [line UTF8StringLength]; + + for (i = 0; i < length; i++) { + switch (cString[i]) { + case ' ': + case '\t': + case '\n': + case '\r': + continue; + default: + return false; + } + } + + return true; +} + +@implementation OFINICategory +- (instancetype)OF_init +{ + self = [super init]; + + @try { + _lines = [[OFMutableArray alloc] init]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_name release]; + [_lines release]; + + [super dealloc]; +} + +- (void)setName: (OFString*)name +{ + OF_SETTER(_name, name, true, true) +} + +- (OFString*)name +{ + OF_GETTER(_name, true) +} + +- (void)OF_parseLine: (OFString*)line +{ + if (![line hasPrefix: @";"]) { + OFINICategory_Pair *pair = + [[[OFINICategory_Pair alloc] init] autorelease]; + OFString *key, *value; + size_t pos; + + if ((pos = [line rangeOfString: @"="].location) == OF_NOT_FOUND) + @throw [OFInvalidFormatException exception]; + + key = [line substringWithRange: of_range(0, pos)]; + value = [line substringWithRange: + of_range(pos + 1, [line length] - pos - 1)]; + + if ([key hasSuffix: @" "]) { + key = [key stringByDeletingEnclosingWhitespaces]; + value = [value stringByDeletingEnclosingWhitespaces]; + } + + pair->_key = [key copy]; + pair->_value = [value copy]; + + [_lines addObject: pair]; + } else { + OFINICategory_Comment *comment = + [[[OFINICategory_Comment alloc] init] autorelease]; + + comment->_comment = [line copy]; + + [_lines addObject: comment]; + } +} + +- (OFString*)stringForKey: (OFString*)key +{ + return [self stringForKey: key + defaultValue: nil]; +} + +- (OFString*)stringForKey: (OFString*)key + defaultValue: (OFString*)defaultValue +{ + void *pool = objc_autoreleasePoolPush(); + OFEnumerator *enumerator = [_lines objectEnumerator]; + id line; + + while ((line = [enumerator nextObject]) != nil) { + OFINICategory_Pair *pair; + + if (![line isKindOfClass: [OFINICategory_Pair class]]) + continue; + + pair = line; + + if ([pair->_key isEqual: key]) { + OFString *value = [pair->_value copy]; + + objc_autoreleasePoolPop(pool); + + return [value autorelease]; + } + } + + objc_autoreleasePoolPop(pool); + + return defaultValue; +} + +- (intmax_t)integerForKey: (OFString*)key + defaultValue: (intmax_t)defaultValue +{ + void *pool = objc_autoreleasePoolPush(); + OFString *value = [self stringForKey: key + defaultValue: nil]; + intmax_t ret; + + if (value != nil) { + if ([value hasPrefix: @"0x"] || [value hasPrefix: @"$"]) + ret = [value hexadecimalValue]; + else + ret = [value decimalValue]; + } else + ret = defaultValue; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (bool)boolForKey: (OFString*)key + defaultValue: (bool)defaultValue +{ + void *pool = objc_autoreleasePoolPush(); + OFString *value = [self stringForKey: key + defaultValue: nil]; + bool ret; + + if (value != nil) { + if ([value isEqual: @"true"]) + ret = true; + else if ([value isEqual: @"false"]) + ret = false; + else + @throw [OFInvalidFormatException exception]; + } else + ret = defaultValue; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (float)floatForKey: (OFString*)key + defaultValue: (float)defaultValue +{ + void *pool = objc_autoreleasePoolPush(); + OFString *value = [self stringForKey: key + defaultValue: nil]; + float ret; + + if (value != nil) + ret = [value floatValue]; + else + ret = defaultValue; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (double)doubleForKey: (OFString*)key + defaultValue: (double)defaultValue +{ + void *pool = objc_autoreleasePoolPush(); + OFString *value = [self stringForKey: key + defaultValue: nil]; + double ret; + + if (value != nil) + ret = [value doubleValue]; + else + ret = defaultValue; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (void)setString: (OFString*)string + forKey: (OFString*)key +{ + void *pool = objc_autoreleasePoolPush(); + OFEnumerator *enumerator = [_lines objectEnumerator]; + OFINICategory_Pair *pair; + id line; + + while ((line = [enumerator nextObject]) != nil) { + if (![line isKindOfClass: [OFINICategory_Pair class]]) + continue; + + pair = line; + + if ([pair->_key isEqual: key]) { + OFString *old = pair->_value; + pair->_value = [string copy]; + [old release]; + + objc_autoreleasePoolPop(pool); + + return; + } + } + + pair = [[[OFINICategory_Pair alloc] init] autorelease]; + pair->_key = [key copy]; + pair->_value = [string copy]; + [_lines addObject: pair]; + + objc_autoreleasePoolPop(pool); +} + +- (void)setInteger: (intmax_t)integer + forKey: (OFString*)key +{ + void *pool = objc_autoreleasePoolPush(); + + [self setString: [OFString stringWithFormat: @"%jd", integer] + forKey: key]; + + objc_autoreleasePoolPop(pool); +} + +- (void)setBool: (bool)bool_ + forKey: (OFString*)key +{ + if (bool_) + [self setString: @"true" + forKey: key]; + else + [self setString: @"false" + forKey: key]; +} + +- (void)setFloat: (float)float_ + forKey: (OFString*)key +{ + void *pool = objc_autoreleasePoolPush(); + + [self setString: [OFString stringWithFormat: @"%g", float_] + forKey: key]; + + objc_autoreleasePoolPop(pool); +} + +- (void)setDouble: (double)double_ + forKey: (OFString*)key +{ + void *pool = objc_autoreleasePoolPush(); + + [self setString: [OFString stringWithFormat: @"%lg", double_] + forKey: key]; + + objc_autoreleasePoolPop(pool); +} + +- (void)removeValueForKey: (OFString*)key +{ + void *pool = objc_autoreleasePoolPush(); + OFEnumerator *enumerator = [_lines objectEnumerator]; + size_t i; + id line; + + i = 0; + while ((line = [enumerator nextObject]) != nil) { + OFINICategory_Pair *pair; + + if (![line isKindOfClass: [OFINICategory_Pair class]]) { + i++; + continue; + } + + pair = line; + + if ([pair->_key isEqual: key]) { + [_lines removeObjectAtIndex: i]; + break; + } + + i++; + } + + objc_autoreleasePoolPop(pool); +} + +- (bool)OF_writeToStream: (OFStream*)stream + first: (bool)first +{ + OFEnumerator *enumerator; + id line; + + if ([_lines count] == 0) + return false; + + if (first) + [stream writeFormat: @"[%@]\n", _name]; + else + [stream writeFormat: @"\n[%@]\n", _name]; + + enumerator = [_lines objectEnumerator]; + while ((line = [enumerator nextObject]) != nil) { + if ([line isKindOfClass: [OFINICategory_Comment class]]) { + OFINICategory_Comment *comment = line; + [stream writeLine: comment->_comment]; + } else if ([line isKindOfClass: [OFINICategory_Pair class]]) { + OFINICategory_Pair *pair = line; + [stream writeFormat: @"%@=%@\n", + pair->_key, pair->_value]; + } else + @throw [OFInvalidArgumentException exception]; + } + + return true; +} +@end + +@implementation OFINICategory_Pair +- (void)dealloc +{ + [_key release]; + [_value release]; + + [super dealloc]; +} +@end + +@implementation OFINICategory_Comment +- (void)dealloc +{ + [_comment release]; + + [super dealloc]; +} +@end + +@implementation OFINIFile ++ (instancetype)fileWithPath: (OFString*)path +{ + return [[[self alloc] initWithPath: path] autorelease]; +} + +- init +{ + OF_INVALID_INIT_METHOD +} + +- initWithPath: (OFString*)path +{ + self = [super init]; + + @try { + _categories = [[OFMutableArray alloc] init]; + + [self OF_parseFile: path]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_categories release]; + + [super dealloc]; +} + +- (OFINICategory*)categoryForName: (OFString*)name +{ + void *pool = objc_autoreleasePoolPush(); + OFEnumerator *enumerator = [_categories objectEnumerator]; + OFINICategory *category; + + while ((category = [enumerator nextObject]) != nil) { + if ([[category name] isEqual: name]) { + OFINICategory *ret = [category retain]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; + } + } + + category = [[[OFINICategory alloc] OF_init] autorelease]; + [category setName: name]; + [_categories addObject: category]; + + [category retain]; + + objc_autoreleasePoolPop(pool); + + return [category autorelease]; +} + +- (void)OF_parseFile: (OFString*)path +{ + void *pool = objc_autoreleasePoolPush(); + OFFile *file = [OFFile fileWithPath: path + mode: @"r"]; + OFINICategory *category = nil; + OFString *line; + + while ((line = [file readLine]) != nil) { + if (isWhitespaceLine(line)) + continue; + + if ([line hasPrefix: @"["]) { + OFString *categoryName; + + if (![line hasSuffix: @"]"]) + @throw [OFInvalidFormatException exception]; + + categoryName = [line substringWithRange: + of_range(1, [line length] - 2)]; + + category = [[[OFINICategory alloc] + OF_init] autorelease]; + [category setName: categoryName]; + [_categories addObject: category]; + } else { + if (category == nil) + @throw [OFInvalidFormatException exception]; + + [category OF_parseLine: line]; + } + } + + objc_autoreleasePoolPop(pool); +} + +- (void)writeToFile: (OFString*)path +{ + void *pool = objc_autoreleasePoolPush(); + OFFile *file = [OFFile fileWithPath: path + mode: @"w"]; + OFEnumerator *enumerator = [_categories objectEnumerator]; + OFINICategory *category; + bool first = true; + + while ((category = [enumerator nextObject]) != nil) + if ([category OF_writeToStream: file + first: first]) + first = false; + + objc_autoreleasePoolPop(pool); +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -47,10 +47,11 @@ #import "OFStream.h" #import "OFStdIOStream.h" #import "OFDeflateStream.h" #ifdef OF_HAVE_FILES # import "OFFile.h" +# import "OFINIFile.h" # import "OFZIPArchive.h" # import "OFZIPArchiveEntry.h" #endif #ifdef OF_HAVE_SOCKETS # import "OFStreamSocket.h" Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -25,11 +25,12 @@ ${USE_SRCS_FILES} \ ${USE_SRCS_PLUGINS} \ ${USE_SRCS_SOCKETS} \ ${USE_SRCS_THREADS} \ ${OFHTTPCLIENTTESTS_M} -SRCS_FILES = OFMD5HashTests.m \ +SRCS_FILES = OFINIFileTests.m \ + OFMD5HashTests.m \ OFSerializationTests.m \ OFSHA1HashTests.m SRCS_PLUGINS = OFPluginTests.m SRCS_SOCKETS = OFTCPSocketTests.m \ OFUDPSocketTests.m @@ -77,11 +78,12 @@ fi echo "Uploading files to iOS device ${IOS_HOST} at ${IOS_TMP}..." ssh ${IOS_USER}@${IOS_HOST} \ 'rm -fr ${IOS_TMP} && mkdir -p ${IOS_TMP}/plugin' scp -q ../src/libobjfw.dylib tests testfile.bin testfile.txt \ - serialization.xml ${IOS_USER}@${IOS_HOST}:${IOS_TMP}/ + testfile.ini serialization.xml \ + ${IOS_USER}@${IOS_HOST}:${IOS_TMP}/ scp -q plugin/TestPlugin.bundle \ ${IOS_USER}@${IOS_HOST}:${IOS_TMP}/plugin/ echo "Running tests binary on iOS device ${IOS_HOST}..." ssh ${IOS_USER}@${IOS_HOST} \ 'cd ${IOS_TMP} && ${TEST_LAUNCHER} ./tests' @@ -89,10 +91,11 @@ run-on-android: all echo "Uploading files to Android device..." adb push tests /data/local/tmp/objfw/tests adb push testfile.bin /data/local/tmp/objfw/testfile.bin adb push testfile.txt /data/local/tmp/objfw/testfile.txt + adb push testfile.ini /data/local/tmp/objfw/testfile.ini adb push serialization.xml /data/local/tmp/objfw/serialization.xml echo "Running tests binary on Android device..." adb shell 'cd /data/local/tmp/objfw && ./tests' ${PROG_NOINST}: ${LIBOBJFW_DEP} ADDED tests/OFINIFileTests.m Index: tests/OFINIFileTests.m ================================================================== --- tests/OFINIFileTests.m +++ tests/OFINIFileTests.m @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 + * 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 "OFINIFile.h" +#import "OFString.h" +#import "OFFile.h" +#import "OFAutoreleasePool.h" + +#import "TestsAppDelegate.h" + +#ifdef _WIN32 +# define NL @"\r\n" +#else +# define NL @"\n" +#endif + +static OFString *module = @"OFINIFile"; + +@implementation TestsAppDelegate (OFINIFileTests) +- (void)INIFileTests +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + OFString *output = @"[tests]" NL + @"foo=baz" NL + @"foobar=baz" NL + @";comment" NL + @"new=new" NL + NL + @"[foobar]" NL + @";foobarcomment" NL + @"qux= asd" NL + NL + @"[types]" NL + @"integer=16" NL + @"bool=false" NL + @"float=0.25" NL + @"double=0.75" NL; + OFINIFile *file; + OFINICategory *category; + + TEST(@"+[fileWithPath:]", + (file = [OFINIFile fileWithPath: @"testfile.ini"])) + + TEST(@"-[categoryForName:]", + (category = [file categoryForName: @"tests"])) + + module = @"OFINICategory"; + + TEST(@"-[stringForKey:]", + [[category stringForKey: @"foo"] isEqual: @"bar"]) + + TEST(@"-[setString:forKey:]", + R([category setString: @"baz" + forKey: @"foo"]) && + R([category setString: @"new" + forKey: @"new"])) + + category = [file categoryForName: @"types"]; + + TEST(@"-[integerForKey:defaultValue:]", + [category integerForKey: @"integer" + defaultValue: 2] == 0x20) + + TEST(@"-[setInteger:forKey:]", R([category setInteger: 0x10 + forKey: @"integer"])) + + TEST(@"-[boolForKey:defaultValue:]", + [category boolForKey: @"bool" + defaultValue: false] == true) + + TEST(@"-[setBool:forKey:]", R([category setBool: false + forKey: @"bool"])) + + TEST(@"-[floatForKey:defaultValue:]", + [category floatForKey: @"float" + defaultValue: 1] == 0.5) + + TEST(@"-[setFloat:forKey:]", R([category setFloat: 0.25 + forKey: @"float"])) + + TEST(@"-[doubleForKey:defaultValue:]", + [category doubleForKey: @"double" + defaultValue: 3] == 0.25) + + TEST(@"-[setDouble:forKey:]", R([category setDouble: 0.75 + forKey: @"double"])) + + category = [file categoryForName: @"foobar"]; + + TEST(@"-[removeValueForKey:]", + R([category removeValueForKey: @"quxqux"])) + + module = @"OFINIFile"; + + TEST(@"-[writeToFile:]", R([file writeToFile: @"tmpfile.ini"]) && + [[OFString stringWithContentsOfFile: @"tmpfile.ini"] + isEqual: output]) + [OFFile removeItemAtPath: @"tmpfile.ini"]; + + [pool drain]; +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -97,10 +97,14 @@ @end @interface TestsAppDelegate (OFHTTPClientTests) - (void)HTTPClientTests; @end + +@interface TestsAppDelegate (OFINIFileTests) +- (void)INIFileTests; +@end @interface TestsAppDelegate (OFJSONTests) - (void)JSONTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -297,23 +297,24 @@ [self objectTests]; #ifdef OF_HAVE_BLOCKS [self blockTests]; #endif -#ifdef OF_HAVE_FILES - [self MD5HashTests]; - [self SHA1HashTests]; -#endif [self stringTests]; [self dataArrayTests]; [self arrayTests]; [self dictionaryTests]; [self listTests]; [self setTests]; [self dateTests]; [self numberTests]; [self streamTests]; +#ifdef OF_HAVE_FILES + [self MD5HashTests]; + [self SHA1HashTests]; + [self INIFileTests]; +#endif #ifdef OF_HAVE_SOCKETS [self TCPSocketTests]; [self UDPSocketTests]; #endif #ifdef OF_HAVE_THREADS ADDED tests/testfile.ini Index: tests/testfile.ini ================================================================== --- tests/testfile.ini +++ tests/testfile.ini @@ -0,0 +1,15 @@ +[tests] +foo = bar +foobar=baz +;comment + +[foobar] +;foobarcomment +qux= asd +quxqux =asd + +[types] +integer = 0x20 +bool = true +float = 0.5 +double = 0.25