pray_core/
manifest_format.rs1use 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}