123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- /*
- * Public Domain Software
- *
- * I (Matthias Ladkau) am the author of the source code in this file.
- * I have placed the source code in this file in the public domain.
- *
- * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
- */
- package parser
- import (
- "bytes"
- "fmt"
- "regexp"
- "strconv"
- "strings"
- "text/template"
- "unicode"
- "devt.de/krotik/common/errorutil"
- "devt.de/krotik/common/stringutil"
- )
- /*
- IndentationLevel is the level of indentation which the pretty printer should use
- */
- const IndentationLevel = 2
- /*
- Map of pretty printer templates for AST nodes
- There is special treatment for NodeVALUE.
- */
- var prettyPrinterMap = map[string]*template.Template{
- NodeArgument + "_2": template.Must(template.New(NodeArgument).Parse("{{.c1}}: {{.c2}}")),
- NodeOperationDefinition + "_1": template.Must(template.New(NodeArgument).Parse("{{.c1}}")),
- NodeOperationDefinition + "_2": template.Must(template.New(NodeArgument).Parse("{{.c1}} {{.c2}}")),
- NodeOperationDefinition + "_3": template.Must(template.New(NodeArgument).Parse("{{.c1}} {{.c2}} {{.c3}}")),
- NodeOperationDefinition + "_4": template.Must(template.New(NodeArgument).Parse("{{.c1}} {{.c2}} {{.c3}} {{.c4}}")),
- NodeOperationDefinition + "_5": template.Must(template.New(NodeArgument).Parse("{{.c1}} {{.c2}} {{.c3}} {{.c4}} {{.c5}}")),
- NodeFragmentDefinition + "_3": template.Must(template.New(NodeArgument).Parse("fragment {{.c1}} {{.c2}} {{.c3}}")),
- NodeFragmentDefinition + "_4": template.Must(template.New(NodeArgument).Parse("fragment {{.c1}} {{.c2}} {{.c3}} {{.c4}}")),
- NodeInlineFragment + "_1": template.Must(template.New(NodeArgument).Parse("... {{.c1}}\n")),
- NodeInlineFragment + "_2": template.Must(template.New(NodeArgument).Parse("... {{.c1}} {{.c2}}\n")),
- NodeInlineFragment + "_3": template.Must(template.New(NodeArgument).Parse("... {{.c1}} {{.c2}} {{.c3}}\n")),
- NodeExecutableDefinition + "_1": template.Must(template.New(NodeArgument).Parse("{{.c1}}")),
- NodeVariableDefinition + "_2": template.Must(template.New(NodeArgument).Parse("{{.c1}}: {{.c2}}")),
- NodeVariableDefinition + "_3": template.Must(template.New(NodeArgument).Parse("{{.c1}}: {{.c2}}{{.c3}}")),
- NodeDirective + "_1": template.Must(template.New(NodeArgument).Parse("@{{.c1}}")),
- NodeDirective + "_2": template.Must(template.New(NodeArgument).Parse("@{{.c1}}{{.c2}}")),
- }
- /*
- PrettyPrint produces a pretty printed EQL query from a given AST.
- */
- func PrettyPrint(ast *ASTNode) (string, error) {
- var visit func(ast *ASTNode, path []*ASTNode) (string, error)
- quoteValue := func(val string, allowNonQuotation bool) string {
- if val == "" {
- return `""`
- }
- isNumber, _ := regexp.MatchString("^[0-9][0-9\\.e-+]*$", val)
- isInlineString, _ := regexp.MatchString("^[a-zA-Z0-9_:.]*$", val)
- if allowNonQuotation && (isNumber || isInlineString) {
- return val
- } else if strings.ContainsRune(val, '"') {
- val = strings.Replace(val, "\"", "\\\"", -1)
- }
- if strings.Contains(val, "\n") {
- return fmt.Sprintf("\"\"\"%v\"\"\"", val)
- }
- return fmt.Sprintf("\"%v\"", val)
- }
- visit = func(ast *ASTNode, path []*ASTNode) (string, error) {
- // Handle special cases which don't have children but values
- if ast.Name == NodeValue {
- v := ast.Token.Val
- _, err := strconv.ParseFloat(v, 32)
- isNum := err == nil
- isConst := stringutil.IndexOf(v, []string{
- "true", "false", "null",
- }) != -1
- return quoteValue(ast.Token.Val, isConst || isNum), nil
- } else if ast.Name == NodeVariable {
- return fmt.Sprintf("$%v", ast.Token.Val), nil
- } else if ast.Name == NodeAlias {
- return fmt.Sprintf("%v :", ast.Token.Val), nil
- } else if ast.Name == NodeFragmentSpread {
- return ppPostProcessing(ast, path, fmt.Sprintf("...%v\n", ast.Token.Val)), nil
- } else if ast.Name == NodeTypeCondition {
- return fmt.Sprintf("on %v", ast.Token.Val), nil
- } else if ast.Name == NodeDefaultValue {
- return fmt.Sprintf("=%v", ast.Token.Val), nil
- }
- var children map[string]string
- var tempKey = ast.Name
- var buf bytes.Buffer
- // First pretty print children
- if len(ast.Children) > 0 {
- children = make(map[string]string)
- for i, child := range ast.Children {
- res, err := visit(child, append(path, child))
- if err != nil {
- return "", err
- }
- children[fmt.Sprint("c", i+1)] = res
- }
- tempKey += fmt.Sprint("_", len(children))
- }
- // Handle special cases requiring children
- if ast.Name == NodeDocument {
- if children != nil {
- i := 1
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i)])
- if ast.Children[i].Name != NodeArguments {
- buf.WriteString("\n\n")
- }
- }
- buf.WriteString(children[fmt.Sprint("c", i)])
- }
- return ppPostProcessing(ast, path, buf.String()), nil
- } else if ast.Name == NodeOperationType || ast.Name == NodeName ||
- ast.Name == NodeFragmentName || ast.Name == NodeType || ast.Name == NodeEnumValue {
- return ast.Token.Val, nil
- } else if ast.Name == NodeArguments {
- buf.WriteString("(")
- if children != nil {
- i := 1
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i)])
- buf.WriteString(", ")
- }
- buf.WriteString(children[fmt.Sprint("c", i)])
- }
- buf.WriteString(")")
- return ppPostProcessing(ast, path, buf.String()), nil
- } else if ast.Name == NodeListValue {
- buf.WriteString("[")
- if children != nil {
- i := 1
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i)])
- buf.WriteString(", ")
- }
- buf.WriteString(children[fmt.Sprint("c", i)])
- }
- buf.WriteString("]")
- return ppPostProcessing(ast, path, buf.String()), nil
- } else if ast.Name == NodeVariableDefinitions {
- buf.WriteString("(")
- if children != nil {
- i := 1
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i)])
- buf.WriteString(", ")
- }
- buf.WriteString(children[fmt.Sprint("c", i)])
- }
- buf.WriteString(")")
- return ppPostProcessing(ast, path, buf.String()), nil
- } else if ast.Name == NodeSelectionSet {
- buf.WriteString("{\n")
- if children != nil {
- i := 1
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i)])
- }
- buf.WriteString(children[fmt.Sprint("c", i)])
- }
- buf.WriteString("}")
- return ppPostProcessing(ast, path, buf.String()), nil
- } else if ast.Name == NodeObjectValue {
- buf.WriteString("{")
- if children != nil {
- i := 1
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i)])
- buf.WriteString(", ")
- }
- buf.WriteString(children[fmt.Sprint("c", i)])
- }
- buf.WriteString("}")
- return ppPostProcessing(ast, path, buf.String()), nil
- } else if ast.Name == NodeObjectField {
- buf.WriteString(ast.Token.Val)
- buf.WriteString(" : ")
- buf.WriteString(children["c1"])
- return buf.String(), nil
- } else if ast.Name == NodeField {
- if children != nil {
- i := 1
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i)])
- if ast.Children[i].Name != NodeArguments {
- buf.WriteString(" ")
- }
- }
- buf.WriteString(children[fmt.Sprint("c", i)])
- buf.WriteString("\n")
- }
- return ppPostProcessing(ast, path, buf.String()), nil
- } else if ast.Name == NodeDirectives {
- if children != nil {
- i := 1
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i)])
- if ast.Children[i].Name != NodeArguments {
- buf.WriteString(" ")
- }
- }
- buf.WriteString(children[fmt.Sprint("c", i)])
- }
- return ppPostProcessing(ast, path, buf.String()), nil
- }
- // Retrieve the template
- temp, ok := prettyPrinterMap[tempKey]
- if !ok {
- return "", fmt.Errorf("Could not find template for %v (tempkey: %v)",
- ast.Name, tempKey)
- }
- // Use the children as parameters for template
- errorutil.AssertOk(temp.Execute(&buf, children))
- return ppPostProcessing(ast, path, buf.String()), nil
- }
- res, err := visit(ast, []*ASTNode{ast})
- return strings.TrimSpace(res), err
- }
- /*
- ppPostProcessing applies post processing rules.
- */
- func ppPostProcessing(ast *ASTNode, path []*ASTNode, ppString string) string {
- ret := ppString
- // Apply indentation
- if len(path) > 1 {
- if stringutil.IndexOf(ast.Name, []string{
- NodeField,
- NodeFragmentSpread,
- NodeInlineFragment,
- }) != -1 {
- parent := path[len(path)-3]
- indentSpaces := stringutil.GenerateRollingString(" ", IndentationLevel)
- ret = strings.ReplaceAll(ret, "\n", "\n"+indentSpaces)
- ret = fmt.Sprintf("%v%v", indentSpaces, ret)
- // Remove indentation from last line unless we have a special case
- if stringutil.IndexOf(parent.Name, []string{
- NodeField,
- NodeOperationDefinition,
- }) == -1 {
- if idx := strings.LastIndex(ret, "\n"); idx != -1 {
- ret = ret[:idx+1] + ret[idx+IndentationLevel+1:]
- }
- }
- }
- }
- // Remove all trailing spaces
- newlineSplit := strings.Split(ret, "\n")
- for i, s := range newlineSplit {
- newlineSplit[i] = strings.TrimRightFunc(s, unicode.IsSpace)
- }
- return strings.Join(newlineSplit, "\n")
- }
|