Module: NRSER

Defined in:
lib/nrser.rb,
lib/nrser/hash.rb,
lib/nrser/array.rb,
lib/nrser/logger.rb,
lib/nrser/truthy.rb,
lib/nrser/version.rb,
lib/nrser/collection.rb,
lib/nrser/enumerable.rb,
lib/nrser/refinements.rb,
lib/nrser/refinements/hash.rb,
lib/nrser/refinements/array.rb,
lib/nrser/refinements/object.rb,
lib/nrser/refinements/string.rb,
lib/nrser/refinements/pathname.rb

Defined Under Namespace

Modules: Collection, Types Classes: Logger

Constant Summary collapse

TRUTHY_STRINGS =

Down-cased versions of strings that are considered to communicate truth.

Set.new [
  'true',
  't',
  'yes',
  'y',
  'on',
  '1',
]
FALSY_STRINGS =

Down-cased versions of strings that are considered to communicate false.

Set.new [
  'false',
  'f',
  'no',
  'n',
  'off',
  '0',
  '',
]
VERSION =
"0.0.18"

Class Method Summary collapse

Class Method Details

.as_array(value) ⇒ Array

Return an array given any value in the way that makes most sense:

  1. If ‘value` is an array, return it.

  2. If ‘value` is `nil`, return `[]`.

  3. If ‘value` responds to `#to_a`, try calling it. If it succeeds, return that.

  4. Return an array with ‘value` as it’s only item.

Refinement


Added to ‘Object` in `nrser/refinements`.

Parameters:

  • value (Object)

Returns:

  • (Array)


23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/nrser/array.rb', line 23

def self.as_array value
  return value if value.is_a? Array
  return [] if value.nil?
  
  if value.respond_to? :to_a
    begin
      return value.to_a
    rescue
    end
  end
  
  [value]
end

.as_hash(value, key = nil) ⇒ Hash

TODO:

It might be nice to have a ‘check` option that ensures the resulting hash has a value for `key`.

Treat the value as the value for ‘key` in a hash if it’s not already a hash and can’t be converted to one:

  1. If the value is a ‘Hash`, return it.

  2. If ‘value` is `nil`, return `{}`.

  3. If the value responds to ‘#to_h` and `#to_h` succeeds, return the resulting hash.

  4. Otherwise, return a new hash where ‘key` points to the value. **`key` MUST be provided in this case.**

Useful in method overloading and similar situations where you expect a hash that may specify a host of options, but want to allow the method to be called with a single value that corresponds to a default key in that option hash.

Refinement


Added to ‘Object` in `nrser/refinements`.

Example Time!


Say you have a method ‘m` that handles a hash of HTML options that can look something like

{class: 'address', data: {confirm: 'Really?'}}

And can call ‘m` like

m({class: 'address', data: {confirm: 'Really?'}})

but often you are just dealing with the ‘:class` option. You can use as_hash to accept a string and treat it as the `:class` key:

using NRSER

def m opts
  opts = opts.as_hash :class
  # ...
end

If you pass a hash, everything works normally, but if you pass a string ‘’address’‘ it will be converted to `’address’‘.

About ‘#to_h` Support


Right now, as_hash also tests if ‘value` responds to `#to_h`, and will try to call it, using the result if it doesn’t raise. This lets it deal with Ruby’s “I used to be a Hash until someone mapped me” values like ‘[[:class, ’address’]]‘. I’m not sure if this is the best approach, but I’m going to try it for now and see how it pans out in actual usage.

Parameters:

  • value (Object)

    The value that we want to be a hash.

  • key (Object) (defaults to: nil)
    default nil

    The key that ‘value` will be stored under in the result if `value` is not a hash or can’t be turned into one via ‘#to_h`. If this happens this value can NOT be `nil` or an `ArgumentError` is raised.

Returns:

  • (Hash)

