diff options
| author | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2023-04-10 13:02:51 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2023-04-10 13:02:51 +0200 |
| commit | c53df649c817f770ae1750bb6d11113e0bcd2b18 (patch) | |
| tree | 6c789c51ba15ba4faa6346b60996bc23a261a6ff /Sources | |
| parent | 1b8bdf0f82d6273dc39a67ac579d34d5b0d7d21f (diff) | |
Update code, add source management config
Diffstat (limited to 'Sources')
| -rw-r--r-- | Sources/lyricli/configuration.swift | 2 | ||||
| -rw-r--r-- | Sources/lyricli/errors/configuration_could_not_be_read.swift | 3 | ||||
| -rw-r--r-- | Sources/lyricli/errors/source_could_not_be_disabled.swift | 6 | ||||
| -rw-r--r-- | Sources/lyricli/errors/source_could_not_be_enabled.swift | 6 | ||||
| -rw-r--r-- | Sources/lyricli/errors/source_could_not_be_reset.swift | 6 | ||||
| -rw-r--r-- | Sources/lyricli/errors/source_not_available.swift | 3 | ||||
| -rw-r--r-- | Sources/lyricli/lyricli.swift | 60 | ||||
| -rw-r--r-- | Sources/lyricli/lyricli_command.swift | 115 | ||||
| -rw-r--r-- | Sources/lyricli/lyrics_engine.swift | 11 | ||||
| -rw-r--r-- | Sources/lyricli/main.swift | 107 | ||||
| -rw-r--r-- | Sources/lyricli/source_manager.swift | 4 | ||||
| -rw-r--r-- | Sources/lyricli/sources/apple_music_source.swift (renamed from Sources/lyricli/sources/itunes_source.swift) | 25 | ||||
| -rw-r--r-- | Sources/lyricli/sources/source_protocol.swift | 4 | ||||
| -rw-r--r-- | Sources/lyricli/sources/spotify_source.swift | 3 |
14 files changed, 189 insertions, 166 deletions
diff --git a/Sources/lyricli/configuration.swift b/Sources/lyricli/configuration.swift index 1b01034..b2defc1 100644 --- a/Sources/lyricli/configuration.swift +++ b/Sources/lyricli/configuration.swift @@ -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 index 0000000..fb2b61a --- /dev/null +++ b/Sources/lyricli/errors/configuration_could_not_be_read.swift @@ -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 index 0000000..b19f3c5 --- /dev/null +++ b/Sources/lyricli/errors/source_could_not_be_disabled.swift @@ -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 index 0000000..7797478 --- /dev/null +++ b/Sources/lyricli/errors/source_could_not_be_enabled.swift @@ -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 index 0000000..beecf54 --- /dev/null +++ b/Sources/lyricli/errors/source_could_not_be_reset.swift @@ -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 index 0000000..e3d598d --- /dev/null +++ b/Sources/lyricli/errors/source_not_available.swift @@ -0,0 +1,3 @@ +struct SourceNotAvailable: Error { + var localizedDescription = "The selected source wasn't available" +} diff --git a/Sources/lyricli/lyricli.swift b/Sources/lyricli/lyricli.swift index 043c004..ad4bb86 100644 --- a/Sources/lyricli/lyricli.swift +++ b/Sources/lyricli/lyricli.swift @@ -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 diff --git a/Sources/lyricli/lyricli_command.swift b/Sources/lyricli/lyricli_command.swift index 88f9c7b..872ab6e 100644 --- a/Sources/lyricli/lyricli_command.swift +++ b/Sources/lyricli/lyricli_command.swift @@ -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") + @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() + } - func run() -> Bool { - return true + private func handleErrorAndQuit(_ error: Error) { + fputs(error.localizedDescription, stderr) + Darwin.exit(1) } } diff --git a/Sources/lyricli/lyrics_engine.swift b/Sources/lyricli/lyrics_engine.swift index 1a182d0..9ca0cad 100644 --- a/Sources/lyricli/lyrics_engine.swift +++ b/Sources/lyricli/lyrics_engine.swift @@ -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 index be1c933..0000000 --- a/Sources/lyricli/main.swift +++ /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() diff --git a/Sources/lyricli/source_manager.swift b/Sources/lyricli/source_manager.swift index 2f0b8f4..481d327 100644 --- a/Sources/lyricli/source_manager.swift +++ b/Sources/lyricli/source_manager.swift @@ -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() ] diff --git a/Sources/lyricli/sources/itunes_source.swift b/Sources/lyricli/sources/apple_music_source.swift index 92b9969..5eee588 100644 --- a/Sources/lyricli/sources/itunes_source.swift +++ b/Sources/lyricli/sources/apple_music_source.swift @@ -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 } } diff --git a/Sources/lyricli/sources/source_protocol.swift b/Sources/lyricli/sources/source_protocol.swift index 0885994..8d52603 100644 --- a/Sources/lyricli/sources/source_protocol.swift +++ b/Sources/lyricli/sources/source_protocol.swift @@ -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 } diff --git a/Sources/lyricli/sources/spotify_source.swift b/Sources/lyricli/sources/spotify_source.swift index 9c516c4..863a399 100644 --- a/Sources/lyricli/sources/spotify_source.swift +++ b/Sources/lyricli/sources/spotify_source.swift @@ -43,4 +43,7 @@ class SpotifySource: Source { return nil } + func enable() -> Bool { return true } + func disable() -> Bool { return true } + func reset() -> Bool { return true } } |