Module: BBLib

Defined in:
lib/bblib/core/util/matching.rb,
lib/bblib/version.rb,
lib/bblib/cli/util.rb,
lib/bblib/html/tag.rb,
lib/bblib/cli/color.rb,
lib/bblib/cli/option.rb,
lib/bblib/classes/cron.rb,
lib/bblib/core/util/os.rb,
lib/bblib/html/builder.rb,
lib/bblib/html/tag_set.rb,
lib/bblib/system/system.rb,
lib/bblib/core/util/file.rb,
lib/bblib/core/util/opal.rb,
lib/bblib/core/util/time.rb,
lib/bblib/system/command.rb,
lib/bblib/cli/opts_parser.rb,
lib/bblib/core/util/array.rb,
lib/bblib/core/util/cases.rb,
lib/bblib/core/util/roman.rb,
lib/bblib/cli/options/bool.rb,
lib/bblib/cli/options/date.rb,
lib/bblib/cli/options/json.rb,
lib/bblib/cli/options/time.rb,
lib/bblib/core/util/number.rb,
lib/bblib/core/util/object.rb,
lib/bblib/core/util/regexp.rb,
lib/bblib/core/util/string.rb,
lib/bblib/cli/options/float.rb,
lib/bblib/core/mixins/attrs.rb,
lib/bblib/core/mixins/hooks.rb,
lib/bblib/core/util/logging.rb,
lib/bblib/cli/options/regexp.rb,
lib/bblib/cli/options/string.rb,
lib/bblib/cli/options/symbol.rb,
lib/bblib/cli/options/toggle.rb,
lib/bblib/core/mixins/bridge.rb,
lib/bblib/core/mixins/logger.rb,
lib/bblib/cli/options/command.rb,
lib/bblib/cli/options/integer.rb,
lib/bblib/cli/options/untoggle.rb,
lib/bblib/classes/fuzzy_matcher.rb,
lib/bblib/core/classes/splitter.rb,
lib/bblib/core/mixins/delegator.rb,
lib/bblib/core/mixins/prototype.rb,
lib/bblib/core/mixins/type_init.rb,
lib/bblib/cli/options/element_of.rb,
lib/bblib/core/mixins/effortless.rb,
lib/bblib/core/mixins/serializer.rb,
lib/bblib/core/classes/task_timer.rb,
lib/bblib/core/mixins/family_tree.rb,
lib/bblib/core/mixins/simple_init.rb,
lib/bblib/core/util/pluralization.rb,
lib/bblib/cli/options/basic_option.rb,
lib/bblib/core/classes/hash_struct.rb,
lib/bblib/core/hash_path/hash_path.rb,
lib/bblib/core/exceptions/exception.rb,
lib/bblib/cli/exceptions/opts_parser.rb,
lib/bblib/cli/exceptions/invalid_argument.rb,
lib/bblib/cli/exceptions/missing_argument.rb,
lib/bblib/core/mixins/numeric_enhancements.rb,
lib/bblib/cli/exceptions/missing_required_argument.rb

Overview

Similar to Ruby’s OpenStruct but uses hash as its parent class providing all of the typical Hash methods. Useful for storing settings on objects.

Defined Under Namespace

Modules: Attrs, Bridge, Command, Console, Delegator, Durations, Effortless, FamilyTree, HTML, Hooks, Logger, NumericEnhancements, OS, Prototype, Serializer, SimpleInit, System, TypeInit Classes: Cron, EffortlessClass, Exception, FuzzyMatcher, HashStruct, InvalidArgumentException, MissingArgumentException, MissingRequiredArgumentException, OptsParser, OptsParserException, Splitter, TaskTimer, WrongEngineError

Constant Summary collapse

