maid 0.10.0.pre.alpha.3 → 0.11.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +14 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.github/workflows/coverage.yml +3 -3
  6. data/.github/workflows/lint.yml +9 -1
  7. data/.github/workflows/merge-gatekeeper.yml +20 -0
  8. data/.github/workflows/release.yml +13 -20
  9. data/.github/workflows/stale.yml +25 -0
  10. data/.github/workflows/test.yml +8 -7
  11. data/.gitignore +1 -1
  12. data/.release-please-manifest.json +1 -1
  13. data/.rubocop.yml +3 -1
  14. data/.rubocop_todo.yml +105 -107
  15. data/.ruby-version +1 -1
  16. data/CHANGELOG.md +23 -0
  17. data/Dockerfile +13 -0
  18. data/Gemfile.lock +226 -0
  19. data/Guardfile +2 -0
  20. data/README.md +82 -49
  21. data/Rakefile +9 -0
  22. data/SECURITY.md +29 -0
  23. data/fixtures/files/test_rules.rb +3 -0
  24. data/fixtures/vcr_cassettes/Dependency_expectations/Geocoder/translates_latitude_and_longitude_into_street_addresses.yml +42 -0
  25. data/fixtures/vcr_cassettes/Maid_Tools/_location_city/given_a_JPEG_image/reports_the_known_location.yml +42 -0
  26. data/lib/maid/logger/logger.rb +63 -0
  27. data/lib/maid/maid.rb +6 -22
  28. data/lib/maid/repeat.rb +2 -2
  29. data/lib/maid/rule.rb +2 -2
  30. data/lib/maid/rule_container.rb +2 -2
  31. data/lib/maid/tools.rb +3 -3
  32. data/lib/maid/trash_migration.rb +2 -0
  33. data/lib/maid/version.rb +1 -1
  34. data/lib/maid/watch.rb +2 -2
  35. data/lib/maid.rb +3 -2
  36. data/maid.gemspec +14 -9
  37. data/release-please-config.json +18 -0
  38. data/script/docker-test +7 -0
  39. data/spec/dependency_spec.rb +1 -1
  40. data/spec/fakefs_helper.rb +13 -0
  41. data/spec/lib/maid/logger/logger_spec.rb +64 -0
  42. data/spec/lib/maid/maid_spec.rb +113 -103
  43. data/spec/lib/maid/rake/single_rule_spec.rb +1 -1
  44. data/spec/lib/maid/tools_spec.rb +384 -225
  45. data/spec/lib/maid/trash_migration_spec.rb +7 -5
  46. data/spec/spec_helper.rb +17 -1
  47. metadata +124 -44
  48. data/Vagrantfile +0 -14
  49. data/script/vagrant-provision +0 -43
  50. data/script/vagrant-test +0 -7
  51. data/script/vagrant-test-all +0 -34
data/lib/maid/maid.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'fileutils'
2
- require 'logger'
3
2
  require 'xdg'
4
3
 
5
4
  # Maid cleans up according to the given rules, logging what it does.
@@ -9,13 +8,8 @@ require 'xdg'
9
8
  class Maid::Maid
10
9
  include Maid::RuleContainer
