mirror of
https://github.com/romdo/go-conventionalcommit.git
synced 2026-02-19 08:06:41 +00:00
refactor(parser): rework RawMessage into Buffer
The old RawMessage implementation effectively brute forced the initial processing of a comment message by breaking it down into lines, and grouping them into paragraphs. This is useful, but, we actually only need the first paragraph, the last paragraph, and then everything between. So there's no need to break down the message into each paragraph. In theory, the Buffer implementation is more performant than RawMessage was, but most importantly I think it will be easier to work with it.
This commit is contained in:
215
buffer.go
Normal file
215
buffer.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package conventionalcommit
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// footerToken will match against all variations of Conventional Commit footer
|
||||
// formats.
|
||||
//
|
||||
// Examples of valid footer tokens:
|
||||
//
|
||||
// Approved-by: John Carter
|
||||
// ReviewdBy: Noctis
|
||||
// Fixes #49
|
||||
// Reverts #SOL-42
|
||||
// BREAKING CHANGE: Flux capacitor no longer exists.
|
||||
// BREAKING-CHANGE: Time will flow backwads
|
||||
//
|
||||
// Examples of invalid footer tokens:
|
||||
//
|
||||
// Approved-by:
|
||||
// Approved-by:John Carter
|
||||
// Approved by: John Carter
|
||||
// ReviewdBy: Noctis
|
||||
// Fixes#49
|
||||
// Fixes #
|
||||
// Fixes 49
|
||||
// BREAKING CHANGE:Flux capacitor no longer exists.
|
||||
// Breaking Change: Flux capacitor no longer exists.
|
||||
// Breaking-Change: Time will flow backwads
|
||||
//
|
||||
var footerToken = regexp.MustCompile(
|
||||
`^(?:([\w-]+)\s+(#.+)|([\w-]+|BREAKING[\s-]CHANGE):\s+(.+))$`,
|
||||
)
|
||||
|
||||
// Buffer represents a commit message in a more structured form than a simple
|
||||
// string or byte slice. This makes it easier to process a message for the
|
||||
// purposes of extracting detailed information, linting, and formatting.
|
||||
//
|
||||
// The commit message is conceptually broken down into two three separate
|
||||
// sections:
|
||||
//
|
||||
// - Head section holds the commit message subject/description, along with type
|
||||
// and scope for conventional commits. The head section should only ever be a
|
||||
// single line according to git convention, but Buffer supports multi-line
|
||||
// headers so they can be parsed and handled as needed.
|
||||
//
|
||||
// - Body section holds the rest of the message. Except if the last paragraph
|
||||
// starts with a footer token, then the last paragraph is omitted from the
|
||||
// body section.
|
||||
//
|
||||
// - Foot section holds conventional commit footers. It is always the last
|
||||
// paragraph of a commit message, and is only considered to be the foot
|
||||
// section if the first line of the paragraph beings with a footer token.
|
||||
//
|
||||
// Each section is returned as a Lines type, which provides per-line access to
|
||||
// the text within the section.
|
||||
type Buffer struct {
|
||||
// firstLine is the lines offset for the first line which contains any
|
||||
// non-whitespace character.
|
||||
firstLine int
|
||||
|
||||
// lastLine is the lines offset for the last line which contains any
|
||||
// non-whitespace character.
|
||||
lastLine int
|
||||
|
||||
// headLen is the number of lines that the headLen section (first paragraph)
|
||||
// spans.
|
||||
headLen int
|
||||
|
||||
// footLen is the number of lines that the footLen section (last paragraph)
|
||||
// spans.
|
||||
footLen int
|
||||
|
||||
// lines is a list of all individual lines of text in the commit message,
|
||||
// which also includes the original line number, making it easy to pass a
|
||||
// single Line around while still knowing where in the original commit
|
||||
// message it belongs.
|
||||
lines Lines
|
||||
}
|
||||
|
||||
// NewBuffer returns a Buffer, with the given commit message broken down into
|
||||
// individual lines of text, with sequential non-empty lines grouped into
|
||||
// paragraphs.
|
||||
func NewBuffer(message []byte) *Buffer {
|
||||
buf := &Buffer{
|
||||
lines: Lines{},
|
||||
}
|
||||
|
||||
if len(message) == 0 {
|
||||
return buf
|
||||
}
|
||||
|
||||
buf.lines = NewLines(message)
|
||||
// Find fist non-whitespace line.
|
||||
if i := buf.lines.FirstTextIndex(); i > -1 {
|
||||
buf.firstLine = i
|
||||
}
|
||||
|
||||
// Find last non-whitespace line.
|
||||
if i := buf.lines.LastTextIndex(); i > -1 {
|
||||
buf.lastLine = i
|
||||
}
|
||||
|
||||
// Determine number of lines in first paragraph (head section).
|
||||
for i := buf.firstLine; i <= buf.lastLine; i++ {
|
||||
if buf.lines[i].Blank() {
|
||||
break
|
||||
}
|
||||
buf.headLen++
|
||||
}
|
||||
|
||||
// Determine number of lines in the last paragraph.
|
||||
lastLen := 0
|
||||
for i := buf.lastLine; i > buf.firstLine+buf.headLen; i-- {
|
||||
if buf.lines[i].Blank() {
|
||||
break
|
||||
}
|
||||
lastLen++
|
||||
}
|
||||
|
||||
// If last paragraph starts with a Convention Commit footer token, it is the
|
||||
// foot section, otherwise it is part of the body.
|
||||
if lastLen > 0 {
|
||||
line := buf.lines[buf.lastLine-lastLen+1]
|
||||
if footerToken.Match(line.Content) {
|
||||
buf.footLen = lastLen
|
||||
}
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
// Head returns the first paragraph, defined as the first group of sequential
|
||||
// lines which contain any non-whitespace characters.
|
||||
func (s *Buffer) Head() Lines {
|
||||
return s.lines[s.firstLine : s.firstLine+s.headLen]
|
||||
}
|
||||
|
||||
// Body returns all lines between the first and last paragraphs. If the body is
|
||||
// surrounded by multiple empty lines, they will be removed, ensuring first and
|
||||
// last line of body is not a blank whitespace line.
|
||||
func (s *Buffer) Body() Lines {
|
||||
if s.firstLine == s.lastLine {
|
||||
return Lines{}
|
||||
}
|
||||
|
||||
first := s.firstLine + s.headLen + 1
|
||||
last := s.lastLine + 1
|
||||
|
||||
if s.footLen > 0 {
|
||||
last -= s.footLen
|
||||
}
|
||||
|
||||
return s.lines[first:last].Trim()
|
||||
}
|
||||
|
||||
// Head returns the last paragraph, defined as the last group of sequential
|
||||
// lines which contain any non-whitespace characters.
|
||||
func (s *Buffer) Foot() Lines {
|
||||
if s.footLen == 0 {
|
||||
return Lines{}
|
||||
}
|
||||
|
||||
return s.lines[s.lastLine-s.footLen+1 : s.lastLine+1]
|
||||
}
|
||||
|
||||
// Lines returns all lines with any blank lines from the beginning and end of
|
||||
// the buffer removed. Effectively all lines from the first to the last line
|
||||
// which contain any non-whitespace characters.
|
||||
func (s *Buffer) Lines() Lines {
|
||||
if s.lastLine+1 > len(s.lines) || (s.lastLine == 0 && s.lines[0].Blank()) {
|
||||
return Lines{}
|
||||
}
|
||||
|
||||
return s.lines[s.firstLine : s.lastLine+1]
|
||||
}
|
||||
|
||||
func (s *Buffer) LineCount() int {
|
||||
if s.headLen == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return (s.lastLine + 1) - s.firstLine
|
||||
}
|
||||
|
||||
// Bytes renders the Buffer back into a byte slice, without any leading or
|
||||
// trailing whitespace lines. Leading whitespace on the first line which
|
||||
// contains non-whitespace characters is retained. It is only whole lines
|
||||
// consisting of only whitespace which are excluded.
|
||||
func (s *Buffer) Bytes() []byte {
|
||||
return s.Lines().Bytes()
|
||||
}
|
||||
|
||||
// String renders the Buffer back into a string, without any leading or trailing
|
||||
// whitespace lines. Leading whitespace on the first line which contains
|
||||
// non-whitespace characters is retained. It is only whole lines consisting of
|
||||
// only whitespace which are excluded.
|
||||
func (s *Buffer) String() string {
|
||||
return s.Lines().String()
|
||||
}
|
||||
|
||||
// BytesRaw renders the Buffer back into a byte slice which is identical to the
|
||||
// original input byte slice given to NewBuffer. This includes retaining the
|
||||
// original line break types for each line.
|
||||
func (s *Buffer) BytesRaw() []byte {
|
||||
return s.lines.Bytes()
|
||||
}
|
||||
|
||||
// StringRaw renders the Buffer back into a string which is identical to the
|
||||
// original input byte slice given to NewBuffer. This includes retaining the
|
||||
// original line break types for each line.
|
||||
func (s *Buffer) StringRaw() string {
|
||||
return s.lines.String()
|
||||
}
|
||||
1107
buffer_test.go
Normal file
1107
buffer_test.go
Normal file
File diff suppressed because it is too large
Load Diff
76
line.go
76
line.go
@@ -1,5 +1,10 @@
|
||||
package conventionalcommit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
lf = 10 // linefeed ("\n") character
|
||||
cr = 13 // carriage return ("\r") character
|
||||
@@ -20,6 +25,17 @@ type Line struct {
|
||||
Break []byte
|
||||
}
|
||||
|
||||
// Empty returns true if line content has a length of zero.
|
||||
func (s *Line) Empty() bool {
|
||||
return len(s.Content) == 0
|
||||
}
|
||||
|
||||
// Blank returns true if line content has a length of zero after leading and
|
||||
// trailing white space has been trimmed.
|
||||
func (s *Line) Blank() bool {
|
||||
return len(bytes.TrimSpace(s.Content)) == 0
|
||||
}
|
||||
|
||||
// Lines is a slice of *Line types with some helper methods attached.
|
||||
type Lines []*Line
|
||||
|
||||
@@ -28,8 +44,9 @@ type Lines []*Line
|
||||
// basis.
|
||||
func NewLines(content []byte) Lines {
|
||||
r := Lines{}
|
||||
cLen := len(content)
|
||||
|
||||
if len(content) == 0 {
|
||||
if cLen == 0 {
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -37,12 +54,13 @@ func NewLines(content []byte) Lines {
|
||||
var breaks [][]int
|
||||
|
||||
// Locate each line break within content.
|
||||
for i := 0; i < len(content); i++ {
|
||||
if content[i] == lf {
|
||||
for i := 0; i < cLen; i++ {
|
||||
switch content[i] {
|
||||
case lf:
|
||||
breaks = append(breaks, []int{i, i + 1})
|
||||
} else if content[i] == cr {
|
||||
case cr:
|
||||
b := []int{i, i + 1}
|
||||
if i+1 < len(content) && content[i+1] == lf {
|
||||
if i+1 < cLen && content[i+1] == lf {
|
||||
b[1]++
|
||||
i++
|
||||
}
|
||||
@@ -76,6 +94,45 @@ func NewLines(content []byte) Lines {
|
||||
return r
|
||||
}
|
||||
|
||||
// FirstTextIndex returns the line offset of the first line which contains any
|
||||
// non-whitespace characters.
|
||||
func (s Lines) FirstTextIndex() int {
|
||||
for i, line := range s {
|
||||
if !line.Blank() {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// LastTextIndex returns the line offset of the last line which contains any
|
||||
// non-whitespace characters.
|
||||
func (s Lines) LastTextIndex() int {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if !s[i].Blank() {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// Trim returns a new Lines instance where all leading and trailing whitespace
|
||||
// lines have been removed, based on index values from FirstTextIndex() and
|
||||
// LastTextIndex().
|
||||
//
|
||||
// If there are no lines with non-whitespace characters, a empty Lines type is
|
||||
// returned.
|
||||
func (s Lines) Trim() Lines {
|
||||
start := s.FirstTextIndex()
|
||||
if start == -1 {
|
||||
return Lines{}
|
||||
}
|
||||
|
||||
return s[start : s.LastTextIndex()+1]
|
||||
}
|
||||
|
||||
// Bytes combines all Lines into a single byte slice, retaining the original
|
||||
// line break types for each line.
|
||||
func (s Lines) Bytes() []byte {
|
||||
@@ -100,3 +157,12 @@ func (s Lines) Bytes() []byte {
|
||||
func (s Lines) String() string {
|
||||
return string(s.Bytes())
|
||||
}
|
||||
|
||||
func (s Lines) Join(sep string) string {
|
||||
r := make([]string, 0, len(s))
|
||||
for _, line := range s {
|
||||
r = append(r, string(line.Content))
|
||||
}
|
||||
|
||||
return strings.Join(r, sep)
|
||||
}
|
||||
|
||||
487
line_test.go
487
line_test.go
@@ -6,6 +6,156 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLine_Empty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line *Line
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
line: &Line{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte(""),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "space only",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte(" "),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "tab only",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte("\t\t"),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "spaces and tabs",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte(" \t "),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte("foobar"),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "text with surrounding white space",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte(" foobar "),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.line.Empty()
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_Blank(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line *Line
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
line: &Line{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte(""),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "space only",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte(" "),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "tab only",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte("\t\t"),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "spaces and tabs",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte(" \t "),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte("foobar"),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "text with surrounding white space",
|
||||
line: &Line{
|
||||
Number: 1,
|
||||
Content: []byte(" foobar "),
|
||||
Break: []byte{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.line.Blank()
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLines(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -290,11 +440,67 @@ func TestNewLines(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var linesBytesTestCases = []struct {
|
||||
name string
|
||||
lines Lines
|
||||
want []byte
|
||||
var linesTestCases = []struct {
|
||||
name string
|
||||
lines Lines
|
||||
bytes []byte
|
||||
firstTextIndex int
|
||||
lastTextIndex int
|
||||
}{
|
||||
{
|
||||
name: "no lines",
|
||||
lines: Lines{},
|
||||
bytes: []byte(""),
|
||||
firstTextIndex: -1,
|
||||
lastTextIndex: -1,
|
||||
},
|
||||
{
|
||||
name: "empty line",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte(""),
|
||||
},
|
||||
},
|
||||
bytes: []byte(""),
|
||||
firstTextIndex: -1,
|
||||
lastTextIndex: -1,
|
||||
},
|
||||
{
|
||||
name: "whitespace line",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte(" "),
|
||||
},
|
||||
},
|
||||
bytes: []byte(" "),
|
||||
firstTextIndex: -1,
|
||||
lastTextIndex: -1,
|
||||
},
|
||||
{
|
||||
name: "multiple whitespace lines",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte(" "),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte("\t"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte(" "),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
bytes: []byte(" \n\t\n "),
|
||||
firstTextIndex: -1,
|
||||
lastTextIndex: -1,
|
||||
},
|
||||
{
|
||||
name: "single line",
|
||||
lines: Lines{
|
||||
@@ -303,7 +509,9 @@ var linesBytesTestCases = []struct {
|
||||
Content: []byte("hello world"),
|
||||
},
|
||||
},
|
||||
want: []byte("hello world"),
|
||||
bytes: []byte("hello world"),
|
||||
firstTextIndex: 0,
|
||||
lastTextIndex: 0,
|
||||
},
|
||||
{
|
||||
name: "single line with trailing LF",
|
||||
@@ -319,7 +527,9 @@ var linesBytesTestCases = []struct {
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []byte("hello world\n"),
|
||||
bytes: []byte("hello world\n"),
|
||||
firstTextIndex: 0,
|
||||
lastTextIndex: 0,
|
||||
},
|
||||
{
|
||||
name: "single line with trailing CRLF",
|
||||
@@ -335,7 +545,9 @@ var linesBytesTestCases = []struct {
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []byte("hello world\r\n"),
|
||||
bytes: []byte("hello world\r\n"),
|
||||
firstTextIndex: 0,
|
||||
lastTextIndex: 0,
|
||||
},
|
||||
{
|
||||
name: "single line with trailing CR",
|
||||
@@ -351,41 +563,53 @@ var linesBytesTestCases = []struct {
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []byte("hello world\r"),
|
||||
bytes: []byte("hello world\r"),
|
||||
firstTextIndex: 0,
|
||||
lastTextIndex: 0,
|
||||
},
|
||||
{
|
||||
name: "multi-line separated by LF",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("Aliquam feugiat tellus ut neque."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte("Sed bibendum."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte("Nullam libero mauris, consequat."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Number: 1,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 7,
|
||||
Number: 2,
|
||||
Content: []byte("Aliquam feugiat tellus ut neque."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("Sed bibendum."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte("Nullam libero mauris, consequat."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Content: []byte("Integer placerat tristique nisl."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Number: 7,
|
||||
Content: []byte("Etiam vel neque nec dui bibendum."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 9,
|
||||
Content: []byte(""),
|
||||
@@ -393,22 +617,18 @@ var linesBytesTestCases = []struct {
|
||||
},
|
||||
{
|
||||
Number: 10,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 11,
|
||||
Content: []byte("Nullam libero mauris, dictum id, arcu."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 12,
|
||||
Number: 11,
|
||||
Content: []byte(""),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []byte(
|
||||
"Aliquam feugiat tellus ut neque.\n" +
|
||||
bytes: []byte(
|
||||
"\n" +
|
||||
"Aliquam feugiat tellus ut neque.\n" +
|
||||
"Sed bibendum.\n" +
|
||||
"Nullam libero mauris, consequat.\n" +
|
||||
"\n" +
|
||||
@@ -418,40 +638,52 @@ var linesBytesTestCases = []struct {
|
||||
"\n" +
|
||||
"Nullam libero mauris, dictum id, arcu.\n",
|
||||
),
|
||||
firstTextIndex: 1,
|
||||
lastTextIndex: 9,
|
||||
},
|
||||
{
|
||||
name: "multi-line separated by CRLF",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("Aliquam feugiat tellus ut neque."),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte("Sed bibendum."),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte("Nullam libero mauris, consequat."),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Number: 1,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 7,
|
||||
Number: 2,
|
||||
Content: []byte("Aliquam feugiat tellus ut neque."),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("Sed bibendum."),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte("Nullam libero mauris, consequat."),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Content: []byte("Integer placerat tristique nisl."),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Number: 7,
|
||||
Content: []byte("Etiam vel neque nec dui bibendum."),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 9,
|
||||
Content: []byte(""),
|
||||
@@ -459,22 +691,18 @@ var linesBytesTestCases = []struct {
|
||||
},
|
||||
{
|
||||
Number: 10,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 11,
|
||||
Content: []byte("Nullam libero mauris, dictum id, arcu."),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 12,
|
||||
Number: 11,
|
||||
Content: []byte(""),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []byte(
|
||||
"Aliquam feugiat tellus ut neque.\r\n" +
|
||||
bytes: []byte(
|
||||
"\r\n" +
|
||||
"Aliquam feugiat tellus ut neque.\r\n" +
|
||||
"Sed bibendum.\r\n" +
|
||||
"Nullam libero mauris, consequat.\r\n" +
|
||||
"\r\n" +
|
||||
@@ -484,40 +712,52 @@ var linesBytesTestCases = []struct {
|
||||
"\r\n" +
|
||||
"Nullam libero mauris, dictum id, arcu.\r\n",
|
||||
),
|
||||
firstTextIndex: 1,
|
||||
lastTextIndex: 9,
|
||||
},
|
||||
{
|
||||
name: "multi-line separated by CR",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("Aliquam feugiat tellus ut neque."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte("Sed bibendum."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte("Nullam libero mauris, consequat."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Number: 1,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 7,
|
||||
Number: 2,
|
||||
Content: []byte("Aliquam feugiat tellus ut neque."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("Sed bibendum."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte("Nullam libero mauris, consequat."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Content: []byte("Integer placerat tristique nisl."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Number: 7,
|
||||
Content: []byte("Etiam vel neque nec dui bibendum."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 9,
|
||||
Content: []byte(""),
|
||||
@@ -525,22 +765,18 @@ var linesBytesTestCases = []struct {
|
||||
},
|
||||
{
|
||||
Number: 10,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 11,
|
||||
Content: []byte("Nullam libero mauris, dictum id, arcu."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 12,
|
||||
Number: 11,
|
||||
Content: []byte(""),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []byte(
|
||||
"Aliquam feugiat tellus ut neque.\r" +
|
||||
bytes: []byte(
|
||||
"\r" +
|
||||
"Aliquam feugiat tellus ut neque.\r" +
|
||||
"Sed bibendum.\r" +
|
||||
"Nullam libero mauris, consequat.\r" +
|
||||
"\r" +
|
||||
@@ -550,21 +786,88 @@ var linesBytesTestCases = []struct {
|
||||
"\r" +
|
||||
"Nullam libero mauris, dictum id, arcu.\r",
|
||||
),
|
||||
firstTextIndex: 1,
|
||||
lastTextIndex: 9,
|
||||
},
|
||||
}
|
||||
|
||||
func TestLines_FirstTextIndex(t *testing.T) {
|
||||
for _, tt := range linesTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.lines.FirstTextIndex()
|
||||
|
||||
assert.Equal(t, tt.firstTextIndex, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLines_FirstTextIndex(b *testing.B) {
|
||||
for _, tt := range linesTestCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = tt.lines.FirstTextIndex()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLines_LastTextIndex(t *testing.T) {
|
||||
for _, tt := range linesTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.lines.LastTextIndex()
|
||||
|
||||
assert.Equal(t, tt.lastTextIndex, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLines_LastTextIndex(b *testing.B) {
|
||||
for _, tt := range linesTestCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = tt.lines.LastTextIndex()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLines_Trim(t *testing.T) {
|
||||
for _, tt := range linesTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
want := Lines{}
|
||||
if tt.firstTextIndex != -1 {
|
||||
want = tt.lines[tt.firstTextIndex : tt.lastTextIndex+1]
|
||||
}
|
||||
|
||||
got := tt.lines.Trim()
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLines_Trim(b *testing.B) {
|
||||
for _, tt := range linesTestCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = tt.lines.Trim()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLines_Bytes(t *testing.T) {
|
||||
for _, tt := range linesBytesTestCases {
|
||||
for _, tt := range linesTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.lines.Bytes()
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
assert.Equal(t, tt.bytes, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLines_Bytes(b *testing.B) {
|
||||
for _, tt := range linesBytesTestCases {
|
||||
for _, tt := range linesTestCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = tt.lines.Bytes()
|
||||
@@ -574,17 +877,17 @@ func BenchmarkLines_Bytes(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestLines_String(t *testing.T) {
|
||||
for _, tt := range linesBytesTestCases {
|
||||
for _, tt := range linesTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.lines.String()
|
||||
|
||||
assert.Equal(t, string(tt.want), got)
|
||||
assert.Equal(t, string(tt.bytes), got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLines_String(b *testing.B) {
|
||||
for _, tt := range linesBytesTestCases {
|
||||
for _, tt := range linesTestCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = tt.lines.String()
|
||||
|
||||
30
paragraph.go
30
paragraph.go
@@ -1,30 +0,0 @@
|
||||
package conventionalcommit
|
||||
|
||||
import "bytes"
|
||||
|
||||
// Paragraph represents a textual paragraph defined as; A continuous sequence of
|
||||
// textual lines which are not empty or and do not consist of only whitespace.
|
||||
type Paragraph struct {
|
||||
// Lines is a list of lines which collectively form a paragraph.
|
||||
Lines Lines
|
||||
}
|
||||
|
||||
func NewParagraphs(lines Lines) []*Paragraph {
|
||||
r := []*Paragraph{}
|
||||
|
||||
paragraph := &Paragraph{Lines: Lines{}}
|
||||
for _, line := range lines {
|
||||
if len(bytes.TrimSpace(line.Content)) > 0 {
|
||||
paragraph.Lines = append(paragraph.Lines, line)
|
||||
} else if len(paragraph.Lines) > 0 {
|
||||
r = append(r, paragraph)
|
||||
paragraph = &Paragraph{Lines: Lines{}}
|
||||
}
|
||||
}
|
||||
|
||||
if len(paragraph.Lines) > 0 {
|
||||
r = append(r, paragraph)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
package conventionalcommit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewParagraphs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
lines Lines
|
||||
want []*Paragraph
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
lines: nil,
|
||||
want: []*Paragraph{},
|
||||
},
|
||||
{
|
||||
name: "no lines",
|
||||
lines: Lines{},
|
||||
want: []*Paragraph{},
|
||||
},
|
||||
{
|
||||
name: "single empty line",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte{},
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []*Paragraph{},
|
||||
},
|
||||
{
|
||||
name: "multiple empty lines",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte{},
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte{},
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte{},
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []*Paragraph{},
|
||||
},
|
||||
{
|
||||
name: "single whitespace line",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("\t "),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []*Paragraph{},
|
||||
},
|
||||
{
|
||||
name: "multiple whitespace lines",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte{},
|
||||
Break: []byte("\t "),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte{},
|
||||
Break: []byte("\t "),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("\t "),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []*Paragraph{},
|
||||
},
|
||||
{
|
||||
name: "single line",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("hello world"),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("hello world"),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple lines",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("hello world"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte("foo bar"),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("hello world"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte("foo bar"),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple lines with trailing line break",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("hello world"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte("foo bar"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte(""),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("hello world"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte("foo bar"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple paragraphs with excess blank lines",
|
||||
lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte("\t "),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("Aliquam feugiat tellus ut neque."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte("Sed bibendum."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte("Nullam libero mauris, consequat."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 7,
|
||||
Content: []byte("Integer placerat tristique nisl."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Content: []byte("Etiam vel neque nec dui bibendum."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 9,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 10,
|
||||
Content: []byte(" "),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 11,
|
||||
Content: []byte("\t\t"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 12,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 13,
|
||||
Content: []byte("Donec hendrerit tempor tellus."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 14,
|
||||
Content: []byte("In id erat non orci commodo lobortis."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 15,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 16,
|
||||
Content: []byte(" "),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 17,
|
||||
Content: []byte("\t\t"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 18,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 18,
|
||||
Content: []byte(""),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
want: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("Aliquam feugiat tellus ut neque."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte("Sed bibendum."),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte("Nullam libero mauris, consequat."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 7,
|
||||
Content: []byte("Integer placerat tristique nisl."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Content: []byte(
|
||||
"Etiam vel neque nec dui bibendum.",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 13,
|
||||
Content: []byte("Donec hendrerit tempor tellus."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 14,
|
||||
Content: []byte(
|
||||
"In id erat non orci commodo lobortis.",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := NewParagraphs(tt.lines)
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package conventionalcommit
|
||||
|
||||
// RawMessage represents a commit message in a more structured form than a
|
||||
// simple string or byte slice. This makes it easier to process a message for
|
||||
// the purposes of extracting detailed information, linting, and formatting.
|
||||
type RawMessage struct {
|
||||
// Lines is a list of all individual lines of text in the commit message,
|
||||
// which also includes the original line number, making it easy to pass a
|
||||
// single Line around while still knowing where in the original commit
|
||||
// message it belongs.
|
||||
Lines Lines
|
||||
|
||||
// Paragraphs is a list of textual paragraphs in the commit message. A
|
||||
// paragraph is defined as any continuous sequence of lines which are not
|
||||
// empty or consist of only whitespace.
|
||||
Paragraphs []*Paragraph
|
||||
}
|
||||
|
||||
// NewRawMessage returns a RawMessage, with the given commit message broken down
|
||||
// into individual lines of text, with sequential non-empty lines grouped into
|
||||
// paragraphs.
|
||||
func NewRawMessage(message []byte) *RawMessage {
|
||||
r := &RawMessage{
|
||||
Lines: Lines{},
|
||||
Paragraphs: []*Paragraph{},
|
||||
}
|
||||
|
||||
if len(message) == 0 {
|
||||
return r
|
||||
}
|
||||
|
||||
r.Lines = NewLines(message)
|
||||
r.Paragraphs = NewParagraphs(r.Lines)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Bytes renders the RawMessage back into a byte slice which is identical to the
|
||||
// original input byte slice given to NewRawMessage. This includes retaining the
|
||||
// original line break types for each line.
|
||||
func (s *RawMessage) Bytes() []byte {
|
||||
return s.Lines.Bytes()
|
||||
}
|
||||
|
||||
// String renders the RawMessage back into a string which is identical to the
|
||||
// original input byte slice given to NewRawMessage. This includes retaining the
|
||||
// original line break types for each line.
|
||||
func (s *RawMessage) String() string {
|
||||
return s.Lines.String()
|
||||
}
|
||||
@@ -1,641 +0,0 @@
|
||||
package conventionalcommit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var rawMessageTestCases = []struct {
|
||||
name string
|
||||
bytes []byte
|
||||
rawMessage *RawMessage
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
bytes: nil,
|
||||
rawMessage: &RawMessage{
|
||||
Lines: Lines{},
|
||||
Paragraphs: []*Paragraph{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
bytes: []byte(""),
|
||||
rawMessage: &RawMessage{
|
||||
Lines: Lines{},
|
||||
Paragraphs: []*Paragraph{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single space",
|
||||
bytes: []byte(" "),
|
||||
rawMessage: &RawMessage{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte(" "),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
Paragraphs: []*Paragraph{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subject only",
|
||||
bytes: []byte("fix: a broken thing"),
|
||||
rawMessage: &RawMessage{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
Paragraphs: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subject and body",
|
||||
bytes: []byte("fix: a broken thing\n\nIt is now fixed."),
|
||||
rawMessage: &RawMessage{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("It is now fixed."),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
Paragraphs: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("It is now fixed."),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subject and body with CRLF line breaks",
|
||||
bytes: []byte("fix: a broken thing\r\n\r\nIt is now fixed."),
|
||||
rawMessage: &RawMessage{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("It is now fixed."),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
Paragraphs: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte("\r\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("It is now fixed."),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subject and body with CR line breaks",
|
||||
bytes: []byte("fix: a broken thing\r\rIt is now fixed."),
|
||||
rawMessage: &RawMessage{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("It is now fixed."),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
Paragraphs: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte("\r"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("It is now fixed."),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "separated by whitespace line",
|
||||
bytes: []byte("fix: a broken thing\n \nIt is now fixed."),
|
||||
rawMessage: &RawMessage{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte(" "),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("It is now fixed."),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
Paragraphs: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: a broken thing"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte("It is now fixed."),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subject and long body",
|
||||
bytes: []byte(`fix: something broken
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec hendrerit
|
||||
tempor tellus. Donec pretium posuere tellus. Proin quam nisl, tincidunt et,
|
||||
mattis eget, convallis nec, purus. Cum sociis natoque penatibus et magnis dis
|
||||
parturient montes, nascetur ridiculous mus. Nulla posuere. Donec vitae dolor.
|
||||
Nullam tristique diam non turpis. Cras placerat accumsan nulla. Nullam rutrum.
|
||||
Nam vestibulum accumsan nisl.
|
||||
|
||||
Nullam eu ante vel est convallis dignissim. Fusce suscipit, wisi nec facilisis
|
||||
facilisis, est dui fermentum leo, quis tempor ligula erat quis odio. Nunc porta
|
||||
vulputate tellus. Nunc rutrum turpis sed pede. Sed bibendum. Aliquam posuere.
|
||||
Nunc aliquet, augue nec adipiscing interdum, lacus tellus malesuada massa, quis
|
||||
varius mi purus non odio. Pellentesque condimentum, magna ut suscipit hendrerit,
|
||||
ipsum augue ornare nulla, non luctus diam neque sit amet urna. Curabitur
|
||||
vulputate vestibulum lorem. Fusce sagittis, libero non molestie mollis, magna
|
||||
orci ultrices dolor, at vulputate neque nulla lacinia eros. Sed id ligula quis
|
||||
est convallis tempor. Curabitur lacinia pulvinar nibh. Nam a sapien.
|
||||
|
||||
Phasellus lacus. Nam euismod tellus id erat.`),
|
||||
rawMessage: &RawMessage{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: something broken"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte(
|
||||
"Lorem ipsum dolor sit amet, consectetuer " +
|
||||
"adipiscing elit. Donec hendrerit"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte(
|
||||
"tempor tellus. Donec pretium posuere tellus. " +
|
||||
"Proin quam nisl, tincidunt et,"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte(
|
||||
"mattis eget, convallis nec, purus. Cum sociis " +
|
||||
"natoque penatibus et magnis dis"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Content: []byte(
|
||||
"parturient montes, nascetur ridiculous mus. " +
|
||||
"Nulla posuere. Donec vitae dolor."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 7,
|
||||
Content: []byte(
|
||||
"Nullam tristique diam non turpis. Cras placerat " +
|
||||
"accumsan nulla. Nullam rutrum."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Content: []byte(
|
||||
"Nam vestibulum accumsan nisl."),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 9,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 10,
|
||||
Content: []byte(
|
||||
"Nullam eu ante vel est convallis dignissim. " +
|
||||
"Fusce suscipit, wisi nec facilisis",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 11,
|
||||
Content: []byte(
|
||||
"facilisis, est dui fermentum leo, quis tempor " +
|
||||
"ligula erat quis odio. Nunc porta",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 12,
|
||||
Content: []byte(
|
||||
"vulputate tellus. Nunc rutrum turpis sed pede. " +
|
||||
"Sed bibendum. Aliquam posuere.",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 13,
|
||||
Content: []byte(
|
||||
"Nunc aliquet, augue nec adipiscing interdum, " +
|
||||
"lacus tellus malesuada massa, quis",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 14,
|
||||
Content: []byte(
|
||||
"varius mi purus non odio. Pellentesque " +
|
||||
"condimentum, magna ut suscipit hendrerit,",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 15,
|
||||
Content: []byte(
|
||||
"ipsum augue ornare nulla, non luctus diam neque " +
|
||||
"sit amet urna. Curabitur",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 16,
|
||||
Content: []byte(
|
||||
"vulputate vestibulum lorem. Fusce sagittis, " +
|
||||
"libero non molestie mollis, magna",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 17,
|
||||
Content: []byte(
|
||||
"orci ultrices dolor, at vulputate neque nulla " +
|
||||
"lacinia eros. Sed id ligula quis",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 18,
|
||||
Content: []byte(
|
||||
"est convallis tempor. Curabitur lacinia " +
|
||||
"pulvinar nibh. Nam a sapien.",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 19,
|
||||
Content: []byte(""),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 20,
|
||||
Content: []byte(
|
||||
"Phasellus lacus. Nam euismod tellus id erat.",
|
||||
),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
Paragraphs: []*Paragraph{
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 1,
|
||||
Content: []byte("fix: something broken"),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 3,
|
||||
Content: []byte(
|
||||
"Lorem ipsum dolor sit amet, " +
|
||||
"consectetuer adipiscing elit. Donec " +
|
||||
"hendrerit",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: []byte(
|
||||
"tempor tellus. Donec pretium posuere " +
|
||||
"tellus. Proin quam nisl, tincidunt " +
|
||||
"et,",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Content: []byte(
|
||||
"mattis eget, convallis nec, purus. Cum " +
|
||||
"sociis natoque penatibus et magnis " +
|
||||
"dis",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Content: []byte(
|
||||
"parturient montes, nascetur ridiculous " +
|
||||
"mus. Nulla posuere. Donec vitae " +
|
||||
"dolor.",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 7,
|
||||
Content: []byte(
|
||||
"Nullam tristique diam non turpis. Cras " +
|
||||
"placerat accumsan nulla. Nullam " +
|
||||
"rutrum.",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Content: []byte(
|
||||
"Nam vestibulum accumsan nisl.",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 10,
|
||||
Content: []byte(
|
||||
"Nullam eu ante vel est convallis " +
|
||||
"dignissim. Fusce suscipit, wisi nec " +
|
||||
"facilisis",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 11,
|
||||
Content: []byte(
|
||||
"facilisis, est dui fermentum leo, quis " +
|
||||
"tempor ligula erat quis odio. Nunc " +
|
||||
"porta",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 12,
|
||||
Content: []byte(
|
||||
"vulputate tellus. Nunc rutrum turpis " +
|
||||
"sed pede. Sed bibendum. Aliquam " +
|
||||
"posuere.",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 13,
|
||||
Content: []byte(
|
||||
"Nunc aliquet, augue nec adipiscing " +
|
||||
"interdum, lacus tellus malesuada " +
|
||||
"massa, quis",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 14,
|
||||
Content: []byte(
|
||||
"varius mi purus non odio. Pellentesque " +
|
||||
"condimentum, magna ut suscipit " +
|
||||
"hendrerit,",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 15,
|
||||
Content: []byte(
|
||||
"ipsum augue ornare nulla, non luctus " +
|
||||
"diam neque sit amet urna. Curabitur",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 16,
|
||||
Content: []byte(
|
||||
"vulputate vestibulum lorem. Fusce " +
|
||||
"sagittis, libero non molestie " +
|
||||
"mollis, magna",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 17,
|
||||
Content: []byte(
|
||||
"orci ultrices dolor, at vulputate neque " +
|
||||
"nulla lacinia eros. Sed id ligula " +
|
||||
"quis",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
{
|
||||
Number: 18,
|
||||
Content: []byte(
|
||||
"est convallis tempor. Curabitur lacinia " +
|
||||
"pulvinar nibh. Nam a sapien.",
|
||||
),
|
||||
Break: []byte("\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lines: Lines{
|
||||
{
|
||||
Number: 20,
|
||||
Content: []byte(
|
||||
"Phasellus lacus. Nam euismod tellus id " +
|
||||
"erat.",
|
||||
),
|
||||
Break: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestNewRawMessage(t *testing.T) {
|
||||
for _, tt := range rawMessageTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := NewRawMessage(tt.bytes)
|
||||
|
||||
assert.Equal(t, tt.rawMessage, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewRawMessage(b *testing.B) {
|
||||
for _, tt := range rawMessageTestCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = NewRawMessage(tt.bytes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawMessage_Bytes(t *testing.T) {
|
||||
for _, tt := range rawMessageTestCases {
|
||||
if tt.bytes == nil {
|
||||
continue
|
||||
}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rawMessage.Bytes()
|
||||
|
||||
assert.Equal(t, tt.bytes, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRawMessage_Bytes(b *testing.B) {
|
||||
for _, tt := range rawMessageTestCases {
|
||||
if tt.bytes == nil {
|
||||
continue
|
||||
}
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = tt.rawMessage.Bytes()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawMessage_String(t *testing.T) {
|
||||
for _, tt := range rawMessageTestCases {
|
||||
if tt.bytes == nil {
|
||||
continue
|
||||
}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rawMessage.String()
|
||||
|
||||
assert.Equal(t, string(tt.bytes), got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRawMessage_String(b *testing.B) {
|
||||
for _, tt := range rawMessageTestCases {
|
||||
if tt.bytes == nil {
|
||||
continue
|
||||
}
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = tt.rawMessage.String()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user