Skip to content

Commit

Permalink
feat: cargo run works with free main components (load, analyze, pub…
Browse files Browse the repository at this point in the history
…lish) in place (nothing producing just yet)
  • Loading branch information
jgeluk committed Dec 4, 2024
1 parent 75465f8 commit ac3a70f
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 3 deletions.
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,16 @@ edition = "2024"
rust-version = "1.85"

[dependencies]
oxigraph = "0.4.4"
oxrdf = "0.2.3"
oxrdfio = { version = "0.1.3", features = ["async-tokio"] }
oxttl = { version = "0.1.3", features = ["async-tokio"] }
anyhow = "1.0.41"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
console = "0.15"
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json", "stream"] }
url = "2.4.0"
tokio-util = "0.7.12"
futures = "0.3"
3 changes: 2 additions & 1 deletion grapharch.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"git.pullBeforeCheckout": true,
"evenBetterToml.taplo.bundled": true,
"editor.formatOnSave": true,
"github.gitProtocol": "ssh"
"github.gitProtocol": "ssh",
"testExplorer.showOnRun": true
}
}
10 changes: 10 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mod model;
mod output;
mod source;
mod util;
pub use {
model::doc_model::DocumentationModel,
output::typst::TypstGenerator,
source::owl::OWLSource,
util::{rdf_load, setup_tracing},
};
47 changes: 45 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
fn main() {
println!("Hello, world!");
use {
console::style,
grapharch::{
DocumentationModel,
OWLSource,
TypstGenerator,
setup_tracing,
},
tracing::{error, info},
};

async fn run() -> anyhow::Result<()> {
setup_tracing()?;

// Read the OWL file
let owl_url = "https://ekgf.github.io/dprod/dprod.ttl";
let mut owl_source = OWLSource::new(owl_url).map_err(|e| {
error!(
"{}: {}. URL: {}",
style("Failed to initialize OWLSource").red(),
e,
owl_url
);

anyhow::Error::msg(format!("{}: URL: {}", e, owl_url))
})?;

// Process the OWL file
let mut doc_model = DocumentationModel::new()?;
owl_source.analyze(&mut doc_model).await?;

// Generate output
let typst_gen = TypstGenerator::new("output");
typst_gen.generate(doc_model.get_store())?;

info!("Documentation generation completed successfully.");
Ok(())
}

#[tokio::main]
async fn main() {
if let Err(e) = run().await {
error!("Application error: {}", e);
std::process::exit(1);
}
}
21 changes: 21 additions & 0 deletions src/model/doc_model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use oxigraph::{model::Quad, store::Store};

pub struct DocumentationModel {
store: Store,
}

impl DocumentationModel {
pub fn new() -> anyhow::Result<Self> {
Ok(Self { store: Store::new()? })
}

pub async fn add_documentable_item(
&mut self,
quad: Quad,
) -> anyhow::Result<()> {
self.store.insert(&quad)?;
Ok(())
}

pub fn get_store(&self) -> &Store { &self.store }
}
1 change: 1 addition & 0 deletions src/model/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod doc_model;
1 change: 1 addition & 0 deletions src/output/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod typst;
22 changes: 22 additions & 0 deletions src/output/typst.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use {oxigraph::store::Store, std::path::Path};

pub struct TypstGenerator {
#[allow(dead_code)]
output_dir: String,
}

impl TypstGenerator {
pub fn new<P: AsRef<Path>>(output_dir: P) -> Self {
Self {
output_dir: output_dir
.as_ref()
.to_string_lossy()
.into_owned(),
}
}

pub fn generate(&self, _store: &Store) -> anyhow::Result<()> {
// TODO: Query the store and generate Typst documentation
Ok(())
}
}
1 change: 1 addition & 0 deletions src/source/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod owl;
99 changes: 99 additions & 0 deletions src/source/owl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use {
crate::{model::doc_model::DocumentationModel, rdf_load},
console::style,
oxigraph::{model::*, store::Store},
std::path::Path,
tracing::{error, info},
};

pub struct OWLSource {
/// A temporary store that just holds the data from the source
store: Store,
file_path: String,
graph: GraphName,
base_iri: String,
}

impl OWLSource {
pub fn new<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
info!(
"{}",
style("Starting OWL source...").green().bold()
);
Ok(Self {
store: Store::new()?,
file_path: path.as_ref().to_string_lossy().into_owned(),
graph: NamedNodeRef::new("http://example.com/g2")?
.into(),
base_iri: "http://example.com".to_string(),
})
}

/// Load the data from the source into the store.
/// Note that this is not the same store as the store held by the
/// documentation model. This store is temporary and is used to
/// hold the data from the source.
async fn load(&mut self) -> anyhow::Result<()> {
let new_store = Store::new()?;
self.store = rdf_load(
std::mem::replace(&mut self.store, new_store),
&self.file_path,
&self.base_iri,
self.graph.as_ref(),
)
.await?;

Ok(())
}

/// Analyze the ontology and give the documentation items to the
/// given DocumentationModel.
pub async fn analyze(
&mut self,
doc_model: &mut DocumentationModel,
) -> anyhow::Result<()> {
info!(
"{}",
style("Analyzing ontology...").green().bold()
);
self.load().await?;

let mut documentable_items = Vec::new();
for quad_result in self.store.quads_for_pattern(
None,
None,
None,
Some(self.graph.as_ref()),
) {
match quad_result {
Ok(quad) => {
if self.is_documentable(&quad) {
documentable_items.push(quad);
}
},
Err(e) => {
error!(
"{}: {}",
style("Error processing quad").red().bold(),
e
);
continue;
},
}
}
// Store in documentation model
for item in documentable_items {
doc_model.add_documentable_item(item).await?;
}

Ok(())
}

fn is_documentable(&self, _quad: &Quad) -> bool {
// TODO: Implement logic to determine if a
// triple represents something we should
// document For example: Classes,
// Properties, Labels, Comments, etc.
true
}
}
4 changes: 4 additions & 0 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod rdf_load;
mod tracing;

