## Commands core

Core Module - Commands - Factorio command making module that makes commands with better parse and more modularity

### Usage

    --- Full code example, see below for explanation
    Commands.new_command('repeat-name', 'Will repeat you name a number of times in chat.')
    :add_param('repeat-count', 'number-range-int', 1, 5) -- required int in range 1 to 5 inclusive
    :add_param('smiley', true, function(input, player, reject) -- optional boolean default false
        if not input then return end
        input = input:lower()
        if input == 'true' or input == 'yes' then return true end
        return false
    end)
    :set_defaults{ smiley = false }
    :set_flag('admin_only') -- command is admin only
    :add_alias('name', 'rname') -- allow alias: name and rname
    :register(function(player, repeat_count, smiley, raw)
        game.print(player.name..' used a command with input: '..raw)

        local msg = ') '..player.name
        if smiley then
            msg = ':'..msg
        end

        for 1 = 1, repeat_count do
            Command.print(1..msg)
        end
    end)

    --- Example Command Explanation:
    -- Making commands basics, the commands can be set up with any number of params and flags that you want,
    -- you can add aliases for the commands and set default values for optional params and of course register your command callback.
    -- In our example we will have a command that will repeat the users name in chat X amount of times and only allow admins to use it.

    -- First we create the new command, note this will not register the command to the game this is done at the end.
    -- We will call the command "repeat-name" and set the help message as follows:
    Commands.new_command('repeat-name', 'Will repeat you name a number of times in chat.')

    -- Now for our first param, we have named it "repeat-count" and it will be a required value, between 1 and 5 inclusive:
    -- By using "number-range-int" we are saying to use this parser to convert our input text, common ones exist in config.expcore.command_general_parse
    :add_param('repeat-count', 'number-range-int', 1, 5)

    -- Our second param needs a custom parser, meaning it isnt defined with add_parser, this is an option for when it is unlikely for
    -- any other command to use the same input type. In the example it is a boolean type and we are just showing it here as part of the example.
    -- As for the param its self it will be called "smiley" and will be optional with a default value of false:
    :add_param('smiley', true, function(input, player, reject)
        -- Since it is optional the input can be nil, in which case we just return
        if not input then return end
        -- If it is not nil then we check for a truthy value
        if input == 'true' or input == 'yes' then return true end
        -- Note that because we did not return nil or reject then false will be passed to command callback, see example parse
        return false
    end)

    -- Once all params are defined you can add some default values for your optional params, the default value will be used only
    -- when no value is given as input, if an invalid value is given then the command will fail and the default will not be used, the
    -- default can also be a function which is passed the player as an argument and should return a value to be the default.
    -- Here we set the default for "smiley" to false:
    :set_defaults{smiley=false}

    -- Another example of defaults if we have: item, amount[opt], player[opt]
    :set_defaults{
        amount = 50, -- More than one value can be set at a time
        player = function(player) return player end -- Default is the player using the command
    }

    -- Now the params are set up we can alter how the command works, we can set auth flags, add aliases, or enable "auto concat":
    :set_flag('admin_only') -- In our case we want "admin_only" to be set to true so only admins can use the command
    :add_alias('name', 'rname') -- We also add two aliases here: "name" and "rname" which point to this command
    -- :enable_auto_concat() -- We do not use this in our case but this can also be used to enable the "auto concat" feature

    -- And finally we want to register a callback to this command, the callback is what defines what the command does, can be as complex as you
    -- want it to be, or as simple as our example; the command receives two params plus all param you have defined:
    -- 1) the player who used the command
    -- 2) in our case repeat_count which will be a number
    -- 3) in our case smiley which will be a boolean
    -- 4) the raw input; this param is always last as is always present as a catch all
    :register(function(player, repeat_count, smiley, raw)
        -- This is to show the value for raw as this is an example command, the log file will also show this
        game.print(player.name..' used a command with input: '..raw)
        local msg = ') '..player.name

        if smiley then
            msg = ':'..msg
        end

        for 1 = 1, repeat_count do
            -- this print function will return ANY value to the user in a desync safe manor, this includes if the command was used through rcon
            Command.print(1..msg)
        end
        -- See below for what can be used here
    end)

    -- Values that can be returned from register callback
    Commands.print(any, colour[opt]) -- This will return any value value to the user including if it is ran through rcon console
    Commands.error(message[opt]) -- This returns a warning to the user, aka an error that does not prevent execution of the command
    return Commands.error(message[opt]) -- This returns an error to the user, and will halt the command execution, ie no success message is returned
    Commands.success(message[opt]) -- Used to return a success message however don't use this method, see below
    return Commands.success(message[opt]) -- Will return the success message to the user and your given message, halts execution
    return <any> -- If any value is returned then it will be returned to the player via a Commands.success call

    --- Example Authenticator:
    -- The command system is best used when you can control who uses commands;
    -- to do this you need to define an authenticator which is ran every time a command is run;
    -- in this example I will show a simple one that requires certain commands to require the user to be a game admin.

    -- For our admin only example we will set a flag to true when we want it to be admin only;
    -- when we define the command will will use :set_flag('admin_only');
    -- then inside the authenticator we will test if the flag is present using: if flags.admin_only then

    -- When the authenticator is called by the command handler it will be passed 4 arguments:
    -- 1) player - the player who used the command
    -- 2) command - the name of the command that is being used
    -- 3) flags - the flags which have been set for this command, flags are set with :set_flag(name, value)
    -- 4) reject - the reject function which is the preferred method to prevent execution of the command

    -- No return is required to allow the command to execute but it is best practice to return true;
    -- we do this in two cases in our authenticator:
    -- 1) when the "admin_only" flag is not set, which we take assume that any one can use it
    -- 2) when the "admin_only" flag is set, and the player is admin

    -- When want to prevent execution of the command we must reject it, listed is how that can be done:
    -- 1) return false -- this is the most basic rejection and should only be used while testing
    -- 2) return reject -- returning the reject function is as a fail safe in case you forget to call it, same as returning false
    -- 3) reject() -- this will block execution while allowing further code to be ran in your authenticator
    -- 4) reject('This command is for admins only!') -- using reject as a function allows a error message to be returned
    -- 5) return reject() -- using return on either case above is best practice as you should execute all your code before rejecting

    -- Example Code:
    Commands.add_authenticator(function(player, command, flags, reject)
        -- Check if the command is admin only
        if flags.admin_only then
            -- Return true if player is admin, or reject and return error message
            return player.admin or reject('This command is for admins only!')
        else
            -- Return true if command was not admin only
            return true
        end
    end)

    --- Example Parser:
    -- Before you make a command it is important to understand the most powerful feature of this command handler;
    -- when you define a command you are able to type the params and have then be parsed and validated before your command is executed;
    -- This module should be paired with a general command parse but you may want to create your own.

    -- For our example we will create a parse to accept only integer numbers in a given range:
    -- 1) we will give it the name "number-range-int" this is the "type" that the input is expected to be
    -- 2) when we define the type we will also define the min and max of the range so we can use the function more than once
    :add_param('repeat_count', 'number-range-int', 5, 10) -- "repeat_count" is a required "number-range-int" in a range 5 to 10 inclusive

    -- The command parse will be passed 3 arguments plus any other which you define, in our case:
    -- 1) input - the input that has been given by the user for this param, the role of this function is to transform this value
    -- nb: the input is a string but can be nil if the param is marked as optional
    -- 2) player - the player who is using the command, this is always present
    -- 3) reject - the reject function to throw an error to the user, this is always present
    -- 4) range_min - the range min, this is user defined and has the value given when the param is defined
    -- 5) range_max - the range max, this is user defined and has the value given when the param is defined

    -- When returning from the param parse you have a few options with how to do this:
    -- 1) you return the new value for the param (any non nil value) this value is then passed to the command callback
    -- 2) not returning will cause a generic invalid error and the command is rejected, not recommenced
    -- 3) return reject -- this is just a failsafe in case the function is not called, same as no return
    -- 4) return reject() -- will give a shorter error message as you pass a nil custom error
    -- 5) return reject('Number entered is not in range: '..range_min..', '..range_max) -- returns a custom error to the user
    -- nb: if you do not return reject after you call it then you will still be returning nil so there will be a duplicate error message

    -- It should be noted that if you want to expand on an existing parse you can use Commands.parse(type, input, player, reject)
    -- this function will either return a new value for the input or nil, if it is nil you should return nil to prevent duplicate
    -- error messages to the user:
    input = Commands.parse('number-int', input, player, reject)
    if not input then return end -- nil check

    -- Example Code:
    Commands.add_parse('number-range-int', function(input, player, reject, range_min, range_max)
        local rtn = tonumber(input) and math.floor(tonumber(input)) or nil -- converts input to number
        if not rtn or rtn < range_min or rtn > range_max then
            -- the input is either not a number or is outside the range
            return reject('Number entered is not in range: '..range_min..', '..range_max)
        else
            -- returns the input as a number value rather than a string, thus the param is now the correct type
            return rtn
        end
    end)

