use crate::content::Content;
use crate::error::FileError;
use crate::error::NoteError;
use crate::filename;
use crate::VERSION;
use atty::{is, Stream};
#[cfg(feature = "read-clipboard")]
use clipboard::ClipboardContext;
#[cfg(feature = "read-clipboard")]
use clipboard::ClipboardProvider;
use confy::ConfyError;
use directories::ProjectDirs;
use lazy_static::lazy_static;
use log::LevelFilter;
use parse_hyperlinks::iterator::first_hyperlink;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::io;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::sync::RwLock;
use structopt::StructOpt;
const CURRENT_EXE: &str = "tp-note";
const CONFIG_FILENAME: &str = "tp-note.toml";
const DEBUG_ARG_DEFAULT: LevelFilter = LevelFilter::Error;
const EDITOR_ARG_DEFAULT: bool = false;
const POPUP_ARG_DEFAULT: bool = true;
const NO_FILENAME_SYNC_ARG_DEFAULT: bool = false;
const SILENTLY_IGNORE_MISSING_HEADER: bool = true;
pub const EXTENSION_DEFAULT: &str = "md";
pub const NOTE_FILE_EXTENSIONS_MD: &[&str] = &["md", "markdown", "markdn", "mdown", "mdtxt"];
pub const NOTE_FILE_EXTENSIONS_RST: &[&str] = &["rst", "rest"];
pub const NOTE_FILE_EXTENSIONS_HTML: &[&str] = &["htmlnote"];
pub const NOTE_FILE_EXTENSIONS_TXT: &[&str] = &["txtnote", "adoc", "asciidoc"];
pub const NOTE_FILE_EXTENSIONS_NO_VIEWER: &[&str] = &["t2t", "textile", "twiki", "mediawiki"];
#[cfg(not(test))]
pub const NOTE_FILENAME_LEN_MAX: usize =
255
- COPY_COUNTER_EXTRA_SEPARATOR.len()
- COPY_COUNTER_OPENING_BRACKETS.len() - 2 - COPY_COUNTER_CLOSING_BRACKETS.len()
- 6;
#[cfg(test)]
pub const NOTE_FILENAME_LEN_MAX: usize = 10;
const TMPL_NEW_CONTENT: &str = "\
---
title: {{ dir_path | trim_tag | cut | json_encode }}
subtitle: {{ 'Note' | json_encode }}
author: {{ username | json_encode }}
date: {{ now() | date(format='%Y-%m-%d') | json_encode }}
lang: {{ get_env(name='LANG', default='') | json_encode }}
---
";
const TMPL_NEW_FILENAME: &str = "\
{{ now() | date(format='%Y%m%d-') }}\
{{ fm_title | sanit(alpha=true) }}{% if fm_subtitle | default(value='') | sanit != '' %}--{% endif %}\
{{ fm_subtitle | default(value='') | sanit }}{{ extension_default | prepend_dot }}\
";
const TMPL_COPY_CONTENT: &str = "\
---
title: {{ fm_title | default(value = path|trim_tag) | cut | json_encode }}
subtitle: {{ fm_subtitle | default(value = 'Note') | cut | json_encode }}
author: {{ fm_author | default(value=username) | json_encode }}
date: {{ fm_date | default(value = now()|date(format='%Y-%m-%d')) | json_encode }}
lang: {{ fm_lang | default(value = get_env(name='LANG', default='')) | json_encode }}
{% for k, v in fm_all\
| remove(var='fm_title')\
| remove(var='fm_subtitle')\
| remove(var='fm_author')\
| remove(var='fm_date')\
| remove(var='fm_lang') %}\
{{ k }}:\t\t{{ v | json_encode }}
{% endfor %}\
---
{{ stdin ~ clipboard }}
";
const TMPL_COPY_FILENAME: &str = "\
{{ fm_sort_tag | default(value = now() | date(format='%Y%m%d-')) }}\
{{ fm_title | sanit(alpha=true) }}\
{% if fm_subtitle | default(value='') | sanit != '' %}--{% endif %}\
{{ fm_subtitle | default(value='') | sanit }}\
{{ fm_file_ext | default(value = extension_default ) | prepend_dot }}\
";
const TMPL_CLIPBOARD_CONTENT: &str = "\
{%- set lname = stdin ~ clipboard | linkname -%}
{%- set ok_linkname = lname !=''\
and not lname is starting_with(\"http\")\
and not lname is starting_with(\"HTTP\") -%}
---
{% if ok_linkname %}\
title: {{ stdin ~ clipboard | linkname | cut | json_encode }}
{% else %}\
title: {{ stdin ~ clipboard | heading | cut | json_encode }}
{% endif %}\
{% if stdin ~ clipboard | linkname !='' and stdin ~ clipboard | heading == stdin ~ clipboard %}\
subtitle: {{ 'URL' | json_encode }}
{% else %}\
subtitle: {{ 'Note' | json_encode }}
{% endif %}\
author: {{ username | json_encode }}
date: {{ now() | date(format='%Y-%m-%d') | json_encode }}
lang: {{ get_env(name='LANG', default='') | json_encode }}
---
{{ stdin ~ clipboard }}
";
const TMPL_CLIPBOARD_FILENAME: &str = "\
{{ now() | date(format='%Y%m%d-') }}\
{{ fm_title | sanit(alpha=true) }}\
{% if fm_subtitle | default(value='') | sanit != '' %}--{% endif %}\
{{ fm_subtitle | default(value='') | sanit }}{{ extension_default | prepend_dot }}\
";
const TMPL_ANNOTATE_CONTENT: &str = "\
---
title: {% filter json_encode %}{{ path | stem }}{{ path | copy_counter }}\
{{ path | ext | prepend_dot }}{% endfilter %}
{% if stdin ~ clipboard | linkname !='' and stdin ~ clipboard | heading == stdin ~ clipboard %}\
subtitle: {{ 'URL' | json_encode }}
{% else %}\
subtitle: {{ 'Note' | json_encode }}
{% endif %}\
author: {{ username | json_encode }}
date: {{ now() | date(format='%Y-%m-%d') | json_encode }}
lang: {{ get_env(name='LANG', default='') | json_encode }}
---
[{{ path | filename }}](<{{ path | filename }}>)
{% if stdin ~ clipboard != '' %}{% if stdin ~ clipboard != stdin ~ clipboard | heading %}
---
{% endif %}
{{ stdin ~ clipboard }}
{% endif %}
";
const TMPL_ANNOTATE_FILENAME: &str = "\
{{ path | tag }}{{ fm_title | sanit(alpha=true) }}\
{% if fm_subtitle | default(value='') | sanit != '' %}--{% endif %}\
{{ fm_subtitle | default(value='') | sanit }}{{ extension_default | prepend_dot }}\
";
const TMPL_SYNC_FILENAME: &str = "\
{{ fm_sort_tag | default(value = path | tag) }}\
{{ fm_title | default(value='No title') | sanit(alpha=true) }}\
{% if fm_subtitle | default(value='') | sanit != '' %}--{% endif %}\
{{ fm_subtitle | default(value='') | sanit }}\
{{ fm_file_ext | default(value = path | ext) | prepend_dot }}\
";
const TMPL_COMPULSORY_FIELD_CONTENT: &str = "title";
#[cfg(all(target_family = "unix", not(target_vendor = "apple")))]
const EDITOR_ARGS: &[&[&str]] = &[
&["code", "-w", "-n"],
&["flatpak", "run", "com.visualstudio.code", "-w", "-n"],
&["atom", "-w"],
&["marktext", "--no-sandbox", "--new-window"],
&[
"flatpak",
"run",
"com.github.marktext.marktext",
"--new-window",
],
&["typora"],
&["retext"],
&["geany", "-s", "-i", "-m"],
&["gedit", "-w"],
&["mousepad", "--disable-server"],
&["leafpad"],
&["nvim-qt", "--nofork"],
&["gvim", "--nofork"],
];
#[cfg(target_family = "windows")]
const EDITOR_ARGS: &[&[&str]] = &[
&["C:\\Program Files\\Typora\\Typora.exe"],
&[
"C:\\Program Files\\Mark Text\\Mark Text.exe",
"--new-window",
],
&[
"C:\\Program Files\\Notepad++\\notepad++.exe",
"-nosession",
"-multiInst",
],
&["C:\\Windows\\notepad.exe"],
];
#[cfg(all(target_family = "unix", target_vendor = "apple"))]
const EDITOR_ARGS: &[&[&str]] = &[
&["code", "-w", "-n"],
&["atom", "-w"],
&["marktext", "--no-sandbox"],
&["typora"],
&["gvim", "--nofork"],
&["mate"],
&["open", "-a", "TextEdit"],
&["open", "-a", "TextMate"],
&["open"],
];
#[cfg(all(target_family = "unix", not(target_vendor = "apple")))]
const EDITOR_CONSOLE_ARGS: &[&[&str]] = &[&["nano"], &["nvim"], &["vim"], &["emacs"], &["vi"]];
#[cfg(target_family = "windows")]
const EDITOR_CONSOLE_ARGS: &[&[&str]] = &[&[]];
#[cfg(all(target_family = "unix", target_vendor = "apple"))]
const EDITOR_CONSOLE_ARGS: &[&[&str]] = &[
&["nano"],
&["pico"],
&["nvim"],
&["vim"],
&["emacs"],
&["vi"],
];
#[cfg(all(target_family = "unix", not(target_vendor = "apple")))]
const BROWSER_ARGS: &[&[&str]] = &[
&["firefox", "--new-window"],
&["flatpak", "run", "org.mozilla.firefox", "--new-window"],
&["firefox-esr", "--new-window"],
&["chromium-browser", "--new-window"],
&[
"flatpak",
"run",
"com.github.Eloston.UngoogledChromium",
"--new-window",
],
&["flatpak", "run", "org.chromium.Chromium", "--new-window"],
&["chrome", "--new-window"],
];
#[cfg(target_family = "windows")]
const BROWSER_ARGS: &[&[&str]] = &[
&[
"C:\\Program Files\\Mozilla Firefox\\firefox.exe",
"--new-window",
],
&[
"C:\\Program Files\\Google\\Chrome\\Application\\chrome",
"--new-window",
],
&["C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"],
];
#[cfg(all(target_family = "unix", target_vendor = "apple"))]
const BROWSER_ARGS: &[&[&str]] = &[];
const CLIPBOARD_READ_ENABLED: bool = true;
const CLIPBOARD_EMPTY_ENABLED: bool = true;
const COPY_COUNTER_EXTRA_SEPARATOR: &str = "-";
const COPY_COUNTER_OPENING_BRACKETS: &str = "(";
const COPY_COUNTER_CLOSING_BRACKETS: &str = ")";
pub const COPY_COUNTER_MAX: usize = 400;
const VIEWER_MISSING_HEADER_DISABLES: bool = false;
const VIEWER_NOTIFY_PERIOD: u64 = 1000;
const VIEWER_TCP_CONNECTIONS_MAX: usize = 16;
const VIEWER_SERVED_MIME_TYPES: &[&[&str]] = &[
&["apng", "image/apng"],
&["avif", "image/avif"],
&["bmp", "image/bmp"],
&["gif", "image/gif"],
&["html", "text/html"],
&["htm", "text/html"],
&["ico", "image/vnd.microsoft.icon"],
&["jpeg", "image/jpeg"],
&["jpg", "image/jpeg"],
&["pdf", "application/pdf"],
&["png", "image/png"],
&["svg", "image/svg+xml"],
&["tiff", "image/tiff"],
&["tif", "image/tiff"],
&["webp", "image/webp"],
&["mp3", "audio/mp3"],
&["ogg", "audio/ogg"],
&["oga", "audio/ogg"],
&["weba", "audio/webm"],
&["flac", "audio/flac"],
&["wav", "audio/wav"],
&["opus", "audio/opus"],
&["mp4", "video/mp4"],
&["ogv", "video/ogg"],
&["webm", "video/webm"],
&["ogx", "application/ogg"],
];
pub const VIEWER_RENDITION_TMPL: &str = r#"<!DOCTYPE html>
<html lang="{{ fm_lang | default(value='en') }}">
<head>
<meta charset="UTF-8">
<title>{{ fm_title }}</title>
<style>
table, th, td { font-weight: normal; }
table.center {
margin-left: auto;
margin-right: auto;
background-color: #f3f2e4;
border:1px solid grey;
}
th, td {
padding: 3px;
padding-left:15px;
padding-right:15px;
}
th.key{ color:#444444; text-align:right; }
th.val{
color:#316128;
text-align:left;
font-family:sans-serif;
}
th.keygrey{ color:grey; text-align:right; }
th.valgrey{ color:grey; text-align:left; }
pre { white-space: pre-wrap; }
em { color: #523626; }
a { color: #316128; }
h1 { font-size: 150% }
h2 { font-size: 132% }
h3 { font-size: 115% }
h4, h5, h6 { font-size: 100% }
h1, h2, h3, h4, h5, h6 { color: #263292; font-family:sans-serif; }
</style>
</head>
<body>
<table class="center">
<tr>
<th class="key">title:</th>
<th class="val"><b>{{ fm_title }}</b></th>
</tr>
<tr>
<th class="key">subtitle:</th>
<th class="val">{{ fm_subtitle | default(value='') }}</th>
</tr>
<tr>
<th class="keygrey">date:</th>
<th class="valgrey">{{ fm_date | default(value='') }}</th>
</tr>
{% for k, v in fm_all| remove(var='fm_title')| remove(var='fm_subtitle')| remove(var='fm_date') %}
<tr>
<th class="keygrey">{{ k }}:</th>
<th class="valgrey">{{ v }}</th>
</tr>
{% endfor %}
</table>
<div class="note-body">{{ note_body }}</div>
<script>{{ note_js }}</script>
</body>
</html>
"#;
pub const VIEWER_ERROR_TMPL: &str = r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Syntax error</title>
<style>
.note-error { color: #523626; }
pre { white-space: pre-wrap; }
a { color: #316128; }
h1, h2, h3, h4, h5, h6 { color: #d3af2c; font-family:sans-serif; }
</style>
</head>
<body>
<h3>Syntax error</h3>
<p> in note file: <pre>{{ path }}</pre><p>
<div class="note-error">
<hr>
<pre>{{ note_error }}</pre>
<hr>
</div>
{{ note_erroneous_content }}
<script>{{ note_js }}</script>
</body>
</html>
"#;
pub const EXPORTER_RENDITION_TMPL: &str = r#"<!DOCTYPE html>
<html lang="{{ fm_lang | default(value='en') }}">
<head>
<meta charset="utf-8">
<title>{{ fm_title }}</title>
<style>
table, th, td { font-weight: normal; }
table.center {
margin-left: auto;
margin-right: auto;
background-color: #f3f2e4;
border:1px solid grey;
}
th, td {
padding: 3px;
padding-left:15px;
padding-right:15px;
}
th.key{ color:#444444; text-align:right; }
th.val{
color:#316128;
text-align:left;
font-family:sans-serif;
}
th.keygrey{ color:grey; text-align:right; }
th.valgrey{ color:grey; text-align:left; }
pre { white-space: pre-wrap; }
em { color: #523626; }
a { color: #316128; }
h1 { font-size: 150% }
h2 { font-size: 132% }
h3 { font-size: 115% }
h4, h5, h6 { font-size: 100% }
h1, h2, h3, h4, h5, h6 { color: #263292; font-family:sans-serif; }
</style>
</head>
<body>
<table class="center">
<tr>
<th class="key">title:</th>
<th class="val"><b>{{ fm_title }}</b></th>
</tr>
<tr>
<th class="key">subtitle:</th>
<th class="val">{{ fm_subtitle | default(value='') }}</th>
</tr>
<tr>
<th class="keygrey">date:</th>
<th class="valgrey">{{ fm_date | default(value='') }}</th>
</tr>
{% for k, v in fm_all| remove(var='fm_title')| remove(var='fm_subtitle')| remove(var='fm_date') %}
<tr>
<th class="keygrey">{{ k }}:</th>
<th class="valgrey">{{ v }}</th>
</tr>
{% endfor %}
</table>
<div class="note-body">{{ note_body }}</div>
</body>
</html>
"#;
#[derive(Debug, PartialEq, StructOpt)]
#[structopt(
name = "Tp-Note",
about = "Fast note taking with templates and filename synchronization."
)]
pub struct Args {
#[structopt(long, short = "b")]
pub batch: bool,
#[structopt(long, short = "c")]
pub config: Option<String>,
#[structopt(long, short = "d")]
pub debug: Option<LevelFilter>,
#[structopt(long, short = "u")]
pub popup: bool,
#[structopt(long, short = "e")]
pub edit: bool,
#[structopt(long, short = "p")]
pub port: Option<u16>,
#[structopt(long, short = "n")]
pub no_filename_sync: bool,
#[structopt(long, short = "v")]
pub view: bool,
#[structopt(name = "PATH", parse(from_os_str))]
pub path: Option<PathBuf>,
#[structopt(long, short = "V")]
pub version: bool,
#[structopt(long, short = "x", parse(from_os_str))]
pub export: Option<PathBuf>,
}
lazy_static! {
pub static ref ARGS : Args = Args::from_args();
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Cfg {
pub version: String,
pub debug_arg_default: LevelFilter,
pub edit_arg_default: bool,
pub no_filename_sync_arg_default: bool,
pub popup_arg_default: bool,
pub silently_ignore_missing_header: bool,
pub extension_default: String,
pub note_file_extensions_md: Vec<String>,
pub note_file_extensions_rst: Vec<String>,
pub note_file_extensions_html: Vec<String>,
pub note_file_extensions_txt: Vec<String>,
pub note_file_extensions_no_viewer: Vec<String>,
pub tmpl_new_content: String,
pub tmpl_new_filename: String,
pub tmpl_copy_content: String,
pub tmpl_copy_filename: String,
pub tmpl_clipboard_content: String,
pub tmpl_clipboard_filename: String,
pub tmpl_annotate_content: String,
pub tmpl_annotate_filename: String,
pub tmpl_sync_filename: String,
pub tmpl_compulsory_field_content: String,
pub editor_args: Vec<Vec<String>>,
pub editor_console_args: Vec<Vec<String>>,
pub browser_args: Vec<Vec<String>>,
pub clipboard_read_enabled: bool,
pub clipboard_empty_enabled: bool,
pub copy_counter_extra_separator: String,
pub copy_counter_opening_brackets: String,
pub copy_counter_closing_brackets: String,
pub viewer_missing_header_disables: bool,
pub viewer_notify_period: u64,
pub viewer_tcp_connections_max: usize,
pub viewer_served_mime_types: Vec<Vec<String>>,
pub viewer_rendition_tmpl: String,
pub viewer_error_tmpl: String,
pub exporter_rendition_tmpl: String,
}
impl ::std::default::Default for Cfg {
fn default() -> Self {
let version = match VERSION {
Some(v) => v.to_string(),
None => "".to_string(),
};
Cfg {
version,
debug_arg_default: DEBUG_ARG_DEFAULT,
edit_arg_default: EDITOR_ARG_DEFAULT,
popup_arg_default: POPUP_ARG_DEFAULT,
no_filename_sync_arg_default: NO_FILENAME_SYNC_ARG_DEFAULT,
silently_ignore_missing_header: SILENTLY_IGNORE_MISSING_HEADER,
extension_default: EXTENSION_DEFAULT.to_string(),
note_file_extensions_md: NOTE_FILE_EXTENSIONS_MD
.iter()
.map(|a| (*a).to_string())
.collect(),
note_file_extensions_rst: NOTE_FILE_EXTENSIONS_RST
.iter()
.map(|a| (*a).to_string())
.collect(),
note_file_extensions_html: NOTE_FILE_EXTENSIONS_HTML
.iter()
.map(|a| (*a).to_string())
.collect(),
note_file_extensions_txt: NOTE_FILE_EXTENSIONS_TXT
.iter()
.map(|a| (*a).to_string())
.collect(),
note_file_extensions_no_viewer: NOTE_FILE_EXTENSIONS_NO_VIEWER
.iter()
.map(|a| (*a).to_string())
.collect(),
tmpl_new_content: TMPL_NEW_CONTENT.to_string(),
tmpl_new_filename: TMPL_NEW_FILENAME.to_string(),
tmpl_copy_content: TMPL_COPY_CONTENT.to_string(),
tmpl_copy_filename: TMPL_COPY_FILENAME.to_string(),
tmpl_clipboard_content: TMPL_CLIPBOARD_CONTENT.to_string(),
tmpl_clipboard_filename: TMPL_CLIPBOARD_FILENAME.to_string(),
tmpl_annotate_content: TMPL_ANNOTATE_CONTENT.to_string(),
tmpl_annotate_filename: TMPL_ANNOTATE_FILENAME.to_string(),
tmpl_sync_filename: TMPL_SYNC_FILENAME.to_string(),
tmpl_compulsory_field_content: TMPL_COMPULSORY_FIELD_CONTENT.to_string(),
editor_args: EDITOR_ARGS
.iter()
.map(|i| i.iter().map(|a| (*a).to_string()).collect())
.collect(),
editor_console_args: EDITOR_CONSOLE_ARGS
.iter()
.map(|i| i.iter().map(|a| (*a).to_string()).collect())
.collect(),
browser_args: BROWSER_ARGS
.iter()
.map(|i| i.iter().map(|a| (*a).to_string()).collect())
.collect(),
clipboard_read_enabled: CLIPBOARD_READ_ENABLED,
clipboard_empty_enabled: CLIPBOARD_EMPTY_ENABLED,
copy_counter_extra_separator: COPY_COUNTER_EXTRA_SEPARATOR.to_string(),
copy_counter_opening_brackets: COPY_COUNTER_OPENING_BRACKETS.to_string(),
copy_counter_closing_brackets: COPY_COUNTER_CLOSING_BRACKETS.to_string(),
viewer_missing_header_disables: VIEWER_MISSING_HEADER_DISABLES,
viewer_notify_period: VIEWER_NOTIFY_PERIOD,
viewer_tcp_connections_max: VIEWER_TCP_CONNECTIONS_MAX,
viewer_served_mime_types: VIEWER_SERVED_MIME_TYPES
.iter()
.map(|i| i.iter().map(|a| (*a).to_string()).collect())
.collect(),
viewer_rendition_tmpl: VIEWER_RENDITION_TMPL.to_string(),
viewer_error_tmpl: VIEWER_ERROR_TMPL.to_string(),
exporter_rendition_tmpl: EXPORTER_RENDITION_TMPL.to_string(),
}
}
}
lazy_static! {
pub static ref VIEWER_SERVED_MIME_TYPES_HMAP: HashMap<&'static str, &'static str> = {
let mut hm = HashMap::new();
for l in &CFG.viewer_served_mime_types {
if l.len() >= 2
{
hm.insert(l[0].as_str(), l[1].as_str());
};
};
hm
};
}
lazy_static! {
pub static ref LAUNCH_EDITOR: bool = {
!ARGS.batch && ARGS.export.is_none() && !ARGS.view
};
}
#[cfg(feature = "viewer")]
lazy_static! {
pub static ref LAUNCH_VIEWER: bool = {
!ARGS.batch && ARGS.export.is_none() && !*RUNS_ON_CONSOLE &&
(ARGS.view || ( !ARGS.edit && !CFG.edit_arg_default ))
};
}
#[cfg(not(feature = "viewer"))]
lazy_static! {
pub static ref LAUNCH_VIEWER: bool = {
false
};
}
lazy_static! {
pub static ref RUNS_ON_CONSOLE: bool = {
#[cfg(target_family = "unix")]
let display = std::env::var("DISPLAY")
.ok()
.and_then(|s: String| if s.is_empty() { None } else { Some(s) });
#[cfg(not(target_family = "unix"))]
let display = Some(String::new());
display.is_none()
};
}
lazy_static! {
pub static ref CFG_FILE_LOADING: RwLock<Result<(), ConfyError>> = RwLock::new(Ok(()));
}
#[cfg(not(test))]
lazy_static! {
pub static ref CFG: Cfg = confy::load::<Cfg>(PathBuf::from(
if let Some(c) = &ARGS.config {
c
} else {
CURRENT_EXE
})
.with_extension("")
.to_str()
.unwrap_or_default()
).unwrap_or_else(|e|{
let mut cfg_file_loading = CFG_FILE_LOADING.write().unwrap();
*cfg_file_loading = Err(e);
Cfg::default()
});
}
#[cfg(test)]
lazy_static! {
pub static ref CFG: Cfg = Cfg::default();
}
lazy_static! {
pub static ref CONFIG_PATH : Option<PathBuf> = {
if let Some(c) = &ARGS.config {
Some(PathBuf::from(c))
} else {
let config = ProjectDirs::from("rs", "", CURRENT_EXE)?;
let mut config = PathBuf::from(config.config_dir());
config.push(Path::new(CONFIG_FILENAME));
Some(config)
}
};
}
pub fn backup_config_file() -> Result<PathBuf, FileError> {
if let Some(ref config_path) = *CONFIG_PATH {
if config_path.exists() {
let config_path_bak = filename::find_unused((&config_path).to_path_buf())?;
fs::rename(&config_path.as_path(), &config_path_bak)?;
Ok(config_path_bak)
} else {
Err(FileError::ConfigFileNotFound)
}
} else {
Err(FileError::PathToConfigFileNotFound)
}
}
lazy_static! {
pub static ref STDIN: Content = {
let mut buffer = String::new();
if !is(Stream::Stdin) {
let stdin = io::stdin();
let mut handle = stdin.lock();
let _ = handle.read_to_string(&mut buffer);
}
buffer.truncate(buffer.trim_end().len());
Content::from_input_with_cr(buffer)
};
}
lazy_static! {
pub static ref CLIPBOARD: Content = {
let mut buffer = String::new();
#[cfg(feature="read-clipboard")]
if CFG.clipboard_read_enabled && !*RUNS_ON_CONSOLE && !ARGS.batch {
let ctx: Option<ClipboardContext> = ClipboardProvider::new().ok();
if ctx.is_some() {
let ctx = &mut ctx.unwrap(); let s = ctx.get_contents().ok();
buffer.push_str(&s.unwrap_or_default());
}
};
buffer.truncate(buffer.trim_end().len());
Content::from_input_with_cr(buffer)
};
}
#[derive(Debug, PartialEq, Default)]
pub struct Hyperlink {
pub name: String,
pub target: String,
pub title: String,
}
impl Hyperlink {
pub fn new(input: &str) -> Result<Hyperlink, NoteError> {
if let Some((link_name, link_target, link_title)) = first_hyperlink(input) {
Ok(Hyperlink {
name: link_name.to_string(),
target: link_target.to_string(),
title: link_title.to_string(),
})
} else {
Err(NoteError::NoHyperlinkFound)
}
}
}
#[cfg(test)]
mod tests {
use super::Hyperlink;
#[test]
fn test_parse_hyperlink() {
let input = r#"abc[Homepage](https://blog.getreu.net "My blog")abc"#;
let expected_output = Hyperlink {
name: "Homepage".to_string(),
target: "https://blog.getreu.net".to_string(),
title: "My blog".to_string(),
};
let output = Hyperlink::new(input);
assert_eq!(expected_output, output.unwrap());
let input = r#"abc[Homepage][home]abc
[home]: https://blog.getreu.net "My blog""#;
let expected_output = Hyperlink {
name: "Homepage".to_string(),
target: "https://blog.getreu.net".to_string(),
title: "My blog".to_string(),
};
let output = Hyperlink::new(input);
assert_eq!(expected_output, output.unwrap());
let input = "abc`Homepage <https://blog.getreu.net>`_\nabc";
let expected_output = Hyperlink {
name: "Homepage".to_string(),
target: "https://blog.getreu.net".to_string(),
title: "".to_string(),
};
let output = Hyperlink::new(input);
assert_eq!(expected_output, output.unwrap());
let input = "abc `Homepage<home_>`_ abc\n.. _home: https://blog.getreu.net\nabc";
let expected_output = Hyperlink {
name: "Homepage".to_string(),
target: "https://blog.getreu.net".to_string(),
title: "".to_string(),
};
let output = Hyperlink::new(input);
assert_eq!(expected_output, output.unwrap());
}
}