Skip to content

Commit be02b27

Browse files
committed
restructure syntax helping tool
1 parent 9d9a546 commit be02b27

File tree

7 files changed

+718
-106
lines changed

7 files changed

+718
-106
lines changed

crates/qmd-syntax-helper/src/conversions/definition_lists.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use regex::Regex;
44
use std::path::Path;
55
use std::process::{Command, Stdio};
66

7+
use crate::rule::{CheckResult, ConvertResult, Rule};
78
use crate::utils::file_io::{read_file, write_file};
89
use crate::utils::resources::ResourceManager;
910
use quarto_markdown_pandoc::readers::json;
@@ -267,3 +268,114 @@ impl DefinitionListConverter {
267268
Ok(())
268269
}
269270
}
271+
272+
impl Rule for DefinitionListConverter {
273+
fn name(&self) -> &str {
274+
"definition-lists"
275+
}
276+
277+
fn description(&self) -> &str {
278+
"Convert definition lists to div-based format"
279+
}
280+
281+
fn check(&self, file_path: &Path, verbose: bool) -> Result<CheckResult> {
282+
let content = read_file(file_path)?;
283+
let lists = self.find_definition_lists(&content);
284+
285+
if verbose {
286+
if lists.is_empty() {
287+
println!(" No definition lists found");
288+
} else {
289+
println!(" Found {} definition list(s)", lists.len());
290+
}
291+
}
292+
293+
Ok(CheckResult {
294+
rule_name: self.name().to_string(),
295+
file_path: file_path.to_string_lossy().to_string(),
296+
has_issue: !lists.is_empty(),
297+
issue_count: lists.len(),
298+
message: if lists.is_empty() {
299+
None
300+
} else {
301+
Some(format!("Found {} definition list(s)", lists.len()))
302+
},
303+
})
304+
}
305+
306+
fn convert(
307+
&self,
308+
file_path: &Path,
309+
in_place: bool,
310+
check_mode: bool,
311+
verbose: bool,
312+
) -> Result<ConvertResult> {
313+
let content = read_file(file_path)?;
314+
let lists = self.find_definition_lists(&content);
315+
316+
if lists.is_empty() {
317+
return Ok(ConvertResult {
318+
rule_name: self.name().to_string(),
319+
file_path: file_path.to_string_lossy().to_string(),
320+
fixes_applied: 0,
321+
message: None,
322+
});
323+
}
324+
325+
// Convert each list and build new content
326+
let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
327+
let mut offset: isize = 0;
328+
329+
for (idx, list) in lists.iter().enumerate() {
330+
if verbose {
331+
println!(" Converting list {}...", idx + 1);
332+
}
333+
334+
let converted = self.convert_list(&list.text)?;
335+
let start = (list.start_line as isize + offset) as usize;
336+
let end = (list.end_line as isize + offset) as usize;
337+
338+
if check_mode && verbose {
339+
println!(
340+
" List {} at lines {}-{}:",
341+
idx + 1,
342+
list.start_line,
343+
list.end_line
344+
);
345+
println!(
346+
" {} {} lines -> {} {} lines",
347+
"Original:".red(),
348+
list.end_line - list.start_line + 1,
349+
"Converted:".green(),
350+
converted.lines().count()
351+
);
352+
}
353+
354+
let converted_lines: Vec<String> = converted.lines().map(|s| s.to_string()).collect();
355+
let new_len = converted_lines.len();
356+
let old_len = end - start + 1;
357+
358+
lines.splice(start..=end, converted_lines);
359+
offset += new_len as isize - old_len as isize;
360+
}
361+
362+
let new_content = lines.join("\n") + "\n";
363+
364+
if !check_mode {
365+
if in_place {
366+
write_file(file_path, &new_content)?;
367+
}
368+
}
369+
370+
Ok(ConvertResult {
371+
rule_name: self.name().to_string(),
372+
file_path: file_path.to_string_lossy().to_string(),
373+
fixes_applied: lists.len(),
374+
message: if in_place {
375+
Some(format!("Converted {} list(s)", lists.len()))
376+
} else {
377+
Some(new_content)
378+
},
379+
})
380+
}
381+
}

crates/qmd-syntax-helper/src/conversions/div_whitespace.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
44
use std::fs;
55
use std::path::Path;
66

7+
use crate::rule::{CheckResult, ConvertResult, Rule};
78
use crate::utils::file_io::{read_file, write_file};
89

