systemprompt_cli/commands/core/content/
edit_apply.rs1use anyhow::{Context, Result, anyhow};
7use std::fs;
8use std::path::Path;
9use systemprompt_content::{CategoryIdUpdate, ContentRepository};
10use systemprompt_identifiers::CategoryId;
11
12use super::edit::EditArgs;
13
14const VALID_KINDS: &[&str] = &["article", "paper", "guide", "tutorial"];
15
16#[derive(Debug)]
17pub struct ContentEditState {
18 pub title: String,
19 pub description: String,
20 pub body: String,
21 pub keywords: String,
22 pub image: Option<String>,
23 pub category_id: CategoryIdUpdate,
24 pub public_value: Option<bool>,
25 pub kind_value: Option<String>,
26}
27
28pub fn apply_visibility_flags(
29 args: &EditArgs,
30 state: &mut ContentEditState,
31 changes: &mut Vec<String>,
32) {
33 if args.public {
34 state.public_value = Some(true);
35 changes.push("public: true".to_owned());
36 }
37 if args.private {
38 state.public_value = Some(false);
39 changes.push("public: false".to_owned());
40 }
41}
42
43pub fn apply_body_flags(
44 args: &EditArgs,
45 state: &mut ContentEditState,
46 changes: &mut Vec<String>,
47) -> Result<()> {
48 if let Some(b) = &args.body {
49 state.body.clone_from(b);
50 changes.push("body: updated".to_owned());
51 }
52 if let Some(file) = &args.body_file {
53 let path = Path::new(file);
54 state.body = fs::read_to_string(path)
55 .with_context(|| format!("Failed to read body file: {}", path.display()))?;
56 changes.push("body: updated from file".to_owned());
57 }
58 Ok(())
59}
60
61pub async fn apply_set_value_flags(
62 args: &EditArgs,
63 state: &mut ContentEditState,
64 changes: &mut Vec<String>,
65 repo: &ContentRepository,
66) -> Result<()> {
67 for set_value in &args.set_values {
68 let parts: Vec<&str> = set_value.splitn(2, '=').collect();
69 if parts.len() != 2 {
70 return Err(anyhow!(
71 "Invalid --set format: '{}'. Expected key=value",
72 set_value
73 ));
74 }
75 let key = parts[0].trim();
76 let value = parts[1].trim();
77 apply_set_field(key, value, state, changes, repo).await?;
78 }
79 Ok(())
80}
81
82async fn apply_set_field(
83 key: &str,
84 value: &str,
85 state: &mut ContentEditState,
86 changes: &mut Vec<String>,
87 repo: &ContentRepository,
88) -> Result<()> {
89 match key {
90 "title" => {
91 state.title = value.to_owned();
92 changes.push(format!("title: {}", value));
93 },
94 "description" => {
95 state.description = value.to_owned();
96 changes.push(format!("description: {}", value));
97 },
98 "keywords" => {
99 state.keywords = value.to_owned();
100 changes.push(format!("keywords: {}", value));
101 },
102 "image" => apply_image_field(value, state, changes),
103 "category_id" | "category" => apply_category_field(value, state, changes, repo).await?,
104 "kind" => apply_kind_field(value, state, changes)?,
105 "public" => apply_public_field(value, state, changes)?,
106 _ => {
107 return Err(anyhow!(
108 "Unknown field: '{}'. Supported fields: title, description, keywords, image, \
109 category_id, kind, public",
110 key
111 ));
112 },
113 }
114 Ok(())
115}
116
117fn apply_image_field(value: &str, state: &mut ContentEditState, changes: &mut Vec<String>) {
118 if value.eq_ignore_ascii_case("none") || value.is_empty() {
119 state.image = None;
120 changes.push("image: cleared".to_owned());
121 } else {
122 state.image = Some(value.to_owned());
123 changes.push(format!("image: {}", value));
124 }
125}
126
127async fn apply_category_field(
128 value: &str,
129 state: &mut ContentEditState,
130 changes: &mut Vec<String>,
131 repo: &ContentRepository,
132) -> Result<()> {
133 if value.eq_ignore_ascii_case("none") || value.is_empty() {
134 state.category_id = CategoryIdUpdate::Clear;
135 changes.push("category_id: cleared".to_owned());
136 return Ok(());
137 }
138 let cat_id = CategoryId::new(value.to_owned());
139 if !repo.category_exists(&cat_id).await? {
140 return Err(anyhow!(
141 "Category '{}' not found. Please use an existing category ID.",
142 value
143 ));
144 }
145 state.category_id = CategoryIdUpdate::Set(cat_id);
146 changes.push(format!("category_id: {}", value));
147 Ok(())
148}
149
150fn apply_kind_field(
151 value: &str,
152 state: &mut ContentEditState,
153 changes: &mut Vec<String>,
154) -> Result<()> {
155 if !VALID_KINDS.contains(&value) {
156 return Err(anyhow!(
157 "Invalid kind '{}'. Must be one of: {}",
158 value,
159 VALID_KINDS.join(", ")
160 ));
161 }
162 state.kind_value = Some(value.to_owned());
163 changes.push(format!("kind: {}", value));
164 Ok(())
165}
166
167fn apply_public_field(
168 value: &str,
169 state: &mut ContentEditState,
170 changes: &mut Vec<String>,
171) -> Result<()> {
172 let p = value.parse::<bool>().map_err(|_e| {
173 anyhow!(
174 "Invalid boolean value for public: '{}'. Use true or false",
175 value
176 )
177 })?;
178 state.public_value = Some(p);
179 changes.push(format!("public: {}", p));
180 Ok(())
181}