Raises:

  • (ArgumentError)

    If it comes to constructing a new Hash with ‘value` as a value and no argument was provided



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/nrser/hash.rb', line 96

def self.as_hash value, key = nil
  return value if value.is_a? Hash
  return {} if value.nil?
  
  if value.respond_to? :to_h
    begin
      return value.to_h
    rescue
    end
  end
  
  # at this point we need a key argument
  if key.nil?
    raise ArgumentError,
          "Need key to construct hash with value #{ value.inspect }, " +
          "found nil."
  end
  
  {key => value}
end

.collection?(obj) ⇒ Boolean

test if an object is considered a collection.

Parameters:

  • obj (Object)

    object to test

Returns:

  • (Boolean)

    true if ‘obj` is a collection.



23
24
25
# File 'lib/nrser/collection.rb', line 23

def collection? obj
  Collection::STDLIB.any? {|cls| obj.is_a? cls} || obj.is_a?(Collection)
end

.common_prefix(strings) ⇒ Object

Raises:

  • (ArgumentError)


18
19
20
21
22
23
24
25
26
27
# File 'lib/nrser.rb', line 18

def common_prefix strings
  raise ArgumentError.new("argument can't be empty") if strings.empty?
  sorted = strings.sort.reject {|line| line == "\n"}
  i = 0
  while sorted.first[i] == sorted.last[i] &&
        i < [sorted.first.length, sorted.last.length].min
    i = i + 1
  end
  strings.first[0...i]
end

.dedent(str) ⇒ Object

common_prefix



29
30
31
32
33
34
35
36
37
# File 'lib/nrser.rb', line 29

def dedent str
  return str if str.empty?
  lines = str.lines
  indent = common_prefix(lines).match(/^\s*/)[0]
  return str if indent.empty?
  lines.map {|line|
    line = line[indent.length..line.length] if line.start_with? indent
  }.join
end

.each(obj) { ... } ⇒ Object

yield on each element of a collection or on the object itself if it’s not a collection. avoids having to normalize to an array to iterate over something that may be an object OR a collection of objects.

Parameters:

  • obj (Object)

    target object.

Yields:

  • each element of a collection or the target object itself.

Returns:

  • (Object)

    obj param.



37
38
39
40
41
42
43
44
# File 'lib/nrser/collection.rb', line 37

def each obj, &block
  if collection? obj
    obj.each &block
  else
    block.call obj
    obj
  end
end

.erb(bnd, str) ⇒ Object Also known as: template

filter_repeated_blank_lines



57
58
59
60
# File 'lib/nrser.rb', line 57

def erb bnd, str
  require 'erb'
  filter_repeated_blank_lines ERB.new(dedent(str)).result(bnd)
end

.except_keys(hash, *keys) ⇒ Hash