910
#[derive(Debug, Serialize, Deserialize)]
@@ -208,3 +209,85 @@ impl DivWhitespaceConverter {
208209
Ok(())
209210
}
210211
}
212+
213+
impl Rule for DivWhitespaceConverter {
214+
fn name(&self) -> &str {
215+
"div-whitespace"
216+
}
217+
218+
fn description(&self) -> &str {
219+
"Fix div fences missing whitespace (:::{ -> ::: {)"
220+
}
221+
222+
fn check(&self, file_path: &Path, verbose: bool) -> Result<CheckResult> {
223+
let content = read_file(file_path)?;
224+
let errors = self.get_parse_errors(file_path)?;
225+
let fix_positions = self.find_div_whitespace_errors(&content, &errors);
226+
227+
if verbose {
228+
if fix_positions.is_empty() {
229+
println!(" No div whitespace issues found");
230+
} else {
231+
println!(
232+
" Found {} div fence(s) needing whitespace fixes",
233+
fix_positions.len()
234+
);
235+
}
236+
}
237+
238+
Ok(CheckResult {
239+
rule_name: self.name().to_string(),
240+
file_path: file_path.to_string_lossy().to_string(),
241+
has_issue: !fix_positions.is_empty(),
242+
issue_count: fix_positions.len(),
243+
message: if fix_positions.is_empty() {
244+
None
245+
} else {
246+
Some(format!(
247+
"Found {} div fence(s) needing whitespace fixes",
248+
fix_positions.len()
249+
))
250+
},
251+
})
252+
}
253+
254+
fn convert(
255+
&self,
256+
file_path: &Path,
257+
in_place: bool,
258+
check_mode: bool,
259+
verbose: bool,
260+
) -> Result<ConvertResult> {
261+
let content = read_file(file_path)?;
262+
let errors = self.get_parse_errors(file_path)?;
263+
let fix_positions = self.find_div_whitespace_errors(&content, &errors);
264+
265+
if fix_positions.is_empty() {
266+
return Ok(ConvertResult {
267+
rule_name: self.name().to_string(),
268+
file_path: file_path.to_string_lossy().to_string(),
269+
fixes_applied: 0,
270+
message: None,
271+
});
272+
}
273+
274+
let new_content = self.apply_fixes(&content, &fix_positions);
275+
276+
if !check_mode {
277+
if in_place {
278+
write_file(file_path, &new_content)?;
279+
}
280+
}
281+
282+
Ok(ConvertResult {
283+
rule_name: self.name().to_string(),
284+
file_path: file_path.to_string_lossy().to_string(),
285+
fixes_applied: fix_positions.len(),
286+
message: if in_place {
287+
Some(format!("Fixed {} div fence(s)", fix_positions.len()))
288+
} else {
289+
Some(new_content)
290+
},
291+
})
292+
}
293+
}

crates/qmd-syntax-helper/src/conversions/grid_tables.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use regex::Regex;
44
use std::path::Path;
55
use std::process::{Command, Stdio};
66

7+
use crate::rule::{CheckResult, ConvertResult, Rule};
78
use crate::utils::file_io::{read_file, write_file};
89
use crate::utils::resources::ResourceManager;
910
use quarto_markdown_pandoc::readers::json;
@@ -228,3 +229,114 @@ impl GridTableConverter {
228229
Ok(())
229230
}
230231
}
232+
233+
impl Rule for GridTableConverter {
234+
fn name(&self) -> &str {
235+
"grid-tables"
236+
}
237+
238+
fn description(&self) -> &str {
239+
"Convert grid tables to list-table format"
240+
}
241+
242+
fn check(&self, file_path: &Path, verbose: bool) -> Result<CheckResult> {
243+
let content = read_file(file_path)?;
244+
let tables = self.find_grid_tables(&content);
245+
246+
if verbose {
247+
if tables.is_empty() {
248+
println!(" No grid tables found");
249+
} else {
250+
println!(" Found {} grid table(s)", tables.len());
251+
}
252+
}
253+
254+
Ok(CheckResult {
255+
rule_name: self.name().to_string(),
256+
file_path: file_path.to_string_lossy().to_string(),
257+
has_issue: !tables.is_empty(),
258+
issue_count: tables.len(),
259+
message: if tables.is_empty() {
260+
None
261+
} else {
262+
Some(format!("Found {} grid table(s)", tables.len()))
263+
},
264+
})
265+
}
266+
267+
fn convert(
268+
&self,
269+
file_path: &Path,
270+
in_place: bool,
271+
check_mode: bool,
272+
verbose: bool,
273+
) -> Result<ConvertResult> {
274+
let content = read_file(file_path)?;
275+
let tables = self.find_grid_tables(&content);
276+
277+
if tables.is_empty() {
278+
return Ok(ConvertResult {
279+
rule_name: self.name().to_string(),
280+
file_path: file_path.to_string_lossy().to_string(),
281+
fixes_applied: 0,
282+
message: None,
283+
});
284+
}
285+
286+
// Convert each table and build new content
287+
let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
288+
let mut offset: isize = 0;
289+
290+
for (idx, table) in tables.iter().enumerate() {
291+
if verbose {
292+
println!(" Converting table {}...", idx + 1);
293+
}
294+
295+
let converted = self.convert_table(&table.text)?;
296+
let start = (table.start_line as isize + offset) as usize;
297+
let end = (table.end_line as isize + offset) as usize;
298+
299+
if check_mode && verbose {
300+
println!(
301+
" Table {} at lines {}-{}:",
302+
idx + 1,
303+
table.start_line,
304+
table.end_line
305+
);
306+
println!(
307+
" {} {} lines -> {} {} lines",
308+
"Original:".red(),
309+
table.end_line - table.start_line + 1,
310+
"Converted:".green(),
311+
converted.lines().count()
312+
);
313+
}
314+
315+
let converted_lines: Vec<String> = converted.lines().map(|s| s.to_string()).collect();
316+
let new_len = converted_lines.len();
317+
let old_len = end - start + 1;
318+
319+
lines.splice(start..=end, converted_lines);
320+
offset += new_len as isize - old_len as isize;
321+
}
322+
323+
let new_content = lines.join("\n") + "\n";
324+
325+
if !check_mode {
326+
if in_place {
327+
write_file(file_path, &new_content)?;
328+
}
329+
}
330+
331+
Ok(ConvertResult {
332+
rule_name: self.name().to_string(),
333+
file_path: file_path.to_string_lossy().to_string(),
334+
fixes_applied: tables.len(),
335+
message: if in_place {
336+
Some(format!("Converted {} table(s)", tables.len()))
337+
} else {
338+
Some(new_content)
339+
},
340+
})
341+
}
342+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod conversions;
22
pub mod diagnostics;
3+
pub mod rule;
34
pub mod utils;

0 commit comments

Comments
 (0)