diff --git a/meli/src/args.rs b/meli/src/args.rs index 96c817e3..c151d234 100644 --- a/meli/src/args.rs +++ b/meli/src/args.rs @@ -22,6 +22,8 @@ //! Command line arguments. use super::*; +#[cfg(feature = "cli-docs")] +use crate::manpages; #[derive(Debug, StructOpt)] #[structopt(name = "meli", about = "terminal mail client", version_short = "v")] @@ -99,131 +101,3 @@ pub struct ManOpt { )] pub no_raw: Option>, } - -#[cfg(feature = "cli-docs")] -pub mod manpages { - use std::{ - env, fs, - path::{Path, PathBuf}, - sync::Arc, - }; - - use melib::log; - - use crate::{Error, Result}; - - pub const POSSIBLE_VALUES: &[&str] = &[ - "meli", - "meli.1", - "conf", - "meli.conf", - "meli.conf.5", - "themes", - "meli-themes", - "meli-themes.5", - "guide", - "meli.7", - ]; - - pub fn parse_manpage(src: &str) -> Result { - match src { - "" | "meli" | "meli.1" | "main" => Ok(ManPages::Main), - "meli.7" | "guide" => Ok(ManPages::Guide), - "meli.conf" | "meli.conf.5" | "conf" | "config" | "configuration" => Ok(ManPages::Conf), - "meli-themes" | "meli-themes.5" | "themes" | "theming" | "theme" => { - Ok(ManPages::Themes) - } - _ => Err(Error::new(format!("Invalid documentation page: {src}",))), - } - } - - #[derive(Clone, Copy, Debug)] - /// Choose manpage - pub enum ManPages { - /// meli(1) - Main = 0, - /// meli.conf(5) - Conf = 1, - /// meli-themes(5) - Themes = 2, - /// meli(7) - Guide = 3, - } - - impl std::fmt::Display for ManPages { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - fmt, - "{}", - match self { - Self::Main => "meli.1", - Self::Conf => "meli.conf.5", - Self::Themes => "meli-themes.5", - Self::Guide => "meli.7", - } - ) - } - } - - impl ManPages { - pub fn install(destination: Option) -> Result { - fn path_valid(p: &Path, tries: &mut Vec) -> bool { - tries.push(p.into()); - p.exists() - && p.is_dir() - && fs::metadata(p) - .ok() - .map(|m| !m.permissions().readonly()) - .unwrap_or(false) - } - - let mut tries = vec![]; - let Some(mut path) = destination - .filter(|p| path_valid(p, &mut tries)) - .or_else(|| { - if let Some(paths) = env::var_os("MANPATH") { - if let Some(path) = - env::split_paths(&paths).find(|p| path_valid(p, &mut tries)) - { - return Some(path); - } - } - None - }) - .or_else(|| { - #[allow(deprecated)] - env::home_dir() - .map(|p| p.join(".local").join("share").join("man")) - .filter(|p| path_valid(p, &mut tries)) - }) - else { - return Err(format!("Could not write to any of these paths: {:?}", tries).into()); - }; - - for (p, dir) in [ - (Self::Main, "man1"), - (Self::Conf, "man5"), - (Self::Themes, "man5"), - (Self::Guide, "man7"), - ] { - let text = crate::subcommands::man(p, true)?; - path.push(dir); - std::fs::create_dir_all(&path).map_err(|err| { - Error::new(format!("Could not create {} directory.", path.display())) - .set_source(Some(Arc::new(err))) - })?; - path.push(&p.to_string()); - - fs::write(&path, text.as_bytes()).map_err(|err| { - Error::new(format!("Could not write to {}", path.display())) - .set_source(Some(Arc::new(err))) - })?; - log::trace!("Installed {} to {}", p, path.display()); - path.pop(); - path.pop(); - } - - Ok(path) - } - } -} diff --git a/meli/src/lib.rs b/meli/src/lib.rs index ecad2f69..d10e66a2 100644 --- a/meli/src/lib.rs +++ b/meli/src/lib.rs @@ -105,6 +105,8 @@ pub use melib::{ }; pub mod args; +#[cfg(feature = "cli-docs")] +pub mod manpages; pub mod subcommands; #[macro_use] diff --git a/meli/src/main.rs b/meli/src/main.rs index f5962ec4..4cf925fe 100644 --- a/meli/src/main.rs +++ b/meli/src/main.rs @@ -151,7 +151,7 @@ fn run_app(opt: Opt) -> Result<()> { } #[cfg(feature = "cli-docs")] Some(SubCommand::InstallMan { destination_path }) => { - match args::manpages::ManPages::install(destination_path) { + match crate::manpages::ManPages::install(destination_path) { Ok(p) => println!("Installed at {}.", p.display()), Err(err) => return Err(err), } diff --git a/meli/src/manpages.rs b/meli/src/manpages.rs new file mode 100644 index 00000000..c1b5aee6 --- /dev/null +++ b/meli/src/manpages.rs @@ -0,0 +1,175 @@ +// +// meli +// +// Copyright 2024 Emmanouil Pitsidianakis +// +// This file is part of meli. +// +// meli is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// meli is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with meli. If not, see . +// +// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later + +use std::{ + env, fs, + io::Read, + path::{Path, PathBuf}, + sync::Arc, +}; + +use flate2::bufread::GzDecoder; +use melib::log; + +use crate::{Error, Result}; + +pub const POSSIBLE_VALUES: &[&str] = &[ + "meli", + "meli.1", + "conf", + "meli.conf", + "meli.conf.5", + "themes", + "meli-themes", + "meli-themes.5", + "guide", + "meli.7", +]; + +pub fn parse_manpage(src: &str) -> Result { + match src { + "" | "meli" | "meli.1" | "main" => Ok(ManPages::Main), + "meli.7" | "guide" => Ok(ManPages::Guide), + "meli.conf" | "meli.conf.5" | "conf" | "config" | "configuration" => Ok(ManPages::Conf), + "meli-themes" | "meli-themes.5" | "themes" | "theming" | "theme" => Ok(ManPages::Themes), + _ => Err(Error::new(format!("Invalid documentation page: {src}",))), + } +} + +#[derive(Clone, Copy, Debug)] +/// Choose manpage +pub enum ManPages { + /// meli(1) + Main = 0, + /// meli.conf(5) + Conf = 1, + /// meli-themes(5) + Themes = 2, + /// meli(7) + Guide = 3, +} + +impl std::fmt::Display for ManPages { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + fmt, + "{}", + match self { + Self::Main => "meli.1", + Self::Conf => "meli.conf.5", + Self::Themes => "meli-themes.5", + Self::Guide => "meli.7", + } + ) + } +} + +impl ManPages { + pub fn install(destination: Option) -> Result { + fn path_valid(p: &Path, tries: &mut Vec) -> bool { + tries.push(p.into()); + p.exists() + && p.is_dir() + && fs::metadata(p) + .ok() + .map(|m| !m.permissions().readonly()) + .unwrap_or(false) + } + + let mut tries = vec![]; + let Some(mut path) = destination + .filter(|p| path_valid(p, &mut tries)) + .or_else(|| { + if let Some(paths) = env::var_os("MANPATH") { + if let Some(path) = env::split_paths(&paths).find(|p| path_valid(p, &mut tries)) + { + return Some(path); + } + } + None + }) + .or_else(|| { + #[allow(deprecated)] + env::home_dir() + .map(|p| p.join(".local").join("share").join("man")) + .filter(|p| path_valid(p, &mut tries)) + }) + else { + return Err(format!("Could not write to any of these paths: {:?}", tries).into()); + }; + + for (p, dir) in [ + (Self::Main, "man1"), + (Self::Conf, "man5"), + (Self::Themes, "man5"), + (Self::Guide, "man7"), + ] { + let text = crate::subcommands::man(p, true)?; + path.push(dir); + std::fs::create_dir_all(&path).map_err(|err| { + Error::new(format!("Could not create {} directory.", path.display())) + .set_source(Some(Arc::new(err))) + })?; + path.push(&p.to_string()); + + fs::write(&path, text.as_bytes()).map_err(|err| { + Error::new(format!("Could not write to {}", path.display())) + .set_source(Some(Arc::new(err))) + })?; + log::trace!("Installed {} to {}", p, path.display()); + path.pop(); + path.pop(); + } + + Ok(path) + } + + pub fn read(self, source: bool) -> Result { + const MANPAGES: [&[u8]; 4] = [ + include_bytes!(concat!(env!("OUT_DIR"), "/meli.txt.gz")), + include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.txt.gz")), + include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.txt.gz")), + include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.txt.gz")), + ]; + const MANPAGES_MDOC: [&[u8]; 4] = [ + include_bytes!(concat!(env!("OUT_DIR"), "/meli.mdoc.gz")), + include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.mdoc.gz")), + include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.mdoc.gz")), + include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.mdoc.gz")), + ]; + + let mut gz = GzDecoder::new(if source { + MANPAGES_MDOC[self as usize] + } else { + MANPAGES[self as usize] + }); + let mut v = String::with_capacity( + str::parse::(unsafe { + std::str::from_utf8_unchecked(gz.header().unwrap().comment().unwrap()) + }) + .unwrap_or_else(|_| panic!("{:?} was not compressed with size comment header", self)), + ); + gz.read_to_string(&mut v)?; + + Ok(v) + } +} diff --git a/meli/src/subcommands.rs b/meli/src/subcommands.rs index 295b8596..70f39fc2 100644 --- a/meli/src/subcommands.rs +++ b/meli/src/subcommands.rs @@ -27,11 +27,9 @@ use std::{ }; use crossbeam::channel::{Receiver, Sender}; -#[cfg(feature = "cli-docs")] -use flate2::bufread::GzDecoder; use melib::Result; -use crate::{args::*, *}; +use crate::*; pub fn create_config(path: Option) -> Result<()> { let config_path = if let Some(path) = path { @@ -74,33 +72,7 @@ pub fn edit_config() -> Result<()> { #[cfg(feature = "cli-docs")] pub fn man(page: manpages::ManPages, source: bool) -> Result { - const MANPAGES: [&[u8]; 4] = [ - include_bytes!(concat!(env!("OUT_DIR"), "/meli.txt.gz")), - include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.txt.gz")), - include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.txt.gz")), - include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.txt.gz")), - ]; - const MANPAGES_MDOC: [&[u8]; 4] = [ - include_bytes!(concat!(env!("OUT_DIR"), "/meli.mdoc.gz")), - include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.mdoc.gz")), - include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.mdoc.gz")), - include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.mdoc.gz")), - ]; - - let mut gz = GzDecoder::new(if source { - MANPAGES_MDOC[page as usize] - } else { - MANPAGES[page as usize] - }); - let mut v = String::with_capacity( - str::parse::(unsafe { - std::str::from_utf8_unchecked(gz.header().unwrap().comment().unwrap()) - }) - .unwrap_or_else(|_| panic!("{:?} was not compressed with size comment header", page)), - ); - gz.read_to_string(&mut v)?; - - Ok(v) + page.read(source) } #[cfg(feature = "cli-docs")] @@ -133,7 +105,7 @@ pub fn pager(v: String, no_raw: Option>) -> Result<()> { } #[cfg(not(feature = "cli-docs"))] -pub fn man(_: ManOpt) -> Result<()> { +pub fn man(_: crate::args::ManOpt) -> Result<()> { Err(Error::new("error: this version of meli was not build with embedded documentation (cargo feature `cli-docs`). You might have it installed as manpages (eg `man meli`), otherwise check https://meli-email.org")) }