pub(crate) mod activity;
pub(crate) mod attachments;
pub(crate) mod comments;
pub(crate) mod insights;
mod issues;
pub(crate) mod members;
mod pages;
pub(crate) mod plans;
pub(crate) mod project_groups;
mod projects;
mod resources;
mod search;
pub(crate) mod settings;
pub(crate) mod users;
pub(crate) mod views;
pub(crate) fn unescape_text(s: &str) -> String {
if s.contains('\n') || s.contains('\t') {
return s.to_string();
}
s.replace("\\n", "\n").replace("\\t", "\t")
}
pub(crate) fn savepoint<F, T>(
conn: &rusqlite::Connection,
name: &str,
f: F,
) -> Result<T, crate::error::LificError>
where
F: FnOnce() -> Result<T, crate::error::LificError>,
{
conn.execute_batch(&format!("SAVEPOINT {name}"))?;
match f() {
Ok(val) => {
conn.execute_batch(&format!("RELEASE {name}"))?;
Ok(val)
}
Err(e) => {
let _ = conn.execute_batch(&format!("ROLLBACK TO {name}"));
let _ = conn.execute_batch(&format!("RELEASE {name}"));
Err(e)
}
}
}
pub use issues::*;
pub use pages::*;
pub use projects::*;
pub use resources::*;
pub use search::*;
#[cfg(test)]
mod tests {
use super::unescape_text;
#[test]
fn unescape_text_preserves_literal_newline_escape_in_multiline_content() {
let input = "```c\nprintf(\"\\n\");\n```";
assert_eq!(unescape_text(input), input);
}
#[test]
fn unescape_text_preserves_literal_tab_escape_when_input_has_real_tab() {
let input = "column\tprintf(\"\\t\");";
assert_eq!(unescape_text(input), input);
}
#[test]
fn unescape_text_repairs_single_line_double_escaped_newline() {
assert_eq!(unescape_text("line1\\nline2"), "line1\nline2");
}
}