From 55ba94df72507c6ef90005ea9db753d36317e56b Mon Sep 17 00:00:00 2001 From: leogdion Date: Wed, 28 Aug 2024 13:02:28 -0400 Subject: [PATCH] v1.0.0 (#1) --- .github/workflows/SublimationService.yml | 138 ++++++ .github/workflows/codeql.yml | 78 ++++ .gitignore | 144 ++++++ .periphery.yml | 3 + .spi.yml | 5 + .swift-format | 70 +++ LICENSE | 21 + Mintfile | 2 + Package.resolved | 51 +++ Package.swift | 62 +++ README.md | 95 ++++ Scripts/gh-md-toc | 421 ++++++++++++++++++ Scripts/header.sh | 91 ++++ Scripts/lint.sh | 50 +++ .../Documentation.docc/Documentation.md | 27 ++ .../Resources/SublimationService.png | Bin 0 -> 23942 bytes .../Resources/SublimationService.svg | 36 ++ .../Resources/SublimationService@0.5x.png | Bin 0 -> 9130 bytes Sources/SublimationService/Service.swift | 46 ++ .../ServiceTests.swift | 35 ++ codecov.yml | 2 + project.yml | 13 + 22 files changed, 1390 insertions(+) create mode 100644 .github/workflows/SublimationService.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .gitignore create mode 100644 .periphery.yml create mode 100644 .spi.yml create mode 100644 .swift-format create mode 100644 LICENSE create mode 100644 Mintfile create mode 100644 Package.resolved create mode 100644 Package.swift create mode 100644 README.md create mode 100755 Scripts/gh-md-toc create mode 100755 Scripts/header.sh create mode 100755 Scripts/lint.sh create mode 100644 Sources/SublimationService/Documentation.docc/Documentation.md create mode 100644 Sources/SublimationService/Documentation.docc/Resources/SublimationService.png create mode 100644 Sources/SublimationService/Documentation.docc/Resources/SublimationService.svg create mode 100644 Sources/SublimationService/Documentation.docc/Resources/SublimationService@0.5x.png create mode 100644 Sources/SublimationService/Service.swift create mode 100644 Tests/SublimationServiceTests/ServiceTests.swift create mode 100644 codecov.yml create mode 100644 project.yml diff --git a/.github/workflows/SublimationService.yml b/.github/workflows/SublimationService.yml new file mode 100644 index 0000000..3f14877 --- /dev/null +++ b/.github/workflows/SublimationService.yml @@ -0,0 +1,138 @@ +name: SublimationService +on: + push: + branches-ignore: + - '*WIP' + +env: + PACKAGE_NAME: SublimationService +jobs: + build-ubuntu: + name: Build on Ubuntu + env: + SWIFT_VER: 6.0 + if: "!contains(github.event.head_commit.message, 'ci skip')" + runs-on: ubuntu-latest + container: + image: swiftlang/swift:nightly-6.0-jammy + steps: + - uses: actions/checkout@v4 + - name: Cache swift package modules + id: cache-spm-linux + uses: actions/cache@v4 + env: + cache-name: cache-spm + with: + path: .build + key: ${{ runner.os }}-${{ env.cache-name }}-${{ matrix.swift-version }}-${{ hashFiles('Package.resolved') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}-${{ matrix.swift-version }}- + ${{ runner.os }}-${{ env.cache-name }}- + - name: Test + run: swift test --enable-code-coverage + - uses: sersoft-gmbh/swift-coverage-action@v4 + id: coverage-files + with: + fail-on-empty-output: true + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + flags: swift-${{ matrix.swift-version }},ubuntu + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }} + build-macos: + name: Build on macOS + runs-on: ${{ matrix.os }} + if: "!contains(github.event.head_commit.message, 'ci skip')" + strategy: + matrix: + include: + - xcode: "/Applications/Xcode_16.1.app" + os: macos-14 + iOSVersion: "18.1" + watchOSVersion: "11.0" + watchName: "Apple Watch Series 9 (41mm)" + iPhoneName: "iPhone 15" + steps: + - uses: actions/checkout@v4 + - name: Cache swift package modules + id: cache-spm-macos + uses: actions/cache@v4 + env: + cache-name: cache-spm + with: + path: .build + key: ${{ matrix.os }}-build-${{ env.cache-name }}-${{ matrix.xcode }}-${{ hashFiles('Package.resolved') }} + restore-keys: | + ${{ matrix.os }}-build-${{ env.cache-name }}-${{ matrix.xcode }}- + - name: Cache mint + if: startsWith(matrix.xcode,'/Applications/Xcode_16.1') + id: cache-mint + uses: actions/cache@v4 + env: + cache-name: cache-mint + with: + path: .mint + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Mintfile') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Set Xcode Name + run: echo "XCODE_NAME=$(basename -- ${{ matrix.xcode }} | sed 's/\.[^.]*$//' | cut -d'_' -f2)" >> $GITHUB_ENV + - name: Setup Xcode + run: sudo xcode-select -s ${{ matrix.xcode }}/Contents/Developer || (sudo ls -1 /Applications | grep "Xcode") + - name: Install mint + if: startsWith(matrix.xcode,'/Applications/Xcode_16.1') + run: | + brew update + brew install mint + - name: Build + run: swift build + - name: Run Swift Package tests + run: swift test --enable-code-coverage + - uses: sersoft-gmbh/swift-coverage-action@v4 + id: coverage-files-spm + with: + fail-on-empty-output: true + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4 + with: + files: ${{ join(fromJSON(steps.coverage-files-spm.outputs.files), ',') }} + token: ${{ secrets.CODECOV_TOKEN }} + flags: macOS,${{ env.XCODE_NAME }},${{ matrix.runs-on }} + - name: Clean up spm build directory + run: rm -rf .build + - name: Lint + run: ./scripts/lint.sh + if: startsWith(matrix.xcode,'/Applications/Xcode_16.1') + - name: Run iOS target tests + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test + - uses: sersoft-gmbh/swift-coverage-action@v4 + id: coverage-files-iOS + with: + fail-on-empty-output: true + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ join(fromJSON(steps.coverage-files-iOS.outputs.files), ',') }} + flags: iOS,iOS${{ matrix.iOSVersion }},macOS,${{ env.XCODE_NAME }} + - name: Run watchOS target tests + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test + - uses: sersoft-gmbh/swift-coverage-action@v4 + id: coverage-files-watchOS + with: + fail-on-empty-output: true + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ join(fromJSON(steps.coverage-files-watchOS.outputs.files), ',') }} + flags: watchOS,watchOS${{ matrix.watchOSVersion }},macOS,${{ env.XCODE_NAME }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..92b69d6 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,78 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches-ignore: + - '*WIP' + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '20 11 * * 3' + +jobs: + analyze: + if: false + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-13') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'swift' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Xcode + run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - run: | + echo "Run, Build Application using script" + swift build + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d84c52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,144 @@ +# Created by https://www.toptal.com/developers/gitignore/api/swift,swiftpm,swiftpackagemanager,xcode,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=swift,swiftpm,swiftpackagemanager,xcode,macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +*.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +.swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# 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/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### SwiftPackageManager ### +Packages +xcuserdata +*.xcodeproj + + +### SwiftPM ### + + +### Xcode ### + +## Xcode 8 and earlier + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings +!Demo/SublimationDemoApp.xcodeproj +.mint +# End of https://www.toptal.com/developers/gitignore/api/swift,swiftpm,swiftpackagemanager,xcode,macos diff --git a/.periphery.yml b/.periphery.yml new file mode 100644 index 0000000..23cea39 --- /dev/null +++ b/.periphery.yml @@ -0,0 +1,3 @@ +retain_public: true +targets: +- SublimationService diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 0000000..357b568 --- /dev/null +++ b/.spi.yml @@ -0,0 +1,5 @@ +version: 1 +builder: + configs: + - documentation_targets: [SublimationService] + swift_version: 6.0 diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..4f562bf --- /dev/null +++ b/.swift-format @@ -0,0 +1,70 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "fileprivate" + }, + "indentation" : { + "spaces" : 2 + }, + "indentConditionalCompilationBlocks" : true, + "indentSwitchCaseLabels" : true, + "lineBreakAroundMultilineExpressionChainComponents" : true, + "lineBreakBeforeControlFlowKeywords" : true, + "lineBreakBeforeEachArgument" : true, + "lineBreakBeforeEachGenericRequirement" : true, + "lineLength" : 100, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : false, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : true, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : true, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : true, + "NeverUseForceTry" : true, + "NeverUseImplicitlyUnwrappedOptionals" : true, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, + "UseEarlyExits" : false, + "UseExplicitNilCheckInConditions" : true, + "UseLetInEveryBoundCaseVariable" : true, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : true, + "ValidateDocumentationComments" : true + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 2, + "version" : 1 +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..575c376 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 BrightDigit + +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/Mintfile b/Mintfile new file mode 100644 index 0000000..7060932 --- /dev/null +++ b/Mintfile @@ -0,0 +1,2 @@ +apple/swift-format@4b62459 +peripheryapp/periphery@2.20.0 \ No newline at end of file diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..5e6c1b9 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,51 @@ +{ + "originHash" : "f87e53f5832f74c7784ee3b439496fffd453b069ddc2c4cc01a035c205279945", + "pins" : [ + { + "identity" : "sublimation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/brightdigit/Sublimation", + "state" : { + "revision" : "99d0da9f907d27deb180582b274c5f83e6732921", + "version" : "2.0.0-beta.1" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "6ae9a051f76b81cc668305ceed5b0e0a7fd93d20", + "version" : "1.0.1" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", + "version" : "1.1.2" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" + } + }, + { + "identity" : "swift-service-lifecycle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-service-lifecycle.git", + "state" : { + "revision" : "24c800fb494fbee6e42bc156dc94232dc08971af", + "version" : "2.6.1" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..0a02192 --- /dev/null +++ b/Package.swift @@ -0,0 +1,62 @@ +// swift-tools-version: 6.0 + +import PackageDescription + +let swiftSettings: [SwiftSetting] = [ + SwiftSetting.enableExperimentalFeature("AccessLevelOnImport"), + SwiftSetting.enableExperimentalFeature("BitwiseCopyable"), + SwiftSetting.enableExperimentalFeature("GlobalActorIsolatedTypesUsability"), + SwiftSetting.enableExperimentalFeature("IsolatedAny"), + SwiftSetting.enableExperimentalFeature("MoveOnlyPartialConsumption"), + SwiftSetting.enableExperimentalFeature("NestedProtocols"), + SwiftSetting.enableExperimentalFeature("NoncopyableGenerics"), + SwiftSetting.enableExperimentalFeature("RegionBasedIsolation"), + SwiftSetting.enableExperimentalFeature("TransferringArgsAndResults"), + SwiftSetting.enableExperimentalFeature("VariadicGenerics"), + + SwiftSetting.enableUpcomingFeature("FullTypedThrows"), + SwiftSetting.enableUpcomingFeature("InternalImportsByDefault") + + // SwiftSetting.unsafeFlags([ + // "-Xfrontend", + // "-warn-long-function-bodies=100" + // ]), + // SwiftSetting.unsafeFlags([ + // "-Xfrontend", + // "-warn-long-expression-type-checking=100" + // ]) +] + +let package = Package( + name: "SublimationService", + platforms: [ + .macOS(.v14), + .iOS(.v17), + .watchOS(.v10), + .tvOS(.v17), + .visionOS(.v1), + .macCatalyst(.v17) + ], + products: [ + .library(name: "SublimationService", targets: ["SublimationService"]) + ], + dependencies: [ + .package(url: "https://github.com/brightdigit/Sublimation", from: "2.0.0-alpha.5"), + .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.6.0") + ], + targets: [ + .target( + name: "SublimationService", + dependencies: [ + .product(name: "Sublimation", package: "Sublimation"), + .product(name: "ServiceLifecycle", package: "swift-service-lifecycle") + ], + swiftSettings: swiftSettings + ), + .testTarget( + name: "SublimationServiceTests", + dependencies: ["SublimationService"] + ) + ] +) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..83c198d --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +

+ Sublimation +

+

SublimationService

+ +Using [Sublimation](https://github.com/brightdigit/Sublimation) as a [Lifecycle Service](https://swiftpackageindex.com/swift-server/swift-service-lifecycle/2.6.1/documentation/servicelifecycle) for applications such as [Hummingbird](https://github.com/hummingbird-project/hummingbird). + +[![](https://img.shields.io/badge/docc-read_documentation-blue)](https://swiftpackageindex.com/brightdigit/SublimationService/documentation) +[![SwiftPM](https://img.shields.io/badge/SPM-Linux%20%7C%20iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS-success?logo=swift)](https://swift.org) +[![Twitter](https://img.shields.io/badge/twitter-@brightdigit-blue.svg?style=flat)](http://twitter.com/brightdigit) +![GitHub](https://img.shields.io/github/license/brightdigit/SublimationService) +![GitHub issues](https://img.shields.io/github/issues/brightdigit/SublimationService) +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/brightdigit/SublimationService/SublimationService.yml?label=actions&logo=github&?branch=main) + +[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FSublimationService%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/brightdigit/SublimationService) +[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FSublimationService%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/brightdigit/SublimationService) + +[![Codecov](https://img.shields.io/codecov/c/github/brightdigit/SublimationService)](https://codecov.io/gh/brightdigit/SublimationService) +[![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/brightdigit/SublimationService)](https://www.codefactor.io/repository/github/brightdigit/SublimationService) +[![codebeat badge](https://codebeat.co/badges/88cc9ee4-5180-4ce5-93c6-a2e23dd532c3)](https://codebeat.co/projects/github-com-brightdigit-SublimationService-main) +[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/brightdigit/SublimationService)](https://codeclimate.com/github/brightdigit/SublimationService) +[![Code Climate technical debt](https://img.shields.io/codeclimate/tech-debt/brightdigit/SublimationService?label=debt)](https://codeclimate.com/github/brightdigit/SublimationService) +[![Code Climate issues](https://img.shields.io/codeclimate/issues/brightdigit/SublimationService)](https://codeclimate.com/github/brightdigit/SublimationService) + +# Table of Contents + +* [Requirements](#requirements) +* [Installation](#installation) +* [Usage](#usage) +* [Documentation](#documentation) +* [License](#license) + +# Requirements + +**Apple Platforms** + +- Xcode 16.0 or later +- Swift 6.0 or later +- iOS 17 / watchOS 10.0 / tvOS 17 / macOS 14 or later deployment targets + +**Linux** + +- Ubuntu 20.04 or later +- Swift 6.0 or later + +# Installation + +To integrate **SublimationService** into your app using SPM, specify it in your Package.swift file: + +```swift +let package = Package( + ... + dependencies: [ + .package(url: "https://github.com/brightdigit/SublimationService.git", from: "1.0.0") + ], + targets: [ + .target( + name: "YourServerApp", + dependencies: [ + .product(name: "SublimationService", package: "SublimationService"), ... + ]), + ... + ] +) +``` + +# Usage + +For instance if you are using this with [Hummingbird](https://github.com/hummingbird-project/hummingbird) and using [Bonjour](https://github.com/brightdigit/SublimationBonjour), you can just add it as a service: + +```swift +let sublimation = Sublimation( + bindingConfiguration: .init( + hosts: hosts, + configuration: configuration.hosting + ) +) + +var app = Application( + router: router, + server: .http1WebSocketUpgrade(webSocketRouter: wsRouter), + configuration: .init(address: .init(setup: configuration.hosting)) +) + +app.addServices(sublimation) +``` + +## Documentation + +To learn more, check out the full [documentation](https://swiftpackageindex.com/brightdigit/SublimationService/documentation). + +# License + +This code is distributed under the MIT license. See the [LICENSE](https://github.com/brightdigit/SublimationService/LICENSE) file for more info. + diff --git a/Scripts/gh-md-toc b/Scripts/gh-md-toc new file mode 100755 index 0000000..03b5ddd --- /dev/null +++ b/Scripts/gh-md-toc @@ -0,0 +1,421 @@ +#!/usr/bin/env bash + +# +# Steps: +# +# 1. Download corresponding html file for some README.md: +# curl -s $1 +# +# 2. Discard rows where no substring 'user-content-' (github's markup): +# awk '/user-content-/ { ... +# +# 3.1 Get last number in each row like ' ... sitemap.js.*<\/h/)+2, RLENGTH-5) +# +# 5. Find anchor and insert it inside "(...)": +# substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8) +# + +gh_toc_version="0.10.0" + +gh_user_agent="gh-md-toc v$gh_toc_version" + +# +# Download rendered into html README.md by its url. +# +# +gh_toc_load() { + local gh_url=$1 + + if type curl &>/dev/null; then + curl --user-agent "$gh_user_agent" -s "$gh_url" + elif type wget &>/dev/null; then + wget --user-agent="$gh_user_agent" -qO- "$gh_url" + else + echo "Please, install 'curl' or 'wget' and try again." + exit 1 + fi +} + +# +# Converts local md file into html by GitHub +# +# -> curl -X POST --data '{"text": "Hello world github/linguist#1 **cool**, and #1!"}' https://api.github.com/markdown +#

Hello world github/linguist#1 cool, and #1!

'" +gh_toc_md2html() { + local gh_file_md=$1 + local skip_header=$2 + + URL=https://api.github.com/markdown/raw + + if [ -n "$GH_TOC_TOKEN" ]; then + TOKEN=$GH_TOC_TOKEN + else + TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt" + if [ -f "$TOKEN_FILE" ]; then + TOKEN="$(cat "$TOKEN_FILE")" + fi + fi + if [ -n "${TOKEN}" ]; then + AUTHORIZATION="Authorization: token ${TOKEN}" + fi + + local gh_tmp_file_md=$gh_file_md + if [ "$skip_header" = "yes" ]; then + if grep -Fxq "" "$gh_src"; then + # cut everything before the toc + gh_tmp_file_md=$gh_file_md~~ + sed '1,//d' "$gh_file_md" > "$gh_tmp_file_md" + fi + fi + + # echo $URL 1>&2 + OUTPUT=$(curl -s \ + --user-agent "$gh_user_agent" \ + --data-binary @"$gh_tmp_file_md" \ + -H "Content-Type:text/plain" \ + -H "$AUTHORIZATION" \ + "$URL") + + rm -f "${gh_file_md}~~" + + if [ "$?" != "0" ]; then + echo "XXNetworkErrorXX" + fi + if [ "$(echo "${OUTPUT}" | awk '/API rate limit exceeded/')" != "" ]; then + echo "XXRateLimitXX" + else + echo "${OUTPUT}" + fi +} + + +# +# Is passed string url +# +gh_is_url() { + case $1 in + https* | http*) + echo "yes";; + *) + echo "no";; + esac +} + +# +# TOC generator +# +gh_toc(){ + local gh_src=$1 + local gh_src_copy=$1 + local gh_ttl_docs=$2 + local need_replace=$3 + local no_backup=$4 + local no_footer=$5 + local indent=$6 + local skip_header=$7 + + if [ "$gh_src" = "" ]; then + echo "Please, enter URL or local path for a README.md" + exit 1 + fi + + + # Show "TOC" string only if working with one document + if [ "$gh_ttl_docs" = "1" ]; then + + echo "Table of Contents" + echo "=================" + echo "" + gh_src_copy="" + + fi + + if [ "$(gh_is_url "$gh_src")" == "yes" ]; then + gh_toc_load "$gh_src" | gh_toc_grab "$gh_src_copy" "$indent" + if [ "${PIPESTATUS[0]}" != "0" ]; then + echo "Could not load remote document." + echo "Please check your url or network connectivity" + exit 1 + fi + if [ "$need_replace" = "yes" ]; then + echo + echo "!! '$gh_src' is not a local file" + echo "!! Can't insert the TOC into it." + echo + fi + else + local rawhtml + rawhtml=$(gh_toc_md2html "$gh_src" "$skip_header") + if [ "$rawhtml" == "XXNetworkErrorXX" ]; then + echo "Parsing local markdown file requires access to github API" + echo "Please make sure curl is installed and check your network connectivity" + exit 1 + fi + if [ "$rawhtml" == "XXRateLimitXX" ]; then + echo "Parsing local markdown file requires access to github API" + echo "Error: You exceeded the hourly limit. See: https://developer.github.com/v3/#rate-limiting" + TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt" + echo "or place GitHub auth token here: ${TOKEN_FILE}" + exit 1 + fi + local toc + toc=`echo "$rawhtml" | gh_toc_grab "$gh_src_copy" "$indent"` + echo "$toc" + if [ "$need_replace" = "yes" ]; then + if grep -Fxq "" "$gh_src" && grep -Fxq "" "$gh_src"; then + echo "Found markers" + else + echo "You don't have or in your file...exiting" + exit 1 + fi + local ts="<\!--ts-->" + local te="<\!--te-->" + local dt + dt=$(date +'%F_%H%M%S') + local ext=".orig.${dt}" + local toc_path="${gh_src}.toc.${dt}" + local toc_createdby="" + local toc_footer + toc_footer="" + # http://fahdshariff.blogspot.ru/2012/12/sed-mutli-line-replacement-between-two.html + # clear old TOC + sed -i"${ext}" "/${ts}/,/${te}/{//!d;}" "$gh_src" + # create toc file + echo "${toc}" > "${toc_path}" + if [ "${no_footer}" != "yes" ]; then + echo -e "\n${toc_createdby}\n${toc_footer}\n" >> "$toc_path" + fi + + # insert toc file + if ! sed --version > /dev/null 2>&1; then + sed -i "" "/${ts}/r ${toc_path}" "$gh_src" + else + sed -i "/${ts}/r ${toc_path}" "$gh_src" + fi + echo + if [ "${no_backup}" = "yes" ]; then + rm "$toc_path" "$gh_src$ext" + fi + echo "!! TOC was added into: '$gh_src'" + if [ -z "${no_backup}" ]; then + echo "!! Origin version of the file: '${gh_src}${ext}'" + echo "!! TOC added into a separate file: '${toc_path}'" + fi + echo + fi + fi +} + +# +# Grabber of the TOC from rendered html +# +# $1 - a source url of document. +# It's need if TOC is generated for multiple documents. +# $2 - number of spaces used to indent. +# +gh_toc_grab() { + + href_regex="/href=\"[^\"]+?\"/" + common_awk_script=' + modified_href = "" + split(href, chars, "") + for (i=1;i <= length(href); i++) { + c = chars[i] + res = "" + if (c == "+") { + res = " " + } else { + if (c == "%") { + res = "\\x" + } else { + res = c "" + } + } + modified_href = modified_href res + } + print sprintf("%*s", (level-1)*'"$2"', "") "* [" text "](" gh_url modified_href ")" + ' + if [ "`uname -s`" == "OS/390" ]; then + grepcmd="pcregrep -o" + echoargs="" + awkscript='{ + level = substr($0, 3, 1) + text = substr($0, match($0, /<\/span><\/a>[^<]*<\/h/)+11, RLENGTH-14) + href = substr($0, match($0, '$href_regex')+6, RLENGTH-7) + '"$common_awk_script"' + }' + else + grepcmd="grep -Eo" + echoargs="-e" + awkscript='{ + level = substr($0, 3, 1) + text = substr($0, match($0, /">.*<\/h/)+2, RLENGTH-5) + href = substr($0, match($0, '$href_regex')+6, RLENGTH-7) + '"$common_awk_script"' + }' + fi + + # if closed is on the new line, then move it on the prev line + # for example: + # was: The command foo1 + # + # became: The command foo1 + sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<\/h/<\/h/g' | + + # Sometimes a line can start with . Fix that. + sed -e ':a' -e 'N' -e '$!ba' -e 's/\n//g' | sed 's/<\/code>//g' | + + # remove g-emoji + sed 's/]*[^<]*<\/g-emoji> //g' | + + # now all rows are like: + #

title

.. + # format result line + # * $0 - whole string + # * last element of each row: "/dev/null; then + $tool --version | head -n 1 + else + echo "not installed" + fi + done +} + +show_help() { + local app_name + app_name=$(basename "$0") + echo "GitHub TOC generator ($app_name): $gh_toc_version" + echo "" + echo "Usage:" + echo " $app_name [options] src [src] Create TOC for a README file (url or local path)" + echo " $app_name - Create TOC for markdown from STDIN" + echo " $app_name --help Show help" + echo " $app_name --version Show version" + echo "" + echo "Options:" + echo " --indent Set indent size. Default: 3." + echo " --insert Insert new TOC into original file. For local files only. Default: false." + echo " See https://github.com/ekalinin/github-markdown-toc/issues/41 for details." + echo " --no-backup Remove backup file. Set --insert as well. Default: false." + echo " --hide-footer Do not write date & author of the last TOC update. Set --insert as well. Default: false." + echo " --skip-header Hide entry of the topmost headlines. Default: false." + echo " See https://github.com/ekalinin/github-markdown-toc/issues/125 for details." + echo "" +} + +# +# Options handlers +# +gh_toc_app() { + local need_replace="no" + local indent=3 + + if [ "$1" = '--help' ] || [ $# -eq 0 ] ; then + show_help + return + fi + + if [ "$1" = '--version' ]; then + show_version + return + fi + + if [ "$1" = '--indent' ]; then + indent="$2" + shift 2 + fi + + if [ "$1" = "-" ]; then + if [ -z "$TMPDIR" ]; then + TMPDIR="/tmp" + elif [ -n "$TMPDIR" ] && [ ! -d "$TMPDIR" ]; then + mkdir -p "$TMPDIR" + fi + local gh_tmp_md + if [ "`uname -s`" == "OS/390" ]; then + local timestamp + timestamp=$(date +%m%d%Y%H%M%S) + gh_tmp_md="$TMPDIR/tmp.$timestamp" + else + gh_tmp_md=$(mktemp "$TMPDIR/tmp.XXXXXX") + fi + while read -r input; do + echo "$input" >> "$gh_tmp_md" + done + gh_toc_md2html "$gh_tmp_md" | gh_toc_grab "" "$indent" + return + fi + + if [ "$1" = '--insert' ]; then + need_replace="yes" + shift + fi + + if [ "$1" = '--no-backup' ]; then + need_replace="yes" + no_backup="yes" + shift + fi + + if [ "$1" = '--hide-footer' ]; then + need_replace="yes" + no_footer="yes" + shift + fi + + if [ "$1" = '--skip-header' ]; then + skip_header="yes" + shift + fi + + + for md in "$@" + do + echo "" + gh_toc "$md" "$#" "$need_replace" "$no_backup" "$no_footer" "$indent" "$skip_header" + done + + echo "" + echo "" +} + +# +# Entry point +# +gh_toc_app "$@" \ No newline at end of file diff --git a/Scripts/header.sh b/Scripts/header.sh new file mode 100755 index 0000000..39eff6c --- /dev/null +++ b/Scripts/header.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# Function to print usage +usage() { + echo "Usage: $0 -d directory -c creator -o company -p package [-y year]" + echo " -d directory Directory to read from (including subdirectories)" + echo " -c creator Name of the creator" + echo " -o company Name of the company with the copyright" + echo " -p package Package or library name" + echo " -y year Copyright year (optional, defaults to current year)" + exit 1 +} + +# Get the current year if not provided +current_year=$(date +"%Y") + +# Default values +year="$current_year" + +# Parse arguments +while getopts ":d:c:o:p:y:" opt; do + case $opt in + d) directory="$OPTARG" ;; + c) creator="$OPTARG" ;; + o) company="$OPTARG" ;; + p) package="$OPTARG" ;; + y) year="$OPTARG" ;; + *) usage ;; + esac +done + +# Check for mandatory arguments +if [ -z "$directory" ] || [ -z "$creator" ] || [ -z "$company" ] || [ -z "$package" ]; then + usage +fi + +# Define the header template +header_template="// +// %s +// %s +// +// Created by %s. +// Copyright © %s %s. +// +// 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. +//" + +# Loop through each Swift file in the specified directory and subdirectories +find "$directory" -type f -name "*.swift" | while read -r file; do + # Create the header with the current filename + filename=$(basename "$file") + header=$(printf "$header_template" "$filename" "$package" "$creator" "$year" "$company") + + # Remove all consecutive lines at the beginning which start with "// ", contain only whitespace, or only "//" + awk ' + BEGIN { skip = 1 } + { + if (skip && ($0 ~ /^\/\/ / || $0 ~ /^\/\/$/ || $0 ~ /^$/)) { + next + } + skip = 0 + print + }' "$file" > temp_file + + # Add the header to the cleaned file + (echo "$header"; echo; cat temp_file) > "$file" + + # Remove the temporary file + rm temp_file +done + +echo "Headers added to all Swift files in the directory and subdirectories." \ No newline at end of file diff --git a/Scripts/lint.sh b/Scripts/lint.sh new file mode 100755 index 0000000..394f6d7 --- /dev/null +++ b/Scripts/lint.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +if [ "$ACTION" == "install" ]; then + if [ -n "$SRCROOT" ]; then + exit + fi +fi + +export MINT_PATH="$PWD/.mint" +MINT_ARGS="-n -m Mintfile --silent" +MINT_RUN="/opt/homebrew/bin/mint run $MINT_ARGS" + +if [ -z "$SRCROOT" ] || [ -n "$CHILD_PACKAGE" ]; then + SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + PACKAGE_DIR="${SCRIPT_DIR}/.." + PERIPHERY_OPTIONS="" +else + PACKAGE_DIR="${SRCROOT}" + PERIPHERY_OPTIONS="--skip-build" +fi + + +if [ "$LINT_MODE" == "NONE" ]; then + exit +elif [ "$LINT_MODE" == "STRICT" ]; then + SWIFTFORMAT_OPTIONS="--strict" +else + SWIFTFORMAT_OPTIONS="" +fi + +/opt/homebrew/bin/mint bootstrap + +echo "LINT Mode is $LINT_MODE" + +if [ "$LINT_MODE" == "INSTALL" ]; then + exit +fi + +if [ -z "$CI" ]; then + $MINT_RUN swift-format format --recursive --parallel --in-place $PACKAGE_DIR/Sources +else + set -e +fi + +$PACKAGE_DIR/scripts/header.sh -d $PACKAGE_DIR/Sources -c "Leo Dion" -o "BrightDigit" -p "SublimationVapor" +$MINT_RUN swift-format lint --recursive --parallel $SWIFTFORMAT_OPTIONS $PACKAGE_DIR/Sources + +pushd $PACKAGE_DIR +$MINT_RUN periphery scan $PERIPHERY_OPTIONS --disable-update-check +popd diff --git a/Sources/SublimationService/Documentation.docc/Documentation.md b/Sources/SublimationService/Documentation.docc/Documentation.md new file mode 100644 index 0000000..b6c2968 --- /dev/null +++ b/Sources/SublimationService/Documentation.docc/Documentation.md @@ -0,0 +1,27 @@ +# ``SublimationService`` + +Using [Sublimation](https://github.com/brightdigit/Sublimation) as a [Lifecycle Service](https://swiftpackageindex.com/swift-server/swift-service-lifecycle/2.6.1/documentation/servicelifecycle) for applications such as [Hummingbird](https://github.com/hummingbird-project/hummingbird). + +## Overview + +![SublimationService Logo](SublimationService.svg) + +For instance if you are using this with [Hummingbird](https://github.com/hummingbird-project/hummingbird) and using [Bonjour](https://github.com/brightdigit/SublimationBonjour), you can just add it as a service: + +```swift +let sublimation = Sublimation( + bindingConfiguration: .init( + hosts: hosts, + configuration: configuration.hosting + ) +) + +var app = Application( + router: router, + server: .http1WebSocketUpgrade(webSocketRouter: wsRouter), + configuration: .init(address: .init(setup: configuration.hosting)) +) + +app.addServices(sublimation) +``` + diff --git a/Sources/SublimationService/Documentation.docc/Resources/SublimationService.png b/Sources/SublimationService/Documentation.docc/Resources/SublimationService.png new file mode 100644 index 0000000000000000000000000000000000000000..0902350392fecd2c21b53109cc2456481ff461f6 GIT binary patch literal 23942 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/SublimationService/Documentation.docc/Resources/SublimationService@0.5x.png b/Sources/SublimationService/Documentation.docc/Resources/SublimationService@0.5x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9aeb95c30cf7dd5ffd383e92ac3347bdcd53f82 GIT binary patch literal 9130 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 literal 0 HcmV?d00001 diff --git a/Sources/SublimationService/Service.swift b/Sources/SublimationService/Service.swift new file mode 100644 index 0000000..20ab2bc --- /dev/null +++ b/Sources/SublimationService/Service.swift @@ -0,0 +1,46 @@ +// +// Service.swift +// SublimationVapor +// +// Created by Leo Dion. +// Copyright © 2024 BrightDigit. +// +// 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. +// + +public import ServiceLifecycle + +@_exported public import class Sublimation.Sublimation + +/// Implementation for `ServiceLifecycle`. +extension Sublimation: @retroactive Service { + /// Implementation for `ServiceLifecycle`. + /// This method is called when the ServiceGroup is starting all the services. + public func run() async throws { + try await withGracefulShutdownHandler { + try await self.sublimatory.run() + } onGracefulShutdown: { + self.sublimatory.shutdown() + } + + } +} diff --git a/Tests/SublimationServiceTests/ServiceTests.swift b/Tests/SublimationServiceTests/ServiceTests.swift new file mode 100644 index 0000000..fbe88b1 --- /dev/null +++ b/Tests/SublimationServiceTests/ServiceTests.swift @@ -0,0 +1,35 @@ +// +// ServiceTests.swift +// SublimationService +// +// Created by Leo Dion on 7/30/24. +// + +import XCTest + +final class ServiceTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..951b97b --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "Tests" diff --git a/project.yml b/project.yml new file mode 100644 index 0000000..fd62d23 --- /dev/null +++ b/project.yml @@ -0,0 +1,13 @@ +name: SublimationService +settings: + LINT_MODE: ${LINT_MODE} +packages: + SublimationService: + path: . +aggregateTargets: + Lint: + buildScripts: + - path: Scripts/lint.sh + name: Lint + basedOnDependencyAnalysis: false + schemes: {}