├── .gitignore ├── .travis.yml ├── Images ├── lens-helper-completion.gif └── lens-result2.gif ├── LICENSE ├── Lensy.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Lensy-OSX.xcscheme │ └── Lensy-iOS.xcscheme ├── Lensy ├── Info.plist ├── Lensy.h └── Lensy.swift ├── LensyTests ├── Info.plist ├── LensBaseTests.swift ├── LensFailableTests.swift ├── LensHelperTests.swift ├── LensResultTests.swift ├── LensyTests.swift └── TestStructs.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | ## Playgrounds 30 | timeline.xctimeline 31 | playground.xcworkspace 32 | 33 | # Swift Package Manager 34 | # 35 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 36 | # Packages/ 37 | .build/ 38 | 39 | # CocoaPods 40 | # 41 | # We recommend against adding the Pods directory to your .gitignore. However 42 | # you should judge for yourself, the pros and cons are mentioned at: 43 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 44 | # 45 | # Pods/ 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | # Carthage/Checkouts 51 | 52 | Carthage/Build 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 57 | # screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 60 | 61 | fastlane/report.xml 62 | fastlane/screenshots 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode7.3 3 | 4 | before_script: 5 | - xcrun simctl list 6 | - SIMULATOR_ID=$(xcrun instruments -s | grep -o "iPhone 6 (9.2) \[.*\]" | grep -o "\[.*\]" | sed "s/^\[\(.*\)\]$/\1/") 7 | 8 | script: 9 | - echo $SIMULATOR_ID 10 | - open -b com.apple.iphonesimulator --args -CurrentDeviceUDID $SIMULATOR_ID 11 | - set -o pipefail 12 | - xcodebuild -project Lensy.xcodeproj -scheme Lensy-iOS test -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 6" | xcpretty -c 13 | - xcodebuild -project Lensy.xcodeproj -scheme Lensy-OSX build -sdk macosx | xcpretty -c 14 | #- pod lib lint --quick 15 | 16 | after_success: 17 | - bash <(curl -s https://codecov.io/bash) 18 | -------------------------------------------------------------------------------- /Images/lens-helper-completion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/safx/Lensy/c60bb494add361873555fcbfb3bca97541d80651/Images/lens-helper-completion.gif -------------------------------------------------------------------------------- /Images/lens-result2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/safx/Lensy/c60bb494add361873555fcbfb3bca97541d80651/Images/lens-result2.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 safx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Lensy.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8D3245941C6F63880081AFC6 /* LensHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D3245931C6F63880081AFC6 /* LensHelperTests.swift */; }; 11 | 8D398BF61C6DDB9500D48469 /* Lensy.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D398BF51C6DDB9500D48469 /* Lensy.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | 8D398BFD1C6DDB9500D48469 /* Lensy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D398BF21C6DDB9500D48469 /* Lensy.framework */; }; 13 | 8D398C021C6DDB9500D48469 /* LensyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D398C011C6DDB9500D48469 /* LensyTests.swift */; }; 14 | 8D398C0D1C6DDBA200D48469 /* Lensy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D398C0C1C6DDBA200D48469 /* Lensy.swift */; }; 15 | 8D398C0F1C6F103D00D48469 /* LensResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D398C0E1C6F103D00D48469 /* LensResultTests.swift */; }; 16 | 8D398C111C6F197300D48469 /* LensBaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D398C101C6F197300D48469 /* LensBaseTests.swift */; }; 17 | 8D398C131C6F3EF700D48469 /* LensFailableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D398C121C6F3EF700D48469 /* LensFailableTests.swift */; }; 18 | 8D398C151C6F40D200D48469 /* TestStructs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D398C141C6F40D200D48469 /* TestStructs.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 8D398BFE1C6DDB9500D48469 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 8D398BE91C6DDB9500D48469 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 8D398BF11C6DDB9500D48469; 27 | remoteInfo = Lensy; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 8D3245931C6F63880081AFC6 /* LensHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LensHelperTests.swift; sourceTree = ""; }; 33 | 8D398BF21C6DDB9500D48469 /* Lensy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Lensy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 8D398BF51C6DDB9500D48469 /* Lensy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Lensy.h; sourceTree = ""; }; 35 | 8D398BF71C6DDB9500D48469 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 8D398BFC1C6DDB9500D48469 /* LensyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LensyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 8D398C011C6DDB9500D48469 /* LensyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LensyTests.swift; sourceTree = ""; }; 38 | 8D398C031C6DDB9500D48469 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 8D398C0C1C6DDBA200D48469 /* Lensy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lensy.swift; sourceTree = ""; }; 40 | 8D398C0E1C6F103D00D48469 /* LensResultTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LensResultTests.swift; sourceTree = ""; }; 41 | 8D398C101C6F197300D48469 /* LensBaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LensBaseTests.swift; sourceTree = ""; }; 42 | 8D398C121C6F3EF700D48469 /* LensFailableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LensFailableTests.swift; sourceTree = ""; }; 43 | 8D398C141C6F40D200D48469 /* TestStructs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestStructs.swift; sourceTree = ""; }; 44 | 8DD54FE21C7170E400C75084 /* Lensy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Lensy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 8D398BEE1C6DDB9500D48469 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | 8D398BF91C6DDB9500D48469 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | 8D398BFD1C6DDB9500D48469 /* Lensy.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | 8DD54FDE1C7170E400C75084 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 8D398BE81C6DDB9500D48469 = { 74 | isa = PBXGroup; 75 | children = ( 76 | 8D398BF41C6DDB9500D48469 /* Lensy */, 77 | 8D398C001C6DDB9500D48469 /* LensyTests */, 78 | 8D398BF31C6DDB9500D48469 /* Products */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | 8D398BF31C6DDB9500D48469 /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 8D398BF21C6DDB9500D48469 /* Lensy.framework */, 86 | 8D398BFC1C6DDB9500D48469 /* LensyTests.xctest */, 87 | 8DD54FE21C7170E400C75084 /* Lensy.framework */, 88 | ); 89 | name = Products; 90 | sourceTree = ""; 91 | }; 92 | 8D398BF41C6DDB9500D48469 /* Lensy */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 8D398BF51C6DDB9500D48469 /* Lensy.h */, 96 | 8D398BF71C6DDB9500D48469 /* Info.plist */, 97 | 8D398C0C1C6DDBA200D48469 /* Lensy.swift */, 98 | ); 99 | path = Lensy; 100 | sourceTree = ""; 101 | }; 102 | 8D398C001C6DDB9500D48469 /* LensyTests */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 8D398C011C6DDB9500D48469 /* LensyTests.swift */, 106 | 8D398C141C6F40D200D48469 /* TestStructs.swift */, 107 | 8D398C101C6F197300D48469 /* LensBaseTests.swift */, 108 | 8D398C121C6F3EF700D48469 /* LensFailableTests.swift */, 109 | 8D3245931C6F63880081AFC6 /* LensHelperTests.swift */, 110 | 8D398C0E1C6F103D00D48469 /* LensResultTests.swift */, 111 | 8D398C031C6DDB9500D48469 /* Info.plist */, 112 | ); 113 | path = LensyTests; 114 | sourceTree = ""; 115 | }; 116 | /* End PBXGroup section */ 117 | 118 | /* Begin PBXHeadersBuildPhase section */ 119 | 8D398BEF1C6DDB9500D48469 /* Headers */ = { 120 | isa = PBXHeadersBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | 8D398BF61C6DDB9500D48469 /* Lensy.h in Headers */, 124 | ); 125 | runOnlyForDeploymentPostprocessing = 0; 126 | }; 127 | 8DD54FDF1C7170E400C75084 /* Headers */ = { 128 | isa = PBXHeadersBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | /* End PBXHeadersBuildPhase section */ 135 | 136 | /* Begin PBXNativeTarget section */ 137 | 8D398BF11C6DDB9500D48469 /* Lensy-iOS */ = { 138 | isa = PBXNativeTarget; 139 | buildConfigurationList = 8D398C061C6DDB9500D48469 /* Build configuration list for PBXNativeTarget "Lensy-iOS" */; 140 | buildPhases = ( 141 | 8D398BED1C6DDB9500D48469 /* Sources */, 142 | 8D398BEE1C6DDB9500D48469 /* Frameworks */, 143 | 8D398BEF1C6DDB9500D48469 /* Headers */, 144 | 8D398BF01C6DDB9500D48469 /* Resources */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | ); 150 | name = "Lensy-iOS"; 151 | productName = Lensy; 152 | productReference = 8D398BF21C6DDB9500D48469 /* Lensy.framework */; 153 | productType = "com.apple.product-type.framework"; 154 | }; 155 | 8D398BFB1C6DDB9500D48469 /* LensyTests */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = 8D398C091C6DDB9500D48469 /* Build configuration list for PBXNativeTarget "LensyTests" */; 158 | buildPhases = ( 159 | 8D398BF81C6DDB9500D48469 /* Sources */, 160 | 8D398BF91C6DDB9500D48469 /* Frameworks */, 161 | 8D398BFA1C6DDB9500D48469 /* Resources */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | 8D398BFF1C6DDB9500D48469 /* PBXTargetDependency */, 167 | ); 168 | name = LensyTests; 169 | productName = LensyTests; 170 | productReference = 8D398BFC1C6DDB9500D48469 /* LensyTests.xctest */; 171 | productType = "com.apple.product-type.bundle.unit-test"; 172 | }; 173 | 8DD54FE11C7170E400C75084 /* Lensy-OSX */ = { 174 | isa = PBXNativeTarget; 175 | buildConfigurationList = 8DD54FF31C7170E500C75084 /* Build configuration list for PBXNativeTarget "Lensy-OSX" */; 176 | buildPhases = ( 177 | 8DD54FDD1C7170E400C75084 /* Sources */, 178 | 8DD54FDE1C7170E400C75084 /* Frameworks */, 179 | 8DD54FDF1C7170E400C75084 /* Headers */, 180 | 8DD54FE01C7170E400C75084 /* Resources */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | ); 186 | name = "Lensy-OSX"; 187 | productName = "Lensy-OSX"; 188 | productReference = 8DD54FE21C7170E400C75084 /* Lensy.framework */; 189 | productType = "com.apple.product-type.framework"; 190 | }; 191 | /* End PBXNativeTarget section */ 192 | 193 | /* Begin PBXProject section */ 194 | 8D398BE91C6DDB9500D48469 /* Project object */ = { 195 | isa = PBXProject; 196 | attributes = { 197 | LastSwiftUpdateCheck = 0720; 198 | LastUpgradeCheck = 0720; 199 | ORGANIZATIONNAME = "Safx Developers"; 200 | TargetAttributes = { 201 | 8D398BF11C6DDB9500D48469 = { 202 | CreatedOnToolsVersion = 7.2.1; 203 | }; 204 | 8D398BFB1C6DDB9500D48469 = { 205 | CreatedOnToolsVersion = 7.2.1; 206 | }; 207 | 8DD54FE11C7170E400C75084 = { 208 | CreatedOnToolsVersion = 7.2.1; 209 | }; 210 | }; 211 | }; 212 | buildConfigurationList = 8D398BEC1C6DDB9500D48469 /* Build configuration list for PBXProject "Lensy" */; 213 | compatibilityVersion = "Xcode 3.2"; 214 | developmentRegion = English; 215 | hasScannedForEncodings = 0; 216 | knownRegions = ( 217 | en, 218 | ); 219 | mainGroup = 8D398BE81C6DDB9500D48469; 220 | productRefGroup = 8D398BF31C6DDB9500D48469 /* Products */; 221 | projectDirPath = ""; 222 | projectRoot = ""; 223 | targets = ( 224 | 8D398BF11C6DDB9500D48469 /* Lensy-iOS */, 225 | 8D398BFB1C6DDB9500D48469 /* LensyTests */, 226 | 8DD54FE11C7170E400C75084 /* Lensy-OSX */, 227 | ); 228 | }; 229 | /* End PBXProject section */ 230 | 231 | /* Begin PBXResourcesBuildPhase section */ 232 | 8D398BF01C6DDB9500D48469 /* Resources */ = { 233 | isa = PBXResourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | 8D398BFA1C6DDB9500D48469 /* Resources */ = { 240 | isa = PBXResourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | 8DD54FE01C7170E400C75084 /* Resources */ = { 247 | isa = PBXResourcesBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXResourcesBuildPhase section */ 254 | 255 | /* Begin PBXSourcesBuildPhase section */ 256 | 8D398BED1C6DDB9500D48469 /* Sources */ = { 257 | isa = PBXSourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | 8D398C0D1C6DDBA200D48469 /* Lensy.swift in Sources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | 8D398BF81C6DDB9500D48469 /* Sources */ = { 265 | isa = PBXSourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | 8D398C111C6F197300D48469 /* LensBaseTests.swift in Sources */, 269 | 8D398C151C6F40D200D48469 /* TestStructs.swift in Sources */, 270 | 8D398C0F1C6F103D00D48469 /* LensResultTests.swift in Sources */, 271 | 8D398C021C6DDB9500D48469 /* LensyTests.swift in Sources */, 272 | 8D398C131C6F3EF700D48469 /* LensFailableTests.swift in Sources */, 273 | 8D3245941C6F63880081AFC6 /* LensHelperTests.swift in Sources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | 8DD54FDD1C7170E400C75084 /* Sources */ = { 278 | isa = PBXSourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | /* End PBXSourcesBuildPhase section */ 285 | 286 | /* Begin PBXTargetDependency section */ 287 | 8D398BFF1C6DDB9500D48469 /* PBXTargetDependency */ = { 288 | isa = PBXTargetDependency; 289 | target = 8D398BF11C6DDB9500D48469 /* Lensy-iOS */; 290 | targetProxy = 8D398BFE1C6DDB9500D48469 /* PBXContainerItemProxy */; 291 | }; 292 | /* End PBXTargetDependency section */ 293 | 294 | /* Begin XCBuildConfiguration section */ 295 | 8D398C041C6DDB9500D48469 /* Debug */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ALWAYS_SEARCH_USER_PATHS = NO; 299 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 300 | CLANG_CXX_LIBRARY = "libc++"; 301 | CLANG_ENABLE_MODULES = YES; 302 | CLANG_ENABLE_OBJC_ARC = YES; 303 | CLANG_WARN_BOOL_CONVERSION = YES; 304 | CLANG_WARN_CONSTANT_CONVERSION = YES; 305 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 306 | CLANG_WARN_EMPTY_BODY = YES; 307 | CLANG_WARN_ENUM_CONVERSION = YES; 308 | CLANG_WARN_INT_CONVERSION = YES; 309 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 310 | CLANG_WARN_UNREACHABLE_CODE = YES; 311 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 312 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 313 | COPY_PHASE_STRIP = NO; 314 | CURRENT_PROJECT_VERSION = 1; 315 | DEBUG_INFORMATION_FORMAT = dwarf; 316 | ENABLE_STRICT_OBJC_MSGSEND = YES; 317 | ENABLE_TESTABILITY = YES; 318 | GCC_C_LANGUAGE_STANDARD = gnu99; 319 | GCC_DYNAMIC_NO_PIC = NO; 320 | GCC_NO_COMMON_BLOCKS = YES; 321 | GCC_OPTIMIZATION_LEVEL = 0; 322 | GCC_PREPROCESSOR_DEFINITIONS = ( 323 | "DEBUG=1", 324 | "$(inherited)", 325 | ); 326 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 327 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 328 | GCC_WARN_UNDECLARED_SELECTOR = YES; 329 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 330 | GCC_WARN_UNUSED_FUNCTION = YES; 331 | GCC_WARN_UNUSED_VARIABLE = YES; 332 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 333 | MTL_ENABLE_DEBUG_INFO = YES; 334 | ONLY_ACTIVE_ARCH = YES; 335 | SDKROOT = iphoneos; 336 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 337 | TARGETED_DEVICE_FAMILY = "1,2"; 338 | VERSIONING_SYSTEM = "apple-generic"; 339 | VERSION_INFO_PREFIX = ""; 340 | }; 341 | name = Debug; 342 | }; 343 | 8D398C051C6DDB9500D48469 /* Release */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | ALWAYS_SEARCH_USER_PATHS = NO; 347 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 348 | CLANG_CXX_LIBRARY = "libc++"; 349 | CLANG_ENABLE_MODULES = YES; 350 | CLANG_ENABLE_OBJC_ARC = YES; 351 | CLANG_WARN_BOOL_CONVERSION = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 354 | CLANG_WARN_EMPTY_BODY = YES; 355 | CLANG_WARN_ENUM_CONVERSION = YES; 356 | CLANG_WARN_INT_CONVERSION = YES; 357 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | CURRENT_PROJECT_VERSION = 1; 363 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 364 | ENABLE_NS_ASSERTIONS = NO; 365 | ENABLE_STRICT_OBJC_MSGSEND = YES; 366 | GCC_C_LANGUAGE_STANDARD = gnu99; 367 | GCC_NO_COMMON_BLOCKS = YES; 368 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 369 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 370 | GCC_WARN_UNDECLARED_SELECTOR = YES; 371 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 372 | GCC_WARN_UNUSED_FUNCTION = YES; 373 | GCC_WARN_UNUSED_VARIABLE = YES; 374 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 375 | MTL_ENABLE_DEBUG_INFO = NO; 376 | SDKROOT = iphoneos; 377 | TARGETED_DEVICE_FAMILY = "1,2"; 378 | VALIDATE_PRODUCT = YES; 379 | VERSIONING_SYSTEM = "apple-generic"; 380 | VERSION_INFO_PREFIX = ""; 381 | }; 382 | name = Release; 383 | }; 384 | 8D398C071C6DDB9500D48469 /* Debug */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | CLANG_ENABLE_MODULES = YES; 388 | DEFINES_MODULE = YES; 389 | DYLIB_COMPATIBILITY_VERSION = 1; 390 | DYLIB_CURRENT_VERSION = 1; 391 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 392 | INFOPLIST_FILE = Lensy/Info.plist; 393 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 394 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 395 | PRODUCT_BUNDLE_IDENTIFIER = "com.blogspot.safx-dev.Lensy"; 396 | PRODUCT_NAME = Lensy; 397 | SKIP_INSTALL = YES; 398 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 399 | }; 400 | name = Debug; 401 | }; 402 | 8D398C081C6DDB9500D48469 /* Release */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | CLANG_ENABLE_MODULES = YES; 406 | DEFINES_MODULE = YES; 407 | DYLIB_COMPATIBILITY_VERSION = 1; 408 | DYLIB_CURRENT_VERSION = 1; 409 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 410 | INFOPLIST_FILE = Lensy/Info.plist; 411 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 412 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 413 | PRODUCT_BUNDLE_IDENTIFIER = "com.blogspot.safx-dev.Lensy"; 414 | PRODUCT_NAME = Lensy; 415 | SKIP_INSTALL = YES; 416 | }; 417 | name = Release; 418 | }; 419 | 8D398C0A1C6DDB9500D48469 /* Debug */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | INFOPLIST_FILE = LensyTests/Info.plist; 423 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 424 | PRODUCT_BUNDLE_IDENTIFIER = "com.blogspot.safx-dev.LensyTests"; 425 | PRODUCT_NAME = "$(TARGET_NAME)"; 426 | }; 427 | name = Debug; 428 | }; 429 | 8D398C0B1C6DDB9500D48469 /* Release */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | INFOPLIST_FILE = LensyTests/Info.plist; 433 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 434 | PRODUCT_BUNDLE_IDENTIFIER = "com.blogspot.safx-dev.LensyTests"; 435 | PRODUCT_NAME = "$(TARGET_NAME)"; 436 | }; 437 | name = Release; 438 | }; 439 | 8DD54FF41C7170E500C75084 /* Debug */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | CODE_SIGN_IDENTITY = "-"; 443 | COMBINE_HIDPI_IMAGES = YES; 444 | DEFINES_MODULE = YES; 445 | DYLIB_COMPATIBILITY_VERSION = 1; 446 | DYLIB_CURRENT_VERSION = 1; 447 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 448 | FRAMEWORK_VERSION = A; 449 | INFOPLIST_FILE = Lensy/Info.plist; 450 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 451 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 452 | MACOSX_DEPLOYMENT_TARGET = 10.11; 453 | PRODUCT_BUNDLE_IDENTIFIER = "com.blogspot.safx-dev.Lensy-OSX"; 454 | PRODUCT_NAME = Lensy; 455 | SDKROOT = macosx; 456 | SKIP_INSTALL = YES; 457 | }; 458 | name = Debug; 459 | }; 460 | 8DD54FF51C7170E500C75084 /* Release */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | CODE_SIGN_IDENTITY = "-"; 464 | COMBINE_HIDPI_IMAGES = YES; 465 | DEFINES_MODULE = YES; 466 | DYLIB_COMPATIBILITY_VERSION = 1; 467 | DYLIB_CURRENT_VERSION = 1; 468 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 469 | FRAMEWORK_VERSION = A; 470 | INFOPLIST_FILE = Lensy/Info.plist; 471 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 472 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 473 | MACOSX_DEPLOYMENT_TARGET = 10.11; 474 | PRODUCT_BUNDLE_IDENTIFIER = "com.blogspot.safx-dev.Lensy-OSX"; 475 | PRODUCT_NAME = Lensy; 476 | SDKROOT = macosx; 477 | SKIP_INSTALL = YES; 478 | }; 479 | name = Release; 480 | }; 481 | /* End XCBuildConfiguration section */ 482 | 483 | /* Begin XCConfigurationList section */ 484 | 8D398BEC1C6DDB9500D48469 /* Build configuration list for PBXProject "Lensy" */ = { 485 | isa = XCConfigurationList; 486 | buildConfigurations = ( 487 | 8D398C041C6DDB9500D48469 /* Debug */, 488 | 8D398C051C6DDB9500D48469 /* Release */, 489 | ); 490 | defaultConfigurationIsVisible = 0; 491 | defaultConfigurationName = Release; 492 | }; 493 | 8D398C061C6DDB9500D48469 /* Build configuration list for PBXNativeTarget "Lensy-iOS" */ = { 494 | isa = XCConfigurationList; 495 | buildConfigurations = ( 496 | 8D398C071C6DDB9500D48469 /* Debug */, 497 | 8D398C081C6DDB9500D48469 /* Release */, 498 | ); 499 | defaultConfigurationIsVisible = 0; 500 | defaultConfigurationName = Release; 501 | }; 502 | 8D398C091C6DDB9500D48469 /* Build configuration list for PBXNativeTarget "LensyTests" */ = { 503 | isa = XCConfigurationList; 504 | buildConfigurations = ( 505 | 8D398C0A1C6DDB9500D48469 /* Debug */, 506 | 8D398C0B1C6DDB9500D48469 /* Release */, 507 | ); 508 | defaultConfigurationIsVisible = 0; 509 | defaultConfigurationName = Release; 510 | }; 511 | 8DD54FF31C7170E500C75084 /* Build configuration list for PBXNativeTarget "Lensy-OSX" */ = { 512 | isa = XCConfigurationList; 513 | buildConfigurations = ( 514 | 8DD54FF41C7170E500C75084 /* Debug */, 515 | 8DD54FF51C7170E500C75084 /* Release */, 516 | ); 517 | defaultConfigurationIsVisible = 0; 518 | }; 519 | /* End XCConfigurationList section */ 520 | }; 521 | rootObject = 8D398BE91C6DDB9500D48469 /* Project object */; 522 | } 523 | -------------------------------------------------------------------------------- /Lensy.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Lensy.xcodeproj/xcshareddata/xcschemes/Lensy-OSX.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 | -------------------------------------------------------------------------------- /Lensy.xcodeproj/xcshareddata/xcschemes/Lensy-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Lensy/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Lensy/Lensy.h: -------------------------------------------------------------------------------- 1 | // 2 | // Lensy.h 3 | // Lensy 4 | // 5 | // Created by Safx Developer on 2016/02/12. 6 | // Copyright © 2016年 Safx Developers. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Lensy. 12 | FOUNDATION_EXPORT double LensyVersionNumber; 13 | 14 | //! Project version string for Lensy. 15 | FOUNDATION_EXPORT const unsigned char LensyVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Lensy/Lensy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Lensy.swift 3 | // Lensy 4 | // 5 | // Created by Safx Developer on 2016/02/12. 6 | // Copyright © 2016 Safx Developers. All rights reserved. 7 | // 8 | 9 | 10 | // MARK: - Lenses API 11 | 12 | public protocol LensType { 13 | associatedtype Whole 14 | associatedtype Part 15 | var get: Whole -> LensResult { get } 16 | var set: (Whole, Part) -> LensResult { get } 17 | } 18 | 19 | public struct Lens: LensType { 20 | public let get: Whole -> LensResult 21 | public let set: (Whole, Part) -> LensResult 22 | } 23 | 24 | public struct OptionalUnwrapLens: LensType { 25 | public typealias Whole = Element? 26 | public typealias Part = Element 27 | public let get: Whole -> LensResult 28 | public let set: (Whole, Part) -> LensResult 29 | } 30 | 31 | public struct ArrayIndexLens: LensType { 32 | public typealias Whole = [Element] 33 | public typealias Part = Element 34 | public let get: Whole -> LensResult 35 | public let set: (Whole, Part) -> LensResult 36 | } 37 | 38 | public extension LensType { 39 | public func compose(other: L) -> Lens { 40 | return Lens( 41 | get: { (object: Whole) -> LensResult in 42 | return self.get(object) 43 | .then(other.get) 44 | }, 45 | set: { (object: Whole, newValue: Subpart) -> LensResult in 46 | return self.get(object) 47 | .then { other.set($0, newValue) } 48 | .then { self.set(object, $0) } 49 | } 50 | ) 51 | } 52 | 53 | public func modify(object: Whole, @noescape _ closure: Part -> Part) -> LensResult { 54 | return get(object) 55 | .then { self.set(object, closure($0)) } 56 | } 57 | 58 | public func tryGet(object: Whole) throws -> Part { 59 | switch get(object) { 60 | case .OK(let v): 61 | return v 62 | case .Error(let e): 63 | throw e 64 | } 65 | } 66 | 67 | public func trySet(object: Whole, _ newValue: Part) throws -> Whole { 68 | switch set(object, newValue) { 69 | case .OK(let v): 70 | return v 71 | case .Error(let e): 72 | throw e 73 | } 74 | } 75 | } 76 | 77 | extension Lens { 78 | public init(g: Whole -> Part, s: (Whole, Part) -> Whole) { 79 | get = { .OK(g($0)) } 80 | set = { .OK(s($0, $1)) } 81 | } 82 | } 83 | 84 | extension OptionalUnwrapLens { 85 | public init() { 86 | get = { optional in 87 | guard let v = optional else { 88 | return .Error(LensErrorType.OptionalNone) 89 | } 90 | return .OK(v) 91 | } 92 | set = { optional, newValue in 93 | guard var v = optional else { 94 | return .Error(LensErrorType.OptionalNone) 95 | } 96 | v = newValue 97 | return .OK(v) 98 | } 99 | } 100 | } 101 | 102 | extension ArrayIndexLens { 103 | public init(at idx: Int) { 104 | get = { array in 105 | guard 0..() -> Lens { 125 | return Lens( 126 | g: { $0 }, 127 | s: { $1 } 128 | ) 129 | } 130 | 131 | // MARK: - Result 132 | 133 | public enum LensResult { 134 | case OK(Element) 135 | case Error(LensErrorType) 136 | } 137 | 138 | extension LensResult { 139 | func then(@noescape closure: Element -> LensResult) -> LensResult { 140 | switch self { 141 | case .OK(let v): 142 | return closure(v) 143 | case .Error(let e): 144 | return .Error(e) 145 | } 146 | } 147 | } 148 | 149 | // MARK: - Error 150 | 151 | public enum LensErrorType: ErrorType { 152 | case OptionalNone 153 | case ArrayIndexOutOfBounds 154 | } 155 | 156 | 157 | // MARK: - Lens Helper 158 | 159 | public protocol LensHelperType { 160 | associatedtype Whole 161 | associatedtype Part 162 | 163 | init(lens: Lens) 164 | var lens: Lens { get } 165 | } 166 | 167 | public protocol HasSubLensHelper { 168 | associatedtype SubLensHelper 169 | } 170 | 171 | public struct LensHelper: LensHelperType { 172 | public let lens: Lens 173 | public init(lens: Lens) { 174 | self.lens = lens 175 | } 176 | } 177 | 178 | public struct ArrayLensHelper: LensHelperType, HasSubLensHelper { 179 | public typealias Part = [Element] 180 | public typealias SubLensHelper = Sub 181 | public let lens: Lens 182 | public init(lens: Lens) { 183 | self.lens = lens 184 | } 185 | } 186 | 187 | public struct OptionalLensHelper: LensHelperType, HasSubLensHelper { 188 | public typealias Part = Element? 189 | public typealias SubLensHelper = Sub 190 | public let lens: Lens 191 | public init(lens: Lens) { 192 | self.lens = lens 193 | } 194 | } 195 | 196 | extension LensHelperType { 197 | public init(parent: Parent, lens: Lens) { 198 | self.init(lens: parent.lens.compose(lens)) 199 | } 200 | 201 | public func get(object: Whole) -> LensResult { 202 | return lens.get(object) 203 | } 204 | 205 | public func set(object: Whole, _ newValue: Part) -> LensResult { 206 | return lens.set(object, newValue) 207 | } 208 | 209 | public func modify(object: Whole, @noescape closure: Part -> Part) -> LensResult { 210 | return lens.modify(object, closure) 211 | } 212 | } 213 | 214 | extension ArrayLensHelper where Sub: LensHelperType, Sub.Whole == Whole, Sub.Part == Element { 215 | public subscript(idx: Int) -> SubLensHelper { 216 | return SubLensHelper(lens: lens.compose(ArrayIndexLens(at: idx))) 217 | } 218 | 219 | public func modifyMap(object: Whole, @noescape closure: Element -> Element) -> LensResult { 220 | return lens.modify(object) { $0.map(closure) } 221 | } 222 | } 223 | 224 | extension OptionalLensHelper where Sub: LensHelperType, Sub.Whole == Whole, Sub.Part == Element { 225 | public var unwrap: SubLensHelper { 226 | return SubLensHelper(lens: lens.compose(OptionalUnwrapLens())) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /LensyTests/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LensyTests/LensBaseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LensTests.swift 3 | // Lensy 4 | // 5 | // Created by Safx Developer on 2016/02/13. 6 | // Copyright © 2016年 Safx Developers. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Lensy 11 | 12 | class LensBaseTests: XCTestCase { 13 | let person = Person(name: "Yamamoto", address: Address(city: "Tokyo"), ids: [9, 11, 42]) 14 | 15 | func testGet() { 16 | let r1 = Person.Lenses.name.get(person) 17 | if case let .OK(v) = r1 { 18 | XCTAssertEqual(v, "Yamamoto") 19 | } else { 20 | XCTAssert(false) 21 | } 22 | 23 | let r2 = Address.Lenses.city.get(person.address) 24 | if case let .OK(v) = r2 { 25 | XCTAssertEqual(v, "Tokyo") 26 | } else { 27 | XCTAssert(false) 28 | } 29 | 30 | let r3 = Person.Lenses.ids.get(person) 31 | if case let .OK(v) = r3 { 32 | XCTAssertEqual(v, [9, 11, 42]) 33 | } else { 34 | XCTAssert(false) 35 | } 36 | } 37 | 38 | func testSet() { 39 | let r1 = Person.Lenses.name.set(person, "Sakamoto") 40 | if case let .OK(v) = r1 { 41 | XCTAssertEqual(v.name, "Sakamoto") 42 | XCTAssertEqual(v.address.city, "Tokyo") 43 | XCTAssertEqual(v.ids, [9, 11, 42]) 44 | } else { 45 | XCTAssert(false) 46 | } 47 | 48 | let r2 = Address.Lenses.city.set(person.address, "Osaka") 49 | if case let .OK(v) = r2 { 50 | XCTAssertEqual(v.city, "Osaka") 51 | } else { 52 | XCTAssert(false) 53 | } 54 | 55 | let r3 = Person.Lenses.ids.set(person, [99, 100]) 56 | if case let .OK(v) = r3 { 57 | XCTAssertEqual(v.name, "Yamamoto") 58 | XCTAssertEqual(v.address.city, "Tokyo") 59 | XCTAssertEqual(v.ids, [99, 100]) 60 | } else { 61 | XCTAssert(false) 62 | } 63 | } 64 | 65 | func testModify() { 66 | let r1 = Person.Lenses.name.modify(person) { "[[\($0)]]" } 67 | if case let .OK(v) = r1 { 68 | XCTAssertEqual(v.name, "[[Yamamoto]]") 69 | XCTAssertEqual(v.address.city, "Tokyo") 70 | XCTAssertEqual(v.ids, [9, 11, 42]) 71 | } else { 72 | XCTAssert(false) 73 | } 74 | 75 | let r2 = Address.Lenses.city.modify(person.address) { $0.uppercaseString } 76 | if case let .OK(v) = r2 { 77 | XCTAssertEqual(v.city, "TOKYO") 78 | } else { 79 | XCTAssert(false) 80 | } 81 | 82 | let r3 = Person.Lenses.ids.modify(person) { $0.map{ 0 - $0 } } 83 | if case let .OK(v) = r3 { 84 | XCTAssertEqual(v.name, "Yamamoto") 85 | XCTAssertEqual(v.address.city, "Tokyo") 86 | XCTAssertEqual(v.ids, [-9, -11, -42]) 87 | } else { 88 | XCTAssert(false) 89 | } 90 | } 91 | 92 | func testComposedGet() { 93 | let r1 = Person.Lenses.address.compose(Address.Lenses.city).get(person) 94 | if case let .OK(v) = r1 { 95 | XCTAssertEqual(v, "Tokyo") 96 | } else { 97 | XCTAssert(false) 98 | } 99 | } 100 | 101 | func testComposedSet() { 102 | let r1 = Person.Lenses.address.compose(Address.Lenses.city).set(person, "Nagoya") 103 | if case let .OK(v) = r1 { 104 | XCTAssertEqual(v.name, "Yamamoto") 105 | XCTAssertEqual(v.address.city, "Nagoya") 106 | XCTAssertEqual(v.ids, [9, 11, 42]) 107 | } else { 108 | XCTAssert(false) 109 | } 110 | } 111 | 112 | func testComposedModify() { 113 | let r1 = Person.Lenses.address.compose(Address.Lenses.city).modify(person) { String($0.characters.reverse()) } 114 | if case let .OK(v) = r1 { 115 | XCTAssertEqual(v.name, "Yamamoto") 116 | XCTAssertEqual(v.address.city, "oykoT") 117 | XCTAssertEqual(v.ids, [9, 11, 42]) 118 | } else { 119 | XCTAssert(false) 120 | } 121 | } 122 | } 123 | 124 | -------------------------------------------------------------------------------- /LensyTests/LensFailableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LensFailableTests.swift 3 | // Lensy 4 | // 5 | // Created by Safx Developer on 2016/02/13. 6 | // Copyright © 2016年 Safx Developers. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Lensy 11 | 12 | class LensFailableTests: XCTestCase { 13 | let person1 = Person(name: "Yamada", address: Address(city: "Tokyo"), ids: []) 14 | var person2 = Person(name: "Suzuki", address: Address(city: "Osaka"), ids: [123, 456]) 15 | var person3 = Person(name: "Takeda", address: Address(city: "Okinawa"), ids: [789]) 16 | var company1 = Company(name: "Bigfoot", address: Address(city: "Nagoya")) 17 | var company2 = Company(name: nil, address: Address(city: "Tiba")) 18 | var book1: Book! 19 | var book2: Book! 20 | var book3: Book! 21 | 22 | override func setUp() { 23 | book1 = Book(title: "Swift Book", authors: [person1, person2], publisher: company1) 24 | book2 = Book(title: "Objective-C Book", authors: [person3], publisher: company2) 25 | book3 = Book(title: "LLVM Book", authors: [person3], publisher: nil) 26 | } 27 | 28 | func testOptionalGet() { 29 | let c1 = Company.Lenses.name 30 | 31 | let r1 = c1.get(company1) 32 | if case let .OK(v) = r1 { 33 | XCTAssertEqual(v, "Bigfoot") 34 | } else { 35 | XCTAssert(false) 36 | } 37 | 38 | let r2 = c1.get(company2) 39 | if case let .OK(v) = r2 { 40 | XCTAssertEqual(v, nil) 41 | } else { 42 | XCTAssert(false) 43 | } 44 | 45 | let c2 = Company.Lenses.name.compose(OptionalUnwrapLens()) 46 | 47 | let r3 = c2.get(company1) 48 | if case let .OK(v) = r3 { 49 | XCTAssertEqual(v, "Bigfoot") 50 | } else { 51 | XCTAssert(false) 52 | } 53 | 54 | let r4 = c2.get(company2) 55 | if case let .Error(v) = r4 { 56 | XCTAssertEqual(v, LensErrorType.OptionalNone) 57 | } else { 58 | XCTAssert(false) 59 | } 60 | 61 | let c3 = Book.Lenses.publisher.compose(OptionalUnwrapLens()).compose(Company.Lenses.name).compose(OptionalUnwrapLens()) 62 | 63 | let r5 = c3.get(book1) 64 | if case let .OK(v) = r5 { 65 | XCTAssertEqual(v, "Bigfoot") 66 | } else { 67 | XCTAssert(false) 68 | } 69 | 70 | let r6 = c3.get(book2) 71 | if case let .Error(v) = r6 { 72 | XCTAssertEqual(v, LensErrorType.OptionalNone) 73 | } else { 74 | XCTAssert(false) 75 | } 76 | 77 | let r7 = c3.get(book3) 78 | if case let .Error(v) = r7 { 79 | XCTAssertEqual(v, LensErrorType.OptionalNone) 80 | } else { 81 | XCTAssert(false) 82 | } 83 | } 84 | 85 | func testOptionalSet() { 86 | let c1 = Company.Lenses.name 87 | 88 | let r1 = c1.set(company1, "Silver Goose") 89 | if case let .OK(v) = r1 { 90 | XCTAssertEqual(v.name, "Silver Goose") 91 | XCTAssertEqual(v.address.city, "Nagoya") 92 | } else { 93 | XCTAssert(false) 94 | } 95 | 96 | let r2 = c1.set(company2, "Silver Goose") 97 | if case let .OK(v) = r2 { 98 | XCTAssertEqual(v.name, "Silver Goose") 99 | XCTAssertEqual(v.address.city, "Tiba") 100 | } else { 101 | XCTAssert(false) 102 | } 103 | 104 | let c2 = Company.Lenses.name.compose(OptionalUnwrapLens()) 105 | 106 | let r3 = c2.set(company1, "Silver Goose") 107 | if case let .OK(v) = r3 { 108 | XCTAssertEqual(v.name, "Silver Goose") 109 | XCTAssertEqual(v.address.city, "Nagoya") 110 | } else { 111 | XCTAssert(false) 112 | } 113 | 114 | let r4 = c2.set(company2, "Silver Goose") 115 | if case let .Error(v) = r4 { 116 | XCTAssertEqual(v, LensErrorType.OptionalNone) 117 | } else { 118 | XCTAssert(false) 119 | } 120 | 121 | let c3 = Book.Lenses.publisher.compose(OptionalUnwrapLens()).compose(Company.Lenses.name).compose(OptionalUnwrapLens()) 122 | 123 | let r5 = c3.set(book1, "Gold Goose") 124 | if case let .OK(v) = r5 { 125 | XCTAssertEqual(v.title, "Swift Book") 126 | XCTAssertEqual(v.publisher!.name, "Gold Goose") 127 | XCTAssertEqual(v.authors[0].name, "Yamada") 128 | XCTAssertEqual(v.authors[1].name, "Suzuki") 129 | } else { 130 | XCTAssert(false) 131 | } 132 | 133 | let r6 = c3.set(book2, "Gold Goose") 134 | if case let .Error(v) = r6 { 135 | XCTAssertEqual(v, LensErrorType.OptionalNone) 136 | } else { 137 | XCTAssert(false) 138 | } 139 | 140 | let r7 = c3.set(book3, "Gold Goose") 141 | if case let .Error(v) = r7 { 142 | XCTAssertEqual(v, LensErrorType.OptionalNone) 143 | } else { 144 | XCTAssert(false) 145 | } 146 | } 147 | 148 | func testArrayGet() { 149 | let c0 = Book.Lenses.authors.compose(ArrayIndexLens(at: 0)) 150 | let c1 = Book.Lenses.authors.compose(ArrayIndexLens(at: 1)) 151 | let c2 = Book.Lenses.authors.compose(ArrayIndexLens(at: 2)) 152 | 153 | let r10 = c0.get(book1) 154 | if case let .OK(v) = r10 { 155 | XCTAssertEqual(v.name, "Yamada") 156 | XCTAssertEqual(v.address.city, "Tokyo") 157 | } else { 158 | XCTAssert(false) 159 | } 160 | 161 | let r11 = c1.get(book1) 162 | if case let .OK(v) = r11 { 163 | XCTAssertEqual(v.name, "Suzuki") 164 | XCTAssertEqual(v.address.city, "Osaka") 165 | } else { 166 | XCTAssert(false) 167 | } 168 | 169 | let r12 = c2.get(book1) 170 | if case let .Error(v) = r12 { 171 | XCTAssertEqual(v, LensErrorType.ArrayIndexOutOfBounds) 172 | } else { 173 | XCTAssert(false) 174 | } 175 | 176 | let n0 = Book.Lenses.authors.compose(ArrayIndexLens(at: 0)).compose(Person.Lenses.name) 177 | let n1 = Book.Lenses.authors.compose(ArrayIndexLens(at: 1)).compose(Person.Lenses.name) 178 | let n2 = Book.Lenses.authors.compose(ArrayIndexLens(at: 2)).compose(Person.Lenses.name) 179 | 180 | let r20 = n0.get(book1) 181 | if case let .OK(v) = r20 { 182 | XCTAssertEqual(v, "Yamada") 183 | } else { 184 | XCTAssert(false) 185 | } 186 | 187 | let r21 = n1.get(book1) 188 | if case let .OK(v) = r21 { 189 | XCTAssertEqual(v, "Suzuki") 190 | } else { 191 | XCTAssert(false) 192 | } 193 | 194 | let r22 = n2.get(book1) 195 | if case let .Error(v) = r22 { 196 | XCTAssertEqual(v, LensErrorType.ArrayIndexOutOfBounds) 197 | } else { 198 | XCTAssert(false) 199 | } 200 | } 201 | 202 | func testArraySet() { 203 | let c0 = Book.Lenses.authors.compose(ArrayIndexLens(at: 0)) 204 | let c1 = Book.Lenses.authors.compose(ArrayIndexLens(at: 1)) 205 | let c2 = Book.Lenses.authors.compose(ArrayIndexLens(at: 2)) 206 | 207 | let r10 = c0.set(book1, Person(name: "Morita", address: Address(city: "Saitama"), ids: [777])) 208 | if case let .OK(v) = r10 { 209 | XCTAssertEqual(v.authors[0].name, "Morita") 210 | XCTAssertEqual(v.authors[0].address.city, "Saitama") 211 | XCTAssertEqual(v.authors[0].ids, [777]) 212 | 213 | XCTAssertEqual(v.title, "Swift Book") 214 | XCTAssertEqual(v.publisher!.name, "Bigfoot") 215 | XCTAssertEqual(v.authors[1].name, "Suzuki") 216 | } else { 217 | XCTAssert(false) 218 | } 219 | 220 | let r11 = c1.set(book1, Person(name: "Morita", address: Address(city: "Saitama"), ids: [777])) 221 | if case let .OK(v) = r11 { 222 | XCTAssertEqual(v.authors[1].name, "Morita") 223 | XCTAssertEqual(v.authors[1].address.city, "Saitama") 224 | XCTAssertEqual(v.authors[1].ids, [777]) 225 | 226 | XCTAssertEqual(v.title, "Swift Book") 227 | XCTAssertEqual(v.publisher!.name, "Bigfoot") 228 | XCTAssertEqual(v.authors[0].name, "Yamada") 229 | } else { 230 | XCTAssert(false) 231 | } 232 | 233 | let r12 = c2.set(book1, Person(name: "Morita", address: Address(city: "Saitama"), ids: [777])) 234 | if case let .Error(v) = r12 { 235 | XCTAssertEqual(v, LensErrorType.ArrayIndexOutOfBounds) 236 | } else { 237 | XCTAssert(false) 238 | } 239 | 240 | let n0 = Book.Lenses.authors.compose(ArrayIndexLens(at: 0)).compose(Person.Lenses.name) 241 | let n1 = Book.Lenses.authors.compose(ArrayIndexLens(at: 1)).compose(Person.Lenses.name) 242 | let n2 = Book.Lenses.authors.compose(ArrayIndexLens(at: 2)).compose(Person.Lenses.name) 243 | 244 | let r20 = n0.set(book1, "Yamashita") 245 | if case let .OK(v) = r20 { 246 | XCTAssertEqual(v.authors[0].name, "Yamashita") 247 | XCTAssertEqual(v.authors[0].address.city, "Tokyo") 248 | XCTAssertEqual(v.authors[0].ids, []) 249 | 250 | XCTAssertEqual(v.title, "Swift Book") 251 | XCTAssertEqual(v.publisher!.name, "Bigfoot") 252 | XCTAssertEqual(v.authors[1].name, "Suzuki") 253 | } else { 254 | XCTAssert(false) 255 | } 256 | 257 | let r21 = n1.set(book1, "Yamashita") 258 | if case let .OK(v) = r21 { 259 | XCTAssertEqual(v.authors[1].name, "Yamashita") 260 | XCTAssertEqual(v.authors[1].address.city, "Osaka") 261 | XCTAssertEqual(v.authors[1].ids, [123, 456]) 262 | 263 | XCTAssertEqual(v.title, "Swift Book") 264 | XCTAssertEqual(v.publisher!.name, "Bigfoot") 265 | XCTAssertEqual(v.authors[0].name, "Yamada") 266 | } else { 267 | XCTAssert(false) 268 | } 269 | 270 | let r22 = n2.set(book1, "Morita") 271 | if case let .Error(v) = r22 { 272 | XCTAssertEqual(v, LensErrorType.ArrayIndexOutOfBounds) 273 | } else { 274 | XCTAssert(false) 275 | } 276 | } 277 | 278 | 279 | func testTryGet1() { 280 | let c = Book.Lenses.publisher.compose(OptionalUnwrapLens()).compose(Company.Lenses.name).compose(OptionalUnwrapLens()) 281 | 282 | let expectation = expectationWithDescription("") 283 | 284 | do { 285 | let v = try c.tryGet(book1) 286 | XCTAssertEqual(v, "Bigfoot") 287 | expectation.fulfill() 288 | } catch { 289 | } 290 | 291 | waitForExpectationsWithTimeout(3) { (error) in 292 | XCTAssertNil(error, "\(error)") 293 | } 294 | } 295 | 296 | func testTryGet2() { 297 | let c = Book.Lenses.publisher.compose(OptionalUnwrapLens()).compose(Company.Lenses.name).compose(OptionalUnwrapLens()) 298 | 299 | let expectation = expectationWithDescription("") 300 | 301 | do { 302 | _ = try c.tryGet(book2) 303 | } catch { 304 | expectation.fulfill() 305 | } 306 | waitForExpectationsWithTimeout(3) { (error) in 307 | XCTAssertNil(error, "\(error)") 308 | } 309 | } 310 | 311 | func testTrySet1() { 312 | let c = Book.Lenses.publisher.compose(OptionalUnwrapLens()).compose(Company.Lenses.name).compose(OptionalUnwrapLens()) 313 | 314 | let expectation = expectationWithDescription("") 315 | 316 | do { 317 | let v = try c.trySet(book1, "Gold Experience") 318 | XCTAssertEqual(v.publisher!.name, "Gold Experience") 319 | 320 | XCTAssertEqual(v.title, "Swift Book") 321 | XCTAssertEqual(v.authors[0].name, "Yamada") 322 | XCTAssertEqual(v.authors[1].name, "Suzuki") 323 | expectation.fulfill() 324 | } catch { 325 | } 326 | 327 | waitForExpectationsWithTimeout(3) { (error) in 328 | XCTAssertNil(error, "\(error)") 329 | } 330 | } 331 | 332 | func testTrySet2() { 333 | let c = Book.Lenses.publisher.compose(OptionalUnwrapLens()).compose(Company.Lenses.name).compose(OptionalUnwrapLens()) 334 | 335 | let expectation = expectationWithDescription("") 336 | 337 | do { 338 | _ = try c.trySet(book2, "Bad Experience") 339 | } catch { 340 | expectation.fulfill() 341 | } 342 | waitForExpectationsWithTimeout(3) { (error) in 343 | XCTAssertNil(error, "\(error)") 344 | } 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /LensyTests/LensHelperTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LensHelperTests.swift 3 | // Lensy 4 | // 5 | // Created by Safx Developer on 2016/02/13. 6 | // Copyright © 2016年 Safx Developers. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Lensy 11 | 12 | class LensHelperTests: XCTestCase { 13 | let person1 = Person(name: "Yamada", address: Address(city: "Tokyo"), ids: []) 14 | var person2 = Person(name: "Suzuki", address: Address(city: "Osaka"), ids: [123, 456]) 15 | var person3 = Person(name: "Takeda", address: Address(city: "Okinawa"), ids: [789]) 16 | var company1 = Company(name: "Bigfoot", address: Address(city: "Nagoya")) 17 | var company2 = Company(name: nil, address: Address(city: "Tiba")) 18 | var book1: Book! 19 | var book2: Book! 20 | var book3: Book! 21 | 22 | override func setUp() { 23 | book1 = Book(title: "Swift Book", authors: [person1, person2], publisher: company1) 24 | book2 = Book(title: "Objective-C Book", authors: [person3], publisher: company2) 25 | book3 = Book(title: "LLVM Book", authors: [person3], publisher: nil) 26 | } 27 | 28 | func testHelperGet() { 29 | let r1 = Person.$.address.city.get(person1) 30 | if case let .OK(v) = r1 { 31 | XCTAssertEqual(v, "Tokyo") 32 | } else { 33 | XCTAssert(false) 34 | } 35 | 36 | let r2 = Book.$.publisher.unwrap.address.city.get(book1) 37 | if case let .OK(v) = r2 { 38 | XCTAssertEqual(v, "Nagoya") 39 | } else { 40 | XCTAssert(false) 41 | } 42 | 43 | let r3 = Book.$.publisher.unwrap.address.city.get(book3) 44 | if case let .Error(v) = r3 { 45 | XCTAssertEqual(v, LensErrorType.OptionalNone) 46 | } else { 47 | XCTAssert(false) 48 | } 49 | 50 | let r4 = Book.$.authors[1].name.get(book1) 51 | if case let .OK(v) = r4 { 52 | XCTAssertEqual(v, "Suzuki") 53 | } else { 54 | XCTAssert(false) 55 | } 56 | 57 | let r5 = Book.$.authors[1].name.get(book2) 58 | if case let .Error(v) = r5 { 59 | XCTAssertEqual(v, LensErrorType.ArrayIndexOutOfBounds) 60 | } else { 61 | XCTAssert(false) 62 | } 63 | } 64 | 65 | func testHelperSet() { 66 | let r1 = Person.$.address.city.set(person1, "Sapporo") 67 | if case let .OK(v) = r1 { 68 | XCTAssertEqual(v.address.city, "Sapporo") 69 | } else { 70 | XCTAssert(false) 71 | } 72 | 73 | let r2 = Book.$.publisher.unwrap.address.city.set(book1, "Yokohama") 74 | if case let .OK(v) = r2 { 75 | XCTAssertEqual(v.publisher!.address.city, "Yokohama") 76 | } else { 77 | XCTAssert(false) 78 | } 79 | 80 | let r3 = Book.$.publisher.unwrap.address.city.set(book3, "Nara") 81 | if case let .Error(v) = r3 { 82 | XCTAssertEqual(v, LensErrorType.OptionalNone) 83 | } else { 84 | XCTAssert(false) 85 | } 86 | 87 | let r4 = Book.$.authors[1].name.set(book1, "Nakano") 88 | if case let .OK(v) = r4 { 89 | XCTAssertEqual(v.authors[1].name, "Nakano") 90 | } else { 91 | XCTAssert(false) 92 | } 93 | 94 | let r5 = Book.$.authors[1].name.set(book2, "Wada") 95 | if case let .Error(v) = r5 { 96 | XCTAssertEqual(v, LensErrorType.ArrayIndexOutOfBounds) 97 | } else { 98 | XCTAssert(false) 99 | } 100 | } 101 | 102 | func testHelperModify() { 103 | let r1 = Book.$.authors[1].ids.modify(book1) { $0 + [-16] } 104 | if case let .OK(v) = r1 { 105 | XCTAssertEqual(v.authors[1].ids, [123, 456, -16]) 106 | } else { 107 | XCTAssert(false) 108 | } 109 | 110 | let r2 = Book.$.authors[1].ids.modify(book2) { $0 + [-16] } 111 | if case let .Error(v) = r2 { 112 | XCTAssertEqual(v, LensErrorType.ArrayIndexOutOfBounds) 113 | } else { 114 | XCTAssert(false) 115 | } 116 | } 117 | 118 | func testHelperModifyMap() { 119 | let r1 = Book.$.authors[1].ids.modifyMap(book1) { 0 - $0 } 120 | if case let .OK(v) = r1 { 121 | XCTAssertEqual(v.authors[1].ids, [-123, -456]) 122 | } else { 123 | XCTAssert(false) 124 | } 125 | 126 | let r2 = Book.$.authors[1].ids.modifyMap(book2) { 0 - $0 } 127 | if case let .Error(v) = r2 { 128 | XCTAssertEqual(v, LensErrorType.ArrayIndexOutOfBounds) 129 | } else { 130 | XCTAssert(false) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /LensyTests/LensResultTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LensResultTests.swift 3 | // Lensy 4 | // 5 | // Created by Safx Developer on 2016/02/13. 6 | // Copyright © 2016年 Safx Developers. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Lensy 11 | 12 | class LensResultTests: XCTestCase { 13 | 14 | func testThenOK() { 15 | let r = LensResult.OK("").then { v -> LensResult in 16 | return .OK(99) 17 | } 18 | 19 | if case let .OK(v) = r { 20 | XCTAssertEqual(v, 99) 21 | } else { 22 | XCTAssert(false) 23 | } 24 | } 25 | 26 | func testThenError() { 27 | let r = LensResult.Error(.OptionalNone).then { v -> LensResult in 28 | return .OK(99) 29 | } 30 | 31 | if case let .Error(v) = r { 32 | XCTAssertEqual(v, LensErrorType.OptionalNone) 33 | } else { 34 | XCTAssert(false) 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /LensyTests/LensyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LensyTests.swift 3 | // LensyTests 4 | // 5 | // Created by Safx Developer on 2016/02/12. 6 | // Copyright © 2016年 Safx Developers. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Lensy 11 | 12 | class LensyTests: XCTestCase { 13 | 14 | func testEnumStruct() { 15 | let e = EnumTest(gender: .Female) 16 | let r1 = EnumTest.Lenses.gender.get(e) 17 | if case let .OK(v) = r1 { 18 | XCTAssertEqual(v, Gender.Female) 19 | } else { 20 | XCTAssert(false) 21 | } 22 | 23 | let r2 = EnumTest.Lenses.gender.set(e, .Unknown) 24 | if case let .OK(v) = r2 { 25 | XCTAssertEqual(v.gender, Gender.Unknown) 26 | } else { 27 | XCTAssert(false) 28 | } 29 | } 30 | 31 | func testEnumStruct2() { 32 | let e = EnumTest(gender: .Female) 33 | let r1 = EnumTest.$.gender.get(e) 34 | if case let .OK(v) = r1 { 35 | XCTAssertEqual(v, Gender.Female) 36 | } else { 37 | XCTAssert(false) 38 | } 39 | 40 | let r2 = EnumTest.$.gender.set(e, .Unknown) 41 | if case let .OK(v) = r2 { 42 | XCTAssertEqual(v.gender, Gender.Unknown) 43 | } else { 44 | XCTAssert(false) 45 | } 46 | } 47 | 48 | } 49 | 50 | // MARK: - Enum Test 51 | 52 | public enum Gender { 53 | case Male 54 | case Female 55 | case Unknown 56 | } 57 | 58 | public struct EnumTest { 59 | public let gender: Gender 60 | 61 | struct Lenses { 62 | static let gender = Lens( 63 | g: { $0.gender }, 64 | s: { (this, newValue) in EnumTest(gender: newValue) } 65 | ) 66 | } 67 | static var $: EnumTestLensHelper { 68 | return EnumTestLensHelper(lens: createIdentityLens()) 69 | } 70 | } 71 | 72 | struct EnumTestLensHelper: LensHelperType { 73 | typealias Part = EnumTest 74 | let lens: Lens 75 | var gender: LensHelper { 76 | return LensHelper(parent: self, lens: EnumTest.Lenses.gender) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /LensyTests/TestStructs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestStructs.swift 3 | // Lensy 4 | // 5 | // Created by Safx Developer on 2016/02/13. 6 | // Copyright © 2016年 Safx Developers. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Lensy 11 | 12 | 13 | public struct Book { 14 | public let title: String 15 | public let authors: [Person] 16 | public let publisher: Company? 17 | 18 | struct Lenses { 19 | static let title = Lens( 20 | g: { $0.title }, 21 | s: { (this, newValue) in Book(title: newValue, authors: this.authors, publisher: this.publisher) } 22 | ) 23 | static let authors = Lens( 24 | g: { $0.authors }, 25 | s: { (this, newValue) in Book(title: this.title, authors: newValue, publisher: this.publisher) } 26 | ) 27 | static let publisher = Lens( 28 | g: { $0.publisher }, 29 | s: { (this, newValue) in Book(title: this.title, authors: this.authors, publisher: newValue) } 30 | ) 31 | } 32 | static var $: BookLensHelper { 33 | return BookLensHelper(lens: createIdentityLens()) 34 | } 35 | } 36 | 37 | struct BookLensHelper: LensHelperType { 38 | typealias Part = Book 39 | let lens: Lens 40 | var title: LensHelper { 41 | return LensHelper(parent: self, lens: Book.Lenses.title) 42 | } 43 | var authors: ArrayLensHelper> { 44 | return ArrayLensHelper>(parent: self, lens: Book.Lenses.authors) 45 | } 46 | var publisher: OptionalLensHelper> { 47 | return OptionalLensHelper>(parent: self, lens: Book.Lenses.publisher) 48 | } 49 | } 50 | 51 | 52 | public struct Company { 53 | public let name: String? 54 | public let address: Address 55 | 56 | struct Lenses { 57 | static let name = Lens( 58 | g: { $0.name }, 59 | s: { (this, newValue) in Company(name: newValue, address: this.address) } 60 | ) 61 | static let address = Lens( 62 | g: { $0.address }, 63 | s: { (this, newValue) in Company(name: this.name, address: newValue) } 64 | ) 65 | } 66 | static var $: CompanyLensHelper { 67 | return CompanyLensHelper(lens: createIdentityLens()) 68 | } 69 | } 70 | 71 | struct CompanyLensHelper: LensHelperType { 72 | typealias Part = Company 73 | let lens: Lens 74 | var name: OptionalLensHelper> { 75 | return OptionalLensHelper>(parent: self, lens: Company.Lenses.name) 76 | } 77 | var address: AddressLensHelper { 78 | return AddressLensHelper(parent: self, lens: Company.Lenses.address) 79 | } 80 | } 81 | 82 | 83 | public struct Person { 84 | public let name: String 85 | public let address: Address 86 | public let ids: [Int] 87 | 88 | struct Lenses { 89 | static let name = Lens( 90 | g: { $0.name }, 91 | s: { (this, newValue) in Person(name: newValue, address: this.address, ids: this.ids) } 92 | ) 93 | static let address = Lens( 94 | g: { $0.address }, 95 | s: { (this, newValue) in Person(name: this.name, address: newValue, ids: this.ids) } 96 | ) 97 | static let ids = Lens( 98 | g: { $0.ids }, 99 | s: { (this, newValue) in Person(name: this.name, address: this.address, ids: newValue) } 100 | ) 101 | } 102 | static var $: PersonLensHelper { 103 | return PersonLensHelper(lens: createIdentityLens()) 104 | } 105 | } 106 | 107 | struct PersonLensHelper: LensHelperType { 108 | typealias Part = Person 109 | let lens: Lens 110 | var name: LensHelper { 111 | return LensHelper(parent: self, lens: Person.Lenses.name) 112 | } 113 | var address: AddressLensHelper { 114 | return AddressLensHelper(parent: self, lens: Person.Lenses.address) 115 | } 116 | var ids: ArrayLensHelper> { 117 | return ArrayLensHelper>(parent: self, lens: Person.Lenses.ids) 118 | } 119 | } 120 | 121 | 122 | public struct Address { 123 | public let city: String 124 | 125 | struct Lenses { 126 | static let city = Lens( 127 | g: { $0.city }, 128 | s: { (this, newValue) in Address(city: newValue) } 129 | ) 130 | } 131 | static var $: AddressLensHelper
{ 132 | return AddressLensHelper
(lens: createIdentityLens()) 133 | } 134 | } 135 | 136 | struct AddressLensHelper: LensHelperType { 137 | typealias Part = Address 138 | let lens: Lens 139 | var city: LensHelper { 140 | return LensHelper(parent: self, lens: Address.Lenses.city) 141 | } 142 | } 143 | 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![TravisCI](http://img.shields.io/travis/safx/Lensy.svg?style=flat)](https://travis-ci.org/safx/Lensy) 2 | [![codecov.io](https://codecov.io/github/safx/Lensy/coverage.svg?branch=master)](https://codecov.io/github/safx/Lensy?branch=master) 3 | 4 | # Lensy 5 | 6 | Simple Lens for Swift 7 | 8 | ## Example 9 | 10 | Here is an example `struct`s: 11 | 12 | ```swift 13 | struct Person { 14 | let name: String 15 | let address: Address 16 | } 17 | 18 | struct Address { 19 | let city: String 20 | } 21 | ``` 22 | 23 | You have to create lenses you want to use first. 24 | 25 | ```swift 26 | let name = Lens( 27 | g: { $0.name }, 28 | s: { (this, newValue) in Person(name: newValue, address: this.address) } 29 | ) 30 | let address = Lens( 31 | g: { $0.address }, 32 | s: { (this, newValue) in Person(name: this.name, address: newValue) } 33 | ) 34 | let city = Lens( 35 | g: { $0.city }, 36 | s: { (this, newValue) in Address(city: newValue) } 37 | ) 38 | ``` 39 | 40 | You can use the lenses you've defined as follows: 41 | 42 | ```swift 43 | let p = Person(name: "Yamada", 44 | address: Address(city: "Tokyo")) 45 | 46 | name.get(p) // OK("Yamada") 47 | name.set(p, "Suzuki") // OK(Person(name:"Suzuki", address: Address(city: "Tokyo")) 48 | 49 | let composed = address.compose(city) 50 | composed.get(p) // OK("Tokyo") 51 | composed.set(p, "Osaka") // OK(Person(name:"Yamada", address: Address(city: "Osaka")) 52 | composed.modify(p) { $0.uppercaseString } // OK(Person(name:"Yamada", address: Address(city: "TOKYO")) 53 | ``` 54 | 55 | ## ArrayIndexLens, OptionalUnwrapLens 56 | 57 | Lensy can access elements of arrays by using `ArrayIndexLens` and unwrap optionals by `OptionalUnwrapLens`. 58 | 59 | ```swift 60 | struct Book { 61 | let title: String 62 | let authors: [Person] 63 | let editor: Person? 64 | } 65 | 66 | let authors = Lens( 67 | g: { $0.authors }, 68 | s: { (this, newValue) in Book(title: this.title, authors: newValue, editor: this.editor) } 69 | ) 70 | 71 | let editor = Lens( 72 | g: { $0.editor }, 73 | s: { (this, newValue) in Book(title: this.title, authors: this.authors, editor: newValue) } 74 | ) 75 | 76 | 77 | let book = Book( 78 | title: "Swift Book", 79 | authors: [ 80 | Person(name: "Yamada", address: Address(city: "Tokyo")), 81 | Person(name: "Tanaka", address: Address(city: "Yokohama")) 82 | ], 83 | editor: Person(name: "Sasaki", address: Address(city: "Sapporo"))) 84 | 85 | authors.compose(ArrayIndexLens(at: 0)).compose(name).get(book) // OK("Yamada") 86 | authors.compose(ArrayIndexLens(at: 1)).compose(composed).get(book) // OK("Yokohama") 87 | editor.compose(OptionalUnwrapLens()).compose(name).get(book) // OK("Sasaki") 88 | ``` 89 | 90 | Lensy returns `Error` when outside of an array is accessed. 91 | 92 | ```swift 93 | authors.compose(ArrayIndexLens(at: 2)).compose(composed).get(book) // Error(ArrayIndexOutOfBounds) 94 | ``` 95 | 96 | # LensHelper 97 | 98 | You can generate some lenses and lens helper classes by using [swift-idl](https://github.com/safx/swift-idl). 99 | 100 | After code generation, you can use lenses defined in the inner struct `Lenses`: 101 | 102 | ```swift 103 | Book.Lenses.authors 104 | .compose(ArrayIndexLens(at: 0)) 105 | .compose(Person.Lenses.address) 106 | .compose(Address.Lenses.city) 107 | .get(book) 108 | ``` 109 | 110 | Or you can use `LensHelper` through `$`: 111 | 112 | ```swift 113 | Book.$.authors[0].address.city.get(book) 114 | ``` 115 | 116 | You can use code completion in Xcode when you use `LensHelper`. 117 | 118 | ![](Images/lens-helper-completion.gif) 119 | 120 | You can use `[]` in LensHelper to access an element of array. 121 | 122 | ![](Images/lens-result2.gif) 123 | --------------------------------------------------------------------------------