Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add tman modify graph #448

Merged
merged 2 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading