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 }