You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
7.0 KiB
316 lines
7.0 KiB
'use strict'
|
|
const _ = require('lodash')
|
|
|
|
const CATCH_ALL = /()(.+)/gi
|
|
const SCISSOR = '# ------------------------ >8 ------------------------'
|
|
|
|
function trimOffNewlines (input) {
|
|
const result = input.match(/[^\r\n]/)
|
|
if (!result) {
|
|
return ''
|
|
}
|
|
const firstIndex = result.index
|
|
let lastIndex = input.length - 1
|
|
while (input[lastIndex] === '\r' || input[lastIndex] === '\n') {
|
|
lastIndex--
|
|
}
|
|
return input.substring(firstIndex, lastIndex + 1)
|
|
}
|
|
|
|
function append (src, line) {
|
|
if (src) {
|
|
src += '\n' + line
|
|
} else {
|
|
src = line
|
|
}
|
|
|
|
return src
|
|
}
|
|
|
|
function getCommentFilter (char) {
|
|
return function (line) {
|
|
return line.charAt(0) !== char
|
|
}
|
|
}
|
|
|
|
function truncateToScissor (lines) {
|
|
const scissorIndex = lines.indexOf(SCISSOR)
|
|
|
|
if (scissorIndex === -1) {
|
|
return lines
|
|
}
|
|
|
|
return lines.slice(0, scissorIndex)
|
|
}
|
|
|
|
function getReferences (input, regex) {
|
|
const references = []
|
|
let referenceSentences
|
|
let referenceMatch
|
|
|
|
const reApplicable = input.match(regex.references) !== null
|
|
? regex.references
|
|
: CATCH_ALL
|
|
|
|
while ((referenceSentences = reApplicable.exec(input))) {
|
|
const action = referenceSentences[1] || null
|
|
const sentence = referenceSentences[2]
|
|
|
|
while ((referenceMatch = regex.referenceParts.exec(sentence))) {
|
|
let owner = null
|
|
let repository = referenceMatch[1] || ''
|
|
const ownerRepo = repository.split('/')
|
|
|
|
if (ownerRepo.length > 1) {
|
|
owner = ownerRepo.shift()
|
|
repository = ownerRepo.join('/')
|
|
}
|
|
|
|
const reference = {
|
|
action: action,
|
|
owner: owner,
|
|
repository: repository || null,
|
|
issue: referenceMatch[3],
|
|
raw: referenceMatch[0],
|
|
prefix: referenceMatch[2]
|
|
}
|
|
|
|
references.push(reference)
|
|
}
|
|
}
|
|
|
|
return references
|
|
}
|
|
|
|
function passTrough () {
|
|
return true
|
|
}
|
|
|
|
function parser (raw, options, regex) {
|
|
if (!raw || !raw.trim()) {
|
|
throw new TypeError('Expected a raw commit')
|
|
}
|
|
|
|
if (_.isEmpty(options)) {
|
|
throw new TypeError('Expected options')
|
|
}
|
|
|
|
if (_.isEmpty(regex)) {
|
|
throw new TypeError('Expected regex')
|
|
}
|
|
|
|
let currentProcessedField
|
|
let mentionsMatch
|
|
const otherFields = {}
|
|
const commentFilter = typeof options.commentChar === 'string'
|
|
? getCommentFilter(options.commentChar)
|
|
: passTrough
|
|
const gpgFilter = line => !line.match(/^\s*gpg:/)
|
|
|
|
const rawLines = trimOffNewlines(raw).split(/\r?\n/)
|
|
const lines = truncateToScissor(rawLines).filter(commentFilter).filter(gpgFilter)
|
|
|
|
let continueNote = false
|
|
let isBody = true
|
|
const headerCorrespondence = _.map(options.headerCorrespondence, function (part) {
|
|
return part.trim()
|
|
})
|
|
const revertCorrespondence = _.map(options.revertCorrespondence, function (field) {
|
|
return field.trim()
|
|
})
|
|
const mergeCorrespondence = _.map(options.mergeCorrespondence, function (field) {
|
|
return field.trim()
|
|
})
|
|
|
|
let body = null
|
|
let footer = null
|
|
let header = null
|
|
const mentions = []
|
|
let merge = null
|
|
const notes = []
|
|
const references = []
|
|
let revert = null
|
|
|
|
if (lines.length === 0) {
|
|
return {
|
|
body: body,
|
|
footer: footer,
|
|
header: header,
|
|
mentions: mentions,
|
|
merge: merge,
|
|
notes: notes,
|
|
references: references,
|
|
revert: revert,
|
|
scope: null,
|
|
subject: null,
|
|
type: null
|
|
}
|
|
}
|
|
|
|
// msg parts
|
|
merge = lines.shift()
|
|
const mergeParts = {}
|
|
const headerParts = {}
|
|
body = ''
|
|
footer = ''
|
|
|
|
const mergeMatch = merge.match(options.mergePattern)
|
|
if (mergeMatch && options.mergePattern) {
|
|
merge = mergeMatch[0]
|
|
|
|
header = lines.shift()
|
|
while (header !== undefined && !header.trim()) {
|
|
header = lines.shift()
|
|
}
|
|
if (!header) {
|
|
header = ''
|
|
}
|
|
|
|
_.forEach(mergeCorrespondence, function (partName, index) {
|
|
const partValue = mergeMatch[index + 1] || null
|
|
mergeParts[partName] = partValue
|
|
})
|
|
} else {
|
|
header = merge
|
|
merge = null
|
|
|
|
_.forEach(mergeCorrespondence, function (partName) {
|
|
mergeParts[partName] = null
|
|
})
|
|
}
|
|
|
|
const headerMatch = header.match(options.headerPattern)
|
|
if (headerMatch) {
|
|
_.forEach(headerCorrespondence, function (partName, index) {
|
|
const partValue = headerMatch[index + 1] || null
|
|
headerParts[partName] = partValue
|
|
})
|
|
} else {
|
|
_.forEach(headerCorrespondence, function (partName) {
|
|
headerParts[partName] = null
|
|
})
|
|
}
|
|
|
|
Array.prototype.push.apply(references, getReferences(header, {
|
|
references: regex.references,
|
|
referenceParts: regex.referenceParts
|
|
}))
|
|
|
|
// body or footer
|
|
_.forEach(lines, function (line) {
|
|
if (options.fieldPattern) {
|
|
const fieldMatch = options.fieldPattern.exec(line)
|
|
|
|
if (fieldMatch) {
|
|
currentProcessedField = fieldMatch[1]
|
|
|
|
return
|
|
}
|
|
|
|
if (currentProcessedField) {
|
|
otherFields[currentProcessedField] = append(otherFields[currentProcessedField], line)
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
let referenceMatched
|
|
|
|
// this is a new important note
|
|
const notesMatch = line.match(regex.notes)
|
|
if (notesMatch) {
|
|
continueNote = true
|
|
isBody = false
|
|
footer = append(footer, line)
|
|
|
|
const note = {
|
|
title: notesMatch[1],
|
|
text: notesMatch[2]
|
|
}
|
|
|
|
notes.push(note)
|
|
|
|
return
|
|
}
|
|
|
|
const lineReferences = getReferences(line, {
|
|
references: regex.references,
|
|
referenceParts: regex.referenceParts
|
|
})
|
|
|
|
if (lineReferences.length > 0) {
|
|
isBody = false
|
|
referenceMatched = true
|
|
continueNote = false
|
|
}
|
|
|
|
Array.prototype.push.apply(references, lineReferences)
|
|
|
|
if (referenceMatched) {
|
|
footer = append(footer, line)
|
|
|
|
return
|
|
}
|
|
|
|
if (continueNote) {
|
|
notes[notes.length - 1].text = append(notes[notes.length - 1].text, line)
|
|
footer = append(footer, line)
|
|
|
|
return
|
|
}
|
|
|
|
if (isBody) {
|
|
body = append(body, line)
|
|
} else {
|
|
footer = append(footer, line)
|
|
}
|
|
})
|
|
|
|
if (options.breakingHeaderPattern && notes.length === 0) {
|
|
const breakingHeader = header.match(options.breakingHeaderPattern)
|
|
if (breakingHeader) {
|
|
const noteText = breakingHeader[3] // the description of the change.
|
|
notes.push({
|
|
title: 'BREAKING CHANGE',
|
|
text: noteText
|
|
})
|
|
}
|
|
}
|
|
|
|
while ((mentionsMatch = regex.mentions.exec(raw))) {
|
|
mentions.push(mentionsMatch[1])
|
|
}
|
|
|
|
// does this commit revert any other commit?
|
|
const revertMatch = raw.match(options.revertPattern)
|
|
if (revertMatch) {
|
|
revert = {}
|
|
_.forEach(revertCorrespondence, function (partName, index) {
|
|
const partValue = revertMatch[index + 1] || null
|
|
revert[partName] = partValue
|
|
})
|
|
} else {
|
|
revert = null
|
|
}
|
|
|
|
_.map(notes, function (note) {
|
|
note.text = trimOffNewlines(note.text)
|
|
|
|
return note
|
|
})
|
|
|
|
const msg = _.merge(headerParts, mergeParts, {
|
|
merge: merge,
|
|
header: header,
|
|
body: body ? trimOffNewlines(body) : null,
|
|
footer: footer ? trimOffNewlines(footer) : null,
|
|
notes: notes,
|
|
references: references,
|
|
mentions: mentions,
|
|
revert: revert
|
|
}, otherFields)
|
|
|
|
return msg
|
|
}
|
|
|
|
module.exports = parser
|
|
|