### Dependencies

utils.game
expcore.common

### Tables

defines | Constant values used by the command system
commands | An array of all custom commands that are registered
authenticators | An array of all custom authenticators that are registered
parsers | Used to store default functions which are common parse function such as player or number in range
_prototype | The command prototype which stores all command defining functions

### Fields

authorization_failure_on_error | When true any authenticator error will result in authorization failure, more secure
print | Returns a value to the player, different to success as this does not signal the end of your command

### Authentication

add_authenticator(authenticator) | Adds an authorization function, function used to check if a player if allowed to use a command
remove_authenticator(authenticator) | Removes an authorization function, can use the index or the function value
authorize(player, command_name) | Mostly used internally, calls all authenticators, returns if the player is authorized

### Parse

add_parse(name, parser) | Adds a parse function which can be called by name (used in add_param) nb: this is not required as you can use the callback directly this just allows it to be called by name
remove_parse(name) | Removes a parse function, see add_parse for adding them, cant be done during runtime
parse(name, input, player, reject) | Intended to be used within other parse functions, runs a parse and returns success and new value

### Getters

get([player]) | Gets all commands that a player is allowed to use, game commands are not included
search(keyword[, player]) | Searches command names and help messages to find possible commands, game commands are included

### Creation

new_command(name, help) | Creates a new command object to added details to, this does not register the command to the game api
Commands._prototype:add_param(name[, optional=false][, parse][, ...]) | Adds a new param to the command this will be displayed in the help and used to parse the input
Commands._prototype:set_defaults(defaults) | Add default values to params, only as an effect if the param is optional, if default value is a function it is called with the acting player
Commands._prototype:set_flag(name[, value=true]) | Adds a flag to the command which is passed via the flags param to the authenticators, can be used to assign command roles or usage type
Commands._prototype:add_alias(...) | Adds an alias, or multiple, that will be registered to this command, eg /teleport can be used as /tp
Commands._prototype:enable_auto_concat() | Enables auto concatenation for this command, all params after the last are added to the last param, useful for reasons or other long text input nb: this will disable max param checking as they will be concatenated onto the end of that last param
Commands._prototype:register(callback) | Adds the callback to the command and registers: aliases, params and help message with the base game api nb: this must be the last function ran on the command and must be done for the command to work

