pipedash-plugin-bitbucket 0.10.0

Bitbucket Pipelines plugin for Pipedash
Documentation
use pipedash_plugin_api::{
    LineMarker,
    LogDialect,
    ParsedLine,
};

const COMMAND_PREFIX: &[u8] = b"+ ";

pub struct BitbucketLogDialect;

impl LogDialect for BitbucketLogDialect {
    fn parse<'a>(&self, line: &'a [u8]) -> ParsedLine<'a> {
        let is_command = line
            .strip_prefix(COMMAND_PREFIX)
            .is_some_and(|command| !command.trim_ascii().is_empty());
        ParsedLine {
            timestamp: None,
            marker: if is_command {
                LineMarker::Command
            } else {
                LineMarker::Plain
            },
            body: line,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn parse(line: &[u8]) -> ParsedLine<'_> {
        BitbucketLogDialect.parse(line)
    }

    #[test]
    fn script_commands_are_commands_and_keep_their_text() {
        let lines: [&[u8]; 5] = [
            b"+ umask 000",
            b"+ git reset --hard 4c684f2c6785329c62e18489ded3dbaf3f76ac0f",
            b"+ GIT_LFS_SKIP_SMUDGE=1 retry 6 git clone --branch=\"main\" --depth 50 $BUILD_DIR",
            b"+ echo \".bitbucket/pipelines/generated\" >> .git/info/exclude",
            b"+ docker container run \\",
        ];
        for line in lines {
            let parsed = parse(line);
            assert_eq!(parsed.marker, LineMarker::Command, "{line:?}");
            assert_eq!(parsed.body, line);
            assert_eq!(parsed.timestamp, None);
        }
    }

    #[test]
    fn output_and_continuation_lines_stay_plain() {
        let lines: [&[u8]; 11] = [
            b"Cloning into '/opt/atlassian/pipelines/agent/build'...",
            b"HEAD is now at 4c684f2 fix syntax error",
            b"   --volume=/opt/atlassian/pipelines/agent/build:/opt/atlassian/pipelines/agent/build \\",
            b"Default variables:",
            b"    build : docker.io/atlassian/default-image@sha256:689e2c63",
            b"+",
            b"+ ",
            b"+   ",
            b"++ nested trace",
            b"+added line",
            b" + indented",
        ];
        for line in lines {
            let parsed = parse(line);
            assert_eq!(parsed.marker, LineMarker::Plain, "{line:?}");
            assert_eq!(parsed.body, line);
            assert_eq!(parsed.timestamp, None);
        }
    }

    #[test]
    fn colored_tool_output_is_not_a_command() {
        let parsed = parse(b"\x1b[32m+ added\x1b[0m");
        assert_eq!(parsed.marker, LineMarker::Plain);
    }
}