use crate::adapters::cli::format::{materialize_help, plan_help, watch_help};
use crate::api::{MaterializationRequest, OutputFormat, RepoSelector};
use std::path::PathBuf;
#[derive(Clone, Debug)]
pub(crate) struct MaterializeOptions {
pub(crate) native_request: Option<PathBuf>,
pub(crate) source_root: Option<PathBuf>,
pub(crate) config: Option<PathBuf>,
pub(crate) db: Option<PathBuf>,
pub(crate) manifest: Option<PathBuf>,
pub(crate) mode: String,
pub(crate) include_fts: bool,
pub(crate) semantic_enrichment: bool,
pub(crate) semantic_provider_mode: String,
pub(crate) use_git: bool,
pub(crate) git_diff: bool,
pub(crate) git_base: Option<String>,
pub(crate) include_patterns: Vec<String>,
pub(crate) exclude_patterns: Vec<String>,
pub(crate) candidate_paths: Vec<String>,
pub(crate) parallel: bool,
pub(crate) progress: bool,
pub(crate) help: bool,
pub(crate) json_output: bool,
}
impl Default for MaterializeOptions {
fn default() -> Self {
Self {
native_request: None,
source_root: None,
config: None,
db: None,
manifest: None,
mode: String::new(),
include_fts: false,
semantic_enrichment: false,
semantic_provider_mode: String::new(),
use_git: false,
git_diff: false,
git_base: None,
include_patterns: Vec::new(),
exclude_patterns: Vec::new(),
candidate_paths: Vec::new(),
parallel: true,
progress: false,
help: false,
json_output: false,
}
}
}
impl MaterializeOptions {
pub(crate) fn parse(args: &[String]) -> Result<Self, String> {
Self::parse_with_command(args, "build")
}
pub(crate) fn parse_with_command(args: &[String], command_name: &str) -> Result<Self, String> {
let mut options = Self {
include_fts: true,
semantic_enrichment: true,
use_git: true,
..Self::default()
};
let mut index = 0;
while index < args.len() {
match args[index].as_str() {
"-h" | "--help" => {
options.help = true;
index += 1;
}
"--native-request" => {
let value = args
.get(index + 1)
.ok_or_else(|| "--native-request requires a path".to_string())?;
options.native_request = Some(PathBuf::from(value));
index += 2;
}
"--source-root" | "--repo-root" => {
let value = args
.get(index + 1)
.ok_or_else(|| format!("{} requires a path", args[index]))?;
options.source_root = Some(PathBuf::from(value));
index += 2;
}
"--db" => {
let value = args
.get(index + 1)
.ok_or_else(|| "--db requires a path".to_string())?;
options.db = Some(PathBuf::from(value));
index += 2;
}
"--manifest" => {
let value = args
.get(index + 1)
.ok_or_else(|| "--manifest requires a path".to_string())?;
options.manifest = Some(PathBuf::from(value));
index += 2;
}
"--mode" => {
let value = args
.get(index + 1)
.ok_or_else(|| "--mode requires a value".to_string())?;
options.mode = value.clone();
index += 2;
}
"--no-fts" => {
options.include_fts = false;
index += 1;
}
"--no-semantic-enrichment" => {
options.semantic_enrichment = false;
index += 1;
}
"--semantic-provider-mode" => {
let value = args
.get(index + 1)
.ok_or_else(|| "--semantic-provider-mode requires a value".to_string())?;
options.semantic_provider_mode = value.clone();
index += 2;
}
"--no-git" => {
options.use_git = false;
index += 1;
}
"--git-diff" => {
options.git_diff = true;
index += 1;
}
"--git-base" => {
let value = args
.get(index + 1)
.ok_or_else(|| "--git-base requires a revision".to_string())?;
options.git_base = Some(value.clone());
options.git_diff = true;
index += 2;
}
"--include" => {
let value = args
.get(index + 1)
.ok_or_else(|| "--include requires a glob pattern".to_string())?;
options.include_patterns.push(value.clone());
index += 2;
}
"--exclude" => {
let value = args
.get(index + 1)
.ok_or_else(|| "--exclude requires a glob pattern".to_string())?;
options.exclude_patterns.push(value.clone());
index += 2;
}
"--single-thread" => {
options.parallel = false;
index += 1;
}
"--parallel" => {
options.parallel = true;
index += 1;
}
"--progress" => {
options.progress = true;
index += 1;
}
"--json" => {
options.json_output = true;
index += 1;
}
other => {
return Err(format!(
"unknown {command_name} option: {other}\n\n{}",
materialize_like_help(command_name)
));
}
}
}
Ok(options)
}
}
pub(in crate::adapters::cli) fn materialize_like_help(command_name: &str) -> &'static str {
match command_name {
"plan" => plan_help(),
"watch" => watch_help(),
_ => materialize_help(),
}
}
pub(in crate::adapters::cli) fn materialize_request(
options: &MaterializeOptions,
output_format: OutputFormat,
) -> MaterializationRequest {
MaterializationRequest {
repo: RepoSelector {
repo_root: options.source_root.clone(),
config_path: options.config.clone(),
db_path: options.db.clone(),
manifest_path: options.manifest.clone(),
},
native_request_path: options.native_request.clone(),
source_root: options
.source_root
.as_ref()
.map(|path| path.to_string_lossy().to_string()),
mode: options.mode.clone(),
include_fts: options.include_fts,
semantic_enrichment: options.semantic_enrichment,
semantic_provider_mode: options.semantic_provider_mode.clone(),
use_git: options.use_git,
git_diff: options.git_diff,
git_base: options.git_base.clone(),
include_patterns: options.include_patterns.clone(),
exclude_patterns: options.exclude_patterns.clone(),
candidate_paths: options.candidate_paths.clone(),
parallel: options.parallel,
progress: options.progress,
output_format,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn materialize_request_maps_command_options_to_public_contract() {
let options = MaterializeOptions::parse(&[
"--repo-root".to_string(),
"/tmp/project".to_string(),
"--db".to_string(),
"/tmp/project/graph.db".to_string(),
"--mode".to_string(),
"incremental".to_string(),
"--no-fts".to_string(),
"--git-diff".to_string(),
"--include".to_string(),
"src/**/*.rs".to_string(),
"--single-thread".to_string(),
])
.expect("command options should parse");
let request = materialize_request(&options, OutputFormat::Block);
assert_eq!(
request.repo.repo_root.as_deref(),
Some(std::path::Path::new("/tmp/project"))
);
assert_eq!(
request.repo.db_path.as_deref(),
Some(std::path::Path::new("/tmp/project/graph.db"))
);
assert_eq!(request.mode, "incremental");
assert!(!request.include_fts);
assert!(request.git_diff);
assert_eq!(request.include_patterns, ["src/**/*.rs"]);
assert!(!request.parallel);
assert_eq!(request.output_format, OutputFormat::Block);
}
}