friends 0.16 → 0.17

Sign up to get free protection for your applications and to get access to all the features.
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