diff options
-rw-r--r-- | Cargo.lock | 12 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | src/bin/ganarchy-cli.rs | 1 | ||||
-rw-r--r-- | src/data.rs | 93 | ||||
-rw-r--r-- | src/data/effective.rs | 173 | ||||
-rw-r--r-- | src/data/kinds.rs | 96 | ||||
-rw-r--r-- | src/data/managers.rs | 98 | ||||
-rw-r--r-- | src/lib.rs | 3 |
8 files changed, 476 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock index d570f62..18724b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "450575f58f7bee32816abbff470cbc47797397c2a81e0eaced4b98436daf52e1" [[package]] +name = "arcstr" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207a708ac06a886d11fbdcafc8b63a52fb0b4e6f930fdc41030f628248a1458e" + +[[package]] name = "ascii" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -138,10 +144,12 @@ dependencies = [ name = "ganarchy" version = "0.1.0" dependencies = [ + "arcstr", "assert_fs", "impl_trait", "predicates", "testserver", + "url", ] [[package]] @@ -210,9 +218,9 @@ dependencies = [ [[package]] name = "impl_trait" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b644c423a283d855eefd08f9bcc97f8d2c4a4b8ed90d08a82b9b733cf6abd1c" +checksum = "acc999e59a1564ac651376310e6557bbdbfa42576893862b2d882225d73cb49d" dependencies = [ "quote", "syn", diff --git a/Cargo.toml b/Cargo.toml index 9dd31ea..94bf88c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -impl_trait = "0.1.3" +impl_trait = "0.1.6" +url = "2.2.2" +arcstr = "1.1.1" [dev-dependencies] assert_fs = "1.0.1" diff --git a/src/bin/ganarchy-cli.rs b/src/bin/ganarchy-cli.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/src/bin/ganarchy-cli.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..3910dda --- /dev/null +++ b/src/data.rs @@ -0,0 +1,93 @@ +// This file is part of GAnarchy - decentralized development hub +// Copyright (C) 2021 Soni L. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! This module handles data source retrieval, parsing and processing. +//! +//! Data sources identify where to find repos and projects, among other things. + +use std::error; +use std::hash::Hash; +use std::time::Duration; + +pub mod effective; +pub mod kinds; +pub mod managers; + +/// A source of data. +pub trait DataSourceBase { + /// Refreshes the data associated with this source, if necessary, and + /// returns the interval at which this should be called again. + /// + /// # Errors + /// + /// Returns an error if the source could not be updated. If an error is + /// returned, an attempt should be made to **not** invalidate this data + /// source, but instead carry on using stale data. Note that an aggregate + /// data source may partially update and return an aggregate error type. + fn update(&mut self) -> ( + Duration, + Result<(), Box<dyn error::Error + Send + Sync + 'static>>, + ); + + /// Returns whether this data source contains any (valid) data. + /// + /// # Panics + /// + /// This is allowed (but not required) to panic if called before `update`, + /// but should not panic if `update` has been called, but failed. + fn exists(&self) -> bool; +} + +/// A source of data of some specified kind. +pub trait DataSource<T: Kind>: DataSourceBase { + /// Returns the values associated with this kind. + /// + /// # Panics + /// + /// This is allowed (but not required) to panic if called before `update`, + /// but should not panic if `update` has been called, but failed. + fn get_values(&self) -> T::Values; + + /// Returns the value associated with this kind. + /// + /// This is the same as [`get_values`] but allows using the singular name, + /// for kinds with up to only one value. + /// + /// # Panics + /// + /// This is allowed (but not required) to panic if called before `update`, + /// but should not panic if `update` has been called, but failed. + fn get_value(&self) -> Option<T> where T: Kind<Values=Option<T>> { + self.get_values() + } +} + +/// A kind of value that can be retrieved from a data source. +pub trait Kind { + /// Values of this kind. + type Values: IntoIterator<Item=Self>; +} + +/// A kind of value that can be retrieved from a data source, which can +/// override values of the same kind. +pub trait OverridableKind: Kind { + /// The key, as in a map key, that gets overridden. + type Key: Hash + Eq + Ord; + + /// Returns the key for use in a key-value map such as a `BTreeMap` or + /// a set such as `BTreeSet`. + fn as_key(&self) -> &Self::Key; +} diff --git a/src/data/effective.rs b/src/data/effective.rs new file mode 100644 index 0000000..12167dc --- /dev/null +++ b/src/data/effective.rs @@ -0,0 +1,173 @@ +// This file is part of GAnarchy - decentralized development hub +// Copyright (C) 2021 Soni L. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! A wrapper for [`DataSource`] that automatically handles +//! [`OverridableKind`]. + +use std::collections::BTreeSet; +use std::collections::HashSet; +use std::error; +use std::time::Duration; + +use impl_trait::impl_trait; + +use super::DataSourceBase; +use super::DataSource; +use super::Kind; +use super::OverridableKind; + +/// A wrapper for [`DataSource`] that automatically handles +/// [`OverridableKind`]. +pub struct EffectiveDataSource<T: DataSourceBase>(T); + +/// A wrapper for [`OverridableKind`] that acts like the key for Eq/Hash/Ord. +#[repr(transparent)] +#[derive(Debug)] +pub struct EffectiveKind<T: OverridableKind>(pub T); + +impl<T: Kind<Values=BTreeSet<T>> + OverridableKind> Kind for EffectiveKind<T> { + type Values = BTreeSet<Self>; +} + +impl_trait! { + impl<T: Kind + OverridableKind> EffectiveKind<T> where Self: Kind { + impl trait OverridableKind { + type Key = T::Key; + + fn as_key(&self) -> &Self::Key { + self.0.as_key() + } + } + impl trait PartialEq { + fn eq(&self, other: &Self) -> bool { + self.as_key().eq(other.as_key()) + } + fn ne(&self, other: &Self) -> bool { + self.as_key().ne(other.as_key()) + } + } + impl trait Eq { + } + impl trait PartialOrd { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } + } + impl trait Ord { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_key().cmp(other.as_key()) + } + } + impl trait std::hash::Hash { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.as_key().hash(state); + } + } + } +} + +/// Hack to provide appropriate impls for [`EffectiveDataSource`]. +pub trait EffectiveDataSourceHelper<K: Kind<Values=V>, V> { + /// Returns the values associated with this kind. + fn get_values_impl(&self) -> K::Values; +} + +impl_trait! { + impl<T: DataSourceBase> EffectiveDataSource<T> { + /// Wraps the given [`DataSource`]. + pub fn new(source: T) -> Self { + Self(source) + } + + /// Unwraps and returns the inner [`DataSource`]. + pub fn into_inner(self) -> T { + self.0 + } + + /// Forwards to the inner [`DataSourceBase`]. + impl trait DataSourceBase { + fn update(&mut self) -> ( + Duration, + Result<(), Box<dyn error::Error + Send + Sync + 'static>>, + ) { + self.0.update() + } + + fn exists(&self) -> bool { + self.0.exists() + } + } + + /// Filters the inner [`DataSource`] using the appropriate impl. + impl trait<K: Kind> DataSource<K> + where + Self: EffectiveDataSourceHelper<K, K::Values> + { + fn get_values(&self) -> K::Values { + self.get_values_impl() + } + } + + /// Filters the inner [`DataSource`] such that only the first instance + /// of a given key is returned. + impl trait<K> EffectiveDataSourceHelper<K, Vec<K>> + where + K: Kind<Values=Vec<K>> + OverridableKind, + T: DataSource<K> + { + fn get_values_impl(&self) -> Vec<K> { + let mut values: Vec<K> = self.0.get_values(); + let mut seen = HashSet::<&K::Key>::new(); + let mut keep = Vec::with_capacity(values.len()); + for value in &values { + keep.push(seen.insert(value.as_key())); + } + drop(seen); + let mut keep = keep.into_iter(); + values.retain(|_| keep.next().unwrap()); + values + } + } + + /// Forwards to the inner [`DataSource`] as there's no filtering we + /// can/need to do here. + impl trait<K> EffectiveDataSourceHelper< + EffectiveKind<K>, + BTreeSet<EffectiveKind<K>>, + > + where + K: Kind<Values=BTreeSet<K>> + OverridableKind, + T: DataSource<EffectiveKind<K>>, + EffectiveKind<K>: Kind<Values=BTreeSet<EffectiveKind<K>>>, + EffectiveKind<K>: OverridableKind, + { + fn get_values_impl(&self) -> BTreeSet<EffectiveKind<K>> { + self.0.get_values() + } + } + + /// Forwards to the inner [`DataSource`]. + impl trait<K> EffectiveDataSourceHelper<K, Option<K>> + where + K: Kind<Values=Option<K>>, + T: DataSource<K> + { + fn get_values_impl(&self) -> Option<K> { + self.0.get_values() + } + } + } +} diff --git a/src/data/kinds.rs b/src/data/kinds.rs new file mode 100644 index 0000000..d492a92 --- /dev/null +++ b/src/data/kinds.rs @@ -0,0 +1,96 @@ +// This file is part of GAnarchy - decentralized development hub +// Copyright (C) 2021 Soni L. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Kinds of data for use with `DataSource`. + +use arcstr::ArcStr; + +use url::Url; + +use super::Kind; +use super::OverridableKind; + +/// Title of an instance. +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct InstanceTitle(pub String); + +impl Kind for InstanceTitle { + /// A source may only have one `InstanceTitle`. + type Values = Option<Self>; +} + +/// URL of an instance. +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct InstanceBaseUrl(pub Url); + +impl Kind for InstanceBaseUrl { + /// A source may only have one `InstanceBaseUrl`. + type Values = Option<Self>; +} + +/// URL of a repo list. +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct RepoListUrl { + // `Url` is actually fairly expensive, but we don't usually have a lot of + // `RepoListUrl` around. Additionally these are generally unique. + /// The actual URL that references a repo list. + pub url: Url, + /// Whether this entry is active. + pub active: bool, + /// Whether this repo list is allowed to have negative entries. + pub allow_negative_entries: bool, +} + +impl Kind for RepoListUrl { + /// A source may have many `RepoListUrl`. + type Values = Vec<Self>; +} + +impl OverridableKind for RepoListUrl { + type Key = Url; + + fn as_key(&self) -> &Self::Key { + &self.url + } +} + +/// The key for a [`ProjectFork`]. +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ProjectForkKey { + project: ArcStr, + url: ArcStr, + branch: ArcStr, +} + +/// A fork of a project. +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ProjectFork { + pub key: ProjectForkKey, + pub active: bool, +} + +impl Kind for ProjectFork { + /// A source may have many `ProjectFork`. + type Values = Vec<Self>; +} + +impl OverridableKind for ProjectFork { + type Key = ProjectForkKey; + + fn as_key(&self) -> &Self::Key { + &self.key + } +} diff --git a/src/data/managers.rs b/src/data/managers.rs new file mode 100644 index 0000000..71a9484 --- /dev/null +++ b/src/data/managers.rs @@ -0,0 +1,98 @@ +// This file is part of GAnarchy - decentralized development hub +// Copyright (C) 2021 Soni L. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use std::error; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::RwLock; +use std::time::Duration; + +use impl_trait::impl_trait; + +use super::DataSourceBase; +use super::DataSource; +use super::effective::EffectiveKind; +//use super::Kind; +use super::kinds::{InstanceTitle, InstanceBaseUrl, RepoListUrl, ProjectFork}; +//use super::OverridableKind; + +/// Stores multiple DataSource capable of ProjectFork +#[derive(Default)] +pub struct RepoListManager { + repos: Vec<Box<dyn DataSource<EffectiveKind<ProjectFork>> + Send + Sync>>, + durations: Vec<Duration>, + valid: usize, +} + +/// Stores multiple DataSource capable of InstanceTitle, InstanceBaseUrl and +/// RepoListUrl +#[derive(Default)] +pub struct ConfigManager { + // conceptually the actual objects + bases: Vec<Arc<RwLock<dyn DataSourceBase + Send + Sync>>>, + // conceptually just views of the above objects + titles: Vec<Option<Arc<RwLock<dyn DataSource<InstanceTitle> + Send + Sync>>>>, + urls: Vec<Option<Arc<RwLock<dyn DataSource<InstanceBaseUrl> + Send + Sync>>>>, + repolists: Vec<Option<Arc<RwLock<dyn DataSource<RepoListUrl> + Send + Sync>>>>, + durations: Vec<Duration>, + // add_source can be called after update. + valid: usize, +} + +impl_trait! { + impl ConfigManager { + /// Creates a new `ConfigManager`. + pub fn new() -> Self { + Default::default() + } + + /// Adds the given combined `DataSource` to this `ConfigManager`. + pub fn add_source<T>(&mut self, source: T) + where + T: DataSource<InstanceTitle>, + T: DataSource<InstanceBaseUrl>, + T: DataSource<RepoListUrl>, + T: Send + Sync + 'static, + { + let arc = Arc::new(RwLock::new(source)); + self.bases.push(arc.clone()); + self.titles.push(Some(arc.clone())); + self.urls.push(Some(arc.clone())); + self.repolists.push(Some(arc)); + } + + impl trait DataSourceBase { + /// Updates the contained `DataSource`s, and returns the shortest + /// duration for the next update. + /// + /// # Errors + /// + /// Returns an error if any `DataSource` returns an error. Always + /// updates all `DataSource`s. + fn update(&mut self) -> ( + Duration, + Result<(), Box<dyn error::Error + Send + Sync + 'static>>, + ) { + todo!() + } + + /// Returns whether this data source contains any (valid) data. + fn exists(&self) -> bool { + todo!() + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 5291931..abc396f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -mod git; +pub mod git; +pub mod data; mod util; //mod system; mod marker; |