/.build
/Packages
/*.xcodeproj
+docs
--- /dev/null
+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=
--- /dev/null
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/)
+and this project adheres to [Semantic Versioning](http://semver.org/).
+
+## 0.1.0 - 2017-05-20
+### 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
+
+[Unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/master...develop
--- /dev/null
+# 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
--- /dev/null
+ 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.
--- /dev/null
+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.1.0 \
+ --module Lyricli \
+
+clean:
+ swift build --clean
+
+.PHONY: build install test clean lint
--- /dev/null
+{
+ "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
--- /dev/null
+import PackageDescription
+
+let package = Package(
+ name: "lyricli",
+ dependencies: [
+ .Package(url: "https://github.com/rbdr/CommandLineKit", majorVersion: 4, minor: 0),
+ .Package(url: "https://github.com/IBM-Swift/swift-html-entities.git", majorVersion: 3, minor: 0)
+ ]
+)
-#Lyricli (lrc)
+# 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] <artist_name> <song_name>
+```
+
+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 <source>` enables a source
+* `lrc -d` or `lrc --disable <source>` disables a source
+* `lrc -r` or `lrc --reset-source <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
+
+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
+
+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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+// Source that reads track artist and name from the command line
+class ArgumentsSource: Source {
+
+ // 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? {
+
+ if CommandLine.arguments.count >= 3 {
+ // expected usage: $ ./lyricli <artist> <name>
+ let trackName: String = CommandLine.arguments[2]
+ let trackArtist: String = CommandLine.arguments[1]
+
+ return Track(withName: trackName, andArtist: trackArtist)
+ }
+ return nil
+ }
+}
--- /dev/null
+import Foundation
+
+// 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"]
+ ]
+
+ // The shared instance of the object
+ static let shared: Configuration = Configuration()
+
+ private init() {
+
+ // 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) {
+ if let parsedConfig = parsedConfig as? [String: Any] {
+ for (key, value) in parsedConfig {
+
+ if key == "enabled_sources" {
+ if let value = value as? [String] {
+ configuration[key] = value
+ }
+ } else {
+ if let value = value as? String {
+ configuration[key] = value
+ }
+ }
+ }
+ }
+ }
+ }
+
+ 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)
+ outputStream.close()
+ }
+ }
+
+ // Allow access to the config properties as a dictionary
+ subscript(index: String) -> Any? {
+ get {
+ return configuration[index]
+ }
+
+ set(newValue) {
+ configuration[index] = newValue
+ writeConfiguration()
+ }
+ }
+}
--- /dev/null
+// The main class, handles all the actions that the executable will call
+class Lyricli {
+
+ // Version of the application
+ static var version = "0.1.0"
+
+ // 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 {
+ if showTitle {
+ printTitle(currentTrack)
+ }
+
+ print(lyrics)
+ } else {
+ print("Lyrics not found :(")
+ }
+
+ } else {
+ print("No Artist/Song could be found :(")
+ }
+ }
+
+ // Print the currently available sources
+ static func printSources() {
+ print("Listing Sources: Not yet implemented")
+ }
+
+ // 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")
+ }
+
+ // Remove a source from the enabled sources configuration
+ static func disableSource(_ sourceName: String) {
+ print("Disable source \(sourceName): Not yet implemented")
+ }
+
+ // 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)")
+ }
+}
--- /dev/null
+import Foundation
+import HTMLEntities
+
+// 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'>(.+)<div"
+
+ // The track we'll be looking for
+ private let track: Track
+
+ // Fetches the lyrics and returns if found
+ var lyrics: String? {
+
+ var lyrics: String?
+
+ // Encode the track artist and name and finish building the API call URL
+
+ if let artist = track.artist.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
+ if let name: String = track.name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
+ if let url = URL(string: "\(apiURL)&artist=\(artist)&song=\(name)") {
+
+ // We'll lock until the async call is finished
+
+ var requestFinished = false
+ let asyncLock = NSCondition()
+ asyncLock.lock()
+
+ // Call the API and unlock when you're done
+
+ fetchLyricsFromAPI(withURL: url, completionHandler: {lyricsResult -> Void in
+ if let lyricsResult = lyricsResult {
+ lyrics = lyricsResult
+ requestFinished = true
+ asyncLock.signal()
+ }
+ })
+
+ while !requestFinished {
+ asyncLock.wait()
+ }
+ asyncLock.unlock()
+ }
+ }
+ }
+
+ return lyrics
+ }
+
+ // Initializes with a track
+ init(withTrack targetTrack: Track) {
+
+ track = targetTrack
+ }
+
+ // 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, _, _ -> Void in
+
+ // If the response is parseable JSON, and has a url, we'll look for
+ // the lyrics in there
+
+ if let data = data {
+ 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
+ }
+ }
+ }
+ }
+ }
+
+ completionHandler(nil)
+ })
+ task.resume()
+ }
+
+ // 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, _, _ -> Void in
+
+ // If the response is parseable JSON, and has a url, we'll look for
+ // the lyrics in there
+
+ if let data = data {
+ if let htmlBody = String(data: data, encoding: String.Encoding.utf8) {
+ self.parseHtmlBody(htmlBody, completionHandler: completionHandler)
+ return
+ }
+ }
+
+ completionHandler(nil)
+ })
+ task.resume()
+ }
+
+ // 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))
+
+ for match in matches {
+
+ let nsBody = body as NSString
+ let range = match.rangeAt(1)
+ let encodedLyrics = nsBody.substring(with: range)
+
+ let decodedLyrics = decodeLyrics(encodedLyrics)
+
+ completionHandler(decodedLyrics)
+ return
+ }
+ }
+
+ completionHandler(nil)
+ }
+
+ // Escapes the HTML entities
+ private func decodeLyrics(_ lyrics: String) -> String {
+
+ let unescapedLyrics = lyrics.htmlUnescape()
+ return unescapedLyrics.replacingOccurrences(of: "<br />", with: "\n")
+ }
+}
-print("Hello, world!")
+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
+private func createParser() -> ([String:Option], CommandLineKit) {
+ let parser = CommandLineKit()
+ var flags: [String:Option] = [:]
+
+ flags["help"] = BoolOption(shortFlag: "h", longFlag: "help", helpMessage: "Prints a help message.")
+ flags["version"] = BoolOption(shortFlag: "v", longFlag: "version", helpMessage: "Prints the version.")
+
+ flags["enableSource"] = StringOption(shortFlag: "e", longFlag: "enable-source", helpMessage: "Enables a source")
+ flags["disableSource"] = StringOption(shortFlag: "d", longFlag: "disable-source", helpMessage: "Disables a source")
+ flags["resetSource"] = StringOption(shortFlag: "r", longFlag: "reset-source", helpMessage: "Resets a source")
+ flags["listSources"] = BoolOption(shortFlag: "l", longFlag: "list-sources", helpMessage: "Lists all sources")
+
+ flags["title"] = BoolOption(shortFlag: "t", longFlag: "title", helpMessage: "Shows title of song if true")
+
+ parser.addOptions(Array(flags.values))
+
+ parser.formatOutput = {parseString, type in
+
+ var formattedString: String
+
+ switch type {
+ case .About:
+ formattedString = "\(parseString) [<artist_name> <song_name>]"
+ break
+ default:
+ formattedString = parseString
+ }
+
+ return parser.defaultFormat(formattedString, type: type)
+ }
+
+ 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
+
+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
+
+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
+ }
+ }
+}
+
+// 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)
+ }
+ }
+}
+
+// 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)
+ }
+ }
+}
+
+// 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)
+ }
+ }
+}
+
+main()
--- /dev/null
+// 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? {
+ for source in enabledSources {
+ if let currentTrack = source.currentTrack {
+ return currentTrack
+ }
+ }
+
+ 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
+
+ var sources = [Source]()
+
+ if let sourceNames = Configuration.shared["enabled_sources"] as? [String] {
+ for sourceName in sourceNames {
+ if let source = availableSources[sourceName] {
+ sources.append(source)
+ }
+ }
+ }
+
+ 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) {
+ }
+}
--- /dev/null
+// 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 }
+}
--- /dev/null
+// 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) {
+
+ name = trackName
+ artist = trackArtist
+ }
+}