semdiff_differ_text/
report_html.rs1use crate::{TextDiff, TextDiffReporter, is_text_file};
2use askama::Template;
3use semdiff_core::fs::FileLeaf;
4use semdiff_core::{DetailReporter, MayUnsupported};
5use semdiff_output::html::{HtmlReport, HtmlReportError};
6use similar::ChangeTag;
7use thiserror::Error;
8
9const COMPARES_NAME: &str = "text";
10
11#[derive(Debug, Error)]
12pub enum TextDiffReportError {
13 #[error("html report error: {0}")]
14 HtmlReport(#[from] HtmlReportError),
15}
16
17#[derive(Template)]
18#[template(path = "text_preview.html")]
19struct TextPreviewTemplate<'a> {
20 body: TextPreviewBody<'a>,
21}
22
23enum TextPreviewBody<'a> {
24 Unchanged { body: &'a str },
25 Modified { diff: &'a similar::TextDiff<'a, 'a, [u8]> },
26 Added { body: &'a str },
27 Deleted { body: &'a str },
28}
29
30impl TextPreviewTemplate<'_> {
31 fn is_equal(change: &similar::Change<&[u8]>) -> bool {
32 matches!(change.tag(), ChangeTag::Equal)
33 }
34}
35
36#[derive(Template)]
37#[template(path = "text_detail.html")]
38struct TextDetailTemplate<'a> {
39 detail: TextDetailBody<'a>,
40}
41
42enum TextDetailBody<'a> {
43 Diff { lines: &'a similar::TextDiff<'a, 'a, [u8]> },
44 Single { label: &'a str, body: &'a str },
45}
46
47impl TextDetailBody<'_> {
48 fn is_multicolumn(&self) -> bool {
49 matches!(self, TextDetailBody::Diff { .. })
50 }
51}
52
53impl DetailReporter<TextDiff, FileLeaf, HtmlReport> for TextDiffReporter {
54 type Error = TextDiffReportError;
55
56 fn report_unchanged(
57 &self,
58 name: &str,
59 diff: &TextDiff,
60 reporter: &HtmlReport,
61 ) -> Result<MayUnsupported<()>, Self::Error> {
62 let body = String::from_utf8_lossy(&diff.expected);
63 let body = body.as_ref();
64 let preview_html = TextPreviewTemplate {
65 body: TextPreviewBody::Unchanged { body },
66 };
67 let detail_html = TextDetailTemplate {
68 detail: TextDetailBody::Single { label: "same", body },
69 };
70 reporter.record_unchanged(name, COMPARES_NAME, preview_html, detail_html)?;
71 Ok(MayUnsupported::Ok(()))
72 }
73
74 fn report_modified(
75 &self,
76 name: &str,
77 diff: &TextDiff,
78 reporter: &HtmlReport,
79 ) -> Result<MayUnsupported<()>, Self::Error> {
80 let diff_view = diff.diff();
81 let preview_html = TextPreviewTemplate {
82 body: TextPreviewBody::Modified { diff: &diff_view },
83 };
84 let detail_html = TextDetailTemplate {
85 detail: TextDetailBody::Diff { lines: &diff_view },
86 };
87 reporter.record_modified(name, COMPARES_NAME, preview_html, detail_html)?;
88 Ok(MayUnsupported::Ok(()))
89 }
90
91 fn report_added(
92 &self,
93 name: &str,
94 data: &FileLeaf,
95 reporter: &HtmlReport,
96 ) -> Result<MayUnsupported<()>, Self::Error> {
97 if !is_text_file(&data.kind, &data.content) {
98 return Ok(MayUnsupported::Unsupported);
99 }
100 let actual_text = str::from_utf8(&data.content).expect("Invalid content");
101 let preview_html = TextPreviewTemplate {
102 body: TextPreviewBody::Added { body: actual_text },
103 };
104 let detail_html = TextDetailTemplate {
105 detail: TextDetailBody::Single {
106 label: "added",
107 body: actual_text,
108 },
109 };
110 reporter.record_added(name, COMPARES_NAME, preview_html, detail_html)?;
111 Ok(MayUnsupported::Ok(()))
112 }
113
114 fn report_deleted(
115 &self,
116 name: &str,
117 data: &FileLeaf,
118 reporter: &HtmlReport,
119 ) -> Result<MayUnsupported<()>, Self::Error> {
120 if !is_text_file(&data.kind, &data.content) {
121 return Ok(MayUnsupported::Unsupported);
122 }
123 let expected_text = str::from_utf8(&data.content).expect("Invalid content");
124 let preview_html = TextPreviewTemplate {
125 body: TextPreviewBody::Deleted { body: expected_text },
126 };
127 let detail_html = TextDetailTemplate {
128 detail: TextDetailBody::Single {
129 label: "deleted",
130 body: expected_text,
131 },
132 };
133 reporter.record_deleted(name, COMPARES_NAME, preview_html, detail_html)?;
134 Ok(MayUnsupported::Ok(()))
135 }
136}