mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-06-08 23:56:37 +02:00
459 lines
10 KiB
Go
459 lines
10 KiB
Go
package goparse
|
|
|
|
import (
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/fatih/structtag"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// GoEmptyLine defined a GoObject for a code line break.
|
|
var GoEmptyLine = GoObject{
|
|
Type: GoObjectType_LineBreak,
|
|
goObjects: &GoObjects{
|
|
list: []*GoObject{},
|
|
},
|
|
}
|
|
|
|
// GoObjectType defines a set of possible types to group
|
|
// parsed code by.
|
|
type GoObjectType = string
|
|
|
|
var (
|
|
GoObjectType_Package = "package"
|
|
GoObjectType_Import = "import"
|
|
GoObjectType_Var = "var"
|
|
GoObjectType_Const = "const"
|
|
GoObjectType_Func = "func"
|
|
GoObjectType_Struct = "struct"
|
|
GoObjectType_Comment = "comment"
|
|
GoObjectType_LineBreak = "linebreak"
|
|
GoObjectType_Line = "line"
|
|
GoObjectType_Type = "type"
|
|
)
|
|
|
|
// GoObject defines a section of code with a nested set of children.
|
|
type GoObject struct {
|
|
Type GoObjectType
|
|
Name string
|
|
startLines []string
|
|
endLines []string
|
|
subLines []string
|
|
goObjects *GoObjects
|
|
Index int
|
|
}
|
|
|
|
// GoObjects stores a list of GoObject.
|
|
type GoObjects struct {
|
|
list []*GoObject
|
|
}
|
|
|
|
// Objects returns the list of *GoObject.
|
|
func (obj *GoObject) Objects() *GoObjects {
|
|
if obj.goObjects == nil {
|
|
obj.goObjects = &GoObjects{
|
|
list: []*GoObject{},
|
|
}
|
|
}
|
|
return obj.goObjects
|
|
}
|
|
|
|
// Clone performs a deep copy of the struct.
|
|
func (obj *GoObject) Clone() *GoObject {
|
|
n := &GoObject{
|
|
Type: obj.Type,
|
|
Name: obj.Name,
|
|
startLines: obj.startLines,
|
|
endLines: obj.endLines,
|
|
subLines: obj.subLines,
|
|
goObjects: &GoObjects{
|
|
list: []*GoObject{},
|
|
},
|
|
Index: obj.Index,
|
|
}
|
|
for _, sub := range obj.Objects().List() {
|
|
n.Objects().Add(sub.Clone())
|
|
}
|
|
return n
|
|
}
|
|
|
|
// IsComment returns whether an object is of type GoObjectType_Comment.
|
|
func (obj *GoObject) IsComment() bool {
|
|
if obj.Type != GoObjectType_Comment {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Contains searches all the lines for the object for a matching string.
|
|
func (obj *GoObject) Contains(match string) bool {
|
|
for _, l := range obj.Lines() {
|
|
if strings.Contains(l, match) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// UpdateLines parses the new code and replaces the current GoObject.
|
|
func (obj *GoObject) UpdateLines(newLines []string) error {
|
|
|
|
// Parse the new lines.
|
|
objs, err := ParseLines(newLines, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var newObj *GoObject
|
|
for _, obj := range objs.List() {
|
|
if obj.Type == GoObjectType_LineBreak {
|
|
continue
|
|
}
|
|
|
|
if newObj == nil {
|
|
newObj = obj
|
|
}
|
|
|
|
// There should only be one resulting parsed object that is
|
|
// not of type GoObjectType_LineBreak.
|
|
return errors.New("Can only update single blocks of code")
|
|
}
|
|
|
|
// No new code was parsed, return error.
|
|
if newObj == nil {
|
|
return errors.New("Failed to render replacement code")
|
|
}
|
|
|
|
return obj.Update(newObj)
|
|
}
|
|
|
|
// Update performs a deep copy that overwrites the existing values.
|
|
func (obj *GoObject) Update(newObj *GoObject) error {
|
|
obj.Type = newObj.Type
|
|
obj.Name = newObj.Name
|
|
obj.startLines = newObj.startLines
|
|
obj.endLines = newObj.endLines
|
|
obj.subLines = newObj.subLines
|
|
obj.goObjects = newObj.goObjects
|
|
return nil
|
|
}
|
|
|
|
// Lines returns a list of strings for current object and all children.
|
|
func (obj *GoObject) Lines() []string {
|
|
l := []string{}
|
|
|
|
// First include any lines before the sub objects.
|
|
for _, sl := range obj.startLines {
|
|
l = append(l, sl)
|
|
}
|
|
|
|
// If there are parsed sub objects include those lines else when
|
|
// no sub objects, just use the sub lines.
|
|
if len(obj.Objects().List()) > 0 {
|
|
for _, sl := range obj.Objects().Lines() {
|
|
l = append(l, sl)
|
|
}
|
|
} else {
|
|
for _, sl := range obj.subLines {
|
|
l = append(l, sl)
|
|
}
|
|
}
|
|
|
|
// Lastly include any other lines that are after all parsed sub objects.
|
|
for _, sl := range obj.endLines {
|
|
l = append(l, sl)
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// String returns the lines separated by line break.
|
|
func (obj *GoObject) String() string {
|
|
return strings.Join(obj.Lines(), "\n")
|
|
}
|
|
|
|
// Lines returns a list of strings for all the list objects.
|
|
func (objs *GoObjects) Lines() []string {
|
|
l := []string{}
|
|
for _, obj := range objs.List() {
|
|
for _, oj := range obj.Lines() {
|
|
l = append(l, oj)
|
|
}
|
|
}
|
|
return l
|
|
}
|
|
|
|
// String returns all the lines for the list objects.
|
|
func (objs *GoObjects) String() string {
|
|
lines := []string{}
|
|
for _, obj := range objs.List() {
|
|
lines = append(lines, obj.String())
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
// List returns the list of GoObjects.
|
|
func (objs *GoObjects) List() []*GoObject {
|
|
return objs.list
|
|
}
|
|
|
|
// HasFunc searches the current list of objects for a function object by name.
|
|
func (objs *GoObjects) HasFunc(name string) bool {
|
|
return objs.HasType(name, GoObjectType_Func)
|
|
}
|
|
|
|
// Get returns the GoObject for the matching name and type.
|
|
func (objs *GoObjects) Get(name string, objType GoObjectType) *GoObject {
|
|
for _, obj := range objs.list {
|
|
if obj.Name == name && (objType == "" || obj.Type == objType) {
|
|
return obj
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HasType checks is a GoObject exists for the matching name and type.
|
|
func (objs *GoObjects) HasType(name string, objType GoObjectType) bool {
|
|
for _, obj := range objs.list {
|
|
if obj.Name == name && (objType == "" || obj.Type == objType) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasObject checks to see if the exact code block exists.
|
|
func (objs *GoObjects) HasObject(src *GoObject) bool {
|
|
if src == nil {
|
|
return false
|
|
}
|
|
|
|
// Generate the code for the supplied object.
|
|
srcLines := []string{}
|
|
for _, l := range src.Lines() {
|
|
// Exclude empty lines.
|
|
l = strings.TrimSpace(l)
|
|
if l != "" {
|
|
srcLines = append(srcLines, l)
|
|
}
|
|
}
|
|
srcStr := strings.Join(srcLines, "\n")
|
|
|
|
// Loop over all the objects and match with src code.
|
|
for _, obj := range objs.list {
|
|
objLines := []string{}
|
|
for _, l := range obj.Lines() {
|
|
// Exclude empty lines.
|
|
l = strings.TrimSpace(l)
|
|
if l != "" {
|
|
objLines = append(objLines, l)
|
|
}
|
|
}
|
|
objStr := strings.Join(objLines, "\n")
|
|
|
|
// Return true if the current object code matches src code.
|
|
if srcStr == objStr {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Add appends a new GoObject to the list.
|
|
func (objs *GoObjects) Add(newObj *GoObject) error {
|
|
newObj.Index = len(objs.list)
|
|
objs.list = append(objs.list, newObj)
|
|
return nil
|
|
}
|
|
|
|
// Insert appends a new GoObject at the desired position to the list.
|
|
func (objs *GoObjects) Insert(pos int, newObj *GoObject) error {
|
|
newList := []*GoObject{}
|
|
|
|
var newIdx int
|
|
for _, obj := range objs.list {
|
|
if obj.Index < pos {
|
|
obj.Index = newIdx
|
|
newList = append(newList, obj)
|
|
} else {
|
|
if obj.Index == pos {
|
|
newObj.Index = newIdx
|
|
newList = append(newList, newObj)
|
|
newIdx++
|
|
}
|
|
obj.Index = newIdx
|
|
newList = append(newList, obj)
|
|
}
|
|
|
|
newIdx++
|
|
}
|
|
|
|
objs.list = newList
|
|
|
|
return nil
|
|
}
|
|
|
|
// Remove deletes a GoObject from the list.
|
|
func (objs *GoObjects) Remove(delObjs ...*GoObject) error {
|
|
for _, delObj := range delObjs {
|
|
oldList := objs.List()
|
|
objs.list = []*GoObject{}
|
|
|
|
var newIdx int
|
|
for _, obj := range oldList {
|
|
if obj.Index == delObj.Index {
|
|
continue
|
|
}
|
|
obj.Index = newIdx
|
|
objs.list = append(objs.list, obj)
|
|
newIdx++
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Replace updates an existing GoObject while maintaining is same position.
|
|
func (objs *GoObjects) Replace(oldObj *GoObject, newObjs ...*GoObject) error {
|
|
if oldObj.Index >= len(objs.list) {
|
|
return errors.WithStack(errGoObjectNotExist)
|
|
} else if len(newObjs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
oldList := objs.List()
|
|
objs.list = []*GoObject{}
|
|
|
|
var newIdx int
|
|
for _, obj := range oldList {
|
|
if obj.Index < oldObj.Index {
|
|
obj.Index = newIdx
|
|
objs.list = append(objs.list, obj)
|
|
newIdx++
|
|
} else if obj.Index == oldObj.Index {
|
|
for _, newObj := range newObjs {
|
|
newObj.Index = newIdx
|
|
objs.list = append(objs.list, newObj)
|
|
newIdx++
|
|
}
|
|
} else {
|
|
obj.Index = newIdx
|
|
objs.list = append(objs.list, obj)
|
|
newIdx++
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReplaceFuncByName finds an existing GoObject with type GoObjectType_Func by name
|
|
// and then performs a replace with the supplied new GoObject.
|
|
func (objs *GoObjects) ReplaceFuncByName(name string, fn *GoObject) error {
|
|
return objs.ReplaceTypeByName(name, fn, GoObjectType_Func)
|
|
}
|
|
|
|
// ReplaceTypeByName finds an existing GoObject with type by name
|
|
// and then performs a replace with the supplied new GoObject.
|
|
func (objs *GoObjects) ReplaceTypeByName(name string, newObj *GoObject, objType GoObjectType) error {
|
|
if newObj.Name == "" {
|
|
newObj.Name = name
|
|
}
|
|
if newObj.Type == "" && objType != "" {
|
|
newObj.Type = objType
|
|
}
|
|
|
|
for _, obj := range objs.list {
|
|
if obj.Name == name && (objType == "" || objType == obj.Type) {
|
|
return objs.Replace(obj, newObj)
|
|
}
|
|
}
|
|
return errors.WithStack(errGoObjectNotExist)
|
|
}
|
|
|
|
// Empty determines if all the GoObject in the list are line breaks.
|
|
func (objs *GoObjects) Empty() bool {
|
|
var hasStuff bool
|
|
for _, obj := range objs.List() {
|
|
switch obj.Type {
|
|
case GoObjectType_LineBreak:
|
|
//case GoObjectType_Comment:
|
|
//case GoObjectType_Import:
|
|
// do nothing
|
|
default:
|
|
hasStuff = true
|
|
}
|
|
}
|
|
return hasStuff
|
|
}
|
|
|
|
// Debug prints out the GoObject to logger.
|
|
func (obj *GoObject) Debug(log *log.Logger) {
|
|
log.Println(obj.Name)
|
|
log.Println(" > type:", obj.Type)
|
|
log.Println(" > start lines:")
|
|
for _, l := range obj.startLines {
|
|
log.Println(" ", l)
|
|
}
|
|
|
|
log.Println(" > sub lines:")
|
|
for _, l := range obj.subLines {
|
|
log.Println(" ", l)
|
|
}
|
|
|
|
log.Println(" > end lines:")
|
|
for _, l := range obj.endLines {
|
|
log.Println(" ", l)
|
|
}
|
|
}
|
|
|
|
// Defines a property of a struct.
|
|
type structProp struct {
|
|
Name string
|
|
Type string
|
|
Tags *structtag.Tags
|
|
}
|
|
|
|
// ParseStructProp extracts the details for a struct property.
|
|
func ParseStructProp(obj *GoObject) (structProp, error) {
|
|
|
|
if obj.Type != GoObjectType_Line {
|
|
return structProp{}, errors.Errorf("Unable to parse object of type %s", obj.Type)
|
|
}
|
|
|
|
// Remove any white space from the code line.
|
|
ls := strings.TrimSpace(strings.Join(obj.Lines(), " "))
|
|
|
|
// Extract the property name and type for the line.
|
|
// ie: ID string `json:"id"`
|
|
var resp structProp
|
|
for _, p := range strings.Split(ls, " ") {
|
|
p = strings.TrimSpace(p)
|
|
if p == "" {
|
|
continue
|
|
}
|
|
if resp.Name == "" {
|
|
resp.Name = p
|
|
} else if resp.Type == "" {
|
|
resp.Type = p
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
// If the line contains tags, extract and parse them.
|
|
if strings.Contains(ls, "`") {
|
|
tagStr := strings.Split(ls, "`")[1]
|
|
|
|
var err error
|
|
resp.Tags, err = structtag.Parse(tagStr)
|
|
if err != nil {
|
|
err = errors.WithMessagef(err, "Failed to parse struct tag for field %s: %s", resp.Name, tagStr)
|
|
return structProp{}, err
|
|
}
|
|
}
|
|
|
|
return resp, nil
|
|
}
|