1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-08 23:56:37 +02:00

302 lines
6.7 KiB
Go
Raw Normal View History

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
}