Skip to content

Commit

Permalink
Re-organize the rev command line tool. Add the "new" command.
Browse files Browse the repository at this point in the history
  • Loading branch information
robfig committed Aug 31, 2012
1 parent e0072fa commit 0904254
Show file tree
Hide file tree
Showing 17 changed files with 540 additions and 198 deletions.
147 changes: 147 additions & 0 deletions cmd/new.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package main

import (
"fmt"
"go/build"
"io/ioutil"
"math/rand"
"os"
"path"
"path/filepath"
"strings"
"text/template"
)

var cmdNew = &Command{
UsageLine: "new [path]",
Short: "create a skeleton Revel application",
Long: `~
~ New creates a few files to get a new Revel application running quickly.
~
~ It puts all of the files in the given directory, taking the final element in
~ the path to be the app name.
~
~ For example:
~ rev new github.com/robfig/chatapp`,
}

func init() {
cmdNew.Run = newApp
}

var (
appDir string
skeletonBase string
)

func newApp(args []string) {
_, err := os.Open(args[0])
if err == nil {
fmt.Fprintf(os.Stderr, "~ Abort: Directory %s already exists.", args[0])
return
}

revelPkg, err := build.Import("github.com/robfig/revel", "", build.FindOnly)
if err != nil {
fmt.Fprintln(os.Stderr, "~ Failed to find revel code.")
return
}

err = os.MkdirAll(args[0], 0777)
if err != nil {
fmt.Fprintln(os.Stderr, "~ Abort: Failed to create directory:", err)
return
}

skeletonBase = path.Join(revelPkg.Dir, "skeleton")
appDir, err = filepath.Abs(args[0])
if err != nil {
fmt.Fprintln(os.Stderr, "~ Abort: Failed to get absolute directory:", err)
return
}

err = filepath.Walk(skeletonBase, copySkeleton)
if err != nil {
fmt.Fprintln(os.Stderr, "~ Failed to copy skeleton:", err)
}

fmt.Fprintln(os.Stdout, "~ Your application is ready:\n~ ", appDir)
}

// copySkeleton copies the skeleton app tree over to a new directory.
func copySkeleton(skelPath string, skelInfo os.FileInfo, err error) error {
// Get the relative path from the skeleton base, and the corresponding path in
// the app directory.
relSkelPath := strings.TrimLeft(skelPath[len(skeletonBase):], string(os.PathSeparator))
appFile := path.Join(appDir, relSkelPath)

if len(relSkelPath) == 0 {
return nil
}

// Create a subdirectory if necessary.
if skelInfo.IsDir() {
err := os.Mkdir(path.Join(appDir, relSkelPath), 0777)
if err != nil {
fmt.Fprintln(os.Stderr, "~ Failed to create directory:", err)
return err
}
return nil
}

// If this is app.conf, we have to render it as a template.
if relSkelPath == "conf/app.conf" {
tmpl, err := template.ParseFiles(skelPath)
if err != nil || tmpl == nil {
fmt.Fprintln(os.Stderr, "Failed to parse skeleton app.conf as a template:", err)
return err
}

f, err := os.Create(appFile)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to create app.conf:", err)
return err
}

err = tmpl.Execute(f, map[string]string{
"AppName": filepath.Base(appDir),
"Secret": genSecret(),
})

if err != nil {
fmt.Fprintln(os.Stderr, "Failed to render template:", err)
return err
}

err = f.Close()
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to close app.conf:", err)
return err
}
return nil
}

// Copy over the files.
skelBytes, err := ioutil.ReadFile(skelPath)
if err != nil {
fmt.Fprintln(os.Stderr, "~ Failed to read file:", err)
return err
}

err = ioutil.WriteFile(appFile, skelBytes, 0666)
if err != nil {
fmt.Fprintln(os.Stderr, "~ Failed to write file:", err)
return err
}
return nil
}

const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

func genSecret() string {
chars := make([]byte, 64)
for i := 0; i < 64; i++ {
chars[i] = alphaNumeric[rand.Intn(len(alphaNumeric))]
}
return string(chars)
}
75 changes: 59 additions & 16 deletions cmd/rev.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,87 @@ package main
import (
"flag"
"fmt"
"log"
"io"
"os"

"github.com/robfig/revel"
"github.com/robfig/revel/harness"
"strings"
"text/template"
)

// Cribbed from the genius organization of the "go" command.
type Command struct {
Run func(args []string)
UsageLine, Short, Long string
}

func (cmd *Command) Name() string {
name := cmd.UsageLine
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}

var commands = []*Command{
cmdRun,
cmdNew,
}

