# frozen_string_literal: true # An implementation of a unique_names_generator in Ruby # with support for PRNG/deterministic seeds require_relative './unique_names_generator/seed' require_relative './unique_names_generator/dictionaries/adjectives' require_relative './unique_names_generator/dictionaries/animals' require_relative './unique_names_generator/dictionaries/colors' require_relative './unique_names_generator/dictionaries/languages' require_relative './unique_names_generator/dictionaries/names' require_relative './unique_names_generator/dictionaries/numbers' require_relative './unique_names_generator/dictionaries/star_wars' # UniqueNamesGenerator implementation module UniqueNamesGenerator # UniqueNamesGenerator::Generator # # This class generates unique names based on specified dictionaries and configuration options. # It allows for customizable name generation with various styles, separators, and creativity levels. class Generator def initialize(dictionaries, separator: '_', style: :lowercase, creativity: 0) @dictionaries = dictionaries @separator = separator @style = style @creativity = creativity # Will raise error if any specified dictionary is invalid dictionaries.map { |dictionary| word_list(dictionary) } raise ArgumentError, 'Outside creativity range. Must be between 0 and 10.' if outside_creativity_bounds? end def generate(seed: nil) @seed = seed generate_name(@dictionaries) end private def outside_creativity_bounds? @creativity.negative? || @creativity > 10 end def match_word_list(dictionary) module_name = camelize_dictionary(dictionary) begin dictionary = Dictionaries.const_get(module_name) dictionary.list_all rescue NameError raise_invalid_dictionary(dictionary) end end def word_list(dictionary) case dictionary when Array dictionary if dictionary.all? { |item| item.is_a?(String) } when Symbol match_word_list(dictionary) else raise ArgumentError, 'Dictionary contains invalid dictionary type' end end def random_seeded_float seed_value = Seed.generate_seed(@seed) prng = @seed.nil? ? Random.new : Random.new(seed_value) prng.rand end def map_dictionaries(dictionaries) dictionaries.map { |dictionary| word_list(dictionary) } end def split_with_separator(word) if @separator.nil? word.split(/(?=[A-Z])/) else word.split(@separator) end end def format_parts(parts) parts.map do |part| if part.match?(/[^a-zA-Z]/) && @style == :capital # Preserve original capitalization for parts with non-letter characters part else format_word(part, @style) end end.join(@separator) end def format_multi_word(word, original_word) if original_word.include?(' ') || original_word.include?('-') parts = split_with_separator(word) format_parts(parts) else format_word(word, @style) end end def generate_name(dictionaries) if @creativity.nil? || @creativity.zero? generate_name_original(dictionaries) else generate_name_creatively(dictionaries) end end def generate_name_original(dictionaries) map_dictionaries(dictionaries).reduce(nil) do |acc, x| rnd = (random_seeded_float * x.length).floor original_word = x[rnd] output_word(acc, original_word) end end def generate_name_creatively(dictionaries) word_lists = map_dictionaries(dictionaries) word_lists.each_with_index.reduce(nil) do |acc, (word_list, index)| creativity = calculate_creativity(index) rnd = (random_seeded_float * word_list.length * creativity).floor original_word = word_list[rnd % word_list.length] # Ensure we don't go out of bounds output_word(acc, original_word) end end def output_word(acc, original_word) word = format_with_separator(original_word) word = format_multi_word(word, original_word) if acc "#{acc}#{@separator}#{word}" else word end end def calculate_creativity(index) if index.zero? @creativity # Base creativity for the first dictionary else @creativity * (2 + index * 0.5) # Increase creativity for subsequent dictionaries end end def camelize_dictionary(dictionary) dictionary.to_s.split('_').map(&:capitalize).join end def raise_invalid_dictionary(dictionary) raise ArgumentError, "Invalid dictionary #{dictionary}" end def format_with_separator(word) if @separator.nil? # If separator is empty, just remove spaces without changing case word.gsub(/\s+/, '') else # If there's a separator, use it to replace spaces word.gsub(/\s+/, @separator) end end def format_word(word, style) case style when :lowercase word.downcase when :uppercase word.upcase when :capital word.capitalize else word end end end end