backup_cleaner 1.0.0.pre1

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Brian Alexander
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = backup_cleaner
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Brian Alexander. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "backup_cleaner"
8
+ gem.summary = %Q{A command-line script for cleaning up old backups}
9
+ gem.description = %Q{A command-line script for cleaning up old backups}
10
+ gem.email = "balexand@gmail.com"
11
+ gem.homepage = "http://github.com/balexand/backup_cleaner"
12
+ gem.authors = ["Brian Alexander"]
13
+ gem.add_dependency "activesupport", ">= 3.0.0"
14
+ gem.add_dependency "trollop", ">= 0"
15
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << 'lib' << 'test'
26
+ test.pattern = 'test/**/test_*.rb'
27
+ test.verbose = true
28
+ end
29
+
30
+ begin
31
+ require 'rcov/rcovtask'
32
+ Rcov::RcovTask.new do |test|
33
+ test.libs << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+ rescue LoadError
38
+ task :rcov do
39
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
+ end
41
+ end
42
+
43
+ task :test => :check_dependencies
44
+
45
+ task :default => :test
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "backup_cleaner #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0.pre1
@@ -0,0 +1,64 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{backup_cleaner}
8
+ s.version = "1.0.0.pre1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Brian Alexander"]
12
+ s.date = %q{2010-10-09}
13
+ s.default_executable = %q{backup_cleaner}
14
+ s.description = %q{A command-line script for cleaning up old backups}
15
+ s.email = %q{balexand@gmail.com}
16
+ s.executables = ["backup_cleaner"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "backup_cleaner.gemspec",
29
+ "bin/backup_cleaner",
30
+ "lib/backup_cleaner.rb",
31
+ "lib/backup_cleaner/cli.rb",
32
+ "test/helper.rb",
33
+ "test/test_backup_cleaner.rb"
34
+ ]
35
+ s.homepage = %q{http://github.com/balexand/backup_cleaner}
36
+ s.rdoc_options = ["--charset=UTF-8"]
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = %q{1.3.7}
39
+ s.summary = %q{A command-line script for cleaning up old backups}
40
+ s.test_files = [
41
+ "test/helper.rb",
42
+ "test/test_backup_cleaner.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0"])
51
+ s.add_runtime_dependency(%q<trollop>, [">= 0"])
52
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
53
+ else
54
+ s.add_dependency(%q<activesupport>, [">= 3.0.0"])
55
+ s.add_dependency(%q<trollop>, [">= 0"])
56
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
57
+ end
58
+ else
59
+ s.add_dependency(%q<activesupport>, [">= 3.0.0"])
60
+ s.add_dependency(%q<trollop>, [">= 0"])
61
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
62
+ end
63
+ end
64
+
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # run this script with --help for instructions
3
+
4
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
5
+
6
+ require 'backup_cleaner'
7
+ require 'backup_cleaner/cli'
8
+
9
+ BackupCleaner::Cli.run
@@ -0,0 +1,39 @@
1
+ require 'backup_cleaner'
2
+ require 'trollop'
3
+
4
+ module BackupCleaner
5
+ module Cli
6
+ def self.run
7
+ opts = Trollop::options do
8
+ banner <<-EOS
9
+ Cleans up backup files or folders within the specified folder. The names of these files/folders must include the backup date \
10
+ in a format like: YYYY-MM-DD. Any files/folders with names not matching this pattern will be left untouched. Between \
11
+ today and <days> days ago, all backups will be kept. Between today and <weeks> weeks ago, weekly backups will be kept. For \
12
+ all time, monthly backups will be kept. For weekly/monthly backups, the earliest available backup from the week/month will \
13
+ be kept. For example, if daily backups are present then weekly backups will be from Sunday and monthly backups will be from\
14
+ the 1st of the month.
15
+
16
+ This script can also handle backups from multiple projects in one directory as long as they are named differently. If files \
17
+ named aaa-2010-01-01.tar.gz and 2010-01-01.bbb.tar.bz2 exist then both will be kept. When comparing the names, only letters \
18
+ are considered. For example, aaa-2010-01-01.tar.gz and aaa-2010-01-01.12.12.12.tar.gz.2 are considered to be from the same \
19
+ backup project and unless these backups are from the last <days> days, one will be deleted.
20
+
21
+ Usage:
22
+ clean_backups.rb [options] <folder>
23
+
24
+ where [options] are:
25
+ EOS
26
+
27
+ opt :days, "Number of days for which to keep daily backups", :type => :int, :default => 14
28
+ opt :weeks, "Number of weeks for which to keep weekly backups", :type => :int, :default => 8
29
+ opt :dry_run, "Don't really delete anything, just print out what would have been deleted", :short => :n
30
+ end
31
+
32
+ folder = ARGV.shift
33
+ Trollop::die "No folder specified" unless folder
34
+
35
+ # clean the backups
36
+ BackupCleaner.clean_backups(folder, opts)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,69 @@
1
+ require 'active_support/time'
2
+ require 'fileutils'
3
+ require 'set'
4
+
5
+ module BackupCleaner
6
+ DATE_PATTERN = /^(.*)(\d{4}[-\.]\d{2}[-\.]\d{2})(.*)$/
7
+
8
+ Entry = Struct.new :name, :date, :prefix, :suffix
9
+ GroupKey = Struct.new :date_property, :prefix, :suffix
10
+
11
+ def self.clean_backups(folder, opts = {})
12
+ days = opts[:days]
13
+ weeks = opts[:weeks]
14
+ dry_run = opts[:dry_run]
15
+
16
+ raise ArgumentError, "'days' option must have an integer value" unless days.is_a? Integer
17
+ raise ArgumentError, "'weeks' option must have an integer value" unless weeks.is_a? Integer
18
+ raise ArgumentError, "#{folder} should be a directory" unless File.directory? folder
19
+
20
+ entries = Dir.entries(folder).select { |name| DATE_PATTERN =~ name }.collect do |name|
21
+ m = DATE_PATTERN.match(name)
22
+ Entry.new(name, Date.parse(m[2]), m[1].gsub(/[^A-Za-z]/, ''), m[3].gsub(/[^A-Za-z]/, ''))
23
+ end
24
+
25
+ weekly_keepers = Set.new(first_by_date_property(entries, :week_ordering_number))
26
+ monthly_keepers = Set.new(first_by_date_property(entries, :month_ordering_number))
27
+
28
+ entries.each do |entry|
29
+ unless entry.date >= days.days.ago.to_date ||
30
+ (week_ordering_number(entry.date) >= week_ordering_number(weeks.weeks.ago.to_date) && weekly_keepers.member?(entry)) ||
31
+ monthly_keepers.member?(entry)
32
+ if dry_run
33
+ puts "pretending to delete (dry_run) #{entry.name}"
34
+ else
35
+ puts "deleting #{entry.name}"
36
+ FileUtils.rm_rf(File.join(folder, entry.name))
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def self.first_by_date_property(entries, property_name)
43
+ # group entries by the specified date property plus file name prefix and suffix
44
+ groups = {}
45
+ entries.each do |entry|
46
+ group_key = GroupKey.new send(property_name, entry.date), entry.prefix, entry.suffix
47
+ groups[group_key] = {} unless groups.has_key? group_key
48
+ groups[group_key].merge!({entry.date => entry})
49
+ end
50
+
51
+ # return the earliest date in each group
52
+ groups.values.collect do |group|
53
+ group[group.keys.sort.first]
54
+ end
55
+ end
56
+ private_class_method :first_by_date_property
57
+
58
+ def self.week_ordering_number(date)
59
+ # cweek considers weeks to start on Monday; adjust so week starts on Sunday
60
+ date += 1.day if date.wday == 0
61
+ date.cweek + date.cwyear * 100
62
+ end
63
+ private_class_method :week_ordering_number
64
+
65
+ def self.month_ordering_number(date)
66
+ date.month + date.year * 100
67
+ end
68
+ private_class_method :month_ordering_number
69
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'backup_cleaner'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,108 @@
1
+ require 'helper'
2
+ require 'backup_cleaner'
3
+ require 'mocha'
4
+
5
+ class TestBackupCleaner < Test::Unit::TestCase
6
+
7
+ should "raise ArgumentError if invalid args passed to constructor" do
8
+ valid_opts = {:days => 14, :weeks => 2, :dry_run => true}
9
+ valid_folder = File.expand_path(Dir.pwd)
10
+
11
+ BackupCleaner.clean_backups(valid_folder, valid_opts)
12
+
13
+ # folder doesn't exist
14
+ assert_raises(ArgumentError) do
15
+ BackupCleaner.clean_backups('/paththatdoesntexist-asoiugsaoifaweasfoo', valid_opts)
16
+ end
17
+
18
+ # folder should be a String, not NilClass
19
+ assert_raises(TypeError) do
20
+ BackupCleaner.clean_backups(nil, valid_opts)
21
+ end
22
+
23
+ # missing days
24
+ assert_raises(ArgumentError) do
25
+ BackupCleaner.clean_backups(valid_folder, valid_opts.reject {|k,v| k == :days})
26
+ end
27
+
28
+ # days of invalid type
29
+ assert_raises(ArgumentError) do
30
+ BackupCleaner.clean_backups(valid_folder, valid_opts.merge(:days => "3"))
31
+ end
32
+
33
+ # missing weeks
34
+ assert_raises(ArgumentError) do
35
+ BackupCleaner.clean_backups(valid_folder, valid_opts.reject {|k,v| k == :weeks})
36
+ end
37
+
38
+ # weeks of invalid type
39
+ assert_raises(ArgumentError) do
40
+ BackupCleaner.clean_backups(valid_folder, valid_opts.merge(:weeks => nil))
41
+ end
42
+ end
43
+
44
+ should "clean correct backups" do
45
+ folder = "/foo/bar"
46
+
47
+ # mock todays date
48
+ fake_today_time = Time.parse("1981-12-18")
49
+ fake_today = fake_today_time.to_date
50
+ Time.expects(:current).at_least_once.returns(fake_today_time)
51
+
52
+ # create mock entries in a directory
53
+ entries = (0..180).collect {|i| "aaa-#{(fake_today-i).strftime('%Y-%m-%d')}.tar.gz"}
54
+ entries += ["1980-03-03-foo.tar.bz2", "1980-03-20-foo.tar.bz2",
55
+ "1980-03-03-bar.tar.bz2", "1980-03-20-bar.tar.bz2", "1980.03.21.2.bar.tar.bz2.3", "1981-03-15-bar.tar.bz2",
56
+ "nodate.txt"]
57
+ Dir.expects(:entries).once.with(folder).returns(entries)
58
+ File.expects(:directory?).at_least_once.with(folder).returns(true)
59
+
60
+ # we expect this list of entries to be deleted
61
+ dates_to_delete =
62
+ (22..30).collect {|i| "1981-06-%02d" % i} +
63
+ (2..31).collect {|i| "1981-07-%02d" % i} +
64
+ (2..31).collect {|i| "1981-08-%02d" % i} +
65
+ (2..30).collect {|i| "1981-09-%02d" % i} +
66
+ (2..31).collect {|i| "1981-10-%02d" % i} +
67
+ (2..7).collect {|i| "1981-11-%02d" % i} +
68
+ (9..14).collect {|i| "1981-11-%02d" % i} +
69
+ (16..21).collect {|i| "1981-11-%02d" % i} +
70
+ (23..28).collect {|i| "1981-11-%02d" % i} +
71
+ ["1981-11-30"] +
72
+ (2..3).collect {|i| "1981-12-%02d" % i}
73
+
74
+ entries_to_delete = dates_to_delete.collect {|date| "aaa-#{date}.tar.gz"}
75
+
76
+ entries_to_delete += ["1980-03-20-foo.tar.bz2", "1980-03-20-bar.tar.bz2", "1980.03.21.2.bar.tar.bz2.3"]
77
+
78
+ entries_to_delete.each do |entry|
79
+ FileUtils.expects(:rm_rf).with(File.join(folder, entry)).once
80
+ end
81
+
82
+ backup_cleaner = BackupCleaner.clean_backups(folder, :days => 14, :weeks => 5)
83
+ end
84
+
85
+ should "not delete anything if :dry_run specified" do
86
+ folder = "/foo/bar"
87
+ entries = ["1980-03-03-bar.tar.bz2", "1980-03-20-bar.tar.bz2"]
88
+ Dir.expects(:entries).once.with(folder).returns(entries)
89
+ File.expects(:directory?).at_least_once.with(folder).returns(true)
90
+
91
+ # assert that nothing is deleted
92
+ FileUtils.expects(:rm_rf).never
93
+
94
+ backup_cleaner = BackupCleaner.clean_backups(folder, :days => 14, :weeks => 5, :dry_run => true)
95
+ end
96
+
97
+ should "return correct week number if week_ordering_number is called" do
98
+ # make private method public for testing
99
+ BackupCleaner.class_eval "public_class_method :week_ordering_number"
100
+
101
+ assert BackupCleaner.week_ordering_number(Date.new(2010, 12, 26)) == 201052
102
+ assert BackupCleaner.week_ordering_number(Date.new(2011, 01, 01)) == 201052
103
+ 2.upto(8) {|i| assert BackupCleaner.week_ordering_number(Date.new(2011, 1, i)) == 201101}
104
+ assert BackupCleaner.week_ordering_number(Date.new(2011, 1, 9)) == 201102
105
+
106
+ BackupCleaner.class_eval "private_class_method :week_ordering_number"
107
+ end
108
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: backup_cleaner
3
+ version: !ruby/object:Gem::Version
4
+ hash: -1876988176
5
+ prerelease: true
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ - pre1
11
+ version: 1.0.0.pre1
12
+ platform: ruby
13
+ authors:
14
+ - Brian Alexander
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-10-09 00:00:00 -07:00
20
+ default_executable: backup_cleaner
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: activesupport
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 7
31
+ segments:
32
+ - 3
33
+ - 0
34
+ - 0
35
+ version: 3.0.0
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: trollop
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 3
47
+ segments:
48
+ - 0
49
+ version: "0"
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: thoughtbot-shoulda
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :development
65
+ version_requirements: *id003
66
+ description: A command-line script for cleaning up old backups
67
+ email: balexand@gmail.com
68
+ executables:
69
+ - backup_cleaner
70
+ extensions: []
71
+
72
+ extra_rdoc_files:
73
+ - LICENSE
74
+ - README.rdoc
75
+ files:
76
+ - .document
77
+ - .gitignore
78
+ - LICENSE
79
+ - README.rdoc
80
+ - Rakefile
81
+ - VERSION
82
+ - backup_cleaner.gemspec
83
+ - bin/backup_cleaner
84
+ - lib/backup_cleaner.rb
85
+ - lib/backup_cleaner/cli.rb
86
+ - test/helper.rb
87
+ - test/test_backup_cleaner.rb
88
+ has_rdoc: true
89
+ homepage: http://github.com/balexand/backup_cleaner
90
+ licenses: []
91
+
92
+ post_install_message:
93
+ rdoc_options:
94
+ - --charset=UTF-8
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 3
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ">"
110
+ - !ruby/object:Gem::Version
111
+ hash: 25
112
+ segments:
113
+ - 1
114
+ - 3
115
+ - 1
116
+ version: 1.3.1
117
+ requirements: []
118
+
119
+ rubyforge_project:
120
+ rubygems_version: 1.3.7
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: A command-line script for cleaning up old backups
124
+ test_files:
125
+ - test/helper.rb
126
+ - test/test_backup_cleaner.rb