friends 0.39 → 0.40

Sign up to get free protection for your applications and to get access to all the features.
@@ -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