Module: BBLib

Defined in:
lib/bblib.rb,
lib/os/bbos.rb,
lib/html/tag.rb,
lib/time/cron.rb,
lib/file/bbfile.rb,
lib/opal/bbopal.rb,
lib/time/bbtime.rb,
lib/html/builder.rb,
lib/html/tag_set.rb,
lib/mixins/attrs.rb,
lib/mixins/hooks.rb,
lib/string/cases.rb,
lib/string/roman.rb,
lib/array/bbarray.rb,
lib/bblib/version.rb,
lib/mixins/bridge.rb,
lib/mixins/logger.rb,
lib/string/regexp.rb,
lib/hash_path/proc.rb,
lib/number/bbnumber.rb,
lib/object/bbobject.rb,
lib/string/bbstring.rb,
lib/string/matching.rb,
lib/system/bbsystem.rb,
lib/time/task_timer.rb,
lib/class/effortless.rb,
lib/hash/hash_struct.rb,
lib/mixins/prototype.rb,
lib/mixins/type_init.rb,
lib/logging/bblogging.rb,
lib/mixins/serializer.rb,
lib/mixins/family_tree.rb,
lib/mixins/simple_init.rb,
lib/hash_path/hash_path.rb,
lib/hash_path/path_hash.rb,
lib/hash_path/processors.rb,
lib/string/fuzzy_matcher.rb,
lib/string/pluralization.rb

Overview

This module provides similar functionality as hash path, but instead generates a PathHash object which wraps a Hash or Array. Elements may be accessed via method calls rather than path strings.

Defined Under Namespace

Modules: Attrs, Bridge, Command, Durations, Effortless, FamilyTree, HTML, HashPathProcs, Hooks, Logger, OS, Prototype, Serializer, SimpleInit, TypeInit Classes: Cron, EffortlessClass, FuzzyMatcher, HashPathProc, HashStruct, PathHash, TaskTimer, WrongEngineError

Constant Summary collapse

