Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ZineScaffold to create project and issue #142

Merged
merged 7 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ markup5ever_rcdom = "0.2"
notify-debouncer-mini = { version = "0.2", default-features = false }
once_cell = "1"
parking_lot = "0.12"
promptly = "0.3"
pulldown-cmark = "0.9"
rayon = "1.6"
regex = "1.7"
Expand Down
48 changes: 4 additions & 44 deletions src/build.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
use std::{
path::{Path, PathBuf},
sync::mpsc,
time::Duration,
};
use std::{path::Path, sync::mpsc, time::Duration};

use crate::{data, entity::Zine, error::ZineError, ZineEngine};
use anyhow::{anyhow, Context, Result};
use crate::{data, ZineEngine};
use anyhow::{Context, Result};
use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode};
use tokio::sync::broadcast::Sender;
use walkdir::WalkDir;

pub async fn watch_build<P: AsRef<Path>>(
source: P,
Expand All @@ -17,7 +12,7 @@ pub async fn watch_build<P: AsRef<Path>>(
sender: Option<Sender<()>>,
) -> Result<()> {
// Use zine.toml to find root path
let (source, zine) = locate_root_zine_folder(std::fs::canonicalize(source)?)?
let (source, zine) = crate::locate_root_zine_folder(std::fs::canonicalize(source)?)?
.with_context(|| "Failed to find the root zine.toml file".to_string())?;

// Also make the dest folder joined in root path?
Expand Down Expand Up @@ -88,38 +83,3 @@ fn build(engine: &mut ZineEngine, reload: bool) -> Result<()> {
println!("Build cost: {}ms", instant.elapsed().as_millis());
Ok(())
}

/// Find the root zine file in current dir and try to parse it
fn parse_root_zine_file<P: AsRef<Path>>(path: P) -> Result<Option<Zine>> {
// Find the name in current dir
if WalkDir::new(&path).max_depth(1).into_iter().any(|entry| {
let entry = entry.as_ref().unwrap();
entry.file_name() == crate::ZINE_FILE
}) {
// Try to parse the root zine.toml as Zine instance
return Ok(Some(Zine::parse_from_toml(path)?));
}

Ok(None)
}

// Locate folder contains the root `zine.toml`, and return path info and Zine instance.
fn locate_root_zine_folder(path: PathBuf) -> Result<Option<(PathBuf, Zine)>> {
match parse_root_zine_file(&path) {
Ok(Some(zine)) => return Ok(Some((path, zine))),
Err(err) => match err.downcast::<ZineError>() {
// Found a root zine.toml, but it has invalid format
Ok(inner_err @ ZineError::InvalidRootTomlFile(_)) => return Err(anyhow!(inner_err)),
// Found a zine.toml, but it isn't a root zine.toml
Ok(ZineError::NotRootTomlFile) => {}
// No zine.toml file found
_ => {}
},
_ => {}
}

match path.parent() {
Some(parent_path) => locate_root_zine_folder(parent_path.to_path_buf()),
None => Ok(None),
}
}
2 changes: 1 addition & 1 deletion src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ impl ZineEngine {

// Copy builtin static files into dest static dir.
let dest_static_dir = self.dest.join("static");
fs::create_dir_all(&dest_static_dir)?;
fs::create_dir_all(dest_static_dir)?;

#[cfg(not(debug_assertions))]
include_dir::include_dir!("static").extract(dest_static_dir)?;
Folyd marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
89 changes: 49 additions & 40 deletions src/entity/zine.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{ensure, Context as _, Result};
use anyhow::{Context as _, Result};
use rayon::{
iter::{IntoParallelRefIterator, ParallelBridge, ParallelExtend, ParallelIterator},
slice::ParallelSliceMut,
Expand Down Expand Up @@ -75,6 +75,53 @@ impl Zine {
})?)
}

/// Parsing issue entities from dir.
pub fn parse_issue_from_dir(&mut self, source: &Path) -> Result<()> {
let content_dir = source.join(crate::ZINE_CONTENT_DIR);
if !content_dir.exists() {
println!(
"`{}` fold not found, creating it...",
crate::ZINE_CONTENT_DIR
);
fs::create_dir_all(&content_dir)?;
}

for entry in WalkDir::new(&content_dir).contents_first(true).into_iter() {
let entry = entry?;
if entry.file_name() != crate::ZINE_FILE {
continue;
}
let content = fs::read_to_string(entry.path()).with_context(|| {
format!(
"Failed to parse `zine.toml` of `{}`",
entry.path().display()
)
})?;
let mut issue = toml::from_str::<Issue>(&content)?;
let dir = entry
.path()
.components()
.fold(Vec::new(), |mut dir, component| {
let name = component.as_os_str();
if !dir.is_empty() && name != crate::ZINE_FILE {
dir.push(name.to_string_lossy().to_string());
return dir;
}

if matches!(component, Component::Normal(c) if c == crate::ZINE_CONTENT_DIR ) {
// a empty indicator we should start collect the components
dir.push(String::new());
}
dir
});
// skip the first empty indicator
issue.dir = dir[1..].join("/");
self.issues.push(issue);
}

Ok(())
}

// Get the article metadata list by author id, sorted by descending order of publishing date.
fn get_articles_by_author(&self, author_id: &str) -> Vec<ArticleRef> {
let mut items = self
Expand Down Expand Up @@ -262,45 +309,7 @@ impl Entity for Zine {
.set_topics(self.topics.keys().cloned().collect());
}

let content_dir = source.join(crate::ZINE_CONTENT_DIR);
ensure!(
content_dir.exists(),
"`{}` fold not found.",
crate::ZINE_CONTENT_DIR
);

for entry in WalkDir::new(&content_dir).contents_first(true).into_iter() {
let entry = entry?;
if entry.file_name() != crate::ZINE_FILE {
continue;
}
let content = fs::read_to_string(entry.path()).with_context(|| {
format!(
"Failed to parse `zine.toml` of `{}`",
entry.path().display()
)
})?;
let mut issue = toml::from_str::<Issue>(&content)?;
let dir = entry
.path()
.components()
.fold(Vec::new(), |mut dir, component| {
let name = component.as_os_str();
if !dir.is_empty() && name != crate::ZINE_FILE {
dir.push(name.to_string_lossy().to_string());
return dir;
}

if matches!(component, Component::Normal(c) if c == crate::ZINE_CONTENT_DIR ) {
// a empty indicator we should start collect the components
dir.push(String::new());
}
dir
});
// skip the first empty indicator
issue.dir = dir[1..].join("/");
self.issues.push(issue);
}
self.parse_issue_from_dir(source)?;

self.issues.parse(source)?;
// Sort all issues by number.
Expand Down
18 changes: 17 additions & 1 deletion src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,23 @@ use hyper::{
};
use hyper_tls::HttpsConnector;
use rayon::iter::{ParallelBridge, ParallelIterator};
use std::{fs, io::Read, path::Path};
use std::{
fs,
io::{self, ErrorKind, Read},
path::Path,
process::Command,
};

pub fn run_command(program: &str, args: &[&str]) -> Result<String, io::Error> {
let out = Command::new(program).args(args).output()?;
match out.status.success() {
true => Ok(String::from_utf8(out.stdout).unwrap().trim().to_string()),
false => Err(io::Error::new(
ErrorKind::Other,
format!("run command `{program} {}` failed.", args.join(" ")),
)),
}
}

pub fn capitalize(text: &str) -> String {
let mut chars = text.chars();
Expand Down
55 changes: 52 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use anyhow::Result;
use std::path::{Path, PathBuf};

use anyhow::{anyhow, Result};
use build::watch_build;
use clap::{Parser, Subcommand};
use new::new_zine_project;
use entity::Zine;
use error::ZineError;
use new::{new_zine_issue, new_zine_project};
use parking_lot::RwLock;
use serve::run_serve;
use walkdir::WalkDir;

mod build;
mod code_blocks;
Expand Down Expand Up @@ -91,6 +96,9 @@ enum Commands {
New {
/// The project name.
name: Option<String>,
/// New issue.
#[arg(short)]
issue: bool,
l1ch40 marked this conversation as resolved.
Show resolved Hide resolved
},
/// Lint Zine project.
Lint {
Expand All @@ -104,6 +112,41 @@ enum Commands {
Version,
}

/// Find the root zine file in current dir and try to parse it
fn parse_root_zine_file<P: AsRef<Path>>(path: P) -> Result<Option<Zine>> {
// Find the name in current dir
if WalkDir::new(&path).max_depth(1).into_iter().any(|entry| {
let entry = entry.as_ref().unwrap();
entry.file_name() == crate::ZINE_FILE
}) {
// Try to parse the root zine.toml as Zine instance
return Ok(Some(Zine::parse_from_toml(path)?));
}

Ok(None)
}

/// Locate folder contains the root `zine.toml`, and return path info and Zine instance.
pub fn locate_root_zine_folder<P: AsRef<Path>>(path: P) -> Result<Option<(PathBuf, Zine)>> {
match parse_root_zine_file(&path) {
Ok(Some(zine)) => return Ok(Some((path.as_ref().to_path_buf(), zine))),
Err(err) => match err.downcast::<ZineError>() {
// Found a root zine.toml, but it has invalid format
Ok(inner_err @ ZineError::InvalidRootTomlFile(_)) => return Err(anyhow!(inner_err)),
// Found a zine.toml, but it isn't a root zine.toml
Ok(ZineError::NotRootTomlFile) => {}
// No zine.toml file found
_ => {}
},
_ => {}
}

match path.as_ref().parent() {
Some(parent_path) => locate_root_zine_folder(parent_path),
None => Ok(None),
}
}

#[tokio::main]
async fn main() -> Result<()> {
match Cli::parse().command {
Expand All @@ -121,7 +164,13 @@ async fn main() -> Result<()> {
set_current_mode(Mode::Serve);
run_serve(source.unwrap_or_else(|| ".".into()), port).await?;
}
Commands::New { name } => new_zine_project(name)?,
Commands::New { name, issue } => {
if issue {
new_zine_issue()?;
} else {
new_zine_project(name)?
}
}
Commands::Lint { source, ci } => {
let success = lint::lint_zine_project(source.unwrap_or_else(|| ".".into())).await?;
if ci && !success {
Expand Down