diff --git a/Makefile b/Makefile index 6931bd2da..b72a26de6 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ cover: build-erasure build-signify build-split build-crc32c build-cpu build-sha1 install: build-erasure @godep go install github.com/minio-io/minio/cmd/minio && echo "Installed minio into ${GOPATH}/bin" + @godep go install github.com/minio-io/minio/cmd/minio-cli && echo "Install minio-cli into ${GOPATH}/bin" save: restore @godep save ./... diff --git a/cmd/minio-cli/.gitignore b/cmd/minio-cli/.gitignore new file mode 100644 index 000000000..d9a86452a --- /dev/null +++ b/cmd/minio-cli/.gitignore @@ -0,0 +1,2 @@ +templates.go +minio-cli \ No newline at end of file diff --git a/cmd/minio-cli/README.md b/cmd/minio-cli/README.md new file mode 100644 index 000000000..9f5cc2a90 --- /dev/null +++ b/cmd/minio-cli/README.md @@ -0,0 +1,32 @@ +## Introduction + +`minio-cli` is option stub builder for ``minio`` project using [codegangsta/cli](https://github.com/codegangsta/cli), + +The idea is to be able to do rapid prototyping and facilitate new contributors to the project + +## Usage + +You just need to set its application name and options: + +```bash +$ minio-cli -options option1,option2,option3 [application] +``` + +Generates three files namely [application].go, [application]-options.go, [application].md + +## Example + +If you want to start to building `bucket` application which has subcommands `get`, `put`, `list`: + +```bash +$ minio-cli -options get,put,list foo +$ ls foo/ +foo-options.go foo.go foo.md +``` + +## Installation + +```bash +$ go get github.com/minio-io/minio +$ make install +``` diff --git a/cmd/minio-cli/formatter.go b/cmd/minio-cli/formatter.go new file mode 100644 index 000000000..93ba646ab --- /dev/null +++ b/cmd/minio-cli/formatter.go @@ -0,0 +1,89 @@ +// this code is from gofmt, modified for our internal usage - http://golang.org/src/cmd/gofmt/gofmt.go +package main + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error { + if in == nil { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + in = f + } + + src, err := ioutil.ReadAll(in) + if err != nil { + return err + } + fileSet := token.NewFileSet() + file, err := parser.ParseFile(fileSet, filename, src, parser.ParseComments) + if err != nil { + return err + } + + ast.SortImports(fileSet, file) + + var buf bytes.Buffer + tabWidth := 8 + printerMode := printer.UseSpaces | printer.TabIndent + err = (&printer.Config{Mode: printerMode, Tabwidth: tabWidth}).Fprint(&buf, fileSet, file) + if err != nil { + return err + } + + res := buf.Bytes() + if !bytes.Equal(src, res) { + err = ioutil.WriteFile(filename, res, 0) + if err != nil { + return err + } + } + return nil +} + +func isGofile(f os.FileInfo) bool { + name := f.Name() + return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") +} + +func visitFile(pathName string, f os.FileInfo, err error) error { + + if err == nil && isGofile(f) { + err = processFile(pathName, nil, os.Stdout, false) + } + + if err != nil { + return err + } + + return nil +} + +func walkDir(pathName string) { + filepath.Walk(pathName, visitFile) +} + +func GoFormat(pathName string) error { + dir, err := os.Stat(pathName) + if err != nil { + return err + } + + if dir.IsDir() { + walkDir(pathName) + } + return nil +} diff --git a/cmd/minio-cli/minio-cli.go b/cmd/minio-cli/minio-cli.go new file mode 100644 index 000000000..1f80cd5e2 --- /dev/null +++ b/cmd/minio-cli/minio-cli.go @@ -0,0 +1,163 @@ +package main + +import ( + "flag" + "log" + "os" + "path" + "strings" + "text/template" + "time" + + "github.com/minio-io/minio/pkgs/utils" +) + +type source struct { + Name string + TempLate template.Template +} + +const ( + // Relative path from GOPATH default + TEMPLATEREPO = "/src/github.com/minio-io/minio/templates/" +) + +type option struct { + Name string + Definename string + Functionname string +} + +type application struct { + Name string + Usage string + Month string + Year int + Options []option +} + +func (f source) generate(appName string, def application) error { + wr, err := os.Create(path.Join(appName, f.Name)) + if err != nil { + return err + } + + defer wr.Close() + return f.TempLate.Execute(wr, def) +} + +func initApplication(appname, usage string, inputOptions []string) application { + year, month, _ := time.Now().Date() + return application{ + Name: appname, + Usage: usage, + Month: month.String(), + Year: year, + Options: initOptions(inputOptions), + } +} + +func initOptions(inputOptions []string) []option { + var options []option + + if inputOptions[0] == "" { + return options + } + + for _, name := range inputOptions { + option := option{ + Name: name, + Definename: utils.FirstUpper(name), + Functionname: "do" + utils.FirstUpper(name), + } + options = append(options, option) + } + + return options +} + +func main() { + var flOptions, flUsage, flTemplatePath string + + flag.StringVar(&flOptions, "options", "", "Comma-separated list of options to build") + flag.StringVar(&flUsage, "usage", "", "A one liner explains the purpose of the cli being built") + flag.StringVar(&flTemplatePath, "templatepath", "", "Non standard templates path") + + flag.Parse() + + inputOptions := strings.Split(flOptions, ",") + + appname := flag.Arg(0) + + if appname == "" { + log.Fatal("app name must not be blank\n") + } + + if inputOptions[0] == "" { + log.Fatal("-options option1 should be specified with appname") + } + + gopath := os.Getenv("GOPATH") + + var mainTemplatePath, optionsTemplatePath, readmeTemplatePath string + if flTemplatePath == "" { + mainTemplatePath = path.Join(gopath, TEMPLATEREPO, "main.tmpl") + optionsTemplatePath = path.Join(gopath, TEMPLATEREPO, "options.tmpl") + readmeTemplatePath = path.Join(gopath, TEMPLATEREPO, "README.tmpl") + } else { + mainTemplatePath = path.Join(flTemplatePath, "main.tmpl") + optionsTemplatePath = path.Join(flTemplatePath, "options.tmpl") + readmeTemplatePath = path.Join(flTemplatePath, "README.tmpl") + } + + if _, err := os.Stat(mainTemplatePath); err != nil { + log.Fatal(err) + } + if _, err := os.Stat(optionsTemplatePath); err != nil { + log.Fatal(err) + } + if _, err := os.Stat(readmeTemplatePath); err != nil { + log.Fatal(err) + } + + var mainTemplate = template.Must(template.ParseFiles(mainTemplatePath)) + var optionsTemplate = template.Must(template.ParseFiles(optionsTemplatePath)) + var readmeTemplate = template.Must(template.ParseFiles(readmeTemplatePath)) + + if _, err := os.Stat(appname); err == nil { + // if exists, we overwrite by default + err = os.RemoveAll(appname) + utils.Assert(err) + } + + err := os.Mkdir(appname, 0755) + utils.Assert(err) + + application := initApplication(appname, flUsage, inputOptions) + + optionsGo := source{ + Name: appname + "-options.go", + TempLate: *optionsTemplate, + } + + readmeMd := source{ + Name: appname + ".md", + TempLate: *readmeTemplate, + } + + mainGo := source{ + Name: appname + ".go", + TempLate: *mainTemplate, + } + + err = readmeMd.generate(appname, application) + utils.Assert(err) + + mainGo.generate(appname, application) + utils.Assert(err) + + optionsGo.generate(appname, application) + + err = GoFormat(appname) + utils.Assert(err) +} diff --git a/pkgs/utils/helpers.go b/pkgs/utils/helpers.go index c40b80a53..64534ebad 100644 --- a/pkgs/utils/helpers.go +++ b/pkgs/utils/helpers.go @@ -16,8 +16,22 @@ package utils -import "io/ioutil" +import ( + "io/ioutil" + "log" + "strings" +) func MakeTempTestDir() (string, error) { return ioutil.TempDir("/tmp", "minio-test-") } + +func Assert(err error) { + if err != nil { + log.Fatal(err) + } +} + +func FirstUpper(str string) string { + return strings.ToUpper(str[0:1]) + str[1:] +} diff --git a/templates/README.tmpl b/templates/README.tmpl new file mode 100644 index 000000000..5a7e122f2 --- /dev/null +++ b/templates/README.tmpl @@ -0,0 +1,13 @@ +% MINIO(1) Minio Manual +% Minio community +% {{.Month}} {{.Year}} +# NAME +{{.Name}} - {{.Usage}} + +# SYNOPSIS + +# DESCRIPTION + +# EXAMPLES + +# AUTHORS \ No newline at end of file diff --git a/templates/main.tmpl b/templates/main.tmpl new file mode 100644 index 000000000..b4dbcc4fa --- /dev/null +++ b/templates/main.tmpl @@ -0,0 +1,31 @@ +/* + * Mini Object Storage, (C) 2014 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "os" + + "github.com/codegangsta/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "{{.Name}}" + app.Usage = "{{.Usage}}" + app.Commands = Options + app.Run(os.Args) +} diff --git a/templates/options.tmpl b/templates/options.tmpl new file mode 100644 index 000000000..221b43017 --- /dev/null +++ b/templates/options.tmpl @@ -0,0 +1,41 @@ +/* + * Mini Object Storage, (C) 2014 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "github.com/codegangsta/cli" +) + +var Options = []cli.Command{ + {{range .Options}}{{.Definename}}, + {{end}} +} + +{{range .Options}} +var {{.Definename}} = cli.Command{ + Name: "{{.Name}}", + Usage: "", + Description: ` +`, + Action: {{.Functionname}}, +} +{{end}} + +{{range .Options}} +func {{.Functionname}}(c *cli.Context) { +} +{{end}}