zest.go

Summary

Maintainability
A
0 mins
Test Coverage
// Package zest contains a lightweight framework based on the codegangsta/cli package allowing
// clean and easy command line interfaces, the codegangsta/negroni middleware
// handler, and the solher/syringe injector.
//
// Zest encourages the use of small, well chosen individual dependencies
// instead of high productivity, full-stack frameworks.
package zest

import (
    "fmt"
    "os"
    "strconv"
    "time"

    "gopkg.in/tylerb/graceful.v1"

    "github.com/codegangsta/cli"
    "github.com/codegangsta/negroni"
    "github.com/go-zoo/bone"
    "github.com/rs/cors"
    "github.com/solher/syringe"
)

// Injector provide a quick access to an instanciated injector.
var Injector = syringe.New()

// SeqFunc is the prototype of the functions present in the launch/exit sequences.
type SeqFunc func(z *Zest) error

// Zest is an aggregation of well known and efficient package, also providing
// a simple launch/exit process to the user.
//
// The launch sequence is divided into three steps:
// - The register sequence is run, allowing the user to register dependencies
// into the injector.
// - The injection is run.
// - The init sequence is run, allowing the user to properly initialize the
// freshly built app.
// Launch and exit sequences are run following the order of the arrays, at each
// start/stop of the app, thanks to Cli and the tylerb/graceful module.
type Zest struct {
    cli     *cli.App
    Context *cli.Context

    Port        int
    ExitTimeout time.Duration

    Server   *negroni.Negroni
    Injector *syringe.Syringe

    RegisterSequence []SeqFunc
    InitSequence     []SeqFunc
    ExitSequence     []SeqFunc
}

// Cli returns a copy of the embedded Cli app.
func (z *Zest) Cli() cli.App {
    return *z.cli
}

// SetCli sets a copy of the embedded Cli app.
func (z *Zest) SetCli(cli cli.App) {
    *z.cli = cli
}

// Run starts the Cli app.
func (z *Zest) Run() error {
    return z.cli.Run(os.Args)
}

// Classic returns a new instance of Zest, with some default register and init steps:
// "classicRegister" which registers the default dependencies (Render, Bone) in the injector.
// "classicInit" which initialize the Bone router and the default middlewares in Negroni.
func Classic() *Zest {
    z := New()

    z.cli.Flags = []cli.Flag{
        cli.IntFlag{
            Name:   "port,p",
            Value:  3000,
            Usage:  "listening port",
            EnvVar: "ZEST_PORT",
        },
        cli.DurationFlag{
            Name:   "exitTimeout,t",
            Value:  10 * time.Second,
            Usage:  "graceful shutdown timeout (0 for infinite)",
            EnvVar: "ZEST_TIMEOUT",
        },
    }

    z.RegisterSequence = append(z.RegisterSequence, classicRegister)
    z.InitSequence = append(z.InitSequence, classicInit)

    return z
}

// New returns a new instance of Zest.
func New() *Zest {
    z := &Zest{
        cli:         cli.NewApp(),
        Server:      negroni.New(),
        Injector:    Injector,
        Port:        3000,
        ExitTimeout: 10 * time.Second,
    }

    z.cli.Usage = "A Zest powered service."
    z.cli.Before = z.init
    z.cli.After = z.exit
    z.cli.Action = z.run

    return z
}

func (z *Zest) init(c *cli.Context) error {
    z.Context = c

    for _, f := range z.RegisterSequence {
        if err := f(z); err != nil {
            return err
        }
    }

    if err := z.Injector.Inject(); err != nil {
        return err
    }

    for _, f := range z.InitSequence {
        if err := f(z); err != nil {
            return err
        }
    }

    return nil
}

func (z *Zest) run(c *cli.Context) {
    fmt.Printf("\n[Zest] Listening on %d\n", z.Port)

    graceful.Run(":"+strconv.Itoa(z.Port), z.ExitTimeout, z.Server)
}

func (z *Zest) exit(c *cli.Context) error {
    z.Context = c

    for _, f := range z.ExitSequence {
        if err := f(z); err != nil {
            return err
        }
    }

    return nil
}

func classicRegister(z *Zest) error {
    z.Injector.Register(NewRender(), bone.New())

    return nil
}

func classicInit(z *Zest) error {
    d := &struct{ Router *bone.Mux }{}

    if err := z.Injector.Get(d); err != nil {
        return err
    }

    z.Server.Use(NewRecovery())
    z.Server.Use(NewLogger())
    z.Server.Use(cors.New(cors.Options{
        AllowedOrigins: []string{"*"},
        AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
    }))

    z.Server.UseHandler(d.Router)

    z.Port = z.Context.GlobalInt("port")
    z.ExitTimeout = z.Context.GlobalDuration("exitTimeout")

    return nil
}