skytool 0.1.0-pre.2

an experimental API client for BlueSky / ATProto
Documentation
use camino::Utf8PathBuf;
use std::{io::Read, str::FromStr};

mod command;
mod environment;
mod file;
mod inline;
mod shell;

pub(crate) use command::CommandInputSource;
pub(crate) use environment::EnvironmentInputSource;
pub(crate) use file::FileInputSource;
pub(crate) use inline::InlineInputSource;
pub(crate) use shell::ShellInputSource;

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[error(transparent)]
#[remain::sorted]
pub(crate) enum Error {
  Command(#[from] command::Error),
  Environment(#[from] environment::Error),
  File(#[from] file::Error),
  Inline(#[from] inline::Error),
  Json(#[from] serde_json::Error),
  Shell(#[from] shell::Error),
  Stdin(#[from] std::io::Error),
  Yaml(#[from] serde_yml::Error),
}

#[derive(Debug, Clone)]
#[remain::sorted]
pub(crate) enum InputSource {
  Command(CommandInputSource),
  Environment(EnvironmentInputSource),
  File(FileInputSource),
  Inline(InlineInputSource),
  Shell(ShellInputSource),
  Stdin,
}

impl InputSource {
  pub(crate) fn cache(self) -> Result<Self, Error> {
    let value = self.try_into()?;
    Ok(InlineInputSource { value }.into())
  }

  pub(crate) fn is_stdin(&self) -> bool {
    matches!(self, Self::Stdin)
  }
}

impl FromStr for InputSource {
  type Err = Error;
  fn from_str(s: &str) -> Result<Self, Self::Err> {
    if s == "-" {
      return Ok(Self::Stdin);
    }

    let source = match s.strip_prefix('$') {
      Some(s) => EnvironmentInputSource::from_str(s)?.into(),
      None => match s.strip_prefix('@') {
        Some(s) => FileInputSource::from_str(s)?.into(),
        None => match s.strip_prefix('|') {
          Some(s) => CommandInputSource::from_str(s)?.into(),
          None => match s.strip_prefix('!') {
            Some(s) => ShellInputSource::from_str(s)?.into(),
            None => InlineInputSource::from_str(s)?.into(),
          },
        },
      },
    };

    Ok(source)
  }
}

impl TryFrom<InputSource> for String {
  type Error = Error;
  fn try_from(source: InputSource) -> Result<Self, Self::Error> {
    let mut value = match source {
      InputSource::Command(input) => input.try_into()?,
      InputSource::Environment(input) => input.try_into()?,
      InputSource::File(input) => input.try_into()?,
      InputSource::Inline(input) => input.try_into()?,
      InputSource::Shell(input) => input.try_into()?,
      InputSource::Stdin => {
        let mut buf = String::new();
        std::io::stdin().read_to_string(&mut buf)?;
        buf
      }
    };

    Ok(value)
  }
}

impl Default for InputSource {
  fn default() -> Self {
    InlineInputSource::default().into()
  }
}

impl From<CommandInputSource> for InputSource {
  fn from(input: CommandInputSource) -> Self {
    Self::Command(input)
  }
}

impl From<EnvironmentInputSource> for InputSource {
  fn from(input: EnvironmentInputSource) -> Self {
    Self::Environment(input)
  }
}

impl From<FileInputSource> for InputSource {
  fn from(input: FileInputSource) -> Self {
    Self::File(input)
  }
}

impl From<InlineInputSource> for InputSource {
  fn from(input: InlineInputSource) -> Self {
    Self::Inline(input)
  }
}

impl From<ShellInputSource> for InputSource {
  fn from(input: ShellInputSource) -> Self {
    Self::Shell(input)
  }
}