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