From b9f79e346bd2427876a69c9c7f3555bfd260c56e Mon Sep 17 00:00:00 2001 From: Unknwon Date: Fri, 12 Sep 2014 01:23:20 -0400 Subject: [PATCH] Completely new version of Gopm --- .bra.toml | 13 + README.md | 8 +- cmd/bin.go | 129 ++--- cmd/build.go | 50 +- cmd/clean.go | 12 +- cmd/cmd.go | 355 ++---------- cmd/config.go | 77 ++- cmd/gen.go | 97 ++-- cmd/get.go | 178 +++--- cmd/install.go | 82 +-- cmd/list.go | 86 ++- cmd/run.go | 182 ++++-- cmd/test.go | 29 +- cmd/update.go | 64 +-- gopm.go | 13 +- gopm/gopm.go => lib/lib.go | 35 +- modules/{doc/vcs.go => base/base.go} | 31 +- modules/base/tool.go | 813 +++++++++++++++++++++++++++ modules/cae/cae.go | 108 ++++ modules/cae/zip/read.go | 67 +++ modules/cae/zip/stream.go | 77 +++ modules/cae/zip/write.go | 362 ++++++++++++ modules/cae/zip/zip.go | 234 ++++++++ modules/cli/app.go | 246 ++++++++ modules/cli/cli.go | 19 + modules/cli/command.go | 144 +++++ modules/cli/context.go | 315 +++++++++++ modules/cli/flag.go | 410 ++++++++++++++ modules/cli/help.go | 224 ++++++++ modules/doc/bitbucket.go | 135 ----- modules/doc/gitcafe.go | 134 ----- modules/doc/github.go | 141 ----- modules/doc/google.go | 251 --------- modules/doc/gopm.go | 93 --- modules/doc/http.go | 2 +- modules/doc/launchpad.go | 123 ---- modules/doc/oschina.go | 110 ---- modules/doc/struct.go | 131 ++++- modules/doc/utils.go | 421 +------------- modules/errors/errors.go | 2 +- modules/goconfig/conf.go | 531 +++++++++++++++++ modules/goconfig/read.go | 251 +++++++++ modules/goconfig/write.go | 108 ++++ modules/log/log.go | 147 +++-- modules/log/logP.go | 80 --- modules/log/log_windows.go | 48 -- modules/setting/setting.go | 209 +++---- 47 files changed, 4821 insertions(+), 2556 deletions(-) create mode 100644 .bra.toml rename gopm/gopm.go => lib/lib.go (71%) rename modules/{doc/vcs.go => base/base.go} (56%) create mode 100644 modules/base/tool.go create mode 100644 modules/cae/cae.go create mode 100644 modules/cae/zip/read.go create mode 100644 modules/cae/zip/stream.go create mode 100644 modules/cae/zip/write.go create mode 100644 modules/cae/zip/zip.go create mode 100644 modules/cli/app.go create mode 100644 modules/cli/cli.go create mode 100644 modules/cli/command.go create mode 100644 modules/cli/context.go create mode 100644 modules/cli/flag.go create mode 100644 modules/cli/help.go delete mode 100644 modules/doc/bitbucket.go delete mode 100644 modules/doc/gitcafe.go delete mode 100644 modules/doc/github.go delete mode 100644 modules/doc/google.go delete mode 100644 modules/doc/gopm.go delete mode 100644 modules/doc/launchpad.go delete mode 100644 modules/doc/oschina.go create mode 100644 modules/goconfig/conf.go create mode 100644 modules/goconfig/read.go create mode 100644 modules/goconfig/write.go delete mode 100644 modules/log/logP.go delete mode 100644 modules/log/log_windows.go diff --git a/.bra.toml b/.bra.toml new file mode 100644 index 0000000..bce9b58 --- /dev/null +++ b/.bra.toml @@ -0,0 +1,13 @@ +[run] +init_cmds = [["go", "install"]] +watch_all = true +watch_dirs = [ + "$WORKDIR/lib", + "$WORKDIR/cmd", + "$WORKDIR/modules", +] +watch_exts = [".go"] +build_delay = 1500 +cmds = [ + ["go", "install"] +] \ No newline at end of file diff --git a/README.md b/README.md index ec863ca..dff8530 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ USAGE: Gopm [global options] command [command options] [arguments...] VERSION: - 0.7.1.0613 Beta + 0.8.0.0912 Beta COMMANDS: - bin download and link dependencies and build binary + list list all dependencies of current project gen generate a gopmfile for current Go project get fetch remote package(s) and dependencies - list list all dependencies of current project + bin download and link dependencies and build binary config configurate gopm settings run link dependencies and go run test link dependencies and go test @@ -43,8 +43,8 @@ GLOBAL OPTIONS: --noterm, -n disable color output --strict, -s strict mode --debug, -d debug mode - --version, -v print the version --help, -h show help + --version, -v print the version ``` ## License diff --git a/cmd/bin.go b/cmd/bin.go index 504e701..af6e038 100644 --- a/cmd/bin.go +++ b/cmd/bin.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -22,9 +22,8 @@ import ( "runtime" "strings" - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/doc" "github.com/gpmgo/gopm/modules/errors" "github.com/gpmgo/gopm/modules/log" @@ -44,10 +43,10 @@ Can only specify one each time, and only works for projects that contain main package`, Action: runBin, Flags: []cli.Flag{ - cli.StringFlag{"dir, d", "./", "build binary to given directory"}, - cli.BoolFlag{"update, u", "update pakcage(s) and dependencies if any"}, - cli.BoolFlag{"remote, r", "build with pakcages in gopm local repository only"}, - cli.BoolFlag{"verbose, v", "show process details"}, + cli.StringFlag{"dir, d", "./", "build binary to given directory", ""}, + cli.BoolFlag{"update, u", "update pakcage(s) and dependencies if any", ""}, + cli.BoolFlag{"remote, r", "build with pakcages in gopm local repository only", ""}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } @@ -58,23 +57,14 @@ func runBin(ctx *cli.Context) { } if len(ctx.Args()) != 1 { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 1")) - return - } - log.Error("bin", "Incorrect number of arguments for command") - log.Error("", "\tshould have 1") - log.Help("Try 'gopm help bin' to get more information") + errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 1")) + return } // Check if given directory exists if specified. - if ctx.IsSet("dir") && !com.IsDir(ctx.String("dir")) { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Indicated path does not exist or not a directory")) - return - } - log.Error("bin", "Cannot start command:") - log.Fatal("", "\tIndicated path does not exist or not a directory") + if ctx.IsSet("dir") && !base.IsDir(ctx.String("dir")) { + errors.SetError(fmt.Errorf("Indicated path does not exist or not a directory")) + return } // Parse package version. @@ -93,7 +83,11 @@ func runBin(ctx *cli.Context) { // Check package name. if !strings.Contains(pkgPath, "/") { - tmpPath := setting.GetPkgFullPath(pkgPath) + tmpPath, err := setting.GetPkgFullPath(pkgPath) + if err != nil { + errors.SetError(err) + return + } if tmpPath != pkgPath { n = doc.NewNode(tmpPath, n.Type, n.Value, n.IsGetDeps) } @@ -106,32 +100,27 @@ func runBin(ctx *cli.Context) { // Check if previous steps were successful. if !n.IsExist() { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Download steps weren't successful")) - return - } - log.Error("bin", "Cannot continue command:") - log.Fatal("", "\tDownload steps weren't successful") - } - - buildPath := path.Join(setting.InstallRepoPath, n.ImportPath) - oldWorkDir := setting.WorkDir - // Change to repository path. - log.Log("Changing work directory to %s", buildPath) - if err := os.Chdir(buildPath); err != nil { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Fail to change work directory: %v", err)) - return - } - log.Error("bin", "Fail to change work directory:") - log.Fatal("", "\t"+err.Error()) + errors.SetError(fmt.Errorf("Download steps weren't successful")) + return } - setting.WorkDir = buildPath + tmpVendor := path.Join("vendor", path.Base(n.RootPath)) + os.RemoveAll(tmpVendor) + os.RemoveAll(setting.VENDOR) // TODO: should use .gopm/temp path. - os.RemoveAll(path.Join(buildPath, doc.VENDOR)) + if err := autoLink(n.InstallPath, tmpVendor); err != nil { + errors.SetError(fmt.Errorf("Fail to link slef: %v", err)) + return + } + os.Chdir(tmpVendor) + oldWorkDir := setting.WorkDir + setting.WorkDir = path.Join(setting.WorkDir, tmpVendor) if !setting.Debug { - defer os.RemoveAll(path.Join(buildPath, doc.VENDOR)) + defer func() { + os.Chdir(oldWorkDir) + os.RemoveAll("vendor") + os.RemoveAll(setting.VENDOR) + }() } if err := buildBinary(ctx); err != nil { @@ -139,28 +128,20 @@ func runBin(ctx *cli.Context) { return } - gf, target, _, err := genGopmfile(ctx) + gf, target, err := parseGopmfile(setting.GOPMFILE) if err != nil { errors.SetError(err) return } - if target == "." { - _, target = filepath.Split(setting.WorkDir) - } // Because build command moved binary to root path. binName := path.Base(target) if runtime.GOOS == "windows" { binName += ".exe" } - if !com.IsFile(binName) { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Previous steps weren't successful or the project does not contain main package")) - return - } - log.Error("bin", "Binary does not exist:") - log.Error("", "\t"+binName) - log.Fatal("", "\tPrevious steps weren't successful or the project does not contain main package") + if !base.IsFile(binName) { + errors.SetError(fmt.Errorf("Previous steps weren't successful or the project does not contain main package")) + return } // Move binary to given directory. @@ -171,44 +152,32 @@ func runBin(ctx *cli.Context) { movePath = path.Join(runtime.GOROOT(), "pkg/tool", runtime.GOOS+"_"+runtime.GOARCH) } - if com.IsExist(movePath + "/" + binName) { - if err := os.Remove(movePath + "/" + binName); err != nil { - log.Warn("Cannot remove binary in work directory:") - log.Warn("\t %s", err) + if base.IsExist(path.Join(movePath, binName)) { + if err := os.Remove(path.Join(movePath, binName)); err != nil { + log.Warn("Cannot remove binary in work directory: %v", err) } } if err := os.Rename(binName, movePath+"/"+binName); err != nil { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Fail to move binary: %v", err)) - return - } - log.Error("bin", "Fail to move binary:") - log.Fatal("", "\t"+err.Error()) + errors.SetError(fmt.Errorf("Fail to move binary: %v", err)) + return } os.Chmod(movePath+"/"+binName, os.ModePerm) includes := strings.Split(gf.MustValue("res", "include"), "|") if len(includes) > 0 { - log.Log("Copying resources to %s", movePath) + log.Info("Copying resources to %s", movePath) for _, include := range includes { - if com.IsDir(include) { + if base.IsDir(include) { os.RemoveAll(path.Join(movePath, include)) - if err := com.CopyDir(include, filepath.Join(movePath, include)); err != nil { - if setting.LibraryMode { - errors.AppendError(errors.NewErrCopyResource(include)) - } else { - log.Error("bin", "Fail to copy following resource:") - log.Error("", "\t"+include) - } + if err := base.CopyDir(include, filepath.Join(movePath, include)); err != nil { + errors.AppendError(errors.NewErrCopyResource(include)) } } } } - log.Log("Changing work directory back to %s", oldWorkDir) - os.Chdir(oldWorkDir) - - log.Success("SUCC", "bin", "Command executed successfully!") + log.Info("Command executed successfully!") fmt.Println("Binary has been built into: " + movePath) + os.Exit(0) // FIXME: I don't what the hack is going on when delete this line. } diff --git a/cmd/build.go b/cmd/build.go index 8bbe7ef..bfd12d4 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -19,10 +19,8 @@ import ( "os" "path" - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/doc" + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/errors" "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" @@ -37,42 +35,38 @@ and execute 'go build' gopm build `, Action: runBuild, Flags: []cli.Flag{ - cli.BoolFlag{"update, u", "update pakcage(s) and dependencies if any"}, - cli.BoolFlag{"remote, r", "build with pakcages in gopm local repository only"}, - cli.BoolFlag{"verbose, v", "show process details"}, + cli.BoolFlag{"update, u", "update pakcage(s) and dependencies if any", ""}, + cli.BoolFlag{"remote, r", "build with pakcages in gopm local repository only", ""}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } func buildBinary(ctx *cli.Context, args ...string) error { - target, newGopath, newCurPath, err := genNewGopath(ctx, false) + _, target, err := parseGopmfile(setting.GOPMFILE) if err != nil { return err } - log.Trace("Building...") + if err := linkVendors(ctx); err != nil { + return err + } + + log.Info("Building...") cmdArgs := []string{"go", "build"} - cmdArgs = append(cmdArgs, args...) - if err := execCmd(newGopath, newCurPath, cmdArgs...); err != nil { - if setting.LibraryMode { - return fmt.Errorf("Fail to build program: %v", err) - } - log.Error("build", "Fail to build program:") - log.Fatal("", "\t"+err.Error()) + cmdArgs = append(cmdArgs, ctx.Args()...) + if err := execCmd(setting.DefaultVendor, setting.WorkDir, cmdArgs...); err != nil { + return fmt.Errorf("fail to build program: %v", err) } if setting.IsWindowsXP { fName := path.Base(target) binName := fName + ".exe" os.Remove(binName) - exePath := path.Join(newCurPath, doc.VENDOR, "src", target, binName) - if com.IsFile(exePath) { - if err := os.Rename(exePath, path.Join(newCurPath, binName)); err != nil { - if setting.LibraryMode { - return fmt.Errorf("Fail to move binary: %v", err) - } - log.Error("build", "Fail to move binary:") - log.Fatal("", "\t"+err.Error()) + exePath := path.Join(setting.DefaultVendorSrc, target, binName) + if base.IsFile(exePath) { + if err := os.Rename(exePath, path.Join(setting.WorkDir, binName)); err != nil { + return fmt.Errorf("fail to move binary: %v", err) } } else { log.Warn("No binary generated") @@ -87,14 +81,14 @@ func runBuild(ctx *cli.Context) { return } - os.RemoveAll(doc.VENDOR) + os.RemoveAll(setting.DefaultVendor) if !setting.Debug { - defer os.RemoveAll(doc.VENDOR) + defer os.RemoveAll(setting.DefaultVendor) } if err := buildBinary(ctx, ctx.Args()...); err != nil { errors.SetError(err) return } - log.Success("SUCC", "build", "Command executed successfully!") + log.Info("Command executed successfully!") } diff --git a/cmd/clean.go b/cmd/clean.go index 41baa34..5cbbd19 100644 --- a/cmd/clean.go +++ b/cmd/clean.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -16,11 +16,8 @@ package cmd import ( "os" - "path" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/doc" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/errors" "github.com/gpmgo/gopm/modules/setting" ) @@ -33,7 +30,7 @@ var CmdClean = cli.Command{ gopm clean`, Action: runClean, Flags: []cli.Flag{ - cli.BoolFlag{"verbose, v", "show process details"}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } @@ -43,6 +40,5 @@ func runClean(ctx *cli.Context) { return } - os.RemoveAll(doc.VENDOR) - os.RemoveAll(path.Join(setting.HomeDir, ".gopm/temp")) + os.RemoveAll(setting.DefaultVendor) } diff --git a/cmd/cmd.go b/cmd/cmd.go index 645236c..f494306 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -16,19 +16,14 @@ package cmd import ( "fmt" - "go/build" "os" "os/exec" "path" - "path/filepath" "runtime" "strings" - "github.com/Unknwon/com" - "github.com/Unknwon/goconfig" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/doc" + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" ) @@ -36,78 +31,68 @@ import ( // setup initializes and checks common environment variables. func setup(ctx *cli.Context) (err error) { setting.Debug = ctx.GlobalBool("debug") - log.PureMode = ctx.GlobalBool("noterm") + log.NonColor = ctx.GlobalBool("noterm") log.Verbose = ctx.Bool("verbose") - setting.HomeDir, err = com.HomeDir() + log.Info("App Version: %s", ctx.App.Version) + + setting.HomeDir, err = base.HomeDir() if err != nil { - if setting.LibraryMode { - return fmt.Errorf("Fail to get home directory: %v", err) - } - log.Error("setup", "") - log.Fatal("", "\t"+err.Error()) + return fmt.Errorf("Fail to get home directory: %v", err) } setting.HomeDir = strings.Replace(setting.HomeDir, "\\", "/", -1) setting.InstallRepoPath = path.Join(setting.HomeDir, ".gopm/repos") if runtime.GOOS == "windows" { setting.IsWindows = true - setting.InstallRepoPath = path.Join(setting.InstallRepoPath, "src") } os.MkdirAll(setting.InstallRepoPath, os.ModePerm) - log.Log("Local repository path: %s", setting.InstallRepoPath) + log.Info("Local repository path: %s", setting.InstallRepoPath) if !setting.LibraryMode || len(setting.WorkDir) == 0 { setting.WorkDir, err = os.Getwd() if err != nil { - if setting.LibraryMode { - return fmt.Errorf("Fail to get work directory: %v", err) - } - log.Error("setup", "Fail to get work directory:") - log.Fatal("", "\t"+err.Error()) + return fmt.Errorf("Fail to get work directory: %v", err) } setting.WorkDir = strings.Replace(setting.WorkDir, "\\", "/", -1) } + setting.DefaultVendor = path.Join(setting.WorkDir, setting.VENDOR) + setting.DefaultVendorSrc = path.Join(setting.DefaultVendor, "src") if !ctx.Bool("remote") { if ctx.Bool("local") { - gf, _, _, err := genGopmfile(ctx) - if err != nil { - return err - } - setting.InstallGopath = gf.MustValue("project", "local_gopath") - if ctx.Command.Name != "gen" { - if com.IsDir(setting.InstallGopath) { - log.Log("Indicated local GOPATH: %s", setting.InstallGopath) - setting.InstallGopath += "/src" - } else { - if setting.LibraryMode { - return fmt.Errorf("Local GOPATH does not exist or is not a directory: %s", - setting.InstallGopath) - } - log.Error("", "Invalid local GOPATH path") - log.Error("", "Local GOPATH does not exist or is not a directory:") - log.Error("", "\t"+setting.InstallGopath) - log.Help("Try 'go help gopath' to get more information") - } - } + // gf, _, _, err := genGopmfile(ctx) + // if err != nil { + // return err + // } + // setting.InstallGopath = gf.MustValue("project", "local_gopath") + // if ctx.Command.Name != "gen" { + // if com.IsDir(setting.InstallGopath) { + // log.Log("Indicated local GOPATH: %s", setting.InstallGopath) + // setting.InstallGopath += "/src" + // } else { + // if setting.LibraryMode { + // return fmt.Errorf("Local GOPATH does not exist or is not a directory: %s", + // setting.InstallGopath) + // } + // log.Error("", "Invalid local GOPATH path") + // log.Error("", "Local GOPATH does not exist or is not a directory:") + // log.Error("", "\t"+setting.InstallGopath) + // log.Help("Try 'go help gopath' to get more information") + // } + // } } else { // Get GOPATH. - setting.InstallGopath = com.GetGOPATHs()[0] - if com.IsDir(setting.InstallGopath) { - log.Log("Indicated GOPATH: %s", setting.InstallGopath) + setting.InstallGopath = base.GetGOPATHs()[0] + if base.IsDir(setting.InstallGopath) { + log.Info("Indicated GOPATH: %s", setting.InstallGopath) setting.InstallGopath += "/src" + setting.HasGOPATHSetting = true } else { if ctx.Bool("gopath") { - if setting.LibraryMode { - return fmt.Errorf("Local GOPATH does not exist or is not a directory: %s", - setting.InstallGopath) - } - log.Error("", "Invalid GOPATH path") - log.Error("", "GOPATH does not exist or is not a directory:") - log.Error("", "\t"+setting.InstallGopath) - log.Help("Try 'go help gopath' to get more information") + return fmt.Errorf("Local GOPATH does not exist or is not a directory: %s", + setting.InstallGopath) } else { // It's OK that no GOPATH setting // when user does not specify to use. @@ -117,208 +102,20 @@ func setup(ctx *cli.Context) (err error) { } } - setting.GopmTempPath = path.Join(setting.HomeDir, ".gopm/temp") - - setting.LocalNodesFile = path.Join(setting.HomeDir, ".gopm/data/localnodes.list") - if err = setting.LoadLocalNodes(); err != nil { + setting.ConfigFile = path.Join(setting.HomeDir, ".gopm/data/gopm.ini") + if err = setting.LoadConfig(); err != nil { return err } - setting.PkgNamesFile = path.Join(setting.HomeDir, ".gopm/data/pkgname.list") + setting.PkgNameListFile = path.Join(setting.HomeDir, ".gopm/data/pkgname.list") if err = setting.LoadPkgNameList(); err != nil { return err } - setting.ConfigFile = path.Join(setting.HomeDir, ".gopm/data/gopm.ini") - if err = setting.LoadConfig(); err != nil { + setting.LocalNodesFile = path.Join(setting.HomeDir, ".gopm/data/localnodes.list") + if err = setting.LoadLocalNodes(); err != nil { return err } - - if com.IsDir(setting.GOPMFILE) { - if setting.LibraryMode { - return fmt.Errorf("gopmfile should be file but found directory") - } - log.Error("setup", "Invalid gopmfile:") - log.Fatal("", "\tit should be file but found directory") - } - - return doc.SetProxy(setting.HttpProxy) -} - -// loadGopmfile loads and returns given gopmfile. -func loadGopmfile(fileName string) (*goconfig.ConfigFile, error) { - gf, err := goconfig.LoadConfigFile(fileName) - if err != nil { - if setting.LibraryMode { - return nil, fmt.Errorf("Fail to load gopmfile: %v", err) - } - log.Error("", "Fail to load gopmfile:") - log.Fatal("", "\t"+err.Error()) - } - return gf, nil -} - -// saveGopmfile saves gopmfile to given path. -func saveGopmfile(gf *goconfig.ConfigFile, fileName string) error { - if err := goconfig.SaveConfigFile(gf, fileName); err != nil { - if setting.LibraryMode { - return fmt.Errorf("Fail to save gopmfile: %v", err) - } - log.Error("", "Fail to save gopmfile:") - log.Fatal("", "\t"+err.Error()) - } - return nil -} - -// validPkgInfo checks if the information of the package is valid. -func validPkgInfo(info string) (doc.RevisionType, string, error) { - infos := strings.Split(info, ":") - tp := doc.RevisionType(infos[0]) - val := infos[1] - - l := len(infos) - switch { - case l == 2: - switch tp { - case doc.BRANCH, doc.COMMIT, doc.TAG: - default: - if setting.LibraryMode { - return "", "", fmt.Errorf("Invalid node type: %v", tp) - } - log.Error("", "Invalid node type:") - log.Error("", fmt.Sprintf("\t%v", tp)) - log.Help("Try 'gopm help get' to get more information") - } - return tp, val, nil - } - - if setting.LibraryMode { - return "", "", fmt.Errorf("Cannot parse dependency version: %v", info) - } - log.Error("", "Cannot parse dependency version:") - log.Error("", "\t"+info) - log.Help("Try 'gopm help get' to get more information") - return "", "", nil -} - -// isSubpackage returns true if given package belongs to current project. -func isSubpackage(rootPath, target string) bool { - return strings.HasSuffix(setting.WorkDir, rootPath) || - strings.HasPrefix(rootPath, target) -} - -func getGopmPkgs( - gf *goconfig.ConfigFile, - target, dirPath string, - isTest bool) (pkgs map[string]*doc.Pkg, err error) { - - var deps map[string]string - if deps, err = gf.GetSection("deps"); err != nil { - deps = nil - } - - imports, err := doc.GetImports(target, doc.GetRootPath(target), dirPath, isTest) - if err != nil { - return nil, err - } - pkgs = make(map[string]*doc.Pkg) - for _, name := range imports { - if name == "C" { - continue - } - - if !doc.IsGoRepoPath(name) { - if deps != nil { - if info, ok := deps[name]; ok { - // Check version. there should chek - // local first because d:\ contains : - if com.IsDir(info) { - pkgs[name] = doc.NewPkg(name, doc.LOCAL, info) - continue - } else if i := strings.Index(info, ":"); i > -1 { - pkgs[name] = doc.NewPkg(name, doc.RevisionType(info[:i]), info[i+1:]) - continue - } - } - } - pkgs[name] = doc.NewDefaultPkg(name) - } - } - return pkgs, nil -} - -func getDepPkgs( - gf *goconfig.ConfigFile, - ctx *cli.Context, - target, curPath string, - depPkgs map[string]*doc.Pkg, - isTest bool) error { - - if setting.Debug { - log.Trace("Current Path: %s", curPath) - } - - pkgs, err := getGopmPkgs(gf, target, curPath, isTest) - if err != nil { - return fmt.Errorf("Fail to get gopmfile dependencies: %v", err) - } - - if setting.Debug { - for _, pkg := range pkgs { - if _, ok := depPkgs[pkg.RootPath]; !ok { - log.Trace("Found new dependency: %s", pkg.ImportPath) - } - } - } - - for name, pkg := range pkgs { - if _, ok := depPkgs[pkg.RootPath]; !ok { - var newPath string - if !build.IsLocalImport(name) && pkg.Type != doc.LOCAL { - pkgPath := pkg.RootPath + pkg.ValSuffix() - newPath = path.Join(setting.InstallRepoPath, pkgPath) - if len(pkg.ValSuffix()) == 0 && !ctx.Bool("remote") && - com.IsDir(path.Join(setting.InstallGopath, pkgPath)) { - newPath = path.Join(setting.InstallGopath, pkgPath) - } - if target != "" && strings.HasPrefix(pkg.ImportPath, target) { - newPath = path.Join(curPath, strings.TrimPrefix(pkg.ImportPath, target)) - } else { - if !com.IsExist(newPath) || ctx.Bool("update") { - node := doc.NewNode(pkg.ImportPath, pkg.Type, pkg.Value, true) - node.IsGetDeps = false - if err = downloadPackages(target, ctx, []*doc.Node{node}); err != nil { - return err - } - } - } - } else { - if pkg.Type == doc.LOCAL { - newPath, err = filepath.Abs(pkg.Value) - } else { - newPath, err = filepath.Abs(name) - } - if err != nil { - return err - } - } - depPkgs[pkg.RootPath] = pkg - - curPath = path.Join(doc.VENDOR, "src", pkg.RootPath) - log.Log("Linking %s", pkg.RootPath+pkg.ValSuffix()) - if err := autoLink(newPath, curPath); err != nil { - if setting.LibraryMode { - return fmt.Errorf("Fail to make link dependency: %v", err) - } - log.Error("", "Fail to make link dependency:") - log.Fatal("", "\t"+err.Error()) - } - - if err = getDepPkgs(gf, ctx, pkg.ImportPath, curPath, depPkgs, false); err != nil { - return err - } - } - } return nil } @@ -327,61 +124,9 @@ func autoLink(oldPath, newPath string) error { return makeLink(oldPath, newPath) } -func genNewGopath(ctx *cli.Context, isTest bool) (string, string, string, error) { - log.Trace("Work directory: %s", setting.WorkDir) - - gf, err := loadGopmfile(setting.GOPMFILE) - if err != nil { - return "", "", "", err - } - - // Check dependencies. - target := doc.ParseTarget(gf.MustValue("target", "path")) - if target == "." { - _, target = filepath.Split(setting.WorkDir) - } - - // Clean old files. - newGopath := path.Join(setting.WorkDir, doc.VENDOR) - newGopathSrc := path.Join(newGopath, "src") - os.RemoveAll(newGopathSrc) - os.MkdirAll(newGopathSrc, os.ModePerm) - - // Link self. - targetRoot := doc.GetRootPath(target) - log.Log("Linking %s", targetRoot) - if setting.Debug { - fmt.Println(target) - fmt.Println(path.Join(strings.TrimSuffix(setting.WorkDir, target), targetRoot)) - fmt.Println(path.Join(newGopathSrc, targetRoot)) - } - if err := autoLink(path.Join(strings.TrimSuffix(setting.WorkDir, target), targetRoot), - path.Join(newGopathSrc, targetRoot)); err != nil && - !strings.Contains(err.Error(), "file exists") { - if setting.LibraryMode { - return "", "", "", fmt.Errorf("Fail to make link self: %v", err) - } - log.Error("", "Fail to make link self:") - log.Fatal("", "\t"+err.Error()) - } - - // Check and loads dependency pakcages. - depPkgs := make(map[string]*doc.Pkg) - if err := getDepPkgs(gf, ctx, target, setting.WorkDir, depPkgs, isTest); err != nil { - if setting.LibraryMode { - return "", "", "", fmt.Errorf("Fail to get dependency pakcages: %v", err) - } - log.Error("", "Fail to get dependency pakcages:") - log.Fatal("", "\t"+err.Error()) - } - - newCurPath := path.Join(newGopathSrc, target) - return target, newGopath, newCurPath, nil -} - func execCmd(gopath, curPath string, args ...string) error { oldGopath := os.Getenv("GOPATH") - log.Log("Setting GOPATH to %s", gopath) + log.Info("Setting GOPATH to %s", gopath) sep := ":" if runtime.GOOS == "windows" { @@ -396,7 +141,7 @@ func execCmd(gopath, curPath string, args ...string) error { log.Fatal("", "\t"+err.Error()) } defer func() { - log.Log("Setting GOPATH back to %s", oldGopath) + log.Info("Setting GOPATH back to %s", oldGopath) os.Setenv("GOPATH", oldGopath) }() @@ -405,10 +150,16 @@ func execCmd(gopath, curPath string, args ...string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - log.Log("===== application outputs start =====\n") + log.Info("===== application outputs start =====\n") err := cmd.Run() - log.Log("====== application outputs end ======") + log.Info("====== application outputs end ======") return err } + +// isSubpackage returns true if given package belongs to current project. +func isSubpackage(rootPath, target string) bool { + return strings.HasSuffix(setting.WorkDir, rootPath) || + strings.HasPrefix(rootPath, target) +} diff --git a/cmd/config.go b/cmd/config.go index b5a108e..6ca336d 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -17,10 +17,8 @@ package cmd import ( "fmt" - "github.com/codegangsta/cli" - + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/errors" - "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" ) @@ -35,7 +33,7 @@ gopm config github [client_id] [client_secret] Action: runConfig, Subcommands: configCommands, Flags: []cli.Flag{ - cli.BoolFlag{"verbose, v", "show process details"}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } @@ -97,13 +95,8 @@ func runConfigGet(ctx *cli.Context) { } if len(ctx.Args()) != 1 { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 1")) - return - } - log.Error("config", "Incorrect number of arguments for command") - log.Error("", "\t'get' should have 1") - log.Help("Try 'gopm config get -h' to get more information") + errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 1")) + return } switch ctx.Args().First() { case "proxy": @@ -118,26 +111,29 @@ func runConfigGet(ctx *cli.Context) { func runConfigUnset(ctx *cli.Context) { if err := setup(ctx); err != nil { - setting.RuntimeError.HasError = true - setting.RuntimeError.Fatal = err + errors.SetError(err) return } if len(ctx.Args()) != 1 { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 1")) - return - } - log.Error("config", "Incorrect number of arguments for command") - log.Error("", "\t'unset' should have 1") - log.Help("Try 'gopm config unset -h' to get more information") + errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 1")) + return } + + var err error switch ctx.Args().First() { case "proxy": - setting.DeleteConfigOption("settings", "HTTP_PROXY") + err = setting.DeleteConfigOption("settings", "HTTP_PROXY") case "github": - setting.DeleteConfigOption("github", "CLIENT_ID") - setting.DeleteConfigOption("github", "CLIENT_SECRET") + if err = setting.DeleteConfigOption("github", "CLIENT_ID"); err != nil { + errors.SetError(err) + return + } + err = setting.DeleteConfigOption("github", "CLIENT_SECRET") + } + if err != nil { + errors.SetError(err) + return } } @@ -148,15 +144,13 @@ func runConfigSetProxy(ctx *cli.Context) { } if len(ctx.Args()) != 1 { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 1")) - return - } - log.Error("config", "Incorrect number of arguments for command") - log.Error("", "\t'set proxy' should have 1") - log.Help("Try 'gopm config set help proxy' to get more information") + errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 1")) + return + } + if err := setting.SetConfigValue("settings", "HTTP_PROXY", ctx.Args().First()); err != nil { + errors.SetError(err) + return } - setting.SetConfigValue("settings", "HTTP_PROXY", ctx.Args().First()) } func runConfigSetGitHub(ctx *cli.Context) { @@ -166,14 +160,15 @@ func runConfigSetGitHub(ctx *cli.Context) { } if len(ctx.Args()) != 2 { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 2")) - return - } - log.Error("config", "Incorrect number of arguments for command") - log.Error("", "\t'set github' should have 2") - log.Help("Try 'gopm config set help github' to get more information") + errors.SetError(fmt.Errorf("Incorrect number of arguments for command: should have 2")) + return + } + if err := setting.SetConfigValue("github", "CLIENT_ID", ctx.Args().First()); err != nil { + errors.SetError(err) + return + } + if err := setting.SetConfigValue("github", "CLIENT_SECRET", ctx.Args().Get(1)); err != nil { + errors.SetError(err) + return } - setting.SetConfigValue("github", "CLIENT_ID", ctx.Args().First()) - setting.SetConfigValue("github", "CLIENT_SECRET", ctx.Args().Get(1)) } diff --git a/cmd/gen.go b/cmd/gen.go index 38d3c7b..0b4acb2 100644 --- a/cmd/gen.go +++ b/cmd/gen.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -17,14 +17,10 @@ package cmd import ( "os" "path" - "sort" "strings" - "github.com/Unknwon/com" - "github.com/Unknwon/goconfig" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/doc" + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/errors" "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" @@ -40,8 +36,8 @@ gopm gen Make sure you run this command in the root path of a go project.`, Action: runGen, Flags: []cli.Flag{ - cli.BoolFlag{"local, l", "generate local GOPATH directories"}, - cli.BoolFlag{"verbose, v", "show process details"}, + cli.BoolFlag{"local, l", "generate local GOPATH directories", ""}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } @@ -51,60 +47,22 @@ func runGen(ctx *cli.Context) { return } - gf, _, _, err := genGopmfile(ctx) + gfPath := path.Join(setting.WorkDir, setting.GOPMFILE) + if !setting.HasGOPATHSetting && !base.IsFile(gfPath) { + log.Warn("Dependency list may contain package itself without GOPATH setting and gopmfile.") + } + gf, target, err := parseGopmfile(gfPath) if err != nil { errors.SetError(err) return } - if ctx.Bool("local") { - localGopath := gf.MustValue("project", "local_gopath") - if len(localGopath) == 0 { - localGopath = "./vendor" - gf.SetValue("project", "local_gopath", localGopath) - if err = saveGopmfile(gf, setting.GOPMFILE); err != nil { - errors.SetError(err) - return - } - } - - for _, name := range []string{"src", "pkg", "bin"} { - os.MkdirAll(path.Join(localGopath, name), os.ModePerm) - } - } - log.Success("SUCC", "gen", "Generate gopmfile successfully!") -} - -// genGopmfile generates gopmfile and returns it, -// along with target and dependencies. -func genGopmfile(ctx *cli.Context) (*goconfig.ConfigFile, string, []string, error) { - if !com.IsExist(setting.GOPMFILE) { - os.Create(setting.GOPMFILE) - } - - if com.IsSliceContainsStr([]string{"gen", "list"}, ctx.Command.Name) { - os.RemoveAll(doc.VENDOR) - if !setting.Debug { - defer os.RemoveAll(doc.VENDOR) - } - } - gf, err := loadGopmfile(setting.GOPMFILE) - if err != nil { - return nil, "", nil, err - } - target, _, _, err := genNewGopath(ctx, false) - if err != nil { - return nil, "", nil, err - } - rootPath := doc.GetRootPath(target) - - imports, err := doc.GetImports(target, rootPath, setting.WorkDir, false) + list, err := getDepList(ctx, target, setting.WorkDir, setting.DefaultVendor, "") if err != nil { - return nil, "", nil, err + errors.SetError(err) + return } - sort.Strings(imports) - for _, name := range imports { - name = doc.GetRootPath(name) + for _, name := range list { // Check if user has specified the version. if val := gf.MustValue("deps", name); len(val) == 0 { gf.SetValue("deps", name, "") @@ -112,15 +70,36 @@ func genGopmfile(ctx *cli.Context) (*goconfig.ConfigFile, string, []string, erro } // Check resources. - if _, err := gf.GetValue("res", "include"); err != nil { + if _, err = gf.GetValue("res", "include"); err != nil { resList := make([]string, 0, len(setting.CommonRes)) for _, res := range setting.CommonRes { - if com.IsExist(res) { + if base.IsExist(res) { resList = append(resList, res) } } gf.SetValue("res", "include", strings.Join(resList, "|")) } - return gf, target, imports, saveGopmfile(gf, setting.GOPMFILE) + if err = setting.SaveGopmfile(gf, gfPath); err != nil { + errors.SetError(err) + return + } + + if ctx.Bool("local") { + localGopath := gf.MustValue("project", "local_gopath") + if len(localGopath) == 0 { + localGopath = "./vendor" + gf.SetValue("project", "local_gopath", localGopath) + if err = setting.SaveGopmfile(gf, gfPath); err != nil { + errors.SetError(err) + return + } + } + + for _, name := range []string{"src", "pkg", "bin"} { + os.MkdirAll(path.Join(localGopath, name), os.ModePerm) + } + } + + log.Info("Generate gopmfile successfully!") } diff --git a/cmd/get.go b/cmd/get.go index df51bb5..ce508da 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -19,14 +19,12 @@ import ( "os" "path" "strings" - "sync" - - "github.com/Unknwon/com" - "github.com/Unknwon/goconfig" - "github.com/codegangsta/cli" + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/doc" "github.com/gpmgo/gopm/modules/errors" + "github.com/gpmgo/gopm/modules/goconfig" "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" ) @@ -42,106 +40,75 @@ gopm get gopm get @[:] gopm get @[:] -Can specify one or more: gopm get beego@tag:v0.9.0 github.com/beego/bee +Can specify one or more: gopm get cli@tag:v1.2.0 github.com/Unknwon/macaron If no version specified and package exists in GOPATH, it will be skipped, unless user enabled '--remote, -r' option then all the packages go into gopm local repository.`, Action: runGet, Flags: []cli.Flag{ - cli.BoolFlag{"download, d", "download given package only"}, - cli.BoolFlag{"update, u", "update pakcage(s) and dependencies if any"}, - cli.BoolFlag{"local, l", "download all packages to local GOPATH"}, - cli.BoolFlag{"gopath, g", "download all pakcages to GOPATH"}, - cli.BoolFlag{"remote, r", "download all pakcages to gopm local repository"}, - cli.BoolFlag{"verbose, v", "show process details"}, + cli.BoolFlag{"download, d", "download given package only", ""}, + cli.BoolFlag{"update, u", "update pakcage(s) and dependencies if any", ""}, + cli.BoolFlag{"local, l", "download all packages to local GOPATH", ""}, + cli.BoolFlag{"gopath, g", "download all pakcages to GOPATH", ""}, + cli.BoolFlag{"remote, r", "download all pakcages to gopm local repository", ""}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } -type safeMap struct { - locker *sync.RWMutex - data map[string]bool -} - -func (s *safeMap) Set(name string) { - s.locker.Lock() - defer s.locker.Unlock() - s.data[name] = true -} - -func (s *safeMap) Get(name string) bool { - s.locker.RLock() - defer s.locker.RUnlock() - return s.data[name] -} - -func NewSafeMap() *safeMap { - return &safeMap{ - locker: &sync.RWMutex{}, - data: make(map[string]bool), - } -} - var ( // Saves packages that have been downloaded. - downloadCache = NewSafeMap() - skipCache = NewSafeMap() - copyCache = NewSafeMap() + downloadCache = base.NewSafeMap() + skipCache = base.NewSafeMap() + copyCache = base.NewSafeMap() downloadCount int failConut int ) // downloadPackage downloads package either use version control tools or not. func downloadPackage(ctx *cli.Context, n *doc.Node) (*doc.Node, []string, error) { - log.Message("", "Downloading package: "+n.VerString()) + log.Info("Downloading package: %s", n.VerString()) downloadCache.Set(n.RootPath) - var imports []string - var err error + vendor := base.GetTempDir() + defer os.RemoveAll(vendor) + + var ( + err error + imports []string + srcPath string + ) + // Check if only need to use VCS tools. vcs := doc.GetVcsName(n.InstallGopath) - // If update, gopath and VCS tools set, - // then use VCS tools to update the package. + // If update, gopath and VCS tools set then use VCS tools to update the package. if ctx.Bool("update") && (ctx.Bool("gopath") || ctx.Bool("local")) && len(vcs) > 0 { - err = n.UpdateByVcs(vcs) - - var errFatal error - imports, errFatal = doc.GetImports(n.ImportPath, n.RootPath, n.InstallGopath, false) - if errFatal != nil { - return nil, nil, errFatal + if err = n.UpdateByVcs(vcs); err != nil { + return nil, nil, fmt.Errorf("fail to update by VCS(%s): %v", n.ImportPath, err) } + srcPath = n.InstallGopath } else { - // IsGetDepsOnly promises package is fixed version and exists in local repository. - if n.IsGetDepsOnly { - imports, err = doc.GetImports(n.ImportPath, n.RootPath, n.InstallPath, false) - if err != nil { - return nil, nil, err - } - } else { + if !n.IsGetDepsOnly || !n.IsExist() { // Get revision value from local records. - if n.IsExist() { - n.Revision = setting.LocalNodes.MustValue(n.RootPath, "value") + n.Revision = setting.LocalNodes.MustValue(n.RootPath, "value") + if err = n.DownloadGopm(ctx); err != nil { + errors.AppendError(errors.NewErrDownload(n.ImportPath + ": " + err.Error())) + failConut++ + os.RemoveAll(n.InstallPath) + return nil, nil, nil } - imports, err = n.Download(ctx) } + srcPath = n.InstallPath } - - if err != nil { - if setting.LibraryMode { - errors.AppendError(errors.NewErrDownload(n.ImportPath + "\n" + err.Error())) + fmt.Println(n.ValSuffix()) + if n.IsGetDeps { + imports, err = getDepList(ctx, n.ImportPath, srcPath, vendor, n.ValSuffix()) + if err != nil { + return nil, nil, fmt.Errorf("fail to list imports(%s): %v", n.ImportPath, err) + } + if setting.Debug { + log.Debug("New imports: %v", imports) } - log.Error("get", "Fail to download package: "+n.ImportPath) - log.Error("", "\t"+err.Error()) - failConut++ - os.RemoveAll(n.InstallPath) - return nil, nil, nil - } - - if !n.IsGetDeps { - imports = nil - } else { - fmt.Println(n.InstallPath, n.ValSuffix()) - // err=autoLink(n.InstallPath, newPath) } return n, imports, err } @@ -154,7 +121,7 @@ func downloadPackages(target string, ctx *cli.Context, nodes []*doc.Node) (err e // Check if it is a valid remote path or C. if n.ImportPath == "C" { continue - } else if !doc.IsValidRemotePath(n.ImportPath) { + } else if !base.IsValidRemotePath(n.ImportPath) { // Invalid import path. if setting.LibraryMode { errors.AppendError(errors.NewErrInvalidPackage(n.VerString())) @@ -177,7 +144,7 @@ func downloadPackages(target string, ctx *cli.Context, nodes []*doc.Node) (err e if downloadCache.Get(n.RootPath) { if !skipCache.Get(n.RootPath) { skipCache.Set(n.RootPath) - log.Trace("Skipped downloaded package: %s", n.VerString()) + log.Debug("Skipped downloaded package: %s", n.VerString()) } continue } @@ -187,8 +154,8 @@ func downloadPackages(target string, ctx *cli.Context, nodes []*doc.Node) (err e if n.IsExist() { if !skipCache.Get(n.RootPath) { skipCache.Set(n.RootPath) - log.Log("%s", n.InstallPath) - log.Trace("Skipped installed package: %s", n.VerString()) + log.Info("%s", n.InstallPath) + log.Debug("Skipped installed package: %s", n.VerString()) } // Only copy when no version control. @@ -213,12 +180,12 @@ func downloadPackages(target string, ctx *cli.Context, nodes []*doc.Node) (err e gfPath := path.Join(n.InstallPath, setting.GOPMFILE) // Check if has gopmfile. - if com.IsFile(gfPath) { - log.Log("Found gopmfile: %s", n.VerString()) + if base.IsFile(gfPath) { + log.Info("Found gopmfile: %s", n.VerString()) var err error - gf, err = loadGopmfile(gfPath) + gf, _, err = parseGopmfile(gfPath) if err != nil { - return err + return fmt.Errorf("fail to parse gopmfile(%s): %v", gfPath, err) } } @@ -251,7 +218,7 @@ func downloadPackages(target string, ctx *cli.Context, nodes []*doc.Node) (err e } // Save record in local nodes. - log.Success("SUCC", "GET", n.VerString()) + log.Info("Got %s", n.VerString()) downloadCount++ // Only save non-commit node. @@ -271,24 +238,29 @@ func downloadPackages(target string, ctx *cli.Context, nodes []*doc.Node) (err e return nil } -func getPackages(target string, ctx *cli.Context, nodes []*doc.Node) (err error) { - if err = downloadPackages(target, ctx, nodes); err != nil { +func getPackages(target string, ctx *cli.Context, nodes []*doc.Node) error { + if err := downloadPackages(target, ctx, nodes); err != nil { return err } - if err = setting.SaveLocalNodes(); err != nil { + if err := setting.SaveLocalNodes(); err != nil { return err } - log.Log("%d package(s) downloaded, %d failed", downloadCount, failConut) + log.Info("%d package(s) downloaded, %d failed", downloadCount, failConut) if ctx.GlobalBool("strict") && failConut > 0 && !setting.LibraryMode { - os.Exit(2) + return fmt.Errorf("fail to download some packages") } return nil } func getByGopmfile(ctx *cli.Context) error { // Make sure gopmfile exists and up-to-date. - gf, target, imports, err := genGopmfile(ctx) + gf, target, err := parseGopmfile(setting.GOPMFILE) + if err != nil { + return err + } + + imports, err := getDepList(ctx, target, setting.WorkDir, setting.DefaultVendor, "") if err != nil { return err } @@ -327,7 +299,10 @@ func getByPaths(ctx *cli.Context) error { // Check package name. if !strings.Contains(pkgPath, "/") { - tmpPath := setting.GetPkgFullPath(pkgPath) + tmpPath, err := setting.GetPkgFullPath(pkgPath) + if err != nil { + return err + } if tmpPath != pkgPath { n = doc.NewNode(tmpPath, n.Type, n.Value, n.IsGetDeps) } @@ -358,27 +333,16 @@ func runGet(ctx *cli.Context) { names = "'--gopath, -g' and '--remote, -r'" } if hasConflict { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Command options have conflicts: %s", names)) - return - } - log.Error("get", "Command options have conflicts") - log.Error("", "Following options are not supposed to use at same time:") - log.Error("", "\t"+names) - log.Help("Try 'gopm help get' to get more information") + errors.SetError(fmt.Errorf("Command options have conflicts: %s", names)) + return } var err error // Check number of arguments to decide which function to call. if len(ctx.Args()) == 0 { if ctx.Bool("download") { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Not enough arguments for option: '--download, -d'")) - return - } - log.Error("get", "Not enough arguments for option:") - log.Error("", "\t'--download, -d'") - log.Help("Try 'gopm help get' to get more information") + errors.SetError(fmt.Errorf("Not enough arguments for option: '--download, -d'")) + return } err = getByGopmfile(ctx) } else { diff --git a/cmd/install.go b/cmd/install.go index 77fed54..3f0df98 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -17,10 +17,9 @@ package cmd import ( "fmt" "os" + "path" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/doc" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/errors" "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" @@ -37,9 +36,9 @@ gopm install If no argument is supplied, then gopmfile must be present`, Action: runInstall, Flags: []cli.Flag{ - cli.BoolFlag{"package, p", "only install non-main packages"}, - cli.BoolFlag{"remote, r", "build with pakcages in gopm local repository only"}, - cli.BoolFlag{"verbose, v", "show process details"}, + // cli.BoolFlag{"package, p", "only install non-main packages", ""}, + cli.BoolFlag{"remote, r", "build with pakcages in gopm local repository only", ""}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } @@ -49,64 +48,35 @@ func runInstall(ctx *cli.Context) { return } - var err error - var target, srcPath string - switch len(ctx.Args()) { - case 0: - _, target, _, err = genGopmfile(ctx) - if err != nil { - errors.SetError(err) - return - } - srcPath = setting.WorkDir - default: - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Too many arguments: no argument needed")) - return - } - log.Error("install", "Too many arguments:") - log.Error("", "\tno argument needed") - log.Help("Try 'gopm help install' to get more information") + os.RemoveAll(setting.DefaultVendor) + if !setting.Debug { + defer os.RemoveAll(setting.DefaultVendor) } - os.RemoveAll(doc.VENDOR) + if err := linkVendors(ctx); err != nil { + errors.SetError(err) + return + } - _, newGopath, newCurPath, err := genNewGopath(ctx, false) + // Get target name. + gfPath := path.Join(setting.WorkDir, setting.GOPMFILE) + _, target, err := parseGopmfile(gfPath) if err != nil { - setting.RuntimeError.HasError = true - setting.RuntimeError.Fatal = err + errors.SetError(fmt.Errorf("fail to parse gopmfile: %v", err)) return } - log.Trace("Installing...") + log.Info("Installing...") - var installRepos []string - if ctx.Bool("package") { - installRepos, err = doc.GetImports(target, doc.GetRootPath(target), srcPath, false) - if err != nil { - errors.SetError(err) - return - } - } else { - installRepos = []string{target} + cmdArgs := []string{"go", "install"} + if ctx.Bool("verbose") { + cmdArgs = append(cmdArgs, "-v") } - - for _, repo := range installRepos { - cmdArgs := []string{"go", "install"} - - if ctx.Bool("verbose") { - cmdArgs = append(cmdArgs, "-v") - } - cmdArgs = append(cmdArgs, repo) - if err := execCmd(newGopath, newCurPath, cmdArgs...); err != nil { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Fail to install program: %v", err)) - return - } - log.Error("install", "Fail to install program:") - log.Fatal("", "\t"+err.Error()) - } + cmdArgs = append(cmdArgs, target) + if err := execCmd(setting.DefaultVendor, setting.WorkDir, cmdArgs...); err != nil { + errors.SetError(fmt.Errorf("fail to run program: %v", err)) + return } - log.Success("SUCC", "install", "Command executed successfully!") + log.Info("Command executed successfully!") } diff --git a/cmd/list.go b/cmd/list.go index fe80bd3..303aba6 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -16,14 +16,19 @@ package cmd import ( "fmt" + "os" + "path" + "path/filepath" "sort" + "strings" - "github.com/Unknwon/com" - "github.com/Unknwon/goconfig" - "github.com/codegangsta/cli" - + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/doc" "github.com/gpmgo/gopm/modules/errors" + "github.com/gpmgo/gopm/modules/goconfig" + "github.com/gpmgo/gopm/modules/log" + "github.com/gpmgo/gopm/modules/setting" ) var CmdList = cli.Command{ @@ -36,10 +41,23 @@ gopm list Make sure you run this command in the root path of a go project.`, Action: runList, Flags: []cli.Flag{ - cli.BoolFlag{"verbose, v", "show process details"}, + cli.BoolFlag{"test, t", "show test imports", ""}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } +func parseGopmfile(fileName string) (*goconfig.ConfigFile, string, error) { + gf, err := setting.LoadGopmfile(fileName) + if err != nil { + return nil, "", err + } + target := doc.ParseTarget(gf.MustValue("target", "path")) + if target == "." { + _, target = filepath.Split(setting.WorkDir) + } + return gf, target, nil +} + func verSuffix(gf *goconfig.ConfigFile, name string) string { val := gf.MustValue("deps", name) if len(val) > 0 { @@ -48,29 +66,69 @@ func verSuffix(gf *goconfig.ConfigFile, name string) string { return val } +// getDepList get list of dependencies in root path format and nature order. +func getDepList(ctx *cli.Context, target, pkgPath, vendor, suffix string) ([]string, error) { + vendorSrc := path.Join(vendor, "src") + rootPath := doc.GetRootPath(target) + // If work directory is not in GOPATH, then need to setup a vendor path. + if !setting.HasGOPATHSetting || !strings.HasPrefix(pkgPath, setting.InstallGopath) { + os.RemoveAll(vendorSrc) + if !setting.Debug { + defer os.RemoveAll(vendor) + } + + // Make link of self. + log.Debug("Linking %s...", rootPath) + from := path.Join(strings.TrimSuffix(pkgPath, target+suffix), rootPath) + suffix + to := path.Join(vendorSrc, rootPath) + if setting.Debug { + log.Debug("Linking from %s to %s", from, to) + } + if err := autoLink(from, to); err != nil { + return nil, err + } + } + + imports, err := doc.ListImports(target, rootPath, vendor, pkgPath, ctx.Bool("test")) + if err != nil { + return nil, err + } + + list := make([]string, 0, len(imports)) + for _, name := range imports { + name = doc.GetRootPath(name) + if !base.IsSliceContainsStr(list, name) { + list = append(list, name) + } + } + sort.Strings(list) + return list, nil +} + func runList(ctx *cli.Context) { if err := setup(ctx); err != nil { errors.SetError(err) return } - gf, _, imports, err := genGopmfile(ctx) + gfPath := path.Join(setting.WorkDir, setting.GOPMFILE) + if !setting.HasGOPATHSetting && !base.IsFile(gfPath) { + log.Warn("Dependency list may contain package itself without GOPATH setting and gopmfile.") + } + gf, target, err := parseGopmfile(gfPath) if err != nil { errors.SetError(err) return } - list := make([]string, 0, len(imports)) - for _, name := range imports { - if !com.IsSliceContainsStr(list, name) { - list = append(list, name) - } + list, err := getDepList(ctx, target, setting.WorkDir, setting.DefaultVendor, "") + if err != nil { + errors.SetError(err) + return } - sort.Strings(list) fmt.Printf("Dependency list(%d):\n", len(list)) for _, name := range list { - name = doc.GetRootPath(name) fmt.Printf("-> %s%s\n", name, verSuffix(gf, name)) } } diff --git a/cmd/run.go b/cmd/run.go index 76d4347..5693e3d 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -17,11 +17,11 @@ package cmd import ( "fmt" "os" + "path" "strings" - "github.com/Unknwon/goconfig" - "github.com/codegangsta/cli" - + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/doc" "github.com/gpmgo/gopm/modules/errors" "github.com/gpmgo/gopm/modules/log" @@ -40,81 +40,151 @@ and run the cmd in the .gopmfile, Windows hasn't supported yet, you need to run the command right at the local_gopath dir.`, Action: runRun, Flags: []cli.Flag{ - cli.BoolFlag{"local, l", "run command with local gopath context"}, - cli.BoolFlag{"verbose, v", "show process details"}, + cli.BoolFlag{"local, l", "run command with local gopath context", ""}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } -func runRun(ctx *cli.Context) { - if err := setup(ctx); err != nil { - errors.SetError(err) - return +func valSuffix(val string) string { + if len(val) > 0 { + return "." + val + } + return "" +} + +// validPkgInfo checks if the information of the package is valid. +func validPkgInfo(info string) (doc.RevisionType, string, error) { + if len(info) == 0 { + return doc.BRANCH, "", nil } - // TODO: So ugly, need to fix. - if ctx.Bool("local") { - var localGopath string - var err error - var wd string - var gf *goconfig.ConfigFile - wd, _ = os.Getwd() - for wd != "/" { - gf, _ = goconfig.LoadConfigFile(".gopmfile") - if gf != nil { - localGopath = gf.MustValue("project", "local_gopath") - } - if localGopath != "" { - break - } - os.Chdir("..") - wd, _ = os.Getwd() + infos := strings.Split(info, ":") + tp := doc.RevisionType(infos[0]) + val := infos[1] + + if len(infos) == 2 { + switch tp { + case doc.BRANCH, doc.COMMIT, doc.TAG: + default: + return "", "", fmt.Errorf("invalid node type: %v", tp) + } + return tp, val, nil + } + return "", "", fmt.Errorf("cannot parse dependency version: %v", info) +} + +func linkVendors(ctx *cli.Context) error { + gfPath := path.Join(setting.WorkDir, setting.GOPMFILE) + gf, target, err := parseGopmfile(gfPath) + if err != nil { + return fmt.Errorf("fail to parse gopmfile: %v", err) + } + rootPath := doc.GetRootPath(target) + + // TODO: local support. + + // Make link of self. + log.Debug("Linking %s...", rootPath) + if err := autoLink(path.Join(strings.TrimSuffix(setting.WorkDir, target), rootPath), + path.Join(setting.DefaultVendorSrc, rootPath)); err != nil { + return fmt.Errorf("fail to link self: %v", err) + } + + // Check and loads dependency pakcages. + log.Debug("Loading dependencies...") + imports, err := doc.ListImports(target, rootPath, setting.DefaultVendor, setting.WorkDir, ctx.Bool("test")) + if err != nil { + return fmt.Errorf("fail to list imports: %v", err) + } + + stack := make([]*doc.Pkg, len(imports)) + for i, name := range imports { + name := doc.GetRootPath(name) + tp, val, err := validPkgInfo(gf.MustValue("deps", name)) + if err != nil { + return fmt.Errorf("fail to validate package(%s): %v", name, err) + } + + stack[i] = doc.NewPkg(name, tp, val) + } + + lastIdx := len(stack) - 1 + for lastIdx >= 0 { + pkg := stack[lastIdx] + linkPath := path.Join(setting.DefaultVendorSrc, pkg.RootPath) + if base.IsExist(linkPath) { + stack = stack[:lastIdx] + lastIdx = len(stack) - 1 + continue } - if wd == "/" { - log.Fatal("run", "no gopmfile in the directory or parent directory") + + if len(pkg.Value) == 0 && setting.HasGOPATHSetting && + base.IsExist(path.Join(setting.InstallGopath, pkg.RootPath)) { + stack = stack[:lastIdx] + lastIdx = len(stack) - 1 + continue } - argss := gf.MustValue("run", "cmd") - if localGopath == "" { - log.Fatal("run", "No local GOPATH set") + + venderPath := path.Join(setting.InstallRepoPath, pkg.RootPath+pkg.ValSuffix()) + if !base.IsExist(venderPath) { + errors.SetError(fmt.Errorf("package not installed: %s", pkg.RootPath+pkg.VerSuffix())) + return nil } - args := strings.Split(argss, " ") - argsLen := len(args) - for i := 0; i < argsLen; i++ { - strings.Trim(args[i], " ") + + log.Debug("Linking %s...", pkg.RootPath+pkg.ValSuffix()) + if err := autoLink(venderPath, linkPath); err != nil { + return fmt.Errorf("fail to link dependency(%s): %v", pkg.RootPath, err) } - if len(args) < 2 { - log.Fatal("run", "cmd arguments less than 2") + stack = stack[:lastIdx] + + gf, target, err := parseGopmfile(path.Join(linkPath, setting.GOPMFILE)) + if err != nil { + return fmt.Errorf("fail to parse gopmfile(%s): %v", linkPath, err) } - if err = execCmd(localGopath, localGopath, args...); err != nil { - log.Error("run", "Fail to run program:") - log.Fatal("", "\t"+err.Error()) + rootPath := doc.GetRootPath(target) + + imports, err := doc.ListImports(target, rootPath, path.Join(linkPath, setting.VENDOR), linkPath, ctx.Bool("test")) + if err != nil { + errors.SetError(err) } + for _, name := range imports { + name := doc.GetRootPath(name) + tp, val, err := validPkgInfo(gf.MustValue("deps", name)) + if err != nil { + return fmt.Errorf("fail to validate package(%s): %v", name, err) + } + + stack = append(stack, doc.NewPkg(name, tp, val)) + } + lastIdx = len(stack) - 1 + } + return nil +} + +func runRun(ctx *cli.Context) { + if err := setup(ctx); err != nil { + errors.SetError(err) return } - os.RemoveAll(doc.VENDOR) + os.RemoveAll(setting.DefaultVendor) if !setting.Debug { - defer os.RemoveAll(doc.VENDOR) + defer os.RemoveAll(setting.DefaultVendor) } - // Run command with gopm repos context - // need version control , auto link to GOPATH/src repos - _, newGopath, newCurPath, err := genNewGopath(ctx, false) - if err != nil { + + if err := linkVendors(ctx); err != nil { errors.SetError(err) return } - log.Trace("Running...") + log.Info("Running...") cmdArgs := []string{"go", "run"} cmdArgs = append(cmdArgs, ctx.Args()...) - if err := execCmd(newGopath, newCurPath, cmdArgs...); err != nil { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Fail to run program: %v", err)) - return - } - log.Error("run", "Fail to run program:") - log.Fatal("", "\t"+err.Error()) + if err := execCmd(setting.DefaultVendor, setting.WorkDir, cmdArgs...); err != nil { + errors.SetError(fmt.Errorf("fail to run program: %v", err)) + return } - log.Success("SUCC", "run", "Command executed successfully!") + log.Info("Command executed successfully!") } diff --git a/cmd/test.go b/cmd/test.go index f0da512..35e68ea 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -18,9 +18,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/doc" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/errors" "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" @@ -35,7 +33,7 @@ and execute 'go test' gopm test `, Action: runTest, Flags: []cli.Flag{ - cli.BoolFlag{"verbose, v", "show process details"}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } @@ -45,29 +43,24 @@ func runTest(ctx *cli.Context) { return } - os.RemoveAll(doc.VENDOR) + os.RemoveAll(setting.DefaultVendor) if !setting.Debug { - defer os.RemoveAll(doc.VENDOR) + defer os.RemoveAll(setting.DefaultVendor) } - _, newGopath, newCurPath, err := genNewGopath(ctx, true) - if err != nil { + if err := linkVendors(ctx); err != nil { errors.SetError(err) return } - log.Trace("Testing...") + log.Info("Testing...") cmdArgs := []string{"go", "test"} cmdArgs = append(cmdArgs, ctx.Args()...) - if err := execCmd(newGopath, newCurPath, cmdArgs...); err != nil { - if setting.LibraryMode { - errors.SetError(fmt.Errorf("Fail to run program: %v", err)) - return - } - log.Error("test", "Fail to run program:") - log.Fatal("", "\t"+err.Error()) + if err := execCmd(setting.DefaultVendor, setting.WorkDir, cmdArgs...); err != nil { + errors.SetError(fmt.Errorf("fail to run program: %v", err)) + return } - log.Success("SUCC", "test", "Command executed successfully!") + log.Info("Command executed successfully!") } diff --git a/cmd/update.go b/cmd/update.go index e073958..b276be0 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -17,13 +17,13 @@ package cmd import ( "encoding/json" "fmt" + "io/ioutil" "os" "path" "runtime" - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/doc" "github.com/gpmgo/gopm/modules/errors" "github.com/gpmgo/gopm/modules/log" @@ -41,7 +41,7 @@ Resources will be updated automatically after executed this command, but you have to confirm before updaing gopm itself.`, Action: runUpdate, Flags: []cli.Flag{ - cli.BoolFlag{"verbose, v", "show process details"}, + cli.BoolFlag{"verbose, v", "show process details", ""}, }, } @@ -54,7 +54,7 @@ func loadLocalVerInfo() (ver version) { verPath := path.Join(setting.HomeDir, setting.VERINFO) // First time run should not exist. - if !com.IsExist(verPath) { + if !base.IsExist(verPath) { return ver } @@ -83,34 +83,32 @@ func runUpdate(ctx *cli.Context) { // Get remote version info. var remoteVerInfo version - if err := com.HttpGetJSON(doc.HttpClient, "http://gopm.io/VERSION.json", &remoteVerInfo); err != nil { - log.Error("Update", "Fail to fetch VERSION.json") - log.Fatal("", err.Error()) + if err := base.HttpGetJSON(doc.HttpClient, "http://gopm.io/VERSION.json", &remoteVerInfo); err != nil { + log.Fatal("Fail to fetch VERSION.json: %v", err) } // Package name list. if remoteVerInfo.PackageNameList > localVerInfo.PackageNameList { - log.Log("Updating pkgname.list...%v > %v", + log.Info("Updating pkgname.list...%v > %v", localVerInfo.PackageNameList, remoteVerInfo.PackageNameList) - data, err := com.HttpGetBytes(doc.HttpClient, "https://raw2.github.com/gpmgo/docs/master/pkgname.list", nil) + data, err := base.HttpGetBytes(doc.HttpClient, "https://raw2.github.com/gpmgo/docs/master/pkgname.list", nil) if err != nil { - log.Error("Update", "Fail to update pkgname.list") - log.Fatal("", err.Error()) + log.Fatal("Fail to update pkgname.list: %v", err) } - if err = com.WriteFile(path.Join(setting.HomeDir, setting.PkgNamesFile), data); err != nil { - log.Error("Update", "Fail to save pkgname.list") - log.Fatal("", err.Error()) + if err = ioutil.WriteFile(setting.PkgNameListFile, data, os.ModePerm); err != nil { + log.Fatal("Fail to save pkgname.list: %v", err) } - log.Log("Update pkgname.list to %v succeed!", remoteVerInfo.PackageNameList) + log.Info("Update pkgname.list to %v succeed!", remoteVerInfo.PackageNameList) isAnythingUpdated = true } // Gopm. if remoteVerInfo.Gopm != setting.VERSION { - log.Log("Updating gopm...%v > %v", setting.VERSION, remoteVerInfo.Gopm) + log.Info("Updating gopm...%v > %v", setting.VERSION, remoteVerInfo.Gopm) - tmpBinPath := path.Join(setting.GopmTempPath, "gopm") + tmpDir := base.GetTempDir() + tmpBinPath := path.Join(tmpDir, "gopm") if runtime.GOOS == "windows" { tmpBinPath += ".exe" } @@ -119,38 +117,36 @@ func runUpdate(ctx *cli.Context) { os.Remove(tmpBinPath) // Fetch code. - args := []string{"bin", "-u", "-r", "-d=" + setting.GopmTempPath} + args := []string{"bin", "-u", "-r", "-d=" + tmpDir} if ctx.Bool("verbose") { args = append(args, "-v") } args = append(args, "github.com/gpmgo/gopm") - stdout, stderr, err := com.ExecCmd("gopm", args...) + stdout, stderr, err := base.ExecCmd("gopm", args...) if err != nil { - log.Error("Update", "Fail to execute 'bin -u -r -d=/Users/jiahuachen/.gopm/temp -v github.com/gpmgo/gopm") - log.Fatal("", "\t"+stderr) + log.Fatal("Fail to execute 'bin -u -r -d=/Users/jiahuachen/.gopm/temp -v github.com/gpmgo/gopm: %s", stderr) } if len(stdout) > 0 { fmt.Print(stdout) } // Check if previous steps were successful. - if !com.IsExist(tmpBinPath) { + if !base.IsExist(tmpBinPath) { log.Error("Update", "Fail to continue command") log.Fatal("", "Previous steps weren't successful, no binary produced") } movePath := path.Join(setting.WorkDir, path.Base(os.Args[0])) - log.Log("New binary will be replaced for %s", movePath) + log.Info("New binary will be replaced for %s", movePath) // Move binary to given directory. if runtime.GOOS != "windows" { err := os.Rename(tmpBinPath, movePath) if err != nil { - log.Error("Update", "Fail to move binary") - log.Fatal("", err.Error()) + log.Fatal("Fail to move binary: %v", err) } os.Chmod(movePath+"/"+path.Base(tmpBinPath), os.ModePerm) } else { - batPath := path.Join(setting.GopmTempPath, "update.bat") + batPath := path.Join(tmpDir, "update.bat") f, err := os.Create(batPath) if err != nil { log.Error("Update", "Fail to generate bat file") @@ -173,7 +169,7 @@ func runUpdate(ctx *cli.Context) { } } - log.Success("SUCC", "Update", "Command execute successfully!") + log.Info("Command execute successfully!") isAnythingUpdated = true } @@ -181,15 +177,13 @@ func runUpdate(ctx *cli.Context) { // Save JSON. f, err := os.Create(setting.VERINFO) if err != nil { - log.Error("Update", "Fail to create VERSION.json") - log.Fatal("", err.Error()) + log.Fatal("Fail to create VERSION.json: %v", err) } if err := json.NewEncoder(f).Encode(&remoteVerInfo); err != nil { - log.Error("Update", "Fail to encode VERSION.json") - log.Fatal("", err.Error()) + log.Fatal("Fail to encode VERSION.json: %v", err) } } else { - log.Log("Nothing need to be updated") + log.Info("Nothing need to be updated") } - log.Log("Exit old gopm") + log.Info("Exit old gopm") } diff --git a/gopm.go b/gopm.go index 3cb1f0e..3c7d8dd 100644 --- a/gopm.go +++ b/gopm.go @@ -18,12 +18,19 @@ package main import ( "os" - "github.com/gpmgo/gopm/gopm" - + "github.com/gpmgo/gopm/lib" + "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" ) func main() { setting.LibraryMode = false - gopm.Run(os.Args) + if err := lib.Run(os.Args); err.HasError { + if err.Fatal != nil { + log.Fatal("%v", err.Fatal) + } + for _, e := range err.Errors { + log.Error("%v", e) + } + } } diff --git a/gopm/gopm.go b/lib/lib.go similarity index 71% rename from gopm/gopm.go rename to lib/lib.go index ec2a2d8..f89360e 100644 --- a/gopm/gopm.go +++ b/lib/lib.go @@ -14,21 +14,20 @@ // License for the specific language governing permissions and limitations // under the License. -// Package gopm is a library form of Gopm(Go Package Manager). -package gopm +// Package lib is a library version of Gopm(Go Package Manager). +package lib import ( "io" "runtime" - "github.com/codegangsta/cli" - "github.com/gpmgo/gopm/cmd" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" ) -const APP_VER = "0.7.2.0727 Beta" +const APP_VER = "0.8.0.0912 Beta" func init() { runtime.GOMAXPROCS(runtime.NumCPU()) @@ -41,23 +40,23 @@ func Run(args []string) *setting.Error { app.Usage = "Go Package Manager" app.Version = APP_VER app.Commands = []cli.Command{ - // cmd.CmdBin, - // cmd.CmdGen, + cmd.CmdList, + cmd.CmdGen, cmd.CmdGet, - // cmd.CmdList, - // cmd.CmdConfig, - // cmd.CmdRun, - // cmd.CmdTest, - // cmd.CmdBuild, - // cmd.CmdInstall, - // cmd.CmdClean, - // cmd.CmdUpdate, + cmd.CmdBin, + cmd.CmdConfig, + cmd.CmdRun, + cmd.CmdTest, + cmd.CmdBuild, + cmd.CmdInstall, + cmd.CmdClean, + cmd.CmdUpdate, // CmdSearch, } app.Flags = append(app.Flags, []cli.Flag{ - cli.BoolFlag{"noterm, n", "disable color output"}, - cli.BoolFlag{"strict, s", "strict mode"}, - cli.BoolFlag{"debug, d", "debug mode"}, + cli.BoolFlag{"noterm, n", "disable color output", ""}, + cli.BoolFlag{"strict, s", "strict mode", ""}, + cli.BoolFlag{"debug, d", "debug mode", ""}, }...) app.Run(args) return setting.RuntimeError diff --git a/modules/doc/vcs.go b/modules/base/base.go similarity index 56% rename from modules/doc/vcs.go rename to modules/base/base.go index 2af7b9e..0d3df89 100644 --- a/modules/doc/vcs.go +++ b/modules/base/base.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -12,17 +12,32 @@ // License for the specific language governing permissions and limitations // under the License. -package doc +package base import ( - "github.com/Unknwon/com" + "sync" ) -var defaultTags = map[string]string{"git": MASTER, "hg": DEFAULT, "svn": TRUNK} +type SafeMap struct { + locker *sync.RWMutex + data map[string]bool +} + +func (s *SafeMap) Set(name string) { + s.locker.Lock() + defer s.locker.Unlock() + s.data[name] = true +} + +func (s *SafeMap) Get(name string) bool { + s.locker.RLock() + defer s.locker.RUnlock() + return s.data[name] +} -func bestTag(tags map[string]string, defaultTag string) (string, string, error) { - if commit, ok := tags[defaultTag]; ok { - return defaultTag, commit, nil +func NewSafeMap() *SafeMap { + return &SafeMap{ + locker: &sync.RWMutex{}, + data: make(map[string]bool), } - return "", "", com.NotFoundError{"Tag or branch not found."} } diff --git a/modules/base/tool.go b/modules/base/tool.go new file mode 100644 index 0000000..8b85c33 --- /dev/null +++ b/modules/base/tool.go @@ -0,0 +1,813 @@ +// Copyright 2014 Unknwon +// +// 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 base + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path" + "regexp" + "runtime" + "strconv" + "strings" + "time" +) + +// IsFile returns true if given path is a file, +// or returns false when it's a directory or does not exist. +func IsFile(filePath string) bool { + f, e := os.Stat(filePath) + if e != nil { + return false + } + return !f.IsDir() +} + +// IsExist checks whether a file or directory exists. +// It returns false when the file or directory does not exist. +func IsExist(path string) bool { + _, err := os.Stat(path) + return err == nil || os.IsExist(err) +} + +// IsDir returns true if given path is a directory, +// or returns false when it's a file or does not exist. +func IsDir(dir string) bool { + f, e := os.Stat(dir) + if e != nil { + return false + } + return f.IsDir() +} + +func statDir(dirPath, recPath string, includeDir, isDirOnly bool) ([]string, error) { + dir, err := os.Open(dirPath) + if err != nil { + return nil, err + } + defer dir.Close() + + fis, err := dir.Readdir(0) + if err != nil { + return nil, err + } + + statList := make([]string, 0) + for _, fi := range fis { + if strings.Contains(fi.Name(), ".DS_Store") { + continue + } + + relPath := path.Join(recPath, fi.Name()) + curPath := path.Join(dirPath, fi.Name()) + if fi.IsDir() { + if includeDir { + statList = append(statList, relPath+"/") + } + s, err := statDir(curPath, relPath, includeDir, isDirOnly) + if err != nil { + return nil, err + } + statList = append(statList, s...) + } else if !isDirOnly { + statList = append(statList, relPath) + } + } + return statList, nil +} + +// StatDir gathers information of given directory by depth-first. +// It returns slice of file list and includes subdirectories if enabled; +// it returns error and nil slice when error occurs in underlying functions, +// or given path is not a directory or does not exist. +// +// Slice does not include given path itself. +// If subdirectories is enabled, they will have suffix '/'. +func StatDir(rootPath string, includeDir ...bool) ([]string, error) { + if !IsDir(rootPath) { + return nil, errors.New("not a directory or does not exist: " + rootPath) + } + + isIncludeDir := false + if len(includeDir) >= 1 { + isIncludeDir = includeDir[0] + } + return statDir(rootPath, "", isIncludeDir, false) +} + +// Copy copies file from source to target path. +func Copy(src, dest string) error { + // Gather file information to set back later. + si, err := os.Lstat(src) + if err != nil { + return err + } + + // Handle symbolic link. + if si.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(src) + if err != nil { + return err + } + // NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link, + // which will lead "no such file or directory" error. + return os.Symlink(target, dest) + } + + sr, err := os.Open(src) + if err != nil { + return err + } + defer sr.Close() + + dw, err := os.Create(dest) + if err != nil { + return err + } + defer dw.Close() + + if _, err = io.Copy(dw, sr); err != nil { + return err + } + + // Set back file information. + if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil { + return err + } + return os.Chmod(dest, si.Mode()) +} + +// CopyDir copy files recursively from source to target directory. +// +// The filter accepts a function that process the path info. +// and should return true for need to filter. +// +// It returns error when error occurs in underlying functions. +func CopyDir(srcPath, destPath string, filters ...func(filePath string) bool) error { + // Check if target directory exists. + if IsExist(destPath) { + return errors.New("file or directory alreay exists: " + destPath) + } + + err := os.MkdirAll(destPath, os.ModePerm) + if err != nil { + return err + } + + // Gather directory info. + infos, err := StatDir(srcPath, true) + if err != nil { + return err + } + + var filter func(filePath string) bool + if len(filters) > 0 { + filter = filters[0] + } + + for _, info := range infos { + if filter != nil && filter(info) { + continue + } + + curPath := path.Join(destPath, info) + if strings.HasSuffix(info, "/") { + err = os.MkdirAll(curPath, os.ModePerm) + } else { + err = Copy(path.Join(srcPath, info), curPath) + } + if err != nil { + return err + } + } + return nil +} + +// ExecCmdDirBytes executes system command in given directory +// and return stdout, stderr in bytes type, along with possible error. +func ExecCmdDirBytes(dir, cmdName string, args ...string) ([]byte, []byte, error) { + bufOut := new(bytes.Buffer) + bufErr := new(bytes.Buffer) + + cmd := exec.Command(cmdName, args...) + cmd.Dir = dir + cmd.Stdout = bufOut + cmd.Stderr = bufErr + + err := cmd.Run() + return bufOut.Bytes(), bufErr.Bytes(), err +} + +// ExecCmdBytes executes system command +// and return stdout, stderr in bytes type, along with possible error. +func ExecCmdBytes(cmdName string, args ...string) ([]byte, []byte, error) { + return ExecCmdDirBytes("", cmdName, args...) +} + +// ExecCmdDir executes system command in given directory +// and return stdout, stderr in string type, along with possible error. +func ExecCmdDir(dir, cmdName string, args ...string) (string, string, error) { + bufOut, bufErr, err := ExecCmdDirBytes(dir, cmdName, args...) + return string(bufOut), string(bufErr), err +} + +// ExecCmd executes system command +// and return stdout, stderr in string type, along with possible error. +func ExecCmd(cmdName string, args ...string) (string, string, error) { + return ExecCmdDir("", cmdName, args...) +} + +// Expand replaces {k} in template with match[k] or subs[atoi(k)] if k is not in match. +func Expand(template string, match map[string]string, subs ...string) string { + var p []byte + var i int + for { + i = strings.Index(template, "{") + if i < 0 { + break + } + p = append(p, template[:i]...) + template = template[i+1:] + i = strings.Index(template, "}") + if s, ok := match[template[:i]]; ok { + p = append(p, s...) + } else { + j, _ := strconv.Atoi(template[:i]) + if j >= len(subs) { + p = append(p, []byte("Missing")...) + } else { + p = append(p, subs[j]...) + } + } + template = template[i+1:] + } + p = append(p, template...) + return string(p) +} + +// GetGOPATHs returns all paths in GOPATH variable. +func GetGOPATHs() []string { + gopath := os.Getenv("GOPATH") + var paths []string + if runtime.GOOS == "windows" { + gopath = strings.Replace(gopath, "\\", "/", -1) + paths = strings.Split(gopath, ";") + } else { + paths = strings.Split(gopath, ":") + } + return paths +} + +// HomeDir returns path of '~'(in Linux) on Windows, +// it returns error when the variable does not exist. +func HomeDir() (home string, err error) { + if runtime.GOOS == "windows" { + home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + } else { + home = os.Getenv("HOME") + } + + if len(home) == 0 { + return "", errors.New("Cannot specify home directory because it's empty") + } + + return home, nil +} + +// IsSliceContainsStr returns true if the string exists in given slice. +func IsSliceContainsStr(sl []string, str string) bool { + str = strings.ToLower(str) + for _, s := range sl { + if strings.ToLower(s) == str { + return true + } + } + return false +} + +var validTLD = map[string]bool{ + // curl http://data.iana.org/TLD/tlds-alpha-by-domain.txt | sed -e '/#/ d' -e 's/.*/"&": true,/' | tr [:upper:] [:lower:] + ".ac": true, + ".ad": true, + ".ae": true, + ".aero": true, + ".af": true, + ".ag": true, + ".ai": true, + ".al": true, + ".am": true, + ".an": true, + ".ao": true, + ".aq": true, + ".ar": true, + ".arpa": true, + ".as": true, + ".asia": true, + ".at": true, + ".au": true, + ".aw": true, + ".ax": true, + ".az": true, + ".ba": true, + ".bb": true, + ".bd": true, + ".be": true, + ".bf": true, + ".bg": true, + ".bh": true, + ".bi": true, + ".biz": true, + ".bj": true, + ".bm": true, + ".bn": true, + ".bo": true, + ".br": true, + ".bs": true, + ".bt": true, + ".bv": true, + ".bw": true, + ".by": true, + ".bz": true, + ".ca": true, + ".cat": true, + ".cc": true, + ".cd": true, + ".cf": true, + ".cg": true, + ".ch": true, + ".ci": true, + ".ck": true, + ".cl": true, + ".cm": true, + ".cn": true, + ".co": true, + ".com": true, + ".coop": true, + ".cr": true, + ".cu": true, + ".cv": true, + ".cw": true, + ".cx": true, + ".cy": true, + ".cz": true, + ".de": true, + ".dj": true, + ".dk": true, + ".dm": true, + ".do": true, + ".dz": true, + ".ec": true, + ".edu": true, + ".ee": true, + ".eg": true, + ".er": true, + ".es": true, + ".et": true, + ".eu": true, + ".fi": true, + ".fj": true, + ".fk": true, + ".fm": true, + ".fo": true, + ".fr": true, + ".ga": true, + ".gb": true, + ".gd": true, + ".ge": true, + ".gf": true, + ".gg": true, + ".gh": true, + ".gi": true, + ".gl": true, + ".gm": true, + ".gn": true, + ".gov": true, + ".gp": true, + ".gq": true, + ".gr": true, + ".gs": true, + ".gt": true, + ".gu": true, + ".gw": true, + ".gy": true, + ".hk": true, + ".hm": true, + ".hn": true, + ".hr": true, + ".ht": true, + ".hu": true, + ".id": true, + ".ie": true, + ".il": true, + ".im": true, + ".in": true, + ".info": true, + ".int": true, + ".io": true, + ".iq": true, + ".ir": true, + ".is": true, + ".it": true, + ".je": true, + ".jm": true, + ".jo": true, + ".jobs": true, + ".jp": true, + ".ke": true, + ".kg": true, + ".kh": true, + ".ki": true, + ".km": true, + ".kn": true, + ".kp": true, + ".kr": true, + ".kw": true, + ".ky": true, + ".kz": true, + ".la": true, + ".lb": true, + ".lc": true, + ".li": true, + ".lk": true, + ".lr": true, + ".ls": true, + ".lt": true, + ".lu": true, + ".lv": true, + ".ly": true, + ".ma": true, + ".mc": true, + ".md": true, + ".me": true, + ".mg": true, + ".mh": true, + ".mil": true, + ".mk": true, + ".ml": true, + ".mm": true, + ".mn": true, + ".mo": true, + ".mobi": true, + ".mp": true, + ".mq": true, + ".mr": true, + ".ms": true, + ".mt": true, + ".mu": true, + ".museum": true, + ".mv": true, + ".mw": true, + ".mx": true, + ".my": true, + ".mz": true, + ".na": true, + ".name": true, + ".nc": true, + ".ne": true, + ".net": true, + ".nf": true, + ".ng": true, + ".ni": true, + ".nl": true, + ".no": true, + ".np": true, + ".nr": true, + ".nu": true, + ".nz": true, + ".om": true, + ".org": true, + ".pa": true, + ".pe": true, + ".pf": true, + ".pg": true, + ".ph": true, + ".pk": true, + ".pl": true, + ".pm": true, + ".pn": true, + ".post": true, + ".pr": true, + ".pro": true, + ".ps": true, + ".pt": true, + ".pw": true, + ".py": true, + ".qa": true, + ".re": true, + ".ro": true, + ".rs": true, + ".ru": true, + ".rw": true, + ".sa": true, + ".sb": true, + ".sc": true, + ".sd": true, + ".se": true, + ".sg": true, + ".sh": true, + ".si": true, + ".sj": true, + ".sk": true, + ".sl": true, + ".sm": true, + ".sn": true, + ".so": true, + ".sr": true, + ".st": true, + ".su": true, + ".sv": true, + ".sx": true, + ".sy": true, + ".sz": true, + ".tc": true, + ".td": true, + ".tel": true, + ".tf": true, + ".tg": true, + ".th": true, + ".tj": true, + ".tk": true, + ".tl": true, + ".tm": true, + ".tn": true, + ".to": true, + ".tp": true, + ".tr": true, + ".travel": true, + ".tt": true, + ".tv": true, + ".tw": true, + ".tz": true, + ".ua": true, + ".ug": true, + ".uk": true, + ".us": true, + ".uy": true, + ".uz": true, + ".va": true, + ".vc": true, + ".ve": true, + ".vg": true, + ".vi": true, + ".vn": true, + ".vu": true, + ".wf": true, + ".ws": true, + ".xn--0zwm56d": true, + ".xn--11b5bs3a9aj6g": true, + ".xn--3e0b707e": true, + ".xn--45brj9c": true, + ".xn--80akhbyknj4f": true, + ".xn--80ao21a": true, + ".xn--90a3ac": true, + ".xn--9t4b11yi5a": true, + ".xn--clchc0ea0b2g2a9gcd": true, + ".xn--deba0ad": true, + ".xn--fiqs8s": true, + ".xn--fiqz9s": true, + ".xn--fpcrj9c3d": true, + ".xn--fzc2c9e2c": true, + ".xn--g6w251d": true, + ".xn--gecrj9c": true, + ".xn--h2brj9c": true, + ".xn--hgbk6aj7f53bba": true, + ".xn--hlcj6aya9esc7a": true, + ".xn--j6w193g": true, + ".xn--jxalpdlp": true, + ".xn--kgbechtv": true, + ".xn--kprw13d": true, + ".xn--kpry57d": true, + ".xn--lgbbat1ad8j": true, + ".xn--mgb9awbf": true, + ".xn--mgbaam7a8h": true, + ".xn--mgbayh7gpa": true, + ".xn--mgbbh1a71e": true, + ".xn--mgbc0a9azcg": true, + ".xn--mgberp4a5d4ar": true, + ".xn--mgbx4cd0ab": true, + ".xn--o3cw4h": true, + ".xn--ogbpf8fl": true, + ".xn--p1ai": true, + ".xn--pgbs0dh": true, + ".xn--s9brj9c": true, + ".xn--wgbh1c": true, + ".xn--wgbl6a": true, + ".xn--xkc2al3hye2a": true, + ".xn--xkc2dl3a5ee0h": true, + ".xn--yfro4i67o": true, + ".xn--ygbi2ammx": true, + ".xn--zckzah": true, + ".xxx": true, + ".ye": true, + ".yt": true, + ".za": true, + ".zm": true, + ".zw": true, +} + +var validHost = regexp.MustCompile(`^[-a-z0-9]+(?:\.[-a-z0-9]+)+$`) +var validPathElement = regexp.MustCompile(`^[-A-Za-z0-9~+][-A-Za-z0-9_.]*$`) + +func isValidPathElement(s string) bool { + return validPathElement.MatchString(s) && s != "testdata" +} + +// IsValidRemotePath returns true if importPath is structurally valid for "go get". +func IsValidRemotePath(importPath string) bool { + parts := strings.Split(importPath, "/") + if len(parts) <= 1 { + // Import path must contain at least one "/". + return false + } + + if !validTLD[path.Ext(parts[0])] { + return false + } + + if !validHost.MatchString(parts[0]) { + return false + } + + for _, part := range parts[1:] { + if !isValidPathElement(part) { + return false + } + } + return true +} + +// Convert string to specify type. +type StrTo string + +func (f StrTo) Exist() bool { + return string(f) != string(0x1E) +} + +func (f StrTo) Uint8() (uint8, error) { + v, err := strconv.ParseUint(f.String(), 10, 8) + return uint8(v), err +} + +func (f StrTo) Int() (int, error) { + v, err := strconv.ParseInt(f.String(), 10, 32) + return int(v), err +} + +func (f StrTo) Int64() (int64, error) { + v, err := strconv.ParseInt(f.String(), 10, 64) + return int64(v), err +} + +func (f StrTo) MustUint8() uint8 { + v, _ := f.Uint8() + return v +} + +func (f StrTo) MustInt() int { + v, _ := f.Int() + return v +} + +func (f StrTo) MustInt64() int64 { + v, _ := f.Int64() + return v +} + +func (f StrTo) String() string { + if f.Exist() { + return string(f) + } + return "" +} + +// Convert any type to string. +func ToStr(value interface{}, args ...int) (s string) { + switch v := value.(type) { + case bool: + s = strconv.FormatBool(v) + case float32: + s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32)) + case float64: + s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64)) + case int: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int8: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int16: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int32: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int64: + s = strconv.FormatInt(v, argInt(args).Get(0, 10)) + case uint: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint8: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint16: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint32: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint64: + s = strconv.FormatUint(v, argInt(args).Get(0, 10)) + case string: + s = v + case []byte: + s = string(v) + default: + s = fmt.Sprintf("%v", v) + } + return s +} + +type argInt []int + +func (a argInt) Get(i int, args ...int) (r int) { + if i >= 0 && i < len(a) { + r = a[i] + } else if len(args) > 0 { + r = args[0] + } + return +} + +//GetTempDir generates and returns time-based unique temporary path. +func GetTempDir() string { + return path.Join(os.TempDir(), ToStr(time.Now().Nanosecond())) +} + +var UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1541.0 Safari/537.36" + +// HttpGet gets the specified resource. ErrNotFound is returned if the +// server responds with status 404. +func HttpGet(client *http.Client, url string, header http.Header) (io.ReadCloser, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", UserAgent) + for k, vs := range header { + req.Header[k] = vs + } + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("fail to make request: %v", err) + } + if resp.StatusCode == 200 { + return resp.Body, nil + } + resp.Body.Close() + if resp.StatusCode == 404 { // 403 can be rate limit error. || resp.StatusCode == 403 { + err = fmt.Errorf("resource not found: %s", url) + } else { + err = fmt.Errorf("get %s -> %d", url, resp.StatusCode) + } + return nil, err +} + +// HttpGetBytes gets the specified resource. ErrNotFound is returned if the server +// responds with status 404. +func HttpGetBytes(client *http.Client, url string, header http.Header) ([]byte, error) { + rc, err := HttpGet(client, url, header) + if err != nil { + return nil, err + } + defer rc.Close() + return ioutil.ReadAll(rc) +} + +// HttpGetJSON gets the specified resource and mapping to struct. +// ErrNotFound is returned if the server responds with status 404. +func HttpGetJSON(client *http.Client, url string, v interface{}) error { + rc, err := HttpGet(client, url, nil) + if err != nil { + return err + } + defer rc.Close() + err = json.NewDecoder(rc).Decode(v) + if _, ok := err.(*json.SyntaxError); ok { + return fmt.Errorf("JSON syntax error at %s", url) + } + return nil +} diff --git a/modules/cae/cae.go b/modules/cae/cae.go new file mode 100644 index 0000000..60e295a --- /dev/null +++ b/modules/cae/cae.go @@ -0,0 +1,108 @@ +// Copyright 2013 Unknown +// +// 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 cae implements PHP-like Compression and Archive Extensions. +package cae + +import ( + "io" + "os" + "strings" +) + +// A Streamer describes an streamable archive object. +type Streamer interface { + StreamFile(string, os.FileInfo, []byte) error + StreamReader(string, os.FileInfo, io.Reader) error + Close() error +} + +// A HookFunc represents a middleware for packing and extracting archive. +type HookFunc func(string, os.FileInfo) error + +// HasPrefix returns true if name has any string in given slice as prefix. +func HasPrefix(name string, prefixes []string) bool { + for _, prefix := range prefixes { + if strings.HasPrefix(name, prefix) { + return true + } + } + return false +} + +// IsEntry returns true if name equals to any string in given slice. +func IsEntry(name string, entries []string) bool { + for _, e := range entries { + if e == name { + return true + } + } + return false +} + +// IsFilter returns true if given name matches any of global filter rule. +func IsFilter(name string) bool { + if strings.Contains(name, ".DS_Store") { + return true + } + return false +} + +// IsExist returns true if given path is a file or directory. +func IsExist(path string) bool { + _, err := os.Stat(path) + return err == nil || os.IsExist(err) +} + +// Copy copies file from source to target path. +func Copy(dest, src string) error { + // Gather file information to set back later. + si, err := os.Lstat(src) + if err != nil { + return err + } + + // Handle symbolic link. + if si.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(src) + if err != nil { + return err + } + // NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link, + // which will lead "no such file or directory" error. + return os.Symlink(target, dest) + } + + sr, err := os.Open(src) + if err != nil { + return err + } + defer sr.Close() + + dw, err := os.Create(dest) + if err != nil { + return err + } + defer dw.Close() + + if _, err = io.Copy(dw, sr); err != nil { + return err + } + + // Set back file information. + if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil { + return err + } + return os.Chmod(dest, si.Mode()) +} diff --git a/modules/cae/zip/read.go b/modules/cae/zip/read.go new file mode 100644 index 0000000..c4825ef --- /dev/null +++ b/modules/cae/zip/read.go @@ -0,0 +1,67 @@ +// Copyright 2013 Unknown +// +// 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 zip + +import ( + "archive/zip" + "os" + "strings" +) + +// OpenFile is the generalized open call; most users will use Open +// instead. It opens the named zip file with specified flag +// (O_RDONLY etc.) if applicable. If successful, +// methods on the returned ZipArchive can be used for I/O. +// If there is an error, it will be of type *PathError. +func (z *ZipArchive) Open(name string, flag int, perm os.FileMode) error { + // Create a new archive if it's specified and not exist. + if flag&os.O_CREATE != 0 { + f, err := os.Create(name) + if err != nil { + return err + } + zw := zip.NewWriter(f) + if err = zw.Close(); err != nil { + return err + } + } + + rc, err := zip.OpenReader(name) + if err != nil { + return err + } + + z.ReadCloser = rc + z.FileName = name + z.Comment = rc.Comment + z.NumFiles = len(rc.File) + z.Flag = flag + z.Permission = perm + z.isHasChanged = false + + z.files = make([]*File, z.NumFiles) + for i, f := range rc.File { + z.files[i] = &File{} + z.files[i].FileHeader, err = zip.FileInfoHeader(f.FileInfo()) + if err != nil { + return err + } + z.files[i].Name = strings.Replace(f.Name, "\\", "/", -1) + if f.FileInfo().IsDir() && !strings.HasSuffix(z.files[i].Name, "/") { + z.files[i].Name += "/" + } + } + return nil +} diff --git a/modules/cae/zip/stream.go b/modules/cae/zip/stream.go new file mode 100644 index 0000000..18d8cc2 --- /dev/null +++ b/modules/cae/zip/stream.go @@ -0,0 +1,77 @@ +// Copyright 2014 Unknown +// +// 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 zip + +import ( + "archive/zip" + "io" + "os" + "path/filepath" +) + +// A StreamArchive represents a streamable archive. +type StreamArchive struct { + *zip.Writer +} + +// NewStreamArachive returns a new streamable archive with given io.Writer. +// It's caller's responsibility to close io.Writer and streamer after operation. +func NewStreamArachive(w io.Writer) *StreamArchive { + return &StreamArchive{zip.NewWriter(w)} +} + +// StreamFile streams a file or directory entry into StreamArchive. +func (s *StreamArchive) StreamFile(relPath string, fi os.FileInfo, data []byte) error { + if fi.IsDir() { + fh, err := zip.FileInfoHeader(fi) + if err != nil { + return err + } + fh.Name = relPath + "/" + if _, err = s.Writer.CreateHeader(fh); err != nil { + return err + } + } else { + fh, err := zip.FileInfoHeader(fi) + if err != nil { + return err + } + fh.Name = filepath.Join(relPath, fi.Name()) + fh.Method = zip.Deflate + fw, err := s.Writer.CreateHeader(fh) + if err != nil { + return err + } else if _, err = fw.Write(data); err != nil { + return err + } + } + return nil +} + +// StreamReader streams data from io.Reader to StreamArchive. +func (s *StreamArchive) StreamReader(relPath string, fi os.FileInfo, r io.Reader) (err error) { + fh, err := zip.FileInfoHeader(fi) + if err != nil { + return err + } + fh.Name = filepath.Join(relPath, fi.Name()) + + fw, err := s.Writer.CreateHeader(fh) + if err != nil { + return err + } + _, err = io.Copy(fw, r) + return err +} diff --git a/modules/cae/zip/write.go b/modules/cae/zip/write.go new file mode 100644 index 0000000..aa3abe6 --- /dev/null +++ b/modules/cae/zip/write.go @@ -0,0 +1,362 @@ +// Copyright 2013 Unknwon +// +// 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 zip + +import ( + "archive/zip" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + + "github.com/gpmgo/gopm/modules/cae" +) + +// Switcher of printing trace information when pack and extract. +var Verbose = true + +// extractFile extracts zip.File to file system. +func extractFile(f *zip.File, destPath string) error { + filePath := path.Join(destPath, f.Name) + os.MkdirAll(path.Dir(filePath), os.ModePerm) + + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() + + fw, err := os.Create(filePath) + if err != nil { + return err + } + defer fw.Close() + + if _, err = io.Copy(fw, rc); err != nil { + return err + } + + // Skip symbolic links. + if f.FileInfo().Mode()&os.ModeSymlink != 0 { + return nil + } + // Set back file information. + if err = os.Chtimes(filePath, f.ModTime(), f.ModTime()); err != nil { + return err + } + return os.Chmod(filePath, f.FileInfo().Mode()) +} + +var defaultExtractFunc = func(fullName string, fi os.FileInfo) error { + if !Verbose { + return nil + } + + fmt.Println("Extracting file..." + fullName) + return nil +} + +// ExtractToFunc extracts the whole archive or the given files to the +// specified destination. +// It accepts a function as a middleware for custom operations. +func (z *ZipArchive) ExtractToFunc(destPath string, fn cae.HookFunc, entries ...string) (err error) { + destPath = strings.Replace(destPath, "\\", "/", -1) + isHasEntry := len(entries) > 0 + if Verbose { + fmt.Println("Unzipping " + z.FileName + "...") + } + os.MkdirAll(destPath, os.ModePerm) + for _, f := range z.File { + f.Name = strings.Replace(f.Name, "\\", "/", -1) + + // Directory. + if strings.HasSuffix(f.Name, "/") { + if isHasEntry { + if cae.IsEntry(f.Name, entries) { + if err = fn(f.Name, f.FileInfo()); err != nil { + continue + } + os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm) + } + continue + } + if err = fn(f.Name, f.FileInfo()); err != nil { + continue + } + os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm) + continue + } + + // File. + if isHasEntry { + if cae.IsEntry(f.Name, entries) { + if err = fn(f.Name, f.FileInfo()); err != nil { + continue + } + err = extractFile(f, destPath) + } + } else { + if err = fn(f.Name, f.FileInfo()); err != nil { + continue + } + err = extractFile(f, destPath) + } + if err != nil { + return err + } + } + return nil +} + +// ExtractToFunc extracts the whole archive or the given files to the +// specified destination. +// It accepts a function as a middleware for custom operations. +func ExtractToFunc(srcPath, destPath string, fn cae.HookFunc, entries ...string) (err error) { + z, err := Open(srcPath) + if err != nil { + return err + } + defer z.Close() + return z.ExtractToFunc(destPath, fn, entries...) +} + +// ExtractTo extracts the whole archive or the given files to the +// specified destination. +// Call Flush() to apply changes before this. +func (z *ZipArchive) ExtractTo(destPath string, entries ...string) (err error) { + return z.ExtractToFunc(destPath, defaultExtractFunc, entries...) +} + +// ExtractTo extracts given archive or the given files to the +// specified destination. +func ExtractTo(srcPath, destPath string, entries ...string) (err error) { + return ExtractToFunc(srcPath, destPath, defaultExtractFunc, entries...) +} + +// extractFile extracts file from ZipArchive to file system. +func (z *ZipArchive) extractFile(f *File) error { + if !z.isHasWriter { + for _, zf := range z.ReadCloser.File { + if f.Name == zf.Name { + return extractFile(zf, path.Dir(f.tmpPath)) + } + } + } + return cae.Copy(f.tmpPath, f.absPath) +} + +// Flush saves changes to original zip file if any. +func (z *ZipArchive) Flush() error { + if !z.isHasChanged || (z.ReadCloser == nil && !z.isHasWriter) { + return nil + } + + // Extract to tmp path and pack back. + tmpPath := path.Join(os.TempDir(), "cae", path.Base(z.FileName)) + os.RemoveAll(tmpPath) + defer os.RemoveAll(tmpPath) + + for _, f := range z.files { + if strings.HasSuffix(f.Name, "/") { + os.MkdirAll(path.Join(tmpPath, f.Name), os.ModePerm) + continue + } + + // Relative path inside zip temporary changed. + f.tmpPath = path.Join(tmpPath, f.Name) + if err := z.extractFile(f); err != nil { + return err + } + } + + if z.isHasWriter { + return packToWriter(tmpPath, z.writer, defaultPackFunc, true) + } + + if err := PackTo(tmpPath, z.FileName); err != nil { + return err + } + return z.Open(z.FileName, os.O_RDWR|os.O_TRUNC, z.Permission) +} + +// packFile packs a file or directory to zip.Writer. +func packFile(srcFile string, recPath string, zw *zip.Writer, fi os.FileInfo) error { + if fi.IsDir() { + fh, err := zip.FileInfoHeader(fi) + if err != nil { + return err + } + fh.Name = recPath + "/" + if _, err = zw.CreateHeader(fh); err != nil { + return err + } + } else { + fh, err := zip.FileInfoHeader(fi) + if err != nil { + return err + } + fh.Name = recPath + fh.Method = zip.Deflate + + fw, err := zw.CreateHeader(fh) + if err != nil { + return err + } + + if fi.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(srcFile) + if err != nil { + return err + } + if _, err = fw.Write([]byte(target)); err != nil { + return err + } + } else { + f, err := os.Open(srcFile) + if err != nil { + return err + } + if _, err = io.Copy(fw, f); err != nil { + return err + } + } + } + return nil +} + +// packDir packs a directory and its subdirectories and files +// recursively to zip.Writer. +func packDir(srcPath string, recPath string, zw *zip.Writer, fn cae.HookFunc) error { + dir, err := os.Open(srcPath) + if err != nil { + return err + } + defer dir.Close() + + fis, err := dir.Readdir(0) + if err != nil { + return err + } + for _, fi := range fis { + if cae.IsFilter(fi.Name()) { + continue + } + curPath := srcPath + "/" + fi.Name() + tmpRecPath := filepath.Join(recPath, fi.Name()) + if err = fn(curPath, fi); err != nil { + continue + } + + if fi.IsDir() { + if err = packFile(srcPath, tmpRecPath, zw, fi); err != nil { + return err + } + err = packDir(curPath, tmpRecPath, zw, fn) + } else { + err = packFile(curPath, tmpRecPath, zw, fi) + } + if err != nil { + return err + } + } + return nil +} + +// packToWriter packs given path object to io.Writer. +func packToWriter(srcPath string, w io.Writer, fn func(fullName string, fi os.FileInfo) error, includeDir bool) error { + zw := zip.NewWriter(w) + defer zw.Close() + + f, err := os.Open(srcPath) + if err != nil { + return err + } + defer f.Close() + + fi, err := f.Stat() + if err != nil { + return err + } + + basePath := path.Base(srcPath) + if fi.IsDir() { + if includeDir { + if err = packFile(srcPath, basePath, zw, fi); err != nil { + return err + } + } else { + basePath = "" + } + return packDir(srcPath, basePath, zw, fn) + } + return packFile(srcPath, basePath, zw, fi) +} + +// packTo packs given source path object to target path. +func packTo(srcPath, destPath string, fn cae.HookFunc, includeDir bool) error { + fw, err := os.Create(destPath) + if err != nil { + return err + } + defer fw.Close() + + return packToWriter(srcPath, fw, fn, includeDir) +} + +// PackToFunc packs the complete archive to the specified destination. +// It accepts a function as a middleware for custom operations. +func PackToFunc(srcPath, destPath string, fn func(fullName string, fi os.FileInfo) error, includeDir ...bool) error { + isIncludeDir := false + if len(includeDir) > 0 && includeDir[0] { + isIncludeDir = true + } + return packTo(srcPath, destPath, fn, isIncludeDir) +} + +var defaultPackFunc = func(fullName string, fi os.FileInfo) error { + if !Verbose { + return nil + } + + if fi.IsDir() { + fmt.Printf("Adding dir...%s\n", fullName) + } else { + fmt.Printf("Adding file...%s\n", fullName) + } + return nil +} + +// PackTo packs the whole archive to the specified destination. +// Call Flush() will automatically call this in the end. +func PackTo(srcPath, destPath string, includeDir ...bool) error { + return PackToFunc(srcPath, destPath, defaultPackFunc, includeDir...) +} + +// Close opens or creates archive and save changes. +func (z *ZipArchive) Close() (err error) { + if err = z.Flush(); err != nil { + return err + } + + if z.ReadCloser != nil { + if err = z.ReadCloser.Close(); err != nil { + return err + } + z.ReadCloser = nil + } + return nil +} diff --git a/modules/cae/zip/zip.go b/modules/cae/zip/zip.go new file mode 100644 index 0000000..9daa27e --- /dev/null +++ b/modules/cae/zip/zip.go @@ -0,0 +1,234 @@ +// Copyright 2013 Unknwon +// +// 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 zip enables you to transparently read or write ZIP compressed archives and the files inside them. +package zip + +import ( + "archive/zip" + "errors" + "io" + "os" + "path" + "path/filepath" + "strings" + + "github.com/gpmgo/gopm/modules/cae" +) + +// A File represents a file or directory entry in archive. +type File struct { + *zip.FileHeader + oldName string // NOTE: unused, for future change name feature. + oldComment string // NOTE: unused, for future change comment feature. + absPath string // Absolute path of local file system. + tmpPath string +} + +// A ZipArchive represents a file archive, compressed with Zip. +type ZipArchive struct { + *zip.ReadCloser + FileName string + Comment string + NumFiles int + Flag int + Permission os.FileMode + + files []*File + isHasChanged bool + + // For supporting flushing to io.Writer. + writer io.Writer + isHasWriter bool +} + +// OpenFile is the generalized open call; most users will use Open +// instead. It opens the named zip file with specified flag +// (O_RDONLY etc.) if applicable. If successful, +// methods on the returned ZipArchive can be used for I/O. +// If there is an error, it will be of type *PathError. +func OpenFile(name string, flag int, perm os.FileMode) (*ZipArchive, error) { + z := new(ZipArchive) + err := z.Open(name, flag, perm) + return z, err +} + +// Create creates the named zip file, truncating +// it if it already exists. If successful, methods on the returned +// ZipArchive can be used for I/O; the associated file descriptor has mode +// O_RDWR. +// If there is an error, it will be of type *PathError. +func Create(name string) (*ZipArchive, error) { + os.MkdirAll(path.Dir(name), os.ModePerm) + return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) +} + +// Open opens the named zip file for reading. If successful, methods on +// the returned ZipArchive can be used for reading; the associated file +// descriptor has mode O_RDONLY. +// If there is an error, it will be of type *PathError. +func Open(name string) (*ZipArchive, error) { + return OpenFile(name, os.O_RDONLY, 0) +} + +// New accepts a variable that implemented interface io.Writer +// for write-only purpose operations. +func New(w io.Writer) *ZipArchive { + return &ZipArchive{ + writer: w, + isHasWriter: true, + } +} + +// List returns a string slice of files' name in ZipArchive. +// Specify prefixes will be used as filters. +func (z *ZipArchive) List(prefixes ...string) []string { + isHasPrefix := len(prefixes) > 0 + names := make([]string, 0, z.NumFiles) + for _, f := range z.files { + if isHasPrefix && !cae.HasPrefix(f.Name, prefixes) { + continue + } + names = append(names, f.Name) + } + return names +} + +// AddEmptyDir adds a raw directory entry to ZipArchive, +// it returns false if same directory enry already existed. +func (z *ZipArchive) AddEmptyDir(dirPath string) bool { + if !strings.HasSuffix(dirPath, "/") { + dirPath += "/" + } + + for _, f := range z.files { + if dirPath == f.Name { + return false + } + } + + dirPath = strings.TrimSuffix(dirPath, "/") + if strings.Contains(dirPath, "/") { + // Auto add all upper level directories. + z.AddEmptyDir(path.Dir(dirPath)) + } + z.files = append(z.files, &File{ + FileHeader: &zip.FileHeader{ + Name: dirPath + "/", + UncompressedSize: 0, + }, + }) + z.updateStat() + return true +} + +// AddDir adds a directory and subdirectories entries to ZipArchive. +func (z *ZipArchive) AddDir(dirPath, absPath string) error { + dir, err := os.Open(absPath) + if err != nil { + return err + } + defer dir.Close() + + // Make sure we have all upper level directories. + z.AddEmptyDir(dirPath) + + fis, err := dir.Readdir(0) + if err != nil { + return err + } + for _, fi := range fis { + curPath := strings.Replace(absPath+"/"+fi.Name(), "\\", "/", -1) + tmpRecPath := strings.Replace(filepath.Join(dirPath, fi.Name()), "\\", "/", -1) + if fi.IsDir() { + if err = z.AddDir(tmpRecPath, curPath); err != nil { + return err + } + } else { + if err = z.AddFile(tmpRecPath, curPath); err != nil { + return err + } + } + } + return nil +} + +// updateStat should be called after every change for rebuilding statistic. +func (z *ZipArchive) updateStat() { + z.NumFiles = len(z.files) + z.isHasChanged = true +} + +// AddFile adds a file entry to ZipArchive. +func (z *ZipArchive) AddFile(fileName, absPath string) error { + if cae.IsFilter(absPath) { + return nil + } + + f, err := os.Open(absPath) + if err != nil { + return err + } + defer f.Close() + + fi, err := f.Stat() + if err != nil { + return err + } + + file := new(File) + file.FileHeader, err = zip.FileInfoHeader(fi) + if err != nil { + return err + } + file.Name = fileName + file.absPath = absPath + + z.AddEmptyDir(path.Dir(fileName)) + + isExist := false + for _, f := range z.files { + if fileName == f.Name { + f = file + isExist = true + break + } + } + if !isExist { + z.files = append(z.files, file) + } + + z.updateStat() + return nil +} + +// DeleteIndex deletes an entry in the archive by its index. +func (z *ZipArchive) DeleteIndex(idx int) error { + if idx >= z.NumFiles { + return errors.New("index out of range of number of files") + } + + z.files = append(z.files[:idx], z.files[idx+1:]...) + return nil +} + +// DeleteName deletes an entry in the archive by its name. +func (z *ZipArchive) DeleteName(name string) error { + for i, f := range z.files { + if f.Name == name { + return z.DeleteIndex(i) + } + } + return errors.New("entry with given name not found") +} diff --git a/modules/cli/app.go b/modules/cli/app.go new file mode 100644 index 0000000..66e541c --- /dev/null +++ b/modules/cli/app.go @@ -0,0 +1,246 @@ +package cli + +import ( + "fmt" + "io/ioutil" + "os" + "time" +) + +// App is the main structure of a cli application. It is recomended that +// and app be created with the cli.NewApp() function +type App struct { + // The name of the program. Defaults to os.Args[0] + Name string + // Description of the program. + Usage 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 + // 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 + // 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 + // Author + Author string + // Author e-mail + Email string +} + +// 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: os.Args[0], + Usage: "A new cli application", + Version: "0.0.0", + BashComplete: DefaultAppComplete, + Action: helpCommand.Action, + Compiled: compileTime(), + } +} + +// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination +func (a *App) Run(arguments []string) error { + // append help to commands + if a.Command(helpCommand.Name) == nil && !a.HideHelp { + a.Commands = append(a.Commands, helpCommand) + a.appendFlag(HelpFlag) + } + + //append version/help flags + if a.EnableBashCompletion { + a.appendFlag(BashCompletionFlag) + } + 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.Println(nerr) + context := NewContext(a, set, set) + ShowAppHelp(context) + fmt.Println("") + return nerr + } + context := NewContext(a, set, set) + + if err != nil { + fmt.Printf("Incorrect Usage.\n\n") + ShowAppHelp(context) + fmt.Println("") + return err + } + + if checkCompletions(context) { + return nil + } + + if checkHelp(context) { + return nil + } + + if checkVersion(context) { + return nil + } + + 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 { + os.Stderr.WriteString(fmt.Sprintln(err)) + os.Exit(1) + } +} + +// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags +func (a *App) RunAsSubcommand(ctx *Context) error { + // append help to commands + if len(a.Commands) > 0 { + if a.Command(helpCommand.Name) == nil && !a.HideHelp { + a.Commands = append(a.Commands, helpCommand) + a.appendFlag(HelpFlag) + } + } + + // 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.globalSet) + + if nerr != nil { + fmt.Println(nerr) + if len(a.Commands) > 0 { + ShowSubcommandHelp(context) + } else { + ShowCommandHelp(ctx, context.Args().First()) + } + fmt.Println("") + return nerr + } + + if err != nil { + fmt.Printf("Incorrect Usage.\n\n") + ShowSubcommandHelp(context) + return err + } + + if checkCompletions(context) { + return nil + } + + if len(a.Commands) > 0 { + if checkSubcommandHelp(context) { + return nil + } + } else { + if checkCommandHelp(ctx, context.Args().First()) { + return nil + } + } + + 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 + if len(a.Commands) > 0 { + a.Action(context) + } else { + a.Action(ctx) + } + + 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) + } +} diff --git a/modules/cli/cli.go b/modules/cli/cli.go new file mode 100644 index 0000000..b742545 --- /dev/null +++ b/modules/cli/cli.go @@ -0,0 +1,19 @@ +// 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 diff --git a/modules/cli/command.go b/modules/cli/command.go new file mode 100644 index 0000000..5622b38 --- /dev/null +++ b/modules/cli/command.go @@ -0,0 +1,144 @@ +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 + ShortName string + // A short description of the usage of this command + Usage string + // A longer explanation of how the command works + Description 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 + // 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 +} + +// 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 { + return c.startApp(ctx) + } + + if !c.HideHelp { + // 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) + + firstFlagIndex := -1 + for index, arg := range ctx.Args() { + if strings.HasPrefix(arg, "-") { + firstFlagIndex = index + break + } + } + + var err error + if firstFlagIndex > -1 && !c.SkipFlagParsing { + args := ctx.Args() + regularArgs := args[1:firstFlagIndex] + flagArgs := args[firstFlagIndex:] + err = set.Parse(append(flagArgs, regularArgs...)) + } else { + err = set.Parse(ctx.Args().Tail()) + } + + if err != nil { + fmt.Printf("Incorrect Usage.\n\n") + ShowCommandHelp(ctx, c.Name) + fmt.Println("") + return err + } + + nerr := normalizeFlags(c.Flags, set) + if nerr != nil { + fmt.Println(nerr) + fmt.Println("") + ShowCommandHelp(ctx, c.Name) + fmt.Println("") + return nerr + } + context := NewContext(ctx.App, set, ctx.globalSet) + + if checkCommandCompletions(context, c.Name) { + return nil + } + + if checkCommandHelp(context, c.Name) { + return nil + } + context.Command = c + c.Action(context) + return nil +} + +// Returns true if Command.Name or Command.ShortName matches given name +func (c Command) HasName(name string) bool { + return c.Name == name || c.ShortName == name +} + +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.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 + + // bash completion + app.EnableBashCompletion = ctx.App.EnableBashCompletion + if c.BashComplete != nil { + app.BashComplete = c.BashComplete + } + + // set the actions + app.Before = c.Before + if c.Action != nil { + app.Action = c.Action + } else { + app.Action = helpSubcommand.Action + } + + return app.RunAsSubcommand(ctx) +} diff --git a/modules/cli/context.go b/modules/cli/context.go new file mode 100644 index 0000000..8b44148 --- /dev/null +++ b/modules/cli/context.go @@ -0,0 +1,315 @@ +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 + globalSet *flag.FlagSet + setFlags map[string]bool +} + +// Creates a new context. For use in when invoking an App or Command action. +func NewContext(app *App, set *flag.FlagSet, globalSet *flag.FlagSet) *Context { + return &Context{App: app, flagSet: set, globalSet: globalSet} +} + +// 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 { + return lookupInt(name, c.globalSet) +} + +// 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 { + return lookupDuration(name, c.globalSet) +} + +// Looks up the value of a global bool flag, returns false if no bool flag exists +func (c *Context) GlobalBool(name string) bool { + return lookupBool(name, c.globalSet) +} + +// Looks up the value of a global string flag, returns "" if no string flag exists +func (c *Context) GlobalString(name string) string { + return lookupString(name, c.globalSet) +} + +// 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 { + return lookupStringSlice(name, c.globalSet) +} + +// 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 { + return lookupIntSlice(name, c.globalSet) +} + +// Looks up the value of a global generic flag, returns nil if no generic flag exists +func (c *Context) GlobalGeneric(name string) interface{} { + return lookupGeneric(name, c.globalSet) +} + +// Determines if the flag was actually set exists +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 +} + +// 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 +} + +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 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 +} diff --git a/modules/cli/flag.go b/modules/cli/flag.go new file mode 100644 index 0000000..b30bca3 --- /dev/null +++ b/modules/cli/flag.go @@ -0,0 +1,410 @@ +package cli + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + "time" +) + +// This flag enables bash-completion for all commands and subcommands +var BashCompletionFlag = BoolFlag{ + Name: "generate-bash-completion", +} + +// This flag prints the version for the application +var VersionFlag = BoolFlag{ + Name: "version, v", + Usage: "print the version", +} + +// This flag prints the help for all commands and subcommands +var HelpFlag = BoolFlag{ + Name: "help, h", + Usage: "show help", +} + +// Flag is a common interface related to parsing flags in cli. +// For more advanced flag parsing techniques, it is recomended that +// this interface be implemented. +type Flag interface { + fmt.Stringer + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) + getName() string +} + +func flagSet(name string, flags []Flag) *flag.FlagSet { + set := flag.NewFlagSet(name, flag.ContinueOnError) + + for _, f := range flags { + f.Apply(set) + } + return set +} + +func eachName(longName string, fn func(string)) { + parts := strings.Split(longName, ",") + for _, name := range parts { + name = strings.Trim(name, " ") + fn(name) + } +} + +// Generic is a generic parseable type identified by a specific flag +type Generic interface { + Set(value string) error + String() string +} + +// GenericFlag is the flag type for types implementing Generic +type GenericFlag struct { + Name string + Value Generic + Usage string + EnvVar string +} + +func (f GenericFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage)) +} + +func (f GenericFlag) Apply(set *flag.FlagSet) { + val := f.Value + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + val.Set(envVal) + } + } + + eachName(f.Name, func(name string) { + set.Var(f.Value, name, f.Usage) + }) +} + +func (f GenericFlag) getName() string { + return f.Name +} + +type StringSlice []string + +func (f *StringSlice) Set(value string) error { + *f = append(*f, value) + return nil +} + +func (f *StringSlice) String() string { + return fmt.Sprintf("%s", *f) +} + +func (f *StringSlice) Value() []string { + return *f +} + +type StringSliceFlag struct { + Name string + Value *StringSlice + Usage string + EnvVar string +} + +func (f StringSliceFlag) String() string { + firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") + pref := prefixFor(firstName) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) +} + +func (f StringSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + newVal.Set(s) + } + f.Value = newVal + } + } + + eachName(f.Name, func(name string) { + set.Var(f.Value, name, f.Usage) + }) +} + +func (f StringSliceFlag) getName() string { + return f.Name +} + +type IntSlice []int + +func (f *IntSlice) Set(value string) error { + + tmp, err := strconv.Atoi(value) + if err != nil { + return err + } else { + *f = append(*f, tmp) + } + return nil +} + +func (f *IntSlice) String() string { + return fmt.Sprintf("%d", *f) +} + +func (f *IntSlice) Value() []int { + return *f +} + +type IntSliceFlag struct { + Name string + Value *IntSlice + Usage string + EnvVar string +} + +func (f IntSliceFlag) String() string { + firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") + pref := prefixFor(firstName) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) +} + +func (f IntSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + newVal := &IntSlice{} + for _, s := range strings.Split(envVal, ",") { + err := newVal.Set(s) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + } + } + f.Value = newVal + } + } + + eachName(f.Name, func(name string) { + set.Var(f.Value, name, f.Usage) + }) +} + +func (f IntSliceFlag) getName() string { + return f.Name +} + +type BoolFlag struct { + Name string + Usage string + EnvVar string +} + +func (f BoolFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) +} + +func (f BoolFlag) Apply(set *flag.FlagSet) { + val := false + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + } + } + } + + eachName(f.Name, func(name string) { + set.Bool(name, val, f.Usage) + }) +} + +func (f BoolFlag) getName() string { + return f.Name +} + +type BoolTFlag struct { + Name string + Usage string + EnvVar string +} + +func (f BoolTFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) +} + +func (f BoolTFlag) Apply(set *flag.FlagSet) { + val := true + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + } + } + } + + eachName(f.Name, func(name string) { + set.Bool(name, val, f.Usage) + }) +} + +func (f BoolTFlag) getName() string { + return f.Name +} + +type StringFlag struct { + Name string + Value string + Usage string + EnvVar string +} + +func (f StringFlag) String() string { + var fmtString string + fmtString = "%s %v\t%v" + + if len(f.Value) > 0 { + fmtString = "%s '%v'\t%v" + } else { + fmtString = "%s %v\t%v" + } + + return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) +} + +func (f StringFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + f.Value = envVal + } + } + + eachName(f.Name, func(name string) { + set.String(name, f.Value, f.Usage) + }) +} + +func (f StringFlag) getName() string { + return f.Name +} + +type IntFlag struct { + Name string + Value int + Usage string + EnvVar string +} + +func (f IntFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +func (f IntFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValInt, err := strconv.ParseUint(envVal, 10, 64) + if err == nil { + f.Value = int(envValInt) + } + } + } + + eachName(f.Name, func(name string) { + set.Int(name, f.Value, f.Usage) + }) +} + +func (f IntFlag) getName() string { + return f.Name +} + +type DurationFlag struct { + Name string + Value time.Duration + Usage string + EnvVar string +} + +func (f DurationFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +func (f DurationFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValDuration, err := time.ParseDuration(envVal) + if err == nil { + f.Value = envValDuration + } + } + } + + eachName(f.Name, func(name string) { + set.Duration(name, f.Value, f.Usage) + }) +} + +func (f DurationFlag) getName() string { + return f.Name +} + +type Float64Flag struct { + Name string + Value float64 + Usage string + EnvVar string +} + +func (f Float64Flag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +func (f Float64Flag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err == nil { + f.Value = float64(envValFloat) + } + } + } + + eachName(f.Name, func(name string) { + set.Float64(name, f.Value, f.Usage) + }) +} + +func (f Float64Flag) getName() string { + return f.Name +} + +func prefixFor(name string) (prefix string) { + if len(name) == 1 { + prefix = "-" + } else { + prefix = "--" + } + + return +} + +func prefixedNames(fullName string) (prefixed string) { + parts := strings.Split(fullName, ",") + for i, name := range parts { + name = strings.Trim(name, " ") + prefixed += prefixFor(name) + name + if i < len(parts)-1 { + prefixed += ", " + } + } + return +} + +func withEnvHint(envVar, str string) string { + envText := "" + if envVar != "" { + envText = fmt.Sprintf(" [$%s]", envVar) + } + return str + envText +} diff --git a/modules/cli/help.go b/modules/cli/help.go new file mode 100644 index 0000000..5020cb6 --- /dev/null +++ b/modules/cli/help.go @@ -0,0 +1,224 @@ +package cli + +import ( + "fmt" + "os" + "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: + {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] + +VERSION: + {{.Version}}{{if or .Author .Email}} + +AUTHOR:{{if .Author}} + {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} + {{.Email}}{{end}}{{end}} + +COMMANDS: + {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{if .Flags}} +GLOBAL OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{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: + {{.Name}} - {{.Usage}} + +USAGE: + command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{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: + {{.Name}} - {{.Usage}} + +USAGE: + {{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...] + +COMMANDS: + {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{if .Flags}} +OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +` + +var helpCommand = Command{ + Name: "help", + ShortName: "h", + Usage: "Shows a list of commands or help for one command", + Action: func(c *Context) { + args := c.Args() + if args.Present() { + ShowCommandHelp(c, args.First()) + } else { + ShowAppHelp(c) + } + }, +} + +var helpSubcommand = Command{ + Name: "help", + ShortName: "h", + Usage: "Shows a list of commands or help for one command", + Action: func(c *Context) { + args := c.Args() + if args.Present() { + ShowCommandHelp(c, args.First()) + } else { + ShowSubcommandHelp(c) + } + }, +} + +// Prints help for the App +var HelpPrinter = printHelp + +// Prints version for the App +var VersionPrinter = printVersion + +func ShowAppHelp(c *Context) { + HelpPrinter(AppHelpTemplate, c.App) +} + +// Prints the list of subcommands as the default app completion method +func DefaultAppComplete(c *Context) { + for _, command := range c.App.Commands { + fmt.Println(command.Name) + if command.ShortName != "" { + fmt.Println(command.ShortName) + } + } +} + +// Prints help for the given command +func ShowCommandHelp(c *Context, command string) { + for _, c := range c.App.Commands { + if c.HasName(command) { + HelpPrinter(CommandHelpTemplate, c) + return + } + } + + if c.App.CommandNotFound != nil { + c.App.CommandNotFound(c, command) + } else { + fmt.Printf("No help topic for '%v'\n", command) + } +} + +// Prints help for the given subcommand +func ShowSubcommandHelp(c *Context) { + HelpPrinter(SubcommandHelpTemplate, c.App) +} + +// Prints the version number of the App +func ShowVersion(c *Context) { + VersionPrinter(c) +} + +func printVersion(c *Context) { + fmt.Printf("%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(templ string, data interface{}) { + w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) + t := template.Must(template.New("help").Parse(templ)) + err := t.Execute(w, data) + if err != nil { + panic(err) + } + w.Flush() +} + +func checkVersion(c *Context) bool { + if c.GlobalBool("version") { + ShowVersion(c) + return true + } + + return false +} + +func checkHelp(c *Context) bool { + if c.GlobalBool("h") || c.GlobalBool("help") { + ShowAppHelp(c) + return true + } + + return false +} + +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.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 +} diff --git a/modules/doc/bitbucket.go b/modules/doc/bitbucket.go deleted file mode 100644 index 4399ab8..0000000 --- a/modules/doc/bitbucket.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2014 Unknown -// -// 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 doc - -import ( - "fmt" - "net/http" - "os" - "path" - "regexp" - "strings" - "time" - - "github.com/Unknwon/cae/zip" - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/log" - "github.com/gpmgo/gopm/modules/setting" -) - -var ( - bitbucketPattern = regexp.MustCompile(`^bitbucket\.org/(?P[a-z0-9A-Z_.\-]+)/(?P[a-z0-9A-Z_.\-]+)(?P/[a-z0-9A-Z_.\-/]*)?$`) - bitbucketEtagRe = regexp.MustCompile(`^(hg|git)-`) -) - -func getBitbucketPkg( - client *http.Client, - match map[string]string, - n *Node, - ctx *cli.Context) ([]string, error) { - - // Check version control. - if m := bitbucketEtagRe.FindStringSubmatch(n.Value); m != nil { - match["vcs"] = m[1] - } else { - var repo struct { - Scm string - } - if err := com.HttpGetJSON(client, com.Expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}", match), &repo); err != nil { - return nil, err - } - match["vcs"] = repo.Scm - } - - tags := make(map[string]string) - for _, nodeType := range []string{"branches", "tags"} { - var nodes map[string]struct { - Node string - } - if err := com.HttpGetJSON(client, com.Expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/{0}", match, nodeType), &nodes); err != nil { - return nil, fmt.Errorf("fail to fetch revision page: %v", err) - } - for t, n := range nodes { - tags[t] = n.Node - } - } - - // Get revision. - var err error - match["tag"], match["commit"], err = bestTag(tags, defaultTags[match["vcs"]]) - if err != nil { - return nil, err - } - - // Check downlaod type. - switch n.Type { - case BRANCH: - if !n.IsEmptyVal() { - match["commit"] = n.Value - break - } - - if match["commit"] == n.Revision { - log.Log("GET Package hasn't changed: %s", n.ImportPath) - return nil, nil - } - case TAG, COMMIT: - match["tag"] = n.Value - default: - return nil, fmt.Errorf("invalid node type: %s", n.Type) - } - n.Revision = match["commit"] - - // We use .zip here. - // zip : https://bitbucket.org/{owner}/{repo}/get/{commit}.zip - // tarball : https://bitbucket.org/{owner}/{repo}/get/{commit}.tar.gz - - // Downlaod archive. - tmpPath := path.Join(setting.HomeDir, ".gopm/temp/archive", - n.RootPath+"-"+fmt.Sprintf("%d", time.Now().Nanosecond())+".zip") - if err := com.HttpGetToFile(client, - com.Expand("https://bitbucket.org/{owner}/{repo}/get/{commit}.zip", match), - nil, tmpPath); err != nil { - return nil, fmt.Errorf("fail to download archive: %v", n.ImportPath, err) - } - defer os.Remove(tmpPath) - - // Remove old files. - os.RemoveAll(n.InstallPath) - os.MkdirAll(path.Dir(n.InstallPath), os.ModePerm) - - var rootDir string - var extractFn = func(fullName string, fi os.FileInfo) error { - if len(rootDir) == 0 { - rootDir = strings.Split(fullName, "/")[0] - } - return nil - } - - if err := zip.ExtractToFunc(tmpPath, path.Dir(n.InstallPath), extractFn); err != nil { - return nil, fmt.Errorf("fail to extract archive: %v", n.ImportPath, err) - } else if err = os.Rename(path.Join(path.Dir(n.InstallPath), rootDir), - n.InstallPath); err != nil { - return nil, fmt.Errorf("fail to rename directory: %v", n.ImportPath, err) - } - - // Check if need to check imports. - if !n.IsGetDeps { - return nil, nil - } - return GetImports(n.ImportPath, n.RootPath, n.InstallPath, false) -} diff --git a/modules/doc/gitcafe.go b/modules/doc/gitcafe.go deleted file mode 100644 index d61c500..0000000 --- a/modules/doc/gitcafe.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2014 Unknown -// -// 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 doc - -import ( - "archive/tar" - "bytes" - "fmt" - "io" - "net/http" - "os" - "path" - "regexp" - "strings" - - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/log" -) - -var ( - gitcafeRevisionRe = regexp.MustCompile(`[a-z0-9A-Z]*`) - gitcafePattern = regexp.MustCompile(`^gitcafe\.com/(?P[a-z0-9A-Z_.\-]+)/(?P[a-z0-9A-Z_.\-]+)(?P/[a-z0-9A-Z_.\-/]*)?$`) -) - -func getGitcafePkg( - client *http.Client, - match map[string]string, - n *Node, - ctx *cli.Context) ([]string, error) { - - // Check downlaod type. - switch n.Type { - case BRANCH: - if !n.IsEmptyVal() { - match["sha"] = n.Value - break - } - - match["sha"] = MASTER - - // Get revision. - p, err := com.HttpGetBytes(client, - com.Expand("http://gitcafe.com/{owner}/{repo}/tree/{sha}", match), nil) - if err != nil { - return nil, fmt.Errorf("fail to fetch revision page: %v", err) - } - - if m := gitcafeRevisionRe.FindSubmatch(p); m == nil { - return nil, fmt.Errorf("fail to get revision: %v", err) - } else { - etag := strings.TrimPrefix(string(m[0]), ``) - if etag == n.Revision { - log.Log("GET Package hasn't changed: %s", n.ImportPath) - return nil, nil - } - n.Revision = etag - } - case TAG, COMMIT: - match["sha"] = n.Value - default: - return nil, fmt.Errorf("invalid node type: %s", n.Type) - } - - // tar.gz: http://{projectRoot}/tarball/{sha} - - // Downlaod archive. - p, err := com.HttpGetBytes(client, com.Expand("http://gitcafe.com/{owner}/{repo}/tarball/{sha}", match), nil) - if err != nil { - return nil, err - } - - // Remove old files. - os.RemoveAll(n.InstallPath) - os.MkdirAll(path.Dir(n.InstallPath), os.ModePerm) - - tr := tar.NewReader(bytes.NewReader(p)) - - var rootPath string - // Get source file data. - for { - h, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - - fname := h.Name - if fname == "pax_global_header" { - continue - } - - if len(rootPath) == 0 { - rootPath = fname[:strings.Index(fname, match["repo"])+len(match["repo"])] - } - absPath := strings.Replace(fname, rootPath, n.InstallPath, 1) - - switch { - case h.FileInfo().IsDir(): - // Create diretory before create file. - os.MkdirAll(absPath+"/", os.ModePerm) - case !strings.HasPrefix(fname, "."): - // Get data from archive. - fbytes := make([]byte, h.Size) - if _, err := io.ReadFull(tr, fbytes); err != nil { - return nil, err - } - - if err = com.WriteFile(absPath, fbytes); err != nil { - return nil, err - } - } - } - - // Check if need to check imports. - if !n.IsGetDeps { - return nil, nil - } - return GetImports(n.ImportPath, n.RootPath, n.InstallPath, false) -} diff --git a/modules/doc/github.go b/modules/doc/github.go deleted file mode 100644 index 846957f..0000000 --- a/modules/doc/github.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2014 Unknown -// -// 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 doc - -import ( - "fmt" - "net/http" - "os" - "path" - "regexp" - "strings" - "time" - - "github.com/Unknwon/cae/zip" - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/log" - "github.com/gpmgo/gopm/modules/setting" -) - -func init() { - zip.Verbose = false -} - -var ( - githubPattern = regexp.MustCompile(`^github\.com/(?P[a-z0-9A-Z_.\-]+)/(?P[a-z0-9A-Z_.\-]+)(?P/[a-z0-9A-Z_.\-/]*)?$`) -) - -func GetGithubCredentials() string { - return "client_id=" + setting.Cfg.MustValue("github", "CLIENT_ID") + - "&client_secret=" + setting.Cfg.MustValue("github", "CLIENT_SECRET") -} - -func getGithubPkg( - client *http.Client, - match map[string]string, - n *Node, - ctx *cli.Context) ([]string, error) { - - match["cred"] = GetGithubCredentials() - - // Check downlaod type. - switch n.Type { - case BRANCH: - if !n.IsEmptyVal() { - match["sha"] = n.Value - break - } - - match["sha"] = MASTER - - // Only get and check revision with the latest version. - var refs []*struct { - Ref string - Object struct { - Sha string - } - } - - if err := com.HttpGetJSON(client, - com.Expand("https://api.github.com/repos/{owner}/{repo}/git/refs?{cred}", match), - &refs); err != nil { - if strings.Contains(err.Error(), "403") { - // NOTE: get revision from center repository. - break - } - log.Warn("GET", "Fail to get revision") - log.Warn("", "\t"+err.Error()) - break - } - - var etag string - for _, ref := range refs { - if strings.HasPrefix(ref.Ref, "refs/heads/master") { - etag = ref.Object.Sha - break - } - } - if etag == n.Revision { - log.Log("Package hasn't changed: %s", n.ImportPath) - return nil, nil - } - n.Revision = etag - case TAG, COMMIT: - match["sha"] = n.Value - default: - return nil, fmt.Errorf("invalid node type: %s", n.Type) - } - - // We use .zip here. - // zip: https://github.com/{owner}/{repo}/archive/{sha}.zip - // tarball: https://github.com/{owner}/{repo}/tarball/{sha} - - // Downlaod archive. - tmpPath := path.Join(setting.HomeDir, ".gopm/temp/archive", - n.RootPath+"-"+fmt.Sprintf("%s", time.Now().Nanosecond())+".zip") - if err := com.HttpGetToFile(client, - com.Expand("https://github.com/{owner}/{repo}/archive/{sha}.zip", match), - nil, tmpPath); err != nil { - return nil, fmt.Errorf("fail to download archive: %v", n.ImportPath, err) - } - defer os.Remove(tmpPath) - - // Remove old files. - os.RemoveAll(n.InstallPath) - os.MkdirAll(path.Dir(n.InstallPath), os.ModePerm) - - var rootDir string - var extractFn = func(fullName string, fi os.FileInfo) error { - if len(rootDir) == 0 { - rootDir = strings.Split(fullName, "/")[0] - } - return nil - } - - if err := zip.ExtractToFunc(tmpPath, path.Dir(n.InstallPath), extractFn); err != nil { - return nil, fmt.Errorf("fail to extract archive: %v", err) - } else if err = os.Rename(path.Join(path.Dir(n.InstallPath), rootDir), - n.InstallPath); err != nil { - return nil, fmt.Errorf("fail to rename directory: %v", err) - } - - // Check if need to check imports. - if !n.IsGetDeps { - return nil, nil - } - return GetImports(n.ImportPath, n.RootPath, n.InstallPath, false) -} diff --git a/modules/doc/google.go b/modules/doc/google.go deleted file mode 100644 index 2f5a37d..0000000 --- a/modules/doc/google.go +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2014 Unknown -// -// 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 doc - -import ( - "fmt" - "net/http" - "os" - "path" - "regexp" - "strings" - "time" - - "github.com/Unknwon/cae/zip" - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/log" - "github.com/gpmgo/gopm/modules/setting" -) - -var ( - googleRepoRe = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`) - googleRevisionRe = regexp.MustCompile(`

