graphlang 0.1.3

Terminal and graphical tool to create and explore graph grammars.
Documentation
use clap::Parser;
use std::{num::NonZeroU64, path::PathBuf, process::exit};

#[allow(unused_imports)]
use graphlang::{
    match_subgraph, Edge, Graph, GraphGrammar, Isomorphism, Node, Production, QuitCondition,
};

#[derive(Parser, Debug)]
#[clap(author, version, about)]
struct Args {
    /// Either a JSON file of a graph grammar or the name of a predefined grammar.
    /// Possible predefined grammars are:
    /// String, LadderN;
    /// Where N is a positive Integer >= 1.
    input: PathBuf,
    /// Optional output path. If given a dot-file of the graph is generated.
    #[clap(short, long)]
    output: Option<PathBuf>,

    /// (*) Maximal number of productions. Example: `100`
    #[clap(short, long)]
    max: Option<NonZeroU64>,
    /// (*) Maximal runtime for graph evolution. Example: `1h 2min 3s 4ns`
    #[clap(short, long)]
    timeout: Option<String>,
    /// (*) Quit evolution as soon as the graph is valid w.r.t. the grammar
    #[clap(short, long)]
    asap: bool,

    /// Enable additional output
    #[clap(short, long)]
    verbose: bool,
}

fn error(errormsg: &str) -> ! {
    eprintln!("{}", errormsg);
    exit(1);
}

fn main() {
    pretty_env_logger::init_timed();
    eprintln!(
        r"NOTE: This application is far from finished or even usable. 
      Currently it always applies 5x a production called 'extend' and 
      after that 'finalize'. It also only supports graph grammars without
      edge labels and edges are assumed to be bidirectional.
      "
    );
    let args = Args::parse();

    // Buffer of the contents, if applicable s.t. the read graphgrammar can
    // hold references into it & does not have to hold owning strings, but can
    // work with references.
    #[allow(unused_assignments)]
    let mut file_contents = String::new();

    let gg = {
        use graphlang::predefined;
        if args.input.starts_with("Ladder") {
            let s = args
                .input
                .to_str()
                .unwrap_or_else(|| error("Ladder{integer} contains invalid unicode symbols"));
            let n = s[6..].parse().expect("Invalid integer after Ladder");
            predefined::ladder_grammar(n)
        } else if args.input == PathBuf::from("String") {
            predefined::string_grammar()
        } else {
            file_contents = std::fs::read_to_string(&args.input)
                .unwrap_or_else(|_| error("Input file can't be opened"));
            serde_json::from_str(&file_contents) //
                .unwrap_or_else(|_| error("Deserialize json"))
        }
    };

    let quitcondition = {
        let mut cond = QuitCondition::new();
        if args.asap {
            cond = cond.on_first_valid_graph();
        }
        if let Some(timeout) = args.timeout {
            let timeout = timeout
                .parse::<humantime::Duration>()
                .expect("Invalid duration given");
            cond = cond.add_timeout(timeout.into());
        }
        if let Some(max) = args.max {
            cond = cond.add_max_productions(max);
        }
        if !cond.is_valid() {
            error("At least one of the starred options has to be used!");
        }
        cond
    };

    // TODO: Random evolution using quitcondition
    let g = {
        let _ = quitcondition;

        let mut g = gg.start_graph.clone();
        println!("----> {:?}", &g);
        for n in 0..5 {
            gg.productions["extend"].apply_inplace(&mut g).unwrap();
            println!("{n:4}: {:?}", &g.nodes);
            println!("      {:?}", &g.edges);
        }
        gg.productions["finalize"].apply_inplace(&mut g).unwrap();
        println!(" fin: {:?}", &g.nodes);
        println!("      {:?}", &g.edges);
        g
    };

    if let Some(ref out) = &args.output {
        let mut out = out.clone();
        out.set_extension("dot");

        println!("\nWriting graph to {}", out.to_string_lossy());
        let mut f = std::fs::File::create(out) //
            .expect("Output path is accesible");
        g.write_dot_to(&mut f)
            .expect("Output path can be written to");
    }
}