1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-08-08 22:36:41 +02:00

moved example-project files back a directory

This commit is contained in:
Lee Brown
2019-07-13 11:42:37 -08:00
parent 541a279b6f
commit bcf022aa18
144 changed files with 200 additions and 206 deletions

View File

@ -0,0 +1,301 @@
package goparse
import (
"fmt"
"go/format"
"io/ioutil"
"strings"
"github.com/pkg/errors"
)
// GoDocument defines a single go code file.
type GoDocument struct {
*GoObjects
Package string
imports GoImports
}
// GoImport defines a single import line with optional alias.
type GoImport struct {
Name string
Alias string
}
// GoImports holds a list of import lines.
type GoImports []GoImport
// NewGoDocument creates a new GoDocument with the package line set.
func NewGoDocument(packageName string) (doc *GoDocument, err error) {
doc = &GoDocument{
GoObjects: &GoObjects{
list: []*GoObject{},
},
}
err = doc.SetPackage(packageName)
return doc, err
}
// Objects returns a list of root GoObject.
func (doc *GoDocument) Objects() *GoObjects {
if doc.GoObjects == nil {
doc.GoObjects = &GoObjects{
list: []*GoObject{},
}
}
return doc.GoObjects
}
// NewObjectPackage returns a new GoObject with a single package definition line.
func NewObjectPackage(packageName string) *GoObject {
lines := []string{
fmt.Sprintf("package %s", packageName),
"",
}
obj, _ := ParseGoObject(lines, 0)
return obj
}
// SetPackage appends sets the package line for the code file.
func (doc *GoDocument) SetPackage(packageName string) error {
var existing *GoObject
for _, obj := range doc.Objects().List() {
if obj.Type == GoObjectType_Package {
existing = obj
break
}
}
new := NewObjectPackage(packageName)
var err error
if existing != nil {
err = doc.Objects().Replace(existing, new)
} else if len(doc.Objects().List()) > 0 {
// Insert after any existing comments or line breaks.
var insertPos int
//for idx, obj := range doc.Objects().List() {
// switch obj.Type {
// case GoObjectType_Comment, GoObjectType_LineBreak:
// insertPos = idx
// default:
// break
// }
//}
err = doc.Objects().Insert(insertPos, new)
} else {
err = doc.Objects().Add(new)
}
return err
}
// AddObject appends a new GoObject to the doc root object list.
func (doc *GoDocument) AddObject(newObj *GoObject) error {
return doc.Objects().Add(newObj)
}
// InsertObject inserts a new GoObject at the desired position to the doc root object list.
func (doc *GoDocument) InsertObject(pos int, newObj *GoObject) error {
return doc.Objects().Insert(pos, newObj)
}
// Imports returns the GoDocument imports.
func (doc *GoDocument) Imports() (GoImports, error) {
// If the doc imports are empty, try to load them from the root objects.
if len(doc.imports) == 0 {
for _, obj := range doc.Objects().List() {
if obj.Type != GoObjectType_Import {
continue
}
res, err := ParseImportObject(obj)
if err != nil {
return doc.imports, err
}
// Combine all the imports into a single definition.
for _, n := range res {
doc.imports = append(doc.imports, n)
}
}
}
return doc.imports, nil
}
// Lines returns all the code lines.
func (doc *GoDocument) Lines() []string {
l := []string{}
for _, ol := range doc.Objects().Lines() {
l = append(l, ol)
}
return l
}
// String returns a single value for all the code lines.
func (doc *GoDocument) String() string {
return strings.Join(doc.Lines(), "\n")
}
// Print writes all the code lines to standard out.
func (doc *GoDocument) Print() {
for _, l := range doc.Lines() {
fmt.Println(l)
}
}
// Save renders all the code lines for the document, formats the code
// and then saves it to the supplied file path.
func (doc *GoDocument) Save(localpath string) error {
res, err := format.Source([]byte(doc.String()))
if err != nil {
err = errors.WithMessage(err, "Failed formatted source code")
return err
}
err = ioutil.WriteFile(localpath, res, 0644)
if err != nil {
err = errors.WithMessagef(err, "Failed write source code to file %s", localpath)
return err
}
return nil
}
// AddImport checks for any duplicate imports by name and adds it if not.
func (doc *GoDocument) AddImport(impt GoImport) error {
impt.Name = strings.Trim(impt.Name, "\"")
// Get a list of current imports for the document.
impts, err := doc.Imports()
if err != nil {
return err
}
// If the document has as the import, don't add it.
if impts.Has(impt.Name) {
return nil
}
// Loop through all the document root objects for an object of type import.
// If one exists, append the import to the existing list.
for _, obj := range doc.Objects().List() {
if obj.Type != GoObjectType_Import || len(obj.Lines()) == 1 {
continue
}
obj.subLines = append(obj.subLines, impt.String())
obj.goObjects.list = append(obj.goObjects.list, impt.Object())
doc.imports = append(doc.imports, impt)
return nil
}
// Document does not have an existing import object, so need to create one and
// then append to the document.
newObj := NewObjectImports(impt)
// Insert after any package, any existing comments or line breaks should be included.
var insertPos int
for idx, obj := range doc.Objects().List() {
switch obj.Type {
case GoObjectType_Package, GoObjectType_Comment, GoObjectType_LineBreak:
insertPos = idx
default:
break
}
}
// Insert the new import object.
err = doc.InsertObject(insertPos, newObj)
if err != nil {
return err
}
return nil
}
// NewObjectImports returns a new GoObject with a single import definition.
func NewObjectImports(impt GoImport) *GoObject {
lines := []string{
"import (",
impt.String(),
")",
"",
}
obj, _ := ParseGoObject(lines, 0)
children, err := ParseLines(obj.subLines, 1)
if err != nil {
return nil
}
for _, child := range children.List() {
obj.Objects().Add(child)
}
return obj
}
// Has checks to see if an import exists by name or alias.
func (impts GoImports) Has(name string) bool {
for _, impt := range impts {
if name == impt.Name || (impt.Alias != "" && name == impt.Alias) {
return true
}
}
return false
}
// Line formats an import as a string.
func (impt GoImport) String() string {
var imptLine string
if impt.Alias != "" {
imptLine = fmt.Sprintf("\t%s \"%s\"", impt.Alias, impt.Name)
} else {
imptLine = fmt.Sprintf("\t\"%s\"", impt.Name)
}
return imptLine
}
// Object returns the first GoObject for an import.
func (impt GoImport) Object() *GoObject {
imptObj := NewObjectImports(impt)
return imptObj.Objects().List()[0]
}
// ParseImportObject extracts all the import definitions.
func ParseImportObject(obj *GoObject) (resp GoImports, err error) {
if obj.Type != GoObjectType_Import {
return resp, errors.Errorf("Invalid type %s", string(obj.Type))
}
for _, l := range obj.Lines() {
if !strings.Contains(l, "\"") {
continue
}
l = strings.TrimSpace(l)
pts := strings.Split(l, "\"")
var impt GoImport
if strings.HasPrefix(l, "\"") {
impt.Name = pts[1]
} else {
impt.Alias = strings.TrimSpace(pts[0])
impt.Name = pts[1]
}
resp = append(resp, impt)
}
return resp, nil
}