### Status

success([value]) | Sends a value to the player, followed by a command complete message, returning a value will trigger this automatically
print(value, colour) | Sends a value to the player, different to success as this does not signal the end of your command
error([error_message=''][, play_sound=utility/wire_pickup]) | Sends an error message to the player and when returned will stop execution of the command nb: this is for non fatal errors meaning there is no log of this event, use during register callback
internal_error(success, command_name, error_message) | Sends an error to the player and logs the error, used internally please avoid direct use nb: use error(error_message) within your callback to trigger do not trigger directly as code execution may still continue
run_command(command_event) | Main event function that is ran for all commands, used internally please avoid direct use

### Dependencies

# utils.game
# expcore.common

### Tables

# defines

  Constant values used by the command system

  Fields:
  - error
  - unauthorized
  - success
# commands

  An array of all custom commands that are registered

# authenticators

  An array of all custom authenticators that are registered

# parsers

  Used to store default functions which are common parse function such as player or number in range

# _prototype

  The command prototype which stores all command defining functions

### Fields

# authorization_failure_on_error

  When true any authenticator error will result in authorization failure, more secure

# print

  Returns a value to the player, different to success as this does not signal the end of your command

### Authentication

# add_authenticator(authenticator)

  Adds an authorization function, function used to check if a player if allowed to use a command

  Parameters:
  - authenticator : (function) The function you want to register as an authenticator
  Returns:
  - (number) The index it was inserted at, used to remove the authenticator
  Usage:

      -- If the admin_only flag is set, then make sure the player is an admin
      local admin_authenticator =
      Commands.add_authenticator(function(player, command, flags, reject)
          if flags.admin_only and not player.admin then
              return reject('This command is for admins only!')
          else
              return true
          end
      end)

# remove_authenticator(authenticator)

  Removes an authorization function, can use the index or the function value

  Parameters:
  - authenticator : (function or number) The authenticator to remove, either the index return from add_authenticator or the function used
  Returns:
  - (boolean) If the authenticator was found and removed successfully
  Usage:

      -- Removing the admin authenticator, can not be done during runtime
      Commands.remove_authenticator(admin_authenticator)

