boty 0.1.0 → 0.1.1

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +18 -1
  5. data/README.md +39 -3
  6. data/Rakefile +1 -1
  7. data/boty.gemspec +9 -5
  8. data/lib/boty.rb +2 -1
  9. data/lib/boty/action.rb +26 -35
  10. data/lib/boty/action_description.rb +24 -0
  11. data/lib/boty/bot.rb +36 -25
  12. data/lib/boty/dsl.rb +3 -3
  13. data/lib/boty/eventable.rb +4 -3
  14. data/lib/boty/http.rb +3 -4
  15. data/lib/boty/locale.rb +12 -13
  16. data/lib/boty/logger.rb +14 -3
  17. data/lib/boty/rspec.rb +15 -16
  18. data/lib/boty/session.rb +7 -8
  19. data/lib/boty/slack.rb +11 -3
  20. data/lib/boty/slack/channel.rb +2 -1
  21. data/lib/boty/slack/chat.rb +5 -1
  22. data/lib/boty/slack/rtm.rb +1 -1
  23. data/lib/boty/slack/url.rb +10 -5
  24. data/lib/boty/slack/user.rb +2 -1
  25. data/lib/boty/slack/users.rb +1 -1
  26. data/lib/boty/version.rb +1 -1
  27. data/script/knows.rb +10 -9
  28. data/script/pug.rb +5 -1
  29. data/spec/boty/bot_spec.rb +89 -72
  30. data/spec/boty/cli_spec.rb +5 -5
  31. data/spec/boty/dsl_spec.rb +12 -13
  32. data/spec/boty/rspec_spec.rb +2 -1
  33. data/spec/boty/script_loader_spec.rb +8 -6
  34. data/spec/boty/session_spec.rb +2 -2
  35. data/spec/boty/slack/chat_spec.rb +18 -18
  36. data/spec/boty/slack/im_spec.rb +4 -4
  37. data/spec/boty/slack/message_spec.rb +11 -9
  38. data/spec/boty/slack/rtm_spec.rb +3 -3
  39. data/spec/boty/slack/url_spec.rb +5 -5
  40. data/spec/boty/slack/users_spec.rb +23 -23
  41. data/spec/happy_path_spec.rb +19 -18
  42. data/spec/script/i18n_spec.rb +18 -10
  43. data/spec/script/knows_spec.rb +8 -7
  44. data/spec/script/pug_spec.rb +27 -10
  45. data/spec/script/remember_spec.rb +27 -21
  46. data/spec/spec_helper.rb +4 -3
  47. data/spec/support/em_support.rb +1 -3
  48. data/spec/support/faraday_support.rb +0 -1
  49. data/spec/support/faye_support.rb +9 -6
  50. data/spec/support/file_system_matchers.rb +4 -4
  51. data/spec/support/logger_support.rb +9 -12
  52. data/spec/support/session_support.rb +26 -15
  53. data/spec/support/slack_support.rb +9 -6
  54. data/spec/support/template_project_support.rb +24 -23
  55. data/spec/support/thor_support.rb +3 -3
  56. data/template/project/%bot_name%.rb +4 -1
  57. data/template/project/spec/spec_helper.rb +4 -2
  58. metadata +21 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1b80fa6a35d917745e219d705ad008950daef3b
4
- data.tar.gz: 03fbea6b6493b940a63bcb963ad26c83cc62f756
3
+ metadata.gz: a7102960a8cdaa6f3a18a7015c1338f2c62a28b7
4
+ data.tar.gz: dfd326ea4c467506825ac473f47636682e366bab
5
5
  SHA512:
6
- metadata.gz: 5ffbaa33ad222082cda2865606090e2e4a67051cafdd342a1a0851333594e3fe1514ee516b4b536a96105b54106e86da749ab449c87f1e369cf3009fea6852b4
7
- data.tar.gz: af874f7e181697c3fe8e75b0a3f09f963a37c607a4f066655271e3435f6de4fcce479bde4f0f6ae0f15fc540482eb11030ad2fafc183181bc05f4df3e572d262
6
+ metadata.gz: bab25a32347e1c4e6cb9cc3b8f995fc4d971f094072ef6c5e54018e0d50b5fe37d8c86c9be5e4291a8e4c823cad802ffc2d8f86603a36141eed1ac4adfa7b0da
7
+ data.tar.gz: fb8d9bfa67bcbf7f624b69dc4f7866e7280ccb3be1a655c7117a124f26147ab7002bec1ec1f6736154ac0a57d33173a0f1a4a50e81d707ac703655586328c007
@@ -0,0 +1,4 @@
1
+ Style/StringLiterals:
2
+ EnforcedStyle: double_quotes
3
+ Style/BlockDelimiters:
4
+ EnforcedStyle: semantic
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boty (0.1.0)
4
+ boty (0.1.1)
5
5
  eventmachine
6
6
  faraday
7
7
  faye-websocket
@@ -11,6 +11,9 @@ PATH
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
+ ast (2.1.0)
15
+ astrolabe (1.3.1)
16
+ parser (~> 2.2)
14
17
  byebug (8.2.0)
15
18
  diff-lcs (1.2.5)
16
19
  dotenv (2.0.2)
@@ -23,6 +26,10 @@ GEM
23
26
  websocket-driver (>= 0.5.1)
24
27
  i18n (0.7.0)
25
28
  multipart-post (2.0.0)
29
+ parser (2.2.3.0)
30
+ ast (>= 1.1, < 3.0)
31
+ powerpack (0.1.1)
32
+ rainbow (2.0.0)
26
33
  rake (10.4.2)
27
34
  rspec (3.4.0)
28
35
  rspec-core (~> 3.4.0)
@@ -37,7 +44,16 @@ GEM
37
44
  diff-lcs (>= 1.2.0, < 2.0)
38
45
  rspec-support (~> 3.4.0)
39
46
  rspec-support (3.4.0)
47
+ rubocop (0.35.1)
48
+ astrolabe (~> 1.3)
49
+ parser (>= 2.2.3.0, < 3.0)
50
+ powerpack (~> 0.1)
51
+ rainbow (>= 1.99.1, < 3.0)
52
+ ruby-progressbar (~> 1.7)
53
+ tins (<= 1.6.0)
54
+ ruby-progressbar (1.7.5)
40
55
  thor (0.19.1)
56
+ tins (1.6.0)
41
57
  websocket-driver (0.6.3)
42
58
  websocket-extensions (>= 0.1.0)
43
59
  websocket-extensions (0.1.2)
@@ -53,6 +69,7 @@ DEPENDENCIES
53
69
  fakefs
54
70
  rake (~> 10.0)
55
71
  rspec
72
+ rubocop
56
73
 
57
74
  BUNDLED WITH
58
75
  1.10.6
data/README.md CHANGED
@@ -642,7 +642,7 @@ implementation is fairly simple. Let's use it as an example of how to write your
642
642
  own adapter.
643
643
 
644
644
  You can extend the ruby _Logger_ class and worry yourself on write the `#add`
645
- overrite:
645
+ override:
646
646
 
647
647
  ```ruby
648
648
  class Multi < ::Logger
@@ -667,7 +667,7 @@ adapters passed as parameters for the constructor.
667
667
  For more information on the `#add` parameters, [check the ruby doc](http://ruby-doc.org/stdlib-2.1.0/libdoc/logger/rdoc/Logger.html#method-i-add).
668
668
 
669
669
  _Multi_ also allows you to change the level for all the underlying adapters at
670
- once, the `#level=` overrite implementation is like this:
670
+ once, the `#level=` overriten implementation is like this:
671
671
 
