Class: Friends::Friend

Inherits:
Object
  • Object
show all
Extended by:
Serializable
Defined in:
lib/friends/friend.rb

Constant Summary collapse

SERIALIZATION_PREFIX =
"- "
NICKNAME_PREFIX =
"a.k.a. "

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Serializable

deserialize

Constructor Details

#initialize(name:, nickname_str: nil, location_name: nil, tags_str: nil) ⇒ Friend



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/friends/friend.rb', line 28

def initialize(
  name:,
  nickname_str: nil,
  location_name: nil,
  tags_str: nil
)
  @name = name
  @nicknames = nickname_str&.split(" #{NICKNAME_PREFIX}") || []
  @location_name = location_name
  @tags = tags_str&.split(/\s+/) || []
end

Instance Attribute Details

#likelihood_scoreObject



110
111
112
# File 'lib/friends/friend.rb', line 110

def likelihood_score
  defined?(@likelihood_score) ? @likelihood_score : 0
end

#location_nameObject

Returns the value of attribute location_name.



41
42
43
# File 'lib/friends/friend.rb', line 41

def location_name
  @location_name
end

#n_activitiesObject



100
101
102
# File 'lib/friends/friend.rb', line 100

def n_activities
  defined?(@n_activities) ? @n_activities : 0
end

#nameObject

Returns the value of attribute name.



40
41
42
# File 'lib/friends/friend.rb', line 40

def name
  @name
end

#tagsObject (readonly)

Returns the value of attribute tags.



42
43
44
# File 'lib/friends/friend.rb', line 42

def tags
  @tags
end

Class Method Details

.deserialization_expectationRegexp



23
24
25
# File 'lib/friends/friend.rb', line 23

def self.deserialization_expectation
  "[Friend Name]"
end

.deserialization_regexRegexp



17
18
19
20
# File 'lib/friends/friend.rb', line 17

def self.deserialization_regex
  # Note: this regex must be on one line because whitespace is important
  /(#{SERIALIZATION_PREFIX})?(?<name>[^\(\[@]*[^\(\[@\s])(\s+\(#{NICKNAME_PREFIX}(?<nickname_str>.+)\))?(\s+\[(?<location_name>[^\]]+)\])?(\s+(?<tags_str>(#{TAG_REGEX}\s*)+))?/ # rubocop:disable Layout/LineLength
end

Instance Method Details

#add_nickname(nickname) ⇒ Object

Adds a nickname, ignoring duplicates.



82
83
84
85
# File 'lib/friends/friend.rb', line 82

def add_nickname(nickname)
  @nicknames << nickname
  @nicknames.uniq!
end

#add_tag(tag) ⇒ Object

Adds a tag, ignoring duplicates.



68
69
70
71
# File 'lib/friends/friend.rb', line 68

def add_tag(tag)
  @tags << tag
  @tags.uniq!
end

#regexes_for_nameArray

NOTE: For now we only match on full names or first names.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/friends/friend.rb', line 120

def regexes_for_name
  # We generously allow any amount of whitespace between parts of a name.
  splitter = "\\s+"

  # Create the list of regexes and return it.
  chunks = name.split(Regexp.new(splitter))

  # We check nicknames before first names because nicknames may contain
  # first names, as in "Amazing Grace" being a nickname for Grace Hopper.
  [
    chunks, # Match a full name with the highest priority.
    *@nicknames.map { |n| [n] },

    # Match a first name followed by a last name initial, period (that via
    # lookahead is *NOT* a part of an ellipsis), and then (via lookahead)
    # either:
    # - other punctuation that would indicate we want to swallow the period
    #   (note that we do not include closing parentheses in this list because
    #   they could be part of an offset sentence), OR
    # - anything, so long as the first alphabetical character afterwards is
    #   lowercase.
    # This matches the "Jake E." part of something like "Jake E. and I went
    # skiing." or "Jake E., Marie Curie, and I studied science." This
    # allows us to correctly count the period as part of the name when it's
    # in the middle of a sentence.
    (
      if chunks.size > 1
        [chunks.first, "#{chunks.last[0]}\\.(?!\\.\\.)(?=([,!?;:—]+|(?-i)[^A-Z]+[a-z]))"]
      end
    ),

    # If the above doesn't match, we check for just the first name and then
    # a last name initial. This matches the "Jake E" part of something like
    # "I went skiing with Jake E." This allows us to correctly exclude the
    # period from the name when it's at the end of a sentence.
    ([chunks.first, chunks.last[0]] if chunks.size > 1),

    *(1..chunks.size - 1).map { |i| chunks.take(i) }.reverse
  ].compact.map do |words|
    Friends::RegexBuilder.regex(words.join(splitter))
  end
end

#remove_nickname(nickname) ⇒ Object

Raises:

  • (FriendsError)

    if the friend does not have the given nickname



89
90
91
92
93
94
95
# File 'lib/friends/friend.rb', line 89

def remove_nickname(nickname)
  unless @nicknames.include? nickname
    raise FriendsError, "Nickname \"#{nickname}\" not found for \"#{name}\""
  end

  @nicknames.delete(nickname)
end

#remove_tag(tag) ⇒ Object

Raises:



74
75
76
77
78
# File 'lib/friends/friend.rb', line 74

def remove_tag(tag)
  raise FriendsError, "Tag \"#{tag}\" not found for \"#{name}\"" unless @tags.include? tag

  @tags.delete(tag)
end

#serializeString



45
46
47
48
# File 'lib/friends/friend.rb', line 45

def serialize
  # Remove terminal effects for serialization.
  Paint.unpaint("#{SERIALIZATION_PREFIX}#{self}")
end

#to_sString



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/friends/friend.rb', line 51

def to_s
  unless @nicknames.empty?
    nickname_str = " (" +
                   @nicknames.map do |nickname|
                     "#{NICKNAME_PREFIX}#{Paint[nickname, :bold, :magenta]}"
                   end.join(" ") + ")"
  end

  location_str = " [#{Paint[@location_name, :bold, :yellow]}]" unless @location_name.nil?

  tag_str = " #{Paint[@tags.join(' '), :bold, :cyan]}" unless @tags.empty?

  "#{Paint[@name, :bold]}#{nickname_str}#{location_str}#{tag_str}"
end