Module: BBLib

Defined in:
lib/string/matching.rb,
lib/bblib.rb,
lib/time/cron.rb,
lib/file/bbfile.rb,
lib/time/bbtime.rb,
lib/string/cases.rb,
lib/string/roman.rb,
lib/array/bbarray.rb,
lib/bblib/version.rb,
lib/hash/hash_path.rb,
lib/number/bbnumber.rb,
lib/object/bbobject.rb,
lib/string/bbstring.rb,
lib/time/task_timer.rb,
lib/hash/hash_path_proc.rb,
lib/string/fuzzy_matcher.rb

Overview

String Comparison Algorithms

Defined Under Namespace

Modules: HashPath Classes: Cron, FuzzyMatcher, TaskTimer

Constant Summary collapse

CONFIGS_PATH =
'config/'
FILE_SIZES =
{
  byte: { mult: 1, exp: ['b', 'byt', 'byte'] },
  kilobyte: { mult: 1024, exp: ['kb', 'kilo', 'k', 'kbyte', 'kilobyte'] },
  megabyte: { mult: 1048576, exp: ['mb', 'mega', 'm', 'mib', 'mbyte', 'megabyte'] },
  gigabyte: { mult: 1073741824, exp: ['gb', 'giga', 'g', 'gbyte', 'gigabyte'] },
  terabyte: { mult: 1099511627776, exp: ['tb', 'tera', 't', 'tbyte', 'terabyte'] },
  petabyte: { mult: 1125899906842624, exp: ['pb', 'peta', 'p', 'pbyte', 'petabyte'] },
  exabyte: { mult: 1152921504606846976, exp: ['eb', 'exa', 'e', 'ebyte', 'exabyte'] },
  zettabyte: { mult: 1180591620717411303424, exp: ['zb', 'zetta', 'z', 'zbyte', 'zettabyte'] },
  yottabyte: { mult: 1208925819614629174706176, exp: ['yb', 'yotta', 'y', 'ybyte', 'yottabyte'] }
}
TIME_EXPS =
{
  yocto: {
    mult: 0.000000000000000000001,
    styles: {full: ' yoctosecond', medium: ' yocto', short: 'ys'},
    exp: ['yoctosecond', 'yocto', 'yoctoseconds', 'yoctos', 'ys']
  },
  zepto: {
    mult: 0.000000000000000001,
    styles: {full: ' zeptosecond', medium: ' zepto', short: 'zs'},
    exp: ['zeptosecond', 'zepto', 'zeptoseconds', 'zeptos', 'zs']
  },
  atto: {
    mult: 0.000000000000001,
    styles: {full: ' attosecond', medium: ' atto', short: 'as'},
    exp: ['attoseconds', 'atto', 'attoseconds', 'attos', 'as']
  },
  femto: {
    mult: 0.000000000001,
    styles: {full: ' femtosecond', medium: ' fempto', short: 'fs'},
    exp: ['femtosecond', 'fempto', 'femtoseconds', 'femptos', 'fs']
  },
  pico: {
    mult: 0.000000001,
    styles: {full: ' picosecond', medium: ' pico', short: 'ps'},
    exp: ['picosecond', 'pico', 'picoseconds', 'picos', 'ps']
  },
  nano: {
    mult: 0.000001,
    styles: {full: ' nanosecond', medium: ' nano', short: 'ns'},
    exp: ['nanosecond', 'nano', 'nanoseconds', 'nanos', 'ns']
  },
  micro: {
    mult: 0.001,
    styles: {full: ' microsecond', medium: ' micro', short: 'μs'},
    exp: ['microsecond', 'micro', 'microseconds', 'micros', 'μs']
  },
  milli: {
    mult: 1,
    styles: {full: ' millisecond', medium: ' mil', short: 'ms'},
    exp: ['ms', 'mil', 'mils', 'milli', 'millis', 'millisecond', 'milliseconds', 'milsec', 'milsecs', 'msec', 'msecs', 'msecond', 'mseconds']},
  sec: {
    mult: 1000,
    styles: {full: ' second', medium: ' sec', short: 's'},
    exp: ['s', 'sec', 'secs', 'second', 'seconds']},
  min: {
    mult: 60000,
    styles: {full: ' minute', medium: ' min', short: 'm'},
    exp: ['m', 'mn', 'mns', 'min', 'mins', 'minute', 'minutes']},
  hour: {
    mult: 3600000,
    styles: {full: ' hour', medium: ' hr', short: 'h'},
    exp: ['h', 'hr', 'hrs', 'hour', 'hours']},
  day: {
    mult: 86400000,
    styles: {full: ' day', medium: ' day', short: 'd'},
    exp: ['d', 'day', 'days']},
  week: {
    mult: 604800000,
    styles: {full: ' week', medium: ' wk', short: 'w'},
    exp: ['w', 'wk', 'wks', 'week', 'weeks']},
  month: {
    mult: 2592000000,
    styles: {full: ' month', medium: ' mo', short: 'mo'},
    exp: ['mo', 'mon', 'mons', 'month', 'months', 'mnth', 'mnths', 'mth', 'mths']},
  year: {
    mult: 31536000000,
    styles: {full: ' year', medium: ' yr', short: 'y'},
    exp: ['y', 'yr', 'yrs', 'year', 'years']}
}
VERSION =
"0.2.2"
HASH_PATH_PROC_TYPES =
{
  evaluate: { aliases: [:eval, :equation, :equate]},
  append: { aliases: [:suffix]},
  prepend: { aliases: [:prefix]},
  split: { aliases: [:delimit, :delim, :separate, :msplit]},
  replace: { aliases: [:swap]},
  extract: { aliases: [:grab, :scan]},
  extract_first: {aliases: [:grab_first, :scan_first]},
  extract_last: {aliases: [:grab_last, :scan_last]},
  parse_date: { aliases: [:date, :parse_time, :time]},
  parse_date_unix: { aliases: [:unix_time, :unix_date]},
  parse_duration: { aliases: [:duration]},
  parse_file_size: { aliases: [:file_size]},
  to_string: {aliases: [:to_s, :stringify]},
  downcase: { aliases: [:lower, :lowercase, :to_lower]},
  upcase: { aliases: [:upper, :uppercase, :to_upper]},
  roman: { aliases: [:convert_roman, :roman_numeral, :parse_roman]},
  remove_symbols: { aliases: [:chop_symbols, :drop_symbols]},
  format_articles: { aliases: [:articles]},
  reverse: { aliases: [:invert]},
  delete: { aliases: [:del]},
  remove: { aliases: [:rem]},
  custom: {aliases: [:send]},
  # TODO

  # titlecase: { aliases: [:title_case]},

  encapsulate: {aliases: []},
  uncapsulate: {aliases: []},
  extract_integers: {aliases: [:extract_ints]},
  extract_floats: {aliases: []},
  extract_numbers: {aliases: []},
  max_number: {aliases: [:max, :maximum, :maximum_number]},
  min_number: {aliases: [:min, :minimum, :minimum_number]},
  avg_number: {aliases: [:avg, :average, :average_number]},
  sum_number: {aliases: [:sum]},
  strip: {aliases: [:trim]},
  # rename: { aliases: [:rename_key]},

  concat: { aliases: [:join, :concat_with]},
  reverse_concat: { aliases: [:reverse_join, :reverse_concat_with]}
}

