hasse 0.2.1

Hasse: utilities for working with partially ordered sets (posets) and Hasse diagrams.
Documentation
mod graph;
mod gui;

use anyhow::{Context, Result};
use graph::Graph;
use std::io::{self, BufRead, Write};

fn prompt(msg: &str) -> Result<String> {
    print!("{} ", msg);
    io::stdout().flush()?;
    
    let stdin = io::stdin();
    let mut line = String::new();
    stdin.lock().read_line(&mut line)?;
    
    Ok(line.trim().to_string())
}

fn parse_number<T: std::str::FromStr>(prompt_msg: &str) -> Result<T>
where
    T::Err: std::error::Error + Send + Sync + 'static,
{
    loop {
        let input = prompt(prompt_msg)?;
        match input.parse::<T>() {
            Ok(n) => return Ok(n),
            Err(e) => {
                eprintln!("Invalid input: {}. Please try again.", e);
            }
        }
    }
}

fn parse_edge(input: &str) -> Result<(usize, usize)> {
    let parts: Vec<&str> = input.split_whitespace().collect();
    
    if parts.len() != 2 {
        anyhow::bail!("Expected two numbers separated by space");
    }
    
    let u = parts[0].parse::<usize>()
        .context("Invalid first vertex")?;
    let v = parts[1].parse::<usize>()
        .context("Invalid second vertex")?;
    
    Ok((u, v))
}

fn read_graph_from_input() -> Result<Graph> {
    println!("\n=== Graph Input ===");
    
    let n: usize = parse_number("Enter number of vertices:")?;
    let m: usize = parse_number("Enter number of edges:")?;
    
    let directed_input = prompt("Is graph directed? (Y/N):")?;
    let directed = matches!(directed_input.to_uppercase().as_str(), "Y" | "YES");
    
    let mut graph = Graph::new(n, directed);
    
    println!("\nEnter edges as 'u v' (vertex indices from 0 to {}):", n - 1);
    for i in 0..m {
        loop {
            let input = prompt(&format!("Edge {} of {}:", i + 1, m))?;
            match parse_edge(&input) {
                Ok((u, v)) => {
                    if u >= n || v >= n {
                        eprintln!("Vertices must be in range [0, {})", n);
                        continue;
                    }
                    graph.add_edge(u, v);
                    break;
                }
                Err(e) => {
                    eprintln!("Error: {}. Please try again.", e);
                }
            }
        }
    }
    
    Ok(graph)
}

fn print_menu() {
    println!("\n=== Hasse Diagram Tool ===");
    println!("1. Show graph information");
    println!("2. Compute transitive reduction (Hasse diagram)");
    println!("3. Compute transitive closure");
    println!("4. Show topological sort");
    println!("5. Display graph (GUI)");
    println!("6. Exit");
    print!("Choose an option: ");
    io::stdout().flush().unwrap();
}

fn show_graph_info(graph: &Graph) {
    println!("\n=== Graph Information ===");
    println!("Number of vertices: {}", graph.num_vertices());
    println!("Number of edges: {}", graph.num_edges());
    println!("Directed: {}", graph.directed);
    
    graph.print_adjacency_list();
    
    let sources = graph.get_sources();
    let sinks = graph.get_sinks();
    
    if !sources.is_empty() {
        println!("\nSource vertices (no incoming edges): {:?}", sources);
    }
    if !sinks.is_empty() {
        println!("Sink vertices (no outgoing edges): {:?}", sinks);
    }
}

fn main() -> Result<()> {
    println!("Welcome to Hasse Diagram Visualization Tool!");
    println!("============================================\n");
    
    let mut graph = read_graph_from_input()?;
    
    loop {
        print_menu();
        
        let choice = prompt("")?;
        
        match choice.trim() {
            "1" => {
                show_graph_info(&graph);
            }
            "2" => {
                if !graph.directed {
                    eprintln!("Error: Transitive reduction only works on directed graphs.");
                    continue;
                }
                
                println!("\nComputing transitive reduction...");
                let reduced = graph.transitive_reduction();
                
                println!("\n=== Hasse Diagram (Transitive Reduction) ===");
                println!("Original edges: {}", graph.num_edges());
                println!("Reduced edges: {}", reduced.num_edges());
                reduced.print_adjacency_list();
                
                let replace = prompt("\nReplace current graph with reduction? (Y/N):")?;
                if matches!(replace.to_uppercase().as_str(), "Y" | "YES") {
                    graph = reduced;
                    println!("Graph replaced with transitive reduction.");
                }
            }
            "3" => {
                println!("\nComputing transitive closure...");
                let closure = graph.transitive_closure();
                
                println!("\n=== Transitive Closure ===");
                println!("Original edges: {}", graph.num_edges());
                println!("Closure edges: {}", closure.num_edges());
                closure.print_adjacency_list();
                
                let replace = prompt("\nReplace current graph with closure? (Y/N):")?;
                if matches!(replace.to_uppercase().as_str(), "Y" | "YES") {
                    graph = closure;
                    println!("Graph replaced with transitive closure.");
                }
            }
            "4" => {
                if !graph.directed {
                    eprintln!("Error: Topological sort only works on directed graphs.");
                    continue;
                }
                
                match graph.topological_sort() {
                    Some(order) => {
                        println!("\n=== Topological Sort ===");
                        println!("Order: {:?}", order);
                    }
                    None => {
                        eprintln!("Error: Graph contains a cycle, cannot perform topological sort.");
                    }
                }
            }
            "5" => {
                println!("\nOpening GUI window...");
                gui::show(&graph)?;
            }
            "6" => {
                println!("\nGoodbye!");
                break;
            }
            _ => {
                eprintln!("Invalid choice. Please select 1-6.");
            }
        }
    }
    
    Ok(())
}