pug-bot 0.1.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,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pug
4
+ # Convenience methods for common Results
5
+ class Results
6
+ def self.unknown_input
7
+ Types::Result.error(Strings.unknown_input)
8
+ end
9
+
10
+ def self.missing_actions
11
+ Types::Result.error(Strings.no_actions)
12
+ end
13
+
14
+ def self.invalid_index(index)
15
+ Types::Result.error(Strings.unable_to_start_action(index))
16
+ end
17
+
18
+ def self.enter_inputs(action_name)
19
+ Types::Result.info(Strings.enter_inputs(action_name))
20
+ end
21
+
22
+ def self.no_action_running
23
+ Types::Result.error(Strings.no_action_running)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pug
4
+ # Convenience methods for user facing strings
5
+ class Strings
6
+ def self.no_action_running
7
+ 'There is no action currently running.'
8
+ end
9
+
10
+ def self.error_occurred
11
+ 'An error occurred acting on your action'
12
+ end
13
+
14
+ def self.finished_running(action_name)
15
+ "Finished running #{action_name}"
16
+ end
17
+
18
+ def self.unable_to_start_action(index)
19
+ "Unable to start action at index: #{index}"
20
+ end
21
+
22
+ def self.enter_inputs(action_name)
23
+ "Enter inputs for #{action_name}"
24
+ end
25
+
26
+ def self.unknown_input
27
+ "Sorry, I don't understand that input."
28
+ end
29
+
30
+ def self.no_actions
31
+ 'There are no actions available to run. Did you set some up?'
32
+ end
33
+
34
+ def self.help(commands)
35
+ "These are available commands:\n#{commands}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'telegram/bot'
4
+
5
+ module Pug
6
+ # The client for Telegram interactions
7
+ class TelegramClient < Interfaces::Client
8
+ # @param token [String] API token for Telegram bot
9
+ # @param chat_id [String] Chat id for Telegram bot
10
+ def initialize(token, chat_id)
11
+ @token = token
12
+ @chat_id = chat_id
13
+ end
14
+
15
+ # Configures keyboard with provided markup
16
+ # This can be useful to make shortcuts for Commands
17
+ # @param keyboard_markup [Array<Array<String>>]
18
+ # A 2D array of strings used to populate on the keyboard
19
+ def configure_keyboard(keyboard_markup)
20
+ @keyboard_markup = keyboard_markup || []
21
+ end
22
+
23
+ # Override of {Interfaces::Client#listen}
24
+ # @yieldparam [String] text
25
+ def listen
26
+ perform_with_bot do |bot|
27
+ bot.listen do |message|
28
+ next if message.nil?
29
+ text = message.text
30
+ next if text.nil?
31
+ yield text
32
+ end
33
+ end
34
+ end
35
+
36
+ # Override of {Interfaces::Client#send_message}
37
+ # @return [void]
38
+ def send_message(message)
39
+ return if message.to_s.empty?
40
+ send_telegram_message(message)
41
+ end
42
+
43
+ private
44
+
45
+ def send_telegram_message(message)
46
+ reply_markup = Telegram::Bot::Types::ReplyKeyboardMarkup.new(
47
+ keyboard: @keyboard_markup || []
48
+ )
49
+ perform_with_bot do |bot|
50
+ bot.api.send_message(
51
+ chat_id: @chat_id,
52
+ text: message,
53
+ reply_markup: reply_markup
54
+ )
55
+ end
56
+ end
57
+
58
+ def client
59
+ return @client if @client
60
+ raise 'No Telegram token provided' if @token.to_s.empty?
61
+ @client = Telegram::Bot::Client.new(@token)
62
+ end
63
+
64
+ def perform_with_bot
65
+ yield client
66
+ rescue StandardError => ex
67
+ puts 'Error performing task with Telegram'
68
+ puts ex
69
+ puts ex.backtrace
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pug
4
+ # The client for Terminal interactions
5
+ class TerminalClient < Interfaces::Client
6
+ # Override of {Interfaces::Client#listen}
7
+ # @yieldparam [String] text
8
+ def listen
9
+ loop do
10
+ message = gets
11
+ yield message.chomp
12
+ end
13
+ end
14
+
15
+ # Override of {Interfaces::Client#send_message}
16
+ # @return [void]
17
+ def send_message(message)
18
+ return if message.to_s.empty?
19
+ puts message.green
20
+ end
21
+ end
22
+ end
23
+
24
+ # Helper class used to spice up Terminal output
25
+ class String
26
+ def colorize(color_code)
27
+ "\e[#{color_code}m#{self}\e[0m"
28
+ end
29
+
30
+ def green
31
+ colorize(32)
32
+ end
33
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pug
4
+ module Types
5
+ # Encapsulates a sucess/failure result
6
+ # @!attribute type
7
+ # @return [Integer] the type of the Result
8
+ # @see Result::SUCCESS
9
+ # @see Result::INFO
10
+ # @see Result::ERROR
11
+ # @!attribute value
12
+ # @return [String] the value of the Result
13
+ # @note exists only for SUCCESS and INFO types
14
+ # @!attribute error
15
+ # @return [String] the output of the Action
16
+ # @note exists only for ERROR type
17
+ class Result
18
+ attr_reader :type, :value, :error
19
+ private_class_method :new
20
+
21
+ # @!group Types
22
+ SUCCESS = 0
23
+ INFO = 1
24
+ ERROR = 2
25
+ # @!endgroup
26
+
27
+ # @!visibility private
28
+ def initialize(type, value, error)
29
+ raise 'Invalid type' unless [SUCCESS, INFO, ERROR].include?(type)
30
+ @type = type
31
+ @value = value
32
+ @error = error
33
+ end
34
+
35
+ # Defines a SUCCESS Result
36
+ # @param value [String] value for Result
37
+ # @return [Result] with value and no error
38
+ def self.success(value)
39
+ new(SUCCESS, value, nil)
40
+ end
41
+
42
+ # Defines an INFO Result
43
+ # @param value [String] value for Result
44
+ # @return [Result] with value and no error
45
+ def self.info(value)
46
+ new(INFO, value, nil)
47
+ end
48
+
49
+ # Defines an ERROR Result
50
+ # @param error [String] error for Result
51
+ # @return [Result] with error and no value
52
+ def self.error(error)
53
+ new(ERROR, nil, error)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pug
4
+ VERSION = '0.1.0'
5
+ end
data/lib/pug.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pug/action/controller'
4
+ require 'pug/action/enumerator'
5
+ require 'pug/action/input'
6
+ require 'pug/action/output'
7
+ require 'pug/clients/factory'
8
+ require 'pug/interfaces/action'
9
+ require 'pug/interfaces/client'
10
+ require 'pug/types/result'
11
+ require 'pug/bot'
12
+ require 'pug/configuration'
13
+ require 'pug/keyword_handler'
14
+ require 'pug/message_handler'
15
+ require 'pug/number_parser'
16
+ require 'pug/results'
17
+ require 'pug/strings'
18
+ require 'pug/telegram_client'
19
+ require 'pug/terminal_client'
20
+ require 'pug/version'
21
+
22
+ # An automation framework for repetitive dev tasks
23
+ # @!attribute configuration
24
+ # @return [Configuration] the Pug config object
25
+ module Pug # :nodoc:
26
+ class << self
27
+ attr_writer :configuration
28
+ end
29
+
30
+ # Grabs the current Pug configuration
31
+ # @return [Configuration] the Pug config object
32
+ def self.configuration
33
+ default_type = Configuration::TELEGRAM
34
+ @configuration ||= Configuration.new(default_type)
35
+ end
36
+
37
+ # @yieldparam [Configuration] configuration
38
+ def self.configure
39
+ yield(configuration)
40
+ end
41
+ end
data/pug-bot.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ require 'pug/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'pug-bot'
5
+ s.version = Pug::VERSION
6
+ s.authors = ['Alex Figueroa']
7
+ s.email = 'alexjfigueroa [ at ] gmail [ dot ] com'
8
+
9
+ s.summary = 'An automation framework for repetitive dev tasks'
10
+ s.homepage = 'https://github.com/ajfigueroa/pug-bot'
11
+ s.license = 'MIT'
12
+
13
+ s.files = Dir['lib/**/*'] + %w(Gemfile LICENSE README.md Rakefile pug-bot.gemspec)
14
+ s.test_files = Dir['spec/**/*']
15
+ s.require_paths = ['lib']
16
+ s.required_ruby_version = '>= 2.3'
17
+
18
+ s.add_runtime_dependency 'telegram-bot-ruby', '~> 0.8.6', '>= 0.8.6'
19
+
20
+ s.add_development_dependency 'rspec', '~> 3.7.0', '>= 3.7.0'
21
+ s.add_development_dependency 'rubocop', '~> 0.53.0', '>= 0.53.0'
22
+ s.add_development_dependency 'yard', '~> 0.9.2', '>= 0.9.2'
23
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pug'
4
+ require_relative '../../spec_helpers/mock_action'
5
+
6
+ describe Pug::Action::Controller do
7
+ describe 'actions?' do
8
+ it 'should return false for nil actions' do
9
+ controller = Pug::Action::Controller.new(nil)
10
+ expect(controller.actions?).to be false
11
+ end
12
+
13
+ it 'should return false for empty actions' do
14
+ controller = Pug::Action::Controller.new([])
15
+ expect(controller.actions?).to be false
16
+ end
17
+
18
+ it 'should return true for any actions' do
19
+ action = MockAction.new('Command')
20
+ controller = Pug::Action::Controller.new([action])
21
+ expect(controller.actions?).to be true
22
+ end
23
+ end
24
+
25
+ describe 'running_action?' do
26
+ it 'should return false when not running a action' do
27
+ action = MockAction.new('Command')
28
+ controller = Pug::Action::Controller.new([action])
29
+ expect(controller.running_action?).to be false
30
+ end
31
+
32
+ it 'should return true when running a action' do
33
+ action = MockAction.new('Command')
34
+ controller = Pug::Action::Controller.new([action])
35
+ controller.start_action(0)
36
+ expect(controller.running_action?).to be true
37
+ end
38
+ end
39
+
40
+ describe 'action_input' do
41
+ it 'should raise an error when not running a action' do
42
+ action = MockAction.new('Command')
43
+ controller = Pug::Action::Controller.new([action])
44
+ expect { controller.action_input }.to raise_error(RuntimeError)
45
+ end
46
+
47
+ it 'should indicate false when running action requires no inputs' do
48
+ action = MockAction.new('Command', false)
49
+ controller = Pug::Action::Controller.new([action])
50
+ controller.start_action(0)
51
+ input = controller.action_input
52
+ expect(input.required?).to be false
53
+ end
54
+
55
+ it 'should indicate true when running action requires inputs' do
56
+ action = MockAction.new('Command', true)
57
+ controller = Pug::Action::Controller.new([action])
58
+ controller.start_action(0)
59
+ input = controller.action_input
60
+ expect(input.required?).to be true
61
+ end
62
+ end
63
+
64
+ describe 'can_start_action?' do
65
+ it 'should return false for out of bounds index' do
66
+ action = MockAction.new('Command')
67
+ controller = Pug::Action::Controller.new([action])
68
+ expect(controller.can_start_action?(-1)).to be false
69
+ expect(controller.can_start_action?(1)).to be false
70
+ end
71
+
72
+ it 'should return false if there is already a running action' do
73
+ action = MockAction.new('Command')
74
+ controller = Pug::Action::Controller.new([action])
75
+ controller.start_action(0)
76
+ expect(controller.can_start_action?(0)).to be false
77
+ end
78
+
79
+ it 'should return true if there is no already running action' do
80
+ action = MockAction.new('Command')
81
+ controller = Pug::Action::Controller.new([action])
82
+ expect(controller.can_start_action?(0)).to be true
83
+ end
84
+ end
85
+
86
+ describe 'start_action' do
87
+ it 'should not start running a action if it can not start action' do
88
+ action = MockAction.new('Command')
89
+ controller = Pug::Action::Controller.new([action])
90
+ controller.start_action(10)
91
+ expect(controller.running_action?).to be false
92
+ end
93
+
94
+ it 'should start running a action if it can start action' do
95
+ action = MockAction.new('Command')
96
+ controller = Pug::Action::Controller.new([action])
97
+ controller.start_action(0)
98
+ expect(controller.running_action?).to be true
99
+ end
100
+ end
101
+
102
+ describe 'run_action' do
103
+ it 'should return an error result if not running action' do
104
+ action = MockAction.new('Command')
105
+ controller = Pug::Action::Controller.new([action])
106
+ allow(action).to receive(:requires_inputs?) { true }
107
+ result = controller.run_action('')
108
+ expect(result.type).to eq(Pug::Types::Result::ERROR)
109
+ expect(result.error).to eq(Pug::Strings.no_action_running)
110
+ end
111
+
112
+ it 'should return success with empty string if action has no output' do
113
+ action = MockAction.new('Command', true, nil)
114
+ controller = Pug::Action::Controller.new([action])
115
+ controller.start_action(0)
116
+ result = controller.run_action('')
117
+ expect(result.type).to eq(Pug::Types::Result::SUCCESS)
118
+ expect(result.value.value).to be_empty
119
+ end
120
+
121
+ it 'should return success with output if action has an output' do
122
+ action = MockAction.new('Command', true, 'Output')
123
+ controller = Pug::Action::Controller.new([action])
124
+ controller.start_action(0)
125
+ result = controller.run_action('')
126
+ expect(result.type).to eq(Pug::Types::Result::SUCCESS)
127
+ expect(result.value.value).to eq('Output')
128
+ end
129
+
130
+ it 'should clear the running action' do
131
+ action = MockAction.new('Command', true, 'Output')
132
+ controller = Pug::Action::Controller.new([action])
133
+ controller.start_action(0)
134
+ controller.run_action('')
135
+ expect(controller.running_action?).to be false
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pug'
4
+ require_relative '../../spec_helpers/mock_action'
5
+
6
+ describe Pug::Action::Enumerator do
7
+ describe 'names' do
8
+ it 'should return an empty array for nil actions' do
9
+ enumerator = Pug::Action::Enumerator.new
10
+ expect(enumerator.names(nil)).to be_empty
11
+ end
12
+
13
+ it 'should return an empty array for empty actions' do
14
+ enumerator = Pug::Action::Enumerator.new
15
+ expect(enumerator.names([])).to be_empty
16
+ end
17
+
18
+ it 'should return a single enumeration for a single action' do
19
+ action = MockAction.new('Command')
20
+ enumerator = Pug::Action::Enumerator.new
21
+ expect(enumerator.names([action])).to eq(['0: Command'])
22
+ end
23
+
24
+ it 'should return multiple enumerations for multiple actions' do
25
+ action0 = MockAction.new('Command0')
26
+ action1 = MockAction.new('Command1')
27
+ action2 = MockAction.new('Command2')
28
+ actions = [action0, action1, action2]
29
+ enumerator = Pug::Action::Enumerator.new
30
+ expected = ['0: Command0', '1: Command1', '2: Command2']
31
+ expect(enumerator.names(actions)).to eq(expected)
32
+ end
33
+
34
+ it 'should not show the description if it is nil' do
35
+ action = MockAction.new('Command')
36
+ action.mock_description = nil
37
+ enumerator = Pug::Action::Enumerator.new
38
+ expect(enumerator.names([action], true)).to eq(['0: Command'])
39
+ end
40
+
41
+ it 'should not show the description if it is empty' do
42
+ action = MockAction.new('Command')
43
+ enumerator = Pug::Action::Enumerator.new
44
+ expect(enumerator.names([action], true)).to eq(['0: Command'])
45
+ end
46
+
47
+ it 'should show the description if it is valid' do
48
+ action = MockAction.new('Command')
49
+ action.mock_description = 'ABOUT'
50
+ enumerator = Pug::Action::Enumerator.new
51
+ expect(enumerator.names([action], true)).to eq(['0: Command # ABOUT'])
52
+ end
53
+ end
54
+
55
+ describe 'grouped_names' do
56
+ it 'should return an empty array for nil actions' do
57
+ enumerator = Pug::Action::Enumerator.new
58
+ expect(enumerator.grouped_names(nil)).to be_empty
59
+ end
60
+
61
+ it 'should return an empty array for a negative group size' do
62
+ action = MockAction.new('Command')
63
+ enumerator = Pug::Action::Enumerator.new
64
+ expect(enumerator.grouped_names([action], -10)).to eq([])
65
+ end
66
+
67
+ it 'should return an empty array for a group size of 0' do
68
+ action = MockAction.new('Command')
69
+ enumerator = Pug::Action::Enumerator.new
70
+ expect(enumerator.grouped_names([action], 0)).to eq([])
71
+ end
72
+
73
+ it 'should return an empty array for empty actions' do
74
+ enumerator = Pug::Action::Enumerator.new
75
+ expect(enumerator.grouped_names([])).to be_empty
76
+ end
77
+
78
+ it 'should return a subarray when less actions than group size' do
79
+ action = MockAction.new('Command')
80
+ enumerator = Pug::Action::Enumerator.new
81
+ expected = [['0: Command']]
82
+ expect(enumerator.grouped_names([action], 10)).to eq(expected)
83
+ end
84
+
85
+ it 'should return a subarray when equal actions to group size' do
86
+ action0 = MockAction.new('Command0')
87
+ action1 = MockAction.new('Command1')
88
+ actions = [action0, action1]
89
+ enumerator = Pug::Action::Enumerator.new
90
+ expected = [['0: Command0', '1: Command1']]
91
+ expect(enumerator.grouped_names(actions, 2)).to eq(expected)
92
+ end
93
+
94
+ it 'should return multiple subarrays when more actions than group size' do
95
+ action0 = MockAction.new('Command0')
96
+ action1 = MockAction.new('Command1')
97
+ action2 = MockAction.new('Command2')
98
+ actions = [action0, action1, action2]
99
+ enumerator = Pug::Action::Enumerator.new
100
+ expected = [['0: Command0', '1: Command1'], ['2: Command2']]
101
+ expect(enumerator.grouped_names(actions, 2)).to eq(expected)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pug'
4
+ require_relative '../spec_helpers/mock_action'
5
+ require_relative '../spec_helpers/mock_client'
6
+
7
+ describe Pug::Bot do
8
+ describe 'start' do
9
+ before(:each) do
10
+ @client = MockClient.new
11
+ action0 = MockAction.new('Action0', false, 'Output0')
12
+ action1 = MockAction.new('Action1', true, 'Output1')
13
+ @bot = Pug::Bot.new(@client, [action0, action1])
14
+ end
15
+
16
+ it 'should respond to "help" command' do
17
+ @client.enqueue_message('help')
18
+ expected = Pug::Strings.help('list')
19
+ @bot.start
20
+ expect(@client.last_sent_message).to eq(expected)
21
+ end
22
+
23
+ it 'should respond to "list" command' do
24
+ @client.enqueue_message('list')
25
+ expected = "0: Action0\n1: Action1"
26
+ @bot.start
27
+ expect(@client.last_sent_message).to eq(expected)
28
+ end
29
+
30
+ describe 'when there are no commands' do
31
+ it 'should indicate there are no commands' do
32
+ client = MockClient.new
33
+ client.enqueue_message('0')
34
+ bot = Pug::Bot.new(client, [])
35
+ expected = Pug::Strings.no_actions
36
+ bot.start
37
+ expect(client.last_sent_message).to eq(expected)
38
+ end
39
+ end
40
+
41
+ describe 'when a command is not running' do
42
+ it 'should respond to an unknown input' do
43
+ @client.enqueue_message('asdfadf')
44
+ expected = Pug::Strings.unknown_input
45
+ @bot.start
46
+ expect(@client.last_sent_message).to eq(expected)
47
+ end
48
+
49
+ it 'should respond to an action call with no inputs required' do
50
+ @client.enqueue_message('0')
51
+ expected0 = 'Output0'
52
+ expected1 = Pug::Strings.finished_running('Action0')
53
+ @bot.start
54
+ expect(@client.sent_messages).to eq([expected0, expected1])
55
+ end
56
+
57
+ it 'should respond to an action call with inputs required' do
58
+ @client.enqueue_message('1')
59
+ expected = Pug::Strings.enter_inputs('Action1')
60
+ @bot.start
61
+ expect(@client.last_sent_message).to eq(expected)
62
+ end
63
+ end
64
+
65
+ describe 'when a command is running' do
66
+ it 'should pass the input and run the command awaiting input' do
67
+ @client.enqueue_message('1')
68
+ @client.enqueue_message('Input')
69
+ expected0 = Pug::Strings.enter_inputs('Action1')
70
+ expected1 = 'Output1'
71
+ expected2 = Pug::Strings.finished_running('Action1')
72
+ expected = [expected0, expected1, expected2]
73
+ @bot.start
74
+ expect(@client.sent_messages).to eq(expected)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pug'
4
+
5
+ describe Pug::Clients::Factory do
6
+ describe 'client_for_config' do
7
+ it 'should return terminal for a nil config' do
8
+ client = Pug::Clients::Factory.client_for_config(nil)
9
+ expect(client).to be_an_instance_of(Pug::TerminalClient)
10
+ end
11
+
12
+ it 'should return terminal for a terminal config' do
13
+ config = Pug::Configuration.new(Pug::Configuration::TERMINAL)
14
+ client = Pug::Clients::Factory.client_for_config(config)
15
+ expect(client).to be_an_instance_of(Pug::TerminalClient)
16
+ end
17
+
18
+ it 'should return telegram for a telegram config' do
19
+ config = Pug::Configuration.new(Pug::Configuration::TELEGRAM)
20
+ client = Pug::Clients::Factory.client_for_config(config)
21
+ expect(client).to be_an_instance_of(Pug::TelegramClient)
22
+ end
23
+ end
24
+ end