friends 0.26 → 0.27

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 045b1ac8af5c8004d171bf68792d452509beb67b
4
- data.tar.gz: 817d9105c51b235a4d317ef4bc7271a55b888739
3
+ metadata.gz: f518378eebb4f86c3a576648152ed462ed01618b
4
+ data.tar.gz: 76c7d599c00fd30eb91034c07f699b9f9b23986e
5
5
  SHA512:
6
- metadata.gz: bc9894a009593305940c9b241374d7e7fa8568f83152efdf59522b74fa7caad9af32c2990cc055428856e5f1835ff4b74028ead5dfa7c96f26e5f6a93e9af8c4
7
- data.tar.gz: 52d6e320aefd0fd4a45373ae20b1aa36c2b96f24367c35d61a13f10d29470c78ada7640460e03565f06084c2f389229f8689eca9519cbe18a26aca9ff391224b
6
+ metadata.gz: c46eadf9adfbf48498f84eed1fe6dd7b7edc7563ad7ec66250465719a5653ebe44553d8639edda26abe14e5afa8d383e2784852a884f61bdb8f4b70bb99b3893
7
+ data.tar.gz: 522058cf6e6b06d9d9fa8fffe24c7d7aeda87fe448a3349bfffe8627b637bfec96fb2ba70526c0fb4c6d37c61ce83a17cc9de4702555d8eb3a4319d89c55fade
@@ -1,5 +1,28 @@
1
1
  # Change Log
2
2
 
