mod builder;
pub mod config;
pub mod error;
mod include;
use error::Result;
use crate::project::config::Config;
use crate::project::error::Error::InvalidProject;
#[cfg(feature = "progress")]
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::path::PathBuf;
#[cfg(feature = "progress")]
use std::time::Duration;
use tracing::debug;
#[cfg(not(feature = "progress"))]
use tracing::info;
pub struct Project {
path: PathBuf,
config: Config,
}
impl Project {
pub fn create(path: PathBuf) -> Result<Self> {
debug!("creating project at {}", path.display());
std::fs::create_dir_all(&path)?;
let config = Config::default();
config.write_to_file(&path.join("ines.toml"))?;
std::fs::write(path.join("Cargo.toml"), include::CARGO_TOML)?;
std::fs::write(path.join("build.rs"), include::BUILD_RS)?;
std::fs::write(path.join("empty.rs"), "#![no_std]")?;
std::fs::create_dir_all(path.join("src").join("components"))?;
std::fs::create_dir_all(path.join("src").join("views"))?;
std::fs::write(path.join("src").join("index.html"), include::INDEX_HTML)?;
std::fs::write(
path.join("src").join("index.js"),
r#"import "./components/load.js""#,
)?;
std::fs::write(path.join("src").join("index.css"), "")?;
std::fs::write(path.join("src").join("components").join("load.js"), "")?;
std::fs::write(
path.join("src").join("components").join("component.js"),
include::COMPONENT_JS,
)?;
Ok(Self { path, config })
}
pub fn load_from_path(path: PathBuf) -> Result<Self> {
debug!("loading project at {}", path.display());
Self::is_file_or_dir(&path.join("ines.toml"))
.take_if(|e| *e)
.ok_or(InvalidProject("ines.toml not found".into()))?;
Self::is_file_or_dir(&path.join("Cargo.toml"))
.take_if(|e| *e)
.ok_or(InvalidProject("Cargo.toml not found".into()))?;
Self::is_file_or_dir(&path.join("build.rs"))
.take_if(|e| *e)
.ok_or(InvalidProject("build.rs not found".into()))?;
Self::is_file_or_dir(&path.join("empty.rs"))
.take_if(|e| *e)
.ok_or(InvalidProject("empty.rs not found".into()))?;
Self::is_file_or_dir(&path.join("src").join("index.html"))
.take_if(|e| *e)
.ok_or(InvalidProject("src/index.html not found".into()))?;
Self::is_file_or_dir(&path.join("src").join("index.css"))
.take_if(|e| *e)
.ok_or(InvalidProject("src/index.css not found".into()))?;
Self::is_file_or_dir(&path.join("src").join("index.js"))
.take_if(|e| *e)
.ok_or(InvalidProject("src/index.js not found".into()))?;
Self::is_file_or_dir(&path.join("src").join("components").join("component.js"))
.take_if(|e| *e)
.ok_or(InvalidProject(
"src/components/component.js not found".into(),
))?;
Self::is_file_or_dir(&path.join("src").join("components").join("load.js"))
.take_if(|e| *e)
.ok_or(InvalidProject("src/components/load.js not found".into()))?;
Self::is_file_or_dir(&path.join("src").join("views"))
.take_if(|e| !*e)
.ok_or(InvalidProject("src/views/ not found".into()))?;
Self::_load_from_path(path)
}
fn is_file_or_dir(path: &PathBuf) -> Option<bool> {
std::fs::metadata(path)
.ok()
.take_if(|e| e.is_dir() || e.is_file())
.map(|e| e.is_file())
}
fn _load_from_path(path: PathBuf) -> Result<Self> {
let config = Config::from_file(&path.join("ines.toml"))?;
Ok(Self { path, config })
}
pub fn build(&self) -> Result<()> {
#[cfg(feature = "progress")]
let multi = MultiProgress::new();
#[cfg(feature = "progress")]
let run_style = ProgressStyle::with_template("{spinner:.cyan} {msg} ({elapsed})")
.unwrap()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ");
#[cfg(feature = "progress")]
let finish_style =
ProgressStyle::with_template("{prefix:.green} {msg} (in {elapsed})").unwrap();
#[cfg(feature = "progress")]
let pb = {
let pb = multi.add(
ProgressBar::new_spinner()
.with_message("Copying base files")
.with_style(run_style.clone()),
);
pb.enable_steady_tick(Duration::from_millis(200));
pb
};
let out_dir = self.path.join(&self.config.out);
_ = std::fs::remove_dir_all(&out_dir);
let strip = self.path.join("src");
std::fs::create_dir_all(out_dir.join("components"))?;
std::fs::copy(
strip.join("components").join("load.js"),
out_dir.join("components").join("load.js"),
)?;
std::fs::copy(
strip.join("components").join("component.js"),
out_dir.join("components").join("component.js"),
)?;
#[cfg(feature = "progress")]
{
pb.set_style(finish_style.clone());
pb.set_message("Base files copied");
pb.set_prefix("✓");
pb.finish();
}
#[cfg(feature = "progress")]
let pb = {
let pb = multi.add(
ProgressBar::new_spinner()
.with_message("Getting components")
.with_style(run_style.clone()),
);
pb.enable_steady_tick(Duration::from_millis(200));
pb
};
#[cfg(not(feature = "progress"))]
info!("getting components");
let components = builder::scan_components(strip.join("components"))?;
#[cfg(feature = "progress")]
{
pb.set_style(finish_style.clone());
pb.set_message(format!("Got {} components", components.len()));
pb.set_prefix("✓");
pb.finish();
}
#[cfg(not(feature = "progress"))]
info!("got {} components", components.len());
#[cfg(feature = "progress")]
let pb = {
let pb = multi.add(
ProgressBar::new_spinner()
.with_message("Building component")
.with_style(
ProgressStyle::with_template(
"{spinner:.cyan} {msg} {prefix:.grey} ({elapsed})",
)
.unwrap()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
),
);
pb.enable_steady_tick(Duration::from_millis(200));
pb
};
let mut htmls = vec![];
for component in components {
#[cfg(feature = "progress")]
pb.set_prefix(component.name());
#[cfg(not(feature = "progress"))]
info!("building component {}", component.name());
std::fs::create_dir_all(
out_dir.join("components").join(
component
.get_base()
.strip_prefix(strip.join("components"))
.unwrap(),
),
)?;
std::fs::write(
out_dir
.join("components")
.join(
component
.get_base()
.strip_prefix(strip.join("components"))
.unwrap(),
)
.join(component.filename()),
component.build_js()?,
)?;
htmls.push(component.build_template()?);
}
#[cfg(feature = "progress")]
{
pb.set_prefix("");
pb.finish();
}
#[cfg(feature = "progress")]
let pb = {
let pb = multi.add(
ProgressBar::new_spinner()
.with_message("Generating index and assets")
.with_style(run_style.clone()),
);
pb.enable_steady_tick(Duration::from_millis(200));
pb
};
#[cfg(not(feature = "progress"))]
info!("generating index.html");
std::fs::write(
out_dir.join("index.html"),
builder::build_html(self.path.join("src").join("index.html"), htmls.join("\n"))?,
)?;
#[cfg(not(feature = "progress"))]
info!("copying assets");
for entry in std::fs::read_dir(self.path.join("src"))?.flatten() {
match entry.file_name().to_str() {
Some("index.html") | Some("components") | Some("views") => {}
_ => {
debug!(entry=?entry.path(), out=?out_dir.join(entry.file_name()));
builder::copy(entry.path(), out_dir.join(entry.file_name()))?;
}
}
}
#[cfg(feature = "progress")]
{
pb.set_style(finish_style.clone());
pb.set_message("Generated index and assets");
pb.set_prefix("✓");
pb.finish();
}
#[cfg(feature = "progress")]
let pb = {
let pb = multi.add(
ProgressBar::new_spinner()
.with_message("Generating redirect")
.with_style(run_style),
);
pb.enable_steady_tick(Duration::from_millis(200));
pb
};
#[cfg(not(feature = "progress"))]
info!("Generating redirects");
if let Some(redirects) = &self.config.generate_redirects {
for redirect in redirects {
let path = redirect
.split("/")
.filter(|e| !e.is_empty())
.collect::<Vec<_>>()
.join("/");
std::fs::create_dir_all(out_dir.join(&path))?;
std::fs::write(
out_dir.join(&path).join("index.html"),
builder::generate_redirect(redirect),
)?;
}
}
#[cfg(feature = "progress")]
{
pb.set_style(finish_style);
pb.set_message("Generated redirects");
pb.set_prefix("✓");
pb.finish();
}
Ok(())
}
pub fn base_path(&self) -> &PathBuf {
&self.path
}
pub fn src_path(&self) -> PathBuf {
self.base_path().join("src")
}
pub fn components_path(&self) -> PathBuf {
self.src_path().join("components")
}
pub fn views_path(&self) -> PathBuf {
self.src_path().join("views")
}
pub fn out_path(&self) -> PathBuf {
self.base_path().join(&self.config.out)
}
}