Module: AIXM::Refinements

Defined in:
lib/aixm/refinements.rb

Constant Summary collapse

UPTRANS_FILTER =
%r(
  [^A-Z0-9, !"&#$%'\(\)\*\+\-\./:;<=>\?@\[\\\]\^_\|\{\}]
)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



286
287
288
289
290
# File 'lib/aixm/refinements.rb', line 286

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)


221
222
223
224
225
# File 'lib/aixm/refinements.rb', line 221

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)


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

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



302
303
304
305
306
307
# File 'lib/aixm/refinements.rb', line 302

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:



269
270
271
272
273
274
275
# File 'lib/aixm/refinements.rb', line 269

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



139
140
141
142
143
144
145
# File 'lib/aixm/refinements.rb', line 139

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

#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)


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

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:



185
186
187
188
189
# File 'lib/aixm/refinements.rb', line 185

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)


252
253
254
255
256
# File 'lib/aixm/refinements.rb', line 252

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



331
332
333
334
335
336
337
338
339
340
341
# File 'lib/aixm/refinements.rb', line 331

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_degFloat

Note:

This is a refinement for Float

Convert an angle from radian to degrees.

Examples:

0.5.to_rad
# => 28.6478897565

Returns:

  • (Float)

    angle in degrees



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

refine Numeric do
  def to_deg
    180 * self / Math::PI
  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 Numeric 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_pretty_xmlString

Note:

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

Pretty printing alternative of to_xml

Examples:

xml = <<~END
  <aaa></aaa>
    <bbb/>
  <ccc   foo="bar"  >
    <ddd>
  </ddd>
  </ccc>
END
Nokogiri::XML::DocumentFragment.parse(xml).to_pretty_xml
# => <aaa/>
     <bbb/>
     <ccc foo="bar">
       <ddd/>
     </ccc>

Returns:

  • (String)


168
169
170
171
172
173
174
# File 'lib/aixm/refinements.rb', line 168

refine Nokogiri::XML::DocumentFragment do
  def to_pretty_xml
    builder = Nokogiri::XML::Builder.new
    builder.DocumentFragment { _1 << self.to_html }
    AIXM::Refinements::PRETTY_XSLT.transform(builder.doc).at_css('DocumentFragment').children.map(&:to_xml).join("\n")
  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)

    angle in radian



96
97
98
99
100
# File 'lib/aixm/refinements.rb', line 96

refine Numeric 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



352
353
354
355
356
# File 'lib/aixm/refinements.rb', line 352

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



113
114
115
116
117
# File 'lib/aixm/refinements.rb', line 113

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



373
374
375
376
377
378
379
380
381
382
# File 'lib/aixm/refinements.rb', line 373

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