geoffreywiseman-prune 1.1.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.mdown +24 -0
 - data/Rakefile +45 -0
 - data/UNLICENSE +24 -0
 - data/bin/prune +7 -0
 - data/lib/prune.rb +9 -0
 - data/lib/prune/archiver.rb +61 -0
 - data/lib/prune/category.rb +30 -0
 - data/lib/prune/cli.rb +50 -0
 - data/lib/prune/default_retention.rb +36 -0
 - data/lib/prune/grouper.rb +27 -0
 - data/lib/prune/pruner.rb +120 -0
 - data/lib/prune/retention.rb +119 -0
 - data/spec/archiver_spec.rb +103 -0
 - data/spec/cli_spec.rb +205 -0
 - data/spec/grouper_spec.rb +63 -0
 - data/spec/pruner_spec.rb +206 -0
 - data/spec/retention_spec.rb +147 -0
 - data/spec/spec_helper.rb +5 -0
 - metadata +82 -0
 
    
        data/README.mdown
    ADDED
    
    | 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Prune:  Deleting and archiving files by date
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            We have some nightly processes that archive information that we'd like to retain, either for reference or
         
     | 
| 
      
 4 
     | 
    
         
            +
            for possible restoration.  In order to keep space usage somewhat reasonable, we'd like to prune some of
         
     | 
| 
      
 5 
     | 
    
         
            +
            those files as they get older.  Prune satisfies that need.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Prune is written as a Ruby library with an optional command-line interface and a wrapping shell script.
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            ## Usage
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            Although prune has a command-line interface, at the moment the retention policy which determines what to
         
     | 
| 
      
 12 
     | 
    
         
            +
            do with each of the files (retain, remove, archive) and how to take action (archiving files by month in
         
     | 
| 
      
 13 
     | 
    
         
            +
            a .tar.gz) is basically hard-coded for our current needs, although it's easy to change.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            To invoke prune:
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            	bin/prune <folder>
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            To get help on the various command-line options:
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            	bin/prune --help
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            In the long run, I expect this will be packaged as a gem and have the retention policy externalized
         
     | 
| 
      
 24 
     | 
    
         
            +
            where it can be configured for your own needs.
         
     | 
    
        data/Rakefile
    ADDED
    
    | 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rake/clean'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rake/packagetask'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rspec/core/rake_task'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'rspec'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'rubygems/package_task'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            CLEAN.include( 'coverage', 'pkg' )
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            desc '"spec" (run RSpec)'
         
     | 
| 
      
 11 
     | 
    
         
            +
            task :default => :spec
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            desc "Run RSpec on spec/*"
         
     | 
| 
      
 14 
     | 
    
         
            +
            RSpec::Core::RakeTask.new
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            desc "Generate code coverage"
         
     | 
| 
      
 17 
     | 
    
         
            +
            RSpec::Core::RakeTask.new(:coverage) do |t|
         
     | 
| 
      
 18 
     | 
    
         
            +
              t.rcov = true
         
     | 
| 
      
 19 
     | 
    
         
            +
              t.rcov_opts = ['--exclude', 'spec,/gems/,/rubygems/', '--text-report']
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            spec = Gem::Specification.new do |spec|
         
     | 
| 
      
 23 
     | 
    
         
            +
              spec.name = 'geoffreywiseman-prune'
         
     | 
| 
      
 24 
     | 
    
         
            +
              spec.version = '1.1.0'
         
     | 
| 
      
 25 
     | 
    
         
            +
              spec.date = '2011-09-09'
         
     | 
| 
      
 26 
     | 
    
         
            +
              spec.summary = 'Prunes files from a folder based on a retention policy, often time-based.'
         
     | 
| 
      
 27 
     | 
    
         
            +
              spec.description = 'Prune is meant to analyze a folder full of files, run them against a retention policy and decide which to keep, which to remove and which to archive. It is extensible and embeddable.'
         
     | 
| 
      
 28 
     | 
    
         
            +
              spec.author = 'Geoffrey Wiseman'
         
     | 
| 
      
 29 
     | 
    
         
            +
              spec.email = 'geoffrey.wiseman@codiform.com'
         
     | 
| 
      
 30 
     | 
    
         
            +
              spec.homepage = 'http://geoffreywiseman.github.com/prune'
         
     | 
| 
      
 31 
     | 
    
         
            +
              spec.executables << 'prune'
         
     | 
| 
      
 32 
     | 
    
         
            +
              
         
     | 
| 
      
 33 
     | 
    
         
            +
              spec.files = Dir['{lib,spec}/**/*.rb', 'bin/*', 'Rakefile', 'README.mdown', 'UNLICENSE']
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            Gem::PackageTask.new( spec ) do |pkg|
         
     | 
| 
      
 37 
     | 
    
         
            +
              pkg.need_tar_gz = true
         
     | 
| 
      
 38 
     | 
    
         
            +
              pkg.need_zip = true
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            # Rake::PackageTask.new( "prune", "1.1.0" ) do |p|
         
     | 
| 
      
 42 
     | 
    
         
            +
            #   p.need_tar_gz = true
         
     | 
| 
      
 43 
     | 
    
         
            +
            #   p.need_zip = true
         
     | 
| 
      
 44 
     | 
    
         
            +
            #   p.package_files.include( '{bin,lib,spec}/**/*', 'Rakefile', 'README.mdown', 'UNLICENSE' )
         
     | 
| 
      
 45 
     | 
    
         
            +
            # end
         
     | 
    
        data/UNLICENSE
    ADDED
    
    | 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            This is free and unencumbered software released into the public domain.
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Anyone is free to copy, modify, publish, use, compile, sell, or
         
     | 
| 
      
 4 
     | 
    
         
            +
            distribute this software, either in source code form or as a compiled
         
     | 
| 
      
 5 
     | 
    
         
            +
            binary, for any purpose, commercial or non-commercial, and by any
         
     | 
| 
      
 6 
     | 
    
         
            +
            means.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            In jurisdictions that recognize copyright laws, the author or authors
         
     | 
| 
      
 9 
     | 
    
         
            +
            of this software dedicate any and all copyright interest in the
         
     | 
| 
      
 10 
     | 
    
         
            +
            software to the public domain. We make this dedication for the benefit
         
     | 
| 
      
 11 
     | 
    
         
            +
            of the public at large and to the detriment of our heirs and
         
     | 
| 
      
 12 
     | 
    
         
            +
            successors. We intend this dedication to be an overt act of
         
     | 
| 
      
 13 
     | 
    
         
            +
            relinquishment in perpetuity of all present and future rights to this
         
     | 
| 
      
 14 
     | 
    
         
            +
            software under copyright law.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 17 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 18 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
         
     | 
| 
      
 19 
     | 
    
         
            +
            IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
         
     | 
| 
      
 20 
     | 
    
         
            +
            OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
         
     | 
| 
      
 21 
     | 
    
         
            +
            ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
         
     | 
| 
      
 22 
     | 
    
         
            +
            OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            For more information, please refer to <http://unlicense.org/>
         
     | 
    
        data/bin/prune
    ADDED
    
    
    
        data/lib/prune.rb
    ADDED
    
    
| 
         @@ -0,0 +1,61 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'zlib'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'archive/tar/minitar'
         
     | 
| 
      
 7 
     | 
    
         
            +
            include Archive::Tar
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            module Prune
         
     | 
| 
      
 10 
     | 
    
         
            +
              class Archiver
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :destination
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize( destination, source, verbose )
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @source = source
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @verbose = verbose
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @destination = destination || get_default_dir
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def get_default_dir
         
     | 
| 
      
 20 
     | 
    
         
            +
                  absolute = File.expand_path @source
         
     | 
| 
      
 21 
     | 
    
         
            +
                  path = File.dirname absolute
         
     | 
| 
      
 22 
     | 
    
         
            +
                  name = File.basename absolute
         
     | 
| 
      
 23 
     | 
    
         
            +
                  File.join( path, "#{name}-archives" )
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def make_destination_dir
         
     | 
| 
      
 27 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 28 
     | 
    
         
            +
                    Dir.mkdir @destination unless File.exists? @destination
         
     | 
| 
      
 29 
     | 
    
         
            +
                  rescue SystemCallError
         
     | 
| 
      
 30 
     | 
    
         
            +
                    raise IOError, "Archive folder #{@destination} does not exist and cannot be created."
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def archive( group_name, files )
         
     | 
| 
      
 35 
     | 
    
         
            +
                  make_destination_dir
         
     | 
| 
      
 36 
     | 
    
         
            +
                  archive_path = File.join( @destination, "archive-#{group_name}.tar.gz")
         
     | 
| 
      
 37 
     | 
    
         
            +
                  paths = files.map { |file| File.join( @source, file ) }
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  if File.exists?( archive_path ) then
         
     | 
| 
      
 40 
     | 
    
         
            +
                    puts "Archive file #{archive_path} exists." if @verbose
         
     | 
| 
      
 41 
     | 
    
         
            +
                    Dir.mktmpdir do |tmp_dir|
         
     | 
| 
      
 42 
     | 
    
         
            +
                      puts "Created temporary directory #{tmp_dir} to extract contents of existing archive file." if @verbose
         
     | 
| 
      
 43 
     | 
    
         
            +
                      tgz = Zlib::GzipReader.new( File.open( archive_path, 'rb' ) )
         
     | 
| 
      
 44 
     | 
    
         
            +
                      Minitar.unpack( tgz, tmp_dir )
         
     | 
| 
      
 45 
     | 
    
         
            +
                      extracted_paths = Dir.entries( tmp_dir ).map { |tmpfile| File.join( tmp_dir, tmpfile ) }.reject { |path| File.directory? path }
         
     | 
| 
      
 46 
     | 
    
         
            +
                      combined_paths = extracted_paths + paths
         
     | 
