Module: AIXM::Refinements

Defined in:
lib/aixm/refinements.rb

Constant Summary collapse

UPTRANS_FILTER =
%r(
  [^A-Z0-9, !"&#$%'\(\)\*\+\-\./:;<=>\[email protected]\[\\\]\^_\|\{\}]
)x.freeze
UPTRANS_MAP =
{
  'Ä' => 'AE',
  'Ö' => 'OE',
  'Ü' => 'UE',
  'Æ' => 'AE',
  'Œ' => 'OE',
  "Å" => "Aa",
  "Ø" => "Oe"
}.freeze
PRETTY_XSLT =
<<~END.then { Nokogiri::XSLT(_1) }
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="/">
      <xsl:copy-of select="."/>
    </xsl:template>
  </xsl:stylesheet>
END

Instance Method Summary collapse

Instance Method Details

#compactString

Note:

This is a refinement for String

Collapse whitespace to one space, but leave \n untouched, then strip what's left.

Examples:

"  foo\n\nbar  baz \r".compact   # => "foo\n\nbar baz"

Returns:

  • (String)

    compacted string


272
273
274
275
276
# File 'lib/aixm/refinements.rb', line 272

refine String do
  def compact
    split("\n").map { _1.gsub(/\s+/, ' ') }.join("\n").strip
  end
end

#decaptureRegexp

Note:

This is a refinement for Regexp

Replace all groups with non-caputuring groups

Examples:

/^(foo)(?<name>bar)/.decapture   # => /^(?:foo)(?:bar)/

Returns:

  • (Regexp)

207
208
209
210
211
# File 'lib/aixm/refinements.rb', line 207

refine Regexp do
  def decapture
    Regexp.new(to_s.gsub(/\(\?<\w+>|(?<![^\\]\\)\((?!\?)/, '(?:'))
  end
end

#dressString

Note:

This is a refinement for String

Prepends and appends the given string after stripping self. Quite the contrary of strip, hence the name.

Examples:

"     foobar\n\n".dress   # => " foobar "

Parameters:

  • padding (String)

    string to prepend and append

Returns:

  • (String)

223
224
225
226
227
# File 'lib/aixm/refinements.rb', line 223

refine String do
  def dress(padding=' ')
    [padding, strip, padding].join
  end
end

#indent(number) ⇒ String

Note:

This is a refinement for String

Indent every line of a string with number spaces.

Examples:

"foo\nbar".indent(2)
# => "  foo\n  bar"

Parameters:

  • number (Integer)

    number of spaces

Returns:

  • (String)

    line indended string


288
289
290
291
292
293
# File 'lib/aixm/refinements.rb', line 288

refine String do
  def indent(number)
    whitespace = ' ' * number
    gsub(/^/, whitespace)
  end
end

#inflectString

Note:

This is a refinement for String

Apply inflections from the dry-inflector gem

Examples:

s = "AIXM::Feature::NavigationalAid"
s.inflect(:demodulize, :tableize, :pluralize)
# => "navigational_aids"

Returns:

  • (String)

See Also:


255
256
257
258
259
260
261
# File 'lib/aixm/refinements.rb', line 255

refine String do
  def inflect(*inflections)
    inflections.inject(self) do |memo, inflection|
      AIXM.config.inflector.send(inflection, memo)
    end
  end
end

#lookup(key_or_value, fallback = omitted=true) ⇒ Object

Note:

This is a refinement for Hash

Fetch a value from the hash, but unlike Hash#fetch, if key_or_value is no hash key, check whether key_or_value is a hash value and if so return it.

Examples:

h = { one: 1, two: 2, three: 3, four: :three }
h.lookup(:one)              # => 1
h.lookup(1)                 # => 1
h.lookup(:three)            # => 3 (key has priority over value)
h.lookup(:foo)              # => KeyError
h.lookup(:foo, :fallback)   # => :fallback
h.lookup(:foo, nil)         # => nil

Parameters:

  • key_or_value (Object)

    key or value of the hash

  • fallback (Object) (defaults to: omitted=true)

    fallback value

Returns:

Raises:

  • (KeyError)

    if neither a matching hash key nor hash value are found and no fallback value has been passed


124
125
126
127
128
129
130
# File 'lib/aixm/refinements.rb', line 124

refine Hash do
  def lookup(key_or_value, fallback=omitted=true)
    self[key_or_value] ||
      (key_or_value if has_value?(key_or_value)) ||
      (omitted ? fail(KeyError, "key or value `#{key_or_value}' not found") : fallback)
  end
end

#prettyNokogiri::XML::Document

Note:

This is a refinement for Nokogiri::XML::Document

Transform the XML document to be pretty when sending to_xml

Examples:

xml = <<~END
  <xml><aaa> AAA </aaa>
    <bbb/>
    <ccc   foo="bar"  >
      CCC
    </ccc>
  </xml>
END
Nokogiri.XML(xml).pretty.to_xml
# => <?xml version=\"1.0\" encoding=\"UTF-8\"?>
     <xml>
       <aaa> AAA </aaa>
       <bbb/>
       <ccc foo="bar">
         CCC
       </ccc>
     </xml>

Returns:

  • (Nokogiri::XML::Document)

156
157
158
159
160
# File 'lib/aixm/refinements.rb', line 156

refine Nokogiri::XML::Document do
  def pretty
    PRETTY_XSLT.transform(self)
  end
end

#singleton_classRange

Note:

This is a refinement for Range

Returns a Range covering the given object.

To ease coverage tests in mixed arrays of single objects and object ranges, this method assures you're always dealing with objects. It returns self if it is already a Range, otherwise builds one with the given single object as both beginning and end.

Examples:

Range.from(5)      # => (5..5)
Range.from(1..3)   # => (1..3)

Parameters:

Returns:

  • (Range)

193
194
195
196
197
# File 'lib/aixm/refinements.rb', line 193

refine Range.singleton_class do
  def from(object)
    object.is_a?(Range) ? object : (object..object)
  end
end

#then_ifObject

Note:

This is a refinement for Object

Same as Object#then but only applied if the condition is true.

Examples:

"foobar".then_if(false) { _1.gsub(/o/, 'i') }   # => "foobar"
"foobar".then_if(true) { _1.gsub(/o/, 'i') }    # => "fiibar"

Returns:


171
172
173
174
175
# File 'lib/aixm/refinements.rb', line 171

refine Object do
  def then_if(condition, &block)   # TODO: [ruby-3.1] use anonymous block "&" on this and next line
    condition ? self.then(&block) : self
  end
end

#to_classClass

Note:

This is a refinement for String

Convert string to class

Examples:

"AIXM::Feature::NavigationalAid".to_class
# => AIXM::Feature::NavigationalAid

Returns:

  • (Class)

238
239
240
241
242
# File 'lib/aixm/refinements.rb', line 238

refine String do
  def to_class
    Object.const_get(self)
  end
end

#to_ddFloat

Note:

This is a refinement for String

Convert DMS angle to DD or nil if the notation is not recognized.

Supported notations:

  • {-}{DD}D°MM'SS{.SS}“{[NESW]}

  • {-}{DD}D MM SS{.SS} {[NESW]}

  • -}{DD}DMMSS{.SS

Quite a number of typos are tolerated such as the wrong use of minute ' and second markers as well as the use of decimal comma , instead of dot ..

Examples:

%q(43°12'77.92").to_dd
# => 43.22164444444445
"431277.92S".to_dd
# => -43.22164444444445
%q(-123).to_dd
# => nil

Returns:

  • (Float)

    angle in DD notation


317
318
319
320
321
322
323
324
325
326
327
# File 'lib/aixm/refinements.rb', line 317

refine String do
  def to_dd
    if match = self.match(DMS_RE)
      "#{match['sgn']}1".to_i * "#{:- if match['hem_sw']}1".to_i * (
        match['deg'].to_f +
        match['min'].to_f/60 +
        match['sec'].tr(',', '.').to_f/3600
      )
    end
  end
end

#to_digestString

Note:

This is a refinement for Array

Builds a 4 byte hex digest from the Array payload.

Examples:

['foo', :bar, nil, [123]].to_digest
# => "f3920098"

Returns:

  • (String)

    4 byte hex


37
38
39
40
41
# File 'lib/aixm/refinements.rb', line 37

refine Array do
  def to_digest
    ::Digest::SHA512.hexdigest(flatten.map(&:to_s).join('|'.freeze))[0, 8]
  end
end

#to_dms(padding = 3) ⇒ String

Note:

This is a refinement for Float

Convert DD angle to DMS with the degrees zero padded to padding length.

Examples:

43.22164444444445.to_dms(2)
# => "43°12'77.92\""
43.22164444444445.to_dms
# => "043°12'77.92\""

Parameters:

  • padding (Integer) (defaults to: 3)

    number of digits for the degree part

Returns:

  • (String)

    angle in DMS notation {-}D°MM'SS.SS“


56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/aixm/refinements.rb', line 56

refine Float do
  def to_dms(padding=3)
    degrees = self.abs.floor
    minutes = ((self.abs - degrees) * 60).floor
    seconds = (self.abs - degrees - minutes.to_f / 60) * 3600
    minutes, seconds = minutes + 1, 0 if seconds.round(2) == 60
    degrees, minutes = degrees + 1, 0 if minutes == 60
    %Q(%s%0#{padding}d°%02d'%05.2f") % [
      ('-' if self.negative?),
      self.abs.truncate,
      minutes.abs.truncate,
      seconds.abs
    ]
  end
end

#to_radFloat

Note:

This is a refinement for Float

Convert an angle from degree to radian.

Examples:

45.to_rad
# => 0.7853981633974483

Returns:

  • (Float)

    radian angle


81
82
83
84
85
# File 'lib/aixm/refinements.rb', line 81

refine Float do
  def to_rad
    self * Math::PI / 180
  end
end

#to_timeTime

Note:

This is a refinement for String

Parse string to date and time.

Examples:

'2018-01-01 15:00'.to_time
# => 2018-01-01 15:00:00 +0100

Returns:

  • (Time)

    date and time


338
339
340
341
342
# File 'lib/aixm/refinements.rb', line 338

refine String do
  def to_time
    Time.parse(self)
  end
end

#trimInteger, Float

Note:

This is a refinement for Float

Convert whole numbers to Integer and leave all other untouched.

Examples:

3.0.trim
# => 3
3.3.trim
# => 3.3

Returns:

  • (Integer, Float)

    converted Float


98
99
100
101
102
# File 'lib/aixm/refinements.rb', line 98

refine Float do
  def trim
    (self % 1).zero? ? self.to_i : self
  end
end

#uptransString

Note:

This is a refinement for String

Upcase and transliterate to match the reduced character set for AIXM names and titles.

See UPTRANS_MAP for supported diacryts and UPTRANS_FILTER for the list of allowed characters in the returned value.

Examples:

"Nîmes-Alès".uptrans
# => "NIMES-ALES"
"Zürich".uptrans
# => "ZUERICH"

Returns:

  • (String)

    upcased and transliterated String


359
360
361
362
363
364
365
366
367
368
# File 'lib/aixm/refinements.rb', line 359

refine String do
  def uptrans
    self.dup.tap do |string|
      string.upcase!
      string.gsub!(/(#{UPTRANS_MAP.keys.join('|'.freeze)})/, UPTRANS_MAP)
      string.unicode_normalize!(:nfd)
      string.gsub!(UPTRANS_FILTER, '')
    end
  end
end