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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/README.md +92 -0
- data/Rakefile +10 -0
- data/exe/vim-recovery +68 -0
- data/lib/vim_recovery.rb +9 -0
- data/lib/vim_recovery/command.rb +41 -0
- data/lib/vim_recovery/command/clean.rb +13 -0
- data/lib/vim_recovery/command/list.rb +14 -0
- data/lib/vim_recovery/swapfile.rb +125 -0
- data/lib/vim_recovery/version.rb +3 -0
- data/vim-recovery.gemspec +21 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
data/exe/vim-recovery
ADDED
@@ -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
|
data/lib/vim_recovery.rb
ADDED
@@ -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,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: []
|