Skip to main content

pray_core/
manifest_format.rs

1use crate::manifest::ManifestPackage;
2use crate::{PrayError, PrayResult};
3
4pub fn format_package_declaration(package: &ManifestPackage) -> String {
5    let mut parts = vec![format!("pray \"{}\"", package.name)];
6    if package.constraint != "*" {
7        parts.push(format!("\"{}\"", package.constraint));
8    }
9    if let Some(path) = &package.path {
10        parts.push(format!("path: \"{path}\""));
11    }
12    if let Some(source) = &package.source {
13        parts.push(format!("source: \"{source}\""));
14    }
15    if let Some(git) = &package.git {
16        parts.push(format!("git: \"{git}\""));
17    }
18    if let Some(tag) = &package.tag {
19        parts.push(format!("tag: \"{tag}\""));
20    }
21    if let Some(rev) = &package.rev {
22        parts.push(format!("rev: \"{rev}\""));
23    }
24    if let Some(tarball) = &package.tarball {
25        parts.push(format!("tarball: \"{tarball}\""));
26    }
27    if let Some(oci) = &package.oci {
28        parts.push(format!("oci: \"{oci}\""));
29    }
30    if let Some(file) = &package.file {
31        parts.push(format!("file: \"{file}\""));
32    }
33    if !package.exports.is_empty() {
34        if package.exports.len() == 1 {
35            parts.push(format!("export: \"{}\"", package.exports[0]));
36        } else {
37            parts.push(format!(
38                "exports: [{}]",
39                format_string_keyword_list(&package.exports)
40            ));
41        }
42    }
43    if !package.targets.is_empty() {
44        parts.push(format!(
45            "targets: [{}]",
46            format_string_keyword_list(&package.targets)
47        ));
48    }
49    if !package.features.is_empty() {
50        parts.push(format!(
51            "features: [{}]",
52            format_string_keyword_list(&package.features)
53        ));
54    }
55    if package.optional {
56        parts.push("optional: true".to_string());
57    }
58    parts.join(", ")
59}
60
61pub fn replace_package_declaration(text: &str, package: &ManifestPackage) -> PrayResult<String> {
62    let name = &package.name;
63    let prefixes = [
64        format!("pray \"{name}\""),
65        format!("pray '{name}'"),
66        format!("use \"{name}\""),
67        format!("include \"{name}\""),
68        format!("agent \"{name}\""),
69        format!("agent '{name}'"),
70        format!("package \"{name}\""),
71        format!("package '{name}'"),
72    ];
73    let mut lines: Vec<String> = text.lines().map(|line| line.to_string()).collect();
74    let mut replaced = 0usize;
75    for line in &mut lines {
76        let trimmed = line.trim_start();
77        if prefixes
78            .iter()
79            .any(|prefix| trimmed.starts_with(prefix.as_str()))
80        {
81            *line =
82                crate::manifest_constraint::rewrite_constraint_on_line(line, &package.constraint)?;
83            replaced += 1;
84        }
85    }
86    if replaced == 0 {
87        return Err(PrayError::Manifest(format!(
88            "package {name} not found in manifest"
89        )));
90    }
91    let mut output = lines.join("\n");
92    if text.ends_with('\n') && !output.ends_with('\n') {
93        output.push('\n');
94    }
95    Ok(output)
96}
97
98fn format_string_keyword_list(values: &[String]) -> String {
99    values
100        .iter()
101        .map(|value| format!("\"{value}\""))
102        .collect::<Vec<_>>()
103        .join(", ")
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::manifest::ManifestPackage;
110
111    fn package(constraint: &str) -> ManifestPackage {
112        ManifestPackage {
113            name: "sample/base".to_string(),
114            constraint: constraint.to_string(),
115            ..Default::default()
116        }
117    }
118
119    #[test]
120    fn rewrites_every_matching_declaration_and_keeps_indent() {
121        let text = r#"
122prayfile "1"
123compose "AGENTS.md" do
124  pray "sample/base", "~> 1.0"
125end
126tree ".agents/skills" do
127  pray "sample/base", "~> 1.0", export: "testing-basics"
128end
129"#;
130        let updated = replace_package_declaration(text, &package("~> 1.1")).expect("replace");
131        assert!(updated.contains(r#"  pray "sample/base", "~> 1.1""#));
132        assert!(updated.contains(r#"  pray "sample/base", "~> 1.1", export: "testing-basics""#));
133        assert!(!updated.contains("~> 1.0"));
134    }
135}