├── .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 |
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 |
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 |
--------------------------------------------------------------------------------