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 {
25 body: &'a str,
26 },
27 Modified {
28 diff: &'a similar::TextDiff<'a, 'a, 'a, [u8]>,
29 },
30 Added {
31 body: &'a str,
32 },
33 Deleted {
34 body: &'a str,
35 },
36}
37
38impl TextPreviewTemplate<'_> {
39 fn is_equal(change: &similar::Change<&[u8]>) -> bool {
40 matches!(change.tag(), ChangeTag::Equal)
41 }
42}
43
44#[derive(Template)]
45#[template(path = "text_detail.html")]
46struct TextDetailTemplate<'a> {
47 detail: TextDetailBody<'a>,
48}
49
50enum TextDetailBody<'a> {
51 Diff {
52 lines: &'a similar::TextDiff<'a, 'a, 'a, [u8]>,
53 },
54 Single {
55 label: &'a str,
56 body: &'a str,
57 },
58}
59
60impl TextDetailBody<'_> {
61 fn is_multicolumn(&self) -> bool {
62 matches!(self, TextDetailBody::Diff { .. })
63 }
64}
65
66impl DetailReporter<TextDiff, FileLeaf, HtmlReport> for TextDiffReporter {
67 type Error = TextDiffReportError;
68
69 fn report_unchanged(
70 &self,
71 name: &str,
72 diff: TextDiff,
73 reporter: &HtmlReport,
74 ) -> Result<MayUnsupported<()>, Self::Error> {
75 let body = String::from_utf8_lossy(&diff.expected);
76 let body = body.as_ref();
77 let preview_html = TextPreviewTemplate {
78 body: TextPreviewBody::Unchanged { body },
79 };
80 let detail_html = TextDetailTemplate {
81 detail: TextDetailBody::Single { label: "same", body },
82 };
83 reporter.record_unchanged(name, COMPARES_NAME, preview_html, detail_html)?;
84 Ok(MayUnsupported::Ok(()))
85 }
86
87 fn report_modified(
88 &self,
89 name: &str,
90 diff: TextDiff,
91 reporter: &HtmlReport,
92 ) -> Result<MayUnsupported<()>, Self::Error> {
93 let diff_view = diff.diff();
94 let preview_html = TextPreviewTemplate {
95 body: TextPreviewBody::Modified { diff: &diff_view },
96 };
97 let detail_html = TextDetailTemplate {
98 detail: TextDetailBody::Diff { lines: &diff_view },
99 };
100 reporter.record_modified(name, COMPARES_NAME, preview_html, detail_html)?;
101 Ok(MayUnsupported::Ok(()))
102 }
103
104 fn report_added(
105 &self,
106 name: &str,
107 data: FileLeaf,
108 reporter: &HtmlReport,
109 ) -> Result<MayUnsupported<()>, Self::Error> {
110 if !is_text_file(&data.kind, &data.content) {
111 return Ok(MayUnsupported::Unsupported);
112 }
113 let actual_text = str::from_utf8(&data.content).expect("Invalid content");
114 let preview_html = TextPreviewTemplate {
115 body: TextPreviewBody::Added { body: actual_text },
116 };
117 let detail_html = TextDetailTemplate {
118 detail: TextDetailBody::Single {
119 label: "added",
120 body: actual_text,
121 },
122 };
123 reporter.record_added(name, COMPARES_NAME, preview_html, detail_html)?;
124 Ok(MayUnsupported::Ok(()))
125 }
126
127 fn report_deleted(
128 &self,
129 name: &str,
130 data: FileLeaf,
131 reporter: &HtmlReport,
132 ) -> Result<MayUnsupported<()>, Self::Error> {
133 if !is_text_file(&data.kind, &data.content) {
134 return Ok(MayUnsupported::Unsupported);
135 }
136 let expected_text = str::from_utf8(&data.content).expect("Invalid content");
137 let preview_html = TextPreviewTemplate {
138 body: TextPreviewBody::Deleted { body: expected_text },
139 };
140 let detail_html = TextDetailTemplate {
141 detail: TextDetailBody::Single {
142 label: "deleted",
143 body: expected_text,
144 },
145 };
146 reporter.record_deleted(name, COMPARES_NAME, preview_html, detail_html)?;
147 Ok(MayUnsupported::Ok(()))
148 }
149}