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 +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
|
+
[](https://opencollective.com/friends)
|
|
7
|
+
[](https://www.patreon.com/jacobevelyn)
|
|
8
|
+
[](https://liberapay.com/jacobevelyn/donate)
|
|
9
|
+
[](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
|
+
[](https://flattr.com/submit/auto?user_id=jacobevelyn&url=https://github.com/JacobEvelyn/friends&title=friends&tags=github&category=software)
|
|
11
|
+
[](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
|
[](https://opencollective.com/friends)
|
|
13
|
-
[](https://www.patreon.com/jacobevelyn)
|
|
14
|
+
[](https://liberapay.com/jacobevelyn/donate)
|
|
15
|
+
[](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
|
[](https://flattr.com/submit/auto?user_id=jacobevelyn&url=https://github.com/JacobEvelyn/friends&title=friends&tags=github&category=software)
|
|
17
|
-
[](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
|