View File

@ -0,0 +1,458 @@
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
}

View File

@ -0,0 +1,352 @@
package goparse
import (
"bufio"
"bytes"
"fmt"
"go/format"
"io/ioutil"
"log"
"strings"
"unicode"
"github.com/pkg/errors"
)
var (
errGoParseType = errors.New("Unable to determine type for line")
errGoTypeMissingCodeTemplate = errors.New("No code defined for type")
errGoObjectNotExist = errors.New("GoObject does not exist")
)
// ParseFile reads a go code file and parses into a easily transformable set of objects.
func ParseFile(log *log.Logger, localPath string) (*GoDocument, error) {
// Read the code file.
src, err := ioutil.ReadFile(localPath)
if err != nil {
err = errors.WithMessagef(err, "Failed to read file %s", localPath)
return nil, err
}
// Format the code file source to ensure parse works.
dat, err := format.Source(src)
if err != nil {
err = errors.WithMessagef(err, "Failed to format source for file %s", localPath)
log.Printf("ParseFile : %v\n%v", err, string(src))
return nil, err
}
// Loop of the formatted source code and generate a list of code lines.
lines := []string{}
r := bytes.NewReader(dat)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
err = errors.WithMessagef(err, "Failed read formatted source code for file %s", localPath)
return nil, err
}
// Parse the code lines into a set of objects.
objs, err := ParseLines(lines, 0)
if err != nil {
log.Println(err)
return nil, err
}
// Append the resulting objects to the document.
doc := &GoDocument{}
for _, obj := range objs.List() {
if obj.Type == GoObjectType_Package {
doc.Package = obj.Name
}
doc.AddObject(obj)
}
return doc, nil
}
// ParseLines takes the list of formatted code lines and returns the GoObjects.
func ParseLines(lines []string, depth int) (objs *GoObjects, err error) {
objs = &GoObjects{
list: []*GoObject{},
}
var (
multiLine bool
multiComment bool
muiliVar bool
)
curDepth := -1
objLines := []string{}
for idx, l := range lines {
ls := strings.TrimSpace(l)
ld := lineDepth(l)
//fmt.Println("l", l)
//fmt.Println("> Depth", ld, "???", depth)
if ld == depth {
if strings.HasPrefix(ls, "/*") {
multiLine = true
multiComment = true
} else if strings.HasSuffix(ls, "(") ||
strings.HasSuffix(ls, "{") {
if !multiLine {
multiLine = true
}
} else if strings.Contains(ls, "`") {
if !multiLine && strings.Count(ls, "`")%2 != 0 {
if muiliVar {
muiliVar = false
} else {
muiliVar = true
}
}
}
//fmt.Println("> multiLine", multiLine)
//fmt.Println("> multiComment", multiComment)
//fmt.Println("> muiliVar", muiliVar)
objLines = append(objLines, l)
if multiComment {
if strings.HasSuffix(ls, "*/") {
multiComment = false
multiLine = false
}
} else {
if strings.HasPrefix(ls, ")") ||
strings.HasPrefix(ls, "}") {
multiLine = false
}
}
if !multiLine && !muiliVar {
for eidx := idx + 1; eidx < len(lines); eidx++ {
if el := lines[eidx]; strings.TrimSpace(el) == "" {
objLines = append(objLines, el)
} else {
break
}
}
//fmt.Println(" > objLines", objLines)
obj, err := ParseGoObject(objLines, depth)
if err != nil {
log.Println(err)
return objs, err
}
err = objs.Add(obj)
if err != nil {
log.Println(err)
return objs, err
}
objLines = []string{}
}
} else if (multiLine && ld >= curDepth && ld >= depth && len(objLines) > 0) || muiliVar {
objLines = append(objLines, l)
if strings.Contains(ls, "`") {
if !multiLine && strings.Count(ls, "`")%2 != 0 {
if muiliVar {
muiliVar = false
} else {
muiliVar = true
}
}
}
}
}
for _, obj := range objs.List() {
children, err := ParseLines(obj.subLines, depth+1)
if err != nil {
log.Println(err)
return objs, err
}
for _, child := range children.List() {
obj.Objects().Add(child)
}
}
return objs, nil
}
// ParseGoObject generates a GoObjected for the given code lines.
func ParseGoObject(lines []string, depth int) (obj *GoObject, err error) {
// If there are no lines, return a line break.
if len(lines) == 0 {
return &GoEmptyLine, nil
}
firstLine := lines[0]
firstStrip := strings.TrimSpace(firstLine)
if len(firstStrip) == 0 {
return &GoEmptyLine, nil
}
obj = &GoObject{
goObjects: &GoObjects{
list: []*GoObject{},
},
}
if strings.HasPrefix(firstStrip, "var") {
obj.Type = GoObjectType_Var
if !strings.HasSuffix(firstStrip, "(") {
if strings.HasPrefix(firstStrip, "var ") {
firstStrip = strings.TrimSpace(strings.Replace(firstStrip, "var ", "", 1))
}
obj.Name = strings.Split(firstStrip, " ")[0]
}
} else if strings.HasPrefix(firstStrip, "const") {
obj.Type = GoObjectType_Const
if !strings.HasSuffix(firstStrip, "(") {
if strings.HasPrefix(firstStrip, "const ") {
firstStrip = strings.TrimSpace(strings.Replace(firstStrip, "const ", "", 1))
}
obj.Name = strings.Split(firstStrip, " ")[0]
}
} else if strings.HasPrefix(firstStrip, "func") {
obj.Type = GoObjectType_Func
if strings.HasPrefix(firstStrip, "func (") {
funcLine := strings.TrimLeft(strings.TrimSpace(strings.Replace(firstStrip, "func ", "", 1)), "(")
var structName string
pts := strings.Split(strings.Split(funcLine, ")")[0], " ")
for i := len(pts) - 1; i >= 0; i-- {
ptVal := strings.TrimSpace(pts[i])
if ptVal != "" {
structName = ptVal
break
}
}
var funcName string
pts = strings.Split(strings.Split(funcLine, "(")[0], " ")
for i := len(pts) - 1; i >= 0; i-- {
ptVal := strings.TrimSpace(pts[i])
if ptVal != "" {
funcName = ptVal
break
}
}
obj.Name = fmt.Sprintf("%s.%s", structName, funcName)
} else {
obj.Name = strings.Replace(firstStrip, "func ", "", 1)
obj.Name = strings.Split(obj.Name, "(")[0]
}
} else if strings.HasSuffix(firstStrip, "struct {") || strings.HasSuffix(firstStrip, "struct{") {
obj.Type = GoObjectType_Struct
if strings.HasPrefix(firstStrip, "type ") {
firstStrip = strings.TrimSpace(strings.Replace(firstStrip, "type ", "", 1))
}
obj.Name = strings.Split(firstStrip, " ")[0]
} else if strings.HasPrefix(firstStrip, "type") {
obj.Type = GoObjectType_Type
firstStrip = strings.TrimSpace(strings.Replace(firstStrip, "type ", "", 1))
obj.Name = strings.Split(firstStrip, " ")[0]
} else if strings.HasPrefix(firstStrip, "package") {
obj.Name = strings.TrimSpace(strings.Replace(firstStrip, "package ", "", 1))
obj.Type = GoObjectType_Package
} else if strings.HasPrefix(firstStrip, "import") {
obj.Type = GoObjectType_Import
} else if strings.HasPrefix(firstStrip, "//") {
obj.Type = GoObjectType_Comment
} else if strings.HasPrefix(firstStrip, "/*") {
obj.Type = GoObjectType_Comment
} else {
if depth > 0 {
obj.Type = GoObjectType_Line
} else {
err = errors.WithStack(errGoParseType)
return obj, err
}
}
var (
hasSub bool
muiliVarStart bool
muiliVarSub bool
muiliVarEnd bool
)
for _, l := range lines {
ld := lineDepth(l)
if (ld == depth && !muiliVarSub) || muiliVarStart || muiliVarEnd {
if hasSub && !muiliVarStart {
if strings.TrimSpace(l) != "" {
obj.endLines = append(obj.endLines, l)
}
if strings.Count(l, "`")%2 != 0 {
if muiliVarEnd {
muiliVarEnd = false
} else {
muiliVarEnd = true
}
}
} else {
obj.startLines = append(obj.startLines, l)
if strings.Count(l, "`")%2 != 0 {
if muiliVarStart {
muiliVarStart = false
} else {
muiliVarStart = true
}
}
}
} else if ld > depth || muiliVarSub {
obj.subLines = append(obj.subLines, l)
hasSub = true
if strings.Count(l, "`")%2 != 0 {
if muiliVarSub {
muiliVarSub = false
} else {
muiliVarSub = true
}
}
}
}
// add trailing linebreak
if len(obj.endLines) > 0 {
obj.endLines = append(obj.endLines, "")
}
return obj, err
}
// lineDepth returns the number of spaces for the given code line
// used to determine the code level for nesting objects.
func lineDepth(l string) int {
depth := len(l) - len(strings.TrimLeftFunc(l, unicode.IsSpace))
ls := strings.TrimSpace(l)
if strings.HasPrefix(ls, "}") && strings.Contains(ls, " else ") {
depth = depth + 1
} else if strings.HasPrefix(ls, "case ") {
depth = depth + 1
}
return depth
}

