jujube 0.1.1

Jujube (an experimental VCS)
Documentation
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::borrow::BorrowMut;
use std::ops::Add;

use jujube_lib::commit::Commit;
use jujube_lib::repo::Repo;
use jujube_lib::store::{CommitId, Signature};

use crate::styler::Styler;

pub trait Template<C> {
    fn format(&self, context: &C, styler: &mut dyn Styler);
}

// TODO: Extract a trait for this type?
pub struct TemplateFormatter<'s, 't: 's, C> {
    template: Box<dyn Template<C> + 't>,
    styler: &'s mut dyn Styler,
}

impl<'s, 't: 's, C> TemplateFormatter<'s, 't, C> {
    pub fn new(template: Box<dyn Template<C> + 't>, styler: &'s mut dyn Styler) -> Self {
        TemplateFormatter { template, styler }
    }

    pub fn format<'c, 'a: 'c>(&'a mut self, context: &'c C) {
        self.template.format(context, self.styler.borrow_mut());
    }
}

pub struct LiteralTemplate(pub String);

impl<C> Template<C> for LiteralTemplate {
    fn format(&self, _context: &C, styler: &mut dyn Styler) {
        styler.write_str(&self.0)
    }
}

// TODO: figure out why this lifetime is needed
pub struct LabelTemplate<'a, C> {
    content: Box<dyn Template<C> + 'a>,
    labels: Vec<String>,
}

impl<'a, C> LabelTemplate<'a, C> {
    pub fn new(content: Box<dyn Template<C> + 'a>, labels: String) -> Self {
        let labels: Vec<String> = labels
            .split_whitespace()
            .map(|label| label.to_string())
            .collect();
        LabelTemplate { content, labels }
    }
}

impl<'a, C> Template<C> for LabelTemplate<'a, C> {
    fn format(&self, context: &C, styler: &mut dyn Styler) {
        for label in &self.labels {
            styler.add_label(label.clone());
        }
        self.content.format(context, styler);
        for _label in &self.labels {
            styler.remove_label();
        }
    }
}

// TODO: figure out why this lifetime is needed
pub struct DynamicLabelTemplate<'a, C> {
    content: Box<dyn Template<C> + 'a>,
    label_property: Box<dyn Fn(&C) -> String + 'a>,
}

impl<'a, C> DynamicLabelTemplate<'a, C> {
    pub fn new(
        content: Box<dyn Template<C> + 'a>,
        label_property: Box<dyn Fn(&C) -> String + 'a>,
    ) -> Self {
        DynamicLabelTemplate {
            content,
            label_property,
        }
    }
}

impl<'a, C> Template<C> for DynamicLabelTemplate<'a, C> {
    fn format(&self, context: &C, styler: &mut dyn Styler) {
        let labels = self.label_property.as_ref()(context);
        let labels: Vec<String> = labels
            .split_whitespace()
            .map(|label| label.to_string())
            .collect();
        for label in &labels {
            styler.add_label(label.clone());
        }
        self.content.format(context, styler);
        for _label in &labels {
            styler.remove_label();
        }
    }
}

// TODO: figure out why this lifetime is needed
pub struct ListTemplate<'a, C>(pub Vec<Box<dyn Template<C> + 'a>>);

impl<'a, C> Template<C> for ListTemplate<'a, C> {
    fn format(&self, context: &C, styler: &mut dyn Styler) {
        for template in &self.0 {
            template.format(context, styler)
        }
    }
}

pub trait TemplateProperty<C, O> {
    fn extract(&self, context: &C) -> O;
}

pub struct ConstantTemplateProperty<O: Clone> {
    pub output: O,
}

impl<C, O: Clone> TemplateProperty<C, O> for ConstantTemplateProperty<O> {
    fn extract(&self, _context: &C) -> O {
        self.output.clone()
    }
}

// TODO: figure out why this lifetime is needed
pub struct StringPropertyTemplate<'a, C> {
    pub property: Box<dyn TemplateProperty<C, String> + 'a>,
}

impl<'a, C> Template<C> for StringPropertyTemplate<'a, C> {
    fn format(&self, context: &C, styler: &mut dyn Styler) {
        let text = self.property.extract(context);
        styler.write_str(&text);
    }
}

pub struct ChangeIdProperty;

impl<'r> TemplateProperty<Commit, String> for ChangeIdProperty {
    fn extract(&self, context: &Commit) -> String {
        context.change_id().hex()
    }
}

pub struct DescriptionProperty;

impl<'r> TemplateProperty<Commit, String> for DescriptionProperty {
    fn extract(&self, context: &Commit) -> String {
        let description = context.description().to_owned();
        if description.ends_with('\n') {
            description
        } else {
            description.add("\n")
        }
    }
}

