Skip to content

Commit

Permalink
Merge pull request #7 from adrianocastro189/release/version-0.0.7-alpha
Browse files Browse the repository at this point in the history
Release - 0.0.7-alpha
  • Loading branch information
adrianocastro189 authored Mar 30, 2024
2 parents 36455e9 + 28842cb commit 5cc5473
Show file tree
Hide file tree
Showing 39 changed files with 1,937 additions and 143 deletions.
2 changes: 1 addition & 1 deletion compiler/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ if (${library}) then return end
${library} = {}
${library}.__index = ${library}
function ${library}.new()
function ${library}.new(props)
local self = setmetatable({}, ${library})
${this.fileContent}
return self
Expand Down
10 changes: 10 additions & 0 deletions documentation/docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 2024.mm.yy - version 0.0.7-alpha

* Add the Events facade class to serve as a way to improve event handling
* Listen to `PLAYER_LOGIN`
* Listen to `PLAYER_TARGET_CHANGED` and split it into three events
* Add the CommandsHandler class to allow addons to register commands
* Add the Output class to replace print() calls
* Add the RaidMarker model class and the library's possible marker instances
* Allow passing addon properties to the library

## 2024.03.16 - version 0.0.6-alpha

* Add the Str support class
Expand Down
7 changes: 7 additions & 0 deletions documentation/docs/resources/commands/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"label": "Commands",
"link": {
"type": "generated-index",
"description": "Resources for easy slash command register for addons."
}
}
72 changes: 72 additions & 0 deletions documentation/docs/resources/commands/command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
sidebar_position: 2
title: Command
---

The command object is a simple DTO object that can also house the callback
if the addon has a good class structure.

It basically holds the required information to register a slash command in
the game:

1. **Operation:** the `setOperation(operation)` expects a string operation
name which will be the one used by the library to forward the command
execution.
1. **Callback:** the `setCallback(callback)` expects a function that will be
executed when the library captures a command. This function may expect
parameters that will be parsed by the commands handler.
1. **Description:** optional property set with `setDescription(description)`
that will also store additional information for that command. When defined,
the [default help operation](commands-handler#the-help-operation) will print
it after the operation name.

## Requirements

In order to add commands, the library must be initialized with the `command`
property. After that, every command instance created in the example below will
be registered as an operation that will be executed by the registered command.

Read the [Addon Properties](../core/addon-properties) documentation for more
reference.

## Example

Imagine an addon that needs to register a **clear** command that will clear
a table. Something like a cache clear. The addon holds the library instance
in a property called `library`.

First, the library must be initialized with the `command` property:

```lua
CustomAddon.library = StormwindLibrary.new({
command = 'myAddon',
-- other properties here
})
```

After that, it's possible to register any other commands, called operations,
as necessary.

```lua
-- instantiates a command using the library factory
local command = CustomAddon.library:new('Command')

function command:commandExecution(arg1, arg2)
-- execute any addon code here
print('command executed with arg1 = ' .. arg1 .. ', and arg2 = ' .. arg2)
end

command
:setDescription('Clears the addon cache')
:setOperation('clear')
:setCallback(command.commandExecution)

-- registers the command in the library
CustomAddon.library.commands:add(command)
```

In game, running the following line in the chat will execute the command:

```shell
/myAddon clear
```
50 changes: 50 additions & 0 deletions documentation/docs/resources/commands/commands-handler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
sidebar_position: 3
title: Commands Handler
---

The commands handler is a class that intercepts all commands registered by an
addon. That allows the library to parse arguments and trigger the
registered callbacks.

As mentioned in the [overview](overview), one of the motivations behind the
commands structure is to allow commands to behave as objects, and that's
possible leaving all the command conditionals, parsing, etc, to the handler,
requiring commands to just expose a callback expecting the desired
arguments.

* See how to register commands [here](command)

## How it works

1. When an addon initializes its library instance, it can pass a property in
the constructor representing the addon main command. That will make the
library register the command during its initialization.
1. The library registers the command associating its own callback at this
point.
1. After that, each `add(command)` call will map its operation and the command
itself.
1. When the command is executed in game, the library will have its callback
triggered along with the argument, which is broken by spaces.
* When a command is executed in the game, everything after the command
itself becomes the argument. Example: `/myCommand arg1 arg2 arg3` will
trigger the callback with `arg1 arg2 arg3`.
* The Stormwind Library commands handler was designed to forward the
arguments like an operating system console where arguments
are separated by blank spaces. Arguments that must contain spaces can
be wrapped by `"` or `'`. Example: `/myCommand arg1 "arg2 arg3"` will
result in two arguments `{'arg1', 'arg2 arg3'}`.
1. This list of arguments are divided, and the first argument is considered
the command **operation**, so it will determine the proper callback to
trigger in the addon. This callback is the one exposed by the command
object.
1. The other arguments (if any) are passed to the operation callback.

## The help operation

By default, the commands handler offers a **help** operation that behaves as
a normal command. It basically prints all the available operations (except for
the help itself) along with their descriptions.

Addons that must need to override the help operation, simply create a command
and add it normally, so the default one will be replaced.
69 changes: 69 additions & 0 deletions documentation/docs/resources/commands/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
sidebar_position: 1
title: Overview
---

Slash commands in World of Warcraft are executed in the chat box that can
execute lots of things for a character as well as for the UI.

Examples of slash commands:

* `/m` opens the macro window
* `/dance` puts the character to dance
* `/logout` logs the character off

There are lots of native commands, and addons can introduce their own.

## Stormwind Library commands

It's very easy to add new slash commands to the game and you can do that
with a couple of code lines.

The Stormwind Library offers a small structure to add commands in a more OOP
approach, which means you can wrap a command in a Lua class in case its
complex enough to be handled by a procedural script.

That said, if an addon needs to use the library commands resources, it must
adhere to a few rules. Otherwise, the addon can "manually" introduce
commands in the traditional way.

1. **Single command name:** the library allows only a single command per
addon. Which means the addon can't register `/myAddonCommand` and
`/myAddonAnotherCommand`, instead, it must use the concept of command
operations
2. **One callback per operation:** considering a single command per addon,
the first argument is considered the operation, which means something like
the real command inside the addon. As an example `/myAddonCommand show` and
`/myAddonCommand hide` are commands with two different operations: **show**
and **hide**
* Still, a command callback may accept parameters, so a command like
`/myAddonCommand show simpleUi darkMode` will call the **show** callback
passing `simpleUi` and `darkMode` arguments

If the addon can handle commands in the proposed way, then it can use the
resources below to register, listen and trigger callbacks for slash
commands.

* [Creating and registering a command](command)
* [How the commands handler works](commands-handler)

## Current limitations

The current command structure has a few limitations that developers need to
be aware. These limitations can be covered in the future depending on their
demand and more clarity on how this structure is being used:

1. **Commands must have an operation:** a command can't be created without
the operation, meaning that `/myAddonCommand` with no arguments won't have
any effects and won't forward to the addon callbacks.
* For cases where the addon needs only one single command, prefer to use
a default operation representing what the command opens or runs, examples:
* `/myAddonCommand show`
* `/myAddonCommand config`
* `/myAddonCommand start`
1. **Arguments can't escape quotes:** when calling commands handled by the
Stormwind Library, arguments are separated by a space (` `) meaning that
`/myAddonCommand operation arg1 arg2` will call the operation passing both
arguments as two Lua variables and it also allows wrapping strings with
spaces in `""` or `''`. However, until the current version, it's not possible
to escape quotes in a way that the argument can't contain those characters.
67 changes: 67 additions & 0 deletions documentation/docs/resources/core/addon-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Addon Properties

When the library is initialized, addons can pass its properties to
improve how it handles its resources.

In the example below, `MyAddon` is the addon main table and `__` is
the library reference. Please, remember that the library class must
carry its version to avoid conflicts, but for the sake of simplicity,
it's simply called `StormwindLibrary`.

```lua
MyAddon.__ = StormwindLibrary.new({
command = 'myAddon',
name = 'My Custom Addon',
})
```

Once initialized, these properties can be accessed in the library's
property called `addon`. The code below will print "My Custom Addon".

```lua
print(MyAddon.__.addon.name)
```

## Available properties

The following sections list the available properties and their effect
on the library. See the first example in this article on how to pass
the addon properties and each subtitle below represents a table index,
so when showing `command` for example, it means passing a table with
`{command = 'myAddon'}` when calling `new()` for a new library instance.

Some parameters are **optional** and some are **required**.

### colors

* **Type**
* A table containing the primary and the secondary colors
* Colors must be provided as hexadecimal strings
```lua
-- ...
colors = {
primary = 'FFFFFF',
secondary = '000000',
}
-- ...
```
* **Optional**
* **Default:** `{}`, indicating there are not default colors
* **Effect:** Most output messages will use the **primary** color to
highlight the prefix; the **secondary** color may also be used to
highlight secondary information.

### command

* **Type:** string
* **Optional**
* **Default:** `nil`
* **Effect:** When initialized, the library will register a command
that can be executed in game. Please, read the
[commands documentation](../commands/overview) for reference.

### name

* **Type:** string
* **Required**
* **Effect:** The library will store the addon name for multiple purposes.
85 changes: 85 additions & 0 deletions documentation/docs/resources/core/classes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Classes

Lua doesn't offer directly a class structure like every
OOP languages like PHP and Java. Because of that, there are
a couple of ways we can emulate classes and effectively be able to
instantiate objects that share logic, property, methods
structure, etc.

The Stormwind Library proposes a few standards to achieve
class structures that are created with a couple of metatables
settings and some indentation.

## Class standards

These are the standards used by Stormwind Library for a table to be
considered a class:

* Each class is defined in its own file, which is later merged to the
single library file
* All lines below the first `local` declaration are indented as
they belong to an opened structure
* Classes have a constructor similar to PHP's, called `__construct()`
* Due to how the library is compiled, when this "class" is being read by
the Lua compiler, the `self` calls are actually referencing the Library
instance, not the class being written -- **only the `self`'s inside
methods refer to the "class" itself**, and because of that, a `self:addClass()`
is called at the top, so the library can store a reference for this "class",
declared as `local`. After that, the library can instantiate that class
anywhere in the addon code.
* Read the [factory documentation](factory) for more information

:::tip "Private" constructors

Sometimes it's recommended not to expose classes structures for free
instantiation. When that happens, simply don't call `self:addClass()`,
so `library:new()` won't have any effects on it, behaving as a private
constructor, when only the classes themselves can create their own
instances.

As an example, the [raid marker](../models/raid-marker) model has a
limited number of possible instances. That class can't be instantiated
by any addons and its instances are generated by the library during
its initialization.

:::

## Class recipe

Use the recipe below to create new models.

This is an example of a simple class with a property called `name` and
a simple method.

```lua
--[[
Class description.
]]
local ClassName = {}
ClassName.__index = ClassName
ClassName.__ = self
self:addClass('ClassName', ClassName)

--[[
ClassName constructor.
@tparam string name the ClassName's name
]]
function ClassName.__construct(name)
local self = setmetatable({}, ClassName)

self.name = name

return self
end

--[[
Function description
@treturn boolean
]]
function ClassName:modelFunction()
return 'something'
end
-- end of ClassName
```
Loading

0 comments on commit 5cc5473

Please sign in to comment.