maid 0.10.0.pre.alpha.1 → 0.10.0.pre.alpha.2
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 +4 -4
- data/.github/workflows/coverage.yml +29 -0
- data/.github/workflows/lint.yml +24 -0
- data/.github/workflows/release.yml +5 -2
- data/.gitignore +1 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +35 -0
- data/.rubocop_todo.yml +372 -0
- data/CHANGELOG.md +8 -1
- data/Guardfile +31 -4
- data/README.md +3 -3
- data/Rakefile +1 -1
- data/Vagrantfile +2 -2
- data/lib/maid/app.rb +48 -51
- data/lib/maid/maid.rb +38 -38
- data/lib/maid/numeric_extensions.rb +26 -25
- data/lib/maid/platform.rb +1 -1
- data/lib/maid/rake/task.rb +1 -1
- data/lib/maid/repeat.rb +8 -8
- data/lib/maid/rule_container.rb +3 -3
- data/lib/maid/rules.sample.rb +17 -17
- data/lib/maid/tools.rb +142 -127
- data/lib/maid/trash_migration.rb +4 -4
- data/lib/maid/user_agent.rb +2 -2
- data/lib/maid/version.rb +5 -2
- data/lib/maid/watch.rb +10 -12
- data/maid.gemspec +29 -22
- data/spec/dependency_spec.rb +9 -8
- data/spec/lib/maid/app_spec.rb +15 -7
- data/spec/lib/maid/maid_spec.rb +63 -41
- data/spec/lib/maid/numeric_extensions_spec.rb +1 -1
- data/spec/lib/maid/rake/single_rule_spec.rb +4 -5
- data/spec/lib/maid/rake/task_spec.rb +3 -5
- data/spec/lib/maid/rule_spec.rb +1 -1
- data/spec/lib/maid/tools_spec.rb +87 -85
- data/spec/lib/maid/trash_migration_spec.rb +7 -6
- data/spec/lib/maid_spec.rb +1 -1
- data/spec/spec_helper.rb +18 -3
- metadata +161 -58
    
        data/lib/maid/tools.rb
    CHANGED
    
    | @@ -26,7 +26,8 @@ module Maid::Tools | |
| 26 26 |  | 
| 27 27 | 
             
              # Move `sources` to a `destination` directory.
         | 
| 28 28 | 
             
              #
         | 
| 29 | 
            -
              # Movement is only allowed to directories that already exist.  If your | 
| 29 | 
            +
              # Movement is only allowed to directories that already exist.  If your
         | 
| 30 | 
            +
              # intention is to rename, see the `rename` method.
         | 
| 30 31 | 
             
              #
         | 
| 31 32 | 
             
              # ## Examples
         | 
| 32 33 | 
             
              #
         | 
| @@ -43,12 +44,13 @@ module Maid::Tools | |
| 43 44 |  | 
| 44 45 | 
             
                if File.directory?(expanded_destination)
         | 
| 45 46 | 
             
                  expand_all(sources).each do |source|
         | 
