diff --git a/Cargo.toml b/Cargo.toml index 1f33756f..8d7acd60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ rustc-serialize = "0.3.16" shaman = "0.1.0" time = "0.1.25" toml = "0.1.20" +curl = "0.2.18" [dependencies.clap] version = "1.4.5" diff --git a/src/main.rs b/src/main.rs index 5d97de9c..a9536ef7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ As such, `cargo-script` does two major things: 2. It caches the generated and compiled packages, regenerating them only if the script or its metadata have changed. */ extern crate clap; +extern crate curl; extern crate env_logger; #[macro_use] extern crate lazy_static; #[macro_use] extern crate log; @@ -41,6 +42,7 @@ mod error; mod manifest; mod platform; mod util; +mod web; use std::borrow::Cow; use std::error::Error; @@ -107,7 +109,7 @@ fn parse_args() -> Args { Major script modes. */ .arg(Arg::with_name("script") - .help("Script file (with or without extension) to execute.") + .help("Script file (with or without extension) or URL to execute.") .index(1) ) .arg(Arg::with_name("args") @@ -311,21 +313,28 @@ fn try_main() -> Result { let input = match (args.script, args.expr, args.loop_) { (Some(script), false, false) => { - let (path, mut file) = try!(find_script(script).ok_or("could not find script")); + if script.starts_with("https://") || script.starts_with("http://") { + // If the script path contains the HTTP or HTTPS protocols, download and execute it as an expression. + content = web::download_script(&script).unwrap(); + Input::Expr(&content) + } else { + // If the script does not define a protocol, it is a file on the local filesystem. + let (path, mut file) = try!(find_script(script).ok_or("could not find script")); - script_name = path.file_stem() - .map(|os| os.to_string_lossy().into_owned()) - .unwrap_or("unknown".into()); + script_name = path.file_stem() + .map(|os| os.to_string_lossy().into_owned()) + .unwrap_or("unknown".into()); - let mut body = String::new(); - try!(file.read_to_string(&mut body)); + let mut body = String::new(); + try!(file.read_to_string(&mut body)); - let mtime = platform::file_last_modified(&file); + let mtime = platform::file_last_modified(&file); - script_path = try!(std::env::current_dir()).join(path); - content = body; + script_path = try!(std::env::current_dir()).join(path); + content = body; - Input::File(&script_name, &script_path, &content, mtime) + Input::File(&script_name, &script_path, &content, mtime) + } }, (Some(expr), true, false) => { content = expr; @@ -1117,7 +1126,7 @@ impl<'a> Input<'a> { /** Shorthand for hashing a string. */ -fn hash_str(s: &str) -> String { +fn hash_str(s: &str) -> String { use shaman::digest::Digest; use shaman::sha1::Sha1; let mut hasher = Sha1::new(); diff --git a/src/web.rs b/src/web.rs new file mode 100644 index 00000000..39c4f17c --- /dev/null +++ b/src/web.rs @@ -0,0 +1,52 @@ +extern crate curl; +use curl::http; + +/// Obtains the raw URL for GitHub and Gist URLs +fn generate_url(url: &str) -> Result { + let mut output = String::from(url); + if output.contains("gist.github.com") { + let content = match get_html_response(url) { + Ok(content) => content, + Err(message) => { return Err(message); } + }; + output = get_raw_gist_url(&content).unwrap(); + } + if output.starts_with("github.com") { + output = output.replace("github.com", "raw.githubusercontent.com"); + } + Ok(output) +} + +/// Obtains the raw Gist URL from the Gist HTML Body +fn get_raw_gist_url(html: &str) -> Option { + for line in html.lines() { + if line.contains("/raw/") { + let suffix = line.split("\"").nth(1).unwrap(); + return Some(String::from("https://gist.githubusercontent.com") + suffix); + } + } + None +} + +/// Returns the HTML body's response. +fn get_html_response(url: &str) -> Result { + let response = match http::handle().get(url).exec() { + Ok(response) => response, + Err(message) => { return Err(message); } + }; + Ok(String::from(match ::std::str::from_utf8(response.get_body()) { + Ok(reply) => reply, + Err(_) => { panic!("response is not a valid UTF8 string"); } + }).replace("fn main()", "")) +} + +/// Opens a URL and returns the source code found at the address. +pub fn download_script(url: &str) -> Result { + match generate_url(&url) { + Ok(url) => match get_html_response(&url) { + Ok(code) => Ok(code), + Err(message) => Err(message) + }, + Err(message) => Err(message) + } +}