├── .github └── CODEOWNERS ├── .gitignore ├── .jazzy.yml ├── .swiftlint.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── Makefile ├── Mechanica.podspec ├── Mechanica.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ ├── IDETemplateMacros.plist │ └── xcschemes │ ├── Cleaning.xcscheme │ ├── Mechanica iOS.xcscheme │ ├── Mechanica macOS.xcscheme │ ├── Mechanica tvOS.xcscheme │ └── Mechanica watchOS.xcscheme ├── Package.swift ├── README.md ├── Sources ├── AVFoundation │ └── AVAsset+Utils.swift ├── AppKit │ └── NSImage+Utils.swift ├── CoreGraphics │ ├── CGFloat+Utils.swift │ ├── CGPoint+Utils.swift │ ├── CGRect+Utils.swift │ └── CGSize+Utils.swift ├── Foundation │ ├── Bundle+Info.swift │ ├── Calendar+Utils.swift │ ├── Data+Utils.swift │ ├── Date+Utils.swift │ ├── Dictionary+FoundationUtils.swift │ ├── DispatchQueue+Utils.swift │ ├── FileManager+Utils.swift │ ├── FoundationUtils.swift │ ├── Locale.swift │ ├── NSAttributedString+Utils.swift │ ├── NSMutableAttributedString+Utils.swift │ ├── NSObjectProtocol+Utils.swift │ ├── NSPredicate+Utils.swift │ ├── ProcessInfo+Utils.swift │ ├── Stat+Utils.swift │ ├── String+FoundationUtils.swift │ ├── URL+Utils.swift │ ├── URLRequest+Utils.swift │ └── UserDefaults+Utils.swift ├── Mechanica.swift ├── Shared │ ├── Color+Utils.swift │ ├── Font+Utils.swift │ ├── Image+Utils.swift │ └── NSDirectionalEdgeInsets+Utils.swift ├── StandardLibrary │ ├── BinaryFloatingPoint+Utils.swift │ ├── BinaryInteger+Utils.swift │ ├── Bool+Utils.swift │ ├── Character+Utils.swift │ ├── Collection+Utils.swift │ ├── Dictionary+Utils.swift │ ├── FixedWidthInteger+Utils.swift │ ├── FloatingPoint+Utils.swift │ ├── Operators.swift │ ├── Optional+Utils.swift │ ├── RangeReplaceableCollection+Utils.swift │ ├── Sequence+Utils.swift │ ├── SignedInteger.swift │ ├── String+Utils.swift │ └── Unicode+Utils.swift └── UIKit │ ├── UIButton+Utils.swift │ ├── UIDevice+Utils.swift │ ├── UIEdgeInsets+Utils.swift │ ├── UIGestureRecognizer+Utils.swift │ ├── UIImage+Utils.swift │ ├── UILayoutPriority+Utils.swift │ ├── UIStackView+Utils.swift │ ├── UIView+Utils.swift │ ├── UIViewController+Utils.swift │ └── UIWindow+Utils.swift ├── Support ├── Info-Tests.plist ├── Info-tvOS.plist ├── Info.plist └── Mechanica.h ├── Tests ├── AVFoundationTests │ └── AVAssetUtilsTests.swift ├── AppKitTests │ ├── NSImageUtilsTests.swift │ └── _Resources.swift ├── CoreGraphicsTests │ ├── CGFloatUtilsTests.swift │ ├── CGPointUtilsTests.swift │ ├── CGRectUtilsTests.swift │ └── CGSizeUtilsTests.swift ├── FoundationTests │ ├── BundleInfoTests.swift │ ├── CalendarUtilsTests.swift │ ├── DataUtilsTests.swift │ ├── DateUtilsTests.swift │ ├── DictionaryFoundationUtilsTests.swift │ ├── DispatchQueueUtilsTests.swift │ ├── FileManagerUtilsTests.swift │ ├── FoundationUtilsTests.swift │ ├── LocaleUtilsTests.swift │ ├── NSAttributedStringUtilsTests.swift │ ├── NSMutableAttributedStringUtilsTests.swift │ ├── NSObjectProtocolUtils.swift │ ├── NSPredicateUtilsTests.swift │ ├── ProcessInfoTests.swift │ ├── StatUtilsTests.swift │ ├── StringFoundationUtilsTests.swift │ ├── URLRequestUtilsTests.swift │ ├── URLUtilsTests.swift │ └── UserDefaultsUtilsTests.swift ├── LinuxMain.swift ├── Resources │ ├── Images │ │ ├── Modified │ │ │ ├── AspectScaleToFill │ │ │ │ ├── apple-aspect.scaled.to.fill-30x60-@1x.png │ │ │ │ ├── apple-aspect.scaled.to.fill-30x60-@2x.png │ │ │ │ ├── apple-aspect.scaled.to.fill-30x60-@3x.png │ │ │ │ ├── apple-aspect.scaled.to.fill-50x50-@1x.png │ │ │ │ ├── apple-aspect.scaled.to.fill-50x50-@2x.png │ │ │ │ ├── apple-aspect.scaled.to.fill-50x50-@3x.png │ │ │ │ ├── apple-aspect.scaled.to.fill-60x30-@1x.png │ │ │ │ ├── apple-aspect.scaled.to.fill-60x30-@2x.png │ │ │ │ └── apple-aspect.scaled.to.fill-60x30-@3x.png │ │ │ ├── AspectScaleToFit │ │ │ │ ├── apple-aspect.scaled.to.fit-30x60-@1x.png │ │ │ │ ├── apple-aspect.scaled.to.fit-30x60-@2x.png │ │ │ │ ├── apple-aspect.scaled.to.fit-30x60-@3x.png │ │ │ │ ├── apple-aspect.scaled.to.fit-50x50-@1x.png │ │ │ │ ├── apple-aspect.scaled.to.fit-50x50-@2x.png │ │ │ │ ├── apple-aspect.scaled.to.fit-50x50-@3x.png │ │ │ │ ├── apple-aspect.scaled.to.fit-60x30-@1x.png │ │ │ │ ├── apple-aspect.scaled.to.fit-60x30-@2x.png │ │ │ │ └── apple-aspect.scaled.to.fit-60x30-@3x.png │ │ │ ├── Circle │ │ │ │ └── apple-circle.png │ │ │ ├── Radius │ │ │ │ └── apple-radius-20.png │ │ │ └── Scale │ │ │ │ ├── apple-scaled-30x60-@1x.png │ │ │ │ ├── apple-scaled-30x60-@2x.png │ │ │ │ ├── apple-scaled-30x60-@3x.png │ │ │ │ ├── apple-scaled-50x50-@1x.png │ │ │ │ ├── apple-scaled-50x50-@2x.png │ │ │ │ ├── apple-scaled-50x50-@3x.png │ │ │ │ ├── apple-scaled-60x30-@1x.png │ │ │ │ ├── apple-scaled-60x30-@2x.png │ │ │ │ └── apple-scaled-60x30-@3x.png │ │ └── Original │ │ │ └── apple.jpg │ ├── Resource.swift │ ├── glasses.png │ ├── glasses_without_alpha.jpeg │ └── robot.png ├── SharedTests │ ├── Color+Flat.swift │ ├── ColorUtilsTests.swift │ ├── FontUtilsTests.swift │ ├── ImageUtilsTests.swift │ ├── NSDirectionalEdgeInsetsUtilsTests.swift │ └── _Resources.swift ├── StandardLibraryTests │ ├── BinaryFloatingPointUtilsTests.swift │ ├── BinaryIntegerUtilsTests.swift │ ├── BoolUtilsTests.swift │ ├── CharacterUtilsTests.swift │ ├── CollectionUtilsTests.swift │ ├── DictionaryUtilsTests.swift │ ├── FloatingPointUtilsTests.swift │ ├── OperatorsTests.swift │ ├── OptionalUtilsTests.swift │ ├── RangeReplaceableCollectionUtilsTests.swift │ ├── SequenceUtilsTests.swift │ ├── SignedIntegerUtilsTests.swift │ ├── StringUtilsTests.swift │ └── UnsignedIntegerUtilsTests.swift └── UIKitTests │ ├── UIButtonUtilsTests.swift │ ├── UIDeviceUtilsTests.swift │ ├── UIEdgeInsetsUtilsTests.swift │ ├── UIGestureRecognizerUtilsTests.swift │ ├── UIImageUtilsTests.swift │ ├── UILayoutPriorityUtilsTests.swift │ ├── UIStackViewUtilsTests.swift │ ├── UIViewControllerUtilsTests.swift │ ├── UIViewUtilsTests.swift │ ├── UIWindowUtilsTests.swift │ └── _Resources.swift ├── codecov.yml └── docs ├── .gitignore ├── Extensions.html ├── Extensions ├── AVAsset.html ├── BinaryFloatingPoint.html ├── BinaryInteger.html ├── Bool.html ├── Bundle.html ├── CGFloat.html ├── CGPoint.html ├── CGRect.html ├── CGSize.html ├── Calendar.html ├── Character.html ├── Collection.html ├── Color.html ├── Data.html ├── Date.html ├── Dictionary.html ├── DispatchQueue.html ├── ExpressibleByIntegerLiteral.html ├── FileManager.html ├── FloatingPoint.html ├── Font.html ├── Image.html ├── Int.html ├── Int16.html ├── Int32.html ├── Int64.html ├── Int8.html ├── Locale.html ├── MutableCollection.html ├── NSAttributedString.html ├── NSMutableAttributedString.html ├── NSObjectProtocol.html ├── NSPredicate.html ├── Optional.html ├── ProcessInfo.html ├── RangeReplaceableCollection.html ├── Sequence.html ├── SignedInteger.html ├── String.html ├── UIButton.html ├── UIDevice.html ├── UIEdgeInsets.html ├── UIGestureRecognizer.html ├── UIImage.html ├── UIStackView.html ├── UIView.html ├── UIViewController.html ├── UIWindow.html ├── UInt.html ├── UInt16.html ├── UInt32.html ├── UInt64.html ├── UInt8.html ├── URL.html ├── Unicode.Scalar.html ├── Unicode.html ├── Unicode │ └── Scalar.html └── UserDefaults.html ├── Functions.html ├── Functions └── ???(_:_:).html ├── Protocols.html ├── Protocols ├── AssociatedValueSupporting.html └── FixedWidthIntegerRandomizable.html ├── Typealiases.html ├── badge.svg ├── css ├── highlight.css └── jazzy.css ├── docsets ├── Mechanica.docset │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ ├── Documents │ │ ├── Extensions.html │ │ ├── Extensions │ │ │ ├── AVAsset.html │ │ │ ├── BinaryFloatingPoint.html │ │ │ ├── BinaryInteger.html │ │ │ ├── Bool.html │ │ │ ├── Bundle.html │ │ │ ├── CGFloat.html │ │ │ ├── CGPoint.html │ │ │ ├── CGRect.html │ │ │ ├── CGSize.html │ │ │ ├── Calendar.html │ │ │ ├── Character.html │ │ │ ├── Collection.html │ │ │ ├── Color.html │ │ │ ├── Data.html │ │ │ ├── Date.html │ │ │ ├── Dictionary.html │ │ │ ├── DispatchQueue.html │ │ │ ├── ExpressibleByIntegerLiteral.html │ │ │ ├── FileManager.html │ │ │ ├── FloatingPoint.html │ │ │ ├── Font.html │ │ │ ├── Image.html │ │ │ ├── Int.html │ │ │ ├── Int16.html │ │ │ ├── Int32.html │ │ │ ├── Int64.html │ │ │ ├── Int8.html │ │ │ ├── Locale.html │ │ │ ├── MutableCollection.html │ │ │ ├── NSAttributedString.html │ │ │ ├── NSMutableAttributedString.html │ │ │ ├── NSObjectProtocol.html │ │ │ ├── NSPredicate.html │ │ │ ├── Optional.html │ │ │ ├── ProcessInfo.html │ │ │ ├── RangeReplaceableCollection.html │ │ │ ├── Sequence.html │ │ │ ├── SignedInteger.html │ │ │ ├── String.html │ │ │ ├── UIButton.html │ │ │ ├── UIDevice.html │ │ │ ├── UIEdgeInsets.html │ │ │ ├── UIGestureRecognizer.html │ │ │ ├── UIImage.html │ │ │ ├── UIStackView.html │ │ │ ├── UIView.html │ │ │ ├── UIViewController.html │ │ │ ├── UIWindow.html │ │ │ ├── UInt.html │ │ │ ├── UInt16.html │ │ │ ├── UInt32.html │ │ │ ├── UInt64.html │ │ │ ├── UInt8.html │ │ │ ├── URL.html │ │ │ ├── Unicode.Scalar.html │ │ │ ├── Unicode.html │ │ │ ├── Unicode │ │ │ │ └── Scalar.html │ │ │ └── UserDefaults.html │ │ ├── Functions.html │ │ ├── Functions │ │ │ └── ???(_:_:).html │ │ ├── Protocols.html │ │ ├── Protocols │ │ │ ├── AssociatedValueSupporting.html │ │ │ └── FixedWidthIntegerRandomizable.html │ │ ├── Typealiases.html │ │ ├── badge.svg │ │ ├── css │ │ │ ├── highlight.css │ │ │ └── jazzy.css │ │ ├── img │ │ │ ├── carat.png │ │ │ ├── dash.png │ │ │ └── gh.png │ │ ├── index.html │ │ ├── js │ │ │ ├── jazzy.js │ │ │ └── jquery.min.js │ │ └── search.json │ │ └── docSet.dsidx └── Mechanica.tgz ├── img ├── carat.png ├── dash.png └── gh.png ├── index.html ├── js ├── jazzy.js └── jquery.min.js └── search.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Default owners for everything in the repo. 2 | * @alemar11 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Miscellanea 5 | Gemfile.lock 6 | 7 | # Xcode 8 | # 9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 10 | 11 | ## Build generated 12 | build/ 13 | DerivedData/ 14 | 15 | ## Various settings 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata/ 25 | *~ 26 | 27 | ## Other 28 | *.xccheckout 29 | *.moved-aside 30 | *.xcuserstate 31 | *.xcscmblueprint 32 | 33 | ## Obj-C/Swift specific 34 | *.hmap 35 | *.ipa 36 | *.dSYM.zip 37 | *.dSYM 38 | 39 | ## Playgrounds 40 | timeline.xctimeline 41 | playground.xcworkspace 42 | 43 | # Swift Package Manager 44 | # 45 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 46 | # Packages/ 47 | .build/ 48 | 49 | ## Bundler 50 | .bundle 51 | 52 | # CocoaPods 53 | # 54 | # We recommend against adding the Pods directory to your .gitignore. However 55 | # you should judge for yourself, the pros and cons are mentioned at: 56 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 57 | # 58 | # Pods/ 59 | 60 | # Carthage 61 | # 62 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 63 | # Carthage/Checkouts 64 | 65 | Carthage/Build 66 | 67 | # fastlane 68 | # 69 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 70 | # screenshots whenever they are needed. 71 | # For more information about the recommended setup visit: 72 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 73 | 74 | fastlane/report.xml 75 | fastlane/Preview.html 76 | fastlane/screenshots 77 | fastlane/test_output -------------------------------------------------------------------------------- /.jazzy.yml: -------------------------------------------------------------------------------- 1 | module: Mechanica 2 | author: Alessandro Marzoli 3 | author_url: http://www.tinrobots.org 4 | github_url: https://github.com/tinrobots/mechanica 5 | copyright: "© 2016-2017 [Tin Robots](http://www.tinrobots.org)" 6 | clean: true 7 | podspec: Mechanica.podspec 8 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | 4 | excluded: # paths to ignore during linting. Takes precedence over `included`. 5 | - Carthage 6 | - Pods 7 | - Tests 8 | - Support 9 | - docs 10 | - build 11 | 12 | disabled_rules: # rule identifiers to exclude from running 13 | - fallthrough 14 | - large_tuple 15 | - todo 16 | 17 | cyclomatic_complexity: 18 | - 15 #warning 19 | - 20 #error 20 | 21 | line_length: 22 | - 200 #warning 23 | - 250 #error 24 | 25 | type_body_length: 26 | - 700 #warning 27 | - 1000 #error 28 | 29 | file_length: 30 | - 1000 #warning 31 | - 1200 #error 32 | 33 | function_body_length: 34 | - 125 #warning 35 | - 200 #error 36 | 37 | type_name: 38 | min_length: 3 # only warning 39 | max_length: # warning and error 40 | warning: 50 41 | error: 50 42 | 43 | identifier_name: 44 | excluded: 45 | - id 46 | - i 47 | - j 48 | - k 49 | 50 | large_tuple: 51 | warning: 3 52 | 53 | colon: 54 | apply_to_dictionaries: false 55 | 56 | indentation: 2 57 | 58 | opt_in_rules: # some rules are only opt-in 59 | - closure_end_indentation 60 | - closure_spacing 61 | - syntactic_sugar 62 | - redundant_nil_coalescing 63 | - number_separator 64 | - sorted_imports 65 | - overridden_super_call 66 | - object_literal 67 | - explicit_init 68 | - first_where 69 | - operator_usage_whitespace 70 | 71 | number_separator: 72 | minimum_length: 7 73 | 74 | custom_rules: 75 | explicit_failure_calls: 76 | name: "Avoid asserting 'false'" 77 | regex: '((assert|precondition)\(false)' 78 | message: "Use assertionFailure() or preconditionFailure() instead." 79 | severity: warning 80 | double_space: # from https://github.com/IBM-Swift/Package-Builder 81 | include: "*.swift" 82 | name: "Double space" 83 | regex: '([a-z,A-Z] \s+)' 84 | message: "Double space between keywords" 85 | match_kinds: keyword 86 | severity: warning 87 | comments_space: # from https://github.com/brandenr/swiftlintconfig 88 | name: "Space After Comment" 89 | regex: '(^ *//\w+)' 90 | message: "There should be a space after //" 91 | severity: warning 92 | empty_line_after_guard: # from https://github.com/brandenr/swiftlintconfig 93 | name: "Empty Line After Guard" 94 | regex: '(^ *guard[ a-zA-Z0-9=?.\(\),> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | claide (1.0.2) 11 | cocoapods (1.2.1) 12 | activesupport (>= 4.0.2, < 5) 13 | claide (>= 1.0.1, < 2.0) 14 | cocoapods-core (= 1.2.1) 15 | cocoapods-deintegrate (>= 1.0.1, < 2.0) 16 | cocoapods-downloader (>= 1.1.3, < 2.0) 17 | cocoapods-plugins (>= 1.0.0, < 2.0) 18 | cocoapods-search (>= 1.0.0, < 2.0) 19 | cocoapods-stats (>= 1.0.0, < 2.0) 20 | cocoapods-trunk (>= 1.2.0, < 2.0) 21 | cocoapods-try (>= 1.1.0, < 2.0) 22 | colored2 (~> 3.1) 23 | escape (~> 0.0.4) 24 | fourflusher (~> 2.0.1) 25 | gh_inspector (~> 1.0) 26 | molinillo (~> 0.5.7) 27 | nap (~> 1.0) 28 | ruby-macho (~> 1.1) 29 | xcodeproj (>= 1.4.4, < 2.0) 30 | cocoapods-core (1.2.1) 31 | activesupport (>= 4.0.2, < 5) 32 | fuzzy_match (~> 2.0.4) 33 | nap (~> 1.0) 34 | cocoapods-deintegrate (1.0.1) 35 | cocoapods-downloader (1.6.3) 36 | cocoapods-plugins (1.0.0) 37 | nap 38 | cocoapods-search (1.0.0) 39 | cocoapods-stats (1.0.0) 40 | cocoapods-trunk (1.2.0) 41 | nap (>= 0.8, < 2.0) 42 | netrc (= 0.7.8) 43 | cocoapods-try (1.1.0) 44 | colored2 (3.1.2) 45 | escape (0.0.4) 46 | fourflusher (2.0.1) 47 | fuzzy_match (2.0.4) 48 | gh_inspector (1.0.3) 49 | i18n (0.8.4) 50 | jazzy (0.8.2) 51 | cocoapods (~> 1.0) 52 | mustache (~> 0.99) 53 | open4 54 | redcarpet (~> 3.2) 55 | rouge (~> 1.5) 56 | sass (~> 3.4) 57 | sqlite3 (~> 1.3) 58 | xcinvoke (~> 0.3.0) 59 | liferaft (0.0.6) 60 | minitest (5.10.2) 61 | molinillo (0.5.7) 62 | mustache (0.99.8) 63 | nanaimo (0.2.3) 64 | nap (1.1.0) 65 | netrc (0.7.8) 66 | open4 (1.3.4) 67 | redcarpet (3.5.1) 68 | rouge (1.11.1) 69 | ruby-macho (1.1.0) 70 | sass (3.4.24) 71 | sqlite3 (1.3.13) 72 | thread_safe (0.3.6) 73 | tzinfo (1.2.3) 74 | thread_safe (~> 0.1) 75 | xcinvoke (0.3.0) 76 | liferaft (~> 0.0.6) 77 | xcodeproj (1.5.0) 78 | CFPropertyList (~> 2.3.3) 79 | claide (>= 1.0.2, < 2.0) 80 | colored2 (~> 3.1) 81 | nanaimo (~> 0.2.3) 82 | 83 | PLATFORMS 84 | ruby 85 | 86 | DEPENDENCIES 87 | jazzy 88 | 89 | BUNDLED WITH 90 | 1.16.0 91 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2019 Tin Robots 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #TODO: https://github.com/apple/swift-package-manager/blob/master/Documentation/PackageDescriptionV4_2.md 2 | TEST_RESOURCE_FILE = ./Tests/Resources/Resource.swift 3 | APPKIT_TESTS_FOLDER = ./Tests/AppKitTests 4 | UIKIT_TESTS_FOLDER = ./Tests/UIKitTests 5 | SHARED_TESTS_FOLDER = ./Tests/SharedTests 6 | COPIED_RESOURCE_FILENAME = _Resources.swift 7 | 8 | copyResources: 9 | cp ${TEST_RESOURCE_FILE} ${APPKIT_TESTS_FOLDER}/${COPIED_RESOURCE_FILENAME} 10 | cp ${TEST_RESOURCE_FILE} ${UIKIT_TESTS_FOLDER}/${COPIED_RESOURCE_FILENAME} 11 | cp ${TEST_RESOURCE_FILE} ${SHARED_TESTS_FOLDER}/${COPIED_RESOURCE_FILENAME} -------------------------------------------------------------------------------- /Mechanica.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Mechanica' 3 | s.version = '3.1.0' 4 | s.license = 'MIT' 5 | s.documentation_url = 'http://www.alessandromarzoli.com/Mechanica' 6 | s.summary = 'A library of Swift utils to ease your iOS/macOS/watchOS/tvOS development.' 7 | s.homepage = 'https://github.com/alemar11/Mechanica' 8 | s.authors = { 'Alessandro Marzoli' => 'me@alessandromarzoli.com' } 9 | s.source = { :git => 'https://github.com/alemar11/Mechanica.git', :tag => s.version } 10 | s.requires_arc = true 11 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0'} 12 | 13 | s.swift_version = "5.0" 14 | s.ios.deployment_target = '12.0' 15 | s.osx.deployment_target = '10.14' 16 | s.tvos.deployment_target = '12.0' 17 | s.watchos.deployment_target = '5.0' 18 | 19 | s.source_files = 'Sources/*.swift', 20 | 'Support/*.{h,m}', 21 | 'Sources/AppKit/*.swift', 22 | 'Sources/CoreGraphics/*.swift', 23 | 'Sources/Foundation/*.swift', 24 | 'Sources/Shared/*.swift', 25 | 'Sources/StandardLibrary/*.swift', 26 | 'Sources/UIKit/*.swift' 27 | 28 | s.ios.exclude_files = 'Sources/AppKit/*.swift' 29 | 30 | s.tvos.exclude_files = 'Sources/AppKit/*.swift' 31 | 32 | s.watchos.exclude_files = 'Sources/AppKit/*.swift' 33 | 34 | s.osx.exclude_files = 'Sources/UIKit/*.swift' 35 | 36 | end 37 | -------------------------------------------------------------------------------- /Mechanica.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Mechanica.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Mechanica.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Latest 7 | 8 | 9 | -------------------------------------------------------------------------------- /Mechanica.xcodeproj/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | ___NSHUMANREADABLECOPYRIGHTPLIST___ 7 | NSHUMANREADABLECOPYRIGHTPLIST 8 | Mechanica 9 | 10 | 11 | -------------------------------------------------------------------------------- /Mechanica.xcodeproj/xcshareddata/xcschemes/Cleaning.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Mechanica.xcodeproj/xcshareddata/xcschemes/Mechanica watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 34 | 35 | 36 | 37 | 47 | 48 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | 3 | import PackageDescription 4 | 5 | let package = Package(name: "Mechanica") 6 | package.products = [.library(name: "Mechanica", targets: ["Mechanica"])] 7 | package.targets = [ 8 | .target(name: "Mechanica", path: "Sources"), 9 | .testTarget(name: "FoundationTests", dependencies: ["Mechanica"]), 10 | .testTarget(name: "SharedTests", dependencies: ["Mechanica"]), 11 | .testTarget(name: "StandardLibraryTests", dependencies: ["Mechanica"]), 12 | .testTarget(name: "UIKitTests", dependencies: ["Mechanica"]), 13 | .testTarget(name: "AppKitTests", dependencies: ["Mechanica"]), 14 | .testTarget(name: "AVFoundationTests", dependencies: ["Mechanica"]) 15 | ] 16 | package.swiftLanguageVersions = [.v5] 17 | package.platforms = [.macOS(.v10_14), .iOS(.v12), .tvOS(.v12), .watchOS(.v5)] 18 | 19 | #if os(Linux) 20 | 21 | package.targets = [ 22 | .target(name: "Mechanica", 23 | path: "Sources", 24 | exclude: ["BundleInfoTests.swift"], 25 | sources: ["StandardLibrary", "Foundation"] 26 | ), 27 | .testTarget(name: "StandardLibraryTests", dependencies: ["Mechanica"]), 28 | .testTarget(name: "FoundationTests", 29 | dependencies: ["Mechanica"], 30 | exclude: ["BundleInfoTests.swift"] 31 | ) 32 | ] 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /Sources/AVFoundation/AVAsset+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(AVAsset) 2 | 3 | import AVFoundation 4 | 5 | extension AVAsset { 6 | /// **Mechanica** 7 | /// 8 | /// Generates a thumbnail image. 9 | /// 10 | /// 11 | /// Example: 12 | /// 13 | /// let url = URL(string: "https://link/to/video.mp4")! 14 | /// let asset = AVAsset(url: url) 15 | /// let thumbnail = asset.thumbnail(fromTime: 5) 16 | /// 17 | /// - Parameter time: Seconds into the video where the image should be generated. 18 | /// - Returns: A thumbnail image. 19 | /// - Throws: Throws an error if no thumbnail could be created. 20 | /// - Note: This function may take some time to complete: it's recommended to dispatch the call on another queue if the thumbnail is not generated from a local resource. 21 | public func thumbnail(fromTime time: Float64 = 0) throws -> Image { 22 | let imageGenerator = AVAssetImageGenerator(asset: self) 23 | imageGenerator.appliesPreferredTrackTransform = true 24 | imageGenerator.requestedTimeToleranceAfter = .zero 25 | imageGenerator.requestedTimeToleranceBefore = .zero 26 | 27 | let time = CMTime(seconds: time, preferredTimescale: 1) 28 | var actualTime = CMTime(value: 0, timescale: 0) 29 | let cgImage = try imageGenerator.copyCGImage(at: time, actualTime: &actualTime) 30 | 31 | #if canImport(UIKit) 32 | return Image(cgImage: cgImage) 33 | #elseif canImport(AppKit) 34 | return Image(cgImage: cgImage, size: .zero) 35 | #endif 36 | } 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /Sources/AppKit/NSImage+Utils.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | 3 | import AppKit 4 | 5 | extension NSImage { 6 | /// **Mechanica** 7 | /// 8 | /// - Parameters: 9 | /// - name: The name of the image. 10 | /// - bundle: The bundle containing the image file or asset catalog, if nil the behavior is identical to `init(named:)`. 11 | /// - Returns: The image object associated with the specified filename. 12 | public class func imageNamed(name: String, in bundle: Bundle?) -> NSImage? { 13 | let name = NSImage.Name(name) 14 | 15 | guard let bundle = bundle else { return NSImage(named: name) } 16 | 17 | if let image = bundle.image(forResource: name) { 18 | image.setName(name) 19 | return image 20 | } 21 | 22 | return nil 23 | } 24 | 25 | /// **Mechanica** 26 | /// 27 | /// Returns a CGImage object for the associated image data. 28 | public var cgImage: CGImage? { 29 | let imageData = self.tiffRepresentation 30 | 31 | guard let source = CGImageSourceCreateWithData(imageData! as CFData, nil) else { return nil } 32 | 33 | return CGImageSourceCreateImageAtIndex(source, 0, nil) 34 | } 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/CoreGraphics/CGFloat+Utils.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 2 | 3 | import CoreGraphics 4 | 5 | extension CGFloat { 6 | /// **Mechanica** 7 | /// 8 | /// Example: 9 | /// 10 | /// CGFloat(180).degreesToRadians -> π 11 | /// 12 | public var degreesToRadians: CGFloat { 13 | return CGFloat.pi * self / 180.0 14 | } 15 | 16 | /// **Mechanica** 17 | /// 18 | /// Example: 19 | /// 20 | /// CGFloat(π).radiansToDegrees -> 180 21 | /// 22 | public var radiansToDegrees: CGFloat { 23 | return self * 180 / CGFloat.pi 24 | } 25 | 26 | /// **Mechanica** 27 | /// 28 | /// Returns the shortest angle between two angles. The result is always between -π and π. 29 | /// 30 | /// Example: 31 | /// 32 | /// CGFloat.shortestAngleInRadians(from: 0, to: 3π/2) -> -π/2 33 | /// 34 | public static func shortestAngleInRadians(from first: CGFloat, to second: CGFloat) -> CGFloat { 35 | // https://github.com/raywenderlich/SKTUtils/blob/master/SKTUtils/CGFloat%2BExtensions.swift 36 | let twoPi = CGFloat(.pi * 2.0) 37 | var angle = (second - first).truncatingRemainder(dividingBy: twoPi) 38 | if angle >= .pi { 39 | angle -= twoPi 40 | } 41 | if angle <= -.pi { 42 | angle += twoPi 43 | } 44 | return angle 45 | } 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/CoreGraphics/CGPoint+Utils.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 2 | 3 | import CoreGraphics.CGGeometry 4 | 5 | extension CGPoint { 6 | /// **Mechanica** 7 | /// 8 | /// Returns the distance between two points. 9 | /// - Parameters: 10 | /// - point1: The first point. 11 | /// - point2: The second point. 12 | /// - Returns: the distance between between two points. 13 | static func distance(from point1: CGPoint, to point2: CGPoint) -> CGFloat { 14 | // return sqrt(pow(point2.x - point1.x, 2) + pow(point2.y - point1.y, 2)) 15 | return hypot(point1.x - point2.x, point1.y - point2.y) 16 | } 17 | 18 | /// **Mechanica** 19 | /// 20 | /// Returns the distance between `self` and another `point`.. 21 | /// - Parameter point: The point to which to calculate the distance. 22 | /// - Returns: the distance between `self` and `point`. 23 | public func distance(to point: CGPoint) -> CGFloat { 24 | return CGPoint.distance(from: self, to: point) 25 | } 26 | 27 | /// **Mechanica** 28 | /// 29 | /// Checks if a point is on a straight line. 30 | /// - Parameters: 31 | /// - firstPoint: The first point that defines a straight line. 32 | /// - secondPoint: The second point that defines a straight line. 33 | /// - Returns: *true* if `self` lies on the straigth lined defined by `firstPoint` and `secondPoint`. 34 | public func isOnStraightLine(passingThrough firstPoint: CGPoint, and secondPoint: CGPoint) -> Bool { 35 | let point1 = (firstPoint.y - self.y) * (firstPoint.x - secondPoint.x) 36 | let point2 = (firstPoint.y - secondPoint.y) * (firstPoint.x - self.x) 37 | return point1 == point2 38 | } 39 | } 40 | 41 | // MARK: - Operators 42 | 43 | extension CGPoint { 44 | /// **Mechanica** 45 | /// 46 | /// Adds two `CGPoints`. 47 | /// 48 | /// Example: 49 | /// 50 | /// CGPoint(x: 1, y: 2) + CGPoint(x: 10, y: 11) -> CGPoint(x: 11, y: 13) 51 | /// 52 | public static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 53 | return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 54 | } 55 | 56 | /// **Mechanica** 57 | /// 58 | /// Adds a `CGPoint` to `self`. 59 | /// 60 | /// Example: 61 | /// 62 | /// var point = CGPoint(x: 1, y: 2) 63 | /// point += CGPoint(x: 0, y: 0) -> point is equal to CGPoint(x: 11, y: 13) 64 | /// 65 | public static func += (lhs: inout CGPoint, rhs: CGPoint) { 66 | lhs = CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 67 | } 68 | 69 | /// **Mechanica** 70 | /// 71 | /// Subtracts two `CGPoints`. 72 | /// 73 | /// Example: 74 | /// 75 | /// CGPoint(x: 1, y: 2) - CGPoint(x: 10, y: 11) -> CGPoint(x: -9, y: -9) 76 | /// 77 | public static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 78 | return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) 79 | } 80 | 81 | /// **Mechanica** 82 | /// 83 | /// Subtracts a `CGPoint` from `self`. 84 | /// 85 | /// Example: 86 | /// 87 | /// var point = CGPoint(x: 1, y: 2) 88 | /// point -= CGPoint(x: 10, y: 11) -> point is equal to CGPoint(x: -9, y: -9) 89 | /// 90 | public static func -= (lhs: inout CGPoint, rhs: CGPoint) { 91 | lhs = CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) 92 | } 93 | 94 | /// **Mechanica** 95 | /// 96 | /// Multiplies a `CGPoint` with a scalar. 97 | /// 98 | /// Example: 99 | /// 100 | /// CGPoint(x: 1, y: 2) * 3 -> CGPoint(x: 3, y: 6) 101 | /// 102 | public static func * (point: CGPoint, scalar: CGFloat) -> CGPoint { 103 | return CGPoint(x: point.x * scalar, y: point.y * scalar) 104 | } 105 | 106 | /// **Mechanica** 107 | /// 108 | /// Multiply `self` with a scalar. 109 | /// 110 | /// Example: 111 | /// 112 | /// var point = CGPoint(x: 1, y: 2) 113 | /// point *= 3 -> point is equal to CGPoint(x: 3, y: 6) 114 | /// 115 | public static func *= (point: inout CGPoint, scalar: CGFloat) { 116 | point = CGPoint(x: point.x * scalar, y: point.y * scalar) 117 | } 118 | } 119 | #endif 120 | -------------------------------------------------------------------------------- /Sources/CoreGraphics/CGRect+Utils.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 2 | 3 | import CoreGraphics 4 | 5 | extension CGRect { 6 | /// **Mechanica** 7 | /// 8 | /// Returns a `CGRect` value initialized with an origin at (0,0) and the provided width and height. 9 | /// 10 | /// - Parameters: 11 | /// - width: The width. 12 | /// - height: The height. 13 | public init(width: CGFloat, height: CGFloat) { 14 | self.init(x: 0, y: 0, width: width, height: height) 15 | } 16 | 17 | /// **Mechanica** 18 | /// 19 | /// The center point. 20 | public var center: CGPoint { 21 | get { 22 | return CGPoint(x: midX, y: midY) 23 | } 24 | set { 25 | // swiftlint:disable identifier_name 26 | let x = newValue.x - width / 2.0 27 | let y = newValue.y - height / 2.0 28 | // swiftlint:enable identifier_name 29 | let newOrigin = CGPoint(x: x, y: y) 30 | 31 | self.origin = newOrigin 32 | } 33 | } 34 | 35 | /// **Mechanica** 36 | /// 37 | /// Returns the `CGRect` area. 38 | public var area: CGFloat { 39 | return height * width 40 | } 41 | 42 | /// **Mechanica** 43 | /// 44 | /// Returns a `CGRect` rounded to an integral value using the specified rounding `rule`. 45 | /// 46 | /// Example: 47 | /// 48 | /// CGRect(x: 10.3, y: 11.5, width: 12.7, height: 13.0).rounded(rule: .down) -> CGRect(x: 10, y: 11, width: 12, height: 13) 49 | /// CGRect(x: 10.3, y: 11.5, width: 12.7, height: 13.0).rounded(rule: .up) -> CGRect(x: 11, y: 12, width: 13, height: 13) 50 | /// 51 | public func rounded(rule: FloatingPointRoundingRule) -> CGRect { 52 | return CGRect( 53 | x: origin.x.rounded(rule), 54 | y: origin.y.rounded(rule), 55 | width: size.width.rounded(rule), 56 | height: size.height.rounded(rule) 57 | ) 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /Sources/CoreGraphics/CGSize+Utils.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 2 | 3 | import CoreGraphics.CGGeometry 4 | 5 | extension CGSize { 6 | /// **Mechanica** 7 | /// 8 | /// Returns the scale to fit `self` into a different `size`. 9 | /// 10 | /// - Parameter size: The size to fit into. 11 | /// - Returns: The scale to fit into the given size. 12 | public func scaleToFit(boundingSize size: CGSize) -> CGFloat { 13 | return fmin(size.width / width, size.height / height) 14 | } 15 | 16 | /// **Mechanica** 17 | /// 18 | /// Returns a `CGSize` to fit `self` into the `size` by maintaining the aspect ratio. 19 | /// 20 | /// - Parameter size: The size to fit into. 21 | /// - Returns: Returns a `CGSize` to fit `self` into the `size` by maintaining the aspect ratio. 22 | /// 23 | /// Example: 24 | /// 25 | /// CGSize(width: 100, height: 50).aspectFit(boundingSize: CGSize(width: 70, height: 30)) -> CGSize(width: 60, height: 30) 26 | /// 27 | public func aspectFit(boundingSize size: CGSize) -> CGSize { 28 | let minRatio = scaleToFit(boundingSize: size) 29 | 30 | return CGSize(width: width * minRatio, height: height * minRatio) 31 | } 32 | } 33 | 34 | // MARK: - Operators 35 | 36 | extension CGSize { 37 | /// **Mechanica** 38 | /// 39 | /// Adds two `CGSize`. 40 | /// 41 | /// Example: 42 | /// 43 | /// CGSize(width: 10, height: 10) + CGSize(width: 20, height: 10) -> CGSize(width: 30, height: 20) 44 | /// 45 | public static func + (lhs: CGSize, rhs: CGSize) -> CGSize { 46 | return CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) 47 | } 48 | 49 | /// **Mechanica** 50 | /// 51 | /// Adds a `CGSize` to `self`. 52 | /// 53 | /// Example: 54 | /// 55 | /// var size = CGSize(width: 10, height: 10) 56 | /// size += CGSize(width: 20, height: 10) -> point is equal to CGSize(width: 30, height: 20) 57 | /// 58 | public static func += (lhs: inout CGSize, rhs: CGSize) { 59 | lhs = CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) 60 | } 61 | 62 | /// **Mechanica** 63 | /// 64 | /// Subtracts two `CGSize`. 65 | /// 66 | /// Example: 67 | /// 68 | /// CGSize(width: 20, height: 10) - CGSize(width: 10, height: 5) -> CGSize(width: 10, height: 5) 69 | /// 70 | public static func - (lhs: CGSize, rhs: CGSize) -> CGSize { 71 | return CGSize(width: lhs.width - rhs.width, height: lhs.height - rhs.height) 72 | } 73 | 74 | /// **Mechanica** 75 | /// 76 | /// Subtracts a CGSize from `self`. 77 | /// 78 | /// Example: 79 | /// 80 | /// var size = CGSize(width: 20, height: 10) 81 | /// size -= CGSize(width: 10, height: 5) -> point is equal to CGSize(width: 10, height: 5) 82 | /// 83 | public static func -= (lhs: inout CGSize, rhs: CGSize) { 84 | lhs = CGSize(width: lhs.width - rhs.width, height: lhs.height - rhs.height) 85 | } 86 | 87 | /// **Mechanica** 88 | /// 89 | /// Multiplies a `CGPoint` with a scalar. 90 | /// 91 | /// Example: 92 | /// 93 | /// CGSize(width: 20, height: 10) * 3 -> CGSize(width: 60, height: 30) 94 | /// 95 | public static func * (size: CGSize, scalar: CGFloat) -> CGSize { 96 | return CGSize(width: size.width * scalar, height: size.height * scalar) 97 | } 98 | 99 | /// **Mechanica** 100 | /// 101 | /// Multiply `self` with a scalar. 102 | /// 103 | /// Example: 104 | /// 105 | /// var size = CGSize(width: 20, height: 10) 106 | /// size *= 3 -> point is equal to CGSize(width: 60, height: 30) 107 | /// 108 | public static func *= (size: inout CGSize, scalar: CGFloat) { 109 | size = CGSize(width: size.width * scalar, height: size.height * scalar) 110 | } 111 | } 112 | #endif 113 | -------------------------------------------------------------------------------- /Sources/Foundation/Bundle+Info.swift: -------------------------------------------------------------------------------- 1 | // Core Foundation Keys: https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 2 | 3 | #if canImport(Foundation) 4 | 5 | import Foundation 6 | 7 | extension Bundle { 8 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 9 | 10 | /// **Mechanica** 11 | /// 12 | /// Returns the app identifier (`bundleIdenfier` or its `executable` file name). 13 | public var appIdentifier: String? { 14 | if let identifier = bundleIdentifier, !identifier.isBlank { //i.e. com.alessandromarzoli.App 15 | return identifier 16 | } else if let identifier = executableFileName, !identifier.isBlank { //i.e. AppExecutableName 17 | return identifier 18 | } 19 | return nil 20 | } 21 | 22 | /// **Mechanica** 23 | /// 24 | /// Returns the user-visible name of the `Bundle`; used by Siri and visible on the Home screen in iOS. 25 | public final var displayName: String? { 26 | return infoDictionary?["CFBundleDisplayName"] as? String 27 | } 28 | 29 | /// **Mechanica** 30 | /// 31 | /// Returns the receiver's executable file name. 32 | public final var executableFileName: String? { 33 | return executableURL?.lastPathComponent 34 | } 35 | 36 | /// **Mechanica** 37 | /// 38 | /// Returns the release-version-number string for the `Bundle`. 39 | public final var shortVersionString: String? { 40 | return infoDictionary?["CFBundleShortVersionString"] as? String 41 | } 42 | 43 | /// **Mechanica** 44 | /// 45 | /// Returns the build-version-number string for the `Bundle`. 46 | public final var version: String? { 47 | return infoDictionary?["CFBundleVersion"] as? String 48 | } 49 | 50 | /// Returns all the defines URL schemes. 51 | /// - Note: The URL scheme is a useful little feature in iOS that allows unrelated applications to communicate with each other in a controlled way. 52 | /// One application can use a custom URL scheme registered by another to pass control to it, supplying arguments as required. 53 | public var urlSchemes: [String] { 54 | guard let infoDictionary = self.infoDictionary, 55 | let urlTypes = infoDictionary["CFBundleURLTypes"] as? [AnyObject], 56 | let urlType = urlTypes.first as? [String: AnyObject], 57 | let urlSchemes = urlType["CFBundleURLSchemes"] as? [String] else { 58 | return [] 59 | } 60 | return urlSchemes 61 | } 62 | 63 | #endif 64 | 65 | #if os(iOS) || os(tvOS) || os(watchOS) 66 | /// **Mechanica** 67 | /// 68 | /// Returns true if the app is running through TestFlight. 69 | /// - Note: For an application installed through TestFlight, the receipt file is named StoreKit\sandboxReceipt vs the usual StoreKit\receipt 70 | public var isAppRunningThroughTestFlight: Bool { 71 | // TODO: is it still a valid approach in 2020? 72 | return appStoreReceiptURL?.path.contains("sandboxReceipt") == true 73 | } 74 | #endif 75 | } 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /Sources/Foundation/Calendar+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension Calendar { 6 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 7 | /// **Mechanica** 8 | /// 9 | /// Returns the quarter for a given date. 10 | /// 11 | /// Example: 12 | /// 13 | /// Calendar.current.quarter(from: Date()) -> 4 // date in fourth quarter of the year. 14 | /// 15 | public func quarter(from date: Date) -> Int { 16 | let month = Double(component(.month, from: date)) 17 | let numberOfMonths = Double(monthSymbols.count) 18 | let numberOfMonthsInQuarter = numberOfMonths / 4 19 | 20 | return Int(ceil(month / numberOfMonthsInQuarter)) 21 | } 22 | 23 | /// **Mechanica** 24 | /// 25 | /// Checks if date is is within the current week. 26 | /// 27 | public func isDateInCurrentWeek(_ date: Date) -> Bool { 28 | return isDate(date, equalTo: Date(), toGranularity: .weekOfYear) 29 | } 30 | 31 | /// **Mechanica** 32 | /// 33 | /// Checks if date is is within the current month. 34 | /// 35 | public func isDateInCurrentMonth(_ date: Date) -> Bool { 36 | return isDate(date, equalTo: Date(), toGranularity: .month) 37 | } 38 | 39 | /// **Mechanica** 40 | /// 41 | /// Checks if date is is within the current year. 42 | /// 43 | public func isDateInCurrentYear(_ date: Date) -> Bool { 44 | return isDate(date, equalTo: Date(), toGranularity: .year) 45 | } 46 | #endif 47 | 48 | /// **Mechanica** 49 | /// 50 | /// Checks if date is is within a weekday period. 51 | /// 52 | public func isDateInWorkDay(_ date: Date) -> Bool { 53 | return !isDateInWeekend(date) 54 | } 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /Sources/Foundation/Data+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension Data { 6 | /// **Mechanica** 7 | /// 8 | /// Returns `self` as an array of bytes. 9 | public var bytes: [UInt8] { 10 | return [UInt8](self) 11 | } 12 | 13 | /// **Mechanica** 14 | /// 15 | /// Returns an hexadecimal string representation (in lowercase) of the content. 16 | public var hexEncodedString: String { 17 | // can be improved if needed: https://stackoverflow.com/questions/39075043/how-to-convert-data-to-hex-string-in-swift 18 | return reduce("") { $0 + String(format: "%02x", $1) } 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/Foundation/Date+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension Date { 6 | /// **Mechanica** 7 | /// 8 | /// Checks if date is the in the future. 9 | public var isFuture: Bool { 10 | return self > Date() 11 | } 12 | 13 | /// **Mechanica** 14 | /// 15 | /// Checks if date is the in the past. 16 | public var isPast: Bool { 17 | return self < Date() 18 | } 19 | 20 | /// **Mechanica** 21 | /// 22 | /// Returns the UNIX timestamp from `self`. 23 | /// 24 | /// Example: 25 | /// 26 | /// Date().unixTimestamp -> 1484233862.826291 27 | /// 28 | public var unixTimestamp: Double { 29 | return timeIntervalSince1970 30 | } 31 | 32 | /// **Mechanica** 33 | /// 34 | /// Returns the number of seconds between two date. 35 | /// 36 | /// - Parameter date: date to compate self to. 37 | /// - Returns: number of seconds between self and given date. 38 | public func secondsSince(_ date: Date) -> Double { 39 | return timeIntervalSince(date) 40 | } 41 | 42 | /// **Mechanica** 43 | /// 44 | /// Returns the number of minutes between two date. 45 | /// 46 | /// - Parameter date: date to compate self to. 47 | /// - Returns: number of minutes between self and given date. 48 | public func minutesSince(_ date: Date) -> Double { 49 | return timeIntervalSince(date) / 60 50 | } 51 | 52 | /// **Mechanica** 53 | /// 54 | /// Returns the number of hours between two date. 55 | /// 56 | /// - Parameter date: date to compate self to. 57 | /// - Returns: number of hours between self and given date. 58 | public func hoursSince(_ date: Date) -> Double { 59 | return timeIntervalSince(date) / 3600 60 | } 61 | 62 | /// **Mechanica** 63 | /// 64 | /// Returns the number of days between two date. 65 | /// 66 | /// - Parameter date: date to compare self to. 67 | /// - Returns: number of days between self and given date. 68 | public func daysSince(_ date: Date) -> Double { 69 | return timeIntervalSince(date) / (3600 * 24) 70 | } 71 | 72 | /// **Mechanica** 73 | /// 74 | /// Checks if a date is between two other dates. 75 | /// 76 | /// - Parameters: 77 | /// - startDate: start date to compare self to. 78 | /// - endDate: endDate date to compare self to. 79 | /// - includingBounds: true if the start and end date should be included (default is false) 80 | /// - Returns: true if the date is between the two given dates. 81 | public func isBetween(_ startDate: Date, _ endDate: Date, includingBounds: Bool = false) -> Bool { 82 | let result = startDate.compare(self).rawValue * compare(endDate).rawValue 83 | if includingBounds { 84 | return result >= 0 85 | } 86 | return result > 0 87 | } 88 | 89 | /// **Mechanica** 90 | /// 91 | /// Generates a random date between two dates. 92 | /// 93 | /// Example: 94 | /// 95 | /// Date.random() 96 | /// Date.random(from: Date()) 97 | /// Date.random(upTo: Date()) 98 | /// Date.random(from: Date(), upTo: Date()) 99 | /// 100 | /// - Parameters: 101 | /// - fromDate: minimum date (default is Date.distantPast) 102 | /// - toDate: maximum date (default is Date.distantFuture) 103 | /// - Returns: random date between two dates. 104 | public static func random(from fromDate: Date = .distantPast, upTo toDate: Date = .distantFuture) -> Date { 105 | guard fromDate != toDate else { return fromDate } 106 | 107 | let diff = llabs(Int64(toDate.timeIntervalSinceReferenceDate - fromDate.timeIntervalSinceReferenceDate)) 108 | var randomValue = Int64.random(in: Int64.min...Int64.max) 109 | randomValue = llabs(randomValue % diff) 110 | 111 | let startReferenceDate = toDate > fromDate ? fromDate : toDate 112 | return startReferenceDate.addingTimeInterval(TimeInterval(randomValue)) 113 | } 114 | } 115 | 116 | // MARK: - Initializers 117 | 118 | extension Date { 119 | /// **Mechanica** 120 | /// 121 | /// Creates a new `Date` instance from an UNIX timestamp. 122 | /// 123 | /// Example: 124 | /// 125 | /// let date = Date(unixTimestamp: 1484239783.922743) // "Jan 12, 2017, 7:49 PM" 126 | /// 127 | /// - Parameter unixTimestamp: UNIX timestamp. 128 | public init(unixTimestamp: Double) { 129 | self.init(timeIntervalSince1970: unixTimestamp) 130 | } 131 | } 132 | 133 | #endif 134 | -------------------------------------------------------------------------------- /Sources/Foundation/Dictionary+FoundationUtils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension Dictionary { 6 | // MARK: - JSON 7 | 8 | /// **Mechanica** 9 | /// 10 | /// Returns a Dictionary from a given JSON String. 11 | public init?(json: String) { 12 | if 13 | let jsonData = json.data(using: .utf8, allowLossyConversion: true), 14 | let jsonDictionary = (try? JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers)) as? Dictionary 15 | { 16 | self = jsonDictionary 17 | } else { 18 | return nil 19 | } 20 | } 21 | 22 | /// **Mechanica** 23 | /// 24 | /// Returns a JSON Data from dictionary. 25 | /// 26 | /// - Parameter prettify: *true* to prettify data (defaults to *false*). 27 | /// - Returns: optional JSON Data. 28 | public func jsonData(prettify: Bool = false) -> Data? { 29 | guard JSONSerialization.isValidJSONObject(self) else { return nil } 30 | 31 | let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions() 32 | 33 | return try? JSONSerialization.data(withJSONObject: self, options: options) 34 | } 35 | 36 | /// **Mechanica** 37 | /// 38 | /// Returns a JSON String from dictionary. 39 | /// 40 | /// - Parameter prettify: *true* to prettify string (defaults to *false*). 41 | /// - Returns: optional JSON String. 42 | public func jsonString(prettify: Bool = false) -> String? { 43 | guard let jsonData = jsonData(prettify: prettify) else { return nil } 44 | 45 | return String(data: jsonData, encoding: .utf8) 46 | } 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /Sources/Foundation/DispatchQueue+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Dispatch) 2 | 3 | import Dispatch 4 | 5 | // MARK: - Properties 6 | 7 | extension DispatchQueue { 8 | /// **Mechanica** 9 | /// 10 | /// Returns a Boolean value indicating whether the current dispatch queue is the main queue. 11 | static var isMainQueue: Bool { 12 | enum Static { 13 | static var key: DispatchSpecificKey = { 14 | let key = DispatchSpecificKey() 15 | DispatchQueue.main.setSpecific(key: key, value: ()) 16 | return key 17 | }() 18 | } 19 | return DispatchQueue.getSpecific(key: Static.key) != nil 20 | } 21 | } 22 | 23 | // MARK: - Methods 24 | 25 | extension DispatchQueue { 26 | /// **Mechanica** 27 | /// 28 | /// Returns a Boolean value indicating whether the current dispatch queue is the specified queue. 29 | /// 30 | /// - Parameter queue: The queue to compare against. 31 | /// - Returns: `true` if the current queue is the specified queue, otherwise `false`. 32 | public static func isCurrent(_ queue: DispatchQueue) -> Bool { 33 | let key = DispatchSpecificKey() 34 | 35 | queue.setSpecific(key: key, value: ()) 36 | defer { queue.setSpecific(key: key, value: nil) } 37 | 38 | return DispatchQueue.getSpecific(key: key) != nil 39 | } 40 | } 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /Sources/Foundation/FileManager+Utils.swift: -------------------------------------------------------------------------------- 1 | // File System Basics: https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html 2 | // Migrating an App to a Sandbox: https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/MigratingALegacyApp/MigratingAnAppToASandbox.html 3 | 4 | #if canImport(Foundation) 5 | 6 | // TODO: checking a file existance ahead of an operation is not recommended 7 | 8 | import Foundation 9 | 10 | extension FileManager { 11 | /// **Mechanica** 12 | /// 13 | /// Cleans all contents in a directory `path`. 14 | /// 15 | /// - Parameter path: **directory** path (if it's not a directory path, nothing is done). 16 | /// - Throws: throws an error in cases of failure. 17 | public final func cleanDirectory(atPath path: String) throws { 18 | // #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 19 | // var isDirectory: ObjCBool = false 20 | // guard fileExists(atPath: path, isDirectory: &isDirectory) == true else { return } 21 | // guard isDirectory.boolValue == true else { return } 22 | // #else 23 | guard fileExists(atPath: path) == true else { return } 24 | 25 | var result = Stat() 26 | stat(path, &result) 27 | guard result.isDirectory == true else { return } 28 | 29 | // #endif 30 | 31 | let contents = try contentsOfDirectory(atPath: path) 32 | 33 | for file in contents { 34 | let path = URL(fileURLWithPath: path).appendingPathComponent(file).path 35 | try removeItem(atPath: path) 36 | } 37 | } 38 | 39 | /// **Mechanica** 40 | /// 41 | /// Delete a file or a directory at a given `path`. 42 | /// 43 | /// - Parameter path: directory or file path. 44 | /// - Throws: throws an error in cases of failure. 45 | public final func deleteFileOrDirectory(atPath path: String) throws { 46 | guard fileExists(atPath: path) == true else { return } 47 | 48 | try removeItem(atPath: path) 49 | } 50 | 51 | /// **Mechanica** 52 | /// 53 | /// Creates and returns always a `new` directory in Library/Caches in the user's home directory for discardable cache files. 54 | public final func newCachesSubDirectory(in domain: FileManager.SearchPathDomainMask = .userDomainMask, withName name: String = UUID().uuidString) throws -> URL { 55 | let cachesDirectoryURL = try url(for: .cachesDirectory, in: domain, appropriateFor: nil, create: true) 56 | let subdirectoryURL = cachesDirectoryURL.appendingPathComponent(name) 57 | 58 | if !fileExists(atPath: subdirectoryURL.path) { 59 | try createDirectory(at: subdirectoryURL, withIntermediateDirectories: false, attributes: nil) 60 | } 61 | 62 | return subdirectoryURL 63 | } 64 | } 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /Sources/Foundation/FoundationUtils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | /// **Mechanica** 6 | /// 7 | /// Returns the type name as `String`. 8 | public func typeName(of some: Any) -> String { 9 | let value = (some is Any.Type) ? "\(some)" : "\(type(of: some))" 10 | if !value.starts(with: "(") { return value } 11 | 12 | // match a word inside "(" and " in" https://regex101.com/r/eO6eB7/17 13 | let pattern = "(?<=\\()[^()]{1,}(?=\\sin)" 14 | // if let result = value.range(of: pattern, options: .regularExpression) { return value[result] } 15 | if let result = value.firstRange(matching: pattern) { 16 | return String(value[result]) 17 | } 18 | 19 | return value 20 | } 21 | 22 | // MARK: - Objective-C Associated Objects 23 | 24 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 25 | /// **Mechanica** 26 | /// 27 | /// Sets an associated value for a given object using a given key and association policy. 28 | /// 29 | /// - Parameters: 30 | /// - value: The value to associate with the key key for object. Pass nil to clear an existing association. 31 | /// - object: The source object for the association. 32 | /// - key: The key for the association. 33 | /// - policy: The policy for the association. 34 | internal func setAssociatedValue(_ value: T?, 35 | forObject object: Any, 36 | usingKey key: UnsafeRawPointer, 37 | andPolicy policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) { 38 | Foundation.objc_setAssociatedObject(object, key, value, policy) 39 | } 40 | 41 | /// **Mechanica** 42 | /// 43 | /// Returns the value associated with a given object for a given key. 44 | /// 45 | /// - Parameters: 46 | /// - object: The source object for the association. 47 | /// - key: The key for the association. 48 | /// - Returns: The value associated with the key for object. 49 | internal func getAssociatedValue(forObject object: Any, usingKey key: UnsafeRawPointer) -> T? { 50 | return Foundation.objc_getAssociatedObject(object, key) as? T 51 | } 52 | 53 | /// **Mechanica** 54 | /// 55 | /// Removes an associated value for a given object using a given key and association policy. 56 | /// 57 | /// - Parameters: 58 | /// - object: The source object for the association. 59 | /// - key: The key for the association. 60 | /// - policy: The policy for the association. 61 | internal func removeAssociatedValue(forObject object: Any, 62 | usingKey key: UnsafeRawPointer, 63 | andPolicy policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) { 64 | Foundation.objc_setAssociatedObject(object, key, nil, policy) 65 | } 66 | 67 | /// **Mechanica** 68 | /// 69 | /// Removes all the associated value for a given object using a given key and association policy. 70 | /// 71 | /// - Parameters: 72 | /// - object: The source object for the association. 73 | /// - key: The key for the association. 74 | /// - policy: The policy for the association. 75 | internal func removeAllAssociatedValues(forObject object: Any) { 76 | Foundation.objc_removeAssociatedObjects(object) 77 | } 78 | #endif 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /Sources/Foundation/Locale.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension Locale { 6 | /// **Mechanica** 7 | /// 8 | /// UNIX representation of locale usually used for normalizing. 9 | public static var posix: Locale { 10 | return Locale(identifier: "en_US_POSIX") 11 | } 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /Sources/Foundation/NSAttributedString+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension NSAttributedString { 6 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 7 | 8 | // MARK: - Initializers 9 | 10 | /// **Mechanica** 11 | /// 12 | /// Initializes and returns a `new` NSAttributedString object from the `html` contained in the given string. 13 | /// - Parameters: 14 | /// - html: an HTML string. 15 | /// - allowLossyConversion: If it is *true* and the receiver can’t be converted without losing some information, some characters may be removed or altered in conversion. 16 | /// For example, in converting a character from NSUnicodeStringEncoding to NSASCIIStringEncoding, the character ‘Á’ becomes ‘A’, losing the accent. 17 | /// - Note: The HTML import mechanism is meant for implementing something like markdown (that is, text styles, colors, and so on), not for general HTML import. 18 | /// [Apple Documentation](https://developer.apple.com/reference/foundation/nsattributedstring/1524613-init) 19 | /// - Warning: Using the HTML importer (NSHTMLTextDocumentType) is only possible on the **main thread**. 20 | public convenience init?(html: String, allowLossyConversion: Bool = false) { 21 | guard let data = html.data(using: .utf8, allowLossyConversion: allowLossyConversion) else { return nil } 22 | 23 | try? self.init(data: data, 24 | options: [.documentType: NSAttributedString.DocumentType.html], 25 | documentAttributes: nil) 26 | } 27 | 28 | #endif 29 | 30 | // MARK: - Operators 31 | 32 | /// **Mechanica** 33 | /// 34 | /// Returns a `new` NSAttributedString appending the right NSAttributedString to the left NSAttributedString. 35 | static public func + (lhs: NSAttributedString, rhs: NSAttributedString) -> NSAttributedString { 36 | let attributedString = NSMutableAttributedString(attributedString: lhs) 37 | attributedString.append(rhs) 38 | return NSAttributedString(attributedString: attributedString) 39 | } 40 | 41 | /// **Mechanica** 42 | /// 43 | /// Returns a `new` NSAttributedString appending the right String to the left NSAttributedString. 44 | static func + (lhs: NSAttributedString, rhs: String) -> NSAttributedString { 45 | // swiftlint:disable force_cast 46 | let attributedString = lhs.mutableCopy() as! NSMutableAttributedString 47 | return (attributedString + rhs).copy() as! NSAttributedString 48 | // swiftlint:enable force_cast 49 | } 50 | 51 | /// **Mechanica** 52 | /// 53 | /// Returns a `new` NSAttributedString appending the right NSAttributedString to the left String. 54 | static public func + (lhs: String, rhs: NSAttributedString) -> NSAttributedString { 55 | let attributedString = NSMutableAttributedString(string: lhs) 56 | // swiftlint:disable:next force_cast 57 | return (attributedString + rhs).copy() as! NSAttributedString 58 | } 59 | 60 | //#endif 61 | } 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /Sources/Foundation/NSMutableAttributedString+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension NSMutableAttributedString { 6 | // MARK: - Attributes 7 | 8 | /// **Mechanica** 9 | /// 10 | /// Removes all the attributes from `self`. 11 | public func removeAllAttributes() { 12 | setAttributes([:], range: NSRange(location: 0, length: string.count)) 13 | } 14 | 15 | /// **Mechanica** 16 | /// 17 | /// Returns a `new` NSMutableAttributedString removing all the attributes. 18 | public func removingAllAttributes() -> NSMutableAttributedString { 19 | return NSMutableAttributedString(string: string) 20 | } 21 | 22 | // MARK: - Operators 23 | 24 | /// **Mechanica** 25 | /// 26 | /// Returns a `new` NSMutableAttributedString appending the right NSMutableAttributedString to the left NSMutableAttributedString. 27 | public static func + (lhs: NSMutableAttributedString, rhs: NSMutableAttributedString) -> NSMutableAttributedString { 28 | // swiftlint:disable force_cast 29 | let leftMutableAttributedString = lhs.mutableCopy() as! NSMutableAttributedString 30 | let rightMutableAttributedString = rhs.mutableCopy() as! NSMutableAttributedString 31 | // swiftlint:enable force_cast 32 | leftMutableAttributedString.append(rightMutableAttributedString) 33 | return leftMutableAttributedString 34 | } 35 | 36 | /// **Mechanica** 37 | /// 38 | /// Returns a `new` NSMutableAttributedString appending the right NSAttributedString to the left NSMutableAttributedString. 39 | public static func + (lhs: NSMutableAttributedString, rhs: NSAttributedString) -> NSMutableAttributedString { 40 | // swiftlint:disable:next force_cast 41 | let leftMutableAttributedString = lhs.mutableCopy() as! NSMutableAttributedString 42 | // swiftlint:enable force_cast 43 | leftMutableAttributedString.append(rhs) 44 | return leftMutableAttributedString 45 | } 46 | 47 | /// **Mechanica** 48 | /// 49 | /// Returns a `new` NSMutableAttributedString appending the right NSMutableAttributedString to the left NSAttributedString. 50 | public static func + (lhs: NSAttributedString, rhs: NSMutableAttributedString) -> NSMutableAttributedString { 51 | let mutableAttributedString = NSMutableAttributedString(attributedString: lhs) 52 | return mutableAttributedString + rhs 53 | } 54 | 55 | /// **Mechanica** 56 | /// 57 | /// Returns a `new` NSMutableAttributedString appending the right String to the left NSMutableAttributedString. 58 | public static func + (lhs: NSMutableAttributedString, rhs: String) -> NSMutableAttributedString { 59 | // swiftlint:disable:next force_cast 60 | let leftMutableAttributedString = lhs.mutableCopy() as! NSMutableAttributedString 61 | let rightMutableAttributedString = NSMutableAttributedString(string: rhs) 62 | return leftMutableAttributedString + rightMutableAttributedString 63 | } 64 | 65 | /// **Mechanica** 66 | /// 67 | /// Returns a `new` NSMutableAttributedString appending the right NSMutableAttributedString to the left String. 68 | public static func + (lhs: String, rhs: NSMutableAttributedString) -> NSMutableAttributedString { 69 | let leftMutableAttributedString = NSMutableAttributedString(string: lhs) 70 | // swiftlint:disable:next force_cast 71 | let rightMutableAttributedString = rhs.mutableCopy() as! NSMutableAttributedString 72 | return leftMutableAttributedString + rightMutableAttributedString 73 | } 74 | 75 | /// **Mechanica** 76 | /// 77 | /// Appends the right NSMutableAttributedString to the left NSMutableAttributedString. 78 | public static func += (lhs: NSMutableAttributedString, rhs: NSMutableAttributedString) { 79 | lhs.append(rhs) 80 | } 81 | 82 | /// **Mechanica** 83 | /// 84 | /// Appends the right NSAttributedString to the left NSMutableAttributedString. 85 | public static func += (lhs: NSMutableAttributedString, rhs: NSAttributedString) { 86 | lhs.append(rhs) 87 | } 88 | 89 | /// **Mechanica** 90 | /// 91 | /// Appends the right String to the left NSMutableAttributedString. 92 | public static func += (lhs: NSMutableAttributedString, rhs: String) { 93 | lhs.append(NSAttributedString(string: rhs)) 94 | } 95 | } 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /Sources/Foundation/NSPredicate+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | import Foundation 3 | 4 | // MARK: Properties 5 | 6 | extension NSPredicate { 7 | /// **Mechanica** 8 | /// 9 | /// An always `true` NSPredicate. 10 | public static let `true` = NSPredicate(value: true) 11 | 12 | /// **Mechanica** 13 | /// 14 | /// An always `false` NSPredicate. 15 | public static let `false` = NSPredicate(value: false) 16 | } 17 | 18 | // MARK: Methods 19 | 20 | extension NSPredicate { 21 | /// **Mechanica** 22 | /// 23 | /// 24 | /// - Parameter predicate: A `NSPredicate` object. 25 | /// - Returns: a `new` compound NSPredicate formed by **AND**-ing `self` with `predicate`. 26 | public func and(_ predicate: NSPredicate) -> NSPredicate { 27 | return NSCompoundPredicate(andPredicateWithSubpredicates: [self, predicate]) 28 | } 29 | 30 | /// **Mechanica** 31 | /// 32 | /// 33 | /// - Parameter predicate: A `NSPredicate` object. 34 | /// - Returns: a `new` compound NSPredicate formed by **OR**-ing `self` with `predicate`. 35 | public func or(_ predicate: NSPredicate) -> NSPredicate { 36 | return NSCompoundPredicate(orPredicateWithSubpredicates: [self, predicate]) 37 | } 38 | } 39 | 40 | // MARK: Operators 41 | 42 | extension NSPredicate { 43 | /// **Mechanica** 44 | /// 45 | /// Returns a `new` predicate formed by **AND-ing** the two predicates. 46 | /// - Parameters: 47 | /// - lhs: The left-hand side of the operation. 48 | /// - rhs: The right-hand side of the operation. 49 | public static func && (lhs: NSPredicate, rhs: NSPredicate) -> NSPredicate { 50 | return NSCompoundPredicate(andPredicateWithSubpredicates: [lhs, rhs]) 51 | } 52 | 53 | /// **Mechanica** 54 | /// 55 | /// Returns a `new` predicate formed by **OR-ing** the two predicates. 56 | /// - Parameters: 57 | /// - lhs: The left-hand side of the operation. 58 | /// - rhs: The right-hand side of the operation. 59 | public static func || (lhs: NSPredicate, rhs: NSPredicate) -> NSPredicate { 60 | return NSCompoundPredicate(orPredicateWithSubpredicates: [lhs, rhs]) 61 | } 62 | 63 | /// **Mechanica** 64 | /// 65 | /// Returns a `new` predicate forme d by **NOT-ing** a given predicate. 66 | /// - Parameter p: The NSPredicate to negate. 67 | public static prefix func ! (predicate: NSPredicate) -> NSPredicate { 68 | return NSCompoundPredicate(notPredicateWithSubpredicate: predicate) 69 | } 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /Sources/Foundation/ProcessInfo+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension ProcessInfo { 6 | /// **Mechanica** 7 | /// 8 | /// Returns true if SwiftPackage tests are running. 9 | public static var isRunningSwiftPackageTests: Bool { 10 | // TODO: is it still ok in 2020? 11 | let testArguments = processInfo.arguments.filter { $0.ends(with: "xctest") } 12 | return processInfo.environment["XCTestConfigurationFilePath"] == nil && testArguments.count > 0 13 | } 14 | 15 | /// **Mechanica** 16 | /// 17 | /// Returns true if Xcode or SwiftPackage Unit Tests are running. 18 | public static var isRunningUnitTests: Bool { 19 | return isRunningXcodeUnitTests || isRunningSwiftPackageTests 20 | } 21 | 22 | /// **Mechanica** 23 | /// 24 | /// Returns true if Xcode Unit Tests are running. 25 | public static var isRunningXcodeUnitTests: Bool { 26 | return processInfo.environment["XCTestConfigurationFilePath"].hasValue 27 | } 28 | 29 | /// **Mechanica** 30 | /// 31 | /// - Returns: Returns the system (re)starting date. 32 | public static var systemStartingDate: Date { 33 | return Date(timeIntervalSinceNow: -processInfo.systemUptime) 34 | } 35 | 36 | #if os(macOS) 37 | /// **Mechanica** 38 | /// 39 | /// Returns true if the app is sandboxed. 40 | public static var isSandboxed: Bool { 41 | return processInfo.environment["APP_SANDBOX_CONTAINER_ID"].hasValue 42 | } 43 | #endif 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /Sources/Foundation/Stat+Utils.swift: -------------------------------------------------------------------------------- 1 | // https://github.com/nsomar/FileUtils 2 | // https://github.com/RubyNative/SwiftRuby 3 | // https://www.gnu.org/software/libc/manual/html_node/Testing-File-Type.html 4 | 5 | #if canImport(Glibc) 6 | import Glibc 7 | #elseif canImport(Darwin) 8 | import Darwin.C 9 | #endif 10 | 11 | /// **Mechanica** 12 | /// 13 | /// The structure of the data returned by the functions `fstat()`, `lstat()`, and `stat()`. 14 | typealias Stat = stat 15 | 16 | extension Stat { 17 | /// **Mechanica** 18 | /// 19 | /// Returns `true` if the file is a symbolik link. 20 | internal var isLink: Bool { 21 | return S_ISLNK(Int(st_mode)) 22 | } 23 | 24 | /// **Mechanica** 25 | /// 26 | /// Returns `true` if the file is a directory. 27 | internal var isDirectory: Bool { 28 | return S_ISDIR(Int(st_mode)) 29 | } 30 | 31 | /// **Mechanica** 32 | /// 33 | /// Returns `true` if the file is a regular file. 34 | internal var isFile: Bool { 35 | return S_ISREG(Int(st_mode)) 36 | } 37 | } 38 | 39 | // MARK: - Symbols in that are not defined in Foundation 40 | 41 | // swiftlint:disable identifier_name 42 | // swiftlint:disable private_over_fileprivate 43 | 44 | /// **Mechanica** 45 | /// 46 | /// This is a bit mask used to extract the file type code from a mode value. 47 | fileprivate let S_IFMT: Int = 0o170000 48 | 49 | /// **Mechanica** 50 | /// 51 | /// This is the file type constant of a FIFO or pipe. 52 | fileprivate let S_IFIFO: Int = 0o010000 53 | 54 | /// **Mechanica** 55 | /// 56 | /// This is the file type constant of a character-oriented device file. 57 | fileprivate let S_IFCHR: Int = 0o020000 58 | 59 | /// **Mechanica** 60 | /// 61 | /// This is the file type constant of a directory file. 62 | fileprivate let S_IFDIR: Int = 0o040000 63 | 64 | /// **Mechanica** 65 | /// 66 | /// This is the file type constant of a block-oriented device file. 67 | fileprivate let S_IFBLK: Int = 0o060000 68 | 69 | /// **Mechanica** 70 | /// 71 | /// This is the file type constant of a regular file. 72 | fileprivate let S_IFREG: Int = 0o100000 73 | 74 | /// **Mechanica** 75 | /// 76 | /// This is the file type constant of a symbolic link. 77 | fileprivate let S_IFLNK: Int = 0o120000 78 | 79 | /// **Mechanica** 80 | /// 81 | /// This is the file type constant of a socket. 82 | fileprivate let S_IFSOCK: Int = 0o140000 83 | 84 | // swiftlint:enable private_over_fileprivate 85 | 86 | /// **Mechanica** 87 | /// 88 | /// Returns `true` if the file is a block special file (a device like a disk). 89 | internal func S_ISBLK(_ m: Int) -> Bool { 90 | return (((m) & S_IFMT) == S_IFBLK) 91 | } 92 | 93 | /// **Mechanica** 94 | /// 95 | /// Returns `true` if the file is a character special file (a device like a terminal). 96 | internal func S_ISCHR(_ m: Int) -> Bool { 97 | return (((m) & S_IFMT) == S_IFCHR) 98 | } 99 | 100 | /// **Mechanica** 101 | /// 102 | /// Returns `true` if the file is a directory. 103 | internal func S_ISDIR(_ m: Int) -> Bool { 104 | return (((m) & S_IFMT) == S_IFDIR) 105 | } 106 | 107 | /// **Mechanica** 108 | /// 109 | /// Returns `true` if the file is a FIFO special file, or a pipe. 110 | internal func S_ISFIFO(_ m: Int) -> Bool { 111 | return (((m) & S_IFMT) == S_IFIFO) 112 | } 113 | 114 | /// **Mechanica** 115 | /// 116 | /// Returns `true` if the file is a regular file. 117 | internal func S_ISREG(_ m: Int) -> Bool { 118 | return (((m) & S_IFMT) == S_IFREG) 119 | } 120 | 121 | /// **Mechanica** 122 | /// 123 | /// Returns `true` if the file is a symbolic link. 124 | internal func S_ISLNK(_ m: Int) -> Bool { 125 | return (((m) & S_IFMT) == S_IFLNK) 126 | } 127 | 128 | /// **Mechanica** 129 | /// 130 | /// Returns `true` if the file is a socket. 131 | internal func S_ISSOCK(_ m: Int) -> Bool { 132 | return (((m) & S_IFMT) == S_IFSOCK) 133 | } 134 | 135 | // swiftlint:enable private_over_fileprivate 136 | // swiftlint:enable identifier_name 137 | -------------------------------------------------------------------------------- /Sources/Foundation/URL+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension URL { 6 | /// **Mechanica** 7 | /// 8 | /// Returns a Dictionary containing the query parameters (is any). 9 | public var queryParameters: [String: String]? { 10 | guard 11 | let components = URLComponents(url: self, resolvingAgainstBaseURL: true), 12 | let queryItems = components.queryItems, 13 | queryItems.count > 0 14 | else { 15 | return nil 16 | } 17 | 18 | var parameters = [String: String]() 19 | for item in queryItems { 20 | parameters[item.name] = item.value 21 | } 22 | 23 | return parameters 24 | } 25 | 26 | /// Removes a specified number of path components from `self`. 27 | /// 28 | /// - Parameter numberOfPathComponents: components to be removed. 29 | /// 30 | /// This function may either remove a path component or append /... 31 | /// If the URL has an empty path (e.g., http://www.example.com), then this function will return the URL unchanged. 32 | public mutating func deleteLastPathComponents(_ numberOfPathComponents: Int) { 33 | self = deletingLastPathComponents(numberOfPathComponents) 34 | } 35 | 36 | /// **Mechanica** 37 | /// 38 | /// - Parameter numberOfPathComponents: components to be removed. 39 | /// - Returns: Returns a URL constructed by removing a specified number of path components. 40 | /// 41 | /// This function may either remove a path component or append /... 42 | /// If the URL has an empty path (e.g., http://www.example.com), then this function will return the URL unchanged. 43 | public func deletingLastPathComponents(_ numberOfPathComponents: Int) -> URL { 44 | var copy = self 45 | 46 | for _ in 0.. Bool { 83 | return child.path.hasPrefix(path) 84 | } 85 | } 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /Sources/Foundation/UserDefaults+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | 3 | import Foundation 4 | 5 | extension UserDefaults { 6 | /// **Mechanica** 7 | /// 8 | /// Returns `true` if `key` exists. 9 | public final func hasKey(_ key: String) -> Bool { 10 | return dictionaryRepresentation().hasKey(key) 11 | } 12 | 13 | /// **Mechanica** 14 | /// 15 | /// - Parameter defaultName: A key in the current user's defaults database. 16 | /// - Returns: The bool value associated with the specified key. If the specified key does not exist, this method returns nil. 17 | public final func optionalBool(forKey defaultName: String) -> Bool? { 18 | return (object(forKey: defaultName) as? NSNumber)?.boolValue 19 | } 20 | 21 | /// **Mechanica** 22 | /// 23 | /// - Parameter defaultName: A key in the current user's defaults database. 24 | /// - Returns: The double value associated with the specified key. If the specified key does not exist, this method returns nil. 25 | public final func optionalDouble(forKey defaultName: String) -> Double? { 26 | return (object(forKey: defaultName) as? NSNumber)?.doubleValue 27 | } 28 | 29 | /// **Mechanica** 30 | /// 31 | /// - Parameter defaultName: A key in the current user's defaults database. 32 | /// - Returns: The floating-point value associated with the specified key. If the key does not exist, this method returns nil. 33 | public final func optionalFloat(forKey defaultName: String) -> Float? { 34 | return (object(forKey: defaultName) as? NSNumber)?.floatValue 35 | } 36 | 37 | /// **Mechanica** 38 | /// 39 | /// - Parameter defaultName: A key in the current user's defaults database. 40 | /// - Returns: The integer value associated with the specified key. If the specified key does not exist, this method returns nil. 41 | public final func optionalInteger(forKey defaultName: String) -> Int? { 42 | return (object(forKey: defaultName) as? NSNumber)?.intValue 43 | } 44 | 45 | //#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 46 | /// **Mechanica** 47 | /// 48 | /// Removes all keys and values from user defaults. 49 | /// - Note: This method only removes keys on the receiver `UserDefaults` object. 50 | /// System-defined keys will still be present afterwards. 51 | /// `resetStandardUserDefaults` simply resets the in-memory user defaults object. 52 | public final func removeAll() { 53 | for (key, _) in dictionaryRepresentation() { 54 | removeObject(forKey: key) 55 | } 56 | } 57 | //#endif 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /Sources/Mechanica.swift: -------------------------------------------------------------------------------- 1 | /// **Mechanica** 2 | /// 3 | /// Mechanica Bundle Identifier 4 | internal let bundleIdentifier = "com.alessandromarzoli.Mechanica" 5 | 6 | /// **Mechanica** 7 | /// 8 | /// Associated Key prefix. 9 | internal let associatedKeyPrefix = "Mechanica.AssociatedKey" 10 | 11 | #if canImport(Foundation) 12 | // TODO: move in another file 13 | import Foundation 14 | 15 | /// **Mechanica** 16 | /// 17 | /// Mechanica Bundle 18 | internal var bundle: Bundle { 19 | class Object {} 20 | return Bundle(for: Object.self) 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /Sources/Shared/NSDirectionalEdgeInsets+Utils.swift: -------------------------------------------------------------------------------- 1 | // TODO: add tests 2 | #if canImport(UIKit) || canImport(AppKit) 3 | 4 | #if canImport(UIKit) 5 | import UIKit 6 | #else 7 | import AppKit 8 | #endif 9 | 10 | // MARK: - Properties 11 | 12 | @available(macOS 10.15, *) 13 | extension NSDirectionalEdgeInsets { 14 | /// **Mechanica** 15 | /// 16 | /// Returns the vertical insets (composed by top + bottom). 17 | public var vertical: CGFloat { 18 | return top + bottom 19 | } 20 | 21 | /// **Mechanica** 22 | /// 23 | /// Returns the horizontal insets (composed by left + right). 24 | public var horizontal: CGFloat { 25 | return trailing + leading 26 | } 27 | } 28 | 29 | // MARK: - Methods 30 | 31 | @available(macOS 10.15, *) 32 | extension NSDirectionalEdgeInsets { 33 | /// **Mechanica** 34 | /// 35 | /// Creates an `UIEdgeInsets` with the same inset value applied to all (top, bottom, right, left) 36 | public init(inset: CGFloat) { 37 | self.init(top: inset, leading: inset, bottom: inset, trailing: inset) 38 | } 39 | 40 | /// **Mechanica** 41 | /// 42 | /// Creates an `NSDirectionalEdgeInsets` with the horizontal value equally divided and applied to right and left and the vertical value equally divided and applied to top and bottom. 43 | /// 44 | /// - Parameter horizontal: Inset to be applied to right and left. 45 | /// - Parameter vertical: Inset to be applied to top and bottom. 46 | public init(horizontal: CGFloat, vertical: CGFloat) { 47 | self.init(top: vertical / 2, leading: horizontal / 2, bottom: vertical / 2, trailing: horizontal / 2) 48 | } 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/BinaryFloatingPoint+Utils.swift: -------------------------------------------------------------------------------- 1 | extension BinaryFloatingPoint { 2 | /// **Mechanica** 3 | /// 4 | /// Creates a string representing the given value in the binary base. 5 | /// 6 | /// Example: 7 | /// 8 | /// Float(-5.625).binaryString -> "11000000101101000000000000000000 9 | /// 10 | public var binaryString: String { 11 | let floatingPointSign = (sign == FloatingPointSign.minus) ? "1" : "0" 12 | let exponentBitCount = Self.exponentBitCount 13 | let mantissaBitCount = Self.significandBitCount 14 | 15 | var exponent = String(Int(self.exponentBitPattern), radix: 2) 16 | var mantissa = String(Int(self.significandBitPattern), radix: 2) 17 | 18 | if exponentBitCount > exponent.count { 19 | exponent = String(repeating: "0", count: (exponentBitCount - exponent.count)) + exponent 20 | } 21 | if mantissaBitCount > mantissa.count { 22 | mantissa = String(repeating: "0", count: (mantissaBitCount - mantissa.count)) + mantissa 23 | } 24 | 25 | return "\(floatingPointSign)\(exponent)\(mantissa)" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/BinaryInteger+Utils.swift: -------------------------------------------------------------------------------- 1 | extension BinaryInteger { 2 | /// **Mechanica** 3 | /// 4 | /// Determine if self is even (equivalent to `self % 2 == 0` or to `isMultiple(of: 2)`). 5 | public var isEven: Bool { 6 | return isMultiple(of: 2) 7 | } 8 | 9 | /// **Mechanica** 10 | /// 11 | /// Determine if self is odd (equivalent to `self % 2 != 0` or to `isMultiple(of: 2)` negated). 12 | public var isOdd: Bool { 13 | return !isEven 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/Bool+Utils.swift: -------------------------------------------------------------------------------- 1 | extension Bool { 2 | /// **Mechanica** 3 | /// 4 | /// Returns 1 if true, or 0 if false. 5 | public var int: Int { 6 | return self ? 1 : 0 7 | } 8 | 9 | /// **Mechanica** 10 | /// 11 | /// Returns a `new` Bool with the inverted value of `self`. 12 | public var toggled: Bool { 13 | return !self 14 | } 15 | 16 | /// **Mechanica** 17 | /// 18 | /// Creates a string representing the given value in the binary base. 19 | /// 20 | /// Example: 21 | /// 22 | /// true.binaryString -> "1" 23 | /// 24 | public var binaryString: String { 25 | return self ? "1" : "0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/Character+Utils.swift: -------------------------------------------------------------------------------- 1 | extension Character { 2 | /// **Mechanica** 3 | /// 4 | /// Returns `true` if `self` is an emoji country flag. 5 | public var isEmojiCountryFlag: Bool { 6 | // TODO: https://github.com/apple/swift-evolution/blob/master/proposals/0211-unicode-scalar-properties.md (Swift 5) 7 | // see https://github.com/apple/swift/blob/swift-5.0-branch/stdlib/public/core/UnicodeScalarProperties.swift 8 | let scalars = unicodeScalars 9 | return scalars.count == 2 && scalars.first!.isRegionalIndicator && scalars.last!.isRegionalIndicator 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/Dictionary+Utils.swift: -------------------------------------------------------------------------------- 1 | extension Dictionary { 2 | /// **Mechanica** 3 | /// 4 | /// Returns true if the `key` exists in the dictionary. 5 | public func hasKey(_ key: Key) -> Bool { 6 | return index(forKey: key) != nil 7 | } 8 | 9 | /// **Mechanica** 10 | /// 11 | /// Removes the given kesy and theris associated values from the dictionary. 12 | /// 13 | /// - Parameter keys: The keys to remove along with theirs associated values. 14 | /// - Returns: The key’s associated values (if any). 15 | public mutating func removeAll(forKeys keys: [Key]) -> [Key: Value]? { 16 | var removedElements: [Key: Value]? 17 | keys.forEach { 18 | if let removed = removeValue(forKey: $0) { 19 | removedElements = removedElements == nil ? [Key: Value]() : removedElements 20 | removedElements?[$0] = removed 21 | } 22 | } 23 | return removedElements 24 | } 25 | } 26 | 27 | extension Dictionary where Key: ExpressibleByStringLiteral { 28 | // MARK: - ExpressibleByStringLiteral 29 | 30 | /// **Mechanica** 31 | /// 32 | /// Lowercase all keys in dictionary. 33 | public mutating func lowercaseAllKeys() { 34 | for key in keys { 35 | if let lowercaseKey = String(describing: key).lowercased() as? Key { 36 | self[lowercaseKey] = removeValue(forKey: key) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/FixedWidthInteger+Utils.swift: -------------------------------------------------------------------------------- 1 | extension FixedWidthInteger { 2 | /// **Mechanica** 3 | /// 4 | /// Creates a string representing the given value in the binary base. 5 | /// 6 | /// Example: 7 | /// 8 | /// Int8(10).binaryString -> "00001010" 9 | /// 255.binaryString -> "11111111" 10 | /// Int16(-1).binaryString -> "1111111111111111" 11 | /// 1.binaryString -> "0000000000000000000000000000000000000000000000000000000000000001" (Int 64 bit) 12 | /// 1.binaryString -> "00000000000000000000000000000001" (Int 32 bit) 13 | /// 14 | /// - Note: Negative integers are converted with the **two's complement operation**. For signed binary use `String(:radix:)` 15 | /// 16 | /// Example: 17 | /// 18 | /// String(Int8(-127), radix: 2) -> -1111111 19 | /// Int8(-127).binaryString -> 10000001 20 | /// 21 | var binaryString: String { 22 | var result: [String] = [] 23 | 24 | for i in 0..<(Self.bitWidth / 8) { 25 | let byte = UInt8(truncatingIfNeeded: self >> (i * 8)) 26 | let byteString = String(byte, radix: 2) 27 | let padding = String(repeating: "0", count: 8 - byteString.count) 28 | result.append(padding + byteString) 29 | } 30 | 31 | // return "0b" + result.reversed().joined(separator: "_") 32 | return result.reversed().joined(separator: "") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/FloatingPoint+Utils.swift: -------------------------------------------------------------------------------- 1 | extension FloatingPoint { 2 | /// **Mechanica** 3 | /// 4 | /// Returns a `new` rounded `FloatingPoint` to specified number of decimal `places`. 5 | /// 6 | /// Example: 7 | /// 8 | /// var piFloat = Float(3.141_592_653_589_793_238_46) 9 | /// 10 | /// piFloat.rounded(to: 0) -> 3.0 11 | /// piFloat.rounded(to: 7) -> 3.1415927 12 | /// 13 | /// var piFloat80 = Float80(3.14159_26535_89793_23846) 14 | /// 15 | /// piFloat80.rounded(to: 16, rule: .down) -> 3.141_592_653_589_793_2 16 | /// piFloat80.rounded(to: 16, rule: .up) -> 3.141_592_653_589_793_3 17 | /// 18 | public func rounded(to decimalPlaces: Int, rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero) -> Self { 19 | guard decimalPlaces >= 0 else { return self } 20 | 21 | var divisor: Self = 1 22 | for _ in 0.. piFloat is 3.142 36 | /// piFloat.round(to: 7) -> piFloat is 3.1415927 37 | /// 38 | /// var piFloat80 = Float80(3.14159_26535_89793_23846) 39 | /// 40 | /// piFloat80.round(to: 16, rule: .down) -> piFloat80 is 3.141_592_653_589_793_2 41 | /// piFloat80.round(to: 16, rule: .up) -> piFloat80 is 3.141_592_653_589_793_3 42 | /// 43 | public mutating func round(to decimalPlaces: Int, rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero) { 44 | self = rounded(to: decimalPlaces, rule: rule) 45 | } 46 | 47 | /// **Mechanica** 48 | /// 49 | /// Returns a `new` ceiled `FloatingPoint` to specified number of decimal `places`. 50 | /// 51 | /// Example: 52 | /// 53 | /// var piFloat = Float(3.141_592_653_589_793_238_46) 54 | /// 55 | /// piFloat.ceiled(to: 0) -> 4.0 56 | /// piFloat.ceiled(to: 5) -> 3.1416 57 | /// 58 | public func ceiled(to decimalPlaces: Int) -> Self { 59 | guard decimalPlaces >= 0 else { return self } 60 | 61 | var divisor: Self = 1 62 | for _ in 0.. piFloat is 4.0 76 | /// piFloat.ceil(to: 5) -> piFloat is 3.1416 77 | /// 78 | public mutating func ceil(to decimalPlaces: Int) { 79 | self = ceiled(to: decimalPlaces) 80 | } 81 | 82 | /// **Mechanica** 83 | /// 84 | /// Returns a `new` floored `FloatingPoint` to specified number of decimal `places`. 85 | /// 86 | /// Example: 87 | /// 88 | /// var piFloat = Float(3.141_592_653_589_793_238_46) 89 | /// 90 | /// piFloat.floored(to: 0) -> 3.0 91 | /// piFloat.floored(to: 5) -> 3.14159 92 | /// 93 | public func floored(to decimalPlaces: Int) -> Self { 94 | guard decimalPlaces >= 0 else { return self } 95 | 96 | var divisor: Self = 1 97 | for _ in 0.. piFloat is 3.0 111 | /// piFloat.floor(to: 5) -> piFloat is 3.14159 112 | /// 113 | public mutating func floor(to decimalPlaces: Int) { 114 | self = floored(to: decimalPlaces) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/Operators.swift: -------------------------------------------------------------------------------- 1 | postfix operator % 2 | 3 | /// **Mechanica** 4 | /// 5 | /// 'Float' percent operator. 6 | public postfix func % (value: Float) -> Float { 7 | return value / 100 8 | } 9 | 10 | /// **Mechanica** 11 | /// 12 | /// 'Double' percent operator. 13 | public postfix func % (value: Double) -> Double { 14 | return value / 100 15 | } 16 | 17 | infix operator ???: NilCoalescingPrecedence 18 | 19 | /// **Mechanica** 20 | /// 21 | /// Optional-string-coalescing operator. 22 | /// 23 | /// The ??? operator takes any `optional` on its left side and a `default` string value on the right, returning a string. 24 | /// If the optional value is non-nil, it unwraps it and returns its string description, otherwise it returns the default value. 25 | public func ??? (optional: T?, defaultValue: @autoclosure () -> String) -> String { 26 | // https://oleb.net/blog/2016/12/optionals-string-interpolation 27 | return optional.map { String(describing: $0) } ?? defaultValue() 28 | } 29 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/Optional+Utils.swift: -------------------------------------------------------------------------------- 1 | extension Optional { 2 | /// **Mechanica** 3 | /// 4 | /// Returns `true` if `self` has a value. 5 | public var hasValue: Bool { 6 | switch self { 7 | case .none: 8 | return false 9 | case .some: 10 | return true 11 | } 12 | } 13 | 14 | /// **Mechanica** 15 | /// 16 | /// Returns the value of the Optional or the `default` parameter. 17 | /// - param: The value to return if the optional is empty. 18 | /// 19 | /// Example: 20 | /// 21 | /// optional.or(anotheValue) 22 | /// 23 | func or(_ default: Wrapped) -> Wrapped { 24 | return self ?? `default` 25 | } 26 | 27 | /// **Mechanica** 28 | /// 29 | /// Returns the unwrapped value of the optional *or* the result of the expression `else`. 30 | /// 31 | /// Example: 32 | /// 33 | /// optional.or(else: print("hello world")) 34 | /// 35 | func or(else: @autoclosure () -> Wrapped) -> Wrapped { 36 | return self ?? `else`() 37 | } 38 | 39 | /// **Mechanica** 40 | /// 41 | /// Returns the unwrapped value of the optional *or* the result of calling the closure `else`. 42 | /// 43 | /// Example: 44 | /// 45 | /// optional.or(else: { 46 | /// ... 47 | /// }) 48 | /// 49 | func or(else: () -> Wrapped) -> Wrapped { 50 | return self ?? `else`() 51 | } 52 | 53 | /// **Mechanica** 54 | /// 55 | /// Returns the unwrapped contents of the optional if it is not empty, otherwise throws an exception. 56 | /// 57 | /// - Parameter exception: The exception to be thrown if the unwrapped contents is empty. 58 | /// - Returns: The unwrapped contents. 59 | /// - Throws: If the unwrapped contents is empty, an exception is thrown. 60 | /// 61 | /// Example: 62 | /// 63 | /// try optional.or(throw: YourError) 64 | /// 65 | func or(throw exception: Error) throws -> Wrapped { 66 | guard let unwrapped = self else { throw exception } 67 | return unwrapped 68 | } 69 | } 70 | 71 | extension Optional where Wrapped: Collection { 72 | /// **Mechanica** 73 | /// 74 | /// Returns true if `self` is nil or empty. 75 | var isNilOrEmpty: Bool { 76 | return self?.isEmpty ?? true 77 | } 78 | } 79 | 80 | extension Optional where Wrapped == String { 81 | /// **Mechanica** 82 | /// 83 | /// Returns true if `self` is nil or empty. 84 | var isNilOrEmpty: Bool { 85 | return self?.isEmpty ?? true 86 | } 87 | 88 | /// **Mechanica** 89 | /// 90 | /// Returns true if `self` is nil or blank (a string that is either empty or contains only space/newline characters). 91 | var isNilOrBlank: Bool { 92 | return self?.isBlank ?? true 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/SignedInteger.swift: -------------------------------------------------------------------------------- 1 | // MARK: Properties 2 | 3 | extension SignedInteger { 4 | /// **Mechanica** 5 | /// 6 | /// Determines if self is positive (equivalent to `self > 0`). 7 | public var isPositive: Bool { 8 | return self > 0 9 | } 10 | 11 | /// **Mechanica** 12 | /// 13 | /// Determines if self is negative (equivalent to `self < 0`). 14 | public var isNegative: Bool { 15 | return self < 0 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/StandardLibrary/Unicode+Utils.swift: -------------------------------------------------------------------------------- 1 | extension Unicode.Scalar { 2 | /// **Mechanica** 3 | /// 4 | /// Returns `true` if `self` is a regional indicator. 5 | public var isRegionalIndicator: Bool { 6 | // TODO: Swift 5 - https://github.com/apple/swift-evolution/blob/master/proposals/0211-unicode-scalar-properties.md 7 | // see https://github.com/apple/swift/blob/swift-5.0-branch/stdlib/public/core/UnicodeScalarProperties.swift 8 | return ("🇦"..."🇿").contains(self) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/UIKit/UIButton+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && (os(iOS) || os(tvOS)) 2 | 3 | import UIKit 4 | 5 | extension UIButton { 6 | /// **Mechanica** 7 | /// 8 | /// Sets the background `color` to use for the specified button `state`. 9 | /// 10 | /// - Parameters: 11 | /// - color: The background color to use for the specified state. 12 | /// - state: The state that uses the specified image (defaults to normal. 13 | public func setBackgroundColor(_ color: UIColor, for state: UIControl.State = .normal) { 14 | let colorImage = UIImage(color: color, size: CGSize(width: 1, height: 1)) 15 | 16 | setBackgroundImage(colorImage, for: state) 17 | } 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/UIKit/UIDevice+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && (os(iOS) || os(tvOS)) 2 | 3 | import UIKit 4 | 5 | extension UIDevice { 6 | // MARK: - Device Interface Type 7 | 8 | /// **Mechanica** 9 | /// 10 | /// Returns `true` if the interface is designed for iPhone or iPod. 11 | public var hasPhoneInterface: Bool { 12 | return userInterfaceIdiom == .phone 13 | } 14 | 15 | /// **Mechanica** 16 | /// 17 | /// Returns `true` if the interface is designed for iPad. 18 | public var hasPadInterface: Bool { 19 | return userInterfaceIdiom == .pad 20 | } 21 | 22 | /// **Mechanica** 23 | /// 24 | /// Returns `true` if the interface is designed for Apple TV. 25 | public var hasTVInterface: Bool { 26 | return userInterfaceIdiom == .tv 27 | } 28 | 29 | /// **Mechanica** 30 | /// 31 | /// Returns `true` if the interface is designed for Apple CarPlay. 32 | public var hasCarPlayInterface: Bool { 33 | return userInterfaceIdiom == .carPlay 34 | } 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/UIKit/UIEdgeInsets+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | 3 | import UIKit 4 | 5 | // MARK: - Properties 6 | 7 | extension UIEdgeInsets { 8 | /// **Mechanica** 9 | /// 10 | /// Returns the vertical insets (composed by top + bottom). 11 | public var vertical: CGFloat { 12 | return top + bottom 13 | } 14 | 15 | /// **Mechanica** 16 | /// 17 | /// Returns the horizontal insets (composed by left + right). 18 | public var horizontal: CGFloat { 19 | return left + right 20 | } 21 | } 22 | 23 | // MARK: - Methods 24 | 25 | extension UIEdgeInsets { 26 | /// **Mechanica** 27 | /// 28 | /// Creates an `UIEdgeInsets` with the same inset value applied to all (top, bottom, right, left) 29 | public init(inset: CGFloat) { 30 | self.init(top: inset, left: inset, bottom: inset, right: inset) 31 | } 32 | 33 | /// **Mechanica** 34 | /// 35 | /// Creates an `UIEdgeInsets` with the horizontal value equally divided and applied to right and left and the vertical value equally divided and applied to top and bottom. 36 | /// 37 | /// - Parameter horizontal: Inset to be applied to right and left. 38 | /// - Parameter vertical: Inset to be applied to top and bottom. 39 | public init(horizontal: CGFloat, vertical: CGFloat) { 40 | self.init(top: vertical / 2, left: horizontal / 2, bottom: vertical / 2, right: horizontal / 2) 41 | } 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /Sources/UIKit/UIGestureRecognizer+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && (os(iOS) || os(tvOS)) 2 | 3 | import UIKit 4 | 5 | extension UIGestureRecognizer { 6 | /// **Mechanica** 7 | /// 8 | /// The gesture recognizer transitions to a cancelled state. 9 | public func cancel() { 10 | isEnabled = false 11 | isEnabled = true 12 | } 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /Sources/UIKit/UILayoutPriority+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && (os(iOS) || os(tvOS)) 2 | 3 | import UIKit 4 | 5 | extension UILayoutPriority { 6 | /// **Mechanica** 7 | /// 8 | /// - Parameters: 9 | /// - lhs: the UILayoutPriority to increase. 10 | /// - rhs: the increasing amount. 11 | /// - Returns: a `new` increased UILayoutPriority. 12 | /// 13 | /// Example: 14 | /// 15 | /// label.setContentHuggingPriority(.defaultLow + 1, for: .horizontal) 16 | /// 17 | static func + (lhs: UILayoutPriority, rhs: Float) -> UILayoutPriority { 18 | return UILayoutPriority(lhs.rawValue + rhs) 19 | } 20 | 21 | /// **Mechanica** 22 | /// 23 | /// - Parameters: 24 | /// - lhs: the UILayoutPriority to increase. 25 | /// - rhs: the decreasing amount. 26 | /// - Returns: a `new` decreased UILayoutPriority. 27 | /// 28 | /// Example: 29 | /// 30 | /// view.setContentCompressionResistancePriority(.defaultHigh - 1, for: .vertical) 31 | /// 32 | static func - (lhs: UILayoutPriority, rhs: Float) -> UILayoutPriority { 33 | return UILayoutPriority(lhs.rawValue - rhs) 34 | } 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/UIKit/UIStackView+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && (os(iOS) || os(tvOS)) 2 | 3 | import UIKit 4 | 5 | extension UIStackView { 6 | /// **Mechanica** 7 | /// 8 | /// Adds a background color. 9 | /// 10 | /// - Parameters: 11 | /// - color: The view’s background color. 12 | /// - Returns: The UIView used to apply the background color. 13 | @discardableResult 14 | func addBackgroundColor(_ color: UIColor) -> UIView { 15 | return addUnarrangedView(color: color, cornerRadius: 0, at: 0) 16 | } 17 | 18 | /// **Mechanica** 19 | /// 20 | /// Adds a foreground color. 21 | /// 22 | /// - Parameters: 23 | /// - color: The view’s foreground color. 24 | /// - Returns: The UIView used to apply the foreground color. 25 | @discardableResult 26 | func addForegroundColor(_ color: UIColor) -> UIView { 27 | let index = subviews.count 28 | return addUnarrangedView(color: color, cornerRadius: 0, at: index) 29 | } 30 | 31 | /// **Mechanica** 32 | /// 33 | /// Adds a `new` *unarranged* subview. 34 | /// 35 | /// - Parameters: 36 | /// - color: The view’s background color. 37 | /// - cornerRadius: The radius used to draw rounded corners. 38 | /// - index: The index in the array of the subviews property at which to insert the view. 39 | /// - Returns: The inserted view. 40 | @discardableResult 41 | func addUnarrangedView(color: UIColor, cornerRadius: CGFloat = 0, at index: Int = 0) -> UIView { 42 | let view = UIView() 43 | view.translatesAutoresizingMaskIntoConstraints = false 44 | view.backgroundColor = color 45 | view.layer.cornerRadius = cornerRadius 46 | insertSubview(view, at: index) 47 | 48 | NSLayoutConstraint.activate([ 49 | view.leadingAnchor.constraint(equalTo: leadingAnchor), 50 | view.trailingAnchor.constraint(equalTo: trailingAnchor), 51 | view.topAnchor.constraint(equalTo: topAnchor), 52 | view.bottomAnchor.constraint(equalTo: bottomAnchor) 53 | ]) 54 | 55 | return view 56 | } 57 | } 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /Sources/UIKit/UIView+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && (os(iOS) || os(tvOS) || watchOS) 2 | 3 | import UIKit 4 | 5 | extension UIView { 6 | /// **Mechanica** 7 | /// 8 | /// The radius to use when drawing rounded corners. 9 | /// - Note: Inspectable from Storyboard. 10 | @IBInspectable 11 | var cornerRadius: CGFloat { 12 | get { 13 | return layer.cornerRadius 14 | } 15 | set { 16 | layer.cornerRadius = newValue 17 | } 18 | } 19 | 20 | /// **Mechanica** 21 | /// 22 | /// The width of the view's border. 23 | /// - Note: Inspectable from Storyboard. 24 | @IBInspectable 25 | var borderWidth: CGFloat { 26 | get { 27 | return layer.borderWidth 28 | } 29 | set { 30 | layer.borderWidth = newValue 31 | } 32 | } 33 | 34 | /// **Mechanica** 35 | /// 36 | /// The color of the view's border. 37 | /// - Note: Inspectable from Storyboard. 38 | @IBInspectable 39 | var borderColor: UIColor? { 40 | get { 41 | guard let color = layer.borderColor else { return nil } 42 | return UIColor(cgColor: color) 43 | } 44 | set { 45 | if let color = newValue { 46 | layer.borderColor = color.cgColor 47 | } else { 48 | layer.borderColor = nil 49 | } 50 | } 51 | } 52 | 53 | /// **Mechanica** 54 | /// 55 | /// The blur radius (in points) used to render the view's shadow. 56 | /// - Note: Inspectable from Storyboard. 57 | @IBInspectable 58 | var shadowRadius: CGFloat { 59 | get { 60 | return layer.shadowRadius 61 | } 62 | set { 63 | layer.shadowRadius = newValue 64 | } 65 | } 66 | 67 | /// **Mechanica** 68 | /// 69 | /// The opacity of the view's shadow. 70 | /// - Note: Inspectable from Storyboard. 71 | @IBInspectable 72 | var shadowOpacity: Float { 73 | get { 74 | return layer.shadowOpacity 75 | } 76 | set { 77 | layer.shadowOpacity = newValue 78 | } 79 | } 80 | 81 | /// **Mechanica** 82 | /// 83 | /// The offset (in points) of the view's shadow. 84 | /// - Note: Inspectable from Storyboard. 85 | @IBInspectable 86 | var shadowOffset: CGSize { 87 | get { 88 | return layer.shadowOffset 89 | } 90 | set { 91 | layer.shadowOffset = newValue 92 | } 93 | } 94 | 95 | /// **Mechanica** 96 | /// 97 | /// The color of the view's shadow. 98 | /// - Note: Inspectable from Storyboard. 99 | @IBInspectable 100 | var shadowColor: UIColor? { 101 | get { 102 | guard let color = layer.shadowColor else { return nil } 103 | return UIColor(cgColor: color) 104 | } 105 | set { 106 | if let color = newValue { 107 | layer.shadowColor = color.cgColor 108 | } else { 109 | layer.shadowColor = nil 110 | } 111 | } 112 | } 113 | } 114 | 115 | #endif 116 | 117 | #if os(iOS) || os(tvOS) 118 | 119 | extension UIView { 120 | /// **Mechanica** 121 | /// 122 | /// A Boolean value that determines whether the view’s autoresizing mask is translated into Auto Layout constraints. 123 | public var usesAutoLayout: Bool { 124 | get { return !translatesAutoresizingMaskIntoConstraints } 125 | set { translatesAutoresizingMaskIntoConstraints = !newValue } 126 | } 127 | 128 | /// **Mechanica** 129 | /// 130 | /// Takes a screenshot with the current device's screen scale. 131 | /// If an animation is running it captures the final frame of the animation. 132 | /// 133 | /// - Parameter scale: The scale factor to apply; if you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen. 134 | public func screenshot(scale: CGFloat = 0.0) -> UIImage { 135 | if #available(iOS 11, tvOS 11, *) { // iOS 11+ 136 | let format = UIGraphicsImageRendererFormat() 137 | format.opaque = isOpaque 138 | format.scale = scale 139 | let renderer = UIGraphicsImageRenderer(size: frame.size, format: format) 140 | 141 | return renderer.image { _ in 142 | drawHierarchy(in: frame, afterScreenUpdates: true) 143 | } 144 | } else { // iOS 10 145 | return UIGraphicsImageRenderer(bounds: bounds).image { layer.render(in: $0.cgContext) } 146 | } 147 | } 148 | } 149 | 150 | #endif 151 | -------------------------------------------------------------------------------- /Sources/UIKit/UIViewController+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && (os(iOS) || os(tvOS)) 2 | 3 | import UIKit 4 | 5 | extension UIViewController { 6 | /// **Mechanica** 7 | /// 8 | /// Returns `true` if the UIViewController is on the screen. 9 | public var isVisible: Bool { 10 | return isViewLoaded && view.window != nil 11 | } 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /Sources/UIKit/UIWindow+Utils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && (os(iOS) || os(tvOS)) 2 | 3 | import UIKit 4 | 5 | extension UIWindow { 6 | /// **Mechanica** 7 | /// 8 | /// Returns the topmost UIViewController. 9 | public var topMostViewController: UIViewController? { 10 | guard let topMostViewController = rootViewController else { return nil } 11 | 12 | func visibleViewController(from viewController: UIViewController?) -> UIViewController? { 13 | if let tabBarController = (viewController as? UITabBarController)?.selectedViewController { 14 | return visibleViewController(from: tabBarController) 15 | } else if let navigationController = (viewController as? UINavigationController)?.visibleViewController { 16 | return visibleViewController(from: navigationController) 17 | } else if let presentingViewController = viewController?.presentedViewController { 18 | return visibleViewController(from: presentingViewController) 19 | } 20 | return viewController 21 | } 22 | 23 | return visibleViewController(from: topMostViewController) 24 | } 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /Support/Info-Tests.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 | 3.0.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Support/Info-tvOS.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 | 3.0.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | UIRequiredDeviceCapabilities 24 | 25 | arm64 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Support/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 | 3.0.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Support/Mechanica.h: -------------------------------------------------------------------------------- 1 | // 2 | // Mechanica 3 | // 4 | // Copyright © 2016-2019 Tinrobots. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | @import Foundation; 25 | 26 | //! Project version number for Mechanica. 27 | FOUNDATION_EXPORT double MechanicaVersionNumber; 28 | 29 | //! Project version string for Mechanica. 30 | FOUNDATION_EXPORT const unsigned char MechanicaVersionString[]; 31 | 32 | // In this header, you should import all the public headers of your framework using statements like #import 33 | 34 | 35 | -------------------------------------------------------------------------------- /Tests/AVFoundationTests/AVAssetUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if canImport(AVAsset) 2 | 3 | import XCTest 4 | import AVAsset 5 | @testable import Mechanica 6 | 7 | class AVAssetUtils: XCTestCase { 8 | 9 | func testThumbnailFromVideoURL() throws { 10 | let url = URL(string: "http://www.webestools.com/page/media/videoTag/BigBuckBunny.mp4")! 11 | let asset = AVAsset(url: url) 12 | let image = try asset.thumbnail(fromTime: 61) 13 | 14 | XCTAssertEqual(image.size.width, 720) 15 | XCTAssertEqual(image.size.height, 404) 16 | } 17 | 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Tests/AppKitTests/NSImageUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | #if os(macOS) 5 | 6 | final class NSImageUtilsTests: XCTestCase { 7 | 8 | func testImageNamed() { 9 | let bundle = Bundle(for: NSImageUtilsTests.self) 10 | 11 | let image = NSImage.imageNamed(name: "glasses", in: bundle) 12 | let notExistingImage = NSImage.imageNamed(name: "not-existing-glasses", in: bundle) 13 | 14 | if !ProcessInfo.isRunningSwiftPackageTests { 15 | // TODO: - see https://bugs.swift.org/browse/SR-2866 16 | XCTAssertNotNil(image) 17 | XCTAssertNil(notExistingImage) 18 | XCTAssertEqual(image!.name(), NSImage.Name("glasses")) 19 | } 20 | 21 | } 22 | 23 | func testCGImage() throws { 24 | let data = Resource.glasses.data 25 | let image = NSImage(data: data) 26 | let cgImage = image?.cgImage 27 | 28 | XCTAssertNotNil(cgImage) 29 | XCTAssertEqual(cgImage!.width, 483) 30 | XCTAssertEqual(cgImage!.height, 221) 31 | } 32 | 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /Tests/AppKitTests/_Resources.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mechanica 3 | // 4 | // Copyright © 2016-2018 Tinrobots. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | import Foundation 25 | @testable import Mechanica 26 | 27 | enum Resource { 28 | 29 | case glasses 30 | case glassesWithoutAlpha 31 | case robot 32 | case apple 33 | case circle(name: String) 34 | case radius(name: String) 35 | case scaled(name: String) 36 | case scaledToFit(name: String) 37 | case scaledToFill(name: String) 38 | 39 | var url: URL { 40 | switch self { 41 | case .glasses: 42 | return Resource.folderURL.appendingPathComponent("glasses.png") 43 | case .glassesWithoutAlpha: 44 | return Resource.folderURL.appendingPathComponent("glasses_without_alpha.jpeg") 45 | case .robot: 46 | return Resource.folderURL.appendingPathComponent("robot.png") 47 | case .apple: 48 | return Resource.folderURL.appendingPathComponent("Images/Original/apple.jpg") 49 | case .circle(let name): 50 | return Resource.folderURL.appendingPathComponent("Images/Modified/Circle/\(name)") 51 | case .radius(let name): 52 | return Resource.folderURL.appendingPathComponent("Images/Modified/Radius/\(name)") 53 | case .scaled(let name): 54 | return Resource.folderURL.appendingPathComponent("Images/Modified/Scale/\(name)") 55 | case .scaledToFit(let name): 56 | return Resource.folderURL.appendingPathComponent("Images/Modified/AspectScaleToFit/\(name)") 57 | case .scaledToFill(let name): 58 | return Resource.folderURL.appendingPathComponent("Images/Modified/AspectScaleToFill/\(name)") 59 | } 60 | } 61 | 62 | var data : Data { 63 | return try! Data(contentsOf: url) 64 | } 65 | 66 | static var folderURL: URL { 67 | var resources = URL(fileURLWithPath: #file, isDirectory: false).deletingLastPathComponents(2) 68 | resources.appendPathComponent("Resources") 69 | return resources 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Tests/CoreGraphicsTests/CGFloatUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 5 | 6 | final class CGFloatUtilsTests: XCTestCase { 7 | 8 | func testDegreesToRadians() { 9 | XCTAssertEqual(CGFloat(180).degreesToRadians, CGFloat.pi) 10 | XCTAssertEqual(CGFloat(45).degreesToRadians, CGFloat.pi/4) 11 | } 12 | 13 | func testRadiansToDegrees() { 14 | XCTAssertEqual(CGFloat.pi.radiansToDegrees, 180) 15 | XCTAssertEqual((CGFloat.pi/4).radiansToDegrees, 45) 16 | } 17 | 18 | func testShortestAngle() { 19 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: .pi, to: .pi * 2), .pi) 20 | 21 | #if (arch(i386) || arch(arm)) 22 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: 0, to: .pi * (3/2)).rounded(), (-CGFloat.pi/2).rounded()) 23 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: 0, to: -.pi * 2).rounded(), 0) 24 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: 0, to: -CGFloat.pi).rounded(to: 4), (CGFloat.pi).rounded(to: 4)) 25 | #else 26 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: 0, to: .pi * (3/2)), -.pi/2) 27 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: 0, to: -.pi * 2), 0) 28 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: 0, to: -.pi), .pi) 29 | #endif 30 | 31 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: .pi * (1/4), to: .pi * (3/2)), -.pi * (3/4)) 32 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: CGFloat(350).degreesToRadians, to: CGFloat(15).degreesToRadians).rounded(), CGFloat(25).degreesToRadians.rounded()) // from 350° to 15° = 25° 33 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: CGFloat(250).degreesToRadians, to: CGFloat(190).degreesToRadians).rounded(), CGFloat(-60).degreesToRadians.rounded()) // from 250° to 190° = 60° 34 | XCTAssertEqual(CGFloat.shortestAngleInRadians(from: CGFloat(190).degreesToRadians, to: CGFloat(250).degreesToRadians).rounded(), CGFloat(60).degreesToRadians.rounded()) 35 | } 36 | 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /Tests/CoreGraphicsTests/CGPointUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 5 | 6 | final class CGPointUtilsTests: XCTestCase { 7 | 8 | func testDistance() { 9 | // https://www.mathportal.org/calculators/analytic-geometry/distance-and-midpoint-calculator.php 10 | XCTAssertEqual(CGPoint(x: 1, y: 2).distance(to: CGPoint(x: 4, y: 5)), 4.2426, accuracy: 0.0001) 11 | XCTAssertEqual(CGPoint(x: 1, y: 0).distance(to: CGPoint(x: 4, y: 0)), 3, accuracy: 0) 12 | XCTAssertEqual(CGPoint(x: 0, y: 10).distance(to: CGPoint(x: 0, y: 11)), 1, accuracy: 0) 13 | XCTAssertEqual(CGPoint(x: 0, y: 0).distance(to: CGPoint(x: 0, y: 0)), 0, accuracy: 0) 14 | XCTAssertEqual(CGPoint(x: 100, y: 0).distance(to: CGPoint(x: 0, y: 0)), 100, accuracy: 0) 15 | XCTAssertEqual(CGPoint(x: 100, y: 0).distance(to: CGPoint(x: 0, y: 0)), 100, accuracy: 0) 16 | XCTAssertEqual(CGPoint(x: 14, y: 30).distance(to: CGPoint(x: 23, y: 21)), 12.7279, accuracy: 0.0001) 17 | XCTAssertEqual(CGPoint(x: 14, y: 30).distance(to: CGPoint(x: 23, y: 21)), 12.7279, accuracy: 0.0001) 18 | XCTAssertEqual(CGPoint(x: 12.12, y: 11.11).distance(to: CGPoint(x: 14.41, y: 13.31)), 3.1755, accuracy: 0.0001) 19 | } 20 | 21 | func testStraightLine() { 22 | XCTAssertTrue(CGPoint(x: 1, y: 2).isOnStraightLine(passingThrough: CGPoint(x:0, y:0), and: CGPoint(x:0, y:0))) 23 | XCTAssertTrue(CGPoint(x: 1, y: 2).isOnStraightLine(passingThrough: CGPoint(x:1, y:1), and: CGPoint(x:1, y:3))) 24 | XCTAssertFalse(CGPoint(x: 1, y: 2).isOnStraightLine(passingThrough: CGPoint(x:1, y:1), and: CGPoint(x:5, y:3))) 25 | XCTAssertTrue(CGPoint(x: 1, y: 2).isOnStraightLine(passingThrough: CGPoint(x:1, y:100), and: CGPoint(x:1, y:11.3))) 26 | XCTAssertTrue(CGPoint(x: 3, y: 2).isOnStraightLine(passingThrough: CGPoint(x:0, y:-10), and: CGPoint(x:1, y:-6))) // y=4x-10 27 | // Conversions between integer and floating-point numeric types must be made explicit 28 | XCTAssertTrue(CGPoint(x: 7, y: 3).isOnStraightLine(passingThrough: CGPoint(x:0, y:Double(Double(11)/Double(6))), and: CGPoint(x:10, y:3.5))) // y=(1/6)x+11/6 29 | } 30 | 31 | func testAdd() { 32 | XCTAssertEqual(CGPoint(x: 1, y: 2) + CGPoint(x: 0, y: 0), CGPoint(x: 1, y: 2)) 33 | XCTAssertEqual(CGPoint(x: 1, y: 2) + CGPoint(x: 10, y: 11), CGPoint(x: 11, y: 13)) 34 | } 35 | 36 | func testAddEqual() { 37 | var point1 = CGPoint(x: 1, y: 2) 38 | point1 += CGPoint(x: 0, y: 0) 39 | XCTAssertEqual(point1, CGPoint(x: 1, y: 2)) 40 | 41 | var point2 = CGPoint(x: 1, y: 2) 42 | point2 += CGPoint(x: 10, y: 11) 43 | XCTAssertEqual(point2, CGPoint(x: 11, y: 13)) 44 | } 45 | 46 | func testSubtract() { 47 | XCTAssertEqual(CGPoint(x: 1, y: 2) - CGPoint(x: 0, y: 0), CGPoint(x: 1, y: 2)) 48 | XCTAssertEqual(CGPoint(x: 1, y: 2) - CGPoint(x: 10, y: 11), CGPoint(x: -9, y: -9)) 49 | } 50 | 51 | func testSubtractEqual() { 52 | var point1 = CGPoint(x: 1, y: 2) 53 | point1 -= CGPoint(x: 0, y: 0) 54 | XCTAssertEqual(point1, CGPoint(x: 1, y: 2)) 55 | 56 | var point2 = CGPoint(x: 1, y: 2) 57 | point2 -= CGPoint(x: 10, y: 11) 58 | XCTAssertEqual(point2, CGPoint(x: -9, y: -9)) 59 | } 60 | 61 | func testMultiplyScalar() { 62 | XCTAssertEqual(CGPoint(x: 1, y: 2) * 0, .zero) 63 | XCTAssertEqual(CGPoint(x: 1, y: 2) * 1, CGPoint(x: 1, y: 2)) 64 | XCTAssertEqual(CGPoint(x: 1, y: 2) * 1.5, CGPoint(x: 1.5, y: 3)) 65 | XCTAssertEqual(CGPoint(x: 1, y: 2) * 3, CGPoint(x: 3, y: 6)) 66 | } 67 | 68 | func testMultiplyScalarEqual() { 69 | var point1 = CGPoint(x: 1, y: 2) 70 | point1 *= 0 71 | XCTAssertEqual(point1, .zero) 72 | 73 | var point2 = CGPoint(x: 1, y: 2) 74 | point2 *= 1 75 | XCTAssertEqual(point2, CGPoint(x: 1, y: 2)) 76 | 77 | var point3 = CGPoint(x: 1, y: 2) 78 | point3 *= 1.5 79 | XCTAssertEqual(point3, CGPoint(x: 1.5, y: 3)) 80 | 81 | var point4 = CGPoint(x: 1, y: 2) 82 | point4 *= 3 83 | XCTAssertEqual(point4, CGPoint(x: 3, y: 6)) 84 | } 85 | 86 | } 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /Tests/CoreGraphicsTests/CGRectUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 5 | 6 | final class CGRectUtilsTests: XCTestCase { 7 | 8 | func testInit() { 9 | let rect = CGRect(width: 100, height: 50) 10 | XCTAssertEqual(rect.origin, .zero) 11 | XCTAssertEqual(rect.size, CGSize(width: 100, height: 50)) 12 | } 13 | 14 | func testCenter() { 15 | var rect = CGRect(width: 100, height: 50) 16 | XCTAssertEqual(rect.center, CGPoint(x: 50, y: 25)) 17 | rect.center = CGPoint(x: 50, y: 25) 18 | XCTAssertEqual(rect.center, CGPoint(x: 50, y: 25)) 19 | 20 | rect.center = .zero 21 | XCTAssertEqual(rect.center, .zero) 22 | XCTAssertEqual(rect.origin, CGPoint(x: -50, y: -25)) 23 | 24 | rect.center = CGPoint(x: 100, y: 30) 25 | XCTAssertEqual(rect.origin, CGPoint(x: 50, y: 5)) 26 | } 27 | 28 | func testRounded() { 29 | XCTAssertEqual(CGRect.zero.rounded(rule: .down), .zero) 30 | XCTAssertEqual(CGRect(x: 10, y: 11, width: 12, height: 13).rounded(rule: .down), CGRect(x: 10, y: 11, width: 12, height: 13)) 31 | XCTAssertEqual(CGRect(x: 10.3, y: 11.5, width: 12.7, height: 13.0).rounded(rule: .down), CGRect(x: 10, y: 11, width: 12, height: 13)) 32 | XCTAssertEqual(CGRect.zero.rounded(rule: .up), .zero) 33 | XCTAssertEqual(CGRect(x: 10, y: 11, width: 12, height: 13).rounded(rule: .up), CGRect(x: 10, y: 11, width: 12, height: 13)) 34 | XCTAssertEqual(CGRect(x: 10.3, y: 11.5, width: 12.7, height: 13.0).rounded(rule: .up), CGRect(x: 11, y: 12, width: 13, height: 13)) 35 | } 36 | 37 | func testArea() { 38 | XCTAssertEqual(CGRect.zero.area, 0) 39 | XCTAssertEqual(CGRect(width: 100, height: 50).area, 5000) 40 | XCTAssertEqual(CGRect(width: 100, height: 0).area, 0) 41 | XCTAssertEqual(CGRect(width: 100, height: 0.1).area, 10) 42 | } 43 | 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /Tests/CoreGraphicsTests/CGSizeUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 5 | 6 | final class CGSizeUtilsTests: XCTestCase { 7 | 8 | func testAspectFit() { 9 | XCTAssertEqual(CGSize(width: 100, height: 50).aspectFit(boundingSize: .zero), .zero) 10 | 11 | do { 12 | let size = CGSize(width: 100, height: 50).aspectFit(boundingSize: CGSize(width: 70, height: 30)) 13 | #if (arch(i386) || arch(arm)) 14 | XCTAssertEqual(size.width.rounded(.down), 60) 15 | XCTAssertEqual(size.height.rounded(.down), 30) 16 | #else 17 | XCTAssertEqual(size.width, 60) 18 | XCTAssertEqual(size.height, 30) 19 | #endif 20 | } 21 | 22 | do { 23 | let size = CGSize(width: 100, height: 50).aspectFit(boundingSize: CGSize(width: 90, height: 30)) 24 | #if (arch(i386) || arch(arm)) 25 | XCTAssertEqual(size.width.rounded(.down), 60) 26 | XCTAssertEqual(size.height.rounded(.down), 30) 27 | #else 28 | XCTAssertEqual(size.width, 60) 29 | XCTAssertEqual(size.height, 30) 30 | #endif 31 | } 32 | 33 | do { 34 | let size = CGSize(width: 100, height: 50).aspectFit(boundingSize: CGSize(width: 100, height: 50)) 35 | #if (arch(i386) || arch(arm)) 36 | XCTAssertEqual(size.width.rounded(.down), 100) 37 | XCTAssertEqual(size.height.rounded(.down), 50) 38 | #else 39 | XCTAssertEqual(size.width, 100) 40 | XCTAssertEqual(size.height, 50) 41 | #endif 42 | } 43 | 44 | do { 45 | let size = CGSize(width: 100, height: 50).aspectFit(boundingSize: CGSize(width: 150, height: 60)) 46 | #if (arch(i386) || arch(arm)) 47 | XCTAssertEqual(size.width.rounded(.down), 120) 48 | XCTAssertEqual(size.height.rounded(.down), 60) 49 | #else 50 | XCTAssertEqual(size.width, 120) 51 | XCTAssertEqual(size.height, 60) 52 | #endif 53 | } 54 | 55 | } 56 | 57 | func testAdd() { 58 | XCTAssertEqual(CGSize(width: 20, height: 20), CGSize(width: 10, height: 10) + CGSize(width: 10, height: 10)) 59 | XCTAssertEqual(CGSize(width: 30, height: 20), CGSize(width: 10, height: 10) + CGSize(width: 20, height: 10)) 60 | XCTAssertEqual(CGSize(width: 20, height: 20), CGSize(width: 20, height: 20) + CGSize.zero) 61 | } 62 | 63 | func testAddEqual() { 64 | var size = CGSize(width: 10, height: 10) 65 | 66 | size += CGSize(width: 10, height: 10) 67 | XCTAssertEqual(CGSize(width: 20, height: 20), size) 68 | } 69 | 70 | func testSubtract() { 71 | XCTAssertEqual(CGSize(width: 0, height: 0), CGSize(width: 10, height: 10) - CGSize(width: 10, height: 10)) 72 | XCTAssertEqual(CGSize(width: -10, height: 0), CGSize(width: 10, height: 10) - CGSize(width: 20, height: 10)) 73 | } 74 | 75 | func testSubtractEqual() { 76 | var size = CGSize(width: 10, height: 10) 77 | 78 | size -= CGSize(width: 10, height: 10) 79 | XCTAssertEqual(CGSize.zero, size) 80 | 81 | } 82 | 83 | func testMultiplyScalar() { 84 | XCTAssertEqual(CGSize(width: 20, height: 20), CGSize(width: 10, height: 10) * 2) 85 | } 86 | 87 | func testMultiplyScalarEqual() { 88 | var size = CGSize(width: 10, height: 10) 89 | size *= 2 90 | XCTAssertEqual(CGSize(width: 20, height: 20), size) 91 | } 92 | 93 | } 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /Tests/FoundationTests/BundleInfoTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | final class BundleInfoTests: XCTestCase { 5 | 6 | func testBundle() { 7 | 8 | // Given, When 9 | let bundle = Mechanica.bundle 10 | // Then 11 | if ProcessInfo.isRunningXcodeUnitTests { 12 | XCTAssertNotNil(bundle.version) 13 | XCTAssertNotNil(bundle.shortVersionString) 14 | XCTAssertNil(bundle.displayName) 15 | XCTAssertEqual(bundle.executableFileName, "Mechanica") 16 | 17 | } else if ProcessInfo.isRunningSwiftPackageTests { 18 | XCTAssertNil(bundle.version) 19 | XCTAssertNil(bundle.shortVersionString) 20 | XCTAssertNil(bundle.displayName) 21 | XCTAssertEqual(bundle.executableFileName, "MechanicaPackageTests") 22 | 23 | } else { 24 | XCTFail("Missing ProcessInfo details.") 25 | } 26 | 27 | } 28 | 29 | func testAppIdentifier() { 30 | if ProcessInfo.isRunningSwiftPackageTests { 31 | XCTAssertEqual(Mechanica.bundle.appIdentifier, "MechanicaPackageTests") 32 | } else { 33 | XCTAssertEqual(Mechanica.bundle.appIdentifier, "com.alessandromarzoli.Mechanica") 34 | } 35 | XCTAssertEqual(Bundle.main.appIdentifier, "com.apple.dt.xctest.tool") 36 | } 37 | 38 | func testUrlSchemes() { 39 | class TestBundle: Bundle { 40 | 41 | private var _infoDictionary: [String : Any]? = nil 42 | 43 | override var infoDictionary: [String : Any]? { 44 | set { 45 | _infoDictionary = newValue 46 | } 47 | get { 48 | return _infoDictionary 49 | } 50 | } 51 | } 52 | 53 | let testBundle = TestBundle() 54 | var testInfoDictionary = [String : Any]() 55 | 56 | var urlType = [String: Any]() 57 | urlType["CFBundleURLSchemes"] = ["url1" as Any, "url2" as Any] 58 | 59 | var urlTypes = [Any]() 60 | urlTypes.append(urlType) 61 | testInfoDictionary["CFBundleURLTypes"] = urlTypes 62 | testBundle.infoDictionary = testInfoDictionary 63 | 64 | 65 | XCTAssertEqual(testBundle.urlSchemes.count, 2) 66 | XCTAssertTrue(Bundle.main.urlSchemes.isEmpty) 67 | } 68 | 69 | #if os(iOS) || os(tvOS) || os(watchOS) 70 | func testIsAppRunningThroughTestFlight() { 71 | XCTAssertFalse(Bundle.main.isAppRunningThroughTestFlight) 72 | } 73 | #endif 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Tests/FoundationTests/CalendarUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension CalendarUtilsTests { 5 | static var allTests = [ 6 | // ("testQuarter", testQuarter), 7 | // ("testIsDateInCurrentWeek", testIsDateInCurrentWeek), // TODO: crash on Swift 5 8 | ("testIsDateInWorkDay", testIsDateInWorkDay), 9 | //("testIsDateInCurrentMonth", testIsDateInCurrentMonth), // TODO: crash on Swift 5 10 | // ("testIsDateInCurrentYear", testIsDateInCurrentYear) //TODO enumerateDates(startingAfter:matching:options:using:) is not yet implemented (Swift 4.1.2) 11 | ] 12 | } 13 | 14 | final class CalendarUtilsTests: XCTestCase { 15 | 16 | let calendar = Calendar.current 17 | 18 | #if os(iOS) || os(tvOS) || os(watchOS) || os(macOS) 19 | func testQuarter() { 20 | let date1 = Date(timeIntervalSince1970: 0) 21 | XCTAssertEqual(calendar.quarter(from: date1), 1) 22 | 23 | let date2 = Calendar.current.date(byAdding: .month, value: 4, to: date1)! 24 | XCTAssertEqual(calendar.quarter(from: date2), 2) 25 | 26 | let date3 = Calendar.current.date(byAdding: .month, value: 8, to: date1)! 27 | XCTAssertEqual(calendar.quarter(from: date3), 3) 28 | 29 | let date4 = Calendar.current.date(byAdding: .month, value: 11, to: date1)! 30 | XCTAssertEqual(calendar.quarter(from: date4), 4) 31 | } 32 | 33 | func testIsDateInCurrentWeek() { 34 | let date = Date() 35 | 36 | let newDate = calendar.date(byAdding: .month, value: 1, to: date)! 37 | XCTAssertFalse(calendar.isDateInCurrentWeek(newDate)) 38 | 39 | XCTAssertTrue(calendar.isDateInCurrentWeek(date)) // unless the test is run between two months... } 40 | } 41 | 42 | func testIsDateInCurrentMonth() { 43 | let date = Date() 44 | 45 | let newDate = calendar.date(byAdding: .month, value: 1, to: date)! 46 | XCTAssertFalse(calendar.isDateInCurrentMonth(newDate)) 47 | 48 | XCTAssertTrue(calendar.isDateInCurrentMonth(date)) // unless the test is run between two months... 49 | } 50 | 51 | func testIsDateInCurrentYear() { 52 | let date = Date() 53 | let year = calendar.component(.year, from: Date()) 54 | 55 | let newDate = calendar.date(bySetting: .year, value: year + 1, of: date)! 56 | XCTAssertFalse(calendar.isDateInCurrentYear(newDate)) 57 | 58 | 59 | let newDate2 = calendar.date(bySetting: .year, value: year, of: date)! 60 | XCTAssertTrue(calendar.isDateInCurrentYear(newDate2)) 61 | } 62 | 63 | #endif 64 | 65 | func testIsDateInWorkDay() { 66 | let components = DateComponents(calendar: calendar, 67 | timeZone: TimeZone(abbreviation: "UTC"), 68 | year: 2018, 69 | month: 7, 70 | day: 14) 71 | let date = calendar.date(from: components)! 72 | XCTAssertFalse(calendar.isDateInWorkDay(date)) 73 | 74 | let newDate = calendar.date(byAdding: .day, value: 2, to: date)! 75 | XCTAssertTrue(calendar.isDateInWorkDay(newDate)) 76 | } 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /Tests/FoundationTests/DataUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension DataUtilsTests { 5 | static var allTests = [ 6 | ("testBytes", testBytes), 7 | ("testHexEncodedString", testHexEncodedString) 8 | ] 9 | } 10 | 11 | final class DataUtilsTests: XCTestCase { 12 | 13 | func testBytes() { 14 | let data = "tinrobots".data(using: .utf8) 15 | let bytes = data?.bytes 16 | XCTAssertNotNil(bytes) 17 | XCTAssertEqual(bytes?.count, 9) 18 | } 19 | 20 | func testHexEncodedString() { 21 | let data = Data([0, 1, 127, 128, 255]) 22 | XCTAssertEqual(data.hexEncodedString, "00017f80ff") 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Tests/FoundationTests/DispatchQueueUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | #if canImport(Dispatch) 5 | 6 | import Dispatch 7 | 8 | extension DispatchQueueUtilsTests { 9 | static var allTests = [ 10 | ("testIsMainQueue", testIsMainQueue), 11 | ("testIsCurrent", testIsCurrent), 12 | ] 13 | } 14 | 15 | final class DispatchQueueUtilsTests: XCTestCase { 16 | 17 | func testIsMainQueue() { 18 | // Given, When 19 | let expect = expectation(description: "testIsMainQueue") 20 | let group = DispatchGroup() 21 | 22 | // Then 23 | DispatchQueue.main.async(group: group) { 24 | XCTAssertTrue(DispatchQueue.isMainQueue) 25 | } 26 | 27 | DispatchQueue.global().async(group: group) { 28 | XCTAssertFalse(DispatchQueue.isMainQueue) 29 | } 30 | 31 | group.notify(queue: .main) { 32 | expect.fulfill() 33 | } 34 | 35 | waitForExpectations(timeout: 0.5, handler: nil) 36 | } 37 | 38 | func testIsCurrent() { 39 | // Given, When 40 | let expect = expectation(description: "testIsCurrent") 41 | let group = DispatchGroup() 42 | let queue = DispatchQueue.global() 43 | 44 | // Then 45 | queue.async(group: group) { 46 | XCTAssertTrue(DispatchQueue.isCurrent(queue)) 47 | } 48 | DispatchQueue.main.async(group: group) { 49 | XCTAssertTrue(DispatchQueue.isCurrent(DispatchQueue.main)) 50 | XCTAssertFalse(DispatchQueue.isCurrent(queue)) 51 | } 52 | 53 | group.notify(queue: .main) { 54 | expect.fulfill() 55 | } 56 | 57 | waitForExpectations(timeout: 0.5, handler: nil) 58 | } 59 | 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /Tests/FoundationTests/FileManagerUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension FileManagerUtilsTests { 5 | static var allTests = [ 6 | ("testDestroyFileOrDirectory", testDestroyFileOrDirectory), 7 | ("testCleanDirectory", testCleanDirectory), 8 | ("testNewCachedSubDirectory", testNewCachedSubDirectory) 9 | ] 10 | } 11 | 12 | final class FileManagerUtilsTests: XCTestCase { 13 | 14 | func testNewCachedSubDirectory() throws { 15 | let url = try FileManager.default.newCachesSubDirectory(withName: "SubFolder") 16 | XCTAssertTrue(FileManager.default.fileExists(atPath: url.path)) 17 | 18 | try FileManager.default.deleteFileOrDirectory(atPath: url.path) 19 | } 20 | 21 | func testDestroyFileOrDirectory() throws { 22 | // Given 23 | let tmpFolderPath = "/tmp/com.alessandromarzoli.Mechanica-\(UUID().uuidString)" 24 | if !FileManager.default.fileExists(atPath: tmpFolderPath) { 25 | try FileManager.default.createDirectory(atPath: tmpFolderPath, withIntermediateDirectories: false, attributes: nil) 26 | } 27 | 28 | // When 29 | let testFilePath = tmpFolderPath + "/" + "TestFile.txt" 30 | let testFolderPath = tmpFolderPath + "/" + "TestDirectory" 31 | XCTAssertTrue(FileManager.default.createFile(atPath: testFilePath, contents: Data(), attributes: nil)) 32 | try FileManager.default.createDirectory(atPath: testFolderPath, withIntermediateDirectories: false, attributes: nil) 33 | 34 | // Then 35 | XCTAssertTrue(FileManager.default.fileExists(atPath: tmpFolderPath), "The directory at path \(tmpFolderPath) should exists.") 36 | XCTAssertTrue(FileManager.default.fileExists(atPath: testFilePath), "The file at path \(testFilePath) should exists.") 37 | XCTAssertTrue(FileManager.default.fileExists(atPath: testFolderPath), "The directory at path \(testFolderPath) should exists.") 38 | 39 | try FileManager.default.deleteFileOrDirectory(atPath: tmpFolderPath) 40 | XCTAssertFalse(FileManager.default.fileExists(atPath: tmpFolderPath)) 41 | 42 | } 43 | 44 | func testCleanDirectory() throws { 45 | // Given 46 | let tmpFolderPath = "/tmp/com.alessandromarzoli.Mechanica-\(UUID().uuidString)" 47 | if !FileManager.default.fileExists(atPath: tmpFolderPath) { 48 | try FileManager.default.createDirectory(atPath: tmpFolderPath, withIntermediateDirectories: false, attributes: nil) 49 | } 50 | 51 | // When 52 | let testFilePath = tmpFolderPath + "/" + "TestFile.txt" 53 | let testFolderPath = tmpFolderPath + "/" + "TestDirectory" 54 | XCTAssertTrue(FileManager.default.createFile(atPath: testFilePath, contents: Data(), attributes: nil)) 55 | try FileManager.default.createDirectory(atPath: testFolderPath, withIntermediateDirectories: false, attributes: nil) 56 | 57 | // Then 58 | XCTAssertTrue(FileManager.default.fileExists(atPath: tmpFolderPath), "The directory at path \(tmpFolderPath) should exists.") 59 | XCTAssertTrue(FileManager.default.fileExists(atPath: testFilePath), "The file at path \(testFilePath) should exists.") 60 | XCTAssertTrue(FileManager.default.fileExists(atPath: testFolderPath), "The directory at path \(testFolderPath) should exists.") 61 | 62 | try FileManager.default.cleanDirectory(atPath: tmpFolderPath) 63 | XCTAssertTrue(FileManager.default.fileExists(atPath: tmpFolderPath), "The directory at path \(tmpFolderPath) should exists.") 64 | XCTAssertFalse(FileManager.default.fileExists(atPath: testFilePath), "The file at path \(testFilePath) should not exists.") 65 | XCTAssertFalse(FileManager.default.fileExists(atPath: testFilePath), "The directory at path \(testFilePath) should not exists.") 66 | XCTAssertTrue(try FileManager.default.contentsOfDirectory(atPath: tmpFolderPath).count == 0, "The directory at path \(tmpFolderPath) should be empty.") 67 | 68 | // When 69 | let testFakeFilePath = tmpFolderPath + "/" + "TestFakeFile.txt" 70 | let testFakeFolderPath = tmpFolderPath + "/" + "TestFakeDirectory" 71 | try FileManager.default.cleanDirectory(atPath: testFakeFilePath) 72 | try FileManager.default.cleanDirectory(atPath: testFakeFolderPath) 73 | 74 | // Then 75 | XCTAssertFalse(FileManager.default.fileExists(atPath: testFakeFilePath), "The file at path \(testFakeFilePath) should not exists.") 76 | XCTAssertFalse(FileManager.default.fileExists(atPath: testFakeFolderPath), "The directory at path \(testFakeFolderPath) should not exists.") 77 | 78 | /// destroy the tmp folder 79 | try FileManager.default.deleteFileOrDirectory(atPath: tmpFolderPath) 80 | XCTAssertFalse(FileManager.default.fileExists(atPath: testFolderPath)) 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /Tests/FoundationTests/LocaleUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension LocaleUtilsTests { 5 | static var allTests = [("testPosix", testPosix)] 6 | } 7 | 8 | final class LocaleUtilsTests: XCTestCase { 9 | func testPosix() { 10 | let test: Locale = .posix 11 | XCTAssertEqual(test.identifier, "en_US_POSIX") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/FoundationTests/NSAttributedStringUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension NSAttributedStringUtilsTests { 5 | static var allTests = [ 6 | ("testAddition", testAddition) 7 | ] 8 | } 9 | 10 | final class NSAttributedStringUtilsTests: XCTestCase { 11 | 12 | #if !os(Linux) 13 | func testInitHTML(){ 14 | do { 15 | // Given, When 16 | let html = """ 17 | 18 | 19 | 20 | Hello World 21 | """ 22 | let s = NSAttributedString(html: html) 23 | // Then 24 | XCTAssertNotNil(s) 25 | 26 | XCTAssertTrue(s!.string == "Hello World") 27 | guard let font = s!.attribute(NSAttributedString.Key.font, at: 0, effectiveRange: nil) as? Font else { 28 | XCTAssert(false, "No Avenir-Roman font name found.") 29 | return 30 | } 31 | XCTAssertTrue(font.familyName == "Avenir") 32 | XCTAssertTrue(font.fontName == "Avenir-Roman") 33 | XCTAssertTrue(font.pointSize == 15.00) 34 | 35 | guard let color = s!.attribute(NSAttributedString.Key.backgroundColor, at: 0, effectiveRange: nil) as? Color else { 36 | XCTAssert(false, "No text backgroud-color found.") 37 | return 38 | } 39 | XCTAssertTrue(color.hexString == "#9999ff".uppercased()) 40 | 41 | guard let _ = s!.attribute(NSAttributedString.Key.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle else { 42 | XCTAssert(false, "No NSParagraphStyle found.") 43 | return 44 | } 45 | } 46 | 47 | do { 48 | let html = "" 49 | let s = NSAttributedString(html: html) 50 | XCTAssertNotNil(s) 51 | XCTAssertTrue(s!.string.isEmpty) 52 | } 53 | 54 | } 55 | #endif 56 | 57 | func testAddition() { 58 | 59 | /// addition between NSAttributedStrings 60 | do { 61 | let s1 = NSAttributedString(string: "Hello World") 62 | let s2 = NSAttributedString(string: "!!") 63 | let s3 = NSAttributedString(string: "🤖") 64 | 65 | let s4 = s1 + s2 + s3 // NSAttributedString + NSAttributedString + NSAttributedString 66 | XCTAssertTrue(s4.string == "Hello World!!🤖") 67 | XCTAssertFalse(s4 is NSMutableAttributedString) 68 | } 69 | 70 | /// addition between NSAttributedString and String 71 | do { 72 | let s1 = NSAttributedString(string: "Hello World") 73 | let s2 = "!!" 74 | let s3 = "🤖" 75 | 76 | let s4 = s1 + s2 + s3 // NSAttributedString + String + String 77 | XCTAssertTrue(s4.string == "Hello World!!🤖") 78 | XCTAssertFalse(s4 is NSMutableAttributedString) 79 | } 80 | 81 | /// addition between String and NSAttributedString 82 | do { 83 | let s1 = "Hello World" 84 | let s2 = NSAttributedString(string: "!!") 85 | let s3 = NSAttributedString(string: "🤖") 86 | 87 | let s4 = s1 + s2 + s3 // String + NSAttributedString + NSAttributedString 88 | XCTAssertTrue(s4.string == "Hello World!!🤖") 89 | XCTAssertFalse(s4 is NSMutableAttributedString) 90 | } 91 | 92 | do { 93 | let s1 = "Hello World" 94 | let s2 = NSAttributedString(string: "!!") 95 | 96 | let s3 = s1 + s2 // String + NSAttributedString 97 | XCTAssertTrue(s3.string == "Hello World!!") 98 | XCTAssertFalse(s3 is NSMutableAttributedString) 99 | } 100 | 101 | } 102 | 103 | } 104 | 105 | -------------------------------------------------------------------------------- /Tests/FoundationTests/NSObjectProtocolUtils.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | final class NSObjectProtocolUtils: XCTestCase { 5 | 6 | static var allTests = [("testClassName", testClassName)] 7 | 8 | private class Demo: NSObject {} 9 | 10 | // MARK: - Utilities 11 | 12 | func testClassName(){ 13 | XCTAssertEqual(Demo.type, "Demo") 14 | XCTAssertEqual(Demo().type, "Demo") 15 | } 16 | 17 | // MARK: - Associated Values 18 | 19 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 20 | func testAssociatedValue() { 21 | 22 | class Demo: NSObject { 23 | let var1: String 24 | let var2: Int 25 | 26 | init(var1: String, var2: Int) { 27 | self.var1 = var1 28 | self.var2 = var2 29 | } 30 | } 31 | 32 | do { 33 | let demo = Demo(var1: "demo", var2: 1) 34 | var key = UInt8(1) 35 | let value = "value" 36 | demo.setAssociatedValue(value, forKey: &key) 37 | XCTAssert(demo.getAssociatedValue(forKey: &key) == value ) 38 | } 39 | 40 | do { 41 | let demo = Demo(var1: "demo", var2: 1) 42 | var key = UInt8(1) 43 | var key2 = UInt8(2) 44 | let value = "value" 45 | let value2 = "value2" 46 | demo.setAssociatedValue(value, forKey: &key) 47 | demo.setAssociatedValue(value2, forKey: &key2) 48 | demo.removeAllAssociatedValues() 49 | XCTAssert(demo.getAssociatedValue(forKey: &key) == nil ) 50 | XCTAssert(demo.getAssociatedValue(forKey: &key2) == nil ) 51 | } 52 | 53 | do { 54 | let demo = Demo(var1: "demo", var2: 1) 55 | var key = UInt8(1) 56 | let value = UInt32(100_000_000) 57 | demo.setAssociatedValue(value, forKey: &key) 58 | XCTAssert(demo.getAssociatedValue(forKey: &key) == value ) 59 | demo.removeAssociatedValue(forKey: &key) 60 | XCTAssert(demo.getAssociatedValue(forKey: &key) == nil) 61 | } 62 | 63 | do { 64 | let demo = Demo(var1: "demo", var2: 1) 65 | var key = UInt8(1) 66 | let value = "value" 67 | demo.setAssociatedValue(value, forKey: &key) 68 | XCTAssert(demo.getAssociatedValue(forKey: &key) == value ) 69 | demo.removeAssociatedValue(forKey: &key) 70 | XCTAssert(demo.getAssociatedValue(forKey: &key) == nil) 71 | } 72 | 73 | do { 74 | let demo = Demo(var1: "demo", var2: 1) 75 | var key = Demo(var1: "key", var2: 2) 76 | let value = Demo(var1: "value", var2: 3) 77 | demo.setAssociatedValue(value, forKey: &key) 78 | XCTAssert(demo.getAssociatedValue(forKey: &key) === value ) 79 | demo.removeAssociatedValue(forKey: &key) 80 | XCTAssert(demo.getAssociatedValue(forKey: &key) == nil) 81 | } 82 | 83 | do { 84 | struct DemoStruct { var var1: String } 85 | let demo = Demo(var1: "demo", var2: 1) 86 | var key = DemoStruct(var1: "key").var1 87 | let value = Demo(var1: "value", var2: 3) 88 | demo.setAssociatedValue(value, forKey: &key) 89 | XCTAssert(demo.getAssociatedValue(forKey: &key) === value ) 90 | demo.removeAssociatedValue(forKey: &key) 91 | XCTAssert(demo.getAssociatedValue(forKey: &key) == nil) 92 | } 93 | 94 | } 95 | #endif 96 | } 97 | 98 | // MARK: - Method Swizzling 99 | 100 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 101 | class Test : NSObject { 102 | @objc dynamic func methodOne() -> Int { return 1 } 103 | @objc dynamic func methodThree() -> String { return "three" } 104 | @objc dynamic func methodFive(string: String) -> String { return ".." + string + ".." } 105 | override init(){} 106 | } 107 | 108 | extension Test { 109 | @objc func methodTwo() -> Int { return methodTwo() + 10 } 110 | @objc func methodFour() -> String { return methodFour() + "!!" } 111 | @objc func methodSix(string: String) -> String { return "--" + string + "--" } 112 | } 113 | 114 | final class NSObjectProtocolSwizzlingTests: XCTestCase { 115 | 116 | override func setUp() { 117 | super.setUp() 118 | Test.swizzle([ 119 | ( #selector(Test.methodOne), #selector(Test.methodTwo) ), 120 | ( #selector(Test.methodThree), #selector(Test.methodFour) ), 121 | ( #selector(Test.methodFive), #selector(Test.methodSix) ) 122 | ]) 123 | } 124 | 125 | func testSwizzlingExtension() { 126 | let test = Test() 127 | XCTAssertTrue(test.methodOne() == 11) 128 | XCTAssertTrue(test.methodTwo() == 1) 129 | XCTAssertTrue(test.methodThree() == "three!!") 130 | XCTAssertTrue(test.methodFour() == "three") 131 | XCTAssertTrue(test.methodFive(string: "five") == "--five--") 132 | XCTAssertTrue(test.methodSix(string: "six") == "..six..") 133 | } 134 | 135 | } 136 | #endif 137 | 138 | -------------------------------------------------------------------------------- /Tests/FoundationTests/ProcessInfoTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension ProcessInfoTests { 5 | static var allTests = [ 6 | ("testSystemStartingDate", testSystemStartingDate), 7 | ("testIsSanboxed", testIsSanboxed), 8 | ("testIsRunningUnitTests", testIsRunningUnitTests), 9 | ("testIsRunningXcodeUnitTests", testIsRunningXcodeUnitTests), 10 | ("testIsRunningSwiftPackageTests", testIsRunningSwiftPackageTests) 11 | ] 12 | } 13 | 14 | final class ProcessInfoTests: XCTestCase { 15 | 16 | func testSystemStartingDate() { 17 | let date = ProcessInfo.systemStartingDate 18 | XCTAssertTrue(date < Date()) 19 | XCTAssertTrue(date.timeIntervalSinceNow < 60*5) 20 | } 21 | 22 | func testIsSanboxed() { 23 | #if os(macOS) 24 | XCTAssertFalse(ProcessInfo.isSandboxed) 25 | #endif 26 | } 27 | 28 | func testIsRunningUnitTests() { 29 | XCTAssertTrue(ProcessInfo.isRunningUnitTests) 30 | } 31 | 32 | func testIsRunningXcodeUnitTests() { 33 | #if os(Linux) 34 | XCTAssertFalse(ProcessInfo.isRunningXcodeUnitTests) 35 | #else 36 | if ProcessInfo.processInfo.arguments.filter({ $0.contains("MechanicaPackageTests") }).isEmpty { 37 | XCTAssertTrue(ProcessInfo.isRunningXcodeUnitTests) 38 | } else { 39 | XCTAssertFalse(ProcessInfo.isRunningXcodeUnitTests) 40 | } 41 | #endif 42 | } 43 | 44 | func testIsRunningSwiftPackageTests() { 45 | #if os(Linux) 46 | XCTAssertTrue(ProcessInfo.isRunningSwiftPackageTests) 47 | #else 48 | if ProcessInfo.processInfo.arguments.filter({ $0.contains("MechanicaPackageTests") }).isEmpty { 49 | XCTAssertFalse(ProcessInfo.isRunningSwiftPackageTests) 50 | } else { 51 | XCTAssertTrue(ProcessInfo.isRunningSwiftPackageTests) 52 | } 53 | #endif 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Tests/FoundationTests/StatUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension StatUtilsTests { 5 | static var allTests = [ 6 | ("testIsDirectory", testIsDirectory), 7 | ("testIsFile", testIsFile), 8 | ("testIsLink", testIsLink) 9 | ] 10 | } 11 | 12 | final class StatUtilsTests: XCTestCase { 13 | 14 | func testIsDirectory() throws { 15 | let tmpPath = "/tmp" 16 | 17 | do { 18 | var result = Stat() 19 | stat(tmpPath, &result) 20 | XCTAssertTrue(result.isDirectory) 21 | XCTAssertFalse(result.isFile) 22 | XCTAssertFalse(result.isLink) 23 | } 24 | 25 | do { 26 | let folderPath = tmpPath + "/" + "test" 27 | try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: false, attributes: nil) 28 | 29 | var result = Stat() 30 | stat(folderPath, &result) 31 | XCTAssertTrue(result.isDirectory) 32 | XCTAssertFalse(result.isFile) 33 | XCTAssertFalse(result.isLink) 34 | 35 | try FileManager.default.removeItem(atPath: folderPath) 36 | } 37 | 38 | } 39 | 40 | func testIsFile() throws { 41 | let tmpPath = "/tmp" 42 | let filePath = tmpPath + "/" + "file.txt" 43 | 44 | XCTAssertTrue(FileManager.default.createFile(atPath: filePath, contents: Data(), attributes: nil)) 45 | 46 | var result = Stat() 47 | stat(filePath, &result) 48 | XCTAssertFalse(result.isDirectory) 49 | XCTAssertTrue(result.isFile) 50 | XCTAssertFalse(result.isLink) 51 | 52 | try FileManager.default.removeItem(atPath: filePath) 53 | 54 | } 55 | 56 | func testIsLink() throws { 57 | let tmpPath = "/tmp" 58 | let filePath = tmpPath + "/" + "file.txt" 59 | let linkPath = tmpPath + "/" + "link" 60 | 61 | XCTAssertTrue(FileManager.default.createFile(atPath: filePath, contents: Data(), attributes: nil)) 62 | try FileManager.default.createSymbolicLink(atPath: linkPath, withDestinationPath: filePath) 63 | 64 | do { 65 | var result = Stat() 66 | // lstat() is identical to stat(), except that if pathname is a symbolic link, then it returns information about the link itself, not the file that it refers to. 67 | lstat(linkPath, &result) 68 | XCTAssertFalse(result.isDirectory) 69 | XCTAssertFalse(result.isFile) 70 | XCTAssertTrue(result.isLink) 71 | } 72 | 73 | do { 74 | var result = Stat() 75 | stat(linkPath, &result) 76 | XCTAssertFalse(result.isDirectory) 77 | XCTAssertTrue(result.isFile) 78 | XCTAssertFalse(result.isLink) 79 | } 80 | 81 | try FileManager.default.removeItem(atPath: linkPath) 82 | try FileManager.default.removeItem(atPath: filePath) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Tests/FoundationTests/URLUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension URLUtilsTests { 5 | static var allTests = [ 6 | ("testQueryParameters", testQueryParameters), 7 | ("testDeletingLastPathComponents", testDeletingLastPathComponents), 8 | ("testIsDirectoryOrFile", testIsDirectoryOrFile), 9 | ("testIsParent", testIsParent), 10 | ] 11 | } 12 | 13 | final class URLUtilsTests: XCTestCase { 14 | 15 | func testQueryParameters() { 16 | do { 17 | let url = URL(string: "http://tinrobots.org/services/test/demo?v=1.1&q=hello") 18 | 19 | if let queryParameters = url?.queryParameters { 20 | XCTAssertEqual(queryParameters["v"], Optional("1.1")) 21 | XCTAssertEqual(queryParameters["q"], Optional("hello")) 22 | XCTAssertEqual(queryParameters["other"], nil) 23 | } 24 | } 25 | 26 | do { 27 | let url = URL(string: "http://tinrobots.org/services/test/demo?") 28 | XCTAssertNil(url!.queryParameters) 29 | } 30 | } 31 | 32 | func testDeletingLastPathComponents() { 33 | do { 34 | var url = URL(string: "http://tinrobots.org/services/test/demo")! 35 | url.deleteLastPathComponents(0) 36 | XCTAssertEqual(url, URL(string: "http://tinrobots.org/services/test/demo")!) 37 | url.deleteLastPathComponents(2) 38 | XCTAssertEqual(url, URL(string: "http://tinrobots.org/services/")!) 39 | url.deleteLastPathComponents(3) 40 | XCTAssertEqual(url, URL(string: "http://tinrobots.org/../../")!) 41 | } 42 | 43 | do { 44 | var url = URL(string: "http://tinrobots.org")! 45 | url.deleteLastPathComponents(0) 46 | XCTAssertEqual(url, URL(string: "http://tinrobots.org")!) 47 | url.deleteLastPathComponents(2) 48 | XCTAssertEqual(url, URL(string: "http://tinrobots.org")!) 49 | } 50 | } 51 | 52 | func testIsParent() { 53 | let url = URL(string: "/path/to/folder")! 54 | XCTAssertTrue(url.isParent(of: URL(string: "/path/to/folder/child")!)) 55 | XCTAssertFalse(url.isParent(of: URL(string: "/path/folder/child")!)) 56 | XCTAssertFalse(url.isParent(of: URL(string: "/")!)) 57 | XCTAssertFalse(url.isParent(of: URL(string: ".")!)) 58 | } 59 | 60 | func testIsDirectoryOrFile() throws { 61 | // Given 62 | let folderPath = "/tmp/com.alessandromarzoli.Mechanica-\(UUID().uuidString)" 63 | 64 | // When, Then 65 | let url = URL(fileURLWithPath: folderPath) 66 | XCTAssertFalse(url.isDirectory) 67 | XCTAssertFalse(url.isFile) 68 | 69 | if !FileManager.default.fileExists(atPath: folderPath) { 70 | try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: false, attributes: nil) 71 | } 72 | 73 | XCTAssertTrue(url.isDirectory) 74 | XCTAssertFalse(url.isFile) 75 | 76 | // When, Then 77 | let filePath = folderPath + "/" + "TestFile.txt" 78 | let url2 = URL(fileURLWithPath: filePath) 79 | XCTAssertFalse(url2.isDirectory) 80 | XCTAssertFalse(url.isFile) 81 | 82 | XCTAssertTrue(FileManager.default.createFile(atPath: filePath, contents: Data(), attributes: nil)) 83 | XCTAssertTrue(url2.isFile) 84 | XCTAssertFalse(url2.isDirectory) 85 | 86 | /// destroy the tmp folder 87 | try FileManager.default.deleteFileOrDirectory(atPath: folderPath) 88 | XCTAssertFalse(FileManager.default.fileExists(atPath: folderPath)) 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import StandardLibraryTests 3 | @testable import FoundationTests 4 | 5 | XCTMain([ 6 | // Standard Library 7 | testCase(BinaryFloatingPointUtilsTests.allTests), 8 | testCase(BinaryIntegerUtilsTests.allTests), 9 | testCase(BoolUtilsTests.allTests), 10 | testCase(CharacterUtilsTests.allTests), 11 | testCase(CollectionUtilsTests.allTests), 12 | testCase(DictionaryUtilsTests.allTests), 13 | testCase(FloatingPointUtilsTests.allTests), 14 | testCase(OperatorsTests.allTests), 15 | testCase(OptionalUtilsTests.allTests), 16 | testCase(RangeReplaceableCollectionUtilsTests.allTests), 17 | testCase(SequenceUtilsTests.allTests), 18 | testCase(SignedIntegerTests.allTests), 19 | testCase(StringUtilsTests.allTests), 20 | testCase(UnsignedIntegerUtilsTests.allTests), 21 | 22 | // Foundation 23 | testCase(CalendarUtilsTests.allTests), 24 | testCase(DataUtilsTests.allTests), 25 | testCase(DateUtilsTests.allTests), 26 | testCase(DictionaryFoundationUtilsTests.allTests), 27 | testCase(DispatchQueueUtilsTests.allTests), 28 | testCase(FileManagerUtilsTests.allTests), 29 | testCase(FoundationUtilsTests.allTests), 30 | testCase(LocaleUtilsTests.allTests), 31 | testCase(NSObjectProtocolUtils.allTests), 32 | testCase(NSPredicateUtilsTests.allTests), 33 | testCase(NSAttributedStringUtilsTests.allTests), 34 | testCase(NSMutableAttributedStringUtilsTests.allTests), 35 | testCase(ProcessInfoTests.allTests), 36 | testCase(StatUtilsTests.allTests), 37 | testCase(StringFoundationUtilsTests.allTests), 38 | testCase(URLRequestUtilsTests.allTests), 39 | testCase(URLUtilsTests.allTests), 40 | testCase(UserDefaultsUtilsTests.allTests) 41 | ]) 42 | 43 | 44 | /** 45 | * Missing files: 46 | * BundleInfoTests.swift (Foundation) 47 | * FoundationUtilsTests.swift (Foundation) 48 | */ 49 | -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-30x60-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-30x60-@1x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-30x60-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-30x60-@2x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-30x60-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-30x60-@3x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-50x50-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-50x50-@1x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-50x50-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-50x50-@2x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-50x50-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-50x50-@3x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-60x30-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-60x30-@1x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-60x30-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-60x30-@2x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-60x30-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFill/apple-aspect.scaled.to.fill-60x30-@3x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-30x60-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-30x60-@1x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-30x60-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-30x60-@2x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-30x60-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-30x60-@3x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-50x50-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-50x50-@1x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-50x50-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-50x50-@2x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-50x50-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-50x50-@3x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-60x30-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-60x30-@1x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-60x30-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-60x30-@2x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-60x30-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/AspectScaleToFit/apple-aspect.scaled.to.fit-60x30-@3x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Circle/apple-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Circle/apple-circle.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Radius/apple-radius-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Radius/apple-radius-20.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Scale/apple-scaled-30x60-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Scale/apple-scaled-30x60-@1x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Scale/apple-scaled-30x60-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Scale/apple-scaled-30x60-@2x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Scale/apple-scaled-30x60-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Scale/apple-scaled-30x60-@3x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Scale/apple-scaled-50x50-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Scale/apple-scaled-50x50-@1x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Scale/apple-scaled-50x50-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Scale/apple-scaled-50x50-@2x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Scale/apple-scaled-50x50-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Scale/apple-scaled-50x50-@3x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Scale/apple-scaled-60x30-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Scale/apple-scaled-60x30-@1x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Scale/apple-scaled-60x30-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Scale/apple-scaled-60x30-@2x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Modified/Scale/apple-scaled-60x30-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Modified/Scale/apple-scaled-60x30-@3x.png -------------------------------------------------------------------------------- /Tests/Resources/Images/Original/apple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/Images/Original/apple.jpg -------------------------------------------------------------------------------- /Tests/Resources/Resource.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import Mechanica 3 | 4 | enum Resource { 5 | 6 | case glasses 7 | case glassesWithoutAlpha 8 | case robot 9 | case apple 10 | case circle(name: String) 11 | case radius(name: String) 12 | case scaled(name: String) 13 | case scaledToFit(name: String) 14 | case scaledToFill(name: String) 15 | 16 | var url: URL { 17 | switch self { 18 | case .glasses: 19 | return Resource.folderURL.appendingPathComponent("glasses.png") 20 | case .glassesWithoutAlpha: 21 | return Resource.folderURL.appendingPathComponent("glasses_without_alpha.jpeg") 22 | case .robot: 23 | return Resource.folderURL.appendingPathComponent("robot.png") 24 | case .apple: 25 | return Resource.folderURL.appendingPathComponent("Images/Original/apple.jpg") 26 | case .circle(let name): 27 | return Resource.folderURL.appendingPathComponent("Images/Modified/Circle/\(name)") 28 | case .radius(let name): 29 | return Resource.folderURL.appendingPathComponent("Images/Modified/Radius/\(name)") 30 | case .scaled(let name): 31 | return Resource.folderURL.appendingPathComponent("Images/Modified/Scale/\(name)") 32 | case .scaledToFit(let name): 33 | return Resource.folderURL.appendingPathComponent("Images/Modified/AspectScaleToFit/\(name)") 34 | case .scaledToFill(let name): 35 | return Resource.folderURL.appendingPathComponent("Images/Modified/AspectScaleToFill/\(name)") 36 | } 37 | } 38 | 39 | var data : Data { 40 | return try! Data(contentsOf: url) 41 | } 42 | 43 | static var folderURL: URL { 44 | var resources = URL(fileURLWithPath: #file, isDirectory: false).deletingLastPathComponents(2) 45 | resources.appendPathComponent("Resources") 46 | return resources 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Tests/Resources/glasses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/glasses.png -------------------------------------------------------------------------------- /Tests/Resources/glasses_without_alpha.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/glasses_without_alpha.jpeg -------------------------------------------------------------------------------- /Tests/Resources/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/Tests/Resources/robot.png -------------------------------------------------------------------------------- /Tests/SharedTests/NSDirectionalEdgeInsetsUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) || canImport(AppKit) 2 | 3 | import XCTest 4 | #if canImport(UIKit) 5 | import UIKit 6 | #else 7 | import AppKit 8 | #endif 9 | @testable import Mechanica 10 | 11 | @available(macOS 10.15, *) 12 | final class NSDirectionalEdgeInsetsUtilsTests: XCTestCase { 13 | 14 | func testHorizontal() { 15 | let inset = NSDirectionalEdgeInsets(top: 70.0, leading: 15.0, bottom: 5.0, trailing: 10.0) 16 | XCTAssertEqual(inset.horizontal, 25.0) 17 | } 18 | 19 | func testVertical() { 20 | let inset = NSDirectionalEdgeInsets(top: 20.0, leading: 10.0, bottom: 5.0, trailing: 10.0) 21 | XCTAssertEqual(inset.vertical, 25.0) 22 | } 23 | 24 | func testInitInset() { 25 | let inset = NSDirectionalEdgeInsets(inset: 15.5) 26 | XCTAssertEqual(inset.top, 15.5) 27 | XCTAssertEqual(inset.bottom, 15.5) 28 | XCTAssertEqual(inset.trailing, 15.5) 29 | XCTAssertEqual(inset.leading, 15.5) 30 | } 31 | 32 | func testInitVerticalHorizontal() { 33 | let inset = NSDirectionalEdgeInsets(horizontal: 20.0, vertical: 20.0) 34 | XCTAssertEqual(inset.top, 10.0) 35 | XCTAssertEqual(inset.bottom, 10.0) 36 | XCTAssertEqual(inset.trailing, 10.0) 37 | XCTAssertEqual(inset.leading, 10.0) 38 | } 39 | 40 | } 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /Tests/SharedTests/_Resources.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mechanica 3 | // 4 | // Copyright © 2016-2018 Tinrobots. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | import Foundation 25 | @testable import Mechanica 26 | 27 | enum Resource { 28 | 29 | case glasses 30 | case glassesWithoutAlpha 31 | case robot 32 | case apple 33 | case circle(name: String) 34 | case radius(name: String) 35 | case scaled(name: String) 36 | case scaledToFit(name: String) 37 | case scaledToFill(name: String) 38 | 39 | var url: URL { 40 | switch self { 41 | case .glasses: 42 | return Resource.folderURL.appendingPathComponent("glasses.png") 43 | case .glassesWithoutAlpha: 44 | return Resource.folderURL.appendingPathComponent("glasses_without_alpha.jpeg") 45 | case .robot: 46 | return Resource.folderURL.appendingPathComponent("robot.png") 47 | case .apple: 48 | return Resource.folderURL.appendingPathComponent("Images/Original/apple.jpg") 49 | case .circle(let name): 50 | return Resource.folderURL.appendingPathComponent("Images/Modified/Circle/\(name)") 51 | case .radius(let name): 52 | return Resource.folderURL.appendingPathComponent("Images/Modified/Radius/\(name)") 53 | case .scaled(let name): 54 | return Resource.folderURL.appendingPathComponent("Images/Modified/Scale/\(name)") 55 | case .scaledToFit(let name): 56 | return Resource.folderURL.appendingPathComponent("Images/Modified/AspectScaleToFit/\(name)") 57 | case .scaledToFill(let name): 58 | return Resource.folderURL.appendingPathComponent("Images/Modified/AspectScaleToFill/\(name)") 59 | } 60 | } 61 | 62 | var data : Data { 63 | return try! Data(contentsOf: url) 64 | } 65 | 66 | static var folderURL: URL { 67 | var resources = URL(fileURLWithPath: #file, isDirectory: false).deletingLastPathComponents(2) 68 | resources.appendPathComponent("Resources") 69 | return resources 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/BinaryFloatingPointUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension BinaryFloatingPointUtilsTests { 5 | static var allTests = [("testbinaryString", testbinaryString)] 6 | } 7 | 8 | final class BinaryFloatingPointUtilsTests: XCTestCase { 9 | 10 | // MARK: - BinaryConvertible 11 | 12 | /// http://www.binaryconvert.com/result_signed_int.html?decimal=045049049049 13 | func testbinaryString() { 14 | XCTAssertEqual(Float(-5.625).binaryString,"11000000101101000000000000000000") 15 | XCTAssertEqual(Float(329.390625).binaryString,"01000011101001001011001000000000") 16 | XCTAssertEqual(Float32(923.52).binaryString,"01000100011001101110000101001000") 17 | XCTAssertEqual(Float32(-1234.5678).binaryString,"11000100100110100101001000101011") 18 | XCTAssertEqual(Float32(50).binaryString,"01000010010010000000000000000000") 19 | XCTAssertEqual(Float32(-50).binaryString,"11000010010010000000000000000000") 20 | #if (arch(x86_64) || arch(arm64)) 21 | XCTAssertEqual(Float64(-100.1235).binaryString,"1100000001011001000001111110011101101100100010110100001110010110") 22 | XCTAssertEqual(Float64(923.52).binaryString,"0100000010001100110111000010100011110101110000101000111101011100") 23 | XCTAssertEqual(Float64(-3.14e-3202).binaryString,"1000000000000000000000000000000000000000000000000000000000000000") 24 | XCTAssertEqual(Float64(5e-324).binaryString,"0000000000000000000000000000000000000000000000000000000000000001") 25 | XCTAssertEqual(Float64.pi.binaryString,"0100000000001001001000011111101101010100010001000010110100011000") 26 | #endif 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/BinaryIntegerUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension BinaryIntegerUtilsTests { 5 | static var allTests = [ 6 | ("testIsEven", testIsEven), 7 | ("testIsOdd", testIsOdd), 8 | ("testIsPositive", testIsPositive), 9 | ("testIsNegative", testIsNegative) 10 | ] 11 | } 12 | 13 | final class BinaryIntegerUtilsTests: XCTestCase { 14 | 15 | func testIsEven() { 16 | XCTAssertTrue(2.isEven) 17 | XCTAssertTrue((-2).isEven) 18 | XCTAssertTrue(22.isEven) 19 | XCTAssertTrue((-22).isEven) 20 | XCTAssertTrue(202220.isEven) 21 | XCTAssertFalse(3.isEven) 22 | XCTAssertFalse(27.isEven) 23 | XCTAssertFalse((-27).isEven) 24 | XCTAssertFalse(202221.isEven) 25 | XCTAssertFalse((-202221).isEven) 26 | } 27 | 28 | func testIsOdd() { 29 | XCTAssertTrue(1.isOdd) 30 | XCTAssertTrue((-1).isOdd) 31 | XCTAssertTrue(11.isOdd) 32 | XCTAssertTrue((-11).isOdd) 33 | XCTAssertTrue(171717.isOdd) 34 | XCTAssertTrue((-171717).isOdd) 35 | 36 | XCTAssertFalse(2.isOdd) 37 | XCTAssertFalse((-2).isOdd) 38 | XCTAssertFalse(22.isOdd) 39 | XCTAssertFalse((-22).isOdd) 40 | XCTAssertFalse(202220.isOdd) 41 | XCTAssertFalse((-202220).isOdd) 42 | } 43 | 44 | func testIsPositive() { 45 | XCTAssertTrue(2.isPositive) 46 | XCTAssertTrue(21.isPositive) 47 | XCTAssertTrue(202220.isPositive) 48 | XCTAssertFalse((-2).isPositive) 49 | XCTAssertFalse((-21).isPositive) 50 | XCTAssertFalse((-202220).isPositive) 51 | } 52 | 53 | func testIsNegative() { 54 | XCTAssertTrue((-2).isNegative) 55 | XCTAssertTrue((-21).isNegative) 56 | XCTAssertTrue((-202220).isNegative) 57 | XCTAssertFalse(2.isNegative) 58 | XCTAssertFalse(21.isNegative) 59 | XCTAssertFalse(202220.isNegative) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/BoolUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension BoolUtilsTests { 5 | static var allTests = [ 6 | ("testInt", testInt), 7 | ("testRandom", testRandom), 8 | ("testToggle", testToggle), 9 | ("testBinaryString", testBinaryString) 10 | ] 11 | } 12 | 13 | final class BoolUtilsTests: XCTestCase { 14 | 15 | func testInt() { 16 | XCTAssert(true.int == 1) 17 | XCTAssert(false.int == 0) 18 | } 19 | 20 | func testRandom() { 21 | let b = Bool.random() 22 | switch b { 23 | case true: 24 | XCTAssert(true) 25 | return 26 | case false: 27 | XCTAssert(true) 28 | } 29 | } 30 | 31 | func testToggle() { 32 | let b1 = true 33 | XCTAssertTrue(b1.toggled == false) 34 | } 35 | 36 | // MARK:- BinaryConvertible 37 | 38 | func testBinaryString() { 39 | XCTAssertEqual(true.binaryString, "1") 40 | XCTAssertEqual(false.binaryString, "0") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/CharacterUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension CharacterUtilsTests { 5 | static var allTests = [ 6 | ("testIsEmojiCountryFlag", testIsEmojiCountryFlag) 7 | ] 8 | } 9 | 10 | final class CharacterUtilsTests: XCTestCase { 11 | 12 | func testIsEmojiCountryFlag() { 13 | XCTAssertTrue(Character("🇮🇹").isEmojiCountryFlag) 14 | XCTAssertTrue(Character("🇯🇵").isEmojiCountryFlag) 15 | XCTAssertTrue(Character("🇨🇦").isEmojiCountryFlag) 16 | XCTAssertTrue(Character("🇦🇶").isEmojiCountryFlag) 17 | XCTAssertFalse(Character("🏴").isEmojiCountryFlag) 18 | XCTAssertFalse(Character("🏁").isEmojiCountryFlag) 19 | XCTAssertFalse(Character("🏳️").isEmojiCountryFlag) 20 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 21 | XCTAssertFalse(Character("🏳️‍🌈").isEmojiCountryFlag) // it crashes on Linux 22 | #endif 23 | 24 | XCTAssertFalse(Character("a").isEmojiCountryFlag) 25 | XCTAssertFalse(Character(".").isEmojiCountryFlag) 26 | XCTAssertFalse(Character("🚩").isEmojiCountryFlag) 27 | XCTAssertFalse(Character("\\").isEmojiCountryFlag) 28 | XCTAssertFalse(Character("😇").isEmojiCountryFlag) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/CollectionUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension CollectionUtilsTests { 5 | static var allTests = [ 6 | ("testAtIndex", testAtIndex), 7 | ("testIndices", testIndices), 8 | ("testScan", testScan), 9 | ] 10 | } 11 | 12 | final class CollectionUtilsTests: XCTestCase { 13 | 14 | func testAtIndex() { 15 | let array: [Any] = [1, 2, "3", "4", 0] 16 | XCTAssertEqual(array.at(0)! as! Int, 1) 17 | XCTAssertEqual(array.at(1)! as! Int, 2) 18 | XCTAssertEqual(array.at(2)! as! String, "3") 19 | XCTAssertEqual(array.at(3)! as! String, "4") 20 | XCTAssertNil(array.at(5)) 21 | XCTAssertNil(array.at(10)) 22 | XCTAssertNil(array.at(100)) 23 | } 24 | 25 | func testIndices() { 26 | do { 27 | let phrase = "tin robots" 28 | XCTAssertEqual(phrase.indices(of: "t"), [phrase.startIndex, phrase.index(phrase.startIndex, offsetBy: 8)]) 29 | XCTAssertTrue(phrase.indices(of: "T").isEmpty) 30 | } 31 | 32 | do { 33 | let list = [1, 2, 1, 2, 3, 4, 5, 1] 34 | XCTAssertEqual(list.indices(of: 1), [0, 2, 7]) 35 | XCTAssertTrue(list.indices(of: 0).isEmpty) 36 | } 37 | } 38 | 39 | func testScan() { 40 | do { 41 | var phrase = "tin robots"[...] 42 | XCTAssertEqual(phrase.scan(count: 4), "tin ") 43 | XCTAssertFalse(phrase.scan(prefix: "s")) 44 | XCTAssertTrue(phrase.scan(prefix: "r")) 45 | XCTAssertTrue(phrase.scan(prefix: "ob")) 46 | phrase.scan { $0 == Character("b") } // "ots": no b here so phrase will be scanned completly until gets emptied 47 | 48 | phrase.scan { $0 == Character("o") } 49 | XCTAssertEqual(phrase, "") 50 | } 51 | 52 | do { 53 | var phrase = "tin robots"[...] 54 | XCTAssertTrue(phrase.scan(prefix: "tin")) 55 | XCTAssertEqual(phrase, " robots") 56 | } 57 | 58 | do { 59 | var phrase = "tin robots"[...] 60 | XCTAssertFalse(phrase.scan(prefix: "in")) 61 | XCTAssertEqual(phrase, "tin robots") 62 | } 63 | 64 | do { 65 | var phrase = "🇮🇹🇮🇹🇮🇹"[...] 66 | phrase.scan { $0 == Character("🇮🇹") } 67 | XCTAssertEqual(phrase, "🇮🇹🇮🇹🇮🇹") 68 | } 69 | 70 | do { 71 | var phrase = "abc"[...] 72 | phrase.scan { $0 == Character("a") } 73 | XCTAssertEqual(phrase, "abc") 74 | } 75 | 76 | do { 77 | var phrase = "tin robots"[...] 78 | var buffer = String() 79 | phrase.scan(upToCondition: { $0 == Character("o")}, into: &buffer) 80 | XCTAssertEqual(phrase, "obots") 81 | XCTAssertEqual(buffer, "tin r") 82 | } 83 | 84 | do { 85 | var phrase = "tin robots"[...] 86 | var buffer = String() 87 | phrase.scan(upToCollection: "obot", into: &buffer) 88 | XCTAssertEqual(phrase, "obots") 89 | XCTAssertEqual(buffer, "tin r") 90 | } 91 | 92 | do { 93 | var phrase = "tin robots"[...] 94 | var buffer = String() 95 | phrase.scan(upToCollection: "obox", into: &buffer) 96 | XCTAssertEqual(phrase, "") 97 | XCTAssertEqual(buffer, "tin robots") 98 | } 99 | 100 | do { 101 | var phrase = "tin robots"[...] 102 | phrase.scan(upToCollection: "obox") 103 | XCTAssertEqual(phrase, "") 104 | } 105 | 106 | do { 107 | var list = [1, 2, 3, 4, 5][...] 108 | var buffer = [Int]() 109 | list.scan(upToCollection: [3, 4], into: &buffer) 110 | 111 | XCTAssertEqual(list, [3, 4, 5]) 112 | XCTAssertEqual(buffer, [1, 2]) 113 | } 114 | 115 | do { 116 | var list = [1, 2, 3, 4, 5][...] 117 | var buffer = [Int]() 118 | list.scan(upToCollection: [3, 4], into: &buffer) 119 | 120 | XCTAssertEqual(list, [3, 4, 5]) 121 | XCTAssertEqual(buffer, [1, 2]) 122 | } 123 | 124 | do { 125 | var list = [1, 2, 3, 4, 5][...] 126 | var buffer = [Int]() 127 | list.scan(upToCollection: [100, 1003], into: &buffer) 128 | 129 | XCTAssertEqual(list, []) 130 | XCTAssertEqual(buffer, [1, 2, 3, 4, 5]) 131 | } 132 | 133 | do { 134 | var list = [Int]()[...] 135 | var buffer = [Int]() 136 | list.scan(upToCollection: [3, 4], into: &buffer) 137 | 138 | XCTAssertEqual(list, []) 139 | XCTAssertEqual(buffer, []) 140 | } 141 | 142 | } 143 | 144 | } 145 | 146 | 147 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/DictionaryUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension DictionaryUtilsTests { 5 | static var allTests = [ 6 | ("testHasKey", testHasKey), 7 | ("testRemoveAll", testRemoveAll) 8 | ] 9 | } 10 | 11 | final class DictionaryUtilsTests: XCTestCase { 12 | 13 | func testHasKey() { 14 | do { 15 | let dictionary: [String:Int] = [:] 16 | XCTAssertFalse(dictionary.hasKey("")) 17 | XCTAssertFalse(dictionary.hasKey("robot")) 18 | } 19 | do { 20 | let dictionary: [String:Int] = ["robot":1, "robot2":2] 21 | XCTAssertFalse(dictionary.hasKey("")) 22 | XCTAssertTrue(dictionary.hasKey("robot")) 23 | } 24 | } 25 | 26 | func testRemoveAll() { 27 | var dictionary = ["a": 0, "b": 1, "c": 2, "d": 3, "e": 4, "f": 5, "g": 6, "h": 7, "i": 8, "l": 9, "m": 10] 28 | 29 | do { 30 | let removed = dictionary.removeAll(forKeys: ["a", "b", "c"]) 31 | if removed != nil { 32 | XCTAssertTrue(removed!.count == 3) 33 | } else { 34 | XCTAssertNotNil(dictionary) 35 | } 36 | } 37 | 38 | do { 39 | let removed = dictionary.removeAll(forKeys: ["a", "b", "c", "d"]) 40 | if removed != nil { 41 | XCTAssertTrue(removed!.count == 1) 42 | } else { 43 | XCTAssertNotNil(dictionary) 44 | } 45 | } 46 | 47 | do { 48 | let removed = dictionary.removeAll(forKeys: ["k"]) 49 | XCTAssertNil(removed) 50 | } 51 | 52 | do { 53 | let removed = dictionary.removeAll(forKeys: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "l", "m"]) 54 | XCTAssertNotNil(removed) 55 | XCTAssertNil(removed!["a"]) 56 | XCTAssertNil(dictionary["a"]) 57 | XCTAssertNotNil(removed!["l"]) 58 | XCTAssertNil(dictionary["l"]) 59 | XCTAssert(dictionary.isEmpty) 60 | } 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/OperatorsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension OperatorsTests { 5 | static var allTests = [ ("testPercent", testPercent) ] 6 | } 7 | 8 | final class OperatorsTests: XCTestCase { 9 | 10 | func testPercent() { 11 | do { 12 | let value1 = 20.0% 13 | XCTAssertTrue(value1 == 0.2) 14 | 15 | let value2 = 11.1% 16 | XCTAssertTrue(value2 == 0.111) 17 | 18 | let value3 = Double(0)% 19 | XCTAssertTrue(value3 == 0) 20 | 21 | let value4 = Double(100)% 22 | XCTAssertTrue(value4 == 1) 23 | } 24 | 25 | do { 26 | let value1 = Float(20)% 27 | XCTAssertTrue(value1 == 0.2) 28 | 29 | let value2 = Float(11.1)% 30 | XCTAssertTrue(value2 == 0.111) 31 | 32 | let value3 = Float(0)% 33 | XCTAssertTrue(value3 == 0) 34 | 35 | let value4 = Float(100)% 36 | XCTAssertTrue(value4 == 1) 37 | } 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/OptionalUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension OptionalUtilsTests { 5 | static var allTests = [ 6 | ("testHasValue", testHasValue), 7 | ("testOr", testOr), 8 | ("testOrWithAutoclosure", testOrWithAutoclosure), 9 | ("testOrWithClosure", testOrWithClosure), 10 | ("testOrThrowException", testOrThrowException), 11 | ("testCollectionIsNilOrEmpty", testCollectionIsNilOrEmpty), 12 | ("testStringIsNilOrEmpty", testStringIsNilOrEmpty), 13 | ("isNilOrBlank", testIsNilOrBlank), 14 | ] 15 | } 16 | 17 | final class OptionalUtilsTests: XCTestCase { 18 | 19 | func testHasValue() { 20 | var value: Any? 21 | XCTAssert(!value.hasValue) 22 | value = "tinrobots" 23 | XCTAssert(value.hasValue) 24 | } 25 | 26 | func testOr() { 27 | let value1: String? = "hello" 28 | XCTAssertEqual(value1.or("world"), "hello") 29 | 30 | let value2: String? = nil 31 | XCTAssertEqual(value2.or("world"), "world") 32 | } 33 | 34 | func testOrWithAutoclosure() { 35 | func runMe() -> String { 36 | return "world" 37 | } 38 | let value1: String? = "hello" 39 | XCTAssertEqual(value1.or(else: runMe()), "hello") 40 | 41 | let value2: String? = nil 42 | XCTAssertEqual(value2.or(else: runMe()), "world") 43 | } 44 | 45 | func testOrWithClosure() { 46 | let value1: String? = "hello" 47 | XCTAssertEqual(value1.or(else: { return "world" }), "hello") 48 | 49 | let value2: String? = nil 50 | XCTAssertEqual(value2.or(else: { return "world" }), "world") 51 | } 52 | 53 | func testOrThrowException() { 54 | enum DemoError: Error { case test } 55 | 56 | let value1: String? = "hello" 57 | XCTAssertNoThrow(try value1.or(throw: DemoError.test)) 58 | 59 | let value2: String? = nil 60 | XCTAssertThrowsError(try value2.or(throw: DemoError.test)) 61 | } 62 | 63 | func testCollectionIsNilOrEmpty() { 64 | do { 65 | let collection: [Int]? = [1, 2, 3] 66 | XCTAssertFalse(collection.isNilOrEmpty) 67 | } 68 | do { 69 | let collection: [Int]? = nil 70 | XCTAssertTrue(collection.isNilOrEmpty) 71 | } 72 | do { 73 | let collection: [Int]? = [] 74 | XCTAssertTrue(collection.isNilOrEmpty) 75 | } 76 | } 77 | 78 | func testStringIsNilOrEmpty() { 79 | do { 80 | let text: String? = "mechanica" 81 | XCTAssertFalse(text.isNilOrEmpty) 82 | } 83 | do { 84 | let text: String? = " " 85 | XCTAssertFalse(text.isNilOrEmpty) 86 | } 87 | do { 88 | let text: String? = "" 89 | XCTAssertTrue(text.isNilOrEmpty) 90 | } 91 | do { 92 | let text: String? = nil 93 | XCTAssertTrue(text.isNilOrEmpty) 94 | } 95 | } 96 | 97 | func testIsNilOrBlank() { 98 | do { 99 | let text: String? = "mechanica" 100 | XCTAssertFalse(text.isNilOrBlank) 101 | } 102 | do { 103 | let text: String? = " " 104 | XCTAssertTrue(text.isNilOrBlank) 105 | } 106 | do { 107 | let text: String? = " " 108 | XCTAssertTrue(text.isNilOrBlank) 109 | } 110 | do { 111 | let text: String? = "" 112 | XCTAssertTrue(text.isNilOrBlank) 113 | } 114 | do { 115 | let text: String? = nil 116 | XCTAssertTrue(text.isNilOrBlank) 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/SignedIntegerUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension SignedIntegerTests { 5 | static var allTests = [ 6 | ("testPositiveOrNegativeValue", testPositiveOrNegativeValue), 7 | ("testBinaryString", testBinaryString) 8 | ] 9 | } 10 | 11 | final class SignedIntegerTests: XCTestCase { 12 | 13 | func testPositiveOrNegativeValue() { 14 | XCTAssertTrue(10.isPositive) 15 | XCTAssertFalse(10.isNegative) 16 | XCTAssertFalse((-10).isPositive) 17 | XCTAssertTrue((-10).isNegative) 18 | } 19 | 20 | // MARK: - BinaryConvertible 21 | 22 | /// http://www.binaryconvert.com/result_signed_int.html?decimal=045049049049 23 | func testBinaryString() { 24 | XCTAssertEqual(Int8(10).binaryString, "00001010") 25 | XCTAssertEqual(Int8(-1).binaryString, "11111111") 26 | XCTAssertEqual(Int8(0).binaryString, "00000000") 27 | XCTAssertEqual(Int8(127).binaryString, "01111111") 28 | XCTAssertEqual(Int8(-127).binaryString, "10000001") 29 | XCTAssertEqual(Int16(-1).binaryString, "1111111111111111") 30 | XCTAssertEqual(Int32(-111).binaryString,"11111111111111111111111110010001") 31 | 32 | #if (arch(x86_64) || arch(arm64)) 33 | // For 64-bit systems 34 | XCTAssertEqual((-3).binaryString, "1111111111111111111111111111111111111111111111111111111111111101") 35 | XCTAssertEqual(255.binaryString, "0000000000000000000000000000000000000000000000000000000011111111") 36 | XCTAssertEqual(1.binaryString, "0000000000000000000000000000000000000000000000000000000000000001") 37 | XCTAssertEqual(11.binaryString,"0000000000000000000000000000000000000000000000000000000000001011") 38 | XCTAssertEqual(111.binaryString,"0000000000000000000000000000000000000000000000000000000001101111") 39 | XCTAssertEqual(Int.max.binaryString,"0111111111111111111111111111111111111111111111111111111111111111") 40 | #elseif (arch(i386) || arch(arm)) 41 | // For 32-bit systems 42 | XCTAssertEqual((-3).binaryString, "11111111111111111111111111111101") 43 | XCTAssertEqual(255.binaryString, "00000000000000000000000011111111") 44 | XCTAssertEqual(1.binaryString, "00000000000000000000000000000001") 45 | XCTAssertEqual(11.binaryString,"00000000000000000000000000001011") 46 | XCTAssertEqual(111.binaryString,"00000000000000000000000001101111") 47 | XCTAssertEqual(Int.max.binaryString,"01111111111111111111111111111111") 48 | #endif 49 | } 50 | 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /Tests/StandardLibraryTests/UnsignedIntegerUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Mechanica 3 | 4 | extension UnsignedIntegerUtilsTests { 5 | static var allTests = [ ("testBinaryString", testBinaryString) ] 6 | } 7 | 8 | final class UnsignedIntegerUtilsTests: XCTestCase { 9 | 10 | // MARK: - BinaryConvertible 11 | 12 | /// http://www.binaryconvert.com/result_signed_int.html?decimal=045049049049 13 | func testBinaryString() { 14 | XCTAssertEqual(UInt8(10).binaryString,"00001010") 15 | XCTAssertEqual(UInt8(255).binaryString,"11111111") 16 | XCTAssertEqual(UInt16(10).binaryString,"0000000000001010") 17 | XCTAssertEqual(UInt32(255).binaryString,"00000000000000000000000011111111") 18 | XCTAssertEqual(UInt64(12345).binaryString,"0000000000000000000000000000000000000000000000000011000000111001") 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Tests/UIKitTests/UIButtonUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | 3 | import XCTest 4 | @testable import Mechanica 5 | 6 | final class UIButtonUtilsTests: XCTestCase { 7 | 8 | func testSetBackgroundColor() { 9 | // Given 10 | let size = CGSize(width: 200, height: 50) 11 | let rect = CGRect(origin: .zero, size: size) 12 | let button = UIButton(frame: rect) 13 | XCTAssertNil(button.backgroundImage(for: .normal)) 14 | XCTAssertNil(button.backgroundImage(for: .highlighted)) 15 | 16 | // When 17 | button.setBackgroundColor(.red, for: .normal) 18 | 19 | // Then 20 | XCTAssertNotNil(button.backgroundImage(for: .normal)) 21 | XCTAssertNotNil(button.backgroundImage(for: .highlighted)) 22 | } 23 | 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Tests/UIKitTests/UIDeviceUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | 3 | import XCTest 4 | @testable import Mechanica 5 | 6 | final class UIDeviceUtilsTests: XCTestCase { 7 | 8 | func testDeviceInterface() { 9 | #if os(iOS) 10 | XCTAssertTrue(UIDevice.current.hasPadInterface || UIDevice.current.hasPhoneInterface) 11 | XCTAssertFalse(UIDevice.current.hasTVInterface) 12 | #elseif os(tvOS) 13 | XCTAssertTrue(UIDevice.current.hasTVInterface) 14 | XCTAssertFalse(UIDevice.current.hasPadInterface || UIDevice.current.hasPhoneInterface) 15 | #endif 16 | 17 | XCTAssertFalse(UIDevice.current.hasCarPlayInterface) 18 | } 19 | 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Tests/UIKitTests/UIEdgeInsetsUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | 3 | import XCTest 4 | import UIKit 5 | @testable import Mechanica 6 | 7 | final class UIEdgeInsetsUtilsTests: XCTestCase { 8 | 9 | func testHorizontal() { 10 | let inset = UIEdgeInsets(top: 70.0, left: 15.0, bottom: 5.0, right: 10.0) 11 | XCTAssertEqual(inset.horizontal, 25.0) 12 | } 13 | 14 | func testVertical() { 15 | let inset = UIEdgeInsets(top: 20.0, left: 10.0, bottom: 5.0, right: 10.0) 16 | XCTAssertEqual(inset.vertical, 25.0) 17 | } 18 | 19 | func testInitInset() { 20 | let inset = UIEdgeInsets(inset: 15.5) 21 | XCTAssertEqual(inset.top, 15.5) 22 | XCTAssertEqual(inset.bottom, 15.5) 23 | XCTAssertEqual(inset.right, 15.5) 24 | XCTAssertEqual(inset.left, 15.5) 25 | } 26 | 27 | func testInitVerticalHorizontal() { 28 | let inset = UIEdgeInsets(horizontal: 20.0, vertical: 20.0) 29 | XCTAssertEqual(inset.top, 10.0) 30 | XCTAssertEqual(inset.bottom, 10.0) 31 | XCTAssertEqual(inset.right, 10.0) 32 | XCTAssertEqual(inset.left, 10.0) 33 | } 34 | 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Tests/UIKitTests/UIGestureRecognizerUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | 3 | import XCTest 4 | import UIKit 5 | @testable import Mechanica 6 | 7 | class UIGestureRecognizerUtilsTests: XCTestCase { 8 | 9 | func testCancel() { 10 | let recognizer = UITapGestureRecognizer() 11 | recognizer.cancel() 12 | XCTAssertTrue(recognizer.isEnabled) 13 | } 14 | 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Tests/UIKitTests/UILayoutPriorityUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | 3 | import XCTest 4 | @testable import Mechanica 5 | 6 | final class UILayoutPriorityUtilsTests: XCTestCase { 7 | 8 | func testIncreaseLayoutPriority() { 9 | XCTAssertTrue(UILayoutPriority.defaultLow + 1 == UILayoutPriority(UILayoutPriority.defaultLow.rawValue + 1)) 10 | } 11 | 12 | func testDecreaseLayoutPriority() { 13 | XCTAssertTrue(UILayoutPriority.defaultLow - 1 == UILayoutPriority(UILayoutPriority.defaultLow.rawValue - 1)) 14 | } 15 | 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /Tests/UIKitTests/UIStackViewUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && (os(iOS) || os(tvOS)) 2 | 3 | import XCTest 4 | import UIKit 5 | @testable import Mechanica 6 | 7 | final class UIStackViewUtilsTests: XCTestCase { 8 | 9 | func testAddBackgroundColor() { 10 | let stackView = UIStackView(frame: .zero) 11 | let color = UIColor.red 12 | let background = stackView.addBackgroundColor(color) 13 | 14 | XCTAssertEqual(background.backgroundColor, color) 15 | XCTAssertEqual(stackView.subviews, [background]) 16 | } 17 | 18 | func testAddForegroundColor() { 19 | let stackView = UIStackView(frame: .zero) 20 | let color = UIColor.red 21 | let view1 = UIView(frame: .zero) 22 | let view2 = UIView(frame: .zero) 23 | let view3 = UIView(frame: .zero) 24 | 25 | stackView.addSubview(view1) 26 | stackView.addSubview(view2) 27 | stackView.addSubview(view3) 28 | 29 | let foreground = stackView.addForegroundColor(color) 30 | 31 | XCTAssertEqual(foreground.backgroundColor, color) 32 | XCTAssertEqual(stackView.subviews.count, 4) 33 | XCTAssertEqual(stackView.subviews.last, foreground) 34 | } 35 | 36 | func testUnarrangedSubviews() { 37 | let stackView = UIStackView(frame: .zero) 38 | let color = UIColor.red 39 | let radius = CGFloat(11.0) 40 | let index = 0 41 | let view = stackView.addUnarrangedView(color: color, cornerRadius: radius, at: index) 42 | 43 | XCTAssertEqual(stackView.subviews.first, view) 44 | XCTAssertEqual(view.backgroundColor, color) 45 | XCTAssertEqual(view.layer.cornerRadius, radius) 46 | } 47 | 48 | } 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /Tests/UIKitTests/UIViewControllerUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | 3 | import XCTest 4 | @testable import Mechanica 5 | 6 | final class UIViewControllerUtilsTests: XCTestCase { 7 | 8 | func testIsVisible() { 9 | let viewController = UIViewController() 10 | XCTAssertFalse(viewController.isVisible) 11 | } 12 | 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /Tests/UIKitTests/UIViewUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) || os(watchOS) 2 | 3 | import XCTest 4 | @testable import Mechanica 5 | 6 | final class UIWiewUtilsTests: XCTestCase { 7 | 8 | func testCornerRadius() { 9 | let view = UIView() 10 | view.cornerRadius = 5.0 11 | 12 | XCTAssertEqual(view.layer.cornerRadius, view.cornerRadius) 13 | } 14 | 15 | func testBorderWidth() { 16 | let view = UIView() 17 | view.borderWidth = 5.0 18 | 19 | XCTAssertEqual(view.layer.borderWidth, view.borderWidth) 20 | } 21 | 22 | func testBorderColor() { 23 | let view = UIView() 24 | view.borderColor = .red 25 | 26 | XCTAssertNotNil(view.borderColor) 27 | 28 | let layerColor = view.layer.borderColor 29 | XCTAssertEqual(layerColor, UIColor.red.cgColor) 30 | 31 | view.borderColor = nil 32 | XCTAssertNil(view.borderColor) 33 | XCTAssertNil(view.layer.borderColor) 34 | } 35 | 36 | func testShadowRadius() { 37 | let view = UIView() 38 | 39 | view.shadowRadius = 15.0 40 | XCTAssertEqual(view.layer.shadowRadius, view.shadowRadius) 41 | } 42 | 43 | func testShadowOpacity() { 44 | let view = UIView() 45 | 46 | view.shadowOpacity = 0.7 47 | XCTAssertEqual(view.layer.shadowOpacity, view.shadowOpacity) 48 | } 49 | 50 | func testShadowOffset() { 51 | let view = UIView() 52 | 53 | view.shadowOffset = CGSize(width: 12.3, height: 45.6) 54 | XCTAssertEqual(view.layer.shadowOffset, view.shadowOffset) 55 | } 56 | 57 | func testShadowColor() { 58 | let view = UIView() 59 | view.shadowColor = .red 60 | XCTAssertNotNil(view.shadowColor) 61 | 62 | let layerShadowColor = view.layer.shadowColor 63 | XCTAssertEqual(layerShadowColor, UIColor.red.cgColor) 64 | 65 | view.shadowColor = nil 66 | XCTAssertNil(view.shadowColor) 67 | XCTAssertNil(view.layer.shadowColor) 68 | } 69 | 70 | #if os(iOS) || os(tvOS) 71 | 72 | func testUseAutolayout() { 73 | let view = UIView() 74 | let view2 = UIView() 75 | 76 | view.usesAutoLayout = true 77 | XCTAssertFalse(view.translatesAutoresizingMaskIntoConstraints) 78 | 79 | view.usesAutoLayout = false 80 | XCTAssertTrue(view.translatesAutoresizingMaskIntoConstraints) 81 | 82 | view2.translatesAutoresizingMaskIntoConstraints = false 83 | XCTAssertTrue(view2.usesAutoLayout) 84 | } 85 | 86 | func testScreenshot() { 87 | let view1 = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 50))) 88 | view1.backgroundColor = .red 89 | let view2 = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 50, height: 25))) 90 | view2.backgroundColor = .green 91 | view1.addSubview(view2) 92 | let screenshot = view1.screenshot() 93 | 94 | XCTAssertTrue(screenshot.size.width == 100) 95 | XCTAssertTrue(screenshot.size.height == 50) 96 | XCTAssertFalse(screenshot.size.width == 50) 97 | XCTAssertFalse(screenshot.size.height == 100) 98 | } 99 | 100 | #endif 101 | 102 | } 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /Tests/UIKitTests/UIWindowUtilsTests.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | 3 | import XCTest 4 | @testable import Mechanica 5 | 6 | final class UIWindowUtilsTests: XCTestCase { 7 | 8 | func testTopViewController() { 9 | // we can't test presented view controller 10 | 11 | let window = UIWindow() 12 | XCTAssertNil(window.topMostViewController) 13 | 14 | let navigationController = UINavigationController() 15 | window.rootViewController = navigationController 16 | XCTAssertTrue(window.topMostViewController == navigationController) 17 | 18 | let viewController = UIViewController() 19 | navigationController.show(viewController, sender: nil) 20 | XCTAssertTrue(window.topMostViewController == viewController) 21 | 22 | let tabBarController = UITabBarController() 23 | window.rootViewController = tabBarController 24 | XCTAssertTrue(window.topMostViewController == tabBarController) 25 | 26 | let viewControllerTab1 = UIViewController() 27 | viewControllerTab1.tabBarItem = UITabBarItem(tabBarSystemItem: .bookmarks, tag: 0) 28 | let viewControllerTab2 = UIViewController() 29 | viewControllerTab2.tabBarItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 1) 30 | tabBarController.viewControllers = [viewControllerTab1, viewControllerTab2] 31 | tabBarController.selectedIndex = 0 32 | 33 | XCTAssertTrue(window.topMostViewController == viewControllerTab1) 34 | XCTAssertTrue(window.topMostViewController != viewControllerTab2) 35 | XCTAssertTrue(tabBarController.selectedViewController == viewControllerTab1) 36 | 37 | } 38 | 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /Tests/UIKitTests/_Resources.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mechanica 3 | // 4 | // Copyright © 2016-2018 Tinrobots. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | import Foundation 25 | @testable import Mechanica 26 | 27 | enum Resource { 28 | 29 | case glasses 30 | case glassesWithoutAlpha 31 | case robot 32 | case apple 33 | case circle(name: String) 34 | case radius(name: String) 35 | case scaled(name: String) 36 | case scaledToFit(name: String) 37 | case scaledToFill(name: String) 38 | 39 | var url: URL { 40 | switch self { 41 | case .glasses: 42 | return Resource.folderURL.appendingPathComponent("glasses.png") 43 | case .glassesWithoutAlpha: 44 | return Resource.folderURL.appendingPathComponent("glasses_without_alpha.jpeg") 45 | case .robot: 46 | return Resource.folderURL.appendingPathComponent("robot.png") 47 | case .apple: 48 | return Resource.folderURL.appendingPathComponent("Images/Original/apple.jpg") 49 | case .circle(let name): 50 | return Resource.folderURL.appendingPathComponent("Images/Modified/Circle/\(name)") 51 | case .radius(let name): 52 | return Resource.folderURL.appendingPathComponent("Images/Modified/Radius/\(name)") 53 | case .scaled(let name): 54 | return Resource.folderURL.appendingPathComponent("Images/Modified/Scale/\(name)") 55 | case .scaledToFit(let name): 56 | return Resource.folderURL.appendingPathComponent("Images/Modified/AspectScaleToFit/\(name)") 57 | case .scaledToFill(let name): 58 | return Resource.folderURL.appendingPathComponent("Images/Modified/AspectScaleToFill/\(name)") 59 | } 60 | } 61 | 62 | var data : Data { 63 | return try! Data(contentsOf: url) 64 | } 65 | 66 | static var folderURL: URL { 67 | var resources = URL(fileURLWithPath: #file, isDirectory: false).deletingLastPathComponents(2) 68 | resources.appendPathComponent("Resources") 69 | return resources 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | ignore: 3 | - Examples/* 4 | - Tests/* 5 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Jazzy 5 | undocumented.json 6 | docset/.docset/Contents/Resources/Documents/undocumented.json 7 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 100% 23 | 24 | 25 | 100% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/Mechanica.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.mechanica 7 | CFBundleName 8 | Mechanica 9 | DocSetPlatformFamily 10 | mechanica 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/Mechanica.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 100% 23 | 24 | 25 | 100% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/Mechanica.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/docs/docsets/Mechanica.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/Mechanica.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/docs/docsets/Mechanica.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/Mechanica.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/docs/docsets/Mechanica.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/Mechanica.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /docs/docsets/Mechanica.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/docs/docsets/Mechanica.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/Mechanica.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/docs/docsets/Mechanica.tgz -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alemar11/Mechanica/076c436ae3ec24af1861b656c0f660d1ca65426f/docs/img/gh.png -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | --------------------------------------------------------------------------------