From 5aed5a1a2ef2ed034cb651106932c0ed4b077a12 Mon Sep 17 00:00:00 2001 From: MrLotU Date: Tue, 5 May 2020 21:23:02 +0200 Subject: [PATCH] Doc updates --- .gitignore | 4 +- .../Commands/Command+Permissions.swift | 9 +- Sources/SwiftHooks/Commands/Command.swift | 100 ++++++++++++++---- .../Commands/CommandArgumentConvertible.swift | 32 +++++- .../Commands/ExecutableCommand.swift | 11 ++ .../Commands/SwiftHooks+Commands.swift | 16 +++ .../SwiftHooks/Listeners/EventListeners.swift | 4 + Sources/SwiftHooks/SwiftHooks.swift | 10 ++ Sources/SwiftHooks/Types/Channelable.swift | 1 + 9 files changed, 164 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 4b1edb6..4d0bd8f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ /.build /Packages /*.xcodeproj -.swiftpm \ No newline at end of file +.swiftpm +build/ +docs/ \ No newline at end of file diff --git a/Sources/SwiftHooks/Commands/Command+Permissions.swift b/Sources/SwiftHooks/Commands/Command+Permissions.swift index d4f69a0..76e6845 100644 --- a/Sources/SwiftHooks/Commands/Command+Permissions.swift +++ b/Sources/SwiftHooks/Commands/Command+Permissions.swift @@ -1,3 +1,8 @@ +/// Check if the given user has the permission to execute the command. +/// +/// let checker = MyChecker() +/// guard checker.check(user, canUse: command, on: event) else { throw CommandError.InvalidPermissions } +/// public protocol CommandPermissionChecker { /// Check if the given user has the permission to execute the command. @@ -11,7 +16,7 @@ public protocol CommandPermissionChecker { /// - event: Event holding the command & related info /// /// - Returns: Wether or not the user is allowed to execute the command - func check(_ user: Userable, canUse command: Command, on event: CommandEvent) -> Bool + func check(_ user: Userable, canUse command: _ExecutableCommand, on event: CommandEvent) -> Bool } /// Checks if a user is allowed to execute based on their ID @@ -27,7 +32,7 @@ public struct IDChecker: CommandPermissionChecker { /// List of whitelisted IDs let ids: [String] - public func check(_ user: Userable, canUse command: Command, on event: CommandEvent) -> Bool { + public func check(_ user: Userable, canUse command: _ExecutableCommand, on event: CommandEvent) -> Bool { guard let id = user.identifier else { return false } return ids.contains(id) } diff --git a/Sources/SwiftHooks/Commands/Command.swift b/Sources/SwiftHooks/Commands/Command.swift index bf07d67..6360b16 100644 --- a/Sources/SwiftHooks/Commands/Command.swift +++ b/Sources/SwiftHooks/Commands/Command.swift @@ -1,3 +1,4 @@ +/// Base command public struct Command: ExecutableCommand { public let name: String public let group: String? @@ -23,6 +24,10 @@ public struct Command: ExecutableCommand { self.closure = closure } + /// Create a new command. + /// + /// - parameters: + /// - name: Name and trigger of the command. public init(_ name: String) { self.name = name self.group = nil @@ -34,10 +39,18 @@ public struct Command: ExecutableCommand { public func validate() throws { } - public func arg(_ t: A.Type, named n: String) -> OneArgCommand { - let x = GenericCommandArgument(componentType: A.typedName, componentName: n) + /// Add an argument to this command + /// + /// Command("echo") + /// .arg(String.Consuming.self, named: "content") + /// + /// - parameters: + /// - t: Type of the argument. + /// - name: Name of the argument. + public func arg(_ t: A.Type, named name: String) -> OneArgCommand { + let x = GenericCommandArgument(componentType: A.typedName, componentName: name) return OneArgCommand( - name: name, + name: self.name, group: group, alias: alias, hookWhitelist: hookWhitelist, @@ -72,6 +85,7 @@ fileprivate extension ExecutableCommand { } } +/// Base command with one argument public struct OneArgCommand: ExecutableCommand where A: CommandArgumentConvertible { public let name: String public let group: String? @@ -107,20 +121,29 @@ public struct OneArgCommand: ExecutableCommand where A: CommandArgumentConver try closure(hooks, event, a) } - public func arg(_ t: B.Type, named: String) -> TwoArgCommand { + /// Add an argument to this command + /// + /// Command("echo") + /// .arg(String.Consuming.self, named: "content") + /// + /// - parameters: + /// - t: Type of the argument. + /// - name: Name of the argument. + public func arg(_ t: B.Type, named name: String) -> TwoArgCommand { return TwoArgCommand( - name: name, + name: self.name, group: group, alias: alias, hookWhitelist: hookWhitelist, permissionChecks: permissionChecks, closure: { _, _, _, _ in }, argOne: arg, - argTwo: GenericCommandArgument(componentType: B.typedName, componentName: named) + argTwo: GenericCommandArgument(componentType: B.typedName, componentName: name) ) } } +/// Base command with two arguments public struct TwoArgCommand: ExecutableCommand where A: CommandArgumentConvertible, B: CommandArgumentConvertible { public let name: String public let group: String? @@ -163,9 +186,17 @@ public struct TwoArgCommand: ExecutableCommand where A: CommandArgumentCon try self.closure(hooks, event, a, b) } - public func arg(_ t: C.Type, named: String) -> ThreeArgCommand { + /// Add an argument to this command + /// + /// Command("echo") + /// .arg(String.Consuming.self, named: "content") + /// + /// - parameters: + /// - t: Type of the argument. + /// - name: Name of the argument. + public func arg(_ t: C.Type, named name: String) -> ThreeArgCommand { return ThreeArgCommand( - name: name, + name: self.name, group: group, alias: alias, hookWhitelist: hookWhitelist, @@ -173,11 +204,12 @@ public struct TwoArgCommand: ExecutableCommand where A: CommandArgumentCon closure: { _, _, _, _, _ in }, argOne: argOne, argTwo: argTwo, - argThree: GenericCommandArgument(componentType: C.typedName, componentName: named) + argThree: GenericCommandArgument(componentType: C.typedName, componentName: name) ) } } +/// Base command with three arguments public struct ThreeArgCommand: ExecutableCommand where A: CommandArgumentConvertible, B: CommandArgumentConvertible, C: CommandArgumentConvertible { public let name: String public let group: String? @@ -226,19 +258,28 @@ public struct ThreeArgCommand: ExecutableCommand where A: CommandArgume try self.closure(hooks, event, a, b, c) } - public func arg(_ t: T.Type, named: String) -> ArrayArgCommand where T: CommandArgumentConvertible { + /// Add an argument to this command + /// + /// Command("echo") + /// .arg(String.Consuming.self, named: "content") + /// + /// - parameters: + /// - t: Type of the argument. + /// - name: Name of the argument. + public func arg(_ t: T.Type, named name: String) -> ArrayArgCommand where T: CommandArgumentConvertible { return ArrayArgCommand( - name: name, + name: self.name, group: group, alias: alias, hookWhitelist: hookWhitelist, permissionChecks: permissionChecks, closure: { _, _, _ in }, - arguments: [argOne, argTwo, argThree, GenericCommandArgument(componentType: T.typedName, componentName: named)] + arguments: [argOne, argTwo, argThree, GenericCommandArgument(componentType: T.typedName, componentName: name)] ) } } +/// Base command with four or more arguments public struct ArrayArgCommand: ExecutableCommand { public let name: String public let group: String? @@ -246,6 +287,7 @@ public struct ArrayArgCommand: ExecutableCommand { public let hookWhitelist: [HookID] public let permissionChecks: [CommandPermissionChecker] public let closure: (SwiftHooks, CommandEvent, Arguments) throws -> Void + /// Arguments for this command. public let arguments: [CommandArgument] public var readableArguments: String? { @@ -278,19 +320,29 @@ public struct ArrayArgCommand: ExecutableCommand { try closure(hooks, event, Arguments(arguments)) } - public func arg(_ t: T.Type, named: String) -> ArrayArgCommand where T: CommandArgumentConvertible { + /// Add an argument to this command + /// + /// Command("echo") + /// .arg(String.Consuming.self, named: "content") + /// + /// - parameters: + /// - t: Type of the argument. + /// - name: Name of the argument. + public func arg(_ t: T.Type, named name: String) -> ArrayArgCommand where T: CommandArgumentConvertible { return ArrayArgCommand( - name: name, + name: self.name, group: group, alias: alias, hookWhitelist: hookWhitelist, permissionChecks: permissionChecks, closure: { _, _, _ in }, - arguments: arguments + GenericCommandArgument(componentType: T.typedName, componentName: named) + arguments: arguments + GenericCommandArgument(componentType: T.typedName, componentName: name) ) } } + +/// Arguments container used in `ArrayArgCommand`. public class Arguments { let arguments: [CommandArgument] private(set) var nilArgs: [String] @@ -300,10 +352,20 @@ public class Arguments { self.nilArgs = [] } - public func getArg(named name: String, on event: CommandEvent) throws -> A where A: CommandArgumentConvertible, A.ResolvedArgument == A { - return try self.get(A.self, named: name, on: event) - } - + /// Resolve an argument from the command arguments + /// + /// let reason = try args.get(String.self, named: "reason", on: event) + /// + /// - parameters: + /// - arg: Type to resolve. + /// - name: Name of the argument to resolve. + /// - event: `CommandEvent` to resolve on. + /// + /// - throws: + /// `CommandError.UnableToConvertArgument` when resolving fails. + /// `CommandError.ArgumentNotFound` when no argument is found. + /// + /// - returns: The resolved argument. public func get(_ arg: A.Type, named name: String, on event: CommandEvent) throws -> A.ResolvedArgument where A: CommandArgumentConvertible { guard let foundArg = self.arguments.first(where: { $0.componentType == arg.typedName && diff --git a/Sources/SwiftHooks/Commands/CommandArgumentConvertible.swift b/Sources/SwiftHooks/Commands/CommandArgumentConvertible.swift index fde9861..e4d72cb 100644 --- a/Sources/SwiftHooks/Commands/CommandArgumentConvertible.swift +++ b/Sources/SwiftHooks/Commands/CommandArgumentConvertible.swift @@ -78,7 +78,9 @@ extension CommandArgument { } } - +/// Any type capable of consuming. +/// +/// See also `ConsumingCommandArgumentConvertible` public protocol AnyConsuming {} protocol AnyOptionalType { var isNil: Bool { get } @@ -122,6 +124,10 @@ extension String: CommandArgumentConvertible { } public extension String { + /// Helper to create consuming `String` arguments. + /// + /// Command("test") + /// .arg(String.Consuming.self, "consumingString") struct Consuming: ConsumingCommandArgumentConvertible { public static var typedName: String { String.typedName @@ -134,6 +140,18 @@ public extension String { } extension FixedWidthInteger { + + /// Attempts to resolve an argument from the provided string. + /// + /// - parameters: + /// - argument: String taken from the message body. + /// - event: The `CommandEvent` this argument should be resolved for. + /// + /// - throws: + /// `CommandError.UnableToConvertArgument` when `ResolvedArgument` can not be created from `argument` + /// `CommandError.ArgumentNotFound` when no argument is found + /// + /// - returns: The resolved argument public static func resolveArgument(_ argument: String, on event: CommandEvent) throws -> Self { guard let number = Self(argument) else { throw CommandError.UnableToConvertArgument(argument, "\(self.self)") @@ -154,6 +172,18 @@ extension UInt32: CommandArgumentConvertible { } extension UInt64: CommandArgumentConvertible { } extension BinaryFloatingPoint { + + /// Attempts to resolve an argument from the provided string. + /// + /// - parameters: + /// - argument: String taken from the message body. + /// - event: The `CommandEvent` this argument should be resolved for. + /// + /// - throws: + /// `CommandError.UnableToConvertArgument` when `ResolvedArgument` can not be created from `argument` + /// `CommandError.ArgumentNotFound` when no argument is found + /// + /// - returns: The resolved argument public static func resolveArgument(_ argument: String, on event: CommandEvent) throws -> Self { guard let number = Double(argument) else { throw CommandError.UnableToConvertArgument(argument, "\(self.self)") diff --git a/Sources/SwiftHooks/Commands/ExecutableCommand.swift b/Sources/SwiftHooks/Commands/ExecutableCommand.swift index a8dd88f..b0fd0c7 100644 --- a/Sources/SwiftHooks/Commands/ExecutableCommand.swift +++ b/Sources/SwiftHooks/Commands/ExecutableCommand.swift @@ -1,3 +1,4 @@ +/// Base `ExecutableCommand` public protocol _ExecutableCommand: Commands { /// Help description of the command. Used to explain usage to users. var help: String { get } @@ -24,7 +25,9 @@ public protocol _ExecutableCommand: Commands { func invoke(on event: CommandEvent, using hooks: SwiftHooks) throws } +/// Base `ExecutableCommand` public protocol ExecutableCommand: _ExecutableCommand { + /// Closure type this command will execute. associatedtype Execute /// Closure to execute when command is invoked. var closure: Execute { get } @@ -34,9 +37,17 @@ public protocol ExecutableCommand: _ExecutableCommand { } public extension ExecutableCommand { + /// Human readable string of arguments required for this command. + /// + /// command.readableArguments // " [c:String]" var readableArguments: String? { return nil } + + /// Trigger of the command. + /// + /// Synonym for `ExecutableCommand.name` var trigger: String { name } + /// Human readable help string explaining command usage. var help: String { [group, name, readableArguments].compactMap { $0 }.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines) } diff --git a/Sources/SwiftHooks/Commands/SwiftHooks+Commands.swift b/Sources/SwiftHooks/Commands/SwiftHooks+Commands.swift index bc372ba..566be7f 100644 --- a/Sources/SwiftHooks/Commands/SwiftHooks+Commands.swift +++ b/Sources/SwiftHooks/Commands/SwiftHooks+Commands.swift @@ -50,6 +50,7 @@ public enum CommandError: Error { /// Thrown from argument decoding case ArgumentNotFound(String) + /// Retrieve the localized description for this error. public var localizedDescription: String { switch self { case .ArgumentNotFound(let arg): @@ -64,15 +65,30 @@ public enum CommandError: Error { } } +/// Event passed in to a command closure containing required data. public struct CommandEvent { + /// Refference to `SwiftHooks` instance dispatching this command. public let hooks: SwiftHooks + /// User that executed the command. Can be downcast to backend specific type. public let user: Userable + /// String arguments passed in to the command. All space separated strings after the commands trigger. public let args: [String] + /// Message that executed the command. public let message: Messageable + /// Full trigger of the command. Either name or name and group. public let name: String + /// Hook that originally dispatched this command. Can be downcast to backend specific type. public let hook: _Hook + /// Command specific logger. Has command trigger set as command metadata by default. public private(set) var logger: Logger + /// Create a new `CommandEvent` + /// + /// - parameters: + /// - hooks: `SwiftHooks` instance dispatching this command. + /// - cmd: Command this event is wrapping. + /// - msg: Message that executed the command. + /// - h: `_Hook` that originally dispatched this command. public init(hooks: SwiftHooks, cmd: _ExecutableCommand, msg: Messageable, for h: _Hook) { self.logger = Logger(label: "SwiftHooks.Command") self.hooks = hooks diff --git a/Sources/SwiftHooks/Listeners/EventListeners.swift b/Sources/SwiftHooks/Listeners/EventListeners.swift index 98ac4ba..fbf667d 100644 --- a/Sources/SwiftHooks/Listeners/EventListeners.swift +++ b/Sources/SwiftHooks/Listeners/EventListeners.swift @@ -10,6 +10,10 @@ public protocol EventListeners { public struct Listeners: EventListeners { let listeners: EventListeners + /// Create new `Listeners` + /// + /// - parameters: + /// - listeners: `EventListeners` in this group. public init(@ListenerBuilder listeners: () -> EventListeners) { self.listeners = listeners() } diff --git a/Sources/SwiftHooks/SwiftHooks.swift b/Sources/SwiftHooks/SwiftHooks.swift index 8c83757..bac3f3a 100644 --- a/Sources/SwiftHooks/SwiftHooks.swift +++ b/Sources/SwiftHooks/SwiftHooks.swift @@ -8,18 +8,27 @@ import Logging /// /// Hooks and Plugins are both connected to the main SwiftHooks class. public final class SwiftHooks { + /// EventLoopGroup this application runs on. public let eventLoopGroup: EventLoopGroup + /// Wether or not `SwiftHooks` did shutdown. public private(set) var didShutdown: Bool private var isBooted: Bool private var running: EventLoopPromise? + /// Config used for this `SwiftHooks` instance. public let config: SwiftHooksConfig + /// Registered `_Hook`s. public internal(set) var hooks: [_Hook] + /// Registered `GlobalListener`s. public internal(set) var globalListeners: [GlobalEvent: [GlobalEventClosure]] + /// Registered `ExecutableCommand`s public internal(set) var commands: [_ExecutableCommand] + /// Registered `Plugin`s. public internal(set) var plugins: [_Plugin] + /// Global `JSONDecoder` public static let decoder = JSONDecoder() + /// Global `JSONEncoder` public static let encoder = JSONEncoder() /// Create a new `SwiftHooks` instance @@ -97,6 +106,7 @@ extension SwiftHooks { return l } + /// Global SwiftHooks logger public var logger: Logger { return type(of: self).logger } diff --git a/Sources/SwiftHooks/Types/Channelable.swift b/Sources/SwiftHooks/Types/Channelable.swift index bc38089..ef4014a 100644 --- a/Sources/SwiftHooks/Types/Channelable.swift +++ b/Sources/SwiftHooks/Types/Channelable.swift @@ -1,5 +1,6 @@ import NIO +/// A generic channel used in `GlobalEvent`s. public protocol Channelable: PayloadType { /// Mentions the channel. For example `#channelId` var mention: String { get }