VERSION =
'2.0.5'.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(attosecond 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
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'
  ]
}
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
EXTRACT_NUMBER_REGEXP =
/(?<=[^\.]|^)\d+\.\d+(?=[^\.]|$)|(?<=[^\.\d]|^)\d+(?=[^\.\d]|$)/
EXTRACT_NUMBER_REGEXP_NO_INNER =
/(?<=[^\.]|^)\d+\.\d+(?=[^\.]|$)|(?<=[^\.\d\w]|^)\d+(?=[^\.\d\w]|$)/
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, *objects) ⇒ Boolean

Easy way to see if all objects in an array are of a given class.

Returns:

  • (Boolean)


3
4
5
# File 'lib/bblib/core/util/object.rb', line 3

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

.are_any?(klass, *objects) ⇒ Boolean

Easy way to see if any of the passed objects are of the given class.

Returns:

  • (Boolean)


8
9
10
# File 'lib/bblib/core/util/object.rb', line 8

def self.are_any?(klass, *objects)
  objects.any? { |object| object.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/bblib/core/util/array.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

.binary?(file, bytes: 1024, ctrl_threshold: 0.5, binary_threshold: 0.05) ⇒ Boolean

Basic detection for whether or not a file is binary or not

Returns:

  • (Boolean)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/bblib/core/util/file.rb', line 109

def self.binary?(file, bytes: 1024, ctrl_threshold: 0.5, binary_threshold: 0.05)
  ascii  = 0
  ctrl   = 0
  binary = 0

  read_bytes = File.open(file, 'rb') { |io| io.read(bytes) }

  return false if read_bytes.nil? || read_bytes.empty?

  read_bytes.each_byte do |byte|
    case byte
    when 0..31
      ctrl += 1
    when 32..127
      ascii += 1
    else
      binary += 1
    end
  end

  ctrl.to_f / ascii > ctrl_threshold || binary.to_f / ascii > binary_threshold
end

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



37
38
39
40
41
42
# File 'lib/bblib/core/util/cases.rb', line 37

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/bblib/core/util/string.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



57
58
59
# File 'lib/bblib/core/util/cases.rb', line 57

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

.class_create(name, *args, **opts, &block) ⇒ Object



114
115
116
# File 'lib/bblib/core/util/object.rb', line 114

def self.class_create(name, *args, **opts, &block)
  const_create(name, Class.new(*args), **opts, &block)
end

.color_logsObject



36
37
38
# File 'lib/bblib/core/util/logging.rb', line 36

def self.color_logs
  @color_logs
end

.color_logs=(toggle) ⇒ Object



40
41
42
# File 'lib/bblib/core/util/logging.rb', line 40

def self.color_logs=(toggle)
  @color_logs = (toggle ? true : false)
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/bblib/core/util/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

.const_create(name, value, strict: true, base: Object, type_of_missing: nil, &block) ⇒ Object

Create a new class or module recursively within a provided namespace. If a constant matching the requested one already exist it is returned. Any block passed to this method will be evaled in the created/found constant.

Raises:

  • (TypeError)


90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/bblib/core/util/object.rb', line 90

def self.const_create(name, value, strict: true, base: Object, type_of_missing: nil, &block)
  namespace = base
  unless base.const_defined?(name)
    type_of_missing = Module unless type_of_missing
    name = name.uncapsulate('::')
    if name.include?('::')
      namespaces = name.split('::')
      name = namespaces.pop
      namespaces.each do |constant|
        unless namespace.const_defined?(constant)
          match = namespace.const_set(constant, type_of_missing.new)
        end
        namespace = namespace.const_get(constant)
      end
    end
    namespace.const_set(name, value)
  end
  object = namespace.const_get(name)
  raise TypeError, "Expected a #{value.class} but #{namespace}::#{name} is a #{object.class}" if strict && object.class != value.class
  object.tap do |constant|
    constant.send(:class_exec, &block) if block
  end
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/bblib/core/util/string.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/bblib/core/util/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
21
# File 'lib/bblib/core/util/logging.rb', line 9

def self.default_logger
  log = ::Logger.new(STDOUT)
  log.level = ::Logger::INFO
  log.formatter = proc do |severity, datetime, progname, msg|
    severity = severity.to_s.to_color(severity) if BBLib.color_logs
    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



44
45
46
47
# File 'lib/bblib/core/util/cases.rb', line 44

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.



9
10
11
# File 'lib/bblib/core/util/string.rb', line 9

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

.enable_logger(enable = true) ⇒ Object



28
29
30
# File 'lib/bblib/core/util/logging.rb', line 28

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.



20
21
22
23
# File 'lib/bblib/core/util/string.rb', line 20

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.



14
15
16
17
# File 'lib/bblib/core/util/string.rb', line 14

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, include_inner: true) ⇒ Object