# authorize(player, command_name)

  Mostly used internally, calls all authenticators, returns if the player is authorized

  Parameters:
  - player : (LuaPlayer) The player who is using the command, passed to authenticators
  - command_name : (string) The name of the command being used, passed to authenticators
  Returns:
  - (boolean) true Player is authorized
  - (string) commands Define value for success

  ##### Or

  - (boolean) false Player is unauthorized
  - (string or locale_string) The reason given by the failed authenticator
  Usage:

      -- Test if a player can use "repeat-name"
      local authorized, status = Commands.authorize(game.player, 'repeat-name')

### Parse

# add_parse(name, parser)

  Adds a parse function which can be called by name (used in add_param) nb: this is not required as you can use the callback directly this just allows it to be called by name

  Parameters:
  - name : (string) The name of the parse, should describe a type of input such as number or player, must be unique
  - parser : (function) The function that is ran to parse the input string
  Returns:
  - (boolean) Was the parse added, will be false if the name is already used
  Usage:

      -- Adding a parse to validate integers in a given range
      Commands.add_parse('number-range-int', function(input, player, reject, range_min, range_max)
          local rtn = tonumber(input) and math.floor(tonumber(input)) or nil -- converts input to number
          if not rtn or rtn < range_min or rtn > range_max then
              -- The input is either not a number or is outside the range
              return reject('Number entered is not in range: '..range_min..', '..range_max)
          else
              -- Returns the input as a number rather than a string, thus the param is now the correct type
              return rtn
          end
      end)

# remove_parse(name)

  Removes a parse function, see add_parse for adding them, cant be done during runtime

  Parameters:
  - name : (string) The name of the parse to remove
  Usage:

      -- Removing a parse
      Commands.remove_parse('number-range-int')

# parse(name, input, player, reject)

  Intended to be used within other parse functions, runs a parse and returns success and new value

  Parameters:
  - name : (string) The name of the parse to call, must be a registered parser
  - input : (string) The input to pass to the parse, must be a string but not necessarily the original input
  - player : (LuaPlayer) The player that is using the command, pass directly from your arguments
  - reject : (function) The reject function, pass directly from your arguments
  Returns:
  - (any) The new value for the input, if nil is return then either there was an error or the input was nil
  Usage:

      -- Parsing an int after first checking it is a number
      Commands.add_parse('number', function(input, player, reject)
          local number = tonumber(input)
          if number then return number end
          return reject('Input must be a number value')
      end)

      Commands.add_parse('number-int', function(input, player, reject)
          local number = Commands.parse('number', input, player, reject)
          if not number then return end
          return math.floor(number)
      end)

### Getters

# get([player])

  Gets all commands that a player is allowed to use, game commands are not included

  Parameters:
  - player : (LuaPlayer) The player that you want to get commands of, nil will return all commands (optional)
  Returns:
  - (table) All commands that that player is allowed to use, or all commands
  Usage:

      -- Get the commands you are allowed to use
      local commands = Commands.get(game.player)

      -- Get all commands that are registered
      local commands = Commands.get()

# search(keyword[, player])

  Searches command names and help messages to find possible commands, game commands are included

  Parameters:
  - keyword : (string) The word which you are trying to find in your search
  - player : (LuaPlayer) The player to get allowed commands of, if nil all commands are searched (optional)
  Returns:
  - (table) All commands that contain the key word, and allowed by the player if a player was given
  Usage:

      -- Get all commands which "repeat"
      local commands = Commands.search('repeat')

      -- Get all commands which "repeat" and you are allowed to use
      local commands = Commands.search('repeat', game.player)

### Creation

# new_command(name, help)

  Creates a new command object to added details to, this does not register the command to the game api

  Parameters:
  - name : (string) The name of the command to be created
  - help : (string) The help message for the command
  Returns:
  - (table) This will be used with other functions to define the new command
  Usage:

      -- Define a new command
      Commands.new_command('repeat-name', 'Will repeat you name a number of times in chat.')

# Commands._prototype:add_param(name[, optional=false][, parse][, ...])

  Adds a new param to the command this will be displayed in the help and used to parse the input

  Parameters:
  - name : (string) The name of the new param that is being added to the command
  - optional : (boolean) Is this param optional, these must be added after all required params (default: false)
  - parse : (string or function) This function will take the input and return a new value, if not given no parse is done (optional)
  - ... : (any) Extra args you want to pass to the parse function; for example if the parse is general use (optional)
  Returns:
  - (table) Pass through to allow more functions to be called
  Usage:

      -- Adding a required param which has a parser pre-defined
      command:add_param('repeat-count', 'number-range-int', 1, 5)

      -- Adding an optional param which has a custom parse, see Commands.add_parse for details
      command:add_param('smiley', true, function(input, player, reject)
          if not input then return end
          return input:lower() == 'true' or input:lower() == 'yes' or false
      end)

