Skip to content

Commit 165d1e0

Browse files
Merge pull request #60 from TheBestTvarynka/diff-checker
Diff checker: Initial implementation
2 parents 0486f5e + 0b5a455 commit 165d1e0

11 files changed

+426
-7
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+5-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ log = "0.4.17"
3232
# utils
3333
hex = "0.4.3"
3434
gloo-timers = "0.2.4"
35-
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
36-
rand_chacha = "0.3.1"
3735
serde = { version = "1.0.162", features = ["derive"] }
3836
serde_qs = "0.12.0"
3937
serde_json = "1.0.89"
@@ -50,8 +48,13 @@ hmac-sha512 = { version = "1.1.2", features = ["sha384"] }
5048
rsa = "0.7.2"
5149
bcrypt = "0.14.0"
5250
flate2 = { version = "1.0.26", features = ["zlib"] }
51+
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
52+
rand_chacha = "0.3.1"
5353

5454
# asn1
5555
asn1-parser = { path = "./crates/asn1-parser", features = ["std"] }
5656
oid = { version = "0.2.1", default-features = false }
5757
paste = "1.0.14"
58+
59+
# diff
60+
similar = "2.4.0"

index.html

+3
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,8 @@
2929
<link data-trunk rel="sass" href="public/styles/asn1/page.scss" />
3030
<link data-trunk rel="sass" href="public/styles/asn1/node.scss" />
3131
<link data-trunk rel="sass" href="public/styles/asn1/hex_viewer.scss" />
32+
33+
<!-- diff styles -->
34+
<link data-trunk rel="sass" href="public/styles/diff/styles.scss" />
3235
</head>
3336
</html>

public/styles/diff/styles.scss

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
.diff-changes-container {
3+
display: flex;
4+
flex-direction: row;
5+
gap: 0;
6+
justify-content: flex-start;
7+
align-items: center;
8+
width: 100%;
9+
flex-wrap: wrap;
10+
//background-color: #edd5ce;
11+
//border-radius: 0.2em;
12+
//padding: 0.5em;
13+
}
14+
15+
.diff-insert {
16+
white-space: pre-wrap;
17+
background-color: #97e0bd;
18+
color: #016939;
19+
}
20+
21+
.diff-remove {
22+
white-space: pre-wrap;
23+
background-color: #d38693;
24+
color: #9d0620;
25+
}
26+
27+
.diff-new-line {
28+
height: 0;
29+
flex-basis: 100%;
30+
}
31+
32+
.diff-line-number {
33+
background-color: #edd5ce;
34+
padding-right: 0.3em;
35+
padding-left: 0.3em;
36+
}