CONFIGS_PATH =
'config/'.freeze
FILE_SIZES =
{
  byte:      { mult: 1, exp: %w(b byt byte), styles: { short: 'B', long: ' byte' } },
  kilobyte:  { mult: 1024, exp: %w(kb kilo k kbyte kilobyte), styles: { short: 'kB', long: ' kilobyte' } },
  megabyte:  { mult: 1024**2, exp: %w(mb mega m mib mbyte megabyte), styles: { short: 'MB', long: ' megabyte' } },
  gigabyte:  { mult: 1024**3, exp: %w(gb giga g gbyte gigabyte), styles: { short: 'GB', long: ' gigabyte' } },
  terabyte:  { mult: 1024**4, exp: %w(tb tera t tbyte terabyte), styles: { short: 'TB', long: ' terabyte' } },
  petabyte:  { mult: 1024**5, exp: %w(pb peta p pbyte petabyte), styles: { short: 'PB', long: ' petabyte' } },
  exabyte:   { mult: 1024**6, exp: %w(eb exa e ebyte exabyte), styles: { short: 'EB', long: ' exabyte' } },
  zettabyte: { mult: 1024**7, exp: %w(zb zetta z zbyte zettabyte), styles: { short: 'ZB', long: ' zettabyte' } },
  yottabyte: { mult: 1024**8, exp: %w(yb yotta y ybyte yottabyte), styles: { short: 'YB', long: ' yottabyte' } }
}.freeze
TIME_EXPS =
{
  yocto: {
    mult: 0.000000000000000000001,
    styles: { long: ' yoctosecond', medium: ' yocto', short: 'ys' },
    exp: %w(yoctosecond yocto yoctoseconds yoctos ys)
  },
  zepto: {
    mult: 0.000000000000000001,
    styles: { long: ' zeptosecond', medium: ' zepto', short: 'zs' },
    exp: %w(zeptosecond zepto zeptoseconds zeptos zs)
  },
  atto: {
    mult: 0.000000000000001,
    styles: { long: ' attosecond', medium: ' atto', short: 'as' },
    exp: %w(attoseconds atto attoseconds attos as)
  },
  femto: {
    mult: 0.000000000001,
    styles: { long: ' femtosecond', medium: ' fempto', short: 'fs' },
    exp: %w(femtosecond fempto femtoseconds femptos fs)
  },
  pico: {
    mult: 0.000000001,
    styles: { long: ' picosecond', medium: ' pico', short: 'ps' },
    exp: %w(picosecond pico picoseconds picos ps)
  },
  nano: {
    mult: 0.000001,
    styles: { long: ' nanosecond', medium: ' nano', short: 'ns' },
    exp: %w(nanosecond nano nanoseconds nanos ns)
  },
  micro: {
    mult: 0.001,
    styles: { long: ' microsecond', medium: ' micro', short: 'μs' },
    exp: %W(microsecond micro microseconds micros \u03BCs)
  },
  milli: {
    mult: 1,
    styles: { long: ' millisecond', medium: ' mil', short: 'ms' },
    exp: %w(ms mil mils milli millis millisecond milliseconds milsec milsecs msec msecs msecond mseconds)
  },
  sec: {
    mult: 1000,
    styles: { long: ' second', medium: ' sec', short: 's' },
    exp: %w(s sec secs second seconds)
  },
  min: {
    mult: 60_000,
    styles: { long: ' minute', medium: ' min', short: 'm' },
    exp: %w(m mn mns min mins minute minutes)
  },
  hour: {
    mult: 3_600_000,
    styles: { long: ' hour', medium: ' hr', short: 'h' },
    exp: %w(h hr hrs hour hours)
  },
  day: {
    mult: 86_400_000,
    styles: { long: ' day', medium: ' day', short: 'd' },
    exp: %w(d day days)
  },
  week: {
    mult: 604_800_000,
    styles: { long: ' week', medium: ' wk', short: 'w' },
    exp: %w(w wk wks week weeks)
  },
  month: {
    mult: 2_592_000_000,
    styles: { long: ' month', medium: ' mo', short: 'mo' },
    exp: %w(mo mon mons month months mnth mnths mth mths)
  },
  year: {
    mult: 31_536_000_000,
    styles: { long: ' year', medium: ' yr', short: 'y' },
    exp: %w(y yr yrs year years)
  }
}.freeze
ROMAN_NUMERALS =
{ 1000 => 'M', 900 => 'CM', 500 => 'D', 400 => 'CD', 100 => 'C', 90 => 'XC', 50 => 'L',
40 => 'XL', 10 => 'X', 9 => 'IX', 5 => 'V', 4 => 'IV', 3 => 'III', 2 => 'II', 1 => 'I' }.freeze
VERSION =
'1.0.2'.freeze
REGEXP_MODE_HASH =
{
  i: Regexp::IGNORECASE,
  m: Regexp::MULTILINE,
  x: Regexp::EXTENDED
}.freeze
REGEXP_OPTIONS =
{
  i: [:ignore_case, :ignorecase, :i, :case_insensitive, Regexp::IGNORECASE],
  m: [:multiline, :multi_line, :m, Regexp::MULTILINE],
  x: [:extended, :x, Regexp::EXTENDED]
}.freeze
NUMBER_WORDS =
{
  special: {
    0 => nil,
    1 => 'one',
    2 => 'two',
    3 => 'three',
    4 => 'four',
    5 => 'five',
    6 => 'six',
    7 => 'seven',
    8 => 'eight',
    9 => 'nine',
    10 => 'ten',
    11 => 'eleven',
    12 => 'twleve',
    13 => 'thirteen',
    14 => 'fourteen',
    15 => 'fifteen',
    16 => 'sixteen',
    17 => 'seventeen',
    18 => 'eighteen',
    19 => 'nineteen'
  },
  double_range: {
    2 => 'twenty',
    3 => 'thirty',
    4 => 'forty',
    5 => 'fifty',
    6 => 'sixty',
    7 => 'seventy',
    8 => 'eighty',
    9 => 'ninety'
  },
  ranges: [
    nil, 'thousand', 'million', 'billion', 'trillion', 'quadrillion',
    'quintillion', 'sextillion', 'septillion'
  ]
}
SPECIAL_PROGRAMS =
['pry', 'irb.cmd', 'irb'].freeze
HASH_PATH_PROC_TYPES =
{
  evaluate:         [:eval, :equation, :equate],
  debug:            [:puts],
  append:           [:suffix],
  prepend:          [:prefix],
  split:            [:delimit, :delim, :separate, :msplit],
  replace:          [:swap],
  replace_with:     [],
  extract:          [:grab, :scan],
  extract_first:    [:grab_first, :scan_first],
  extract_last:     [:grab_last, :scan_last],
  parse_date:       [:date, :parse_time, :time],
  parse_date_unix:  [:unix_time, :unix_date],
  parse_duration:   [:duration],
  parse_file_size:  [:file_size],
  to_string:        [:to_s, :stringify],
  downcase:         [:lower, :lowercase, :to_lower],
  upcase:           [:upper, :uppercase, :to_upper],
  roman:            [:convert_roman, :roman_numeral, :parse_roman],
  remove_symbols:   [:chop_symbols, :drop_symbols],
  format_articles:  [:articles],
  reverse:          [:invert],
  delete:           [:del],
  remove:           [:rem],
  custom:           [:send],
  encapsulate:      [],
  uncapsulate:      [],
  extract_integers: [:extract_ints, :extract_i],
  extract_floats:   [:extract_f],
  extract_numbers:  [:extract_nums],
  max_number:       [:max, :maximum, :maximum_number],
  min_number:       [:min, :minimum, :minimum_number],
  avg_number:       [:avg, :average, :average_number],
  sum_number:       [:sum],
  strip:            [:trim],
  concat:           [:join, :concat_with],
  reverse_concat:   [:reverse_join, :reverse_concat_with]
}.freeze
SPECIAL_PLURALS =
{
  addendum: :addenda,
  alga: :algae,
  alumnus: :alumni,
  amoeba: :amoebae,
  analysis: :analyses,
  antenna: :antennae,
  appendix: :appendices,
  auto: :autos,
  axis: :axes,
  bacterium: :bacteria,
  barracks: :barracks,
  basis: :bases,
  cactus: :cacti,
  calf: :calves,
  crisis: :crises,
  curriculum: :curricula,
  datum: :data,
  deer: :deer,
  diagnosis: :diagnoses,
  echo: :echoes,
  elf: :elves,
  ellipsis: :ellipses,
  embargo: :embargoes,
  emphasis: :emphases,
  fish: :fish,
  foot: :feet,
  fungus: :fungi,
  gallows: :gallows,
  genus: :genera,
  goose: :geese,
  half: :halves,
  hero: :heroes,
  hoof: :hooves,
  hypothesis: :hypotheses,
  index: :indices,
  kangaroo: :kangaroos,
  kilo: :kilos,
  knife: :knives,
  larva: :larvae,
  leaf: :leaves,
  life: :lives,
  loaf: :loaves,
  louse: :lice,
  man: :men,
  matrix: :matrices,
  means: :means,
  memo: :memos,
  memorandum: :memoranda,
  mouse: :mice,
  neurosis: :neuroses,
  oasis: :oases,
  offspring: :offspring,
  paralysis: :paralyses,
  parenthesis: :parentheses,
  person: :people,
  photo: :photos,
  piano: :pianos,
  pimento: :pimentos,
  potato: :potatoes,
  pro: :pros,
  self: :selves,
  series: :series,
  sheep: :sheep,
  shelf: :shelves,
  solo: :solos,
  soprano: :sopranos,
  species: :species,
  stimulus: :stimuli,
  studio: :studios,
  syllabus: :syllabi,
  tattoo: :tattoos,
  thesis: :theses,
  thief: :thieves,
  tomato: :tomatoes,
  tooth: :teeth,
  torpedo: :torpedoes,
  vertebra: :vertebrae,
  veto: :vetoes,
  video: :videos,
  wife: :wives,
  wolf: :wolves,
  woman: :women,
  zoo: :zoos
}

Class Method Summary collapse

Class Method Details

.are_all?(klass, *vars) ⇒ Boolean

Returns:

  • (Boolean)


2
3
4
# File 'lib/object/bbobject.rb', line 2

def self.are_all?(klass, *vars)
  vars.all? { |var| var.is_a?(klass) }
end

.average(ary) ⇒ Object

Takes an array and averages all of the floats and integers within it. Non numeric values are ignored.



