chrony-confile 0.1.0

A full-featured Rust library for parsing, editing, validating, and serializing chrony configuration files
Documentation
//! `.sources` file parser implementation.
//!
//! Chrony writes `.sources` files (e.g. `/var/lib/chrony/.sources`) to persist known NTP
//! sources. Unlike the main config parser, this parser is always lenient: it silently skips
//! unparseable lines and unknown directive names.

use crate::ast::*;
use crate::parser::lexer;

impl SourceFile {
    /// Parse a `.sources` file in lenient mode.
    ///
    /// Unlike [`ChronyConfig::parse`], this parser is always lenient: unparseable lines
    /// and unknown directive names are silently skipped. Only `server`, `pool`, and `peer`
    /// directives are recognized; all other lines are ignored.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use chrony_confile::SourceFile;
    ///
    /// let sources = SourceFile::parse("\
    /// server ntp1.example.com iburst
    /// pool pool.ntp.org maxsources 6
    /// unknown_directive
    /// ");
    ///
    /// // The unknown directive is silently skipped
    /// assert_eq!(sources.nodes.len(), 2);
    /// ```
    pub fn parse(input: &str) -> Self {
        let mut nodes = Vec::new();
        let mut leading_comments: Vec<String> = Vec::new();

        for raw_line in input.lines() {
            match lexer::normalize_line(raw_line) {
                lexer::LineType::Blank => {
                    for c in leading_comments.drain(..) {
                        nodes.push(SourceNode::Comment(c));
                    }
                    nodes.push(SourceNode::BlankLine);
                }
                lexer::LineType::Comment(c) => leading_comments.push(c),
                lexer::LineType::Directive { command, args } => {
                    let span = crate::span::Span::new(None, 0, 0, 0);
                    let result = match command.to_lowercase().as_str() {
                        "server" => {
                            crate::parser::grammar::parse_source_options(
                                &args, span.clone(), "server",
                            )
                            .ok()
                        }
                        "pool" => {
                            crate::parser::grammar::parse_source_options(
                                &args, span.clone(), "pool",
                            )
                            .ok()
                        }
                        "peer" => {
                            crate::parser::grammar::parse_source_options(
                                &args, span.clone(), "peer",
                            )
                            .ok()
                        }
                        _ => None,
                    };
                    if let Some(kind) = result {
                        match kind {
                            DirectiveKind::Server(config) => {
                                nodes.push(SourceNode::Entry(SourceEntry::Server(config)))
                            }
                            DirectiveKind::Pool(config) => {
                                nodes.push(SourceNode::Entry(SourceEntry::Pool(config)))
                            }
                            DirectiveKind::Peer(config) => {
                                nodes.push(SourceNode::Entry(SourceEntry::Peer(config)))
                            }
                            _ => {}
                        }
                    }
                    leading_comments.clear();
                }
            }
        }
        for c in leading_comments {
            nodes.push(SourceNode::Comment(c));
        }
        Self { nodes }
    }
}