1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
//! Tp-Note's high level API. The low level API is documented
//! in the module `tpnote_lib::note`.
//!
//! How to integrate this in your text editor code?
//! First, call `create_new_note_or_synchronize_filename()`
//! with the first positional command line parameter `<path>`.
//! Then open the text file `<Note>.rendered_filename` in your
//! text editor or alternatively, load the string
//! `<Note>.content.as_str()` directly into your text editor.
//! After saving the text file, call `synchronize_filename()`
//! and update your file path with `<Note>.rendered_filename`.
use crate::config::LIB_CFG;
use crate::config::TMPL_VAR_CLIPBOARD;
use crate::config::TMPL_VAR_CLIPBOARD_HEADER;
use crate::config::TMPL_VAR_FM_;
use crate::config::TMPL_VAR_FM_FILENAME_SYNC;
use crate::config::TMPL_VAR_FM_NO_FILENAME_SYNC;
use crate::config::TMPL_VAR_STDIN;
use crate::config::TMPL_VAR_STDIN_HEADER;
use crate::content::Content;
use crate::context::Context;
use crate::error::NoteError;
use crate::note::Note;
use crate::template::TemplateKind;
use std::path::Path;
use std::path::PathBuf;
use tera::Value;
/// Open the note file `path` on disk and read its YAML front matter.
/// Then calculate from the front matter how the filename should be to
/// be in sync. If it is different, rename the note on disk.
/// Returns the note's new or existing filename in `<Note>.rendered_filename`.
///
/// ## Example with `TemplateKind::SyncFilename`
///
/// ```rust
/// use tpnote_lib::content::ContentString;
/// use tpnote_lib::workflow::synchronize_filename;
/// use std::env::temp_dir;
/// use std::fs;
/// use std::path::Path;
///
/// // Prepare test: create existing note.
/// let raw = r#"
///
/// ---
/// title: "My day"
/// subtitle: "Note"
/// ---
/// Body text
/// "#;
/// let notefile = temp_dir().join("20221030-hello.md");
/// fs::write(¬efile, raw.as_bytes()).unwrap();
///
/// let expected = temp_dir().join("20221030-My day--Note.md");
/// let _ = fs::remove_file(&expected);
///
/// // Start test.
/// // You can plug in your own type (must impl. `Content`).
/// let n = synchronize_filename::<ContentString>(¬efile).unwrap();
/// let res_fn = n.rendered_filename;
///
/// // Check result
/// assert_eq!(res_fn, expected);
/// assert!(res_fn.is_file());
/// let res_raw = fs::read_to_string(&res_fn).unwrap();
/// assert_eq!(res_raw, raw);
/// ```
pub fn synchronize_filename<T: Content>(path: &Path) -> Result<Note<T>, NoteError> {
// Collect input data for templates.
let mut context = Context::from(&path);
context.insert_environment()?;
let content = <T>::open(&path).unwrap_or_default();
let n = synchronize::<T>(context, content)?;
Ok(n)
}
#[inline]
/// Create a new note by inserting `Tp-Note`'s environment in a template.
/// If the note to be created exists already, append a so called `copy_counter`
/// to the filename and try to save it again. In case this does not succeed either,
/// increment the `copy_counter` until a free filename is found.
/// The return path in `<Note>.rendered_filename` points to the (new) note file on disk.
/// Depending on the context, Tp-Note chooses one `TemplateKind` to operate
/// (c.f. `tpnote_lib::template::TemplateKind::from()`).
/// The `tk-filter` allows to overwrite this choice, e.g. you may set
/// `TemplateKind::None` under certain circumstances. This way the caller
/// can inject command line parameters like `--no-filename-sync`.
///
/// Returns the note's new or existing filename in `<Note>.rendered_filename`.
///
/// ## Example with `TemplateKind::FromClipboard`
///
/// ```rust
/// use tpnote_lib::content::Content;
/// use tpnote_lib::content::ContentString;
/// use tpnote_lib::workflow::create_new_note_or_synchronize_filename;
/// use std::env::temp_dir;
/// use std::path::PathBuf;
/// use std::fs;
///
/// // Prepare test.
/// let notedir = temp_dir();
///
/// let clipboard = ContentString::from_string("my clipboard\n".to_string());
/// let stdin = ContentString::from_string("my stdin\n".to_string());
/// // This is the condition to choose: `TemplateKind::FromClipboard`:
/// assert!(clipboard.header().is_empty() && stdin.header().is_empty());
/// assert!(!clipboard.body().is_empty() || !stdin.body().is_empty());
/// let template_kind_filer = |tk|tk;
///
/// // Start test.
/// // You can plug in your own type (must impl. `Content`).
/// let n = create_new_note_or_synchronize_filename::<ContentString, _>(
/// ¬edir, &clipboard, &stdin, template_kind_filer, None).unwrap();
/// // Check result.
/// assert!(n.rendered_filename.as_os_str().to_str().unwrap()
/// .contains("my stdin-my clipboard--Note"));
/// assert!(n.rendered_filename.is_file());
/// let raw_note = fs::read_to_string(n.rendered_filename).unwrap();
/// assert!(raw_note.starts_with("\u{feff}---\ntitle: \"my stdin\\nmy clipboard\\n\""));
/// ```
pub fn create_new_note_or_synchronize_filename<T, F>(
path: &Path,
clipboard: &T,
stdin: &T,
tk_filter: F,
args_export: Option<&Path>,
) -> Result<Note<T>, NoteError>
where
T: Content,
F: Fn(TemplateKind) -> TemplateKind,
{
// First, generate a new note (if it does not exist), then parse its front_matter
// and finally rename the file, if it is not in sync with its front matter.
// Collect input data for templates.
let mut context = Context::from(path);
context.insert_environment()?;
context.insert_content(TMPL_VAR_CLIPBOARD, TMPL_VAR_CLIPBOARD_HEADER, clipboard)?;
context.insert_content(TMPL_VAR_STDIN, TMPL_VAR_STDIN_HEADER, stdin)?;
// `template_king` will tell us what to do.
let (template_kind, content) = TemplateKind::from::<T>(path, clipboard, stdin);
let template_kind = tk_filter(template_kind);
let n = match template_kind {
TemplateKind::New
| TemplateKind::FromClipboardYaml
| TemplateKind::FromClipboard
| TemplateKind::AnnotateFile => {
// CREATE A NEW NOTE WITH `TMPL_NEW_CONTENT` TEMPLATE
let mut n = Note::from_content_template(context, template_kind)?;
n.render_filename(template_kind)?;
// Check if the filename is not taken already
n.set_next_unused_rendered_filename()?;
n.save()?;
n
}
TemplateKind::FromTextFile => {
let mut n = Note::from_text_file(context, content.unwrap(), template_kind)?;
// Render filename.
n.render_filename(template_kind)?;
// Save new note.
let context_path = n.context.path.clone();
n.set_next_unused_rendered_filename_or(&context_path)?;
n.save_and_delete_from(&context_path)?;
n
}
TemplateKind::SyncFilename => synchronize(context, content.unwrap())?,
TemplateKind::None => Note::from_text_file(context, content.unwrap(), template_kind)?,
};
// Export HTML rendition, if wanted.
if let Some(dir) = args_export {
n.export_html(&LIB_CFG.read().unwrap().tmpl_html.exporter, dir)?;
}
// If no new filename was rendered, return the old one.
let mut n = n;
if n.rendered_filename == PathBuf::new() {
n.rendered_filename = n.context.path.clone();
}
Ok(n)
}
/// Helper function.
fn synchronize<T: Content>(context: Context, content: T) -> Result<Note<T>, NoteError> {
// parse file again to check for synchronicity with filename
let mut n = Note::from_text_file(context, content, TemplateKind::SyncFilename)?;
let no_filename_sync = match (
n.context.get(TMPL_VAR_FM_FILENAME_SYNC),
n.context.get(TMPL_VAR_FM_NO_FILENAME_SYNC),
) {
// By default we sync.
(None, None) => false,
(None, Some(Value::Bool(nsync))) => *nsync,
(None, Some(_)) => true,
(Some(Value::Bool(sync)), None) => !*sync,
_ => false,
};
if no_filename_sync {
log::info!(
"Filename synchronisation disabled with the front matter field: `{}: {}`",
TMPL_VAR_FM_FILENAME_SYNC.trim_start_matches(TMPL_VAR_FM_),
!no_filename_sync
);
} else {
n.render_filename(TemplateKind::SyncFilename)?;
n.set_next_unused_rendered_filename_or(&n.context.path.clone())?;
// Silently fails is source and target are identical.
n.rename_file_from(&n.context.path)?;
}
Ok(n)
}