1use crate::lex::{lex, SyntaxKind};
31use rowan::ast::AstNode;
32use rowan::{GreenNode, GreenNodeBuilder};
33use std::path::Path;
34use std::str::FromStr;
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub struct PositionedParseError {
39 pub message: String,
41 pub range: rowan::TextRange,
43 pub code: Option<String>,
45}
46
47impl std::fmt::Display for PositionedParseError {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{}", self.message)
50 }
51}
52
53impl std::error::Error for PositionedParseError {}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash)]
57pub struct ParseError(pub Vec<String>);
58
59impl std::fmt::Display for ParseError {
60 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
61 for err in &self.0 {
62 writeln!(f, "{}", err)?;
63 }
64 Ok(())
65 }
66}
67
68impl std::error::Error for ParseError {}
69
70#[derive(Debug)]
72pub enum Error {
73 ParseError(ParseError),
75
76 IoError(std::io::Error),
78}
79
80impl std::fmt::Display for Error {
81 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
82 match &self {
83 Error::ParseError(err) => write!(f, "{}", err),
84 Error::IoError(err) => write!(f, "{}", err),
85 }
86 }
87}
88
89impl From<ParseError> for Error {
90 fn from(err: ParseError) -> Self {
91 Self::ParseError(err)
92 }
93}
94
95impl From<std::io::Error> for Error {
96 fn from(err: std::io::Error) -> Self {
97 Self::IoError(err)
98 }
99}
100
101impl std::error::Error for Error {}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
105pub enum Lang {}
106
107impl rowan::Language for Lang {
108 type Kind = SyntaxKind;
109
110 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
111 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
112 }
113
114 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
115 kind.into()
116 }
117}
118
119pub(crate) struct ParseResult {
121 pub(crate) green_node: GreenNode,
122 pub(crate) errors: Vec<String>,
123 pub(crate) positioned_errors: Vec<PositionedParseError>,
124}
125
126pub(crate) fn parse(text: &str) -> ParseResult {
128 struct Parser<'a> {
129 tokens: Vec<(SyntaxKind, &'a str)>,
130 builder: GreenNodeBuilder<'static>,
131 errors: Vec<String>,
132 positioned_errors: Vec<PositionedParseError>,
133 pos: usize,
134 }
135
136 impl<'a> Parser<'a> {
137 fn current(&self) -> Option<SyntaxKind> {
138 if self.pos < self.tokens.len() {
139 Some(self.tokens[self.tokens.len() - 1 - self.pos].0)
140 } else {
141 None
142 }
143 }
144
145 fn bump(&mut self) {
146 if self.pos < self.tokens.len() {
147 let (kind, text) = self.tokens[self.tokens.len() - 1 - self.pos];
148 self.builder.token(kind.into(), text);
149 self.pos += 1;
150 }
151 }
152
153 fn skip_ws(&mut self) {
154 while self.current() == Some(SyntaxKind::WHITESPACE) {
155 self.bump();
156 }
157 }
158
159 fn skip_blank_lines(&mut self) {
160 while let Some(kind) = self.current() {
161 match kind {
162 SyntaxKind::NEWLINE => {
163 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
164 self.bump();
165 self.builder.finish_node();
166 }
167 SyntaxKind::WHITESPACE => {
168 if self.pos + 1 < self.tokens.len()
170 && self.tokens[self.tokens.len() - 2 - self.pos].0
171 == SyntaxKind::NEWLINE
172 {
173 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
174 self.bump(); self.bump(); self.builder.finish_node();
177 } else {
178 break;
179 }
180 }
181 _ => break,
182 }
183 }
184 }
185
186 fn parse_section_header(&mut self) {
187 self.builder.start_node(SyntaxKind::SECTION_HEADER.into());
188
189 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
191 self.bump();
192 } else {
193 self.errors
194 .push("expected '[' at start of section header".to_string());
195 }
196
197 if self.current() == Some(SyntaxKind::SECTION_NAME) {
199 self.bump();
200 } else {
201 self.errors
202 .push("expected section name in section header".to_string());
203 }
204
205 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
207 self.bump();
208 } else {
209 self.errors
210 .push("expected ']' at end of section header".to_string());
211 }
212
213 if self.current() == Some(SyntaxKind::NEWLINE) {
215 self.bump();
216 }
217
218 self.builder.finish_node();
219 }
220
221 fn parse_entry(&mut self) {
222 self.builder.start_node(SyntaxKind::ENTRY.into());
223
224 if self.current() == Some(SyntaxKind::COMMENT) {
226 self.bump();
227 if self.current() == Some(SyntaxKind::NEWLINE) {
228 self.bump();
229 }
230 self.builder.finish_node();
231 return;
232 }
233
234 if self.current() == Some(SyntaxKind::KEY) {
236 self.bump();
237 } else {
238 self.errors
239 .push(format!("expected key, got {:?}", self.current()));
240 }
241
242 self.skip_ws();
243
244 if self.current() == Some(SyntaxKind::EQUALS) {
246 self.bump();
247 } else {
248 self.errors.push("expected '=' after key".to_string());
249 }
250
251 self.skip_ws();
252
253 while let Some(kind) = self.current() {
255 match kind {
256 SyntaxKind::VALUE => self.bump(),
257 SyntaxKind::LINE_CONTINUATION => {
258 self.bump();
259 self.skip_ws();
261 }
262 SyntaxKind::NEWLINE => {
263 self.bump();
264 break;
265 }
266 _ => break,
267 }
268 }
269
270 self.builder.finish_node();
271 }
272
273 fn parse_section(&mut self) {
274 self.builder.start_node(SyntaxKind::SECTION.into());
275
276 self.parse_section_header();
278
279 while let Some(kind) = self.current() {
281 match kind {
282 SyntaxKind::LEFT_BRACKET => break, SyntaxKind::KEY | SyntaxKind::COMMENT => self.parse_entry(),
284 SyntaxKind::NEWLINE | SyntaxKind::WHITESPACE => {
285 self.skip_blank_lines();
286 }
287 _ => {
288 self.errors
289 .push(format!("unexpected token in section: {:?}", kind));
290 self.bump();
291 }
292 }
293 }
294
295 self.builder.finish_node();
296 }
297
298 fn parse_file(&mut self) {
299 self.builder.start_node(SyntaxKind::ROOT.into());
300
301 while let Some(kind) = self.current() {
303 match kind {
304 SyntaxKind::COMMENT => {
305 self.builder.start_node(SyntaxKind::ENTRY.into());
306 self.bump();
307 if self.current() == Some(SyntaxKind::NEWLINE) {
308 self.bump();
309 }
310 self.builder.finish_node();
311 }
312 SyntaxKind::NEWLINE | SyntaxKind::WHITESPACE => {
313 self.skip_blank_lines();
314 }
315 _ => break,
316 }
317 }
318
319 while self.current().is_some() {
321 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
322 self.parse_section();
323 } else {
324 self.errors
325 .push(format!("expected section header, got {:?}", self.current()));
326 self.bump();
327 }
328 }
329
330 self.builder.finish_node();
331 }
332 }
333
334 let mut tokens: Vec<_> = lex(text).collect();
335 tokens.reverse();
336
337 let mut parser = Parser {
338 tokens,
339 builder: GreenNodeBuilder::new(),
340 errors: Vec::new(),
341 positioned_errors: Vec::new(),
342 pos: 0,
343 };
344
345 parser.parse_file();
346
347 ParseResult {
348 green_node: parser.builder.finish(),
349 errors: parser.errors,
350 positioned_errors: parser.positioned_errors,
351 }
352}
353
354type SyntaxNode = rowan::SyntaxNode<Lang>;
356
357#[derive(Debug, Clone, PartialEq, Eq, Hash)]
359pub struct SystemdUnit(SyntaxNode);
360
361impl SystemdUnit {
362 pub fn sections(&self) -> impl Iterator<Item = Section> {
364 self.0.children().filter_map(Section::cast)
365 }
366
367 pub fn get_section(&self, name: &str) -> Option<Section> {
369 self.sections().find(|s| s.name().as_deref() == Some(name))
370 }
371
372 pub fn add_section(&mut self, name: &str) {
374 let new_section = Section::new(name);
375 let insertion_index = self.0.children_with_tokens().count();
376 self.0
377 .splice_children(insertion_index..insertion_index, vec![new_section.0.into()]);
378 }
379
380 pub fn syntax(&self) -> &SyntaxNode {
382 &self.0
383 }
384
385 pub fn text(&self) -> String {
387 self.0.text().to_string()
388 }
389
390 pub fn from_file(path: &Path) -> Result<Self, Error> {
392 let text = std::fs::read_to_string(path)?;
393 Self::from_str(&text)
394 }
395
396 pub fn write_to_file(&self, path: &Path) -> Result<(), Error> {
398 std::fs::write(path, self.text())?;
399 Ok(())
400 }
401}
402
403impl AstNode for SystemdUnit {
404 type Language = Lang;
405
406 fn can_cast(kind: SyntaxKind) -> bool {
407 kind == SyntaxKind::ROOT
408 }
409
410 fn cast(node: SyntaxNode) -> Option<Self> {
411 if node.kind() == SyntaxKind::ROOT {
412 Some(SystemdUnit(node))
413 } else {
414 None
415 }
416 }
417
418 fn syntax(&self) -> &SyntaxNode {
419 &self.0
420 }
421}
422
423impl FromStr for SystemdUnit {
424 type Err = Error;
425
426 fn from_str(s: &str) -> Result<Self, Self::Err> {
427 let parsed = parse(s);
428 if !parsed.errors.is_empty() {
429 return Err(Error::ParseError(ParseError(parsed.errors)));
430 }
431 let node = SyntaxNode::new_root_mut(parsed.green_node);
432 Ok(SystemdUnit::cast(node).expect("root node should be SystemdUnit"))
433 }
434}
435
436impl std::fmt::Display for SystemdUnit {
437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
438 write!(f, "{}", self.0.text())
439 }
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, Hash)]
444pub struct Section(SyntaxNode);
445
446impl Section {
447 pub fn new(name: &str) -> Section {
449 use rowan::GreenNodeBuilder;
450
451 let mut builder = GreenNodeBuilder::new();
452 builder.start_node(SyntaxKind::SECTION.into());
453
454 builder.start_node(SyntaxKind::SECTION_HEADER.into());
456 builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
457 builder.token(SyntaxKind::SECTION_NAME.into(), name);
458 builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
459 builder.token(SyntaxKind::NEWLINE.into(), "\n");
460 builder.finish_node();
461
462 builder.finish_node();
463 Section(SyntaxNode::new_root_mut(builder.finish()))
464 }
465
466 pub fn name(&self) -> Option<String> {
468 let header = self
469 .0
470 .children()
471 .find(|n| n.kind() == SyntaxKind::SECTION_HEADER)?;
472 let value = header
473 .children_with_tokens()
474 .find(|e| e.kind() == SyntaxKind::SECTION_NAME)?;
475 Some(value.as_token()?.text().to_string())
476 }
477
478 pub fn entries(&self) -> impl Iterator<Item = Entry> {
480 self.0.children().filter_map(Entry::cast)
481 }
482
483 pub fn get(&self, key: &str) -> Option<String> {
485 self.entries()
486 .find(|e| e.key().as_deref() == Some(key))
487 .and_then(|e| e.value())
488 }
489
490 pub fn get_all(&self, key: &str) -> Vec<String> {
492 self.entries()
493 .filter(|e| e.key().as_deref() == Some(key))
494 .filter_map(|e| e.value())
495 .collect()
496 }
497
498 pub fn set(&mut self, key: &str, value: &str) {
500 let new_entry = Entry::new(key, value);
501
502 for entry in self.entries() {
504 if entry.key().as_deref() == Some(key) {
505 self.0.splice_children(
506 entry.0.index()..entry.0.index() + 1,
507 vec![new_entry.0.into()],
508 );
509 return;
510 }
511 }
512
513 let children: Vec<_> = self.0.children_with_tokens().collect();
515 let insertion_index = children
516 .iter()
517 .enumerate()
518 .rev()
519 .find(|(_, child)| {
520 child.kind() != SyntaxKind::BLANK_LINE
521 && child.kind() != SyntaxKind::NEWLINE
522 && child.kind() != SyntaxKind::WHITESPACE
523 })
524 .map(|(idx, _)| idx + 1)
525 .unwrap_or(children.len());
526
527 self.0
528 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
529 }
530
531 pub fn add(&mut self, key: &str, value: &str) {
533 let new_entry = Entry::new(key, value);
534
535 let children: Vec<_> = self.0.children_with_tokens().collect();
537 let insertion_index = children
538 .iter()
539 .enumerate()
540 .rev()
541 .find(|(_, child)| {
542 child.kind() != SyntaxKind::BLANK_LINE
543 && child.kind() != SyntaxKind::NEWLINE
544 && child.kind() != SyntaxKind::WHITESPACE
545 })
546 .map(|(idx, _)| idx + 1)
547 .unwrap_or(children.len());
548
549 self.0
550 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
551 }
552
553 pub fn insert_at(&mut self, index: usize, key: &str, value: &str) {
580 let new_entry = Entry::new(key, value);
581
582 let entries: Vec<_> = self.entries().collect();
584
585 if index >= entries.len() {
586 self.add(key, value);
588 } else {
589 let target_entry = &entries[index];
591 let insertion_index = target_entry.0.index();
592 self.0
593 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
594 }
595 }
596
597 pub fn insert_before(&mut self, existing_key: &str, key: &str, value: &str) {
623 let new_entry = Entry::new(key, value);
624
625 let target_entry = self
627 .entries()
628 .find(|e| e.key().as_deref() == Some(existing_key));
629
630 if let Some(entry) = target_entry {
631 let insertion_index = entry.0.index();
632 self.0
633 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
634 }
635 }
637
638 pub fn insert_after(&mut self, existing_key: &str, key: &str, value: &str) {
664 let new_entry = Entry::new(key, value);
665
666 let target_entry = self
668 .entries()
669 .find(|e| e.key().as_deref() == Some(existing_key));
670
671 if let Some(entry) = target_entry {
672 let insertion_index = entry.0.index() + 1;
673 self.0
674 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
675 }
676 }
678
679 pub fn set_list(&mut self, key: &str, values: &[&str]) {
695 let value = values.join(" ");
696 self.set(key, &value);
697 }
698
699 pub fn get_list(&self, key: &str) -> Vec<String> {
704 self.entries()
705 .find(|e| e.key().as_deref() == Some(key))
706 .map(|e| e.value_as_list())
707 .unwrap_or_default()
708 }
709
710 pub fn get_bool(&self, key: &str) -> Option<bool> {
724 self.entries()
725 .find(|e| e.key().as_deref() == Some(key))
726 .and_then(|e| e.value_as_bool())
727 }
728
729 pub fn set_bool(&mut self, key: &str, value: bool) {
744 self.set(key, Entry::format_bool(value));
745 }
746
747 pub fn remove(&mut self, key: &str) {
749 let entry_to_remove = self.0.children().find_map(|child| {
751 let entry = Entry::cast(child)?;
752 if entry.key().as_deref() == Some(key) {
753 Some(entry)
754 } else {
755 None
756 }
757 });
758
759 if let Some(entry) = entry_to_remove {
760 entry.syntax().detach();
761 }
762 }
763
764 pub fn remove_all(&mut self, key: &str) {
766 let entries_to_remove: Vec<_> = self
768 .0
769 .children()
770 .filter_map(Entry::cast)
771 .filter(|e| e.key().as_deref() == Some(key))
772 .collect();
773
774 for entry in entries_to_remove {
775 entry.syntax().detach();
776 }
777 }
778
779 pub fn remove_value(&mut self, key: &str, value_to_remove: &str) {
807 let entries_to_process: Vec<_> = self
809 .entries()
810 .filter(|e| e.key().as_deref() == Some(key))
811 .collect();
812
813 for entry in entries_to_process {
814 let current_list = entry.value_as_list();
816
817 let new_list: Vec<_> = current_list
819 .iter()
820 .filter(|v| v.as_str() != value_to_remove)
821 .map(|s| s.as_str())
822 .collect();
823
824 if new_list.is_empty() {
825 entry.syntax().detach();
827 } else if new_list.len() < current_list.len() {
828 let new_entry = Entry::new(key, &new_list.join(" "));
831
832 let index = entry.0.index();
834 self.0
835 .splice_children(index..index + 1, vec![new_entry.0.into()]);
836 }
837 }
839 }
840
841 pub fn remove_entries_where<F>(&mut self, mut predicate: F)
872 where
873 F: FnMut(&str, &str) -> bool,
874 {
875 let entries_to_remove: Vec<_> = self
877 .entries()
878 .filter(|entry| {
879 if let (Some(key), Some(value)) = (entry.key(), entry.value()) {
880 predicate(&key, &value)
881 } else {
882 false
883 }
884 })
885 .collect();
886
887 for entry in entries_to_remove {
888 entry.syntax().detach();
889 }
890 }
891
892 pub fn syntax(&self) -> &SyntaxNode {
894 &self.0
895 }
896}
897
898impl AstNode for Section {
899 type Language = Lang;
900
901 fn can_cast(kind: SyntaxKind) -> bool {
902 kind == SyntaxKind::SECTION
903 }
904
905 fn cast(node: SyntaxNode) -> Option<Self> {
906 if node.kind() == SyntaxKind::SECTION {
907 Some(Section(node))
908 } else {
909 None
910 }
911 }
912
913 fn syntax(&self) -> &SyntaxNode {
914 &self.0
915 }
916}
917
918fn unescape_string(s: &str) -> String {
920 let mut result = String::new();
921 let mut chars = s.chars().peekable();
922
923 while let Some(ch) = chars.next() {
924 if ch == '\\' {
925 match chars.next() {
926 Some('n') => result.push('\n'),
927 Some('t') => result.push('\t'),
928 Some('r') => result.push('\r'),
929 Some('\\') => result.push('\\'),
930 Some('"') => result.push('"'),
931 Some('\'') => result.push('\''),
932 Some('x') => {
933 let hex: String = chars.by_ref().take(2).collect();
935 if let Ok(byte) = u8::from_str_radix(&hex, 16) {
936 result.push(byte as char);
937 } else {
938 result.push('\\');
940 result.push('x');
941 result.push_str(&hex);
942 }
943 }
944 Some('u') => {
945 let hex: String = chars.by_ref().take(4).collect();
947 if let Ok(code) = u32::from_str_radix(&hex, 16) {
948 if let Some(unicode_char) = char::from_u32(code) {
949 result.push(unicode_char);
950 } else {
951 result.push('\\');
953 result.push('u');
954 result.push_str(&hex);
955 }
956 } else {
957 result.push('\\');
959 result.push('u');
960 result.push_str(&hex);
961 }
962 }
963 Some('U') => {
964 let hex: String = chars.by_ref().take(8).collect();
966 if let Ok(code) = u32::from_str_radix(&hex, 16) {
967 if let Some(unicode_char) = char::from_u32(code) {
968 result.push(unicode_char);
969 } else {
970 result.push('\\');
972 result.push('U');
973 result.push_str(&hex);
974 }
975 } else {
976 result.push('\\');
978 result.push('U');
979 result.push_str(&hex);
980 }
981 }
982 Some(c) if c.is_ascii_digit() => {
983 let mut octal = String::from(c);
985 for _ in 0..2 {
986 if let Some(&next_ch) = chars.peek() {
987 if next_ch.is_ascii_digit() && next_ch < '8' {
988 octal.push(chars.next().unwrap());
989 } else {
990 break;
991 }
992 }
993 }
994 if let Ok(byte) = u8::from_str_radix(&octal, 8) {
995 result.push(byte as char);
996 } else {
997 result.push('\\');
999 result.push_str(&octal);
1000 }
1001 }
1002 Some(c) => {
1003 result.push('\\');
1005 result.push(c);
1006 }
1007 None => {
1008 result.push('\\');
1010 }
1011 }
1012 } else {
1013 result.push(ch);
1014 }
1015 }
1016
1017 result
1018}
1019
1020fn escape_string(s: &str) -> String {
1022 let mut result = String::new();
1023
1024 for ch in s.chars() {
1025 match ch {
1026 '\\' => result.push_str("\\\\"),
1027 '\n' => result.push_str("\\n"),
1028 '\t' => result.push_str("\\t"),
1029 '\r' => result.push_str("\\r"),
1030 '"' => result.push_str("\\\""),
1031 _ => result.push(ch),
1032 }
1033 }
1034
1035 result
1036}
1037
1038fn unquote_string(s: &str) -> String {
1046 let trimmed = s.trim();
1047
1048 if trimmed.len() < 2 {
1049 return trimmed.to_string();
1050 }
1051
1052 let first = trimmed.chars().next();
1053 let last = trimmed.chars().last();
1054
1055 if let (Some('"'), Some('"')) = (first, last) {
1057 trimmed[1..trimmed.len() - 1].to_string()
1059 } else if let (Some('\''), Some('\'')) = (first, last) {
1060 trimmed[1..trimmed.len() - 1].to_string()
1062 } else {
1063 trimmed.to_string()
1065 }
1066}
1067
1068#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1070pub struct Entry(SyntaxNode);
1071
1072impl Entry {
1073 pub fn new(key: &str, value: &str) -> Entry {
1075 use rowan::GreenNodeBuilder;
1076
1077 let mut builder = GreenNodeBuilder::new();
1078 builder.start_node(SyntaxKind::ENTRY.into());
1079 builder.token(SyntaxKind::KEY.into(), key);
1080 builder.token(SyntaxKind::EQUALS.into(), "=");
1081 builder.token(SyntaxKind::VALUE.into(), value);
1082 builder.token(SyntaxKind::NEWLINE.into(), "\n");
1083 builder.finish_node();
1084 Entry(SyntaxNode::new_root_mut(builder.finish()))
1085 }
1086
1087 pub fn key(&self) -> Option<String> {
1089 let key_token = self
1090 .0
1091 .children_with_tokens()
1092 .find(|e| e.kind() == SyntaxKind::KEY)?;
1093 Some(key_token.as_token()?.text().to_string())
1094 }
1095
1096 pub fn value(&self) -> Option<String> {
1098 let mut found_equals = false;
1100 let mut value_parts = Vec::new();
1101
1102 for element in self.0.children_with_tokens() {
1103 match element.kind() {
1104 SyntaxKind::EQUALS => found_equals = true,
1105 SyntaxKind::VALUE if found_equals => {
1106 value_parts.push(element.as_token()?.text().to_string());
1107 }
1108 SyntaxKind::LINE_CONTINUATION if found_equals => {
1109 let should_add_space = value_parts
1112 .last()
1113 .map(|s| !s.ends_with(' ') && !s.ends_with('\t'))
1114 .unwrap_or(true);
1115 if should_add_space {
1116 value_parts.push(" ".to_string());
1117 }
1118 }
1119 SyntaxKind::WHITESPACE if found_equals && !value_parts.is_empty() => {
1120 value_parts.push(element.as_token()?.text().to_string());
1123 }
1124 SyntaxKind::NEWLINE => break,
1125 _ => {}
1126 }
1127 }
1128
1129 if value_parts.is_empty() {
1130 None
1131 } else {
1132 Some(value_parts.join(""))
1134 }
1135 }
1136
1137 pub fn raw_value(&self) -> Option<String> {
1139 let mut found_equals = false;
1140 let mut value_parts = Vec::new();
1141
1142 for element in self.0.children_with_tokens() {
1143 match element.kind() {
1144 SyntaxKind::EQUALS => found_equals = true,
1145 SyntaxKind::VALUE if found_equals => {
1146 value_parts.push(element.as_token()?.text().to_string());
1147 }
1148 SyntaxKind::LINE_CONTINUATION if found_equals => {
1149 value_parts.push(element.as_token()?.text().to_string());
1150 }
1151 SyntaxKind::WHITESPACE if found_equals => {
1152 value_parts.push(element.as_token()?.text().to_string());
1153 }
1154 SyntaxKind::NEWLINE => break,
1155 _ => {}
1156 }
1157 }
1158
1159 if value_parts.is_empty() {
1160 None
1161 } else {
1162 Some(value_parts.join(""))
1163 }
1164 }
1165
1166 pub fn unescape_value(&self) -> Option<String> {
1180 let value = self.value()?;
1181 Some(unescape_string(&value))
1182 }
1183
1184 pub fn escape_value(value: &str) -> String {
1193 escape_string(value)
1194 }
1195
1196 pub fn is_quoted(&self) -> Option<char> {
1201 let value = self.value()?;
1202 let trimmed = value.trim();
1203
1204 if trimmed.len() < 2 {
1205 return None;
1206 }
1207
1208 let first = trimmed.chars().next()?;
1209 let last = trimmed.chars().last()?;
1210
1211 if (first == '"' || first == '\'') && first == last {
1212 Some(first)
1213 } else {
1214 None
1215 }
1216 }
1217
1218 pub fn unquoted_value(&self) -> Option<String> {
1223 let value = self.value()?;
1224 Some(unquote_string(&value))
1225 }
1226
1227 pub fn quoted_value(&self) -> Option<String> {
1231 self.value()
1233 }
1234
1235 pub fn value_as_list(&self) -> Vec<String> {
1243 let value = match self.unquoted_value() {
1244 Some(v) => v,
1245 None => return Vec::new(),
1246 };
1247
1248 value.split_whitespace().map(|s| s.to_string()).collect()
1249 }
1250
1251 pub fn value_as_bool(&self) -> Option<bool> {
1270 let value = self.unquoted_value()?;
1271 let value_lower = value.trim().to_lowercase();
1272
1273 match value_lower.as_str() {
1274 "1" | "yes" | "true" | "on" => Some(true),
1275 "0" | "no" | "false" | "off" => Some(false),
1276 _ => None,
1277 }
1278 }
1279
1280 pub fn format_bool(value: bool) -> &'static str {
1294 if value {
1295 "yes"
1296 } else {
1297 "no"
1298 }
1299 }
1300
1301 pub fn expand_specifiers(
1321 &self,
1322 context: &crate::specifier::SpecifierContext,
1323 ) -> Option<String> {
1324 let value = self.value()?;
1325 Some(context.expand(&value))
1326 }
1327
1328 pub fn set_value(&self, new_value: &str) {
1362 let key = self.key().expect("Entry should have a key");
1363 let new_entry = Entry::new(&key, new_value);
1364
1365 let parent = self.0.parent().expect("Entry should have a parent");
1367 let index = self.0.index();
1368 parent.splice_children(index..index + 1, vec![new_entry.0.into()]);
1369 }
1370
1371 pub fn syntax(&self) -> &SyntaxNode {
1373 &self.0
1374 }
1375}
1376
1377impl AstNode for Entry {
1378 type Language = Lang;
1379
1380 fn can_cast(kind: SyntaxKind) -> bool {
1381 kind == SyntaxKind::ENTRY
1382 }
1383
1384 fn cast(node: SyntaxNode) -> Option<Self> {
1385 if node.kind() == SyntaxKind::ENTRY {
1386 Some(Entry(node))
1387 } else {
1388 None
1389 }
1390 }
1391
1392 fn syntax(&self) -> &SyntaxNode {
1393 &self.0
1394 }
1395}
1396
1397#[cfg(test)]
1398mod tests {
1399 use super::*;
1400
1401 #[test]
1402 fn test_parse_simple() {
1403 let input = r#"[Unit]
1404Description=Test Service
1405After=network.target
1406"#;
1407 let unit = SystemdUnit::from_str(input).unwrap();
1408 assert_eq!(unit.sections().count(), 1);
1409
1410 let section = unit.sections().next().unwrap();
1411 assert_eq!(section.name(), Some("Unit".to_string()));
1412 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1413 assert_eq!(section.get("After"), Some("network.target".to_string()));
1414 }
1415
1416 #[test]
1417 fn test_parse_with_comments() {
1418 let input = r#"# Top comment
1419[Unit]
1420# Comment before description
1421Description=Test Service
1422; Semicolon comment
1423After=network.target
1424"#;
1425 let unit = SystemdUnit::from_str(input).unwrap();
1426 assert_eq!(unit.sections().count(), 1);
1427
1428 let section = unit.sections().next().unwrap();
1429 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1430 }
1431
1432 #[test]
1433 fn test_parse_multiple_sections() {
1434 let input = r#"[Unit]
1435Description=Test Service
1436
1437[Service]
1438Type=simple
1439ExecStart=/usr/bin/test
1440
1441[Install]
1442WantedBy=multi-user.target
1443"#;
1444 let unit = SystemdUnit::from_str(input).unwrap();
1445 assert_eq!(unit.sections().count(), 3);
1446
1447 let unit_section = unit.get_section("Unit").unwrap();
1448 assert_eq!(
1449 unit_section.get("Description"),
1450 Some("Test Service".to_string())
1451 );
1452
1453 let service_section = unit.get_section("Service").unwrap();
1454 assert_eq!(service_section.get("Type"), Some("simple".to_string()));
1455 assert_eq!(
1456 service_section.get("ExecStart"),
1457 Some("/usr/bin/test".to_string())
1458 );
1459
1460 let install_section = unit.get_section("Install").unwrap();
1461 assert_eq!(
1462 install_section.get("WantedBy"),
1463 Some("multi-user.target".to_string())
1464 );
1465 }
1466
1467 #[test]
1468 fn test_parse_with_spaces() {
1469 let input = "[Unit]\nDescription = Test Service\n";
1470 let unit = SystemdUnit::from_str(input).unwrap();
1471
1472 let section = unit.sections().next().unwrap();
1473 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1474 }
1475
1476 #[test]
1477 fn test_line_continuation() {
1478 let input = "[Service]\nExecStart=/bin/echo \\\n hello world\n";
1479 let unit = SystemdUnit::from_str(input).unwrap();
1480
1481 let section = unit.sections().next().unwrap();
1482 let entry = section.entries().next().unwrap();
1483 assert_eq!(entry.key(), Some("ExecStart".to_string()));
1484 assert_eq!(entry.value(), Some("/bin/echo hello world".to_string()));
1486 }
1487
1488 #[test]
1489 fn test_lossless_roundtrip() {
1490 let input = r#"# Comment
1491[Unit]
1492Description=Test Service
1493After=network.target
1494
1495[Service]
1496Type=simple
1497ExecStart=/usr/bin/test
1498"#;
1499 let unit = SystemdUnit::from_str(input).unwrap();
1500 let output = unit.text();
1501 assert_eq!(input, output);
1502 }
1503
1504 #[test]
1505 fn test_set_value() {
1506 let input = r#"[Unit]
1507Description=Test Service
1508"#;
1509 let unit = SystemdUnit::from_str(input).unwrap();
1510 {
1511 let mut section = unit.sections().next().unwrap();
1512 section.set("Description", "Updated Service");
1513 }
1514
1515 let section = unit.sections().next().unwrap();
1516 assert_eq!(
1517 section.get("Description"),
1518 Some("Updated Service".to_string())
1519 );
1520 }
1521
1522 #[test]
1523 fn test_add_new_entry() {
1524 let input = r#"[Unit]
1525Description=Test Service
1526"#;
1527 let unit = SystemdUnit::from_str(input).unwrap();
1528 {
1529 let mut section = unit.sections().next().unwrap();
1530 section.set("After", "network.target");
1531 }
1532
1533 let section = unit.sections().next().unwrap();
1534 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1535 assert_eq!(section.get("After"), Some("network.target".to_string()));
1536 }
1537
1538 #[test]
1539 fn test_multiple_values_same_key() {
1540 let input = r#"[Unit]
1541Wants=foo.service
1542Wants=bar.service
1543"#;
1544 let unit = SystemdUnit::from_str(input).unwrap();
1545 let section = unit.sections().next().unwrap();
1546
1547 assert_eq!(section.get("Wants"), Some("foo.service".to_string()));
1549
1550 let all_wants = section.get_all("Wants");
1552 assert_eq!(all_wants.len(), 2);
1553 assert_eq!(all_wants[0], "foo.service");
1554 assert_eq!(all_wants[1], "bar.service");
1555 }
1556
1557 #[test]
1558 fn test_add_multiple_entries() {
1559 let input = r#"[Unit]
1560Description=Test Service
1561"#;
1562 let unit = SystemdUnit::from_str(input).unwrap();
1563 {
1564 let mut section = unit.sections().next().unwrap();
1565 section.add("Wants", "foo.service");
1566 section.add("Wants", "bar.service");
1567 }
1568
1569 let section = unit.sections().next().unwrap();
1570 let all_wants = section.get_all("Wants");
1571 assert_eq!(all_wants.len(), 2);
1572 assert_eq!(all_wants[0], "foo.service");
1573 assert_eq!(all_wants[1], "bar.service");
1574 }
1575
1576 #[test]
1577 fn test_remove_entry() {
1578 let input = r#"[Unit]
1579Description=Test Service
1580After=network.target
1581"#;
1582 let unit = SystemdUnit::from_str(input).unwrap();
1583 {
1584 let mut section = unit.sections().next().unwrap();
1585 section.remove("After");
1586 }
1587
1588 let section = unit.sections().next().unwrap();
1589 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1590 assert_eq!(section.get("After"), None);
1591 }
1592
1593 #[test]
1594 fn test_remove_all_entries() {
1595 let input = r#"[Unit]
1596Wants=foo.service
1597Wants=bar.service
1598Description=Test
1599"#;
1600 let unit = SystemdUnit::from_str(input).unwrap();
1601 {
1602 let mut section = unit.sections().next().unwrap();
1603 section.remove_all("Wants");
1604 }
1605
1606 let section = unit.sections().next().unwrap();
1607 assert_eq!(section.get_all("Wants").len(), 0);
1608 assert_eq!(section.get("Description"), Some("Test".to_string()));
1609 }
1610
1611 #[test]
1612 fn test_unescape_basic() {
1613 let input = r#"[Unit]
1614Description=Test\nService
1615"#;
1616 let unit = SystemdUnit::from_str(input).unwrap();
1617 let section = unit.sections().next().unwrap();
1618 let entry = section.entries().next().unwrap();
1619
1620 assert_eq!(entry.value(), Some("Test\\nService".to_string()));
1621 assert_eq!(entry.unescape_value(), Some("Test\nService".to_string()));
1622 }
1623
1624 #[test]
1625 fn test_unescape_all_escapes() {
1626 let input = r#"[Unit]
1627Value=\n\t\r\\\"\'\x41\101\u0041\U00000041
1628"#;
1629 let unit = SystemdUnit::from_str(input).unwrap();
1630 let section = unit.sections().next().unwrap();
1631 let entry = section.entries().next().unwrap();
1632
1633 let unescaped = entry.unescape_value().unwrap();
1634 assert_eq!(unescaped, "\n\t\r\\\"'AAAA");
1638 }
1639
1640 #[test]
1641 fn test_unescape_unicode() {
1642 let input = r#"[Unit]
1643Value=Hello\u0020World\U0001F44D
1644"#;
1645 let unit = SystemdUnit::from_str(input).unwrap();
1646 let section = unit.sections().next().unwrap();
1647 let entry = section.entries().next().unwrap();
1648
1649 let unescaped = entry.unescape_value().unwrap();
1650 assert_eq!(unescaped, "Hello World👍");
1652 }
1653
1654 #[test]
1655 fn test_escape_value() {
1656 let text = "Hello\nWorld\t\"Test\"\\Path";
1657 let escaped = Entry::escape_value(text);
1658 assert_eq!(escaped, "Hello\\nWorld\\t\\\"Test\\\"\\\\Path");
1659 }
1660
1661 #[test]
1662 fn test_escape_unescape_roundtrip() {
1663 let original = "Test\nwith\ttabs\rand\"quotes\"\\backslash";
1664 let escaped = Entry::escape_value(original);
1665 let unescaped = unescape_string(&escaped);
1666 assert_eq!(original, unescaped);
1667 }
1668
1669 #[test]
1670 fn test_unescape_invalid_sequences() {
1671 let input = r#"[Unit]
1673Value=\z\xFF\u12\U1234
1674"#;
1675 let unit = SystemdUnit::from_str(input).unwrap();
1676 let section = unit.sections().next().unwrap();
1677 let entry = section.entries().next().unwrap();
1678
1679 let unescaped = entry.unescape_value().unwrap();
1680 assert!(unescaped.contains("\\z"));
1682 }
1683
1684 #[test]
1685 fn test_quoted_double_quotes() {
1686 let input = r#"[Unit]
1687Description="Test Service"
1688"#;
1689 let unit = SystemdUnit::from_str(input).unwrap();
1690 let section = unit.sections().next().unwrap();
1691 let entry = section.entries().next().unwrap();
1692
1693 assert_eq!(entry.value(), Some("\"Test Service\"".to_string()));
1694 assert_eq!(entry.quoted_value(), Some("\"Test Service\"".to_string()));
1695 assert_eq!(entry.unquoted_value(), Some("Test Service".to_string()));
1696 assert_eq!(entry.is_quoted(), Some('"'));
1697 }
1698
1699 #[test]
1700 fn test_quoted_single_quotes() {
1701 let input = r#"[Unit]
1702Description='Test Service'
1703"#;
1704 let unit = SystemdUnit::from_str(input).unwrap();
1705 let section = unit.sections().next().unwrap();
1706 let entry = section.entries().next().unwrap();
1707
1708 assert_eq!(entry.value(), Some("'Test Service'".to_string()));
1709 assert_eq!(entry.unquoted_value(), Some("Test Service".to_string()));
1710 assert_eq!(entry.is_quoted(), Some('\''));
1711 }
1712
1713 #[test]
1714 fn test_quoted_with_whitespace() {
1715 let input = r#"[Unit]
1716Description=" Test Service "
1717"#;
1718 let unit = SystemdUnit::from_str(input).unwrap();
1719 let section = unit.sections().next().unwrap();
1720 let entry = section.entries().next().unwrap();
1721
1722 assert_eq!(entry.unquoted_value(), Some(" Test Service ".to_string()));
1724 }
1725
1726 #[test]
1727 fn test_unquoted_value() {
1728 let input = r#"[Unit]
1729Description=Test Service
1730"#;
1731 let unit = SystemdUnit::from_str(input).unwrap();
1732 let section = unit.sections().next().unwrap();
1733 let entry = section.entries().next().unwrap();
1734
1735 assert_eq!(entry.value(), Some("Test Service".to_string()));
1736 assert_eq!(entry.unquoted_value(), Some("Test Service".to_string()));
1737 assert_eq!(entry.is_quoted(), None);
1738 }
1739
1740 #[test]
1741 fn test_mismatched_quotes() {
1742 let input = r#"[Unit]
1743Description="Test Service'
1744"#;
1745 let unit = SystemdUnit::from_str(input).unwrap();
1746 let section = unit.sections().next().unwrap();
1747 let entry = section.entries().next().unwrap();
1748
1749 assert_eq!(entry.is_quoted(), None);
1751 assert_eq!(entry.unquoted_value(), Some("\"Test Service'".to_string()));
1752 }
1753
1754 #[test]
1755 fn test_empty_quotes() {
1756 let input = r#"[Unit]
1757Description=""
1758"#;
1759 let unit = SystemdUnit::from_str(input).unwrap();
1760 let section = unit.sections().next().unwrap();
1761 let entry = section.entries().next().unwrap();
1762
1763 assert_eq!(entry.is_quoted(), Some('"'));
1764 assert_eq!(entry.unquoted_value(), Some("".to_string()));
1765 }
1766
1767 #[test]
1768 fn test_value_as_list() {
1769 let input = r#"[Unit]
1770After=network.target remote-fs.target
1771"#;
1772 let unit = SystemdUnit::from_str(input).unwrap();
1773 let section = unit.sections().next().unwrap();
1774 let entry = section.entries().next().unwrap();
1775
1776 let list = entry.value_as_list();
1777 assert_eq!(list.len(), 2);
1778 assert_eq!(list[0], "network.target");
1779 assert_eq!(list[1], "remote-fs.target");
1780 }
1781
1782 #[test]
1783 fn test_value_as_list_single() {
1784 let input = r#"[Unit]
1785After=network.target
1786"#;
1787 let unit = SystemdUnit::from_str(input).unwrap();
1788 let section = unit.sections().next().unwrap();
1789 let entry = section.entries().next().unwrap();
1790
1791 let list = entry.value_as_list();
1792 assert_eq!(list.len(), 1);
1793 assert_eq!(list[0], "network.target");
1794 }
1795
1796 #[test]
1797 fn test_value_as_list_empty() {
1798 let input = r#"[Unit]
1799After=
1800"#;
1801 let unit = SystemdUnit::from_str(input).unwrap();
1802 let section = unit.sections().next().unwrap();
1803 let entry = section.entries().next().unwrap();
1804
1805 let list = entry.value_as_list();
1806 assert_eq!(list.len(), 0);
1807 }
1808
1809 #[test]
1810 fn test_value_as_list_with_extra_whitespace() {
1811 let input = r#"[Unit]
1812After= network.target remote-fs.target
1813"#;
1814 let unit = SystemdUnit::from_str(input).unwrap();
1815 let section = unit.sections().next().unwrap();
1816 let entry = section.entries().next().unwrap();
1817
1818 let list = entry.value_as_list();
1819 assert_eq!(list.len(), 2);
1820 assert_eq!(list[0], "network.target");
1821 assert_eq!(list[1], "remote-fs.target");
1822 }
1823
1824 #[test]
1825 fn test_section_get_list() {
1826 let input = r#"[Unit]
1827After=network.target remote-fs.target
1828"#;
1829 let unit = SystemdUnit::from_str(input).unwrap();
1830 let section = unit.sections().next().unwrap();
1831
1832 let list = section.get_list("After");
1833 assert_eq!(list.len(), 2);
1834 assert_eq!(list[0], "network.target");
1835 assert_eq!(list[1], "remote-fs.target");
1836 }
1837
1838 #[test]
1839 fn test_section_get_list_missing() {
1840 let input = r#"[Unit]
1841Description=Test
1842"#;
1843 let unit = SystemdUnit::from_str(input).unwrap();
1844 let section = unit.sections().next().unwrap();
1845
1846 let list = section.get_list("After");
1847 assert_eq!(list.len(), 0);
1848 }
1849
1850 #[test]
1851 fn test_section_set_list() {
1852 let input = r#"[Unit]
1853Description=Test
1854"#;
1855 let unit = SystemdUnit::from_str(input).unwrap();
1856 {
1857 let mut section = unit.sections().next().unwrap();
1858 section.set_list("After", &["network.target", "remote-fs.target"]);
1859 }
1860
1861 let section = unit.sections().next().unwrap();
1862 let list = section.get_list("After");
1863 assert_eq!(list.len(), 2);
1864 assert_eq!(list[0], "network.target");
1865 assert_eq!(list[1], "remote-fs.target");
1866 }
1867
1868 #[test]
1869 fn test_section_set_list_replaces() {
1870 let input = r#"[Unit]
1871After=foo.target
1872"#;
1873 let unit = SystemdUnit::from_str(input).unwrap();
1874 {
1875 let mut section = unit.sections().next().unwrap();
1876 section.set_list("After", &["network.target", "remote-fs.target"]);
1877 }
1878
1879 let section = unit.sections().next().unwrap();
1880 let list = section.get_list("After");
1881 assert_eq!(list.len(), 2);
1882 assert_eq!(list[0], "network.target");
1883 assert_eq!(list[1], "remote-fs.target");
1884 }
1885
1886 #[test]
1887 fn test_value_as_bool_positive() {
1888 let inputs = vec!["yes", "true", "1", "on", "YES", "True", "ON"];
1889
1890 for input_val in inputs {
1891 let input = format!("[Service]\nRemainAfterExit={}\n", input_val);
1892 let unit = SystemdUnit::from_str(&input).unwrap();
1893 let section = unit.sections().next().unwrap();
1894 let entry = section.entries().next().unwrap();
1895 assert_eq!(
1896 entry.value_as_bool(),
1897 Some(true),
1898 "Failed for input: {}",
1899 input_val
1900 );
1901 }
1902 }
1903
1904 #[test]
1905 fn test_value_as_bool_negative() {
1906 let inputs = vec!["no", "false", "0", "off", "NO", "False", "OFF"];
1907
1908 for input_val in inputs {
1909 let input = format!("[Service]\nRemainAfterExit={}\n", input_val);
1910 let unit = SystemdUnit::from_str(&input).unwrap();
1911 let section = unit.sections().next().unwrap();
1912 let entry = section.entries().next().unwrap();
1913 assert_eq!(
1914 entry.value_as_bool(),
1915 Some(false),
1916 "Failed for input: {}",
1917 input_val
1918 );
1919 }
1920 }
1921
1922 #[test]
1923 fn test_value_as_bool_invalid() {
1924 let input = r#"[Service]
1925RemainAfterExit=maybe
1926"#;
1927 let unit = SystemdUnit::from_str(input).unwrap();
1928 let section = unit.sections().next().unwrap();
1929 let entry = section.entries().next().unwrap();
1930 assert_eq!(entry.value_as_bool(), None);
1931 }
1932
1933 #[test]
1934 fn test_value_as_bool_with_whitespace() {
1935 let input = r#"[Service]
1936RemainAfterExit= yes
1937"#;
1938 let unit = SystemdUnit::from_str(input).unwrap();
1939 let section = unit.sections().next().unwrap();
1940 let entry = section.entries().next().unwrap();
1941 assert_eq!(entry.value_as_bool(), Some(true));
1942 }
1943
1944 #[test]
1945 fn test_format_bool() {
1946 assert_eq!(Entry::format_bool(true), "yes");
1947 assert_eq!(Entry::format_bool(false), "no");
1948 }
1949
1950 #[test]
1951 fn test_section_get_bool() {
1952 let input = r#"[Service]
1953RemainAfterExit=yes
1954Type=simple
1955"#;
1956 let unit = SystemdUnit::from_str(input).unwrap();
1957 let section = unit.sections().next().unwrap();
1958
1959 assert_eq!(section.get_bool("RemainAfterExit"), Some(true));
1960 assert_eq!(section.get_bool("Type"), None); assert_eq!(section.get_bool("Missing"), None); }
1963
1964 #[test]
1965 fn test_section_set_bool() {
1966 let input = r#"[Service]
1967Type=simple
1968"#;
1969 let unit = SystemdUnit::from_str(input).unwrap();
1970 {
1971 let mut section = unit.sections().next().unwrap();
1972 section.set_bool("RemainAfterExit", true);
1973 section.set_bool("PrivateTmp", false);
1974 }
1975
1976 let section = unit.sections().next().unwrap();
1977 assert_eq!(section.get("RemainAfterExit"), Some("yes".to_string()));
1978 assert_eq!(section.get("PrivateTmp"), Some("no".to_string()));
1979 assert_eq!(section.get_bool("RemainAfterExit"), Some(true));
1980 assert_eq!(section.get_bool("PrivateTmp"), Some(false));
1981 }
1982
1983 #[test]
1984 fn test_add_entry_with_trailing_whitespace() {
1985 let input = r#"[Unit]
1987Description=Test Service
1988
1989"#;
1990 let unit = SystemdUnit::from_str(input).unwrap();
1991 {
1992 let mut section = unit.sections().next().unwrap();
1993 section.add("After", "network.target");
1994 }
1995
1996 let output = unit.text();
1997 let expected = r#"[Unit]
1999Description=Test Service
2000After=network.target
2001
2002"#;
2003 assert_eq!(output, expected);
2004 }
2005
2006 #[test]
2007 fn test_set_new_entry_with_trailing_whitespace() {
2008 let input = r#"[Unit]
2010Description=Test Service
2011
2012"#;
2013 let unit = SystemdUnit::from_str(input).unwrap();
2014 {
2015 let mut section = unit.sections().next().unwrap();
2016 section.set("After", "network.target");
2017 }
2018
2019 let output = unit.text();
2020 let expected = r#"[Unit]
2022Description=Test Service
2023After=network.target
2024
2025"#;
2026 assert_eq!(output, expected);
2027 }
2028
2029 #[test]
2030 fn test_remove_value_from_space_separated_list() {
2031 let input = r#"[Unit]
2032After=network.target syslog.target remote-fs.target
2033"#;
2034 let unit = SystemdUnit::from_str(input).unwrap();
2035 {
2036 let mut section = unit.sections().next().unwrap();
2037 section.remove_value("After", "syslog.target");
2038 }
2039
2040 let section = unit.sections().next().unwrap();
2041 assert_eq!(
2042 section.get("After"),
2043 Some("network.target remote-fs.target".to_string())
2044 );
2045 }
2046
2047 #[test]
2048 fn test_remove_value_removes_entire_entry() {
2049 let input = r#"[Unit]
2050After=syslog.target
2051Description=Test
2052"#;
2053 let unit = SystemdUnit::from_str(input).unwrap();
2054 {
2055 let mut section = unit.sections().next().unwrap();
2056 section.remove_value("After", "syslog.target");
2057 }
2058
2059 let section = unit.sections().next().unwrap();
2060 assert_eq!(section.get("After"), None);
2061 assert_eq!(section.get("Description"), Some("Test".to_string()));
2062 }
2063
2064 #[test]
2065 fn test_remove_value_from_multiple_entries() {
2066 let input = r#"[Unit]
2067After=network.target syslog.target
2068After=remote-fs.target
2069After=syslog.target multi-user.target
2070"#;
2071 let unit = SystemdUnit::from_str(input).unwrap();
2072 {
2073 let mut section = unit.sections().next().unwrap();
2074 section.remove_value("After", "syslog.target");
2075 }
2076
2077 let section = unit.sections().next().unwrap();
2078 let all_after = section.get_all("After");
2079 assert_eq!(all_after.len(), 3);
2080 assert_eq!(all_after[0], "network.target");
2081 assert_eq!(all_after[1], "remote-fs.target");
2082 assert_eq!(all_after[2], "multi-user.target");
2083 }
2084
2085 #[test]
2086 fn test_remove_value_not_found() {
2087 let input = r#"[Unit]
2088After=network.target remote-fs.target
2089"#;
2090 let unit = SystemdUnit::from_str(input).unwrap();
2091 {
2092 let mut section = unit.sections().next().unwrap();
2093 section.remove_value("After", "nonexistent.target");
2094 }
2095
2096 let section = unit.sections().next().unwrap();
2097 assert_eq!(
2099 section.get("After"),
2100 Some("network.target remote-fs.target".to_string())
2101 );
2102 }
2103
2104 #[test]
2105 fn test_remove_value_preserves_order() {
2106 let input = r#"[Unit]
2107Description=Test Service
2108After=network.target syslog.target
2109Wants=foo.service
2110After=remote-fs.target
2111Requires=bar.service
2112"#;
2113 let unit = SystemdUnit::from_str(input).unwrap();
2114 {
2115 let mut section = unit.sections().next().unwrap();
2116 section.remove_value("After", "syslog.target");
2117 }
2118
2119 let section = unit.sections().next().unwrap();
2120 let entries: Vec<_> = section.entries().collect();
2121
2122 assert_eq!(entries[0].key(), Some("Description".to_string()));
2124 assert_eq!(entries[1].key(), Some("After".to_string()));
2125 assert_eq!(entries[1].value(), Some("network.target".to_string()));
2126 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2127 assert_eq!(entries[3].key(), Some("After".to_string()));
2128 assert_eq!(entries[3].value(), Some("remote-fs.target".to_string()));
2129 assert_eq!(entries[4].key(), Some("Requires".to_string()));
2130 }
2131
2132 #[test]
2133 fn test_remove_value_key_not_found() {
2134 let input = r#"[Unit]
2135Description=Test Service
2136"#;
2137 let unit = SystemdUnit::from_str(input).unwrap();
2138 {
2139 let mut section = unit.sections().next().unwrap();
2140 section.remove_value("After", "network.target");
2141 }
2142
2143 let section = unit.sections().next().unwrap();
2145 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
2146 assert_eq!(section.get("After"), None);
2147 }
2148
2149 #[test]
2150 fn test_entry_set_value_basic() {
2151 let input = r#"[Unit]
2152After=network.target
2153Description=Test
2154"#;
2155 let unit = SystemdUnit::from_str(input).unwrap();
2156 let section = unit.get_section("Unit").unwrap();
2157
2158 for entry in section.entries() {
2159 if entry.key().as_deref() == Some("After") {
2160 entry.set_value("remote-fs.target");
2161 }
2162 }
2163
2164 let section = unit.get_section("Unit").unwrap();
2165 assert_eq!(section.get("After"), Some("remote-fs.target".to_string()));
2166 assert_eq!(section.get("Description"), Some("Test".to_string()));
2167 }
2168
2169 #[test]
2170 fn test_entry_set_value_preserves_order() {
2171 let input = r#"[Unit]
2172Description=Test Service
2173After=network.target syslog.target
2174Wants=foo.service
2175After=remote-fs.target
2176Requires=bar.service
2177"#;
2178 let unit = SystemdUnit::from_str(input).unwrap();
2179 let section = unit.get_section("Unit").unwrap();
2180
2181 for entry in section.entries() {
2182 if entry.key().as_deref() == Some("After") {
2183 let values = entry.value_as_list();
2184 let filtered: Vec<_> = values
2185 .iter()
2186 .filter(|v| v.as_str() != "syslog.target")
2187 .map(|s| s.as_str())
2188 .collect();
2189 if !filtered.is_empty() {
2190 entry.set_value(&filtered.join(" "));
2191 }
2192 }
2193 }
2194
2195 let section = unit.get_section("Unit").unwrap();
2196 let entries: Vec<_> = section.entries().collect();
2197
2198 assert_eq!(entries[0].key(), Some("Description".to_string()));
2200 assert_eq!(entries[1].key(), Some("After".to_string()));
2201 assert_eq!(entries[1].value(), Some("network.target".to_string()));
2202 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2203 assert_eq!(entries[3].key(), Some("After".to_string()));
2204 assert_eq!(entries[3].value(), Some("remote-fs.target".to_string()));
2205 assert_eq!(entries[4].key(), Some("Requires".to_string()));
2206 }
2207
2208 #[test]
2209 fn test_entry_set_value_multiple_entries() {
2210 let input = r#"[Unit]
2211After=network.target
2212After=syslog.target
2213After=remote-fs.target
2214"#;
2215 let unit = SystemdUnit::from_str(input).unwrap();
2216 let section = unit.get_section("Unit").unwrap();
2217
2218 let entries_to_modify: Vec<_> = section
2220 .entries()
2221 .filter(|e| e.key().as_deref() == Some("After"))
2222 .collect();
2223
2224 for entry in entries_to_modify {
2226 let old_value = entry.value().unwrap();
2227 entry.set_value(&format!("{} multi-user.target", old_value));
2228 }
2229
2230 let section = unit.get_section("Unit").unwrap();
2231 let all_after = section.get_all("After");
2232 assert_eq!(all_after.len(), 3);
2233 assert_eq!(all_after[0], "network.target multi-user.target");
2234 assert_eq!(all_after[1], "syslog.target multi-user.target");
2235 assert_eq!(all_after[2], "remote-fs.target multi-user.target");
2236 }
2237
2238 #[test]
2239 fn test_entry_set_value_with_empty_string() {
2240 let input = r#"[Unit]
2241After=network.target
2242"#;
2243 let unit = SystemdUnit::from_str(input).unwrap();
2244 let section = unit.get_section("Unit").unwrap();
2245
2246 for entry in section.entries() {
2247 if entry.key().as_deref() == Some("After") {
2248 entry.set_value("");
2249 }
2250 }
2251
2252 let section = unit.get_section("Unit").unwrap();
2253 assert_eq!(section.get("After"), Some("".to_string()));
2254 }
2255
2256 #[test]
2257 fn test_remove_entries_where_basic() {
2258 let input = r#"[Unit]
2259After=network.target
2260Wants=foo.service
2261After=syslog.target
2262"#;
2263 let unit = SystemdUnit::from_str(input).unwrap();
2264 {
2265 let mut section = unit.sections().next().unwrap();
2266 section.remove_entries_where(|key, _value| key == "After");
2267 }
2268
2269 let section = unit.sections().next().unwrap();
2270 assert_eq!(section.get_all("After").len(), 0);
2271 assert_eq!(section.get("Wants"), Some("foo.service".to_string()));
2272 }
2273
2274 #[test]
2275 fn test_remove_entries_where_with_value_check() {
2276 let input = r#"[Unit]
2277After=network.target syslog.target
2278Wants=foo.service
2279After=remote-fs.target
2280"#;
2281 let unit = SystemdUnit::from_str(input).unwrap();
2282 {
2283 let mut section = unit.sections().next().unwrap();
2284 section.remove_entries_where(|key, value| {
2285 key == "After" && value.split_whitespace().any(|v| v == "syslog.target")
2286 });
2287 }
2288
2289 let section = unit.sections().next().unwrap();
2290 let all_after = section.get_all("After");
2291 assert_eq!(all_after.len(), 1);
2292 assert_eq!(all_after[0], "remote-fs.target");
2293 assert_eq!(section.get("Wants"), Some("foo.service".to_string()));
2294 }
2295
2296 #[test]
2297 fn test_remove_entries_where_preserves_order() {
2298 let input = r#"[Unit]
2299Description=Test Service
2300After=network.target
2301Wants=foo.service
2302After=syslog.target
2303Requires=bar.service
2304After=remote-fs.target
2305"#;
2306 let unit = SystemdUnit::from_str(input).unwrap();
2307 {
2308 let mut section = unit.sections().next().unwrap();
2309 section.remove_entries_where(|key, value| key == "After" && value.contains("syslog"));
2310 }
2311
2312 let section = unit.sections().next().unwrap();
2313 let entries: Vec<_> = section.entries().collect();
2314
2315 assert_eq!(entries.len(), 5);
2316 assert_eq!(entries[0].key(), Some("Description".to_string()));
2317 assert_eq!(entries[1].key(), Some("After".to_string()));
2318 assert_eq!(entries[1].value(), Some("network.target".to_string()));
2319 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2320 assert_eq!(entries[3].key(), Some("Requires".to_string()));
2321 assert_eq!(entries[4].key(), Some("After".to_string()));
2322 assert_eq!(entries[4].value(), Some("remote-fs.target".to_string()));
2323 }
2324
2325 #[test]
2326 fn test_remove_entries_where_no_matches() {
2327 let input = r#"[Unit]
2328After=network.target
2329Wants=foo.service
2330"#;
2331 let unit = SystemdUnit::from_str(input).unwrap();
2332 {
2333 let mut section = unit.sections().next().unwrap();
2334 section.remove_entries_where(|key, _value| key == "Requires");
2335 }
2336
2337 let section = unit.sections().next().unwrap();
2338 assert_eq!(section.get("After"), Some("network.target".to_string()));
2339 assert_eq!(section.get("Wants"), Some("foo.service".to_string()));
2340 }
2341
2342 #[test]
2343 fn test_remove_entries_where_all_entries() {
2344 let input = r#"[Unit]
2345After=network.target
2346Wants=foo.service
2347Requires=bar.service
2348"#;
2349 let unit = SystemdUnit::from_str(input).unwrap();
2350 {
2351 let mut section = unit.sections().next().unwrap();
2352 section.remove_entries_where(|_key, _value| true);
2353 }
2354
2355 let section = unit.sections().next().unwrap();
2356 assert_eq!(section.entries().count(), 0);
2357 }
2358
2359 #[test]
2360 fn test_remove_entries_where_complex_predicate() {
2361 let input = r#"[Unit]
2362After=network.target
2363After=syslog.target remote-fs.target
2364Wants=foo.service
2365After=multi-user.target
2366Requires=bar.service
2367"#;
2368 let unit = SystemdUnit::from_str(input).unwrap();
2369 {
2370 let mut section = unit.sections().next().unwrap();
2371 section.remove_entries_where(|key, value| {
2373 key == "After" && value.split_whitespace().count() > 1
2374 });
2375 }
2376
2377 let section = unit.sections().next().unwrap();
2378 let all_after = section.get_all("After");
2379 assert_eq!(all_after.len(), 2);
2380 assert_eq!(all_after[0], "network.target");
2381 assert_eq!(all_after[1], "multi-user.target");
2382 }
2383
2384 #[test]
2385 fn test_insert_at_beginning() {
2386 let input = r#"[Unit]
2387Description=Test Service
2388After=network.target
2389"#;
2390 let unit = SystemdUnit::from_str(input).unwrap();
2391 {
2392 let mut section = unit.sections().next().unwrap();
2393 section.insert_at(0, "Wants", "foo.service");
2394 }
2395
2396 let section = unit.sections().next().unwrap();
2397 let entries: Vec<_> = section.entries().collect();
2398 assert_eq!(entries.len(), 3);
2399 assert_eq!(entries[0].key(), Some("Wants".to_string()));
2400 assert_eq!(entries[0].value(), Some("foo.service".to_string()));
2401 assert_eq!(entries[1].key(), Some("Description".to_string()));
2402 assert_eq!(entries[2].key(), Some("After".to_string()));
2403 }
2404
2405 #[test]
2406 fn test_insert_at_middle() {
2407 let input = r#"[Unit]
2408Description=Test Service
2409After=network.target
2410"#;
2411 let unit = SystemdUnit::from_str(input).unwrap();
2412 {
2413 let mut section = unit.sections().next().unwrap();
2414 section.insert_at(1, "Wants", "foo.service");
2415 }
2416
2417 let section = unit.sections().next().unwrap();
2418 let entries: Vec<_> = section.entries().collect();
2419 assert_eq!(entries.len(), 3);
2420 assert_eq!(entries[0].key(), Some("Description".to_string()));
2421 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2422 assert_eq!(entries[1].value(), Some("foo.service".to_string()));
2423 assert_eq!(entries[2].key(), Some("After".to_string()));
2424 }
2425
2426 #[test]
2427 fn test_insert_at_end() {
2428 let input = r#"[Unit]
2429Description=Test Service
2430After=network.target
2431"#;
2432 let unit = SystemdUnit::from_str(input).unwrap();
2433 {
2434 let mut section = unit.sections().next().unwrap();
2435 section.insert_at(2, "Wants", "foo.service");
2436 }
2437
2438 let section = unit.sections().next().unwrap();
2439 let entries: Vec<_> = section.entries().collect();
2440 assert_eq!(entries.len(), 3);
2441 assert_eq!(entries[0].key(), Some("Description".to_string()));
2442 assert_eq!(entries[1].key(), Some("After".to_string()));
2443 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2444 assert_eq!(entries[2].value(), Some("foo.service".to_string()));
2445 }
2446
2447 #[test]
2448 fn test_insert_at_beyond_end() {
2449 let input = r#"[Unit]
2450Description=Test Service
2451"#;
2452 let unit = SystemdUnit::from_str(input).unwrap();
2453 {
2454 let mut section = unit.sections().next().unwrap();
2455 section.insert_at(100, "Wants", "foo.service");
2456 }
2457
2458 let section = unit.sections().next().unwrap();
2459 let entries: Vec<_> = section.entries().collect();
2460 assert_eq!(entries.len(), 2);
2461 assert_eq!(entries[0].key(), Some("Description".to_string()));
2462 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2463 assert_eq!(entries[1].value(), Some("foo.service".to_string()));
2464 }
2465
2466 #[test]
2467 fn test_insert_at_empty_section() {
2468 let input = r#"[Unit]
2469"#;
2470 let unit = SystemdUnit::from_str(input).unwrap();
2471 {
2472 let mut section = unit.sections().next().unwrap();
2473 section.insert_at(0, "Description", "Test Service");
2474 }
2475
2476 let section = unit.sections().next().unwrap();
2477 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
2478 }
2479
2480 #[test]
2481 fn test_insert_before_basic() {
2482 let input = r#"[Unit]
2483Description=Test Service
2484After=network.target
2485"#;
2486 let unit = SystemdUnit::from_str(input).unwrap();
2487 {
2488 let mut section = unit.sections().next().unwrap();
2489 section.insert_before("After", "Wants", "foo.service");
2490 }
2491
2492 let section = unit.sections().next().unwrap();
2493 let entries: Vec<_> = section.entries().collect();
2494 assert_eq!(entries.len(), 3);
2495 assert_eq!(entries[0].key(), Some("Description".to_string()));
2496 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2497 assert_eq!(entries[1].value(), Some("foo.service".to_string()));
2498 assert_eq!(entries[2].key(), Some("After".to_string()));
2499 }
2500
2501 #[test]
2502 fn test_insert_before_first_entry() {
2503 let input = r#"[Unit]
2504Description=Test Service
2505After=network.target
2506"#;
2507 let unit = SystemdUnit::from_str(input).unwrap();
2508 {
2509 let mut section = unit.sections().next().unwrap();
2510 section.insert_before("Description", "Wants", "foo.service");
2511 }
2512
2513 let section = unit.sections().next().unwrap();
2514 let entries: Vec<_> = section.entries().collect();
2515 assert_eq!(entries.len(), 3);
2516 assert_eq!(entries[0].key(), Some("Wants".to_string()));
2517 assert_eq!(entries[0].value(), Some("foo.service".to_string()));
2518 assert_eq!(entries[1].key(), Some("Description".to_string()));
2519 assert_eq!(entries[2].key(), Some("After".to_string()));
2520 }
2521
2522 #[test]
2523 fn test_insert_before_nonexistent_key() {
2524 let input = r#"[Unit]
2525Description=Test Service
2526"#;
2527 let unit = SystemdUnit::from_str(input).unwrap();
2528 {
2529 let mut section = unit.sections().next().unwrap();
2530 section.insert_before("After", "Wants", "foo.service");
2531 }
2532
2533 let section = unit.sections().next().unwrap();
2534 let entries: Vec<_> = section.entries().collect();
2535 assert_eq!(entries.len(), 1);
2536 assert_eq!(entries[0].key(), Some("Description".to_string()));
2537 }
2538
2539 #[test]
2540 fn test_insert_before_multiple_occurrences() {
2541 let input = r#"[Unit]
2542After=network.target
2543After=syslog.target
2544"#;
2545 let unit = SystemdUnit::from_str(input).unwrap();
2546 {
2547 let mut section = unit.sections().next().unwrap();
2548 section.insert_before("After", "Wants", "foo.service");
2549 }
2550
2551 let section = unit.sections().next().unwrap();
2552 let entries: Vec<_> = section.entries().collect();
2553 assert_eq!(entries.len(), 3);
2554 assert_eq!(entries[0].key(), Some("Wants".to_string()));
2555 assert_eq!(entries[1].key(), Some("After".to_string()));
2556 assert_eq!(entries[1].value(), Some("network.target".to_string()));
2557 assert_eq!(entries[2].key(), Some("After".to_string()));
2558 assert_eq!(entries[2].value(), Some("syslog.target".to_string()));
2559 }
2560
2561 #[test]
2562 fn test_insert_after_basic() {
2563 let input = r#"[Unit]
2564Description=Test Service
2565After=network.target
2566"#;
2567 let unit = SystemdUnit::from_str(input).unwrap();
2568 {
2569 let mut section = unit.sections().next().unwrap();
2570 section.insert_after("Description", "Wants", "foo.service");
2571 }
2572
2573 let section = unit.sections().next().unwrap();
2574 let entries: Vec<_> = section.entries().collect();
2575 assert_eq!(entries.len(), 3);
2576 assert_eq!(entries[0].key(), Some("Description".to_string()));
2577 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2578 assert_eq!(entries[1].value(), Some("foo.service".to_string()));
2579 assert_eq!(entries[2].key(), Some("After".to_string()));
2580 }
2581
2582 #[test]
2583 fn test_insert_after_last_entry() {
2584 let input = r#"[Unit]
2585Description=Test Service
2586After=network.target
2587"#;
2588 let unit = SystemdUnit::from_str(input).unwrap();
2589 {
2590 let mut section = unit.sections().next().unwrap();
2591 section.insert_after("After", "Wants", "foo.service");
2592 }
2593
2594 let section = unit.sections().next().unwrap();
2595 let entries: Vec<_> = section.entries().collect();
2596 assert_eq!(entries.len(), 3);
2597 assert_eq!(entries[0].key(), Some("Description".to_string()));
2598 assert_eq!(entries[1].key(), Some("After".to_string()));
2599 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2600 assert_eq!(entries[2].value(), Some("foo.service".to_string()));
2601 }
2602
2603 #[test]
2604 fn test_insert_after_nonexistent_key() {
2605 let input = r#"[Unit]
2606Description=Test Service
2607"#;
2608 let unit = SystemdUnit::from_str(input).unwrap();
2609 {
2610 let mut section = unit.sections().next().unwrap();
2611 section.insert_after("After", "Wants", "foo.service");
2612 }
2613
2614 let section = unit.sections().next().unwrap();
2615 let entries: Vec<_> = section.entries().collect();
2616 assert_eq!(entries.len(), 1);
2617 assert_eq!(entries[0].key(), Some("Description".to_string()));
2618 }
2619
2620 #[test]
2621 fn test_insert_after_multiple_occurrences() {
2622 let input = r#"[Unit]
2623After=network.target
2624After=syslog.target
2625"#;
2626 let unit = SystemdUnit::from_str(input).unwrap();
2627 {
2628 let mut section = unit.sections().next().unwrap();
2629 section.insert_after("After", "Wants", "foo.service");
2630 }
2631
2632 let section = unit.sections().next().unwrap();
2633 let entries: Vec<_> = section.entries().collect();
2634 assert_eq!(entries.len(), 3);
2635 assert_eq!(entries[0].key(), Some("After".to_string()));
2636 assert_eq!(entries[0].value(), Some("network.target".to_string()));
2637 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2638 assert_eq!(entries[2].key(), Some("After".to_string()));
2639 assert_eq!(entries[2].value(), Some("syslog.target".to_string()));
2640 }
2641
2642 #[test]
2643 fn test_insert_preserves_whitespace() {
2644 let input = r#"[Unit]
2645Description=Test Service
2646
2647After=network.target
2648"#;
2649 let unit = SystemdUnit::from_str(input).unwrap();
2650 {
2651 let mut section = unit.sections().next().unwrap();
2652 section.insert_at(1, "Wants", "foo.service");
2653 }
2654
2655 let section = unit.sections().next().unwrap();
2656 let entries: Vec<_> = section.entries().collect();
2657 assert_eq!(entries.len(), 3);
2658 assert_eq!(entries[0].key(), Some("Description".to_string()));
2659 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2660 assert_eq!(entries[2].key(), Some("After".to_string()));
2661
2662 let expected = r#"[Unit]
2663Description=Test Service
2664
2665Wants=foo.service
2666After=network.target
2667"#;
2668 assert_eq!(unit.text(), expected);
2669 }
2670}