feat(parser): implement RawMessage

The NewRawMessage function returns a RawMessage struct, which has broken
the given commit message down into separate lines, and also grouped the
lines into paragraphs.

This should make it easier to implement proper conventional commit
parser, linter, and formatter.
This commit is contained in:
2021-08-08 20:26:55 +01:00
parent 99a28a0346
commit ce4b06f67c
8 changed files with 1771 additions and 0 deletions

594
line_test.go Normal file
View File

@@ -0,0 +1,594 @@
package conventionalcommit
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewLines(t *testing.T) {
tests := []struct {
name string
content []byte
want Lines
}{
{
name: "nil",
content: nil,
want: Lines{},
},
{
name: "empty",
content: []byte{},
want: Lines{},
},
{
name: "single line without trailing linebreak",
content: []byte("hello world"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte{},
},
},
},
{
name: "single line with trailing LF",
content: []byte("hello world\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "single line with trailing CRLF",
content: []byte("hello world\r\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r\n"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "single line with trailing CR",
content: []byte("hello world\r"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by LF",
content: []byte("hello world\nfoo\nbar"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\n"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by LF with trailing LF",
content: []byte("hello world\nfoo\nbar\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\n"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte("\n"),
},
{
Number: 4,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by CRLF",
content: []byte("hello world\r\nfoo\r\nbar"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r\n"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\r\n"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by CRLF with trailing CRLF",
content: []byte("hello world\r\nfoo\r\nbar\r\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r\n"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\r\n"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte("\r\n"),
},
{
Number: 4,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by CR",
content: []byte("hello world\rfoo\rbar"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\r"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by CR with trailing CR",
content: []byte("hello world\rfoo\rbar\r"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\r"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte("\r"),
},
{
Number: 4,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by mixed break types",
content: []byte("hello\nworld\r\nfoo\rbar"),
want: Lines{
{
Number: 1,
Content: []byte("hello"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("world"),
Break: []byte("\r\n"),
},
{
Number: 3,
Content: []byte("foo"),
Break: []byte("\r"),
},
{
Number: 4,
Content: []byte("bar"),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by mixed break types with " +
"trailing LF",
content: []byte("hello\nworld\r\nfoo\rbar\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("world"),
Break: []byte("\r\n"),
},
{
Number: 3,
Content: []byte("foo"),
Break: []byte("\r"),
},
{
Number: 4,
Content: []byte("bar"),
Break: []byte("\n"),
},
{
Number: 5,
Content: []byte(""),
Break: []byte{},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewLines(tt.content)
assert.Equal(t, tt.want, got)
})
}
}
var linesBytesTestCases = []struct {
name string
lines Lines
want []byte
}{
{
name: "single line",
lines: Lines{
{
Number: 1,
Content: []byte("hello world"),
},
},
want: []byte("hello world"),
},
{
name: "single line with trailing LF",
lines: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
want: []byte("hello world\n"),
},
{
name: "single line with trailing CRLF",
lines: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r\n"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
want: []byte("hello world\r\n"),
},
{
name: "single line with trailing CR",
lines: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
want: []byte("hello world\r"),
},
{
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,
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("Nullam libero mauris, dictum id, arcu."),
Break: []byte("\n"),
},
{
Number: 12,
Content: []byte(""),
Break: []byte{},
},
},
want: []byte(
"Aliquam feugiat tellus ut neque.\n" +
"Sed bibendum.\n" +
"Nullam libero mauris, consequat.\n" +
"\n" +
"Integer placerat tristique nisl.\n" +
"Etiam vel neque nec dui bibendum.\n" +
"\n" +
"\n" +
"Nullam libero mauris, dictum id, arcu.\n",
),
},
{
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,
Content: []byte(""),
Break: []byte("\r\n"),
},
{
Number: 7,
Content: []byte("Integer placerat tristique nisl."),
Break: []byte("\r\n"),
},
{
Number: 8,
Content: []byte("Etiam vel neque nec dui bibendum."),
Break: []byte("\r\n"),
},
{
Number: 9,
Content: []byte(""),
Break: []byte("\r\n"),
},
{
Number: 10,
Content: []byte(""),
Break: []byte("\r\n"),
},
{
Number: 11,
Content: []byte("Nullam libero mauris, dictum id, arcu."),
Break: []byte("\r\n"),
},
{
Number: 12,
Content: []byte(""),
Break: []byte{},
},
},
want: []byte(
"Aliquam feugiat tellus ut neque.\r\n" +
"Sed bibendum.\r\n" +
"Nullam libero mauris, consequat.\r\n" +
"\r\n" +
"Integer placerat tristique nisl.\r\n" +
"Etiam vel neque nec dui bibendum.\r\n" +
"\r\n" +
"\r\n" +
"Nullam libero mauris, dictum id, arcu.\r\n",
),
},
{
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,
Content: []byte(""),
Break: []byte("\r"),
},
{
Number: 7,
Content: []byte("Integer placerat tristique nisl."),
Break: []byte("\r"),
},
{
Number: 8,
Content: []byte("Etiam vel neque nec dui bibendum."),
Break: []byte("\r"),
},
{
Number: 9,
Content: []byte(""),
Break: []byte("\r"),
},
{
Number: 10,
Content: []byte(""),
Break: []byte("\r"),
},
{
Number: 11,
Content: []byte("Nullam libero mauris, dictum id, arcu."),
Break: []byte("\r"),
},
{
Number: 12,
Content: []byte(""),
Break: []byte{},
},
},
want: []byte(
"Aliquam feugiat tellus ut neque.\r" +
"Sed bibendum.\r" +
"Nullam libero mauris, consequat.\r" +
"\r" +
"Integer placerat tristique nisl.\r" +
"Etiam vel neque nec dui bibendum.\r" +
"\r" +
"\r" +
"Nullam libero mauris, dictum id, arcu.\r",
),
},
}
func TestLines_Bytes(t *testing.T) {
for _, tt := range linesBytesTestCases {
t.Run(tt.name, func(t *testing.T) {
got := tt.lines.Bytes()
assert.Equal(t, tt.want, got)
})
}
}
func BenchmarkLines_Bytes(b *testing.B) {
for _, tt := range linesBytesTestCases {
b.Run(tt.name, func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = tt.lines.Bytes()
}
})
}
}
func TestLines_String(t *testing.T) {
for _, tt := range linesBytesTestCases {
t.Run(tt.name, func(t *testing.T) {
got := tt.lines.String()
assert.Equal(t, string(tt.want), got)
})
}
}
func BenchmarkLines_String(b *testing.B) {
for _, tt := range linesBytesTestCases {
b.Run(tt.name, func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = tt.lines.String()
}
})
}
}