@@ -10,6 +10,7 @@ use oxigraph::model::NamedNode;
1010use serde_json;
1111use std:: env:: current_dir;
1212use std:: path:: PathBuf ;
13+ use std:: collections:: { BTreeMap , BTreeSet } ;
1314
1415#[ derive( Debug , Parser ) ]
1516#[ command( name = "ontoenv" ) ]
@@ -49,7 +50,7 @@ struct Cli {
4950 #[ clap( long = "no-search" , short = 'n' , action, global = true ) ]
5051 no_search : bool ,
5152 /// Directories to search for ontologies. If not provided, the current directory is used.
52- #[ clap( global = true ) ]
53+ #[ clap( global = true , last = true ) ]
5354 locations : Option < Vec < PathBuf > > ,
5455}
5556
@@ -111,7 +112,11 @@ enum Commands {
111112 /// Prints the version of the ontoenv binary
112113 Version ,
113114 /// Prints the status of the ontology environment
114- Status ,
115+ Status {
116+ /// Output JSON instead of text
117+ #[ clap( long, action, default_value = "false" ) ]
118+ json : bool ,
119+ } ,
115120 /// Update the ontology environment
116121 Update {
117122 /// Suppress per-ontology update output
@@ -120,6 +125,9 @@ enum Commands {
120125 /// Update all ontologies, ignoring modification times
121126 #[ clap( long, short = 'a' , action) ]
122127 all : bool ,
128+ /// Output JSON instead of text
129+ #[ clap( long, action, default_value = "false" ) ]
130+ json : bool ,
123131 } ,
124132 /// Compute the owl:imports closure of an ontology and write it to a file
125133 Closure {
@@ -147,8 +155,14 @@ enum Commands {
147155 no_imports : bool ,
148156 } ,
149157 /// List various properties of the environment
150- #[ command( subcommand) ]
151- List ( ListCommands ) ,
158+ /// List various properties of the environment
159+ List {
160+ #[ command( subcommand) ]
161+ list_cmd : ListCommands ,
162+ /// Output JSON instead of text
163+ #[ clap( long, action, default_value = "false" ) ]
164+ json : bool ,
165+ } ,
152166 // TODO: dump all ontologies; nest by ontology name (sorted), w/n each ontology name list all
153167 // the places where that graph can be found. List basic stats: the metadata field in the
154168 // Ontology struct and # of triples in the graph; last updated; etc
@@ -170,9 +184,16 @@ enum Commands {
170184 Why {
171185 /// The name (URI) of the ontology to find importers for
172186 ontologies : Vec < String > ,
187+ /// Output JSON instead of text
188+ #[ clap( long, action, default_value = "false" ) ]
189+ json : bool ,
173190 } ,
174191 /// Run the doctor to check the environment for issues
175- Doctor ,
192+ Doctor {
193+ /// Output JSON instead of text
194+ #[ clap( long, action, default_value = "false" ) ]
195+ json : bool ,
196+ } ,
176197 /// Reset the ontology environment by removing the .ontoenv directory
177198 Reset {
178199 #[ clap( long, short, action = clap:: ArgAction :: SetTrue , default_value = "false" ) ]
@@ -188,15 +209,15 @@ impl ToString for Commands {
188209 match self {
189210 Commands :: Init { .. } => "Init" . to_string ( ) ,
190211 Commands :: Version => "Version" . to_string ( ) ,
191- Commands :: Status => "Status" . to_string ( ) ,
212+ Commands :: Status { .. } => "Status" . to_string ( ) ,
192213 Commands :: Update { .. } => "Update" . to_string ( ) ,
193214 Commands :: Closure { .. } => "Closure" . to_string ( ) ,
194215 Commands :: Add { .. } => "Add" . to_string ( ) ,
195- Commands :: List ( .. ) => "List" . to_string ( ) ,
216+ Commands :: List { .. } => "List" . to_string ( ) ,
196217 Commands :: Dump { .. } => "Dump" . to_string ( ) ,
197218 Commands :: DepGraph { .. } => "DepGraph" . to_string ( ) ,
198219 Commands :: Why { .. } => "Why" . to_string ( ) ,
199- Commands :: Doctor => "Doctor" . to_string ( ) ,
220+ Commands :: Doctor { .. } => "Doctor" . to_string ( ) ,
200221 Commands :: Reset { .. } => "Reset" . to_string ( ) ,
201222 Commands :: Config { .. } => "Config" . to_string ( ) ,
202223 }
@@ -482,17 +503,49 @@ fn main() -> Result<()> {
482503 env!( "GIT_HASH" )
483504 ) ;
484505 }
485- Commands :: Status => {
506+ Commands :: Status { json } => {
486507 let env = require_ontoenv ( env) ?;
487- // load env from .ontoenv/ontoenv.json
488- let status = env. status ( ) ?;
489- // pretty print the status
490- println ! ( "{status}" ) ;
508+ if json {
509+ // Recompute status details similar to env.status()
510+ let ontoenv_dir = current_dir ( ) ?. join ( ".ontoenv" ) ;
511+ let last_updated = if ontoenv_dir. exists ( ) {
512+ Some ( std:: fs:: metadata ( & ontoenv_dir) ?. modified ( ) ?. into ( ) ) as Option < std:: time:: SystemTime >
513+ } else { None } ;
514+ let size: u64 = if ontoenv_dir. exists ( ) {
515+ walkdir:: WalkDir :: new ( & ontoenv_dir)
516+ . into_iter ( )
517+ . filter_map ( Result :: ok)
518+ . filter ( |e| e. file_type ( ) . is_file ( ) )
519+ . filter_map ( |e| e. metadata ( ) . ok ( ) )
520+ . map ( |m| m. len ( ) )
521+ . sum ( )
522+ } else { 0 } ;
523+ let missing: Vec < String > = env
524+ . missing_imports ( )
525+ . into_iter ( )
526+ . map ( |n| n. to_uri_string ( ) )
527+ . collect ( ) ;
528+ let last_str = last_updated. map ( |t| chrono:: DateTime :: < chrono:: Utc > :: from ( t) . to_rfc3339 ( ) ) ;
529+ let obj = serde_json:: json!( {
530+ "exists" : true ,
531+ "num_ontologies" : env. ontologies( ) . len( ) ,
532+ "last_updated" : last_str,
533+ "store_size_bytes" : size,
534+ "missing_imports" : missing,
535+ } ) ;
536+ println ! ( "{}" , serde_json:: to_string_pretty( & obj) ?) ;
537+ } else {
538+ let status = env. status ( ) ?;
539+ println ! ( "{status}" ) ;
540+ }
491541 }
492- Commands :: Update { quiet, all } => {
542+ Commands :: Update { quiet, all, json } => {
493543 let mut env = require_ontoenv ( env) ?;
494544 let updated = env. update_all ( all) ?;
495- if !quiet {
545+ if json {
546+ let arr: Vec < String > = updated. iter ( ) . map ( |id| id. to_uri_string ( ) ) . collect ( ) ;
547+ println ! ( "{}" , serde_json:: to_string_pretty( & arr) ?) ;
548+ } else if !quiet {
496549 for id in updated {
497550 if let Some ( ont) = env. ontologies ( ) . get ( & id) {
498551 let name = ont. name ( ) . to_string ( ) ;
@@ -546,30 +599,46 @@ fn main() -> Result<()> {
546599 let _ = env. add ( location, true ) ?;
547600 }
548601 }
549- Commands :: List ( list_cmd) => {
602+ Commands :: List { list_cmd, json } => {
550603 let env = require_ontoenv ( env) ?;
551604 match list_cmd {
552605 ListCommands :: Locations => {
553606 let mut locations = env. find_files ( ) ?;
554607 locations. sort_by ( |a, b| a. as_str ( ) . cmp ( b. as_str ( ) ) ) ;
555- for loc in locations {
556- println ! ( "{}" , loc) ;
608+ if json {
609+ println ! ( "{}" , serde_json:: to_string_pretty( & locations) ?) ;
610+ } else {
611+ for loc in locations {
612+ println ! ( "{}" , loc) ;
613+ }
557614 }
558615 }
559616 ListCommands :: Ontologies => {
560617 // print list of ontology URLs from env.ontologies.values() sorted alphabetically
561618 let mut ontologies: Vec < & GraphIdentifier > = env. ontologies ( ) . keys ( ) . collect ( ) ;
562619 ontologies. sort_by ( |a, b| a. name ( ) . cmp ( & b. name ( ) ) ) ;
563620 ontologies. dedup_by ( |a, b| a. name ( ) == b. name ( ) ) ;
564- for ont in ontologies {
565- println ! ( "{}" , ont. to_uri_string( ) ) ;
621+ if json {
622+ let out: Vec < String > =
623+ ontologies. into_iter ( ) . map ( |o| o. to_uri_string ( ) ) . collect ( ) ;
624+ println ! ( "{}" , serde_json:: to_string_pretty( & out) ?) ;
625+ } else {
626+ for ont in ontologies {
627+ println ! ( "{}" , ont. to_uri_string( ) ) ;
628+ }
566629 }
567630 }
568631 ListCommands :: Missing => {
569632 let mut missing_imports = env. missing_imports ( ) ;
570633 missing_imports. sort ( ) ;
571- for import in missing_imports {
572- println ! ( "{}" , import. to_uri_string( ) ) ;
634+ if json {
635+ let out: Vec < String > =
636+ missing_imports. into_iter ( ) . map ( |n| n. to_uri_string ( ) ) . collect ( ) ;
637+ println ! ( "{}" , serde_json:: to_string_pretty( & out) ?) ;
638+ } else {
639+ for import in missing_imports {
640+ println ! ( "{}" , import. to_uri_string( ) ) ;
641+ }
573642 }
574643 }
575644 }
@@ -607,28 +676,67 @@ fn main() -> Result<()> {
607676 ) ) ;
608677 }
609678 }
610- Commands :: Why { ontologies } => {
679+ Commands :: Why { ontologies, json } => {
611680 let env = require_ontoenv ( env) ?;
612- for ont in ontologies {
613- let iri = NamedNode :: new ( ont) . map_err ( |e| anyhow:: anyhow!( e. to_string( ) ) ) ?;
614- let importers = env. get_importers ( & iri) ?;
615- println ! ( "Imported by {}: " , iri. to_uri_string( ) ) ;
616- for dep in importers {
617- println ! ( "{}" , dep. to_uri_string( ) ) ;
681+ if json {
682+ let mut all: BTreeMap < String , Vec < Vec < String > > > = BTreeMap :: new ( ) ;
683+ for ont in ontologies {
684+ let iri = NamedNode :: new ( ont) . map_err ( |e| anyhow:: anyhow!( e. to_string( ) ) ) ?;
685+ let paths = env. get_import_paths ( & iri) ?;
686+ let mut unique: BTreeSet < Vec < String > > = BTreeSet :: new ( ) ;
687+ for p in paths {
688+ unique. insert ( p. into_iter ( ) . map ( |id| id. to_uri_string ( ) ) . collect ( ) ) ;
689+ }
690+ let arr: Vec < Vec < String > > = unique. into_iter ( ) . collect ( ) ;
691+ all. insert ( iri. to_uri_string ( ) , arr) ;
692+ }
693+ println ! ( "{}" , serde_json:: to_string_pretty( & all) ?) ;
694+ } else {
695+ for ont in ontologies {
696+ let iri = NamedNode :: new ( ont) . map_err ( |e| anyhow:: anyhow!( e. to_string( ) ) ) ?;
697+ let paths = env. get_import_paths ( & iri) ?;
698+ if paths. is_empty ( ) {
699+ println ! ( "No importers found for {}" , iri. to_uri_string( ) ) ;
700+ continue ;
701+ }
702+ println ! ( "Why {}:" , iri. to_uri_string( ) ) ;
703+ let mut lines: BTreeSet < String > = BTreeSet :: new ( ) ;
704+ for p in paths {
705+ let line = p
706+ . into_iter ( )
707+ . map ( |id| id. to_uri_string ( ) )
708+ . collect :: < Vec < _ > > ( )
709+ . join ( " -> " ) ;
710+ lines. insert ( line) ;
711+ }
712+ for line in lines {
713+ println ! ( "{}" , line) ;
714+ }
618715 }
619716 }
620717 }
621- Commands :: Doctor => {
718+ Commands :: Doctor { json } => {
622719 let env = require_ontoenv ( env) ?;
623720 let problems = env. doctor ( ) ?;
624- if problems. is_empty ( ) {
625- println ! ( "No issues found." ) ;
721+ if json {
722+ let out: Vec < serde_json:: Value > = problems
723+ . into_iter ( )
724+ . map ( |p| serde_json:: json!( {
725+ "message" : p. message,
726+ "locations" : p. locations. into_iter( ) . map( |loc| loc. to_string( ) ) . collect:: <Vec <_>>( )
727+ } ) )
728+ . collect ( ) ;
729+ println ! ( "{}" , serde_json:: to_string_pretty( & out) ?) ;
626730 } else {
627- println ! ( "Found {} issues:" , problems. len( ) ) ;
628- for problem in problems {
629- println ! ( "- {}" , problem. message) ;
630- for location in problem. locations {
631- println ! ( " - {location}" ) ;
731+ if problems. is_empty ( ) {
732+ println ! ( "No issues found." ) ;
733+ } else {
734+ println ! ( "Found {} issues:" , problems. len( ) ) ;
735+ for problem in problems {
736+ println ! ( "- {}" , problem. message) ;
737+ for location in problem. locations {
738+ println ! ( " - {location}" ) ;
739+ }
632740 }
633741 }
634742 }
0 commit comments