| 
      
 47 
     | 
    
         
            +
                      tgz = Zlib::GzipWriter.new( File.open( archive_path, 'wb' ) )
         
     | 
| 
      
 48 
     | 
    
         
            +
                      Minitar.pack( combined_paths, tgz )
         
     | 
| 
      
 49 
     | 
    
         
            +
                      puts "Added #{files.size} file(s) to #{archive_path} archive already containing #{extracted_paths.size} file(s)." if @verbose
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
                  else
         
     | 
| 
      
 52 
     | 
    
         
            +
                    tgz = Zlib::GzipWriter.new( File.open( archive_path, 'wb' ) )
         
     | 
| 
      
 53 
     | 
    
         
            +
                    Minitar.pack( paths, tgz )
         
     | 
| 
      
 54 
     | 
    
         
            +
                    puts "Compressed #{files.size} file(s) into #{archive_path} archive." if @verbose
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  File.delete( *paths )
         
     | 
| 
      
 58 
     | 
    
         
            +
                  puts "Removing #{files.size} source files that have been archived." if @verbose
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Category
         
     | 
| 
      
 2 
     | 
    
         
            +
              attr_accessor :action, :description
         
     | 
| 
      
 3 
     | 
    
         
            +
              
         
     | 
| 
      
 4 
     | 
    
         
            +
              def initialize( description, action, quiet = false, predicate = Proc.new { |x| true } )
         
     | 
| 
      
 5 
     | 
    
         
            +
                @description = description
         
     | 
| 
      
 6 
     | 
    
         
            +
                @action = action
         
     | 
| 
      
 7 
     | 
    
         
            +
                @predicate = predicate
         
     | 
| 
      
 8 
     | 
    
         
            +
                @quiet = quiet
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
              
         
     | 
| 
      
 11 
     | 
    
         
            +
              def requires_prompt?
         
     | 
| 
      
 12 
     | 
    
         
            +
                case @action
         
     | 
| 
      
 13 
     | 
    
         
            +
                when :remove
         
     | 
| 
      
 14 
     | 
    
         
            +
                  true
         
     | 
| 
      
 15 
     | 
    
         
            +
                when :archive
         
     | 
| 
      
 16 
     | 
    
         
            +
                  true
         
     | 
| 
      
 17 
     | 
    
         
            +
                else
         
     | 
| 
      
 18 
     | 
    
         
            +
                  false
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
              
         
     | 
| 
      
 22 
     | 
    
         
            +
              def includes?( filename )
         
     | 
| 
      
 23 
     | 
    
         
            +
                @predicate.call filename
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
              
         
     | 
| 
      
 26 
     | 
    
         
            +
              def quiet?
         
     | 
| 
      
 27 
     | 
    
         
            +
                @quiet
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
              
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/prune/cli.rb
    ADDED
    
    | 
         @@ -0,0 +1,50 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'zlib'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'archive/tar/minitar'
         
     | 
| 
      
 7 
     | 
    
         
            +
            include Archive::Tar
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            module Prune
         
     | 
| 
      
 10 
     | 
    
         
            +
              VERSION = [1,0,0]
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              class CommandLineInterface
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                DEFAULT_OPTIONS = { :verbose => false, :did_work => false, :dry_run => false, :prompt => true, :archive => true }
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def self.parse_and_run
         
     | 
| 
      
 17 
     | 
    
         
            +
                  options = DEFAULT_OPTIONS.dup
         
     | 
| 
      
 18 
     | 
    
         
            +
                  parser = OptionParser.new do |opts|
         
     | 
| 
      
 19 
     | 
    
         
            +
                    opts.banner = "Usage: prune [options] folder"
         
     | 
| 
      
 20 
     | 
    
         
            +
                    opts.on( "-v", "--verbose", "Prints much more frequently during execution about what it's doing." ) { options[:verbose] = true }
         
     | 
| 
      
 21 
     | 
    
         
            +
                    opts.on( "-d", "--dry-run", "Categorizes files, but does not take any actions on them." ) { options[:dry_run] = true }
         
     | 
| 
      
 22 
     | 
    
         
            +
                    opts.on( "-f", "--force", "--no-prompt", "Will take action without asking permissions; useful for automation." ) { options[:prompt] = false }
         
     | 
| 
      
 23 
     | 
    
         
            +
                    opts.on( "-a", "--archive-folder FOLDER", "The folder in which archives should be stored; defaults to <folder>/../<folder-name>-archives." ) { |path| options[:archive_path] = path }
         
     | 
| 
      
 24 
     | 
    
         
            +
                    opts.on( "--no-archive", "Don't perform archival; typically if the files you're pruning are already compressed." ) { options[:archive] = false }
         
     | 
| 
      
 25 
     | 
    
         
            +
                    opts.on_tail( "--version", "Displays version information." ) do
         
     | 
| 
      
 26 
     | 
    
         
            +
                      options[:did_work] = true
         
     | 
| 
      
 27 
     | 
    
         
            +
                      print "Prune #{VERSION.join('.')}, by Geoffrey Wiseman."
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
                    opts.on_tail( "-?", "--help", "Shows quick help about using prune." ) do
         
     | 
| 
      
 30 
     | 
    
         
            +
                      options[:did_work] = true
         
     | 
| 
      
 31 
     | 
    
         
            +
                      puts opts
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 36 
     | 
    
         
            +
                    parser.parse!
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    if ARGV.size != 1 then
         
     | 
| 
      
 39 
     | 
    
         
            +
                      print parser.help unless options[:did_work]
         
     | 
| 
      
 40 
     | 
    
         
            +
                    else
         
     | 
| 
      
 41 
     | 
    
         
            +
                      Pruner.new( options ).prune( ARGV.first )
         
     | 
| 
      
 42 
     | 
    
         
            +
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  rescue OptionParser::ParseError
         
     | 
| 
      
 44 
     | 
    
         
            +
                    $stderr.print "Error: " + $! + "\n"
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            preprocess do |file|
         
     | 
| 
      
 2 
     | 
    
         
            +
              file.modified_time = File.mtime( file.name )
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              modified_date = Date.parse modified_time.to_s
         
     | 
| 
      
 5 
     | 
    
         
            +
              file.days_since_modified = Date.today - modified_date
         
     | 
| 
      
 6 
     | 
    
         
            +
              file.months_since_modified = ( Date.today.year - modified_date.year ) * 12 + (Date.today.month - modified_date.month)
         
     | 
| 
      
 7 
     | 
    
         
            +
            end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            category "Ignoring directories" do
         
     | 
| 
      
 10 
     | 
    
         
            +
              match { |file| File.directory?(file.name) }
         
     | 
| 
      
 11 
     | 
    
         
            +
              ignore
         
     | 
| 
      
 12 
     | 
    
         
            +
              quiet
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            category "Retaining Files from the Last Two Weeks" do
         
     | 
| 
      
 16 
     | 
    
         
            +
              match do |file|
         
     | 
| 
      
 17 
     | 
    
         
            +
                file.days_since_modified <= 14
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
              retain
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            category "Retaining 'Friday' files Older than Two Weeks" do
         
     | 
| 
      
 23 
     | 
    
         
            +
              match { |file| file.modified_time.wday == 5 && file.months_since_modified < 2 && file.days_since_modified > 14 }
         
     | 
| 
      
 24 
     | 
    
         
            +
              retain
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            category "Removing 'Non-Friday' files Older than Two Weeks" do
         
     | 
| 
      
 28 
     | 
    
         
            +
              match { |file| file.modified_time.wday != 5 && file.days_since_modified > 14 }
         
     | 
| 
      
 29 
     | 
    
         
            +
              remove 
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            category "Archiving Files Older than Two Months" do 
         
     | 
| 
      
 33 
     | 
    
         
            +
              match { |file| file.modified_time.wday == 5 && file.months_since_modified >= 2 }
         
     | 
| 
      
 34 
     | 
    
         
            +
              archive
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Prune
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Grouper
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                def initialize( archiver )
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @groups = Hash.new{ |h,k| h[k] = [] }
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @archiver = archiver
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def group( folder_name, files )
         
     | 
| 
      
 10 
     | 
    
         
            +
                  files.each do |file|
         
     | 
| 
      
 11 
     | 
    
         
            +
                    mtime = File.mtime( File.join( folder_name, file ) )
         
     | 
| 
      
 12 
     | 
    
         
            +
                    month_name = Date::ABBR_MONTHNAMES[ mtime.month ]
         
     | 
| 
      
 13 
     | 
    
         
            +
                    group_name = "#{month_name}-#{mtime.year}"
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @groups[ group_name ] << file
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  return self
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def archive
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @groups.each_pair do |month,files|
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @archiver.archive( month, files )
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  sizes = @groups.values.map { |x| x.size }.join( ', ' )
         
     | 
| 
      
 24 
     | 
    
         
            +
                  "#{@groups.size} archive(s) created (#{sizes} file(s), respectively)"
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/prune/pruner.rb
    ADDED
    
    | 
         @@ -0,0 +1,120 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'zlib'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'archive/tar/minitar'
         
     | 
| 
      
 7 
     | 
    
         
            +
            include Archive::Tar
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            module Prune
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              class Pruner
         
     | 
| 
      
 12 
     | 
    
         
            +
                attr_reader :categories
         
     | 
| 
      
 13 
     | 
    
         
            +
                attr_reader :options
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def initialize( options )
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @options = options
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @categories = Hash.new { |h,k| h[k] = [] } # initialize new keys with an empty array
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @analyzed_count = 0
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def prune( folder_name )
         
     | 
| 
      
 22 
     | 
    
         
            +
                  return print( "ERROR: Cannot find folder: #{folder_name}\n" ) unless File.exists? folder_name
         
     | 
| 
      
 23 
     | 
    
         
            +
                  return puts( "ERROR: #{folder_name} is not a folder" ) unless File.directory? folder_name
         
     | 