Extracts any correctly formed integers or floats from a string



29
30
31
32
# File 'lib/bblib/core/util/string.rb', line 29

def self.extract_numbers(str, convert: true, include_inner: true)
  str.scan(include_inner ? EXTRACT_NUMBER_REGEXP : EXTRACT_NUMBER_REGEXP_NO_INNER)
     .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/bblib/core/util/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

Similar to named_args but also treats String keys as named arguments.



61
62
63
# File 'lib/bblib/core/util/object.rb', line 61

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



46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/bblib/core/hash_path/hash_path.rb', line 46

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



73
74
75
76
77
# File 'lib/bblib/core/hash_path/hash_path.rb', line 73

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



79
80
81
82
# File 'lib/bblib/core/hash_path/hash_path.rb', line 79

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



84
85
86
87
88
# File 'lib/bblib/core/hash_path/hash_path.rb', line 84

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



63
64
65
# File 'lib/bblib/core/hash_path/hash_path.rb', line 63

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



59
60
61
# File 'lib/bblib/core/hash_path/hash_path.rb', line 59

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

.hash_path_move(hash, *paths) ⇒ Object



90
91
92
93
94
# File 'lib/bblib/core/hash_path/hash_path.rb', line 90

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



96
97
98
99
100
101
102
103
# File 'lib/bblib/core/hash_path/hash_path.rb', line 96

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_set(hash, *paths) ⇒ Object



67
68
69
70
71
# File 'lib/bblib/core/hash_path/hash_path.rb', line 67

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/bblib/core/util/opal.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/bblib/core/util/array.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?(object, *klasses) ⇒ Boolean

Checks to see if an object is of any of the given classes.

Returns:

  • (Boolean)


13
14
15
# File 'lib/bblib/core/util/object.rb', line 13

def self.is_any?(object, *klasses)
  klasses.any? { |klass| object.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/bblib/core/util/number.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/bblib/core/util/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/bblib/core/util/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)


32
33
34
# File 'lib/bblib/core/util/logging.rb', line 32

def self.log_enabled?
  @logger_on
end

.loggerObject



5
6
7
# File 'lib/bblib/core/util/logging.rb', line 5

def self.logger
  @logger ||= default_logger
end

.logger=(logger) ⇒ Object

Raises:

  • (ArgumentError)


23
24
25
26
# File 'lib/bblib/core/util/logging.rb', line 23

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/bblib/core/util/number.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



53
54
55
# File 'lib/bblib/core/util/cases.rb', line 53

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

.module_create(name, *args, **opts, &block) ⇒ Object



118
119
120
# File 'lib/bblib/core/util/object.rb', line 118

def self.module_create(name, *args, **opts, &block)
  const_create(name, Module.new(*args), **opts, &block)
end

.most_frequent(*args) ⇒ Object

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



15
16
17
18
19
# File 'lib/bblib/core/util/array.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/bblib/core/util/array.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/bblib/core/util/string.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

Extracts all hash based arguments from an ary of arguments. Only hash pairs with a symbol as the key are returned. Use hash_args if you also want to treat String keys as named arguments.



