1
0
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

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
}