| 
      
 24 
     | 
    
         
            +
                  policy = RetentionPolicy.new folder_name
         
     | 
| 
      
 25 
     | 
    
         
            +
                  return puts( "ERROR: Retention policy contains no categories." ) if policy.categories.empty?
         
     | 
| 
      
 26 
     | 
    
         
            +
                  policy.categories.each { |cat| @categories[cat] = Array.new } # retain category order
         
     | 
| 
      
 27 
     | 
    
         
            +
                  analyze folder_name, policy
         
     | 
| 
      
 28 
     | 
    
         
            +
                  execute_prune( folder_name, policy ) unless @options[:dry_run]
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def analyze( folder_name, policy )
         
     | 
| 
      
 32 
     | 
    
         
            +
                  print "Analyzing '#{folder_name}':\n"
         
     | 
| 
      
 33 
     | 
    
         
            +
                  files = Dir.entries( folder_name ).sort_by { |f| test(?M, File.join( folder_name, f ) ) }
         
     | 
| 
      
 34 
     | 
    
         
            +
                  files.each do |file|
         
     | 
| 
      
 35 
     | 
    
         
            +
                    analyze_file( policy, file )
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  print "\n" if @options[:verbose]
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  display_categories policy
         
     | 
| 
      
 40 
     | 
    
         
            +
                  print "\t#{@analyzed_count} file(s) analyzed\n"
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def execute_prune( folder_name, policy )
         
     | 
| 
      
 44 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 45 
     | 
    
         
            +
                    if should_prompt?( policy ) && !prompt then
         
     | 
| 
      
 46 
     | 
    
         
            +
                      puts "Not proceeding; no actions taken."
         
     | 
| 
      
 47 
     | 
    
         
            +
                    else
         
     | 
| 
      
 48 
     | 
    
         
            +
                      take_all_actions( folder_name, policy )
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
                  rescue IOError
         
     | 
| 
      
 51 
     | 
    
         
            +
                    $stderr.print "ERROR: #{$!}\n"
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def should_prompt?( policy )
         
     | 
| 
      
 56 
     | 
    
         
            +
                  @options[:prompt] && actions_require_prompt( policy )
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def actions_require_prompt( policy )
         
     | 
| 
      
 60 
     | 
    
         
            +
                  @categories.keys.any? { |category| category.requires_prompt? }
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def prompt
         
     | 
| 
      
 64 
     | 
    
         
            +
                  print "Proceed? [y/N]: "
         
     | 
| 
      
 65 
     | 
    
         
            +
                  response = STDIN.gets.chomp.strip.downcase
         
     | 
| 
      
 66 
     | 
    
         
            +
                  ['y','yes','true'].include? response
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                def take_all_actions( folder_name, policy )
         
     | 
| 
      
 70 
     | 
    
         
            +
                  actions = 0
         
     | 
| 
      
 71 
     | 
    
         
            +
                  @categories.each_pair do |category,files|
         
     | 
| 
      
 72 
     | 
    
         
            +
                    action = category.action
         
     | 
| 
      
 73 
     | 
    
         
            +
                    result = take_action( action, folder_name, files )
         
     | 
| 
      
 74 
     | 
    
         
            +
                    if !result.nil? then
         
     | 
| 
      
 75 
     | 
    
         
            +
                      puts result
         
     | 
| 
      
 76 
     | 
    
         
            +
                      actions += 1
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                  print "No actions necessary.\n" if actions == 0
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                def take_action( action, folder_name, files )
         
     | 
| 
      
 83 
     | 
    
         
            +
                  case action
         
     | 
| 
      
 84 
     | 
    
         
            +
                  when :remove
         
     | 
| 
      
 85 
     | 
    
         
            +
                    paths = files.map { |file| File.join folder_name, file }
         
     | 
| 
      
 86 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 87 
     | 
    
         
            +
                      File.delete *paths
         
     | 
| 
      
 88 
     | 
    
         
            +
                      "#{files.size} file(s) deleted"
         
     | 
| 
      
 89 
     | 
    
         
            +
                    rescue
         
     | 
| 
      
 90 
     | 
    
         
            +
                      raise IOError, "Could not remove file(s): #{$!}"
         
     | 
| 
      
 91 
     | 
    
         
            +
                    end
         
     | 
| 
      
 92 
     | 
    
         
            +
                  when :archive
         
     | 
| 
      
 93 
     | 
    
         
            +
                    if @options[:archive] then
         
     | 
| 
      
 94 
     | 
    
         
            +
                      archiver = Archiver.new( @options[:archive_path], folder_name, @options[:verbose] )
         
     | 
| 
      
 95 
     | 
    
         
            +
                      grouper = Grouper.new( archiver )
         
     | 
| 
      
 96 
     | 
    
         
            +
                      grouper.group( folder_name, files );
         
     | 
| 
      
 97 
     | 
    
         
            +
                      grouper.archive
         
     | 
| 
      
 98 
     | 
    
         
            +
                    else
         
     | 
| 
      
 99 
     | 
    
         
            +
                      "Archive option disabled. Archive(s) not created."
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                def display_categories( policy )
         
     | 
| 
      
 105 
     | 
    
         
            +
                  @categories.each_pair do |category,files|
         
     | 
| 
      
 106 
     | 
    
         
            +
                    if !category.quiet? || @options[:verbose] then 
         
     | 
| 
      
 107 
     | 
    
         
            +
                      print "\t#{category.description}:\n\t\t"
         
     | 
| 
      
 108 
     | 
    
         
            +
                      puts files.join( "\n\t\t")
         
     | 
| 
      
 109 
     | 
    
         
            +
                    end
         
     | 
| 
      
 110 
     | 
    
         
            +
                  end
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                def analyze_file( policy, file )
         
     | 
| 
      
 114 
     | 
    
         
            +
                  category = policy.categorize( file )
         
     | 
| 
      
 115 
     | 
    
         
            +
                  @categories[ category ] << file unless category.nil?
         
     | 
| 
      
 116 
     | 
    
         
            +
                  @analyzed_count += 1
         
     | 
| 
      
 117 
     | 
    
         
            +
                end
         
     | 
| 
      
 118 
     | 
    
         
            +
              end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,119 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'pathname'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Prune
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              class RetentionPolicy
         
     | 
| 
      
 9 
     | 
    
         
            +
                
         
     | 
| 
      
 10 
     | 
    
         
            +
                attr_accessor :categories
         
     | 
| 
      
 11 
     | 
    
         
            +
                
         
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize( folder_name )
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @folder_name = folder_name
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @today = Date.today
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @categories = Array.new
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @default_category = Category.new "Unmatched Files", :retain, true
         
     | 
| 
      
 17 
     | 
    
         
            +
                  instance_eval *get_retention_dsl( folder_name )
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
                
         
     | 
| 
      
 20 
     | 
    
         
            +
                def categorize( file_name )
         
     | 
| 
      
 21 
     | 
    
         
            +
                  file_context = FileContext.new( @folder_name, file_name, @preprocessor )
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @categories.find { |cat| cat.includes? file_context } || @default_category
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
                
         
     | 
| 
      
 25 
     | 
    
         
            +
                def get_retention_dsl( folder_name )
         
     | 
| 
      
 26 
     | 
    
         
            +
                  get_dsl( folder_name, '.prune' ) || get_dsl( File.dirname(__FILE__), 'default_retention.rb', 'core retention policy' )
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
                
         
     | 
| 
      
 29 
     | 
    
         
            +
                def get_dsl( dsl_folder, dsl_file, human_name=nil )
         
     | 
| 
      
 30 
     | 
    
         
            +
                  dsl = File.join( dsl_folder, dsl_file )
         
     | 
| 
      
 31 
     | 
    
         
            +
                  human_name = Pathname.new( dsl ).cleanpath.to_s if human_name.nil?
         
     | 
| 
      
 32 
     | 
    
         
            +
                  if File.exists?( dsl ) then
         
     | 
| 
      
 33 
     | 
    
         
            +
                    puts "Loading retention policy from: #{human_name}"
         
     | 
| 
      
 34 
     | 
    
         
            +
                    return File.read( dsl ), dsl_file
         
     | 
| 
      
 35 
     | 
    
         
            +
                  else
         
     | 
| 
      
 36 
     | 
    
         
            +
                    return nil
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
                
         
     | 
| 
      
 40 
     | 
    
         
            +
                def category( description, &block )
         
     | 
| 
      
 41 
     | 
    
         
            +
                  builder = CategoryBuilder.new( description )
         
     | 
| 
      
 42 
     | 
    
         
            +
                  builder.instance_eval &block
         
     | 
| 
      
 43 
     | 
    
         
            +
                  @categories << builder.build
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
                
         
     | 
| 
      
 46 
     | 
    
         
            +
                def preprocess( &block )
         
     | 
| 
      
 47 
     | 
    
         
            +
                  @preprocessor = Proc.new &block
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
                
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
              
         
     | 
| 
      
 52 
     | 
    
         
            +
              class CategoryBuilder
         
     | 
| 
      
 53 
     | 
    
         
            +
                
         
     | 
| 
      
 54 
     | 
    
         
            +
                def initialize( description )
         
     | 
| 
      
 55 
     | 
    
         
            +
                  @description = description
         
     | 
| 
      
 56 
     | 
    
         
            +
                  @quiet = false
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
                
         
     | 
| 
      
 59 
     | 
    
         
            +
                def build
         
     | 
| 
      
 60 
     | 
    
         
            +
                  if @predicate.nil? then
         
     | 
| 
      
 61 
     | 
    
         
            +
                    raise "Category #{@description} has no predicate defined."
         
     | 
| 
      
 62 
     | 
    
         
            +
                  elsif @action.nil? then
         
     | 
