summaryrefslogtreecommitdiff
path: root/lib/commands.sh
blob: b914a58b0ed0ac57a564e2b2e37aa72ac54a472c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
#                                                                         #
#  envbot - an IRC bot in bash                                            #
#  Copyright (C) 2007-2008  Arvid Norlander                               #
#                                                                         #
#  This program is free software: you can redistribute it and/or modify   #
#  it under the terms of the GNU General Public License as published by   #
#  the Free Software Foundation, either version 3 of the License, or      #
#  (at your option) any later version.                                    #
#                                                                         #
#  This program is distributed in the hope that it will be useful,        #
#  but WITHOUT ANY WARRANTY; without even the implied warranty of         #
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          #
#  GNU General Public License for more details.                           #
#                                                                         #
#  You should have received a copy of the GNU General Public License      #
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.  #
#                                                                         #
###########################################################################
#---------------------------------------------------------------------
## Handle registering of commands
#---------------------------------------------------------------------

#---------------------------------------------------------------------
## List of commands (maps to function for the command), a hash
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_list=''

#---------------------------------------------------------------------
## List of functions (by module), a hash
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_modules_functions=''

#---------------------------------------------------------------------
## List of commands (by function), a hash
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_function_commands=''

#---------------------------------------------------------------------
## List of commands (by module)
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_module_commands=''

#---------------------------------------------------------------------
## List of modules (by command)
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_commands_module=''

#---------------------------------------------------------------------
## Comma separated list of all commands
## @Type Semi-private
#---------------------------------------------------------------------
commands_commands=''

# Just unset dummy variables.
unset commands_list commands_modules_functions commands_function_commands commands_module_commands commands_module_commands

#---------------------------------------------------------------------
## Register a command.
## @Type API
## @param Module name
## @param Function name (Part after module_modulename_handler_)
## @param Command name (on IRC, may contain spaces) (optional, defaults to same as function name, that is $2)
## @return 0 If successful
## @return 1 If failed for other reason
## @return 2 If invalid command name
## @return 3 If the command already exists (maybe from some other module)
## @return 4 If the function already exists for other command.
## @return 5 If the function in question is not declared.
#---------------------------------------------------------------------
commands_register() {
	# Speed isn't that important here, it is only called at module load after all.
	local module="$1"
	local function_name="$2"
	local command_name="$3"
	# Command name is optional
	if [[ -z $command_name ]]; then
		command_name="$function_name"
	fi
	# Check for valid command name
	if ! [[ $command_name =~ ^[a-zA-Z0-9] ]]; then
		log_error "commands_register_command: Module \"$module\" gave invalid command name \"$command_name\". First char of command must be alphanumeric."
		return 2
	fi
	if ! [[ $command_name =~ ^[a-zA-Z0-9][^\ ,]*( [^, ]+)?$ ]]; then
		log_error "commands_register_command: Module \"$module\" gave invalid command name \"$command_name\". A command can be at most 2 words and should have no trailing white space and may not contain a \",\" (comma)."
		return 2
	fi
	# Bail out if command is already registered.
	if hash_exists 'commands_list' "$command_name"; then
		log_error "commands_register_command: Failed to register command from \"$module\": a command with the name \"$command_name\" already exists."
		return 3
	fi
	# Bail out if the function already is mapped to some other command
	if hash_exists 'commands_function_commands' "$function_name"; then
		log_error "commands_register_command: Failed to register command from \"$module\": the function is already registered under another command name."
		return 4
	fi

	# Does the function itself exist?
	local full_function_name="module_${module}_handler_${function_name}"
	if ! declare -F | grep -qe "^declare -f ${full_function_name}$"; then
		log_error "commands_register_command: Failed to register command from \"$module\": the function $full_function_name does not exist"
		return 5
	fi
	# So it was valid. Lets add it then.

	# Store in module -> function mapping.
	hash_append 'commands_modules_functions' "$module" "$function_name" || {
		log_error "commands_register_command: module -> commands mapping failed: mod=\"$module\" func=\"$function_name\"."
		return 1
	}
	# Store in command -> function mapping
	hash_set 'commands_list' "$command_name" "$full_function_name" || {
		log_error "commands_register_command: command -> function mapping failed: cmd=\"$command_name\" full_func=\"$full_function_name\"."
		return 1
	}
	# Store in function -> command mapping
	hash_set 'commands_function_commands' "$function_name" "$command_name" || {
		log_error "commands_register_command: function -> command mapping failed: func=\"$function_name\" cmd=\"$command_name\"."
		return 1
	}
	# Store in command -> module mapping
	hash_set 'commands_commands_module' "$command_name" "$module" || {
		log_error "commands_register_command: command -> module mapping failed: cmd=\"$command_name\" mod=\"$module\"."
		return 1
	}
	# Store in module -> commands mapping (ick!)
	hash_append 'commands_module_commands' "$module" "$command_name" ',' || {
		log_error "commands_register_command: module -> command mapping failed: mod=\"$module\" cmd=\"$command_name\"."
	}
	# Store in comma-separated command list
	if [[ $commands_commands ]]; then
		commands_commands+=",$command_name" || return 1
	else
		commands_commands="$command_name" || return 1
	fi
}

