├── etc ├── repos-map.default └── login ├── LICENSE.txt └── bin └── vvm /etc/repos-map.default: -------------------------------------------------------------------------------- 1 | # $fork $vcs $repos_uri 2 | 3 | macvim git https://github.com/macvim-dev/macvim.git 4 | vimorg git https://github.com/vim/vim.git 5 | -------------------------------------------------------------------------------- /etc/login: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | __vvm_completions() 4 | { 5 | if [ "${#COMP_WORDS[@]}" == "2" ]; then 6 | COMPREPLY=($(compgen -W "fetch help install list rebuild reinstall setup uninstall update_itself use" "${COMP_WORDS[1]}")) 7 | elif [ "${#COMP_WORDS[@]}" == "3" ]; then 8 | local vims=$(echo $HOME/.vvm/vims/* | sed "s@$HOME/.vvm/vims/@@g") 9 | COMPREPLY=($(compgen -W "$vims" "${COMP_WORDS[2]}")) 10 | else 11 | return 12 | fi 13 | } 14 | 15 | __vvm_configure_path() 16 | { 17 | local vvm_bin_path="$HOME/.vvm/bin:$HOME/.vvm/vims/current/bin" 18 | 19 | case "$SHELL" in 20 | */bash) 21 | complete -F __vvm_completions vvm 22 | ;; 23 | *) 24 | ;; 25 | esac 26 | 27 | echo "$PATH" | grep -Fqv "$vvm_bin_path" && 28 | PATH="$vvm_bin_path:$PATH" 29 | } 30 | 31 | 32 | __vvm_configure_path 33 | 34 | # __END__ 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2013 Kana Natsuno 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /bin/vvm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # FIXME: Add tests. 3 | # FIXME: Refine implementation of subcommands. 4 | 5 | import optparse 6 | import os 7 | import os.path 8 | import re 9 | import shutil 10 | import sys 11 | 12 | 13 | 14 | 15 | class VimVersionManager: 16 | # Directory structure: 17 | # ~/.vvm The root directory for Vim Version Manager. 18 | # bin For command-line utilities. 19 | # etc For configuration files. 20 | # repos Place to store repository of each fork. 21 | # $fork 22 | # ... 23 | # src Place to store source code of each version. 24 | # $fork--$tag Naming is the same as ~/.vvm/vims. 25 | # ... 26 | # vims Place to store deployed versions. 27 | # current Symbolic link for an install directory. 28 | # $fork--$tag This is the "version" format. 29 | # vimorg--v7-3-254 Example: Install directory for the original one. 30 | # macvim--v7.3-53 Example: Install directory for MacVim. 31 | # ... 32 | 33 | vvm_repos_uri = 'https://github.com/kana/vim-version-manager.git' 34 | 35 | # Driver:: 36 | 37 | def main(self, args): 38 | p = VvmOptionParser(version='%prog 2.0.0') 39 | (options, left_args) = p.parse_args(args[1:]) 40 | if len(left_args) == 0: 41 | left_args = ['help'] 42 | subcmd_name = left_args[0] 43 | try: 44 | subcmd_func = getattr(self, 'cmd_%s' % subcmd_name) 45 | except AttributeError: 46 | die('Subcommand "%s" is not available.' % subcmd_name) 47 | subcmd_func(left_args) 48 | return 49 | 50 | # Subcommands:: 51 | 52 | def cmd_fetch(self, args): 53 | '''Fetch source code of a specific version of Vim.''' 54 | p = VvmOptionParser() 55 | p.usage = '%prog fetch ' 56 | (options, left_args) = p.parse_args(args[1:]) 57 | if len(left_args) == 0: 58 | die('Please specify a version of Vim to fetch its source code.') 59 | version = left_args[0] 60 | return ( 61 | self.fetch(version) 62 | and self.checkout(version) 63 | ) 64 | 65 | def cmd_help(self, args): 66 | # FIXME: Add more useful description. 67 | '''Show help message to use Vim Version Manager.''' 68 | subcmd_names = [n for n in dir(self) if n.startswith('cmd_')] 69 | subcmd_names.sort() 70 | longest_subcmd_name = max(len(n) for n in subcmd_names) 71 | print('Available commands:') 72 | for n in subcmd_names: 73 | print(' %s%s%s' % ( 74 | n[len('cmd_'):], 75 | ' ' * (longest_subcmd_name - len(n) + 2), 76 | getattr(self, n).__doc__.split('\n')[0] 77 | )) 78 | return True 79 | 80 | def cmd_install(self, args): 81 | '''Install a specific version of Vim.''' 82 | p = VvmOptionParser() 83 | p.usage = '%prog install [...]' 84 | (options, left_args) = p.parse_args(args[1:]) 85 | if len(left_args) == 0: 86 | die('Please specify a version of Vim to install.') 87 | version = left_args[0] 88 | configure_options = left_args[1:] 89 | vims_dir = get_vims_dir(version) 90 | if isdir(vims_dir): 91 | echo('%s is already installed.' % version) 92 | return 93 | return ( 94 | self.fetch(version) 95 | and self.checkout(version) 96 | and self.configure(version, configure_options) 97 | and self.make_install(version) 98 | ) 99 | 100 | def cmd_list(self, args): 101 | '''List currently installed versions of Vim.''' 102 | current_version = get_current_version() 103 | versions = listdir(get_vims_dir_itself()) 104 | versions.sort() 105 | for version in versions: 106 | if isdir(get_vims_dir(version)) and version != 'current': 107 | mark = '*' if version == current_version else ' ' 108 | print(mark, version) 109 | return True 110 | 111 | def cmd_rebuild(self, args): 112 | '''Rebuild a specific version of Vim, then install it.''' 113 | p = VvmOptionParser() 114 | p.usage = '%prog rebuild [...]' 115 | (options, left_args) = p.parse_args(args[1:]) 116 | if len(left_args) == 0: 117 | die('Please specify a version of Vim to rebuild.') 118 | version = left_args[0] 119 | configure_options = left_args[1:] 120 | return ( 121 | self.make_clean(version) 122 | and self.configure(version, configure_options) 123 | and self.make_install(version) 124 | ) 125 | 126 | def cmd_reinstall(self, args): 127 | '''Reinstall a specific version of Vim.''' 128 | p = VvmOptionParser() 129 | p.usage = '%prog reinstall [...]' 130 | (options, left_args) = p.parse_args(args[1:]) 131 | if len(left_args) == 0: 132 | die('Please specify a version of Vim to reinstall.') 133 | return ( 134 | self.cmd_uninstall(('uninstall',) + tuple(left_args)) 135 | and self.cmd_install(('install',) + tuple(left_args)) 136 | ) 137 | 138 | def cmd_setup(self, args): 139 | '''Set up files and directories for VVM''' 140 | repos_dir = get_repos_dir('vvm') 141 | return ( 142 | ( 143 | has('git') 144 | or die('Git is required to set up.') 145 | ) and ( 146 | not (isdir(get_vvm_dir()) and isdir(repos_dir)) 147 | or die('Files and directories for VVM are already set up.') 148 | ) and ( 149 | sh('git clone %s %s' % ( 150 | self.vvm_repos_uri, 151 | repos_dir 152 | )) == 0 153 | and ln_s('%s/bin' % repos_dir, '%s/bin' % get_vvm_dir()) 154 | and ln_s('%s/etc' % repos_dir, '%s/etc' % get_vvm_dir()) 155 | and echo('\n'.join(( 156 | '', 157 | 'VVM is successfully installed. For daily use,', 158 | 'please add the following line into your ~/.bash_login etc:', 159 | 'test -f ~/.vvm/etc/login && source ~/.vvm/etc/login', 160 | ))) 161 | ) 162 | ) 163 | 164 | def cmd_uninstall(self, args): 165 | '''Uninstall a specific version of Vim.''' 166 | p = VvmOptionParser() 167 | p.usage = '%prog uninstall ' 168 | (options, left_args) = p.parse_args(args[1:]) 169 | if len(left_args) == 0: 170 | die('Please specify a version of Vim to uninstall.') 171 | version = left_args[0] 172 | current_path = get_vims_dir('current') 173 | src_dir = get_src_dir(version) 174 | vims_dir = get_vims_dir(version) 175 | if isdir(current_path): 176 | target_path = readlink(current_path) 177 | if normalize_path(target_path) == normalize_path(vims_dir): 178 | die('%s can not be uninstalled; it is currently used.' % version) 179 | if isdir(src_dir): 180 | rm_r(src_dir) 181 | else: 182 | print('Something wrong; %s source does not exist.' % version) 183 | if isdir(vims_dir): 184 | rm_r(vims_dir) 185 | else: 186 | print('Something wrong; %s binary does not exist.' % version) 187 | return True 188 | 189 | def cmd_update_itself(self, args): 190 | '''Update VVM itself''' 191 | repos_dir = get_repos_dir('vvm') 192 | return ( 193 | ( 194 | has('git') 195 | or die('Git is required to update VVM.') 196 | ) and ( 197 | isdir(get_vvm_dir()) 198 | or die('VVM is not installed. Please set up at the first.') 199 | ) and ( 200 | sh('cd %s && git pull' % repos_dir) == 0 201 | and echo('\n'.join(( 202 | '', 203 | 'VVM is successfully updated.', 204 | ))) 205 | ) 206 | ) 207 | 208 | def cmd_use(self, args): 209 | '''Use a specific version of Vim as the default one.''' 210 | p = VvmOptionParser() 211 | p.usage = '%prog use [--install [...]]' 212 | (options, left_args) = p.parse_args(args[1:]) 213 | if len(left_args) == 0: 214 | die('Please specify a version of Vim to use.') 215 | version = left_args[0] 216 | if version == 'system': 217 | current_path = get_vims_dir('current') 218 | if isdir(current_path): 219 | rm(current_path) 220 | else: 221 | vims_dir = get_vims_dir(version) 222 | if not isdir(vims_dir): 223 | if 2 <= len(left_args) and left_args[1] == '--install': 224 | self.cmd_install(('install', version) + tuple(left_args[2:])) or die() 225 | else: 226 | die('%s is not installed.' % version) 227 | current_path = get_vims_dir('current') 228 | if isdir(current_path): 229 | rm(current_path) 230 | ln_s(vims_dir, current_path) 231 | return True 232 | 233 | # Utilities:: 234 | 235 | def checkout(self, version): 236 | vi = VersionInfo.parse(version) 237 | try: 238 | checkout_func = getattr(self, 'checkout_%s' % vi.vcs) 239 | except AttributeError: 240 | die('Fork "%s" is not known.' % vi.vcs) 241 | return checkout_func(version, vi) 242 | 243 | def checkout_git(self, version, vi): 244 | # FIXME: Refine - Most steps are similar to checkout_mercurial. 245 | repos_dir = get_repos_dir(vi.fork) 246 | src_dir = get_src_dir(version) 247 | return ( 248 | (has('git') or die('Git is required to checkout %s.' % vi.fork)) 249 | and (isdir(get_src_dir_itself()) or mkdir(get_src_dir_itself())) 250 | and ( 251 | isdir(src_dir) 252 | or sh('cd %s && git archive --prefix=%s/ %s | (cd %s && tar xf -)' 253 | % (repos_dir, version, vi.tag, get_src_dir_itself())) == 0 254 | or die() 255 | ) 256 | ) 257 | 258 | def checkout_mercurial(self, version, vi): 259 | repos_dir = get_repos_dir(vi.fork) 260 | src_dir = get_src_dir(version) 261 | return ( 262 | (has('hg') or die('Mercurial is required to checkout %s.' % vi.fork)) 263 | and (isdir(get_src_dir_itself()) or mkdir(get_src_dir_itself())) 264 | and ( 265 | isdir(src_dir) 266 | or sh('cd %s && hg archive -t tar -r %s -p %s - | (cd %s && tar xf -)' 267 | % (repos_dir, vi.tag, version, get_src_dir_itself())) == 0 268 | or die() 269 | ) 270 | ) 271 | 272 | def configure(self, version, custom_configure_options): 273 | default_configure_options = ( 274 | '"--prefix=%s"' % os.path.expanduser(get_vims_dir(version)), 275 | ) 276 | return sh('cd %s && ./configure %s' % ( 277 | get_src_dir(version), 278 | ' '.join( 279 | default_configure_options 280 | + tuple("'%s'" % o for o in custom_configure_options) 281 | ) 282 | )) == 0 283 | 284 | def fetch(self, version): 285 | vi = VersionInfo.parse(version) 286 | try: 287 | fetch_func = getattr(self, 'fetch_%s' % vi.vcs) 288 | except AttributeError: 289 | die('Fork "%s" is not known.' % vi.vcs) 290 | return fetch_func(version, vi) 291 | 292 | def fetch_git(self, version, vi): 293 | # FIXME: Refine - Most steps are similar to fetch_mercurial. 294 | repos_uri = vi.uri 295 | repos_dir = get_repos_dir(vi.fork) 296 | src_dir = get_src_dir(version) 297 | return ( 298 | (has('git') or die('Git is required to install %s.' % vi.fork)) 299 | and (isdir(get_repos_dir_itself()) or mkdir(get_repos_dir_itself())) 300 | and ( 301 | isdir(repos_dir) 302 | or sh('git clone --bare %s %s' % (repos_uri, repos_dir)) == 0 303 | or die() 304 | ) 305 | and (sh('cd %s && git fetch --tags' % repos_dir) == 0 or die()) 306 | ) 307 | 308 | def fetch_mercurial(self, version, vi): 309 | repos_uri = vi.uri 310 | repos_dir = get_repos_dir(vi.fork) 311 | src_dir = get_src_dir(version) 312 | return ( 313 | (has('hg') or die('Mercurial is required to install %s.' % vi.fork)) 314 | and (isdir(get_repos_dir_itself()) or mkdir(get_repos_dir_itself())) 315 | and ( 316 | isdir(repos_dir) 317 | or sh('hg clone %s %s' % (repos_uri, repos_dir)) == 0 318 | or die() 319 | ) 320 | and (sh('cd %s && hg pull' % repos_dir) == 0 or die()) 321 | ) 322 | 323 | def make_clean(self, version): 324 | return make(version, ('clean',)) 325 | 326 | def make_install(self, version): 327 | return make(version, ('all', 'install',)) 328 | 329 | 330 | 331 | 332 | def get_current_version(): 333 | try: 334 | path = os.path.basename(readlink(get_vims_dir('current'))) 335 | return path 336 | except: 337 | return None 338 | 339 | def get_etc_file(filename): 340 | return '%s/%s' % (get_etc_dir_itself(), filename) 341 | 342 | def get_etc_dir_itself(): 343 | return '~/.vvm/etc' 344 | 345 | def get_repos_dir(fork): 346 | return '%s/%s' % (get_repos_dir_itself(), fork) 347 | 348 | def get_repos_dir_itself(): 349 | return '~/.vvm/repos' 350 | 351 | def get_src_dir(version): 352 | return '%s/%s' % (get_src_dir_itself(), version) 353 | 354 | def get_src_dir_itself(): 355 | return '~/.vvm/src' 356 | 357 | def get_vims_dir(version): 358 | return '%s/%s' % (get_vims_dir_itself(), version) 359 | 360 | def get_vims_dir_itself(): 361 | return '~/.vvm/vims' 362 | 363 | def get_vvm_dir(): 364 | return '~/.vvm' 365 | 366 | 367 | 368 | 369 | class VvmOptionParser(optparse.OptionParser): 370 | def __init__(self, *args, **kw): 371 | optparse.OptionParser.__init__(self, *args, **kw) 372 | self.disable_interspersed_args() 373 | return 374 | 375 | def parse_args(self, args): 376 | # optparse.OptionParser.parse_args doesn't accespt tuple. 377 | return optparse.OptionParser.parse_args(self, list(args)) 378 | 379 | class VersionInfo(object): 380 | __slots__ = ( 381 | 'fork', 382 | 'tag', 383 | 'uri', 384 | 'vcs', 385 | ) 386 | 387 | def __init__(self): 388 | return 389 | 390 | @classmethod 391 | def parse(cls, version_string): 392 | (fork_name, tag_name) = version_string.split('--') 393 | try: 394 | known_fork = cls.get_dict()[fork_name] 395 | except KeyError: 396 | die('\n'.join(( 397 | 'Fork %s is not known to VVM.', 398 | 'Edit %s to direct VVM how to fetch the fork.', 399 | )) % (fork_name, get_etc_file('repos-map'))) 400 | 401 | vi = VersionInfo() 402 | vi.fork = fork_name 403 | vi.tag = tag_name 404 | vi.uri = known_fork.uri 405 | vi.vcs = known_fork.vcs 406 | return vi 407 | 408 | @classmethod 409 | def read_config_files(cls): 410 | vi_dict = {} 411 | for filename in ('repos-map.default', 'repos-map'): 412 | path = os.path.expanduser(get_etc_file(filename)) 413 | if not isfile(path): 414 | continue 415 | for line in open(path): 416 | if re.match(r'^\s*$', line) or re.match(r'^\s*#', line): 417 | continue 418 | else: 419 | vi = cls() 420 | (vi.fork, vi.vcs, vi.uri) = line.split() 421 | vi_dict[vi.fork] = vi 422 | return vi_dict 423 | 424 | @classmethod 425 | def get_dict(cls): 426 | if cls.__vi_dict is None: 427 | cls.__vi_dict = cls.read_config_files() 428 | return cls.__vi_dict 429 | __vi_dict = None 430 | 431 | def die(message=''): 432 | return exit(message) 433 | 434 | def echo(message): 435 | print(message) 436 | return True 437 | 438 | def has(progname): 439 | return sh('which %s >/dev/null 2>/dev/null' % progname) == 0 440 | 441 | def isdir(path): 442 | return os.path.isdir(os.path.expanduser(path)) 443 | 444 | def isfile(path): 445 | return os.path.isfile(os.path.expanduser(path)) 446 | 447 | def listdir(path): 448 | return os.listdir(os.path.expanduser(path)) 449 | 450 | def ln_s(target, link_name): 451 | os.symlink(os.path.expanduser(target), os.path.expanduser(link_name)) 452 | return True 453 | 454 | def make(version, args): 455 | return sh('cd %s && make %s' % ( 456 | get_src_dir(version), 457 | ' '.join(args) 458 | )) == 0 459 | 460 | def mkdir(path): 461 | os.makedirs(os.path.expanduser(path), 0o755) 462 | return True 463 | 464 | def normalize_path(path): 465 | return os.path.abspath(os.path.expanduser(path)) 466 | 467 | def readlink(path): 468 | return os.readlink(os.path.expanduser(path)) 469 | 470 | def rm(path): 471 | os.remove(os.path.expanduser(path)) 472 | return True 473 | 474 | def rm_r(path): 475 | shutil.rmtree(os.path.expanduser(path)) 476 | return True 477 | 478 | def sh(cmdline): 479 | return os.system(cmdline) 480 | 481 | 482 | 483 | 484 | if __name__ == '__main__': 485 | VimVersionManager().main(sys.argv) 486 | 487 | # __END__ 488 | --------------------------------------------------------------------------------