Skip to content

Commit

Permalink
feat: add tman modify graph (#448)
Browse files Browse the repository at this point in the history
  • Loading branch information
halajohn authored Dec 22, 2024
1 parent ea7de0e commit d4d7f3e
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 38 deletions.
10 changes: 5 additions & 5 deletions core/src/ten_manager/src/cmd/cmd_designer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,22 @@ pub async fn execute_cmd(
}
};

let mut actual_base_dir: Option<String> = Some(base_dir);
let mut actual_base_dir_opt: Option<String> = 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(),
}));
Expand Down
150 changes: 120 additions & 30 deletions core/src/ten_manager/src/cmd/cmd_modify/cmd_modify_graph.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::{fs, path::PathBuf};

//
// Copyright © 2024 Agora
Expand All @@ -19,32 +19,34 @@ use crate::{
pub struct ModifyGraphCommand {
pub app_dir: String,
pub predefined_graph_name: String,
pub modifications: Vec<String>,
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 {
Expand All @@ -57,11 +59,10 @@ pub fn parse_sub_cmd(sub_cmd_args: &ArgMatches) -> ModifyGraphCommand {
.get_one::<String>("PREDEFINED_GRAPH_NAME")
.unwrap()
.to_string(),
modifications: sub_cmd_args
.get_many::<String>("MODIFICATIONS")
.unwrap_or_default()
.map(|s| s.to_string())
.collect(),
modification: sub_cmd_args
.get_one::<String>("MODIFICATION")
.unwrap()
.to_string(),
};

cmd
Expand All @@ -74,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)
})?;

Expand Down Expand Up @@ -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::<Vec<_>>();

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))
}
2 changes: 1 addition & 1 deletion core/src/ten_manager/src/cmd/cmd_modify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 4 additions & 2 deletions tools/rust/cargo_clippy.sh
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit d4d7f3e

Please sign in to comment.