diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 693af62..60f6e77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,10 +5,18 @@ on: pull_request: jobs: macos: - runs-on: macos-latest + strategy: + fail-fast: false + matrix: + macos: + - 11 + - 14 + xcode: + - latest-stable + runs-on: macos-${{ matrix.macos }} steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 - name: Build and Test run: | xcodebuild -scheme "LRUCache" -sdk macosx clean build test -enableCodeCoverage YES -derivedDataPath Build/ @@ -18,13 +26,25 @@ jobs: pathCoverage=Build/Build/ProfileData/${directory}/Coverage.profdata cd ../../../../ xcrun llvm-cov export -format="lcov" -instr-profile $pathCoverage Build/Build/Products/Debug/LRUCache.framework/LRUCache > info.lcov - bash <(curl https://codecov.io/bash) -t 5bfd6ece-264f-4cef-8dbf-4c5cfa0f87aa - env: - DEVELOPER_DIR: /Applications/Xcode_11.7.app/Contents/Developer + - name: Codecov + uses: codecov/codecov-action@v2 + with: + # the token is optional for a public repo, but including it anyway + token: 5bfd6ece-264f-4cef-8dbf-4c5cfa0f87aa + env_vars: MD_APPLE_SDK_ROOT,RUNNER_OS,RUNNER_ARCH + linux: + strategy: + fail-fast: false + matrix: + swiftver: + - swift:5.2 + - swiftlang/swift:nightly-main + swiftos: + - focal runs-on: ubuntu-latest container: - image: swift:5.2 + image: ${{ format('{0}-{1}', matrix.swiftver, matrix.swiftos) }} options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --security-opt apparmor=unconfined steps: - name: Checkout diff --git a/CHANGELOG.md b/CHANGELOG.md index 2be4e34..aaa50c0 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [1.0.5](https://github.com/nicklockwood/LRUCache/releases/tag/1.0.5) (2024-01-20) + +- Added `allKeys` computed property + ## [1.0.4](https://github.com/nicklockwood/LRUCache/releases/tag/1.0.4) (2022-09-16) - Added `allValues` computed property diff --git a/LRUCache.xcodeproj/project.pbxproj b/LRUCache.xcodeproj/project.pbxproj index d64fafa..25f95d5 100644 --- a/LRUCache.xcodeproj/project.pbxproj +++ b/LRUCache.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 0162A09923795EB30078AE84 /* LRUCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 016FAB2921BFE78100AF60DC /* LRUCache.framework */; }; + 01E6777E2B5BDBE1005594D6 /* MetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E6777D2B5BDBE1005594D6 /* MetadataTests.swift */; }; 01FFF38226BCDCCC004D99ED /* LRUPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FFF38126BCDCCC004D99ED /* LRUPerformanceTests.swift */; }; 0A240137256A64FB00C1535C /* LRUCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A240136256A64FB00C1535C /* LRUCacheTests.swift */; }; 0A24013F256A671600C1535C /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A24013E256A671600C1535C /* LRUCache.swift */; }; @@ -28,6 +29,7 @@ 016FAB2D21BFE78100AF60DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 016FAB3221BFE78100AF60DC /* LRUCacheTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LRUCacheTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 016FAB3921BFE78100AF60DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 01E6777D2B5BDBE1005594D6 /* MetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataTests.swift; sourceTree = ""; }; 01FFF38126BCDCCC004D99ED /* LRUPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRUPerformanceTests.swift; sourceTree = ""; }; 0A240136256A64FB00C1535C /* LRUCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRUCacheTests.swift; sourceTree = ""; }; 0A24013E256A671600C1535C /* LRUCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = ""; }; @@ -94,6 +96,7 @@ children = ( 0A240136256A64FB00C1535C /* LRUCacheTests.swift */, 01FFF38126BCDCCC004D99ED /* LRUPerformanceTests.swift */, + 01E6777D2B5BDBE1005594D6 /* MetadataTests.swift */, 016FAB3921BFE78100AF60DC /* Info.plist */, ); path = Tests; @@ -262,6 +265,7 @@ files = ( 01FFF38226BCDCCC004D99ED /* LRUPerformanceTests.swift in Sources */, 0A240137256A64FB00C1535C /* LRUCacheTests.swift in Sources */, + 01E6777E2B5BDBE1005594D6 /* MetadataTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -331,7 +335,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -339,6 +343,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; + TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -393,13 +398,14 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 4.2; + TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -426,16 +432,18 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.5; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=75"; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.LRUCache; PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; PRODUCT_NAME = LRUCache; SDKROOT = ""; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator watchsimulator watchos"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6,7"; }; name = Debug; }; @@ -460,15 +468,17 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.5; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=75"; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.LRUCache; PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; PRODUCT_NAME = LRUCache; SDKROOT = ""; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator watchsimulator watchos"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,6,7"; }; name = Release; }; diff --git a/README.md b/README.md index 3b93813..dbb8066 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Platforms](https://img.shields.io/badge/platforms-iOS%20|%20Mac%20|%20tvOS%20|%20watchOS%20|%20Linux-lightgray.svg)]() [![Swift 5.1](https://img.shields.io/badge/swift-5.1-red.svg?style=flat)](https://developer.apple.com/swift) [![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT) -[![Twitter](https://img.shields.io/badge/twitter-@nicklockwood-blue.svg)](http://twitter.com/nicklockwood) +[![Mastodon](https://img.shields.io/badge/mastodon-@nicklockwood@mastodon.social-636dff.svg)](https://mastodon.social/@nicklockwood) - [Introduction](#introduction) - [Installation](#installation) @@ -27,7 +27,7 @@ LRUCache is packaged as a dynamic framework that you can import into your Xcode To install using Swift Package Manage, add this to the `dependencies:` section in your Package.swift file: ```swift -.package(url: "https://github.com/nicklockwood/LRUCache.git", .upToNextMinor(from: "1.0.0")), +.package(url: "https://github.com/nicklockwood/LRUCache.git", .upToNextMinor(from: "1.0.5")), ``` @@ -51,9 +51,10 @@ To fetch a cached value, use: let value = cache.value(forKey: "foo") // Returns nil if value not found ``` -To fetch *all* the values stored in the cache, use: +To fetch *all* the key or values stored in the cache, use: ```swift +let keys = cache.allKeys // Ordered from oldest to newest let values = cache.allValues // Ordered from oldest to newest ``` diff --git a/Sources/LRUCache.swift b/Sources/LRUCache.swift index 1ed9a19..04f5ee9 100644 --- a/Sources/LRUCache.swift +++ b/Sources/LRUCache.swift @@ -13,22 +13,22 @@ // https://github.com/nicklockwood/LRUCache // // 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 +// 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 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. +// 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. // import Foundation @@ -69,7 +69,8 @@ public final class LRUCache { didSet { clean() } } - /// Initialize the cache with the specified `totalCostLimit` and `countLimit` + /// Initialize the cache with the specified `totalCostLimit` and + /// `countLimit` public init( totalCostLimit: Int = .max, countLimit: Int = .max, diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift new file mode 100644 index 0000000..aa6a002 --- /dev/null +++ b/Tests/MetadataTests.swift @@ -0,0 +1,94 @@ +// +// MetadataTests.swift +// LRUCacheTests +// +// Created by Nick Lockwood on 04/07/2021. +// Copyright © 2019 Nick Lockwood. All rights reserved. +// + +@testable import LRUCache +import XCTest + +private let projectDirectory = URL(fileURLWithPath: #file) + .deletingLastPathComponent().deletingLastPathComponent() + +private let changelogURL = projectDirectory + .appendingPathComponent("CHANGELOG.md") + +private let projectURL = projectDirectory + .appendingPathComponent("LRUCache.xcodeproj") + .appendingPathComponent("project.pbxproj") + +private let projectVersion: String = { + let string = try! String(contentsOf: projectURL) + let start = string.range(of: "MARKETING_VERSION = ")!.upperBound + let end = string.range(of: ";", range: start ..< string.endIndex)! + .lowerBound + return String(string[start ..< end]) +}() + +private let changelogTitles: [Substring] = { + let changelog = try! String(contentsOf: changelogURL, encoding: .utf8) + var range = changelog.startIndex ..< changelog.endIndex + var matches = [Substring]() + while let match = changelog.range( + of: "## \\[[^]]+\\]\\([^)]+\\) \\([^)]+\\)", + options: .regularExpression, + range: range + ) { + matches.append(changelog[match]) + range = match.upperBound ..< changelog.endIndex + } + return matches +}() + +class MetadataTests: XCTestCase { + // MARK: releases + + func testProjectVersionMatchesChangelog() throws { + let changelog = try String(contentsOf: changelogURL, encoding: .utf8) + let range = try XCTUnwrap(changelog.range(of: "releases/tag/")) + XCTAssertTrue( + changelog[range.upperBound...].hasPrefix(projectVersion), + "Marketing version \(projectVersion) does not match most recent tag in CHANGELOG.md" + ) + } + + func testLatestVersionInChangelog() throws { + let changelog = try String(contentsOf: changelogURL, encoding: .utf8) + XCTAssertTrue( + changelog.contains("[\(projectVersion)]"), + "CHANGELOG.md does not mention latest release" + ) + XCTAssertTrue( + changelog + .contains( + "(https://github.com/nicklockwood/LRUCache/releases/tag/\(projectVersion))" + ), + "CHANGELOG.md does not include correct link for latest release" + ) + } + + func testChangelogDatesAreAscending() throws { + var lastDate: Date? + let dateParser = DateFormatter() + dateParser.timeZone = TimeZone(identifier: "UTC") + dateParser.locale = Locale(identifier: "en_GB") + dateParser.dateFormat = " (yyyy-MM-dd)" + for title in changelogTitles { + let dateRange = try XCTUnwrap(title.range( + of: " \\([^)]+\\)$", + options: .regularExpression + )) + let dateString = String(title[dateRange]) + let date = try XCTUnwrap(dateParser.date(from: dateString)) + if let lastDate = lastDate, date > lastDate { + XCTFail( + "\(title) has newer date than subsequent version (\(date) vs \(lastDate))" + ) + return + } + lastDate = date + } + } +}