friends 0.34 → 0.35

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: c8288b02c441d46ce2481d4a39f8ea457519a6e7
4
- data.tar.gz: 551632c785ec6d2447adf65d56ab4fa0aac16a49
3
+ metadata.gz: 850118ba419db4de89e23cd279ea57d0e9c7305b
4
+ data.tar.gz: a4440dc2489f1756eaaa22ff395909d85abbf471
5
5
  SHA512:
6
- metadata.gz: 9fc27186b237b3a244d88e5c85cc019bfce8ae51a1b74becae9c103d33fa777429c247ae3070b3da288104299856475ab1e67e376a5b4bdb719f62f9550f6b8d
7
- data.tar.gz: 3206d0662d64b42f8c8ce5e2fb5bc8ad9a95dda27b4d07541798fc27a177f0d7d5c343bfbf540357f891a6bf954e2b09acf38aa8a09d71988b84d480a92d8944
6
+ metadata.gz: 643172f6f2be216b21bbc47e43fb17ee712c3d2e9ba092cff0cec4657e29391739712c9d3bb6b6d6811fabf8a77626c91a43cba7bb7e86a1e33b3d6326183ffb
7
+ data.tar.gz: a64b0fc7a57e929af581457a3eeb2be21184dd60da9056ad91aa72068274fa602d13613f1faae2b86125bfd8c1e43d0d536bd3d35a1232873b32b76d215fd631
@@ -1,5 +1,26 @@
1
1
  # Change Log
2
2
 
