summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMicha White <botahamec@outlook.com>2025-09-27 08:37:13 -0400
committerMicha White <botahamec@outlook.com>2025-09-27 08:37:13 -0400
commiteb0a3f7a022fa6dce2a43a5427262d54b2001e71 (patch)
tree8111c6e6e25660bcbf7910aa65f915495c806818 /src
Working area
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs132
-rw-r--r--src/workarea.rs74
2 files changed, 206 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..7f6b86f
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,132 @@
+#![warn(clippy::pedantic)]
+#![warn(clippy::nursery)]
+
+use std::collections::HashMap;
+use std::fs::{File, Metadata};
+use std::path::{Path, PathBuf};
+use std::time::Instant;
+
+mod workarea;
+
+struct ContributorId(String);
+struct ChannelId(String);
+struct PatchId(String);
+struct FileId(String);
+struct SpanId(String);
+
+struct Remote {
+ url: String,
+ name: String,
+}
+
+struct Contributor {
+ id: ContributorId,
+ emails: Vec<String>,
+ nickname: String,
+ realname: Option<String>,
+ public_keys: Vec<String>,
+}
+
+struct Channel {
+ id: ChannelId,
+ name: String,
+ remotes: Vec<Remote>,
+ main_remote: Option<Remote>,
+ patches: Vec<PatchId>,
+}
+
+struct Patch {
+ id: PatchId,
+ authors: Vec<ContributorId>,
+ recorder: ContributorId,
+ metadata: HashMap<String, String>,
+ added_spans: Vec<SpanId>,
+ deleted_spans: Vec<SpanId>,
+ added_files: Vec<FileId>,
+ deleted_files: Vec<FileId>,
+}
+
+struct FileInfo {
+ id: FileId,
+ inode: Option<u64>,
+ spans: Vec<SpanId>,
+ added_by: Vec<PatchId>,
+ renamed_by: Vec<(PatchId, PathBuf)>,
+ deleted_by: Vec<PatchId>,
+}
+
+struct Span {
+ id: SpanId,
+ file: FileId,
+ after: Vec<SpanId>,
+ before: Vec<SpanId>,
+ contents: Vec<u8>,
+ added_by: Vec<PatchId>,
+ deleted_by: Vec<PatchId>,
+}
+
+type DiffAlgorithm = fn(File, File) -> Diff;
+
+struct Diff(Vec<DiffSpan>);
+
+struct DiffSpan {
+ left: Vec<u8>,
+ right: Vec<u8>,
+}
+
+struct Log {
+ entries: Vec<LogEntry>,
+}
+
+enum LogEntry {
+ CreatePatch(PatchId),
+}
+
+type RevertAlgorithm = fn(Patch) -> Patch;
+
+trait StagingArea {
+ fn list_files() -> std::io::Result<Metadata>;
+ fn open_file(path: &Path) -> std::io::Result<File>;
+ fn file_metadata(path: &Path) -> std::io::Result<Metadata>;
+ fn is_file_changed(path: &Path, since: Instant) -> std::io::Result<bool>;
+}
+
+trait Repository {
+ fn archive(&self) -> Vec<u8>;
+
+ fn remotes(&self) -> Vec<Remote>;
+ fn main_remote(&self) -> Option<Remote>;
+ fn set_main_remote(&mut self, remote: Option<Remote>);
+ fn add_remote(&mut self, remote: Remote);
+ fn delete_remote(&mut self, remote: Remote);
+ fn push(&self, remote: Remote);
+ fn pull(&mut self, remote: Remote);
+
+ fn all_contributors(&self) -> Vec<ContributorId>;
+ fn contributor(&self, id: ContributorId) -> Contributor;
+ fn credit(&self, filename: &Path, byte: usize) -> Option<ContributorId>;
+
+ fn all_channels(&self) -> Vec<ChannelId>;
+ fn active_channel(&self) -> ChannelId;
+ fn channel(&self, id: ChannelId) -> Channel;
+ fn create_channel(&mut self, channel: Channel);
+ fn change_channel(&mut self, id: ChannelId) -> Channel;
+ fn rename_channel(&mut self, id: ChannelId, name: &str) -> Channel;
+ fn delete_channel(&mut self, id: ChannelId);
+ fn add_patches_to_channel(&mut self, channel: ChannelId, patches: &[PatchId]) -> Channel;
+ fn add_channel_to_channel(&mut self, channel: ChannelId, plus: ChannelId) -> Channel;
+
+ fn all_patches(&self) -> Vec<PatchId>;
+ fn active_patches(&self) -> Vec<PatchId>;
+ fn patch(&self, id: PatchId) -> Option<Patch>;
+ fn create_patch(&mut self, patch: Patch);
+ fn delete_patch(&mut self, id: PatchId);
+ fn set_active_patches(&mut self, ids: &[PatchId]);
+ fn combine_patches(&mut self, patches: &[PatchId]) -> Patch;
+
+ fn active_files(&self) -> Vec<FileId>;
+ fn file(&self, id: FileId) -> Option<FileInfo>;
+ fn write_file_from_patch(&self, id: FileId, patch: PatchId) -> Vec<u8>;
+
+ fn span(&self, id: SpanId) -> Option<Span>;
+}
diff --git a/src/workarea.rs b/src/workarea.rs
new file mode 100644
index 0000000..d5d2e13
--- /dev/null
+++ b/src/workarea.rs
@@ -0,0 +1,74 @@
+use std::fs::File;
+use std::io::{BufRead, BufReader};
+use std::ops::Deref;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+use std::time::{Instant, SystemTime};
+
+use walkdir::WalkDir;
+
+pub struct IgnoreFile {
+ globs: Arc<[Arc<str>]>,
+}
+
+pub struct WorkFileMetadata {
+ name: Arc<Path>,
+ last_modified: Instant,
+}
+
+fn equal_to_path(a: impl AsRef<Path>, b: impl AsRef<Path>) -> bool {
+ a.as_ref() == b.as_ref()
+}
+pub fn included_files(
+ root: impl AsRef<Path>,
+ ignored_files: Option<&IgnoreFile>,
+ since: Option<SystemTime>,
+) -> std::io::Result<Vec<PathBuf>> {
+ let mut files = Vec::new();
+
+ let walker = WalkDir::new(root).into_iter().filter_entry(|entry| {
+ !equal_to_path(entry.path(), ".pj")
+ && !equal_to_path(entry.path(), ".ignore")
+ && entry
+ .metadata()
+ .ok()
+ .is_some_and(|metadata| metadata.is_file())
+ && ignored_files
+ .is_none_or(|file| !file.should_ignore(entry.path().to_string_lossy().deref()))
+ && since
+ .zip(entry.metadata().ok())
+ .and_then(|(since, metadata)| {
+ metadata.modified().ok().map(|modified| (since, modified))
+ })
+ .is_none_or(|(since, modified)| since < modified)
+ });
+
+ for entry in walker {
+ files.push(entry?.into_path());
+ }
+
+ Ok(files)
+}
+
+impl IgnoreFile {
+ fn open(root: impl AsRef<Path>) -> std::io::Result<Self> {
+ let mut globs = Vec::new();
+ globs.push(String::from(".pj/"));
+
+ let ignore_file = Path::join(root.as_ref(), ".ignore");
+ let ignore_file = BufReader::new(File::open(ignore_file)?);
+ for line in ignore_file.lines() {
+ globs.push(line?);
+ }
+
+ Ok(Self {
+ globs: globs.iter().map(|s| s.deref().into()).collect(),
+ })
+ }
+
+ fn should_ignore(&self, path: &str) -> bool {
+ self.globs
+ .iter()
+ .any(|glob| fast_glob::glob_match(&**glob, path))
+ }
+}