use super::snipmate_tekton::build_snippets_from_file;
use crate::{
errors::TektonError,
models::{
friendly::{FriendlySnippetBody, FriendlySnippets, Table},
snipmate::Snipmate,
},
utils::{clear_terminal, get_input, hash2ordered_string},
};
use regex::Regex;
use std::{collections::HashMap, fs};
const MISSING_PREFIX: &str = "File contains snippets with missing prefix field(s). Aborting.";
pub fn compose_friendly_snippets(lines: Vec<String>) -> Result<String, TektonError> {
let snips = build_snippets_from_file(lines);
let friendlies = convert_snipmate_to_friendlysnippets(snips);
let result = build_friendly_string(friendlies)?;
Ok(result)
}
pub fn build_friendly_string(friendlies: FriendlySnippets) -> Result<String, TektonError> {
hash2ordered_string(&friendlies.snippets)
}
pub fn convert_snipmate_to_friendlysnippets(snips: Vec<Snipmate>) -> FriendlySnippets {
let mut count: usize = 0;
let target = snips.len();
let mut friendly_handle: FriendlySnippets = FriendlySnippets::new();
for snippet in snips {
let friendly_body = friendly_tekton(snippet);
match serde_json::to_string_pretty(&friendly_body) {
Ok(snip) => {
count += 1;
clear_terminal();
println!(
"Snippet {} of {}:\n{}\n\nEnter name below:",
count, target, snip
);
let key = get_input();
friendly_handle.snippets.insert(key, friendly_body);
}
Err(e) => {
eprintln!("Match had an error in conversion ----> {}", e);
}
}
}
friendly_handle
}
fn friendly_tekton(snippet: Snipmate) -> FriendlySnippetBody {
let prefix: Option<String> = Some(snippet.prefix);
let mut body: Vec<String> = snippet.body;
let mut description: Option<String> = None;
let re = Regex::new(r##"\\""##).unwrap();
body.reverse();
if let Some(first_line) = body.pop() {
let temp = first_line.trim_start().to_string();
body.reverse();
body.insert(0, temp);
}
if let Some(descrip) = &snippet.description {
description = Some(re.replace_all(descrip, "").to_string());
}
FriendlySnippetBody::new(prefix, body, description)
}
pub fn read_in_json_snippets(
file_name: &str,
interactive: bool,
) -> Result<FriendlySnippets, TektonError> {
let file_contents = fs::read_to_string(file_name)?;
let snippets: Result<FriendlySnippets, serde_json::Error> =
serde_json::from_str(&file_contents);
match snippets {
Ok(snippets) => Ok(snippets),
Err(_) => match dynamically_read_json_snippets(file_contents, interactive) {
Ok(snippets) => Ok(snippets),
Err(e) => Err(e),
},
}
}
pub fn dynamically_read_json_snippets(
file: String,
interactive: bool,
) -> Result<FriendlySnippets, TektonError> {
let mut snippets: Table = HashMap::new();
let json: serde_json::Value = serde_json::from_str(&file).unwrap();
let mut snippets_to_fix: Vec<(String, FriendlySnippetBody)> = Vec::new();
if let Some(obj) = json.as_object() {
for (name, v) in obj {
let body = retrieve_body(&v["body"]);
let mut snip_body = FriendlySnippetBody::new(None, body, None);
if let Some(description) = v["description"].as_str() {
if !description.is_empty() {
snip_body.description = Some(description.to_string());
}
}
if let Some(pref_candidate) = retrieve_prefix(&v["prefix"]) {
snip_body.prefix = Some(pref_candidate);
} else if interactive {
snippets_to_fix.push((name.to_string(), snip_body));
continue;
} else {
return Err(TektonError::Reason(MISSING_PREFIX.into()));
}
snippets.insert(name.to_string(), snip_body);
}
correct_missing_prefix_snippets(&mut snippets_to_fix, &mut snippets);
}
Ok(FriendlySnippets { snippets })
}
pub fn correct_missing_prefix_snippets(
snippets_to_fix: &mut Vec<(String, FriendlySnippetBody)>,
snippets: &mut Table,
) {
if !snippets_to_fix.is_empty() {
let mut count = 0;
let total = snippets_to_fix.len();
loop {
if count == total {
break;
}
if let Some((name, snip_body)) = snippets_to_fix.pop() {
count += 1;
println!("Fixing snippet {} of {}", count, total);
let snip_body = handle_prompt_for_prefix(&name, snip_body);
snippets.insert(name.to_string(), snip_body);
}
}
}
}
fn handle_prompt_for_prefix(name: &str, mut snip_body: FriendlySnippetBody) -> FriendlySnippetBody {
println!(
"---- Snippet: {} ---\n{}\n--------",
name,
serde_json::to_string_pretty(&snip_body).ok().unwrap() );
println!("Enter a prefix:");
loop {
let prefix_candidate = get_input();
println!("Proceed? (y/n):");
let resp = get_input().to_lowercase();
if resp == "y" {
snip_body.prefix = Some(prefix_candidate);
break;
}
println!("Enter a new prefix: ");
}
clear_terminal();
snip_body
}
fn retrieve_prefix(val: &serde_json::Value) -> Option<String> {
val.as_str().map(|prefix| prefix.to_string())
}
pub fn retrieve_body(val: &serde_json::Value) -> Vec<String> {
let mut body: Vec<String> = Vec::new();
if let Some(lines) = val.as_array() {
for line in lines.iter() {
body.push(line.as_str().unwrap_or("").to_string());
}
} else {
body.push(val.as_str().unwrap_or("").to_string());
}
body
}
pub fn sort_friendly_snippets(snippets: FriendlySnippets) -> Result<String, TektonError> {
let table = &snippets.snippets;
match table.len() {
0 => Err(TektonError::Reason(
"Refusing to build string for 0 snippets".to_string(),
)),
_ => hash2ordered_string(table),
}
}
#[cfg(test)]
mod tests {
use super::*;
const INTERACTIVE: bool = false;
#[test]
fn standard_json_reading() {
let file = r#"{
"beta": {
"prefix": "println",
"body": ["println!(\"${1}\");"],
"description": "println!(…);"
},
"alpha": {
"prefix": "print",
"body": ["print!(\"${1}\");"],
"description": "print!(…);"
}
}"#
.to_string();
let snippets: Result<FriendlySnippets, serde_json::Error> = serde_json::from_str(&file);
match snippets {
Ok(res) => {
let expected_struct = FriendlySnippetBody::new(
Some("println".to_string()),
vec!["println!(\"${1}\");".to_string()],
Some("println!(…);".to_string()),
);
assert_eq!(res.snippets.len(), 2);
let item = res.snippets.get("beta").unwrap();
assert_eq!(item.prefix, expected_struct.prefix);
assert_eq!(item, &expected_struct);
}
Err(e) => {
println!("Error: {}", e.to_string());
assert!(false);
}
}
}
#[test]
fn dyn_json_reading() {
let file = r#"{
"beta": {
"prefix": "println",
"body": ["println!(\"${1}\");"],
"description": "println!(…);"
},
"alpha": {
"prefix": "print",
"body": ["print!(\"${1}\");"],
"description": "print!(…);"
}
}"#
.to_string();
let res = dynamically_read_json_snippets(file, INTERACTIVE);
match res {
Ok(res) => {
let expected_struct = FriendlySnippetBody::new(
Some("println".to_string()),
vec!["println!(\"${1}\");".to_string()],
Some("println!(…);".to_string()),
);
assert_eq!(res.snippets.len(), 2);
let item = res.snippets.get("beta").unwrap();
assert_eq!(item.prefix, expected_struct.prefix);
assert_eq!(item, &expected_struct);
}
Err(e) => {
println!("Error: {}", e.to_string());
assert!(false);
}
}
}
#[test]
fn jekyll() {
let file = r#"{
"Filter downcase": {
"prefix": "downcase",
"description": "String filter: downcase",
"body": "| downcase }}"
}}"#
.to_string();
let res = dynamically_read_json_snippets(file, INTERACTIVE);
match res {
Ok(res) => {
let expected_struct = FriendlySnippetBody::new(
Some("downcase".to_string()),
vec!["| downcase }}".to_string()],
Some("String filter: downcase".to_string()),
);
assert_eq!(res.snippets.len(), 1);
let item = res.snippets.get("Filter downcase").unwrap();
assert_eq!(item.prefix, expected_struct.prefix);
assert_eq!(item, &expected_struct);
}
Err(e) => {
println!("Error: {}", e.to_string());
assert!(false);
}
}
}
#[test]
fn serialization() {
let file = r#"{
"Filter downcase": {
"prefix": "downcase",
"description": "String filter: downcase",
"body": "| downcase }}"
}}"#
.to_string();
let res = dynamically_read_json_snippets(file, INTERACTIVE);
match res {
Ok(res) => {
let expected_struct = FriendlySnippetBody::new(
Some("downcase".to_string()),
vec!["| downcase }}".to_string()],
Some("String filter: downcase".to_string()),
);
assert_eq!(res.snippets.len(), 1);
let item = res.snippets.get("Filter downcase").unwrap();
assert_eq!(item.prefix, expected_struct.prefix);
assert_eq!(item, &expected_struct);
if let Ok(s) = serde_json::to_string(&res) {
const EXPECTED: &str = "{\"Filter downcase\":{\"prefix\":\"downcase\",\"body\":[\"| downcase }}\"],\"description\":\"String filter: downcase\"}}";
assert_eq!(s, EXPECTED);
} else {
assert!(false);
}
}
Err(e) => {
println!("Error: {}", e.to_string());
assert!(false);
}
}
}
#[test]
fn serialization_with_empty_description() {
let file = r#"{
"Filter downcase": {
"prefix": "downcase",
"body": "| downcase }}"
}}"#
.to_string();
let res = dynamically_read_json_snippets(file, INTERACTIVE);
match res {
Ok(res) => {
let expected_struct = FriendlySnippetBody::new(
Some("downcase".to_string()),
vec!["| downcase }}".to_string()],
None,
);
assert_eq!(res.snippets.len(), 1);
let item = res.snippets.get("Filter downcase").unwrap();
assert_eq!(item.prefix, expected_struct.prefix);
assert_eq!(item, &expected_struct);
if let Ok(s) = serde_json::to_string(&res) {
const EXPECTED: &str = "{\"Filter downcase\":{\"prefix\":\"downcase\",\"body\":[\"| downcase }}\"]}}";
assert_eq!(s, EXPECTED);
} else {
assert!(false);
}
}
Err(e) => {
println!("Error: {}", e.to_string());
assert!(false);
}
}
}
#[test]
fn serialization_missing_prefix() {
let file = r#"{
"Filter downcase": {
"body": "| downcase }}"
}}"#
.to_string();
let res = dynamically_read_json_snippets(file, INTERACTIVE);
match res {
Ok(_) => {
assert!(false);
}
Err(e) => {
assert_eq!(e, TektonError::Reason(MISSING_PREFIX.into()));
}
}
}
#[test]
fn serialization_missing_prefix_throw_error() {
let file = r#"{
"Filter downcase": {
"body": "| downcase }}"
}
}"#
.to_string();
let res = dynamically_read_json_snippets(file, INTERACTIVE);
match res {
Ok(_) => {
assert!(false, "Failed to throw the error");
}
Err(e) => {
assert_eq!(e, TektonError::Reason(MISSING_PREFIX.into()));
}
}
}
#[test]
fn test_friendly_tekton() {
let input: Vec<String> = vec![
"snippet test".to_string(),
" test snippet".to_string(),
"snippet test2 an epic description".to_string(),
" a second snippet".to_string(),
" with several".to_string(),
" lines.".to_string(),
];
let snippets = build_snippets_from_file(input);
assert_eq!(snippets.len(), 2);
let vsnip = snippets[0].clone();
let mut friendlies: FriendlySnippets = FriendlySnippets {
snippets: HashMap::new(),
};
let mut names = vec!["test2".to_string(), "test1".to_string()];
for snippet in snippets {
let body = friendly_tekton(snippet);
friendlies
.snippets
.insert(names.pop().unwrap().to_string(), body);
}
assert_eq!(friendlies.snippets.len(), 2);
let fsnip = friendlies.snippets.get("test1").unwrap();
assert_eq!(fsnip.prefix, Some(vsnip.prefix));
assert_eq!(
fsnip.body.concat(),
vsnip.body.concat().strip_prefix(" ").unwrap()
);
assert_eq!(fsnip.description, vsnip.description);
}
}