pub struct AuthorProperty;

impl<'r> TemplateProperty<Commit, Signature> for AuthorProperty {
    fn extract(&self, context: &Commit) -> Signature {
        context.author().clone()
    }
}

pub struct CommitterProperty;

impl<'r> TemplateProperty<Commit, Signature> for CommitterProperty {
    fn extract(&self, context: &Commit) -> Signature {
        context.committer().clone()
    }
}

pub struct OpenProperty;

impl<'r> TemplateProperty<Commit, bool> for OpenProperty {
    fn extract(&self, context: &Commit) -> bool {
        context.is_open()
    }
}

pub struct PrunedProperty;

impl TemplateProperty<Commit, bool> for PrunedProperty {
    fn extract(&self, context: &Commit) -> bool {
        context.is_pruned()
    }
}

pub struct CurrentCheckoutProperty<'r> {
    pub repo: &'r dyn Repo,
}

impl<'r> TemplateProperty<Commit, bool> for CurrentCheckoutProperty<'r> {
    fn extract(&self, context: &Commit) -> bool {
        context.id() == self.repo.view().checkout()
    }
}

pub struct ObsoleteProperty<'r> {
    pub repo: &'r dyn Repo,
}

impl<'r> TemplateProperty<Commit, bool> for ObsoleteProperty<'r> {
    fn extract(&self, context: &Commit) -> bool {
        self.repo.evolution().is_obsolete(context.id())
    }
}

pub struct OrphanProperty<'r> {
    pub repo: &'r dyn Repo,
}

impl<'r> TemplateProperty<Commit, bool> for OrphanProperty<'r> {
    fn extract(&self, context: &Commit) -> bool {
        self.repo.evolution().is_orphan(context.id())
    }
}

pub struct DivergentProperty<'r> {
    pub repo: &'r dyn Repo,
}

impl<'r> TemplateProperty<Commit, bool> for DivergentProperty<'r> {
    fn extract(&self, context: &Commit) -> bool {
        self.repo.evolution().is_divergent(context.change_id())
    }
}

pub struct ConflictProperty;

impl<'r> TemplateProperty<Commit, bool> for ConflictProperty {
    fn extract(&self, context: &Commit) -> bool {
        context.tree().has_conflict()
    }
}

pub struct ConditionalTemplate<'a, C> {
    pub condition: Box<dyn TemplateProperty<C, bool> + 'a>,
    pub true_template: Box<dyn Template<C> + 'a>,
    pub false_template: Option<Box<dyn Template<C> + 'a>>,
}

// TODO: figure out why this lifetime is needed
impl<'a, C> ConditionalTemplate<'a, C> {
    pub fn new(
        condition: Box<dyn TemplateProperty<C, bool> + 'a>,
        true_template: Box<dyn Template<C> + 'a>,
        false_template: Option<Box<dyn Template<C> + 'a>>,
    ) -> Self {
        ConditionalTemplate {
            condition,
            true_template,
            false_template,
        }
    }
}

impl<'a, C> Template<C> for ConditionalTemplate<'a, C> {
    fn format(&self, context: &C, styler: &mut dyn Styler) {
        if self.condition.extract(context) {
            self.true_template.format(context, styler);
        } else if let Some(false_template) = &self.false_template {
            false_template.format(context, styler);
        }
    }
}

// TODO: If needed, add a ContextualTemplateFunction where the function also
// gets the context
pub struct TemplateFunction<'a, C, I, O> {
    pub property: Box<dyn TemplateProperty<C, I> + 'a>,
    pub function: Box<dyn Fn(I) -> O + 'a>,
}

// TODO: figure out why this lifetime is needed
impl<'a, C, I, O> TemplateFunction<'a, C, I, O> {
    pub fn new(
        template: Box<dyn TemplateProperty<C, I> + 'a>,
        function: Box<dyn Fn(I) -> O + 'a>,
    ) -> Self {
        TemplateFunction {
            property: template,
            function,
        }
    }
}

impl<'a, C, I, O> TemplateProperty<C, O> for TemplateFunction<'a, C, I, O> {
    fn extract(&self, context: &C) -> O {
        (self.function)(self.property.extract(context))
    }
}

pub struct CommitIdKeyword;

impl CommitIdKeyword {
    pub fn default_format(commit_id: CommitId) -> String {
        commit_id.hex()
    }

    pub fn shortest_format(commit_id: CommitId) -> String {
        // TODO: make this actually be the shortest unambiguous prefix
        commit_id.hex()[..12].to_string()
    }
}

impl<'r> TemplateProperty<Commit, CommitId> for CommitIdKeyword {
    fn extract(&self, context: &Commit) -> CommitId {
        context.id().clone()
    }
}