Skip to content
This repository was archived by the owner on Feb 16, 2025. It is now read-only.

Commit 7d28d26

Browse files
committed
feat: add single file support
Signed-off-by: Ryan Brue <[email protected]>
1 parent b1bc54a commit 7d28d26

File tree

5 files changed

+130
-83
lines changed

5 files changed

+130
-83
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ authors = ["Ryan Brue <[email protected]>"]
44
repository = "https://github.com/ryanabx/simple-ssg"
55
license = "MIT"
66
readme = "README.md"
7-
version = "3.1.2"
7+
version = "3.2.0"
88
edition = "2021"
99
description = "Plain and simple static site generator for Djot and Markdown light markup languages"
1010

docs/command_reference.dj

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ cargo build --release
1919
## Usage
2020

2121
```shell
22-
simple-ssg <PATH_TO_TARGET>
22+
simple-ssg [OPTIONS] <TARGET_PATH>
2323
```
2424

2525
### Options
2626

2727
```
28-
-o <OUTPUT_PATH> Optional output path override. Defaults to ./output
28+
Options:
29+
-f Process a single file instead of a directory
30+
-o <OUTPUT_PATH> Optional output path override. Defaults to ./output for directories
2931
--clean Clean the output directory before generating the site. Useful for multiple runs
3032
--no-warn Disallow any warnings
3133
--web-prefix <WEB_PREFIX> Specify the website prefix (defaults to local paths i.e. `./`)

src/main.rs

+122-79
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use anyhow::anyhow;
12
use errors::SsgError;
23
use jotdown::{Container, Event};
34
use pulldown_cmark::CowStr;
@@ -23,11 +24,14 @@ mod utils;
2324
struct ConsoleArgs {
2425
/// Path to the directory to use to generate the site
2526
target_path: PathBuf,
26-
/// Optional output path override. Defaults to ./output
27+
/// Process a single file instead of a directory
28+
#[arg(short, conflicts_with = "clean")]
29+
file: bool,
30+
/// Optional output path override. Defaults to ./output for directories
2731
#[arg(short)]
2832
output_path: Option<PathBuf>,
2933
/// Clean the output directory before generating the site. Useful for multiple runs
30-
#[arg(long)]
34+
#[arg(long, conflicts_with = "file")]
3135
clean: bool,
3236
/// Disallow any warnings
3337
#[arg(long)]
@@ -50,11 +54,19 @@ fn main() -> anyhow::Result<()> {
5054
}
5155

5256
fn run_program(args: ConsoleArgs) -> anyhow::Result<()> {
53-
let output_path = args
54-
.output_path
55-
.unwrap_or(env::current_dir()?.join("output"));
57+
let output_path = args.output_path.unwrap_or(if args.file {
58+
env::current_dir()?
59+
} else {
60+
env::current_dir()?.join("output")
61+
});
62+
if args.target_path.is_file() && !args.file {
63+
return Err(anyhow!(
64+
"Target path {} is a file! If you meant to specify a file, please specify the -f flag.",
65+
args.target_path.display()
66+
));
67+
}
5668
// Clean the output directory if clean is specified
57-
if args.clean {
69+
if args.clean && args.target_path.is_dir() {
5870
log::debug!(
5971
"Clean argument specified, cleaning output path {:?}...",
6072
&output_path
@@ -100,86 +112,49 @@ fn generate_site(
100112
"Created output directory {:?} if it didn't exist...",
101113
output_path
102114
);
103-
if !utils::check_has_index(target_path) {
104-
warn_or_error(SsgError::IndexPageNotFound, no_warn)?;
105-
}
115+
106116
let mut first_pass_results = Vec::new();
107117

108118
log::info!("1/3: Site generation and indexing...");
109-
for entry in WalkDir::new(target_path) {
110-
match entry {
111-
Ok(direntry) => {
112-
let relative = match direntry.path().strip_prefix(target_path) {
113-
Ok(relative) => relative.to_path_buf(),
114-
Err(_) => {
115-
warn_or_error(
116-
SsgError::PathNotRelative(direntry.path().to_path_buf()),
117-
no_warn,
118-
)?;
119-
continue;
120-
}
121-
};
122-
log::debug!("{:?} :: {}", &relative, direntry.depth());
123-
if direntry.path().is_dir() {
124-
log::trace!("Path {:?} is a directory, continuing...", direntry.path());
125-
first_pass_results.push(FirstPassResult::Dir {
126-
depth: direntry.depth(),
127-
relative_path: relative,
128-
});
129-
continue;
130-
} else if direntry.path().ends_with("template.html") {
131-
log::trace!("Path {:?} is a template, continuing...", direntry.path());
132-
continue;
133-
}
134-
log::trace!("Path: {:?}", direntry.path());
135-
let new_path = output_path.join(&relative);
136-
let _ = std::fs::create_dir_all(new_path.parent().unwrap());
137-
match direntry.path().extension().map(|x| x.to_str().unwrap()) {
138-
Some("dj") | Some("djot") | Some("md") => {
139-
let html_template = template.clone().map_or(
140-
utils::get_template_if_exists(direntry.path(), target_path)?,
141-
|template| Some(template.get_template()),
142-
);
143-
let result_path = new_path.with_extension("html");
144-
log::debug!(
145-
"Generating .html from {:?} and moving to {:?}",
146-
direntry.path(),
147-
&result_path
148-
);
149-
let input_str = std::fs::read_to_string(direntry.path())?;
150-
let html = match direntry.path().extension().map(|x| x.to_str().unwrap()) {
151-
Some("md") => process_markdown(
152-
&input_str,
153-
direntry.path().parent().unwrap(),
154-
no_warn,
155-
web_prefix,
156-
)?,
157-
Some("dj") | Some("djot") => process_djot(
158-
&input_str,
159-
direntry.path().parent().unwrap(),
160-
no_warn,
161-
web_prefix,
162-
)?,
163-
_ => unreachable!(),
164-
};
165-
let html_formatted =
166-
utils::wrap_html_content(&html, html_template.as_deref());
167-
first_pass_results.push(FirstPassResult::HtmlOutput {
168-
depth: direntry.depth(),
169-
html: html_formatted,
170-
relative_path: relative.with_extension("html"),
171-
});
172-
}
173-
_ => {
174-
std::fs::copy(direntry.path(), &new_path)?;
175-
}
119+
if target_path.is_dir() && output_path.is_dir() {
120+
if !utils::check_has_index(target_path) {
121+
warn_or_error(SsgError::IndexPageNotFound, no_warn)?;
122+
}
123+
for entry in WalkDir::new(target_path) {
124+
match entry {
125+
Ok(direntry) => process_path(
126+
direntry.path(),
127+
target_path,
128+
output_path,
129+
&template,
130+
web_prefix,
131+
no_warn,
132+
direntry.depth(),
133+
&mut first_pass_results,
134+
)?,
135+
Err(e) => {
136+
warn_or_error(SsgError::DirEntryError(e), no_warn)?;
176137
}
177138
}
178-
Err(e) => {
179-
warn_or_error(SsgError::DirEntryError(e), no_warn)?;
180-
}
181139
}
140+
} else if target_path.is_file() {
141+
process_path(
142+
target_path,
143+
target_path.parent().unwrap(),
144+
output_path,
145+
&template,
146+
web_prefix,
147+
no_warn,
148+
1,
149+
&mut first_pass_results,
150+
)?;
151+
} else {
152+
return Err(anyhow!(
153+
"Target path {} is not a file or a directory.",
154+
target_path.display()
155+
));
182156
}
157+
183158
// Validation pass
184159
log::info!("2/3: Generating additional site content (if necessary) and saving...");
185160

@@ -211,6 +186,74 @@ fn generate_site(
211186
Ok(())
212187
}
213188

189+
fn process_path(
190+
entity: &Path,
191+
target_path: &Path,
192+
output_path: &Path,
193+
template: &Option<BuiltInTemplate>,
194+
web_prefix: Option<&str>,
195+
no_warn: bool,
196+
depth: usize,
197+
first_pass_results: &mut Vec<FirstPassResult>,
198+
) -> anyhow::Result<()> {
199+
let relative = match entity.strip_prefix(target_path) {
200+
Ok(relative) => relative.to_path_buf(),
201+
Err(_) => {
202+
warn_or_error(SsgError::PathNotRelative(entity.to_path_buf()), no_warn)?;
203+
return Ok(());
204+
}
205+
};
206+
log::debug!("{:?} :: {}", &relative, depth);
207+
if entity.is_dir() {
208+
log::trace!("Path {:?} is a directory, continuing...", entity);
209+
first_pass_results.push(FirstPassResult::Dir {
210+
depth,
211+
relative_path: relative,
212+
});
213+
return Ok(());
214+
} else if entity.ends_with("template.html") {
215+
log::trace!("Path {:?} is a template, continuing...", entity);
216+
return Ok(());
217+
}
218+
log::trace!("Path: {:?}", entity);
219+
let new_path = output_path.join(&relative);
220+
let _ = std::fs::create_dir_all(new_path.parent().unwrap());
221+
match entity.extension().map(|x| x.to_str().unwrap()) {
222+
Some("dj") | Some("djot") | Some("md") => {
223+
let html_template = template.clone().map_or(
224+
utils::get_template_if_exists(entity, target_path)?,
225+
|template| Some(template.get_template()),
226+
);
227+
let result_path = new_path.with_extension("html");
228+
log::debug!(
229+
"Generating .html from {:?} and moving to {:?}",
230+
entity,
231+
&result_path
232+
);
233+
let input_str = std::fs::read_to_string(entity)?;
234+
let html = match entity.extension().map(|x| x.to_str().unwrap()) {
235+
Some("md") => {
236+
process_markdown(&input_str, entity.parent().unwrap(), no_warn, web_prefix)?
237+
}
238+
Some("dj") | Some("djot") => {
239+
process_djot(&input_str, entity.parent().unwrap(), no_warn, web_prefix)?
240+
}
241+
_ => unreachable!(),
242+
};
243+
let html_formatted = utils::wrap_html_content(&html, html_template.as_deref());
244+
first_pass_results.push(FirstPassResult::HtmlOutput {
245+
depth: depth,
246+
html: html_formatted,
247+
relative_path: relative.with_extension("html"),
248+
});
249+
}
250+
_ => {
251+
std::fs::copy(entity, &new_path)?;
252+
}
253+
}
254+
Ok(())
255+
}
256+
214257
fn process_markdown(
215258
markdown_input: &str,
216259
file_parent_dir: &Path,

src/tests.rs

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ fn site_with_links() -> anyhow::Result<()> {
4949
no_warn: true,
5050
web_prefix: None,
5151
template: Some(crate::templates::BuiltInTemplate::ForceNone),
52+
file: false,
5253
};
5354
log::trace!("Running program");
5455
crate::run_program(args)?;
@@ -103,6 +104,7 @@ fn site_warn_without_index() -> anyhow::Result<()> {
103104
no_warn: true,
104105
web_prefix: None,
105106
template: Some(crate::templates::BuiltInTemplate::ForceNone),
107+
file: false,
106108
};
107109
crate::run_program(args)?;
108110
Ok(())

0 commit comments

Comments
 (0)