11
10
  DEFAULTS = {
12
- progname: 'Maid',
13
-
14
11
  log_device: File.expand_path('~/.maid/maid.log'),
15
- # We don't want the log files to grow without check, but 50 MB doesn't seem
16
- # too bad. (We're going with a larger size just for safety right now.)
17
- log_shift_age: 5,
18
- log_shift_size: 10 * 1_048_576, # 10 * 1 MB
12
+ logger: ::Maid::Logger,
19
13
 
20
14
  rules_path: File.expand_path('~/.maid/rules.rb'),
21
15
  file_options: { noop: false }, # for `FileUtils`
@@ -34,17 +28,7 @@ class Maid::Maid
34
28
  def initialize(options = {})
35
29
  options = DEFAULTS.merge(options.reject { |_k, v| v.nil? })
36
30
 
37
- # TODO: Refactor and simplify (see also https://github.com/benjaminoakes/maid/pull/48#discussion_r1683942)
38
- @logger = if options[:logger]
39
- options[:logger]
40
- else
41
- @log_device = options[:log_device]
42
- FileUtils.mkdir_p(File.dirname(@log_device)) unless @log_device.is_a?(IO)
43
- @logger = Logger.new(@log_device, options[:log_shift_age], options[:log_shift_size])
44
- end
45
-
46
- @logger.progname = options[:progname]
47
- @logger.formatter = options[:log_formatter] if options[:log_formatter]
31
+ @logger = options[:logger].new(device: options[:log_device])
48
32
 
49
33
  @rules_path = options[:rules_path]
50
34
  @trash_path = options[:trash_path] || default_trash_path
@@ -87,7 +71,7 @@ class Maid::Maid
87
71
  warn e.message
88
72
  end
89
73
 
90
- def watch(path, options = {}, &block)
74
+ def watch(path, options = {}, &)
91
75
  full_path = File.expand_path(path)
92
76
 
93
77
  unless File.directory?(full_path)
@@ -97,11 +81,11 @@ class Maid::Maid
97
81
  raise message
98
82
  end
99
83
 
100
- @watches << ::Maid::Watch.new(self, path, options, &block)
84
+ @watches << ::Maid::Watch.new(self, path, options, &)
101
85
  end
102
86
 
103
- def repeat(timestring, options = {}, &block)
104
- @repeats << ::Maid::Repeat.new(self, timestring, options, &block)
87
+ def repeat(timestring, options = {}, &)
88
+ @repeats << ::Maid::Repeat.new(self, timestring, options, &)
105
89
  end
106
90
 
107
91
  # Daemonizes the process by starting all watches and repeats and joining
data/lib/maid/repeat.rb CHANGED
@@ -4,13 +4,13 @@ class Maid::Repeat
4
4
 
5
5
  attr_reader :timestring, :scheduler, :logger
6
6
 
7
- def initialize(maid, timestring, options = {}, &block)
7
+ def initialize(maid, timestring, options = {}, &)
8
8
  @maid = maid
9
9
  @logger = maid.logger # TODO: Maybe it's better to create seperate loggers?
10
10
  @scheduler = Rufus::Scheduler.singleton
11
11
  @timestring = timestring
12
12
  @options = options
13
- initialize_rules(&block)
13
+ initialize_rules(&)
14
14
  end
15
15
 
16
16
  def run
data/lib/maid/rule.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  class Maid::Rule < Struct.new(:description, :instructions, :maid)
2
2
  # Follow the instructions of the rule.
3
- def follow(*args)
4
- maid.instance_exec(*args, &instructions)
3
+ def follow(*)
4
+ maid.instance_exec(*, &instructions)
5
5
  end
6
6
  end
@@ -3,9 +3,9 @@ module Maid::RuleContainer
3
3
  attr_reader :rules
4
4
 
5
5
  # initialize_rules
6
- def initialize_rules(&block)
6
+ def initialize_rules(&)
7
7
  @rules ||= []
8
- instance_exec(&block)
8
+ instance_exec(&)
9
9
  end
10
10
 
11
11
  # Register a rule with a description and instructions (lambda function).
data/lib/maid/tools.rb CHANGED
@@ -328,7 +328,7 @@ module Maid::Tools
328
328
  def mkdir(path, options = {})
329
329
  path = expand(path)
330
330
  log("mkdir -p #{sh_escape(path)}")
331
- FileUtils.mkdir_p(path, **@file_options.merge(options))
331
+ FileUtils.mkdir_p(path, **@file_options, **options)
332
332
  path
333
333
  end
334
334
 
@@ -727,7 +727,7 @@ module Maid::Tools
727
727
  # where_content_type(dir('~/Downloads/*'), 'public.image')
728
728
  def where_content_type(paths, filter_types)
729
729
  filter_types = Array(filter_types)
730
- Array(paths).select { |p| !(filter_types & content_types(p)).empty? }
730
+ Array(paths).select { |p| filter_types.intersect?(content_types(p)) }
731
731
  end
732
732
 
733
733
  # Test whether a directory is either empty, or contains only empty
@@ -739,7 +739,7 @@ module Maid::Tools
739
739
  # trash('~/Downloads/foo')
740
740
  # end
741
741
  def tree_empty?(root)
742
- return nil if File.file?(root)
742
+ return false if File.file?(root)
743
743
  return true if Dir.glob(root + '/*').length == 0
744
744
 
745
745
  ignore = []
@@ -20,6 +20,8 @@ module Maid
20
20
  end
21
21
 
22
22
  def perform
23
+ # FIXME: This is tightly coupled, we can't pass a custom log device
24
+ # etc.
23
25
  maid = ::Maid::Maid.new(trash_path: correct_trash)
24
26
  # Use local variable so it's available in the closure used by `instance_eval`
25
27
  path = incorrect_trash
data/lib/maid/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Maid
4
- VERSION = '0.10.0-alpha.3'
4
+ VERSION = '0.11.0'
5
5
  SUMMARY = 'Be lazy. Let Maid clean up after you, based on rules you define. ' \
6
6
  'Think of it as "Hazel for hackers".'
7
7
  end
data/lib/maid/watch.rb CHANGED
@@ -5,7 +5,7 @@ class Maid::Watch
5
5
 
6
6
  attr_reader :path, :listener, :logger
7
7
 
8
- def initialize(maid, path, options = {}, &block)
8
+ def initialize(maid, path, options = {}, &)
9
9
  @maid = maid
10
10
 
11
11
  if options.nil? || options.empty?
@@ -19,7 +19,7 @@ class Maid::Watch
19
19
 
20
20
  @logger = maid.logger # TODO: Maybe it's better to create seperate loggers?
21
21
  @path = File.expand_path(path)
22
- initialize_rules(&block)
22
+ initialize_rules(&)
23
23
  end
24
24
 
25
25
  def run
data/lib/maid.rb CHANGED
@@ -4,6 +4,7 @@ Deprecated.set_action(:warn)
4
4
 
5
5
  # Must be in this order:
6
6
  require 'maid/version'
7
+ require 'maid/logger/logger'
7
8
  require 'maid/downloading'
8
9
  require 'maid/tools'
9
10
  require 'maid/rule_container'
@@ -32,8 +33,8 @@ module Maid
32
33
  end
33
34
 
34
35
  # Define rules for the Maid instance.
35
- def rules(&block)
36
- @instance.instance_exec(&block)
36
+ def rules(&)
37
+ @instance.instance_exec(&)
37
38
  end
38
39
  end
39
40
  end
data/maid.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
23
23
 
24
24
  s.rubyforge_project = 'maid'
25
25
 
26
- s.required_ruby_version = '>= 2.7.0'
26
+ s.required_ruby_version = '>= 3.2.0'
27
27
 
28
28
  # Strategy: if possible, use ranges (so there are fewer chances of version conflicts)
29
29
  s.add_dependency('deprecated', '~> 3.0.0')
@@ -46,21 +46,26 @@ Gem::Specification.new do |s|
46
46
 
47
47
  # Strategy: specific versions (since they're just for development)
48
48
  s.add_development_dependency('fakefs', '~> 2.4.0')
49
- s.add_development_dependency('fuubar')
49
+ s.add_development_dependency('fuubar', '~> 2.5.1')
50
50
  s.add_development_dependency('guard', '~> 2.18.0')
51
51
  s.add_development_dependency('guard-bundler', '~> 3.0.1')
52
52
  s.add_development_dependency('guard-rspec', '~> 4.7.3')
53
- s.add_development_dependency('guard-rubocop')
54
- s.add_development_dependency('pry-byebug')
53
+ s.add_development_dependency('guard-rubocop', '~> 1.5.0')
54
+ s.add_development_dependency('irb', '~> 1.15.1')
55
+ s.add_development_dependency('ostruct', '~> 0.6.1')
56
+ s.add_development_dependency('pry-byebug', '~> 3.10.1')
55
57
  s.add_development_dependency('rake', '~> 13.0.6')
56
- s.add_development_dependency('rake-notes')
58
+ s.add_development_dependency('rake-notes', '~> 0.2.2')
57
59
  s.add_development_dependency('redcarpet', '~> 3.6.0') # Soft dependency of `yard`
60
+ s.add_development_dependency('reline', '~> 0.6.0')
58
61
  s.add_development_dependency('rspec', '~> 3.12.0')
59
- s.add_development_dependency('rubocop')
60
- s.add_development_dependency('rubocop-rake')
61
- s.add_development_dependency('rubocop-rspec')
62
- s.add_development_dependency('simplecov')
62
+ s.add_development_dependency('rubocop', '~> 1.50')
63
+ s.add_development_dependency('rubocop-rake', '~> 0.6.0')
64
+ s.add_development_dependency('rubocop-rspec', '~> 3.5.0')
65
+ s.add_development_dependency('simplecov', '~> 0.22.0')
63
66
  s.add_development_dependency('timecop', '~> 0.9.6')
67
+ s.add_development_dependency('vcr', '~> 6.1.0')
68
+ s.add_development_dependency('webmock', '~> 3.18.1')
64
69
  s.add_development_dependency('yard', '>= 0.9.11')
65
70
 
66
71
  # In Vagrant, polling won't cross over the OS boundary if you develop in the host OS but run your tests in the
@@ -0,0 +1,18 @@
1
+ {
2
+ "always-update": true,
3
+ "bootstrap-sha": "d4901ad446c8a614ef80472ed5e6f1ba8c702ed5",
4
+ "bump-minor-pre-major": true,
5
+ "bump-patch-for-minor-pre-major": true,
6
+ "commit-search-depth": 500,
7
+ "group-pull-request-title-pattern": "chore: release ${version}",
8
+ "release-search-depth": 400,
9
+ "release-type": "ruby",
10
+ "sequential-calls": false,
11
+ "packages": {
12
+ ".": {
13
+ "release-type": "ruby",
14
+ "package-name": "maid",
15
+ "version-file": "lib/maid/version.rb"
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Run RSpec in a docker container, to avoid mistakenly writing to the live
4
+ # filesystem while developing.
5
+ docker rm -f maid-dev
6
+ docker build -t maid-dev .
7
+ docker run -it --rm --name maid-dev --mount type=bind,src="$(pwd)",target=/usr/src/app maid-dev
@@ -95,7 +95,7 @@ describe 'Dependency expectations' do
95
95
  end
96
96
  end
97
97
 
98
- describe Geocoder do
98
+ describe Geocoder, vcr: { record: :new_episodes } do
99
99
  it 'translates latitude and longitude into street addresses' do
100
100
  city = Geocoder.search('-33.85608611111111,151.219925').map { |location| location.city }.uniq.compact
101
101
  expect(city).to eq(['Sydney'])
@@ -0,0 +1,13 @@
1
+ # FakeFS is missing #flock, see https://github.com/fakefs/fakefs/issues/433 and
2
+ # https://github.com/whitesmith/rubycritic/commit/57edc6244a9ebea8078a9c1dba32204ee7d1d895
3
+
4
+ # NOTE: This avoid NotImplementedError on File.flock, but causes a myriad of
5
+ # other issues since it doesn't really provide any locking in practice.
6
+ # If required, include this file in spec_helper.rb to monkey-patch FakeFS.
7
+ module FakeFS
8
+ class File < StringIO
9
+ def flock(*)
10
+ true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+ require 'maid/logger/logger'
3
+
4
+ module Maid
5
+ # FakeFS not required because we're writing the log to /tmp/ and deleting it
6
+ # after the test.
7
+ describe Logger do
8
+ let(:logfile) { '/tmp/maid/test.log' }
9
+ let(:logger) { described_class.new(device: logfile) }
10
+
11
+ after { FileUtils.rm('/tmp/maid/test.log', force: true) }
12
+
13
+ levels = %i[debug info warn error fatal unknown]
14
+ levels.each do |level|
15
+ it "responds to #{level}" do
16
+ expect(logger).to respond_to(level)
17
+ end
18
+ end
19
+
20
+ context 'with a filename' do
21
+ before { logger.info('test message') }
22
+
23
+ it 'creates that file' do
24
+ expect(File.exist?(logfile)).to be true
25
+ end
26
+
27
+ it 'sets the default progname' do
28
+ expect(File.read(logfile)).to match(/Maid: /)
29
+ end
30
+
31
+ context 'with the ::Logger::DEBUG log level' do
32
+ let(:logger) { described_class.new(device: logfile, level: ::Logger::DEBUG) }
33
+
34
+ levels.each do |level|
35
+ it "logs #{level} messages" do
36
+ logger.send(level, "#{level} test message")
37
+
38
+ expect(File.read(logfile)).to match("#{level} test message")
39
+ end
40
+ end
41
+ end
42
+
43
+ it 'works with a string' do
44
+ logger.info('test message')
45
+
46
+ expect(File.read(logfile)).to match 'test message'
47
+ end
48
+
49
+ it 'works with a custom progname and a block' do
50
+ logger.info('TestProgname') { 'test message' }
51
+
52
+ expect(File.read(logfile)).to match 'TestProgname: test message'
53
+ end
54
+ end
55
+
56
+ context 'with an IO' do
57
+ let(:logger) { described_class.new(device: StringIO.new) }
58
+
59
+ it 'is happy' do
60
+ expect { logger.info('test message') }.not_to raise_error
61
+ end
62
+ end
63
+ end
64
+ end