Class Method Summary collapse

Class Method Details

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



38
39
40
41
42
43
44
45
# File 'lib/string/cases.rb', line 38

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

.composition_similarity(a, b) ⇒ Object

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



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/string/matching.rb', line 27

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

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



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

def self.delimited_case str, delimiter = '_'
  regx = /[[:space:]]+|[^[[:alnum:]]]+|#{delimiter}+/
  words = 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.



14
15
16
# File 'lib/string/bbstring.rb', line 14

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

.extract_floats(str, convert: true) ⇒ Object

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



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

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.



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

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
# File 'lib/string/bbstring.rb', line 29

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

.from_roman(str) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/string/roman.rb', line 32

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

.hash_path(hashes, path, recursive: false, delimiter: '.', symbol_sensitive: false) ⇒ Object



5
6
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
33
34
35
36
37
# File 'lib/hash/hash_path.rb', line 5

def self.hash_path hashes, path, recursive: false, delimiter: '.', symbol_sensitive: false
  if String === path then path = BBLib.split_and_analyze(path, delimiter) end
  if !hashes.is_a? Array then hashes = [hashes] end
  return hashes if path.nil? || path.empty?
  if path[0][:key] == '' then return BBLib.hash_path(hashes, path[1..-1], recursive: true, symbol_sensitive:symbol_sensitive) end
  matches, p = Array.new, path.first
  hashes.each do |hash|
    if recursive
      patterns = Regexp === p[:key] ? p[:key] : p[:key].to_s == '*' ? /.*/ : (symbol_sensitive ? p[:key] : [p[:key].to_sym, p[:key].to_s])
      hash.dig(patterns)[p[:slice]].each{ |va| matches.push va }
    else
      if p[:key].nil?
        if hash.is_a?(Array) then hash[p[:slice]].each{ |h| matches << h } end
      elsif Symbol === p[:key] || String === p[:key]
        if p[:key].to_s == '*'
          hash.values[p[:slice]].each{ |va| matches.push va }
        else
          next unless symbol_sensitive ? hash.include?(p[:key]) : (hash.include?(p[:key].to_sym) || hash.include?(p[:key].to_s) )
          mat = (symbol_sensitive ? hash[p[:key]] : ( if hash.include?(p[:key].to_sym) then hash[p[:key].to_sym] else hash[p[:key].to_s] end ))
          matches.push mat.is_a?(Array) ? mat[p[:slice]] : mat
        end
      elsif Regexp === p[:key]
        hash.keys.find_all{ |k| k =~ p[:key] }.each{ |m| matches << hash[m] }
      end
    end
  end
  matches = BBLib.analyze_hash_path_formula p[:formula], matches
  if path.size > 1 && !matches.empty?
    BBLib.hash_path(matches.reject{ |r| !(r.is_a?(Hash) || r.is_a?(Array)) }, path[1..-1], symbol_sensitive:symbol_sensitive)
  else
    return matches
  end