| 
      
 63 
     | 
    
         
            +
                    raise "Category #{@description} has no action defined."
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
                  Category.new( @description, @action, @quiet, @predicate )
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
                
         
     | 
| 
      
 68 
     | 
    
         
            +
                def match( &block )
         
     | 
| 
      
 69 
     | 
    
         
            +
                  @predicate = Proc.new &block
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
                
         
     | 
| 
      
 72 
     | 
    
         
            +
                def ignore
         
     | 
| 
      
 73 
     | 
    
         
            +
                  @action = :ignore
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
                
         
     | 
| 
      
 76 
     | 
    
         
            +
                def retain
         
     | 
| 
      
 77 
     | 
    
         
            +
                  @action = :retain
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
                
         
     | 
| 
      
 80 
     | 
    
         
            +
                def archive
         
     | 
| 
      
 81 
     | 
    
         
            +
                  @action = :archive
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
                
         
     | 
| 
      
 84 
     | 
    
         
            +
                def remove
         
     | 
| 
      
 85 
     | 
    
         
            +
                  @action = :remove
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
                
         
     | 
| 
      
 88 
     | 
    
         
            +
                def quiet
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @quiet = true
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
                
         
     | 
| 
      
 92 
     | 
    
         
            +
              end
         
     | 
| 
      
 93 
     | 
    
         
            +
              
         
     | 
| 
      
 94 
     | 
    
         
            +
              class FileContext
         
     | 
| 
      
 95 
     | 
    
         
            +
                attr_accessor :name
         
     | 
| 
      
 96 
     | 
    
         
            +
                
         
     | 
| 
      
 97 
     | 
    
         
            +
                def initialize( path, filename, preprocessor )
         
     | 
| 
      
 98 
     | 
    
         
            +
                  @name = File.join( path, filename )
         
     | 
| 
      
 99 
     | 
    
         
            +
                  @attributes = Hash.new
         
     | 
| 
      
 100 
     | 
    
         
            +
                  instance_eval &preprocessor unless preprocessor.nil?
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
                
         
     | 
| 
      
 103 
     | 
    
         
            +
                # def responds_to?( symbol )
         
     | 
| 
      
 104 
     | 
    
         
            +
                #   symbol.to_s.end_with? '=' || @attributes.has_key? symbol
         
     | 
| 
      
 105 
     | 
    
         
            +
                # end
         
     | 
| 
      
 106 
     | 
    
         
            +
                # 
         
     | 
| 
      
 107 
     | 
    
         
            +
                def method_missing( symbol, *arguments )
         
     | 
| 
      
 108 
     | 
    
         
            +
                  if symbol.to_s =~ /(.+)=/ && arguments.size == 1 then
         
     | 
| 
      
 109 
     | 
    
         
            +
                    @attributes[ $1.to_sym ] = arguments.first
         
     | 
| 
      
 110 
     | 
    
         
            +
                  elsif @attributes.has_key?( symbol ) && arguments.empty? then
         
     | 
| 
      
 111 
     | 
    
         
            +
                    @attributes[ symbol ]
         
     | 
| 
      
 112 
     | 
    
         
            +
                  else
         
     | 
| 
      
 113 
     | 
    
         
            +
                    super symbol, arguments
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
                end
         
     | 
| 
      
 116 
     | 
    
         
            +
                
         
     | 
| 
      
 117 
     | 
    
         
            +
              end
         
     | 
| 
      
 118 
     | 
    
         
            +
              
         
     | 
| 
      
 119 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,103 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'prune/archiver'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'tmpdir'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rspec'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            describe Prune::Archiver do
         
     | 
| 
      
 6 
     | 
    
         
            +
              SOURCE='/mysql'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              describe "with a #{SOURCE} source" do
         
     | 
| 
      
 9 
     | 
    
         
            +
                DESTINATION = '/mysql-archives'
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                subject { Prune::Archiver.new( nil, '/mysql', true ) }
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                it "should have #{DESTINATION} destination" do
         
     | 
| 
      
 14 
     | 
    
         
            +
                  subject.destination.should eq( DESTINATION )
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                it "should create #{DESTINATION} if does not exist" do
         
     | 
| 
      
 18 
     | 
    
         
            +
                  File.stub( :exists? ).with( DESTINATION ) { false }
         
     | 
| 
      
 19 
     | 
    
         
            +
                  Dir.should_receive( :mkdir ).with( DESTINATION )
         
     | 
| 
      
 20 
     | 
    
         
            +
                  subject.make_destination_dir
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                it "should not attempt to create #{DESTINATION} if it exists" do
         
     | 
| 
      
 24 
     | 
    
         
            +
                  File.stub( :exists? ).with( DESTINATION ) { true }
         
     | 
| 
      
 25 
     | 
    
         
            +
                  Dir.should_not_receive( :mkdir )
         
     | 
| 
      
 26 
     | 
    
         
            +
                  subject.make_destination_dir
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                context "for May 2011" do
         
     | 
| 
      
 30 
     | 
    
         
            +
                  ARCHIVE_FILE="#{DESTINATION}/archive-May-2011.tar.gz"
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  it "should write new archive file in #{DESTINATION} if none exists" do
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    # Destination Exists
         
     | 
| 
      
 35 
     | 
    
         
            +
                    File.stub( :exists? ).with( DESTINATION ) { true }
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    # Archive File Exists
         
     | 
| 
      
 38 
     | 
    
         
            +
                    File.stub( :exists? ).with( ARCHIVE_FILE ) { false }
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                    # Create Zip File
         
     | 
| 
      
 41 
     | 
    
         
            +
                    archive_file = double "file"
         
     | 
| 
      
 42 
     | 
    
         
            +
                    gz = double "GzipWriter"
         
     | 
| 
      
 43 
     | 
    
         
            +
                    paths = [ "/mysql/a", "/mysql/b", "/mysql/c" ]
         
     | 
| 
      
 44 
     | 
    
         
            +
                    File.stub( :open ).with( ARCHIVE_FILE, 'wb' ) { archive_file }
         
     | 
| 
      
 45 
     | 
    
         
            +
                    Zlib::GzipWriter.stub( :new ) { gz }
         
     | 
| 
      
 46 
     | 
    
         
            +
                    Minitar.should_receive( :pack ).with( paths, gz )
         
     | 
| 
      
 47 
     | 
    
         
            +
                    File.should_receive( :delete ).with( *paths )
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    subject.archive "May-2011", ["a", "b", "c"]
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  it "should add to existing archive file if it exists" do
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    # Destination Exists
         
     | 
| 
      
 55 
     | 
    
         
            +
                    File.stub( :exists? ).with( DESTINATION ) { true }
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    # Archive File Exists
         
     | 
| 
      
 58 
     | 
    
         
            +
                    File.stub( :exists? ).with( ARCHIVE_FILE ) { true }
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    # Should Create Temp Dir
         
     | 
| 
      
 61 
     | 
    
         
            +
                    tmpdir = "/tmp"
         
     | 
| 
      
 62 
     | 
    
         
            +
                    Dir.stub( :mktmpdir ).and_yield( tmpdir )
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    # Should Extract Contents
         
     | 
| 
      
 65 
     | 
    
         
            +
                    archive_file = double "archive file"
         
     | 
| 
      
 66 
     | 
    
         
            +
                    File.stub( :open ).with( ARCHIVE_FILE, 'rb' ) { archive_file }
         
     | 
| 
      
 67 
     | 
    
         
            +
                    gzr = double "GzipReader"
         
     | 
| 
      
 68 
     | 
    
         
            +
                    Zlib::GzipReader.stub( :new ) { gzr }
         
     | 
| 
      
 69 
     | 
    
         
            +
                    Minitar.should_receive( :unpack ).with( gzr, tmpdir )
         
     | 
| 
      
 70 
     | 
    
         
            +
                    Dir.should_receive( :entries ).with( tmpdir ) { ["c", "d"] }
         
     | 
| 
      
 71 
     | 
    
         
            +
                    extracted_paths = [ "/tmp/c", "/tmp/d" ]
         
     | 
| 
      
 72 
     | 
    
         
            +
                    extracted_paths.each { |path| File.stub( :directory? ).with( path ).and_return( false ) }
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                    # Should Create Final Archive
         
     | 
| 
      
 75 
     | 
    
         
            +
                    File.stub( :open ).with( ARCHIVE_FILE, 'wb' ) { archive_file }
         
     | 
| 
      
 76 
     | 
    
         
            +
                    gzw = double "GzipWriter"
         
     | 
| 
      
 77 
     | 
    
         
            +
                    Zlib::GzipWriter.stub( :new ) { gzw }
         
     | 
| 
      
 78 
     | 
    
         
            +
                    original_paths = [ "/mysql/a", "/mysql/b" ]
         
     | 
| 
      
 79 
     | 
    
         
            +
                    combined_paths = extracted_paths + original_paths
         
     | 
| 
      
 80 
     | 
    
         
            +
                    Minitar.should_receive( :pack ).with( combined_paths, gzw )
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                    # Delete Files
         
     | 
| 
      
 83 
     | 
    
         
            +
                    File.should_receive( :delete ).with( *original_paths )
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                    # Go
         
     | 
| 
      
 86 
     | 
    
         
            +
                    subject.archive "May-2011", ["a", "b"]
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                context "and a /mysql/archives destination" do
         
     | 
| 
      
 93 
     | 
    
         
            +
                  subject { Prune::Archiver.new( '/mysql/archives', '/mysql', true ) }
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  it "should use the explicit destination" do
         
     | 
| 
      
 96 
     | 
    
         
            +
                    subject.destination.should eq( '/mysql/archives' )
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
              end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
            end
         
     | 
    
        data/spec/cli_spec.rb
    ADDED
    
    | 
         @@ -0,0 +1,205 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'prune/cli'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'prune/pruner'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'rspec'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            describe Prune::CommandLineInterface do
         
     | 