#---------------------------------------------------------------------
## Get what module provides a command.
## @param Command to find.
## @param Variable to return in
## @Type Semi-private
#---------------------------------------------------------------------
commands_provides() {
	hash_get "commands_commands_module" "$1" "$2"
}

#---------------------------------------------------------------------
## Get what commands exist in a module.
## @param Command to find.
## @param Variable to return comma separated list in
## @Type Semi-private
#---------------------------------------------------------------------
commands_in_module() {
	hash_get "commands_module_commands" "$1" "$2"
}

#---------------------------------------------------------------------
## Will remove all commands from a module and unset the functions in question.
## @Type Private
## @param Module
## @return 0 If successful (or no commands exist for module)
## @return 1 If error
## @return 2 If fatal error
#---------------------------------------------------------------------
commands_unregister() {
	local module="$1"
	# Are there any commands for the module?
	hash_exists 'commands_modules_functions' "$module" || {
		return 0
	}
	local function_name full_function_name command_name functions
	# Get list of functions
	hash_get 'commands_modules_functions' "$module" 'functions' || return 2
	# Iterate through the functions
	for function_name in $functions; do
		# Get command name
		hash_get 'commands_function_commands' "$function_name" 'command_name' || return 2
		# Unset from function -> command hash
		hash_unset 'commands_function_commands' "$function_name" || return 2
		# Unset from command -> function hash
		hash_unset 'commands_list' "$command_name" || return 2
		# Unset from command -> module mapping
		hash_unset 'commands_commands_module' "$command_name" || return 2
		# Remove from command list.
		list_remove 'commands_commands' "$command_name" 'commands_commands' "," || return 1
		# Unset help strings (if any):
		unset helpentry_${module}_${function_name}_syntax
		unset helpentry_${module}_${function_name}_description
		# Unset function itself.
		full_function_name="module_${module}_handler_${function_name}"
		unset "$full_function_name" || return 2
	done
	# Unset the module -> commands mapping.
	hash_unset 'commands_module_commands' "$module" || return 2
	# Finally unset module -> functions mapping.
	hash_unset 'commands_modules_functions' "$module" || return 2
}

#---------------------------------------------------------------------
## Process a line finding what command it would be
## @Type Private
## @param Sender
## @param Target
## @param Query
## @return 0 If not a command
## @return 1 If it indeed was a command that we therefore handled.
## @return 2 A command but that didn't exist.
#---------------------------------------------------------------------
commands_call_command() {
	local regex="${config_commands_listenregex}"
	# Not on a channel?
	if [[ ! $2 =~ ^# ]]; then
		# Should we treat it as a command anyway?
		if [[ $config_commands_private_always == 1 ]]; then
			local regex="(${config_commands_listenregex})?"
		fi
	fi
	# Check if it is a command.
	# (${config_commands_listenregex}, followed by an alphanumeric char.)
	if [[ "$3" =~ ^${regex}([a-zA-Z0-9].*) ]]; then
		local data="${BASH_REMATCH[@]: -1}"
		# Right, get the parts of the command
		if [[ $data =~ ^([a-zA-Z0-9][^ ]*)( [^, ]+)?( .*)? ]]; then
			local firstword="${BASH_REMATCH[1]}"
			local secondword="${BASH_REMATCH[2]}"
			local parameters="${BASH_REMATCH[3]}"

			local function=
			# Check for two word commands first.
			hash_get 'commands_list' "${firstword}${secondword}" 'function'
			if [[ -z "$function" ]]; then
				# Maybe one word then?
				hash_get 'commands_list' "$firstword" 'function'
				if [[ "$function" ]]; then
					parameters="${secondword}${parameters}"
				# No, not that either
				else
					return 2
				fi
			fi

			# So we got a command, now lets run it
			# (strip leading white spaces) from parameters.
			"$function" "$1" "$2" "${parameters## }"
			return 1
		fi
		return 2
	fi
	return 0
}