end

.hash_path_copy(hash, *args) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/hash/hash_path.rb', line 87

def self.hash_path_copy hash, *args
  details = BBLib.hash_path_setup(hash, args)
  details[:paths].each do |path, d|
    d[:hashes].each do |h|
      if Hash === h || Array === h
        exist = details[:symbol_sensitive] ? h.include?(d[:last][:key]) : (h.include?(d[:last][:key].to_sym) || h.include?(d[:last][:key].to_s) )
        next unless exist || details[:bridge]
        value = details[:symbol_sensitive] ? h[d[:last][:key]] : (if h.include?(d[:last][:key].to_sym) then h[d[:last][:key].to_sym] else h[d[:last][:key].to_s] end )
        if value
          BBLib.hash_path_set hash, d[:value] => value, symbols:details[:symbols]
        end
      elsif !details[:stop_on_nil]
        BBLib.hash_path_set hash, d[:value] => nil, symbols:details[:symbols]
      end
    end
  end
  hash
end

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



106
107
108
109
110
111
112
113
114
115
116
# File 'lib/hash/hash_path.rb', line 106

def self.hash_path_copy_to from, to, *args
  details = BBLib.hash_path_setup(from, args)
  details[:paths].each do |path, d|
    value = from.hash_path(path)
    if !value.empty? || !details[:stop_on_nil]
      if !details[:arrays].include?(d[:value]) then value = value.first end
      to = to.bridge(d[:value], value: value, symbols:details[:symbols])
    end
  end
  to
end

.hash_path_delete(hash, *args) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/hash/hash_path.rb', line 118