| 
      
 7 
     | 
    
         
            +
              USAGE_TEXT = /Usage: prune \[options\] folder/
         
     | 
| 
      
 8 
     | 
    
         
            +
              ARCHIVE_PATH = "/prune/fake/archive-path"
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              before(:each) do
         
     | 
| 
      
 11 
     | 
    
         
            +
                @messages = []
         
     | 
| 
      
 12 
     | 
    
         
            +
                $stdout.stub( :write ) { |message| @messages << message  }
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                @pruner = double( "pruner" )
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              describe "with no arguments" do
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                it "should print help" do
         
     | 
| 
      
 20 
     | 
    
         
            +
                  ARGV.clear
         
     | 
| 
      
 21 
     | 
    
         
            +
                  Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @messages.should include_match( USAGE_TEXT )
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              describe "with a path argument" do
         
     | 
| 
      
 28 
     | 
    
         
            +
                PATH = "/prune/fake/path"
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                before(:each) do
         
     | 
| 
      
 31 
     | 
    
         
            +
                  ARGV.clear.push( PATH )
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @pruner.stub(:prune)
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                it "should call prune with path" do
         
     | 
| 
      
 36 
     | 
    
         
            +
                  Prune::Pruner.stub( :new ) { @pruner }
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @pruner.should_receive( :prune ).with( PATH )
         
     | 
| 
      
 38 
     | 
    
         
            +
                  Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                it "should create pruner with defaults" do
         
     | 
| 
      
 42 
     | 
    
         
            +
                  Prune::Pruner.stub( :new ).with( Prune::CommandLineInterface::DEFAULT_OPTIONS ) { @pruner }
         
     | 
| 
      
 43 
     | 
    
         
            +
                  Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                describe "and a -v argument" do
         
     | 
| 
      
 47 
     | 
    
         
            +
                  it "should set verbose option" do
         
     | 
| 
      
 48 
     | 
    
         
            +
                    assert_arg_to_option( "-v", :verbose => true )
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                describe "and a --verbose argument" do
         
     | 
| 
      
 53 
     | 
    
         
            +
                  it "should set verbose option" do
         
     | 
| 
      
 54 
     | 
    
         
            +
                    assert_arg_to_option( "--verbose", :verbose => true )
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                describe "and a -d argument" do
         
     | 
| 
      
 59 
     | 
    
         
            +
                  it "should set the dry-run option" do
         
     | 
| 
      
 60 
     | 
    
         
            +
                    assert_arg_to_option( "-d", :dry_run => true )
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                describe "and a --dry-run argument" do
         
     | 
| 
      
 65 
     | 
    
         
            +
                  it "should set the dry-run option" do
         
     | 
| 
      
 66 
     | 
    
         
            +
                    assert_arg_to_option( "--dry-run", :dry_run => true )
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                describe "and a -f argument" do
         
     | 
| 
      
 71 
     | 
    
         
            +
                  it "should set the prompt option to false" do
         
     | 
| 
      
 72 
     | 
    
         
            +
                    assert_arg_to_option "-f", :prompt => false
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                describe "and a --force argument" do
         
     | 
| 
      
 77 
     | 
    
         
            +
                  it "should set the prompt option to false" do
         
     | 
| 
      
 78 
     | 
    
         
            +
                    assert_arg_to_option "--force", :prompt => false
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                describe "and a --no-prompt argument" do
         
     | 
| 
      
 83 
     | 
    
         
            +
                  it "should set the prompt option to false" do
         
     | 
| 
      
 84 
     | 
    
         
            +
                    assert_arg_to_option "--no-prompt", :prompt => false
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                describe "and a -a argument" do
         
     | 
| 
      
 89 
     | 
    
         
            +
                  before(:each) do
         
     | 
| 
      
 90 
     | 
    
         
            +
                    ARGV.push "-a"
         
     | 
| 
      
 91 
     | 
    
         
            +
                  end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                  describe "with no folder name" do
         
     | 
| 
      
 94 
     | 
    
         
            +
                    it "should print a parsing error" do
         
     | 
| 
      
 95 
     | 
    
         
            +
                      $stderr.stub( :print ) { |message| @messages << message  }
         
     | 
| 
      
 96 
     | 
    
         
            +
                      Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 97 
     | 
    
         
            +
                      @messages.should include_match( /missing argument: -a/ )
         
     | 
| 
      
 98 
     | 
    
         
            +
                    end
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                  describe "with a folder name" do
         
     | 
| 
      
 102 
     | 
    
         
            +
                    it "should set the archive_path option to the folder" do
         
     | 
| 
      
 103 
     | 
    
         
            +
                      assert_arg_to_option ARCHIVE_PATH, :archive_path => ARCHIVE_PATH
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                describe "and a --archive-folder argument" do
         
     | 
| 
      
 109 
     | 
    
         
            +
                  before(:each) do
         
     | 
| 
      
 110 
     | 
    
         
            +
                    ARGV.push "--archive-folder"
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                  describe "with no folder name" do
         
     | 
| 
      
 114 
     | 
    
         
            +
                    it "should print a parsing error" do
         
     | 
| 
      
 115 
     | 
    
         
            +
                      $stderr.stub( :print ) { |message| @messages << message  }
         
     | 
| 
      
 116 
     | 
    
         
            +
                      Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 117 
     | 
    
         
            +
                      @messages.should include_match( /missing argument: --archive-folder/ )
         
     | 
| 
      
 118 
     | 
    
         
            +
                    end
         
     | 
| 
      
 119 
     | 
    
         
            +
                  end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                  describe "with a folder name" do
         
     | 
| 
      
 122 
     | 
    
         
            +
                    it "should set the archive_path option to the folder" do
         
     | 
| 
      
 123 
     | 
    
         
            +
                      assert_arg_to_option ARCHIVE_PATH, :archive_path => ARCHIVE_PATH
         
     | 
| 
      
 124 
     | 
    
         
            +
                    end
         
     | 
| 
      
 125 
     | 
    
         
            +
                  end
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                describe "and a -? argument" do
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                  before(:each) do
         
     | 
| 
      
 131 
     | 
    
         
            +
                    ARGV.push "-?"
         
     | 
| 
      
 132 
     | 
    
         
            +
                  end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                  it "should print help" do
         
     | 
| 
      
 135 
     | 
    
         
            +
                    Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 136 
     | 
    
         
            +
                    @messages.should include_match( USAGE_TEXT )
         
     | 
| 
      
 137 
     | 
    
         
            +
                  end
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                  it "should not invoke prune" do
         
     | 
| 
      
 140 
     | 
    
         
            +
                    @pruner.should_not_receive( :prune )
         
     | 
| 
      
 141 
     | 
    
         
            +
                    Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                describe "and a --help argument" do
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                  before(:each) do
         
     | 
| 
      
 149 
     | 
    
         
            +
                    ARGV.push "--help"
         
     | 
| 
      
 150 
     | 
    
         
            +
                  end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                  it "should print help" do
         
     | 
| 
      
 153 
     | 
    
         
            +
                    Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 154 
     | 
    
         
            +
                    @messages.should include_match( USAGE_TEXT )
         
     | 
| 
      
 155 
     | 
    
         
            +
                  end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                  it "should not invoke prune" do
         
     | 
| 
      
 158 
     | 
    
         
            +
                    @pruner.should_not_receive( :prune )
         
     | 
| 
      
 159 
     | 
    
         
            +
                    Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 160 
     | 
    
         
            +
                  end
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                end
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                describe "and an unknown argument" do
         
     | 
| 
      
 165 
     | 
    
         
            +
                  it "should print a parsing error" do
         
     | 
| 
      
 166 
     | 
    
         
            +
                    ARGV.push "--unknown-argument"
         
     | 
| 
      
 167 
     | 
    
         
            +
                    $stderr.stub( :print ) { |message| @messages << message  }
         
     | 
| 
      
 168 
     | 
    
         
            +
                    Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 169 
     | 
    
         
            +
                    @messages.should include_match( /invalid option/ )
         
     | 
| 
      
 170 
     | 
    
         
            +
                  end
         
     | 
| 
      
 171 
     | 
    
         
            +
                end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                describe "and a --version argument" do
         
     | 
| 
      
 174 
     | 
    
         
            +
                  before(:each) do
         
     | 
| 
      
 175 
     | 
    
         
            +
                    ARGV.push "--version"
         
     | 
| 
      
 176 
     | 
    
         
            +
                  end
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
                  it "should print version number" do
         
     | 
| 
      
 179 
     | 
    
         
            +
                      $stderr.stub( :print ) { |message| @messages << message  }
         
     | 
| 
      
 180 
     | 
    
         
            +
                      Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 181 
     | 
    
         
            +
                      @messages.should include_match( /Prune 1.0.0/ )
         
     | 
| 
      
 182 
     | 
    
         
            +
                  end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                  it "should not invoke prune" do
         
     | 
| 
      
 185 
     | 
    
         
            +
                    @pruner.should_not_receive( :prune )
         
     | 
| 
      
 186 
     | 
    
         
            +
                    Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 187 
     | 
    
         
            +
                  end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                end
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                describe "and a --no-archive argument" do
         
     | 
| 
      
 192 
     | 
    
         
            +
                  it "should set the archive option to false" do
         
     | 
| 
      
 193 
     | 
    
         
            +
                    assert_arg_to_option "--no-archive", :archive=>false
         
     | 
| 
      
 194 
     | 
    
         
            +
                  end
         
     | 
| 
      
 195 
     | 
    
         
            +
                end
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                def assert_arg_to_option( arg, *options )
         
     | 
| 
      
 198 
     | 
    
         
            +
                  Prune::Pruner.should_receive( :new ).with( hash_including( *options ) ).and_return( @pruner )
         
     | 
| 
      
 199 
     | 
    
         
            +
                  ARGV.push( arg )
         
     | 
