From: Ben Beltran Date: Sat, 20 May 2017 14:40:30 +0000 (-0500) Subject: Merge branch 'feature/rbdr-document-and-cleanup' into develop X-Git-Tag: 0.1.0^2~1 X-Git-Url: https://git.r.bdr.sh/rbdr/lyricli/commitdiff_plain/29a566fada656091bf92456f6fb0f1bea5b6dca3?hp=85d0536f2e9a3d4596c01b263d76b2b8d9ba70ae Merge branch 'feature/rbdr-document-and-cleanup' into develop --- diff --git a/.gitignore b/.gitignore index 02c0875..2deb6d7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.build /Packages /*.xcodeproj +docs diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..46250e5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +language: + - swift + +osx_image: xcode8.3 + +install: + - gem install jazzy + - "./Scripts/install_sourcekitten.sh" + - "./Scripts/install_swiftlint.sh" + +script: + - make lint + - make build + +before_deploy: + - make document + +deploy: + provider: pages + skip_cleanup: true + github_token: "$GITHUB_TOKEN" + local_dir: docs + on: + branch: master + +env: + global: + secure: EyOzJFSGY2ifBVqnQz7Xc0sDcg9maLb7VDKWIC2+1n2RsMHGptsxDfJf9r/bOc2kJN9mCzw19eA3XTkypeHKgIgPZ+boLPTDqiiNcD+0iVkYxqw/Q0v5et1+pJaOUo93cKfl2WLWXvISU1MYuzbjGwmnjPDUmujTwGZH1SFvhOKynqx9V/PiL4ZF+CurU2far+diLDhJXUPT4mDV6lDfiALUBvfj50AplM928Vwc6xr71SFii4fE+1GGGGI23ZyXmhnYIJBfQ/9d2wzW6szSRz+q0Gq8jQFJ2cZmBQPnfPY6/xARkDIf5H55HIxLg8pqA7Yn+WDT6/a8uoFLY6OzI8B/TTZ/pX4LXhkK0gbmXeeigRjxN3Dcsb++n9e5+3/Bq0y/Vm+Ufy+TtEvExvU6vdzDu8YZQaE0T2Loyqaw3BQBMoCunv4i7z0crXTLyNYNuc3zDGDmjkR3laxX8lcEZ85zTRTuYqxmvQxkxWUHKYQOvGy7SfkD1xc73f1XvCqpx45utZX0U/OzIxRflWFNy4mlgLvo23h5T0b44LGBBBWEVkjt5YduOuSo9L1wtOrADcDYyxSciIby2SHd4B2fGOb059KyCIUcX/qgOS6FJlmPeC963NCAuZB6DyscaoT6DrJto9nuZW2wNYdo7dvCC2E4ZqHnRPl2zux/RTmeuCU= diff --git a/CHANGELOG.md b/CHANGELOG.md index b6712ba..810e765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- Documentation with Jazzy / SourceKitten +- Apache License +- Documentation to help use lyricli and contribute +- Makefile to help build and install +- CI integration +- Lyrics engine to fetch the lyrics from lyricswiki +- Arguments source to read song and artist from command line - Parsing of options to match legacy lyricli - Placeholder for the library with expected endpoints diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..966e0e1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing to Lyricli + +At this moment this is a really small project that I used to revive an +old ruby project and learn swift, it probably has a lot of bad code that +is not in "the swift way", it has no tests and can always be extended to +support more sources. + +## The ~~Roadmap~~ ~~Streetmap~~ ~~Pathmap~~ Corridormap + +* Writing sources (They're used to automatically obtain artist and song + name from +* Writing tests +* Improving code to match idiomatic swift +* Improving the documentation +* Extending the lyrics engine to support different lyrics sources +* Improving the build system + +## How to contribute + +Be nice, always, to others and yourself: use welcoming and inclusive language, +be patient and respectful of others' opinions and experiences. + +Do not insult others, use sexualized language, publish others' personal +information without their consent. Do not harass others. + +To report unacceptabe behavior, send an [e-mail][email] + +## Sending Pull Requests + +* Run [swiftlint][swiftlint] on the Source directory and make sure there are no errors +* There should be no warnings on compilation +* [CI][ci] should be green +* Make the PRs according to [Git Flow][gitflow]: (features go to + develop, hotfixes go to master) + +[gitflow]: https://github.com/nvie/gitflow +[swiftlint]: https://github.com/realm/SwiftLint +[email]: mailto:ben@nsovocal.com +[ci]: https://travis-ci.org/lyricli-app/lyricli diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..499e1d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +configuration = debug +build_path = .build + +# These are used to rename the executable to lrc without renaming the package +source_binary_name = lyricli +target_binary_name = lrc +install_path = /usr/local/bin +source_binary_path = $(build_path)/$(configuration)/$(source_binary_name) +install_binary_path = $(install_path)/$(target_binary_name) + +# Default to release configuration on install +install: configuration = release + +default: build + +build: + swift build --build-path $(build_path) --configuration $(configuration) + +install: build + cp $(source_binary_path) $(install_binary_path) + +test: build + swift test + +lint: + cd Sources && swiftlint + +document: build + sourcekitten doc --spm-module $(source_binary_name) > $(build_path)/$(source_binary_name).json + jazzy \ + -s $(build_path)/$(source_binary_name).json \ + --readme README.md \ + --clean \ + --author Lyricli \ + --author_url https://github.com/lyricli-app \ + --github_url https://github.com/lyricli-app/lyricli \ + --module-version 0.0.0 \ + --module Lyricli \ + +clean: + swift build --clean + +.PHONY: build install test clean lint diff --git a/Package.pins b/Package.pins new file mode 100644 index 0000000..4bb7a96 --- /dev/null +++ b/Package.pins @@ -0,0 +1,18 @@ +{ + "autoPin": true, + "pins": [ + { + "package": "CommandeLineKit", + "reason": null, + "repositoryURL": "https://github.com/rbdr/CommandLineKit", + "version": "4.0.0" + }, + { + "package": "HTMLEntities", + "reason": null, + "repositoryURL": "https://github.com/IBM-Swift/swift-html-entities.git", + "version": "3.0.3" + } + ], + "version": 1 +} \ No newline at end of file diff --git a/README.md b/README.md index 71d7259..5e7c995 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,85 @@ # Lyricli (lrc) -A command line tool to show the lyrics of your current song +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: + +``` +$ lrc [-t] +``` + +When you run it without arguments, it will look in the available source +to try to find a playing song and extract the lyrics. If you include the +`-t` flag, it will show the song and artist names before the lyrics. + +``` +$ lrc [-t] +``` + +When you run it with arguments, it will use them to search for the +lyrics. This won't work if you manually disable the arguments source in +your configuration file. If you include the `-t` flag, it will show the +song and artist names before the lyrics. + +### Commands + +In order to configure + +* `lrc -l` or `lrc --list-sources` lists the available sources. Enabled + sourcess will have a `*` +* `lrc -e` or `lrc --enable ` enables a source +* `lrc -d` or `lrc --disable ` disables a source +* `lrc -r` or `lrc --reset-source ` resets the configuration for + a source and disables it. +* `lrc -v` or `lrc --version` prints the version +* `lrc -h` or `lrc --help` display built-in help ## Building -Run `swift build` +The build has only been tested on OSX using Swift 3.1. Building defaults +to the debug configuration. + +``` +make +``` + +## Installing from source + +Builds lyricli in release configuration and copies the executable as +`lrc` to `/usr/local/bin` + +``` +make install +``` + +### Installing to a custom directory + +This can be done by overriding the `install_path` variable + +``` +make install install_path=/opt/bin +``` + +## Linting and Generating Documentation + +We use [swiftlint][swiftlint] to lint, and `make lint` to run it. +We use [jazzy][jazzy] and [SourceKitten][sourcekitten] to document, and +`make document` to generate it. ## Running tests -Run `swift test` +No tests at the moment 😬... but the makefile is mapped to run the swift +tests. + +``` +make test +``` + +[![Build Status](https://travis-ci.org/lyricli-app/lyricli.svg?branch=master)](https://travis-ci.org/lyricli-app/lyricli) + +[swiftlint]: https://github.com/realm/SwiftLint +[jazzy]: https://github.com/realm/jazzy +[sourcekitten]: https://github.com/jpsim/SourceKitten diff --git a/Scripts/install_sourcekitten.sh b/Scripts/install_sourcekitten.sh new file mode 100755 index 0000000..9420a26 --- /dev/null +++ b/Scripts/install_sourcekitten.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Taken from: https://alexplescan.com/posts/2016/03/03/setting-up-swiftlint-on-travis-ci/ +# And adapted for sourcekitten + +# Installs the SourceKitten package. +# Tries to get the precompiled .pkg file from Github, but if that +# fails just recompiles from source. + +set -e + +SOURCEKITTEN_PKG_PATH="/tmp/SourceKitten.pkg" +SOURCEKITTEN_PKG_URL="https://github.com/jpsim/SourceKitten/releases/download/0.17.3/SourceKitten.pkg" + +wget --output-document=$SOURCEKITTEN_PKG_PATH $SOURCEKITTEN_PKG_URL + +if [ -f $SOURCEKITTEN_PKG_PATH ]; then + echo "SourceKitten package exists! Installing it..." + sudo installer -pkg $SOURCEKITTEN_PKG_PATH -target / +else + echo "SourceKitten package doesn't exist. Compiling from source..." && + git clone https://github.com/jspim/SourceKitten.git /tmp/SourceKitten && + cd /tmp/SourceKitten && + sudo make install +fi diff --git a/Scripts/install_swiftlint.sh b/Scripts/install_swiftlint.sh new file mode 100755 index 0000000..6a2f250 --- /dev/null +++ b/Scripts/install_swiftlint.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Taken from: https://alexplescan.com/posts/2016/03/03/setting-up-swiftlint-on-travis-ci/ + +# Installs the SwiftLint package. +# Tries to get the precompiled .pkg file from Github, but if that +# fails just recompiles from source. + +set -e + +SWIFTLINT_PKG_PATH="/tmp/SwiftLint.pkg" +SWIFTLINT_PKG_URL="https://github.com/realm/SwiftLint/releases/download/0.18.1/SwiftLint.pkg" + +wget --output-document=$SWIFTLINT_PKG_PATH $SWIFTLINT_PKG_URL + +if [ -f $SWIFTLINT_PKG_PATH ]; then + echo "SwiftLint package exists! Installing it..." + sudo installer -pkg $SWIFTLINT_PKG_PATH -target / +else + echo "SwiftLint package doesn't exist. Compiling from source..." && + git clone https://github.com/realm/SwiftLint.git /tmp/SwiftLint && + cd /tmp/SwiftLint && + git submodule update --init --recursive && + sudo make install +fi diff --git a/Sources/arguments_source.swift b/Sources/arguments_source.swift index c07c952..9615318 100644 --- a/Sources/arguments_source.swift +++ b/Sources/arguments_source.swift @@ -1,17 +1,18 @@ -/// Source that deals with command line +// Source that reads track artist and name from the command line class ArgumentsSource: Source { - public var currentTrack: Track? { - get { - if CommandLine.arguments.count >= 3 { - // expected usage: $ ./lyricli + // Returns a track based on the arguments. It assumes the track artist + // will be the first argument, and the name will be the second, excluding + // any flags. + var currentTrack: Track? { - let trackName: String = CommandLine.arguments[2] - let trackArtist: String = CommandLine.arguments[1] + if CommandLine.arguments.count >= 3 { + // expected usage: $ ./lyricli + let trackName: String = CommandLine.arguments[2] + let trackArtist: String = CommandLine.arguments[1] - return Track(withName: trackName, andArtist: trackArtist) - } - return nil + return Track(withName: trackName, andArtist: trackArtist) } + return nil } } diff --git a/Sources/configuration.swift b/Sources/configuration.swift index f5e4b0f..f1a1ee1 100644 --- a/Sources/configuration.swift +++ b/Sources/configuration.swift @@ -1,34 +1,38 @@ import Foundation -/// Handles reading and writing the configuration -public class Configuration { - - let configurationPath = NSString(string: "~/.lyricli.conf").expandingTildeInPath - - // The defaults are added here +// Reads and writes the configuration. Config keys are accessed as a dictionary. +class Configuration { + // Location of the global configuration file + private let configurationPath = NSString(string: "~/.lyricli.conf").expandingTildeInPath + // Default options, will be automatically written to the global config if + // not found. private var configuration: [String: Any] = [ - "enabled_sources": ["arguments"], - "default": true + "enabled_sources": ["arguments"] ] - static let sharedInstance: Configuration = Configuration() + // The shared instance of the object + static let shared: Configuration = Configuration() private init() { - // Read the config file and attempt toset any of the values. Otherwise - // Don't do anything. - // IMPROVEMENT: Enable a debug mode + // Read the config file and attempt to set any of the values. Otherwise + // don't do anything. if let data = try? NSData(contentsOfFile: configurationPath) as Data { - if let parsedConfig = try? JSONSerialization.jsonObject(with: data) as! [String:Any] { - for (key, value) in parsedConfig { + if let parsedConfig = try? JSONSerialization.jsonObject(with: data) { + if let parsedConfig = parsedConfig as? [String: Any] { + for (key, value) in parsedConfig { - if key == "enabled_sources" { - configuration[key] = value as! [String] - } - else { - configuration[key] = value as! String + if key == "enabled_sources" { + if let value = value as? [String] { + configuration[key] = value + } + } else { + if let value = value as? String { + configuration[key] = value + } + } } } } @@ -37,18 +41,22 @@ public class Configuration { writeConfiguration() } + // Write the configuration back to the file private func writeConfiguration() { var error: NSError? if let outputStream = OutputStream(toFileAtPath: configurationPath, append: false) { outputStream.open() - JSONSerialization.writeJSONObject(configuration, to: outputStream, options: [JSONSerialization.WritingOptions.prettyPrinted], error: &error) + JSONSerialization.writeJSONObject(configuration, + to: outputStream, + options: [JSONSerialization.WritingOptions.prettyPrinted], + error: &error) outputStream.close() } } - // Allow access as an object + // Allow access to the config properties as a dictionary subscript(index: String) -> Any? { get { return configuration[index] diff --git a/Sources/lyricli.swift b/Sources/lyricli.swift index c44a7f4..e7d14b9 100644 --- a/Sources/lyricli.swift +++ b/Sources/lyricli.swift @@ -1,15 +1,20 @@ -/// The main Lyricli interface -public class Lyricli { - public static var version = "0.0.0-feature/option-parsing" +// The main class, handles all the actions that the executable will call +class Lyricli { - public static var showTitle = false + // Version of the application + static var version = "0.0.0" - public static func printLyrics() { + // Flag that controls whether we should show the track artist and name before + // the lyrics + static var showTitle = false + + // Obtains the name of the current track from a source, fetches the lyrics + // from an engine and prints them + static func printLyrics() { let sourceManager = SourceManager() if let currentTrack = sourceManager.currentTrack { - let engine = LyricsEngine(withTrack: currentTrack) if let lyrics = engine.lyrics { @@ -18,33 +23,37 @@ public class Lyricli { } print(lyrics) - } - else { + } else { print("Lyrics not found :(") } - } - else { + } else { print("No Artist/Song could be found :(") } } - public static func printSources() { + // Print the currently available sources + static func printSources() { print("Listing Sources: Not yet implemented") } - public static func enableSource(_ sourceName: String) { + // 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") } - public static func disableSource(_ sourceName: String) { + // Remove a source from the enabled sources configuration + static func disableSource(_ sourceName: String) { print("Disable source \(sourceName): Not yet implemented") } - public static func resetSource(_ sourceName: String) { + // Removes any configuration for a source, and disables it + static func resetSource(_ sourceName: String) { print("Reset source \(sourceName): Not yet implemented") } + // Prints the track artist and name private static func printTitle(_ track: Track) { print("\(track.artist) - \(track.name)") } diff --git a/Sources/lyrics_engine.swift b/Sources/lyrics_engine.swift index d6b1985..85e4735 100644 --- a/Sources/lyrics_engine.swift +++ b/Sources/lyrics_engine.swift @@ -1,80 +1,87 @@ import Foundation import HTMLEntities -/// Looks for lyrics on the internet +// Given a track, attempts to fetch the lyrics from lyricswiki class LyricsEngine { + // URL of the API endpoint to use private let apiURL = "https://lyrics.wikia.com/api.php?action=lyrics&func=getSong&fmt=realjson" + + // Method used to call the API private let apiMethod = "GET" + + // Regular expxression used to find the lyrics in the lyricswiki HTML private let lyricsMatcher = "class='lyricbox'>(.+) Void in - if let lyricsResult = lyricsResult { - lyrics = lyricsResult - requestFinished = true - asyncLock.signal() - } - }) + // Call the API and unlock when you're done - while(!requestFinished) { - asyncLock.wait() + fetchLyricsFromAPI(withURL: url, completionHandler: {lyricsResult -> Void in + if let lyricsResult = lyricsResult { + lyrics = lyricsResult + requestFinished = true + asyncLock.signal() } - asyncLock.unlock() + }) + + while !requestFinished { + asyncLock.wait() } + asyncLock.unlock() } } - - return lyrics } + + return lyrics } + // Initializes with a track init(withTrack targetTrack: Track) { track = targetTrack } - // Fetch the lyrics from the API and request / parse the page - + // Fetch the lyrics URL from the API, triggers the request to fetch the + // lyrics page private func fetchLyricsFromAPI(withURL url: URL, completionHandler: @escaping (String?) -> Void) { var apiRequest = URLRequest(url: url) apiRequest.httpMethod = "GET" - let task = URLSession.shared.dataTask(with: apiRequest, completionHandler: {data, response, error -> Void in + let task = URLSession.shared.dataTask(with: apiRequest, completionHandler: {data, _, _ -> Void in // If the response is parseable JSON, and has a url, we'll look for // the lyrics in there if let data = data { - let jsonResponse = try? JSONSerialization.jsonObject(with: data) as! [String: Any] - if let jsonResponse = jsonResponse { - if let lyricsUrlString = jsonResponse["url"] as? String { - if let lyricsUrl = URL(string: lyricsUrlString) { - - // At this point we have a valid wiki url - self.fetchLyricsFromPage(withURL: lyricsUrl, completionHandler: completionHandler) - return + if let jsonResponse = try? JSONSerialization.jsonObject(with: data) { + if let jsonResponse = jsonResponse as? [String: Any] { + if let lyricsUrlString = jsonResponse["url"] as? String { + if let lyricsUrl = URL(string: lyricsUrlString) { + + // At this point we have a valid wiki url + self.fetchLyricsFromPage(withURL: lyricsUrl, completionHandler: completionHandler) + return + } } } } @@ -85,14 +92,13 @@ class LyricsEngine { task.resume() } - // Fetch the lyrics from the page and parse the page - + // Fetch the lyrics from the page and send it to the parser private func fetchLyricsFromPage(withURL url: URL, completionHandler: @escaping (String?) -> Void) { var pageRequest = URLRequest(url: url) pageRequest.httpMethod = "GET" - let task = URLSession.shared.dataTask(with: pageRequest, completionHandler: {data, response, error -> Void in + let task = URLSession.shared.dataTask(with: pageRequest, completionHandler: {data, _, _ -> Void in // If the response is parseable JSON, and has a url, we'll look for // the lyrics in there @@ -109,10 +115,11 @@ class LyricsEngine { task.resume() } - // Parses the wiki to obtain the lyrics - + // Parses the wiki to find the lyrics, decodes the lyrics object private func parseHtmlBody(_ body: String, completionHandler: @escaping (String?) -> Void) { + // Look for the lyrics lightbox + if let regex = try? NSRegularExpression(pattern: lyricsMatcher) { let matches = regex.matches(in: body, range: NSRange(location: 0, length: body.characters.count)) @@ -133,7 +140,6 @@ class LyricsEngine { } // Escapes the HTML entities - private func decodeLyrics(_ lyrics: String) -> String { let unescapedLyrics = lyrics.htmlUnescape() diff --git a/Sources/main.swift b/Sources/main.swift index e4b2760..9d46e92 100644 --- a/Sources/main.swift +++ b/Sources/main.swift @@ -1,11 +1,44 @@ import CommandLineKit import Foundation +// Entry point of the application. This is the main executable +private func main() { + let (flags, parser) = createParser() + + do { + try parser.parse() + } catch { + parser.printUsage(error) + exit(EX_USAGE) + } + + // Boolean Options + + checkHelpFlag(flags["help"], withParser: parser) + checkVersionFlag(flags["version"], withParser: parser) + checkListSourcesFlag(flags["listSources"], withParser: parser) + checkTitleFlag(flags["title"], withParser: parser) + + // String Options + + checkEnableSourceFlag(flags["enableSource"], withParser: parser) + checkDisableSourceFlag(flags["disableSource"], withParser: parser) + checkResetSourceFlag(flags["resetSource"], withParser: parser) + + // Remove any flags so anyone after this gets the unprocessed values + + let programName: [String] = [CommandLine.arguments[0]] + CommandLine.arguments = programName + parser.unparsedArguments + + // Run Lyricli + + Lyricli.printLyrics() +} + /// Sets up and returns a new options parser /// /// - Returns: A Dictionary of Options, and a new CommandLineKit instance -func createParser() -> ([String:Option], CommandLineKit) { - +private func createParser() -> ([String:Option], CommandLineKit) { let parser = CommandLineKit() var flags: [String:Option] = [:] @@ -21,74 +54,98 @@ func createParser() -> ([String:Option], CommandLineKit) { parser.addOptions(Array(flags.values)) - return (flags, parser) -} + parser.formatOutput = {parseString, type in -func main() { + var formattedString: String - let (flags, parser) = createParser() + switch type { + case .About: + formattedString = "\(parseString) [ ]" + break + default: + formattedString = parseString + } - do { - try parser.parse() - } - catch { - parser.printUsage(error) - exit(EX_USAGE) + return parser.defaultFormat(formattedString, type: type) } - if let helpFlag = flags["help"] as? BoolOption { - if helpFlag.value == true { + return (flags, parser) +} + +// Handle the Help flag + +private func checkHelpFlag(_ flag: Option?, withParser parser: CommandLineKit) { + if let helpFlag = flag as? BoolOption { + if helpFlag.value { parser.printUsage() exit(0) } } +} + +// Handle the version flag - if let versionFlag = flags["version"] as? BoolOption { - if versionFlag.value == true { +private func checkVersionFlag(_ flag: Option?, withParser parser: CommandLineKit) { + if let versionFlag = flag as? BoolOption { + if versionFlag.value { print(Lyricli.version) exit(0) } } +} + +// Handle the list sources flag - if let listSourcesFlag = flags["listSources"] as? BoolOption { - if listSourcesFlag.value == true { +private func checkListSourcesFlag(_ flag: Option?, withParser parser: CommandLineKit) { + if let listSourcesFlag = flag as? BoolOption { + if listSourcesFlag.value { Lyricli.printSources() exit(0) } } +} + +// Handle the title flag + +private func checkTitleFlag(_ flag: Option?, withParser parser: CommandLineKit) { + if let titleFlag = flag as? BoolOption { + if titleFlag.value { + Lyricli.showTitle = true + } + } +} - if let enableSourceFlag = flags["enableSource"] as? StringOption { +// Handle the enable source flag + +private func checkEnableSourceFlag(_ flag: Option?, withParser parser: CommandLineKit) { + if let enableSourceFlag = flag as? StringOption { if let source = enableSourceFlag.value { Lyricli.enableSource(source) exit(0) } } +} - if let disableSourceFlag = flags["disableSource"] as? StringOption { +// Handle the disable source flag + +private func checkDisableSourceFlag(_ flag: Option?, withParser parser: CommandLineKit) { + if let disableSourceFlag = flag as? StringOption { if let source = disableSourceFlag.value { Lyricli.disableSource(source) exit(0) } } +} - if let resetSourceFlag = flags["resetSource"] as? StringOption { +// Handle the reset source flag + +private func checkResetSourceFlag(_ flag: Option?, withParser parser: CommandLineKit) { + if let resetSourceFlag = flag as? StringOption { if let source = resetSourceFlag.value { Lyricli.resetSource(source) exit(0) } } - - if let titleFlag = flags["title"] as? BoolOption { - if titleFlag.value == true { - Lyricli.showTitle = true - } - } - - // Remove any flags so anyone after this gets the unprocessed values - let programName: [String] = [CommandLine.arguments[0]] - CommandLine.arguments = programName + parser.unparsedArguments - - Lyricli.printLyrics() } main() diff --git a/Sources/source_manager.swift b/Sources/source_manager.swift index dd956ad..5ee1305 100644 --- a/Sources/source_manager.swift +++ b/Sources/source_manager.swift @@ -1,52 +1,50 @@ -/// Manages the different sources. Keeps track of them, lists and toggles +// Collect and manage the available and enabled source class SourceManager { + // List of sources enabled for the crurent platform private var availableSources: [String: Source] = [ "arguments": ArgumentsSource() ] + // Iterate over the sources until we find a track or run out of sources var currentTrack: Track? { - get { - - for source in enabledSources { - if let currentTrack = source.currentTrack { - return currentTrack - } + for source in enabledSources { + if let currentTrack = source.currentTrack { + return currentTrack } - - return nil } + + return nil } + // Return the list of enabled sources based on the configuration var enabledSources: [Source] { // Checks the config and returns an array of sources based on the // enabled and available ones - get { - var sources = [Source]() + var sources = [Source]() - if let sourceNames = Configuration.sharedInstance["enabled_sources"] as? [String]{ - for sourceName in sourceNames { - if let source = availableSources[sourceName] { - sources.append(source) - } + if let sourceNames = Configuration.shared["enabled_sources"] as? [String] { + for sourceName in sourceNames { + if let source = availableSources[sourceName] { + sources.append(source) } } - - return sources } + + return sources } + // Given a source name, it will enable it and add it to the enabled sources config func enable(sourceName: String) { } + // Given a source name, it will remove it from the enabled sources config func disable(sourceName: String) { } + // Given a source name, it removes any stored configuration and disables it func reset(sourceName: String) { } - - func getSources(sourceName: String) { - } } diff --git a/Sources/source_protocol.swift b/Sources/source_protocol.swift index ee9350c..0885994 100644 --- a/Sources/source_protocol.swift +++ b/Sources/source_protocol.swift @@ -1,3 +1,5 @@ +// All sources should comply with this protocol. The currentTrack computed +// property will return a track if the conditions are met protocol Source { var currentTrack: Track? { get } } diff --git a/Sources/track.swift b/Sources/track.swift index d2a9047..ead4359 100644 --- a/Sources/track.swift +++ b/Sources/track.swift @@ -1,6 +1,10 @@ -/// Contains the artist and name of a track +// Holds the name and artist of a track class Track { + + // The name of the track to search for let name: String + + // The name of the artist let artist: String init(withName trackName: String, andArtist trackArtist: String) {