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.
@@ -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.flag [:limit],
105
- arg_name: "NUMBER",
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.flag [:limit],
118
- arg_name: "NUMBER",
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
@@ -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
 
@@ -17,9 +17,9 @@ module Friends
17
17
  @end_date = @all_activities.first.date
18
18
  end
19
19
 
20
- # Prints the graph to STDOUT, with colors.
21
- def draw
22
- to_h.each do |month, (filtered_count, total_count)|
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
- puts str
33
- end
32
+
33
+ str
34
+ end.reverse!
34
35
  end
35
36
 
36
37
  private
@@ -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
- # @param quiet [Boolean] true iff we should suppress all STDOUT output
25
- def initialize(filename:, quiet:)
25
+ def initialize(filename:)
26
26
  @filename = filename
27
- @quiet = quiet
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
- safe_puts "File cleaned: \"#{@filename}\"" if clean_command
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
- safe_puts "Friend added: \"#{friend.name}\""
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
- safe_puts "Activity added: \"#{activity}\""
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
- safe_puts "Note added: \"#{note}\""
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
- safe_puts "Location added: \"#{location.name}\"" # Return the added location.
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
- safe_puts "#{friend.name}'s location set to: \"#{location.name}\""
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
- safe_puts "Name changed: \"#{friend}\""
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
- safe_puts "Location renamed: \"#{loc.name}\""
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
- safe_puts "Nickname added: \"#{friend}\""
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
- safe_puts "Tag added to friend: \"#{friend}\""
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
- safe_puts "Tag removed from friend: \"#{friend}\""
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
- safe_puts "Nickname removed: \"#{friend}\""
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
- safe_puts(verbose ? fs.map(&:to_s) : fs.map(&:name))
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
- # @param limit [Integer] the number of favorite friends to return
274
- def list_favorite_friends(limit:)
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
- # @param limit [Integer] the number of favorite locations to return
280
- def list_favorite_locations(limit:)
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
- safe_puts @locations.map(&:name)
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
- safe_puts tags(from: from).sort_by(&:downcase)
303
+ tags(from: from).sort_by(&:downcase).each { |tag| @output << tag }
304
304
  end
305
305
 
306
- # Find data points for graphing activities over time.
306
+ # Graph activities over time.
307
307
  # Optionally filter by friend, location and tag
308
308
  #
309
- # The returned hash uses the following format:
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
- ).draw
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
- safe_puts "Distant friend: "\
392
- "#{Paint[distant_friend_names.sample || 'None found', :bold, :magenta]}"
393
- safe_puts "Moderate friend: "\
394
- "#{Paint[moderate_friend_names.sample || 'None found', :bold, :magenta]}"
395
- safe_puts "Close friend: "\
396
- "#{Paint[close_friend_names.sample || 'None found', :bold, :magenta]}"
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
- safe_puts "Total time elapsed: #{elapsed_days} day#{'s' if elapsed_days != 1}"
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:, limit:, with:, location_name:, tagged:, since_date:, until_date:)
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
- # If we need to, trim the list.
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
- # @raise [ArgumentError] if limit is < 1
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.take(limit) # Trim the list.
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
- max_str_size = results.map(&:name).map(&:size).max
591
+ @output << "Your favorite #{type}s:"
626
592
 
627
- grouped_results = results.group_by(&:n_activities)
593
+ max_str_size = results.map(&:name).map(&:size).max
628
594
 
629
- rank = 1
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
- arr << [rank, str]
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
- rank += things.size
643
- end
606
+ str = "#{name} (#{n_activities}#{label})"
644
607
 
645
- # We need to use `data.last.first` instead of `rank` to determine the size
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