Trait tpnote_lib::content::Content
source · pub trait Content: AsRef<str> + Debug + Eq + PartialEq + Default + From<String> {
fn header(&self) -> &str;
fn body(&self) -> &str;
fn open(path: &Path) -> Result<Self, Error>
where
Self: Sized,
{ ... }
fn from_string_with_cr(input: String) -> Self { ... }
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
Provides cheap access to the header with header(), the body
with body(), and the whole raw text with as_str().
use tpnote_lib::content::Content;
use tpnote_lib::content::ContentString;
let input = "---\ntitle: \"My note\"\n---\nMy body";
let c = ContentString::from(String::from(input));
assert_eq!(c.header(), r#"title: "My note""#);
assert_eq!(c.body(), r#"My body"#);
assert_eq!(c.as_str(), input);
// A test without front matter leads to an empty header:
let c = ContentString::from(String::from("No header"));
assert_eq!(c.header(), "");
assert_eq!(c.body(), "No header");
assert_eq!(c.as_str(), "No header");The Content trait allows to plug in you own storage back end if
ContentString does not suit you. Note: you can overwrite
Content::open() and Content::save_as() also (note shown).
use tpnote_lib::content::Content;
use std::string::String;
#[derive(Debug, Eq, PartialEq, Default)]
struct MyString(String);
impl Content for MyString {
/// 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
}
}
impl From<String> for MyString {
fn from(input: String) -> Self {
Self(input)
}
}
impl AsRef<str> for MyString {
fn as_ref(&self) -> &str {
&self.0
}
}
let input = "---\ntitle: \"My note\"\n---\nMy body";
let s = MyString::from(input.to_string());
assert_eq!(s.header(), r#"title: "My note""#);
assert_eq!(s.body(), r#"My body"#);
assert_eq!(s.as_str(), input);Required Methods§
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(), r#"title: "My note""#);
assert_eq!(c.body(), "My body");Examples found in repository?
More examples
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
pub fn from<T: Content>(path: &Path, clipboard: &T, stdin: &T) -> (Self, Option<T>) {
let stdin_is_empty = stdin.is_empty();
let stdin_has_header = !stdin.header().is_empty();
let clipboard_is_empty = clipboard.is_empty();
let clipboard_has_header = !clipboard.header().is_empty();
let input_stream_is_some = !stdin_is_empty || !clipboard_is_empty;
let input_stream_has_header = stdin_has_header || clipboard_has_header;
let path_is_dir = path.is_dir();
let path_is_file = path.is_file();
let path_has_tpnote_extension = path.has_tpnote_extension();
let path_is_tpnote_file = path_is_file && path_has_tpnote_extension;
let (path_is_tpnote_file_and_has_header, content) = if path_is_tpnote_file {
let content: T = Content::open(path).unwrap_or_default();
(!content.header().is_empty(), Some(content))
} else {
(false, None)
};
// This determines the workflow and what template will be applied.
let template_kind = match (
path_is_dir,
input_stream_is_some,
input_stream_has_header,
path_is_file,
path_is_tpnote_file,
path_is_tpnote_file_and_has_header,
) {
(true, false, _, false, _, _) => TemplateKind::New,
(true, true, false, false, _, _) => TemplateKind::FromClipboard,
(true, true, true, false, _, _) => TemplateKind::FromClipboardYaml,
(false, _, _, true, true, true) => TemplateKind::SyncFilename,
(false, _, _, true, true, false) => TemplateKind::FromTextFile,
(false, _, _, true, false, _) => TemplateKind::AnnotateFile,
(_, _, _, _, _, _) => TemplateKind::None,
};
log::debug!("Chosing the \"{:?}\" template.", template_kind);
log::trace!(
"Template choice is based on:
path=\"{}\",
path_is_dir={},
input_stream_is_some={},
input_stream_has_header={},
path_is_file={},
path_is_tpnote_file={},
path_is_tpnote_file_and_has_header={}",
path.to_str().unwrap(),
path_is_dir,
input_stream_is_some,
input_stream_has_header,
path_is_file,
path_is_tpnote_file,
path_is_tpnote_file_and_has_header,
);
(template_kind, content)
}sourcefn from_string_with_cr(input: String) -> Self
fn from_string_with_cr(input: 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"));
assert_eq!(c.header(), r#"title: "My note""#);
assert_eq!(c.body(), "My\nbody\n");
// A test without front matter leads to an empty header:
let c = ContentString::from(String::from("No header"));
assert_eq!(c.borrow_dependent().header, "");
assert_eq!(c.borrow_dependent().body, r#"No header"#);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::from("prelude\n\n---\ntitle: \"My note\"\n---\nMy body"));
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.
Examples found in repository?
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
fn save_as(&self, new_file_path: &Path) -> Result<(), std::io::Error> {
// Create missing directories, if there are any.
create_dir_all(new_file_path.parent().unwrap_or_else(|| Path::new("")))?;
let mut outfile = OpenOptions::new()
.write(true)
.create(true)
.open(new_file_path)?;
log::trace!("Creating file: {:?}", new_file_path);
write!(outfile, "\u{feff}")?;
for l in self.as_str().lines() {
write!(outfile, "{}", l)?;
#[cfg(target_family = "windows")]
write!(outfile, "\r")?;
writeln!(outfile)?;
}
Ok(())
}More examples
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
pub fn render_erroneous_content_html<T: Content>(
mut context: Context,
note_erroneous_content: T,
) -> Result<String, NoteError> {
// Render to HTML.
let note_erroneous_content = text_rawlinks2html(note_erroneous_content.as_str());
// Insert.
context.insert(
TMPL_HTML_VAR_NOTE_ERRONEOUS_CONTENT_HTML,
¬e_erroneous_content,
);
let tmpl_html = &LIB_CFG.read().unwrap().tmpl_html.viewer_error;
// Apply template.
let mut tera = Tera::default();
tera.extend(&TERA)?;
let html = tera
.render_str(tmpl_html, &context)
.map_err(|e| note_error_tera_template!(e, "[html_tmpl] viewer_error".to_string()))?;
Ok(html)
}sourcefn is_empty(&self) -> bool
fn is_empty(&self) -> bool
True if the header and body is empty.
Examples found in repository?
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
pub fn from<T: Content>(path: &Path, clipboard: &T, stdin: &T) -> (Self, Option<T>) {
let stdin_is_empty = stdin.is_empty();
let stdin_has_header = !stdin.header().is_empty();
let clipboard_is_empty = clipboard.is_empty();
let clipboard_has_header = !clipboard.header().is_empty();
let input_stream_is_some = !stdin_is_empty || !clipboard_is_empty;
let input_stream_has_header = stdin_has_header || clipboard_has_header;
let path_is_dir = path.is_dir();
let path_is_file = path.is_file();
let path_has_tpnote_extension = path.has_tpnote_extension();
let path_is_tpnote_file = path_is_file && path_has_tpnote_extension;
let (path_is_tpnote_file_and_has_header, content) = if path_is_tpnote_file {
let content: T = Content::open(path).unwrap_or_default();
(!content.header().is_empty(), Some(content))
} else {
(false, None)
};
// This determines the workflow and what template will be applied.
let template_kind = match (
path_is_dir,
input_stream_is_some,
input_stream_has_header,
path_is_file,
path_is_tpnote_file,
path_is_tpnote_file_and_has_header,
) {
(true, false, _, false, _, _) => TemplateKind::New,
(true, true, false, false, _, _) => TemplateKind::FromClipboard,
(true, true, true, false, _, _) => TemplateKind::FromClipboardYaml,
(false, _, _, true, true, true) => TemplateKind::SyncFilename,
(false, _, _, true, true, false) => TemplateKind::FromTextFile,
(false, _, _, true, false, _) => TemplateKind::AnnotateFile,
(_, _, _, _, _, _) => TemplateKind::None,
};
log::debug!("Chosing the \"{:?}\" template.", template_kind);
log::trace!(
"Template choice is based on:
path=\"{}\",
path_is_dir={},
input_stream_is_some={},
input_stream_has_header={},
path_is_file={},
path_is_tpnote_file={},
path_is_tpnote_file_and_has_header={}",
path.to_str().unwrap(),
path_is_dir,
input_stream_is_some,
input_stream_has_header,
path_is_file,
path_is_tpnote_file,
path_is_tpnote_file_and_has_header,
);
(template_kind, content)
}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
---\nor ignore all bytes until\n\n---\n, - followed by header bytes,
- optionally followed by
\n, - followed by
\n---\nor\n...\n, - optionally followed by some
\tand/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 …
Implementors§
impl Content for ContentString
Add header() and body() implementation.