tms 1.2.0 → 1.3.0
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.
- data/README.markdown +1 -1
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/bin/tms +41 -19
- data/ext/tms/tms.c +26 -2
- data/lib/tms.rb +11 -9
- data/lib/tms/backup.rb +92 -111
- data/lib/tms/better_attr_accessor.rb +20 -0
- data/lib/tms/comparison.rb +110 -0
- data/lib/tms/path.rb +118 -0
- data/lib/tms/space.rb +41 -32
- data/lib/tms/table.rb +30 -28
- data/tms.gemspec +6 -4
- metadata +8 -6
- data/lib/tms/pathname.rb +0 -49
data/README.markdown
CHANGED
@@ -4,7 +4,7 @@ Time Machine Status
|
|
4
4
|
|
5
5
|
View avaliable Time Machine backups and show changes
|
6
6
|
|
7
|
-
Name
|
7
|
+
Name from [fernlightning.com](http://www.fernlightning.com/doku.php?id=software:misc:tms)
|
8
8
|
|
9
9
|
## Copyright
|
10
10
|
|
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'rubygems'
|
1
|
+
# require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
require 'jeweler'
|
4
4
|
require 'rake/gem_ghost_task'
|
@@ -9,7 +9,7 @@ name = 'tms'
|
|
9
9
|
Jeweler::Tasks.new do |gem|
|
10
10
|
gem.name = name
|
11
11
|
gem.summary = %Q{Time Machine Status}
|
12
|
-
gem.description = %Q{View avaliable Time Machine backups and show diff}
|
12
|
+
gem.description = %Q{View avaliable Time Machine backups and show their diff}
|
13
13
|
gem.homepage = "http://github.com/toy/#{name}"
|
14
14
|
gem.license = 'MIT'
|
15
15
|
gem.authors = ['Boba Fat']
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.3.0
|
data/bin/tms
CHANGED
@@ -1,46 +1,68 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
require 'tms'
|
5
|
-
rescue LoadError
|
6
|
-
require 'rubygems'
|
7
|
-
require 'tms'
|
8
|
-
end
|
9
|
-
|
3
|
+
require 'tms'
|
10
4
|
require 'optparse'
|
11
5
|
|
12
6
|
def option_parser
|
13
|
-
OptionParser.new do |op|
|
7
|
+
@option_parser ||= OptionParser.new do |op|
|
14
8
|
op.banner = <<-BANNER
|
9
|
+
#{op.program_name}, version #{Tms.version}
|
10
|
+
|
15
11
|
Usege:
|
16
|
-
List:
|
17
|
-
Diff:
|
18
|
-
Diff with previous: #{op.program_name} [options] id|nxxx
|
12
|
+
List: #{op.program_name} [options]
|
13
|
+
Diff: #{op.program_name} [options] id|nXXX [id|nXXX]
|
19
14
|
BANNER
|
20
15
|
|
21
|
-
op.on('-d', '--directory DIRECTORY', 'Use backup directory') do |
|
22
|
-
Tms::Backup.backups_dir =
|
16
|
+
op.on('-d', '--directory DIRECTORY', 'Use backup directory') do |backups_dir|
|
17
|
+
Tms::Backup.backups_dir = backups_dir
|
23
18
|
end
|
24
19
|
|
25
|
-
op.on('-f', '--filter DIRECTORY', 'Show diff starting from directory') do |
|
26
|
-
Tms::Backup.filter_dir
|
20
|
+
op.on('-f', '--filter DIRECTORY', 'Show diff starting from directory', ' (can be used multiple times)') do |filter_dir|
|
21
|
+
Tms::Backup.add_filter_dir(filter_dir)
|
27
22
|
end
|
28
23
|
|
29
|
-
op.on('-
|
24
|
+
op.on('-i', '--[no-]in-progress', 'Show backups in progress',
|
25
|
+
' (note: some directories will be',
|
26
|
+
' empty in unfinished backups)') do |show_in_progress|
|
30
27
|
Tms::Backup.show_in_progress = show_in_progress
|
31
28
|
end
|
32
29
|
|
33
30
|
op.on('-l', '--[no-]long', 'Show more info about backup in list') do |show_all_columns|
|
34
31
|
Tms::Backup.show_all_columns = show_all_columns
|
35
32
|
end
|
33
|
+
|
34
|
+
op.on('--[no-]color', 'Use color', ' (true by default if stdout is a tty)') do |colorize|
|
35
|
+
Tms::Backup.colorize = colorize
|
36
|
+
end
|
37
|
+
|
38
|
+
op.on('--[no-]progress', 'Show progress when counting folder size', ' (true by default if stderr is a tty)') do |show_progress|
|
39
|
+
Tms::Backup.show_progress = show_progress
|
40
|
+
end
|
41
|
+
|
42
|
+
op.on('--[no-]decimal', 'Use base 10 size') do |use_decimal|
|
43
|
+
Tms::Space.base10 = use_decimal
|
44
|
+
end
|
45
|
+
|
46
|
+
op.on_tail('-h', '--help', 'Show this message') do
|
47
|
+
puts op.help
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
op.on_tail('--version', 'Show version') do
|
52
|
+
puts Tms.version
|
53
|
+
exit
|
54
|
+
end
|
36
55
|
end
|
37
56
|
end
|
38
57
|
|
39
|
-
|
58
|
+
ids, options = ARGV.partition{ |arg| arg =~ /^[\-n]?\d+$/ }
|
40
59
|
begin
|
41
60
|
option_parser.parse!(options)
|
42
|
-
rescue OptionParser::
|
43
|
-
abort e
|
61
|
+
rescue OptionParser::ParseError => e
|
62
|
+
abort "#{e.to_s}\n#{option_parser.help}"
|
63
|
+
end
|
64
|
+
unless options.empty?
|
65
|
+
abort "Unknown arguments: #{options.join(' ')}"
|
44
66
|
end
|
45
67
|
|
46
68
|
case ids.length
|
data/ext/tms/tms.c
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
#include "ruby.h"
|
2
2
|
#include <CoreServices/CoreServices.h>
|
3
|
+
#include <SystemConfiguration/SystemConfiguration.h>>
|
3
4
|
|
4
5
|
static VALUE backup_volume(VALUE self){
|
5
6
|
OSStatus status = pathTooLongErr;
|
6
7
|
char *path;
|
7
|
-
size_t pathLength
|
8
|
+
size_t pathLength;
|
8
9
|
|
9
10
|
CFDataRef aliasData;
|
10
11
|
AliasHandle alias;
|
@@ -15,7 +16,7 @@ static VALUE backup_volume(VALUE self){
|
|
15
16
|
if (aliasData) {
|
16
17
|
if (noErr == PtrToHand(CFDataGetBytePtr(aliasData), (Handle *)&alias, CFDataGetLength(aliasData))) {
|
17
18
|
if (noErr == FSResolveAlias(NULL, alias, &fs, &wasChanged)) {
|
18
|
-
path = malloc(pathLength);
|
19
|
+
path = malloc(pathLength = 256);
|
19
20
|
while (noErr != (status = FSRefMakePath(&fs, (UInt8*)path, pathLength))) {
|
20
21
|
if (pathTooLongErr == status) {
|
21
22
|
pathLength += 256;
|
@@ -35,7 +36,30 @@ static VALUE backup_volume(VALUE self){
|
|
35
36
|
}
|
36
37
|
}
|
37
38
|
|
39
|
+
static VALUE computer_name(VALUE self){
|
40
|
+
char *name;
|
41
|
+
size_t nameLength;
|
42
|
+
|
43
|
+
CFStringEncoding encoding;
|
44
|
+
CFStringRef cfName;
|
45
|
+
|
46
|
+
if (cfName = SCDynamicStoreCopyComputerName(NULL, &encoding)) {
|
47
|
+
name = malloc(nameLength = 256);
|
48
|
+
while (!CFStringGetCString(cfName, name, nameLength, encoding)) {
|
49
|
+
nameLength += 256;
|
50
|
+
name = reallocf(name, nameLength);
|
51
|
+
}
|
52
|
+
|
53
|
+
CFRelease(cfName);
|
54
|
+
|
55
|
+
return rb_str_new2(name);
|
56
|
+
} else {
|
57
|
+
return Qnil;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
38
61
|
void Init_tms() {
|
39
62
|
VALUE cTms = rb_define_module("Tms");
|
40
63
|
rb_define_singleton_method(cTms, "backup_volume", backup_volume, 0);
|
64
|
+
rb_define_singleton_method(cTms, "computer_name", computer_name, 0);
|
41
65
|
}
|
data/lib/tms.rb
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
+
require 'tms/path'
|
2
|
+
require 'tms/backup'
|
3
|
+
require 'tms/table'
|
4
|
+
|
1
5
|
module Tms
|
2
6
|
class << self
|
7
|
+
def version
|
8
|
+
File.read(File.join(File.dirname(__FILE__), '../VERSION')).strip
|
9
|
+
end
|
10
|
+
|
3
11
|
def list
|
4
12
|
backups = Backup.list
|
5
13
|
Table.new do |t|
|
6
|
-
t.col '', :red
|
7
|
-
t.col '', :blue
|
14
|
+
t.col '', Backup.colorize? && :red
|
15
|
+
t.col '', Backup.colorize? && :blue
|
8
16
|
t.col 'num'
|
9
17
|
t.col 'name'
|
10
18
|
if Backup.show_all_columns
|
@@ -53,7 +61,7 @@ module Tms
|
|
53
61
|
end
|
54
62
|
backup_a = Backup.list[a_id] or abort("No backup #{a}")
|
55
63
|
backup_b = Backup.list[b_id] or abort("No backup #{b}")
|
56
|
-
|
64
|
+
Comparison.new(backup_a, backup_b).run
|
57
65
|
end
|
58
66
|
|
59
67
|
private
|
@@ -88,9 +96,3 @@ module Tms
|
|
88
96
|
end
|
89
97
|
end
|
90
98
|
end
|
91
|
-
|
92
|
-
require 'tms.so'
|
93
|
-
require 'tms/backup'
|
94
|
-
require 'tms/space'
|
95
|
-
require 'tms/table'
|
96
|
-
require 'tms/pathname'
|
data/lib/tms/backup.rb
CHANGED
@@ -1,135 +1,116 @@
|
|
1
|
-
require 'colored'
|
2
1
|
require 'xattr'
|
2
|
+
require 'tms.so'
|
3
|
+
require 'tms/comparison'
|
4
|
+
require 'tms/better_attr_accessor'
|
3
5
|
|
4
|
-
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
backup_volume = Tms.backup_volume
|
9
|
-
abort 'backup volume not avaliable' if backup_volume.nil?
|
6
|
+
module Tms
|
7
|
+
class Backup
|
8
|
+
class << self
|
9
|
+
extend BetterAttrAccessor
|
10
10
|
|
11
|
-
|
12
|
-
abort
|
11
|
+
def backup_volume
|
12
|
+
Tms.backup_volume or abort('backup volume not avaliable')
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
def computer_name
|
16
|
+
Tms.computer_name or abort('can\'t get computer name')
|
17
|
+
end
|
16
18
|
|
17
|
-
|
19
|
+
def backups_dir
|
20
|
+
unless @backups_dir
|
21
|
+
self.backups_dir = Path.new(backup_volume) / 'Backups.backupdb' / computer_name
|
22
|
+
end
|
23
|
+
@backups_dir
|
24
|
+
end
|
25
|
+
def backups_dir=(backups_dir)
|
26
|
+
backups_dir = Path.new(backups_dir)
|
27
|
+
abort %{backups dir «#{backups_dir}» is not a dir} unless backups_dir.directory?
|
28
|
+
@backups_dir = backups_dir
|
18
29
|
end
|
19
|
-
end
|
20
|
-
def backups_dir=(dir)
|
21
|
-
@backups_dir = Pathname(dir)
|
22
|
-
end
|
23
30
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
when /^\d{4}-\d{2}-\d{2}-\d{6}$/
|
33
|
-
new(path)
|
34
|
-
when /^\d{4}-\d{2}-\d{2}-\d{6}\.inProgress$/
|
35
|
-
if show_in_progress
|
36
|
-
path.children.select(&:directory?).each do |path_in_progress|
|
37
|
-
new(path_in_progress, true)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end.flatten.compact.sort
|
31
|
+
def filter_dirs
|
32
|
+
@filter_dirs ||= []
|
33
|
+
end
|
34
|
+
def filter_dirs?
|
35
|
+
!filter_dirs.empty?
|
36
|
+
end
|
37
|
+
def add_filter_dir(filter_dir)
|
38
|
+
filter_dirs << File.expand_path(filter_dir)
|
42
39
|
end
|
43
|
-
end
|
44
40
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
(a.path.children(false) | b.path.children(false)).reject{ |child| child.to_s[0, 1] == '.' }.sort.each do |path|
|
53
|
-
total += compare(a.path + path, b.path + path, Pathname('/') + path)
|
54
|
-
end
|
41
|
+
better_attr_accessor :show_in_progress
|
42
|
+
better_attr_accessor :show_all_columns
|
43
|
+
better_attr_accessor :colorize
|
44
|
+
better_attr_accessor :show_progress
|
45
|
+
|
46
|
+
def colorize?
|
47
|
+
!colorize.nil? ? colorize : $stdout.tty?
|
55
48
|
end
|
56
|
-
puts "#{'Total:'.bold} #{Tms::Space.space(total, :color => true)}"
|
57
|
-
end
|
58
49
|
|
59
|
-
|
50
|
+
def show_progress?
|
51
|
+
!show_progress.nil? ? show_progress : $stderr.tty?
|
52
|
+
end
|
60
53
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
if a.readable? && b.readable?
|
74
|
-
puts "#{'█≠█'.yellow} #{a.colored_size} #{path}#{a.postfix}" unless a.symlink? && a.readlink == b.readlink
|
75
|
-
if a.real_directory?
|
76
|
-
total = 0
|
77
|
-
(a.children(false) | b.children(false)).sort.each do |child|
|
78
|
-
total += compare(a + child, b + child, path + child)
|
54
|
+
def list
|
55
|
+
@list ||= begin
|
56
|
+
backups_dir.children.map do |path|
|
57
|
+
case path.basename.to_s
|
58
|
+
when /^\d{4}-\d{2}-\d{2}-\d{6}$/
|
59
|
+
new(path)
|
60
|
+
when /^\d{4}-\d{2}-\d{2}-\d{6}\.inProgress$/
|
61
|
+
if show_in_progress?
|
62
|
+
path.children.select(&:directory?).map do |path_in_progress|
|
63
|
+
new(path_in_progress, true)
|
64
|
+
end
|
65
|
+
end
|
79
66
|
end
|
80
|
-
|
81
|
-
else
|
82
|
-
b.size
|
83
|
-
end
|
84
|
-
else
|
85
|
-
puts "??? #{path}#{a.postfix}".red.bold
|
86
|
-
0
|
67
|
+
end.flatten.compact.sort
|
87
68
|
end
|
88
|
-
else
|
89
|
-
0
|
90
69
|
end
|
91
70
|
end
|
92
|
-
end
|
93
71
|
|
94
|
-
|
95
|
-
def initialize(path, in_progress = false)
|
96
|
-
@path = path
|
97
|
-
@in_progress = in_progress
|
98
|
-
end
|
72
|
+
extend BetterAttrAccessor
|
99
73
|
|
100
|
-
|
101
|
-
|
102
|
-
|
74
|
+
better_attr_reader :path, :in_progress
|
75
|
+
def initialize(path, in_progress = false)
|
76
|
+
@path = path
|
77
|
+
@in_progress = in_progress
|
78
|
+
end
|
103
79
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
def finished_at
|
108
|
-
@finished_at ||= Time.at(xattr.get('com.apple.backupd.SnapshotCompletionDate').to_i / 1_000_000.0)
|
109
|
-
end
|
110
|
-
def completed_in
|
111
|
-
finished_at - started_at
|
112
|
-
end
|
113
|
-
{
|
114
|
-
:state => 'com.apple.backupd.SnapshotState',
|
115
|
-
:type => 'com.apple.backupd.SnapshotType',
|
116
|
-
:version => 'com.apple.backup.SnapshotVersion',
|
117
|
-
:number => 'com.apple.backup.SnapshotNumber',
|
118
|
-
}.each do |name, attr|
|
119
|
-
class_eval <<-src
|
120
|
-
def #{name}
|
121
|
-
@#{name} ||= xattr.get('#{attr}').to_i rescue '-'
|
122
|
-
end
|
123
|
-
src
|
124
|
-
end
|
80
|
+
def name
|
81
|
+
@name ||= in_progress? ? "#{path.dirname.basename}/#{path.basename}" : path.basename.to_s
|
82
|
+
end
|
125
83
|
|
126
|
-
|
127
|
-
|
128
|
-
|
84
|
+
def started_at
|
85
|
+
@start_date ||= Time.at(xattr.get('com.apple.backupd.SnapshotStartDate').to_i / 1_000_000.0)
|
86
|
+
end
|
87
|
+
def finished_at
|
88
|
+
@finished_at ||= Time.at(xattr.get('com.apple.backupd.SnapshotCompletionDate').to_i / 1_000_000.0)
|
89
|
+
end
|
90
|
+
def completed_in
|
91
|
+
finished_at - started_at
|
92
|
+
end
|
93
|
+
{
|
94
|
+
:state => 'com.apple.backupd.SnapshotState',
|
95
|
+
:type => 'com.apple.backupd.SnapshotType',
|
96
|
+
:version => 'com.apple.backup.SnapshotVersion',
|
97
|
+
:number => 'com.apple.backup.SnapshotNumber',
|
98
|
+
}.each do |name, attr|
|
99
|
+
class_eval <<-src
|
100
|
+
def #{name}
|
101
|
+
@#{name} ||= xattr.get('#{attr}').to_i rescue '-'
|
102
|
+
end
|
103
|
+
src
|
104
|
+
end
|
129
105
|
|
130
|
-
|
106
|
+
def <=>(other)
|
107
|
+
name <=> other.name
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
131
111
|
|
132
|
-
|
133
|
-
|
112
|
+
def xattr
|
113
|
+
@xattr ||= Xattr.new(path)
|
114
|
+
end
|
134
115
|
end
|
135
116
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module BetterAttrAccessor
|
2
|
+
def better_attr_reader(*names)
|
3
|
+
names.each do |name|
|
4
|
+
attr_reader name
|
5
|
+
# leaves nil and false as is, returns true for everything else
|
6
|
+
class_eval <<-RUBY
|
7
|
+
def #{name}?
|
8
|
+
@#{name} && true
|
9
|
+
end
|
10
|
+
RUBY
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def better_attr_accessor(*names)
|
15
|
+
better_attr_reader *names
|
16
|
+
names.each do |name|
|
17
|
+
attr_writer name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'tms/space'
|
2
|
+
|
3
|
+
module Tms
|
4
|
+
class Comparison
|
5
|
+
attr_reader :backup_a, :backup_b, :total
|
6
|
+
def initialize(backup_a, backup_b)
|
7
|
+
@backup_a, @backup_b = backup_a, backup_b
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
@total = 0
|
12
|
+
begin
|
13
|
+
root_dirs = (backup_a.path.children(false) | backup_b.path.children(false)).sort
|
14
|
+
root_dirs.reject!{ |root_dir| root_dir.path[0, 1] == '.' }
|
15
|
+
root_dirs.each do |root_dir|
|
16
|
+
dirs = Backup.filter_dirs? ? Backup.filter_dirs.map{ |filter_dir| root_dir / filter_dir } : [root_dir]
|
17
|
+
dirs.each do |dir|
|
18
|
+
compare(backup_a.path / dir, backup_b.path / dir, Path.new('/', dir))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
rescue Interrupt
|
22
|
+
puts
|
23
|
+
puts 'Interrupted'
|
24
|
+
end
|
25
|
+
line "#{colorize 'Total:', :total} #{space(total)}"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def compare(a, b, path)
|
31
|
+
case
|
32
|
+
when !a.exist?
|
33
|
+
count_space b, path, colorize(' █', :right), "#{path}#{b.postfix}", :recursive => true
|
34
|
+
when !b.exist?
|
35
|
+
count_space a, path, colorize('█ ', :left), "#{path}#{a.postfix}", :recursive => true, :no_total => true
|
36
|
+
when a.ftype != b.ftype
|
37
|
+
count_space b, path, colorize('█≠█', :diff_type), "#{path}#{b.postfix} (#{a.ftype}=>#{b.ftype})", :recursive => true
|
38
|
+
when a.lstat.ino != b.lstat.ino
|
39
|
+
if a.readable_real? && b.readable_real?
|
40
|
+
count_space b, path, colorize('█≠█', :diff), "#{path}#{b.postfix}" unless b.symlink? && a.readlink == b.readlink
|
41
|
+
if b.directory? && !b.symlink?
|
42
|
+
(a.children(false) | b.children(false)).sort.each do |child|
|
43
|
+
compare(a / child, b / child, path / child)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
line colorize("??? #{Space::NOT_COUNTED_SPACE} #{path}#{a.postfix}", :unreadable)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
progress do
|
51
|
+
path
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
COLORS = {
|
57
|
+
:total => {:extra => :bold},
|
58
|
+
:right => {:foreground => :green},
|
59
|
+
:left => {:foreground => :blue},
|
60
|
+
:diff_type => {:foreground => :red, :extra => :bold},
|
61
|
+
:diff => {:foreground => :yellow},
|
62
|
+
:unreadable => {:foreground => :red, :extra => :bold},
|
63
|
+
}
|
64
|
+
CLEAR_LINE = "\e[K"
|
65
|
+
|
66
|
+
def line(s)
|
67
|
+
$stdout.puts "#{s}#{CLEAR_LINE}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def progress
|
71
|
+
if Tms::Backup.show_progress?
|
72
|
+
@last_progress ||= Time.now
|
73
|
+
if (now = Time.now) > @last_progress + 0.1
|
74
|
+
$stderr.print "#{yield}#{CLEAR_LINE}\r"
|
75
|
+
@last_progress = now
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def space(size)
|
81
|
+
Tms::Space.space(size, :color => Tms::Backup.colorize?)
|
82
|
+
end
|
83
|
+
|
84
|
+
def colorize(s, type)
|
85
|
+
if Tms::Backup.colorize?
|
86
|
+
Colored.colorize(s, COLORS[type])
|
87
|
+
else
|
88
|
+
s
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def count_space(backup_path, path, prefix, postfix, options = {})
|
93
|
+
sub_total = 0
|
94
|
+
if options[:recursive]
|
95
|
+
backup_path.find do |sub_path|
|
96
|
+
sub_total += sub_path.size_if_real_file
|
97
|
+
progress do
|
98
|
+
"#{prefix} #{space sub_total} #{path / sub_path.to_s[backup_path.to_s.length..-1].to_s}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
else
|
102
|
+
sub_total = backup_path.size_if_real_file
|
103
|
+
end
|
104
|
+
line "#{prefix} #{space sub_total} #{postfix}"
|
105
|
+
unless options[:no_total]
|
106
|
+
@total += sub_total
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/tms/path.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
module Tms
|
4
|
+
# cleaned up Pathname
|
5
|
+
class Path
|
6
|
+
attr_reader :path
|
7
|
+
def initialize(*parts)
|
8
|
+
@path = File.join(*parts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def /(other)
|
12
|
+
self.class.new(@path, other)
|
13
|
+
end
|
14
|
+
|
15
|
+
def hash
|
16
|
+
@path.hash
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
return false unless Path === other
|
21
|
+
other.to_s == @path
|
22
|
+
end
|
23
|
+
alias_method :===, :==
|
24
|
+
alias_method :eql?, :==
|
25
|
+
|
26
|
+
def <=>(other)
|
27
|
+
return nil unless Path === other
|
28
|
+
@path <=> other.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def basename(*args)
|
32
|
+
self.class.new(File.basename(@path, *args))
|
33
|
+
end
|
34
|
+
|
35
|
+
def dirname(*args)
|
36
|
+
self.class.new(File.dirname(@path))
|
37
|
+
end
|
38
|
+
|
39
|
+
def readlink
|
40
|
+
self.class.new(File.readlink(@path))
|
41
|
+
end
|
42
|
+
|
43
|
+
def ftype
|
44
|
+
File.ftype(@path)
|
45
|
+
end
|
46
|
+
|
47
|
+
def lstat
|
48
|
+
File.lstat(@path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def size
|
52
|
+
File.size(@path)
|
53
|
+
end
|
54
|
+
|
55
|
+
def size_if_real_file
|
56
|
+
file? && !symlink? ? File.size(@path) : 0
|
57
|
+
end
|
58
|
+
|
59
|
+
def exist?
|
60
|
+
File.exist?(@path)
|
61
|
+
end
|
62
|
+
|
63
|
+
def file?
|
64
|
+
File.file?(@path)
|
65
|
+
end
|
66
|
+
|
67
|
+
def directory?
|
68
|
+
File.directory?(@path)
|
69
|
+
end
|
70
|
+
|
71
|
+
def symlink?
|
72
|
+
File.symlink?(@path)
|
73
|
+
end
|
74
|
+
|
75
|
+
def readable_real?
|
76
|
+
File.readable_real?(@path)
|
77
|
+
end
|
78
|
+
|
79
|
+
def children(with_directory = true)
|
80
|
+
with_directory = false if @path == '.'
|
81
|
+
result = []
|
82
|
+
Dir.foreach(@path) do |e|
|
83
|
+
next if e == '.' || e == '..'
|
84
|
+
if with_directory
|
85
|
+
result << self.class.new(File.join(@path, e))
|
86
|
+
else
|
87
|
+
result << self.class.new(e)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
93
|
+
def find(&block)
|
94
|
+
if @path == '.'
|
95
|
+
Find.find(@path){ |f| yield self.class.new(f.sub(/^.\//, '')) }
|
96
|
+
else
|
97
|
+
Find.find(@path){ |f| yield self.class.new(f) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_s
|
102
|
+
@path.dup
|
103
|
+
end
|
104
|
+
alias_method :to_str, :to_s
|
105
|
+
alias_method :to_path, :to_s
|
106
|
+
|
107
|
+
def postfix
|
108
|
+
case
|
109
|
+
when symlink?
|
110
|
+
'@'
|
111
|
+
when directory?
|
112
|
+
'/'
|
113
|
+
else
|
114
|
+
''
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/tms/space.rb
CHANGED
@@ -1,41 +1,50 @@
|
|
1
1
|
require 'colored'
|
2
2
|
|
3
|
-
module Tms
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
3
|
+
module Tms
|
4
|
+
module Space
|
5
|
+
SIZE_SYMBOLS = %w[B K M G T P E Z Y].freeze
|
6
|
+
COLORS = [].tap do |colors|
|
7
|
+
[:white, :black, :yellow, :red].each do |color|
|
8
|
+
colors << {:foreground => color}
|
9
|
+
colors << {:foreground => color, :extra => :bold}
|
10
|
+
end
|
11
|
+
colors << {:foreground => :yellow, :extra => :reversed}
|
12
|
+
colors << {:foreground => :red, :extra => :reversed}
|
13
|
+
end.freeze
|
14
|
+
PRECISION = 1
|
15
|
+
LENGTH = 4 + PRECISION + 1
|
16
|
+
COEF = 1 / Math.log(10)
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
length = 4 + precision + (options[:can_be_negative] ? 1 : 0)
|
18
|
+
EMPTY_SPACE = ' ' * LENGTH
|
19
|
+
NOT_COUNTED_SPACE = '!' * LENGTH
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
class << self
|
22
|
+
attr_writer :base10
|
23
|
+
def denominator
|
24
|
+
@denominator ||= @base10 ? 1000.0 : 1024.0
|
25
|
+
end
|
26
|
+
|
27
|
+
def space(size, options = {})
|
28
|
+
case size
|
29
|
+
when false
|
30
|
+
NOT_COUNTED_SPACE.bold.red
|
31
|
+
when 0
|
32
|
+
EMPTY_SPACE
|
33
|
+
else
|
34
|
+
number, degree = size, 0
|
35
|
+
while number.abs >= 1000 && degree < SIZE_SYMBOLS.length - 1
|
36
|
+
number /= denominator
|
37
|
+
degree += 1
|
38
|
+
end
|
24
39
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
40
|
+
space = "#{degree == 0 ? number.to_s : "%.#{PRECISION}f" % number}#{SIZE_SYMBOLS[degree]}".rjust(LENGTH)
|
41
|
+
if options[:color]
|
42
|
+
color = [[Math.log(size) * COEF, 1].max.to_i, COLORS.length].min - 1
|
43
|
+
space = Colored.colorize(space, COLORS[color])
|
44
|
+
end
|
45
|
+
space
|
46
|
+
end
|
30
47
|
end
|
31
|
-
step = options[:color].is_a?(Hash) && options[:color][:step] || 10
|
32
|
-
start = options[:color].is_a?(Hash) && options[:color][:start] || 1
|
33
|
-
coef = 10.0 / (step * Math.log(10))
|
34
|
-
color = [[Math.log(size) * coef - start, 0].max.to_i, COLORS.length - 1].min rescue 0
|
35
|
-
Colored.colorize(space, COLORS[color])
|
36
|
-
else
|
37
|
-
space
|
38
48
|
end
|
39
49
|
end
|
40
|
-
self.extend self
|
41
50
|
end
|
data/lib/tms/table.rb
CHANGED
@@ -1,39 +1,41 @@
|
|
1
1
|
require 'colored'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Tms
|
4
|
+
class Table
|
5
|
+
attr_accessor :cols, :rows
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
ADJUST = {:left => :ljust, :right => :rjust, :center => :center}
|
12
|
-
def col(name, color = nil, adjust = nil)
|
13
|
-
@cols << {:name => name, :color => color, :adjust => ADJUST[adjust]}
|
14
|
-
end
|
7
|
+
def initialize(&block)
|
8
|
+
@cols, @rows = [], []
|
9
|
+
yield self if block_given?
|
10
|
+
end
|
15
11
|
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
ADJUST = {:left => :ljust, :right => :rjust, :center => :center}
|
13
|
+
def col(name, color = nil, adjust = nil)
|
14
|
+
@cols << {:name => name, :color => color, :adjust => ADJUST[adjust]}
|
15
|
+
end
|
19
16
|
|
20
|
-
|
21
|
-
|
22
|
-
col[:width] = ([col[:name]] + @rows.map{ |row| row[i] }).map(&:to_s).map(&:length).max
|
17
|
+
def <<(row)
|
18
|
+
@rows << row
|
23
19
|
end
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
width
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
21
|
+
def lines
|
22
|
+
@cols.each_with_index do |col, i|
|
23
|
+
col[:width] = ([col[:name]] + @rows.map{ |row| row[i] }).map(&:to_s).map(&:length).max
|
24
|
+
end
|
25
|
+
|
26
|
+
([@cols.map{ |col| col[:name] }] + @rows).each_with_index.map do |line, i|
|
27
|
+
line.zip(@cols).map do |val, col|
|
28
|
+
width, color, adjust = col.values_at(:width, :color, :adjust)
|
29
|
+
adjust ||= val.is_a?(Integer) ? :rjust : :ljust
|
30
|
+
val_s = val.to_s.send(adjust, width)
|
31
|
+
val_s = Colored.colorize(val_s, :foreground => color) if color
|
32
|
+
val_s
|
33
|
+
end.join(' ')
|
34
|
+
end
|
33
35
|
end
|
34
|
-
end
|
35
36
|
|
36
|
-
|
37
|
-
|
37
|
+
def print
|
38
|
+
puts lines
|
39
|
+
end
|
38
40
|
end
|
39
41
|
end
|
data/tms.gemspec
CHANGED
@@ -5,13 +5,13 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{tms}
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Boba Fat"]
|
12
|
-
s.date = %q{2010-12-
|
12
|
+
s.date = %q{2010-12-11}
|
13
13
|
s.default_executable = %q{tms}
|
14
|
-
s.description = %q{View avaliable Time Machine backups and show diff}
|
14
|
+
s.description = %q{View avaliable Time Machine backups and show their diff}
|
15
15
|
s.executables = ["tms"]
|
16
16
|
s.extensions = ["ext/tms/extconf.rb"]
|
17
17
|
s.extra_rdoc_files = [
|
@@ -28,7 +28,9 @@ Gem::Specification.new do |s|
|
|
28
28
|
"ext/tms/tms.c",
|
29
29
|
"lib/tms.rb",
|
30
30
|
"lib/tms/backup.rb",
|
31
|
-
"lib/tms/
|
31
|
+
"lib/tms/better_attr_accessor.rb",
|
32
|
+
"lib/tms/comparison.rb",
|
33
|
+
"lib/tms/path.rb",
|
32
34
|
"lib/tms/space.rb",
|
33
35
|
"lib/tms/table.rb",
|
34
36
|
"tms.gemspec"
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tms
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
8
|
+
- 3
|
9
9
|
- 0
|
10
|
-
version: 1.
|
10
|
+
version: 1.3.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Boba Fat
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-12-
|
18
|
+
date: 2010-12-11 00:00:00 +03:00
|
19
19
|
default_executable: tms
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -90,7 +90,7 @@ dependencies:
|
|
90
90
|
version: "0"
|
91
91
|
type: :development
|
92
92
|
version_requirements: *id005
|
93
|
-
description: View avaliable Time Machine backups and show diff
|
93
|
+
description: View avaliable Time Machine backups and show their diff
|
94
94
|
email:
|
95
95
|
executables:
|
96
96
|
- tms
|
@@ -109,7 +109,9 @@ files:
|
|
109
109
|
- ext/tms/tms.c
|
110
110
|
- lib/tms.rb
|
111
111
|
- lib/tms/backup.rb
|
112
|
-
- lib/tms/
|
112
|
+
- lib/tms/better_attr_accessor.rb
|
113
|
+
- lib/tms/comparison.rb
|
114
|
+
- lib/tms/path.rb
|
113
115
|
- lib/tms/space.rb
|
114
116
|
- lib/tms/table.rb
|
115
117
|
- tms.gemspec
|
data/lib/tms/pathname.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
require 'pathname'
|
2
|
-
|
3
|
-
class Pathname
|
4
|
-
def real_directory?
|
5
|
-
directory? && !symlink?
|
6
|
-
end
|
7
|
-
|
8
|
-
def lino
|
9
|
-
@ino ||= lstat.ino
|
10
|
-
end
|
11
|
-
|
12
|
-
def postfix
|
13
|
-
case
|
14
|
-
when symlink?
|
15
|
-
'@'
|
16
|
-
when directory?
|
17
|
-
'/'
|
18
|
-
else
|
19
|
-
''
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def count_size(options = {})
|
24
|
-
if @count_size.nil?
|
25
|
-
if exist?
|
26
|
-
if directory?
|
27
|
-
@counted_size = 0
|
28
|
-
find{ |path| @counted_size += path.size rescue nil } if options[:recursive]
|
29
|
-
else
|
30
|
-
@counted_size = size
|
31
|
-
end
|
32
|
-
else
|
33
|
-
@counted_size = false
|
34
|
-
end
|
35
|
-
end
|
36
|
-
@counted_size
|
37
|
-
end
|
38
|
-
|
39
|
-
def colored_size(options = {})
|
40
|
-
case size = count_size(options)
|
41
|
-
when false
|
42
|
-
'!!!!!!'
|
43
|
-
when 0
|
44
|
-
' '
|
45
|
-
else
|
46
|
-
Tms::Space.space(size, :color => true)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|