From ef1c86df18559f2cc452298f1e6609f533d91124 Mon Sep 17 00:00:00 2001 From: Simon Persson Date: Sun, 19 Jul 2015 20:36:14 +0000 Subject: [PATCH] Make command line argument parsing more robust. --- Cargo.lock | 42 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ README.md | 9 ++++----- src/main.rs | 49 ++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 90 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bcfda472..5f42547d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,7 @@ name = "librespot" version = "0.1.0" dependencies = [ "byteorder 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "librespot-protocol 0.1.0", "mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -12,6 +13,7 @@ dependencies = [ "protobuf_macros 0.1.0 (git+https://github.com/plietar/rust-protobuf-macros.git)", "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "readall 0.1.0 (git+https://github.com/plietar/rust-readall.git)", + "rpassword 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", "rust-gmp 0.2.0 (git+https://github.com/plietar/rust-gmp.git)", "shannon 0.1.0 (git+https://github.com/plietar/rust-shannon.git)", @@ -41,6 +43,14 @@ name = "gcc" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "getopts" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.1.2" @@ -68,6 +78,14 @@ dependencies = [ "protobuf 1.0.1 (git+https://github.com/plietar/rust-protobuf.git)", ] +[[package]] +name = "log" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mod_path" version = "0.1.5" @@ -139,6 +157,17 @@ name = "readall" version = "0.1.0" source = "git+https://github.com/plietar/rust-readall.git#d2bcc1de325705230e79ba444cde2f39b469f891" +[[package]] +name = "rpassword" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "termios 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rust-crypto" version = "0.2.31" @@ -193,6 +222,14 @@ dependencies = [ "winapi 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "termios" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.30" @@ -254,6 +291,11 @@ dependencies = [ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "winapi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi-build" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 186139bc..92aeb381 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,8 @@ lazy_static = "0.1.*" rust-crypto = "*" time = "*" tempfile = "*" +rpassword = "*" +getopts = "0.2.4" [dependencies.protobuf] git = "https://github.com/plietar/rust-protobuf.git" diff --git a/README.md b/README.md index e519e4a3..e8b699b5 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,11 @@ cargo build A sample program implementing a headless Spotify Connect receiver is provided. Once you've built *librespot*, run it using : ```shell -target/debug/main APPKEY USERNAME PASSWORD CACHEDIR DEVICENAME +target/debug/main -a APPKEY -u USERNAME -c CACHEDIR -d DEVICENAME ``` -where `APPKEY` is the path to a Spotify application key file, `USERNAME` and -`PASSWORD` are your Spotify credentials, `CACHEDIR` is the path to directory -where data will be cached, and `DEVICENAME` is the name that will appear in the -Spotify Connect menu. +where `APPKEY` is the path to a Spotify application key file, `USERNAME` is your +Spotify username, `CACHEDIR` is the path to directory where data will be cached, +and `DEVICENAME` is the name that will appear in the Spotify Connect menu. ## Disclaimer Using this code to connect to Spotify's API is probably forbidden by them, and diff --git a/src/main.rs b/src/main.rs index 638f6e8f..9284f4fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,62 @@ #![feature(scoped)] +#![feature(result_expect)] #![allow(deprecated)] +extern crate getopts; extern crate librespot; +extern crate rpassword; use std::clone::Clone; use std::fs::File; -use std::io::Read; +use std::io::{stdout, Read, Write}; use std::path::Path; use std::thread; use std::path::PathBuf; +use getopts::Options; +use rpassword::read_password; + use librespot::session::{Config, Session}; use librespot::util::version::version_string; use librespot::player::Player; use librespot::spirc::SpircManager; +fn usage(program: &str, opts: &Options) -> String { + let brief = format!("Usage: {} [options]", program); + format!("{}", opts.usage(&brief)) +} + fn main() { - let mut args = std::env::args().skip(1); - let mut appkey_file = File::open(Path::new(&args.next().unwrap())).unwrap(); - let username = args.next().unwrap(); - let password = args.next().unwrap(); - let cache_location = args.next().unwrap(); - let name = args.next().unwrap(); + let args: Vec = std::env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.reqopt("a", "appkey", "Path to a spotify appkey", "APPKEY"); + opts.reqopt("u", "username", "Username to sign in with", "USERNAME"); + opts.optopt("p", "password", "Password (optional)", "PASSWORD"); + opts.reqopt("c", "cache", "Path to a directory where files will be cached.", "CACHE"); + opts.reqopt("n", "name", "Device name", "NAME"); + let matches = match opts.parse(&args[1..]) { + Ok(m) => { m }, + Err(f) => { + print!("Error: {}\n{}", f.to_string(), usage(&*program, &opts)); + return; + } + }; + + let mut appkey_file = File::open( + Path::new(&*matches.opt_str("a").unwrap()) + ).expect("Could not open app key."); + + let username = matches.opt_str("u").unwrap(); + let cache_location = matches.opt_str("c").unwrap(); + let name = matches.opt_str("n").unwrap(); + + let password = matches.opt_str("p").unwrap_or_else(|| { + print!("Password: "); + stdout().flush().unwrap(); + read_password().unwrap() + }); let mut appkey = Vec::new(); appkey_file.read_to_end(&mut appkey).unwrap();