owl2shacl 0.2.0

A CLI tool that tries to convert simple OWL ontologies into SHACL shapes. OWL ontologies define logical relationships. SHACL shapes define a data scheme, and allow to validate data against them. Strictly speaking, as these are different things, such a conversion is thus illegal/wrong in the ideological/theoretical sense. Thus this tool is not generally applicable, but only under the circumstance, that the OWL ontology is actually written as a data specification - if it is understood as a kind of distributed database schema, rather then for logical inference. Not only that, but it also has to conform to certain, very narrow rules, and only a few basic properties are translated into SHACL; the rest is ignored.
// SPDX-FileCopyrightText: 2021 - 2024 Robin Vobruba <hoijui.quaero@gmail.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

use std::path::PathBuf;

use clap::{command, value_parser, Arg, ArgAction, Command, ValueHint};
use cli_utils::BoxResult;
use const_format::formatcp;

use crate::config::{Config, Settings};

pub const A_S_VERSION: char = 'V';
pub const A_L_VERSION: &str = "version";
pub const A_S_QUIET: char = 'q';
pub const A_L_QUIET: &str = "quiet";
pub const A_S_VERBOSE: char = 'v';
pub const A_L_VERBOSE: &str = "verbose";
pub const A_S_BASE_IRI: char = 'b';
pub const A_L_BASE_IRI: &str = "base-iri";
pub const A_L_OWL_FILE: &str = "owl-file";
pub const A_L_SHACL_FILE: &str = "shacl-file";
pub const A_L_PREFIXES_FILE: &str = "prefixes-file";

fn arg_version() -> Arg {
    Arg::new(A_L_VERSION)
        .help(formatcp!(
            "Print version information and exit. \
May be combined with -{A_S_QUIET},--{A_L_QUIET}, \
to really only output the version string."
        ))
        .short(A_S_VERSION)
        .long(A_L_VERSION)
        .action(ArgAction::SetTrue)
}

fn arg_quiet() -> Arg {
    Arg::new(A_L_QUIET)
        .help("Minimize or suppress output to stderr")
        .long_help("Minimize or suppress output to stderr; stdout is never used by this program, with or without this option set.")
        .action(ArgAction::SetTrue)
        .short(A_S_QUIET)
        .long(A_L_QUIET)
        .conflicts_with(A_L_VERBOSE)
}

fn arg_verbose() -> Arg {
    Arg::new(A_L_VERBOSE)
        .help("more verbose output (useful for debugging)")
        .short(A_S_VERBOSE)
        .long(A_L_VERBOSE)
        .action(ArgAction::SetTrue)
}

fn arg_base_iri() -> Arg {
    Arg::new(A_L_BASE_IRI)
        .help("Sets the OWL ontologies base-IRI")
        .short(A_S_BASE_IRI)
        .long(A_L_BASE_IRI)
        .action(ArgAction::Set)
}

fn arg_owl_file() -> Arg {
    Arg::new(A_L_OWL_FILE)
        .help("The OWL input file")
        .action(ArgAction::Set)
        .value_parser(value_parser!(std::path::PathBuf))
        .value_hint(ValueHint::FilePath)
        .value_name("OWL_FILE")
        .required(true)
}

fn arg_shacl_file() -> Arg {
    Arg::new(A_L_SHACL_FILE)
        .help("The SHACL output file")
        .action(ArgAction::Set)
        .value_parser(value_parser!(std::path::PathBuf))
        .value_hint(ValueHint::FilePath)
        .value_name("SHACL_FILE")
}

fn arg_prefixes_file() -> Arg {
    Arg::new(A_L_PREFIXES_FILE)
        .help("The prefixes output file")
        .action(ArgAction::Set)
        .value_parser(value_parser!(std::path::PathBuf))
        .value_hint(ValueHint::FilePath)
        .value_name("PREFIXES_FILE")
}

pub fn args_matcher() -> Command {
    command!()
        .about(clap::crate_description!())
        .bin_name(clap::crate_name!())
        .help_expected(true)
        .disable_version_flag(true)
        .arg(arg_version())
        .arg(arg_quiet())
        .arg(arg_verbose())
        .arg(arg_base_iri())
        .arg(arg_owl_file())
        .arg(arg_shacl_file())
        .arg(arg_prefixes_file())
}

#[allow(clippy::print_stdout)]
fn print_version_and_exit(quiet: bool) {
    if !quiet {
        print!("{} ", clap::crate_name!());
    }
    println!("{}", crate::VERSION);
    std::process::exit(0);
}

#[derive(Clone, Debug)]
pub struct Args {
    pub quiet: bool,
    pub verbose: bool,
    pub config: Config,
}

pub fn parse() -> BoxResult<Args> {
    let args = args_matcher().get_matches();

    let quiet = args.get_flag(A_L_QUIET);
    let version = args.get_flag(A_L_VERSION);
    if version {
        print_version_and_exit(quiet);
    }

    let verbose = args.get_flag(A_L_VERBOSE);
    let base_iri = args.get_one::<String>(A_L_BASE_IRI).cloned();
    let owl = args
        .get_one::<PathBuf>(A_L_OWL_FILE)
        .cloned()
        .expect("OWL input file is required");
    let shacl = args.get_one::<PathBuf>(A_L_SHACL_FILE).cloned();
    let prefixes = args.get_one::<PathBuf>(A_L_PREFIXES_FILE).cloned();

    let settings = Settings::default();

    let parsed_args = Args {
        quiet,
        verbose,
        config: Config {
            settings,
            base_iri,
            owl,
            shacl,
            prefixes,
        },
    };

    Ok(parsed_args)
}