lib/jekyll/algolia/utils.rb
# frozen_string_literal: true require 'nokogiri' module Jekyll module Algolia # Generic language-wide utils module Utils # Public: Allow redefining an instance method on the fly with a new one # # instance - The instance to overwrite # method - The method symbol to overwrite # block - The new block to use for replacing (as a proc) # # Solution found on # https://stackoverflow.com/questions/803020/redefining-a-single-ruby-method-on-a-single-instance-with-a-lambda/16631789 def self.monkey_patch(instance, method, block) metaclass = class << instance; self; end metaclass.send(:define_method, method, block) end # Public: Convert a hash with string keys to a hash with symbol keys # # hash - The input hash, with string keys def self.keys_to_symbols(hash) Hash[hash.map { |key, value| [key.to_sym, value] }] end # Public: Check if a variable is an instance of a specific class # # input - the variable to test # classname - the string representation of the class def self.instance_of?(input, classname) input.instance_of? Object.const_get(classname) rescue StandardError # The class might not even exist false end # Public: Convert an HTML string to its content only # # html - String representation of the HTML node def self.html_to_text(html) return nil if html.nil? text = Nokogiri::HTML(html).text text.tr("\n", ' ').squeeze(' ').strip end # Public: Remove all keys with a nil value or an empty string from a hash # # hash - The input hash def self.compact_empty(hash) new_hash = {} hash.each do |key, value| next if value.nil? next if value.is_a?(String) && value.empty? new_hash[key] = value end new_hash end # Public: Check if a string matches a regex # # string - The string to test # regex - The regex to match against # # Newer versions of Ruby have easy ways to test this, but a wrapper is # needed for older versions. def self.match?(string, regex) # Ruby 2.4 introduces .match? return regex.match?(string) if regex.respond_to?(:match?) # Older versions of Ruby have to deal with =~ returning nil if no match # is found !(string =~ regex).nil? end # Public: Find an item from an array based on the value of one of its key # # items - The array of hashes to search # key - The key to search for # value - The value of the key to filter # # It is basically a wrapper around [].find, handling more edge-cases def self.find_by_key(items, key, value) return nil if items.nil? items.find do |item| item[key] == value end end # Public: Convert an object into an object that can easily be converted to # JSON, to be stored as a record # # item - The object to convert # # It will keep any string, number, boolean,boolean,array or nested object, # but will try to stringify other objects, excluding the one that contain # a unique identifier once serialized. def self.jsonify(item) simple_types = [ NilClass, TrueClass, FalseClass, Integer, Float, String ] # Integer arrived in Ruby 2.4. Before that it was Fixnum and Bignum if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0') # rubocop:disable Lint/UnifiedInteger simple_types += [Fixnum, Bignum] # rubocop:enable Lint/UnifiedInteger end return item if simple_types.member?(item.class) # Recursive types return item.map { |value| jsonify(value) } if item.is_a?(Array) if item.is_a?(Hash) return item.map { |key, value| [key, jsonify(value)] }.to_h end # Can't be stringified, discard it return nil unless item.respond_to?(:to_s) # Discard also if is a serialized version with unique identifier stringified = item.to_s return nil if match?(stringified, /#<[^ ].*@[0-9]* .*>/) stringified end # Public: Get a hash representing the difference between two hashes # # It only checks that all keys of alpha are also in beta, with the same # value. If not, it remember what was the value of beta and return it in # the output def self.diff_keys(alpha, beta) diff = {} alpha.each do |key, value| diff[key] = beta[key] if beta[key] != value end return nil if diff.empty? diff end # Public: Split a long text into lines of specific length # # It takes care to not cut words def self.split_lines(input, max_length) # Force splitting on actual new lines first if input.include?("\n") output = [] input.split("\n").each do |line| output += split_lines(line, max_length) end return output end output = [] words = input.split(' ') current_line = words.shift || '' test_line = '' # must be defined outside of the loop words.each do |word| test_line = "#{current_line} #{word}" if test_line.length > max_length output << current_line current_line = word next end current_line = test_line end output << current_line # Making sure all lines are the same length output.map { |line| line.ljust(max_length, ' ') } end end end end