View File

@ -0,0 +1,201 @@
package goparse
import (
"log"
"os"
"strings"
"testing"
"github.com/onsi/gomega"
)
var logger *log.Logger
func init() {
logger = log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
}
func TestMultilineVar(t *testing.T) {
g := gomega.NewGomegaWithT(t)
code := `func ContextAllowedAccountIds(ctx context.Context, db *gorm.DB) (resp akdatamodels.Uint32List, err error) {
resp = []uint32{}
accountId := akcontext.ContextAccountId(ctx)
m := datamodels.UserAccount{}
q := fmt.Sprintf("select
distinct account_id
from %s where account_id = ?", m.TableName())
db = db.Raw(q, accountId)
}
`
code = strings.Replace(code, "\"", "`", -1)
lines := strings.Split(code, "\n")
objs, err := ParseLines(lines, 0)
if err != nil {
t.Fatalf("got error %v", err)
}
g.Expect(objs.Lines()).Should(gomega.Equal(lines))
}
func TestNewDocImports(t *testing.T) {
g := gomega.NewGomegaWithT(t)
expected := []string{
"package goparse",
"",
"import (",
" \"github.com/go/pkg1\"",
" \"github.com/go/pkg2\"",
")",
"",
}
doc := &GoDocument{}
doc.SetPackage("goparse")
doc.AddImport(GoImport{Name: "github.com/go/pkg1"})
doc.AddImport(GoImport{Name: "github.com/go/pkg2"})
g.Expect(doc.Lines()).Should(gomega.Equal(expected))
}
func TestParseLines1(t *testing.T) {
g := gomega.NewGomegaWithT(t)
codeTests := []string{
`func testCreate(t *testing.T, ctx context.Context, sess *datamodels.Session) *datamodels.Model {
g := gomega.NewGomegaWithT(t)
obj := datamodels.MockModelNew()
resp, err := ModelCreate(ctx, DB, &obj)
if err != nil {
t.Fatalf("got error %v", err)
}
g.Expect(resp.Name).Should(gomega.Equal(obj.Name))
g.Expect(resp.Status).Should(gomega.Equal(datamodels.{{ .Name }}Status_Active))
return resp
}
`,
`var (
// ErrNotFound abstracts the postgres not found error.
ErrNotFound = errors.New("Entity not found")
// ErrInvalidID occurs when an ID is not in a valid form.
ErrInvalidID = errors.New("ID is not in its proper form")
// ErrForbidden occurs when a user tries to do something that is forbidden to them according to our access control policies.
ErrForbidden = errors.New("Attempted action is not allowed")
)
`,
}
for _, code := range codeTests {
lines := strings.Split(code, "\n")
objs, err := ParseLines(lines, 0)
if err != nil {
t.Fatalf("got error %v", err)
}
g.Expect(objs.Lines()).Should(gomega.Equal(lines))
}
}
func TestParseLines2(t *testing.T) {
code := `func structToMap(s interface{}) (resp map[string]interface{}) {
dat, _ := json.Marshal(s)
_ = json.Unmarshal(dat, &resp)
for k, x := range resp {
switch v := x.(type) {
case time.Time:
if v.IsZero() {
delete(resp, k)
}
case *time.Time:
if v == nil || v.IsZero() {
delete(resp, k)
}
case nil:
delete(resp, k)
}
}
return resp
}
`
lines := strings.Split(code, "\n")
objs, err := ParseLines(lines, 0)
if err != nil {
t.Fatalf("got error %v", err)
}
testLineTextMatches(t, objs.Lines(), lines)
}
func TestParseLines3(t *testing.T) {
g := gomega.NewGomegaWithT(t)
code := `type UserAccountRoleName = string
const (
UserAccountRoleName_None UserAccountRoleName = ""
UserAccountRoleName_Admin UserAccountRoleName = "admin"
UserAccountRoleName_User UserAccountRoleName = "user"
)
type UserAccountRole struct {
Id uint32 ^gorm:"column:id;type:int(10) unsigned AUTO_INCREMENT;primary_key;not null;auto_increment;" truss:"internal:true"^
CreatedAt time.Time ^gorm:"column:created_at;type:datetime;default:CURRENT_TIMESTAMP;not null;" truss:"internal:true"^
UpdatedAt time.Time ^gorm:"column:updated_at;type:datetime;" truss:"internal:true"^
DeletedAt *time.Time ^gorm:"column:deleted_at;type:datetime;" truss:"internal:true"^
Role UserAccountRoleName ^gorm:"unique_index:user_account_role;column:role;type:enum('admin', 'user')"^
// belongs to User
User *User ^gorm:"foreignkey:UserId;association_foreignkey:Id;association_autoupdate:false;association_autocreate:false;association_save_reference:false;preload:false;" truss:"internal:true"^
UserId uint32 ^gorm:"unique_index:user_account_role;"^
// belongs to Account
Account *Account ^gorm:"foreignkey:AccountId;association_foreignkey:Id;association_autoupdate:false;association_autocreate:false;association_save_reference:false;preload:false;" truss:"internal:true;api_ro:true;"^
AccountId uint32 ^gorm:"unique_index:user_account_role;" truss:"internal:true;api_ro:true;"^
}
func (UserAccountRole) TableName() string {
return "user_account_roles"
}
`
code = strings.Replace(code, "^", "'", -1)
lines := strings.Split(code, "\n")
objs, err := ParseLines(lines, 0)
if err != nil {
t.Fatalf("got error %v", err)
}
g.Expect(objs.Lines()).Should(gomega.Equal(lines))
}
func testLineTextMatches(t *testing.T, l1, l2 []string) {
g := gomega.NewGomegaWithT(t)
m1 := []string{}
for _, l := range l1 {
l = strings.TrimSpace(l)
if l != "" {
m1 = append(m1, l)
}
}
m2 := []string{}
for _, l := range l2 {
l = strings.TrimSpace(l)
if l != "" {
m2 = append(m2, l)
}
}
g.Expect(m1).Should(gomega.Equal(m2))
}