<commit_before>class String
  def surrounded_by_brackets?
    self[0..0] == '(' && self[-1..-1] == ')'
  end
end

# Handle all our silly name parsing needs
class Name
  attr_reader :title, :initials, :first, :nick, :middle, :last, :post_title
  
  def initialize(params)
    @title = params[:title] || ""
    @first = (Name.capitalize_name(params[:first]) if params[:first]) || ""
    @nick = (Name.capitalize_name(params[:nick]) if params[:nick]) || ""
    @middle = (Name.capitalize_each_name(params[:middle]) if params[:middle]) || ""
    @post_title = (params[:post_title].upcase if params[:post_title]) || ""
    @last = (Name.capitalize_each_name(params[:last]) if params[:last]) || ""
    if params[:initials]
      @initials = params[:initials].upcase
      if has_first? || has_middle?
        throw "Given initials #{params[:initials]} do not match given names" unless initials_from_names == @initials
      end
    else
      @initials = initials_from_names
    end
      
    throw "Invalid keys" unless (params.keys - [:title, :initials, :first, :nick, :middle, :last, :post_title]).empty?
  end
  
  # Initials purely obtained from the first and middle names
  def initials_from_names
    i = ""
    i = i + first[0..0] if has_first?
    i = i + middle.split(' ').map{|n| n[0..0]}.join if has_middle?
    i
  end
  
  def Name.last_title_first(text)
    names = text.delete(',').split(' ')
    # Check for a name in brackets which we take as the nickname
    nickname_text = names.find{|n| n.surrounded_by_brackets?}
    if nickname_text
      nick = nickname_text[1..-2]
      names.delete(nickname_text)
    end
    # Hack to deal with a specific person who has two last names that aren't hyphenated
    if names.size >= 2 && names[0].downcase == "stott" && names[1].downcase == "despoja"
      last = names[0..1].join(' ')
      names.shift
      names.shift
    else
      last = names.shift
    end
    title = Name.extract_title_at_start(names)
    first = names.shift
    throw "Too few names" if first.nil?
    post_titles = []
    while post_title = Name.post_title(names)
      post_titles.unshift(post_title)
    end
    post_title = post_titles.join(' ')
    middle = names[0..-1].join(' ')
    Name.new(:title => title, :last => last, :first => first, :nick => nick, :middle => middle, :post_title => post_title)
  end
  
  # Parse name in the form: "<NAME>"
  def Name.last_title_initials(text)
    names = text.delete(',').split(' ')
    last = names.shift
    title = Name.extract_title_at_start(names)
    initials = names.shift
    Name.new(:last => last, :title => title, :initials => initials)
  end
  
  # Extract a post title from the end if one is available
  def Name.post_title(names)
    if names.last == "AM" || names.last == "SC" || names.last == "AO" ||
      names.last == "MBE" || names.last == "QC" || names.last == "OBE" ||
      names.last == "KSJ" || names.last == "JP"
      names.pop
    end
  end
  
  def Name.title_first_last(text)
    names = text.delete(',').split(' ')
    title = Name.extract_title_at_start(names)
    throw "Too few names" if names.empty?
    if names.size == 1
      last = names[0]
    else
      first = names[0]
      last = names[-1]
      middle = names[1..-2].join(' ')
    end
    Name.new(:title => title, :last => last, :first => first, :middle => middle)
  end
  
  def informal_name
    throw "No last name" unless has_last?
    if @nick != ""
      "#{@nick} #{@last}"
    else
      if has_first?
        "#{@first} #{@last}"
      elsif has_initials?
        "#{@initials} #{@last}"
      else
        throw "No first name or initials"
      end
    end
  end
  
  def full_name
    t = ""
    t = t + "#{title} " if has_title?
    t = t + "#{first} " if has_first?
    t = t + "(#{nick}) " if has_nick?
    t = t + "#{middle} " if has_middle?
    t = t + "#{last}"
    t = t + ", #{post_title}" if has_post_title?
    t
  end
  
  def has_title?
    @title != ""
  end
  
  def has_initials?
    @initials != ""
  end
  
  def has_first?
    @first != ""
  end
  
  def has_nick?
    @nick != ""
  end
  
  def has_middle?
    @middle != ""
  end
  
  def has_last?
    @last != ""
  end
  
  def has_post_title?
    @post_title != ""
  end
  
  # Names don't have to be identical to match but rather the parts of the name
  # that exist in both names have to match
  def matches_simply?(name)
    # True if there is overlap between the names
    overlap = (has_title? && name.has_title?) ||
      (has_initials?   && name.has_initials?) ||
      (has_first?      && name.has_first?) ||
      (has_nick?       && name.has_nick?) ||
      (has_middle?     && name.has_middle?) ||
      (has_last?       && name.has_last?) ||
      (has_post_title? && name.has_post_title?)
      
    overlap &&
      (!has_title?      || !name.has_title?      || @title      == name.title) &&
      (!has_initials?   || !name.has_initials?   || @initials   == name.initials) &&
      (!has_first?      || !name.has_first?      || @first      == name.first) &&
      (!has_nick?       || !name.has_nick?       || @nick       == name.nick) &&
      (!has_middle?     || !name.has_middle?     || @middle     == name.middle) &&
      (!has_last?       || !name.has_last?       || @last       == name.last) &&
      (!has_post_title? || !name.has_post_title? || @post_title == name.post_title)<commit_msg>Fixed regression in name matching introduced by adding initials support<commit_after>class String
  def surrounded_by_brackets?
    self[0..0] == '(' && self[-1..-1] == ')'
  end
