Merge branch 'develop'
diff --git a/Cargo.toml b/Cargo.toml
index c8a21ce..45bc374 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "tuf"
-version = "0.1.0"
+version = "0.1.1"
 authors = [ "heartsucker <heartsucker@autistici.org>" ]
 description = "Library for The Update Framework (TUF)"
 homepage = "https://github.com/heartsucker/rust-tuf"
diff --git a/README.md b/README.md
index a3ed744..b87f132 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,14 @@
 # rust-tuf
 
-A rust implementation of The Update Framework (TUF).
+[![Travis build Status](https://travis-ci.org/heartsucker/rust-tuf.svg?branch=master)](https://travis-ci.org/heartsucker/rust-tuf) [![Appveyor build status](https://ci.appveyor.com/api/projects/status/kfyvpkdvn5ap7dqc?svg=true)](https://ci.appveyor.com/project/heartsucker/rust-tuf)
 
-## Warning: Alpha Software
+A Rust implementation of [The Update Framework (TUF)](https://theupdateframework.github.io/).
 
-This is under active development and is not suitable for production use. Further,
+Full documentation is hosted at [docs.rs](https://docs.rs/crate/tuf).
+
+## Warning: Beta Software
+
+This is under active development and may not suitable for production use. Further,
 the API is unstable and you should be prepared to refactor on even patch releases.
 
 ## Contributing
diff --git a/src/lib.rs b/src/lib.rs
index 990ce2a..2b2f218 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -19,7 +19,7 @@
 mod metadata;
 mod error;
 mod tuf;
-pub mod util;
+mod util;
 
 pub use tuf::*;
 pub use error::*;
diff --git a/src/main.rs b/src/main.rs
index 3e739f9..695546e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,9 +7,9 @@
 extern crate tuf as _tuf;
 extern crate url;
 
-use clap::{App, AppSettings, SubCommand, Arg, ArgMatches};
+use clap::{App, AppSettings, SubCommand, Arg, ArgMatches, ArgGroup};
 use std::path::PathBuf;
-use _tuf::{Tuf, Config, Error};
+use _tuf::{Tuf, Config, Error, RemoteRepo};
 use url::Url;
 
 // TODO logging
@@ -28,7 +28,10 @@
 }
 
 fn run_main(matches: ArgMatches) -> Result<(), Error> {
-    let config = Config::build().url(Url::parse(matches.value_of("url").unwrap())?)
+    let remote = matches.value_of("url").map(|u| RemoteRepo::Http(Url::parse(u).unwrap()))
+        .or_else(|| matches.value_of("file").map(|p| RemoteRepo::File(PathBuf::from(p))))
+        .unwrap();
+    let config = Config::build().remote(remote)
         .local_path(PathBuf::from(matches.value_of("path").unwrap()))
         .finish()?;
 
@@ -69,15 +72,22 @@
             .short("U")
             .long("url")
             .takes_value(true)
-            .required(true)
             .validator(url_validator)
-            .help("URL of the TUF repo (local or remote)"))
+            .help("URL of the TUF repo"))
+        .arg(Arg::with_name("file")
+            .short("f")
+            .long("file")
+            .takes_value(true)
+            .help("Path to the TUF repo (remote)"))
         .arg(Arg::with_name("path")
             .short("p")
             .long("path")
             .takes_value(true)
             .required(true)
             .help("Local path the TUF repo"))
+        .group(ArgGroup::with_name("remote_repo")
+               .args(&["url", "file"])
+               .required(true))
         .subcommand(SubCommand::with_name("init").about("Initializes a new TUF repo"))
         .subcommand(SubCommand::with_name("list").about("Lists available targets"))
         .subcommand(SubCommand::with_name("update").about("Updates metadata from remotes"))
@@ -119,7 +129,6 @@
     use std::fs::{self, DirBuilder};
     use std::path::{Path, PathBuf};
     use tempdir::TempDir;
-    use _tuf::util;
 
     fn vector_path() -> PathBuf {
         PathBuf::from(env!("CARGO_MANIFEST_DIR"))
@@ -152,14 +161,14 @@
     fn run_it() {
         let temp = TempDir::new("rust-tuf").expect("couldn't make temp dir");
         init_temp(temp.path());
-        let url = util::path_to_url(&vector_path()).expect("bad path");
-        println!("Test path: {:?}", temp.path());
-        println!("Test URL: {:?}", url);
+        let path = vector_path();
+        println!("Temp path: {:?}", temp.path());
+        println!("Test path: {:?}", path);
 
         let matches = parser()
             .get_matches_from_safe(vec!["tuf",
-                                        "--url",
-                                        &url.to_string(),
+                                        "--file",
+                                        &path.to_string_lossy(),
                                         "--path",
                                         temp.path().to_str().expect("path not utf-8"),
                                         "init"])
@@ -168,8 +177,8 @@
 
         let matches = parser()
             .get_matches_from_safe(vec!["tuf",
-                                        "--url",
-                                        &url.to_string(),
+                                        "--file",
+                                        &path.to_string_lossy(),
                                         "--path",
                                         temp.path().to_str().expect("path not utf-8"),
                                         "update"])
@@ -178,8 +187,8 @@
 
         let matches = parser()
             .get_matches_from_safe(vec!["tuf",
-                                        "--url",
-                                        &url.to_string(),
+                                        "--file",
+                                        &path.to_string_lossy(),
                                         "--path",
                                         temp.path().to_str().expect("path not utf-8"),
                                         "verify",
diff --git a/src/tuf.rs b/src/tuf.rs
index 0b1e9f2..336688e 100644
--- a/src/tuf.rs
+++ b/src/tuf.rs
@@ -19,10 +19,26 @@
 use util;
 
 
+#[derive(Debug)]
+pub enum RemoteRepo {
+    File(PathBuf),
+    Http(Url),
+}
+
+impl RemoteRepo {
+    fn as_fetch(&self) -> FetchType {
+        match self {
+            &RemoteRepo::File(ref path) => FetchType::File(path.clone()),
+            &RemoteRepo::Http(ref url) => FetchType::Http(url.clone()),
+        }
+    }
+}
+
+
 /// Interface for interacting with TUF repositories.
 #[derive(Debug)]
 pub struct Tuf {
-    url: Url,
+    remote: RemoteRepo,
     local_path: PathBuf,
     http_client: Client,
     root: RootMetadata,
@@ -50,11 +66,7 @@
                 }
                 Err(e) => {
                     debug!("Failed to read root locally: {:?}", e);
-                    let fetch_type = &match config.url.scheme() {
-                        "file" => FetchType::File(util::url_path_to_os_path(config.url.path())?),
-                        "http" | "https" => FetchType::Http(util::url_to_hyper_url(&config.url)?),
-                        x => return Err(Error::Generic(format!("Unsupported URL scheme: {}", x))),
-                    };
+                    let fetch_type = &config.remote.as_fetch();
                     let modified_root =
                         Self::read_root_with_keys(fetch_type, &config.http_client, &root_keys)?;
                     Self::get_meta_num::<Root, RootMetadata, File>(fetch_type,
@@ -67,7 +79,7 @@
         };
 
         let mut tuf = Tuf {
-            url: config.url,
+            remote: config.remote,
             local_path: config.local_path,
             http_client: config.http_client,
             root: root,
@@ -96,7 +108,7 @@
         };
 
         let mut tuf = Tuf {
-            url: config.url,
+            remote: config.remote,
             local_path: config.local_path,
             http_client: config.http_client,
             root: root,
@@ -152,18 +164,7 @@
 
     fn update_remote(&mut self) -> Result<(), Error> {
         debug!("Updating metadata from remote sources");
-        let fetch_type = &match self.url.scheme() {
-            "file" => FetchType::File(util::url_path_to_os_path(self.url.path())?),
-            "http" | "https" => FetchType::Http(util::url_to_hyper_url(&self.url)?),
-            x => {
-                let msg = format!("Programming error: unsupported URL scheme {}. Please report \
-                                   this as a bug.",
-                                  x);
-                error!("{}", msg);
-                return Err(Error::Generic(msg));
-            }
-        };
-
+        let fetch_type = &self.remote.as_fetch();
         self.update_root(fetch_type)?;
 
         if self.update_timestamp(fetch_type)? && self.update_snapshot(fetch_type)? {
@@ -826,16 +827,10 @@
         } else {
             let (out, out_path) = self.temp_file()?;
 
-            match self.url.scheme() {
-                "file" => {
-                    let mut url = self.url.clone();
-                    {
-                        url.path_segments_mut()
-                            .map_err(|_| Error::Generic("Path could not be mutated".to_string()))?
-                            .extend(util::url_path_to_os_path_components(target)?);
-                    }
-
-                    let path = util::url_path_to_os_path(url.path())?;
+            match self.remote {
+                RemoteRepo::File(ref path) => {
+                    let mut path = path.clone();
+                    path.extend(util::url_path_to_path_components(target)?);
                     let mut file = File::open(path.clone()).map_err(|e| Error::from_io(e, &path))?;
 
                     match Self::read_and_verify(&mut file,
@@ -845,7 +840,7 @@
                         Ok(()) => {
                             // TODO ensure intermediate directories exist
                             let mut storage_path = self.local_path.join("targets");
-                            storage_path.extend(util::url_path_to_os_path_components(target)?);
+                            storage_path.extend(util::url_path_to_path_components(target)?);
 
                             {
                                 let parent = storage_path.parent()
@@ -870,8 +865,14 @@
                         }
                     }
                 }
-                "http" | "https" => {
-                    let url = util::url_to_hyper_url(&self.url.join(target)?)?;
+                RemoteRepo::Http(ref url) => {
+                    let mut url = url.clone();
+                    {
+                        url.path_segments_mut()
+                            .map_err(|_| Error::Generic("URL path could not be mutated".to_string()))?
+                            .extend(util::url_path_to_path_components(&target)?);
+                    }
+                    let url = util::url_to_hyper_url(&url)?;
                     let mut resp = self.http_client.get(url).send()?;
 
                     match Self::read_and_verify(&mut resp,
@@ -882,7 +883,7 @@
                             // TODO this isn't windows friendly
                             // TODO ensure intermediate directories exist
                             let mut storage_path = self.local_path.join("targets");
-                            storage_path.extend(util::url_path_to_os_path_components(target)?);
+                            storage_path.extend(util::url_path_to_path_components(target)?);
 
                             {
                                 let parent = storage_path.parent()
@@ -908,7 +909,6 @@
                         }
                     }
                 }
-                x => Err(Error::Generic(format!("Unsupported URL scheme: {}", x))),
             }
         }
     }
@@ -989,9 +989,9 @@
 
 /// The configuration used to initialize a `Tuf` struct.
 pub struct Config {
-    url: Url,
+    remote: RemoteRepo,
     local_path: PathBuf,
-    http_client: Client,
+    http_client: Client, 
     // TODO add `init: bool` to specify whether or not to create dir structure
 }
 
@@ -1005,7 +1005,7 @@
 
 /// Helper that constructs `Config`s and verifies the options.
 pub struct ConfigBuilder {
-    url: Option<Url>,
+    remote: Option<RemoteRepo>,
     local_path: Option<PathBuf>,
     http_client: Option<Client>,
 }
@@ -1014,15 +1014,15 @@
     /// Create a new builder with the default configurations where applicable.
     pub fn new() -> Self {
         ConfigBuilder {
-            url: None,
+            remote: None,
             local_path: None,
             http_client: None,
         }
     }
 
-    /// The remote URL of the TUF repo.
-    pub fn url(mut self, url: Url) -> Self {
-        self.url = Some(url);
+    /// The remote TUF repo.
+    pub fn remote(mut self, remote: RemoteRepo) -> Self {
+        self.remote = Some(remote);
         self
     }
 
@@ -1040,19 +1040,14 @@
 
     /// Verify the configuration.
     pub fn finish(self) -> Result<Config, Error> {
-        let url = self.url
-            .ok_or_else(|| Error::InvalidConfig("Repository URL was not set".to_string()))?;
-
-        match url.scheme() {
-            "file" | "http" | "https" => (),
-            x => return Err(Error::InvalidConfig(format!("Unsupported URL scheme: {}", x))),
-        };
+        let remote = self.remote
+            .ok_or_else(|| Error::InvalidConfig("Remote repository was not set".to_string()))?;
 
         let local_path = self.local_path
             .ok_or_else(|| Error::InvalidConfig("Local path was not set".to_string()))?;
 
         Ok(Config {
-            url: url,
+            remote: remote,
             local_path: local_path,
             http_client: self.http_client.unwrap_or_else(|| Client::new()),
         })
diff --git a/src/util.rs b/src/util.rs
index 77c3945..67d561d 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,44 +1,10 @@
-//! Intended for internal use only, but made public for testing. This SHOULD NOT be used or relied
-//! upon.
-
 use hyper;
-use std::path::{Path, PathBuf, Component};
+use std::path::{Path, PathBuf};
 use url::Url;
-use url::percent_encoding::{percent_encode, percent_decode, DEFAULT_ENCODE_SET};
+use url::percent_encoding::percent_decode;
 
 use error::Error;
 
-// TODO I'm pretty sure all of this path stuff is a mess, but at least the basics of
-// windows will work, right? Right?
-
-/// Converts a `Path` into a URL with scheme `file://`.
-pub fn path_to_url(path: &Path) -> Result<Url, Error> {
-    // TODO handle sloppy to_string_lossy() calls
-
-    let path_str = path.components()
-        .fold(PathBuf::new(), |buf, c| match c {
-            Component::Normal(os_str) => {
-                buf.join(format!("{}",
-                                 percent_encode(os_str.to_string_lossy().as_bytes(),
-                                                DEFAULT_ENCODE_SET)))
-            }
-            Component::RootDir => buf.join("/"),
-            Component::Prefix(pref) => {
-                if cfg!(windows) {
-                    buf.join("/").join(pref.as_os_str().to_string_lossy().into_owned())
-                } else {
-                    buf
-                }
-            }
-            Component::CurDir => buf.join("."),
-            Component::ParentDir => buf.join(".."),
-        });
-
-    Url::parse(&format!("file://{}",
-                        percent_encode(path_str.to_string_lossy().as_bytes(), DEFAULT_ENCODE_SET)))
-        .map_err(|e| Error::Generic(format!("{}", e)))
-}
-
 /// Converts a URL string (without scheme) into an OS specific path.
 pub fn url_path_to_os_path(url_path: &str) -> Result<PathBuf, Error> {
     let url_path = if cfg!(os = "windows") {
@@ -55,13 +21,12 @@
     Ok(Path::new(&url_path).to_path_buf())
 }
 
-pub fn url_path_to_os_path_components(url_path: &str) -> Result<Vec<String>, Error> {
+pub fn url_path_to_path_components(url_path: &str) -> Result<Vec<String>, Error> {
     let mut out = Vec::new();
     for component in url_path.split("/") {
         let component = percent_decode(component.as_bytes())
             .decode_utf8()
-            .map_err(|e| Error::Generic(format!("Path component not utf-8: {:?}", e)))
-            ?
+            .map_err(|e| Error::Generic(format!("Path component not utf-8: {:?}", e)))?
             .into_owned();
         out.push(component);
     }
@@ -79,38 +44,6 @@
 
     #[test]
     #[cfg(not(target_os = "windows"))]
-    fn test_path_to_url_nix() {
-        let path = Path::new("/tmp/test");
-        assert_eq!(path_to_url(path),
-                   Ok(Url::parse("file:///tmp/test").unwrap()));
-    }
-
-    #[test]
-    #[cfg(not(target_os = "windows"))]
-    fn test_path_to_url_spaces_nix() {
-        let path = Path::new("/tmp/test stuff");
-        assert_eq!(path_to_url(path),
-                   Ok(Url::parse("file:///tmp/test%20stuff").unwrap()));
-    }
-
-    #[test]
-    #[cfg(target_os = "windows")]
-    fn test_path_to_url_win() {
-        let path = Path::new(r"C:\tmp\test");
-        assert_eq!(path_to_url(path),
-                   Ok(Url::parse("file:///C:/tmp/test").unwrap()));
-    }
-
-    #[test]
-    #[cfg(target_os = "windows")]
-    fn test_path_to_url_spaces_win() {
-        let path = Path::new(r"C:\tmp\test stuff");
-        assert_eq!(path_to_url(path),
-                   Ok(Url::parse("file:///C:/tmp/test%20stuff").unwrap()));
-    }
-
-    #[test]
-    #[cfg(not(target_os = "windows"))]
     fn test_url_path_to_os_path_nix() {
         let path = "/tmp/test";
         assert_eq!(url_path_to_os_path(path), Ok(PathBuf::from("/tmp/test")));
@@ -140,11 +73,13 @@
     }
 
     #[test]
-    fn test_url_path_to_os_path_components() {
+    fn test_url_path_to_path_components() {
         let path = "test/foo";
-        assert_eq!(url_path_to_os_path_components(path), Ok(vec!["test".into(), "foo".into()]));
+        assert_eq!(url_path_to_path_components(path),
+                   Ok(vec!["test".into(), "foo".into()]));
 
         let path = "test/foo%20bar";
-        assert_eq!(url_path_to_os_path_components(path), Ok(vec!["test".into(), "foo bar".into()]));
+        assert_eq!(url_path_to_path_components(path),
+                   Ok(vec!["test".into(), "foo bar".into()]));
     }
 }
diff --git a/tests/vectors.rs b/tests/vectors.rs
index 1c8ab9c..4766dcd 100644
--- a/tests/vectors.rs
+++ b/tests/vectors.rs
@@ -11,9 +11,8 @@
 use std::io::Read;
 use std::path::PathBuf;
 use tempdir::TempDir;
-use tuf::{Tuf, Config, Error};
+use tuf::{Tuf, Config, Error, RemoteRepo};
 use tuf::meta::{Key, KeyValue, KeyType};
-use tuf::util;
 
 
 fn load_vector_meta() -> String {
@@ -56,7 +55,8 @@
     let vector_meta: VectorMeta = json::from_str(&load_vector_meta())
         .expect("couldn't deserializd meta");
 
-    let test_vector = vector_meta.vectors.iter()
+    let test_vector = vector_meta.vectors
+        .iter()
         .filter(|v| v.repo == test_path)
         .collect::<Vec<&VectorMetaEntry>>()
         .pop()
@@ -94,7 +94,7 @@
         .collect();
 
     let config = Config::build()
-        .url(util::path_to_url(&vector_path.join("repo")).expect("couldn't make url"))
+        .remote(RemoteRepo::File(vector_path.join("repo")))
         .local_path(temp_path.clone())
         .finish()
         .expect("bad config");