Returns a new hash without ‘keys`.

Lifted from ActiveSupport.

Parameters:

  • hash (Hash)

    Source hash.

Returns:

  • (Hash)

See Also:



184
185
186
# File 'lib/nrser/hash.rb', line 184

def self.except_keys hash, *keys
  except_keys! hash.dup, *keys
end

.except_keys!(hash, *keys) ⇒ Hash

Removes the given keys from hash and returns it.

Lifted from ActiveSupport.

Parameters:

  • hash (Hash)

    Hash to mutate.

Returns:

  • (Hash)

See Also:



165
166
167
168
# File 'lib/nrser/hash.rb', line 165

def self.except_keys! hash, *keys
  keys.each { |key| hash.delete(key) }
  hash
end

.falsy?(object) ⇒ Boolean

Opposite of truthy?.

Parameters:

  • object (Nil, String)

    Value to test.

Returns:

  • (Boolean)

    The negation of truthy?.



68
69
70
# File 'lib/nrser/truthy.rb', line 68

def self.falsy? object
  ! truthy?(object)
end

.filter_repeated_blank_lines(str) ⇒ Object

dedent



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/nrser.rb', line 39

def filter_repeated_blank_lines str
  out = []
  lines = str.lines
  skipping = false
  str.lines.each do |line|
    if line =~ /^\s*$/
      unless skipping
        out << line
      end
      skipping = true
    else
      skipping = false
      out << line
    end
  end
  out.join
end

.format_exception(e) ⇒ Object



64
65
66
# File 'lib/nrser.rb', line 64

def format_exception e
  "#{ e.message } (#{ e.class }):\n  #{ e.backtrace.join("\n  ") }"
end

.indent(str, amount = 2, indent_string = nil, indent_empty_lines = false) ⇒ Object



72
73
74
75
76
# File 'lib/nrser.rb', line 72

def indent str, amount = 2, indent_string=nil, indent_empty_lines=false
  indent_string = indent_string || str[/^[ \t]/] || ' '
  re = indent_empty_lines ? /^/ : /^(?!$)/
  str.gsub(re, indent_string * amount)
end

.leaves(hash, path: [], results: {}) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
# File 'lib/nrser/hash.rb', line 3

def self.leaves hash, path: [], results: {}
  hash.each { |key, value|
    new_path = [*path, key]
    
    case value
    when Hash
      leaves value, path: new_path, results: results
    else
      results[new_path] = value
    end
  }
  
  results
end

.map(obj) { ... } ⇒ Object

if ‘obj` is a collection, calls `#map` with the block. otherwise, applies block to the object and returns the result.

Parameters:

  • obj (Object)

    target object.

Yields:

  • each element of a collection or the target object itself.

Returns:

  • (Object)

    the result of mapping or applying the block.



55
56
57
58
59
60
61
# File 'lib/nrser/collection.rb', line 55

def map obj, &block
  if collection? obj
    obj.map &block
  else
    block.call obj
  end
end

.map_hash_values(hash, &block) ⇒ return_type

TODO:

Document map_hash_values method.

Returns @todo Document return value.

Parameters:

  • hash (Hash)

    @todo Add name param description.

Returns:

  • (return_type)

    @todo Document return value.



127
128
129
130
131
# File 'lib/nrser/hash.rb', line 127

def self.map_hash_values hash, &block
  result = {}
  hash.each { |key, value| result[key] = block.call key, value }
  result
end

.map_values(enumerable, &block) ⇒ Hash

Maps an enumerable object to a new hash with the same keys and values obtained by calling ‘block` with the current key and value.

If ‘enumerable` *does not* sucessfully respond to `#to_h` then it’s treated as a hash where it’s elements are the keys and all the values are ‘nil`.

Returns:

  • (Hash)


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/nrser/enumerable.rb', line 11

def self.map_values enumerable, &block
  # Short-cut for Hash itself - though it would of course work through the
  # next step, it's going to probably be *the* most common argument type,
  # and there's no reason to do the tests and set up the exception 
  # handler if we already know what's up with it.
  return NRSER.map_hash_values(enumerable) if enumerable.is_a? Hash
  
  if enumerable.respond_to? :to_h
    begin
      hash = enumerable.to_h
    rescue TypeError => e
    else
      return NRSER.map_hash_values hash
    end
  end
  
  result = {}
  enumerable.each { |key| result[key] = block.call key, nil }
  result
end

.slice_keys(hash, *keys) ⇒ Object

Lifted from ActiveSupport.



262
263
264
265
266
267
268
269
270
271
# File 'lib/nrser/hash.rb', line 262

def self.slice_keys hash, *keys
  # We're not using this, but, whatever, leave it in...
  if hash.respond_to?(:convert_key, true)
    keys.map! { |key| hash.convert_key(key) }
  end
  
  keys.each_with_object(hash.class.new) { |k, new_hash|
    new_hash[k] = hash[k] if hash.has_key?(k)
  }
end

.squish(str) ⇒ Object Also known as: unblock

turn a multi-line string into a single line, collapsing whitespace to a single space.

same as ActiveSupport’s String.squish, adapted from there.



12
13
14
# File 'lib/nrser.rb', line 12

def squish str
  str.gsub(/[[:space:]]+/, ' ').strip
end

.symbolize_keys(hash) ⇒ Object



252
253
254
255
# File 'lib/nrser/hash.rb', line 252

def self.symbolize_keys hash
  # File 'lib/active_support/core_ext/hash/keys.rb', line 54
  transform_keys(hash) { |key| key.to_sym rescue key }
end

.symbolize_keys!(hash) ⇒ return_type

Mutates ‘hash` by converting all keys that respond to `#to_sym` to symbols.

