summaryrefslogtreecommitdiff
path: root/src/parser.rs
blob: 55a2be9601c682feafe1f03ee5017dc825159a66 (plain)
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
use clap::Parser;
use std::{
    path::{Path, PathBuf},
    str::FromStr,
};
use thiserror::Error;

#[derive(Parser, Clone, Debug, PartialEq, Eq)]
#[command(name = "")]
pub enum ShackleCommand {
    #[command(skip)]
    Whitespace,
    Exit,
    GitInit(GitInitArgs),
    GitUploadPack(GitUploadPackArgs),
    GitReceivePack(GitReceivePackArgs),
}

#[derive(Parser, Clone, Debug, PartialEq, Eq)]
pub struct GitInitArgs {
    pub repo_name: String,
}

#[derive(Parser, Clone, Debug, PartialEq, Eq)]
pub struct GitUploadPackArgs {
    #[arg(long)]
    pub strict: bool,
    #[arg(long)]
    pub no_strict: bool,
    #[arg(long)]
    pub timeout: Option<u32>,
    #[arg(long)]
    pub stateless_rpc: bool,
    #[arg(long)]
    pub advertise_refs: bool,
    #[arg(value_parser = RelativePathParser)]
    pub directory: PathBuf,
}

#[derive(Parser, Clone, Debug, PartialEq, Eq)]
pub struct GitReceivePackArgs {
    #[arg(long)]
    pub http_backend_info_refs: bool,
    #[arg(value_parser = RelativePathParser)]
    pub directory: PathBuf,
}

#[derive(Error, Debug)]
pub enum ParserError {
    #[error(transparent)]
    ClapError(#[from] clap::error::Error),
    #[error("`{0}`")]
    LexerError(String),
}

impl FromStr for ShackleCommand {
    type Err = ParserError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let trimmed = s.trim();
        if trimmed.len() == 0 {
            Ok(ShackleCommand::Whitespace)
        } else {
            let lexed = shlex::split(trimmed);
            match lexed {
                None => Err(ParserError::LexerError("Incomplete input".to_string())),
                Some(lexed) => {
                    let parsed =
                        ShackleCommand::try_parse_from(["".to_owned()].into_iter().chain(lexed))?;
                    Ok(parsed)
                }
            }
        }
    }
}

#[derive(Clone)]
struct RelativePathParser;

impl clap::builder::TypedValueParser for RelativePathParser {
    type Value = std::path::PathBuf;

    fn parse_ref(
        &self,
        cmd: &clap::Command,
        arg: Option<&clap::Arg>,
        value: &std::ffi::OsStr,
    ) -> Result<Self::Value, clap::Error> {
        clap::builder::TypedValueParser::parse(self, cmd, arg, value.to_owned())
    }

    fn parse(
        &self,
        cmd: &clap::Command,
        arg: Option<&clap::Arg>,
        value: std::ffi::OsString,
    ) -> Result<Self::Value, clap::Error> {
        let raw = clap::builder::PathBufValueParser::default().parse(cmd, arg, value)?;
        Ok(raw
            .strip_prefix(Path::new("/~"))
            .or_else(|_| raw.strip_prefix(Path::new("~")))
            .map(|m| m.to_owned())
            .unwrap_or(raw))
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn it_parses_exit_correctly() {
        assert_eq!(
            "exit".parse::<ShackleCommand>().unwrap(),
            ShackleCommand::Exit
        );
    }

    #[test]
    fn it_parses_git_upload_pack_correctly() {
        assert_eq!(
            "git-upload-pack --stateless-rpc foobar.git"
                .parse::<ShackleCommand>()
                .unwrap(),
            ShackleCommand::GitUploadPack(GitUploadPackArgs {
                strict: false,
                no_strict: false,
                timeout: None,
                stateless_rpc: true,
                advertise_refs: false,
                directory: PathBuf::from("foobar.git"),
            })
        );
    }

    #[test]
    fn it_parses_whitespace_correctly() {
        assert_eq!(
            "   ".parse::<ShackleCommand>().unwrap(),
            ShackleCommand::Whitespace
        );
    }
}