friends 0.39 → 0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +17 -2
- data/README.md +93 -95
- data/bin/friends +13 -4
- data/friends.gemspec +15 -10
- data/lib/friends/commands/edit.rb +1 -4
- data/lib/friends/commands/list.rb +4 -23
- data/lib/friends/event.rb +2 -0
- data/lib/friends/graph.rb +6 -5
- data/lib/friends/introvert.rb +65 -98
- data/lib/friends/version.rb +1 -1
- data/test/commands/graph_spec.rb +94 -94
- data/test/commands/list/activities_spec.rb +0 -21
- data/test/commands/list/favorite/friends_spec.rb +0 -48
- data/test/commands/list/favorite/locations_spec.rb +0 -53
- data/test/commands/list/notes_spec.rb +0 -21
- data/test/paging_spec.rb +34 -0
- metadata +21 -4
|
@@ -10,10 +10,7 @@ command :edit do |edit|
|
|
|
10
10
|
|
|
11
11
|
# Mark the file for cleaning once the editor was closed correctly.
|
|
12
12
|
if Kernel.system("#{editor} #{filename}")
|
|
13
|
-
@introvert = Friends::Introvert.new(
|
|
14
|
-
filename: global_options[:filename],
|
|
15
|
-
quiet: global_options[:quiet]
|
|
16
|
-
)
|
|
13
|
+
@introvert = Friends::Introvert.new(filename: global_options[:filename])
|
|
17
14
|
@clean_command = true
|
|
18
15
|
@dirty = true
|
|
19
16
|
elsif !global_options[:quiet]
|
|
@@ -31,12 +31,6 @@ command :list do |list|
|
|
|
31
31
|
[:activities, :notes].each do |events|
|
|
32
32
|
list.desc "Lists all #{events}"
|
|
33
33
|
list.command events do |list_events|
|
|
34
|
-
list_events.flag [:limit],
|
|
35
|
-
arg_name: "NUMBER",
|
|
36
|
-
desc: "The number of #{events} to return",
|
|
37
|
-
default_value: 10,
|
|
38
|
-
type: Integer
|
|
39
|
-
|
|
40
34
|
list_events.flag [:with],
|
|
41
35
|
arg_name: "NAME",
|
|
42
36
|
desc: "List only #{events} with the given friend",
|
|
@@ -67,7 +61,6 @@ command :list do |list|
|
|
|
67
61
|
list_events.action do |_, options|
|
|
68
62
|
@introvert.send(
|
|
69
63
|
"list_#{events}",
|
|
70
|
-
limit: options[:limit],
|
|
71
64
|
with: options[:with],
|
|
72
65
|
location_name: options[:in],
|
|
73
66
|
tagged: options[:tagged],
|
|
@@ -101,27 +94,15 @@ command :list do |list|
|
|
|
101
94
|
list.command :favorite do |list_favorite|
|
|
102
95
|
list_favorite.desc "List favorite friends"
|
|
103
96
|
list_favorite.command :friends do |list_favorite_friends|
|
|
104
|
-
list_favorite_friends.
|
|
105
|
-
|
|
106
|
-
desc: "The number of friends to return",
|
|
107
|
-
default_value: 10,
|
|
108
|
-
type: Integer
|
|
109
|
-
|
|
110
|
-
list_favorite_friends.action do |_, options|
|
|
111
|
-
@introvert.list_favorite_friends(limit: options[:limit])
|
|
97
|
+
list_favorite_friends.action do
|
|
98
|
+
@introvert.list_favorite_friends
|
|
112
99
|
end
|
|
113
100
|
end
|
|
114
101
|
|
|
115
102
|
list_favorite.desc "List favorite locations"
|
|
116
103
|
list_favorite.command :locations do |list_favorite_locations|
|
|
117
|
-
list_favorite_locations.
|
|
118
|
-
|
|
119
|
-
desc: "The number of locations to return",
|
|
120
|
-
default_value: 10,
|
|
121
|
-
type: Integer
|
|
122
|
-
|
|
123
|
-
list_favorite_locations.action do |_, options|
|
|
124
|
-
@introvert.list_favorite_locations(limit: options[:limit])
|
|
104
|
+
list_favorite_locations.action do
|
|
105
|
+
@introvert.list_favorite_locations
|
|
125
106
|
end
|
|
126
107
|
end
|
|
127
108
|
end
|
data/lib/friends/event.rb
CHANGED
|
@@ -291,7 +291,9 @@ module Friends
|
|
|
291
291
|
def description_matches(regex:, replace:, indicator:)
|
|
292
292
|
# rubocop:disable Lint/AssignmentInCondition
|
|
293
293
|
return unless match = @description.match(regex) # Abort if no match.
|
|
294
|
+
|
|
294
295
|
# rubocop:enable Lint/AssignmentInCondition
|
|
296
|
+
|
|
295
297
|
str = yield # It's important to execute the block even if not replacing.
|
|
296
298
|
return unless replace # Only continue if we want to replace text.
|
|
297
299
|
|
data/lib/friends/graph.rb
CHANGED
|
@@ -17,9 +17,9 @@ module Friends
|
|
|
17
17
|
@end_date = @all_activities.first.date
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
def
|
|
22
|
-
to_h.
|
|
20
|
+
# @return [Array<String>] the output to print, with colors
|
|
21
|
+
def output
|
|
22
|
+
to_h.map do |month, (filtered_count, total_count)|
|
|
23
23
|
str = "#{month} |"
|
|
24
24
|
str += Array.new(filtered_count) do |count|
|
|
25
25
|
Paint["█", color(count)]
|
|
@@ -29,8 +29,9 @@ module Friends
|
|
|
29
29
|
Paint["∙", color(filtered_count + count)]
|
|
30
30
|
end.join + Paint["|", color(total_count + 1)]
|
|
31
31
|
end
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
|
|
33
|
+
str
|
|
34
|
+
end.reverse!
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
private
|
data/lib/friends/introvert.rb
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# the command-line script explicitly.
|
|
6
6
|
|
|
7
7
|
require "set"
|
|
8
|
+
require "tty/pager"
|
|
8
9
|
|
|
9
10
|
require "friends/activity"
|
|
10
11
|
require "friends/note"
|
|
@@ -21,16 +22,17 @@ module Friends
|
|
|
21
22
|
LOCATIONS_HEADER = "### Locations:".freeze
|
|
22
23
|
|
|
23
24
|
# @param filename [String] the name of the friends Markdown file
|
|
24
|
-
|
|
25
|
-
def initialize(filename:, quiet:)
|
|
25
|
+
def initialize(filename:)
|
|
26
26
|
@filename = filename
|
|
27
|
-
@
|
|
27
|
+
@output = []
|
|
28
28
|
|
|
29
29
|
# Read in the input file. It's easier to do this now and optimize later
|
|
30
30
|
# than try to overly be clever about what we read and write.
|
|
31
31
|
read_file
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
attr_reader :output
|
|
35
|
+
|
|
34
36
|
# Write out the friends file with cleaned/sorted data.
|
|
35
37
|
# @param clean_command [Boolean] true iff the command the user
|
|
36
38
|
# executed is `friends clean`; false if this is called as the
|
|
@@ -73,7 +75,7 @@ module Friends
|
|
|
73
75
|
|
|
74
76
|
# This is a special-case piece of code that lets us print a message that
|
|
75
77
|
# includes the filename when `friends clean` is called.
|
|
76
|
-
|
|
78
|
+
@output << "File cleaned: \"#{@filename}\"" if clean_command
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
# Add a friend.
|
|
@@ -88,7 +90,7 @@ module Friends
|
|
|
88
90
|
|
|
89
91
|
@friends << friend
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
@output << "Friend added: \"#{friend.name}\""
|
|
92
94
|
end
|
|
93
95
|
|
|
94
96
|
# Add an activity.
|
|
@@ -106,7 +108,7 @@ module Friends
|
|
|
106
108
|
|
|
107
109
|
@activities.unshift(activity)
|
|
108
110
|
|
|
109
|
-
|
|
111
|
+
@output << "Activity added: \"#{activity}\""
|
|
110
112
|
end
|
|
111
113
|
end
|
|
112
114
|
|
|
@@ -125,7 +127,7 @@ module Friends
|
|
|
125
127
|
|
|
126
128
|
@notes.unshift(note)
|
|
127
129
|
|
|
128
|
-
|
|
130
|
+
@output << "Note added: \"#{note}\""
|
|
129
131
|
end
|
|
130
132
|
end
|
|
131
133
|
|
|
@@ -141,7 +143,7 @@ module Friends
|
|
|
141
143
|
|
|
142
144
|
@locations << location
|
|
143
145
|
|
|
144
|
-
|
|
146
|
+
@output << "Location added: \"#{location.name}\"" # Return the added location.
|
|
145
147
|
end
|
|
146
148
|
|
|
147
149
|
# Set a friend's location.
|
|
@@ -154,7 +156,7 @@ module Friends
|
|
|
154
156
|
location = thing_with_name_in(:location, location_name)
|
|
155
157
|
friend.location_name = location.name
|
|
156
158
|
|
|
157
|
-
|
|
159
|
+
@output << "#{friend.name}'s location set to: \"#{location.name}\""
|
|
158
160
|
end
|
|
159
161
|
|
|
160
162
|
# Rename an existing friend.
|
|
@@ -168,7 +170,7 @@ module Friends
|
|
|
168
170
|
end
|
|
169
171
|
friend.name = new_name
|
|
170
172
|
|
|
171
|
-
|
|
173
|
+
@output << "Name changed: \"#{friend}\""
|
|
172
174
|
end
|
|
173
175
|
|
|
174
176
|
# Rename an existing location.
|
|
@@ -190,7 +192,7 @@ module Friends
|
|
|
190
192
|
|
|
191
193
|
loc.name = new_name # Update location itself.
|
|
192
194
|
|
|
193
|
-
|
|
195
|
+
@output << "Location renamed: \"#{loc.name}\""
|
|
194
196
|
end
|
|
195
197
|
|
|
196
198
|
# Add a nickname to an existing friend.
|
|
@@ -203,7 +205,7 @@ module Friends
|
|
|
203
205
|
friend = thing_with_name_in(:friend, name)
|
|
204
206
|
friend.add_nickname(nickname)
|
|
205
207
|
|
|
206
|
-
|
|
208
|
+
@output << "Nickname added: \"#{friend}\""
|
|
207
209
|
end
|
|
208
210
|
|
|
209
211
|
# Add a tag to an existing friend.
|
|
@@ -216,7 +218,7 @@ module Friends
|
|
|
216
218
|
friend = thing_with_name_in(:friend, name)
|
|
217
219
|
friend.add_tag(tag)
|
|
218
220
|
|
|
219
|
-
|
|
221
|
+
@output << "Tag added to friend: \"#{friend}\""
|
|
220
222
|
end
|
|
221
223
|
|
|
222
224
|
# Remove a tag from an existing friend.
|
|
@@ -228,7 +230,7 @@ module Friends
|
|
|
228
230
|
friend = thing_with_name_in(:friend, name)
|
|
229
231
|
friend.remove_tag(tag)
|
|
230
232
|
|
|
231
|
-
|
|
233
|
+
@output << "Tag removed from friend: \"#{friend}\""
|
|
232
234
|
end
|
|
233
235
|
|
|
234
236
|
# Remove a nickname from an existing friend.
|
|
@@ -240,7 +242,7 @@ module Friends
|
|
|
240
242
|
friend = thing_with_name_in(:friend, name)
|
|
241
243
|
friend.remove_nickname(nickname)
|
|
242
244
|
|
|
243
|
-
|
|
245
|
+
@output << "Nickname removed: \"#{friend}\""
|
|
244
246
|
end
|
|
245
247
|
|
|
246
248
|
# List all friend names in the friends file.
|
|
@@ -266,19 +268,17 @@ module Friends
|
|
|
266
268
|
end
|
|
267
269
|
end
|
|
268
270
|
|
|
269
|
-
|
|
271
|
+
(verbose ? fs.map(&:to_s) : fs.map(&:name)).each { |line| @output << line }
|
|
270
272
|
end
|
|
271
273
|
|
|
272
274
|
# List your favorite friends.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
list_favorite_things(:friend, limit: limit)
|
|
275
|
+
def list_favorite_friends
|
|
276
|
+
list_favorite_things(:friend)
|
|
276
277
|
end
|
|
277
278
|
|
|
278
279
|
# List your favorite friends.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
list_favorite_things(:location, limit: limit)
|
|
280
|
+
def list_favorite_locations
|
|
281
|
+
list_favorite_things(:location)
|
|
282
282
|
end
|
|
283
283
|
|
|
284
284
|
# See `list_events` for all of the parameters we can pass.
|
|
@@ -293,26 +293,20 @@ module Friends
|
|
|
293
293
|
|
|
294
294
|
# List all location names in the friends file.
|
|
295
295
|
def list_locations
|
|
296
|
-
|
|
296
|
+
@locations.each { |location| @output << location.name }
|
|
297
297
|
end
|
|
298
298
|
|
|
299
299
|
# @param from [Array] containing any of: ["activities", "friends", "notes"]
|
|
300
300
|
# If not empty, limits the tags returned to only those from either
|
|
301
301
|
# activities, notes, or friends.
|
|
302
302
|
def list_tags(from:)
|
|
303
|
-
|
|
303
|
+
tags(from: from).sort_by(&:downcase).each { |tag| @output << tag }
|
|
304
304
|
end
|
|
305
305
|
|
|
306
|
-
#
|
|
306
|
+
# Graph activities over time.
|
|
307
307
|
# Optionally filter by friend, location and tag
|
|
308
308
|
#
|
|
309
|
-
# The
|
|
310
|
-
# {
|
|
311
|
-
# "Jan 2015" => 3, # The number of activities during each month.
|
|
312
|
-
# "Feb 2015" => 0,
|
|
313
|
-
# "Mar 2015" => 9
|
|
314
|
-
# }
|
|
315
|
-
# The keys of the hash are all of the months (inclusive) between the first
|
|
309
|
+
# The graph displays all of the months (inclusive) between the first
|
|
316
310
|
# and last month in which activities have been recorded.
|
|
317
311
|
#
|
|
318
312
|
# @param with [Array<String>] the names of friends to filter by, or empty for
|
|
@@ -353,7 +347,7 @@ module Friends
|
|
|
353
347
|
Graph.new(
|
|
354
348
|
filtered_activities: filtered_activities_to_graph,
|
|
355
349
|
all_activities: all_activities_to_graph
|
|
356
|
-
).
|
|
350
|
+
).output.each { |line| @output << line }
|
|
357
351
|
end
|
|
358
352
|
|
|
359
353
|
# Suggest friends to do something with.
|
|
@@ -388,12 +382,12 @@ module Friends
|
|
|
388
382
|
map!(&:name)
|
|
389
383
|
close_friend_names = sorted_friends.map!(&:name)
|
|
390
384
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
385
|
+
@output << "Distant friend: "\
|
|
386
|
+
"#{Paint[distant_friend_names.sample || 'None found', :bold, :magenta]}"
|
|
387
|
+
@output << "Moderate friend: "\
|
|
388
|
+
"#{Paint[moderate_friend_names.sample || 'None found', :bold, :magenta]}"
|
|
389
|
+
@output << "Close friend: "\
|
|
390
|
+
"#{Paint[close_friend_names.sample || 'None found', :bold, :magenta]}"
|
|
397
391
|
end
|
|
398
392
|
|
|
399
393
|
###################################################################
|
|
@@ -472,12 +466,6 @@ module Friends
|
|
|
472
466
|
end
|
|
473
467
|
|
|
474
468
|
def stats
|
|
475
|
-
safe_puts "Total activities: #{@activities.size}"
|
|
476
|
-
safe_puts "Total friends: #{@friends.size}"
|
|
477
|
-
safe_puts "Total locations: #{@locations.size}"
|
|
478
|
-
safe_puts "Total notes: #{@notes.size}"
|
|
479
|
-
safe_puts "Total tags: #{tags.size}"
|
|
480
|
-
|
|
481
469
|
events = @activities + @notes
|
|
482
470
|
|
|
483
471
|
elapsed_days = if events.size < 2
|
|
@@ -487,17 +475,16 @@ module Friends
|
|
|
487
475
|
(sorted_events.first.date - sorted_events.last.date).to_i
|
|
488
476
|
end
|
|
489
477
|
|
|
490
|
-
|
|
478
|
+
@output << "Total activities: #{@activities.size}"
|
|
479
|
+
@output << "Total friends: #{@friends.size}"
|
|
480
|
+
@output << "Total locations: #{@locations.size}"
|
|
481
|
+
@output << "Total notes: #{@notes.size}"
|
|
482
|
+
@output << "Total tags: #{tags.size}"
|
|
483
|
+
@output << "Total time elapsed: #{elapsed_days} day#{'s' if elapsed_days != 1}"
|
|
491
484
|
end
|
|
492
485
|
|
|
493
486
|
private
|
|
494
487
|
|
|
495
|
-
# Print the message unless we're in `quiet` mode.
|
|
496
|
-
# @param str [String] a message to print
|
|
497
|
-
def safe_puts(str)
|
|
498
|
-
puts str unless @quiet
|
|
499
|
-
end
|
|
500
|
-
|
|
501
488
|
# @param from [Array] containing any of: ["activities", "friends", "notes"]
|
|
502
489
|
# If not empty, limits the tags returned to only those from either
|
|
503
490
|
# activities, notes, or friends.
|
|
@@ -518,8 +505,6 @@ module Friends
|
|
|
518
505
|
|
|
519
506
|
# List all event details.
|
|
520
507
|
# @param events [Array<Event>] the base events to list, either @activities or @notes
|
|
521
|
-
# @param limit [Integer] the number of events to return, or nil for no
|
|
522
|
-
# limit
|
|
523
508
|
# @param with [Array<String>] the names of friends to filter by, or empty for
|
|
524
509
|
# unfiltered
|
|
525
510
|
# @param location_name [String] the name of a location to filter by, or
|
|
@@ -528,12 +513,9 @@ module Friends
|
|
|
528
513
|
# unfiltered
|
|
529
514
|
# @param since_date [Date] a date on or after which to find events, or nil for unfiltered
|
|
530
515
|
# @param until_date [Date] a date before or on which to find events, or nil for unfiltered
|
|
531
|
-
# @raise [ArgumentError] if limit is present but limit < 1
|
|
532
516
|
# @raise [FriendsError] if friend, location or tag cannot be found or
|
|
533
517
|
# is ambiguous
|
|
534
|
-
def list_events(events:,
|
|
535
|
-
raise ArgumentError, "Limit must be positive" if limit && limit < 1
|
|
536
|
-
|
|
518
|
+
def list_events(events:, with:, location_name:, tagged:, since_date:, until_date:)
|
|
537
519
|
events = filtered_events(
|
|
538
520
|
events: events,
|
|
539
521
|
with: with,
|
|
@@ -543,10 +525,7 @@ module Friends
|
|
|
543
525
|
until_date: until_date
|
|
544
526
|
)
|
|
545
527
|
|
|
546
|
-
|
|
547
|
-
events = events.take(limit) unless limit.nil?
|
|
548
|
-
|
|
549
|
-
safe_puts events.map(&:to_s)
|
|
528
|
+
events.each { |event| @output << event.to_s }
|
|
550
529
|
end
|
|
551
530
|
|
|
552
531
|
# @param arr [Array] an unsorted array
|
|
@@ -598,57 +577,45 @@ module Friends
|
|
|
598
577
|
end
|
|
599
578
|
|
|
600
579
|
# @param type [Symbol] one of: [:friend, :location]
|
|
601
|
-
# @param limit [Integer] the number of favorite things to return
|
|
602
580
|
# @raise [ArgumentError] if type is not one of: [:friend, :location]
|
|
603
|
-
|
|
604
|
-
def list_favorite_things(type, limit:)
|
|
581
|
+
def list_favorite_things(type)
|
|
605
582
|
unless [:friend, :location].include? type
|
|
606
583
|
raise ArgumentError, "Type must be either :friend or :location"
|
|
607
584
|
end
|
|
608
585
|
|
|
609
|
-
raise ArgumentError, "Favorites limit must be positive" if limit < 1
|
|
610
|
-
|
|
611
586
|
# Sort the results, with the most favorite thing first.
|
|
612
587
|
results = instance_variable_get("@#{type}s").sort_by do |thing|
|
|
613
588
|
-thing.n_activities
|
|
614
|
-
end
|
|
615
|
-
|
|
616
|
-
if results.size == 1
|
|
617
|
-
favorite = results.first
|
|
618
|
-
safe_puts "Your favorite #{type} is "\
|
|
619
|
-
"#{favorite.name} "\
|
|
620
|
-
"(#{favorite.n_activities} "\
|
|
621
|
-
"#{favorite.n_activities == 1 ? 'activity' : 'activities'})"
|
|
622
|
-
else
|
|
623
|
-
safe_puts "Your favorite #{type}s:"
|
|
589
|
+
end
|
|
624
590
|
|
|
625
|
-
|
|
591
|
+
@output << "Your favorite #{type}s:"
|
|
626
592
|
|
|
627
|
-
|
|
593
|
+
max_str_size = results.map(&:name).map(&:size).max
|
|
628
594
|
|
|
629
|
-
|
|
630
|
-
first = true
|
|
631
|
-
data = grouped_results.each.with_object([]) do |(n_activities, things), arr|
|
|
632
|
-
things.each do |thing|
|
|
633
|
-
name = thing.name.ljust(max_str_size)
|
|
634
|
-
if first
|
|
635
|
-
label = n_activities == 1 ? " activity" : " activities"
|
|
636
|
-
first = false
|
|
637
|
-
end
|
|
638
|
-
str = "#{name} (#{n_activities}#{label})"
|
|
595
|
+
grouped_results = results.group_by(&:n_activities)
|
|
639
596
|
|
|
640
|
-
|
|
597
|
+
rank = 1
|
|
598
|
+
first = true
|
|
599
|
+
data = grouped_results.each.with_object([]) do |(n_activities, things), arr|
|
|
600
|
+
things.each do |thing|
|
|
601
|
+
name = thing.name.ljust(max_str_size)
|
|
602
|
+
if first
|
|
603
|
+
label = n_activities == 1 ? " activity" : " activities"
|
|
604
|
+
first = false
|
|
641
605
|
end
|
|
642
|
-
|
|
643
|
-
end
|
|
606
|
+
str = "#{name} (#{n_activities}#{label})"
|
|
644
607
|
|
|
645
|
-
|
|
646
|
-
# of the numbering prefix because `rank` will simply be the size of all
|
|
647
|
-
# elements, which may be too large if the last element in the list is a tie.
|
|
648
|
-
num_str_size = data.last.first.to_s.size + 1 unless data.empty?
|
|
649
|
-
data.each do |ranking, str|
|
|
650
|
-
safe_puts "#{"#{ranking}.".ljust(num_str_size)} #{str}"
|
|
608
|
+
arr << [rank, str]
|
|
651
609
|
end
|
|
610
|
+
rank += things.size
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
# We need to use `data.last.first` instead of `rank` to determine the size
|
|
614
|
+
# of the numbering prefix because `rank` will simply be the size of all
|
|
615
|
+
# elements, which may be too large if the last element in the list is a tie.
|
|
616
|
+
num_str_size = data.last.first.to_s.size + 1 unless data.empty?
|
|
617
|
+
data.each do |ranking, str|
|
|
618
|
+
@output << "#{"#{ranking}.".ljust(num_str_size)} #{str}"
|
|
652
619
|
end
|
|
653
620
|
end
|
|
654
621
|
|