Skip to content

Commit d438ac0

Browse files
sawa-kofavna
andauthored
feat(commands): add category getters (#244)
Co-authored-by: Jeroen Claassens <[email protected]>
1 parent 8f2ed97 commit d438ac0

File tree

1 file changed

+81
-0
lines changed

1 file changed

+81
-0
lines changed

src/lib/structures/Command.ts

+81
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AliasPiece, AliasPieceOptions, PieceContext } from '@sapphire/pieces';
22
import { Awaited, isNullish } from '@sapphire/utilities';
33
import { Message, PermissionResolvable, Permissions, Snowflake } from 'discord.js';
44
import * as Lexure from 'lexure';
5+
import { sep } from 'path';
56
import { Args } from '../parsers/Args';
67
import { BucketScope } from '../types/Enums';
78
import { PreconditionContainerArray, PreconditionEntryResolvable } from '../utils/preconditions/PreconditionContainerArray';
@@ -26,6 +27,16 @@ export abstract class Command<T = Args> extends AliasPiece {
2627
*/
2728
public detailedDescription: string;
2829

30+
/**
31+
* The full category for the command. Either an array of strings that denote every (sub)folder the command is in,
32+
* or `null` if it could not be resolved automatically.
33+
*
34+
* If this is `null` for how you setup your code then you can overwrite how the `fullCategory` is resolved by
35+
* extending this class and overwriting the assignment in the constructor.
36+
* @since 2.0.0
37+
*/
38+
public readonly fullCategory: readonly string[] | null = null;
39+
2940
/**
3041
* The strategy to use for the lexer.
3142
* @since 1.0.0
@@ -49,6 +60,7 @@ export abstract class Command<T = Args> extends AliasPiece {
4960
this.description = options.description ?? '';
5061
this.detailedDescription = options.detailedDescription ?? '';
5162
this.strategy = new FlagUnorderedStrategy(options);
63+
5264
this.lexer.setQuotes(
5365
options.quotes ?? [
5466
['"', '"'], // Double quotes
@@ -57,6 +69,19 @@ export abstract class Command<T = Args> extends AliasPiece {
5769
]
5870
);
5971

72+
if (options.fullCategory) {
73+
this.fullCategory = options.fullCategory;
74+
} else {
75+
const commandsFolders = [...this.container.stores.get('commands').paths.values()].map((p) => p.split(sep).pop() ?? '');
76+
const commandPath = context.path.split(sep);
77+
for (const commandFolder of commandsFolders) {
78+
if (commandPath.includes(commandFolder)) {
79+
this.fullCategory = commandPath.slice(commandPath.indexOf(commandFolder) + 1, -1);
80+
break;
81+
}
82+
}
83+
}
84+
6085
if (options.generateDashLessAliases) {
6186
const dashLessAliases = [];
6287
if (this.name.includes('-')) dashLessAliases.push(this.name.replace(/-/g, ''));
@@ -81,6 +106,46 @@ export abstract class Command<T = Args> extends AliasPiece {
81106
return new Args(message, this as any, args, context) as any;
82107
}
83108

109+
/**
110+
* Get all the main categories of commands.
111+
*/
112+
public get categories(): (string | null)[] {
113+
return Array.from(new Set([...this.container.stores.get('commands').values()].map(({ category }) => category)));
114+
}
115+
116+
/**
117+
* The main category for the command, if any.
118+
* This is resolved from {@link Command.fullCategory}, which is automatically
119+
* resolved in the constructor. If you need different logic for category
120+
* then please first look into overwriting {@link Command.fullCategory} before
121+
* looking to overwrite this getter.
122+
*/
123+
public get category(): string | null {
124+
return (this.fullCategory?.length ?? 0) > 0 ? this.fullCategory?.[0] ?? null : null;
125+
}
126+
127+
/**
128+
* The sub category for the command
129+
* This is resolved from {@link Command.fullCategory}, which is automatically
130+
* resolved in the constructor. If you need different logic for category
131+
* then please first look into overwriting {@link Command.fullCategory} before
132+
* looking to overwrite this getter.
133+
*/
134+
public get subCategory(): string | null {
135+
return (this.fullCategory?.length ?? 0) > 1 ? this.fullCategory?.[1] ?? null : null;
136+
}
137+
138+
/**
139+
* The parent category for the command
140+
* This is resolved from {@link Command.fullCategory}, which is automatically
141+
* resolved in the constructor. If you need different logic for category
142+
* then please first look into overwriting {@link Command.fullCategory} before
143+
* looking to overwrite this getter.
144+
*/
145+
public get parentCategory(): string | null {
146+
return (this.fullCategory?.length ?? 0) > 0 ? this.fullCategory?.[(this.fullCategory?.length ?? 1) - 1] ?? null : null;
147+
}
148+
84149
/**
85150
* Executes the command's logic.
86151
* @param message The message that triggered the command.
@@ -96,6 +161,7 @@ export abstract class Command<T = Args> extends AliasPiece {
96161
...super.toJSON(),
97162
description: this.description,
98163
detailedDescription: this.detailedDescription,
164+
category: this.category,
99165
strategy: this.strategy
100166
};
101167
}
@@ -312,6 +378,21 @@ export interface CommandOptions extends AliasPieceOptions, FlagStrategyOptions {
312378
*/
313379
detailedDescription?: string;
314380

381+
/**
382+
* The full category path for the command
383+
* @since 2.0.0
384+
* @default 'An array of folder names that lead back to the folder that is registered for in the commands store'
385+
* @example
386+
* ```typescript
387+
* // Given a file named `ping.js` at the path of `commands/General/ping.js`
388+
* ['General']
389+
*
390+
* // Given a file named `info.js` at the path of `commands/General/About/ping.js`
391+
* ['General', 'About']
392+
* ```
393+
*/
394+
fullCategory?: string[];
395+
315396
/**
316397
* The {@link Precondition}s to be run, accepts an array of their names.
317398
* @seealso {@link PreconditionContainerArray}

0 commit comments

Comments
 (0)