vim-flavor 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in vim-flavor.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Kana Natsuno
2
+
3
+ So-called MIT/X 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.
data/README.asciidoc ADDED
@@ -0,0 +1,142 @@
1
+ = vim-flavor: a tool to manage your favorite Vim plugins
2
+
3
+
4
+
5
+
6
+ == Set up the utility for a new-style plugin management
7
+
8
+ === Requirements
9
+
10
+ * Git 1.7.9 or later
11
+ * Ruby 1.9.2 or later
12
+ * Vim 7.3 or later
13
+
14
+
15
+ === Supported platforms
16
+
17
+ * Unix-like environments such as Linux, Mac OS X, etc.
18
+ * Though Microsoft Windows is not directly supported,
19
+ it is possible to manage Vim plugins via Cygwin or other environments.
20
+
21
+
22
+ === Installation steps
23
+
24
+ ----
25
+ gem install vim-flavor
26
+ ----
27
+
28
+
29
+
30
+
31
+ == Start using vim-flavor
32
+
33
+ ----
34
+ cd $YOUR_REPOSITORY_FOR_DOTFILES
35
+
36
+ cat >VimFlavor <<'END'
37
+ # * Declare using git://github.com/kana/vim-textobj-indent.git
38
+ # * vim-flavor fetches a plugin from git://github.com/$USER/$REPO.git
39
+ # if the argument is written in '$USER/$REPO' format.
40
+ # * kana/vim-textobj-indent requires kana/vim-textobj-user.
41
+ # Such dependencies are automatically installed
42
+ # if the flavored plugin declares its dependencies with VimFlavor file.
43
+ # (FIXME: Resolving dependencies will be implemented later.)
44
+ flavor 'kana/vim-textobj-indent'
45
+
46
+ # * Declare using git://github.com/vim-scripts/fakeclip.git
47
+ # * vim-flavor fetches a plugin from git://github.com/vim-scripts/$REPO.git
48
+ # if the argument is written in '$REPO' format.
49
+ flavor 'fakeclip'
50
+
51
+ # * Declare using git://github.com/kana/vim-altr.git
52
+ # * vim-flavor fetches a plugin from the URI
53
+ # if the argument seems to be a URI.
54
+ flavor 'git://github.com/kana/vim-altr.git'
55
+
56
+ # * Declare using kana/vim-smartchr 0.1.0 or later and older than 0.2.0.
57
+ flavor 'kana/vim-smartchr', '~> 0.1.0'
58
+
59
+ # * Declare using kana/vim-smartword 0.1 or later and older than 1.0.
60
+ flavor 'kana/vim-smartword', '~> 0.1'
61
+
62
+ # * Declare using kana/vim-smarttill 0.1.0 or later.
63
+ flavor 'kana/vim-smarttill', '>= 0.1.0'
64
+ END
65
+
66
+ # Fetch the plugins declared in the VimFlavor,
67
+ # create VimFlavor.lock for a snapshot of all plugins and versions,
68
+ # then install the plugins and a bootstrap script into ~/.vim etc.
69
+ vim-flavor install
70
+
71
+ # Add the following line into the first line of your vimrc:
72
+ #
73
+ # runtime flavors/bootstrap.vim
74
+ vim vimrc
75
+
76
+ git add VimFlavor VimFlavor.lock vimrc
77
+ git commit -m 'Use vim-flavor to manage my favorite Vim plugins'
78
+ ----
79
+
80
+
81
+
82
+
83
+ == Upgrade all plugins to the latest version
84
+
85
+ ----
86
+ vim-flavor upgrade
87
+
88
+ git add VimFlavor.lock
89
+ git commit -m 'Upgrade my favorite Vim plugins'
90
+ ----
91
+
92
+
93
+
94
+
95
+ == Add more plugins into your dotfile repository
96
+
97
+ ----
98
+ cat >>VimFlavor <<'END'
99
+
100
+ flavor 'kana/vim-operator-replace'
101
+
102
+ END
103
+
104
+ # Fetch newly added plugins,
105
+ # update VimFlavor.lock for the plugins,
106
+ # then install the plugins into ~/.vim etc.
107
+ vim-flavor install
108
+
109
+ git add VimFlavor VimFlavor.lock
110
+ git commit -m 'Use kana/vim-operator-replace'
111
+ ----
112
+
113
+
114
+
115
+
116
+ == Remove plugins from your dotfile repository
117
+
118
+ ----
119
+ # Remove declarations of unused plugins from VimFlavor.
120
+ sed -i~ -e '/vim-smartchr/d' VimFlavor
121
+
122
+ # Update VimFlavor.lock for the removed plugins,
123
+ # then clean up the plugins from ~/.vim etc.
124
+ vim-flavor install
125
+
126
+ git add VimFlavor VimFlavor.lock
127
+ git commit -m 'Farewell kana/vim-smartchr'
128
+ ----
129
+
130
+
131
+
132
+
133
+ == Install plugins into a non-standard directory
134
+
135
+ ----
136
+ vim-flavor install --vimfiles-path=/cygdrive/c/Users/kana/vimfiles
137
+ ----
138
+
139
+
140
+
141
+
142
+ // vim: filetype=asciidoc
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/vim-flavor ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'vim-flavor'
3
+ Vim::Flavor::CLI.start()
data/lib/vim-flavor.rb ADDED
@@ -0,0 +1,471 @@
1
+ require 'bundler/setup'
2
+ require 'fileutils'
3
+ require 'thor'
4
+ require 'vim-flavor/version'
5
+ require 'yaml'
6
+
7
+ module Vim
8
+ module Flavor
9
+ module StringExtension
10
+ def to_flavors_path()
11
+ "#{self}/flavors"
12
+ end
13
+ end
14
+
15
+ class ::String
16
+ include StringExtension
17
+ end
18
+
19
+ class VersionConstraint
20
+ attr_reader :base_version, :operator
21
+
22
+ def initialize(s)
23
+ @base_version, @operator = parse(s)
24
+ end
25
+
26
+ def to_s()
27
+ "#{@operator} #{@base_version}"
28
+ end
29
+
30
+ def ==(other)
31
+ self.base_version == other.base_version &&
32
+ self.operator == other.operator
33
+ end
34
+
35
+ def parse(s)
36
+ m = /^\s*(>=|~>)\s+(\S+)$/.match(s)
37
+ if m then
38
+ [Gem::Version.create(m[2]), m[1]]
39
+ else
40
+ raise "Invalid version constraint: #{s.inspect}"
41
+ end
42
+ end
43
+
44
+ def compatible?(other_version_or_s)
45
+ v = Gem::Version.create(other_version_or_s)
46
+ if @operator == '~>' then
47
+ self.base_version.bump() > v and v >= self.base_version
48
+ elsif @operator == '>=' then
49
+ v >= self.base_version
50
+ else
51
+ raise NotImplementedError
52
+ end
53
+ end
54
+
55
+ def find_the_best_version(versions)
56
+ versions.
57
+ select {|v| compatible?(v)}.
58
+ sort().
59
+ reverse().
60
+ first
61
+ end
62
+ end
63
+
64
+ DOT_PATH = "#{Dir.getwd()}/.vim-flavor"
65
+ CACHED_REPOS_PATH = "#{DOT_PATH}/repos"
66
+
67
+ class Flavor
68
+ @@properties = [
69
+ :groups,
70
+ :locked_version,
71
+ :repo_name,
72
+ :repo_uri,
73
+ :version_contraint,
74
+ ]
75
+
76
+ @@properties.each do |p|
77
+ attr_accessor p
78
+ end
79
+
80
+ def initialize()
81
+ @groups = []
82
+ end
83
+
84
+ def ==(other)
85
+ return false if self.class != other.class
86
+ @@properties.all? do |p|
87
+ self.send(p) == other.send(p)
88
+ end
89
+ end
90
+
91
+ def zapped_repo_dir_name
92
+ @repo_name.gsub(/[^A-Za-z0-9._-]/, '_')
93
+ end
94
+
95
+ def cached_repo_path
96
+ @cached_repo_path ||=
97
+ "#{CACHED_REPOS_PATH}/#{zapped_repo_dir_name}"
98
+ end
99
+
100
+ def make_deploy_path(vimfiles_path)
101
+ "#{vimfiles_path.to_flavors_path()}/#{zapped_repo_dir_name}"
102
+ end
103
+
104
+ def clone()
105
+ message = %x[
106
+ {
107
+ git clone '#{@repo_uri}' '#{cached_repo_path}'
108
+ } 2>&1
109
+ ]
110
+ if $? != 0 then
111
+ raise RuntimeError, message
112
+ end
113
+ true
114
+ end
115
+
116
+ def fetch()
117
+ message = %x[
118
+ {
119
+ cd #{cached_repo_path.inspect} &&
120
+ git fetch origin
121
+ } 2>&1
122
+ ]
123
+ if $? != 0 then
124
+ raise RuntimeError, message
125
+ end
126
+ end
127
+
128
+ def deploy(vimfiles_path)
129
+ deploy_path = make_deploy_path(vimfiles_path)
130
+ message = %x[
131
+ {
132
+ cd '#{cached_repo_path}' &&
133
+ git checkout -f #{locked_version.inspect} &&
134
+ git checkout-index -a -f --prefix='#{deploy_path}/' &&
135
+ vim -u NONE -i NONE -n -N -e -c 'helptags #{deploy_path}/doc | qall!'
136
+ } 2>&1
137
+ ]
138
+ if $? != 0 then
139
+ raise RuntimeError, message
140
+ end
141
+ end
142
+
143
+ def undeploy(vimfiles_path)
144
+ deploy_path = make_deploy_path(vimfiles_path)
145
+ message = %x[
146
+ {
147
+ rm -fr '#{deploy_path}'
148
+ } 2>&1
149
+ ]
150
+ if $? != 0 then
151
+ raise RuntimeError, message
152
+ end
153
+ end
154
+
155
+ def list_versions()
156
+ tags = %x[
157
+ {
158
+ cd '#{cached_repo_path}' &&
159
+ git tag
160
+ } 2>&1
161
+ ]
162
+ if $? != 0 then
163
+ raise RuntimeError, message
164
+ end
165
+
166
+ tags.
167
+ split(/[\r\n]/).
168
+ select {|t| t != ''}.
169
+ map {|t| Gem::Version.create(t)}
170
+ end
171
+
172
+ def update_locked_version()
173
+ @locked_version =
174
+ version_contraint.find_the_best_version(list_versions())
175
+ end
176
+ end
177
+
178
+ class FlavorFile
179
+ attr_reader :flavors
180
+
181
+ def initialize()
182
+ @flavors = {}
183
+ @default_groups = [:default]
184
+ end
185
+
186
+ def interpret(&block)
187
+ instance_eval(&block)
188
+ end
189
+
190
+ def eval_flavorfile(flavorfile_path)
191
+ content = File.open(flavorfile_path, 'rb') do |f|
192
+ f.read()
193
+ end
194
+ interpret do
195
+ instance_eval(content)
196
+ end
197
+ end
198
+
199
+ def repo_uri_from_repo_name(repo_name)
200
+ if /^([^\/]+)$/.match(repo_name) then
201
+ m = Regexp.last_match
202
+ "git://github.com/vim-scripts/#{m[1]}.git"
203
+ elsif /^([A-Za-z0-9_-]+)\/(.*)$/.match(repo_name) then
204
+ m = Regexp.last_match
205
+ "git://github.com/#{m[1]}/#{m[2]}.git"
206
+ elsif /^[a-z]+:\/\/.*$/.match(repo_name) then
207
+ repo_name
208
+ else
209
+ raise "repo_name is written in invalid format: #{repo_name.inspect}"
210
+ end
211
+ end
212
+
213
+ def flavor(repo_name, *args)
214
+ options = Hash === args.last ? args.pop : {}
215
+ options[:groups] ||= []
216
+ version_contraint = VersionConstraint.new(args.last || '>= 0')
217
+
218
+ f = Flavor.new()
219
+ f.repo_name = repo_name
220
+ f.repo_uri = repo_uri_from_repo_name(repo_name)
221
+ f.version_contraint = version_contraint
222
+ f.groups = @default_groups + options[:groups]
223
+
224
+ @flavors[f.repo_uri] = f
225
+ end
226
+
227
+ def group(*group_names, &block)
228
+ @default_groups.concat(group_names)
229
+ yield
230
+ ensure
231
+ group_names.each do
232
+ @default_groups.pop()
233
+ end
234
+ end
235
+ end
236
+
237
+ class LockFile
238
+ # TODO: Resolve dependencies recursively.
239
+
240
+ attr_reader :flavors, :path
241
+
242
+ def initialize(path)
243
+ @flavors = {} # repo_uri => flavor
244
+ @path = path
245
+ end
246
+
247
+ def load()
248
+ h = File.open(@path, 'rb') do |f|
249
+ YAML.load(f.read())
250
+ end
251
+
252
+ @flavors = self.class.flavors_from_poro(h[:flavors])
253
+ end
254
+
255
+ def save()
256
+ h = {}
257
+
258
+ h[:flavors] = self.class.poro_from_flavors(@flavors)
259
+
260
+ File.open(@path, 'wb') do |f|
261
+ YAML.dump(h, f)
262
+ end
263
+ end
264
+
265
+ def self.poro_from_flavors(flavors)
266
+ Hash[
267
+ flavors.values.map {|f|
268
+ [
269
+ f.repo_uri,
270
+ {
271
+ :groups => f.groups,
272
+ :locked_version => f.locked_version.to_s(),
273
+ :repo_name => f.repo_name,
274
+ :version_contraint => f.version_contraint.to_s(),
275
+ }
276
+ ]
277
+ }
278
+ ]
279
+ end
280
+
281
+ def self.flavors_from_poro(poro)
282
+ Hash[
283
+ poro.to_a().map {|repo_uri, h|
284
+ f = Flavor.new()
285
+ f.groups = h[:groups]
286
+ f.locked_version = Gem::Version.create(h[:locked_version])
287
+ f.repo_name = h[:repo_name]
288
+ f.repo_uri = repo_uri
289
+ f.version_contraint = VersionConstraint.new(h[:version_contraint])
290
+ [f.repo_uri, f]
291
+ }
292
+ ]
293
+ end
294
+ end
295
+
296
+ class Facade
297
+ attr_reader :flavorfile
298
+ attr_accessor :flavorfile_path
299
+ attr_reader :lockfile
300
+ attr_accessor :lockfile_path
301
+ attr_accessor :traced
302
+
303
+ def initialize()
304
+ @flavorfile = nil # FlavorFile
305
+ @flavorfile_path = "#{Dir.getwd()}/VimFlavor"
306
+ @lockfile = nil # LockFile
307
+ @lockfile_path = "#{Dir.getwd()}/VimFlavor.lock"
308
+ @traced = false
309
+ end
310
+
311
+ def trace(message)
312
+ print(message) if @traced
313
+ end
314
+
315
+ def load()
316
+ @flavorfile = FlavorFile.new()
317
+ @flavorfile.eval_flavorfile(@flavorfile_path)
318
+
319
+ @lockfile = LockFile.new(@lockfile_path)
320
+ @lockfile.load() if File.exists?(@lockfile_path)
321
+ end
322
+
323
+ def make_new_flavors(current_flavors, locked_flavors, mode)
324
+ new_flavors = {}
325
+
326
+ current_flavors.each do |repo_uri, cf|
327
+ lf = locked_flavors[repo_uri]
328
+ nf = cf.dup()
329
+
330
+ nf.locked_version =
331
+ if (not lf) or
332
+ cf.version_contraint != lf.version_contraint or
333
+ mode == :update then
334
+ cf.locked_version
335
+ else
336
+ lf.locked_version
337
+ end
338
+
339
+ new_flavors[repo_uri] = nf
340
+ end
341
+
342
+ new_flavors
343
+ end
344
+
345
+ def create_vim_script_for_bootstrap(vimfiles_path)
346
+ bootstrap_path = "#{vimfiles_path.to_flavors_path()}/bootstrap.vim"
347
+ FileUtils.mkdir_p(File.dirname(bootstrap_path))
348
+ File.open(bootstrap_path, 'w') do |f|
349
+ f.write(<<-'END')
350
+ function! s:bootstrap()
351
+ let current_rtp = &runtimepath
352
+ let current_rtps = split(current_rtp, ',')
353
+ set runtimepath&
354
+ let default_rtp = &runtimepath
355
+ let default_rtps = split(default_rtp, ',')
356
+ let user_dir = default_rtps[0]
357
+ let user_after_dir = default_rtps[-1]
358
+ let base_rtps =
359
+ \ filter(copy(current_rtps),
360
+ \ 'v:val !=# user_dir && v:val !=# user_after_dir')
361
+ let flavor_dirs =
362
+ \ filter(split(glob(user_dir . '/flavors/*'), '\n'),
363
+ \ 'isdirectory(v:val)')
364
+ let new_rtps =
365
+ \ []
366
+ \ + [user_dir]
367
+ \ + flavor_dirs
368
+ \ + base_rtps
369
+ \ + map(reverse(copy(flavor_dirs)), 'v:val . "/after"')
370
+ \ + [user_after_dir]
371
+ let &runtimepath = join(new_rtps, ',')
372
+ endfunction
373
+
374
+ call s:bootstrap()
375
+ END
376
+ end
377
+ end
378
+
379
+ def deploy_flavors(flavor_list, vimfiles_path)
380
+ FileUtils.rm_rf(
381
+ ["#{vimfiles_path.to_flavors_path()}"],
382
+ :secure => true
383
+ )
384
+
385
+ create_vim_script_for_bootstrap(vimfiles_path)
386
+ flavor_list.each do |f|
387
+ trace("Deploying #{f.repo_name} (#{f.locked_version})\n")
388
+ f.deploy(vimfiles_path)
389
+ end
390
+ end
391
+
392
+ def save_lockfile()
393
+ @lockfile.save()
394
+ end
395
+
396
+ def complete_locked_flavors(mode)
397
+ nfs = {}
398
+ @flavorfile.flavors.each do |repo_uri, cf|
399
+ nf = cf.dup()
400
+ lf = @lockfile.flavors[repo_uri]
401
+
402
+ trace("Using #{nf.repo_name} ... ")
403
+ begin
404
+ if not File.exists?(nf.cached_repo_path)
405
+ nf.clone()
406
+ end
407
+
408
+ if mode == :upgrade_all or
409
+ (not lf) or
410
+ nf.version_contraint != lf.version_contraint then
411
+ nf.fetch()
412
+ nf.update_locked_version()
413
+ else
414
+ nf.locked_version = lf.locked_version
415
+ end
416
+ end
417
+ trace("(#{nf.locked_version})\n")
418
+
419
+ nfs[repo_uri] = nf
420
+ end
421
+
422
+ @lockfile.instance_eval do
423
+ @flavors = nfs
424
+ end
425
+ end
426
+
427
+ def get_default_vimfiles_path()
428
+ # FIXME: Compute more appropriate value.
429
+ "#{ENV['HOME']}/.vim"
430
+ end
431
+
432
+ def install(vimfiles_path)
433
+ load()
434
+ complete_locked_flavors(:upgrade_if_necessary)
435
+ save_lockfile()
436
+ deploy_flavors(lockfile.flavors.values, vimfiles_path)
437
+ end
438
+
439
+ def upgrade(vimfiles_path)
440
+ load()
441
+ complete_locked_flavors(:upgrade_all)
442
+ save_lockfile()
443
+ deploy_flavors(lockfile.flavors.values, vimfiles_path)
444
+ end
445
+ end
446
+
447
+ class CLI < Thor
448
+ desc 'install', 'Install Vim plugins according to VimFlavor file.'
449
+ method_option :vimfiles_path,
450
+ :desc => 'A path to your vimfiles directory.'
451
+ def install()
452
+ facade = Facade.new()
453
+ facade.traced = true
454
+ facade.install(
455
+ options[:vimfiles_path] || facade.get_default_vimfiles_path()
456
+ )
457
+ end
458
+
459
+ desc 'upgrade', 'Upgrade Vim plugins according to VimFlavor file.'
460
+ method_option :vimfiles_path,
461
+ :desc => 'A path to your vimfiles directory.'
462
+ def upgrade()
463
+ facade = Facade.new()
464
+ facade.traced = true
465
+ facade.upgrade(
466
+ options[:vimfiles_path] || facade.get_default_vimfiles_path()
467
+ )
468
+ end
469
+ end
470
+ end
471
+ end