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.
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