use std::collections::{BTreeMap, BTreeSet};
use std::fmt::{self, Write};
use {Cons, Custom, Formatter, IntoTokens, Quoted, Tokens};
static SEP: &'static str = ".";
static PATH_SEP: &'static str = "/";
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct JavaScript<'el> {
module: Option<Cons<'el>>,
name: Cons<'el>,
alias: Option<Cons<'el>>,
}
impl<'el> fmt::Display for JavaScript<'el> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref alias) = self.alias {
fmt.write_str(alias.as_ref())?;
fmt.write_str(SEP)?;
}
fmt.write_str(self.name.as_ref())?;
Ok(())
}
}
into_tokens_impl_from!(JavaScript<'el>, JavaScript<'el>);
into_tokens_impl_from!(&'el JavaScript<'el>, JavaScript<'el>);
impl<'el> JavaScript<'el> {
pub fn alias<N: Into<Cons<'el>>>(self, alias: N) -> JavaScript<'el> {
JavaScript {
alias: Some(alias.into()),
..self
}
}
fn module_to_path(path: &str) -> String {
let parts: Vec<&str> = path.split(SEP).collect();
format!("{}.js", parts.join(PATH_SEP))
}
fn imports(tokens: &'el Tokens<'el, Self>) -> Option<Tokens<'el, Self>> {
let mut sets = BTreeMap::new();
let mut wildcard = BTreeSet::new();
for custom in tokens.walk_custom() {
match (&custom.module, &custom.alias) {
(&Some(ref module), &None) => {
sets.entry(module.as_ref())
.or_insert_with(Tokens::new)
.append(custom.name.as_ref());
}
(&Some(ref module), &Some(ref alias)) => {
wildcard.insert((module.as_ref(), alias.as_ref()));
}
_ => {}
}
}
if wildcard.is_empty() {
return None;
}
let mut out = Tokens::new();
for (module, names) in sets {
let mut s = Tokens::new();
s.append("import {");
s.append(names.join(", "));
s.append("} from ");
s.append(Self::module_to_path(module).quoted());
s.append(";");
out.push(s);
}
for (module, alias) in wildcard {
let mut s = Tokens::new();
s.append("import * as ");
s.append(alias);
s.append(" from ");
s.append(Self::module_to_path(module).quoted());
s.append(";");
out.push(s);
}
Some(out)
}
}
impl<'el> Custom for JavaScript<'el> {
type Extra = ();
fn format(&self, out: &mut Formatter, _extra: &mut Self::Extra, _level: usize) -> fmt::Result {
write!(out, "{}", self)
}
fn quote_string(out: &mut Formatter, input: &str) -> fmt::Result {
out.write_char('"')?;
for c in input.chars() {
match c {
'\t' => out.write_str("\\t")?,
'\u{0007}' => out.write_str("\\b")?,
'\n' => out.write_str("\\n")?,
'\r' => out.write_str("\\r")?,
'\u{0014}' => out.write_str("\\f")?,
'\'' => out.write_str("\\'")?,
'"' => out.write_str("\\\"")?,
'\\' => out.write_str("\\\\")?,
c => out.write_char(c)?,
};
}
out.write_char('"')?;
Ok(())
}
fn write_file<'a>(
tokens: Tokens<'a, JavaScript<'a>>,
out: &mut Formatter,
extra: &mut Self::Extra,
level: usize,
) -> fmt::Result {
let mut toks = Tokens::new();
if let Some(imports) = JavaScript::imports(&tokens) {
toks.push(imports);
}
toks.push_ref(&tokens);
toks.join_line_spacing().format(out, extra, level)
}
}
pub fn imported<'el, M, N>(module: M, name: N) -> JavaScript<'el>
where
M: Into<Cons<'el>>,
N: Into<Cons<'el>>,
{
JavaScript {
module: Some(module.into()),
name: name.into(),
alias: None,
}
}
pub fn local<'el, N>(name: N) -> JavaScript<'el>
where
N: Into<Cons<'el>>,
{
JavaScript {
module: None,
name: name.into(),
alias: None,
}
}
#[cfg(test)]
mod tests {
use super::{imported, local, JavaScript};
use quoted::Quoted;
use tokens::Tokens;
#[test]
fn test_function() {
let mut file: Tokens<JavaScript> = Tokens::new();
file.push("function foo(v) {");
file.nested(toks!("return v + ", ", World".quoted(), ";"));
file.push("}");
file.push(toks!("foo(", "Hello".quoted(), ");"));
assert_eq!(
Ok(String::from(
"function foo(v) {\n return v + \", World\";\n}\nfoo(\"Hello\");",
)),
file.to_string()
);
}
#[test]
fn test_string() {
let mut toks: Tokens<JavaScript> = Tokens::new();
toks.append("hello \n world".quoted());
assert_eq!(Ok(String::from("\"hello \\n world\"")), toks.to_string());
}
#[test]
fn test_imported() {
let mut toks: Tokens<JavaScript> = Tokens::new();
toks.push(toks!(imported("collections", "vec").alias("list")));
toks.push(toks!(imported("collections", "vec")));
assert_eq!(
Ok("import {vec} from \"collections.js\";\nimport * as list from \"collections.js\";\n\nlist.vec\nvec\n"),
toks.to_file().as_ref().map(|s| s.as_str())
);
}
#[test]
fn test_local() {
let dbg = local("vec");
let mut toks: Tokens<JavaScript> = Tokens::new();
toks.push(toks!(&dbg));
assert_eq!(Ok("vec\n"), toks.to_file().as_ref().map(|s| s.as_str()));
}
}