Skip to content

Console Commands

Cronyx edited this page Mar 23, 2021 · 21 revisions

Overview

Console commands represent subroutines that can be invoked from the console. Every command has two parts: a unique name to distinguish it from other commands, and a function that is invoked to call and process the command. Commands are invoked by entering the name of the command into the console, such as "help" or "pwd". Any additional arguments can be passed to the command by entering them after the command name in the format <command> [args]. Before a command can be invoked, it must be registered to the console. There are several ways to create and register commands in this package. This page serves to document how to create, register, and use console commands, and some guidelines for doing so.

Guidelines for naming commands

It is worth remembering that command names are case-insensitive and that leading and trailing whitespace around commands names is ignored. When entering a command to the console, single or double quotes may surround the command name. For example, all of the following command names are equivalent, and will invoke the same command when entered to the console:

  • foo
  • FOO
  • ' foo '
  • " FOO "

Command names cannot be null, empty, or composed only of whitespace characters. For instance, all of the following strings would be invalid names for a command:

  • null
  • "" (empty string)
  • " " (whitespace only)
  • "\n\t\r" (whitespace only)

While leading and trailing whitespace around a command name is ignored, whitespace within a command name is not. While you are allowed to include whitespace within a command name, it is not recommended to do so, as this can make the command difficult to invoke, and can cause confusion among other commands. For instance, while it would be valid to name two different commands create and create cube, this would lead to several confusing scenarios:

  • $ create → invokes create command
  • $ create cube → invokes create command, passing cube as an argument
  • $ "create cube" → invokes create cube command

