RKPathMatcher can now accommodate non-KVM dots that follow parameter keys in the pattern. Where /:filename.json would fail before, we use special escapes like /:filename\.json ... this fixes #349. Thanks @jverkoey for the fix and @coryalder for the catch.

This commit is contained in:
Greg Combs
2011-09-16 15:35:27 -05:00
parent 68afe008f7
commit c49310162e
15 changed files with 457 additions and 63 deletions

View File

@@ -37,11 +37,19 @@
* 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
* evaluated against the object being routed using Key-Value coding and coerced into a string.
* *NOTE* - The pattern matcher fully supports KVM, so /:key1.otherKey normally resolves as it
* would in any other KVM situation, ... otherKey is a sub-key on a the object represented by
* key1. This presents a problem in situations where you might want to build a pattern like
* /:filename.json, where the dot isn't intended as a sub-key on the dynamic "filename", but
* rather it is part of the "json" static string. In these instances, you need to escape the
* dot with two backslashes, like so: /:filename\\.json
* @see RKPathMatcher
*/
- (void)routeClass:(Class)objectClass toResourcePath:(NSString*)resourcePath;
/**
* Register a mapping from an object class to a resource path for a specific HTTP method.
* @see RKPathMatcher
*/
- (void)routeClass:(Class)objectClass toResourcePath:(NSString*)resourcePath forMethod:(RKRequestMethod)method;
@@ -52,9 +60,13 @@
* For example, if your Person model has a string attribute titled "polymorphicResourcePath" that returns
* @"/this/is/the/path", you should configure the route with url escapes 'off', otherwise the router will return
* @"%2Fthis%2Fis%2Fthe%2Fpath".
* @see RKPathMatcher
*/
- (void)routeClass:(Class)objectClass toResourcePath:(NSString*)resourcePath forMethod:(RKRequestMethod)method escapeRoutedPath:(BOOL)addEscapes;
/**
* Returns the resource path to send requests for a given object and HTTP method
*/
- (NSString*)resourcePathForObject:(NSObject*)object method:(RKRequestMethod)method;
@end

View File

@@ -57,10 +57,16 @@
Pattern strings should include encoded parameter keys, delimited by a single colon at the
beginning of the key name.
*NOTE* - Numerous colon-encoded parameter keys can be joined in a long pattern, but each key must be
separated by at least one unmapped character. For instance, /:key1:key2:key3/ is invalid, wheras
*NOTE 1* - Numerous colon-encoded parameter keys can be joined in a long pattern, but each key must be
separated by at least one unmapped character. For instance, /:key1:key2:key3/ is invalid, whereas
/:key1/:key2/:key3/ is acceptable.
*NOTE 2* - The pattern matcher supports KVM, so :key1.otherKey normally resolves as it would in any other KVM
situation, ... otherKey is a sub-key on a the object represented by key1. This presents problems in circumstances where
you might want to build a pattern like /:filename.json, where the dot isn't intended as a sub-key on the filename, but rather
part of the json static string. In these instances, you need to escape the dot with two backslashes, like so:
/:filename\\.json
@param patternString The pattern to use for evaluating, such as /:entityName/:stateID/:chamber/
@param shouldTokenize If YES, any query parameters will be tokenized and inserted into the parsed argument dictionary.
@param arguments A pointer to a dictionary that contains the key/values from the pattern (and parameter) matching.
@@ -73,10 +79,16 @@
matchesPath:tokenizeQueryStrings:parsedArguments: Patterns should include encoded parameter keys,
delimited by a single colon at the beginning of the key name.
*NOTE* - Numerous colon-encoded parameter keys can be joined in a long pattern, but each key must be
separated by at least one unmapped character. For instance, /:key1:key2:key3/ is invalid, wheras
*NOTE 1* - Numerous colon-encoded parameter keys can be joined in a long pattern, but each key must be
separated by at least one unmapped character. For instance, /:key1:key2:key3/ is invalid, whereas
/:key1/:key2/:key3/ is acceptable.
*NOTE 2* - The pattern matcher supports KVM, so :key1.otherKey normally resolves as it would in any other KVM
situation, ... otherKey is a sub-key on a the object represented by key1. This presents problems in circumstances where
you might want to build a pattern like /:filename.json, where the dot isn't intended as a sub-key on the filename, but rather
part of the json static string. In these instances, you need to escape the dot with two backslashes, like so:
/:filename\\.json
@param patternString The pattern to use for evaluating, such as /:entityName/:stateID/:chamber/
@return An instantiated RKPathMatcher with an established pattern.
*/