31
32
33
34
35
36
# File 'lib/array/bbarray.rb', line 31

def self.average(ary)
  numbers = ary.select { |v| BBLib.is_any?(v, Integer, Float) }
  numbers.inject(0) do |sum, x|
    sum += x
  end / numbers.size.to_f
end

.camel_case(str, style = :lower) ⇒ Object



35
36
37
38
39
40
# File 'lib/string/cases.rb', line 35

def self.camel_case(str, style = :lower)
  regx = /[[:space:]]+|[^[[:alnum:]]]+/
  words = str.split(regx).map(&:capitalize)
  words[0].downcase! if style == :lower
  words.join
end

.chars_up_to(str, cap, too_long = '...', style: :front) ⇒ Object

Displays a portion of an object (as a string) with an ellipse displayed if the string is over a certain size. Supported styles:

> front - “for exam…”

> back - “… example”

> middle - “… exam…”

> outter - “for e…ple”

The length of the too_long string is NOT factored into the cap



66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/string/bbstring.rb', line 66

def self.chars_up_to(str, cap, too_long = '...', style: :front)
  return str if str.to_s.size <= cap
  str = str.to_s
  case style
  when :back
    "#{too_long}#{str[(str.size - cap)..-1]}"
  when :outter
    "#{str[0...(cap / 2).to_i + (cap.odd? ? 1 : 0)]}#{too_long}#{str[-(cap / 2).to_i..-1]}"
  when :middle
    "#{too_long}#{str[(str.size / 2 - cap / 2 - (cap.odd? ? 1 : 0)).to_i...(str.size / 2 + cap / 2).to_i]}#{too_long}"
  else
    "#{str[0...cap]}#{too_long}"
  end
end

.class_case(str) ⇒ Object



55
56
57
# File 'lib/string/cases.rb', line 55

def self.class_case(str)
  str.gsub(/(?<=[^^])([A-Z])/, ' \1').gsub(/\s+/, ' ').title_case.gsub(/\s+|\_/, '')
end

.cmd_line(*args, include_args: true, include_ruby: true, prefix: nil, suffix: nil) ⇒ Object

A string representation of the command line that evoked this ruby instance (platform agnostic)



3
4
5
6
7
8
9
# File 'lib/system/bbsystem.rb', line 3

def self.cmd_line(*args, include_args: true, include_ruby: true, prefix: nil, suffix: nil)
  args = ARGV if args.empty?
  include_ruby = false if special_program?
  "#{prefix}#{include_ruby ? Command.quote(Gem.ruby) : nil} #{Command.quote($PROGRAM_NAME)}" \
  " #{include_args ? args.map { |a| Command.quote(a) }.join(' ') : nil}#{suffix}"
    .strip
end

.composition_similarity(a, b) ⇒ Object

Calculates a percentage based match of two strings based on their character composition.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/string/matching.rb', line 28

def self.composition_similarity(a, b)
  if a.length <= b.length
    t = a
    a = b
    b = t
  end
  matches = 0
  temp = b.dup
  a.chars.each do |c|
    if temp.chars.include? c
      matches+=1
      temp = temp.sub(c, '')
    end
  end
  (matches / [a.length, b.length].max.to_f)* 100.0
end

.copy_capitalization(str_a, str_b) ⇒ Object

Takes two strings and tries to apply the same capitalization from the first string to the second. Supports lower case, upper case and capital case



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/string/bbstring.rb', line 84

def self.copy_capitalization(str_a, str_b)
  str_a = str_a.to_s
  str_b = str_b.to_s
  if str_a.upper?
    str_b.upcase
  elsif str_a.lower?
    str_b.downcase
  elsif str_a.capital?
    str_b.capitalize
  else
    str_b
  end
end

.custom_pluralize(num, base, plural = 's', singular = nil) ⇒ Object



128
129
130
# File 'lib/string/pluralization.rb', line 128

def self.custom_pluralize(num, base, plural = 's', singular = nil)
  num == 1 ? "#{base}#{singular}" : "#{base}#{plural}"
end

.default_loggerObject



9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/logging/bblogging.rb', line 9

def self.default_logger
  log = ::Logger.new(STDOUT)
  log.level = ::Logger::INFO
  log.formatter = proc do |severity, datetime, progname, msg|
    if msg.is_a?(Exception)
      msg = msg.inspect + "\n\t" + msg.backtrace.join("\n\t")
    end
    "[#{datetime}] #{severity} - #{msg.to_s.chomp}\n"
  end
  log.datetime_format = '%Y-%m-%d %H:%M:%S'
  log
end

.delimited_case(str, delimiter = '_') ⇒ Object



42
43
44
45
# File 'lib/string/cases.rb', line 42

def self.delimited_case(str, delimiter = '_')
  regx = /[[:space:]]+|\s+|[^\w\d]+|\#{delimiter}+/
  str.split(regx).join(delimiter)
end

.drop_symbols(str) ⇒ Object

Quickly remove any symbols from a string leaving only alpha-numeric characters and white space.



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

def self.drop_symbols(str)
  str.gsub(/[^\w\s\d]|_/, '')
end

.enable_logger(enable = true) ⇒ Object



27
28
29
# File 'lib/logging/bblogging.rb', line 27

def self.enable_logger(enable = true)
  @logger_on = enable
end

.extract_floats(str, convert: true) ⇒ Object

Extracts all integers or decimals from a string into an array.



23
24
25
26
# File 'lib/string/bbstring.rb', line 23

def self.extract_floats(str, convert: true)
  BBLib.extract_numbers(str, convert: false).reject { |r| !r.include?('.') }
       .map { |m| convert ? m.to_f : m }
end

.extract_integers(str, convert: true) ⇒ Object

Extract all integers from a string. Use extract_floats if numbers may contain decimal places.



17
18
19
20
# File 'lib/string/bbstring.rb', line 17

def self.extract_integers(str, convert: true)
  BBLib.extract_numbers(str, convert: false).reject { |r| r.include?('.') }
       .map { |m| convert ? m.to_i : m }
end

.extract_numbers(str, convert: true) ⇒ Object

Extracts any correctly formed integers or floats from a string



29
30
31
32
# File 'lib/string/bbstring.rb', line 29

