PageRenderTime 25ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/bin/vim-update-bundles

https://github.com/yundem/x
Ruby | 373 lines | 278 code | 64 blank | 31 comment | 24 complexity | 97d4890321ac2e315d7256c5dcd6fbab MD5 | raw file
  1. #!/usr/bin/env ruby
  2. # Reads the bundles you want installed out of your $HOME/.vimrc file,
  3. # then synchronizes .vim/bundles to match, downloading new repositories
  4. # as needed. It also removes bundles that are no longer used.
  5. #
  6. # To specify a bundle in your .vimrc, just add a line like this:
  7. # " BUNDLE: git://git.wincent.com/command-t.git
  8. # If you want a branch other than 'master', add the branch name on the end:
  9. # " Bundle: git://github.com/vim-ruby/vim-ruby.git noisy
  10. # Or tag or sha1: (this results in a detached head, see 'git help checkout')
  11. # " bundle: git://github.com/bronson/vim-closebuffer.git 0.2
  12. # " bundle: git://github.com/tpope/vim-rails.git 42bb0699
  13. #
  14. # If your .vim folder is stored in a git repository, you can add bundles
  15. # as submodules by putting "submodule=true" in ~/.vim-update-bundles.conf.
  16. #
  17. # Modified by: roktas
  18. # - allow using alternative vim files to put bundle lines (currently
  19. # ~/.vim/bundles.vim and ~/.vim/bundle.vim)
  20. # - rename Trashed-Bundle to simply .bundle
  21. require 'fileutils'
  22. require 'open-uri'
  23. require 'openssl'
  24. # XXX: Hack to avoid ECONNRESET errors when connecting to GitHub over SSL
  25. OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ssl_version] = "SSLv3"
  26. def dotvim *path
  27. # Path to files inside your .vim directory, i.e. dotvim('autoload', 'pathogen')
  28. File.join $dotvim, *path
  29. end
  30. def ensure_dir dir
  31. Dir.mkdir dir unless test ?d, dir
  32. end
  33. def download_file url, file
  34. open(url, :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE) do |r|
  35. File.open(file, 'w') do |w|
  36. w.write(r.read)
  37. end
  38. end
  39. end
  40. def run *cmd
  41. # runs cmd, returns its stdout, bails on error. Just a more powerful system().
  42. # arg, IO.popen only accepts a string on 1.8 so rewrite it here.
  43. options = { :acceptable_exit_codes => [0] }
  44. options.merge!(cmd.pop) if cmd.last.kind_of?(Hash)
  45. puts "-> #{[cmd].join(" ")}" if $verbose
  46. outr, outw = IO::pipe
  47. pid = fork {
  48. outr.close; STDOUT.reopen outw; outw.close
  49. exec *cmd.flatten.map { |c| c.to_s }
  50. }
  51. outw.close
  52. result = outr.read
  53. outr.close
  54. Process.waitpid pid
  55. unless options[:acceptable_exit_codes].include?($?.exitstatus)
  56. raise "command <<#{[cmd].join(" ")}>> exited with code #{$?.exitstatus}"
  57. end
  58. result
  59. end
  60. def git *cmd
  61. if !$verbose && %w{checkout clone fetch pull}.include?(cmd.first.to_s)
  62. cmd.insert 1, '-q'
  63. end
  64. run :git, *cmd
  65. end
  66. def print_bundle dir, doc
  67. version = date = ''
  68. Dir.chdir(dir) do
  69. version = `git describe --tags 2>/dev/null`.chomp
  70. version = "n/a" if version == ''
  71. date = git(:log, '-1', '--pretty=format:%ai').chomp
  72. end
  73. doc.printf " %-32s %-22s %s\n", "|#{dir}|", version, date.split(' ').first
  74. end
  75. def ignore_doc_tags
  76. exclude = File.read ".git/info/exclude"
  77. if exclude !~ /doc\/tags/
  78. File.open(".git/info/exclude", "w") { |f|
  79. f.write exclude.chomp + "\ndoc/tags\n"
  80. }
  81. end
  82. end
  83. def in_git_root inpath=nil
  84. # submodules often require the cwd to be the git root. if you pass a path
  85. # relative to the cwd, your block receives it relative to the root.
  86. path = File.join Dir.pwd, inpath if inpath
  87. Dir.chdir("./" + git('rev-parse', '--show-cdup').chomp) do
  88. path.sub! /^#{Dir.pwd}\/?/, '' if path
  89. yield path
  90. end rescue nil # git deletes the bundle dir if it's empty
  91. end
  92. def clone_bundle dir, url, tagstr
  93. unless $submodule
  94. puts "cloning #{dir} from #{url}#{tagstr}"
  95. git :clone, url, dir
  96. else
  97. puts "adding submodule #{dir} from #{url}#{tagstr}"
  98. in_git_root(dir) { |mod| git :submodule, :add, url, mod }
  99. end
  100. end
  101. def download_bundle dir, url, tag, doc
  102. tagstr = " at #{tag}" if tag
  103. if test ?d, dir
  104. remote = Dir.chdir(dir) { git(:config, '--get', 'remote.origin.url').chomp }
  105. if remote == url
  106. puts "updating #{dir} from #{url}#{tagstr}"
  107. Dir.chdir(dir) { git :fetch }
  108. else
  109. puts "repo has changed from #{remote} to #{url}"
  110. remove_bundle dir
  111. ensure_dir dotvim('bundle') # if it was the last bundle, git removed the dir
  112. clone_bundle dir, url, tagstr
  113. end
  114. else
  115. clone_bundle dir, url, tagstr
  116. end
  117. Dir.chdir(dir) do
  118. # if branch is checked out then it must be pulled, not checked out again
  119. if tag && !test(?f, ".git/refs/heads/#{tag}")
  120. git :checkout, tag
  121. else
  122. git :pull, :origin, tag || :master
  123. end
  124. ignore_doc_tags
  125. end
  126. in_git_root(dir) { |mod| git :add, mod } if $submodule
  127. print_bundle(dir, doc)
  128. end
  129. def read_vimrc
  130. File.open("#{ENV['HOME']}/.vimrc") do |file|
  131. file.each_line { |line| yield line }
  132. end
  133. end
  134. def read_bundle_lines(*args)
  135. ([*args] + [
  136. "#{ENV['HOME']}/.vimrc",
  137. "#{ENV['HOME']}/.vim/local.vim",
  138. "#{ENV['HOME']}/.vim/bundles.vim",
  139. "#{ENV['HOME']}/.vim/bundle.vim"
  140. ]).each do |path|
  141. next unless File.exist? path
  142. File.open(path) do |file|
  143. file.each_line { |line| yield line }
  144. end
  145. end
  146. end
  147. def remove_bundle_to dir, destination
  148. puts "Erasing #{dir}, find it in #{destination}"
  149. FileUtils.mv dir, destination
  150. if $submodule
  151. in_git_root(dir) do |mod|
  152. git :rm, mod
  153. ['.gitmodules', '.git/config'].each do |filename|
  154. begin
  155. text = File.read filename
  156. File.open(filename, 'w+') do |file|
  157. file.puts text.gsub(/\[submodule "#{mod}"\][^\[]+/m,'')
  158. end
  159. rescue
  160. puts " Could not delete submodule entries from .gitmodules and .git/config"
  161. end
  162. end
  163. end
  164. end
  165. end
  166. def remove_bundle dir
  167. trash_dir = dotvim(".bundle")
  168. ensure_dir trash_dir
  169. 1.upto(100) do |i|
  170. destination = "#{trash_dir}/#{dir}-#{'%02d' % i}"
  171. unless test ?d, destination
  172. remove_bundle_to dir, destination
  173. return
  174. end
  175. end
  176. raise "unable to remove #{dir}, please delete #{trash_dir}"
  177. end
  178. def run_bundle_command dir, cmd
  179. puts " running: #{cmd}"
  180. status = Dir.chdir(dir) { system(cmd); $? }
  181. unless status.success?
  182. puts " BUNDLE-COMMAND command failed!"
  183. exit 47
  184. end
  185. end
  186. def update_bundles doc
  187. existing_bundles = Dir['*']
  188. dir = nil
  189. read_bundle_lines do |line|
  190. if line =~ /^\s*"\s*bundle:\s*(.*)$/i
  191. url, tag = $1.split
  192. dir = url.split('/').last.gsub(/^vim-|\.git$/, '')
  193. download_bundle dir, url, tag, doc
  194. existing_bundles.delete dir
  195. elsif line =~ /^\s*"\s*bundle[ -]command:\s*(.*)$/i
  196. raise "BUNDLE-COMMAND must come after BUNDLE" if dir.nil?
  197. run_bundle_command dir, $1
  198. elsif line =~ /^\s*"\s*static:\s*(.*)$/i
  199. dir = $1
  200. puts " leavig #{dir} alone"
  201. existing_bundles.delete dir
  202. end
  203. end
  204. existing_bundles.each { |dir| remove_bundle(dir) }
  205. if $submodule
  206. in_git_root do
  207. puts " updating submodules"
  208. git :submodule, :init
  209. git :submodule, :update
  210. end
  211. end
  212. end
  213. def update_bundles_and_docs
  214. ensure_dir dotvim('doc')
  215. File.open(dotvim('doc', 'bundles.txt'), "w") do |doc|
  216. doc.printf "%-34s %s\n\n\n", "*bundles* *bundles.txt*", "Installed Bundles"
  217. doc.puts "Last updated by vim-update-bundles on #{Time.now}.\n\n"
  218. doc.printf " %-32s %-22s %s\n", "PLUGIN", "VERSION", "RELEASE DATE"
  219. doc.puts "-" * 72
  220. bundle_dir = dotvim('bundle')
  221. ensure_dir bundle_dir
  222. Dir.chdir(bundle_dir) { update_bundles(doc) }
  223. doc.puts "\n"
  224. end
  225. end
  226. def interpolate options, val, message, i
  227. raise "Interpolation is now $#{$1} instead of ENV[#{$1}] #{message} #{i}" if val =~ /ENV\[['"]?([^\]]*)['"]?\]/
  228. STDERR.puts "WARNING: quotes in a config item are probably a mistake #{message} #{i}" if val =~ /["']/
  229. val.gsub(/\$([A-Za-z0-9_]+)/) { options[$1.to_sym] || ENV[$1] || raise("$#{$1} is not defined #{message} #{i}") }
  230. end
  231. def process_options options, args, message
  232. args.each_with_index do |arg,i|
  233. arg = arg.gsub /^\s*-?-?|\s*$/, '' # leading dashes in front of options are optional
  234. return if arg == '' || arg =~ /^#/
  235. k,v = arg.split /\s*=\s*/, 2
  236. raise "Unknown option #{k.inspect} #{message} #{i}" unless options.has_key? k.to_sym
  237. # config file has changed, carry the warning for a few point releases...
  238. options[k.to_sym] = v ? interpolate(options,v,message,i).split("'").join("\\'") : true
  239. end
  240. end
  241. # Returns the first path that exists or the last one if nothing exists.
  242. def choose_file *paths
  243. paths.find { |p| test ?f, p } || paths[-1]
  244. end
  245. def set_default_options opts
  246. dotfiles = File.join(ENV['HOME'], '.dotfiles')
  247. opts[:dotfiles_path] ||= dotfiles if test(?d, dotfiles)
  248. if opts[:dotfiles_path]
  249. raise "#{opts[:dotfiles_path]} doesn't exist!" unless test(?d, opts[:dotfiles_path])
  250. opts[:vimdir_path] ||= File.join(opts[:dotfiles_path], 'vim')
  251. opts[:vimrc_path] ||= choose_file(File.join([opts[:dotfiles_path], 'vim', 'vimrc']),
  252. File.join([opts[:dotfiles_path], 'vimrc']))
  253. else
  254. opts[:vimdir_path] ||= File.join(ENV['HOME'], '.vim')
  255. opts[:vimrc_path] ||= choose_file(File.join([ENV['HOME'], '.vim', 'vimrc']),
  256. File.join([ENV['HOME'], '.vimrc']))
  257. end
  258. end
  259. def ensure_vim_environment configuration
  260. ensure_dir dotvim
  261. ensure_dir dotvim('autoload')
  262. unless test ?f, dotvim('autoload', 'pathogen.vim')
  263. puts "Downloading Pathogen..."
  264. download_file configuration[:pathogen_url], dotvim('autoload', 'pathogen.vim')
  265. end
  266. unless configuration[:starter_url].nil? || test(?f, configuration[:vimrc_path])
  267. puts "Downloading starter vimrc..."
  268. download_file configuration[:starter_url], configuration[:vimrc_path]
  269. end
  270. run :ln, '-s', configuration[:vimdir_path], "#{ENV['HOME']}/.vim" unless test ?e, "#{ENV['HOME']}/.vim"
  271. run :ln, '-s', configuration[:vimrc_path], "#{ENV['HOME']}/.vimrc" unless test ?e, "#{ENV['HOME']}/.vimrc"
  272. end
  273. def generate_helptags
  274. puts "updating helptags..."
  275. # looks like stock vim on a Mac often exits with 1, even when doing nothing
  276. run :vim, '-e', '-c', 'call pathogen#helptags()', '-c', 'q', :acceptable_exit_codes => [0, 1] unless ENV['TESTING']
  277. end
  278. def read_configuration configuration
  279. conf_file = File.join ENV['HOME'], '.vim-update-bundles.conf'
  280. process_options configuration, File.open(conf_file).readlines, "in #{conf_file} line" if test(?f, conf_file)
  281. process_options configuration, ARGV, "in command line argument"
  282. set_default_options configuration
  283. configuration.keys.sort.each { |k| puts "# option #{k} = #{configuration[k].inspect}" } if configuration[:verbose]
  284. configuration.delete :dotfiles_path # ensure we don't accidentally use it later on
  285. end
  286. configuration = {
  287. :verbose => nil, # git commands are quiet by default, set verbose=true to hear everything
  288. :submodule => false, # if submodule is set then use git submodules instead of cloning
  289. :dotfiles_path => nil, # full path to the dotfiles directory
  290. :vimdir_path => nil, # full path to ~/.vim (creates symlink if not in $HOME)
  291. :vimrc_path => nil, # full path to ~/.vimrc (creates symlink if not in $HOME)
  292. # used when spinning up a new Vim environment
  293. :starter_url => "https://github.com/bronson/dotfiles/raw/master/.vimrc",
  294. :pathogen_url => "https://github.com/tpope/vim-pathogen/raw/master/autoload/pathogen.vim",
  295. }
  296. read_configuration configuration
  297. # these globals are unfortunate but life is even more heinous without them
  298. $dotvim = configuration[:vimdir_path]
  299. $submodule = configuration[:submodule]
  300. $verbose = configuration[:verbose]
  301. ensure_vim_environment configuration
  302. update_bundles_and_docs
  303. generate_helptags
  304. puts "done! Start Vim and type ':help bundles' to see what you have installed."