command_bot 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ce3490ea16754fc8c40d25e6a1c88ca15ecbb2457bb380b466c47797ad8d0af3
4
+ data.tar.gz: 7aff185e761c5770f853cb78053dc57b81c9ecc388ad61e93642ee98e8f2121d
5
+ SHA512:
6
+ metadata.gz: b0ec894158f3d357ef9267c40549b5b8869184eda4b4beb7a30817ddd3440744108e0fa80d57a921ce493375dc050c40eed7f7e4916d808d8cdb5a77c2cc95a8
7
+ data.tar.gz: 831ea48cfaf594e2375a651798a1a0f775c09bbcbb6854a4a383674e87f2a0296ad75a22c190717f87bd17abfbd4210eb065da94cb839002b133ab35d80043e4
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ Gemfile.lock
@@ -0,0 +1,36 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ require:
4
+ - rubocop-performance
5
+
6
+ AllCops:
7
+ DisplayCopNames: true
8
+ DisplayStyleGuide: true
9
+ ExtraDetails: false
10
+ TargetRubyVersion: 2.6
11
+ Exclude:
12
+ - config/**/*
13
+ - tmp/**/*
14
+ - Capfile
15
+ - Gemfile
16
+ - Rakefile
17
+
18
+ # Follow RubyGuides on this one
19
+ Layout/EndOfLine:
20
+ EnforcedStyle: lf
21
+
22
+ # Just enough
23
+ Layout/LineLength:
24
+ Max: 100
25
+
26
+ # Follow RubyGuides on this one
27
+ Style/HashSyntax:
28
+ EnforcedStyle: ruby19_no_mixed_keys
29
+
30
+ # Prefer using Rubies methods
31
+ Style/HashEachMethods:
32
+ Enabled: true
33
+ Style/HashTransformKeys:
34
+ Enabled: true
35
+ Style/HashTransformValues:
36
+ Enabled: true
@@ -0,0 +1,7 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2020-06-12 22:08:35 +0300 using RuboCop version 0.85.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.6.5
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rake', '~> 12.0'
4
+ gem 'minitest', '~> 5.0'
5
+
6
+ gem 'rubocop', '~> 0.85.0'
7
+ gem 'rubocop-performance', require: false
8
+
9
+ gem 'pry', '~> 0.13.1'
10
+
11
+ gem "logger", "~> 1.4"
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Fizvlad
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,26 @@
1
+ # CommandBot
2
+
3
+ This gem can be used to create highly configurable text bots.
4
+
5
+ ## Installation
6
+
7
+ Add gem to your Gemfile using
8
+
9
+ $ bundle add command_bot
10
+
11
+ ## Usage
12
+
13
+ See `examples` folder.
14
+
15
+ ## Development
16
+
17
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
18
+
19
+ ## Contributing
20
+
21
+ Bug reports and pull requests are welcome on GitHub at https://github.com/fizvlad/command_bot.
22
+
23
+
24
+ ## License
25
+
26
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,48 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rubocop/rake_task'
4
+ RuboCop::RakeTask.new(:rubocop) {}
5
+
6
+ require 'rake/testtask'
7
+ Rake::TestTask.new do |t|
8
+ t.libs << 'test'
9
+ t.test_files = FileList['test/test*.rb']
10
+ t.verbose = true
11
+ end
12
+
13
+ namespace 'rubocop' do
14
+ desc 'Generate rubocop TODO file.'
15
+ task 'todo' do
16
+ puts `rubocop --auto-gen-config`
17
+ end
18
+ end
19
+
20
+ namespace 'yardoc' do
21
+ desc 'Generate documentation'
22
+ task 'generate' do
23
+ puts `yardoc lib/*`
24
+ end
25
+
26
+ desc 'List undocumented elements'
27
+ task 'undoc' do
28
+ puts `yardoc stats --list-undoc lib/*`
29
+ end
30
+ end
31
+
32
+ namespace 'eol' do
33
+ desc 'Replace CRLF with LF.'
34
+ task :dos2unix, [:pattern] do |_t, args|
35
+ path_list = Dir.glob(args.pattern || '**/*.{rb,rake}', File::FNM_EXTGLOB)
36
+ counter = 0
37
+ path_list.each do |path|
38
+ next unless File.file?(path)
39
+
40
+ counter += 1
41
+ puts "Handling `#{path}`..."
42
+ content = File.read(path, mode: 'rb')
43
+ content.gsub!(/\r\n/, "\n")
44
+ File.write(path, content, mode: 'wb')
45
+ end
46
+ puts "Handled #{counter} files."
47
+ end
48
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'command_bot'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ require 'pry'
12
+ Pry.start
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/command_bot/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'command_bot'
7
+ spec.version = CommandBot::VERSION
8
+ spec.authors = ['Fizvlad']
9
+ spec.email = ['fizvlad@mail.ru']
10
+
11
+ spec.summary = 'General purpose text bots'
12
+ spec.homepage = 'https://github.com/fizvlad/command_bot'
13
+ spec.license = 'MIT'
14
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.6.5')
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/fizvlad/command_bot'
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = 'exe'
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ['lib']
27
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ bot = CommandBot::Bot.new(identifier: CommandBot::CommandIdentifier.primitive)
6
+ bot.add_commands(PingCommand, HelpCommand)
7
+
8
+ puts 'Awaiting input:'
9
+ loop do
10
+ text = gets.chomp
11
+ break if text.empty?
12
+
13
+ re = bot.handle(text)
14
+ puts re if re
15
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
4
+ require 'command_bot'
5
+
6
+ PingCommand = CommandBot::Command.new(
7
+ name: 'ping',
8
+ aliases: ['p'],
9
+ data: {
10
+ description: 'ping-pong',
11
+ description_long: 'Ping-pong.'
12
+ }
13
+ ) do |_bot, _arguments, _options, *_other|
14
+ 'pong'
15
+ end
16
+
17
+ HelpCommand = CommandBot::Command.new(
18
+ name: 'help',
19
+ aliases: ['h'],
20
+ data: {
21
+ description: 'show help message',
22
+ description_long: 'Show help message for specified command. Shows list of ' \
23
+ 'command if atgument is not specified.',
24
+ another_info: 'you can pass any data here'
25
+ }
26
+ ) do |bot, arguments, _options, *_other|
27
+ commands = if arguments.empty?
28
+ bot.commands
29
+ else
30
+ bot.commands.select { |e| arguments.include? e.name }
31
+ end
32
+
33
+ help_line = if arguments.empty?
34
+ proc do |e|
35
+ "#{e.name} (also known as #{e.aliases.join(', ')}) " \
36
+ "- #{e.data[:description]}"
37
+ end
38
+ else
39
+ proc do |e|
40
+ "#{e.name} (also known as #{e.aliases.join(', ')}) " \
41
+ "- #{e.data[:description]}\n#{e.data[:description_long]}"
42
+ end
43
+ end
44
+
45
+ <<~HELP
46
+ Commands list:
47
+ #{commands.map(&help_line).join("\n")}
48
+ HELP
49
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ command_identifier1 = CommandBot::CommandIdentifier.default(prefix: '!')
6
+ command_identifier2 = CommandBot::CommandIdentifier.default(prefix: '?')
7
+
8
+ bot1 = CommandBot::Bot.new(identifier: command_identifier1, log_level: Logger::DEBUG)
9
+ bot1.add_commands(PingCommand, HelpCommand)
10
+
11
+ bot2 = CommandBot::Bot.new(identifier: command_identifier2)
12
+ bot2.add_commands(HelpCommand, PingCommand)
13
+
14
+ puts 'Awaiting input:'
15
+ loop do
16
+ text = gets.chomp
17
+ break if text.empty?
18
+
19
+ re1 = bot1.handle(text)
20
+ puts re1 if re1
21
+
22
+ re2 = bot2.handle(text)
23
+ puts re2 if re2
24
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'command_bot/version'
4
+ require 'command_bot/command'
5
+ require 'command_bot/command_call'
6
+ require 'command_bot/command_identifier'
7
+ require 'command_bot/bot'
8
+
9
+ # Main module.
10
+ module CommandBot; end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module CommandBot
6
+ # Main class.
7
+ class Bot
8
+ # Initialize new bot.
9
+ # @paran identifier [CommandIdentifier] defaults to {CommandIdentifier.default}.
10
+ # @param logger [Logger]
11
+ # @param log_level [Logger::Severity]
12
+ # @param command_not_found [Proc] process to execute when command can't be
13
+ # identified. Accepts single argument of {CommandCall}. Defaults to returning +nil+.
14
+ def initialize(identifier: nil, logger: nil, log_level: Logger::INFO, command_not_found: nil)
15
+ @identifier = identifier || CommandIdentifier.default
16
+ @command_not_found = command_not_found || proc { |_command_call| nil }
17
+
18
+ @commands = []
19
+
20
+ log_formatter = proc do |severity, datetime, progname, msg|
21
+ "[#{datetime}] #{severity} - #{progname || object_id}:\t #{msg}\n"
22
+ end
23
+ @logger = logger || Logger.new(STDOUT, formatter: log_formatter)
24
+ @logger.level = log_level
25
+ end
26
+
27
+ # @return [Array<Command>]
28
+ attr_reader :commands
29
+
30
+ # @param name [String]
31
+ # @return [Command, nil]
32
+ def find_command(name)
33
+ logger.debug "Searching for command `#{name}`"
34
+ commands.find { |c| c.name_matches?(name) }
35
+ end
36
+
37
+ # Add new commands.
38
+ # @param commands [Command]
39
+ def add_commands(*commands)
40
+ logger.debug "Adding #{commands.size} new commands"
41
+ all_aliases_array = all_aliases
42
+ commands.each { |c| add_command_unless_alias_is_in_array(c, all_aliases_array) }
43
+ end
44
+
45
+ # Remove commands.
46
+ # @param commands [Command, String]
47
+ def remove_commands(*commands)
48
+ logger.debug "Removing #{commands.size} commands"
49
+ command_names = commands.map { |c| c.is_a?(String) ? c : c.name }
50
+ @commands.reject! { |ec| ec.name_matches?(*command_names) }
51
+ end
52
+
53
+ # Handle message.
54
+ # @param text [String] message text.
55
+ # @param data [Hash] additional data which should be passed to handler procedure.
56
+ # @return [void, nil] result depends on handler of command.
57
+ def handle(text, data = {})
58
+ logger.debug "Handling text: `#{text}` with additional data: #{data}"
59
+ command_call = identify_command_call(text, data)
60
+ return nil if command_call.nil? # Not a command call, so not handling.
61
+
62
+ handle_command_call(command_call)
63
+ end
64
+
65
+ # Hadnle {CommandCall}.
66
+ # @param command_call [CommandCall]
67
+ # @return [void, nil] result depends on handler of command.
68
+ def handle_command_call(command_call)
69
+ logger.debug "Hadnling command call: #{command_call}"
70
+ if command_call.command
71
+ execute_command_call(command_call)
72
+ else
73
+ command_not_found(command_call)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # @return [Logger]
80
+ attr_reader :logger
81
+
82
+ # @return [Array<String>]
83
+ def all_aliases
84
+ re = @commands.map(&:all_aliases)
85
+ re.flatten!
86
+ re
87
+ end
88
+
89
+ # @note modifies +all_aliases_array+
90
+ def add_command_unless_alias_is_in_array(command, all_aliases_array)
91
+ command_aliases = command.all_aliases
92
+ intersection = all_aliases_array & command_aliases
93
+ if intersection.empty?
94
+ @commands << command
95
+ all_aliases_array.concat(command_aliases)
96
+ else
97
+ logger.warn "Command #{command.name} will not be added due to name intersection!"
98
+ end
99
+ end
100
+
101
+ # @return [CommandCall, nil]
102
+ def identify_command_call(text, data)
103
+ @identifier.call(self, text, data)
104
+ end
105
+
106
+ # @return [void]
107
+ def execute_command_call(command_call)
108
+ command_call.execute
109
+ # NOTE: it is possible to execute it from here, but IHMO command_call should
110
+ # be able to execute itself.
111
+ end
112
+
113
+ def command_not_found(command_call)
114
+ @command_not_found.call(command_call)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandBot
4
+ # Stores command handler.
5
+ class Command
6
+ # Initalize new command.
7
+ # @param name [String]
8
+ # @param aliases [Array<String>]
9
+ # @param data [Hash]
10
+ # @yieldparam bot [Bot]
11
+ # @yieldparam arguments [Array<String>]
12
+ # @yieldparam options [Hash<String, String>]
13
+ # @yieldparam other [Array<void>]
14
+ # @yieldreturn [void]
15
+ def initialize(name: '', aliases: [], data: {}, &handler)
16
+ @name = name
17
+ @aliases = aliases
18
+ @data = data
19
+
20
+ @handler = handler
21
+ end
22
+
23
+ # @return [String]
24
+ attr_reader :name
25
+
26
+ # @return [Array<String>]
27
+ attr_reader :aliases
28
+
29
+ # @return [Hash]
30
+ attr_reader :data
31
+
32
+ # @return [Proc]
33
+ attr_reader :handler
34
+
35
+ # @return [Array<String>]
36
+ def all_aliases
37
+ aliases + [name]
38
+ end
39
+
40
+ # @param names [Array<String>]
41
+ # @return [Boolean]
42
+ def name_matches?(*names)
43
+ all_aliases.any? { |a| names.include?(a) }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandBot
4
+ # Stores command call information.
5
+ class CommandCall
6
+ # New command call.
7
+ # @param bot [Bot]
8
+ # @param text [String]
9
+ # @param data [Hash]
10
+ def initialize(bot, text, data)
11
+ @bot = bot
12
+ @text = text
13
+ @data = data
14
+
15
+ @command = nil
16
+ @arguments = []
17
+ @options = {}
18
+ end
19
+
20
+ # @return [Bot]
21
+ attr_reader :bot
22
+
23
+ # @return [String]
24
+ attr_reader :text
25
+
26
+ # @return [Hash]
27
+ attr_reader :data
28
+
29
+ # @return [Command, nil] +nil+ if command can't be found.
30
+ attr_accessor :command
31
+
32
+ # @return [Array<String>]
33
+ attr_accessor :arguments
34
+
35
+ # @return [Hash<String, String>]
36
+ attr_accessor :options
37
+
38
+ # Executes command.
39
+ # @return [void]
40
+ def execute
41
+ raise 'Command not identified!' if command.nil?
42
+
43
+ command.handler.call(bot, arguments, options, data)
44
+ end
45
+
46
+ # @return [String]
47
+ def to_s
48
+ if command.nil?
49
+ "COMMAND_NOT_FOUND(`#{text}`)"
50
+ else
51
+ arg_str = arguments.join(', ')
52
+ opt_str = options.map { |k, v| "#{k}: #{v}" }.join(', ')
53
+ "#{command.name}[#{arg_str}]{#{opt_str}}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandBot
4
+ # Stores command call information.
5
+ class CommandIdentifier
6
+ # Create new identifier.
7
+ # @yieldparam bot [Bot]
8
+ # @yieldparam text [String]
9
+ # @yieldparam data [Hash]
10
+ # @yieldreturn [CommandCall, nil] +nil+ if not a command.
11
+ def initialize(&identifier)
12
+ @identifier = identifier
13
+ end
14
+
15
+ # Call identifier process.
16
+ # @param text [String]
17
+ # @param data [Hash]
18
+ # @return [CommandCall, nil] +nil+ if not a command.
19
+ def call(bot, text, data)
20
+ @identifier.call(bot, text, data)
21
+ end
22
+
23
+ # @param prefix [String] command prefix.
24
+ # @param disallow_whitespace [Boolean] whether to disallow whitespace
25
+ # between prefix and command name.
26
+ # @param o_prefix [String] prefix of option key.
27
+ # @param o_separator [String] separator between option and value.
28
+ # @return [CommandIdentifier]
29
+ def self.default(prefix: '', disallow_whitespace: false, o_prefix: '--', o_separator: '=') # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
30
+ new do |bot, text_original, data|
31
+ command_call = CommandCall.new(bot, text_original, data)
32
+ text = text_original.dup
33
+
34
+ # Not a command if got prefix and text do not start with it
35
+ next nil unless prefix.empty? || text.delete_prefix!(prefix)
36
+ # Check for whitespace
37
+ next nil if disallow_whitespace && text.lstrip!
38
+
39
+ words = text.split
40
+ next nil if words.empty? # Just a prefix
41
+
42
+ command_name = words.shift
43
+ command_name&.downcase! # Usually commands should not depend on case, so downcasing
44
+
45
+ options = _get_options(words, o_prefix, o_separator)
46
+
47
+ command_call.command = bot.find_command(command_name)
48
+ command_call.arguments = words # Everything left is considered to be arguments
49
+ command_call.options = options
50
+ command_call
51
+ end
52
+ end
53
+
54
+ # @return [CommandIdentifier] primitive optionless identifier.
55
+ def self.primitive
56
+ new do |bot, text, data|
57
+ command_call = CommandCall.new(bot, text, data)
58
+ words = text.split
59
+ command_call.command = bot.find_command(words.shift || '')
60
+ command_call.arguments = words
61
+ command_call.options = {}
62
+ command_call
63
+ end
64
+ end
65
+
66
+ # Utility method to get options hash from words array (it *will* be modified).
67
+ # @param words [Array<String>]
68
+ # @param prefix [String] prefix of option key.
69
+ # @param separator [String] separator between option and value.
70
+ def self._get_options(words, prefix, separator)
71
+ options = {}
72
+ while words.first&.start_with?(prefix)
73
+ str = words.shift
74
+ str.downcase!
75
+ str.delete_prefix! prefix
76
+
77
+ # Need to check for splitter in key
78
+ key, *, value = str.partition(separator)
79
+
80
+ # Following line allows writing '!command --key = val' with words=['--key', '=', 'val']
81
+ *, value = words.shift(2) if value.empty? && words.first == separator
82
+
83
+ options[key] = value || ''
84
+ end
85
+ options
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandBot
4
+ # Library version.
5
+ VERSION = '1.0.0'
6
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: command_bot
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Fizvlad
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-06-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - fizvlad@mail.ru
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rubocop.yml"
22
+ - ".rubocop_todo.yml"
23
+ - ".travis.yml"
24
+ - Gemfile
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - bin/console
29
+ - command_bot.gemspec
30
+ - examples/console_bot.rb
31
+ - examples/helper.rb
32
+ - examples/two_console_bots.rb
33
+ - lib/command_bot.rb
34
+ - lib/command_bot/bot.rb
35
+ - lib/command_bot/command.rb
36
+ - lib/command_bot/command_call.rb
37
+ - lib/command_bot/command_identifier.rb
38
+ - lib/command_bot/version.rb
39
+ homepage: https://github.com/fizvlad/command_bot
40
+ licenses:
41
+ - MIT
42
+ metadata:
43
+ homepage_uri: https://github.com/fizvlad/command_bot
44
+ source_code_uri: https://github.com/fizvlad/command_bot
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.6.5
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.1.0.pre2
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: General purpose text bots
64
+ test_files: []