Commit 3f067989 by Carl Bergquist

Merge pull request #4037 from grafana/cmd

Move grafana/grafana-cli into grafana/grafana
parents 96e98f6d 4131d8b5
[run]
init_cmds = [
["go", "build", "-o", "./bin/grafana-server"],
["go", "build", "-o", "./bin/grafana-server", "./pkg/cmd/grafana-server"],
["./bin/grafana-server"]
]
watch_all = true
......@@ -12,6 +12,6 @@ watch_dirs = [
watch_exts = [".go", ".ini", ".toml", ".html"]
build_delay = 1500
cmds = [
["go", "build", "-o", "./bin/grafana-server"],
["go", "build", "-o", "./bin/grafana-server", "./pkg/cmd/grafana-server"],
["./bin/grafana-server"]
]
......@@ -34,3 +34,5 @@ fig.yml
profile.cov
/grafana
.notouch
/pkg/cmd/grafana-cli/grafana-cli
/pkg/cmd/grafana-server/grafana-server
......@@ -70,10 +70,19 @@
"Rev": "72a68649ba712ee7c4b5b4a943a626bcd7d90eb8"
},
{
"ImportPath": "github.com/codegangsta/cli",
"Comment": "1.2.0-187-gc31a797",
"Rev": "c31a7975863e7810c92e2e288a9ab074f9a88f29"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "2df174808ee097f90d259e432cc04442cf60be21"
},
{
"ImportPath": "github.com/franela/goreq",
"Rev": "3ddeded65be21dacb5a2e2d0b95af9ff6862a2b5"
},
{
"ImportPath": "github.com/go-ini/ini",
"Comment": "v0-48-g060d7da",
"Rev": "060d7da055ba6ec5ea7a31f116332fe5efa04ce0"
......@@ -115,10 +124,18 @@
"Rev": "f56113384f2c63dfe4cd8e768e349f1c35122b58"
},
{
"ImportPath": "github.com/gopherjs/gopherjs/js",
"Rev": "14d893dca2e4adb93a5ccc9494040acc0821cd8d"
},
{
"ImportPath": "github.com/gosimple/slug",
"Rev": "8d258463b4459f161f51d6a357edacd3eef9d663"
},
{
"ImportPath": "github.com/hashicorp/go-version",
"Rev": "7e3c02b30806fa5779d3bdfc152ce4c6f40e7b38"
},
{
"ImportPath": "github.com/jmespath/go-jmespath",
"Comment": "0.2.2",
"Rev": "3433f3ea46d9f8019119e7dd41274e112a2359a9"
......
language: go
sudo: false
go:
- 1.0.3
- 1.1.2
- 1.2.2
- 1.3.3
- 1.4.2
- 1.5.1
- tip
matrix:
allow_failures:
- go: tip
script:
- go vet ./...
- go test -v ./...
Copyright (C) 2013 Jeremy Saenz
All Rights Reserved.
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli)
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli)
[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli)
# cli.go
`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
## Overview
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive!
## Installation
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
To install `cli.go`, simply run:
```
$ go get github.com/codegangsta/cli
```
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
```
export PATH=$PATH:$GOPATH/bin
```
## Getting Started
One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`.
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
cli.NewApp().Run(os.Args)
}
```
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "boom"
app.Usage = "make an explosive entrance"
app.Action = func(c *cli.Context) {
println("boom! I say!")
}
app.Run(os.Args)
}
```
Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below.
## Example
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "greet"
app.Usage = "fight the loneliness!"
app.Action = func(c *cli.Context) {
println("Hello friend!")
}
app.Run(os.Args)
}
```
Install our command to the `$GOPATH/bin` directory:
```
$ go install
```
Finally run our new command:
```
$ greet
Hello friend!
```
`cli.go` also generates neat help text:
```
$ greet help
NAME:
greet - fight the loneliness!
USAGE:
greet [global options] command [command options] [arguments...]
VERSION:
0.0.0
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS
--version Shows version information
```
### Arguments
You can lookup arguments by calling the `Args` function on `cli.Context`.
``` go
...
app.Action = func(c *cli.Context) {
println("Hello", c.Args()[0])
}
...
```
### Flags
Setting and querying flags is simple.
``` go
...
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
}
app.Action = func(c *cli.Context) {
name := "someone"
if len(c.Args()) > 0 {
name = c.Args()[0]
}
if c.String("lang") == "spanish" {
println("Hola", name)
} else {
println("Hello", name)
}
}
...
```
You can also set a destination variable for a flag, to which the content will be scanned.
``` go
...
var language string
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
Destination: &language,
},
}
app.Action = func(c *cli.Context) {
name := "someone"
if len(c.Args()) > 0 {
name = c.Args()[0]
}
if language == "spanish" {
println("Hola", name)
} else {
println("Hello", name)
}
}
...
```
See full list of flags at http://godoc.org/github.com/codegangsta/cli
#### Alternate Names
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
},
}
```
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
#### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "APP_LANG",
},
}
```
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
},
}
```
### Subcommands
Subcommands can be defined for a more git-like command line app.
```go
...
app.Commands = []cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) {
println("added task: ", c.Args().First())
},
},
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
},
{
Name: "template",
Aliases: []string{"r"},
Usage: "options for task templates",
Subcommands: []cli.Command{
{
Name: "add",
Usage: "add a new template",
Action: func(c *cli.Context) {
println("new task template: ", c.Args().First())
},
},
{
Name: "remove",
Usage: "remove an existing template",
Action: func(c *cli.Context) {
println("removed task template: ", c.Args().First())
},
},
},
},
}
...
```
### Bash Completion
You can enable completion commands by setting the `EnableBashCompletion`
flag on the `App` object. By default, this setting will only auto-complete to
show an app's subcommands, but you can write your own completion methods for
the App or its subcommands.
```go
...
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := cli.NewApp()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
BashComplete: func(c *cli.Context) {
// This will complete if no args are passed
if len(c.Args()) > 0 {
return
}
for _, t := range tasks {
fmt.Println(t)
}
},
}
}
...
```
#### To Enable
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
setting the `PROG` variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
#### To Distribute
Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename
it to the name of the program you wish to add autocomplete support for (or
automatically install it there if you are distributing a package). Don't forget
to source the file to make it active in the current shell.
```
sudo cp src/bash_autocomplete /etc/bash_completion.d/<myprogram>
source /etc/bash_completion.d/<myprogram>
```
Alternatively, you can just document that users should source the generic
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set
to the name of their program (as above).
## Contribution Guidelines
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.
package cli
import (
"fmt"
"io"
"io/ioutil"
"os"
"path"
"time"
)
// App is the main structure of a cli application. It is recomended that
// an app be created with the cli.NewApp() function
type App struct {
// The name of the program. Defaults to path.Base(os.Args[0])
Name string
// Full name of command for help, defaults to Name
HelpName string
// Description of the program.
Usage string
// Description of the program argument format.
ArgsUsage string
// Version of the program
Version string
// List of commands to execute
Commands []Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag
HideVersion bool
// An action to execute when the bash-completion flag is set
BashComplete func(context *Context)
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The action to execute when no subcommands are specified
Action func(context *Context)
// Execute this function if the proper command cannot be found
CommandNotFound func(context *Context, command string)
// Compilation date
Compiled time.Time
// List of all authors who contributed
Authors []Author
// Copyright of the binary if any
Copyright string
// Name of Author (Note: Use App.Authors, this is deprecated)
Author string
// Email of Author (Note: Use App.Authors, this is deprecated)
Email string
// Writer writer to write output to
Writer io.Writer
}
// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
info, err := os.Stat(os.Args[0])
if err != nil {
return time.Now()
}
return info.ModTime()
}
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App {
return &App{
Name: path.Base(os.Args[0]),
HelpName: path.Base(os.Args[0]),
Usage: "A new cli application",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Writer: os.Stdout,
}
}
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
if a.Author != "" || a.Email != "" {
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
// append help to commands
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
//append version/help flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
if !a.HideVersion {
a.appendFlag(VersionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
context := NewContext(a, set, nil)
ShowAppHelp(context)
return nerr
}
context := NewContext(a, set, nil)
if checkCompletions(context) {
return nil
}
if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowAppHelp(context)
return err
}
if !a.HideHelp && checkHelp(context) {
ShowAppHelp(context)
return nil
}
if !a.HideVersion && checkVersion(context) {
ShowVersion(context)
return nil
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
a.Action(context)
return nil
}
// Another entry point to the cli app, takes care of passing arguments and error handling
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
// append flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 {
ShowSubcommandHelp(context)
} else {
ShowCommandHelp(ctx, context.Args().First())
}
return nerr
}
if checkCompletions(context) {
return nil
}
if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowSubcommandHelp(context)
return err
}
if len(a.Commands) > 0 {
if checkSubcommandHelp(context) {
return nil
}
} else {
if checkCommandHelp(ctx, context.Args().First()) {
return nil
}
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
a.Action(context)
return nil
}
// Returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
for _, c := range a.Commands {
if c.HasName(name) {
return &c
}
}
return nil
}
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if flag == f {
return true
}
}
return false
}
func (a *App) appendFlag(flag Flag) {
if !a.hasFlag(flag) {
a.Flags = append(a.Flags, flag)
}
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
Email string // The Authors email
}
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
func (a Author) String() string {
e := ""
if a.Email != "" {
e = "<" + a.Email + "> "
}
return fmt.Sprintf("%v %v", a.Name, e)
}
#! /bin/bash
: ${PROG:=$(basename ${BASH_SOURCE})}
_cli_bash_autocomplete() {
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _cli_bash_autocomplete $PROG
autoload -U compinit && compinit
autoload -U bashcompinit && bashcompinit
script_dir=$(dirname $0)
source ${script_dir}/bash_autocomplete
// Package cli provides a minimal framework for creating and organizing command line
// Go applications. cli is designed to be easy to understand and write, the most simple
// cli application can be written as follows:
// func main() {
// cli.NewApp().Run(os.Args)
// }
//
// Of course this application does not do much, so let's make this an actual application:
// func main() {
// app := cli.NewApp()
// app.Name = "greet"
// app.Usage = "say a greeting"
// app.Action = func(c *cli.Context) {
// println("Greetings")
// }
//
// app.Run(os.Args)
// }
package cli
import (
"strings"
)
type MultiError struct {
Errors []error
}
func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err}
}
func (m MultiError) Error() string {
errs := make([]string, len(m.Errors))
for i, err := range m.Errors {
errs[i] = err.Error()
}
return strings.Join(errs, "\n")
}
package cli
import (
"fmt"
"io/ioutil"
"strings"
)
// Command is a subcommand for a cli.App.
type Command struct {
// The name of the command
Name string
// short name of the command. Typically one character (deprecated, use `Aliases`)
ShortName string
// A list of aliases for the command
Aliases []string
// A short description of the usage of this command
Usage string
// A longer explanation of how the command works
Description string
// A short description of the arguments of this command
ArgsUsage string
// The function to call when checking for bash command completions
BashComplete func(context *Context)
// An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run
Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The function to call when this command is invoked
Action func(context *Context)
// List of child commands
Subcommands []Command
// List of flags to parse
Flags []Flag
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Boolean to hide built-in help command
HideHelp bool
// Full name of command for help, defaults to full command name, including parent commands.
HelpName string
commandNamePath []string
}
// Returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
if c.commandNamePath == nil {
return c.Name
}
return strings.Join(c.commandNamePath, " ")
}
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) error {
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
return c.startApp(ctx)
}
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
// append help to flags
c.Flags = append(
c.Flags,
HelpFlag,
)
}
if ctx.App.EnableBashCompletion {
c.Flags = append(c.Flags, BashCompletionFlag)
}
set := flagSet(c.Name, c.Flags)
set.SetOutput(ioutil.Discard)
var err error
if !c.SkipFlagParsing {
firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() {
if arg == "--" {
terminatorIndex = index
break
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index
}
}
if firstFlagIndex > -1 {
args := ctx.Args()
regularArgs := make([]string, len(args[1:firstFlagIndex]))
copy(regularArgs, args[1:firstFlagIndex])
var flagArgs []string
if terminatorIndex > -1 {
flagArgs = args[firstFlagIndex:terminatorIndex]
regularArgs = append(regularArgs, args[terminatorIndex:]...)
} else {
flagArgs = args[firstFlagIndex:]
}
err = set.Parse(append(flagArgs, regularArgs...))
} else {
err = set.Parse(ctx.Args().Tail())
}
} else {
if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
}
}
if err != nil {
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
}
nerr := normalizeFlags(c.Flags, set)
if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return nerr
}
context := NewContext(ctx.App, set, ctx)
if checkCommandCompletions(context, c.Name) {
return nil
}
if checkCommandHelp(context, c.Name) {
return nil
}
context.Command = c
c.Action(context)
return nil
}
func (c Command) Names() []string {
names := []string{c.Name}
if c.ShortName != "" {
names = append(names, c.ShortName)
}
return append(names, c.Aliases...)
}
// Returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
for _, n := range c.Names() {
if n == name {
return true
}
}
return false
}
func (c Command) startApp(ctx *Context) error {
app := NewApp()
// set the name and usage
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
if c.HelpName == "" {
app.HelpName = c.HelpName
} else {
app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
}
if c.Description != "" {
app.Usage = c.Description
} else {
app.Usage = c.Usage
}
// set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound
// set the flags and commands
app.Commands = c.Subcommands
app.Flags = c.Flags
app.HideHelp = c.HideHelp
app.Version = ctx.App.Version
app.HideVersion = ctx.App.HideVersion
app.Compiled = ctx.App.Compiled
app.Author = ctx.App.Author
app.Email = ctx.App.Email
app.Writer = ctx.App.Writer
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {
app.BashComplete = c.BashComplete
}
// set the actions
app.Before = c.Before
app.After = c.After
if c.Action != nil {
app.Action = c.Action
} else {
app.Action = helpSubcommand.Action
}
var newCmds []Command
for _, cc := range app.Commands {
cc.commandNamePath = []string{c.Name, cc.Name}
newCmds = append(newCmds, cc)
}
app.Commands = newCmds
return app.RunAsSubcommand(ctx)
}
package cli
import (
"errors"
"flag"
"strconv"
"strings"
"time"
)
// Context is a type that is passed through to
// each Handler action in a cli application. Context
// can be used to retrieve context-specific Args and
// parsed command-line options.
type Context struct {
App *App
Command Command
flagSet *flag.FlagSet
setFlags map[string]bool
globalSetFlags map[string]bool
parentContext *Context
}
// Creates a new context. For use in when invoking an App or Command action.
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
return &Context{App: app, flagSet: set, parentContext: parentCtx}
}
// Looks up the value of a local int flag, returns 0 if no int flag exists
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// Looks up the value of a local bool flag, returns false if no bool flag exists
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// Looks up the value of a local boolT flag, returns false if no bool flag exists
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// Looks up the value of a local string flag, returns "" if no string flag exists
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// Looks up the value of a local string slice flag, returns nil if no string slice flag exists
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// Looks up the value of a local int slice flag, returns nil if no int slice flag exists
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// Looks up the value of a local generic flag, returns nil if no generic flag exists
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// Looks up the value of a global int flag, returns 0 if no int flag exists
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
// Looks up the value of a global bool flag, returns false if no bool flag exists
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
// Looks up the value of a global string flag, returns "" if no string flag exists
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
// Looks up the value of a global generic flag, returns nil if no generic flag exists
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
// Returns the number of flags set
func (c *Context) NumFlags() int {
return c.flagSet.NFlag()
}
// Determines if the flag was actually set
func (c *Context) IsSet(name string) bool {
if c.setFlags == nil {
c.setFlags = make(map[string]bool)
c.flagSet.Visit(func(f *flag.Flag) {
c.setFlags[f.Name] = true
})
}
return c.setFlags[name] == true
}
// Determines if the global flag was actually set
func (c *Context) GlobalIsSet(name string) bool {
if c.globalSetFlags == nil {
c.globalSetFlags = make(map[string]bool)
ctx := c
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext {
ctx.flagSet.Visit(func(f *flag.Flag) {
c.globalSetFlags[f.Name] = true
})
}
}
return c.globalSetFlags[name]
}
// Returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags {
name := strings.Split(flag.GetName(), ",")[0]
if name == "help" {
continue
}
names = append(names, name)
}
return
}
// Returns a slice of global flag names used by the app.
func (c *Context) GlobalFlagNames() (names []string) {
for _, flag := range c.App.Flags {
name := strings.Split(flag.GetName(), ",")[0]
if name == "help" || name == "version" {
continue
}
names = append(names, name)
}
return
}
// Returns the parent context, if any
func (c *Context) Parent() *Context {
return c.parentContext
}
type Args []string
// Returns the command line arguments associated with the context.
func (c *Context) Args() Args {
args := Args(c.flagSet.Args())
return args
}
// Returns the nth argument, or else a blank string
func (a Args) Get(n int) string {
if len(a) > n {
return a[n]
}
return ""
}
// Returns the first argument, or else a blank string
func (a Args) First() string {
return a.Get(0)
}
// Return the rest of the arguments (not the first one)
// or else an empty string slice
func (a Args) Tail() []string {
if len(a) >= 2 {
return []string(a)[1:]
}
return []string{}
}
// Checks if there are any arguments present
func (a Args) Present() bool {
return len(a) != 0
}
// Swaps arguments at the given indexes
func (a Args) Swap(from, to int) error {
if from >= len(a) || to >= len(a) {
return errors.New("index out of range")
}
a[from], a[to] = a[to], a[from]
return nil
}
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if f := ctx.flagSet.Lookup(name); f != nil {
return ctx.flagSet
}
}
return nil
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
val, err := strconv.Atoi(f.Value.String())
if err != nil {
return 0
}
return val
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
val, err := time.ParseDuration(f.Value.String())
if err == nil {
return val
}
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return val
}
return 0
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
return f.Value.String()
}
return ""
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*StringSlice)).Value()
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*IntSlice)).Value()
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
return f.Value
}
return nil
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return val
}
return false
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return true
}
return val
}
return false
}
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) {
case *StringSlice:
default:
set.Set(name, ff.Value.String())
}
}
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited := make(map[string]bool)
set.Visit(func(f *flag.Flag) {
visited[f.Name] = true
})
for _, f := range flags {
parts := strings.Split(f.GetName(), ",")
if len(parts) == 1 {
continue
}
var ff *flag.Flag
for _, name := range parts {
name = strings.Trim(name, " ")
if visited[name] {
if ff != nil {
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
}
ff = set.Lookup(name)
}
}
if ff == nil {
continue
}
for _, name := range parts {
name = strings.Trim(name, " ")
if !visited[name] {
copyFlag(name, ff, set)
}
}
}
return nil
}
package cli
import (
"fmt"
"io"
"strings"
"text/tabwriter"
"text/template"
)
// The text template for the Default help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
{{if .Version}}
VERSION:
{{.Version}}
{{end}}{{if len .Authors}}
AUTHOR(S):
{{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}}
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{end}}{{if .Flags}}
GLOBAL OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}{{if .Copyright }}
COPYRIGHT:
{{.Copyright}}
{{end}}
`
// The text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{ end }}
`
// The text template for the subcommand help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}
`
var helpCommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) {
args := c.Args()
if args.Present() {
ShowCommandHelp(c, args.First())
} else {
ShowAppHelp(c)
}
},
}
var helpSubcommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) {
args := c.Args()
if args.Present() {
ShowCommandHelp(c, args.First())
} else {
ShowSubcommandHelp(c)
}
},
}
// Prints help for the App or Command
type helpPrinter func(w io.Writer, templ string, data interface{})
var HelpPrinter helpPrinter = printHelp
// Prints version for the App
var VersionPrinter = printVersion
func ShowAppHelp(c *Context) {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
}
// Prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands {
for _, name := range command.Names() {
fmt.Fprintln(c.App.Writer, name)
}
}
}
// Prints help for the given command
func ShowCommandHelp(ctx *Context, command string) {
// show the subcommand help for a command with subcommands
if command == "" {
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
return
}
for _, c := range ctx.App.Commands {
if c.HasName(command) {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
return
}
}
if ctx.App.CommandNotFound != nil {
ctx.App.CommandNotFound(ctx, command)
} else {
fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command)
}
}
// Prints help for the given subcommand
func ShowSubcommandHelp(c *Context) {
ShowCommandHelp(c, c.Command.Name)
}
// Prints the version number of the App
func ShowVersion(c *Context) {
VersionPrinter(c)
}
func printVersion(c *Context) {
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
}
// Prints the lists of commands within a given context
func ShowCompletions(c *Context) {
a := c.App
if a != nil && a.BashComplete != nil {
a.BashComplete(c)
}
}
// Prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command)
if c != nil && c.BashComplete != nil {
c.BashComplete(ctx)
}
}
func printHelp(out io.Writer, templ string, data interface{}) {
funcMap := template.FuncMap{
"join": strings.Join,
}
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data)
if err != nil {
panic(err)
}
w.Flush()
}
func checkVersion(c *Context) bool {
found := false
if VersionFlag.Name != "" {
eachName(VersionFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
}
})
}
return found
}
func checkHelp(c *Context) bool {
found := false
if HelpFlag.Name != "" {
eachName(HelpFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
}
})
}
return found
}
func checkCommandHelp(c *Context, name string) bool {
if c.Bool("h") || c.Bool("help") {
ShowCommandHelp(c, name)
return true
}
return false
}
func checkSubcommandHelp(c *Context) bool {
if c.GlobalBool("h") || c.GlobalBool("help") {
ShowSubcommandHelp(c)
return true
}
return false
}
func checkCompletions(c *Context) bool {
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
ShowCompletions(c)
return true
}
return false
}
func checkCommandCompletions(c *Context, name string) bool {
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
ShowCommandCompletions(c, name)
return true
}
return false
}
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
src
language: go
go:
- 1.5.3
- tip
notifications:
email:
- ionathan@gmail.com
- marcosnils@gmail.com
The MIT License (MIT)
Copyright (c) 2013 Jonathan Leibiusky and Marcos Lilljedahl
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
test:
go get -v -d -t ./...
go test -v
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package goreq
import (
"strings"
"unicode"
)
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
default:
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
return true
}
Copyright (c) 2013 Richard Musiol. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Package js provides functions for interacting with native JavaScript APIs. Calls to these functions are treated specially by GopherJS and translated directly to their corresponding JavaScript syntax.
//
// Use MakeWrapper to expose methods to JavaScript. When passing values directly, the following type conversions are performed:
//
// | Go type | JavaScript type | Conversions back to interface{} |
// | --------------------- | --------------------- | ------------------------------- |
// | bool | Boolean | bool |
// | integers and floats | Number | float64 |
// | string | String | string |
// | []int8 | Int8Array | []int8 |
// | []int16 | Int16Array | []int16 |
// | []int32, []int | Int32Array | []int |
// | []uint8 | Uint8Array | []uint8 |
// | []uint16 | Uint16Array | []uint16 |
// | []uint32, []uint | Uint32Array | []uint |
// | []float32 | Float32Array | []float32 |
// | []float64 | Float64Array | []float64 |
// | all other slices | Array | []interface{} |
// | arrays | see slice type | see slice type |
// | functions | Function | func(...interface{}) *js.Object |
// | time.Time | Date | time.Time |
// | - | instanceof Node | *js.Object |
// | maps, structs | instanceof Object | map[string]interface{} |
//
// Additionally, for a struct containing a *js.Object field, only the content of the field will be passed to JavaScript and vice versa.
package js
// Object is a container for a native JavaScript object. Calls to its methods are treated specially by GopherJS and translated directly to their JavaScript syntax. A nil pointer to Object is equal to JavaScript's "null". Object can not be used as a map key.
type Object struct{ object *Object }
// Get returns the object's property with the given key.
func (o *Object) Get(key string) *Object { return o.object.Get(key) }
// Set assigns the value to the object's property with the given key.
func (o *Object) Set(key string, value interface{}) { o.object.Set(key, value) }
// Delete removes the object's property with the given key.
func (o *Object) Delete(key string) { o.object.Delete(key) }
// Length returns the object's "length" property, converted to int.
func (o *Object) Length() int { return o.object.Length() }
// Index returns the i'th element of an array.
func (o *Object) Index(i int) *Object { return o.object.Index(i) }
// SetIndex sets the i'th element of an array.
func (o *Object) SetIndex(i int, value interface{}) { o.object.SetIndex(i, value) }
// Call calls the object's method with the given name.
func (o *Object) Call(name string, args ...interface{}) *Object { return o.object.Call(name, args...) }
// Invoke calls the object itself. This will fail if it is not a function.
func (o *Object) Invoke(args ...interface{}) *Object { return o.object.Invoke(args...) }
// New creates a new instance of this type object. This will fail if it not a function (constructor).
func (o *Object) New(args ...interface{}) *Object { return o.object.New(args...) }
// Bool returns the object converted to bool according to JavaScript type conversions.
func (o *Object) Bool() bool { return o.object.Bool() }
// String returns the object converted to string according to JavaScript type conversions.
func (o *Object) String() string { return o.object.String() }
// Int returns the object converted to int according to JavaScript type conversions (parseInt).
func (o *Object) Int() int { return o.object.Int() }
// Int64 returns the object converted to int64 according to JavaScript type conversions (parseInt).
func (o *Object) Int64() int64 { return o.object.Int64() }
// Uint64 returns the object converted to uint64 according to JavaScript type conversions (parseInt).
func (o *Object) Uint64() uint64 { return o.object.Uint64() }
// Float returns the object converted to float64 according to JavaScript type conversions (parseFloat).
func (o *Object) Float() float64 { return o.object.Float() }
// Interface returns the object converted to interface{}. See GopherJS' README for details.
func (o *Object) Interface() interface{} { return o.object.Interface() }
// Unsafe returns the object as an uintptr, which can be converted via unsafe.Pointer. Not intended for public use.
func (o *Object) Unsafe() uintptr { return o.object.Unsafe() }
// Error encapsulates JavaScript errors. Those are turned into a Go panic and may be recovered, giving an *Error that holds the JavaScript error object.
type Error struct {
*Object
}
// Error returns the message of the encapsulated JavaScript error object.
func (err *Error) Error() string {
return "JavaScript error: " + err.Get("message").String()
}
// Stack returns the stack property of the encapsulated JavaScript error object.
func (err *Error) Stack() string {
return err.Get("stack").String()
}
// Global gives JavaScript's global object ("window" for browsers and "GLOBAL" for Node.js).
var Global *Object
// Module gives the value of the "module" variable set by Node.js. Hint: Set a module export with 'js.Module.Get("exports").Set("exportName", ...)'.
var Module *Object
// Undefined gives the JavaScript value "undefined".
var Undefined *Object
// Debugger gets compiled to JavaScript's "debugger;" statement.
func Debugger() {}
// InternalObject returns the internal JavaScript object that represents i. Not intended for public use.
func InternalObject(i interface{}) *Object {
return nil
}
// MakeFunc wraps a function and gives access to the values of JavaScript's "this" and "arguments" keywords.
func MakeFunc(func(this *Object, arguments []*Object) interface{}) *Object {
return nil
}
// Keys returns the keys of the given JavaScript object.
func Keys(o *Object) []string {
if o == nil || o == Undefined {
return nil
}
a := Global.Get("Object").Call("keys", o)
s := make([]string, a.Length())
for i := 0; i < a.Length(); i++ {
s[i] = a.Index(i).String()
}
return s
}
// MakeWrapper creates a JavaScript object which has wrappers for the exported methods of i. Use explicit getter and setter methods to expose struct fields to JavaScript.
func MakeWrapper(i interface{}) *Object {
v := InternalObject(i)
o := Global.Get("Object").New()
o.Set("__internal_object__", v)
methods := v.Get("constructor").Get("methods")
for i := 0; i < methods.Length(); i++ {
m := methods.Index(i)
if m.Get("pkg").String() != "" { // not exported
continue
}
o.Set(m.Get("name").String(), func(args ...*Object) *Object {
return Global.Call("$externalizeFunction", v.Get(m.Get("prop").String()), m.Get("typ"), true).Call("apply", v, args)
})
}
return o
}
// NewArrayBuffer creates a JavaScript ArrayBuffer from a byte slice.
func NewArrayBuffer(b []byte) *Object {
slice := InternalObject(b)
offset := slice.Get("$offset").Int()
length := slice.Get("$length").Int()
return slice.Get("$array").Get("buffer").Call("slice", offset, offset+length)
}
// M is a simple map type. It is intended as a shorthand for JavaScript objects (before conversion).
type M map[string]interface{}
// S is a simple slice type. It is intended as a shorthand for JavaScript arrays (before conversion).
type S []interface{}
func init() {
// avoid dead code elimination
e := Error{}
_ = e
}
package commands
import (
"github.com/codegangsta/cli"
)
type CommandLine interface {
ShowHelp()
ShowVersion()
Application() *cli.App
Args() cli.Args
Bool(name string) bool
Int(name string) int
String(name string) string
StringSlice(name string) []string
GlobalString(name string) string
FlagNames() (names []string)
Generic(name string) interface{}
}
type contextCommandLine struct {
*cli.Context
}
func (c *contextCommandLine) ShowHelp() {
cli.ShowCommandHelp(c.Context, c.Command.Name)
}
func (c *contextCommandLine) ShowVersion() {
cli.ShowVersion(c.Context)
}
func (c *contextCommandLine) Application() *cli.App {
return c.App
}
package commands
import (
"github.com/codegangsta/cli"
"github.com/grafana/grafana-cli/pkg/log"
)
func runCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
return func(context *cli.Context) {
cmd := &contextCommandLine{context}
if err := command(cmd); err != nil {
log.Errorf("%v\n\n", err)
cmd.ShowHelp()
} else {
log.Info("Restart grafana after installing plugins . <service grafana-server restart>\n")
}
}
}
var Commands = []cli.Command{
{
Name: "install",
Usage: "installs stuff",
Action: runCommand(installCommand),
}, {
Name: "list-remote",
Usage: "list remote available plugins",
Action: runCommand(listremoteCommand),
}, {
Name: "upgrade",
Usage: "upgrades one plugin",
Action: runCommand(upgradeCommand),
}, {
Name: "upgrade-all",
Usage: "upgrades all your installed plugins",
Action: runCommand(upgradeAllCommand),
}, {
Name: "ls",
Usage: "list all installed plugins",
Action: runCommand(lsCommand),
}, {
Name: "remove",
Usage: "removes stuff",
Action: runCommand(removeCommand),
},
}
package commandstest
import (
"github.com/codegangsta/cli"
)
type FakeFlagger struct {
Data map[string]interface{}
}
type FakeCommandLine struct {
LocalFlags, GlobalFlags *FakeFlagger
HelpShown, VersionShown bool
CliArgs []string
}
func (ff FakeFlagger) String(key string) string {
if value, ok := ff.Data[key]; ok {
return value.(string)
}
return ""
}
func (ff FakeFlagger) StringSlice(key string) []string {
if value, ok := ff.Data[key]; ok {
return value.([]string)
}
return []string{}
}
func (ff FakeFlagger) Int(key string) int {
if value, ok := ff.Data[key]; ok {
return value.(int)
}
return 0
}
func (ff FakeFlagger) Bool(key string) bool {
if value, ok := ff.Data[key]; ok {
return value.(bool)
}
return false
}
func (fcli *FakeCommandLine) String(key string) string {
return fcli.LocalFlags.String(key)
}
func (fcli *FakeCommandLine) StringSlice(key string) []string {
return fcli.LocalFlags.StringSlice(key)
}
func (fcli *FakeCommandLine) Int(key string) int {
return fcli.LocalFlags.Int(key)
}
func (fcli *FakeCommandLine) Bool(key string) bool {
if fcli.LocalFlags == nil {
return false
}
return fcli.LocalFlags.Bool(key)
}
func (fcli *FakeCommandLine) GlobalString(key string) string {
return fcli.GlobalFlags.String(key)
}
func (fcli *FakeCommandLine) Generic(name string) interface{} {
return fcli.LocalFlags.Data[name]
}
func (fcli *FakeCommandLine) FlagNames() []string {
flagNames := []string{}
for key := range fcli.LocalFlags.Data {
flagNames = append(flagNames, key)
}
return flagNames
}
func (fcli *FakeCommandLine) ShowHelp() {
fcli.HelpShown = true
}
func (fcli *FakeCommandLine) Application() *cli.App {
return cli.NewApp()
}
func (fcli *FakeCommandLine) Args() cli.Args {
return fcli.CliArgs
}
func (fcli *FakeCommandLine) ShowVersion() {
fcli.VersionShown = true
}
package commandstest
import (
"os"
"time"
)
type FakeIoUtil struct {
FakeReadDir []os.FileInfo
FakeIsDirectory bool
}
func (util *FakeIoUtil) Stat(path string) (os.FileInfo, error) {
return FakeFileInfo{IsDirectory: util.FakeIsDirectory}, nil
}
func (util *FakeIoUtil) RemoveAll(path string) error {
return nil
}
func (util *FakeIoUtil) ReadDir(path string) ([]os.FileInfo, error) {
return util.FakeReadDir, nil
}
func (i *FakeIoUtil) ReadFile(filename string) ([]byte, error) {
return make([]byte, 0), nil
}
type FakeFileInfo struct {
IsDirectory bool
}
func (ffi FakeFileInfo) IsDir() bool {
return ffi.IsDirectory
}
func (ffi FakeFileInfo) Size() int64 {
return 1
}
func (ffi FakeFileInfo) Mode() os.FileMode {
return 0777
}
func (ffi FakeFileInfo) Name() string {
return ""
}
func (ffi FakeFileInfo) ModTime() time.Time {
return time.Time{}
}
func (ffi FakeFileInfo) Sys() interface{} {
return nil
}
package commands
import (
"archive/zip"
"bytes"
"errors"
"github.com/grafana/grafana-cli/pkg/log"
m "github.com/grafana/grafana-cli/pkg/models"
services "github.com/grafana/grafana-cli/pkg/services"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"regexp"
)
func validateInput(c CommandLine, pluginFolder string) error {
arg := c.Args().First()
if arg == "" {
return errors.New("please specify plugin to install")
}
pluginDir := c.GlobalString("path")
if pluginDir == "" {
return errors.New("missing path flag")
}
fileinfo, err := os.Stat(pluginDir)
if err != nil && !fileinfo.IsDir() {
return errors.New("path is not a directory")
}
return nil
}
func installCommand(c CommandLine) error {
pluginFolder := c.GlobalString("path")
if err := validateInput(c, pluginFolder); err != nil {
return err
}
pluginToInstall := c.Args().First()
version := c.Args().Get(1)
log.Infof("version: %v\n", version)
return InstallPlugin(pluginToInstall, pluginFolder, version)
}
func InstallPlugin(pluginName, pluginFolder, version string) error {
plugin, err := services.GetPlugin(pluginName)
if err != nil {
return err
}
v, err := SelectVersion(plugin, version)
if err != nil {
return err
}
url := v.Url
commit := v.Commit
downloadURL := url + "/archive/" + commit + ".zip"
log.Infof("installing %v @ %v\n", plugin.Id, version)
log.Infof("from url: %v\n", downloadURL)
log.Infof("on commit: %v\n", commit)
log.Infof("into: %v\n", pluginFolder)
err = downloadFile(plugin.Id, pluginFolder, downloadURL)
if err == nil {
log.Info("Installed %s successfully ✔\n", plugin.Id)
}
res := services.ReadPlugin(pluginFolder, pluginName)
for _, v := range res.Dependency.Plugins {
log.Infof("Depends on %s install!\n", v.Id)
//Todo: uncomment this code once the repo is more correct.
//InstallPlugin(v.Id, pluginFolder, "")
}
return err
}
func SelectVersion(plugin m.Plugin, version string) (m.Version, error) {
if version == "" {
return plugin.Versions[0], nil
}
for _, v := range plugin.Versions {
if v.Version == version {
return v, nil
}
}
return m.Version{}, errors.New("Could not find the version your looking for")
}
func RemoveGitBuildFromname(pluginname, filename string) string {
r := regexp.MustCompile("^[a-zA-Z0-9_.-]*/")
return r.ReplaceAllString(filename, pluginname+"/")
}
func downloadFile(pluginName, filepath, url string) (err error) {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
r, err := zip.NewReader(bytes.NewReader(body), resp.ContentLength)
if err != nil {
return err
}
for _, zf := range r.File {
newfile := path.Join(filepath, RemoveGitBuildFromname(pluginName, zf.Name))
if zf.FileInfo().IsDir() {
os.Mkdir(newfile, 0777)
} else {
dst, err := os.Create(newfile)
if err != nil {
log.Errorf("%v", err)
}
defer dst.Close()
src, err := zf.Open()
if err != nil {
log.Errorf("%v", err)
}
defer src.Close()
io.Copy(dst, src)
}
}
return nil
}
package commands
import (
"github.com/grafana/grafana-cli/pkg/log"
"github.com/grafana/grafana-cli/pkg/services"
)
func listremoteCommand(c CommandLine) error {
plugin, err := services.ListAllPlugins()
if err != nil {
return err
}
for _, i := range plugin.Plugins {
log.Infof("id: %v version:\n", i.Id)
}
return nil
}
package commands
import (
"errors"
"github.com/grafana/grafana-cli/pkg/log"
m "github.com/grafana/grafana-cli/pkg/models"
s "github.com/grafana/grafana-cli/pkg/services"
)
var getPlugins func(path string) []m.InstalledPlugin
var GetStat m.IoUtil
func init() {
getPlugins = s.GetLocalPlugins
GetStat = s.IoUtil
}
func validateCommand(pluginDir string) error {
if pluginDir == "" {
return errors.New("missing path flag")
}
log.Info("plugindir: " + pluginDir + "\n")
pluginDirInfo, err := GetStat.Stat(pluginDir)
if err != nil {
return errors.New("missing path flag")
}
if pluginDirInfo.IsDir() == false {
return errors.New("plugin path is not a directory")
}
return nil
}
func lsCommand(c CommandLine) error {
pluginDir := c.GlobalString("path")
if err := validateCommand(pluginDir); err != nil {
return err
}
for _, plugin := range getPlugins(pluginDir) {
log.Infof("plugin: %s @ %s \n", plugin.Name, plugin.Info.Version)
}
return nil
}
package commands
import (
"errors"
"github.com/grafana/grafana-cli/pkg/log"
m "github.com/grafana/grafana-cli/pkg/models"
services "github.com/grafana/grafana-cli/pkg/services"
)
var getPluginss func(path string) []m.InstalledPlugin = services.GetLocalPlugins
var removePlugin func(pluginPath, id string) error = services.RemoveInstalledPlugin
func removeCommand(c CommandLine) error {
pluginPath := c.GlobalString("path")
localPlugins := getPluginss(pluginPath)
log.Info("remove!\n")
plugin := c.Args().First()
log.Info("plugin: " + plugin + "\n")
if plugin == "" {
return errors.New("Missing which plugin parameter")
}
log.Infof("plugins : \n%v\n", localPlugins)
for _, p := range localPlugins {
log.Infof("is %s == %s ? %v", p.Id, c.Args().First(), p.Id == c.Args().First())
if p.Id == c.Args().First() {
removePlugin(pluginPath, p.Id)
}
}
return nil
}
package commands
import (
"github.com/grafana/grafana-cli/pkg/log"
m "github.com/grafana/grafana-cli/pkg/models"
services "github.com/grafana/grafana-cli/pkg/services"
"github.com/hashicorp/go-version"
)
func ShouldUpgrade(installed string, remote m.Plugin) bool {
installedVersion, err1 := version.NewVersion(installed)
if err1 != nil {
return false
}
for _, v := range remote.Versions {
remoteVersion, err2 := version.NewVersion(v.Version)
if err2 == nil {
if installedVersion.LessThan(remoteVersion) {
return true
}
}
}
return false
}
func upgradeAllCommand(c CommandLine) error {
pluginDir := c.GlobalString("path")
localPlugins := services.GetLocalPlugins(pluginDir)
remotePlugins, err := services.ListAllPlugins()
if err != nil {
return err
}
pluginsToUpgrade := make([]m.InstalledPlugin, 0)
for _, localPlugin := range localPlugins {
for _, remotePlugin := range remotePlugins.Plugins {
if localPlugin.Id == remotePlugin.Id {
if ShouldUpgrade(localPlugin.Info.Version, remotePlugin) {
pluginsToUpgrade = append(pluginsToUpgrade, localPlugin)
}
}
}
}
for _, p := range pluginsToUpgrade {
log.Infof("lets upgrade %v \n", p)
services.RemoveInstalledPlugin(pluginDir, p.Id)
InstallPlugin(p.Id, pluginDir, "")
}
return nil
}
package commands
import (
"errors"
)
func upgradeCommand(c CommandLine) error {
return errors.New("Not yet Implemented")
}
package log
import (
"fmt"
)
var (
debugmode = false
)
func Debug(args ...interface{}) {
if debugmode {
fmt.Print(args...)
}
}
func Debugf(fmtString string, args ...interface{}) {
if debugmode {
fmt.Printf(fmtString, args...)
}
}
func Error(args ...interface{}) {
fmt.Print(args...)
}
func Errorf(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
}
func Info(args ...interface{}) {
fmt.Print(args...)
}
func Infof(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
}
func Warn(args ...interface{}) {
fmt.Print(args...)
}
func Warnf(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
}
func SetDebug(value bool) {
debugmode = value
}
package models
import (
"os"
)
type InstalledPlugin struct {
Id string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Info PluginInfo `json:"info"`
Dependency Dependency `json:"dependencies"`
}
type Dependency struct {
GrafanaVersion string `json:"grafanaVersion"`
Plugins []Plugin `json:"plugins"`
}
type PluginInfo struct {
Version string `json:"version"`
Updated string `json:"updated"`
}
type Plugin struct {
Id string `json:"id"`
Category string `json:"category"`
Versions []Version `json:"versions"`
}
type Version struct {
Commit string `json:"commit"`
Url string `json:"url"`
Version string `json:"version"`
}
type PluginRepo struct {
Plugins []Plugin `json:"plugins"`
Version string `json:"version"`
}
type IoUtil interface {
Stat(path string) (os.FileInfo, error)
RemoveAll(path string) error
ReadDir(path string) ([]os.FileInfo, error)
ReadFile(filename string) ([]byte, error)
}
package services
import (
m "github.com/grafana/grafana-cli/pkg/models"
"io/ioutil"
"os"
)
var IoUtil m.IoUtil = IoUtilImp{}
type IoUtilImp struct {
}
func (i IoUtilImp) Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}
func (i IoUtilImp) RemoveAll(path string) error {
return os.RemoveAll(path)
}
func (i IoUtilImp) ReadDir(path string) ([]os.FileInfo, error) {
return ioutil.ReadDir(path)
}
func (i IoUtilImp) ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}
package services
import (
"encoding/json"
"errors"
"github.com/franela/goreq"
m "github.com/grafana/grafana-cli/pkg/models"
"path"
)
var IoHelper m.IoUtil = IoUtilImp{}
func ListAllPlugins() (m.PluginRepo, error) {
res, _ := goreq.Request{Uri: "https://raw.githubusercontent.com/grafana/grafana-plugin-repository/master/repo.json"}.Do()
var resp m.PluginRepo
err := res.Body.FromJsonTo(&resp)
if err != nil {
return m.PluginRepo{}, errors.New("Could not load plugin data")
}
return resp, nil
}
func ReadPlugin(pluginDir, pluginName string) m.InstalledPlugin {
pluginDataPath := path.Join(pluginDir, pluginName, "plugin.json")
pluginData, _ := IoHelper.ReadFile(pluginDataPath)
res := m.InstalledPlugin{}
json.Unmarshal(pluginData, &res)
if res.Info.Version == "" {
res.Info.Version = "0.0.0"
}
if res.Id == "" {
res.Id = res.Name
}
return res
}
func GetLocalPlugins(pluginDir string) []m.InstalledPlugin {
result := make([]m.InstalledPlugin, 0)
files, _ := IoHelper.ReadDir(pluginDir)
for _, f := range files {
res := ReadPlugin(pluginDir, f.Name())
result = append(result, res)
}
return result
}
func RemoveInstalledPlugin(pluginPath, id string) error {
return IoHelper.RemoveAll(path.Join(pluginPath, id))
}
func GetPlugin(id string) (m.Plugin, error) {
resp, err := ListAllPlugins()
if err != nil {
}
for _, i := range resp.Plugins {
if i.Id == id {
return i, nil
}
}
return m.Plugin{}, errors.New("could not find plugin named \"" + id + "\"")
}
language: go
go:
- 1.0
- 1.1
- 1.2
- 1.3
- 1.4
script:
- go test
# Versioning Library for Go
[![Build Status](https://travis-ci.org/hashicorp/go-version.svg?branch=master)](https://travis-ci.org/hashicorp/go-version)
go-version is a library for parsing versions and version constraints,
and verifying versions against a set of constraints. go-version
can sort a collection of versions properly, handles prerelease/beta
versions, can increment versions, etc.
Versions used with go-version must follow [SemVer](http://semver.org/).
## Installation and Usage
Package documentation can be found on
[GoDoc](http://godoc.org/github.com/hashicorp/go-version).
Installation can be done with a normal `go get`:
```
$ go get github.com/hashicorp/go-version
```
#### Version Parsing and Comparison
```go
v1, err := version.NewVersion("1.2")
v2, err := version.NewVersion("1.5+metadata")
// Comparison example. There is also GreaterThan, Equal, and just
// a simple Compare that returns an int allowing easy >=, <=, etc.
if v1.LessThan(v2) {
fmt.Printf("%s is less than %s", v1, v2)
}
```
#### Version Constraints
```go
v1, err := version.NewVersion("1.2")
// Constraints example.
constraints, err := version.NewConstraint(">= 1.0, < 1.4")
if constraints.Check(v1) {
fmt.Printf("%s satisfies constraints %s", v1, constraints)
}
```
#### Version Sorting
```go
versionsRaw := []string{"1.1", "0.7.1", "1.4-beta", "1.4", "2"}
versions := make([]*version.Version, len(versionsRaw))
for i, raw := range versionsRaw {
v, _ := version.NewVersion(raw)
versions[i] = v
}
// After this, the versions are properly sorted
sort.Sort(version.Collection(versions))
```
## Issues and Contributing
If you find an issue with this library, please report an issue. If you'd
like, we welcome any contributions. Fork this library and submit a pull
request.
package version
import (
"fmt"
"regexp"
"strings"
)
// Constraint represents a single constraint for a version, such as
// ">= 1.0".
type Constraint struct {
f constraintFunc
check *Version
original string
}
// Constraints is a slice of constraints. We make a custom type so that
// we can add methods to it.
type Constraints []*Constraint
type constraintFunc func(v, c *Version) bool
var constraintOperators map[string]constraintFunc
var constraintRegexp *regexp.Regexp
func init() {
constraintOperators = map[string]constraintFunc{
"": constraintEqual,
"=": constraintEqual,
"!=": constraintNotEqual,
">": constraintGreaterThan,
"<": constraintLessThan,
">=": constraintGreaterThanEqual,
"<=": constraintLessThanEqual,
"~>": constraintPessimistic,
}
ops := make([]string, 0, len(constraintOperators))
for k, _ := range constraintOperators {
ops = append(ops, regexp.QuoteMeta(k))
}
constraintRegexp = regexp.MustCompile(fmt.Sprintf(
`^\s*(%s)\s*(%s)\s*$`,
strings.Join(ops, "|"),
VersionRegexpRaw))
}
// NewConstraint will parse one or more constraints from the given
// constraint string. The string must be a comma-separated list of
// constraints.
func NewConstraint(v string) (Constraints, error) {
vs := strings.Split(v, ",")
result := make([]*Constraint, len(vs))
for i, single := range vs {
c, err := parseSingle(single)
if err != nil {
return nil, err
}
result[i] = c
}
return Constraints(result), nil
}
// Check tests if a version satisfies all the constraints.
func (cs Constraints) Check(v *Version) bool {
for _, c := range cs {
if !c.Check(v) {
return false
}
}
return true
}
// Returns the string format of the constraints
func (cs Constraints) String() string {
csStr := make([]string, len(cs))
for i, c := range cs {
csStr[i] = c.String()
}
return strings.Join(csStr, ",")
}
// Check tests if a constraint is validated by the given version.
func (c *Constraint) Check(v *Version) bool {
return c.f(v, c.check)
}
func (c *Constraint) String() string {
return c.original
}
func parseSingle(v string) (*Constraint, error) {
matches := constraintRegexp.FindStringSubmatch(v)
if matches == nil {
return nil, fmt.Errorf("Malformed constraint: %s", v)
}
check, err := NewVersion(matches[2])
if err != nil {
return nil, err
}
return &Constraint{
f: constraintOperators[matches[1]],
check: check,
original: v,
}, nil
}
//-------------------------------------------------------------------
// Constraint functions
//-------------------------------------------------------------------
func constraintEqual(v, c *Version) bool {
return v.Equal(c)
}
func constraintNotEqual(v, c *Version) bool {
return !v.Equal(c)
}
func constraintGreaterThan(v, c *Version) bool {
return v.Compare(c) == 1
}
func constraintLessThan(v, c *Version) bool {
return v.Compare(c) == -1
}
func constraintGreaterThanEqual(v, c *Version) bool {
return v.Compare(c) >= 0
}
func constraintLessThanEqual(v, c *Version) bool {
return v.Compare(c) <= 0
}
func constraintPessimistic(v, c *Version) bool {
if v.LessThan(c) {
return false
}
for i := 0; i < c.si-1; i++ {
if v.segments[i] != c.segments[i] {
return false
}
}
return true
}
package version
import (
"bytes"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
// The compiled regular expression used to test the validity of a version.
var versionRegexp *regexp.Regexp
// The raw regular expression string used for testing the validity
// of a version.
const VersionRegexpRaw string = `([0-9]+(\.[0-9]+){0,2})` +
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`?`
// Version represents a single version.
type Version struct {
metadata string
pre string
segments []int
si int
}
func init() {
versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
}
// NewVersion parses the given version and returns a new
// Version.
func NewVersion(v string) (*Version, error) {
matches := versionRegexp.FindStringSubmatch(v)
if matches == nil {
return nil, fmt.Errorf("Malformed version: %s", v)
}
segmentsStr := strings.Split(matches[1], ".")
segments := make([]int, len(segmentsStr), 3)
si := 0
for i, str := range segmentsStr {
val, err := strconv.ParseInt(str, 10, 32)
if err != nil {
return nil, fmt.Errorf(
"Error parsing version: %s", err)
}
segments[i] = int(val)
si += 1
}
for i := len(segments); i < 3; i++ {
segments = append(segments, 0)
}
return &Version{
metadata: matches[7],
pre: matches[4],
segments: segments,
si: si,
}, nil
}
// Must is a helper that wraps a call to a function returning (*Version, error)
// and panics if error is non-nil.
func Must(v *Version, err error) *Version {
if err != nil {
panic(err)
}
return v
}
// Compare compares this version to another version. This
// returns -1, 0, or 1 if this version is smaller, equal,
// or larger than the other version, respectively.
//
// If you want boolean results, use the LessThan, Equal,
// or GreaterThan methods.
func (v *Version) Compare(other *Version) int {
// A quick, efficient equality check
if v.String() == other.String() {
return 0
}
segmentsSelf := v.Segments()
segmentsOther := other.Segments()
// If the segments are the same, we must compare on prerelease info
if reflect.DeepEqual(segmentsSelf, segmentsOther) {
preSelf := v.Prerelease()
preOther := other.Prerelease()
if preSelf == "" && preOther == "" {
return 0
}
if preSelf == "" {
return 1
}
if preOther == "" {
return -1
}
return comparePrereleases(preSelf, preOther)
}
// Compare the segments
for i := 0; i < len(segmentsSelf); i++ {
lhs := segmentsSelf[i]
rhs := segmentsOther[i]
if lhs == rhs {
continue
} else if lhs < rhs {
return -1
} else {
return 1
}
}
panic("should not be reached")
}
func comparePart(preSelf string, preOther string) int {
if preSelf == preOther {
return 0
}
// if a part is empty, we use the other to decide
if preSelf == "" {
_, notIsNumeric := strconv.ParseInt(preOther, 10, 64)
if notIsNumeric == nil {
return -1
}
return 1
}
if preOther == "" {
_, notIsNumeric := strconv.ParseInt(preSelf, 10, 64)
if notIsNumeric == nil {
return 1
}
return -1
}
if preSelf > preOther {
return 1
}
return -1
}
func comparePrereleases(v string, other string) int {
// the same pre release!
if v == other {
return 0
}
// split both pre releases for analyse their parts
selfPreReleaseMeta := strings.Split(v, ".")
otherPreReleaseMeta := strings.Split(other, ".")
selfPreReleaseLen := len(selfPreReleaseMeta)
otherPreReleaseLen := len(otherPreReleaseMeta)
biggestLen := otherPreReleaseLen
if selfPreReleaseLen > otherPreReleaseLen {
biggestLen = selfPreReleaseLen
}
// loop for parts to find the first difference
for i := 0; i < biggestLen; i = i + 1 {
partSelfPre := ""
if i < selfPreReleaseLen {
partSelfPre = selfPreReleaseMeta[i]
}
partOtherPre := ""
if i < otherPreReleaseLen {
partOtherPre = otherPreReleaseMeta[i]
}
compare := comparePart(partSelfPre, partOtherPre)
// if parts are equals, continue the loop
if compare != 0 {
return compare
}
}
return 0
}
// Equal tests if two versions are equal.
func (v *Version) Equal(o *Version) bool {
return v.Compare(o) == 0
}
// GreaterThan tests if this version is greater than another version.
func (v *Version) GreaterThan(o *Version) bool {
return v.Compare(o) > 0
}
// LessThan tests if this version is less than another version.
func (v *Version) LessThan(o *Version) bool {
return v.Compare(o) < 0
}
// Metadata returns any metadata that was part of the version
// string.
//
// Metadata is anything that comes after the "+" in the version.
// For example, with "1.2.3+beta", the metadata is "beta".
func (v *Version) Metadata() string {
return v.metadata
}
// Prerelease returns any prerelease data that is part of the version,
// or blank if there is no prerelease data.
//
// Prerelease information is anything that comes after the "-" in the
// version (but before any metadata). For example, with "1.2.3-beta",
// the prerelease information is "beta".
func (v *Version) Prerelease() string {
return v.pre
}
// Segments returns the numeric segments of the version as a slice.
//
// This excludes any metadata or pre-release information. For example,
// for a version "1.2.3-beta", segments will return a slice of
// 1, 2, 3.
func (v *Version) Segments() []int {
return v.segments
}
// String returns the full version string included pre-release
// and metadata information.
func (v *Version) String() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "%d.%d.%d", v.segments[0], v.segments[1], v.segments[2])
if v.pre != "" {
fmt.Fprintf(&buf, "-%s", v.pre)
}
if v.metadata != "" {
fmt.Fprintf(&buf, "+%s", v.metadata)
}
return buf.String()
}
package version
// Collection is a type that implements the sort.Interface interface
// so that versions can be sorted.
type Collection []*Version
func (v Collection) Len() int {
return len(v)
}
func (v Collection) Less(i, j int) bool {
return v[i].LessThan(v[j])
}
func (v Collection) Swap(i, j int) {
v[i], v[j] = v[j], v[i]
}
......@@ -31,7 +31,7 @@ var (
linuxPackageIteration string = ""
race bool
workingDir string
serverBinaryName string = "grafana-server"
binaries []string = []string{"grafana-server", "grafana-cli"}
)
const minGoVersion = 1.3
......@@ -63,9 +63,10 @@ func main() {
setup()
case "build":
pkg := "."
clean()
build(pkg, []string{})
for _, binary := range binaries {
build(binary, "./pkg/cmd/"+binary, []string{})
}
case "test":
test("./pkg/...")
......@@ -139,6 +140,8 @@ type linuxPackageOptions struct {
packageType string
homeDir string
binPath string
serverBinPath string
cliBinPath string
configDir string
configFilePath string
ldapFilePath string
......@@ -159,7 +162,7 @@ func createDebPackages() {
createPackage(linuxPackageOptions{
packageType: "deb",
homeDir: "/usr/share/grafana",
binPath: "/usr/sbin/grafana-server",
binPath: "/usr/sbin",
configDir: "/etc/grafana",
configFilePath: "/etc/grafana/grafana.ini",
ldapFilePath: "/etc/grafana/ldap.toml",
......@@ -181,7 +184,7 @@ func createRpmPackages() {
createPackage(linuxPackageOptions{
packageType: "rpm",
homeDir: "/usr/share/grafana",
binPath: "/usr/sbin/grafana-server",
binPath: "/usr/sbin",
configDir: "/etc/grafana",
configFilePath: "/etc/grafana/grafana.ini",
ldapFilePath: "/etc/grafana/ldap.toml",
......@@ -216,7 +219,9 @@ func createPackage(options linuxPackageOptions) {
runPrint("mkdir", "-p", filepath.Join(packageRoot, "/usr/sbin"))
// copy binary
runPrint("cp", "-p", filepath.Join(workingDir, "tmp/bin/"+serverBinaryName), filepath.Join(packageRoot, options.binPath))
for _, binary := range binaries {
runPrint("cp", "-p", filepath.Join(workingDir, "tmp/bin/"+binary), filepath.Join(packageRoot, "/usr/sbin/"+binary))
}
// copy init.d script
runPrint("cp", "-p", options.initdScriptSrc, filepath.Join(packageRoot, options.initdScriptFilePath))
// copy environment var file
......@@ -312,8 +317,8 @@ func test(pkg string) {
runPrint("go", "test", "-short", "-timeout", "60s", pkg)
}
func build(pkg string, tags []string) {
binary := "./bin/" + serverBinaryName
func build(binaryName, pkg string, tags []string) {
binary := "./bin/" + binaryName
if goos == "windows" {
binary += ".exe"
}
......
package commands
import (
"github.com/codegangsta/cli"
)
type CommandLine interface {
ShowHelp()
ShowVersion()
Application() *cli.App
Args() cli.Args
Bool(name string) bool
Int(name string) int
String(name string) string
StringSlice(name string) []string
GlobalString(name string) string
FlagNames() (names []string)
Generic(name string) interface{}
}
type contextCommandLine struct {
*cli.Context
}
func (c *contextCommandLine) ShowHelp() {
cli.ShowCommandHelp(c.Context, c.Command.Name)
}
func (c *contextCommandLine) ShowVersion() {
cli.ShowVersion(c.Context)
}
func (c *contextCommandLine) Application() *cli.App {
return c.App
}
package commands
import (
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
"os"
)
func runCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
return func(context *cli.Context) {
cmd := &contextCommandLine{context}
if err := command(cmd); err != nil {
log.Errorf("%v\n\n", err)
cmd.ShowHelp()
os.Exit(1)
} else {
log.Info("\nRestart grafana after installing plugins . <service grafana-server restart>\n\n")
}
}
}
var Commands = []cli.Command{
{
Name: "install",
Usage: "install <plugin name>",
Action: runCommand(installCommand),
}, {
Name: "list-remote",
Usage: "list remote available plugins",
Action: runCommand(listremoteCommand),
}, {
Name: "upgrade",
Usage: "upgrade <plugin name>",
Action: runCommand(upgradeCommand),
}, {
Name: "upgrade-all",
Usage: "upgrades all your installed plugins",
Action: runCommand(upgradeAllCommand),
}, {
Name: "ls",
Usage: "list all installed plugins",
Action: runCommand(lsCommand),
}, {
Name: "remove",
Usage: "remove <plugin name>",
Action: runCommand(removeCommand),
},
}
package commandstest
import (
"github.com/codegangsta/cli"
)
type FakeFlagger struct {
Data map[string]interface{}
}
type FakeCommandLine struct {
LocalFlags, GlobalFlags *FakeFlagger
HelpShown, VersionShown bool
CliArgs []string
}
func (ff FakeFlagger) String(key string) string {
if value, ok := ff.Data[key]; ok {
return value.(string)
}
return ""
}
func (ff FakeFlagger) StringSlice(key string) []string {
if value, ok := ff.Data[key]; ok {
return value.([]string)
}
return []string{}
}
func (ff FakeFlagger) Int(key string) int {
if value, ok := ff.Data[key]; ok {
return value.(int)
}
return 0
}
func (ff FakeFlagger) Bool(key string) bool {
if value, ok := ff.Data[key]; ok {
return value.(bool)
}
return false
}
func (fcli *FakeCommandLine) String(key string) string {
return fcli.LocalFlags.String(key)
}
func (fcli *FakeCommandLine) StringSlice(key string) []string {
return fcli.LocalFlags.StringSlice(key)
}
func (fcli *FakeCommandLine) Int(key string) int {
return fcli.LocalFlags.Int(key)
}
func (fcli *FakeCommandLine) Bool(key string) bool {
if fcli.LocalFlags == nil {
return false
}
return fcli.LocalFlags.Bool(key)
}
func (fcli *FakeCommandLine) GlobalString(key string) string {
return fcli.GlobalFlags.String(key)
}
func (fcli *FakeCommandLine) Generic(name string) interface{} {
return fcli.LocalFlags.Data[name]
}
func (fcli *FakeCommandLine) FlagNames() []string {
flagNames := []string{}
for key := range fcli.LocalFlags.Data {
flagNames = append(flagNames, key)
}
return flagNames
}
func (fcli *FakeCommandLine) ShowHelp() {
fcli.HelpShown = true
}
func (fcli *FakeCommandLine) Application() *cli.App {
return cli.NewApp()
}
func (fcli *FakeCommandLine) Args() cli.Args {
return fcli.CliArgs
}
func (fcli *FakeCommandLine) ShowVersion() {
fcli.VersionShown = true
}
package commandstest
import (
"os"
"time"
)
type FakeIoUtil struct {
FakeReadDir []os.FileInfo
FakeIsDirectory bool
}
func (util *FakeIoUtil) Stat(path string) (os.FileInfo, error) {
return &FakeFileInfo{IsDirectory: util.FakeIsDirectory}, nil
}
func (util *FakeIoUtil) RemoveAll(path string) error {
return nil
}
func (util *FakeIoUtil) ReadDir(path string) ([]os.FileInfo, error) {
return util.FakeReadDir, nil
}
func (i *FakeIoUtil) ReadFile(filename string) ([]byte, error) {
return make([]byte, 0), nil
}
type FakeFileInfo struct {
IsDirectory bool
}
func (ffi *FakeFileInfo) IsDir() bool {
return ffi.IsDirectory
}
func (ffi FakeFileInfo) Size() int64 {
return 1
}
func (ffi FakeFileInfo) Mode() os.FileMode {
return 0777
}
func (ffi FakeFileInfo) Name() string {
return ""
}
func (ffi FakeFileInfo) ModTime() time.Time {
return time.Time{}
}
func (ffi FakeFileInfo) Sys() interface{} {
return nil
}
package commands
import (
"archive/zip"
"bytes"
"errors"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"regexp"
)
func validateInput(c CommandLine, pluginFolder string) error {
arg := c.Args().First()
if arg == "" {
return errors.New("please specify plugin to install")
}
pluginDir := c.GlobalString("path")
if pluginDir == "" {
return errors.New("missing path flag")
}
fileinfo, err := os.Stat(pluginDir)
if err != nil && !fileinfo.IsDir() {
return errors.New("path is not a directory")
}
return nil
}
func installCommand(c CommandLine) error {
pluginFolder := c.GlobalString("path")
if err := validateInput(c, pluginFolder); err != nil {
return err
}
pluginToInstall := c.Args().First()
version := c.Args().Get(1)
log.Infof("version: %v\n", version)
return InstallPlugin(pluginToInstall, pluginFolder, version)
}
func InstallPlugin(pluginName, pluginFolder, version string) error {
plugin, err := s.GetPlugin(pluginName)
if err != nil {
return err
}
v, err := SelectVersion(plugin, version)
if err != nil {
return err
}
url := v.Url
commit := v.Commit
downloadURL := url + "/archive/" + commit + ".zip"
log.Infof("installing %v @ %v\n", plugin.Id, version)
log.Infof("from url: %v\n", downloadURL)
log.Infof("on commit: %v\n", commit)
log.Infof("into: %v\n", pluginFolder)
err = downloadFile(plugin.Id, pluginFolder, downloadURL)
if err == nil {
log.Infof("Installed %v successfully ✔\n", plugin.Id)
}
res, _ := s.ReadPlugin(pluginFolder, pluginName)
for _, v := range res.Dependency.Plugins {
InstallPlugin(v.Id, pluginFolder, "")
log.Infof("Installed Dependency: %v ✔\n", v.Id)
}
return err
}
func SelectVersion(plugin m.Plugin, version string) (m.Version, error) {
if version == "" {
return plugin.Versions[0], nil
}
for _, v := range plugin.Versions {
if v.Version == version {
return v, nil
}
}
return m.Version{}, errors.New("Could not find the version your looking for")
}
func RemoveGitBuildFromname(pluginname, filename string) string {
r := regexp.MustCompile("^[a-zA-Z0-9_.-]*/")
return r.ReplaceAllString(filename, pluginname+"/")
}
func downloadFile(pluginName, filepath, url string) (err error) {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
r, err := zip.NewReader(bytes.NewReader(body), resp.ContentLength)
if err != nil {
return err
}
for _, zf := range r.File {
newfile := path.Join(filepath, RemoveGitBuildFromname(pluginName, zf.Name))
if zf.FileInfo().IsDir() {
os.Mkdir(newfile, 0777)
} else {
dst, err := os.Create(newfile)
if err != nil {
log.Errorf("%v", err)
}
defer dst.Close()
src, err := zf.Open()
if err != nil {
log.Errorf("%v", err)
}
defer src.Close()
io.Copy(dst, src)
}
}
return nil
}
package commands
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestFoldernameReplacement(t *testing.T) {
Convey("path containing git commit path", t, func() {
pluginName := "datasource-plugin-kairosdb"
paths := map[string]string{
"datasource-plugin-kairosdb-cc4a3965ef5d3eb1ae0ee4f93e9e78ec7db69e64/": "datasource-plugin-kairosdb/",
"datasource-plugin-kairosdb-cc4a3965ef5d3eb1ae0ee4f93e9e78ec7db69e64/README.md": "datasource-plugin-kairosdb/README.md",
"datasource-plugin-kairosdb-cc4a3965ef5d3eb1ae0ee4f93e9e78ec7db69e64/partials/": "datasource-plugin-kairosdb/partials/",
"datasource-plugin-kairosdb-cc4a3965ef5d3eb1ae0ee4f93e9e78ec7db69e64/partials/config.html": "datasource-plugin-kairosdb/partials/config.html",
}
Convey("should be replaced with plugin name", func() {
for k, v := range paths {
So(RemoveGitBuildFromname(pluginName, k), ShouldEqual, v)
}
})
})
Convey("path containing git commit path", t, func() {
pluginName := "app-example"
paths := map[string]string{
"app-plugin-example-3c28f65ac6fb7f1e234b0364b97081d836495439/": "app-example/",
}
Convey("should be replaced with plugin name", func() {
for k, v := range paths {
So(RemoveGitBuildFromname(pluginName, k), ShouldEqual, v)
}
})
})
}
package commands
import (
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
)
func listremoteCommand(c CommandLine) error {
plugin, err := s.ListAllPlugins()
if err != nil {
return err
}
for _, i := range plugin.Plugins {
log.Infof("id: %v version:\n", i.Id)
}
return nil
}
package commands
import (
"errors"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
)
var ls_getPlugins func(path string) []m.InstalledPlugin = s.GetLocalPlugins
var validateLsCommmand = func(pluginDir string) error {
if pluginDir == "" {
return errors.New("missing path flag")
}
log.Info("plugindir: " + pluginDir + "\n")
pluginDirInfo, err := s.IoHelper.Stat(pluginDir)
if err != nil {
return errors.New("missing path flag")
}
if pluginDirInfo.IsDir() == false {
return errors.New("plugin path is not a directory")
}
return nil
}
func lsCommand(c CommandLine) error {
pluginDir := c.GlobalString("path")
if err := validateLsCommmand(pluginDir); err != nil {
return err
}
for _, plugin := range ls_getPlugins(pluginDir) {
log.Infof("plugin: %s @ %s \n", plugin.Name, plugin.Info.Version)
}
return nil
}
package commands
import (
"errors"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/commandstest"
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestMissingPath(t *testing.T) {
var org = validateLsCommmand
Convey("ls command", t, func() {
validateLsCommmand = org
Convey("Missing path", func() {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"ls"},
GlobalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"path": "",
},
},
}
s.IoHelper = &commandstest.FakeIoUtil{}
Convey("should return error", func() {
err := lsCommand(commandLine)
So(err, ShouldNotBeNil)
})
})
Convey("Path is not a directory", func() {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"ls"},
GlobalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"path": "/var/lib/grafana/plugins",
},
},
}
s.IoHelper = &commandstest.FakeIoUtil{
FakeIsDirectory: false,
}
Convey("should return error", func() {
err := lsCommand(commandLine)
So(err, ShouldNotBeNil)
})
})
Convey("can override validateLsCommand", func() {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"ls"},
GlobalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"path": "/var/lib/grafana/plugins",
},
},
}
validateLsCommmand = func(pluginDir string) error {
return errors.New("dummie error")
}
Convey("should return error", func() {
err := lsCommand(commandLine)
So(err.Error(), ShouldEqual, "dummie error")
})
})
Convey("Validate that validateLsCommand is reset", func() {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"ls"},
GlobalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"path": "/var/lib/grafana/plugins",
},
},
}
Convey("should return error", func() {
err := lsCommand(commandLine)
So(err.Error(), ShouldNotEqual, "dummie error")
})
})
})
}
package commands
import (
"errors"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
)
var getPluginss func(path string) []m.InstalledPlugin = services.GetLocalPlugins
var removePlugin func(pluginPath, id string) error = services.RemoveInstalledPlugin
func removeCommand(c CommandLine) error {
pluginPath := c.GlobalString("path")
localPlugins := getPluginss(pluginPath)
log.Info("remove!\n")
plugin := c.Args().First()
log.Info("plugin: " + plugin + "\n")
if plugin == "" {
return errors.New("Missing plugin parameter")
}
log.Infof("plugins : \n%v\n", localPlugins)
for _, p := range localPlugins {
if p.Id == c.Args().First() {
log.Infof("removing plugin %s", p.Id)
removePlugin(pluginPath, p.Id)
}
}
return nil
}
package commands
import (
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
"github.com/hashicorp/go-version"
)
func ShouldUpgrade(installed string, remote m.Plugin) bool {
installedVersion, err1 := version.NewVersion(installed)
if err1 != nil {
return false
}
for _, v := range remote.Versions {
remoteVersion, err2 := version.NewVersion(v.Version)
if err2 == nil {
if installedVersion.LessThan(remoteVersion) {
return true
}
}
}
return false
}
func upgradeAllCommand(c CommandLine) error {
pluginDir := c.GlobalString("path")
localPlugins := s.GetLocalPlugins(pluginDir)
remotePlugins, err := s.ListAllPlugins()
if err != nil {
return err
}
pluginsToUpgrade := make([]m.InstalledPlugin, 0)
for _, localPlugin := range localPlugins {
for _, remotePlugin := range remotePlugins.Plugins {
if localPlugin.Id == remotePlugin.Id {
if ShouldUpgrade(localPlugin.Info.Version, remotePlugin) {
pluginsToUpgrade = append(pluginsToUpgrade, localPlugin)
}
}
}
}
for _, p := range pluginsToUpgrade {
log.Infof("Upgrading %v \n", p.Id)
s.RemoveInstalledPlugin(pluginDir, p.Id)
InstallPlugin(p.Id, pluginDir, "")
}
return nil
}
package commands
import (
"testing"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
. "github.com/smartystreets/goconvey/convey"
)
func TestVersionComparsion(t *testing.T) {
Convey("Validate that version is outdated", t, func() {
versions := []m.Version{
{Version: "1.1.1"},
{Version: "2.0.0"},
}
shouldUpgrade := map[string]m.Plugin{
"0.0.0": {Versions: versions},
"1.0.0": {Versions: versions},
}
Convey("should return error", func() {
for k, v := range shouldUpgrade {
So(ShouldUpgrade(k, v), ShouldBeTrue)
}
})
})
Convey("Validate that version is ok", t, func() {
versions := []m.Version{
{Version: "1.1.1"},
{Version: "2.0.0"},
}
shouldNotUpgrade := map[string]m.Plugin{
"2.0.0": {Versions: versions},
"6.0.0": {Versions: versions},
}
Convey("should return error", func() {
for k, v := range shouldNotUpgrade {
So(ShouldUpgrade(k, v), ShouldBeFalse)
}
})
})
}
package commands
import (
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
)
func upgradeCommand(c CommandLine) error {
pluginDir := c.GlobalString("path")
pluginName := c.Args().First()
localPlugin, err := s.ReadPlugin(pluginDir, pluginName)
if err != nil {
return err
}
remotePlugins, err2 := s.ListAllPlugins()
if err2 != nil {
return err2
}
for _, v := range remotePlugins.Plugins {
if localPlugin.Id == v.Id {
if ShouldUpgrade(localPlugin.Info.Version, v) {
s.RemoveInstalledPlugin(pluginDir, pluginName)
return InstallPlugin(localPlugin.Id, pluginDir, "")
}
}
}
return nil
}
package log
import (
"fmt"
)
var (
debugmode = false
)
func Debug(args ...interface{}) {
if debugmode {
fmt.Print(args...)
}
}
func Debugf(fmtString string, args ...interface{}) {
if debugmode {
fmt.Printf(fmtString, args...)
}
}
func Error(args ...interface{}) {
fmt.Print(args...)
}
func Errorf(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
}
func Info(args ...interface{}) {
fmt.Print(args...)
}
func Infof(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
}
func Warn(args ...interface{}) {
fmt.Print(args...)
}
func Warnf(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
}
func SetDebug(value bool) {
debugmode = value
}
package main
import (
"fmt"
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
"os"
"runtime"
)
var version = "master"
func getGrafanaPluginPath() string {
//TODO: try to get path from os:env GF_PLUGIN_FOLDER
os := runtime.GOOS
if os == "windows" {
return "C:\\opt\\grafana\\plugins"
} else {
return "/var/lib/grafana/plugins"
}
}
func main() {
SetupLogging()
app := cli.NewApp()
app.Name = "Grafana cli"
app.Author = "raintank"
app.Email = "https://github.com/grafana/grafana"
app.Version = version
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "path",
Usage: "path to the grafana installation",
Value: getGrafanaPluginPath(),
},
cli.BoolFlag{
Name: "debug, d",
Usage: "enable debug logging",
},
}
app.Commands = commands.Commands
app.CommandNotFound = cmdNotFound
if err := app.Run(os.Args); err != nil {
log.Errorf("%v", err)
}
}
func SetupLogging() {
for _, f := range os.Args {
if f == "-D" || f == "--debug" || f == "-debug" {
log.SetDebug(true)
}
}
}
func cmdNotFound(c *cli.Context, command string) {
fmt.Printf(
"%s: '%s' is not a %s command. See '%s --help'.\n",
c.App.Name,
command,
c.App.Name,
os.Args[0],
)
os.Exit(1)
}
package models
import (
"os"
)
type InstalledPlugin struct {
Id string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Info PluginInfo `json:"info"`
Dependency Dependency `json:"dependencies"`
}
type Dependency struct {
GrafanaVersion string `json:"grafanaVersion"`
Plugins []Plugin `json:"plugins"`
}
type PluginInfo struct {
Version string `json:"version"`
Updated string `json:"updated"`
}
type Plugin struct {
Id string `json:"id"`
Category string `json:"category"`
Versions []Version `json:"versions"`
}
type Version struct {
Commit string `json:"commit"`
Url string `json:"url"`
Version string `json:"version"`
}
type PluginRepo struct {
Plugins []Plugin `json:"plugins"`
Version string `json:"version"`
}
type IoUtil interface {
Stat(path string) (os.FileInfo, error)
RemoveAll(path string) error
ReadDir(path string) ([]os.FileInfo, error)
ReadFile(filename string) ([]byte, error)
}
package services
import (
"io/ioutil"
"os"
)
type IoUtilImp struct {
}
func (i IoUtilImp) Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}
func (i IoUtilImp) RemoveAll(path string) error {
return os.RemoveAll(path)
}
func (i IoUtilImp) ReadDir(path string) ([]os.FileInfo, error) {
return ioutil.ReadDir(path)
}
func (i IoUtilImp) ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}
package services
import (
"encoding/json"
"errors"
"github.com/franela/goreq"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
"path"
)
var IoHelper m.IoUtil = IoUtilImp{}
func ListAllPlugins() (m.PluginRepo, error) {
res, _ := goreq.Request{Uri: "https://raw.githubusercontent.com/grafana/grafana-plugin-repository/master/repo.json"}.Do()
var resp m.PluginRepo
err := res.Body.FromJsonTo(&resp)
if err != nil {
return m.PluginRepo{}, errors.New("Could not load plugin data")
}
return resp, nil
}
func ReadPlugin(pluginDir, pluginName string) (m.InstalledPlugin, error) {
pluginDataPath := path.Join(pluginDir, pluginName, "plugin.json")
pluginData, _ := IoHelper.ReadFile(pluginDataPath)
res := m.InstalledPlugin{}
json.Unmarshal(pluginData, &res)
if res.Info.Version == "" {
res.Info.Version = "0.0.0"
}
if res.Id == "" {
return m.InstalledPlugin{}, errors.New("could not read find plugin " + pluginName)
}
return res, nil
}
func GetLocalPlugins(pluginDir string) []m.InstalledPlugin {
result := make([]m.InstalledPlugin, 0)
files, _ := IoHelper.ReadDir(pluginDir)
for _, f := range files {
res, err := ReadPlugin(pluginDir, f.Name())
if err == nil {
result = append(result, res)
}
}
return result
}
func RemoveInstalledPlugin(pluginPath, id string) error {
log.Infof("Removing plugin: %v\n", id)
return IoHelper.RemoveAll(path.Join(pluginPath, id))
}
func GetPlugin(id string) (m.Plugin, error) {
resp, err := ListAllPlugins()
if err != nil {
}
for _, i := range resp.Plugins {
if i.Id == id {
return i, nil
}
}
return m.Plugin{}, errors.New("could not find plugin named \"" + id + "\"")
}
......@@ -12,7 +12,6 @@ import (
"syscall"
"time"
"github.com/grafana/grafana/pkg/cmd"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/metrics"
......@@ -74,7 +73,7 @@ func main() {
go metrics.StartUsageReportLoop()
}
cmd.StartServer()
StartServer()
exitChan <- 0
}
......
// Copyright 2014 Unknwon
// Copyright 2014 Torkel Ödegaard
package cmd
package main
import (
"fmt"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment