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.

1037 lines
25 KiB

package token
import (
"fmt"
"strings"
)
// Character type for character
type Character byte
const (
// SequenceEntryCharacter character for sequence entry
SequenceEntryCharacter Character = '-'
// MappingKeyCharacter character for mapping key
MappingKeyCharacter = '?'
// MappingValueCharacter character for mapping value
MappingValueCharacter = ':'
// CollectEntryCharacter character for collect entry
CollectEntryCharacter = ','
// SequenceStartCharacter character for sequence start
SequenceStartCharacter = '['
// SequenceEndCharacter character for sequence end
SequenceEndCharacter = ']'
// MappingStartCharacter character for mapping start
MappingStartCharacter = '{'
// MappingEndCharacter character for mapping end
MappingEndCharacter = '}'
// CommentCharacter character for comment
CommentCharacter = '#'
// AnchorCharacter character for anchor
AnchorCharacter = '&'
// AliasCharacter character for alias
AliasCharacter = '*'
// TagCharacter character for tag
TagCharacter = '!'
// LiteralCharacter character for literal
LiteralCharacter = '|'
// FoldedCharacter character for folded
FoldedCharacter = '>'
// SingleQuoteCharacter character for single quote
SingleQuoteCharacter = '\''
// DoubleQuoteCharacter character for double quote
DoubleQuoteCharacter = '"'
// DirectiveCharacter character for directive
DirectiveCharacter = '%'
// SpaceCharacter character for space
SpaceCharacter = ' '
// LineBreakCharacter character for line break
LineBreakCharacter = '\n'
)
// Type type identifier for token
type Type int
const (
// UnknownType reserve for invalid type
UnknownType Type = iota
// DocumentHeaderType type for DocumentHeader token
DocumentHeaderType
// DocumentEndType type for DocumentEnd token
DocumentEndType
// SequenceEntryType type for SequenceEntry token
SequenceEntryType
// MappingKeyType type for MappingKey token
MappingKeyType
// MappingValueType type for MappingValue token
MappingValueType
// MergeKeyType type for MergeKey token
MergeKeyType
// CollectEntryType type for CollectEntry token
CollectEntryType
// SequenceStartType type for SequenceStart token
SequenceStartType
// SequenceEndType type for SequenceEnd token
SequenceEndType
// MappingStartType type for MappingStart token
MappingStartType
// MappingEndType type for MappingEnd token
MappingEndType
// CommentType type for Comment token
CommentType
// AnchorType type for Anchor token
AnchorType
// AliasType type for Alias token
AliasType
// TagType type for Tag token
TagType
// LiteralType type for Literal token
LiteralType
// FoldedType type for Folded token
FoldedType
// SingleQuoteType type for SingleQuote token
SingleQuoteType
// DoubleQuoteType type for DoubleQuote token
DoubleQuoteType
// DirectiveType type for Directive token
DirectiveType
// SpaceType type for Space token
SpaceType
// NullType type for Null token
NullType
// InfinityType type for Infinity token
InfinityType
// NanType type for Nan token
NanType
// IntegerType type for Integer token
IntegerType
// BinaryIntegerType type for BinaryInteger token
BinaryIntegerType
// OctetIntegerType type for OctetInteger token
OctetIntegerType
// HexIntegerType type for HexInteger token
HexIntegerType
// FloatType type for Float token
FloatType
// StringType type for String token
StringType
// BoolType type for Bool token
BoolType
)
// String type identifier to text
func (t Type) String() string {
switch t {
case UnknownType:
return "Unknown"
case DocumentHeaderType:
return "DocumentHeader"
case DocumentEndType:
return "DocumentEnd"
case SequenceEntryType:
return "SequenceEntry"
case MappingKeyType:
return "MappingKey"
case MappingValueType:
return "MappingValue"
case MergeKeyType:
return "MergeKey"
case CollectEntryType:
return "CollectEntry"
case SequenceStartType:
return "SequenceStart"
case SequenceEndType:
return "SequenceEnd"
case MappingStartType:
return "MappingStart"
case MappingEndType:
return "MappingEnd"
case CommentType:
return "Comment"
case AnchorType:
return "Anchor"
case AliasType:
return "Alias"
case TagType:
return "Tag"
case LiteralType:
return "Literal"
case FoldedType:
return "Folded"
case SingleQuoteType:
return "SingleQuote"
case DoubleQuoteType:
return "DoubleQuote"
case DirectiveType:
return "Directive"
case SpaceType:
return "Space"
case StringType:
return "String"
case BoolType:
return "Bool"
case IntegerType:
return "Integer"
case BinaryIntegerType:
return "BinaryInteger"
case OctetIntegerType:
return "OctetInteger"
case HexIntegerType:
return "HexInteger"
case FloatType:
return "Float"
case NullType:
return "Null"
case InfinityType:
return "Infinity"
case NanType:
return "Nan"
}
return ""
}
// CharacterType type for character category
type CharacterType int
const (
// CharacterTypeIndicator type of indicator character
CharacterTypeIndicator CharacterType = iota
// CharacterTypeWhiteSpace type of white space character
CharacterTypeWhiteSpace
// CharacterTypeMiscellaneous type of miscellaneous character
CharacterTypeMiscellaneous
// CharacterTypeEscaped type of escaped character
CharacterTypeEscaped
)
// String character type identifier to text
func (c CharacterType) String() string {
switch c {
case CharacterTypeIndicator:
return "Indicator"
case CharacterTypeWhiteSpace:
return "WhiteSpcae"
case CharacterTypeMiscellaneous:
return "Miscellaneous"
case CharacterTypeEscaped:
return "Escaped"
}
return ""
}
// Indicator type for indicator
type Indicator int
const (
// NotIndicator not indicator
NotIndicator Indicator = iota
// BlockStructureIndicator indicator for block structure ( '-', '?', ':' )
BlockStructureIndicator
// FlowCollectionIndicator indicator for flow collection ( '[', ']', '{', '}', ',' )
FlowCollectionIndicator
// CommentIndicator indicator for comment ( '#' )
CommentIndicator
// NodePropertyIndicator indicator for node property ( '!', '&', '*' )
NodePropertyIndicator
// BlockScalarIndicator indicator for block scalar ( '|', '>' )
BlockScalarIndicator
// QuotedScalarIndicator indicator for quoted scalar ( ''', '"' )
QuotedScalarIndicator
// DirectiveIndicator indicator for directive ( '%' )
DirectiveIndicator
// InvalidUseOfReservedIndicator indicator for invalid use of reserved keyword ( '@', '`' )
InvalidUseOfReservedIndicator
)
// String indicator to text
func (i Indicator) String() string {
switch i {
case NotIndicator:
return "NotIndicator"
case BlockStructureIndicator:
return "BlockStructure"
case FlowCollectionIndicator:
return "FlowCollection"
case CommentIndicator:
return "Comment"
case NodePropertyIndicator:
return "NodeProperty"
case BlockScalarIndicator:
return "BlockScalar"
case QuotedScalarIndicator:
return "QuotedScalar"
case DirectiveIndicator:
return "Directive"
case InvalidUseOfReservedIndicator:
return "InvalidUseOfReserved"
}
return ""
}
var (
reservedNullKeywords = []string{
"null",
"Null",
"NULL",
"~",
}
reservedBoolKeywords = []string{
"true",
"True",
"TRUE",
"false",
"False",
"FALSE",
}
reservedInfKeywords = []string{
".inf",
".Inf",
".INF",
"-.inf",
"-.Inf",
"-.INF",
}
reservedNanKeywords = []string{
".nan",
".NaN",
".NAN",
}
reservedKeywordMap = map[string]func(string, string, *Position) *Token{}
)
func reservedKeywordToken(typ Type, value, org string, pos *Position) *Token {
return &Token{
Type: typ,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
func init() {
for _, keyword := range reservedNullKeywords {
reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
return reservedKeywordToken(NullType, value, org, pos)
}
}
for _, keyword := range reservedBoolKeywords {
reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
return reservedKeywordToken(BoolType, value, org, pos)
}
}
for _, keyword := range reservedInfKeywords {
reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
return reservedKeywordToken(InfinityType, value, org, pos)
}
}
for _, keyword := range reservedNanKeywords {
reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
return reservedKeywordToken(NanType, value, org, pos)
}
}
}
// ReservedTagKeyword type of reserved tag keyword
type ReservedTagKeyword string
const (
// IntegerTag `!!int` tag
IntegerTag ReservedTagKeyword = "!!int"
// FloatTag `!!float` tag
FloatTag ReservedTagKeyword = "!!float"
// NullTag `!!null` tag
NullTag ReservedTagKeyword = "!!null"
// SequenceTag `!!seq` tag
SequenceTag ReservedTagKeyword = "!!seq"
// MappingTag `!!map` tag
MappingTag ReservedTagKeyword = "!!map"
// StringTag `!!str` tag
StringTag ReservedTagKeyword = "!!str"
// BinaryTag `!!binary` tag
BinaryTag ReservedTagKeyword = "!!binary"
// OrderedMapTag `!!omap` tag
OrderedMapTag ReservedTagKeyword = "!!omap"
// SetTag `!!set` tag
SetTag ReservedTagKeyword = "!!set"
// TimestampTag `!!timestamp` tag
TimestampTag ReservedTagKeyword = "!!timestamp"
)
var (
// ReservedTagKeywordMap map for reserved tag keywords
ReservedTagKeywordMap = map[ReservedTagKeyword]func(string, string, *Position) *Token{
IntegerTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
FloatTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
NullTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
SequenceTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
MappingTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
StringTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
BinaryTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
OrderedMapTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
SetTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
TimestampTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
}
)
type numType int
const (
numTypeNone numType = iota
numTypeBinary
numTypeOctet
numTypeHex
numTypeFloat
)
type numStat struct {
isNum bool
typ numType
}
func getNumberStat(str string) *numStat {
stat := &numStat{}
if str == "" {
return stat
}
if str == "-" || str == "." || str == "+" || str == "_" {
return stat
}
if str[0] == '_' {
return stat
}
dotFound := false
isNegative := false
isExponent := false
if str[0] == '-' {
isNegative = true
}
for idx, c := range str {
switch c {
case 'x':
if (isNegative && idx == 2) || (!isNegative && idx == 1) {
continue
}
case 'o':
if (isNegative && idx == 2) || (!isNegative && idx == 1) {
continue
}
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
continue
case 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F':
if (len(str) > 2 && str[0] == '0' && str[1] == 'x') ||
(len(str) > 3 && isNegative && str[1] == '0' && str[2] == 'x') {
// hex number
continue
}
if c == 'b' && ((isNegative && idx == 2) || (!isNegative && idx == 1)) {
// binary number
continue
}
if (c == 'e' || c == 'E') && dotFound {
// exponent
isExponent = true
continue
}
case '.':
if dotFound {
// multiple dot
return stat
}
dotFound = true
continue
case '-':
if idx == 0 || isExponent {
continue
}
case '+':
if idx == 0 || isExponent {
continue
}
case '_':
continue
}
return stat
}
stat.isNum = true
switch {
case dotFound:
stat.typ = numTypeFloat
case strings.HasPrefix(str, "0b") || strings.HasPrefix(str, "-0b"):
stat.typ = numTypeBinary
case strings.HasPrefix(str, "0x") || strings.HasPrefix(str, "-0x"):
stat.typ = numTypeHex
case strings.HasPrefix(str, "0o") || strings.HasPrefix(str, "-0o"):
stat.typ = numTypeOctet
case (len(str) > 1 && str[0] == '0') || (len(str) > 1 && str[0] == '-' && str[1] == '0'):
stat.typ = numTypeOctet
}
return stat
}
func looksLikeTimeValue(value string) bool {
for i, c := range value {
switch c {
case ':', '1', '2', '3', '4', '5', '6', '7', '8', '9':
continue
case '0':
if i == 0 {
return false
}
continue
}
return false
}
return true
}
// IsNeedQuoted whether need quote for passed string or not
func IsNeedQuoted(value string) bool {
if value == "" {
return true
}
if _, exists := reservedKeywordMap[value]; exists {
return true
}
if stat := getNumberStat(value); stat.isNum {
return true
}
first := value[0]
switch first {
case '*', '&', '[', '{', '}', ']', ',', '!', '|', '>', '%', '\'', '"':
return true
}
last := value[len(value)-1]
switch last {
case ':':
return true
}
if looksLikeTimeValue(value) {
return true
}
for i, c := range value {
switch c {
case '#', '\\':
return true
case ':':
if i+1 < len(value) && value[i+1] == ' ' {
return true
}
}
}
return false
}
// LiteralBlockHeader detect literal block scalar header
func LiteralBlockHeader(value string) string {
lbc := DetectLineBreakCharacter(value)
switch {
case !strings.Contains(value, lbc):
return ""
case strings.HasSuffix(value, fmt.Sprintf("%s%s", lbc, lbc)):
return "|+"
case strings.HasSuffix(value, lbc):
return "|"
default:
return "|-"
}
}
// New create reserved keyword token or number token and other string token
func New(value string, org string, pos *Position) *Token {
fn := reservedKeywordMap[value]
if fn != nil {
return fn(value, org, pos)
}
if stat := getNumberStat(value); stat.isNum {
tk := &Token{
Type: IntegerType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: value,
Origin: org,
Position: pos,
}
switch stat.typ {
case numTypeFloat:
tk.Type = FloatType
case numTypeBinary:
tk.Type = BinaryIntegerType
case numTypeOctet:
tk.Type = OctetIntegerType
case numTypeHex:
tk.Type = HexIntegerType
}
return tk
}
return String(value, org, pos)
}
// Position type for position in YAML document
type Position struct {
Line int
Column int
Offset int
IndentNum int
IndentLevel int
}
// String position to text
func (p *Position) String() string {
return fmt.Sprintf("[level:%d,line:%d,column:%d,offset:%d]", p.IndentLevel, p.Line, p.Column, p.Offset)
}
// Token type for token
type Token struct {
Type Type
CharacterType CharacterType
Indicator Indicator
Value string
Origin string
Position *Position
Next *Token
Prev *Token
}
// PreviousType previous token type
func (t *Token) PreviousType() Type {
if t.Prev != nil {
return t.Prev.Type
}
return UnknownType
}
// NextType next token type
func (t *Token) NextType() Type {
if t.Next != nil {
return t.Next.Type
}
return UnknownType
}
// AddColumn append column number to current position of column
func (t *Token) AddColumn(col int) {
if t == nil {
return
}
t.Position.Column += col
}
// Clone copy token ( preserve Prev/Next reference )
func (t *Token) Clone() *Token {
if t == nil {
return nil
}
copied := *t
if t.Position != nil {
pos := *(t.Position)
copied.Position = &pos
}
return &copied
}
// Tokens type of token collection
type Tokens []*Token
func (t *Tokens) add(tk *Token) {
tokens := *t
if len(tokens) == 0 {
tokens = append(tokens, tk)
} else {
last := tokens[len(tokens)-1]
last.Next = tk
tk.Prev = last
tokens = append(tokens, tk)
}
*t = tokens
}
// Add append new some tokens
func (t *Tokens) Add(tks ...*Token) {
for _, tk := range tks {
t.add(tk)
}
}
// Dump dump all token structures for debugging
func (t Tokens) Dump() {
for _, tk := range t {
fmt.Printf("- %+v\n", tk)
}
}
// String create token for String
func String(value string, org string, pos *Position) *Token {
return &Token{
Type: StringType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// SequenceEntry create token for SequenceEntry
func SequenceEntry(org string, pos *Position) *Token {
return &Token{
Type: SequenceEntryType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockStructureIndicator,
Value: string(SequenceEntryCharacter),
Origin: org,
Position: pos,
}
}
// MappingKey create token for MappingKey
func MappingKey(pos *Position) *Token {
return &Token{
Type: MappingKeyType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockStructureIndicator,
Value: string(MappingKeyCharacter),
Origin: string(MappingKeyCharacter),
Position: pos,
}
}
// MappingValue create token for MappingValue
func MappingValue(pos *Position) *Token {
return &Token{
Type: MappingValueType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockStructureIndicator,
Value: string(MappingValueCharacter),
Origin: string(MappingValueCharacter),
Position: pos,
}
}
// CollectEntry create token for CollectEntry
func CollectEntry(org string, pos *Position) *Token {
return &Token{
Type: CollectEntryType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(CollectEntryCharacter),
Origin: org,
Position: pos,
}
}
// SequenceStart create token for SequenceStart
func SequenceStart(org string, pos *Position) *Token {
return &Token{
Type: SequenceStartType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(SequenceStartCharacter),
Origin: org,
Position: pos,
}
}
// SequenceEnd create token for SequenceEnd
func SequenceEnd(org string, pos *Position) *Token {
return &Token{
Type: SequenceEndType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(SequenceEndCharacter),
Origin: org,
Position: pos,
}
}
// MappingStart create token for MappingStart
func MappingStart(org string, pos *Position) *Token {
return &Token{
Type: MappingStartType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(MappingStartCharacter),
Origin: org,
Position: pos,
}
}
// MappingEnd create token for MappingEnd
func MappingEnd(org string, pos *Position) *Token {
return &Token{
Type: MappingEndType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(MappingEndCharacter),
Origin: org,
Position: pos,
}
}
// Comment create token for Comment
func Comment(value string, org string, pos *Position) *Token {
return &Token{
Type: CommentType,
CharacterType: CharacterTypeIndicator,
Indicator: CommentIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// Anchor create token for Anchor
func Anchor(org string, pos *Position) *Token {
return &Token{
Type: AnchorType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: string(AnchorCharacter),
Origin: org,
Position: pos,
}
}
// Alias create token for Alias
func Alias(org string, pos *Position) *Token {
return &Token{
Type: AliasType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: string(AliasCharacter),
Origin: org,
Position: pos,
}
}
// Tag create token for Tag
func Tag(value string, org string, pos *Position) *Token {
fn := ReservedTagKeywordMap[ReservedTagKeyword(value)]
if fn != nil {
return fn(value, org, pos)
}
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// Literal create token for Literal
func Literal(value string, org string, pos *Position) *Token {
return &Token{
Type: LiteralType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockScalarIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// Folded create token for Folded
func Folded(value string, org string, pos *Position) *Token {
return &Token{
Type: FoldedType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockScalarIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// SingleQuote create token for SingleQuote
func SingleQuote(value string, org string, pos *Position) *Token {
return &Token{
Type: SingleQuoteType,
CharacterType: CharacterTypeIndicator,
Indicator: QuotedScalarIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// DoubleQuote create token for DoubleQuote
func DoubleQuote(value string, org string, pos *Position) *Token {
return &Token{
Type: DoubleQuoteType,
CharacterType: CharacterTypeIndicator,
Indicator: QuotedScalarIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// Directive create token for Directive
func Directive(org string, pos *Position) *Token {
return &Token{
Type: DirectiveType,
CharacterType: CharacterTypeIndicator,
Indicator: DirectiveIndicator,
Value: string(DirectiveCharacter),
Origin: org,
Position: pos,
}
}
// Space create token for Space
func Space(pos *Position) *Token {
return &Token{
Type: SpaceType,
CharacterType: CharacterTypeWhiteSpace,
Indicator: NotIndicator,
Value: string(SpaceCharacter),
Origin: string(SpaceCharacter),
Position: pos,
}
}
// MergeKey create token for MergeKey
func MergeKey(org string, pos *Position) *Token {
return &Token{
Type: MergeKeyType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: "<<",
Origin: org,
Position: pos,
}
}
// DocumentHeader create token for DocumentHeader
func DocumentHeader(org string, pos *Position) *Token {
return &Token{
Type: DocumentHeaderType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: "---",
Origin: org,
Position: pos,
}
}
// DocumentEnd create token for DocumentEnd
func DocumentEnd(org string, pos *Position) *Token {
return &Token{
Type: DocumentEndType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: "...",
Origin: org,
Position: pos,
}
}
// DetectLineBreakCharacter detect line break character in only one inside scalar content scope.
func DetectLineBreakCharacter(src string) string {
nc := strings.Count(src, "\n")
rc := strings.Count(src, "\r")
rnc := strings.Count(src, "\r\n")
switch {
case nc == rnc && rc == rnc:
return "\r\n"
case rc > nc:
return "\r"
default:
return "\n"
}
}