friends 0.28 → 0.29

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