end

# Handle all our silly name parsing needs
class Name
  attr_reader :title, :initials, :first, :nick, :middle, :last, :post_title
  
  def initialize(params)
    @title = params[:title] || ""
    @first = (Name.capitalize_name(params[:first]) if params[:first]) || ""
    @nick = (Name.capitalize_name(params[:nick]) if params[:nick]) || ""
    @middle = (Name.capitalize_each_name(params[:middle]) if params[:middle]) || ""
    @post_title = (params[:post_title].upcase if params[:post_title]) || ""
    @last = (Name.capitalize_each_name(params[:last]) if params[:last]) || ""
    if params[:initials]
      @initials = params[:initials].upcase
      if has_first? || has_middle?
        throw "Given initials #{params[:initials]} do not match given names" unless initials_from_names == @initials
      end
    else
      @initials = initials_from_names
    end
      
    throw "Invalid keys" unless (params.keys - [:title, :initials, :first, :nick, :middle, :last, :post_title]).empty?
  end
  
  # Initials purely obtained from the first and middle names
  def initials_from_names
    i = ""
    i = i + first[0..0] if has_first?
    i = i + middle.split(' ').map{|n| n[0..0]}.join if has_middle?
    i
  end
  
  def Name.last_title_first(text)
    names = text.delete(',').split(' ')
    # Check for a name in brackets which we take as the nickname
    nickname_text = names.find{|n| n.surrounded_by_brackets?}
    if nickname_text
      nick = nickname_text[1..-2]
      names.delete(nickname_text)
    end
    # Hack to deal with a specific person who has two last names that aren't hyphenated
    if names.size >= 2 && names[0].downcase == "stott" && names[1].downcase == "despoja"
      last = names[0..1].join(' ')
      names.shift
      names.shift
    else
      last = names.shift
    end
    title = Name.extract_title_at_start(names)
    first = names.shift
    throw "Too few names" if first.nil?
    post_titles = []
    while post_title = Name.post_title(names)
      post_titles.unshift(post_title)
    end
    post_title = post_titles.join(' ')
    middle = names[0..-1].join(' ')
    Name.new(:title => title, :last => last, :first => first, :nick => nick, :middle => middle, :post_title => post_title)
  end
  
  # Parse name in the form: "<NAME>"
  def Name.last_title_initials(text)
    names = text.delete(',').split(' ')
    last = names.shift
    title = Name.extract_title_at_start(names)
    initials = names.shift
    Name.new(:last => last, :title => title, :initials => initials)
  end
  
  # Extract a post title from the end if one is available
  def Name.post_title(names)
    if names.last == "AM" || names.last == "SC" || names.last == "AO" ||
      names.last == "MBE" || names.last == "QC" || names.last == "OBE" ||
      names.last == "KSJ" || names.last == "JP"
      names.pop
    end
  end
  
  def Name.title_first_last(text)
    names = text.delete(',').split(' ')
    title = Name.extract_title_at_start(names)
    throw "Too few names" if names.empty?
    if names.size == 1
      last = names[0]
    else
      first = names[0]
      last = names[-1]
      middle = names[1..-2].join(' ')
    end
    Name.new(:title => title, :last => last, :first => first, :middle => middle)
  end
  
  def informal_name
    throw "No last name" unless has_last?
    if @nick != ""
      "#{@nick} #{@last}"
    else
      if has_first?
        "#{@first} #{@last}"
      elsif has_initials?
        "#{@initials} #{@last}"
      else
        throw "No first name or initials"
      end
    end
  end
  
  def full_name
    t = ""
    t = t + "#{title} " if has_title?
    t = t + "#{first} " if has_first?
    t = t + "(#{nick}) " if has_nick?
    t = t + "#{middle} " if has_middle?
    t = t + "#{last}"
    t = t + ", #{post_title}" if has_post_title?
    t
  end
  
  def has_title?
    @title != ""
  end
  
  def has_initials?
    @initials != ""
  end
  
  def has_first?
    @first != ""
  end
  
  def has_nick?
    @nick != ""
  end
  
  def has_middle?
    @middle != ""
  end
  
  def has_last?
    @last != ""
  end
  
  def has_post_title?
    @post_title != ""
  end
  
  # Names don't have to be identical to match but rather the parts of the name
  # that exist in both names have to match
  def matches_simply?(name)
    # True if there is overlap between the names
    overlap = (has_title? && name.has_title?) ||
      (has_initials?   && name.has_initials?) ||
      (has_first?      && name.has_first?) ||
      (has_nick?       && name.has_nick?) ||
      (has_middle?     && name.has_middle?) ||
      (has_last?       && name.has_last?) ||
      (has_post_title? && name.has_post_title?)
      
    overlap &&
      (!has_title?      || !name.has_title?      || @title      == name.title) &&
      initials_matches?(name) &&
      (!has_first?      || !name.has_first?      || @first      == name.first) &&
      (!has_nick?       || !name.has_nick?       || @nick       == name.nick) &&
      (!has_middle?     || !name.has_middle?     || @middle     == name.middle) &&
      (!has_last?       || !name.has_last?       || @last       == name.last) &&
      (!has_post_title? || !name.has_post_title? || @post_title == name.post_title)
  end
  
  def first_initial
    if @initials.size > 0
      @initials[0..0]
    else
      ""
    end
  end
  
  def middle_initials
    if @initials.size > 1
      @initials[1..-1]
    else
      ""
    end  
  end
  
  def has_first_initial?
    first_initial != ""
  end
  
  def has_middle_initials?
    middle_initials != ""
  end
  
  def initials_matches?(name)
    (!has_first_initial?   || !name.has_first_initial? || first_initial == name.first_initial) &&
    (!has_middle_initials?   || !name.has_middle_initials? || middle_initials == name.middle_initials)
{'fraction_non_alphanumeric': 0.15237927076895913, 'fraction_numerical': 0.004590800741590889, 'mean_word_length': 2.5488721804511276, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5120502', 'n_tokens_mistral': 3944, 'n_tokens_neox': 3903, 'n_words': 960}