expire 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.reek.yml +23 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +45 -0
  6. data/.simplecov +3 -0
  7. data/.travis.yml +7 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +152 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +551 -0
  12. data/Rakefile +11 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/example_rules/bad_rules.yml +2 -0
  16. data/example_rules/good_rules.yml +1 -0
  17. data/exe/expire +7 -0
  18. data/expire.gemspec +54 -0
  19. data/lib/expire.rb +51 -0
  20. data/lib/expire/all_backups_expired_error.rb +7 -0
  21. data/lib/expire/backup.rb +74 -0
  22. data/lib/expire/backup_from_path_service.rb +56 -0
  23. data/lib/expire/backup_list.rb +69 -0
  24. data/lib/expire/cli.rb +221 -0
  25. data/lib/expire/command.rb +122 -0
  26. data/lib/expire/commands/newest.rb +21 -0
  27. data/lib/expire/commands/oldest.rb +21 -0
  28. data/lib/expire/commands/purge.rb +23 -0
  29. data/lib/expire/commands/remove.rb +26 -0
  30. data/lib/expire/commands/rule_classes.rb +18 -0
  31. data/lib/expire/commands/rule_names.rb +20 -0
  32. data/lib/expire/commands/rule_option_names.rb +20 -0
  33. data/lib/expire/from_now_keep_adjective_for_rule_base.rb +38 -0
  34. data/lib/expire/from_now_keep_daily_for_rule.rb +7 -0
  35. data/lib/expire/from_now_keep_hourly_for_rule.rb +7 -0
  36. data/lib/expire/from_now_keep_monthly_for_rule.rb +7 -0
  37. data/lib/expire/from_now_keep_most_recent_for_rule.rb +41 -0
  38. data/lib/expire/from_now_keep_weekly_for_rule.rb +8 -0
  39. data/lib/expire/from_now_keep_yearly_for_rule.rb +8 -0
  40. data/lib/expire/from_range_value.rb +29 -0
  41. data/lib/expire/generate_backup_list_service.rb +45 -0
  42. data/lib/expire/invalid_path_error.rb +7 -0
  43. data/lib/expire/keep_adjective_for_rule_base.rb +34 -0
  44. data/lib/expire/keep_adjective_rule_base.rb +97 -0
  45. data/lib/expire/keep_daily_for_rule.rb +7 -0
  46. data/lib/expire/keep_daily_rule.rb +7 -0
  47. data/lib/expire/keep_hourly_for_rule.rb +7 -0
  48. data/lib/expire/keep_hourly_rule.rb +7 -0
  49. data/lib/expire/keep_monthly_for_rule.rb +7 -0
  50. data/lib/expire/keep_monthly_rule.rb +7 -0
  51. data/lib/expire/keep_most_recent_for_rule.rb +31 -0
  52. data/lib/expire/keep_most_recent_rule.rb +38 -0
  53. data/lib/expire/keep_weekly_for_rule.rb +8 -0
  54. data/lib/expire/keep_weekly_rule.rb +7 -0
  55. data/lib/expire/keep_yearly_for_rule.rb +7 -0
  56. data/lib/expire/keep_yearly_rule.rb +7 -0
  57. data/lib/expire/no_backups_error.rb +7 -0
  58. data/lib/expire/no_rules_error.rb +7 -0
  59. data/lib/expire/numerus_unit.rb +10 -0
  60. data/lib/expire/path_already_exists_error.rb +7 -0
  61. data/lib/expire/playground.rb +62 -0
  62. data/lib/expire/purge_service.rb +91 -0
  63. data/lib/expire/refine_all_and_none.rb +29 -0
  64. data/lib/expire/report_base.rb +23 -0
  65. data/lib/expire/report_enhanced.rb +14 -0
  66. data/lib/expire/report_expired.rb +10 -0
  67. data/lib/expire/report_kept.rb +10 -0
  68. data/lib/expire/report_null.rb +21 -0
  69. data/lib/expire/report_simple.rb +14 -0
  70. data/lib/expire/rule_base.rb +56 -0
  71. data/lib/expire/rule_list.rb +52 -0
  72. data/lib/expire/rules.rb +66 -0
  73. data/lib/expire/templates/newest/.gitkeep +1 -0
  74. data/lib/expire/templates/oldest/.gitkeep +1 -0
  75. data/lib/expire/templates/purge/.gitkeep +1 -0
  76. data/lib/expire/templates/remove/.gitkeep +1 -0
  77. data/lib/expire/templates/rule_classes/.gitkeep +1 -0
  78. data/lib/expire/templates/rule_names/.gitkeep +1 -0
  79. data/lib/expire/templates/rule_option_names/.gitkeep +1 -0
  80. data/lib/expire/unknown_rule_error.rb +10 -0
  81. data/lib/expire/version.rb +9 -0
  82. 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expire
4
+ # Keea a daily backup from now for a certain period of time
5
+ class FromNowKeepDailyForRule < FromNowKeepAdjectiveForRuleBase
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expire
4
+ # Keep one backup per hour from now for a certain period of time
5
+ class FromNowKeepHourlyForRule < FromNowKeepAdjectiveForRuleBase
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expire
4
+ # Keep one backup per month from now for a certain period of time
5
+ class FromNowKeepMonthlyForRule < FromNowKeepAdjectiveForRuleBase
6
+ end
7
+ 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expire
4
+ # Keep one backup per week from now for a certain
5
+ # period of time
6
+ class FromNowKeepWeeklyForRule < FromNowKeepAdjectiveForRuleBase
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expire
4
+ # Keep one backup per year from now for
5
+ # a ceretain period of time.
6
+ class FromNowKeepYearlyForRule < FromNowKeepAdjectiveForRuleBase
7
+ end
8
+ 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