672
672
  ```ruby
673
673
  def level=(level)
@@ -679,7 +679,34 @@ end
679
679
 
680
680
  ### I18n
681
681
 
682
- **todo: document**
682
+ The descriptions for the commands and listeners that ship with `Boty` can be
683
+ customized via the [I18n gem mechanics](https://github.com/svenfuchs/i18n).
684
+ `Boty` ships with translations for `:en` and `:pt-br`. If you want to override
685
+ those messages, you can take a look at the files on [`locale`](https://github.com/ricardovaleriano/boty/tree/master/locale).
686
+
687
+ I'll recomend that you just copy and paste the file that you want to customize
688
+ on the `locale` dir of your project, and them make the editions that you want.
689
+
690
+ Of course, you can add translations for any language that you want on your
691
+ `locale` dir. And even add any translation that you want for your own
692
+ command descriptions.
693
+
694
+ To instruct your bot about which language to use, set the `Boty.locale`
695
+ configuration and you're done.
696
+
697
+ The `bot` executable created on your project generated by `boty new` have the
698
+ following line before the session start:
699
+
700
+ ```ruby
701
+ Boty.locale = ARGV.pop || :en
702
+ ```
703
+
704
+ This means that you can start your bot with a command line argument to tell
705
+ which is the idiom that this session should use. This allows you to have the
706
+ same bot running in different sessions with different idioms. The following
707
+ usage is totally fine:
708
+
709
+ ./bot pt-br
683
710
 
684
711
  ## Development
685
712
 
@@ -691,8 +718,17 @@ To install this gem onto your local machine, run `bundle exec rake install`.
691
718
 
692
719
  ### The local ./bin/bot
693
720
 
721
+ The project have a `./bin/bot` executable that should be the product of
722
+ compiling the `template/project/bot.tt` erb file.
723
+ This is an easy way to just run the bot using the same logic that a project
724
+ generated by `bot new` will use to run the bot.
725
+ It's highly recomendable that you test your changes using this approach before
726
+ submit a `PR` or generate a new release. =)
727
+
694
728
  ### Code guidelines
695
729
 
730
+ **todo: add some**
731
+
696
732
  ## Contributing<a name="contributing" />
697
733
 
698
734
  Bug reports and pull requests are very welcome on GitHub at
data/Rakefile CHANGED
@@ -1,5 +1,5 @@
1
1
  require "bundler/gem_tasks"
2
- require 'rspec/core/rake_task'
2
+ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
  task default: :spec
@@ -1,7 +1,7 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'boty/version'
4
+ require "boty/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "boty"
@@ -9,12 +9,15 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Ricardo Valeriano"]
10
10
  spec.email = ["ricardo.valeriano@gmail.com"]
11
11
 
12
- spec.summary = %q{Boty is a pretty bot specially tailored for Slack.}
13
- spec.description = %q{Boty is intendted to be a framework for construction of automated Slack Bots for your needs.}
12
+ spec.summary = %(Boty is a pretty bot specially tailored for Slack.)
13
+ spec.description = %(Boty is intendted to be a framework for construction of
14
+ automated Slack Bots for your needs.)
14
15
  spec.homepage = "http://github.com/ricardovaleriano/boty"
15
16
  spec.license = "MIT"
16
17
 
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(features)/}) }
18
+ spec.files = `git ls-files -z`.split("\x0").reject {|f|
19
+ f.match(%r{^(features)/})
20
+ }
18
21
  spec.bindir = "exe"
19
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
23
  spec.require_paths = ["lib"]
@@ -32,4 +35,5 @@ Gem::Specification.new do |spec|
32
35
  spec.add_development_dependency "rspec"
33
36
  spec.add_development_dependency "dotenv"
34
37
  spec.add_development_dependency "fakefs"
38
+ spec.add_development_dependency "rubocop"
35
39
  end
@@ -13,7 +13,7 @@ require "faye/websocket"
13
13
  require "i18n"
14
14
  require "faraday"
15
15
 
16
- $:.unshift File.expand_path("../../lib", __FILE__)
16
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
17
17
 
18
18
  module Boty
19
19
  def self.locale=(lang)
@@ -29,6 +29,7 @@ require "boty/version"
29
29
  require "boty/logger"
30
30
  require "boty/slack"
31
31
  require "boty/session"
32
+ require "boty/action_description"
32
33
  require "boty/action"
33
34
  require "boty/script_loader"
34
35
  require "boty/dsl"
@@ -1,53 +1,44 @@
1
1
  module Boty
2
+ # Public: Wrap the idea of something that should happen when some regex is
3
+ # matched.
4
+ #
5
+ #
2
6
  class Action
3
7
  include Boty::Logger
4
8
  attr_reader :regex, :desc, :action
5
9
 
6
- class Description
7
- attr_reader :command, :description
8
- attr_writer :regex
9
-
10
- def initialize(command, description: nil, regex: nil)
11
- if description.nil?
12
- @description = command
13
- else
14
- @command, @description = command, description
15
- end
16
-
17
- @regex = regex
18
- end
19
-
20
- def command
21
- return @command if @command
22
-
23
- match = /\?(i?)-(mx|mix):(.*)\)/.match @regex.to_s
24
- "/#{match[3]}/#{match[1]}"
10
+ def initialize(bot, regex, description, &action)
11
+ if description
12
+ description.regex = regex
13
+ else
14
+ description = ActionDescription.new(nil, regex: regex)
25
15
  end
26
- end
27
16
 
28
- def initialize(regex, desc, &action)
29
- desc = Hash(desc)
30
- description = Description.new(desc[:command],
31
- description: desc[:description],
32
- regex: regex)
33
- @regex, @desc, @action = regex, description, action
17
+ @dsl = DSL.new bot
18
+ @regex = regex
19
+ @desc = description
20
+ @action = action
34
21
  end
35
22
 
36
- def is_this?(regex, block)
23
+ def this?(regex, block)
37
24
  same_regex = regex == self.regex
38
- block ? same_regex && block == self.action : same_regex
25
+ block ? same_regex && block == action : same_regex
39
26
  end
40
27
 
41
- def execute(bot, message)
42
- dsl = DSL.new(bot)
43
- if match = message.match!(regex)
44
- matches = Array(match)
45
- matches.shift
46
- dsl.instance_exec(*matches, &action)
47
- end
28
+ def execute(message)
29
+ action_call message.match!(regex)
48
30
  rescue => e
49
31
  logger.error e.message
50
32
  raise e
51
33
  end
34
+
35
+ private
36
+
37
+ def action_call(match)
38
+ return unless match
39
+ matches = Array(match)
40
+ matches.shift
41
+ @dsl.instance_exec(*matches, &action)
42
+ end
52
43
  end
53
44
  end
@@ -0,0 +1,24 @@
1
+ module Boty
2
+ class ActionDescription
3
+ attr_reader :command, :description
4
+ attr_writer :regex
5
+
6
+ def initialize(command, description: nil, regex: nil)
7
+ if description.nil?
8
+ @description = command
9
+ else
10
+ @command = command
11
+ @description = description
12
+ end
13
+
14
+ @regex = regex
15
+ end
16
+
17
+ def command
18
+ return @command if @command
19
+ return unless @regex
20
+ match = /\?(i?)-(mx|mix):(.*)\)/.match @regex.to_s
21
+ @command = "/#{match[3]}/#{match[1]}"
22
+ end
23
+ end
24
+ end
@@ -8,10 +8,14 @@ module Boty
8
8
 
9
9
  def initialize(bot_info)
10
10
  Locale.reload
11
- @raw_info, @id, @name = bot_info, bot_info["id"], bot_info["name"]
11
+ @raw_info = bot_info
12
+ @id = bot_info["id"]
13
+ @name = bot_info["name"]
14
+
12
15
  @listeners ||= []
13
16
  @commands ||= []
14
17
  @brain ||= {}
18
+
15
19
  on :message, &method(:message_handler)
16
20
  ScriptLoader.new(self).load
17
21
  end
@@ -38,7 +42,8 @@ module Boty
38
42
  end
39
43
 
40
44
  def desc(command, description = nil)
41
- @current_desc = { command: command, description: description }
45
+ @current_desc = ActionDescription.new command,
46
+ description: description
42
47
  end
43
48
 
44
49
  def say(message, api_parameters = {})
@@ -49,33 +54,33 @@ module Boty
49
54
  end
50
55
 
51
56
  def im(text, destiny: nil, to: nil, user_id: nil)
52
- if destiny = User(user_id) || user_by_name(destiny, to)
53
- logger.debug { "Sending #{text} to #{destiny.name}." }
57
+ destiny = User(user_id) || user_by_name(destiny, to)
58
+ if destiny
59
+ logger.debug do "Sending #{text} to #{destiny.name}." end
54
60
  Slack.chat.post_im destiny.id, text
55
61
  else
56
- logger.debug { "User not found, refusing to send im." }
62
+ logger.debug do "User not found, refusing to send im." end
57
63
  end
58
64
  end
59
65
 
60
66
  # TODO: return an Action object instead of a hash
61
67
  def know_how
62
- descriptions = (Array(@commands) + Array(@listeners)).compact.map(&:desc)
63
- descriptions.inject({}) { |hsh, desc|
64
- hsh.merge!(desc.command => desc.description)
65
- }
68
+ actions = Array(@commands) + Array(@listeners)
69
+ actions.sort_by { |action| action.desc.command || "_" }
66
70
  end
67
71
 
68
72
  private
69
73
 
70
74
  def remove_action(collection, regex = nil, block = nil, command: nil)
71
75
  collection.delete_if do |action|
72
- action.desc.command == command || action.is_this?(regex, block)
76
+ action.desc.command == command || action.this?(regex, block)
73
77
  end
74
78
  end
75
79
 
76
80
  def user_by_name(destiny, to)
77
- if _to = to || destiny
78
- Slack.users.by_name _to
81
+ to ||= destiny
82
+ if to
83
+ Slack.users.by_name to
79
84
  else
80
85
  @trigger_message.user
81
86
  end
@@ -83,34 +88,40 @@ module Boty
83
88
 
84
89
  def create_action(regex, &block)
85
90
  regex = Regexp.new(regex) if regex.is_a? String
86
- Boty::Action.new(regex, @current_desc, &block).tap {
91
+ Boty::Action.new(self, regex, @current_desc, &block).tap {
87
92
  @current_desc = nil
88
93
  }
89
94
  end
90
95
 
91
96
  def message_from_bot_itself?(data)
92
- data["user"] == self.id || data["user"] == self.name
97
+ data["user"] == id || data["user"] == name
93
98
  end
94
99
 
95
- def has_valid_mention?(data)
100
+ def valid_mention?(data)
96
101
  return false if message_from_bot_itself? data
97
- !!(/(<@#{id}|#{name}>)/ =~ data["text"])
102
+
103
+ if /(<@#{id}|#{name}>)/ =~ data["text"]
104
+ true
105
+ else
106
+ false
107
+ end
98
108
  end
99
109
 
100
110
  def message_handler(data)
101
111
  unless data["text"]
102
- return logger.debug "Non text message, just ignoring."
112
+ return logger.debug do "Non text message, just ignoring." end
103
113
  end
114
+ actions = valid_mention?(data) ? @commands : @listeners
115
+ execute_all Message.new(data), actions
116
+ end
104
117
 
105
- actions = has_valid_mention?(data) ? @commands : @listeners
106
- @trigger_message = Message.new data
107
- begin
108
- Array(actions).each do |action|
109
- action.execute self, @trigger_message
110
- end
111
- ensure
112
- @trigger_message = nil
118
+ def execute_all(message, actions)
119
+ @trigger_message = message
120
+ Array(actions).each do |action|
121
+ action.execute @trigger_message
113
122
  end
123
+ ensure
124
+ @trigger_message = nil
114
125
  end
115
126
  end
116
127
  end
@@ -7,14 +7,14 @@ module Boty
7
7
  INSTANCES = {}
8
8
  private_constant :INSTANCES
9
9
  class << self
10
- alias original_constructor new
10
+ alias_method :original_constructor, :new
11
11
  end
12
12
 
13
13
  attr_accessor :bot
14
14
 
15
15
  extend Forwardable
16
- def_delegators :bot, :desc, :respond, :name, :brain, :know_how, :im, :say,
17
- :match
16
+ def_delegators :bot,
17
+ :desc, :respond, :name, :brain, :know_how, :im, :say, :match
18
18
  def_delegators :message, :user, :channel
19
19
 
20
20
  def self.new(bot)