mirror of
https://github.com/romdo/go-conventionalcommit.git
synced 2026-02-19 08:06:41 +00:00
183 lines
4.1 KiB
Go
183 lines
4.1 KiB
Go
package conventionalcommit
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
lf = 10 // ASCII linefeed ("\n") character.
|
|
cr = 13 // ASCII carriage return ("\r") character.
|
|
hash = 35 // ASCII hash ("#") character.
|
|
|
|
)
|
|
|
|
// Line represents a single line of text defined as; A continuous sequence of
|
|
// bytes which do not contain a \r (carriage return) or \n (line-feed) byte.
|
|
type Line struct {
|
|
// Line number within commit message, starting a 1 rather than 0, as
|
|
// text viewed in a text editor starts on line 1, not line 0.
|
|
Number int
|
|
|
|
// Content is the raw bytes that make up the text content in the line.
|
|
Content []byte
|
|
|
|
// Break is the linebreak type used at the end of the line. It will be one
|
|
// of "\n", "\r\n", "\r", or empty if it is the very last line.
|
|
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
|
|
}
|
|
|
|
// Comment returns true if line content is a commit comment, where the first
|
|
// non-whitespace character in the line is a hash (#).
|
|
func (s *Line) Comment() bool {
|
|
trimmed := bytes.TrimSpace(s.Content)
|
|
|
|
if len(trimmed) == 0 {
|
|
return false
|
|
}
|
|
|
|
return trimmed[0] == hash
|
|
}
|
|
|
|
// Lines is a slice of *Line types with some helper methods attached.
|
|
type Lines []*Line
|
|
|
|
// NewLines breaks the given byte slice down into a slice of Line structs,
|
|
// allowing easier inspection and manipulation of content on a line-by-line
|
|
// basis.
|
|
func NewLines(content []byte) Lines {
|
|
r := Lines{}
|
|
length := len(content)
|
|
|
|
if length == 0 {
|
|
return r
|
|
}
|
|
|
|
// List of start/end offsets for each line break.
|
|
var breaks [][]int
|
|
|
|
// Locate each line break within content.
|
|
for i := 0; i < length; i++ {
|
|
switch content[i] {
|
|
case lf:
|
|
breaks = append(breaks, []int{i, i + 1})
|
|
case cr:
|
|
b := []int{i, i + 1}
|
|
if i+1 < length && content[i+1] == lf {
|
|
b[1]++
|
|
i++
|
|
}
|
|
breaks = append(breaks, b)
|
|
}
|
|
}
|
|
|
|
// Return a single line if there are no line breaks.
|
|
if len(breaks) == 0 {
|
|
return Lines{{Number: 1, Content: content, Break: []byte{}}}
|
|
}
|
|
|
|
// Extract each line based on linebreak offsets.
|
|
offset := 0
|
|
for n, loc := range breaks {
|
|
r = append(r, &Line{
|
|
Number: n + 1,
|
|
Content: content[offset:loc[0]],
|
|
Break: content[loc[0]:loc[1]],
|
|
})
|
|
offset = loc[1]
|
|
}
|
|
|
|
// Extract final line
|
|
r = append(r, &Line{
|
|
Number: len(breaks) + 1,
|
|
Content: content[offset:],
|
|
Break: []byte{},
|
|
})
|
|
|
|
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 {
|
|
// Pre-calculate capacity of result byte slice.
|
|
size := 0
|
|
for _, l := range s {
|
|
size = size + len(l.Content) + len(l.Break)
|
|
}
|
|
|
|
b := make([]byte, 0, size)
|
|
|
|
for _, l := range s {
|
|
b = append(b, l.Content...)
|
|
b = append(b, l.Break...)
|
|
}
|
|
|
|
return b
|
|
}
|
|
|
|
// Bytes combines all Lines into a single string, retaining the original line
|
|
// break types for each line.
|
|
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)
|
|
}
|