| 
      
 200 
     | 
    
         
            +
                  Prune::CommandLineInterface::parse_and_run
         
     | 
| 
      
 201 
     | 
    
         
            +
                end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
              end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,63 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'prune/grouper'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'prune/retention'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'rspec'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            describe "Prune::Grouper" do
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              GROUP_PATH = '/example/prune/folder'
         
     | 
| 
      
 9 
     | 
    
         
            +
              before( :each ) do
         
     | 
| 
      
 10 
     | 
    
         
            +
                 @archiver = double( "Archiver" )
         
     | 
| 
      
 11 
     | 
    
         
            +
                 @grouper = Prune::Grouper.new( @archiver )
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              context "w/o files" do
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                it "should not archive" do
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @archiver.should_not_receive( :archive )
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @grouper.group( GROUP_PATH, [] ).archive
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              context "with files" do
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                it "should archive files" do
         
     | 
| 
      
 26 
     | 
    
         
            +
                  files = mock_files( Date.new(2011,01,01) )
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @archiver.should_receive( :archive )
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @grouper.group( GROUP_PATH, files ).archive
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                it "should specify month and year" do
         
     | 
| 
      
 32 
     | 
    
         
            +
                  files = mock_files( Date.new(2008,03,01) )
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @archiver.should_receive( :archive ).with( "Mar-2008", files )
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @grouper.group( GROUP_PATH, files ).archive
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                it "should combine files with same month/year" do
         
     | 
| 
      
 38 
     | 
    
         
            +
                  files = mock_files( Date.new(2008,03,01), Date.new(2008,03,02) )
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @archiver.should_receive( :archive ).with( "Mar-2008", files )
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @grouper.group( GROUP_PATH, files ).archive
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                it "should not combine files with same month, different year" do
         
     | 
| 
      
 44 
     | 
    
         
            +
                  files = mock_files( Date.new(2008,03,01), Date.new(2009,03,01) )
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @archiver.should_receive( :archive ).with( "Mar-2008", [files.first] )
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @archiver.should_receive( :archive ).with( "Mar-2009", [files.last] )
         
     | 
| 
      
 47 
     | 
    
         
            +
                  @grouper.group( GROUP_PATH, files ).archive
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              def mock_files( *dates )
         
     | 
| 
      
 53 
     | 
    
         
            +
                files = []
         
     | 
| 
      
 54 
     | 
    
         
            +
                dates.map do |item|
         
     | 
| 
      
 55 
     | 
    
         
            +
                  month_name = Date::ABBR_MONTHNAMES[item.month]
         
     | 
| 
      
 56 
     | 
    
         
            +
                  file_name = "file-#{month_name}-#{item.year}.sql"
         
     | 
| 
      
 57 
     | 
    
         
            +
                  File.stub( :mtime ).with( "#{GROUP_PATH}/#{file_name}" ) { item }
         
     | 
| 
      
 58 
     | 
    
         
            +
                  files << file_name
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
                return files;
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
            end
         
     | 
    
        data/spec/pruner_spec.rb
    ADDED
    
    | 
         @@ -0,0 +1,206 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'prune'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rspec'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            describe Prune::Pruner do
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              PRUNE_PATH = '/example/prune/folder'
         
     | 
| 
      
 8 
     | 
    
         
            +
              subject { Prune::Pruner.new Hash.new }
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              before( :each ) do
         
     | 
| 
      
 11 
     | 
    
         
            +
                @retention_policy = double( "RetentionPolicy" )
         
     | 
| 
      
 12 
     | 
    
         
            +
                @retention_policy.stub( :categories ) { [ Category.new( "Unmatched Files", :retain, true ) ] }
         
     | 
| 
      
 13 
     | 
    
         
            +
                Prune::RetentionPolicy.stub( :new ) { @retention_policy }
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              context "w/o prompt" do
         
     | 
| 
      
 17 
     | 
    
         
            +
                before( :each ) do
         
     | 
| 
      
 18 
     | 
    
         
            +
                  subject.options[:prompt]=false
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                it "should not attempt to process folder that does not exist" do
         
     | 
| 
      
 22 
     | 
    
         
            +
                  File.stub( :exists? ).with( PRUNE_PATH ) { false }
         
     | 
| 
      
 23 
     | 
    
         
            +
                  Dir.should_not_receive( :foreach )
         
     | 
| 
      
 24 
     | 
    
         
            +
                  $stdout.should_receive( :write ).with( /ERROR: Cannot find folder/ )
         
     | 
| 
      
 25 
     | 
    
         
            +
                  subject.prune( PRUNE_PATH )
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                context "with no files" do
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  before( :each ) do
         
     | 
| 
      
 31 
     | 
    
         
            +
                    stub_files
         
     | 
| 
      
 32 
     | 
    
         
            +
                    stub_messages
         
     | 
| 
      
 33 
     | 
    
         
            +
                    subject.prune PRUNE_PATH
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  it "should not invoke the retention policy" do
         
     | 
| 
      
 37 
     | 
    
         
            +
                    @retention_policy.should_not_receive( :categorize )
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  it "should print 'Analyzing #{PRUNE_PATH}'" do
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @messages.should include("Analyzing '#{PRUNE_PATH}':\n")
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  it "should say no action was required" do
         
     | 
| 
      
 45 
     | 
    
         
            +
                    @messages.should include("No actions necessary.\n")
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  it "should say no files were analyzed" do
         
     | 
| 
      
 49 
     | 
    
         
            +
                    @messages.should include_match( /0 file\(s\) analyzed/ )
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                context "with three files" do
         
     | 
| 
      
 55 
     | 
    
         
            +
                  ONE_DAY = 86400
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  before( :each ) do
         
     | 
| 
      
 58 
     | 
    
         
            +
                    stub_files "beta.txt" => Time.now - ONE_DAY, "alpha.txt" => Time.now - 3*ONE_DAY, "gamma.txt" => Time.now
         
     | 
| 
      
 59 
     | 
    
         
            +
                    stub_messages
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  it "should categorize each file in modified order" do
         
     | 
| 
      
 63 
     | 
    
         
            +
                    @retention_policy.should_receive( :categorize ).with( 'alpha.txt' ).ordered
         
     | 
| 
      
 64 
     | 
    
         
            +
                    @retention_policy.should_receive( :categorize ).with( 'beta.txt' ).ordered
         
     | 
| 
      
 65 
     | 
    
         
            +
                    @retention_policy.should_receive( :categorize ).with( 'gamma.txt' ).ordered
         
     | 
| 
      
 66 
     | 
    
         
            +
                    subject.prune PRUNE_PATH
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  it "should say three files were analyzed" do
         
     | 
| 
      
 70 
     | 
    
         
            +
                    @retention_policy.as_null_object
         
     | 
| 
      
 71 
     | 
    
         
            +
                    subject.prune PRUNE_PATH
         
     | 
| 
      
 72 
     | 
    
         
            +
                    @messages.should include_match( /3 file\(s\) analyzed/ )
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                context "with file categorized as :remove" do
         
     | 
| 
      
 78 
     | 
    
         
            +
                  it "should delete file" do
         
     | 
| 
      
 79 
     | 
    
         
            +
                    filename = 'delete-me.txt'
         
     | 
| 
      
 80 
     | 
    
         
            +
                    stub_files filename
         
     | 
| 
      
 81 
     | 
    
         
            +
                    
         
     | 
| 
      
 82 
     | 
    
         
            +
                    category = double( category )
         
     | 
| 
      
 83 
     | 
    
         
            +
                    category.stub( :description ) { "Old" }
         
     | 
| 
      
 84 
     | 
    
         
            +
                    category.stub( :action ) { :remove }
         
     | 
| 
      
 85 
     | 
    
         
            +
                    category.stub( :quiet? ) { false }
         
     | 
| 
      
 86 
     | 
    
         
            +
                    
         
     | 
| 
      
 87 
     | 
    
         
            +
                    @retention_policy.should_receive( :categorize ).with( filename ) { category }
         
     | 
| 
      
 88 
     | 
    
         
            +
                    File.should_receive( :delete ).with( File.join( PRUNE_PATH, filename ) )
         
     | 
| 
      
 89 
     | 
    
         
            +
                    subject.prune PRUNE_PATH
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                context "with files categorized as :archive" do
         
     | 
| 
      
 94 
     | 
    
         
            +
                  let!(:files) { [ 'one.tar.gz', 'two.tar.gz', 'three.tar.gz' ] }
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                  before do
         
     | 
| 
      
 97 
     | 
    
         
            +
                    subject.options[:archive] = true
         
     | 
| 
      
 98 
     | 
    
         
            +
                    stub_files files
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                  it "should archive files in groups" do
         
     | 
| 
      
 102 
     | 
    
         
            +
                    category = double( "category" )
         
     | 
| 
      
 103 
     | 
    
         
            +
                    @retention_policy.stub( :categorize ) { category }
         
     | 
| 
      
 104 
     | 
    
         
            +
                    category.stub( :action ) { :archive }
         
     | 
| 
      
 105 
     | 
    
         
            +
                    category.stub( :description ) { "Ancient" }
         
     | 
| 
      
 106 
     | 
    
         
            +
                    category.stub( :quiet? ) { false }
         
     | 
| 
      
 107 
     | 
    
         
            +
                    
         
     | 
| 
      
 108 
     | 
    
         
            +
                    grouper = double( "Grouper" )
         
     | 
| 
      
 109 
     | 
    
         
            +
                    Prune::Grouper.stub( :new ) { grouper }
         
     | 
| 
      
 110 
     | 
    
         
            +
                    grouper.should_receive( :group ).with( PRUNE_PATH, files )
         
     | 
| 
      
 111 
     | 
    
         
            +
                    grouper.should_receive( :archive ) { "2 Archives created." }
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                    subject.prune PRUNE_PATH
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
                end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
              end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
              describe "Confirmation Prompt" do
         
     | 
