Module: Metaclean::Display
- Defined in:
- lib/metaclean/display.rb
Constant Summary collapse
- COLORS =
{ reset: "\e[0m", bold: "\e[1m", dim: "\e[2m", red: "\e[31m", green: "\e[32m", yellow: "\e[33m", blue: "\e[34m", magenta: "\e[35m", cyan: "\e[36m", gray: "\e[90m" }.freeze
- NON_METADATA_GROUPS =
ExifTool reports four “groups” that are descriptions of the file itself, not embedded metadata: System (filesystem stat), File (header info), ExifTool (its own version), Composite (computed values). Excluding these makes the diff focus on what actually got stripped.
%w[System File ExifTool Composite].freeze
Class Method Summary collapse
-
.c(text, color) ⇒ Object
‘c` for “color”.
-
.color? ⇒ Boolean
Decides whether to emit ANSI color codes.
-
.count_embedded(meta) ⇒ Object
How many “real” embedded tags are there? Used for the “Before (24 embedded tags) → After (0)” summary line.
-
.diff(before, after) ⇒ Object
Compares two metadata hashes (before vs after) and prints three sections: removed, changed, still-present.
-
.error(text) ⇒ Object
‘error` returns a string instead of printing it — callers usually want to send it to STDERR via `warn`, not stdout via `puts`.
-
.format_value(v) ⇒ Object
Make any value safe to print on a single line.
-
.group_of(key) ⇒ Object
Pull the group name out of “Group:Tag”.
-
.header(text) ⇒ Object
Visual section markers used throughout the runner’s output.
- .info(text) ⇒ Object
-
.metadata_table(meta, only_embedded: false) ⇒ Object
Prints a metadata Hash as a grouped, indented table.
- .section(text) ⇒ Object
- .success(text) ⇒ Object
-
.truncate(s, n) ⇒ Object
Truncate to N chars with a single-character ellipsis.
- .warning(text) ⇒ Object
Class Method Details
.c(text, color) ⇒ Object
‘c` for “color”. Wraps text in the requested color, or returns it plain if colors are disabled. The reset code at the end stops the color from bleeding into following output.
57 58 59 60 61 |
# File 'lib/metaclean/display.rb', line 57 def c(text, color) return text.to_s unless color? "#{COLORS[color]}#{text}#{COLORS[:reset]}" end |
.color? ⇒ Boolean
Decides whether to emit ANSI color codes. Colors are wrong when:
* stdout is a pipe/file (not a terminal) — `tty?` is false there
* NO_COLOR env var is set (de-facto convention, see no-color.org)
* we're on classic Windows cmd.exe (modern Windows Terminal is fine,
but to be safe we require an explicit FORCE_COLOR opt-in there)
43 44 45 46 47 48 49 50 51 52 |
# File 'lib/metaclean/display.rb', line 43 def color? return @color if defined?(@color) # Per https://no-color.org: disable only when NO_COLOR is set to a # non-empty value. An unset or empty NO_COLOR leaves colors on. no_color = ENV['NO_COLOR'].to_s @color = $stdout.tty? && no_color.empty? && !Gem.win_platform? @color = true if ENV['FORCE_COLOR'] @color end |
.count_embedded(meta) ⇒ Object
How many “real” embedded tags are there? Used for the “Before (24 embedded tags) → After (0)” summary line.
190 191 192 193 194 195 |
# File 'lib/metaclean/display.rb', line 190 def () .keys .reject { |k| k == 'SourceFile' } .reject { |k| NON_METADATA_GROUPS.include?(group_of(k)) } .size end |
.diff(before, after) ⇒ Object
Compares two metadata hashes (before vs after) and prints three sections: removed, changed, still-present. This is the “before/after” the user asked for.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/metaclean/display.rb', line 111 def diff(before, after) keys = (before.keys + after.keys).uniq .reject { |k| k == 'SourceFile' } .reject { |k| NON_METADATA_GROUPS.include?(group_of(k)) } removed = [] changed = [] kept = [] # Classifying each key into one of three buckets keeps the rest of # the method simple and testable. keys.sort.each do |k| b = before[k] a = after[k] if a.nil? && !b.nil? removed << [k, b] elsif !b.nil? && a != b changed << [k, b, a] elsif !b.nil? kept << [k, b] end end if removed.any? section "Removed (#{removed.size})" removed.each do |k, b| puts " #{c('-', :red)} #{c(k, :red)} #{c(truncate(format_value(b), 60), :gray)}" end end if changed.any? section "Changed (#{changed.size})" changed.each do |k, b, a| puts " #{c('~', :yellow)} #{c(k, :yellow)}" puts " #{c('-', :red)} #{truncate(format_value(b), 60)}" puts " #{c('+', :green)} #{truncate(format_value(a), 60)}" end end if kept.any? section "Still present (#{kept.size})" kept.each do |k, b| puts " #{c('=', :gray)} #{c(k, :gray)} #{c(truncate(format_value(b), 60), :gray)}" end end if removed.empty? && changed.empty? && kept.empty? info 'Nothing to strip — file already clean.' elsif removed.empty? && changed.empty? info 'No tags were removed — see "Still present" above.' end end |
.error(text) ⇒ Object
‘error` returns a string instead of printing it — callers usually want to send it to STDERR via `warn`, not stdout via `puts`.
79 |
# File 'lib/metaclean/display.rb', line 79 def error(text); c("✗ #{text}", :red); end |
.format_value(v) ⇒ Object
Make any value safe to print on a single line. Hashes/Arrays get ‘inspect` (shows their structure); strings are collapsed to single spaces so a multiline tag value doesn’t wreck the table.
173 174 175 176 177 178 |
# File 'lib/metaclean/display.rb', line 173 def format_value(v) case v when Hash, Array then v.inspect else v.to_s.gsub(/\s+/, ' ') end end |
.group_of(key) ⇒ Object
Pull the group name out of “Group:Tag”. The ‘2` argument to split caps the result at 2 elements, so a value containing “:” doesn’t break it.
166 167 168 |
# File 'lib/metaclean/display.rb', line 166 def group_of(key) key.to_s.split(':', 2).first.to_s end |
.header(text) ⇒ Object
Visual section markers used throughout the runner’s output. Keeping them here means a single change updates the look everywhere.
65 66 67 68 69 70 |
# File 'lib/metaclean/display.rb', line 65 def header(text) puts puts c('━' * 64, :gray) puts c(text, :bold) puts c('━' * 64, :gray) end |
.info(text) ⇒ Object
73 |
# File 'lib/metaclean/display.rb', line 73 def info(text); puts c(" #{text}", :gray); end |
.metadata_table(meta, only_embedded: false) ⇒ Object
Prints a metadata Hash as a grouped, indented table. ‘only_embedded:` filters out the System/File/etc. noise.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/metaclean/display.rb', line 83 def (, only_embedded: false) rows = .reject { |k, _| k == 'SourceFile' } rows = rows.reject { |k, _| NON_METADATA_GROUPS.include?(group_of(k)) } if if rows.empty? info( ? '(no embedded metadata)' : '(no metadata)') return end # `group_by` partitions an Enumerable into a Hash keyed by the block's # result. Here we group all "GPS:*" tags together, all "EXIF:*" together, # etc., then print each group as a labeled sub-table. grouped = rows.group_by { |k, _| group_of(k) } grouped.sort_by { |g, _| g.to_s }.each do |group, pairs| puts c(" [#{group}]", :magenta) pairs.sort_by { |k, _| k.to_s }.each do |k, v| tag = k.to_s.split(':', 2).last # `format` (alias of sprintf) does column alignment: %-38s = left- # aligned, padded to 38 chars. line = format(' %-38s %s', truncate(tag, 38), truncate(format_value(v), 60)) puts c(line, :dim) end end end |
.section(text) ⇒ Object
72 |
# File 'lib/metaclean/display.rb', line 72 def section(text); puts c("▸ #{text}", :cyan); end |
.success(text) ⇒ Object
74 |
# File 'lib/metaclean/display.rb', line 74 def success(text); puts c("✓ #{text}", :green); end |
.truncate(s, n) ⇒ Object
Truncate to N chars with a single-character ellipsis. We use “…” (one Unicode char) instead of “…” so the truncation doesn’t itself spill over the budget.
183 184 185 186 |
# File 'lib/metaclean/display.rb', line 183 def truncate(s, n) s = s.to_s s.length > n ? "#{s[0, n - 1]}…" : s end |
.warning(text) ⇒ Object
75 |
# File 'lib/metaclean/display.rb', line 75 def warning(text); puts c("⚠ #{text}", :yellow);end |