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§

Return a reference to theader part in between ---

Return the body below the second ---.

Provided Methods§

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(&notefile, raw.as_bytes());

// Start test.
let c = ContentString::open(&notefile).unwrap();

assert_eq!(c.header(), r#"title: "My note""#);
assert_eq!(c.body(), "My body");
Examples found in repository?
src/workflow.rs (line 193)
189
190
191
192
193
194
195
196
197
pub fn synchronize_filename<T: Content>(path: &Path) -> Result<PathBuf, NoteError> {
    // Collect input data for templates.
    let context = Context::from(path);

    let content = <T>::open(path).unwrap_or_default();
    let n = synchronize::<T>(context, content)?;

    Ok(n.rendered_filename)
}
More examples
Hide additional examples
src/template.rs (line 54)
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)
    }

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"#);
Examples found in repository?
src/content.rs (line 105)
101
102
103
104
105
106
    fn open(path: &Path) -> Result<Self, std::io::Error>
    where
        Self: Sized,
    {
        Ok(Self::from_string_with_cr(read_to_string(path)?))
    }

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);
Examples found in repository?
src/note.rs (line 303)
296
297
298
299
300
301
302
303
304
305
    pub fn save(&self) -> Result<(), NoteError> {
        debug_assert_ne!(self.rendered_filename, PathBuf::new());

        log::trace!(
            "Writing the note's content to file: {:?}",
            self.rendered_filename
        );
        self.content.save_as(&self.rendered_filename)?;
        Ok(())
    }

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?
src/content.rs (line 181)
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
Hide additional examples
src/workflow.rs (line 542)
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,
        &note_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)
}

True if the header and body is empty.

Examples found in repository?
src/template.rs (line 38)
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)
    }

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.

  1. Ignore \u{feff} if present
  2. Ignore ---\n or ignore all bytes until\n\n---\n,
  3. followed by header bytes,
  4. optionally followed by \n,
  5. followed by \n---\n or \n...\n,
  6. optionally followed by some \t and/or some ,
  7. 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:

  1. skip all text (BEFORE_HEADER_MAX_IGNORED_CHARS) until you find "\n\n---"
  2. followed by header bytes,
  3. same as above …
Examples found in repository?
src/content.rs (line 362)
360
361
362
363
364
365
    fn from(input: String) -> Self {
        ContentString::new(input, |owner: &String| {
            let (header, body) = ContentString::split(owner);
            ContentRef { header, body }
        })
    }

Implementors§

Add header() and body() implementation.