expire 0.2.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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.reek.yml +23 -0
- data/.rspec +3 -0
- data/.rubocop.yml +45 -0
- data/.simplecov +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +152 -0
- data/LICENSE.txt +21 -0
- data/README.md +551 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example_rules/bad_rules.yml +2 -0
- data/example_rules/good_rules.yml +1 -0
- data/exe/expire +7 -0
- data/expire.gemspec +54 -0
- data/lib/expire.rb +51 -0
- data/lib/expire/all_backups_expired_error.rb +7 -0
- data/lib/expire/backup.rb +74 -0
- data/lib/expire/backup_from_path_service.rb +56 -0
- data/lib/expire/backup_list.rb +69 -0
- data/lib/expire/cli.rb +221 -0
- data/lib/expire/command.rb +122 -0
- data/lib/expire/commands/newest.rb +21 -0
- data/lib/expire/commands/oldest.rb +21 -0
- data/lib/expire/commands/purge.rb +23 -0
- data/lib/expire/commands/remove.rb +26 -0
- data/lib/expire/commands/rule_classes.rb +18 -0
- data/lib/expire/commands/rule_names.rb +20 -0
- data/lib/expire/commands/rule_option_names.rb +20 -0
- data/lib/expire/from_now_keep_adjective_for_rule_base.rb +38 -0
- data/lib/expire/from_now_keep_daily_for_rule.rb +7 -0
- data/lib/expire/from_now_keep_hourly_for_rule.rb +7 -0
- data/lib/expire/from_now_keep_monthly_for_rule.rb +7 -0
- data/lib/expire/from_now_keep_most_recent_for_rule.rb +41 -0
- data/lib/expire/from_now_keep_weekly_for_rule.rb +8 -0
- data/lib/expire/from_now_keep_yearly_for_rule.rb +8 -0
- data/lib/expire/from_range_value.rb +29 -0
- data/lib/expire/generate_backup_list_service.rb +45 -0
- data/lib/expire/invalid_path_error.rb +7 -0
- data/lib/expire/keep_adjective_for_rule_base.rb +34 -0
- data/lib/expire/keep_adjective_rule_base.rb +97 -0
- data/lib/expire/keep_daily_for_rule.rb +7 -0
- data/lib/expire/keep_daily_rule.rb +7 -0
- data/lib/expire/keep_hourly_for_rule.rb +7 -0
- data/lib/expire/keep_hourly_rule.rb +7 -0
- data/lib/expire/keep_monthly_for_rule.rb +7 -0
- data/lib/expire/keep_monthly_rule.rb +7 -0
- data/lib/expire/keep_most_recent_for_rule.rb +31 -0
- data/lib/expire/keep_most_recent_rule.rb +38 -0
- data/lib/expire/keep_weekly_for_rule.rb +8 -0
- data/lib/expire/keep_weekly_rule.rb +7 -0
- data/lib/expire/keep_yearly_for_rule.rb +7 -0
- data/lib/expire/keep_yearly_rule.rb +7 -0
- data/lib/expire/no_backups_error.rb +7 -0
- data/lib/expire/no_rules_error.rb +7 -0
- data/lib/expire/numerus_unit.rb +10 -0
- data/lib/expire/path_already_exists_error.rb +7 -0
- data/lib/expire/playground.rb +62 -0
- data/lib/expire/purge_service.rb +91 -0
- data/lib/expire/refine_all_and_none.rb +29 -0
- data/lib/expire/report_base.rb +23 -0
- data/lib/expire/report_enhanced.rb +14 -0
- data/lib/expire/report_expired.rb +10 -0
- data/lib/expire/report_kept.rb +10 -0
- data/lib/expire/report_null.rb +21 -0
- data/lib/expire/report_simple.rb +14 -0
- data/lib/expire/rule_base.rb +56 -0
- data/lib/expire/rule_list.rb +52 -0
- data/lib/expire/rules.rb +66 -0
- data/lib/expire/templates/newest/.gitkeep +1 -0
- data/lib/expire/templates/oldest/.gitkeep +1 -0
- data/lib/expire/templates/purge/.gitkeep +1 -0
- data/lib/expire/templates/remove/.gitkeep +1 -0
- data/lib/expire/templates/rule_classes/.gitkeep +1 -0
- data/lib/expire/templates/rule_names/.gitkeep +1 -0
- data/lib/expire/templates/rule_option_names/.gitkeep +1 -0
- data/lib/expire/unknown_rule_error.rb +10 -0
- data/lib/expire/version.rb +9 -0
- metadata +321 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Expire
|
6
|
+
# Helpers for TTY
|
7
|
+
class Command
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :command, :run
|
11
|
+
|
12
|
+
# Execute this command
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
def execute(*)
|
16
|
+
raise(
|
17
|
+
NotImplementedError,
|
18
|
+
"#{self.class}##{__method__} must be implemented"
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
# The external commands runner
|
23
|
+
#
|
24
|
+
# @see http://www.rubydoc.info/gems/tty-command
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def command(**options)
|
28
|
+
require 'tty-command'
|
29
|
+
TTY::Command.new(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# The cursor movement
|
33
|
+
#
|
34
|
+
# @see http://www.rubydoc.info/gems/tty-cursor
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def cursor
|
38
|
+
require 'tty-cursor'
|
39
|
+
TTY::Cursor
|
40
|
+
end
|
41
|
+
|
42
|
+
# Open a file or text in the user's preferred editor
|
43
|
+
#
|
44
|
+
# @see http://www.rubydoc.info/gems/tty-editor
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def editor
|
48
|
+
require 'tty-editor'
|
49
|
+
TTY::Editor
|
50
|
+
end
|
51
|
+
|
52
|
+
# File manipulation utility methods
|
53
|
+
#
|
54
|
+
# @see http://www.rubydoc.info/gems/tty-file
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def generator
|
58
|
+
require 'tty-file'
|
59
|
+
TTY::File
|
60
|
+
end
|
61
|
+
|
62
|
+
# Terminal output paging
|
63
|
+
#
|
64
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
def pager(**options)
|
68
|
+
require 'tty-pager'
|
69
|
+
TTY::Pager.new(options)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Terminal platform and OS properties
|
73
|
+
#
|
74
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def platform
|
78
|
+
require 'tty-platform'
|
79
|
+
TTY::Platform.new
|
80
|
+
end
|
81
|
+
|
82
|
+
# The interactive prompt
|
83
|
+
#
|
84
|
+
# @see http://www.rubydoc.info/gems/tty-prompt
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
def prompt(**options)
|
88
|
+
require 'tty-prompt'
|
89
|
+
TTY::Prompt.new(options)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get terminal screen properties
|
93
|
+
#
|
94
|
+
# @see http://www.rubydoc.info/gems/tty-screen
|
95
|
+
#
|
96
|
+
# @api public
|
97
|
+
def screen
|
98
|
+
require 'tty-screen'
|
99
|
+
TTY::Screen
|
100
|
+
end
|
101
|
+
|
102
|
+
# The unix which utility
|
103
|
+
#
|
104
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
105
|
+
#
|
106
|
+
# @api public
|
107
|
+
def which(*args)
|
108
|
+
require 'tty-which'
|
109
|
+
TTY::Which.which(*args)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Check if executable exists
|
113
|
+
#
|
114
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
115
|
+
#
|
116
|
+
# @api public
|
117
|
+
def exec_exist?(*args)
|
118
|
+
require 'tty-which'
|
119
|
+
TTY::Which.exist?(*args)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
module Expire
|
6
|
+
module Commands
|
7
|
+
# Print the newest backup
|
8
|
+
class Newest < Expire::Command
|
9
|
+
def initialize(path, options)
|
10
|
+
@path = path
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :path
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
output.puts Expire.newest(path).pathname
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
module Expire
|
6
|
+
module Commands
|
7
|
+
# Print the oldest backup
|
8
|
+
class Oldest < Expire::Command
|
9
|
+
def initialize(path, options)
|
10
|
+
@path = path
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :path
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
output.puts Expire.oldest(path).pathname
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
module Expire
|
6
|
+
module Commands
|
7
|
+
# Purge expired backups
|
8
|
+
class Purge < Expire::Command
|
9
|
+
def initialize(path, options)
|
10
|
+
@path = path
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :path, :options
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
Expire.purge(path, options)
|
18
|
+
rescue StandardError => _e
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
module Expire
|
6
|
+
module Commands
|
7
|
+
# Remove files and directories
|
8
|
+
class Remove < Expire::Command
|
9
|
+
def initialize(path:)
|
10
|
+
@path = path
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
def execute(output: $stdout)
|
16
|
+
begin
|
17
|
+
Expire.remove(path)
|
18
|
+
rescue Errno::ENOENT => e
|
19
|
+
output.puts "can't remove #{path}: #{e}"
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
output.puts "removed #{path}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
module Expire
|
6
|
+
module Commands
|
7
|
+
# Print all rule-classes
|
8
|
+
class RuleClasses < Expire::Command
|
9
|
+
def initialize(_); end
|
10
|
+
|
11
|
+
def execute(output: $stdout)
|
12
|
+
Expire.rule_classes.each do |rule_class|
|
13
|
+
output.puts rule_class
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
module Expire
|
6
|
+
module Commands
|
7
|
+
# Print the names of all rules
|
8
|
+
class RuleNames < Expire::Command
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(input: $stdin, output: $stdout)
|
14
|
+
Expire.rule_names.each do |rule_name|
|
15
|
+
output.puts rule_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
module Expire
|
6
|
+
module Commands
|
7
|
+
# Print the option names of all rules
|
8
|
+
class RuleOptionNames < Expire::Command
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(input: $stdin, output: $stdout)
|
14
|
+
Expire.rule_option_names.each do |option_name|
|
15
|
+
output.puts option_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Expire
|
4
|
+
# Base class for from-now rules
|
5
|
+
class FromNowKeepAdjectiveForRuleBase < KeepAdjectiveRuleBase
|
6
|
+
extend FromRangeValue
|
7
|
+
include NumerusUnit
|
8
|
+
|
9
|
+
PRIMARY_RANK = 40
|
10
|
+
|
11
|
+
def self.primary_rank
|
12
|
+
PRIMARY_RANK
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(unit:, **args)
|
16
|
+
super(**args)
|
17
|
+
|
18
|
+
@unit = unit
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :unit
|
22
|
+
|
23
|
+
def apply(backups, reference_datetime)
|
24
|
+
minimal_datetime = reference_datetime - amount.send(unit)
|
25
|
+
kept = backups.one_per(spacing).not_older_than(minimal_datetime)
|
26
|
+
|
27
|
+
kept.each { |backup| backup.add_reason_to_keep(reason_to_keep) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def primary_rank
|
31
|
+
self.class.primary_rank
|
32
|
+
end
|
33
|
+
|
34
|
+
def reason_to_keep
|
35
|
+
"from now keep all backups for #{amount} #{numerus_unit}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Expire
|
4
|
+
# Keep the most recent backups from now for a certain
|
5
|
+
# period of time.
|
6
|
+
class FromNowKeepMostRecentForRule < RuleBase
|
7
|
+
extend FromRangeValue
|
8
|
+
include NumerusUnit
|
9
|
+
|
10
|
+
RULE_RANK = 12
|
11
|
+
|
12
|
+
def self.rank
|
13
|
+
RULE_RANK
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(unit:, **args)
|
17
|
+
super(**args)
|
18
|
+
|
19
|
+
@unit = unit
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :unit
|
23
|
+
|
24
|
+
def apply(backups, datetime_now)
|
25
|
+
reference_datetime = datetime_now - amount.send(unit)
|
26
|
+
kept = backups.not_older_than(reference_datetime)
|
27
|
+
|
28
|
+
kept.each do |backup|
|
29
|
+
backup.add_reason_to_keep(reason_to_keep)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def rank
|
34
|
+
self.class.rank
|
35
|
+
end
|
36
|
+
|
37
|
+
def reason_to_keep
|
38
|
+
"from now keep most recent backups for #{amount} #{numerus_unit}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Expire
|
4
|
+
# Provide a pseudo constructor for rules that require a time range
|
5
|
+
module FromRangeValue
|
6
|
+
using RefineAllAndNone
|
7
|
+
|
8
|
+
FROM_VALUE_REGEX = /
|
9
|
+
\A
|
10
|
+
(([0-9](_[0-9]+){0,})+)
|
11
|
+
(\s+|\.)
|
12
|
+
(hour|day|week|month|year)s?
|
13
|
+
\z
|
14
|
+
/x.freeze
|
15
|
+
|
16
|
+
def from_value(string, **args)
|
17
|
+
# return new(args.merge({ amount: 0, unit: nil })) if string.none?
|
18
|
+
return new(**args.merge(amount: 0, unit: nil)) if string.none?
|
19
|
+
|
20
|
+
stripped_down = string.strip.downcase
|
21
|
+
match = stripped_down.match FROM_VALUE_REGEX
|
22
|
+
raise ArgumentError, "#{string} is not a valid range value" unless match
|
23
|
+
|
24
|
+
amount = Integer(match[1])
|
25
|
+
unit = match[5]
|
26
|
+
new(**args.merge(amount: amount, unit: unit))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|