def self.hash_path_delete hash, *args
  args.flatten!
  details = BBLib.hash_path_setup hash, [args.find{ |f| Hash === f }.to_h.merge(args.find_all{ |a| String === a }.zip([]).to_h)]
  deleted = []
  details[:paths].each do |path, d|
    d[:hashes].each do |h|
      next unless details[:symbol_sensitive] ? h.include?(d[:last][:key]) : (h.include?(d[:last][:key].to_sym) || h.include?(d[:last][:key].to_s) )
      if Fixnum === d[:last][:slice]
        (details[:symbol_sensitive] ? h[d[:last][:key]] : (if h.include?(d[:last][:key].to_sym) then h[d[:last][:key].to_sym] else h[d[:last][:key].to_s] end)).delete_at d[:last][:slice]
      else
        if details[:symbol_sensitive]
          deleted << h.delete(d[:last][:key])
        else
          if h.include?(d[:last][:key].to_sym) then deleted << h.delete(d[:last][:key].to_sym) end
          if h.include?(d[:last][:key].to_s) then deleted << h.delete(d[:last][:key].to_s) end
        end
      end
    end
  end
  return deleted.flatten
end

.hash_path_exists?(hash, path, delimiter: '.', symbol_sensitive: false) ⇒ Boolean

Returns:

  • (Boolean)


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

def self.hash_path_exists? hash, path, delimiter: '.', symbol_sensitive: false
  return !BBLib.hash_path(hash, path, delimiter:delimiter, symbol_sensitive:symbol_sensitive).empty?
end

.hash_path_key_for(hash, value) ⇒ Object



144
145
146
# File 'lib/hash/hash_path.rb', line 144

def self.hash_path_key_for hash, value
  hash.squish.find_all{ |k,v| v == value }.to_h.keys
end

.hash_path_keys(hash) ⇒ Object



140
141
142
# File 'lib/hash/hash_path.rb', line 140

def self.hash_path_keys hash
  hash.squish.keys
end

.hash_path_move(hash, *args) ⇒ Object



70
71
72
73
74
75
76
77
78
79
# File 'lib/hash/hash_path.rb', line 70

def self.hash_path_move hash, *args
  BBLib.hash_path_copy hash, args
  details = BBLib.hash_path_setup(hash, args)
  opts = Hash.new
  details.each do |k, v|
    if HASH_PATH_PARAMS.include?(k) then opts[k] = v end
  end
  BBLib.hash_path_delete hash, [details[:paths].keys, opts ].flatten
  return hash
end

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



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

def self.hash_path_move_to from, to, *args
  BBLib.hash_path_copy_to from, to, args
  BBLib.hash_path_delete from, args
  return to
end

.hash_path_proc(hash, action, paths, *args, **params) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/hash/hash_path_proc.rb', line 17

def self.hash_path_proc hash, action, paths, *args, **params
  action = HASH_PATH_PROC_TYPES.keys.find{ |k| k == action || HASH_PATH_PROC_TYPES[k][:aliases].include?(action) }
  return nil unless action
  paths.to_a.each do |path|
    hash.hash_path(path).each do |value|
      if params.include?(:condition) && params[:condition]
        begin
          next unless eval(params[:condition].gsub('$', value.to_s))
        rescue StandardError, SyntaxError => e
          next
        end
      end
      HashPath.send(action, hash, path, value, *args, **params)
    end
  end
  return hash
end

.hash_path_set(hash, *args) ⇒ Object



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
# File 'lib/hash/hash_path.rb', line 39

def self.hash_path_set hash, *args
  details = BBLib.hash_path_setup(hash, args)
  count = 0
  details[:paths].each do |path, d|
    d[:hashes].each do |h|
      count+=1
      if d[:last][:key].is_a?(Regexp)
        exists = h.keys.any?{ |k| k.to_s =~ d[:last][:key] }
      else
        exists = (details[:symbol_sensitive] ? h.include?(d[:last][:key]) : (h.include?(d[:last][:key].to_sym) || h.include?(d[:last][:key].to_s) ))
      end
      next unless details[:bridge] || exists
      key = details[:symbol_sensitive] || d[:last][:key].is_a?(Regexp) ? d[:last][:key] : (h.include?(d[:last][:key].to_sym) ? d[:last][:key].to_sym : d[:last][:key].to_s)
      # if details[:symbols] then key = key.to_sym elsif !exists then key = d[:last][:key] end

      if Fixnum === d[:last][:slice]
        h[key][d[:last][:slice]] = d[:value]
      else
        if h.is_a?(Hash)
          h[key] = d[:value]
        end
      end
    end
    if count == 0 && details[:bridge] then hash.bridge(path, value:d[:value], symbols:details[:symbols]) end
  end
  hash