View File

@@ -115,7 +115,7 @@ NSString *RKEncodeURLString(NSString *unencodedString) {
RKLogWarning(@"The parsed arguments dictionary reference is nil.");
return YES;
}
NSDictionary *extracted = [self.socPattern extractParameterKeyValuesFromSourceString:self.rootPath];
NSDictionary *extracted = [self.socPattern parameterDictionaryFromSourceString:self.rootPath];
if (extracted)
[argumentsCollection addEntriesFromDictionary:[extracted removePercentEscapesFromKeysAndObjects]];
*arguments = argumentsCollection;

View File

@@ -110,7 +110,6 @@
assertThat(interpolatedPath, isNot(equalTo(nil)));
NSString *expectedPath = @"/people/15/Joe%20Bob%20Briggs?password=JUICE%7CBOX%26121";
assertThat(interpolatedPath, is(equalTo(expectedPath)));
}
- (void)itShouldCreatePathsFromInterpolatedObjectsWithoutAddedEscapes {
@@ -121,6 +120,16 @@
assertThat(interpolatedPath, isNot(equalTo(nil)));
NSString *expectedPath = @"/people/15/Joe Bob Briggs?password=JUICE|BOX&121";
assertThat(interpolatedPath, is(equalTo(expectedPath)));
}
- (void)itShouldCreatePathsThatIncludePatternArgumentsFollowedByEscapedNonPatternDots {
NSDictionary *arguments = [NSDictionary dictionaryWithObjectsAndKeys:@"Resources", @"filename", nil];
RKPathMatcher *matcher = [RKPathMatcher matcherWithPattern:@"/directory/:filename\\.json"];
NSString *interpolatedPath = [matcher pathFromObject:arguments];
assertThat(interpolatedPath, isNot(equalTo(nil)));
NSString *expectedPath = @"/directory/Resources.json";
assertThat(interpolatedPath, is(equalTo(expectedPath)));
}
@end

11
Vendor/SOCKit/AUTHORS vendored Normal file
View File

@@ -0,0 +1,11 @@
SOCKit Authors
The following people have contributed to SOCKit:
Greg Combs gh:grgcombs
Jeff Verkoeyen gh:jverkoey @featherless (project founder)
Thanks to everyone who has given feedback and reported bugs as well:
Blake Watters gh:blakewatters

202
Vendor/SOCKit/LICENSE vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

16
Vendor/SOCKit/NOTICE vendored Normal file
View File

@@ -0,0 +1,16 @@
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.
See the AUTHORS file for a list of contributors to SOCKit.

25
Vendor/SOCKit/README.mdown vendored Executable file → Normal file
View File

