Skip to main content
Glama
by microsoft
changelog.ts6.19 kB
/** * Defines interfaces and functions for parsing and applying changelogs. * A changelog describes changes between original and modified code segments. */ import { unfence } from "./unwrappers" // Represents a chunk of code with a start and end line and its content. export interface ChangeLogChunk { start: number // Starting line number end: number // Ending line number lines: { index: number; content: string }[] // Lines of code within the chunk } // Represents a change between an original and a changed code chunk. export interface ChangeLogChange { original: ChangeLogChunk // Original code chunk changed: ChangeLogChunk // Changed code chunk } // Represents a complete changelog for a file. export interface ChangeLog { index: number // Index of the changelog entry filename: string // Filename associated with the changelog description: string // Description of the changes changes: ChangeLogChange[] // List of changes within the changelog } /** * Parses a raw changelog string into a structured array of ChangeLog objects. * * @param source The raw input string containing changelog information. * Must include headers, descriptions, and detailed changes with line numbers and contents. * The input is expected to be wrapped in a "changelog" fence. * Throws an error if the format is invalid, required fields are missing, or parsing fails. * * @returns An array of ChangeLog objects parsed from the input. */ export function parseChangeLogs(source: string): ChangeLog[] { const lines = unfence(source, "changelog").split("\n") const changelogs: ChangeLog[] = [] // Process each line to extract changelog information. while (lines.length) { if (!lines[0].trim()) { lines.shift() continue } // each back ticks if (/^[\`\.]{3,}/.test(lines[0])) { lines.shift() continue } // Parse the ChangeLog header line. let m = /^ChangeLog:\s*(?<index>\d+)@(?<file>.*)\s*$/i.exec(lines[0]) if (!m) throw new Error("missing ChangeLog header in |" + lines[0] + "|") const changelog: ChangeLog = { index: parseInt(m.groups.index), filename: m.groups.file.trim(), description: undefined, changes: [], } changelogs.push(changelog) lines.shift() // Parse the Description line. m = /^Description:(?<description>.*)$/i.exec(lines[0]) if (!m) throw new Error("missing ChangeLog description") changelog.description = m.groups.description.trim() lines.shift() // Parse changes block. while (lines.length) { // Skip empty lines. if (/^\s*$/.test(lines[0])) { lines.shift() continue } // each back ticks if (/^[\`\.]{3,}/.test(lines[0])) { // somehow we have finished this changed lines.shift() continue } // Attempt to parse a change. const change = parseChange() if (change) changelog.changes.push(change) else break } } return changelogs // Parses a single change within the changelog. function parseChange(): ChangeLogChange { // Parse OriginalCode block let m = /^OriginalCode@(?<start>\d+)-(?<end>\d+):$/i.exec(lines[0]) if (!m) return undefined lines.shift() const original = parseChunk(m) // Parse ChangedCode block m = /^ChangedCode@(?<start>\d+)-(?<end>\d+):\s*$/i.exec(lines[0]) if (!m) throw new Error("missing ChangedCode Changed in '" + lines[0] + "'") lines.shift() const changed = parseChunk(m) const res = <ChangeLogChange>{ original, changed } return res } // Parses a chunk of code from the changelog. function parseChunk(m: RegExpExecArray): ChangeLogChunk { const start = parseInt(m.groups.start) const end = parseInt(m.groups.end) const chunk: ChangeLogChunk = { start, end, lines: [], } while (lines.length) { m = /^\[(?<index>\d+)\](?<content>.*)$/i.exec(lines[0]) if (m) { let content = m.groups.content if (content[0] === " ") content = content.slice(1) chunk.lines.push({ index: parseInt(m.groups.index), content, }) lines.shift() } else { break } } return chunk } /* Example changelog format: ChangeLog:1@<file> Description: <summary>. OriginalCode@4-6: [4] <white space> <original code line> // More lines ChangedCode@4-6: [4] <white space> <changed code line> // More lines */ } /** * Applies a changelog to a given source string, modifying it according to the changes. * * @param source The original source code as a string. * @param changelog The ChangeLog object containing the changes to apply. Updates line indices for subsequent changes. * @returns The modified source code as a string. */ export function applyChangeLog(source: string, changelog: ChangeLog): string { const lines = source.split("\n") for (let i = 0; i < changelog.changes.length; ++i) { const change = changelog.changes[i] const { original, changed } = change // Replace original lines with changed lines in the source. lines.splice( original.start - 1, original.end - original.start + 1, ...changed.lines.map((l) => l.content) ) // Adjust subsequent change indices based on the shift in lines. const shift = changed.lines.length - original.lines.length for (let j = i + 1; j < changelog.changes.length; ++j) { const c = changelog.changes[j] c.original.start += shift c.original.end += shift } } return lines.join("\n") }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/microsoft/genaiscript'

If you have feedback or need assistance with the MCP directory API, please join our Discord server