mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-24 04:46:01 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
11
Vendor/SOCKit/AUTHORS
vendored
Normal 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
202
Vendor/SOCKit/LICENSE
vendored
Normal 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
16
Vendor/SOCKit/NOTICE
vendored
Normal 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
25
Vendor/SOCKit/README.mdown
vendored
Executable file → Normal 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
84
Vendor/SOCKit/SOCKit.h
vendored
Executable file → Normal 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
97
Vendor/SOCKit/SOCKit.m
vendored
Executable file → Normal 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
6
Vendor/SOCKit/SOCKit.xcodeproj/project.pbxproj
vendored
Executable file → Normal 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
0
Vendor/SOCKit/SOCKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
vendored
Executable file → Normal file
0
Vendor/SOCKit/tests/SOCKitTests-Info.plist
vendored
Executable file → Normal file
0
Vendor/SOCKit/tests/SOCKitTests-Info.plist
vendored
Executable file → Normal file
32
Vendor/SOCKit/tests/SOCKitTests.m
vendored
Executable file → Normal file
32
Vendor/SOCKit/tests/SOCKitTests.m
vendored
Executable file → Normal 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
0
Vendor/SOCKit/tests/en.lproj/InfoPlist.strings
vendored
Executable file → Normal file
Reference in New Issue
Block a user