Class: Pod::YAMLHelper

Inherits:
Object
  • Object
show all
Defined in:
lib/cocoapods-core/yaml_helper.rb

Overview

TODO:

Remove any code required solely for Ruby 1.8.x.

Note:

This class misses important features necessary for a correct YAML serialization and thus it is safe to use only for the Lockfile. The missing features include:

  • Strings are never quoted even when ambiguous.

Converts objects to their YAML representation.

This class was created for the need having control on how the YAML is representation is generated. In details it provides:

  • sorting for hashes in ruby 1.8.x
  • ability to hint the sorting of the keys of a dictionary when converting it. In this case the keys are also separated by an additional new line feed for readability.

Array Sorting collapse

RESOLVED_TAGS =
Regexp.union(
  'null', 'Null', 'NULL', '~', '', # resolve to null
  'true', 'True', 'TRUE', 'false', 'False', 'FALSE', # bool
  'yes', 'Yes', 'YES', 'no', 'No', 'NO', # yes/no
  'on', 'On', 'ON', 'off', 'Off', 'OFF', # no/off
  Regexp.new(%{
    [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] # (ymd)
   |[0-9][0-9][0-9][0-9] # (year)
    -[0-9][0-9]? # (month)
    -[0-9][0-9]? # (day)
    ([Tt]|[ \t]+)[0-9][0-9]? # (hour)
    :[0-9][0-9] # (minute)
    :[0-9][0-9] # (second)
    (\.[0-9]*)? # (fraction)
    (([ \t]*)(Z|[-+][0-9][0-9]?(:[0-9][0-9])?))? # (time zone)
  }, Regexp::EXTENDED), # https://yaml.org/type/timestamp.html
  /[-+]?[0-9]+/, # base 10 int
  /00[0-7]+/, # base 8 int
  /0x[0-9a-fA-F]+/, # base 16 int
  /[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?/, # float
  /[-+]?\.(inf|Inf|INF)/, # infinity
  /\.(nan|NaN|NAN)/ # NaN
)
INDICATOR_START_CHARS =
%w(- ? : , [ ] { } # & * ! | > ' " % @ `).freeze
INDICATOR_START =
/\A#{Regexp.union(INDICATOR_START_CHARS)}/.freeze
RESOLVED_TAGS_PATTERN =
/\A#{Regexp.union(RESOLVED_TAGS)}\z/.freeze
VALID_PLAIN_SCALAR_STRING =
%r{\A
  [\w&&[^#{INDICATOR_START_CHARS}]] # valid first character
  [\w/\ \(\)~<>=\.:`,-]* # all characters allowed after the first one
\z}ox.freeze

Private Helpers collapse

Array Sorting collapse

Class Method Summary collapse

Class Method Details

.convert(value) ⇒ String

Returns the YAML representation of the given object. If the given object is a Hash, it accepts an optional hint for sorting the keys.

Parameters:

  • object (String, Symbol, Array, Hash)

    the object to convert

  • hash_keys_hint (Array)

    an array to use as a hint for sorting the keys of the object to convert if it is a hash.

Returns:

  • (String)

    the YAML representation of the given object.



35
36
37
38
# File 'lib/cocoapods-core/yaml_helper.rb', line 35

def convert(value)
  result = process_according_to_class(value)
  result << "\n"
end

.convert_hash(value, hash_keys_hint, line_separator = "\n") ⇒ Object



40
41
42
43
# File 'lib/cocoapods-core/yaml_helper.rb', line 40

def convert_hash(value, hash_keys_hint, line_separator = "\n")
  result = process_hash(value, hash_keys_hint, line_separator)
  result << "\n"
end

.load_file(file_path) ⇒ Hash, Array

Loads a YAML file and leans on the #load_string imp to do error detection

Parameters:

  • file_path (Pathname)

    The file path to be used for read for the YAML file

Returns:

  • (Hash, Array)

    the Ruby YAML representaton



74
75
76
# File 'lib/cocoapods-core/yaml_helper.rb', line 74

def load_file(file_path)
  load_string(File.read(file_path), file_path)
end

.load_string(yaml_string, file_path = nil) ⇒ Hash, Array

Loads a YAML string and provide more informative error messages in special cases like merge conflict.

Parameters:

  • yaml_string (String)

    The YAML String to be loaded

  • file_path (Pathname) (defaults to: nil)

    The (optional) file path to be used for read for the YAML file

Returns:

  • (Hash, Array)

    the Ruby YAML representaton



56
57
58
59
60
61
62
63
64
# File 'lib/cocoapods-core/yaml_helper.rb', line 56

def load_string(yaml_string, file_path = nil)
  YAML.safe_load(yaml_string, :permitted_classes => [Date, Time, Symbol])
rescue
  if yaml_has_merge_error?(yaml_string)
    raise Informative, yaml_merge_conflict_msg(yaml_string, file_path)
  else
    raise Informative, yaml_parsing_error_msg(yaml_string, file_path)
  end
end

.process_according_to_class(value, hash_keys_hint = nil) ⇒ String (private)

Returns the YAML representation of the given object.

Returns:

  • (String)

    the YAML representation of the given object.



95
96
97
98
99
100
101
102
# File 'lib/cocoapods-core/yaml_helper.rb', line 95

def process_according_to_class(value, hash_keys_hint = nil)
  case value
  when Array      then process_array(value)
  when Hash       then process_hash(value, hash_keys_hint)
  when String     then process_string(value)
  else                 YAML.dump(value, :line_width => 2**31 - 1).sub(/\A---/, '').sub(/[.]{3}\s*\Z/, '')
  end.strip
end

.process_array(array) ⇒ String (private)

Converts an array to YAML after sorting it.

Parameters:

  • array (Array)

    the array to convert.

Returns:

  • (String)

    the YAML representation of the given object.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/cocoapods-core/yaml_helper.rb', line 111

def process_array(array)
  return '[]' if array.empty?

  result = sorted_array(array).map do |array_value|
    processed = process_according_to_class(array_value)
    case array_value
    when Array, Hash
      if array_value.size > 1
        processed = processed.gsub(/^.*/).to_a
        head = processed.shift
        processed.map { |s| "  #{s}" }.prepend(head).join("\n")
      else
        processed
      end
    else
      processed
    end
  end
  "- #{result.join("\n- ").strip}"
end

.process_hash(hash, hash_keys_hint = nil, line_separator = "\n") ⇒ String (private)

Note:

If a hint for sorting the keys is provided the array is assumed to be the root object and the keys are separated by an additional new line feed for readability.

Note:

If the value of a given key is a String it displayed inline, otherwise it is displayed below and indented.

Converts a hash to YAML after sorting its keys. Optionally accepts a hint for sorting the keys.

Parameters:

  • hash (Hash)

    the hash to convert.

Returns:

  • (String)

    the YAML representation of the given object.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/cocoapods-core/yaml_helper.rb', line 147

def process_hash(hash, hash_keys_hint = nil, line_separator = "\n")
  return '{}' if hash.empty?

  keys = sorted_array_with_hint(hash.keys, hash_keys_hint)
  key_lines = keys.map do |key|
    key_value = hash[key]
    processed = process_according_to_class(key_value)
    processed_key = process_according_to_class(key)
    case key_value
    when Hash, Array
      key_partial_yaml = processed.lines.map { |line| "  #{line}" } * ''
      "#{processed_key}:\n#{key_partial_yaml}"
    else
      "#{processed_key}: #{processed}"
    end
  end
  key_lines * line_separator
end

.process_string(string) ⇒ Object (private)



320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/cocoapods-core/yaml_helper.rb', line 320

def process_string(string)
  case string
  when RESOLVED_TAGS_PATTERN
    "'#{string}'"
  when /\A\s*\z/, INDICATOR_START, /:\z/
    string.inspect
  when VALID_PLAIN_SCALAR_STRING
    string
  else
    string.inspect
  end
end

.sorted_array(array) ⇒ Array

TODO:

This stuff is here only because the Lockfile intermixes strings and hashes for the PODS key. The Lockfile should be more consistent.

Note:

If the value contained in the array is another Array or a Hash the first value of the collection is used for sorting, as this method is more useful, for arrays which contains a collection composed by one object.

Sorts an array according to the string representation of it values. This method allows to sort arrays which contains strings or hashes.

Returns:

  • (Array)

    The sorted array.



256
257
258
259
260
# File 'lib/cocoapods-core/yaml_helper.rb', line 256

def sorted_array(array)
  array.each_with_index.sort_by do |element, index|
    [sorting_string(element), index]
  end.map(&:first)
end

.sorted_array_with_hint(array, sort_hint) ⇒ Array (private)

Sorts an array using another one as a sort hint. All the values of the hint which appear in the array will be returned respecting the order in the hint. If any other key is present in the original array they are sorted using the #sorted_array method.

Parameters:

  • array (Array)

    The array which needs to be sorted.

  • sort_hint (Array)

    The array which should be used to sort the keys.

Returns:

  • (Array)

    The sorted Array.



230
231
232
233
234
235
236
237
238
# File 'lib/cocoapods-core/yaml_helper.rb', line 230

def sorted_array_with_hint(array, sort_hint)
  if sort_hint
    hinted = sort_hint & array
    remaining = array - sort_hint
    hinted + sorted_array(remaining)
  else
    sorted_array(array)
  end
end

.sorting_string(value) ⇒ String (private)

Returns the string representation of a value useful for sorting.

Parameters:

  • value (String, Symbol, Array, Hash)

    The value which needs to be sorted

Returns:

  • (String)

    A string useful to compare the value with other ones.



271
272
273
274
275
276
277
278
279
280
# File 'lib/cocoapods-core/yaml_helper.rb', line 271

def sorting_string(value)
  return '' unless value
  case value
  when String then value.downcase
  when Symbol then sorting_string(value.to_s)
  when Array  then sorting_string(value.first)
  when Hash   then value.keys.map { |key| key.to_s.downcase }.sort.first
  else             raise ArgumentError, "Cannot sort #{value.inspect}"
  end
end

.yaml_has_merge_error?(yaml_string) ⇒ Boolean (private)

Check for merge errors in a YAML string.

Parameters:

  • yaml_string (String)

    A YAML string to evaluate

Returns:

  • (Boolean)

    If a merge error was detected or not.



173
174
175
# File 'lib/cocoapods-core/yaml_helper.rb', line 173

def yaml_has_merge_error?(yaml_string)
  yaml_string.include?('<<<<<<< HEAD')
end

.yaml_merge_conflict_msg(yaml, path = nil) ⇒ String (private)

Error message describing that a merge conflict was found while parsing the YAML.

Parameters:

  • yaml (String)

    Offending YAML

  • path (Pathname) (defaults to: nil)

    The (optional) offending path

Returns:

  • (String)

    The Error Message



188
189
190
191
192
193
# File 'lib/cocoapods-core/yaml_helper.rb', line 188

def yaml_merge_conflict_msg(yaml, path = nil)
  err = 'ERROR: Parsing unable to continue due '
  err += "to merge conflicts present in:\n"
  err += "the file located at #{path}\n" if path
  err + "#{yaml}"
end

.yaml_parsing_error_msg(yaml, path = nil) ⇒ String (private)

Error message describing a general error took happened while parsing the YAML.

Parameters:

  • yaml (String)

    Offending YAML

  • path (Pathname) (defaults to: nil)

    The (optional) offending path

Returns:

  • (String)

    The Error Message



206
207
208
209
210
211
# File 'lib/cocoapods-core/yaml_helper.rb', line 206

def yaml_parsing_error_msg(yaml, path = nil)
  err = 'ERROR: Parsing unable to continue due '
  err += "to parsing error:\n"
  err += "contained in the file located at #{path}\n" if path
  err + "#{yaml}"
end