friends 0.28 → 0.29

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -4
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +2 -0
  5. data/CHANGELOG.md +13 -0
  6. data/README.md +35 -5
  7. data/Rakefile +1 -1
  8. data/bin/friends +7 -379
  9. data/friends.gemspec +3 -1
  10. data/lib/friends/activity.rb +4 -4
  11. data/lib/friends/commands/add.rb +64 -0
  12. data/lib/friends/commands/clean.rb +9 -0
  13. data/lib/friends/commands/edit.rb +12 -0
  14. data/lib/friends/commands/graph.rb +56 -0
  15. data/lib/friends/commands/list.rb +143 -0
  16. data/lib/friends/commands/remove.rb +27 -0
  17. data/lib/friends/commands/rename.rb +30 -0
  18. data/lib/friends/commands/set.rb +14 -0
  19. data/lib/friends/commands/stats.rb +11 -0
  20. data/lib/friends/commands/suggest.rb +20 -0
  21. data/lib/friends/commands/update.rb +29 -0
  22. data/lib/friends/graph.rb +7 -7
  23. data/lib/friends/introvert.rb +23 -18
  24. data/lib/friends/version.rb +1 -1
  25. data/test/commands/add/activity_spec.rb +379 -0
  26. data/test/commands/add/friend_spec.rb +30 -0
  27. data/test/commands/add/location_spec.rb +30 -0
  28. data/test/commands/add/nickname_spec.rb +50 -0
  29. data/test/commands/add/tag_spec.rb +65 -0
  30. data/test/commands/clean_spec.rb +39 -0
  31. data/test/commands/graph_spec.rb +147 -0
  32. data/test/commands/help_spec.rb +45 -0
  33. data/test/commands/list/activities_spec.rb +136 -0
  34. data/test/commands/list/favorite/friends_spec.rb +77 -0
  35. data/test/commands/list/favorite/locations_spec.rb +82 -0
  36. data/test/commands/list/friends_spec.rb +76 -0
  37. data/test/commands/list/locations_spec.rb +35 -0
  38. data/test/commands/list/tags_spec.rb +58 -0
  39. data/test/commands/remove/nickname_spec.rb +63 -0
  40. data/test/commands/remove/tag_spec.rb +64 -0
  41. data/test/commands/rename/friend_spec.rb +55 -0
  42. data/test/commands/rename/location_spec.rb +43 -0
  43. data/test/commands/set/location_spec.rb +54 -0
  44. data/test/commands/stats_spec.rb +41 -0
  45. data/test/commands/suggest_spec.rb +86 -0
  46. data/test/commands/update_spec.rb +13 -0
  47. data/test/helper.rb +114 -0
  48. metadata +89 -15
  49. data/test/activity_spec.rb +0 -597
  50. data/test/friend_spec.rb +0 -241
  51. data/test/graph_spec.rb +0 -92
  52. data/test/introvert_spec.rb +0 -969
  53. data/test/location_spec.rb +0 -60
data/friends.gemspec CHANGED
@@ -25,13 +25,15 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.add_dependency "chronic", "~> 0.10"
27
27
  spec.add_dependency "gli", "~> 2.12"
28
- spec.add_dependency "memoist", "~> 0.14"
28
+ spec.add_dependency "memoist", "~> 0.15"
29
29
  spec.add_dependency "paint", "~> 1.0"
30
30
  spec.add_dependency "semverse", "~> 1.2"
31
31
 
32
32
  spec.add_development_dependency "bundler", "~> 1.6"
33
33
  spec.add_development_dependency "codeclimate-test-reporter", "~> 0.5"
34
34
  spec.add_development_dependency "minitest", "~> 5.5"
35
+ spec.add_development_dependency "minitest-parallel_fork", "~> 1.0"
36
+ spec.add_development_dependency "minitest-proveit", "~> 1.0"
35
37
  spec.add_development_dependency "overcommit", "~> 0.34"
36
38
  spec.add_development_dependency "rake", "~> 11.2"
37
39
  spec.add_development_dependency "rubocop", "~> 0.40"
@@ -254,10 +254,10 @@ module Friends
254
254
  position = 0 # Prevent infinite looping by tracking last match position.
255
255
  loop do
256
256
  # Only make a replacement if we're not between a set of "**"s or "_"s.