| 
      
 120 
     | 
    
         
            +
                before( :each ) do
         
     | 
| 
      
 121 
     | 
    
         
            +
                  subject.options[:prompt]=false
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                it "should interpret 'Y' as true" do
         
     | 
| 
      
 125 
     | 
    
         
            +
                  expect_prompt_with_response( "Y\n")
         
     | 
| 
      
 126 
     | 
    
         
            +
                  subject.prompt.should be_true
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                it "should interpret 'Y ' as true" do
         
     | 
| 
      
 130 
     | 
    
         
            +
                  expect_prompt_with_response("Y \n")
         
     | 
| 
      
 131 
     | 
    
         
            +
                  subject.prompt.should be_true
         
     | 
| 
      
 132 
     | 
    
         
            +
                end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                it "should interpret ' Y' as true" do
         
     | 
| 
      
 135 
     | 
    
         
            +
                  expect_prompt_with_response(" Y\n")
         
     | 
| 
      
 136 
     | 
    
         
            +
                  subject.prompt.should be_true
         
     | 
| 
      
 137 
     | 
    
         
            +
                end
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                it "should interpret ' Y ' as true" do
         
     | 
| 
      
 140 
     | 
    
         
            +
                  expect_prompt_with_response(" Y \n")
         
     | 
| 
      
 141 
     | 
    
         
            +
                  subject.prompt.should be_true
         
     | 
| 
      
 142 
     | 
    
         
            +
                end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                it "should interpret 'y' as true" do
         
     | 
| 
      
 145 
     | 
    
         
            +
                  expect_prompt_with_response("y\n")
         
     | 
| 
      
 146 
     | 
    
         
            +
                  subject.prompt.should be_true
         
     | 
| 
      
 147 
     | 
    
         
            +
                end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                it "should interpret 'yes' as true" do
         
     | 
| 
      
 150 
     | 
    
         
            +
                  expect_prompt_with_response("yes\n")
         
     | 
| 
      
 151 
     | 
    
         
            +
                  subject.prompt.should be_true
         
     | 
| 
      
 152 
     | 
    
         
            +
                end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                it "should interpret 'no' as false" do
         
     | 
| 
      
 155 
     | 
    
         
            +
                  expect_prompt_with_response("no\n")
         
     | 
| 
      
 156 
     | 
    
         
            +
                  subject.prompt.should be_false
         
     | 
| 
      
 157 
     | 
    
         
            +
                end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                it "should interpret 'n' as false" do
         
     | 
| 
      
 160 
     | 
    
         
            +
                  expect_prompt_with_response("n\n")
         
     | 
| 
      
 161 
     | 
    
         
            +
                  subject.prompt.should be_false
         
     | 
| 
      
 162 
     | 
    
         
            +
                end
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                it "should interpret 'N' as false" do
         
     | 
| 
      
 165 
     | 
    
         
            +
                  expect_prompt_with_response("N\n")
         
     | 
| 
      
 166 
     | 
    
         
            +
                  subject.prompt.should be_false
         
     | 
| 
      
 167 
     | 
    
         
            +
                end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                it "should interpret 'q' as false" do
         
     | 
| 
      
 170 
     | 
    
         
            +
                  expect_prompt_with_response("q\n")
         
     | 
| 
      
 171 
     | 
    
         
            +
                  subject.prompt.should be_false
         
     | 
| 
      
 172 
     | 
    
         
            +
                end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                def expect_prompt_with_response( response )
         
     | 
| 
      
 175 
     | 
    
         
            +
                  $stdout.should_receive( :write ).with( /Proceed?/ )
         
     | 
| 
      
 176 
     | 
    
         
            +
                  STDIN.stub(:gets) { response }
         
     | 
| 
      
 177 
     | 
    
         
            +
                end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
              end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
              def stub_files( files = nil )
         
     | 
| 
      
 182 
     | 
    
         
            +
                File.stub( :exists? ).with( PRUNE_PATH ) { true }
         
     | 
| 
      
 183 
     | 
    
         
            +
                File.stub( :directory? ).with( PRUNE_PATH ) { true }
         
     | 
| 
      
 184 
     | 
    
         
            +
                case files
         
     | 
| 
      
 185 
     | 
    
         
            +
                when nil
         
     | 
| 
      
 186 
     | 
    
         
            +
                  Dir.stub( :entries ).with( PRUNE_PATH ) { Array.new }
         
     | 
| 
      
 187 
     | 
    
         
            +
                when String
         
     | 
| 
      
 188 
     | 
    
         
            +
                  subject.stub(:test).with( ?M, File.join( PRUNE_PATH, files ) ) { Time.now }
         
     | 
| 
      
 189 
     | 
    
         
            +
                  Dir.stub( :entries ).with( PRUNE_PATH ) { [ files ] }
         
     | 
| 
      
 190 
     | 
    
         
            +
                when Array
         
     | 
| 
      
 191 
     | 
    
         
            +
                  files.each_index { |index| subject.stub(:test).with( ?M, File.join( PRUNE_PATH, files[index] ) ) { index }  }
         
     | 
| 
      
 192 
     | 
    
         
            +
                  Dir.stub( :entries ).with( PRUNE_PATH ) { files }
         
     | 
| 
      
 193 
     | 
    
         
            +
                when Hash
         
     | 
| 
      
 194 
     | 
    
         
            +
                  files.each_key { |key| subject.stub(:test).with( ?M, File.join( PRUNE_PATH, key ) ) { files[key] } }
         
     | 
| 
      
 195 
     | 
    
         
            +
                  Dir.stub( :entries ).with( PRUNE_PATH ) { files.keys }
         
     | 
| 
      
 196 
     | 
    
         
            +
                else
         
     | 
| 
      
 197 
     | 
    
         
            +
                  raise "Don't know how to stub files for #{files.class}"
         
     | 
| 
      
 198 
     | 
    
         
            +
                end
         
     | 
| 
      
 199 
     | 
    
         
            +
              end
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
              def stub_messages
         
     | 
| 
      
 202 
     | 
    
         
            +
                @messages = []
         
     | 
| 
      
 203 
     | 
    
         
            +
                $stdout.stub( :write ) { |message| @messages << message  }
         
     | 
| 
      
 204 
     | 
    
         
            +
              end
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,147 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'prune/retention'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'prune/category'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rspec'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'spec/spec_helper'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            DAY = 24 * 60 * 60
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            describe Prune::RetentionPolicy do
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              SOURCE_DIR = "source_path"
         
     | 
| 
      
 11 
     | 
    
         
            +
              SOURCE_FILE = "source_file"
         
     | 
| 
      
 12 
     | 
    
         
            +
              SOURCE_PATH = "#{SOURCE_DIR}/#{SOURCE_FILE}"
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              subject { Prune::RetentionPolicy.new SOURCE_DIR }
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              describe "default retention policy" do
         
     | 
| 
      
 17 
     | 
    
         
            +
                
         
     | 
| 
      
 18 
     | 
    
         
            +
                it "should return categories in dsl order" do
         
     | 
| 
      
 19 
     | 
    
         
            +
                  cats = subject.categories
         
     | 
| 
      
 20 
     | 
    
         
            +
                  cats.shift.description.should include( "Ignoring directories" )
         
     | 
| 
      
 21 
     | 
    
         
            +
                  cats.shift.description.should include( "from the Last Two Weeks" )
         
     | 
| 
      
 22 
     | 
    
         
            +
                  cats.shift.description.should include( "Retaining 'Friday'" )
         
     | 
| 
      
 23 
     | 
    
         
            +
                  cats.shift.description.should include( "Removing 'Non-Friday'" )
         
     | 
| 
      
 24 
     | 
    
         
            +
                  cats.shift.description.should include( "Archiving" )
         
     | 
| 
      
 25 
     | 
    
         
            +
                  cats.should be_empty
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              
         
     | 
| 
      
 28 
     | 
    
         
            +
                describe "analyzing a directory" do
         
     | 
| 
      
 29 
     | 
    
         
            +
                  let( :dircat ) do
         
     | 
| 
      
 30 
     | 
    
         
            +
                    File.stub( :directory? ).with( SOURCE_PATH ) { true }
         
     | 
| 
      
 31 
     | 
    
         
            +
                    File.stub( :mtime ).with( SOURCE_PATH ) { Time.now }
         
     | 
| 
      
 32 
     | 
    
         
            +
                    subject.categorize( SOURCE_FILE )
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  
         
     | 
| 
      
 35 
     | 
    
         
            +
                  
         
     | 
| 
      
 36 
     | 
    
         
            +
                  it "should be categorized as 'Ignoring directories'" do
         
     | 
| 
      
 37 
     | 
    
         
            +
                    dircat.description.should eq( "Ignoring directories" )
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  it "should invoke action :ignore" do
         
     | 
| 
      
 41 
     | 
    
         
            +
                    dircat.action.should eq( :ignore )
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                describe "analyzing a file" do
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  describe "created yesterday" do
         
     | 
| 
      
 48 
     | 
    
         
            +
                    
         
     | 
| 
      
 49 
     | 
    
         
            +
                    let( :yestercat ) do
         
     | 
| 
      
 50 
     | 
    
         
            +
                      File.stub( :directory? ).with( SOURCE_PATH ) { false }
         
     | 
| 
      
 51 
     | 
    
         
            +
                      File.stub( :mtime ).with( SOURCE_PATH ) { Time.now - DAY }
         
     | 
| 
      
 52 
     | 
    
         
            +
                      subject.categorize( SOURCE_FILE )
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                    it "should be categorized as '... Last Two Weeks'" do
         
     | 
| 
      
 56 
     | 
    
         
            +
                      yestercat.description.should include( 'Last Two Weeks' )
         
     | 
