├── .gitignore ├── CHANGES.txt ├── LICENSE.txt ├── Pandemic ├── __init__.py ├── bundle.py └── printer.py ├── README.md ├── bin └── pandemic └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | var 14 | sdist 15 | develop-eggs 16 | .installed.cfg 17 | lib 18 | lib64 19 | 20 | # Installer logs 21 | pip-log.txt 22 | 23 | # Unit test / coverage reports 24 | .coverage 25 | .tox 26 | nosetests.xml 27 | 28 | # Translations 29 | *.mo 30 | 31 | # Mr Developer 32 | .mr.developer.cfg 33 | .project 34 | .pydevproject 35 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v1.2.0, 06 Dec 2013 -- Sort bundle list before writing to ensure a consistent bundle list 2 | v1.1.4, 24 Oct 2013 -- Add two-way checking to list-dead 3 | v1.1.3, 09 Jul 2013 -- Fix bundle delete issue 4 | v1.1.2, 20 Jun 2013 -- Make argparse a requirement 5 | v1.1.1, 17 Jun 2013 -- Fix license 6 | v1.1.0, 01 Jun 2013 -- Allow removal of multiple packages (keep with -k as first arg), print bundle messages with printer 7 | v1.0.3, 01 Jun 2013 -- Fix bug with -t 8 | v1.0.2, 27 May 2013 -- Update URLs 9 | v1.0.1, 25 May 2013 -- Adding python package directory structure 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Joe Colosimo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pandemic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwcxz/vim-pandemic/13c2b5053d2e8c26e2cedc96c05dcf6f500909e4/Pandemic/__init__.py -------------------------------------------------------------------------------- /Pandemic/bundle.py: -------------------------------------------------------------------------------- 1 | import os, shutil, subprocess 2 | 3 | class BundleActioner: 4 | def __init__(self): pass; 5 | 6 | def clone(self, source, name): 7 | return ""; 8 | 9 | def update(self): 10 | return ""; 11 | 12 | def remove(self, name): 13 | shutil.rmtree(name); 14 | return ""; 15 | 16 | 17 | class BundleGit(BundleActioner): 18 | def clone(self, source, name): 19 | return subprocess.check_output(['git', 'clone', source, name]); 20 | def update(self): 21 | return subprocess.check_output(['git', 'pull']); 22 | 23 | class BundleHg(BundleActioner): 24 | def clone(self, source, name): 25 | return subprocess.check_output(['hg', 'clone', source, name]); 26 | def update(self): 27 | return subprocess.check_output(['hg', 'pull']); 28 | 29 | class BundleLocal(BundleActioner): 30 | def clone(self, source, name): 31 | outmsg = subprocess.check_output(['cp', '-R', source, name]); 32 | 33 | f = open("%s/.source" %name, 'w'); 34 | f.write(source); 35 | f.close(); 36 | 37 | return outmsg; 38 | 39 | def update(self): 40 | f = open(".source", 'r'); 41 | source = f.read(); 42 | f.close(); 43 | 44 | name = os.path.split(os.getcwd())[1]; 45 | 46 | os.chdir(os.path.split(os.getcwd())[0]); 47 | return self.clone(source, name); 48 | 49 | class BundleScript(BundleActioner): 50 | def clone(self, source, name): 51 | return subprocess.check_output(['cp', '-R', source, name]); 52 | 53 | def update(self): 54 | return subprocess.check_output(['./.update']); 55 | 56 | 57 | actioners = { 'git' : BundleGit, 58 | 'hg' : BundleHg, 59 | 'local' : BundleLocal, 60 | 'script' : BundleScript }; 61 | 62 | 63 | class Bundle: 64 | def __init__(self, name, source, btype, bdir, printer): 65 | self.printer = printer; 66 | 67 | self.name = name; 68 | self.bdir = os.path.expanduser(bdir); 69 | self.source = source; 70 | self.btype = btype; 71 | 72 | self.bname = self.__findbundle(); 73 | self.actioner = actioners[btype](); 74 | 75 | 76 | def clone(self): 77 | # clone from whatever repository or whatever 78 | self.__savecwd(); 79 | os.chdir(self.bdir); 80 | 81 | if self.bname != None: 82 | # path already exists 83 | # best action to take is probably to just remove and clone 84 | # XXX: this isn't safe, though :( 85 | self.printer.warn("%s exists!" % (self.bname)); 86 | self.remove(); 87 | 88 | msg = self.actioner.clone(self.source, self.name); 89 | self.printer.message(msg); 90 | 91 | self.__restorecwd(); 92 | 93 | 94 | def remove(self): 95 | # delete an existing bundle directory 96 | self.__savecwd(); 97 | os.chdir(self.bdir); 98 | 99 | if self.bname != None: 100 | msg = self.actioner.remove(self.bname); 101 | self.printer.message(msg); 102 | else: 103 | self.printer.warn("%s doesn't exist!" % (self.name)); 104 | 105 | self.__restorecwd(); 106 | 107 | 108 | def update(self): 109 | # update a repository 110 | self.__savecwd(); 111 | os.chdir(self.bdir); 112 | 113 | if self.bname != None: 114 | os.chdir(self.bname); 115 | msg = self.actioner.update(); 116 | self.printer.message(msg); 117 | else: 118 | self.printer.warn("%s doesn't exist!" % (self.name)); 119 | self.clone(); 120 | 121 | self.__restorecwd(); 122 | 123 | 124 | def __findbundle(self): 125 | orig = os.path.join(self.bdir, self.name); 126 | disabled = os.path.join(self.bdir, "%s~" % self.name); 127 | 128 | if os.path.exists(orig): 129 | return self.name; 130 | elif os.path.exists(disabled): 131 | self.printer.warn("Using disabled form of %s..." % self.name); 132 | return "%s~" % self.name; 133 | else: 134 | return None; 135 | 136 | def __savecwd(self): 137 | self.__lastcwd = os.getcwd(); 138 | 139 | def __restorecwd(self): 140 | os.chdir(self.__lastcwd); 141 | -------------------------------------------------------------------------------- /Pandemic/printer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | RED = "\033[1;31m"; 4 | YLW = "\033[1;33m"; 5 | CLR = "\033[0m"; 6 | 7 | class Printer: 8 | def __init__(self): 9 | pass; 10 | 11 | def message(self, msg): 12 | print msg; 13 | 14 | def error(self, msg): 15 | print >> sys.stderr, "%sERR: %s%s" %(RED, msg, CLR); 16 | 17 | def warn(self, msg): 18 | print >> sys.stderr, "%sWRN: %s%s" %(YLW, msg, CLR); 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vim-pandemic 2 | ============ 3 | 4 | Manage remote [pathogen] bundles from multiple source types. 5 | 6 | I've been looking for a way to maintain a `~/.vim{,rc}` repository. The natural 7 | solution is to just have a Git repo containing my `~/.vimrc` and contents of 8 | `~/.vim`. Sure, that's no problem. 9 | 10 | Since I use [pathogen], I have a variety of bundles that are repositories of 11 | various types: Git, Mercurial, etc. I strongly believe in the philosophy that 12 | version control should not be tied into the operation of the thing it is 13 | tracking. For that reason, using Git submodules as part of my vim 14 | configuration repository just seems stupid. This was not the problem that 15 | submodules tries to solve and it leads to a lot of problems and configuration 16 | difficulties. My adherence to this philosophy also means that I don't want to 17 | use systems like [Vundle], though I appreciate the author's work in developing 18 | this sort of workflow. 19 | 20 | So I decided to build a way to easily manage remote repositories outside of 21 | vim, without requiring the use of a specific repository. The result is 22 | [vim-pandemic]. 23 | 24 | ## Installation 25 | 26 | Clone this repo, and then run: 27 | 28 | ``` 29 | sudo python setup.py install 30 | ``` 31 | 32 | This places the [pandemic] executable onto your path (probably at 33 | `/usr/local/bin/pandemic`), and installs its dependencies to your 34 | `site-packages` folder. 35 | 36 | ## Getting Started 37 | 38 | By default, [pandemic] manages bundles in `~/.vim/bundle.remote`. So, in your `~/.vimrc`, 39 | you're going to want: 40 | 41 | ```vim 42 | execute pathogen#infect('bundle.remote/{}') 43 | ``` 44 | 45 | In addition to having calls to `pathogen#infect()` for your local bundles. 46 | 47 | 48 | ## Managing Bundles 49 | 50 | Using [pandemic] is easy! Like, really, it actually is easy. 51 | 52 | 53 | ### Adding a bundle 54 | 55 | Using the `add BUNDLE TYPE SOURCE` command, we can easily add new bundles. 56 | `BUNDLE` can be any name you want to give the bundle you're storing, `TYPE` is 57 | one of the source types (run `pandemic --types` to find out supported types), 58 | and `SOURCE` is the remote source. For example: 59 | 60 | ``` 61 | $ pandemic add nerdtree git https://github.com/scrooloose/nerdtree.git 62 | ``` 63 | 64 | will add NERD Tree to our bundle list. It's being developed in a Git repo on Github. 65 | 66 | What if we wanted something from Mercurial because the developer is some kind of hipster? Easy: 67 | 68 | ``` 69 | $ pandemic add l9 hg https://bitbucket.org/ns9tks/vim-l9 70 | ``` 71 | 72 | [pandemic] also supports things that aren't version-controlled. For example, 73 | you might have a directory that you simply want to copy over to your bundle 74 | directory. For that, you can just use the `local` type. Or, you might have a 75 | directory that contains its own update script called `.update`; for that, you 76 | can use the type `script`. 77 | 78 | Adding new types is as easy as modifying `bundle.py` to have more 79 | `BundleActioners`. 80 | 81 | 82 | ### Removing a bundle 83 | 84 | Simply: 85 | 86 | ``` 87 | $ pandemic remove nerdtree 88 | ``` 89 | 90 | If you stick `keep` at the end, it will not delete the data from the bundle 91 | directory. I highly don't recommend that. 92 | 93 | 94 | ### Updating a bundle 95 | 96 | To update all bundles, just run: 97 | 98 | ``` 99 | $ pandemic update 100 | ``` 101 | 102 | Or, to update specific ones: 103 | 104 | ``` 105 | $ pandemic update nerdtree tagbar 106 | ``` 107 | 108 | 109 | ### Listing Bundles 110 | 111 | Try: 112 | 113 | ``` 114 | $ pandemic list | column -t 115 | ``` 116 | 117 | 118 | ### Synchronization 119 | 120 | Let's say you removed some entries from [pandemic]'s database file but left the 121 | physical bundle files in the bundle directory. You can use: 122 | 123 | ``` 124 | $ pandemic list-dead 125 | ``` 126 | 127 | to find which bundles are still in the directory but not in the database file 128 | and vice-versa. You can then run `pandemic update` to get missing bundles or 129 | delete extra bundles yourself. 130 | 131 | 132 | ## But what about disabled bundles? 133 | 134 | [pathogen] allows you to append `~` at the end of a bundle name to disable it 135 | from being used at runtime. [pandemic] does a decent job of detecting this 136 | when you are trying to perform operations. Simply keep using the original name 137 | in your tasks. For example, let's say you installed NERD Tree, but then you 138 | disabled it and now you want to remove it. That's okay! 139 | 140 | ``` 141 | $ pandemic add nerdtree git https://github.com/scrooloose/nerdtree.git 142 | $ mv ~/.bundle.remote/nerdtree ~/.bundle.remote/nerdtree~ 143 | $ pandemic remove nerdtree 144 | ``` 145 | 146 | [pandemic] knows what to do. Please, just don't try to simultaneously have 147 | `~/.vim/bundle.remote/nerdtree` and `~/.vim/bundle.remote/nerdtree~` exist at 148 | the same time. [pandemic] doesn't detect every form of idiocy. 149 | 150 | 151 | ## What's up for the future? 152 | 153 | I'm deciding whether I want to allow the user to enable or disable bundles from 154 | [pandemic]. Part of me thinks that this is a bad idea and is missing the point 155 | of the program. 156 | 157 | 158 | ## This is dumb. 159 | 160 | Yeah, probably. But it's suitable for my workflow, which is what I care about. 161 | When I was trying to think up names for this program, I accidentally ran across 162 | [vim-epidemic], which does just about the same thing, only in Ruby, and also 163 | only with Git repositories. 164 | 165 | 166 | 167 | [pathogen]:https://github.com/tpope/vim-pathogen 168 | [Vundle]:https://github.com/gmarik/vundle 169 | [vim-pandemic]:http://jwcxz.com/git/vim-pandemic 170 | [pandemic]:http://jwcxz.com/git/vim-pandemic 171 | [vim-epidemic]:https://github.com/AlphaHydrae/vim-epidemic 172 | -------------------------------------------------------------------------------- /bin/pandemic: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse, ConfigParser, os, sys 4 | 5 | from Pandemic.printer import Printer 6 | from Pandemic.bundle import Bundle 7 | 8 | DEF_CONFIG = '~/.vim/pandemic-bundles'; 9 | DEF_BUNDLEDIR = '~/.vim/bundle.remote'; 10 | 11 | class Pandemic: 12 | bundles = {}; 13 | 14 | def __init__(self, actionstr, config=DEF_CONFIG, bundledir=DEF_BUNDLEDIR, printer=Printer()): 15 | self.printer = printer; 16 | 17 | self.bundledir = os.path.expanduser(bundledir); 18 | 19 | if not os.path.exists(self.bundledir): 20 | os.mkdir(self.bundledir); 21 | 22 | self.config = os.path.expanduser(config); 23 | 24 | if len(actionstr) > 0: 25 | action_args = actionstr[1:]; 26 | 27 | actions = { 'list' : self.list, 28 | 'add' : self.add, 29 | 'remove' : self.remove, 30 | 'update' : self.update, 31 | 'list-dead' : self.listdead }; 32 | 33 | if actionstr[0] not in actions.keys(): 34 | self.printer.error("Invalid action."); 35 | 36 | self.action = actions[actionstr[0]]; 37 | 38 | self.parse_config(); 39 | self.action(action_args); 40 | else: 41 | self.printer.error("No action specified."); 42 | sys.exit(1); 43 | 44 | 45 | def parse_config(self): 46 | cfg = ConfigParser.SafeConfigParser(); 47 | cfg.read(self.config); 48 | 49 | self.bundles = {}; 50 | 51 | for bundle in cfg.sections(): 52 | if not cfg.has_option(bundle, 'source') or \ 53 | not cfg.has_option(bundle, 'type'): 54 | self.printer.error("Bundle '%s' is malformed." % bundle); 55 | continue; 56 | 57 | # append bundle to bundle list 58 | self.__append_to_bundles(bundle, cfg.get(bundle, 'source'), 59 | cfg.get(bundle, 'type')); 60 | 61 | 62 | def save_config(self): 63 | cfg = ConfigParser.SafeConfigParser(); 64 | for b in sorted(self.bundles.keys()): 65 | bundle = self.bundles[b]; 66 | cfg.add_section(b); 67 | cfg.set(b, 'source', bundle.source); 68 | cfg.set(b, 'type', bundle.btype); 69 | 70 | f = open(self.config, 'w'); 71 | cfg.write(f); 72 | f.close(); 73 | 74 | 75 | def list(self, args): 76 | for b in sorted(self.bundles.keys()): 77 | bundle = self.bundles[b]; 78 | if b == bundle.bname: 79 | e = "enabled"; 80 | else: 81 | e = "disabled"; 82 | self.printer.message("%s: %s %s %s" %(b, e, bundle.btype, bundle.source)); 83 | 84 | 85 | def add(self, args): 86 | bundle, btype, source = args; 87 | 88 | # add the new bundle to the list 89 | self.__append_to_bundles(bundle, source, btype); 90 | # get the new bundle to the directory 91 | self.bundles[bundle].clone(); 92 | # save the new bundle list 93 | self.save_config(); 94 | 95 | 96 | def remove(self, args): 97 | if len(args) > 1 and args[0] == '-k': 98 | self.printer.warn("Keeping bundle '%s'... this is a bad idea." % bundle); 99 | keep = True; 100 | bundles = args[1:]; 101 | else: 102 | keep = False; 103 | bundles = args; 104 | 105 | for bundle in bundles: 106 | if not keep: 107 | self.bundles[bundle].remove(); 108 | 109 | self.__delete_from_bundles(bundle); 110 | 111 | self.save_config(); 112 | 113 | 114 | def update(self, args): 115 | if len(args) == 0: 116 | updates = self.bundles.keys(); 117 | else: 118 | updates = args; 119 | 120 | for bundle in updates: 121 | self.printer.message("Updating %s..." % bundle); 122 | self.bundles[bundle].update(); 123 | 124 | 125 | def listdead(self, args): 126 | # list bundles that are no longer in the config 127 | found_bundles = os.listdir(self.bundledir); 128 | 129 | for b in found_bundles: 130 | if b not in self.bundles.keys(): 131 | self.printer.message("Bundle %s not found in database." %b); 132 | 133 | for b in self.bundles.keys(): 134 | if b not in found_bundles: 135 | self.printer.message("Bundle %s not found on filesystem. Run pandemic update." %b); 136 | 137 | 138 | def __append_to_bundles(self, bundle, source, btype): 139 | self.bundles[bundle] = Bundle(bundle, source, btype, self.bundledir, self.printer); 140 | 141 | def __delete_from_bundles(self, bundle): 142 | del self.bundles[bundle]; 143 | 144 | 145 | 146 | if __name__ == "__main__": 147 | p = argparse.ArgumentParser(description="Manage vim-pathogen bundles."); 148 | 149 | p.add_argument('-c', '--config', dest='config', action='store', 150 | default=DEF_CONFIG, 151 | help='remote bundle configuration file'); 152 | 153 | p.add_argument('-b', '--bundle-directory', dest='bundledir', 154 | action='store', default=DEF_BUNDLEDIR, 155 | help='directory contaning pandemic-managed bundles'); 156 | 157 | p.add_argument('-t', '--show-types', dest='showtypes', 158 | action='store_true', default=False, 159 | help='show supported source types'); 160 | 161 | p.add_argument(dest='action', action='store', nargs='*', 162 | help='action (list, add BUNDLE TYPE SOURCE, remove BUNDLE, update [BUNDLE, BUNDLE, ...], list-dead)'); 163 | 164 | args = p.parse_args(); 165 | 166 | printer = Printer(); 167 | 168 | if args.showtypes == True: 169 | from Pandemic.bundle import actioners 170 | printer.message("Supported types: %s" % ' '.join(actioners.keys())); 171 | 172 | pan = Pandemic(args.action, args.config, args.bundledir, printer); 173 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name="Pandemic", 5 | version="1.2.0", 6 | author="http://jwcxz.com/", 7 | url="http://jwcxz.com/projects/vim-pandemic", 8 | requires=["argparse"], 9 | packages=["Pandemic"], 10 | scripts=["bin/pandemic"] 11 | ) 12 | --------------------------------------------------------------------------------