friends 0.19 → 0.20

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: 4ba2ca1e570ce12bae59bd92e45eed450b15859d
4
- data.tar.gz: f29a32cae219ae83c7cbc925d1493894d76409cb
3
+ metadata.gz: 1429eb3d555f53ebb68816b253350252370d37bc
4
+ data.tar.gz: 42095e0608c73c17fd75f8f4bedab8313e19a4b1
5
5
  SHA512:
6
- metadata.gz: 264af4e8b7d6956401c9a7e6babecb6a8e8b6dc154de383394aef7890c2262685ea90c24c1d525d03802ecb1a028ffbc445cd4306fa91c5badbf044389667d1e
7
- data.tar.gz: 674aa75753fc7aedc6f50bbb54956ecd91f6df2d8930d886f5f018e08d9c89b0db2e4f5aa74c014b45b35c6d429809d6fbdb7982598b52b66b0636f377136d3a
6
+ metadata.gz: ab66c9042a1c23ec4e5c2a1685d03b91d5b66c1382ca3ed64b1822277bf08ed1bbe321a9144f59256d6c369b93f9b954d8d6307a9a50b6c9f6fc4374c9e7df6f
7
+ data.tar.gz: 87719311645cdff9c7af1a862c9533e8671c1e000c7221f7833226bfcad3f86935de92440acbad5a65aa87bf005485c2b31589cdb3d9ea094973093d13896add
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ mkmf.log
16
16
  ideas.txt
17
17
  TODO.txt
18
18
  .DS_Store
19
+ .byebug_history
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Change Log
2
2
 
