friends 0.49 → 0.54
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 +1 -1
- data/.rubocop.yml +20 -5
- data/.travis.yml +6 -6
- data/CHANGELOG.md +76 -4
- data/Gemfile +2 -2
- data/README.md +29 -7
- data/RELEASING.md +1 -1
- data/bin/friends +3 -7
- data/friends.gemspec +3 -5
- data/lib/friends/commands/add.rb +12 -3
- data/lib/friends/commands/list.rb +5 -2
- data/lib/friends/commands/remove.rb +9 -0
- data/lib/friends/commands/update.rb +2 -2
- data/lib/friends/event.rb +2 -2
- data/lib/friends/friend.rb +5 -7
- data/lib/friends/graph.rb +1 -1
- data/lib/friends/introvert.rb +55 -17
- data/lib/friends/location.rb +40 -8
- 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 +6 -4
- data/test/commands/add/alias_spec.rb +74 -0
- data/test/commands/add/nickname_spec.rb +9 -0
- data/test/commands/add/tag_spec.rb +9 -0
- data/test/commands/edit_spec.rb +1 -0
- data/test/commands/list/locations_spec.rb +14 -0
- data/test/commands/remove/alias_spec.rb +68 -0
- data/test/commands/remove/tag_spec.rb +9 -0
- data/test/commands/stats_spec.rb +1 -1
- data/test/editor +2 -0
- data/test/helper.rb +4 -2
- metadata +13 -28
|
@@ -11,6 +11,15 @@ command :remove do |remove|
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
remove.desc "Removes an alias from a location"
|
|
15
|
+
remove.arg_name "LOCATION ALIAS"
|
|
16
|
+
remove.command :alias do |remove_alias|
|
|
17
|
+
remove_alias.action do |_, _, args|
|
|
18
|
+
@introvert.remove_alias(name: args.first.to_s.strip, nickname: args[1].to_s.strip)
|
|
19
|
+
@dirty = true # Mark the file for cleaning.
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
14
23
|
remove.desc "Removes a tag from a friend"
|
|
15
24
|
remove.arg_name "NAME @TAG"
|
|
16
25
|
remove.command :tag do |remove_tag|
|
|
@@ -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
|
data/lib/friends/friend.rb
CHANGED
|
@@ -10,13 +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
|
-
/(#{SERIALIZATION_PREFIX})?(?<name>[^\(\[@]*[^\(\[@\s])(\s+\(#{NICKNAME_PREFIX}(?<nickname_str>.+)\))?(\s+\[(?<location_name>[^\]]+)\])?(\s+(?<tags_str>(#{TAG_REGEX}\s*)+))?/ # rubocop:disable
|
|
19
|
+
/(#{SERIALIZATION_PREFIX})?(?<name>[^\(\[@]*[^\(\[@\s])(\s+\(#{NICKNAME_PREFIX}(?<nickname_str>.+)\))?(\s+\[(?<location_name>[^\]]+)\])?(\s+(?<tags_str>(#{TAG_REGEX}\s*)+))?/ # rubocop:disable Layout/LineLength
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# @return [Regexp] the string of what we expected during deserialization
|
|
@@ -32,11 +32,9 @@ module Friends
|
|
|
32
32
|
tags_str: nil
|
|
33
33
|
)
|
|
34
34
|
@name = name
|
|
35
|
-
@nicknames = nickname_str
|
|
36
|
-
nickname_str.split(" #{NICKNAME_PREFIX}") ||
|
|
37
|
-
[]
|
|
35
|
+
@nicknames = nickname_str&.split(" #{NICKNAME_PREFIX}") || []
|
|
38
36
|
@location_name = location_name
|
|
39
|
-
@tags = tags_str
|
|
37
|
+
@tags = tags_str&.split(/\s+/) || []
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
attr_accessor :name
|
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:)
|
|
@@ -203,6 +203,7 @@ module Friends
|
|
|
203
203
|
# @param nickname [String] the nickname to add to the friend
|
|
204
204
|
# @raise [FriendsError] if 0 or 2+ friends match the given name
|
|
205
205
|
def add_nickname(name:, nickname:)
|
|
206
|
+
raise FriendsError, "Expected \"[Friend Name]\" \"[Nickname]\"" if name.empty?
|
|
206
207
|
raise FriendsError, "Nickname cannot be blank" if nickname.empty?
|
|
207
208
|
|
|
208
209
|
friend = thing_with_name_in(:friend, name)
|
|
@@ -211,11 +212,37 @@ module Friends
|
|
|
211
212
|
@output << "Nickname added: \"#{friend}\""
|
|
212
213
|
end
|
|
213
214
|
|
|
215
|
+
# Add an alias to an existing location.
|
|
216
|
+
# @param name [String] the name of the location
|
|
217
|
+
# @param nickname [String] the alias to add to the location
|
|
218
|
+
# @raise [FriendsError] if 0 or 2+ locations match the given name
|
|
219
|
+
# @raise [FriendsError] if the alias is already taken
|
|
220
|
+
def add_alias(name:, nickname:)
|
|
221
|
+
raise FriendsError, "Expected \"[Location Name]\" \"[Alias]\"" if name.empty?
|
|
222
|
+
raise FriendsError, "Alias cannot be blank" if nickname.empty?
|
|
223
|
+
|
|
224
|
+
collision = @locations.find do |loc|
|
|
225
|
+
loc.name.casecmp(nickname).zero? || loc.aliases.any? { |a| a.casecmp(nickname).zero? }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
if collision
|
|
229
|
+
raise FriendsError,
|
|
230
|
+
"The location alias \"#{nickname}\" is already taken by "\
|
|
231
|
+
"\"#{collision}\""
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
location = thing_with_name_in(:location, name)
|
|
235
|
+
location.add_alias(nickname)
|
|
236
|
+
|
|
237
|
+
@output << "Alias added: \"#{location}\""
|
|
238
|
+
end
|
|
239
|
+
|
|
214
240
|
# Add a tag to an existing friend.
|
|
215
241
|
# @param name [String] the name of the friend
|
|
216
242
|
# @param tag [String] the tag to add to the friend, of the form: "@tag"
|
|
217
243
|
# @raise [FriendsError] if 0 or 2+ friends match the given name
|
|
218
244
|
def add_tag(name:, tag:)
|
|
245
|
+
raise FriendsError, "Expected \"[Friend Name]\" \"[Tag]\"" if name.empty?
|
|
219
246
|
raise FriendsError, "Tag cannot be blank" if tag == "@"
|
|
220
247
|
|
|
221
248
|
friend = thing_with_name_in(:friend, name)
|
|
@@ -248,6 +275,21 @@ module Friends
|
|
|
248
275
|
@output << "Nickname removed: \"#{friend}\""
|
|
249
276
|
end
|
|
250
277
|
|
|
278
|
+
# Remove an alias from an existing location.
|
|
279
|
+
# @param name [String] the name of the location
|
|
280
|
+
# @param nickname [String] the alias to remove from the location
|
|
281
|
+
# @raise [FriendsError] if 0 or 2+ locations match the given name
|
|
282
|
+
# @raise [FriendsError] if the location does not have the given alias
|
|
283
|
+
def remove_alias(name:, nickname:)
|
|
284
|
+
raise FriendsError, "Expected \"[Location Name]\" \"[Alias]\"" if name.empty?
|
|
285
|
+
raise FriendsError, "Alias cannot be blank" if nickname.empty?
|
|
286
|
+
|
|
287
|
+
location = thing_with_name_in(:location, name)
|
|
288
|
+
location.remove_alias(nickname)
|
|
289
|
+
|
|
290
|
+
@output << "Alias removed: \"#{location}\""
|
|
291
|
+
end
|
|
292
|
+
|
|
251
293
|
# List all friend names in the friends file.
|
|
252
294
|
# @param location_name [String] the name of a location to filter by, or nil
|
|
253
295
|
# for unfiltered
|
|
@@ -295,8 +337,8 @@ module Friends
|
|
|
295
337
|
end
|
|
296
338
|
|
|
297
339
|
# List all location names in the friends file.
|
|
298
|
-
def list_locations
|
|
299
|
-
@locations.each { |
|
|
340
|
+
def list_locations(verbose:)
|
|
341
|
+
(verbose ? @locations.map(&:to_s) : @locations.map(&:name)).each { |line| @output << line }
|
|
300
342
|
end
|
|
301
343
|
|
|
302
344
|
# @param from [Array] containing any of: ["activities", "friends", "notes"]
|
|
@@ -427,16 +469,16 @@ module Friends
|
|
|
427
469
|
#
|
|
428
470
|
# The returned hash uses the following format:
|
|
429
471
|
# {
|
|
430
|
-
# /regex/ =>
|
|
472
|
+
# /regex/ => location
|
|
431
473
|
# }
|
|
432
474
|
#
|
|
433
475
|
# This hash is sorted (because Ruby's hashes are ordered) by decreasing
|
|
434
476
|
# regex key length, so the key /Paris, France/ appears before /Paris/.
|
|
435
477
|
#
|
|
436
|
-
# @return [Hash{Regexp =>
|
|
478
|
+
# @return [Hash{Regexp => location}]
|
|
437
479
|
def regex_location_map
|
|
438
480
|
@locations.each_with_object({}) do |location, hash|
|
|
439
|
-
hash[
|
|
481
|
+
location.regexes_for_name.each { |regex| hash[regex] = location }
|
|
440
482
|
end.sort_by { |k, _| -k.to_s.size }.to_h
|
|
441
483
|
end
|
|
442
484
|
|
|
@@ -725,8 +767,8 @@ module Friends
|
|
|
725
767
|
|
|
726
768
|
begin
|
|
727
769
|
instance_variable_get("@#{stage.id}") << stage.klass.deserialize(line)
|
|
728
|
-
rescue StandardError =>
|
|
729
|
-
bad_line(
|
|
770
|
+
rescue StandardError => e
|
|
771
|
+
bad_line(e, line_num)
|
|
730
772
|
end
|
|
731
773
|
|
|
732
774
|
state
|
|
@@ -749,11 +791,7 @@ module Friends
|
|
|
749
791
|
# @raise [FriendsError] if 0 or 2+ friends match the given text
|
|
750
792
|
def thing_with_name_in(type, text)
|
|
751
793
|
things = instance_variable_get("@#{type}s").select do |thing|
|
|
752
|
-
|
|
753
|
-
thing.regexes_for_name.any? { |regex| regex.match(text) }
|
|
754
|
-
else
|
|
755
|
-
thing.regex_for_name.match(text)
|
|
756
|
-
end
|
|
794
|
+
thing.regexes_for_name.any? { |regex| regex.match(text) }
|
|
757
795
|
end
|
|
758
796
|
|
|
759
797
|
# If there's more than one match with fuzzy regexes but exactly one thing
|
|
@@ -808,7 +846,7 @@ module Friends
|
|
|
808
846
|
a.default_location && a.default_location != activity.default_location
|
|
809
847
|
end
|
|
810
848
|
|
|
811
|
-
str += " to #{Paint[
|
|
849
|
+
str += " to #{Paint[later_activity&.date || 'present', :bold]}"
|
|
812
850
|
end
|
|
813
851
|
|
|
814
852
|
str += " already" if earlier_activity_with_default_location != activity
|
data/lib/friends/location.rb
CHANGED
|
@@ -9,12 +9,13 @@ module Friends
|
|
|
9
9
|
class Location
|
|
10
10
|
extend Serializable
|
|
11
11
|
|
|
12
|
-
SERIALIZATION_PREFIX = "- "
|
|
12
|
+
SERIALIZATION_PREFIX = "- "
|
|
13
|
+
ALIAS_PREFIX = "a.k.a. "
|
|
13
14
|
|
|
14
15
|
# @return [Regexp] the regex for capturing groups in deserialization
|
|
15
16
|
def self.deserialization_regex
|
|
16
17
|
# Note: this regex must be on one line because whitespace is important
|
|
17
|
-
/(#{SERIALIZATION_PREFIX})?(?<name>.+)
|
|
18
|
+
/(#{SERIALIZATION_PREFIX})?(?<name>[^\(]*[^\(\s])(\s+\(#{ALIAS_PREFIX}(?<alias_str>.+)\))?/
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
# @return [Regexp] the string of what we expected during deserialization
|
|
@@ -23,21 +24,52 @@ module Friends
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# @param name [String] the name of the location
|
|
26
|
-
def initialize(name:)
|
|
27
|
+
def initialize(name:, alias_str: nil)
|
|
27
28
|
@name = name
|
|
29
|
+
@aliases = alias_str&.split(" #{ALIAS_PREFIX}") || []
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
attr_accessor :name
|
|
33
|
+
attr_reader :aliases
|
|
31
34
|
|
|
32
35
|
# @return [String] the file serialization text for the location
|
|
33
36
|
def serialize
|
|
34
|
-
"#{SERIALIZATION_PREFIX}#{
|
|
37
|
+
Paint.unpaint("#{SERIALIZATION_PREFIX}#{self}")
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
# @return [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
# @return [String] a string representing the location's name and aliases
|
|
41
|
+
def to_s
|
|
42
|
+
unless @aliases.empty?
|
|
43
|
+
alias_str = " (" +
|
|
44
|
+
@aliases.map do |nickname|
|
|
45
|
+
"#{ALIAS_PREFIX}#{Paint[nickname, :bold, :yellow]}"
|
|
46
|
+
end.join(" ") + ")"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
"#{Paint[@name, :bold]}#{alias_str}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Add an alias, ignoring duplicates.
|
|
53
|
+
# @param nickname [String] the alias to add
|
|
54
|
+
def add_alias(nickname)
|
|
55
|
+
@aliases << nickname
|
|
56
|
+
@aliases.uniq!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param nickname [String] the alias to remove
|
|
60
|
+
# @raise [FriendsError] if the location does not have the given alias
|
|
61
|
+
def remove_alias(nickname)
|
|
62
|
+
unless @aliases.include? nickname
|
|
63
|
+
raise FriendsError, "Alias \"#{nickname}\" not found for \"#{name}\""
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@aliases.delete(nickname)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Array] a list of all regexes to match the name in a string
|
|
70
|
+
# NOTE: Only full names and aliases
|
|
71
|
+
def regexes_for_name
|
|
72
|
+
[name, *@aliases].map { |str| Friends::RegexBuilder.regex(str) }
|
|
41
73
|
end
|
|
42
74
|
|
|
43
75
|
# The number of activities this location is in. This is for internal use
|
|
@@ -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." }
|
|
@@ -123,7 +125,7 @@ def description_parsing_specs(test_stdout: true)
|
|
|
123
125
|
|
|
124
126
|
it { line_added "- #{date}: Met **Grace Hopper**, and others, at 12." }
|
|
125
127
|
if test_stdout
|
|
126
|
-
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper, and others, at 12.\"" } # rubocop:disable
|
|
128
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper, and others, at 12.\"" } # rubocop:disable Layout/LineLength
|
|
127
129
|
end
|
|
128
130
|
end
|
|
129
131
|
|
|
@@ -132,7 +134,7 @@ def description_parsing_specs(test_stdout: true)
|
|
|
132
134
|
|
|
133
135
|
it { line_added "- #{date}: Met **Grace Hopper**, King James, and others at 12." }
|
|
134
136
|
if test_stdout
|
|
135
|
-
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper, King James, and others at 12.\"" } # rubocop:disable
|
|
137
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper, King James, and others at 12.\"" } # rubocop:disable Layout/LineLength
|
|
136
138
|
end
|
|
137
139
|
end
|
|
138
140
|
|
|
@@ -141,7 +143,7 @@ def description_parsing_specs(test_stdout: true)
|
|
|
141
143
|
|
|
142
144
|
it { line_added "- #{date}: Met someone—**Grace Hopper**?! At 12." }
|
|
143
145
|
if test_stdout
|
|
144
|
-
it { stdout_only "#{capitalized_event} added: \"#{date}: Met someone—Grace Hopper?! At 12.\"" } # rubocop:disable
|
|
146
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met someone—Grace Hopper?! At 12.\"" } # rubocop:disable Layout/LineLength
|
|
145
147
|
end
|
|
146
148
|
end
|
|
147
149
|
|
|
@@ -150,7 +152,7 @@ def description_parsing_specs(test_stdout: true)
|
|
|
150
152
|
|
|
151
153
|
it { line_added "- #{date}: Met someone {**Grace Hopper**}—at 12." }
|
|
152
154
|
if test_stdout
|
|
153
|
-
it { stdout_only "#{capitalized_event} added: \"#{date}: Met someone {Grace Hopper}—at 12.\"" } # rubocop:disable
|
|
155
|
+
it { stdout_only "#{capitalized_event} added: \"#{date}: Met someone {Grace Hopper}—at 12.\"" } # rubocop:disable Layout/LineLength
|
|
154
156
|
end
|
|
155
157
|
end
|
|
156
158
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "./test/helper"
|
|
4
|
+
|
|
5
|
+
clean_describe "add alias" do
|
|
6
|
+
subject { run_cmd("add alias #{location_name} #{nickname}") }
|
|
7
|
+
|
|
8
|
+
let(:content) { CONTENT }
|
|
9
|
+
|
|
10
|
+
describe "when location name and alias are blank" do
|
|
11
|
+
let(:location_name) { nil }
|
|
12
|
+
let(:nickname) { nil }
|
|
13
|
+
|
|
14
|
+
it "prints an error message" do
|
|
15
|
+
stderr_only 'Error: Expected "[Location Name]" "[Alias]"'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "when location name has no matches" do
|
|
20
|
+
let(:location_name) { "Garbage" }
|
|
21
|
+
let(:nickname) { "Big Apple Pie" }
|
|
22
|
+
|
|
23
|
+
it "prints an error message" do
|
|
24
|
+
stderr_only 'Error: No location found for "Garbage"'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "when location alias has more than one match" do
|
|
29
|
+
let(:location_name) { "'New York City'" }
|
|
30
|
+
let(:nickname) { "'Big Apple'" }
|
|
31
|
+
before do
|
|
32
|
+
run_cmd("add location Manhattan")
|
|
33
|
+
run_cmd("add alias Manhattan 'Big Apple'")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "prints an error message" do
|
|
37
|
+
stderr_only "Error: The location alias "\
|
|
38
|
+
'"Big Apple" is already taken by "Manhattan (a.k.a. Big Apple)"'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "when location name has one match" do
|
|
43
|
+
let(:location_name) { "'New York City'" }
|
|
44
|
+
|
|
45
|
+
describe "when alias is blank" do
|
|
46
|
+
let(:nickname) { "' '" }
|
|
47
|
+
|
|
48
|
+
it "prints an error message" do
|
|
49
|
+
stderr_only "Error: Alias cannot be blank"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe "when alias is nil" do
|
|
54
|
+
let(:nickname) { nil }
|
|
55
|
+
|
|
56
|
+
it "prints an error message" do
|
|
57
|
+
stderr_only "Error: Alias cannot be blank"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "when alias is not blank" do
|
|
62
|
+
let(:nickname) { "'Big Apple'" }
|
|
63
|
+
|
|
64
|
+
it "adds alias to location" do
|
|
65
|
+
line_changed "- New York City (a.k.a. NYC a.k.a. NY)",
|
|
66
|
+
"- New York City (a.k.a. NYC a.k.a. NY a.k.a. Big Apple)"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "prints an output message" do
|
|
70
|
+
stdout_only 'Alias added: "New York City (a.k.a. NYC a.k.a. NY a.k.a. Big Apple)"'
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|