friends 0.34 → 0.35

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: 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