| 
      
 57 
     | 
    
         
            +
                    end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    it "should invoke action :retain" do
         
     | 
| 
      
 60 
     | 
    
         
            +
                      yestercat.action.should eq( :retain )
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  describe "created three weeks ago, wednesday" do
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    let( :weeksago ) do
         
     | 
| 
      
 67 
     | 
    
         
            +
                      File.stub( :directory? ).with( SOURCE_PATH ) { false }
         
     | 
| 
      
 68 
     | 
    
         
            +
                      File.stub( :mtime ).with( SOURCE_PATH ) { weeks_ago( 3, 'Wed' ) }
         
     | 
| 
      
 69 
     | 
    
         
            +
                      subject.categorize( SOURCE_FILE )
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                    it "should be described as 'Older than Two Weeks' and 'Non-Friday'" do
         
     | 
| 
      
 73 
     | 
    
         
            +
                      weeksago.description.should include 'Non-Friday'
         
     | 
| 
      
 74 
     | 
    
         
            +
                      weeksago.description.should include 'Older than Two Weeks'
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                    it "should invoke action :remove" do
         
     | 
| 
      
 78 
     | 
    
         
            +
                      weeksago.action.should eq( :remove )
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  describe "created three weeks ago, friday" do
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                    let( :weeksagofriday ) do
         
     | 
| 
      
 86 
     | 
    
         
            +
                      File.stub( :directory? ).with( SOURCE_PATH ) { false }
         
     | 
| 
      
 87 
     | 
    
         
            +
                      File.stub( :mtime ).with( SOURCE_PATH ) { weeks_ago( 3, 'Fri' ) }
         
     | 
| 
      
 88 
     | 
    
         
            +
                      subject.categorize( SOURCE_FILE )
         
     | 
| 
      
 89 
     | 
    
         
            +
                    end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                    it "should be described as 'Friday files', 'Older than Two Weeks'" do
         
     | 
| 
      
 92 
     | 
    
         
            +
                      weeksagofriday.description.should include( "'Friday' files" )
         
     | 
| 
      
 93 
     | 
    
         
            +
                      weeksagofriday.description.should include( 'Older than Two Weeks' )
         
     | 
| 
      
 94 
     | 
    
         
            +
                    end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    it "should invoke action :remove" do
         
     | 
| 
      
 97 
     | 
    
         
            +
                      weeksagofriday.action.should eq( :retain )
         
     | 
| 
      
 98 
     | 
    
         
            +
                    end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  describe "created three months ago, friday" do
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                    let( :oldfriday ) do
         
     | 
| 
      
 105 
     | 
    
         
            +
                      File.stub( :directory? ).with( SOURCE_PATH ) { false }
         
     | 
| 
      
 106 
     | 
    
         
            +
                      File.stub( :mtime ).with( SOURCE_PATH ) { weeks_ago( 12, 'Fri' ) }
         
     | 
| 
      
 107 
     | 
    
         
            +
                      subject.categorize( SOURCE_FILE )
         
     | 
| 
      
 108 
     | 
    
         
            +
                    end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                    it "should be described as 'Older than Two Months'" do
         
     | 
| 
      
 111 
     | 
    
         
            +
                      oldfriday.description.should include( 'Older than Two Months' )
         
     | 
| 
      
 112 
     | 
    
         
            +
                    end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                    it "should invoke action :archive" do
         
     | 
| 
      
 115 
     | 
    
         
            +
                      oldfriday.action.should eq( :archive )
         
     | 
| 
      
 116 
     | 
    
         
            +
                    end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                  end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                  describe "created three months ago, wednesday" do
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                    let( :oldwednesday ) do
         
     | 
| 
      
 123 
     | 
    
         
            +
                      File.stub( :directory? ).with( SOURCE_PATH ) { false }
         
     | 
| 
      
 124 
     | 
    
         
            +
                      File.stub( :mtime ).with( SOURCE_PATH ) { weeks_ago( 12, 'Wed' ) }
         
     | 
| 
      
 125 
     | 
    
         
            +
                      subject.categorize( SOURCE_FILE )
         
     | 
| 
      
 126 
     | 
    
         
            +
                    end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                    it "should be described as 'Non-Friday files', 'Older than Two Weeks'" do
         
     | 
| 
      
 129 
     | 
    
         
            +
                      oldwednesday.description.should include( "'Non-Friday' files" )
         
     | 
| 
      
 130 
     | 
    
         
            +
                      oldwednesday.description.should include( "Older than Two Weeks" )
         
     | 
| 
      
 131 
     | 
    
         
            +
                    end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                    it "should invoke action :remove" do
         
     | 
| 
      
 134 
     | 
    
         
            +
                      oldwednesday.action.should eq( :remove )
         
     | 
| 
      
 135 
     | 
    
         
            +
                    end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                  end
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
              end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
            end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
            def weeks_ago( weeks, weekday )
         
     | 
| 
      
 144 
     | 
    
         
            +
              sub_weeks = Time.now - ( DAY * 7 * weeks )
         
     | 
| 
      
 145 
     | 
    
         
            +
              weekday_adjustment = Time.now.wday - Date::ABBR_DAYNAMES.index( weekday )
         
     | 
| 
      
 146 
     | 
    
         
            +
              sub_weeks - ( weekday_adjustment * DAY )
         
     | 
| 
      
 147 
     | 
    
         
            +
            end
         
     | 
    
        data/spec/spec_helper.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,82 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification 
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: geoffreywiseman-prune
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version 
         
     | 
| 
      
 4 
     | 
    
         
            +
              hash: 19
         
     | 
| 
      
 5 
     | 
    
         
            +
              prerelease: 
         
     | 
| 
      
 6 
     | 
    
         
            +
              segments: 
         
     | 
| 
      
 7 
     | 
    
         
            +
              - 1
         
     | 
| 
      
 8 
     | 
    
         
            +
              - 1
         
     | 
| 
      
 9 
     | 
    
         
            +
              - 0
         
     | 
| 
      
 10 
     | 
    
         
            +
              version: 1.1.0
         
     | 
| 
      
 11 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 12 
     | 
    
         
            +
            authors: 
         
     | 
| 
      
 13 
     | 
    
         
            +
            - Geoffrey Wiseman
         
     | 
| 
      
 14 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 15 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 16 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            date: 2011-09-09 00:00:00 Z
         
     | 
| 
      
 19 
     | 
    
         
            +
            dependencies: []
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            description: Prune is meant to analyze a folder full of files, run them against a retention policy and decide which to keep, which to remove and which to archive. It is extensible and embeddable.
         
     | 
| 
      
 22 
     | 
    
         
            +
            email: geoffrey.wiseman@codiform.com
         
     | 
| 
      
 23 
     | 
    
         
            +
            executables: 
         
     | 
| 
      
 24 
     | 
    
         
            +
            - prune
         
     | 
| 
      
 25 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            files: 
         
     | 
| 
      
 30 
     | 
    
         
            +
            - lib/prune/archiver.rb
         
     | 
| 
      
 31 
     | 
    
         
            +
            - lib/prune/category.rb
         
     | 
| 
      
 32 
     | 
    
         
            +
            - lib/prune/cli.rb
         
     | 
| 
      
 33 
     | 
    
         
            +
            - lib/prune/default_retention.rb
         
     | 
| 
      
 34 
     | 
    
         
            +
            - lib/prune/grouper.rb
         
     | 
| 
      
 35 
     | 
    
         
            +
            - lib/prune/pruner.rb
         
     | 
| 
      
 36 
     | 
    
         
            +
            - lib/prune/retention.rb
         
     | 
| 
      
 37 
     | 
    
         
            +
            - lib/prune.rb
         
     | 
| 
      
 38 
     | 
    
         
            +
            - spec/archiver_spec.rb
         
     | 
| 
      
 39 
     | 
    
         
            +
            - spec/cli_spec.rb
         
     | 
| 
      
 40 
     | 
    
         
            +
            - spec/grouper_spec.rb
         
     | 
| 
      
 41 
     | 
    
         
            +
            - spec/pruner_spec.rb
         
     | 
| 
      
 42 
     | 
    
         
            +
            - spec/retention_spec.rb
         
     | 
| 
      
 43 
     | 
    
         
            +
            - spec/spec_helper.rb
         
     | 
| 
      
 44 
     | 
    
         
            +
            - bin/prune
         
     | 
| 
      
 45 
     | 
    
         
            +
            - Rakefile
         
     | 
| 
      
 46 
     | 
    
         
            +
            - README.mdown
         
     | 
| 
      
 47 
     | 
    
         
            +
            - UNLICENSE
         
     | 
| 
      
 48 
     | 
    
         
            +
            homepage: http://geoffreywiseman.github.com/prune
         
     | 
| 
      
 49 
     | 
    
         
            +
            licenses: []
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 52 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
            require_paths: 
         
     | 
| 
      
 55 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 56 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 57 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 58 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 59 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 60 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 61 
     | 
    
         
            +
                  hash: 3
         
     | 
| 
      
 62 
     | 
    
         
            +
                  segments: 
         
     | 
| 
      
 63 
     | 
    
         
            +
                  - 0
         
     | 
| 
      
 64 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 65 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 66 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 67 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 68 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 69 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 70 
     | 
    
         
            +
                  hash: 3
         
     | 
| 
      
 71 
     | 
    
         
            +
                  segments: 
         
     | 
| 
      
 72 
     | 
    
         
            +
                  - 0
         
     | 
| 
      
 73 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 74 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 77 
     | 
    
         
            +
            rubygems_version: 1.8.10
         
     | 
| 
      
 78 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 79 
     | 
    
         
            +
            specification_version: 3
         
     | 
| 
      
 80 
     | 
    
         
            +
            summary: Prunes files from a folder based on a retention policy, often time-based.
         
     | 
| 
      
 81 
     | 
    
         
            +
            test_files: []
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     |