main.rs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. //
  2. // imag - the personal information management suite for the commandline
  3. // Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
  4. //
  5. // This library is free software; you can redistribute it and/or
  6. // modify it under the terms of the GNU Lesser General Public
  7. // License as published by the Free Software Foundation; version
  8. // 2.1 of the License.
  9. //
  10. // This library is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. // Lesser General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Lesser General Public
  16. // License along with this library; if not, write to the Free Software
  17. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. //
  19. extern crate crossbeam;
  20. extern crate clap;
  21. #[macro_use] extern crate version;
  22. #[macro_use] extern crate log;
  23. extern crate walkdir;
  24. extern crate libimagrt;
  25. extern crate libimagerror;
  26. use std::env;
  27. use std::process::exit;
  28. use std::process::Command;
  29. use std::process::Stdio;
  30. use std::io::ErrorKind;
  31. use walkdir::WalkDir;
  32. use crossbeam::*;
  33. use clap::{Arg, AppSettings, SubCommand};
  34. use libimagrt::runtime::Runtime;
  35. use libimagerror::trace::trace_error;
  36. /// Returns the helptext, putting the Strings in cmds as possible
  37. /// subcommands into it
  38. fn help_text(cmds: Vec<String>) -> String {
  39. let text = format!(r#"
  40. _
  41. (_)_ __ ___ __ _ __ _
  42. | | '_ \` _ \/ _\`|/ _\`|
  43. | | | | | | | (_| | (_| |
  44. |_|_| |_| |_|\__,_|\__, |
  45. |___/
  46. -------------------------
  47. Usage: imag [--version | --versions | -h | --help] <command> <args...>
  48. imag - the personal information management suite for the commandline
  49. imag is a PIM suite for the commandline. It consists of several commands,
  50. called "modules". Each module implements one PIM aspect and all of these
  51. modules can be used independently.
  52. Available commands:
  53. {imagbins}
  54. Call a command with 'imag <command> <args>'
  55. Each command can be called with "--help" to get the respective helptext.
  56. Please visit https://github.com/matthiasbeyer/imag to view the source code,
  57. follow the development of imag or maybe even contribute to imag.
  58. imag is free software. It is released under the terms of LGPLv2.1
  59. (c) 2016 Matthias Beyer and contributors"#, imagbins = cmds.into_iter()
  60. .map(|cmd| format!("\t{}\n", cmd))
  61. .fold(String::new(), |s, c| {
  62. let s = s + c.as_str();
  63. s
  64. }));
  65. text
  66. }
  67. /// Returns the list of imag-* executables found in $PATH
  68. fn get_commands() -> Vec<String> {
  69. let path = env::var("PATH");
  70. if path.is_err() {
  71. println!("PATH error: {:?}", path);
  72. exit(1);
  73. }
  74. let pathelements = path.unwrap();
  75. let pathelements = pathelements.split(":");
  76. let joinhandles : Vec<ScopedJoinHandle<Vec<String>>> = pathelements
  77. .map(|elem| {
  78. crossbeam::scope(|scope| {
  79. scope.spawn(|| {
  80. WalkDir::new(elem)
  81. .max_depth(1)
  82. .into_iter()
  83. .filter(|path| {
  84. match path {
  85. &Ok(ref p) => p.file_name()
  86. .to_str()
  87. .map_or(false, |filename| filename.starts_with("imag-")),
  88. &Err(_) => false,
  89. }
  90. })
  91. .filter_map(|x| x.ok())
  92. .filter_map(|path| {
  93. path.file_name()
  94. .to_str()
  95. .and_then(|s| s.splitn(2, "-").nth(1).map(String::from))
  96. })
  97. .collect()
  98. })
  99. })
  100. })
  101. .collect();
  102. let mut execs = vec![];
  103. for joinhandle in joinhandles.into_iter() {
  104. let mut v = joinhandle.join();
  105. execs.append(&mut v);
  106. }
  107. execs
  108. }
  109. fn main() {
  110. // Initialize the Runtime and build the CLI
  111. let appname = "imag";
  112. let version = &version!();
  113. let about = "imag - the PIM suite for the commandline";
  114. let commands = get_commands();
  115. let helptext = help_text(commands.clone());
  116. let app = Runtime::get_default_cli_builder(appname, version, about)
  117. .settings(&[AppSettings::AllowExternalSubcommands, AppSettings::ArgRequiredElseHelp])
  118. .arg(Arg::with_name("version")
  119. .long("version")
  120. .takes_value(false)
  121. .required(false)
  122. .multiple(false)
  123. .help("Get the version of imag"))
  124. .arg(Arg::with_name("versions")
  125. .long("versions")
  126. .takes_value(false)
  127. .required(false)
  128. .multiple(false)
  129. .help("Get the versions of the imag commands"))
  130. .subcommand(SubCommand::with_name("help").help("Show help"))
  131. .help(helptext.as_str());
  132. let rt = Runtime::new(app)
  133. .unwrap_or_else(|e| {
  134. println!("Runtime couldn't be setup. Exiting");
  135. trace_error(&e);
  136. exit(1);
  137. });
  138. let matches = rt.cli();
  139. debug!("matches: {:?}", matches);
  140. // Begin checking for arguments
  141. if matches.is_present("version") {
  142. debug!("Showing version");
  143. println!("imag {}", &version!()[..]);
  144. exit(0);
  145. }
  146. if matches.is_present("versions") {
  147. debug!("Showing versions");
  148. let mut result = vec![];
  149. for command in commands.iter() {
  150. result.push(crossbeam::scope(|scope| {
  151. scope.spawn(|| {
  152. let v = Command::new(format!("imag-{}",command)).arg("--version").output();
  153. match v {
  154. Ok(v) => match String::from_utf8(v.stdout) {
  155. Ok(s) => format!("{:10} -> {}", command, s),
  156. Err(e) => format!("Failed calling {} -> {:?}", command, e),
  157. },
  158. Err(e) => format!("Failed calling {} -> {:?}", command, e),
  159. }
  160. })
  161. }))
  162. }
  163. for versionstring in result.into_iter().map(|handle| handle.join()) {
  164. // The amount of newlines may differ depending on the subprocess
  165. println!("{}", versionstring.trim());
  166. }
  167. }
  168. // Matches any subcommand given
  169. match matches.subcommand() {
  170. (subcommand, Some(scmd)) => {
  171. // Get all given arguments and further subcommands to pass to
  172. // the imag-<> binary
  173. // Providing no arguments is OK, and is therefore ignored here
  174. let subcommand_args : Vec<&str> = match scmd.values_of("") {
  175. Some(values) => values.collect(),
  176. None => Vec::new()
  177. };
  178. // Typos happen, so check if the given subcommand is one found in $PATH
  179. if !commands.contains(&String::from(subcommand)) {
  180. println!("No such command: 'imag-{}'", subcommand);
  181. println!("See 'imag --help' for available subcommands");
  182. exit(2);
  183. }
  184. debug!("Calling 'imag-{}' with args: {:?}", subcommand, subcommand_args);
  185. // Create a Command, and pass it the gathered arguments
  186. match Command::new(format!("imag-{}", subcommand))
  187. .stdin(Stdio::inherit())
  188. .stdout(Stdio::inherit())
  189. .stderr(Stdio::inherit())
  190. .args(&subcommand_args[..])
  191. .spawn()
  192. .and_then(|mut handle| handle.wait())
  193. {
  194. Ok(exit_status) => {
  195. if !exit_status.success() {
  196. debug!("{} exited with non-zero exit code: {:?}", subcommand, exit_status);
  197. println!("{} exited with non-zero exit code", subcommand);
  198. exit(exit_status.code().unwrap_or(1));
  199. }
  200. debug!("Successful exit!");
  201. },
  202. Err(e) => {
  203. debug!("Error calling the subcommand");
  204. match e.kind() {
  205. ErrorKind::NotFound => {
  206. // With the check above, this absolutely should not happen.
  207. // Keeping it to be safe
  208. println!("No such command: 'imag-{}'", subcommand);
  209. println!("See 'imag --help' for available subcommands");
  210. exit(2);
  211. },
  212. ErrorKind::PermissionDenied => {
  213. println!("No permission to execute: 'imag-{}'", subcommand);
  214. exit(1);
  215. },
  216. _ => {
  217. println!("Error spawning: {:?}", e);
  218. exit(1);
  219. }
  220. }
  221. }
  222. }
  223. },
  224. // Calling for example 'imag --versions' will lead here, as this option does not exit.
  225. // There's nothing to do in such a case
  226. _ => {},
  227. }
  228. }