use std::fmt::{self, Debug};
use std::path::{Path, PathBuf};
use either::Either;
use semver::Version;
use toml_edit::{Entry, InlineTable, Item, KeyMut, Table, TableLike, Value, value};
pub struct Dependency {
name: String,
version: Option<Version>,
path: Option<PathBuf>,
}
impl Dependency {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
version: None,
path: None,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn version(&self) -> Option<&Version> {
self.version.as_ref()
}
pub fn set_version(&mut self, version: impl Into<Version>) -> &mut Self {
self.version = Some(version.into());
self
}
pub fn with_version(mut self, version: impl Into<Version>) -> Self {
self.set_version(version);
self
}
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
pub fn set_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.path = Some(path.into());
self
}
pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
self.set_path(path);
self
}
}
#[derive(Clone)]
pub struct DependencyRef<'a> {
name: &'a str,
version: Option<&'a str>,
table: Option<&'a dyn TableLike>,
}
impl<'a> DependencyRef<'a> {
pub fn name(&self) -> &'a str {
self.name
}
pub fn version(&self) -> Option<&'a str> {
match self.version {
Some(version) => Some(version),
None => match self.table {
Some(table) => match table.get("version") {
Some(version) => version.as_str(),
None => None,
},
None => None,
},
}
}
pub fn path(&self) -> Option<&'a str> {
match self.table {
Some(table) => match table.get("path") {
Some(path) => path.as_str(),
None => None,
},
None => None,
}
}
}
impl Debug for DependencyRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DependencyRef")
.field("name", &self.name())
.field("version", &self.version())
.field("path", &self.path())
.finish()
}
}
impl<'a> From<(&'a str, &'a Item)> for DependencyRef<'a> {
fn from((name, item): (&'a str, &'a Item)) -> Self {
Self {
name,
version: item.as_str(),
table: item.as_table_like(),
}
}
}
#[derive(Clone, Default)]
pub struct Dependencies<'a> {
pub(super) table: Option<&'a dyn TableLike>,
}
impl<'a> Dependencies<'a> {
pub fn get(&self, name: impl AsRef<str>) -> Option<DependencyRef<'a>> {
self.clone()
.into_iter()
.find(|dependency| dependency.name() == name.as_ref())
}
}
impl<'a> IntoIterator for Dependencies<'a> {
type Item = DependencyRef<'a>;
type IntoIter = Box<dyn Iterator<Item = DependencyRef<'a>> + 'a>;
fn into_iter(self) -> Self::IntoIter {
match self.table {
Some(table) => Box::new(table.iter().map(Into::into)),
None => Box::new(std::iter::empty()),
}
}
}
impl Debug for Dependencies<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.clone()).finish()
}
}
impl<'a> From<&'a Item> for Dependencies<'a> {
fn from(item: &'a Item) -> Self {
Self {
table: item.as_table_like(),
}
}
}
pub struct DependencyMut<'a> {
name: KeyMut<'a>,
item: &'a mut Item,
}
impl DependencyMut<'_> {
pub fn name(&self) -> &str {
self.name.get()
}
pub fn version(&self) -> Option<Version> {
match self.item.as_str() {
Some(version) => version.parse().ok(),
None => match self.item.as_table()?.get("version") {
Some(version) => version.as_str()?.parse().ok(),
None => None,
},
}
}
pub fn set_version(&mut self, version: impl Into<Version>) {
if let Some(table) = self.item.as_table_like_mut() {
let item = table.entry("version").or_insert_with(Item::default);
*item = Item::Value(Value::from(version.into().to_string()));
} else if let Some(value) = self.item.as_value_mut() {
*value = Value::from(version.into().to_string());
}
}
pub fn path(&self) -> Option<&str> {
match self.item.as_table()?.get("path") {
Some(path) => path.as_str(),
None => None,
}
}
}
impl Debug for DependencyMut<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DependencyMut")
.field("name", &self.name())
.field("version", &self.version())
.field("path", &self.path())
.finish()
}
}
impl<'a> From<(KeyMut<'a>, &'a mut Item)> for DependencyMut<'a> {
fn from((name, item): (KeyMut<'a>, &'a mut Item)) -> Self {
Self { name, item }
}
}
pub struct DependenciesMut<'a> {
table: Either<&'a mut dyn TableLike, Option<Entry<'a>>>,
}
impl<'a> DependenciesMut<'a> {
pub(super) fn new(entry: Entry<'a>) -> Self {
Self {
table: Either::Right(Some(entry)),
}
}
}
impl<'a> DependenciesMut<'a> {
pub fn get_mut(&mut self, name: impl AsRef<str>) -> Option<DependencyMut<'_>> {
self.table(false)?
.iter_mut()
.map(Into::<DependencyMut>::into)
.find(|dependency| dependency.name() == name.as_ref())
}
pub fn into_get_mut(self, name: impl AsRef<str>) -> Option<DependencyMut<'a>> {
self.into_table(false)?
.iter_mut()
.map(Into::<DependencyMut>::into)
.find(|dependency| dependency.name() == name.as_ref())
}
pub fn insert(&mut self, dependency: impl Into<Dependency>) {
let dependency = dependency.into();
let dependencies = self.table(true).expect("table");
let is_table = dependency.path().is_some();
dependencies.remove(dependency.name());
if is_table {
let mut table = InlineTable::new();
if let Some(version) = dependency.version() {
table.insert("version", Value::from(version.to_string()));
}
if let Some(path) = dependency.path() {
table.insert("path", Value::from(path.display().to_string()));
}
dependencies.insert(dependency.name(), Item::Value(Value::InlineTable(table)));
} else if let Some(version) = dependency.version() {
dependencies.insert(dependency.name(), value(version.to_string()));
}
}
}
impl<'a> DependenciesMut<'a> {
fn init_table(&mut self, overwrite: bool) -> Option<()> {
if let Either::Right(option) = &mut self.table {
match option.take().expect("some") {
Entry::Occupied(entry) if entry.get().as_table_like().is_some() => {
self.table = Either::Left(entry.into_mut().as_table_like_mut().expect("table"));
}
Entry::Occupied(mut entry) if overwrite => {
*entry.get_mut() = Item::Table(Table::new());
self.table = Either::Left(entry.into_mut().as_table_like_mut().expect("table"));
}
Entry::Vacant(entry) if overwrite => {
self.table = Either::Left(
entry
.insert(Item::Table(Table::new()))
.as_table_like_mut()
.expect("table"),
);
}
entry => {
option.replace(entry);
return None;
}
}
}
Some(())
}
fn table(&mut self, overwrite: bool) -> Option<&mut dyn TableLike> {
self.init_table(overwrite)?;
match self.table.as_mut().left() {
Some(table) => Some(*table),
None => None,
}
}
fn into_table(mut self, overwrite: bool) -> Option<&'a mut dyn TableLike> {
self.init_table(overwrite)?;
self.table.left()
}
}
impl<'a> IntoIterator for DependenciesMut<'a> {
type Item = DependencyMut<'a>;
type IntoIter = Box<dyn Iterator<Item = DependencyMut<'a>> + 'a>;
fn into_iter(self) -> Self::IntoIter {
match self.into_table(false) {
Some(table) => Box::new(table.iter_mut().map(Into::into)),
None => Box::new(std::iter::empty()),
}
}
}
impl Debug for DependenciesMut<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.table {
Either::Left(table) => f
.debug_list()
.entries(Dependencies {
table: Some(&**table),
})
.finish(),
Either::Right(_) => f.debug_list().finish(),
}
}
}