/
gaea.go
420 lines (381 loc) · 9.98 KB
/
gaea.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
package main
import (
"bufio"
"fmt"
su "github.com/etgryphon/stringUp"
flag "github.com/ogier/pflag"
"io"
"log"
"os"
"os/exec"
"strings"
"errors"
"html/template"
"path/filepath"
"go/parser"
"go/token"
"go/ast"
"bytes"
)
const APP_VERSION = "0.1"
const inputDelim = '\n'
var fileCount int = 0
var fileBytesRead int = 0
var fileBytesWritten int = 0
// The flag package provides a default help printer via -h switch
var versionFlag *bool = flag.Bool("v", false, "Print the version number.")
// Files
var FileSep = string(os.PathSeparator)
var LocalPkgs = []byte{'p','k','g','s','/'}
func main() {
flag.Parse() // Scan the arguments list
fmt.Fprintln(os.Stdout, LOGO)
if *versionFlag {
fmt.Println("Version:", APP_VERSION)
return
}
cmd := flag.Arg(0)
name := flag.Arg(1)
switch cmd {
case "help":
printHelpCommand(nil)
case "init":
createNewProject(name)
case "get":
getNewImport(name)
case "run":
runDevelopmentServer(name)
default:
printHelpCommand("Do Not Recognize command: [" + cmd + "]")
}
}
func printHelpCommand(preamble interface{}) {
if preamble != nil {
fmt.Fprintf(os.Stdout, "%s\n%s\n", preamble, HELP_MESSAGE)
} else {
fmt.Fprintf(os.Stdout, "%s\n", HELP_MESSAGE)
}
}
func createNewProject(name string) {
if len(name) < 1 {
name = readProjectName()
}
camelName := su.CamelCase(name)
projPath := camelName + "Project"
fmt.Fprintf(os.Stdout, "Creating a New Project...[%s] in directory: %s\n", camelName, projPath)
createGAEDirectoryStructure(projPath, camelName)
}
func readProjectName() (name string) {
r := bufio.NewReader(os.Stdin)
fmt.Fprintf(os.Stdout, "Please Enter a Name for your GAE project: ")
name, err := r.ReadString(inputDelim)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
name = strings.Trim(name, " \n")
return name
}
func createGAEDirectoryStructure(path string, name string) {
var err error
// Create Base Project Directory
err = createProjectDirectory("."+FileSep, path, 1)
if err != nil {
panic(err)
}
// Create app.yml
projRoot := "."+FileSep+path+FileSep
err = createProjectFile(name, projRoot, "app.yml", YML_TEMPLATE, 2)
if err != nil {
panic(err)
}
// Create the main folder
err = createProjectDirectory(projRoot, name, 2)
if err != nil {
panic(err)
}
err = createProjectFile(name, projRoot+name+FileSep, name+".go", BASE_GAE_APP_TEMPLATE, 3)
if err != nil {
panic(err)
}
// Create Public Folder
err = createProjectDirectory(projRoot, "public", 2)
if err != nil {
panic(err)
}
// Create Public stylesheets folder
err = createProjectDirectory(projRoot+"public"+FileSep, "stylesheets", 3)
if err != nil {
panic(err)
}
// Create Public Images Folder
err = createProjectDirectory(projRoot+"public"+FileSep, "images", 3)
if err != nil {
panic(err)
}
// Create Public Javascript Folder
err = createProjectDirectory(projRoot+"public"+FileSep, "js", 3)
if err != nil {
panic(err)
}
}
func createProjectDirectory(path, name string, level int) error {
fullPath := path + name
fmtStr := fmt.Sprintf("%%%ds\n", 1+len(name)+(level*2))
exists, err := checkIfPathExists(fullPath)
if !exists {
fmt.Fprintf(os.Stdout, fmtStr, "+"+name)
err = os.Mkdir(fullPath, 0755)
} else {
fmt.Fprintf(os.Stdout, fmtStr, "-"+name)
}
return err
}
func createProjectFile(pkgName, path, name, tmplInstance string, level int) error {
fmtStr := fmt.Sprintf("%%%ds\n", 1+len(name)+(level*2))
fileIO, err := os.Create(path + name)
tmpl, err := template.New(name).Parse(tmplInstance)
if err != nil {
panic(err)
}
err = tmpl.Execute(fileIO, pkgName)
if err != nil {
panic(err)
}
fileIO.Close()
fmt.Fprintf(os.Stdout, fmtStr, "+"+name)
return err
}
func getNewImport(name string) {
var err error
if len(name) < 1 {
printHelpCommand("In order to get a package, you must have a name")
return
}
// check to see if it is present in the GOPATH
pkgIsThere, srcPath, _ := checkIfPackageIsPresent(name)
if !pkgIsThere {
err := fetchExternalPackage(name)
if err != nil {
log.Fatalf("External Package Fetch Error:\n\t%s", err)
}
}
// Convert package to local package
err = convertToLocalPackage(srcPath, name)
if err != nil {
log.Fatalf("Package Conversion Error:\n\t%s", err)
}
}
/*
@private function to check for the presence of a local package
*/
func checkIfPackageIsPresent(name string) (doesExist bool, srcPath, formattedPath string) {
doesExist = false
gopaths := strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator))
fmtString := FileSep+"src"+FileSep
for _,x := range gopaths {
srcPath = x+FileSep+"src"
path := x + fmtString + name + FileSep
formattedPath = filepath.FromSlash(path)
there, _ := checkIfPathExists(formattedPath)
if (there) {
doesExist = true
break
}
}
return
}
/*
@private function to check the presents of a directory
*/
func checkIfPathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil || os.IsExist(err) {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
/*
@private function for actually fetching the package
*/
func fetchExternalPackage(name string) error {
log.Println("Fetching External Package: ", name)
cmd := exec.Command("go", "get", name)
output, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(output))
}
return nil
}
func convertFileToLocalUse(gopath, basePkg, path string, f os.FileInfo, err error) error {
base := filepath.Base(path)
if base[0] == '.' {
return filepath.SkipDir
}
curDir, _ := os.Getwd()
newPath := strings.Replace(path, gopath, curDir+FileSep+"pkgs"+FileSep, -1)
// Now, check what the item is:
info, err := os.Stat(path)
if err != nil {
return filepath.SkipDir
}
if !(info.IsDir()) {
fileCount += 1
err = translateFile(gopath, basePkg, path, newPath)
}
return nil
}
/*
@private function that will copy and translate files to local use
*/
func translateFile(gopath, basePkg, source, dest string) error {
s, err := os.Open(source)
if err != nil {
return nil
}
// Check the destination is valid
_, e := os.Stat(dest)
if e == nil {
return nil
}
sInfo, _ := s.Stat()
buffer := make([]byte, sInfo.Size())
bufferPtr := &buffer
_, err = s.Read(buffer)
if err != nil {
return errors.New(fmt.Sprintf("Could not write read from %s", source))
}
// If it is a go file then we have to re-write the imports to
// have the correct '/pkg' prefix
if (filepath.Ext(source) == ".go"){
fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseFile(fset, "", buffer, parser.PackageClauseOnly)
if err != nil {
fmt.Println(err)
}
// Identify the packages
var pkgName string
ast.Inspect(f, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.Ident:
pkgName = x.Name
break
}
return true
})
baseName := filepath.Base(basePkg)
if (pkgName != baseName){
return nil
}
// Parse the file containing this very example
// but stop after processing the imports.
f, err = parser.ParseFile(fset, "", buffer, parser.ImportsOnly)
if err != nil {
fmt.Println(err)
}
// Print the imports from the file's AST.
for _,s := range f.Imports {
end := len(s.Path.Value)-1
pkg := s.Path.Value[1:end]
found, srcPath, _ := checkIfPackageIsPresent(pkg)
if found {
fmt.Fprintf(os.Stdout, "\tConverting Internal Import ref of [%s] to [pkgs%s%s]\n", pkg, FileSep, pkg)
err = convertToLocalPackage(srcPath, pkg)
if err != nil {
log.Fatalf("Internal Package Conversion Error:\n\t%s", err)
}
idx := bytes.Index(buffer, []byte(pkg))
newBuffer := make([]byte, len(*bufferPtr)+5)
copy(newBuffer, (*bufferPtr)[0:idx])
// have to do something special because there is a null term character here
// that we have to overwrite before we can continue...
l := idx
for _, c := range LocalPkgs {
newBuffer[l] = c
l += 1
}
for _,c := range (*bufferPtr)[idx:]{
newBuffer[l] = c
l += 1
}
bufferPtr = &newBuffer
}
}
}
// If we have gotten here, we know that we have a valid item in a package and can
// create and write to the file.
os.MkdirAll(filepath.Dir(dest), 0755)
d, err := os.Create(dest)
if err != nil {
return nil
}
_, err = d.Write(*bufferPtr)
if err != nil {
return errors.New(fmt.Sprintf("Could not write to %s", dest))
}
return nil
}
/*
@private convert the local package to be used in GAE format
*/
func convertToLocalPackage(root string, name string) error {
fmt.Println("\n\nTransfering GOPATH package [" + name + "] to local use...")
curriedConvertFileToLocalUse := func(path string, f os.FileInfo, err error) error {
return convertFileToLocalUse(root, name, path, f, err)
}
err := filepath.Walk(root+FileSep+name, curriedConvertFileToLocalUse)
if err != nil {
return err
}
printOutTransferInformation(name)
return nil
}
func printOutTransferInformation(name string) {
fmt.Fprintln(os.Stdout, "\nTotals")
fmt.Fprintln(os.Stdout, "\tFiles: ", fileCount)
fmt.Fprintf(os.Stdout, "\nTo Use it in your Google App Engine program:\n\n\timport \"pkgs%s%s\"\n\n", FileSep, name)
}
func runDevelopmentServer(path string) {
if len(path) < 1 {
printHelpCommand("A GAEA project directory must be specified:")
return
}
verified := verifyAppServerExists()
if !verified {
return
}
fmt.Fprintln(os.Stdout, "Running Dev App Server through GAEA...")
cmd := exec.Command("dev_appserver.py", path)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
go io.Copy(os.Stdout, stdout)
go io.Copy(os.Stderr, stderr)
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
}
func verifyAppServerExists() bool {
cmd := exec.Command("which", "dev_appserver.py")
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stdout, "Error: %s\n", string(output))
return false
}
if len(output) == 0 {
fmt.Fprintln(os.Stdout, "Error: Can't find dev_appserver.py in your PATH")
return false
}
return true
}