use super::stubs::Store;
use super::CommandResult;
use crate::cli::logging::CommandLogger;
use crate::cli::CliContext;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
use std::fs;
use std::path::{Path, PathBuf};
pub async fn run(name: String, format: String, location: Option<PathBuf>) -> CommandResult {
let ctx = CliContext::new();
let cmd_logger = CommandLogger::new("init", vec![name.clone(), format.clone()]);
ctx.info(&format!(
"Initializing dataset '{name}' with format '{format}'"
));
let (final_name, final_format, final_location, features) = if ctx.interactive {
interactive_init(&name, &format, location, &ctx)?
} else {
(
name.clone(),
format.clone(),
location.unwrap_or_else(|| PathBuf::from(&name)),
Features::default(),
)
};
let dataset_path = final_location;
match format.as_str() {
"tdb2" | "memory" => {}
_ => {
return Err(
format!("Unsupported format '{format}'. Supported formats: tdb2, memory").into(),
);
}
}
if dataset_path.exists() {
if ctx.interactive {
let overwrite = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!(
"Directory '{}' already exists. Overwrite?",
dataset_path.display()
))
.default(false)
.interact()?;
if !overwrite {
ctx.info("Initialization cancelled.");
return Ok(());
}
fs::remove_dir_all(&dataset_path)?;
} else {
return Err(format!(
"Dataset directory '{}' already exists",
dataset_path.display()
)
.into());
}
}
fs::create_dir_all(&dataset_path)?;
ctx.success(&format!(
"Created dataset directory: {}",
dataset_path.display()
));
let _store = match final_format.as_str() {
"tdb2" => Store::create(&dataset_path)?,
"memory" => Store::create(&dataset_path)?, _ => {
return Err(format!(
"Internal error: unsupported format '{}' passed validation",
final_format
)
.into());
}
};
let config_path = dataset_path.join("oxirs.toml");
let config_content =
create_config_with_features(&final_name, &final_format, &dataset_path, &features)?;
fs::write(&config_path, config_content)?;
ctx.success(&format!(
"Created configuration file: {}",
config_path.display()
));
ctx.info("Dataset Initialized Successfully");
ctx.info(&format!("Name: {final_name}"));
ctx.info(&format!("Storage format: {final_format}"));
ctx.info(&format!("Location: {}", dataset_path.display()));
ctx.info(&format!("Configuration: {}", config_path.display()));
if features.any_enabled() {
ctx.info("Enabled Features");
if features.reasoning {
ctx.verbose("• Reasoning (RDFS/OWL-RL)");
}
if features.validation {
ctx.verbose("• SHACL Validation");
}
if features.text_search {
ctx.verbose("• Full-text Search");
}
if features.vector_search {
ctx.verbose("• Vector Search");
}
if features.graphql {
ctx.verbose("• GraphQL Endpoint");
}
}
cmd_logger.success();
Ok(())
}
#[allow(dead_code)]
fn create_default_config(
name: &str,
format: &str,
_path: &Path,
) -> Result<String, Box<dyn std::error::Error>> {
let config = format!(
r#"# OxiRS Configuration
# Generated by oxirs init
[general]
default_format = "turtle"
[server]
port = 3030
host = "localhost"
enable_cors = true
enable_graphql = false
[datasets.{}]
name = "{}"
location = "."
dataset_type = "{}"
read_only = false
enable_reasoning = false
enable_validation = false
enable_text_search = false
enable_vector_search = false
"#,
name, name, format
);
Ok(config)
}
#[derive(Default)]
struct Features {
reasoning: bool,
validation: bool,
text_search: bool,
vector_search: bool,
graphql: bool,
}
impl Features {
fn any_enabled(&self) -> bool {
self.reasoning || self.validation || self.text_search || self.vector_search || self.graphql
}
}
fn interactive_init(
default_name: &str,
default_format: &str,
default_location: Option<PathBuf>,
ctx: &CliContext,
) -> Result<(String, String, PathBuf, Features), Box<dyn std::error::Error>> {
let theme = ColorfulTheme::default();
ctx.info("Dataset Initialization Wizard");
let name: String = Input::with_theme(&theme)
.with_prompt("Dataset name")
.default(default_name.to_string())
.interact_text()?;
let formats = vec!["tdb2", "memory"];
let format_index = formats
.iter()
.position(|&f| f == default_format)
.unwrap_or(0);
let format_idx = Select::with_theme(&theme)
.with_prompt("Storage format")
.items(&formats)
.default(format_index)
.interact()?;
let format = formats[format_idx].to_string();
let default_loc = default_location.unwrap_or_else(|| PathBuf::from(&name));
let location_str: String = Input::with_theme(&theme)
.with_prompt("Dataset location")
.default(default_loc.display().to_string())
.interact_text()?;
let location = PathBuf::from(location_str);
ctx.info("Optional Features");
ctx.verbose("Select features to enable for this dataset:");
let reasoning = Confirm::with_theme(&theme)
.with_prompt("Enable RDFS/OWL reasoning?")
.default(false)
.interact()?;
let validation = Confirm::with_theme(&theme)
.with_prompt("Enable SHACL validation?")
.default(false)
.interact()?;
let text_search = Confirm::with_theme(&theme)
.with_prompt("Enable full-text search?")
.default(false)
.interact()?;
let vector_search = Confirm::with_theme(&theme)
.with_prompt("Enable vector search (semantic similarity)?")
.default(false)
.interact()?;
let graphql = Confirm::with_theme(&theme)
.with_prompt("Enable GraphQL endpoint?")
.default(false)
.interact()?;
let features = Features {
reasoning,
validation,
text_search,
vector_search,
graphql,
};
ctx.info("Configuration Summary");
ctx.info(&format!("Name: {name}"));
ctx.info(&format!("Format: {format}"));
ctx.info(&format!("Location: {}", location.display()));
if features.any_enabled() {
ctx.verbose("Enabled features:");
if features.reasoning {
ctx.verbose("• Reasoning");
}
if features.validation {
ctx.verbose("• Validation");
}
if features.text_search {
ctx.verbose("• Text search");
}
if features.vector_search {
ctx.verbose("• Vector search");
}
if features.graphql {
ctx.verbose("• GraphQL");
}
}
let confirm = Confirm::with_theme(&theme)
.with_prompt("Proceed with initialization?")
.default(true)
.interact()?;
if !confirm {
return Err("Initialization cancelled by user".into());
}
Ok((name, format, location, features))
}
fn create_config_with_features(
name: &str,
format: &str,
_path: &Path,
features: &Features,
) -> Result<String, Box<dyn std::error::Error>> {
let config = format!(
r#"# OxiRS Configuration
# Generated by oxirs init
[general]
default_format = "turtle"
[server]
port = 3030
host = "localhost"
enable_cors = true
enable_graphql = {}
[datasets.{}]
name = "{}"
location = "."
dataset_type = "{}"
read_only = false
enable_reasoning = {}
enable_validation = {}
enable_text_search = {}
enable_vector_search = {}
"#,
features.graphql,
name,
name,
format,
features.reasoning,
features.validation,
features.text_search,
features.vector_search,
);
Ok(config)
}