Module: BBLib

Defined in:
lib/string/matching.rb,
lib/bblib.rb,
lib/file/bbfile.rb,
lib/time/bbtime.rb,
lib/string/roman.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: 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.0"
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_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]},
  # titlecase: { aliases: [:title_case]},

  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]}
  # rename: { aliases: [:rename_key]},

  # concat: { aliases: [:join, :concat_with]},

  # reverse_concat: { aliases: [:reverse_join, :reverse_concat_with]}

}

Class Method Summary collapse

Class Method Details

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

.drop_symbols(str) ⇒ Object

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



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

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.



27
28
29
# File 'lib/string/bbstring.rb', line 27

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.



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

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

Alias for extract_floats



32
33
34
# File 'lib/string/bbstring.rb', line 32

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
38
39
# 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])
      matches.push hash.dig(patterns).flatten(1)[p[:slice]]
    else
      if p[:key].nil?
        if hash.is_a?(Array) then matches << hash[p[:slice]] end
      elsif Symbol === p[:key] || String === p[:key]
        if p[:key].to_s == '*'
          matches.push hash.values.flatten(1)[p[:slice]]
        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?
    # p "MAT #{matches}"

    # matches.map!{ |m| m.is_a?(Array) ? [m] : m }

    BBLib.hash_path(matches.reject{ |r| !(r.is_a?(Hash) || r.is_a?(Array)) }, path[1..-1], symbol_sensitive:symbol_sensitive)
  else
    return matches.flatten(1)
  end
end

.hash_path_copy(hash, *args) ⇒ Object



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

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



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

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



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

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)


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

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



142
143
144
# File 'lib/hash/hash_path.rb', line 142

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



138
139
140
# File 'lib/hash/hash_path.rb', line 138

def self.hash_path_keys hash
  hash.squish.keys
end

.hash_path_move(hash, *args) ⇒ Object



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

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



79
80
81
82
83
# File 'lib/hash/hash_path.rb', line 79

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



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/hash/hash_path_proc.rb', line 11

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|
    value = hash.hash_path(path).first
    if params.include?(:condition) && params[:condition]
      begin
        next unless eval(params[:condition].gsub('$', value.to_s))
      rescue
        next
      end
    end
    HashPath.send(action, hash, path, value, *args, **params)
  end
  return hash
end

.hash_path_set(hash, *args) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/hash/hash_path.rb', line 41

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
      exists = (details[:symbol_sensitive] ? h.include?(d[:last][:key]) : (h.include?(d[:last][:key].to_sym) || h.include?(d[:last][:key].to_s) ))
      next unless details[:bridge] || exists
      key = details[:symbol_sensitive] ? 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

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



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

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?
  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) ⇒ Object

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



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

def self.parse_duration str, output: :sec
  msecs = 0.0
  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



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

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

.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 ? '/**/*' : '/*')
  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

.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

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

Turns a numeric input into a time string.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/time/bbtime.rb', line 20

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