thin-fetch 0.1.0

Low-level Git crate to fetch objects with high granularity
Documentation
//! Git protocol commands.
//!
//! Currently implemented:
//!
//! - `ls-refs`: [`LsRefs`]
//! - `fetch`: [`Fetch`]

use crate::{
    Error, HashKind, Repo,
    parcel::{Parcel, Section},
};

mod fetch;
mod ls_refs;

pub use fetch::Fetch;
pub use ls_refs::LsRefs;

// what
#[allow(clippy::missing_errors_doc)]
pub trait Execute {
    type Output;

    fn execute(&self, repo: &Repo) -> Result<Self::Output, Error>;
}

/// Wrapper to compose and send custom commands.
///
/// # Example
///
/// ```rust
/// # use thin_fetch::command::Command;
/// # use thin_fetch::HashKind;
/// let cmd = Command::new("ls-refs")
///     .agent("my-cool-program/1.0")
///     .object_format(HashKind::Sha256)
///     .arg("unborn");
/// ```
#[derive(Debug, Clone)]
pub struct Command {
    name: String,
    agent: String,
    object_format: String,
    args: Vec<String>,
}

impl Command {
    pub fn new<S: Into<String>>(cmd: S) -> Self {
        Self {
            name: cmd.into(),
            agent: "git/2.56.0".to_owned(),
            object_format: "sha1".to_string(),
            args: vec![],
        }
    }

    #[must_use]
    pub fn agent<S: Into<String>>(mut self, agent: S) -> Self {
        self.agent = agent.into();
        self
    }

    #[must_use]
    pub fn object_format(mut self, hash_kind: HashKind) -> Self {
        self.object_format = hash_kind.to_string();
        self
    }

    #[must_use]
    pub fn arg<S: Into<String>>(mut self, arg: S) -> Self {
        self.args.push(arg.into());
        self
    }

    #[must_use]
    pub fn args<I: IntoIterator<Item = String>>(mut self, args: I) -> Self {
        self.args.extend(args);
        self
    }
}

impl From<&Command> for Parcel {
    fn from(value: &Command) -> Self {
        let args = value
            .args
            .iter()
            .map(String::as_bytes)
            .map(<[u8]>::to_vec)
            .collect();

        Parcel::new(vec![
            Section::new(vec![
                format!("command={}\n", value.name).into_bytes(),
                format!("agent={}", value.agent).into_bytes(),
                format!("object-format={}", value.object_format).into_bytes(),
            ]),
            Section::new(args),
        ])
    }
}

impl Execute for Command {
    type Output = Parcel;

    fn execute(&self, repo: &Repo) -> Result<Self::Output, Error> {
        repo.call(self)
    }
}