Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 568 lines (466 sloc) 17.476 kb
628a30ea »
2010-11-01 Initial (includes code for uploading to vim.org)
1 #!/usr/bin/env ruby
2 # vimball.rb
3 # @Author: Tom Link (micathom AT gmail com)
4 # @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
5 # @Created: 2009-02-10.
c4d3104e »
2010-11-06 Display error message if script id wan't found
6 # @Last Change: 2010-11-06.
628a30ea »
2010-11-01 Initial (includes code for uploading to vim.org)
7 #
8 # This script creates and installs vimballs without vim.
9 #
10 # Before actually using this script, you might want to run
11 #
12 # vimball.rb --print-config
13 #
14 # and check the values. If they don't seem right, you can change them in
15 # the configuration file (in YAML format).
16 #
17 # Known incompatibilities:
18 # - Vim's vimball silently converts windows line end markers to unix
19 # markers. This script won't -- unless you run it with Windows's ruby
20 # maybe.
21
22
23 require 'yaml'
24 require 'logger'
25 require 'optparse'
26 require 'pathname'
27 require 'fileutils'
28 require 'zlib'
29 require 'rbconfig'
30 require 'yaml'
31 require 'digest/md5'
32
33
34 class Vimball
35
36 APPNAME = 'vimball'
37 VERSION = '1.0.217'
38 HEADER = <<HEADER
39 " Vimball Archiver by Charles E. Campbell, Jr., Ph.D.
40 UseVimball
41 finish
42 HEADER
43
44
45 class AppLog
46 def initialize(output=$stdout)
47 @output = output
48 $logger = Logger.new(output)
49 $logger.progname = APPNAME
50 $logger.datetime_format = "%H:%M:%S"
51 AppLog.set_level
52 end
53
54 def self.set_level
55 if $DEBUG
56 $logger.level = Logger::DEBUG
57 elsif $VERBOSE
58 $logger.level = Logger::INFO
59 else
60 $logger.level = Logger::WARN
61 end
62 end
63 end
64
65
66 class << self
67
68 def with_args(args)
69
70 AppLog.new
71
72 config = Hash.new
73
74 config['vimfiles'] = catch(:ok) do
75 throw :ok, ENV['VIMFILES'] if ENV['VIMFILES']
76 ['.vim', 'vimfiles'].each do |dir|
77 ['HOME', 'USERPROFILE', 'VIM'].each do |env|
78 pdir = ENV[env]
79 if pdir
80 vimfiles = File.join(pdir, dir)
81 throw :ok, vimfiles if File.directory?(vimfiles)
82 end
83 end
84 end
85 $logger.warn "Couldn't find your vimfiles directory."
86 $logger.warn "Please use the -b command-line option,"
87 $logger.warn "or set it in your config file."
88 '.'
89 end
90 config['installdir'] = config['vimfiles']
91
92 config['configfile'] = File.join(config['vimfiles'], 'vimballs', "config_#{ENV['HOSTNAME']}.yml")
93 unless File.exists?(config['configfile'])
94 config['configfile'] = File.join(config['vimfiles'], 'vimballs', 'config.yml')
95 end
96 @configs = []
97 read_config(config)
98
99 config['compress'] ||= false
100 config['helptags'] ||= %{vim -T dumb -c "helptags %s" -cq"}
101 config['outdir'] ||= File.join(config['vimfiles'], 'vimballs')
102 config['vimoutdir'] ||= nil
103 config['dry'] ||= false
104 config['record'] ||= true
105 config['repo'] ||= false
106 config['repodir'] ||= 'bundle'
107
108 opts = OptionParser.new do |opts|
109 opts.banner = 'Usage: vimball.rb [OPTIONS] COMMAND ARGS ...'
110 opts.separator ' '
111 opts.separator 'vimball.rb is a free software with ABSOLUTELY NO WARRANTY under'
112 opts.separator 'the terms of the GNU General Public License version 2 or newer.'
113 opts.separator ' '
114 opts.separator 'Commands:'
115 opts.separator ' install VIMBALL ... Install a vimball (implicit if the only argument ends with ".vba")'
116 opts.separator ' vba RECIPE ... Create a vimball'
117 opts.separator ' list VIMBALL ... List files in a vimball'
118 opts.separator ' '
119
120 opts.on('-b', '--vimfiles DIR', String, 'Vimfiles directory') do |value|
121 config['vimfiles'] = value
122 end
123
124 opts.on('-c', '--config YAML', String, 'Config file') do |value|
125 config['configfile'] = value
126 read_config(config)
127 end
128
129 opts.on('-d', '--dir DIR', String, 'Destination directory for vimballs') do |value|
130 config['outdir'] = value
131 end
132
133 opts.on('-D', '--dir4vim DIR', String, 'Destination directory name for vim (don\'t use this unless you\'re me)') do |value|
134 config['vimoutdir'] = value
135 end
136
137 opts.on('--[no-]helptags', 'Build the helptags file') do |value|
138 config['helptags'] = nil unless value
139 end
140
141 opts.on('-n', '--[no-]dry-run', 'Don\'t actually run any commands; just print them') do |bool|
142 config['dry'] = bool
143 end
144
145 opts.on('--print-config', 'Print the configuration and exit') do |bool|
146 puts "Configuration file: #{config['configfile']}"
147 puts YAML.dump(config)
148 exit
149 end
150
151 opts.on('-R', '--[no-]recipe', 'On install, save the recipe in DESTDIR/vimballs/recipes') do |bool|
152 config['save_recipes'] = bool
153 end
154
155 opts.on('-r', '--[no-]record', 'Save record in .VimballRecord') do |bool|
156 config['record'] = bool
157 end
158
159 opts.on('--[no-]repo', 'Install as single directory in a code repository') do |bool|
160 config['repo'] = true
161 end
162
163 opts.on('-u', '--[no-]update', 'Create VBA only if it is outdated') do |bool|
164 config['update'] = bool
165 end
166
167 opts.on('-z', '--gzip', 'Save as vba.gz') do |value|
168 config['compress'] = value
169 end
170
171
172 opts.separator ' '
173 opts.separator 'Other Options:'
174
175 opts.on('--debug', 'Show debug messages') do |v|
176 $DEBUG = true
177 $VERBOSE = true
178 AppLog.set_level
179 end
180
181 opts.on('-v', '--verbose', 'Run verbosely') do |v|
182 $VERBOSE = true
183 AppLog.set_level
184 end
185
186 opts.on('--version', 'Version number') do |bool|
187 puts VERSION
188 exit 1
189 end
190
191 opts.on_tail('-h', '--help', 'Show this message') do
192 puts opts
193 exit 1
194 end
195 end
196 $logger.debug "command-line arguments: #{args}"
197
198 config['files'] ||= []
199 rest = opts.parse!(args)
200 if rest.size == 1 && rest.last =~ /\.vba$/
201 config['cmd'] = 'install'
202 config['files'] << rest.shift
203 else
204 config['cmd'] = rest.shift
205 config['files'].concat(rest)
206 end
207 config['vimoutdir'] ||= config['outdir']
208
209 return Vimball.new(config)
210
211 end
212
213
214 protected
215
216
217 def read_config(config)
218 file = config['configfile']
219 until @configs.include?(file)
220 @configs << file
221 if File.readable?(file)
222 $logger.debug "Read configuration from #{file}"
223 config.merge!(YAML.load_file(file))
224 file = config['configfile']
225 break
226 end
227 end
228 end
229
230 end
231
232
233 def initialize(config)
234 @config = config
235 end
236
237
238 def run
239 if ready?
240
241 meth = "do_#{@config['cmd']}"
242 @config['files'].each do |file|
243 @repo = nil
244 $logger.debug "#{@config['cmd']}: #{file}"
245 if respond_to?(meth)
246 send(meth, file)
247 else
248 $logger.fatal "Unknown command: #{@config['cmd']}"
249 exit 5
250 end
251 end
252
253 post = "post_#{@config['cmd']}"
254 send(post) if respond_to?(post)
255
256 end
257 end
258
259
260 def ready?
261
262 unless @config['vimfiles'] and File.directory?(@config['vimfiles'])
263 $logger.fatal "Where are your vimfiles?"
264 exit 5
265 end
266
267 cmds = ['vba', 'install', 'list']
268 unless cmds.include?(@config['cmd'])
269 $logger.fatal "Command must be one of: #{cmds.join(', ')}"
270 exit 5
271 end
272
273 if @config['files'].empty?
274 $logger.fatal "No input files"
275 exit 5
276 end
277
278 return true
279
280 end
281
282
283 def do_vba(recipe)
284
285 vimball = [HEADER]
286
287 name = File.basename(recipe, '.recipe')
288 vbafile = File.join(@config['outdir'], name + '.vba')
289 vbafile << '.gz' if @config['compress']
cd46be53 »
2012-09-28 Allow glob patterns in vimball recipes (probably incompatible with vi…
290
291 files = File.readlines(recipe)
292 files.map! do |pattern0|
293 pattern = pattern0.chomp
294 fullpattern = File.join(@config['vimfiles'], pattern)
295 fullpattern1 = filename_on_disk(name, pattern, fullpattern)
296 files1 = Dir[fullpattern1]
297 unless @repo.nil?
298 file_start = @repo.size + 1
299 files1.map! do |file|
300 file[file_start..-1]
301 end
302 end
303 files1
304 end
305 files.flatten!
628a30ea »
2010-11-01 Initial (includes code for uploading to vim.org)
306
307
308 if @config['update'] and File.exist?(vbafile)
309 vba_mtime = File.mtime(vbafile)
310 $logger.debug "MTIME VBA: #{vbafile}: #{vba_mtime}"
311 if files.all? {|file|
312 file = file.strip
313 filename = File.join(@config['vimfiles'], file)
314 filename1 = filename_on_disk(name, file, filename)
315 unless File.exist?(filename1)
316 $logger.error "File does not exist: #{filename1}"
317 return
318 end
319 mtime = File.mtime(filename1)
320 older = mtime <= vba_mtime
321 $logger.debug "MTIME: #{filename1}: #{mtime} => #{older}"
322 older
323 }
324 $logger.info "VBA is up to date: #{vbafile}"
325 return
326 end
327 end
328
329 files.each do |file|
330 file = file.strip
331 unless file.empty?
332 filename = File.join(@config['vimfiles'], file)
333 filename1 = filename_on_disk(name, file, filename)
334 if File.readable?(filename1)
335 content = File.readlines(filename1)
336 else
337 $logger.error "File does not exist: #{filename}"
338 return
339 end
340 # content.each do |line|
341 # line.sub!(/(\r\n|\r)$/, "\n")
342 # end
343
344 filename = clean_filename(filename)
345
346 rewrite = @config['rewrite']
347 if rewrite
348 rewrite.each do |pattern, replacement|
349 rx = Regexp.new(pattern)
350 filename.gsub!(rx, replacement)
351 end
352 end
353
354 vimball << "#{filename} [[[1\n#{content.size}\n"
355 vimball.concat(content)
356 end
357 end
358
359 ensure_dir_exists(File.dirname(vbafile))
360 vimball = vimball.join
361
362 if @config['compress']
363 $logger.warn "Save as: #{vbafile}"
364 unless @config['dry']
365 Zlib::GzipWriter.open(vbafile) do |gz|
366 gz.write(vimball)
367 end
368 end
369 else
370 $logger.warn "Save as: #{vbafile}"
371 file_write(vbafile, 'w') do |io|
372 io.puts(vimball)
373 end
374 end
375
376 end
377
378
379 def do_install(file)
380 filebase, vimball = read_vimball(file)
381 installdir = get_installdir(filebase)
382 $logger.warn "Install #{file} in #{installdir}"
383
384 recipe = with_vimball(vimball) do |basename, content|
385 filename = File.join(installdir, basename)
386 ensure_dir_exists(File.dirname(filename))
387 $logger.info "Write #{filename}"
388 file_write(filename) do |io|
389 io.puts(content.join)
390 end
391 end
392
393 if @config['save_recipes']
394 recipefile = File.join(@config['installdir'], 'vimballs', 'recipes', filebase + '.recipe')
395 $logger.debug "Save recipe file: #{recipefile}"
396 ensure_dir_exists(File.dirname(recipefile))
397 file_write(recipefile) do |io|
398 io.puts recipe.join("\n")
399 end
400 end
401
402 if @config['record']
403 record = File.join(@config['vimfiles'], '.VimballRecord')
404 $logger.debug "Save vimball-record information: #{record}"
405 file_write(record, 'a') do |io|
406 info = recipe.map {|r|
407 rr = File.expand_path(File.join(@config['vimoutdir'], r))
408 "call delete(#{rr.inspect})"
409 }.join('|')
410 io.puts "#{filebase}.vba: #{info}"
411 end
412 end
413
414 end
415
416
417 def post_install
418 helptags = @config['helptags']
419 if helptags.is_a?(String) and !helptags.empty?
420 helptags = helptags % File.join(@config['outdir'], 'doc')
421 if File.exist?(helptags)
422 $logger.info "Create helptags: #{helptags}"
423 `#{helptags}` unless @config['dry']
424 end
425 end
426 end
427
428
429 def do_list(file)
430 filebase, vimball = read_vimball(file)
431 $logger.info "List #{file}"
432 recipe = with_vimball(vimball)
433 puts recipe.join("\n")
434 end
435
436
437 def read_vimball(file)
438 vimball = nil
439 if file =~ /\.gz$/
440 filebase = File.basename(File.basename(file, '.gz'), '.*')
441 File.open(file) do |f|
442 gzip = Zlib::GzipReader.new(f)
443 vimball = gzip.readlines
444 end
445 else
446 filebase = File.basename(file, '.*')
447 vimball = File.readlines(file)
448 end
449 header = vimball.shift(3).join
450 if header != HEADER
451 $logger.fatal "Not a vimball: #{file}"
452 exit 5
453 end
454 return filebase, vimball
455 end
456
457
458 # Takes optional block as argument.
459 def with_vimball(vimball)
460 recipe = []
461 until vimball.empty?
462
463 fileheader = vimball.shift
464 nlines = vimball.shift.to_i
465 m = /^(.*?)\t\[\[\[1$/.match(fileheader)
466 if m and nlines > 0
467 basename = m[1]
468 recipe << basename
469 content = vimball.shift(nlines)
470 yield(basename, content) if block_given?
471 else
472 $logger.fatal "Error when parsing vimball: #{file}"
473 exit 5
474 end
475
476 end
477 return recipe
478 end
479
480
481 def get_installdir(vimball)
482 installdir = @config['installdir']
483 if @config['repo']
484 installdir = File.join(installdir, @config['repodir'], File.basename(vimball, '.*'))
485 end
486 installdir
487 end
488
489 def ensure_dir_exists(dir)
490 unless @config['dry'] or File.exist?(dir) or dir.empty? or dir == '.'
491 parent = File.dirname(dir)
492 unless File.exist?(parent)
493 ensure_dir_exists(parent)
494 end
495 $logger.info "mkdir #{dir}"
496 Dir.mkdir(dir)
497 end
498 end
499
500 def file_write(filename, mode='w', &block)
501 $logger.info "Write file: #{filename}"
502 unless @config['dry']
503 if File.exist?(filename) and mode !~ /^a/
504 $logger.warn "Overwrite existing file"
505 end
506 File.open(filename, mode, &block)
507 end
508 end
509
510
511 def clean_filename(filename)
512 filename = Pathname.new(filename).relative_path_from(Pathname.new(@config['vimfiles'])).to_s
513 filename.gsub!(/\\/, '/')
514 return filename
515 end
516
517
518 def filename_on_disk(name, file, filename)
519 if File.exist?(filename)
520 return filename
521 else
522 case @repo
523 when String
524 return File.join(@repo, file)
525 when nil
526 for root in @config['roots'] || []
527 if @config['repo_fmt']
528 repo_name = @config['repo_fmt'] % name
529 else
530 repo_name = name
531 end
532 repo = File.join(root, repo_name)
533 filename1 = File.join(repo, file)
534 if File.exist?(filename1)
535 @repo = repo
536 return filename1
537 end
538 end
539 @repo = false
540 end
541 r = @config['replacements']
542 if r and r[filename]
543 return r[filename]
544 else
545 g = @config['gsub']
546 if g
547 for rxs, rpl in g
548 filename = filename.gsub(Regexp.new(rxs), rpl)
549 end
550 end
551 return filename
552 end
553 end
554 end
555
556 end
557
558
559 if __FILE__ == $0
560
561 Vimball.with_args(ARGV).run
562
563 end
564
565
566 # Local Variables:
567 # revisionRx: VERSION\s\+=\s\+\'
568 # End:
Something went wrong with that request. Please try again.