| 46 | 
            -
                    log("move #{ | 
| 47 | 
            -
                    FileUtils.mv(source, expanded_destination,  | 
| 47 | 
            +
                    log("move #{sh_escape(source)} #{sh_escape(expanded_destination)}")
         | 
| 48 | 
            +
                    FileUtils.mv(source, expanded_destination, **@file_options)
         | 
| 48 49 | 
             
                  end
         | 
| 49 50 | 
             
                else
         | 
| 50 51 | 
             
                  # Unix `mv` warns about the target not being a directory with multiple sources.  Maid checks the same.
         | 
| 51 | 
            -
                  warn("skipping move because #{ | 
| 52 | 
            +
                  warn("skipping move because #{sh_escape(expanded_destination)} is not a" \
         | 
| 53 | 
            +
                       "directory (use 'mkdir' to create first, or use 'rename')")
         | 
| 52 54 | 
             
                end
         | 
| 53 55 | 
             
              end
         | 
| 54 56 |  | 
| @@ -56,7 +58,8 @@ module Maid::Tools | |
| 56 58 | 
             
              #
         | 
| 57 59 | 
             
              # Any directories needed in order to complete the rename are made automatically.
         | 
| 58 60 | 
             
              #
         | 
| 59 | 
            -
              # Overwriting is not allowed; it logs a warning.  If overwriting is desired, | 
| 61 | 
            +
              # Overwriting is not allowed; it logs a warning.  If overwriting is desired,
         | 
| 62 | 
            +
              # use `remove` to delete the file first, then use `rename`.
         | 
| 60 63 | 
             
              #
         | 
| 61 64 | 
             
              # ## Examples
         | 
| 62 65 | 
             
              #
         | 
| @@ -78,10 +81,10 @@ module Maid::Tools | |
| 78 81 | 
             
                mkdir(File.dirname(destination))
         | 
| 79 82 |  | 
| 80 83 | 
             
                if File.exist?(destination)
         | 
| 81 | 
            -
                  warn("skipping rename of #{ | 
| 84 | 
            +
                  warn("skipping rename of #{sh_escape(source)} to #{sh_escape(destination)} because it would overwrite")
         | 
| 82 85 | 
             
                else
         | 
| 83 | 
            -
                  log("rename #{ | 
| 84 | 
            -
                  FileUtils.mv(source, destination,  | 
| 86 | 
            +
                  log("rename #{sh_escape(source)} #{sh_escape(destination)}")
         | 
| 87 | 
            +
                  FileUtils.mv(source, destination, **@file_options)
         | 
| 85 88 | 
             
                end
         | 
| 86 89 | 
             
              end
         | 
| 87 90 |  | 
| @@ -115,26 +118,27 @@ module Maid::Tools | |
| 115 118 | 
             
              def trash(paths, options = {})
         | 
| 116 119 | 
             
                # ## Implementation Notes
         | 
| 117 120 | 
             
                #
         | 
| 118 | 
            -
                # Trashing files correctly is surprisingly hard.  What Maid ends up doing | 
| 119 | 
            -
                # solutions:  moving the file.
         | 
| 121 | 
            +
                # Trashing files correctly is surprisingly hard.  What Maid ends up doing
         | 
| 122 | 
            +
                # is one the easiest, most foolproof solutions:  moving the file.
         | 
| 120 123 | 
             
                #
         | 
| 121 | 
            -
                # Unfortunately, that means it's not possile to restore files automatically | 
| 122 | 
            -
                # of the file is lost.
         | 
| 124 | 
            +
                # Unfortunately, that means it's not possile to restore files automatically
         | 
| 125 | 
            +
                # in OSX or Ubuntu.  The previous location of the file is lost.
         | 
| 123 126 | 
             
                #
         | 
| 124 | 
            -
                # OSX support depends on AppleScript or would require a not-yet-written C | 
| 125 | 
            -
                #  | 
| 126 | 
            -
                #  | 
| 127 | 
            +
                # OSX support depends on AppleScript or would require a not-yet-written C
         | 
| 128 | 
            +
                # extension to interface with the OS.  The AppleScript solution is less
         | 
| 129 | 
            +
                # than ideal: the user has to be logged in, Finder has to be running, and
         | 
| 130 | 
            +
                # it makes the "trash can sound" every time a file is moved.
         | 
| 127 131 | 
             
                #
         | 
| 128 | 
            -
                # Ubuntu makes it easy to implement, and there's a Python library for doing | 
| 129 | 
            -
                # not a Ruby equivalent yet.
         | 
| 132 | 
            +
                # Ubuntu makes it easy to implement, and there's a Python library for doing
         | 
| 133 | 
            +
                # so (see `trash-cli`).  However, there's not a Ruby equivalent yet.
         | 
| 130 134 |  | 
| 131 135 | 
             
                expand_all(paths).each do |path|
         | 
| 132 136 | 
             
                  target = File.join(@trash_path, File.basename(path))
         | 
| 133 | 
            -
                  safe_trash_path = File.join(@trash_path, "#{ | 
| 137 | 
            +
                  safe_trash_path = File.join(@trash_path, "#{File.basename(path)} #{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}")
         | 
| 134 138 |  | 
| 135 139 | 
             
                  if options[:remove_over] &&
         | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 140 | 
            +
                     File.exist?(path) &&
         | 
| 141 | 
            +
                     disk_usage(path) > options[:remove_over]
         | 
| 138 142 | 
             
                    remove(path)
         | 
| 139 143 | 
             
                  end
         | 
| 140 144 |  | 
| @@ -150,8 +154,10 @@ module Maid::Tools | |
| 150 154 |  | 
| 151 155 | 
             
              # Copy from `sources` to `destination`
         | 
| 152 156 | 
             
              #
         | 
| 153 | 
            -
              # The path is not copied if a file already exists at the destination with the | 
| 154 | 
            -
              #  | 
| 157 | 
            +
              # The path is not copied if a file already exists at the destination with the
         | 
| 158 | 
            +
              # same name.  A warning is logged instead. Note: Similar functionality is
         | 
| 159 | 
            +
              # provided by the sync tool, but this requires installation of the `rsync`
         | 
| 160 | 
            +
              # binary
         | 
| 155 161 | 
             
              # ## Examples
         | 
| 156 162 | 
             
              #
         | 
| 157 163 | 
             
              # Single path:
         | 
| @@ -166,20 +172,23 @@ module Maid::Tools | |
| 166 172 | 
             
                destination = expand(destination)
         | 
| 167 173 |  | 
| 168 174 | 
             
                expand_all(sources).each do |source|
         | 
| 169 | 
            -
             | 
| 175 | 
            +
                  target = File.join(destination, File.basename(source))
         | 
| 170 176 |  | 
| 171 | 
            -
                   | 
| 172 | 
            -
                     | 
| 173 | 
            -
                    FileUtils.cp(source, destination, @file_options)
         | 
| 177 | 
            +
                  if File.exist?(target)
         | 
| 178 | 
            +
                    warn("skipping copy because #{sh_escape(source)} because #{sh_escape(target)} already exists")
         | 
| 174 179 | 
             
                  else
         | 
| 175 | 
            -
                     | 
| 180 | 
            +
                    log("cp #{sh_escape(source)} #{sh_escape(destination)}")
         | 
| 181 | 
            +
                    FileUtils.cp(source, destination, **@file_options)
         | 
| 176 182 | 
             
                  end
         | 
| 177 183 | 
             
                end
         | 
| 178 184 | 
             
              end
         | 
| 179 185 |  | 
| 180 186 | 
             
              # Delete the files at the given path recursively.
         | 
| 181 187 | 
             
              #
         | 
| 182 | 
            -
              # **NOTE**: In most cases, `trash` is a safer choice, since the files will be | 
| 188 | 
            +
              # **NOTE**: In most cases, `trash` is a safer choice, since the files will be
         | 
| 189 | 
            +
              # recoverable by retreiving them from the trash.  Once you delete a file
         | 
| 190 | 
            +
              # using `remove`, it's gone!  Please use `trash` whenever possible and only
         | 
| 191 | 
            +
              # use `remove` when necessary.
         | 
| 183 192 | 
             
              #
         | 
| 184 193 | 
             
              # ## Options
         | 
| 185 194 | 
             
              #
         | 
| @@ -207,8 +216,8 @@ module Maid::Tools | |
| 207 216 | 
             
                expand_all(paths).each do |path|
         | 
| 208 217 | 
             
                  options = @file_options.merge(options)
         | 
| 209 218 |  | 
| 210 | 
            -
                  log("Removing #{ | 
| 211 | 
            -
                  FileUtils.rm_r(path, options)
         | 
| 219 | 
            +
                  log("Removing #{sh_escape(path)}")
         | 
| 220 | 
            +
                  FileUtils.rm_r(path, **options)
         | 
| 212 221 | 
             
                end
         | 
| 213 222 | 
             
              end
         | 
| 214 223 |  | 
| @@ -240,10 +249,10 @@ module Maid::Tools | |
| 240 249 | 
             
              #     dir('~/Music/**/*.m4a')
         | 
| 241 250 | 
             
              #
         | 
| 242 251 | 
             
              def dir(globs)
         | 
| 243 | 
            -
                expand_all(globs) | 
| 244 | 
            -
                  map { |glob| Dir.glob(glob) } | 
| 245 | 
            -
                  flatten | 
| 246 | 
            -
                  sort
         | 
| 252 | 
            +
                expand_all(globs)
         | 
| 253 | 
            +
                  .map { |glob| Dir.glob(glob) }
         | 
| 254 | 
            +
                  .flatten
         | 
| 255 | 
            +
                  .sort
         | 
| 247 256 | 
             
              end
         | 
| 248 257 |  | 
| 249 258 | 
             
              # Same as `dir`, but excludes files that are (possibly) being
         | 
| @@ -256,8 +265,8 @@ module Maid::Tools | |
| 256 265 | 
             
              #     move dir_safe('~/Downloads/*.deb'), '~/Archive/Software'
         | 
| 257 266 | 
             
              #
         | 
| 258 267 | 
             
              def dir_safe(globs)
         | 
| 259 | 
            -
                dir(globs) | 
| 260 | 
            -
                  reject { |path| downloading?(path) }
         | 
| 268 | 
            +
                dir(globs)
         | 
| 269 | 
            +
                  .reject { |path| downloading?(path) }
         | 
| 261 270 | 
             
              end
         | 
| 262 271 |  | 
| 263 272 | 
             
              # Give only files matching the given glob.
         | 
| @@ -265,8 +274,8 @@ module Maid::Tools | |
| 265 274 | 
             
              # This is the same as `dir` but only includes actual files (no directories or symlinks).
         | 
| 266 275 | 
             
              #
         | 
| 267 276 | 
             
              def files(globs)
         | 
| 268 | 
            -
                dir(globs) | 
| 269 | 
            -
                  select { |f| File.file?(f) }
         | 
| 277 | 
            +
                dir(globs)
         | 
| 278 | 
            +
                  .select { |f| File.file?(f) }
         | 
| 270 279 | 
             
              end
         | 
| 271 280 |  | 
| 272 281 | 
             
              # Escape characters that have special meaning as a part of path global patterns.
         | 
| @@ -277,7 +286,7 @@ module Maid::Tools | |
| 277 286 | 
             
              #
         | 
| 278 287 | 
             
              #     escape_glob('test [tmp]') # => 'test \\[tmp\\]'
         | 
| 279 288 | 
             
              def escape_glob(glob)
         | 
| 280 | 
            -
                glob.gsub(/[ | 
| 289 | 
            +
                glob.gsub(/[{}\[\]]/) { |s| '\\' + s }
         | 
| 281 290 | 
             
              end
         | 
| 282 291 |  | 
| 283 292 | 
             
              # Create a directory and all of its parent directories.
         | 
| @@ -301,8 +310,8 @@ module Maid::Tools | |
| 301 310 | 
             
              #     move('~/Downloads/Pink Floyd*.mp3', mkdir('~/Music/Pink Floyd/'))
         | 
| 302 311 | 
             
              def mkdir(path, options = {})
         | 
| 303 312 | 
             
                path = expand(path)
         | 
| 304 | 
            -
                log("mkdir -p #{ | 
| 305 | 
            -
                FileUtils.mkdir_p(path,  | 
| 313 | 
            +
                log("mkdir -p #{sh_escape(path)}")
         | 
| 314 | 
            +
                FileUtils.mkdir_p(path, **@file_options.merge(options))
         | 
| 306 315 | 
             
                path
         | 
| 307 316 | 
             
              end
         | 
| 308 317 |  | 
| @@ -346,7 +355,7 @@ module Maid::Tools | |
| 346 355 | 
             
              #
         | 
| 347 356 | 
             
              #     locate('foo.zip') # => ['/a/foo.zip', '/b/foo.zip']
         | 
| 348 357 | 
             
              def locate(name)
         | 
| 349 | 
            -
                cmd("#{Maid::Platform::Commands.locate} #{ | 
| 358 | 
            +
                cmd("#{Maid::Platform::Commands.locate} #{sh_escape(name)}").split("\n")
         | 
| 350 359 | 
             
              end
         | 
| 351 360 |  | 
| 352 361 | 
             
              # [Mac OS X] Use Spotlight metadata to determine the site from which a file was downloaded.
         | 
| @@ -390,14 +399,14 @@ module Maid::Tools | |
| 390 399 | 
             
              #
         | 
| 391 400 | 
             
              def dupes_in(globs)
         | 
| 392 401 | 
             
                dupes = []
         | 
| 393 | 
            -
                files(globs) | 
| 394 | 
            -
                  group_by { |f| size_of(f) } | 
| 395 | 
            -
                  reject { | | 
| 396 | 
            -
                  map do | | 
| 397 | 
            -
                    dupes += candidates | 
| 398 | 
            -
             | 
| 399 | 
            -
             | 
| 400 | 
            -
             | 
| 402 | 
            +
                files(globs) # Start by filtering out non-files
         | 
| 403 | 
            +
                  .group_by { |f| size_of(f) } # ... then grouping by size, since that's fast
         | 
| 404 | 
            +
                  .reject { |_s, p| p.length < 2 } # ... and filter out any non-dupes
         | 
| 405 | 
            +
                  .map do |_size, candidates|
         | 
| 406 | 
            +
                    dupes += candidates
         | 
| 407 | 
            +
                             .group_by { |p| checksum_of(p) } # Now group our candidates by a slower checksum calculation
         | 
| 408 | 
            +
                             .reject { |_c, p| p.length < 2 } # ... and filter out any non-dupes
         | 
| 409 | 
            +
                             .values
         | 
| 401 410 | 
             
                  end
         | 
| 402 411 | 
             
                dupes
         | 
| 403 412 | 
             
              end
         | 
| @@ -411,9 +420,9 @@ module Maid::Tools | |
| 411 420 | 
             
              #     trash newest_dupes_in('~/Downloads/*')
         | 
| 412 421 | 
             
              #
         | 
| 413 422 | 
             
              def newest_dupes_in(globs)
         | 
| 414 | 
            -
                dupes_in(globs) | 
| 415 | 
            -
                  map { |dupes| dupes.sort_by { |p| File.mtime(p) }[1..-1] } | 
| 416 | 
            -
                  flatten
         | 
| 423 | 
            +
                dupes_in(globs)
         | 
| 424 | 
            +
                  .map { |dupes| dupes.sort_by { |p| File.mtime(p) }[1..-1] }
         | 
| 425 | 
            +
                  .flatten
         | 
| 417 426 | 
             
              end
         | 
| 418 427 |  | 
| 419 428 | 
             
              # Convenience method for `dupes_in` that excludes the dupe with the shortest name.
         | 
| @@ -427,9 +436,9 @@ module Maid::Tools | |
| 427 436 | 
             
              #     trash verbose_dupes_in('~/Downloads/*')
         | 
| 428 437 | 
             
              #
         | 
| 429 438 | 
             
              def verbose_dupes_in(globs)
         | 
| 430 | 
            -
                dupes_in(globs) | 
| 431 | 
            -
                  map { |dupes| dupes.sort_by { |p| File.basename(p).length }[1..-1] } | 
| 432 | 
            -
                  flatten
         | 
| 439 | 
            +
                dupes_in(globs)
         | 
| 440 | 
            +
                  .map { |dupes| dupes.sort_by { |p| File.basename(p).length }[1..-1] }
         | 
| 441 | 
            +
                  .flatten
         | 
| 433 442 | 
             
              end
         | 
| 434 443 |  | 
| 435 444 | 
             
              # Determine the dimensions of GIF, PNG, JPEG, or TIFF images.
         | 
| @@ -466,7 +475,7 @@ module Maid::Tools | |
| 466 475 | 
             
              #
         | 
| 467 476 | 
             
              #     duration_s('foo.mp3') # => 235.705
         | 
| 468 477 | 
             
              def duration_s(path)
         | 
| 469 | 
            -
                cmd("mdls -raw -name kMDItemDurationSeconds #{ | 
| 478 | 
            +
                cmd("mdls -raw -name kMDItemDurationSeconds #{sh_escape(path)}").to_f
         | 
| 470 479 | 
             
              end
         | 
| 471 480 |  | 
| 472 481 | 
             
              # List the contents of a zip file.
         | 
| @@ -489,15 +498,13 @@ module Maid::Tools | |
| 489 498 | 
             
              #
         | 
| 490 499 | 
             
              #     disk_usage('foo.zip') # => 136
         | 
| 491 500 | 
             
              def disk_usage(path)
         | 
| 492 | 
            -
                raw = cmd("du -s #{ | 
| 501 | 
            +
                raw = cmd("du -s #{sh_escape(path)}")
         | 
| 493 502 | 
             
                # FIXME: This reports in kilobytes, but should probably report in bytes.
         | 
| 494 503 | 
             
                usage_kb = raw.split(/\s+/).first.to_i
         | 
| 495 504 |  | 
| 496 | 
            -
                if usage_kb.zero?
         | 
| 497 | 
            -
             | 
| 498 | 
            -
                 | 
| 499 | 
            -
                  usage_kb
         | 
| 500 | 
            -
                end
         | 
| 505 | 
            +
                raise "Stopping pessimistically because of unexpected value from du (#{raw.inspect})" if usage_kb.zero?
         | 
| 506 | 
            +
             | 
| 507 | 
            +
                usage_kb
         | 
| 501 508 | 
             
              end
         | 
| 502 509 |  | 
| 503 510 | 
             
              # Get the creation time of a file.
         | 
| @@ -572,8 +579,8 @@ module Maid::Tools | |
| 572 579 | 
             
              #     git_piston('~/code/projectname')
         | 
| 573 580 | 
             
              def git_piston(path)
         | 
| 574 581 | 
             
                full_path = expand(path)
         | 
| 575 | 
            -
                stdout = cmd("cd #{ | 
| 576 | 
            -
                log("Fired git piston on #{ | 
| 582 | 
            +
                stdout = cmd("cd #{sh_escape(full_path)} && git pull && git push 2>&1")
         | 
| 583 | 
            +
                log("Fired git piston on #{sh_escape(full_path)}.  STDOUT:\n\n#{stdout}")
         | 
| 577 584 | 
             
              end
         | 
| 578 585 |  | 
| 579 586 | 
             
              deprecated :git_piston, 'SparkleShare (http://sparkleshare.org/)'
         | 
| @@ -612,7 +619,7 @@ module Maid::Tools | |
| 612 619 | 
             
                from = expand(from) + (from.end_with?('/') ? '/' : '')
         | 
| 613 620 | 
             
                to = expand(to) + (to.end_with?('/') ? '/' : '')
         | 
| 614 621 | 
             
                # default options
         | 
| 615 | 
            -
                options = { : | 
| 622 | 
            +
                options = { archive: true, update: true }.merge(options)
         | 
| 616 623 | 
             
                ops = []
         | 
| 617 624 | 
             
                ops << '-a' if options[:archive]
         | 
| 618 625 | 
             
                ops << '-v' if options[:verbose]
         | 
| @@ -621,12 +628,12 @@ module Maid::Tools | |
| 621 628 | 
             
                ops << '-n' if @file_options[:noop]
         | 
| 622 629 |  | 
| 623 630 | 
             
                Array(options[:exclude]).each do |path|
         | 
| 624 | 
            -
                  ops << "--exclude=#{ | 
| 631 | 
            +
                  ops << "--exclude=#{sh_escape(path)}"
         | 
| 625 632 | 
             
                end
         | 
| 626 633 |  | 
| 627 634 | 
             
                ops << '--delete' if options[:delete]
         | 
| 628 | 
            -
                stdout = cmd("rsync #{ | 
| 629 | 
            -
                log("Fired sync from #{ | 
| 635 | 
            +
                stdout = cmd("rsync #{ops.join(' ')} #{sh_escape(from)} #{sh_escape(to)} 2>&1")
         | 
| 636 | 
            +
                log("Fired sync from #{sh_escape(from)} to #{sh_escape(to)}.  STDOUT:\n\n#{stdout}")
         | 
| 630 637 | 
             
              end
         | 
| 631 638 |  | 
| 632 639 | 
             
              # [Mac OS X] Use Spotlight metadata to determine which content types a file has.
         | 
| @@ -644,7 +651,8 @@ module Maid::Tools | |
| 644 651 | 
             
              #
         | 
| 645 652 | 
             
              # ## Examples
         | 
| 646 653 | 
             
              #
         | 
| 647 | 
            -
              #     content_types('foo.zip') # => ["public.zip-archive", "com.pkware.zip-archive", | 
| 654 | 
            +
              #     content_types('foo.zip') # => ["public.zip-archive", "com.pkware.zip-archive",
         | 
| 655 | 
            +
              #                                    "public.archive", "application/zip", "application"]
         | 
| 648 656 | 
             
              #     content_types('bar.jpg') # => ["public.jpeg", "public.image", "image/jpeg", "image"]
         | 
| 649 657 | 
             
              def content_types(path)
         | 
| 650 658 | 
             
                [spotlight_content_types(path), mime_type(path), media_type(path)].flatten
         | 
| @@ -658,9 +666,9 @@ module Maid::Tools | |
| 658 666 | 
             
              def mime_type(path)
         | 
| 659 667 | 
             
                type = MIME::Types.type_for(path)[0]
         | 
| 660 668 |  | 
| 661 | 
            -
                 | 
| 662 | 
            -
             | 
| 663 | 
            -
                 | 
| 669 | 
            +
                return unless type
         | 
| 670 | 
            +
             | 
| 671 | 
            +
                [type.media_type, type.sub_type].join('/')
         | 
| 664 672 | 
             
              end
         | 
| 665 673 |  | 
| 666 674 | 
             
              # Get the Internet media type of the file.
         | 
| @@ -673,9 +681,9 @@ module Maid::Tools | |
| 673 681 | 
             
              def media_type(path)
         | 
| 674 682 | 
             
                type = MIME::Types.type_for(path)[0]
         | 
| 675 683 |  | 
| 676 | 
            -
                 | 
| 677 | 
            -
             | 
| 678 | 
            -
                 | 
| 684 | 
            +
                return unless type
         | 
| 685 | 
            +
             | 
| 686 | 
            +
                type.media_type
         | 
| 679 687 | 
             
              end
         | 
| 680 688 |  | 
| 681 689 | 
             
              # Filter an array by content types.
         | 
| @@ -722,24 +730,24 @@ module Maid::Tools | |
| 722 730 | 
             
                # Look for files.
         | 
| 723 731 | 
             
                return false if Dir.glob(root + '/*').select { |f| File.file?(f) }.length > 0
         | 
| 724 732 |  | 
| 725 | 
            -
                empty_dirs = Dir.glob(root + '/**/*').select  | 
| 733 | 
            +
                empty_dirs = Dir.glob(root + '/**/*').select do |d|
         | 
| 726 734 | 
             
                  File.directory?(d)
         | 
| 727 | 
            -
                 | 
| 735 | 
            +
                end.reverse.select do |d|
         | 
| 728 736 | 
             
                  # `.reverse` sorts deeper directories first.
         | 
| 729 737 |  | 
| 730 738 | 
             
                  # If the directory is empty, its parent should ignore it.
         | 
| 731 | 
            -
                  should_ignore = Dir.glob(d + '/*').select  | 
| 739 | 
            +
                  should_ignore = Dir.glob(d + '/*').select do |n|
         | 
| 732 740 | 
             
                    !ignore.include?(n)
         | 
| 733 | 
            -
                   | 
| 741 | 
            +
                  end.length == 0
         | 
| 734 742 |  | 
| 735 743 | 
             
                  ignore << d if should_ignore
         | 
| 736 744 |  | 
| 737 745 | 
             
                  should_ignore
         | 
| 738 | 
            -
                 | 
| 746 | 
            +
                end
         | 
| 739 747 |  | 
| 740 | 
            -
                Dir.glob(root + '/*').select  | 
| 748 | 
            +
                Dir.glob(root + '/*').select do |n|
         | 
| 741 749 | 
             
                  !empty_dirs.include?(n)
         | 
| 742 | 
            -
                 | 
| 750 | 
            +
                end.length == 0
         | 
| 743 751 | 
             
              end
         | 
| 744 752 |  | 
| 745 753 | 
             
              # Given an array of directories, return a new array without any child
         | 
| @@ -749,14 +757,13 @@ module Maid::Tools | |
| 749 757 | 
             
              #
         | 
| 750 758 | 
             
              #     ignore_child_dirs(["foo", "foo/a", "foo/b", "bar"]) # => ["foo", "bar"]
         | 
| 751 759 | 
             
              def ignore_child_dirs(arr)
         | 
| 752 | 
            -
                arr.sort  | 
| 760 | 
            +
                arr.sort do |x, y|
         | 
| 753 761 | 
             
                  y.count('/') - x.count('/')
         | 
| 754 | 
            -
                 | 
| 762 | 
            +
                end.select do |d|
         | 
| 755 763 | 
             
                  !arr.include?(File.dirname(d))
         | 
| 756 | 
            -
                 | 
| 764 | 
            +
                end
         | 
| 757 765 | 
             
              end
         | 
| 758 766 |  | 
| 759 | 
            -
             | 
| 760 767 | 
             
              # Get a list of Finder labels of a file or directory. Only available on OS X when you have tag installed.
         | 
| 761 768 | 
             
              #
         | 
| 762 769 | 
             
              # ## Example
         | 
| @@ -772,7 +779,8 @@ module Maid::Tools | |
| 772 779 | 
             
                end
         | 
| 773 780 | 
             
              end
         | 
| 774 781 |  | 
| 775 | 
            -
              # Tell if a file or directory has any Finder labels. Only available on OS X | 
| 782 | 
            +
              # Tell if a file or directory has any Finder labels. Only available on OS X
         | 
| 783 | 
            +
              # when you have tag installed.
         | 
| 776 784 | 
             
              #
         | 
| 777 785 | 
             
              # ## Example
         | 
| 778 786 | 
             
              #
         | 
| @@ -786,7 +794,8 @@ module Maid::Tools | |
| 786 794 | 
             
                end
         | 
| 787 795 | 
             
              end
         | 
| 788 796 |  | 
| 789 | 
            -
              # Tell if a file or directory has a certain Finder labels. Only available on | 
| 797 | 
            +
              # Tell if a file or directory has a certain Finder labels. Only available on
         | 
| 798 | 
            +
              # OS X when you have tag installed.
         | 
| 790 799 | 
             
              #
         | 
| 791 800 | 
             
              # ## Example
         | 
| 792 801 | 
             
              #
         | 
| @@ -801,52 +810,55 @@ module Maid::Tools | |
| 801 810 | 
             
                end
         | 
| 802 811 | 
             
              end
         | 
| 803 812 |  | 
| 804 | 
            -
              # Add a Finder label or a list of labels to a file or directory. Only | 
| 813 | 
            +
              # Add a Finder label or a list of labels to a file or directory. Only
         | 
| 814 | 
            +
              # available on OS X when you have tag installed.
         | 
| 805 815 | 
             
              #
         | 
| 806 816 | 
             
              # ## Example
         | 
| 807 817 | 
             
              #
         | 
| 808 818 | 
             
              #     add_tag("~/Downloads/a.dmg.download", "Unfinished")
         | 
| 809 819 | 
             
              def add_tag(path, tag)
         | 
| 810 | 
            -
                 | 
| 811 | 
            -
             | 
| 812 | 
            -
             | 
| 813 | 
            -
             | 
| 814 | 
            -
             | 
| 815 | 
            -
             | 
| 816 | 
            -
             | 
| 817 | 
            -
                 | 
| 820 | 
            +
                return unless has_tag_available_and_warn?
         | 
| 821 | 
            +
             | 
| 822 | 
            +
                path = expand(path)
         | 
| 823 | 
            +
                ts = Array(tag).join(',')
         | 
| 824 | 
            +
                log "add tags #{ts} to #{path}"
         | 
| 825 | 
            +
                return if @file_options[:noop]
         | 
| 826 | 
            +
             | 
| 827 | 
            +
                cmd("tag -a #{sh_escape(ts)} #{sh_escape(path)}")
         | 
| 818 828 | 
             
              end
         | 
| 819 829 |  | 
| 820 | 
            -
              # Remove a Finder label or a list of labels from a file or directory. Only | 
| 830 | 
            +
              # Remove a Finder label or a list of labels from a file or directory. Only
         | 
| 831 | 
            +
              # available on OS X when you have tag installed.
         | 
| 821 832 | 
             
              #
         | 
| 822 833 | 
             
              # ## Example
         | 
| 823 834 | 
             
              #
         | 
| 824 835 | 
             
              #     remove_tag("~/Downloads/a.dmg", "Unfinished")
         | 
| 825 836 | 
             
              def remove_tag(path, tag)
         | 
| 826 | 
            -
                 | 
| 827 | 
            -
             | 
| 828 | 
            -
             | 
| 829 | 
            -
             | 
| 830 | 
            -
             | 
| 831 | 
            -
             | 
| 832 | 
            -
             | 
| 833 | 
            -
                 | 
| 837 | 
            +
                return unless has_tag_available_and_warn?
         | 
| 838 | 
            +
             | 
| 839 | 
            +
                path = expand(path)
         | 
| 840 | 
            +
                ts = Array(tag).join(',')
         | 
| 841 | 
            +
                log "remove tags #{ts} from #{path}"
         | 
| 842 | 
            +
                return if @file_options[:noop]
         | 
| 843 | 
            +
             | 
| 844 | 
            +
                cmd("tag -r #{sh_escape(ts)} #{sh_escape(path)}")
         | 
| 834 845 | 
             
              end
         | 
| 835 846 |  | 
| 836 | 
            -
              # Set Finder label of a file or directory to a label or a list of labels. | 
| 847 | 
            +
              # Set Finder label of a file or directory to a label or a list of labels.
         | 
| 848 | 
            +
              # Only available on OS X when you have tag installed.
         | 
| 837 849 | 
             
              #
         | 
| 838 850 | 
             
              # ## Example
         | 
| 839 851 | 
             
              #
         | 
| 840 852 | 
             
              #     set_tag("~/Downloads/a.dmg.download", "Unfinished")
         | 
| 841 853 | 
             
              def set_tag(path, tag)
         | 
| 842 | 
            -
                 | 
| 843 | 
            -
             | 
| 844 | 
            -
             | 
| 845 | 
            -
             | 
| 846 | 
            -
             | 
| 847 | 
            -
             | 
| 848 | 
            -
             | 
| 849 | 
            -
                 | 
| 854 | 
            +
                return unless has_tag_available_and_warn?
         | 
| 855 | 
            +
             | 
| 856 | 
            +
                path = expand(path)
         | 
| 857 | 
            +
                ts = Array(tag).join(',')
         | 
| 858 | 
            +
                log "set tags #{ts} to #{path}"
         | 
| 859 | 
            +
                return if @file_options[:noop]
         | 
| 860 | 
            +
             | 
| 861 | 
            +
                cmd("tag -s #{sh_escape(ts)} #{sh_escape(path)}")
         | 
| 850 862 | 
             
              end
         | 
| 851 863 |  | 
| 852 864 | 
             
              # Tell if a file is hidden
         | 
| @@ -856,7 +868,7 @@ module Maid::Tools | |
| 856 868 | 
             
              #     hidden?("~/.maid") # => true
         | 
| 857 869 | 
             
              def hidden?(path)
         | 
| 858 870 | 
             
                if Maid::Platform.osx?
         | 
| 859 | 
            -
                  raw = cmd("mdls -raw -name kMDItemFSInvisible #{ | 
| 871 | 
            +
                  raw = cmd("mdls -raw -name kMDItemFSInvisible #{sh_escape(path)}")
         | 
| 860 872 | 
             
                  raw == '1'
         | 
| 861 873 | 
             
                else
         | 
| 862 874 | 
             
                  p = Pathname.new(expand(path))
         | 
| @@ -872,9 +884,9 @@ module Maid::Tools | |
| 872 884 | 
             
              def has_been_used?(path)
         | 
| 873 885 | 
             
                if Maid::Platform.osx?
         | 
| 874 886 | 
             
                  path = expand(path)
         | 
| 875 | 
            -
                  raw = cmd("mdls -raw -name kMDItemLastUsedDate #{ | 
| 887 | 
            +
                  raw = cmd("mdls -raw -name kMDItemLastUsedDate #{sh_escape(path)}")
         | 
| 876 888 |  | 
| 877 | 
            -
                  if raw ==  | 
| 889 | 
            +
                  if raw == '(null)'
         | 
| 878 890 | 
             
                    false
         | 
| 879 891 | 
             
                  else
         | 
| 880 892 | 
             
                    begin
         | 
| @@ -897,9 +909,9 @@ module Maid::Tools | |
| 897 909 | 
             
              def used_at(path)
         | 
| 898 910 | 
             
                if Maid::Platform.osx?
         | 
| 899 911 | 
             
                  path = expand(path)
         | 
| 900 | 
            -
                  raw = cmd("mdls -raw -name kMDItemLastUsedDate #{ | 
| 912 | 
            +
                  raw = cmd("mdls -raw -name kMDItemLastUsedDate #{sh_escape(path)}")
         | 
| 901 913 |  | 
| 902 | 
            -
                  if raw ==  | 
| 914 | 
            +
                  if raw == '(null)'
         | 
| 903 915 | 
             
                    nil
         | 
| 904 916 | 
             
                  else
         | 
| 905 917 | 
             
                    begin
         | 
| @@ -921,9 +933,9 @@ module Maid::Tools | |
| 921 933 | 
             
              def added_at(path)
         | 
| 922 934 | 
             
                if Maid::Platform.osx?
         | 
| 923 935 | 
             
                  path = expand(path)
         | 
| 924 | 
            -
                  raw = cmd("mdls -raw -name kMDItemDateAdded #{ | 
| 936 | 
            +
                  raw = cmd("mdls -raw -name kMDItemDateAdded #{sh_escape(path)}")
         | 
| 925 937 |  | 
| 926 | 
            -
                  if raw ==  | 
| 938 | 
            +
                  if raw == '(null)'
         | 
| 927 939 | 
             
                    1.second.ago
         | 
| 928 940 | 
             
                  else
         | 
| 929 941 | 
             
                    begin
         | 
| @@ -948,9 +960,9 @@ module Maid::Tools | |
| 948 960 | 
             
                  true
         | 
| 949 961 | 
             
                else
         | 
| 950 962 | 
             
                  if Maid::Platform.osx?
         | 
| 951 | 
            -
                    warn( | 
| 963 | 
            +
                    warn('To use this feature, you need `tag` installed.  Run `brew install tag`')
         | 
| 952 964 | 
             
                  else
         | 
| 953 | 
            -
                    warn( | 
| 965 | 
            +
                    warn('sorry, tagging is unavailable on your platform')
         | 
| 954 966 | 
             
                  end
         | 
| 955 967 |  | 
| 956 968 | 
             
                  false
         | 
| @@ -979,13 +991,16 @@ module Maid::Tools | |
| 979 991 |  | 
| 980 992 | 
             
              def mdls_to_array(path, attribute)
         | 
| 981 993 | 
             
                if Maid::Platform.osx?
         | 
| 982 | 
            -
                  raw = cmd("mdls -raw -name #{sh_escape(attribute)} #{ | 
| 994 | 
            +
                  raw = cmd("mdls -raw -name #{sh_escape(attribute)} #{sh_escape(path)}")
         | 
| 983 995 |  | 
| 984 996 | 
             
                  if raw.empty?
         | 
| 985 997 | 
             
                    []
         | 
| 986 998 | 
             
                  else
         | 
| 987 999 | 
             
                    clean = raw[1, raw.length - 2]
         | 
| 988 | 
            -
                    clean.split(/,\s+/).map  | 
| 1000 | 
            +
                    clean.split(/,\s+/).map do |s|
         | 
| 1001 | 
            +
                      t = s.strip
         | 
| 1002 | 
            +
                      t[1, t.length - 2]
         | 
| 1003 | 
            +
                    end
         | 
| 989 1004 | 
             
                  end
         | 
| 990 1005 | 
             
                else
         | 
| 991 1006 | 
             
                  []
         | 
    
        data/lib/maid/trash_migration.rb
    CHANGED
    
    | @@ -8,7 +8,7 @@ module Maid | |
| 8 8 | 
             
                  def incorrect_trash
         | 
| 9 9 | 
             
                    File.expand_path('~/.Trash') + '/'
         | 
| 10 10 | 
             
                  end
         | 
| 11 | 
            -
             | 
| 11 | 
            +
             | 
| 12 12 | 
             
                  def correct_trash
         | 
| 13 13 | 
             
                    Maid.new.trash_path
         | 
| 14 14 | 
             
                  end
         | 
| @@ -18,16 +18,16 @@ module Maid | |
| 18 18 | 
             
                      File.directory?(incorrect_trash) &&
         | 
| 19 19 | 
             
                      !ENV['MAID_NO_MIGRATE_TRASH']
         | 
| 20 20 | 
             
                  end
         | 
| 21 | 
            -
             | 
| 21 | 
            +
             | 
| 22 22 | 
             
                  def perform
         | 
| 23 | 
            -
                    maid = ::Maid::Maid.new(: | 
| 23 | 
            +
                    maid = ::Maid::Maid.new(trash_path: correct_trash)
         | 
| 24 24 | 
             
                    # Use local variable so it's available in the closure used by `instance_eval`
         | 
| 25 25 | 
             
                    path = incorrect_trash
         | 
| 26 26 |  | 
| 27 27 | 
             
                    # Might as well use Maid itself for this :)
         | 
| 28 28 | 
             
                    maid.instance_eval do
         | 
| 29 29 | 
             
                      rule 'Migrate Linux trash to correct path' do
         | 
| 30 | 
            -
                        trash(dir("#{ | 
| 30 | 
            +
                        trash(dir("#{path}/*"))
         | 
| 31 31 | 
             
                        trash(path)
         | 
| 32 32 | 
             
                      end
         | 
| 33 33 | 
             
                    end
         | 
    
        data/lib/maid/user_agent.rb
    CHANGED
    
    | @@ -5,12 +5,12 @@ | |
| 5 5 | 
             
            module Maid::UserAgent
         | 
| 6 6 | 
             
              class << self
         | 
| 7 7 | 
             
                def short
         | 
| 8 | 
            -
                  "Maid/#{ | 
| 8 | 
            +
                  "Maid/#{::Maid.const_get(:VERSION)}"
         | 
| 9 9 | 
             
                end
         | 
| 10 10 |  | 
| 11 11 | 
             
                # This used to be called `#to_s`, but that made things difficult when testing.
         | 
| 12 12 | 
             
                def value
         | 
| 13 | 
            -
                  "#{ | 
| 13 | 
            +
                  "#{short} (#{RUBY_DESCRIPTION})"
         | 
| 14 14 | 
             
                end
         | 
| 15 15 | 
             
              end
         | 
| 16 16 | 
             
            end
         | 
    
        data/lib/maid/version.rb
    CHANGED
    
    | @@ -1,4 +1,7 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Maid
         | 
| 2 | 
            -
              VERSION = '0.10.0-alpha. | 
| 3 | 
            -
              SUMMARY = 'Be lazy. Let Maid clean up after you, based on rules you define.  | 
| 4 | 
            +
              VERSION = '0.10.0-alpha.2'
         | 
| 5 | 
            +
              SUMMARY = 'Be lazy. Let Maid clean up after you, based on rules you define. ' \
         | 
| 6 | 
            +
                        'Think of it as "Hazel for hackers".'
         | 
| 4 7 | 
             
            end
         |