diff --git a/.gitignore b/.gitignore index c725787..fee5e3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,12 @@ -# Created by https://www.gitignore.io/api/macos +# ========== ### macOS ### + *.DS_Store .AppleDouble .LSOverride -# Icon must end with two \r +# Icon must end with two Icon # Thumbnails @@ -27,50 +28,16 @@ Network Trash Folder Temporary Items .apdisk -# End of https://www.gitignore.io/api/macos -# =========================== +### macOS ### +# ========== # # # -# Created by https://www.gitignore.io/api/cocoapods +# ========== +### Cocoa ### -### CocoaPods ### -## CocoaPods GitIgnore Template - -# CocoaPods - Only use to conserve bandwidth / Save time on Pushing -# - Also handy if you have a lage number of dependant pods -# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGONRE THE LOCK FILE -Pods/ - -# End of https://www.gitignore.io/api/cocoapods -# =========================== -# -# -# - -# Created by https://www.gitignore.io/api/carthage - -### Carthage ### -# Carthage - -# Add this line if you want to avoid checking in source code from Carthage dependencies. -Carthage/Checkouts - -Carthage/Build - -# End of https://www.gitignore.io/api/carthage -# =========================== -# -# -# - -# Created by https://www.gitignore.io/api/swift - -### Swift ### # Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ @@ -102,32 +69,34 @@ xcuserdata/ timeline.xctimeline playground.xcworkspace -# Swift Package Manager +### Cocoa ### +# ========== # -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. -# Packages/ -# Package.pins +# +# + +# ========== +### Swift Package Manager ### +# +Packages/ +Package.pins .build/ -# CocoaPods - Refactored to standalone file - -# Carthage - Refactored to standalone file - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - -fastlane/README.md -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output - -# End of https://www.gitignore.io/api/swift -# =========================== +### Swift Package Manager ### +# ========== # # # + +# ========== +### Custom repo-specific ### + +# we don't need to store project file, +# as we generate it on-demand +*.xcodeproj + +### Custom repo-specific ### +# ========== +# +# +# \ No newline at end of file diff --git a/.setup/.gitignore b/.setup/.gitignore new file mode 100644 index 0000000..02c0875 --- /dev/null +++ b/.setup/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj diff --git a/.setup/Package.resolved b/.setup/Package.resolved new file mode 100644 index 0000000..2695abe --- /dev/null +++ b/.setup/Package.resolved @@ -0,0 +1,52 @@ +{ + "object": { + "pins": [ + { + "package": "PathKit", + "repositoryURL": "https://github.com/kylef/PathKit", + "state": { + "branch": null, + "revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511", + "version": "1.0.0" + } + }, + { + "package": "XCERepoConfigurator", + "repositoryURL": "https://github.com/XCEssentials/RepoConfigurator", + "state": { + "branch": null, + "revision": "c8635818ec6cb096944d42db855ded210b1b1cbe", + "version": "2.7.3" + } + }, + { + "package": "Spectre", + "repositoryURL": "https://github.com/kylef/Spectre.git", + "state": { + "branch": null, + "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5", + "version": "0.9.0" + } + }, + { + "package": "SwiftHamcrest", + "repositoryURL": "https://github.com/nschum/SwiftHamcrest", + "state": { + "branch": null, + "revision": "034159ba9ac342372e3af119e7d80f38b5014782", + "version": "2.1.1" + } + }, + { + "package": "Version", + "repositoryURL": "https://github.com/mxcl/Version.git", + "state": { + "branch": null, + "revision": "682f0da36005924672506b96fac93269831004b7", + "version": "1.1.1" + } + } + ] + }, + "version": 1 +} diff --git a/.setup/Package.swift b/.setup/Package.swift new file mode 100644 index 0000000..ed9f34c --- /dev/null +++ b/.setup/Package.swift @@ -0,0 +1,20 @@ +// swift-tools-version:4.2 +// Managed by ice + +import PackageDescription + +let package = Package( + name: "ValidatableValueSetup", + dependencies: [ + .package(url: "https://github.com/kylef/PathKit", from: "1.0.0"), + .package(url: "https://github.com/XCEssentials/RepoConfigurator", from: "2.7.0") + ], + targets: [ + .target( + name: "ValidatableValueSetup", + dependencies: ["XCERepoConfigurator", "PathKit"], + path: ".", + sources: ["main.swift"] + ) + ] +) diff --git a/.setup/main.swift b/.setup/main.swift new file mode 100644 index 0000000..1f6e026 --- /dev/null +++ b/.setup/main.swift @@ -0,0 +1,212 @@ +import PathKit + +import XCERepoConfigurator + +// MARK: - PRE-script invocation output + +print("\n") +print("--- BEGIN of '\(Executable.name)' script ---") + +// MARK: - + +// MARK: Parameters + +Spec.BuildSettings.swiftVersion.value = "5.0" +let swiftLangVersions = "[.v5]" + +let localRepo = try Spec.LocalRepo.current() + +let remoteRepo = try Spec.RemoteRepo( + accountName: localRepo.context, + name: localRepo.name +) + +let travisCI = ( + address: "https://travis-ci.com/\(remoteRepo.accountName)/\(remoteRepo.name)", + branch: "master" +) + +let company = ( + prefix: "XCE", + name: remoteRepo.accountName +) + +let project = ( + name: remoteRepo.name, + summary: "Generic value wrapper with built-in validation", + copyrightYear: 2016 +) + +let productName = company.prefix + project.name + +let authors = [ + ("Maxim Khatskevich", "maxim@khatskevi.ch") +] + +typealias PerSubSpec = ( + core: T, + tests: T +) + +let subSpecs: PerSubSpec = ( + "Core", + "AllTests" +) + +let targetNames: PerSubSpec = ( + productName, + productName + subSpecs.tests +) + +let sourcesLocations: PerSubSpec = ( + Spec.Locations.sources + subSpecs.core, + Spec.Locations.tests + subSpecs.tests +) + +let dummyFiles = [ + sourcesLocations.core + "\(subSpecs.core).swift", + sourcesLocations.tests + "\(subSpecs.tests).swift" +] + +// MARK: Parameters - Summary + +localRepo.report() +remoteRepo.report() + +// MARK: - + +// MARK: Write - Dummy files + +try dummyFiles + .forEach{ + + try CustomTextFile + .init( + "//" + ) + .prepare( + at: $0 + ) + .writeToFileSystem( + ifFileExists: .skip + ) + } + +// MARK: Write - ReadMe + +try ReadMe() + .addGitHubLicenseBadge( + account: company.name, + repo: project.name + ) + .addGitHubTagBadge( + account: company.name, + repo: project.name + ) + .addSwiftPMCompatibleBadge() + .addCarthageCompatibleBadge() + .addWrittenInSwiftBadge( + version: Spec.BuildSettings.swiftVersion.value + ) + .addStaticShieldsBadge( + "platforms", + status: "macOS | iOS | tvOS | watchOS | Linux", + color: "blue", + title: "Supported platforms", + link: "Package.swift" + ) + .add(""" + [![Build Status](\(travisCI.address).svg?branch=\(travisCI.branch))](\(travisCI.address)) + """ + ) + .add(""" + + # \(project.name) + + \(project.summary) + + """ + ) + .prepare( + removeRepeatingEmptyLines: false + ) + .writeToFileSystem( + ifFileExists: .skip + ) + +// MARK: Write - License + +try License + .MIT( + copyrightYear: UInt(project.copyrightYear), + copyrightEntity: authors.map{ $0.0 }.joined(separator: ", ") + ) + .prepare() + .writeToFileSystem() + +// MARK: Write - GitHub - PagesConfig + +try GitHub + .PagesConfig() + .prepare() + .writeToFileSystem() + +// MARK: Write - Git - .gitignore + +try Git + .RepoIgnore() + .addMacOSSection() + .addCocoaSection() + .addSwiftPackageManagerSection(ignoreSources: true) + .add( + """ + # we don't need to store project file, + # as we generate it on-demand + *.\(Xcode.Project.extension) + """ + ) + .prepare() + .writeToFileSystem() + +// MARK: Write - Package.swift + +try CustomTextFile(""" + // swift-tools-version:\(Spec.BuildSettings.swiftVersion.value) + + import PackageDescription + + let package = Package( + name: "\(productName)", + products: [ + .library( + name: "\(productName)", + targets: [ + "\(targetNames.core)" + ] + ) + ], + targets: [ + .target( + name: "\(targetNames.core)", + path: "\(sourcesLocations.core)" + ), + .testTarget( + name: "\(targetNames.tests)", + dependencies: [ + "\(targetNames.core)" + ], + path: "\(sourcesLocations.tests)" + ), + ], + swiftLanguageVersions: \(swiftLangVersions) + ) + """ + ) + .prepare( + at: ["Package.swift"] + ) + .writeToFileSystem() + +// MARK: - POST-script invocation output + +print("--- END of '\(Executable.name)' script ---") diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..129c538 --- /dev/null +++ b/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version:5.0 + +import PackageDescription + +let package = Package( + name: "XCEValidatableValue", + products: [ + .library( + name: "XCEValidatableValue", + targets: [ + "XCEValidatableValue" + ] + ) + ], + targets: [ + .target( + name: "XCEValidatableValue", + path: "Sources/Core" + ), + .testTarget( + name: "XCEValidatableValueAllTests", + dependencies: [ + "XCEValidatableValue" + ], + path: "Tests/AllTests" + ), + ], + swiftLanguageVersions: [.v5] +) \ No newline at end of file diff --git a/README.md b/README.md index 67c4ea4..736d7c1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,16 @@ -[![license](https://img.shields.io/github/license/XCEssentials/ValidatableValue.svg)](https://opensource.org/licenses/MIT) -[![GitHub tag](https://img.shields.io/github/tag/XCEssentials/ValidatableValue.svg)](https://cocoapods.org/?q=XCERequirement) -[![CocoaPods](https://img.shields.io/cocoapods/v/XCEValidatableValue.svg)](https://cocoapods.org/?q=XCERequirement) -[![CocoaPods](https://img.shields.io/cocoapods/p/XCEValidatableValue.svg)](https://cocoapods.org/?q=XCEUniFlow) +[![GitHub License](https://img.shields.io/github/license/XCEssentials/ValidatableValue.svg?longCache=true)](LICENSE) +[![GitHub Tag](https://img.shields.io/github/tag/XCEssentials/ValidatableValue.svg?longCache=true)](https://github.com/XCEssentials/ValidatableValue/tags) +[![Swift Package Manager Compatible](https://img.shields.io/badge/SPM-compatible-brightgreen.svg?longCache=true)](Package.swift) +[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg?longCache=true)](https://github.com/Carthage/Carthage) +[![Written in Swift](https://img.shields.io/badge/Swift-5.0-orange.svg?longCache=true)](https://swift.org) +[![Supported platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20iOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux-blue.svg?longCache=true)](Package.swift) +[![Build Status](https://travis-ci.com/XCEssentials/ValidatableValue.svg?branch=master)](https://travis-ci.com/XCEssentials/ValidatableValue) -# Problem +# ValidatableValue + +Generic value wrapper with built-in validation + +## Problem Every app has [data model](https://en.wikipedia.org/wiki/Data_model). A data model is usually implemented in the form of a custom composite [data type](https://en.wikipedia.org/wiki/Data_type) that stores one or more [properties](https://en.wikipedia.org/wiki/Property_(programming)). Ideally, the type of each property (no matter if it's atomic or composite) defines desired set of all possible values for this property. Plus, there are might be special rules that may define whatever any given value is acceptable for the property or not. @@ -11,22 +18,22 @@ Swift has no built-in mechanizm of how to narrow-down set of allowed values with For example, to limit a vlaue to just integer numbers in the range from 1 to 100 and avoid all odd numbers within this range we usually use just Integer, and then somehow later implement the needed checks before actually put a value in this property. That leads to distribution of single portion of business logic (requirements for this particualr property) across at least two (sometimes more) places in the codebase. -# Wishlist +## Wishlist 1. concise inline definition of value validation logic; 2. safe value validation before actually writing it into property; 3. combination of several requirements together to create composite requirement that defines custom allowed values set; 4. eliminate any side effects by making validation logic to be written as pure function. -# How to install +## How to install The recommended way is to install using [CocoaPods](https://cocoapods.org/?q=XCEValidatableValue). -# How it works +## How it works The `ValidatableValue` data type represents a value that can be validated against custom rules/requirements. It also relies on `Value` generic type that represents base data type for potential value (one of the standard system data types or any custom one). The validation logic must be supplied as input parameter to the constructor in form of set of [Requirement](https://github.com/XCEssentials/Requirement) instances (might be empty in an edge case when any value of the base type is fine). There are several ways of creating a validatable value, see below. -# How to use +## How to use Assume we need to define a basic data model for representing user in our app. @@ -131,4 +138,4 @@ let newVal: Any = //... try u.firstName.setValue(newVal) ``` -See full example in [unit tests](https://github.com/XCEssentials/ValidatableValue/tree/master/Tst). \ No newline at end of file +See full example in [unit tests](https://github.com/XCEssentials/ValidatableValue/tree/master/Tst). diff --git a/Sources/Core/Core.swift b/Sources/Core/Core.swift new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/Sources/Core/Core.swift @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/Tests/AllTests/AllTests.swift b/Tests/AllTests/AllTests.swift new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/Tests/AllTests/AllTests.swift @@ -0,0 +1 @@ +// \ No newline at end of file