]> git.r.bdr.sh - rbdr/lyricli/commitdiff
Merge branch 'feature/rbdr-document-and-cleanup' into develop
authorBen Beltran <redacted>
Sat, 20 May 2017 14:40:30 +0000 (09:40 -0500)
committerBen Beltran <redacted>
Sat, 20 May 2017 14:40:30 +0000 (09:40 -0500)
18 files changed:
.gitignore
.travis.yml [new file with mode: 0644]
CHANGELOG.md
CONTRIBUTING.md [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
Package.pins [new file with mode: 0644]
README.md
Scripts/install_sourcekitten.sh [new file with mode: 0755]
Scripts/install_swiftlint.sh [new file with mode: 0755]
Sources/arguments_source.swift
Sources/configuration.swift
Sources/lyricli.swift
Sources/lyrics_engine.swift
Sources/main.swift
Sources/source_manager.swift
Sources/source_protocol.swift
Sources/track.swift

index 02c087533d1ecf6e6cd20888338045a340fc9737..2deb6d7f914d7b49b51e7769d71774e044854247 100644 (file)
@@ -2,3 +2,4 @@
 /.build
 /Packages
 /*.xcodeproj
+docs
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..46250e5
--- /dev/null
@@ -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=
index b6712bab2322dc77d98c8bdc0fd02853bc84f94b..810e76503ac3a5f3c3e539f681b6de031439b7df 100644 (file)
@@ -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 (file)
index 0000000..966e0e1
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
index 0000000..4bb7a96
--- /dev/null
@@ -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
index 71d72599319231f4d6a93baee8fc643609a982b6..5e7c9951e48d0f52e7cb60bf13ed5e0140735366 100644 (file)
--- 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] <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
 
-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 (executable)
index 0000000..9420a26
--- /dev/null
@@ -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 (executable)
index 0000000..6a2f250
--- /dev/null
@@ -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
index c07c9522102cb8a125c6dfcd35009f50fad094e9..9615318d4037f63b000a8d6561648daa0a6075a9 100644 (file)
@@ -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 <artist> <name>
+    // 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 <artist> <name>
+            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
     }
 }
index f5e4b0f53b04f52cebf5cc524d247728761b3d4b..f1a1ee1203ac04c5e129d80ff69ab98b96be220a 100644 (file)
@@ -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]
index c44a7f401f4e9d3e20af94f58531764700340e70..e7d14b9d416cbf867b3776007a766dbc5bad9fe9 100644 (file)
@@ -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)")
     }
index d6b1985908ec8eb8af269b1b3ce0487691da61f1..85e4735eaa00a5027f795285a65b46061d89bcf2 100644 (file)
@@ -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'>(.+)<div"
 
+    // The track we'll be looking for
     private let track: Track
 
     // Fetches the lyrics and returns if found
-
     var lyrics: String? {
-        get {
 
-            var lyrics: String?
+        var lyrics: String?
 
-            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)") {
+        // Encode the track artist and name and finish building the API call URL
 
-                        // We'll lock until the async call is finished
+        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)") {
 
-                        var requestFinished = false
-                        let asyncLock = NSCondition()
-                        asyncLock.lock()
+                    // We'll lock until the async call is finished
 
-                        // Call the API and unlock when you're done
+                    var requestFinished = false
+                    let asyncLock = NSCondition()
+                    asyncLock.lock()
 
-                        fetchLyricsFromAPI(withURL: url, completionHandler: {lyricsResult -> 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()
index e4b2760e873888695e8174b42db9c84b77ca82e3..9d46e926fef8b7e1d9bea7721df6405a9b9a9b4f 100644 (file)
@@ -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) [<artist_name> <song_name>]"
+            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()
index dd956ad3ca680418376cc06e1b494d14f511ac3b..5ee1305c9927cb8968ba2aba05b074c19a3c5b17 100644 (file)
@@ -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) {
-    }
 }
index ee9350c1d4cb63ccece7e466361ffa249614a389..0885994d338589b456f5dde7f85b1062c204c844 100644 (file)
@@ -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 }
 }
index d2a9047faa6e4a21bce15b0f15fd3711a312135d..ead43591661ad16939e29cd73fda6d1342418e9d 100644 (file)
@@ -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) {