Class: Metaclean::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/metaclean/runner.rb

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Runner

Constructor — just stashes the options Hash. The CLI builds it.



19
20
21
# File 'lib/metaclean/runner.rb', line 19

def initialize(options)
  @options = options
end

Instance Method Details

#clean_paths(paths) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/metaclean/runner.rb', line 43

def clean_paths(paths)
  files = expand_files(paths)
  # See inspect_paths: nothing to act on is a non-zero condition, not success.
  if files.empty?
    Display.warning('No files to process.')
    exit 1
  end

  announce_tools

  # Confirmation prompt — skipped for --force and --dry-run (since
  # dry-run never modifies anything anyway).
  unless @options[:force] || @options[:dry_run]
    action = @options[:in_place] ? 'OVERWRITE' : 'create cleaned copies of'
    puts Display.c("About to #{action} #{files.size} file(s).", :yellow)
    if @options[:in_place]
      puts Display.c('Backups will be saved alongside as <file>.bak.', :gray)
    end
    print Display.c('Proceed? [y/N] ', :bold)
    # `&.` is the safe-navigation operator: if `gets` returns nil
    # (e.g. user hit Ctrl-D), the chain short-circuits to nil.
    ans = $stdin.gets&.strip&.downcase
    return Display.warning('Aborted.') unless %w[y yes].include?(ans)
  end

  summary = { cleaned: 0, unverified: 0, failed: 0, removed_total: 0, residual_files: 0 }

  # `each_with_index` gives us the file AND its position. We pass both
  # to `clean_one` so it can render "[3/47]" in batch mode.
  files.each_with_index do |file, idx|
    result = clean_one(file, index: idx + 1, total: files.size)
    summary[result[:status]] += 1
    summary[:removed_total]  += result[:removed].to_i
    summary[:residual_files] += 1 if result[:residual].to_i.positive?
  rescue Error, SystemCallError => e
    # Block-level rescue (Ruby 2.5+). Catches errors from `clean_one`
    # without aborting the whole batch — one bad file shouldn't stop
    # the next 99 from being cleaned. `SystemCallError` (Errno::*: disk
    # full, permission denied, read-only fs) is a SIBLING of our `Error`,
    # not a subclass, so it must be named explicitly or it would escape
    # this rescue and crash the run with a raw backtrace.
    warn Display.error("#{file}: #{e.message}")
    summary[:failed] += 1
  end

  print_summary(summary)

  # Non-zero exit so CI/scripts can detect a failed or not-fully-verified file.
  exit 1 if summary[:failed].positive? || summary[:unverified].positive?
end

#inspect_paths(paths) ⇒ Object

Public entry points: one for ‘–inspect`, one for the cleaning flow.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/metaclean/runner.rb', line 25

def inspect_paths(paths)
  files = expand_files(paths)
  if files.empty?
    Display.warning('No files to inspect.')
    exit 1
  end
  files.each do |file|
    Display.header "📄 #{file}"
    meta = Exiftool.read(file)
    Display.section "Metadata (#{Display.count_embedded(meta)} embedded tags)"
    Display.(meta)
  rescue Error, SystemCallError => e
    # One unreadable/odd file shouldn't abort inspecting the rest — mirrors
    # the per-file rescue in the clean batch.
    warn Display.error("#{file}: #{e.message}")
  end
end