1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use crate::{common::*, prompt, Codeblock, Diff, Parser, Result, RopeExt};
/// Represents a parsed Markdown file that can be presented
#[derive(Debug, Clone)]
pub struct File {
codeblocks: Vec<Codeblock>,
content: Rope,
interactive: bool,
path: PathBuf,
remove: bool,
}
impl File {
/// Create a new [`File`] by parsing the file at `path`
///
/// # Errors
///
/// This function will return an error if the following conditions
/// are true:
/// - The file is not readable into a string
/// - The parser failed to parse the file contents
pub fn new(path: PathBuf) -> Result<Self> {
let content = fs::read_to_string(&path)?;
let parser = Parser::new(&content);
Ok(Self {
codeblocks: parser.parse()?,
content: Rope::from_str(&content.clone()),
interactive: false,
path,
remove: false,
})
}
/// Setting this to true will make the [`present`](File::present) function
/// replace the whole code block with the command output. If kept at false
/// (the default), it will place the output inside the code block.
///
/// # Example
///
/// ```ignore
/// # use present::File;
/// let file = File::new()
/// .unwrap()
/// .remove(true);
/// ```
pub fn remove(self, on: bool) -> Self {
Self { remove: on, ..self }
}
/// Setting this to true will make the [`present`](File::present) function
/// interactive. For each diff in a file, the user will be asked if they
/// want to apply it or not.
///
/// # Example
///
/// ```ignore
/// let file = File::new()
/// .unwrap()
/// .interactive(true);
/// ```
pub fn interactive(self, on: bool) -> Self {
Self {
interactive: on,
..self
}
}
/// Returns an iterator of [`Diff`]s in the file.
///
/// The [`Diff`]s are returned as results. If the command fails, the item will
/// be of the `Err` kind.
pub fn diffs(&self) -> impl Iterator<Item = Result<Diff>> + '_ {
self.codeblocks.iter().map(|codeblock| {
Ok(Diff {
content: codeblock.command.execute()?,
range: match self.remove {
// Replace the entire codeblock with `stdout`
true => {
codeblock.position.start.start..codeblock.position.end.end + 2
}
// Insert in between the codeblock (start, end)
false => {
codeblock.position.start.end + 1..codeblock.position.end.start + 1
}
},
})
})
}
/// Applies all diffs produced by [`diffs`](File::diffs) by mutating self.
///
/// If [`interactive`](File::interactive) is set to `true`, the user will be
/// asked if they want to apply the change for each diff.
pub fn present(&mut self) -> Result {
let mut offset: isize = 0;
let diffs = self.diffs().collect::<Result<Vec<Diff>>>()?;
for mut diff in diffs {
let prev = self.content.len_bytes();
diff.offset(offset);
if self.interactive {
diff.print(&self.content);
if prompt("Apply changes? [Y/N]")?.as_str() != "y" {
continue;
}
}
self.content.apply(diff.clone());
offset += self.content.len_bytes() as isize - prev as isize;
}
Ok(())
}
/// Saves the current state to the original file.
pub fn save(&self) -> Result {
Ok(fs::write(&self.path, self.content.to_string())?)
}
/// Prints the current state to stdout. If `pretty` is true, [`termimad`] will
/// be used to prettyprint the content.
pub fn print(&self, pretty: bool) {
match pretty {
true => print_inline(&self.content.to_string()),
_ => print!("{}", self.content),
}
}
}