From 6a8f869e5b9b80d7150f1aeb00a262601da9e143 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 23 Oct 2019 10:45:13 +0300 Subject: [PATCH] Show manuals with command line arguments Add --manual, --conf-manual command line arguments that display manpages through a pager. If no pager is found, this currently fails. It should print the manuals to stdout instead. The manuals are read from src/manuals and are generated with mandoc whenever changes to the manpage sources meli.1 and meli.conf.5 are made. --- .gitignore | 1 + Cargo.toml | 1 + build.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ meli.1 | 6 ++++++ src/bin.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 build.rs diff --git a/.gitignore b/.gitignore index 8f5ed0573..3bd6f70da 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ target/ **/*.rs.bk .gdb_history *.log +src/manuals diff --git a/Cargo.toml b/Cargo.toml index 612060c66..9418e520a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "meli" version = "0.3.2" authors = ["Manos Pitsidianakis "] edition = "2018" +build = "build.rs" [[bin]] name = "meli" diff --git a/build.rs b/build.rs new file mode 100644 index 000000000..95e161794 --- /dev/null +++ b/build.rs @@ -0,0 +1,52 @@ +/* + * meli - bin.rs + * + * Copyright 2019 Manos 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 . + */ +use std::fs::File; +use std::io::prelude::*; +use std::io::BufWriter; +use std::path::PathBuf; +use std::process::Command; + +fn main() -> Result<(), std::io::Error> { + if let Err(e) = std::fs::create_dir("src/manuals") { + if e.kind() != std::io::ErrorKind::AlreadyExists { + Err(e)?; + } + } + let meli_1_metadata = std::fs::metadata("meli.1")?; + if let Ok(metadata) = std::fs::metadata("src/manuals/meli.txt") { + if metadata.modified()? < meli_1_metadata.modified()? { + let output = Command::new("mandoc").args(&["meli.1"]).output()?; + let man_path = PathBuf::from("src/manuals/meli.txt"); + let file = File::create(&man_path)?; + BufWriter::new(file).write_all(&output.stdout)?; + } + } + let meli_conf_5_metadata = std::fs::metadata("meli.conf.5")?; + if let Ok(metadata) = std::fs::metadata("src/manuals/meli_conf.txt") { + if metadata.modified()? < meli_conf_5_metadata.modified()? { + let output = Command::new("mandoc").args(&["meli.conf.5"]).output()?; + let man_path = PathBuf::from("src/manuals/meli_conf.txt"); + let file = File::create(&man_path)?; + BufWriter::new(file).write_all(&output.stdout)?; + } + } + Ok(()) +} diff --git a/meli.1 b/meli.1 index 12bed547e..6c50c3364 100644 --- a/meli.1 +++ b/meli.1 @@ -43,6 +43,10 @@ if given, or at .Pa $XDG_CONFIG_HOME/meli/config .It Fl -config Ar path Start meli with given configuration file. +.It Fl -manual +Show (this) manual for meli. +.It Fl -conf-manual +Show manual for meli configuration file. .El .Sh STARTING WITH meli When launched for the first time, meli will search for its configuration directory, @@ -280,6 +284,8 @@ catchall for general errors Specifies the editor to use .It Ev MELI_CONFIG Override the configuration file +.It Ev PAGER +Pager to use for command line help output. .El .Sh FILES meli uses the following parts of the XDG standard: diff --git a/src/bin.rs b/src/bin.rs index 8131d6e8c..73f554b73 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -81,6 +81,8 @@ struct CommandLineArguments { config: Option, help: bool, version: bool, + manual: bool, + conf_manual: bool, } fn main() -> std::result::Result<(), std::io::Error> { @@ -95,6 +97,8 @@ fn main() -> std::result::Result<(), std::io::Error> { config: None, help: false, version: false, + manual: false, + conf_manual: false, }; for i in std::env::args().skip(1) { @@ -119,6 +123,12 @@ fn main() -> std::result::Result<(), std::io::Error> { "--version" | "-v" => { args.version = true; } + "--manual" => { + args.manual = true; + } + "--conf-manual" => { + args.conf_manual = true; + } e => match prev { None => error_and_exit!("error: value without command {}", e), Some(CreateConfig) if args.create_config.is_none() => { @@ -144,6 +154,8 @@ fn main() -> std::result::Result<(), std::io::Error> { println!("\t--version, -v\t\tprint version and exit"); println!("\t--create-config[ PATH]\tCreate a sample configuration file with available configuration options. If PATH is not specified, meli will try to create it in $XDG_CONFIG_HOME/meli/config"); println!("\t--config PATH, -c PATH\tUse specified configuration file"); + println!("\t--manual\t\tshow manual for meli"); + println!("\t--conf-manual\t\tshow manual for meli configuration file"); std::process::exit(0); } @@ -152,6 +164,36 @@ fn main() -> std::result::Result<(), std::io::Error> { std::process::exit(0); } + if args.manual { + let man_contents = include_str!("manuals/meli.txt"); + let pager = option_env!("PAGER").unwrap_or("less"); + let (read_end, write_end) = nix::unistd::pipe() + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; + use std::os::unix::io::FromRawFd; + unsafe { std::fs::File::from_raw_fd(write_end) }.write_all(man_contents.as_bytes())?; + let mut handle = std::process::Command::new(pager) + .stdin(unsafe { std::process::Stdio::from_raw_fd(read_end) }) + .stdout(std::process::Stdio::inherit()) + .spawn()?; + handle.wait()?; + std::process::exit(0); + } + + if args.conf_manual { + let man_contents = include_str!("manuals/meli_conf.txt"); + let pager = option_env!("PAGER").unwrap_or("less"); + let (read_end, write_end) = nix::unistd::pipe() + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; + use std::os::unix::io::FromRawFd; + unsafe { std::fs::File::from_raw_fd(write_end) }.write_all(man_contents.as_bytes())?; + let mut handle = std::process::Command::new(pager) + .stdin(unsafe { std::process::Stdio::from_raw_fd(read_end) }) + .stdout(std::process::Stdio::inherit()) + .spawn()?; + handle.wait()?; + std::process::exit(0); + } + match prev { None => {} Some(CreateConfig) if args.create_config.is_none() => args.create_config = Some("".into()),