Compare commits

...

1 Commits

Author SHA1 Message Date
Tomáš Mládek 3a6db03ac1 wip
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-08-29 18:27:55 +02:00
13 changed files with 150 additions and 88 deletions

View File

@ -1,4 +1,4 @@
use crate::addressing::Address; use crate::addressing::{Address, Addressable};
use crate::entry::InvariantEntry; use crate::entry::InvariantEntry;
use crate::hash::{LargeMultihash, UpMultihash}; use crate::hash::{LargeMultihash, UpMultihash};
@ -20,7 +20,7 @@ pub const ATTR_KEY: &str = "KEY";
lazy_static! { lazy_static! {
pub static ref HIER_ROOT_INVARIANT: InvariantEntry = InvariantEntry { pub static ref HIER_ROOT_INVARIANT: InvariantEntry = InvariantEntry {
attribute: String::from(ATTR_KEY), attribute: String::from(ATTR_KEY),
value: "HIER_ROOT".into(), value: "HIER_ROOT".address().unwrap().into(),
}; };
pub static ref HIER_ROOT_ADDR: Address = HIER_ROOT_INVARIANT.entity().unwrap(); pub static ref HIER_ROOT_ADDR: Address = HIER_ROOT_INVARIANT.entity().unwrap();
pub static ref TYPE_HASH_ADDRESS: Address = pub static ref TYPE_HASH_ADDRESS: Address =

View File

@ -1,4 +1,4 @@
use crate::addressing::Address; use crate::addressing::{Address, Addressable};
use crate::error::UpEndError; use crate::error::UpEndError;
use crate::hash::{b58_decode, sha256hash, AsMultihash, AsMultihashError, UpMultihash}; use crate::hash::{b58_decode, sha256hash, AsMultihash, AsMultihashError, UpMultihash};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
@ -28,11 +28,9 @@ pub struct InvariantEntry {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "t", content = "c")] #[serde(tag = "t", content = "c")]
pub enum EntryValue { pub enum EntryValue {
String(String),
Number(f64),
Address(Address), Address(Address),
Number(f64),
Null, Null,
Invalid,
} }
impl Default for Entry { impl Default for Entry {
@ -68,7 +66,7 @@ impl InvariantEntry {
.write_all(self.attribute.as_bytes()) .write_all(self.attribute.as_bytes())
.map_err(UpEndError::from_any)?; .map_err(UpEndError::from_any)?;
entity entity
.write_all(self.value.to_string()?.as_bytes()) .write_all(self.value.to_string().as_bytes())
.map_err(UpEndError::from_any)?; .map_err(UpEndError::from_any)?;
Ok(Address::Hash( Ok(Address::Hash(
sha256hash(entity.into_inner()).map_err(UpEndError::from_any)?, sha256hash(entity.into_inner()).map_err(UpEndError::from_any)?,
@ -92,12 +90,7 @@ impl AsMultihash for Entry {
.as_slice(), .as_slice(),
)?; )?;
result.write_all(self.attribute.as_bytes())?; result.write_all(self.attribute.as_bytes())?;
result.write_all( result.write_all(self.value.to_string().as_bytes())?;
self.value
.to_string()
.map_err(|e| AsMultihashError(e.to_string()))?
.as_bytes(),
)?;
sha256hash(result.get_ref()) sha256hash(result.get_ref())
} }
} }
@ -111,27 +104,25 @@ impl AsMultihash for InvariantEntry {
} }
impl EntryValue { impl EntryValue {
pub fn to_string(&self) -> Result<String, UpEndError> { pub fn to_string(&self) -> String {
let (type_char, content) = match self { let (type_char, content) = match self {
EntryValue::String(value) => ('S', value.to_owned()),
EntryValue::Number(n) => ('N', n.to_string()),
EntryValue::Address(address) => ('O', address.to_string()), EntryValue::Address(address) => ('O', address.to_string()),
EntryValue::Number(n) => ('N', n.to_string()),
EntryValue::Null => ('X', "".to_string()), EntryValue::Null => ('X', "".to_string()),
EntryValue::Invalid => return Err(UpEndError::CannotSerializeInvalid),
}; };
Ok(format!("{}{}", type_char, content)) format!("{}{}", type_char, content)
} }
pub fn guess_from<S: AsRef<str>>(string: S) -> Self { pub fn guess_from<S: AsRef<str>>(string: S) -> Result<Self, UpEndError> {
let string = string.as_ref(); let string = string.as_ref();
match string.parse::<f64>() { match string.parse::<f64>() {
Ok(num) => EntryValue::Number(num), Ok(num) => Ok(EntryValue::Number(num)),
Err(_) => { Err(_) => {
if let Ok(url) = Url::parse(string) { if let Ok(url) = Url::parse(string) {
EntryValue::Address(Address::Url(url)) Ok(EntryValue::Address(Address::Url(url)))
} else { } else {
EntryValue::String(string.to_string()) Ok(EntryValue::Address(string.address()?))
} }
} }
} }
@ -139,34 +130,32 @@ impl EntryValue {
} }
impl std::str::FromStr for EntryValue { impl std::str::FromStr for EntryValue {
type Err = std::convert::Infallible; type Err = UpEndError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() < 2 { if s.len() < 2 {
match s.chars().next() { match s.chars().next() {
Some('S') => Ok(EntryValue::String("".into())),
Some('X') => Ok(EntryValue::Null), Some('X') => Ok(EntryValue::Null),
_ => Ok(EntryValue::Invalid), _ => Err(UpEndError::EntryValueInvalid(s.to_string())),
} }
} else { } else {
let (type_char, content) = s.split_at(1); let (type_char, content) = s.split_at(1);
match (type_char, content) { match (type_char, content) {
("S", content) => Ok(EntryValue::String(String::from(content))),
("N", content) => { ("N", content) => {
if let Ok(n) = content.parse::<f64>() { if let Ok(n) = content.parse::<f64>() {
Ok(EntryValue::Number(n)) Ok(EntryValue::Number(n))
} else { } else {
Ok(EntryValue::Invalid) Err(UpEndError::EntryValueInvalid(s.to_string()))
} }
} }
("O", content) => { ("O", content) => {
if let Ok(addr) = b58_decode(content).and_then(|v| Address::decode(&v)) { if let Ok(addr) = b58_decode(content).and_then(|v| Address::decode(&v)) {
Ok(EntryValue::Address(addr)) Ok(EntryValue::Address(addr))
} else { } else {
Ok(EntryValue::Invalid) Err(UpEndError::EntryValueInvalid(s.to_string()))
} }
} }
_ => Ok(EntryValue::Invalid), _ => Err(UpEndError::EntryValueInvalid(s.to_string())),
} }
} }
} }
@ -182,27 +171,13 @@ impl std::fmt::Display for EntryValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (entry_type, entry_value) = match self { let (entry_type, entry_value) = match self {
EntryValue::Address(address) => ("ADDRESS", address.to_string()), EntryValue::Address(address) => ("ADDRESS", address.to_string()),
EntryValue::String(string) => ("STRING", string.to_owned()),
EntryValue::Number(n) => ("NUMBER", n.to_string()), EntryValue::Number(n) => ("NUMBER", n.to_string()),
EntryValue::Null => ("NULL", "NULL".to_string()), EntryValue::Null => ("NULL", "NULL".to_string()),
EntryValue::Invalid => ("INVALID", "INVALID".to_string()),
}; };
write!(f, "{}: {}", entry_type, entry_value) write!(f, "{}: {}", entry_type, entry_value)
} }
} }
impl From<&str> for EntryValue {
fn from(str: &str) -> Self {
Self::String(str.to_string())
}
}
impl From<String> for EntryValue {
fn from(str: String) -> Self {
Self::String(str)
}
}
impl From<f64> for EntryValue { impl From<f64> for EntryValue {
fn from(num: f64) -> Self { fn from(num: f64) -> Self {
Self::Number(num) Self::Number(num)
@ -221,28 +196,28 @@ mod tests {
#[test] #[test]
fn test_value_from_to_string() -> Result<(), UpEndError> { fn test_value_from_to_string() -> Result<(), UpEndError> {
let entry = EntryValue::String("hello".to_string()); let entry = EntryValue::Address("hello".address().unwrap());
let encoded = entry.to_string()?; let encoded = entry.to_string();
let decoded = encoded.parse::<EntryValue>().unwrap(); let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded); assert_eq!(entry, decoded);
let entry = EntryValue::Number(1337.93); let entry = EntryValue::Number(1337.93);
let encoded = entry.to_string()?; let encoded = entry.to_string();
let decoded = encoded.parse::<EntryValue>().unwrap(); let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded); assert_eq!(entry, decoded);
let entry = EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap())); let entry = EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap()));
let encoded = entry.to_string()?; let encoded = entry.to_string();
let decoded = encoded.parse::<EntryValue>().unwrap(); let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded); assert_eq!(entry, decoded);
let entry = EntryValue::String("".to_string()); let entry = EntryValue::Address("".address().unwrap());
let encoded = entry.to_string()?; let encoded = entry.to_string();
let decoded = encoded.parse::<EntryValue>().unwrap(); let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded); assert_eq!(entry, decoded);
let entry = EntryValue::Null; let entry = EntryValue::Null;
let encoded = entry.to_string()?; let encoded = entry.to_string();
let decoded = encoded.parse::<EntryValue>().unwrap(); let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded); assert_eq!(entry, decoded);
@ -251,7 +226,6 @@ mod tests {
#[test] #[test]
fn test_into() { fn test_into() {
assert_eq!(EntryValue::String(String::from("UPEND")), "UPEND".into());
assert_eq!(EntryValue::Number(1337.93), 1337.93.into()); assert_eq!(EntryValue::Number(1337.93), 1337.93.into());
let addr = Address::Url(Url::parse("https://upend.dev").unwrap()); let addr = Address::Url(Url::parse("https://upend.dev").unwrap());
assert_eq!(EntryValue::Address(addr.clone()), addr.into()); assert_eq!(EntryValue::Address(addr.clone()), addr.into());
@ -260,15 +234,15 @@ mod tests {
#[test] #[test]
fn test_guess_value() { fn test_guess_value() {
assert_eq!( assert_eq!(
EntryValue::guess_from("UPEND"), EntryValue::guess_from("UPEND").unwrap(),
EntryValue::String("UPEND".into()) EntryValue::Address("UPEND".address().unwrap())
); );
assert_eq!( assert_eq!(
EntryValue::guess_from("1337.93"), EntryValue::guess_from("1337.93").unwrap(),
EntryValue::Number(1337.93) EntryValue::Number(1337.93)
); );
assert_eq!( assert_eq!(
EntryValue::guess_from("https://upend.dev"), EntryValue::guess_from("https://upend.dev").unwrap(),
EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap())) EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap()))
); );
} }