func main() {
fmt.Fprintf(os.Stdout, header)
flag.Usage = usage
flag.Parse()
args := flag.Args()

if len(args) < 1 || len(args) > 2 || args[0] == "help" {
if len(args) < 1 || args[0] == "help" {
if len(args) > 1 {
for _, cmd := range commands {
if cmd.Name() == args[1] {
tmpl(os.Stdout, helpTemplate, cmd)
return
}
}
}
usage()
}

mode := rev.DEV
if len(args) == 2 && args[1] == "prod" {
mode = rev.PROD
for _, cmd := range commands {
if cmd.Name() == args[0] {
cmd.Run(args[1:])
return
}
}

// Find and parse app.conf
rev.Init(args[0], mode)
log.Printf("Running app (%s): %s (%s)\n", mode, rev.AppName, rev.BasePath)

harness.Run(mode)
fmt.Fprintf(os.Stderr, "~ unknown command %q\nRun 'rev help' for usage.\n", args[0])
}

const header = `~
~ revel! http://www.github.com/robfig/revel
~ revel! http://robfig.github.com/revel
~
`

const usageTemplate = `~ usage: rev command [arguments]
~
~ The commands are:
~{{range .}}
~ {{.Name | printf "%-11s"}} {{.Short}}{{end}}
~
~ Use "rev help [command]" for more information.
`

const usageText = `~ Usage: rev import_path [mode]
var helpTemplate = `~ usage: rev {{.UsageLine}}
{{.Long}}
`

func usage() {
fmt.Fprintf(os.Stderr, usageText)
tmpl(os.Stderr, usageTemplate, commands)
os.Exit(2)
}

func tmpl(w io.Writer, text string, data interface{}) {
t := template.New("top")
template.Must(t.Parse(text))
if err := t.Execute(w, data); err != nil {
panic(err)
}
}
41 changes: 41 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import (
"github.com/robfig/revel"
"github.com/robfig/revel/harness"
"log"
)

var cmdRun = &Command{
UsageLine: "run [import path] [run mode]",
Short: "run a Revel application",
Long: `~
~ Run the Revel web application named by the given import path.
~
~ For example, to run the chat room sample application:
~
~ rev run github.com/robfig/revel/samples/chat
~
~ The run mode is used to select which set of app.conf configuration should
~ apply and may be used to determine logic in the application itself.
~
~ Run mode defaults to "dev". To run in production mode, specify "prod"`,
}

func init() {
cmdRun.Run = runApp
}

func runApp(args []string) {
// TODO: Why can't it be any arbitrary string again?
mode := rev.DEV
if len(args) == 2 && args[1] == rev.PROD {
mode = rev.PROD
}

// Find and parse app.conf
rev.Init(args[0], mode)
log.Printf("Running app (%s): %s (%s)\n", mode, rev.AppName, rev.BasePath)

harness.Run(mode)
}
11 changes: 11 additions & 0 deletions skeleton/app/controllers/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package controllers

import "github.com/robfig/revel"

type Application struct {
*rev.Controller
}

func (c Application) Index() rev.Result {
return c.Render()
}
7 changes: 7 additions & 0 deletions skeleton/app/views/Application/Index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{set "title" "Home" .}}
{{template "header.html" .}}

<h1>Your Application Is Ready</h1>

{{template "footer.html" .}}

20 changes: 20 additions & 0 deletions skeleton/app/views/errors/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Not found</title>
</head>
<body>
{{if eq .RunMode "dev"}}
{{template "errors/404-dev.html" .}}
{{else}}
{{with .Error}}
<h1>
{{.Title}}
</h1>
<p>
{{.Description}}
</p>
{{end}}
{{end}}
</body>
</html>
16 changes: 16 additions & 0 deletions skeleton/app/views/errors/500.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>Application error</title>
</head>
<body>
{{if eq .RunMode "dev"}}
{{template "errors/500-dev.html" .}}
{{else}}
<h1>Oops, an error occured</h1>
<p>
This exception has been logged.
</p>
{{end}}
</body>
</html>
2 changes: 2 additions & 0 deletions skeleton/app/views/footer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
</body>
</html>
17 changes: 17 additions & 0 deletions skeleton/app/views/header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>

<html>
<head>
<title>{{.title}}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" media="screen" href="/public/stylesheets/main.css">
<link rel="shortcut icon" type="image/png" href="/public/images/favicon.png">
<script src="/public/javascripts/jquery-1.5.2.min.js" type="text/javascript" charset="utf-8"></script>
{{range .moreStyles}}
<link rel="stylesheet" type="text/css" href="/public/{{.}}">
{{end}}
{{range .moreScripts}}
<script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
{{end}}
</head>
<body>
7 changes: 7 additions & 0 deletions skeleton/conf/app.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
app.name={{ .AppName }}
app.mode=dev
app.secret={{ .Secret }}
http.port=9000

[prod]
app.mode=prod
14 changes: 14 additions & 0 deletions skeleton/conf/routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

GET / Application.Index

# Ignore favicon requests
GET /favicon.ico 404

# Map static resources from the /app/public folder to the /public path
GET /public/ staticDir:public

# Catch all
* /{controller}/{action} {controller}.{action}
Binary file added skeleton/public/images/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 0904254

Please sign in to comment.