3
+ ## [v0.27](https://github.com/JacobEvelyn/friends/tree/v0.27) (2016-06-22)
4
+ [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.26...v0.27)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - Allow tags to be added and removed from friends without quotes [\#148](https://github.com/JacobEvelyn/friends/issues/148)
9
+ - Allow multi-word locations to be added without quotes [\#147](https://github.com/JacobEvelyn/friends/issues/147)
10
+ - Speed up initialization [\#143](https://github.com/JacobEvelyn/friends/issues/143)
11
+ - `friends update` can skip reading the friends.md file [\#137](https://github.com/JacobEvelyn/friends/issues/137)
12
+ - Add Gemnasium badge to README [\#130](https://github.com/JacobEvelyn/friends/issues/130)
13
+
14
+ **Fixed bugs:**
15
+
16
+ - Commands that find a friend fail on exact text matches when there's more than one fuzzy match [\#149](https://github.com/JacobEvelyn/friends/issues/149)
17
+
18
+ **Merged pull requests:**
19
+
20
+ - Small improvements to UX for some commands [\#150](https://github.com/JacobEvelyn/friends/pull/150) ([JacobEvelyn](https://github.com/JacobEvelyn))
21
+ - Bump development dependencies [\#146](https://github.com/JacobEvelyn/friends/pull/146) ([JacobEvelyn](https://github.com/JacobEvelyn))
22
+ - Skips reading the `friends.md` file on update [\#145](https://github.com/JacobEvelyn/friends/pull/145) ([JacobEvelyn](https://github.com/JacobEvelyn))
23
+ - Simplify code and improve performance [\#144](https://github.com/JacobEvelyn/friends/pull/144) ([JacobEvelyn](https://github.com/JacobEvelyn))
24
+ - Add Gemnasium integration with README badge [\#142](https://github.com/JacobEvelyn/friends/pull/142) ([JacobEvelyn](https://github.com/JacobEvelyn))
25
+
3
26
  ## [v0.26](https://github.com/JacobEvelyn/friends/tree/v0.26) (2016-05-23)
4
27
  [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.25...v0.26)
5
28
 
@@ -1,24 +1,41 @@
1
- # Contributor Code of Conduct
1
+ # Contributor Covenant Code of Conduct
2
2
 
3
- As contributors and maintainers of this project, and in the interest of
4
- fostering an open and welcoming community, we pledge to respect all people who
5
- contribute through reporting issues, posting feature requests, updating
6
- documentation, submitting pull requests or patches, and other activities.
3
+ ## Our Pledge
7
4
 
8
- We are committed to making participation in this project a harassment-free
9
- experience for everyone, regardless of level of experience, gender, gender
10
- identity and expression, sexual orientation, disability, personal appearance,
11
- body size, race, ethnicity, age, religion, or nationality.
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
12
22
 
13
23
  Examples of unacceptable behavior by participants include:
14
24
 
15
- * The use of sexualized language or imagery
16
- * Personal attacks
17
- * Trolling or insulting/derogatory comments
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
18
28
  * Public or private harassment
19
- * Publishing other's private information, such as physical or electronic
20
- addresses, without explicit permission
21
- * Other unethical or unprofessional conduct
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
22
39
 
23
40
  Project maintainers have the right and responsibility to remove, edit, or
24
41
  reject comments, commits, code, wiki edits, issues, and other contributions
@@ -26,25 +43,32 @@ that are not aligned to this Code of Conduct, or to ban temporarily or
26
43
  permanently any contributor for other behaviors that they deem inappropriate,
27
44
  threatening, offensive, or harmful.
28
45
 
29
- By adopting this Code of Conduct, project maintainers commit themselves to
30
- fairly and consistently applying these principles to every aspect of managing
31
- this project. Project maintainers who do not follow or enforce the Code of
32
- Conduct may be permanently removed from the project team.
46
+ ## Scope
33
47
 
34
48
  This Code of Conduct applies both within project spaces and in public spaces
35
- when an individual is representing the project or its community.
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
36
56
 
37
57
  Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
- reported by contacting a project maintainer at jacobevelyn@gmail.com. All
39
- complaints will be reviewed and investigated and will result in a response that
40
- is deemed necessary and appropriate to the circumstances. Maintainers are
41
- obligated to maintain confidentiality with regard to the reporter of an
42
- incident.
58
+ reported by contacting the project team at jacobevelyn@gmail.com. The project team
59
+ will review and investigate all complaints, and will respond in a way that it deems
60
+ appropriate to the circumstances. The project team is obligated to maintain
61
+ confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
43
67
 
68
+ ## Attribution
44
69
 
45
- This Code of Conduct is adapted from the [Contributor Covenant][homepage],
46
- version 1.3.0, available at
47
- [http://contributor-covenant.org/version/1/3/0/][version]
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
48
72
 
49
73
  [homepage]: http://contributor-covenant.org
50
- [version]: http://contributor-covenant.org/version/1/3/0/
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
- [![Gem Version](https://badge.fury.io/rb/friends.svg)](https://badge.fury.io/rb/friends) [![Code Climate](https://codeclimate.com/github/JacobEvelyn/friends/badges/gpa.svg)](https://codeclimate.com/github/JacobEvelyn/friends) [![Test Coverage](https://codeclimate.com/github/JacobEvelyn/friends/badges/coverage.svg)](https://codeclimate.com/github/JacobEvelyn/friends) [![Build Status](https://travis-ci.org/JacobEvelyn/friends.svg?branch=master)](https://travis-ci.org/JacobEvelyn/friends) [![Inline docs](http://inch-ci.org/github/JacobEvelyn/friends.png)](http://inch-ci.org/github/JacobEvelyn/friends) [![ghit.me](https://ghit.me/badge.svg?repo=JacobEvelyn/friends)](https://ghit.me/repo/JacobEvelyn/friends)
1
+ [![Gem Version](https://badge.fury.io/rb/friends.svg)](https://badge.fury.io/rb/friends) [![Dependency Status](https://gemnasium.com/badges/github.com/JacobEvelyn/friends.svg)](https://gemnasium.com/github.com/JacobEvelyn/friends)
2
+ [![Code Climate](https://codeclimate.com/github/JacobEvelyn/friends/badges/gpa.svg)](https://codeclimate.com/github/JacobEvelyn/friends) [![Test Coverage](https://codeclimate.com/github/JacobEvelyn/friends/badges/coverage.svg)](https://codeclimate.com/github/JacobEvelyn/friends) [![Build Status](https://travis-ci.org/JacobEvelyn/friends.svg?branch=master)](https://travis-ci.org/JacobEvelyn/friends) [![Inline docs](http://inch-ci.org/github/JacobEvelyn/friends.png)](http://inch-ci.org/github/JacobEvelyn/friends) [![ghit.me](https://ghit.me/badge.svg?repo=JacobEvelyn/friends)](https://ghit.me/repo/JacobEvelyn/friends)
2
3
 
3
- # Friends
4
+ # `friends`
4
5
 
5
6
  Spend time with the people you care about. Introvert-tested.
6
7
  Extrovert-approved.
@@ -270,7 +271,7 @@ Friend added: "Grace Hopper"
270
271
  #### `add tag`
271
272
 
272
273
  ```bash
273
- $ friends add tag "Grace Hopper" science
274
+ $ friends add tag Grace Hopper science
274
275
  Tag added to friend: "Grace Hopper @science"
275
276
  ```
276
277
 
@@ -568,7 +569,7 @@ Paris
568
569
  Removes a specific tag from a friend:
569
570
 
570
571
  ```bash
571
- $ friends remove tag "Grace Hopper" fun
572
+ $ friends remove tag Grace Hopper fun
572
573
  Tag removed from friend: "Grace Hopper (a.k.a. Amazing Grace) @OtherTag"
573
574
  ```
574
575
 
@@ -8,7 +8,8 @@ These are steps for the maintainer to take to release a new version of this gem.
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. `git add -A && git commit -m 'Update CHANGELOG for vX.X'`
12
- 7. `git push`
13
- 8. `gem build friends.gemspec && gem push *.gem && rm *.gem`
14
- 9. Celebrate!
11
+ 6. Confirm the `CHANGELOG` looks good with `git diff`
12
+ 7. `git add -A && git commit -m 'Update CHANGELOG for vX.X'`
13
+ 8. `git push`
14
+ 9. `gem build friends.gemspec && gem push *.gem && rm *.gem`
15
+ 10. Celebrate!
@@ -63,14 +63,18 @@ command :update do |update|
63
63
  Semverse::Version.coerce(Friends::VERSION)
64
64
  `gem update friends && gem cleanup friends`
65
65
  if $?.success?
66
- puts Paint["Updated to friends #{remote_version}", :bold, :green]
66
+ @message = Paint[
67
+ "Updated to friends #{remote_version}", :bold, :green
68
+ ]
67
69
  else
68
- puts Paint[
70
+ @message = Paint[
69
71
  "Error updating to friends version #{remote_version}", :bold, :red
70
72
  ]
71
73
  end
72
74
  else
73
- puts Paint["Already up-to-date (#{Friends::VERSION})", :bold, :green]
75
+ @message = Paint[
76
+ "Already up-to-date (#{Friends::VERSION})", :bold, :green
77
+ ]
74
78
  end
75
79
  end
76
80
  end
@@ -250,7 +254,7 @@ command :add do |add|
250
254
  add.arg_name "LOCATION"
251
255
  add.command :location do |add_location|
252
256
  add_location.action do |_, _, args|
253
- location = @introvert.add_location(name: args.first)
257
+ location = @introvert.add_location(name: args.join(" "))
254
258
  @message = "Location added: \"#{location.name}\""
255
259
  @dirty = true # Mark the file for cleaning.
256
260
  end
@@ -271,8 +275,8 @@ command :add do |add|
271
275
  add.command :tag do |add_tag|
272
276
  add_tag.action do |_, _, args|
273
277
  friend = @introvert.add_tag(
274
- name: args.first,
275
- tag: Tag.convert_to_tag(args[1])
278
+ name: args[0..-2].join(" "),
279
+ tag: Tag.convert_to_tag(args.last)
276
280
  )
277
281
  @message = "Tag added to friend: \"#{friend}\""
278
282
  @dirty = true # Mark the file for cleaning.
@@ -310,8 +314,8 @@ command :remove do |remove|
310
314
  remove.command :tag do |remove_tag|
311
315
  remove_tag.action do |_, _, args|
312
316
  friend = @introvert.remove_tag(
313
- name: args.first,
314
- tag: Tag.convert_to_tag(args[1])
317
+ name: args[0..-2].join(" "),
318
+ tag: Tag.convert_to_tag(args.last)
315
319
  )
316
320
  @message = "Tag removed from friend: \"#{friend}\""
317
321
  @dirty = true # Mark the file for cleaning.
@@ -429,15 +433,19 @@ command :rename do |rename|
429
433
  end
430
434
 
431
435
  # Before each command, clean up all arguments and create the global Introvert.
432
- pre do |global_options, _, options|
436
+ pre do |global_options, cmd, options|
433
437
  @debug_mode = global_options[:debug]
434
438
 
435
439
  final_options = global_options.merge!(options).select do |key, _|
436
440
  [:filename].include? key
437
441
  end
438
442
 
439
- @introvert = Friends::Introvert.new(final_options)
440
- true
443
+ # If we're updating the friends program we don't need to read the friends file
444
+ # but we don't skip this block entirely because we might still want to enable
445
+ # debug mode.
446
+ @introvert = Friends::Introvert.new(final_options) unless cmd.name == :update
447
+
448
+ true # Continue executing the command.
441
449
  end
442
450
 
443
451
  post do |global_options|
@@ -25,14 +25,14 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.add_dependency "chronic", "~> 0.10"
27
27
  spec.add_dependency "gli", "~> 2.12"
28
- spec.add_dependency "memoist", "~> 0.11"
28
+ spec.add_dependency "memoist", "~> 0.14"
29
29
  spec.add_dependency "paint", "~> 1.0"
30
30
  spec.add_dependency "semverse", "~> 1.2"
31
31
 
32
32
  spec.add_development_dependency "bundler", "~> 1.6"
33
- spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4"
33
+ spec.add_development_dependency "codeclimate-test-reporter", "~> 0.5"
34
34
  spec.add_development_dependency "minitest", "~> 5.5"
35
- spec.add_development_dependency "overcommit", "~> 0.30"
36
- spec.add_development_dependency "rake", "~> 10.0"
35
+ spec.add_development_dependency "overcommit", "~> 0.34"
36
+ spec.add_development_dependency "rake", "~> 11.2"
37
37
  spec.add_development_dependency "rubocop", "~> 0.40"
38
38
  end
@@ -37,7 +37,7 @@ module Friends
37
37
  date_s, _, description = str.partition(DATE_PARTITION)
38
38
 
39
39
  # rubocop:disable Lint/AssignmentInCondition
40
- if time = Chronic.parse(date_s)
40
+ if time = (date_s =~ /^\d{4}-\d{2}-\d{2}$/ ? Time : Chronic).parse(date_s)
41
41
  # rubocop:enable Lint/AssignmentInCondition
42
42
  @date = time.to_date
43
43
  @description = description
@@ -211,7 +211,6 @@ module Friends
211
211
  end
212
212
 
213
213
  # Now, we compute the likelihood of each friend in the possible-match set.
214
- introvert.set_friend_n_activities!
215
214
  introvert.set_likelihood_score!(
216
215
  matches: matched_friends,
217
216
  possible_matches: possible_matched_friends
@@ -103,7 +103,7 @@ module Friends
103
103
  # and is set by the Introvert as needed.
104
104
  attr_writer :n_activities
105
105
  def n_activities
106
- @n_activities || 0
106
+ defined?(@n_activities) ? @n_activities : 0
107
107
  end
108
108
 
109
109
  # The likelihood_score that an activity description that matches part of
@@ -113,7 +113,7 @@ module Friends
113
113
  # introvert#set_likelihood_score! methods.
114
114
  attr_writer :likelihood_score
115
115
  def likelihood_score
116
- @likelihood_score || 0
116
+ defined?(@likelihood_score) ? @likelihood_score : 0
117
117
  end
118
118
 
119
119
  # @return [Array] a list of all regexes to match the name in a string
@@ -49,15 +49,11 @@ module Friends
49
49
  # @raise [FriendsError] when a friend with that name is already in the file
50
50
  # @return [Friend] the added friend
51
51
  def add_friend(name:)
52
- if friend_with_exact_name(name)
53
- raise FriendsError, "Friend named #{name} already exists"
52
+ if @friends.any? { |friend| friend.name == name }
53
+ raise FriendsError, "Friend named \"#{name}\" already exists"
54
54
  end
55
55
 
56
- begin
57
- friend = Friend.deserialize(name)
58
- rescue Serializable::SerializationError => e
59
- raise FriendsError, e
60
- end
56
+ friend = Friend.deserialize(name)
61
57
 
62
58
  @friends << friend
63
59
 
@@ -68,11 +64,7 @@ module Friends
68
64
  # @param serialization [String] the serialized activity
69
65
  # @return [Activity] the added activity
70
66
  def add_activity(serialization:)
71
- begin
72
- activity = Activity.deserialize(serialization)
73
- rescue Serializable::SerializationError => e
74
- raise FriendsError, e
75
- end
67
+ activity = Activity.deserialize(serialization)
76
68
 
77
69
  activity.highlight_description(introvert: self) if activity.description
78
70
  @activities.unshift(activity)
@@ -89,11 +81,7 @@ module Friends
89
81
  raise FriendsError, "Location \"#{name}\" already exists"
90
82
  end
91
83
 
92
- begin
93
- location = Location.deserialize(name)
94
- rescue Serializable::SerializationError => e
95
- raise FriendsError, e
96
- end
84
+ location = Location.deserialize(name)
97
85
 
98
86
  @locations << location
99
87
 
@@ -107,8 +95,8 @@ module Friends
107
95
  # @raise [FriendsError] if 0 or 2+ locations match the given location name
108
96
  # @return [Friend] the modified friend
109
97
  def set_location(name:, location_name:)
110
- friend = friend_with_name_in(name)
111
- location = location_with_name_in(location_name)
98
+ friend = thing_with_name_in(:friend, name)
99
+ location = thing_with_name_in(:location, location_name)
112
100
  friend.location_name = location.name
113
101
  friend
114
102
  end
@@ -119,7 +107,7 @@ module Friends
119
107
  # @raise [FriendsError] if 0 or 2+ friends match the given name
120
108
  # @return [Friend] the existing friend
121
109
  def rename_friend(old_name:, new_name:)
122
- friend = friend_with_name_in(old_name)
110
+ friend = thing_with_name_in(:friend, old_name)
123
111
  @activities.each do |activity|
124
112
  activity.update_friend_name(old_name: friend.name, new_name: new_name)
125
113
  end
@@ -133,7 +121,7 @@ module Friends
133
121
  # @raise [FriendsError] if 0 or 2+ friends match the given name
134
122
  # @return [Location] the existing location
135
123
  def rename_location(old_name:, new_name:)
136
- loc = location_with_name_in(old_name)
124
+ loc = thing_with_name_in(:location, old_name)
137
125
 
138
126
  # Update locations in activities.
139
127
  @activities.each do |activity|
@@ -155,7 +143,7 @@ module Friends
155
143
  # @raise [FriendsError] if 0 or 2+ friends match the given name
156
144
  # @return [Friend] the existing friend
157
145
  def add_nickname(name:, nickname:)
158
- friend = friend_with_name_in(name)
146
+ friend = thing_with_name_in(:friend, name)
159
147
  friend.add_nickname(nickname)
160
148
  friend
161
149
  end
@@ -166,7 +154,7 @@ module Friends
166
154
  # @raise [FriendsError] if 0 or 2+ friends match the given name
167
155
  # @return [Friend] the existing friend
168
156
  def add_tag(name:, tag:)
169
- friend = friend_with_name_in(name)
157
+ friend = thing_with_name_in(:friend, name)
170
158
  friend.add_tag(tag)
171
159
  friend
172
160
  end
@@ -178,7 +166,7 @@ module Friends
178
166
  # @raise [FriendsError] if the friend does not have the given nickname
179
167
  # @return [Friend] the existing friend
180
168
  def remove_tag(name:, tag:)
181
- friend = friend_with_name_in(name)
169
+ friend = thing_with_name_in(:friend, name)
182
170
  friend.remove_tag(tag)
183
171
  friend
184
172
  end
@@ -190,7 +178,7 @@ module Friends
190
178
  # @raise [FriendsError] if the friend does not have the given nickname
191
179
  # @return [Friend] the existing friend
192
180
  def remove_nickname(name:, nickname:)
193
- friend = friend_with_name_in(name)
181
+ friend = thing_with_name_in(:friend, name)
194
182
  friend.remove_nickname(nickname)
195
183
  friend
196
184
  end
@@ -208,7 +196,7 @@ module Friends
208
196
 
209
197
  # Filter by location if a name is passed.
210
198
  if location_name
211
- location = location_with_name_in(location_name)
199
+ location = thing_with_name_in(:location, location_name)
212
200
  fs = fs.select { |friend| friend.location_name == location.name }
213
201
  end
214
202
 
@@ -244,9 +232,12 @@ module Friends
244
232
  # @param tagged [String] the name of a tag to filter by (of the form:
245
233
  # "@tag"), or nil for unfiltered
246
234
  # @return [Array] a list of all activity text values
235
+ # @raise [ArgumentError] if limit is present but limit < 1
247
236
  # @raise [FriendsError] if friend, location or tag cannot be found or
248
237
  # is ambiguous
249
238
  def list_activities(limit:, with:, location_name:, tagged:)
239
+ raise ArgumentError, "Limit must be positive" if limit && limit < 1
240
+
250
241
  acts = filtered_activities(
251
242
  with: with,
252
243
  location_name: location_name,
@@ -338,8 +329,6 @@ module Friends
338
329
  # for unfiltered
339
330
  # @return [Hash{String => Array<String>}]
340
331
  def suggest(location_name:)
341
- set_friend_n_activities! # Set n_activities for all friends.
342
-
343
332
  # Filter our friends by location if necessary.
344
333
  fs = @friends
345
334
  fs = fs.select { |f| f.location_name == location_name } if location_name
@@ -369,11 +358,6 @@ module Friends
369
358
  # Methods below this are only used internally and are not tested. #
370
359
  ###################################################################
371
360
 
372
- # Sets the n_activities field on each friend.
373
- def set_friend_n_activities!
374
- set_n_activities!(:friend)
375
- end
376
-
377
361
  # Get a regex friend map.
378
362
  #
379
363
  # The returned hash uses the following format:
@@ -480,13 +464,13 @@ module Friends
480
464
 
481
465
  # Filter by friend name if argument is passed.
482
466
  unless with.nil?
483
- friend = friend_with_name_in(with)
467
+ friend = thing_with_name_in(:friend, with)
484
468
  acts = acts.select { |act| act.includes_friend?(friend) }
485
469
  end
486
470
 
487
471
  # Filter by location name if argument is passed.
488
472
  unless location_name.nil?
489
- location = location_with_name_in(location_name)
473
+ location = thing_with_name_in(:location, location_name)
490
474
  acts = acts.select { |act| act.includes_location?(location) }
491
475
  end
492
476
 
@@ -501,16 +485,14 @@ module Friends
501
485
  # @param type [Symbol] one of: [:friend, :location]
502
486
  # @param limit [Integer] the number of favorite things to return
503
487
  # @return [Array] a list of the favorite things' names and activity counts
488
+ # @raise [ArgumentError] if type is not one of: [:friend, :location]
489
+ # @raise [ArgumentError] if limit is < 1
504
490
  def list_favorite_things(type, limit:)
505
491
  unless [:friend, :location].include? type
506
- raise FriendsError, "Type must be either :friend or :location"
492
+ raise ArgumentError, "Type must be either :friend or :location"
507
493
  end
508
494
 
509
- if limit < 1
510
- raise FriendsError, "Favorites limit must be positive"
511
- end
512
-
513
- send("set_n_activities!", type) # Set n_activities for all things.
495
+ raise ArgumentError, "Favorites limit must be positive" if limit < 1
514
496
 
515
497
  # Sort the results, with the most favorite thing first.
516
498
  results = instance_variable_get("@#{type}s").sort_by do |thing|
@@ -531,9 +513,10 @@ module Friends
531
513
 
532
514
  # Sets the n_activities field on each thing.
533
515
  # @param type [Symbol] one of: [:friend, :location]
516
+ # @raise [ArgumentError] if `type` is not one of: [:friend, :location]
534
517
  def set_n_activities!(type)
535
518
  unless [:friend, :location].include? type
536
- raise FriendsError, "Type must be either :friend or :location"
519
+ raise ArgumentError, "Type must be either :friend or :location"
537
520
  end
538
521
 
539
522
  # Construct a hash of location name to frequency of appearance.
@@ -546,8 +529,16 @@ module Friends
546
529
 
547
530
  # Remove names that are not in the locations list.
548
531
  freq_table.each do |name, count|
549
- thing = send("#{type}_with_exact_name", name)
550
- thing.n_activities = count if thing # Do nothing if name isn't valid.
532
+ things = instance_variable_get("@#{type}s").select do |thing|
533
+ thing.name == name
534
+ end
535
+
536
+ # Do nothing if no matches found.
537
+ if things.size == 1
538
+ things.first.n_activities = count
539
+ elsif things.size > 1
540
+ raise FriendsError, "More than one #{type} named \"#{name}\""
541
+ end
551
542
  end
552
543
  end
553
544
 
@@ -569,6 +560,9 @@ module Friends
569
560
  # Parse the line and update the parsing state.
570
561
  state = parse_line!(line, line_num: line_num, state: state)
571
562
  end
563
+
564
+ set_n_activities!(:friend)
565
+ set_n_activities!(:location)
572
566
  end
573
567
 
574
568
  # Parse the given line, adding to the various internal data structures as
@@ -599,7 +593,7 @@ module Friends
599
593
 
600
594
  begin
601
595
  instance_variable_get("@#{stage.id}") << stage.klass.deserialize(line)
602
- rescue FriendsError => e
596
+ rescue => e
603
597
  bad_line(e, line_num)
604
598
  end
605
599
 
@@ -615,67 +609,37 @@ module Friends
615
609
  ParsingStage.new(:locations, Location)
616
610
  ].freeze
617
611
 
618
- # @param name [String] the name of the friend to search for
619
- # @return [Friend] the friend whose name exactly matches the argument
620
- # @raise [FriendsError] if more than one friend has the given name
621
- def friend_with_exact_name(name)
622
- results = @friends.select { |friend| friend.name == name }
623
-
624
- case results.size
625
- when 0 then nil
626
- when 1 then results.first
627
- else raise FriendsError, "More than one friend named #{name}"
628
- end
629
- end
630
-
631
- # @param text [String] the name (or substring) of the friend to search for
632
- # @return [Friend] the friend that matches
612
+ # @param type [Symbol] one of: [:friend, :location]
613
+ # @param text [String] the name (or substring) of the friend or location to
614
+ # search for
615
+ # @return [Friend/Location] the friend or location that matches
633
616
  # @raise [FriendsError] if 0 or 2+ friends match the given text
634
- def friend_with_name_in(text)
635
- regex = Regexp.new(text, Regexp::IGNORECASE)
636
- friends = @friends.select { |friend| friend.name.match(regex) }
637
-
638
- case friends.size
639
- when 1
640
- # If exactly one friend matches, use that friend.
641
- return friends.first
642
- when 0 then raise FriendsError, "No friend found for \"#{text}\""
643
- else
644
- raise FriendsError,
645
- "More than one friend found for \"#{text}\": "\
646
- "#{friends.map(&:name).join(', ')}"
617
+ def thing_with_name_in(type, text)
618
+ things = instance_variable_get("@#{type}s").select do |thing|
619
+ if type == :friend
620
+ thing.regexes_for_name.any? { |regex| regex.match(text) }
621
+ else
622
+ thing.regex_for_name.match(text)
623
+ end
647
624
  end
648
- end
649
625
 
650
- # @param name [String] the name of the location to search for
651
- # @return [Location] the location whose name exactly matches the argument
652
- # @raise [FriendsError] if more than one location has the given name
653
- def location_with_exact_name(name)
654
- results = @locations.select { |location| location.name == name }
626
+ # If there's more than one match with fuzzy regexes but exactly one thing
627
+ # with that exact name, match it.
628
+ if things.size > 1
629
+ exact_things = things.select do |thing|
630
+ thing.name.casecmp(text) == 0 # We ignore case for an "exact" match.
631
+ end
655
632
 
656
- case results.size
657
- when 0 then nil
658
- when 1 then results.first
659
- else raise FriendsError, "More than one location named #{name}"
633
+ things = exact_things if exact_things.size == 1
660
634
  end
661
- end
662
-
663
- # @param text [String] the name (or substring) of the location to search for
664
- # @return [Location] the location that matches
665
- # @raise [FriendsError] if 0 or 2+ location match the given text
666
- def location_with_name_in(text)
667
- regex = Regexp.new(text, Regexp::IGNORECASE)
668
- locations = @locations.select { |location| location.name.match(regex) }
669
635
 
670
- case locations.size
671
- when 1
672
- # If exactly one location matches, use that location.
673
- return locations.first
674
- when 0 then raise FriendsError, "No location found for \"#{text}\""
636
+ case things.size
637
+ when 1 then things.first # If exactly one thing matches, use that thing.
638
+ when 0 then raise FriendsError, "No #{type} found for \"#{text}\""
675
639
  else
676
640
  raise FriendsError,
677
- "More than one location found for \"#{text}\": "\
678
- "#{locations.map(&:name).join(', ')}"
641
+ "More than one #{type} found for \"#{text}\": "\
642
+ "#{things.map(&:name).join(', ')}"
679
643
  end
680
644
  end
681
645
 
@@ -44,7 +44,7 @@ module Friends
44
44
  # only and is set by the Introvert as needed.
45
45
  attr_writer :n_activities
46
46
  def n_activities
47
- @n_activities || 0
47
+ defined?(@n_activities) ? @n_activities : 0
48
48
  end
49
49
 
50
50
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Friends
4
- VERSION = "0.26"
4
+ VERSION = "0.27"
5
5
  end
@@ -125,6 +125,7 @@ describe Friends::Activity do
125
125
  def stub_friends(val)
126
126
  old_val = introvert.instance_variable_get(:@friends)
127
127
  introvert.instance_variable_set(:@friends, val)
128
+ introvert.send(:set_n_activities!, :friend)
128
129
  yield
129
130
  introvert.instance_variable_set(:@friends, old_val)
130
131
  end
@@ -132,6 +133,8 @@ describe Friends::Activity do
132
133
  def stub_activities(val)
133
134
  old_val = introvert.instance_variable_get(:@activities)
134
135
  introvert.instance_variable_set(:@activities, val)
136
+ introvert.send(:set_n_activities!, :friend)
137
+ introvert.send(:set_n_activities!, :location)
135
138
  yield
136
139
  introvert.instance_variable_set(:@activities, old_val)
137
140
  end
@@ -139,6 +142,7 @@ describe Friends::Activity do
139
142
  def stub_locations(val)
140
143
  old_val = introvert.instance_variable_get(:@locations)
141
144
  introvert.instance_variable_set(:@locations, val)
145
+ introvert.send(:set_n_activities!, :location)
142
146
  yield
143
147
  introvert.instance_variable_set(:@locations, old_val)
144
148
  end
@@ -14,6 +14,7 @@ describe Friends::Introvert do
14
14
  def stub_friends(val)
15
15
  old_val = introvert.instance_variable_get(:@friends)
16
16
  introvert.instance_variable_set(:@friends, val)
17
+ introvert.send(:set_n_activities!, :friend)
17
18
  yield
18
19
  introvert.instance_variable_set(:@friends, old_val)
19
20
  end
@@ -21,6 +22,8 @@ describe Friends::Introvert do
21
22
  def stub_activities(val)
22
23
  old_val = introvert.instance_variable_get(:@activities)
23
24
  introvert.instance_variable_set(:@activities, val)
25
+ introvert.send(:set_n_activities!, :friend)
26
+ introvert.send(:set_n_activities!, :location)
24
27
  yield
25
28
  introvert.instance_variable_set(:@activities, old_val)
26
29
  end
@@ -28,6 +31,7 @@ describe Friends::Introvert do
28
31
  def stub_locations(val)
29
32
  old_val = introvert.instance_variable_get(:@locations)
30
33
  introvert.instance_variable_set(:@locations, val)
34
+ introvert.send(:set_n_activities!, :location)
31
35
  yield
32
36
  introvert.instance_variable_set(:@locations, old_val)
33
37
  end
@@ -79,7 +83,7 @@ describe Friends::Introvert do
79
83
  subject { introvert.clean }
80
84
 
81
85
  # Delete the file that is created each time.
82
- after { File.delete(filename) if File.exists?(filename) }
86
+ after { File.delete(filename) if File.exist?(filename) }
83
87
 
84
88
  it "writes cleaned file" do
85
89
  sorted_friends = friends.sort
@@ -394,7 +398,7 @@ describe Friends::Introvert do
394
398
  let(:with) { "george" }
395
399
 
396
400
  describe "when there is more than one friend match" do
397
- let(:friend_names) { ["George Washington Carver", "Boy George"] }
401
+ let(:friend_names) { ["George Washington Carver", "George Harrison"] }
398
402
 
399
403
  it "raises an error" do
400
404
  stub_friends(friends) do
@@ -695,6 +699,28 @@ describe Friends::Introvert do
695
699
  subject.must_equal friends.first
696
700
  end
697
701
  end
702
+
703
+ describe "when more than one friend name matches" do
704
+ let(:friend_names) { ["George Washington Carver", "George Washington"] }
705
+
706
+ describe "when one friend name matches exactly" do
707
+ it "returns the modified friend" do
708
+ stub_friends(friends) do
709
+ subject.must_equal friends.first
710
+ end
711
+ end
712
+ end
713
+
714
+ describe "when no friend name matches exactly" do
715
+ it "raises an error" do
716
+ proc do
717
+ stub_friends(friends) do
718
+ introvert.add_tag(name: "George", tag: "@school")
719
+ end
720
+ end.must_raise Friends::FriendsError
721
+ end
722
+ end
723
+ end
698
724
  end
699
725
 
700
726
  describe "#remove_tag" do
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.26'
4
+ version: '0.27'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob Evelyn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-23 00:00:00.000000000 Z
11
+ date: 2016-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chronic
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.11'
47
+ version: '0.14'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.11'
54
+ version: '0.14'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: paint
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '0.4'
103
+ version: '0.5'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '0.4'
110
+ version: '0.5'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: minitest
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -128,28 +128,28 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '0.30'
131
+ version: '0.34'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '0.30'
138
+ version: '0.34'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: rake
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '10.0'
145
+ version: '11.2'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '10.0'
152
+ version: '11.2'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: rubocop
155
155
  requirement: !ruby/object:Gem::Requirement