blob: 9b92ff787955652d7ea39bbbf5865a8055ab0076 [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2024 Boeing
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Boeing - initial API and implementation
**********************************************************************/
use applic_config::BatConfigElement;
use applicability_parser::{parse_applicability,sanitize_applicability::SanitizeApplicability,substitute_applicability::SubstituteApplicability};
use clap::Parser;
use std::{ thread::{ self }, fs::{File, create_dir_all, self}, sync::mpsc::channel, path::Path, io::ErrorKind };
use common_path::common_path;
mod applic_config;
/// Block Applicability Tool(BAT)
/// Supported Default Formats:
/// *.md: Starting Syntax: `` Ending Syntax: ``
#[derive(Parser)]
#[clap(author="Luciano Vaglienti",version,verbatim_doc_comment)]
struct CliOptions {
/// Config file containing the valid applicabilities,configurations, and substitutions.
/// An example:
///[
/// {
/// "normalizedName":"PRODUCT_A",
/// "features":["ENGINE_5=A2543","JHU_CONTROLLER=EXCLUDED","ROBOT_ARM_LIGHT=EXCLUDED","ROBOT_SPEAKER=SPKR_A"],
/// "substitutions":[
/// {"matchText":"SOME_SUBSTITUTION","substitute":"SOME NEW TEXT CONTENT"}
/// ]
/// },
/// {
/// "normalizedName":"PRODUCT_B",
/// "features":["ENGINE_5=A2543","JHU_CONTROLLER=INCLUDED","ROBOT_ARM_LIGHT=INCLUDED","ROBOT_SPEAKER=SPKR_A"]
/// },
/// {
/// "normalizedName":"abGroup",
/// "features":["ENGINE_5=A2543","JHU_CONTROLLER=INCLUDED","ROBOT_ARM_LIGHT=INCLUDED","ROBOT_SPEAKER=SPKR_A"]
/// },
/// {
/// "normalizedName":"PRODUCT_D",
/// "features":["ENGINE_5=B5543","JHU_CONTROLLER=EXCLUDED","ROBOT_ARM_LIGHT=EXCLUDED","ROBOT_SPEAKER=SPKR_B"]
/// },
/// {
/// "normalizedName":"PRODUCT_C",
/// "features":["ENGINE_5=A2543","JHU_CONTROLLER=INCLUDED","ROBOT_ARM_LIGHT=EXCLUDED","ROBOT_SPEAKER=SPKR_B"]
/// }
///]
#[clap(short, long,verbatim_doc_comment)]
applicability_config: std::path::PathBuf,
/// The output directory for processed files.
#[clap(short, long)]
out_dir: std::path::PathBuf,
/// The input files to pre-process
#[clap(short, long, value_delimiter = ',', value_terminator=";")]
srcs: Vec<std::path::PathBuf>,
/// Override start comment syntax if the file type is not already natively supported.
/// For a C style language, you should opt for // or if you are intending to use multi-line,
/// use /*
#[clap(short, long, default_value="//",verbatim_doc_comment)]
begin_comment_syntax:String,
/// Override end comment syntax if the file type is not already natively supported.
/// For a C style language you should not fill this out, unless you are intending to use multi-line, in which case
/// you should use */
#[clap(short, long, default_value=None,verbatim_doc_comment)]
end_comment_syntax:Option<String>
}
fn main() {
let args = CliOptions::parse();
let out_dir = args.out_dir.as_path();
let applic_config: Vec<BatConfigElement> = match File::open(args.applicability_config) {
Ok(file) =>
match serde_json::from_reader(file) {
Ok(res) => res,
Err(e) => panic!("Could not parse applicability config JSON \n{:?}: \tat line {:?} column {:?}",e.classify(),e.line(),e.column()),
}
Err(e) => panic!("Could not find applicability config {:?}",e),
};
let start_comment_syntax = args.begin_comment_syntax.as_str();
let end_comment_syntax_temp = match args.end_comment_syntax{
Some(i) =>i,
None => "".to_owned(),
};
let end_comment_syntax= end_comment_syntax_temp.as_str();
thread::scope(|scope| {
for input in &args.srcs {
let applic_config_for_file = applic_config.clone();
let _outer_thread = scope.spawn(move ||{
println!("Processing input {}", input.to_str().unwrap_or(""));
let file_contents = get_file_contents(input);
let (start_syntax,end_syntax)= get_comment_syntax(input, start_comment_syntax, end_comment_syntax);
let content_result = parse_applicability(&file_contents,start_syntax.as_str(),end_syntax.as_str());
let contents = match content_result{
Ok((_remaining, results)) => results,
Err(_) => panic!("Failed to unwrap parsed AST"),
};
for config in applic_config_for_file.iter().cloned(){
let copy = contents.clone();
let cloned_config= config.clone();
let (sender, receiver) = channel();
let _s1 =scope.spawn(move ||{
let substitutions = match config.substitutions{
Some(res) => res,
None => vec![],
};
let sanitized_content = copy.iter().cloned().map(|c|c.substitute(&substitutions).sanitize(config.features.to_owned(), &config.normalized_name,&substitutions).into()).collect::<Vec<String>>().join("");
sender.send(sanitized_content)
});
let _s2 = scope.spawn(move||{
// make sure the folders are available
match create_dir_all(out_dir){
Ok(dir) => dir,
Err(e) => panic!("Failed to create output directory {:#?}! Error: {:#?}",out_dir,e),
};
//convert any relative paths to absolute paths
let out_dirs = match fs:: canonicalize(out_dir){
Ok(i) => i,
Err(e) => {println!("Error finding output directory {:#?} .Received {:#?}",out_dir,e); out_dir.to_path_buf()},
};
let input_path = match fs:: canonicalize(input){
Ok(i) => i,
Err(e) => {println!("Error finding input file {:#?} .Received {:#?}. You should check to see if the file exists.",input,e); input.to_path_buf()},
};
let prefix = common_path(&input_path,&out_dirs).unwrap();
let config_path = Path::new("config").join(Path::new(&cloned_config.normalized_name));
let output_config_path = out_dirs.join(config_path);
let processed_path =output_config_path.join(match input_path.strip_prefix(prefix){
Ok(i) => i,
Err(e) => {println!("Error stripping input prefix {:?} from input {:?}",e, input); input},
});
let parent = &processed_path.parent().unwrap();
let _create_dir_all = create_dir_all(parent);
let _f = match File::create(&processed_path){
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
};
let file_result = File::open(&processed_path);
let _file = match file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create(&processed_path) {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error);
}
},
};
for received in receiver {
//write the file out
let _text = received.clone();
match fs::write(&processed_path,received){
Ok(r) => r,
Err(e) => println!("Failed to write {:#?} to {:#?}. \n Error Code: {:#?}",_text, processed_path,e),
};
}
//close the file
});
}
});
}
});
}
fn get_file_contents(file:&Path) ->String{
match std::fs::read_to_string(file){
Ok(i) => i,
Err(e) => panic!("Can't convert file {:#?} to bytes. \n Error: {:#?}",file.as_os_str(),e),
}
}
/// Sets the comment syntax to the defaults if they are defined for a given file type.
///
/// Currently supported:
///
/// .md
fn get_comment_syntax(file: &Path, start_comment_syntax:& str,end_comment_syntax:& str) ->(String,String){
let file_ref_copy = file;
let ext = match file_ref_copy.extension(){
Some(extension) => extension.to_str(),
None => None,//do nothing
};
let (start_comment_syntax, end_comment_syntax) = match ext{
Some("md")=>("``","``"),
_rest=> (start_comment_syntax,end_comment_syntax),
};
println!("start comment syntax {:#?}\r\n end comment syntax {:#?}",start_comment_syntax,end_comment_syntax);
(start_comment_syntax.to_string(),end_comment_syntax.to_string())
}