@@ -7,6 +7,7 @@ use ontoenv::ontology::{GraphIdentifier, OntologyLocation};
77use ontoenv:: util:: write_dataset_to_file;
88use ontoenv:: ToUriString ;
99use oxigraph:: model:: NamedNode ;
10+ use oxigraph:: io:: { RdfFormat , JsonLdProfileSet } ;
1011use serde_json;
1112use std:: env:: current_dir;
1213use std:: path:: PathBuf ;
@@ -49,9 +50,6 @@ struct Cli {
4950 /// Do not search for ontologies in the search directories
5051 #[ clap( long = "no-search" , short = 'n' , action, global = true ) ]
5152 no_search : bool ,
52- /// Directories to search for ontologies. If not provided, the current directory is used.
53- #[ clap( global = true , last = true ) ]
54- locations : Option < Vec < PathBuf > > ,
5553}
5654
5755#[ derive( Debug , Subcommand ) ]
@@ -108,6 +106,9 @@ enum Commands {
108106 /// Overwrite the environment if it already exists
109107 #[ clap( long, default_value = "false" ) ]
110108 overwrite : bool ,
109+ /// Directories to search for ontologies. If not provided, the current directory is used.
110+ #[ clap( last = true ) ]
111+ locations : Option < Vec < PathBuf > > ,
111112 } ,
112113 /// Prints the version of the ontoenv binary
113114 Version ,
@@ -146,6 +147,20 @@ enum Commands {
146147 #[ clap( long, default_value = "-1" ) ]
147148 recursion_depth : i32 ,
148149 } ,
150+ /// Retrieve a single graph from the environment and write it to STDOUT or a file
151+ Get {
152+ /// Ontology IRI (name)
153+ ontology : String ,
154+ /// Optional source location (file path or URL) to disambiguate
155+ #[ clap( long, short = 'l' ) ]
156+ location : Option < String > ,
157+ /// Output file path; if omitted, writes to STDOUT
158+ #[ clap( long) ]
159+ output : Option < String > ,
160+ /// Serialization format: one of [turtle, ntriples, rdfxml, jsonld] (default: turtle)
161+ #[ clap( long, short = 'f' ) ]
162+ format : Option < String > ,
163+ } ,
149164 /// Add an ontology to the environment
150165 Add {
151166 /// The location of the ontology to add (file path or URL)
@@ -212,6 +227,7 @@ impl ToString for Commands {
212227 Commands :: Status { .. } => "Status" . to_string ( ) ,
213228 Commands :: Update { .. } => "Update" . to_string ( ) ,
214229 Commands :: Closure { .. } => "Closure" . to_string ( ) ,
230+ Commands :: Get { .. } => "Get" . to_string ( ) ,
215231 Commands :: Add { .. } => "Add" . to_string ( ) ,
216232 Commands :: List { .. } => "List" . to_string ( ) ,
217233 Commands :: Dump { .. } => "Dump" . to_string ( ) ,
@@ -406,8 +422,9 @@ fn main() -> Result<()> {
406422 . temporary ( cmd. temporary )
407423 . no_search ( cmd. no_search ) ;
408424
409- if let Some ( locations) = cmd. locations {
410- builder = builder. locations ( locations) ;
425+ // Locations only apply to `init`; other commands ignore positional LOCATIONS
426+ if let Commands :: Init { locations : Some ( locs) , .. } = & cmd. command {
427+ builder = builder. locations ( locs. clone ( ) ) ;
411428 }
412429 // only set includes if they are provided on the command line, otherwise use builder defaults
413430 if !cmd. includes . is_empty ( ) {
@@ -469,20 +486,30 @@ fn main() -> Result<()> {
469486 // create the env object to use in the subcommand.
470487 // - if temporary is true, create a new env object each time
471488 // - if temporary is false, load the env from the .ontoenv directory if it exists
489+ // Determine if this command needs write access to the store
490+ let needs_rw = matches ! (
491+ cmd. command,
492+ Commands :: Add { .. } | Commands :: Update { .. }
493+ ) ;
494+
472495 let env: Option < OntoEnv > = if cmd. temporary {
473496 // Create a new OntoEnv object in temporary mode
474497 let e = OntoEnv :: init ( config. clone ( ) , false ) ?;
475498 Some ( e)
476499 } else if cmd. command . to_string ( ) != "Init" && ontoenv_exists {
477500 // if .ontoenv exists, load it from discovered root
478- Some ( OntoEnv :: load_from_directory ( discovered_root. unwrap ( ) , false ) ?) // no read-only
501+ // Open read-only unless the command requires write access
502+ Some ( OntoEnv :: load_from_directory (
503+ discovered_root. unwrap ( ) ,
504+ !needs_rw,
505+ ) ?)
479506 } else {
480507 None
481508 } ;
482509 info ! ( "OntoEnv loaded: {}" , env. is_some( ) ) ;
483510
484511 match cmd. command {
485- Commands :: Init { overwrite } => {
512+ Commands :: Init { overwrite, .. } => {
486513 // if temporary, raise an error
487514 if cmd. temporary {
488515 return Err ( anyhow:: anyhow!(
@@ -509,6 +536,68 @@ fn main() -> Result<()> {
509536 // `update` will also save it to the directory.
510537 let _ = OntoEnv :: init ( config, overwrite) ?;
511538 }
539+ Commands :: Get {
540+ ontology,
541+ location,
542+ output,
543+ format,
544+ } => {
545+ let env = require_ontoenv ( env) ?;
546+
547+ // If a location is provided, resolve by location. Otherwise resolve by name (IRI).
548+ let graph = if let Some ( loc) = location {
549+ let oloc = if loc. starts_with ( "http://" ) || loc. starts_with ( "https://" ) {
550+ OntologyLocation :: Url ( loc)
551+ } else {
552+ // Normalize to absolute path
553+ ontoenv:: ontology:: OntologyLocation :: from_str ( & loc) . unwrap_or_else ( |_| OntologyLocation :: File ( PathBuf :: from ( loc) ) )
554+ } ;
555+ // Read directly from the specified location to disambiguate
556+ oloc. graph ( ) ?
557+ } else {
558+ let iri = NamedNode :: new ( ontology)
559+ . map_err ( |e| anyhow:: anyhow!( e. to_string( ) ) ) ?;
560+ let graphid = env
561+ . resolve ( ResolveTarget :: Graph ( iri) )
562+ . ok_or ( anyhow:: anyhow!( "Ontology not found" ) ) ?;
563+ env. get_graph ( & graphid) ?
564+ } ;
565+
566+ let fmt = match format
567+ . as_deref ( )
568+ . unwrap_or ( "turtle" )
569+ . to_ascii_lowercase ( )
570+ . as_str ( )
571+ {
572+ "turtle" | "ttl" => RdfFormat :: Turtle ,
573+ "ntriples" | "nt" => RdfFormat :: NTriples ,
574+ "rdfxml" | "xml" => RdfFormat :: RdfXml ,
575+ "jsonld" | "json-ld" => RdfFormat :: JsonLd { profile : JsonLdProfileSet :: default ( ) } ,
576+ other => {
577+ return Err ( anyhow:: anyhow!(
578+ "Unsupported format '{}'. Use one of: turtle, ntriples, rdfxml, jsonld" ,
579+ other
580+ ) )
581+ }
582+ } ;
583+
584+ if let Some ( path) = output {
585+ let mut file = std:: fs:: File :: create ( path) ?;
586+ let mut serializer = oxigraph:: io:: RdfSerializer :: from_format ( fmt) . for_writer ( & mut file) ;
587+ for t in graph. iter ( ) {
588+ serializer. serialize_triple ( t) ?;
589+ }
590+ serializer. finish ( ) ?;
591+ } else {
592+ let stdout = std:: io:: stdout ( ) ;
593+ let mut handle = stdout. lock ( ) ;
594+ let mut serializer = oxigraph:: io:: RdfSerializer :: from_format ( fmt) . for_writer ( & mut handle) ;
595+ for t in graph. iter ( ) {
596+ serializer. serialize_triple ( t) ?;
597+ }
598+ serializer. finish ( ) ?;
599+ }
600+ }
512601 Commands :: Version => {
513602 println ! (
514603 "ontoenv {} @ {}" ,
0 commit comments