summary refs log tree commit diff stats
path: root/pkgs/by-name/ba/back/src/git_bug/issue/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/by-name/ba/back/src/git_bug/issue/mod.rs')
-rw-r--r--pkgs/by-name/ba/back/src/git_bug/issue/mod.rs185
1 files changed, 185 insertions, 0 deletions
diff --git a/pkgs/by-name/ba/back/src/git_bug/issue/mod.rs b/pkgs/by-name/ba/back/src/git_bug/issue/mod.rs
new file mode 100644
index 0000000..f27bfec
--- /dev/null
+++ b/pkgs/by-name/ba/back/src/git_bug/issue/mod.rs
@@ -0,0 +1,185 @@
+// Back - An extremely simple git issue tracking system. Inspired by tvix's
+// panettone
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This file is part of Back.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+use std::fmt::Display;
+
+use entity::{Entity, Id};
+use identity::Author;
+use label::Label;
+use operation::Operation;
+use serde_json::Value;
+
+use super::format::{MarkDown, TimeStamp};
+
+pub mod entity;
+pub mod identity;
+pub mod label;
+pub mod operation;
+
+#[derive(Debug, Eq, PartialEq, Copy, Clone)]
+pub enum Status {
+    Open,
+    Closed,
+}
+impl From<&Value> for Status {
+    fn from(value: &Value) -> Self {
+        match value.as_u64().expect("This should be a integer") {
+            1 => Self::Open,
+            2 => Self::Closed,
+            other => unimplemented!("Invalid status string: '{other}'"),
+        }
+    }
+}
+impl Display for Status {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Status::Open => f.write_str("Open"),
+            Status::Closed => f.write_str("Closed"),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct CollapsedIssue {
+    pub id: Id,
+    pub author: Author,
+    pub timestamp: TimeStamp,
+    pub title: MarkDown,
+    pub message: MarkDown,
+    pub comments: Vec<Comment>,
+    pub status: Status,
+    pub last_status_change: TimeStamp,
+    pub labels: Vec<Label>,
+}
+impl From<RawCollapsedIssue> for CollapsedIssue {
+    fn from(r: RawCollapsedIssue) -> Self {
+        macro_rules! get {
+            ($name:ident) => {
+                r.$name.expect(concat!(
+                    "'",
+                    stringify!($name),
+                    "' is unset, when trying to collapes an issue! (This is likely a bug)"
+                ))
+            };
+        }
+
+        Self {
+            id: get! {id},
+            author: get! {author},
+            timestamp: get! {timestamp},
+            title: get! {title},
+            message: get! {message},
+            comments: r.comments,
+            status: get! {status},
+            last_status_change: get! {last_status_change},
+            labels: r.labels,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct Comment {
+    pub id: Id,
+    pub author: Author,
+    pub timestamp: TimeStamp,
+    pub message: MarkDown,
+}
+
+#[derive(Debug, Default)]
+pub struct RawCollapsedIssue {
+    pub id: Option<Id>,
+    pub author: Option<Author>,
+    pub timestamp: Option<TimeStamp>,
+    pub title: Option<MarkDown>,
+    pub message: Option<MarkDown>,
+    pub status: Option<Status>,
+    pub last_status_change: Option<TimeStamp>,
+
+    // NOTE(@bpeetz): These values set here already, because an issue without these
+    // would be perfectly valid. <2024-12-26>
+    pub labels: Vec<Label>,
+    pub comments: Vec<Comment>,
+}
+
+impl RawCollapsedIssue {
+    pub fn append_entity(&mut self, entity: Entity) {
+        for op in entity.operations {
+            match op {
+                Operation::AddComment { timestamp, message } => {
+                    self.comments.push(Comment {
+                        id: entity.id.clone(),
+                        author: entity.author.clone(),
+                        timestamp,
+                        message,
+                    });
+                }
+                Operation::Create {
+                    timestamp,
+                    title,
+                    message,
+                } => {
+                    self.id = Some(entity.id.clone());
+                    self.author = Some(entity.author.clone());
+                    self.timestamp = Some(timestamp.clone());
+                    self.title = Some(title);
+                    self.message = Some(message);
+                    self.status = Some(Status::Open); // This is the default in git_bug
+                    self.last_status_change = Some(timestamp);
+                }
+                Operation::EditComment {
+                    timestamp,
+                    target,
+                    message,
+                } => {
+                    let comments = &mut self.comments;
+
+                    let target_comment = comments
+                        .iter_mut()
+                        .find(|comment| comment.id == target)
+                        .expect("The target must be a valid comment");
+
+                    // TODO(@bpeetz): We should probably set a `edited = true` flag here. <2024-12-26>
+                    // TODO(@bpeetz): Should we also change the author? <2024-12-26>
+
+                    target_comment.timestamp = timestamp;
+                    target_comment.message = message;
+                }
+                Operation::LabelChange {
+                    timestamp: _,
+                    added,
+                    removed,
+                } => {
+                    let labels = self.labels.clone();
+
+                    self.labels = labels
+                        .into_iter()
+                        .filter(|val| !removed.contains(val))
+                        .chain(added.into_iter())
+                        .collect();
+                }
+                Operation::SetStatus { timestamp, status } => {
+                    self.status = Some(status);
+                    self.last_status_change = Some(timestamp);
+                }
+                Operation::SetTitle {
+                    timestamp: _,
+                    title,
+                    was: _,
+                } => {
+                    self.title = Some(title);
+                }
+
+                Operation::NoOp {} => unimplemented!(),
+                Operation::SetMetadata {} => unimplemented!(),
+            }
+        }
+    }
+}