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
357fn line_col_at_offset(node: &SyntaxNode, offset: rowan::TextSize) -> (usize, usize) {
360 let root = node.ancestors().last().unwrap_or_else(|| node.clone());
361 let mut line = 0;
362 let mut last_newline_offset = rowan::TextSize::from(0);
363
364 for element in root.preorder_with_tokens() {
365 if let rowan::WalkEvent::Enter(rowan::NodeOrToken::Token(token)) = element {
366 if token.text_range().start() >= offset {
367 break;
368 }
369
370 for (idx, _) in token.text().match_indices('\n') {
372 line += 1;
373 last_newline_offset =
374 token.text_range().start() + rowan::TextSize::from((idx + 1) as u32);
375 }
376 }
377 }
378
379 let column: usize = (offset - last_newline_offset).into();
380 (line, column)
381}
382
383#[derive(Debug, Clone, PartialEq, Eq, Hash)]
385pub struct SystemdUnit(SyntaxNode);
386
387impl SystemdUnit {
388 pub fn sections(&self) -> impl Iterator<Item = Section> {
390 self.0.children().filter_map(Section::cast)
391 }
392
393 pub fn get_section(&self, name: &str) -> Option<Section> {
395 self.sections().find(|s| s.name().as_deref() == Some(name))
396 }
397
398 pub fn add_section(&mut self, name: &str) {
400 let new_section = Section::new(name);
401 let insertion_index = self.0.children_with_tokens().count();
402 self.0
403 .splice_children(insertion_index..insertion_index, vec![new_section.0.into()]);
404 }
405
406 pub fn syntax(&self) -> &SyntaxNode {
408 &self.0
409 }
410
411 pub fn text(&self) -> String {
413 self.0.text().to_string()
414 }
415
416 pub fn from_file(path: &Path) -> Result<Self, Error> {
418 let text = std::fs::read_to_string(path)?;
419 Self::from_str(&text)
420 }
421
422 pub fn write_to_file(&self, path: &Path) -> Result<(), Error> {
424 std::fs::write(path, self.text())?;
425 Ok(())
426 }
427
428 pub fn line(&self) -> usize {
430 line_col_at_offset(&self.0, self.0.text_range().start()).0
431 }
432
433 pub fn column(&self) -> usize {
435 line_col_at_offset(&self.0, self.0.text_range().start()).1
436 }
437
438 pub fn line_col(&self) -> (usize, usize) {
441 line_col_at_offset(&self.0, self.0.text_range().start())
442 }
443}
444
445impl AstNode for SystemdUnit {
446 type Language = Lang;
447
448 fn can_cast(kind: SyntaxKind) -> bool {
449 kind == SyntaxKind::ROOT
450 }
451
452 fn cast(node: SyntaxNode) -> Option<Self> {
453 if node.kind() == SyntaxKind::ROOT {
454 Some(SystemdUnit(node))
455 } else {
456 None
457 }
458 }
459
460 fn syntax(&self) -> &SyntaxNode {
461 &self.0
462 }
463}
464
465impl FromStr for SystemdUnit {
466 type Err = Error;
467
468 fn from_str(s: &str) -> Result<Self, Self::Err> {
469 let parsed = parse(s);
470 if !parsed.errors.is_empty() {
471 return Err(Error::ParseError(ParseError(parsed.errors)));
472 }
473 let node = SyntaxNode::new_root_mut(parsed.green_node);
474 Ok(SystemdUnit::cast(node).expect("root node should be SystemdUnit"))
475 }
476}
477
478impl std::fmt::Display for SystemdUnit {
479 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
480 write!(f, "{}", self.0.text())
481 }
482}
483
484#[derive(Debug, Clone, PartialEq, Eq, Hash)]
486pub struct Section(SyntaxNode);
487
488impl Section {
489 pub fn new(name: &str) -> Section {
491 use rowan::GreenNodeBuilder;
492
493 let mut builder = GreenNodeBuilder::new();
494 builder.start_node(SyntaxKind::SECTION.into());
495
496 builder.start_node(SyntaxKind::SECTION_HEADER.into());
498 builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
499 builder.token(SyntaxKind::SECTION_NAME.into(), name);
500 builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
501 builder.token(SyntaxKind::NEWLINE.into(), "\n");
502 builder.finish_node();
503
504 builder.finish_node();
505 Section(SyntaxNode::new_root_mut(builder.finish()))
506 }
507
508 pub fn name(&self) -> Option<String> {
510 let header = self
511 .0
512 .children()
513 .find(|n| n.kind() == SyntaxKind::SECTION_HEADER)?;
514 let value = header
515 .children_with_tokens()
516 .find(|e| e.kind() == SyntaxKind::SECTION_NAME)?;
517 Some(value.as_token()?.text().to_string())
518 }
519
520 pub fn entries(&self) -> impl Iterator<Item = Entry> {
522 self.0.children().filter_map(Entry::cast)
523 }
524
525 pub fn get(&self, key: &str) -> Option<String> {
527 self.entries()
528 .find(|e| e.key().as_deref() == Some(key))
529 .and_then(|e| e.value())
530 }
531
532 pub fn get_all(&self, key: &str) -> Vec<String> {
534 self.entries()
535 .filter(|e| e.key().as_deref() == Some(key))
536 .filter_map(|e| e.value())
537 .collect()
538 }
539
540 pub fn set(&mut self, key: &str, value: &str) {
542 let new_entry = Entry::new(key, value);
543
544 for entry in self.entries() {
546 if entry.key().as_deref() == Some(key) {
547 self.0.splice_children(
548 entry.0.index()..entry.0.index() + 1,
549 vec![new_entry.0.into()],
550 );
551 return;
552 }
553 }
554
555 let children: Vec<_> = self.0.children_with_tokens().collect();
557 let insertion_index = children
558 .iter()
559 .enumerate()
560 .rev()
561 .find(|(_, child)| {
562 child.kind() != SyntaxKind::BLANK_LINE
563 && child.kind() != SyntaxKind::NEWLINE
564 && child.kind() != SyntaxKind::WHITESPACE
565 })
566 .map(|(idx, _)| idx + 1)
567 .unwrap_or(children.len());
568
569 self.0
570 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
571 }
572
573 pub fn add(&mut self, key: &str, value: &str) {
575 let new_entry = Entry::new(key, value);
576
577 let children: Vec<_> = self.0.children_with_tokens().collect();
579 let insertion_index = children
580 .iter()
581 .enumerate()
582 .rev()
583 .find(|(_, child)| {
584 child.kind() != SyntaxKind::BLANK_LINE
585 && child.kind() != SyntaxKind::NEWLINE
586 && child.kind() != SyntaxKind::WHITESPACE
587 })
588 .map(|(idx, _)| idx + 1)
589 .unwrap_or(children.len());
590
591 self.0
592 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
593 }
594
595 pub fn insert_at(&mut self, index: usize, key: &str, value: &str) {
622 let new_entry = Entry::new(key, value);
623
624 let entries: Vec<_> = self.entries().collect();
626
627 if index >= entries.len() {
628 self.add(key, value);
630 } else {
631 let target_entry = &entries[index];
633 let insertion_index = target_entry.0.index();
634 self.0
635 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
636 }
637 }
638
639 pub fn insert_before(&mut self, existing_key: &str, key: &str, value: &str) {
665 let new_entry = Entry::new(key, value);
666
667 let target_entry = self
669 .entries()
670 .find(|e| e.key().as_deref() == Some(existing_key));
671
672 if let Some(entry) = target_entry {
673 let insertion_index = entry.0.index();
674 self.0
675 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
676 }
677 }
679
680 pub fn insert_after(&mut self, existing_key: &str, key: &str, value: &str) {
706 let new_entry = Entry::new(key, value);
707
708 let target_entry = self
710 .entries()
711 .find(|e| e.key().as_deref() == Some(existing_key));
712
713 if let Some(entry) = target_entry {
714 let insertion_index = entry.0.index() + 1;
715 self.0
716 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
717 }
718 }
720
721 pub fn set_list(&mut self, key: &str, values: &[&str]) {
737 let value = values.join(" ");
738 self.set(key, &value);
739 }
740
741 pub fn get_list(&self, key: &str) -> Vec<String> {
746 self.entries()
747 .find(|e| e.key().as_deref() == Some(key))
748 .map(|e| e.value_as_list())
749 .unwrap_or_default()
750 }
751
752 pub fn get_bool(&self, key: &str) -> Option<bool> {
766 self.entries()
767 .find(|e| e.key().as_deref() == Some(key))
768 .and_then(|e| e.value_as_bool())
769 }
770
771 pub fn set_bool(&mut self, key: &str, value: bool) {
786 self.set(key, Entry::format_bool(value));
787 }
788
789 pub fn remove(&mut self, key: &str) {
791 let entry_to_remove = self.0.children().find_map(|child| {
793 let entry = Entry::cast(child)?;
794 if entry.key().as_deref() == Some(key) {
795 Some(entry)
796 } else {
797 None
798 }
799 });
800
801 if let Some(entry) = entry_to_remove {
802 entry.syntax().detach();
803 }
804 }
805
806 pub fn remove_all(&mut self, key: &str) {
808 let entries_to_remove: Vec<_> = self
810 .0
811 .children()
812 .filter_map(Entry::cast)
813 .filter(|e| e.key().as_deref() == Some(key))
814 .collect();
815
816 for entry in entries_to_remove {
817 entry.syntax().detach();
818 }
819 }
820
821 pub fn remove_value(&mut self, key: &str, value_to_remove: &str) {
849 let entries_to_process: Vec<_> = self
851 .entries()
852 .filter(|e| e.key().as_deref() == Some(key))
853 .collect();
854
855 for entry in entries_to_process {
856 let current_list = entry.value_as_list();
858
859 let new_list: Vec<_> = current_list
861 .iter()
862 .filter(|v| v.as_str() != value_to_remove)
863 .map(|s| s.as_str())
864 .collect();
865
866 if new_list.is_empty() {
867 entry.syntax().detach();
869 } else if new_list.len() < current_list.len() {
870 let new_entry = Entry::new(key, &new_list.join(" "));
873
874 let index = entry.0.index();
876 self.0
877 .splice_children(index..index + 1, vec![new_entry.0.into()]);
878 }
879 }
881 }
882
883 pub fn remove_entries_where<F>(&mut self, mut predicate: F)
914 where
915 F: FnMut(&str, &str) -> bool,
916 {
917 let entries_to_remove: Vec<_> = self
919 .entries()
920 .filter(|entry| {
921 if let (Some(key), Some(value)) = (entry.key(), entry.value()) {
922 predicate(&key, &value)
923 } else {
924 false
925 }
926 })
927 .collect();
928
929 for entry in entries_to_remove {
930 entry.syntax().detach();
931 }
932 }
933
934 pub fn syntax(&self) -> &SyntaxNode {
936 &self.0
937 }
938
939 pub fn line(&self) -> usize {
941 line_col_at_offset(&self.0, self.0.text_range().start()).0
942 }
943
944 pub fn column(&self) -> usize {
946 line_col_at_offset(&self.0, self.0.text_range().start()).1
947 }
948
949 pub fn line_col(&self) -> (usize, usize) {
952 line_col_at_offset(&self.0, self.0.text_range().start())
953 }
954}
955
956impl AstNode for Section {
957 type Language = Lang;
958
959 fn can_cast(kind: SyntaxKind) -> bool {
960 kind == SyntaxKind::SECTION
961 }
962
963 fn cast(node: SyntaxNode) -> Option<Self> {
964 if node.kind() == SyntaxKind::SECTION {
965 Some(Section(node))
966 } else {
967 None
968 }
969 }
970
971 fn syntax(&self) -> &SyntaxNode {
972 &self.0
973 }
974}
975
976fn unescape_string(s: &str) -> String {
978 let mut result = String::new();
979 let mut chars = s.chars().peekable();
980
981 while let Some(ch) = chars.next() {
982 if ch == '\\' {
983 match chars.next() {
984 Some('n') => result.push('\n'),
985 Some('t') => result.push('\t'),
986 Some('r') => result.push('\r'),
987 Some('\\') => result.push('\\'),
988 Some('"') => result.push('"'),
989 Some('\'') => result.push('\''),
990 Some('x') => {
991 let hex: String = chars.by_ref().take(2).collect();
993 if let Ok(byte) = u8::from_str_radix(&hex, 16) {
994 result.push(byte as char);
995 } else {
996 result.push('\\');
998 result.push('x');
999 result.push_str(&hex);
1000 }
1001 }
1002 Some('u') => {
1003 let hex: String = chars.by_ref().take(4).collect();
1005 if let Ok(code) = u32::from_str_radix(&hex, 16) {
1006 if let Some(unicode_char) = char::from_u32(code) {
1007 result.push(unicode_char);
1008 } else {
1009 result.push('\\');
1011 result.push('u');
1012 result.push_str(&hex);
1013 }
1014 } else {
1015 result.push('\\');
1017 result.push('u');
1018 result.push_str(&hex);
1019 }
1020 }
1021 Some('U') => {
1022 let hex: String = chars.by_ref().take(8).collect();
1024 if let Ok(code) = u32::from_str_radix(&hex, 16) {
1025 if let Some(unicode_char) = char::from_u32(code) {
1026 result.push(unicode_char);
1027 } else {
1028 result.push('\\');
1030 result.push('U');
1031 result.push_str(&hex);
1032 }
1033 } else {
1034 result.push('\\');
1036 result.push('U');
1037 result.push_str(&hex);
1038 }
1039 }
1040 Some(c) if c.is_ascii_digit() => {
1041 let mut octal = String::from(c);
1043 for _ in 0..2 {
1044 if let Some(&next_ch) = chars.peek() {
1045 if next_ch.is_ascii_digit() && next_ch < '8' {
1046 octal.push(chars.next().unwrap());
1047 } else {
1048 break;
1049 }
1050 }
1051 }
1052 if let Ok(byte) = u8::from_str_radix(&octal, 8) {
1053 result.push(byte as char);
1054 } else {
1055 result.push('\\');
1057 result.push_str(&octal);
1058 }
1059 }
1060 Some(c) => {
1061 result.push('\\');
1063 result.push(c);
1064 }
1065 None => {
1066 result.push('\\');
1068 }
1069 }
1070 } else {
1071 result.push(ch);
1072 }
1073 }
1074
1075 result
1076}
1077
1078fn escape_string(s: &str) -> String {
1080 let mut result = String::new();
1081
1082 for ch in s.chars() {
1083 match ch {
1084 '\\' => result.push_str("\\\\"),
1085 '\n' => result.push_str("\\n"),
1086 '\t' => result.push_str("\\t"),
1087 '\r' => result.push_str("\\r"),
1088 '"' => result.push_str("\\\""),
1089 _ => result.push(ch),
1090 }
1091 }
1092
1093 result
1094}
1095
1096fn unquote_string(s: &str) -> String {
1104 let trimmed = s.trim();
1105
1106 if trimmed.len() < 2 {
1107 return trimmed.to_string();
1108 }
1109
1110 let first = trimmed.chars().next();
1111 let last = trimmed.chars().last();
1112
1113 if let (Some('"'), Some('"')) = (first, last) {
1115 trimmed[1..trimmed.len() - 1].to_string()
1117 } else if let (Some('\''), Some('\'')) = (first, last) {
1118 trimmed[1..trimmed.len() - 1].to_string()
1120 } else {
1121 trimmed.to_string()
1123 }
1124}
1125
1126#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1128pub struct Entry(SyntaxNode);
1129
1130impl Entry {
1131 pub fn new(key: &str, value: &str) -> Entry {
1133 use rowan::GreenNodeBuilder;
1134
1135 let mut builder = GreenNodeBuilder::new();
1136 builder.start_node(SyntaxKind::ENTRY.into());
1137 builder.token(SyntaxKind::KEY.into(), key);
1138 builder.token(SyntaxKind::EQUALS.into(), "=");
1139 builder.token(SyntaxKind::VALUE.into(), value);
1140 builder.token(SyntaxKind::NEWLINE.into(), "\n");
1141 builder.finish_node();
1142 Entry(SyntaxNode::new_root_mut(builder.finish()))
1143 }
1144
1145 pub fn key(&self) -> Option<String> {
1147 let key_token = self
1148 .0
1149 .children_with_tokens()
1150 .find(|e| e.kind() == SyntaxKind::KEY)?;
1151 Some(key_token.as_token()?.text().to_string())
1152 }
1153
1154 pub fn value(&self) -> Option<String> {
1156 let mut found_equals = false;
1158 let mut value_parts = Vec::new();
1159
1160 for element in self.0.children_with_tokens() {
1161 match element.kind() {
1162 SyntaxKind::EQUALS => found_equals = true,
1163 SyntaxKind::VALUE if found_equals => {
1164 value_parts.push(element.as_token()?.text().to_string());
1165 }
1166 SyntaxKind::LINE_CONTINUATION if found_equals => {
1167 let should_add_space = value_parts
1170 .last()
1171 .map(|s| !s.ends_with(' ') && !s.ends_with('\t'))
1172 .unwrap_or(true);
1173 if should_add_space {
1174 value_parts.push(" ".to_string());
1175 }
1176 }
1177 SyntaxKind::WHITESPACE if found_equals && !value_parts.is_empty() => {
1178 value_parts.push(element.as_token()?.text().to_string());
1181 }
1182 SyntaxKind::NEWLINE => break,
1183 _ => {}
1184 }
1185 }
1186
1187 if value_parts.is_empty() {
1188 None
1189 } else {
1190 Some(value_parts.join(""))
1192 }
1193 }
1194
1195 pub fn raw_value(&self) -> Option<String> {
1197 let mut found_equals = false;
1198 let mut value_parts = Vec::new();
1199
1200 for element in self.0.children_with_tokens() {
1201 match element.kind() {
1202 SyntaxKind::EQUALS => found_equals = true,
1203 SyntaxKind::VALUE if found_equals => {
1204 value_parts.push(element.as_token()?.text().to_string());
1205 }
1206 SyntaxKind::LINE_CONTINUATION if found_equals => {
1207 value_parts.push(element.as_token()?.text().to_string());
1208 }
1209 SyntaxKind::WHITESPACE if found_equals => {
1210 value_parts.push(element.as_token()?.text().to_string());
1211 }
1212 SyntaxKind::NEWLINE => break,
1213 _ => {}
1214 }
1215 }
1216
1217 if value_parts.is_empty() {
1218 None
1219 } else {
1220 Some(value_parts.join(""))
1221 }
1222 }
1223
1224 pub fn unescape_value(&self) -> Option<String> {
1238 let value = self.value()?;
1239 Some(unescape_string(&value))
1240 }
1241
1242 pub fn escape_value(value: &str) -> String {
1251 escape_string(value)
1252 }
1253
1254 pub fn is_quoted(&self) -> Option<char> {
1259 let value = self.value()?;
1260 let trimmed = value.trim();
1261
1262 if trimmed.len() < 2 {
1263 return None;
1264 }
1265
1266 let first = trimmed.chars().next()?;
1267 let last = trimmed.chars().last()?;
1268
1269 if (first == '"' || first == '\'') && first == last {
1270 Some(first)
1271 } else {
1272 None
1273 }
1274 }
1275
1276 pub fn unquoted_value(&self) -> Option<String> {
1281 let value = self.value()?;
1282 Some(unquote_string(&value))
1283 }
1284
1285 pub fn quoted_value(&self) -> Option<String> {
1289 self.value()
1291 }
1292
1293 pub fn value_as_list(&self) -> Vec<String> {
1301 let value = match self.unquoted_value() {
1302 Some(v) => v,
1303 None => return Vec::new(),
1304 };
1305
1306 value.split_whitespace().map(|s| s.to_string()).collect()
1307 }
1308
1309 pub fn value_as_bool(&self) -> Option<bool> {
1328 let value = self.unquoted_value()?;
1329 let value_lower = value.trim().to_lowercase();
1330
1331 match value_lower.as_str() {
1332 "1" | "yes" | "true" | "on" => Some(true),
1333 "0" | "no" | "false" | "off" => Some(false),
1334 _ => None,
1335 }
1336 }
1337
1338 pub fn format_bool(value: bool) -> &'static str {
1352 if value {
1353 "yes"
1354 } else {
1355 "no"
1356 }
1357 }
1358
1359 pub fn expand_specifiers(
1379 &self,
1380 context: &crate::specifier::SpecifierContext,
1381 ) -> Option<String> {
1382 let value = self.value()?;
1383 Some(context.expand(&value))
1384 }
1385
1386 pub fn set_value(&self, new_value: &str) {
1420 let key = self.key().expect("Entry should have a key");
1421 let new_entry = Entry::new(&key, new_value);
1422
1423 let parent = self.0.parent().expect("Entry should have a parent");
1425 let index = self.0.index();
1426 parent.splice_children(index..index + 1, vec![new_entry.0.into()]);
1427 }
1428
1429 pub fn syntax(&self) -> &SyntaxNode {
1431 &self.0
1432 }
1433
1434 pub fn line(&self) -> usize {
1436 line_col_at_offset(&self.0, self.0.text_range().start()).0
1437 }
1438
1439 pub fn column(&self) -> usize {
1441 line_col_at_offset(&self.0, self.0.text_range().start()).1
1442 }
1443
1444 pub fn line_col(&self) -> (usize, usize) {
1447 line_col_at_offset(&self.0, self.0.text_range().start())
1448 }
1449}
1450
1451impl AstNode for Entry {
1452 type Language = Lang;
1453
1454 fn can_cast(kind: SyntaxKind) -> bool {
1455 kind == SyntaxKind::ENTRY
1456 }
1457
1458 fn cast(node: SyntaxNode) -> Option<Self> {
1459 if node.kind() == SyntaxKind::ENTRY {
1460 Some(Entry(node))
1461 } else {
1462 None
1463 }
1464 }
1465
1466 fn syntax(&self) -> &SyntaxNode {
1467 &self.0
1468 }
1469}
1470
1471#[cfg(test)]
1472mod tests {
1473 use super::*;
1474
1475 #[test]
1476 fn test_parse_simple() {
1477 let input = r#"[Unit]
1478Description=Test Service
1479After=network.target
1480"#;
1481 let unit = SystemdUnit::from_str(input).unwrap();
1482 assert_eq!(unit.sections().count(), 1);
1483
1484 let section = unit.sections().next().unwrap();
1485 assert_eq!(section.name(), Some("Unit".to_string()));
1486 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1487 assert_eq!(section.get("After"), Some("network.target".to_string()));
1488 }
1489
1490 #[test]
1491 fn test_parse_with_comments() {
1492 let input = r#"# Top comment
1493[Unit]
1494# Comment before description
1495Description=Test Service
1496; Semicolon comment
1497After=network.target
1498"#;
1499 let unit = SystemdUnit::from_str(input).unwrap();
1500 assert_eq!(unit.sections().count(), 1);
1501
1502 let section = unit.sections().next().unwrap();
1503 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1504 }
1505
1506 #[test]
1507 fn test_parse_multiple_sections() {
1508 let input = r#"[Unit]
1509Description=Test Service
1510
1511[Service]
1512Type=simple
1513ExecStart=/usr/bin/test
1514
1515[Install]
1516WantedBy=multi-user.target
1517"#;
1518 let unit = SystemdUnit::from_str(input).unwrap();
1519 assert_eq!(unit.sections().count(), 3);
1520
1521 let unit_section = unit.get_section("Unit").unwrap();
1522 assert_eq!(
1523 unit_section.get("Description"),
1524 Some("Test Service".to_string())
1525 );
1526
1527 let service_section = unit.get_section("Service").unwrap();
1528 assert_eq!(service_section.get("Type"), Some("simple".to_string()));
1529 assert_eq!(
1530 service_section.get("ExecStart"),
1531 Some("/usr/bin/test".to_string())
1532 );
1533
1534 let install_section = unit.get_section("Install").unwrap();
1535 assert_eq!(
1536 install_section.get("WantedBy"),
1537 Some("multi-user.target".to_string())
1538 );
1539 }
1540
1541 #[test]
1542 fn test_parse_with_spaces() {
1543 let input = "[Unit]\nDescription = Test Service\n";
1544 let unit = SystemdUnit::from_str(input).unwrap();
1545
1546 let section = unit.sections().next().unwrap();
1547 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1548 }
1549
1550 #[test]
1551 fn test_line_continuation() {
1552 let input = "[Service]\nExecStart=/bin/echo \\\n hello world\n";
1553 let unit = SystemdUnit::from_str(input).unwrap();
1554
1555 let section = unit.sections().next().unwrap();
1556 let entry = section.entries().next().unwrap();
1557 assert_eq!(entry.key(), Some("ExecStart".to_string()));
1558 assert_eq!(entry.value(), Some("/bin/echo hello world".to_string()));
1560 }
1561
1562 #[test]
1563 fn test_lossless_roundtrip() {
1564 let input = r#"# Comment
1565[Unit]
1566Description=Test Service
1567After=network.target
1568
1569[Service]
1570Type=simple
1571ExecStart=/usr/bin/test
1572"#;
1573 let unit = SystemdUnit::from_str(input).unwrap();
1574 let output = unit.text();
1575 assert_eq!(input, output);
1576 }
1577
1578 #[test]
1579 fn test_set_value() {
1580 let input = r#"[Unit]
1581Description=Test Service
1582"#;
1583 let unit = SystemdUnit::from_str(input).unwrap();
1584 {
1585 let mut section = unit.sections().next().unwrap();
1586 section.set("Description", "Updated Service");
1587 }
1588
1589 let section = unit.sections().next().unwrap();
1590 assert_eq!(
1591 section.get("Description"),
1592 Some("Updated Service".to_string())
1593 );
1594 }
1595
1596 #[test]
1597 fn test_add_new_entry() {
1598 let input = r#"[Unit]
1599Description=Test Service
1600"#;
1601 let unit = SystemdUnit::from_str(input).unwrap();
1602 {
1603 let mut section = unit.sections().next().unwrap();
1604 section.set("After", "network.target");
1605 }
1606
1607 let section = unit.sections().next().unwrap();
1608 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1609 assert_eq!(section.get("After"), Some("network.target".to_string()));
1610 }
1611
1612 #[test]
1613 fn test_multiple_values_same_key() {
1614 let input = r#"[Unit]
1615Wants=foo.service
1616Wants=bar.service
1617"#;
1618 let unit = SystemdUnit::from_str(input).unwrap();
1619 let section = unit.sections().next().unwrap();
1620
1621 assert_eq!(section.get("Wants"), Some("foo.service".to_string()));
1623
1624 let all_wants = section.get_all("Wants");
1626 assert_eq!(all_wants.len(), 2);
1627 assert_eq!(all_wants[0], "foo.service");
1628 assert_eq!(all_wants[1], "bar.service");
1629 }
1630
1631 #[test]
1632 fn test_add_multiple_entries() {
1633 let input = r#"[Unit]
1634Description=Test Service
1635"#;
1636 let unit = SystemdUnit::from_str(input).unwrap();
1637 {
1638 let mut section = unit.sections().next().unwrap();
1639 section.add("Wants", "foo.service");
1640 section.add("Wants", "bar.service");
1641 }
1642
1643 let section = unit.sections().next().unwrap();
1644 let all_wants = section.get_all("Wants");
1645 assert_eq!(all_wants.len(), 2);
1646 assert_eq!(all_wants[0], "foo.service");
1647 assert_eq!(all_wants[1], "bar.service");
1648 }
1649
1650 #[test]
1651 fn test_remove_entry() {
1652 let input = r#"[Unit]
1653Description=Test Service
1654After=network.target
1655"#;
1656 let unit = SystemdUnit::from_str(input).unwrap();
1657 {
1658 let mut section = unit.sections().next().unwrap();
1659 section.remove("After");
1660 }
1661
1662 let section = unit.sections().next().unwrap();
1663 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
1664 assert_eq!(section.get("After"), None);
1665 }
1666
1667 #[test]
1668 fn test_remove_all_entries() {
1669 let input = r#"[Unit]
1670Wants=foo.service
1671Wants=bar.service
1672Description=Test
1673"#;
1674 let unit = SystemdUnit::from_str(input).unwrap();
1675 {
1676 let mut section = unit.sections().next().unwrap();
1677 section.remove_all("Wants");
1678 }
1679
1680 let section = unit.sections().next().unwrap();
1681 assert_eq!(section.get_all("Wants").len(), 0);
1682 assert_eq!(section.get("Description"), Some("Test".to_string()));
1683 }
1684
1685 #[test]
1686 fn test_unescape_basic() {
1687 let input = r#"[Unit]
1688Description=Test\nService
1689"#;
1690 let unit = SystemdUnit::from_str(input).unwrap();
1691 let section = unit.sections().next().unwrap();
1692 let entry = section.entries().next().unwrap();
1693
1694 assert_eq!(entry.value(), Some("Test\\nService".to_string()));
1695 assert_eq!(entry.unescape_value(), Some("Test\nService".to_string()));
1696 }
1697
1698 #[test]
1699 fn test_unescape_all_escapes() {
1700 let input = r#"[Unit]
1701Value=\n\t\r\\\"\'\x41\101\u0041\U00000041
1702"#;
1703 let unit = SystemdUnit::from_str(input).unwrap();
1704 let section = unit.sections().next().unwrap();
1705 let entry = section.entries().next().unwrap();
1706
1707 let unescaped = entry.unescape_value().unwrap();
1708 assert_eq!(unescaped, "\n\t\r\\\"'AAAA");
1712 }
1713
1714 #[test]
1715 fn test_unescape_unicode() {
1716 let input = r#"[Unit]
1717Value=Hello\u0020World\U0001F44D
1718"#;
1719 let unit = SystemdUnit::from_str(input).unwrap();
1720 let section = unit.sections().next().unwrap();
1721 let entry = section.entries().next().unwrap();
1722
1723 let unescaped = entry.unescape_value().unwrap();
1724 assert_eq!(unescaped, "Hello World👍");
1726 }
1727
1728 #[test]
1729 fn test_escape_value() {
1730 let text = "Hello\nWorld\t\"Test\"\\Path";
1731 let escaped = Entry::escape_value(text);
1732 assert_eq!(escaped, "Hello\\nWorld\\t\\\"Test\\\"\\\\Path");
1733 }
1734
1735 #[test]
1736 fn test_escape_unescape_roundtrip() {
1737 let original = "Test\nwith\ttabs\rand\"quotes\"\\backslash";
1738 let escaped = Entry::escape_value(original);
1739 let unescaped = unescape_string(&escaped);
1740 assert_eq!(original, unescaped);
1741 }
1742
1743 #[test]
1744 fn test_unescape_invalid_sequences() {
1745 let input = r#"[Unit]
1747Value=\z\xFF\u12\U1234
1748"#;
1749 let unit = SystemdUnit::from_str(input).unwrap();
1750 let section = unit.sections().next().unwrap();
1751 let entry = section.entries().next().unwrap();
1752
1753 let unescaped = entry.unescape_value().unwrap();
1754 assert!(unescaped.contains("\\z"));
1756 }
1757
1758 #[test]
1759 fn test_quoted_double_quotes() {
1760 let input = r#"[Unit]
1761Description="Test Service"
1762"#;
1763 let unit = SystemdUnit::from_str(input).unwrap();
1764 let section = unit.sections().next().unwrap();
1765 let entry = section.entries().next().unwrap();
1766
1767 assert_eq!(entry.value(), Some("\"Test Service\"".to_string()));
1768 assert_eq!(entry.quoted_value(), Some("\"Test Service\"".to_string()));
1769 assert_eq!(entry.unquoted_value(), Some("Test Service".to_string()));
1770 assert_eq!(entry.is_quoted(), Some('"'));
1771 }
1772
1773 #[test]
1774 fn test_quoted_single_quotes() {
1775 let input = r#"[Unit]
1776Description='Test Service'
1777"#;
1778 let unit = SystemdUnit::from_str(input).unwrap();
1779 let section = unit.sections().next().unwrap();
1780 let entry = section.entries().next().unwrap();
1781
1782 assert_eq!(entry.value(), Some("'Test Service'".to_string()));
1783 assert_eq!(entry.unquoted_value(), Some("Test Service".to_string()));
1784 assert_eq!(entry.is_quoted(), Some('\''));
1785 }
1786
1787 #[test]
1788 fn test_quoted_with_whitespace() {
1789 let input = r#"[Unit]
1790Description=" Test Service "
1791"#;
1792 let unit = SystemdUnit::from_str(input).unwrap();
1793 let section = unit.sections().next().unwrap();
1794 let entry = section.entries().next().unwrap();
1795
1796 assert_eq!(entry.unquoted_value(), Some(" Test Service ".to_string()));
1798 }
1799
1800 #[test]
1801 fn test_unquoted_value() {
1802 let input = r#"[Unit]
1803Description=Test Service
1804"#;
1805 let unit = SystemdUnit::from_str(input).unwrap();
1806 let section = unit.sections().next().unwrap();
1807 let entry = section.entries().next().unwrap();
1808
1809 assert_eq!(entry.value(), Some("Test Service".to_string()));
1810 assert_eq!(entry.unquoted_value(), Some("Test Service".to_string()));
1811 assert_eq!(entry.is_quoted(), None);
1812 }
1813
1814 #[test]
1815 fn test_mismatched_quotes() {
1816 let input = r#"[Unit]
1817Description="Test Service'
1818"#;
1819 let unit = SystemdUnit::from_str(input).unwrap();
1820 let section = unit.sections().next().unwrap();
1821 let entry = section.entries().next().unwrap();
1822
1823 assert_eq!(entry.is_quoted(), None);
1825 assert_eq!(entry.unquoted_value(), Some("\"Test Service'".to_string()));
1826 }
1827
1828 #[test]
1829 fn test_empty_quotes() {
1830 let input = r#"[Unit]
1831Description=""
1832"#;
1833 let unit = SystemdUnit::from_str(input).unwrap();
1834 let section = unit.sections().next().unwrap();
1835 let entry = section.entries().next().unwrap();
1836
1837 assert_eq!(entry.is_quoted(), Some('"'));
1838 assert_eq!(entry.unquoted_value(), Some("".to_string()));
1839 }
1840
1841 #[test]
1842 fn test_value_as_list() {
1843 let input = r#"[Unit]
1844After=network.target remote-fs.target
1845"#;
1846 let unit = SystemdUnit::from_str(input).unwrap();
1847 let section = unit.sections().next().unwrap();
1848 let entry = section.entries().next().unwrap();
1849
1850 let list = entry.value_as_list();
1851 assert_eq!(list.len(), 2);
1852 assert_eq!(list[0], "network.target");
1853 assert_eq!(list[1], "remote-fs.target");
1854 }
1855
1856 #[test]
1857 fn test_value_as_list_single() {
1858 let input = r#"[Unit]
1859After=network.target
1860"#;
1861 let unit = SystemdUnit::from_str(input).unwrap();
1862 let section = unit.sections().next().unwrap();
1863 let entry = section.entries().next().unwrap();
1864
1865 let list = entry.value_as_list();
1866 assert_eq!(list.len(), 1);
1867 assert_eq!(list[0], "network.target");
1868 }
1869
1870 #[test]
1871 fn test_value_as_list_empty() {
1872 let input = r#"[Unit]
1873After=
1874"#;
1875 let unit = SystemdUnit::from_str(input).unwrap();
1876 let section = unit.sections().next().unwrap();
1877 let entry = section.entries().next().unwrap();
1878
1879 let list = entry.value_as_list();
1880 assert_eq!(list.len(), 0);
1881 }
1882
1883 #[test]
1884 fn test_value_as_list_with_extra_whitespace() {
1885 let input = r#"[Unit]
1886After= network.target remote-fs.target
1887"#;
1888 let unit = SystemdUnit::from_str(input).unwrap();
1889 let section = unit.sections().next().unwrap();
1890 let entry = section.entries().next().unwrap();
1891
1892 let list = entry.value_as_list();
1893 assert_eq!(list.len(), 2);
1894 assert_eq!(list[0], "network.target");
1895 assert_eq!(list[1], "remote-fs.target");
1896 }
1897
1898 #[test]
1899 fn test_section_get_list() {
1900 let input = r#"[Unit]
1901After=network.target remote-fs.target
1902"#;
1903 let unit = SystemdUnit::from_str(input).unwrap();
1904 let section = unit.sections().next().unwrap();
1905
1906 let list = section.get_list("After");
1907 assert_eq!(list.len(), 2);
1908 assert_eq!(list[0], "network.target");
1909 assert_eq!(list[1], "remote-fs.target");
1910 }
1911
1912 #[test]
1913 fn test_section_get_list_missing() {
1914 let input = r#"[Unit]
1915Description=Test
1916"#;
1917 let unit = SystemdUnit::from_str(input).unwrap();
1918 let section = unit.sections().next().unwrap();
1919
1920 let list = section.get_list("After");
1921 assert_eq!(list.len(), 0);
1922 }
1923
1924 #[test]
1925 fn test_section_set_list() {
1926 let input = r#"[Unit]
1927Description=Test
1928"#;
1929 let unit = SystemdUnit::from_str(input).unwrap();
1930 {
1931 let mut section = unit.sections().next().unwrap();
1932 section.set_list("After", &["network.target", "remote-fs.target"]);
1933 }
1934
1935 let section = unit.sections().next().unwrap();
1936 let list = section.get_list("After");
1937 assert_eq!(list.len(), 2);
1938 assert_eq!(list[0], "network.target");
1939 assert_eq!(list[1], "remote-fs.target");
1940 }
1941
1942 #[test]
1943 fn test_section_set_list_replaces() {
1944 let input = r#"[Unit]
1945After=foo.target
1946"#;
1947 let unit = SystemdUnit::from_str(input).unwrap();
1948 {
1949 let mut section = unit.sections().next().unwrap();
1950 section.set_list("After", &["network.target", "remote-fs.target"]);
1951 }
1952
1953 let section = unit.sections().next().unwrap();
1954 let list = section.get_list("After");
1955 assert_eq!(list.len(), 2);
1956 assert_eq!(list[0], "network.target");
1957 assert_eq!(list[1], "remote-fs.target");
1958 }
1959
1960 #[test]
1961 fn test_value_as_bool_positive() {
1962 let inputs = vec!["yes", "true", "1", "on", "YES", "True", "ON"];
1963
1964 for input_val in inputs {
1965 let input = format!("[Service]\nRemainAfterExit={}\n", input_val);
1966 let unit = SystemdUnit::from_str(&input).unwrap();
1967 let section = unit.sections().next().unwrap();
1968 let entry = section.entries().next().unwrap();
1969 assert_eq!(
1970 entry.value_as_bool(),
1971 Some(true),
1972 "Failed for input: {}",
1973 input_val
1974 );
1975 }
1976 }
1977
1978 #[test]
1979 fn test_value_as_bool_negative() {
1980 let inputs = vec!["no", "false", "0", "off", "NO", "False", "OFF"];
1981
1982 for input_val in inputs {
1983 let input = format!("[Service]\nRemainAfterExit={}\n", input_val);
1984 let unit = SystemdUnit::from_str(&input).unwrap();
1985 let section = unit.sections().next().unwrap();
1986 let entry = section.entries().next().unwrap();
1987 assert_eq!(
1988 entry.value_as_bool(),
1989 Some(false),
1990 "Failed for input: {}",
1991 input_val
1992 );
1993 }
1994 }
1995
1996 #[test]
1997 fn test_value_as_bool_invalid() {
1998 let input = r#"[Service]
1999RemainAfterExit=maybe
2000"#;
2001 let unit = SystemdUnit::from_str(input).unwrap();
2002 let section = unit.sections().next().unwrap();
2003 let entry = section.entries().next().unwrap();
2004 assert_eq!(entry.value_as_bool(), None);
2005 }
2006
2007 #[test]
2008 fn test_value_as_bool_with_whitespace() {
2009 let input = r#"[Service]
2010RemainAfterExit= yes
2011"#;
2012 let unit = SystemdUnit::from_str(input).unwrap();
2013 let section = unit.sections().next().unwrap();
2014 let entry = section.entries().next().unwrap();
2015 assert_eq!(entry.value_as_bool(), Some(true));
2016 }
2017
2018 #[test]
2019 fn test_format_bool() {
2020 assert_eq!(Entry::format_bool(true), "yes");
2021 assert_eq!(Entry::format_bool(false), "no");
2022 }
2023
2024 #[test]
2025 fn test_section_get_bool() {
2026 let input = r#"[Service]
2027RemainAfterExit=yes
2028Type=simple
2029"#;
2030 let unit = SystemdUnit::from_str(input).unwrap();
2031 let section = unit.sections().next().unwrap();
2032
2033 assert_eq!(section.get_bool("RemainAfterExit"), Some(true));
2034 assert_eq!(section.get_bool("Type"), None); assert_eq!(section.get_bool("Missing"), None); }
2037
2038 #[test]
2039 fn test_section_set_bool() {
2040 let input = r#"[Service]
2041Type=simple
2042"#;
2043 let unit = SystemdUnit::from_str(input).unwrap();
2044 {
2045 let mut section = unit.sections().next().unwrap();
2046 section.set_bool("RemainAfterExit", true);
2047 section.set_bool("PrivateTmp", false);
2048 }
2049
2050 let section = unit.sections().next().unwrap();
2051 assert_eq!(section.get("RemainAfterExit"), Some("yes".to_string()));
2052 assert_eq!(section.get("PrivateTmp"), Some("no".to_string()));
2053 assert_eq!(section.get_bool("RemainAfterExit"), Some(true));
2054 assert_eq!(section.get_bool("PrivateTmp"), Some(false));
2055 }
2056
2057 #[test]
2058 fn test_add_entry_with_trailing_whitespace() {
2059 let input = r#"[Unit]
2061Description=Test Service
2062
2063"#;
2064 let unit = SystemdUnit::from_str(input).unwrap();
2065 {
2066 let mut section = unit.sections().next().unwrap();
2067 section.add("After", "network.target");
2068 }
2069
2070 let output = unit.text();
2071 let expected = r#"[Unit]
2073Description=Test Service
2074After=network.target
2075
2076"#;
2077 assert_eq!(output, expected);
2078 }
2079
2080 #[test]
2081 fn test_set_new_entry_with_trailing_whitespace() {
2082 let input = r#"[Unit]
2084Description=Test Service
2085
2086"#;
2087 let unit = SystemdUnit::from_str(input).unwrap();
2088 {
2089 let mut section = unit.sections().next().unwrap();
2090 section.set("After", "network.target");
2091 }
2092
2093 let output = unit.text();
2094 let expected = r#"[Unit]
2096Description=Test Service
2097After=network.target
2098
2099"#;
2100 assert_eq!(output, expected);
2101 }
2102
2103 #[test]
2104 fn test_remove_value_from_space_separated_list() {
2105 let input = r#"[Unit]
2106After=network.target syslog.target remote-fs.target
2107"#;
2108 let unit = SystemdUnit::from_str(input).unwrap();
2109 {
2110 let mut section = unit.sections().next().unwrap();
2111 section.remove_value("After", "syslog.target");
2112 }
2113
2114 let section = unit.sections().next().unwrap();
2115 assert_eq!(
2116 section.get("After"),
2117 Some("network.target remote-fs.target".to_string())
2118 );
2119 }
2120
2121 #[test]
2122 fn test_remove_value_removes_entire_entry() {
2123 let input = r#"[Unit]
2124After=syslog.target
2125Description=Test
2126"#;
2127 let unit = SystemdUnit::from_str(input).unwrap();
2128 {
2129 let mut section = unit.sections().next().unwrap();
2130 section.remove_value("After", "syslog.target");
2131 }
2132
2133 let section = unit.sections().next().unwrap();
2134 assert_eq!(section.get("After"), None);
2135 assert_eq!(section.get("Description"), Some("Test".to_string()));
2136 }
2137
2138 #[test]
2139 fn test_remove_value_from_multiple_entries() {
2140 let input = r#"[Unit]
2141After=network.target syslog.target
2142After=remote-fs.target
2143After=syslog.target multi-user.target
2144"#;
2145 let unit = SystemdUnit::from_str(input).unwrap();
2146 {
2147 let mut section = unit.sections().next().unwrap();
2148 section.remove_value("After", "syslog.target");
2149 }
2150
2151 let section = unit.sections().next().unwrap();
2152 let all_after = section.get_all("After");
2153 assert_eq!(all_after.len(), 3);
2154 assert_eq!(all_after[0], "network.target");
2155 assert_eq!(all_after[1], "remote-fs.target");
2156 assert_eq!(all_after[2], "multi-user.target");
2157 }
2158
2159 #[test]
2160 fn test_remove_value_not_found() {
2161 let input = r#"[Unit]
2162After=network.target remote-fs.target
2163"#;
2164 let unit = SystemdUnit::from_str(input).unwrap();
2165 {
2166 let mut section = unit.sections().next().unwrap();
2167 section.remove_value("After", "nonexistent.target");
2168 }
2169
2170 let section = unit.sections().next().unwrap();
2171 assert_eq!(
2173 section.get("After"),
2174 Some("network.target remote-fs.target".to_string())
2175 );
2176 }
2177
2178 #[test]
2179 fn test_remove_value_preserves_order() {
2180 let input = r#"[Unit]
2181Description=Test Service
2182After=network.target syslog.target
2183Wants=foo.service
2184After=remote-fs.target
2185Requires=bar.service
2186"#;
2187 let unit = SystemdUnit::from_str(input).unwrap();
2188 {
2189 let mut section = unit.sections().next().unwrap();
2190 section.remove_value("After", "syslog.target");
2191 }
2192
2193 let section = unit.sections().next().unwrap();
2194 let entries: Vec<_> = section.entries().collect();
2195
2196 assert_eq!(entries[0].key(), Some("Description".to_string()));
2198 assert_eq!(entries[1].key(), Some("After".to_string()));
2199 assert_eq!(entries[1].value(), Some("network.target".to_string()));
2200 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2201 assert_eq!(entries[3].key(), Some("After".to_string()));
2202 assert_eq!(entries[3].value(), Some("remote-fs.target".to_string()));
2203 assert_eq!(entries[4].key(), Some("Requires".to_string()));
2204 }
2205
2206 #[test]
2207 fn test_remove_value_key_not_found() {
2208 let input = r#"[Unit]
2209Description=Test Service
2210"#;
2211 let unit = SystemdUnit::from_str(input).unwrap();
2212 {
2213 let mut section = unit.sections().next().unwrap();
2214 section.remove_value("After", "network.target");
2215 }
2216
2217 let section = unit.sections().next().unwrap();
2219 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
2220 assert_eq!(section.get("After"), None);
2221 }
2222
2223 #[test]
2224 fn test_entry_set_value_basic() {
2225 let input = r#"[Unit]
2226After=network.target
2227Description=Test
2228"#;
2229 let unit = SystemdUnit::from_str(input).unwrap();
2230 let section = unit.get_section("Unit").unwrap();
2231
2232 for entry in section.entries() {
2233 if entry.key().as_deref() == Some("After") {
2234 entry.set_value("remote-fs.target");
2235 }
2236 }
2237
2238 let section = unit.get_section("Unit").unwrap();
2239 assert_eq!(section.get("After"), Some("remote-fs.target".to_string()));
2240 assert_eq!(section.get("Description"), Some("Test".to_string()));
2241 }
2242
2243 #[test]
2244 fn test_entry_set_value_preserves_order() {
2245 let input = r#"[Unit]
2246Description=Test Service
2247After=network.target syslog.target
2248Wants=foo.service
2249After=remote-fs.target
2250Requires=bar.service
2251"#;
2252 let unit = SystemdUnit::from_str(input).unwrap();
2253 let section = unit.get_section("Unit").unwrap();
2254
2255 for entry in section.entries() {
2256 if entry.key().as_deref() == Some("After") {
2257 let values = entry.value_as_list();
2258 let filtered: Vec<_> = values
2259 .iter()
2260 .filter(|v| v.as_str() != "syslog.target")
2261 .map(|s| s.as_str())
2262 .collect();
2263 if !filtered.is_empty() {
2264 entry.set_value(&filtered.join(" "));
2265 }
2266 }
2267 }
2268
2269 let section = unit.get_section("Unit").unwrap();
2270 let entries: Vec<_> = section.entries().collect();
2271
2272 assert_eq!(entries[0].key(), Some("Description".to_string()));
2274 assert_eq!(entries[1].key(), Some("After".to_string()));
2275 assert_eq!(entries[1].value(), Some("network.target".to_string()));
2276 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2277 assert_eq!(entries[3].key(), Some("After".to_string()));
2278 assert_eq!(entries[3].value(), Some("remote-fs.target".to_string()));
2279 assert_eq!(entries[4].key(), Some("Requires".to_string()));
2280 }
2281
2282 #[test]
2283 fn test_entry_set_value_multiple_entries() {
2284 let input = r#"[Unit]
2285After=network.target
2286After=syslog.target
2287After=remote-fs.target
2288"#;
2289 let unit = SystemdUnit::from_str(input).unwrap();
2290 let section = unit.get_section("Unit").unwrap();
2291
2292 let entries_to_modify: Vec<_> = section
2294 .entries()
2295 .filter(|e| e.key().as_deref() == Some("After"))
2296 .collect();
2297
2298 for entry in entries_to_modify {
2300 let old_value = entry.value().unwrap();
2301 entry.set_value(&format!("{} multi-user.target", old_value));
2302 }
2303
2304 let section = unit.get_section("Unit").unwrap();
2305 let all_after = section.get_all("After");
2306 assert_eq!(all_after.len(), 3);
2307 assert_eq!(all_after[0], "network.target multi-user.target");
2308 assert_eq!(all_after[1], "syslog.target multi-user.target");
2309 assert_eq!(all_after[2], "remote-fs.target multi-user.target");
2310 }
2311
2312 #[test]
2313 fn test_entry_set_value_with_empty_string() {
2314 let input = r#"[Unit]
2315After=network.target
2316"#;
2317 let unit = SystemdUnit::from_str(input).unwrap();
2318 let section = unit.get_section("Unit").unwrap();
2319
2320 for entry in section.entries() {
2321 if entry.key().as_deref() == Some("After") {
2322 entry.set_value("");
2323 }
2324 }
2325
2326 let section = unit.get_section("Unit").unwrap();
2327 assert_eq!(section.get("After"), Some("".to_string()));
2328 }
2329
2330 #[test]
2331 fn test_remove_entries_where_basic() {
2332 let input = r#"[Unit]
2333After=network.target
2334Wants=foo.service
2335After=syslog.target
2336"#;
2337 let unit = SystemdUnit::from_str(input).unwrap();
2338 {
2339 let mut section = unit.sections().next().unwrap();
2340 section.remove_entries_where(|key, _value| key == "After");
2341 }
2342
2343 let section = unit.sections().next().unwrap();
2344 assert_eq!(section.get_all("After").len(), 0);
2345 assert_eq!(section.get("Wants"), Some("foo.service".to_string()));
2346 }
2347
2348 #[test]
2349 fn test_remove_entries_where_with_value_check() {
2350 let input = r#"[Unit]
2351After=network.target syslog.target
2352Wants=foo.service
2353After=remote-fs.target
2354"#;
2355 let unit = SystemdUnit::from_str(input).unwrap();
2356 {
2357 let mut section = unit.sections().next().unwrap();
2358 section.remove_entries_where(|key, value| {
2359 key == "After" && value.split_whitespace().any(|v| v == "syslog.target")
2360 });
2361 }
2362
2363 let section = unit.sections().next().unwrap();
2364 let all_after = section.get_all("After");
2365 assert_eq!(all_after.len(), 1);
2366 assert_eq!(all_after[0], "remote-fs.target");
2367 assert_eq!(section.get("Wants"), Some("foo.service".to_string()));
2368 }
2369
2370 #[test]
2371 fn test_remove_entries_where_preserves_order() {
2372 let input = r#"[Unit]
2373Description=Test Service
2374After=network.target
2375Wants=foo.service
2376After=syslog.target
2377Requires=bar.service
2378After=remote-fs.target
2379"#;
2380 let unit = SystemdUnit::from_str(input).unwrap();
2381 {
2382 let mut section = unit.sections().next().unwrap();
2383 section.remove_entries_where(|key, value| key == "After" && value.contains("syslog"));
2384 }
2385
2386 let section = unit.sections().next().unwrap();
2387 let entries: Vec<_> = section.entries().collect();
2388
2389 assert_eq!(entries.len(), 5);
2390 assert_eq!(entries[0].key(), Some("Description".to_string()));
2391 assert_eq!(entries[1].key(), Some("After".to_string()));
2392 assert_eq!(entries[1].value(), Some("network.target".to_string()));
2393 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2394 assert_eq!(entries[3].key(), Some("Requires".to_string()));
2395 assert_eq!(entries[4].key(), Some("After".to_string()));
2396 assert_eq!(entries[4].value(), Some("remote-fs.target".to_string()));
2397 }
2398
2399 #[test]
2400 fn test_remove_entries_where_no_matches() {
2401 let input = r#"[Unit]
2402After=network.target
2403Wants=foo.service
2404"#;
2405 let unit = SystemdUnit::from_str(input).unwrap();
2406 {
2407 let mut section = unit.sections().next().unwrap();
2408 section.remove_entries_where(|key, _value| key == "Requires");
2409 }
2410
2411 let section = unit.sections().next().unwrap();
2412 assert_eq!(section.get("After"), Some("network.target".to_string()));
2413 assert_eq!(section.get("Wants"), Some("foo.service".to_string()));
2414 }
2415
2416 #[test]
2417 fn test_remove_entries_where_all_entries() {
2418 let input = r#"[Unit]
2419After=network.target
2420Wants=foo.service
2421Requires=bar.service
2422"#;
2423 let unit = SystemdUnit::from_str(input).unwrap();
2424 {
2425 let mut section = unit.sections().next().unwrap();
2426 section.remove_entries_where(|_key, _value| true);
2427 }
2428
2429 let section = unit.sections().next().unwrap();
2430 assert_eq!(section.entries().count(), 0);
2431 }
2432
2433 #[test]
2434 fn test_remove_entries_where_complex_predicate() {
2435 let input = r#"[Unit]
2436After=network.target
2437After=syslog.target remote-fs.target
2438Wants=foo.service
2439After=multi-user.target
2440Requires=bar.service
2441"#;
2442 let unit = SystemdUnit::from_str(input).unwrap();
2443 {
2444 let mut section = unit.sections().next().unwrap();
2445 section.remove_entries_where(|key, value| {
2447 key == "After" && value.split_whitespace().count() > 1
2448 });
2449 }
2450
2451 let section = unit.sections().next().unwrap();
2452 let all_after = section.get_all("After");
2453 assert_eq!(all_after.len(), 2);
2454 assert_eq!(all_after[0], "network.target");
2455 assert_eq!(all_after[1], "multi-user.target");
2456 }
2457
2458 #[test]
2459 fn test_insert_at_beginning() {
2460 let input = r#"[Unit]
2461Description=Test Service
2462After=network.target
2463"#;
2464 let unit = SystemdUnit::from_str(input).unwrap();
2465 {
2466 let mut section = unit.sections().next().unwrap();
2467 section.insert_at(0, "Wants", "foo.service");
2468 }
2469
2470 let section = unit.sections().next().unwrap();
2471 let entries: Vec<_> = section.entries().collect();
2472 assert_eq!(entries.len(), 3);
2473 assert_eq!(entries[0].key(), Some("Wants".to_string()));
2474 assert_eq!(entries[0].value(), Some("foo.service".to_string()));
2475 assert_eq!(entries[1].key(), Some("Description".to_string()));
2476 assert_eq!(entries[2].key(), Some("After".to_string()));
2477 }
2478
2479 #[test]
2480 fn test_insert_at_middle() {
2481 let input = r#"[Unit]
2482Description=Test Service
2483After=network.target
2484"#;
2485 let unit = SystemdUnit::from_str(input).unwrap();
2486 {
2487 let mut section = unit.sections().next().unwrap();
2488 section.insert_at(1, "Wants", "foo.service");
2489 }
2490
2491 let section = unit.sections().next().unwrap();
2492 let entries: Vec<_> = section.entries().collect();
2493 assert_eq!(entries.len(), 3);
2494 assert_eq!(entries[0].key(), Some("Description".to_string()));
2495 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2496 assert_eq!(entries[1].value(), Some("foo.service".to_string()));
2497 assert_eq!(entries[2].key(), Some("After".to_string()));
2498 }
2499
2500 #[test]
2501 fn test_insert_at_end() {
2502 let input = r#"[Unit]
2503Description=Test Service
2504After=network.target
2505"#;
2506 let unit = SystemdUnit::from_str(input).unwrap();
2507 {
2508 let mut section = unit.sections().next().unwrap();
2509 section.insert_at(2, "Wants", "foo.service");
2510 }
2511
2512 let section = unit.sections().next().unwrap();
2513 let entries: Vec<_> = section.entries().collect();
2514 assert_eq!(entries.len(), 3);
2515 assert_eq!(entries[0].key(), Some("Description".to_string()));
2516 assert_eq!(entries[1].key(), Some("After".to_string()));
2517 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2518 assert_eq!(entries[2].value(), Some("foo.service".to_string()));
2519 }
2520
2521 #[test]
2522 fn test_insert_at_beyond_end() {
2523 let input = r#"[Unit]
2524Description=Test Service
2525"#;
2526 let unit = SystemdUnit::from_str(input).unwrap();
2527 {
2528 let mut section = unit.sections().next().unwrap();
2529 section.insert_at(100, "Wants", "foo.service");
2530 }
2531
2532 let section = unit.sections().next().unwrap();
2533 let entries: Vec<_> = section.entries().collect();
2534 assert_eq!(entries.len(), 2);
2535 assert_eq!(entries[0].key(), Some("Description".to_string()));
2536 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2537 assert_eq!(entries[1].value(), Some("foo.service".to_string()));
2538 }
2539
2540 #[test]
2541 fn test_insert_at_empty_section() {
2542 let input = r#"[Unit]
2543"#;
2544 let unit = SystemdUnit::from_str(input).unwrap();
2545 {
2546 let mut section = unit.sections().next().unwrap();
2547 section.insert_at(0, "Description", "Test Service");
2548 }
2549
2550 let section = unit.sections().next().unwrap();
2551 assert_eq!(section.get("Description"), Some("Test Service".to_string()));
2552 }
2553
2554 #[test]
2555 fn test_insert_before_basic() {
2556 let input = r#"[Unit]
2557Description=Test Service
2558After=network.target
2559"#;
2560 let unit = SystemdUnit::from_str(input).unwrap();
2561 {
2562 let mut section = unit.sections().next().unwrap();
2563 section.insert_before("After", "Wants", "foo.service");
2564 }
2565
2566 let section = unit.sections().next().unwrap();
2567 let entries: Vec<_> = section.entries().collect();
2568 assert_eq!(entries.len(), 3);
2569 assert_eq!(entries[0].key(), Some("Description".to_string()));
2570 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2571 assert_eq!(entries[1].value(), Some("foo.service".to_string()));
2572 assert_eq!(entries[2].key(), Some("After".to_string()));
2573 }
2574
2575 #[test]
2576 fn test_insert_before_first_entry() {
2577 let input = r#"[Unit]
2578Description=Test Service
2579After=network.target
2580"#;
2581 let unit = SystemdUnit::from_str(input).unwrap();
2582 {
2583 let mut section = unit.sections().next().unwrap();
2584 section.insert_before("Description", "Wants", "foo.service");
2585 }
2586
2587 let section = unit.sections().next().unwrap();
2588 let entries: Vec<_> = section.entries().collect();
2589 assert_eq!(entries.len(), 3);
2590 assert_eq!(entries[0].key(), Some("Wants".to_string()));
2591 assert_eq!(entries[0].value(), Some("foo.service".to_string()));
2592 assert_eq!(entries[1].key(), Some("Description".to_string()));
2593 assert_eq!(entries[2].key(), Some("After".to_string()));
2594 }
2595
2596 #[test]
2597 fn test_insert_before_nonexistent_key() {
2598 let input = r#"[Unit]
2599Description=Test Service
2600"#;
2601 let unit = SystemdUnit::from_str(input).unwrap();
2602 {
2603 let mut section = unit.sections().next().unwrap();
2604 section.insert_before("After", "Wants", "foo.service");
2605 }
2606
2607 let section = unit.sections().next().unwrap();
2608 let entries: Vec<_> = section.entries().collect();
2609 assert_eq!(entries.len(), 1);
2610 assert_eq!(entries[0].key(), Some("Description".to_string()));
2611 }
2612
2613 #[test]
2614 fn test_insert_before_multiple_occurrences() {
2615 let input = r#"[Unit]
2616After=network.target
2617After=syslog.target
2618"#;
2619 let unit = SystemdUnit::from_str(input).unwrap();
2620 {
2621 let mut section = unit.sections().next().unwrap();
2622 section.insert_before("After", "Wants", "foo.service");
2623 }
2624
2625 let section = unit.sections().next().unwrap();
2626 let entries: Vec<_> = section.entries().collect();
2627 assert_eq!(entries.len(), 3);
2628 assert_eq!(entries[0].key(), Some("Wants".to_string()));
2629 assert_eq!(entries[1].key(), Some("After".to_string()));
2630 assert_eq!(entries[1].value(), Some("network.target".to_string()));
2631 assert_eq!(entries[2].key(), Some("After".to_string()));
2632 assert_eq!(entries[2].value(), Some("syslog.target".to_string()));
2633 }
2634
2635 #[test]
2636 fn test_insert_after_basic() {
2637 let input = r#"[Unit]
2638Description=Test Service
2639After=network.target
2640"#;
2641 let unit = SystemdUnit::from_str(input).unwrap();
2642 {
2643 let mut section = unit.sections().next().unwrap();
2644 section.insert_after("Description", "Wants", "foo.service");
2645 }
2646
2647 let section = unit.sections().next().unwrap();
2648 let entries: Vec<_> = section.entries().collect();
2649 assert_eq!(entries.len(), 3);
2650 assert_eq!(entries[0].key(), Some("Description".to_string()));
2651 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2652 assert_eq!(entries[1].value(), Some("foo.service".to_string()));
2653 assert_eq!(entries[2].key(), Some("After".to_string()));
2654 }
2655
2656 #[test]
2657 fn test_insert_after_last_entry() {
2658 let input = r#"[Unit]
2659Description=Test Service
2660After=network.target
2661"#;
2662 let unit = SystemdUnit::from_str(input).unwrap();
2663 {
2664 let mut section = unit.sections().next().unwrap();
2665 section.insert_after("After", "Wants", "foo.service");
2666 }
2667
2668 let section = unit.sections().next().unwrap();
2669 let entries: Vec<_> = section.entries().collect();
2670 assert_eq!(entries.len(), 3);
2671 assert_eq!(entries[0].key(), Some("Description".to_string()));
2672 assert_eq!(entries[1].key(), Some("After".to_string()));
2673 assert_eq!(entries[2].key(), Some("Wants".to_string()));
2674 assert_eq!(entries[2].value(), Some("foo.service".to_string()));
2675 }
2676
2677 #[test]
2678 fn test_insert_after_nonexistent_key() {
2679 let input = r#"[Unit]
2680Description=Test Service
2681"#;
2682 let unit = SystemdUnit::from_str(input).unwrap();
2683 {
2684 let mut section = unit.sections().next().unwrap();
2685 section.insert_after("After", "Wants", "foo.service");
2686 }
2687
2688 let section = unit.sections().next().unwrap();
2689 let entries: Vec<_> = section.entries().collect();
2690 assert_eq!(entries.len(), 1);
2691 assert_eq!(entries[0].key(), Some("Description".to_string()));
2692 }
2693
2694 #[test]
2695 fn test_insert_after_multiple_occurrences() {
2696 let input = r#"[Unit]
2697After=network.target
2698After=syslog.target
2699"#;
2700 let unit = SystemdUnit::from_str(input).unwrap();
2701 {
2702 let mut section = unit.sections().next().unwrap();
2703 section.insert_after("After", "Wants", "foo.service");
2704 }
2705
2706 let section = unit.sections().next().unwrap();
2707 let entries: Vec<_> = section.entries().collect();
2708 assert_eq!(entries.len(), 3);
2709 assert_eq!(entries[0].key(), Some("After".to_string()));
2710 assert_eq!(entries[0].value(), Some("network.target".to_string()));
2711 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2712 assert_eq!(entries[2].key(), Some("After".to_string()));
2713 assert_eq!(entries[2].value(), Some("syslog.target".to_string()));
2714 }
2715
2716 #[test]
2717 fn test_insert_preserves_whitespace() {
2718 let input = r#"[Unit]
2719Description=Test Service
2720
2721After=network.target
2722"#;
2723 let unit = SystemdUnit::from_str(input).unwrap();
2724 {
2725 let mut section = unit.sections().next().unwrap();
2726 section.insert_at(1, "Wants", "foo.service");
2727 }
2728
2729 let section = unit.sections().next().unwrap();
2730 let entries: Vec<_> = section.entries().collect();
2731 assert_eq!(entries.len(), 3);
2732 assert_eq!(entries[0].key(), Some("Description".to_string()));
2733 assert_eq!(entries[1].key(), Some("Wants".to_string()));
2734 assert_eq!(entries[2].key(), Some("After".to_string()));
2735
2736 let expected = r#"[Unit]
2737Description=Test Service
2738
2739Wants=foo.service
2740After=network.target
2741"#;
2742 assert_eq!(unit.text(), expected);
2743 }
2744
2745 #[test]
2746 fn test_line_col() {
2747 let text = r#"[Unit]
2748Description=Test Service
2749After=network.target
2750
2751[Service]
2752Type=simple
2753ExecStart=/usr/bin/test
2754Environment="FOO=bar"
2755"#;
2756 let unit = SystemdUnit::from_str(text).unwrap();
2757
2758 assert_eq!(unit.line(), 0);
2760 assert_eq!(unit.column(), 0);
2761 assert_eq!(unit.line_col(), (0, 0));
2762
2763 let sections: Vec<_> = unit.sections().collect();
2765 assert_eq!(sections.len(), 2);
2766
2767 assert_eq!(sections[0].line(), 0);
2769 assert_eq!(sections[0].column(), 0);
2770 assert_eq!(sections[0].line_col(), (0, 0));
2771
2772 assert_eq!(sections[1].line(), 4);
2774 assert_eq!(sections[1].column(), 0);
2775 assert_eq!(sections[1].line_col(), (4, 0));
2776
2777 let unit_entries: Vec<_> = sections[0].entries().collect();
2779 assert_eq!(unit_entries.len(), 2);
2780 assert_eq!(unit_entries[0].line(), 1); assert_eq!(unit_entries[0].column(), 0); assert_eq!(unit_entries[1].line(), 2); assert_eq!(unit_entries[1].column(), 0); let service_entries: Vec<_> = sections[1].entries().collect();
2786 assert_eq!(service_entries.len(), 3);
2787 assert_eq!(service_entries[0].line(), 5); assert_eq!(service_entries[0].column(), 0); assert_eq!(service_entries[1].line(), 6); assert_eq!(service_entries[1].column(), 0); assert_eq!(service_entries[2].line(), 7); assert_eq!(service_entries[2].column(), 0); assert_eq!(unit_entries[0].line_col(), (1, 0));
2796 assert_eq!(service_entries[2].line_col(), (7, 0));
2797 }
2798
2799 #[test]
2800 fn test_line_col_multiline() {
2801 let text = r#"[Unit]
2803Description=A long \
2804value that spans \
2805multiple lines
2806After=network.target
2807"#;
2808 let unit = SystemdUnit::from_str(text).unwrap();
2809 let section = unit.sections().next().unwrap();
2810 let entries: Vec<_> = section.entries().collect();
2811
2812 assert_eq!(entries.len(), 2);
2813 assert_eq!(entries[0].line(), 1);
2815 assert_eq!(entries[0].column(), 0);
2816
2817 assert_eq!(entries[1].line(), 4);
2819 assert_eq!(entries[1].column(), 0);
2820 }
2821}