vim-recovery 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 822874fa51c53d423d5516a2518af51f81a0cb82
4
+ data.tar.gz: b14cea8e6c0031129c88ba42df039be631a65aab
5
+ SHA512:
6
+ metadata.gz: e6f0729247d75f2bb0abd9349c7557103144ab30a83b397baf79dfbccec1b7a17499da826e30fc5421bdbc99af84e4805475c946aff993dfedb9395d689bf9e5
7
+ data.tar.gz: d13a2828334a8b7d57d051185f07120ea766569c20dd5fd08615ff07322b3e13ad9b419909ea82a227f56eb5389cbb5a66984ddb8b6e26f53f4fd1d823a8a037
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ /pkg/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in vim-recovery.gemspec
4
+ gemspec
@@ -0,0 +1,92 @@
1
+ # vim-recovery
2
+
3
+ vim-recovery is a utility to find and recover swapfiles.
4
+
5
+
6
+ ### Rationale
7
+
8
+ After a system crash, my system is often littered with Vim swapfiles. I
9
+ usually want to do two things. (1) Recover my in-progress work (unsaved files)
10
+ and (2) remove any unmodified files to prevent Vim from prompting me about the
11
+ swapfile whenever I open a file.
12
+
13
+ `find . -type f -name "*.sw[a-p]"` doesn't take into account the actual type of
14
+ the file. For example, it finds Shockwave Flash files (\*.swf).
15
+
16
+ vim-recovery searches directories for swap files and checks the header of the
17
+ file to determine if it actually is a Vim swapfile.
18
+
19
+
20
+ ### Installation
21
+
22
+ $ gem install vim-recovery
23
+
24
+
25
+ ### Usage
26
+
27
+ $ vim-recovery --help
28
+ Usage: vim-recovery [options] [paths...]
29
+ Commands:
30
+ -l, --list Find and list Vim swapfiles
31
+ --clean Delete unmodified swapfiles if process is not still running
32
+ Options:
33
+ -r, --recursive Also search subdirectories
34
+ -v, --verbose Be more verbose
35
+
36
+ --version Show version
37
+ -h, --help Display this help
38
+
39
+ **Finding swapfiles**
40
+
41
+ The "M" flag means the file was modified, and the "R" flag means the process is
42
+ still running. The output is tab-delimited to make it easier to parse with
43
+ tools such as cut.
44
+
45
+ $ vim-recovery --list
46
+ [ R] ./.gitignore.swp ~speckins/git/vim-recovery/.gitignore
47
+ [MR] ./.README.md.swp ~speckins/git/vim-recovery/README.md
48
+ [ R] ./.vim-recovery.gemspec.swp ~speckins/git/vim-recovery/vim-recovery.gemspec
49
+ [ ] ./.crashed.txt.swp ~speckins/git/vim-recovery/crashed.txt
50
+
51
+ **Removing unmodified swapfiles**
52
+
53
+ $ vim-recovery --clean --verbose
54
+ ./.crashed.txt.swp
55
+
56
+ $ vim-recovery --list
57
+ [ R] ./.gitignore.swp ~speckins/git/vim-recovery/.gitignore
58
+ [MR] ./.README.md.swp ~speckins/git/vim-recovery/README.md
59
+ [ R] ./.vim-recovery.gemspec.swp ~speckins/git/vim-recovery/vim-recovery.gemspec
60
+
61
+ **Recursively cleaning swapfiles**
62
+
63
+ $ vim-recovery --clean --recursive --verbose
64
+ ./.crashed.txt.swp
65
+ ./lib/vim_recovery/.crashed.txt.swp
66
+
67
+ **Filtering the output of --list**
68
+
69
+ $ vim-recovery --list --recursive
70
+ [ R] ./.gitignore.swp ~speckins/git/vim-recovery/.gitignore
71
+ [MR] ./.README.md.swp ~speckins/git/vim-recovery/README.md
72
+ [ R] ./.vim-recovery.gemspec.swp ~speckins/git/vim-recovery/vim-recovery.gemspec
73
+ [M ] ./.crashed.txt.swp ~speckins/git/vim-recovery/crashed.txt
74
+
75
+ $ vim-recovery --list --recursive | grep -a '^\[M \]' | cut -f2
76
+ ./.crashed.txt.swp
77
+
78
+
79
+ ### Vim options
80
+
81
+ The directory option can be added to .vimrc to make it easier to find
82
+ swapfiles. This is not necessary to use vim-recovery, but it can make it
83
+ faster. (It takes a long time to search the 900,000+ files in my home
84
+ directory.)
85
+
86
+ " .vimrc:
87
+ set directory=~/tmp/swapfiles//
88
+
89
+ > For Unix and Win32, if a directory ends in two path separators "//"
90
+ > or "\\\\", the swap file name will be built from the complete path to
91
+ > the file with all path separators substituted to percent '%' signs.
92
+ > This will ensure file name uniqueness in the preserve directory.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_{test,spec}.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'vim_recovery/command'
5
+ require 'vim_recovery/version'
6
+
7
+ command = nil
8
+ options = {}
9
+
10
+ parser = OptionParser.new do |parser|
11
+ parser.banner = "Usage: vim-recovery [options] [paths...]"
12
+
13
+ # --------------------------------------------------------------------------
14
+ parser.separator 'Commands:'
15
+ # --------------------------------------------------------------------------
16
+
17
+ parser.on '--list', '-l', 'Find and list Vim swapfiles' do
18
+ command = VimRecovery::Command::List
19
+ end
20
+
21
+ parser.on '--clean', 'Delete unmodified swapfiles if process is not still running' do
22
+ command = VimRecovery::Command::Clean
23
+ end
24
+
25
+ parser.on_tail '--version', 'Show version' do
26
+ puts VimRecovery::Version
27
+ exit
28
+ end
29
+
30
+ parser.on_tail '--help', '-h', 'Display this help' do
31
+ puts parser.help
32
+ exit
33
+ end
34
+
35
+ # --------------------------------------------------------------------------
36
+ parser.separator 'Options:'
37
+ # --------------------------------------------------------------------------
38
+
39
+ parser.on '--recursive', '-r', 'Also search subdirectories' do
40
+ options[:recursive] = true
41
+ end
42
+
43
+ parser.on '--verbose', '-v', 'Be more verbose' do
44
+ options[:verbose] = true
45
+ end
46
+
47
+ parser.separator ''
48
+ end
49
+
50
+ paths = parser.parse ARGV
51
+
52
+ if command.nil?
53
+ puts "Required: --list or --clean"
54
+ puts parser.help
55
+ exit 1
56
+ end
57
+
58
+ paths << '.' if paths.empty?
59
+
60
+ paths.reject! do |path|
61
+ unless File.directory? path
62
+ STDERR.puts "#{path} is neither a directory nor a symlink to a directory"
63
+ end
64
+ end
65
+
66
+ exit 1 if paths.empty?
67
+
68
+ command.new(paths, options).run
@@ -0,0 +1,9 @@
1
+ module VimRecovery
2
+
3
+ # Strictly speaking, ".saa" will never be a swapfile because the loop
4
+ # gives up at that name, but including it saves us a separate pattern.
5
+ # https://github.com/vim/vim/blob/95f096030ed1a8afea028f2ea295d6f6a70f466f/src/memline.c#L4566
6
+ SWAPFILES = ['*.sw[a-p]', '*.s[a-v][a-z]'].freeze
7
+ end
8
+
9
+ require 'vim_recovery/swapfile'
@@ -0,0 +1,41 @@
1
+ require 'vim_recovery'
2
+
3
+ module VimRecovery
4
+ class Command
5
+ def initialize(paths, options = {})
6
+ @paths = paths
7
+ @options = options
8
+ end
9
+
10
+ def patterns
11
+ @patterns ||=
12
+ if @options[:recursive]
13
+ SWAPFILES.map { |swp| "**/#{swp}" }
14
+ else
15
+ SWAPFILES
16
+ end
17
+ end
18
+
19
+ def each_swapfile &block
20
+ @paths.each do |path|
21
+ path = path.chomp '/'
22
+ path_patterns = patterns.map { |swp| "#{path}/#{swp}" }
23
+
24
+ Dir.glob(path_patterns, File::FNM_DOTMATCH).each do |filename|
25
+ next unless File.file? filename
26
+
27
+ begin
28
+ swapfile = VimRecovery::Swapfile.open filename
29
+ next unless swapfile.valid_block0?
30
+ yield swapfile
31
+ ensure
32
+ swapfile.close
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ require_relative 'command/list'
41
+ require_relative 'command/clean'
@@ -0,0 +1,13 @@
1
+ module VimRecovery
2
+ class Command::Clean < Command
3
+ def run
4
+ each_swapfile do |swapfile|
5
+ next if swapfile.modified? || swapfile.still_running?
6
+
7
+ puts swapfile.path if @options[:verbose]
8
+ swapfile.close
9
+ File.unlink swapfile
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module VimRecovery
2
+ class Command::List < Command
3
+ def run
4
+ each_swapfile do |swapfile|
5
+ puts "[%s%s]\t%s\t%s" % [
6
+ swapfile.modified? ? 'M' : ' ',
7
+ swapfile.still_running? ? 'R' : ' ',
8
+ swapfile.path,
9
+ swapfile.original_filename
10
+ ]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,125 @@
1
+ module VimRecovery
2
+ class Swapfile < ::File
3
+
4
+ Block0 = Struct.new :id, :version, :page_size, :mtime, :ino, :pid, :uname,
5
+ :hname, :fname, :crypt_seed, :flags, :dirty
6
+
7
+ # https://github.com/vim/vim/blob/95f096030ed1a8afea028f2ea295d6f6a70f466f/src/memline.c#L62
8
+ VALID_BLOCK_IDS = %w{b0 bc bC bd}.freeze
9
+
10
+ # https://github.com/vim/vim/blob/95f096030ed1a8afea028f2ea295d6f6a70f466f/src/memline.c#L143
11
+ B0_MAGIC_LONG = 0x30313233
12
+ B0_MAGIC_INT = 0x20212223
13
+ B0_MAGIC_SHORT = 0x10111213
14
+ B0_MAGIC_CHAR = 0x55
15
+ B0_DIRTY = 0x55
16
+ B0_FF_MASK = 3
17
+ B0_SAME_DIR = 4
18
+ B0_HAS_FENC = 8
19
+
20
+ # https://github.com/vim/vim/blob/95f096030ed1a8afea028f2ea295d6f6a70f466f/src/option.h#L80
21
+ EOL_UNIX = 0 # NL
22
+ EOL_DOS = 1 # CR NL
23
+ EOL_MAC = 2 # CR
24
+
25
+ EOL = {
26
+ EOL_UNIX => :unix,
27
+ EOL_DOS => :dos,
28
+ EOL_MAC => :mac
29
+ }.freeze
30
+
31
+ # https://github.com/vim/vim/blob/95f096030ed1a8afea028f2ea295d6f6a70f466f/src/memline.c#L160
32
+ HEADER_FORMAT = [
33
+ 'A2', # identifier, "b0"
34
+ 'A10', # version, e.g., "VIM 7.4"
35
+ 'V', # page_size
36
+ 'V', # mtime (not used)
37
+ 'V', # inode
38
+ 'V', # pid
39
+ 'A40', # username or uid
40
+ 'A40', # hostname
41
+ 'A890', # filename
42
+ 'a8', # crypt seed (for encrypted swapfiles)
43
+ 'C', # flags
44
+ 'C', # dirty (0x00/0x55)
45
+ #'l!', # magic_long
46
+ #'i!', # magic_int
47
+ #'s!', # magic_short
48
+ #'c', # magic_char
49
+ ].join ''
50
+
51
+ def self.swapfile?(name)
52
+ open(name) { |f| f.valid_block0? }
53
+ end
54
+
55
+ def encrypted?
56
+ # "b0" not encrypted
57
+ # "bc" encrypted (zip)
58
+ # "bC" encrypted (blowfish)
59
+ # "bd" encrypted (blowfish2)
60
+ block0.id[1] != '0'
61
+ end
62
+
63
+ def modified?
64
+ block0.dirty == B0_DIRTY
65
+ end
66
+
67
+ def mtime
68
+ block0.mtime == 0 ? super : Time.at(block0.mtime)
69
+ end
70
+
71
+ def pid
72
+ block0.pid
73
+ end
74
+
75
+ def original_filename
76
+ block0.fname unless block0.fname.empty?
77
+ end
78
+
79
+ def still_running?
80
+ Process.getpgid pid
81
+ true
82
+ rescue Errno::ESRCH
83
+ false
84
+ end
85
+
86
+ # Flags
87
+
88
+ # The original length of the filename field was 900 chars. At some point
89
+ # (flags were introduced in Vim 7.0) the last two bytes were used to store
90
+ # "other things" ("dirty" char and flags). When encrypted swapfiles were
91
+ # introduced (Vim 7.3) another 8 bytes were used to store the crypt seed,
92
+ # so the contemporary filename field size is 890.
93
+
94
+ # Swap file is in directory of edited file (see ":help directory").
95
+ def same_dir?
96
+ B0_SAME_DIR & block0.flags > 0
97
+ end
98
+
99
+ def has_file_encoding?
100
+ B0_HAS_FENC & block0.flags > 0
101
+ end
102
+
103
+ def file_format
104
+ # "Zero means it's not set (compatible with Vim 6.x), otherwise it's
105
+ # EOL_UNIX + 1, EOL_DOS + 1 or EOL_MAC + 1."
106
+ EOL[(block0.flags & B0_FF_MASK) - 1]
107
+ end
108
+
109
+ def valid_block0?
110
+ rewind
111
+ VALID_BLOCK_IDS.include?(read 2)
112
+ end
113
+
114
+ private
115
+
116
+ def block0
117
+ @block0 ||=
118
+ begin
119
+ rewind
120
+ Block0.new *read(1032).unpack(HEADER_FORMAT)
121
+ end
122
+ end
123
+
124
+ end
125
+ end
@@ -0,0 +1,3 @@
1
+ module VimRecovery
2
+ Version = '0.0.1'
3
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'lib/vim_recovery/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+
5
+ spec.name = 'vim-recovery'
6
+ spec.version = VimRecovery::Version
7
+ spec.authors = ['Steven Peckins']
8
+ spec.email = ['steven.peckins@gmail.com']
9
+ spec.summary = 'A utility for finding and recovering Vim swapfiles'
10
+ spec.homepage = 'https://github.com/speckins/vim-recovery'
11
+ spec.license = 'MIT'
12
+
13
+ spec.files = `git ls-files -z`.split("\x0")
14
+
15
+ spec.bindir = 'exe'
16
+ spec.executables << 'vim-recovery'
17
+
18
+ spec.add_development_dependency 'bundler', '~> 1.13'
19
+ spec.add_development_dependency 'rake', '~> 10.0'
20
+
21
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vim-recovery
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Steven Peckins
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-12-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description:
42
+ email:
43
+ - steven.peckins@gmail.com
44
+ executables:
45
+ - vim-recovery
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - Gemfile
51
+ - README.md
52
+ - Rakefile
53
+ - exe/vim-recovery
54
+ - lib/vim_recovery.rb
55
+ - lib/vim_recovery/command.rb
56
+ - lib/vim_recovery/command/clean.rb
57
+ - lib/vim_recovery/command/list.rb
58
+ - lib/vim_recovery/swapfile.rb
59
+ - lib/vim_recovery/version.rb
60
+ - vim-recovery.gemspec
61
+ homepage: https://github.com/speckins/vim-recovery
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.5.1
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: A utility for finding and recovering Vim swapfiles
85
+ test_files: []