Permalink
Browse files

Fetching gist metadata from GitHub

  • Loading branch information...
Xion committed Oct 4, 2016
1 parent 1ab3684 commit 1e54ad05480b42089977171f10d4727beca5f835
Showing with 92 additions and 33 deletions.
  1. +20 −8 src/gist/info.rs
  2. +1 −1 src/gist/mod.rs
  3. +59 −23 src/hosts/github.rs
  4. +12 −1 src/hosts/mod.rs
View
@@ -66,26 +66,38 @@ impl Info {
/// Builder for the gist Info struct.
#[derive(Clone, Debug)]
pub struct Builder {
pub struct InfoBuilder {
data: BTreeMap<Datum, Value>,
}
impl Builder {
impl InfoBuilder {
#[inline]
pub fn new() -> Self {
Builder{data: BTreeMap::new()}
InfoBuilder{data: BTreeMap::new()}
}
#[inline]
pub fn set<V: ?Sized>(mut self, datum: Datum, value: &V) -> Self
pub fn with<V: ?Sized>(mut self, datum: Datum, value: &V) -> Self
where Value: Borrow<V>, V: ToOwned<Owned=Value>
{
self.set(datum, value); self
}
#[inline]
pub fn without(mut self, datum: Datum) -> Self {
self.unset(datum); self
}
#[inline]
pub fn set<V: ?Sized>(&mut self, datum: Datum, value: &V) -> &mut Self
where Value: Borrow<V>, V: ToOwned<Owned=Value>
{
self.data.insert(datum, value.to_owned());
self
}
#[inline]
pub fn unset(mut self, datum: Datum) -> Self {
pub fn unset(&mut self, datum: Datum,) -> &mut Self {
self.data.remove(&datum); self
}
@@ -98,7 +110,7 @@ impl Builder {
#[cfg(test)]
mod tests {
use super::{Datum, Builder};
use super::{Datum, InfoBuilder};
#[test]
fn datum_order_id_always_first() {
@@ -124,7 +136,7 @@ mod tests {
#[test]
fn info_empty() {
let info = Builder::new().build();
let info = InfoBuilder::new().build();
for datum in Datum::iter_variants() {
assert!(!info.has(datum));
assert_eq!(datum.default_value(), *info.get(datum));
@@ -134,7 +146,7 @@ mod tests {
#[test]
fn info_regular() {
let id = String::from("some_id");
let info = Builder::new()
let info = InfoBuilder::new()
.set(Datum::Id, &id)
.set(Datum::Owner, "JohnDoe")
.set(Datum::Description, "Amazing gist")
View
@@ -10,10 +10,10 @@ mod uri;
use std::path::PathBuf;
use super::{BIN_DIR, GISTS_DIR};
pub use self::info::{Datum, Info, InfoBuilder};
pub use self::uri::{Uri, UriError};
/// Structure representing a single gist.
#[derive(Debug, Clone, Eq, Hash)]
pub struct Gist {
View
@@ -18,7 +18,7 @@ use url::Url;
use super::super::USER_AGENT;
use ext::hyper::header::Link;
use gist::{self, Gist};
use gist::{self, Datum, Gist};
use util::{mark_executable, symlink_file};
use super::Host;
@@ -42,17 +42,15 @@ impl Host for GitHub {
/// If the gist hasn't been downloaded already, a clone of the gist's Git repo is performed.
/// Otherwise, it's just a simple Git pull.
fn fetch_gist(&self, gist: &Gist) -> io::Result<()> {
if gist.uri.host_id != "gh" {
return Err(io::Error::new(io::ErrorKind::InvalidData, format!(
"expected a GitHub Gist, but got a '{}' one", gist.uri.host_id)));
}
try!(ensure_github_gist(gist));
let gist = try!(resolve_gist(gist));
if gist.is_local() {
try!(update_gist(gist));
} else {
try!(clone_gist(gist));
}
Ok(())
}
@@ -65,6 +63,29 @@ impl Host for GitHub {
url.set_path(&format!("{}/{}", gist.uri.owner, gist.id.as_ref().unwrap()));
Ok(url.into_string())
}
/// Return a structure with gist metadata.
fn gist_info(&self, gist: &Gist) -> io::Result<Option<gist::Info>> {
try!(ensure_github_gist(gist));
let gist = try!(resolve_gist(gist));
let info = try!(get_gist_info(gist.id.as_ref().unwrap()));
// Build the gist::Info structure from known keys in the gist info JSON.
const INFO_FIELDS: &'static [(Datum, &'static str)] = &[
(Datum::Id, "id"),
(Datum::Description, "description"),
(Datum::Url, "html_url"),
(Datum::CreatedAt, "created_at"),
(Datum::UpdatedAt, "updated_at"),
];
let mut result = gist::InfoBuilder::new();
for &(datum, field) in INFO_FIELDS {
result.set(datum, info[field].as_string().unwrap());
}
result.set(Datum::Owner, info["owner"]["login"].as_string().unwrap());
Ok(Some(result.build()))
}
}
/// Base URL to gist HTML pages.
@@ -158,24 +179,10 @@ fn clone_gist<G: AsRef<Gist>>(gist: G) -> io::Result<()> {
// Talk to GitHub to obtain the URL that we can clone the gist from
// as a Git repository.
let clone_url = {
let http = Client::new();
let gist_url = {
let mut url = Url::parse(API_URL).unwrap();
url.set_path(&format!("gists/{}", gist.id.as_ref().unwrap()));
url.into_string()
};
debug!("Getting GitHub gist info from {}", gist_url);
let mut resp = try!(http.get(&gist_url)
.header(UserAgent(USER_AGENT.clone()))
.send()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)));
let gist_info_json = read_json(&mut resp);
let clone_url = gist_info_json["git_pull_url"].as_string().unwrap().to_owned();
let gist_info = try!(get_gist_info(&gist.id.as_ref().unwrap()));
let clone_url = gist_info["git_pull_url"].as_string().unwrap().to_owned();
trace!("GitHub gist #{} has a git_pull_url=\"{}\"",
gist_info_json["id"].as_string().unwrap(), clone_url);
gist_info["id"].as_string().unwrap(), clone_url);
clone_url
};
@@ -263,8 +270,37 @@ fn list_gists(owner: &str) -> Vec<Gist> {
}
/// Retrieve information/metadata about a gist.
/// Returns a Json object with the parsed GitHub response.
fn get_gist_info(gist_id: &str) -> io::Result<Json> {
let http = Client::new();
let gist_url = {
let mut url = Url::parse(API_URL).unwrap();
url.set_path(&format!("gists/{}", gist_id));
url.into_string()
};
debug!("Getting GitHub gist info from {}", gist_url);
let mut resp = try!(http.get(&gist_url)
.header(UserAgent(USER_AGENT.clone()))
.send()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)));
Ok(read_json(&mut resp))
}
// Utility functions
/// Check if given Gist is a GitHub gist. Invoke using try!().
fn ensure_github_gist(gist: &Gist) -> io::Result<()> {
if gist.uri.host_id != "gh" {
return Err(io::Error::new(io::ErrorKind::InvalidData, format!(
"expected a GitHub Gist, but got a '{}' one", gist.uri.host_id)));
}
Ok(())
}
/// Read HTTP response from hype and parse it as JSON.
fn read_json(response: &mut Response) -> Json {
let mut body = match response.headers.get::<ContentLength>() {
View
@@ -11,7 +11,7 @@ use std::collections::HashMap;
use std::io;
use std::sync::Arc;
use super::gist::Gist;
use super::gist::{self, Gist};
/// Represents a gists' host: a (web) service that hosts gists (code snippets).
@@ -30,6 +30,17 @@ pub trait Host : Send + Sync {
/// Return a URL to a HTML page that can display the gist.
/// This may involving talking to the remote host.
fn gist_url(&self, gist: &Gist) -> io::Result<String>;
/// Return a structure with information/metadata about the gist.
///
/// Note: The return type for this method is io::Result<Option<Info>>
/// rather than Option<io::Result<Info>> because the availability of
/// gist metadata may be gist-specific (i.e. some gists have it,
/// some don't).
fn gist_info(&self, _: &Gist) -> io::Result<Option<gist::Info>> {
// This default indicates the host doesn't expose any gist metadata.
Ok(None)
}
}

0 comments on commit 1e54ad0

Please sign in to comment.