Skip to content

Commit

Permalink
Doc updates
Browse files Browse the repository at this point in the history
  • Loading branch information
MrLotU committed May 5, 2020
1 parent 689e6f7 commit 5aed5a1
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 23 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
/.build
/Packages
/*.xcodeproj
.swiftpm
.swiftpm
build/
docs/
9 changes: 7 additions & 2 deletions Sources/SwiftHooks/Commands/Command+Permissions.swift
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand All @@ -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)
}
Expand Down
100 changes: 81 additions & 19 deletions Sources/SwiftHooks/Commands/Command.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// Base command
public struct Command: ExecutableCommand {
public let name: String
public let group: String?
Expand All @@ -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
Expand All @@ -34,10 +39,18 @@ public struct Command: ExecutableCommand {

public func validate() throws { }

public func arg<A>(_ t: A.Type, named n: String) -> OneArgCommand<A> {
let x = GenericCommandArgument<A>(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<A>(_ t: A.Type, named name: String) -> OneArgCommand<A> {
let x = GenericCommandArgument<A>(componentType: A.typedName, componentName: name)
return OneArgCommand<A>(
name: name,
name: self.name,
group: group,
alias: alias,
hookWhitelist: hookWhitelist,
Expand Down Expand Up @@ -72,6 +85,7 @@ fileprivate extension ExecutableCommand {
}
}

/// Base command with one argument
public struct OneArgCommand<A>: ExecutableCommand where A: CommandArgumentConvertible {
public let name: String
public let group: String?
Expand Down Expand Up @@ -107,20 +121,29 @@ public struct OneArgCommand<A>: ExecutableCommand where A: CommandArgumentConver
try closure(hooks, event, a)
}

public func arg<B>(_ t: B.Type, named: String) -> TwoArgCommand<A, B> {
/// 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<B>(_ t: B.Type, named name: String) -> TwoArgCommand<A, B> {
return TwoArgCommand<A, B>(
name: name,
name: self.name,
group: group,
alias: alias,
hookWhitelist: hookWhitelist,
permissionChecks: permissionChecks,
closure: { _, _, _, _ in },
argOne: arg,
argTwo: GenericCommandArgument<B>(componentType: B.typedName, componentName: named)
argTwo: GenericCommandArgument<B>(componentType: B.typedName, componentName: name)
)
}
}

/// Base command with two arguments
public struct TwoArgCommand<A, B>: ExecutableCommand where A: CommandArgumentConvertible, B: CommandArgumentConvertible {
public let name: String
public let group: String?
Expand Down Expand Up @@ -163,21 +186,30 @@ public struct TwoArgCommand<A, B>: ExecutableCommand where A: CommandArgumentCon
try self.closure(hooks, event, a, b)
}

public func arg<C>(_ t: C.Type, named: String) -> ThreeArgCommand<A, B, C> {
/// 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<C>(_ t: C.Type, named name: String) -> ThreeArgCommand<A, B, C> {
return ThreeArgCommand<A, B, C>(
name: name,
name: self.name,
group: group,
alias: alias,
hookWhitelist: hookWhitelist,
permissionChecks: permissionChecks,
closure: { _, _, _, _, _ in },
argOne: argOne,
argTwo: argTwo,
argThree: GenericCommandArgument<C>(componentType: C.typedName, componentName: named)
argThree: GenericCommandArgument<C>(componentType: C.typedName, componentName: name)
)
}
}

/// Base command with three arguments
public struct ThreeArgCommand<A, B, C>: ExecutableCommand where A: CommandArgumentConvertible, B: CommandArgumentConvertible, C: CommandArgumentConvertible {
public let name: String
public let group: String?
Expand Down Expand Up @@ -226,26 +258,36 @@ public struct ThreeArgCommand<A, B, C>: ExecutableCommand where A: CommandArgume
try self.closure(hooks, event, a, b, c)
}

public func arg<T>(_ 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: 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<T>(componentType: T.typedName, componentName: named)]
arguments: [argOne, argTwo, argThree, GenericCommandArgument<T>(componentType: T.typedName, componentName: name)]
)
}
}

/// Base command with four or more arguments
public struct ArrayArgCommand: ExecutableCommand {
public let name: String
public let group: String?
public let alias: [String]
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? {
Expand Down Expand Up @@ -278,19 +320,29 @@ public struct ArrayArgCommand: ExecutableCommand {
try closure(hooks, event, Arguments(arguments))
}

public func arg<T>(_ 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: 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<T>(componentType: T.typedName, componentName: named)
arguments: arguments + GenericCommandArgument<T>(componentType: T.typedName, componentName: name)
)
}
}


/// Arguments container used in `ArrayArgCommand`.
public class Arguments {
let arguments: [CommandArgument]
private(set) var nilArgs: [String]
Expand All @@ -300,10 +352,20 @@ public class Arguments {
self.nilArgs = []
}

public func getArg<A>(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<A>(_ 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 &&
Expand Down
32 changes: 31 additions & 1 deletion Sources/SwiftHooks/Commands/CommandArgumentConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ extension CommandArgument {
}
}


/// Any type capable of consuming.
///
/// See also `ConsumingCommandArgumentConvertible`
public protocol AnyConsuming {}
protocol AnyOptionalType {
var isNil: Bool { get }
Expand Down Expand Up @@ -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
Expand All @@ -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)")
Expand All @@ -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)")
Expand Down
11 changes: 11 additions & 0 deletions Sources/SwiftHooks/Commands/ExecutableCommand.swift
Original file line number Diff line number Diff line change
@@ -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 }
Expand All @@ -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 }
Expand All @@ -34,9 +37,17 @@ public protocol ExecutableCommand: _ExecutableCommand {
}

public extension ExecutableCommand {
/// Human readable string of arguments required for this command.
///
/// command.readableArguments // "<a:Int> <b:Int> [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)
}
Expand Down
16 changes: 16 additions & 0 deletions Sources/SwiftHooks/Commands/SwiftHooks+Commands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftHooks/Listeners/EventListeners.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
Loading

0 comments on commit 5aed5a1

Please sign in to comment.