vapor / console-kit Goto Github PK
View Code? Open in Web Editor NEW💻 APIs for creating interactive CLI tools.
License: MIT License
💻 APIs for creating interactive CLI tools.
License: MIT License
sample project: https://github.com/EricWVGG/vapor-command-problem
This command runs correctly, but returns a warning…
vapor run quiz --answer=one
warning: [Deprecated] --option=value syntax is deprecated. Please use --option value (with no =) instead.
If we follow those directions…
vapor run quiz --answer one
we get Fatal error: Error raised at top level: ⚠️ CommandError: Too many arguments or unsupported options were supplied: ["one"]
https://gist.github.com/calebkleveter/9f269d8f5323f2a4679be53e57939c9e
Note: This issue will be fleshed out more when I have time.
Hi,
could you please add to Terminal.swift line 82:
#else
var pass = ""
This fixes compilation for other platforms. In my case I have an iOS project, that shares some API with the backend and that depends on async-kit -> console-kit. I know, that my dependencies are too strong and that the API models should have less dependencies, but they are managed externally so I have less influence there. If console-kit compiled for other platforms as well with the above lines, it would be nice and solves the problem. Maybe add a #warning
, that the current platform is not supported in the #else
case.
If you rather want a pull request, let me know.
Add a validation
property to CommandArgument
and CommandOption
which is an array of (Sting)throws -> Void
that validates the values when they are parsed from input.
It would look something like this:
CommandArgument(name: "times", default: "1", validations: [isInt])
let isInt: (String)throws -> Void = { times in
guard Int(times) != nil else {
throw CommandError(identifier: "validationFailed", reason: "Cannot convert value `\(times)` to Int"
}
}
This value would be assigned an empty array by default, so it would not be a breaking change.
In the context of converting a code base using Vapor commands for batch jobs to Swift 5.5's async/await, the issue arose that run
does not have an async overload. This poses the following problem when converting
fun run(using context: CommandContext, signature: Signature) throws {
try f.wait()
}
where f
is func f() → EventLoopFuture<Void>
.
A workaround is to use
func run(using context: CommandContext, signature: Signature) throws {
let promise = context.application.eventLoopGroup.next()
.makePromise(of: Void.self)
promise.completeWithAsync {
try await f()
}
try promise.futureResult.wait()
}
but it would be nice if this wrapper (or simply handling via @main
, if possible, or some other solution) existed upstream such that one could simply write
func run(using context: CommandContext, signature: Signature) async throws {
try await f()
}
Something like moveTo(x: y:)
that moves the cursor to x,y
position and any output commands afterward would start at the given position.
I am working on a new project Autobahn and am using Console for the CLI tool 👍 I am wanting to define a default command that runs if no existing matches are found. So here is my setup:
let group = Group(id: executable, commands: [
Drive(console: terminal),
Init(console: terminal),
], help: [
"CLI for 'autobahn' - The swiftiest way to automate mundane developer tasks for your iOS apps",
], fallback: Drive(console: terminal))
try terminal.run(group, arguments: args)
This is the output for autobahn --help
:
Autobahn v0.1.0
Usage: autobahn command
CLI for 'autobahn' - The swiftiest way to automate mundane developer tasks for your iOS apps
Commands:
drive *Drive a specific highway
init Creates a default Autobahn.swift file
Use `autobahn command --help` for more information on a command.
And this is the output for autobahn drive --help
Autobahn v0.1.0
Usage: autobahn drive <highway> [--verbose]
*Drive a specific highway
Arugments:
highway The name of the highway you would like to drive
Options:
verbose Print more info to help debug issues
Now what I want to do is mark Drive as the default to I can just run autobahn <highway>
and have it run autobahn drive <highway>
behind the scenes. Basically I want to be able to pass arguments to the fallback command
Incorrect behavior while logging HTTP requests (method+path), using a custom middleware, into console. As a result, different requests end up interleaving each other in the console. The middleware itself is pretty much the same as the default ErrorMW, except it uses Logger to log the actual request (logger's methods aren't promised based E.g: https://github.com/vapor/vapor/blob/master/Sources/Vapor/Logging/ConsoleLogger.swift#L15)
The console should log each request in a new line. E.g:
[VERBOSE] [date] GET /file1.html (ABC.swift)
[VERBOSE] [date] GET /file2.css (ABC.swift)
[VERBOSE] [date] GET /file3.css (ABC.swift)
[VERBOSE] [date] GET /file4.js (ABC.swift)
[VERBOSE] [date] GET /file5.js (ABC.swift)
Requests will be interleaved instead:
[VERBOSE] [date] GET /file1.html [VERBOSE] [date] GET /file2.css (ABC.swift)
[VERBOSE] [date] GET /file3.css (ABC.swift)
[VERBOSEVERBOSE] [ ] [date] [date] GET /file4.js GET /file5.js
When trying to build a project setup as iOS 13+, that includes Vapor, the project fails to build with the following errors in the console:
/Users/runner/work/1/s/DerivedData/SourcePackages/checkouts/console-kit/Sources/ConsoleKit/Command/Async/AsyncCommand.swift:84:67: concurrency is only available in iOS 13.0.0 or newer
func run(using context: CommandContext, signature: Signature) async throws
Steps to reproduce the behavior:
The functions should be properly tagged with @available(iOS 13, *) so Swift will be able to compile without those errors.
We are having some warnings at the moment that we are building a vapor project
Steps to reproduce the behavior:
Using the latest version 4.85.0
, the displayed warnings are like this when we build:
.build/checkouts/console-kit/Sources/ConsoleKit/Activity/ActivityIndicator.swift:1:1: remark: add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'Dispatch'
import Dispatch
.build/checkouts/vapor/Sources/Vapor/Validation/Validators/CharacterSet.swift:1:1: remark: add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'Foundation'
import Foundation
.build/checkouts/vapor/Sources/Vapor/Sessions/MemorySessions.swift:1:1: remark: add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'Dispatch'
import Foundation
No warnings
4.85.0
18.6.0
22.04
The context is this
Sometimes, it's handy to change colors of text in a console. Right now, it's not a very fluent process to do that.
The goal is to make styling text in the console a much easier process.
Create an class called Styler
. This class would look something like this:
class Styler: CustomStringConvertible {
var str: String
var color: ConsoleColor = .none
var bgColor: ConsoleColor = .none
var modifier: ConsoleModifier = .none // e.g. bold, dim, underline, etc.
var description: String {
// Stylize the string and reset after string finished
}
init(str: String) {
self.str = str
}
// Modifiers
var red: Styler {
color = .red
return self
}
var blue: Styler {
color = .blue
return self
}
...
var bold: Styler {
modifier = ConsoleModifier(raw: modifier.rawValue | ConsoleModifier.bold)
return self
}
}
Then, it could be used like this:
console.info("Plain text, \( Styler("blue text").blue ), \( Styler("bold text").bold.bgGreen.white ), \( Styler("no text").hidden.bgBlue.white ).")
With a bit more tweaking, it could be used for nested styling.
Note: The idea was borrowed from here.
This will not change any existing applications.
We could stick with the existing way of styling colors, which is a bit painful.
I've been using Console for building some internal tools, and it's been going great so far. Thanks for separating it from vapor, and for the recent ConsoleKit changes!
Anyway, I was wondering today whether you folks considered allowing for an argument to be specified multiple times? For instance, say I have an input
argument that can be specified multiple times. It'd be used like so:
my-command --input /path/to/input --input /path/to/other/input --output /path/to/output
Ideally, this would allow me to get an array of paths for the key "input"
from a CommandContext
.
Thoughts?
Please add a method to detect single or multiple pressed keys and returning the keyCode(s). This feature is required for accepting password input or screen navigation control (console games?). If you have ever used Pascal and utilized the famous CRT
unit, you'll know the ReadKey()
and KeyPressed()
functions that provide such ability.
Thank you.
When use group command, it will pass redundant argument to arguments.
For example:
final class BuildProject: Command {
let id = "project"
let signature: [Argument] = [
Value(name: "projectname")
]
let console: ConsoleProtocol
init(console: ConsoleProtocol) {
self.console = console
}
func run(arguments: [String]) throws {
guard let projectname = try value("projectname", from: arguments).string else {
throw HVPackageError.general("no projectname")
}
_ = try console.backgroundExecute(program: "xcodebuild", arguments: ["-project", projectname])
}
}
try terminal.run(executable: executable, commands: [
Group(id: "build", commands: [
BuildProject(console: terminal)
], help: [
])
], arguments: Array(iterator), help: [
])
use in commanline:
package build project projectName
but let projectname = try value("projectname", from: arguments).string will be project not projectName
See #165 results
/Users/jonasschwartz/Documents/VaporCloud/Swift/deployment3/Sources/App/Git.swift:22:38: error: ambiguous use of 'backgroundExecute(program:arguments:)'
let result = try context.console.backgroundExecute(
^
Oftentimes, it's handy to render tables to the console to debug data. This would be a good thing to include in Console.
These would be the parameters I would envision being included:
This could be useful for improving the output from Vapor itself. For instance, when printing results form queries, it would be much better to print the data in tables than plain text.
For reference, someone already has a table generator written in Swift here, but it's fairly limited.
Sometimes part of the error messages is given here.
the #if Xcode
flag is not working on generated xcode projects with latest swift 3.0 packages.
I have raised a PR that points out the issue in Swift Package Manager repo and they are reviewing. Just wanted to loop people in since this project caused me to notice it.
Rename Dsimple
to be NO_ANIMATION
We can pass in a -y
flag to automatically answer yes
to a confirmation dialogue. I think it would be helpful to add a -n
flag to automatically answer no
. This could be useful if we need to regenerate an Xcode project the toolbox and don't want to re-open it:
vapor xcode -n
A new protocol UnvalidatedCommand
that did not require a command to pre-define it's supported arguments and options would be useful for implementing commands like vapor run
that forward to an underlying program.
The documentation reads:
/// Upon answering, the prompt and options will be cleared from the console and only the output will remain:
I was building a console tool for users who aren't very comfortable on the command line, and I'm concerned that it may be disorienting for them to have the prompt and options disappear after they answer. I was concerned enough about it that I added in a 1 second pause after each choice.
Is your feature request related to a problem? Please describe.
Parsing property wrappers like Argument
, Option
, and Flag
don't have the ability to set a default value in the event the user doesn't pass a value for it at the command line. Currently, Argument
will fatalError
(effectively making all arguments required), Option
will default to nil
, and Flag
will default to false
. Allowing for a default value would enable cleaner code with less duplication since there would be less need for checking $isPresent
and handling nil
unnecessarily
For example, let's say I've defined an enumeration for handling conflicts between 2 values:
enum ConflictResolutionStrategy: String, LosslessStringConvertible {
case error
case keepExisting = "keep-existing"
case overwrite
init?(_ description: String) {
self.init(rawValue: description)
}
var description: String {
rawValue
}
}
I'd like the user to be able to choose their preferred strategy, but I'd also like to have a sensible default in case they don't. Currently, my only option for using ConsoleKit to achieve something like this is to declare my property type as an Option
, like this:
@Option(name: "conflict-strategy", help: "The method by which conflicts should be resolved")
var conflictStrategy: ConflictResolutionStrategy?
However, this forces me to declare my property as optional and handle the case where the user doesn't choose a strategy by checking for nil
. This means that everywhere that I want to apply special handling based on the current conflictStrategy
, I not only need to unwrap the optional, but I also need to choose a strategy to use every time in the event that the user didn't. This can quickly lead to fragmentation and inconsistency across the codebase around how conflicts should be handled by default, which could easily lead to different default behaviors in different places and a confusing user experience
Describe the solution you'd like
I'd like to assign a default value at the site where the property is defined with Swift's standard inline property initialization syntax, such as what's familiar from ArgumentParser
's corresponding types or SwiftUI's wrappers like @State
or @Published
. For example:
@Option(name: "conflict-strategy", help: "The method by which conflicts should be resolved")
var conflictStrategy: ConflictResolutionStrategy = .error
This would allow the property type to be non-optional and have a default value chosen once and respected everywhere. This is possible by defining an initializer for the relevant property wrappers that accept an argument wrappedValue: WrappedValue
as their first argument
Describe alternatives you've considered
Alternatively, the relevant property wrappers could take an extra parameter to their initializers, so a default value could be passed there. For example:
@Option(name: "conflict-strategy", help: "The method by which conflicts should be resolved", default: .error)
var conflictStrategy: ConflictResolutionStrategy
However, this is less obvious and discoverable in my opinion, since there's already well-established language syntax for handling this case
Additional context
N/A
Hey all 👋
I'm new Vapor and loving it so far! My prior development experience is most recently iOS, but before that I was an SRE using Rails. I've discovered that logging in Vapor doesn't quite match my expectations based on my past experiences.
Example 1: Server
[ INFO ] GET /favicon.ico
[ ERROR ] Not Found
At the info
log level this output is OK (if a little terse), but at the warning
level and above it becomes useless since the error line provides no context.
Example 2: Queues
[ INFO ] Starting jobs worker (queue: tmdb)
[ INFO ] Dequeing Job:
[ INFO ] Dispatched queue job
[ INFO ] Dequeing Job:
[ INFO ] Dequeing Job:
[ INFO ] Dispatched queue job
[ INFO ] Dequeing Job:
This output at least tells me something happened, but otherwise it's mostly useless. I'll discuss log levels more below, but I think it's important to mention that this is the default output for Vapor in the development environment, and therefore what newcomers experience.
Vapor can of course log more information using the metadata mechanism, but at at least in ConsoleLogger
that's restricted to the debug
level. This behavior is contrary to my past experiences. For iOS we would log quite liberally at the info
level, and include those logs in crash reports sent to Crashlytics. Things we'd log at this level included network activity, state machine transitions, events, etc. We logged everything necessary to help us track the users progress through the app to help in diagnosing the crash. In Rails we also used logging liberally.
We could try to define exactly the amount of information that each log level should include, but I think many people have their own preference, so while sensible defaults are great, it'd be great if we could accommodate everyone's personal preference.
Therefore I'd like to put forward some ideas. Please note that the examples provided below aren't serious API suggestions, they're just to convey the concepts.
logger.info("Hello", metadata: [
.info("job", name),
.debug("payload", payload)
])
Rather than all metadata only being logged at the debug level, this gives us the ability to more finely control individual log line verbosity.
Each subsystem (e.g queues, server, etc.) defines a default set of metadata items that are included on every log line output by that subsystem, even for custom lines added by users. For example the queues subsystem might contain:
struct MetadataItem {
static var timestamp: Self {
.init(name: "timestamp", value: { ... })
}
static var jobId: Self {
.init(name: "job-id", value: { ... })
}
static var jobName: Self {
.init(name: "job-name", value: { ... })
}
static var location: Self {
.init(name: "location", value: { ... })
}
}
Then calling logger.debug("Hello")
would yield 2020-05-20 12:00:00 [ INFO ] Hello [job-name: "MyJob", job-id: 1234] (File.swift:42)
To accommodate personal preference, users would also be able to customize the default items output at each log level, e.g:
queues.logger.defaultMetadata.include([.timestamp, .jobName], for: [.critical, .error, .warning])
queues.logger.defaultMetadata.include([.timestamp, .jobName, .jobId], for: .info)
queues.logger.defaultMetadata.include(.all, for: [.debug, .trace])
Since info is the default in development mode, I think it should be more informative to aid development efforts, whilst also being a more friendly experience for newcomers.
By trying to install the vapor-beta toolbox the brew formula throws an error. (vapor/homebrew-tap#26)
This relates all to the errors that appear while building this project.
Logs of Cannot convert value of type 'Option<_>' to specified type 'String'
appear in Xcode or the command line.
I've Swift 5.1.3 installed and the latest Xcode version too.
Maybe there is an obvious thing I'm not seeing to fix this issues.
When I run the ConsoleKit tests with the thread sanitizer on, there are a few races that need to be cleaned up.
I noticed this while validating my own tools that rely on heavy multithreading, and the ActivityIndicator
was giving me grief, and making it hard to find my own issues.
To reproduce this, just try the following at the command-line:
swift test --sanitize=thread
PR incoming…
Right now the easiest way to create a working CLI is to boot up a Vapor Application
instance when the command is run. This seems a bit like overkill. We should either document a better way to do this if one already exists or create a better API for Console 4.
i miss a detailed documentation of the console package
When using the secure input within ConsoleKit, app crashes with Trace/breakpoint trap
error.
Simplified version of my code that produces error within Docker but NOT on macOS.
struct CreateUserCommand: Command {
struct Signature: CommandSignature {
@Argument(name: "username")
var username: String
}
var help: String {
NSLocalizedString("Create a new user with the provided email address.", comment: "")
}
func run(using context: ConsoleKit.CommandContext, signature: Signature) throws {
context.console.print("We're about to setup a new user using the username `\(signature.username)`.")
let firstName: String = context.console.ask("User's First Name: ")
let lastName: String = context.console.ask("User's Last Name: ")
let emailAddress: String = context.console.ask("E-Mail Address: ")
// Crashes right after this displays.
let password: String = context.console.ask("Password: ", isSecure: true)
let passwordConfirmation: String = context.console.ask("Confirm Password: ", isSecure: true)
}
}
Steps to reproduce the behavior:
swift run App createUser userName
and follow promptsTo not crash and accept secure input.
FROM swift:5.8-jammy as build
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get -q update \
&& apt-get -q dist-upgrade -y \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
COPY ./Package.* ./
RUN swift package resolve
COPY . .
RUN swift build -c release --static-swift-stdlib
WORKDIR /staging
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/App" ./
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true
FROM ubuntu:jammy
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get -q update \
&& apt-get -q dist-upgrade -y \
&& apt-get -q install -y \
ca-certificates \
tzdata \
&& rm -r /var/lib/apt/lists/*
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor
WORKDIR /app
COPY --from=build --chown=vapor:vapor /staging /app
USER vapor:vapor
EXPOSE 8080
ENTRYPOINT ["./App"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
Package.swift
// swift-tools-version:5.8
import PackageDescription
let package = Package(
name: "FASAAPI",
platforms: [
.macOS(.v13)
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"),
.package(url: "https://github.com/vapor/leaf.git", from: "4.0.0"),
.package(url: "https://github.com/swift-server-community/SwiftPrometheus.git", from: "1.0.0"),
.package(url: "https://github.com/vapor-community/sendgrid.git", from: "5.0.0"),
.package(url: "https://github.com/DiscordBM/DiscordBM", from: "1.0.0"),
.package(url: "https://github.com/DiscordBM/DiscordLogger", from: "1.0.0-beta.1"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.1.0")
],
targets: [
.executableTarget(
name: "App",
dependencies: [
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
.product(name: "Leaf", package: "leaf"),
.product(name: "Vapor", package: "vapor"),
.product(name: "SwiftPrometheus", package: "SwiftPrometheus"),
.product(name: "SendGrid", package: "sendgrid"),
.product(name: "DiscordBM", package: "DiscordBM"),
.product(name: "DiscordLogger", package: "DiscordLogger"),
.product(name: "Logging", package: "swift-log"),
.product(name: "AsyncHTTPClient", package: "async-http-client")
],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
.enableUpcomingFeature("ConciseMagicFile"),
.enableUpcomingFeature("BareSlashRegexLiterals"),
.enableUpcomingFeature("ForwardTrailingClosures"),
.enableUpcomingFeature("ExistentialAny"),
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]
),
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor")
])
]
)
Oddly all tests pass within CI/CD system but this command fails when ran from a Docker terminal.
Currently the implementation of flags isn't conformant to standard convention vapor/docs#689. This and I assume several other issues could be alleviated by using ArgumentParser
to format input for a Command
.
You should probably tell people what exactly this package does and how to use it.
I wrote a little command line tool in Xcode. While the tool is doing his job I'd like to indicate that the tool is working with a little loading bar. For this I've chosen the LoadingBar
. Unfortunately the loading bar behaves not as expected.
Given is the following code:
import ConsoleKit
import Foundation
let loadingBar = Terminal().loadingBar(title: "TEST")
loadingBar.start()
sleep(3)
loadingBar.succeed()
When I run this code I get the following output:
The output seems to be wrong because I expect every update on one/the same line and not each print in a separate line. But when I run the very same code with swift run
on the console the output is as expected:
Does anyone know whether this is a bug or do I have to configure the console differently?
help output order of commands is random or inconsistent each time, since it's a dictionary, maybe should be alphabetical?
When using ConsoleKit version 4.8.0 the log output that is created via the request loggers in Vapor is not printed to the console immediately as it was generated but only every 30 seconds or so in bulk.
The issue does not show in Xcode but on Linux.
In the deployment in question, I'm using Docker Swarm, and usually in that scenario logs are read via the docker service logs -f ...
command. But the issue also shows when investigating a single Docker container via docker logs -f ...
.
Steps to reproduce the behavior:
docker logs -f ...
Log entries appearing as they are generated as it was with ConsoleKit prior to version 4.8.0
Now that SE-0252 has been accepted, we should try to adopt @dynamicMemberLookup
in ConsoleKit:
let foo = try context.option(\.foo)
// could become
let foo = context.options.foo
Conformance to @dynamicMemberLookup
should be fairly straightforward except for one issue: throwing. Since @dynamicMemberLookup
will result in a subscript / computed-property for the given key path, we will not be able to throw. To remedy this, we can insert an "input validation" step before the command actually runs. In this step, we would ensure any supplied arguments / options have been converted to their desired types and store them. Then the member lookup could be done once the command runs without throwing. (Even if we don't decide to adopt @dynamicMemberLookup
, this early validation step could be a nice addition)
vapor/console 2.0 supported commands that could have an indefinite number of arguments / options. This was used to power commands like vapor run
:
vapor run serve
vapor run prepare --revert
In vapor/console 3.0, this ability was lost due to type-safe commands. We should consider whether we want to re-allow this in 4.0 w/ a new mechanism, or if we should implement dynamic commands slightly differently. In the current version, it would be possible to do:
vapor run serve
vapor run "serve"
vapor run "prepare --revert"
struct Signature: CommandSignature {
let command = Argument<String>("command")
}
Property wrappers could be used to make the CommandSignature
API more Swifty. Here's an idea for how this could look:
struct Cowsay: Command {
struct Signature: CommandSignature {
@Argument(help: "What the cow should say")
var message: String
@Option(short: "e", help: "Change the cow's eyes")
var eyes: String?
@Option(short: "t", help: "Change the cow's tongue")
var tongue: String?
@Flag(help: "uses == for cow's eyes")
var borg: Bool
}
let help = "Prints an ASCII cow with a message"
func run(context: CommandContext, signature: Signature) throws {
print(signature.message) // String
print(signature.eyes) // String?
print(signature.tongue) // String?
print(signature.borg) // Bool
}
}
Here's how this command could be used:
Usage: cowsay <message> [--eyes,-e eyes] [--tongue,-t tongue] [--borg]
Prints an ASCII cow with a message
Arguments:
message What the cow should say
Options:
--eyes, -e Change the cow's eyes
--tongue, -t Change the cow's tongue
Flags:
--borg uses == for cow's eyes
Add support for helping users with typos in command names. Like this:
Error: unknown command "bza"
Did you mean this?
baz
This might algorithm be useful: https://en.wikipedia.org/wiki/Levenshtein_distance
sample project: https://github.com/EricWVGG/vapor-command-problem
This command should respect "one" as the default value for "answer"
vapor run quiz
Expected response: better not be the middle one
Actual response: What?
Is your feature request related to a problem? Please describe.
I'm writing a Command for migrating between two database. The majority (if not all) of database interaction with Fluent is async.
So my command needs to conform to ConsoleKit.AsyncCommand
. However, application.commands
(of type ConsoleKit.Commands
) only accepts synchronous commands.
Describe the solution you'd like
An extension on Commands to accept any AsyncCommand
, which wraps it into a makeshift "sync" command.
import ConsoleKit
extension Commands {
public mutating func use(_ command: any AsyncCommand, as name: String, isDefault: Bool = false) {
self.use(command.wrapped(), as: name, isDefault: isDefault)
}
}
extension AsyncCommand {
private func wrapped() -> AnyCommand {
AsyncCommandWrapper(asyncCommand: self)
}
}
/// Inspired by others.
private struct AsyncCommandWrapper<Wrapped: AsyncCommand>: Command {
let asyncCommand: Wrapped
var help: String { asyncCommand.help }
func run(using context: CommandContext, signature: Wrapped.Signature) throws {
let promise = context.application.eventLoopGroup.next()
.makePromise(of: Void.self)
promise.completeWithTask {
try await asyncCommand.run(using: context, signature: signature)
}
try promise.futureResult.wait()
}
func outputAutoComplete(using context: inout CommandContext) throws {
try asyncCommand.outputAutoComplete(using: &context)
}
func outputHelp(using context: inout CommandContext) throws {
try asyncCommand.outputHelp(using: &context)
}
func renderCompletionFunctions(using context: CommandContext, shell: Shell) -> String {
return asyncCommand.renderCompletionFunctions(using: context, shell: shell)
}
}
Describe alternatives you've considered
Adding an async commands container to Vapor.Application. I think this might be less desirable since it broadens the interface.
public var asyncCommands: AsyncCommands {
get { self.core.storage.asyncCommands }
set { self.core.storage.asyncCommands = newValue }
}
Additional context
The suggested solution does conflate some of the responsibilities of Commands and AsyncCommands. I would say it is useful from a vapor
standpoint, but not necessarily from a ConsoleKit
standpoint.
I've no strong preference. I was confused that there is this nice async API inside vapor/console-kit
which does not fit into Vapor.Application
. I'm sure someone with a better understanding of these packages and their relationship can bring insight.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.