Class: Cooltrainer::Change

Inherits:
Struct
  • Object
show all
Defined in:
lib/distorted/element_of_media/change.rb

Overview

Struct to encapsulate all the data needed to perform one (1) MIME::Type transformation of a source media file into any supported MIME::Type, possibly even the same type as input.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(type, src: nil, molecule: nil, tag: nil, breaks: Array.new, **atoms) ⇒ Change

Customize the destination filename and other values before doing the normal Struct setup.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/distorted/element_of_media/change.rb', line 17

def initialize(type, src: nil, molecule: nil, tag: nil, breaks: Array.new, **atoms)
  # `name` might have a leading slash if referenced as an absolute path as the Tag.
  basename = File.basename(src, '.*'.freeze).reverse.chomp('/'.freeze).reverse

  # Set the &default_proc on the kwarg-glob Hash instead of making a new Hash,
  atoms.default_proc = lambda { |h,k| h[k] = Cooltrainer::Atom.new }
  atoms.transform_values {
    # We might get Atoms already instantiated, but do it for any that aren't.
    # We won't have a default value for them in that case.
    |v| v.is_a?(Cooltrainer::Atom) ? atom : Cooltrainer::Atom.new(v, nil)
  }.each_key { |k|
    # Define accessors for context-specific :atoms keys/values that aren't normal Struct members.
    self.singleton_class.define_method(k) { self[:atoms]&.fetch(k, nil)&.get }
    self.singleton_class.define_method("#{k}=".to_sym) { |v| self[:atoms][k] = v }
  }

  # And now back to your regularly-scheduled Struct
  super(type: type, src: src, basename: basename, molecule: molecule, tag: tag, breaks: breaks, atoms: atoms)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *a, **k, &b) ⇒ Object

Support setting Atoms that were not defined at instantiation.



95
96
97
98
99
100
101
102
103
# File 'lib/distorted/element_of_media/change.rb', line 95

def method_missing(meth, *a, **k, &b)
  # Are we a setter?
  if meth.to_s[-1] == '='.freeze
    # Set the :value of an existing Atom Struct
    self[:atoms][meth.to_s.chomp('='.freeze).to_sym].value = a.first
  else
    self[:atoms]&.fetch(meth, nil)
  end
end

Instance Attribute Details

#atomsObject

Returns the value of attribute atoms

Returns:

  • (Object)

    the current value of atoms



14
15
16
# File 'lib/distorted/element_of_media/change.rb', line 14

def atoms
  @atoms
end

#basenameObject

Returns the value of attribute basename

Returns:

  • (Object)

    the current value of basename



14
15
16
# File 'lib/distorted/element_of_media/change.rb', line 14

def basename
  @basename
end

#breaksObject

Returns the value of attribute breaks

Returns:

  • (Object)

    the current value of breaks



14
15
16
# File 'lib/distorted/element_of_media/change.rb', line 14

def breaks
  @breaks
end

#moleculeObject

Returns the value of attribute molecule

Returns:

  • (Object)

    the current value of molecule



14
15
16
# File 'lib/distorted/element_of_media/change.rb', line 14

def molecule
  @molecule
end

#srcObject

Returns the value of attribute src

Returns:

  • (Object)

    the current value of src



14
15
16
# File 'lib/distorted/element_of_media/change.rb', line 14

def src
  @src
end

#tagObject

Returns the value of attribute tag

Returns:

  • (Object)

    the current value of tag



14
15
16
# File 'lib/distorted/element_of_media/change.rb', line 14

def tag
  @tag
end

#typeObject

Returns the value of attribute type

Returns:

  • (Object)

    the current value of type



14
15
16
# File 'lib/distorted/element_of_media/change.rb', line 14

def type
  @type
end

Instance Method Details

#dig(*keys) ⇒ Object



92
# File 'lib/distorted/element_of_media/change.rb', line 92

def dig(*keys); self.to_hash.dig(*keys); end

#extnameObject

Returns the Change Type’s :preferred_extension as a String with leading dot (.)



38
39
40
41
42
43
# File 'lib/distorted/element_of_media/change.rb', line 38

def extname
  @extname ||= begin
    dot = '.'.freeze unless type.preferred_extension.nil? || type.preferred_extension&.empty?
    "#{dot}#{type.preferred_extension}"
  end
end

#nameObject

Returns a String describing the :names but rolled into one, e.g. “IIDX-turntable-(400|800|1500).png”



57
58
59
60
# File 'lib/distorted/element_of_media/change.rb', line 57

def name
  tags = self[:breaks].length > 1 ? "-(#{self[:breaks].join('|'.freeze)})" : ''.freeze
  "#{self.basename}#{tags}#{self.extname}"
end

#namesObject

Returns an Array of filenames this Change should generate, one ‘full’/‘original’ plus any limit-breaks, e.g. [“DistorteD.png”, “DistorteD-333.png”, “DistorteD-555.png”, “DistorteD-888.png”, “DistorteD-1111.png”]



48
49
50
51
52
53
# File 'lib/distorted/element_of_media/change.rb', line 48

def names
  Array[''.freeze].concat(self[:breaks]).map { |b|
    filetag = (b.nil? || b&.to_s.empty?) ? ''.freeze : '-'.concat(b.to_s)
    "#{self[:basename]}#{"-#{self.tag}" unless self.tag.nil?}#{filetag}#{extname}"
  }
end

#path(dest_root, break_value) ⇒ Object

Returns a String absolute destination path for only one limit-break.



70
71
72
73
# File 'lib/distorted/element_of_media/change.rb', line 70

def path(dest_root, break_value)
  output_dir = self[:atoms]&.fetch(:dir, ''.freeze)
  return File.join(File.expand_path(dest_root), output_dir, "#{self.basename}#{"-#{self.tag}" unless self.tag.nil?}-#{break_value}#{self.extname}")
end

#paths(dest_root = ''.freeze) ⇒ Object

Returns an Array of all absolute destination paths this Change should generate, given a root destination directory.



64
65
66
67
# File 'lib/distorted/element_of_media/change.rb', line 64

def paths(dest_root = ''.freeze)  # Empty String will expand to current working directory
  output_dir = self[:atoms]&.fetch(:dir, ''.freeze)
  return self.names.map { |n| File.join(File.expand_path(dest_root), output_dir, n) }
end

#to_hObject

Struct#to_h does exist in stdlib, but redefine its behavior to match our ‘:to_hash`.



89
90
91
# File 'lib/distorted/element_of_media/change.rb', line 89

def to_h  # Explicit
  Hash[self.members.reject{|m| m == :atoms}.zip(self.values.reject{|v| v.is_a?(Hash)})].merge(self[:atoms].transform_values(&:get))
end

#to_hashObject

A generic version of Struct#to_hash was rejected with good reason, but I’m going to use it here because I want the implicit Struct-to-Hash conversion to let me destructure these Structs with a double-splat: bugs.ruby-lang.org/issues/4862

Defining this method causes Ruby 2.7 to emit a “Using the last argument as keyword parameters is deprecated” warning if this Struct is passed to a method as the final positional argument! Ruby 2.7 will actually do the conversion when calling the method in that scenario, causing incorrect behavior to methods expecting Struct. This is why DD-Floor’s ‘:write` and DD-Jekyll’s ‘:render_to_output_buffer` pass an empty kwargs Hash. www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/



85
86
87
# File 'lib/distorted/element_of_media/change.rb', line 85

def to_hash  # Implicit
  Hash[self.members.reject{|m| m == :atoms}.zip(self.values.reject{|v| v.is_a?(Hash)})].merge(self[:atoms].transform_values(&:get))
end