horned_bin/
lib.rs

1//! Support for Horned command line programmes
2
3use horned_owl::{
4    error::HornedError,
5    io::{ParserConfiguration, ParserOutput, ResourceType},
6    model::{Build, ForIRI, IRI, MutableOntology, OntologyID, RcAnnotatedComponent, RcStr},
7    ontology::{
8        component_mapped::{ComponentMappedOntology, RcComponentMappedOntology},
9        indexed::ForIndex,
10        set::SetOntology,
11    },
12    resolve::{localize_iri_favored, path_to_file_iri, strict_resolve_iri},
13};
14
15use std::{
16    fs::File,
17    io::{BufReader, Write as StdWrite},
18    path::{Path, PathBuf},
19    str::FromStr,
20};
21
22pub mod error {
23    use super::*;
24
25    pub fn error_missing_input() -> HornedError {
26        HornedError::CommandError("Command requires an INPUT parameter".to_string())
27    }
28}
29
30pub fn write<A: ForIRI, AA: ForIndex<A>, W: StdWrite>(
31    format: &str,
32    write: W,
33    ont: &ComponentMappedOntology<A, AA>,
34) -> Result<W, HornedError> {
35    match format {
36        "owx" => horned_owl::io::owx::writer::write(write, ont, None),
37        "ofn" => horned_owl::io::ofn::writer::write(write, ont, None),
38        "owl" | "ttl" => horned_owl::io::rdf::writer::write_to_rdf_format(write, ont, format),
39
40        _ => Err(HornedError::CommandError(format!(
41            "Format is unknown: {format}"
42        ))),
43    }
44}
45
46pub fn path_type(path: &Path) -> Option<ResourceType> {
47    match path.extension().and_then(|s| s.to_str()) {
48        Some("ofn") => Some(ResourceType::OFN),
49        Some("owx") => Some(ResourceType::OWX),
50        Some("owl") => Some(ResourceType::RDF),
51        _ => None,
52    }
53}
54
55pub fn parse_path(
56    path: &Path,
57    config: ParserConfiguration,
58) -> Result<ParserOutput<RcStr, RcAnnotatedComponent>, HornedError> {
59    Ok(match path_type(path) {
60        Some(ResourceType::OFN) => {
61            let file = File::open(path)?;
62            let mut bufreader = BufReader::new(file);
63            ParserOutput::ofn(horned_owl::io::ofn::reader::read(&mut bufreader, config)?)
64        }
65        Some(ResourceType::OWX) => {
66            let file = File::open(path)?;
67            let mut bufreader = BufReader::new(file);
68            ParserOutput::owx(horned_owl::io::owx::reader::read(&mut bufreader, config)?)
69        }
70        Some(ResourceType::RDF) => {
71            let b = Build::new();
72            let iri = horned_owl::resolve::path_to_file_iri(&b, path);
73            ParserOutput::rdf(horned_owl::io::rdf::closure_reader::read(&iri, config)?)
74        }
75        None => {
76            return Err(HornedError::CommandError(format!(
77                "Cannot parse a file of this format: {path:?}"
78            )));
79        }
80    })
81}
82
83/// Parse but only as far as the imports, if that makes sense.
84pub fn parse_imports(
85    path: &Path,
86    config: ParserConfiguration,
87) -> Result<ParserOutput<RcStr, RcAnnotatedComponent>, HornedError> {
88    let file = File::open(path)?;
89    let mut bufreader = BufReader::new(file);
90    Ok(match path_type(path) {
91        Some(ResourceType::OFN) => {
92            ParserOutput::ofn(horned_owl::io::owx::reader::read(&mut bufreader, config)?)
93        }
94        Some(ResourceType::OWX) => {
95            ParserOutput::owx(horned_owl::io::owx::reader::read(&mut bufreader, config)?)
96        }
97        Some(ResourceType::RDF) => {
98            let b = Build::new();
99            let mut p = horned_owl::io::rdf::reader::parser_with_build(&mut bufreader, &b, config);
100            p.parse_imports()?;
101            ParserOutput::rdf(p.as_ontology_and_incomplete())
102        }
103        None => {
104            return Err(HornedError::CommandError(format!(
105                "Cannot parse a file of this format: {path:?}"
106            )));
107        }
108    })
109}
110
111pub fn materialize(
112    file_or_iri: &str,
113    config: ParserConfiguration,
114) -> Result<Vec<IRI<RcStr>>, HornedError> {
115    let mut v = vec![];
116    let b = Build::new();
117
118    // We need to determine at this point whether we have an IRI or a file location, already.
119    let parsed = oxiri::Iri::parse(file_or_iri);
120
121    // If it is an IRI then we need to run ensure_local on it to bring it local
122    // If it is a file location, then we just turn it into a path buf
123    // Can we just do this with parse_iri method from OxIri?
124
125    let file_pathbuf = match parsed {
126        Result::Ok(_) => ensure_local(&b.iri(file_or_iri), None)?,
127        Result::Err(_) => PathBuf::from_str(file_or_iri).expect("Result is infallable"),
128    };
129
130    materialize_1(&file_pathbuf, config, &mut v, true)?;
131    Ok(v)
132}
133
134fn ensure_local(
135    iri: &IRI<RcStr>,
136    relative_doc_iri: Option<&IRI<RcStr>>,
137) -> Result<PathBuf, HornedError> {
138    let local_path = localize_iri_favored(iri, relative_doc_iri);
139
140    if !local_path.exists() {
141        println!("Retrieving Ontology: {}", iri);
142        let imported_data = strict_resolve_iri(iri)?;
143        println!("Saving to {}", local_path.display());
144        let mut file = File::create(&local_path)?;
145        file.write_all(imported_data.as_bytes())?;
146    } else {
147        println!("Already Present: {}", local_path.display());
148    }
149    Ok(local_path)
150}
151
152fn materialize_1<'a>(
153    file_location: &PathBuf,
154    config: ParserConfiguration,
155    done: &'a mut Vec<IRI<RcStr>>,
156    recurse: bool,
157) -> Result<&'a mut Vec<IRI<RcStr>>, HornedError> {
158    println!("Parsing: {}", file_location.display());
159    let amont: RcComponentMappedOntology = parse_imports(Path::new(file_location), config)?.into();
160    let import = amont.i().import();
161
162    let b = Build::new_rc();
163    let doc_iri = path_to_file_iri(&b, file_location.as_path());
164    // Get all the imports
165    for i in import {
166        if !done.contains(&i.0) {
167            done.push(i.0.clone());
168            let local_path = ensure_local(&i.0, Some(&doc_iri))?;
169
170            if recurse {
171                materialize_1(&local_path, config, done, true)?;
172            }
173        } else {
174            println!("Already materialized: {}", &i.0);
175        }
176    }
177
178    Ok(done)
179}
180
181pub fn generate_big_owl<W: StdWrite>(size: isize, format: &str, w: W) -> Result<W, HornedError> {
182    let b = Build::new_rc();
183    let mut o = SetOntology::new_rc();
184
185    o.insert(OntologyID {
186        iri: Some(b.iri("http://www.example.com/iri")),
187        viri: None,
188    });
189
190    for i in 1..size + 1 {
191        o.declare(b.class(format!("https://www.example.com/o{}", i)));
192    }
193
194    let amo: RcComponentMappedOntology = o.into();
195    write(format, w, &amo)
196}
197
198pub mod naming {
199    use horned_owl::model::ComponentKind;
200    use horned_owl::model::ComponentKind::*;
201
202    pub fn name(axk: &ComponentKind) -> &'static str {
203        match axk {
204            OntologyID => "Ontology ID",
205            DocIRI => "Doc IRI",
206            OntologyAnnotation => "Ontology Annotation",
207            Import => "Import",
208            DeclareClass => "Declare Class",
209            DeclareObjectProperty => "Declare Object Property",
210            DeclareAnnotationProperty => "Declare Annotation Property",
211            DeclareDataProperty => "Declare Data Property",
212            DeclareNamedIndividual => "Declare Named Individual",
213            DeclareDatatype => "Declare Datatype",
214            SubClassOf => "Sub-Class Of",
215            EquivalentClasses => "Equivalent Classes",
216            DisjointClasses => "Disjoint Classes",
217            DisjointUnion => "Disjoint Union",
218            SubObjectPropertyOf => "Sub Object Property Of",
219            EquivalentObjectProperties => "Equivalent Object Properties",
220            DisjointObjectProperties => "Disjoint Object Properties",
221            InverseObjectProperties => "Inverse Object Properties",
222            ObjectPropertyDomain => "Object Property Domain",
223            ObjectPropertyRange => "Object Property Range",
224            FunctionalObjectProperty => "Functional Object Property",
225            InverseFunctionalObjectProperty => "Inverse Functional Object Property",
226            ReflexiveObjectProperty => "Reflexive Object Property",
227            IrreflexiveObjectProperty => "Irreflexive Object Property",
228            SymmetricObjectProperty => "Symmetric Object Property",
229            AsymmetricObjectProperty => "Asymmetric Object Property",
230            TransitiveObjectProperty => "Transitive Object Property",
231            SubDataPropertyOf => "Sub Data Property Of",
232            EquivalentDataProperties => "Equivalent Data Properties",
233            DisjointDataProperties => "Disjoint Data Properties",
234            DataPropertyDomain => "Data Property Domain",
235            DataPropertyRange => "Data Property Range",
236            FunctionalDataProperty => "Functional Data Property",
237            DatatypeDefinition => "Datatype Definition",
238            HasKey => "Has Key",
239            SameIndividual => "Same Individual",
240            DifferentIndividuals => "Different Individuals",
241            ClassAssertion => "Class Assertion",
242            ObjectPropertyAssertion => "Object Property Assertion",
243            NegativeObjectPropertyAssertion => "Negative Object Property Assertion",
244            DataPropertyAssertion => "Data Property Assertion",
245            NegativeDataPropertyAssertion => "Negative Data Property Assertion",
246            AnnotationAssertion => "Annotation Assertion",
247            SubAnnotationPropertyOf => "Sub Annotation Property Of",
248            AnnotationPropertyDomain => "Annotation Property Domain",
249            AnnotationPropertyRange => "Annotation Property Range",
250            Rule => "Rule",
251        }
252    }
253}
254
255pub mod validation {
256    use horned_owl::{io::rdf::reader::IncompleteParse, model::ForIRI};
257
258    pub fn write_incomplete<T: ForIRI>(incomplete: IncompleteParse<T>) {
259        println!("\n\nIncompleted Parsed");
260        println!("\tSimple Triples: {:#?}", incomplete.simple);
261        println!("\tbnode: {:#?}", incomplete.bnode);
262        println!("\tsequences: {:#?}", incomplete.bnode_seq);
263        println!("\tClass Expressions: {:#?}", incomplete.class_expression);
264        println!(
265            "\tObject Property Expressions: {:#?}",
266            incomplete.object_property_expression
267        );
268        println!("\tData Range: {:#?}", incomplete.data_range);
269        println!("\tAnnotations: {:#?}", incomplete.ann_map);
270    }
271}
272
273pub mod summary {
274
275    use horned_owl::{
276        model::{ComponentKind, HigherKinded},
277        ontology::component_mapped::RcComponentMappedOntology,
278    };
279    use indexmap::map::IndexMap;
280
281    #[derive(Debug)]
282    pub struct SummaryStatistics {
283        pub logical_axiom: usize,
284        pub annotation_axiom: usize,
285        pub meta_comp: usize,
286        pub axiom_type: IndexMap<ComponentKind, usize>,
287    }
288
289    impl SummaryStatistics {
290        pub fn with_axiom_types(&self) -> impl Iterator<Item = (&ComponentKind, &usize)> + '_ {
291            self.axiom_type.iter().filter(|&(_, v)| v > &0)
292        }
293    }
294
295    pub fn summarize<O: Into<RcComponentMappedOntology>>(ont: O) -> SummaryStatistics
296    where
297        O:,
298    {
299        let ont: RcComponentMappedOntology = ont.into();
300        SummaryStatistics {
301            logical_axiom: ont.i().iter().filter(|c| c.is_axiom()).count(),
302            annotation_axiom: ont.i().iter().map(|aa| aa.ann.len()).sum::<usize>(),
303            meta_comp: ont.i().iter().filter(|c| c.is_meta()).count(),
304            axiom_type: axiom_types(ont),
305        }
306    }
307
308    fn axiom_types<O: Into<RcComponentMappedOntology>>(ont: O) -> IndexMap<ComponentKind, usize> {
309        let ont: RcComponentMappedOntology = ont.into();
310        let mut im = IndexMap::new();
311        for ax in ComponentKind::all_kinds() {
312            im.insert(ax, ont.i().component(ax).count());
313        }
314
315        im
316    }
317}
318
319pub mod config {
320    use clap::App;
321    use clap::ArgAction;
322    use clap::ArgMatches;
323    use horned_owl::io::ParserConfiguration;
324    use horned_owl::io::RDFParserConfiguration;
325
326    pub fn parser_app(app: App<'static>) -> App<'static> {
327        app.arg(
328            clap::arg!(--"lax")
329                .required(false)
330                .action(ArgAction::SetTrue)
331                .help("Parse RDF in a lax manner"),
332        )
333    }
334
335    pub fn parser_config(matches: &ArgMatches) -> ParserConfiguration {
336        ParserConfiguration {
337            rdf: RDFParserConfiguration {
338                lax: *matches.get_one::<bool>("lax").unwrap_or(&false),
339                format: None,
340            },
341            ..Default::default()
342        }
343    }
344}