lita-conferenz 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b4b5b603523ab6945ca312403138be66d58bb927
4
+ data.tar.gz: 94583855cb7b7a9a8545ece100e7db0b8202f8bf
5
+ SHA512:
6
+ metadata.gz: 6e3089bc86479ea11ee2da7a1b1ba80594883bd9b443481ae74d6cb482426f30b6d6741cf4d788b0c19672b4851bb98c1d069858e440666953a0b54e99ba2454
7
+ data.tar.gz: b54af757417eff2d669d94fe99b6b4e2092375d9c78a3bcb69a801c4ec7d2cf94eac855d5ed1cd6d76356dd63c0b2776968c071e039a814d6d3951f14eb5c355
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ vendor/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,11 @@
1
+ require "set"
2
+
3
+ require "lita"
4
+
5
+ Lita.load_locales Dir[File.expand_path(
6
+ File.join("..", "..", "locales", "*.yml"), __FILE__
7
+ )]
8
+
9
+ require "lita/handlers/conferenz/chat"
10
+ require 'lita/handlers/conferenz/config'
11
+ require 'lita/handlers/conferenz/term'
@@ -0,0 +1,205 @@
1
+ module Lita::Handlers::Conferenz
2
+ class Chat < Lita::Handler
3
+ namespace "conferenz"
4
+
5
+ on :loaded, :define_routes
6
+
7
+ def define_routes(payload)
8
+ define_static_routes
9
+ define_dynamic_routes(config.term_pattern.source)
10
+ end
11
+
12
+ def increment(response)
13
+ modify(response, :increment)
14
+ end
15
+
16
+ def decrement(response)
17
+ modify(response, :decrement)
18
+ end
19
+
20
+ def check(response)
21
+ seen = Set.new
22
+
23
+ output = response.matches.map do |match|
24
+ term = get_term(match[0])
25
+ next if seen.include?(term)
26
+ seen << term
27
+ term.check
28
+ end.compact
29
+
30
+ response.reply output.join("; ")
31
+ end
32
+
33
+ def list_best(response)
34
+ list(response, :list_best)
35
+ end
36
+
37
+ def list_worst(response)
38
+ list(response, :list_worst)
39
+ end
40
+
41
+ def link(response)
42
+ response.matches.each do |match|
43
+ term1 = get_term(match[0])
44
+ term2 = get_term(match[1])
45
+
46
+ result = term1.link(term2)
47
+
48
+ case result
49
+ when Integer
50
+ response.reply t("threshold_not_satisfied", threshold: result)
51
+ when true
52
+ response.reply t("link_success", source: term2, target: term1)
53
+ else
54
+ response.reply t("already_linked", source: term2, target: term1)
55
+ end
56
+ end
57
+ end
58
+
59
+ def unlink(response)
60
+ response.matches.each do |match|
61
+ term1 = get_term(match[0])
62
+ term2 = get_term(match[1])
63
+
64
+ if term1.unlink(term2)
65
+ response.reply t("unlink_success", source: term2, target: term1)
66
+ else
67
+ response.reply t("already_unlinked", source: term2, target: term1)
68
+ end
69
+ end
70
+ end
71
+
72
+ def modified(response)
73
+ term = get_term(response.args[1])
74
+
75
+ users = term.modified
76
+
77
+ if users.empty?
78
+ response.reply t("never_modified", term: term)
79
+ else
80
+ response.reply users.map(&:name).join(", ")
81
+ end
82
+ end
83
+
84
+ def delete(response)
85
+ term = Term.new(robot, response.message.body.sub(/^conferenz delete /, ""), normalize: false)
86
+
87
+ if term.delete
88
+ response.reply t("delete_success", term: term)
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def define_dynamic_routes(pattern)
95
+ self.class.route(
96
+ %r{(#{pattern})\+\+#{token_terminator.source}},
97
+ :increment,
98
+ help: { t("help.increment_key") => t("help.increment_value") }
99
+ )
100
+
101
+ self.class.route(
102
+ %r{(#{pattern})--#{token_terminator.source}},
103
+ :decrement,
104
+ help: { t("help.decrement_key") => t("help.decrement_value") }
105
+ )
106
+
107
+ self.class.route(
108
+ %r{(#{pattern})~~#{token_terminator.source}},
109
+ :check,
110
+ help: { t("help.check_key") => t("help.check_value") }
111
+ )
112
+
113
+ self.class.route(
114
+ %r{^(#{pattern})\s*\+=\s*(#{pattern})(?:\+\+|--|~~)?#{token_terminator.source}},
115
+ :link,
116
+ command: true,
117
+ help: { t("help.link_key") => t("help.link_value") }
118
+ )
119
+
120
+ self.class.route(
121
+ %r{^(#{pattern})\s*-=\s*(#{pattern})(?:\+\+|--|~~)?#{token_terminator.source}},
122
+ :unlink,
123
+ command: true,
124
+ help: { t("help.unlink_key") => t("help.unlink_value") }
125
+ )
126
+ end
127
+
128
+ def define_static_routes
129
+ self.class.route(
130
+ %r{^conferenz\s+worst},
131
+ :list_worst,
132
+ command: true,
133
+ help: { t("help.list_worst_key") => t("help.list_worst_value") }
134
+ )
135
+
136
+ self.class.route(
137
+ %r{^conferenz\s+best},
138
+ :list_best,
139
+ command: true,
140
+ help: { t("help.list_best_key") => t("help.list_best_value") }
141
+ )
142
+
143
+ self.class.route(
144
+ %r{^conferenz\s+modified\s+.+},
145
+ :modified,
146
+ command: true,
147
+ help: { t("help.modified_key") => t("help.modified_value") }
148
+ )
149
+
150
+ self.class.route(
151
+ %r{^conferenz\s+delete},
152
+ :delete,
153
+ command: true,
154
+ restrict_to: :conferenz_admins,
155
+ help: { t("help.delete_key") => t("help.delete_value") }
156
+ )
157
+
158
+ self.class.route(%r{^conferenz\s*$}, :list_best, command: true)
159
+ end
160
+
161
+ def determine_list_count(response)
162
+ n = (response.args[1] || 5).to_i - 1
163
+ n = 25 if n > 25
164
+ n
165
+ end
166
+
167
+ def get_term(term)
168
+ Term.new(robot, term)
169
+ end
170
+
171
+ def list(response, method_name)
172
+ terms_and_scores = Term.public_send(method_name, robot, determine_list_count(response))
173
+
174
+ output = terms_and_scores.each_with_index.map do |term_and_score, index|
175
+ "#{index + 1}. #{term_and_score[0]} (#{term_and_score[1].to_i})"
176
+ end.join("\n")
177
+
178
+ if output.empty?
179
+ response.reply t("no_terms")
180
+ else
181
+ response.reply output
182
+ end
183
+ end
184
+
185
+ def modify(response, method_name)
186
+ user = response.user
187
+
188
+ output = response.matches.map do |match|
189
+ get_term(match[0]).public_send(method_name, user)
190
+ end
191
+
192
+ response.reply output.join("; ")
193
+ end
194
+
195
+ # To ensure that constructs like foo++bar or foo--bar (the latter is
196
+ # common in some URL generation schemes) do not cause errant conferenz
197
+ # modifications, force conferenz tokens be followed by whitespace (in a zero-
198
+ # width, look-ahead operator) or the end of the string.
199
+ def token_terminator
200
+ %r{(?:(?=\s)|$)}
201
+ end
202
+ end
203
+ end
204
+
205
+ Lita.register_handler(Lita::Handlers::Conferenz::Chat)
@@ -0,0 +1,20 @@
1
+ module Lita::Handlers::Conferenz
2
+ class Config < Lita::Handler
3
+ namespace "conferenz"
4
+
5
+ default_term_normalizer = proc do |term|
6
+ term.to_s.downcase.strip
7
+ end
8
+
9
+ config :cooldown, types: [Integer, nil], default: 0
10
+ config :link_conferenz_threshold, types: [Integer, nil], default: 0
11
+ config :term_pattern, type: Regexp, default: /[\[\]\p{Word}\._|\{\}]{2,}/
12
+ config :term_normalizer, default: default_term_normalizer do
13
+ validate do |value|
14
+ t("callable_required") unless value.respond_to?(:call)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ Lita.register_handler(Lita::Handlers::Conferenz::Config)
@@ -0,0 +1,153 @@
1
+ module Lita::Handlers::Conferenz
2
+ class Term
3
+ include Lita::Handler::Common
4
+
5
+ namespace "conferenz"
6
+
7
+ attr_reader :term
8
+
9
+ class << self
10
+ def list_best(robot, n = 5)
11
+ list(:zrevrange, robot, n)
12
+ end
13
+
14
+ def list_worst(robot, n = 5)
15
+ list(:zrange, robot, n)
16
+ end
17
+
18
+ private
19
+
20
+ def list(redis_command, robot, n)
21
+ n = 24 if n > 24
22
+
23
+ handler = new(robot, '', normalize: false)
24
+ handler.redis.public_send(redis_command, "terms", 0, n, with_scores: true)
25
+ end
26
+ end
27
+
28
+ def initialize(robot, term, normalize: true)
29
+ super(robot)
30
+ @term = normalize ? normalize_term(term) : term
31
+ @link_cache = {}
32
+ end
33
+
34
+ def check
35
+ string = "#{self}: #{total_score}"
36
+
37
+ unless links_with_scores.empty?
38
+ link_text = links_with_scores.map { |term, score| "#{term}: #{score}" }.join(", ")
39
+ string << " (#{own_score}), #{t("linked_to")}: #{link_text}"
40
+ end
41
+
42
+ string
43
+ end
44
+
45
+ def decrement(user)
46
+ modify(user, -1)
47
+ end
48
+
49
+ def delete
50
+ redis.zrem("terms", to_s)
51
+ redis.del("modified:#{self}")
52
+ redis.del("links:#{self}")
53
+ redis.smembers("linked_to:#{self}").each do |key|
54
+ redis.srem("links:#{key}", to_s)
55
+ end
56
+ redis.del("linked_to:#{self}")
57
+ end
58
+
59
+ def eql?(other)
60
+ term.eql?(other.term)
61
+ end
62
+ alias_method :==, :eql?
63
+
64
+ def hash
65
+ term.hash
66
+ end
67
+
68
+ def increment(user)
69
+ modify(user, 1)
70
+ end
71
+
72
+ def link(other)
73
+ if config.link_conferenz_threshold
74
+ threshold = config.link_conferenz_threshold.abs
75
+
76
+ if own_score.abs < threshold || other.own_score.abs < threshold
77
+ return threshold
78
+ end
79
+ end
80
+
81
+ redis.sadd("links:#{self}", other.to_s) && redis.sadd("linked_to:#{other}", to_s)
82
+ end
83
+
84
+ def links
85
+ @links ||= begin
86
+ redis.smembers("links:#{self}").each do |term|
87
+ linked_term = self.class.new(robot, term)
88
+ @link_cache[linked_term.term] = linked_term
89
+ end
90
+ end
91
+ end
92
+
93
+ def links_with_scores
94
+ @links_with_scores ||= begin
95
+ {}.tap do |h|
96
+ links.each do |link|
97
+ h[link] = @link_cache[link].own_score
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ def modified
104
+ redis.smembers("modified:#{term}").map do |user_id|
105
+ Lita::User.find_by_id(user_id)
106
+ end
107
+ end
108
+
109
+ def own_score
110
+ @own_score ||= redis.zscore("terms", term).to_i
111
+ end
112
+
113
+ def to_s
114
+ term
115
+ end
116
+
117
+ def total_score
118
+ @total_score ||= begin
119
+ links.inject(own_score) do |memo, linked_term|
120
+ memo + @link_cache[linked_term].own_score
121
+ end
122
+ end
123
+ end
124
+
125
+ def unlink(other)
126
+ redis.srem("links:#{self}", other.to_s) && redis.srem("linked_to:#{other}", to_s)
127
+ end
128
+
129
+ private
130
+
131
+ def modify(user, delta)
132
+ ttl = redis.ttl("cooldown:#{user.id}:#{term}")
133
+
134
+ if ttl > 0
135
+ t("cooling_down", term: self, ttl: ttl, count: ttl)
136
+ else
137
+ modify!(user, delta)
138
+ end
139
+ end
140
+
141
+ def modify!(user, delta)
142
+ user_id = user.id
143
+ redis.zincrby("terms", delta, term)
144
+ redis.sadd("modified:#{self}", user_id)
145
+ redis.setex("cooldown:#{user_id}:#{self}", config.cooldown, 1) if config.cooldown
146
+ check
147
+ end
148
+
149
+ def normalize_term(term)
150
+ config.term_normalizer.call(term)
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "lita-conferenz"
3
+ spec.version = "1.0.1"
4
+ spec.authors = ["Piotr Limanowski"]
5
+ spec.email = ["plimanowski+conferenz@gmail.com"]
6
+ spec.description = %q{A Lita handler for tracking conferences attendance.}
7
+ spec.summary = %q{A Lita handler for tracking conferences attendance.}
8
+ spec.homepage = "https://github.com/peel/conferenz"
9
+ spec.license = "MIT"
10
+ spec.metadata = { "lita_plugin_type" => "handler" }
11
+
12
+ spec.files = `git ls-files`.split($/)
13
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ["lib"]
16
+
17
+ spec.add_runtime_dependency "lita", ">= 4.0"
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.3"
20
+ spec.add_development_dependency "rake"
21
+ spec.add_development_dependency "rspec", ">= 3.0.0"
22
+ spec.add_development_dependency "simplecov"
23
+ spec.add_development_dependency "coveralls"
24
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,40 @@
1
+ en:
2
+ lita:
3
+ handlers:
4
+ karma:
5
+ already_linked: "%{source} is already linked to %{target}."
6
+ already_unlinked: "%{source} is not linked to %{target}."
7
+ callable_required: must be a callable object
8
+ cooling_down:
9
+ one: "You cannot modify %{term} for another %{ttl} second."
10
+ other: "You cannot modify %{term} for another %{ttl} seconds."
11
+ delete_success: "%{term} has been deleted."
12
+ help:
13
+ increment_key: TERM++
14
+ increment_value: Increments TERM by one.
15
+ decrement_key: TERM--
16
+ decrement_value: Decrements TERM by one.
17
+ check_key: TERM~~
18
+ check_value: Shows the current karma of TERM.
19
+ link_key: TERM1 += TERM2
20
+ link_value: Links TERM2 to TERM1.
21
+ unlink_key: TERM1 -= TERM2
22
+ unlink_value: Unlinks TERM2 from TERM1.
23
+ list_worst_key: "karma worst [N]"
24
+ list_worst_value: Lists the bottom N terms by karma. N defaults to 5.
25
+ list_best_key: "karma best [N]"
26
+ list_best_value: Lists the top N terms by karma. N defaults to 5.
27
+ modified_key: karma modified TERM
28
+ modified_value: Lists the names of users who have upvoted or downvoted TERM.
29
+ delete_key: karma delete TERM
30
+ delete_value: >-
31
+ Permanently removes TERM and all its link information. TERM is matched
32
+ exactly as typed and does not adhere to the usual pattern for terms.
33
+ link_success: "%{source} has been linked to %{target}."
34
+ linked_to: linked to
35
+ never_modified: "%{term} has never been modified."
36
+ no_terms: There are no terms being tracked yet.
37
+ threshold_not_satisfied: >-
38
+ Terms must have less than or equal to -%{threshold} or greater than or equal to
39
+ %{threshold} karma to be linked or linked to.
40
+ unlink_success: "%{source} has been unlinked from %{target}."
@@ -0,0 +1,339 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "spec_helper"
3
+
4
+ describe Lita::Handlers::Conferenz::Chat, lita_handler: true do
5
+ let(:payload) { double("payload") }
6
+
7
+ prepend_before { registry.register_handler(Lita::Handlers::Conferenz::Config) }
8
+
9
+ before do
10
+ registry.config.handlers.conferenz.cooldown = nil
11
+ registry.config.handlers.conferenz.link_karma_threshold = nil
12
+ described_class.routes.clear
13
+ subject.define_routes(payload)
14
+ end
15
+
16
+ it { is_expected.to route("foo++").to(:increment) }
17
+ it { is_expected.to route("foo--").to(:decrement) }
18
+ it { is_expected.to route("foo++ bar").to(:increment) }
19
+ it { is_expected.to route("foo-- bar").to(:decrement) }
20
+ it { is_expected.to route("foo~~").to(:check) }
21
+ it { is_expected.to route_command("conferenz best").to(:list_best) }
22
+ it { is_expected.to route_command("conferenz worst").to(:list_worst) }
23
+ it { is_expected.to route_command("conferenz modified foo").to(:modified) }
24
+ it do
25
+ is_expected.to route_command("conferenz delete").with_authorization_for(:karma_admins).to(:delete)
26
+ end
27
+ it { is_expected.to route_command("conferenz").to(:list_best) }
28
+ it { is_expected.to route_command("foo += bar").to(:link) }
29
+ it { is_expected.to route_command("foo += bar++").to(:link) }
30
+ it { is_expected.to route_command("foo += bar--").to(:link) }
31
+ it { is_expected.to route_command("foo += bar~~").to(:link) }
32
+ it { is_expected.to route_command("foo -= bar").to(:unlink) }
33
+ it { is_expected.to route_command("foo -= bar++").to(:unlink) }
34
+ it { is_expected.to route_command("foo -= bar--").to(:unlink) }
35
+ it { is_expected.to route_command("foo -= bar~~").to(:unlink) }
36
+ it { is_expected.not_to route("+++++").to(:increment) }
37
+ it { is_expected.not_to route("-----").to(:decrement) }
38
+ it { is_expected.not_to route("foo++bar").to(:increment) }
39
+ it { is_expected.not_to route("foo--bar").to(:decrement) }
40
+
41
+ describe "#increment" do
42
+ it "increases the term's score by one and says the new score" do
43
+ send_message("foo++")
44
+ expect(replies.last).to eq("foo: 1")
45
+ end
46
+
47
+ it "matches multiple terms in one message" do
48
+ send_message("foo++ bar++")
49
+ expect(replies.last).to eq("foo: 1; bar: 1")
50
+ end
51
+
52
+ it "doesn't start from zero if the term already has a positive score" do
53
+ send_message("foo++")
54
+ send_message("foo++")
55
+ expect(replies.last).to eq("foo: 2")
56
+ end
57
+
58
+ it "replies with a warning if term increment is on cooldown" do
59
+ registry.config.handlers.conferenz.cooldown = 10
60
+ send_message("foo++")
61
+ send_message("foo++")
62
+ expect(replies.last).to match(/cannot modify foo/)
63
+ end
64
+
65
+ it "is case insensitive" do
66
+ send_message("foo++")
67
+ send_message("FOO++")
68
+ expect(replies.last).to eq("foo: 2")
69
+ end
70
+
71
+ it "handles Unicode word characters" do
72
+ send_message("föö++")
73
+ expect(replies.last).to eq("föö: 1")
74
+ end
75
+ end
76
+
77
+ describe "#decrement" do
78
+ it "decreases the term's score by one and says the new score" do
79
+ send_message("foo--")
80
+ expect(replies.last).to eq("foo: -1")
81
+ end
82
+
83
+ it "matches multiple terms in one message" do
84
+ send_message("foo-- bar--")
85
+ expect(replies.last).to eq("foo: -1; bar: -1")
86
+ end
87
+
88
+ it "doesn't start from zero if the term already has a positive score" do
89
+ send_message("foo++")
90
+ send_message("foo--")
91
+ expect(replies.last).to eq("foo: 0")
92
+ end
93
+
94
+ it "replies with a warning if term increment is on cooldown" do
95
+ registry.config.handlers.conferenz.cooldown = 10
96
+ send_message("foo--")
97
+ send_message("foo--")
98
+ expect(replies.last).to match(/cannot modify foo/)
99
+ end
100
+ end
101
+
102
+ describe "#check" do
103
+ it "says the term's current score" do
104
+ send_message("foo~~")
105
+ expect(replies.last).to eq("foo: 0")
106
+ end
107
+
108
+ it "matches multiple terms in one message" do
109
+ send_message("foo~~ bar~~")
110
+ expect(replies.last).to eq("foo: 0; bar: 0")
111
+ end
112
+
113
+ it "doesn't match the same term multiple times in one message" do
114
+ send_message("foo~~ foo~~")
115
+ expect(replies.size).to eq(1)
116
+ end
117
+ end
118
+
119
+ describe "#list" do
120
+ it "replies with a warning if there are no terms" do
121
+ send_command("conferenz")
122
+ expect(replies.last).to match(/no terms being tracked/)
123
+ end
124
+
125
+ context "with modified terms" do
126
+ before do
127
+ send_message(
128
+ "one++ one++ one++ two++ two++ three++ four++ four-- five--"
129
+ )
130
+ end
131
+
132
+ it "lists the top 5 terms by default" do
133
+ send_command("conferenz")
134
+ expect(replies.last).to eq <<-MSG.chomp
135
+ 1. one (3)
136
+ 2. two (2)
137
+ 3. three (1)
138
+ 4. four (0)
139
+ 5. five (-1)
140
+ MSG
141
+ end
142
+
143
+ it 'lists the bottom 5 terms when passed "worst"' do
144
+ send_command("conferenz worst")
145
+ expect(replies.last).to eq <<-MSG.chomp
146
+ 1. five (-1)
147
+ 2. four (0)
148
+ 3. three (1)
149
+ 4. two (2)
150
+ 5. one (3)
151
+ MSG
152
+ end
153
+
154
+ it "limits the list to the count passed as the second argument" do
155
+ send_command("conferenz best 2")
156
+ expect(replies.last).to eq <<-MSG.chomp
157
+ 1. one (3)
158
+ 2. two (2)
159
+ MSG
160
+ end
161
+ end
162
+ end
163
+
164
+ describe "#link" do
165
+ it "says that it's linked term 2 to term 1" do
166
+ send_command("foo += bar")
167
+ expect(replies.last).to eq("bar has been linked to foo.")
168
+ end
169
+
170
+ it "says that term 2 was already linked to term 1 if it was" do
171
+ send_command("foo += bar")
172
+ send_command("foo += bar")
173
+ expect(replies.last).to eq("bar is already linked to foo.")
174
+ end
175
+
176
+ it "causes term 1's score to be modified by term 2's" do
177
+ send_message("foo++ bar++ baz++")
178
+ send_command("foo += bar")
179
+ send_command("foo += baz")
180
+ send_message("foo~~")
181
+ expect(replies.last).to match(
182
+ /foo: 3 \(1\), linked to: ba[rz]: 1, ba[rz]: 1/
183
+ )
184
+ end
185
+
186
+ context "when link_karma_threshold is set" do
187
+ before do
188
+ registry.config.handlers.conferenz.link_karma_threshold = 1
189
+ end
190
+
191
+ it "doesn't allow a term to be linked if both are below the threshold" do
192
+ send_command("foo += bar")
193
+ expect(replies.last).to include("must have less than")
194
+ end
195
+
196
+ it "doesn't allow a term to be linked if it's below the threshold" do
197
+ send_command("foo++")
198
+ send_command("foo += bar")
199
+ expect(replies.last).to include("must have less than")
200
+ end
201
+
202
+ it "doesn't allow a term to be linked to another term below the threshold" do
203
+ send_command("bar++")
204
+ send_command("foo += bar")
205
+ expect(replies.last).to include("must have less than")
206
+ end
207
+
208
+ it "allows links if both terms meet the threshold" do
209
+ send_command("foo++ bar++")
210
+ send_command("foo += bar")
211
+ expect(replies.last).to include("has been linked")
212
+ send_command("bar += foo")
213
+ expect(replies.last).to include("has been linked")
214
+ end
215
+
216
+ it "uses the absolute value for terms with negative karma" do
217
+ send_command("foo-- bar--")
218
+ send_command("foo += bar")
219
+ expect(replies.last).to include("has been linked")
220
+ send_command("bar += foo")
221
+ expect(replies.last).to include("has been linked")
222
+ end
223
+ end
224
+ end
225
+
226
+ describe "#unlink" do
227
+ it "says that it's unlinked term 2 from term 1" do
228
+ send_command("foo += bar")
229
+ send_command("foo -= bar")
230
+ expect(replies.last).to eq("bar has been unlinked from foo.")
231
+ end
232
+
233
+ it "says that term 2 was not linked to term 1 if it wasn't" do
234
+ send_command("foo -= bar")
235
+ expect(replies.last).to eq("bar is not linked to foo.")
236
+ end
237
+
238
+ it "causes term 1's score to stop being modified by term 2's" do
239
+ send_message("foo++ bar++")
240
+ send_command("foo += bar")
241
+ send_command("foo -= bar")
242
+ send_message("foo~~")
243
+ expect(replies.last).to eq("foo: 1")
244
+ end
245
+ end
246
+
247
+ describe "#modified" do
248
+ it "replies with a message if the term hasn't been modified" do
249
+ send_command("conferenz modified foo")
250
+ expect(replies.last).to match(/never been modified/)
251
+ end
252
+
253
+ it "lists users who have modified the given term in count order" do
254
+ other_user = Lita::User.create("2", name: "Other User")
255
+ send_message("foo++", as: user)
256
+ send_message("foo++", as: user)
257
+ send_message("foo++", as: other_user)
258
+ send_command("conferenz modified foo")
259
+ expect(replies.last).to eq("#{user.name}, #{other_user.name}")
260
+ end
261
+ end
262
+
263
+ describe "#delete" do
264
+ before do
265
+ robot.auth.add_user_to_group!(user, :karma_admins)
266
+ end
267
+
268
+ it "deletes the term" do
269
+ send_message("foo++")
270
+ send_command("conferenz delete foo")
271
+ expect(replies.last).to eq("foo has been deleted.")
272
+ send_message("foo~~")
273
+ expect(replies.last).to eq("foo: 0")
274
+ end
275
+
276
+ it "matches terms exactly, including leading whitespace" do
277
+ term = " 'foo bar* 'baz''/ :"
278
+ subject.redis.zincrby("terms", 1, term)
279
+ send_command("conferenz delete #{term}")
280
+ expect(replies.last).to include("has been deleted")
281
+ end
282
+
283
+ it "clears the modification list" do
284
+ send_message("foo++")
285
+ send_command("conferenz delete foo")
286
+ send_command("conferenz modified foo")
287
+ expect(replies.last).to eq("foo has never been modified.")
288
+ end
289
+
290
+ it "clears the deleted term's links" do
291
+ send_command("foo += bar")
292
+ send_command("foo += baz")
293
+ send_command("conferenz delete foo")
294
+ send_message("foo++")
295
+ expect(replies.last).to eq("foo: 1")
296
+ end
297
+
298
+ it "clears links from other terms connected to the deleted term" do
299
+ send_command("bar += foo")
300
+ send_command("baz += foo")
301
+ send_command("conferenz delete foo")
302
+ send_message("bar++")
303
+ expect(replies.last).to eq("bar: 1")
304
+ send_message("baz++")
305
+ expect(replies.last).to eq("baz: 1")
306
+ end
307
+ end
308
+
309
+ describe "custom term patterns and normalization" do
310
+ before do
311
+ registry.config.handlers.conferenz.term_pattern = /[<:]([^>:]+)[>:]/
312
+ registry.config.handlers.conferenz.term_normalizer = lambda do |term|
313
+ term.to_s.downcase.strip.sub(/[<:]([^>:]+)[>:]/, '\1')
314
+ end
315
+ described_class.routes.clear
316
+ subject.define_routes(payload)
317
+ end
318
+
319
+ it "increments multi-word terms bounded by delimeters" do
320
+ send_message(":Some Thing:++")
321
+ expect(replies.last).to eq("some thing: 1")
322
+ end
323
+
324
+ it "increments terms with symbols that are bounded by delimeters" do
325
+ send_message("<C++>++")
326
+ expect(replies.last).to eq("c++: 1")
327
+ end
328
+
329
+ it "decrements multi-word terms bounded by delimeters" do
330
+ send_message(":Some Thing:--")
331
+ expect(replies.last).to eq("some thing: -1")
332
+ end
333
+
334
+ it "checks multi-word terms bounded by delimeters" do
335
+ send_message(":Some Thing:~~")
336
+ expect(replies.last).to eq("some thing: 0")
337
+ end
338
+ end
339
+ end
@@ -0,0 +1,12 @@
1
+ require "simplecov"
2
+ require "coveralls"
3
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
4
+ SimpleCov::Formatter::HTMLFormatter,
5
+ Coveralls::SimpleCov::Formatter
6
+ ]
7
+ SimpleCov.start { add_filter "/spec/" }
8
+
9
+ require "lita-conferenz"
10
+ require "lita/rspec"
11
+
12
+ Lita.version_3_compatibility_mode = false
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lita-conferenz
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Limanowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lita
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 3.0.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.0.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: coveralls
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: A Lita handler for tracking conferences attendance.
98
+ email:
99
+ - plimanowski+conferenz@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - Rakefile
107
+ - lib/lita-conferenz.rb
108
+ - lib/lita/handlers/conferenz/chat.rb
109
+ - lib/lita/handlers/conferenz/config.rb
110
+ - lib/lita/handlers/conferenz/term.rb
111
+ - lita-conferenz.gemspec
112
+ - locales/en.yml
113
+ - spec/lita/handlers/conferenz/chat_spec.rb
114
+ - spec/spec_helper.rb
115
+ homepage: https://github.com/peel/conferenz
116
+ licenses:
117
+ - MIT
118
+ metadata:
119
+ lita_plugin_type: handler
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.4.8
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: A Lita handler for tracking conferences attendance.
140
+ test_files:
141
+ - spec/lita/handlers/conferenz/chat_spec.rb
142
+ - spec/spec_helper.rb