friends 0.54 → 0.55

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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