diff --git a/.travis.yml b/.travis.yml index 4af3b144..beb220fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,12 +6,13 @@ matrix: go: 1.9 - os: linux go: tip - - os: osx - osx_image: xcode8.3 - go: 1.9 - - os: osx - osx_image: xcode8.3 - go: tip + # macOS build *extraordinary* slow, disable them + # - os: osx + # osx_image: xcode8.3 + # go: 1.9 + # - os: osx + # osx_image: xcode8.3 + # go: tip script: - go vet -v ./... diff --git a/CMDREF.md b/CMDREF.md index 4c2115b9..4a0a96a7 100644 --- a/CMDREF.md +++ b/CMDREF.md @@ -57,13 +57,13 @@ Description ``` Usage - volt get [-help] [-l] [-u] [-v] [{repository} ...] + volt get [-help] [-l] [-u] [{repository} ...] Quick example $ volt get tyru/caw.vim # will install tyru/caw.vim plugin $ volt get -u tyru/caw.vim # will upgrade tyru/caw.vim plugin $ volt get -l -u # will upgrade all installed plugins - $ volt get -v tyru/caw.vim # will output more verbosely + $ VOLT_DEBUG=1 volt get tyru/caw.vim # will output more verbosely $ mkdir -p ~/volt/repos/localhost/local/hello/plugin $ echo 'command! Hello echom "hello"' >~/volt/repos/localhost/local/hello/plugin/hello.vim @@ -78,8 +78,6 @@ Description and install it to: $VOLTPATH/plugconf/{repository}.vim - If -v option was specified, output more verbosely. - Repository List {repository} list (=target to perform installing, upgrading, and so on) is determined as followings: * If -l option is specified, all installed vim plugins (regardless current profile) are used @@ -122,21 +120,97 @@ Repository path Options -l use all installed repositories as targets -u upgrade repositories - -v output more verbosely ``` # volt list ``` Usage - volt list [-help] + volt list [-help] [-f {text/template string}] Quick example $ volt list # will list installed plugins + Show all installed repositories: + + $ volt list -f '{{ range .Repos }}{{ println .Path }}{{ end }}' + + Show repositories used by current profile: + + $ volt list -f '{{ range .Profiles }}{{ if eq $.CurrentProfileName .Name }}{{ range .ReposPath }}{{ . }}{{ end }}{{ end }}{{ end }}' + + Or (see "Additional property"): + + $ volt list -f '{{ range currentProfile.ReposPath }}{{ println . }}{{ end }}' + +Template functions + + json value [prefix [indent]] (string) + Returns JSON representation of value. + The argument is same as json.MarshalIndent(). + + currentProfile (Profile (see "Structures")) + Returns current profile + + currentProfile (Profile (see "Structures")) + Returns given name's profile + + version (string) + Returns volt version string. format is "v{major}.{minor}.{patch}" (e.g. "v0.3.0") + + versionMajor (number) + Returns volt major version + + versionMinor (number) + Returns volt minor version + + versionPatch (number) + Returns volt patch version + +Structures + This describes the structure of lock.json . + { + // lock.json structure compatibility version + "version": , + + // Unique number of transaction + "trx_id": , + + // Current profile name (e.g. "default") + "current_profile_name": , + + // All Installed repositories + // ("volt list" shows current profile's repositories, which is not the same as this) + "repos": [ + { + // "git" (git repository) or "static" (static repository) + "type": , + + // Unique number of transaction + "trx_id": , + + // Repository path like "github.com/vim-volt/vim-volt" + "path": , + + // Git commit hash. if "type" is "static" this property does not exist + "version": , + }, + ], + + // Profiles + "profiles": [ + // Profile name (.e.g. "default") + "name": , + + // Repositories ("volt list" shows these repositories) + "repos_path": [ ], + ] + } + Description - This is shortcut of: - volt profile show {current profile} + Vim plugin information extractor. + If -f flag is not given, this command shows vim plugins of **current profile** (not all installed plugins) by default. + If -f flag is given, it renders by given template which can access the information of lock.json . ``` # volt migrate @@ -183,10 +257,6 @@ Command profile rm [-current | {name}] {repository} [{repository2} ...] Remove one or more repositories from profile {name}. - profile use [-current | {name}] vimrc [true | false] - profile use [-current | {name}] gvimrc [true | false] - Set vimrc / gvimrc flag to true or false. - Quick example $ volt profile list # default profile is "default" * default @@ -208,9 +278,6 @@ Quick example $ volt profile rm foo tyru/caw.vim # disable loading tyru/caw.vim on "foo" profile $ volt profile destroy foo # will delete profile "foo" - - $ volt profile use -current vimrc false # Disable installing vimrc on current profile on "volt build" - $ volt profile use default gvimrc true # Enable installing gvimrc on profile default on "volt build" ``` # volt rm @@ -226,7 +293,8 @@ Quick example $ volt rm -r -p tyru/caw.vim # Remove tyru/caw.vim plugin from lock.json, and remove repository directory, plugconf Description - Uninstall {repository} on every profile. + Uninstall one or more {repository} from every profile. + This results in removing vim plugins from ~/.vim/pack/volt/opt/ directory. If {repository} is depended by other repositories, this command exits with an error. If -r option was given, remove also repository directories of specified repositories. diff --git a/Makefile b/Makefile index 7d52b75f..fc388db9 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME := volt SRC := $(shell find . -type d -name 'vendor' -prune -o -type f -name '*.go' -print) VERSION := $(shell sed -n -E 's/var voltVersion string = "([^"]+)"/\1/p' cmd/version.go) -RELEASE_LDFLAGS := -extldflags '-static' +RELEASE_LDFLAGS := -s -w -extldflags '-static' RELEASE_OS := linux windows darwin RELEASE_ARCH := amd64 386 @@ -32,17 +32,12 @@ release: $(BIN_DIR)/$(NAME) rm -fr $(DIST_DIR) @for os in $(RELEASE_OS); do \ for arch in $(RELEASE_ARCH); do \ + exe=$(DIST_DIR)/$(NAME)-$(VERSION)-$$os-$$arch; \ if [ $$os = windows ]; then \ - exe=$(DIST_DIR)/$(NAME)-$(VERSION)-$$os-$$arch.exe; \ - echo "Creating $$exe ... (os=$$os, arch=$$arch)"; \ - GOOS=$$os GOARCH=$$arch go build -tags netgo -installsuffix netgo -ldflags "$(RELEASE_LDFLAGS)" -o $$exe; \ - else \ - exe=$(DIST_DIR)/$(NAME)-$(VERSION)-$$os-$$arch; \ - echo "Creating $$exe ... (os=$$os, arch=$$arch)"; \ - GOOS=$$os GOARCH=$$arch go build -tags netgo -installsuffix netgo -ldflags "$(RELEASE_LDFLAGS)" -o $$exe; \ - strip $(DIST_DIR)/$(NAME)-$(VERSION)-$$os-$$arch 2>/dev/null; \ - true; \ + exe=$$exe.exe; \ fi; \ + echo "Creating $$exe ... (os=$$os, arch=$$arch)"; \ + GOOS=$$os GOARCH=$$arch go build -tags netgo -installsuffix netgo -ldflags "$(RELEASE_LDFLAGS)" -o $$exe; \ done; \ done diff --git a/README.md b/README.md index 0a23b83c..42590456 100644 --- a/README.md +++ b/README.md @@ -21,20 +21,20 @@ Usage volt COMMAND ARGS Command - get [-l] [-u] [-v] [{repository} ...] + get [-l] [-u] [{repository} ...] Install or upgrade given {repository} list, or add local {repository} list as plugins - rm {repository} [{repository2} ...] - Uninstall vim plugin and plugconf files + rm [-r] [-p] {repository} [{repository2} ...] + Remove vim plugin from ~/.vim/pack/volt/opt/ directory + + list [-f {text/template string}] + Vim plugin information extractor. + Unless -f flag was given, this command shows vim plugins of **current profile** (not all installed plugins) by default. enable {repository} [{repository2} ...] This is shortcut of: volt profile add -current {repository} [{repository2} ...] - list - This is shortcut of: - volt profile show -current - disable {repository} [{repository2} ...] This is shortcut of: volt profile rm -current {repository} [{repository2} ...] @@ -63,10 +63,6 @@ Command profile rm {name} {repository} [{repository2} ...] Remove one or more repositories to profile - profile use [-current | {name}] vimrc [true | false] - profile use [-current | {name}] gvimrc [true | false] - Set vimrc / gvimrc flag to true or false. - build [-full] Build ~/.vim/pack/volt/ directory @@ -78,7 +74,7 @@ Command version Show volt command version - ``` +``` See [the command reference](https://github.com/vim-volt/volt/blob/master/CMDREF.md) for more details. @@ -175,7 +171,7 @@ This is `$HOME/volt` by default. For example, installing [tyru/caw.vim](https://github.com/tyru/caw.vim) plugin: ``` -$ volt get https://github.com/tyru/caw.vim # most verbose way +$ volt get https://github.com/tyru/caw.vim # most verbose way (but handy when you copy & paste from browser address bar :) $ volt get github.com/tyru/caw.vim # you can omit https:// of repository URL $ volt get tyru/caw.vim # you can omit github.com/ if the repository is on GitHub ``` diff --git a/_scripts/update-readme.go b/_scripts/update-readme.go index ea02a412..9825c12b 100644 --- a/_scripts/update-readme.go +++ b/_scripts/update-readme.go @@ -85,8 +85,21 @@ func Main() int { return 0 } +// start = The first line number **inside** code block +// end = The end of code block line (```) line number func findTopCodeBlockRange(lines []string) (int, int) { - return 6, 80 + start, end := -1, -1 + for i := range lines { + if lines[i] == "```" { + if start == -1 { + start = i + 1 + } else { + end = i + break + } + } + } + return start, end } func getVoltHelpOutput() (string, error) { diff --git a/cmd/build.go b/cmd/build.go index a8a9a469..ba1b8ef3 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -116,13 +116,13 @@ func (cmd *buildCmd) doBuild(full bool) error { // Put repos into map to be able to search with O(1). // Use empty build-info.json map if the -full option was given // because the repos info is unnecessary because it is not referenced. - var buildReposMap map[string]*buildinfo.Repos + var buildReposMap map[pathutil.ReposPath]*buildinfo.Repos optDir := pathutil.VimVoltOptDir() if full { - buildReposMap = make(map[string]*buildinfo.Repos) + buildReposMap = make(map[pathutil.ReposPath]*buildinfo.Repos) logger.Info("Full building " + optDir + " directory ...") } else { - buildReposMap = make(map[string]*buildinfo.Repos, len(buildInfo.Repos)) + buildReposMap = make(map[pathutil.ReposPath]*buildinfo.Repos, len(buildInfo.Repos)) for i := range buildInfo.Repos { repos := &buildInfo.Repos[i] buildReposMap[repos.Path] = repos diff --git a/cmd/build_test.go b/cmd/build_test.go index 0efedd6b..d939be3e 100644 --- a/cmd/build_test.go +++ b/cmd/build_test.go @@ -490,7 +490,7 @@ func voltBuildGitNoVimRepos(t *testing.T, full bool, strategy string) { // =============== setup =============== // testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := []pathutil.ReposPath{"github.com/tyru/caw.vim"} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, strategy) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -536,14 +536,14 @@ func voltBuildGitVimDirOlder(t *testing.T, full bool, strategy string) { // =============== setup =============== // testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := []pathutil.ReposPath{"github.com/tyru/caw.vim"} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, strategy) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") out, err := testutil.RunVolt("build") testutil.SuccessExit(t, out, err) for _, reposPath := range reposPathList { - touchFiles(t, pathutil.FullReposPathOf(reposPath)) + touchFiles(t, pathutil.FullReposPath(reposPath)) } // =============== run =============== // @@ -587,14 +587,14 @@ func voltBuildGitVimDirNewer(t *testing.T, full bool, strategy string) { // =============== setup =============== // testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := []pathutil.ReposPath{"github.com/tyru/caw.vim"} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, strategy) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") out, err := testutil.RunVolt("build") testutil.SuccessExit(t, out, err) for _, reposPath := range reposPathList { - touchFiles(t, pathutil.PackReposPathOf(reposPath)) + touchFiles(t, pathutil.EncodeReposPath(reposPath)) } // =============== run =============== // @@ -639,7 +639,7 @@ func voltBuildStaticNoVimRepos(t *testing.T, full bool, strategy string) { // =============== setup =============== // testutil.SetUpEnv(t) - reposPathList := []string{"localhost/local/hello"} + reposPathList := []pathutil.ReposPath{"localhost/local/hello"} teardown := testutil.SetUpRepos(t, "hello", lockjson.ReposStaticType, reposPathList, strategy) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -685,14 +685,14 @@ func voltBuildStaticVimDirOlder(t *testing.T, full bool, strategy string) { // =============== setup =============== // testutil.SetUpEnv(t) - reposPathList := []string{"localhost/local/hello"} + reposPathList := []pathutil.ReposPath{"localhost/local/hello"} teardown := testutil.SetUpRepos(t, "hello", lockjson.ReposStaticType, reposPathList, strategy) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") out, err := testutil.RunVolt("build") testutil.SuccessExit(t, out, err) for _, reposPath := range reposPathList { - touchFiles(t, pathutil.FullReposPathOf(reposPath)) + touchFiles(t, pathutil.FullReposPath(reposPath)) } // =============== run =============== // @@ -736,14 +736,14 @@ func voltBuildStaticVimDirNewer(t *testing.T, full bool, strategy string) { // =============== setup =============== // testutil.SetUpEnv(t) - reposPathList := []string{"localhost/local/hello"} + reposPathList := []pathutil.ReposPath{"localhost/local/hello"} teardown := testutil.SetUpRepos(t, "hello", lockjson.ReposStaticType, reposPathList, strategy) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") out, err := testutil.RunVolt("build") testutil.SuccessExit(t, out, err) for _, reposPath := range reposPathList { - touchFiles(t, pathutil.PackReposPathOf(reposPath)) + touchFiles(t, pathutil.EncodeReposPath(reposPath)) } // =============== run =============== // @@ -823,10 +823,10 @@ func checkBuildOutput(t *testing.T, full bool, out []byte, strategy string) { } } -func checkCopied(t *testing.T, reposPath string, strategy string) { +func checkCopied(t *testing.T, reposPath pathutil.ReposPath, strategy string) { t.Helper() - vimReposDir := pathutil.PackReposPathOf(reposPath) - reposDir := pathutil.FullReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) + reposDir := pathutil.FullReposPath(reposPath) tagsFile := filepath.Join("doc", "tags") filepath.Walk(vimReposDir, func(path string, fi os.FileInfo, err error) error { if err != nil { diff --git a/cmd/builder/base.go b/cmd/builder/base.go index 58a1ce71..ff4e231f 100644 --- a/cmd/builder/base.go +++ b/cmd/builder/base.go @@ -19,7 +19,7 @@ import ( type BaseBuilder struct{} -func (builder *BaseBuilder) installVimrcAndGvimrc(profileName, vimrcPath, gvimrcPath string, useVimrc, useGvimrc bool) error { +func (builder *BaseBuilder) installVimrcAndGvimrc(profileName, vimrcPath, gvimrcPath string) error { // Save old vimrc file as {vimrc}.bak vimrcInfo, err := os.Stat(vimrcPath) if err != nil && !os.IsNotExist(err) { @@ -39,7 +39,6 @@ func (builder *BaseBuilder) installVimrcAndGvimrc(profileName, vimrcPath, gvimrc profileName, pathutil.ProfileVimrc, vimrcPath, - useVimrc, ) if err != nil { return err @@ -50,7 +49,6 @@ func (builder *BaseBuilder) installVimrcAndGvimrc(profileName, vimrcPath, gvimrc profileName, pathutil.ProfileGvimrc, gvimrcPath, - useGvimrc, ) if err != nil { // Restore old vimrc @@ -70,7 +68,7 @@ func (builder *BaseBuilder) installVimrcAndGvimrc(profileName, vimrcPath, gvimrc return nil } -func (builder *BaseBuilder) installRCFile(profileName, srcRCFileName, dst string, install bool) error { +func (builder *BaseBuilder) installRCFile(profileName, srcRCFileName, dst string) error { src := filepath.Join(pathutil.RCDir(profileName), srcRCFileName) // Return error if destination file does not have magic comment @@ -90,8 +88,8 @@ func (builder *BaseBuilder) installRCFile(profileName, srcRCFileName, dst string return errors.New("failed to remove " + dst) } - // Skip if use_vimrc/use_gvimrc is false or rc file does not exist - if !install || !pathutil.Exists(src) { + // Skip if rc file does not exist + if !pathutil.Exists(src) { return nil } @@ -165,22 +163,22 @@ type actionReposResult struct { files buildinfo.FileMap } -func (builder *BaseBuilder) getCurrentProfileAndReposList(lockJSON *lockjson.LockJSON) (*lockjson.Profile, lockjson.ReposList, error) { +func (builder *BaseBuilder) getCurrentReposList(lockJSON *lockjson.LockJSON) (lockjson.ReposList, error) { // Find current profile profile, err := lockJSON.Profiles.FindByName(lockJSON.CurrentProfileName) if err != nil { // this must not be occurred because lockjson.Read() // validates that the matching profile exists - return nil, nil, err + return nil, err } reposList, err := lockJSON.GetReposListByProfile(profile) - return profile, reposList, err + return reposList, err } -func (builder *BaseBuilder) helptags(reposPath, vimExePath string) error { +func (builder *BaseBuilder) helptags(reposPath pathutil.ReposPath, vimExePath string) error { // Do nothing if /doc directory doesn't exist - docdir := filepath.Join(pathutil.PackReposPathOf(reposPath), "doc") + docdir := filepath.Join(pathutil.EncodeReposPath(reposPath), "doc") if !pathutil.Exists(docdir) { return nil } @@ -194,8 +192,8 @@ func (builder *BaseBuilder) helptags(reposPath, vimExePath string) error { return nil } -func (*BaseBuilder) makeVimArgs(reposPath string) []string { - path := pathutil.PackReposPathOf(reposPath) +func (*BaseBuilder) makeVimArgs(reposPath pathutil.ReposPath) []string { + path := pathutil.EncodeReposPath(reposPath) return []string{ "-u", "NONE", "-i", "NONE", "-N", "--cmd", "cd " + path, diff --git a/cmd/builder/builder.go b/cmd/builder/builder.go index 111d99cc..690ab54b 100644 --- a/cmd/builder/builder.go +++ b/cmd/builder/builder.go @@ -5,10 +5,11 @@ import ( "github.com/vim-volt/volt/cmd/buildinfo" "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/pathutil" ) type Builder interface { - Build(buildInfo *buildinfo.BuildInfo, buildReposMap map[string]*buildinfo.Repos) error + Build(buildInfo *buildinfo.BuildInfo, buildReposMap map[pathutil.ReposPath]*buildinfo.Repos) error } func Get(strategy string) (Builder, error) { diff --git a/cmd/builder/copy.go b/cmd/builder/copy.go index e32eb4f2..114c879e 100644 --- a/cmd/builder/copy.go +++ b/cmd/builder/copy.go @@ -2,6 +2,7 @@ package builder import ( "errors" + "fmt" "io/ioutil" "os" "path/filepath" @@ -10,6 +11,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/vim-volt/volt/cmd/buildinfo" "github.com/vim-volt/volt/fileutil" + "github.com/vim-volt/volt/gitutil" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/pathutil" @@ -23,7 +25,7 @@ type copyBuilder struct { BaseBuilder } -func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap map[string]*buildinfo.Repos) error { +func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap map[pathutil.ReposPath]*buildinfo.Repos) error { // Exit if vim executable was not found in PATH vimExePath, err := pathutil.VimExecutable() if err != nil { @@ -37,7 +39,7 @@ func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap } // Get current profile's repos list - profile, reposList, err := builder.getCurrentProfileAndReposList(lockJSON) + reposList, err := builder.getCurrentReposList(lockJSON) if err != nil { return err } @@ -48,7 +50,7 @@ func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap vimrcPath := filepath.Join(vimDir, pathutil.Vimrc) gvimrcPath := filepath.Join(vimDir, pathutil.Gvimrc) err = builder.installVimrcAndGvimrc( - lockJSON.CurrentProfileName, vimrcPath, gvimrcPath, profile.UseVimrc, profile.UseGvimrc, + lockJSON.CurrentProfileName, vimrcPath, gvimrcPath, ) if err != nil { return err @@ -75,7 +77,7 @@ func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap // Wait copy var copyModified bool copyErr := builder.waitCopyRepos(copyDone, copyCount, func(result *actionReposResult) error { - logger.Info("Installing " + string(result.repos.Type) + " repository " + result.repos.Path + " ... Done.") + logger.Info("Installing " + string(result.repos.Type) + " repository " + result.repos.Path.String() + " ... Done.") // Construct buildInfo from the result builder.constructBuildInfo(buildInfo, result) copyModified = true @@ -118,7 +120,7 @@ func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap return nil } -func (builder *copyBuilder) copyReposList(buildReposMap map[string]*buildinfo.Repos, reposList []lockjson.Repos, optDir, vimExePath string) (chan actionReposResult, int) { +func (builder *copyBuilder) copyReposList(buildReposMap map[pathutil.ReposPath]*buildinfo.Repos, reposList []lockjson.Repos, optDir, vimExePath string) (chan actionReposResult, int) { copyDone := make(chan actionReposResult, len(reposList)) copyCount := 0 for i := range reposList { @@ -144,8 +146,21 @@ func (builder *copyBuilder) copyReposList(buildReposMap map[string]*buildinfo.Re } func (builder *copyBuilder) copyReposGit(repos *lockjson.Repos, buildRepos *buildinfo.Repos, vimExePath string, done chan actionReposResult) (int, error) { + src := pathutil.FullReposPath(repos.Path) + + // Show warning when HEAD and locked revision are different + head, err := gitutil.GetHEAD(repos.Path) + if err != nil { + return 0, fmt.Errorf("failed to get HEAD revision of %q: %s", src, err.Error()) + } + if head != repos.Version { + logger.Warnf("%s: HEAD and locked revision are different", repos.Path) + logger.Warn(" HEAD: " + head) + logger.Warn(" locked revision: " + repos.Version) + logger.Warn(" Please run 'volt get -l' to update locked revision.") + } + // Open ~/volt/repos/{repos} - src := pathutil.FullReposPathOf(repos.Path) r, err := git.PlainOpen(src) if err != nil { return 0, errors.New("failed to open repository: " + err.Error()) @@ -184,17 +199,17 @@ func (builder *copyBuilder) copyReposStatic(repos *lockjson.Repos, buildRepos *b // Remove vim repos not found in lock.json current repos list func (builder *copyBuilder) removeReposList(reposList lockjson.ReposList, reposDirList []os.FileInfo) (chan actionReposResult, int) { - removeList := make([]string, 0, len(reposList)) + removeList := make([]pathutil.ReposPath, 0, len(reposList)) for i := range reposDirList { - reposPath := pathutil.UnpackPathOf(reposDirList[i].Name()) + reposPath := pathutil.DecodeReposPath(reposDirList[i].Name()) if !reposList.Contains(reposPath) { removeList = append(removeList, reposPath) } } removeDone := make(chan actionReposResult, len(removeList)) for i := range removeList { - go func(reposPath string) { - err := os.RemoveAll(pathutil.PackReposPathOf(reposPath)) + go func(reposPath pathutil.ReposPath) { + err := os.RemoveAll(pathutil.EncodeReposPath(reposPath)) logger.Info("Removing " + reposPath + " ... Done.") removeDone <- actionReposResult{ err: err, @@ -213,7 +228,7 @@ func (*copyBuilder) waitCopyRepos(copyDone chan actionReposResult, copyCount int merr = multierror.Append( merr, errors.New( - "failed to copy repository '"+result.repos.Path+ + "failed to copy repository '"+result.repos.Path.String()+ "': "+result.err.Error())) } else { err := callback(&result) @@ -270,7 +285,7 @@ func (*copyBuilder) waitRemoveRepos(removeDone chan actionReposResult, removeCou if result.err != nil { target := "files" if result.repos != nil { - target = result.repos.Path + target = result.repos.Path.String() } merr = multierror.Append( merr, errors.New( @@ -316,8 +331,8 @@ func (*copyBuilder) hasChangedGitRepos(repos *lockjson.Repos, buildRepos *buildi // Remove ~/.vim/volt/opt/{repos} and copy from ~/volt/repos/{repos} func (builder *copyBuilder) updateGitRepos(repos *lockjson.Repos, r *git.Repository, copyFromGitObjects bool, vimExePath string, done chan actionReposResult) { - src := pathutil.FullReposPathOf(repos.Path) - dst := pathutil.PackReposPathOf(repos.Path) + src := pathutil.FullReposPath(repos.Path) + dst := pathutil.EncodeReposPath(repos.Path) // Remove ~/.vim/volt/opt/{repos} // TODO: Do not remove here, copy newer files only after @@ -472,7 +487,7 @@ func (builder *copyBuilder) hasChangedStaticRepos(repos *lockjson.Repos, buildRe return true } - src := pathutil.FullReposPathOf(repos.Path) + src := pathutil.FullReposPath(repos.Path) // Get latest mtime of src // TODO: Don't check mtime here, do it when copy altogether @@ -499,8 +514,8 @@ func (builder *copyBuilder) hasChangedStaticRepos(repos *lockjson.Repos, buildRe // Remove ~/.vim/volt/opt/{repos} and copy from ~/volt/repos/{repos} func (builder *copyBuilder) updateStaticRepos(repos *lockjson.Repos, vimExePath string, done chan actionReposResult) { - src := pathutil.FullReposPathOf(repos.Path) - dst := pathutil.PackReposPathOf(repos.Path) + src := pathutil.FullReposPath(repos.Path) + dst := pathutil.EncodeReposPath(repos.Path) // Remove ~/.vim/volt/opt/{repos} // TODO: Do not remove here, copy newer files only after diff --git a/cmd/builder/symlink.go b/cmd/builder/symlink.go index 0de65043..e252bda9 100644 --- a/cmd/builder/symlink.go +++ b/cmd/builder/symlink.go @@ -12,6 +12,7 @@ import ( "gopkg.in/src-d/go-git.v4" "github.com/vim-volt/volt/cmd/buildinfo" + "github.com/vim-volt/volt/gitutil" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/pathutil" @@ -23,7 +24,7 @@ type symlinkBuilder struct { } // TODO: rollback when return err (!= nil) -func (builder *symlinkBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap map[string]*buildinfo.Repos) error { +func (builder *symlinkBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap map[pathutil.ReposPath]*buildinfo.Repos) error { // Exit if vim executable was not found in PATH if _, err := pathutil.VimExecutable(); err != nil { return err @@ -34,7 +35,7 @@ func (builder *symlinkBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposM if err != nil { return errors.New("could not read lock.json: " + err.Error()) } - profile, reposList, err := builder.getCurrentProfileAndReposList(lockJSON) + reposList, err := builder.getCurrentReposList(lockJSON) if err != nil { return err } @@ -45,7 +46,7 @@ func (builder *symlinkBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposM vimrcPath := filepath.Join(vimDir, pathutil.Vimrc) gvimrcPath := filepath.Join(vimDir, pathutil.Gvimrc) err = builder.installVimrcAndGvimrc( - lockJSON.CurrentProfileName, vimrcPath, gvimrcPath, profile.UseVimrc, profile.UseGvimrc, + lockJSON.CurrentProfileName, vimrcPath, gvimrcPath, ) if err != nil { return err @@ -80,7 +81,7 @@ func (builder *symlinkBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposM return err } if result.repos != nil { - logger.Debug("Installing " + string(result.repos.Type) + " repository " + result.repos.Path + " ... Done.") + logger.Debug("Installing " + string(result.repos.Type) + " repository " + result.repos.Path.String() + " ... Done.") } } @@ -101,10 +102,26 @@ func (builder *symlinkBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposM } func (builder *symlinkBuilder) installRepos(repos *lockjson.Repos, vimExePath string, done chan actionReposResult) { - src := pathutil.FullReposPathOf(repos.Path) - dst := pathutil.PackReposPathOf(repos.Path) + src := pathutil.FullReposPath(repos.Path) + dst := pathutil.EncodeReposPath(repos.Path) + copied := false if repos.Type == lockjson.ReposGitType { + // Show warning when HEAD and locked revision are different + head, err := gitutil.GetHEAD(repos.Path) + if err != nil { + done <- actionReposResult{ + err: fmt.Errorf("failed to get HEAD revision of %q: %s", src, err.Error()), + } + return + } + if head != repos.Version { + logger.Warnf("%s: HEAD and locked revision are different", repos.Path) + logger.Warn(" HEAD: " + head) + logger.Warn(" locked revision: " + repos.Version) + logger.Warn(" Please run 'volt get -l' to update locked revision.") + } + // Open a repository to determine it is bare repository or not r, err := git.PlainOpen(src) if err != nil { diff --git a/cmd/buildinfo/buildinfo.go b/cmd/buildinfo/buildinfo.go index 201a61f4..281e06cf 100644 --- a/cmd/buildinfo/buildinfo.go +++ b/cmd/buildinfo/buildinfo.go @@ -19,7 +19,7 @@ type ReposList []Repos type Repos struct { Type lockjson.ReposType `json:"type"` - Path string `json:"path"` + Path pathutil.ReposPath `json:"path"` Version string `json:"version"` Files FileMap `json:"files,omitempty"` DirtyWorktree bool `json:"dirty_worktree,omitempty"` @@ -73,18 +73,18 @@ func (buildInfo *BuildInfo) Write() error { func (buildInfo *BuildInfo) validate() error { // Validate if repos do not have duplicate repository - dupRepos := make(map[string]bool, len(buildInfo.Repos)) + dupRepos := make(map[pathutil.ReposPath]bool, len(buildInfo.Repos)) for i := range buildInfo.Repos { r := &buildInfo.Repos[i] if _, exists := dupRepos[r.Path]; exists { - return errors.New("duplicate repos: " + r.Path) + return errors.New("duplicate repos: " + r.Path.String()) } dupRepos[r.Path] = true } return nil } -func (reposList *ReposList) FindByReposPath(reposPath string) *Repos { +func (reposList *ReposList) FindByReposPath(reposPath pathutil.ReposPath) *Repos { for i := range *reposList { repos := &(*reposList)[i] if repos.Path == reposPath { @@ -94,7 +94,7 @@ func (reposList *ReposList) FindByReposPath(reposPath string) *Repos { return nil } -func (reposList *ReposList) RemoveByReposPath(reposPath string) { +func (reposList *ReposList) RemoveByReposPath(reposPath pathutil.ReposPath) { for i := range *reposList { repos := &(*reposList)[i] if repos.Path == reposPath { diff --git a/cmd/disable.go b/cmd/disable.go index 43dda5b4..4b41329b 100644 --- a/cmd/disable.go +++ b/cmd/disable.go @@ -53,7 +53,7 @@ func (cmd *disableCmd) Run(args []string) int { profCmd := profileCmd{} err = profCmd.doRm(append( []string{"-current"}, - reposPathList..., + reposPathList.Strings()..., )) if err != nil { logger.Error(err.Error()) @@ -63,7 +63,7 @@ func (cmd *disableCmd) Run(args []string) int { return 0 } -func (cmd *disableCmd) parseArgs(args []string) ([]string, error) { +func (cmd *disableCmd) parseArgs(args []string) (pathutil.ReposPathList, error) { fs := cmd.FlagSet() fs.Parse(args) if cmd.helped { @@ -76,7 +76,7 @@ func (cmd *disableCmd) parseArgs(args []string) ([]string, error) { } // Normalize repos path - var reposPathList []string + reposPathList := make(pathutil.ReposPathList, 0, len(fs.Args())) for _, arg := range fs.Args() { reposPath, err := pathutil.NormalizeRepos(arg) if err != nil { diff --git a/cmd/enable.go b/cmd/enable.go index 2791584c..d69dcb07 100644 --- a/cmd/enable.go +++ b/cmd/enable.go @@ -53,7 +53,7 @@ func (cmd *enableCmd) Run(args []string) int { profCmd := profileCmd{} err = profCmd.doAdd(append( []string{"-current"}, - reposPathList..., + reposPathList.Strings()..., )) if err != nil { logger.Error(err.Error()) @@ -63,7 +63,7 @@ func (cmd *enableCmd) Run(args []string) int { return 0 } -func (cmd *enableCmd) parseArgs(args []string) ([]string, error) { +func (cmd *enableCmd) parseArgs(args []string) (pathutil.ReposPathList, error) { fs := cmd.FlagSet() fs.Parse(args) if cmd.helped { @@ -76,7 +76,7 @@ func (cmd *enableCmd) parseArgs(args []string) ([]string, error) { } // Normalize repos path - var reposPathList []string + reposPathList := make(pathutil.ReposPathList, 0, len(fs.Args())) for _, arg := range fs.Args() { reposPath, err := pathutil.NormalizeRepos(arg) if err != nil { diff --git a/cmd/get.go b/cmd/get.go index 5bbded67..fb6d81f9 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -12,6 +12,7 @@ import ( "github.com/vim-volt/volt/config" "github.com/vim-volt/volt/fileutil" + "github.com/vim-volt/volt/gitutil" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/pathutil" @@ -20,7 +21,6 @@ import ( multierror "github.com/hashicorp/go-multierror" "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/sideband" ) func init() { @@ -31,7 +31,6 @@ type getCmd struct { helped bool lockJSON bool upgrade bool - verbose bool } func (cmd *getCmd) FlagSet() *flag.FlagSet { @@ -40,13 +39,13 @@ func (cmd *getCmd) FlagSet() *flag.FlagSet { fs.Usage = func() { fmt.Println(` Usage - volt get [-help] [-l] [-u] [-v] [{repository} ...] + volt get [-help] [-l] [-u] [{repository} ...] Quick example $ volt get tyru/caw.vim # will install tyru/caw.vim plugin $ volt get -u tyru/caw.vim # will upgrade tyru/caw.vim plugin $ volt get -l -u # will upgrade all installed plugins - $ volt get -v tyru/caw.vim # will output more verbosely + $ VOLT_DEBUG=1 volt get tyru/caw.vim # will output more verbosely $ mkdir -p ~/volt/repos/localhost/local/hello/plugin $ echo 'command! Hello echom "hello"' >~/volt/repos/localhost/local/hello/plugin/hello.vim @@ -61,8 +60,6 @@ Description and install it to: $VOLTPATH/plugconf/{repository}.vim - If -v option was specified, output more verbosely. - Repository List {repository} list (=target to perform installing, upgrading, and so on) is determined as followings: * If -l option is specified, all installed vim plugins (regardless current profile) are used @@ -109,7 +106,6 @@ Options`) } fs.BoolVar(&cmd.lockJSON, "l", false, "use all installed repositories as targets") fs.BoolVar(&cmd.upgrade, "u", false, "upgrade repositories") - fs.BoolVar(&cmd.verbose, "v", false, "output more verbosely") return fs } @@ -165,8 +161,8 @@ func (cmd *getCmd) parseArgs(args []string) ([]string, error) { return fs.Args(), nil } -func (cmd *getCmd) getReposPathList(args []string, lockJSON *lockjson.LockJSON) ([]string, error) { - reposPathList := make([]string, 0, 32) +func (cmd *getCmd) getReposPathList(args []string, lockJSON *lockjson.LockJSON) ([]pathutil.ReposPath, error) { + reposPathList := make([]pathutil.ReposPath, 0, 32) if cmd.lockJSON { for _, repos := range lockJSON.Repos { reposPathList = append(reposPathList, repos.Path) @@ -183,7 +179,7 @@ func (cmd *getCmd) getReposPathList(args []string, lockJSON *lockjson.LockJSON) return reposPathList, nil } -func (cmd *getCmd) doGet(reposPathList []string, lockJSON *lockjson.LockJSON) error { +func (cmd *getCmd) doGet(reposPathList []pathutil.ReposPath, lockJSON *lockjson.LockJSON) error { // Find matching profile profile, err := lockJSON.Profiles.FindByName(lockJSON.CurrentProfileName) if err != nil { @@ -287,7 +283,7 @@ func (*getCmd) formatStatus(r *getParallelResult) string { } type getParallelResult struct { - reposPath string + reposPath pathutil.ReposPath status string hash string reposType lockjson.ReposType @@ -308,13 +304,14 @@ const ( fmtAlreadyExists = "%s %s > already exists" fmtAddedRepos = "%s %s > added repository to current profile" fmtInstalled = "%s %s > installed" + fmtRevUpdate = "%s %s > updated lock.json revision (%s..%s)" fmtUpgraded = "%s %s > upgraded (%s..%s)" ) // This function is executed in goroutine of each plugin. // 1. install plugin if it does not exist // 2. install plugconf if it does not exist and createPlugconf=true -func (cmd *getCmd) getParallel(reposPath string, repos *lockjson.Repos, createPlugconf bool, done chan<- getParallelResult) { +func (cmd *getCmd) getParallel(reposPath pathutil.ReposPath, repos *lockjson.Repos, createPlugconf bool, done chan<- getParallelResult) { pluginDone := make(chan getParallelResult) go cmd.installPlugin(reposPath, repos, pluginDone) pluginResult := <-pluginDone @@ -327,23 +324,19 @@ func (cmd *getCmd) getParallel(reposPath string, repos *lockjson.Repos, createPl done <- (<-plugconfDone) } -func (cmd *getCmd) installPlugin(reposPath string, repos *lockjson.Repos, done chan<- getParallelResult) { +func (cmd *getCmd) installPlugin(reposPath pathutil.ReposPath, repos *lockjson.Repos, done chan<- getParallelResult) { // true:upgrade, false:install - fullReposPath := pathutil.FullReposPathOf(reposPath) + fullReposPath := pathutil.FullReposPath(reposPath) doUpgrade := cmd.upgrade && pathutil.Exists(fullReposPath) var fromHash string var err error if doUpgrade { // Get HEAD hash string - fromHash, err = getReposHEAD(reposPath) + fromHash, err = gitutil.GetHEAD(reposPath) if err != nil { result := errors.New("failed to get HEAD commit hash: " + err.Error()) - if cmd.verbose { - logger.Info("Rollbacking " + fullReposPath + " ...") - } else { - logger.Debug("Rollbacking " + fullReposPath + " ...") - } + logger.Debug("Rollbacking " + fullReposPath + " ...") err = cmd.rollbackRepos(fullReposPath) if err != nil { result = multierror.Append(result, err) @@ -372,19 +365,11 @@ func (cmd *getCmd) installPlugin(reposPath string, repos *lockjson.Repos, done c return } // Upgrade plugin - if cmd.verbose { - logger.Info("Upgrading " + reposPath + " ...") - } else { - logger.Debug("Upgrading " + reposPath + " ...") - } + logger.Debug("Upgrading " + reposPath + " ...") err := cmd.upgradePlugin(reposPath) if err != git.NoErrAlreadyUpToDate && err != nil { result := errors.New("failed to upgrade plugin: " + err.Error()) - if cmd.verbose { - logger.Info("Rollbacking " + fullReposPath + " ...") - } else { - logger.Debug("Rollbacking " + fullReposPath + " ...") - } + logger.Debug("Rollbacking " + fullReposPath + " ...") err = cmd.rollbackRepos(fullReposPath) if err != nil { result = multierror.Append(result, err) @@ -403,20 +388,12 @@ func (cmd *getCmd) installPlugin(reposPath string, repos *lockjson.Repos, done c } } else if !pathutil.Exists(fullReposPath) { // Install plugin - if cmd.verbose { - logger.Info("Installing " + reposPath + " ...") - } else { - logger.Debug("Installing " + reposPath + " ...") - } + logger.Debug("Installing " + reposPath + " ...") err := cmd.fetchPlugin(reposPath) // if err == errRepoExists, silently skip if err != nil && err != errRepoExists { result := errors.New("failed to install plugin: " + err.Error()) - if cmd.verbose { - logger.Info("Rollbacking " + fullReposPath + " ...") - } else { - logger.Debug("Rollbacking " + fullReposPath + " ...") - } + logger.Debug("Rollbacking " + fullReposPath + " ...") err = cmd.rollbackRepos(fullReposPath) if err != nil { result = multierror.Append(result, err) @@ -439,14 +416,10 @@ func (cmd *getCmd) installPlugin(reposPath string, repos *lockjson.Repos, done c reposType, err := cmd.detectReposType(fullReposPath) if err == nil && reposType == lockjson.ReposGitType { // Get HEAD hash string - toHash, err = getReposHEAD(reposPath) + toHash, err = gitutil.GetHEAD(reposPath) if err != nil { result := errors.New("failed to get HEAD commit hash: " + err.Error()) - if cmd.verbose { - logger.Info("Rollbacking " + fullReposPath + " ...") - } else { - logger.Debug("Rollbacking " + fullReposPath + " ...") - } + logger.Debug("Rollbacking " + fullReposPath + " ...") err = cmd.rollbackRepos(fullReposPath) if err != nil { result = multierror.Append(result, err) @@ -465,6 +438,12 @@ func (cmd *getCmd) installPlugin(reposPath string, repos *lockjson.Repos, done c status = fmt.Sprintf(fmtUpgraded, statusPrefixUpgraded, reposPath, fromHash, toHash) } + if repos != nil && repos.Version != toHash { + status = fmt.Sprintf(fmtRevUpdate, statusPrefixUpgraded, reposPath, repos.Version, toHash) + } else { + status = fmt.Sprintf(fmtNoChange, statusPrefixNoChange, reposPath) + } + done <- getParallelResult{ reposPath: reposPath, status: status, @@ -473,22 +452,14 @@ func (cmd *getCmd) installPlugin(reposPath string, repos *lockjson.Repos, done c } } -func (cmd *getCmd) installPlugconf(reposPath string, pluginResult *getParallelResult, done chan<- getParallelResult) { +func (cmd *getCmd) installPlugconf(reposPath pathutil.ReposPath, pluginResult *getParallelResult, done chan<- getParallelResult) { // Install plugconf - if cmd.verbose { - logger.Info("Installing plugconf " + reposPath + " ...") - } else { - logger.Debug("Installing plugconf " + reposPath + " ...") - } + logger.Debug("Installing plugconf " + reposPath + " ...") err := cmd.fetchPlugconf(reposPath) if err != nil { result := errors.New("failed to install plugconf: " + err.Error()) - fullReposPath := pathutil.FullReposPathOf(reposPath) - if cmd.verbose { - logger.Info("Rollbacking " + fullReposPath + " ...") - } else { - logger.Debug("Rollbacking " + fullReposPath + " ...") - } + fullReposPath := pathutil.FullReposPath(reposPath) + logger.Debug("Rollbacking " + fullReposPath + " ...") err = cmd.rollbackRepos(fullReposPath) if err != nil { result = multierror.Append(result, err) @@ -525,13 +496,8 @@ func (*getCmd) rollbackRepos(fullReposPath string) error { return nil } -func (cmd *getCmd) upgradePlugin(reposPath string) error { - fullpath := pathutil.FullReposPathOf(reposPath) - - var progress sideband.Progress = nil - // if cmd.verbose { - // progress = os.Stdout - // } +func (cmd *getCmd) upgradePlugin(reposPath pathutil.ReposPath) error { + fullpath := pathutil.FullReposPath(reposPath) repos, err := git.PlainOpen(fullpath) if err != nil { @@ -546,7 +512,6 @@ func (cmd *getCmd) upgradePlugin(reposPath string) error { if cfg.Core.IsBare { return repos.Fetch(&git.FetchOptions{ RemoteName: "origin", - Progress: progress, }) } else { wt, err := repos.Worktree() @@ -554,25 +519,20 @@ func (cmd *getCmd) upgradePlugin(reposPath string) error { return err } return wt.Pull(&git.PullOptions{ - RemoteName: "origin", - Progress: progress, + RemoteName: "origin", + RecurseSubmodules: 10, }) } } var errRepoExists = errors.New("repository exists") -func (cmd *getCmd) fetchPlugin(reposPath string) error { - fullpath := pathutil.FullReposPathOf(reposPath) +func (cmd *getCmd) fetchPlugin(reposPath pathutil.ReposPath) error { + fullpath := pathutil.FullReposPath(reposPath) if pathutil.Exists(fullpath) { return errRepoExists } - var progress sideband.Progress = nil - // if cmd.verbose { - // progress = os.Stdout - // } - err := os.MkdirAll(filepath.Dir(fullpath), 0755) if err != nil { return err @@ -581,43 +541,18 @@ func (cmd *getCmd) fetchPlugin(reposPath string) error { // Clone repository to $VOLTPATH/repos/{site}/{user}/{name} isBare := false r, err := git.PlainClone(fullpath, isBare, &git.CloneOptions{ - URL: pathutil.CloneURLOf(reposPath), - Progress: progress, + URL: pathutil.CloneURL(reposPath), + RecurseSubmodules: 10, }) if err != nil { return err } - return cmd.setUpstreamBranch(r) -} - -func (cmd *getCmd) setUpstreamBranch(r *git.Repository) error { - cfg, err := r.Config() - if err != nil { - return err - } - - head, err := r.Head() - if err != nil { - return err - } - - refBranch := head.Name().String() - branch := refHeadsRx.FindStringSubmatch(refBranch) - if len(branch) == 0 { - return errors.New("HEAD is not matched to refs/heads/...: " + refBranch) - } - - sec := cfg.Raw.Section("branch") - subsec := sec.Subsection(branch[1]) - subsec.AddOption("remote", "origin") - subsec.AddOption("merge", refBranch) - - return r.Storer.SetConfig(cfg) + return gitutil.SetUpstreamBranch(r) } -func (cmd *getCmd) fetchPlugconf(reposPath string) error { - filename := pathutil.PlugconfOf(reposPath) +func (cmd *getCmd) fetchPlugconf(reposPath pathutil.ReposPath) error { + filename := pathutil.Plugconf(reposPath) if pathutil.Exists(filename) { logger.Debugf("plugconf '%s' exists... skip", filename) return nil @@ -643,7 +578,7 @@ func (cmd *getCmd) fetchPlugconf(reposPath string) error { // * Add repos to 'repos' if not found // * Add repos to 'profiles[]/repos_path' if not found -func (*getCmd) updateReposVersion(lockJSON *lockjson.LockJSON, reposPath string, reposType lockjson.ReposType, version string, profile *lockjson.Profile) bool { +func (*getCmd) updateReposVersion(lockJSON *lockjson.LockJSON, reposPath pathutil.ReposPath, reposType lockjson.ReposType, version string, profile *lockjson.Profile) bool { repos, err := lockJSON.Repos.FindByPath(reposPath) if err != nil { repos = nil diff --git a/cmd/get_test.go b/cmd/get_test.go index ab860e62..78b606d1 100644 --- a/cmd/get_test.go +++ b/cmd/get_test.go @@ -28,10 +28,10 @@ import ( func TestVoltGetOnePlugin(t *testing.T) { for _, tt := range []struct { withHelp bool - reposPath string + reposPath pathutil.ReposPath }{ - {true, "github.com/tyru/caw.vim"}, - {false, "github.com/tyru/dummy"}, + {true, pathutil.ReposPath("github.com/tyru/caw.vim")}, + {false, pathutil.ReposPath("github.com/tyru/dummy")}, } { t.Run(fmt.Sprintf("with help=%v", tt.withHelp), func(t *testing.T) { testGetMatrix(t, func(t *testing.T, strategy string) { @@ -42,12 +42,12 @@ func TestVoltGetOnePlugin(t *testing.T) { // =============== run =============== // - out, err := testutil.RunVolt("get", tt.reposPath) + out, err := testutil.RunVolt("get", tt.reposPath.String()) // (A, B) testutil.SuccessExit(t, out, err) // (C) - reposDir := pathutil.FullReposPathOf(tt.reposPath) + reposDir := pathutil.FullReposPath(tt.reposPath) if !pathutil.Exists(reposDir) { t.Error("repos does not exist: " + reposDir) } @@ -57,14 +57,14 @@ func TestVoltGetOnePlugin(t *testing.T) { } // (D) - plugconf := pathutil.PlugconfOf(tt.reposPath) + plugconf := pathutil.Plugconf(tt.reposPath) if !pathutil.Exists(plugconf) { t.Error("plugconf does not exist: " + plugconf) } // TODO: check plugconf has s:config(), s:loaded_on(), depends() // (E) - vimReposDir := pathutil.PackReposPathOf(tt.reposPath) + vimReposDir := pathutil.EncodeReposPath(tt.reposPath) if !pathutil.Exists(vimReposDir) { t.Error("vim repos does not exist: " + vimReposDir) } @@ -93,10 +93,16 @@ func TestVoltGetOnePlugin(t *testing.T) { func TestVoltGetTwoOrMorePlugin(t *testing.T) { for _, tt := range []struct { withHelp bool - reposPathList []string + reposPathList pathutil.ReposPathList }{ - {true, []string{"github.com/tyru/caw.vim", "github.com/tyru/capture.vim"}}, - {false, []string{"github.com/tyru/dummy", "github.com/tyru/dummy2"}}, + {true, pathutil.ReposPathList{ + pathutil.ReposPath("github.com/tyru/caw.vim"), + pathutil.ReposPath("github.com/tyru/capture.vim"), + }}, + {false, pathutil.ReposPathList{ + pathutil.ReposPath("github.com/tyru/dummy"), + pathutil.ReposPath("github.com/tyru/dummy2"), + }}, } { t.Run(fmt.Sprintf("with help=%v", tt.withHelp), func(t *testing.T) { testGetMatrix(t, func(t *testing.T, strategy string) { @@ -108,13 +114,13 @@ func TestVoltGetTwoOrMorePlugin(t *testing.T) { // =============== run =============== // // (A, B) - args := append([]string{"get"}, tt.reposPathList...) + args := append([]string{"get"}, tt.reposPathList.Strings()...) out, err := testutil.RunVolt(args...) testutil.SuccessExit(t, out, err) for _, reposPath := range tt.reposPathList { // (C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if !pathutil.Exists(reposDir) { t.Error("repos does not exist: " + reposDir) } @@ -124,14 +130,14 @@ func TestVoltGetTwoOrMorePlugin(t *testing.T) { } // (D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if !pathutil.Exists(plugconf) { t.Error("plugconf does not exist: " + plugconf) } // TODO: check plugconf has s:config(), s:loaded_on(), depends() // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if !pathutil.Exists(vimReposDir) { t.Error("vim repos does not exist: " + vimReposDir) } @@ -168,21 +174,24 @@ func TestErrVoltGetInvalidArgs(t *testing.T) { // (!A, !B) testutil.FailExit(t, out, err) - for _, reposPath := range []string{"caw.vim", "github.com/caw.vim"} { + for _, reposPath := range []pathutil.ReposPath{ + pathutil.ReposPath("caw.vim"), + pathutil.ReposPath("github.com/caw.vim"), + } { // (!C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if pathutil.Exists(reposDir) { t.Error("repos exists: " + reposDir) } // (!D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if pathutil.Exists(plugconf) { t.Error("plugconf exists: " + plugconf) } // (!E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos exists: " + vimReposDir) } @@ -209,22 +218,22 @@ func TestErrVoltGetNotFound(t *testing.T) { out, err := testutil.RunVolt("get", "vim-volt/not_found") // (!A, !B) testutil.FailExit(t, out, err) - reposPath := "github.com/vim-volt/not_found" + reposPath := pathutil.ReposPath("github.com/vim-volt/not_found") // (!C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if pathutil.Exists(reposDir) { t.Error("repos exists: " + reposDir) } // (!D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if pathutil.Exists(plugconf) { t.Error("plugconf exists: " + plugconf) } // (!E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos exists: " + vimReposDir) } @@ -239,7 +248,7 @@ func TestErrVoltGetNotFound(t *testing.T) { } } -func testReposPathWereAdded(t *testing.T, reposPath string) { +func testReposPathWereAdded(t *testing.T, reposPath pathutil.ReposPath) { t.Helper() lockJSON, err := lockjson.Read() if err != nil { @@ -255,7 +264,7 @@ func testReposPathWereAdded(t *testing.T, reposPath string) { } } -func testReposPathWereNotAdded(t *testing.T, reposPath string) { +func testReposPathWereNotAdded(t *testing.T, reposPath pathutil.ReposPath) { t.Helper() lockJSON, err := lockjson.Read() if err != nil { diff --git a/cmd/help.go b/cmd/help.go index e468ce1d..33e58105 100644 --- a/cmd/help.go +++ b/cmd/help.go @@ -38,20 +38,20 @@ Usage volt COMMAND ARGS Command - get [-l] [-u] [-v] [{repository} ...] + get [-l] [-u] [{repository} ...] Install or upgrade given {repository} list, or add local {repository} list as plugins - rm {repository} [{repository2} ...] - Uninstall vim plugin and plugconf files + rm [-r] [-p] {repository} [{repository2} ...] + Remove vim plugin from ~/.vim/pack/volt/opt/ directory + + list [-f {text/template string}] + Vim plugin information extractor. + Unless -f flag was given, this command shows vim plugins of **current profile** (not all installed plugins) by default. enable {repository} [{repository2} ...] This is shortcut of: volt profile add -current {repository} [{repository2} ...] - list - This is shortcut of: - volt profile show -current - disable {repository} [{repository2} ...] This is shortcut of: volt profile rm -current {repository} [{repository2} ...] @@ -80,10 +80,6 @@ Command profile rm {name} {repository} [{repository2} ...] Remove one or more repositories to profile - profile use [-current | {name}] vimrc [true | false] - profile use [-current | {name}] gvimrc [true | false] - Set vimrc / gvimrc flag to true or false. - build [-full] Build ~/.vim/pack/volt/ directory diff --git a/cmd/list.go b/cmd/list.go index c6f540d5..b2c74d57 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -1,10 +1,14 @@ package cmd import ( + "encoding/json" + "errors" "flag" "fmt" "os" + "text/template" + "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" ) @@ -14,6 +18,7 @@ func init() { type listCmd struct { helped bool + format string } func (cmd *listCmd) FlagSet() *flag.FlagSet { @@ -22,37 +27,174 @@ func (cmd *listCmd) FlagSet() *flag.FlagSet { fs.Usage = func() { fmt.Print(` Usage - volt list [-help] + volt list [-help] [-f {text/template string}] Quick example $ volt list # will list installed plugins + Show all installed repositories: + + $ volt list -f '{{ range .Repos }}{{ println .Path }}{{ end }}' + + Show repositories used by current profile: + + $ volt list -f '{{ range .Profiles }}{{ if eq $.CurrentProfileName .Name }}{{ range .ReposPath }}{{ . }}{{ end }}{{ end }}{{ end }}' + + Or (see "Additional property"): + + $ volt list -f '{{ range currentProfile.ReposPath }}{{ println . }}{{ end }}' + +Template functions + + json value [prefix [indent]] (string) + Returns JSON representation of value. + The argument is same as json.MarshalIndent(). + + currentProfile (Profile (see "Structures")) + Returns current profile + + currentProfile (Profile (see "Structures")) + Returns given name's profile + + version (string) + Returns volt version string. format is "v{major}.{minor}.{patch}" (e.g. "v0.3.0") + + versionMajor (number) + Returns volt major version + + versionMinor (number) + Returns volt minor version + + versionPatch (number) + Returns volt patch version + +Structures + This describes the structure of lock.json . + { + // lock.json structure compatibility version + "version": , + + // Unique number of transaction + "trx_id": , + + // Current profile name (e.g. "default") + "current_profile_name": , + + // All Installed repositories + // ("volt list" shows current profile's repositories, which is not the same as this) + "repos": [ + { + // "git" (git repository) or "static" (static repository) + "type": , + + // Unique number of transaction + "trx_id": , + + // Repository path like "github.com/vim-volt/vim-volt" + "path": , + + // Git commit hash. if "type" is "static" this property does not exist + "version": , + }, + ], + + // Profiles + "profiles": [ + // Profile name (.e.g. "default") + "name": , + + // Repositories ("volt list" shows these repositories) + "repos_path": [ ], + ] + } + Description - This is shortcut of: - volt profile show {current profile}` + "\n\n") + Vim plugin information extractor. + If -f flag is not given, this command shows vim plugins of **current profile** (not all installed plugins) by default. + If -f flag is given, it renders by given template which can access the information of lock.json .` + "\n\n") //fmt.Println("Options") //fs.PrintDefaults() fmt.Println() cmd.helped = true } + fs.StringVar(&cmd.format, "f", cmd.defaultTemplate(), "text/template format string") return fs } +func (*listCmd) defaultTemplate() string { + return `name: {{ .CurrentProfileName }} +repos path: +{{- range currentProfile.ReposPath }} + {{ . }} +{{- end }} +` +} + func (cmd *listCmd) Run(args []string) int { fs := cmd.FlagSet() fs.Parse(args) if cmd.helped { return 0 } + if err := cmd.list(cmd.format); err != nil { + logger.Error("Failed to render template:", err.Error()) + return 10 + } + return 0 +} - profCmd := profileCmd{} - err := profCmd.doShow(append( - []string{"-current"}, - )) +func (cmd *listCmd) list(format string) error { + // Read lock.json + lockJSON, err := lockjson.Read() if err != nil { - logger.Error(err.Error()) - return 10 + return errors.New("failed to read lock.json: " + err.Error()) } + // Parse template string + t, err := template.New("volt").Funcs(cmd.funcMap(lockJSON)).Parse(format) + if err != nil { + return err + } + // Output templated information + return t.Execute(os.Stdout, lockJSON) +} - return 0 +func (*listCmd) funcMap(lockJSON *lockjson.LockJSON) template.FuncMap { + profileOf := func(name string) *lockjson.Profile { + profile, err := lockJSON.Profiles.FindByName(name) + if err != nil { + return &lockjson.Profile{} + } + return profile + } + + return template.FuncMap{ + "json": func(value interface{}, args ...string) string { + var b []byte + switch len(args) { + case 0: + b, _ = json.MarshalIndent(value, "", "") + case 1: + b, _ = json.MarshalIndent(value, args[0], "") + default: + b, _ = json.MarshalIndent(value, args[0], args[1]) + } + return string(b) + }, + "currentProfile": func() *lockjson.Profile { + return profileOf(lockJSON.CurrentProfileName) + }, + "profile": profileOf, + "version": func() string { + return voltVersion + }, + "versionMajor": func() int { + return voltVersionInfo()[0] + }, + "versionMinor": func() int { + return voltVersionInfo()[1] + }, + "versionPatch": func() int { + return voltVersionInfo()[2] + }, + } } diff --git a/cmd/list_test.go b/cmd/list_test.go new file mode 100644 index 00000000..9e9a4b37 --- /dev/null +++ b/cmd/list_test.go @@ -0,0 +1,167 @@ +package cmd + +import ( + "strconv" + "testing" + + "github.com/vim-volt/volt/internal/testutil" + "github.com/vim-volt/volt/lockjson" +) + +// Checks: +// (A) Does not show `[ERROR]`, `[WARN]` messages +// (B) Exit with zero status + +// Checks: +// (a) `volt list` and `volt profile show -current` output is same +func TestVoltListAndVoltProfileAreSame(t *testing.T) { + // =============== setup =============== // + + testutil.SetUpEnv(t) + + // =============== run =============== // + + out1, err := testutil.RunVolt("list") + testutil.SuccessExit(t, out1, err) // (A, B) + out2, err := testutil.RunVolt("profile", "show", "-current") + testutil.SuccessExit(t, out2, err) + + // (a) + if string(out1) != string(out2) { + t.Errorf("=== expected ===\n[%s]\n=== got ===\n[%s]", string(out2), string(out1)) + } +} + +// Checks: +// (a) `currentProfile` returns current profile +// (b) `version` returns current version +// (c) `versionMajor` returns current version +// (d) `versionMinor` returns current version +// (e) `versionPatch` returns current version +func TestVoltListFunctions(t *testing.T) { + t.Run("json", func(t *testing.T) { + // =============== setup =============== // + + testutil.SetUpEnv(t) + + // =============== run =============== // + + out, err := testutil.RunVolt("list", "-f", "{{ json .CurrentProfileName }}") + // (A, B) + testutil.SuccessExit(t, out, err) + + // (a) + if string(out) != "\"default\"" { + t.Errorf("expected %q but got %q", "\"default\"", string(out)) + } + }) + + t.Run("currentProfile", func(t *testing.T) { + // =============== setup =============== // + + testutil.SetUpEnv(t) + + // =============== run =============== // + + out, err := testutil.RunVolt("list", "-f", "{{ currentProfile.Name }}") + // (A, B) + testutil.SuccessExit(t, out, err) + + lockJSON, err := lockjson.Read() + if err != nil { + t.Fatal("failed to read lock.json: " + err.Error()) + } + // (a) + if string(out) != lockJSON.CurrentProfileName { + t.Errorf("expected %q but got %q", lockJSON.CurrentProfileName, string(out)) + } + }) + + t.Run("profile", func(t *testing.T) { + // =============== setup =============== // + + testutil.SetUpEnv(t) + + // =============== run =============== // + + out, err := testutil.RunVolt("list", "-f", "{{ with profile \"default\" }}{{ .Name }}{{ end }}") + // (A, B) + testutil.SuccessExit(t, out, err) + + // (a) + if string(out) != "default" { + t.Errorf("expected %q but got %q", "default", string(out)) + } + }) + + t.Run("version", func(t *testing.T) { + // =============== setup =============== // + + testutil.SetUpEnv(t) + + // =============== run =============== // + + out, err := testutil.RunVolt("list", "-f", "{{ version }}") + // (A, B) + testutil.SuccessExit(t, out, err) + + // (b) + if string(out) != voltVersion { + t.Errorf("expected %q but got %q", voltVersion, string(out)) + } + }) + + t.Run("versionMajor", func(t *testing.T) { + // =============== setup =============== // + + testutil.SetUpEnv(t) + + // =============== run =============== // + + out, err := testutil.RunVolt("list", "-f", "{{ versionMajor }}") + // (A, B) + testutil.SuccessExit(t, out, err) + + // (c) + expected := strconv.Itoa(voltVersionInfo()[0]) + if string(out) != expected { + t.Errorf("expected %q but got %q", expected, string(out)) + } + }) + + t.Run("versionMinor", func(t *testing.T) { + // =============== setup =============== // + + testutil.SetUpEnv(t) + + // =============== run =============== // + + out, err := testutil.RunVolt("list", "-f", "{{ versionMinor }}") + // (A, B) + testutil.SuccessExit(t, out, err) + + // (d) + expected := strconv.Itoa(voltVersionInfo()[1]) + if string(out) != expected { + t.Errorf("expected %q but got %q", expected, string(out)) + } + }) + + t.Run("versionPatch", func(t *testing.T) { + // =============== setup =============== // + + testutil.SetUpEnv(t) + + // =============== run =============== // + + out, err := testutil.RunVolt("list", "-f", "{{ versionPatch }}") + // (A, B) + testutil.SuccessExit(t, out, err) + + // (e) + expected := strconv.Itoa(voltVersionInfo()[2]) + if string(out) != expected { + t.Errorf("expected %q but got %q", expected, string(out)) + } + }) +} diff --git a/cmd/profile.go b/cmd/profile.go index 87218880..c3f4156d 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -5,7 +5,6 @@ import ( "flag" "fmt" "os" - "strconv" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" @@ -57,10 +56,6 @@ Command profile rm [-current | {name}] {repository} [{repository2} ...] Remove one or more repositories from profile {name}. - profile use [-current | {name}] vimrc [true | false] - profile use [-current | {name}] gvimrc [true | false] - Set vimrc / gvimrc flag to true or false. - Quick example $ volt profile list # default profile is "default" * default @@ -81,10 +76,7 @@ Quick example $ volt disable tyru/caw.vim # disable loading tyru/caw.vim on current profile $ volt profile rm foo tyru/caw.vim # disable loading tyru/caw.vim on "foo" profile - $ volt profile destroy foo # will delete profile "foo" - - $ volt profile use -current vimrc false # Disable installing vimrc on current profile on "volt build" - $ volt profile use default gvimrc true # Enable installing gvimrc on profile default on "volt build"` + "\n\n") + $ volt profile destroy foo # will delete profile "foo"` + "\n\n") cmd.helped = true } return fs @@ -119,8 +111,6 @@ func (cmd *profileCmd) Run(args []string) int { err = cmd.doAdd(args[1:]) case "rm": err = cmd.doRm(args[1:]) - case "use": - err = cmd.doUse(args[1:]) default: logger.Error("unknown subcommand: " + subCmd) return 11 @@ -243,46 +233,27 @@ func (cmd *profileCmd) doShow(args []string) error { profileName = lockJSON.CurrentProfileName } else { profileName = args[0] - } - - // Return error if profiles[]/name does not match profileName - profile, err := lockJSON.Profiles.FindByName(profileName) - if err != nil { - return err - } - - fmt.Println("name:", profile.Name) - fmt.Println("use vimrc:", profile.UseVimrc) - fmt.Println("use gvimrc:", profile.UseGvimrc) - fmt.Println("repos path:") - for _, reposPath := range profile.ReposPath { - hash, err := getReposHEAD(reposPath) - if err != nil { - hash = "?" + if lockJSON.Profiles.FindIndexByName(profileName) == -1 { + return fmt.Errorf("profile '%s' does not exist", profileName) } - fmt.Printf(" %s (%s)\n", reposPath, hash) } - return nil + return (&listCmd{}).list(fmt.Sprintf(`name: %s +repos path: +{{- with profile %q -}} +{{- range .ReposPath }} + {{ . }} +{{- end -}} +{{- end }} +`, profileName, profileName)) } func (cmd *profileCmd) doList(args []string) error { - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } - - // List profile names - for _, profile := range lockJSON.Profiles { - if profile.Name == lockJSON.CurrentProfileName { - fmt.Println("* " + profile.Name) - } else { - fmt.Println(" " + profile.Name) - } - } - - return nil + return (&listCmd{}).list(` +{{- range .Profiles -}} +{{- if eq .Name $.CurrentProfileName -}}*{{- else }} {{ end }} {{ .Name }} +{{ end -}} +`) } func (cmd *profileCmd) doNew(args []string) error { @@ -315,9 +286,7 @@ func (cmd *profileCmd) doNew(args []string) error { // Add profile lockJSON.Profiles = append(lockJSON.Profiles, lockjson.Profile{ Name: profileName, - ReposPath: make([]string, 0), - UseVimrc: true, - UseGvimrc: true, + ReposPath: make([]pathutil.ReposPath, 0), }) // Write to lock.json @@ -465,10 +434,10 @@ func (cmd *profileCmd) doAdd(args []string) error { // Add repositories to profile if the repository does not exist for _, reposPath := range reposPathList { if profile.ReposPath.Contains(reposPath) { - logger.Warn("repository '" + reposPath + "' is already enabled") + logger.Warn("repository '" + reposPath.String() + "' is already enabled") } else { profile.ReposPath = append(profile.ReposPath, reposPath) - logger.Info("Enabled '" + reposPath + "' on profile '" + profileName + "'") + logger.Info("Enabled '" + reposPath.String() + "' on profile '" + profileName + "'") } } }) @@ -510,9 +479,9 @@ func (cmd *profileCmd) doRm(args []string) error { if index >= 0 { // Remove profile.ReposPath[index] profile.ReposPath = append(profile.ReposPath[:index], profile.ReposPath[index+1:]...) - logger.Info("Disabled '" + reposPath + "' from profile '" + profileName + "'") + logger.Info("Disabled '" + reposPath.String() + "' from profile '" + profileName + "'") } else { - logger.Warn("repository '" + reposPath + "' is already disabled") + logger.Warn("repository '" + reposPath.String() + "' is already disabled") } } }) @@ -529,7 +498,7 @@ func (cmd *profileCmd) doRm(args []string) error { return nil } -func (cmd *profileCmd) parseAddArgs(lockJSON *lockjson.LockJSON, subCmd string, args []string) (string, []string, error) { +func (cmd *profileCmd) parseAddArgs(lockJSON *lockjson.LockJSON, subCmd string, args []string) (string, []pathutil.ReposPath, error) { if len(args) == 0 { cmd.FlagSet().Usage() logger.Errorf("'volt profile %s' receives profile name and one or more repositories.", subCmd) @@ -537,7 +506,7 @@ func (cmd *profileCmd) parseAddArgs(lockJSON *lockjson.LockJSON, subCmd string, } profileName := args[0] - reposPathList := make([]string, 0, len(args)-1) + reposPathList := make([]pathutil.ReposPath, 0, len(args)-1) for _, arg := range args[1:] { reposPath, err := pathutil.NormalizeRepos(arg) if err != nil { @@ -581,93 +550,3 @@ func (*profileCmd) transactProfile(lockJSON *lockjson.LockJSON, profileName stri } return lockJSON, nil } - -func (cmd *profileCmd) doUse(args []string) error { - // Validate arguments - if len(args) != 3 { - cmd.FlagSet().Usage() - logger.Error("'volt profile use' receives profile name, rc name, value.") - return nil - } - if args[1] != "vimrc" && args[1] != "gvimrc" { - cmd.FlagSet().Usage() - logger.Error("volt profile use: Please specify \"vimrc\" or \"gvimrc\" to the 2nd argument") - return nil - } - if args[2] != "true" && args[2] != "false" { - cmd.FlagSet().Usage() - logger.Error("volt profile use: Please specify \"true\" or \"false\" to the 3rd argument") - return nil - } - - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } - - // Convert arguments - var profileName string - var rcName string - var value bool - if args[0] == "-current" { - profileName = lockJSON.CurrentProfileName - } else { - profileName = args[0] - } - rcName = args[1] - if args[2] == "true" { - value = true - } else { - value = false - } - - // Look up specified profile - profile, err := lockJSON.Profiles.FindByName(profileName) - if err != nil { - return err - } - - // Begin transaction - err = transaction.Create() - if err != nil { - return err - } - defer transaction.Remove() - - // Set use_vimrc / use_gvimrc flag - changed := false - if rcName == "vimrc" { - if profile.UseVimrc != value { - logger.Infof("Set vimrc flag of profile '%s' to '%s'", profileName, strconv.FormatBool(value)) - profile.UseVimrc = value - changed = true - } else { - logger.Warnf("vimrc flag of profile '%s' is already '%s'", profileName, strconv.FormatBool(value)) - } - } else { - if profile.UseGvimrc != value { - logger.Infof("Set gvimrc flag of profile '%s' to '%s'", profileName, strconv.FormatBool(value)) - profile.UseGvimrc = value - changed = true - } else { - logger.Warnf("gvimrc flag of profile '%s' is already '%s'", profileName, strconv.FormatBool(value)) - } - } - - if changed { - // Write to lock.json - err = lockJSON.Write() - if err != nil { - return err - } - } - - // Build ~/.vim/pack/volt dir - err = (&buildCmd{}).doBuild(false) - if err != nil { - return errors.New("could not build " + pathutil.VimVoltDir() + ": " + err.Error()) - } - - return nil -} diff --git a/cmd/profile_test.go b/cmd/profile_test.go index d28a8f60..f876c33e 100644 --- a/cmd/profile_test.go +++ b/cmd/profile_test.go @@ -30,7 +30,7 @@ func TestVoltProfileSet(t *testing.T) { testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := []pathutil.ReposPath{pathutil.ReposPath("github.com/tyru/caw.vim")} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, strategy) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -60,7 +60,7 @@ func TestVoltProfileSet(t *testing.T) { // (b) for _, reposPath := range reposPathList { - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if !pathutil.Exists(vimReposDir) { t.Error("vim repos does not exist: " + vimReposDir) } @@ -121,13 +121,11 @@ func TestVoltProfileSet(t *testing.T) { // Checks: // (a) Output has profile name -// (b) Output has "use vimrc" -// (c) Output has "use gvimrc" -// (d) Output has "repos path" +// (b) Output has "repos path" // -// * Run `volt profile show ` (`` is existing profile) (A, B, a, b, c, d) -// * Run `volt profile show -current` (A, B, a, b, c, d) -// * Run `volt profile show ` (`` is non-existing profile) (!A, !B, !a, !b, !c, !d) +// * Run `volt profile show ` (`` is existing profile) (A, B, a, b) +// * Run `volt profile show -current` (A, B, a, b) +// * Run `volt profile show ` (`` is non-existing profile) (!A, !B, !a, !b) func TestVoltProfileShow(t *testing.T) { t.Run("Run `volt profile show ` (`` is existing profile)", func(t *testing.T) { // =============== setup =============== // @@ -140,17 +138,11 @@ func TestVoltProfileShow(t *testing.T) { // (A, B) testutil.SuccessExit(t, out, err) - // (a, b, c, d) + // (a, b) outstr := string(out) if !strings.Contains(outstr, "name: default\n") { t.Errorf("Expected 'name: default' line, but got: %s", outstr) } - if !strings.Contains(outstr, "use vimrc: true\n") { - t.Errorf("Expected 'use vimrc: true' line, but got: %s", outstr) - } - if !strings.Contains(outstr, "use gvimrc: true\n") { - t.Errorf("Expected 'use gvimrc: true' line, but got: %s", outstr) - } if !strings.Contains(outstr, "repos path:\n") { t.Errorf("Expected 'repos path:' line, but got: %s", outstr) } @@ -167,17 +159,11 @@ func TestVoltProfileShow(t *testing.T) { // (A, B) testutil.SuccessExit(t, out, err) - // (a, b, c, d) + // (a, b) outstr := string(out) if !strings.Contains(outstr, "name: default\n") { t.Errorf("Expected 'name: default' line, but got: %s", outstr) } - if !strings.Contains(outstr, "use vimrc: true\n") { - t.Errorf("Expected 'use vimrc: true' line, but got: %s", outstr) - } - if !strings.Contains(outstr, "use gvimrc: true\n") { - t.Errorf("Expected 'use gvimrc: true' line, but got: %s", outstr) - } if !strings.Contains(outstr, "repos path:\n") { t.Errorf("Expected 'repos path:' line, but got: %s", outstr) } @@ -194,7 +180,7 @@ func TestVoltProfileShow(t *testing.T) { // (!A, !B) testutil.FailExit(t, out, err) - // (!a, !b, !c, !d) + // (!a, !b) outstr := string(out) expected := "[ERROR] profile 'bar' does not exist" if strings.Trim(outstr, " \t\r\n") != expected { @@ -661,7 +647,7 @@ func TestVoltProfileAdd(t *testing.T) { testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := []pathutil.ReposPath{pathutil.ReposPath("github.com/tyru/caw.vim")} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, config.SymlinkBuilder) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -676,8 +662,8 @@ func TestVoltProfileAdd(t *testing.T) { // =============== run =============== // - reposPath := "github.com/tyru/caw.vim" - out, err = testutil.RunVolt("profile", "add", "empty", reposPath) + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + out, err = testutil.RunVolt("profile", "add", "empty", reposPath.String()) // (A, B) testutil.SuccessExit(t, out, err) @@ -706,7 +692,7 @@ func TestVoltProfileAdd(t *testing.T) { testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim", "github.com/tyru/capture.vim"} + reposPathList := pathutil.ReposPathList{pathutil.ReposPath("github.com/tyru/caw.vim"), pathutil.ReposPath("github.com/tyru/capture.vim")} teardown := testutil.SetUpRepos(t, "caw-and-capture", lockjson.ReposGitType, reposPathList, config.SymlinkBuilder) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -721,7 +707,7 @@ func TestVoltProfileAdd(t *testing.T) { // =============== run =============== // - args := append([]string{"profile", "add", "empty"}, reposPathList...) + args := append([]string{"profile", "add", "empty"}, reposPathList.Strings()...) out, err = testutil.RunVolt(args...) // (A, B) testutil.SuccessExit(t, out, err) @@ -735,7 +721,7 @@ func TestVoltProfileAdd(t *testing.T) { // (a) for _, reposPath := range reposPathList { if !reposList.Contains(reposPath) { - t.Errorf("expected '%s' is added to profile '%s', but not added", reposPath, "empty") + t.Errorf("expected '%s' is added to profile '%s', but not added", reposPath.String(), "empty") } } // (b) @@ -753,7 +739,7 @@ func TestVoltProfileAdd(t *testing.T) { testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := pathutil.ReposPathList{pathutil.ReposPath("github.com/tyru/caw.vim")} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, config.SymlinkBuilder) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -768,8 +754,8 @@ func TestVoltProfileAdd(t *testing.T) { // =============== run =============== // - reposPath := "github.com/tyru/caw.vim" - out, err = testutil.RunVolt("profile", "add", "-current", reposPath) + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + out, err = testutil.RunVolt("profile", "add", "-current", reposPath.String()) // (A, B) testutil.SuccessExit(t, out, err) @@ -798,7 +784,7 @@ func TestVoltProfileAdd(t *testing.T) { testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := pathutil.ReposPathList{pathutil.ReposPath("github.com/tyru/caw.vim")} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, config.SymlinkBuilder) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -846,8 +832,8 @@ func TestVoltProfileAdd(t *testing.T) { // =============== run =============== // - reposPath := "github.com/tyru/caw.vim" - out, err = testutil.RunVolt("profile", "add", "empty", reposPath) + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + out, err = testutil.RunVolt("profile", "add", "empty", reposPath.String()) // (!A, !B) testutil.FailExit(t, out, err) @@ -859,7 +845,7 @@ func TestVoltProfileAdd(t *testing.T) { // (!a) if reposList.Contains(reposPath) { - t.Errorf("expected '%s' is not added to profile '%s', but added", reposPath, "empty") + t.Errorf("expected '%s' is not added to profile '%s', but added", reposPath.String(), "empty") } // (b) testNotChangedProfileExcept(t, oldLockJSON, lockJSON, "empty") @@ -884,8 +870,8 @@ func TestVoltProfileAdd(t *testing.T) { // =============== run =============== // - reposPath := "github.com/tyru/caw.vim" - out, err := testutil.RunVolt("profile", "add", "not_existing_profile", reposPath) + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + out, err := testutil.RunVolt("profile", "add", "not_existing_profile", reposPath.String()) // (!A, !B) testutil.FailExit(t, out, err) @@ -922,7 +908,7 @@ func TestVoltProfileRm(t *testing.T) { testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := pathutil.ReposPathList{pathutil.ReposPath("github.com/tyru/caw.vim")} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, config.SymlinkBuilder) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -934,8 +920,8 @@ func TestVoltProfileRm(t *testing.T) { // =============== run =============== // - reposPath := "github.com/tyru/caw.vim" - out, err := testutil.RunVolt("profile", "rm", "default", reposPath) + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + out, err := testutil.RunVolt("profile", "rm", "default", reposPath.String()) // (A, B) testutil.SuccessExit(t, out, err) @@ -964,7 +950,7 @@ func TestVoltProfileRm(t *testing.T) { testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim", "github.com/tyru/capture.vim"} + reposPathList := pathutil.ReposPathList{pathutil.ReposPath("github.com/tyru/caw.vim"), pathutil.ReposPath("github.com/tyru/capture.vim")} teardown := testutil.SetUpRepos(t, "caw-and-capture", lockjson.ReposGitType, reposPathList, config.SymlinkBuilder) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -976,7 +962,7 @@ func TestVoltProfileRm(t *testing.T) { // =============== run =============== // - args := append([]string{"profile", "rm", "default"}, reposPathList...) + args := append([]string{"profile", "rm", "default"}, reposPathList.Strings()...) out, err := testutil.RunVolt(args...) // (A, B) testutil.SuccessExit(t, out, err) @@ -990,7 +976,7 @@ func TestVoltProfileRm(t *testing.T) { // (a) for _, reposPath := range reposPathList { if reposList.Contains(reposPath) { - t.Errorf("expected '%s' is removed from profile '%s', but not removed", reposPath, "default") + t.Errorf("expected '%s' is removed from profile '%s', but not removed", reposPath.String(), "default") } } // (b) @@ -1008,7 +994,7 @@ func TestVoltProfileRm(t *testing.T) { testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := pathutil.ReposPathList{pathutil.ReposPath("github.com/tyru/caw.vim")} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, config.SymlinkBuilder) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -1020,8 +1006,8 @@ func TestVoltProfileRm(t *testing.T) { // =============== run =============== // - reposPath := "github.com/tyru/caw.vim" - out, err := testutil.RunVolt("profile", "rm", "-current", reposPath) + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + out, err := testutil.RunVolt("profile", "rm", "-current", reposPath.String()) // (A, B) testutil.SuccessExit(t, out, err) @@ -1050,7 +1036,7 @@ func TestVoltProfileRm(t *testing.T) { testutil.SetUpEnv(t) - reposPathList := []string{"github.com/tyru/caw.vim"} + reposPathList := pathutil.ReposPathList{pathutil.ReposPath("github.com/tyru/caw.vim")} teardown := testutil.SetUpRepos(t, "caw.vim", lockjson.ReposGitType, reposPathList, config.SymlinkBuilder) defer teardown() testutil.InstallConfig(t, "strategy-"+strategy+".toml") @@ -1095,8 +1081,8 @@ func TestVoltProfileRm(t *testing.T) { // =============== run =============== // - reposPath := "github.com/tyru/caw.vim" - out, err := testutil.RunVolt("profile", "rm", "default", reposPath) + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + out, err := testutil.RunVolt("profile", "rm", "default", reposPath.String()) // (!A, !B) testutil.FailExit(t, out, err) diff --git a/cmd/rm.go b/cmd/rm.go index 874519fd..50b6d396 100644 --- a/cmd/rm.go +++ b/cmd/rm.go @@ -41,7 +41,8 @@ Quick example $ volt rm -r -p tyru/caw.vim # Remove tyru/caw.vim plugin from lock.json, and remove repository directory, plugconf Description - Uninstall {repository} on every profile. + Uninstall one or more {repository} from every profile. + This results in removing vim plugins from ~/.vim/pack/volt/opt/ directory. If {repository} is depended by other repositories, this command exits with an error. If -r option was given, remove also repository directories of specified repositories. @@ -84,7 +85,7 @@ func (cmd *rmCmd) Run(args []string) int { return 0 } -func (cmd *rmCmd) parseArgs(args []string) ([]string, error) { +func (cmd *rmCmd) parseArgs(args []string) ([]pathutil.ReposPath, error) { fs := cmd.FlagSet() fs.Parse(args) if cmd.helped { @@ -96,7 +97,7 @@ func (cmd *rmCmd) parseArgs(args []string) ([]string, error) { return nil, errors.New("repository was not given") } - var reposPathList []string + var reposPathList []pathutil.ReposPath for _, arg := range fs.Args() { reposPath, err := pathutil.NormalizeRepos(arg) if err != nil { @@ -107,7 +108,7 @@ func (cmd *rmCmd) parseArgs(args []string) ([]string, error) { return reposPathList, nil } -func (cmd *rmCmd) doRemove(reposPathList []string) error { +func (cmd *rmCmd) doRemove(reposPathList []pathutil.ReposPath) error { // Read lock.json lockJSON, err := lockjson.Read() if err != nil { @@ -130,7 +131,7 @@ func (cmd *rmCmd) doRemove(reposPathList []string) error { } if len(rdeps) > 0 { return fmt.Errorf("cannot remove '%s' because it's depended by '%s'", - reposPath, strings.Join(rdeps, "', '")) + reposPath, strings.Join(rdeps.Strings(), "', '")) } } @@ -138,7 +139,7 @@ func (cmd *rmCmd) doRemove(reposPathList []string) error { for _, reposPath := range reposPathList { // Remove repository directory if cmd.rmRepos { - fullReposPath := pathutil.FullReposPathOf(reposPath) + fullReposPath := pathutil.FullReposPath(reposPath) if pathutil.Exists(fullReposPath) { if err = cmd.removeRepos(fullReposPath); err != nil { return err @@ -151,7 +152,7 @@ func (cmd *rmCmd) doRemove(reposPathList []string) error { // Remove plugconf file if cmd.rmPlugconf { - plugconfPath := pathutil.PlugconfOf(reposPath) + plugconfPath := pathutil.Plugconf(reposPath) if pathutil.Exists(plugconfPath) { if err = cmd.removePlugconf(plugconfPath); err != nil { return err diff --git a/cmd/rm_test.go b/cmd/rm_test.go index e545f77f..baa1bb82 100644 --- a/cmd/rm_test.go +++ b/cmd/rm_test.go @@ -37,22 +37,22 @@ func TestVoltRmOnePlugin(t *testing.T) { out, err = testutil.RunVolt("rm", "tyru/caw.vim") // (A, B) testutil.SuccessExit(t, out, err) - reposPath := "github.com/tyru/caw.vim" + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") // (!C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if !pathutil.Exists(reposDir) { t.Error("repos was removed: " + reposDir) } // (!D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if !pathutil.Exists(plugconf) { t.Error("plugconf was removed: " + plugconf) } // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -78,22 +78,22 @@ func TestVoltRmRoptOnePlugin(t *testing.T) { out, err = testutil.RunVolt("rm", "-r", "tyru/caw.vim") // (A, B) testutil.SuccessExit(t, out, err) - reposPath := "github.com/tyru/caw.vim" + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") // (C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if pathutil.Exists(reposDir) { t.Error("repos was not removed: " + reposDir) } // (!D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if !pathutil.Exists(plugconf) { t.Error("plugconf was removed: " + plugconf) } // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -113,8 +113,8 @@ func TestVoltRmOnePluginNoPlugconf(t *testing.T) { out, err := testutil.RunVolt("get", "tyru/caw.vim") testutil.SuccessExit(t, out, err) - reposPath := "github.com/tyru/caw.vim" - if err := os.Remove(pathutil.PlugconfOf(reposPath)); err != nil { + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + if err := os.Remove(pathutil.Plugconf(reposPath)); err != nil { t.Error("failed to remove plugconf: " + err.Error()) } @@ -125,13 +125,13 @@ func TestVoltRmOnePluginNoPlugconf(t *testing.T) { testutil.SuccessExit(t, out, err) // (!C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if !pathutil.Exists(reposDir) { t.Error("repos was removed: " + reposDir) } // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -157,24 +157,24 @@ func TestVoltRmTwoOrMorePluginNoPlugconf(t *testing.T) { out, err = testutil.RunVolt("rm", "tyru/caw.vim", "tyru/capture.vim") // (A, B) testutil.SuccessExit(t, out, err) - cawReposPath := "github.com/tyru/caw.vim" - captureReposPath := "github.com/tyru/capture.vim" + cawReposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + captureReposPath := pathutil.ReposPath("github.com/tyru/capture.vim") - for _, reposPath := range []string{cawReposPath, captureReposPath} { + for _, reposPath := range []pathutil.ReposPath{cawReposPath, captureReposPath} { // (!C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if !pathutil.Exists(reposDir) { t.Error("repos was removed: " + reposDir) } // (!D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if !pathutil.Exists(plugconf) { t.Error("plugconf was removed: " + plugconf) } // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -195,8 +195,8 @@ func TestVoltRmOnePluginNoRepos(t *testing.T) { out, err := testutil.RunVolt("get", "tyru/caw.vim") testutil.SuccessExit(t, out, err) - reposPath := "github.com/tyru/caw.vim" - if err := os.RemoveAll(pathutil.FullReposPathOf(reposPath)); err != nil { + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + if err := os.RemoveAll(pathutil.FullReposPath(reposPath)); err != nil { t.Error("failed to remove repos: " + err.Error()) } @@ -207,13 +207,13 @@ func TestVoltRmOnePluginNoRepos(t *testing.T) { testutil.SuccessExit(t, out, err) // (!D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if !pathutil.Exists(plugconf) { t.Error("plugconf was removed: " + plugconf) } // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -233,11 +233,11 @@ func TestVoltRmOnePluginNoReposNoPlugconf(t *testing.T) { out, err := testutil.RunVolt("get", "tyru/caw.vim") testutil.SuccessExit(t, out, err) - reposPath := "github.com/tyru/caw.vim" - if err := os.RemoveAll(pathutil.FullReposPathOf(reposPath)); err != nil { + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + if err := os.RemoveAll(pathutil.FullReposPath(reposPath)); err != nil { t.Error("failed to remove repos: " + err.Error()) } - if err := os.Remove(pathutil.PlugconfOf(reposPath)); err != nil { + if err := os.Remove(pathutil.Plugconf(reposPath)); err != nil { t.Error("failed to remove plugconf: " + err.Error()) } @@ -248,7 +248,7 @@ func TestVoltRmOnePluginNoReposNoPlugconf(t *testing.T) { testutil.SuccessExit(t, out, err) // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -274,22 +274,22 @@ func TestVoltRmPoptOnePlugin(t *testing.T) { out, err = testutil.RunVolt("rm", "-p", "tyru/caw.vim") // (A, B) testutil.SuccessExit(t, out, err) - reposPath := "github.com/tyru/caw.vim" + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") // (!C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if !pathutil.Exists(reposDir) { t.Error("repos was removed: " + reposDir) } // (D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if pathutil.Exists(plugconf) { t.Error("plugconf was not removed: " + plugconf) } // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -309,8 +309,8 @@ func TestVoltRmPoptOnePluginNoPlugconf(t *testing.T) { out, err := testutil.RunVolt("get", "tyru/caw.vim") testutil.SuccessExit(t, out, err) - reposPath := "github.com/tyru/caw.vim" - if err := os.Remove(pathutil.PlugconfOf(reposPath)); err != nil { + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + if err := os.Remove(pathutil.Plugconf(reposPath)); err != nil { t.Error("failed to remove plugconf: " + err.Error()) } @@ -321,13 +321,13 @@ func TestVoltRmPoptOnePluginNoPlugconf(t *testing.T) { testutil.SuccessExit(t, out, err) // (!C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if !pathutil.Exists(reposDir) { t.Error("repos was removed: " + reposDir) } // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -347,8 +347,8 @@ func TestVoltRmPoptOnePluginNoRepos(t *testing.T) { out, err := testutil.RunVolt("get", "tyru/caw.vim") testutil.SuccessExit(t, out, err) - reposPath := "github.com/tyru/caw.vim" - if err := os.RemoveAll(pathutil.FullReposPathOf(reposPath)); err != nil { + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + if err := os.RemoveAll(pathutil.FullReposPath(reposPath)); err != nil { t.Error("failed to remove repos: " + err.Error()) } @@ -359,13 +359,13 @@ func TestVoltRmPoptOnePluginNoRepos(t *testing.T) { testutil.SuccessExit(t, out, err) // (D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if pathutil.Exists(plugconf) { t.Error("plugconf was not removed: " + plugconf) } // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -391,24 +391,24 @@ func TestVoltRmPoptTwoOrMorePluginNoPlugconf(t *testing.T) { out, err = testutil.RunVolt("rm", "-p", "tyru/caw.vim", "tyru/capture.vim") // (A, B) testutil.SuccessExit(t, out, err) - cawReposPath := "github.com/tyru/caw.vim" - captureReposPath := "github.com/tyru/capture.vim" + cawReposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + captureReposPath := pathutil.ReposPath("github.com/tyru/capture.vim") - for _, reposPath := range []string{cawReposPath, captureReposPath} { + for _, reposPath := range []pathutil.ReposPath{cawReposPath, captureReposPath} { // (!C) - reposDir := pathutil.FullReposPathOf(reposPath) + reposDir := pathutil.FullReposPath(reposPath) if !pathutil.Exists(reposDir) { t.Error("repos was removed: " + reposDir) } // (D) - plugconf := pathutil.PlugconfOf(reposPath) + plugconf := pathutil.Plugconf(reposPath) if pathutil.Exists(plugconf) { t.Error("plugconf was not removed: " + plugconf) } // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -429,11 +429,11 @@ func TestVoltRmPoptOnePluginNoReposNoPlugconf(t *testing.T) { out, err := testutil.RunVolt("get", "tyru/caw.vim") testutil.SuccessExit(t, out, err) - reposPath := "github.com/tyru/caw.vim" - if err := os.RemoveAll(pathutil.FullReposPathOf(reposPath)); err != nil { + reposPath := pathutil.ReposPath("github.com/tyru/caw.vim") + if err := os.RemoveAll(pathutil.FullReposPath(reposPath)); err != nil { t.Error("failed to remove repos: " + err.Error()) } - if err := os.Remove(pathutil.PlugconfOf(reposPath)); err != nil { + if err := os.Remove(pathutil.Plugconf(reposPath)); err != nil { t.Error("failed to remove plugconf: " + err.Error()) } @@ -444,7 +444,7 @@ func TestVoltRmPoptOnePluginNoReposNoPlugconf(t *testing.T) { testutil.SuccessExit(t, out, err) // (E) - vimReposDir := pathutil.PackReposPathOf(reposPath) + vimReposDir := pathutil.EncodeReposPath(reposPath) if pathutil.Exists(vimReposDir) { t.Error("vim repos was not removed: " + vimReposDir) } @@ -480,7 +480,7 @@ func TestErrVoltRmNotFound(t *testing.T) { testutil.FailExit(t, out, err) } -func testReposPathWereRemoved(t *testing.T, reposPath string) { +func testReposPathWereRemoved(t *testing.T, reposPath pathutil.ReposPath) { t.Helper() lockJSON, err := lockjson.Read() if err != nil { diff --git a/cmd/version.go b/cmd/version.go index f8943d7f..02497c6c 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -9,7 +9,7 @@ import ( "strconv" ) -var voltVersion string = "v0.2.3" +var voltVersion string = "v0.3.0" func init() { cmdMap["version"] = &versionCmd{} diff --git a/cmd/repos_util.go b/gitutil/gitutil.go similarity index 64% rename from cmd/repos_util.go rename to gitutil/gitutil.go index 6e3fb969..41c074b8 100644 --- a/cmd/repos_util.go +++ b/gitutil/gitutil.go @@ -1,4 +1,4 @@ -package cmd +package gitutil import ( "errors" @@ -16,8 +16,8 @@ var refHeadsRx = regexp.MustCompile(`^refs/heads/(.+)$`) // where {branch} is default branch // If the repository is non-bare: // Return the reference of current branch's HEAD -func getReposHEAD(reposPath string) (string, error) { - repos, err := git.PlainOpen(pathutil.FullReposPathOf(reposPath)) +func GetHEAD(reposPath pathutil.ReposPath) (string, error) { + repos, err := git.PlainOpen(pathutil.FullReposPath(reposPath)) if err != nil { return "", err } @@ -55,3 +55,28 @@ func getReposHEAD(reposPath string) (string, error) { } return ref.Hash().String(), nil } + +func SetUpstreamBranch(r *git.Repository) error { + cfg, err := r.Config() + if err != nil { + return err + } + + head, err := r.Head() + if err != nil { + return err + } + + refBranch := head.Name().String() + branch := refHeadsRx.FindStringSubmatch(refBranch) + if len(branch) == 0 { + return errors.New("HEAD is not matched to refs/heads/...: " + refBranch) + } + + sec := cfg.Raw.Section("branch") + subsec := sec.Subsection(branch[1]) + subsec.AddOption("remote", "origin") + subsec.AddOption("merge", refBranch) + + return r.Storer.SetConfig(cfg) +} diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go index 44128d32..c6783348 100644 --- a/internal/testutil/testutil.go +++ b/internal/testutil/testutil.go @@ -120,7 +120,7 @@ func GetCmdList() ([]string, error) { // Set up $VOLTPATH after "volt get " // but the same repository is cloned only at first time // under testdata/voltpath/{testdataName}/repos/ -func SetUpRepos(t *testing.T, testdataName string, rType lockjson.ReposType, reposPathList []string, strategy string) func() { +func SetUpRepos(t *testing.T, testdataName string, rType lockjson.ReposType, reposPathList []pathutil.ReposPath, strategy string) func() { voltpath := os.Getenv("VOLTPATH") tmpVoltpath := filepath.Join(testdataDir, "voltpath", testdataName) localSrcDir := filepath.Join(testdataDir, "local", testdataName) @@ -128,7 +128,7 @@ func SetUpRepos(t *testing.T, testdataName string, rType lockjson.ReposType, rep buf := make([]byte, 32*1024) for _, reposPath := range reposPathList { - testRepos := filepath.Join(tmpVoltpath, "repos", reposPath) + testRepos := filepath.Join(tmpVoltpath, "repos", reposPath.String()) if !pathutil.Exists(testRepos) { switch rType { case lockjson.ReposGitType: @@ -145,7 +145,7 @@ func SetUpRepos(t *testing.T, testdataName string, rType lockjson.ReposType, rep t.Fatal("failed to set VOLTPATH") } defer os.Setenv("VOLTPATH", voltpath) - out, err := RunVolt("get", reposPath) + out, err := RunVolt("get", reposPath.String()) SuccessExit(t, out, err) case lockjson.ReposStaticType: err := os.Setenv("VOLTPATH", tmpVoltpath) @@ -165,7 +165,7 @@ func SetUpRepos(t *testing.T, testdataName string, rType lockjson.ReposType, rep } // Copy repository - repos := filepath.Join(voltpath, "repos", reposPath) + repos := filepath.Join(voltpath, "repos", reposPath.String()) os.MkdirAll(filepath.Dir(repos), 0777) if err := fileutil.CopyDir(testRepos, repos, buf, 0777, os.FileMode(0)); err != nil { t.Fatalf("failed to copy %s to %s", testRepos, repos) @@ -182,7 +182,7 @@ func SetUpRepos(t *testing.T, testdataName string, rType lockjson.ReposType, rep if strategy == config.SymlinkBuilder { return func() { for _, reposPath := range reposPathList { - dir := filepath.Join(tmpVoltpath, "repos", reposPath, "doc") + dir := filepath.Join(tmpVoltpath, "repos", reposPath.String(), "doc") for _, name := range []string{"tags", "tags-ja"} { path := filepath.Join(dir, name) os.Remove(path) diff --git a/lockjson/lockjson.go b/lockjson/lockjson.go index c154525f..44db85a3 100644 --- a/lockjson/lockjson.go +++ b/lockjson/lockjson.go @@ -33,19 +33,17 @@ const ( ) type Repos struct { - Type ReposType `json:"type"` - TrxID int64 `json:"trx_id"` - Path string `json:"path"` - Version string `json:"version"` + Type ReposType `json:"type"` + TrxID int64 `json:"trx_id"` + Path pathutil.ReposPath `json:"path"` + Version string `json:"version"` } -type profReposPath []string +type profReposPath []pathutil.ReposPath type Profile struct { Name string `json:"name"` ReposPath profReposPath `json:"repos_path"` - UseVimrc bool `json:"use_vimrc"` - UseGvimrc bool `json:"use_gvimrc"` } const lockJSONVersion = 2 @@ -59,9 +57,7 @@ func initialLockJSON() *LockJSON { Profiles: []Profile{ Profile{ Name: "default", - ReposPath: make([]string, 0), - UseVimrc: true, - UseGvimrc: true, + ReposPath: make([]pathutil.ReposPath, 0), }, }, } @@ -128,14 +124,18 @@ func validate(lockJSON *LockJSON) error { return err } - // Validate if duplicate repos[]/path exist dup := make(map[string]bool, len(lockJSON.Repos)) for i := range lockJSON.Repos { repos := &lockJSON.Repos[i] - if _, exists := dup[repos.Path]; exists { - return errors.New("duplicate repos '" + repos.Path + "'") + // Validate if repos[]/path is invalid format + if _, err := pathutil.NormalizeRepos(repos.Path.String()); err != nil { + return errors.New("'" + repos.Path.String() + "' is invalid repos path") } - dup[repos.Path] = true + // Validate if duplicate repos[]/path exist + if _, exists := dup[repos.Path.String()]; exists { + return errors.New("duplicate repos '" + repos.Path.String() + "'") + } + dup[repos.Path.String()] = true } // Validate if duplicate profiles[]/name exist @@ -148,15 +148,19 @@ func validate(lockJSON *LockJSON) error { dup[profile.Name] = true } - // Validate if duplicate profiles[]/repos_path[] exist for i := range lockJSON.Profiles { profile := &lockJSON.Profiles[i] dup = make(map[string]bool, len(lockJSON.Profiles)*10) for _, reposPath := range profile.ReposPath { - if _, exists := dup[reposPath]; exists { - return errors.New("duplicate '" + reposPath + "' (repos_path) in profile '" + profile.Name + "'") + // Validate if profiles[]/repos_path[] is invalid format + if _, err := pathutil.NormalizeRepos(reposPath.String()); err != nil { + return errors.New("'" + reposPath.String() + "' is invalid repos path") + } + // Validate if duplicate profiles[]/repos_path[] exist + if _, exists := dup[reposPath.String()]; exists { + return errors.New("duplicate '" + reposPath.String() + "' (repos_path) in profile '" + profile.Name + "'") } - dup[reposPath] = true + dup[reposPath.String()] = true } } @@ -176,14 +180,14 @@ func validate(lockJSON *LockJSON) error { // Validate if profiles[]/repos_path[] exists in repos[]/path reposMap := make(map[string]*Repos, len(lockJSON.Repos)) for i := range lockJSON.Repos { - reposMap[lockJSON.Repos[i].Path] = &lockJSON.Repos[i] + reposMap[lockJSON.Repos[i].Path.String()] = &lockJSON.Repos[i] } for i := range lockJSON.Profiles { profile := &lockJSON.Profiles[i] for j, reposPath := range profile.ReposPath { - if _, exists := reposMap[reposPath]; !exists { + if _, exists := reposMap[reposPath.String()]; !exists { return errors.New( - "'" + reposPath + "' (profiles[" + strconv.Itoa(i) + + "'" + reposPath.String() + "' (profiles[" + strconv.Itoa(i) + "].repos_path[" + strconv.Itoa(j) + "]) doesn't exist in repos") } } @@ -232,7 +236,7 @@ func validateMissing(lockJSON *LockJSON) error { if repos.TrxID == 0 { return errors.New("missing: repos[" + strconv.Itoa(i) + "].trx_id") } - if repos.Path == "" { + if repos.Path.String() == "" { return errors.New("missing: repos[" + strconv.Itoa(i) + "].path") } default: @@ -251,7 +255,7 @@ func validateMissing(lockJSON *LockJSON) error { return errors.New("missing: profile[" + strconv.Itoa(i) + "].repos_path") } for j, reposPath := range profile.ReposPath { - if reposPath == "" { + if reposPath.String() == "" { return errors.New("missing: profile[" + strconv.Itoa(i) + "].repos_path[" + strconv.Itoa(j) + "]") } } @@ -301,7 +305,7 @@ func (profs *ProfileList) FindIndexByName(name string) int { return -1 } -func (profs *ProfileList) RemoveAllReposPath(reposPath string) error { +func (profs *ProfileList) RemoveAllReposPath(reposPath pathutil.ReposPath) error { removed := false for i := range *profs { for j := 0; j < len((*profs)[i].ReposPath); { @@ -317,41 +321,41 @@ func (profs *ProfileList) RemoveAllReposPath(reposPath string) error { } } if !removed { - return errors.New("no matching profiles[]/repos_path[]: " + reposPath) + return errors.New("no matching profiles[]/repos_path[]: " + reposPath.String()) } return nil } -func (reposList *ReposList) Contains(reposPath string) bool { +func (reposList *ReposList) Contains(reposPath pathutil.ReposPath) bool { _, err := reposList.FindByPath(reposPath) return err == nil } -func (reposList *ReposList) FindByPath(reposPath string) (*Repos, error) { +func (reposList *ReposList) FindByPath(reposPath pathutil.ReposPath) (*Repos, error) { for i := range *reposList { repos := &(*reposList)[i] if repos.Path == reposPath { return repos, nil } } - return nil, errors.New("repos '" + reposPath + "' does not exist") + return nil, errors.New("repos '" + reposPath.String() + "' does not exist") } -func (reposList *ReposList) RemoveAllByPath(reposPath string) error { +func (reposList *ReposList) RemoveAllByPath(reposPath pathutil.ReposPath) error { for i := range *reposList { if (*reposList)[i].Path == reposPath { *reposList = append((*reposList)[:i], (*reposList)[i+1:]...) return nil } } - return errors.New("no matching repos[]/path: " + reposPath) + return errors.New("no matching repos[]/path: " + reposPath.String()) } -func (reposPathList *profReposPath) Contains(reposPath string) bool { +func (reposPathList *profReposPath) Contains(reposPath pathutil.ReposPath) bool { return reposPathList.IndexOf(reposPath) >= 0 } -func (reposPathList *profReposPath) IndexOf(reposPath string) int { +func (reposPathList *profReposPath) IndexOf(reposPath pathutil.ReposPath) int { for i := range *reposPathList { if (*reposPathList)[i] == reposPath { return i diff --git a/pathutil/pathutil.go b/pathutil/pathutil.go index 5f5db588..86b0df30 100644 --- a/pathutil/pathutil.go +++ b/pathutil/pathutil.go @@ -13,30 +13,49 @@ import ( // 1. user/name[.git] // 2. github.com/user/name[.git] // 3. [git|http|https]://github.com/user/name[.git] -func NormalizeRepos(rawReposPath string) (string, error) { +func NormalizeRepos(rawReposPath string) (ReposPath, error) { rawReposPath = filepath.ToSlash(rawReposPath) paths := strings.Split(rawReposPath, "/") if len(paths) == 3 { - return strings.TrimSuffix(rawReposPath, ".git"), nil + return ReposPath(strings.TrimSuffix(rawReposPath, ".git")), nil } if len(paths) == 2 { - return strings.TrimSuffix("github.com/"+rawReposPath, ".git"), nil + return ReposPath(strings.TrimSuffix("github.com/"+rawReposPath, ".git")), nil } if paths[0] == "https:" || paths[0] == "http:" || paths[0] == "git:" { - reposPath := strings.Join(paths[len(paths)-3:], "/") - return strings.TrimSuffix(reposPath, ".git"), nil + path := strings.Join(paths[len(paths)-3:], "/") + return ReposPath(strings.TrimSuffix(path, ".git")), nil } - return "", errors.New("invalid format of repository: " + rawReposPath) + return ReposPath(""), errors.New("invalid format of repository: " + rawReposPath) } -func NormalizeLocalRepos(name string) (string, error) { +type ReposPath string +type ReposPathList []ReposPath + +func (path *ReposPath) String() string { + return string(*path) +} + +func (list ReposPathList) Strings() []string { + // TODO: Use unsafe + result := make([]string, 0, len(list)) + for i := range list { + result = append(result, string(list[i])) + } + return result +} + +func NormalizeLocalRepos(name string) (ReposPath, error) { if !strings.Contains(name, "/") { - return "localhost/local/" + name, nil + return ReposPath("localhost/local/" + name), nil } else { return NormalizeRepos(name) } } +// Detect HOME path. +// If HOME environment variable is not set, +// use USERPROFILE environment variable instead. func HomeDir() string { home := os.Getenv("HOME") if home != "" { @@ -51,6 +70,7 @@ func HomeDir() string { panic("Couldn't look up HOME") } +// $HOME/volt func VoltPath() string { path := os.Getenv("VOLTPATH") if path != "" { @@ -59,8 +79,8 @@ func VoltPath() string { return filepath.Join(HomeDir(), "volt") } -func FullReposPathOf(reposPath string) string { - reposList := strings.Split(filepath.ToSlash(reposPath), "/") +func FullReposPath(reposPath ReposPath) string { + reposList := strings.Split(filepath.ToSlash(reposPath.String()), "/") paths := make([]string, 0, len(reposList)+2) paths = append(paths, VoltPath()) paths = append(paths, "repos") @@ -68,12 +88,13 @@ func FullReposPathOf(reposPath string) string { return filepath.Join(paths...) } -func CloneURLOf(reposPath string) string { - return "https://" + filepath.ToSlash(reposPath) +// https://{reposPath} +func CloneURL(reposPath ReposPath) string { + return "https://" + filepath.ToSlash(reposPath.String()) } -func PlugconfOf(reposPath string) string { - filenameList := strings.Split(filepath.ToSlash(reposPath+".vim"), "/") +func Plugconf(reposPath ReposPath) string { + filenameList := strings.Split(filepath.ToSlash(reposPath.String()+".vim"), "/") paths := make([]string, 0, len(filenameList)+2) paths = append(paths, VoltPath()) paths = append(paths, "plugconf") @@ -86,6 +107,7 @@ const ProfileGvimrc = "gvimrc.vim" const Vimrc = "vimrc" const Gvimrc = "gvimrc" +// $HOME/volt/rc/{profileName} func RCDir(profileName string) string { return filepath.Join([]string{VoltPath(), "rc", profileName}...) } @@ -94,32 +116,43 @@ var packer = strings.NewReplacer("_", "__", "/", "_") var unpacker1 = strings.NewReplacer("_", "/") var unpacker2 = strings.NewReplacer("//", "_") -func PackReposPathOf(reposPath string) string { - path := packer.Replace(reposPath) +// Encode repos path to directory name. +// The directory name is: ~/.vim/pack/volt/opt/{name} +func EncodeReposPath(reposPath ReposPath) string { + path := packer.Replace(reposPath.String()) return filepath.Join(VimVoltOptDir(), path) } -func UnpackPathOf(path string) (reposPath string) { - path = filepath.Base(path) - return unpacker2.Replace(unpacker1.Replace(path)) +// Decode name to repos path. +// name is directory name: ~/.vim/pack/volt/opt/{name} +func DecodeReposPath(name string) ReposPath { + name = filepath.Base(name) + return ReposPath(unpacker2.Replace(unpacker1.Replace(name))) } +// $HOME/volt/lock.json func LockJSON() string { return filepath.Join(VoltPath(), "lock.json") } +// $HOME/volt/config.toml func ConfigTOML() string { return filepath.Join(VoltPath(), "config.toml") } +// $HOME/volt/trx.lock func TrxLock() string { return filepath.Join(VoltPath(), "trx.lock") } -func TempPath() string { +// $HOME/tmp +func TempDir() string { return filepath.Join(VoltPath(), "tmp") } +// Detect vim executable path. +// If VOLT_VIM environment variable is set, use it. +// Otherwise look up "vim" binary from PATH. func VimExecutable() (string, error) { var vim string if vim = os.Getenv("VOLT_VIM"); vim != "" { @@ -132,6 +165,8 @@ func VimExecutable() (string, error) { return exec.LookPath(exeName) } +// Windows: $HOME/vimfiles +// Otherwise: $HOME/.vim func VimDir() string { if runtime.GOOS == "windows" { return filepath.Join(HomeDir(), "vimfiles") @@ -140,30 +175,36 @@ func VimDir() string { } } +// (vim dir)/pack/volt func VimVoltDir() string { return filepath.Join(VimDir(), "pack", "volt") } +// (vim dir)/pack/volt/opt func VimVoltOptDir() string { return filepath.Join(VimDir(), "pack", "volt", "opt") } +// (vim dir)/pack/volt/start func VimVoltStartDir() string { return filepath.Join(VimDir(), "pack", "volt", "start") } +// (vim dir)/pack/volt/build-info.json func BuildInfoJSON() string { return filepath.Join(VimVoltDir(), "build-info.json") } +// (vim dir)/pack/volt/start/system/plugin/bundled_plugconf.vim func BundledPlugConf() string { return filepath.Join(VimVoltStartDir(), "system", "plugin", "bundled_plugconf.vim") } -func LookUpVimrcOrGvimrc() []string { - return append(LookUpVimrc(), LookUpGvimrc()...) -} - +// Look up vimrc path from the following candidates: +// Windows : $HOME/_vimrc +// (vim dir)/vimrc +// Otherwise: $HOME/.vimrc +// (vim dir)/vimrc func LookUpVimrc() []string { var vimrcPaths []string if runtime.GOOS == "windows" { @@ -187,6 +228,11 @@ func LookUpVimrc() []string { return vimrcPaths } +// Look up gvimrc path from the following candidates: +// Windows : $HOME/_gvimrc +// (vim dir)/gvimrc +// Otherwise: $HOME/.gvimrc +// (vim dir)/gvimrc func LookUpGvimrc() []string { var gvimrcPaths []string if runtime.GOOS == "windows" { @@ -210,6 +256,8 @@ func LookUpGvimrc() []string { return gvimrcPaths } +// Returns true if path exists, otherwise returns false. +// Existence is checked by os.Lstat(). func Exists(path string) bool { _, err := os.Lstat(path) return !os.IsNotExist(err) diff --git a/pathutil/pathutil_test.go b/pathutil/pathutil_test.go index 431d25c9..a49c16e7 100644 --- a/pathutil/pathutil_test.go +++ b/pathutil/pathutil_test.go @@ -5,20 +5,20 @@ import "testing" func TestNormalizeRepos(t *testing.T) { var tests = []struct { in string - out string + out ReposPath }{ - {"user/name", "github.com/user/name"}, - {"user/name.git", "github.com/user/name"}, - {"github.com/user/name", "github.com/user/name"}, - {"github.com/user/name.git", "github.com/user/name"}, - {"https://github.com/user/name", "github.com/user/name"}, - {"https://github.com/user/name.git", "github.com/user/name"}, - {"http://github.com/user/name", "github.com/user/name"}, - {"http://github.com/user/name.git", "github.com/user/name"}, - {"git://github.com/user/name", "github.com/user/name"}, - {"git://github.com/user/name.git", "github.com/user/name"}, - {"localhost/local/name", "localhost/local/name"}, - {"localhost/local/name.git", "localhost/local/name"}, + {"user/name", ReposPath("github.com/user/name")}, + {"user/name.git", ReposPath("github.com/user/name")}, + {"github.com/user/name", ReposPath("github.com/user/name")}, + {"github.com/user/name.git", ReposPath("github.com/user/name")}, + {"https://github.com/user/name", ReposPath("github.com/user/name")}, + {"https://github.com/user/name.git", ReposPath("github.com/user/name")}, + {"http://github.com/user/name", ReposPath("github.com/user/name")}, + {"http://github.com/user/name.git", ReposPath("github.com/user/name")}, + {"git://github.com/user/name", ReposPath("github.com/user/name")}, + {"git://github.com/user/name.git", ReposPath("github.com/user/name")}, + {"localhost/local/name", ReposPath("localhost/local/name")}, + {"localhost/local/name.git", ReposPath("localhost/local/name")}, } for _, tt := range tests { result, err := NormalizeRepos(tt.in) diff --git a/plugconf/plugconf.go b/plugconf/plugconf.go index f72b815d..4e4c7915 100644 --- a/plugconf/plugconf.go +++ b/plugconf/plugconf.go @@ -31,17 +31,17 @@ const ( type Plugconf struct { reposID int - reposPath string + reposPath pathutil.ReposPath functions []string configFunc string loadOnFunc string loadOn loadOnType loadOnArg string dependsFunc string - depends []string + depends pathutil.ReposPathList } -func ParsePlugconfFile(plugConf string, reposID int, reposPath string) (*Plugconf, error) { +func ParsePlugconfFile(plugConf string, reposID int, reposPath pathutil.ReposPath) (*Plugconf, error) { content, err := ioutil.ReadFile(plugConf) if err != nil { return nil, err @@ -67,7 +67,7 @@ func ParsePlugconf(file *ast.File, src string) (*Plugconf, error) { var configFunc string var functions []string var dependsFunc string - var depends []string + var depends pathutil.ReposPathList var parseErr error // Inspect nodes and get above values from plugconf script @@ -100,7 +100,11 @@ func ParsePlugconf(file *ast.File, src string) (*Plugconf, error) { configFunc = extractBody(fn, src) case "s:depends": dependsFunc = extractBody(fn, src) - depends = getDependencies(fn, src) + var err error + depends, err = getDependencies(fn, src) + if err != nil { + parseErr = err + } default: functions = append(functions, extractBody(fn, src)) } @@ -173,8 +177,9 @@ func extractBody(fn *ast.Function, src string) string { return src[pos.Offset:endpos.Offset] } -func getDependencies(fn *ast.Function, src string) []string { - var deps []string +func getDependencies(fn *ast.Function, src string) (pathutil.ReposPathList, error) { + var deps pathutil.ReposPathList + var parseErr error ast.Inspect(fn, func(node ast.Node) bool { // Cast to return node (return if it's not a return node) @@ -188,10 +193,15 @@ func getDependencies(fn *ast.Function, src string) []string { for i := range list.Values { if str, ok := list.Values[i].(*ast.BasicLit); ok { if deps == nil { - deps = make([]string, 0, len(list.Values)) + deps = make(pathutil.ReposPathList, 0, len(list.Values)) } if str.Kind == token.STRING { - deps = append(deps, str.Value[1:len(str.Value)-1]) + reposPath, err := pathutil.NormalizeRepos(str.Value[1 : len(str.Value)-1]) + if err != nil { + parseErr = err + return false + } + deps = append(deps, reposPath) } } } @@ -199,18 +209,18 @@ func getDependencies(fn *ast.Function, src string) []string { return true }) - return deps + return deps, parseErr } // s:loaded_on() function is not included -func makeBundledPlugconf(reposList []lockjson.Repos, plugconf map[string]*Plugconf) []byte { +func makeBundledPlugconf(reposList []lockjson.Repos, plugconf map[pathutil.ReposPath]*Plugconf) []byte { functions := make([]string, 0, 64) loadCmds := make([]string, 0, len(reposList)) for _, repos := range reposList { p, exists := plugconf[repos.Path] // :packadd - optName := filepath.Base(pathutil.PackReposPathOf(repos.Path)) + optName := filepath.Base(pathutil.EncodeReposPath(repos.Path)) packadd := fmt.Sprintf("packadd %s", optName) // autocommand event & patterns var loadOn string @@ -268,11 +278,11 @@ let g:loaded_volt_system_bundled_plugconf = 1`) var rxFuncName = regexp.MustCompile(`^(fu\w+!?\s+s:\w+)`) -func convertToDecodableFunc(funcBody string, reposPath string, reposID int) string { +func convertToDecodableFunc(funcBody string, reposPath pathutil.ReposPath, reposID int) string { // Change function name (e.g. s:loaded_on() -> s:loaded_on_1()) funcBody = rxFuncName.ReplaceAllString(funcBody, fmt.Sprintf("${1}_%d", reposID)) // Add repos path as comment - funcBody = "\" " + reposPath + "\n" + funcBody + funcBody = "\" " + reposPath.String() + "\n" + funcBody return funcBody } @@ -296,7 +306,7 @@ func GenerateBundlePlugconf(reposList []lockjson.Repos) ([]byte, *multierror.Err return makeBundledPlugconf(reposList, plugconfMap), nil } -func RdepsOf(reposPath string, reposList []lockjson.Repos) ([]string, error) { +func RdepsOf(reposPath pathutil.ReposPath, reposList []lockjson.Repos) (pathutil.ReposPathList, error) { plugconfMap, merr := parsePlugconfAsMap(reposList) if merr.ErrorOrNil() != nil { return nil, merr @@ -304,20 +314,20 @@ func RdepsOf(reposPath string, reposList []lockjson.Repos) ([]string, error) { _, _, rdepsMap := getDepMaps(reposList, plugconfMap) rdeps := rdepsMap[reposPath] if rdeps == nil { - rdeps = make([]string, 0) + rdeps = make(pathutil.ReposPathList, 0) } return rdeps, nil } // Parse plugconf of reposList and return parsed plugconf info as map -func parsePlugconfAsMap(reposList []lockjson.Repos) (map[string]*Plugconf, *multierror.Error) { +func parsePlugconfAsMap(reposList []lockjson.Repos) (map[pathutil.ReposPath]*Plugconf, *multierror.Error) { var merr *multierror.Error - plugconfMap := make(map[string]*Plugconf, len(reposList)) + plugconfMap := make(map[pathutil.ReposPath]*Plugconf, len(reposList)) reposID := 1 for _, repos := range reposList { var parsed *Plugconf var err error - path := pathutil.PlugconfOf(repos.Path) + path := pathutil.Plugconf(repos.Path) if pathutil.Exists(path) { parsed, err = ParsePlugconfFile(path, reposID, repos.Path) } else { @@ -335,9 +345,9 @@ func parsePlugconfAsMap(reposList []lockjson.Repos) (map[string]*Plugconf, *mult // Move the plugins which was depended to previous plugin which depends to them. // reposList is sorted in-place. -func sortByDepends(reposList []lockjson.Repos, plugconfMap map[string]*Plugconf) { +func sortByDepends(reposList []lockjson.Repos, plugconfMap map[pathutil.ReposPath]*Plugconf) { reposMap, depsMap, rdepsMap := getDepMaps(reposList, plugconfMap) - rank := make(map[string]int, len(reposList)) + rank := make(map[pathutil.ReposPath]int, len(reposList)) for i := range reposList { if _, exists := rank[reposList[i].Path]; !exists { tree := makeDepTree(reposList[i].Path, reposMap, depsMap, rdepsMap) @@ -351,10 +361,10 @@ func sortByDepends(reposList []lockjson.Repos, plugconfMap map[string]*Plugconf) }) } -func getDepMaps(reposList []lockjson.Repos, plugconfMap map[string]*Plugconf) (map[string]*lockjson.Repos, map[string][]string, map[string][]string) { - reposMap := make(map[string]*lockjson.Repos, len(reposList)) - depsMap := make(map[string][]string, len(reposList)) - rdepsMap := make(map[string][]string, len(reposList)) +func getDepMaps(reposList []lockjson.Repos, plugconfMap map[pathutil.ReposPath]*Plugconf) (map[pathutil.ReposPath]*lockjson.Repos, map[pathutil.ReposPath]pathutil.ReposPathList, map[pathutil.ReposPath]pathutil.ReposPathList) { + reposMap := make(map[pathutil.ReposPath]*lockjson.Repos, len(reposList)) + depsMap := make(map[pathutil.ReposPath]pathutil.ReposPathList, len(reposList)) + rdepsMap := make(map[pathutil.ReposPath]pathutil.ReposPathList, len(reposList)) for i := range reposList { reposPath := reposList[i].Path reposMap[reposPath] = &reposList[i] @@ -368,8 +378,8 @@ func getDepMaps(reposList []lockjson.Repos, plugconfMap map[string]*Plugconf) (m return reposMap, depsMap, rdepsMap } -func makeDepTree(reposPath string, reposMap map[string]*lockjson.Repos, depsMap map[string][]string, rdepsMap map[string][]string) *reposDepTree { - visited := make(map[string]*reposDepNode, len(reposMap)) +func makeDepTree(reposPath pathutil.ReposPath, reposMap map[pathutil.ReposPath]*lockjson.Repos, depsMap map[pathutil.ReposPath]pathutil.ReposPathList, rdepsMap map[pathutil.ReposPath]pathutil.ReposPathList) *reposDepTree { + visited := make(map[pathutil.ReposPath]*reposDepNode, len(reposMap)) node := makeNodes(visited, reposPath, reposMap, depsMap, rdepsMap) leaves := make([]reposDepNode, 0, 10) visitNode(node, func(n *reposDepNode) { @@ -380,7 +390,7 @@ func makeDepTree(reposPath string, reposMap map[string]*lockjson.Repos, depsMap return &reposDepTree{leaves: leaves} } -func makeNodes(visited map[string]*reposDepNode, reposPath string, reposMap map[string]*lockjson.Repos, depsMap map[string][]string, rdepsMap map[string][]string) *reposDepNode { +func makeNodes(visited map[pathutil.ReposPath]*reposDepNode, reposPath pathutil.ReposPath, reposMap map[pathutil.ReposPath]*lockjson.Repos, depsMap map[pathutil.ReposPath]pathutil.ReposPathList, rdepsMap map[pathutil.ReposPath]pathutil.ReposPathList) *reposDepNode { if node, exists := visited[reposPath]; exists { return node } @@ -398,11 +408,11 @@ func makeNodes(visited map[string]*reposDepNode, reposPath string, reposMap map[ } func visitNode(node *reposDepNode, callback func(*reposDepNode)) { - visited := make(map[string]bool, 10) + visited := make(map[pathutil.ReposPath]bool, 10) doVisitNode(visited, node, callback) } -func doVisitNode(visited map[string]bool, node *reposDepNode, callback func(*reposDepNode)) { +func doVisitNode(visited map[pathutil.ReposPath]bool, node *reposDepNode, callback func(*reposDepNode)) { if node == nil || node.repos == nil || visited[node.repos.Path] { return } @@ -416,15 +426,15 @@ func doVisitNode(visited map[string]bool, node *reposDepNode, callback func(*rep } } -func makeRank(rank map[string]int, node *reposDepNode, value int) { +func makeRank(rank map[pathutil.ReposPath]int, node *reposDepNode, value int) { rank[node.repos.Path] = value for i := range node.dependedBy { makeRank(rank, &node.dependedBy[i], value+1) } } -func FetchPlugconf(reposPath string) (string, error) { - url := path.Join("https://raw.githubusercontent.com/vim-volt/plugconf-templates/master/templates", reposPath+".vim") +func FetchPlugconf(reposPath pathutil.ReposPath) (string, error) { + url := path.Join("https://raw.githubusercontent.com/vim-volt/plugconf-templates/master/templates", reposPath.String()+".vim") return httputil.GetContentString(url) }