friends 0.54 → 0.55

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 555bc2e9643af62a0c4afb4d070d59de4ef54060d553e17d9eaa563bae82f1b5
4
- data.tar.gz: a4f705f276a41c147aab67333c1ffec0ccc7bbba1ab454b9a014b7affa66f017
3
+ metadata.gz: 04bb70e6973e141306240dba6c4c8f0d26322ad9e795c70b9407b36fa243386d
4
+ data.tar.gz: 4495b8c22adc4274ca870c81c5112d0ef616a2bf804e168cf469cdbfafee6a99
5
5
  SHA512:
6
- metadata.gz: 3d8ca490b127f8a60e97e3dacd4cbc893ee33ad8959d8e3bf2c53289154a58e76714958dc29e297e74ff024ac86f3bbfb1e168e5eccc9bd37f178c6c41d903e7
7
- data.tar.gz: 14159aa7682fd24ad0cab80d558b91a234eb6d5d52904828c45f6de595b281364da52080288c782b50488cf5ec40a30d36fc2c8e64a251c33924ac8d299fc0df
6
+ metadata.gz: 8e2638a0504eca66dc731052edaa2c86c070222571509187165a08eaff29a57f89c69aa00ee7b2ca1bd633cc783fbeb7f880767151ec7b6de737938b5dea875e
7
+ data.tar.gz: 16c2a9a1ba086507060dc53cc1a76ef4789195bf8152a45a80a8f453027ee7c494921b7ec30c26ebcebb89c0a3c8653a63a9de6d258273d62d7b3cd474201a3f
@@ -8,9 +8,9 @@ merge this change:
8
8
  - [ ] The code in these changes works correctly.
9
9
  - [ ] Code has tests as appropriate.
10
10
  - [ ] Code has been reviewed by @JacobEvelyn.
11
- - [ ] All tests pass on Travis CI.
11
+ - [ ] All tests pass on GitHub.
12
12
  - [ ] Code coverage remains high.
13
- - [ ] Rubocop reports no issues on Travis CI.
13
+ - [ ] RuboCop reports no issues on GitHub.
14
14
  - [ ] The `README.md` file is updated as appropriate.
15
15
 
16
16
  Don't worry—this list will get checked off in no time!
@@ -0,0 +1,36 @@
1
+ name: Main
2
+ on:
3
+ pull_request:
4
+ branches:
5
+ - main
6
+ push:
7
+ branches:
8
+ - main
9
+ jobs:
10
+ ci:
11
+ name: CI
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0]
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+
20
+ # Conditionally configure bundler via environment variables as advised
21
+ # * https://github.com/ruby/setup-ruby#bundle-config
22
+ - name: Set code coverage environment variable
23
+ run: echo "CODE_COVERAGE=true" >> $GITHUB_ENV
24
+ if: matrix.ruby == 3.0
25
+
26
+ # Use 'bundler-cache: true' instead of actions/cache as advised:
27
+ # * https://github.com/actions/cache/blob/main/examples.md#ruby---bundler
28
+ - uses: ruby/setup-ruby@v1
29
+ with:
30
+ ruby-version: ${{ matrix.ruby }}
31
+ bundler-cache: true
32
+
33
+ - run: bundle exec rake test
34
+
35
+ - run: bundle exec rubocop
36
+ if: matrix.ruby == 3.0
data/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@
4
4
  making a small donation (🙏) with the **Sponsor** button at the top of this page to
5
5
  show you appreciate its continued development.
6
6
 