As you can see, it is easy to confuse the create command with create cube, and so this practice should be avoided. Furthermore, in order to invoke a command that has whitespace embedded in its name, the command name must always be surrounded by quotes. This makes calling the command quite cumbersome in practice. Additionally, while quotes (" and ') may technically be used in command names, these must be escaped when entered to the console using a backslash character (\" or \') and should be avoided as such.

For these reasons, the following guidelines are advised when naming your own commands:

  • ✔️ DO use short, one-word command names, such as help or cube, that succinctly describe the operation that they perform.
  • ✔️ DO use dashes or underscores to separate words in command names, such as in create-cube ordraw-ray, where it is otherwise impossible to use a single word, or when using one word would be less descriptive than using multiple words.
  • DO NOT use spaces in command names, as this can be a potential source of confusion and makes invoking the command difficult.
  • DO NOT use special characters (such as λ, §, ", etc.) or escape characters (such as \n or \b) as it may be difficult or impossible to type these on a keyboard.
  • DO NOT use command names that are already in use, such as the names of built-in commands like help or cd. This will either cause exceptions at runtime or will result in your command being "hidden" by the already existing commands.

Built-in commands

This package comes with several built-in commands to make development easier. Built-in commands are always available and cannot be unregistered programmatically using DeveloperConsole.UnregisterCommand. This section describes each command, its syntax, and what it does.

help

$ help [command]

Invoke this command with no arguments to retrieve a list of all commands currently registered to the console. Alongside each command's name is a short description of that command's function if a description has been supplied. For example, calling help might print the following readout:

~ $ help
help             Displays help information about available commands
cd               Changes the current directory
ls               List all files and subdirectories in the current directory
pwd              Prints the current directory

Invoke this command with the name of a registered command to get a more detailed description of that command's function, usage, arguments, etc., if such a description has been supplied. For example, to get information about the cd command, enter help cd to the console:

~ $ help cd
cd: Changes the current directory

usage: cd [dir]
If no directory is provided, navigates to the home directory.

cd

$ cd [directory]

Changes the console's current working directory to the supplied directory argument. directory can either be either a path relative to the current working directory, such as ..\ or an absolute directory, such as C:\. If directory is not provided, navigates to the console's home directory, represented by a tilde character (~). For more information on which path the console uses as its home directory, see the "Home Directory" setting of the console settings.

~ $ pwd
C:\Users\[USERNAME]\
cd ..\
C:\Users $ pwd
C:\Users

ls

$ ls [directory]

Lists all files and subdirectories in the directory whose path is directory. directory can either be a path relative to the current working directory, such as ..\ or an absolute directory, such as C:\. If directory is not provided, lists all files and subdirectories in the current working directory. Files and subdirectories are listed in alphabetical order, and subdirectories are colored blue to distinguish them from files.

C:\Users $ ls
All Users\
Default\
Default User\
desktop.ini
...

pwd

$ pwd

Logs the absolute path of the current working directory to the console.

~ $ pwd
C:\Users\[USERNAME]

Optional command metadata

In addition to a name and a function that is called when a command is invoked, each command has a couple of pieces of optional metadata that can be used to document its form and function.

Metadata Name Metadata Description
Description A short description of the command that is shown in the list of commands when the built-in help command is called with no arguments. This should be a short, one-sentence long description that succinctly describes the command.
Help A longer help string that is printed to the console when the built-in help command is called with the name of this command. Can contain optional documentation of the command, usage information, parameter descriptions, etc. Can be multiple lines long.

Creating custom commands

It is likely that in your own project, you'll want to extend the console's functionality by creating your own commands. This package was designed to be minimally invasive with respect to implementing console functionality within your codebase—adding a new command can be as easy as marking a method with an attribute! This section will describe some of the available options for creating and registering custom commands to the console.

CommandAttribute applied to static methods

By far the easiest way to create and register a command is to annotate a static method with a CommandAttribute, like so:

using Cronyx.Console.Commands;

[Command("command-name", Description = "A short, optional description of this command")]
public static void ConsoleCommand()
{
    // Console command implementation
}

The command will appear in the list of all commands (see the built-in help command) and can be invoked by entering command-name to the console. Optionally, a short description can be supplied with command by setting the Description property of the CommandAttribute (see the section on optional command metadata).

The method marked with a CommandAttribute must be static, though it may or may not be publicly accessible. This system allows for a great deal of flexibility; for example, it can automatically parse command-line arguments from the user input and pass them to the function, like so:

using Cronyx.Console.Commands;
using UnityEngine;

[Command("create-cube", Description = "Creates a cube at the given position with the given scale")]
public static void CreateCube(Vector3 position, float scale)
{
    var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
    cube.transform.position = position;
    cube.transform.localScale = scale * Vector3.one;
}

Typing help create-cube will show the automatically generated usage information and the type/format of each of the parameters:

$ help create-cube
create-cube: Creates a cube at the given position with the given scale

usage: create-cube position scale
Format:
    position        Vector3        (x y z)
    scale           float

and the command can be invoked by calling, for example, $ create-cube (1 2 3) 5, which will create a cube with a scale of 5 in all directions at the position (1, 2, 3).

By default, a variety of parameter types can be parsed and passed to methods marked with CommandAtribute. These include all of C#'s built-in value types; strings; Unity vectors, colors, quaternions; Tuples, Lists, and Dictionaries of these types; and many more. For instance, a command that instantiates multiple cubes at once with different positions and scales might look like this:

using Cronyx.Console.Commands;
using UnityEngine;

[Command("create-cubes", Description = "Creates cubes at the given positions and with the given scales")]
public static void CreateCubes((Vector3 position, float scale)[] positionAndScales)
{
    foreach (var tuple in positionAndScales)
    {
        var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.transform.position = tuple.position;
        cube.transform.localScale = tuple.scale * Vector3.one;
    }
}

To create three different cubes, all with different positions and scales, one could enter $ create-cubes [((0 0 0) 0.5), ((1 2 -3) 1), ((-5 10 6) 2)] to the console.

Finally, each method parameter can be annotated with PositionalAttribute or SwitchAttribute to create optional and switch parameters, and to provide detailed metadata about each parameter type. This allows each console command to be highly configurable in a manner similar to GNU's getopt function. The following sample showcases some of these features:

using Cronyx.Console.Commands;
using Cronyx.Console.Parsing;
using UnityEngine;

[Command("showcase", Description = "Showcases the flexibility of the command parser.")]
public static void CustomCommand(
    [Positional(Description = "The first positional parameter", Meta = "FOO")] Vector3 posOne,
    [Positional(Description = "The second positional parameter", Max = 4, Meta = "BAR", Optional = true)] IEnumerable<string> posTwo,
    [Switch('a', LongName = "flag", Description = "A flag parameter")] bool flag,
    [Switch('b', LongName = "switch", Description = "A switch parameter", Meta = "STR")] string @switch
    )
{
    // Command implementation
}

which generates the following usage and help text:

$ help showcase
showcase: Showcases the flexibility of the command parser

usage: showcase FOO [BAR] [-a] [-b STR]
Format:
    FOO            Vector3                (x y z)
    BAR            IEnumerable<string>    [foo bar ...]
    STR            string                 

Mandatory Parameters:
    FOO            The first positional parameter

Optional Parameters:
    BAR            The second positional parameter
                   Can have at most 4 elements
    -a, --flag     A flag parameter
    -b, --switch   A switch parameter

For a more detailed description and documentation of this system, see the section on parsing command line arguments automatically.

CommandAttribute applied to IConsoleCommand instances

There are instances in which the above system may not be desirable for your use case. For instance, it does not by itself allow you to store any state with your command, and furthermore, it assumes in advance that your command uses a getopt-style syntax like the one above. To overcome these limitations and provide greater extensibility, you can create a class that implements the IConsoleCommand interface and annotate it with a CommandAttribute. The console will instantiate an instance of your command that will persist for the lifetime of the console. Here is a simple example:

using Cronyx.Console.Commands;

[Command("command-name", Description = "A short, optional description of this command")]
public class MyCommand : IConsoleCommand
{
    // Generate any help text to go along with this command when the user enters "$ help command-name"
    // Can be null.
    public string Help => null;

    public void Invoke(string data)
    {
	// Parse the input passed with this command stored in data
	// and/or execute any command-related code
    }
}

To use this approach, you must mark your class with a CommandAttribute (which can supply an optional description; see the section on optional command metadata) and implement the members of IConsoleCommand, as described below:

Member Name Syntax Description
Help
public string Help { get; }
An optional piece of metadata that is written to the console when the user types help your-command. Can be null or empty, in which case no help information is supplied with this command. For more information, see the section on optional command metadata.
Invoke
public void Invoke (string data);
A function that parses and processes this command using any input supplied by the user in the data argument. data contains the raw input (with leading and trailing whitespace removed) occuring after the command name when it was entered. The following examples illustrate the value passed to this command when it is entered for a command whose name is command:
  • $ command foobardata="foobar"
  • $ " command " foobardata="foobar"
  • $ command    foo   bar    data="foo   bar"
  • $ command 'foo bar'data="'foo bar'"

The type marked with CommandAttribute must meet the following criteria:

  • The type must implement IConsoleCommand and its members, as described above.
  • The type must contain an implicit or explicit parameterless constructor, which may or may not be publicly accessible.
  • The type must not contain any unassigned generic type parameters;

    public class MyCommand<T> : IConsoleComand {}
    would be invalid.
  • The type must not be abstract or an interface.

Additionally, you can inherit from MonoBehaviour if your command needs to receive Unity messages, such as Update() or Start(), like so:

using Cronyx.Console.Commands;
using UnityEngine;

[Command("command-name", Description = "A short, optional description of this command")]
public class MyCommand : MonoBehaviour, IConsoleCommand
{
    public string Help => null;
    public void Invoke(string data) {}

    public void Update()
    {
        // Execute any Update() related code for this command
    }
}

In this case, your command will be instantiated as a component and attached to a child GameObject of the console using AddComponent<T>() at the same time that the console is initialized, and will persist for the lifetime of the console. Commands implemented in this fashion should not call Destroy or create any other Unity objects.

Dynamically register a command that parses arguments automatically

The above design patterns should cover most, if not all, of your project's console-related needs. However, they do not allow you to register console commands dynamically at runtime; rather, they force you to mark methods or types statically with the CommandAttribute. To overcome this limitation, this package exposes several methods in the DeveloperConsole API that allow you to create and register console commands dynamically.

void DeveloperConsole.RegisterCommand(string name, Delegate command, string description = null)

This method allows you to register an arbitrary Delegate for which the console will automatically parse parameters and generate help information. This method functions identically to the system described in the section CommandAttribute applied to static methods, but allows you to register instance commands as well as static commands and to do so dynamically. To use it, pass a function delegate with a command name and optional description, like so:

using Cronyx.Console;
using Cronyx.Console.Commands;

public static void MyCommand (
    Vector3 paramOne,
    [Positional(Optional = true)] string paramTwo
    )
{
    // Implement command here
}

DeveloperConsole.RegisterCommand("my-command", MyCommand, "A short description of this command");

Additionally, generic overloads for this command are provided to quickly register lambda expressions to the console:

void DeveloperConsole.RegisterCommand(string name, Action command, string description=null);
void DeveloperConsole.RegisterCommand<T0>(string name, Action<T0> command, string description=null);
void DeveloperConsole.RegisterCommand<T0, T1>(string name, Action<T0, T1> command, string description=null);

// ...

and can be used like so:

using Cronyx.Console;
using Cronyx.Console.Commands;

DeveloperConsole.RegisterCommand ("my-command", (Vector3 vec, string param) => {
        // Do something
    }, "A short description of this command");

which will register a command that automatically parses a Vector3 and a string as required arguments.

Take note when dynamically registering a command, as if you attempt to register a command with a name that is already taken, an InvalidOperationException will be thrown. If this is a possibility, take care to check if a command with that name already exists by calling DeveloperConsole.CommandExists(string name) before registering your command.

Dynamically register a command that parses arguments manually

void DeveloperConsole.RegisterCommandManual(string name, Action<string> parseCommand, string description = null, string help = null)

To dynamically register a delegate or lambda expression for a command that will parse arguments manually, use the above method from the DeveloperConsole API. parseCommand is an Action<string> that will parse the command and execute any additional code in the exact same fashion as the IConsoleCommand.Invoke(string data) method described here. description and help are optional arguments that can be used to supply metadata to the command (see the section on optional command metadata).

Unregister a command

If for any reason you would like to unregister a command that has been created and registered using any of the above methods, use

void DeveloperConsole.UnregisterCommand(string name);

passing the name of the command to this method. This method will throw an InvalidOperationException if no registered command exists with the given name, or when trying to unregister a built-in command, such as help or cd.

Enumerating registered commands

A collection of all commands currently registered to the console can be obtained using the DeveloperConsole.Commands property of the DeveloperConsole API. This will return an IEnumerable<CommandData> instance. Each CommandData contains information about a command, such as its name and metadata, as well as the underlying IConsoleCommand that contains its implementation. A summary of the information contained in CommandData is given in the table below:

Member Name Description
CommandData.Name Gets the unique name of this command.
CommandData.Essential Gets a boolean indicating whether or not this command is essential. Essential (built-in) commands are registered before all other commands and cannot be unregistered using DeveloperConsole.Unregister(string) (see the section about unregistering commands).
CommandData.Description Gets a string containing a short description of this command. Can be null. See the section on optional command metadata for more information.
CommandData.Command Gets the IConsoleCommand object containing the implementation and optional help metadata for this command.

Clone this wiki locally