1 | use crate::{AnsiColors, Color, DynColor, DynColors}; |
2 | use core::fmt; |
3 | |
4 | #[cfg (doc)] |
5 | use crate::OwoColorize; |
6 | |
7 | /// A runtime-configurable text effect for use with [`Style`] |
8 | #[allow (missing_docs)] |
9 | #[derive (Debug, Copy, Clone)] |
10 | pub enum Effect { |
11 | Bold, |
12 | Dimmed, |
13 | Italic, |
14 | Underline, |
15 | Blink, |
16 | BlinkFast, |
17 | Reversed, |
18 | Hidden, |
19 | Strikethrough, |
20 | } |
21 | |
22 | macro_rules! color_methods { |
23 | ($( |
24 | #[$fg_meta:meta] #[$bg_meta:meta] $color:ident $fg_method:ident $bg_method:ident |
25 | ),* $(,)?) => { |
26 | $( |
27 | #[$fg_meta] |
28 | #[must_use] |
29 | pub fn $fg_method(mut self) -> Self { |
30 | self.fg = Some(DynColors::Ansi(AnsiColors::$color)); |
31 | self |
32 | } |
33 | |
34 | #[$fg_meta] |
35 | #[must_use] |
36 | pub fn $bg_method(mut self) -> Self { |
37 | self.bg = Some(DynColors::Ansi(AnsiColors::$color)); |
38 | self |
39 | } |
40 | )* |
41 | }; |
42 | } |
43 | |
44 | macro_rules! style_methods { |
45 | ($(#[$meta:meta] ($name:ident, $set_name:ident)),* $(,)?) => { |
46 | $( |
47 | #[$meta] |
48 | #[must_use] |
49 | pub fn $name(mut self) -> Self { |
50 | self.style_flags.$set_name(true); |
51 | self |
52 | } |
53 | )* |
54 | }; |
55 | } |
56 | |
57 | const _: () = (); // workaround for syntax highlighting bug |
58 | |
59 | /// A wrapper type which applies a [`Style`] when displaying the inner type |
60 | pub struct Styled<T> { |
61 | /// The target value to be styled |
62 | pub(crate) target: T, |
63 | /// The style to apply to target |
64 | pub style: Style, |
65 | } |
66 | |
67 | /// A pre-computed style that can be applied to a struct using [`OwoColorize::style`]. Its |
68 | /// interface mimicks that of [`OwoColorize`], but instead of chaining methods on your |
69 | /// object, you instead chain them on the `Style` object before applying it. |
70 | /// |
71 | /// ```rust |
72 | /// use owo_colors::{OwoColorize, Style}; |
73 | /// |
74 | /// let my_style = Style::new() |
75 | /// .red() |
76 | /// .on_white() |
77 | /// .strikethrough(); |
78 | /// |
79 | /// println!("{}" , "red text, white background, struck through" .style(my_style)); |
80 | /// ``` |
81 | #[derive (Debug, Default, Copy, Clone, PartialEq)] |
82 | pub struct Style { |
83 | pub(crate) fg: Option<DynColors>, |
84 | pub(crate) bg: Option<DynColors>, |
85 | pub(crate) bold: bool, |
86 | pub(crate) style_flags: StyleFlags, |
87 | } |
88 | |
89 | #[repr (transparent)] |
90 | #[derive (Debug, Default, Copy, Clone, PartialEq)] |
91 | pub(crate) struct StyleFlags(pub(crate) u8); |
92 | |
93 | const DIMMED_SHIFT: u8 = 0; |
94 | const ITALIC_SHIFT: u8 = 1; |
95 | const UNDERLINE_SHIFT: u8 = 2; |
96 | const BLINK_SHIFT: u8 = 3; |
97 | const BLINK_FAST_SHIFT: u8 = 4; |
98 | const REVERSED_SHIFT: u8 = 5; |
99 | const HIDDEN_SHIFT: u8 = 6; |
100 | const STRIKETHROUGH_SHIFT: u8 = 7; |
101 | |
102 | macro_rules! style_flags_methods { |
103 | ($(($shift:ident, $name:ident, $set_name:ident)),* $(,)?) => { |
104 | $( |
105 | fn $name(&self) -> bool { |
106 | ((self.0 >> $shift) & 1) != 0 |
107 | } |
108 | |
109 | fn $set_name(&mut self, $name: bool) { |
110 | self.0 = (self.0 & !(1 << $shift)) | (($name as u8) << $shift); |
111 | } |
112 | )* |
113 | }; |
114 | } |
115 | |
116 | impl StyleFlags { |
117 | style_flags_methods! { |
118 | (DIMMED_SHIFT, dimmed, set_dimmed), |
119 | (ITALIC_SHIFT, italic, set_italic), |
120 | (UNDERLINE_SHIFT, underline, set_underline), |
121 | (BLINK_SHIFT, blink, set_blink), |
122 | (BLINK_FAST_SHIFT, blink_fast, set_blink_fast), |
123 | (REVERSED_SHIFT, reversed, set_reversed), |
124 | (HIDDEN_SHIFT, hidden, set_hidden), |
125 | (STRIKETHROUGH_SHIFT, strikethrough, set_strikethrough), |
126 | } |
127 | } |
128 | |
129 | impl Style { |
130 | /// Create a new style to be applied later |
131 | #[must_use ] |
132 | pub fn new() -> Self { |
133 | Self::default() |
134 | } |
135 | |
136 | /// Apply the style to a given struct to output |
137 | pub fn style<T>(&self, target: T) -> Styled<T> { |
138 | Styled { |
139 | target, |
140 | style: *self, |
141 | } |
142 | } |
143 | |
144 | /// Set the foreground color generically |
145 | /// |
146 | /// ```rust |
147 | /// use owo_colors::{OwoColorize, colors::*}; |
148 | /// |
149 | /// println!("{}" , "red foreground" .fg::<Red>()); |
150 | /// ``` |
151 | #[must_use ] |
152 | pub fn fg<C: Color>(mut self) -> Self { |
153 | self.fg = Some(C::into_dyncolors()); |
154 | self |
155 | } |
156 | |
157 | /// Set the background color generically. |
158 | /// |
159 | /// ```rust |
160 | /// use owo_colors::{OwoColorize, colors::*}; |
161 | /// |
162 | /// println!("{}" , "black background" .bg::<Black>()); |
163 | /// ``` |
164 | #[must_use ] |
165 | pub fn bg<C: Color>(mut self) -> Self { |
166 | self.bg = Some(C::into_dyncolors()); |
167 | self |
168 | } |
169 | |
170 | /// Removes the foreground color from the style. Note that this does not apply |
171 | /// the default color, but rather represents not changing the current terminal color. |
172 | /// |
173 | /// If you wish to actively change the terminal color back to the default, see |
174 | /// [`Style::default_color`]. |
175 | #[must_use ] |
176 | pub fn remove_fg(mut self) -> Self { |
177 | self.fg = None; |
178 | self |
179 | } |
180 | |
181 | /// Removes the background color from the style. Note that this does not apply |
182 | /// the default color, but rather represents not changing the current terminal color. |
183 | /// |
184 | /// If you wish to actively change the terminal color back to the default, see |
185 | /// [`Style::on_default_color`]. |
186 | #[must_use ] |
187 | pub fn remove_bg(mut self) -> Self { |
188 | self.bg = None; |
189 | self |
190 | } |
191 | |
192 | color_methods! { |
193 | /// Change the foreground color to black |
194 | /// Change the background color to black |
195 | Black black on_black, |
196 | /// Change the foreground color to red |
197 | /// Change the background color to red |
198 | Red red on_red, |
199 | /// Change the foreground color to green |
200 | /// Change the background color to green |
201 | Green green on_green, |
202 | /// Change the foreground color to yellow |
203 | /// Change the background color to yellow |
204 | Yellow yellow on_yellow, |
205 | /// Change the foreground color to blue |
206 | /// Change the background color to blue |
207 | Blue blue on_blue, |
208 | /// Change the foreground color to magenta |
209 | /// Change the background color to magenta |
210 | Magenta magenta on_magenta, |
211 | /// Change the foreground color to purple |
212 | /// Change the background color to purple |
213 | Magenta purple on_purple, |
214 | /// Change the foreground color to cyan |
215 | /// Change the background color to cyan |
216 | Cyan cyan on_cyan, |
217 | /// Change the foreground color to white |
218 | /// Change the background color to white |
219 | White white on_white, |
220 | |
221 | /// Change the foreground color to the terminal default |
222 | /// Change the background color to the terminal default |
223 | Default default_color on_default_color, |
224 | |
225 | /// Change the foreground color to bright black |
226 | /// Change the background color to bright black |
227 | BrightBlack bright_black on_bright_black, |
228 | /// Change the foreground color to bright red |
229 | /// Change the background color to bright red |
230 | BrightRed bright_red on_bright_red, |
231 | /// Change the foreground color to bright green |
232 | /// Change the background color to bright green |
233 | BrightGreen bright_green on_bright_green, |
234 | /// Change the foreground color to bright yellow |
235 | /// Change the background color to bright yellow |
236 | BrightYellow bright_yellow on_bright_yellow, |
237 | /// Change the foreground color to bright blue |
238 | /// Change the background color to bright blue |
239 | BrightBlue bright_blue on_bright_blue, |
240 | /// Change the foreground color to bright magenta |
241 | /// Change the background color to bright magenta |
242 | BrightMagenta bright_magenta on_bright_magenta, |
243 | /// Change the foreground color to bright purple |
244 | /// Change the background color to bright purple |
245 | BrightMagenta bright_purple on_bright_purple, |
246 | /// Change the foreground color to bright cyan |
247 | /// Change the background color to bright cyan |
248 | BrightCyan bright_cyan on_bright_cyan, |
249 | /// Change the foreground color to bright white |
250 | /// Change the background color to bright white |
251 | BrightWhite bright_white on_bright_white, |
252 | } |
253 | |
254 | /// Make the text bold |
255 | #[must_use ] |
256 | pub fn bold(mut self) -> Self { |
257 | self.bold = true; |
258 | self |
259 | } |
260 | |
261 | style_methods! { |
262 | /// Make the text dim |
263 | (dimmed, set_dimmed), |
264 | /// Make the text italicized |
265 | (italic, set_italic), |
266 | /// Make the text italicized |
267 | (underline, set_underline), |
268 | /// Make the text blink |
269 | (blink, set_blink), |
270 | /// Make the text blink (but fast!) |
271 | (blink_fast, set_blink_fast), |
272 | /// Swap the foreground and background colors |
273 | (reversed, set_reversed), |
274 | /// Hide the text |
275 | (hidden, set_hidden), |
276 | /// Cross out the text |
277 | (strikethrough, set_strikethrough), |
278 | } |
279 | |
280 | fn set_effect(&mut self, effect: Effect, to: bool) { |
281 | use Effect::*; |
282 | match effect { |
283 | Bold => self.bold = to, |
284 | Dimmed => self.style_flags.set_dimmed(to), |
285 | Italic => self.style_flags.set_italic(to), |
286 | Underline => self.style_flags.set_underline(to), |
287 | Blink => self.style_flags.set_blink(to), |
288 | BlinkFast => self.style_flags.set_blink_fast(to), |
289 | Reversed => self.style_flags.set_reversed(to), |
290 | Hidden => self.style_flags.set_hidden(to), |
291 | Strikethrough => self.style_flags.set_strikethrough(to), |
292 | } |
293 | } |
294 | |
295 | fn set_effects(&mut self, effects: &[Effect], to: bool) { |
296 | for e in effects { |
297 | self.set_effect(*e, to) |
298 | } |
299 | } |
300 | |
301 | /// Apply a given effect from the style |
302 | #[must_use ] |
303 | pub fn effect(mut self, effect: Effect) -> Self { |
304 | self.set_effect(effect, true); |
305 | self |
306 | } |
307 | |
308 | /// Remove a given effect from the style |
309 | #[must_use ] |
310 | pub fn remove_effect(mut self, effect: Effect) -> Self { |
311 | self.set_effect(effect, false); |
312 | self |
313 | } |
314 | |
315 | /// Apply a given set of effects to the style |
316 | #[must_use ] |
317 | pub fn effects(mut self, effects: &[Effect]) -> Self { |
318 | self.set_effects(effects, true); |
319 | self |
320 | } |
321 | |
322 | /// Remove a given set of effects from the style |
323 | #[must_use ] |
324 | pub fn remove_effects(mut self, effects: &[Effect]) -> Self { |
325 | self.set_effects(effects, false); |
326 | self |
327 | } |
328 | |
329 | /// Disables all the given effects from the style |
330 | #[must_use ] |
331 | pub fn remove_all_effects(mut self) -> Self { |
332 | self.bold = false; |
333 | self.style_flags = StyleFlags::default(); |
334 | self |
335 | } |
336 | |
337 | /// Set the foreground color at runtime. Only use if you do not know which color will be used at |
338 | /// compile-time. If the color is constant, use either [`OwoColorize::fg`](crate::OwoColorize::fg) or |
339 | /// a color-specific method, such as [`OwoColorize::green`](crate::OwoColorize::green), |
340 | /// |
341 | /// ```rust |
342 | /// use owo_colors::{OwoColorize, AnsiColors}; |
343 | /// |
344 | /// println!("{}" , "green" .color(AnsiColors::Green)); |
345 | /// ``` |
346 | #[must_use ] |
347 | pub fn color<Color: DynColor>(mut self, color: Color) -> Self { |
348 | self.fg = Some(color.get_dyncolors_fg()); |
349 | self |
350 | } |
351 | |
352 | /// Set the background color at runtime. Only use if you do not know what color to use at |
353 | /// compile-time. If the color is constant, use either [`OwoColorize::bg`](crate::OwoColorize::bg) or |
354 | /// a color-specific method, such as [`OwoColorize::on_yellow`](crate::OwoColorize::on_yellow), |
355 | /// |
356 | /// ```rust |
357 | /// use owo_colors::{OwoColorize, AnsiColors}; |
358 | /// |
359 | /// println!("{}" , "yellow background" .on_color(AnsiColors::BrightYellow)); |
360 | /// ``` |
361 | #[must_use ] |
362 | pub fn on_color<Color: DynColor>(mut self, color: Color) -> Self { |
363 | self.bg = Some(color.get_dyncolors_bg()); |
364 | self |
365 | } |
366 | |
367 | /// Set the foreground color to a specific RGB value. |
368 | #[must_use ] |
369 | pub fn fg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self { |
370 | self.fg = Some(DynColors::Rgb(R, G, B)); |
371 | |
372 | self |
373 | } |
374 | |
375 | /// Set the background color to a specific RGB value. |
376 | #[must_use ] |
377 | pub fn bg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self { |
378 | self.bg = Some(DynColors::Rgb(R, G, B)); |
379 | |
380 | self |
381 | } |
382 | |
383 | /// Sets the foreground color to an RGB value. |
384 | #[must_use ] |
385 | pub fn truecolor(mut self, r: u8, g: u8, b: u8) -> Self { |
386 | self.fg = Some(DynColors::Rgb(r, g, b)); |
387 | self |
388 | } |
389 | |
390 | /// Sets the background color to an RGB value. |
391 | #[must_use ] |
392 | pub fn on_truecolor(mut self, r: u8, g: u8, b: u8) -> Self { |
393 | self.bg = Some(DynColors::Rgb(r, g, b)); |
394 | self |
395 | } |
396 | |
397 | /// Returns if the style does not apply any formatting |
398 | #[must_use ] |
399 | #[inline ] |
400 | pub fn is_plain(&self) -> bool { |
401 | let s = &self; |
402 | !(s.fg.is_some() || s.bg.is_some() || s.bold || s.style_flags != StyleFlags::default()) |
403 | } |
404 | |
405 | /// Applies the ANSI-prefix for this style to the given formatter |
406 | #[inline ] |
407 | #[allow (unused_assignments)] |
408 | pub fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
409 | let s = self; |
410 | let format_less_important_effects = s.style_flags != StyleFlags::default(); |
411 | let format_effect = s.bold || format_less_important_effects; |
412 | let format_any = !self.is_plain(); |
413 | |
414 | let mut semicolon = false; |
415 | |
416 | if format_any { |
417 | f.write_str(" \x1b[" )?; |
418 | } |
419 | |
420 | if let Some(fg) = s.fg { |
421 | <DynColors as DynColor>::fmt_raw_ansi_fg(&fg, f)?; |
422 | semicolon = true; |
423 | } |
424 | |
425 | if let Some(bg) = s.bg { |
426 | if s.fg.is_some() { |
427 | f.write_str(";" )?; |
428 | } |
429 | <DynColors as DynColor>::fmt_raw_ansi_bg(&bg, f)?; |
430 | } |
431 | |
432 | if format_effect { |
433 | if s.bold { |
434 | if semicolon { |
435 | f.write_str(";" )?; |
436 | } |
437 | |
438 | f.write_str("1" )?; |
439 | |
440 | semicolon = true; |
441 | } |
442 | |
443 | macro_rules! text_effect_fmt { |
444 | ($style:ident, $formatter:ident, $semicolon:ident, $(($attr:ident, $value:literal)),* $(,)?) => { |
445 | $( |
446 | if $style.style_flags.$attr() { |
447 | if $semicolon { |
448 | $formatter.write_str(";" )?; |
449 | } |
450 | $formatter.write_str($value)?; |
451 | |
452 | $semicolon = true; |
453 | } |
454 | )+ |
455 | } |
456 | } |
457 | |
458 | if format_less_important_effects { |
459 | text_effect_fmt! { |
460 | s, f, semicolon, |
461 | (dimmed, "2" ), |
462 | (italic, "3" ), |
463 | (underline, "4" ), |
464 | (blink, "5" ), |
465 | (blink_fast, "6" ), |
466 | (reversed, "7" ), |
467 | (hidden, "8" ), |
468 | (strikethrough, "9" ), |
469 | } |
470 | } |
471 | } |
472 | |
473 | if format_any { |
474 | f.write_str("m" )?; |
475 | } |
476 | Ok(()) |
477 | } |
478 | |
479 | /// Applies the ANSI-suffix for this style to the given formatter |
480 | #[inline ] |
481 | pub fn fmt_suffix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
482 | if !self.is_plain() { |
483 | f.write_str(" \x1b[0m" )?; |
484 | } |
485 | Ok(()) |
486 | } |
487 | } |
488 | |
489 | /// Helper to create [`Style`]s more ergonomically |
490 | pub fn style() -> Style { |
491 | Style::new() |
492 | } |
493 | |
494 | impl<T> Styled<T> { |
495 | /// Returns a reference to the inner value to be styled |
496 | pub fn inner(&self) -> &T { |
497 | &self.target |
498 | } |
499 | |
500 | /// Returns a mutable reference to the inner value to be styled |
501 | pub fn inner_mut(&mut self) -> &mut T { |
502 | &mut self.target |
503 | } |
504 | } |
505 | |
506 | macro_rules! impl_fmt { |
507 | ($($trait:path),* $(,)?) => { |
508 | $( |
509 | impl<T: $trait> $trait for Styled<T> { |
510 | #[allow(unused_assignments)] |
511 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
512 | self.style.fmt_prefix(f)?; |
513 | <T as $trait>::fmt(&self.target, f)?; |
514 | self.style.fmt_suffix(f) |
515 | } |
516 | } |
517 | )* |
518 | }; |
519 | } |
520 | |
521 | impl_fmt! { |
522 | fmt::Display, |
523 | fmt::Debug, |
524 | fmt::UpperHex, |
525 | fmt::LowerHex, |
526 | fmt::Binary, |
527 | fmt::UpperExp, |
528 | fmt::LowerExp, |
529 | fmt::Octal, |
530 | fmt::Pointer, |
531 | } |
532 | |
533 | #[cfg (test)] |
534 | mod tests { |
535 | use super::*; |
536 | use crate::{AnsiColors, OwoColorize}; |
537 | |
538 | struct StylePrefixOnly(Style); |
539 | impl fmt::Display for StylePrefixOnly { |
540 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
541 | self.0.fmt_prefix(f) |
542 | } |
543 | } |
544 | |
545 | struct StyleSuffixOnly(Style); |
546 | impl fmt::Display for StyleSuffixOnly { |
547 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
548 | self.0.fmt_suffix(f) |
549 | } |
550 | } |
551 | |
552 | #[test ] |
553 | fn test_it() { |
554 | let style = Style::new() |
555 | .bright_white() |
556 | .on_blue() |
557 | .bold() |
558 | .dimmed() |
559 | .italic() |
560 | .underline() |
561 | .blink() |
562 | //.blink_fast() |
563 | //.reversed() |
564 | //.hidden() |
565 | .strikethrough(); |
566 | let s = style.style("TEST" ); |
567 | let s2 = format!("{}" , &s); |
568 | println!("{}" , &s2); |
569 | assert_eq!(&s2, " \u{1b}[97;44;1;2;3;4;5;9mTEST \u{1b}[0m" ); |
570 | |
571 | let prefix = format!("{}" , StylePrefixOnly(style)); |
572 | assert_eq!(&prefix, " \u{1b}[97;44;1;2;3;4;5;9m" ); |
573 | |
574 | let suffix = format!("{}" , StyleSuffixOnly(style)); |
575 | assert_eq!(&suffix, " \u{1b}[0m" ); |
576 | } |
577 | |
578 | #[test ] |
579 | fn test_effects() { |
580 | use Effect::*; |
581 | let style = Style::new().effects(&[Strikethrough, Underline]); |
582 | |
583 | let s = style.style("TEST" ); |
584 | let s2 = format!("{}" , &s); |
585 | println!("{}" , &s2); |
586 | assert_eq!(&s2, " \u{1b}[4;9mTEST \u{1b}[0m" ); |
587 | } |
588 | |
589 | #[test ] |
590 | fn test_color() { |
591 | let style = Style::new() |
592 | .color(AnsiColors::White) |
593 | .on_color(AnsiColors::Black); |
594 | |
595 | let s = style.style("TEST" ); |
596 | let s2 = format!("{}" , &s); |
597 | println!("{}" , &s2); |
598 | assert_eq!(&s2, " \u{1b}[37;40mTEST \u{1b}[0m" ); |
599 | } |
600 | |
601 | #[test ] |
602 | fn test_truecolor() { |
603 | let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0); |
604 | |
605 | let s = style.style("TEST" ); |
606 | let s2 = format!("{}" , &s); |
607 | println!("{}" , &s2); |
608 | assert_eq!(&s2, " \u{1b}[38;2;255;255;255;48;2;0;0;0mTEST \u{1b}[0m" ); |
609 | } |
610 | |
611 | #[test ] |
612 | fn test_string_reference() { |
613 | let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0); |
614 | |
615 | let string = String::from("TEST" ); |
616 | let s = style.style(&string); |
617 | let s2 = format!("{}" , &s); |
618 | println!("{}" , &s2); |
619 | assert_eq!(&s2, " \u{1b}[38;2;255;255;255;48;2;0;0;0mTEST \u{1b}[0m" ); |
620 | } |
621 | |
622 | #[test ] |
623 | fn test_owocolorize() { |
624 | let style = Style::new().bright_white().on_blue(); |
625 | |
626 | let s = "TEST" .style(style); |
627 | let s2 = format!("{}" , &s); |
628 | println!("{}" , &s2); |
629 | assert_eq!(&s2, " \u{1b}[97;44mTEST \u{1b}[0m" ); |
630 | } |
631 | |
632 | #[test ] |
633 | fn test_is_plain() { |
634 | let style = Style::new().bright_white().on_blue(); |
635 | |
636 | assert!(!style.is_plain()); |
637 | assert!(Style::default().is_plain()); |
638 | |
639 | let string = String::from("TEST" ); |
640 | let s = Style::default().style(&string); |
641 | let s2 = format!("{}" , &s); |
642 | |
643 | assert_eq!(string, s2) |
644 | } |
645 | |
646 | #[test ] |
647 | fn test_inner() { |
648 | let style = Style::default(); |
649 | |
650 | let mut s = "TEST" .style(style); |
651 | |
652 | assert_eq!(&&"TEST" , s.inner()); |
653 | |
654 | *s.inner_mut() = &"changed" ; |
655 | assert_eq!(&&"changed" , s.inner()); |
656 | assert_eq!("changed" , format!("{}" , s)); |
657 | } |
658 | } |
659 | |