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(&notefile, 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>(&notefile).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, _>(
///        &notedir, &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)
}