1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
/* Copyright (c) 2018 - Mathieu Bridon <bochecha@daitauha.fr> * * This file is part of Plume * * Plume is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Plume 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Plume. If not, see <http://www.gnu.org/licenses/>. */ //! Plume enables your command-line tools to ask users to write text in their //! favourite editor. //! //! This works similarly to how Git spawns your `${EDITOR}` to let you write a //! commit message. //! //! Plume will first check the `${EDITOR}` environment variable. If it is set, //! then the value is used as the text editor. //! //! If `${EDITOR}` is not set, then Plume will search for a well-known text //! editor. If it finds one installed, then it will use it. //! //! Plume then spawns the text editor, letting the user type their text. When //! they save and close the editor, Plume will retrieve the entered text and //! return it. //! //! Currently, the list of well-known text editors are, in this order: //! //! * `/usr/bin/nano` //! * `/usr/bin/vim` //! * `/usr/bin/vi` //! //! This should work on most UNIX-like operating systems. #[macro_use] extern crate failure; extern crate tempfile; use std::env; use std::io::prelude::*; use std::io::SeekFrom; use std::path::Path; use std::process::Command; use failure::Error; use tempfile::NamedTempFile; static KNOWN_EDITORS: &[&str; 3] = &["/usr/bin/nano", "/usr/bin/vim", "/usr/bin/vi"]; fn get_editor() -> Result<String, Error> { env::var("EDITOR").or_else(|_| { for editor in KNOWN_EDITORS { if Path::new(editor).exists() { return Ok(editor.to_owned().to_string()); } } bail!("Could not find a suitable text editor; Set the EDITOR environment variable") }) } /// Get some text from the user /// /// This function will: /// /// 1. find the text editor to use /// * if the ${EDITOR} environment variable is set, then its value is used; /// * otherwise, this will search for known text editors like nano or vim; /// 2. launch that text editor and capture the text entered by the user; /// 3. return that text. /// /// # Examples /// /// ```rust,no_run /// # extern crate failure; /// # use failure::Error; /// # /// # extern crate plume; /// # /// # fn try_main() -> Result<(), Error> { /// let text = plume::get_text()?; /// println!("Got text:\n{}\n----------", text); /// # /// # Ok(()) /// # } /// # /// # fn main() { /// # try_main().unwrap(); /// # } /// ``` /// /// # Errors /// /// This function will return `failure::Error` instances in a few cases: /// /// * a temporary file could not be created, seeked, read or closed; (see the /// `tempfile::NamedTempFile` documentation) /// * the temporary file path is not valid UTF-8; (see the /// `std::path::Path.to_str()` documentation) /// * no text editor could be found, either because the `${EDITOR}` /// environment variable was not set, or because no known text editor was /// installed; /// * the command spawn to launch the text editor failed or exited with a /// non-zero return code; pub fn get_text() -> Result<String, Error> { let mut tmp_file = NamedTempFile::new()?; let tmp_path = match tmp_file.path().to_str() { Some(ref s) => s.to_owned().to_string(), None => bail!("Invalid temporary file path: {:?}", tmp_file), }; let editor = get_editor()?; let status = Command::new(&editor).arg(&tmp_path).spawn()?.wait()?; if !status.success() { bail!( "Could not launch editor: {} returned {:?}", editor, status.code() ) } tmp_file.seek(SeekFrom::Start(0))?; let mut text = String::new(); tmp_file.read_to_string(&mut text)?; tmp_file.close()?; Ok(text) } #[cfg(test)] mod tests { use super::*; #[test] fn editor_from_environment() { env::set_var("EDITOR", "/plume/test"); let editor = get_editor().unwrap(); assert_eq!(editor, "/plume/test"); } }