@@ -26,7 +26,7 @@ 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
you've already invested a ton of energy into Three20's routing tech, but here are a few reasons
why SOCKit is better:
1. *Selectors are not defined in the pattern*. The fact that Three20 requires that you define
@@ -48,16 +48,17 @@ Three20: [map from:[Tweet class] name:@"thread" toURL:@"twitter://tweet/(id)/thr
SOCKit: [map from:[Tweet class] name:@"thread" toURL:@"twitter://tweet/:id/thread"];
```
## Heads up
## Where it's being used
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.
SOCKit is a sibling project to [Nimbus][], a light-weight and 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][]. In fact, both `RKMakePathWithObject` and the underlying `RKPathMatcher` class rely on SOCKit behind the scenes.
[RKMakePathWithObject][]. In fact, both `RKMakePathWithObject` and the underlying `RKPathMatcher`
class rely on SOCKit behind the scenes.
## Add SOCKit to your project
## Adding 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
@@ -76,6 +77,16 @@ the `user` property, and that TwitterUser object has a `username` property. Chec
`: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.
## Learning more
In-depth documentation can be found in the [SOCKit.h][SOCPattern] header file.
## Contributing
If you find a bug in SOCKit please file an issue on the Github [SOCKit issue tracker][]. Even
better: if you have a solution for the bug then fork the project and make a pull request.
[SOCKit issue tracker]: https://github.com/jverkoey/sockit/issues
[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

84
Vendor/SOCKit/SOCKit.h vendored Executable file → Normal file
View File

@@ -28,17 +28,17 @@
* 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):
* Inbound examples (creating strings from objects):
*
* pattern: api.github.com/users/:username/gists
* > [pattern stringFromObject:githubUser];
* > [pattern stringFromObject:[GithubUser userWithUsername:@"jverkoey"]];
* returns: api.github.com/users/jverkoey/gists
*
* pattern: api.github.com/repos/:username/:repo/issues
* > [pattern stringFromObject:githubRepo];
* > [pattern stringFromObject:[GithubRepo repoWithUsername:@"jverkoey" repo:@"sockit"]];
* returns: api.github.com/repos/jverkoey/sockit/issues
*
* Outbound example (turn a string into an object):
* Outbound examples (performing selectors on objects with values from given strings):
*
* pattern: github.com/:username
* > [pattern performSelector:@selector(initWithUsername:) onObject:[GithubUser class] sourceString:@"github.com/jverkoey"];
@@ -55,19 +55,45 @@
* returns: nil because setUsername: does not have a return value. githubUser's username property
* is now @"jverkoey".
*
* Note:
* Note 1: Parameters must be separated by string literals
*
* 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:
* Note 2: When colons aren't seen as parameters
*
* 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.
* is treated as a string literal and not a parameter.
*
* Note 3: Escaping KVC characters
*
* If you need to use KVC characters in SOCKit patterns as literal string tokens and not
* treated with KVC then you must escape the characters using double backslashes. For example,
* @"/:userid.json" would create a pattern that uses KVC to access the json property of the
* username value. In this case, however, we wish to interpret the ".json" portion as a
* static string.
*
* In order to do so we must escape the "." using a double backslash: "\\.". For example:
* @"/:userid\\.json". This makes it possible to create strings of the form @"/3.json".
* This also works with outbound parameters, so that the string @"/3.json" can
* be used with the pattern to invoke a selector with "3" as the first argument rather
* than "3.json".
*
* You can escape the following characters:
* ":" => @"\\:"
* "@" => @"\\@"
* "." => @"\\."
* "\\" => @"\\\\"
*
* Note 4: Allocating new objects with outbound patterns
*
* SOCKit will allocate a new object of a given class if
* performSelector:onObject:sourceString: is provided a selector with "init" as a prefix
* and object is a Class. E.g. [GithubUser class].
*/
@interface SOCPattern : NSObject {
@private
@@ -78,6 +104,8 @@
/**
* Initializes a newly allocated pattern object with the given pattern string.
*
* Designated initializer.
*/
- (id)initWithString:(NSString *)string;
+ (id)patternWithString:(NSString *)string;
@@ -117,9 +145,9 @@
* 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.
* overwritten by later values.
*/
- (NSDictionary *)extractParameterKeyValuesFromSourceString:(NSString *)sourceString;
- (NSDictionary *)parameterDictionaryFromSourceString:(NSString *)sourceString;
/**
* Returns a string with the parameters of this pattern replaced using Key-Value Coding (KVC)
@@ -133,7 +161,11 @@
*
* Collection Operators:
* http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html#//apple_ref/doc/uid/20002176-BAJEAIEE
* @see stringFromObject:withBlock:
*
* @param object The object whose properties will be used to replace the parameters in
* the pattern.
* @returns A string with the pattern parameters replaced by the object property values.
* @see stringFromObject:withBlock:
*/
- (NSString *)stringFromObject:(id)object;
@@ -143,21 +175,27 @@
* on the receiving object, and the result is (optionally) modified or encoded by the block.
*
* For example, consider we have individual object values that need percent escapes added to them,
* while preserving the the slashes, question marks, and ampersands of a typical resource path.
* while preserving the slashes, question marks, and ampersands of a typical resource path.
* Using blocks, this is very succinct:
*
* NSDictionary *person = [NSDictionary dictionaryWithObjectsAndKeys:@"SECRET|KEY",@"password",
* @"Joe Bob Briggs", @"name", nil];
* SOCPattern *soc = [SOCPattern patternWithString:@"/people/:name/:password"];
* NSString *actualPath = [soc stringFromObject:person withBlock:^(NSString *)interpolatedString) {
* return [interpolatedString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
* }
* NSString *expectedPath = @"/people/Joe%20Bob%20Briggs/SECRET%7CKEY";
*
* @param object The object whose properties you want to interpolate
* @param block An optional block (may be nil) that modifies or encodes the interpolated
* property value string. If block is not nil, it must at least the incoming string.
* @returns A string with the interpolated values from the object, using the pre-configured pattern.
* @code
* NSDictionary* person = [NSDictionary dictionaryWithObjectsAndKeys:
* @"SECRET|KEY",@"password",
* @"Joe Bob Briggs", @"name", nil];
* SOCPattern* soc = [SOCPattern patternWithString:@"/people/:name/:password"];
* NSString* actualPath = [soc stringFromObject:person withBlock:^(NSString *)propertyValue) {
* return [propertyValue stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
* }
* NSString* expectedPath = @"/people/Joe%20Bob%20Briggs/SECRET%7CKEY";
* @endcode
*
* @param object The object whose properties will be used to replace the parameters in
* the pattern.
* @param block An optional block (may be nil) that modifies or encodes each
* property value string. The block accepts one parameter - the property
* value as a string - and should return the modified property string.
* @returns A string with the pattern parameters replaced by the block-processed object
* property values.
* @see stringFromObject:
*/
- (NSString *)stringFromObject:(id)object withBlock:(NSString*(^)(NSString*))block;

97
Vendor/SOCKit/SOCKit.m vendored Executable file → Normal file
View File

@@ -30,6 +30,7 @@ typedef enum {
} SOCArgumentType;
SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
NSString* kTemporaryBackslashToken = @"/backslash/";
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -115,9 +116,17 @@ SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
NSCharacterSet* nonParameterCharacterSet = [self nonParameterCharacterSet];
// Turn escaped backslashes into a special backslash token to avoid \\. being interpreted as
// `\` and `\.` rather than `\\` and `.`.
NSString* escapedPatternString = _patternString;
if ([escapedPatternString rangeOfString:@"\\\\"].length > 0) {
escapedPatternString = [escapedPatternString stringByReplacingOccurrencesOfString: @"\\\\"
withString: kTemporaryBackslashToken];
}
// Scan through the string, creating tokens that are either strings or parameters.
// Parameters are prefixed with ":".
NSScanner* scanner = [NSScanner scannerWithString:_patternString];
NSScanner* scanner = [NSScanner scannerWithString:escapedPatternString];
// NSScanner skips whitespace and newlines by default (not ideal!).
[scanner setCharactersToBeSkipped:nil];
@@ -127,8 +136,18 @@ SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
[scanner scanUpToString:@":" intoString:&token];
if ([token length] > 0) {
// Add this static text to the token list.
[tokens addObject:token];
if (![token hasSuffix:@"\\"]) {
// Add this static text to the token list.
[tokens addObject:token];
} else {
// This token is escaping the next colon, so we skip the parameter creation.
[tokens addObject:[token stringByAppendingString:@":"]];
// Skip the colon.
[scanner setScanLocation:[scanner scanLocation] + 1];
continue;
}
}
if (![scanner isAtEnd]) {
@@ -178,6 +197,26 @@ SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
- (NSString *)_stringFromEscapedToken:(NSString *)token {
if ([token rangeOfString:@"\\"].length == 0
&& [token rangeOfString:kTemporaryBackslashToken].length == 0) {
// The common case (faster and creates fewer autoreleased strings).
return token;
} else {
// Escaped characters may exist.
// Create a mutable copy so that we don't excessively create new autoreleased strings.
NSMutableString* mutableToken = [token mutableCopy];
[mutableToken replaceOccurrencesOfString:@"\\." withString:@"." options:0 range:NSMakeRange(0, [mutableToken length])];
[mutableToken replaceOccurrencesOfString:@"\\@" withString:@"@" options:0 range:NSMakeRange(0, [mutableToken length])];
[mutableToken replaceOccurrencesOfString:@"\\:" withString:@":" options:0 range:NSMakeRange(0, [mutableToken length])];
[mutableToken replaceOccurrencesOfString:kTemporaryBackslashToken withString:@"\\" options:0 range:NSMakeRange(0, [mutableToken length])];
return [mutableToken autorelease];
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Public Methods
@@ -198,6 +237,9 @@ SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
for (id token in _tokens) {
if ([token isKindOfClass:[NSString class]]) {
// Replace the escaped characters in the token before we start comparing the string.
token = [self _stringFromEscapedToken:token];
NSInteger tokenLength = [token length];
if (validUpUntil + tokenLength > stringLength) {
// There aren't enough characters in the string to satisfy this token.
@@ -218,7 +260,7 @@ SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
// Look ahead for the next string token match.
if (tokenIndex + 1 < [_tokens count]) {
NSString* nextToken = [_tokens objectAtIndex:tokenIndex + 1];
NSString* nextToken = [self _stringFromEscapedToken:[_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)];
@@ -355,7 +397,7 @@ SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
///////////////////////////////////////////////////////////////////////////////////////////////////
- (NSDictionary *)extractParameterKeyValuesFromSourceString:(NSString *)sourceString {
- (NSDictionary *)parameterDictionaryFromSourceString:(NSString *)sourceString {
NSMutableDictionary* kvs = [[NSMutableDictionary alloc] initWithCapacity:[_parameters count]];
NSArray* values = nil;
@@ -374,25 +416,26 @@ SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
///////////////////////////////////////////////////////////////////////////////////////////////////
- (NSString *)accumulatedStringWithParameterValues:(NSDictionary *)parameterValues {
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 *)_stringWithParameterValues:(NSDictionary *)parameterValues {
NSMutableString* accumulator = [[NSMutableString alloc] initWithCapacity:[_patternString length]];
for (id token in _tokens) {
if ([token isKindOfClass:[NSString class]]) {
[accumulator appendString:[self _stringFromEscapedToken:token]];
} else {
SOCParameter* parameter = token;
[accumulator appendString:[parameterValues objectForKey:parameter.string]];
}
NSString* result = nil;
result = [[accumulator copy] autorelease];
[accumulator release]; accumulator = nil;
return result;
}
NSString* result = nil;
result = [[accumulator copy] autorelease];
[accumulator release]; accumulator = nil;
return result;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
- (NSString *)stringFromObject:(id)object {
if ([_tokens count] == 0) {
@@ -404,9 +447,10 @@ SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
NSString* stringValue = [NSString stringWithFormat:@"%@", [object valueForKeyPath:parameter.string]];
[parameterValues setObject:stringValue forKey:parameter.string];
}
return [self accumulatedStringWithParameterValues:parameterValues];
return [self _stringWithParameterValues:parameterValues];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#if NS_BLOCKS_AVAILABLE
- (NSString *)stringFromObject:(id)object withBlock:(NSString *(^)(NSString*))block {
@@ -416,11 +460,14 @@ SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType);
NSMutableDictionary* parameterValues = [NSMutableDictionary dictionaryWithCapacity:[_parameters count]];
for (SOCParameter* parameter in _parameters) {
NSString* stringValue = [NSString stringWithFormat:@"%@", [object valueForKeyPath:parameter.string]];
if (block)
if (nil != block) {
stringValue = block(stringValue);
[parameterValues setObject:stringValue forKey:parameter.string];
}
if (nil != stringValue) {
[parameterValues setObject:stringValue forKey:parameter.string];
}
}
return [self accumulatedStringWithParameterValues:parameterValues];
return [self _stringWithParameterValues:parameterValues];
}
#endif

6
Vendor/SOCKit/SOCKit.xcodeproj/project.pbxproj vendored Executable file → Normal file
View File

@@ -37,6 +37,9 @@
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>"; };
6612F706141FEAD500B76B8B /* AUTHORS */ = {isa = PBXFileReference; lastKnownFileType = text; path = AUTHORS; sourceTree = "<group>"; };
6612F707141FEAD500B76B8B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
6612F708141FEAD500B76B8B /* NOTICE */ = {isa = PBXFileReference; lastKnownFileType = text; path = NOTICE; 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; };
@@ -72,6 +75,9 @@
isa = PBXGroup;
children = (
661278FA140BD69B00164779 /* README.mdown */,
6612F706141FEAD500B76B8B /* AUTHORS */,
6612F707141FEAD500B76B8B /* LICENSE */,
6612F708141FEAD500B76B8B /* NOTICE */,
667E34CE140BD732002FD733 /* SOCKit.h */,
667E34CF140BD732002FD733 /* SOCKit.m */,
667E34DA140BD776002FD733 /* SOCKitTests.m */,

0
Vendor/SOCKit/SOCKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata generated vendored Executable file → Normal file
View File

0
Vendor/SOCKit/tests/SOCKitTests-Info.plist vendored Executable file → Normal file
View File

32
Vendor/SOCKit/tests/SOCKitTests.m vendored Executable file → Normal file
View File

@@ -116,6 +116,36 @@ NSString *sockitBetterURLEncodeString(NSString *unencodedString);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
- (void)testCharacterEscapes {
NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:1337], @"leet",
[NSNumber numberWithInt:5000], @"five",
nil];
STAssertTrue([SOCStringFromStringWithObject(@".", obj) isEqualToString:@"."], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@"\\.", obj) isEqualToString:@"."], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@":", obj) isEqualToString:@":"], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@"\\:", obj) isEqualToString:@":"], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@"@", obj) isEqualToString:@"@"], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@"\\@", obj) isEqualToString:@"@"], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@":leet\\.value", obj) isEqualToString:@"1337.value"], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@":leet\\:value", obj) isEqualToString:@"1337:value"], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@":leet\\@value", obj) isEqualToString:@"1337@value"], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@":leet\\:\\:value", obj) isEqualToString:@"1337::value"], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@":leet\\:\\:\\.\\@value", obj) isEqualToString:@"1337::.@value"], @"Should be the same string.");
STAssertTrue([SOCStringFromStringWithObject(@"\\\\:leet", obj) isEqualToString:@"\\1337"], @"Should be the same string.");
SOCPattern* pattern = [SOCPattern patternWithString:@"soc://\\:ident"];
STAssertTrue([pattern stringMatches:@"soc://:ident"], @"String should conform.");
pattern = [SOCPattern patternWithString:@"soc://\\\\:ident"];
STAssertTrue([pattern stringMatches:@"soc://\\3"], @"String should conform.");
pattern = [SOCPattern patternWithString:@"soc://:ident\\.json"];
STAssertTrue([pattern stringMatches:@"soc://3.json"], @"String should conform.");
}
///////////////////////////////////////////////////////////////////////////////////////////////////
- (void)testCollectionOperators {
NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys:
@@ -209,7 +239,7 @@ NSString *sockitBetterURLEncodeString(NSString *unencodedString);
///////////////////////////////////////////////////////////////////////////////////////////////////
- (void)testExtractParameterKeyValuesFromSourceString {
SOCPattern* pattern = [SOCPattern patternWithString:@"soc://:ident/:flv/:dv/:llv/:string"];
NSDictionary* kvs = [pattern extractParameterKeyValuesFromSourceString:@"soc://3/3.5/6.14/13413143124321/dilly"];
NSDictionary* kvs = [pattern parameterDictionaryFromSourceString:@"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.");

0
Vendor/SOCKit/tests/en.lproj/InfoPlist.strings vendored Executable file → Normal file
View File