1use std::{
17 borrow::Cow,
18 collections::HashMap,
19 fs,
20 io::{self, Read},
21 rc::Rc,
22};
23
24use crate::{cli, error::Error};
25
26#[derive(Debug)]
27pub struct Source {
28 pub content: String,
29 pub file: Option<String>,
30}
31
32pub fn load_sources(
33 source_args: &[cli::SourceArg],
34 template_args: &[cli::TemplateArg],
35) -> Result<Vec<Source>, Error> {
36 let context = Context::from(template_args);
37 let mut result = Vec::new();
38 for source_arg in source_args.iter() {
39 let (file, raw_content) = load_content(source_arg)?;
40 let content = render_source(&context, raw_content.as_ref());
41 result.push(Source { content, file });
42 }
43 Ok(result)
44}
45
46fn load_content(
47 source_arg: &cli::SourceArg,
48) -> Result<(Option<String>, Cow<'_, str>), Error> {
49 use cli::SourceArg::*;
50 match source_arg {
51 Pipe => {
52 let stdin = io::stdin();
53 let mut handle = stdin.lock();
54 let mut buffer = String::new();
55 handle
56 .read_to_string(&mut buffer)
57 .map_err(|_| Error::CannotReadStdIn)?;
58 Ok((None, Cow::Owned(buffer)))
59 }
60 Expr(e) => Ok((None, Cow::Borrowed(e.as_str()))),
61 File(f) => {
62 let mut file = fs::File::open(f)
63 .map_err(|_| Error::CannotReadFile(f.to_string_lossy().to_string()))?;
64 let mut buffer = String::new();
65 file
66 .read_to_string(&mut buffer)
67 .map_err(|_| Error::CannotReadFile(f.to_string_lossy().to_string()))?;
68 Ok((Some(f.to_string_lossy().to_string()), Cow::Owned(buffer)))
69 }
70 }
71}
72
73fn render_source(context: &Context, source: &str) -> String {
80 let after_shebang = if source.starts_with("#!") {
81 match source.split_once('\n') {
82 Some((_, remaining)) => remaining,
83 None => "",
84 }
85 } else {
86 source
87 }
88 .trim();
89 if let Some(ref regex) = context.regex {
90 let mut fragments = Vec::<Rc<str>>::new();
91 let mut remaining = after_shebang;
92 while let Some(captures) = regex.captures(remaining) {
93 let full_match = captures.get(0).unwrap();
94 let (upto, after) = remaining.split_at(full_match.end());
95 let (before, _) = upto.split_at(full_match.start());
96 fragments.push(before.to_string().into());
97 let value = context
98 .table
99 .get(captures.get(1).unwrap().as_str())
100 .unwrap();
101 fragments.push(value.clone());
102 remaining = after;
103 }
104 fragments.push(remaining.to_string().into());
105 fragments.join("")
106 } else {
107 after_shebang.into()
108 }
109}
110
111#[derive(Debug)]
112struct Context {
113 table: HashMap<Rc<str>, Rc<str>>,
114 regex: Option<regex::Regex>,
115}
116
117impl From<&[cli::TemplateArg]> for Context {
118 fn from(template_args: &[cli::TemplateArg]) -> Self {
119 let table = template_args.iter().fold(HashMap::new(), |mut m, a| {
120 if let Some(ref n) = a.name {
121 m.insert(n.clone(), a.value.clone());
122 }
123 if let Some(i) = a.pos {
124 m.insert((i + 1).to_string().into(), a.value.clone());
125 }
126 m
127 });
128 let regex = if table.is_empty() {
129 None
130 } else {
131 let keys = table
132 .keys()
133 .map(|s| regex::escape(s))
134 .collect::<Vec<String>>();
135 let key_union = keys.join("|");
136 let pat = format!(r#"#nr\s*\[\s*({})\s*\]"#, key_union,);
137 Some(regex::Regex::new(&pat).unwrap())
150 };
151 Self { table, regex }
152 }
153}