github.com/mmatczuk/gohan@v0.0.0-20170206152520-30e45d9bdb69/cli/template.go (about)

     1  package cli
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"regexp"
     9  
    10  	"github.com/cloudwan/gohan/schema"
    11  	"github.com/cloudwan/gohan/util"
    12  	"github.com/codegangsta/cli"
    13  
    14  	"github.com/flosch/pongo2"
    15  	"io/ioutil"
    16  	"strings"
    17  )
    18  
    19  func deleteGohanExtendedProperties(node map[string]interface{}) {
    20  	extendedProperties := [...]string{"unique", "permission", "relation",
    21  		"relation_property", "view", "detail_view", "propertiesOrder",
    22  		"on_delete_cascade", "indexed", "relationColumn" }
    23  
    24  	for _, extendedProperty := range extendedProperties {
    25  		delete(node, extendedProperty)
    26  	}
    27  }
    28  
    29  func fixEnumDefaultValue(node map[string]interface{}) {
    30  	if defaultValue, ok := node["default"]; ok {
    31  		if enums, ok := node["enum"]; ok {
    32  			if defaultValueStr, ok := defaultValue.(string); ok {
    33  				enumsArr := util.MaybeStringList(enums)
    34  				if !util.ContainsString(enumsArr, defaultValueStr) {
    35  					delete(node, "default")
    36  				}
    37  			}
    38  		}
    39  	}
    40  }
    41  
    42  func removeEmptyRequiredList(node map[string]interface{}) {
    43  	const requiredProperty = "required"
    44  
    45  	if required, ok := node[requiredProperty]; ok {
    46  		switch list := required.(type) {
    47  		case []string:
    48  			if len(list) == 0 {
    49  				delete(node, requiredProperty)
    50  			}
    51  		case []interface{}:
    52  			if len(list) == 0 {
    53  				delete(node, requiredProperty)
    54  			}
    55  		}
    56  	}
    57  }
    58  
    59  func removeNotSupportedFormat(node map[string]interface{}) {
    60  	const formatProperty string = "format"
    61  	var allowedFormats = []string{"uri", "uuid", "email", "int32", "int64", "float", "double",
    62  		"byte", "binary", "date", "date-time", "password"}
    63  
    64  	if format, ok := node[formatProperty]; ok {
    65  		if format, ok := format.(string); ok {
    66  			if !util.ContainsString(allowedFormats, format) {
    67  				delete(node, formatProperty)
    68  			}
    69  		}
    70  	}
    71  }
    72  
    73  func fixPropertyTree(node map[string]interface{}) {
    74  
    75  	deleteGohanExtendedProperties(node)
    76  	fixEnumDefaultValue(node)
    77  	removeEmptyRequiredList(node)
    78  	removeNotSupportedFormat(node)
    79  
    80  	for _, value := range node {
    81  		switch childs := value.(type) {
    82  		case map[string]interface{}:
    83  			fixPropertyTree(childs)
    84  		case map[string]map[string]interface{}:
    85  			for _, value := range childs {
    86  				fixPropertyTree(value)
    87  			}
    88  		}
    89  	}
    90  
    91  }
    92  
    93  func toSwagger(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
    94  	i := in.Interface()
    95  	m := i.(map[string]interface{})
    96  
    97  	fixPropertyTree(m)
    98  
    99  	data, _ := json.MarshalIndent(i, param.String(), "    ")
   100  	return pongo2.AsValue(string(data)), nil
   101  }
   102  
   103  func toSwaggerPath(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
   104  	i := in.String()
   105  	r := regexp.MustCompile(":([^/]+)")
   106  	return pongo2.AsValue(r.ReplaceAllString(i, "{$1}")), nil
   107  }
   108  
   109  func hasIdParam(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
   110  	i := in.String()
   111  	return pongo2.AsValue(strings.Contains(i, ":id")), nil
   112  }
   113  
   114  func init() {
   115  	pongo2.RegisterFilter("swagger", toSwagger)
   116  	pongo2.RegisterFilter("swagger_path", toSwaggerPath)
   117  	pongo2.RegisterFilter("swagger_has_id_param", hasIdParam)
   118  }
   119  
   120  func doTemplate(c *cli.Context) {
   121  	template := c.String("template")
   122  	manager := schema.GetManager()
   123  	configFile := c.String("config-file")
   124  	config := util.GetConfig()
   125  	err := config.ReadConfig(configFile)
   126  	if err != nil {
   127  		util.ExitFatal(err)
   128  		return
   129  	}
   130  	templateCode, err := util.GetContent(template)
   131  	if err != nil {
   132  		util.ExitFatal(err)
   133  		return
   134  	}
   135  	pwd, _ := os.Getwd()
   136  	os.Chdir(path.Dir(configFile))
   137  	schemaFiles := config.GetStringList("schemas", nil)
   138  	if schemaFiles == nil {
   139  		util.ExitFatal("No schema specified in configuraion")
   140  	} else {
   141  		err = manager.LoadSchemasFromFiles(schemaFiles...)
   142  		if err != nil {
   143  			util.ExitFatal(err)
   144  			return
   145  		}
   146  	}
   147  	schemas := manager.OrderedSchemas()
   148  
   149  	if err != nil {
   150  		util.ExitFatal(err)
   151  		return
   152  	}
   153  	tpl, err := pongo2.FromString(string(templateCode))
   154  	if err != nil {
   155  		util.ExitFatal(err)
   156  		return
   157  	}
   158  	if c.IsSet("split-by-resource-group") {
   159  		saveAllResources(schemas, tpl)
   160  		return
   161  	}
   162  	policies := manager.Policies()
   163  	policy := c.String("policy")
   164  	schemasPolicy := filterSchemasForPolicy(policy, policies, schemas)
   165  	output, err := tpl.Execute(pongo2.Context{"schemas": schemasPolicy})
   166  	if err != nil {
   167  		util.ExitFatal(err)
   168  		return
   169  	}
   170  	os.Chdir(pwd)
   171  	fmt.Println(output)
   172  }
   173  
   174  func saveAllResources(schemas []*schema.Schema, tpl *pongo2.Template) {
   175  	for _, resource := range getAllResourcesFromSchemas(schemas) {
   176  		resourceSchemas := filerSchemasByResource(resource, schemas)
   177  		output, _ := tpl.Execute(pongo2.Context{"schemas": resourceSchemas})
   178  		ioutil.WriteFile(resource+".json", []byte(output), 0644)
   179  	}
   180  }
   181  
   182  func getAllResourcesFromSchemas(schemas []*schema.Schema) []string {
   183  	resourcesSet := make(map[string]bool)
   184  	for _, schema := range schemas {
   185  		metadata, _ := schema.Metadata["resource_group"].(string)
   186  		resourcesSet[metadata] = true
   187  	}
   188  	resources := make([]string, 0, len(resourcesSet))
   189  	for resource := range resourcesSet {
   190  		resources = append(resources, resource)
   191  	}
   192  	return resources
   193  }
   194  
   195  func filerSchemasByResource(resource string, schemas []*schema.Schema) []*schema.Schema {
   196  	var filteredSchemas []*schema.Schema
   197  	for _, schema := range schemas {
   198  		if schema.Metadata["resource_group"] == resource {
   199  			filteredSchemas = append(filteredSchemas, schema)
   200  		}
   201  	}
   202  	return filteredSchemas
   203  }
   204  
   205  func filterSchemasForPolicy(principal string, policies []*schema.Policy, schemas []*schema.Schema) []*schema.Schema {
   206  	var schemasPolicy []*schema.Schema
   207  	var matchedPolicies []* schema.Policy
   208  	for _, policy := range policies {
   209  		if policy.Principal == principal {
   210  			matchedPolicies = append(matchedPolicies, policy)
   211  		}
   212  	}
   213  	for _, schema := range schemas {
   214  		for _, policy := range matchedPolicies {
   215  			if policy.Resource.Path.MatchString(schema.URL)  {
   216  				schemasPolicy = append(schemasPolicy, schema)
   217  				break
   218  			}
   219  		}
   220  	}
   221  	return schemasPolicy
   222  }
   223  
   224  func getTemplateCommand() cli.Command {
   225  	return cli.Command{
   226  		Name:        "template",
   227  		ShortName:   "template",
   228  		Usage:       "Convert gohan schema using pongo2 template",
   229  		Description: "Convert gohan schema using pongo2 template",
   230  		Flags: []cli.Flag{
   231  			cli.StringFlag{Name: "config-file", Value: "gohan.yaml", Usage: "Server config File"},
   232  			cli.StringFlag{Name: "template, t", Value: "", Usage: "Template File"},
   233  			cli.StringFlag{Name: "split-by-resource-group", Value: "", Usage: "Group by resource"},
   234  			cli.StringFlag{Name: "policy", Value: "admin", Usage: "Policy"},
   235  		},
   236  		Action: doTemplate,
   237  	}
   238  }
   239  
   240  func getOpenAPICommand() cli.Command {
   241  	return cli.Command{
   242  		Name:        "openapi",
   243  		ShortName:   "openapi",
   244  		Usage:       "Convert gohan schema to OpenAPI",
   245  		Description: "Convert gohan schema to OpenAPI",
   246  		Flags: []cli.Flag{
   247  			cli.StringFlag{Name: "config-file", Value: "gohan.yaml", Usage: "Server config File"},
   248  			cli.StringFlag{Name: "template, t", Value: "embed://etc/templates/openapi.tmpl", Usage: "Template File"},
   249  			cli.StringFlag{Name: "split-by-resource-group", Value: "", Usage: "Group by resource"},
   250  			cli.StringFlag{Name: "policy", Value: "admin", Usage: "Policy"},
   251  		},
   252  		Action: doTemplate,
   253  	}
   254  }
   255  
   256  func getMarkdownCommand() cli.Command {
   257  	return cli.Command{
   258  		Name:        "markdown",
   259  		ShortName:   "markdown",
   260  		Usage:       "Convert gohan schema to markdown doc",
   261  		Description: "Convert gohan schema to markdown doc",
   262  		Flags: []cli.Flag{
   263  			cli.StringFlag{Name: "config-file", Value: "gohan.yaml", Usage: "Server config File"},
   264  			cli.StringFlag{Name: "template, t", Value: "embed://etc/templates/markdown.tmpl", Usage: "Template File"},
   265  			cli.StringFlag{Name: "split-by-resource-group", Value: "", Usage: "Group by resource"},
   266  			cli.StringFlag{Name: "policy", Value: "admin", Usage: "Policy"},
   267  		},
   268  		Action: doTemplate,
   269  	}
   270  }
   271  
   272  func getDotCommand() cli.Command {
   273  	return cli.Command{
   274  		Name:        "dot",
   275  		ShortName:   "dot",
   276  		Usage:       "Convert gohan schema to dot file for graphviz",
   277  		Description: "Convert gohan schema to dot file for graphviz",
   278  		Flags: []cli.Flag{
   279  			cli.StringFlag{Name: "config-file", Value: "gohan.yaml", Usage: "Server config File"},
   280  			cli.StringFlag{Name: "template, t", Value: "embed://etc/templates/dot.tmpl", Usage: "Template File"},
   281  			cli.StringFlag{Name: "split-by-resource-group", Value: "", Usage: "Group by resource"},
   282  			cli.StringFlag{Name: "policy", Value: "admin", Usage: "Policy"},
   283  		},
   284  		Action: doTemplate,
   285  	}
   286  }