From f69910b2fa03d2d5a37809acccc3d46d1e44d3e9 Mon Sep 17 00:00:00 2001 From: david-swift Date: Fri, 4 Oct 2024 00:35:31 +0200 Subject: [PATCH] Initial commit --- .github/ISSUE_TEMPLATE/bug_report.yml | 40 +++++ .github/ISSUE_TEMPLATE/feature_request.yml | 36 ++++ .github/PULL_REQUEST_TEMPLATE.md | 11 ++ .github/workflows/docs.yml | 45 +++++ .github/workflows/swiftlint.yml | 30 ++++ .gitignore | 14 ++ .swiftlint.yml | 163 +++++++++++++++++++ LICENSE.md | 21 +++ Package.swift | 41 +++++ README.md | 28 ++++ Sources/State.swift | 93 +++++++++++ Sources/meta-sqlite.docc/MetaSQLite.md | 4 + Sources/meta-sqlite.docc/theme-settings.json | 45 +++++ Tests/Test.swift | 23 +++ 14 files changed, 594 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/swiftlint.yml create mode 100644 .gitignore create mode 100644 .swiftlint.yml create mode 100644 LICENSE.md create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/State.swift create mode 100644 Sources/meta-sqlite.docc/MetaSQLite.md create mode 100644 Sources/meta-sqlite.docc/theme-settings.json create mode 100644 Tests/Test.swift diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ee1a591 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,40 @@ +name: Bug report +description: Something is not working as expected. +title: Description of the bug +labels: bug + +body: + - type: textarea + attributes: + label: Describe the bug + description: >- + A clear and concise description of what the bug is. + validations: + required: true + + - type: textarea + attributes: + label: To Reproduce + description: >- + Steps to reproduce the behavior. + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + + - type: textarea + attributes: + label: Expected behavior + description: >- + A clear and concise description of what you expected to happen. + validations: + required: true + + - type: textarea + attributes: + label: Additional context + description: >- + Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..c244dbb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,36 @@ +name: Feature request +description: Suggest an idea for this project +title: Description of the feature request +labels: enhancement + +body: + - type: input + attributes: + label: Is your feature request related to a problem? Please describe. + placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: false + + - type: textarea + attributes: + label: Describe the solution you'd like + placeholder: >- + A clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + attributes: + label: Describe alternatives you've considered + placeholder: >- + A clear and concise description of any alternative solutions or features you've considered. + validations: + required: true + + - type: textarea + attributes: + label: Additional context + placeholder: >- + Add any other context or screenshots about the feature request here. + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..cce03ae --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## Steps +- [ ] Build the project on your machine. If it does not compile, fix the errors. +- [ ] Describe the purpose and approach of your pull request below. +- [ ] Submit the pull request. Thank you very much for your contribution! + +## Purpose +_Describe the problem or feature._ +_If there is a related issue, add the link._ + +## Approach +_Describe how this pull request solves the problem or adds the feature._ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..6491463 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,45 @@ +name: Deploy Docs + +on: + push: + branches: ["main"] + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + Deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - name: Build Docs + run: | + xcrun xcodebuild docbuild \ + -scheme Meta \ + -destination 'generic/platform=macOS' \ + -derivedDataPath "$PWD/.derivedData" \ + -skipPackagePluginValidation + xcrun docc process-archive transform-for-static-hosting \ + "$PWD/.derivedData/Build/Products/Debug/Meta.doccarchive" \ + --output-path "docs" \ + --hosting-base-path "Meta" + - name: Modify Docs + run: | + echo "" > docs/index.html; + sed -i '' 's/,2px/,10px/g' docs/css/index.*.css + - name: Upload Artifact + uses: actions/upload-pages-artifact@v3 + with: + path: 'docs' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml new file mode 100644 index 0000000..5348bdb --- /dev/null +++ b/.github/workflows/swiftlint.yml @@ -0,0 +1,30 @@ +name: SwiftLint + +on: + push: + paths: + - '.github/workflows/swiftlint.yml' + - '.swiftlint.yml' + - '**/*.swift' + pull_request: + paths: + - '.github/workflows/swiftlint.yml' + - '.swiftlint.yml' + - '**/*.swift' + workflow_dispatch: + paths: + - '.github/workflows/swiftlint.yml' + - '.swiftlint.yml' + - '**/*.swift' + +jobs: + SwiftLint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: SwiftLint + uses: norio-nomura/action-swiftlint@3.2.1 + with: + args: --strict + env: + WORKING_DIRECTORY: Source diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1c5612 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +/Package.resolved +.Ulysses-Group.plist +/.docc-build +/io.github.AparokshaUI.Generation.json +/.vscode \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..8de0e3f --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,163 @@ +# Opt-In Rules +opt_in_rules: + - anonymous_argument_in_multiline_closure + - array_init + - attributes + - closure_body_length + - closure_end_indentation + - closure_spacing + - collection_alignment + - comma_inheritance + - conditional_returns_on_newline + - contains_over_filter_count + - contains_over_filter_is_empty + - contains_over_first_not_nil + - contains_over_range_nil_comparison + - convenience_type + - discouraged_none_name + - discouraged_object_literal + - discouraged_optional_boolean + - discouraged_optional_collection + - empty_collection_literal + - empty_count + - empty_string + - enum_case_associated_values_count + - explicit_init + - fallthrough + - file_header + - file_name + - file_name_no_space + - first_where + - flatmap_over_map_reduce + - force_unwrapping + - function_default_parameter_at_end + - identical_operands + - implicit_return + - implicitly_unwrapped_optional + - joined_default_parameter + - last_where + - legacy_multiple + - let_var_whitespace + - literal_expression_end_indentation + - local_doc_comment + - lower_acl_than_parent + - missing_docs + - modifier_order + - multiline_arguments + - multiline_arguments_brackets + - multiline_function_chains + - multiline_literal_brackets + - multiline_parameters + - multiline_parameters_brackets + - no_extension_access_modifier + - no_grouping_extension + - no_magic_numbers + - number_separator + - operator_usage_whitespace + - optional_enum_case_matching + - prefer_self_in_static_references + - prefer_self_type_over_type_of_self + - prefer_zero_over_explicit_init + - prohibited_interface_builder + - redundant_nil_coalescing + - redundant_type_annotation + - return_value_from_void_function + - shorthand_optional_binding + - sorted_first_last + - sorted_imports + - static_operator + - strict_fileprivate + - switch_case_on_newline + - toggle_bool + - trailing_closure + - type_contents_order + - unneeded_parentheses_in_closure_argument + - yoda_condition + +# Disabled Rules +disabled_rules: + - block_based_kvo + - class_delegate_protocol + - dynamic_inline + - is_disjoint + - no_fallthrough_only + - notification_center_detachment + - ns_number_init_as_function_reference + - nsobject_prefer_isequal + - private_over_fileprivate + - redundant_objc_attribute + - self_in_property_initialization + - todo + - unavailable_condition + - valid_ibinspectable + - xctfail_message + +# Custom Rules +custom_rules: + github_issue: + name: 'GitHub Issue' + regex: '//.(TODO|FIXME):.(?!.*(https://github\.com/AparokshaUI/meta-sqlite/issues/\d))' + message: 'The related GitHub issue must be included in a TODO or FIXME.' + severity: warning + + fatal_error: + name: 'Fatal Error' + regex: 'fatalError.*\(.*\)' + message: 'Fatal error should not be used.' + severity: error + + enum_case_parameter: + name: 'Enum Case Parameter' + regex: 'case [a-zA-Z0-9]*\([a-zA-Z0-9\.<>?,\n\t =]+\)' + message: 'The associated values of an enum case should have parameters.' + severity: warning + + tab: + name: 'Whitespaces Instead of Tab' + regex: '\t' + message: 'Spaces should be used instead of tabs.' + severity: warning + + # Thanks to the creator of the SwiftLint rule + # "empty_first_line" + # https://github.com/coteditor/CotEditor/blob/main/.swiftlint.yml + # in the GitHub repository + # "CotEditor" + # https://github.com/coteditor/CotEditor + empty_first_line: + name: 'Empty First Line' + regex: '(^[ a-zA-Z ]*(?:protocol|extension|class|struct) (?!(?:var|let))[ a-zA-Z:]*\{\n *\S+)' + message: 'There should be an empty line after a declaration' + severity: error + +# Analyzer Rules +analyzer_rules: + - unused_declaration + - unused_import + +# Options +file_header: + required_pattern: '(// swift-tools-version: .+)?//\n// .*.swift\n// meta-sqlite\n//\n// Created by .* on .*\.(\n// Edited by (.*,)+\.)*\n(\n// Thanks to .* for the .*:\n// ".*"\n// https://.* \(\d\d.\d\d.\d\d\))*//\n' +missing_docs: + warning: [internal, private] + error: [open, public] + excludes_inherited_types: false +type_contents_order: + order: + - case + - type_alias + - associated_type + - type_property + - instance_property + - ib_inspectable + - ib_outlet + - subscript + - initializer + - deinitializer + - subtype + - type_method + - view_life_cycle_method + - ib_action + - other_method +excluded: + - .build/ \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..626e124 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 david-swift + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..f3e24ba --- /dev/null +++ b/Package.swift @@ -0,0 +1,41 @@ +// swift-tools-version: 6.0 +// +// Package.swift +// meta-sqlite +// +// Created by david-swift on 04.10.24. +// + +import PackageDescription + +/// The meta-sqlite package is part of the Aparoksha project. +let package = Package( + name: "meta-sqlite", + platforms: [ + .macOS(.v10_15), + .iOS(.v13) + ], + products: [ + .library( + name: "MetaSQLite", + targets: ["MetaSQLite"] + ) + ], + dependencies: [ + .package(url: "https://github.com/AparokshaUI/Meta", branch: "main"), + .package(url: "https://github.com/stephencelis/SQLite.swift", from: "0.15.3") + ], + targets: [ + .target( + name: "MetaSQLite", + dependencies: ["Meta", .product(name: "SQLite", package: "SQLite.swift")], + path: "Sources" + ), + .executableTarget( + name: "Tests", + dependencies: ["MetaSQLite"], + path: "Tests" + ) + ], + swiftLanguageModes: [.v5] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..305e506 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +

