Skip to content

Commit

Permalink
Merge pull request #21 from FAIRChemistry/add-linkml
Browse files Browse the repository at this point in the history
Implement LinkML export
  • Loading branch information
JR-1991 authored Jan 30, 2025
2 parents 3828b93 + 5828ecd commit 8e5f7eb
Show file tree
Hide file tree
Showing 15 changed files with 1,273 additions and 115 deletions.
67 changes: 54 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/FAIRChemistry/md-models"
crate-type = ["cdylib", "rlib"]

[dependencies]
serde_yaml = "0.9.34"
serde = { version = "1.0.198", features = ["derive"] }
pulldown-cmark = "0.12.2"
serde_json = { "version" = "1.0.116", features = ["preserve_order"] }
Expand All @@ -36,6 +37,8 @@ openai-api-rs = { version = "5.2.3", optional = true }
jsonschema = { version = "0.27.1", default-features = false }
getrandom = { version = "0.2.15", features = ["js"] }
tsify-next = { version = "0.5.4", features = ["js"], optional = true }
strum = "0.26.3"
strum_macros = "0.26.4"

[features]
default = ["openai"]
Expand Down
118 changes: 23 additions & 95 deletions src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
*
*/

use crate::{markdown::position::Position, xmltype::XMLType};
use crate::{
markdown::{parser::OptionKey, position::Position},
option::{AttrOption, RawOption},
xmltype::XMLType,
};
use serde::{de::Visitor, Deserialize, Serialize};
use std::{error::Error, fmt, str::FromStr};