7
+ ## [v0.55](https://github.com/JacobEvelyn/friends/tree/v0.55) (2021-07-25)
8
+
9
+ [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.54...v0.55)
10
+
11
+ **Implemented enhancements:**
12
+
13
+ - Add --sort flag to list friends/locations [\#276](https://github.com/JacobEvelyn/friends/pull/276) ([JacobEvelyn](https://github.com/JacobEvelyn))
14
+
15
+ **Closed issues:**
16
+
17
+ - Add tests for Ruby 3.0 [\#279](https://github.com/JacobEvelyn/friends/issues/279)
18
+ - Switch from Travis to GitHub Actions [\#277](https://github.com/JacobEvelyn/friends/issues/277)
19
+ - Add new list command to log any activity or note related to specified friend [\#270](https://github.com/JacobEvelyn/friends/issues/270)
20
+ - Replace `favorites` commands with more flexible `--sort` options [\#247](https://github.com/JacobEvelyn/friends/issues/247)
21
+
22
+ **Merged pull requests:**
23
+
24
+ - Move CI from Travis to GitHub Actions [\#278](https://github.com/JacobEvelyn/friends/pull/278) ([JacobEvelyn](https://github.com/JacobEvelyn))
25
+
7
26
  ## [v0.54](https://github.com/JacobEvelyn/friends/tree/v0.54) (2020-10-29)
8
27
 
9
28
  [Full Changelog](https://github.com/JacobEvelyn/friends/compare/v0.53...v0.54)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/friends.svg)](https://badge.fury.io/rb/friends)
2
2
  [![Code Coverage](https://codecov.io/gh/JacobEvelyn/friends/branch/main/graph/badge.svg)](https://codecov.io/gh/JacobEvelyn/friends)
3
- [![Build Status](https://travis-ci.com/JacobEvelyn/friends.svg?branch=main)](https://travis-ci.com/JacobEvelyn/friends)
3
+ [![Tests](https://github.com/JacobEvelyn/friends/workflows/Main/badge.svg)](https://github.com/JacobEvelyn/friends/actions?query=workflow%3AMain)
4
4
  [![Readme Score](http://readme-score-api.herokuapp.com/score.svg?url=JacobEvelyn/friends&bust=1)](http://clayallsopp.github.io/readme-score?url=JacobEvelyn/friends)
5
5
  [![Inline docs](http://inch-ci.org/github/JacobEvelyn/friends.png)](http://inch-ci.org/github/JacobEvelyn/friends)
6
6
  [![Gem](https://img.shields.io/gem/dt/friends.svg)](https://rubygems.org/gems/friends)
@@ -32,22 +32,19 @@ lots of help), and give feedback! This project is
32
32
  - [Command reference](#command-reference)
33
33
  - `add`
34
34
  - [`add activity`](#add-activity)
35
+ - [Setting a default location](#setting-a-default-location)
35
36
  - [`add note`](#add-note)
36
37
  - [`add friend`](#add-friend)
37
38
  - [`add tag`](#add-tag)
38
39
  - [`add location`](#add-location)
39
40
  - [`add nickname`](#add-nickname)
40
41
  - [`add alias`](#add-alias)
41
- - [Adding a default location](#adding-a-default-location)
42
42
  - [`clean`](#clean)
43
43
  - [`graph`](#graph)
44
44
  - [`help`](#help)
45
45
  - `list`
46
46
  - [`list activities`](#list-activities)
47
47
  - [`list notes`](#list-notes)
48
- - `list favorite`
49
- - [`list favorite friends`](#list-favorite-friends)
50
- - [`list favorite locations`](#list-favorite-locations)
51
48
  - [`list friends`](#list-friends)
52
49
  - [`list tags`](#list-tags)
53
50
  - [`list locations`](#list-locations)
@@ -354,6 +351,27 @@ This is really handy for when you have an activity involving a friend or locatio
354
351
  you can't remember if you've already added. Just use the signifiers and
355
352
  they'll be added if necessary!
356
353
 
354
+ ##### Setting a default location
355
+
356
+ When an activity includes the phrase to \_LOCATION\_ (e.g., Took a plane to \_Paris\_), all future activities that have no explicit location will be associated with that location:
357
+
358
+ ```bash
359
+ $ friends add activity Took a plane to Paris
360
+ Activity added: "2020-01-04: Took a plane to Paris"
361
+ Default location set to: "Paris"
362
+ $ friends add activity Ate lunch at a charming café
363
+ Activity added: "2020-01-04: Ate lunch at a charming café"
364
+ $ friends add activity Left the city to go to Chamonix
365
+ Activity added: "2020-01-04: Left the city to go to Chamonix"
366
+ Default location set to: "Chamonix"
367
+ ```
368
+
369
+ ```bash
370
+ $ friends list activities --in Paris
371
+ 2019-01-04: Ate lunch at a charming café
372
+ 2019-01-04: Took a plane to Paris
373
+ ```
374
+
357
375
  #### `add note`
358
376
 
359
377
  Notes can be added exactly like activities, either on one line:
@@ -410,7 +428,7 @@ Location added: "Atlantis"
410
428
 
411
429
  ```bash
412
430
  $ friends add nickname "Grace Hopper" "The Admiral"
413
- Nickname added: "Grace Hopper (a.k.a. The Admiral)
431
+ Nickname added: "Grace Hopper (a.k.a. The Admiral)"
414
432
  $ friends add nickname "Grace Hopper" "Amazing Grace"
415
433
  Nickname added: "Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace)"
416
434
  ```
@@ -419,32 +437,11 @@ Nickname added: "Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace)"
419
437
 
420
438
  ```bash
421
439
  $ friends add alias "New York City" "NYC"
422
- Alias added: "New York City (a.k.a. NYC)
440
+ Alias added: "New York City (a.k.a. NYC)"
423
441
  $ friends add alias "New York City" "Big Apple"
424
442
  Alias added: "New York City (a.k.a. NYC a.k.a. Big Apple)"
425
443
  ```
426
444
 
427
- #### Setting a default location
428
-
429
- When an activity includes the phrase to \_LOCATION\_ (e.g., Took a plane to \_Paris\_), all future activities that have no explicit location will be associated with that location:
430
-
431
- ```bash
432
- $ friends add activity Took a plane to Paris
433
- Activity added: "2020-01-04: Took a plane to Paris"
434
- Default location set to: "Paris"
435
- $ friends add activity Ate lunch at a charming café
436
- Activity added: "2020-01-04: Ate lunch at a charming café"
437
- $ friends add activity Left the city to go to Chamonix
438
- Activity added: "2020-01-04: Left the city to go to Chamonix"
439
- Default location set to: "Chamonix"
440
- ```
441
-
442
- ```bash
443
- $ friends list activities --in Paris
444
- 2019-01-04: Ate lunch at a charming café
445
- 2019-01-04: Took a plane to Paris
446
- ```
447
-
448
445
  #### `clean`
449
446
 
450
447
  Reads and re-writes the `friends.md` file:
@@ -711,39 +708,42 @@ $ friends list notes --tagged school --with Marie
711
708
  2015-06-06: Marie Curie just got accepted into a PhD program in Paris. @school
712
709
  ```
713
710
 
714
- #### `list favorite friends`
711
+ #### `list friends`
715
712
 
716
- Lists your "favorite" friends (by total number of activities):
713
+ Lists all of your friends in alphabetical order:
717
714
 
718
715
  ```bash
719
- $ friends list favorite friends
720
- Your favorite friends:
721
- 1. George Washington Carver (2 activities)
722
- 2. Grace Hopper (1)
723
- 3. Marie Curie (0)
716
+ $ friends list friends
717
+ George Washington Carver
718
+ Grace Hopper
719
+ Marie Curie
724
720
  ```
725
721
 
726
- #### `list favorite locations`
727
-
728
- Lists your "favorite" locations (by total number of activities):
722
+ Or you can choose to sort by number of activities:
729
723
 
730
724
  ```bash
731
- $ friends list favorite locations
732
- Your favorite locations:
733
- 1. Atlantis (2 activities)
734
- 2. Paris (1)
735
- 3. London (0)
725
+ $ friends list friends --sort n-activities
726
+ 2 activities: George Washington Carver
727
+ 2 activities: Grace Hopper
728
+ 1 activity: Marie Curie
736
729
  ```
737
730
 
738
- #### `list friends`
731
+ Or by most recent activity:
739
732
 
740
- Lists all of your friends in alphabetical order:
733
+ ```bash
734
+ $ friends list friends --sort recency
735
+ 7 days ago: Grace Hopper
736
+ 308 days ago: George Washington Carver
737
+ 312 days ago: Marie Curie
738
+ ```
739
+
740
+ And you can reverse the sorting at any time:
741
741
 
742
742
  ```bash
743
- $ friends list friends
744
- George Washington Carver
745
- Grace Hopper
746
- Marie Curie
743
+ $ friends list friends --sort n-activities --reverse
744
+ 1 activity: Marie Curie
745
+ 2 activities: Grace Hopper
746
+ 2 activities: George Washington Carver
747
747
  ```
748
748
 
749
749
  You can also include friend nicknames, locations, and tags:
@@ -836,6 +836,42 @@ New York City
836
836
  Paris
837
837
  ```
838
838
 
839
+ Or you can choose to sort by number of activities:
840
+
841
+ ```bash
842
+ $ friends list locations --sort n-activities
843
+ 1 activity: New York City
844
+ 1 activity: Paris
845
+ 0 activities: Atlantis
846
+ ```
847
+
848
+ Or by most recent activity:
849
+
850
+ ```bash
851
+ $ friends list locations --sort recency
852
+ N/A days ago: Atlantis
853
+ 7 days ago: New York City
854
+ 312 days ago: Paris
855
+ ```
856
+
857
+ And you can reverse the sorting at any time:
858
+
859
+ ```bash
860
+ $ friends list friends --sort n-activities --reverse
861
+ 0 activities: Atlantis
862
+ 1 activity: Paris
863
+ 1 activity: New York City
864
+ ```
865
+
866
+ You can also include location aliases:
867
+
868
+ ```bash
869
+ $ friends list locations --verbose
870
+ Atlantis
871
+ New York City (a.k.a. NYC)
872
+ Paris
873
+ ```
874
+
839
875
  #### Advanced searching
840
876
 
841
877
  Since `friends` is a command-line program, we can easily support
data/bin/friends CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- if ENV["TRAVIS"] == "true" && ENV["CODE_COVERAGE"] == "true"
4
+ if ENV["CI"] == "true" && ENV["CODE_COVERAGE"] == "true"
5
5
  require "simplecov"
6
6
  SimpleCov.print_error_status = false
7
7
  SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter
@@ -19,11 +19,23 @@ command :list do |list|
19
19
  negatable: false,
20
20
  desc: "Output friend nicknames, locations, and tags"
21
21
 
22
+ list_friends.flag :sort,
23
+ default_value: "alphabetical",
24
+ arg_name: "ATTRIBUTE",
25
+ must_match: %w[alphabetical n-activities recency],
26
+ desc: "Sort output by one of: alphabetical, n-activities, recency"
27
+
28
+ list_friends.switch :reverse,
29
+ negatable: false,
30
+ desc: "Reverse the sort order"
31
+
22
32
  list_friends.action do |_, options|
23
33
  @introvert.list_friends(
24
34
  location_name: options[:in],
25
35
  tagged: options[:tagged],
26
- verbose: options[:verbose]
36
+ verbose: options[:verbose],
37
+ sort: options[:sort],
38
+ reverse: options[:reverse]
27
39
  )
28
40
  end
29
41
  end
@@ -76,8 +88,23 @@ command :list do |list|
76
88
  list_locations.switch [:verbose],
77
89
  negatable: false,
78
90
  desc: "Output location aliases"
91
+
92
+ list_locations.flag :sort,
93
+ default_value: "alphabetical",
94
+ arg_name: "ATTRIBUTE",
95
+ must_match: %w[alphabetical n-activities recency],
96
+ desc: "Sort output by one of: alphabetical, n-activities, recency"
97
+
98
+ list_locations.switch :reverse,
99
+ negatable: false,
100
+ desc: "Reverse the sort order"
101
+
79
102
  list_locations.action do |_, options|
80
- @introvert.list_locations(verbose: options[:verbose])
103
+ @introvert.list_locations(
104
+ verbose: options[:verbose],
105
+ sort: options[:sort],
106
+ reverse: options[:reverse]
107
+ )
81
108
  end
82
109
  end
83
110
 
@@ -92,21 +119,4 @@ command :list do |list|
92
119
  @introvert.list_tags(from: options[:from])
93
120
  end
94
121
  end
95
-
96
- list.desc "List favorite friends and locations"
97
- list.command :favorite do |list_favorite|
98
- list_favorite.desc "List favorite friends"
99
- list_favorite.command :friends do |list_favorite_friends|
100
- list_favorite_friends.action do
101
- @introvert.list_favorite_friends
102
- end
103
- end
104
-
105
- list_favorite.desc "List favorite locations"
106
- list_favorite.command :locations do |list_favorite_locations|
107
- list_favorite_locations.action do
108
- @introvert.list_favorite_locations
109
- end
110
- end
111
- end
112
122
  end
@@ -297,7 +297,11 @@ module Friends
297
297
  # unfiltered
298
298
  # @param verbose [Boolean] true iff we should output friend names with
299
299
  # nicknames, locations, and tags; false for names only
300
- def list_friends(location_name:, tagged:, verbose:)
300
+ # @param sort [String] one of:
301
+ # ["alphabetical", "n-activities", "recency"]
302
+ # @param reverse [Boolean] true iff we should reverse the sorted order of
303
+ # our output
304
+ def list_friends(location_name:, tagged:, verbose:, sort:, reverse:)
301
305
  fs = @friends
302
306
 
303
307
  # Filter by location if a name is passed.
@@ -313,18 +317,11 @@ module Friends
313
317
  end
314
318
  end
315
319
 
316
- (verbose ? fs.map(&:to_s) : fs.map(&:name)).each { |line| @output << line }
320
+ list_things(type: :friend, arr: fs, verbose: verbose, sort: sort, reverse: reverse)
317
321
  end
318
322
 
319
- # List your favorite friends.
320
- def list_favorite_friends
321
- list_favorite_things(:friend)
322
- end
323
-
324
- # List your favorite friends.
325
- def list_favorite_locations
326
- list_favorite_things(:location)
327
- end
323
+ NA_STR = "N/A"
324
+ private_constant :NA_STR
328
325
 
329
326
  # See `list_events` for all of the parameters we can pass.
330
327
  def list_activities(**args)
@@ -337,8 +334,14 @@ module Friends
337
334
  end
338
335
 
339
336
  # List all location names in the friends file.
340
- def list_locations(verbose:)
341
- (verbose ? @locations.map(&:to_s) : @locations.map(&:name)).each { |line| @output << line }
337
+ # @param verbose [Boolean] true iff we should output location names with
338
+ # aliases; false for names only
339
+ # @param sort [String] one of:
340
+ # ["alphabetical", "n-activities", "recency"]
341
+ # @param reverse [Boolean] true iff we should reverse the sorted order of
342
+ # our output
343
+ def list_locations(verbose:, sort:, reverse:)
344
+ list_things(type: :location, verbose: verbose, sort: sort, reverse: reverse)
342
345
  end
343
346
 
344
347
  # @param from [Array] containing any of: ["activities", "friends", "notes"]
@@ -537,6 +540,49 @@ module Friends
537
540
 
538
541
  private
539
542
 
543
+ # List either friends or activities
544
+ # @param arr [Array<Friend|Activity>] a filtered list to print
545
+ # @param verbose [Boolean] true iff we should output names with
546
+ # aliases/nicknames/etc.; false for names only
547
+ # @param sort [String] one of:
548
+ # ["alphabetical", "n-activities", "recency"]
549
+ # @param reverse [Boolean] true iff we should reverse the sorted order of
550
+ # our output
551
+ def list_things(type:, arr: instance_variable_get("@#{type}s"), verbose:, sort:, reverse:)
552
+ case sort
553
+ when "alphabetical"
554
+ arr = stable_sort(arr) # In case the input file was not already sorted.
555
+ when "n-activities"
556
+ arr = stable_sort_by(arr) { |thing| -thing.n_activities }
557
+ when "recency"
558
+ today = Date.today
559
+
560
+ most_recent_activity_by_thing = @activities.each_with_object({}) do |activity, output|
561
+ activity.send("#{type}_names").each do |thing_name|
562
+ output[thing_name] = (today - activity.date).to_i unless output.key?(thing_name)
563
+ end
564
+ end
565
+
566
+ arr = stable_sort_by(arr) do |thing|
567
+ most_recent_activity_by_thing[thing.name] || -Float::INFINITY
568
+ end
569
+ end
570
+
571
+ (reverse ? arr.reverse : arr).each do |thing|
572
+ case sort
573
+ when "n-activities"
574
+ prefix = "#{Paint[thing.n_activities, :bold, :red]} "\
575
+ "activit#{thing.n_activities == 1 ? 'y' : 'ies'}: "
576
+ when "recency"
577
+ n_days = most_recent_activity_by_thing[thing.name] || NA_STR
578
+ prefix = "#{Paint[n_days, :bold, :red]} "\
579
+ "day#{'s' unless n_days == 1} ago: "
580
+ end
581
+
582
+ @output << "#{prefix}#{verbose ? thing.to_s : thing.name}"
583
+ end
584
+ end
585
+
540
586
  # @param from [Array] containing any of: ["activities", "friends", "notes"]
541
587
  # If not empty, limits the tags returned to only those from either
542
588
  # activities, notes, or friends.
@@ -586,6 +632,13 @@ module Friends
586
632
  arr.sort_by.with_index { |x, idx| [x, idx] }
587
633
  end
588
634
 
635
+ # @param arr [Array] an unsorted array
636
+ # @param &block [block] used to return a value for each element's sort position
637
+ # @return [Array] a stably-sorted array
638
+ def stable_sort_by(arr)
639
+ arr.sort_by.with_index { |x, idx| [yield(x), idx] }
640
+ end
641
+
589
642
  # Filter activities by friend, location and tag
590
643
  # @param events [Array<Event>] the base events to list, either @activities or @notes
591
644
  # @param with [Array<String>] the names of friends to filter by, or empty for
@@ -628,49 +681,6 @@ module Friends
628
681
  events
629
682
  end
630
683
 
631
- # @param type [Symbol] one of: [:friend, :location]
632
- # @raise [ArgumentError] if type is not one of: [:friend, :location]
633
- def list_favorite_things(type)
634
- unless [:friend, :location].include? type
635
- raise ArgumentError, "Type must be either :friend or :location"
636
- end
637
-
638
- # Sort the results, with the most favorite thing first.
639
- results = instance_variable_get("@#{type}s").sort_by do |thing|
640
- -thing.n_activities
641
- end
642
-
643
- @output << "Your favorite #{type}s:"
644
-
645
- max_str_size = results.map(&:name).map(&:size).max
646
-
647
- grouped_results = results.group_by(&:n_activities)
648
-
649
- rank = 1
650
- first = true
651
- data = grouped_results.each.with_object([]) do |(n_activities, things), arr|
652
- things.each do |thing|
653
- name = thing.name.ljust(max_str_size)
654
- if first
655
- label = n_activities == 1 ? " activity" : " activities"
656
- first = false
657
- end
658
- str = "#{name} (#{n_activities}#{label})"
659
-
660
- arr << [rank, str]
661
- end
662
- rank += things.size
663
- end
664
-
665
- # We need to use `data.last.first` instead of `rank` to determine the size
666
- # of the numbering prefix because `rank` will simply be the size of all
667
- # elements, which may be too large if the last element in the list is a tie.
668
- num_str_size = data.last.first.to_s.size + 1 unless data.empty?
669
- data.each do |ranking, str|
670
- @output << "#{"#{ranking}.".ljust(num_str_size)} #{str}"
671
- end
672
- end
673
-
674
684
  # Sets the n_activities field on each thing.
675
685
  # @param type [Symbol] one of: [:friend, :location]
676
686
  # @raise [ArgumentError] if `type` is not one of: [:friend, :location]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Friends
4
- VERSION = "0.54"
4
+ VERSION = "0.55"
5
5
  end
@@ -26,26 +26,26 @@ clean_describe "list friends" do
26
26
  # only reads from the (usually-sorted) file.
27
27
  let(:content) { SCRAMBLED_CONTENT }
28
28
 
29
- it "lists friends in file order" do
29
+ it "lists friends in alphabetical order" do
30
30
  stdout_only <<-OUTPUT
31
31
  George Washington Carver
32
- Marie Curie
33
32
  Grace Hopper
34
- Stanislav Petrov
33
+ Marie Curie
35
34
  Norman Borlaug
35
+ Stanislav Petrov
36
36
  OUTPUT
37
37
  end
38
38
 
39
39
  describe "--verbose" do
40
40
  subject { run_cmd("list friends --verbose") }
41
41
 
42
- it "lists friends in file order with details" do
42
+ it "lists friends in sorted order with details" do
43
43
  stdout_only <<-OUTPUT
44
44
  George Washington Carver
45
- Marie Curie [Atlantis] @science
46
45
  Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris] @navy @science
47
- Stanislav Petrov (a.k.a. Stan) @doesnt-trust-computers @doesnt-trust-computers:military-uses
46
+ Marie Curie [Atlantis] @science
48
47
  Norman Borlaug (a.k.a. Norm) @science @science:outdoors @science:outdoors:agronomy
48
+ Stanislav Petrov (a.k.a. Stan) @doesnt-trust-computers @doesnt-trust-computers:military-uses
49
49
  OUTPUT
50
50
  end
51
51
  end
@@ -73,8 +73,8 @@ Norman Borlaug (a.k.a. Norm) @science @science:outdoors @science:outdoors:agrono
73
73
 
74
74
  it "matches tag case-insensitively" do
75
75
  stdout_only <<-OUTPUT
76
- Marie Curie
77
76
  Grace Hopper
77
+ Marie Curie
78
78
  Norman Borlaug
79
79
  OUTPUT
80
80
  end
@@ -114,5 +114,99 @@ Stanislav Petrov
114
114
  end
115
115
  end
116
116
  end
117
+
118
+ describe "--sort" do
119
+ subject { run_cmd("list friends --sort #{sort} #{reverse}") }
120
+
121
+ let(:reverse) { nil }
122
+
123
+ # Use scrambled content to differentiate between output that is sorted and output that
124
+ # only reads from the (usually-sorted) file.
125
+ let(:content) { SCRAMBLED_CONTENT }
126
+
127
+ describe "alphabetical" do
128
+ let(:sort) { "alphabetical" }
129
+
130
+ it "lists friends in sorted order" do
131
+ stdout_only <<-OUTPUT
132
+ George Washington Carver
133
+ Grace Hopper
134
+ Marie Curie
135
+ Norman Borlaug
136
+ Stanislav Petrov
137
+ OUTPUT
138
+ end
139
+
140
+ describe "--reverse" do
141
+ let(:reverse) { "--reverse" }
142
+
143
+ it "lists friends in reverse order" do
144
+ stdout_only <<-OUTPUT
145
+ Stanislav Petrov
146
+ Norman Borlaug
147
+ Marie Curie
148
+ Grace Hopper
149
+ George Washington Carver
150
+ OUTPUT
151
+ end
152
+ end
153
+ end
154
+
155
+ describe "n-activities" do
156
+ let(:sort) { "n-activities" }
157
+
158
+ it "lists friends in sorted order" do
159
+ stdout_only <<-OUTPUT
160
+ 3 activities: George Washington Carver
161
+ 2 activities: Grace Hopper
162
+ 1 activity: Marie Curie
163
+ 1 activity: Norman Borlaug
164
+ 0 activities: Stanislav Petrov
165
+ OUTPUT
166
+ end
167
+
168
+ describe "--reverse" do
169
+ let(:reverse) { "--reverse" }
170
+
171
+ it "lists friends in reverse order" do
172
+ stdout_only <<-OUTPUT
173
+ 0 activities: Stanislav Petrov
174
+ 1 activity: Norman Borlaug
175
+ 1 activity: Marie Curie
176
+ 2 activities: Grace Hopper
177
+ 3 activities: George Washington Carver
178
+ OUTPUT
179
+ end
180
+ end
181
+ end
182
+
183
+ describe "recency" do
184
+ let(:sort) { "recency" }
185
+
186
+ it "lists friends in sorted order" do
187
+ stdout_only_regexes [
188
+ "N/A days ago: Stanislav Petrov",
189
+ /\d+ days ago: George Washington Carver/,
190
+ /\d+ days ago: Norman Borlaug/,
191
+ /\d+ days ago: Grace Hopper/,
192
+ /\d+ days ago: Marie Curie/
193
+ ]
194
+ end
195
+
196
+ describe "--reverse" do
197
+ let(:reverse) { "--reverse" }
198
+
199
+ it "lists friends in reverse order" do
200
+ stdout_only_regexes [
201
+ /\d+ days ago: Marie Curie/,
202
+ /\d+ days ago: Grace Hopper/,
203
+ /\d+ days ago: Norman Borlaug/,
204
+ /\d+ days ago: George Washington Carver/,
205
+ "N/A days ago: Stanislav Petrov"
206
+ ]
207
+ end
208
+ end
209
+ end
210
+ end
117
211
  end
118
212
  end
@@ -26,24 +26,112 @@ clean_describe "list locations" do
26
26
  # only reads from the (usually-sorted) file.
27
27
  let(:content) { SCRAMBLED_CONTENT }
28
28
 
29
- it "lists locations in file order" do
29
+ it "lists locations in alphabetical" do
30
30
  stdout_only <<-OUTPUT
31
- Paris
32
31
  Atlantis
33
32
  Martha's Vineyard
34
33
  New York City
34
+ Paris
35
35
  OUTPUT
36
36
  end
37
37
 
38
+ describe "--sort" do
39
+ subject { run_cmd("list locations --sort #{sort} #{reverse}") }
40
+
41
+ let(:reverse) { nil }
42
+
43
+ # Use scrambled content to differentiate between output that is sorted and output that
44
+ # only reads from the (usually-sorted) file.
45
+ let(:content) { SCRAMBLED_CONTENT }
46
+
47
+ describe "alphabetical" do
48
+ let(:sort) { "alphabetical" }
49
+
50
+ it "lists locations in sorted order" do
51
+ stdout_only <<-OUTPUT
52
+ Atlantis
53
+ Martha's Vineyard
54
+ New York City
55
+ Paris
56
+ OUTPUT
57
+ end
58
+
59
+ describe "--reverse" do
60
+ let(:reverse) { "--reverse" }
61
+
62
+ it "lists locations in reverse order" do
63
+ stdout_only <<-OUTPUT
64
+ Paris
65
+ New York City
66
+ Martha's Vineyard
67
+ Atlantis
68
+ OUTPUT
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "n-activities" do
74
+ let(:sort) { "n-activities" }
75
+
76
+ it "lists locations in sorted order" do
77
+ stdout_only <<-OUTPUT
78
+ 1 activity: Paris
79
+ 1 activity: Atlantis
80
+ 1 activity: Martha's Vineyard
81
+ 0 activities: New York City
82
+ OUTPUT
83
+ end
84
+
85
+ describe "--reverse" do
86
+ let(:reverse) { "--reverse" }
87
+
88
+ it "lists locations in reverse order" do
89
+ stdout_only <<-OUTPUT
90
+ 0 activities: New York City
91
+ 1 activity: Martha's Vineyard
92
+ 1 activity: Atlantis
93
+ 1 activity: Paris
94
+ OUTPUT
95
+ end
96
+ end
97
+ end
98
+
99
+ describe "recency" do
100
+ let(:sort) { "recency" }
101
+
102
+ it "lists locations in sorted order" do
103
+ stdout_only_regexes [
104
+ "N/A days ago: New York City",
105
+ /\d+ days ago: Atlantis/,
106
+ /\d+ days ago: Martha's Vineyard/,
107
+ /\d+ days ago: Paris/
108
+ ]
109
+ end
110
+
111
+ describe "--reverse" do
112
+ let(:reverse) { "--reverse" }
113
+
114
+ it "lists locations in reverse order" do
115
+ stdout_only_regexes [
116
+ /\d+ days ago: Paris/,
117
+ /\d+ days ago: Martha's Vineyard/,
118
+ /\d+ days ago: Atlantis/,
119
+ "N/A days ago: New York City"
120
+ ]
121
+ end
122
+ end
123
+ end
124
+ end
125
+
38
126
  describe "--verbose" do
39
127
  subject { run_cmd("list locations --verbose") }
40
128
 
41
- it "lists locations in file order with details" do
129
+ it "lists locations in alphabetical order with details" do
42
130
  stdout_only <<-OUTPUT
43
- Paris
44
131
  Atlantis
45
132
  Martha's Vineyard
46
133
  New York City (a.k.a. NYC a.k.a. NY)
134
+ Paris
47
135
  OUTPUT
48
136
  end
49
137
  end
@@ -4,7 +4,7 @@ require "./test/helper"
4
4
 
5
5
  # Since this touches the ~/friends.md file instead of a temp
6
6
  # one, we only want to run it on our CI servers.
7
- if ENV["TRAVIS"] == "true"
7
+ if ENV["CI"] == "true"
8
8
  describe "default filename behavior" do
9
9
  let(:filename) { File.expand_path("~/friends.md") }
10
10
 
data/test/helper.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if ENV["TRAVIS"] == "true" && ENV["CODE_COVERAGE"] == "true"
3
+ if ENV["CI"] == "true" && ENV["CODE_COVERAGE"] == "true"
4
4
  require "simplecov"
5
5
  require "codecov"
6
6
  SimpleCov.formatter = SimpleCov::Formatter::Codecov
@@ -121,6 +121,17 @@ def stderr_only(expected)
121
121
  value(subject[:status]).must_be :>, 0
122
122
  end
123
123
 
124
+ def stdout_only_regexes(regexes)
125
+ puts subject[:stderr] unless subject[:stderr] == ""
126
+ lines = subject[:stdout].split("\n")
127
+ regexes.each_with_index do |regex, index|
128
+ value(lines[index]).must_match regex
129
+ end
130
+ assert_nil(lines[regexes.size])
131
+ value(subject[:stderr]).must_equal ""
132
+ value(subject[:status]).must_equal 0
133
+ end
134
+
124
135
  def file_equals(expected)
125
136
  subject
126
137
  value(File.read(filename)).must_equal expected
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.54'
4
+ version: '0.55'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob Evelyn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-29 00:00:00.000000000 Z
11
+ date: 2021-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chronic
@@ -120,10 +120,10 @@ files:
120
120
  - ".github/FUNDING.yml"
121
121
  - ".github/ISSUE_TEMPLATE.md"
122
122
  - ".github/PULL_REQUEST_TEMPLATE.md"
123
+ - ".github/workflows/main.yml"
123
124
  - ".gitignore"
124
125
  - ".overcommit.yml"
125
126
  - ".rubocop.yml"
126
- - ".travis.yml"
127
127
  - CHANGELOG.md
128
128
  - CODE_OF_CONDUCT.md
129
129
  - Gemfile
@@ -174,8 +174,6 @@ files:
174
174
  - test/commands/graph_spec.rb
175
175
  - test/commands/help_spec.rb
176
176
  - test/commands/list/activities_spec.rb
177
- - test/commands/list/favorite/friends_spec.rb
178
- - test/commands/list/favorite/locations_spec.rb
179
177
  - test/commands/list/friends_spec.rb
180
178
  - test/commands/list/locations_spec.rb
181
179
  - test/commands/list/notes_spec.rb
@@ -232,8 +230,6 @@ test_files:
232
230
  - test/commands/graph_spec.rb
233
231
  - test/commands/help_spec.rb
234
232
  - test/commands/list/activities_spec.rb
235
- - test/commands/list/favorite/friends_spec.rb
236
- - test/commands/list/favorite/locations_spec.rb
237
233
  - test/commands/list/friends_spec.rb
238
234
  - test/commands/list/locations_spec.rb
239
235
  - test/commands/list/notes_spec.rb
data/.travis.yml DELETED
@@ -1,24 +0,0 @@
1
- language: ruby
2
- cache: bundler
3
- rvm:
4
- - 2.3
5
- - 2.4
6
- - 2.5
7
- - 2.6 # 2.7 is tested below
8
- gemfile: Gemfile.old # The latest Ruby version uses Gemfile below
9
- script:
10
- - bundle exec rake test
11
- matrix:
12
- include:
13
- - rvm: 2.7
14
- gemfile: Gemfile
15
- script:
16
- - bundle exec rake test
17
- - bundle exec rubocop
18
- env:
19
- - CODE_COVERAGE=true
20
- branches:
21
- only:
22
- - main # Always run on the main branch
23
- notifications:
24
- email: false
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "./test/helper"
4
-
5
- clean_describe "list favorite friends" do
6
- subject { run_cmd("list favorite friends") }
7
-
8
- describe "when file does not exist" do
9
- let(:content) { nil }
10
-
11
- it "prints a no-data message" do
12
- stdout_only "Your favorite friends:"
13
- end
14
- end
15
-
16
- describe "when file is empty" do
17
- let(:content) { "" }
18
-
19
- it "prints a no-data message" do
20
- stdout_only "Your favorite friends:"
21
- end
22
- end
23
-
24
- describe "when file has content" do
25
- let(:content) do
26
- <<-FILE
27
- ### Activities:
28
- - 2017-01-01: Did some math with **Grace Hopper**.
29
- - 2015-11-01: **Grace Hopper** and I went to _Martha's Vineyard_. George had to cancel at the last minute.
30
- - 2015-01-04: Got lunch with **Grace Hopper** and **George Washington Carver**. @food
31
- - 2014-12-31: Celebrated the new year in _Paris_ with **Marie Curie**. @partying
32
- - 2014-11-15: Talked to **George Washington Carver** on the phone for an hour.
33
-
34
- ### Friends:
35
- - George Washington Carver
36
- - Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris] @navy @science
37
- - Marie Curie [Atlantis] @science
38
- FILE
39
- end
40
-
41
- it "lists friends in order of decreasing activity" do
42
- stdout_only <<-OUTPUT
43
- Your favorite friends:
44
- 1. Grace Hopper (3 activities)
45
- 2. George Washington Carver (2)
46
- 3. Marie Curie (1)
47
- OUTPUT
48
- end
49
-
50
- describe "when friends are tied for the same number of activities" do
51
- let(:content) do
52
- <<-FILE
53
- ### Activities:
54
- - 2017-01-01: Did something with **Friend A**.
55
- - 2017-01-01: Did something with **Friend A**.
56
- - 2017-01-01: Did something with **Friend B**.
57
- - 2017-01-01: Did something with **Friend B**.
58
- - 2017-01-01: Did something with **Friend C**.
59
- - 2017-01-01: Did something with **Friend D**.
60
- - 2017-01-01: Did something with **Friend E**.
61
- - 2017-01-01: Did something with **Friend F**.
62
- - 2017-01-01: Did something with **Friend G**.
63
- - 2017-01-01: Did something with **Friend H**.
64
- - 2017-01-01: Did something with **Friend I**.
65
- - 2017-01-01: Did something with **Friend J**.
66
-
67
- ### Friends:
68
- - Friend A
69
- - Friend B
70
- - Friend C
71
- - Friend D
72
- - Friend E
73
- - Friend F
74
- - Friend G
75
- - Friend H
76
- - Friend I
77
- - Friend J
78
- FILE
79
- end
80
-
81
- it "uses tied ranks" do
82
- value(subject[:stderr]).must_equal ""
83
- value(subject[:status]).must_equal 0
84
-
85
- lines = subject[:stdout].split("\n")
86
- value(lines[1]).must_match(/1\. Friend (A|B)/)
87
- value(lines[2]).must_match(/1\. Friend (A|B)/)
88
- value(lines[3]).must_include "3. Friend"
89
- end
90
-
91
- it "only uses the word 'activities' for the first item, even when a tie" do
92
- value(subject[:stderr]).must_equal ""
93
- value(subject[:status]).must_equal 0
94
-
95
- lines = subject[:stdout].split("\n")
96
- value(lines[1]).must_include "activities"
97
- value(lines[2]).wont_include "activities"
98
- end
99
-
100
- it "indents based on the highest rank number, not the number of friends" do
101
- value(subject[:stderr]).must_equal ""
102
- value(subject[:status]).must_equal 0
103
-
104
- # Since there are 10 friends, a naive implementation would pad our output
105
- # assuming the (numerically) highest rank is "10." but since the highest
106
- # rank is a tie, we never display a double-digit rank, so we don't need to
107
- # pad our output for double digits.
108
- lines = subject[:stdout].split("\n")
109
- value(lines.last).must_include "3. Friend"
110
- end
111
- end
112
- end
113
- end
@@ -1,149 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "./test/helper"
4
-
5
- clean_describe "list favorite locations" do
6
- subject { run_cmd("list favorite locations") }
7
-
8
- describe "when file does not exist" do
9
- let(:content) { nil }
10
-
11
- it "prints a no-data message" do
12
- stdout_only "Your favorite locations:"
13
- end
14
- end
15
-
16
- describe "when file is empty" do
17
- let(:content) { "" }
18
-
19
- it "prints a no-data message" do
20
- stdout_only "Your favorite locations:"
21
- end
22
- end
23
-
24
- describe "when file has content" do
25
- let(:content) do
26
- <<-FILE
27
- ### Activities:
28
- - 2017-01-01: **Grace Hopper** and I went to _Martha's Vineyard_ for breakfast.
29
- - 2015-11-01: **Grace Hopper** and I went to _Martha's Vineyard_. George had to cancel at the last minute.
30
- - 2015-01-04: Got lunch with **Grace Hopper** and **George Washington Carver**. @food
31
- - 2014-12-31: Celebrated the new year in _Paris_ with **Marie Curie**. @partying
32
- - 2014-11-15: Talked to **George Washington Carver** on the phone for an hour.
33
-
34
- ### Friends:
35
- - George Washington Carver
36
- - Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris] @navy @science
37
- - Marie Curie [Atlantis] @science
38
-
39
- ### Locations:
40
- - Atlantis
41
- - Martha's Vineyard
42
- - Paris
43
- FILE
44
- end
45
-
46
- it "lists locations in order of decreasing activity" do
47
- stdout_only <<-OUTPUT
48
- Your favorite locations:
49
- 1. Martha's Vineyard (2 activities)
50
- 2. Paris (1)
51
- 3. Atlantis (0)
52
- OUTPUT
53
- end
54
-
55
- describe "when locations are tied for the same number of activities" do
56
- let(:content) do
57
- <<-FILE
58
- ### Activities:
59
- - 2017-01-01: Did something in _Location A_.
60
- - 2017-01-01: Did something in _Location A_.
61
- - 2017-01-01: Did something in _Location B_.
62
- - 2017-01-01: Did something in _Location B_.
63
- - 2017-01-01: Did something in _Location C_.
64
- - 2017-01-01: Did something in _Location D_.
65
- - 2017-01-01: Did something in _Location E_.
66
- - 2017-01-01: Did something in _Location F_.
67
- - 2017-01-01: Did something in _Location G_.
68
- - 2017-01-01: Did something in _Location H_.
69
- - 2017-01-01: Did something in _Location I_.
70
- - 2017-01-01: Did something in _Location J_.
71
-
72
- ### Locations:
73
- - Location A
74
- - Location B
75
- - Location C
76
- - Location D
77
- - Location E
78
- - Location F
79
- - Location G
80
- - Location H
81
- - Location I
82
- - Location J
83
- FILE
84
- end
85
-
86
- it "uses tied ranks" do
87
- value(subject[:stderr]).must_equal ""
88
- value(subject[:status]).must_equal 0
89
-
90
- lines = subject[:stdout].split("\n")
91
- value(lines[1]).must_match(/1\. Location (A|B)/)
92
- value(lines[2]).must_match(/1\. Location (A|B)/)
93
- value(lines[3]).must_include "3. Location"
94
- end
95
-
96
- it "only uses the word 'activities' for the first item, even when a tie" do
97
- value(subject[:stderr]).must_equal ""
98
- value(subject[:status]).must_equal 0
99
-
100
- lines = subject[:stdout].split("\n")
101
- value(lines[1]).must_include "activities"
102
- value(lines[2]).wont_include "activities"
103
- end
104
-
105
- it "indents based on the highest rank number, not the number of locations" do
106
- value(subject[:stderr]).must_equal ""
107
- value(subject[:status]).must_equal 0
108
-
109
- # Since there are 10 friends, a naive implementation would pad our output
110
- # assuming the (numerically) highest rank is "10." but since the highest
111
- # rank is a tie, we never display a double-digit rank, so we don't need to
112
- # pad our output for double digits.
113
- lines = subject[:stdout].split("\n")
114
- value(lines.last).must_include "3. Location"
115
- end
116
- end
117
-
118
- describe "when implied locations are set" do
119
- let(:content) do
120
- <<-FILE
121
- ### Activities:
122
- - 2015-01-30: Went to a museum with **George Washington Carver**.
123
- - 2015-01-29: Moved to _Paris_.
124
- - 2015-01-01: Got lunch with **Grace Hopper** and **George Washington Carver**. @food
125
- - 2014-12-31: Celebrated the new year in _Paris_ with **Marie Curie**. @partying @food
126
- - 2014-12-30: Went to _Atlantis_.
127
- - 2014-12-29: Talked to **George Washington Carver** on the phone for an hour.
128
-
129
- ### Friends:
130
- - George Washington Carver
131
- - Marie Curie [Atlantis] @science
132
- - Grace Hopper (a.k.a. The Admiral a.k.a. Amazing Grace) [Paris] @navy @science
133
-
134
- ### Locations:
135
- - Atlantis
136
- - Paris
137
- FILE
138
- end
139
-
140
- it "lists locations in order of decreasing activity" do
141
- stdout_only <<-OUTPUT
142
- Your favorite locations:
143
- 1. Paris (3 activities)
144
- 2. Atlantis (2)
145
- OUTPUT
146
- end
147
- end
148
- end
149
- end