Skip to content

Commit acadcfa

Browse files
committed
Add ix.io as gist host
1 parent 2a4cebc commit acadcfa

File tree

4 files changed

+153
-4
lines changed

4 files changed

+153
-4
lines changed

src/args.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ pub fn parse_from_argv<I, T>(argv: I) -> Result<Options, ArgsError>
5555
},
5656
}
5757
} else {
58-
// If help was requested, use the full parser (with subcommands).
59-
// This ensure the correct help/usage instructions are shown.
58+
// If help was requested, use the full parser (with subcommands,
59+
// though make them optional to account for the hack above).
60+
// Al this ensures the correct help/usage instructions are shown.
6061
create_full_parser()
62+
.unset_setting(AppSettings::SubcommandRequiredElseHelp)
6163
}
6264
};
6365

src/hosts/common/basic.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,12 @@ impl Host for Basic {
152152
let url = self.sanitize_url(url);
153153

154154
// Check if it matches the pattern of gist's page URLs.
155+
trace!("Matching sanitized URL {} against the regex: {}",
156+
url, self.html_url_re.as_str());
155157
let captures = match self.html_url_re.captures(&*url) {
156158
Some(c) => c,
157159
None => {
158-
debug!("URL {} doesn't point to a {} gist", self.name, orig_url);
160+
debug!("URL {} doesn't point to a {} gist", orig_url, self.name);
159161
return None;
160162
},
161163
};
@@ -232,7 +234,7 @@ impl Basic {
232234
// Resolving gist URLs.
233235
impl Basic {
234236
fn sanitize_url<'u>(&self, url: &'u str) -> Cow<'u, str> {
235-
let mut url = Cow::Borrowed(url.trim().trim_right_matches("/"));
237+
let mut url = Cow::Borrowed(url.trim());
236238

237239
// Convert between HTTPS and HTTP if necessary.
238240
let (canonical_proto, other_http_proto);

src/hosts/ix_io.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
//! Module implementing ix.io as a gist host.
2+
3+
use regex::Regex;
4+
5+
use hosts::common::Basic;
6+
7+
8+
/// ix.io gist host.
9+
pub const ID: &'static str = "ix";
10+
11+
12+
pub type Ix = internal::Ix<Basic>;
13+
14+
impl Ix {
15+
#[inline]
16+
pub fn new() -> Self {
17+
// Similarly to Hastebin, ix.io URLs may have the language added
18+
// to the URL. We will strip it before handing over to the Basic host,
19+
// and then re-add it when generating HTML URL.
20+
let inner = Basic::new(ID, "ix.io",
21+
"http://ix.io/${id}",
22+
"http://ix.io/${id}/", // Yes, just a slash.
23+
Regex::new("[0-9a-z]+").unwrap()).unwrap();
24+
internal::Ix{inner: inner}
25+
}
26+
}
27+
28+
29+
mod internal {
30+
use std::borrow::Cow;
31+
use std::io;
32+
33+
use url::{self, Url};
34+
35+
use gist::{self, Datum, Gist};
36+
use hosts::{FetchMode, Host};
37+
38+
/// Actual implementation type for ix.io,
39+
/// taking a generic parameter so it can be substituted in tests.
40+
pub struct Ix<T: Host> {
41+
pub inner: T,
42+
}
43+
44+
impl<T: Host> Host for Ix<T> {
45+
fn id(&self) -> &'static str { self.inner.id() }
46+
fn name(&self) -> &str { self.inner.name() }
47+
48+
fn fetch_gist(&self, gist: &Gist, mode: FetchMode) -> io::Result<()> {
49+
self.inner.fetch_gist(gist, mode)
50+
}
51+
52+
/// Return the URL to given ix.io gist.
53+
fn gist_url(&self, gist: &Gist) -> io::Result<String> {
54+
let mut url = try!(self.inner.gist_url(gist));
55+
56+
// Add language to the URL.
57+
if let Some(ref lang) = gist.info(gist::Datum::Language) {
58+
url = format!("{}/{}", url.trim_right_matches("/"), lang);
59+
}
60+
Ok(url)
61+
}
62+
63+
fn gist_info(&self, gist: &Gist) -> io::Result<Option<gist::Info>> {
64+
self.inner.gist_info(gist)
65+
}
66+
67+
/// Resolve given URL as potentially pointing to a sprunge.us gist.
68+
fn resolve_url(&self, url: &str) -> Option<io::Result<Gist>> {
69+
let url_obj = try_opt!(Url::parse(url).ok());
70+
71+
// Check the correct domain manually first,
72+
// as we'll be doing some additional processing.
73+
if url_obj.host() != Some(url::Host::Domain("ix.io")) {
74+
debug!("URL {} doesn't point to an ix.io gist", url);
75+
return None;
76+
}
77+
78+
// According to ix.io homepage, there are two ways the URL can
79+
// contain language information:
80+
// * http://ix.io/$ID/$LANG
81+
// * http://ix.io/$ID+$LANG
82+
// We need to count the number of path segments to see which case
83+
// it is (if any).
84+
let mut url: Cow<str> = url.into();
85+
let path_segments: Vec<_> = url_obj.path_segments()
86+
.map(|ps| ps.collect())
87+
.unwrap_or_else(Vec::new);
88+
let ps_count = path_segments.len();
89+
90+
// Determine the language from the path segment pattern,
91+
// and remove it from the URL for resolving.
92+
let lang: Option<&str> = match ps_count {
93+
1 => {
94+
let id_parts: Vec<_> = path_segments[0].splitn(2, "-").collect();
95+
if id_parts.len() == 2 {
96+
trace!("Treating the URL as http://ix.io/$ID-$LANG");
97+
let lang = id_parts[1];
98+
// Trim the language part but ensure trailing slash.
99+
url = format!("{}/",
100+
url.trim_right_matches("/")
101+
.trim_right_matches(lang).trim_right_matches("-"))
102+
.into();
103+
Some(lang)
104+
}
105+
else {
106+
trace!("Treating the URL as http://ix.io/$ID");
107+
None
108+
}
109+
}
110+
2 => {
111+
trace!("Treating the URL as http://ix.io/$ID/$LANG");
112+
let lang = path_segments[1];
113+
// Trim the language path segment, but ensure trailing slash.
114+
url = format!("{}/",
115+
url.trim_right_matches("/").trim_right_matches(lang)
116+
.trim_right_matches("/"))
117+
.into();
118+
Some(lang)
119+
}
120+
_ => {
121+
warn!("Spurious format of ix.io URL: {}", url);
122+
None
123+
}
124+
};
125+
126+
// Resolve the URL using the wrapped method
127+
// and include the language in gist info.
128+
trace!("Resolving ix.io URL: {}", url);
129+
let mut gist = match self.inner.resolve_url(&*url) {
130+
Some(Ok(gist)) => gist,
131+
other => return other,
132+
};
133+
if let Some(lang) = lang {
134+
trace!("Adding language to ix.io gist: {}", lang);
135+
let info_builder = gist.info.clone()
136+
.map(|i| i.to_builder()).unwrap_or_else(gist::InfoBuilder::new);
137+
gist.info = Some(info_builder.with(Datum::Language, lang).build());
138+
}
139+
140+
Some(Ok(gist))
141+
}
142+
}
143+
}

src/hosts/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod bpaste;
1111
mod dpaste_de;
1212
mod hastebin;
1313
mod heypasteit;
14+
mod ix_io;
1415
mod lpaste;
1516
mod mibpaste;
1617
mod mozilla;
@@ -110,6 +111,7 @@ lazy_static! {
110111
sprunge::ID => Arc::new(sprunge::Sprunge::new()) as Arc<Host>,
111112
dpaste_de::ID => Arc::new(dpaste_de::create()) as Arc<Host>,
112113
thepasteb_in::ID => Arc::new(thepasteb_in::create()) as Arc<Host>,
114+
ix_io::ID => Arc::new(ix_io::Ix::new()) as Arc<Host>,
113115
};
114116
}
115117
#[cfg(not(test))]

0 commit comments

Comments
 (0)