friends 0.16 → 0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c928a672142f62f482efa128ae2d09e8bb724fd4
4
- data.tar.gz: 72d50a764d49a98b1d0e227817536a87274c9588
3
+ metadata.gz: 67aca011d40b8ec1f5f92dda50a441dff1363523
4
+ data.tar.gz: fdd5c7f7459f4bf28773d4e6c1ef3ab4d462f35e
5
5
  SHA512:
6
- metadata.gz: 1cfd5df61aa2f136dc49117a6e7bab7fd4b7c28b776885d564d59c4ffffe0a5c5a92ca04f7557b2a2a214dc6eaefbffc47e23003fd4d658a3d62eff03047c311
7
- data.tar.gz: dc9e3a6aeac267ca1ab720936d170cf921224a637b8a9ae96da2672acc5bfe74873810825d53138d24082303e1c9ec5057aeea1ae4110ad80be72ed4f9079567
6
+ metadata.gz: 4d05eff21075277e8bc244c69c4bf506c52b74955ef841d65a0fd23cd883d48af4f0d652ccde694ec453e4460355f542251c891f97ad6fe0986f49b08952e273
7
+ data.tar.gz: 8df26c543ce7c25dc6a1c1474914fd170ca973019b27834df33c5003b1900e07f5112a02c96117dd8b4eb7c17fb1f1c3af9e9c8a5e722ce2ee06b9ca883e2e36
@@ -1,5 +1,28 @@
1
1
  # Change Log
2
2
 
