clean_files 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 37dff8ca32b3bb5589f6da10f5f432b6ee451915
4
+ data.tar.gz: 440c67a524f989b7fda79b8f476eac4e4351cf74
5
+ SHA512:
6
+ metadata.gz: e50503dc029b2af56a3b94d713b767c41859b42176e91c97ba2e5a924e60a513a1dbfc5e54395e2bcb3be8733f84770b40d082623cf6f76b6239fa172cbe5fcb
7
+ data.tar.gz: 7a85cdae3fdbd4a1c5d135a8b5cb1932726524fb4479c23a43ec5ce31d0df3bd89fc370dbf2d84314c64055f1eca8a1078c251e15635158c1d39992ccc50a5ae
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,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ clean_files (1.0.1)
5
+ activesupport
6
+ rake
7
+ rdoc
8
+ rspec
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activesupport (3.2.12)
14
+ i18n (~> 0.6)
15
+ multi_json (~> 1.0)
16
+ diff-lcs (1.2.1)
17
+ i18n (0.6.4)
18
+ multi_json (1.6.1)
19
+ rake (10.0.3)
20
+ rdoc (4.0.0)
21
+ rspec (2.13.0)
22
+ rspec-core (~> 2.13.0)
23
+ rspec-expectations (~> 2.13.0)
24
+ rspec-mocks (~> 2.13.0)
25
+ rspec-core (2.13.0)
26
+ rspec-expectations (2.13.0)
27
+ diff-lcs (>= 1.1.3, < 2.0)
28
+ rspec-mocks (2.13.0)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ clean_files!
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 AlphaSights Ltd
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,46 @@
1
+ == Synopsis
2
+
3
+ Executable to delete files fitting certain criteria.
4
+
5
+ The intended purpose is to delete backups older than a certain date,
6
+ whilst keeping hourly, daily or weekly and so on backups.
7
+
8
+ Specifying an option like --hourly will keep the first file of the hour
9
+ and delete the rest of the files created within the same hour,
10
+ provided that the those files were created before the threshold date.
11
+
12
+ The executable does not itself create any backups, it is only
13
+ intended for cleaning up existing ones.
14
+
15
+ == Usage
16
+
17
+ clean_files file_paths [options]
18
+
19
+ For help use: clean_files -h
20
+ == Options
21
+
22
+ -v, --verbose Print name of files deleted
23
+ -p, --pretend Implies -v, only prints what files would have been deleted
24
+ -r, --recursive Delete directories as well as files
25
+ -t, --threshold Time ago in days for when to start deleting files
26
+ File newer than this date are never deleted.
27
+ The default is 30 days.
28
+
29
+ For example:
30
+
31
+ -t 10 or --threshold=30
32
+
33
+ -H, --hourly Keep hourly files
34
+ -D, --daily Keep daily files
35
+ -W, --weekly Keep weekly files
36
+ -M, --monthly Keep monthly files
37
+ -Y, --yearly Keep yearly files
38
+
39
+ == Examples
40
+
41
+ clean_files /backups/sql/*.sql --threshold 60 --daily
42
+ clean_files /Users/me/Downloads/* --pretend --verbose --recursive -t 10
43
+
44
+ == Copyright
45
+
46
+ Copyright (c) 2009 AlphaSights Ltd. See LICENSE for details.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 0
4
+ :patch: 1
data/bin/clean_files ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '/../lib'))
3
+ require 'clean_files'
4
+
5
+ app = CleanFiles.new(ARGV)
6
+ app.run
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "clean_files"
5
+ s.version = "1.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Tor Erik Linnerud"]
9
+ s.date = %q{2009-05-05}
10
+ s.default_executable = %q{clean_files}
11
+ s.email = %q{torerik.linnerud@alphasights.com}
12
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
14
+ s.files = `git ls-files`.split($/)
15
+ s.has_rdoc = true
16
+ s.homepage = %q{http://github.com/alphasights/clean-files}
17
+ s.require_paths = ["lib"]
18
+ s.summary = %q{Executable to delete files which fit certain criteria}
19
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
20
+
21
+ s.add_dependency 'rake'
22
+ s.add_dependency 'rspec'
23
+ s.add_dependency 'rdoc'
24
+ s.add_dependency 'activesupport'
25
+
26
+ s.rdoc_options = ["--charset=UTF-8"]
27
+ s.extra_rdoc_files = [
28
+ "LICENSE",
29
+ "README.rdoc"
30
+ ]
31
+ end
@@ -0,0 +1,91 @@
1
+ require 'optparse'
2
+ require 'active_support'
3
+ require 'fileutils'
4
+
5
+ require 'cleaner'
6
+
7
+ class CleanFiles
8
+ VERSION = '0.1.0'
9
+
10
+ attr_reader :options
11
+
12
+ def initialize(arguments)
13
+ @arguments = arguments
14
+
15
+ # Set defaults
16
+ @options = Hash.new
17
+ @options[:threshold] = 1.month.ago
18
+ end
19
+
20
+ # Parse options, check arguments, then process the command
21
+ def run
22
+
23
+ if parsed_options? && arguments_valid?
24
+ Cleaner.new(@options.delete(:paths), @options).start
25
+ else
26
+ output_usage
27
+ end
28
+
29
+ end
30
+
31
+ protected
32
+
33
+ def parsed_options?
34
+
35
+ # Specify options
36
+ opts = OptionParser.new
37
+ opts.on('-h', '--help') { output_help }
38
+ opts.on('-v', '--verbose') { @options[:verbose] = true }
39
+ opts.on('-d', '--pretend') { @options[:pretend] = true
40
+ @options[:verbose] = true }
41
+ opts.on('-r', '--recursive') {@options[:recursive] = true }
42
+
43
+ opts.on('-t', '--treshold [DAYS]') do |days|
44
+ raise OptionParser::InvalidOption unless days.to_i > 0 || days == "0"
45
+ @options[:threshold] = days.to_i.send(:days).ago
46
+ end
47
+
48
+ Cleaner::VALID_INTERVALS.each do |interval|
49
+ opts.on("-#{interval.to_s.first.upcase}", "--#{interval}") {@options[interval] = true}
50
+ end
51
+ return false unless opts.parse!(@arguments)
52
+
53
+ @options[:paths] = @arguments.dup
54
+
55
+ true
56
+ end
57
+
58
+ def arguments_valid?
59
+ @options[:paths].present?
60
+ end
61
+
62
+ def output_help
63
+ rdoc
64
+ exit -1
65
+ end
66
+
67
+ def output_usage
68
+ rdoc('Usage', 'Options')
69
+ exit -1
70
+ end
71
+
72
+ def rdoc(*sections)
73
+ readme_path = File.join(File.dirname(__FILE__), '/../README.rdoc')
74
+ comment = File.read(readme_path)
75
+
76
+ markup = SM::SimpleMarkup.new
77
+ flow_convertor = SM::ToFlow.new
78
+ flow = markup.convert(comment, flow_convertor)
79
+ format = "plain"
80
+
81
+ unless sections.empty?
82
+ flow = RDoc.extract_sections(flow, sections)
83
+ end
84
+
85
+ options = RI::Options.instance
86
+
87
+ formatter = options.formatter.new(options, "")
88
+ formatter.display_flow(flow)
89
+ end
90
+
91
+ end
data/lib/cleaner.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'active_support/core_ext'
2
+ class Cleaner
3
+ attr_reader :paths, :options
4
+
5
+ VALID_OPTIONS = [:threshold, :pretend, :verbose, :recursive]
6
+ VALID_INTERVALS = [:hourly, :daily, :weekly, :monthly, :yearly]
7
+ TIME_INTERVALS = [:hour, :day, :cweek, :month, :year]
8
+
9
+ SimpleFile = Struct.new(:path, :stat) do
10
+ delegate :ctime, :file?, :directory?, :to => :stat
11
+ end
12
+
13
+ def initialize(paths, options = {})
14
+ @paths = [paths].flatten
15
+ @options = options.reverse_merge(:threshold => 30.days.ago)
16
+ options.assert_valid_keys(VALID_OPTIONS + VALID_INTERVALS)
17
+ given_intervals = VALID_INTERVALS & options.keys
18
+ if given_intervals.present?
19
+ lowest_selected_position = VALID_INTERVALS.index(given_intervals.first)
20
+ @key_intervals = TIME_INTERVALS[lowest_selected_position..-1]
21
+ end
22
+ end
23
+
24
+ def start
25
+ files = files_to_delete.map(&:path)
26
+ puts files.join("\n") if options[:verbose]
27
+ FileUtils.rm_rf(files, :noop => !!options[:pretend])
28
+ end
29
+
30
+ def files
31
+ if paths.any?{|path| path.include?('*')}
32
+ paths.map!{|path| Dir.glob(path)}
33
+ paths.flatten!
34
+ end
35
+ @_files ||= paths.map do |file_path|
36
+ begin
37
+ SimpleFile.new(file_path, File.stat(file_path))
38
+ rescue Errno::EOPNOTSUPP
39
+ nil
40
+ rescue Errno::ENOENT => e
41
+ puts e.to_s
42
+ exit -2
43
+ end
44
+ end.compact.select do |file|
45
+ file.file? || (options[:recursive] && file.directory?)
46
+ end.sort_by(&:ctime)
47
+ end
48
+
49
+ def files_to_delete
50
+ files_before_threshold - files_to_preserve
51
+ end
52
+
53
+ def files_before_threshold
54
+ files.select{|file| file.ctime < options[:threshold]}
55
+ end
56
+
57
+ def files_to_preserve
58
+ return [] if @key_intervals.nil?
59
+ files_before_threshold.group_by do |file|
60
+ cdate = file.ctime.to_datetime
61
+ @key_intervals.map{|interval| cdate.send(interval)}
62
+ end.map do |_, files|
63
+ files.first
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,117 @@
1
+ require File.join(File.dirname(__FILE__), '/spec_helper')
2
+
3
+ describe Cleaner do
4
+
5
+ it 'find files older than threshold' do
6
+ cleaner = Cleaner.new('/path', :threshold => 5.days.ago)
7
+ cleaner.should_receive(:files).and_return(mock_files(7.days.ago, 4.days.ago))
8
+ cleaner.files_before_threshold.should == mock_files(7.days.ago)
9
+ end
10
+
11
+ it 'deletes files' do
12
+ cleaner = Cleaner.new('/path')
13
+ files = mock_files(3.days.ago, 2.days.ago, 7.days.ago)
14
+ cleaner.should_receive(:files_to_delete).and_return(files)
15
+ FileUtils.should_receive(:rm_rf).with(files.map(&:path), :noop => false)
16
+ cleaner.start
17
+ end
18
+
19
+ it 'does not delete any files if specifying pretend => true' do
20
+ cleaner = Cleaner.new('/path', :pretend => true)
21
+ files = mock_files(3.days.ago, 2.days.ago, 7.days.ago)
22
+ cleaner.should_receive(:files_to_delete).and_return(files)
23
+ FileUtils.should_receive(:rm_rf).with(files.map(&:path), :noop => true)
24
+ cleaner.start
25
+ end
26
+
27
+ it 'has has files to delete' do
28
+ cleaner = Cleaner.new('/path')
29
+ cleaner.should_receive(:files_before_threshold).and_return(mock_files('12:00', '13:00'))
30
+ cleaner.should_receive(:files_to_preserve).and_return(mock_files('13:00'))
31
+ cleaner.files_to_delete.should == mock_files('12:00')
32
+ end
33
+
34
+ it 'prints name of file to be deleted if specifying verbose => true' do
35
+ cleaner = Cleaner.new('/path', :verbose => true)
36
+ files = mock_files('00:50', '13:30')
37
+ cleaner.should_receive(:files_to_delete).and_return(files)
38
+ FileUtils.should_receive(:rm_rf).with(files.map(&:path), :noop => false)
39
+ cleaner.should_receive(:puts).with("01 May 00:50\n01 May 13:30")
40
+ cleaner.start
41
+ end
42
+
43
+ it 'preserves the first file of the hour' do
44
+ should_preserve(:hourly,
45
+ '12:05' => true,
46
+ '12:10' => false,
47
+ '13:15' => true,
48
+ '13:16' => false)
49
+ end
50
+
51
+ it 'preserves the first file of the day' do
52
+ should_preserve(:daily,
53
+ '01 10:00' => true,
54
+ '01 12:00' => false,
55
+ '02 00:00' => true,
56
+ '02 00:01' => false)
57
+ end
58
+
59
+ it 'preserves the first file of the week' do
60
+ should_preserve(:weekly,
61
+ 'Sun Jan 02 03:40 00' => true,
62
+ 'Tue Jan 04 03:40 00' => true,
63
+ 'Wed Jan 05 03:40 00' => false)
64
+ end
65
+
66
+ it 'preserves the first file of the month' do
67
+ should_preserve(:monthly,
68
+ 'Sun Jan 02 12:00 00' => true,
69
+ 'Tue Feb 01 12:00 00' => true,
70
+ 'Wed Feb 02 12:00 00' => false)
71
+ end
72
+
73
+ it 'preserves the first file of the year' do
74
+ should_preserve(:yearly,
75
+ 'Sun Jan 02 03:40 01' => true,
76
+ 'Tue Jan 04 03:40 02' => true,
77
+ 'Wed Jan 05 03:40 02' => false)
78
+ end
79
+
80
+ MockFile = Struct.new(:ctime) do
81
+ def ==(other)
82
+ (ctime.to_i - other.ctime.to_i) < 2
83
+ end
84
+
85
+ def inspect
86
+ ctime.to_s(:short)
87
+ end
88
+
89
+ def stat
90
+ stat = Object.new
91
+ def stat.file?
92
+ true
93
+ end
94
+ stat
95
+ end
96
+
97
+ alias path inspect
98
+ end
99
+
100
+ def mock_files(*ctimes)
101
+ ctimes.map do |ctime|
102
+ if ctime.is_a?(String)
103
+ ctime = "1 May #{ctime}" unless ctime =~ /\d\d /
104
+ ctime = "May #{ctime} 2009" unless ctime =~ /\d{2}/
105
+ ctime = Time.parse(ctime)
106
+ end
107
+ MockFile.new(ctime)
108
+ end
109
+ end
110
+
111
+ def should_preserve(interval, files)
112
+ cleaner = Cleaner.new('/path', interval => true)
113
+ cleaner.should_receive(:files_before_threshold).and_return(mock_files(*files.keys.sort))
114
+ expected_files = files.select{|_, select| select == true}.map{|file, _| file}
115
+ cleaner.files_to_preserve.to_set.should == mock_files(*expected_files).to_set
116
+ end
117
+ end
@@ -0,0 +1,10 @@
1
+ require 'rspec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+
6
+ require 'clean_files'
7
+
8
+ RSpec.configure do |config|
9
+
10
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clean_files
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tor Erik Linnerud
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2009-05-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rdoc
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email: torerik.linnerud@alphasights.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files:
74
+ - LICENSE
75
+ - README.rdoc
76
+ files:
77
+ - .document
78
+ - .gitignore
79
+ - Gemfile
80
+ - Gemfile.lock
81
+ - LICENSE
82
+ - README.rdoc
83
+ - VERSION.yml
84
+ - bin/clean_files
85
+ - clean_files.gemspec
86
+ - lib/clean_files.rb
87
+ - lib/cleaner.rb
88
+ - spec/cleaner_spec.rb
89
+ - spec/spec_helper.rb
90
+ homepage: http://github.com/alphasights/clean-files
91
+ licenses: []
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options:
95
+ - --charset=UTF-8
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.0.0
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Executable to delete files which fit certain criteria
114
+ test_files:
115
+ - spec/cleaner_spec.rb
116
+ - spec/spec_helper.rb