pub trait Content:
AsRef<str>
+ Debug
+ Eq
+ PartialEq
+ Default {
// Required methods
fn from_string(input: String, name: String) -> Self;
fn header(&self) -> &str;
fn body(&self) -> &str;
fn name(&self) -> &str;
// Provided methods
fn open(path: &Path) -> Result<Self, Error>
where Self: Sized { ... }
fn from_string_with_cr(input: String, name: String) -> Self { ... }
fn from_html(input: String, name: String) -> Result<Self, InputStreamError> { ... }
fn save_as(&self, new_file_path: &Path) -> Result<(), Error> { ... }
fn as_str(&self) -> &str { ... }
fn is_empty(&self) -> bool { ... }
fn split(content: &str) -> (&str, &str) { ... }
}
Expand description
This trait represents Tp-Note content.
The content is devided into header and body.
The header is the YAML meta data describing the body.
In some cases the header might be empty, e.g. when the data comes from
the clipboard (the txt_clipboard
data might come with a header).
The body is flat UTF-8 markup formatted text, e.g. in
Markdown or in ReStructuredText.
A special case is HTML data in the body, originating from the HTML
clipboard. Here, the body always starts with an HTML start tag
(for details see the html::HtmlStream
trait) and the header is always
empty.
The trait provides cheap access to the header with header()
, the body
with body()
, and the whole raw text with as_str()
.
Implementers should cache the header()
and body()
function results in
order to keep these as cheap as possible.
use tpnote_lib::content::Content;
use tpnote_lib::content::ContentString;
let input = "---\ntitle: My note\n---\nMy body";
let c = ContentString::from_string(
String::from(input), "doc".to_string());
assert_eq!(c.header(), "title: My note");
assert_eq!(c.body(), "My body");
assert_eq!(c.name(), "doc");
assert_eq!(c.as_str(), input);
// A test without front matter leads to an empty header:
let c = ContentString::from_string(
String::from("No header"), "doc".to_string());
assert_eq!(c.header(), "");
assert_eq!(c.body(), "No header");
assert_eq!(c.name(), "doc");
assert_eq!(c.as_str(), "No header");
The Content
trait allows to plug in your own storage back end if
ContentString
does not suit you. In addition to the example shown below,
you can overwrite Content::open()
and Content::save_as()
as well.
use tpnote_lib::content::Content;
use std::string::String;
#[derive(Debug, Eq, PartialEq, Default)]
struct MyString(String, String);
impl Content for MyString {
/// Constructor
fn from_string(input: String,
name: String) -> Self {
MyString(input, name)
}
/// This sample implementation may be too expensive.
/// Better precalculate this in `Self::from()`.
fn header(&self) -> &str {
Self::split(&self.as_str()).0
}
fn body(&self) -> &str {
Self::split(&self.as_str()).1
}
fn name(&self) -> &str {
&self.1
}
}
impl AsRef<str> for MyString {
fn as_ref(&self) -> &str {
&self.0
}
}
let input = "---\ntitle: My note\n---\nMy body";
let s = MyString::from_string(
input.to_string(), "doc".to_string());
assert_eq!(s.header(), "title: My note");
assert_eq!(s.body(), "My body");
assert_eq!(s.name(), "doc");
assert_eq!(s.as_str(), input);
Required Methods§
Sourcefn from_string(input: String, name: String) -> Self
fn from_string(input: String, name: String) -> Self
Constructor that parses a Tp-Note document.
A valid document is UTF-8 encoded and starts with an optional
BOM (byte order mark) followed by ---
. When the start marker
---
does not follow directly the BOM, it must be prepended
by an empty line. In this case all text before is ignored:
BOM + ignored text + empty line + ---
.
Contract: the input string does not contain \r\n
. If
it may, use Content::from_string_with_cr()
instead.
use tpnote_lib::content::Content;
use tpnote_lib::content::ContentString;
let input = "---\ntitle: My note\n---\nMy body";
let c = ContentString::from_string(
input.to_string(), "doc".to_string());
assert_eq!(c.header(), "title: My note");
assert_eq!(c.body(), "My body");
assert_eq!(c.name(), "doc");
// A test without front matter leads to an empty header:
let c = ContentString::from_string("No header".to_string(),
"doc".to_string());
assert_eq!(c.header(), "");
assert_eq!(c.body(), "No header");
assert_eq!(c.name(), "doc");
Self referential. The constructor splits the content in header and body and associates names to both. These names are referenced in various templates.
Provided Methods§
Sourcefn open(path: &Path) -> Result<Self, Error>where
Self: Sized,
fn open(path: &Path) -> Result<Self, Error>where
Self: Sized,
Reads the file at path
and stores the content
Content
. Possible \r\n
are replaced by \n
.
This trait has a default implementation, the empty content.
use tpnote_lib::content::Content;
use tpnote_lib::content::ContentString;
use std::env::temp_dir;
// Prepare test.
let raw = "---\ntitle: My note\n---\nMy body";
let notefile = temp_dir().join("20221030-hello -- world.md");
let _ = std::fs::write(¬efile, raw.as_bytes());
// Start test.
let c = ContentString::open(¬efile).unwrap();
assert_eq!(c.header(), "title: My note");
assert_eq!(c.body(), "My body");
assert_eq!(c.name(), "doc");
Sourcefn from_string_with_cr(input: String, name: String) -> Self
fn from_string_with_cr(input: String, name: String) -> Self
Constructor that reads a structured document with a YAML header
and body. All \r\n
are converted to \n
if there are any.
If not no memory allocation occurs and the buffer remains untouched.
use tpnote_lib::content::Content;
use tpnote_lib::content::ContentString;
let c = ContentString::from_string_with_cr(String::from(
"---\r\ntitle: My note\r\n---\r\nMy\nbody\r\n"),
"doc".to_string(),
);
assert_eq!(c.header(), "title: My note");
assert_eq!(c.body(), "My\nbody\n");
assert_eq!(c.borrow_dependent().name, "doc");
// A test without front matter leads to an empty header:
let c = ContentString::from_string(
"No header".to_string(), "doc".to_string());
assert_eq!(c.borrow_dependent().header, "");
assert_eq!(c.borrow_dependent().body, "No header");
assert_eq!(c.borrow_dependent().name, "doc");
Sourcefn from_html(input: String, name: String) -> Result<Self, InputStreamError>
fn from_html(input: String, name: String) -> Result<Self, InputStreamError>
Constructor that accepts and store HTML input in the body.
If the HTML input does not start with <!DOCTYPE html...>
it is
automatically prepended.
If the input starts with another DOCTYPE than HTMl, return
InputStreamError::NonHtmlDoctype
.
use tpnote_lib::content::Content;
use tpnote_lib::content::ContentString;
let c = ContentString::from_html(
"Some HTML content".to_string(),
"html_clipboard".to_string()).unwrap();
assert_eq!(c.header(), "");
assert_eq!(c.body(), "<!DOCTYPE html>Some HTML content");
assert_eq!(c.name(), "html_clipboard");
let c = ContentString::from_html(String::from(
"<!DOCTYPE html>Some HTML content"),
"html_clipboard".to_string()).unwrap();
assert_eq!(c.header(), "");
assert_eq!(c.body(), "<!DOCTYPE html>Some HTML content");
assert_eq!(c.name(), "html_clipboard");
let c = ContentString::from_html(String::from(
"<!DOCTYPE xml>Some HTML content"), "".to_string());
assert!(c.is_err());
Sourcefn save_as(&self, new_file_path: &Path) -> Result<(), Error>
fn save_as(&self, new_file_path: &Path) -> Result<(), Error>
Writes the note to disk with new_file_path
as filename.
If new_file_path
contains missing directories, they will be
created on the fly.
use std::path::Path;
use std::env::temp_dir;
use std::fs;
use tpnote_lib::content::Content;
use tpnote_lib::content::ContentString;
let c = ContentString::from_string(
String::from("prelude\n\n---\ntitle: My note\n---\nMy body"),
"doc".to_string()
);
let outfile = temp_dir().join("mynote.md");
#[cfg(not(target_family = "windows"))]
let expected = "\u{feff}prelude\n\n---\ntitle: My note\n---\nMy body\n";
#[cfg(target_family = "windows")]
let expected = "\u{feff}prelude\r\n\r\n---\r\ntitle: My note\r\n---\r\nMy body\r\n";
c.save_as(&outfile).unwrap();
let result = fs::read_to_string(&outfile).unwrap();
assert_eq!(result, expected);
fs::remove_file(&outfile);
Sourcefn as_str(&self) -> &str
fn as_str(&self) -> &str
Accesses the whole content with all ---
.
Contract: The content does not contain any \r\n
.
If your content contains \r\n
use the
from_string_with_cr()
constructor.
Possible BOM at the first position is not returned.
Sourcefn is_empty(&self) -> bool
fn is_empty(&self) -> bool
True if the header and body is empty.
use tpnote_lib::content::Content;
use tpnote_lib::content::ContentString;
let c = ContentString::default();
assert_eq!(c.header(), "");
assert_eq!(c.body(), "");
assert!(c.is_empty());
let c = ContentString::from_string(
"".to_string(),
"doc".to_string(),
);
assert_eq!(c.header(), "");
assert_eq!(c.body(), "");
assert!(c.is_empty());
let c = ContentString::from_string(
"Some content".to_string(),
"doc".to_string(),
);
assert_eq!(c.header(), "");
assert_eq!(c.body(), "Some content");
assert!(!c.is_empty());
let c = ContentString::from_html(
"".to_string(),
"doc".to_string(),
).unwrap();
assert_eq!(c.header(), "");
assert_eq!(c.body(), "<!DOCTYPE html>");
assert!(c.is_empty());
let c = ContentString::from_html(
"Some HTML content".to_string(),
"doc".to_string(),
).unwrap();
assert_eq!(c.header(), "");
assert_eq!(c.body(), "<!DOCTYPE html>Some HTML content");
assert!(!c.is_empty());
let c = ContentString::from_string(
String::from("---\ntitle: My note\n---\n"),
"doc".to_string(),
);
assert_eq!(c.header(), "title: My note");
assert_eq!(c.body(), "");
assert!(!c.is_empty());
Sourcefn split(content: &str) -> (&str, &str)
fn split(content: &str) -> (&str, &str)
Helper function that splits the content into header and body.
The header, if present, is trimmed (trim()
), the body
is kept as it is.
Any BOM (byte order mark) at the beginning is ignored.
- Ignore
\u{feff}
if present - Ignore
---\n
or ignore all bytes until\n\n---\n
, - followed by header bytes,
- optionally followed by
\n
, - followed by
\n---\n
or\n...\n
, - optionally followed by some
\t
and/or some - optionally followed by
\n
.
The remaining bytes are the “body”.
Alternatively, a YAML metadata block may occur anywhere in the document, but if it is not at the beginning, it must be preceded by a blank line:
- skip all text (BEFORE_HEADER_MAX_IGNORED_CHARS) until you find
"\n\n---"
- followed by header bytes,
- same as above …
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.
Implementors§
impl Content for ContentString
Add header()
and body()
implementation.