1
+ use anyhow:: anyhow;
1
2
use errors:: SsgError ;
2
3
use jotdown:: { Container , Event } ;
3
4
use pulldown_cmark:: CowStr ;
@@ -23,11 +24,14 @@ mod utils;
23
24
struct ConsoleArgs {
24
25
/// Path to the directory to use to generate the site
25
26
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
27
31
#[ arg( short) ]
28
32
output_path : Option < PathBuf > ,
29
33
/// Clean the output directory before generating the site. Useful for multiple runs
30
- #[ arg( long) ]
34
+ #[ arg( long, conflicts_with = "file" ) ]
31
35
clean : bool ,
32
36
/// Disallow any warnings
33
37
#[ arg( long) ]
@@ -50,11 +54,19 @@ fn main() -> anyhow::Result<()> {
50
54
}
51
55
52
56
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
+ }
56
68
// Clean the output directory if clean is specified
57
- if args. clean {
69
+ if args. clean && args . target_path . is_dir ( ) {
58
70
log:: debug!(
59
71
"Clean argument specified, cleaning output path {:?}..." ,
60
72
& output_path
@@ -100,86 +112,49 @@ fn generate_site(
100
112
"Created output directory {:?} if it didn't exist..." ,
101
113
output_path
102
114
) ;
103
- if !utils:: check_has_index ( target_path) {
104
- warn_or_error ( SsgError :: IndexPageNotFound , no_warn) ?;
105
- }
115
+
106
116
let mut first_pass_results = Vec :: new ( ) ;
107
117
108
118
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) ?;
176
137
}
177
138
}
178
- Err ( e) => {
179
- warn_or_error ( SsgError :: DirEntryError ( e) , no_warn) ?;
180
- }
181
139
}
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
+ ) ) ;
182
156
}
157
+
183
158
// Validation pass
184
159
log:: info!( "2/3: Generating additional site content (if necessary) and saving..." ) ;
185
160
@@ -211,6 +186,74 @@ fn generate_site(
211
186
Ok ( ( ) )
212
187
}
213
188
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
+
214
257
fn process_markdown (
215
258
markdown_input : & str ,
216
259
file_parent_dir : & Path ,
0 commit comments