Skip to content
Browse files

Introduces RKPathMatcher. This is basically a dressed up front end to…

… jverkoey/SOCKit. Using this will make it very easy to do complex things with patterns, resource paths, and object property interpolation thereof. Whereas RKMakePathWithObject() once took parenthesized parameters like "/stuff/(things)" it now uses colons like "/stuff/:things". It has specs and updated header docs where appropriate. Closes #305.
  • Loading branch information...
1 parent 4299cab commit e38562ffced103501d8fec0e5014c59b9c84c38b @grgcombs grgcombs committed Sep 6, 2011
View
9 Code/Network/RKClient.h
@@ -41,13 +41,16 @@ NSString* RKMakeURLPath(NSString* resourcePath);
/**
* Convenience method for generating a path against the properties of an object. Takes
- * a string with property names encoded in parentheses and interpolates the values of
+ * a string with property names encoded with colons and interpolates the values of
* the properties specified and returns the generated path.
*
- * For example, given an 'article' object with an 'articleID' property of 12345
- * RKMakePathWithObject(@"articles/(articleID)", article) would generate @"articles/12345"
+ * For example, given an 'article' object with an 'articleID' property of 12345 and a 'name' of Blake,
+ * RKMakePathWithObject(@"articles/:articleID/:name", article) would generate @"articles/12345/Blake"
*
* This functionality is the basis for resource path generation in the Router.
+ * @param path The colon encoded path pattern string to use for interpolation.
+ * @param object The object containing the properties needed for interpolation.
+ * @return A new path string, replacing the pattern's parameters with the object's actual property values.
*/
NSString* RKMakePathWithObject(NSString* path, id object);
View
54 Code/Network/RKClient.m
@@ -13,6 +13,7 @@
#import "RKNotifications.h"
#import "RKAlert.h"
#import "RKLog.h"
+#import "RKPathMatcher.h"
// Set Logging Component
#undef RKLogComponent
@@ -21,7 +22,7 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
// Global
-static RKClient *sharedClient = nil;
+static RKClient* sharedClient = nil;
///////////////////////////////////////////////////////////////////////////////////////////////////
// URL Conveniences functions
@@ -34,49 +35,18 @@
return [[RKClient sharedClient] URLPathForResourcePath:resourcePath];
}
-NSString* RKMakePathWithObject(NSString* path, id object) {
- NSMutableDictionary* substitutions = [NSMutableDictionary dictionary];
- NSScanner* scanner = [NSScanner scannerWithString:path];
-
- BOOL startsWithParentheses = [[path substringToIndex:1] isEqualToString:@"("];
- while ([scanner isAtEnd] == NO) {
- NSString* keyPath = nil;
- if (startsWithParentheses || [scanner scanUpToString:@"(" intoString:nil]) {
- // Advance beyond the opening parentheses
- if (NO == [scanner isAtEnd]) {
- [scanner setScanLocation:[scanner scanLocation] + 1];
- }
- if ([scanner scanUpToString:@")" intoString:&keyPath]) {
- NSString* searchString = [NSString stringWithFormat:@"(%@)", keyPath];
- // TODO: Add warning when the value generated a nil? Only for paths values (i.e. contaning '.')?
- NSString* propertyStringValue = [NSString stringWithFormat:@"%@", [object valueForKeyPath:keyPath]];
- [substitutions setObject:propertyStringValue forKey:searchString];
- }
- }
- }
-
- if (0 == [substitutions count]) {
- return path;
- }
-
- NSMutableString* interpolatedPath = [[path mutableCopy] autorelease];
- for (NSString* find in substitutions) {
- NSString* replace = [substitutions valueForKey:find];
- [interpolatedPath replaceOccurrencesOfString:find
- withString:replace
- options:NSLiteralSearch
- range:NSMakeRange(0, [interpolatedPath length])];
- }
-
- return [NSString stringWithString:interpolatedPath];
+NSString* RKMakePathWithObject(NSString* pattern, id object) {
+ NSCAssert(pattern != NULL, @"Pattern string must not be empty in order to create a path from an interpolated object.");
+ NSCAssert(object != NULL, @"Object provided is invalid; cannot create a path from a NULL object");
+ RKPathMatcher *matcher = [RKPathMatcher matcherWithPattern:pattern];
+ NSString *interpolatedPath = [matcher pathFromObject:object];
+ return interpolatedPath;
}
NSString * RKPathAppendQueryParams(NSString *resourcePath, NSDictionary *queryParams) {
- if ([queryParams count] > 0) {
+ if ([queryParams count] > 0)
return [NSString stringWithFormat:@"%@?%@", resourcePath, [queryParams URLEncodedString]];
- } else {
- return resourcePath;
- }
+ return resourcePath;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -104,12 +74,12 @@ + (RKClient *)sharedClient {
return sharedClient;
}
-+ (void)setSharedClient:(RKClient*)client {
++ (void)setSharedClient:(RKClient *)client {
[sharedClient release];
sharedClient = [client retain];
}
-+ (RKClient *)clientWithBaseURL:(NSString*)baseURL {
++ (RKClient *)clientWithBaseURL:(NSString *)baseURL {
RKClient *client = [[[self alloc] initWithBaseURL:baseURL] autorelease];
return client;
}
View
4 Code/ObjectMapping/RKObjectRouter.h
@@ -15,15 +15,15 @@
* static or dynamic route generation. Static routes are added by simply encoding
* the resourcePath that the mappable object should be sent to when a GET, POST, PUT
* or DELETE action is invoked. Dynamic routes are available by encoding key paths into
- * the resourcePath surrounded by parentheses (i.e. /users/(userID))
+ * the resourcePath using a single colon delimiter, such as /users/:userID
*/
@interface RKObjectRouter : NSObject {
NSMutableDictionary* _routes;
}
/**
* Register a mapping from an object class to a resource path. This resourcePath can be static
- * (i.e. /this/is/the/path) or dynamic (i.e. /users/(userID)/(username)). Dynamic routes are
+ * (i.e. /this/is/the/path) or dynamic (i.e. /users/:userID/:username). Dynamic routes are
* evaluated against the object being routed using Key-Value coding and coerced into a string.
*/
- (void)routeClass:(Class)objectClass toResourcePath:(NSString*)resourcePath;
View
5 Code/Support/NSDictionary+RKAdditions.h
@@ -16,4 +16,9 @@
*/
+ (id)dictionaryWithKeysAndObjects:(id)firstKey, ... NS_REQUIRES_NIL_TERMINATION;
+/**
+ * Strips out any percent escapes (such as %20) from the receiving dictionary's key and objects.
+ */
+- (NSDictionary *)removePercentEscapesFromKeysAndObjects;
+
@end
View
13 Code/Support/NSDictionary+RKAdditions.m
@@ -28,4 +28,17 @@ + (id)dictionaryWithKeysAndObjects:(id)firstKey, ... {
return [self dictionaryWithObjects:values forKeys:keys];
}
+- (NSDictionary *)removePercentEscapesFromKeysAndObjects {
+ NSMutableDictionary *results = [NSMutableDictionary dictionaryWithCapacity:[self count]];
+ [self enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop)
+ {
+ NSString *escapedKey = [key stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
+ id escapedValue = value;
+ if ([value respondsToSelector:@selector(stringByReplacingPercentEscapesUsingEncoding:)])
+ escapedValue = [value stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
+ [results setObject:escapedValue forKey:escapedKey];
+ }];
+ return results;
+}
+
@end
View
38 Code/Support/NSString+RestKit.h
@@ -15,7 +15,7 @@
@interface NSString (RestKit)
/**
- Returns a resource path with a dictionary of query parameters URL encoded and appended
+ Returns a resource path from a dictionary of query parameters URL encoded and appended
This is a convenience method for constructing a new resource path that includes a query. For example,
when given a resourcePath of /contacts and a dictionary of parameters containing foo=bar and color=red,
will return /contacts?foo=bar&color=red
@@ -29,15 +29,47 @@
/**
Convenience method for generating a path against the properties of an object. Takes
- a string with property names encoded in parentheses and interpolates the values of
+ a string with property names prefixed with a colon and interpolates the values of
the properties specified and returns the generated path.
For example, given an 'article' object with an 'articleID' property of 12345
- [@"articles/(articleID)" interpolateWithObject:article] would generate @"articles/12345"
+ [@"articles/:articleID" interpolateWithObject:article] would generate @"articles/12345"
This functionality is the basis for resource path generation in the Router.
@param object The object to interpolate the properties against
+ @see RKMakePathWithObject
+ @see RKPathMatcher
*/
- (NSString*)interpolateWithObject:(id)object;
+/**
+ Returns a dictionary of parameter keys and values given a URL-style query string
+ on the receiving object. For example, when given the string /contacts?foo=bar&color=red,
+ this will return a dictionary of parameters containing foo=bar and color=red, excludes the path "/contacts?"
+
+ This method originally appeared as queryContentsUsingEncoding: in the Three20 project:
+ https://github.com/facebook/three20/blob/master/src/Three20Core/Sources/NSStringAdditions.m
+
+ @param receiver A string in the form of @"/object/?sortBy=name", or @"/object/?sortBy=name&color=red"
+ @param encoding The encoding for to use while parsing the query string.
+ @return A new dictionary of query parameters, with keys like 'sortBy' and values like 'name'.
+ */
+- (NSDictionary*)queryParametersUsingEncoding:(NSStringEncoding)encoding;
+
+/**
+ Returns a dictionary of parameter keys and values arrays (if requested) given a URL-style query string
+ on the receiving object. For example, when given the string /contacts?foo=bar&color=red,
+ this will return a dictionary of parameters containing foo=[bar] and color=[red], excludes the path "/contacts?"
+
+ This method originally appeared as queryContentsUsingEncoding: in the Three20 project:
+ https://github.com/facebook/three20/blob/master/src/Three20Core/Sources/NSStringAdditions.m
+
+ @param receiver A string in the form of @"/object?sortBy=name", or @"/object?sortBy=name&color=red"
+ @param shouldUseArrays If NO, it yields the same results as queryParametersUsingEncoding:, otherwise it creates value arrays instead of value strings.
+ @param encoding The encoding for to use while parsing the query string.
+ @return A new dictionary of query parameters, with keys like 'sortBy' and value arrays (if requested) like ['name'].
+ @see queryParametersUsingEncoding:
+ */
+- (NSDictionary*)queryParametersUsingArrays:(BOOL)shouldUseArrays encoding:(NSStringEncoding)encoding;
+
@end
View
53 Code/Support/NSString+RestKit.m
@@ -22,4 +22,57 @@ - (NSString*)interpolateWithObject:(id)object {
return RKMakePathWithObject(self, object);
}
+- (NSDictionary*)queryParametersUsingEncoding:(NSStringEncoding)encoding {
+ return [self queryParametersUsingArrays:NO encoding:encoding];
+}
+
+- (NSDictionary*)queryParametersUsingArrays:(BOOL)shouldUseArrays encoding:(NSStringEncoding)encoding {
+ NSString *stringToParse = self;
+ NSRange chopRange = [stringToParse rangeOfString:@"?"];
+ if (chopRange.length > 0) {
+ chopRange.location+=1; // we want inclusive chopping up *through* "?"
+ if (chopRange.location < [stringToParse length])
+ stringToParse = [stringToParse substringFromIndex:chopRange.location];
+ }
+ NSCharacterSet* delimiterSet = [NSCharacterSet characterSetWithCharactersInString:@"&;"];
+ NSMutableDictionary* pairs = [NSMutableDictionary dictionary];
+ NSScanner* scanner = [[[NSScanner alloc] initWithString:stringToParse] autorelease];
+ while (![scanner isAtEnd]) {
+ NSString* pairString = nil;
+ [scanner scanUpToCharactersFromSet:delimiterSet intoString:&pairString];
+ [scanner scanCharactersFromSet:delimiterSet intoString:NULL];
+ NSArray* kvPair = [pairString componentsSeparatedByString:@"="];
+
+ if (!shouldUseArrays) {
+ if (kvPair.count == 2) {
+ NSString* key = [[kvPair objectAtIndex:0]
+ stringByReplacingPercentEscapesUsingEncoding:encoding];
+ NSString* value = [[kvPair objectAtIndex:1]
+ stringByReplacingPercentEscapesUsingEncoding:encoding];
+ [pairs setObject:value forKey:key];
+ }
+ }
+ else {
+ if (kvPair.count == 1 || kvPair.count == 2) {
+ NSString* key = [[kvPair objectAtIndex:0]
+ stringByReplacingPercentEscapesUsingEncoding:encoding];
+ NSMutableArray* values = [pairs objectForKey:key];
+ if (nil == values) {
+ values = [NSMutableArray array];
+ [pairs setObject:values forKey:key];
+ }
+ if (kvPair.count == 1) {
+ [values addObject:[NSNull null]];
+
+ } else if (kvPair.count == 2) {
+ NSString* value = [[kvPair objectAtIndex:1]
+ stringByReplacingPercentEscapesUsingEncoding:encoding];
+ [values addObject:value];
+ }
+ }
+ }
+ }
+ return [NSDictionary dictionaryWithDictionary:pairs];
+}
+
@end
View
1 Code/Support/Support.h
@@ -11,3 +11,4 @@
#import "RKMIMETypes.h"
#import "RKLog.h"
#import "NSString+RestKit.h"
+#import "RKPathMatcher.h"
View
2 Docs/Object Mapping.md
@@ -782,7 +782,7 @@ The currently available result coercions are:
is useful when you encountered a server side error and want to coerce the mapping results into an NSError. This is how `objectLoader:didFailWithError`
returns server side error messages to you.
- **RKObjectRouter** - Responsible for generating resource paths for accessing remote representations of objects. Capable of generating a resource
-path by interpolating property values into a string. For example, a path of "/articles/(articleID)" when applied to an Article object with a `articleID` property
+path by interpolating property values into a string. For example, a path of "/articles/:articleID" when applied to an Article object with a `articleID` property
with the value 12345, would generate "/articles/12345". The object router is used to generate resource paths when getObject, postObject, putObject and deleteObject
are invoked.
- **RKErrorMessage** - A simple class providing for the mapping of server-side error messages back to NSError objects. Contains a single `errorMessage` property. When an
View
33 RestKit.xcodeproj/project.pbxproj
@@ -276,6 +276,12 @@
25FB6D5613E4836C00F48969 /* RKObjectDynamicMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 252CF8B013E250730093BBD6 /* RKObjectDynamicMapping.h */; settings = {ATTRIBUTES = (Public, ); }; };
25FB6DBD13E4858400F48969 /* RKObjectDynamicMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 252CF8B113E250730093BBD6 /* RKObjectDynamicMapping.m */; };
37CA4C501410A4D1009A3DCE /* RKFixCategoryBug.h in Headers */ = {isa = PBXBuildFile; fileRef = 258F846E1410574B007AABCD /* RKFixCategoryBug.h */; };
+ 37CA4C7C1410A7CF009A3DCE /* SOCKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 37CA4C6F1410A7CF009A3DCE /* SOCKit.h */; };
+ 37CA4C7D1410A7CF009A3DCE /* SOCKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 37CA4C701410A7CF009A3DCE /* SOCKit.m */; };
+ 37CA4C871410ABD2009A3DCE /* RKPathMatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 37CA4C851410ABD2009A3DCE /* RKPathMatcher.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 37CA4C881410ABD2009A3DCE /* RKPathMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 37CA4C861410ABD2009A3DCE /* RKPathMatcher.m */; };
+ 37DEBA62141123BB00FDF847 /* RKPathMatcherSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 37DEBA61141123BB00FDF847 /* RKPathMatcherSpec.m */; };
+ 37DEBA651411298300FDF847 /* NSStringRestKitSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 37DEBA641411298300FDF847 /* NSStringRestKitSpec.m */; };
3F032A7910FFB89100F35142 /* RKCat.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F032A7810FFB89100F35142 /* RKCat.m */; };
3F032AA810FFBBCD00F35142 /* RKHouse.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F032AA710FFBBCD00F35142 /* RKHouse.m */; };
3F032AAB10FFBC1F00F35142 /* RKResident.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F032AAA10FFBC1F00F35142 /* RKResident.m */; };
@@ -697,6 +703,12 @@
25E9682D13E6156100ABAE92 /* RKObjectMappingDefinition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKObjectMappingDefinition.h; sourceTree = "<group>"; };
25F5182313724865009B2E22 /* RKObjectRelationshipMapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKObjectRelationshipMapping.h; sourceTree = "<group>"; };
25F5182413724866009B2E22 /* RKObjectRelationshipMapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKObjectRelationshipMapping.m; sourceTree = "<group>"; };
+ 37CA4C6F1410A7CF009A3DCE /* SOCKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOCKit.h; sourceTree = "<group>"; };
+ 37CA4C701410A7CF009A3DCE /* SOCKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOCKit.m; sourceTree = "<group>"; };
+ 37CA4C851410ABD2009A3DCE /* RKPathMatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKPathMatcher.h; sourceTree = "<group>"; };
+ 37CA4C861410ABD2009A3DCE /* RKPathMatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKPathMatcher.m; sourceTree = "<group>"; };
+ 37DEBA61141123BB00FDF847 /* RKPathMatcherSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKPathMatcherSpec.m; sourceTree = "<group>"; };
+ 37DEBA641411298300FDF847 /* NSStringRestKitSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSStringRestKitSpec.m; sourceTree = "<group>"; };
3F032A7710FFB89100F35142 /* RKCat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKCat.h; sourceTree = "<group>"; };
3F032A7810FFB89100F35142 /* RKCat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKCat.m; sourceTree = "<group>"; };
3F032AA610FFBBCD00F35142 /* RKHouse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKHouse.h; sourceTree = "<group>"; };
@@ -1109,6 +1121,8 @@
isa = PBXGroup;
children = (
253A08B41255212300976E89 /* Parsers */,
+ 37CA4C851410ABD2009A3DCE /* RKPathMatcher.h */,
+ 37CA4C861410ABD2009A3DCE /* RKPathMatcher.m */,
253A089412551D8D00976E89 /* Errors.h */,
253A089512551D8D00976E89 /* Errors.m */,
253A089612551D8D00976E89 /* NSDictionary+RKAdditions.h */,
@@ -1218,6 +1232,7 @@
children = (
258F8500141061C3007AABCD /* PortableStaticLibrary.xcconfig */,
250D5BD913A0698100471F0E /* LibComponentLogging */,
+ 37CA4C5D1410A7CE009A3DCE /* SOCKit */,
20808DB713DE8C7C000A156A /* NXJSON */,
73057FC61331AA28001908EE /* JSONKit */,
2590E69A1252372800531FA8 /* YAJL */,
@@ -1455,6 +1470,8 @@
259D511B1328547000897272 /* NSDictionary+RKRequestSerializationSpec.m */,
3F4EAF57134205CF00F944E4 /* RKXMLParserSpec.m */,
258E490013C51FE600C9C883 /* RKJSONParserJSONKitSpec.m */,
+ 37DEBA61141123BB00FDF847 /* RKPathMatcherSpec.m */,
+ 37DEBA641411298300FDF847 /* NSStringRestKitSpec.m */,
);
path = Support;
sourceTree = "<group>";
@@ -1501,6 +1518,16 @@
name = "Other Frameworks";
sourceTree = "<group>";
};
+ 37CA4C5D1410A7CE009A3DCE /* SOCKit */ = {
+ isa = PBXGroup;
+ children = (
+ 37CA4C6F1410A7CF009A3DCE /* SOCKit.h */,
+ 37CA4C701410A7CF009A3DCE /* SOCKit.m */,
+ );
+ name = SOCKit;
+ path = Vendor/SOCKit;
+ sourceTree = "<group>";
+ };
3F6C3A9210FE750E008F47C5 /* Specs */ = {
isa = PBXGroup;
children = (
@@ -1637,6 +1664,8 @@
250D5C0413A06A4D00471F0E /* lcl_config_logger.h in Headers */,
251939B613A94B670073A39B /* NSString+RestKit.h in Headers */,
258F846F1410574B007AABCD /* RKFixCategoryBug.h in Headers */,
+ 37CA4C7C1410A7CF009A3DCE /* SOCKit.h in Headers */,
+ 37CA4C871410ABD2009A3DCE /* RKPathMatcher.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2154,6 +2183,8 @@
250D5BFB13A0698100471F0E /* LCLNSLog.m in Sources */,
25DBB3A113A2486400CE90F1 /* RKLog.m in Sources */,
251939B713A94B670073A39B /* NSString+RestKit.m in Sources */,
+ 37CA4C7D1410A7CF009A3DCE /* SOCKit.m in Sources */,
+ 37CA4C881410ABD2009A3DCE /* RKPathMatcher.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2265,6 +2296,8 @@
25769F0C1409C5A0003FCDBC /* RKChild.m in Sources */,
25769F0D1409C5A0003FCDBC /* RKParent.m in Sources */,
25E5E66B1415665C00233720 /* RKObjectSerializerSpec.m in Sources */,
+ 37DEBA62141123BB00FDF847 /* RKPathMatcherSpec.m in Sources */,
+ 37DEBA651411298300FDF847 /* NSStringRestKitSpec.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
2 Specs/ObjectMapping/RKObjectMappingNextGenSpec.m
@@ -1298,7 +1298,7 @@ - (void)itShouldRegisterRailsIdiomaticObjects {
[mapping mapAttributes:@"name", @"website", nil];
[mapping mapKeyPath:@"id" toAttribute:@"userID"];
- [objectManager.router routeClass:[RKExampleUser class] toResourcePath:@"/humans/(userID)"];
+ [objectManager.router routeClass:[RKExampleUser class] toResourcePath:@"/humans/:userID"];
[objectManager.router routeClass:[RKExampleUser class] toResourcePath:@"/humans" forMethod:RKRequestMethodPOST];
[objectManager.mappingProvider registerMapping:mapping withRootKeyPath:@"human"];
View
22 Specs/ObjectMapping/RKObjectRouterSpec.m
@@ -121,6 +121,17 @@ - (void)itShouldInterpolatePropertyNamesReferencedInTheMapping {
blake.name = @"blake";
blake.railsID = [NSNumber numberWithInt:31337];
RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
+ [router routeClass:[RKHuman class] toResourcePath:@"/humans/:railsID/:name" forMethod:RKRequestMethodGET];
+
+ NSString* resourcePath = [router resourcePathForObject:blake method:RKRequestMethodGET];
+ [expectThat(resourcePath) should:be(@"/humans/31337/blake")];
+}
+
+- (void)itShouldInterpolatePropertyNamesReferencedInTheMappingWithDeprecatedParentheses {
+ RKHuman* blake = [RKHuman object];
+ blake.name = @"blake";
+ blake.railsID = [NSNumber numberWithInt:31337];
+ RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
[router routeClass:[RKHuman class] toResourcePath:@"/humans/(railsID)/(name)" forMethod:RKRequestMethodGET];
NSString* resourcePath = [router resourcePathForObject:blake method:RKRequestMethodGET];
@@ -132,6 +143,17 @@ - (void)itShouldAllowForPolymorphicURLsViaMethodCalls {
blake.name = @"blake";
blake.railsID = [NSNumber numberWithInt:31337];
RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
+ [router routeClass:[RKHuman class] toResourcePath:@":polymorphicResourcePath" forMethod:RKRequestMethodGET];
+
+ NSString* resourcePath = [router resourcePathForObject:blake method:RKRequestMethodGET];
+ [expectThat(resourcePath) should:be(@"/this/is/the/path")];
+}
+
+- (void)itShouldAllowForPolymorphicURLsViaMethodCallsWithDeprecatedParentheses {
+ RKHuman* blake = [RKHuman object];
+ blake.name = @"blake";
+ blake.railsID = [NSNumber numberWithInt:31337];
+ RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
[router routeClass:[RKHuman class] toResourcePath:@"(polymorphicResourcePath)" forMethod:RKRequestMethodGET];
NSString* resourcePath = [router resourcePathForObject:blake method:RKRequestMethodGET];
View
61 Specs/Support/NSStringRestKitSpec.m
@@ -0,0 +1,61 @@
+//
+// NSStringRestKitSpec.m
+// RestKit
+//
+// Created by Greg Combs on 9/2/11.
+// Copyright (c) 2011 RestKit. All rights reserved.
+//
+
+#import "RKSpecEnvironment.h"
+#import "NSString+RestKit.h"
+#import "RKObjectMapperSpecModel.h"
+
+@interface NSStringRestKitSpec : RKSpec
+
+@end
+
+@implementation NSStringRestKitSpec
+
+- (void)itShouldAppendQueryParameters {
+ NSString *resourcePath = @"/controller/objects/";
+ NSDictionary *queryParams = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"ascend", @"sortOrder",
+ @"name", @"groupBy",nil];
+ NSString *resultingPath = [resourcePath appendQueryParams:queryParams];
+ assertThat(resultingPath, isNot(equalTo(nil)));
+ NSString *expectedPath1 = @"/controller/objects/?sortOrder=ascend&groupBy=name";
+ NSString *expectedPath2 = @"/controller/objects/?groupBy=name&sortOrder=ascend";
+ BOOL isValidPath = ( [resultingPath isEqualToString:expectedPath1] ||
+ [resultingPath isEqualToString:expectedPath2] );
+ assertThatBool(isValidPath, is(equalToBool(YES)));
+}
+
+- (void)itShouldInterpolateObjects {
+ RKObjectMapperSpecModel *person = [[[RKObjectMapperSpecModel alloc] init] autorelease];
+ person.name = @"CuddleGuts";
+ person.age = [NSNumber numberWithInt:6];
+ NSString *interpolatedPath = [@"/people/:name/:age" interpolateWithObject:person];
+ assertThat(interpolatedPath, isNot(equalTo(nil)));
+ NSString *expectedPath = @"/people/CuddleGuts/6";
+ assertThat(interpolatedPath, is(equalTo(expectedPath)));
+}
+
+- (void)itShouldInterpolateObjectsWithDeprecatedParentheses {
+ RKObjectMapperSpecModel *person = [[[RKObjectMapperSpecModel alloc] init] autorelease];
+ person.name = @"CuddleGuts";
+ person.age = [NSNumber numberWithInt:6];
+ NSString *interpolatedPath = [@"/people/(name)/(age)" interpolateWithObject:person];
+ assertThat(interpolatedPath, isNot(equalTo(nil)));
+ NSString *expectedPath = @"/people/CuddleGuts/6";
+ assertThat(interpolatedPath, is(equalTo(expectedPath)));
+}
+
+- (void)itShouldParseQueryParameters {
+ NSString *resourcePath = @"/views/thing/?keyA=valA&keyB=valB";
+ NSDictionary *queryParams = [resourcePath queryParametersUsingEncoding:NSASCIIStringEncoding];
+ assertThat(queryParams, isNot(empty()));
+ assertThat(queryParams, hasCountOf(2));
+ assertThat(queryParams, hasEntries(@"keyA", @"valA", @"keyB", @"valB", nil));
+}
+
+@end
View
93 Specs/Support/RKPathMatcherSpec.m
@@ -0,0 +1,93 @@
+//
+// RKPathMatcherSpec.m
+// RestKit
+//
+// Created by Greg Combs on 9/2/11.
+// Copyright (c) 2011 RestKit. All rights reserved.
+//
+
+#import "RKSpecEnvironment.h"
+#import "RKPathMatcher.h"
+
+@interface RKPathMatcherSpec : RKSpec
+
+@end
+
+@implementation RKPathMatcherSpec
+
+- (void)itShouldMatchPathsWithQueryArguments {
+ NSDictionary *arguments = nil;
+ RKPathMatcher *pathMatcher = [RKPathMatcher matcherWithPath:@"/this/is/my/backend?foo=bar&this=that"];
+ BOOL isMatchingPattern = [pathMatcher matchesPattern:@"/this/is/:controllerName/:entityName" tokenizeQueryStrings:YES parsedArguments:&arguments];
+ assertThatBool(isMatchingPattern, is(equalToBool(YES)));
+ assertThat(arguments, isNot(empty()));
+ assertThat(arguments, hasEntries(@"controllerName", @"my", @"entityName", @"backend", @"foo", @"bar", @"this", @"that", nil));
+
+}
+
+- (void)itShouldMatchPathsWithEscapedArguments {
+ NSDictionary *arguments = nil;
+ RKPathMatcher *pathMatcher = [RKPathMatcher matcherWithPath:@"/bills/tx/82/SB%2014?apikey=GC12d0c6af"];
+ BOOL isMatchingPattern = [pathMatcher matchesPattern:@"/bills/:stateID/:session/:billID" tokenizeQueryStrings:YES parsedArguments:&arguments];
+ assertThatBool(isMatchingPattern, is(equalToBool(YES)));
+ assertThat(arguments, isNot(empty()));
+ assertThat(arguments, hasEntries(@"stateID", @"tx", @"session", @"82", @"billID", @"SB 14", @"apikey", @"GC12d0c6af", nil));
+
+}
+
+- (void)itShouldMatchPathsWithoutQueryArguments {
+ NSDictionary *arguments = nil;
+ RKPathMatcher* patternMatcher = [RKPathMatcher matcherWithPattern:@"github.com/:username"];
+ BOOL isMatchingPattern = [patternMatcher matchesPath:@"github.com/jverkoey" tokenizeQueryStrings:NO parsedArguments:&arguments];
+ assertThatBool(isMatchingPattern, is(equalToBool(YES)));
+ assertThat(arguments, isNot(empty()));
+ assertThat(arguments, hasEntry(@"username", @"jverkoey"));
+}
+
+- (void)itShouldMatchPathsWithoutAnyArguments {
+ NSDictionary *arguments = nil;
+ RKPathMatcher* patternMatcher = [RKPathMatcher matcherWithPattern:@"/metadata"];
+ BOOL isMatchingPattern = [patternMatcher matchesPath:@"/metadata" tokenizeQueryStrings:NO parsedArguments:&arguments];
+ assertThatBool(isMatchingPattern, is(equalToBool(YES)));
+ assertThat(arguments, is(empty()));
+}
+
+- (void)itShouldPerformTwoMatchesInARow {
+ NSDictionary *arguments = nil;
+ RKPathMatcher *pathMatcher = [RKPathMatcher matcherWithPath:@"/metadata?apikey=GC12d0c6af"];
+ BOOL isMatchingPattern1 = [pathMatcher matchesPattern:@"/metadata/:stateID" tokenizeQueryStrings:YES parsedArguments:&arguments];
+ assertThatBool(isMatchingPattern1, is(equalToBool(NO)));
+ BOOL isMatchingPattern2 = [pathMatcher matchesPattern:@"/metadata" tokenizeQueryStrings:YES parsedArguments:&arguments];
+ assertThatBool(isMatchingPattern2, is(equalToBool(YES)));
+ assertThat(arguments, isNot(empty()));
+ assertThat(arguments, hasEntry(@"apikey", @"GC12d0c6af"));
+}
+
+- (void)itShouldMatchPathsWithDeprecatedParentheses {
+ NSDictionary *arguments = nil;
+ RKPathMatcher* patternMatcher = [RKPathMatcher matcherWithPattern:@"github.com/(username)"];
+ BOOL isMatchingPattern = [patternMatcher matchesPath:@"github.com/jverkoey" tokenizeQueryStrings:NO parsedArguments:&arguments];
+ assertThatBool(isMatchingPattern, is(equalToBool(YES)));
+}
+
+- (void)itShouldCreatePathsFromInterpolatedObjects {
+ NSDictionary *person = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"CuddleGuts", @"name", [NSNumber numberWithInt:6], @"age", nil];
+ RKPathMatcher *matcher = [RKPathMatcher matcherWithPattern:@"/people/:name/:age"];
+ NSString *interpolatedPath = [matcher pathFromObject:person];
+ assertThat(interpolatedPath, isNot(equalTo(nil)));
+ NSString *expectedPath = @"/people/CuddleGuts/6";
+ assertThat(interpolatedPath, is(equalTo(expectedPath)));
+}
+
+- (void)itShouldCreatePathsFromInterpolatedObjectsWithDeprecatedParentheses {
+ NSDictionary *person = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"CuddleGuts", @"name", [NSNumber numberWithInt:6], @"age", nil];
+ RKPathMatcher *matcher = [RKPathMatcher matcherWithPattern:@"/people/(name)/(age)"];
+ NSString *interpolatedPath = [matcher pathFromObject:person];
+ assertThat(interpolatedPath, isNot(equalTo(nil)));
+ NSString *expectedPath = @"/people/CuddleGuts/6";
+ assertThat(interpolatedPath, is(equalTo(expectedPath)));
+}
+
+@end
View
2 Vendor/SOCKit/.gitignore
@@ -0,0 +1,2 @@
+.DS_Store
+*.xcuserdatad
View
84 Vendor/SOCKit/README.mdown
@@ -0,0 +1,84 @@
+SOCKit
+======
+
+String <-> Object Coding for Objective-C. Rhymes with "socket".
+
+With SOCKit and [SOCPattern][] you can easily transform objects into strings and vice versa.
+
+### Two examples, cuz devs love examples.
+
+```obj-c
+SOCPattern* pattern = [SOCPattern patternWithString:@"api.github.com/users/:username/gists"];
+[pattern stringFromObject:githubUser];
+> @"api.github.com/users/jverkoey/gists"
+```
+
+```obj-c
+SOCPattern* pattern = [SOCPattern patternWithString:@"github.com/:username"];
+[pattern performSelector:@selector(initWithUsername:) onObject:[GithubUser class] sourceString:@"github.com/jverkoey"];
+> <GithubUser> username = jverkoey
+```
+
+### Hey, this is really similar to defining routes in Rails.
+
+Damn straight it is.
+
+### And isn't this kind of like Three20's navigator?
+
+Except hella better. It's also entirely incompatible with Three20 routes. This kinda blows if
+you've already invested a ton of energy into Three20's routing tech, but here's a few reasons
+why SOCKit is better:
+
+1. *Selectors are not defined in the pattern*. The fact that Three20 requires that you define
+ selectors in the pattern is scary as hell: rename a method in one of your controllers and
+ your URL routing will silently break. No warnings, just broke. With SOCKit you define the
+ selectors using @selector notation and SOCKit infers the parameters from the pattern definition.
+ This way you can depend on the compiler to fire a warning if the selector isn't defined anywhere.
+2. *Parameters are encoded using true KVC*. You now have full access to [KVC collection operators].
+3. *SOCKit is fully unit tested and documented*. Not much more to be said here.
+
+Here's a quick breakdown of the differences between Three20 and SOCKit, if SOCKit were used as
+the backend for Three20's URL routing.
+
+```
+Three20: [map from:@"twitter://tweet/(initWithTweetId:)" toViewController:[TweetController class]];
+SOCKit: [map from:@"twitter://tweet/:id" toViewController:[TweetController class] selector:@selector(initWithTweetId:)];
+
+Three20: [map from:[Tweet class] name:@"thread" toURL:@"twitter://tweet/(id)/thread"];
+SOCKit: [map from:[Tweet class] name:@"thread" toURL:@"twitter://tweet/:id/thread"];
+```
+
+## Heads up
+
+SOCKit is a sibling project to [Nimbus][], a lightweight modular framework that makes it easy to
+blaze a trail with your iOS apps. Nimbus will soon be using SOCKit in a re-envisioning of Three20's
+navigator.
+
+Users of RESTKit will notice that SOCKit provides similar functionality to RESTKit's
+[RKMakePathWithObject][]. It's easy to imagine a point in the near future where
+`RKMakePathWithObject` uses SOCKit behind the scenes.
+
+## Add SOCKit to your project
+
+This lightweight library is built to be a dead-simple airdrop directly into your project. Contained
+in SOCKit.h and SOCKit.m is all of the functionality you will need in order to start mapping
+Strings <-> Objects. To start using SOCKit, simply download or `git checkout` the SOCKit repo
+and drag SOCKit.h and SOCKit.m to your project's source tree. `#import "SOCKit.h"` where you want
+to use SOCKit and start pumping out some mad String <-> Object coding.
+
+## Some cool things
+
+When coding objects into strings you define parameters by prefixing the property name with a colon.
+So if you have a Tweet object with a `tweetId` property, the pattern parameter name would look like
+`:tweetId`. Simple enough.
+
+But now let's say you have a Tweet object that contains a reference to a TwitterUser object via
+the `user` property, and that TwitterUser object has a `username` property. Check this out:
+`:user.username`. If this was one of my tweets and I encoded the Tweet object using a SOCKit
+pattern the resulting string would be `@"featherless"`. KVC rocks.
+
+[SOCPattern]: https://github.com/jverkoey/sockit/blob/master/SOCKit.h
+[KVC collection operators]: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html#//apple_ref/doc/uid/20002176-BAJEAIEE
+[Nimbus]: http://jverkoey.github.com/nimbus
+[RESTKit]: https://github.com/RestKit/RestKit
+[RKMakePathWithObject]: https://github.com/RestKit/RestKit/blob/master/Code/Network/RKClient.m#L37
View
149 Vendor/SOCKit/SOCKit.h
@@ -0,0 +1,149 @@
+//
+// Copyright 2011 Jeff Verkoeyen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+
+/**
+ * String <-> Object Coding.
+ *
+ * Code information from strings into objects and vice versa.
+ *
+ * A pattern is a string with parameter names prefixed by colons (":").
+ * An example of a pattern string with one parameter named :username is:
+ * api.github.com/users/:username/gists
+ *
+ * Patterns, once created, can be used to efficiently turn objects into strings and
+ * vice versa. Respectively, these techniques are referred to as inbound and outbound.
+ *
+ * Inbound example (turn an object into a string):
+ *
+ * pattern: api.github.com/users/:username/gists
+ * > [pattern stringFromObject:githubUser];
+ * returns: api.github.com/users/jverkoey/gists
+ *
+ * pattern: api.github.com/repos/:username/:repo/issues
+ * > [pattern stringFromObject:githubRepo];
+ * returns: api.github.com/repos/jverkoey/sockit/issues
+ *
+ * Outbound example (turn a string into an object):
+ *
+ * pattern: github.com/:username
+ * > [pattern performSelector:@selector(initWithUsername:) onObject:[GithubUser class] sourceString:@"github.com/jverkoey"];
+ * returns: an allocated, initialized, and autoreleased GithubUser object with @"jverkoey" passed
+ * to the initWithUsername: method.
+ *
+ * pattern: github.com/:username/:repo
+ * > [pattern performSelector:@selector(initWithUsername:repoName:) onObject:[GithubUser class] sourceString:@"github.com/jverkoey/sockit"];
+ * returns: an allocated, initialized, and autoreleased GithubUser object with @"jverkoey" and
+ * @"sockit" passed to the initWithUsername:repoName: method.
+ *
+ * pattern: github.com/:username
+ * > [pattern performSelector:@selector(setUsername:) onObject:githubUser sourceString:@"github.com/jverkoey"];
+ * returns: nil because setUsername: does not have a return value. githubUser's username property
+ * is now @"jverkoey".
+ *
+ * Note:
+ *
+ * Pattern parameters must be separated by some sort of non-parameter character.
+ * This means that you can't define a pattern like :user:repo. This is because when we
+ * get around to wanting to decode the string back into an object we need some sort of
+ * delimiter between the parameters.
+ *
+ * Note 2:
+ *
+ * If you have colons in your text that aren't followed by a valid parameter name then the
+ * colon will be treated as static text. This is handy if you're defining a URL pattern.
+ * For example: @"http://github.com/:user" only has one parameter, :user. The ":" in http://
+ * is ignored.
+ */
+@interface SOCPattern : NSObject {
+@private
+ NSString* _patternString;
+ NSArray* _tokens;
+ NSArray* _parameters;
+}
+
+/**
+ * Initializes a newly allocated pattern object with the given pattern string.
+ */
+- (id)initWithString:(NSString *)string;
++ (id)patternWithString:(NSString *)string;
+
+/**
+ * Returns YES if the given string can be used with performSelector:onObject:sourceString: or
+ * extractParameterKeyValuesFromSourceString:.
+ *
+ * A matching string must exactly match all of the static portions of the pattern and provide
+ * values for each of the parameters.
+ *
+ * @param string A string that may or may not conform to this pattern.
+ * @returns YES if the given string conforms to this pattern, NO otherwise.
+ */
+- (BOOL)stringMatches:(NSString *)string;
+
+/**
+ * Performs the given selector on the object with the matching parameter values from sourceString.
+ *
+ * @param selector The selector to perform on the object. If there aren't enough
+ * parameters in the pattern then the excess parameters in the selector
+ * will be nil.
+ * @param object The object to perform the selector on.
+ * @param sourceString A string that conforms to this pattern. The parameter values from
+ * this string are used as the arguments when performing the selector
+ * on the object.
+ * @returns The initialized, autoreleased object if the selector is an initializer
+ * (prefixed with "init") and object is a Class, otherwise the return value from
+ * invoking the selector.
+ */
+- (id)performSelector:(SEL)selector onObject:(id)object sourceString:(NSString *)sourceString;
+
+/**
+ * Extracts the matching parameter values from sourceString into an NSDictionary.
+ *
+ * @param sourceString A string that conforms to this pattern. The parameter values from
+ * this string are extracted into the NSDictionary.
+ * @returns A dictionary of key value pairs. All values will be NSStrings. The keys will
+ * correspond to the pattern's parameter names. Duplicate key values will be
+ * written over by later values.
+ */
+- (NSDictionary *)extractParameterKeyValuesFromSourceString:(NSString *)sourceString;
+
+/**
+ * Returns a string with the parameters of this pattern replaced using Key-Value Coding (KVC)
+ * on the receiving object.
+ *
+ * Parameters of the pattern are evaluated using valueForKeyPath:. See Apple's KVC documentation
+ * for more details.
+ *
+ * Key-Value Coding Fundamentals:
+ * http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/BasicPrinciples.html#//apple_ref/doc/uid/20002170-BAJEAIEE
+ *
+ * Collection Operators:
+ * http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html#//apple_ref/doc/uid/20002176-BAJEAIEE
+ */
+- (NSString *)stringFromObject:(id)object;
+
+@end
+
+/**
+ * A convenience method for:
+ *
+ * SOCPattern* pattern = [SOCPattern patternWithString:string];
+ * NSString* result = [pattern stringFromObject:object];
+ *
+ * @see documentation for stringFromObject:
+ */
+NSString* SOCStringFromStringWithObject(NSString* string, id object);
View
468 Vendor/SOCKit/SOCKit.m
@@ -0,0 +1,468 @@
+//
+// Copyright 2011 Jeff Verkoeyen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "SOCKit.h"
+
+#import <objc/runtime.h>
+#import <assert.h>
+
+typedef enum {
+ SOCArgumentTypeNone,
+ SOCArgumentTypePointer,
+ SOCArgumentTypeBool,
+ SOCArgumentTypeInteger,
+ SOCArgumentTypeLongLong,
+ SOCArgumentTypeFloat,
+ SOCArgumentTypeDouble,
+} SOCArgumentType;
+
+SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+@interface SOCParameter : NSObject {
+@private
+ NSString* _string;
+}
+
+- (id)initWithString:(NSString *)string;
++ (id)parameterWithString:(NSString *)string;
+
+- (NSString *)string;
+
+@end
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+@interface SOCPattern()
+
+- (void)_compilePattern;
+
+@end
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+@implementation SOCPattern
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)dealloc {
+ [_patternString release]; _patternString = nil;
+ [_tokens release]; _tokens = nil;
+ [_parameters release]; _parameters = nil;
+ [super dealloc];
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (id)initWithString:(NSString *)string {
+ if ((self = [super init])) {
+ _patternString = [string copy];
+
+ [self _compilePattern];
+ }
+ return self;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
++ (id)patternWithString:(NSString *)string {
+ return [[[self alloc] initWithString:string] autorelease];
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Pattern Compilation
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (NSCharacterSet *)nonParameterCharacterSet {
+ NSMutableCharacterSet* parameterCharacterSet = [NSMutableCharacterSet alphanumericCharacterSet];
+ [parameterCharacterSet addCharactersInString:@".@_"];
+ NSCharacterSet* nonParameterCharacterSet = [parameterCharacterSet invertedSet];
+ return nonParameterCharacterSet;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)_compilePattern {
+ if ([_patternString length] == 0) {
+ return;
+ }
+
+ NSMutableArray* tokens = [[NSMutableArray alloc] init];
+ NSMutableArray* parameters = [[NSMutableArray alloc] init];
+
+ NSCharacterSet* nonParameterCharacterSet = [self nonParameterCharacterSet];
+
+ // Scan through the string, creating tokens that are either strings or parameters.
+ // Parameters are prefixed with ":".
+ NSScanner* scanner = [NSScanner scannerWithString:_patternString];
+
+ // NSScanner skips whitespace and newlines by default (not ideal!).
+ [scanner setCharactersToBeSkipped:nil];
+
+ while (![scanner isAtEnd]) {
+ NSString* token = nil;
+ [scanner scanUpToString:@":" intoString:&token];
+
+ if ([token length] > 0) {
+ // Add this static text to the token list.
+ [tokens addObject:token];
+ }
+
+ if (![scanner isAtEnd]) {
+ // Skip the colon.
+ [scanner setScanLocation:[scanner scanLocation] + 1];
+
+ // Scanning won't modify the token if there aren't any characters to be read, so we must
+ // clear it before scanning again.
+ token = nil;
+ [scanner scanUpToCharactersFromSet:nonParameterCharacterSet intoString:&token];
+
+ if ([token length] > 0) {
+ // Only add parameters that have valid names.
+ SOCParameter* parameter = [SOCParameter parameterWithString:token];
+ [parameters addObject:parameter];
+ [tokens addObject:parameter];
+
+ } else {
+ // Allows for http:// to get by without creating a parameter.
+ [tokens addObject:@":"];
+ }
+ }
+ }
+
+ // This is an outbound pattern.
+ if ([parameters count] > 0) {
+ BOOL lastWasParameter = NO;
+ for (id token in tokens) {
+ if ([token isKindOfClass:[SOCParameter class]]) {
+ NSAssert(!lastWasParameter, @"Parameters must be separated by non-parameter characters.");
+ lastWasParameter = YES;
+
+ } else {
+ lastWasParameter = NO;
+ }
+ }
+ }
+
+ [_tokens release];
+ _tokens = [tokens copy];
+ [_parameters release]; _parameters = nil;
+ if ([parameters count] > 0) {
+ _parameters = [parameters copy];
+ }
+ [tokens release]; tokens = nil;
+ [parameters release]; parameters = nil;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Public Methods
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (BOOL)gatherParameterValues:(NSArray**)pValues fromString:(NSString *)string {
+ const NSInteger stringLength = [string length];
+ NSInteger validUpUntil = 0;
+ NSInteger matchingTokens = 0;
+
+ NSMutableArray* values = nil;
+ if (nil != pValues) {
+ values = [NSMutableArray array];
+ }
+
+ NSInteger tokenIndex = 0;
+ for (id token in _tokens) {
+
+ if ([token isKindOfClass:[NSString class]]) {
+ NSInteger tokenLength = [token length];
+ if (validUpUntil + tokenLength > stringLength) {
+ // There aren't enough characters in the string to satisfy this token.
+ break;
+ }
+ if (![[string substringWithRange:NSMakeRange(validUpUntil, tokenLength)]
+ isEqualToString:token]) {
+ // The tokens don't match up.
+ break;
+ }
+
+ // The string token matches.
+ validUpUntil += tokenLength;
+ ++matchingTokens;
+
+ } else {
+ NSInteger parameterLocation = validUpUntil;
+
+ // Look ahead for the next string token match.
+ if (tokenIndex + 1 < [_tokens count]) {
+ NSString* nextToken = [_tokens objectAtIndex:tokenIndex + 1];
+ NSAssert([nextToken isKindOfClass:[NSString class]], @"The token following a parameter must be a string.");
+
+ NSRange nextTokenRange = [string rangeOfString:nextToken options:0 range:NSMakeRange(validUpUntil, stringLength - validUpUntil)];
+ if (nextTokenRange.length == 0) {
+ // Couldn't find the next token.
+ break;
+ }
+ if (nextTokenRange.location == validUpUntil) {
+ // This parameter is empty.
+ break;
+ }
+
+ validUpUntil = nextTokenRange.location;
+ ++matchingTokens;
+
+ } else {
+ // Anything goes until the end of the string then.
+ if (validUpUntil == stringLength) {
+ // The last parameter is empty.
+ break;
+ }
+
+ validUpUntil = stringLength;
+ ++matchingTokens;
+ }
+
+ NSRange parameterRange = NSMakeRange(parameterLocation, validUpUntil - parameterLocation);
+ [values addObject:[string substringWithRange:parameterRange]];
+ }
+
+ ++tokenIndex;
+ }
+
+ if (nil != pValues) {
+ *pValues = [[values copy] autorelease];
+ }
+
+ return validUpUntil == stringLength && matchingTokens == [_tokens count];
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (BOOL)stringMatches:(NSString *)string {
+ return [self gatherParameterValues:nil fromString:string];
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)setArgument:(NSString*)text withType:(SOCArgumentType)type atIndex:(NSInteger)index forInvocation:(NSInvocation*)invocation {
+ // There are two implicit arguments with an invocation.
+ index+=2;
+
+ switch (type) {
+ case SOCArgumentTypeNone: {
+ break;
+ }
+ case SOCArgumentTypeInteger: {
+ int val = [text intValue];
+ [invocation setArgument:&val atIndex:index];
+ break;
+ }
+ case SOCArgumentTypeLongLong: {
+ long long val = [text longLongValue];
+ [invocation setArgument:&val atIndex:index];
+ break;
+ }
+ case SOCArgumentTypeFloat: {
+ float val = [text floatValue];
+ [invocation setArgument:&val atIndex:index];
+ break;
+ }
+ case SOCArgumentTypeDouble: {
+ double val = [text doubleValue];
+ [invocation setArgument:&val atIndex:index];
+ break;
+ }
+ case SOCArgumentTypeBool: {
+ BOOL val = [text boolValue];
+ [invocation setArgument:&val atIndex:index];
+ break;
+ }
+ default: {
+ [invocation setArgument:&text atIndex:index];
+ break;
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)setArgumentsFromValues:(NSArray *)values forInvocation:(NSInvocation *)invocation {
+ Method method = class_getInstanceMethod([invocation.target class], invocation.selector);
+ NSAssert(nil != method, @"The method must exist with the given invocation target.");
+
+ for (NSInteger ix = 0; ix < [values count]; ++ix) {
+ NSString* value = [values objectAtIndex:ix];
+
+ char argType[4];
+ method_getArgumentType(method, ix + 2, argType, sizeof(argType) / sizeof(argType[0]));
+ SOCArgumentType type = SOCArgumentTypeForTypeAsChar(argType[0]);
+
+ [self setArgument:value withType:type atIndex:ix forInvocation:invocation];
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (id)performSelector:(SEL)selector onObject:(id)object sourceString:(NSString *)sourceString {
+ BOOL isInitializer = [NSStringFromSelector(selector) hasPrefix:@"init"] && [object class] == object;
+
+ if (isInitializer) {
+ object = [[object alloc] autorelease];
+ }
+
+ NSArray* values = nil;
+ NSAssert([self gatherParameterValues:&values fromString:sourceString], @"The pattern can't be used with this string.");
+
+ id returnValue = nil;
+
+ NSMethodSignature* sig = [object methodSignatureForSelector:selector];
+ NSAssert(nil != sig, @"%@ does not respond to selector: '%@'", object, NSStringFromSelector(selector));
+ NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setTarget:object];
+ [invocation setSelector:selector];
+ [self setArgumentsFromValues:values forInvocation:invocation];
+ [invocation invoke];
+
+ if (sig.methodReturnLength) {
+ [invocation getReturnValue:&returnValue];
+ }
+
+ return returnValue;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (NSDictionary *)extractParameterKeyValuesFromSourceString:(NSString *)sourceString {
+ NSMutableDictionary* kvs = [[NSMutableDictionary alloc] initWithCapacity:[_parameters count]];
+
+ NSArray* values = nil;
+ NSAssert([self gatherParameterValues:&values fromString:sourceString], @"The pattern can't be used with this string.");
+
+ for (NSInteger ix = 0; ix < [values count]; ++ix) {
+ SOCParameter* parameter = [_parameters objectAtIndex:ix];
+ id value = [values objectAtIndex:ix];
+ [kvs setObject:value forKey:parameter.string];
+ }
+
+ NSDictionary* result = [[kvs copy] autorelease];
+ [kvs release]; kvs = nil;
+ return result;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (NSString *)stringFromObject:(id)object {
+ if ([_tokens count] == 0) {
+ return @"";
+ }
+
+ NSMutableDictionary* parameterValues =
+ [NSMutableDictionary dictionaryWithCapacity:[_parameters count]];
+ for (SOCParameter* parameter in _parameters) {
+ NSString* stringValue = [NSString stringWithFormat:@"%@", [object valueForKeyPath:parameter.string]];
+ [parameterValues setObject:stringValue forKey:parameter.string];
+ }
+
+ NSMutableString* accumulator = [[NSMutableString alloc] initWithCapacity:[_patternString length]];
+
+ for (id token in _tokens) {
+ if ([token isKindOfClass:[NSString class]]) {
+ [accumulator appendString:token];
+
+ } else {
+ SOCParameter* parameter = token;
+ [accumulator appendString:[parameterValues objectForKey:parameter.string]];
+ }
+ }
+
+ NSString* result = nil;
+ result = [[accumulator copy] autorelease];
+ [accumulator release]; accumulator = nil;
+ return result;
+}
+
+@end
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+@implementation SOCParameter
+
+- (void)dealloc {
+ [_string release]; _string = nil;
+ [super dealloc];
+}
+
+- (id)initWithString:(NSString *)string {
+ if ((self = [super init])) {
+ _string = [string copy];
+ }
+ return self;
+}
+
++ (id)parameterWithString:(NSString *)string {
+ return [[[self alloc] initWithString:string] autorelease];
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"Parameter: %@", _string];
+}
+
+- (NSString *)string {
+ return [[_string retain] autorelease];
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType) {
+ if (argType == 'c' || argType == 'i' || argType == 's' || argType == 'l' || argType == 'C'
+ || argType == 'I' || argType == 'S' || argType == 'L') {
+ return SOCArgumentTypeInteger;
+
+ } else if (argType == 'q' || argType == 'Q') {
+ return SOCArgumentTypeLongLong;
+
+ } else if (argType == 'f') {
+ return SOCArgumentTypeFloat;
+
+ } else if (argType == 'd') {
+ return SOCArgumentTypeDouble;
+
+ } else if (argType == 'B') {
+ return SOCArgumentTypeBool;
+
+ } else {
+ return SOCArgumentTypePointer;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+NSString* SOCStringFromStringWithObject(NSString* string, id object) {
+ SOCPattern* pattern = [[SOCPattern alloc] initWithString:string];
+ NSString* result = [pattern stringFromObject:object];
+ [pattern release];
+ return result;
+}
View
377 Vendor/SOCKit/SOCKit.xcodeproj/project.pbxproj
@@ -0,0 +1,377 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 660B689114088B4A00EAAFDC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 660B689014088B4A00EAAFDC /* Foundation.framework */; };
+ 660B689F14088B4A00EAAFDC /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 660B689E14088B4A00EAAFDC /* SenTestingKit.framework */; };
+ 660B68A114088B4A00EAAFDC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 660B68A014088B4A00EAAFDC /* UIKit.framework */; };
+ 660B68A214088B4A00EAAFDC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 660B689014088B4A00EAAFDC /* Foundation.framework */; };
+ 660B68A414088B4A00EAAFDC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 660B68A314088B4A00EAAFDC /* CoreGraphics.framework */; };
+ 660B68A714088B4A00EAAFDC /* libSOCKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 660B688D14088B4A00EAAFDC /* libSOCKit.a */; };
+ 667E34D0140BD732002FD733 /* SOCKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 667E34CE140BD732002FD733 /* SOCKit.h */; };
+ 667E34D1140BD732002FD733 /* SOCKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 667E34CF140BD732002FD733 /* SOCKit.m */; };
+ 667E34DB140BD776002FD733 /* SOCKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 667E34DA140BD776002FD733 /* SOCKitTests.m */; };
+ 66FAD61E140BF5E40015B014 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 667E34D7140BD75B002FD733 /* InfoPlist.strings */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 660B68A514088B4A00EAAFDC /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 660B688414088B4900EAAFDC /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 660B688C14088B4A00EAAFDC;
+ remoteInfo = SOCKit;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 660B688D14088B4A00EAAFDC /* libSOCKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSOCKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ 660B689014088B4A00EAAFDC /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 660B689D14088B4A00EAAFDC /* SOCKitTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SOCKitTests.octest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 660B689E14088B4A00EAAFDC /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
+ 660B68A014088B4A00EAAFDC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
+ 660B68A314088B4A00EAAFDC /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
+ 661278FA140BD69B00164779 /* README.mdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.mdown; sourceTree = "<group>"; };
+ 667E34CE140BD732002FD733 /* SOCKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOCKit.h; sourceTree = SOURCE_ROOT; };
+ 667E34CF140BD732002FD733 /* SOCKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOCKit.m; sourceTree = SOURCE_ROOT; };
+ 667E34D8140BD75B002FD733 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = tests/en.lproj/InfoPlist.strings; sourceTree = SOURCE_ROOT; };
+ 667E34DA140BD776002FD733 /* SOCKitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SOCKitTests.m; path = tests/SOCKitTests.m; sourceTree = SOURCE_ROOT; };
+ 667E34DC140BD77B002FD733 /* SOCKitTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "SOCKitTests-Info.plist"; path = "tests/SOCKitTests-Info.plist"; sourceTree = SOURCE_ROOT; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 660B688A14088B4A00EAAFDC /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 660B689114088B4A00EAAFDC /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 660B689914088B4A00EAAFDC /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 660B689F14088B4A00EAAFDC /* SenTestingKit.framework in Frameworks */,
+ 660B68A114088B4A00EAAFDC /* UIKit.framework in Frameworks */,
+ 660B68A214088B4A00EAAFDC /* Foundation.framework in Frameworks */,
+ 660B68A414088B4A00EAAFDC /* CoreGraphics.framework in Frameworks */,
+ 660B68A714088B4A00EAAFDC /* libSOCKit.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 660B688214088B4900EAAFDC = {
+ isa = PBXGroup;
+ children = (
+ 661278FA140BD69B00164779 /* README.mdown */,
+ 667E34CE140BD732002FD733 /* SOCKit.h */,
+ 667E34CF140BD732002FD733 /* SOCKit.m */,
+ 667E34DA140BD776002FD733 /* SOCKitTests.m */,
+ 660B68A814088B4A00EAAFDC /* SOCKitTests */,
+ 660B688F14088B4A00EAAFDC /* Frameworks */,
+ 660B688E14088B4A00EAAFDC /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ 660B688E14088B4A00EAAFDC /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 660B688D14088B4A00EAAFDC /* libSOCKit.a */,
+ 660B689D14088B4A00EAAFDC /* SOCKitTests.octest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 660B688F14088B4A00EAAFDC /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 660B689014088B4A00EAAFDC /* Foundation.framework */,
+ 660B689E14088B4A00EAAFDC /* SenTestingKit.framework */,
+ 660B68A014088B4A00EAAFDC /* UIKit.framework */,
+ 660B68A314088B4A00EAAFDC /* CoreGraphics.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 660B68A814088B4A00EAAFDC /* SOCKitTests */ = {
+ isa = PBXGroup;
+ children = (
+ 667E34DC140BD77B002FD733 /* SOCKitTests-Info.plist */,
+ 667E34D7140BD75B002FD733 /* InfoPlist.strings */,
+ );
+ path = SOCKitTests;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ 660B688B14088B4A00EAAFDC /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 667E34D0140BD732002FD733 /* SOCKit.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ 660B688C14088B4A00EAAFDC /* SOCKit */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 660B68B314088B4A00EAAFDC /* Build configuration list for PBXNativeTarget "SOCKit" */;
+ buildPhases = (
+ 660B688914088B4A00EAAFDC /* Sources */,
+ 660B688A14088B4A00EAAFDC /* Frameworks */,
+ 660B688B14088B4A00EAAFDC /* Headers */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SOCKit;
+ productName = SOCKit;
+ productReference = 660B688D14088B4A00EAAFDC /* libSOCKit.a */;
+ productType = "com.apple.product-type.library.static";
+ };
+ 660B689C14088B4A00EAAFDC /* SOCKitTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 660B68B614088B4A00EAAFDC /* Build configuration list for PBXNativeTarget "SOCKitTests" */;
+ buildPhases = (
+ 660B689814088B4A00EAAFDC /* Sources */,
+ 660B689914088B4A00EAAFDC /* Frameworks */,
+ 660B689A14088B4A00EAAFDC /* Resources */,
+ 660B689B14088B4A00EAAFDC /* ShellScript */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 660B68A614088B4A00EAAFDC /* PBXTargetDependency */,
+ );
+ name = SOCKitTests;
+ productName = SOCKitTests;
+ productReference = 660B689D14088B4A00EAAFDC /* SOCKitTests.octest */;
+ productType = "com.apple.product-type.bundle";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 660B688414088B4900EAAFDC /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0420;
+ ORGANIZATIONNAME = "Jeff Verkoeyen";
+ };
+ buildConfigurationList = 660B688714088B4900EAAFDC /* Build configuration list for PBXProject "SOCKit" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 660B688214088B4900EAAFDC;
+ productRefGroup = 660B688E14088B4A00EAAFDC /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 660B688C14088B4A00EAAFDC /* SOCKit */,
+ 660B689C14088B4A00EAAFDC /* SOCKitTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 660B689A14088B4A00EAAFDC /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 66FAD61E140BF5E40015B014 /* InfoPlist.strings in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 660B689B14088B4A00EAAFDC /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 660B688914088B4A00EAAFDC /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 667E34D1140BD732002FD733 /* SOCKit.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 660B689814088B4A00EAAFDC /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 667E34DB140BD776002FD733 /* SOCKitTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 660B68A614088B4A00EAAFDC /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 660B688C14088B4A00EAAFDC /* SOCKit */;
+ targetProxy = 660B68A514088B4A00EAAFDC /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 667E34D7140BD75B002FD733 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 667E34D8140BD75B002FD733 /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 660B68B114088B4A00EAAFDC /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ CLANG_ENABLE_OBJC_ARC = NO;
+ COPY_PHASE_STRIP = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 3.0;
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ 660B68B214088B4A00EAAFDC /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ CLANG_ENABLE_OBJC_ARC = NO;
+ COPY_PHASE_STRIP = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 3.0;
+ SDKROOT = iphoneos;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 660B68B414088B4A00EAAFDC /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ DSTROOT = /tmp/SOCKit.dst;
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ };
+ name = Debug;
+ };
+ 660B68B514088B4A00EAAFDC /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ DSTROOT = /tmp/SOCKit.dst;
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ };
+ name = Release;
+ };
+ 660B68B714088B4A00EAAFDC /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(DEVELOPER_LIBRARY_DIR)/Frameworks",
+ );
+ INFOPLIST_FILE = "tests/SOCKitTests-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = octest;
+ };
+ name = Debug;
+ };
+ 660B68B814088B4A00EAAFDC /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(DEVELOPER_LIBRARY_DIR)/Frameworks",
+ );
+ INFOPLIST_FILE = "tests/SOCKitTests-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = octest;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 660B688714088B4900EAAFDC /* Build configuration list for PBXProject "SOCKit" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 660B68B114088B4A00EAAFDC /* Debug */,
+ 660B68B214088B4A00EAAFDC /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 660B68B314088B4A00EAAFDC /* Build configuration list for PBXNativeTarget "SOCKit" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 660B68B414088B4A00EAAFDC /* Debug */,
+ 660B68B514088B4A00EAAFDC /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 660B68B614088B4A00EAAFDC /* Build configuration list for PBXNativeTarget "SOCKitTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 660B68B714088B4A00EAAFDC /* Debug */,
+ 660B68B814088B4A00EAAFDC /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 660B688414088B4900EAAFDC /* Project object */;
+}
View
7 Vendor/SOCKit/SOCKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "self:SOCKit.xcodeproj">
+ </FileRef>
+</Workspace>
View
22 Vendor/SOCKit/tests/SOCKitTests-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.jeffverkoeyen.omkit</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
View
195 Vendor/SOCKit/tests/SOCKitTests.m
@@ -0,0 +1,195 @@
+//
+// Copyright 2011 Jeff Verkoeyen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <QuartzCore/QuartzCore.h>
+
+#import "SOCKit.h"
+
+typedef void (^SimpleBlock)(void);
+
+@interface SOCTestObject : NSObject
+
+- (id)initWithId:(NSInteger)ident floatValue:(CGFloat)flv doubleValue:(double)dv longLongValue:(long long)llv stringValue:(NSString *)string;
+- (id)initWithId:(NSInteger)ident floatValue:(CGFloat)flv doubleValue:(double)dv longLongValue:(long long)llv stringValue:(NSString *)string userInfo:(id)userInfo;
+
+@property (nonatomic, readwrite, assign) NSInteger ident;
+@property (nonatomic, readwrite, assign) CGFloat flv;
+@property (nonatomic, readwrite, assign) double dv;
+@property (nonatomic, readwrite, assign) long long llv;
+@property (nonatomic, readwrite, copy) NSString* string;
+@end
+
+@implementation SOCTestObject
+
+@synthesize ident;
+@synthesize flv;
+@synthesize dv;
+@synthesize llv;
+@synthesize string;
+
+- (void)dealloc {
+ [string release]; string = nil;
+ [super dealloc];
+}
+
+- (id)initWithId:(NSInteger)anIdent floatValue:(CGFloat)anFlv doubleValue:(double)aDv longLongValue:(long long)anLlv stringValue:(NSString *)aString {
+ if ((self = [super init])) {
+ self.ident = anIdent;
+ self.flv = anFlv;
+ self.dv = aDv;
+ self.llv = anLlv;
+ self.string = aString;
+ }
+ return self;
+}
+
+- (id)initWithId:(NSInteger)anIdent floatValue:(CGFloat)anFlv doubleValue:(double)aDv longLongValue:(long long)anLlv stringValue:(NSString *)aString userInfo:(id)userInfo {
+ return [self initWithId:anIdent floatValue:anFlv doubleValue:aDv longLongValue:anLlv stringValue:aString];
+}
+
+@end
+
+@interface SOCKitTests : SenTestCase
+@end
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+@implementation SOCKitTests
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)testEmptyCases {
+ STAssertTrue([SOCStringFromStringWithObject(nil, nil) isEqualToString:@""], @"Should be the same string.");
+
+ STAssertTrue([SOCStringFromStringWithObject(@"", nil) isEqualToString:@""], @"Should be the same string.");
+ STAssertTrue([SOCStringFromStringWithObject(@" ", nil) isEqualToString:@" "], @"Should be the same string.");
+
+ STAssertTrue([SOCStringFromStringWithObject(@"abcdef", nil) isEqualToString:@"abcdef"], @"Should be the same string.");
+ STAssertTrue([SOCStringFromStringWithObject(@"abcdef", [NSArray array]) isEqualToString:@"abcdef"], @"Should be the same string.");
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)testFailureCases {
+ STAssertThrows([SOCPattern patternWithString:@":dilly:isacat"], @"Parameters must be separated by strings.");
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)testSingleParameterCoding {
+ NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:1337], @"leet",
+ [NSNumber numberWithInt:5000], @"five",
+ nil];
+ STAssertTrue([SOCStringFromStringWithObject(@":leet", obj) isEqualToString:@"1337"], @"Should be the same string.");
+ STAssertTrue([SOCStringFromStringWithObject(@":five", obj) isEqualToString:@"5000"], @"Should be the same string.");
+ STAssertTrue([SOCStringFromStringWithObject(@":six", obj) isEqualToString:@"(null)"], @"Should be the same string.");
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)testMultiParameterCoding {
+ NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:1337], @"leet",
+ [NSNumber numberWithInt:5000], @"five",
+ nil];
+ STAssertTrue([SOCStringFromStringWithObject(@":leet/:five", obj) isEqualToString:@"1337/5000"], @"Should be the same string.");
+ STAssertTrue([SOCStringFromStringWithObject(@":five/:five", obj) isEqualToString:@"5000/5000"], @"Should be the same string.");
+ STAssertTrue([SOCStringFromStringWithObject(@":five/:five/:five/:five/:five/:five", obj) isEqualToString:@"5000/5000/5000/5000/5000/5000"], @"Should be the same string.");
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)testCollectionOperators {
+ NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:1337], @"leet",
+ [NSNumber numberWithInt:5000], @"five",
+ nil];
+ STAssertTrue([SOCStringFromStringWithObject(@":@count", obj) isEqualToString:@"2"], @"Should be the same string.");
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)testOutboundParameters {
+ SOCPattern* pattern = [SOCPattern patternWithString:@"soc://:ident"];
+ STAssertTrue([pattern stringMatches:@"soc://3"], @"String should conform.");
+ STAssertTrue([pattern stringMatches:@"soc://33413413454353254235245235"], @"String should conform.");
+
+ STAssertFalse([pattern stringMatches:@""], @"String should not conform.");
+ STAssertFalse([pattern stringMatches:@"soc://"], @"String should not conform.");
+
+ STAssertTrue([pattern stringMatches:@"soc://joe"], @"String might conform.");
+
+ pattern = [SOCPattern patternWithString:@"soc://:ident/sandwich"];
+ STAssertTrue([pattern stringMatches:@"soc://3/sandwich"], @"String should conform.");
+ STAssertTrue([pattern stringMatches:@"soc://33413413454353254235245235/sandwich"], @"String should conform.");
+
+ STAssertFalse([pattern stringMatches:@""], @"String should not conform.");
+ STAssertFalse([pattern stringMatches:@"soc://"], @"String should not conform.");
+ STAssertFalse([pattern stringMatches:@"soc:///sandwich"], @"String should not conform.");
+
+ pattern = [SOCPattern patternWithString:@"soc://:ident/sandwich/:catName"];
+ STAssertTrue([pattern stringMatches:@"soc://3/sandwich/dilly"], @"String should conform.");
+ STAssertTrue([pattern stringMatches:@"soc://33413413454353254235245235/sandwich/dilly"], @"String should conform.");
+
+ STAssertFalse([pattern stringMatches:@""], @"String should not conform.");
+ STAssertFalse([pattern stringMatches:@"soc://"], @"String should not conform.");
+ STAssertFalse([pattern stringMatches:@"soc://33413413454353254235245235/sandwich/"], @"String should not conform.");
+ STAssertFalse([pattern stringMatches:@"soc:///sandwich/"], @"String should not conform.");
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)testPerformSelectorOnObjectWithSourceString {
+ SOCPattern* pattern = [SOCPattern patternWithString:@"soc://:ident/:flv/:dv/:llv/:string"];
+ SOCTestObject* testObject = [pattern performSelector:@selector(initWithId:floatValue:doubleValue:longLongValue:stringValue:userInfo:) onObject:[SOCTestObject class] sourceString:@"soc://3/3.5/6.14/13413143124321/dilly"];
+ STAssertEquals(testObject.ident, (NSInteger)3, @"Values should be equal.");
+ STAssertEquals(testObject.flv, (CGFloat)3.5, @"Values should be equal.");
+ STAssertEquals(testObject.dv, 6.14, @"Values should be equal.");
+ STAssertEquals(testObject.llv, (long long)13413143124321, @"Values should be equal.");
+ STAssertTrue([testObject.string isEqualToString:@"dilly"], @"Values should be equal.");
+
+ testObject = [pattern performSelector:@selector(initWithId:floatValue:doubleValue:longLongValue:stringValue:) onObject:[SOCTestObject class] sourceString:@"soc://3/3.5/6.14/13413143124321/dilly"];
+ STAssertEquals(testObject.ident, (NSInteger)3, @"Values should be equal.");
+ STAssertEquals(testObject.flv, (CGFloat)3.5, @"Values should be equal.");
+ STAssertEquals(testObject.dv, 6.14, @"Values should be equal.");
+ STAssertEquals(testObject.llv, (long long)13413143124321, @"Values should be equal.");
+ STAssertTrue([testObject.string isEqualToString:@"dilly"], @"Values should be equal.");
+
+ pattern = [SOCPattern patternWithString:@"soc://:ident"];
+ [pattern performSelector:@selector(setIdent:) onObject:testObject sourceString:@"soc://6"];
+ STAssertEquals(testObject.ident, (NSInteger)6, @"Values should be equal.");
+
+ [pattern performSelector:@selector(setLlv:) onObject:testObject sourceString:@"soc://6"];
+ STAssertEquals(testObject.llv, (long long)6, @"Values should be equal.");
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+- (void)testExtractParameterKeyValuesFromSourceString {
+ SOCPattern* pattern = [SOCPattern patternWithString:@"soc://:ident/:flv/:dv/:llv/:string"];
+ NSDictionary* kvs = [pattern extractParameterKeyValuesFromSourceString:@"soc://3/3.5/6.14/13413143124321/dilly"];
+ STAssertEquals([[kvs objectForKey:@"ident"] intValue], 3, @"Values should be equal.");
+ STAssertEquals([[kvs objectForKey:@"flv"] floatValue], 3.5f, @"Values should be equal.");
+ STAssertEquals([[kvs objectForKey:@"dv"] doubleValue], 6.14, @"Values should be equal.");
+ STAssertEquals([[kvs objectForKey:@"llv"] longLongValue], 13413143124321L, @"Values should be equal.");
+ STAssertTrue([[kvs objectForKey:@"string"] isEqualToString:@"dilly"], @"Values should be equal.");
+}
+
+@end
View
2 Vendor/SOCKit/tests/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+

0 comments on commit e38562f

Please sign in to comment.
Something went wrong with that request. Please try again.