Skip to main content

acorn/schema/standard/
text.rs

1//! Plain-text standard model with no schema structure
2//!
3//! Primary use case is to leverage prose analysis and readability metrics on unstructured text content, such as abstracts or descriptions.
4
5#[cfg(feature = "std")]
6use crate::error::ApiResult;
7#[cfg(feature = "std")]
8use crate::io::document::{DocumentFormat, SourceDocument};
9#[cfg(feature = "std")]
10use crate::io::{read_file, write_file, InputOutput};
11use crate::prelude::*;
12#[cfg(feature = "std")]
13use crate::prelude::{Path, PathBuf};
14#[cfg(feature = "std")]
15use crate::util::file_extension;
16#[cfg(feature = "std")]
17use crate::util::MimeType;
18use crate::util::{MarkdownSupport, ToProse, Unstructured};
19#[cfg(feature = "std")]
20use color_eyre::eyre::eyre;
21use schemars::JsonSchema;
22use serde::{Deserialize, Serialize};
23use validator::Validate;
24
25/// Text document sourced from DOCX or text-like content.
26#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize, Validate)]
27#[serde(transparent)]
28pub struct Docx {
29    /// Raw extracted text content.
30    pub content: String,
31}
32/// Text document with unconstrained content
33#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize, Validate)]
34#[serde(transparent)]
35pub struct Text {
36    /// Raw text content
37    pub content: String,
38}
39impl From<&str> for Text {
40    fn from(content: &str) -> Self {
41        Self {
42            content: content.to_string(),
43        }
44    }
45}
46impl From<String> for Text {
47    fn from(content: String) -> Self {
48        Self { content }
49    }
50}
51#[cfg(feature = "std")]
52impl TryFrom<&Path> for Text {
53    type Error = color_eyre::Report;
54
55    fn try_from(path: &Path) -> Result<Self, Self::Error> {
56        Self::read(path.to_path_buf())
57    }
58}
59#[cfg(feature = "std")]
60impl InputOutput for Text {
61    fn read(path: impl Into<PathBuf>) -> ApiResult<Self> {
62        let source = path.into();
63        match MimeType::from(source.display().to_string()) {
64            | MimeType::Markdown | MimeType::Text => Self::read_text(source),
65            | MimeType::Json => Self::read_json(source),
66            | MimeType::Yaml => Self::read_yaml(source),
67            | _ => Err(eyre!("Unsupported plain text file extension")),
68        }
69    }
70    fn read_json(path: PathBuf) -> ApiResult<Self> {
71        Self::read_text(path)
72    }
73    fn read_markdown(path: PathBuf) -> ApiResult<Self> {
74        Self::read_text(path)
75    }
76    fn read_yaml(path: PathBuf) -> ApiResult<Self> {
77        Self::read_text(path)
78    }
79    fn write(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
80        let output = path.into();
81        match MimeType::from(output.display().to_string()) {
82            | MimeType::Markdown | MimeType::Text => self.write_text(output),
83            | MimeType::Json => self.write_json(output),
84            | MimeType::Yaml => self.write_yaml(output),
85            | _ => Err(eyre!("Unsupported plain text file extension for writing")),
86        }
87    }
88    fn write_json(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
89        self.write_text(path)
90    }
91    fn write_markdown(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
92        self.write_text(path)
93    }
94    fn write_yaml(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
95        self.write_text(path)
96    }
97}
98#[cfg(feature = "std")]
99impl InputOutput for Docx {
100    fn read(path: impl Into<PathBuf>) -> ApiResult<Self> {
101        let source = path.into();
102        match DocumentFormat::try_from(&MimeType::from(source.display().to_string())) {
103            | Ok(_) => Self::read_docx(source),
104            | Err(_) => match file_extension(source.display().to_string()).as_deref() {
105                | Some("md") | Some("markdown") | Some("txt") | Some("json") | Some("yml") | Some("yaml") => Self::read_text(source),
106                | _ => Err(eyre!("Unsupported document file extension")),
107            },
108        }
109    }
110    fn read_json(path: PathBuf) -> ApiResult<Self> {
111        Self::read_text(path)
112    }
113    fn read_markdown(path: PathBuf) -> ApiResult<Self> {
114        Self::read_text(path)
115    }
116    fn read_yaml(path: PathBuf) -> ApiResult<Self> {
117        Self::read_text(path)
118    }
119    fn write(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
120        let output = path.into();
121        match file_extension(output.display().to_string()).as_deref() {
122            | Some("md") | Some("markdown") | Some("txt") | Some("json") | Some("yml") | Some("yaml") => self.write_text(output),
123            | Some("docx") => Err(eyre!("DOCX writing is not implemented")),
124            | _ => Err(eyre!("Unsupported DOCX file extension for writing")),
125        }
126    }
127    fn write_json(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
128        self.write_text(path)
129    }
130    fn write_markdown(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
131        self.write_text(path)
132    }
133    fn write_yaml(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
134        self.write_text(path)
135    }
136}
137impl MarkdownSupport for Docx {
138    fn to_markdown(&self) -> String {
139        self.content().to_string()
140    }
141}
142impl MarkdownSupport for Text {
143    fn to_markdown(&self) -> String {
144        self.content().to_string()
145    }
146}
147impl ToProse for Docx {
148    fn to_prose(&self) -> String {
149        self.content().to_string()
150    }
151}
152impl ToProse for Text {
153    fn to_prose(&self) -> String {
154        self.content().to_string()
155    }
156}
157impl Unstructured for Docx {
158    fn content(&self) -> &str {
159        &self.content
160    }
161}
162impl Unstructured for Text {
163    fn content(&self) -> &str {
164        &self.content
165    }
166}
167#[cfg(feature = "std")]
168impl Docx {
169    fn read_docx(path: impl Into<PathBuf>) -> ApiResult<Self> {
170        SourceDocument::at(path).extract().map(|content| Self { content })
171    }
172    fn read_text(path: impl Into<PathBuf>) -> ApiResult<Self> {
173        read_file(path.into()).map(|content| Self { content })
174    }
175    fn write_text(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
176        write_file(path, self.content().to_string())
177    }
178}
179#[cfg(feature = "std")]
180impl Text {
181    fn read_text(path: impl Into<PathBuf>) -> ApiResult<Self> {
182        read_file(path.into()).map(|content| Self { content })
183    }
184    fn write_text(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
185        write_file(path, self.content().to_string())
186    }
187}