(?:[^ ]+ - )?Revision *([^:]+):`) - googleFileRe = regexp.MustCompile(`
  • [a-z0-9\-]+)(:?\.(?P[a-z0-9\-]+))?(?P/[a-z0-9A-Z_.\-/]+)?$`) -) - -func setupGoogleMatch(match map[string]string) { - if s := match["subrepo"]; s != "" { - match["dot"] = "." - match["query"] = "?repo=" + s - } else { - match["dot"] = "" - match["query"] = "" - } -} - -func getGoogleVCS(client *http.Client, match map[string]string) error { - // Scrape the HTML project page to find the VCS. - p, err := com.HttpGetBytes(client, com.Expand("http://code.google.com/p/{repo}/source/checkout", match), nil) - if err != nil { - return fmt.Errorf("fail to fetch page: %v", err) - } - m := googleRepoRe.FindSubmatch(p) - if m == nil { - return com.NotFoundError{"Could not VCS on Google Code project page."} - } - match["vcs"] = string(m[1]) - return nil -} - -type rawFile struct { - name string - rawURL string - data []byte -} - -func (rf *rawFile) Name() string { - return rf.name -} - -func (rf *rawFile) RawUrl() string { - return rf.rawURL -} - -func (rf *rawFile) Data() []byte { - return rf.data -} - -func (rf *rawFile) SetData(p []byte) { - rf.data = p -} - -func downloadFiles( - client *http.Client, - match map[string]string, - rootPath, installPath, commit string, - dirs []string) error { - - suf := "?r=" + commit - if len(commit) == 0 { - suf = "" - } - - for _, d := range dirs { - p, err := com.HttpGetBytes(client, rootPath+d+suf, nil) - if err != nil { - return err - } - - // Create destination directory. - os.MkdirAll(path.Join(installPath, d), os.ModePerm) - - // Get source files in current path. - files := make([]com.RawFile, 0, 5) - for _, m := range googleFileRe.FindAllSubmatch(p, -1) { - fname := strings.Split(string(m[1]), "?")[0] - files = append(files, &rawFile{ - name: fname, - rawURL: rootPath + d + fname + suf, - }) - } - - // Fetch files from VCS. - if err = com.FetchFiles(client, files, nil); err != nil { - return err - } - - // Save files. - for _, f := range files { - absPath := path.Join(installPath, d) - - // Create diretory before create file. - os.MkdirAll(path.Dir(absPath), os.ModePerm) - - // Write data to file - fw, err := os.Create(path.Join(absPath, f.Name())) - if err != nil { - return err - } - - _, err = fw.Write(f.Data()) - fw.Close() - if err != nil { - return err - } - } - files = nil - - subdirs := make([]string, 0, 3) - // Get subdirectories. - for _, m := range googleDirRe.FindAllSubmatch(p, -1) { - dirName := strings.Split(string(m[1]), "?")[0] - if strings.HasSuffix(dirName, "/") { - subdirs = append(subdirs, d+dirName) - } - } - - if err = downloadFiles(client, match, - rootPath, installPath, commit, subdirs); err != nil { - return err - } - } - return nil -} - -func getGooglePkg( - client *http.Client, - match map[string]string, - n *Node, - ctx *cli.Context) ([]string, error) { - - setupGoogleMatch(match) - // Check version control. - if err := getGoogleVCS(client, match); err != nil { - return nil, fmt.Errorf("fail to get package(%s) VCS: %v", n.ImportPath, err) - } - - switch n.Type { - case BRANCH: - if !n.IsEmptyVal() { - match["tag"] = n.Value - break - } - - match["tag"] = defaultTags[match["vcs"]] - case TAG, COMMIT: - match["tag"] = n.Value - default: - return nil, fmt.Errorf("invalid node type: %s", n.Type) - } - - // Get revision. - p, err := com.HttpGetBytes(client, - com.Expand("http://{subrepo}{dot}{repo}.googlecode.com/{vcs}{dir}/?r={tag}", match), nil) - if err != nil { - log.Warn("Fail to fetch revision page: %v", err) - } else { - if m := googleRevisionRe.FindSubmatch(p); m == nil { - log.Warn("Fail to get revision: %v", err) - } else { - etag := string(m[1]) - if n.Type == BRANCH && etag == n.Revision { - log.Log("GET Package hasn't changed: %s", n.ImportPath) - return nil, nil - } - n.Revision = etag - } - } - - // Remove old files. - os.RemoveAll(n.InstallPath) - os.MkdirAll(path.Dir(n.InstallPath), os.ModePerm) - - if match["vcs"] == "svn" { - log.Warn("SVN detected, may take very long time to finish.") - - rootPath := com.Expand("http://{subrepo}{dot}{repo}.googlecode.com/{vcs}", match) - d, f := path.Split(rootPath) - - if err := downloadFiles(client, match, d, n.InstallPath, match["tag"], - []string{f + "/"}); err != nil { - return nil, fmt.Errorf("fail to downlaod file: %v", n.ImportPath, err) - } - } else { - // Downlaod archive. - tmpPath := path.Join(setting.HomeDir, ".gopm/temp/archive", - n.RootPath+"-"+fmt.Sprintf("%d", time.Now().Nanosecond())+".zip") - if err := com.HttpGetToFile(client, - com.Expand("http://{subrepo}{dot}{repo}.googlecode.com/archive/{tag}.zip", match), - nil, tmpPath); err != nil { - return nil, fmt.Errorf("fail to download archive: %v", n.ImportPath, err) - } - defer os.Remove(tmpPath) - - var rootDir string - var extractFn = func(fullName string, fi os.FileInfo) error { - if len(rootDir) == 0 { - rootDir = strings.Split(fullName, "/")[0] - } - return nil - } - - if err := zip.ExtractToFunc(tmpPath, path.Dir(n.InstallPath), extractFn); err != nil { - return nil, fmt.Errorf("fail to extract archive: %v", n.ImportPath, err) - } else if err = os.Rename(path.Join(path.Dir(n.InstallPath), rootDir), - n.InstallPath); err != nil { - return nil, fmt.Errorf("fail to rename directory: %v", n.ImportPath, err) - } - } - - // Check if need to check imports. - if !n.IsGetDeps { - return nil, nil - } - return GetImports(n.ImportPath, n.RootPath, n.InstallPath, false) -} diff --git a/modules/doc/gopm.go b/modules/doc/gopm.go deleted file mode 100644 index 0cacbe9..0000000 --- a/modules/doc/gopm.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2014 Unknown -// -// 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 doc - -import ( - "fmt" - "net/http" - "os" - "path" - "regexp" - "time" - - "github.com/Unknwon/cae/zip" - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/log" - "github.com/gpmgo/gopm/modules/setting" -) - -var ( - gopmPattern = regexp.MustCompile(`^gopm\.io/(?P[a-z0-9A-Z_.\-]+)/(?P[a-z0-9A-Z_.\-]+)(?P/[a-z0-9A-Z_.\-/]*)?$`) -) - -func getGopmPkg( - client *http.Client, - match map[string]string, - n *Node, - ctx *cli.Context) ([]string, error) { - - if !n.IsEmptyVal() { - match["sha"] = n.Value - } else { - var rel struct { - Tag string `json:"tag"` - Error string `json:"error"` - } - - if err := com.HttpGetJSON(client, - com.Expand("http://gopm.io/api/v1/{owner}/{repo}/releases/latest", match), - &rel); err != nil { - log.Warn("GET", "Fail to get revision") - log.Warn("", "\t"+err.Error()) - } else if len(rel.Tag) == 0 { - log.Warn("GET", "Fail to get revision") - log.Warn("", "\t"+rel.Error) - } else { - if rel.Tag == n.Revision { - log.Log("Package hasn't changed: %s", n.ImportPath) - return nil, nil - } - n.Revision = rel.Tag - match["sha"] = n.Revision - } - } - - // Downlaod archive. - tmpPath := path.Join(setting.HomeDir, ".gopm/temp/archive", - n.RootPath+"-"+fmt.Sprintf("%d", time.Now().Nanosecond())+".zip") - if err := com.HttpGetToFile(client, - com.Expand("http://gopm.io/{owner}/{repo}.zip?r={sha}", match), - nil, tmpPath); err != nil { - return nil, fmt.Errorf("fail to download archive: %v", n.ImportPath, err) - } - defer os.Remove(tmpPath) - - // Remove old files. - os.RemoveAll(n.InstallPath) - os.MkdirAll(path.Dir(n.InstallPath), os.ModePerm) - - // To prevent same output folder name, need to extract to temp path then move. - if err := zip.ExtractTo(tmpPath, n.InstallPath); err != nil { - return nil, fmt.Errorf("fail to extract archive: %v", err) - } - - // Check if need to check imports. - if !n.IsGetDeps { - return nil, nil - } - return GetImports(n.ImportPath, n.RootPath, n.InstallPath, false) -} diff --git a/modules/doc/http.go b/modules/doc/http.go index f462bdd..7a8227f 100644 --- a/modules/doc/http.go +++ b/modules/doc/http.go @@ -1,4 +1,4 @@ -// Copyright 2013 Unknown +// Copyright 2013 Unknwon // // 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 diff --git a/modules/doc/launchpad.go b/modules/doc/launchpad.go deleted file mode 100644 index 251e67b..0000000 --- a/modules/doc/launchpad.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2014 Unknown -// -// 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 doc - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "io" - "net/http" - "os" - "path" - "regexp" - "strings" - - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - - // "github.com/gpmgo/gopm/modules/log" - // "github.com/gpmgo/gopm/modules/setting" -) - -var launchpadPattern = regexp.MustCompile(`^launchpad\.net/(?P(?P[a-z0-9A-Z_.\-]+)(?P/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+)(?P/[a-z0-9A-Z_.\-/]+)*$`) - -func getLaunchpadPkg( - client *http.Client, - match map[string]string, - n *Node, - ctx *cli.Context) ([]string, error) { - - if match["project"] != "" && match["series"] != "" { - rc, err := com.HttpGet(client, com.Expand("https://code.launchpad.net/{project}{series}/.bzr/branch-format", match), nil) - _, isNotFound := err.(com.NotFoundError) - switch { - case err == nil: - rc.Close() - // The structure of the import path is launchpad.net/{root}/{dir}. - case isNotFound: - // The structure of the import path is is launchpad.net/{project}/{dir}. - match["repo"] = match["project"] - match["dir"] = com.Expand("{series}{dir}", match) - default: - return nil, err - } - } - - var downloadPath string - // Check if download with specific revision. - if len(n.Value) == 0 { - downloadPath = com.Expand("https://bazaar.launchpad.net/+branch/{repo}/tarball", match) - } else { - downloadPath = com.Expand("https://bazaar.launchpad.net/+branch/{repo}/tarball/"+n.Value, match) - } - - // Scrape the repo browser to find the project revision and individual Go files. - p, err := com.HttpGetBytes(client, downloadPath, nil) - if err != nil { - return nil, err - } - - // Remove old files. - os.RemoveAll(n.InstallPath) - os.MkdirAll(path.Dir(n.InstallPath), os.ModePerm) - - gzr, err := gzip.NewReader(bytes.NewReader(p)) - if err != nil { - return nil, err - } - defer gzr.Close() - - tr := tar.NewReader(gzr) - - var rootPath string // Auto path is the root path. - // Get source file data. - for { - h, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - - fname := h.Name - if len(rootPath) == 0 { - rootPath = fname[:strings.Index(fname, match["repo"])+len(match["repo"])] - } - absPath := strings.Replace(fname, rootPath, n.InstallPath, 1) - - switch { - case h.FileInfo().IsDir(): - // Create diretory before create file. - os.MkdirAll(absPath+"/", os.ModePerm) - case !strings.HasPrefix(fname, "."): - // Get data from archive. - fbytes := make([]byte, h.Size) - if _, err := io.ReadFull(tr, fbytes); err != nil { - return nil, err - } - - if err = com.WriteFile(absPath, fbytes); err != nil { - return nil, err - } - } - } - - // Check if need to check imports. - if !n.IsGetDeps { - return nil, nil - } - return GetImports(n.ImportPath, n.RootPath, n.InstallPath, false) -} diff --git a/modules/doc/oschina.go b/modules/doc/oschina.go deleted file mode 100644 index 176a19e..0000000 --- a/modules/doc/oschina.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2014 Unknown -// -// 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 doc - -import ( - "fmt" - "net/http" - "os" - "path" - "regexp" - "strings" - "time" - - "github.com/Unknwon/cae/zip" - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - - "github.com/gpmgo/gopm/modules/log" - "github.com/gpmgo/gopm/modules/setting" -) - -var ( - oscTagRe = regexp.MustCompile(`/repository/archive\?ref=(.*)">`) - oscRevisionRe = regexp.MustCompile(`[a-z0-9A-Z]*`) - oscPattern = regexp.MustCompile(`^git\.oschina\.net/(?P[a-z0-9A-Z_.\-]+)/(?P[a-z0-9A-Z_.\-]+)(?P/[a-z0-9A-Z_.\-/]*)?$`) -) - -func getOscPkg( - client *http.Client, - match map[string]string, - n *Node, - ctx *cli.Context) ([]string, error) { - - // Check downlaod type. - switch n.Type { - case BRANCH: - if !n.IsEmptyVal() { - match["sha"] = n.Value - break - } - - match["sha"] = MASTER - - // Get revision. - p, err := com.HttpGetBytes(client, - com.Expand("http://git.oschina.net/{owner}/{repo}/tree/{sha}", match), nil) - if err != nil { - return nil, fmt.Errorf("fail to fetch revision page: %v", err) - } - - if m := oscRevisionRe.FindSubmatch(p); m == nil { - return nil, fmt.Errorf("fail to get revision: %v", err) - } else { - etag := strings.TrimPrefix(string(m[0]), ``) - if etag == n.Revision { - log.Log("GET Package hasn't changed: %s", n.ImportPath) - return nil, nil - } - n.Revision = etag - } - case TAG, COMMIT: - match["sha"] = n.Value - default: - return nil, fmt.Errorf("invalid node type: %s", n.Type) - } - - // zip: http://{projectRoot}/repository/archive?ref={sha} - - // Downlaod archive. - tmpPath := path.Join(setting.HomeDir, ".gopm/temp/archive", - n.RootPath+"-"+fmt.Sprintf("%d", time.Now().Nanosecond())+".zip") - if err := com.HttpGetToFile(client, - com.Expand("http://git.oschina.net/{owner}/{repo}/repository/archive?ref={sha}", match), - nil, tmpPath); err != nil { - return nil, fmt.Errorf("fail to download archive: %v", n.ImportPath, err) - } - defer os.Remove(tmpPath) - - // Remove old files. - os.RemoveAll(n.InstallPath) - os.MkdirAll(path.Dir(n.InstallPath), os.ModePerm) - - tmpExtPath := path.Join(setting.HomeDir, ".gopm/temp/archive", - n.RootPath+"-"+fmt.Sprintf("%d", time.Now().Nanosecond())) - // To prevent same output folder name, need to extract to temp path then move. - if err := zip.ExtractTo(tmpPath, tmpExtPath); err != nil { - return nil, fmt.Errorf("fail to extract archive: %v", err) - } else if err = os.Rename(path.Join(tmpExtPath, com.Expand("{repo}", match)), - n.InstallPath); err != nil { - return nil, fmt.Errorf("fail to rename directory: %v", err) - } - - // Check if need to check imports. - if !n.IsGetDeps { - return nil, nil - } - return GetImports(n.ImportPath, n.RootPath, n.InstallPath, false) -} diff --git a/modules/doc/struct.go b/modules/doc/struct.go index e8b29a8..3d9d6d7 100644 --- a/modules/doc/struct.go +++ b/modules/doc/struct.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -15,6 +15,7 @@ package doc import ( + "encoding/json" "encoding/xml" "errors" "fmt" @@ -24,10 +25,11 @@ import ( "path" "regexp" "strings" + "time" - "github.com/Unknwon/com" - "github.com/codegangsta/cli" - + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/cae/zip" + "github.com/gpmgo/gopm/modules/cli" "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" ) @@ -41,13 +43,12 @@ type service struct { // services is the list of source code control services handled by gopm. var services = []*service{ - {githubPattern, "github.com/", getGithubPkg}, - {googlePattern, "code.google.com/", getGooglePkg}, - {bitbucketPattern, "bitbucket.org/", getBitbucketPkg}, - {oscPattern, "git.oschina.net/", getOscPkg}, - {gitcafePattern, "gitcafe.com/", getGitcafePkg}, - {launchpadPattern, "launchpad.net/", getLaunchpadPkg}, - {gopmPattern, "gopm.io/", getGopmPkg}, +// {githubPattern, "github.com/", getGithubPkg}, +// {googlePattern, "code.google.com/", getGooglePkg}, +// {bitbucketPattern, "bitbucket.org/", getBitbucketPkg}, +// {oscPattern, "git.oschina.net/", getOscPkg}, +// {gitcafePattern, "gitcafe.com/", getGitcafePkg}, +// {launchpadPattern, "launchpad.net/", getLaunchpadPkg}, } type RevisionType string @@ -99,7 +100,14 @@ func (pkg *Pkg) ValSuffix() string { if len(pkg.Value) > 0 { return "." + pkg.Value } - return pkg.Value + return "" +} + +func (pkg *Pkg) VerSuffix() string { + if len(pkg.Value) > 0 { + return " @ " + string(pkg.Type) + ":" + pkg.Value + } + return "" } // A Node represents a node object to be fetched from remote. @@ -137,12 +145,12 @@ func NewNode( // IsExist returns true if package exists in local repository. func (n *Node) IsExist() bool { - return com.IsExist(n.InstallPath) + return base.IsExist(n.InstallPath) } // IsExistGopath returns true if package exists in GOPATH. func (n *Node) IsExistGopath() bool { - return com.IsExist(n.InstallGopath) + return base.IsExist(n.InstallGopath) } func (n *Node) ValString() string { @@ -167,14 +175,14 @@ func (n *Node) CopyToGopath() error { } os.RemoveAll(n.InstallGopath) - if err := com.CopyDir(n.InstallPath, n.InstallGopath); err != nil { + if err := base.CopyDir(n.InstallPath, n.InstallGopath); err != nil { if setting.LibraryMode { return fmt.Errorf("Fail to copy to GOPATH: %v", err) } log.Error("", "Fail to copy to GOPATH:") log.Fatal("", "\t"+err.Error()) } - log.Log("Package copied to GOPATH: %s", n.RootPath) + log.Info("Package copied to GOPATH: %s", n.RootPath) return nil } @@ -182,7 +190,7 @@ func (n *Node) CopyToGopath() error { func (n *Node) UpdateByVcs(vcs string) error { switch vcs { case "git": - branch, stderr, err := com.ExecCmdDir(n.InstallGopath, + branch, stderr, err := base.ExecCmdDir(n.InstallGopath, "git", "rev-parse", "--abbrev-ref", "HEAD") if err != nil { log.Error("", "Error occurs when 'git rev-parse --abbrev-ref HEAD'") @@ -191,7 +199,7 @@ func (n *Node) UpdateByVcs(vcs string) error { } branch = strings.TrimSpace(branch) - _, stderr, err = com.ExecCmdDir(n.InstallGopath, + _, stderr, err = base.ExecCmdDir(n.InstallGopath, "git", "pull", "origin", branch) if err != nil { log.Error("", "Error occurs when 'git pull origin "+branch+"'") @@ -199,7 +207,7 @@ func (n *Node) UpdateByVcs(vcs string) error { return errors.New(stderr) } case "hg": - _, stderr, err := com.ExecCmdDir(n.InstallGopath, + _, stderr, err := base.ExecCmdDir(n.InstallGopath, "hg", "pull") if err != nil { log.Error("", "Error occurs when 'hg pull'") @@ -207,7 +215,7 @@ func (n *Node) UpdateByVcs(vcs string) error { return errors.New(stderr) } - _, stderr, err = com.ExecCmdDir(n.InstallGopath, + _, stderr, err = base.ExecCmdDir(n.InstallGopath, "hg", "up") if err != nil { log.Error("", "Error occurs when 'hg up'") @@ -215,7 +223,7 @@ func (n *Node) UpdateByVcs(vcs string) error { return errors.New(stderr) } case "svn": - _, stderr, err := com.ExecCmdDir(n.InstallGopath, + _, stderr, err := base.ExecCmdDir(n.InstallGopath, "svn", "update") if err != nil { log.Error("", "Error occurs when 'svn update'") @@ -266,7 +274,7 @@ metaScan: continue metaScan } if match != nil { - return nil, com.NotFoundError{"More than one found at " + scheme + "://" + importPath} + return nil, fmt.Errorf("more than one found at %s://%s", scheme, importPath) } projectRoot, vcs, repo := f[0], f[1], f[2] @@ -274,7 +282,7 @@ metaScan: repo = strings.TrimSuffix(repo, "."+vcs) i := strings.Index(repo, "://") if i < 0 { - return nil, com.NotFoundError{"Bad repo URL in ."} + return nil, fmt.Errorf("bad repo URL in ") } proto := repo[:i] repo = repo[i+len("://"):] @@ -297,7 +305,7 @@ metaScan: } } if match == nil { - return nil, com.NotFoundError{" not found."} + return nil, fmt.Errorf(" not found") } return match, nil } @@ -319,7 +327,7 @@ func fetchMeta(client *http.Client, importPath string) (map[string]string, error scheme = "http" resp, err = client.Get(scheme + "://" + uri) if err != nil { - return nil, &com.RemoteError{strings.SplitN(importPath, "/", 2)[0], err} + return nil, fmt.Errorf("fail to make request(%s): %v", strings.SplitN(importPath, "/", 2)[0], err) } } defer resp.Body.Close() @@ -338,11 +346,11 @@ func (n *Node) getDynamic(client *http.Client, ctx *cli.Context) ([]string, erro return nil, err } if rootMatch["projectRoot"] != match["projectRoot"] { - return nil, com.NotFoundError{"Project root mismatch."} + return nil, fmt.Errorf("project root mismatch") } } - n.DownloadURL = com.Expand("{repo}{dir}", match) + n.DownloadURL = base.Expand("{repo}{dir}", match) return n.Download(ctx) } @@ -375,6 +383,73 @@ func (n *Node) Download(ctx *cli.Context) ([]string, error) { return nil, errors.New("Didn't find any match service") } - log.Log("Cannot match any service, getting dynamic...") + log.Info("Cannot match any service, getting dynamic...") return n.getDynamic(HttpClient, ctx) } + +type ApiError struct { + Error string `json:"error"` +} + +func init() { + zip.Verbose = false +} + +// DownloadGopm downloads remote package from gopm registry. +func (n *Node) DownloadGopm(ctx *cli.Context) error { + req, err := http.NewRequest("GET", fmt.Sprintf("%s%s?pkgname=%s&revision=%s", + setting.RegistryUrl, setting.URL_API_DOWNLOAD, n.RootPath, n.Value), nil) + if err != nil { + return fmt.Errorf("fail to new request: %v", err) + } + req.Header.Set("User-Agent", base.UserAgent) + resp, err := HttpClient.Do(req) + if err != nil { + return fmt.Errorf("fail to make request: %v", err) + } + if resp.StatusCode != 200 { + var apiErr ApiError + if err = json.NewDecoder(resp.Body).Decode(&apiErr); err != nil { + return fmt.Errorf("fail to decode response JSON: %v", err) + } + return errors.New(apiErr.Error) + } + + tmpPath := path.Join(setting.HomeDir, ".gopm/temp/archive", + n.RootPath+"-"+base.ToStr(time.Now().Nanosecond())+".zip") + defer os.Remove(tmpPath) + if setting.Debug { + log.Debug("Temp archive path: %s", tmpPath) + } + + os.MkdirAll(path.Dir(tmpPath), os.ModePerm) + fw, err := os.Create(tmpPath) + if err != nil { + return err + } + defer fw.Close() + if _, err = io.Copy(fw, resp.Body); err != nil { + return fmt.Errorf("fail to save archive: %v", err) + } + + // Remove old files. + os.RemoveAll(n.InstallPath) + os.MkdirAll(path.Dir(n.InstallPath), os.ModePerm) + + var rootDir string + var extractFn = func(fullName string, fi os.FileInfo) error { + if len(rootDir) == 0 { + rootDir = strings.Split(fullName, "/")[0] + } + return nil + } + + if err := zip.ExtractToFunc(tmpPath, path.Dir(n.InstallPath), extractFn); err != nil { + return fmt.Errorf("fail to extract archive: %v", err) + } else if err = os.Rename(path.Join(path.Dir(n.InstallPath), rootDir), + n.InstallPath); err != nil { + return fmt.Errorf("fail to rename directory: %v", err) + } + // fmt.Println(rootDir) + return nil +} diff --git a/modules/doc/utils.go b/modules/doc/utils.go index 8a375a0..2402b13 100644 --- a/modules/doc/utils.go +++ b/modules/doc/utils.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -19,22 +19,14 @@ import ( "go/build" "os" "path" - "regexp" "runtime" "strings" - "github.com/Unknwon/com" - + "github.com/gpmgo/gopm/modules/base" "github.com/gpmgo/gopm/modules/log" "github.com/gpmgo/gopm/modules/setting" ) -const VENDOR = ".vendor" - -var ( - hasGuessedGopath bool -) - // ParseTarget guesses import path of current package // if target is empty. func ParseTarget(target string) string { @@ -42,26 +34,13 @@ func ParseTarget(target string) string { return target } - for _, gopath := range com.GetGOPATHs() { + for _, gopath := range base.GetGOPATHs() { if strings.HasPrefix(setting.WorkDir, gopath) { target = strings.TrimPrefix(setting.WorkDir, path.Join(gopath, "src")+"/") - if !hasGuessedGopath { - hasGuessedGopath = true - log.Log("Guess import path: %s", target) - } + log.Info("Guess import path: %s", target) return target } } - - gopmPrefix := path.Join(setting.HomeDir, ".gopm/repos") - if strings.HasPrefix(setting.WorkDir, gopmPrefix) { - target = strings.TrimPrefix(setting.WorkDir, gopmPrefix+"/") - if !hasGuessedGopath { - hasGuessedGopath = true - log.Log("Guess import path: %s", target) - } - return target - } return "." } @@ -85,413 +64,67 @@ func GetRootPath(name string) string { // IsGoRepoPath returns true if package is from standard library. func IsGoRepoPath(name string) bool { - return com.IsDir(path.Join(runtime.GOROOT(), "src/pkg", name)) + return base.IsDir(path.Join(runtime.GOROOT(), "src/pkg", name)) } -// GetImports returns package denpendencies. -func GetImports(importPath, rootPath, srcPath string, isTest bool) ([]string, error) { - oldGopath := os.Getenv("GOPATH") - +// ListImports checks and returns a list of imports of given import path and options. +func ListImports(importPath, rootPath, vendorPath, srcPath string, isTest bool) ([]string, error) { + oldGOPATH := os.Getenv("GOPATH") sep := ":" if runtime.GOOS == "windows" { sep = ";" } ctxt := build.Default - ctxt.GOPATH = VENDOR + sep + oldGopath + // TODO: support tags. + ctxt.GOPATH = vendorPath + sep + oldGOPATH + if setting.Debug { + log.Debug("Import/root path: %s : %s", importPath, rootPath) + log.Debug("Context GOPATH: %s", ctxt.GOPATH) + log.Debug("Srouce path: %s", srcPath) + } pkg, err := ctxt.Import(importPath, srcPath, build.AllowBinary) if err != nil { if _, ok := err.(*build.NoGoError); !ok { - if setting.LibraryMode { - return nil, fmt.Errorf("Fail to get imports: %v", err) - } - log.Error("", "Fail to get imports:") - log.Fatal("", "\t"+err.Error()) + return nil, fmt.Errorf("fail to get imports(%s): %v", importPath, err) } + log.Warn("Getting imports: %v", err) } rawImports := pkg.Imports - length := len(pkg.Imports) + numImports := len(rawImports) if isTest { rawImports = append(rawImports, pkg.TestImports...) - length += len(pkg.TestImports) + numImports = len(rawImports) } - imports := make([]string, 0, length) + imports := make([]string, 0, numImports) for _, name := range rawImports { if IsGoRepoPath(name) { continue } else if strings.HasPrefix(name, rootPath) { - moreImports, err := GetImports(name, rootPath, name, isTest) + moreImports, err := ListImports(name, rootPath, vendorPath, srcPath, isTest) if err != nil { return nil, err } imports = append(imports, moreImports...) continue } + if setting.Debug { + log.Debug("Found dependency: %s", name) + } imports = append(imports, name) } return imports, nil } -var validTLD = map[string]bool{ - // curl http://data.iana.org/TLD/tlds-alpha-by-domain.txt | sed -e '/#/ d' -e 's/.*/"&": true,/' | tr [:upper:] [:lower:] - ".ac": true, - ".ad": true, - ".ae": true, - ".aero": true, - ".af": true, - ".ag": true, - ".ai": true, - ".al": true, - ".am": true, - ".an": true, - ".ao": true, - ".aq": true, - ".ar": true, - ".arpa": true, - ".as": true, - ".asia": true, - ".at": true, - ".au": true, - ".aw": true, - ".ax": true, - ".az": true, - ".ba": true, - ".bb": true, - ".bd": true, - ".be": true, - ".bf": true, - ".bg": true, - ".bh": true, - ".bi": true, - ".biz": true, - ".bj": true, - ".bm": true, - ".bn": true, - ".bo": true, - ".br": true, - ".bs": true, - ".bt": true, - ".bv": true, - ".bw": true, - ".by": true, - ".bz": true, - ".ca": true, - ".cat": true, - ".cc": true, - ".cd": true, - ".cf": true, - ".cg": true, - ".ch": true, - ".ci": true, - ".ck": true, - ".cl": true, - ".cm": true, - ".cn": true, - ".co": true, - ".com": true, - ".coop": true, - ".cr": true, - ".cu": true, - ".cv": true, - ".cw": true, - ".cx": true, - ".cy": true, - ".cz": true, - ".de": true, - ".dj": true, - ".dk": true, - ".dm": true, - ".do": true, - ".dz": true, - ".ec": true, - ".edu": true, - ".ee": true, - ".eg": true, - ".er": true, - ".es": true, - ".et": true, - ".eu": true, - ".fi": true, - ".fj": true, - ".fk": true, - ".fm": true, - ".fo": true, - ".fr": true, - ".ga": true, - ".gb": true, - ".gd": true, - ".ge": true, - ".gf": true, - ".gg": true, - ".gh": true, - ".gi": true, - ".gl": true, - ".gm": true, - ".gn": true, - ".gov": true, - ".gp": true, - ".gq": true, - ".gr": true, - ".gs": true, - ".gt": true, - ".gu": true, - ".gw": true, - ".gy": true, - ".hk": true, - ".hm": true, - ".hn": true, - ".hr": true, - ".ht": true, - ".hu": true, - ".id": true, - ".ie": true, - ".il": true, - ".im": true, - ".in": true, - ".info": true, - ".int": true, - ".io": true, - ".iq": true, - ".ir": true, - ".is": true, - ".it": true, - ".je": true, - ".jm": true, - ".jo": true, - ".jobs": true, - ".jp": true, - ".ke": true, - ".kg": true, - ".kh": true, - ".ki": true, - ".km": true, - ".kn": true, - ".kp": true, - ".kr": true, - ".kw": true, - ".ky": true, - ".kz": true, - ".la": true, - ".lb": true, - ".lc": true, - ".li": true, - ".lk": true, - ".lr": true, - ".ls": true, - ".lt": true, - ".lu": true, - ".lv": true, - ".ly": true, - ".ma": true, - ".mc": true, - ".md": true, - ".me": true, - ".mg": true, - ".mh": true, - ".mil": true, - ".mk": true, - ".ml": true, - ".mm": true, - ".mn": true, - ".mo": true, - ".mobi": true, - ".mp": true, - ".mq": true, - ".mr": true, - ".ms": true, - ".mt": true, - ".mu": true, - ".museum": true, - ".mv": true, - ".mw": true, - ".mx": true, - ".my": true, - ".mz": true, - ".na": true, - ".name": true, - ".nc": true, - ".ne": true, - ".net": true, - ".nf": true, - ".ng": true, - ".ni": true, - ".nl": true, - ".no": true, - ".np": true, - ".nr": true, - ".nu": true, - ".nz": true, - ".om": true, - ".org": true, - ".pa": true, - ".pe": true, - ".pf": true, - ".pg": true, - ".ph": true, - ".pk": true, - ".pl": true, - ".pm": true, - ".pn": true, - ".post": true, - ".pr": true, - ".pro": true, - ".ps": true, - ".pt": true, - ".pw": true, - ".py": true, - ".qa": true, - ".re": true, - ".ro": true, - ".rs": true, - ".ru": true, - ".rw": true, - ".sa": true, - ".sb": true, - ".sc": true, - ".sd": true, - ".se": true, - ".sg": true, - ".sh": true, - ".si": true, - ".sj": true, - ".sk": true, - ".sl": true, - ".sm": true, - ".sn": true, - ".so": true, - ".sr": true, - ".st": true, - ".su": true, - ".sv": true, - ".sx": true, - ".sy": true, - ".sz": true, - ".tc": true, - ".td": true, - ".tel": true, - ".tf": true, - ".tg": true, - ".th": true, - ".tj": true, - ".tk": true, - ".tl": true, - ".tm": true, - ".tn": true, - ".to": true, - ".tp": true, - ".tr": true, - ".travel": true, - ".tt": true, - ".tv": true, - ".tw": true, - ".tz": true, - ".ua": true, - ".ug": true, - ".uk": true, - ".us": true, - ".uy": true, - ".uz": true, - ".va": true, - ".vc": true, - ".ve": true, - ".vg": true, - ".vi": true, - ".vn": true, - ".vu": true, - ".wf": true, - ".ws": true, - ".xn--0zwm56d": true, - ".xn--11b5bs3a9aj6g": true, - ".xn--3e0b707e": true, - ".xn--45brj9c": true, - ".xn--80akhbyknj4f": true, - ".xn--80ao21a": true, - ".xn--90a3ac": true, - ".xn--9t4b11yi5a": true, - ".xn--clchc0ea0b2g2a9gcd": true, - ".xn--deba0ad": true, - ".xn--fiqs8s": true, - ".xn--fiqz9s": true, - ".xn--fpcrj9c3d": true, - ".xn--fzc2c9e2c": true, - ".xn--g6w251d": true, - ".xn--gecrj9c": true, - ".xn--h2brj9c": true, - ".xn--hgbk6aj7f53bba": true, - ".xn--hlcj6aya9esc7a": true, - ".xn--j6w193g": true, - ".xn--jxalpdlp": true, - ".xn--kgbechtv": true, - ".xn--kprw13d": true, - ".xn--kpry57d": true, - ".xn--lgbbat1ad8j": true, - ".xn--mgb9awbf": true, - ".xn--mgbaam7a8h": true, - ".xn--mgbayh7gpa": true, - ".xn--mgbbh1a71e": true, - ".xn--mgbc0a9azcg": true, - ".xn--mgberp4a5d4ar": true, - ".xn--mgbx4cd0ab": true, - ".xn--o3cw4h": true, - ".xn--ogbpf8fl": true, - ".xn--p1ai": true, - ".xn--pgbs0dh": true, - ".xn--s9brj9c": true, - ".xn--wgbh1c": true, - ".xn--wgbl6a": true, - ".xn--xkc2al3hye2a": true, - ".xn--xkc2dl3a5ee0h": true, - ".xn--yfro4i67o": true, - ".xn--ygbi2ammx": true, - ".xn--zckzah": true, - ".xxx": true, - ".ye": true, - ".yt": true, - ".za": true, - ".zm": true, - ".zw": true, -} - -var validHost = regexp.MustCompile(`^[-a-z0-9]+(?:\.[-a-z0-9]+)+$`) -var validPathElement = regexp.MustCompile(`^[-A-Za-z0-9~+][-A-Za-z0-9_.]*$`) - -func isValidPathElement(s string) bool { - return validPathElement.MatchString(s) && s != "testdata" -} - -// IsValidRemotePath returns true if importPath is structurally valid for "go get". -func IsValidRemotePath(importPath string) bool { - parts := strings.Split(importPath, "/") - if len(parts) <= 1 { - // Import path must contain at least one "/". - return false - } - - if !validTLD[path.Ext(parts[0])] { - return false - } - - if !validHost.MatchString(parts[0]) { - return false - } - - for _, part := range parts[1:] { - if !isValidPathElement(part) { - return false - } - } - return true -} - // GetVcsName checks whether dirPath has .git .hg .svn else return "" func GetVcsName(dirPath string) string { switch { - case com.IsExist(path.Join(dirPath, ".git")): + case base.IsExist(path.Join(dirPath, ".git")): return "git" - case com.IsExist(path.Join(dirPath, ".hg")): + case base.IsExist(path.Join(dirPath, ".hg")): return "hg" - case com.IsExist(path.Join(dirPath, ".svn")): + case base.IsExist(path.Join(dirPath, ".svn")): return "svn" } return "" diff --git a/modules/errors/errors.go b/modules/errors/errors.go index 65de549..feca8f4 100644 --- a/modules/errors/errors.go +++ b/modules/errors/errors.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 diff --git a/modules/goconfig/conf.go b/modules/goconfig/conf.go new file mode 100644 index 0000000..98b0a10 --- /dev/null +++ b/modules/goconfig/conf.go @@ -0,0 +1,531 @@ +// Copyright 2013 Unknwon +// +// 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 goconfig is a fully functional and comments-support configuration file(.ini) parser. +package goconfig + +import ( + "fmt" + "regexp" + "runtime" + "strconv" + "strings" + "sync" +) + +const ( + // Default section name. + DEFAULT_SECTION = "DEFAULT" + // Maximum allowed depth when recursively substituing variable names. + _DEPTH_VALUES = 200 +) + +// Parse error types. +const ( + ErrSectionNotFound = iota + 1 + ErrKeyNotFound + ErrBlankSectionName + ErrCouldNotParse +) + +var LineBreak = "\n" + +// Variable regexp pattern: %(variable)s +var varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`) + +func init() { + if runtime.GOOS == "windows" { + LineBreak = "\r\n" + } +} + +// A ConfigFile represents a INI formar configuration file. +type ConfigFile struct { + lock sync.RWMutex // Go map is not safe. + fileNames []string // Support mutil-files. + data map[string]map[string]string // Section -> key : value + + // Lists can keep sections and keys in order. + sectionList []string // Section name list. + keyList map[string][]string // Section -> Key name list + + sectionComments map[string]string // Sections comments. + keyComments map[string]map[string]string // Keys comments. + BlockMode bool // Indicates whether use lock or not. +} + +// newConfigFile creates an empty configuration representation. +func newConfigFile(fileNames []string) *ConfigFile { + c := new(ConfigFile) + c.fileNames = fileNames + c.data = make(map[string]map[string]string) + c.keyList = make(map[string][]string) + c.sectionComments = make(map[string]string) + c.keyComments = make(map[string]map[string]string) + c.BlockMode = true + return c +} + +// SetValue adds a new section-key-value to the configuration. +// It returns true if the key and value were inserted, +// or returns false if the value was overwritten. +// If the section does not exist in advance, it will be created. +func (c *ConfigFile) SetValue(section, key, value string) bool { + if c.BlockMode { + c.lock.Lock() + defer c.lock.Unlock() + } + + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + + // Check if section exists. + if _, ok := c.data[section]; !ok { + // Execute add operation. + c.data[section] = make(map[string]string) + // Append section to list. + c.sectionList = append(c.sectionList, section) + } + + // Check if key exists. + _, ok := c.data[section][key] + c.data[section][key] = value + if !ok { + // If not exists, append to key list. + c.keyList[section] = append(c.keyList[section], key) + } + return !ok +} + +// DeleteKey deletes the key in given section. +// It returns true if the key was deleted, +// or returns false if the section or key didn't exist. +func (c *ConfigFile) DeleteKey(section, key string) bool { + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + + // Check if section exists. + if _, ok := c.data[section]; !ok { + return false + } + + // Check if key exists. + if _, ok := c.data[section][key]; ok { + delete(c.data[section], key) + // Remove comments of key. + c.SetKeyComments(section, key, "") + // Get index of key. + i := 0 + for _, keyName := range c.keyList[section] { + if keyName == key { + break + } + i++ + } + // Remove from key list. + c.keyList[section] = append(c.keyList[section][:i], c.keyList[section][i+1:]...) + return true + } + return false +} + +// GetValue returns the value of key available in the given section. +// If the value needs to be unfolded +// (see e.g. %(google)s example in the GoConfig_test.go), +// then String does this unfolding automatically, up to +// _DEPTH_VALUES number of iterations. +// It returns an error and empty string value if the section does not exist, +// or key does not exist in DEFAULT and current sections. +func (c *ConfigFile) GetValue(section, key string) (string, error) { + if c.BlockMode { + c.lock.RLock() + defer c.lock.RUnlock() + } + + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + + // Check if section exists + if _, ok := c.data[section]; !ok { + // Section does not exist. + return "", getError{ErrSectionNotFound, section} + } + + // Section exists. + // Check if key exists or empty value. + value, ok := c.data[section][key] + if !ok { + // Check if it is a sub-section. + if i := strings.LastIndex(section, "."); i > -1 { + return c.GetValue(section[:i], key) + } + + // Return empty value. + return "", getError{ErrKeyNotFound, key} + } + + // Key exists. + var i int + for i = 0; i < _DEPTH_VALUES; i++ { + vr := varPattern.FindString(value) + if len(vr) == 0 { + break + } + + // Take off leading '%(' and trailing ')s'. + noption := strings.TrimLeft(vr, "%(") + noption = strings.TrimRight(noption, ")s") + + // Search variable in default section. + nvalue, err := c.GetValue(DEFAULT_SECTION, noption) + if err != nil && section != DEFAULT_SECTION { + // Search in the same section. + if _, ok := c.data[section][noption]; ok { + nvalue = c.data[section][noption] + } + } + + // Substitute by new value and take off leading '%(' and trailing ')s'. + value = strings.Replace(value, vr, nvalue, -1) + } + return value, nil +} + +// Bool returns bool type value. +func (c *ConfigFile) Bool(section, key string) (bool, error) { + value, err := c.GetValue(section, key) + if err != nil { + return false, err + } + return strconv.ParseBool(value) +} + +// Float64 returns float64 type value. +func (c *ConfigFile) Float64(section, key string) (float64, error) { + value, err := c.GetValue(section, key) + if err != nil { + return 0.0, err + } + return strconv.ParseFloat(value, 64) +} + +// Int returns int type value. +func (c *ConfigFile) Int(section, key string) (int, error) { + value, err := c.GetValue(section, key) + if err != nil { + return 0, err + } + return strconv.Atoi(value) +} + +// Int64 returns int64 type value. +func (c *ConfigFile) Int64(section, key string) (int64, error) { + value, err := c.GetValue(section, key) + if err != nil { + return 0, err + } + return strconv.ParseInt(value, 10, 64) +} + +// MustValue always returns value without error. +// It returns empty string if error occurs, or the default value if given. +func (c *ConfigFile) MustValue(section, key string, defaultVal ...string) string { + val, err := c.GetValue(section, key) + if len(defaultVal) > 0 && (err != nil || len(val) == 0) { + return defaultVal[0] + } + return val +} + +// MustValue always returns value without error, +// It returns empty string if error occurs, or the default value if given, +// and a bool value indicates whether default value is returned. +func (c *ConfigFile) MustValueSet(section, key string, defaultVal ...string) (string, bool) { + val, err := c.GetValue(section, key) + if len(defaultVal) > 0 && (err != nil || len(val) == 0) { + c.SetValue(section, key, defaultVal[0]) + return defaultVal[0], true + } + return val, false +} + +// MustValueRange always returns value without error, +// it returns default value if error occurs or doesn't fit into range. +func (c *ConfigFile) MustValueRange(section, key, defaultVal string, candidates []string) string { + val, err := c.GetValue(section, key) + if err != nil || len(val) == 0 { + return defaultVal + } + + for _, cand := range candidates { + if val == cand { + return val + } + } + return defaultVal +} + +// MustValueArray always returns value array without error, +// it returns empty array if error occurs, split by delimiter otherwise. +func (c *ConfigFile) MustValueArray(section, key, delim string) []string { + val, err := c.GetValue(section, key) + if err != nil || len(val) == 0 { + return []string{} + } + + vals := strings.Split(val, delim) + for i := range vals { + vals[i] = strings.TrimSpace(vals[i]) + } + return vals +} + +// MustBool always returns value without error, +// it returns false if error occurs. +func (c *ConfigFile) MustBool(section, key string, defaultVal ...bool) bool { + val, err := c.Bool(section, key) + if len(defaultVal) > 0 && err != nil { + return defaultVal[0] + } + return val +} + +// MustFloat64 always returns value without error, +// it returns 0.0 if error occurs. +func (c *ConfigFile) MustFloat64(section, key string, defaultVal ...float64) float64 { + value, err := c.Float64(section, key) + if len(defaultVal) > 0 && err != nil { + return defaultVal[0] + } + return value +} + +// MustInt always returns value without error, +// it returns 0 if error occurs. +func (c *ConfigFile) MustInt(section, key string, defaultVal ...int) int { + value, err := c.Int(section, key) + if len(defaultVal) > 0 && err != nil { + return defaultVal[0] + } + return value +} + +// MustInt64 always returns value without error, +// it returns 0 if error occurs. +func (c *ConfigFile) MustInt64(section, key string, defaultVal ...int64) int64 { + value, err := c.Int64(section, key) + if len(defaultVal) > 0 && err != nil { + return defaultVal[0] + } + return value +} + +// GetSectionList returns the list of all sections +// in the same order in the file. +func (c *ConfigFile) GetSectionList() []string { + list := make([]string, len(c.sectionList)) + copy(list, c.sectionList) + return list +} + +// GetKeyList returns the list of all keys in give section +// in the same order in the file. +// It returns nil if given section does not exist. +func (c *ConfigFile) GetKeyList(section string) []string { + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + + // Check if section exists. + if _, ok := c.data[section]; !ok { + return nil + } + + // Non-default section has a blank key as section keeper. + offset := 1 + if section == DEFAULT_SECTION { + offset = 0 + } + + list := make([]string, len(c.keyList[section])-offset) + copy(list, c.keyList[section][offset:]) + return list +} + +// DeleteSection deletes the entire section by given name. +// It returns true if the section was deleted, and false if the section didn't exist. +func (c *ConfigFile) DeleteSection(section string) bool { + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + + // Check if section exists. + if _, ok := c.data[section]; !ok { + return false + } + + delete(c.data, section) + // Remove comments of section. + c.SetSectionComments(section, "") + // Get index of section. + i := 0 + for _, secName := range c.sectionList { + if secName == section { + break + } + i++ + } + // Remove from section list. + c.sectionList = append(c.sectionList[:i], c.sectionList[i+1:]...) + return true +} + +// GetSection returns key-value pairs in given section. +// It section does not exist, returns nil and error. +func (c *ConfigFile) GetSection(section string) (map[string]string, error) { + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + + // Check if section exists. + if _, ok := c.data[section]; !ok { + // Section does not exist. + return nil, getError{ErrSectionNotFound, section} + } + + // Remove pre-defined key. + secMap := c.data[section] + delete(c.data[section], " ") + + // Section exists. + return secMap, nil +} + +// SetSectionComments adds new section comments to the configuration. +// If comments are empty(0 length), it will remove its section comments! +// It returns true if the comments were inserted or removed, +// or returns false if the comments were overwritten. +func (c *ConfigFile) SetSectionComments(section, comments string) bool { + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + + if len(comments) == 0 { + if _, ok := c.sectionComments[section]; ok { + delete(c.sectionComments, section) + } + + // Not exists can be seen as remove. + return true + } + + // Check if comments exists. + _, ok := c.sectionComments[section] + if comments[0] != '#' && comments[0] != ';' { + comments = "; " + comments + } + c.sectionComments[section] = comments + return !ok +} + +// SetKeyComments adds new section-key comments to the configuration. +// If comments are empty(0 length), it will remove its section-key comments! +// It returns true if the comments were inserted or removed, +// or returns false if the comments were overwritten. +// If the section does not exist in advance, it is created. +func (c *ConfigFile) SetKeyComments(section, key, comments string) bool { + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + + // Check if section exists. + if _, ok := c.keyComments[section]; ok { + if len(comments) == 0 { + if _, ok := c.keyComments[section][key]; ok { + delete(c.keyComments[section], key) + } + + // Not exists can be seen as remove. + return true + } + } else { + if len(comments) == 0 { + // Not exists can be seen as remove. + return true + } else { + // Execute add operation. + c.keyComments[section] = make(map[string]string) + } + } + + // Check if key exists. + _, ok := c.keyComments[section][key] + if comments[0] != '#' && comments[0] != ';' { + comments = "; " + comments + } + c.keyComments[section][key] = comments + return !ok +} + +// GetSectionComments returns the comments in the given section. +// It returns an empty string(0 length) if the comments do not exist. +func (c *ConfigFile) GetSectionComments(section string) (comments string) { + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + return c.sectionComments[section] +} + +// GetKeyComments returns the comments of key in the given section. +// It returns an empty string(0 length) if the comments do not exist. +func (c *ConfigFile) GetKeyComments(section, key string) (comments string) { + // Blank section name represents DEFAULT section. + if len(section) == 0 { + section = DEFAULT_SECTION + } + + if _, ok := c.keyComments[section]; ok { + return c.keyComments[section][key] + } + return "" +} + +// getError occurs when get value in configuration file with invalid parameter. +type getError struct { + Reason int + Name string +} + +// Error implements Error interface. +func (err getError) Error() string { + switch err.Reason { + case ErrSectionNotFound: + return fmt.Sprintf("section '%s' not found", err.Name) + case ErrKeyNotFound: + return fmt.Sprintf("key '%s' not found", err.Name) + } + return "invalid get error" +} diff --git a/modules/goconfig/read.go b/modules/goconfig/read.go new file mode 100644 index 0000000..20e178a --- /dev/null +++ b/modules/goconfig/read.go @@ -0,0 +1,251 @@ +// Copyright 2013 Unknwon +// +// 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 goconfig + +import ( + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "strings" + "time" +) + +// Read reads an io.Reader and returns a configuration representation. +// This representation can be queried with GetValue. +func (c *ConfigFile) read(reader io.Reader) (err error) { + buf := bufio.NewReader(reader) + + count := 1 // Counter for auto increment. + // Current section name. + section := DEFAULT_SECTION + var comments string + // Parse line-by-line + for { + line, err := buf.ReadString('\n') + line = strings.TrimSpace(line) + lineLengh := len(line) //[SWH|+] + if err != nil { + if err != io.EOF { + return err + } + + // Reached end of file, if nothing to read then break, + // otherwise handle the last line. + if lineLengh == 0 { + break + } + } + + // switch written for readability (not performance) + switch { + case lineLengh == 0: // Empty line + continue + case line[0] == '#' || line[0] == ';': // Comment + // Append comments + if len(comments) == 0 { + comments = line + } else { + comments += LineBreak + line + } + continue + case line[0] == '[' && line[lineLengh-1] == ']': // New sction. + // Get section name. + section = strings.TrimSpace(line[1 : lineLengh-1]) + // Set section comments and empty if it has comments. + if len(comments) > 0 { + c.SetSectionComments(section, comments) + comments = "" + } + // Make section exist even though it does not have any key. + c.SetValue(section, " ", " ") + // Reset counter. + count = 1 + continue + case section == "": // No section defined so far + return readError{ErrBlankSectionName, line} + default: // Other alternatives + var ( + i int + keyQuote string + key string + valQuote string + value string + ) + //[SWH|+]:支持引号包围起来的字串 + if line[0] == '"' { + if lineLengh >= 6 && line[0:3] == `"""` { + keyQuote = `"""` + } else { + keyQuote = `"` + } + } else if line[0] == '`' { + keyQuote = "`" + } + if keyQuote != "" { + qLen := len(keyQuote) + pos := strings.Index(line[qLen:], keyQuote) + if pos == -1 { + return readError{ErrCouldNotParse, line} + } + pos = pos + qLen + i = strings.IndexAny(line[pos:], "=:") + if i <= 0 { + return readError{ErrCouldNotParse, line} + } + i = i + pos + key = line[qLen:pos] //保留引号内的两端的空格 + } else { + i = strings.IndexAny(line, "=:") + if i <= 0 { + return readError{ErrCouldNotParse, line} + } + key = strings.TrimSpace(line[0:i]) + } + //[SWH|+]; + + // Check if it needs auto increment. + if key == "-" { + key = "#" + fmt.Sprint(count) + count++ + } + + //[SWH|+]:支持引号包围起来的字串 + lineRight := strings.TrimSpace(line[i+1:]) + lineRightLength := len(lineRight) + firstChar := "" + if lineRightLength >= 2 { + firstChar = lineRight[0:1] + } + if firstChar == "`" { + valQuote = "`" + } else if lineRightLength >= 6 && lineRight[0:3] == `"""` { + valQuote = `"""` + } + if valQuote != "" { + qLen := len(valQuote) + pos := strings.LastIndex(lineRight[qLen:], valQuote) + if pos == -1 { + return readError{ErrCouldNotParse, line} + } + pos = pos + qLen + value = lineRight[qLen:pos] + } else { + value = strings.TrimSpace(lineRight[0:]) + } + //[SWH|+]; + + c.SetValue(section, key, value) + // Set key comments and empty if it has comments. + if len(comments) > 0 { + c.SetKeyComments(section, key, comments) + comments = "" + } + } + + // Reached end of file. + if err == io.EOF { + break + } + } + return nil +} + +// LoadFromData accepts raw data directly from memory +// and returns a new configuration representation. +func LoadFromData(data []byte) (c *ConfigFile, err error) { + // Save memory data to temporary file to support further operations. + tmpName := path.Join(os.TempDir(), "goconfig", fmt.Sprintf("%d", time.Now().Nanosecond())) + os.MkdirAll(path.Dir(tmpName), os.ModePerm) + if err = ioutil.WriteFile(tmpName, data, 0655); err != nil { + return nil, err + } + + c = newConfigFile([]string{tmpName}) + err = c.read(bytes.NewBuffer(data)) + return c, err +} + +func (c *ConfigFile) loadFile(fileName string) (err error) { + f, err := os.Open(fileName) + if err != nil { + return err + } + defer f.Close() + + return c.read(f) +} + +// LoadConfigFile reads a file and returns a new configuration representation. +// This representation can be queried with GetValue. +func LoadConfigFile(fileName string, moreFiles ...string) (c *ConfigFile, err error) { + // Append files' name together. + fileNames := make([]string, 1, len(moreFiles)+1) + fileNames[0] = fileName + if len(moreFiles) > 0 { + fileNames = append(fileNames, moreFiles...) + } + + c = newConfigFile(fileNames) + + for _, name := range fileNames { + if err = c.loadFile(name); err != nil { + return nil, err + } + } + + return c, nil +} + +// Reload reloads configuration file in case it has changes. +func (c *ConfigFile) Reload() (err error) { + var cfg *ConfigFile + if len(c.fileNames) == 1 { + cfg, err = LoadConfigFile(c.fileNames[0]) + } else { + cfg, err = LoadConfigFile(c.fileNames[0], c.fileNames[1:]...) + } + + if err == nil { + *c = *cfg + } + return err +} + +// AppendFiles appends more files to ConfigFile and reload automatically. +func (c *ConfigFile) AppendFiles(files ...string) error { + c.fileNames = append(c.fileNames, files...) + return c.Reload() +} + +// readError occurs when read configuration file with wrong format. +type readError struct { + Reason int + Content string // Line content +} + +// Error implement Error interface. +func (err readError) Error() string { + switch err.Reason { + case ErrBlankSectionName: + return "empty section name not allowed" + case ErrCouldNotParse: + return fmt.Sprintf("could not parse line: %s", string(err.Content)) + } + return "invalid read error" +} diff --git a/modules/goconfig/write.go b/modules/goconfig/write.go new file mode 100644 index 0000000..385d516 --- /dev/null +++ b/modules/goconfig/write.go @@ -0,0 +1,108 @@ +// Copyright 2013 Unknwon +// +// 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 goconfig + +import ( + "bytes" + "os" + "strings" +) + +// Write spaces around "=" to look better. +var PrettyFormat = true + +// SaveConfigFile writes configuration file to local file system +func SaveConfigFile(c *ConfigFile, filename string) (err error) { + // Write configuration file by filename. + var f *os.File + if f, err = os.Create(filename); err != nil { + return err + } + + equalSign := "=" + if PrettyFormat { + equalSign = " = " + } + + buf := bytes.NewBuffer(nil) + for _, section := range c.sectionList { + // Write section comments. + if len(c.GetSectionComments(section)) > 0 { + if _, err = buf.WriteString(c.GetSectionComments(section) + LineBreak); err != nil { + return err + } + } + + if section != DEFAULT_SECTION { + // Write section name. + if _, err = buf.WriteString("[" + section + "]" + LineBreak); err != nil { + return err + } + } + + for _, key := range c.keyList[section] { + if key != " " { + // Write key comments. + if len(c.GetKeyComments(section, key)) > 0 { + if _, err = buf.WriteString(c.GetKeyComments(section, key) + LineBreak); err != nil { + return err + } + } + + keyName := key + // Check if it's auto increment. + if keyName[0] == '#' { + keyName = "-" + } + //[SWH|+]:支持键名包含等号和冒号 + if strings.Contains(keyName, `=`) || strings.Contains(keyName, `:`) { + if strings.Contains(keyName, "`") { + if strings.Contains(keyName, `"`) { + keyName = `"""` + keyName + `"""` + } else { + keyName = `"` + keyName + `"` + } + } else { + keyName = "`" + keyName + "`" + } + } + value := c.data[section][key] + // In case key value contains "`" or "\"". + if strings.Contains(value, "`") { + if strings.Contains(value, `"`) { + value = `"""` + value + `"""` + } else { + value = `"` + value + `"` + } + } + + // Write key and value. + if _, err = buf.WriteString(keyName + equalSign + value + LineBreak); err != nil { + return err + } + } + } + + // Put a line between sections. + if _, err = buf.WriteString(LineBreak); err != nil { + return err + } + } + + if _, err = buf.WriteTo(f); err != nil { + return err + } + return f.Close() +} diff --git a/modules/log/log.go b/modules/log/log.go index 9537a7b..24a9511 100644 --- a/modules/log/log.go +++ b/modules/log/log.go @@ -1,6 +1,4 @@ -// +build !windows - -// Copyright 2013 Unknown +// Copyright 2014 Unknwon // // 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 @@ -14,111 +12,102 @@ // License for the specific language governing permissions and limitations // under the License. -// Package log provides npm-like style log output. package log import ( "fmt" "io" "os" + "runtime" + "time" +) - "github.com/aybabtme/color/brush" +const ( + PREFIX = "[GOPM]" + TIME_FORMAT = "06-01-02 15:04:05" ) -var Output io.Writer = os.Stdout +var ( + Verbose, NonColor bool + Output io.Writer = os.Stdout -func Error(hl, msg string) { - if PureMode { - errorP(hl, msg) - } + LEVEL_FLAGS = [...]string{"DEBUG", " INFO", " WARN", "ERROR", "FATAL"} +) - if len(hl) > 0 { - hl = " " + brush.Red(hl).String() +func init() { + if runtime.GOOS == "windows" { + NonColor = true } - fmt.Fprintf(Output, "gopm %s%s %s\n", brush.Red("ERR!"), hl, msg) } -func Fatal(hl, msg string) { - if PureMode { - fatal(hl, msg) - } - - Error(hl, msg) - os.Exit(2) -} +const ( + DEBUG = iota + INFO + WARNING + ERROR + FATAL +) -func Warn(format string, args ...interface{}) { - if PureMode { - warn(format, args...) +func Print(level int, format string, args ...interface{}) { + if !Verbose && level < WARNING { return } - fmt.Fprintf(Output, "gopm %s %s\n", brush.Purple("WARN"), - fmt.Sprintf(format, args...)) -} - -func Log(format string, args ...interface{}) { - if PureMode { - log(format, args...) + if NonColor { + fmt.Fprintf(Output, "%s %s [%s] %s\n", + PREFIX, time.Now().Format(TIME_FORMAT), LEVEL_FLAGS[level], + fmt.Sprintf(format, args...)) + if level == FATAL { + os.Exit(1) + } return } - if !Verbose { - return + switch level { + case DEBUG: + fmt.Fprintf(Output, "%s \033[36m%s\033[0m [\033[34m%s\033[0m] %s\n", + PREFIX, time.Now().Format(TIME_FORMAT), LEVEL_FLAGS[level], + fmt.Sprintf(format, args...)) + case INFO: + fmt.Fprintf(Output, "%s \033[36m%s\033[0m [\033[32m%s\033[0m] %s\n", + PREFIX, time.Now().Format(TIME_FORMAT), LEVEL_FLAGS[level], + fmt.Sprintf(format, args...)) + case WARNING: + fmt.Fprintf(Output, "%s \033[36m%s\033[0m [\033[33m%s\033[0m] %s\n", + PREFIX, time.Now().Format(TIME_FORMAT), LEVEL_FLAGS[level], + fmt.Sprintf(format, args...)) + case ERROR: + fmt.Fprintf(Output, "%s \033[36m%s\033[0m [\033[31m%s\033[0m] %s\n", + PREFIX, time.Now().Format(TIME_FORMAT), LEVEL_FLAGS[level], + fmt.Sprintf(format, args...)) + case FATAL: + fmt.Fprintf(Output, "%s \033[36m%s\033[0m [\033[35m%s\033[0m] %s\n", + PREFIX, time.Now().Format(TIME_FORMAT), LEVEL_FLAGS[level], + fmt.Sprintf(format, args...)) + os.Exit(1) + default: + fmt.Fprintf(Output, "%s %s [%s] %s\n", + PREFIX, time.Now().Format(TIME_FORMAT), LEVEL_FLAGS[level], + fmt.Sprintf(format, args...)) } - fmt.Fprintf(Output, "gopm %s %s\n", brush.White("INFO"), - fmt.Sprintf(format, args...)) } -func Trace(format string, args ...interface{}) { - if PureMode { - trace(format, args...) - return - } - - if !Verbose { - return - } - fmt.Fprintf(Output, "gopm %s %s\n", brush.Blue("TRAC"), - fmt.Sprintf(format, args...)) +func Debug(format string, args ...interface{}) { + Print(DEBUG, format, args...) } -func Success(title, hl, msg string) { - if PureMode { - success(title, hl, msg) - return - } - - if !Verbose { - return - } - if len(hl) > 0 { - hl = " " + brush.Green(hl).String() - } - fmt.Fprintf(Output, "gopm %s%s %s\n", brush.Green(title), hl, msg) +func Warn(format string, args ...interface{}) { + Print(WARNING, format, args...) } -func Message(hl, msg string) { - if PureMode { - message(hl, msg) - return - } - - if !Verbose { - return - } - if len(hl) > 0 { - hl = " " + brush.Yellow(hl).String() - } - fmt.Fprintf(Output, "gopm %s%s %s\n", brush.Yellow("MSG!"), hl, msg) +func Info(format string, args ...interface{}) { + Print(INFO, format, args...) } -func Help(format string, args ...interface{}) { - if PureMode { - help(format, args...) - } +func Error(format string, args ...interface{}) { + Print(ERROR, format, args...) +} - fmt.Fprintf(Output, "gopm %s %s\n", brush.Cyan("HELP"), - fmt.Sprintf(format, args...)) - os.Exit(2) +func Fatal(format string, args ...interface{}) { + Print(FATAL, format, args...) } diff --git a/modules/log/logP.go b/modules/log/logP.go deleted file mode 100644 index ce6c068..0000000 --- a/modules/log/logP.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2013 Unknown -// -// 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 log - -import ( - "fmt" - "os" -) - -var ( - PureMode = false - Verbose = false -) - -func errorP(hl, msg string) { - if len(hl) > 0 { - hl = " " + hl - } - fmt.Printf("gopm ERR!%s %s\n", hl, msg) -} - -func fatal(hl, msg string) { - errorP(hl, msg) - os.Exit(2) -} - -func warn(format string, args ...interface{}) { - fmt.Printf("gopm WARN %s\n", fmt.Sprintf(format, args...)) -} - -func log(format string, args ...interface{}) { - if !Verbose { - return - } - fmt.Printf("gopm INFO %s\n", fmt.Sprintf(format, args...)) -} - -func trace(format string, args ...interface{}) { - if !Verbose { - return - } - fmt.Printf("gopm TRAC %s\n", fmt.Sprintf(format, args...)) -} - -func success(title, hl, msg string) { - if !Verbose { - return - } - if len(hl) > 0 { - hl = " " + hl - } - fmt.Printf("gopm %s%s %s\n", title, hl, msg) -} - -func message(hl, msg string) { - if !Verbose { - return - } - if len(hl) > 0 { - hl = " " + hl - } - fmt.Printf("gopm MSG!%s %s\n", hl, msg) -} - -func help(format string, args ...interface{}) { - fmt.Printf("gopm HELP %s\n", fmt.Sprintf(format, args...)) - os.Exit(2) -} diff --git a/modules/log/log_windows.go b/modules/log/log_windows.go deleted file mode 100644 index f6313a5..0000000 --- a/modules/log/log_windows.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2013 Unknown -// -// 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 log provides npm-like style log output. -package log - -func Error(hl, msg string) { - errorP(hl, msg) -} - -func Fatal(hl, msg string) { - fatal(hl, msg) -} - -func Warn(format string, args ...interface{}) { - warn(format, args...) -} - -func Log(format string, args ...interface{}) { - log(format, args...) -} - -func Trace(format string, args ...interface{}) { - trace(format, args...) -} - -func Success(title, hl, msg string) { - success(title, hl, msg) -} - -func Message(hl, msg string) { - message(hl, msg) -} - -func Help(format string, args ...interface{}) { - help(format, args...) -} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 0afa6f4..e2e3854 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1,4 +1,4 @@ -// Copyright 2014 Unknown +// Copyright 2014 Unknwon // // 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 @@ -21,17 +21,8 @@ import ( "path" "strings" - "github.com/Unknwon/com" - "github.com/Unknwon/goconfig" - - "github.com/gpmgo/gopm/modules/log" -) - -const ( - GOPMFILE = ".gopmfile" - VERINFO = "data/VERSION.json" - - VERSION = 201406081 + "github.com/gpmgo/gopm/modules/base" + "github.com/gpmgo/gopm/modules/goconfig" ) type Error struct { @@ -40,30 +31,51 @@ type Error struct { Errors []error } -var ( - Debug bool - - HomeDir string - WorkDir string // The path of gopm was executed. - InstallRepoPath string // The gopm local repository. - InstallGopath string +type Option struct { +} - GopmTempPath string +const ( + VERSION = 201406081 + VENDOR = ".vendor" + GOPMFILE = ".gopmfile" + PKGNAMELIST = "pkgname.list" + VERINFO = "data/VERSION.json" +) - LocalNodesFile string - LocalNodes *goconfig.ConfigFile +const ( + URL_API_DOWNLOAD = "/api/v1/download" +) - PkgNamesFile string +var ( + // Global variables. + HomeDir string + WorkDir string // The path of gopm was executed. + PkgNameListFile string + LocalNodesFile string + DefaultVendor string + DefaultVendorSrc string + InstallRepoPath string // The gopm local repository. + InstallGopath string + HttpProxy string + RegistryUrl string = "http://gopm.io" + + // System settings. + IsWindows bool + IsWindowsXP bool + HasGOPATHSetting bool + + // Global settings. + Debug bool + LibraryMode bool + RuntimeError = new(Error) + + // Configuration settings. + ConfigFile string + Cfg *goconfig.ConfigFile PackageNameList = make(map[string]string) + LocalNodes *goconfig.ConfigFile - ConfigFile string - Cfg *goconfig.ConfigFile - - IsWindows bool - IsWindowsXP bool - - LibraryMode bool - RuntimeError = new(Error) + // TODO: configurable. RootPathPairs = map[string]int{ "github.com": 3, "code.google.com": 3, @@ -72,51 +84,76 @@ var ( "gitcafe.com": 3, "launchpad.net": 2, "labix.org": 3, - "gopm.io": 3, } CommonRes = []string{"views", "templates", "static", "public", "conf"} ) -func LoadLocalNodes() (err error) { - if !com.IsFile(LocalNodesFile) { - os.MkdirAll(path.Dir(LocalNodesFile), os.ModePerm) - os.Create(LocalNodesFile) +// LoadGopmfile loads and returns given gopmfile. +func LoadGopmfile(fileName string) (*goconfig.ConfigFile, error) { + if !base.IsFile(fileName) { + return goconfig.LoadFromData([]byte("")) } - LocalNodes, err = goconfig.LoadConfigFile(LocalNodesFile) + gf, err := goconfig.LoadConfigFile(fileName) if err != nil { - if LibraryMode { - return fmt.Errorf("Fail to load localnodes.list: %v", err) - } - log.Error("", "Fail to load localnodes.list:") - log.Fatal("", "\t"+err.Error()) + return nil, fmt.Errorf("Fail to load gopmfile: %v", err) + } + return gf, nil +} + +// SaveGopmfile saves gopmfile to given path. +func SaveGopmfile(gf *goconfig.ConfigFile, fileName string) error { + if err := goconfig.SaveConfigFile(gf, fileName); err != nil { + return fmt.Errorf("Fail to save gopmfile: %v", err) } return nil } -func SaveLocalNodes() error { - if err := goconfig.SaveConfigFile(LocalNodes, LocalNodesFile); err != nil { - if LibraryMode { - return fmt.Errorf("Fail to save localnodes.list: %v", err) +// LoadConfig loads gopm global configuration. +func LoadConfig() (err error) { + if !base.IsExist(ConfigFile) { + os.MkdirAll(path.Dir(ConfigFile), os.ModePerm) + if _, err = os.Create(ConfigFile); err != nil { + return fmt.Errorf("fail to create config file: %v", err) } - log.Error("", "Fail to save localnodes.list:") - log.Error("", "\t"+err.Error()) } + + Cfg, err = goconfig.LoadConfigFile(ConfigFile) + if err != nil { + return fmt.Errorf("fail to load config file: %v", err) + } + + HttpProxy = Cfg.MustValue("settings", "HTTP_PROXY") return nil } +// SetConfigValue sets and saves gopm configuration. +func SetConfigValue(section, key, val string) error { + Cfg.SetValue(section, key, val) + if err := goconfig.SaveConfigFile(Cfg, ConfigFile); err != nil { + return fmt.Errorf("fail to set config value(%s:%s=%s): %v", section, key, val, err) + } + return nil +} + +// DeleteConfigOption deletes and saves gopm configuration. +func DeleteConfigOption(section, key string) error { + Cfg.DeleteKey(section, key) + if err := goconfig.SaveConfigFile(Cfg, ConfigFile); err != nil { + return fmt.Errorf("fail to delete config key(%s:%s): %v", section, key, err) + } + return nil +} + +// LoadPkgNameList loads package name pairs. func LoadPkgNameList() error { - if !com.IsFile(PkgNamesFile) { + if !base.IsFile(PkgNameListFile) { return nil } - data, err := ioutil.ReadFile(PkgNamesFile) + data, err := ioutil.ReadFile(PkgNameListFile) if err != nil { - if LibraryMode { - return fmt.Errorf("Fail to load pkgname.list: %v", err) - } - log.Error("", "Fail to load pkgname.list:") - log.Fatal("", "\t"+err.Error()) + return fmt.Errorf("fail to load package name list: %v", err) } pkgs := strings.Split(string(data), "\n") @@ -127,68 +164,38 @@ func LoadPkgNameList() error { if i == len(pkgs)-1 { continue } - if LibraryMode { - return fmt.Errorf("Fail to parse package name: %v", line) - } - log.Error("", "Fail to parse package name: "+line) - log.Fatal("", "Invalid package information") + return fmt.Errorf("fail to parse package name: %v", line) } PackageNameList[strings.TrimSpace(infos[0])] = strings.TrimSpace(infos[1]) } return nil } -func GetPkgFullPath(short string) string { +// GetPkgFullPath attmpts to get full path by given package short name. +func GetPkgFullPath(short string) (string, error) { name, ok := PackageNameList[short] if !ok { - log.Error("", "Invalid package name") - log.Error("", "It's not a invalid import path and no match in the package name list:") - log.Fatal("", "\t"+short) + return "", fmt.Errorf("no match package import path with given short name: %s", short) } - return name + return name, nil } -var ( - HttpProxy string -) - -func LoadConfig() error { - var err error - if !com.IsExist(ConfigFile) { - os.MkdirAll(path.Dir(ConfigFile), os.ModePerm) - if _, err = os.Create(ConfigFile); err != nil { - if LibraryMode { - return fmt.Errorf("Fail to create gopm config file: %v", err) - } - log.Error("", "Fail to create gopm config file:") - log.Fatal("", "\t"+err.Error()) - } +func LoadLocalNodes() (err error) { + if !base.IsFile(LocalNodesFile) { + os.MkdirAll(path.Dir(LocalNodesFile), os.ModePerm) + os.Create(LocalNodesFile) } - Cfg, err = goconfig.LoadConfigFile(ConfigFile) + + LocalNodes, err = goconfig.LoadConfigFile(LocalNodesFile) if err != nil { - if LibraryMode { - return fmt.Errorf("Fail to load gopm config file: %v", err) - } - log.Error("", "Fail to load gopm config file") - log.Fatal("", "\t"+err.Error()) + return fmt.Errorf("fail to load localnodes.list: %v", err) } - - HttpProxy = Cfg.MustValue("settings", "HTTP_PROXY") return nil } -func SetConfigValue(section, key, val string) { - Cfg.SetValue(section, key, val) - if err := goconfig.SaveConfigFile(Cfg, ConfigFile); err != nil { - log.Error("", "Fail to save gopm config file:") - log.Fatal("", "\t"+err.Error()) - } -} - -func DeleteConfigOption(section, key string) { - Cfg.DeleteKey(section, key) - if err := goconfig.SaveConfigFile(Cfg, ConfigFile); err != nil { - log.Error("", "Fail to save gopm config file:") - log.Fatal("", "\t"+err.Error()) +func SaveLocalNodes() error { + if err := goconfig.SaveConfigFile(LocalNodes, LocalNodesFile); err != nil { + return fmt.Errorf("fail to save localnodes.list: %v", err) } + return nil }