@aaronshaf/ger
Version:
Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS
154 lines (137 loc) • 5.44 kB
text/typescript
import type { CommentInfo } from '@/schemas/gerrit'
import { colors, formatDate } from './formatters'
export interface CommentWithContext {
comment: CommentInfo
context?: {
before: string[]
line?: string
after: string[]
}
}
export const formatCommentsPretty = (comments: CommentWithContext[]): void => {
if (comments.length === 0) {
console.log('No comments found on this change')
return
}
console.log(`Found ${comments.length} comment${comments.length === 1 ? '' : 's'}:\n`)
let currentPath: string | undefined
for (const { comment, context } of comments) {
// Group by file
if (comment.path !== currentPath) {
currentPath = comment.path
console.log(`${colors.blue}═══ ${currentPath} ═══${colors.reset}`)
}
// Comment metadata
const author = comment.author?.name || 'Unknown'
const date = comment.updated ? formatDate(comment.updated) : ''
const status = comment.unresolved ? `${colors.yellow}[UNRESOLVED]${colors.reset} ` : ''
console.log(`\n${status}${colors.dim}${author} • ${date}${colors.reset}`)
if (comment.line) {
console.log(`${colors.dim}Line ${comment.line}:${colors.reset}`)
// Show context if available
if (context && (context.before.length > 0 || context.line || context.after.length > 0)) {
console.log(`${colors.dim}───────────────────${colors.reset}`)
for (const line of context.before) {
console.log(`${colors.dim} ${line}${colors.reset}`)
}
if (context.line) {
console.log(`${colors.green}> ${context.line}${colors.reset}`)
}
for (const line of context.after) {
console.log(`${colors.dim} ${line}${colors.reset}`)
}
console.log(`${colors.dim}───────────────────${colors.reset}`)
}
}
// Comment message (indent each line)
const messageLines = comment.message.split('\n')
for (const line of messageLines) {
console.log(` ${line}`)
}
}
}
// Escape special XML characters to prevent XSS
const escapeXml = (str: string): string => {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
export const formatCommentsXml = (changeId: string, comments: CommentWithContext[]): void => {
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
console.log(`<comments_result>`)
console.log(` <change_id>${escapeXml(changeId)}</change_id>`)
console.log(` <comment_count>${comments.length}</comment_count>`)
console.log(` <comments>`)
for (const { comment, context } of comments) {
console.log(` <comment>`)
console.log(` <id>${escapeXml(comment.id)}</id>`)
if (comment.path) {
console.log(` <path><![CDATA[${comment.path}]]></path>`)
}
if (comment.line) {
console.log(` <line>${comment.line}</line>`)
}
if (comment.range) {
console.log(` <range>`)
console.log(` <start_line>${comment.range.start_line}</start_line>`)
console.log(` <end_line>${comment.range.end_line}</end_line>`)
if (comment.range.start_character !== undefined) {
console.log(` <start_character>${comment.range.start_character}</start_character>`)
}
if (comment.range.end_character !== undefined) {
console.log(` <end_character>${comment.range.end_character}</end_character>`)
}
console.log(` </range>`)
}
if (comment.author) {
console.log(` <author>`)
if (comment.author.name) {
console.log(` <name><![CDATA[${comment.author.name}]]></name>`)
}
if (comment.author.email) {
console.log(` <email>${escapeXml(comment.author.email)}</email>`)
}
if (comment.author._account_id !== undefined) {
console.log(` <account_id>${comment.author._account_id}</account_id>`)
}
console.log(` </author>`)
}
if (comment.updated) {
console.log(` <updated>${escapeXml(comment.updated)}</updated>`)
}
if (comment.unresolved !== undefined) {
console.log(` <unresolved>${comment.unresolved}</unresolved>`)
}
if (comment.in_reply_to) {
console.log(` <in_reply_to>${escapeXml(comment.in_reply_to)}</in_reply_to>`)
}
console.log(` <message><![CDATA[${comment.message}]]></message>`)
if (context && (context.before.length > 0 || context.line || context.after.length > 0)) {
console.log(` <diff_context>`)
if (context.before.length > 0) {
console.log(` <before>`)
for (const line of context.before) {
console.log(` <line><![CDATA[${line}]]></line>`)
}
console.log(` </before>`)
}
if (context.line) {
console.log(` <target_line><![CDATA[${context.line}]]></target_line>`)
}
if (context.after.length > 0) {
console.log(` <after>`)
for (const line of context.after) {
console.log(` <line><![CDATA[${line}]]></line>`)
}
console.log(` </after>`)
}
console.log(` </diff_context>`)
}
console.log(` </comment>`)
}
console.log(` </comments>`)
console.log(`</comments_result>`)
}