From 78f5ba7ac12cc09b266aa4eb5102f301b7ca06a9 Mon Sep 17 00:00:00 2001 From: emilis Date: Mon, 16 Jan 2023 15:23:44 +0000 Subject: [PATCH] added config file --- Cargo.lock | 103 +++++++++++++++++++++++++++ Cargo.toml | 13 ++++ src/cfg.rs | 35 +++++++++ src/display/body.rs | 36 ++++------ src/display/frame.rs | 141 +++++++++++++++++++------------------ src/display/mod.rs | 13 ++-- src/display/theme.rs | 164 ++++++++++++++++++++++++++++++++++--------- src/main.rs | 5 +- 8 files changed, 378 insertions(+), 132 deletions(-) create mode 100644 src/cfg.rs diff --git a/Cargo.lock b/Cargo.lock index 5bf2bd2..c1ec684 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,37 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -46,8 +77,11 @@ name = "kk" version = "0.1.0" dependencies = [ "anyhow", + "serde", "termion", "tokio", + "toml", + "xdg", ] [[package]] @@ -174,12 +208,43 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -228,6 +293,26 @@ dependencies = [ "redox_termios", ] +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.24.1" @@ -259,6 +344,15 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +dependencies = [ + "serde", +] + [[package]] name = "unicode-ident" version = "1.0.6" @@ -349,3 +443,12 @@ name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "xdg" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" +dependencies = [ + "dirs", +] diff --git a/Cargo.toml b/Cargo.toml index 1998d27..0b246b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,23 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[profile.release] +lto = true + +# [profile.release.build-override] +# opt-level = 3 +# lto = true + [dependencies] anyhow = "1.0.68" # misskey = "0.2.0" termion = "2.0.1" +xdg = "2.4.1" +toml = "0.5.10" + +[dependencies.serde] +version = "1.0" +features = ["std", "derive"] [dependencies.tokio] version = "1.24.1" diff --git a/src/cfg.rs b/src/cfg.rs new file mode 100644 index 0000000..015ab53 --- /dev/null +++ b/src/cfg.rs @@ -0,0 +1,35 @@ +use std::fs::{self, File}; + +use crate::display::theme::Theme; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct Config { + pub auth: Auth, + pub theme: Theme, +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct Auth { + hostname: Option, + token: Option, +} + +impl Config { + pub fn load_or_create() -> Result { + const CONFIG_FILENAME: &str = "kk.toml"; + let xdg_dirs = xdg::BaseDirectories::with_prefix("kk")?; + let cfg_filename = + xdg_dirs.place_config_file(CONFIG_FILENAME)?; + Ok(match fs::read(&cfg_filename) { + Ok(f) => toml::from_slice(&f)?, + Err(_) => { + // Save default file + let cfg = Self::default(); + let fmt_cfg = toml::to_string_pretty(&cfg)?; + fs::write(cfg_filename, fmt_cfg)?; + cfg + } + }) + } +} diff --git a/src/display/body.rs b/src/display/body.rs index 7fe2dd7..e7d19c1 100644 --- a/src/display/body.rs +++ b/src/display/body.rs @@ -45,7 +45,7 @@ impl Body { "{hide}{clear}{fr}{cursor}", clear = clear::All, hide = cursor::Hide, - fr = env.frame.frame_str(env.theme.colors.primary), + fr = env.frame.frame_str(env.theme.windows.primary), cursor = env.frame.goto(0, 0), )?; screen.flush()?; @@ -240,6 +240,9 @@ impl SigninPage { #[inline] fn char(&mut self, c: char) { + if ['\t', '\n', '\r'].contains(&c) { + return; + } match self.cursor { SigninCursorLocation::Hostname => { self.hostname.push(c); @@ -264,13 +267,7 @@ impl Page for SigninPage { termion::event::Key::Char(c) => self.char(c), termion::event::Key::Up => self.previous(), termion::event::Key::Down => self.next(), - termion::event::Key::Backspace => { - if let SigninCursorLocation::Hostname = - self.cursor - { - self.hostname.pop(); - } - } + termion::event::Key::Backspace => self.del(), termion::event::Key::Delete => {} termion::event::Key::Left => {} termion::event::Key::Right => {} @@ -284,10 +281,10 @@ impl Page for SigninPage { screen, "{}{}{}", env.primary_frame(), - fr.frame_str(env.theme.colors.subwin), + fr.frame_str(env.theme.windows.subwin), fr.make(self.draw( - env.theme.colors.highlight, - env.theme.colors.subwin + env.theme.windows.highlight, + env.theme.windows.subwin )) )?; screen.flush()?; @@ -301,24 +298,21 @@ impl Page for SigninPage { screen: &mut RawTerminal, ) -> Result<()> { self.frame = Some(env.frame.sub( - env.theme.colors.subframe, - super::frame::FrameSize::ByAbsolute(70, 15), - 1, + env.theme.frames.inner, + super::frame::FrameSize::ByAbsolute(70, 10), )); write!( screen, - "{theme}{clear}{frame}{subframe}{content}{hide_cursor}", - theme = env.theme.frame(), - clear = clear::All, - frame = env.frame.frame_str(env.theme.colors.primary), + "{frame}{subframe}{content}{hide_cursor}", + frame = env.frame.frame_str(env.theme.windows.primary), subframe = self .frame .unwrap() - .frame_str(env.theme.colors.subwin), + .frame_str(env.theme.windows.subwin), hide_cursor = cursor::Hide, content = self.frame.unwrap().make(self.draw( - env.theme.colors.highlight, - env.theme.colors.subwin + env.theme.windows.highlight, + env.theme.windows.subwin )) )?; screen.flush()?; diff --git a/src/display/frame.rs b/src/display/frame.rs index 35bac99..68a8219 100644 --- a/src/display/frame.rs +++ b/src/display/frame.rs @@ -1,17 +1,10 @@ use termion::cursor; -const ESTIMATED_FRAME_BIT_SIZE: usize = 11; - -use crate::display::compose::Text; - use super::{ compose::{Component, Components}, - theme::ColorSet, + theme::{self, ColorSet}, }; -const FRAME_CHAR_HORIZONTAL: char = ' '; -const FRAME_CHAR_VERTICAL: char = ' '; - pub enum FrameSize { ByAbsolute(u16, u16), ByPercent(u16, u16), @@ -22,36 +15,30 @@ pub struct Frame { // Both are (w, h) start: (u16, u16), end: (u16, u16), - border: u16, - theme: ColorSet, + theme: theme::Frame, } impl Frame { #[inline(always)] - pub fn sub( - &self, - theme: ColorSet, - size: FrameSize, - border: u16, - ) -> Frame { + pub fn sub(&self, theme: theme::Frame, size: FrameSize) -> Frame { let (p_width, p_height) = self.size(); let sub_size = size.abs_size(( - p_width - self.border * 2, - p_height - self.border * 2, + p_width - self.theme.width * 2, + p_height - self.theme.width * 2, )); - self.child_centered(sub_size, border, theme) + self.child_centered(sub_size, theme) } #[inline(always)] pub fn goto(&self, x: u16, y: u16) -> String { cursor::Goto( - self.border.max( - (self.border + self.start.0 + x) - .min(self.end.0 - self.border), + self.theme.width.max( + (self.theme.width + self.start.0 + x) + .min(self.end.0 - self.theme.width), ), - self.border.max( - (self.border + self.start.1 + y) - .min(self.end.1 - self.border), + self.theme.width.max( + (self.theme.width + self.start.1 + y) + .min(self.end.1 - self.theme.width), ), ) .into() @@ -86,14 +73,14 @@ impl Frame { #[inline(always)] fn write_centered_clear(&self, s: &str) -> Component { let width = self.size().0; - let limit = width - self.border * 2; + let limit = width - self.theme.width * 2; let s = if s.len() > limit as usize { &s[..limit as usize] } else { s }; let len = s.len() as u16; - let base_size = ((width - len) / 2) - self.border; + let base_size = ((width - len) / 2) - self.theme.width; Component::String(super::compose::Text::PaddedBothSides( base_size + ((width - len) % 2), s.into(), @@ -102,12 +89,14 @@ impl Frame { } #[inline(always)] - fn write_frame_line(&self, y: u16, length: u16) -> Components { - vec![ - self.goto_internal(0, y), - Component::Repeated(FRAME_CHAR_HORIZONTAL, length), - ] - .into() + fn write_frame_line( + &self, + y: u16, + length: u16, + c: char, + ) -> Components { + vec![self.goto_internal(0, y), Component::Repeated(c, length)] + .into() } pub fn make(&self, comp: Components) -> String { @@ -123,31 +112,31 @@ impl Frame { #[inline(always)] pub fn inner_size(&self) -> (u16, u16) { ( - self.end.0 - self.start.0 - self.border * 2, - self.end.1 - self.start.1 - self.border * 2, + self.end.0 - self.start.0 - self.theme.width * 2, + self.end.1 - self.start.1 - self.theme.width * 2, ) } #[inline(always)] pub fn from_terminal_size( - theme: ColorSet, - width: u16, + theme: theme::Frame, ) -> Result { let term = termion::terminal_size()?; - Ok(Self::from_bottom_right(term, term, width, theme)) + Ok(Self::from_bottom_right(term, term, theme)) } #[inline(always)] fn child_centered( &self, (pos_w, pos_h): (u16, u16), - border: u16, - theme: ColorSet, + theme: theme::Frame, ) -> Frame { - let parent_offset = self.border / 2; + let parent_offset = self.theme.width / 2; let (parent_w, parent_h) = self.size(); - let (parent_w, parent_h) = - (parent_w - self.border, parent_h - self.border); + let (parent_w, parent_h) = ( + parent_w - self.theme.width, + parent_h - self.theme.width, + ); let start = ( 1.max( (parent_w - pos_w) / 2 @@ -163,7 +152,6 @@ impl Frame { let frame = Self { end: (start.0 + pos_w, start.1 + pos_h), start, - border, theme, }; frame @@ -173,21 +161,19 @@ impl Frame { fn from_bottom_right( (pos_w, pos_h): (u16, u16), (term_w, term_h): (u16, u16), - border: u16, - theme: ColorSet, + theme: theme::Frame, ) -> Self { let start = (1.max(term_w - pos_w), 1.max(term_h - pos_h)); let frame = Self { end: (start.0 + pos_w, start.1 + pos_h), start, - border, theme, }; if frame.start.0 > frame.end.0 || frame.start.1 > frame.end.1 { eprintln!( "pos_w {}, pos_h {}, term_w {}, term_h {}, border {}", - pos_w, pos_h, term_w, term_h, border + pos_w, pos_h, term_w, term_h, theme.width, ); panic!( "start.0: {} end.0: {}, start.1: {}, end.1: {}", @@ -211,49 +197,66 @@ impl Frame { pub fn compose(&self, body_theme: ColorSet) -> Components { let body_theme = Component::Internal(body_theme.to_string()); let (w_len, h_len) = self.size(); - let frame_theme = Component::Internal(self.theme.to_string()); + let frame_theme = + Component::Internal(self.theme.color.to_string()); let body_clear = Component::Internal(self.make(Into::::into( vec![body_theme.clone(), Component::Padding(w_len)], ))); - let border = - Component::Repeated(FRAME_CHAR_VERTICAL, self.border); + let border_left = Component::Repeated( + self.theme.frame_chars.left, + self.theme.width, + ); + let border_right = Component::Repeated( + self.theme.frame_chars.right, + self.theme.width, + ); // This seems cursed but, I assure you, it's fine vec![vec![frame_theme.clone()]] .into_iter() // Top horizontal borders - .chain( - (0..self.border) - .into_iter() - .map(|y: u16| self.write_frame_line(y, w_len).0), - ) + .chain((0..self.theme.width).into_iter().map(|y: u16| { + self.write_frame_line( + y, + w_len, + self.theme.frame_chars.top, + ) + .0 + })) // Frame body + vertical frame borders .chain( - (self.border..h_len - self.border).into_iter().map( - |y: u16| { + (self.theme.width..h_len - self.theme.width) + .into_iter() + .map(|y: u16| { let left = self.goto_internal(0, y); - let right = self - .goto_internal(w_len - self.border, y); + let right = self.goto_internal( + w_len - self.theme.width, + y, + ); vec![ left.clone(), body_clear.clone(), frame_theme.clone(), left, - border.clone(), + border_left.clone(), right, - border.clone(), + border_right.clone(), ] - }, - ), + }), ) // Bottom horizontal border - .chain( - (h_len - self.border..h_len) - .into_iter() - .map(|y: u16| self.write_frame_line(y, w_len).0), - ) + .chain((h_len - self.theme.width..h_len).into_iter().map( + |y: u16| { + self.write_frame_line( + y, + w_len, + self.theme.frame_chars.bottom, + ) + .0 + }, + )) // Theme reset + move to beginning of inside frame .chain( vec![vec![Component::Goto(0, 0), body_theme]] diff --git a/src/display/mod.rs b/src/display/mod.rs index 4ff880f..6765cda 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -89,25 +89,22 @@ pub struct Environment { impl Into for Environment { fn into(self) -> String { - self.frame.frame_str(self.theme.colors.primary) + self.frame.frame_str(self.theme.windows.primary) } } impl Environment { fn initial_must(theme: Theme) -> Self { - let w: u16 = std::env::var("FRAME_WIDTH") - .map(|f| f.parse().unwrap_or(1)) - .unwrap_or(1); Self { theme, - frame: Frame::from_terminal_size(theme.colors.frame, w) + frame: Frame::from_terminal_size(theme.frames.outer) .expect("could not get terminal size"), } } #[inline(always)] pub fn primary_frame(&self) -> String { - self.frame.frame_str(self.theme.colors.primary) + self.frame.frame_str(self.theme.windows.primary) } } @@ -159,7 +156,9 @@ impl Screen { write!( scr, "{color}{clear}{show_cursor}{move_to_start}", - color = Into::::into("#000000").bg_string(), + color = TryInto::::try_into("#000000") + .unwrap() + .bg_string(), move_to_start = cursor::Goto(1, 1), clear = clear::All, show_cursor = cursor::Show, diff --git a/src/display/theme.rs b/src/display/theme.rs index 74cd831..e598485 100644 --- a/src/display/theme.rs +++ b/src/display/theme.rs @@ -1,23 +1,46 @@ +use serde::{de::Visitor, Deserialize, Serialize}; use std::ops::Deref; use termion::color; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Theme { - pub colors: Colors, + pub windows: Colors, + pub frames: Frames, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct ColorSet { pub fg: Color, pub bg: Color, } +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct Frames { + pub outer: Frame, + pub inner: Frame, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct FrameChars { + pub top: char, + pub bottom: char, + pub left: char, + pub right: char, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct Frame { + pub width: u16, + pub frame_chars: FrameChars, + pub color: ColorSet, +} + impl Default for ColorSet { fn default() -> Self { Self { - fg: "#ffffff".into(), - bg: "#000000".into(), + fg: "#ffffff".try_into().unwrap(), + bg: "#000000".try_into().unwrap(), } } } @@ -29,18 +52,71 @@ impl ToString for ColorSet { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Colors { pub primary: ColorSet, - pub frame: ColorSet, - pub subframe: ColorSet, pub subwin: ColorSet, pub highlight: ColorSet, } -impl Theme {} #[derive(Clone, Copy, Debug)] pub struct Color(color::Rgb); + +impl Serialize for Color { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let rgb = self.0; + serializer.serialize_str(&format!( + "#{:02X}{:02X}{:02X}", + rgb.0, rgb.1, rgb.2, + )) + } +} + +impl<'de> Deserialize<'de> for Color { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct ColorCodeVisitor; + impl<'de> Visitor<'de> for ColorCodeVisitor { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("hex color code") + } + type Value = Color; + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match TryInto::::try_into(v) { + Ok(c) => Ok(c), + Err(e) => Err(E::custom(e.to_string())), + } + } + + fn visit_string( + self, + v: String, + ) -> Result + where + E: serde::de::Error, + { + match TryInto::::try_into(v) { + Ok(c) => Ok(c), + Err(e) => Err(E::custom(e.to_string())), + } + } + } + deserializer.deserialize_any(ColorCodeVisitor {}) + } +} + impl Deref for Color { type Target = color::Rgb; @@ -56,8 +132,10 @@ impl TryFrom for Color { } } -impl From<&str> for Color { - fn from(value: &str) -> Self { +impl TryFrom<&str> for Color { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result { if value.len() < 6 || value.len() > 7 { panic!("hex code length invalid: {}", value.len()); } @@ -65,40 +143,60 @@ impl From<&str> for Color { if value.starts_with('#') { i = 1; } - Self(color::Rgb( + Ok(Self(color::Rgb( u8::from_str_radix(&value[i..i + 2], 16) - .expect("red hex"), + .map_err(|_| anyhow::anyhow!("red hex"))?, u8::from_str_radix(&value[i + 2..i + 4], 16) - .expect("green hex"), + .map_err(|_| anyhow::anyhow!("green hex"))?, u8::from_str_radix(&value[i + 4..i + 6], 16) - .expect("blue hex"), - )) + .map_err(|_| anyhow::anyhow!("blue hex"))?, + ))) } } impl Default for Theme { fn default() -> Self { Self { - colors: Colors { + windows: Colors { primary: ColorSet { - fg: "#330033".into(), - bg: "#a006d3".into(), - }, - frame: ColorSet { - fg: "#ffe6ff".into(), - bg: "#9005c2".into(), - }, - subframe: ColorSet { - fg: "#ffe6ff".into(), - bg: "#7003a0".into(), + fg: "#330033".try_into().unwrap(), + bg: "#a006d3".try_into().unwrap(), }, highlight: ColorSet { - fg: "#ffffff".into(), - bg: "#0000ff".into(), + fg: "#ffffff".try_into().unwrap(), + bg: "#0000ff".try_into().unwrap(), }, subwin: ColorSet { - fg: "#ffffff".into(), - bg: "#500170".into(), + fg: "#ffffff".try_into().unwrap(), + bg: "#500170".try_into().unwrap(), + }, + }, + frames: Frames { + outer: Frame { + color: ColorSet { + fg: "#ff36ff".try_into().unwrap(), + bg: "#9005c2".try_into().unwrap(), + }, + frame_chars: FrameChars { + top: ' ', + bottom: ' ', + left: ' ', + right: ' ', + }, + width: 1, + }, + inner: Frame { + color: ColorSet { + fg: "#ffe6ff".try_into().unwrap(), + bg: "#7003a0".try_into().unwrap(), + }, + frame_chars: FrameChars { + top: ' ', + bottom: ' ', + left: ' ', + right: ' ', + }, + width: 1, }, }, } @@ -108,11 +206,11 @@ impl Default for Theme { impl Theme { #[inline(always)] pub fn primary(&self) -> String { - self.colors.primary.to_string() + self.windows.primary.to_string() } #[inline(always)] pub fn frame(&self) -> String { - self.colors.frame.to_string() + self.frames.outer.color.to_string() } } diff --git a/src/main.rs b/src/main.rs index 86cf4ab..da0d279 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,14 @@ use display::theme::Theme; use crate::display::Screen; extern crate termion; +mod cfg; mod display; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { + let config = cfg::Config::load_or_create()?; { - let theme = Theme::default(); - let x = Screen::new(theme).unwrap(); + let x = Screen::new(config.theme).unwrap(); x.start().await?; } process::exit(0);