friends 0.34 → 0.35
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +79 -20
- data/RELEASING.md +7 -6
- data/friends.md +4 -0
- data/lib/friends/activity.rb +4 -291
- data/lib/friends/commands/add.rb +15 -13
- data/lib/friends/commands/list.rb +51 -47
- data/lib/friends/commands/stats.rb +3 -0
- data/lib/friends/event.rb +298 -0
- data/lib/friends/introvert.rb +109 -57
- data/lib/friends/note.rb +19 -0
- data/lib/friends/version.rb +1 -1
- data/test/add_event_helper.rb +410 -0
- data/test/commands/add/activity_spec.rb +6 -350
- data/test/commands/add/note_spec.rb +55 -0
- data/test/commands/clean_spec.rb +25 -3
- data/test/commands/list/notes_spec.rb +179 -0
- data/test/commands/list/tags_spec.rb +24 -0
- data/test/commands/stats_spec.rb +100 -7
- data/test/helper.rb +12 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 850118ba419db4de89e23cd279ea57d0e9c7305b
|
4
|
+
data.tar.gz: a4440dc2489f1756eaaa22ff395909d85abbf471
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 643172f6f2be216b21bbc47e43fb17ee712c3d2e9ba092cff0cec4657e29391739712c9d3bb6b6d6811fabf8a77626c91a43cba7bb7e86a1e33b3d6326183ffb
|
7
|
+
data.tar.gz: a64b0fc7a57e929af581457a3eeb2be21184dd60da9056ad91aa72068274fa602d13613f1faae2b86125bfd8c1e43d0d536bd3d35a1232873b32b76d215fd631
|
data/CHANGELOG.md
CHANGED
@@ -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/
|
14
|
-
[![Donate via Liberapay](https://
|
15
|
-
[![Donate via PayPal](https://img.shields.io/badge/
|
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-
|
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
|
-
|
128
|
-
|
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
|
-
|
138
|
-
|
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
|
-
*
|
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
|
-
*
|
148
|
-
|
149
|
-
|
150
|
-
|
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:
|
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
|
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
|
|
data/RELEASING.md
CHANGED
@@ -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
|
12
|
-
7.
|
13
|
-
8. `git
|
14
|
-
9. `
|
15
|
-
10.
|
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
|
data/lib/friends/activity.rb
CHANGED
@@ -1,302 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Activity represents an activity you've done
|
3
|
+
# Activity represents an activity you've done, usually
|
4
|
+
# with one or more friends.
|
4
5
|
|
5
|
-
require "
|
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
|