use anyhow::{bail, Context, Error};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
fmt::Display,
fs::File,
io::{BufReader, BufWriter},
path::Path,
str::FromStr,
};
impl Config {
pub fn read_from_or_default(path: &Path) -> Result<Config, Error> {
if !path.exists() {
return Ok(Config::default());
}
let file = File::open(path).context(format!("while opening {}", path.to_string_lossy()))?;
let reader = BufReader::new(file);
serde_json::from_reader(reader).context(format!(
"while deserializing config: {}",
path.to_string_lossy()
))
}
pub fn write_to(&self, path: &Path) -> Result<(), Error> {
let file =
File::create(path).context(format!("while opening {}", path.to_string_lossy()))?;
let writer = BufWriter::new(file);
Ok(serde_json::to_writer_pretty(writer, self)?)
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Config {
pub sources: BTreeMap<String, Source>,
}
impl Config {
pub fn upsert_source(
&mut self,
explicit_name: Option<String>,
source: Source,
) -> Option<Source> {
let name = explicit_name
.or_else(|| source.name().map(|s| s.to_string()))
.expect("No name given");
self.sources.insert(name, source)
}
pub fn print_sources(&self) {
if self.sources.is_empty() {
eprintln!("No sources configured.\n");
return;
}
let max_len = self
.sources
.iter()
.map(|(n, _)| n.len())
.max()
.unwrap_or_default();
for (name, source) in &self.sources {
println!("{:width$} {}", name, source, width = max_len);
println!();
println!(
"{:width$} crate2nix source add {}",
"",
source.as_command(name),
width = max_len
);
println!();
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum Source {
CratesIo {
name: String,
version: semver::Version,
sha256: String,
},
Git {
#[serde(with = "url_serde")]
url: url::Url,
rev: String,
sha256: String,
},
Nix {
#[serde(flatten)]
file: NixFile,
#[serde(skip_serializing_if = "Option::is_none")]
attr: Option<String>,
},
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq, Hash)]
pub enum NixFile {
#[serde(rename = "import")]
Import(String),
#[serde(rename = "package")]
Package(String),
}
impl Display for NixFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Import(path) => write!(f, "import {}", path),
Self::Package(path) => write!(f, "pkgs.callPackage {} {{}}", path),
}
}
}
impl NixFile {
pub fn as_command(&self) -> String {
match self {
Self::Import(path) => format!("--import '{}'", path),
Self::Package(path) => format!("--package '{}'", path),
}
}
}
impl Source {
pub fn name(&self) -> Option<&str> {
match self {
Source::CratesIo { name, .. } => Some(name),
Source::Git { url, .. } => {
let path = url.path();
let after_last_slash = path.split('/').last().unwrap_or(path);
let without_dot_git = after_last_slash
.strip_suffix(".git")
.unwrap_or(after_last_slash);
Some(without_dot_git)
}
Source::Nix {
attr: Some(attr), ..
} => attr.split('.').last().or(if attr.trim().is_empty() {
None
} else {
Some(attr.trim())
}),
_ => None,
}
}
}
impl Display for Source {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Source::CratesIo {
name,
version,
sha256,
} => write!(f, "{} {} from crates.io: {}", name, version, sha256),
Source::Git { url, rev, sha256 } => write!(f, "{}#{} via git: {}", url, rev, sha256),
Source::Nix { file, attr: None } => write!(f, "{}", file),
Source::Nix {
file,
attr: Some(attr),
} => write!(f, "({}).{}", file, attr),
}
}
}
impl Source {
pub fn as_command(&self, name: &str) -> String {
match self {
Source::CratesIo {
name: crate_name,
version,
..
} => format!("cratesIo --name '{}' '{}' '{}'", name, crate_name, version),
Source::Git { url, rev, .. } => {
format!("git --name '{}' '{}' --rev {}", name, url, rev)
}
Source::Nix { file, attr: None } => {
format!("nix --name '{}' {}", name, file.as_command())
}
Source::Nix {
file,
attr: Some(attr),
} => format!("nix --name '{}' {} '{}'", name, file.as_command(), attr),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum SourceType {
CratesIo,
Git,
}
impl FromStr for SourceType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"cratesIo" => Ok(SourceType::CratesIo),
"git" => Ok(SourceType::Git),
_ => bail!("unkown source type: {}", s),
}
}
}