module BBLib
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.
String
Comparison Algorithms
Constants
- EXTRACT_NUMBER_REGEXP
- EXTRACT_NUMBER_REGEXP_NO_INNER
- FILE_SIZES
- NUMBER_WORDS
- REGEXP_MODE_HASH
- REGEXP_OPTIONS
- ROMAN_NUMERALS
- SPECIAL_PLURALS
- TIME_EXPS
- VERSION
Public Class Methods
Easy way to see if all objects in an array are of a given class.
# File lib/bblib/core/util/object.rb, line 3 def self.are_all?(klass, *objects) objects.all? { |object| object.is_a?(klass) } end
Easy way to see if any of the passed objects are of the given class.
# File lib/bblib/core/util/object.rb, line 8 def self.are_any?(klass, *objects) objects.any? { |object| object.is_a?(klass) } end
Takes an array and averages all of the floats and integers within it. Non numeric values are ignored.
# 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
Basic detection for whether or not a file is binary or not
# 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
# 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
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
# 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
# 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
# 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
# File lib/bblib/core/util/logging.rb, line 36 def self.color_logs @color_logs end
# File lib/bblib/core/util/logging.rb, line 40 def self.color_logs=(toggle) @color_logs = (toggle ? true : false) end
Calculates a percentage based match of two strings based on their character composition.
# 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
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.
# 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
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
# 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
# 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
# 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
# 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
Quickly remove any symbols from a string leaving only alpha-numeric characters and white space.
# File lib/bblib/core/util/string.rb, line 9 def self.drop_symbols(str) str.gsub(/[^\w\s\d]|_/, '') end
# File lib/bblib/core/util/logging.rb, line 28 def self.enable_logger(enable = true) @logger_on = enable end
Extracts all integers or decimals from a string into an array.
# 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 all integers from a string. Use extract_floats
if numbers may contain decimal places.
# 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
Extracts any correctly formed integers or floats from a string
# 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
# 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
Similar to named_args
but also treats String
keys as named arguments.
# 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
# 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
# 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
# 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
# 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
# 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
# File lib/bblib/core/hash_path/hash_path.rb, line 59 def self.hash_path_keys(hash) hash.to_tree_hash.absolute_paths end
# 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
# 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
# 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
# File lib/bblib/core/util/opal.rb, line 2 def self.in_opal? RUBY_ENGINE == 'opal' end
Takes two arrays (can be of different length) and interleaves them like [a, b, a, b…]
# 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
Checks to see if an object is of any of the given classes.
# File lib/bblib/core/util/object.rb, line 13 def self.is_any?(object, *klasses) klasses.any? { |klass| object.is_a?(klass) } end
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.
# 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
A simple rendition of the levenshtein distance algorithm
# 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
Calculates a percentage based match using the levenshtein distance algorithm
# 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
# File lib/bblib/core/util/logging.rb, line 32 def self.log_enabled? @logger_on end
# File lib/bblib/core/util/logging.rb, line 5 def self.logger @logger ||= default_logger end
# 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
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.
# 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
# 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
# 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
Returns the element that occurs the most frequently in an array or
# 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
Returns the most commonly occurring string in an arrray of params. Elements that are not strings are converted to their string representations.
@param [TrueClass, FalseClass] case_insensitive Compare strings case isensitively.
# 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
Used to move the position of the articles 'the', 'a' and 'an' in strings for normalization.
# 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
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.
# 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
Same as standard named_args
but removes the named arguments from the array.
# 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
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.
# 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
TODO: Support floats eventually?
# 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
Extracts all numbers from two strings and compares them and generates a percentage of match. Percentage calculations here need to be weighted better…TODO
# 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
Parses known time based patterns out of a string to construct a numeric duration.
# 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
A file size parser for strings. Extracts any known patterns for file sizes.
# 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
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
# 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 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.
# 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
Calculates a percentage based match between two strings based on the similarity of word matches.
# 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
# File lib/bblib/core/util/pluralization.rb, line 132 def self.plural_string(num, string) "#{num} #{pluralize(string, num)}" end
# 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
A simple character distance calculator that uses qwerty key positions to determine how similar two strings are. May be useful for typo detection.
# 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
Send a chain of methods to an object and each result of the previous method.
# 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
Returns the root namespace of a given class if it is nested.
# File lib/bblib/core/util/object.rb, line 83 def self.root_namespace_of(klass) Object.const_get(klass.to_s.gsub(/::.*/, '')) end
Scan for files and directories. Can be set to be recursive and can also have filters applied. @param [String] path The directory to scan files from. @param [String…, Regexp
…] filters 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.
@param [Boolean] recursive When true scan will recursively search directories @param [Boolean] files If true, paths to files matching the filter will be returned. @param [Boolean] dirs If true, paths to dirs matching the filter will be returned. @param [Array] exclude 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.
# 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
Uses BBLib.scan_dir
but returns only directories.
# 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
Uses BBLib.scan_dir
but returns only files
# 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
# 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
# File lib/bblib/core/util/cases.rb, line 49 def self.snake_case(str) BBLib.delimited_case(str, '_') end
# File lib/bblib/core/util/cases.rb, line 61 def self.spinal_case(str) BBLib.delimited_case str, '-' end
# 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
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.
# 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
# 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
# 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
Turns a numeric input into a time string.
# 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
Takes an integer or float and converts it into a string that represents
a file size (e.g. "5 MB 156 kB")
@param [Integer, Float] num The number of bytes to convert to a file size string. @param [Symbol] input Sets the value of the input. Default is byte. @param [Symbol] stop Sets a minimum file size to display.
e.g. If stop is set to :megabyte, :kilobyte and below will be truncated.
@param [Symbol] style The out style, Current options are :short and :long
# 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
Takes any type of object and converts it into a hash based on its instance variables.
# 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
# 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
Converts any integer up to 1000 to a roman numeral
# 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
# File lib/bblib/core/util/cases.rb, line 65 def self.train_case(str) BBLib.spinal_case(BBLib.start_case(str)) end