]> git.r.bdr.sh - rbdr/lyricli/commitdiff
Update code, add source management config
authorRuben Beltran del Rio <redacted>
Mon, 10 Apr 2023 11:02:51 +0000 (13:02 +0200)
committerRuben Beltran del Rio <redacted>
Mon, 10 Apr 2023 11:02:51 +0000 (13:02 +0200)
17 files changed:
Package.resolved
Package.swift
README.md
Sources/lyricli/configuration.swift
Sources/lyricli/errors/configuration_could_not_be_read.swift [new file with mode: 0644]
Sources/lyricli/errors/source_could_not_be_disabled.swift [new file with mode: 0644]
Sources/lyricli/errors/source_could_not_be_enabled.swift [new file with mode: 0644]
Sources/lyricli/errors/source_could_not_be_reset.swift [new file with mode: 0644]
Sources/lyricli/errors/source_not_available.swift [new file with mode: 0644]
Sources/lyricli/lyricli.swift
Sources/lyricli/lyricli_command.swift
Sources/lyricli/lyrics_engine.swift
Sources/lyricli/main.swift [deleted file]
Sources/lyricli/source_manager.swift
Sources/lyricli/sources/apple_music_source.swift [moved from Sources/lyricli/sources/itunes_source.swift with 72% similarity]
Sources/lyricli/sources/source_protocol.swift
Sources/lyricli/sources/spotify_source.swift

