"pest",
"pest_derive",
"pretty_assertions",
- "walkdir",
]
[[package]]
clap = { version = "4", features = ["derive"] }
pandoc = "0.8"
color-eyre = { version = "0.5", default-features = false }
-walkdir = "2"
pest = "2"
pest_derive = "2"
need_stdout = true
allow_warnings = true
+[jobs.help]
+command = [ "cargo", "run", "--", "-h" ]
+need_stdout = true
+allow_warnings = true
+
+[jobs.very_verbose]
+command = [ "cargo", "run", "--", "-vvvv", "-r", "/home/huck/repos/metaforge/files/site/" ]
+need_stdout = true
+allow_warnings = true
+
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
+alt-h = "job:help"
+alt-v = "job:very_verbose"
use criterion::{black_box, criterion_group, criterion_main, Criterion};
-use metaforge::{build_site, Options};
+use metaforge::Options;
pub fn build_site_benchmark(c: &mut Criterion) {
let dir = std::path::PathBuf::from("files/site")
.canonicalize()
.unwrap();
- let opts = Options {
- root: dir.clone(),
- source: dir.join("source"),
- build: dir.join("build"),
- pattern: dir.join("pattern"),
- verbose: 0,
- quiet: false,
- force: false,
- undefined: false,
- clean: true,
- };
+ let mut opts = Options::new();
+ opts.root = dir.clone();
+ opts.source = dir.join("source");
+ opts.build = dir.join("build");
+ opts.pattern = dir.join("pattern");
+ opts.clean = true;
- c.bench_function("build test site", |b| {
- b.iter(|| build_site(black_box(&opts)))
- });
+ todo!("implement with DirNode")
+ // c.bench_function("build test site", |b| {
+ // b.iter(|| build_site(black_box(&opts)))
+ // });
}
criterion_group!(benches, build_site_benchmark);
fs,
path::{Path, PathBuf},
};
-use walkdir::WalkDir;
-
-pub fn build_site(opts: &Options) -> Result<()> {
- log!(opts, "finding files", 2);
- let files: Vec<PathBuf> = WalkDir::new(&opts.source)
- .into_iter()
- .filter_map(|file| {
- if file.as_ref().ok()?.file_type().is_dir() {
- // need to create directories in build dir
- let path = file.unwrap().into_path();
- let path = path.strip_prefix(&opts.source).ok()?;
- let path = opts.build.join(path);
- log!(opts, format!("\tcreating dir: {}", path.display()), 3);
- std::fs::create_dir(path).ok()?;
- // don't need them for any further operations so we filter them out
- None
- } else if let Ok(file) = file {
- log!(opts, format!("\tadding file: {}", file.path().display()), 3);
- Some(file.into_path())
- } else {
- None
- }
- })
- .collect();
- log!(opts, "building files", 2);
- for file in files.iter() {
- match build_metafile(file, opts) {
- Ok(_) => continue,
- Err(e) => {
- if opts.force {
- // print a line to stderr about failure but continue with other files
- eprintln!("{}: {}", file.display(), e);
- continue;
- } else {
- return Err(e.wrap_err(eyre!("{}:", file.display())));
- }
- }
- }
- }
-
- Ok(())
-}
-
-fn build_metafile(path: &Path, opts: &Options) -> Result<()> {
- log!(opts, format!("\t{}", path.display()), 1);
- let file =
- fs::read_to_string(path).wrap_err_with(|| eyre!("failed to read: {}\n", path.display()))?;
-
- log!(opts, "\tparsing", 2);
- let file =
- parse_file(&file).wrap_err_with(|| eyre!("failed to parse: {}\n", path.display()))?;
-
- let html = get_source_html(&file, opts)
- .wrap_err_with(|| eyre!("failed converting to html: {}\n", path.display()))?;
+pub fn build_metafile(file: &MetaFile, opts: &Options) -> Result<String> {
+ let html = get_source_html(file, opts)
+ .wrap_err_with(|| eyre!("failed converting to html: {}\n", file.path.display()))?;
let pattern = get_pattern("base", &file, opts).wrap_err("failed to get base pattern\n")?;
- let mut base = parse_file(&pattern).wrap_err("failed to parse base pattern\n")?;
+ let mut base = parse_file(pattern).wrap_err("failed to parse base pattern\n")?;
- base.variables = file.variables;
- base.arrays = file.arrays;
- base.patterns = file.patterns;
-
- base.patterns.insert("SOURCE", &html);
+ base.merge(&file);
+ base.patterns.insert("SOURCE".to_string(), html);
let output = metafile_to_string(&base, opts, Some("base"))
- .wrap_err_with(|| eyre!("failed to build: {}\n", path.display()))?;
+ .wrap_err_with(|| eyre!("failed to build: {}\n", file.path.display()))?;
- log!(opts, "\twriting", 2);
- let dest = find_dest(path, opts)
- .wrap_err_with(|| format!("could not find destination file: {}\n", path.display()))?;
+ Ok(output)
+}
+pub fn write_file(path: &Path, html: String, opts: &Options) -> Result<()> {
+ let dest = find_dest(path, opts)?;
// want newline to end file
- fs::write(&dest, output + "\n")
+ fs::write(&dest, html + "\n")
.wrap_err_with(|| eyre!("could not write to: {}\n", dest.display()))?;
Ok(())
}
fn get_source_html(file: &MetaFile, opts: &Options) -> Result<String> {
log!(opts, "\tbuilding source", 2);
let file = metafile_to_string(file, opts, Some("SOURCE")).wrap_err("failed building source")?;
- let mut pandoc = Pandoc::new();
- log!(opts, "\t\tsetting up pandoc", 3);
+ if opts.no_pandoc {
+ return Ok(file);
+ }
+
+ log!(opts, "\t\tcalling pandoc", 3);
+ log!(opts, "\t\t\tbuilding pandoc command", 4);
+ let mut pandoc = Pandoc::new();
pandoc
.set_input(InputKind::Pipe(file))
.set_output(OutputKind::Pipe)
.set_input_format(InputFormat::Markdown, vec![])
.set_output_format(OutputFormat::Html, vec![]);
- log!(opts, "\t\texecuting pandoc command", 3);
+ log!(opts, "\t\t\texecuting pandoc command", 4);
if let Ok(PandocOutput::ToBuffer(html)) = pandoc.execute() {
Ok(html)
} else {
// we just need to return that
if key == "SOURCE" {
log!(opts, "\t\t\treturning SOURCE", 4);
- let source = file.patterns.get("SOURCE").unwrap_or(&"");
- return Ok(source.to_string());
+ if let Some(source) = file.patterns.get("SOURCE") {
+ return Ok(source.to_string());
+ }
}
log!(opts, format!("\t\tpattern: {}", key), 3);
// anything not defined should have a default.meta file to fall back to
- let mut filename = file.get_pat(key).unwrap_or("default");
+ let mut filename: String;
+ if let Some(name) = file.get_pat(key) {
+ filename = name.to_string();
+ } else {
+ filename = "default".to_string()
+ }
// if we're building from base pattern we need to wait on
// parsing/expansion so we can build and convert source to html
// we just want to return the string right now
if key == "base" {
log!(opts, "\t\t\treturning base", 4);
- let pattern_path = key.to_string() + "/" + filename;
+ let pattern_path = key.to_string() + "/" + &filename;
let mut path = opts.pattern.join(pattern_path);
path.set_extension("meta");
let base = fs::read_to_string(&path)
- .wrap_err_with(|| eyre!("could not read: {}\n", path.display()))?;
+ .wrap_err_with(|| eyre!("base pattern does not exist: {}\n", path.display()))?;
return Ok(base);
}
return Ok(String::new());
};
- // DEFAULT override for variables defined higher in chain
+ // DEFAULT override for patterns defined higher in chain
if filename == "DEFAULT" {
log!(opts, "\t\t\tdefault pattern", 4);
- filename = "default";
+ filename = "default".to_string();
}
log!(opts, "\t\t\tbuilding path from key", 4);
- let pattern_path = key.replace('.', "/") + "/" + filename;
+ let pattern_path = key.replace('.', "/") + "/" + &filename;
let mut path = opts.pattern.join(pattern_path);
path.set_extension("meta");
log!(opts, "\t\t\tparsing file", 4);
- let pattern = &fs::read_to_string(&path)
- .wrap_err_with(|| eyre!("could not read: {}\n", path.display()))?;
- let mut pattern =
- parse_file(pattern).wrap_err_with(|| eyre!("could not parse: {}\n", path.display()))?;
+ let mut pattern = MetaFile::build(path)?;
// copy over maps for expanding contained variables
- // TODO: Make this a merge so patterns can define/override their own variables
- pattern.variables = file.variables.clone();
- pattern.arrays = file.arrays.clone();
- pattern.patterns = file.patterns.clone();
+ pattern.merge(&file);
log!(opts, "\t\t\tbuilding pattern", 4);
metafile_to_string(&pattern, opts, Some(key))
}
fn find_dest(path: &Path, opts: &Options) -> Result<PathBuf> {
- log!(opts, "\t\tfinding destination", 3);
- let source = opts.source.to_string_lossy();
- let build = opts.build.to_string_lossy();
-
let path = path
.canonicalize()
.wrap_err_with(|| eyre!("could not get absolute path: {}\n", path.display()))?;
- let path = path.to_string_lossy();
- let path = path.replace(&*source, &build);
+
+ let path = opts.build.join(path.strip_prefix(&opts.source)?);
let mut path = PathBuf::from(path);
path.set_extension("html");
}
fn expand_arrays(output: String, file: &MetaFile, name: Option<&str>) -> Result<String> {
- let map: HashMap<&str, &[&str]> = file
+ let map: HashMap<String, &[String]> = file
.source
.iter()
// filter out arrays from source vec
key = array.to_string();
}
let value = file.get_arr(&key).unwrap_or_default();
- (*array, value)
+ (array.to_string(), value)
})
.collect();
let mut str = output.clone();
// replace each key in the file
for (key, val) in map.iter() {
- str = str.replace(&format!("-{{{key}}}"), val.get(i).unwrap_or(&""));
+ if let Some(value) = val.get(i) {
+ str = str.replace(&format!("-{{{key}}}"), value);
+ }
}
// concatenate to final file
expanded.push_str(&str);
Ok(expanded)
}
-fn get_max_size(map: &HashMap<&str, &[&str]>) -> usize {
+fn get_max_size(map: &HashMap<String, &[String]>) -> usize {
let mut max = 0;
for val in map.values() {
if max < val.len() {
fn build_options() -> Result<Options> {
let dir = PathBuf::from("files/site").canonicalize()?;
- let opts = Options {
- root: dir.clone(),
- source: dir.join("source"),
- build: dir.join("build"),
- pattern: dir.join("pattern"),
- verbose: 0,
- quiet: false,
- force: false,
- undefined: false,
- clean: true,
- };
+ let mut opts = Options::new();
+ opts.root = dir.clone();
+ opts.source = dir.join("source");
+ opts.build = dir.join("build");
+ opts.pattern = dir.join("pattern");
+ opts.clean = true;
Ok(opts)
}
assert_eq!(find_dest(&path, &opts)?, opts.build.join("dir1/dir.html"));
Ok(())
}
+
+ #[test]
+ fn test_build_metafile() -> Result<()> {
+ let opts = build_options()?;
+ let path = opts.source.join("dir1/sub_dir1/deep2/deep.meta");
+ let expanded = "<html>\n<body>\nGOOD\n</body>\n</html>\n";
+ build_metafile(&MetaFile::build(path)?, &opts)?;
+ assert_eq!(
+ std::fs::read_to_string(opts.build.join("dir1/sub_dir1/deep2/deep.html"))?,
+ expanded
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn test_get_pattern() -> Result<()> {
+ let opts = build_options()?;
+ let file = MetaFile::new();
+ let pat = get_pattern("header", &file, &opts)?;
+ assert_eq!(pat, "<header>HEADER</header>");
+ Ok(())
+ }
}
+use clap::Parser;
use color_eyre::Result;
-use metaforge::{build_site, log, parse_opts};
+use metaforge::{log, Options, Opts};
fn main() -> Result<()> {
color_eyre::install()?;
- let opts = parse_opts()?;
+ let opts = Options::try_from(Opts::parse())?;
log!(
opts,
std::fs::create_dir(&opts.build)?;
}
- build_site(&opts)
+ todo!("implement DirNode chain")
}
+use crate::{build_metafile, parse_file, Options};
+use color_eyre::{eyre::eyre, Result};
use std::collections::HashMap;
+use std::{fs, path::PathBuf};
-#[derive(Debug, Default, Clone)]
-pub struct MetaFile<'a> {
- pub variables: HashMap<&'a str, &'a str>,
- pub arrays: HashMap<&'a str, Vec<&'a str>>,
- pub patterns: HashMap<&'a str, &'a str>,
- pub source: Vec<Source<'a>>,
+#[derive(Debug, Clone)]
+pub struct MetaFile {
+ pub path: PathBuf,
+ pub variables: HashMap<String, String>,
+ pub arrays: HashMap<String, Vec<String>>,
+ pub patterns: HashMap<String, String>,
+ pub source: Vec<Source>,
}
-impl<'a> MetaFile<'a> {
+impl MetaFile {
+ pub fn build(path: PathBuf) -> Result<Self> {
+ let str = fs::read_to_string(&path)?;
+ let mut metafile = MetaFile::build_from_string(str)?;
+ metafile.path = path.to_path_buf();
+ Ok(metafile)
+ }
+
+ fn build_from_string(string: String) -> Result<Self> {
+ let metafile = parse_file(string)?;
+ Ok(metafile)
+ }
+
pub fn new() -> Self {
Self {
+ path: PathBuf::new(),
variables: HashMap::new(),
arrays: HashMap::new(),
patterns: HashMap::new(),
}
}
- pub fn get_var(&self, key: &str) -> Option<&str> {
- self.variables.get(key).copied()
+ pub fn get_var(&self, key: &str) -> Option<&String> {
+ self.variables.get(key)
}
- pub fn get_arr(&self, key: &str) -> Option<&[&str]> {
- self.arrays.get(key).map(|val| &val[..])
+ pub fn get_arr(&self, key: &str) -> Option<&[String]> {
+ self.arrays.get(key).map(|a| &a[..])
}
- pub fn get_pat(&self, key: &str) -> Option<&str> {
- self.patterns.get(key).copied()
+ pub fn get_pat(&self, key: &str) -> Option<&String> {
+ self.patterns.get(key)
+ }
+
+ pub fn merge(&mut self, other: &Self) {
+ for (key, val) in other.variables.iter() {
+ match self.variables.get(key) {
+ Some(_) => continue,
+ None => self.variables.insert(key.to_string(), val.to_string()),
+ };
+ }
+ for (key, val) in other.arrays.iter() {
+ match self.arrays.get(key) {
+ Some(_) => continue,
+ None => self.arrays.insert(key.to_string(), val.to_vec()),
+ };
+ }
+ for (key, val) in other.patterns.iter() {
+ match self.patterns.get(key) {
+ Some(_) => continue,
+ None => self.patterns.insert(key.to_string(), val.to_string()),
+ };
+ }
}
}
#[macro_export]
macro_rules! source (
- (var($s:expr)) => { Source::Sub(Substitution::Variable($s))};
- (arr($s:expr)) => { Source::Sub(Substitution::Array($s))};
- (pat($s:expr)) => { Source::Sub(Substitution::Pattern($s))};
- ($s:expr) => { Source::Str($s)};
+ (var($s:expr)) => { Source::Sub(Substitution::Variable($s.to_string()))};
+ (arr($s:expr)) => { Source::Sub(Substitution::Array($s.to_string()))};
+ (pat($s:expr)) => { Source::Sub(Substitution::Pattern($s.to_string()))};
+ ($s:expr) => { Source::Str($s.to_string())};
);
#[derive(Debug, Clone, PartialEq)]
-pub enum Source<'a> {
- Str(&'a str),
- Sub(Substitution<'a>),
+pub enum Source {
+ Str(String),
+ Sub(Substitution),
}
#[derive(Debug, Clone, PartialEq)]
-pub enum Substitution<'a> {
- Variable(&'a str),
- Array(&'a str),
- Pattern(&'a str),
+pub enum Substitution {
+ Variable(String),
+ Array(String),
+ Pattern(String),
+}
+
+pub struct DirNode<'a> {
+ path: PathBuf,
+ opts: &'a Options,
+ global: MetaFile,
+ files: Vec<MetaFile>,
+ dirs: Vec<DirNode<'a>>,
+}
+
+impl<'a> DirNode<'a> {
+ pub fn build(path: PathBuf, opts: &'a Options) -> Result<Self> {
+ assert!(path.is_dir() && path.exists());
+
+ fs::create_dir(opts.build.join(path.strip_prefix(&opts.source)?))?;
+
+ let files: Vec<MetaFile> = Vec::new();
+ let dirs: Vec<DirNode> = Vec::new();
+ let global = MetaFile::new();
+
+ Ok(Self {
+ path: path.to_path_buf(),
+ opts,
+ global,
+ files,
+ dirs,
+ })
+ }
+
+ // parses all contained files and directories and pushes
+ // parsed structures into the files and directories vectors
+ pub fn map(&mut self, global: &'a MetaFile) -> Result<()> {
+ for f in fs::read_dir(&self.path)?.into_iter() {
+ let file = f?.path();
+
+ if file.is_dir() {
+ let dir = DirNode::build(file, self.opts)?;
+ self.dirs.push(dir);
+ } else if file.file_name().and_then(|f| f.to_str()) == Some("default.meta") {
+ let mut new_global = MetaFile::build(file)?;
+ new_global.merge(global);
+ self.global = new_global;
+ } else if file.extension().and_then(|f| f.to_str()) == Some("meta") {
+ let file = MetaFile::build(file)?;
+ self.files.push(file)
+ }
+ }
+
+ Ok(())
+ }
+
+ pub fn build_files(&mut self) -> Result<()> {
+ for file in self.files.iter_mut() {
+ file.merge(&self.global);
+ if let Err(e) = build_metafile(file, self.opts) {
+ if self.opts.force {
+ // print a line to stderr about failure but continue with other files
+ eprintln!("ignoring {}: {}", file.path.display(), e);
+ continue;
+ } else {
+ return Err(e.wrap_err(eyre!("{}:", file.path.display())));
+ }
+ }
+ }
+ Ok(())
+ }
+
+ pub fn build_dir(&mut self) -> Result<()> {
+ self.build_files()?;
+
+ for dir in self.dirs.iter_mut() {
+ dir.build_dir()?;
+ }
+
+ Ok(())
+ }
}
/// Clean build directory before building site [FALSE]
#[arg(long, default_value_t = false)]
clean: bool,
+ /// Don't convert markdown to html. Runs even if pandoc is not installed [FALSE]
+ #[arg(long, default_value_t = false)]
+ no_pandoc: bool,
}
#[derive(Debug, Clone, Default)]
pub force: bool,
pub undefined: bool,
pub clean: bool,
+ pub no_pandoc: bool,
}
impl Options {
force: false,
undefined: false,
clean: false,
+ no_pandoc: false,
}
}
}
-#[macro_export]
-macro_rules! log {
- ($opts:ident, $string:expr, $level:expr) => {
- if $opts.verbose >= $level && !$opts.quiet {
- println!("{}", $string);
- }
- };
-}
+impl TryFrom<Opts> for Options {
+ type Error = color_eyre::eyre::Error;
+ fn try_from(value: Opts) -> Result<Self, Self::Error> {
+ let mut options = Options::new();
-pub fn parse_opts() -> Result<Options> {
- let opts = Opts::parse();
+ options.verbose = value.verbose;
+ options.quiet = value.quiet;
+ options.force = value.force;
+ options.undefined = value.undefined;
+ options.clean = value.clean;
+ options.no_pandoc = value.no_pandoc;
- let mut options = Options::new();
- options.verbose = opts.verbose;
- options.quiet = opts.quiet;
- options.force = opts.force;
- options.undefined = opts.undefined;
- options.clean = opts.clean;
+ if let Some(root) = value.root.as_deref() {
+ options.root = PathBuf::from(root).canonicalize()?;
+ } else {
+ options.root = std::env::current_dir()?;
+ }
- if let Some(root) = opts.root.as_deref() {
- options.root = PathBuf::from(root).canonicalize()?;
- } else {
- options.root = std::env::current_dir()?;
- }
+ if let Some(source) = value.source.as_deref() {
+ options.source = PathBuf::from(source).canonicalize()?;
+ } else {
+ options.source = options.root.join("source");
+ }
- if let Some(source) = opts.source.as_deref() {
- options.source = PathBuf::from(source).canonicalize()?;
- } else {
- options.source = options.root.join("source");
- }
+ if let Some(build) = value.build.as_deref() {
+ options.build = PathBuf::from(build).canonicalize()?;
+ } else {
+ options.build = options.root.join("build");
+ }
- if let Some(build) = opts.build.as_deref() {
- options.build = PathBuf::from(build).canonicalize()?;
- } else {
- options.build = options.root.join("build");
- }
+ if let Some(pattern) = value.pattern.as_deref() {
+ options.pattern = PathBuf::from(pattern).canonicalize()?;
+ } else {
+ options.pattern = options.root.join("pattern");
+ }
- if let Some(pattern) = opts.pattern.as_deref() {
- options.pattern = PathBuf::from(pattern).canonicalize()?;
- } else {
- options.pattern = options.root.join("pattern");
+ Ok(options)
}
+}
- Ok(options)
+#[macro_export]
+macro_rules! log {
+ ($opts:ident, $string:expr, $level:expr) => {
+ if $opts.verbose >= $level && !$opts.quiet {
+ println!("{}", $string);
+ }
+ };
}
#[grammar = "meta.pest"]
pub struct MetaParser;
-pub fn parse_file(file: &str) -> Result<MetaFile> {
- let meta_source = MetaParser::parse(Rule::file, file)
+pub fn parse_file<'a>(file: String) -> Result<MetaFile> {
+ let meta_source = MetaParser::parse(Rule::file, &file)
.wrap_err("parser error")?
.next()
.unwrap();
- Ok(parse_pair(meta_source))
+ let metafile = parse_pair(meta_source);
+ Ok(metafile)
}
fn parse_pair(pair: Pair<Rule>) -> MetaFile {
meta_file
}
-fn parse_defs(pairs: Pairs<Rule>) -> HashMap<&'_ str, &'_ str> {
+fn parse_defs(pairs: Pairs<Rule>) -> HashMap<String, String> {
let mut map = HashMap::new();
for pair in pairs {
if Rule::assign == pair.as_rule() {
let (key, val) = parse_assign(pair);
- map.insert(key, val);
+ map.insert(key.to_string(), val.to_string());
}
}
map
}
-fn parse_array_defs(pairs: Pairs<Rule>) -> HashMap<&str, Vec<&str>> {
+fn parse_array_defs(pairs: Pairs<Rule>) -> HashMap<String, Vec<String>> {
let mut map = HashMap::new();
for pair in pairs {
if Rule::assign == pair.as_rule() {
let (key, val) = parse_assign_array(pair);
- map.insert(key, val);
+ map.insert(key.to_string(), val);
}
}
map
Rule::var_sub => vec.push(source!(var(parse_sub(pair)))),
Rule::arr_sub => vec.push(source!(arr(parse_sub(pair)))),
Rule::pat_sub => vec.push(source!(pat(parse_sub(pair)))),
- Rule::char_seq => vec.push(source!(pair.as_str())),
+ Rule::char_seq => vec.push(source!(pair)),
// anything that isn't a substitution is a char_seq inside source
_ => unreachable!(),
}
vec
}
-fn parse_sub(pair: Pair<Rule>) -> &'_ str {
+fn parse_sub(pair: Pair<Rule>) -> &str {
match pair.as_rule() {
Rule::var_sub | Rule::arr_sub | Rule::pat_sub => {
let str = pair.as_str();
}
}
-fn parse_assign(pair: Pair<Rule>) -> (&'_ str, &'_ str) {
+fn parse_assign(pair: Pair<Rule>) -> (&str, &str) {
let mut key = "";
let mut val = "";
(key, val)
}
-fn parse_assign_array(pair: Pair<Rule>) -> (&str, Vec<&str>) {
+fn parse_assign_array(pair: Pair<Rule>) -> (String, Vec<String>) {
let mut key = "";
let mut val = Vec::default();
}
}
- (key, val)
+ (key.to_string(), val)
}
-fn parse_array(pairs: Pairs<Rule>) -> Vec<&str> {
- let mut vec: Vec<&str> = Vec::default();
+fn parse_array(pairs: Pairs<Rule>) -> Vec<String> {
+ let mut vec: Vec<String> = Vec::default();
for pair in pairs {
if Rule::string == pair.as_rule() {
// remove surrounding quotes from values
// see parse_assign() for reasoning
let val = &tmp[1..tmp.len() - 1];
- vec.push(val);
+ vec.push(val.to_string());
}
}
macro_rules! test_str (
($s: expr) => {
- let str = $s;
+ let str = $s.to_string();
parse_file(str).unwrap();
};
);