aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs165
1 files changed, 165 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..02fbcd4
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,165 @@
1use clap::Parser;
2use pandoc::{
3 InputFormat,InputKind,OutputFormat,OutputKind,Pandoc
4};
5use pandoc_ast::Block;
6use std::borrow::Cow;
7use std::collections::HashMap;
8use lazy_static::lazy_static;
9use regex::{Captures,Regex};
10use std::fs;
11use std::io::Result;
12use std::path::PathBuf;
13
14const BASE: &str = "./";
15
16/// A tangler for Literate Programming in Pandoc
17#[derive(Parser, Debug)]
18#[clap(author, version, about, long_about = None)]
19struct Config {
20 /// Maximum substitution depth
21 #[clap(short, long, default_value_t = 10)]
22 depth: u32,
23 /// Base output directory [default: './']
24 #[clap(short, long)]
25 output: Option<PathBuf>,
26 /// Input files
27 input: Vec<PathBuf>,
28}
29
30type Blocks<'a> = HashMap<String,Cow<'a,str>>;
31
32fn build(
33 base: &Option<PathBuf>,
34 blocks: &Blocks,
35 max_depth: u32
36) {
37 lazy_static! {
38 static ref PATH: Regex =
39 Regex::new(
40 r"^(?:[[:word:]\.-]+/)*[[:word:]\.-]+\.[[:alpha:]]+$"
41 ).unwrap();
42 static ref MACRO: Regex =
43 Regex::new(
44 r"(?m)^([[:blank:]]*)<<([^>\s]+)>>"
45 ).unwrap();
46 }
47 blocks
48 .iter()
49 .for_each(|(path,code)| if PATH.is_match(path) {
50 let mut current_depth = 0;
51 let mut code = code.clone();
52 while let Cow::Owned(new_code) = MACRO.replace_all(
53 &code,
54 |caps: &Captures| {
55 if current_depth < max_depth {
56 let block = blocks
57 .get(&caps[2])
58 .expect("Block not present")
59 .clone();
60 indent(block, caps[1].len())
61 } else {
62 eprintln!("Reached maximum depth, \
63 output might be truncated.\n\
64 Increase `--depth` accordingly.");
65 Cow::Owned(String::from(""))
66 }
67 }
68 ) {
69 code = Cow::from(new_code);
70 current_depth += 1;
71 }
72 let file = base
73 .clone()
74 .unwrap_or(PathBuf::from(BASE))
75 .join(path);
76 write_to_file(file, &code)
77 .expect("Unable to write to file");
78 })
79}
80
81fn indent<'a>(
82 input: Cow<'a,str>,
83 indent: usize
84) -> Cow<'a,str> {
85 if indent > 0 {
86 let prefix = format!("{:indent$}", "");
87 let size = input.len() + indent*input.lines().count();
88 let mut output = String::with_capacity(size);
89 input.lines().enumerate().for_each(|(i,line)| {
90 if i > 0 {
91 output.push('\n');
92 }
93 if !line.is_empty() {
94 output.push_str(&prefix);
95 output.push_str(line);
96 }
97 });
98 Cow::Owned(output)
99 } else {
100 input
101 }
102}
103
104fn write_to_file(
105 path: PathBuf, content: &str
106) -> std::io::Result<()> {
107 if path.is_relative() {
108 fs::create_dir_all(path.parent().unwrap())?;
109 fs::write(path, content)?;
110 } else {
111 eprintln!(
112 "Absolute paths not supported: {}",
113 path.to_string_lossy()
114 )
115 }
116 Ok(())
117}
118
119
120fn main() -> Result<()> {
121 let config = Config::parse();
122 let mut pandoc = Pandoc::new();
123 pandoc.set_input(InputKind::Files(config.input));
124 pandoc.set_input_format(InputFormat::Markdown, vec![]);
125 pandoc.set_output(OutputKind::Pipe);
126 pandoc.set_output_format(OutputFormat::Json, vec![]);
127 pandoc.add_filter(
128 move |json| pandoc_ast::filter(json,
129 |pandoc| {
130 let mut blocks: Blocks = HashMap::new();
131 pandoc.blocks.iter().for_each(|block|
132 if let Block::CodeBlock((id,clss,attrs), code) = block {
133 if !id.is_empty() {
134 let key = {
135 let path = attrs.iter().find(|(k,_)| k == "path");
136 if let Some(path) = path {
137 format!("{}{}", path.1, id)
138 } else {
139 id.to_string()
140 }
141 };
142 if clss.iter().any(|c| c == "override") {
143 blocks.insert(key, Cow::from(code));
144 } else {
145 blocks.entry(key)
146 .and_modify(|s| {
147 *s += "\n";
148 *s += Cow::from(code)
149 })
150 .or_insert(Cow::from(code));
151 }
152 } else {
153 eprintln!("Ignoring code block without ID:");
154 eprintln!("{}", indent(Cow::from(code),4));
155 }
156 }
157 );
158 build(&config.output, &blocks, config.depth);
159 pandoc
160 }
161 )
162 );
163 pandoc.execute().unwrap();
164 Ok(())
165} \ No newline at end of file