├── .gitignore ├── deps.json ├── SOCKit.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ └── SOCKit iOS.xcscheme └── project.pbxproj ├── tests ├── Info.plist └── SOCKitTests.m ├── NOTICE ├── Info.plist ├── src ├── NimbusSockit.h ├── SOCKit.h └── SOCKit.m ├── README.mdown └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.xcuserdatad 3 | -------------------------------------------------------------------------------- /deps.json: -------------------------------------------------------------------------------- 1 | {"NimbusKit":[],"Frameworks":["Foundation"]} -------------------------------------------------------------------------------- /SOCKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2012 Jeff Verkoeyen 2 | http://NimbusKit.info 3 | For a list of contributors see https://github.com/jverkoey/nimbus/graphs/contributors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | See the AUTHORS and DONORS files for more information about Nimbus 18 | contributors. -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/NimbusSockit.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2011-2014 NimbusKit 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #ifdef NIMBUSKIT_FRAMEWORK 18 | #import 19 | #else 20 | #import "SOCKit.h" 21 | #endif 22 | 23 | #import 24 | 25 | /** 26 | * @defgroup NimbusSockit Nimbus Sockit 27 | * 28 | *
29 | * 30 | */ 31 | -------------------------------------------------------------------------------- /SOCKit.xcodeproj/xcshareddata/xcschemes/SOCKit iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | SOCKit 2 | ====== 3 | 4 | String <-> Object Coding for Objective-C. Rhymes with "socket". 5 | 6 | With SOCKit and [SOCPattern][] you can easily transform objects into strings and vice versa. 7 | 8 | ### Two examples, cuz devs love examples. 9 | 10 | ```obj-c 11 | SOCPattern* pattern = [SOCPattern patternWithString:@"api.github.com/users/:username/gists"]; 12 | [pattern stringFromObject:githubUser]; 13 | > @"api.github.com/users/jverkoey/gists" 14 | ``` 15 | 16 | ```obj-c 17 | SOCPattern* pattern = [SOCPattern patternWithString:@"github.com/:username"]; 18 | [pattern performSelector:@selector(initWithUsername:) onObject:[GithubUser class] sourceString:@"github.com/jverkoey"]; 19 | > username = jverkoey 20 | ``` 21 | 22 | ### Hey, this is really similar to defining routes in Rails. 23 | 24 | Damn straight it is. 25 | 26 | ### And isn't this kind of like Three20's navigator? 27 | 28 | Except hella better. It's also entirely incompatible with Three20 routes. This kinda blows if 29 | you've already invested a ton of energy into Three20's routing tech, but here are a few reasons 30 | why SOCKit is better: 31 | 32 | 1. *Selectors are not defined in the pattern*. The fact that Three20 requires that you define 33 | selectors in the pattern is scary as hell: rename a method in one of your controllers and 34 | your URL routing will silently break. No warnings, just broke. With SOCKit you define the 35 | selectors using @selector notation and SOCKit infers the parameters from the pattern definition. 36 | This way you can depend on the compiler to fire a warning if the selector isn't defined anywhere. 37 | 2. *Parameters are encoded using true KVC*. You now have full access to [KVC collection operators]. 38 | 3. *SOCKit is fully unit tested and documented*. Not much more to be said here. 39 | 40 | Here's a quick breakdown of the differences between Three20 and SOCKit, if SOCKit were used as 41 | the backend for Three20's URL routing. 42 | 43 | ``` 44 | Three20: [map from:@"twitter://tweet/(initWithTweetId:)" toViewController:[TweetController class]]; 45 | SOCKit: [map from:@"twitter://tweet/:id" toViewController:[TweetController class] selector:@selector(initWithTweetId:)]; 46 | 47 | Three20: [map from:[Tweet class] name:@"thread" toURL:@"twitter://tweet/(id)/thread"]; 48 | SOCKit: [map from:[Tweet class] name:@"thread" toURL:@"twitter://tweet/:id/thread"]; 49 | ``` 50 | 51 | ## Where it's being used 52 | 53 | SOCKit is a sibling project to [Nimbus][], a light-weight and modular framework that makes it 54 | easy to blaze a trail with your iOS apps. 55 | 56 | Users of RESTKit will notice that SOCKit provides similar functionality to RESTKit's 57 | [RKMakePathWithObject][]. In fact, both `RKMakePathWithObject` and the underlying `RKPathMatcher` 58 | class rely on SOCKit behind the scenes. 59 | 60 | ## Adding SOCKit to your project 61 | 62 | This lightweight library is built to be a dead-simple airdrop directly into your project. Contained 63 | in SOCKit.h and SOCKit.m is all of the functionality you will need in order to start mapping 64 | Strings <-> Objects. To start using SOCKit, simply download or `git checkout` the SOCKit repo 65 | and drag SOCKit.h and SOCKit.m to your project's source tree. `#import "SOCKit.h"` where you want 66 | to use SOCKit and start pumping out some mad String <-> Object coding. 67 | 68 | ## Some cool things 69 | 70 | When coding objects into strings you define parameters by prefixing the property name with a colon. 71 | So if you have a Tweet object with a `tweetId` property, the pattern parameter name would look like 72 | `:tweetId`. Simple enough. 73 | 74 | But now let's say you have a Tweet object that contains a reference to a TwitterUser object via 75 | the `user` property, and that TwitterUser object has a `username` property. Check this out: 76 | `:user.username`. If this was one of my tweets and I encoded the Tweet object using a SOCKit 77 | pattern the resulting string would be `@"featherless"`. KVC rocks. 78 | 79 | ## Learning more 80 | 81 | In-depth documentation can be found in the [SOCKit.h][SOCPattern] header file. 82 | 83 | ## Contributing 84 | 85 | If you find a bug in SOCKit please file an issue on the Github [SOCKit issue tracker][]. Even 86 | better: if you have a solution for the bug then fork the project and make a pull request. 87 | 88 | [SOCKit issue tracker]: https://github.com/jverkoey/sockit/issues 89 | [SOCPattern]: https://github.com/jverkoey/sockit/blob/master/SOCKit.h 90 | [KVC collection operators]: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html#//apple_ref/doc/uid/20002176-BAJEAIEE 91 | [Nimbus]: http://jverkoey.github.com/nimbus 92 | [RESTKit]: https://github.com/RestKit/RestKit 93 | [RKMakePathWithObject]: https://github.com/RestKit/RestKit/blob/master/Code/Network/RKClient.m#L37 -------------------------------------------------------------------------------- /src/SOCKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2011-2014 NimbusKit 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import 18 | 19 | /** 20 | * String <-> Object Coding. 21 | * 22 | * Code information from strings into objects and vice versa. 23 | * 24 | * A pattern is a string with parameter names prefixed by colons (":"). 25 | * An example of a pattern string with one parameter named :username is: 26 | * api.github.com/users/:username/gists 27 | * 28 | * Patterns, once created, can be used to efficiently turn objects into strings and 29 | * vice versa. Respectively, these techniques are referred to as inbound and outbound. 30 | * 31 | * Inbound examples (creating strings from objects): 32 | * 33 | * pattern: api.github.com/users/:username/gists 34 | * > [pattern stringFromObject:[GithubUser userWithUsername:@"jverkoey"]]; 35 | * returns: api.github.com/users/jverkoey/gists 36 | * 37 | * pattern: api.github.com/repos/:username/:repo/issues 38 | * > [pattern stringFromObject:[GithubRepo repoWithUsername:@"jverkoey" repo:@"sockit"]]; 39 | * returns: api.github.com/repos/jverkoey/sockit/issues 40 | * 41 | * Outbound examples (performing selectors on objects with values from given strings): 42 | * 43 | * pattern: github.com/:username 44 | * > [pattern performSelector:@selector(initWithUsername:) onObject:[GithubUser class] sourceString:@"github.com/jverkoey"]; 45 | * returns: an allocated, initialized, and autoreleased GithubUser object with @"jverkoey" passed 46 | * to the initWithUsername: method. 47 | * 48 | * pattern: github.com/:username/:repo 49 | * > [pattern performSelector:@selector(initWithUsername:repoName:) onObject:[GithubUser class] sourceString:@"github.com/jverkoey/sockit"]; 50 | * returns: an allocated, initialized, and autoreleased GithubUser object with @"jverkoey" and 51 | * @"sockit" passed to the initWithUsername:repoName: method. 52 | * 53 | * pattern: github.com/:username 54 | * > [pattern performSelector:@selector(setUsername:) onObject:githubUser sourceString:@"github.com/jverkoey"]; 55 | * returns: nil because setUsername: does not have a return value. githubUser's username property 56 | * is now @"jverkoey". 57 | * 58 | * Note 1: Parameters must be separated by string literals 59 | * 60 | * Pattern parameters must be separated by some sort of non-parameter character. 61 | * This means that you can't define a pattern like :user:repo. This is because when we 62 | * get around to wanting to decode the string back into an object we need some sort of 63 | * delimiter between the parameters. 64 | * 65 | * Note 2: When colons aren't seen as parameters 66 | * 67 | * If you have colons in your text that aren't followed by a valid parameter name then the 68 | * colon will be treated as static text. This is handy if you're defining a URL pattern. 69 | * For example: @"http://github.com/:user" only has one parameter, :user. The ":" in http:// 70 | * is treated as a string literal and not a parameter. 71 | * 72 | * Note 3: Escaping KVC characters 73 | * 74 | * If you need to use KVC characters in SOCKit patterns as literal string tokens and not 75 | * treated with KVC then you must escape the characters using double backslashes. For example, 76 | * @"/:userid.json" would create a pattern that uses KVC to access the json property of the 77 | * userid value. In this case, however, we wish to interpret the ".json" portion as a 78 | * static string. 79 | * 80 | * In order to do so we must escape the "." using a double backslash: "\\.". For example: 81 | * @"/:userid\\.json". This makes it possible to create strings of the form @"/3.json". 82 | * This also works with outbound parameters, so that the string @"/3.json" can 83 | * be used with the pattern to invoke a selector with "3" as the first argument rather 84 | * than "3.json". 85 | * 86 | * You can escape the following characters: 87 | * ":" => @"\\:" 88 | * "@" => @"\\@" 89 | * "." => @"\\." 90 | * "\\" => @"\\\\" 91 | * 92 | * Note 4: Allocating new objects with outbound patterns 93 | * 94 | * SOCKit will allocate a new object of a given class if 95 | * performSelector:onObject:sourceString: is provided a selector with "init" as a prefix 96 | * and object is a Class. E.g. [GithubUser class]. 97 | * 98 | * @ingroup NimbusSockit 99 | */ 100 | @interface SOCPattern : NSObject { 101 | @private 102 | NSString* _patternString; 103 | NSArray* _tokens; 104 | NSArray* _parameters; 105 | } 106 | 107 | /** 108 | * Initializes a newly allocated pattern object with the given pattern string. 109 | * 110 | * Designated initializer. 111 | */ 112 | - (id)initWithString:(NSString *)string; 113 | + (id)patternWithString:(NSString *)string; 114 | 115 | /** 116 | * Returns YES if the given string can be used with performSelector:onObject:sourceString: or 117 | * extractParameterKeyValuesFromSourceString:. 118 | * 119 | * A matching string must exactly match all of the static portions of the pattern and provide 120 | * values for each of the parameters. 121 | * 122 | * @param string A string that may or may not conform to this pattern. 123 | * @returns YES if the given string conforms to this pattern, NO otherwise. 124 | */ 125 | - (BOOL)stringMatches:(NSString *)string; 126 | 127 | /** 128 | * Performs the given selector on the object with the matching parameter values from sourceString. 129 | * 130 | * @param selector The selector to perform on the object. If there aren't enough 131 | * parameters in the pattern then the excess parameters in the selector 132 | * will be nil. 133 | * @param object The object to perform the selector on. 134 | * @param sourceString A string that conforms to this pattern. The parameter values from 135 | * this string are used as the arguments when performing the selector 136 | * on the object. 137 | * @returns The initialized, autoreleased object if the selector is an initializer 138 | * (prefixed with "init") and object is a Class, otherwise the return value from 139 | * invoking the selector. 140 | */ 141 | - (id)performSelector:(SEL)selector onObject:(id)object sourceString:(NSString *)sourceString; 142 | 143 | /** 144 | * Extracts the matching parameter values from sourceString into an NSDictionary. 145 | * 146 | * @param sourceString A string that conforms to this pattern. The parameter values from 147 | * this string are extracted into the NSDictionary. 148 | * @returns A dictionary of key value pairs. All values will be NSStrings. The keys will 149 | * correspond to the pattern's parameter names. Duplicate key values will be 150 | * overwritten by later values. 151 | */ 152 | - (NSDictionary *)parameterDictionaryFromSourceString:(NSString *)sourceString; 153 | 154 | /** 155 | * Returns a string with the parameters of this pattern replaced using Key-Value Coding (KVC) 156 | * on the receiving object. 157 | * 158 | * Parameters of the pattern are evaluated using valueForKeyPath:. See Apple's KVC documentation 159 | * for more details. 160 | * 161 | * Key-Value Coding Fundamentals: 162 | * http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/BasicPrinciples.html#//apple_ref/doc/uid/20002170-BAJEAIEE 163 | * 164 | * Collection Operators: 165 | * http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html#//apple_ref/doc/uid/20002176-BAJEAIEE 166 | * 167 | * @param object The object whose properties will be used to replace the parameters in 168 | * the pattern. 169 | * @returns A string with the pattern parameters replaced by the object property values. 170 | * @see stringFromObject:withBlock: 171 | */ 172 | - (NSString *)stringFromObject:(id)object; 173 | 174 | /** 175 | * Returns a string with the parameters of this pattern replaced using Key-Value Coding (KVC) 176 | * on the receiving object, and the result is (optionally) modified or encoded by the block. 177 | * 178 | * For example, consider we have individual object values that need percent escapes added to them, 179 | * while preserving the slashes, question marks, and ampersands of a typical resource path. 180 | * Using blocks, this is very succinct: 181 | * 182 | * @code 183 | * NSDictionary* person = [NSDictionary dictionaryWithObjectsAndKeys: 184 | * @"SECRET|KEY",@"password", 185 | * @"Joe Bob Briggs", @"name", nil]; 186 | * SOCPattern* soc = [SOCPattern patternWithString:@"/people/:name/:password"]; 187 | * NSString* actualPath = [soc stringFromObject:person withBlock:^(NSString *)propertyValue) { 188 | * return [propertyValue stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 189 | * } 190 | * NSString* expectedPath = @"/people/Joe%20Bob%20Briggs/SECRET%7CKEY"; 191 | * @endcode 192 | * 193 | * @param object The object whose properties will be used to replace the parameters in 194 | * the pattern. 195 | * @param block An optional block (may be nil) that modifies or encodes each 196 | * property value string. The block accepts one parameter - the property 197 | * value as a string - and should return the modified property string. 198 | * @returns A string with the pattern parameters replaced by the block-processed object 199 | * property values. 200 | * @see stringFromObject: 201 | */ 202 | - (NSString *)stringFromObject:(id)object withBlock:(NSString*(^)(NSString*))block; 203 | 204 | @end 205 | 206 | /** 207 | * A convenience method for: 208 | * 209 | * SOCPattern* pattern = [SOCPattern patternWithString:string]; 210 | * NSString* result = [pattern stringFromObject:object]; 211 | * 212 | * @see documentation for stringFromObject: 213 | */ 214 | NSString* SOCStringFromStringWithObject(NSString* string, id object); 215 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /tests/SOCKitTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2011-2014 NimbusKit 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import 18 | #import 19 | 20 | #import "SOCKit.h" 21 | 22 | typedef void (^SimpleBlock)(void); 23 | NSString *sockitBetterURLEncodeString(NSString *unencodedString); 24 | 25 | @interface SOCTestObject : NSObject 26 | 27 | - (id)initWithId:(NSInteger)ident floatValue:(CGFloat)flv doubleValue:(double)dv longLongValue:(long long)llv stringValue:(NSString *)string; 28 | - (id)initWithId:(NSInteger)ident floatValue:(CGFloat)flv doubleValue:(double)dv longLongValue:(long long)llv stringValue:(NSString *)string userInfo:(id)userInfo; 29 | 30 | @property (nonatomic, assign) NSInteger ident; 31 | @property (nonatomic, assign) CGFloat flv; 32 | @property (nonatomic, assign) double dv; 33 | @property (nonatomic, assign) long long llv; 34 | @property (nonatomic, copy) NSString* string; 35 | @end 36 | 37 | @implementation SOCTestObject 38 | 39 | - (id)initWithId:(NSInteger)anIdent floatValue:(CGFloat)anFlv doubleValue:(double)aDv longLongValue:(long long)anLlv stringValue:(NSString *)aString { 40 | if ((self = [super init])) { 41 | self.ident = anIdent; 42 | self.flv = anFlv; 43 | self.dv = aDv; 44 | self.llv = anLlv; 45 | self.string = aString; 46 | } 47 | return self; 48 | } 49 | 50 | - (id)initWithId:(NSInteger)anIdent floatValue:(CGFloat)anFlv doubleValue:(double)aDv longLongValue:(long long)anLlv stringValue:(NSString *)aString userInfo:(id)userInfo { 51 | return [self initWithId:anIdent floatValue:anFlv doubleValue:aDv longLongValue:anLlv stringValue:aString]; 52 | } 53 | 54 | @end 55 | 56 | @interface SOCKitTests : XCTestCase 57 | @end 58 | 59 | @implementation SOCKitTests 60 | 61 | - (void)testEmptyCases { 62 | XCTAssertTrue([SOCStringFromStringWithObject(nil, nil) isEqualToString:@""], @"Should be the same string."); 63 | 64 | XCTAssertTrue([SOCStringFromStringWithObject(@"", nil) isEqualToString:@""], @"Should be the same string."); 65 | XCTAssertTrue([SOCStringFromStringWithObject(@" ", nil) isEqualToString:@" "], @"Should be the same string."); 66 | 67 | XCTAssertTrue([SOCStringFromStringWithObject(@"abcdef", nil) isEqualToString:@"abcdef"], @"Should be the same string."); 68 | XCTAssertTrue([SOCStringFromStringWithObject(@"abcdef", [NSArray array]) isEqualToString:@"abcdef"], @"Should be the same string."); 69 | } 70 | 71 | - (void)testFailureCases { 72 | XCTAssertThrows([SOCPattern patternWithString:@":dilly:isacat"], @"Parameters must be separated by strings."); 73 | } 74 | 75 | - (void)testSingleParameterCoding { 76 | NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys: 77 | [NSNumber numberWithInt:1337], @"leet", 78 | [NSNumber numberWithInt:5000], @"five", 79 | nil]; 80 | XCTAssertTrue([SOCStringFromStringWithObject(@":leet", obj) isEqualToString:@"1337"], @"Should be the same string."); 81 | XCTAssertTrue([SOCStringFromStringWithObject(@":five", obj) isEqualToString:@"5000"], @"Should be the same string."); 82 | XCTAssertTrue([SOCStringFromStringWithObject(@":six", obj) isEqualToString:@"(null)"], @"Should be the same string."); 83 | } 84 | 85 | - (void)testMultiParameterCoding { 86 | NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys: 87 | [NSNumber numberWithInt:1337], @"leet", 88 | [NSNumber numberWithInt:5000], @"five", 89 | nil]; 90 | XCTAssertTrue([SOCStringFromStringWithObject(@":leet/:five", obj) isEqualToString:@"1337/5000"], @"Should be the same string."); 91 | XCTAssertTrue([SOCStringFromStringWithObject(@":five/:five", obj) isEqualToString:@"5000/5000"], @"Should be the same string."); 92 | XCTAssertTrue([SOCStringFromStringWithObject(@":five/:five/:five/:five/:five/:five", obj) isEqualToString:@"5000/5000/5000/5000/5000/5000"], @"Should be the same string."); 93 | } 94 | 95 | - (void)testCharacterEscapes { 96 | NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys: 97 | [NSNumber numberWithInt:1337], @"leet", 98 | [NSNumber numberWithInt:5000], @"five", 99 | nil]; 100 | 101 | XCTAssertTrue([SOCStringFromStringWithObject(@".", obj) isEqualToString:@"."], @"Should be the same string."); 102 | XCTAssertTrue([SOCStringFromStringWithObject(@"\\.", obj) isEqualToString:@"."], @"Should be the same string."); 103 | XCTAssertTrue([SOCStringFromStringWithObject(@":", obj) isEqualToString:@":"], @"Should be the same string."); 104 | XCTAssertTrue([SOCStringFromStringWithObject(@"\\:", obj) isEqualToString:@":"], @"Should be the same string."); 105 | XCTAssertTrue([SOCStringFromStringWithObject(@"@", obj) isEqualToString:@"@"], @"Should be the same string."); 106 | XCTAssertTrue([SOCStringFromStringWithObject(@"\\@", obj) isEqualToString:@"@"], @"Should be the same string."); 107 | 108 | XCTAssertTrue([SOCStringFromStringWithObject(@":leet\\.value", obj) isEqualToString:@"1337.value"], @"Should be the same string."); 109 | XCTAssertTrue([SOCStringFromStringWithObject(@":leet\\:value", obj) isEqualToString:@"1337:value"], @"Should be the same string."); 110 | XCTAssertTrue([SOCStringFromStringWithObject(@":leet\\@value", obj) isEqualToString:@"1337@value"], @"Should be the same string."); 111 | XCTAssertTrue([SOCStringFromStringWithObject(@":leet\\:\\:value", obj) isEqualToString:@"1337::value"], @"Should be the same string."); 112 | XCTAssertTrue([SOCStringFromStringWithObject(@":leet\\:\\:\\.\\@value", obj) isEqualToString:@"1337::.@value"], @"Should be the same string."); 113 | XCTAssertTrue([SOCStringFromStringWithObject(@"\\\\:leet", obj) isEqualToString:@"\\1337"], @"Should be the same string."); 114 | 115 | SOCPattern* pattern = [SOCPattern patternWithString:@"soc://\\:ident"]; 116 | XCTAssertTrue([pattern stringMatches:@"soc://:ident"], @"String should conform."); 117 | pattern = [SOCPattern patternWithString:@"soc://\\\\:ident"]; 118 | XCTAssertTrue([pattern stringMatches:@"soc://\\3"], @"String should conform."); 119 | pattern = [SOCPattern patternWithString:@"soc://:ident\\.json"]; 120 | XCTAssertTrue([pattern stringMatches:@"soc://3.json"], @"String should conform."); 121 | } 122 | 123 | - (void)testCollectionOperators { 124 | NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys: 125 | [NSNumber numberWithInt:1337], @"leet", 126 | [NSNumber numberWithInt:5000], @"five", 127 | nil]; 128 | XCTAssertTrue([SOCStringFromStringWithObject(@":@count", obj) isEqualToString:@"2"], @"Should be the same string."); 129 | } 130 | 131 | - (void)testBlockTransformations { 132 | NSDictionary *obj = [NSDictionary dictionaryWithObjectsAndKeys: 133 | @"JUICE|BOX&121", @"password", @"Joe Bob Briggs", @"name", [NSNumber numberWithInt:15], @"group", nil]; 134 | SOCPattern *pattern = [SOCPattern patternWithString:@"/people/:group/:name?password=:password"]; 135 | NSString *expectedString = @"/people/15/Joe Bob Briggs?password=JUICE|BOX&121"; 136 | NSString *actualString = [pattern stringFromObject:obj withBlock:nil]; 137 | XCTAssertTrue([actualString isEqualToString:expectedString], @"Should be the same string (testing nil block parameter)."); 138 | XCTAssertTrue([actualString isEqualToString:[pattern stringFromObject:obj]], @"Should be the same string (testing nil block parameter)."); 139 | actualString = [pattern stringFromObject:obj withBlock:^NSString *(NSString* interpolatedValue) { 140 | return @"Test Message"; 141 | }]; 142 | expectedString = @"/people/Test Message/Test Message?password=Test Message"; 143 | XCTAssertTrue([actualString isEqualToString:expectedString], @"Should be the same string (testing nil block parameter)."); 144 | XCTAssertFalse([actualString isEqualToString:[pattern stringFromObject:obj]], @"Should not be the same string, one was transformed with a block."); 145 | actualString = [pattern stringFromObject:obj withBlock:^NSString *(NSString* interpolatedValue) { 146 | return sockitBetterURLEncodeString(interpolatedValue); 147 | }]; 148 | expectedString = @"/people/15/Joe%20Bob%20Briggs?password=JUICE%7CBOX%26121"; 149 | XCTAssertTrue([actualString isEqualToString:expectedString], @"Should be the same string (testing nil block parameter)."); 150 | XCTAssertFalse([actualString isEqualToString:[pattern stringFromObject:obj]], @"Should not be the same string, one was transformed with a block."); 151 | } 152 | 153 | - (void)testOutboundParameters { 154 | SOCPattern* pattern = [SOCPattern patternWithString:@"soc://:ident"]; 155 | XCTAssertTrue([pattern stringMatches:@"soc://3"], @"String should conform."); 156 | XCTAssertTrue([pattern stringMatches:@"soc://33413413454353254235245235"], @"String should conform."); 157 | 158 | XCTAssertFalse([pattern stringMatches:@""], @"String should not conform."); 159 | XCTAssertFalse([pattern stringMatches:@"soc://"], @"String should not conform."); 160 | 161 | XCTAssertTrue([pattern stringMatches:@"soc://joe"], @"String might conform."); 162 | 163 | pattern = [SOCPattern patternWithString:@"soc://:ident/sandwich"]; 164 | XCTAssertTrue([pattern stringMatches:@"soc://3/sandwich"], @"String should conform."); 165 | XCTAssertTrue([pattern stringMatches:@"soc://33413413454353254235245235/sandwich"], @"String should conform."); 166 | 167 | XCTAssertFalse([pattern stringMatches:@""], @"String should not conform."); 168 | XCTAssertFalse([pattern stringMatches:@"soc://"], @"String should not conform."); 169 | XCTAssertFalse([pattern stringMatches:@"soc:///sandwich"], @"String should not conform."); 170 | 171 | pattern = [SOCPattern patternWithString:@"soc://:ident/sandwich/:catName"]; 172 | XCTAssertTrue([pattern stringMatches:@"soc://3/sandwich/dilly"], @"String should conform."); 173 | XCTAssertTrue([pattern stringMatches:@"soc://33413413454353254235245235/sandwich/dilly"], @"String should conform."); 174 | 175 | XCTAssertFalse([pattern stringMatches:@""], @"String should not conform."); 176 | XCTAssertFalse([pattern stringMatches:@"soc://"], @"String should not conform."); 177 | XCTAssertFalse([pattern stringMatches:@"soc://33413413454353254235245235/sandwich/"], @"String should not conform."); 178 | XCTAssertFalse([pattern stringMatches:@"soc:///sandwich/"], @"String should not conform."); 179 | } 180 | 181 | - (void)testPerformSelectorOnObjectWithSourceString { 182 | SOCPattern* pattern = [SOCPattern patternWithString:@"soc://:ident/:flv/:dv/:llv/:string"]; 183 | SOCTestObject* testObject = [pattern performSelector:@selector(initWithId:floatValue:doubleValue:longLongValue:stringValue:userInfo:) onObject:[SOCTestObject class] sourceString:@"soc://3/3.5/6.14/13413143124321/dilly"]; 184 | XCTAssertEqual(testObject.ident, (NSInteger)3, @"Values should be equal."); 185 | XCTAssertEqual(testObject.flv, (CGFloat)3.5, @"Values should be equal."); 186 | XCTAssertEqual(testObject.dv, 6.14, @"Values should be equal."); 187 | XCTAssertEqual(testObject.llv, (long long)13413143124321, @"Values should be equal."); 188 | XCTAssertTrue([testObject.string isEqualToString:@"dilly"], @"Values should be equal."); 189 | 190 | testObject = [pattern performSelector:@selector(initWithId:floatValue:doubleValue:longLongValue:stringValue:) onObject:[SOCTestObject class] sourceString:@"soc://3/3.5/6.14/13413143124321/dilly"]; 191 | XCTAssertEqual(testObject.ident, (NSInteger)3, @"Values should be equal."); 192 | XCTAssertEqual(testObject.flv, (CGFloat)3.5, @"Values should be equal."); 193 | XCTAssertEqual(testObject.dv, 6.14, @"Values should be equal."); 194 | XCTAssertEqual(testObject.llv, (long long)13413143124321, @"Values should be equal."); 195 | XCTAssertTrue([testObject.string isEqualToString:@"dilly"], @"Values should be equal."); 196 | 197 | pattern = [SOCPattern patternWithString:@"soc://:ident"]; 198 | [pattern performSelector:@selector(setIdent:) onObject:testObject sourceString:@"soc://6"]; 199 | XCTAssertEqual(testObject.ident, (NSInteger)6, @"Values should be equal."); 200 | 201 | [pattern performSelector:@selector(setLlv:) onObject:testObject sourceString:@"soc://6"]; 202 | XCTAssertEqual(testObject.llv, (long long)6, @"Values should be equal."); 203 | } 204 | 205 | - (void)testExtractParameterKeyValuesFromSourceString { 206 | SOCPattern* pattern = [SOCPattern patternWithString:@"soc://:ident/:flv/:dv/:llv/:string"]; 207 | NSDictionary* kvs = [pattern parameterDictionaryFromSourceString:@"soc://3/3.5/6.14/13413143124321/dilly"]; 208 | XCTAssertEqual([[kvs objectForKey:@"ident"] intValue], 3, @"Values should be equal."); 209 | XCTAssertEqual([[kvs objectForKey:@"flv"] floatValue], 3.5f, @"Values should be equal."); 210 | XCTAssertEqual([[kvs objectForKey:@"dv"] doubleValue], 6.14, @"Values should be equal."); 211 | XCTAssertEqual([[kvs objectForKey:@"llv"] longLongValue], 13413143124321L, @"Values should be equal."); 212 | XCTAssertTrue([[kvs objectForKey:@"string"] isEqualToString:@"dilly"], @"Values should be equal."); 213 | } 214 | 215 | @end 216 | 217 | // NSString's stringByAddingPercentEscapes doesn't do a complete job (it ignores "/?&", among others) 218 | NSString *sockitBetterURLEncodeString(NSString *unencodedString) { 219 | NSString * encodedString = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes( NULL, (CFStringRef)unencodedString, NULL, 220 | (CFStringRef)@"!*'();:@&=+$,/?%#[]", NSASCIIStringEncoding )); 221 | return encodedString; 222 | } 223 | 224 | -------------------------------------------------------------------------------- /SOCKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FE0935DD1E8031930092BF67 /* NimbusSockit.h in Headers */ = {isa = PBXBuildFile; fileRef = FE0935D81E8031930092BF67 /* NimbusSockit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | FE0935DE1E8031930092BF67 /* SOCKit.h in Headers */ = {isa = PBXBuildFile; fileRef = FE0935D91E8031930092BF67 /* SOCKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | FE0935DF1E8031930092BF67 /* SOCKit.m in Sources */ = {isa = PBXBuildFile; fileRef = FE0935DA1E8031930092BF67 /* SOCKit.m */; }; 13 | FE0935EA1E8031F10092BF67 /* SOCKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE0935CC1E8030B60092BF67 /* SOCKit.framework */; }; 14 | FE0935F11E80325C0092BF67 /* SOCKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FE0935DC1E8031930092BF67 /* SOCKitTests.m */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | FE0935EB1E8031F10092BF67 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = FE0935C31E8030B60092BF67 /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = FE0935CB1E8030B60092BF67; 23 | remoteInfo = "SOCKit iOS"; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | FE0935CC1E8030B60092BF67 /* SOCKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SOCKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | FE0935D81E8031930092BF67 /* NimbusSockit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NimbusSockit.h; sourceTree = ""; }; 30 | FE0935D91E8031930092BF67 /* SOCKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOCKit.h; sourceTree = ""; }; 31 | FE0935DA1E8031930092BF67 /* SOCKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOCKit.m; sourceTree = ""; }; 32 | FE0935DC1E8031930092BF67 /* SOCKitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOCKitTests.m; sourceTree = ""; }; 33 | FE0935E51E8031F10092BF67 /* SOCKit.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SOCKit.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | FE0935C81E8030B60092BF67 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | FE0935E21E8031F10092BF67 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | FE0935EA1E8031F10092BF67 /* SOCKit.framework in Frameworks */, 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXFrameworksBuildPhase section */ 53 | 54 | /* Begin PBXGroup section */ 55 | FE0935C21E8030B60092BF67 = { 56 | isa = PBXGroup; 57 | children = ( 58 | FE0935D71E8031930092BF67 /* src */, 59 | FE0935DB1E8031930092BF67 /* tests */, 60 | FE0935CD1E8030B60092BF67 /* Products */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | FE0935CD1E8030B60092BF67 /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | FE0935CC1E8030B60092BF67 /* SOCKit.framework */, 68 | FE0935E51E8031F10092BF67 /* SOCKit.xctest */, 69 | ); 70 | name = Products; 71 | sourceTree = ""; 72 | }; 73 | FE0935D71E8031930092BF67 /* src */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | FE0935D81E8031930092BF67 /* NimbusSockit.h */, 77 | FE0935D91E8031930092BF67 /* SOCKit.h */, 78 | FE0935DA1E8031930092BF67 /* SOCKit.m */, 79 | ); 80 | path = src; 81 | sourceTree = ""; 82 | }; 83 | FE0935DB1E8031930092BF67 /* tests */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | FE0935DC1E8031930092BF67 /* SOCKitTests.m */, 87 | ); 88 | path = tests; 89 | sourceTree = ""; 90 | }; 91 | /* End PBXGroup section */ 92 | 93 | /* Begin PBXHeadersBuildPhase section */ 94 | FE0935C91E8030B60092BF67 /* Headers */ = { 95 | isa = PBXHeadersBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | FE0935DD1E8031930092BF67 /* NimbusSockit.h in Headers */, 99 | FE0935DE1E8031930092BF67 /* SOCKit.h in Headers */, 100 | ); 101 | runOnlyForDeploymentPostprocessing = 0; 102 | }; 103 | /* End PBXHeadersBuildPhase section */ 104 | 105 | /* Begin PBXNativeTarget section */ 106 | FE0935CB1E8030B60092BF67 /* SOCKit iOS */ = { 107 | isa = PBXNativeTarget; 108 | buildConfigurationList = FE0935D41E8030B60092BF67 /* Build configuration list for PBXNativeTarget "SOCKit iOS" */; 109 | buildPhases = ( 110 | FE0935C71E8030B60092BF67 /* Sources */, 111 | FE0935C81E8030B60092BF67 /* Frameworks */, 112 | FE0935C91E8030B60092BF67 /* Headers */, 113 | FE0935CA1E8030B60092BF67 /* Resources */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = "SOCKit iOS"; 120 | productName = SOCKit; 121 | productReference = FE0935CC1E8030B60092BF67 /* SOCKit.framework */; 122 | productType = "com.apple.product-type.framework"; 123 | }; 124 | FE0935E41E8031F10092BF67 /* SOCKit iOS Tests */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = FE0935ED1E8031F10092BF67 /* Build configuration list for PBXNativeTarget "SOCKit iOS Tests" */; 127 | buildPhases = ( 128 | FE0935E11E8031F10092BF67 /* Sources */, 129 | FE0935E21E8031F10092BF67 /* Frameworks */, 130 | FE0935E31E8031F10092BF67 /* Resources */, 131 | ); 132 | buildRules = ( 133 | ); 134 | dependencies = ( 135 | FE0935EC1E8031F10092BF67 /* PBXTargetDependency */, 136 | ); 137 | name = "SOCKit iOS Tests"; 138 | productName = "SOCKit iOS Tests"; 139 | productReference = FE0935E51E8031F10092BF67 /* SOCKit.xctest */; 140 | productType = "com.apple.product-type.bundle.unit-test"; 141 | }; 142 | /* End PBXNativeTarget section */ 143 | 144 | /* Begin PBXProject section */ 145 | FE0935C31E8030B60092BF67 /* Project object */ = { 146 | isa = PBXProject; 147 | attributes = { 148 | LastUpgradeCheck = 0820; 149 | ORGANIZATIONNAME = NimbusKit; 150 | TargetAttributes = { 151 | FE0935CB1E8030B60092BF67 = { 152 | CreatedOnToolsVersion = 8.2.1; 153 | ProvisioningStyle = Automatic; 154 | }; 155 | FE0935E41E8031F10092BF67 = { 156 | CreatedOnToolsVersion = 8.2.1; 157 | ProvisioningStyle = Automatic; 158 | }; 159 | }; 160 | }; 161 | buildConfigurationList = FE0935C61E8030B60092BF67 /* Build configuration list for PBXProject "SOCKit" */; 162 | compatibilityVersion = "Xcode 3.2"; 163 | developmentRegion = English; 164 | hasScannedForEncodings = 0; 165 | knownRegions = ( 166 | en, 167 | ); 168 | mainGroup = FE0935C21E8030B60092BF67; 169 | productRefGroup = FE0935CD1E8030B60092BF67 /* Products */; 170 | projectDirPath = ""; 171 | projectRoot = ""; 172 | targets = ( 173 | FE0935CB1E8030B60092BF67 /* SOCKit iOS */, 174 | FE0935E41E8031F10092BF67 /* SOCKit iOS Tests */, 175 | ); 176 | }; 177 | /* End PBXProject section */ 178 | 179 | /* Begin PBXResourcesBuildPhase section */ 180 | FE0935CA1E8030B60092BF67 /* Resources */ = { 181 | isa = PBXResourcesBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | ); 185 | runOnlyForDeploymentPostprocessing = 0; 186 | }; 187 | FE0935E31E8031F10092BF67 /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXResourcesBuildPhase section */ 195 | 196 | /* Begin PBXSourcesBuildPhase section */ 197 | FE0935C71E8030B60092BF67 /* Sources */ = { 198 | isa = PBXSourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | FE0935DF1E8031930092BF67 /* SOCKit.m in Sources */, 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | FE0935E11E8031F10092BF67 /* Sources */ = { 206 | isa = PBXSourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | FE0935F11E80325C0092BF67 /* SOCKitTests.m in Sources */, 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | /* End PBXSourcesBuildPhase section */ 214 | 215 | /* Begin PBXTargetDependency section */ 216 | FE0935EC1E8031F10092BF67 /* PBXTargetDependency */ = { 217 | isa = PBXTargetDependency; 218 | target = FE0935CB1E8030B60092BF67 /* SOCKit iOS */; 219 | targetProxy = FE0935EB1E8031F10092BF67 /* PBXContainerItemProxy */; 220 | }; 221 | /* End PBXTargetDependency section */ 222 | 223 | /* Begin XCBuildConfiguration section */ 224 | FE0935D21E8030B60092BF67 /* Debug */ = { 225 | isa = XCBuildConfiguration; 226 | buildSettings = { 227 | ALWAYS_SEARCH_USER_PATHS = NO; 228 | CLANG_ANALYZER_NONNULL = YES; 229 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 230 | CLANG_CXX_LIBRARY = "libc++"; 231 | CLANG_ENABLE_MODULES = YES; 232 | CLANG_ENABLE_OBJC_ARC = NO; 233 | CLANG_WARN_BOOL_CONVERSION = YES; 234 | CLANG_WARN_CONSTANT_CONVERSION = YES; 235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 236 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INFINITE_RECURSION = YES; 240 | CLANG_WARN_INT_CONVERSION = YES; 241 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 242 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 243 | CLANG_WARN_UNREACHABLE_CODE = YES; 244 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 245 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 246 | COPY_PHASE_STRIP = NO; 247 | CURRENT_PROJECT_VERSION = 1; 248 | DEBUG_INFORMATION_FORMAT = dwarf; 249 | ENABLE_STRICT_OBJC_MSGSEND = YES; 250 | ENABLE_TESTABILITY = YES; 251 | GCC_C_LANGUAGE_STANDARD = gnu99; 252 | GCC_DYNAMIC_NO_PIC = NO; 253 | GCC_NO_COMMON_BLOCKS = YES; 254 | GCC_OPTIMIZATION_LEVEL = 0; 255 | GCC_PREPROCESSOR_DEFINITIONS = ( 256 | "DEBUG=1", 257 | "$(inherited)", 258 | ); 259 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 260 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 261 | GCC_WARN_UNDECLARED_SELECTOR = YES; 262 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 263 | GCC_WARN_UNUSED_FUNCTION = YES; 264 | GCC_WARN_UNUSED_VARIABLE = YES; 265 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 266 | MTL_ENABLE_DEBUG_INFO = YES; 267 | ONLY_ACTIVE_ARCH = YES; 268 | PRODUCT_NAME = SOCKit; 269 | SDKROOT = iphoneos; 270 | TARGETED_DEVICE_FAMILY = "1,2"; 271 | VERSIONING_SYSTEM = "apple-generic"; 272 | VERSION_INFO_PREFIX = ""; 273 | }; 274 | name = Debug; 275 | }; 276 | FE0935D31E8030B60092BF67 /* Release */ = { 277 | isa = XCBuildConfiguration; 278 | buildSettings = { 279 | ALWAYS_SEARCH_USER_PATHS = NO; 280 | CLANG_ANALYZER_NONNULL = YES; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = NO; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_CONSTANT_CONVERSION = YES; 287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 288 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 289 | CLANG_WARN_EMPTY_BODY = YES; 290 | CLANG_WARN_ENUM_CONVERSION = YES; 291 | CLANG_WARN_INFINITE_RECURSION = YES; 292 | CLANG_WARN_INT_CONVERSION = YES; 293 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 294 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 295 | CLANG_WARN_UNREACHABLE_CODE = YES; 296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 297 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 298 | COPY_PHASE_STRIP = NO; 299 | CURRENT_PROJECT_VERSION = 1; 300 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 301 | ENABLE_NS_ASSERTIONS = NO; 302 | ENABLE_STRICT_OBJC_MSGSEND = YES; 303 | GCC_C_LANGUAGE_STANDARD = gnu99; 304 | GCC_NO_COMMON_BLOCKS = YES; 305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 307 | GCC_WARN_UNDECLARED_SELECTOR = YES; 308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 309 | GCC_WARN_UNUSED_FUNCTION = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 312 | MTL_ENABLE_DEBUG_INFO = NO; 313 | PRODUCT_NAME = SOCKit; 314 | SDKROOT = iphoneos; 315 | TARGETED_DEVICE_FAMILY = "1,2"; 316 | VALIDATE_PRODUCT = YES; 317 | VERSIONING_SYSTEM = "apple-generic"; 318 | VERSION_INFO_PREFIX = ""; 319 | }; 320 | name = Release; 321 | }; 322 | FE0935D51E8030B60092BF67 /* Debug */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | CODE_SIGN_IDENTITY = ""; 326 | DEFINES_MODULE = YES; 327 | DYLIB_COMPATIBILITY_VERSION = 1; 328 | DYLIB_CURRENT_VERSION = 1; 329 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 330 | INFOPLIST_FILE = Info.plist; 331 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 332 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 333 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 334 | PRODUCT_BUNDLE_IDENTIFIER = info.nimbuskit.SOCKit; 335 | SKIP_INSTALL = YES; 336 | }; 337 | name = Debug; 338 | }; 339 | FE0935D61E8030B60092BF67 /* Release */ = { 340 | isa = XCBuildConfiguration; 341 | buildSettings = { 342 | CODE_SIGN_IDENTITY = ""; 343 | DEFINES_MODULE = YES; 344 | DYLIB_COMPATIBILITY_VERSION = 1; 345 | DYLIB_CURRENT_VERSION = 1; 346 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 347 | INFOPLIST_FILE = Info.plist; 348 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 349 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 350 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 351 | PRODUCT_BUNDLE_IDENTIFIER = info.nimbuskit.SOCKit; 352 | SKIP_INSTALL = YES; 353 | }; 354 | name = Release; 355 | }; 356 | FE0935EE1E8031F10092BF67 /* Debug */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | INFOPLIST_FILE = tests/Info.plist; 360 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 361 | PRODUCT_BUNDLE_IDENTIFIER = "info.nimbuskit.SOCKit-iOS-Tests"; 362 | }; 363 | name = Debug; 364 | }; 365 | FE0935EF1E8031F10092BF67 /* Release */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | INFOPLIST_FILE = tests/Info.plist; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 370 | PRODUCT_BUNDLE_IDENTIFIER = "info.nimbuskit.SOCKit-iOS-Tests"; 371 | }; 372 | name = Release; 373 | }; 374 | /* End XCBuildConfiguration section */ 375 | 376 | /* Begin XCConfigurationList section */ 377 | FE0935C61E8030B60092BF67 /* Build configuration list for PBXProject "SOCKit" */ = { 378 | isa = XCConfigurationList; 379 | buildConfigurations = ( 380 | FE0935D21E8030B60092BF67 /* Debug */, 381 | FE0935D31E8030B60092BF67 /* Release */, 382 | ); 383 | defaultConfigurationIsVisible = 0; 384 | defaultConfigurationName = Release; 385 | }; 386 | FE0935D41E8030B60092BF67 /* Build configuration list for PBXNativeTarget "SOCKit iOS" */ = { 387 | isa = XCConfigurationList; 388 | buildConfigurations = ( 389 | FE0935D51E8030B60092BF67 /* Debug */, 390 | FE0935D61E8030B60092BF67 /* Release */, 391 | ); 392 | defaultConfigurationIsVisible = 0; 393 | defaultConfigurationName = Release; 394 | }; 395 | FE0935ED1E8031F10092BF67 /* Build configuration list for PBXNativeTarget "SOCKit iOS Tests" */ = { 396 | isa = XCConfigurationList; 397 | buildConfigurations = ( 398 | FE0935EE1E8031F10092BF67 /* Debug */, 399 | FE0935EF1E8031F10092BF67 /* Release */, 400 | ); 401 | defaultConfigurationIsVisible = 0; 402 | defaultConfigurationName = Release; 403 | }; 404 | /* End XCConfigurationList section */ 405 | }; 406 | rootObject = FE0935C31E8030B60092BF67 /* Project object */; 407 | } 408 | -------------------------------------------------------------------------------- /src/SOCKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2011-2014 NimbusKit 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import "SOCKit.h" 18 | 19 | #import 20 | #import 21 | 22 | typedef enum { 23 | SOCArgumentTypeNone, 24 | SOCArgumentTypePointer, 25 | SOCArgumentTypeBool, 26 | SOCArgumentTypeInteger, 27 | SOCArgumentTypeLongLong, 28 | SOCArgumentTypeFloat, 29 | SOCArgumentTypeDouble, 30 | } SOCArgumentType; 31 | 32 | SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType); 33 | NSString* kTemporaryBackslashToken = @"/backslash/"; 34 | 35 | @interface SOCParameter : NSObject { 36 | NSString* _string; 37 | } 38 | 39 | - (id)initWithString:(NSString *)string; 40 | + (id)parameterWithString:(NSString *)string; 41 | 42 | - (NSString *)string; 43 | 44 | @end 45 | 46 | @implementation SOCPattern 47 | 48 | - (id)initWithString:(NSString *)string { 49 | if ((self = [super init])) { 50 | _patternString = [string copy]; 51 | 52 | [self _compilePattern]; 53 | } 54 | return self; 55 | } 56 | 57 | + (id)patternWithString:(NSString *)string { 58 | return [[self alloc] initWithString:string]; 59 | } 60 | 61 | - (id)copyWithZone:(NSZone *)zone { 62 | SOCPattern* copy = [[[self class] alloc] init]; 63 | 64 | copy->_patternString = [_patternString copy]; 65 | copy->_tokens = [_tokens copy]; 66 | copy->_parameters = [_parameters copy]; 67 | 68 | return copy; 69 | } 70 | 71 | #pragma mark - Pattern Compilation 72 | 73 | - (NSCharacterSet *)nonParameterCharacterSet { 74 | NSMutableCharacterSet* parameterCharacterSet = [NSMutableCharacterSet alphanumericCharacterSet]; 75 | [parameterCharacterSet addCharactersInString:@".@_"]; 76 | NSCharacterSet* nonParameterCharacterSet = [parameterCharacterSet invertedSet]; 77 | return nonParameterCharacterSet; 78 | } 79 | 80 | - (void)_compilePattern { 81 | if ([_patternString length] == 0) { 82 | return; 83 | } 84 | 85 | NSMutableArray* tokens = [[NSMutableArray alloc] init]; 86 | NSMutableArray* parameters = [[NSMutableArray alloc] init]; 87 | 88 | NSCharacterSet* nonParameterCharacterSet = [self nonParameterCharacterSet]; 89 | 90 | // Turn escaped backslashes into a special backslash token to avoid \\. being interpreted as 91 | // `\` and `\.` rather than `\\` and `.`. 92 | NSString* escapedPatternString = _patternString; 93 | if ([escapedPatternString rangeOfString:@"\\\\"].length > 0) { 94 | escapedPatternString = [escapedPatternString stringByReplacingOccurrencesOfString: @"\\\\" 95 | withString: kTemporaryBackslashToken]; 96 | } 97 | 98 | // Scan through the string, creating tokens that are either strings or parameters. 99 | // Parameters are prefixed with ":". 100 | NSScanner* scanner = [NSScanner scannerWithString:escapedPatternString]; 101 | 102 | // NSScanner skips whitespace and newlines by default (not ideal!). 103 | [scanner setCharactersToBeSkipped:nil]; 104 | 105 | while (![scanner isAtEnd]) { 106 | NSString* token = nil; 107 | [scanner scanUpToString:@":" intoString:&token]; 108 | 109 | if ([token length] > 0) { 110 | if (![token hasSuffix:@"\\"]) { 111 | // Add this static text to the token list. 112 | [tokens addObject:token]; 113 | 114 | } else { 115 | // This token is escaping the next colon, so we skip the parameter creation. 116 | [tokens addObject:[token stringByAppendingString:@":"]]; 117 | 118 | // Skip the colon. 119 | [scanner setScanLocation:[scanner scanLocation] + 1]; 120 | continue; 121 | } 122 | } 123 | 124 | if (![scanner isAtEnd]) { 125 | // Skip the colon. 126 | [scanner setScanLocation:[scanner scanLocation] + 1]; 127 | 128 | // Scanning won't modify the token if there aren't any characters to be read, so we must 129 | // clear it before scanning again. 130 | token = nil; 131 | [scanner scanUpToCharactersFromSet:nonParameterCharacterSet intoString:&token]; 132 | 133 | if ([token length] > 0) { 134 | // Only add parameters that have valid names. 135 | SOCParameter* parameter = [SOCParameter parameterWithString:token]; 136 | [parameters addObject:parameter]; 137 | [tokens addObject:parameter]; 138 | 139 | } else { 140 | // Allows for http:// to get by without creating a parameter. 141 | [tokens addObject:@":"]; 142 | } 143 | } 144 | } 145 | 146 | // This is an outbound pattern. 147 | if ([parameters count] > 0) { 148 | BOOL lastWasParameter = NO; 149 | for (id token in tokens) { 150 | if ([token isKindOfClass:[SOCParameter class]]) { 151 | NSAssert(!lastWasParameter, @"Parameters must be separated by non-parameter characters."); 152 | lastWasParameter = YES; 153 | 154 | } else { 155 | lastWasParameter = NO; 156 | } 157 | } 158 | } 159 | 160 | _tokens = [tokens copy]; 161 | _parameters = nil; 162 | if ([parameters count] > 0) { 163 | _parameters = [parameters copy]; 164 | } 165 | } 166 | 167 | - (NSString *)_stringFromEscapedToken:(NSString *)token { 168 | if ([token rangeOfString:@"\\"].length == 0 169 | && [token rangeOfString:kTemporaryBackslashToken].length == 0) { 170 | // The common case (faster and creates fewer autoreleased strings). 171 | return token; 172 | 173 | } else { 174 | // Escaped characters may exist. 175 | // Create a mutable copy so that we don't excessively create new autoreleased strings. 176 | NSMutableString* mutableToken = [token mutableCopy]; 177 | [mutableToken replaceOccurrencesOfString:@"\\." withString:@"." options:0 range:NSMakeRange(0, [mutableToken length])]; 178 | [mutableToken replaceOccurrencesOfString:@"\\@" withString:@"@" options:0 range:NSMakeRange(0, [mutableToken length])]; 179 | [mutableToken replaceOccurrencesOfString:@"\\:" withString:@":" options:0 range:NSMakeRange(0, [mutableToken length])]; 180 | [mutableToken replaceOccurrencesOfString:kTemporaryBackslashToken withString:@"\\" options:0 range:NSMakeRange(0, [mutableToken length])]; 181 | return mutableToken; 182 | } 183 | } 184 | 185 | #pragma mark - Public Methods 186 | 187 | - (BOOL)gatherParameterValues:(NSArray**)pValues fromString:(NSString *)string { 188 | const NSInteger stringLength = [string length]; 189 | NSInteger validUpUntil = 0; 190 | NSInteger matchingTokens = 0; 191 | 192 | NSMutableArray* values = nil; 193 | if (nil != pValues) { 194 | values = [NSMutableArray array]; 195 | } 196 | 197 | NSInteger tokenIndex = 0; 198 | for (id token in _tokens) { 199 | 200 | if ([token isKindOfClass:[NSString class]]) { 201 | // Replace the escaped characters in the token before we start comparing the string. 202 | id cleanedToken = [self _stringFromEscapedToken:token]; 203 | 204 | NSInteger tokenLength = [cleanedToken length]; 205 | if (validUpUntil + tokenLength > stringLength) { 206 | // There aren't enough characters in the string to satisfy this token. 207 | break; 208 | } 209 | if (![[string substringWithRange:NSMakeRange(validUpUntil, tokenLength)] isEqualToString:cleanedToken]) { 210 | // The tokens don't match up. 211 | break; 212 | } 213 | 214 | // The string token matches. 215 | validUpUntil += tokenLength; 216 | ++matchingTokens; 217 | 218 | } else { 219 | NSInteger parameterLocation = validUpUntil; 220 | 221 | // Look ahead for the next string token match. 222 | if (tokenIndex + 1 < [_tokens count]) { 223 | NSString* nextToken = [self _stringFromEscapedToken:[_tokens objectAtIndex:tokenIndex + 1]]; 224 | NSAssert([nextToken isKindOfClass:[NSString class]], @"The token following a parameter must be a string."); 225 | 226 | NSRange nextTokenRange = [string rangeOfString:nextToken options:0 range:NSMakeRange(validUpUntil, stringLength - validUpUntil)]; 227 | if (nextTokenRange.length == 0) { 228 | // Couldn't find the next token. 229 | break; 230 | } 231 | if (nextTokenRange.location == validUpUntil) { 232 | // This parameter is empty. 233 | break; 234 | } 235 | 236 | validUpUntil = nextTokenRange.location; 237 | ++matchingTokens; 238 | 239 | } else { 240 | // Anything goes until the end of the string then. 241 | if (validUpUntil == stringLength) { 242 | // The last parameter is empty. 243 | break; 244 | } 245 | 246 | validUpUntil = stringLength; 247 | ++matchingTokens; 248 | } 249 | 250 | NSRange parameterRange = NSMakeRange(parameterLocation, validUpUntil - parameterLocation); 251 | [values addObject:[string substringWithRange:parameterRange]]; 252 | } 253 | 254 | ++tokenIndex; 255 | } 256 | 257 | if (nil != pValues) { 258 | *pValues = [values copy]; 259 | } 260 | 261 | return validUpUntil == stringLength && matchingTokens == [_tokens count]; 262 | } 263 | 264 | - (BOOL)stringMatches:(NSString *)string { 265 | return [self gatherParameterValues:nil fromString:string]; 266 | } 267 | 268 | - (void)setArgument:(NSString*)text withType:(SOCArgumentType)type atIndex:(NSInteger)index forInvocation:(NSInvocation*)invocation { 269 | // There are two implicit arguments with an invocation. 270 | index+=2; 271 | 272 | switch (type) { 273 | case SOCArgumentTypeNone: { 274 | break; 275 | } 276 | case SOCArgumentTypeInteger: { 277 | int val = [text intValue]; 278 | [invocation setArgument:&val atIndex:index]; 279 | break; 280 | } 281 | case SOCArgumentTypeLongLong: { 282 | long long val = [text longLongValue]; 283 | [invocation setArgument:&val atIndex:index]; 284 | break; 285 | } 286 | case SOCArgumentTypeFloat: { 287 | float val = [text floatValue]; 288 | [invocation setArgument:&val atIndex:index]; 289 | break; 290 | } 291 | case SOCArgumentTypeDouble: { 292 | double val = [text doubleValue]; 293 | [invocation setArgument:&val atIndex:index]; 294 | break; 295 | } 296 | case SOCArgumentTypeBool: { 297 | BOOL val = [text boolValue]; 298 | [invocation setArgument:&val atIndex:index]; 299 | break; 300 | } 301 | default: { 302 | [invocation setArgument:&text atIndex:index]; 303 | break; 304 | } 305 | } 306 | } 307 | 308 | - (void)setArgumentsFromValues:(NSArray *)values forInvocation:(NSInvocation *)invocation { 309 | Method method = class_getInstanceMethod([invocation.target class], invocation.selector); 310 | NSAssert(nil != method, @"The method must exist with the given invocation target."); 311 | 312 | for (NSInteger ix = 0; ix < [values count]; ++ix) { 313 | NSString* value = [values objectAtIndex:ix]; 314 | 315 | char argType[4]; 316 | method_getArgumentType(method, (unsigned int) ix + 2, argType, sizeof(argType) / sizeof(argType[0])); 317 | SOCArgumentType type = SOCArgumentTypeForTypeAsChar(argType[0]); 318 | 319 | [self setArgument:value withType:type atIndex:ix forInvocation:invocation]; 320 | } 321 | } 322 | 323 | - (id)performSelector:(SEL)selector onObject:(id)object sourceString:(NSString *)sourceString { 324 | BOOL isInitializer = [NSStringFromSelector(selector) hasPrefix:@"init"] && [object class] == object; 325 | 326 | if (isInitializer) { 327 | object = [object alloc]; 328 | } 329 | 330 | NSArray* values = nil; 331 | BOOL succeeded = [self gatherParameterValues:&values fromString:sourceString]; 332 | NSAssert(succeeded, @"The pattern can't be used with this string."); 333 | 334 | id returnValue = nil; 335 | 336 | if (succeeded) { 337 | NSMethodSignature* sig = [object methodSignatureForSelector:selector]; 338 | NSAssert(nil != sig, @"%@ does not respond to selector: '%@'", object, NSStringFromSelector(selector)); 339 | 340 | NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sig]; 341 | [invocation setTarget:object]; 342 | [invocation setSelector:selector]; 343 | [self setArgumentsFromValues:values forInvocation:invocation]; 344 | [invocation invoke]; 345 | 346 | if (sig.methodReturnLength) { 347 | [invocation getReturnValue:&returnValue]; 348 | } 349 | 350 | // Necessary for transferring this object into ARC-world. 351 | returnValue = CFBridgingRelease(CFBridgingRetain(returnValue)); 352 | } 353 | 354 | return returnValue; 355 | } 356 | 357 | - (NSDictionary *)parameterDictionaryFromSourceString:(NSString *)sourceString { 358 | NSMutableDictionary* kvs = [[NSMutableDictionary alloc] initWithCapacity:[_parameters count]]; 359 | 360 | NSArray* values = nil; 361 | BOOL succeeded = [self gatherParameterValues:&values fromString:sourceString]; 362 | NSAssert(succeeded, @"The pattern can't be used with this string."); 363 | 364 | NSDictionary* result = nil; 365 | 366 | if (succeeded) { 367 | for (NSInteger ix = 0; ix < [values count]; ++ix) { 368 | SOCParameter* parameter = [_parameters objectAtIndex:ix]; 369 | id value = [values objectAtIndex:ix]; 370 | [kvs setObject:value forKey:parameter.string]; 371 | } 372 | 373 | result = [kvs copy]; 374 | } 375 | 376 | return result; 377 | } 378 | 379 | - (NSString *)_stringWithParameterValues:(NSDictionary *)parameterValues { 380 | NSMutableString* accumulator = [[NSMutableString alloc] initWithCapacity:[_patternString length]]; 381 | 382 | for (id token in _tokens) { 383 | if ([token isKindOfClass:[NSString class]]) { 384 | [accumulator appendString:[self _stringFromEscapedToken:token]]; 385 | 386 | } else { 387 | SOCParameter* parameter = token; 388 | [accumulator appendString:[parameterValues objectForKey:parameter.string]]; 389 | } 390 | } 391 | 392 | return [accumulator copy]; 393 | } 394 | 395 | - (NSString *)stringFromObject:(id)object { 396 | if ([_tokens count] == 0) { 397 | return @""; 398 | } 399 | NSMutableDictionary* parameterValues = 400 | [NSMutableDictionary dictionaryWithCapacity:[_parameters count]]; 401 | for (SOCParameter* parameter in _parameters) { 402 | NSString* stringValue = [NSString stringWithFormat:@"%@", [object valueForKeyPath:parameter.string]]; 403 | [parameterValues setObject:stringValue forKey:parameter.string]; 404 | } 405 | return [self _stringWithParameterValues:parameterValues]; 406 | } 407 | 408 | - (NSString *)stringFromObject:(id)object withBlock:(NSString *(^)(NSString*))block { 409 | if ([_tokens count] == 0) { 410 | return @""; 411 | } 412 | NSMutableDictionary* parameterValues = [NSMutableDictionary dictionaryWithCapacity:[_parameters count]]; 413 | for (SOCParameter* parameter in _parameters) { 414 | NSString* stringValue = [NSString stringWithFormat:@"%@", [object valueForKeyPath:parameter.string]]; 415 | if (nil != block) { 416 | stringValue = block(stringValue); 417 | } 418 | if (nil != stringValue) { 419 | [parameterValues setObject:stringValue forKey:parameter.string]; 420 | } 421 | } 422 | return [self _stringWithParameterValues:parameterValues]; 423 | } 424 | 425 | @end 426 | 427 | @implementation SOCParameter 428 | 429 | - (id)initWithString:(NSString *)string { 430 | if ((self = [super init])) { 431 | _string = [string copy]; 432 | } 433 | return self; 434 | } 435 | 436 | + (id)parameterWithString:(NSString *)string { 437 | return [[self alloc] initWithString:string]; 438 | } 439 | 440 | - (NSString *)description { 441 | return [NSString stringWithFormat:@"Parameter: %@", _string]; 442 | } 443 | 444 | - (NSString *)string { 445 | return _string; 446 | } 447 | 448 | @end 449 | 450 | SOCArgumentType SOCArgumentTypeForTypeAsChar(char argType) { 451 | if (argType == 'c' || argType == 'i' || argType == 's' || argType == 'l' || argType == 'C' 452 | || argType == 'I' || argType == 'S' || argType == 'L') { 453 | return SOCArgumentTypeInteger; 454 | 455 | } else if (argType == 'q' || argType == 'Q') { 456 | return SOCArgumentTypeLongLong; 457 | 458 | } else if (argType == 'f') { 459 | return SOCArgumentTypeFloat; 460 | 461 | } else if (argType == 'd') { 462 | return SOCArgumentTypeDouble; 463 | 464 | } else if (argType == 'B') { 465 | return SOCArgumentTypeBool; 466 | 467 | } else { 468 | return SOCArgumentTypePointer; 469 | } 470 | } 471 | 472 | NSString* SOCStringFromStringWithObject(NSString* string, id object) { 473 | SOCPattern* pattern = [[SOCPattern alloc] initWithString:string]; 474 | return [pattern stringFromObject:object]; 475 | } 476 | --------------------------------------------------------------------------------