From e59e463d929f224ff71dfa3419b1023815361af5 Mon Sep 17 00:00:00 2001 From: Jaesung Lee Date: Sun, 28 Jul 2024 18:36:08 +0900 Subject: [PATCH] [ADD] Added matching keyword to improve chords searching [MOD] Updated Swift tools version to 5.9 --- Package.swift | 15 ++++--- README.md | 8 ++++ Sources/SwiftyChords/Array+Chords.swift | 37 +++++++++++++++ .../ArrayExtensionTests.swift | 27 +++++++++++ Tests/SwiftyChordsTests/Chord.Tests.swift | 45 +++++++++++++++++++ 5 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 Tests/SwiftyChordsTests/ArrayExtensionTests.swift create mode 100644 Tests/SwiftyChordsTests/Chord.Tests.swift diff --git a/Package.swift b/Package.swift index 1c1ad98..386d2ed 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,4 @@ -// swift-tools-version:5.4 -// The swift-tools-version declares the minimum version of Swift required to build this package. +// swift-tools-version:5.9 import PackageDescription @@ -7,17 +6,21 @@ let package = Package( name: "SwiftyChords", platforms: [.iOS(.v13), .macOS(.v11)], products: [ - // Products define the executables and libraries produced by a package, and make them visible to other packages. .library( name: "SwiftyChords", - targets: ["SwiftyChords"]), + targets: ["SwiftyChords"] + ), ], - dependencies: [], targets: [ .target( name: "SwiftyChords", - dependencies: [], resources: [.process("Resources")] ), + .testTarget( + name: "SwiftyChordsTests", + dependencies: [ + "SwiftyChords" + ] + ) ] ) diff --git a/README.md b/README.md index 4b5d21f..685c666 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,14 @@ Returns all suspended chords in the database along with their variants such as s Chords.guitar.matching(group: .suspended) ``` +### Filter by String Keyword +Returns all Dsus4 chords. This works the same as filtering by key and suffix. + +```swift +Chords.guitar.matching(keyword: "Dsus4") // same as Chords.guitar.matching(key: .d).matching(suffix: .sus4) +``` + + ## Display Swifty Chords suports a number of alternative texts you can use in your UI including an accessibility text-to-speech friendly variant. Display texts from both Key and Suffix properties can be combined to complete the chord name. diff --git a/Sources/SwiftyChords/Array+Chords.swift b/Sources/SwiftyChords/Array+Chords.swift index 23ebf9e..9faa10a 100644 --- a/Sources/SwiftyChords/Array+Chords.swift +++ b/Sources/SwiftyChords/Array+Chords.swift @@ -29,4 +29,41 @@ public extension Array where Element == ChordPosition { return self.filter { $0.suffix.group == group } } + func matching(keyword: String) -> [ChordPosition] { + // check keyword invalidation... + let trimmedKeyword = keyword.trimmingCharacters(in: .whitespaces) + if trimmedKeyword.isEmpty { return [] } + + // If the keyword has 1 or 0 character, returns the matching `ChordPosition`s immediately + if trimmedKeyword.count < 2 { + guard let key = Chords.Key(rawValue: trimmedKeyword) else { return [] } + return self.matching(key: key) + } + + // Get second character + let sharpOrFlat = trimmedKeyword[trimmedKeyword.index(trimmedKeyword.startIndex, offsetBy: 1)] + let hasSharpOrFlat = (sharpOrFlat == "b") || (sharpOrFlat == "#") // C#, Bb, Eb, D#, ... + + // Get string for key + let keyString = String(trimmedKeyword.prefix(hasSharpOrFlat ? 2 : 1)) + + // If the string for key is invalid, returns immediately + guard let key = Chords.Key(rawValue: keyString) else { return [] } + + // Get string for suffix + var suffixString = String(trimmedKeyword.dropFirst(hasSharpOrFlat ? 2 : 1)) + + // If the string for suffix is empty, assigns "major" instead. + // If the string for suffix is "m", assigns "minor" instead. + if suffixString.isEmpty { + suffixString = "major" + } else if suffixString == "m" { + suffixString = "minor" + } + + guard let suffix = Chords.Suffix(rawValue: suffixString) else { return [] } + + // Returns the matching chord positions + return self.matching(key: key).matching(suffix: suffix) + } } diff --git a/Tests/SwiftyChordsTests/ArrayExtensionTests.swift b/Tests/SwiftyChordsTests/ArrayExtensionTests.swift new file mode 100644 index 0000000..a09918a --- /dev/null +++ b/Tests/SwiftyChordsTests/ArrayExtensionTests.swift @@ -0,0 +1,27 @@ +// +// ArrayExtensionTests.swift +// +// +// Created on 7/28/24. +// + +#if canImport(Testing) +import Testing +@testable import SwiftyChords + +@Suite +struct ArrayExtensionTests { + @Test( + "Tests 'matching(keyword:)'", + arguments: Chord.allCases + ) + func matchingKeyword(chord: Chord) throws { + let chords = Chords.guitar + let positions = chords.matching(keyword: chord.keyword) + + #expect(positions.filter { $0.key.rawValue != chord.key.rawValue }.isEmpty) + #expect(positions.filter { $0.suffix.rawValue != chord.suffix.rawValue }.isEmpty) + } +} +#endif + diff --git a/Tests/SwiftyChordsTests/Chord.Tests.swift b/Tests/SwiftyChordsTests/Chord.Tests.swift new file mode 100644 index 0000000..ab35d59 --- /dev/null +++ b/Tests/SwiftyChordsTests/Chord.Tests.swift @@ -0,0 +1,45 @@ +// +// Chord.Tests.swift +// +// +// Created on 7/28/24. +// + +enum Chord: CaseIterable { + case cSharpMajor, aMinor, cBasedG, dSus4, g7 + + var keyword: String { + switch self { + case .cSharpMajor: + "C#" + case .aMinor: + "Am" + case .cBasedG: + "C/G" + case .dSus4: + "Dsus4" + case .g7: + "G7" + } + } + var key: Chords.Key { + switch self { + case .cSharpMajor: Chords.Key.cSharp + case .aMinor: Chords.Key.a + case .cBasedG: Chords.Key.c + case .dSus4: Chords.Key.d + case .g7: Chords.Key.g + } + } + + var suffix: Chords.Suffix { + switch self { + case .cSharpMajor: Chords.Suffix.major + case .aMinor: Chords.Suffix.minor + case .cBasedG: Chords.Suffix.slashG + case .dSus4: Chords.Suffix.susFour + case .g7: Chords.Suffix.seven + } + } +} +