Lifted from ActiveSupport.

Parameters:

  • hash (Hash)

    Hash to mutate.

Returns:

  • (return_type)

    @todo Document return value.

See Also:



246
247
248
# File 'lib/nrser/hash.rb', line 246

def self.symbolize_keys! hash
  transform_keys!(hash) { |key| key.to_sym rescue key }
end

.transform_keys(hash, &block) ⇒ Hash

Returns a new hash with each key transformed by the provided block.

Lifted from ActiveSupport.

Parameters:

  • hash (Hash)

Returns:

  • (Hash)

    New hash with transformed keys.

See Also:



221
222
223
224
225
226
227
228
# File 'lib/nrser/hash.rb', line 221

def self.transform_keys hash, &block
  # File 'lib/active_support/core_ext/hash/keys.rb', line 12
  result = {}
  hash.each_key do |key|
    result[yield(key)] = hash[key]
  end
  result
end

.transform_keys!(hash) ⇒ Hash

Lifted from ActiveSupport.

Parameters:

  • hash (Hash)

    Hash to mutate keys.

Returns:

  • (Hash)

    The mutated hash.

See Also:



201
202
203
204
205
206
207
# File 'lib/nrser/hash.rb', line 201

def self.transform_keys! hash
  # File 'lib/active_support/core_ext/hash/keys.rb', line 23
  hash.keys.each do |key|
    hash[yield(key)] = hash.delete(key)
  end
  hash
end

.truncate(str, truncate_at, options = {}) ⇒ Object

Truncates a given text after a given length if text is longer than length:

'Once upon a time in a world far far away'.truncate(27)
# => "Once upon a time in a wo..."

Pass a string or regexp :separator to truncate text at a natural break:

'Once upon a time in a world far far away'.truncate(27, separator: ' ')
# => "Once upon a time in a..."

'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
# => "Once upon a time in a..."

The last characters will be replaced with the :omission string (defaults to “…”) for a total length not exceeding length:

'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
# => "And they f... (continued)"

adapted from

<github.com/rails/rails/blob/7847a19f476fb9bee287681586d872ea43785e53/activesupport/lib/active_support/core_ext/string/filters.rb#L46>



101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/nrser.rb', line 101

def truncate(str, truncate_at, options = {})
  return str.dup unless str.length > truncate_at

  omission = options[:omission] || '...'
  length_with_room_for_omission = truncate_at - omission.length
  stop = \
    if options[:separator]
      str.rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
    else
      length_with_room_for_omission
    end

  "#{str[0, stop]}#{omission}"
end

.truthy?(object) ⇒ Boolean

Evaluate an object (that probably came from outside Ruby, like an environment variable) to see if it’s meant to represent true or false.

Parameters:

  • object (Nil, String)

    Value to test.

Returns:

  • (Boolean)

    ‘true` if the object is “truthy”.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/nrser/truthy.rb', line 34

def self.truthy? object
  case object
  when nil
    false
    
  when String
    downcased = object.downcase
    
    if TRUTHY_STRINGS.include? downcased
      true
    elsif FALSY_STRINGS.include? downcased
      false
    else
      raise ArgumentError,
            "String #{ object.inspect } not recognized as true or false."
    end
    
  when TrueClass, FalseClass
    object
    
  else
    raise TypeError,
          "Can't evaluate truthiness of #{ object.inspect }"
  end
end