257
- if match.pre_match.scan("**").size % 2 == 0 &&
258
- match.post_match.scan("**").size % 2 == 0 &&
259
- match.pre_match.scan("_").size % 2 == 0 &&
260
- match.post_match.scan("_").size % 2 == 0
257
+ if (match.pre_match.scan("**").size % 2).zero? &&
258
+ (match.post_match.scan("**").size % 2).zero? &&
259
+ (match.pre_match.scan("_").size % 2).zero? &&
260
+ (match.post_match.scan("_").size % 2).zero?
261
261
  @description = [
262
262
  match.pre_match,
263
263
  indicator,
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Adds a friend (or nickname), activity, or location"
4
+ command :add do |add|
5
+ add.desc "Adds a friend"
6
+ add.arg_name "NAME"
7
+ add.command :friend do |add_friend|
8
+ add_friend.action do |_, _, args|
9
+ friend = @introvert.add_friend(name: args.join(" "))
10
+ @message = "Friend added: \"#{friend.name}\""
11
+ @dirty = true # Mark the file for cleaning.
12
+ end
13
+ end
14
+
15
+ add.desc "Adds an activity"
16
+ add.arg_name "DESCRIPTION"
17
+ add.command :activity do |add_activity|
18
+ add_activity.action do |_, _, args|
19
+ activity = @introvert.add_activity(serialization: args.join(" "))
20
+
21
+ # If there's no description, prompt the user for one.
22
+ if activity.description.nil? || activity.description.empty?
23
+ activity.description = Readline.readline(activity.to_s)
24
+ activity.highlight_description(introvert: @introvert)
25
+ end
26
+
27
+ @message = "Activity added: \"#{activity}\""
28
+ @dirty = true # Mark the file for cleaning.
29
+ end
30
+ end
31
+
32
+ add.desc "Adds a location"
33
+ add.arg_name "LOCATION"
34
+ add.command :location do |add_location|
35
+ add_location.action do |_, _, args|
36
+ location = @introvert.add_location(name: args.join(" "))
37
+ @message = "Location added: \"#{location.name}\""
38
+ @dirty = true # Mark the file for cleaning.
39
+ end
40
+ end
41
+
42
+ add.desc "Adds a nickname to a friend"
43
+ add.arg_name "NAME NICKNAME"
44
+ add.command :nickname do |add_nickname|
45
+ add_nickname.action do |_, _, args|
46
+ friend = @introvert.add_nickname(name: args.first, nickname: args[1])
47
+ @message = "Nickname added: \"#{friend}\""
48
+ @dirty = true # Mark the file for cleaning.
49
+ end
50
+ end
51
+
52
+ add.desc "Adds a tag to a friend"
53
+ add.arg_name "NAME @TAG"
54
+ add.command :tag do |add_tag|
55
+ add_tag.action do |_, _, args|
56
+ friend = @introvert.add_tag(
57
+ name: args[0..-2].join(" "),
58
+ tag: Tag.convert_to_tag(args.last)
59
+ )
60
+ @message = "Tag added to friend: \"#{friend}\""
61
+ @dirty = true # Mark the file for cleaning.
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Cleans your friends.md file"
4
+ command :clean do |clean|
5
+ clean.action do
6
+ @clean_command = true
7
+ @dirty = true # Mark the file for cleaning.
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Opens the `friends.md` file in $EDITOR for manual editing"
4
+ command :edit do |edit|
5
+ edit.action do |global_options|
6
+ editor = ENV["EDITOR"] || "vim"
7
+ filename = global_options[:filename]
8
+
9
+ puts "Opening \"#{filename}\" in #{editor}" unless global_options[:quiet]
10
+ Kernel.exec "#{editor} #{filename}"
11
+ end
12
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Graph activities over time"
4
+ command :graph do |graph|
5
+ graph.flag [:with],
6
+ arg_name: "NAME",
7
+ desc: "Graph activities with the given friend",
8
+ type: Stripped
9
+
10
+ graph.flag [:in],
11
+ arg_name: "LOCATION",
12
+ desc: "Graph activities in the given location",
13
+ type: Stripped
14
+
15
+ graph.flag [:tagged],
16
+ arg_name: "@TAG",
17
+ desc: "Graph activities with the given tag",
18
+ type: Tag
19
+
20
+ graph.flag [:since],
21
+ arg_name: "DATE",
22
+ desc: "Graph activities on or after the given date",
23
+ type: InputDate
24
+
25
+ graph.flag [:until],
26
+ arg_name: "DATE",
27
+ desc: "Graph activities before or on the given date",
28
+ type: InputDate
29
+
30
+ graph.action do |_, options|
31
+ # This math is taken from Minitest's Pride plugin (the PrideLOL class).
32
+ PI_3 = Math::PI / 3
33
+
34
+ colors = (0...(6 * 7)).map do |n|
35
+ n *= 1.0 / 6
36
+ r = (3 * Math.sin(n) + 3).to_i
37
+ g = (3 * Math.sin(n + 2 * PI_3) + 3).to_i
38
+ b = (3 * Math.sin(n + 4 * PI_3) + 3).to_i
39
+
40
+ [r, g, b].map { |c| c * 51 }
41
+ end
42
+
43
+ data = @introvert.graph(
44
+ with: options[:with],
45
+ location_name: options[:in],
46
+ tagged: options[:tagged],
47
+ since_date: options[:since],
48
+ until_date: options[:until]
49
+ )
50
+
51
+ data.each do |month, count|
52
+ print "#{month} |"
53
+ puts colors.take(count).map { |rgb| Paint["█", rgb] }.join
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Lists friends, activities, and locations"
4
+ command :list do |list|
5
+ list.desc "List all friends"
6
+ list.command :friends do |list_friends|
7
+ list_friends.flag [:in],
8
+ arg_name: "LOCATION",
9
+ desc: "List only friends in the given location",
10
+ type: Stripped
11
+
12
+ list_friends.flag [:tagged],
13
+ arg_name: "@TAG",
14
+ desc: "List only friends with the given tag",
15
+ type: Tag
16
+
17
+ list_friends.switch [:verbose],
18
+ negatable: false,
19
+ desc: "Output friend nicknames, locations, and tags"
20
+
21
+ list_friends.action do |_, options|
22
+ puts @introvert.list_friends(
23
+ location_name: options[:in],
24
+ tagged: options[:tagged],
25
+ verbose: options[:verbose]
26
+ )
27
+ end
28
+ end
29
+
30
+ list.desc "Lists all activities"
31
+ list.command :activities do |list_activities|
32
+ list_activities.flag [:limit],
33
+ arg_name: "NUMBER",
34
+ desc: "The number of activities to return",
35
+ default_value: 10,
36
+ type: Integer
37
+
38
+ list_activities.flag [:with],
39
+ arg_name: "NAME",
40
+ desc: "List only activities with the given friend",
41
+ type: Stripped
42
+
43
+ list_activities.flag [:in],
44
+ arg_name: "LOCATION",
45
+ desc: "List only activities in the given location",
46
+ type: Stripped
47
+
48
+ list_activities.flag [:tagged],
49
+ arg_name: "@TAG",
50
+ desc: "List only activities with the given tag",
51
+ type: Tag
52
+
53
+ list_activities.flag [:since],
54
+ arg_name: "DATE",
55
+ desc: "List only activities on or after the given date",
56
+ type: InputDate
57
+
58
+ list_activities.flag [:until],
59
+ arg_name: "DATE",
60
+ desc: "List only activities before or on the given date",
61
+ type: InputDate
62
+
63
+ list_activities.action do |_, options|
64
+ puts @introvert.list_activities(
65
+ limit: options[:limit],
66
+ with: options[:with],
67
+ location_name: options[:in],
68
+ tagged: options[:tagged],
69
+ since_date: options[:since],
70
+ until_date: options[:until]
71
+ )
72
+ end
73
+ end
74
+
75
+ list.desc "List all locations"
76
+ list.command :locations do |list_locations|
77
+ list_locations.action do
78
+ puts @introvert.list_locations
79
+ end
80
+ end
81
+
82
+ list.desc "List all tags used"
83
+ list.command :tags do |list_tags|
84
+ list_tags.flag [:from],
85
+ arg_name: '"activities" or "friends" (default: both)',
86
+ desc: "List only tags from activities or friends instead of"\
87
+ "both"
88
+ list_tags.action do |_, options|
89
+ puts @introvert.list_tags(from: options[:from])
90
+ end
91
+ end
92
+
93
+ list.desc "List favorite friends and locations"
94
+ list.command :favorite do |list_favorite|
95
+ list_favorite.desc "List favorite friends"
96
+ list_favorite.command :friends do |list_favorite_friends|
97
+ list_favorite_friends.flag [:limit],
98
+ arg_name: "NUMBER",
99
+ desc: "The number of friends to return",
100
+ default_value: 10,
101
+ type: Integer
102
+
103
+ list_favorite_friends.action do |_, options|
104
+ favorites = @introvert.list_favorite_friends(limit: options[:limit])
105
+
106
+ if options[:limit] == 1
107
+ puts "Your best friend is #{favorites.first}"
108
+ else
109
+ puts "Your favorite friends:"
110
+
111
+ num_str_size = favorites.size.to_s.size + 1
112
+ favorites.each.with_index(1) do |name, rank|
113
+ puts "#{"#{rank}.".ljust(num_str_size)} #{name}"
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ list_favorite.desc "List favorite locations"
120
+ list_favorite.command :locations do |list_favorite_locations|
121
+ list_favorite_locations.flag [:limit],
122
+ arg_name: "NUMBER",
123
+ desc: "The number of locations to return",
124
+ default_value: 10,
125
+ type: Integer
126
+
127
+ list_favorite_locations.action do |_, options|
128
+ favorites = @introvert.list_favorite_locations(limit: options[:limit])
129
+
130
+ if options[:limit] == 1
131
+ puts "Your favorite location is #{favorites.first}"
132
+ else
133
+ puts "Your favorite locations:"
134
+
135
+ num_str_size = favorites.size.to_s.size + 1
136
+ favorites.each.with_index(1) do |name, rank|
137
+ puts "#{"#{rank}.".ljust(num_str_size)} #{name}"
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Remove a nickname"
4
+ command :remove do |remove|
5
+ remove.desc "Removes a nickname from a friend"
6
+ remove.arg_name "NAME NICKNAME"
7
+ remove.command :nickname do |remove_nickname|
8
+ remove_nickname.action do |_, _, args|
9
+ friend = @introvert.remove_nickname(name: args.first, nickname: args[1])
10
+ @message = "Nickname removed: \"#{friend}\""
11
+ @dirty = true # Mark the file for cleaning.
12
+ end
13
+ end
14
+
15
+ remove.desc "Removes a tag from a friend"
16
+ remove.arg_name "NAME @TAG"
17
+ remove.command :tag do |remove_tag|
18
+ remove_tag.action do |_, _, args|
19
+ friend = @introvert.remove_tag(
20
+ name: args[0..-2].join(" "),
21
+ tag: Tag.convert_to_tag(args.last)
22
+ )
23
+ @message = "Tag removed from friend: \"#{friend}\""
24
+ @dirty = true # Mark the file for cleaning.
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Renames a friend or location"
4
+ command :rename do |rename|
5
+ rename.desc "Renames a friend"
6
+ rename.arg_name "NAME NEW_NAME"
7
+ rename.command :friend do |rename_friend|
8
+ rename_friend.action do |_, _, args|
9
+ friend = @introvert.rename_friend(
10
+ old_name: args.first,
11
+ new_name: args[1]
12
+ )
13
+ @message = "Name changed: \"#{friend}\""
14
+ @dirty = true # Mark the file for cleaning.
15
+ end
16
+ end
17
+
18
+ rename.desc "Renames a location"
19
+ rename.arg_name "NAME NEW_NAME"
20
+ rename.command :location do |rename_location|
21
+ rename_location.action do |_, _, args|
22
+ location = @introvert.rename_location(
23
+ old_name: args.first,
24
+ new_name: args[1]
25
+ )
26
+ @message = "Location renamed: \"#{location.name}\""
27
+ @dirty = true # Mark the file for cleaning.
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Set data about friends"
4
+ command :set do |set|
5
+ set.desc "Set a friend's location"
6
+ set.arg_name "NAME LOCATION"
7
+ set.command :location do |set_location|
8
+ set_location.action do |_, _, args|
9
+ friend = @introvert.set_location(name: args.first, location_name: args[1])
10
+ @message = "#{friend.name}'s location set to: #{friend.location_name}"
11
+ @dirty = true # Mark the file for cleaning.
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "List all stats"
4
+ command :stats do |stats|
5
+ stats.action do
6
+ puts "Total activities: #{@introvert.total_activities}"
7
+ puts "Total friends: #{@introvert.total_friends}"
8
+ days = @introvert.elapsed_days
9
+ puts "Total time elapsed: #{days} day#{'s' if days != 1}"
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Suggest friends to do activities with"
4
+ command :suggest do |suggest|
5
+ suggest.flag [:in],
6
+ arg_name: "LOCATION",
7
+ desc: "Suggest only friends in the given location",
8
+ type: Stripped
9
+
10
+ suggest.action do |_, options|
11
+ suggestions = @introvert.suggest(location_name: options[:in])
12
+
13
+ puts "Distant friend: "\
14
+ "#{Paint[suggestions[:distant].sample || 'None found', :bold, :magenta]}"
15
+ puts "Moderate friend: "\
16
+ "#{Paint[suggestions[:moderate].sample || 'None found', :bold, :magenta]}"
17
+ puts "Close friend: "\
18
+ "#{Paint[suggestions[:close].sample || 'None found', :bold, :magenta]}"
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Updates the `friends` program"
4
+ command :update do |update|
5
+ update.action do
6
+ # rubocop:disable Lint/AssignmentInCondition
7
+ if match = `gem search friends`.match(/^friends\s\(([^\)]+)\)$/)
8
+ # rubocop:enable Lint/AssignmentInCondition
9
+ remote_version = match[1]
10
+ if Semverse::Version.coerce(remote_version) >
11
+ Semverse::Version.coerce(Friends::VERSION)
12
+ `gem update friends && gem cleanup friends`
13
+ @message = if $?.success?
14
+ Paint[
15
+ "Updated to friends #{remote_version}", :bold, :green
16
+ ]
17
+ else
18
+ Paint[
19
+ "Error updating to friends version #{remote_version}", :bold, :red
20
+ ]
21
+ end
22
+ else
23
+ @message = Paint[
24
+ "Already up-to-date (#{Friends::VERSION})", :bold, :green
25
+ ]
26
+ end
27
+ end
28
+ end
29
+ end