123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- '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
|