add beginning of parser
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
12ce11bb6e
commit
54794b57a4
12 changed files with 457 additions and 8 deletions
64
base.go
64
base.go
|
@ -1,7 +1,67 @@
|
||||||
package devicepath
|
package devicepath
|
||||||
|
|
||||||
type DevicePathNode interface{}
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type DevicePath interface {
|
type DeviceNodeType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
HardwareDevicePath DeviceNodeType = 0x01
|
||||||
|
ACPIDevicePath DeviceNodeType = 0x02
|
||||||
|
MessagingDevicePath DeviceNodeType = 0x03
|
||||||
|
MediaDevicePath DeviceNodeType = 0x04
|
||||||
|
BIOSBootSpecificationsDevicePath DeviceNodeType = 0x05
|
||||||
|
EndOfHardwareDevicePath DeviceNodeType = 0x7F
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeviceNodeSubType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
PCIHardware DeviceNodeSubType = 0x01
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SATAMessaging DeviceNodeSubType = 18
|
||||||
|
)
|
||||||
|
|
||||||
|
const EndOfEntireDevicePath DeviceNodeSubType = 0xFF
|
||||||
|
|
||||||
|
const ACPISubType DeviceNodeSubType = 1
|
||||||
|
|
||||||
|
type DevicePathNode interface {
|
||||||
|
Type() DeviceNodeType
|
||||||
|
SubType() DeviceNodeSubType
|
||||||
|
ParseString(raw string) error
|
||||||
String() string
|
String() string
|
||||||
|
Bytes() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrPathComplete = errors.New("device path already complete")
|
||||||
|
|
||||||
|
type DevicePath struct {
|
||||||
|
nodes []DevicePathNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp *DevicePath) complete() bool {
|
||||||
|
return len(dp.nodes) > 0 && dp.nodes[len(dp.nodes)-1].Type() == EndOfHardwareDevicePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp *DevicePath) PushNode(node DevicePathNode) error {
|
||||||
|
if dp.complete() {
|
||||||
|
return ErrPathComplete
|
||||||
|
}
|
||||||
|
dp.nodes = append(dp.nodes, node)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp *DevicePath) String() string {
|
||||||
|
var res []string
|
||||||
|
for _, node := range dp.nodes {
|
||||||
|
if node.String() != "" {
|
||||||
|
res = append(res, node.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(res, "/")
|
||||||
}
|
}
|
||||||
|
|
26
end.go
Normal file
26
end.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package devicepath
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
type FinalDevicePath struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *FinalDevicePath) Type() DeviceNodeType {
|
||||||
|
return EndOfHardwareDevicePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *FinalDevicePath) SubType() DeviceNodeSubType {
|
||||||
|
return EndOfEntireDevicePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *FinalDevicePath) Bytes() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *FinalDevicePath) String() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *FinalDevicePath) ParseString(raw string) error {
|
||||||
|
return errors.New("unparsable")
|
||||||
|
}
|
8
go.mod
8
go.mod
|
@ -1,3 +1,11 @@
|
||||||
module git.faercol.me/faercol/device-path
|
module git.faercol.me/faercol/device-path
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.8.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
10
go.sum
Normal file
10
go.sum
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
64
parser.go
64
parser.go
|
@ -1,9 +1,65 @@
|
||||||
package devicepath
|
package devicepath
|
||||||
|
|
||||||
func ParseNodeString(string) (DevicePathNode, error) {
|
import (
|
||||||
return nil, nil
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnknownSubType = errors.New("unknown device sub-type")
|
||||||
|
ErrMalformedString = errors.New("malformed device string")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrInvalidArguments struct {
|
||||||
|
subErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParsePathString(string) (DevicePath, error) {
|
func (e *ErrInvalidArguments) Error() string {
|
||||||
return nil, nil
|
return fmt.Sprintf("invalid argument: %s", e.subErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrInvalidPath struct {
|
||||||
|
subErr error
|
||||||
|
node string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrInvalidPath) Error() string {
|
||||||
|
return fmt.Sprintf("invalid device path on section %q: %s", e.node, e.subErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeGenerator func() DevicePathNode
|
||||||
|
|
||||||
|
var prefixTypeAssociation = map[string]nodeGenerator{
|
||||||
|
sataPrefix: func() DevicePathNode { return &SataDevicePath{} },
|
||||||
|
pciPrefix: func() DevicePathNode { return &PCIDevicePath{} },
|
||||||
|
pciRootPrefix: func() DevicePathNode { return &PCIRootDevicePath{} },
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parsenode(raw string) (DevicePathNode, error) {
|
||||||
|
prefix := getPrefix(raw)
|
||||||
|
devGen, ok := prefixTypeAssociation[prefix]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrUnknownSubType
|
||||||
|
}
|
||||||
|
dev := devGen()
|
||||||
|
if err := dev.ParseString(raw); err != nil {
|
||||||
|
return nil, &ErrInvalidArguments{subErr: err}
|
||||||
|
}
|
||||||
|
return dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseDevicePath(raw string) (*DevicePath, error) {
|
||||||
|
dp := DevicePath{}
|
||||||
|
for _, nodeStr := range strings.Split(raw, "/") {
|
||||||
|
node, err := Parsenode(nodeStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &ErrInvalidPath{subErr: err, node: nodeStr}
|
||||||
|
}
|
||||||
|
if err := dp.PushNode(node); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dp.PushNode(&FinalDevicePath{})
|
||||||
|
return &dp, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,25 @@
|
||||||
package devicepath_test
|
package devicepath_test
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
func TestTest(t *testing.T) {
|
devicepath "git.faercol.me/faercol/device-path"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOK(t *testing.T) {
|
||||||
|
input := "Sata(1,65535,0)"
|
||||||
|
res, err := devicepath.Parsenode(input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, devicepath.MessagingDevicePath, res.Type())
|
||||||
|
assert.Equal(t, devicepath.SATAMessaging, res.SubType())
|
||||||
|
assert.Equal(t, "Sata(1,65535,0)", res.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseDevicePathOK(t *testing.T) {
|
||||||
|
input := "PciRoot(0x0)/Pci(0x1,0x2)/Pci(0x0,0x1)/Sata(0,65535,0)"
|
||||||
|
res, err := devicepath.ParseDevicePath(input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "PciRoot(0x0)/Pci(0x1,0x2)/Pci(0x0,0x1)/Sata(0,65535,0)", res.String())
|
||||||
}
|
}
|
||||||
|
|
72
pci.go
Normal file
72
pci.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package devicepath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pciLength uint16 = 6
|
||||||
|
pciPrefix string = "Pci"
|
||||||
|
pciStringLength int = 12
|
||||||
|
)
|
||||||
|
|
||||||
|
type PCIDevicePath struct {
|
||||||
|
function uint8
|
||||||
|
device uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pcp *PCIDevicePath) Type() DeviceNodeType {
|
||||||
|
return HardwareDevicePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pcp *PCIDevicePath) SubType() DeviceNodeSubType {
|
||||||
|
return PCIHardware
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pcp *PCIDevicePath) Bytes() []byte {
|
||||||
|
res := make([]byte, pciLength)
|
||||||
|
res[0] = byte(pcp.Type())
|
||||||
|
res[1] = byte(pcp.SubType())
|
||||||
|
binary.LittleEndian.PutUint16(res[2:3], pciLength)
|
||||||
|
res[4] = pcp.function
|
||||||
|
res[5] = pcp.device
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pcp *PCIDevicePath) String() string {
|
||||||
|
return fmt.Sprintf("%s(0x%x,0x%x)", pciPrefix, pcp.function, pcp.device)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pcp *PCIDevicePath) ParseString(raw string) error {
|
||||||
|
if !checkStringFormat(raw, pciPrefix, pciStringLength) {
|
||||||
|
return ErrMalformedString
|
||||||
|
}
|
||||||
|
args := strings.Split(raw[len(pciPrefix)+1:pciStringLength-1], ",")
|
||||||
|
if len(args) != 2 {
|
||||||
|
return errors.New("unexpected number of arguments")
|
||||||
|
}
|
||||||
|
if function, err := strconv.ParseUint(args[0], 0, 8); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
fmt.Println("function", function)
|
||||||
|
pcp.function = uint8(function)
|
||||||
|
}
|
||||||
|
if device, err := strconv.ParseUint(args[1], 0, 8); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
fmt.Println("device", device)
|
||||||
|
pcp.device = uint8(device)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPCIDevicePath(function, device uint8) *PCIDevicePath {
|
||||||
|
return &PCIDevicePath{
|
||||||
|
function: function,
|
||||||
|
device: device,
|
||||||
|
}
|
||||||
|
}
|
22
pci_test.go
Normal file
22
pci_test.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package devicepath_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
devicepath "git.faercol.me/faercol/device-path"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToString(t *testing.T) {
|
||||||
|
t.Run("Only digits", func(t *testing.T) {
|
||||||
|
expected := "Pci(0x0,0x1)"
|
||||||
|
dev := devicepath.NewPCIDevicePath(0, 1)
|
||||||
|
assert.Equal(t, expected, dev.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Letters", func(t *testing.T) {
|
||||||
|
expected := "Pci(0x2,0xa)"
|
||||||
|
dev := devicepath.NewPCIDevicePath(2, 10)
|
||||||
|
assert.Equal(t, expected, dev.String())
|
||||||
|
})
|
||||||
|
}
|
46
pciroot.go
Normal file
46
pciroot.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package devicepath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const pciRootPrefix string = "PciRoot"
|
||||||
|
|
||||||
|
type PCIRootDevicePath struct {
|
||||||
|
uid int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prp *PCIRootDevicePath) Type() DeviceNodeType {
|
||||||
|
return ACPIDevicePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prp *PCIRootDevicePath) SubType() DeviceNodeSubType {
|
||||||
|
return ACPISubType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prp *PCIRootDevicePath) Bytes() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prp *PCIRootDevicePath) String() string {
|
||||||
|
return fmt.Sprintf("%s(0x%x)", pciRootPrefix, prp.uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prp *PCIRootDevicePath) ParseString(raw string) error {
|
||||||
|
if !checkStringFormat(raw, pciRootPrefix, -1) {
|
||||||
|
return ErrMalformedString
|
||||||
|
}
|
||||||
|
args := strings.Split(raw[len(pciRootPrefix)+1:len(raw)-1], ",")
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.New("unexpected number of arguments")
|
||||||
|
}
|
||||||
|
if uid, err := strconv.ParseInt(args[0], 0, 32); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
prp.uid = int32(uid)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
71
sata.go
Normal file
71
sata.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package devicepath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPortMultiplier = 0xffff
|
||||||
|
defaultLogicalUnitNumber = 0
|
||||||
|
sataLength uint16 = 10
|
||||||
|
sataPrefix string = "Sata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SataDevicePath struct {
|
||||||
|
hbaPortNumber uint16
|
||||||
|
portMultiplierPortNumber uint16
|
||||||
|
logicalUnitNumber uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SataDevicePath) Type() DeviceNodeType {
|
||||||
|
return MessagingDevicePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SataDevicePath) SubType() DeviceNodeSubType {
|
||||||
|
return SATAMessaging
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SataDevicePath) Bytes() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SataDevicePath) String() string {
|
||||||
|
return fmt.Sprintf("%s(%d,%d,%d)", sataPrefix, sp.hbaPortNumber, sp.portMultiplierPortNumber, sp.logicalUnitNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SataDevicePath) ParseString(raw string) error {
|
||||||
|
if !checkStringFormat(raw, sataPrefix, -1) {
|
||||||
|
return ErrMalformedString
|
||||||
|
}
|
||||||
|
args := strings.Split(raw[len(sataPrefix)+1:len(raw)-1], ",")
|
||||||
|
if len(args) != 3 {
|
||||||
|
return errors.New("unexpected number of arguments")
|
||||||
|
}
|
||||||
|
if hbaPortNumber, err := strconv.Atoi(args[0]); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
sp.hbaPortNumber = uint16(hbaPortNumber)
|
||||||
|
}
|
||||||
|
if portMultiplierPortNumber, err := strconv.Atoi(args[1]); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
sp.portMultiplierPortNumber = uint16(portMultiplierPortNumber)
|
||||||
|
}
|
||||||
|
if logicalUnitNumber, err := strconv.Atoi(args[2]); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
sp.logicalUnitNumber = uint16(logicalUnitNumber)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSataFromPortNumber(portNumber uint16) *SataDevicePath {
|
||||||
|
return &SataDevicePath{
|
||||||
|
hbaPortNumber: portNumber,
|
||||||
|
portMultiplierPortNumber: defaultPortMultiplier,
|
||||||
|
logicalUnitNumber: defaultLogicalUnitNumber,
|
||||||
|
}
|
||||||
|
}
|
42
sata_test.go
Normal file
42
sata_test.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package devicepath_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
devicepath "git.faercol.me/faercol/device-path"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseString(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
var sataDev devicepath.SataDevicePath
|
||||||
|
require.NoError(t, sataDev.ParseString("Sata(1,65535,0)"))
|
||||||
|
assert.Equal(t, "Sata(1,65535,0)", sataDev.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Err invalid prefix", func(t *testing.T) {
|
||||||
|
var sataDev devicepath.SataDevicePath
|
||||||
|
assert.ErrorIs(t, sataDev.ParseString("PciRoot(0x0)"), devicepath.ErrMalformedString)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Err invalid number arguments", func(t *testing.T) {
|
||||||
|
var sataDev devicepath.SataDevicePath
|
||||||
|
assert.ErrorContains(t, sataDev.ParseString("Sata(1,2,3,4)"), "unexpected number of arguments")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Err invalid format arg0", func(t *testing.T) {
|
||||||
|
var sataDev devicepath.SataDevicePath
|
||||||
|
assert.ErrorContains(t, sataDev.ParseString("Sata(toto,65535,0)"), `strconv.Atoi: parsing "toto": invalid syntax`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Err invalid format arg1", func(t *testing.T) {
|
||||||
|
var sataDev devicepath.SataDevicePath
|
||||||
|
assert.ErrorContains(t, sataDev.ParseString("Sata(0,toto,0)"), `strconv.Atoi: parsing "toto": invalid syntax`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Err invalid format arg2", func(t *testing.T) {
|
||||||
|
var sataDev devicepath.SataDevicePath
|
||||||
|
assert.ErrorContains(t, sataDev.ParseString("Sata(0,65535,toto)"), `strconv.Atoi: parsing "toto": invalid syntax`)
|
||||||
|
})
|
||||||
|
}
|
18
utils.go
Normal file
18
utils.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package devicepath
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func getPrefix(raw string) string {
|
||||||
|
firstCharAfterId := strings.Index(raw, "(")
|
||||||
|
if firstCharAfterId == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return raw[0:firstCharAfterId]
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkStringFormat(raw, prefix string, length int) bool {
|
||||||
|
if length > 0 && len(raw) != length {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(raw, prefix+"(") && strings.HasSuffix(raw, ")")
|
||||||
|
}
|
Loading…
Reference in a new issue