pub use {rdf_load::rdf_load, tracing::setup_tracing};
102 changes: 102 additions & 0 deletions src/util/rdf_load.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use {
console::style,
futures::TryStreamExt,
oxigraph::store::Store,
oxrdf::{GraphName, GraphNameRef},
oxrdfio::{RdfFormat, RdfParser},
reqwest::Client,
std::path::Path,
tokio::{fs::File as AsyncFile, io::AsyncReadExt, task},
tracing::info,
url::Url,
};

pub async fn rdf_load<'a>(
mut store: Store,
file_path: &String,
base_iri: &String,
graph: GraphNameRef<'a>,
) -> anyhow::Result<Store> {
info!(
"{}",
style("Loading ontology from source...").green().bold()
);

if let Ok(url) = Url::parse(&file_path) {
// Handle URL
let client = Client::new();
let response = client.get(url).send().await.map_err(|e| {
anyhow::Error::new(e).context("Failed to send request")
})?;
if !response.status().is_success() {
return Err(anyhow::Error::msg(format!(
"Failed to fetch URL: {}",
response.status()
)));
}
let mut stream = response.bytes_stream().map_err(|e| {
std::io::Error::new(std::io::ErrorKind::Other, e)
});
let mut buffer = Vec::new();
while let Some(chunk) = stream.try_next().await? {
buffer.extend_from_slice(&chunk);
}

let base_iri = base_iri.clone();
let graph = GraphName::from(graph);
store =
task::spawn_blocking(move || -> anyhow::Result<Store> {
let reader = std::io::Cursor::new(buffer);
store
.load_from_reader(
RdfParser::from_format(RdfFormat::Turtle)
.with_base_iri(base_iri)
.map_err(anyhow::Error::msg)?
.without_named_graphs()
.with_default_graph(graph),
reader,
)
.map_err(anyhow::Error::msg)?;
Ok(store)
})
.await??;
} else {
// Handle file path
let path = Path::new(&file_path);
if !path.exists() {
return Err(anyhow::Error::msg(format!(
"File not found: {}",
file_path
)));
}
let mut file = AsyncFile::open(path).await?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).await?;

let base_iri = base_iri.clone();
let graph = GraphName::from(graph);
store =
task::spawn_blocking(move || -> anyhow::Result<Store> {
let reader = std::io::Cursor::new(buffer);
store
.load_from_reader(
RdfParser::from_format(RdfFormat::Turtle)
.with_base_iri(base_iri)
.map_err(anyhow::Error::msg)?
.without_named_graphs()
.with_default_graph(graph),
reader,
)
.map_err(anyhow::Error::msg)?;
Ok(store)
})
.await??;
}

info!(
"{}",
style("Ontology loaded successfully.").green().bold()
);

Ok(store)
}
15 changes: 15 additions & 0 deletions src/util/tracing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use tracing_subscriber::{EnvFilter, fmt};

/// Initialize tracing with custom format
pub fn setup_tracing() -> anyhow::Result<()> {
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info")); // Default to "info" if RUST_LOG is not set

fmt()
.with_env_filter(env_filter)
.without_time()
.with_target(false)
.init();

Ok(())
}

0 comments on commit ac3a70f

Please sign in to comment.