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