# Commands._prototype:set_defaults(defaults)

  Add default values to params, only as an effect if the param is optional, if default value is a function it is called with the acting player

  Parameters:
  - defaults : (table) A table which is keyed by the name of the param and the value is the default value for that param
  Returns:
  - (table) Pass through to allow more functions to be called
  Usage:

      -- Adding default values
      command:set_defaults{
          smiley = false,
          -- not in example just used to show arguments given
          player_name = function(player)
              return player.name
          end
      }

# Commands._prototype:set_flag(name[, value=true])

  Adds a flag to the command which is passed via the flags param to the authenticators, can be used to assign command roles or usage type

  Parameters:
  - name : (string) The name of the flag to be added, set to true if no value is given
  - value : (any) The value for the flag, can be anything that the authenticators are expecting (default: true)
  Returns:
  - (table) Pass through to allow more functions to be called
  Usage:

      -- Setting a custom flag
      command:set_flag('admin_only', true)

      -- When value is true it does not need to be given
      command:set_flag('admin_only')

# Commands._prototype:add_alias(...)

  Adds an alias, or multiple, that will be registered to this command, eg /teleport can be used as /tp

  Parameters:
  - ... : (string) Any amount of aliases that you want this command to be callable with
  Returns:
  - (table) Pass through to allow more functions to be called
  Usage:

      -- Added multiple aliases to a command
      command:add_alias('name', 'rname')

# Commands._prototype:enable_auto_concat()

  Enables auto concatenation for this command, all params after the last are added to the last param, useful for reasons or other long text input nb: this will disable max param checking as they will be concatenated onto the end of that last param

  Returns:
  - (table) Pass through to allow more functions to be called
  Usage:

      -- Enable auto concat for a command
      command:enable_auto_concat()

# Commands._prototype:register(callback)

  Adds the callback to the command and registers: aliases, params and help message with the base game api nb: this must be the last function ran on the command and must be done for the command to work

  Parameters:
  - callback : (function) The callback for the command, will receive the player running command, and any params added with add_param
  Usage:

      -- Registering your command to the base game api
      command:register(function(player, repeat_count, smiley, raw)
          local msg = ') '..player.name
          if smiley then msg = ':'..msg end

          for 1 = 1, repeat_count do
              Command.print(1..msg)
          end
      end)

### Status

# success([value])

  Sends a value to the player, followed by a command complete message, returning a value will trigger this automatically

  Parameters:
  - value : (any) The value to return to the player, if nil then only the success message is returned (optional)
  Returns:
  - (Commands.defines.success) Return this to the command handler to prevent two success messages
  Usage:

      -- Print a custom success message
      return Commands.success('Your message has been printed')

      -- Returning the value has the same result
      return 'Your message has been printed'

# print(value, colour)

  Sends a value to the player, different to success as this does not signal the end of your command

  Parameters:
  - value : (any) The value that you want to return to the player
  - colour : (table) The colour of the message that the player sees
  Usage:

      -- Output a message to the player
      Commands.print('Your command is in progress')

# error([error_message=''][, play_sound=utility/wire_pickup])

  Sends an error message to the player and when returned will stop execution of the command nb: this is for non fatal errors meaning there is no log of this event, use during register callback

  Parameters:
  - error_message : (string) An optional error message that can be sent to the user (default: '')
  - play_sound : (string) The sound to play for the error (default: utility/wire_pickup)
  Returns:
  - (Commands.defines.error) Return this to command handler to terminate execution
  Usage:

      -- Send an error message to the player, and stops further code running
      return Commands.error('The player you selected is offline')

# internal_error(success, command_name, error_message)

  Sends an error to the player and logs the error, used internally please avoid direct use nb: use error(error_message) within your callback to trigger do not trigger directly as code execution may still continue

  Parameters:
  - success : (boolean) The success value returned from pcall, or just false to trigger error
  - command_name : (string) The name of the command this is used within the log
  - error_message : (string) The error returned by pcall or some other error, this is logged and not returned to player
  Returns:
  - (boolean) The opposite of success so true means to cancel execution, used internally
  Usage:

      -- Used in the command system to log handler errors
      local success, err = pcall(command_data.callback, player, unpack(params))
      if Commands.internal_error(success, command_data.name, err) then
          return command_log(player, command_data, 'Internal Error: Command Callback Fail', raw_params, command_event.parameter, err)
      end

# run_command(command_event)

  Main event function that is ran for all commands, used internally please avoid direct use

  Parameters:
  - command_event : (table) Passed directly from the add_command function
  Usage:

      Commands.run_command(event)