From e4a5669dc99ce49b5f4ed64f86851d62032a80e3 Mon Sep 17 00:00:00 2001 From: Hu Yueh-Wei Date: Sun, 22 Dec 2024 11:51:20 +0800 Subject: [PATCH 1/2] feat: add tman modify graph --- core/src/ten_manager/src/cmd/cmd_designer.rs | 10 +- .../src/cmd/cmd_modify/cmd_modify_graph.rs | 146 ++++++++++++++---- .../src/ten_manager/src/cmd/cmd_modify/mod.rs | 2 +- 3 files changed, 124 insertions(+), 34 deletions(-) diff --git a/core/src/ten_manager/src/cmd/cmd_designer.rs b/core/src/ten_manager/src/cmd/cmd_designer.rs index 3cc260b8e..46864cfc5 100644 --- a/core/src/ten_manager/src/cmd/cmd_designer.rs +++ b/core/src/ten_manager/src/cmd/cmd_designer.rs @@ -124,22 +124,22 @@ pub async fn execute_cmd( } }; - let mut actual_base_dir: Option = Some(base_dir); + let mut actual_base_dir_opt: Option = Some(base_dir); // Check if the base_dir is an app folder. - if let Some(actual_base_dir_ref) = actual_base_dir.as_ref() { - if let Err(e) = check_is_app_folder(Path::new(actual_base_dir_ref)) { + if let Some(actual_base_dir) = actual_base_dir_opt.as_ref() { + if let Err(e) = check_is_app_folder(Path::new(actual_base_dir)) { println!( "{} base_dir is not an app folder: {}", Emoji("🚨", ":-("), e ); - actual_base_dir = None; + actual_base_dir_opt = None; } } let state = Arc::new(RwLock::new(DesignerState { - base_dir: actual_base_dir, + base_dir: actual_base_dir_opt, all_pkgs: None, tman_config: TmanConfig::default(), })); diff --git a/core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs b/core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs index c7e7ef362..bd0ada7fb 100644 --- a/core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs +++ b/core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{fs, path::PathBuf}; // // Copyright © 2024 Agora @@ -19,32 +19,34 @@ use crate::{ pub struct ModifyGraphCommand { pub app_dir: String, pub predefined_graph_name: String, - pub modifications: Vec, + pub modification: String, } pub fn create_sub_cmd(_args_cfg: &crate::cmd_line::ArgsCfg) -> Command { Command::new("graph") -.about("Modify a specified predefined graph in property.json") -.arg( - Arg::new("APP_DIR") - .long("app-dir") - .help("Specify the app directory") - .required(true) - .num_args(1) -) -.arg( - Arg::new("PREDEFINED_GRAPH_NAME") - .long("predefined-graph-name") - .help("Specify the predefined graph name to be modified") - .required(true) - .num_args(1) -) -.arg( - Arg::new("MODIFICATIONS") - .help("The path=JsonString to modify in the selected graph. E.g. nodes[1].property.a=\"\\\"foo\\\"\"") - .required(true) - .num_args(1) -) + .about("Modify a specified predefined graph in property.json") + .arg( + Arg::new("APP_DIR") + .long("app-dir") + .help("Specify the app directory") + .required(true) + .num_args(1) + ) + .arg( + Arg::new("PREDEFINED_GRAPH_NAME") + .long("predefined-graph-name") + .help("Specify the predefined graph name to be modified") + .required(true) + .num_args(1) + ) + .arg( + Arg::new("MODIFICATION") + .long("modification") + .short('m') + .help("The path=JsonString to modify in the selected graph. E.g. nodes[1].property.a=\"\\\"foo\\\"\"") + .required(true) + .num_args(1) + ) } pub fn parse_sub_cmd(sub_cmd_args: &ArgMatches) -> ModifyGraphCommand { @@ -57,11 +59,10 @@ pub fn parse_sub_cmd(sub_cmd_args: &ArgMatches) -> ModifyGraphCommand { .get_one::("PREDEFINED_GRAPH_NAME") .unwrap() .to_string(), - modifications: sub_cmd_args - .get_many::("MODIFICATIONS") - .unwrap_or_default() - .map(|s| s.to_string()) - .collect(), + modification: sub_cmd_args + .get_one::("MODIFICATION") + .unwrap() + .to_string(), }; cmd @@ -119,9 +120,98 @@ pub async fn execute_cmd( ) })?; + // Handle modification. The format is `path=JsonString`. + // e.g. nodes[1].property.a="\"foo\"" + // Split it into "nodes[1].property.a" 與 "\"foo\"" + let mut iter = command_data.modification.splitn(2, '='); + let json_path = iter + .next() + .ok_or_else(|| anyhow::anyhow!("Invalid modification format"))? + .trim(); + let raw_value_str = iter + .next() + .ok_or_else(|| anyhow::anyhow!("No '=' found in modification"))? + .trim(); + + // raw_value_str should be a valid JSON string, e.g., "\"foo\"" => + // After parsing, it will become "foo" + let new_value: Value = + serde_json::from_str(raw_value_str).with_context(|| { + format!("Invalid JSON for the new value: {}", raw_value_str) + })?; + + // Replace this `new_value` into target_graph. + apply_json_patch(target_graph, json_path, new_value)?; + + // Serialize and write back to property.json. + let new_property_str = serde_json::to_string_pretty(&property_json)?; + fs::write(&property_file_path, new_property_str)?; + println!( "Successfully modified the graph '{}'", command_data.predefined_graph_name ); Ok(()) } + +/// Based on user input like "nodes[1].property.a", locate the corresponding +/// position in `json_obj` and write `new_value` to it. +fn apply_json_patch( + json_obj: &mut Value, + path_expr: &str, + new_value: Value, +) -> Result<()> { + // Ex: nodes[1].property.a => ["nodes[1]", "property", "a"] + let segments = path_expr.split('.').collect::>(); + + let mut current_val = json_obj; + + for (i, seg) in segments.iter().enumerate() { + // Check if an array index is included. + if let Some(idx_start) = seg.find('[') { + // Ex, if seg="nodes[1]", then key="nodes" and arr_idx=1 + let key_part = &seg[..idx_start]; + let idx_part = &seg[idx_start + 1..seg.len() - 1]; + let arr_idx: usize = idx_part.parse()?; + + // First, find key_part in the current layer. + current_val = current_val + .get_mut(key_part) + .ok_or_else(|| anyhow::anyhow!("Key {} not found", key_part))?; + + // Then, access the array. + let arr = current_val.as_array_mut().ok_or_else(|| { + anyhow::anyhow!("Value of key {} is not array", key_part) + })?; + + if arr_idx >= arr.len() { + return Err(anyhow::anyhow!( + "Array index out of range: {}", + arr_idx + )); + } + current_val = &mut arr[arr_idx]; + } else { + // Regular object key. If it's the last segment, assign the value. + if i == segments.len() - 1 { + // Reached the end. + current_val + .as_object_mut() + .ok_or_else(|| { + anyhow::anyhow!("Not an object at segment: {}", seg) + })? + .insert(seg.to_string(), new_value); + return Ok(()); + } else { + // Not the last segment, continue traversing. + current_val = current_val + .get_mut(*seg) + .ok_or_else(|| anyhow::anyhow!("Key {} not found", seg))?; + } + } + } + + // If `path_expr` is empty, theoretically the loop won't be entered, and + // this case is not supported. + Err(anyhow::anyhow!("Invalid path expression: '{}'", path_expr)) +} diff --git a/core/src/ten_manager/src/cmd/cmd_modify/mod.rs b/core/src/ten_manager/src/cmd/cmd_modify/mod.rs index 32b47e0f0..b6bb47185 100644 --- a/core/src/ten_manager/src/cmd/cmd_modify/mod.rs +++ b/core/src/ten_manager/src/cmd/cmd_modify/mod.rs @@ -18,7 +18,7 @@ pub enum ModifyCommandData { pub fn create_sub_cmd(args_cfg: &crate::cmd_line::ArgsCfg) -> Command { Command::new("modify") - .about("Modify various configurations") + .about("Modify something in the TEN framework") .subcommand_required(true) .arg_required_else_help(true) .subcommand(crate::cmd::cmd_modify::cmd_modify_graph::create_sub_cmd( From ba3bc503c6cfd4fdcdff3b84c5c9d4838cb6794d Mon Sep 17 00:00:00 2001 From: Hu Yueh-Wei Date: Sun, 22 Dec 2024 11:59:26 +0800 Subject: [PATCH 2/2] feat: add tman modify graph --- core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs | 4 ++-- tools/rust/cargo_clippy.sh | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs b/core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs index bd0ada7fb..6fee4b791 100644 --- a/core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs +++ b/core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs @@ -75,8 +75,8 @@ pub async fn execute_cmd( // Find `property.json`. let property_file_path = PathBuf::from(&command_data.app_dir).join(PROPERTY_JSON_FILENAME); - let mut property_str = read_file_to_string(&property_file_path) - .with_context(|| { + let property_str = + read_file_to_string(&property_file_path).with_context(|| { format!("Failed to read file: {:?}", property_file_path) })?; diff --git a/tools/rust/cargo_clippy.sh b/tools/rust/cargo_clippy.sh index 95aa7e333..8125cae2b 100755 --- a/tools/rust/cargo_clippy.sh +++ b/tools/rust/cargo_clippy.sh @@ -1,9 +1,11 @@ #!/bin/bash cd core/src/ten_rust || exit 1 -cargo clippy -- -D warnings +cargo clippy --all-features --tests -- -D warnings -W clippy::all +cargo clippy --release --all-features --tests -- -D warnings -W clippy::all cd ../../.. cd core/src/ten_manager || exit 1 -cargo clippy -- -D warnings +cargo clippy --all-features --tests -- -D warnings -W clippy::all +cargo clippy --release --all-features --tests -- -D warnings -W clippy::all