3
+ ## [v0.20](https://github.com/JacobEvelyn/friends/tree/v0.20) (2016-05-08)
4
+ [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.19...v0.20)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - Add --tagged option to `list friends` [\#119](https://github.com/JacobEvelyn/friends/issues/119)
9
+ - Add --verbose option to `list friends` [\#117](https://github.com/JacobEvelyn/friends/issues/117)
10
+ - Add `list hashtags` command [\#116](https://github.com/JacobEvelyn/friends/issues/116)
11
+ - Add hashtag capabilities to friends [\#90](https://github.com/JacobEvelyn/friends/issues/90)
12
+ - Add hashtag capabilities to activities [\#89](https://github.com/JacobEvelyn/friends/issues/89)
13
+ - Add location data to friends [\#66](https://github.com/JacobEvelyn/friends/issues/66)
14
+
15
+ **Merged pull requests:**
16
+
17
+ - Implement hashtags [\#118](https://github.com/JacobEvelyn/friends/pull/118) ([JacobEvelyn](https://github.com/JacobEvelyn))
18
+
3
19
  ## [v0.19](https://github.com/JacobEvelyn/friends/tree/v0.19) (2016-05-02)
4
20
  [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.18...v0.19)
5
21
 
data/README.md CHANGED
@@ -17,6 +17,7 @@ Extrovert-approved.
17
17
  - `add`
18
18
  - [`add activity`](#add-activity)
19
19
  - [`add friend`](#add-friend)
20
+ - [`add hashtag`](#add-hashtag)
20
21
  - [`add location`](#add-location)
21
22
  - [`add nickname`](#add-nickname)
22
23
  - [`clean`](#clean)
@@ -28,8 +29,11 @@ Extrovert-approved.
28
29
  - [`list favorite friends`](#list-favorite-friends)
29
30
  - [`list favorite locations`](#list-favorite-locations)
30
31
  - [`list friends`](#list-friends)
32
+ - [`list hashtags`](#list-hashtags)
31
33
  - [`list locations`](#list-locations)
32
- - [`remove nickname`](#remove-nickname)
34
+ - `remove`
35
+ - [`remove hashtag`](#remove-hashtag)
36
+ - [`remove nickname`](#remove-nickname)
33
37
  - `rename`
34
38
  - [`rename friend`](#rename-friend)
35
39
  - [`rename location`](#rename-location)
@@ -83,10 +87,14 @@ Easy, huh?
83
87
  `friends` is structured around several different types of things:
84
88
 
85
89
  - **Activities**: The things you do. Each activity has a date associated with
86
- it.
90
+ it. Activities may optionally contain any number of *friends*, *locations*,
91
+ and *hashtags*.
87
92
  - **Friends**: The people you do *activities* with. Each friend has a name and,
88
- optionally, one or several nicknames.
89
- - **Locations**: The places in which *activities* happen.
93
+ optionally, one or several nicknames. (Examples: `John`, `Grace Hopper`)
94
+ - **Locations**: The places in which *activities* happen. (Examples: `Paris`,
95
+ `Marie's Diner`)
96
+ - **Hashtags**: A way to categorize your *activities* with tags of your
97
+ choosing. (Examples: `#exercise`, `#concert`)
90
98
 
91
99
  The `friends.md` Markdown file that stores all of your data contains:
92
100
 
@@ -212,6 +220,14 @@ $ friends add activity "Went swimming near atlantis with George."
212
220
  Activity added: "2016-01-06: Went swimming near Atlantis with George Washington Carver."
213
221
  ```
214
222
 
223
+ Hashtags will be colored if they're provided (though this README can't display
224
+ color so you'll just have to have faith here):
225
+
226
+ ```bash
227
+ $ friends add activity "The office softball team wins a game! #work #exercise"
228
+ Activity added: "2016-05-05: The office softball team wins a game! #work #exercise"
229
+ ```
230
+
215
231
  You can of course specify a date for the activity:
216
232
 
217
233
  ```bash
@@ -247,6 +263,13 @@ $ friends add friend "Grace Hopper"
247
263
  Friend added: "Grace Hopper"
248
264
  ```
249
265
 
266
+ #### `add hashtag`
267
+
268
+ ```bash
269
+ $ friends add hashtag "Grace Hopper" science
270
+ Hashtag added to friend: "Grace Hopper #science
271
+ ```
272
+
250
273
  #### `add location`
251
274
 
252
275
  ```
@@ -350,8 +373,8 @@ Lists recent activities:
350
373
 
351
374
  ```bash
352
375
  $ friends list activities
353
- 2015-01-04: Got lunch with Grace Hopper and George Washington Carver.
354
- 2014-12-31: Celebrated the new year with Marie Curie in New York City.
376
+ 2015-01-04: Got lunch with Grace Hopper and George Washington Carver. #food
377
+ 2014-12-31: Celebrated the new year with Marie Curie in New York City. #partying
355
378
  2014-11-15: Talked to George Washington Carver on the phone for an hour.
356
379
  ```
357
380
 
@@ -359,15 +382,15 @@ You can adjust how many activities are shown:
359
382
 
360
383
  ```bash
361
384
  $ friends list activities --limit 2
362
- 2015-01-04: Got lunch with Grace Hopper and George Washington Carver.
363
- 2014-12-31: Celebrated the new year with Marie Curie in New York City.
385
+ 2015-01-04: Got lunch with Grace Hopper and George Washington Carver. #food
386
+ 2014-12-31: Celebrated the new year with Marie Curie in New York City. #partying
364
387
  ```
365
388
 
366
389
  Or only list the activities you did with a certain friend:
367
390
 
368
391
  ```bash
369
- $ friends list activities --with "George"
370
- 2015-01-04: Got lunch with Grace Hopper and George Washington Carver.
392
+ $ friends list activities --with George
393
+ 2015-01-04: Got lunch with Grace Hopper and George Washington Carver. #food
371
394
  2014-11-15: Talked to George Washington Carver on the phone for an hour.
372
395
  ```
373
396
 
@@ -375,7 +398,21 @@ Or filter your activities by location:
375
398
 
376
399
  ```bash
377
400
  $ friends list activities --in "New York"
378
- 2014-12-31: Celebrated the new year with Marie Curie in New York City.
401
+ 2014-12-31: Celebrated the new year with Marie Curie in New York City. #partying
402
+ ```
403
+
404
+ Or by hashtag:
405
+
406
+ ```bash
407
+ $ friends list activities --tagged food
408
+ 2015-01-04: Got lunch with Grace Hopper and George Washington Carver. #food
409
+ ```
410
+
411
+ And you can mix and match these options to your heart's content:
412
+
413
+ ```bash
414
+ $ friends list activities --tagged food --with Grace
415
+ 2015-01-04: Got lunch with Grace Hopper and George Washington Carver. #food
379
416
  ```
380
417
 
381
418
  #### `list favorite friends`
@@ -422,7 +459,7 @@ Your favorite locations:
422
459
 
423
460
  #### `list friends`
424
461
 
425
- Lists all of your friends:
462
+ Lists all of your friends in alphabetical order:
426
463
 
427
464
  ```bash
428
465
  $ friends list friends
@@ -431,6 +468,15 @@ Grace Hopper
431
468
  Marie Curie
432
469
  ```
433
470
 
471
+ You can also include friend nicknames, locations, and hashtags:
472
+
473
+ ```bash
474
+ $ friends list friends --verbose
475
+ George Washington Carver
476
+ Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris] #navy #science
477
+ Marie Curie [Atlantis] #science
478
+ ```
479
+
434
480
  You can filter your friends by location:
435
481
 
436
482
  ```bash
@@ -438,9 +484,46 @@ $ friends list friends --in Paris
438
484
  Marie Curie
439
485
  ```
440
486
 
487
+ And you can also filter your friends by hashtag:
488
+
489
+ ```bash
490
+ $ friends list friends --tagged science
491
+ Grace Hopper
492
+ Marie Curie
493
+ ```
494
+
495
+ #### `list hashtags`
496
+
497
+ Lists all hashtags you've used, in alphabetical order:
498
+
499
+ ```bash
500
+ $ friends list hashtags
501
+ #dancing
502
+ #food
503
+ #school
504
+ #swanky
505
+ ```
506
+
507
+ You can limit this to only hashtags from activities:
508
+
509
+ ```bash
510
+ $ friends list hashtags --from activities
511
+ #dancing
512
+ #food
513
+ #swanky
514
+ ```
515
+
516
+ Or only hashtags from friends:
517
+
518
+ ```bash
519
+ $ friends list hashtags --from friends
520
+ #school
521
+ #swanky
522
+ ```
523
+
441
524
  #### `list locations`
442
525
 
443
- Lists all of the locations you've added:
526
+ Lists all of the locations you've added, in alphabetical order::
444
527
 
445
528
  ```
446
529
  $ friends list locations
@@ -449,6 +532,15 @@ New York City
449
532
  Paris
450
533
  ```
451
534
 
535
+ #### `remove hashtag`
536
+
537
+ Removes a specific hashtag from a friend:
538
+
539
+ ```bash
540
+ $ friends remove hashtag "Grace Hopper" fun
541
+ Hashtag removed from friend: "Grace Hopper (a.k.a. Amazing Grace) #OtherHashtag"
542
+ ```
543
+
452
544
  #### `remove nickname`
453
545
 
454
546
  Removes a specific nickname from a friend:
data/bin/friends CHANGED
@@ -18,6 +18,28 @@ version Friends::VERSION
18
18
  subcommand_option_handling :normal
19
19
  arguments :strict
20
20
 
21
+ class Hashtag
22
+ # @param str [String] of the form "hashtag" or "#hashtag"
23
+ # @return [String] the string, with whitespace stripped and a hashtag
24
+ # prepended if there isn't one already
25
+ # NOTE: This logic could be only in the accept block if GLI allowed type
26
+ # conversions for arguments.
27
+ # See: https://github.com/davetron5000/gli/issues/241
28
+ def self.convert_to_hashtag(str)
29
+ str = str.strip
30
+ str.size > 0 && str[0] == "#" ? str : "##{str}"
31
+ end
32
+ end
33
+
34
+ accept(Hashtag) do |value|
35
+ Hashtag.convert_to_hashtag(value)
36
+ end
37
+
38
+ class Stripped ; end
39
+ accept(Stripped) do |value|
40
+ value.strip
41
+ end
42
+
21
43
  switch [:quiet],
22
44
  negatable: false,
23
45
  desc: "Quiet output messages"
@@ -72,10 +94,75 @@ command :list do |list|
72
94
  list.command :friends do |list_friends|
73
95
  list_friends.flag [:in],
74
96
  arg_name: "LOCATION",
75
- desc: "List only friends in the given location"
97
+ desc: "List only friends in the given location",
98
+ type: Stripped
99
+
100
+ list_friends.flag [:tagged],
101
+ arg_name: "HASHTAG",
102
+ desc: "List only friends with the given hashtag",
103
+ type: Hashtag
104
+
105
+ list_friends.switch [:verbose],
106
+ negatable: false,
107
+ desc: "Output friend nicknames, locations, and hashtags"
76
108
 
77
109
  list_friends.action do |_, options|
78
- puts @introvert.list_friends(location_name: options[:in])
110
+ puts @introvert.list_friends(
111
+ location_name: options[:in],
112
+ tagged: options[:tagged],
113
+ verbose: options[:verbose]
114
+ )
115
+ end
116
+ end
117
+
118
+ list.desc "Lists all activities"
119
+ list.command :activities do |list_activities|
120
+ list_activities.flag [:limit],
121
+ arg_name: "NUMBER",
122
+ desc: "The number of activities to return",
123
+ default_value: 10,
124
+ type: Integer
125
+
126
+ list_activities.flag [:with],
127
+ arg_name: "NAME",
128
+ desc: "List only activities with the given friend",
129
+ type: Stripped
130
+
131
+ list_activities.flag [:in],
132
+ arg_name: "LOCATION",
133
+ desc: "List only activities in the given location",
134
+ type: Stripped
135
+
136
+ list_activities.flag [:tagged],
137
+ arg_name: "HASHTAG",
138
+ desc: "List only activities with the given hashtag",
139
+ type: Hashtag
140
+
141
+ list_activities.action do |_, options|
142
+ puts @introvert.list_activities(
143
+ limit: options[:limit],
144
+ with: options[:with],
145
+ location_name: options[:in],
146
+ tagged: options[:tagged]
147
+ )
148
+ end
149
+ end
150
+
151
+ list.desc "List all locations"
152
+ list.command :locations do |list_locations|
153
+ list_locations.action do
154
+ puts @introvert.list_locations
155
+ end
156
+ end
157
+
158
+ list.desc "List all hashtags used"
159
+ list.command :hashtags do |list_hashtags|
160
+ list_hashtags.flag [:from],
161
+ arg_name: '"activities" or "friends" (default: both)',
162
+ desc: "List only hashtags from activities or friends "\
163
+ "instead of both"
164
+ list_hashtags.action do |_, options|
165
+ puts @introvert.list_hashtags(from: options[:from])
79
166
  end
80
167
  end
81
168
 
@@ -85,12 +172,12 @@ command :list do |list|
85
172
  list_favorite.command :friends do |list_favorite_friends|
86
173
  list_favorite_friends.flag [:limit],
87
174
  arg_name: "NUMBER",
175
+ desc: "The number of friends to return",
88
176
  default_value: 10,
89
- desc: "The number of friends to return"
177
+ type: Integer
90
178
 
91
179
  list_favorite_friends.action do |_, options|
92
- limit = options[:limit].to_i
93
- favorites = @introvert.list_favorite_friends(limit: limit)
180
+ favorites = @introvert.list_favorite_friends(limit: options[:limit])
94
181
 
95
182
  if limit == 1
96
183
  puts "Your best friend is #{favorites.first}"
@@ -109,12 +196,12 @@ command :list do |list|
109
196
  list_favorite.command :locations do |list_favorite_locations|
110
197
  list_favorite_locations.flag [:limit],
111
198
  arg_name: "NUMBER",
199
+ desc: "The number of locations to return",
112
200
  default_value: 10,
113
- desc: "The number of locations to return"
201
+ type: Integer
114
202
 
115
203
  list_favorite_locations.action do |_, options|
116
- limit = options[:limit].to_i
117
- favorites = @introvert.list_favorite_locations(limit: limit)
204
+ favorites = @introvert.list_favorite_locations(limit: options[:limit])
118
205
 
119
206
  if limit == 1
120
207
  puts "Your favorite location is #{favorites.first}"
@@ -129,38 +216,6 @@ command :list do |list|
129
216
  end
130
217
  end
131
218
  end
132
-
133
- list.desc "Lists all activities"
134
- list.command :activities do |list_activities|
135
- list_activities.flag [:limit],
136
- arg_name: "NUMBER",
137
- default_value: 10,
138
- desc: "The number of activities to return"
139
-
140
- list_activities.flag [:with],
141
- arg_name: "NAME",
142
- desc: "List only activities involving the given friend"
143
-
144
- list_activities.flag [:in],
145
- arg_name: "LOCATION",
146
- desc: "List only activities in the given location"
147
-
148
- list_activities.action do |_, options|
149
- limit = options[:limit].to_i
150
- puts @introvert.list_activities(
151
- limit: limit,
152
- with: options[:with],
153
- location_name: options[:in]
154
- )
155
- end
156
- end
157
-
158
- list.desc "List all locations"
159
- list.command :locations do |list_locations|
160
- list_locations.action do
161
- puts @introvert.list_locations
162
- end
163
- end
164
219
  end
165
220
 
166
221
  desc "Adds a friend (or nickname), activity, or location"
@@ -183,11 +238,11 @@ command :add do |add|
183
238
 
184
239
  # If there's no description, prompt the user for one.
185
240
  if activity.description.nil? || activity.description.empty?
186
- activity.description = Readline.readline(activity.display_text)
241
+ activity.description = Readline.readline(activity.to_s)
187
242
  activity.highlight_description(introvert: @introvert)
188
243
  end
189
244
 
190
- @message = "Activity added: \"#{activity.display_text}\""
245
+ @message = "Activity added: \"#{activity}\""
191
246
  @dirty = true # Mark the file for cleaning.
192
247
  end
193
248
  end
@@ -211,6 +266,19 @@ command :add do |add|
211
266
  @dirty = true # Mark the file for cleaning.
212
267
  end
213
268
  end
269
+
270
+ add.desc "Adds a hashtag to a friend"
271
+ add.arg_name "NAME HASHTAG"
272
+ add.command :hashtag do |add_hashtag|
273
+ add_hashtag.action do |_, _, args|
274
+ friend = @introvert.add_hashtag(
275
+ name: args.first,
276
+ hashtag: Hashtag.convert_to_hashtag(args[1])
277
+ )
278
+ @message = "Hashtag added to friend: \"#{friend}\""
279
+ @dirty = true # Mark the file for cleaning.
280
+ end
281
+ end
214
282
  end
215
283
 
216
284
  desc "Set data about friends"
@@ -237,6 +305,19 @@ command :remove do |remove|
237
305
  @dirty = true # Mark the file for cleaning.
238
306
  end
239
307
  end
308
+
309
+ remove.desc "Removes a hashtag to a friend"
310
+ remove.arg_name "NAME HASHTAG"
311
+ remove.command :hashtag do |remove_hashtag|
312
+ remove_hashtag.action do |_, _, args|
313
+ friend = @introvert.remove_hashtag(
314
+ name: args.first,
315
+ hashtag: Hashtag.convert_to_hashtag(args[1])
316
+ )
317
+ @message = "Hashtag removed from friend: \"#{friend}\""
318
+ @dirty = true # Mark the file for cleaning.
319
+ end
320
+ end
240
321
  end
241
322
 
242
323
  desc "Graph all activities or a friend's relationship over time"
@@ -268,7 +349,8 @@ desc "Suggest friends to do activities with"
268
349
  command :suggest do |suggest|
269
350
  suggest.flag [:in],
270
351
  arg_name: "LOCATION",
271
- desc: "Suggest only friends in the given location"
352
+ desc: "Suggest only friends in the given location",
353
+ type: Stripped
272
354
 
273
355
  suggest.action do |_, options|
274
356
  suggestions = @introvert.suggest(location_name: options[:in])
data/friends.md CHANGED
@@ -1,13 +1,13 @@
1
1
  ### Activities:
2
- - 2015-11-01: **Grace Hopper** and I went to _Marie's Diner_. George had to cancel at the last minute.
3
- - 2015-01-04: Got lunch with **Grace Hopper** and **George Washington Carver**.
4
- - 2014-12-31: Celebrated the new year in _Paris_ with **Marie Curie**.
2
+ - 2015-11-01: **Grace Hopper** and I went to _Marie's Diner_. George had to cancel at the last minute. #food
3
+ - 2015-01-04: Got lunch with **Grace Hopper** and **George Washington Carver**. #food
4
+ - 2014-12-31: Celebrated the new year in _Paris_ with **Marie Curie**. #partying
5
5
  - 2014-11-15: Talked to **George Washington Carver** on the phone for an hour.
6
6
 
7
7
  ### Friends:
8
8
  - George Washington Carver
9
- - Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris]
10
- - Marie Curie [Atlantis]
9
+ - Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris] #navy #science
10
+ - Marie Curie [Atlantis] #science
11
11
 
12
12
  ### Locations:
13
13
  - Atlantis
data/lib/friends.rb CHANGED
@@ -2,4 +2,5 @@ require "friends/introvert"
2
2
  require "friends/version"
3
3
 
4
4
  module Friends
5
+ HASHTAG_REGEX = /#\p{Alnum}+/
5
6
  end
@@ -50,7 +50,7 @@ module Friends
50
50
  attr_accessor :description
51
51
 
52
52
  # @return [String] the command-line display text for the activity
53
- def display_text
53
+ def to_s
54
54
  date_s = Paint[date, :bold]
55
55
  description_s = description.to_s
56
56
  # rubocop:disable Lint/AssignmentInCondition
@@ -68,6 +68,10 @@ module Friends
68
68
  "#{Paint[match[1], :bold, :yellow]}"\
69
69
  "#{match.post_match}"
70
70
  end
71
+
72
+ description_s = description_s.
73
+ gsub(HASHTAG_REGEX, Paint['\0', :bold, :cyan])
74
+
71
75
  "#{date_s}: #{description_s}"
72
76
  end
73
77
 
@@ -120,6 +124,17 @@ module Friends
120
124
  friend_names.include? friend.name
121
125
  end
122
126
 
127
+ # @param hashtag [String] the hashtag to test, of the form "#hashtag"
128
+ # @return [Boolean] true iff this activity includes the given hashtag
129
+ def includes_hashtag?(hashtag:)
130
+ hashtags.include? hashtag
131
+ end
132
+
133
+ # @return [Set] all hashtags in this activity (including the "#")
134
+ def hashtags
135
+ Set.new(@description.scan(HASHTAG_REGEX))
136
+ end
137
+
123
138
  # Find the names of all friends in this description.
124
139
  # @return [Array] list of all friend names in the description
125
140
  def friend_names
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # Friend represents a friend. You know, a real-life friend!
3
3
 
4
+ require "friends"
4
5
  require "friends/regex_builder"
5
6
  require "friends/serializable"
6
7
 
@@ -15,7 +16,7 @@ module Friends
15
16
  def self.deserialization_regex
16
17
  # Note: this regex must be on one line because whitespace is important
17
18
  # rubocop:disable Metrics/LineLength
18
- /(#{SERIALIZATION_PREFIX})?(?<name>[^\(\[]+)(\((?<nickname_str>#{NICKNAME_PREFIX}.+)\))?\s?(\[(?<location_name>[^\]]+)\])?/
19
+ /(#{SERIALIZATION_PREFIX})?(?<name>[^\(\[#]*[^\(\[#\s])(\s+\(#{NICKNAME_PREFIX}(?<nickname_str>.+)\))?(\s+\[(?<location_name>[^\]]+)\])?(\s+(?<hashtags_str>(#{HASHTAG_REGEX}\s*)+))?/
19
20
  # rubocop:enable Metrics/LineLength
20
21
  end
21
22
 
@@ -25,16 +26,23 @@ module Friends
25
26
  end
26
27
 
27
28
  # @param name [String] the name of the friend
28
- def initialize(name:, nickname_str: nil, location_name: nil)
29
- @name = name.strip
29
+ def initialize(
30
+ name:,
31
+ nickname_str: nil,
32
+ location_name: nil,
33
+ hashtags_str: nil
34
+ )
35
+ @name = name
30
36
  @nicknames = nickname_str &&
31
- nickname_str.split(NICKNAME_PREFIX)[1..-1].map(&:strip) ||
37
+ nickname_str.split(" #{NICKNAME_PREFIX}") ||
32
38
  []
33
39
  @location_name = location_name
40
+ @hashtags = hashtags_str && hashtags_str.split(/\s+/) || []
34
41
  end
35
42
 
36
43
  attr_accessor :name
37
44
  attr_accessor :location_name
45
+ attr_reader :hashtags
38
46
 
39
47
  # @return [String] the file serialization text for the friend
40
48
  def serialize
@@ -52,10 +60,28 @@ module Friends
52
60
 
53
61
  location_str = " [#{@location_name}]" unless @location_name.nil?
54
62
 
55
- "#{@name}#{nickname_str}#{location_str}"
63
+ hashtag_str = " #{@hashtags.join(' ')}" unless @hashtags.empty?
64
+
65
+ "#{@name}#{nickname_str}#{location_str}#{hashtag_str}"
66
+ end
67
+
68
+ # Adds a hashtag, ignoring duplicates.
69
+ # @param hashtag [String] the hashtag to add
70
+ def add_hashtag(hashtag)
71
+ @hashtags << hashtag
72
+ @hashtags.uniq!
73
+ end
74
+
75
+ # @param hashtag [String] the hashtag to remove
76
+ def remove_hashtag(hashtag)
77
+ unless @hashtags.include? hashtag
78
+ raise FriendsError, "Hashtag \"#{hashtag}\" not found for \"#{name}\""
79
+ end
80
+
81
+ @hashtags.delete(hashtag)
56
82
  end
57
83
 
58
- # Adds a nickname, avoiding duplicates and stripping surrounding whitespace.
84
+ # Adds a nickname, ignoring duplicates.
59
85
  # @param nickname [String] the nickname to add
60
86
  def add_nickname(nickname)
61
87
  @nicknames << nickname
@@ -63,7 +89,7 @@ module Friends
63
89
  end
64
90
 
65
91
  # @param nickname [String] the nickname to remove
66
- # @return [Boolean] true if the nickname was present, false otherwise
92
+ # @raise [FriendsError] if the friend does not have the given nickname
67
93
  def remove_nickname(nickname)
68
94
  unless @nicknames.include? nickname
69
95
  raise FriendsError, "Nickname \"#{nickname}\" not found for \"#{name}\""
@@ -116,9 +116,6 @@ module Friends
116
116
  # @raise [FriendsError] if 0 or 2+ friends match the given name
117
117
  # @return [Friend] the existing friend
118
118
  def rename_friend(old_name:, new_name:)
119
- old_name.strip!
120
- new_name.strip!
121
-
122
119
  friend = friend_with_name_in(old_name)
123
120
  @activities.each do |activity|
124
121
  activity.update_friend_name(old_name: friend.name, new_name: new_name)
@@ -133,9 +130,6 @@ module Friends
133
130
  # @raise [FriendsError] if 0 or 2+ friends match the given name
134
131
  # @return [Location] the existing location
135
132
  def rename_location(old_name:, new_name:)
136
- old_name.strip!
137
- new_name.strip!
138
-
139
133
  loc = location_with_name_in(old_name)
140
134
 
141
135
  # Update locations in activities.
@@ -159,27 +153,54 @@ module Friends
159
153
  # @return [Friend] the existing friend
160
154
  def add_nickname(name:, nickname:)
161
155
  friend = friend_with_name_in(name)
162
- friend.add_nickname(nickname.strip)
156
+ friend.add_nickname(nickname)
157
+ friend
158
+ end
159
+
160
+ # Add a hashtag to an existing friend.
161
+ # @param name [String] the name of the friend
162
+ # @param hashtag [String] the hashtag to add to the friend
163
+ # @raise [FriendsError] if 0 or 2+ friends match the given name
164
+ # @return [Friend] the existing friend
165
+ def add_hashtag(name:, hashtag:)
166
+ friend = friend_with_name_in(name)
167
+ friend.add_hashtag(hashtag)
168
+ friend
169
+ end
170
+
171
+ # Remove a hashtag from an existing friend.
172
+ # @param name [String] the name of the friend
173
+ # @param hashtag [String] the hashtag to remove from the friend
174
+ # @raise [FriendsError] if 0 or 2+ friends match the given name
175
+ # @raise [FriendsError] if the friend does not have the given nickname
176
+ # @return [Friend] the existing friend
177
+ def remove_hashtag(name:, hashtag:)
178
+ friend = friend_with_name_in(name)
179
+ friend.remove_hashtag(hashtag)
163
180
  friend
164
181
  end
165
182
 
166
- # Remove a nickname from an existing friend and write out the new friends
167
- # file.
183
+ # Remove a nickname from an existing friend.
168
184
  # @param name [String] the name of the friend
169
185
  # @param nickname [String] the nickname to remove from the friend
170
186
  # @raise [FriendsError] if 0 or 2+ friends match the given name
187
+ # @raise [FriendsError] if the friend does not have the given nickname
171
188
  # @return [Friend] the existing friend
172
189
  def remove_nickname(name:, nickname:)
173
190
  friend = friend_with_name_in(name)
174
- friend.remove_nickname(nickname.strip)
191
+ friend.remove_nickname(nickname)
175
192
  friend
176
193
  end
177
194
 
178
195
  # List all friend names in the friends file.
179
196
  # @param location_name [String] the name of a location to filter by, or nil
180
197
  # for unfiltered
198
+ # @param tagged [String] the name of a hashtag to filter by, or nil for
199
+ # unfiltered
200
+ # @param verbose [Boolean] true iff we should output friend names with
201
+ # nicknames, locations, and hashtags; false for names only
181
202
  # @return [Array] a list of all friend names
182
- def list_friends(location_name:)
203
+ def list_friends(location_name:, tagged:, verbose:)
183
204
  fs = @friends
184
205
 
185
206
  # Filter by location if a name is passed.
@@ -188,7 +209,10 @@ module Friends
188
209
  fs = fs.select { |friend| friend.location_name == location.name }
189
210
  end
190
211
 
191
- fs.map(&:name)
212
+ # Filter by hashtag if one is passed.
213
+ fs = fs.select { |friend| friend.hashtags.include? tagged } if tagged
214
+
215
+ verbose ? fs.map(&:to_s) : fs.map(&:name)
192
216
  end
193
217
 
194
218
  # List your favorite friends.
@@ -214,9 +238,11 @@ module Friends
214
238
  # unfiltered
215
239
  # @param location_name [String] the name of a location to filter by, or nil
216
240
  # for unfiltered
241
+ # @param tagged [String] the name of a hashtag to filter by, or nil for
242
+ # unfiltered
217
243
  # @return [Array] a list of all activity text values
218
244
  # @raise [FriendsError] if 0 or 2+ friends match the given `with` text
219
- def list_activities(limit:, with:, location_name:)
245
+ def list_activities(limit:, with:, location_name:, tagged:)
220
246
  acts = @activities
221
247
 
222
248
  # Filter by friend name if argument is passed.
@@ -231,10 +257,15 @@ module Friends
231
257
  acts = acts.select { |act| act.includes_location?(location: location) }
232
258
  end
233
259
 
260
+ # Filter by tag if argument is passed.
261
+ unless tagged.nil?
262
+ acts = acts.select { |act| act.includes_hashtag?(hashtag: tagged) }
263
+ end
264
+
234
265
  # If we need to, trim the list.
235
266
  acts = acts.take(limit) unless limit.nil?
236
267
 
237
- acts.map(&:display_text)
268
+ acts.map(&:to_s)
238
269
  end
239
270
 
240
271
  # List all location names in the friends file.
@@ -243,6 +274,28 @@ module Friends
243
274
  @locations.map(&:name)
244
275
  end
245
276
 
277
+ # @param from [String] one of: ["activities", "friends", nil]
278
+ # If not nil, limits the hashtags returned to only those from either
279
+ # activities or friends.
280
+ # @return [Array] a sorted list of all hashtags in activity descriptions
281
+ def list_hashtags(from:)
282
+ output = Set.new
283
+
284
+ unless from == "friends" # If from is "activities" or nil.
285
+ @activities.each_with_object(output) do |activity, set|
286
+ set.merge(activity.hashtags)
287
+ end
288
+ end
289
+
290
+ unless from == "activities" # If from is "friends" or nil.
291
+ @friends.each_with_object(output) do |friend, set|
292
+ set.merge(friend.hashtags)
293
+ end
294
+ end
295
+
296
+ output.sort_by(&:downcase)
297
+ end
298
+
246
299
  # Find data points for graphing activities over time.
247
300
  # Optionally filter by a friend to see a given relationship over time.
248
301
  #
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Friends
3
- VERSION = "0.19".freeze
3
+ VERSION = "0.20".freeze
4
4
  end
@@ -95,8 +95,8 @@ describe Friends::Activity do
95
95
  it { subject.description.must_equal description }
96
96
  end
97
97
 
98
- describe "#display_text" do
99
- subject { activity.display_text }
98
+ describe "#to_s" do
99
+ subject { activity.to_s }
100
100
 
101
101
  it do
102
102
  subject.
@@ -456,6 +456,40 @@ describe Friends::Activity do
456
456
  end
457
457
  end
458
458
 
459
+ describe "#hashtags" do
460
+ subject { activity.hashtags }
461
+
462
+ describe "when the activity has no hashtags" do
463
+ let(:activity) { Friends::Activity.new(str: "Enormous ball pit!") }
464
+ it { subject.must_be :empty? }
465
+ end
466
+
467
+ describe "when the activity has hashtags" do
468
+ let(:activity) { Friends::Activity.new(str: "Party! #fun #crazy #fun") }
469
+ it { subject.must_equal Set.new(["#fun", "#crazy"]) }
470
+ end
471
+ end
472
+
473
+ describe "#includes_hashtag?" do
474
+ subject { activity.includes_hashtag?(hashtag: hashtag) }
475
+ let(:activity) { Friends::Activity.new(str: "Enormous ball pit! #fun") }
476
+
477
+ describe "when the given hashtag is not in the activity" do
478
+ let(:hashtag) { "#garbage" }
479
+ it { subject.must_equal false }
480
+ end
481
+
482
+ describe "when the given word is in the activity but not as a hashtag" do
483
+ let(:hashtag) { "#ball" }
484
+ it { subject.must_equal false }
485
+ end
486
+
487
+ describe "when the given hashtag is in the activity" do
488
+ let(:hashtag) { "#fun" }
489
+ it { subject.must_equal true }
490
+ end
491
+ end
492
+
459
493
  describe "#friend_names" do
460
494
  subject { activity.friend_names }
461
495
 
data/test/friend_spec.rb CHANGED
@@ -62,7 +62,7 @@ describe Friends::Friend do
62
62
 
63
63
  describe "when the nickname is present" do
64
64
  let(:friend) do
65
- Friends::Friend.new(name: friend_name, nickname_str: "a.k.a. Jake")
65
+ Friends::Friend.new(name: friend_name, nickname_str: "Jake")
66
66
  end
67
67
 
68
68
  it "removes the nickname" do
@@ -79,6 +79,45 @@ describe Friends::Friend do
79
79
  end
80
80
  end
81
81
 
82
+ describe "#add_hashtag" do
83
+ subject { friend.add_hashtag("#college") }
84
+
85
+ it "adds the nickname" do
86
+ subject
87
+ friend.hashtags.must_include("#college")
88
+ end
89
+
90
+ it "does not keep duplicates" do
91
+ # Add the same nickname twice. Do not use `subject` because it's memoized.
92
+ friend.add_hashtag("#college")
93
+ friend.add_hashtag("#college")
94
+
95
+ friend.hashtags.must_equal ["#college"]
96
+ end
97
+ end
98
+
99
+ describe "#remove_hashtag" do
100
+ subject { friend.remove_hashtag("#school") }
101
+
102
+ describe "when the hashtag is present" do
103
+ let(:friend) do
104
+ Friends::Friend.new(name: friend_name, hashtags_str: "#school #work")
105
+ end
106
+
107
+ it "removes the nickname" do
108
+ friend.instance_variable_get(:@hashtags).must_equal ["#school", "#work"]
109
+ subject
110
+ friend.instance_variable_get(:@hashtags).must_equal ["#work"]
111
+ end
112
+ end
113
+
114
+ describe "when the nickname is not present" do
115
+ it "raises an error if the nickname is not found" do
116
+ proc { subject }.must_raise Friends::FriendsError
117
+ end
118
+ end
119
+ end
120
+
82
121
  describe "#n_activities" do
83
122
  subject { friend.n_activities }
84
123
 
@@ -34,7 +34,11 @@ describe Friends::Introvert do
34
34
  let(:args) { { filename: filename } }
35
35
  let(:introvert) { Friends::Introvert.new(args) }
36
36
  let(:friend_names) { ["George Washington Carver", "Betsy Ross"] }
37
- let(:friends) { friend_names.map { |name| Friends::Friend.new(name: name) } }
37
+ let(:friends) do
38
+ friend_names.map do |name|
39
+ Friends::Friend.new(name: name, hashtags_str: "#test")
40
+ end
41
+ end
38
42
  let(:activities) do
39
43
  [
40
44
  Friends::Activity.new(
@@ -110,20 +114,18 @@ describe Friends::Introvert do
110
114
  end
111
115
 
112
116
  describe "#list_friends" do
113
- subject { introvert.list_friends(location_name: location_name) }
114
-
115
- describe "when no location name has been passed" do
116
- let(:location_name) { nil }
117
-
118
- it "lists the names of friends" do
119
- stub_friends(friends) do
120
- subject.must_equal friend_names
121
- end
122
- end
117
+ subject do
118
+ introvert.list_friends(
119
+ location_name: location_name,
120
+ tagged: tagged,
121
+ verbose: verbose
122
+ )
123
123
  end
124
124
 
125
125
  describe "when a location name has been passed" do
126
126
  let(:location_name) { "Atlantis" }
127
+ let(:tagged) { nil }
128
+ let(:verbose) { false }
127
129
  let(:friends) do
128
130
  [
129
131
  Friends::Friend.new(name: "Mark Watney", location_name: "Mars"),
@@ -133,7 +135,29 @@ describe Friends::Introvert do
133
135
  ]
134
136
  end
135
137
 
136
- it "lists the names of friends" do
138
+ it "lists the names of friends in that location" do
139
+ stub_friends(friends) do
140
+ stub_locations(locations) do
141
+ subject.must_equal ["Aquaman", "Shark-Boy"]
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ describe "when a hashtag has been passed" do
148
+ let(:location_name) { nil }
149
+ let(:tagged) { "#superhero" }
150
+ let(:verbose) { false }
151
+ let(:friends) do
152
+ [
153
+ Friends::Friend.new(name: "Mark Watney"),
154
+ Friends::Friend.new(name: "Aquaman", hashtags_str: "#superhero"),
155
+ Friends::Friend.new(name: "Shark-Boy", hashtags_str: "#superhero"),
156
+ Friends::Friend.new(name: "Ms. Nowhere")
157
+ ]
158
+ end
159
+
160
+ it "lists the names of friends in that location" do
137
161
  stub_friends(friends) do
138
162
  stub_locations(locations) do
139
163
  subject.must_equal ["Aquaman", "Shark-Boy"]
@@ -141,6 +165,32 @@ describe Friends::Introvert do
141
165
  end
142
166
  end
143
167
  end
168
+
169
+ describe "when not verbose" do
170
+ let(:verbose) { false }
171
+ let(:location_name) { nil }
172
+ let(:tagged) { nil }
173
+ it "lists the names of friends" do
174
+ stub_friends(friends) do
175
+ subject.must_equal friend_names
176
+ end
177
+ end
178
+ end
179
+
180
+ describe "when verbose" do
181
+ let(:verbose) { true }
182
+ let(:location_name) { nil }
183
+ let(:tagged) { nil }
184
+ it "lists the names and details of friends" do
185
+ stub_friends(friends) do
186
+ # Just check that there's a difference between the verbose and
187
+ # non-verbose versions of friends (otherwise our test is useless).
188
+ subject.wont_equal friend_names
189
+
190
+ subject.must_equal friends.map(&:to_s)
191
+ end
192
+ end
193
+ end
144
194
  end
145
195
 
146
196
  describe "#add_friend" do
@@ -151,7 +201,9 @@ describe Friends::Introvert do
151
201
  it "adds the given friend" do
152
202
  stub_friends(friends) do
153
203
  subject
154
- introvert.list_friends(location_name: nil).
204
+ introvert.
205
+ instance_variable_get(:@friends).
206
+ map(&:name).
155
207
  must_include new_friend_name
156
208
  end
157
209
  end
@@ -214,17 +266,77 @@ describe Friends::Introvert do
214
266
  end
215
267
  end
216
268
 
269
+ describe "#list_hashtags" do
270
+ subject { introvert.list_hashtags(from: from) }
271
+
272
+ let(:activities) do
273
+ [
274
+ Friends::Activity.new(str: "Lunch in the park. #picnic #food"),
275
+ Friends::Activity.new(str: "Fancy dinner. #food #swanky")
276
+ ]
277
+ end
278
+
279
+ let(:friends) do
280
+ [
281
+ Friends::Friend.new(name: "Grace Hopper", hashtags_str: "#coder #navy"),
282
+ Friends::Friend.new(name: "James Bond", hashtags_str: "#cool #swanky")
283
+ ]
284
+ end
285
+
286
+ describe "when from flag is nil" do
287
+ let(:from) { nil }
288
+ it "lists all hashtags in sorted order" do
289
+ stub_activities(activities) do
290
+ stub_friends(friends) do
291
+ subject.must_equal [
292
+ "#coder",
293
+ "#cool",
294
+ "#food",
295
+ "#navy",
296
+ "#picnic",
297
+ "#swanky"
298
+ ]
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ describe 'when from flag is "activities"' do
305
+ let(:from) { "activities" }
306
+ it "lists all activity hashtags in sorted order" do
307
+ stub_activities(activities) do
308
+ stub_friends(friends) do
309
+ subject.must_equal ["#food", "#picnic", "#swanky"]
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ describe 'when from flag is "friends"' do
316
+ let(:from) { "friends" }
317
+ it "lists all friend hashtags in sorted order" do
318
+ stub_activities(activities) do
319
+ stub_friends(friends) do
320
+ subject.must_equal ["#coder", "#cool", "#navy", "#swanky"]
321
+ end
322
+ end
323
+ end
324
+ end
325
+ end
326
+
217
327
  describe "#list_activities" do
218
328
  subject do
219
329
  introvert.list_activities(
220
330
  limit: limit,
221
331
  with: with,
222
- location_name: location_name
332
+ location_name: location_name,
333
+ tagged: tagged
223
334
  )
224
335
  end
225
336
  let(:limit) { nil }
226
337
  let(:with) { nil }
227
338
  let(:location_name) { nil }
339
+ let(:tagged) { nil }
228
340
 
229
341
  describe "when the limit is lower than the number of activities" do
230
342
  let(:limit) { 1 }
@@ -271,7 +383,7 @@ describe Friends::Introvert do
271
383
 
272
384
  it "lists the activities" do
273
385
  stub_activities(activities) do
274
- subject.must_equal activities.map(&:display_text)
386
+ subject.must_equal activities.map(&:to_s)
275
387
  end
276
388
  end
277
389
  end
@@ -308,7 +420,7 @@ describe Friends::Introvert do
308
420
  stub_friends(friends) do
309
421
  stub_activities(activities) do
310
422
  # Only one activity has that friend.
311
- subject.must_equal activities[0..0].map(&:display_text)
423
+ subject.must_equal activities[0..0].map(&:to_s)
312
424
  end
313
425
  end
314
426
  end
@@ -320,7 +432,7 @@ describe Friends::Introvert do
320
432
 
321
433
  it "lists the activities" do
322
434
  stub_activities(activities) do
323
- subject.must_equal activities.map(&:display_text)
435
+ subject.must_equal activities.map(&:to_s)
324
436
  end
325
437
  end
326
438
  end
@@ -375,13 +487,60 @@ describe Friends::Introvert do
375
487
  stub_locations(locations) do
376
488
  stub_activities(activities) do
377
489
  # Only one activity has that friend.
378
- subject.must_equal activities[0..0].map(&:display_text)
490
+ subject.must_equal activities[0..0].map(&:to_s)
379
491
  end
380
492
  end
381
493
  end
382
494
  end
383
495
  end
384
496
  end
497
+
498
+ describe "when not filtering by a hashtag" do
499
+ let(:tagged) { nil }
500
+
501
+ it "lists the activities" do
502
+ stub_activities(activities) do
503
+ subject.must_equal activities.map(&:to_s)
504
+ end
505
+ end
506
+ end
507
+
508
+ describe "when filtering by a hashtag" do
509
+ let(:activities) do
510
+ [
511
+ Friends::Activity.new(str: "Tennis after work. #exercise #tennis"),
512
+ Friends::Activity.new(str: "Wimbledon! #tennis"),
513
+ Friends::Activity.new(str: "Drinks after work. #beer")
514
+ ]
515
+ end
516
+
517
+ describe "when the hashtag ('#hashtag') is not used at all" do
518
+ let(:tagged) { "#garbage" }
519
+ it "returns no results" do
520
+ stub_activities(activities) do
521
+ subject.must_equal []
522
+ end
523
+ end
524
+ end
525
+
526
+ describe "when the hashtag ('#hashtag') is used once" do
527
+ let(:tagged) { "#beer" }
528
+ it "returns the activity with that hashtag" do
529
+ stub_activities(activities) do
530
+ subject.must_equal [activities.last.to_s]
531
+ end
532
+ end
533
+ end
534
+
535
+ describe "when the hashtag ('#hashtag') is used multiple times" do
536
+ let(:tagged) { "#tennis" }
537
+ it "returns the activities with that hashtag" do
538
+ stub_activities(activities) do
539
+ subject.must_equal activities[0..1].map(&:to_s)
540
+ end
541
+ end
542
+ end
543
+ end
385
544
  end
386
545
 
387
546
  describe "#add_activity" do
@@ -426,24 +585,6 @@ describe Friends::Introvert do
426
585
  end
427
586
  end
428
587
  end
429
-
430
- describe "when given names with leading and trailing spaces" do
431
- let(:new_name) { " David Bowie " }
432
- let(:old_name) { friend_names.last + " " }
433
- subject do
434
- introvert.rename_friend(old_name: old_name, new_name: new_name)
435
- end
436
-
437
- it "correctly strips the spaces" do
438
- stub_friends(friends) do
439
- stub_activities(activities) do
440
- subject
441
- introvert.activities.first.description.must_include "David Bowie"
442
- introvert.activities.last.description.must_include "David Bowie"
443
- end
444
- end
445
- end
446
- end
447
588
  end
448
589
 
449
590
  describe "#rename_location" do
@@ -498,25 +639,6 @@ describe Friends::Introvert do
498
639
  end
499
640
  end
500
641
  end
501
-
502
- describe "when given names with leading and trailing spaces" do
503
- let(:new_name) { " Paris, France " }
504
- let(:old_name) { " Paris " }
505
- subject do
506
- introvert.rename_location(old_name: old_name, new_name: new_name)
507
- end
508
-
509
- it "correctly strips the spaces" do
510
- stub_locations(locations) do
511
- stub_activities(activities) do
512
- subject
513
- introvert.activities.map do |activity|
514
- activity.description.include? new_name
515
- end.must_equal [true, true, false]
516
- end
517
- end
518
- end
519
- end
520
642
  end
521
643
 
522
644
  describe "#set_location" do
@@ -554,8 +676,32 @@ describe Friends::Introvert do
554
676
  end
555
677
 
556
678
  it "returns the modified friend" do
557
- friend = Friends::Friend.new(name: "Jeff",
558
- nickname_str: "a.k.a. The Dude")
679
+ friend = Friends::Friend.new(name: "Jeff", nickname_str: "The Dude")
680
+ stub_friends([friend]) do
681
+ subject.must_equal friend
682
+ end
683
+ end
684
+ end
685
+
686
+ describe "#add_hashtag" do
687
+ subject do
688
+ introvert.add_hashtag(name: friend_names.first, hashtag: "#school")
689
+ end
690
+
691
+ it "returns the modified friend" do
692
+ stub_friends(friends) do
693
+ subject.must_equal friends.first
694
+ end
695
+ end
696
+ end
697
+
698
+ describe "#remove_hashtag" do
699
+ subject do
700
+ introvert.remove_hashtag(name: "Jeff", hashtag: "#school")
701
+ end
702
+
703
+ it "returns the modified friend" do
704
+ friend = Friends::Friend.new(name: "Jeff", hashtags_str: "#school")
559
705
  stub_friends([friend]) do
560
706
  subject.must_equal friend
561
707
  end
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.19'
4
+ version: '0.20'
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-02 00:00:00.000000000 Z
11
+ date: 2016-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chronic