|
|
|
@ -17,37 +17,47 @@ copied, modified, or distributed except according to those terms.
|
|
|
|
|
//! - Generating a new application configuration and directory structure (when `arse new` is called)
|
|
|
|
|
|
|
|
|
|
use std::fs::create_dir_all;
|
|
|
|
|
use std::{io::BufRead, usize};
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
use std::{io::BufRead, usize};
|
|
|
|
|
|
|
|
|
|
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand, crate_authors, crate_description, crate_version};
|
|
|
|
|
use clap::{
|
|
|
|
|
crate_authors, crate_description, crate_version, App, AppSettings, Arg, ArgMatches, SubCommand,
|
|
|
|
|
};
|
|
|
|
|
use log::{debug, error, info, trace};
|
|
|
|
|
use simplelog::{SimpleLogger, ConfigBuilder};
|
|
|
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use simplelog::{ConfigBuilder, SimpleLogger};
|
|
|
|
|
|
|
|
|
|
use super::common;
|
|
|
|
|
use super::{anyhow, Context, Result};
|
|
|
|
|
|
|
|
|
|
fn args() -> App<'static, 'static> {
|
|
|
|
|
App::new("A Rust Site Engine")
|
|
|
|
|
.version(crate_version!())
|
|
|
|
|
.author(crate_authors!())
|
|
|
|
|
.version(crate_version!())
|
|
|
|
|
.author(crate_authors!())
|
|
|
|
|
.about(crate_description!())
|
|
|
|
|
.setting(AppSettings::ArgRequiredElseHelp)
|
|
|
|
|
.arg(Arg::with_name("verbosity")
|
|
|
|
|
.short("v")
|
|
|
|
|
.multiple(true)
|
|
|
|
|
.help("Sets the log level. Default: INFO. -v = DEBUG, -vv = TRACE"))
|
|
|
|
|
.subcommand(SubCommand::with_name("run")
|
|
|
|
|
.about("Run the site server")
|
|
|
|
|
.arg(Arg::with_name("config")
|
|
|
|
|
.help("Provides the path to the server configuration file.")
|
|
|
|
|
.required(true)
|
|
|
|
|
.takes_value(true)
|
|
|
|
|
.index(1)))
|
|
|
|
|
.subcommand(SubCommand::with_name("new")
|
|
|
|
|
.about("Generates a base directory structure and configuration file for a new site")
|
|
|
|
|
)
|
|
|
|
|
.arg(
|
|
|
|
|
Arg::with_name("verbosity")
|
|
|
|
|
.short("v")
|
|
|
|
|
.multiple(true)
|
|
|
|
|
.help("Sets the log level. Default: INFO. -v = DEBUG, -vv = TRACE"),
|
|
|
|
|
)
|
|
|
|
|
.subcommand(
|
|
|
|
|
SubCommand::with_name("run")
|
|
|
|
|
.about("Run the site server")
|
|
|
|
|
.arg(
|
|
|
|
|
Arg::with_name("config")
|
|
|
|
|
.help("Provides the path to the server configuration file.")
|
|
|
|
|
.required(true)
|
|
|
|
|
.takes_value(true)
|
|
|
|
|
.index(1),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.subcommand(
|
|
|
|
|
SubCommand::with_name("new").about(
|
|
|
|
|
"Generates a base directory structure and configuration file for a new site",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Processes command-line arguments and configures logging.
|
|
|
|
@ -58,34 +68,36 @@ pub(crate) fn load() -> Result<AppConfig> {
|
|
|
|
|
let matches = args().get_matches();
|
|
|
|
|
|
|
|
|
|
// Create a Config with ISO timestamps
|
|
|
|
|
let log_config = ConfigBuilder::new()
|
|
|
|
|
.set_time_format_str("%+")
|
|
|
|
|
.build();
|
|
|
|
|
let log_config = ConfigBuilder::new().set_time_format_str("%+").build();
|
|
|
|
|
|
|
|
|
|
// After this block logging is configured at the specified level
|
|
|
|
|
match matches.occurrences_of("verbosity") {
|
|
|
|
|
0 => SimpleLogger::init(log::LevelFilter::Info, log_config).context("failed to initialize logger at level - INFO")?,
|
|
|
|
|
1 => SimpleLogger::init(log::LevelFilter::Debug, log_config).context("failed to initialize logger at level - DEBUG")?,
|
|
|
|
|
_ => SimpleLogger::init(log::LevelFilter::Trace, log_config).context("failed to initialize logger at level - TRACE")?,
|
|
|
|
|
0 => SimpleLogger::init(log::LevelFilter::Info, log_config)
|
|
|
|
|
.context("failed to initialize logger at level - INFO")?,
|
|
|
|
|
1 => SimpleLogger::init(log::LevelFilter::Debug, log_config)
|
|
|
|
|
.context("failed to initialize logger at level - DEBUG")?,
|
|
|
|
|
_ => SimpleLogger::init(log::LevelFilter::Trace, log_config)
|
|
|
|
|
.context("failed to initialize logger at level - TRACE")?,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info!("Logging started");
|
|
|
|
|
|
|
|
|
|
debug!("Processing subcommands");
|
|
|
|
|
if matches.is_present("run") {
|
|
|
|
|
trace!("Application called with `run` subcommand - loading config from disk");
|
|
|
|
|
config = runner_config(matches);
|
|
|
|
|
trace!("Application called with `run` subcommand - loading config from disk");
|
|
|
|
|
config = runner_config(matches);
|
|
|
|
|
} else if matches.is_present("new") {
|
|
|
|
|
trace!("Application called with `new` subcommand - creating config from user input");
|
|
|
|
|
let reader = std::io::stdin();
|
|
|
|
|
let mut reader = reader.lock();
|
|
|
|
|
let current_path = std::env::current_dir().context("failed to get current working directory")?;
|
|
|
|
|
let _ = AppConfig::generate(current_path, &mut reader);
|
|
|
|
|
std::process::exit(0);
|
|
|
|
|
trace!("Application called with `new` subcommand - creating config from user input");
|
|
|
|
|
let reader = std::io::stdin();
|
|
|
|
|
let mut reader = reader.lock();
|
|
|
|
|
let current_path =
|
|
|
|
|
std::env::current_dir().context("failed to get current working directory")?;
|
|
|
|
|
let _ = AppConfig::generate(current_path, &mut reader);
|
|
|
|
|
std::process::exit(0);
|
|
|
|
|
} else {
|
|
|
|
|
let msg = "Unable to load configuration".to_owned();
|
|
|
|
|
error!("{}", &msg);
|
|
|
|
|
config = Err(anyhow!("{}", msg));
|
|
|
|
|
let msg = "Unable to load configuration".to_owned();
|
|
|
|
|
error!("{}", &msg);
|
|
|
|
|
config = Err(anyhow!("{}", msg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config
|
|
|
|
@ -93,34 +105,38 @@ pub(crate) fn load() -> Result<AppConfig> {
|
|
|
|
|
|
|
|
|
|
fn runner_config(m: ArgMatches) -> Result<AppConfig> {
|
|
|
|
|
if let Some(run) = m.subcommand_matches("run") {
|
|
|
|
|
let value = run.value_of("config").unwrap();
|
|
|
|
|
let config = AppConfig::from_path(value)?;
|
|
|
|
|
Ok(config)
|
|
|
|
|
let value = run.value_of("config").unwrap();
|
|
|
|
|
let config = AppConfig::from_path(value)?;
|
|
|
|
|
Ok(config)
|
|
|
|
|
} else {
|
|
|
|
|
let msg = "Failed to read arguments for 'run' subcommand".to_owned();
|
|
|
|
|
error!("{}", &msg);
|
|
|
|
|
Err(anyhow!("{}", msg))
|
|
|
|
|
let msg = "Failed to read arguments for 'run' subcommand".to_owned();
|
|
|
|
|
error!("{}", &msg);
|
|
|
|
|
Err(anyhow!("{}", msg))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_input<R: BufRead>(prompt: &str, reader: &mut R) -> Result<String> {
|
|
|
|
|
let mut buf = String::new();
|
|
|
|
|
println!("{}", prompt);
|
|
|
|
|
reader.read_line(&mut buf)
|
|
|
|
|
reader
|
|
|
|
|
.read_line(&mut buf)
|
|
|
|
|
.context("failed reading input from user")?;
|
|
|
|
|
let buf = String::from(buf
|
|
|
|
|
.trim_start_matches(char::is_whitespace)
|
|
|
|
|
.trim_end_matches(char::is_whitespace));
|
|
|
|
|
let buf = String::from(
|
|
|
|
|
buf.trim_start_matches(char::is_whitespace)
|
|
|
|
|
.trim_end_matches(char::is_whitespace),
|
|
|
|
|
);
|
|
|
|
|
Ok(buf)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn csv_to_vec(csv: &str) -> Vec<String> {
|
|
|
|
|
debug!("Creating Vec<String> from csv topics: {}", &csv);
|
|
|
|
|
let val_vec: Vec<String> = csv.split(',')
|
|
|
|
|
.map(|s| s
|
|
|
|
|
.trim_start_matches(char::is_whitespace)
|
|
|
|
|
.trim_end_matches(char::is_whitespace)
|
|
|
|
|
.to_string())
|
|
|
|
|
let val_vec: Vec<String> = csv
|
|
|
|
|
.split(',')
|
|
|
|
|
.map(|s| {
|
|
|
|
|
s.trim_start_matches(char::is_whitespace)
|
|
|
|
|
.trim_end_matches(char::is_whitespace)
|
|
|
|
|
.to_string()
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
val_vec
|
|
|
|
@ -139,16 +155,22 @@ pub(crate) struct Site {
|
|
|
|
|
impl Site {
|
|
|
|
|
/// Creates a new [`Site`] from user input.
|
|
|
|
|
pub(crate) fn new_from_input<R: BufRead>(reader: &mut R) -> Result<Site> {
|
|
|
|
|
let name = get_input("Please enter a name for the site: ", reader)?;
|
|
|
|
|
let author = get_input("Please enter the site author's name: ", reader)?;
|
|
|
|
|
let url = get_input("Please enter the base URL for your site: ", reader)?;
|
|
|
|
|
let topics = get_input("Please enter comma-separated site topics: ", reader)?;
|
|
|
|
|
let topics = csv_to_vec(&topics);
|
|
|
|
|
let template = "default.tmpl".to_owned();
|
|
|
|
|
let site = Site { name, author, url, template, topics };
|
|
|
|
|
|
|
|
|
|
trace!("Site: {:?}", site);
|
|
|
|
|
Ok(site)
|
|
|
|
|
let name = get_input("Please enter a name for the site: ", reader)?;
|
|
|
|
|
let author = get_input("Please enter the site author's name: ", reader)?;
|
|
|
|
|
let url = get_input("Please enter the base URL for your site: ", reader)?;
|
|
|
|
|
let topics = get_input("Please enter comma-separated site topics: ", reader)?;
|
|
|
|
|
let topics = csv_to_vec(&topics);
|
|
|
|
|
let template = "default.tmpl".to_owned();
|
|
|
|
|
let site = Site {
|
|
|
|
|
name,
|
|
|
|
|
author,
|
|
|
|
|
url,
|
|
|
|
|
template,
|
|
|
|
|
topics,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
trace!("Site: {:?}", site);
|
|
|
|
|
Ok(site)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -162,10 +184,10 @@ pub(crate) struct Server {
|
|
|
|
|
impl Server {
|
|
|
|
|
/// Creates a new [`Server`] instance with defaults: `0.0.0.0:9090`.
|
|
|
|
|
pub(crate) fn new() -> Server {
|
|
|
|
|
Server {
|
|
|
|
|
bind: "0.0.0.0".to_owned(),
|
|
|
|
|
port: 9090,
|
|
|
|
|
}
|
|
|
|
|
Server {
|
|
|
|
|
bind: "0.0.0.0".to_owned(),
|
|
|
|
|
port: 9090,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -181,14 +203,14 @@ impl DocPaths {
|
|
|
|
|
///
|
|
|
|
|
/// Relative paths are supported, and can be changed in the resulting `config.toml`
|
|
|
|
|
pub(crate) fn new<P: AsRef<Path>>(dir: P) -> DocPaths {
|
|
|
|
|
debug!("Creating site DocPaths");
|
|
|
|
|
let dir = dir.as_ref().display();
|
|
|
|
|
let templates = format!("{}/site/templates", dir);
|
|
|
|
|
let webroot = format!("{}/site/webroot", dir);
|
|
|
|
|
let docpaths = DocPaths { templates, webroot };
|
|
|
|
|
|
|
|
|
|
trace!("Site DocPaths: {:?}", docpaths);
|
|
|
|
|
docpaths
|
|
|
|
|
debug!("Creating site DocPaths");
|
|
|
|
|
let dir = dir.as_ref().display();
|
|
|
|
|
let templates = format!("{}/site/templates", dir);
|
|
|
|
|
let webroot = format!("{}/site/webroot", dir);
|
|
|
|
|
let docpaths = DocPaths { templates, webroot };
|
|
|
|
|
|
|
|
|
|
trace!("Site DocPaths: {:?}", docpaths);
|
|
|
|
|
docpaths
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -203,61 +225,70 @@ pub(crate) struct AppConfig {
|
|
|
|
|
impl AppConfig {
|
|
|
|
|
/// Loads an existing [`AppConfig`] from disk.
|
|
|
|
|
pub(crate) fn from_path<T: AsRef<Path>>(config: T) -> Result<AppConfig> {
|
|
|
|
|
debug!("Loading site configuration from {}", &config.as_ref().display());
|
|
|
|
|
let config_string = std::fs::read_to_string(&config)
|
|
|
|
|
.with_context(|| format!("failed reading '{}' to string", &config.as_ref().display()))?;
|
|
|
|
|
|
|
|
|
|
trace!("Parsing configuration TOML");
|
|
|
|
|
let app_config: AppConfig = toml::from_str(&config_string)
|
|
|
|
|
.context("failed to parse TOML")?;
|
|
|
|
|
|
|
|
|
|
Ok(app_config)
|
|
|
|
|
debug!(
|
|
|
|
|
"Loading site configuration from {}",
|
|
|
|
|
&config.as_ref().display()
|
|
|
|
|
);
|
|
|
|
|
let config_string = std::fs::read_to_string(&config).with_context(|| {
|
|
|
|
|
format!("failed reading '{}' to string", &config.as_ref().display())
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
trace!("Parsing configuration TOML");
|
|
|
|
|
let app_config: AppConfig =
|
|
|
|
|
toml::from_str(&config_string).context("failed to parse TOML")?;
|
|
|
|
|
|
|
|
|
|
Ok(app_config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generates a new [`AppConfig`] from user input, and creates necessary [`DocPaths`] paths on disk.
|
|
|
|
|
pub(crate) fn generate<P: AsRef<Path>, R: BufRead>(dir: P, reader: &mut R) -> Result<AppConfig> {
|
|
|
|
|
info!("Generating new site configuration");
|
|
|
|
|
let docpaths = DocPaths::new(&dir);
|
|
|
|
|
let site = Site::new_from_input(reader)?;
|
|
|
|
|
let server = Server::new();
|
|
|
|
|
|
|
|
|
|
let config = AppConfig {
|
|
|
|
|
site,
|
|
|
|
|
server,
|
|
|
|
|
docpaths,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config.create_paths()
|
|
|
|
|
.context("failed while creating site paths")?;
|
|
|
|
|
config.write(&dir)
|
|
|
|
|
.context("failed to write site config to disk")?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
pub(crate) fn generate<P: AsRef<Path>, R: BufRead>(
|
|
|
|
|
dir: P,
|
|
|
|
|
reader: &mut R,
|
|
|
|
|
) -> Result<AppConfig> {
|
|
|
|
|
info!("Generating new site configuration");
|
|
|
|
|
let docpaths = DocPaths::new(&dir);
|
|
|
|
|
let site = Site::new_from_input(reader)?;
|
|
|
|
|
let server = Server::new();
|
|
|
|
|
|
|
|
|
|
let config = AppConfig {
|
|
|
|
|
site,
|
|
|
|
|
server,
|
|
|
|
|
docpaths,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config
|
|
|
|
|
.create_paths()
|
|
|
|
|
.context("failed while creating site paths")?;
|
|
|
|
|
config
|
|
|
|
|
.write(&dir)
|
|
|
|
|
.context("failed to write site config to disk")?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_paths(&self) -> Result<()> {
|
|
|
|
|
info!("Creating site filesystem tree");
|
|
|
|
|
create_dir_all(&self.docpaths.templates)?;
|
|
|
|
|
create_dir_all(format!("{}/static", &self.docpaths.webroot))?;
|
|
|
|
|
create_dir_all(format!("{}/main/ext", &self.docpaths.webroot))?;
|
|
|
|
|
create_dir_all(format!("{}/main/posts", &self.docpaths.webroot))?;
|
|
|
|
|
|
|
|
|
|
for topic in &self.site.topics {
|
|
|
|
|
let topic = common::slugify(&topic);
|
|
|
|
|
|
|
|
|
|
create_dir_all(format!("{}/{}/ext", &self.docpaths.webroot, &topic))?;
|
|
|
|
|
create_dir_all(format!("{}/{}/posts", &self.docpaths.webroot, &topic))?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
info!("Creating site filesystem tree");
|
|
|
|
|
create_dir_all(&self.docpaths.templates)?;
|
|
|
|
|
create_dir_all(format!("{}/static", &self.docpaths.webroot))?;
|
|
|
|
|
create_dir_all(format!("{}/main/ext", &self.docpaths.webroot))?;
|
|
|
|
|
create_dir_all(format!("{}/main/posts", &self.docpaths.webroot))?;
|
|
|
|
|
|
|
|
|
|
for topic in &self.site.topics {
|
|
|
|
|
let topic = common::slugify(topic);
|
|
|
|
|
|
|
|
|
|
create_dir_all(format!("{}/{}/ext", &self.docpaths.webroot, &topic))?;
|
|
|
|
|
create_dir_all(format!("{}/{}/posts", &self.docpaths.webroot, &topic))?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Writes an [`AppConfig`] to disk in the current working directory as `config.toml`.
|
|
|
|
|
fn write<P: AsRef<Path>>(&self, dir: P) -> Result<()> {
|
|
|
|
|
info!("Writing site configuration to disk");
|
|
|
|
|
let config = toml::to_string_pretty(&self).context("failure creating TOML")?;
|
|
|
|
|
let conf_path = &dir.as_ref().join("config.toml");
|
|
|
|
|
common::str_to_ro_file(&config, &conf_path)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
info!("Writing site configuration to disk");
|
|
|
|
|
let config = toml::to_string_pretty(&self).context("failure creating TOML")?;
|
|
|
|
|
let conf_path = &dir.as_ref().join("config.toml");
|
|
|
|
|
common::str_to_ro_file(&config, &conf_path)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -267,53 +298,68 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn build_run_config() {
|
|
|
|
|
let arg_vec = vec!["arse", "run", "./test_files/test-config.toml"];
|
|
|
|
|
let matches = args().get_matches_from(arg_vec);
|
|
|
|
|
let config = runner_config(matches);
|
|
|
|
|
assert!(config.is_ok());
|
|
|
|
|
let arg_vec = vec!["arse", "run", "./test_files/test-config.toml"];
|
|
|
|
|
let matches = args().get_matches_from(arg_vec);
|
|
|
|
|
let config = runner_config(matches);
|
|
|
|
|
assert!(config.is_ok());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn build_config_from_input() {
|
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
|
|
|
// Setup all target fields
|
|
|
|
|
let mut src: &[u8] = b"Site Name\nAuthor Name\nhttps://my.example.site\nOne, Two, Three, And More\n";
|
|
|
|
|
let config = AppConfig::generate(&dir, &mut src);
|
|
|
|
|
assert!(config.is_ok());
|
|
|
|
|
|
|
|
|
|
let tmp_dir = &dir.path();
|
|
|
|
|
let config_path = &tmp_dir.join("config.toml");
|
|
|
|
|
let site = &tmp_dir.join("site");
|
|
|
|
|
let templates = &tmp_dir.join("site/templates");
|
|
|
|
|
let webroot = &tmp_dir.join("site/webroot");
|
|
|
|
|
let static_data = &tmp_dir.join("site/webroot/static");
|
|
|
|
|
let main_ext = &tmp_dir.join("site/webroot/main/ext");
|
|
|
|
|
let main_posts = &tmp_dir.join("site/webroot/main/posts");
|
|
|
|
|
let one_ext = &tmp_dir.join("site/webroot/one/ext");
|
|
|
|
|
let one_posts = &tmp_dir.join("site/webroot/one/posts");
|
|
|
|
|
let two_ext = &tmp_dir.join("site/webroot/two/ext");
|
|
|
|
|
let two_posts = &tmp_dir.join("site/webroot/two/posts");
|
|
|
|
|
let three_ext = &tmp_dir.join("site/webroot/three/ext");
|
|
|
|
|
let three_posts = &tmp_dir.join("site/webroot/three/posts");
|
|
|
|
|
let and_more_ext = &tmp_dir.join("site/webroot/and-more/ext");
|
|
|
|
|
let and_more_posts = &tmp_dir.join("site/webroot/and-more/posts");
|
|
|
|
|
let core = vec![config_path, site, templates,
|
|
|
|
|
webroot, static_data, main_ext, main_posts,
|
|
|
|
|
one_ext, one_posts, two_ext, two_posts,
|
|
|
|
|
three_ext, three_posts, and_more_ext, and_more_posts];
|
|
|
|
|
for p in core {
|
|
|
|
|
assert!(Path::new(p).exists())
|
|
|
|
|
}
|
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
|
|
|
// Setup all target fields
|
|
|
|
|
let mut src: &[u8] =
|
|
|
|
|
b"Site Name\nAuthor Name\nhttps://my.example.site\nOne, Two, Three, And More\n";
|
|
|
|
|
let config = AppConfig::generate(&dir, &mut src);
|
|
|
|
|
assert!(config.is_ok());
|
|
|
|
|
|
|
|
|
|
let tmp_dir = &dir.path();
|
|
|
|
|
let config_path = &tmp_dir.join("config.toml");
|
|
|
|
|
let site = &tmp_dir.join("site");
|
|
|
|
|
let templates = &tmp_dir.join("site/templates");
|
|
|
|
|
let webroot = &tmp_dir.join("site/webroot");
|
|
|
|
|
let static_data = &tmp_dir.join("site/webroot/static");
|
|
|
|
|
let main_ext = &tmp_dir.join("site/webroot/main/ext");
|
|
|
|
|
let main_posts = &tmp_dir.join("site/webroot/main/posts");
|
|
|
|
|
let one_ext = &tmp_dir.join("site/webroot/one/ext");
|
|
|
|
|
let one_posts = &tmp_dir.join("site/webroot/one/posts");
|
|
|
|
|
let two_ext = &tmp_dir.join("site/webroot/two/ext");
|
|
|
|
|
let two_posts = &tmp_dir.join("site/webroot/two/posts");
|
|
|
|
|
let three_ext = &tmp_dir.join("site/webroot/three/ext");
|
|
|
|
|
let three_posts = &tmp_dir.join("site/webroot/three/posts");
|
|
|
|
|
let and_more_ext = &tmp_dir.join("site/webroot/and-more/ext");
|
|
|
|
|
let and_more_posts = &tmp_dir.join("site/webroot/and-more/posts");
|
|
|
|
|
let core = vec![
|
|
|
|
|
config_path,
|
|
|
|
|
site,
|
|
|
|
|
templates,
|
|
|
|
|
webroot,
|
|
|
|
|
static_data,
|
|
|
|
|
main_ext,
|
|
|
|
|
main_posts,
|
|
|
|
|
one_ext,
|
|
|
|
|
one_posts,
|
|
|
|
|
two_ext,
|
|
|
|
|
two_posts,
|
|
|
|
|
three_ext,
|
|
|
|
|
three_posts,
|
|
|
|
|
and_more_ext,
|
|
|
|
|
and_more_posts,
|
|
|
|
|
];
|
|
|
|
|
for p in core {
|
|
|
|
|
assert!(Path::new(p).exists())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn handle_csv_topics() {
|
|
|
|
|
let reference_topics: Vec<String> = vec!["One".to_owned(),
|
|
|
|
|
"Two".to_owned(),
|
|
|
|
|
"Three".to_owned(),
|
|
|
|
|
"And More".to_owned()];
|
|
|
|
|
let topics = "One, Two, Three, And More".to_owned();
|
|
|
|
|
assert_eq!(reference_topics, csv_to_vec(&topics))
|
|
|
|
|
let reference_topics: Vec<String> = vec![
|
|
|
|
|
"One".to_owned(),
|
|
|
|
|
"Two".to_owned(),
|
|
|
|
|
"Three".to_owned(),
|
|
|
|
|
"And More".to_owned(),
|
|
|
|
|
];
|
|
|
|
|
let topics = "One, Two, Three, And More".to_owned();
|
|
|
|
|
assert_eq!(reference_topics, csv_to_vec(&topics))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|