Merge branch 'feature/config_file'
This commit is contained in:
commit
4803792d4c
|
@ -1064,6 +1064,7 @@ dependencies = [
|
||||||
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
"syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"yaml-rust 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1410,6 +1411,14 @@ dependencies = [
|
||||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaml-rust"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380"
|
"checksum actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380"
|
||||||
"checksum actix-connect 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c95cc9569221e9802bf4c377f6c18b90ef10227d787611decf79fd47d2a8e76c"
|
"checksum actix-connect 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c95cc9569221e9802bf4c377f6c18b90ef10227d787611decf79fd47d2a8e76c"
|
||||||
|
@ -1562,3 +1571,4 @@ dependencies = [
|
||||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
|
"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
|
||||||
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||||
|
"checksum yaml-rust 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# Derive Options
|
# Configuration
|
||||||
|
|
||||||
|
## Derive options
|
||||||
|
|
||||||
You can control the rendering behaviour via `template` attribute.
|
You can control the rendering behaviour via `template` attribute.
|
||||||
|
|
||||||
|
@ -13,7 +15,7 @@ struct TemplateStruct {
|
||||||
`template` attribute accepts the following options.
|
`template` attribute accepts the following options.
|
||||||
|
|
||||||
- `path`: path to template file. This options is always required.
|
- `path`: path to template file. This options is always required.
|
||||||
- `escape`: Enable HTML escaping (default: `false`)
|
- `escape`: Enable HTML escaping (default: `true`)
|
||||||
- `delimiter`: Replace the '%' character used for the tag delimiter (default: '%')
|
- `delimiter`: Replace the '%' character used for the tag delimiter (default: '%')
|
||||||
- `rm_whitespace`: try to strip whitespaces as much as possible without collapsing HTML structure (default: `false`). This option might not work correctly if your templates have inline `script` tag.
|
- `rm_whitespace`: try to strip whitespaces as much as possible without collapsing HTML structure (default: `false`). This option might not work correctly if your templates have inline `script` tag.
|
||||||
|
|
||||||
|
@ -28,3 +30,32 @@ struct TemplateStruct {
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Configuration file
|
||||||
|
|
||||||
|
Sailfish allows global and local configuration in a file named `sailfish.yml`. Sailfish looks for this file in same directory as `Cargo.toml` and all parent directories.
|
||||||
|
If, for example, `Cargo.toml` exists in `/foo/bar/baz` directory, then the following configuration files would be scanned in this order.
|
||||||
|
|
||||||
|
- `/foo/bar/baz/sailfish.yml`
|
||||||
|
- `/foo/bar/sailfish.yml`
|
||||||
|
- `/foo/sailfish.yml`
|
||||||
|
- `/sailfish.yml`
|
||||||
|
|
||||||
|
If a key is specified in multiple configuration files, the value in the deeper directory takes precedence over ancestor directories.
|
||||||
|
|
||||||
|
If a key is specified in both configuration file and derive options, then the value specified in the derive options takes precedence over the configuration file.
|
||||||
|
|
||||||
|
### Configuration file format
|
||||||
|
|
||||||
|
Configuration files are written in the YAML 1.2 format. The default configuration is described below.
|
||||||
|
|
||||||
|
```
|
||||||
|
template_dir: "templates"
|
||||||
|
escape: true
|
||||||
|
delimiter: "%"
|
||||||
|
|
||||||
|
optimization:
|
||||||
|
rm_whitespace: false
|
||||||
|
```
|
||||||
|
|
||||||
|
You can specify another template directory in `template_dir` option. Other options are same as derive options.
|
||||||
|
|
|
@ -44,6 +44,6 @@ nav:
|
||||||
- 'Welcome': 'index.md'
|
- 'Welcome': 'index.md'
|
||||||
- 'Installation': 'installation.md'
|
- 'Installation': 'installation.md'
|
||||||
- 'Getting Started': 'getting-started.md'
|
- 'Getting Started': 'getting-started.md'
|
||||||
- 'Options': 'options.md'
|
- 'Configuration': 'options.md'
|
||||||
- 'Syntax':
|
- 'Syntax':
|
||||||
- 'Overview': 'syntax/overview.md'
|
- 'Overview': 'syntax/overview.md'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
1
|
1
|
||||||
<% include!("/includes/include-parent.stpl"); %>
|
<% include!("includes/include-parent.stpl"); %>
|
||||||
4
|
4
|
||||||
<% include!("./included.stpl"); %>
|
<% include!("./included.stpl"); %>
|
||||||
5
|
5
|
||||||
|
|
|
@ -17,12 +17,14 @@ name = "sailfish_compiler"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["config"]
|
||||||
procmacro = []
|
procmacro = []
|
||||||
|
config = ["yaml-rust"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memchr = "2.3.3"
|
memchr = "2.3.3"
|
||||||
quote = { version = "1.0.6", default-features = false }
|
quote = { version = "1.0.6", default-features = false }
|
||||||
|
yaml-rust = { version = "0.4.4", optional = true }
|
||||||
|
|
||||||
[dependencies.syn]
|
[dependencies.syn]
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
|
|
|
@ -41,7 +41,6 @@ impl Compiler {
|
||||||
|
|
||||||
pub fn compile_file(
|
pub fn compile_file(
|
||||||
&self,
|
&self,
|
||||||
template_dir: &Path,
|
|
||||||
input: &Path,
|
input: &Path,
|
||||||
output: &Path,
|
output: &Path,
|
||||||
) -> Result<CompilationReport, Error> {
|
) -> Result<CompilationReport, Error> {
|
||||||
|
@ -64,7 +63,7 @@ impl Compiler {
|
||||||
let mut tsource = self.translate_file_contents(input)?;
|
let mut tsource = self.translate_file_contents(input)?;
|
||||||
let mut report = CompilationReport { deps: Vec::new() };
|
let mut report = CompilationReport { deps: Vec::new() };
|
||||||
|
|
||||||
let r = resolver.resolve(template_dir, &*input, &mut tsource.ast)?;
|
let r = resolver.resolve(&*input, &mut tsource.ast)?;
|
||||||
report.deps = r.deps;
|
report.deps = r.deps;
|
||||||
|
|
||||||
optimizer.optimize(&mut tsource.ast);
|
optimizer.optimize(&mut tsource.ast);
|
||||||
|
|
|
@ -4,8 +4,10 @@ use std::path::{Path, PathBuf};
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub delimiter: char,
|
pub delimiter: char,
|
||||||
pub escape: bool,
|
pub escape: bool,
|
||||||
pub cache_dir: PathBuf,
|
|
||||||
pub rm_whitespace: bool,
|
pub rm_whitespace: bool,
|
||||||
|
pub template_dirs: Vec<PathBuf>,
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub cache_dir: PathBuf,
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub _non_exhaustive: (),
|
pub _non_exhaustive: (),
|
||||||
}
|
}
|
||||||
|
@ -13,6 +15,7 @@ pub struct Config {
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
template_dirs: Vec::new(),
|
||||||
delimiter: '%',
|
delimiter: '%',
|
||||||
escape: true,
|
escape: true,
|
||||||
cache_dir: Path::new(env!("OUT_DIR")).join("cache"),
|
cache_dir: Path::new(env!("OUT_DIR")).join("cache"),
|
||||||
|
@ -22,4 +25,211 @@ impl Default for Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Global configration file
|
#[cfg(feature = "config")]
|
||||||
|
mod config {
|
||||||
|
use std::fs;
|
||||||
|
use yaml_rust::yaml::{Yaml, YamlLoader};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::error::*;
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn search_file_and_read(base: &Path) -> Result<Config, Error> {
|
||||||
|
// search config file
|
||||||
|
let mut path = PathBuf::new();
|
||||||
|
let mut config = Config::default();
|
||||||
|
|
||||||
|
for component in base.iter() {
|
||||||
|
path.push(component);
|
||||||
|
path.push("sailfish.yml");
|
||||||
|
|
||||||
|
if path.is_file() {
|
||||||
|
let config_file =
|
||||||
|
ConfigFile::read_from_file(&*path).map_err(|mut e| {
|
||||||
|
e.source_file = Some(path.to_owned());
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(template_dirs) = config_file.template_dirs {
|
||||||
|
for template_dir in template_dirs.into_iter().rev() {
|
||||||
|
if template_dir.is_absolute() {
|
||||||
|
config.template_dirs.push(template_dir);
|
||||||
|
} else {
|
||||||
|
config
|
||||||
|
.template_dirs
|
||||||
|
.push(path.parent().unwrap().join(template_dir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(delimiter) = config_file.delimiter {
|
||||||
|
config.delimiter = delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(escape) = config_file.escape {
|
||||||
|
config.escape = escape;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rm_whitespace) = config_file.rm_whitespace {
|
||||||
|
config.rm_whitespace = rm_whitespace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ConfigFile {
|
||||||
|
template_dirs: Option<Vec<PathBuf>>,
|
||||||
|
delimiter: Option<char>,
|
||||||
|
escape: Option<bool>,
|
||||||
|
rm_whitespace: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigFile {
|
||||||
|
fn read_from_file(path: &Path) -> Result<Self, Error> {
|
||||||
|
let mut config = Self::default();
|
||||||
|
let content = fs::read_to_string(path)
|
||||||
|
.chain_err(|| format!("Failed to read configuration file {:?}", path))?;
|
||||||
|
|
||||||
|
let entries = YamlLoader::load_from_str(&*content)
|
||||||
|
.map_err(|e| ErrorKind::ConfigError(e.to_string()))?;
|
||||||
|
drop(content);
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
config.visit_global(entry)?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_global(&mut self, entry: Yaml) -> Result<(), Error> {
|
||||||
|
let hash = entry.into_hash().ok_or_else(|| {
|
||||||
|
ErrorKind::ConfigError("Invalid configuration format".to_owned())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for (k, v) in hash {
|
||||||
|
match k {
|
||||||
|
Yaml::String(ref s) => match &**s {
|
||||||
|
"template_dir" => self.visit_template_dir(v)?,
|
||||||
|
"delimiter" => self.visit_delimiter(v)?,
|
||||||
|
"escape" => self.visit_escape(v)?,
|
||||||
|
"optimization" => self.visit_optimization(v)?,
|
||||||
|
_ => return Err(Self::error(format!("Unknown key ({})", s))),
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(Self::error("Invalid configuration format"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_template_dir(&mut self, value: Yaml) -> Result<(), Error> {
|
||||||
|
if self.template_dirs.is_some() {
|
||||||
|
return Err(Self::error("Duplicate key (template_dir)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Yaml::String(s) => self.template_dirs = Some(vec![PathBuf::from(s)]),
|
||||||
|
Yaml::Array(v) => {
|
||||||
|
let mut template_dirs = Vec::new();
|
||||||
|
for e in v {
|
||||||
|
if let Yaml::String(s) = e {
|
||||||
|
template_dirs.push(PathBuf::from(s));
|
||||||
|
} else {
|
||||||
|
return Err(Self::error(
|
||||||
|
"Arguments of `template_dir` must be string",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.template_dirs = Some(template_dirs);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Self::error(
|
||||||
|
"Arguments of `template_dir` must be string",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_delimiter(&mut self, value: Yaml) -> Result<(), Error> {
|
||||||
|
if self.delimiter.is_some() {
|
||||||
|
return Err(Self::error("Duplicate key (delimiter)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Yaml::String(s) = value {
|
||||||
|
if s.chars().count() == 1 {
|
||||||
|
self.delimiter = Some(s.chars().next().unwrap());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Self::error("`escape` must be single character"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Self::error("`escape` must be single character"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_escape(&mut self, value: Yaml) -> Result<(), Error> {
|
||||||
|
if self.escape.is_some() {
|
||||||
|
return Err(Self::error("Duplicate key (escape)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Yaml::Boolean(b) = value {
|
||||||
|
self.escape = Some(b);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Self::error("`escape` must be boolean"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_optimization(&mut self, entry: Yaml) -> Result<(), Error> {
|
||||||
|
let hash = entry.into_hash().ok_or_else(|| {
|
||||||
|
ErrorKind::ConfigError("Invalid configuration format".to_owned())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for (k, v) in hash {
|
||||||
|
match k {
|
||||||
|
Yaml::String(ref s) => match &**s {
|
||||||
|
"rm_whitespace" => self.visit_rm_whitespace(v)?,
|
||||||
|
_ => {
|
||||||
|
return Err(Self::error(format!(
|
||||||
|
"Unknown key (optimization.{})",
|
||||||
|
s
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(Self::error("Invalid configuration format"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_rm_whitespace(&mut self, value: Yaml) -> Result<(), Error> {
|
||||||
|
if self.rm_whitespace.is_some() {
|
||||||
|
return Err(Self::error("Duplicate key (rm_whitespace)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Yaml::Boolean(b) = value {
|
||||||
|
self.rm_whitespace = Some(b);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Self::error("`rm_whitespace` must be boolean"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error<T: Into<String>>(msg: T) -> Error {
|
||||||
|
make_error!(ErrorKind::ConfigError(msg.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub enum ErrorKind {
|
||||||
FmtError(fmt::Error),
|
FmtError(fmt::Error),
|
||||||
IoError(io::Error),
|
IoError(io::Error),
|
||||||
RustSyntaxError(syn::Error),
|
RustSyntaxError(syn::Error),
|
||||||
|
ConfigError(String),
|
||||||
ParseError(String),
|
ParseError(String),
|
||||||
AnalyzeError(String),
|
AnalyzeError(String),
|
||||||
Unimplemented(String),
|
Unimplemented(String),
|
||||||
|
@ -22,6 +23,7 @@ impl fmt::Display for ErrorKind {
|
||||||
ErrorKind::FmtError(ref e) => e.fmt(f),
|
ErrorKind::FmtError(ref e) => e.fmt(f),
|
||||||
ErrorKind::IoError(ref e) => e.fmt(f),
|
ErrorKind::IoError(ref e) => e.fmt(f),
|
||||||
ErrorKind::RustSyntaxError(ref e) => write!(f, "Rust Syntax Error: {}", e),
|
ErrorKind::RustSyntaxError(ref e) => write!(f, "Rust Syntax Error: {}", e),
|
||||||
|
ErrorKind::ConfigError(ref e) => write!(f, "Invalid configuration: {}", e),
|
||||||
ErrorKind::ParseError(ref msg) => write!(f, "Parse error: {}", msg),
|
ErrorKind::ParseError(ref msg) => write!(f, "Parse error: {}", msg),
|
||||||
ErrorKind::AnalyzeError(ref msg) => write!(f, "Analyzation error: {}", msg),
|
ErrorKind::AnalyzeError(ref msg) => write!(f, "Analyzation error: {}", msg),
|
||||||
ErrorKind::Unimplemented(ref msg) => f.write_str(&**msg),
|
ErrorKind::Unimplemented(ref msg) => f.write_str(&**msg),
|
||||||
|
|
|
@ -98,13 +98,7 @@ impl DeriveTemplateOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile(
|
fn merge_config_options(config: &mut Config, options: &DeriveTemplateOptions) {
|
||||||
template_dir: &Path,
|
|
||||||
input_file: &Path,
|
|
||||||
output_file: &Path,
|
|
||||||
options: &DeriveTemplateOptions,
|
|
||||||
) -> Result<CompilationReport, Error> {
|
|
||||||
let mut config = Config::default();
|
|
||||||
if let Some(ref delimiter) = options.delimiter {
|
if let Some(ref delimiter) = options.delimiter {
|
||||||
config.delimiter = delimiter.value();
|
config.delimiter = delimiter.value();
|
||||||
}
|
}
|
||||||
|
@ -114,9 +108,66 @@ fn compile(
|
||||||
if let Some(ref rm_whitespace) = options.rm_whitespace {
|
if let Some(ref rm_whitespace) = options.rm_whitespace {
|
||||||
config.rm_whitespace = rm_whitespace.value;
|
config.rm_whitespace = rm_whitespace.value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_template_file(path: &str, template_dirs: &[PathBuf]) -> Option<PathBuf> {
|
||||||
|
for template_dir in template_dirs.iter().rev() {
|
||||||
|
let p = template_dir.join(path);
|
||||||
|
if p.is_file() {
|
||||||
|
return Some(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fallback = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect(
|
||||||
|
"Internal error: environmental variable `CARGO_MANIFEST_DIR` is not set.",
|
||||||
|
));
|
||||||
|
fallback.push("templates");
|
||||||
|
fallback.push(path);
|
||||||
|
|
||||||
|
if fallback.is_file() {
|
||||||
|
return Some(fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filename_hash(path: &Path) -> String {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
const FNV_PRIME: u64 = 1_099_511_628_211;
|
||||||
|
const FNV_OFFSET_BASIS: u64 = 14_695_981_039_346_656_037;
|
||||||
|
|
||||||
|
let mut hash = String::with_capacity(16);
|
||||||
|
|
||||||
|
if let Some(n) = path.file_name() {
|
||||||
|
let mut filename = &*n.to_string_lossy();
|
||||||
|
if let Some(p) = filename.find('.') {
|
||||||
|
filename = &filename[..p];
|
||||||
|
}
|
||||||
|
hash.push_str(filename);
|
||||||
|
hash.push('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate 64bit hash
|
||||||
|
let mut h = FNV_OFFSET_BASIS;
|
||||||
|
for b in (&*path.to_string_lossy()).bytes() {
|
||||||
|
h = h.wrapping_mul(FNV_PRIME);
|
||||||
|
h ^= b as u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert 64bit hash into ascii
|
||||||
|
let _ = write!(hash, "{:016x}", h);
|
||||||
|
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile(
|
||||||
|
input_file: &Path,
|
||||||
|
output_file: &Path,
|
||||||
|
config: Config,
|
||||||
|
) -> Result<CompilationReport, Error> {
|
||||||
let compiler = Compiler::with_config(config);
|
let compiler = Compiler::with_config(config);
|
||||||
compiler.compile_file(template_dir, input_file, &*output_file)
|
compiler.compile_file(input_file, &*output_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error> {
|
fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error> {
|
||||||
|
@ -130,13 +181,20 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut template_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect(
|
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect(
|
||||||
"Internal error: environmental variable `CARGO_MANIFEST_DIR` is not set.",
|
"Internal error: environmental variable `CARGO_MANIFEST_DIR` is not set.",
|
||||||
));
|
));
|
||||||
template_dir.push("templates");
|
|
||||||
|
#[cfg(feature = "config")]
|
||||||
|
let mut config = Config::search_file_and_read(&*manifest_dir)
|
||||||
|
.map_err(|e| syn::Error::new(Span::call_site(), e))?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "config"))]
|
||||||
|
let mut config = Config::default();
|
||||||
|
|
||||||
if env::var("SAILFISH_INTEGRATION_TESTS").map_or(false, |s| s == "1") {
|
if env::var("SAILFISH_INTEGRATION_TESTS").map_or(false, |s| s == "1") {
|
||||||
template_dir = template_dir
|
let template_dir = env::current_dir()
|
||||||
|
.unwrap()
|
||||||
.ancestors()
|
.ancestors()
|
||||||
.find(|p| p.join("LICENSE").exists())
|
.find(|p| p.join("LICENSE").exists())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -144,40 +202,30 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
||||||
.join("tests")
|
.join("tests")
|
||||||
.join("fails")
|
.join("fails")
|
||||||
.join("templates");
|
.join("templates");
|
||||||
|
config.template_dirs.push(template_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
let input_file = match all_options.path {
|
let input_file = {
|
||||||
Some(ref path) => template_dir.join(path.value()),
|
let path = all_options.path.as_ref().ok_or_else(|| {
|
||||||
None => {
|
syn::Error::new(Span::call_site(), "`path` option must be specified.")
|
||||||
return Err(syn::Error::new(
|
})?;
|
||||||
Span::call_site(),
|
resolve_template_file(&*path.value(), &*config.template_dirs).ok_or_else(
|
||||||
"`path` option must be specified.",
|
|| {
|
||||||
|
syn::Error::new(
|
||||||
|
path.span(),
|
||||||
|
format!("Template file {:?} not found", path.value()),
|
||||||
)
|
)
|
||||||
.into())
|
},
|
||||||
}
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let filename = match input_file.file_name() {
|
let out_dir = PathBuf::from(env!("OUT_DIR"));
|
||||||
Some(f) => f,
|
|
||||||
None => {
|
|
||||||
return Err(syn::Error::new(
|
|
||||||
Span::call_site(),
|
|
||||||
format!("Invalid file name: {:?}", input_file),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let out_dir =
|
|
||||||
if std::env::var("SAILFISH_INTEGRATION_TESTS").map_or(false, |s| s == "1") {
|
|
||||||
template_dir.parent().unwrap().join("out")
|
|
||||||
} else {
|
|
||||||
PathBuf::from(env!("OUT_DIR"))
|
|
||||||
};
|
|
||||||
let mut output_file = out_dir.clone();
|
let mut output_file = out_dir.clone();
|
||||||
output_file.push("templates");
|
output_file.push("templates");
|
||||||
output_file.push(filename);
|
output_file.push(filename_hash(&*input_file));
|
||||||
|
|
||||||
let report = compile(&*template_dir, &*input_file, &*output_file, &all_options)
|
merge_config_options(&mut config, &all_options);
|
||||||
|
let report = compile(&*input_file, &*output_file, config)
|
||||||
.map_err(|e| syn::Error::new(Span::call_site(), e))?;
|
.map_err(|e| syn::Error::new(Span::call_site(), e))?;
|
||||||
|
|
||||||
let input_file_string = input_file.to_string_lossy();
|
let input_file_string = input_file.to_string_lossy();
|
||||||
|
|
|
@ -27,15 +27,14 @@ pub struct ResolveReport {
|
||||||
pub deps: Vec<PathBuf>,
|
pub deps: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResolverImpl<'s, 'h> {
|
struct ResolverImpl<'h> {
|
||||||
template_dir: &'s Path,
|
|
||||||
path_stack: Vec<PathBuf>,
|
path_stack: Vec<PathBuf>,
|
||||||
deps: Vec<PathBuf>,
|
deps: Vec<PathBuf>,
|
||||||
error: Option<Error>,
|
error: Option<Error>,
|
||||||
include_handler: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
|
include_handler: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s, 'h> ResolverImpl<'s, 'h> {
|
impl<'h> ResolverImpl<'h> {
|
||||||
fn resolve_include(&mut self, i: &ExprMacro) -> Result<Expr, Error> {
|
fn resolve_include(&mut self, i: &ExprMacro) -> Result<Expr, Error> {
|
||||||
let arg = match syn::parse2::<LitStr>(i.mac.tokens.clone()) {
|
let arg = match syn::parse2::<LitStr>(i.mac.tokens.clone()) {
|
||||||
Ok(l) => l.value(),
|
Ok(l) => l.value(),
|
||||||
|
@ -50,8 +49,8 @@ impl<'s, 'h> ResolverImpl<'s, 'h> {
|
||||||
|
|
||||||
// resolve include! for rust file
|
// resolve include! for rust file
|
||||||
if arg.ends_with(".rs") {
|
if arg.ends_with(".rs") {
|
||||||
let absolute_path = if arg.starts_with('/') {
|
let absolute_path = if Path::new(&*arg).is_absolute() {
|
||||||
self.template_dir.join(&arg[1..])
|
PathBuf::from(&arg[1..])
|
||||||
} else {
|
} else {
|
||||||
self.path_stack.last().unwrap().parent().unwrap().join(arg)
|
self.path_stack.last().unwrap().parent().unwrap().join(arg)
|
||||||
};
|
};
|
||||||
|
@ -61,9 +60,9 @@ impl<'s, 'h> ResolverImpl<'s, 'h> {
|
||||||
|
|
||||||
// resolve the template file path
|
// resolve the template file path
|
||||||
// TODO: How should arguments be interpreted on Windows?
|
// TODO: How should arguments be interpreted on Windows?
|
||||||
let child_template_file = if arg.starts_with('/') {
|
let child_template_file = if Path::new(&*arg).is_absolute() {
|
||||||
// absolute imclude
|
// absolute imclude
|
||||||
self.template_dir.join(&arg[1..])
|
PathBuf::from(&arg[1..])
|
||||||
} else {
|
} else {
|
||||||
// relative include
|
// relative include
|
||||||
self.path_stack
|
self.path_stack
|
||||||
|
@ -95,7 +94,7 @@ impl<'s, 'h> ResolverImpl<'s, 'h> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s, 'h> VisitMut for ResolverImpl<'s, 'h> {
|
impl<'h> VisitMut for ResolverImpl<'h> {
|
||||||
fn visit_expr_mut(&mut self, i: &mut Expr) {
|
fn visit_expr_mut(&mut self, i: &mut Expr) {
|
||||||
return_if_some!(self.error);
|
return_if_some!(self.error);
|
||||||
let em = matches_or_else!(*i, Expr::Macro(ref mut em), em, {
|
let em = matches_or_else!(*i, Expr::Macro(ref mut em), em, {
|
||||||
|
@ -134,23 +133,20 @@ impl<'h> Resolver<'h> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn include_handler(
|
pub fn include_handler(
|
||||||
self,
|
mut self,
|
||||||
new: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
|
new: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
|
||||||
) -> Resolver<'h> {
|
) -> Resolver<'h> {
|
||||||
Self {
|
self.include_handler = new;
|
||||||
include_handler: new,
|
self
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
&self,
|
&self,
|
||||||
template_dir: &Path,
|
|
||||||
input_file: &Path,
|
input_file: &Path,
|
||||||
ast: &mut Block,
|
ast: &mut Block,
|
||||||
) -> Result<ResolveReport, Error> {
|
) -> Result<ResolveReport, Error> {
|
||||||
let mut child = ResolverImpl {
|
let mut child = ResolverImpl {
|
||||||
template_dir,
|
|
||||||
path_stack: vec![input_file.to_owned()],
|
path_stack: vec![input_file.to_owned()],
|
||||||
deps: Vec::new(),
|
deps: Vec::new(),
|
||||||
error: None,
|
error: None,
|
||||||
|
|
|
@ -20,6 +20,15 @@ proc-macro = true
|
||||||
test = false
|
test = false
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["config"]
|
||||||
|
config = ["sailfish-compiler/config"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sailfish-compiler = { path = "../sailfish-compiler", version = "0.0.5", features = ["procmacro"] }
|
|
||||||
proc-macro2 = "1.0.17"
|
proc-macro2 = "1.0.17"
|
||||||
|
|
||||||
|
[dependencies.sailfish-compiler]
|
||||||
|
path = "../sailfish-compiler"
|
||||||
|
version = "0.0.5"
|
||||||
|
default-features = false
|
||||||
|
features = ["procmacro"]
|
||||||
|
|
Loading…
Reference in New Issue