+

SQLite for Meta

+

+ +

+ + Documentation + + ยท + + GitHub + +

+ +_SQLite for Meta_ is a simple extension for the [Meta](https://aparokshaui.github.io/Meta) framework remembering state data between app launches. + +## Thanks + +### Dependencies + +- [Meta](https://github.com/AparokshaUI/Meta) licensed under the [MIT License](https://github.com/AparokshaUI/Meta/blob/main/LICENSE.md) +- [SQLite.swift](https://github.com/stephencelis/SQLite.swift) licensed under the [MIT License](https://github.com/stephencelis/SQLite.swift/blob/master/LICENSE.txt) + +### Other Thanks + +- [DocC](https://github.com/apple/swift-docc) used for the documentation +- [SwiftLint](https://github.com/realm/SwiftLint) for checking whether code style conventions are violated +- The programming language [Swift](https://github.com/swiftlang/swift) diff --git a/Sources/State.swift b/Sources/State.swift new file mode 100644 index 0000000..d3490ea --- /dev/null +++ b/Sources/State.swift @@ -0,0 +1,93 @@ +// +// State.swift +// meta-sqlite +// +// Created by david-swift on 04.10.24. +// + +import Foundation +import Meta +import SQLite + +extension State where Value: Codable { + + /// Initialize a property being remembered between launches of the app. + /// - Parameters: + /// - wrappedValue: The wrapped value, used as an initial value if no stored value is found. + /// - stateID: The identifier for the stored value. + /// - forceUpdates: Whether to force update all available views when the property gets modified. + /// + /// Define a custom path for storing the data with ``DatabaseInformation/setPath(_:)``. + public init(wrappedValue: @autoclosure @escaping () -> Value, _ stateID: String, forceUpdates: Bool = false) { + self.init( + wrappedValue: { + let query = DatabaseInformation.table + .filter(DatabaseInformation.id == stateID) + .limit(1) + var data: Data? + guard let rows = try? DatabaseInformation.connection?.prepare(query) else { + return wrappedValue() + } + for row in rows { + data = row[DatabaseInformation.data] + } + guard let data else { + return wrappedValue() + } + let value = try? JSONDecoder().decode(Value.self, from: data) + return value ?? wrappedValue() + }, + writeValue: { value in + if let data = try? JSONEncoder().encode(value) { + _ = try? DatabaseInformation.connection?.run( + DatabaseInformation.table.insert( + or: .replace, + DatabaseInformation.id <- stateID, + DatabaseInformation.data <- data + ) + ) + } + }, + forceUpdates: forceUpdates + ) + } + +} + +/// Information about the database. +public enum DatabaseInformation { + + /// The path. + static var path = "./database.sqlite" + /// The table. + static let table = Table("data") + /// The ID field. + static let id = SQLite.Expression("id") + /// The data field. + static let data = SQLite.Expression("data") + /// The SQLite connection. + private static var privateConnection: Connection? + + /// The SQLite connection. + static var connection: Connection? { + if let privateConnection { + return privateConnection + } + let connection = try? Connection(path) + privateConnection = connection + _ = try? connection?.run(table.create { table in + table.column(id, primaryKey: true) + table.column(data) + }) + return connection + } + + /// Set the path to the SQLite file. + /// - Parameter path: The path. + /// + /// Call this function before accessing any state values (when setting up the app storage). + public static func setPath(_ path: String) { + self.path = path + } + +} diff --git a/Sources/meta-sqlite.docc/MetaSQLite.md b/Sources/meta-sqlite.docc/MetaSQLite.md new file mode 100644 index 0000000..8fb7461 --- /dev/null +++ b/Sources/meta-sqlite.docc/MetaSQLite.md @@ -0,0 +1,4 @@ +# ``MetaSQLite`` + +_SQLite for Meta_ is a simple extension for the [Meta](https://aparokshaui.github.io/Meta) framework remembering state data between app launches. + diff --git a/Sources/meta-sqlite.docc/theme-settings.json b/Sources/meta-sqlite.docc/theme-settings.json new file mode 100644 index 0000000..8d4aafc --- /dev/null +++ b/Sources/meta-sqlite.docc/theme-settings.json @@ -0,0 +1,45 @@ +{ + "theme": { + "border-radius": "10px", + "button": { + "border-radius": "20px" + }, + "color": { + "button-background": "#ea3358", + "button-background-active": "#ea3358", + "button-background-hover": "#fc557a", + "button-text": "#ffffff", + "header": "#7f313b", + "documentation-intro-accent": "var(--color-header)", + "link": "#ea3358", + "nav-link-color": "#ea3358", + "nav-dark-link-color": "#ea3358", + "tutorials-overview-link": "#fb4469", + "step-background": { + "light": "#fffaff", + "dark": "#302c2d" + }, + "step-focused": "#ea3358", + "tabnav-item-border-color": "#ea3358", + "tutorial-background": { + "light": "", + "dark": "#1d1d1f" + }, + "tutorials-overview-background": "linear-gradient(180deg, rgba(43,20,23,1) 0%, rgba(41, 3, 8, 0.808) 60%, rgba(0,0,0,1) 100%)", + "fill-light-blue-secondary": "#ea3358", + "fill-blue": "#ea3358", + "figure-blue": "#ea3358", + "standard-blue-documentation-intro-fill": "#ea3358", + "figure-blue": "#ea3358", + "tutorial-hero-background": "#100a0b", + "navigator-item-hover": { + "light": "#ea335815", + "dark": "#7f313b" + } + }, + "additionalProperties": "#ea3358", + "tutorial-step": { + "border-radius": "15px" + } + } +} diff --git a/Tests/Test.swift b/Tests/Test.swift new file mode 100644 index 0000000..868dd88 --- /dev/null +++ b/Tests/Test.swift @@ -0,0 +1,23 @@ +// +// Test.swift +// meta-sqlite +// +// Created by david-swift on 04.10.24. +// + +import Meta +import MetaSQLite + +/// The test function. +func main() { + @State("test") + var test = 0 + @State("other") + var other = 3 + print(test) + print(other) + test = 1 + other = 0 +} + +main()