friends 0.47 → 0.52
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 +4 -4
- data/.github/CONTRIBUTING.md +41 -36
- data/.github/FUNDING.yml +4 -0
- data/.github/ISSUE_TEMPLATE.md +1 -6
- data/.rubocop.yml +20 -5
- data/.travis.yml +5 -5
- data/CHANGELOG.md +78 -10
- data/Gemfile +2 -4
- data/README.md +31 -28
- data/RELEASING.md +1 -1
- data/bin/friends +3 -7
- data/friends.gemspec +3 -5
- data/friends.md +2 -2
- data/lib/friends/commands/edit.rb +1 -1
- data/lib/friends/commands/update.rb +2 -2
- data/lib/friends/event.rb +4 -4
- data/lib/friends/friend.rb +20 -13
- data/lib/friends/graph.rb +1 -1
- data/lib/friends/introvert.rb +47 -9
- data/lib/friends/location.rb +1 -1
- data/lib/friends/post_install_message.rb +1 -2
- data/lib/friends/regex_builder.rb +8 -8
- data/lib/friends/sem_ver_comparator.rb +20 -0
- data/lib/friends/serializable.rb +1 -1
- data/lib/friends/version.rb +1 -1
- data/test/add_event_helper.rb +59 -3
- data/test/commands/add/activity_spec.rb +294 -0
- data/test/commands/edit_spec.rb +33 -2
- data/test/commands/graph_spec.rb +4 -4
- data/test/commands/list/activities_spec.rb +55 -25
- data/test/commands/list/favorite/friends_spec.rb +1 -1
- data/test/commands/list/favorite/locations_spec.rb +8 -8
- data/test/commands/list/locations_spec.rb +1 -1
- data/test/commands/remove/tag_spec.rb +9 -0
- data/test/commands/rename/friend_spec.rb +2 -2
- data/test/commands/suggest_spec.rb +2 -2
- data/test/editor +2 -0
- data/test/helper.rb +7 -7
- metadata +7 -25
data/friends.gemspec
CHANGED
@@ -23,17 +23,15 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
24
24
|
spec.require_paths = ["lib"]
|
25
25
|
|
26
|
-
# We need Ruby 2.
|
27
|
-
|
28
|
-
spec.required_ruby_version = ">= 2.1"
|
26
|
+
# We need Ruby 2.3's safe navigation operator.
|
27
|
+
spec.required_ruby_version = ">= 2.3"
|
29
28
|
|
30
29
|
spec.add_dependency "chronic", "~> 0.10"
|
31
30
|
spec.add_dependency "gli", "~> 2.14"
|
32
31
|
spec.add_dependency "paint", "~> 2.0"
|
33
|
-
spec.add_dependency "semverse", ">= 2", "< 4"
|
34
32
|
spec.add_dependency "tty-pager", "~> 0.11"
|
35
33
|
|
36
34
|
spec.add_development_dependency "minitest", "~> 5.5"
|
37
35
|
spec.add_development_dependency "minitest-proveit", "~> 1.0"
|
38
|
-
spec.add_development_dependency "rake", "~>
|
36
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
39
37
|
end
|
data/friends.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
### Activities:
|
2
|
-
- 2018-11-01: **Grace Hopper** and I went to
|
2
|
+
- 2018-11-01: **Grace Hopper** and I went to _Martha's Vineyard_. George had to cancel at the last minute.
|
3
3
|
- 2018-01-04: Got lunch with **Grace Hopper** and **George Washington Carver**. @food
|
4
4
|
- 2017-12-31: Celebrated the new year in _Paris_ with **Marie Curie**. @partying
|
5
5
|
- 2017-11-15: Talked to **George Washington Carver** on the phone for an hour.
|
@@ -15,5 +15,5 @@
|
|
15
15
|
|
16
16
|
### Locations:
|
17
17
|
- Atlantis
|
18
|
-
-
|
18
|
+
- Martha's Vineyard
|
19
19
|
- Paris
|
@@ -10,7 +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(filename:
|
13
|
+
@introvert = Friends::Introvert.new(filename: filename)
|
14
14
|
@clean_command = true
|
15
15
|
@dirty = true
|
16
16
|
elsif !global_options[:quiet]
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "friends/post_install_message"
|
4
|
+
require "friends/sem_ver_comparator"
|
4
5
|
|
5
6
|
desc "Updates the `friends` program"
|
6
7
|
command :update do |update|
|
@@ -9,8 +10,7 @@ command :update do |update|
|
|
9
10
|
if match = `gem search friends`.match(/^friends\s\(([^\)]+)\)$/)
|
10
11
|
# rubocop:enable Lint/AssignmentInCondition
|
11
12
|
remote_version = match[1]
|
12
|
-
if
|
13
|
-
Semverse::Version.coerce(Friends::VERSION)
|
13
|
+
if Friends::SemVerComparator.greater?(remote_version, Friends::VERSION)
|
14
14
|
`gem update friends && gem cleanup friends`
|
15
15
|
|
16
16
|
unless global_options[:quiet]
|
data/lib/friends/event.rb
CHANGED
@@ -14,8 +14,8 @@ module Friends
|
|
14
14
|
class Event
|
15
15
|
extend Serializable
|
16
16
|
|
17
|
-
SERIALIZATION_PREFIX = "- "
|
18
|
-
DATE_PARTITION = ": "
|
17
|
+
SERIALIZATION_PREFIX = "- "
|
18
|
+
DATE_PARTITION = ": "
|
19
19
|
|
20
20
|
# @return [Regexp] the regex for capturing groups in deserialization
|
21
21
|
def self.deserialization_regex
|
@@ -138,8 +138,8 @@ module Friends
|
|
138
138
|
location_in_description?(location) || location_is_implicit?(location)
|
139
139
|
end
|
140
140
|
|
141
|
-
def
|
142
|
-
@description[/(?<=
|
141
|
+
def default_location
|
142
|
+
@default_location ||= @description[/(?<=to _)\w[^_]*(?=_)/]
|
143
143
|
end
|
144
144
|
|
145
145
|
# @param friend [Friend] the friend to test
|
data/lib/friends/friend.rb
CHANGED
@@ -10,15 +10,13 @@ module Friends
|
|
10
10
|
class Friend
|
11
11
|
extend Serializable
|
12
12
|
|
13
|
-
SERIALIZATION_PREFIX = "- "
|
14
|
-
NICKNAME_PREFIX = "a.k.a. "
|
13
|
+
SERIALIZATION_PREFIX = "- "
|
14
|
+
NICKNAME_PREFIX = "a.k.a. "
|
15
15
|
|
16
16
|
# @return [Regexp] the regex for capturing groups in deserialization
|
17
17
|
def self.deserialization_regex
|
18
18
|
# Note: this regex must be on one line because whitespace is important
|
19
|
-
# rubocop:disable
|
20
|
-
/(#{SERIALIZATION_PREFIX})?(?<name>[^\(\[@]*[^\(\[@\s])(\s+\(#{NICKNAME_PREFIX}(?<nickname_str>.+)\))?(\s+\[(?<location_name>[^\]]+)\])?(\s+(?<tags_str>(#{TAG_REGEX}\s*)+))?/
|
21
|
-
# rubocop:enable Metrics/LineLength
|
19
|
+
/(#{SERIALIZATION_PREFIX})?(?<name>[^\(\[@]*[^\(\[@\s])(\s+\(#{NICKNAME_PREFIX}(?<nickname_str>.+)\))?(\s+\[(?<location_name>[^\]]+)\])?(\s+(?<tags_str>(#{TAG_REGEX}\s*)+))?/ # rubocop:disable Layout/LineLength
|
22
20
|
end
|
23
21
|
|
24
22
|
# @return [Regexp] the string of what we expected during deserialization
|
@@ -34,11 +32,9 @@ module Friends
|
|
34
32
|
tags_str: nil
|
35
33
|
)
|
36
34
|
@name = name
|
37
|
-
@nicknames = nickname_str
|
38
|
-
nickname_str.split(" #{NICKNAME_PREFIX}") ||
|
39
|
-
[]
|
35
|
+
@nicknames = nickname_str&.split(" #{NICKNAME_PREFIX}") || []
|
40
36
|
@location_name = location_name
|
41
|
-
@tags = tags_str
|
37
|
+
@tags = tags_str&.split(/\s+/) || []
|
42
38
|
end
|
43
39
|
|
44
40
|
attr_accessor :name
|
@@ -134,12 +130,23 @@ module Friends
|
|
134
130
|
chunks, # Match a full name with the highest priority.
|
135
131
|
*@nicknames.map { |n| [n] },
|
136
132
|
|
137
|
-
# Match a first name followed by a last name initial, period
|
138
|
-
#
|
139
|
-
#
|
133
|
+
# Match a first name followed by a last name initial, period (that via
|
134
|
+
# lookahead is *NOT* a part of an ellipsis), and then (via lookahead)
|
135
|
+
# either:
|
136
|
+
# - other punctuation that would indicate we want to swallow the period
|
137
|
+
# (note that we do not include closing parentheses in this list because
|
138
|
+
# they could be part of an offset sentence), OR
|
139
|
+
# - anything, so long as the first alphabetical character afterwards is
|
140
|
+
# lowercase.
|
141
|
+
# This matches the "Jake E." part of something like "Jake E. and I went
|
142
|
+
# skiing." or "Jake E., Marie Curie, and I studied science." This
|
140
143
|
# allows us to correctly count the period as part of the name when it's
|
141
144
|
# in the middle of a sentence.
|
142
|
-
(
|
145
|
+
(
|
146
|
+
if chunks.size > 1
|
147
|
+
[chunks.first, "#{chunks.last[0]}\\.(?!\\.\\.)(?=([,!?;:—]+|(?-i)[^A-Z]+[a-z]))"]
|
148
|
+
end
|
149
|
+
),
|
143
150
|
|
144
151
|
# If the above doesn't match, we check for just the first name and then
|
145
152
|
# a last name initial. This matches the "Jake E" part of something like
|
data/lib/friends/graph.rb
CHANGED
data/lib/friends/introvert.rb
CHANGED
@@ -16,10 +16,10 @@ require "friends/friends_error"
|
|
16
16
|
|
17
17
|
module Friends
|
18
18
|
class Introvert
|
19
|
-
ACTIVITIES_HEADER = "### Activities:"
|
20
|
-
NOTES_HEADER = "### Notes:"
|
21
|
-
FRIENDS_HEADER = "### Friends:"
|
22
|
-
LOCATIONS_HEADER = "### Locations:"
|
19
|
+
ACTIVITIES_HEADER = "### Activities:"
|
20
|
+
NOTES_HEADER = "### Notes:"
|
21
|
+
FRIENDS_HEADER = "### Friends:"
|
22
|
+
LOCATIONS_HEADER = "### Locations:"
|
23
23
|
|
24
24
|
# @param filename [String] the name of the friends Markdown file
|
25
25
|
def initialize(filename:)
|
@@ -107,9 +107,11 @@ module Friends
|
|
107
107
|
|
108
108
|
activity.highlight_description(introvert: self)
|
109
109
|
|
110
|
-
@activities.unshift(activity)
|
111
|
-
|
112
110
|
@output << "Activity added: \"#{activity}\""
|
111
|
+
|
112
|
+
@output << default_location_output(activity) if activity.default_location
|
113
|
+
|
114
|
+
@activities.unshift(activity)
|
113
115
|
end
|
114
116
|
end
|
115
117
|
|
@@ -660,8 +662,9 @@ module Friends
|
|
660
662
|
|
661
663
|
def set_implicit_locations!
|
662
664
|
implicit_location = nil
|
665
|
+
# reverse_each here moves through the activities in chronological order
|
663
666
|
@activities.reverse_each do |activity|
|
664
|
-
implicit_location = activity.
|
667
|
+
implicit_location = activity.default_location if activity.default_location
|
665
668
|
activity.implicit_location = implicit_location if activity.description_location_names.empty?
|
666
669
|
end
|
667
670
|
end
|
@@ -685,6 +688,9 @@ module Friends
|
|
685
688
|
# Parse the line and update the parsing state.
|
686
689
|
state = parse_line!(line, line_num: line_num, state: state)
|
687
690
|
end
|
691
|
+
# sort the activities from earliest to latest, in case friends.md has been corrupted
|
692
|
+
@activities = stable_sort(@activities)
|
693
|
+
|
688
694
|
set_implicit_locations!
|
689
695
|
|
690
696
|
set_n_activities!(:friend)
|
@@ -719,8 +725,8 @@ module Friends
|
|
719
725
|
|
720
726
|
begin
|
721
727
|
instance_variable_get("@#{stage.id}") << stage.klass.deserialize(line)
|
722
|
-
rescue =>
|
723
|
-
bad_line(
|
728
|
+
rescue StandardError => e
|
729
|
+
bad_line(e, line_num)
|
724
730
|
end
|
725
731
|
|
726
732
|
state
|
@@ -777,5 +783,37 @@ module Friends
|
|
777
783
|
def bad_line(expected, line_num)
|
778
784
|
raise FriendsError, "Expected \"#{expected}\" on line #{line_num}"
|
779
785
|
end
|
786
|
+
|
787
|
+
# @param [Activity] the activity that was added by the user
|
788
|
+
# @return [String] specifying default location and its time range
|
789
|
+
def default_location_output(activity)
|
790
|
+
str = "Default location"
|
791
|
+
|
792
|
+
earlier_activities, later_activities = @activities.partition { |a| a.date <= activity.date }
|
793
|
+
|
794
|
+
earlier_activity_with_default_location = activity
|
795
|
+
|
796
|
+
earlier_activities.each do |a|
|
797
|
+
next unless a.default_location
|
798
|
+
|
799
|
+
break unless a.default_location == activity.default_location
|
800
|
+
|
801
|
+
earlier_activity_with_default_location = a
|
802
|
+
end
|
803
|
+
|
804
|
+
unless later_activities.empty?
|
805
|
+
str += " from #{Paint[earlier_activity_with_default_location.date, :bold]}"
|
806
|
+
|
807
|
+
later_activity = later_activities.find do |a|
|
808
|
+
a.default_location && a.default_location != activity.default_location
|
809
|
+
end
|
810
|
+
|
811
|
+
str += " to #{Paint[later_activity&.date || 'present', :bold]}"
|
812
|
+
end
|
813
|
+
|
814
|
+
str += " already" if earlier_activity_with_default_location != activity
|
815
|
+
|
816
|
+
"#{str} set to: \"#{activity.default_location}\""
|
817
|
+
end
|
780
818
|
end
|
781
819
|
end
|
data/lib/friends/location.rb
CHANGED
@@ -3,6 +3,5 @@
|
|
3
3
|
module Friends
|
4
4
|
POST_INSTALL_MESSAGE = "\nfriends is a volunteer project. If you find it useful, please "\
|
5
5
|
"consider making a small donation:\n\n\t"\
|
6
|
-
"https://github.com/JacobEvelyn/friends#contributing-its-encouraged\n\n"
|
7
|
-
freeze
|
6
|
+
"https://github.com/JacobEvelyn/friends#contributing-its-encouraged\n\n"
|
8
7
|
end
|
@@ -6,26 +6,26 @@ module Friends
|
|
6
6
|
class RegexBuilder
|
7
7
|
# We don't want to match strings that are "escaped" with a leading
|
8
8
|
# backslash.
|
9
|
-
NO_LEADING_BACKSLASH = "(?<!\\\\)"
|
9
|
+
NO_LEADING_BACKSLASH = "(?<!\\\\)"
|
10
10
|
|
11
11
|
# We don't want to match strings that are directly touching double asterisks
|
12
12
|
# as these are treated as sacred by our program.
|
13
|
-
NO_LEADING_ASTERISKS = "(?<!\\*\\*)"
|
14
|
-
NO_TRAILING_ASTERISKS = "(?!\\*\\*)"
|
13
|
+
NO_LEADING_ASTERISKS = "(?<!\\*\\*)"
|
14
|
+
NO_TRAILING_ASTERISKS = "(?!\\*\\*)"
|
15
15
|
|
16
16
|
# We don't want to match strings that are directly touching underscores
|
17
17
|
# as these are treated as sacred by our program.
|
18
|
-
NO_LEADING_UNDERSCORES = "(?<!_)"
|
19
|
-
NO_TRAILING_UNDERSCORES = "(?!_)"
|
18
|
+
NO_LEADING_UNDERSCORES = "(?<!_)"
|
19
|
+
NO_TRAILING_UNDERSCORES = "(?!_)"
|
20
20
|
|
21
21
|
# We don't want to match strings that are part of other words.
|
22
|
-
NO_LEADING_ALPHABETICALS = "(?<![A-z])"
|
23
|
-
NO_TRAILING_ALPHABETICALS = "(?![A-z])"
|
22
|
+
NO_LEADING_ALPHABETICALS = "(?<![A-z])"
|
23
|
+
NO_TRAILING_ALPHABETICALS = "(?![A-z])"
|
24
24
|
|
25
25
|
# We ignore case within the regex as opposed to globally to allow consumers
|
26
26
|
# of this API the ability to pass in strings that turn off this modifier
|
27
27
|
# with the "(?-i)" string.
|
28
|
-
IGNORE_CASE = "(?i)"
|
28
|
+
IGNORE_CASE = "(?i)"
|
29
29
|
|
30
30
|
def self.regex(str)
|
31
31
|
Regexp.new(
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Friends
|
4
|
+
module SemVerComparator
|
5
|
+
SEPARATOR = "."
|
6
|
+
NUMBER_REGEX = /\d+/.freeze
|
7
|
+
|
8
|
+
def self.greater?(version_a, version_b)
|
9
|
+
version_a.split(SEPARATOR).zip(version_b.split(SEPARATOR)) do |a, b|
|
10
|
+
a_num = a&.[](NUMBER_REGEX)&.to_i
|
11
|
+
b_num = b&.[](NUMBER_REGEX)&.to_i
|
12
|
+
return false if a_num.nil?
|
13
|
+
return true if b_num.nil? || a_num > b_num
|
14
|
+
return false if a_num < b_num
|
15
|
+
end
|
16
|
+
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/friends/serializable.rb
CHANGED
data/lib/friends/version.rb
CHANGED
data/test/add_event_helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
def date_parsing_specs(test_stdout: true)
|
2
4
|
describe "date parsing" do
|
3
5
|
let(:description) { "Test." }
|
@@ -117,6 +119,60 @@ def description_parsing_specs(test_stdout: true)
|
|
117
119
|
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper at 12.\"" }
|
118
120
|
end
|
119
121
|
end
|
122
|
+
|
123
|
+
describe "when followed by a period and a comma" do
|
124
|
+
let(:description) { "Met grace h., and others, at 12." }
|
125
|
+
|
126
|
+
it { line_added "- #{date}: Met **Grace Hopper**, and others, at 12." }
|
127
|
+
if test_stdout
|
128
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper, and others, at 12.\"" } # rubocop:disable Layout/LineLength
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "when followed by a period, a comma, and a proper noun" do
|
133
|
+
let(:description) { "Met grace h., King James, and others at 12." }
|
134
|
+
|
135
|
+
it { line_added "- #{date}: Met **Grace Hopper**, King James, and others at 12." }
|
136
|
+
if test_stdout
|
137
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper, King James, and others at 12.\"" } # rubocop:disable Layout/LineLength
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "when followed by a period and a complex series of sentence-ending punctuation" do
|
142
|
+
let(:description) { "Met someone—grace h.?! At 12." }
|
143
|
+
|
144
|
+
it { line_added "- #{date}: Met someone—**Grace Hopper**?! At 12." }
|
145
|
+
if test_stdout
|
146
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met someone—Grace Hopper?! At 12.\"" } # rubocop:disable Layout/LineLength
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "when followed by a period and a complex series of mid-sentence punctuation" do
|
151
|
+
let(:description) { "Met someone {grace h.}—at 12." }
|
152
|
+
|
153
|
+
it { line_added "- #{date}: Met someone {**Grace Hopper**}—at 12." }
|
154
|
+
if test_stdout
|
155
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met someone {Grace Hopper}—at 12.\"" } # rubocop:disable Layout/LineLength
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "when followed by a period as part of a sentence-ending ellipsis" do
|
160
|
+
let(:description) { "Met grace h... Great!" }
|
161
|
+
|
162
|
+
it { line_added "- #{date}: Met **Grace Hopper**... Great!" }
|
163
|
+
if test_stdout
|
164
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper... Great!\"" }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "when followed by a period as part of a mid-sentence ellipsis" do
|
169
|
+
let(:description) { "Met grace h... at 12." }
|
170
|
+
|
171
|
+
it { line_added "- #{date}: Met **Grace Hopper**... at 12." }
|
172
|
+
if test_stdout
|
173
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper... at 12.\"" }
|
174
|
+
end
|
175
|
+
end
|
120
176
|
end
|
121
177
|
|
122
178
|
describe "when description includes a friend's nickname (case insensitive)" do
|
@@ -449,15 +505,15 @@ FILE
|
|
449
505
|
end
|
450
506
|
|
451
507
|
describe "when description contains both names and locations" do
|
452
|
-
let(:description) { "Grace and I
|
508
|
+
let(:description) { "Grace and I visited Atlantis and then Paris for lunch with George." }
|
453
509
|
|
454
510
|
it do
|
455
|
-
line_added "- #{date}: **Grace Hopper** and I
|
511
|
+
line_added "- #{date}: **Grace Hopper** and I visited _Atlantis_ and then _Paris_ for "\
|
456
512
|
"lunch with **George Washington Carver**."
|
457
513
|
end
|
458
514
|
if test_stdout
|
459
515
|
it do
|
460
|
-
stdout_only "#{capitalized_event} added: \"#{date}: Grace Hopper and I
|
516
|
+
stdout_only "#{capitalized_event} added: \"#{date}: Grace Hopper and I visited "\
|
461
517
|
"Atlantis and then Paris for lunch with George Washington Carver.\""
|
462
518
|
end
|
463
519
|
end
|
@@ -51,5 +51,299 @@ FILE
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
describe "adding default location" do
|
55
|
+
describe "when it is the latest activity" do
|
56
|
+
subject { run_cmd("add activity Moved to _Paris_") }
|
57
|
+
let(:content) do
|
58
|
+
<<-FILE
|
59
|
+
### Activities:
|
60
|
+
- #{preceding_activity}
|
61
|
+
|
62
|
+
### Notes:
|
63
|
+
|
64
|
+
### Friends:
|
65
|
+
|
66
|
+
### Locations:
|
67
|
+
FILE
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "when there is no preceding default location" do
|
71
|
+
let(:preceding_activity) { "2016-01-01: Went to the library." }
|
72
|
+
|
73
|
+
it "prints 'Default location set to [LOCATION]'" do
|
74
|
+
output = 'Default location set to: "Paris"'
|
75
|
+
assert_default_location_output(output)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "when preceding default location is different" do
|
80
|
+
let(:preceding_activity) { "2016-01-01: Moved to _Berlin_." }
|
81
|
+
|
82
|
+
it "prints 'Default location set to [LOCATION]'" do
|
83
|
+
output = 'Default location set to: "Paris"'
|
84
|
+
assert_default_location_output(output)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "when preceding default location is the same" do
|
89
|
+
let(:preceding_activity) { "2016-01-01: Flew to _Paris_." }
|
90
|
+
|
91
|
+
it "prints 'Default location already set to [LOCATION]'" do
|
92
|
+
output = 'Default location already set to: "Paris"'
|
93
|
+
assert_default_location_output(output)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "when it is not the latest activity" do
|
99
|
+
subject { run_cmd("add activity 2009-01-01: Moved to _Paris_") }
|
100
|
+
let(:content) do
|
101
|
+
<<-FILE
|
102
|
+
### Activities:
|
103
|
+
- #{following_activity}
|
104
|
+
- #{preceding_activity}
|
105
|
+
|
106
|
+
### Notes:
|
107
|
+
|
108
|
+
### Friends:
|
109
|
+
|
110
|
+
### Locations:
|
111
|
+
FILE
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "when there is no following default location" do
|
115
|
+
let(:following_activity) { "2019-01-01: Visited a cafe" }
|
116
|
+
|
117
|
+
describe "when there is no preceding default location" do
|
118
|
+
let(:preceding_activity) { "1999-01-01: Visited a library" }
|
119
|
+
|
120
|
+
it "prints 'Default location from [ADDED ACTIVITY DATE] to present set to [LOCATION]'" do
|
121
|
+
message = 'Default location from 2009-01-01 to present set to: "Paris"'
|
122
|
+
assert_default_location_output(message)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "when preceding default location is different" do
|
127
|
+
let(:preceding_activity) { "1999-01-01: Went to _Berlin_" }
|
128
|
+
|
129
|
+
it "prints 'Default location from [ADDED ACTIVITY DATE] to present set to [LOCATION]'" do
|
130
|
+
output = 'Default location from 2009-01-01 to present set to: "Paris"'
|
131
|
+
assert_default_location_output(output)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "when preceding default location is same" do
|
136
|
+
let(:preceding_activity) { "1999-01-01: Went to _Paris_" }
|
137
|
+
|
138
|
+
it "prints 'Default location from [PRECEDING DEFAULT LOCATION ACTIVITY DATE] to " \
|
139
|
+
"present already set to [LOCATION]'" do
|
140
|
+
output = 'Default location from 1999-01-01 to present already set to: "Paris"'
|
141
|
+
assert_default_location_output(output)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "when multiple preceding default locations are same and consecutive" do
|
146
|
+
let(:content) do
|
147
|
+
<<-FILE
|
148
|
+
### Activities:
|
149
|
+
- 2019-01-01: Visited a cafe
|
150
|
+
- 1999-01-01: Went to _Paris_
|
151
|
+
- 1989-01-01: Relocated to _Paris_
|
152
|
+
|
153
|
+
### Notes:
|
154
|
+
|
155
|
+
### Friends:
|
156
|
+
|
157
|
+
### Locations:
|
158
|
+
FILE
|
159
|
+
end
|
160
|
+
|
161
|
+
it "prints 'Default location from " \
|
162
|
+
"[EARLIEST CONSECUTIVE DEFAULT LOCATION ACTIVITY DATE] to " \
|
163
|
+
"present already set to [LOCATION]'" do
|
164
|
+
output = 'Default location from 1989-01-01 to present already set to: "Paris"'
|
165
|
+
assert_default_location_output(output)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "when multiple preceding default locations are the same but not consecutive" do
|
170
|
+
let(:content) do
|
171
|
+
<<-FILE
|
172
|
+
### Activities:
|
173
|
+
- 2019-01-01: Visited a cafe
|
174
|
+
- 1999-01-01: Went to _Paris_
|
175
|
+
- 1989-01-01: Went to _Berlin_
|
176
|
+
- 1979-01-01: Relocated to _Paris_
|
177
|
+
|
178
|
+
### Notes:
|
179
|
+
|
180
|
+
### Friends:
|
181
|
+
|
182
|
+
### Locations:
|
183
|
+
FILE
|
184
|
+
end
|
185
|
+
|
186
|
+
it "prints 'Default location from " \
|
187
|
+
"[EARLIEST CONSECUTIVE DEFAULT LOCATION ACTIVITY DATE] to " \
|
188
|
+
"present already set to [LOCATION]'" do
|
189
|
+
output = 'Default location from 1999-01-01 to present already set to: "Paris"'
|
190
|
+
assert_default_location_output(output)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "when there are no preceding default locations" do
|
196
|
+
let(:preceding_activity) { "1999-01-01: Visited a cafe" }
|
197
|
+
|
198
|
+
describe "when following default location is the same" do
|
199
|
+
let(:following_activity) { "2019-01-01: Went to _Paris_" }
|
200
|
+
|
201
|
+
it "prints 'Default location from [ADDED ACTIVITY DATE] to present set to [LOCATION]'" do
|
202
|
+
output = 'Default location from 2009-01-01 to present set to: "Paris"'
|
203
|
+
assert_default_location_output(output)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "when following default location is different" do
|
208
|
+
let(:following_activity) { "2019-01-01: Went to _Berlin_" }
|
209
|
+
|
210
|
+
it "prints 'Default location from [ADDED ACTIVITY DATE] to " \
|
211
|
+
"[NEXT DIFFERENT DEFAULT LOCATION ACIVITY DATE] set to [LOCATION]'" do
|
212
|
+
output = 'Default location from 2009-01-01 to 2019-01-01 set to: "Paris"'
|
213
|
+
assert_default_location_output(output)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe "when multiple following default locations are the same and consecutive" do
|
218
|
+
let(:content) do
|
219
|
+
<<-FILE
|
220
|
+
### Activities:
|
221
|
+
- 2019-01-01: Went to _Paris_
|
222
|
+
- 2018-01-01: Relocated to _Paris_
|
223
|
+
|
224
|
+
### Notes:
|
225
|
+
|
226
|
+
### Friends:
|
227
|
+
|
228
|
+
### Locations:
|
229
|
+
FILE
|
230
|
+
end
|
231
|
+
|
232
|
+
it "prints 'Default location from [ADDED ACTIVITY DATE] to present set to [LOCATION]'" do
|
233
|
+
output = 'Default location from 2009-01-01 to present set to: "Paris"'
|
234
|
+
assert_default_location_output(output)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe "when multiple following default locations are the same but not consecutive" do
|
239
|
+
let(:content) do
|
240
|
+
<<-FILE
|
241
|
+
### Activities:
|
242
|
+
- 2019-01-01: Went to _Paris_
|
243
|
+
- 2018-01-01: Went to _Berlin_
|
244
|
+
- 2017-01-01: Relocated to _Paris_
|
245
|
+
|
246
|
+
### Notes:
|
247
|
+
|
248
|
+
### Friends:
|
249
|
+
|
250
|
+
### Locations:
|
251
|
+
FILE
|
252
|
+
end
|
253
|
+
|
254
|
+
it "prints 'Default location from [ADDED ACTIVITY DATE] to " \
|
255
|
+
"[NEXT DIFFERENT DEFAULT LOCATION ACIVITY DATE] set to [LOCATION]'" do
|
256
|
+
output = 'Default location from 2009-01-01 to 2018-01-01 set to: "Paris"'
|
257
|
+
assert_default_location_output(output)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe "when preceding default location is the same" do
|
263
|
+
let(:preceding_activity) { "1999-01-01: Went to _Paris_" }
|
264
|
+
|
265
|
+
describe "when following default location is the same" do
|
266
|
+
let(:following_activity) { "2019-01-01: Relocated to _Paris_" }
|
267
|
+
|
268
|
+
it "prints 'Default location from " \
|
269
|
+
"[PRECEDING ACTIVITY DATE] to present already set to [LOCATION]'" do
|
270
|
+
output = 'Default location from 1999-01-01 to present already set to: "Paris"'
|
271
|
+
assert_default_location_output(output)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe "when following default location is different" do
|
276
|
+
let(:following_activity) { "2019-01-01: Relocated to _Berlin_" }
|
277
|
+
|
278
|
+
it "prints 'Default location from " \
|
279
|
+
"[PRECEDING ACTIVITY DATE] to " \
|
280
|
+
"[FOLLOWING ACTIVITY DATE] set to [LOCATION]'" do
|
281
|
+
output = 'Default location from 1999-01-01 to 2019-01-01 already set to: "Paris"'
|
282
|
+
assert_default_location_output(output)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe "when preceding default location is different" do
|
288
|
+
let(:preceding_activity) { "1999-01-01: Went to _Berlin_" }
|
289
|
+
|
290
|
+
describe "when following default location is the same" do
|
291
|
+
let(:following_activity) { "2019-01-01: Relocated to _Paris_" }
|
292
|
+
|
293
|
+
it "prints 'Default location from [ADDED ACTIVITY DATE] to present set to [LOCATION]'" do
|
294
|
+
output = 'Default location from 2009-01-01 to present set to: "Paris"'
|
295
|
+
assert_default_location_output(output)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
describe "when following default location is different" do
|
300
|
+
let(:following_activity) { "2019-01-01: Relocated to _Berlin_" }
|
301
|
+
|
302
|
+
it "prints 'Default location from " \
|
303
|
+
"[ADDED ACTIVITY DATE] to " \
|
304
|
+
"[FOLLOWING ACTIVITY DATE] set to [LOCATION]'" do
|
305
|
+
output = 'Default location from 2009-01-01 to 2019-01-01 set to: "Paris"'
|
306
|
+
assert_default_location_output(output)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
describe "when activities are out of order" do
|
312
|
+
let(:content) do
|
313
|
+
<<-FILE
|
314
|
+
### Activities:
|
315
|
+
- 2018-01-01: Went to _Berlin_
|
316
|
+
- 2019-01-01: Went to _Paris_
|
317
|
+
|
318
|
+
### Notes:
|
319
|
+
|
320
|
+
### Friends:
|
321
|
+
|
322
|
+
### Locations:
|
323
|
+
FILE
|
324
|
+
end
|
325
|
+
|
326
|
+
it "uses the sorted order for determining output" do
|
327
|
+
output = 'Default location from 2009-01-01 to 2018-01-01 set to: "Paris"'
|
328
|
+
assert_default_location_output(output)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
54
334
|
parsing_specs(event: :activity)
|
335
|
+
|
336
|
+
private
|
337
|
+
|
338
|
+
def assert_default_location_output(expected_output)
|
339
|
+
output = select_default_activity_output(subject[:stdout])
|
340
|
+
|
341
|
+
value(output.size).must_equal(1)
|
342
|
+
value(output).must_include(expected_output)
|
343
|
+
end
|
344
|
+
|
345
|
+
def select_default_activity_output(output)
|
346
|
+
lines = output.split("\n")
|
347
|
+
lines.select { |line| line.include?("Default") }
|
348
|
+
end
|
55
349
|
end
|