src/about.rs

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub fn about() -> Html {
2626
<li>{"ES384"}</li>
2727
<li>{"ES512"}</li>
2828
</ul>
29+
<li>{"ASN1 decoder"}</li>
30+
<li>{"Diff checker"}</li>
2931
<li>{"Ability to share the sample by url"}</li>
3032
</ul>
3133
<span>{"All computations are performed on the client side."}</span>

src/asn1.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,7 @@ pub fn asn1_parser_page() -> Html {
8383
let asn1_setter = parsed_asn1.setter();
8484
let raw_data = (*raw_asn1).clone();
8585
let parse_asn1 = Callback::from(move |_| match Asn1::decode_buff(&raw_data) {
86-
Ok(asn1) => {
87-
debug!("parsed!");
88-
asn1_setter.set(asn1.to_owned_with_asn1(asn1.inner_asn1().to_owned()));
89-
}
86+
Ok(asn1) => asn1_setter.set(asn1.to_owned_with_asn1(asn1.inner_asn1().to_owned())),
9087
Err(error) => notifications.spawn(Notification::new(
9188
NotificationType::Error,
9289
"Invalid asn1 data",
@@ -197,7 +194,7 @@ pub fn asn1_parser_page() -> Html {
197194
</span>
198195
<ByteInput bytes={(*raw_asn1).clone()} setter={Callback::from(move |data| raw_asn1_setter.set(data))} placeholder={"asn1 data".to_owned()} rows={10} />
199196
<div class="horizontal">
200-
<button class="action-button" {onclick}>{"Process"}</button>
197+
<button class="action-button" {onclick}>{"Decode"}</button>
201198
<span class="total">{"(ctrl+enter)"}</span>
202199
<button class="button-with-icon" onclick={share_by_link}>
203200
<img src="/public/img/icons/share_by_link.png" />

src/diff.rs

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
mod diff_algo;
2+
mod diff_viewer;
3+
4+
use similar::{capture_diff_slices, Algorithm, DiffOp, TextDiff};
5+
use web_sys::{HtmlInputElement, KeyboardEvent};
6+
use yew::html::onchange::Event;
7+
use yew::virtual_dom::VNode;
8+
use yew::{classes, function_component, html, use_effect_with_deps, use_state, Callback, Html, TargetCast};
9+
use yew_hooks::use_local_storage;
10+
11+
use self::diff_algo::DiffAlgo;
12+
use self::diff_viewer::DiffViewer;
13+
14+
const DEFAULT_ORIGINAL: &str = "TheBestTvarynka
15+
TheBestTvarynka
16+
TheBestTvarynka";
17+
const DEFAULT_CHANGED: &str = "thebesttravynka
18+
thebesttravynka
19+
thebesttravynka
20+
";
21+
const DEFAULT_ALGORITHM: DiffAlgo = DiffAlgo(Algorithm::Myers);
22+
23+
const LOCAL_STORAGE_ORIGINAL: &str = "ORIGINAL_DATA";
24+
const LOCAL_STORAGE_ALGORITHM: &str = "ALGORITHM";
25+
const LOCAL_STORAGE_CHANGED: &str = "CHANGED_DATA";
26+
27+
const ALL_ALGORITHMS: &[DiffAlgo] = &[
28+
DiffAlgo(Algorithm::Myers),
29+
DiffAlgo(Algorithm::Lcs),
30+
DiffAlgo(Algorithm::Patience),
31+
];
32+
33+
#[derive(Debug, Clone, PartialEq)]
34+
struct DiffData {
35+
pub original: Vec<char>,
36+
pub changed: Vec<char>,
37+
pub changes: Vec<DiffOp>,
38+
}
39+
40+
impl DiffData {
41+
pub fn empty() -> Self {
42+
Self {
43+
original: Vec::new(),
44+
changed: Vec::new(),
45+
changes: Vec::new(),
46+
}
47+
}
48+
}
49+
50+
fn render_algorithm_options(current_algorithm: DiffAlgo) -> Vec<VNode> {
51+
ALL_ALGORITHMS
52+
.iter()
53+
.map(|algo| {
54+
html! {
55+
<option selected={current_algorithm == *algo} value={algo.to_string()}>{algo}</option>
56+
}
57+
})
58+
.collect()
59+
}
60+
61+
#[function_component(DiffPage)]
62+
pub fn diff_page() -> Html {
63+
let original = use_state(|| DEFAULT_ORIGINAL.to_owned());
64+
let changed = use_state(|| DEFAULT_CHANGED.to_owned());
65+
let algorithm = use_state(|| DEFAULT_ALGORITHM);
66+
let diffs = use_state(|| {
67+
let original = DEFAULT_ORIGINAL.chars().collect::<Vec<_>>();
68+
let changed = DEFAULT_CHANGED.chars().collect::<Vec<_>>();
69+
let changes = TextDiff::configure()
70+
.algorithm(DEFAULT_ALGORITHM.into())
71+
.newline_terminated(true)
72+
.diff_chars(DEFAULT_ORIGINAL, DEFAULT_CHANGED);
73+
74+
DiffData {
75+
original,
76+
changed,
77+
changes: changes.ops().to_owned(),
78+
}
79+
});
80+
81+
let original_data = original.chars().collect::<Vec<_>>();
82+
let changed_data = changed.chars().collect::<Vec<_>>();
83+
let diffs_setter = diffs.setter();
84+
let algo = *algorithm;
85+
let compute_diff = Callback::from(move |_: ()| {
86+
let changes = capture_diff_slices(algo.into(), &original_data, &changed_data);
87+
88+
diffs_setter.set(DiffData {
89+
original: original_data.clone(),
90+
changed: changed_data.clone(),
91+
changes,
92+
});
93+
});
94+
95+
let original_local_storage = use_local_storage::<String>(LOCAL_STORAGE_ORIGINAL.to_owned());
96+
let original_setter = original.setter();
97+
let changed_local_storage = use_local_storage::<String>(LOCAL_STORAGE_CHANGED.to_owned());
98+
let changed_setter = changed.setter();
99+
let algorithm_local_storage = use_local_storage::<String>(LOCAL_STORAGE_ALGORITHM.to_owned());
100+
let algorithm_setter = algorithm.setter();
101+
let diffs_setter = diffs.setter();
102+
use_effect_with_deps(
103+
move |_: &[(); 0]| {
104+
let mut flag = false;
105+
106+
if let Some(original) = (*original_local_storage).as_ref() {
107+
original_setter.set(original.to_string());
108+
flag = true;
109+
}
110+
if let Some(changed) = (*changed_local_storage).as_ref() {
111+
changed_setter.set(changed.to_string());
112+
flag = true;
113+
}
114+
if let Some(raw_algorithm) = (*algorithm_local_storage).as_ref() {
115+
if let Ok(algorithm) = raw_algorithm.as_str().try_into() {
116+
algorithm_setter.set(algorithm);
117+
flag = true;
118+
}
119+
}
120+
121+
if flag {
122+
diffs_setter.set(DiffData::empty());
123+
}
124+
},
125+
[],
126+
);
127+
128+
let local_storage = use_local_storage::<String>(LOCAL_STORAGE_ORIGINAL.to_owned());
129+
use_effect_with_deps(
130+
move |[original]| {
131+
local_storage.set((*original).to_string());
132+
},
133+
[original.clone()],
134+
);
135+
136+
let local_storage = use_local_storage::<String>(LOCAL_STORAGE_CHANGED.to_owned());
137+
use_effect_with_deps(
138+
move |[changed]| {
139+
local_storage.set((*changed).to_string());
140+
},
141+
[changed.clone()],
142+
);
143+
144+
let local_storage = use_local_storage::<String>(LOCAL_STORAGE_ALGORITHM.to_owned());
145+
use_effect_with_deps(
146+
move |[algorithm]| {
147+
local_storage.set((*algorithm).to_string());
148+
},
149+
[algorithm.clone()],
150+
);
151+
152+
let original_setter = original.setter();
153+
let on_original_input = Callback::from(move |event: html::oninput::Event| {
154+
let input: HtmlInputElement = event.target_unchecked_into();
155+
original_setter.set(input.value());
156+
});
157+
158+
let changed_setter = changed.setter();
159+
let on_changed_input = Callback::from(move |event: html::oninput::Event| {
160+
let input: HtmlInputElement = event.target_unchecked_into();
161+
changed_setter.set(input.value());
162+
});
163+
164+
let diff = compute_diff.clone();
165+
let onclick = Callback::from(move |_| {
166+
diff.emit(());
167+
});
168+
169+
let algorithm_setter = algorithm.setter();
170+
let on_algorithm_change = Callback::from(move |event: Event| {
171+
let input: HtmlInputElement = event.target_unchecked_into();
172+
if let Ok(algorithm) = input.value().as_str().try_into() {
173+
algorithm_setter.set(algorithm);
174+
}
175+
});
176+
177+
let onkeydown = Callback::from(move |event: KeyboardEvent| {
178+
if event.ctrl_key() && event.code() == "Enter" {
179+
compute_diff.emit(());
180+
}
181+
});
182+
183+
html! {
184+
<div class={classes!("vertical", "asn1-page")} {onkeydown}>
185+
<div class="horizontal">
186+
<span>{"Diff algorithm:"}</span>
187+
<div>
188+
<select class="base-input" onchange={on_algorithm_change}>
189+
{render_algorithm_options(*algorithm)}
190+
</select>
191+
</div>
192+
</div>
193+
<div class="horizontal">
194+
<textarea
195+
rows="8"
196+
placeholder={"original"}
197+
class="base-input"
198+
value={(*original).clone()}
199+
oninput={on_original_input}
200+
/>
201+
<textarea
202+
rows="8"
203+
placeholder={"changed"}
204+
class="base-input"
205+
value={(*changed).clone()}
206+
oninput={on_changed_input}
207+
/>
208+
</div>
209+
<div class="horizontal">
210+
<button class="action-button" onclick={onclick}>{"Diff"}</button>
211+
<span class="total">{"(ctrl+enter)"}</span>
212+
</div>
213+
<DiffViewer diff={(*diffs).clone()} />
214+
</div>
215+
}
216+
}

src/diff/diff_algo.rs

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use std::fmt::{Display, Formatter};
2+
use std::ops::Deref;
3+
4+
use similar::Algorithm;
5+
6+
const MYERS: &str = "Myers";
7+
const PATIENCE: &str = "Patience";
8+
const LCS: &str = "Lcs";
9+
10+
#[derive(Clone, Copy, Eq, PartialEq)]
11+
pub struct DiffAlgo(pub Algorithm);
12+
13+
impl From<DiffAlgo> for Algorithm {
14+
fn from(value: DiffAlgo) -> Self {
15+
value.0
16+
}
17+
}
18+
19+
impl From<Algorithm> for DiffAlgo {
20+
fn from(value: Algorithm) -> Self {
21+
Self(value)
22+
}
23+
}
24+
25+
impl Display for DiffAlgo {
26+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
27+
f.write_str(match self.0 {
28+
Algorithm::Myers => MYERS,
29+
Algorithm::Patience => PATIENCE,
30+
Algorithm::Lcs => LCS,
31+
})
32+
}
33+
}
34+
35+
impl Deref for DiffAlgo {
36+
type Target = Algorithm;
37+
38+
fn deref(&self) -> &Self::Target {
39+
&self.0
40+
}
41+
}
42+
43+
impl TryFrom<&str> for DiffAlgo {
44+
type Error = String;
45+
46+
fn try_from(value: &str) -> Result<Self, Self::Error> {
47+
Ok(match value {
48+
MYERS => Algorithm::Myers.into(),
49+
PATIENCE => Algorithm::Patience.into(),
50+
LCS => Algorithm::Lcs.into(),
51+
_ => return Err(format!("Unsupported diff algorithm: {}.", value)),
52+
})
53+
}
54+
}

0 commit comments

Comments
 (0)