47
48
49
# File 'lib/bblib/core/util/object.rb', line 47

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

Same as standard named_args but removes the named arguments from the array.



52
53
54
55
56
57
58
# File 'lib/bblib/core/util/object.rb', line 52

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

Returns the encapsulating object space of a given class. Ex: For a class called BBLib::String, this method will return BBLib as the namespace. Ex2: For a class BBLib::String::Char, this method will return BBLib::String as the namespace.



76
77
78
79
80
# File 'lib/bblib/core/util/object.rb', line 76

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

TODO: Support floats eventually?



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
89
# File 'lib/bblib/core/util/number.rb', line 61

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 && !three_digit.to_s.end_with?('00')
    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/bblib/core/util/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.



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/bblib/core/util/time.rb', line 3

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/bblib/core/util/file.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

.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/bblib/core/util/file.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 = {}) ⇒ 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
118
119
120
# File 'lib/bblib/core/util/string.rb', line 102

def self.pattern_render(text, context = {})
  raise ArgumentError, "Expected text argument to be a String, got a #{text.class}" unless text.is_a?(String)
  # TODO Make patterns customizable
  pattern       = /\{{2}.*?\}{2}/
  field_pattern = /(?<=^\{{2}).*(?=\}{2})/
  txt           = text.dup
  txt.scan(pattern).each do |match|
    field = match.scan(field_pattern).first
    next unless field
    value = case context
    when Hash
      context.hpath(field).first
    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/bblib/core/util/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/bblib/core/util/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/bblib/core/util/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/bblib/core/util/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

Send a chain of methods to an object and each result of the previous method.



66
67
68
69
70
71
# File 'lib/bblib/core/util/object.rb', line 66

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

.root_namespace_of(klass) ⇒ Object

Returns the root namespace of a given class if it is nested.



83
84
85
# File 'lib/bblib/core/util/object.rb', line 83

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: [], filter_base: true, &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/bblib/core/util/file.rb', line 17

def self.scan_dir(path, *filters, recursive: false, files: true, dirs: true, exclude: [], filter_base: true, &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 || filter_base && item.file_name =~ 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 || filter_base && item.file_name =~ filter })
        (block_given? ? yield(item) : [item] + recur)
      elsif recursive
        recur
      end
    end
  end.compact
end

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

Uses BBLib.scan_dir but returns only directories.



45
46
47
# File 'lib/bblib/core/util/file.rb', line 45

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

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

Uses BBLib.scan_dir but returns only files



40
41
42
# File 'lib/bblib/core/util/file.rb', line 40

def self.scan_files(path, *filters, recursive: false, exclude: [], filter_base: true, &block)
  scan_dir(path, *filters, recursive: recursive, dirs: false, exclude: exclude, filter_base: filter_base, &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/bblib/core/util/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



49
50
51
# File 'lib/bblib/core/util/cases.rb', line 49

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

.spinal_case(str) ⇒ Object



61
62
63
# File 'lib/bblib/core/util/cases.rb', line 61

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

.start_case(str, first_only: false) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/bblib/core/util/cases.rb', line 23

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/bblib/core/util/file.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/bblib/core/util/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
20
21
# File 'lib/bblib/core/util/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\d])\.(?=[\w\d])|(?<=\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
  combined = words.interleave(spacing).join
  combined.scan(/(?<=\.)\w(?=\.)/).each { |part| combined.sub!(".#{part}.", ".#{part}.".upcase) }
  combined
end

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

Turns a numeric input into a time string.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/bblib/core/util/time.rb', line 31

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/bblib/core/util/file.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

Takes any type of object and converts it into a hash based on its instance variables.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/bblib/core/util/object.rb', line 19

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



50
51
52
53
54
55
56
57
58
# File 'lib/bblib/core/util/time.rb', line 50

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/bblib/core/util/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



65
66
67
# File 'lib/bblib/core/util/cases.rb', line 65

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