command_bot 1.0.0

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.
@@ -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: []