3
+ `friends` is a volunteer project. If you find it useful, please consider
4
+ making a small donation to show me you appreciate its continued development.
5
+
6
+ [![Donate via OpenCollective](https://opencollective.com/friends/contributors/badge.svg)](https://opencollective.com/friends)
7
+ [![Support via Patreon](https://img.shields.io/badge/support-Patreon-green.svg)](https://www.patreon.com/jacobevelyn)
8
+ [![Donate via Liberapay](https://img.shields.io/badge/donate-Liberapay-green.svg)](https://liberapay.com/jacobevelyn/donate)
9
+ [![Donate via PayPal](https://img.shields.io/badge/donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=jacobevelyn%40gmail%2ecom&lc=US&item_name=Development%20of%20JacobEvelyn%2ffriends%20%28GitHub%20repository%29&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest)
10
+ [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=jacobevelyn&url=https://github.com/JacobEvelyn/friends&title=friends&tags=github&category=software)
11
+ [![Donate bitcoin](https://img.shields.io/badge/donate-Bitcoin-green.svg)](https://nrobinson2000.github.io/donate-bitcoin?address=1CFu6gWpmS89EnitPPdYssZhFMRWx5qhW4&amount=10&name=support-friends-development)
12
+
13
+ ## [v0.35](https://github.com/JacobEvelyn/friends/tree/v0.35) (2018-01-14)
14
+ [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.34...v0.35)
15
+
16
+ **Implemented enhancements:**
17
+
18
+ - Add notes [\#175](https://github.com/JacobEvelyn/friends/issues/175)
19
+
20
+ **Merged pull requests:**
21
+
22
+ - Add ability to add notes [\#187](https://github.com/JacobEvelyn/friends/pull/187) ([JacobEvelyn](https://github.com/JacobEvelyn))
23
+
3
24
  ## [v0.34](https://github.com/JacobEvelyn/friends/tree/v0.34) (2018-01-10)
4
25
  [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.33...v0.34)
5
26
 
data/README.md CHANGED
@@ -10,11 +10,11 @@
10
10
  making a small donation to show me you appreciate its continued development.
11
11
 
12
12
  [![Donate via OpenCollective](https://opencollective.com/friends/contributors/badge.svg)](https://opencollective.com/friends)
13
- [![Support via Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://www.patreon.com/jacobevelyn)
14
- [![Donate via Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/jacobevelyn/donate)
15
- [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=jacobevelyn%40gmail%2ecom&lc=US&item_name=Development%20of%20JacobEvelyn%2ffriends%20%28GitHub%20repository%29&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest)
13
+ [![Support via Patreon](https://img.shields.io/badge/support-Patreon-green.svg)](https://www.patreon.com/jacobevelyn)
14
+ [![Donate via Liberapay](https://img.shields.io/badge/donate-Liberapay-green.svg)](https://liberapay.com/jacobevelyn/donate)
15
+ [![Donate via PayPal](https://img.shields.io/badge/donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=jacobevelyn%40gmail%2ecom&lc=US&item_name=Development%20of%20JacobEvelyn%2ffriends%20%28GitHub%20repository%29&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest)
16
16
  [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=jacobevelyn&url=https://github.com/JacobEvelyn/friends&title=friends&tags=github&category=software)
17
- [![Donate bitcoin](https://img.shields.io/badge/donate-bitcoin-green.svg)](https://nrobinson2000.github.io/donate-bitcoin?address=1CFu6gWpmS89EnitPPdYssZhFMRWx5qhW4&amount=10&name=support-friends-development)
17
+ [![Donate bitcoin](https://img.shields.io/badge/donate-Bitcoin-green.svg)](https://nrobinson2000.github.io/donate-bitcoin?address=1CFu6gWpmS89EnitPPdYssZhFMRWx5qhW4&amount=10&name=support-friends-development)
18
18
 
19
19
  # `friends`
20
20
 
@@ -37,6 +37,7 @@ lots of help), and give feedback!**
37
37
  * [Command reference](#command-reference)
38
38
  * `add`
39
39
  * [`add activity`](#add-activity)
40
+ * [`add note`](#add-note)
40
41
  * [`add friend`](#add-friend)
41
42
  * [`add tag`](#add-tag)
42
43
  * [`add location`](#add-location)
@@ -46,6 +47,7 @@ lots of help), and give feedback!**
46
47
  * [`help`](#help)
47
48
  * `list`
48
49
  * [`list activities`](#list-activities)
50
+ * [`list notes`](#list-notes)
49
51
  * `list favorite`
50
52
  * [`list favorite friends`](#list-favorite-friends)
51
53
  * [`list favorite locations`](#list-favorite-locations)
@@ -116,6 +118,8 @@ Easy, huh?
116
118
  `Marie's Diner`)
117
119
  * **Tags**: A way to categorize your _activities_ with tags of your
118
120
  choosing. (Examples: `@exercise`, `@school`)
121
+ * **Notes**: Any additional information you want to record about a _friend_
122
+ or _location_. (Example: `John and Jane got engaged.`)
119
123
 
120
124
  The `friends.md` Markdown file that stores all of your data contains:
121
125
 
@@ -123,31 +127,36 @@ The `friends.md` Markdown file that stores all of your data contains:
123
127
 
124
128
  ```markdown
125
129
  ### Locations:
126
-
127
- * Atlantis
128
- * Marie's Diner
129
- * Paris
130
+ - Atlantis
131
+ - Marie's Diner
132
+ - Paris
130
133
  ```
131
134
 
132
135
  * an alphabetical list of all friends and their nicknames and locations:
133
136
 
134
137
  ```markdown
135
138
  ### Friends:
136
-
137
- * George Washington Carver
138
- * Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris]
139
- * Marie Curie [Atlantis]
139
+ - George Washington Carver
140
+ - Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris]
141
+ - Marie Curie [Atlantis]
140
142
  ```
141
143
 
142
- * and an ordered list of all activities:
144
+ * an ordered list of all activities:
143
145
 
144
146
  ```markdown
145
147
  ### Activities:
148
+ - 2018-11-01: **Grace Hopper** and I went to _Marie's Diner_. George had to cancel at the last minute.
149
+ - 2018-01-04: Got lunch with **Grace Hopper** and **George Washington Carver**.
150
+ - 2017-12-31: Celebrated the new year in _Paris_ with **Marie Curie**.
151
+ - 2017-11-15: Talked to **George Washington Carver** on the phone for an hour.
152
+ ```
146
153
 
147
- * 2018-11-01: **Grace Hopper** and I went to _Marie's Diner_. George had to cancel at the last minute.
148
- * 2018-01-04: Got lunch with **Grace Hopper** and **George Washington Carver**.
149
- * 2017-12-31: Celebrated the new year in _Paris_ with **Marie Curie**.
150
- * 2017-11-15: Talked to **George Washington Carver** on the phone for an hour.
154
+ * and an ordered list of all notes:
155
+
156
+ ```markdown
157
+ ### Notes:
158
+ - 2018-06-15: **Grace Hopper** found out she's getting a big Naval Academy building named after her. @navy
159
+ - 2017-06-06: **Marie Curie** just got accepted into a PhD program in _Paris_. @school
151
160
  ```
152
161
 
153
162
  See the example
@@ -163,7 +172,7 @@ These flags are:
163
172
 
164
173
  * `--colorless`: Disable output colorization and other effects.
165
174
  * `--debug`: Debug error messages with a full backtrace.
166
- * `--filename`: Set the location of the friends file to use (default: ./friends.md).
175
+ * `--filename`: Set the location of the friends file to use (default: `./friends.md`).
167
176
 
168
177
  ```bash
169
178
  $ friends --filename ./test/tmp/friends.md clean
@@ -304,6 +313,24 @@ $ friends add activity "2018-11-01: Grace and I went to \Marie's Diner. \George
304
313
  Activity added: "2018-11-01: Grace Hopper and I went to Marie's Diner. George had to cancel at the last minute."
305
314
  ```
306
315
 
316
+ #### `add note`
317
+
318
+ Notes can be added exactly like activities, either on one line:
319
+
320
+ ```bash
321
+ $ friends add note Yesterday: Marie got accepted into a PhD program
322
+ Note added: "2017-12-31: Marie Curie got accepted into a PhD program"
323
+ ```
324
+
325
+ Or with a prompt:
326
+
327
+ ```bash
328
+ $ friends add note last Monday
329
+ 2017-03-07: <type description here>
330
+ ```
331
+
332
+ And just like with `add activity`, dates, friends, locations, nicknames, and tags will all be intelligently matched.
333
+
307
334
  #### `add friend`
308
335
 
309
336
  ```bash
@@ -559,6 +586,16 @@ $ friends list activities --tagged food --with Grace --with George
559
586
  2018-01-04: Got lunch with Grace Hopper and George Washington Carver. @food
560
587
  ```
561
588
 
589
+ #### `list notes`
590
+
591
+ You can list notes the same way you list activities:
592
+
593
+ ```bash
594
+ $ friends list notes --tagged school --with Marie
595
+ 2017-03-12: Marie Curie completed her PhD in record time. @school
596
+ 2015-06-06: Marie Curie just got accepted into a PhD program in Paris. @school
597
+ ```
598
+
562
599
  #### `list favorite friends`
563
600
 
564
601
  Lists your "favorite" friends (by total number of activities):
@@ -672,6 +709,25 @@ $ friends list tags --from friends
672
709
  @swanky
673
710
  ```
674
711
 
712
+ Or only tags from notes:
713
+
714
+ ```bash
715
+ $ friends list tags --from notes
716
+ @navy
717
+ @school
718
+ ```
719
+
720
+ Or only tags from two out of three:
721
+
722
+ ```bash
723
+ $ friends list tags --from activities --from friends
724
+ @dancing
725
+ @food
726
+ @navy
727
+ @school
728
+ @swanky
729
+ ```
730
+
675
731
  #### `list locations`
676
732
 
677
733
  Lists all of the locations you've added, in alphabetical order::
@@ -732,7 +788,10 @@ Gives you your lifetime usage stats:
732
788
  $ friends stats
733
789
  Total activities: 4
734
790
  Total friends: 3
735
- Total time elapsed: 5 days
791
+ Total locations: 3
792
+ Total notes: 4
793
+ Total tags: 5
794
+ Total time elapsed: 848 days
736
795
  ```
737
796
 
738
797
  #### `suggest`
@@ -780,7 +839,7 @@ A big big thanks to all of this project's lovely
780
839
  [contributors](https://github.com/JacobEvelyn/friends/graphs/contributors)!
781
840
 
782
841
  Another way to contribute is to make a donation (see the buttons at the top
783
- of this `README`!
842
+ of this `README`)!
784
843
 
785
844
  ## Code of Conduct
786
845
 
@@ -3,13 +3,14 @@
3
3
  These are steps for the maintainer to take to release a new version of this gem.
4
4
 
5
5
  1. On the `master` branch, update the `VERSION` constant in
6
- `lib/friends/version.rb`.
6
+ `lib/friends/version.rb`.
7
7
  2. Commit the change (`git add -A && git commit -m 'Bump to vX.X'`).
8
8
  3. Add a tag (`git tag -am "vX.X" vX.X`).
9
9
  4. `git push && git push --tags`
10
10
  5. `CHANGELOG_GITHUB_TOKEN=... github_changelog_generator`
11
- 6. Confirm the `CHANGELOG` looks good with `git diff`
12
- 7. `git add -A && git commit -m 'Update CHANGELOG for vX.X'`
13
- 8. `git push`
14
- 9. `gem build friends.gemspec && gem push *.gem && rm *.gem`
15
- 10. Celebrate!
11
+ 6. Confirm the `CHANGELOG` looks correct with `git diff`
12
+ 7. Copy-paste the donation info from the `README` to the `CHANGELOG`.
13
+ 8. `git add -A && git commit -m 'Update CHANGELOG for vX.X'`
14
+ 9. `git push`
15
+ 10. `gem build friends.gemspec && gem push *.gem && rm *.gem`
16
+ 11. Celebrate!
data/friends.md CHANGED
@@ -4,6 +4,10 @@
4
4
  - 2017-12-31: Celebrated the new year in _Paris_ with **Marie Curie**. @partying
5
5
  - 2017-11-15: Talked to **George Washington Carver** on the phone for an hour.
6
6
 
7
+ ### Notes:
8
+ - 2018-06-15: **Grace Hopper** found out she's getting a big Naval Academy building named after her. @navy
9
+ - 2017-06-06: **Marie Curie** just got accepted into a PhD program in _Paris_. @school
10
+
7
11
  ### Friends:
8
12
  - George Washington Carver
9
13
  - Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris] @navy @science
@@ -1,302 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Activity represents an activity you've done with one or more Friends.
3
+ # Activity represents an activity you've done, usually
4
+ # with one or more friends.
4
5
 
5
- require "chronic"
6
- require "paint"
7
- require "set"
8
-
9
- require "friends/serializable"
6
+ require "friends/event"
10
7
 
11
8
  module Friends
12
- class Activity
13
- extend Serializable
14
-
15
- SERIALIZATION_PREFIX = "- ".freeze
16
- DATE_PARTITION = ": ".freeze
17
-
18
- # @return [Regexp] the regex for capturing groups in deserialization
19
- def self.deserialization_regex
20
- /(#{SERIALIZATION_PREFIX})?(?<str>.+)?/
21
- end
22
-
9
+ class Activity < Event
23
10
  # @return [Regexp] the string of what we expected during deserialization
24
11
  def self.deserialization_expectation
25
12
  "[YYYY-MM-DD]: [Activity]"
26
13
  end
27
-
28
- # @param str [String] the text of the activity, of one of the formats:
29
- # "<date>: <description>"
30
- # "<date>" (Program will prompt for description.)
31
- # "<description>" (The current date will be used by default.)
32
- # @return [Activity] the new activity
33
- def initialize(str: "")
34
- # Partition lets us parse "Today" and "Today: I awoke." identically.
35
- date_s, _, description = str.partition(DATE_PARTITION)
36
-
37
- time = if date_s =~ /^\d{4}-\d{2}-\d{2}$/
38
- Time.parse(date_s)
39
- else
40
- # If the user inputed a non YYYY-MM-DD format, asssume
41
- # it is in the past.
42
- past_time = Chronic.parse(date_s, context: :past)
43
-
44
- # If there's no year, Chronic will sometimes parse the date
45
- # as being the next occurrence of that date in the future.
46
- # Instead, we want to subtract one year to make it the last
47
- # occurrence of the date in the past.
48
- # NOTE: This is a hacky workaround for the fact that
49
- # Chronic's `context: :past` doesn't actually work. We should
50
- # remove this when that behavior is fixed.
51
- if past_time && past_time > Time.now
52
- Time.local(past_time.year - 1, past_time.month, past_time.day)
53
- else
54
- past_time
55
- end
56
- end
57
-
58
- if time
59
- @date = time.to_date
60
- @description = description
61
- else
62
- # If the user didn't input a date, we fall back to the current date.
63
- @date = Date.today
64
- @description = str # Use str in case DATE_PARTITION occurred naturally.
65
- end
66
- end
67
-
68
- attr_reader :date
69
- attr_accessor :description
70
-
71
- # @return [String] the command-line display text for the activity
72
- def to_s
73
- date_s = Paint[date, :bold]
74
- description_s = description.to_s
75
- # rubocop:disable Lint/AssignmentInCondition
76
- while match = description_s.match(/\*\*([^\*]+)\*\*/)
77
- # rubocop:enable Lint/AssignmentInCondition
78
- description_s = "#{match.pre_match}"\
79
- "#{Paint[match[1], :bold, :magenta]}"\
80
- "#{match.post_match}"
81
- end
82
-
83
- # rubocop:disable Lint/AssignmentInCondition
84
- while match = description_s.match(/_([^_]+)_/)
85
- # rubocop:enable Lint/AssignmentInCondition
86
- description_s = "#{match.pre_match}"\
87
- "#{Paint[match[1], :bold, :yellow]}"\
88
- "#{match.post_match}"
89
- end
90
-
91
- description_s = description_s.
92
- gsub(TAG_REGEX, Paint['\0', :bold, :cyan])
93
-
94
- "#{date_s}: #{description_s}"
95
- end
96
-
97
- # @return [String] the file serialization text for the activity
98
- def serialize
99
- "#{SERIALIZATION_PREFIX}#{date}: #{description}"
100
- end
101
-
102
- # Modify the description to turn inputted friend names
103
- # (e.g. "Jacob" or "Jacob Evelyn") into full asterisk'd names
104
- # (e.g. "**Jacob Evelyn**") and inputted location names (e.g. "Atlantis")
105
- # into full underscore'd names (e.g. "_Atlantis_").
106
- # @param introvert [Introvert] used to access internal data structures to
107
- # perform object matching
108
- def highlight_description(introvert:)
109
- highlight_locations(introvert: introvert)
110
- highlight_friends(introvert: introvert)
111
- end
112
-
113
- # Updates a friend's old_name to their new_name
114
- # @param [String] old_name
115
- # @param [String] new_name
116
- # @return [String] if name found in description
117
- # @return [nil] if no change was made
118
- def update_friend_name(old_name:, new_name:)
119
- @description = @description.gsub(
120
- Regexp.new("(?<=\\*\\*)#{old_name}(?=\\*\\*)"),
121
- new_name
122
- )
123
- end
124
-
125
- # Updates a location's old_name to their new_name
126
- # @param [String] old_name
127
- # @param [String] new_name
128
- # @return [String] if name found in description
129
- # @return [nil] if no change was made
130
- def update_location_name(old_name:, new_name:)
131
- @description = @description.gsub(
132
- Regexp.new("(?<=_)#{old_name}(?=_)"),
133
- new_name
134
- )
135
- end
136
-
137
- # @param location [Location] the location to test
138
- # @return [Boolean] true iff this activity includes the given location
139
- def includes_location?(location)
140
- @description.scan(/(?<=_)[^_]+(?=_)/).include? location.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
- # @param tag [String] the tag to test, of the form "@tag"
150
- # @return [Boolean] true iff this activity includes the given tag
151
- def includes_tag?(tag)
152
- tags.include? tag
153
- end
154
-
155
- # @return [Set] all tags in this activity (including the "@")
156
- def tags
157
- Set.new(@description.scan(TAG_REGEX))
158
- end
159
-
160
- # Find the names of all friends in this description.
161
- # @return [Array] list of all friend names in the description
162
- def friend_names
163
- @_friend_names ||= @description.scan(/(?<=\*\*)\w[^\*]*(?=\*\*)/).uniq
164
- end
165
-
166
- # Find the names of all locations in this description.
167
- # @return [Array] list of all location names in the description
168
- def location_names
169
- @_location_names ||= @description.scan(/(?<=_)\w[^_]*(?=_)/).uniq
170
- end
171
-
172
- private
173
-
174
- # Modify the description to turn inputted location names (e.g. "Atlantis")
175
- # into full underscore'd names (e.g. "_Atlantis_").
176
- # @param introvert [Introvert] used to access internal data structures to
177
- # perform location matching
178
- def highlight_locations(introvert:)
179
- introvert.regex_location_map.each do |regex, location|
180
- # If we find a match, replace all instances of the matching text with
181
- # the location's name. We use single-underscores to indicate locations.
182
- description_matches(regex: regex, replace: true, indicator: "_") do
183
- location.name
184
- end
185
- end
186
- end
187
-
188
- # Modify the description to turn inputted friend names
189
- # (e.g. "Jacob" or "Jacob Evelyn") into full asterisk'd names
190
- # (e.g. "**Jacob Evelyn**")
191
- # @param introvert [Introvert] used to access internal data structures to
192
- # perform friend matching
193
- # NOTE: When a friend name matches more than one friend, this method chooses
194
- # a friend based on a best-guess algorithm that looks at which friends do
195
- # activities together and which friends are stronger than others. For
196
- # more information see the comments below and the
197
- # introvert#set_likelihood_score! method.
198
- def highlight_friends(introvert:)
199
- # Split the regex friend map into two maps: one for names with only one
200
- # friend match and another for ambiguous names
201
- definite_map, ambiguous_map =
202
- introvert.regex_friend_map.partition { |_, arr| arr.size == 1 }
203
-
204
- matched_friends = []
205
-
206
- # First, we find all of the unambiguous matches, and make those
207
- # substitutions.
208
- definite_map.each do |regex, friend_list|
209
- # If we find a match, add the friend to the matched list and replace all
210
- # instances of the matching text with the friend's name.
211
- description_matches(regex: regex, replace: true, indicator: "**") do
212
- friend = friend_list.first # There's only one friend in the list.
213
- matched_friends << friend
214
- friend.name
215
- end
216
- end
217
-
218
- possible_matched_friends = []
219
-
220
- # Now, we look at regex matches that are ambiguous.
221
- ambiguous_map.each do |regex, friend_list|
222
- # If we find a match, add the friend to the possible-match list.
223
- description_matches(regex: regex, replace: false, indicator: "**") do
224
- possible_matched_friends << friend_list
225
- end
226
- end
227
-
228
- # Now, we compute the likelihood of each friend in the possible-match set.
229
- introvert.set_likelihood_score!(
230
- matches: matched_friends,
231
- possible_matches: possible_matched_friends
232
- )
233
-
234
- # Now we replace all of the ambiguous matches with our best guesses.
235
- ambiguous_map.each do |regex, friend_list|
236
- # If we find a match, take the most likely and replace all instances of
237
- # the matching text with that friend's name.
238
- description_matches(regex: regex, replace: true, indicator: "**") do
239
- friend_list.sort_by do |friend|
240
- [-friend.likelihood_score, -friend.n_activities]
241
- end.first.name
242
- end
243
- end
244
-
245
- # Lastly, we remove any backslashes, as these are used to escape friends'
246
- # names that we don't want to match.
247
- @description = @description.delete("\\")
248
- end
249
-
250
- # This method accepts a block, and tests a regex on the @description
251
- # instance variable.
252
- # - If the regex does not match, the block is not executed.
253
- # - If the regex matches, the block is executed exactly once, and:
254
- # - If `replace` is true, all of the regex's matches are replaced by the
255
- # return value of the block, EXCEPT when the matched text is between a
256
- # set of double-asterisks ("**") or single-underscores ("_") indicating
257
- # it is already part of another location or friend's matched name.
258
- # - If `replace` is not true, we do not modify @description.
259
- # @param regex [Regexp] the regex to test against @description
260
- # @param replace [Boolean] true iff we should replace regex matches with the
261
- # yielded block's result in @description
262
- def description_matches(regex:, replace:, indicator:)
263
- # rubocop:disable Lint/AssignmentInCondition
264
- return unless match = @description.match(regex) # Abort if no match.
265
- # rubocop:enable Lint/AssignmentInCondition
266
- str = yield # It's important to execute the block even if not replacing.
267
- return unless replace # Only continue if we want to replace text.
268
-
269
- position = 0 # Prevent infinite looping by tracking last match position.
270
- loop do
271
- # Only make a replacement if we're not between a set of "**"s or "_"s.
272
- if (match.pre_match.scan("**").size % 2).zero? &&
273
- (match.post_match.scan("**").size % 2).zero? &&
274
- (match.pre_match.scan("_").size % 2).zero? &&
275
- (match.post_match.scan("_").size % 2).zero?
276
- @description = [
277
- match.pre_match,
278
- indicator,
279
- str,
280
- indicator,
281
- match.post_match
282
- ].join
283
- else
284
- # If we're between double-asterisks or single-underscores we're
285
- # already part of a name, so we don't make a substitution. We update
286
- # `position` to avoid infinite looping.
287
- position = match.end(0)
288
- end
289
-
290
- # Exit when there are no more matches.
291
- # rubocop:disable Lint/AssignmentInCondition
292
- break unless match = @description.match(regex, position)
293
- # rubocop:enable Lint/AssignmentInCondition
294
- end
295
- end
296
-
297
- # Default sorting for an array of activities is reverse-date.
298
- def <=>(other)
299
- other.date <=> date
300
- end
301
14
  end
302
15
  end