Module: NRSER
- Defined in:
- lib/nrser.rb,
lib/nrser/rspex.rb,
lib/nrser/errors.rb,
lib/nrser/logger.rb,
lib/nrser/no_arg.rb,
lib/nrser/message.rb,
lib/nrser/version.rb,
lib/nrser/collection.rb,
lib/nrser/temp/where.rb,
lib/nrser/types/array.rb,
lib/nrser/types/pairs.rb,
lib/nrser/types/paths.rb,
lib/nrser/types/labels.rb,
lib/nrser/functions/path.rb,
lib/nrser/functions/proc.rb,
lib/nrser/functions/array.rb,
lib/nrser/meta/props/prop.rb,
lib/nrser/refinements/set.rb,
lib/nrser/functions/string.rb,
lib/nrser/refinements/hash.rb,
lib/nrser/functions/binding.rb,
lib/nrser/refinements/array.rb,
lib/nrser/refinements/types.rb,
lib/nrser/temp/unicode_math.rb,
lib/nrser/functions/merge_by.rb,
lib/nrser/refinements/object.rb,
lib/nrser/refinements/string.rb,
lib/nrser/refinements/symbol.rb,
lib/nrser/functions/exception.rb,
lib/nrser/functions/hash/bury.rb,
lib/nrser/refinements/binding.rb,
lib/nrser/functions/enumerable.rb,
lib/nrser/functions/text/lines.rb,
lib/nrser/refinements/pathname.rb,
lib/nrser/functions/open_struct.rb,
lib/nrser/functions/tree/leaves.rb,
lib/nrser/refinements/exception.rb,
lib/nrser/rspex/shared_examples.rb,
lib/nrser/refinements/enumerator.rb,
lib/nrser/functions/object/truthy.rb,
lib/nrser/functions/tree/map_tree.rb,
lib/nrser/refinements/open_struct.rb,
lib/nrser/functions/object/as_hash.rb,
lib/nrser/functions/text/word_wrap.rb,
lib/nrser/functions/tree/transform.rb,
lib/nrser/functions/hash/deep_merge.rb,
lib/nrser/functions/hash/slice_keys.rb,
lib/nrser/functions/object/as_array.rb,
lib/nrser/functions/tree/map_leaves.rb,
lib/nrser/functions/hash/except_keys.rb,
lib/nrser/functions/text/indentation.rb,
lib/nrser/functions/tree/each_branch.rb,
lib/nrser/functions/string/looks_like.rb,
lib/nrser/functions/tree/map_branches.rb,
lib/nrser/functions/enumerable/find_map.rb,
lib/nrser/functions/hash/stringify_keys.rb,
lib/nrser/functions/hash/symbolize_keys.rb,
lib/nrser/functions/hash/transform_keys.rb,
lib/nrser/functions/enumerable/find_all_map.rb,
lib/nrser/functions/hash/guess_label_key_type.rb
Overview
Definitions
Defined Under Namespace
Modules: Collection, Ext, Meta, RSpex, Refinements, Types, UnicodeMath, Version Classes: AbstractMethodError, ConflictError, Lines, Logger, Message, MultipleErrors, NoArg, SendSerializer, Where
String Functions collapse
- WHITESPACE_RE =
/\A[[:space:]]*\z/
- UNICODE_ELLIPSIS =
'…'
- JSON_ARRAY_RE =
Regexp used to guess if a string is a JSON-encoded array.
/\A\s*\[.*\]\s*\z/m.freeze
Text Functions collapse
- INDENT_RE =
Constants
/\A[\ \t]*/
- INDENT_TAG_MARKER =
"\x1E"
- INDENT_TAG_SEPARATOR =
"\x1F"
Object Functions collapse
- TRUTHY_STRINGS =
Down-cased versions of strings that are considered to communicate true in things like ENV vars, CLI options, etc.
Set.new [ 'true', 't', 'yes', 'y', 'on', '1', ].freeze
- FALSY_STRINGS =
Down-cased versions of strings that are considered to communicate false in things like ENV vars, CLI options, etc.
Set.new [ 'false', 'f', 'no', 'n', 'off', '0', '', ].freeze
Constant Summary collapse
- ROOT =
Absolute, expanded path to the gem’s root directory.
( Pathname.new(__FILE__).dirname / '..' ).
- NO_ARG =
NoArg.instance
- VERSION =
"0.1.0"
Path Functions collapse
-
.find_up(rel_path, from: Pathname.pwd, glob: :guess, test: :exist?, result: :common_root) ⇒ nil, ...
Ascend the directory tree starting at ‘from` (defaults to working directory) looking for a relative path.
-
.find_up!(*args) ⇒ Object
Exactly like NRSER.find_up but raises if nothing is found.
- .looks_globish?(path) ⇒ return_type
- .pn_from(path) ⇒ Pathname
String Functions collapse
- .common_prefix(strings) ⇒ Object
-
.constantize(camel_cased_word) ⇒ Object
Get the constant identified by a string.
-
.ellipsis(string, max, omission: UNICODE_ELLIPSIS) ⇒ String
Cut the middle out of a string and stick an ellipsis in there instead.
- .filter_repeated_blank_lines(str, remove_leading: false) ⇒ Object
- .lazy_filter_repeated_blank_lines(source, remove_leading: false) ⇒ Object
-
.looks_like_json_array?(string) ⇒ Boolean
Test if a string looks like it might encode an array in JSON format by seeing if it’s first non-whitespace character is ‘[` and last non-whitespace character is `]`.
-
.smart_ellipsis(string, max, omission: UNICODE_ELLIPSIS, split: ', ') ⇒ String
Try to do “smart” job adding ellipsis to the middle of strings by splitting them by a separator ‘split` - that defaults to `, ` - then building the result up by bouncing back and forth between tokens at the beginning and end of the string until we reach the `max` length limit.
-
.squish(str) ⇒ Object
turn a multi-line string into a single line, collapsing whitespace to a single space.
-
.truncate(str, truncate_at, options = {}) ⇒ Object
Truncates a given
text
after a givenlength
iftext
is longer thanlength
:. - .whitespace?(string) ⇒ Boolean
Exception Functions collapse
-
.format_exception(e) ⇒ String
String format an exception the same way they are printed to the CLI when not handled (when they crash programs - what you’re used to seeing), including the message, class and backtrace.
Hash Functions collapse
-
.bury!(hash, key_path, value, parsed_key_type: :guess, clobber: false, create_arrays_for_unsigned_keys: false) ⇒ return_type
The opposite of ‘#dig` - set a value at a deep key path, creating necessary structures along the way and optionally clobbering whatever’s in the way to achieve success.
-
.deep_merge(base_hash, other_hash, &block) ⇒ Hash
Returns a new hash created by recursively merging ‘other_hash` on top of `base_hash`.
-
.deep_merge!(base_hash, other_hash, &block) ⇒ Hash
Same as NRSER.deep_merge, but modifies ‘base_hash`.
- .deep_stringify_keys(object) ⇒ return_type
- .deep_symbolize_keys(object, &block) ⇒ return_type
-
.deep_transform_keys(object, &block) ⇒ Object
Deeply transform Hash keys that we can find by traversing Hash and Array instances that we can find from ‘object` and piping keys through `block`.
-
.deep_transform_keys!(object, &block) ⇒ Object
Like NRSER.deep_transform_keys but mutates the objects (works in place).
-
.except_keys(hash, *keys) ⇒ Hash
Returns a new hash without ‘keys`.
-
.except_keys!(hash, *keys) ⇒ Hash
Removes the given keys from hash and returns it.
-
.guess_label_key_type(keyed) ⇒ nil, Class
Guess which type of “label” key - strings or symbols - a hash (or other object that responds to ‘#keys` and `#empty`) uses.
-
.slice_keys(hash, *keys) ⇒ Object
Lifted from ActiveSupport.
-
.slice_keys!(hash, *keys) ⇒ Object
Meant to be a drop-in replacement for the ActiveSupport version, though I’ve changed the implementation a bit…
-
.stringify_keys(hash) ⇒ Hash<String, *>
Returns a new hash with all keys transformed to strings by calling ‘#to_s` on them.
-
.stringify_keys!(hash) ⇒ Hash<String, *>
Converts all keys into strings by calling ‘#to_s` on them.
-
.symbolize_keys(hash) ⇒ Hash
Returns a new hash with all keys that respond to ‘#to_sym` converted to symbols.
-
.symbolize_keys!(hash) ⇒ Hash
Mutates ‘hash` by converting all keys that respond to `#to_sym` to symbols.
-
.transform_keys(hash, &block) ⇒ Hash
Returns a new hash with each key transformed by the provided block.
-
.transform_keys!(hash) ⇒ Hash
Lifted from ActiveSupport.
Enumerable Functions collapse
-
.array_like?(object) ⇒ Boolean
Test if an object is “array-like” - is it an Enumerable and does it respond to ‘#each_index`?.
-
.count_by(enum, &block) ⇒ Hash{C=>Integer}
Count entries in an Enumerable by the value returned when they are passed to the block.
-
.enumerate_as_values(enum) ⇒ Enumerator
Create an Enumerator that iterates over the “values” of an Enumerable ‘enum`.
-
.find_all_map(enum, &block) ⇒ nil, R
Find all truthy (not ‘nil` or `false`) results of calling `&block` with entries from `enum`.
-
.find_bounded(enum, bounds, &block) ⇒ Array<E>
Find all entries in an Enumerable for which ‘&block` returns a truthy value, then check the amount of results found against the Types.length created from `bounds`, raising a TypeError if the results’ length doesn’t satisfy the bounds type.
-
.find_map(enum, ifnone = nil, &block) ⇒ nil, ...
Find the first truthy (not ‘nil` or `false`) result of calling `&block` with entries from `enum`.
-
.find_only(enum, &block) ⇒ E
Find the only entry in ‘enum` for which `&block` responds truthy, raising if either no entries or more than one are found.
-
.hash_like?(object) ⇒ Boolean
Test if an object is “hash-like” - is it an Enumerable and does it respond to ‘#each_pair`?.
-
.map_values(enum) {|key, value| ... } ⇒ 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.
-
.only(enum, default: nil) ⇒ E, D
Return the first entry if the enumerable has ‘#count` one.
-
.only!(enum) ⇒ E
Return the only entry if the enumerable has ‘#count` one.
-
.to_h_by(enum, &block) ⇒ Hash<K, V>
Convert an enumerable to a hash by passing each entry through ‘&block` to get it’s key, raising an error if multiple entries map to the same key.
-
.try_find(enum, &block) ⇒ V
Like ‘Enumerable#find`, but wraps each call to `&block` in a `begin` / `rescue`, returning the result of the first call that doesn’t raise an error.
Text Functions collapse
- .dedent(text, ignore_whitespace_lines: true) ⇒ Object
-
.find_indent(text) ⇒ Object
Functions =====================================================================.
-
.indent(text, amount = 2, indent_string: nil, indent_empty_lines: false, skip_first_line: false) ⇒ Object
adapted from active_support 4.2.0.
-
.indent_tag(text, marker: INDENT_TAG_MARKER, separator: INDENT_TAG_SEPARATOR) ⇒ String
Tag each line of ‘text` with special marker characters around it’s leading indent so that the resulting text string can be fed through an interpolation process like ERB that may inject multiline strings and the result can then be fed through NRSER.indent_untag to apply the correct indentation to the interpolated lines.
-
.indent_untag(text, marker: INDENT_TAG_MARKER, separator: INDENT_TAG_SEPARATOR) ⇒ String
Reverse indent tagging that was done via NRSER.indent_tag, indenting any untagged lines to the same level as the one above them.
- .indented?(text) ⇒ Boolean
-
.lines(text) ⇒ Object
Functions =====================================================================.
-
.with_indent_tagged(text, marker: INDENT_TAG_MARKER, separator: INDENT_TAG_SEPARATOR, &interpolate_block) ⇒ String
Indent tag a some text via NRSER.indent_tag, call the block with it, then pass the result through NRSER.indent_untag and return that.
-
.word_wrap(text, line_width: 80, break_sequence: "\n") ⇒ String
Split text at whitespace to fit in line length.
Tree Functions collapse
-
.each_branch(tree) {|key, value| ... } ⇒ Enumerator, #each_pair | (#each_index & #each_with_index)
Enumerate over the immediate “branches” of a structure that can be used to compose our idea of a tree: nested hash-like and array-like structures like you would get from parsing a JSON document.
-
.leaves(tree) ⇒ Hash<Array, Object>
Create a new hash where all the values are the scalar “leaves” of the possibly nested ‘hash` param.
-
.map_branches(tree) {|key, value| ... } ⇒ Array | Hash
Map the immediate “branches” of a structure that can be used to compose our idea of a tree: nested hash-like and array-like structures like you would get from parsing a JSON document.
- .map_leaves(tree, &block) ⇒ Object
Object Functions collapse
-
.as_array(value) ⇒ Array
Return an array given any value in the way that makes most sense:.
-
.as_hash(value, key = nil) ⇒ Hash
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:.
-
.falsy?(object) ⇒ Boolean
Opposite of NRSER.truthy?.
-
.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.
Class Method Summary collapse
-
.chainer(mappable, publicly: true) ⇒ Proc
Map *each entry* in ‘mappable` to a Message and return a Proc that accepts a single `receiver` argument and reduces it by applying each message in turn.
-
.collection?(obj) ⇒ Boolean
test if an object is considered a collection.
-
.each(object) { ... } ⇒ Object
Yield on each element of a collection or on the object itself if it’s not a collection.
- .erb(bnd, str) ⇒ Object (also: template)
-
.extract_from_array!(array, &block) ⇒ Object
A destructive partition.
-
.map(object) { ... } ⇒ Object
If ‘object` is a collection, calls `#map` with the block.
-
.map_tree(tree, prune: false) {|element| ... } ⇒ Object
Recursively descend through a tree mapping all non-structural elements - anything not Types.hash_like or Types.array_like, both hash keys and values, as well as array entries - through ‘block` to produce a new structure.
-
.merge_by(current, *updates, &merge_key) ⇒ Array<Hash>
Deep merge arrays of data hashes, matching hashes by computing a key with ‘&merge_key`.
-
.message(*args, &block) ⇒ NRSER::Message
Creates a new Message from the array.
-
.private_sender(symbol, *args, &block) ⇒ Proc
Create a Proc that sends the arguments to a receiver via ‘#send`, forcing access to private and protected methods.
-
.public_sender(symbol, *args, &block) ⇒ Proc
Create a Proc that sends the arguments to a receiver via ‘#public_send`.
-
.rest(array) ⇒ return_type
Functional implementation of “rest” for arrays.
-
.retriever(key) ⇒ Proc
Return a Proc that accepts a single argument that must respond to ‘#[]` and retrieves `key` from it.
-
.to_open_struct(hash, freeze: false) ⇒ OpenStruct
Deeply convert a Hash to an OpenStruct.
- .transform(tree, source) ⇒ return_type
- .transformer(&block) ⇒ return_type
Class Method Details
.array_like?(object) ⇒ Boolean
Test if an object is “array-like” - is it an Enumerable and does it respond to ‘#each_index`?
17 18 19 20 |
# File 'lib/nrser/functions/enumerable.rb', line 17 def self.array_like? object object.is_a?( ::Enumerable ) && object.respond_to?( :each_index ) end |
.as_array(value) ⇒ Array
Return an array given any value in the way that makes most sense:
-
If ‘value` is an array, return it.
-
If ‘value` is `nil`, return `[]`.
-
If ‘value` responds to `#to_a`, try calling it. If it succeeds, return that.
-
Return an array with ‘value` as it’s only item.
Refinement
Added to ‘Object` in `nrser/refinements`.
25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/nrser/functions/object/as_array.rb', line 25 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
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:
-
If the value is a ‘Hash`, return it.
-
If ‘value` is `nil`, return `{}`.
-
If the value responds to ‘#to_h` and `#to_h` succeeds, return the resulting hash.
-
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.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/nrser/functions/object/as_hash.rb', line 82 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 |
.bury!(hash, key_path, value, parsed_key_type: :guess, clobber: false, create_arrays_for_unsigned_keys: false) ⇒ return_type
The opposite of ‘#dig` - set a value at a deep key path, creating necessary structures along the way and optionally clobbering whatever’s in the way to achieve success.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/nrser/functions/hash/bury.rb', line 45 def self.bury! hash, key_path, value, parsed_key_type: :guess, clobber: false, create_arrays_for_unsigned_keys: false # Parse the key if it's not an array unless key_path.is_a?( Array ) key_path = key_path.to_s.split '.' # Convert the keys to symbols now if that's what we want to use if parsed_key_type == Symbol key_path.map! &:to_sym end end _internal_bury! \ hash, key_path, value, guess_key_type: ( parsed_key_type == :guess ), clobber: clobber, create_arrays_for_unsigned_keys: create_arrays_for_unsigned_keys end |
.chainer(mappable, publicly: true) ⇒ Proc
‘mappable“ entries are mapped into messages when #to_chain is called, meaning subsequent changes to `mappable` **will not** affect the returned proc.
Map *each entry* in ‘mappable` to a Message and return a Proc that accepts a single `receiver` argument and reduces it by applying each message in turn.
In less precise terms: create a proc that chains the entries as methods calls.
87 88 89 90 91 92 93 94 95 |
# File 'lib/nrser/functions/proc.rb', line 87 def self.chainer mappable, publicly: true = mappable.map { |value| *value } ->( receiver ) { .reduce( receiver ) { |receiver, | .send_to receiver, publicly: publicly } } end |
.collection?(obj) ⇒ Boolean
test if an object is considered a collection.
25 26 27 |
# File 'lib/nrser/collection.rb', line 25 def collection? obj Collection::STDLIB.any? {|cls| obj.is_a? cls} || obj.is_a?(Collection) end |
.common_prefix(strings) ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/nrser/functions/string.rb', line 30 def self.common_prefix strings raise ArgumentError.new("argument can't be empty") if strings.empty? sorted = strings.sort i = 0 while sorted.first[i] == sorted.last[i] && i < [sorted.first.length, sorted.last.length].min i = i + 1 end sorted.first[0...i] end |
.constantize(camel_cased_word) ⇒ Object
Get the constant identified by a string.
Lifted from ActiveSupport.
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/nrser/functions/string.rb', line 264 def self.constantize(camel_cased_word) names = camel_cased_word.split('::') # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(camel_cased_word) if names.empty? # Remove the first blank element in case of '::ClassName' notation. names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object constant.const_get(name) else candidate = constant.const_get(name) next candidate if constant.const_defined?(name, false) next candidate unless Object.const_defined?(name) # Go down the ancestors to check if it is owned directly. The check # stops when we reach Object or the end of ancestors tree. constant = constant.ancestors.inject do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) const end # owner is in Object, so raise constant.const_get(name, false) end end end |
.count_by(enum, &block) ⇒ Hash{C=>Integer}
Count entries in an Enumerable by the value returned when they are passed to the block.
294 295 296 297 298 |
# File 'lib/nrser/functions/enumerable.rb', line 294 def self.count_by enum, &block enum.each_with_object( Hash.new 0 ) do |entry, hash| hash[block.call entry] += 1 end end |
.dedent(text, ignore_whitespace_lines: true) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/nrser/functions/text/indentation.rb', line 55 def self.dedent text, ignore_whitespace_lines: true return text if text.empty? all_lines = text.lines indent_significant_lines = if ignore_whitespace_lines all_lines.reject { |line| whitespace? line } else all_lines end indent = find_indent indent_significant_lines return text if indent.empty? all_lines.map { |line| if line.start_with? indent line[indent.length..-1] elsif line.end_with? "\n" "\n" else "" end }.join end |
.deep_merge(base_hash, other_hash, &block) ⇒ Hash
Returns a new hash created by recursively merging ‘other_hash` on top of `base_hash`.
Adapted from ActiveSupport.
28 29 30 |
# File 'lib/nrser/functions/hash/deep_merge.rb', line 28 def self.deep_merge base_hash, other_hash, &block deep_merge! base_hash.dup, other_hash, &block end |
.deep_merge!(base_hash, other_hash, &block) ⇒ Hash
Same as deep_merge, but modifies ‘base_hash`.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/nrser/functions/hash/deep_merge.rb', line 38 def self.deep_merge! base_hash, other_hash, &block other_hash.each_pair do |current_key, other_value| this_value = base_hash[current_key] base_hash[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash) deep_merge this_value, other_value, &block else if block_given? && base_hash.key?( current_key ) block.call(current_key, this_value, other_value) else other_value end end end base_hash end |
.deep_stringify_keys(object) ⇒ return_type
Document deep_stringify_keys method.
Returns @todo Document return value.
48 49 50 |
# File 'lib/nrser/functions/hash/stringify_keys.rb', line 48 def self.deep_stringify_keys object deep_transform_keys object, &:to_s end |
.deep_symbolize_keys(object, &block) ⇒ return_type
Document deep_symbolize_keys method.
Returns @todo Document return value.
52 53 54 |
# File 'lib/nrser/functions/hash/symbolize_keys.rb', line 52 def self.deep_symbolize_keys object, &block deep_transform_keys( object ) { key.to_sym rescue key } end |
.deep_transform_keys(object, &block) ⇒ Object
Maybe this is a tree function?
Deeply transform Hash keys that we can find by traversing Hash and Array instances that we can find from ‘object` and piping keys through `block`.
From ActiveSupport.
92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/nrser/functions/hash/transform_keys.rb', line 92 def self.deep_transform_keys object, &block case object when Hash object.each_with_object( {} ) do |(key, value), result| result[block.call( key )] = deep_transform_keys value, &block end when Array object.map { |entry| deep_transform_keys entry, &block } else object end end |
.deep_transform_keys!(object, &block) ⇒ Object
Like deep_transform_keys but mutates the objects (works in place).
119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/nrser/functions/hash/transform_keys.rb', line 119 def self.deep_transform_keys! object, &block case object when Hash object.keys.each do |key| value = object.delete key object[block.call( key )] = deep_transform_keys! value, &block end object when Array object.map! {|e| deep_transform_keys!(e, &block)} else object end end |
.each(object) { ... } ⇒ 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.
NOTE Implemented for our idea of a collection instead of testing
for response to `#each` (or similar) to avoid catching things
like {IO} instances, which include {Enumerable} but are
probably not what is desired when using {NRSER.each}
(more likely that you mean "I expect one or more files" than
"I expect one or more strings which may be represented by
lines in an open {File}").
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/nrser/collection.rb', line 51 def each object, &block if collection? object # We need to test for response because {OpenStruct} *will* respond to # #each because *it will respond to anything* (which sucks), but it # will return `false` for `respond_to? :each` and the like, and this # behavior could be shared by other collection objects, so it seems # like a decent idea. if object.respond_to? :each_pair object.each_pair &block elsif object.respond_to? :each object.each &block else raise TypeError.squished <<-END Object #{ obj.inpsect } does not respond to #each or #each_pair END end else block.call object end object end |
.each_branch(tree) {|key, value| ... } ⇒ Enumerator, #each_pair | (#each_index & #each_with_index)
Not sure what will happen if the tree has circular references!
Enumerate over the immediate “branches” of a structure that can be used to compose our idea of a tree: nested hash-like and array-like structures like you would get from parsing a JSON document.
Written and tested against Hash and Array instances, but should work with anything hash-like that responds to ‘#each_pair` appropriately or array-like that responds to `#each_index` and `#each_with_index`.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/nrser/functions/tree/each_branch.rb', line 40 def self.each_branch tree, &block if tree.respond_to? :each_pair # Hash-like tree.each_pair &block elsif tree.respond_to? :each_index # Array-like... we test for `each_index` because - unintuitively - # `#each_with_index` is a method of {Enumerable}, meaning that {Set} # responds to it, though sets are unordered and the values can't be # accessed via those indexes. Hence we look for `#each_index`, which # {Set} does not respond to. if block.nil? index_enumerator = tree.each_with_index Enumerator.new( index_enumerator.size ) { |yielder| index_enumerator.each { |value, index| yielder.yield [index, value] } } else tree.each_with_index.map { |value, index| block.call [index, value] } end else raise NoMethodError.new NRSER.squish <<-END `tree` param must respond to `#each_pair` or `#each_index`, found #{ tree.inspect } END end # if / else end |
.ellipsis(string, max, omission: UNICODE_ELLIPSIS) ⇒ String
Cut the middle out of a string and stick an ellipsis in there instead.
140 141 142 143 144 145 146 147 148 149 |
# File 'lib/nrser/functions/string.rb', line 140 def self.ellipsis string, max, omission: UNICODE_ELLIPSIS return string unless string.length > max trim_to = max - omission.length start = string[0, (trim_to / 2) + (trim_to % 2)] finish = string[-( (trim_to / 2) - (trim_to % 2) )..-1] start + omission + finish end |
.enumerate_as_values(enum) ⇒ Enumerator
Create an Enumerator that iterates over the “values” of an Enumerable ‘enum`. If `enum` responds to `#each_value` than we return that. Otherwise, we return `#each_entry`.
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/nrser/functions/enumerable.rb', line 256 def self.enumerate_as_values enum # NRSER.match enum, # t.respond_to(:each_value), :each_value.to_proc, # t.respond_to(:each_entry), :each_entry.to_proc # if enum.respond_to? :each_value enum.each_value elsif enum.respond_to? :each_entry enum.each_entry else raise ArgumentError.new erb binding, <<-END Expected `enum` arg to respond to :each_value or :each_entry, found: <%= enum.inspect %> END end end |
.erb(bnd, str) ⇒ Object Also known as: template
4 5 6 7 8 9 10 11 12 13 |
# File 'lib/nrser/functions/binding.rb', line 4 def erb bnd, str require 'erb' filter_repeated_blank_lines( with_indent_tagged( dedent( str ) ) { |tagged_str| ERB.new( tagged_str ).result( bnd ) }, remove_leading: true ) end |
.except_keys(hash, *keys) ⇒ Hash
Returns a new hash without ‘keys`.
Lifted from ActiveSupport.
38 39 40 |
# File 'lib/nrser/functions/hash/except_keys.rb', line 38 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.
19 20 21 22 |
# File 'lib/nrser/functions/hash/except_keys.rb', line 19 def self.except_keys! hash, *keys keys.each { |key| hash.delete(key) } hash end |
.extract_from_array!(array, &block) ⇒ Object
A destructive partition.
17 18 19 20 21 22 23 24 25 26 27 |
# File 'lib/nrser/functions/array.rb', line 17 def self.extract_from_array! array, &block extracted = [] array.reject! { |entry| test = block.call entry if test extracted << entry end test } extracted end |
.falsy?(object) ⇒ Boolean
Opposite of truthy?.
107 108 109 |
# File 'lib/nrser/functions/object/truthy.rb', line 107 def self.falsy? object ! truthy?(object) end |
.filter_repeated_blank_lines(str, remove_leading: false) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/nrser/functions/string.rb', line 46 def self.filter_repeated_blank_lines str, remove_leading: false out = [] lines = str.lines skipping = remove_leading 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 |
.find_all_map(enum, &block) ⇒ nil, R
Find all truthy (not ‘nil` or `false`) results of calling `&block` with entries from `enum`.
29 30 31 |
# File 'lib/nrser/functions/enumerable/find_all_map.rb', line 29 def self.find_all_map enum, &block enum.map( &block ).select { |entry| entry } end |
.find_bounded(enum, bounds, &block) ⇒ Array<E>
Find all entries in an Enumerable for which ‘&block` returns a truthy value, then check the amount of results found against the NRSER::Types.length created from `bounds`, raising a TypeError if the results’ length doesn’t satisfy the bounds type.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/nrser/functions/enumerable.rb', line 115 def self.find_bounded enum, bounds, &block NRSER::Types. length(bounds). check(enum.find_all &block) { |type:, value:| erb binding, <<-END Length of found elements (<%= value.length %>) FAILED to satisfy <%= type.to_s %>. Found entries: <%= value.pretty_inspect %> from enumerable: <%= enum.pretty_inspect %> END } end |
.find_indent(text) ⇒ Object
Functions
17 18 19 |
# File 'lib/nrser/functions/text/indentation.rb', line 17 def self.find_indent text common_prefix lines( text ).map { |line| line[INDENT_RE] } end |
.find_map(enum, ifnone = nil, &block) ⇒ nil, ...
Find the first truthy (not ‘nil` or `false`) result of calling `&block` with entries from `enum`.
Like Enumerable#find, accept an optional ‘ifnone` procedure to call if no match is found.
41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/nrser/functions/enumerable/find_map.rb', line 41 def self.find_map enum, ifnone = nil, &block enum.each do |entry| if result = block.call( entry ) # Found a match, short-circuit return result end end # No matches, return `ifnone` ifnone.call if ifnone end |
.find_only(enum, &block) ⇒ E
Find the only entry in ‘enum` for which `&block` responds truthy, raising if either no entries or more than one are found.
Returns the entry itself, not an array of length 1.
Just calls find_bounded with ‘bounds = 1`.
153 154 155 |
# File 'lib/nrser/functions/enumerable.rb', line 153 def self.find_only enum, &block find_bounded(enum, 1, &block).first end |
.find_up(rel_path, from: Pathname.pwd, glob: :guess, test: :exist?, result: :common_root) ⇒ nil, ...
There should be a way to cut the search off early or detect ‘**` in the `rel_path` and error out or something to prevent full FS search.
Ascend the directory tree starting at ‘from` (defaults to working directory) looking for a relative path.
How it works and what it returns is dependent on the sent options.
In the simplest / default case:
1.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/nrser/functions/path.rb', line 100 def self.find_up( rel_path, from: Pathname.pwd, glob: :guess, test: :exist?, result: :common_root ) # If `glob` is `:guess`, override `glob` with the result of # {.looks_globish?} # glob = looks_globish?( rel_path ) if glob == :guess found = find_map( pn_from( from ).ascend ) { |dir| path = dir / rel_path found_path = if glob Pathname.glob( path ).find { |match_path| match_path.public_send test } else path.public_send test end unless found_path.nil? [dir, found_path] end } return nil if found.nil? dir, path = found Types.match result, :common_root, dir, :pair, found, :path, path end |
.find_up!(*args) ⇒ Object
Exactly like find_up but raises if nothing is found.
141 142 143 144 145 146 147 |
# File 'lib/nrser/functions/path.rb', line 141 def self.find_up! *args find_up( *args ).tap { |result| if result.nil? raise "HERE! #{ args.inspect }" end } end |
.format_exception(e) ⇒ String
String format an exception the same way they are printed to the CLI when not handled (when they crash programs - what you’re used to seeing), including the message, class and backtrace.
13 14 15 |
# File 'lib/nrser/functions/exception.rb', line 13 def self.format_exception e "#{ e. } (#{ e.class }):\n #{ e.backtrace.join("\n ") }" end |
.guess_label_key_type(keyed) ⇒ nil, Class
Guess which type of “label” key - strings or symbols - a hash (or other object that responds to ‘#keys` and `#empty`) uses.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/nrser/functions/hash/guess_label_key_type.rb', line 23 def self.guess_label_key_type keyed # We can't tell shit if the hash is empty return nil if keyed.empty? name_types = keyed. keys. map( &:class ). select { |klass| klass == String || klass == Symbol }. uniq return name_types[0] if name_types.length == 1 # There are both string and symbol keys present, we can't guess nil end |
.hash_like?(object) ⇒ Boolean
Test if an object is “hash-like” - is it an Enumerable and does it respond to ‘#each_pair`?
32 33 34 35 |
# File 'lib/nrser/functions/enumerable.rb', line 32 def self.hash_like? object object.is_a?( ::Enumerable ) && object.respond_to?( :each_pair ) end |
.indent(text, amount = 2, indent_string: nil, indent_empty_lines: false, skip_first_line: false) ⇒ Object
adapted from active_support 4.2.0
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/nrser/functions/text/indentation.rb', line 31 def self.indent text, amount = 2, indent_string: nil, indent_empty_lines: false, skip_first_line: false if skip_first_line lines = self.lines text lines.first + indent( rest( lines ).join, amount, indent_string: indent_string, skip_first_line: false ) else indent_string = indent_string || text[/^[ \t]/] || ' ' re = indent_empty_lines ? /^/ : /^(?!$)/ text.gsub re, indent_string * amount end end |
.indent_tag(text, marker: INDENT_TAG_MARKER, separator: INDENT_TAG_SEPARATOR) ⇒ String
Tag each line of ‘text` with special marker characters around it’s leading indent so that the resulting text string can be fed through an interpolation process like ERB that may inject multiline strings and the result can then be fed through indent_untag to apply the correct indentation to the interpolated lines.
Each line of ‘text` is re-formatted like:
"<marker><leading_indent><separator><line_without_leading_indent>"
‘marker` and `separator` can be configured via keyword arguments, but they
default to:
-
‘marker` - INDENT_TAG_MARKER, the no-printable ASCII *record separator* (ASCII character 30, “x1E” / “u001E”).
-
‘separator` - INDENT_TAG_SEPARATOR, the non-printable ASCII *unit separator* (ASCII character 31, “x1F” / “u001F”)
121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/nrser/functions/text/indentation.rb', line 121 def self.indent_tag text, marker: INDENT_TAG_MARKER, separator: INDENT_TAG_SEPARATOR text.lines.map { |line| indent = if match = INDENT_RE.match( line ) match[0] else '' end "#{ marker }#{ indent }#{ separator }#{ line[indent.length..-1] }" }.join end |
.indent_untag(text, marker: INDENT_TAG_MARKER, separator: INDENT_TAG_SEPARATOR) ⇒ String
Reverse indent tagging that was done via indent_tag, indenting any untagged lines to the same level as the one above them.
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/nrser/functions/text/indentation.rb', line 151 def self.indent_untag text, marker: INDENT_TAG_MARKER, separator: INDENT_TAG_SEPARATOR current_indent = '' text.lines.map { |line| if line.start_with? marker current_indent, line = line[marker.length..-1].split( separator, 2 ) end current_indent + line }.join end |
.indented?(text) ⇒ Boolean
22 23 24 |
# File 'lib/nrser/functions/text/indentation.rb', line 22 def self.indented? text !( find_indent( text ).empty? ) end |
.lazy_filter_repeated_blank_lines(source, remove_leading: false) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/nrser/functions/string.rb', line 65 def self.lazy_filter_repeated_blank_lines source, remove_leading: false skipping = remove_leading source = source.each_line if source.is_a? String Enumerator::Lazy.new source do |yielder, line| if line =~ /^\s*$/ unless skipping yielder << line end skipping = true else skipping = false yielder << line end end end |
.leaves(tree) ⇒ Hash<Array, Object>
Create a new hash where all the values are the scalar “leaves” of the possibly nested ‘hash` param. Leaves are keyed by “key path” arrays representing the sequence of keys to dig that leaf out of the has param.
In abstract, if ‘h` is the `hash` param and
l = NRSER.leaves h
then for each key ‘k` and corresponding value `v` in `l`
h.dig( *k ) == v
51 52 53 54 55 |
# File 'lib/nrser/functions/tree/leaves.rb', line 51 def self.leaves tree {}.tap { |results| _internal_leaves tree, path: [], results: results } end |
.lines(text) ⇒ Object
Functions
40 41 42 43 44 45 46 47 48 49 |
# File 'lib/nrser/functions/text/lines.rb', line 40 def self.lines text case text when String text.lines when Array text else raise TypeError, "Expected String or Array, found #{ text.class.name }" end end |
.looks_globish?(path) ⇒ return_type
Document glob? method.
Returns @todo Document return value.
23 24 25 |
# File 'lib/nrser/functions/path.rb', line 23 def self.looks_globish? path %w|* ? [ {|.any? &path.to_s.method( :include? ) end |
.looks_like_json_array?(string) ⇒ Boolean
Test if a string looks like it might encode an array in JSON format by seeing if it’s first non-whitespace character is ‘[` and last non-whitespace character is `]`.
40 41 42 |
# File 'lib/nrser/functions/string/looks_like.rb', line 40 def self.looks_like_json_array? string !!( string =~ JSON_ARRAY_RE ) end |
.map(object) { ... } ⇒ Object
If ‘object` is a collection, calls `#map` with the block. Otherwise, applies block to the object and returns the result.
See note in each for discussion of why this tests for a collection instead of duck-typing ‘#map`.
89 90 91 92 93 94 95 |
# File 'lib/nrser/collection.rb', line 89 def map object, &block if collection? object object.map &block else block.call object end end |
.map_branches(tree) {|key, value| ... } ⇒ Array | Hash
Might be nice to have an option to preserve the tree class that creates a new instance of whatever it was and populates that, though I could see this relying on problematic assumptions and producing confusing results depending on the actual classes.
Maybe this could be encoded in a mixin that we would detect or something.
Not sure what will happen if the tree has circular references!
Map the immediate “branches” of a structure that can be used to compose our idea of a tree: nested hash-like and array-like structures like you would get from parsing a JSON document.
The ‘block` MUST return a pair (Array of length 2), the first value of which is the key or index in the new Hash or Array.
These pairs are then converted into a Hash or Array depending on it ‘tree` was NRSER::Types.hash_like or NRSER::Types.array_like, and that value is returned.
Uses each_branch internally.
Written and tested against Hash and Array instances, but should work with anything:
-
hash-like that responds to ‘#each_pair` appropriately.
-
array-like that responds to ‘#each_index` and `#each_with_index` appropriately.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/nrser/functions/tree/map_branches.rb', line 64 def self.map_branches tree, &block if block.nil? raise ArgumentError, "Must provide block" end pairs = each_branch( tree ).map &block if hash_like? tree pairs.to_h elsif array_like? tree pairs.each_with_object( [] ) { |(index, value), array| array[index] = value } else raise TypeError.new erb binding, <<-END Excepted `tree` arg to be array or hash-like. Received (<%= tree.class %>): <%= tree.pretty_inspect %> END end end |
.map_leaves(tree, &block) ⇒ Object
5 6 7 8 9 |
# File 'lib/nrser/functions/tree/map_leaves.rb', line 5 def self.map_leaves tree, &block NRSER::Types.tree.check tree _internal_map_leaves tree, key_path: [], &block end |
.map_tree(tree, prune: false) {|element| ... } ⇒ Object
Array indexes **are not mapped** through ‘block` and can not be changed via this method. This makes it easier to do things like “convert all the integers to strings” when you mean the data entries, not the array indexes (which would fail since the new array wouldn’t accept string indices).
If you don’t want to map hash keys use map_leaves.
Recursively descend through a tree mapping all non-structural elements
-
anything not NRSER::Types.hash_like or NRSER::Types.array_like, both
hash keys and values, as well as array entries - through ‘block` to produce a new structure.
Useful when you want to translate pieces of a tree structure depending on their type or some other property that can be determined *from the element alone* - ‘block` receives only the value as an argument, no location information (because it’s weirder to represent for keys and I didn’t need it for the transformer stuff this was written for).
See the specs for examples. Used in transformer.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/nrser/functions/tree/map_tree.rb', line 35 def self.map_tree tree, prune: false, &block # TODO type check tree? mapped = tree.map { |element| # Recur if `element` is a tree. # # Since `element` will be an {Array} of `key`, `value` when `tree` is a # {Hash} (or similar), this will descend into hash keys that are also # trees, as well as into hash values and array entries. # if Types.tree.test element map_tree element, prune: prune, &block else # When we've run out of trees, finally pipe through the block: block.call element end } # If `tree` is hash-like, we want to convert the array of pair arrays # back into a hash. if Types.hash_like.test tree if prune pruned = {} mapped.each { |key, value| if Types.label.test( key ) && key.to_s.end_with?( '?' ) unless value.nil? new_key = key.to_s[0..-2] if key.is_a?( Symbol ) new_key = new_key.to_sym end pruned[new_key] = value end else pruned[key] = value end } pruned else mapped.to_h end else # Getting here means it was array-like, so it's already fine mapped end end |
.map_values(enum) {|key, value| ... } ⇒ 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* respond to `#to_pairs` then it’s treated as a hash where the elements iterated by ‘#each` are it’s keys and all it’s values are ‘nil`.
In this way, map_values handles Hash, Array, Set, OpenStruct, and probably pretty much anything else reasonable you may throw at it.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/nrser/functions/enumerable.rb', line 66 def self.map_values enum, &block result = {} if enum.respond_to? :each_pair enum.each_pair { |key, value| result[key] = block.call key, value } elsif enum.respond_to? :each enum.each { |key| result[key] = block.call key, nil } else raise ArgumentError.new erb binding, <<-END First argument to {NRSER.map_values} must respond to #each_pair or #each Received <%= enum.pretty_inspect %> of class <%= enum.class %> END end result end |
.merge_by(current, *updates, &merge_key) ⇒ Array<Hash>
Deep merge arrays of data hashes, matching hashes by computing a key with ‘&merge_key`.
Uses deep_merge! to merge.
23 24 25 26 27 |
# File 'lib/nrser/functions/merge_by.rb', line 23 def self.merge_by current, *updates, &merge_key updates.reduce( to_h_by current, &merge_key ) { |result, update| deep_merge! result, to_h_by( update, &merge_key ) }.values end |
.message(*args, &block) ⇒ NRSER::Message
Creates a new Message from the array.
13 14 15 16 17 18 19 |
# File 'lib/nrser/functions/proc.rb', line 13 def self. *args, &block if args.length == 1 && args[0].is_a?( Message ) args[0] else Message.new *args, &block end end |
.only(enum, default: nil) ⇒ E, D
Return the first entry if the enumerable has ‘#count` one.
Otherwise, return ‘default` (which defaults to `nil`).
175 176 177 178 179 180 181 |
# File 'lib/nrser/functions/enumerable.rb', line 175 def self.only enum, default: nil if enum.count == 1 enum.first else default end end |
.only!(enum) ⇒ E
Return the only entry if the enumerable has ‘#count` one. Otherwise raise an error.
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/nrser/functions/enumerable.rb', line 195 def self.only! enum unless enum.count == 1 raise ArgumentError.new erb binding, <<-END Expected enumerable to have #count == 1 but it has #count = <%= enum.count %> Enumerable (class: <%= enum.class %>): <%= enum.pretty_inspect %> END end enum.first end |
.pn_from(path) ⇒ Pathname
6 7 8 9 10 11 12 |
# File 'lib/nrser/functions/path.rb', line 6 def self.pn_from path if path.is_a? Pathname path else Pathname.new path end end |
.private_sender(symbol, *args, &block) ⇒ Proc
Create a Proc that sends the arguments to a receiver via ‘#send`, forcing access to private and protected methods.
Equivalent to
( symbol, *args, &block ).to_proc publicly: false
Pretty much here for completeness’ sake.
63 64 65 |
# File 'lib/nrser/functions/proc.rb', line 63 def self.private_sender symbol, *args, &block ( symbol, *args, &block ).to_proc publicly: false end |
.public_sender(symbol, *args, &block) ⇒ Proc
Create a Proc that sends the arguments to a receiver via ‘#public_send`.
Equivalent to
( symbol, *args, &block ).to_proc
Pretty much here for completeness’ sake.
39 40 41 |
# File 'lib/nrser/functions/proc.rb', line 39 def self.public_sender symbol, *args, &block ( symbol, *args, &block ).to_proc end |
.rest(array) ⇒ return_type
Functional implementation of “rest” for arrays. Used when refining ‘#rest` into Array.
11 12 13 |
# File 'lib/nrser/functions/array.rb', line 11 def self.rest array array[1..-1] end |
.retriever(key) ⇒ Proc
Return a Proc that accepts a single argument that must respond to ‘#[]` and retrieves `key` from it.
108 109 110 |
# File 'lib/nrser/functions/proc.rb', line 108 def self.retriever key ->( indexed ) { indexed[key] } end |
.slice_keys(hash, *keys) ⇒ Object
Lifted from ActiveSupport.
13 14 15 16 17 18 19 20 21 22 |
# File 'lib/nrser/functions/hash/slice_keys.rb', line 13 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.send :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 |
.slice_keys!(hash, *keys) ⇒ Object
Meant to be a drop-in replacement for the ActiveSupport version, though I’ve changed the implementation a bit… because honestly I didn’t understand why they were doing it the way they do :/
32 33 34 35 36 37 38 39 40 41 |
# File 'lib/nrser/functions/hash/slice_keys.rb', line 32 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.send :convert_key, key } end slice_keys( hash, *keys ).tap { |slice| except_keys! hash, *keys } end |
.smart_ellipsis(string, max, omission: UNICODE_ELLIPSIS, split: ', ') ⇒ String
Try to do “smart” job adding ellipsis to the middle of strings by splitting them by a separator ‘split` - that defaults to `, ` - then building the result up by bouncing back and forth between tokens at the beginning and end of the string until we reach the `max` length limit.
Intended to be used with possibly long single-line strings like ‘#inspect` returns for complex objects, where tokens are commonly separated by `, `, and producing a reasonably nice result that will fit in a reasonable amount of space, like `rspec` output (which was the motivation).
If ‘string` is already less than `max` then it is just returned.
If ‘string` doesn’t contain ‘split` or just the first and last tokens alone would push the result over `max` then falls back to ellipsis.
If ‘max` is too small it’s going to fall back nearly always… around ‘64` has seemed like a decent place to start from screwing around on the REPL a bit.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/nrser/functions/string.rb', line 199 def self.smart_ellipsis string, max, omission: UNICODE_ELLIPSIS, split: ', ' return string unless string.length > max unless string.include? split return ellipsis string, max, omission: omission end tokens = string.split split char_budget = max - omission.length start = tokens[0] + split finish = tokens[tokens.length - 1] if start.length + finish.length > char_budget return ellipsis string, max, omission: omission end next_start_index = 1 next_finish_index = tokens.length - 2 next_index_is = :start next_index = next_start_index while ( start.length + finish.length + tokens[next_index].length + split.length ) <= char_budget do if next_index_is == :start start += tokens[next_index] + split next_start_index += 1 next_index = next_finish_index next_index_is = :finish else # == :finish finish = tokens[next_index] + split + finish next_finish_index -= 1 next_index = next_start_index next_index_is = :start end end start + omission + finish end |
.squish(str) ⇒ Object
turn a multi-line string into a single line, collapsing whitespace to a single space.
same as ActiveSupport’s String.squish, adapted from there.
23 24 25 |
# File 'lib/nrser/functions/string.rb', line 23 def self.squish str str.gsub(/[[:space:]]+/, ' ').strip end |
.stringify_keys(hash) ⇒ Hash<String, *>
Returns a new hash with all keys transformed to strings by calling ‘#to_s` on them.
Lifted from ActiveSupport.
33 34 35 |
# File 'lib/nrser/functions/hash/stringify_keys.rb', line 33 def self.stringify_keys hash transform_keys hash, &:to_s end |
.stringify_keys!(hash) ⇒ Hash<String, *>
Converts all keys into strings by calling ‘#to_s` on them. **Mutates the hash.**
Lifted from ActiveSupport.
17 18 19 |
# File 'lib/nrser/functions/hash/stringify_keys.rb', line 17 def self.stringify_keys! hash transform_keys! hash, &:to_s end |
.symbolize_keys(hash) ⇒ Hash
Returns a new hash with all keys that respond to ‘#to_sym` converted to symbols.
Lifted from ActiveSupport.
36 37 38 39 |
# File 'lib/nrser/functions/hash/symbolize_keys.rb', line 36 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) ⇒ Hash
Mutates ‘hash` by converting all keys that respond to `#to_sym` to symbols.
Lifted from ActiveSupport.
18 19 20 |
# File 'lib/nrser/functions/hash/symbolize_keys.rb', line 18 def self.symbolize_keys! hash transform_keys!(hash) { |key| key.to_sym rescue key } end |
.to_h_by(enum, &block) ⇒ Hash<K, V>
Convert an enumerable to a hash by passing each entry through ‘&block` to get it’s key, raising an error if multiple entries map to the same key.
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/nrser/functions/enumerable.rb', line 227 def self.to_h_by enum, &block enum.each_with_object( {} ) { |element, result| key = block.call element if result.key? key raise NRSER::ConflictError.new erb binding, <<-END Key <%= key.inspect %> is already in results with value: <%= result[key].pretty_inspect %> END end result[key] = element } end |
.to_open_struct(hash, freeze: false) ⇒ OpenStruct
Deeply convert a Hash to an OpenStruct.
17 18 19 20 21 22 23 24 |
# File 'lib/nrser/functions/open_struct.rb', line 17 def to_open_struct hash, freeze: false unless hash.is_a? Hash raise TypeError, "Argument must be hash (found #{ hash.inspect })" end _to_open_struct hash, freeze: freeze end |
.transform(tree, source) ⇒ return_type
Document transform method.
Returns @todo Document return value.
11 12 13 14 15 16 17 18 19 |
# File 'lib/nrser/functions/tree/transform.rb', line 11 def self.transform tree, source map_tree( tree, prune: true ) { |value| if value.is_a? Proc value.call source else value end } end |
.transform_keys(hash, &block) ⇒ Hash
Returns a new hash with each key transformed by the provided block.
Lifted from ActiveSupport.
35 36 37 38 39 40 41 42 |
# File 'lib/nrser/functions/hash/transform_keys.rb', line 35 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.
15 16 17 18 19 20 21 |
# File 'lib/nrser/functions/hash/transform_keys.rb', line 15 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 |
.transformer(&block) ⇒ return_type
Document transformer method.
Returns @todo Document return value.
51 52 53 54 55 56 57 58 59 |
# File 'lib/nrser/functions/tree/transform.rb', line 51 def self.transformer &block map_tree( block.call SendSerializer.new ) { |value| if value.is_a? SendSerializer value.to_proc else value end } 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
108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/nrser/functions/string.rb', line 108 def self.truncate(str, truncate_at, = {}) return str.dup unless str.length > truncate_at omission = [:omission] || '...' length_with_room_for_omission = truncate_at - omission.length stop = \ if [:separator] str.rindex([: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.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/nrser/functions/object/truthy.rb', line 64 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 |
.try_find(enum, &block) ⇒ V
Like ‘Enumerable#find`, but wraps each call to `&block` in a `begin` / `rescue`, returning the result of the first call that doesn’t raise an error.
If no calls succeed, raises a MultipleErrors containing the errors from the block calls.
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/nrser/functions/enumerable.rb', line 323 def self.try_find enum, &block errors = [] enum.each do |*args| begin result = block.call *args rescue Exception => error errors << error else return result end end if errors.empty? raise ArgumentError, "Appears that enumerable was empty: #{ enum.inspect }" else raise NRSER::MultipleErrors.new errors end end |
.whitespace?(string) ⇒ Boolean
14 15 16 |
# File 'lib/nrser/functions/string.rb', line 14 def self.whitespace? string string =~ WHITESPACE_RE end |
.with_indent_tagged(text, marker: INDENT_TAG_MARKER, separator: INDENT_TAG_SEPARATOR, &interpolate_block) ⇒ String
Indent tag a some text via indent_tag, call the block with it, then pass the result through indent_untag and return that.
183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/nrser/functions/text/indentation.rb', line 183 def self.with_indent_tagged text, marker: INDENT_TAG_MARKER, separator: INDENT_TAG_SEPARATOR, &interpolate_block indent_untag( interpolate_block.call( indent_tag text, marker: marker, separator: separator ), marker: marker, separator: separator, ) end |
.word_wrap(text, line_width: 80, break_sequence: "\n") ⇒ String
Split text at whitespace to fit in line length. Lifted from Rails’ ActionView.
21 22 23 24 25 |
# File 'lib/nrser/functions/text/word_wrap.rb', line 21 def self.word_wrap text, line_width: 80, break_sequence: "\n" text.split("\n").collect! do |line| line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line end * break_sequence end |