def self.extract_numbers(str, convert: true)
  str.scan(/\d+\.\d+(?<=[^\.])|\d+(?<=[^\.])|\d+\.\d+$|\d+$/)
     .map { |f| convert ? (f.include?('.') ? f.to_f : f.to_i) : f }
end

.from_roman(str) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
# File 'lib/string/roman.rb', line 30

def self.from_roman(str)
  sp = str.split(' ')
  (0..1000).each do |n|
    num = BBLib.to_roman n
    next if sp.select { |i| i[/#{num}/i] }.empty?
    (0..(sp.length-1)).each do |i|
      sp[i] = sp[i].sub(num, n.to_s) if sp[i].drop_symbols.upcase == num
    end
  end
  sp.join ' '
end

.hash_args(*args) ⇒ Object



47
48
49
# File 'lib/object/bbobject.rb', line 47

def self.hash_args(*args)
  args.find_all { |a| a.is_a?(Hash) }.each_with_object({}) { |a, h| h.merge!(a) }
end

.hash_path(hash, *paths, multi_path: false, multi_join: false, multi_join_hash: false) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/hash_path/hash_path.rb', line 48

def self.hash_path(hash, *paths, multi_path: false, multi_join: false, multi_join_hash: false)
  tree = TreeHash.new(hash)
  if multi_path
    tree.find_multi(*paths).map  { |r| r.map { |sr| sr.value } }
  elsif multi_join
    tree.find_join(*paths).map { |r| r.map { |sr| sr.value } }
  elsif multi_join_hash
    tree.find_join(*paths).map { |r| r.map { |sr| sr.value } }.to_h
  else
    tree.find(paths).map(&:value)
  end
end

.hash_path_copy(hash, *paths) ⇒ Object



75
76
77
78
79
# File 'lib/hash_path/hash_path.rb', line 75

def self.hash_path_copy(hash, *paths)
  tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
  tree.copy(*paths)
  hash.replace(tree.value)
end

.hash_path_copy_to(from, to, *paths) ⇒ Object



81
82
83
84
# File 'lib/hash_path/hash_path.rb', line 81

def self.hash_path_copy_to(from, to, *paths)
  tree = from.is_a?(TreeHash) ? from : TreeHash.new(from)
  tree.hash_path_copy_to(to, *paths)
end

.hash_path_delete(hash, *paths) ⇒ Object



86
87
88
89
90
# File 'lib/hash_path/hash_path.rb', line 86

def self.hash_path_delete(hash, *paths)
  tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
  tree.delete(*paths)
  hash.replace(tree.value)
end

.hash_path_key_for(hash, value) ⇒ Object



65
66
67
# File 'lib/hash_path/hash_path.rb', line 65

def self.hash_path_key_for(hash, value)
  hash.squish.find_all { |_k, v| value.is_a?(Regexp) ? v =~ value : v == value }.to_h.keys
end

.hash_path_keys(hash) ⇒ Object



61
62
63
# File 'lib/hash_path/hash_path.rb', line 61

def self.hash_path_keys(hash)
  hash.to_tree_hash.absolute_paths
end

.hash_path_move(hash, *paths) ⇒ Object



92
93
94
95
96
# File 'lib/hash_path/hash_path.rb', line 92

def self.hash_path_move(hash, *paths)
  tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
  tree.move(*paths)
  hash.replace(tree.value)
end

.hash_path_move_to(from, to, *paths) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/hash_path/hash_path.rb', line 98

def self.hash_path_move_to(from, to, *paths)
  tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
  tree.hash_path_copy_to(to, *paths).tap do |res|
    from.replace(tree.value)
    to.replace(res.value)
  end
  to
end

.hash_path_proc(hash, *args) ⇒ Object



3
4
5
# File 'lib/hash_path/processors.rb', line 3

def self.hash_path_proc(hash, *args)
  HashPathProc.new(*args).process(hash)
end

.hash_path_set(hash, *paths) ⇒ Object



69
70
71
72
73
# File 'lib/hash_path/hash_path.rb', line 69

def self.hash_path_set(hash, *paths)
  tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
  tree.bridge(*paths)
  hash.replace(tree.value)
end

.in_opal?Boolean

Returns:

  • (Boolean)


2
3
4
# File 'lib/opal/bbopal.rb', line 2

def self.in_opal?
  RUBY_ENGINE == 'opal'
end

.interleave(ary_a, ary_b) ⇒ Object

Takes two arrays (can be of different length) and interleaves them like [a, b, a, b…]



5
6
7
8
9
10
11
12
# File 'lib/array/bbarray.rb', line 5

def self.interleave(ary_a, ary_b)
  ary = []
  [ary_a.size, ary_b.size].max.times do |indx|
    ary.push(ary_a[indx]) if indx < ary_a.size
    ary.push(ary_b[indx]) if indx < ary_b.size
  end
  ary
end

.is_any?(obj, *klasses) ⇒ Boolean

Returns:

  • (Boolean)


6
7
8
# File 'lib/object/bbobject.rb', line 6

def self.is_any?(obj, *klasses)
  klasses.any? { |klass| obj.is_a?(klass) }
end

.keep_between(num, min, max) ⇒ Object

Used to keep any numeric number between a set of bounds. Passing nil as min or max represents no bounds in that direction. min and max are inclusive to the allowed bounds.



5
6
7
8
9
10
# File 'lib/number/bbnumber.rb', line 5

def self.keep_between(num, min, max)
  num = num.to_f unless num.is_a?(Numeric)
  num = min if min && num < min
  num = max if max && num > max
  num
end

.levenshtein_distance(a, b) ⇒ Object

A simple rendition of the levenshtein distance algorithm



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/string/matching.rb', line 8

def self.levenshtein_distance(a, b)
  costs = (0..b.length).to_a
  (1..a.length).each do |i|
    costs[0] = i
    nw = i - 1
    (1..b.length).each do |j|
      costs[j], nw = [costs[j] + 1, costs[j-1] + 1, a[i-1] == b[j-1] ? nw : nw + 1].min, costs[j]
    end
  end
  costs[b.length]
end

.levenshtein_similarity(a, b) ⇒ Object

Calculates a percentage based match using the levenshtein distance algorithm



21
22
23
24
25
# File 'lib/string/matching.rb', line 21

def self.levenshtein_similarity(a, b)
  distance = BBLib.levenshtein_distance a, b
  max = [a.length, b.length].max.to_f
  ((max - distance.to_f) / max) * 100.0
end

.log_enabled?Boolean

Returns:

  • (Boolean)


31
32
33
# File 'lib/logging/bblogging.rb', line 31

def self.log_enabled?
  @logger_on
end

.loggerObject



5
6
7
# File 'lib/logging/bblogging.rb', line 5

def self.logger
  @logger ||= default_logger
end

.logger=(logger) ⇒ Object

Raises:

  • (ArgumentError)


22
23
24
25
# File 'lib/logging/bblogging.rb', line 22

def self.logger=(logger)
  raise ArgumentError, 'Must be set to a valid logger' unless logger.is_a?(Logger)
  @logger = logger
end

.loop_between(num, min, max) ⇒ Object

Similar to keep between but when a number exceeds max or is less than min it is looped to the min or max value respectively.



14
15
16
17
18
19
# File 'lib/number/bbnumber.rb', line 14

def self.loop_between(num, min, max)
  num = num.to_f unless num.is_a?(Numeric)
  num = max if min && num < min
  num = min if max && num > max
  num
end

.method_case(str) ⇒ Object



51
52
53
# File 'lib/string/cases.rb', line 51

def self.method_case(str)
  str.gsub(/(?<=[^^])([A-Z])(?=[^A-Z\s])/, '_\1').gsub(/[\s\_]+/, '_').snake_case.downcase
end

.most_frequent(*args) ⇒ Object

Returns the element that occurs the most frequently in an array or



15
16
17
18
19
# File 'lib/array/bbarray.rb', line 15

def self.most_frequent(*args)
  totals = args.each_with_object(Hash.new(0)) { |elem, hash| hash[elem] += 1 }
  max = totals.values.max
  totals.keys.find { |key| totals[key] == max }
end

.most_frequent_str(*args, case_insensitive: false) ⇒ Object

Returns the most commonly occurring string in an arrray of params. Elements that are not strings are converted to their string representations.

Parameters:

  • case_insensitive (TrueClass, FalseClass) (defaults to: false)

    Compare strings case isensitively.



25
26
27
# File 'lib/array/bbarray.rb', line 25

def self.most_frequent_str(*args, case_insensitive: false)
  most_frequent(*args.map { |arg| case_insensitive ? arg.to_s.downcase : arg.to_s })
end

.move_articles(str, position = :front, capitalize: true) ⇒ Object

Used to move the position of the articles ‘the’, ‘a’ and ‘an’ in strings for normalization.



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

def self.move_articles(str, position = :front, capitalize: true)
  return str unless [:front, :back, :none].include?(position)
  %w(the a an).each do |a|
    starts = str.downcase.start_with?(a + ' ')
    ends = str.downcase.end_with?(' ' + a)
    if starts && position != :front
      if position == :none
        str = str[(a.length + 1)..str.length]
      elsif position == :back
        str = str[(a.length + 1)..str.length] + (!ends ? ", #{capitalize ? a.capitalize : a}" : '')
      end
    end
    next unless ends && position != :back
    if position == :none
      str = str[0..-(a.length + 2)]
    elsif position == :front
      str = (!starts ? "#{capitalize ? a.capitalize : a} " : '') + str[0..-(a.length + 2)]
    end
  end
  str = str.strip.chop while str.strip.end_with?(',')
  str
end

.named_args(*args) ⇒ Object



35
36
37
# File 'lib/object/bbobject.rb', line 35

def self.named_args(*args)
  args.last.is_a?(Hash) && args.last.keys.all? { |k| k.is_a?(Symbol) } ? args.last : {}
end

.named_args!(*args) ⇒ Object



39
40
41
42
43
44
45
# File 'lib/object/bbobject.rb', line 39

def self.named_args!(*args)
  if args.last.is_a?(Hash) && args.last.keys.all? { |k| k.is_a?(Symbol) }
    args.delete_at(-1)
  else
    {}
  end
end

.namespace_of(klass) ⇒ Object



58
59
60
61
62
# File 'lib/object/bbobject.rb', line 58

def self.namespace_of(klass)
  split = klass.to_s.split('::')
  return klass if split.size == 1
  Object.const_get(split[0..-2].join('::'))
end

.number_spelled_out(number, range = 0, include_and: true) ⇒ Object



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
85
86
87
88
# File 'lib/number/bbnumber.rb', line 60

def self.number_spelled_out(number, range = 0, include_and: true)
  number = number.to_i
  negative = number.negative?
  number = number * -1 if negative
  return 'zero' if number.zero?
  str = []
  three_digit = number > 999 ? number.to_s[-3..-1].to_i : number
  case three_digit
  when 1..19
    str << NUMBER_WORDS[:special][three_digit]
  when 20..99
    str << NUMBER_WORDS[:double_range][three_digit.to_s[-2].to_i]
    str << NUMBER_WORDS[:special][three_digit.to_s[-1].to_i]
  when 100..999
    str << NUMBER_WORDS[:special][three_digit.to_s[0].to_i]
    str << 'hundred'
    str << 'and' if include_and
    if three_digit.to_s[-2].to_i == 1
      str << NUMBER_WORDS[:special][three_digit.to_s[-2..-1].to_i]
    else
      str << NUMBER_WORDS[:double_range][three_digit.to_s[-2].to_i]
      str << NUMBER_WORDS[:special][three_digit.to_s[-1].to_i]
    end
  end
  str << NUMBER_WORDS[:ranges][range] unless str.compact.empty?
  (negative ? 'negative ' : '') +
  ((number.to_s.size > 3 ? "#{number_spelled_out(number.to_s[0..-4].to_i, range + 1)} " : '') +
  str.compact.join(' ')).gsub(/\s+/, ' ')
end

.numeric_similarity(a, b) ⇒ Object

Extracts all numbers from two strings and compares them and generates a percentage of match. Percentage calculations here need to be weighted better…TODO



60
61
62
63
64
65
66
67
68
69
# File 'lib/string/matching.rb', line 60

def self.numeric_similarity(a, b)
  a = a.extract_numbers
  b = b.extract_numbers
  return 100.0 if a.empty? && b.empty? || a == b
  matches = []
  (0..[a.size, b.size].max-1).each do |i|
    matches << 1.0 / ([a[i].to_f, b[i].to_f].max - [a[i].to_f, b[i].to_f].min + 1.0)
  end
  (matches.inject { |sum, m| sum + m } / matches.size.to_f) * 100.0
end

.parse_duration(str, output: :sec, min_interval: :sec) ⇒ Object

Parses known time based patterns out of a string to construct a numeric duration.



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/time/bbtime.rb', line 7

def self.parse_duration(str, output: :sec, min_interval: :sec)
  msecs = 0.0

  # Parse time expressions such as 04:05.
  # The argument min_interval controls what time interval the final number represents
  str.scan(/\d+\:[\d+\:]+\d+/).each do |e|
    keys = TIME_EXPS.keys
    position = keys.index(min_interval)
    e.split(':').reverse.each do |sec|
      key = keys[position]
      msecs+= sec.to_f * TIME_EXPS[key][:mult]
      position+=1
    end
  end

  # Parse expressions such as '1m' or '1 min'
  TIME_EXPS.each do |_k, v|
    v[:exp].each do |e|
      numbers = str.downcase.scan(/(?=\w|\D|\A)\d*\.?\d+[[:space:]]*#{e}(?=\W|\d|\z)/i)
      numbers.each do |n|
        msecs+= n.to_f * v[:mult]
      end
    end
  end
  msecs / TIME_EXPS[output][:mult]
end

.parse_file_size(str, output: :byte) ⇒ Object

A file size parser for strings. Extracts any known patterns for file sizes.



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/file/bbfile.rb', line 58

def self.parse_file_size(str, output: :byte)
  output = FILE_SIZES.keys.find { |fs| fs == output || FILE_SIZES[fs][:exp].include?(output.to_s.downcase) } || :byte
  bytes = 0.0
  FILE_SIZES.each do |_k, v|
    v[:exp].each do |exp|
      str.scan(/(?=\w|\D|^)\d*\.?\d+\s*#{exp}s?(?=\W|\d|$)/i)
         .each { |num| bytes += num.to_f * v[:mult] }
    end
  end
  bytes / FILE_SIZES[output][:mult]
end

.path_hash(hash) ⇒ Object



8
9
10
# File 'lib/hash_path/path_hash.rb', line 8

def self.path_hash(hash)
  PathHash.new(hash)
end

.pathify(*strings) ⇒ Object

Takes one or more strings and normalizes slashes to create a consistent file path Useful when concating two strings that when you don’t know if one or both will end or begin with a slash



5
6
7
# File 'lib/file/bbfile.rb', line 5

def self.pathify(*strings)
  (strings.first.start_with?('/', '\\') ? strings.first.scan(/^[\/\\]{1,2}/).first : '') + strings.map(&:to_s).msplit('/', '\\').map(&:strip).join('/')
end

.pattern_render(text, context = {}, pattern: /\{{2}.*?\}{2}/, field_pattern: /(?<=^\{{2}).*(?=\}{2})/) ⇒ Object

Pattern render takes (by default) a mustache style template and then uses a context (either a Hash or Object) to then interpolate in placeholders. The default pattern looks for {method_name} within the string but can be customized to a different pattern by setting the pattern named argument.

Raises:

  • (ArgumentError)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/string/bbstring.rb', line 102

def self.pattern_render(text, context = {}, pattern: /\{{2}.*?\}{2}/, field_pattern: /(?<=^\{{2}).*(?=\}{2})/)
  raise ArgumentError, "Expected text argument to be a String, got a #{text.class}" unless text.is_a?(String)
  txt = text.dup
  txt.scan(pattern).each do |match|
    field = match.scan(field_pattern).first
    next unless field
    value = case context
    when Hash
      context[field.to_sym] || context[field]
    else
      context.send(field) if context.respond_to?(field)
    end.to_s
    txt.sub!(match, value)
  end
  txt
end

.phrase_similarity(a, b) ⇒ Object

Calculates a percentage based match between two strings based on the similarity of word matches.



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/string/matching.rb', line 46

def self.phrase_similarity(a, b)
  temp = b.drop_symbols.split ' '
  matches = 0
  a.drop_symbols.split(' ').each do |w|
    if temp.include? w
      matches+=1
      temp.delete_at temp.find_index w
    end
  end
  (matches.to_f / [a.split(' ').size, b.split(' ').size].max.to_f) * 100.0
end

.plural_string(num, string) ⇒ Object



132
133
134
# File 'lib/string/pluralization.rb', line 132

def self.plural_string(num, string)
  "#{num} #{pluralize(string, num)}"
end

.pluralize(string, num = 2) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/string/pluralization.rb', line 91

def self.pluralize(string, num = 2)
  full_string = string.to_s
  string = string.split(/\s+/).last
  sym = string.to_s.downcase.to_sym
  if plural = SPECIAL_PLURALS[sym]
    result = num == 1 ? string : plural
  else
    if string.end_with?(*%w{ch z s x o})
      result = num == 1 ? string : (string + 'es')
    elsif string =~ /[^aeiou]y$/i
      result = num == 1 ? string : string.sub(/y$/i, 'ies')
    else
      result = num == 1 ? string : (string + 's')
    end
  end
  full_string.sub(/#{Regexp.escape(string)}$/, copy_capitalization(string, result).to_s)
end

.qwerty_distance(a, b) ⇒ Object

A simple character distance calculator that uses qwerty key positions to determine how similar two strings are. May be useful for typo detection.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/string/matching.rb', line 73

def self.qwerty_distance(a, b)
  a = a.downcase.strip
  b = b.downcase.strip
  if a.length <= b.length
    t = a
    a = b
    b = t
  end
  qwerty = {
    1 => %w(1 2 3 4 5 6 7 8 9 0),
    2 => %w(q w e r t y u i o p),
    3 => %w(a s d f g h j k l),
    4 => %w(z x c v b n m)
  }
  count = 0
  offset = 0
  a.chars.each do |c|
    if b.length <= count
      offset+=10
    else
      ai = qwerty.keys.find { |f| qwerty[f].include? c }.to_i
      bi = qwerty.keys.find { |f| qwerty[f].include? b.chars[count] }.to_i
      offset+= (ai - bi).abs
      offset+= (qwerty[ai].index(c) - qwerty[bi].index(b.chars[count])).abs
    end
    count+=1
  end
  offset
end

.recursive_send(obj, *methods) ⇒ Object



51
52
53
54
55
56
# File 'lib/object/bbobject.rb', line 51

def self.recursive_send(obj, *methods)
  methods.each do |args|
    obj = obj.send(*args)
  end
  obj
end

.reload(include_args: true) ⇒ Object

EXPERIMENTAL: Reloads the original file that was called Use at your own risk, this could cause some weird issues



13
14
15
16
# File 'lib/system/bbsystem.rb', line 13

def self.reload(include_args: true)
  return false if special_program?
  load cmd_line(*args, include_ruby: false, include_args: include_args)
end

.restart(*args, include_args: true, stay_alive: 1) ⇒ Object

EXPERIMENTAL: Restart the ruby process that is currently running. Use at your own risk



20
21
22
23
24
25
26
27
28
# File 'lib/system/bbsystem.rb', line 20

def self.restart(*args, include_args: true, stay_alive: 1)
  exit(0)
rescue SystemExit
  opts = BBLib::OS.windows? ? { new_pgroup: true } : { pgroup: true }
  pid = spawn(cmd_line(*args, include_args: include_args, prefix: (BBLib::OS.windows? ? 'start ' : nil)), **opts)
  Process.detach(pid)
  sleep(stay_alive)
  exit(0) if special_program?
end

.root_namespace_of(klass) ⇒ Object



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

def self.root_namespace_of(klass)
  Object.const_get(klass.to_s.gsub(/::.*/, ''))
end

.scan_dir(path, *filters, recursive: false, files: true, dirs: true, exclude: [], &block) ⇒ Object

Scan for files and directories. Can be set to be recursive and can also have filters applied.

Parameters:

  • path (String)

    The directory to scan files from.

  • filters (String..., Regexp...)

    A list of filters to apply. Can be regular expressions or strings. Strings with a * are treated as regular expressions with a .*. If no filters are passed, all files/dirs are returned.

  • recursive (Boolean) (defaults to: false)

    When true scan will recursively search directories

  • files (Boolean) (defaults to: true)

    If true, paths to files matching the filter will be returned.

  • dirs (Boolean) (defaults to: true)

    If true, paths to dirs matching the filter will be returned.

  • exclude (Array) (defaults to: [])

    Can be an array of regular expressions or strings that should be ignored when scanning. * in a string is expanded into .*, but all other characters are literal.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/file/bbfile.rb', line 17

def self.scan_dir(path, *filters, recursive: false, files: true, dirs: true, exclude: [], &block)
  return [] unless Dir.exist?(path)
  filters = filters.map { |filter| filter.is_a?(Regexp) ? filter : /^#{Regexp.quote(filter).gsub('\\*', '.*')}$/ }
  exclude = exclude ? [exclude].flatten.map { |exp| exp.is_a?(Regexp) ? exp : /^#{Regexp.quote(exp).gsub('\\*', '.*')}$/ } : []
  Dir.foreach(path).flat_map do |item|
    next if item =~ /^\.{1,2}$/ || (!exclude.empty? && exclude.any? { |exp| item =~ exp })
    item = "#{path}/#{item}".gsub('\\', '/')
    if File.file?(item)
      if files && (filters.empty? || filters.any? { |filter| item =~ filter })
        block_given? ? yield(item) : item
      end
    elsif File.directory?(item)
      recur = recursive ? scan_dir(item, *filters, recursive: recursive, exclude: exclude, files: files, dirs: dirs, &block) : []
      if dirs && (filters.empty? || filters.any? { |filter| item =~ filter })
        (block_given? ? yield(item) : [item] + recur)
      elsif recursive
        recur
      end
    end
  end.compact
end

.scan_dirs(path, *filters, recursive: false, exclude: [], &block) ⇒ Object

Uses BBLib.scan_dir but returns only directories.



45
46
47
# File 'lib/file/bbfile.rb', line 45

def self.scan_dirs(path, *filters, recursive: false, exclude: [], &block)
  scan_dir(path, *filters, recursive: recursive, files: false, exclude: exclude, &block)
end

.scan_files(path, *filters, recursive: false, exclude: [], &block) ⇒ Object

Uses BBLib.scan_dir but returns only files



40
41
42
# File 'lib/file/bbfile.rb', line 40

def self.scan_files(path, *filters, recursive: false, exclude: [], &block)
  scan_dir(path, *filters, recursive: recursive, dirs: false, exclude: exclude, &block)
end

.singularize(string) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/string/pluralization.rb', line 109

def self.singularize(string)
  full_string = string.to_s
  string = string.split(/\s+/).last
  sym = string.to_s.downcase.to_sym
  sym = string.to_s.downcase.to_sym
  if singular = SPECIAL_PLURALS.find { |k, v| v == sym }
    result = singular.first
  elsif string.downcase.end_with?(*%w{oes ches zes ses xes})
    result = string.sub(/es$/i, '')
  elsif string =~ /ies$/i
    result = string.sub(/ies$/i, 'y')
  elsif string =~ /s$/i && !(string =~ /s{2}$/i)
    result = string.sub(/s$/i, '')
  else
    result = string
  end
  full_string.sub(/#{Regexp.escape(string)}$/, copy_capitalization(string, result).to_s)
end

.snake_case(str) ⇒ Object



47
48
49
# File 'lib/string/cases.rb', line 47

def self.snake_case(str)
  BBLib.delimited_case(str, '_')
end

.special_program?Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/system/bbsystem.rb', line 32

def self.special_program?
  SPECIAL_PROGRAMS.include?($PROGRAM_NAME)
end

.spinal_case(str) ⇒ Object



59
60
61
# File 'lib/string/cases.rb', line 59

def self.spinal_case(str)
  BBLib.delimited_case str, '-'
end

.start_case(str, first_only: false) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/string/cases.rb', line 21

def self.start_case(str, first_only: false)
  regx = /[[:space:]]+|\-|\_|\"|\'|\(|\)|\[|\]|\{|\}|\#/
  spacing = str.scan(regx).to_a
  words = str.split(regx).map do |word|
    if first_only
      word[0] = word[0].upcase
      word
    else
      word.capitalize
    end
  end
  words.interleave(spacing).join
end

.string_to_file(str, path, mkpath: true, mode: 'a') ⇒ Object

Shorthand method to write a string to disk. By default the path is created if it doesn’t exist. Set mode to w to truncate file or leave at a to append.



52
53
54
55
# File 'lib/file/bbfile.rb', line 52

def self.string_to_file(str, path, mkpath: true, mode: 'a')
  FileUtils.mkpath(File.dirname(path)) if mkpath && !Dir.exist?(path)
  File.write(path, str.to_s, mode: mode)
end

.string_to_roman(str) ⇒ Object



19
20
21
22
23
24
25
26
27
28
# File 'lib/string/roman.rb', line 19

def self.string_to_roman(str)
  sp = str.split ' '
  sp.map do |s|
    if s.drop_symbols.to_i.to_s == s.drop_symbols && !(s =~ /\d+\.\d+/)
      s.sub(s.scan(/\d+/).first.to_s, BBLib.to_roman(s.to_i))
    else
      s
    end
  end.join(' ')
end

.title_case(str, first_only: true) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/string/cases.rb', line 2

def self.title_case(str, first_only: true)
  str = str.to_s unless str.is_a?(String)
  ignoreables = %w(a an the on upon and but or in with to)
  regx = /\s+|\-|\_|(?<=\W|^)\"(?=\w|$)|(?<=\W|^)\'(?=\w|$)|\(|\)|\[|\]|\{|\}|\#/
  spacing = str.scan(regx).to_a
  words = str.split(regx).map do |word|
    if ignoreables.include?(word.downcase)
      word.downcase
    elsif first_only
      word.to_s.slice(0,1).to_s.upcase + word.to_s[1..-1].to_s
    else
      word.capitalize
    end
  end
  # Always cap the first word
  words[0] = words.first.to_s.slice(0,1).to_s.upcase + words.first.to_s[1..-1].to_s
  words.interleave(spacing).join
end

.to_duration(num, input: :sec, stop: :milli, style: :medium) ⇒ Object

Turns a numeric input into a time string.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/time/bbtime.rb', line 35

def self.to_duration(num, input: :sec, stop: :milli, style: :medium)
  return nil unless num.is_a?(Numeric)
  return '0' if num.zero?
  style = :medium unless [:long, :medium, :short].include?(style)
  expression = []
  n = num * TIME_EXPS[input.to_sym][:mult]
  done = false
  TIME_EXPS.reverse.each do |k, v|
    next if done
    done = true if k == stop
    div = n / v[:mult]
    next unless div >= 1
    val = (done ? div.round : div.floor)
    expression << "#{val}#{v[:styles][style]}#{val > 1 && style != :short ? 's' : nil}"
    n -= val.to_f * v[:mult]
  end
  expression.join(' ')
end

.to_file_size(num, input: :byte, stop: :byte, style: :short) ⇒ Object

Takes an integer or float and converts it into a string that represents

a file size (e.g. "5 MB 156 kB")

Parameters:

  • num (Integer, Float)

    The number of bytes to convert to a file size string.

  • input (Symbol) (defaults to: :byte)

    Sets the value of the input. Default is byte.

  • stop (Symbol) (defaults to: :byte)

    Sets a minimum file size to display. e.g. If stop is set to :megabyte, :kilobyte and below will be truncated.

  • style (Symbol) (defaults to: :short)

    The out style, Current options are :short and :long



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/file/bbfile.rb', line 77

def self.to_file_size(num, input: :byte, stop: :byte, style: :short)
  return nil unless num.is_a?(Numeric)
  return '0' if num.zero?
  style = :short unless [:long, :short].include?(style)
  expression = []
  n = num * FILE_SIZES[input.to_sym][:mult]
  done = false
  FILE_SIZES.reverse.each do |k, v|
    next if done
    done = true if k == stop
    div = n / v[:mult]
    next unless div >= 1
    val = (done ? div.round : div.floor)
    expression << "#{val}#{v[:styles][style]}#{val > 1 && style != :short ? 's' : nil}"
    n -= val.to_f * v[:mult]
  end
  expression.join(' ')
end

.to_hash(obj) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/object/bbobject.rb', line 10

def self.to_hash(obj)
  return { obj => nil } if obj.instance_variables.empty?
  hash = {}
  obj.instance_variables.each do |var|
    value = obj.instance_variable_get(var)
    if value.is_a?(Array)
      hash[var.to_s.delete('@')] = value.map { |v| v.respond_to?(:obj_to_hash) && !v.instance_variables.empty? ? v.obj_to_hash : v }
    elsif value.is_a?(Hash)
      begin
        unless hash[var.to_s.delete('@')].is_a?(Hash) then hash[var.to_s.delete('@')] = {} end
      rescue
        hash[var.to_s.delete('@')] = {}
      end
      value.each do |k, v|
        hash[var.to_s.delete('@')][k.to_s.delete('@')] = v.respond_to?(:obj_to_hash) && !v.instance_variables.empty? ? v.obj_to_hash : v
      end
    elsif value.respond_to?(:obj_to_hash) && !value.instance_variables.empty?
      hash[var.to_s.delete('@')] = value.obj_to_hash
    else
      hash[var.to_s.delete('@')] = value
    end
  end
  hash
end

.to_nearest_duration(num, input: :sec, style: :medium) ⇒ Object



54
55
56
57
58
59
60
61
62
# File 'lib/time/bbtime.rb', line 54

def self.to_nearest_duration(num, input: :sec, style: :medium)
  n = num * TIME_EXPS[input.to_sym][:mult]
  stop = nil
  TIME_EXPS.each do |k, v|
    stop = k if v[:mult] <= n
  end
  stop = :year unless stop
  to_duration(num, input: input, style: style, stop: stop)
end

.to_roman(num) ⇒ Object

Converts any integer up to 1000 to a roman numeral



7
8
9
10
11
12
13
14
15
16
17
# File 'lib/string/roman.rb', line 7

def self.to_roman(num)
  return num.to_s if num > 1000
  numeral = ''
  ROMAN_NUMERALS.each do |n, r|
    while num >= n
      num -= n
      numeral += r
    end
  end
  numeral
end

.train_case(str) ⇒ Object



63
64
65
# File 'lib/string/cases.rb', line 63

def self.train_case(str)
  BBLib.spinal_case(BBLib.start_case(str))
end