Skip to main content

ephemeris_files/
state.rs

1use std::collections::BTreeMap;
2
3use ephemeris_documents::DocumentSpec;
4use facet::Facet;
5
6use crate::path::RepositoryPath;
7use crate::path_policy::PathRuleState;
8use crate::{FileMode, FilesError, FormatterError};
9
10/// File-management input projected from evaluated configuration.
11#[derive(Debug, Facet)]
12#[facet(deny_unknown_fields)]
13pub struct FileState {
14    /// Priority-resolved managed, source and ignore rules for repository paths.
15    pub(crate) path_rules: Vec<PathRuleState>,
16    /// Declared files, keyed by repository-relative path.
17    pub(crate) files: BTreeMap<RepositoryPath, FileSpec>,
18    /// The default formatter for complete managed files.
19    pub(crate) formatter: Option<FormatterSpec>,
20}
21
22/// A formatter supplied by the evaluated configuration.
23#[derive(Debug, Clone, Facet, PartialEq, Eq, PartialOrd, Ord)]
24#[facet(deny_unknown_fields)]
25pub(crate) struct FormatterSpec {
26    /// The formatter executable.
27    pub(crate) command: String,
28    /// Arguments passed before the target file.
29    pub(crate) args: Vec<String>,
30    /// Other managed files the formatter reads, besides its target.
31    pub(crate) inputs: Vec<RepositoryPath>,
32    /// The repository-relative file that identifies the formatter's project
33    /// root.
34    pub(crate) project_root_file: RepositoryPath,
35}
36
37/// File metadata shared by every managed document kind.
38#[derive(Debug, Clone, Facet)]
39#[facet(deny_unknown_fields)]
40pub(crate) struct FileSpec {
41    /// Whether the file is executable.
42    pub(crate) executable: bool,
43    /// Per-file formatter override. Null inherits the component default.
44    pub(crate) formatter: Option<FormatterSpec>,
45    /// The file's desired content.
46    pub(crate) document: DocumentSpec,
47}
48
49impl FileSpec {
50    pub(crate) fn validate(&self, path: &RepositoryPath) -> Result<(), FilesError> {
51        if self.is_partial() && self.formatter.is_some() {
52            return Err(FormatterError::PartialOverride { path: path.clone() }.into());
53        }
54        self.document
55            .validate()
56            .map_err(|source| FilesError::Document {
57                path: path.clone(),
58                source,
59            })
60    }
61
62    pub(crate) const fn is_partial(&self) -> bool {
63        self.document.is_partial()
64    }
65
66    pub(crate) const fn mode(&self) -> FileMode {
67        if self.executable {
68            FileMode::Executable
69        } else {
70            FileMode::Regular
71        }
72    }
73
74    pub(crate) fn formatter<'a>(
75        &'a self,
76        default: Option<&'a FormatterSpec>,
77    ) -> Option<&'a FormatterSpec> {
78        self.formatter.as_ref().or(default)
79    }
80
81    pub(crate) fn reconciled_content(
82        &self,
83        path: &RepositoryPath,
84        current: Option<&[u8]>,
85    ) -> Result<Vec<u8>, FilesError> {
86        self.document
87            .reconcile(current)
88            .map_err(|source| FilesError::Document {
89                path: path.clone(),
90                source,
91            })
92    }
93}