use indicatif::{ProgressBar, ProgressStyle};
use std::io::{self, Write};
use std::time::Duration;
pub mod alias;
pub mod ascii_diagram;
pub mod checkpoint;
pub mod completion;
pub mod dataset_manager;
pub mod doc_generator;
pub mod error;
pub mod error_suggestions;
pub mod formatters;
pub mod fuzzy_history;
pub mod graphviz_export;
pub mod help;
pub mod interactive;
pub mod logging;
pub mod output;
pub mod pagination;
pub mod progress;
pub mod query_bookmarks;
pub mod result_export;
pub mod schema_autocomplete;
pub mod sparql_autocomplete;
pub mod syntax_highlighting;
pub mod template_formatter;
pub mod transaction;
pub mod tutorial;
pub mod utils;
pub mod validation;
pub mod visual_query_builder;
pub use alias::{AliasConfig, AliasManager};
pub use ascii_diagram::{AsciiDiagramGenerator, DiagramConfig, DiagramTriple, LayoutStyle};
pub use checkpoint::{Checkpoint, CheckpointManager};
pub use completion::{CommandCompletionProvider, CompletionContext, CompletionProvider};
pub use dataset_manager::{
ConnectionState, DatasetConnection, DatasetManager, DatasetManagerConfig, DatasetManagerStats,
};
pub use doc_generator::{ArgumentDoc, CommandDoc, DocFormat, DocGenerator, ExampleDoc, OptionDoc};
pub use error::{CliError, CliResult};
pub use error_suggestions::{enhance_error, enhanced_error_from_message};
#[cfg(feature = "pdf-export")]
pub use formatters::PdfFormatter;
pub use formatters::{
create_formatter, Binding, CsvFormatter, HtmlFormatter, JsonFormatter, MarkdownFormatter,
QueryResults, RdfTerm, ResultFormatter, TableFormatter, XmlFormatter,
};
pub use fuzzy_history::{FuzzyConfig, FuzzyHistorySearch, FuzzyMatch, HistoryEntryWithMetadata};
pub use graphviz_export::{
GraphOptions, GraphvizExporter, LayoutEngine, NodeShape, NodeStyle, PlanNode, PlanOptions,
QueryPlanExporter,
};
pub use help::{HelpCategory, HelpProvider};
#[allow(deprecated)]
pub use interactive::InteractiveMode;
pub use logging::{
init_logging, CommandLogger, DataLogger, LogConfig, LogFormat, PerfLogger, QueryLogger,
};
pub use output::{ColorScheme, OutputFormatter};
pub use pagination::{NavigationCommand, PaginationConfig, ResultPaginator};
pub use progress::{ProgressTracker, ProgressType};
pub use query_bookmarks::{BookmarkConfig, BookmarkManager, QueryBookmark};
pub use result_export::{ExportConfig, ExportFormat, ResultExporter};
pub use schema_autocomplete::{
CacheStats, SchemaAutocompleteProvider, SchemaDiscoveryConfig, SchemaInfo,
};
pub use sparql_autocomplete::SparqlAutocompleteProvider;
pub use syntax_highlighting::{
highlight_error, highlight_info, highlight_sparql, highlight_success, highlight_warning,
HighlightConfig,
};
pub use template_formatter::{TemplateFormatter, TemplatePresets};
pub use transaction::{
IsolationLevel, TransactionConfig, TransactionManager, TransactionMetadata,
TransactionOperation, TransactionState, TransactionStats,
};
pub use tutorial::{Difficulty, TutorialLesson, TutorialManager, TutorialStep};
pub use utils::*;
pub use validation::ArgumentValidator;
pub use visual_query_builder::{
FilterExpression, OptionalClause, OrderByClause, QueryBuilderConfig, QueryBuilderStats,
QueryType, TriplePattern, VisualQueryBuilder,
};
pub struct CliContext {
pub verbose: bool,
pub quiet: bool,
pub no_color: bool,
pub interactive: bool,
pub profile: Option<String>,
pub output_formatter: OutputFormatter,
pub progress_tracker: Option<ProgressTracker>,
}
impl CliContext {
pub fn new() -> Self {
use std::io::IsTerminal;
let no_color = std::env::var("NO_COLOR").is_ok() || !std::io::stdout().is_terminal();
Self {
verbose: false,
quiet: false,
no_color,
interactive: false,
profile: None,
output_formatter: OutputFormatter::new_with_color(no_color),
progress_tracker: None,
}
}
pub fn from_cli(verbose: bool, quiet: bool, no_color: bool) -> Self {
let mut ctx = Self::new();
ctx.verbose = verbose;
ctx.quiet = quiet;
ctx.no_color = no_color || ctx.no_color;
ctx.output_formatter = OutputFormatter::new_with_color(ctx.no_color);
ctx
}
pub fn should_show_output(&self) -> bool {
!self.quiet
}
pub fn should_show_verbose(&self) -> bool {
self.verbose && !self.quiet
}
pub fn start_progress(&mut self, total: Option<u64>, message: &str) -> Option<ProgressBar> {
if self.quiet || !self.should_show_output() {
return None;
}
let pb = match total {
Some(len) => {
let pb = ProgressBar::new(len);
pb.set_style(
ProgressStyle::default_bar()
.template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
.expect("progress bar template should be valid")
.progress_chars("=>-"),
);
pb
}
None => {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {msg}")
.expect("progress bar template should be valid"),
);
pb.enable_steady_tick(Duration::from_millis(100));
pb
}
};
pb.set_message(message.to_string());
Some(pb)
}
pub fn info(&self, message: &str) {
if self.should_show_output() {
self.output_formatter.info(message);
}
}
pub fn success(&self, message: &str) {
if self.should_show_output() {
self.output_formatter.success(message);
}
}
pub fn warn(&self, message: &str) {
if self.should_show_output() {
self.output_formatter.warn(message);
}
}
pub fn error(&self, message: &str) {
self.output_formatter.error(message);
}
pub fn verbose(&self, message: &str) {
if self.should_show_verbose() {
self.output_formatter.verbose(message);
}
}
pub fn confirm(&self, prompt: &str) -> io::Result<bool> {
if !self.interactive {
return Ok(true); }
print!("{prompt} [y/N]: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
Ok(input.trim().to_lowercase() == "y" || input.trim().to_lowercase() == "yes")
}
pub fn prompt(&self, prompt: &str) -> io::Result<String> {
print!("{prompt}: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
}
impl Default for CliContext {
fn default() -> Self {
Self::new()
}
}
pub mod suggestions {
use strsim::levenshtein;
pub fn find_similar_commands(input: &str, commands: &[&str], threshold: usize) -> Vec<String> {
let mut suggestions: Vec<(String, usize)> = commands
.iter()
.map(|&cmd| (cmd.to_string(), levenshtein(input, cmd)))
.filter(|(_, distance)| *distance <= threshold)
.collect();
suggestions.sort_by_key(|(_, distance)| *distance);
suggestions.into_iter().map(|(cmd, _)| cmd).collect()
}
pub fn suggest_command(input: &str, commands: &[&str]) -> Option<String> {
let similar = find_similar_commands(input, commands, 3);
if !similar.is_empty() {
Some(format!("Did you mean: {}?", similar.join(", ")))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_context_creation() {
let ctx = CliContext::new();
assert!(!ctx.verbose);
assert!(!ctx.quiet);
assert!(!ctx.interactive);
}
#[test]
fn test_output_control() {
let mut ctx = CliContext::new();
assert!(ctx.should_show_output());
ctx.quiet = true;
assert!(!ctx.should_show_output());
assert!(!ctx.should_show_verbose());
ctx.quiet = false;
ctx.verbose = true;
assert!(ctx.should_show_verbose());
}
#[test]
fn test_command_suggestions() {
use suggestions::*;
let commands = vec!["query", "update", "import", "export"];
let similar = find_similar_commands("qeury", &commands, 3);
assert_eq!(similar, vec!["query"]);
let suggestion = suggest_command("improt", &commands);
assert!(suggestion.is_some());
assert!(suggestion.unwrap().contains("import"));
}
}