summaryrefslogtreecommitdiff
path: root/wrapper_command.py
blob: d630a5da76ff6a8d091cd78e7c69c684a7490bd4 (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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
from wrapper_player import *
from helpers import *

class Command(object):
    """
      # Documentation to come.s
    """

    SENDER_ANY = 0
    SENDER_PLAYER = 1
    SENDER_CONSOLE = 2

    ACTION_IGNORE = 3
    ACTION_SYNTAXERROR = 4
    ACTION_DISPLAYSYNTAX = 5
    ACTION_DISPLAYHELP = 6

    def __init__(self,
                command, 
                parent = None,
                aliases = tuple(), 
                permission = None,
                description = "Description",
                type = 0, 
                no_arg_action = 3,
                help_request_action = 3,
                arguments = tuple(), 
        ):

        self.command = command.lower()
        self.aliases = tuple(alias.lower() for alias in aliases)
        self.description = description
        self.type = type
        self.no_arg_action = no_arg_action
        self.help_request_action = help_request_action
        self.arguments = arguments
        self.parent = parent
        self.sub_commands = Command_dict()

        # ---- Check if argument layout is valid ----
        prev_arg = arguments[0] if len(arguments) > 0 else None
        for arg_info in arguments[1:]:

            if not prev_arg.required and arg_info.required:
                raise Argument_exception("Command: %s; There may not be required arguments after non-required arguments" % command)

            if prev_arg.type == Argument.MESSAGE:
                raise Argument_exception("Command: %s; An argument of type MESSAGE may not be followed by other arguments" % command)

            prev_arg = arg_info

        # ---- Add self to parent sub_commands and set permission node ----
        perm_builder = "utils"
        if self.parent == None:
            root_commands[self.command] = self
        else:
            try:
                parent_route = self.parent.split(" ")
                parent_sub_commands = root_commands
                parent_obj = None
                for cmd_name in parent_route:
                    parent_obj = parent_sub_commands[cmd_name]
                    parent_sub_commands = parent_obj.sub_commands

                parent_obj.sub_commands[self.command] = self

            except KeyError as e:
                raise Argument_exception("Error occurred while setting up command hierarchy: " + e.message + "\n" + trace())

        perm_builder += "." + (permission if permission else command).lower()

    def __call__(self, handler):
        """
          # To clarify: This function is called when you 'call' an instance of a class.
          # This means, that Command() calls __init__() and Command()() calls __call__().
          # This makes it possible to use class instances for decoration. The decorator is this function.
        """
        self.handler = handler

        if self.parent == None:
            @hook.command(self.command, aliases = self.aliases)
            def run(sender, command, label, args):
                """
                  # This function will take care of prefixing and colouring of messages in the future.
                  # So it's very much WIP.
                """
                try:
                    message = self.execute(sender, command, label, args)
                except Command_exception as e:
                    message = e.message
                except Argument_exception as e:
                    message = e.message
                except Exception:
                    error(trace())
                    return True
                if message:
                    sender.sendMessage(message)
                return True

        return handler

    def execute(self, sender, command, label, args):
        try:
            return self.sub_commands[args[0].lower()].execute(sender, command, label, args[1:])
        except (KeyError, IndexError):
            return self.execute_checks(sender, command, label, args)

    def execute_checks(self, sender, command, label, args):

        # ---- Check sender type ----
        if is_player(sender):
            Validate.is_true(self.type != Command.SENDER_CONSOLE, "That command can only be used by the console")
            #sender = py_players.__getattr__(sender)
        else:
            Validate.is_true(self.type != Command.SENDER_PLAYER, "That command can only be used by players")

        # ---- Check permission ----
        Validate.is_authorized(sender, self.permission)

        # ---- Check if a help message is expected ----
        if len(args) == 0:
            action = self.no_arg_action
        elif args[0].lower() == "help":
            action = self.help_request_action
        else:
            action = Command.ACTION_IGNORE

        if action != Command.ACTION_IGNORE:
            if action == Command.ACTION_SYNTAXERROR:
                return "&cInvalid syntax, please try again."
            if action == Command.ACTION_DISPLAYSYNTAX:
                return self.syntax()
            if action == Command.ACTION_DISPLAYHELP:
                return self.help()

        # ---- Set up passed arguments, prepare for handler call ----
        scape = Command_scape(args, self.arguments, command, label)
        if is_player(sender):
            #sender = py_players[sender]
            pass

        return self.handler(sender, self, scape)
        # @Command("hello") def on_hello_command(sender, command, scape/args)

    def syntax(self):
        return " ".join(tuple(arg_info.syntax() for arg_info in self.arguments))

    def help(self):
        syntax = self.syntax()
        return syntax #WIP...

class Argument():

    """
      # A more advanced implementation of amin and amax, though it doesn't do exactly the same.
      # You can now pass a list of Argument objects which define what the argument represents.
      # In the process of doing so, you can set an argument type, one of the ones mentioned below.
      # For example, if Argument.PLAYER is given, the server will be searched for the given player, and
      # they will be passed as the argument, instead of a string representing their name.
      #
      # Feel free to add your own argument types. If you want to make a change to the API to make it different,
      # please don't do so on your own behalf.
    """

    STRING = 0
    INTEGER = 1
    FLOAT = 2
    PLAYER = 3
    OFFLINE_PLAYER = 4
    MESSAGE = 5

    def __init__(self, name, type, definition, required = True):
        self.name = name
        self.type = type
        self.definition = definition
        self.required = required

    def syntax(self):
        syntax = self.name
        if self.type == Argument.MESSAGE:
            syntax += "..."
        return (("<%s>" if self.required else "[%s]") % syntax)

class Validate():

    """
      # Much like what you often see in Java.
      # Instead of having to check if a condition is met, and if not,
      # sending the player a message and returning true,
      # You can use one of these methods to check the condition, and
      # pass a message if it's not met.
      #
      # For example:
      # > if not sender.hasPermission("utils.smth"):
      #       noperm(sender)
      #       return True
      #
      # Can be replaced with:
      # > Validate.is_authorized(sender, "utils.smth")
      #
    """

    @staticmethod
    def is_true(expression, fail_message):
        if not expression:
            raise Command_exception(fail_message)

    @staticmethod
    def not_none(obj, fail_message):
        if obj == None:
            raise Command_exception(fail_message)

    @staticmethod
    def is_authorized(player, permission, msg = "You do not have permission to use that command"):
        if not player.hasPermission(permission):
            raise Command_exception(msg)

    @staticmethod
    def is_player(sender):
        if not is_player(sender):
            raise Command_exception("That command can only be used by players")

    @staticmethod
    def is_console(sender):
        if is_player(sender):
            raise Command_exception("That command can only be used by the console")

"""
  # ---------- API classes ----------
"""

class Command_dict(dict):
    #{"cmd1" : cmd_object}
    def __getattr__(self, alias):
        for cmd_name, cmd_obj in self.iteritems():
            if alias == cmd_name or alias in cmd_obj.aliases:
                return cmd_obj
        raise KeyError("Subcommand '%s' was not found" % alias)

root_commands = Command_dict() # {"command": command_object}

class Command_exception(Exception):

    def __init__(self, message):
        self.message = message

class Command_scape(list):

    def __init__(self, args, arg_layout, command, label):
        super(list, self).__init__()
        self.raw = args
        self.arg_layout = arg_layout
        self.command = command
        self.label = label

        has_message = False
        for i in range(len(arg_layout)):
            arg_info = arg_layout[i]

            given = (len(args) >= i + 1)
            if arg_info.required and not given:
                raise Argument_exception("You must specify the " + arg_info.name)

            if not given:
                self.append(None)
                continue

            given_arg = args[i]
            arg_type = arg_info.type

            if arg_type == Argument.STRING:
                self.append(given_arg)

            elif arg_type == Argument.INTEGER:
                try:
                    value = int(given_arg)
                except ValueError:
                    raise Argument_exception("The %s has to be a round number" % arg_info.name)
                self.append(value)

            elif arg_type == Argument.FLOAT:
                try:
                    value = float(given_arg)
                except ValueError:
                    raise Argument_exception("The %s has to be a number" % arg_info.name)
                self.append(value)

            elif arg_type == Argument.PLAYER:
                target = server.getPlayer(given_arg)
                if target == None:
                    raise Argument_exception("The %s has to be an online player" % arg_info.name)
                self.append(target)
                #self.append(py_players[target])

            elif arg_type == Argument.OFFLINE_PLAYER:
                try:
                    # Code to get the PY PLAYER by name. Possibly, uid(server.getOfflinePlayer(given_arg)) can be used?
                    pass
                except KeyError:
                    raise Argument_exception("The %s has to be an existing player" % arg_info.name)
                self.append(None)

            elif arg_type == Argument.MESSAGE:
                self.append(" ".join(args[i:]))
                has_message = True
            else:
                error("Argument type not found: %d" % arg_type)
                raise Argument_exception("A weird thing has happened, please contact an administrator")

        if not has_message:
            self.remainder = args[len(arg_layout):]
        else:
            self.remainder = None

    def has_flag(self, flag, check_all = False):
        return (("-" + flag) in self.raw) if check_all else (("-" + flag) in self.remainder)

    def get_raw(self):
        return self.raw

    def get_arg_layout(self):
        return self.arg_layout

class Argument_exception(Exception):

    def __init__(self, message):
        self.message = message