lintje 0.6.0

Lintje is an opinionated linter for Git.
use core::ops::Range;
use std::fmt;

#[derive(Debug, PartialEq)]
pub enum Rule {
    MergeCommit,
    NeedsRebase,
    SubjectLength,
    SubjectMood,
    SubjectWhitespace,
    SubjectCapitalization,
    SubjectPunctuation,
    SubjectTicketNumber,
    SubjectPrefix,
    SubjectBuildTag,
    SubjectCliche,
    MessageEmptyFirstLine,
    MessagePresence,
    MessageLineLength,
    DiffPresence,
    BranchNameTicketNumber,
    BranchNameLength,
    BranchNamePunctuation,
    BranchNameCliche,
}

#[derive(Debug, PartialEq)]
pub struct Violation {
    pub rule: Rule,
    pub message: String,
    pub position: Position,
    pub context: Vec<Context>,
}

#[derive(Debug, PartialEq)]
pub enum Position {
    Subject { column: usize },
    MessageLine { line: usize, column: usize },
    Diff,
    Branch { column: usize },
}

impl Position {
    pub fn line_number(&self) -> Option<usize> {
        match self {
            Self::Subject { column: _ } => Some(1),
            Self::MessageLine { line, column: _ } => Some(*line + 1),
            Self::Diff => None,
            Self::Branch { column: _ } => None,
        }
    }

    pub fn column(&self) -> Option<usize> {
        match self {
            Self::Subject { column } => Some(*column),
            Self::MessageLine { line: _, column } => Some(*column),
            Self::Diff => None,
            Self::Branch { column } => Some(*column),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Source {
    Subject { content: String },
    MessageLine { line: usize, content: String },
    Branch { content: String },
    Diff { content: String },
}

#[derive(Debug, PartialEq)]
pub struct Context {
    pub source: Source,
    pub hint: Option<Hint>,
}

impl Source {
    pub fn line_number(&self) -> Option<usize> {
        match self {
            Self::Subject { content: _ } => Some(0),
            Self::MessageLine { line, content: _ } => Some(*line + 1),
            _ => None,
        }
    }

    pub fn content(&self) -> &str {
        match self {
            Self::Subject { content } => &*content,
            Self::MessageLine { line: _, content } => &*content,
            Self::Branch { content } => &*content,
            Self::Diff { content } => &*content,
        }
    }
}

impl Context {
    pub fn subject(content: String) -> Self {
        Self {
            source: Source::Subject { content },
            hint: None,
        }
    }

    pub fn subject_hint(content: String, range: Range<usize>, message: String) -> Self {
        Self {
            source: Source::Subject { content },
            hint: Some(Hint { range, message }),
        }
    }

    pub fn message_line(line: usize, content: String) -> Self {
        Self {
            source: Source::MessageLine { line, content },
            hint: None,
        }
    }

    pub fn message_line_hint(
        line: usize,
        content: String,
        range: Range<usize>,
        message: String,
    ) -> Self {
        Self {
            source: Source::MessageLine { line, content },
            hint: Some(Hint { range, message }),
        }
    }

    pub fn diff_hint(content: String, range: Range<usize>, message: String) -> Self {
        Self {
            source: Source::Diff { content },
            hint: Some(Hint { range, message }),
        }
    }

    pub fn branch_hint(content: String, range: Range<usize>, message: String) -> Self {
        Self {
            source: Source::Branch { content },
            hint: Some(Hint { range, message }),
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct Hint {
    pub range: Range<usize>,
    pub message: String,
}

impl fmt::Display for Rule {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let label = match self {
            Rule::MergeCommit => "MergeCommit",
            Rule::NeedsRebase => "NeedsRebase",
            Rule::SubjectLength => "SubjectLength",
            Rule::SubjectMood => "SubjectMood",
            Rule::SubjectWhitespace => "SubjectWhitespace",
            Rule::SubjectCapitalization => "SubjectCapitalization",
            Rule::SubjectPunctuation => "SubjectPunctuation",
            Rule::SubjectTicketNumber => "SubjectTicketNumber",
            Rule::SubjectPrefix => "SubjectPrefix",
            Rule::SubjectBuildTag => "SubjectBuildTag",
            Rule::SubjectCliche => "SubjectCliche",
            Rule::MessageEmptyFirstLine => "MessageEmptyFirstLine",
            Rule::MessagePresence => "MessagePresence",
            Rule::MessageLineLength => "MessageLineLength",
            Rule::DiffPresence => "DiffPresence",
            Rule::BranchNameTicketNumber => "BranchNameTicketNumber",
            Rule::BranchNameLength => "BranchNameLength",
            Rule::BranchNamePunctuation => "BranchNamePunctuation",
            Rule::BranchNameCliche => "BranchNameCliche",
        };
        write!(f, "{}", label)
    }
}

pub fn rule_by_name(name: &str) -> Option<Rule> {
    match name {
        "MergeCommit" => Some(Rule::MergeCommit),
        "NeedsRebase" => Some(Rule::NeedsRebase),
        "SubjectLength" => Some(Rule::SubjectLength),
        "SubjectMood" => Some(Rule::SubjectMood),
        "SubjectWhitespace" => Some(Rule::SubjectWhitespace),
        "SubjectCapitalization" => Some(Rule::SubjectCapitalization),
        "SubjectPunctuation" => Some(Rule::SubjectPunctuation),
        "SubjectTicketNumber" => Some(Rule::SubjectTicketNumber),
        "SubjectBuildTag" => Some(Rule::SubjectBuildTag),
        "SubjectPrefix" => Some(Rule::SubjectPrefix),
        "SubjectCliche" => Some(Rule::SubjectCliche),
        "MessageEmptyFirstLine" => Some(Rule::MessageEmptyFirstLine),
        "MessagePresence" => Some(Rule::MessagePresence),
        "MessageLineLength" => Some(Rule::MessageLineLength),
        "DiffPresence" => Some(Rule::DiffPresence),
        _ => None,
    }
}