3
+ ## [v0.17](https://github.com/JacobEvelyn/friends/tree/v0.17) (2016-03-28)
4
+ [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.16...v0.17)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - Add --in flag to `suggest` [\#106](https://github.com/JacobEvelyn/friends/issues/106)
9
+ - Allow locations to be renamed [\#105](https://github.com/JacobEvelyn/friends/issues/105)
10
+ - Add --in location flag to `list activities` [\#100](https://github.com/JacobEvelyn/friends/issues/100)
11
+ - Add --in location flag to `list friends` [\#99](https://github.com/JacobEvelyn/friends/issues/99)
12
+ - Add location matching to activity descriptions [\#97](https://github.com/JacobEvelyn/friends/issues/97)
13
+ - Add `list locations` command [\#96](https://github.com/JacobEvelyn/friends/issues/96)
14
+ - Add `add location` command [\#95](https://github.com/JacobEvelyn/friends/issues/95)
15
+ - Add backwards-compatible \#\#\# Locations: heading to friends.md file. [\#94](https://github.com/JacobEvelyn/friends/issues/94)
16
+ - Update documentation for `graph` and `stats` commands [\#93](https://github.com/JacobEvelyn/friends/issues/93)
17
+ - Add location features [\#107](https://github.com/JacobEvelyn/friends/pull/107) ([JacobEvelyn](https://github.com/JacobEvelyn))
18
+ - Fix documentation formatting and typos [\#104](https://github.com/JacobEvelyn/friends/pull/104) ([andypearson](https://github.com/andypearson))
19
+ - Add backwards-compatible `add location` and `list locations` commands [\#101](https://github.com/JacobEvelyn/friends/pull/101) ([JacobEvelyn](https://github.com/JacobEvelyn))
20
+
21
+ **Merged pull requests:**
22
+
23
+ - Add location matching/highlighting to activities [\#103](https://github.com/JacobEvelyn/friends/pull/103) ([JacobEvelyn](https://github.com/JacobEvelyn))
24
+ - Add documentation for `graph` and `stats` commands [\#102](https://github.com/JacobEvelyn/friends/pull/102) ([JacobEvelyn](https://github.com/JacobEvelyn))
25
+
3
26
  ## [v0.16](https://github.com/JacobEvelyn/friends/tree/v0.16) (2016-03-23)
4
27
  [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.15...v0.16)
5
28
 
@@ -17,7 +17,8 @@ that's great as well!
17
17
  (`git checkout -b my-new-feature`).
18
18
  6. Make your changes. Add or modify tests if necessary!
19
19
  (Run tests with `rake test`.) Do your best to conform to
20
- existing style and commenting patterns.
20
+ existing style and commenting patterns. You can run the local version of the
21
+ `friends` script with `bundle exec bin/friends`.
21
22
  7. Update the `README.md` as necessary to include your changes.
22
23
  8. Commit your changes
23
24
  (`git commit -am "Add some feature"`). You should see
data/README.md CHANGED
@@ -87,11 +87,21 @@ And they can be removed as well:
87
87
  $ friends remove nickname "Grace Hopper" "The Admiral"
88
88
  Nickname removed: "Grace Hopper (a.k.a. Amazing Grace)"
89
89
  ```
90
+ ##### Set a friend's location:
91
+ ```
92
+ $ friends set location Marie Paris
93
+ Marie Curie's location set to: Paris
94
+ ```
90
95
  ##### Change a friend's name:
91
96
  ```
92
97
  $ friends rename friend "Grace Hopper" "Grace Brewster Murray Hopper"
93
98
  Name changed: "Grace Brewster Murray Hopper (a.k.a. Amazing Grace)"
94
99
  ```
100
+ ##### Change the name of a location:
101
+ ```
102
+ $ friends rename location Paris "Paris, France"
103
+ Location renamed: "Paris, France"
104
+ ```
95
105
  ##### Suggest a friend to do something with:
96
106
  ```
97
107
  $ friends suggest
@@ -99,11 +109,26 @@ Distant friend: Marie Curie
99
109
  Moderate friend: Grace Hopper
100
110
  Close friend: George Washington Carver
101
111
  ```
112
+ Or only suggest friends in a specific location:
113
+ ```
114
+ $ friends suggest --in Paris
115
+ Distant friend: Marie Curie
116
+ ```
117
+ ##### Add a location for your friends and activities:
118
+ ```
119
+ $ friends add location Atlantis
120
+ Location added: "Atlantis"
121
+ ```
122
+ ##### And locations will be matched as well:
123
+ ```
124
+ $ friends add activity "Went swimming near atlantis with George."
125
+ Activity added: "2016-01-06: Went swimming near Atlantis with George Washington Carver."
126
+ ```
102
127
  ##### List the activities you've recorded:
103
128
  ```
104
129
  $ friends list activities
105
130
  2015-01-04: Got lunch with Grace Hopper and George Washington Carver.
106
- 2014-12-31: Celebrated the new year with Marie Curie.
131
+ 2014-12-31: Celebrated the new year with Marie Curie in New York City.
107
132
  2014-11-15: Talked to George Washington Carver on the phone for an hour.
108
133
  ```
109
134
  Or only list the activities you did with a certain friend:
@@ -112,6 +137,11 @@ $ friends list activities --with "George"
112
137
  2015-01-04: Got lunch with Grace Hopper and George Washington Carver.
113
138
  2014-11-15: Talked to George Washington Carver on the phone for an hour.
114
139
 
140
+ ```
141
+ Or filter your activities by location:
142
+ ```
143
+ $ friends list activities --in "New York"
144
+ 2014-12-31: Celebrated the new year with Marie Curie in New York City.
115
145
  ```
116
146
  ##### Find your favorite friends:
117
147
  ```
@@ -130,12 +160,27 @@ Your favorite friends:
130
160
  ```
131
161
  ##### Graph (in color!) your relationship with a friend over time:
132
162
  ```
133
- $ friends graph "George"
163
+ $ friends graph George
134
164
  Nov 2014 |█
135
165
  Dec 2014 |
136
166
  Jan 2015 |█████
137
167
  Feb 2015 |███
138
168
  ```
169
+ ##### Or just graph all of your activities:
170
+ ```
171
+ $ friends graph
172
+ Nov 2014 |███
173
+ Dec 2014 |██
174
+ Jan 2015 |███████
175
+ Feb 2015 |█████
176
+ ```
177
+ ##### Or just check out your lifetime stats:
178
+ ```
179
+ $ friends stats
180
+ Total activities: 4
181
+ Total friends: 3
182
+ Total time elapsed: 5 days
183
+ ```
139
184
  ##### List all of your friends:
140
185
  ```
141
186
  $ friends list friends
@@ -143,6 +188,18 @@ George Washington Carver
143
188
  Grace Hopper
144
189
  Marie Curie
145
190
  ```
191
+ Or list only the friends in a specific location:
192
+ ```
193
+ $ friends list friends --in Paris
194
+ Marie Curie
195
+ ```
196
+ ##### List the locations you've added:
197
+ ```
198
+ $ friends list locations
199
+ Atlantis
200
+ New York City
201
+ Paris
202
+ ```
146
203
  ##### Update to the latest version:
147
204
  ```
148
205
  $ friends update
@@ -1,10 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Todo:
4
- # - Split out serialization into separate repository.
5
- # - Add auto check for updates.
6
- # - Allow easy editing of most recent entry.
7
-
8
3
  require "gli"
9
4
  require "paint"
10
5
  require "readline"
@@ -61,12 +56,16 @@ command :update do |update|
61
56
  end
62
57
  end
63
58
 
64
- desc "Lists friends or activities"
59
+ desc "Lists friends, activities, and locations"
65
60
  command :list do |list|
66
61
  list.desc "List all friends"
67
62
  list.command :friends do |list_friends|
68
- list_friends.action do
69
- puts @introvert.list_friends
63
+ list_friends.flag [:in],
64
+ arg_name: "LOCATION",
65
+ desc: "List only friends in the given location"
66
+
67
+ list_friends.action do |_, options|
68
+ puts @introvert.list_friends(location_name: options[:in])
70
69
  end
71
70
  end
72
71
 
@@ -105,14 +104,29 @@ command :list do |list|
105
104
  arg_name: "NAME",
106
105
  desc: "List only activities involving the given friend"
107
106
 
107
+ list_activities.flag [:in],
108
+ arg_name: "LOCATION",
109
+ desc: "List only activities in the given location"
110
+
108
111
  list_activities.action do |_, options|
109
112
  limit = options[:limit].to_i
110
- puts @introvert.list_activities(limit: limit, with: options[:with])
113
+ puts @introvert.list_activities(
114
+ limit: limit,
115
+ with: options[:with],
116
+ location_name: options[:in]
117
+ )
118
+ end
119
+ end
120
+
121
+ list.desc "List all locations"
122
+ list.command :locations do |list_locations|
123
+ list_locations.action do
124
+ puts @introvert.list_locations
111
125
  end
112
126
  end
113
127
  end
114
128
 
115
- desc "Adds a friend or activity"
129
+ desc "Adds a friend (or nickname), activity, or location"
116
130
  command :add do |add|
117
131
  add.desc "Adds a friend"
118
132
  add.arg_name "NAME"
@@ -133,7 +147,7 @@ command :add do |add|
133
147
  # If there's no description, prompt the user for one.
134
148
  if activity.description.nil? || activity.description.empty?
135
149
  activity.description = Readline.readline(activity.display_text)
136
- activity.highlight_friends(introvert: @introvert)
150
+ activity.highlight_description(introvert: @introvert)
137
151
  end
138
152
 
139
153
  @message = "Activity added: \"#{activity.display_text}\""
@@ -141,6 +155,16 @@ command :add do |add|
141
155
  end
142
156
  end
143
157
 
158
+ add.desc "Adds a location"
159
+ add.arg_name "LOCATION"
160
+ add.command :location do |add_location|
161
+ add_location.action do |_, _, args|
162
+ location = @introvert.add_location(name: args.first)
163
+ @message = "Location added: \"#{location.name}\""
164
+ @dirty = true # Mark the file for cleaning.
165
+ end
166
+ end
167
+
144
168
  add.desc "Adds a nickname to a friend"
145
169
  add.arg_name "NAME NICKNAME"
146
170
  add.command :nickname do |add_nickname|
@@ -152,6 +176,19 @@ command :add do |add|
152
176
  end
153
177
  end
154
178
 
179
+ desc "Set data about friends"
180
+ command :set do |set|
181
+ set.desc "Set a friend's location"
182
+ set.arg_name "NAME LOCATION"
183
+ set.command :location do |set_location|
184
+ set_location.action do |_, _, args|
185
+ friend = @introvert.set_location(name: args.first, location_name: args[1])
186
+ @message = "#{friend.name}'s location set to: #{friend.location_name}"
187
+ @dirty = true # Mark the file for cleaning.
188
+ end
189
+ end
190
+ end
191
+
155
192
  desc "Remove a nickname"
156
193
  command :remove do |remove|
157
194
  remove.desc "Removes a nickname from a friend"
@@ -165,8 +202,8 @@ command :remove do |remove|
165
202
  end
166
203
  end
167
204
 
168
- desc "Graph a friend's relationship over time"
169
- arg_name "NAME"
205
+ desc "Graph all activities or a friend's relationship over time"
206
+ arg_name "NAME (default: none)"
170
207
  command :graph do |graph|
171
208
  graph.action do |_, _, args|
172
209
  # This math is taken from Minitest's Pride plugin (the PrideLOL class).
@@ -192,8 +229,12 @@ end
192
229
 
193
230
  desc "Suggest friends to do activities with"
194
231
  command :suggest do |suggest|
195
- suggest.action do
196
- suggestions = @introvert.suggest
232
+ suggest.flag [:in],
233
+ arg_name: "LOCATION",
234
+ desc: "Suggest only friends in the given location"
235
+
236
+ suggest.action do |_, options|
237
+ suggestions = @introvert.suggest(location_name: options[:in])
197
238
 
198
239
  puts "Distant friend: "\
199
240
  "#{Paint[suggestions[:distant].sample || 'None found', :bold, :magenta]}"
@@ -222,18 +263,33 @@ command :stats do |stats|
222
263
  end
223
264
  end
224
265
 
225
- desc "Renames a friend"
266
+ desc "Renames a friend or location"
226
267
  command :rename do |rename|
227
268
  rename.desc "Renames a friend"
228
269
  rename.arg_name "NAME NEW_NAME"
229
270
  rename.command :friend do |rename_friend|
230
271
  rename_friend.action do |_, _, args|
231
- friend = @introvert.rename_friend(old_name: args.first,
232
- new_name: args[1])
272
+ friend = @introvert.rename_friend(
273
+ old_name: args.first,
274
+ new_name: args[1]
275
+ )
233
276
  @message = "Name changed: \"#{friend}\""
234
277
  @dirty = true # Mark the file for cleaning.
235
278
  end
236
279
  end
280
+
281
+ rename.desc "Renames a location"
282
+ rename.arg_name "NAME NEW_NAME"
283
+ rename.command :location do |rename_location|
284
+ rename_location.action do |_, _, args|
285
+ location = @introvert.rename_location(
286
+ old_name: args.first,
287
+ new_name: args[1]
288
+ )
289
+ @message = "Location renamed: \"#{location.name}\""
290
+ @dirty = true # Mark the file for cleaning.
291
+ end
292
+ end
237
293
  end
238
294
 
239
295
  # Before each command, clean up all arguments and create the global Introvert.
data/friends.md CHANGED
@@ -1,10 +1,15 @@
1
1
  ### Activities:
2
- - 2015-11-01: **Grace Hopper** and I went to Marie's Diner. George had to cancel at the last minute.
2
+ - 2015-11-01: **Grace Hopper** and I went to _Marie's Diner_. George had to cancel at the last minute.
3
3
  - 2015-01-04: Got lunch with **Grace Hopper** and **George Washington Carver**.
4
- - 2014-12-31: Celebrated the new year with **Marie Curie**.
4
+ - 2014-12-31: Celebrated the new year in _Paris_ with **Marie Curie**.
5
5
  - 2014-11-15: Talked to **George Washington Carver** on the phone for an hour.
6
6
 
7
7
  ### Friends:
8
8
  - George Washington Carver
9
- - Grace Hopper (a.k.a. The Admiral)
10
- - Marie Curie
9
+ - Grace Hopper (a.k.a. The Admiral) [Paris]
10
+ - Marie Curie [Atlantis]
11
+
12
+ ### Locations:
13
+ - Atlantis
14
+ - Marie's Diner
15
+ - Paris
@@ -34,7 +34,9 @@ module Friends
34
34
  # Partition lets us parse "Today" and "Today: I awoke." identically.
35
35
  date_s, _, description = str.partition(DATE_PARTITION)
36
36
 
37
+ # rubocop:disable Lint/AssignmentInCondition
37
38
  if time = Chronic.parse(date_s)
39
+ # rubocop:enable Lint/AssignmentInCondition
38
40
  @date = time.to_date
39
41
  @description = description
40
42
  else
@@ -52,10 +54,18 @@ module Friends
52
54
  date_s = Paint[date, :bold]
53
55
  description_s = description.to_s
54
56
  # rubocop:disable Lint/AssignmentInCondition
55
- while match = description_s.match(/(\*\*)([^\*]+)(\*\*)/)
57
+ while match = description_s.match(/\*\*([^\*]+)\*\*/)
56
58
  # rubocop:enable Lint/AssignmentInCondition
57
59
  description_s = "#{match.pre_match}"\
58
- "#{Paint[match[2], :bold, :magenta]}"\
60
+ "#{Paint[match[1], :bold, :magenta]}"\
61
+ "#{match.post_match}"
62
+ end
63
+
64
+ # rubocop:disable Lint/AssignmentInCondition
65
+ while match = description_s.match(/_([^_]+)_/)
66
+ # rubocop:enable Lint/AssignmentInCondition
67
+ description_s = "#{match.pre_match}"\
68
+ "#{Paint[match[1], :bold, :yellow]}"\
59
69
  "#{match.post_match}"
60
70
  end
61
71
  "#{date_s}: #{description_s}"
@@ -66,11 +76,78 @@ module Friends
66
76
  "#{SERIALIZATION_PREFIX}#{date}: #{description}"
67
77
  end
68
78
 
79
+ # Modify the description to turn inputted friend names
80
+ # (e.g. "Jacob" or "Jacob Evelyn") into full asterisk'd names
81
+ # (e.g. "**Jacob Evelyn**") and inputted location names (e.g. "Atlantis")
82
+ # into full underscore'd names (e.g. "_Atlantis_").
83
+ # @param introvert [Introvert] used to access internal data structures to
84
+ # perform object matching
85
+ def highlight_description(introvert:)
86
+ highlight_locations(introvert: introvert)
87
+ highlight_friends(introvert: introvert)
88
+ end
89
+
90
+ # Updates a friend's old_name to their new_name
91
+ # @param [String] old_name
92
+ # @param [String] new_name
93
+ # @return [String] if name found in description
94
+ # @return [nil] if no change was made
95
+ def update_friend_name(old_name:, new_name:)
96
+ @description.gsub!(
97
+ Regexp.new("(?<=\\*\\*)#{old_name}(?=\\*\\*)"),
98
+ new_name
99
+ )
100
+ end
101
+
102
+ # Updates a location's old_name to their new_name
103
+ # @param [String] old_name
104
+ # @param [String] new_name
105
+ # @return [String] if name found in description
106
+ # @return [nil] if no change was made
107
+ def update_location_name(old_name:, new_name:)
108
+ @description.gsub!(Regexp.new("(?<=_)#{old_name}(?=_)"), new_name)
109
+ end
110
+
111
+ # @param location [Location] the location to test
112
+ # @return [Boolean] true iff this activity includes the given location
113
+ def includes_location?(location:)
114
+ @description.scan(/(?<=_)[^_]+(?=_)/).include? location.name
115
+ end
116
+
117
+ # @param friend [Friend] the friend to test
118
+ # @return [Boolean] true iff this activity includes the given friend
119
+ def includes_friend?(friend:)
120
+ friend_names.include? friend.name
121
+ end
122
+
123
+ # Find the names of all friends in this description.
124
+ # @return [Array] list of all friend names in the description
125
+ def friend_names
126
+ @description.scan(/(?<=\*\*)\w[^\*]*(?=\*\*)/).uniq
127
+ end
128
+ memoize :friend_names
129
+
130
+ private
131
+
132
+ # Modify the description to turn inputted location names (e.g. "Atlantis")
133
+ # into full underscore'd names (e.g. "_Atlantis_").
134
+ # @param introvert [Introvert] used to access internal data structures to
135
+ # perform location matching
136
+ def highlight_locations(introvert:)
137
+ introvert.regex_location_map.each do |regex, location|
138
+ # If we find a match, replace all instances of the matching text with
139
+ # the location's name. We use single-underscores to indicate locations.
140
+ description_matches(regex: regex, replace: true, indicator: "_") do
141
+ location.name
142
+ end
143
+ end
144
+ end
145
+
69
146
  # Modify the description to turn inputted friend names
70
147
  # (e.g. "Jacob" or "Jacob Evelyn") into full asterisk'd names
71
148
  # (e.g. "**Jacob Evelyn**")
72
- # @param introvert [Introvert] used to access the list of friends and the
73
- # connections between the
149
+ # @param introvert [Introvert] used to access internal data structures to
150
+ # perform friend matching
74
151
  # NOTE: When a friend name matches more than one friend, this method chooses
75
152
  # a friend based on a best-guess algorithm that looks at which friends do
76
153
  # activities together and which friends are stronger than others. For
@@ -89,7 +166,7 @@ module Friends
89
166
  definite_map.each do |regex, friend_list|
90
167
  # If we find a match, add the friend to the matched list and replace all
91
168
  # instances of the matching text with the friend's name.
92
- description_matches(regex: regex, replace: true) do
169
+ description_matches(regex: regex, replace: true, indicator: "**") do
93
170
  friend = friend_list.first # There's only one friend in the list.
94
171
  matched_friends << friend
95
172
  friend.name
@@ -101,7 +178,7 @@ module Friends
101
178
  # Now, we look at regex matches that are ambiguous.
102
179
  ambiguous_map.each do |regex, friend_list|
103
180
  # If we find a match, add the friend to the possible-match list.
104
- description_matches(regex: regex, replace: false) do
181
+ description_matches(regex: regex, replace: false, indicator: "**") do
105
182
  possible_matched_friends << friend_list
106
183
  end
107
184
  end
@@ -117,7 +194,7 @@ module Friends
117
194
  ambiguous_map.each do |regex, friend_list|
118
195
  # If we find a match, take the most likely and replace all instances of
119
196
  # the matching text with that friend's name.
120
- description_matches(regex: regex, replace: true) do
197
+ description_matches(regex: regex, replace: true, indicator: "**") do
121
198
  friend_list.sort_by do |friend|
122
199
  [-friend.likelihood_score, -friend.n_activities]
123
200
  end.first.name
@@ -129,45 +206,19 @@ module Friends
129
206
  @description = @description.delete("\\")
130
207
  end
131
208
 
132
- # Updates a friend's old_name to their new_name
133
- # @param [String] old_name
134
- # @param [String] new_name
135
- # @return [String] if name found in description
136
- # @return [nil] if no change was made
137
- def update_name(old_name:, new_name:)
138
- description.gsub!(
139
- Regexp.new("(?<=\\*\\*)#{old_name}(?=\\*\\*)"),
140
- new_name)
141
- end
142
-
143
- # @param friend [Friend] the friend to test
144
- # @return [Boolean] true iff this activity includes the given friend
145
- def includes_friend?(friend:)
146
- friend_names.include? friend.name
147
- end
148
-
149
- # Find the names of all friends in this description.
150
- # @return [Array] list of all friend names in the description
151
- def friend_names
152
- description.scan(/(?<=\*\*)\w[^\*]*(?=\*\*)/).uniq
153
- end
154
- memoize :friend_names
155
-
156
- private
157
-
158
209
  # This method accepts a block, and tests a regex on the @description
159
210
  # instance variable.
160
211
  # - If the regex does not match, the block is not executed.
161
212
  # - If the regex matches, the block is executed exactly once, and:
162
213
  # - If `replace` is true, all of the regex's matches are replaced by the
163
214
  # return value of the block, EXCEPT when the matched text is between a
164
- # set of double-asterisks ("**") indicating it is already part of
165
- # another friend's matched name.
215
+ # set of double-asterisks ("**") or single-underscores ("_") indicating
216
+ # it is already part of another location or friend's matched name.
166
217
  # - If `replace` is not true, we do not modify @description.
167
218
  # @param regex [Regexp] the regex to test against @description
168
219
  # @param replace [Boolean] true iff we should replace regex matches with the
169
220
  # yielded block's result in @description
170
- def description_matches(regex:, replace:)
221
+ def description_matches(regex:, replace:, indicator:)
171
222
  # rubocop:disable Lint/AssignmentInCondition
172
223
  return unless match = @description.match(regex) # Abort if no match.
173
224
  # rubocop:enable Lint/AssignmentInCondition
@@ -176,14 +227,22 @@ module Friends
176
227
 
177
228
  position = 0 # Prevent infinite looping by tracking last match position.
178
229
  loop do
179
- # Only make a replacement if we're not between a set of "**"s.
230
+ # Only make a replacement if we're not between a set of "**"s or "_"s.
180
231
  if match.pre_match.scan("**").size % 2 == 0 &&
181
- match.post_match.scan("**").size % 2 == 0
182
- @description = "#{match.pre_match}**#{str}**#{match.post_match}"
232
+ match.post_match.scan("**").size % 2 == 0 &&
233
+ match.pre_match.scan("_").size % 2 == 0 &&
234
+ match.post_match.scan("_").size % 2 == 0
235
+ @description = [
236
+ match.pre_match,
237
+ indicator,
238
+ str,
239
+ indicator,
240
+ match.post_match
241
+ ].join
183
242
  else
184
- # If we're between double-asterisks we're already part of a name, so
185
- # we don't make a substitution. We update `position` to avoid infinite
186
- # looping.
243
+ # If we're between double-asterisks or single-underscores we're
244
+ # already part of a name, so we don't make a substitution. We update
245
+ # `position` to avoid infinite looping.
187
246
  position = match.end(0)
188
247
  end
189
248