index a2a49b3863362a690685f37b929a5ce91750476c..4d93f8f012c6038db6437fe8e29040549475ec79 100644 (file)
@@ -1,34 +1,14 @@
 {
-  "object": {
-    "pins": [
-      {
-        "package": "Bariloche",
-        "repositoryURL": "https://github.com/Subito-it/Bariloche",
-        "state": {
-          "branch": null,
-          "revision": "507f4121d2a7479522908dabf83aed78a6e5e268",
-          "version": "1.0.4"
-        }
-      },
-      {
-        "package": "Rainbow",
-        "repositoryURL": "https://github.com/onevcat/Rainbow",
-        "state": {
-          "branch": null,
-          "revision": "9c52c1952e9b2305d4507cf473392ac2d7c9b155",
-          "version": "3.1.5"
-        }
-      },
-      {
-        "package": "HTMLEntities",
-        "repositoryURL": "https://github.com/IBM-Swift/swift-html-entities.git",
-        "state": {
-          "branch": null,
-          "revision": "dc15f4d8eba5be23280a561c698fc36ab4fb6c76",
-          "version": "3.0.12"
-        }
+  "pins" : [
+    {
+      "identity" : "swift-argument-parser",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-argument-parser",
+      "state" : {
+        "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
+        "version" : "1.2.2"
       }
-    ]
-  },
-  "version": 1
+    }
+  ],
+  "version" : 2
 }
index 9adbd39653703733005aea5951c513476950d730..2f49d107c935aeb4dbd79d94f578177367ab9962 100644 (file)
@@ -1,19 +1,16 @@
-// swift-tools-version:5.0
+// swift-tools-version:5.8
 
 import PackageDescription
 
 let package = Package(
     name: "lyricli",
     dependencies: [
-        /// ðŸ”¡ Tools for working with HTML entities
-        .package(url: "https://github.com/IBM-Swift/swift-html-entities.git", from: "3.0.11"),
-
         /// ðŸš© Command Line Arguments
-        .package(url: "https://github.com/Subito-it/Bariloche", from: "1.0.4")
+        .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2")
     ],
     targets: [
-        .target(
+        .executableTarget(
             name: "lyricli",
-            dependencies: ["HTMLEntities", "Bariloche"])
+            dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")])
     ]
 )
index 57dff9a286981385065aea2ad5f9a0529002e395..6d6046aee9c308e86f5e61f8ea96ee750aad353a 100644 (file)
--- a/README.md
+++ b/README.md
@@ -4,8 +4,7 @@ A command line tool to show the lyrics of your current song.
 
 ## Usage
 
-Lyricli can be invoked with the command `lrc`. It can be invoked without
-arguments, with an artist and song or with a special command:
+Lyricli can be invoked with the command `lrc`.
 
 ```
 $ lrc [-t]
@@ -26,20 +25,24 @@ song and artist names before the lyrics.
 
 ### Commands
 
-In order to configure
+In order to configure sources, lyricli provides a few commands:
 
-* `lrc -l` or `lrc --list` lists the available sources. Enabled
+* `lrc -l` or `lrc --list-sources` lists the available sources. Enabled
   sourcess will have a `*`
-* `lrc -e` or `lrc --enable <source>` enables a source
-* `lrc -d` or `lrc --disable <source>` disables a source
-* `lrc -r` or `lrc --reset <source>` resets the configuration for
-  a source and disables it.
+* `lrc -e` or `lrc --enable-source <source>` enables a source
+* `lrc -d` or `lrc --disable-source <source>` disables a source without
+  resetting its configuration.
+* `lrc -r` or `lrc --reset-source <source>` resets the configuration
+  for a source and disables it.
+
+And you can print the help or the version:
+
 * `lrc -v` or `lrc --version` prints the version
 * `lrc -h` or `lrc --help` display built-in help
 
 ## Building
 
-The build has only been tested on OSX using Swift 5.0.1. Building defaults
+The build has only been tested on OSX using Swift 5.8 Building defaults
 to the debug configuration.
 
 ```
index 1b0103493cf3e77cbdf9474511843843af154bba..b2defc13ee5b79801e0a8d6840c0e61610b297f9 100644 (file)
@@ -8,7 +8,7 @@ class Configuration {
     // Default options, will be automatically written to the global config if
     // not found.
     private var configuration: [String: Any] =  [
-        "enabled_sources": ["itunes", "spotify"]
+        "enabled_sources": ["apple_music", "spotify"]
     ]
 
     // The shared instance of the object
diff --git a/Sources/lyricli/errors/configuration_could_not_be_read.swift b/Sources/lyricli/errors/configuration_could_not_be_read.swift
new file mode 100644 (file)
index 0000000..fb2b61a
--- /dev/null
@@ -0,0 +1,3 @@
+struct ConfigurationCouldNotBeRead: Error {
+    var localizedDescription = "The configuration could not be read, check ~/.lyricli.conf"
+}
diff --git a/Sources/lyricli/errors/source_could_not_be_disabled.swift b/Sources/lyricli/errors/source_could_not_be_disabled.swift
new file mode 100644 (file)
index 0000000..b19f3c5
--- /dev/null
@@ -0,0 +1,6 @@
+// Future improvement: At the moment the sources don't need any special
+// configuration. Once we do have more operations it would make sense
+// to throw more descriptive errors.
+struct SourceCouldNotBeDisabled: Error {
+    var localizedDescription = "The selected source failed while disabling"
+}
diff --git a/Sources/lyricli/errors/source_could_not_be_enabled.swift b/Sources/lyricli/errors/source_could_not_be_enabled.swift
new file mode 100644 (file)
index 0000000..7797478
--- /dev/null
@@ -0,0 +1,6 @@
+// Future improvement: At the moment the sources don't need any special
+// configuration. Once we do have more operations it would make sense
+// to throw more descriptive errors.
+struct SourceCouldNotBeEnabled: Error {
+    var localizedDescription = "The selected source failed while enabling"
+}
diff --git a/Sources/lyricli/errors/source_could_not_be_reset.swift b/Sources/lyricli/errors/source_could_not_be_reset.swift
new file mode 100644 (file)
index 0000000..beecf54
--- /dev/null
@@ -0,0 +1,6 @@
+// Future improvement: At the moment the sources don't need any special
+// configuration. Once we do have more operations it would make sense
+// to throw more descriptive errors.
+struct SourceCouldNotBeReset: Error {
+    var localizedDescription = "The selected source failed while resetting"
+}
diff --git a/Sources/lyricli/errors/source_not_available.swift b/Sources/lyricli/errors/source_not_available.swift
new file mode 100644 (file)
index 0000000..e3d598d
--- /dev/null
@@ -0,0 +1,3 @@
+struct SourceNotAvailable: Error {
+    var localizedDescription = "The selected source wasn't available"
+}
index 043c0049de8b86e98000f749f07f5c9e54e88c74..ad4bb86c17d8c843b266e566a38b9c0ca5ad2a83 100644 (file)
@@ -25,11 +25,10 @@ class Lyricli {
     static func printLyrics(_ currentTrack: Track) {
         let engine = LyricsEngine(withTrack: currentTrack)
 
+        if showTitle {
+            printTitle(currentTrack)
+        }
         if let lyrics = engine.lyrics {
-            if showTitle {
-                printTitle(currentTrack)
-            }
-
             print(lyrics)
         } else {
             print("Lyrics not found :(")
@@ -38,23 +37,62 @@ class Lyricli {
 
     // Print the currently available sources
     static func printSources() {
-        print("Listing Sources: Not yet implemented")
+        let sourceManager = SourceManager()
+        for (sourceName, _) in sourceManager.availableSources {
+            if (Configuration.shared["enabled_sources"] as? [String] ?? []).contains(sourceName) {
+                print("\(sourceName) (enabled)")
+            } else {
+                print(sourceName)
+            }
+        }
     }
 
     // Runs the enable method of a source and writes the configuration to set it
     // as enabled
-    static func enableSource(_ sourceName: String) {
-        print("Enable source \(sourceName): Not yet implemented")
+    static func enableSource(_ sourceName: String) throws {
+        let sourceManager = SourceManager()
+        if let source = sourceManager.availableSources[sourceName] {
+            if let enabledSources = Configuration.shared["enabled_sources"] as? [String] {
+                if source.enable() == false {
+                    throw SourceCouldNotBeEnabled()
+                }
+                if !enabledSources.contains(sourceName) {
+                    Configuration.shared["enabled_sources"] = enabledSources + [sourceName]
+                }
+                return
+            }
+            throw ConfigurationCouldNotBeRead()
+        }
+        throw SourceNotAvailable()
     }
 
     // Remove a source from the enabled sources configuration
-    static func disableSource(_ sourceName: String) {
-        print("Disable source \(sourceName): Not yet implemented")
+    static func disableSource(_ sourceName: String) throws {
+        let sourceManager = SourceManager()
+        if let source = sourceManager.availableSources[sourceName] {
+            if let enabledSources = Configuration.shared["enabled_sources"] as? [String] {
+                if source.disable() == false {
+                    throw SourceCouldNotBeDisabled()
+                }
+                Configuration.shared["enabled_sources"] = enabledSources.filter { $0 != sourceName }
+                return
+            }
+            throw ConfigurationCouldNotBeRead()
+        }
+        throw SourceNotAvailable()
     }
 
     // Removes any configuration for a source, and disables it
-    static func resetSource(_ sourceName: String) {
-        print("Reset source \(sourceName): Not yet implemented")
+    static func resetSource(_ sourceName: String) throws {
+        let sourceManager = SourceManager()
+        if let source = sourceManager.availableSources[sourceName] {
+            if source.reset() == false {
+                throw SourceCouldNotBeReset()
+            }
+            try disableSource(sourceName)
+            return
+        }
+        throw SourceNotAvailable()
     }
 
     // Prints the track artist and name
index 88f9c7b01bc01f8e65d55d1ea599c07c7d548196..872ab6e10cbf2801be327bbd1bf10996cff2844a 100644 (file)
@@ -1,38 +1,95 @@
-import Bariloche
+import Darwin
+import ArgumentParser
 
-class LyricliCommand: Command {
-    let usage: String? = "Fetch the lyrics for current playing track or the one specified via arguments"
+@main
+struct LyricliCommand: ParsableCommand {
+
+    // Positional Arguments
+    @Argument var artist: String?
+    @Argument var trackName: String?
 
     // Flags
-    let version = Flag(short: "v", long: "version", help: "Prints the version.")
-    let showTitle = Flag(short: "t", long: "title", help: "Shows title of song if true")
-    let listSources = Flag(short: "l", long: "list", help: "Lists all sources")
+    @Flag(name: .shortAndLong, help: "Prints the version.")
+    var version = false
+
+    @Flag(name: [.long, .customShort("t")], help: "Shows title of track if true")
+    var showTitle = false
+
+    @Flag(name: .shortAndLong, help: "Lists all sources")
+    var listSources = false
 
     // Named Arguments
-    let enableSource = Argument<String>(name: "source",
-        kind: .named(short: "e", long: "enable"),
-        optional: true,
-        help: "Enables a source")
-    let disableSource = Argument<String>(name: "source",
-        kind: .named(short: "d", long: "disable"),
-        optional: true,
-        help: "Disables a source")
-    let resetSource = Argument<String>(name: "source",
-        kind: .named(short: "r", long: "reset"),
-        optional: true,
-        help: "Resets a source")
+    @Option(name: .shortAndLong, help: ArgumentHelp("Enables a source", valueName: "source"))
+    var enableSource: String?
 
-    // Positional Arguments
-    let artist = Argument<String>(name: "artist",
-        kind: .positional,
-        optional: true,
-        help: "The name of the artist")
-    let trackName = Argument<String>(name: "trackName",
-        kind: .positional,
-        optional: true,
-        help: "The name of the track")
-
-    func run() -> Bool {
-        return true
+    @Option(name: .shortAndLong, help: ArgumentHelp("Disables a source", valueName: "source"))
+    var disableSource: String?
+
+    @Option(name: .shortAndLong, help: ArgumentHelp("Resets a source", valueName: "source"))
+    var resetSource: String?
+
+    mutating func run() throws {
+
+        // Handle the version flag
+        if version {
+            print(Lyricli.version)
+            Darwin.exit(0)
+        }
+
+        // Handle the list sources flag
+        if listSources {
+            Lyricli.printSources()
+            Darwin.exit(0)
+        }
+
+        // Handle the enable source option
+        if let source = enableSource {
+            do {
+                try Lyricli.enableSource(source)
+            } catch let error {
+                handleErrorAndQuit(error)
+            }
+            Darwin.exit(0)
+        }
+
+        // Handle the disable source option
+        if let source = disableSource {
+            do {
+                try Lyricli.disableSource(source)
+            } catch let error {
+                handleErrorAndQuit(error)
+            }
+            Darwin.exit(0)
+        }
+
+        // Handle the reset source flag
+        if let source = resetSource {
+            do {
+                try Lyricli.resetSource(source)
+            } catch let error {
+                handleErrorAndQuit(error)
+            }
+            Darwin.exit(0)
+        }
+
+        Lyricli.showTitle = showTitle
+
+        if let artist {
+            let currentTrack: Track
+            if let trackName {
+                currentTrack = Track(withName: trackName, andArtist: artist)
+            } else {
+                currentTrack = Track(withName: "", andArtist: artist)
+            }
+            Lyricli.printLyrics(currentTrack)
+            Darwin.exit(0)
+        }
+
+        Lyricli.printLyrics()
+    }
+
+    private func handleErrorAndQuit(_ error: Error) {
+        fputs(error.localizedDescription, stderr)
+        Darwin.exit(1)
     }
 }
index 1a182d0cd58687b734164e25347ffdb3415d8ad0..9ca0cad3c4358dcfdb03a524d50ff20663784cb1 100644 (file)
@@ -1,5 +1,4 @@
 import Foundation
-import HTMLEntities
 
 // Given a track, attempts to fetch the lyrics from lyricswiki
 class LyricsEngine {
@@ -82,7 +81,10 @@ class LyricsEngine {
                                         if let lyricsUrl = URL(string: lyricsUrlString) {
 
                                             // At this point we have a valid wiki url
-                                            self.fetchLyricsFromPage(withURL: lyricsUrl, completionHandler: completionHandler)
+                                            self.fetchLyricsFromPage(
+                                                withURL: lyricsUrl,
+                                                completionHandler: completionHandler
+                                            )
                                             return
                                         }
                                     }
@@ -145,10 +147,9 @@ class LyricsEngine {
         completionHandler(nil)
     }
 
-    // Escapes the HTML entities
+    // Escapes the HTML entities and HTML
     private func decodeLyrics(_ lyrics: String) -> String {
 
-        let unescapedLyrics = lyrics.htmlUnescape()
-        return unescapedLyrics.replacingOccurrences(of: "<br />", with: "\n")
+        return lyrics
     }
 }
diff --git a/Sources/lyricli/main.swift b/Sources/lyricli/main.swift
deleted file mode 100644 (file)
index be1c933..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-import Foundation
-import Bariloche
-
-// Entry point of the application. This is the main executable
-private func main() {
-
-    // Bariloche assumes at least one argument, so bypass
-    // if that's the case.
-    if CommandLine.arguments.count > 1 {
-        let parser = Bariloche(command: LyricliCommand())
-        let result = parser.parse()
-
-        if result.count == 0 {
-            exit(EX_USAGE)
-        }
-
-        if let lyricliCommand = result[0] as? LyricliCommand {
-            // Flags
-            checkVersionFlag(lyricliCommand)
-            checkListSourcesFlag(lyricliCommand)
-            checkTitleFlag(lyricliCommand)
-
-            // String Options
-
-            checkEnableSourceFlag(lyricliCommand)
-            checkDisableSourceFlag(lyricliCommand)
-            checkResetSourceFlag(lyricliCommand)
-
-            checkPositionalArguments(lyricliCommand)
-
-        }
-    }
-
-    // Run Lyricli
-    Lyricli.printLyrics()
-}
-
-// Handle the version flag
-
-private func checkVersionFlag(_ command: LyricliCommand) {
-    if command.version.value {
-        print(Lyricli.version)
-        exit(0)
-    }
-}
-
-// Handle the list sources flag
-
-private func checkListSourcesFlag(_ command: LyricliCommand) {
-    if command.listSources.value {
-        Lyricli.printSources()
-        exit(0)
-    }
-}
-
-// Handle the title flag
-
-private func checkTitleFlag(_ command: LyricliCommand) {
-    Lyricli.showTitle = command.showTitle.value
-}
-
-// Handle the enable source flag
-
-private func checkEnableSourceFlag(_ command: LyricliCommand) {
-    if let source = command.enableSource.value {
-        Lyricli.enableSource(source)
-        exit(0)
-    }
-}
-
-// Handle the disable source flag
-
-private func checkDisableSourceFlag(_ command: LyricliCommand) {
-    if let source = command.disableSource.value {
-        Lyricli.disableSource(source)
-        exit(0)
-    }
-}
-
-// Handle the reset source flag
-
-private func checkResetSourceFlag(_ command: LyricliCommand) {
-    if let source = command.resetSource.value {
-        Lyricli.resetSource(source)
-        exit(0)
-    }
-}
-
-// Handle the positional arguments
-
-private func checkPositionalArguments(_ command: LyricliCommand) {
-    if let artist = command.artist.value {
-
-        let currentTrack: Track
-
-        if let trackName = command.trackName.value {
-            currentTrack = Track(withName: trackName, andArtist: artist)
-        } else {
-            currentTrack = Track(withName: "", andArtist: artist)
-        }
-
-        Lyricli.printLyrics(currentTrack)
-        exit(0)
-    }
-}
-
-main()
index 2f0b8f48ac5d8bfd2c737ad4f6e4b5bf2cbda3ab..481d327de3b9b57ee36174d380d7f017a52450dd 100644 (file)
@@ -2,8 +2,8 @@
 class SourceManager {
 
     // List of sources enabled for the crurent platform
-    private var availableSources: [String: Source] = [
-        "itunes": ItunesSource(),
+    var availableSources: [String: Source] = [
+        "apple_music": AppleMusicSource(),
         "spotify": SpotifySource()
     ]
 
similarity index 72%
rename from Sources/lyricli/sources/itunes_source.swift
rename to Sources/lyricli/sources/apple_music_source.swift
index 92b99695d81f590bbf22c9f117bc6e8f9ff976cf..5eee5889cd7e784a708de0cc834fb94c25c8d840 100644 (file)
@@ -1,35 +1,35 @@
 import ScriptingBridge
 import Foundation
 
-// Protocol to obtain the track from iTunes
-@objc protocol iTunesTrack {
+// Protocol to obtain the track from 
+@objc protocol AppleMusicTrack {
     @objc optional var name: String {get}
     @objc optional var artist: String {get}
 }
 
-// Protocol to interact with iTunes
-@objc protocol iTunesApplication {
-    @objc optional var currentTrack: iTunesTrack? {get}
+// Protocol to interact with Apple Music
+@objc protocol AppleMusicApplication {
+    @objc optional var currentTrack: AppleMusicTrack? {get}
     @objc optional var currentStreamTitle: String? {get}
 }
 
-extension SBApplication: iTunesApplication {}
+extension SBApplication: AppleMusicApplication {}
 
 // Source that reads track artist and name from current itunes track
-class ItunesSource: Source {
+class AppleMusicSource: Source {
 
     // Calls the spotify API and returns the current track
     var currentTrack: Track? {
 
-        if let iTunes: iTunesApplication = SBApplication(bundleIdentifier: bundleIdentifier) {
-            if let application = iTunes as? SBApplication {
+        if let appleMusic: AppleMusicApplication = SBApplication(bundleIdentifier: bundleIdentifier) {
+            if let application = appleMusic as? SBApplication {
               if !application.isRunning {
                 return nil
               }
             }
 
             // Attempt to fetch the title from a stream
-            if let currentStreamTitle = iTunes.currentStreamTitle {
+            if let currentStreamTitle = appleMusic.currentStreamTitle {
                 if let track = currentStreamTitle {
 
                     let trackComponents = track.split(separator: "-").map(String.init)
@@ -45,7 +45,7 @@ class ItunesSource: Source {
             }
 
             // Attempt to fetch the title from a song
-            if let currentTrack = iTunes.currentTrack {
+            if let currentTrack = appleMusic.currentTrack {
                 if let track = currentTrack {
                     if let name = track.name {
                         if let artist = track.artist {
@@ -74,4 +74,7 @@ class ItunesSource: Source {
         return "com.apple.iTunes"
     }
 
+    func enable() -> Bool { return true }
+    func disable() -> Bool { return true }
+    func reset() -> Bool { return true }
 }
index 0885994d338589b456f5dde7f85b1062c204c844..8d52603667113ae28c08a3c7a390ed59d830bf72 100644 (file)
@@ -2,4 +2,8 @@
 // property will return a track if the conditions are met
 protocol Source {
     var currentTrack: Track? { get }
+
+    func disable() -> Bool
+    func enable() -> Bool
+    func reset() -> Bool
 }
index 9c516c4d4e96b550e3967f960cccbb51cbb19ad0..863a399f2e68bc13cede3bcada3de8005ae264c1 100644 (file)
@@ -43,4 +43,7 @@ class SpotifySource: Source {
         return nil
     }
 
+    func enable() -> Bool { return true }
+    func disable() -> Bool { return true }
+    func reset() -> Bool { return true }
 }