end

.interleave(a, b, filler: nil) ⇒ 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
13
# File 'lib/array/bbarray.rb', line 5

def self.interleave a, b, filler: nil
  if a.size < b.size
    a = a.dup
    while a.size < b.size
      a.push filler
    end
  end
  a.zip(b).flatten(1)
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
  raise "Argument must be numeric: #{num} (#{num.class})" unless Numeric === num
  if !min.nil? && num < min then num = min end
  if !max.nil? && num > max then num = max end
  return 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
# 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], nw = i, 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



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

def self.levenshtein_similarity a, b
  distance = BBLib.levenshtein_distance a, b
  max = [a.length, b.length].max.to_f
  return ((max - distance.to_f) / max) * 100.0
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.



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

def self.move_articles str, position = :front, capitalize: true
  return str unless [:front, :back, :none].include? position
  articles = ["the", "a", "an"]
  articles.each do |a|
    starts, ends = str.downcase.start_with?(a + ' '), 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
    if 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
  end
  while str.strip.end_with?(',')
    str.strip!
    str.chop!
  end
  str
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



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

def self.numeric_similarity a, b
  a, b = a.extract_numbers, b.extract_numbers
  return 100.0 if a.empty? && b.empty? || a == b
  matches = []
  for i in 0..[a.size, b.size].max-1
    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
33
# 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] rescue 1)
end

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

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



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/file/bbfile.rb', line 35

