mod json;
mod unified;
use self::json::Json;
use crate::diff::RichHunks;
use anyhow::anyhow;
use console::{Color, Style, Term};
use enum_dispatch::enum_dispatch;
use serde::{Deserialize, Serialize};
use std::io::Write;
use strum::{self, Display, EnumIter, EnumString, IntoEnumIterator};
use unified::Unified;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct DocumentDiffData<'a> {
pub filename: &'a str,
pub text: &'a str,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct DisplayData<'a> {
pub hunks: RichHunks<'a>,
pub old: DocumentDiffData<'a>,
pub new: DocumentDiffData<'a>,
}
#[enum_dispatch]
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Display, EnumIter, EnumString)]
#[strum(serialize_all = "snake_case")]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Renderers {
Unified,
Json,
}
impl Default for Renderers {
fn default() -> Self {
Renderers::Unified(Unified::default())
}
}
#[enum_dispatch(Renderers)]
pub trait Renderer {
fn render(
&self,
writer: &mut dyn Write,
data: &DisplayData,
term_info: Option<&Term>,
) -> anyhow::Result<()>;
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
#[serde(remote = "Color", rename_all = "snake_case")]
#[derive(Default)]
enum ColorDef {
Color256(u8),
#[default]
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
impl From<ColorDef> for Color {
fn from(c: ColorDef) -> Self {
match c {
ColorDef::Black => Color::Black,
ColorDef::White => Color::White,
ColorDef::Red => Color::Red,
ColorDef::Green => Color::Green,
ColorDef::Yellow => Color::Yellow,
ColorDef::Blue => Color::Blue,
ColorDef::Magenta => Color::Magenta,
ColorDef::Cyan => Color::Cyan,
ColorDef::Color256(c) => Color::Color256(c),
}
}
}
mod opt_color_def {
use super::{Color, ColorDef};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn serialize<S>(value: &Option<Color>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Helper<'a>(#[serde(with = "ColorDef")] &'a Color);
value.as_ref().map(Helper).serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Color>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper(#[serde(with = "ColorDef")] Color);
let helper = Option::deserialize(deserializer)?;
Ok(helper.map(|Helper(external)| external))
}
}
fn default_option<T>() -> Option<T> {
None
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct RegularStyle(Style);
#[derive(Clone, Debug, PartialEq, Eq)]
struct EmphasizedStyle(Style);
#[derive(Debug, PartialEq, EnumString, Serialize, Deserialize, Eq)]
#[strum(serialize_all = "snake_case")]
#[derive(Default)]
pub enum Emphasis {
None,
#[default]
Bold,
Underline,
Highlight(HighlightColors),
}
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct HighlightColors {
#[serde(with = "ColorDef")]
pub addition: Color,
#[serde(with = "ColorDef")]
pub deletion: Color,
}
impl Default for HighlightColors {
fn default() -> Self {
HighlightColors {
addition: Color::Color256(0),
deletion: Color::Color256(0),
}
}
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
#[serde(rename_all = "snake_case", default)]
pub struct RenderConfig {
default: String,
unified: unified::Unified,
json: json::Json,
}
impl Default for RenderConfig {
fn default() -> Self {
let default_renderer = Renderers::default();
RenderConfig {
default: default_renderer.to_string(),
unified: Unified::default(),
json: Json::default(),
}
}
}
impl RenderConfig {
pub fn get_renderer(self, tag: Option<String>) -> anyhow::Result<Renderers> {
if let Some(t) = tag {
let cand_enum = Renderers::iter().find(|e| e.to_string() == t);
match cand_enum {
None => Err(anyhow!("'{}' is not a valid renderer", &t)),
Some(renderer) => Ok(renderer),
}
} else {
Ok(Renderers::default())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test_case("unified")]
#[test_case("json")]
fn test_get_renderer_custom_tag(tag: &str) {
let cfg = RenderConfig::default();
let res = cfg.get_renderer(Some(tag.into()));
assert!(res.is_ok());
}
#[test]
fn test_render_config_default_tag() {
let cfg = RenderConfig::default();
let res = cfg.get_renderer(None);
assert_eq!(res.unwrap(), Renderers::default());
}
}