goby-lang/api.doc

View on GitHub
parser.go

Summary

Maintainability
A
2 hrs
Test Coverage
package main

import (
    "encoding/json"
    "fmt"
    "go/ast"
    "go/parser"
    "go/token"
    "html/template"
    "io/ioutil"
    "strings"
)

func ClassesFromDir(dir string) []Class {
    classes := []Class{}
    files, err := ioutil.ReadDir(dir)
    if err != nil {
        panic(err)
    }
    for _, file := range files {
        if strings.Contains(file.Name(), "spec.go") {
            continue
        }
        filename := dir + "/" + file.Name()
        class := classFromFile(filename)
        if class.Line != 0 || class.ClassMethods != nil || class.InstanceMethods != nil {
            class.Filename = strings.Replace(file.Name(), ".go", "", 1)
            classes = append(classes, class)
        }
    }
    return classes
}

func classFromFile(filepath string) Class {
    class := Class{}
    class.SetClassname(filepath)

    // Parse target file
    fset := token.NewFileSet()
    f, err := parser.ParseFile(fset, filepath, nil, parser.ParseComments)
    if err != nil {
        fmt.Println(err)
        return class
    }

    // Get comments
    allComments := AllComments{fset, f.Comments}
    // ast.Print(fset, f.Comments)

    // Find class & methods
    var classMethods *ast.ValueSpec
    var instanceMethods *ast.ValueSpec
    // Loop through declarations
    for _, decl := range f.Decls {
        // Continue only for general declarations
        if genDecl, ok := decl.(*ast.GenDecl); ok {
            for _, spec := range genDecl.Specs {
                // Assign class line number if found
                if tSpec, ok := spec.(*ast.TypeSpec); ok && class.MatchName(tSpec.Name.Name) {
                    node := tSpec.Name
                    class.Line = fset.Position(node.NamePos).Line
                }
                // Assign class methods if found
                if vSpec, ok := spec.(*ast.ValueSpec); ok && class.MatchClassMethods(vSpec.Names[0].Name) {
                    classMethods = vSpec
                }
                // Assign instance methods if found
                if vSpec, ok := spec.(*ast.ValueSpec); ok && class.MatchInstanceMethods(vSpec.Names[0].Name) {
                    instanceMethods = vSpec
                }
            }
        }
    }

    // Return blank class if class definition is not found
    // if class.Line == 0 {
    //     return class
    // }

    // Retrieve class comments
    comments := allComments.findCommentFor(class.Line)
    class.Comment = template.HTML(comments.Description)

    // Loop through instance methods to find each method
    if classMethods != nil {
        class.ClassMethods = retrieveMethodsFromNode(fset, classMethods, allComments)
    }
    if instanceMethods != nil {
        class.InstanceMethods = retrieveMethodsFromNode(fset, instanceMethods, allComments)
    }
    return class
}

func retrieveMethodsFromNode(fset *token.FileSet, valueSpec *ast.ValueSpec, allComments AllComments) []Method {
    methods := []Method{}
    allExpr := valueSpec.Values[0].(*ast.CompositeLit).Elts
    var attrs []ast.Expr
    for _, expr := range allExpr {
        attrs = expr.(*ast.CompositeLit).Elts
        method := Method{}
        // Attributes should only contain "Name" & "Fn" for now
        for _, attr := range attrs {
            thisExpr := attr.(*ast.KeyValueExpr)
            name := thisExpr.Key.(*ast.Ident).Name
            if name == "Name" {
                method.FnName = strings.Replace(thisExpr.Value.(*ast.BasicLit).Value, "\"", "", -1)
                method.FnLine = fset.Position(thisExpr.Key.(*ast.Ident).NamePos).Line
            }
            if name == "Fn" {
                methodComments := allComments.findCommentFor(method.FnLine)
                method.Params = methodComments.Params
                method.Returns = methodComments.Returns
                method.Comment = template.HTML(methodComments.Description)
            }
        }
        methods = append(methods, method)
    }
    return methods
}

func Write(filepath string, classes []Class) {
    b, err := json.Marshal(classes)
    if err != nil {
        panic(err)
    }
    fmt.Println("Generated:", filepath)
    err = ioutil.WriteFile(filepath, b, 0644)
    if err != nil {
        panic(err)
    }
}

func InsertLinkToComment(text string, class_name string) string {
    t := text
    puncs := []string{" ", ",", ".", ";", "\n"}
    class_link := " [" + class_name + "](" + class_name + ".html) "
    split_t := strings.Split(t, "```")
    for i, _ := range split_t {
        if i%2 == 1 {
            continue
        }
        for _, punc := range puncs {
            split_t[i] = strings.Replace(split_t[i], " "+class_name+punc, class_link, -1)
        }
    }
    return strings.Join(split_t, "```")
}

func DirectInsertLinkToComment(text string, class_name string) string {
    class_link := " [" + class_name + "](" + class_name + ".html) "
    return strings.Replace(text, class_name, class_link, -1)
}

func insertClassLinksForMethods(methods Methods, classes Classes) Methods {
    // loop methods in a class
    for i, method := range methods {
        text := string(method.Comment)
        // insert link to method comment
        for _, each_class := range classes {
            text = InsertLinkToComment(text, each_class.Name)
        }
        methods[i].Comment = template.HTML(text)

        // insert link to params
        for j, param := range method.Params {
            c := string(param.Class)
            d := string(param.Description)
            for _, each_class := range classes {
                c = DirectInsertLinkToComment(c, each_class.Name)
                d = DirectInsertLinkToComment(d, each_class.Name)
            }
            param.Class = template.HTML(c)
            param.Description = template.HTML(d)
            methods[i].Params[j] = param
        }

        // insert link to returns
        for j, r := range method.Returns {
            c := string(r.Class)
            d := string(r.Description)
            for _, each_class := range classes {
                c = DirectInsertLinkToComment(c, each_class.Name)
                d = DirectInsertLinkToComment(d, each_class.Name)
            }
            r.Class = template.HTML(c)
            r.Description = template.HTML(d)
            methods[i].Returns[j] = r
        }

    }

    return methods
}

func InsertClassLinks(classes Classes) Classes {
    var returned_classes Classes
    // loop classes
    for _, class := range classes {
        text := string(class.Comment)
        // insert link to class comment
        for _, each_class := range classes {
            text = InsertLinkToComment(text, each_class.Name)
        }

        class.InstanceMethods = insertClassLinksForMethods(class.InstanceMethods, classes)
        class.ClassMethods = insertClassLinksForMethods(class.ClassMethods, classes)

        class.Comment = template.HTML(text)
        returned_classes = append(returned_classes, class)
    }

    return returned_classes
}