def self.parse_file_size str, output: :byte
  output = FILE_SIZES.keys.find{ |f| f == output || FILE_SIZES[f][:exp].include?(output.to_s.downcase) } || :byte
  bytes = 0.0
  FILE_SIZES.each do |k, v|
    v[:exp].each do |e|
      numbers = str.scan(/(?=\w|\D|\A)\d*\.?\d+[[:space:]]*#{e}s?(?=\W|\d|\z)/i)
      numbers.each{ |n| bytes+= n.to_f * v[:mult] }
    end
  end
  return bytes / FILE_SIZES[output][:mult]
end

.path_nav(obj, path = '', delimiter = '.', &block) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
# File 'lib/hash/hash_path.rb', line 148

def self.path_nav obj, path = '', delimiter = '.', &block
  case [obj.class]
  when [Hash]
    obj.each{ |k,v| path_nav v, "#{path}#{path.nil? || path.empty? ? nil : delimiter}#{k}", delimiter, &block }
  when [Array]
    index = 0
    obj.each{ |o| path_nav o, "#{path}#{path.end_with?(']') ? delimiter : nil}[#{index}]", delimiter, &block ; index+=1 }
  else
    yield path, obj
  end
end

.phrase_similarity(a, b) ⇒ Object

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



40
41
42
43
44
45
46
47
48
49
50
# File 'lib/string/matching.rb', line 40

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

.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.



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/string/matching.rb', line 66

def self.qwerty_distance a, b
  a, b = a.downcase.strip, b.downcase.strip
  if a.length <= b.length then t = a; a = b; b = t; end
  qwerty = {
    1 => ['1','2','3','4','5','6','7','8','9','0'],
    2 => ['q','w','e','r','t','y','u','i','o','p'],
    3 => ['a','s','d','f','g','h','j','k','l'],
    4 => ['z','x','c','v','b','n','m']
  }
  count, offset = 0, 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

.root_dirsObject

A mostly platform agnostic call to get root volumes



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/file/bbfile.rb', line 48

def self.root_dirs
  begin # For windows

    `wmic logicaldisk get name`.split("\n").map{ |m| m.strip }[1..-1].reject{ |r| r == '' }
  rescue
    begin # Windows attempt 2

      `fsutil fsinfo drives`.scan(/(?<=\s)\w\:/)
    rescue  # Linux

      begin
        `ls /`.split("\n").map{ |m| m.strip }.reject{ |r| r == '' }
      rescue # All attempts failed

        nil
      end
    end
  end
end

.root_volume_labelsObject

Windows only method to get the volume labels of disk drives



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

def self.root_volume_labels
  `wmic logicaldisk get caption,volumename`.split("\n")[1..-1].map{ |m| [m.split("  ").first.to_s.strip, m.split("  ")[1..-1].to_a.join(' ').strip] }.reject{ |o,t| o == '' }.to_h
end

.scan_dir(path = Dir.pwd, filter: nil, recursive: false) ⇒ Object

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



6
7
8
9
10
11
12
13
# File 'lib/file/bbfile.rb', line 6

def self.scan_dir path = Dir.pwd, filter: nil, recursive: false
  if !filter.nil?
    filter = [filter].flatten.map{ |f| path.to_s + (recursive ? '/**/' : '/') + f.to_s }
  else
    filter = (path.to_s + (recursive ? '/**/*' : '/*')).gsub('//', '/')
  end
  Dir.glob(filter)
end

.scan_dirs(path, filter: nil, recursive: false, mode: :path) ⇒ Object

Uses BBLib.scan_dir but returns only directories. Mode can be used to return strings (:path) or Dir objects (:dir)



21
22
23
# File 'lib/file/bbfile.rb', line 21

def self.scan_dirs path, filter: nil, recursive: false, mode: :path
  BBLib.scan_dir(path, filter: filter, recursive: recursive).map{ |f| File.directory?(f) ? (mode == :dir ? Dir.new(f) : f ) : nil}.reject{ |r| r.nil? }
end

.scan_files(path, filter: nil, recursive: false, mode: :path) ⇒ Object

Uses BBLib.scan_dir but returns only files. Mode can be used to return strings (:path) or File objects (:file)



16
17
18
# File 'lib/file/bbfile.rb', line 16

def self.scan_files path, filter: nil, recursive: false, mode: :path
  BBLib.scan_dir(path, filter: filter, recursive: recursive).map{ |f| File.file?(f) ? (mode == :file ? File.new(f) : f) : nil}.reject{ |r| r.nil? }
end

.snake_case(str) ⇒ Object



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

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

.spinal_case(str) ⇒ Object



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

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

.start_case(str, first_only: false) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/string/cases.rb', line 24

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(path, str, mkpath = true, mode: 'a') ⇒ Object

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



27
28
29
30
31
32
# File 'lib/file/bbfile.rb', line 27

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

.string_to_roman(str) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
# 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
  sp.join ' '
end

.title_case(str, first_only: true) ⇒ Object



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

def self.title_case str, first_only: true
  ignoreables = ['a', 'an', 'the', 'on', 'upon', 'and', 'but', 'or', 'in', 'with', 'to']
  regx = /[[:space:]]+|\-|\_|\"|\'|\(|\)|\[|\]|\{|\}|\#/
  spacing = str.scan(regx).to_a
  words = str.split(regx).map do |word|
    if ignoreables.include?(word.downcase)
      word.downcase
    else
      if first_only
        word[0] = word[0].upcase
        word
      else
        word.capitalize
      end
    end
  end
  # Always cap the first word

  words.first.capitalize
  words.interleave(spacing).join
end

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

Turns a numeric input into a time string.



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

def self.to_duration num, input: :sec, stop: :milli, style: :medium
  return nil unless Numeric === num || num > 0
  if ![:full, :medium, :short].include?(style) then style = :medium end
  expression = []
  n, done = num * TIME_EXPS[input.to_sym][:mult], false
  TIME_EXPS.reverse.each do |k, v|
    next unless !done
    if k == stop then done = true end
    div = n / v[:mult]
    if 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
  end
  expression.join ' '
end

.to_hash(obj) ⇒ Object



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

def self.to_hash obj
  return {obj => nil} unless !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
        if !hash[var.to_s.delete("@")].is_a?(Hash) then hash[var.to_s.delete("@")] = Hash.new end
      rescue
        hash[var.to_s.delete("@")] = Hash.new
      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
  return hash
end

.to_roman(num) ⇒ Object

Converts any integer up to 1000 to a roman numeral string_a



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

def self.to_roman num
  return num.to_s if num > 1000
   roman = {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'}
  numeral = ""
  roman.each do |n, r|
    while num >= n
      num-= n
      numeral+= r
    end
  end
  numeral
end

.train_case(str) ⇒ Object



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

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