~savoy/mailcap

060c85b3c80c4b4072ca1563a2d22d56326e9bb6 — savoy 2 years ago 4028701
implements the mailcap entry `test` command

Must be called through an unsafe libc system call. As mailcaps are a
UNIX thing, no need to think of Windows/Mac compatibility.

All previous "command" fields have been converted to Option<String>
instead of Option<Vec<String>> as in order to leverage the flexibility
of mailcap vs XDG, being able to run complex shell commands is required.

Signed-off-by: savoy <git@liberation.red>
2 files changed, 37 insertions(+), 35 deletions(-)

M Cargo.toml
M src/lib.rs
M Cargo.toml => Cargo.toml +1 -0
@@ 7,3 7,4 @@ edition = "2021"

[dependencies]
serial_test = "0.6.0"
libc = "0.2.0"

M src/lib.rs => src/lib.rs +36 -35
@@ 14,7 14,9 @@
// You should have received a copy of the GNU General Public License
// along with mailcap.  If not, see <http://www.gnu.org/licenses/>.

use libc::system;
use std::collections::HashMap;
use std::ffi::CString;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;


@@ 36,12 38,12 @@ pub struct Mailcap {
#[derive(Default, Debug, PartialEq)]
pub struct Entry {
    mime_type: String,
    command: Vec<String>,
    compose: Option<Vec<String>>,
    compose_typed: Option<Vec<String>>,
    edit: Option<Vec<String>>,
    print: Option<Vec<String>>,
    test: Option<Vec<String>>,
    command: String,
    compose: Option<String>,
    compose_typed: Option<String>,
    edit: Option<String>,
    print: Option<String>,
    test: Option<String>,
    note: Option<String>,
    description: Option<String>,
    name_template: Option<String>,


@@ 171,19 173,15 @@ impl Entry {
        let mut entry = Entry::default();
        // TODO: validate mime_type against database
        entry.mime_type = line[0].to_owned();
        entry.command = Self::split_string_into_vector(&line[1]);
        entry.command = line[1].to_owned();

        for field in line[2..].iter() {
            match Self::parse_arg(field) {
                Some(("compose", v)) => {
                    entry.compose = Some(Self::split_string_into_vector(&v[1..]))
                }
                Some(("composetyped", v)) => {
                    entry.compose_typed = Some(Self::split_string_into_vector(&v[1..]))
                }
                Some(("edit", v)) => entry.edit = Some(Self::split_string_into_vector(&v[1..])),
                Some(("print", v)) => entry.print = Some(Self::split_string_into_vector(&v[1..])),
                Some(("test", v)) => entry.test = Some(Self::split_string_into_vector(&v[1..])),
                Some(("compose", v)) => entry.compose = Some(v[1..].to_string()),
                Some(("composetyped", v)) => entry.compose_typed = Some(v[1..].to_string()),
                Some(("edit", v)) => entry.edit = Some(v[1..].to_string()),
                Some(("print", v)) => entry.print = Some(v[1..].to_string()),
                Some(("test", v)) => entry.test = Some(v[1..].to_string()),
                Some(("note", v)) => entry.note = Some(v[1..].to_string()),
                Some(("description", v)) => entry.description = Some(v[1..].to_string()),
                Some(("nametemplate", v)) => entry.name_template = Some(v[1..].to_string()),


@@ 194,34 192,40 @@ impl Entry {
            }
        }

        Some(entry)
        match entry.test {
            Some(ref c) => match unsafe { Self::test_entry(c) } {
                Ok(()) => Some(entry),
                Err(()) => None,
            },
            None => None,
        }
    }

    pub fn mime(&self) -> &String {
        &self.mime_type
    }

    pub fn command(&self) -> &Vec<String> {
    pub fn command(&self) -> &String {
        &self.command
    }

    pub fn compose(&self) -> &Option<Vec<String>> {
    pub fn compose(&self) -> &Option<String> {
        &self.compose
    }

    pub fn compose_typed(&self) -> &Option<Vec<String>> {
    pub fn compose_typed(&self) -> &Option<String> {
        &self.compose_typed
    }

    pub fn edit(&self) -> &Option<Vec<String>> {
    pub fn edit(&self) -> &Option<String> {
        &self.edit
    }

    pub fn print(&self) -> &Option<Vec<String>> {
    pub fn print(&self) -> &Option<String> {
        &self.print
    }

    pub fn test(&self) -> &Option<Vec<String>> {
    pub fn test(&self) -> &Option<String> {
        &self.test
    }



@@ 256,12 260,13 @@ impl Entry {
        }
    }

    fn split_string_into_vector(string: &str) -> Vec<String> {
        string
            .split(" ")
            .map(|i| i.trim())
            .map(str::to_string)
            .collect()
    unsafe fn test_entry(test_command: &String) -> Result<(), ()> {
        let c_str = CString::new(test_command.as_str()).unwrap();

        match system(c_str.as_ptr()) {
            0 => Ok(()),
            _ => Err(()),
        }
    }
}



@@ 360,16 365,12 @@ mod tests {
        assert_eq!(
            Entry {
                mime_type: "text/html".to_string(),
                command: vec!["qutebrowser".to_string(), "'%s'".to_string()],
                command: "qutebrowser '%s'".to_string(),
                compose: None,
                compose_typed: None,
                edit: None,
                print: None,
                test: Some(vec![
                    "test".to_string(),
                    "-n".to_string(),
                    "\"$DISPLAY\"".to_string()
                ]),
                test: Some("test -n \"$DISPLAY\"".to_string()),
                note: None,
                description: None,
                name_template: Some("%s.html".to_string()),