extern crate base64;
#[macro_use]
extern crate lazy_static;
extern crate md5;
#[macro_use]
extern crate nom;
#[cfg(feature = "sass")]
extern crate rsass;
pub mod How_to_use_ructe;
mod spacelike;
#[macro_use]
mod errors;
mod expression;
#[macro_use]
mod templateexpression;
mod template;
pub mod Template_syntax;
pub mod Using_static_files;
use errors::get_error;
use nom::{prepare_errors, ErrorKind};
use nom::IResult::*;
use std::collections::BTreeMap;
use std::fs::{create_dir_all, read_dir, File};
use std::io::{self, Read, Write};
use std::path::Path;
use std::str::from_utf8;
use template::template;
pub fn compile_static_files(indir: &Path, outdir: &Path) -> io::Result<()> {
let mut out = StaticFiles::new(outdir)?;
out.add_files(indir)
}
pub struct StaticFiles {
src: File,
names: BTreeMap<String, String>,
names_r: BTreeMap<String, String>,
}
impl StaticFiles {
pub fn new(outdir: &Path) -> io::Result<Self> {
let outdir = outdir.join("templates");
create_dir_all(&outdir)?;
let mut src = File::create(outdir.join("statics.rs"))?;
if cfg!(feature = "mime03") {
write!(src, "extern crate mime;\nuse self::mime::Mime;\n\n")?;
}
write!(
src,
"/// A static file has a name (so its url can be recognized) \
and the\n\
/// actual file contents.\n\
///\n\
/// The name includes a short (48 bits as 8 base64 characters) \
hash of\n\
/// the content, to enable long-time caching of static \
resourses in\n\
/// the clients.\n\
#[allow(dead_code)]\n\
pub struct StaticFile {{\n \
pub content: &'static [u8],\n \
pub name: &'static str,\n"
)?;
if cfg!(feature = "mime02") {
write!(src, " _mime: &'static str,\n")?;
}
if cfg!(feature = "mime03") {
write!(src, " pub mime: &'static Mime,\n")?;
}
write!(
src,
"}}\n\n\
#[allow(dead_code)]\n\
impl StaticFile {{\n \
/// Get a single `StaticFile` by name, if it exists.\n \
pub fn get(name: &str) -> Option<&'static Self> {{\n \
if let Ok(pos) = STATICS.\
binary_search_by_key(&name, |s| s.name) {{\n \
return Some(STATICS[pos]);\n \
}} else {{\n \
None\n \
}}\n \
}}\n\
}}\n"
)?;
if cfg!(feature = "mime02") {
write!(
src,
"extern crate mime;\n\
use self::mime::Mime;\n\n\
impl StaticFile {{\n \
/// Get the mime type of this static file.\n \
///\n \
/// Currently, this method parses a (static) string every \
time.\n \
/// A future release of `mime` may support statically \
created\n \
/// `Mime` structs, which will make this nicer.\n \
#[allow(unused)]\n \
pub fn mime(&self) -> Mime {{\n \
self._mime.parse().unwrap()\n \
}}\n\
}}\n"
)?;
}
Ok(StaticFiles {
src: src,
names: BTreeMap::new(),
names_r: BTreeMap::new(),
})
}
pub fn add_files(&mut self, indir: &Path) -> io::Result<()> {
for entry in read_dir(indir)? {
let entry = entry?;
if entry.file_type()?.is_file() {
self.add_file(&entry.path())?;
}
}
Ok(())
}
pub fn add_files_as(&mut self, indir: &Path, to: &str) -> io::Result<()> {
for entry in read_dir(indir)? {
let entry = entry?;
let file_type = entry.file_type()?;
let to = format!("{}/{}", to, entry.file_name().to_string_lossy());
if file_type.is_file() {
self.add_file_as(&entry.path(), &to)?;
} else if file_type.is_dir() {
self.add_files_as(&entry.path(), &to)?;
}
}
Ok(())
}
pub fn add_file(&mut self, path: &Path) -> io::Result<()> {
if let Some((name, ext)) = name_and_ext(path) {
println!("cargo:rerun-if-changed={}", path.to_string_lossy());
let mut input = File::open(&path)?;
let mut buf = Vec::new();
input.read_to_end(&mut buf)?;
let from_name = format!("{}_{}", name, ext);
let to_name = format!("{}-{}.{}", name, checksum_slug(&buf), &ext);
self.write_static_file(path, name, &buf, ext)?;
self.names.insert(from_name.clone(), to_name.clone());
self.names_r.insert(to_name, from_name.clone());
}
Ok(())
}
pub fn add_file_as(
&mut self,
path: &Path,
to_name: &str,
) -> io::Result<()> {
if let Some((_name, ext)) = name_and_ext(path) {
println!("cargo:rerun-if-changed={}", path.to_string_lossy());
let mut input = File::open(&path)?;
let mut buf = Vec::new();
input.read_to_end(&mut buf)?;
let from_name = to_name
.replace("/", "_")
.replace("-", "_")
.replace(".", "_");
self.write_static_file2(path, &from_name, to_name, ext)?;
self.names.insert(from_name.clone(), to_name.to_string());
self.names_r.insert(to_name.to_string(), from_name.clone());
}
Ok(())
}
pub fn add_file_data(
&mut self,
path: &Path,
data: &[u8],
) -> io::Result<()> {
if let Some((name, ext)) = name_and_ext(path) {
let from_name = format!("{}_{}", name, ext);
let to_name = format!("{}-{}.{}", name, checksum_slug(data), &ext);
self.write_static_buf(path, name, data, ext)?;
self.names.insert(from_name.clone(), to_name.clone());
self.names_r.insert(to_name, from_name.clone());
}
Ok(())
}
#[cfg(feature = "sass")]
pub fn add_sass_file(&mut self, src: &Path) -> io::Result<()> {
use rsass::*;
use std::sync::Arc;
let mut scope = GlobalScope::new();
println!("cargo:rerun-if-changed={}", src.to_string_lossy());
let existing_statics = Arc::new(self.get_names().clone());
scope.define_function(
"static_name",
SassFunction::builtin(
vec![("name".into(), sass::Value::Null)],
false,
Arc::new(move |s| match s.get("name") {
css::Value::Literal(name, _) => {
let name = name.replace('-', "_").replace('.', "_");
for (n, v) in existing_statics.as_ref() {
if name == *n {
return Ok(css::Value::Literal(
v.clone(),
Quotes::Double,
));
}
}
Err(Error::S(format!(
"Static file {} not found",
name,
)))
}
name => Err(Error::badarg("string", &name)),
}),
),
);
let file_context = FileContext::new();
let (file_context, src) = file_context.file(src);
let scss = parse_scss_file(&src).unwrap();
let style = OutputStyle::Compressed;
let css = style.write_root(&scss, &mut scope, &file_context).unwrap();
self.add_file_data(&src.with_extension("css"), &css)
}
fn write_static_file(
&mut self,
path: &Path,
name: &str,
content: &[u8],
suffix: &str,
) -> io::Result<()> {
write!(
self.src,
"\n/// From {path:?}\n\
#[allow(non_upper_case_globals)]\n\
pub static {name}_{suf}: StaticFile = StaticFile {{\n \
content: include_bytes!({path:?}),\n \
name: \"{name}-{hash}.{suf}\",\n\
{mime}\
}};\n",
path = path,
name = name,
hash = checksum_slug(content),
suf = suffix,
mime = mime_arg(suffix),
)
}
fn write_static_file2(
&mut self,
path: &Path,
name: &str,
as_name: &str,
suffix: &str,
) -> io::Result<()> {
write!(
self.src,
"\n/// From {path:?}\n\
#[allow(non_upper_case_globals)]\n\
pub static {name}: StaticFile = StaticFile {{\n \
content: include_bytes!({path:?}),\n \
name: \"{as_name}\",\n\
{mime}\
}};\n",
path = path,
name = name,
as_name = as_name,
mime = mime_arg(suffix),
)
}
fn write_static_buf(
&mut self,
path: &Path,
name: &str,
content: &[u8],
suffix: &str,
) -> io::Result<()> {
write!(
self.src,
"\n/// From {path:?}\n\
#[allow(non_upper_case_globals)]\n\
pub static {name}_{suf}: StaticFile = StaticFile {{\n \
content: &{content:?},\n \
name: \"{name}-{hash}.{suf}\",\n\
{mime}\
}};\n",
path = path,
name = name,
content = content,
hash = checksum_slug(content),
suf = suffix,
mime = mime_arg(suffix),
)
}
pub fn get_names(&self) -> &BTreeMap<String, String> {
&self.names
}
}
#[cfg(not(feature = "mime02"))]
#[cfg(not(feature = "mime03"))]
fn mime_arg(_: &str) -> String {
"".to_string()
}
#[cfg(feature = "mime02")]
fn mime_arg(suffix: &str) -> String {
format!("_mime: {:?},\n", mime_from_suffix(suffix))
}
#[cfg(feature = "mime02")]
fn mime_from_suffix(suffix: &str) -> &'static str {
match suffix.to_lowercase().as_ref() {
"css" => "text/css",
"eot" => "application/vnd.ms-fontobject",
"jpg" | "jpeg" => "image/jpeg",
"js" => "application/javascript",
"png" => "image/png",
"woff" => "application/font-woff",
_ => "Application/OctetStream",
}
}
#[cfg(feature = "mime03")]
fn mime_arg(suffix: &str) -> String {
format!("mime: &mime::{},\n", mime_from_suffix(suffix))
}
#[cfg(feature = "mime03")]
fn mime_from_suffix(suffix: &str) -> &'static str {
match suffix.to_lowercase().as_ref() {
"bmp" => "IMAGE_BMP",
"css" => "TEXT_CSS",
"gif" => "IMAGE_GIF",
"jpg" | "jpeg" => "IMAGE_JPEG",
"js" => "TEXT_JAVASCRIPT",
"json" => "APPLICATION_JSON",
"png" => "IMAGE_PNG",
_ => "APPLICATION_OCTET_STREAM",
}
}
impl Drop for StaticFiles {
fn drop(&mut self) {
let _ = write!(
self.src,
"\npub static STATICS: &'static [&'static StaticFile] \
= &[{}];\n",
self.names_r
.iter()
.map(|s| format!("&{}", s.1))
.collect::<Vec<_>>()
.join(", "),
);
}
}
fn name_and_ext(path: &Path) -> Option<(&str, &str)> {
if let (Some(name), Some(ext)) = (path.file_name(), path.extension()) {
if let (Some(name), Some(ext)) = (name.to_str(), ext.to_str()) {
return Some((&name[..name.len() - ext.len() - 1], ext));
}
}
None
}
fn checksum_slug(data: &[u8]) -> String {
base64::encode_config(&md5::compute(data)[..6], base64::URL_SAFE)
}
pub fn compile_templates(indir: &Path, outdir: &Path) -> io::Result<()> {
File::create(outdir.join("templates.rs")).and_then(|mut f| {
write!(
f,
"pub mod templates {{\n\
use std::io::{{self, Write}};\n\
use std::fmt::Display;\n\n",
)?;
let outdir = outdir.join("templates");
create_dir_all(&outdir)?;
handle_entries(&mut f, indir, &outdir)?;
if outdir.join("statics.rs").exists() {
write!(f, "pub mod statics;\n")?;
}
write!(
f,
"{}\n}}\n",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/template_utils.rs",
)),
)
})
}
fn handle_entries(
f: &mut Write,
indir: &Path,
outdir: &Path,
) -> io::Result<()> {
println!("cargo:rerun-if-changed={}", indir.to_string_lossy());
let suffix = ".rs.html";
for entry in read_dir(indir)? {
let entry = entry?;
let path = entry.path();
if entry.file_type()?.is_dir() {
if let Some(filename) = entry.file_name().to_str() {
let outdir = outdir.join(filename);
create_dir_all(&outdir)?;
File::create(outdir.join("mod.rs"))
.and_then(|mut f| handle_entries(&mut f, &path, &outdir))?;
write!(f, "pub mod {name};\n\n", name = filename)?;
}
} else if let Some(filename) = entry.file_name().to_str() {
if filename.ends_with(suffix) {
println!("cargo:rerun-if-changed={}", path.to_string_lossy());
let name = &filename[..filename.len() - suffix.len()];
if handle_template(name, &path, outdir)? {
write!(
f,
"mod template_{name};\n\
pub use self::template_{name}::{name};\n\n",
name = name,
)?;
}
}
}
}
Ok(())
}
fn handle_template(
name: &str,
path: &Path,
outdir: &Path,
) -> io::Result<bool> {
let mut input = File::open(path)?;
let mut buf = Vec::new();
input.read_to_end(&mut buf)?;
match template(&buf) {
Done(_, t) => {
let fname = outdir.join(format!("template_{}.rs", name));
File::create(fname).and_then(|mut f| t.write_rust(&mut f, name))?;
Ok(true)
}
result => {
println!("cargo:warning=Template parse error in {:?}:", path);
show_errors(&mut io::stdout(), &buf, result, "cargo:warning=");
Ok(false)
}
}
}
fn show_errors<E>(
out: &mut Write,
buf: &[u8],
result: nom::IResult<&[u8], E>,
prefix: &str,
) {
if let Some(errors) = prepare_errors(buf, result) {
for &(ref kind, ref from, ref _to) in &errors {
show_error(out, buf, *from, &get_message(kind), prefix);
}
}
}
fn get_message(err: &ErrorKind) -> String {
match err {
&ErrorKind::Custom(n) => match get_error(n) {
Some(msg) => msg,
None => format!("Unknown error #{}", n),
},
err => format!("{:?}", err),
}
}
fn show_error(
out: &mut Write,
buf: &[u8],
pos: usize,
msg: &str,
prefix: &str,
) {
let mut line_start = buf[0..pos].rsplitn(2, |c| *c == b'\n');
let _ = line_start.next();
let line_start =
line_start.next().map(|bytes| bytes.len() + 1).unwrap_or(0);
let line = buf[line_start..]
.splitn(2, |c| *c == b'\n')
.next()
.and_then(|s| from_utf8(s).ok())
.unwrap_or("(Failed to display line)");
let line_no = what_line(buf, line_start);
let pos_in_line =
from_utf8(&buf[line_start..pos]).unwrap().chars().count() + 1;
writeln!(
out,
"{prefix}{:>4}:{}\n\
{prefix} {:>pos$} {}",
line_no,
line,
"^",
msg,
pos = pos_in_line,
prefix = prefix,
).unwrap();
}
fn what_line(buf: &[u8], pos: usize) -> usize {
1 + buf[0..pos].iter().filter(|c| **c == b'\n').count()
}
pub mod templates {
use std::fmt::Display;
use std::io::{self, Write};
pub type Mime = u8;
pub struct StaticFile {
pub content: &'static [u8],
pub name: &'static str,
#[cfg(feature = "mime03")]
pub mime: &'static Mime,
}
impl StaticFile {
#[allow(unused)]
#[cfg(feature = "mime02")]
pub fn mime(&self) -> Mime {
unimplemented!()
}
}
include!("template_utils.rs");
#[test]
fn encoded() {
let mut buf = Vec::new();
"a < b".to_html(&mut buf).unwrap();
assert_eq!(b"a < b", &buf[..]);
}
#[test]
fn raw_html() {
let mut buf = Vec::new();
Html("a<b>c</b>").to_html(&mut buf).unwrap();
assert_eq!(b"a<b>c</b>", &buf[..]);
}
}