View File

@ -2,6 +2,7 @@
pub enum UpEndError { pub enum UpEndError {
HashDecodeError(String), HashDecodeError(String),
AddressParseError(String), AddressParseError(String),
EntryValueInvalid(String),
AddressComponentsDecodeError(AddressComponentsDecodeError), AddressComponentsDecodeError(AddressComponentsDecodeError),
CannotSerializeInvalid, CannotSerializeInvalid,
QueryParseError(String), QueryParseError(String),
@ -23,6 +24,7 @@ impl std::fmt::Display for UpEndError {
match self { match self {
UpEndError::HashDecodeError(err) => format!("Could not decode hash: {err}"), UpEndError::HashDecodeError(err) => format!("Could not decode hash: {err}"),
UpEndError::AddressParseError(err) => format!("Error parsing address: {err}"), UpEndError::AddressParseError(err) => format!("Error parsing address: {err}"),
UpEndError::EntryValueInvalid(err) => format!("Invalid Entry value: {err}"),
UpEndError::AddressComponentsDecodeError(cde) => match cde { UpEndError::AddressComponentsDecodeError(cde) => match cde {
AddressComponentsDecodeError::UnknownType(t) => AddressComponentsDecodeError::UnknownType(t) =>
format!("Unknown type: \"{t}\""), format!("Unknown type: \"{t}\""),
@ -47,3 +49,9 @@ impl UpEndError {
UpEndError::Other(error.to_string()) UpEndError::Other(error.to_string())
} }
} }
impl From<crate::hash::AsMultihashError> for UpEndError {
fn from(error: crate::hash::AsMultihashError) -> Self {
UpEndError::Other(error.to_string())
}
}

View File

@ -1,4 +1,4 @@
use crate::addressing::Address; use crate::addressing::{Address, Addressable};
use crate::entry::EntryValue; use crate::entry::EntryValue;
use crate::error::UpEndError; use crate::error::UpEndError;
use nonempty::NonEmpty; use nonempty::NonEmpty;
@ -64,8 +64,28 @@ impl TryFrom<lexpr::Value> for EntryValue {
lexpr::Value::Number(num) => Ok(EntryValue::Number(num.as_f64().ok_or_else(|| { lexpr::Value::Number(num) => Ok(EntryValue::Number(num.as_f64().ok_or_else(|| {
UpEndError::QueryParseError(format!("Error processing number ({num:?}).")) UpEndError::QueryParseError(format!("Error processing number ({num:?})."))
})?)), })?)),
lexpr::Value::Char(chr) => Ok(EntryValue::String(chr.to_string())), lexpr::Value::Char(chr) => Ok(EntryValue::Address(
lexpr::Value::String(str) => Ok(EntryValue::String(str.to_string())), chr.to_string()
.address()
.map_err(|e| {
UpEndError::QueryParseError(format!(
"Error hashing character `{}`: {:}",
chr, e
))
})?
.into(),
)),
lexpr::Value::String(str) => Ok(EntryValue::Address(
str.to_string()
.address()
.map_err(|e| {
UpEndError::QueryParseError(format!(
"Error hashing string `{}`: {:}",
str, e
))
})?
.into(),
)),
lexpr::Value::Symbol(_) => Ok(EntryValue::Address(Address::try_from(value.clone())?)), lexpr::Value::Symbol(_) => Ok(EntryValue::Address(Address::try_from(value.clone())?)),
_ => Err(UpEndError::QueryParseError( _ => Err(UpEndError::QueryParseError(
"Value can only be a string, number or address.".into(), "Value can only be a string, number or address.".into(),
@ -298,7 +318,7 @@ impl FromStr for Query {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::error::UpEndError; use crate::{addressing::Addressable, error::UpEndError};
use super::*; use super::*;
use url::Url; use url::Url;
@ -377,7 +397,10 @@ mod test {
})) }))
); );
let values: Vec<EntryValue> = vec!["FOO".into(), "BAR".into()]; let values: Vec<EntryValue> = vec![
"FOO".address().unwrap().into(),
"BAR".address().unwrap().into(),
];
let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::<Query>()?; let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::<Query>()?;
assert_eq!( assert_eq!(
query, query,
@ -400,7 +423,8 @@ mod test {
); );
// Invalid queries // Invalid queries
let values: Vec<EntryValue> = vec!["FOO".into(), EntryValue::Number(1337.93)]; let values: Vec<EntryValue> =
vec!["FOO".address().unwrap().into(), EntryValue::Number(1337.93)];
let query = r#"(matches ? ? (in "FOO" 1337.93))"#.parse::<Query>()?; let query = r#"(matches ? ? (in "FOO" 1337.93))"#.parse::<Query>()?;
assert_eq!( assert_eq!(
@ -412,7 +436,7 @@ mod test {
})) }))
); );
let values = vec![EntryValue::Number(1337.93), "FOO".into()]; let values = vec![EntryValue::Number(1337.93), "FOO".address().unwrap().into()];
let query = r#"(matches ? ? (in 1337.93 "FOO"))"#.parse::<Query>()?; let query = r#"(matches ? ? (in 1337.93 "FOO"))"#.parse::<Query>()?;
assert_eq!( assert_eq!(

View File

@ -698,7 +698,7 @@ pub async fn get_all_attributes(state: web::Data<State>) -> Result<HttpResponse,
.into_iter() .into_iter()
.filter_map(|e| { .filter_map(|e| {
if e.attribute == ATTR_LABEL { if e.attribute == ATTR_LABEL {
if let EntryValue::String(label) = e.value { if let EntryValue::String(label) = e.value { // TODO
Some(label) Some(label)
} else { } else {
None None

View File

@ -197,11 +197,7 @@ fn to_sqlite_predicates(query: Query) -> Result<SqlResult, QueryExecutionError>
match &eq.value { match &eq.value {
QueryComponent::Exact(q_value) => match q_value { QueryComponent::Exact(q_value) => match q_value {
EntryValue::Number(n) => subqueries.push(Box::new(data::value_num.eq(*n))), EntryValue::Number(n) => subqueries.push(Box::new(data::value_num.eq(*n))),
_ => subqueries.push(Box::new(data::value_str.eq( _ => subqueries.push(Box::new(data::value_str.eq(q_value.to_string()))),
q_value.to_string().map_err(|e| {
QueryExecutionError(format!("failed producing sql: {e}"))
})?,
))),
}, },
QueryComponent::In(q_values) => { QueryComponent::In(q_values) => {
let first = q_values.first().ok_or_else(|| { let first = q_values.first().ok_or_else(|| {
@ -239,11 +235,7 @@ fn to_sqlite_predicates(query: Query) -> Result<SqlResult, QueryExecutionError>
string values! (Found {v})" string values! (Found {v})"
))) )))
} else { } else {
v.to_string().map_err(|e| { Ok(v.to_string())
QueryExecutionError(format!(
"failed producing sql: {e}"
))
})
} }
}) })
.collect::<Result<Vec<String>, QueryExecutionError>>()?, .collect::<Result<Vec<String>, QueryExecutionError>>()?,

View File

@ -48,7 +48,7 @@ impl TryFrom<&Entry> for models::Entry {
entity_searchable: match &e.entity { entity_searchable: match &e.entity {
Address::Attribute(attr) => Some(attr.clone()), Address::Attribute(attr) => Some(attr.clone()),
Address::Url(url) => Some(url.to_string()), Address::Url(url) => Some(url.to_string()),
_ => None, _ => None, // TODO
}, },
entity: e.entity.encode()?, entity: e.entity.encode()?,
attribute: e.attribute.clone(), attribute: e.attribute.clone(),
@ -66,7 +66,7 @@ impl TryFrom<&Entry> for models::Entry {
..base_entry ..base_entry
}), }),
_ => Ok(models::Entry { _ => Ok(models::Entry {
value_str: Some(e.value.to_string()?), value_str: Some(e.value.to_string()),
value_num: None, value_num: None,
..base_entry ..base_entry
}), }),

View File

@ -6,7 +6,7 @@ use lru::LruCache;
use tracing::trace; use tracing::trace;
use uuid::Uuid; use uuid::Uuid;
use upend_base::addressing::Address; use upend_base::addressing::{Address, Addressable};
use upend_base::constants::ATTR_LABEL; use upend_base::constants::ATTR_LABEL;
use upend_base::constants::{ATTR_IN, HIER_ROOT_ADDR, HIER_ROOT_INVARIANT}; use upend_base::constants::{ATTR_IN, HIER_ROOT_ADDR, HIER_ROOT_INVARIANT};
use upend_base::entry::Entry; use upend_base::entry::Entry;
@ -112,7 +112,7 @@ pub fn fetch_or_create_dir(
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery { .query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None), entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact(ATTR_LABEL.into()), attribute: QueryComponent::Exact(ATTR_LABEL.into()),
value: QueryComponent::Exact(String::from(directory.clone()).into()), value: QueryComponent::Exact(String::from(directory.clone()).address()?.into()),
})))? })))?
.into_iter() .into_iter()
.map(|e: Entry| e.entity); .map(|e: Entry| e.entity);
@ -142,7 +142,7 @@ pub fn fetch_or_create_dir(
let directory_entry = Entry { let directory_entry = Entry {
entity: new_directory_address.clone(), entity: new_directory_address.clone(),
attribute: String::from(ATTR_LABEL), attribute: String::from(ATTR_LABEL),
value: String::from(directory).into(), value: String::from(directory).address()?.into(),
provenance: "SYSTEM FS".to_string(), provenance: "SYSTEM FS".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
}; };

View File

@ -247,7 +247,7 @@ impl UpEndConnection {
let primary = data let primary = data
.filter(entity.eq(object_address.encode()?)) .filter(entity.eq(object_address.encode()?))
.or_filter(value_str.eq(EntryValue::Address(object_address.clone()).to_string()?)) .or_filter(value_str.eq(EntryValue::Address(object_address.clone()).to_string()))
.load::<models::Entry>(&conn)?; .load::<models::Entry>(&conn)?;
let entries = primary let entries = primary
@ -287,7 +287,7 @@ impl UpEndConnection {
let matches = data let matches = data
.filter(identity.eq(object_address.encode()?)) .filter(identity.eq(object_address.encode()?))
.or_filter(entity.eq(object_address.encode()?)) .or_filter(entity.eq(object_address.encode()?))
.or_filter(value_str.eq(EntryValue::Address(object_address).to_string()?)); .or_filter(value_str.eq(EntryValue::Address(object_address).to_string()));
Ok(diesel::delete(matches).execute(&conn)?) Ok(diesel::delete(matches).execute(&conn)?)
} }

View File

@ -4,7 +4,7 @@ macro_rules! upend_insert_val {
$db_connection.insert_entry(Entry { $db_connection.insert_entry(Entry {
entity: $entity.clone(), entity: $entity.clone(),
attribute: String::from($attribute), attribute: String::from($attribute),
value: upend_base::entry::EntryValue::String(String::from($value)), value: upend_base::entry::EntryValue::Address(String::from($value).address().unwrap()),
provenance: "SYSTEM INIT".to_string(), provenance: "SYSTEM INIT".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
}) })

View File

@ -21,7 +21,7 @@ use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use std::{fs, iter}; use std::{fs, iter};
use tracing::{error, info, trace, warn}; use tracing::{error, info, trace, warn};
use upend_base::addressing::Address; use upend_base::addressing::{Address, Addressable};
use upend_base::constants::{ATTR_ADDED, ATTR_BY, ATTR_IN, ATTR_LABEL, ATTR_OF, TYPE_HASH_ADDRESS}; use upend_base::constants::{ATTR_ADDED, ATTR_BY, ATTR_IN, ATTR_LABEL, ATTR_OF, TYPE_HASH_ADDRESS};
use upend_base::entry::Entry; use upend_base::entry::Entry;
use upend_base::hash::{b58_encode, UpMultihash}; use upend_base::hash::{b58_encode, UpMultihash};
@ -410,12 +410,16 @@ impl FsStore {
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
}; };
let mime_entry = mime_type.map(|mime_type| Entry { let mime_entry = mime_type.and_then(|mime_type| {
entity: blob_address.clone(), mime_type.address().ok().and_then(|mime_type_address| {
attribute: FILE_MIME_KEY.to_string(), Some(Entry {
value: mime_type.into(), entity: blob_address.clone(),
provenance: "SYSTEM INIT".to_string(), attribute: FILE_MIME_KEY.to_string(),
timestamp: chrono::Utc::now().naive_utc(), value: mime_type_address.into(),
provenance: "SYSTEM INIT".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
})
})
}); });
let added_entry = Entry { let added_entry = Entry {
@ -472,6 +476,7 @@ impl FsStore {
attribute: ATTR_LABEL.to_string(), attribute: ATTR_LABEL.to_string(),
value: name value: name
.unwrap_or_else(|| filename.as_os_str().to_string_lossy().to_string()) .unwrap_or_else(|| filename.as_os_str().to_string_lossy().to_string())
.address()?
.into(), .into(),
provenance: "SYSTEM INIT".to_string(), provenance: "SYSTEM INIT".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),

View File

@ -0,0 +1,14 @@
[package]
name = "upend_text"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]
[dependencies]
pulldown-cmark = "0.9.3"
serde = "1.0.188"
wasm-bindgen = "0.2.87"
wee_alloc = "0.4.5"

View File

@ -0,0 +1,45 @@
use pulldown_cmark::{Options, Parser};
pub struct Annotation {
pub id: String,
pub body: String,
pub target: String,
}
pub struct MarkdownResult {
pub text: String,
pub annotations: Vec<Annotation>,
}
#[derive(Debug)]
pub struct UpEndTextError(pub String);
impl std::fmt::Display for UpEndTextError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:#?}", self.0)
}
}
pub fn markdown_to_annotations(markdown: &str) -> Result<MarkdownResult, UpEndTextError> {
let mut options = Options::empty();
options.insert(Options::ENABLE_STRIKETHROUGH);
let parser = Parser::new_ext(markdown, options);
for ev in parser {
println!("{:?}", ev);
}
todo!()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let markdown_input = "Hello world, this is a ~~complicated~~ *very simple* example.";
let result = markdown_to_annotations(markdown_input);
result.unwrap();
}
}