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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
//! Facilities to patch a file compared to the default package provided one.
use super::error::KResult;
use eyre::WrapErr;
use regex::Regex;
use rune::Any;
use rune::ContextError;
use rune::Module;
use rune::runtime::Shared;
use rune::runtime::VmResult;
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
/// A simple line editor, like sed
#[derive(Debug, Default, Any)]
#[rune(item = ::patch)]
struct LineEditor {
inner: Rc<RefCell<konfigkoll_utils::line_edit::EditProgram>>,
}
impl LineEditor {
/// Create a new empty line editor
#[rune::function(path = Self::new)]
fn new() -> Self {
Default::default()
}
/// Add a new rule to the line editor.
///
/// Returns a Result<()>, where the error variant can happen on invalid
/// regexes.
#[rune::function]
pub fn add(&mut self, selector: &Selector, action: &Action) -> KResult<()> {
self.inner
.borrow_mut()
.add(selector.try_into()?, false, action.try_into()?);
Ok(())
}
/// Add a new rule where the selector condition has been inverted to the
/// line editor
///
/// Returns a Result<()>, where the error variant can happen on invalid
/// regexes.
#[rune::function]
pub fn add_inverted(&mut self, selector: &Selector, action: &Action) -> KResult<()> {
self.inner
.borrow_mut()
.add(selector.try_into()?, true, action.try_into()?);
Ok(())
}
/// Apply the line editor to a string
#[rune::function]
fn apply(&self, text: &str) -> String {
self.inner.borrow().apply(text)
}
/// Clone the line editor, allowing "forking it" into two different related
/// variants
#[rune::function]
fn clone(&self) -> Self {
Self {
inner: Rc::new(RefCell::new(self.inner.borrow().clone())),
}
}
}
/// Selects if a line should be edited by [`LineEditor`] or not
#[derive(Debug, Any)]
#[rune(item = ::patch)]
enum Selector {
/// Match all lines
#[rune(constructor)]
All,
/// End of file
#[rune(constructor)]
Eof,
/// Match a specific line number (1-indexed)
#[rune(constructor)]
Line(#[rune(get)] usize),
/// A range of line numbers (1-indexed, inclusive)
#[rune(constructor)]
Range(#[rune(get)] usize, #[rune(get)] usize),
/// A regex to match the line
#[rune(constructor)]
Regex(#[rune(get)] String),
/// A custom function, passed the line number and current line, returning a
/// bool
#[rune(constructor)]
Function(#[rune(get)] Shared<rune::runtime::Function>),
}
impl TryFrom<&Selector> for konfigkoll_utils::line_edit::Selector {
type Error = eyre::Error;
fn try_from(value: &Selector) -> Result<Self, Self::Error> {
match value {
Selector::All => Ok(Self::All),
Selector::Eof => Ok(Self::Eof),
Selector::Line(n) => Ok(Self::Line(*n)),
Selector::Range(a, b) => Ok(Self::Range(*a, *b)),
Selector::Regex(r) => Ok(Self::Regex(Regex::new(r).wrap_err("invalid regex")?)),
Selector::Function(f) => {
let f = f.clone();
Ok(Self::Function(Rc::new(move |lineno, s| {
let guard = f.borrow_mut().expect("Failed to borrow function object");
match guard.call::<_, bool>((lineno, s)) {
VmResult::Ok(v) => v,
VmResult::Err(e) => {
tracing::error!(
"Error in custom selector function {:?}: {:?}",
*guard,
e
);
false
}
}
})))
}
}
}
}
/// Action to perform on a line when matched by a [`Selector`]
#[derive(Debug, Any)]
#[rune(item = ::patch)]
enum Action {
/// Copy the current line to the output. Only needed when auto-print is
/// disabled.
#[rune(constructor)]
Print,
/// Delete the current line and short circuit the rest of the program
/// (immediately go to the next line)
#[rune(constructor)]
Delete,
/// Replace pattern space with next line (will print unless auto-print is
/// disabled)
#[rune(constructor)]
NextLine,
/// Stop processing the input and program and terminate early (do not print
/// rest of file)
#[rune(constructor)]
Stop,
/// Stop processing the input and program and terminate early (auto-print
/// rest of file)
#[rune(constructor)]
StopAndPrint,
/// Insert a new line *before* the current line
#[rune(constructor)]
InsertBefore(#[rune(get)] String),
/// Insert a new line *after* the current line
#[rune(constructor)]
InsertAfter(#[rune(get)] String),
/// Replace the entire current string with the given string
#[rune(constructor)]
Replace(#[rune(get)] String),
/// Do a regex search and replace in the current line.
///
/// Only the first match is replaced in any given line.
///
/// Capture groups in the replacement string works as with `::regex::Regex`.
#[rune(constructor)]
RegexReplace(#[rune(get)] String, #[rune(get)] String),
/// Like `RegexReplace` but replaces all matches on the line.
#[rune(constructor)]
RegexReplaceAll(#[rune(get)] String, #[rune(get)] String),
/// A sub-program that is executed. Will share pattern space with parent
/// program
Subprogram(LineEditor),
/// A custom function passed the current pattern buffer, returning a new
/// pattern buffer
#[rune(constructor)]
Function(#[rune(get)] Shared<rune::runtime::Function>),
}
impl Action {
/// Create an action for a nested sub-program
#[rune::function(path = Self::sub_program)]
const fn sub_program(sub: LineEditor) -> Self {
Self::Subprogram(sub)
}
}
impl TryFrom<&Action> for konfigkoll_utils::line_edit::Action {
type Error = eyre::Error;
fn try_from(value: &Action) -> Result<Self, Self::Error> {
match value {
Action::Print => Ok(Self::Print),
Action::Delete => Ok(Self::Delete),
Action::Stop => Ok(Self::Stop),
Action::StopAndPrint => Ok(Self::StopAndPrint),
Action::InsertBefore(s) => Ok(Self::InsertBefore(s.into())),
Action::InsertAfter(s) => Ok(Self::InsertAfter(s.into())),
Action::Replace(s) => Ok(Self::Replace(s.into())),
Action::RegexReplace(a, b) => Ok(Self::RegexReplace {
regex: Regex::new(a)?,
replacement: b.into(),
replace_all: false,
}),
Action::RegexReplaceAll(a, b) => Ok(Self::RegexReplace {
regex: Regex::new(a)?,
replacement: b.into(),
replace_all: true,
}),
Action::Function(f) => {
let f = f.clone();
Ok(Self::Function(Rc::new(move |s| {
let guard = f.borrow_mut().expect("Failed to borrow function object");
match guard.call::<_, String>((s,)) {
VmResult::Ok(v) => Cow::Owned(v),
VmResult::Err(e) => {
tracing::error!(
"Error in custom action function {:?}: {:?}",
*guard,
e
);
Cow::Borrowed(s)
}
}
})))
}
Action::NextLine => Ok(Self::NextLine),
Action::Subprogram(sub) => Ok(Self::Subprogram(sub.inner.clone())),
}
}
}
#[rune::module(::patch)]
/// Utilities for patching file contents conveniently.
pub(crate) fn module() -> Result<Module, ContextError> {
let mut m = Module::from_meta(module_meta)?;
m.ty::<LineEditor>()?;
m.function_meta(LineEditor::new)?;
m.function_meta(LineEditor::apply)?;
m.function_meta(LineEditor::add)?;
m.function_meta(LineEditor::add_inverted)?;
m.function_meta(LineEditor::clone)?;
m.ty::<Selector>()?;
m.ty::<Action>()?;
m.function_meta(Action::sub_program)?;
Ok(m)
}