From d31a502badfbc5e797d6abad2281e3676e6a3b05 Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Wed, 25 Mar 2020 03:34:58 +0400 Subject: [PATCH] Initial commit. Signed-off-by: Pavel Kirilin --- .gitignore | 4 + Cargo.lock | 406 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 18 ++ PKGBUILD | 25 +++ src/cli.rs | 48 +++++ src/config.rs | 103 +++++++++++ src/initialization.rs | 17 ++ src/main.rs | 30 ++++ src/result.rs | 29 +++ src/run_modes.rs | 61 +++++++ 10 files changed, 741 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 PKGBUILD create mode 100644 src/cli.rs create mode 100644 src/config.rs create mode 100644 src/initialization.rs create mode 100644 src/main.rs create mode 100644 src/result.rs create mode 100644 src/run_modes.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7494822 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +.idea +pkg +*.pkg.tar.xz diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d3069c7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,406 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +dependencies = [ + "memchr", +] + +[[package]] +name = "alphanumeric-sort" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22b991f3d9c054bb99ed18f24e906b89ec3bc450e788d4911b2b9e123ab3109b" + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "awatch" +version = "0.1.0" +dependencies = [ + "alphanumeric-sort", + "failure", + "failure_derive", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "structopt", +] + +[[package]] +name = "backtrace" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" +dependencies = [ + "backtrace-sys", + "cfg-if", + "libc", + "rustc-demangle", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cc" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "failure" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "proc-macro-error" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "syn-mid", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8900ebc1363efa7ea1c399ccc32daed870b4002651e0bed86e72d501ebbe0048" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "ryu" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" + +[[package]] +name = "serde" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" + +[[package]] +name = "serde_derive" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8faa2719539bbe9d77869bfb15d4ee769f99525e707931452c97b693b3f159d" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88b8e18c69496aad6f9ddf4630dd7d585bcaf765786cb415b9aec2fe5a0430" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f7a9c3d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "awatch" +version = "0.1.0" +authors = ["Pavel Kirilin "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +structopt = "0.3" # Used to build CLI. +serde = "1.0" # A generic serialization/deserialization framework. +serde_derive = "1.0.105" # Used to configure json config stucture. +serde_json = "1.0" # Used to store config. +failure = "0.1.7" # Experimental error handling abstraction. +failure_derive = "0.1.7" # Used to create new error type. +lazy_static = "1.4" # Define lazy static vars. +alphanumeric-sort = "1.0.12" # Used to search for videos. +regex = "1" # Regular expressions. \ No newline at end of file diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..7cd73e0 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,25 @@ +# Maintainer: s3rius +pkgname=awatch +pkgver=0.1.0 +pkgrel=2 +pkgdesc="Anime watcher." +arch=('i686' 'x86_64' 'arm' 'armv7h' 'armv6h' 'aarch64') +url="https://gitlab.le-memese.com/s3rius/awatch" +license=('GPL') +options=(!emptydirs) +depends=() +makedepends=( + 'rust' +) + + +build() { + cd "$startdir/" + pwd + cargo build --release +} + +package() { + mkdir -p "$pkgdir/usr/bin" + cp "$startdir/target/release/awatch" "$pkgdir/usr/bin" +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..a97dfe7 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,48 @@ +#[derive(Debug, StructOpt)] +#[structopt( +name = "awatch", +about = "Here to help you with watching anime on your PC." +)] +pub struct Opt { + #[structopt(subcommand)] + pub mode: Option +} + +#[derive(StructOpt, Debug)] +pub enum RunMode { + #[structopt(name = "init", about = "Initialize config")] + Init, + #[structopt(name = "play", about = "Play next episode")] + Play, + #[structopt(name = "prev", about = "Decrease episode counter by one")] + Prev, + #[structopt(name = "next", about = "Increase episode counter by one")] + Next, + #[structopt(name = "update", about = "Update saved config")] + Update(UpdateOptions), +} + +#[derive(StructOpt, Debug)] +pub struct UpdateOptions { + #[structopt( + short, + long, + name = "episode", + about = "Update saved last episode" + )] + pub last_episode: Option, + #[structopt( + short, + long, + name = "command", + about = "Update saved command to show the next episode" + )] + pub command: Option, + #[structopt( + short, + long, + name = "pattern", + about = "Update pattern for filenames" + )] + pub pattern: Option, +} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..1632596 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,103 @@ +use crate::result::{AppResult, AppError}; +use crate::{CONFIG_PATH, UpdateOptions}; +use std::io::{Write, Read}; + +#[derive(Serialize, Default, Deserialize)] +pub struct Config { + current_episode_count: usize, + pattern: String, + #[serde(default = "default_command")] + pub command: String, +} + +pub fn default_command() -> String { + String::from("mpv --fullscreen {}") +} + +impl Config { + pub fn new(pattern: String, maybe_command: String) -> AppResult { + let mut command = maybe_command; + if pattern.is_empty() { + return Err(AppError::RuntimeError( + String::from("Pattern can't be empty") + )); + } + if command.is_empty() { + command = default_command(); + } + Ok(Self { + current_episode_count: 0, + pattern, + command, + }) + } + + pub fn save(&self) -> AppResult<()> { + let json = serde_json::to_string_pretty(self)?; + let mut file = std::fs::File::create(CONFIG_PATH.as_str())?; + file.write_all(json.as_bytes())?; + Ok(()) + } + + pub fn read() -> AppResult { + let config_path = std::path::Path::new(CONFIG_PATH.as_str()); + if !config_path.exists() { + return Err( + AppError::StdErr(String::from("Run 'awatch init' first.")) + ); + } + let mut file = std::fs::File::open(CONFIG_PATH.as_str())?; + let mut buffer = String::new(); + file.read_to_string(&mut buffer)?; + let config: Self = serde_json::from_str(buffer.as_str())?; + Ok(config) + } + + pub fn get_current_episode(&self) -> AppResult { + let current_dir = std::env::current_dir()?; + let mut names = Vec::new(); + + let episode_regex = regex::Regex::new(self.pattern.as_str())?; + for entry in std::fs::read_dir(current_dir)? { + let entry = entry?; + let path = entry.path(); + let meta = std::fs::metadata(&path)?; + if meta.is_dir() { + continue; + } + let name = entry.file_name(); + let name_str = name.into_string(); + if let Ok(name) = name_str { + if episode_regex.is_match(name.as_str()) { + names.push(name); + } + } + } + alphanumeric_sort::sort_str_slice(names.as_mut_slice()); + if let Some(episode) = names.get(self.current_episode_count) { + Ok(episode.clone()) + } else { + Err(AppError::RuntimeError(String::from("This is the end."))) + } + } +} + +pub fn update_episode(episode_func: fn(usize) -> AppResult) -> AppResult<()> { + let mut conf = Config::read()?; + conf.current_episode_count = episode_func(conf.current_episode_count)?; + conf.save() +} + +pub fn update_config(options: UpdateOptions) -> AppResult<()> { + let mut conf = Config::read()?; + if let Some(pattern) = options.pattern { + conf.pattern = pattern; + } + if let Some(command) = options.command { + conf.command = command; + } + if let Some(episode) = options.last_episode { + conf.current_episode_count = episode; + } + conf.save() +} \ No newline at end of file diff --git a/src/initialization.rs b/src/initialization.rs new file mode 100644 index 0000000..9ea8feb --- /dev/null +++ b/src/initialization.rs @@ -0,0 +1,17 @@ +use crate::result::AppResult; +use crate::config::Config; + +pub fn init_config() -> AppResult<()> { + let stdin = std::io::stdin(); + println!("How can we recognize anime files? Enter filename regex."); + let mut pattern = String::new(); + stdin.read_line(&mut pattern)?; + pattern = pattern.trim().to_string(); + println!("How can we show you the next episode? If 'mpv --fullscreen {{}}' is correct, leave it blank."); + let mut command = String::new(); + stdin.read_line(&mut command)?; + command = command.trim().to_string(); + let config = Config::new(pattern, command)?; + config.save()?; + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3d16a7c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,30 @@ +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate failure; +#[macro_use] +extern crate failure_derive; +#[macro_use] +extern crate lazy_static; + +lazy_static! { + pub static ref CONFIG_PATH: String = String::from(".awatch.json"); +} + +use structopt::StructOpt; + +use crate::run_modes::run; +use crate::result::AppResult; + +pub mod result; +pub mod config; +pub mod run_modes; +pub mod initialization; + +include!("cli.rs"); + +fn main() -> AppResult<()> { + let opt: Opt = Opt::from_args(); + run(opt)?; + Ok(()) +} diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..a3d9495 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,29 @@ +pub type AppResult = Result; + +#[derive(Debug, Fail)] +pub enum AppError { + #[fail(display = "Execution failed: {}", _0)] + StdErr(String), + #[fail(display = "Config processing failed: {}", _0)] + ParseError(String), + #[fail(display = "Error was found: {}", _0)] + RuntimeError(String), +} + +impl From for AppError { + fn from(err: std::io::Error) -> Self { + Self::StdErr(err.to_string()) + } +} + +impl From for AppError { + fn from(err: serde_json::error::Error) -> Self { + Self::StdErr(err.to_string()) + } +} + +impl From for AppError { + fn from(err: regex::Error) -> Self { + Self::RuntimeError(err.to_string()) + } +} \ No newline at end of file diff --git a/src/run_modes.rs b/src/run_modes.rs new file mode 100644 index 0000000..22f71c8 --- /dev/null +++ b/src/run_modes.rs @@ -0,0 +1,61 @@ +use crate::{Opt, RunMode}; +use crate::result::{AppResult, AppError}; +use crate::initialization::init_config; +use crate::config::{update_episode, update_config, Config}; +use std::process::Command; + +pub fn run(opts: Opt) -> AppResult<()> { + let mode = opts.mode.unwrap_or_else(|| RunMode::Play); + match mode { + RunMode::Init => { + init_config() + } + RunMode::Play => { + play() + } + RunMode::Prev => { + update_episode(prev_episode) + } + RunMode::Next => { + update_episode(next_episode) + } + RunMode::Update(options) => { + update_config(options) + } + } +} + +pub fn prev_episode(current: usize) -> AppResult { + if let Some(episode) = current.checked_sub(1) { + Ok(episode) + } else { + Err(AppError::RuntimeError( + String::from("Episode can't be less than zero.") + )) + } +} + +pub fn next_episode(current: usize) -> AppResult { + if let Some(episode) = current.checked_add(1) { + Ok(episode) + } else { + Err(AppError::RuntimeError( + String::from("Reached usize limit. Sorry.") + )) + } +} + +pub fn play() -> AppResult<()> { + let conf = Config::read()?; + let mut episode = conf.get_current_episode()?; + while !episode.is_empty() { + let mut child = Command::new("sh") + .arg("-c") + .arg(conf.command.replace("{}", episode.as_str())) + .spawn()?; + child.wait()?; + update_episode(next_episode)?; + episode = conf.get_current_episode()?; + } + Ok(()) +} \ No newline at end of file