github.com/woremacx/kocha@v0.7.1-0.20150731103243-a5889322afc9/cmd/kocha-generate/kocha-generate-controller/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"go/ast"
     8  	"go/format"
     9  	"go/parser"
    10  	"go/token"
    11  	"io"
    12  	"os"
    13  	"path/filepath"
    14  	"reflect"
    15  	"runtime"
    16  	"strings"
    17  
    18  	"github.com/woremacx/kocha"
    19  	"github.com/woremacx/kocha/util"
    20  )
    21  
    22  var (
    23  	routeTableTypeName = reflect.TypeOf(kocha.RouteTable{}).Name()
    24  )
    25  
    26  type generateControllerCommand struct {
    27  	option struct {
    28  		Help bool `short:"h" long:"help"`
    29  	}
    30  }
    31  
    32  func (c *generateControllerCommand) Name() string {
    33  	return "kocha generate controller"
    34  }
    35  
    36  func (c *generateControllerCommand) Usage() string {
    37  	return fmt.Sprintf(`Usage: %s [OPTIONS] NAME
    38  
    39  Generate the skeleton files of controller.
    40  
    41  Options:
    42      -h, --help        display this help and exit
    43  
    44  `, c.Name())
    45  }
    46  
    47  func (c *generateControllerCommand) Option() interface{} {
    48  	return &c.option
    49  }
    50  
    51  // Run generates the controller templates.
    52  func (c *generateControllerCommand) Run(args []string) error {
    53  	if len(args) < 1 || args[0] == "" {
    54  		return fmt.Errorf("no NAME given")
    55  	}
    56  	name := args[0]
    57  	camelCaseName := util.ToCamelCase(name)
    58  	snakeCaseName := util.ToSnakeCase(name)
    59  	receiverName := strings.ToLower(name)
    60  	if len(receiverName) > 1 {
    61  		receiverName = receiverName[:2]
    62  	} else {
    63  		receiverName = receiverName[:1]
    64  	}
    65  	data := map[string]interface{}{
    66  		"Name":     camelCaseName,
    67  		"Receiver": receiverName,
    68  	}
    69  	if err := util.CopyTemplate(
    70  		filepath.Join(skeletonDir("controller"), "controller.go"+util.TemplateSuffix),
    71  		filepath.Join("app", "controller", snakeCaseName+".go"), data); err != nil {
    72  		return err
    73  	}
    74  	if err := util.CopyTemplate(
    75  		filepath.Join(skeletonDir("controller"), "view.html"+util.TemplateSuffix),
    76  		filepath.Join("app", "view", snakeCaseName+".html"), data); err != nil {
    77  		return err
    78  	}
    79  	return addRouteToFile(name)
    80  }
    81  
    82  func addRouteToFile(name string) error {
    83  	routeFilePath := filepath.Join("config", "routes.go")
    84  	fset := token.NewFileSet()
    85  	f, err := parser.ParseFile(fset, routeFilePath, nil, 0)
    86  	if err != nil {
    87  		return fmt.Errorf("failed to read file: %v", err)
    88  	}
    89  	routeStructName := util.ToCamelCase(name)
    90  	routeName := util.ToSnakeCase(name)
    91  	routeTableAST, err := findRouteTableAST(f)
    92  	if err != nil {
    93  		return err
    94  	}
    95  	if routeTableAST == nil {
    96  		return nil
    97  	}
    98  	routeASTs := findRouteASTs(routeTableAST)
    99  	if routeASTs == nil {
   100  		return nil
   101  	}
   102  	if isRouteDefined(routeASTs, routeStructName) {
   103  		return nil
   104  	}
   105  	routeFile, err := os.OpenFile(routeFilePath, os.O_RDWR, 0644)
   106  	if err != nil {
   107  		return fmt.Errorf("failed to open file: %v", err)
   108  	}
   109  	defer routeFile.Close()
   110  	lastRouteAST := routeASTs[len(routeASTs)-1]
   111  	offset := int64(fset.Position(lastRouteAST.End()).Offset)
   112  	var buf bytes.Buffer
   113  	if _, err := io.CopyN(&buf, routeFile, offset); err != nil {
   114  		return fmt.Errorf("failed to read file: %v", err)
   115  	}
   116  	buf.WriteString(fmt.Sprintf(`, {
   117  	Name:       "%s",
   118  	Path:       "/%s",
   119  	Controller: &controller.%s{},
   120  }`, routeName, routeName, routeStructName))
   121  	if _, err := io.Copy(&buf, routeFile); err != nil {
   122  		return fmt.Errorf("failed to read file: %v", err)
   123  	}
   124  	formatted, err := format.Source(buf.Bytes())
   125  	if err != nil {
   126  		return fmt.Errorf("failed to format file: %v", err)
   127  	}
   128  	if _, err := routeFile.WriteAt(formatted, 0); err != nil {
   129  		return fmt.Errorf("failed to update file: %v", err)
   130  	}
   131  	return nil
   132  }
   133  
   134  var ErrRouteTableASTIsFound = errors.New("route table AST is found")
   135  
   136  func findRouteTableAST(file *ast.File) (routeTableAST *ast.CompositeLit, err error) {
   137  	defer func() {
   138  		if e := recover(); e != nil && e != ErrRouteTableASTIsFound {
   139  			err = e.(error)
   140  		}
   141  	}()
   142  	ast.Inspect(file, func(node ast.Node) bool {
   143  		switch aType := node.(type) {
   144  		case *ast.GenDecl:
   145  			if aType.Tok != token.VAR {
   146  				return false
   147  			}
   148  			ast.Inspect(aType, func(n ast.Node) bool {
   149  				switch typ := n.(type) {
   150  				case *ast.CompositeLit:
   151  					switch t := typ.Type.(type) {
   152  					case *ast.Ident:
   153  						if t.Name == routeTableTypeName {
   154  							routeTableAST = typ
   155  							panic(ErrRouteTableASTIsFound)
   156  						}
   157  					}
   158  				}
   159  				return true
   160  			})
   161  		}
   162  		return true
   163  	})
   164  	return routeTableAST, nil
   165  }
   166  
   167  func findRouteASTs(clit *ast.CompositeLit) []*ast.CompositeLit {
   168  	var routeASTs []*ast.CompositeLit
   169  	for _, c := range clit.Elts {
   170  		if a, ok := c.(*ast.CompositeLit); ok {
   171  			routeASTs = append(routeASTs, a)
   172  		}
   173  	}
   174  	return routeASTs
   175  }
   176  
   177  func isRouteDefined(routeASTs []*ast.CompositeLit, routeStructName string) bool {
   178  	for _, a := range routeASTs {
   179  		for _, elt := range a.Elts {
   180  			kv, ok := elt.(*ast.KeyValueExpr)
   181  			if !ok {
   182  				continue
   183  			}
   184  			if kv.Key.(*ast.Ident).Name != "Controller" {
   185  				continue
   186  			}
   187  			unary, ok := kv.Value.(*ast.UnaryExpr)
   188  			if !ok {
   189  				continue
   190  			}
   191  			lit, ok := unary.X.(*ast.CompositeLit)
   192  			if !ok {
   193  				continue
   194  			}
   195  			selector, ok := lit.Type.(*ast.SelectorExpr)
   196  			if !ok {
   197  				continue
   198  			}
   199  			if selector.X.(*ast.Ident).Name == "controller" && selector.Sel.Name == routeStructName {
   200  				return true
   201  			}
   202  		}
   203  	}
   204  	return false
   205  }
   206  
   207  func skeletonDir(name string) string {
   208  	_, filename, _, _ := runtime.Caller(0)
   209  	baseDir := filepath.Dir(filename)
   210  	return filepath.Join(baseDir, "skeleton", name)
   211  }
   212  
   213  func main() {
   214  	util.RunCommand(&generateControllerCommand{})
   215  }