diff --git a/devtools/text-optimizer/src/error.rs b/devtools/text-optimizer/src/error.rs index e3dd29f0433..3f42ece2300 100644 --- a/devtools/text-optimizer/src/error.rs +++ b/devtools/text-optimizer/src/error.rs @@ -1,7 +1,9 @@ #[derive(Debug)] pub enum MyError { + CommitId, Io(std::io::Error), Serde(serde_yaml::Error), + PathError, } impl From for MyError { diff --git a/devtools/text-optimizer/src/extractors/mod.rs b/devtools/text-optimizer/src/extractors/mod.rs index 9fc561ec0d4..ad98909de76 100644 --- a/devtools/text-optimizer/src/extractors/mod.rs +++ b/devtools/text-optimizer/src/extractors/mod.rs @@ -4,7 +4,7 @@ pub mod std_output_extractor; pub mod thiserror_extractor; use crate::{ - types::TextInfo, yaml_processor::save_yaml, CLAP_TEXT_FILE, LOG_TEXT_FILE, + error::MyError, types::TextInfo, yaml_processor::save_yaml, CLAP_TEXT_FILE, LOG_TEXT_FILE, STD_OUTPUT_TEXT_FILE, THISERROR_TEXT_FILE, }; @@ -27,14 +27,11 @@ pub trait Extractor { fn text_list(&self) -> Vec; fn scanning_file_path(&self) -> &PathBuf; fn save_file_path(&self) -> &PathBuf; - fn save_as_file(&self) { - save_yaml(self.save_file_path(), &self.text_list()).expect("save yaml"); - } fn visit_file(&mut self, node: &File); } -pub fn extract(project_root: PathBuf, output_dir: &PathBuf) { - // extractors +pub fn extract(root_cargo_config_path: PathBuf, commit_id: &str, output_dir: &PathBuf) { + // init all extractors let mut clap_extractor = ClapExtractor::new(output_dir.join(CLAP_TEXT_FILE)); let mut log_extractor = LogExtractor::new(output_dir.join(LOG_TEXT_FILE)); let mut std_output_extractor = StdOutputExtractor::new(output_dir.join(STD_OUTPUT_TEXT_FILE)); @@ -48,21 +45,24 @@ pub fn extract(project_root: PathBuf, output_dir: &PathBuf) { ]; let project_metadata = MetadataCommand::new() - .manifest_path(project_root) + .manifest_path(&root_cargo_config_path) .exec() .expect("Failed to get current directory"); for package in project_metadata.workspace_packages() { log::info!("Scanning Crate: {}", package.name); - let crate_src_path = Path::new(&package.manifest_path) + let crate_src_path = PathBuf::from(&package.manifest_path) .parent() .expect("workspace member crate path") .join("src"); + let crate_src_path = + to_relative_path(&crate_src_path, &root_cargo_config_path).expect("to_relative_path"); + process_rs_files_in_src(&crate_src_path, &mut extractors); } - save_extractors(output_dir, &extractors); + save_text_info_files(output_dir, &extractors, commit_id); } pub fn process_rs_files_in_src(dir_path: &Path, extractors: &mut [&mut dyn Extractor]) { @@ -103,14 +103,34 @@ pub fn extract_contents_in_brackets(lit: String) -> Option { None } -fn save_extractors(output_dir: &PathBuf, extractors: &[&mut dyn Extractor]) { +fn save_text_info_files(output_dir: &PathBuf, extractors: &[&mut dyn Extractor], commit_id: &str) { fs::create_dir_all(output_dir).expect("create dir all"); println!(); for extractor in extractors { - extractor.save_as_file(); + save_yaml( + extractor.save_file_path(), + &extractor.text_list(), + commit_id, + ) + .expect("save yaml"); + let file_name = extractor.save_file_path().file_name().unwrap(); let text_len = extractor.text_list().len(); println!("{:?}: {:?}", file_name, text_len); } } + +fn to_relative_path( + absolut_path: &Path, + root_cargo_config_path: &Path, +) -> Result { + let cargo_config_path_abs = root_cargo_config_path.canonicalize()?; + let project_root_path = cargo_config_path_abs.parent().ok_or(MyError::PathError)?; + let relative_path = absolut_path + .strip_prefix(project_root_path) + .map_err(|_| MyError::PathError)? + .to_str() + .ok_or(MyError::PathError)?; + Ok(Path::new("../..").join(relative_path)) +} diff --git a/devtools/text-optimizer/src/main.rs b/devtools/text-optimizer/src/main.rs index ef1a925a2c3..a326de8b0a6 100644 --- a/devtools/text-optimizer/src/main.rs +++ b/devtools/text-optimizer/src/main.rs @@ -6,10 +6,14 @@ mod yaml_processor; use backfill::backfill; use clap::{Parser, Subcommand}; +use error::MyError; use extractors::extract; use std::path::PathBuf; +use std::process::Command; +use std::result::Result; -pub const PROJECT_ROOT: &str = "../../Cargo.toml"; +pub const GITHUB_REPO: &str = "https://github.com/nervosnetwork/ckb/tree"; +pub const PROJECT_ROOT_CARGO_CONFIG_PATH: &str = "../../Cargo.toml"; pub const LOG_TEXT_FILE: &str = "log_text_list.yml"; pub const CLAP_TEXT_FILE: &str = "clap_text_list.yml"; pub const STD_OUTPUT_TEXT_FILE: &str = "std_output_text_list.yml"; @@ -26,6 +30,10 @@ struct Cli { enum Commands { /// extracting text Extract { + /// specify a github commit id as the rev for generating the source link + #[arg(short, long)] + commit_id: String, + /// specifies a directory path for .yml text files output #[arg(short, long, default_value = PathBuf::from("./").into_os_string())] output_dir: PathBuf, @@ -46,8 +54,16 @@ fn main() { ); match &cli.command { - Some(Commands::Extract { output_dir }) => { - extract(PathBuf::from(PROJECT_ROOT), output_dir); + Some(Commands::Extract { + commit_id, + output_dir, + }) => { + check_commit_id(commit_id).expect("check commit id"); + extract( + PathBuf::from(PROJECT_ROOT_CARGO_CONFIG_PATH), + commit_id, + output_dir, + ); } Some(Commands::Backfill { input_dir }) => { backfill(input_dir); @@ -55,3 +71,34 @@ fn main() { None => {} } } + +fn check_commit_id(commit_id: &str) -> Result<(), MyError> { + let output = Command::new("git") + .arg("rev-parse") + .arg("HEAD") + .output() + .expect("Failed to execute git command"); + + if output.status.success() { + let current_commit_id = String::from_utf8_lossy(&output.stdout); + let current_commit_id: String = current_commit_id.trim().chars().take(7).collect(); + let commit_id: String = commit_id.trim().chars().take(7).collect(); + if current_commit_id == commit_id { + log::info!( + "Current commit ID matches the expected commit ID: {}", + current_commit_id + ); + Ok(()) + } else { + log::warn!( + "Current commit ID {} does not match the expected commit ID {}.", + current_commit_id, + commit_id + ); + Err(MyError::CommitId) + } + } else { + log::error!("Failed to retrieve the current commit ID"); + Err(MyError::CommitId) + } +} diff --git a/devtools/text-optimizer/src/yaml_processor.rs b/devtools/text-optimizer/src/yaml_processor.rs index 097cff99c31..caa71b3c7b3 100644 --- a/devtools/text-optimizer/src/yaml_processor.rs +++ b/devtools/text-optimizer/src/yaml_processor.rs @@ -1,15 +1,76 @@ -use super::{error::MyError, types::TextInfo}; +use super::{ + error::MyError, + types::{Category, Meta, TextInfo}, + GITHUB_REPO, +}; +use serde::{Deserialize, Serialize}; use std::io::Read; use std::io::Write; use std::{fs::File, path::PathBuf}; -pub fn save_yaml(file: &PathBuf, data: &[TextInfo]) -> Result<(), MyError> { +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct TextInfoSave { + original: String, + editable: String, + metadata: Metadata, +} + +impl TextInfoSave { + pub fn from_text_info(text_info: TextInfo, git_repo: &str, commit_id: &str) -> Self { + // 使用 Metadata 的 from_meta 方法进行 Meta 到 Metadata 的转换 + let metadata = Metadata::from_meta(text_info.metadata(), git_repo, commit_id); + + // 创建 TextInfoSave 结构体并返回 + TextInfoSave { + original: text_info.original().to_owned(), + editable: text_info.editable().to_owned(), + metadata, + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct Metadata { + category: Category, + file: PathBuf, + code_line_link: Vec, +} + +impl Metadata { + // 定义从 Meta 到 Metadata 的转换方法 + pub fn from_meta(meta: &Meta, github_repo: &str, commit_id: &str) -> Self { + // 创建 GitHub 代码行链接的前缀 + let github_link_prefix = format!("{}/{}/", github_repo, commit_id); + + // 为每个代码行生成 GitHub 链接 + let code_line_link: Vec = meta + .start_lines() + .iter() + .map(|line| format!("{}#L{}", github_link_prefix, line)) + .collect(); + + Metadata { + category: meta.category().to_owned(), + file: meta.file().to_owned(), + code_line_link, + } + } +} + +pub fn save_yaml(file: &PathBuf, data: &[TextInfo], commit_id: &str) -> Result<(), MyError> { let mut file = File::create(file)?; + + // Convert TextInfo to TextInfoSave + let data_save: Vec = data + .iter() + .map(|text_info| TextInfoSave::from_text_info(text_info.clone(), GITHUB_REPO, commit_id)) + .collect(); + file.write_fmt(format_args!( "# Number of TextInfo items: {}\n\n", data.len() ))?; - serde_yaml::to_writer(file, data)?; + serde_yaml::to_writer(file, &data_save)?; Ok(()) }