From 15b4d091afd998458b6bca44b5673f23aed086bc Mon Sep 17 00:00:00 2001 From: Vojta Bartos Date: Tue, 10 Feb 2015 14:29:49 +0100 Subject: [PATCH 1/4] Saving regex route matches to route parameters dictionary --- DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m | 49 ++++++++++++++++------ Tests/RouteMatcher/DPLRouteMatcherSpec.m | 36 ++++++++++++++++ 2 files changed, 72 insertions(+), 13 deletions(-) diff --git a/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m b/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m index ac7026d..efc7596 100644 --- a/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m +++ b/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m @@ -2,6 +2,7 @@ #import "DPLDeepLink_Private.h" #import "NSString+DPLTrim.h" +static NSString * const DPLComponentPattern = @":[a-zA-Z0-9-_][^/]+"; static NSString * const DPLRouteParameterPattern = @":[a-zA-Z0-9-_]+"; static NSString * const DPLURLParameterPattern = @"([^/]+)"; @@ -34,27 +35,49 @@ - (instancetype)initWithRoute:(NSString *)route { } +- (NSMutableArray *)routeParamaterNames { + if (!_routeParamaterNames) { + _routeParamaterNames = [NSMutableArray array]; + } + return _routeParamaterNames; +} + - (NSRegularExpression *)regex { if (!_regex) { - _routeParamaterNames = [NSMutableArray array]; + NSString *modifiedRoute = [self.route copy]; + NSRegularExpression *componentRegex = [NSRegularExpression regularExpressionWithPattern:DPLComponentPattern + options:0 + error:nil]; NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern options:0 error:nil]; - - __block NSString *modifiedRoute = [self.route copy]; - NSArray *matches = [parameterRegex matchesInString:self.route - options:0 - range:NSMakeRange(0, self.route.length)]; - + NSArray *matches = [componentRegex matchesInString:self.route + options:0 + range:NSMakeRange(0, self.route.length)]; for (NSTextCheckingResult *result in matches) { + NSString *component = [self.route substringWithRange:result.range]; + NSString *modifiedComponent = [component copy]; + NSArray *componentMatches = [parameterRegex matchesInString:component + options:0 + range:NSMakeRange(0, component.length)]; + NSTextCheckingResult *componentResult = [componentMatches firstObject]; + if (componentResult) { + NSString *stringToReplace = [component substringWithRange:componentResult.range]; + NSString *variableName = [stringToReplace stringByReplacingOccurrencesOfString:@":" + withString:@""]; + + [self.routeParamaterNames addObject:variableName]; + + modifiedComponent = [modifiedComponent stringByReplacingOccurrencesOfString:stringToReplace + withString:@""]; + } - NSString *stringToReplace = [self.route substringWithRange:result.range]; - NSString *variableName = [stringToReplace stringByReplacingOccurrencesOfString:@":" - withString:@""]; - [self.routeParamaterNames addObject:variableName]; + if (modifiedComponent.length == 0) { + modifiedComponent = DPLURLParameterPattern; + } - modifiedRoute = [modifiedRoute stringByReplacingOccurrencesOfString:stringToReplace - withString:DPLURLParameterPattern]; + modifiedRoute = [modifiedRoute stringByReplacingOccurrencesOfString:component + withString:modifiedComponent]; } modifiedRoute = [modifiedRoute stringByAppendingString:@"$"]; diff --git a/Tests/RouteMatcher/DPLRouteMatcherSpec.m b/Tests/RouteMatcher/DPLRouteMatcherSpec.m index a8ef623..48d427e 100644 --- a/Tests/RouteMatcher/DPLRouteMatcherSpec.m +++ b/Tests/RouteMatcher/DPLRouteMatcherSpec.m @@ -16,6 +16,7 @@ NSURL *url = URLWithPath(@"/table/book"); DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; expect(deepLink).toNot.beNil(); + expect(deepLink.routeParameters).to.equal(@{}); }); it(@"returns a deep link when a URL matches a host", ^{ @@ -23,6 +24,7 @@ NSURL *url = URLWithPath(@""); DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; expect(deepLink).toNot.beNil(); + expect(deepLink.routeParameters).to.equal(@{}); }); it(@"does NOT return a deep link when a host does NOT match the URL host", ^{ @@ -108,9 +110,11 @@ NSURL *url2 = URLWithPath(@"/abc123"); DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; expect(deepLink).notTo.beNil(); + expect(deepLink.routeParameters).to.equal(@{}); DPLDeepLink *deepLink2 = [matcher deepLinkWithURL:url2]; expect(deepLink2).notTo.beNil(); + expect(deepLink2.routeParameters).to.equal(@{}); }); it(@"matches URLs with commas", ^{ @@ -120,6 +124,38 @@ expect(deepLink.routeParameters[@"weird_comma_path_thing"]).to.equal(@"33.89,-84.46"); expect(deepLink).notTo.beNil(); }); + + it(@"returns a deep link with route parameters when a URL matches a parameterized regex route", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:table([a-zA-Z]+)/:id([0-9]+)"]; + NSURL *url = URLWithPath(@"/table/randomTableName/109"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).notTo.beNil(); + expect(deepLink.routeParameters).to.equal(@{@"table": @"randomTableName", + @"id": @"109" }); + }); + + it(@"does NOT return a deep link when the URL path does not match regex table parameter", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:table([a-zA-Z]+)/:id([0-9])"]; + NSURL *url = URLWithPath(@"/table/table_name/109"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).to.beNil(); + }); + + it(@"does NOT return a deep link when the URL path does not match regex id parameter", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:table([a-zA-Z]+)/:id([0-9])"]; + NSURL *url = URLWithPath(@"/table/tableName/1a9"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).to.beNil(); + }); + + it(@"matches a wildcard deeplink to route parameters", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:path(.*)"]; + NSURL *url = URLWithPath(@"/table/some/path/which/should/be/in/route/parameters"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).notTo.beNil(); + expect(deepLink.routeParameters).to.equal(@{@"path": @"some/path/which/should/be/in/route/parameters"}); + }); + }); SpecEnd From 7fa57517ef68c94a91d8d32f64ff19fc2a8195df Mon Sep 17 00:00:00 2001 From: wessmith and chrismaddern Date: Tue, 12 May 2015 16:15:24 -0400 Subject: [PATCH 2/4] Refactor named groups support into RegularExpression subclass --- DeepLinkSDK.xcodeproj/project.pbxproj | 28 ++++ DeepLinkSDK/Regex/DPLMatchResult.h | 8 ++ DeepLinkSDK/Regex/DPLMatchResult.m | 5 + DeepLinkSDK/Regex/DPLRegularExpression.h | 12 ++ DeepLinkSDK/Regex/DPLRegularExpression.m | 135 ++++++++++++++++++ DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m | 74 +--------- Rakefile | 2 +- .../DPLRouteHandlerIntegrationTest.m | 11 +- Tests/Regex/DPLRegularExpressionSpec.m | 47 ++++++ Tests/RouteMatcher/DPLRouteMatcherSpec.m | 28 +++- Tests/Router/DPLDeepLinkRouterSpec.m | 2 +- 11 files changed, 271 insertions(+), 81 deletions(-) create mode 100644 DeepLinkSDK/Regex/DPLMatchResult.h create mode 100644 DeepLinkSDK/Regex/DPLMatchResult.m create mode 100644 DeepLinkSDK/Regex/DPLRegularExpression.h create mode 100644 DeepLinkSDK/Regex/DPLRegularExpression.m create mode 100644 Tests/Regex/DPLRegularExpressionSpec.m diff --git a/DeepLinkSDK.xcodeproj/project.pbxproj b/DeepLinkSDK.xcodeproj/project.pbxproj index b335b72..3867d4d 100644 --- a/DeepLinkSDK.xcodeproj/project.pbxproj +++ b/DeepLinkSDK.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1252340A196B4771BE27D5FD /* libPods-ReceiverDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CFE63F7FEA51182807D98A78 /* libPods-ReceiverDemo.a */; }; 2F4988DE1AE71ABC0069EF2B /* DPLRouteHandlerIntegrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4988DD1AE71ABC0069EF2B /* DPLRouteHandlerIntegrationTest.m */; }; + 4D4F412B1B02A96400B710DB /* DPLRegularExpressionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D4F412A1B02A96400B710DB /* DPLRegularExpressionSpec.m */; }; 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; }; 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; @@ -65,6 +66,11 @@ 19367BAF0FEDE5B798128F3D /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2AE3E05821FBC0C05F248E61 /* Pods-Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.debug.xcconfig"; sourceTree = ""; }; 2F4988DD1AE71ABC0069EF2B /* DPLRouteHandlerIntegrationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DPLRouteHandlerIntegrationTest.m; path = IntegrationTests/DPLRouteHandlerIntegrationTest.m; sourceTree = ""; }; + 4D4F41231B0298FF00B710DB /* DPLRegularExpression.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DPLRegularExpression.h; sourceTree = ""; }; + 4D4F41241B0298FF00B710DB /* DPLRegularExpression.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DPLRegularExpression.m; sourceTree = ""; }; + 4D4F41251B029D9E00B710DB /* DPLMatchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DPLMatchResult.h; sourceTree = ""; }; + 4D4F41261B029D9E00B710DB /* DPLMatchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DPLMatchResult.m; sourceTree = ""; }; + 4D4F412A1B02A96400B710DB /* DPLRegularExpressionSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DPLRegularExpressionSpec.m; sourceTree = ""; }; 57D5F02E049D7887B4F4ACDF /* Pods-ReceiverDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReceiverDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReceiverDemo/Pods-ReceiverDemo.debug.xcconfig"; sourceTree = ""; }; 6003F58A195388D20070C39A /* ReceiverDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReceiverDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -220,6 +226,25 @@ name = Pods; sourceTree = ""; }; + 4D4F41221B0298AA00B710DB /* Regex */ = { + isa = PBXGroup; + children = ( + 4D4F41231B0298FF00B710DB /* DPLRegularExpression.h */, + 4D4F41241B0298FF00B710DB /* DPLRegularExpression.m */, + 4D4F41251B029D9E00B710DB /* DPLMatchResult.h */, + 4D4F41261B029D9E00B710DB /* DPLMatchResult.m */, + ); + path = Regex; + sourceTree = ""; + }; + 4D4F41291B02A95800B710DB /* Regex */ = { + isa = PBXGroup; + children = ( + 4D4F412A1B02A96400B710DB /* DPLRegularExpressionSpec.m */, + ); + path = Regex; + sourceTree = ""; + }; 6003F581195388D10070C39A = { isa = PBXGroup; children = ( @@ -405,6 +430,7 @@ DE99EF6A1A3B6CDD00CE3449 /* Protocols */, DE3E61071A3B4485008D6DFC /* Categories */, DEB4EDBB1A49CEA400F31D14 /* Errors */, + 4D4F41221B0298AA00B710DB /* Regex */, DE16E91F1A42882F00077E18 /* Router */, DEAC406F1A5DA7B8004A9095 /* RouteHandler */, DE16E9331A4289D500077E18 /* RouteMatcher */, @@ -481,6 +507,7 @@ children = ( 2F4988DC1AE71A930069EF2B /* IntegrationTests */, DEEBD4A61AAB7928000BCA84 /* Fixtures */, + 4D4F41291B02A95800B710DB /* Regex */, DE16E9221A42883B00077E18 /* Router */, DE16E9361A4289DF00077E18 /* RouteMatcher */, DE058E0E1A3B485500147C04 /* DeepLink */, @@ -806,6 +833,7 @@ files = ( DE16E9281A42883B00077E18 /* DPLDeepLinkRouterSpec.m in Sources */, DE16E9381A4289DF00077E18 /* DPLRouteMatcherSpec.m in Sources */, + 4D4F412B1B02A96400B710DB /* DPLRegularExpressionSpec.m in Sources */, DECB32531A881CA10071C76E /* NSObject_DPLJSONObjectSpec.m in Sources */, DEEBD4A91AAB7946000BCA84 /* DPLSerializableObject.m in Sources */, DE058E101A3B485500147C04 /* DPLDeepLinkSpec.m in Sources */, diff --git a/DeepLinkSDK/Regex/DPLMatchResult.h b/DeepLinkSDK/Regex/DPLMatchResult.h new file mode 100644 index 0000000..c9f39ca --- /dev/null +++ b/DeepLinkSDK/Regex/DPLMatchResult.h @@ -0,0 +1,8 @@ +#import + +@interface DPLMatchResult : NSObject + +@property (nonatomic, assign, getter=isMatch) BOOL match; +@property (nonatomic, strong) NSDictionary *namedProperties; + +@end diff --git a/DeepLinkSDK/Regex/DPLMatchResult.m b/DeepLinkSDK/Regex/DPLMatchResult.m new file mode 100644 index 0000000..a80a8e9 --- /dev/null +++ b/DeepLinkSDK/Regex/DPLMatchResult.m @@ -0,0 +1,5 @@ +#import "DPLMatchResult.h" + +@implementation DPLMatchResult + +@end diff --git a/DeepLinkSDK/Regex/DPLRegularExpression.h b/DeepLinkSDK/Regex/DPLRegularExpression.h new file mode 100644 index 0000000..e2affc2 --- /dev/null +++ b/DeepLinkSDK/Regex/DPLRegularExpression.h @@ -0,0 +1,12 @@ +#import +#import "DPLMatchResult.h" + +@interface DPLRegularExpression : NSRegularExpression + +@property (nonatomic, strong) NSArray *groupNames; + ++ (instancetype)regularExpressionWithPattern:(NSString *)pattern; + +- (DPLMatchResult *)matchResultForString:(NSString *)str; + +@end diff --git a/DeepLinkSDK/Regex/DPLRegularExpression.m b/DeepLinkSDK/Regex/DPLRegularExpression.m new file mode 100644 index 0000000..809e9d2 --- /dev/null +++ b/DeepLinkSDK/Regex/DPLRegularExpression.m @@ -0,0 +1,135 @@ +#import "DPLRegularExpression.h" + +static NSString * const DPLNamedGroupComponentPattern = @":[a-zA-Z0-9-_][^/]+"; +static NSString * const DPLRouteParameterPattern = @":[a-zA-Z0-9-_]+"; +static NSString * const DPLURLParameterPattern = @"([^/]+)"; + +@implementation DPLRegularExpression + ++ (instancetype)regularExpressionWithPattern:(NSString *)pattern { + return [[self alloc] initWithPattern:pattern options:0 error:nil]; +} + + +- (instancetype)initWithPattern:(NSString *)pattern + options:(NSRegularExpressionOptions)options + error:(NSError *__autoreleasing *)error { + + NSString *cleanedPattern = [[self class] stringByRemovingNamedGroupsFromString:pattern]; + + self = [super initWithPattern:cleanedPattern options:options error:error]; + if (self) { + self.groupNames = [[self class] namedGroupsForString:pattern]; + } + return self; +} + + +- (DPLMatchResult *)matchResultForString:(NSString *)str { + NSArray *matches = [self matchesInString:str options:0 range:NSMakeRange(0, str.length)]; + DPLMatchResult *matchResult = [[DPLMatchResult alloc] init]; + + if (!matches.count) { + return matchResult; + } + + matchResult.match = YES; + + // Set route parameters in the routeParameters dictionary + NSMutableDictionary *routeParameters = [NSMutableDictionary dictionary]; + for (NSTextCheckingResult *result in matches) { + // Begin at 1 as first range is the whole match + for (NSInteger i = 1; i < result.numberOfRanges && i <= self.groupNames.count; i++) { + NSString *parameterName = self.groupNames[i - 1]; + NSString *parameterValue = [str substringWithRange:[result rangeAtIndex:i]]; + routeParameters[parameterName] = parameterValue; + } + } + + matchResult.namedProperties = routeParameters; + return matchResult; +} + + +#pragma mark - Named Group Helpers + ++ (NSArray *)namedGroupTokensForString:(NSString *)str { + NSRegularExpression *componentRegex = [NSRegularExpression regularExpressionWithPattern:DPLNamedGroupComponentPattern + options:0 + error:nil]; + NSArray *matches = [componentRegex matchesInString:str + options:0 + range:NSMakeRange(0, str.length)]; + + NSMutableArray *namedGroupTokens = [NSMutableArray array]; + for (NSTextCheckingResult *result in matches) { + NSString *namedGroupToken = [str substringWithRange:result.range]; + [namedGroupTokens addObject:namedGroupToken]; + } + return [NSArray arrayWithArray:namedGroupTokens]; +} + + ++ (NSString *)stringByRemovingNamedGroupsFromString:(NSString *)str { + NSString *modifiedStr = [str copy]; + + NSArray *namedGroupExpressions = [self namedGroupTokensForString:str]; + NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern + options:0 + error:nil]; + // For each of the named group expressions (including name & regex) + for (NSString *namedExpression in namedGroupExpressions) { + NSString *replacementExpression = [namedExpression copy]; + NSTextCheckingResult *foundGroupName = [[parameterRegex matchesInString:namedExpression + options:0 + range:NSMakeRange(0, namedExpression.length)] firstObject]; + // If it's a named group, remove the name + if (foundGroupName) { + NSString *stringToReplace = [namedExpression substringWithRange:foundGroupName.range]; + replacementExpression = [replacementExpression stringByReplacingOccurrencesOfString:stringToReplace + withString:@""]; + } + + // If it was a named group, without regex constraining it, put in default regex + if (replacementExpression.length == 0) { + replacementExpression = DPLURLParameterPattern; + } + + modifiedStr = [modifiedStr stringByReplacingOccurrencesOfString:namedExpression + withString:replacementExpression]; + } + + if (modifiedStr.length && !([modifiedStr characterAtIndex:0] == '/')) { + modifiedStr = [@"^" stringByAppendingString:modifiedStr]; + } + modifiedStr = [modifiedStr stringByAppendingString:@"$"]; + + return modifiedStr; +} + + ++ (NSArray *)namedGroupsForString:(NSString *)str { + NSMutableArray *groupNames = [NSMutableArray array]; + + NSArray *namedGroupExpressions = [self namedGroupTokensForString:str]; + NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern + options:0 + error:nil]; + + for (NSString *namedExpression in namedGroupExpressions) { + NSArray *componentMatches = [parameterRegex matchesInString:namedExpression + options:0 + range:NSMakeRange(0, namedExpression.length)]; + NSTextCheckingResult *foundGroupName = [componentMatches firstObject]; + if (foundGroupName) { + NSString *stringToReplace = [namedExpression substringWithRange:foundGroupName.range]; + NSString *variableName = [stringToReplace stringByReplacingOccurrencesOfString:@":" + withString:@""]; + + [groupNames addObject:variableName]; + } + } + return [NSArray arrayWithArray:groupNames]; +} + +@end diff --git a/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m b/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m index efc7596..7391422 100644 --- a/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m +++ b/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m @@ -1,15 +1,12 @@ #import "DPLRouteMatcher.h" #import "DPLDeepLink_Private.h" #import "NSString+DPLTrim.h" - -static NSString * const DPLComponentPattern = @":[a-zA-Z0-9-_][^/]+"; -static NSString * const DPLRouteParameterPattern = @":[a-zA-Z0-9-_]+"; -static NSString * const DPLURLParameterPattern = @"([^/]+)"; +#import "DPLRegularExpression.h" @interface DPLRouteMatcher () @property (nonatomic, copy) NSString *route; -@property (nonatomic, strong) NSRegularExpression *regex; +@property (nonatomic, strong) DPLRegularExpression *regexMatcher; @property (nonatomic, strong) NSMutableArray *routeParamaterNames; @end @@ -29,6 +26,7 @@ - (instancetype)initWithRoute:(NSString *)route { self = [super init]; if (self) { _route = route; + _regexMatcher = [DPLRegularExpression regularExpressionWithPattern:route]; } return self; @@ -42,78 +40,20 @@ - (NSMutableArray *)routeParamaterNames { return _routeParamaterNames; } -- (NSRegularExpression *)regex { - if (!_regex) { - NSString *modifiedRoute = [self.route copy]; - NSRegularExpression *componentRegex = [NSRegularExpression regularExpressionWithPattern:DPLComponentPattern - options:0 - error:nil]; - NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern - options:0 - error:nil]; - NSArray *matches = [componentRegex matchesInString:self.route - options:0 - range:NSMakeRange(0, self.route.length)]; - for (NSTextCheckingResult *result in matches) { - NSString *component = [self.route substringWithRange:result.range]; - NSString *modifiedComponent = [component copy]; - NSArray *componentMatches = [parameterRegex matchesInString:component - options:0 - range:NSMakeRange(0, component.length)]; - NSTextCheckingResult *componentResult = [componentMatches firstObject]; - if (componentResult) { - NSString *stringToReplace = [component substringWithRange:componentResult.range]; - NSString *variableName = [stringToReplace stringByReplacingOccurrencesOfString:@":" - withString:@""]; - - [self.routeParamaterNames addObject:variableName]; - - modifiedComponent = [modifiedComponent stringByReplacingOccurrencesOfString:stringToReplace - withString:@""]; - } - - if (modifiedComponent.length == 0) { - modifiedComponent = DPLURLParameterPattern; - } - - modifiedRoute = [modifiedRoute stringByReplacingOccurrencesOfString:component - withString:modifiedComponent]; - } - - modifiedRoute = [modifiedRoute stringByAppendingString:@"$"]; - _regex = [NSRegularExpression regularExpressionWithPattern:modifiedRoute - options:0 - error:nil]; - } - - return _regex; -} - - (DPLDeepLink *)deepLinkWithURL:(NSURL *)url { DPLDeepLink *deepLink = [[DPLDeepLink alloc] initWithURL:url]; NSString *deepLinkString = [NSString stringWithFormat:@"%@%@", deepLink.URL.host, deepLink.URL.path]; - NSArray *matches = [self.regex matchesInString:deepLinkString - options:0 - range:NSMakeRange(0, deepLinkString.length)]; - if (!matches.count) { + + DPLMatchResult *matchResult = [self.regexMatcher matchResultForString:deepLinkString]; + if (!matchResult.isMatch) { return nil; } - // Set route parameters in the routeParameters dictionary - NSMutableDictionary *routeParameters = [NSMutableDictionary dictionary]; - for (NSTextCheckingResult *result in matches) { - // Begin at 1 as first range is the whole match - for (int i = 1; i < result.numberOfRanges && i <= self.routeParamaterNames.count; i++) { - NSString *parameterName = self.routeParamaterNames[i - 1]; - NSString *parameterValue = [deepLinkString substringWithRange:[result rangeAtIndex:i]]; - routeParameters[parameterName] = parameterValue; - } - } - deepLink.routeParameters = routeParameters; + deepLink.routeParameters = matchResult.namedProperties; return deepLink; } diff --git a/Rakefile b/Rakefile index c526852..e428350 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ desc "Runs the specs" task :spec do - sh "xcodebuild test -workspace 'DeepLinkSDK.xcworkspace' -scheme 'DeepLinkSDK' -destination 'platform=iOS Simulator,name=iPhone 6,OS=8.1' | xcpretty -c" + sh "xcodebuild test -workspace 'DeepLinkSDK.xcworkspace' -scheme 'ReceiverDemo' -destination 'platform=iOS Simulator,name=iPhone 6,OS=8.1' | xcpretty -c" end desc "Synchronizes Xcode project folder with Xcode groups" diff --git a/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m b/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m index f2ec1b8..4f90378 100644 --- a/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m +++ b/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m @@ -7,11 +7,10 @@ @interface DPLRouteHandlerIntegrationTest : XCTestCase @implementation DPLRouteHandlerIntegrationTest -- (void)testClassBasedRouteHandlerFlow -{ - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"dpl://dpl.com/product/93598?"]]; - [tester waitForViewWithAccessibilityLabel:@"Shiner Oktoberfest"]; - [tester waitForViewWithAccessibilityLabel:@"8.99"]; -} +//- (void)testClassBasedRouteHandlerFlow { +// [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"dpl://dpl.com/product/93598?"]]; +// [tester waitForViewWithAccessibilityLabel:@"Shiner Oktoberfest"]; +// [tester waitForViewWithAccessibilityLabel:@"8.99"]; +//} @end diff --git a/Tests/Regex/DPLRegularExpressionSpec.m b/Tests/Regex/DPLRegularExpressionSpec.m new file mode 100644 index 0000000..f12ab76 --- /dev/null +++ b/Tests/Regex/DPLRegularExpressionSpec.m @@ -0,0 +1,47 @@ +#import "Specta.h" +#import "DPLRegularExpression.h" + +SpecBegin(DPLRegularExpression) + +describe(@"Matching named components", ^{ + + it(@"should match named components without regex", ^{ + DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do/:this/and/:that"]; + + DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"]; + expect(matchResult.match).to.beTruthy(); + expect(matchResult.namedProperties).to.equal(@{ @"do": @"dovalue", + @"this": @"thisvalue", + @"that": @"thatvalue" }); + }); + + it(@"should match named components with regex", ^{ + DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do([a-zA-Z]+)/:this([a-zA-Z]+)/and/:that([a-zA-Z]+)"]; + + DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"]; + expect(matchResult.match).to.beTruthy(); + expect(matchResult.namedProperties).to.equal(@{ @"do": @"dovalue", + @"this": @"thisvalue", + @"that": @"thatvalue" }); + }); + + it(@"should match a mixture of with and without regex", ^{ + DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do/:this([a-zA-Z]+)/and/:that([a-zA-Z]+)"]; + + DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"]; + expect(matchResult.match).to.beTruthy(); + expect(matchResult.namedProperties).to.equal(@{ @"do": @"dovalue", + @"this": @"thisvalue", + @"that": @"thatvalue" }); + }); + + it(@"should NOT match named components with regex that does not match", ^{ + DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do([a-zA-Z]+)/:this([0-9]+)/and/:that([a-zA-Z]+)"]; + + DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"]; + expect(matchResult.match).to.beFalsy(); + }); + +}); + +SpecEnd diff --git a/Tests/RouteMatcher/DPLRouteMatcherSpec.m b/Tests/RouteMatcher/DPLRouteMatcherSpec.m index 48d427e..2df7b7d 100644 --- a/Tests/RouteMatcher/DPLRouteMatcherSpec.m +++ b/Tests/RouteMatcher/DPLRouteMatcherSpec.m @@ -117,6 +117,14 @@ expect(deepLink2.routeParameters).to.equal(@{}); }); + it(@"matches a wildcard deeplink to route parameters", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:path(.*)"]; + NSURL *url = URLWithPath(@"/table/some/path/which/should/be/in/route/parameters"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).notTo.beNil(); + expect(deepLink.routeParameters).to.equal(@{@"path": @"some/path/which/should/be/in/route/parameters"}); + }); + it(@"matches URLs with commas", ^{ DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"TenDay/:weird_comma_path_thing"]; NSURL *url = [NSURL URLWithString:@"twcweather://TenDay/33.89,-84.46?aw_campaign=com.weather.TWC.TWCWidget"]; @@ -133,6 +141,16 @@ expect(deepLink.routeParameters).to.equal(@{@"table": @"randomTableName", @"id": @"109" }); }); + + it(@"allows some named groups to be expressed with regex and not others", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:table([a-zA-Z]+)/[a-z]+/:other([a-z]+)/:thing"]; + NSURL *url = URLWithPath(@"/table/anytable/anychair/another/anything"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).notTo.beNil(); + expect(deepLink.routeParameters).to.equal(@{@"table": @"anytable", + @"other": @"another", + @"thing": @"anything" }); + }); it(@"does NOT return a deep link when the URL path does not match regex table parameter", ^{ DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:table([a-zA-Z]+)/:id([0-9])"]; @@ -148,14 +166,12 @@ expect(deepLink).to.beNil(); }); - it(@"matches a wildcard deeplink to route parameters", ^{ - DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:path(.*)"]; - NSURL *url = URLWithPath(@"/table/some/path/which/should/be/in/route/parameters"); + it(@"does NOT match partial strings", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"me"]; + NSURL *url = URLWithPath(@"home"); DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; - expect(deepLink).notTo.beNil(); - expect(deepLink.routeParameters).to.equal(@{@"path": @"some/path/which/should/be/in/route/parameters"}); + expect(deepLink).to.beNil(); }); - }); SpecEnd diff --git a/Tests/Router/DPLDeepLinkRouterSpec.m b/Tests/Router/DPLDeepLinkRouterSpec.m index b19bfb4..cdc179f 100644 --- a/Tests/Router/DPLDeepLinkRouterSpec.m +++ b/Tests/Router/DPLDeepLinkRouterSpec.m @@ -36,7 +36,7 @@ }); it(@"does NOT register routes that are not strings", ^{ - router[@(0)] = [DPLRouteHandler class]; + router[(id)@(0)] = [DPLRouteHandler class]; expect(router[route]).to.beNil(); }); From 3f929d89bc8ea781b10de0f7fdf5a6ff3248c4dd Mon Sep 17 00:00:00 2001 From: wessmith and chrismaddern Date: Tue, 12 May 2015 18:07:31 -0400 Subject: [PATCH 3/4] Remove unused code. Add init tests for Route matcher --- DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m | 11 ----------- Tests/RouteMatcher/DPLRouteMatcherSpec.m | 12 ++++++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m b/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m index 7391422..f325abe 100644 --- a/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m +++ b/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m @@ -5,9 +5,7 @@ @interface DPLRouteMatcher () -@property (nonatomic, copy) NSString *route; @property (nonatomic, strong) DPLRegularExpression *regexMatcher; -@property (nonatomic, strong) NSMutableArray *routeParamaterNames; @end @@ -25,7 +23,6 @@ - (instancetype)initWithRoute:(NSString *)route { self = [super init]; if (self) { - _route = route; _regexMatcher = [DPLRegularExpression regularExpressionWithPattern:route]; } @@ -33,14 +30,6 @@ - (instancetype)initWithRoute:(NSString *)route { } -- (NSMutableArray *)routeParamaterNames { - if (!_routeParamaterNames) { - _routeParamaterNames = [NSMutableArray array]; - } - return _routeParamaterNames; -} - - - (DPLDeepLink *)deepLinkWithURL:(NSURL *)url { DPLDeepLink *deepLink = [[DPLDeepLink alloc] initWithURL:url]; diff --git a/Tests/RouteMatcher/DPLRouteMatcherSpec.m b/Tests/RouteMatcher/DPLRouteMatcherSpec.m index 2df7b7d..9068f19 100644 --- a/Tests/RouteMatcher/DPLRouteMatcherSpec.m +++ b/Tests/RouteMatcher/DPLRouteMatcherSpec.m @@ -9,6 +9,18 @@ SpecBegin(DPLRouteMatcher) +describe(@"Initialization", ^{ + it(@"creates an instance with a route", ^{ + DPLRouteMatcher *routeMatcher = [DPLRouteMatcher matcherWithRoute:@"/thing/:another"]; + expect(routeMatcher).notTo.beNil(); + }); + + it(@"does not create an instance with no route", ^{ + DPLRouteMatcher *routeMatcher = [DPLRouteMatcher matcherWithRoute:@""]; + expect(routeMatcher).to.beNil(); + }); +}); + describe(@"Matching Routes", ^{ it(@"returns a deep link when a URL matches a route", ^{ From dc588ea4a830772a566d21d2fa4b59aca085554f Mon Sep 17 00:00:00 2001 From: Wes Smith Date: Wed, 13 May 2015 11:33:53 -0400 Subject: [PATCH 4/4] uncomment integration test --- .../IntegrationTests/DPLRouteHandlerIntegrationTest.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m b/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m index 4f90378..403299a 100644 --- a/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m +++ b/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m @@ -7,10 +7,10 @@ @interface DPLRouteHandlerIntegrationTest : XCTestCase @implementation DPLRouteHandlerIntegrationTest -//- (void)testClassBasedRouteHandlerFlow { -// [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"dpl://dpl.com/product/93598?"]]; -// [tester waitForViewWithAccessibilityLabel:@"Shiner Oktoberfest"]; -// [tester waitForViewWithAccessibilityLabel:@"8.99"]; -//} +- (void)testClassBasedRouteHandlerFlow { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"dpl://dpl.com/product/93598?"]]; + [tester waitForViewWithAccessibilityLabel:@"Shiner Oktoberfest"]; + [tester waitForViewWithAccessibilityLabel:@"8.99"]; +} @end