Trait Content

Source
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§

Source

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.

Source

fn header(&self) -> &str

Returns a reference to the inner part in between ---.

Source

fn body(&self) -> &str

Returns the body below the second ---.

Source

fn name(&self) -> &str

Returns the associated name exactly as it was given to the constructor.

Provided Methods§

Source

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

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

assert_eq!(c.header(), "title: My note");
assert_eq!(c.body(), "My body");
assert_eq!(c.name(), "doc");
Source

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");
Source

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());
Source

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);
Source

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.

Source

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());
Source

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.

  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 …

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§

Source§

impl Content for ContentString

Add header() and body() implementation.