Expand Down Expand Up @@ -113,14 +117,14 @@ impl Attribute {
/// # Arguments
///
/// * `option` - The option to add.
pub fn add_option(&mut self, option: AttrOption) -> Result<(), Box<dyn Error>> {
pub fn add_option(&mut self, option: RawOption) -> Result<(), Box<dyn Error>> {
match OptionKey::from_str(option.key.as_str()) {
OptionKey::Type => self.set_dtype(option.value)?,
OptionKey::Term => self.term = Some(option.value),
OptionKey::Description => self.docstring = option.value,
OptionKey::Default => self.default = Some(DataType::from_str(&option.value)?),
OptionKey::Multiple => self.is_array = option.value.to_lowercase() == "true",
OptionKey::Other => self.options.push(option),
OptionKey::Other => self.options.push(option.try_into()?),
OptionKey::Xml => {
self.set_xml(XMLType::from_str(&option.value).expect("Invalid XML type"))
}
Expand Down Expand Up @@ -249,51 +253,6 @@ impl Attribute {
}
}

/// Represents an option for an attribute.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "python", pyclass(get_all))]
#[cfg_attr(feature = "wasm", derive(Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
pub struct AttrOption {
/// The key of the option.
pub key: String,
/// The value of the option.
pub value: String,
}

impl AttrOption {
/// Creates a new `AttrOption` with the given key and value.
///
/// # Arguments
///
/// * `key` - The key of the option.
/// * `value` - The value of the option.
pub fn new(key: String, value: String) -> Self {
Self {
key: key.to_lowercase(),
value,
}
}

/// Gets the key of the option.
///
/// # Returns
///
/// A reference to the key.
pub fn key(&self) -> &str {
&self.key
}

/// Gets the value of the option.
///
/// # Returns
///
/// A reference to the value.
pub fn value(&self) -> &str {
&self.value
}
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "python", pyclass)]
#[cfg_attr(feature = "wasm", derive(Tsify))]
Expand Down Expand Up @@ -444,47 +403,6 @@ impl<'de> Deserialize<'de> for DataType {
}
}

/// Represents the different keys that can be used for attribute options.
enum OptionKey {
/// Represents the data type of the attribute.
Type,
/// Represents the term associated with the attribute.
Term,
/// Represents the description of the attribute.
Description,
/// Represents the XML type information for the attribute.
Xml,
/// Represents the default value for the attribute.
Default,
/// Indicates if the attribute can have multiple values.
Multiple,
/// Represents any other option not covered by the predefined keys.
Other,
}

impl OptionKey {
/// Converts a string to an `OptionKey`.
///
/// # Arguments
///
/// * `key` - The string representation of the key.
///
/// # Returns
///
/// An `OptionKey` corresponding to the given string.
fn from_str(key: &str) -> Self {
match key.to_lowercase().as_str() {
"type" => OptionKey::Type,
"term" => OptionKey::Term,
"description" => OptionKey::Description,
"xml" => OptionKey::Xml,
"default" => OptionKey::Default,
"multiple" => OptionKey::Multiple,
_ => OptionKey::Other,
}
}
}

#[cfg(test)]
mod tests {
use crate::xmltype::XMLType;
Expand Down Expand Up @@ -515,7 +433,7 @@ mod tests {
#[test]
fn test_attribute_add_type_option() {
let mut attr = Attribute::new("name".to_string(), false);
let option = AttrOption::new("type".to_string(), "string".to_string());
let option = RawOption::new("type".to_string(), "string".to_string());
attr.add_option(option).expect("Failed to add option");
assert_eq!(attr.dtypes.len(), 1);
assert_eq!(attr.dtypes[0], "string");
Expand All @@ -524,22 +442,32 @@ mod tests {
#[test]
fn test_attribute_add_term_option() {
let mut attr = Attribute::new("name".to_string(), false);
let option = AttrOption::new("term".to_string(), "string".to_string());
let option = RawOption::new("term".to_string(), "string".to_string());
attr.add_option(option).expect("Failed to add option");
assert_eq!(attr.term, Some("string".to_string()));
}

#[test]
fn test_attribute_add_option() {
let mut attr = Attribute::new("name".to_string(), false);
let option = AttrOption::new("description".to_string(), "This is a test".to_string());
let option = RawOption::new("description".to_string(), "This is a test".to_string());
attr.add_option(option).expect("Failed to add option");
let option = AttrOption::new("something".to_string(), "something".to_string());
let option = RawOption::new("something".to_string(), "something".to_string());
attr.add_option(option).expect("Failed to add option");

assert_eq!(attr.options.len(), 1);
assert_eq!(attr.options[0].key, "something");
assert_eq!(attr.options[0].value, "something");

if let Some(option) = attr.options.first() {
if let AttrOption::Other { key, value } = option {
assert_eq!(key, "something");
assert_eq!(value, "something");
} else {
panic!("Option is not an AttributeOption::Other");
}
} else {
panic!("Option not found");
}

assert_eq!(attr.docstring, "This is a test");
}

Expand Down
2 changes: 2 additions & 0 deletions src/bin/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use mdmodels::{
datamodel::DataModel,
exporters::{render_jinja_template, Templates},
json::validation::validate_json,
linkml::export::serialize_linkml,
llm::extraction::query_openai,
pipeline::process_pipeline,
};
Expand Down Expand Up @@ -319,6 +320,7 @@ fn convert(args: ConvertArgs) -> Result<(), Box<dyn Error>> {
// Render the template.
let rendered = match args.template {
Templates::JsonSchema => model.json_schema(args.root, false)?,
Templates::Linkml => serialize_linkml(model, args.output.as_ref())?,
_ => render_jinja_template(&args.template, &mut model, None)?,
};

Expand Down
2 changes: 1 addition & 1 deletion src/datamodel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ use tsify_next::Tsify;
// * `json_schema` - Generate a JSON schema from the data model
// * `json_schema_all` - Generate JSON schemas for all objects in the data model
// * `internal_schema` - Generate an internal schema from the data model
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[cfg_attr(feature = "python", pyclass(get_all))]
#[cfg_attr(feature = "wasm", derive(Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
Expand Down
2 changes: 2 additions & 0 deletions src/exporters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub enum Templates {
Protobuf,
Graphql,
Golang,
Linkml,
}

impl Display for Templates {
Expand All @@ -133,6 +134,7 @@ impl Display for Templates {
Templates::Protobuf => write!(f, "protobuf"),
Templates::Graphql => write!(f, "graphql"),
Templates::Golang => write!(f, "golang"),
Templates::Linkml => write!(f, "linkml"),
}
}
}
Expand Down
Loading

0 comments on commit 8e5f7eb

Please sign in to comment.