oxirs 0.3.1

Command-line interface for OxiRS - import, export, migration, and benchmarking tools
Documentation
//! Enhanced CLI Infrastructure
//!
//! Provides advanced argument validation, interactive mode support,
//! progress tracking, and improved user experience features.

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,
};

/// CLI Context for managing global state
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,
        }
    }

    /// Initialize from CLI arguments
    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
    }

    /// Check if we should show output
    pub fn should_show_output(&self) -> bool {
        !self.quiet
    }

    /// Check if we should show verbose output
    pub fn should_show_verbose(&self) -> bool {
        self.verbose && !self.quiet
    }

    /// Start a progress operation
    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)
    }

    /// Print an info message
    pub fn info(&self, message: &str) {
        if self.should_show_output() {
            self.output_formatter.info(message);
        }
    }

    /// Print a success message
    pub fn success(&self, message: &str) {
        if self.should_show_output() {
            self.output_formatter.success(message);
        }
    }

    /// Print a warning message
    pub fn warn(&self, message: &str) {
        if self.should_show_output() {
            self.output_formatter.warn(message);
        }
    }

    /// Print an error message
    pub fn error(&self, message: &str) {
        self.output_formatter.error(message);
    }

    /// Print verbose output
    pub fn verbose(&self, message: &str) {
        if self.should_show_verbose() {
            self.output_formatter.verbose(message);
        }
    }

    /// Prompt user for confirmation (returns true if yes)
    pub fn confirm(&self, prompt: &str) -> io::Result<bool> {
        if !self.interactive {
            return Ok(true); // Non-interactive mode assumes yes
        }

        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")
    }

    /// Prompt user for input
    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()
    }
}

/// Command suggestions
pub mod suggestions {
    use strsim::levenshtein;

    /// Find similar commands for suggestions
    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()
    }

    /// Suggest commands based on user input
    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"));
    }
}