friends 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -18
- data/bin/friends +23 -9
- data/friends.gemspec +3 -2
- data/lib/friends/activity.rb +55 -55
- data/lib/friends/friend.rb +45 -0
- data/lib/friends/introvert.rb +67 -32
- data/lib/friends/version.rb +1 -1
- data/test/activity_spec.rb +120 -83
- data/test/friend_spec.rb +37 -0
- data/test/introvert_spec.rb +44 -28
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 014d82e8189c2cf8442af3e9058d56dbfdfb0fd3
|
4
|
+
data.tar.gz: a4a10982b5139cbe3b800aea6f984e32763beea9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac424ae74a5b3640c9374d4c7a9691d8e5305be1afba2a97f5714dfb662ffa2ea50238c6e30beb54ce48132dc610d8371229d9250a92e349772a5840ea1c9cf1
|
7
|
+
data.tar.gz: 8df418e8482b5d27eeb6823d870cabe1b818931628283e64c74cdb6d8ade3a78ed0371a2bf16415d08cc3e45492cd92ba88db78e0ec227ea138e69ff42c51cd7
|
data/README.md
CHANGED
@@ -31,34 +31,36 @@ care about.
|
|
31
31
|
$ gem install friends
|
32
32
|
```
|
33
33
|
|
34
|
-
## Usage
|
34
|
+
## Usage*
|
35
|
+
|
36
|
+
*Note that the command-line output is colored, which this README cannot show.
|
35
37
|
|
36
38
|
### Basic commands:
|
37
39
|
|
38
|
-
Add a friend:
|
40
|
+
##### Add a friend:
|
39
41
|
|
40
42
|
```
|
41
43
|
$ friends add friend "Grace Hopper"
|
42
44
|
Friend added: "Grace Hopper"
|
43
45
|
```
|
44
|
-
|
45
|
-
```
|
46
|
-
$ friends list friends
|
47
|
-
George Washington Carver
|
48
|
-
Grace Hopper
|
49
|
-
Marie Curie
|
50
|
-
```
|
51
|
-
Record an activity with a friend:
|
46
|
+
##### Record an activity with a friend:
|
52
47
|
```
|
53
48
|
$ friends add activity "Got lunch with Grace and George."
|
54
49
|
Activity added: "2015-01-04: Got lunch with Grace Hopper and George Washington Carver."
|
55
50
|
```
|
56
|
-
|
51
|
+
`friends` will **automatically** figure out which "Grace" and "George" you're referring to, *even if you're friends with lots of different Graces and Georges*.
|
52
|
+
|
53
|
+
You can of course specify a date for the activity:
|
57
54
|
```
|
58
55
|
$ friends add activity "2014-12-31: Celebrated the new year with Marie."
|
59
56
|
Activity added: "2014-12-31: Celebrated the new year with Marie Curie."
|
60
57
|
```
|
61
|
-
|
58
|
+
Or get an **interactive prompt** by just typing `friends add activity`, with or without a date specified:
|
59
|
+
```
|
60
|
+
$ friends add activity 2015-11-01
|
61
|
+
2015-11-01: <type description here>
|
62
|
+
```
|
63
|
+
##### List the activities you've recorded:
|
62
64
|
```
|
63
65
|
$ friends list activities
|
64
66
|
2015-01-04: Got lunch with Grace Hopper and George Washington Carver.
|
@@ -72,7 +74,7 @@ $ friends list activities --with "George"
|
|
72
74
|
2014-11-15: Talked to George Washington Carver on the phone for an hour.
|
73
75
|
|
74
76
|
```
|
75
|
-
Find your favorite friends:
|
77
|
+
##### Find your favorite friends:
|
76
78
|
```
|
77
79
|
$ friends list favorites
|
78
80
|
Your favorite friends:
|
@@ -87,7 +89,7 @@ Your favorite friends:
|
|
87
89
|
1. George Washington Carver (2 activities)
|
88
90
|
2. Grace Hopper (1)
|
89
91
|
```
|
90
|
-
Graph your relationship with a friend over time:
|
92
|
+
##### Graph (in color!) your relationship with a friend over time:
|
91
93
|
```
|
92
94
|
$ friends graph "George"
|
93
95
|
Nov 2014 |█
|
@@ -95,16 +97,21 @@ Dec 2014 |
|
|
95
97
|
Jan 2015 |█████
|
96
98
|
Feb 2015 |███
|
97
99
|
```
|
98
|
-
|
100
|
+
##### List all of your friends:
|
101
|
+
```
|
102
|
+
$ friends list friends
|
103
|
+
George Washington Carver
|
104
|
+
Grace Hopper
|
105
|
+
Marie Curie
|
106
|
+
```
|
99
107
|
### Global options:
|
100
108
|
|
101
109
|
##### --quiet
|
102
110
|
|
103
111
|
Quiet output messages:
|
104
112
|
```
|
105
|
-
$ friends add activity "Went rollerskating with George."
|
113
|
+
$ friends --quiet add activity "Went rollerskating with George."
|
106
114
|
$ # No output!
|
107
|
-
|
108
115
|
```
|
109
116
|
|
110
117
|
##### --filename
|
@@ -160,7 +167,7 @@ In case you're *really* interested, we have
|
|
160
167
|
|
161
168
|
If you have an idea,
|
162
169
|
[make a GitHub Issue](https://github.com/JacobEvelyn/friends/issues/new)!
|
163
|
-
Suggestions are very very welcome, and
|
170
|
+
Suggestions are very very welcome, and usually are implemented very
|
164
171
|
quickly. And if you'd like to do the implementing yourself:
|
165
172
|
|
166
173
|
1. Fork it (https://github.com/JacobEvelyn/friends/fork)
|
data/bin/friends
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
# Todo:
|
4
|
+
# - Create file if none exists.
|
5
|
+
# - Split out serialization into separate repository.
|
6
|
+
# - Add auto check for updates.
|
7
|
+
# - Allow friends to have nicknames.
|
4
8
|
# - Allow easy editing of most recent entry.
|
5
|
-
# - Make automatching use context better. (David & Meryl vs. David & Patricia)
|
6
9
|
# - Allow escape char to prevent automatching. ("\Zane wasn't there.")
|
7
10
|
|
8
11
|
require "gli"
|
9
|
-
require "
|
12
|
+
require "paint"
|
10
13
|
|
11
14
|
require "friends/introvert"
|
12
15
|
require "friends/version"
|
@@ -110,14 +113,23 @@ desc "Graph"
|
|
110
113
|
arg_name "NAME"
|
111
114
|
command :graph do |graph|
|
112
115
|
graph.action do |_, _, args|
|
116
|
+
# This math is taken from Minitest's Pride plugin (the PrideLOL class).
|
117
|
+
PI_3 = Math::PI / 3
|
118
|
+
|
119
|
+
colors = (0...(6 * 7)).map do |n|
|
120
|
+
n *= 1.0 / 6
|
121
|
+
r = (3 * Math.sin(n ) + 3).to_i
|
122
|
+
g = (3 * Math.sin(n + 2 * PI_3) + 3).to_i
|
123
|
+
b = (3 * Math.sin(n + 4 * PI_3) + 3).to_i
|
124
|
+
|
125
|
+
[r, g, b].map { |c| c * 51 }
|
126
|
+
end
|
127
|
+
|
113
128
|
data = @introvert.graph(name: args.first)
|
114
129
|
|
115
130
|
data.each do |month, count|
|
116
131
|
print "#{month} |"
|
117
|
-
|
118
|
-
colorer = Minitest::PrideLOL.new($stdout)
|
119
|
-
count.times { print colorer.pride "█" }
|
120
|
-
puts
|
132
|
+
puts colors.take(count).map { |rgb| Paint["█", rgb] }.join
|
121
133
|
end
|
122
134
|
end
|
123
135
|
end
|
@@ -127,9 +139,11 @@ command :suggest do |suggest|
|
|
127
139
|
suggest.action do
|
128
140
|
suggestions = @introvert.suggest
|
129
141
|
|
130
|
-
puts "Distant friend: \
|
131
|
-
|
132
|
-
puts "
|
142
|
+
puts "Distant friend: "\
|
143
|
+
"#{Paint[suggestions[:distant].sample, :bold, :magenta]}"
|
144
|
+
puts "Moderate friend: "\
|
145
|
+
"#{Paint[suggestions[:moderate].sample, :bold, :magenta]}"
|
146
|
+
puts "Close friend: #{Paint[suggestions[:close].sample, :bold, :magenta]}"
|
133
147
|
end
|
134
148
|
end
|
135
149
|
|
data/friends.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Friends::VERSION
|
9
9
|
spec.authors = ["Jacob Evelyn"]
|
10
10
|
spec.email = ["jacobevelyn@gmail.com"]
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
11
|
+
spec.summary = "Spend time with the people you care about."
|
12
|
+
spec.description = "Spend time with the people you care about. Introvert-tested. Extrovert-approved."
|
13
13
|
spec.homepage = "https://github.com/JacobEvelyn/friends"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_dependency "gli", "~> 2.12"
|
22
22
|
spec.add_dependency "memoist", "~> 0.11"
|
23
|
+
spec.add_dependency "paint", "~> 1.0"
|
23
24
|
|
24
25
|
spec.add_development_dependency "bundler", "~> 1.7"
|
25
26
|
spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4"
|
data/lib/friends/activity.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Activity represents an activity you've done with one or more Friends.
|
2
2
|
|
3
3
|
require "memoist"
|
4
|
+
require "paint"
|
4
5
|
|
5
6
|
require "friends/serializable"
|
6
7
|
|
@@ -34,11 +35,12 @@ module Friends
|
|
34
35
|
|
35
36
|
# @return [String] the command-line display text for the activity
|
36
37
|
def display_text
|
37
|
-
date_s =
|
38
|
+
date_s = Paint[date, :bold]
|
38
39
|
description_s = description.to_s
|
39
40
|
while match = description_s.match(/(\*\*)([^\*]+)(\*\*)/)
|
40
|
-
description_s =
|
41
|
-
|
41
|
+
description_s = "#{match.pre_match}"\
|
42
|
+
"#{Paint[match[2], :bold, :magenta]}"\
|
43
|
+
"#{match.post_match}"
|
42
44
|
end
|
43
45
|
"#{date_s}: #{description_s}"
|
44
46
|
end
|
@@ -51,14 +53,15 @@ module Friends
|
|
51
53
|
# Modify the description to turn inputted friend names
|
52
54
|
# (e.g. "Jacob" or "Jacob Evelyn") into full asterisk'd names
|
53
55
|
# (e.g. "**Jacob Evelyn**")
|
54
|
-
# @param introvert [Introvert]
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
# @param introvert [Introvert] used to access the list of friends and the
|
57
|
+
# connections between the
|
58
|
+
# NOTE: When a friend name matches more than one friend, this method chooses
|
59
|
+
# a friend based on a best-guess algorithm that looks at which friends do
|
60
|
+
# activities together and which friends are stronger than others. For
|
61
|
+
# more information see the comments below and the
|
62
|
+
# introvert#set_likelihood_score! method.
|
63
|
+
def highlight_friends(introvert:)
|
64
|
+
friend_regexes = introvert.friend_regex_map
|
62
65
|
|
63
66
|
# Create hash mapping regex to friend. Note that because two friends may
|
64
67
|
# have the same regex (e.g. /John/), we need to store the names in an
|
@@ -74,19 +77,51 @@ module Friends
|
|
74
77
|
end
|
75
78
|
end
|
76
79
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
matched_friends = []
|
81
|
+
|
82
|
+
# First, we find all of the regex matches with only one possibility, and
|
83
|
+
# make those substitutions.
|
84
|
+
regex_map.
|
85
|
+
select { |_, arr| arr.size == 1 }.each do |regex, friend_list|
|
86
|
+
if match = @description.match(regex)
|
87
|
+
friend = friend_list.first # There's only one friend in the list.
|
88
|
+
matched_friends << friend
|
89
|
+
@description = "#{match.pre_match}"\
|
90
|
+
"**#{friend.name}**"\
|
91
|
+
"#{match.post_match}"
|
84
92
|
end
|
93
|
+
end
|
94
|
+
|
95
|
+
possible_matched_friends = []
|
85
96
|
|
86
|
-
|
97
|
+
# Now, we look at regex matches that are ambiguous.
|
98
|
+
regex_map.
|
99
|
+
reject { |_, arr| arr.size == 1 }.each do |regex, friend_list|
|
100
|
+
if @description.match(regex)
|
101
|
+
possible_matched_friends << friend_list
|
102
|
+
end
|
87
103
|
end
|
88
104
|
|
89
|
-
|
105
|
+
# Now, we compute the likelihood of each friend in the possible-match set.
|
106
|
+
introvert.set_n_activities!
|
107
|
+
introvert.set_likelihood_score!(
|
108
|
+
matches: matched_friends,
|
109
|
+
possible_matches: possible_matched_friends
|
110
|
+
)
|
111
|
+
|
112
|
+
# Now we go through and replace all of the ambiguous matches with our best
|
113
|
+
# guess.
|
114
|
+
regex_map.
|
115
|
+
reject { |_, arr| arr.size == 1 }.each do |regex, friend_list|
|
116
|
+
if match = @description.match(regex)
|
117
|
+
guessed_friend = friend_list.sort_by do |friend|
|
118
|
+
[-friend.likelihood_score, -friend.n_activities]
|
119
|
+
end.first
|
120
|
+
@description = "#{match.pre_match}"\
|
121
|
+
"**#{guessed_friend.name}**"\
|
122
|
+
"#{match.post_match}"
|
123
|
+
end
|
124
|
+
end
|
90
125
|
end
|
91
126
|
|
92
127
|
# @param friend [Friend] the friend to test
|
@@ -108,40 +143,5 @@ module Friends
|
|
108
143
|
def <=>(other)
|
109
144
|
other.date <=> date
|
110
145
|
end
|
111
|
-
|
112
|
-
# @return [Array] a list of all regexes to match the name in a string, with
|
113
|
-
# longer regexes first
|
114
|
-
# Note: for now we only match on full names or first names
|
115
|
-
# Example: [
|
116
|
-
# /Jacob\s+Morris\s+Evelyn/,
|
117
|
-
# /Jacob/
|
118
|
-
# ]
|
119
|
-
def regexes_for_name(name)
|
120
|
-
# We generously allow any amount of whitespace between parts of a name.
|
121
|
-
splitter = "\\s+"
|
122
|
-
|
123
|
-
# We don't want to match names that are directly touching double asterisks
|
124
|
-
# as these are treated as sacred by our program.
|
125
|
-
no_leading_asterisks = "(?<!\\*\\*)"
|
126
|
-
no_ending_asterisks = "(?!\\*\\*)"
|
127
|
-
|
128
|
-
# We don't want to match names that are part of other words.
|
129
|
-
no_leading_alphabeticals = "(?<![A-z])"
|
130
|
-
no_ending_alphabeticals = "(?![A-z])"
|
131
|
-
|
132
|
-
# Create the list of regexes and return it.
|
133
|
-
chunks = name.split(Regexp.new(splitter))
|
134
|
-
|
135
|
-
[chunks, [chunks.first]].map do |words|
|
136
|
-
Regexp.new(
|
137
|
-
no_leading_asterisks +
|
138
|
-
no_leading_alphabeticals +
|
139
|
-
words.join(splitter) +
|
140
|
-
no_ending_alphabeticals +
|
141
|
-
no_ending_asterisks,
|
142
|
-
Regexp::IGNORECASE
|
143
|
-
)
|
144
|
-
end
|
145
|
-
end
|
146
146
|
end
|
147
147
|
end
|
data/lib/friends/friend.rb
CHANGED
@@ -37,6 +37,51 @@ module Friends
|
|
37
37
|
@n_activities || 0
|
38
38
|
end
|
39
39
|
|
40
|
+
# The likelihood_score that an activity description that matches part of
|
41
|
+
# this friend's name does in fact refer to this friend. A higher
|
42
|
+
# likelihood_score means it is more likely to be this friend. For more
|
43
|
+
# information see the activity#highlight_friends and
|
44
|
+
# introvert#set_likelihood_score! methods.
|
45
|
+
attr_writer :likelihood_score
|
46
|
+
def likelihood_score
|
47
|
+
@likelihood_score || 0
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Array] a list of all regexes to match the name in a string, with
|
51
|
+
# longer regexes first
|
52
|
+
# Note: for now we only match on full names or first names
|
53
|
+
# Example: [
|
54
|
+
# /Jacob\s+Morris\s+Evelyn/,
|
55
|
+
# /Jacob/
|
56
|
+
# ]
|
57
|
+
def regexes_for_name
|
58
|
+
# We generously allow any amount of whitespace between parts of a name.
|
59
|
+
splitter = "\\s+"
|
60
|
+
|
61
|
+
# We don't want to match names that are directly touching double asterisks
|
62
|
+
# as these are treated as sacred by our program.
|
63
|
+
no_leading_asterisks = "(?<!\\*\\*)"
|
64
|
+
no_ending_asterisks = "(?!\\*\\*)"
|
65
|
+
|
66
|
+
# We don't want to match names that are part of other words.
|
67
|
+
no_leading_alphabeticals = "(?<![A-z])"
|
68
|
+
no_ending_alphabeticals = "(?![A-z])"
|
69
|
+
|
70
|
+
# Create the list of regexes and return it.
|
71
|
+
chunks = name.split(Regexp.new(splitter))
|
72
|
+
|
73
|
+
[chunks, [chunks.first]].map do |words|
|
74
|
+
Regexp.new(
|
75
|
+
no_leading_asterisks +
|
76
|
+
no_leading_alphabeticals +
|
77
|
+
words.join(splitter) +
|
78
|
+
no_ending_alphabeticals +
|
79
|
+
no_ending_asterisks,
|
80
|
+
Regexp::IGNORECASE
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
40
85
|
private
|
41
86
|
|
42
87
|
# Default sorting for an array of friends is alphabetical.
|
data/lib/friends/introvert.rb
CHANGED
@@ -22,23 +22,20 @@ module Friends
|
|
22
22
|
|
23
23
|
# Read in the input file. It's easier to do this now and optimize later
|
24
24
|
# than try to overly be clever about what we read and write.
|
25
|
-
read_file
|
25
|
+
read_file
|
26
26
|
end
|
27
27
|
|
28
|
-
attr_reader :filename
|
29
|
-
attr_reader :activities
|
30
|
-
|
31
28
|
# Write out the friends file with cleaned/sorted data.
|
32
29
|
def clean
|
33
30
|
# Short-circuit if we've already cleaned the file so we don't write it
|
34
31
|
# twice.
|
35
|
-
return filename if @cleaned_file
|
32
|
+
return @filename if @cleaned_file
|
36
33
|
|
37
|
-
descriptions = activities.sort.map(&:serialize)
|
38
|
-
names = friends.sort.map(&:serialize)
|
34
|
+
descriptions = @activities.sort.map(&:serialize)
|
35
|
+
names = @friends.sort.map(&:serialize)
|
39
36
|
|
40
37
|
# Write out the cleaned file.
|
41
|
-
File.open(filename, "w") do |file|
|
38
|
+
File.open(@filename, "w") do |file|
|
42
39
|
file.puts(ACTIVITIES_HEADER)
|
43
40
|
descriptions.each { |desc| file.puts(desc) }
|
44
41
|
file.puts # Blank line separating friends from activities.
|
@@ -48,7 +45,7 @@ module Friends
|
|
48
45
|
|
49
46
|
@cleaned_file = true
|
50
47
|
|
51
|
-
filename
|
48
|
+
@filename
|
52
49
|
end
|
53
50
|
|
54
51
|
# Add a friend and write out the new friends file.
|
@@ -66,7 +63,7 @@ module Friends
|
|
66
63
|
raise FriendsError, e
|
67
64
|
end
|
68
65
|
|
69
|
-
friends << friend
|
66
|
+
@friends << friend
|
70
67
|
clean # Write a cleaned file.
|
71
68
|
|
72
69
|
friend # Return the added friend.
|
@@ -85,8 +82,8 @@ module Friends
|
|
85
82
|
# If there's no description, prompt the user for one.
|
86
83
|
activity.description ||= Readline.readline(activity.display_text)
|
87
84
|
|
88
|
-
activity.highlight_friends(introvert: self
|
89
|
-
activities << activity
|
85
|
+
activity.highlight_friends(introvert: self)
|
86
|
+
@activities << activity
|
90
87
|
clean # Write a cleaned file.
|
91
88
|
|
92
89
|
activity # Return the added activity.
|
@@ -95,7 +92,7 @@ module Friends
|
|
95
92
|
# List all friend names in the friends file.
|
96
93
|
# @return [Array] a list of all friend names
|
97
94
|
def list_friends
|
98
|
-
friends.map(&:name)
|
95
|
+
@friends.map(&:name)
|
99
96
|
end
|
100
97
|
|
101
98
|
# List your favorite friends.
|
@@ -111,13 +108,11 @@ module Friends
|
|
111
108
|
set_n_activities! # Set n_activities for all friends.
|
112
109
|
|
113
110
|
# Sort the results, with the most favorite friend first.
|
114
|
-
results = friends.sort_by { |friend| -friend.n_activities }
|
111
|
+
results = @friends.sort_by { |friend| -friend.n_activities }
|
115
112
|
|
116
113
|
# If we need to, trim the list.
|
117
114
|
results = results.take(limit) unless limit.nil?
|
118
115
|
|
119
|
-
# max_str_size = results.first.n_activities.to_s.size
|
120
|
-
# results.map { |friend| "#{friend.n_activities.to_s.rjust(max_str_size)} #{friend.name}" }
|
121
116
|
max_str_size = results.map(&:name).map(&:size).max
|
122
117
|
results.map.with_index(0) do |friend, index|
|
123
118
|
name = friend.name.ljust(max_str_size)
|
@@ -133,7 +128,7 @@ module Friends
|
|
133
128
|
# unfiltered
|
134
129
|
# @return [Array] a list of all activity text values
|
135
130
|
def list_activities(limit:, with:)
|
136
|
-
acts = activities
|
131
|
+
acts = @activities
|
137
132
|
|
138
133
|
# Filter by friend name if argument is passed.
|
139
134
|
unless with.nil?
|
@@ -162,7 +157,7 @@ module Friends
|
|
162
157
|
friend = friend_with_name_in(name) # Find the friend by name.
|
163
158
|
|
164
159
|
# Filter out activities that don't include the given friend.
|
165
|
-
acts = activities.select { |act| act.includes_friend?(friend: friend) }
|
160
|
+
acts = @activities.select { |act| act.includes_friend?(friend: friend) }
|
166
161
|
|
167
162
|
# Initialize the table of activities to have all of the months of that
|
168
163
|
# friend's activity range (including months in the middle of the range
|
@@ -189,12 +184,12 @@ module Friends
|
|
189
184
|
set_n_activities! # Set n_activities for all friends.
|
190
185
|
|
191
186
|
# Sort our friends, with the least favorite friend first.
|
192
|
-
sorted_friends = friends.sort_by(&:n_activities)
|
187
|
+
sorted_friends = @friends.sort_by(&:n_activities)
|
193
188
|
|
194
189
|
output = Hash.new { |h, k| h[k] = [] }
|
195
190
|
|
196
191
|
# First, get not-so-good friends.
|
197
|
-
while sorted_friends.first.n_activities < 2
|
192
|
+
while sorted_friends.first.n_activities < 2
|
198
193
|
output[:distant] << sorted_friends.shift.name
|
199
194
|
end
|
200
195
|
|
@@ -205,11 +200,15 @@ module Friends
|
|
205
200
|
output
|
206
201
|
end
|
207
202
|
|
203
|
+
###################################################################
|
204
|
+
# Methods below this are only used internally and are not tested. #
|
205
|
+
###################################################################
|
206
|
+
|
208
207
|
# Sets the n_activities field on each friend.
|
209
208
|
def set_n_activities!
|
210
209
|
# Construct a hash of friend name to frequency of appearance.
|
211
210
|
freq_table = Hash.new { |h, k| h[k] = 0 }
|
212
|
-
activities.each do |activity|
|
211
|
+
@activities.each do |activity|
|
213
212
|
activity.friend_names.each do |friend_name|
|
214
213
|
freq_table[friend_name] += 1
|
215
214
|
end
|
@@ -222,28 +221,64 @@ module Friends
|
|
222
221
|
end
|
223
222
|
end
|
224
223
|
|
225
|
-
|
224
|
+
# @return [Hash] mapping each friend to a list of all possible regexes for
|
225
|
+
# that friend's name
|
226
|
+
def friend_regex_map
|
227
|
+
@friends.each_with_object({}) do |friend, hash|
|
228
|
+
hash[friend] = friend.regexes_for_name
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Sets the likelihood_score field on each friend in `possible_matches`. This
|
233
|
+
# score represents how likely it is that an activity containing the friends
|
234
|
+
# in `matches` and containing a friend from each group in `possible_matches`
|
235
|
+
# contains that given friend.
|
236
|
+
# @param matches [Array<Friend>] the friends in a specific activity
|
237
|
+
# @param possible_matches [Array<Array<Friend>>] an array of groups of
|
238
|
+
# possible matches, for example:
|
239
|
+
# [
|
240
|
+
# [Friend.new(name: "John Doe"), Friend.new(name: "John Deere")],
|
241
|
+
# [Friend.new(name: "Aunt Mae"), Friend.new(name: "Aunt Sue")]
|
242
|
+
# ]
|
243
|
+
# These groups will all contain friends with similar names; the purpose of
|
244
|
+
# this method is to give us a likelihood that a "John" in an activity
|
245
|
+
# description, for instance, is "John Deere" vs. "John Doe"
|
246
|
+
def set_likelihood_score!(matches:, possible_matches:)
|
247
|
+
combinations = (matches + possible_matches.flatten).
|
248
|
+
combination(2).
|
249
|
+
reject do |friend1, friend2|
|
250
|
+
(matches & [friend1, friend2]).size == 2 ||
|
251
|
+
possible_matches.any? do |group|
|
252
|
+
(group & [friend1, friend2]).size == 2
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
@activities.each do |activity|
|
257
|
+
names = activity.friend_names
|
226
258
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
259
|
+
combinations.each do |group|
|
260
|
+
if (names & group.map(&:name)).size == 2
|
261
|
+
group.each { |friend| friend.likelihood_score += 1 }
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
231
265
|
end
|
232
266
|
|
267
|
+
private
|
268
|
+
|
233
269
|
# Process the friends.md file and store its contents in internal data
|
234
270
|
# structures.
|
235
|
-
|
236
|
-
def read_file(filename:)
|
271
|
+
def read_file
|
237
272
|
@friends = []
|
238
273
|
@activities = []
|
239
274
|
|
240
|
-
return unless File.exist?(filename)
|
275
|
+
return unless File.exist?(@filename)
|
241
276
|
|
242
277
|
state = :initial
|
243
278
|
line_num = 0
|
244
279
|
|
245
280
|
# Loop through all lines in the file and process them.
|
246
|
-
File.foreach(filename) do |line|
|
281
|
+
File.foreach(@filename) do |line|
|
247
282
|
line_num += 1
|
248
283
|
line.chomp! # Remove trailing newline from each line.
|
249
284
|
|
@@ -278,7 +313,7 @@ module Friends
|
|
278
313
|
# @return [Friend] the friend whose name exactly matches the argument
|
279
314
|
# @raise [FriendsError] if more than one friend has the given name
|
280
315
|
def friend_with_exact_name(name)
|
281
|
-
results = friends.select { |friend| friend.name == name }
|
316
|
+
results = @friends.select { |friend| friend.name == name }
|
282
317
|
|
283
318
|
case results.size
|
284
319
|
when 0 then nil
|
@@ -309,7 +344,7 @@ module Friends
|
|
309
344
|
# @return [Array] a list of all friends that match the given text
|
310
345
|
def friends_with_name_in(text)
|
311
346
|
regex = Regexp.new(text, Regexp::IGNORECASE)
|
312
|
-
friends.select { |friend| friend.name.match(regex) }
|
347
|
+
@friends.select { |friend| friend.name.match(regex) }
|
313
348
|
end
|
314
349
|
|
315
350
|
# Raise an error that a line in the friends file is malformed.
|
data/lib/friends/version.rb
CHANGED
data/test/activity_spec.rb
CHANGED
@@ -56,8 +56,9 @@ describe Friends::Activity do
|
|
56
56
|
|
57
57
|
it do
|
58
58
|
subject.
|
59
|
-
must_equal "
|
60
|
-
"Lunch with
|
59
|
+
must_equal "#{Paint[date_s, :bold]}: "\
|
60
|
+
"Lunch with #{Paint[friend1.name, :bold, :magenta]} and "\
|
61
|
+
"#{Paint[friend2.name, :bold, :magenta]}"
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
@@ -72,117 +73,153 @@ describe Friends::Activity do
|
|
72
73
|
end
|
73
74
|
|
74
75
|
describe "#highlight_friends" do
|
75
|
-
|
76
|
-
|
76
|
+
# Add helpers to set internal states for friends and activities.
|
77
|
+
def stub_friends(val)
|
78
|
+
introvert.instance_variable_set(:@friends, val)
|
79
|
+
yield
|
80
|
+
end
|
81
|
+
|
82
|
+
def stub_activities(val)
|
83
|
+
introvert.instance_variable_set(:@activities, val)
|
84
|
+
yield
|
85
|
+
end
|
86
|
+
|
77
87
|
let(:friends) { [friend1, friend2] }
|
78
|
-
let(:
|
79
|
-
let(:introvert) { Minitest::Mock.new }
|
88
|
+
let(:introvert) { Friends::Introvert.new }
|
80
89
|
subject do
|
81
|
-
activity.highlight_friends(introvert: introvert
|
90
|
+
stub_friends(friends) { activity.highlight_friends(introvert: introvert) }
|
82
91
|
end
|
83
92
|
|
84
93
|
it "finds all friends" do
|
85
94
|
subject
|
86
95
|
activity.description.
|
87
|
-
must_equal "Lunch with **#{friend1.name}** and **#{friend2.name}
|
96
|
+
must_equal "Lunch with **#{friend1.name}** and **#{friend2.name}**"
|
88
97
|
end
|
89
98
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
must_equal "Lunch with **#{friend1.name}** and **#{friend2.name}**."
|
99
|
+
describe "when description has first names" do
|
100
|
+
let(:description) { "Lunch with Elizabeth and John." }
|
101
|
+
it "matches friends" do
|
102
|
+
subject
|
103
|
+
activity.description.
|
104
|
+
must_equal "Lunch with **#{friend1.name}** and **#{friend2.name}**."
|
105
|
+
end
|
98
106
|
end
|
99
107
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
activity.description.
|
107
|
-
must_equal "Lunch with **Elizabeth Cady Stanton**."
|
108
|
+
describe "when names are not entered case-sensitively" do
|
109
|
+
let(:description) { "Lunch with elizabeth cady stanton." }
|
110
|
+
it "matches friends" do
|
111
|
+
subject
|
112
|
+
activity.description.must_equal "Lunch with **Elizabeth Cady Stanton**."
|
113
|
+
end
|
108
114
|
end
|
109
115
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
# No match found.
|
118
|
-
activity.description.must_equal "Field trip to the Johnson Co."
|
116
|
+
describe "when name is at beginning of word" do
|
117
|
+
let(:description) { "Field trip to the Johnson Co." }
|
118
|
+
it "does not match a friend" do
|
119
|
+
subject
|
120
|
+
# No match found.
|
121
|
+
activity.description.must_equal "Field trip to the Johnson Co."
|
122
|
+
end
|
119
123
|
end
|
120
124
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
# No match found.
|
129
|
-
activity.description.must_equal "Field trip to the JimJohnJames Co."
|
125
|
+
describe "when name is in middle of word" do
|
126
|
+
let(:description) { "Field trip to the JimJohnJames Co." }
|
127
|
+
it "does not match a friend" do
|
128
|
+
subject
|
129
|
+
# No match found.
|
130
|
+
activity.description.must_equal "Field trip to the JimJohnJames Co."
|
131
|
+
end
|
130
132
|
end
|
131
133
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
# No match found.
|
140
|
-
activity.description.must_equal "Field trip to the JimJohn Co."
|
134
|
+
describe "when name is at end of word" do
|
135
|
+
let(:description) { "Field trip to the JimJohn Co." }
|
136
|
+
it "does not match a friend" do
|
137
|
+
subject
|
138
|
+
# No match found.
|
139
|
+
activity.description.must_equal "Field trip to the JimJohn Co."
|
140
|
+
end
|
141
141
|
end
|
142
142
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
# No match found.
|
151
|
-
activity.description.must_equal "Dinner with **Elizabeth Cady Stanton."
|
143
|
+
describe "when name has leading asterisks" do
|
144
|
+
let(:description) { "Dinner with **Elizabeth Cady Stanton." }
|
145
|
+
it "does not match a friend" do
|
146
|
+
subject
|
147
|
+
# No match found.
|
148
|
+
activity.description.must_equal "Dinner with **Elizabeth Cady Stanton."
|
149
|
+
end
|
152
150
|
end
|
153
151
|
|
154
|
-
|
155
|
-
|
156
|
-
|
152
|
+
describe "when name has ending asterisks" do
|
153
|
+
let(:description) { "Dinner with Elizabeth**." }
|
154
|
+
it "does not match a friend" do
|
155
|
+
subject
|
157
156
|
|
158
157
|
# Note: for now we can't guarantee that "Elizabeth Cady Stanton**" won't
|
159
158
|
# match, because the Elizabeth isn't surrounded by asterisks.
|
160
|
-
description
|
161
|
-
|
162
|
-
activity.highlight_friends(introvert: introvert, friends: friends)
|
163
|
-
|
164
|
-
# No match found.
|
165
|
-
activity.description.must_equal "Dinner with Elizabeth**."
|
159
|
+
activity.description.must_equal "Dinner with Elizabeth**."
|
160
|
+
end
|
166
161
|
end
|
167
162
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
163
|
+
describe "when there are multiple matches" do
|
164
|
+
describe "when there is context from past activities" do
|
165
|
+
let(:description) { "Dinner with Elizabeth and John." }
|
166
|
+
let(:friends) do
|
167
|
+
[
|
168
|
+
friend1,
|
169
|
+
friend2,
|
170
|
+
Friends::Friend.new(name: "Elizabeth II")
|
171
|
+
]
|
172
|
+
end
|
173
|
+
|
174
|
+
it "chooses a match based on the context" do
|
175
|
+
# Create a past activity in which Elizabeth Cady Stanton did something
|
176
|
+
# with John Cage. Then, create past activities to make Elizabeth II a
|
177
|
+
# better friend than Elizabeth Cady Stanton.
|
178
|
+
old_activities = [
|
179
|
+
Friends::Activity.new(
|
180
|
+
date_s: date_s,
|
181
|
+
description: "Picnic with **Elizabeth Cady Stanton** and "\
|
182
|
+
"**John Cage**."
|
183
|
+
),
|
184
|
+
Friends::Activity.new(
|
185
|
+
date_s: date_s,
|
186
|
+
description: "Got lunch with with **Elizabeth II**."
|
187
|
+
),
|
188
|
+
Friends::Activity.new(
|
189
|
+
date_s: date_s,
|
190
|
+
description: "Ice skated with **Elizabeth II**."
|
191
|
+
)
|
192
|
+
]
|
193
|
+
|
194
|
+
# Elizabeth II is the better friend, but historical activities have
|
195
|
+
# had Elizabeth Cady Stanton and John Cage together. Thus, we should
|
196
|
+
# interpret "Elizabeth" as Elizabeth Cady Stanton.
|
197
|
+
stub_activities(old_activities) { subject }
|
198
|
+
|
199
|
+
activity.description.
|
200
|
+
must_equal "Dinner with **Elizabeth Cady Stanton** and "\
|
201
|
+
"**John Cage**."
|
202
|
+
end
|
203
|
+
end
|
174
204
|
|
175
|
-
|
176
|
-
|
177
|
-
friend1.n_activities = 5
|
178
|
-
friend2.n_activities = 7
|
205
|
+
describe "when there is no context from past activities" do
|
206
|
+
let(:description) { "Dinner with Elizabeth." }
|
179
207
|
|
180
|
-
|
208
|
+
it "falls back to choosing the better friend" do
|
209
|
+
friend2.name = "Elizabeth II"
|
181
210
|
|
182
|
-
|
183
|
-
|
211
|
+
# Give a past activity to Elizabeth II.
|
212
|
+
old_activity = Friends::Activity.new(
|
213
|
+
date_s: date_s,
|
214
|
+
description: "Do something with **Elizabeth II**."
|
215
|
+
)
|
184
216
|
|
185
|
-
|
217
|
+
stub_activities([old_activity]) { subject }
|
218
|
+
|
219
|
+
# Pick the friend with more activities.
|
220
|
+
activity.description.must_equal "Dinner with **Elizabeth II**."
|
221
|
+
end
|
222
|
+
end
|
186
223
|
end
|
187
224
|
end
|
188
225
|
|
data/test/friend_spec.rb
CHANGED
@@ -40,6 +40,43 @@ describe Friends::Friend do
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
describe "#n_activities" do
|
44
|
+
subject { friend.n_activities }
|
45
|
+
|
46
|
+
it "defaults to zero" do
|
47
|
+
subject.must_equal 0
|
48
|
+
end
|
49
|
+
|
50
|
+
it "is writable" do
|
51
|
+
friend.n_activities += 1
|
52
|
+
subject.must_equal 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#likelihood_score" do
|
57
|
+
subject { friend.likelihood_score }
|
58
|
+
|
59
|
+
it "defaults to zero" do
|
60
|
+
subject.must_equal 0
|
61
|
+
end
|
62
|
+
|
63
|
+
it "is writable" do
|
64
|
+
friend.likelihood_score += 1
|
65
|
+
subject.must_equal 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#regexes_for_name" do
|
70
|
+
subject { friend.regexes_for_name }
|
71
|
+
|
72
|
+
it "generates appropriate regexes" do
|
73
|
+
subject.must_equal [
|
74
|
+
/(?<!\*\*)(?<![A-z])Jacob\s+Evelyn(?![A-z])(?!\*\*)/i,
|
75
|
+
/(?<!\*\*)(?<![A-z])Jacob(?![A-z])(?!\*\*)/i
|
76
|
+
]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
43
80
|
describe "#<=>" do
|
44
81
|
it "sorts alphabetically" do
|
45
82
|
aaron = Friends::Friend.new(name: "Aaron")
|
data/test/introvert_spec.rb
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
require_relative "helper"
|
2
2
|
|
3
3
|
describe Friends::Introvert do
|
4
|
+
# Add readers to make internal state easier to test.
|
5
|
+
class Friends::Introvert
|
6
|
+
attr_reader :filename, :activities, :friends
|
7
|
+
end
|
8
|
+
|
9
|
+
# Add helpers to set internal states for friends and activities.
|
10
|
+
def stub_friends(val)
|
11
|
+
introvert.instance_variable_set(:@friends, val)
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
|
15
|
+
def stub_activities(val)
|
16
|
+
introvert.instance_variable_set(:@activities, val)
|
17
|
+
yield
|
18
|
+
end
|
19
|
+
|
4
20
|
let(:filename) { "test/tmp/friends.md" }
|
5
21
|
let(:args) { { filename: filename } }
|
6
22
|
let(:introvert) { Friends::Introvert.new(args) }
|
@@ -60,8 +76,8 @@ describe Friends::Introvert do
|
|
60
76
|
"#{Friends::Introvert::FRIENDS_HEADER}\n#{name_output}\n"
|
61
77
|
|
62
78
|
# Read the input as unsorted, and make sure we get sorted output.
|
63
|
-
|
64
|
-
|
79
|
+
stub_friends(unsorted_friends) do
|
80
|
+
stub_activities(unsorted_activities) do
|
65
81
|
subject
|
66
82
|
File.read(filename).must_equal expected_output
|
67
83
|
end
|
@@ -75,7 +91,7 @@ describe Friends::Introvert do
|
|
75
91
|
subject { introvert.list_friends }
|
76
92
|
|
77
93
|
it "lists the names of friends" do
|
78
|
-
|
94
|
+
stub_friends(friends) do
|
79
95
|
subject.must_equal friend_names
|
80
96
|
end
|
81
97
|
end
|
@@ -90,14 +106,14 @@ describe Friends::Introvert do
|
|
90
106
|
|
91
107
|
describe "when there is no existing friend with that name" do
|
92
108
|
it "adds the given friend" do
|
93
|
-
|
109
|
+
stub_friends(friends) do
|
94
110
|
subject
|
95
111
|
introvert.list_friends.must_include new_friend_name
|
96
112
|
end
|
97
113
|
end
|
98
114
|
|
99
115
|
it "returns the friend added" do
|
100
|
-
|
116
|
+
stub_friends(friends) do
|
101
117
|
subject.name.must_equal new_friend_name
|
102
118
|
end
|
103
119
|
end
|
@@ -107,7 +123,7 @@ describe Friends::Introvert do
|
|
107
123
|
let(:new_friend_name) { friend_names.first }
|
108
124
|
|
109
125
|
it "raises an error" do
|
110
|
-
|
126
|
+
stub_friends(friends) do
|
111
127
|
proc { subject }.must_raise Friends::FriendsError
|
112
128
|
end
|
113
129
|
end
|
@@ -123,7 +139,7 @@ describe Friends::Introvert do
|
|
123
139
|
let(:limit) { 1 }
|
124
140
|
|
125
141
|
it "lists that number of activities" do
|
126
|
-
|
142
|
+
stub_activities(activities) do
|
127
143
|
subject.size.must_equal limit
|
128
144
|
end
|
129
145
|
end
|
@@ -133,7 +149,7 @@ describe Friends::Introvert do
|
|
133
149
|
let(:limit) { activities.size }
|
134
150
|
|
135
151
|
it "lists all activities" do
|
136
|
-
|
152
|
+
stub_activities(activities) do
|
137
153
|
subject.size.must_equal activities.size
|
138
154
|
end
|
139
155
|
end
|
@@ -143,7 +159,7 @@ describe Friends::Introvert do
|
|
143
159
|
let(:limit) { activities.size + 5 }
|
144
160
|
|
145
161
|
it "lists all activities" do
|
146
|
-
|
162
|
+
stub_activities(activities) do
|
147
163
|
subject.size.must_equal activities.size
|
148
164
|
end
|
149
165
|
end
|
@@ -153,7 +169,7 @@ describe Friends::Introvert do
|
|
153
169
|
let(:limit) { nil }
|
154
170
|
|
155
171
|
it "lists all activities" do
|
156
|
-
|
172
|
+
stub_activities(activities) do
|
157
173
|
subject.size.must_equal activities.size
|
158
174
|
end
|
159
175
|
end
|
@@ -163,7 +179,7 @@ describe Friends::Introvert do
|
|
163
179
|
let(:with) { nil }
|
164
180
|
|
165
181
|
it "lists the activities" do
|
166
|
-
|
182
|
+
stub_activities(activities) do
|
167
183
|
subject.must_equal activities.map(&:display_text)
|
168
184
|
end
|
169
185
|
end
|
@@ -176,8 +192,8 @@ describe Friends::Introvert do
|
|
176
192
|
let(:friend_names) { ["George Washington Carver", "Boy George"] }
|
177
193
|
|
178
194
|
it "raises an error" do
|
179
|
-
|
180
|
-
|
195
|
+
stub_friends(friends) do
|
196
|
+
stub_activities(activities) do
|
181
197
|
proc { subject }.must_raise Friends::FriendsError
|
182
198
|
end
|
183
199
|
end
|
@@ -188,8 +204,8 @@ describe Friends::Introvert do
|
|
188
204
|
let(:friend_names) { ["Joe"] }
|
189
205
|
|
190
206
|
it "raises an error" do
|
191
|
-
|
192
|
-
|
207
|
+
stub_friends(friends) do
|
208
|
+
stub_activities(activities) do
|
193
209
|
proc { subject }.must_raise Friends::FriendsError
|
194
210
|
end
|
195
211
|
end
|
@@ -198,8 +214,8 @@ describe Friends::Introvert do
|
|
198
214
|
|
199
215
|
describe "when there is exactly one friend match" do
|
200
216
|
it "filters the activities by that friend" do
|
201
|
-
|
202
|
-
|
217
|
+
stub_friends(friends) do
|
218
|
+
stub_activities(activities) do
|
203
219
|
# Only one activity has that friend.
|
204
220
|
subject.must_equal activities[0..0].map(&:display_text)
|
205
221
|
end
|
@@ -218,14 +234,14 @@ describe Friends::Introvert do
|
|
218
234
|
after { File.delete(filename) if File.exists?(filename) }
|
219
235
|
|
220
236
|
it "adds the given activity" do
|
221
|
-
|
237
|
+
stub_friends(friends) do
|
222
238
|
subject
|
223
239
|
introvert.activities.last.description.must_equal activity_description
|
224
240
|
end
|
225
241
|
end
|
226
242
|
|
227
243
|
it "returns the activity added" do
|
228
|
-
|
244
|
+
stub_friends(friends) do
|
229
245
|
subject.description.must_equal activity_description
|
230
246
|
end
|
231
247
|
end
|
@@ -238,8 +254,8 @@ describe Friends::Introvert do
|
|
238
254
|
let(:limit) { nil }
|
239
255
|
|
240
256
|
it "returns all friends in order of favoritism with activity counts" do
|
241
|
-
|
242
|
-
|
257
|
+
stub_friends(friends) do
|
258
|
+
stub_activities(activities) do
|
243
259
|
subject.must_equal [
|
244
260
|
"Betsy Ross (2 activities)",
|
245
261
|
"George Washington Carver (1)"
|
@@ -253,8 +269,8 @@ describe Friends::Introvert do
|
|
253
269
|
let(:limit) { 1 }
|
254
270
|
|
255
271
|
it "returns the number of favorites requested" do
|
256
|
-
|
257
|
-
|
272
|
+
stub_friends(friends) do
|
273
|
+
stub_activities(activities) do
|
258
274
|
subject.must_equal ["Betsy Ross (2 activities)"]
|
259
275
|
end
|
260
276
|
end
|
@@ -266,8 +282,8 @@ describe Friends::Introvert do
|
|
266
282
|
subject { introvert.suggest }
|
267
283
|
|
268
284
|
it "returns distant, moderate, and close friends" do
|
269
|
-
|
270
|
-
|
285
|
+
stub_friends(friends) do
|
286
|
+
stub_activities(activities) do
|
271
287
|
subject.must_equal(
|
272
288
|
distant: ["George Washington Carver"],
|
273
289
|
moderate: [],
|
@@ -293,7 +309,7 @@ describe Friends::Introvert do
|
|
293
309
|
let(:friend_name) { "e" }
|
294
310
|
|
295
311
|
it "raises an error" do
|
296
|
-
|
312
|
+
stub_friends(friends) do
|
297
313
|
proc { subject }.must_raise Friends::FriendsError
|
298
314
|
end
|
299
315
|
end
|
@@ -325,8 +341,8 @@ describe Friends::Introvert do
|
|
325
341
|
end
|
326
342
|
|
327
343
|
it "returns a hash of months and frequencies" do
|
328
|
-
|
329
|
-
|
344
|
+
stub_friends(friends) do
|
345
|
+
stub_activities(activities) do
|
330
346
|
strftime_format = Friends::Introvert::GRAPH_DATE_FORMAT
|
331
347
|
|
332
348
|
first = activities[0]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: friends
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.3'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jacob Evelyn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gli
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: paint
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|