From 50bed81038f10091d35c5719df8078612393ae95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Nicole?= Date: Fri, 3 Aug 2018 16:59:16 +0200 Subject: Improve documentation of SinkSH Summary: - Add support for positional arguments, options, flags in the syntax tree - Add automatic generation of usage string TODO: - ~~Better document the `--reduce` option in the `list` command~~ - Get the parent command for sub-commands so we could show help for `trace on` and not just `on` - Pass the syntax to the implementation of the command so commands can easily show help on wrong usage. Also, SyntaxTree could do automatic input validation in the future, this could also help. - Even with the new documentation, some command could still be confusing. Should we add some usage examples? Reviewers: cmollekopf Reviewed By: cmollekopf Tags: #sink Differential Revision: https://phabricator.kde.org/D14547 --- sinksh/syntax_modules/core_syntax.cpp | 14 +---- sinksh/syntax_modules/sink_clear.cpp | 7 ++- sinksh/syntax_modules/sink_count.cpp | 10 +++- sinksh/syntax_modules/sink_create.cpp | 35 ++++++++---- sinksh/syntax_modules/sink_drop.cpp | 6 +- sinksh/syntax_modules/sink_inspect.cpp | 22 ++++++- sinksh/syntax_modules/sink_list.cpp | 18 +++++- sinksh/syntax_modules/sink_livequery.cpp | 15 ++++- sinksh/syntax_modules/sink_modify.cpp | 27 +++++---- sinksh/syntax_modules/sink_remove.cpp | 28 ++++----- sinksh/syntax_modules/sink_stat.cpp | 11 +++- sinksh/syntax_modules/sink_sync.cpp | 5 ++ sinksh/syntax_modules/sink_trace.cpp | 7 +-- sinksh/syntaxtree.cpp | 98 ++++++++++++++++++++++++++++++++ sinksh/syntaxtree.h | 23 ++++++++ 15 files changed, 262 insertions(+), 64 deletions(-) diff --git a/sinksh/syntax_modules/core_syntax.cpp b/sinksh/syntax_modules/core_syntax.cpp index e90d894..07acc28 100644 --- a/sinksh/syntax_modules/core_syntax.cpp +++ b/sinksh/syntax_modules/core_syntax.cpp @@ -57,19 +57,7 @@ bool showHelp(const QStringList &commands, State &state) if (!syntax->help.isEmpty()) { state.print(": " + syntax->help); } - state.printLine(); - - if (!syntax->children.isEmpty()) { - state.printLine("Sub-commands:", 1); - QSet sorted; - for (auto childSyntax: syntax->children) { - sorted.insert(childSyntax.keyword); - } - - for (auto keyword: sorted) { - state.printLine(keyword, 1); - } - } + state.printLine(QString("\n\n") + syntax->usage()); } else { state.printError("Unknown command: " + commands.join(" ")); } diff --git a/sinksh/syntax_modules/sink_clear.cpp b/sinksh/syntax_modules/sink_clear.cpp index e676dd6..ca38943 100644 --- a/sinksh/syntax_modules/sink_clear.cpp +++ b/sinksh/syntax_modules/sink_clear.cpp @@ -35,10 +35,12 @@ namespace SinkClear { +Syntax::List syntax(); + bool clear(const QStringList &args, State &state) { if (args.isEmpty()) { - state.printError(QObject::tr("Please provide at least one resource to clear.")); + state.printError(syntax()[0].usage()); return false; } for (const auto &resource : args) { @@ -53,6 +55,9 @@ bool clear(const QStringList &args, State &state) Syntax::List syntax() { Syntax clear("clear", QObject::tr("Clears the local cache of one or more resources (be careful!)"), &SinkClear::clear, Syntax::NotInteractive); + + clear.addPositionalArgument({.name = "resource", .help = "The resource to clear"}); + clear.completer = &SinkshUtils::resourceCompleter; return Syntax::List() << clear; diff --git a/sinksh/syntax_modules/sink_count.cpp b/sinksh/syntax_modules/sink_count.cpp index 04a9550..1edf1c2 100644 --- a/sinksh/syntax_modules/sink_count.cpp +++ b/sinksh/syntax_modules/sink_count.cpp @@ -37,12 +37,14 @@ namespace SinkCount { +Syntax::List syntax(); + bool count(const QStringList &args, State &state) { Sink::Query query; query.setId("count"); if (!SinkshUtils::applyFilter(query, SyntaxTree::parseOptions(args))) { - state.printError(QObject::tr("Options: $type $filter")); + state.printError(syntax()[0].usage()); return false; } @@ -63,7 +65,11 @@ bool count(const QStringList &args, State &state) Syntax::List syntax() { - Syntax count("count", QObject::tr("Returns the number of items of a given type in a resource. Usage: count "), &SinkCount::count, Syntax::EventDriven); + Syntax count("count", QObject::tr("Returns the number of items of a given type in a resource"), &SinkCount::count, Syntax::EventDriven); + + count.addPositionalArgument({.name = "type", .help = "The entity type to count"}); + count.addPositionalArgument({.name = "resource", .help = "A resource id where to count", .required = false}); + count.completer = &SinkshUtils::typeCompleter; return Syntax::List() << count; diff --git a/sinksh/syntax_modules/sink_create.cpp b/sinksh/syntax_modules/sink_create.cpp index f18a990..b1631cd 100644 --- a/sinksh/syntax_modules/sink_create.cpp +++ b/sinksh/syntax_modules/sink_create.cpp @@ -40,15 +40,12 @@ using namespace Sink; namespace SinkCreate { +Syntax::List syntax(); + bool create(const QStringList &allArgs, State &state) { - if (allArgs.isEmpty()) { - state.printError(QObject::tr("A type is required"), "sinkcreate/02"); - return false; - } - if (allArgs.count() < 2) { - state.printError(QObject::tr("A resource ID is required to create items"), "sinkcreate/03"); + state.printError(syntax()[0].usage()); return false; } @@ -173,15 +170,33 @@ bool identity(const QStringList &args, State &state) return true; } - Syntax::List syntax() { Syntax::List syntax; Syntax create("create", QObject::tr("Create items in a resource"), &SinkCreate::create); - create.children << Syntax("resource", QObject::tr("Creates a new resource"), &SinkCreate::resource); - create.children << Syntax("account", QObject::tr("Creates a new account"), &SinkCreate::account); - create.children << Syntax("identity", QObject::tr("Creates a new identity"), &SinkCreate::identity); + create.addPositionalArgument({ .name = "type", .help = "The type of entity to create (mail, event, etc.)" }); + create.addPositionalArgument({ .name = "resourceId", .help = "The ID of the resource that will contain the new entity" }); + create.addPositionalArgument( + { .name = "key value", .help = "Content of the entity", .required = false, .variadic = true }); + + Syntax resource("resource", QObject::tr("Creates a new resource"), &SinkCreate::resource); + resource.addPositionalArgument({ .name = "type", .help = "The type of resource to create" }); + resource.addPositionalArgument( + { .name = "key value", .help = "Content of the resource", .required = false, .variadic = true }); + + Syntax account("account", QObject::tr("Creates a new account"), &SinkCreate::account); + account.addPositionalArgument({ .name = "type", .help = "The type of account to create" }); + account.addPositionalArgument( + { .name = "key value", .help = "Content of the account", .required = false, .variadic = true }); + + Syntax identity("identity", QObject::tr("Creates a new identity"), &SinkCreate::identity); + identity.addPositionalArgument( + { .name = "key value", .help = "Content of the identity", .required = false, .variadic = true }); + + create.children << resource; + create.children << account; + create.children << identity; syntax << create; return syntax; diff --git a/sinksh/syntax_modules/sink_drop.cpp b/sinksh/syntax_modules/sink_drop.cpp index 3b9a817..eaa3041 100644 --- a/sinksh/syntax_modules/sink_drop.cpp +++ b/sinksh/syntax_modules/sink_drop.cpp @@ -35,10 +35,12 @@ namespace SinkDrop { +Syntax::List syntax(); + bool drop(const QStringList &args, State &state) { if (args.isEmpty()) { - state.printError(QObject::tr("Please provide at least one resource to drop.")); + state.printError(syntax()[0].usage()); return false; } @@ -60,6 +62,8 @@ bool drop(const QStringList &args, State &state) Syntax::List syntax() { Syntax drop("drop", QObject::tr("Drop all caches of a resource."), &SinkDrop::drop, Syntax::NotInteractive); + drop.addPositionalArgument({.name = "resource", .help = "Id(s) of the resource(s) to drop", .required = true, .variadic = true}); + drop.completer = &SinkshUtils::resourceOrTypeCompleter; return Syntax::List() << drop; } diff --git a/sinksh/syntax_modules/sink_inspect.cpp b/sinksh/syntax_modules/sink_inspect.cpp index f9cc50a..355efa3 100644 --- a/sinksh/syntax_modules/sink_inspect.cpp +++ b/sinksh/syntax_modules/sink_inspect.cpp @@ -55,10 +55,14 @@ QString parse(const QByteArray &bytes) } } +Syntax::List syntax(); + bool inspect(const QStringList &args, State &state) { if (args.isEmpty()) { - state.printError(QObject::tr("Options: [--resource $resource] ([--db $db] [--filter $id] [--showinternal] | [--validaterids $type] | [--fulltext [$id]])")); + //state.printError(QObject::tr("Options: [--resource $resource] ([--db $db] [--filter $id] [--showinternal] | [--validaterids $type] | [--fulltext [$id]])")); + state.printError(syntax()[0].usage()); + return false; } auto options = SyntaxTree::parseOptions(args); auto resource = SinkshUtils::parseUid(options.options.value("resource").value(0).toUtf8()); @@ -236,7 +240,21 @@ bool inspect(const QStringList &args, State &state) Syntax::List syntax() { - Syntax state("inspect", QObject::tr("Inspect database for the resource requested"), &SinkInspect::inspect, Syntax::NotInteractive); + Syntax state("inspect", QObject::tr("Inspect database for the resource requested"), + &SinkInspect::inspect, Syntax::NotInteractive); + + state.addParameter("resource", + { .name = "resource", .help = "Which resource to inspect", .required = true }); + state.addParameter("db", + { .name = "database", .help = "Which database to inspect"}); + state.addParameter("filter", + { .name = "id", .help = "A specific id to filter the results by (currently not working)"}); + state.addFlag("showinternal", "Show internal fields only"); + state.addParameter("validaterids", + { .name = "type", .help = "Validate remote Ids of the given type"}); + state.addParameter("fulltext", + { .name = "id", .help = "If 'id' is not given, count the number of fulltext documents. Else, print the terms of the document with the given id"}); + state.completer = &SinkshUtils::resourceCompleter; return Syntax::List() << state; diff --git a/sinksh/syntax_modules/sink_list.cpp b/sinksh/syntax_modules/sink_list.cpp index 32fae4f..eef188b 100644 --- a/sinksh/syntax_modules/sink_list.cpp +++ b/sinksh/syntax_modules/sink_list.cpp @@ -39,6 +39,8 @@ namespace SinkList { +Syntax::List syntax(); + static QByteArray compressId(bool compress, const QByteArray &id) { if (!compress) { @@ -117,7 +119,7 @@ QStringList printToList(const Sink::ApplicationDomain::ApplicationDomainType &o, bool list(const QStringList &args_, State &state) { if (args_.isEmpty()) { - state.printError(QObject::tr("Options: $type [--resource $resource] [--compact] [--filter $property=$value] [--id $id] [--showall|--show $property] [--reduce $reduceProperty:$selectorProperty] [--sort $sortProperty] [--limit $count]")); + state.printError(syntax()[0].usage()); return false; } @@ -127,7 +129,7 @@ bool list(const QStringList &args_, State &state) Sink::Query query; query.setId("list"); if (!SinkshUtils::applyFilter(query, options)) { - state.printError(QObject::tr("Options: $type [--resource $resource] [--compact] [--filter $property=$value] [--showall|--show $property]")); + state.printError(syntax()[0].usage()); return false; } @@ -198,6 +200,18 @@ bool list(const QStringList &args_, State &state) Syntax::List syntax() { Syntax list("list", QObject::tr("List all resources, or the contents of one or more resources."), &SinkList::list, Syntax::NotInteractive); + + list.addPositionalArgument({.name = "type", .help = "The type of content to list (resource, identity, account, mail, etc.)"}); + list.addParameter("resource", { .name = "resource", .help = "List only the content of the given resource" }); + list.addFlag("compact", "Use a compact view (reduces the size of IDs)"); + list.addParameter("filter", { .name = "property=$value", .help = "Filter the results" }); + list.addParameter("id", { .name = "id", .help = "List only the content with the given ID" }); + list.addFlag("showall", "Show all properties"); + list.addParameter("show", { .name = "property", .help = "Only show the given property" }); + list.addParameter("reduce", { .name = "property:$selectorProperty", .help = "Combine the result with the same $property, sorted by $selectorProperty" }); + list.addParameter("sort", { .name = "property", .help = "Sort the results according to the given property" }); + list.addParameter("limit", { .name = "count", .help = "Limit the results" }); + list.completer = &SinkshUtils::resourceOrTypeCompleter; return Syntax::List() << list; } diff --git a/sinksh/syntax_modules/sink_livequery.cpp b/sinksh/syntax_modules/sink_livequery.cpp index e9e85ec..2d8d5d0 100644 --- a/sinksh/syntax_modules/sink_livequery.cpp +++ b/sinksh/syntax_modules/sink_livequery.cpp @@ -38,10 +38,12 @@ namespace SinkLiveQuery { +Syntax::List syntax(); + bool livequery(const QStringList &args_, State &state) { if (args_.isEmpty()) { - state.printError(QObject::tr("Options: $type [--resource $resource] [--compact] [--filter $property=$value] [--id $id] [--showall|--show $property]")); + state.printError(syntax()[0].usage()); return false; } @@ -55,7 +57,7 @@ bool livequery(const QStringList &args_, State &state) query.setId("livequery"); query.setFlags(Sink::Query::LiveQuery); if (!SinkshUtils::applyFilter(query, options)) { - state.printError(QObject::tr("Options: $type [--resource $resource] [--compact] [--filter $property=$value] [--showall|--show $property]")); + state.printError(syntax()[0].usage()); return false; } if (options.options.contains("resource")) { @@ -124,6 +126,15 @@ bool livequery(const QStringList &args_, State &state) Syntax::List syntax() { Syntax list("livequery", QObject::tr("Run a livequery."), &SinkLiveQuery::livequery, Syntax::EventDriven); + + list.addPositionalArgument({ .name = "type", .help = "The type to run the livequery on" }); + list.addParameter("resource", { .name = "resource", .help = "Filter the livequery to the given resource" }); + list.addFlag("compact", "Use a compact view (reduces the size of IDs)"); + list.addParameter("filter", { .name = "property=$value", .help = "Filter the results" }); + list.addParameter("id", { .name = "id", .help = "List only the content with the given ID" }); + list.addFlag("showall", "Show all properties"); + list.addParameter("show", { .name = "property", .help = "Only show the given property" }); + list.completer = &SinkshUtils::resourceOrTypeCompleter; return Syntax::List() << list; } diff --git a/sinksh/syntax_modules/sink_modify.cpp b/sinksh/syntax_modules/sink_modify.cpp index 2579550..6cda5ab 100644 --- a/sinksh/syntax_modules/sink_modify.cpp +++ b/sinksh/syntax_modules/sink_modify.cpp @@ -38,20 +38,12 @@ namespace SinkModify { +Syntax::List syntax(); + bool modify(const QStringList &args, State &state) { - if (args.isEmpty()) { - state.printError(QObject::tr("A type is required"), "sink_modify/02"); - return false; - } - - if (args.count() < 2) { - state.printError(QObject::tr("A resource ID is required to remove items"), "sink_modify/03"); - return false; - } - if (args.count() < 3) { - state.printError(QObject::tr("An object ID is required to remove items"), "sink_modify/03"); + state.printError(syntax()[0].usage()); return false; } @@ -81,6 +73,7 @@ bool modify(const QStringList &args, State &state) bool resource(const QStringList &args, State &state) { if (args.isEmpty()) { + // TODO: pass the syntax as parameter state.printError(QObject::tr("A resource can not be modified without an id"), "sink_modify/01"); } @@ -105,11 +98,21 @@ bool resource(const QStringList &args, State &state) return true; } - Syntax::List syntax() { Syntax modify("modify", QObject::tr("Modify items in a resource"), &SinkModify::modify); + modify.addPositionalArgument({ .name = "type", .help = "The type of entity to modify (mail, event, etc.)" }); + modify.addPositionalArgument({ .name = "resourceId", .help = "The ID of the resource containing the entity" }); + modify.addPositionalArgument({ .name = "objectId", .help = "The ID of the entity" }); + modify.addPositionalArgument( + { .name = "key value", .help = "Attributes and values to modify", .required = false, .variadic = true }); + Syntax resource("resource", QObject::tr("Modify a resource"), &SinkModify::resource);//, Syntax::EventDriven); + + resource.addPositionalArgument({ .name = "id", .help = "The ID of the resource" }); + resource.addPositionalArgument( + { .name = "key value", .help = "Attributes and values to modify", .required = false, .variadic = true }); + resource.completer = &SinkshUtils::resourceOrTypeCompleter; modify.children << resource; diff --git a/sinksh/syntax_modules/sink_remove.cpp b/sinksh/syntax_modules/sink_remove.cpp index 6baa60f..999a889 100644 --- a/sinksh/syntax_modules/sink_remove.cpp +++ b/sinksh/syntax_modules/sink_remove.cpp @@ -37,20 +37,12 @@ namespace SinkRemove { +Syntax::List syntax(); + bool remove(const QStringList &args, State &state) { - if (args.isEmpty()) { - state.printError(QObject::tr("A type is required"), "sink_remove/02"); - return false; - } - - if (args.count() < 2) { - state.printError(QObject::tr("A resource ID is required to remove items"), "sink_remove/03"); - return false; - } - if (args.count() < 3) { - state.printError(QObject::tr("An object ID is required to remove items"), "sink_remove/03"); + state.printError(syntax()[0].usage()); return false; } @@ -137,14 +129,24 @@ bool identity(const QStringList &args, State &state) return true; } - Syntax::List syntax() { Syntax remove("remove", QObject::tr("Remove items in a resource"), &SinkRemove::remove); + + remove.addPositionalArgument({ .name = "type", .help = "The type of entity to remove (mail, event, etc.)" }); + remove.addPositionalArgument({ .name = "resourceId", .help = "The ID of the resource containing the entity" }); + remove.addPositionalArgument({ .name = "objectId", .help = "The ID of the entity to remove" }); + Syntax resource("resource", QObject::tr("Removes a resource"), &SinkRemove::resource, Syntax::NotInteractive); + resource.addPositionalArgument({ .name = "id", .help = "The ID of the resource to remove" }); + resource.completer = &SinkshUtils::resourceCompleter; + Syntax account("account", QObject::tr("Removes a account"), &SinkRemove::account, Syntax::NotInteractive); + account.addPositionalArgument({ .name = "id", .help = "The ID of the account to remove" }); + Syntax identity("identity", QObject::tr("Removes an identity"), &SinkRemove::identity, Syntax::NotInteractive); - resource.completer = &SinkshUtils::resourceCompleter; + identity.addPositionalArgument({ .name = "id", .help = "The ID of the account to remove" }); + remove.children << resource << account << identity; return Syntax::List() << remove; diff --git a/sinksh/syntax_modules/sink_stat.cpp b/sinksh/syntax_modules/sink_stat.cpp index 7263941..a936af2 100644 --- a/sinksh/syntax_modules/sink_stat.cpp +++ b/sinksh/syntax_modules/sink_stat.cpp @@ -69,7 +69,6 @@ void statResource(const QString &resource, const State &state) state.printLine(QObject::tr("Actual database file sizes total: %1 [kb]").arg(size), 1); QDir dataDir{Sink::resourceStorageLocation(resource.toLatin1()) + "/fulltext/"}; - Q_ASSERT(dataDir.exists()); qint64 dataSize = 0; for (const auto &e : dataDir.entryInfoList(QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot)) { dataSize += e.size(); @@ -102,7 +101,15 @@ bool stat(const QStringList &args, State &state) Syntax::List syntax() { - Syntax state("stat", QObject::tr("Shows database usage for the resources requested"), &SinkStat::stat, Syntax::NotInteractive); + Syntax state("stat", QObject::tr("Shows database usage for the resources requested"), + &SinkStat::stat, Syntax::NotInteractive); + + state.addPositionalArgument({ .name = "resourceId", + .help = "Show statistics of the given resource(s). If no resource is provided, show " + "statistics of all resources", + .required = false, + .variadic = true }); + state.completer = &SinkshUtils::resourceCompleter; return Syntax::List() << state; diff --git a/sinksh/syntax_modules/sink_sync.cpp b/sinksh/syntax_modules/sink_sync.cpp index f165f58..e13e8f8 100644 --- a/sinksh/syntax_modules/sink_sync.cpp +++ b/sinksh/syntax_modules/sink_sync.cpp @@ -83,6 +83,11 @@ bool sync(const QStringList &args, State &state) Syntax::List syntax() { Syntax sync("sync", QObject::tr("Synchronizes a resource."), &SinkSync::sync, Syntax::EventDriven); + + sync.addPositionalArgument({ .name = "type", .help = "The type of resource to synchronize" }); + sync.addPositionalArgument({ .name = "resourceId", .help = "The ID of the resource to synchronize" }); + sync.addParameter("password", { .name = "password", .help = "The password of the resource", .required = true }); + sync.completer = &SinkshUtils::resourceCompleter; return Syntax::List() << sync; diff --git a/sinksh/syntax_modules/sink_trace.cpp b/sinksh/syntax_modules/sink_trace.cpp index ed5e2d8..2811258 100644 --- a/sinksh/syntax_modules/sink_trace.cpp +++ b/sinksh/syntax_modules/sink_trace.cpp @@ -66,18 +66,17 @@ bool trace(const QStringList &args, State &state) return traceOn(args, state); } - Syntax::List syntax() { Syntax trace("trace", QObject::tr("Control trace debug output."), &SinkTrace::trace, Syntax::NotInteractive); - trace.completer = &SinkshUtils::debugareaCompleter; + trace.completer = &SinkshUtils::debugareaCompleter; Syntax traceOff("off", QObject::tr("Turns off trace output."), &SinkTrace::traceOff, Syntax::NotInteractive); - traceOff.completer = &SinkshUtils::debugareaCompleter; + traceOff.completer = &SinkshUtils::debugareaCompleter; trace.children << traceOff; Syntax traceOn("on", QObject::tr("Turns on trace output."), &SinkTrace::traceOn, Syntax::NotInteractive); - traceOn.completer = &SinkshUtils::debugareaCompleter; + traceOn.completer = &SinkshUtils::debugareaCompleter; trace.children << traceOn; return Syntax::List() << trace; diff --git a/sinksh/syntaxtree.cpp b/sinksh/syntaxtree.cpp index 0eb9782..fea99ef 100644 --- a/sinksh/syntaxtree.cpp +++ b/sinksh/syntaxtree.cpp @@ -33,6 +33,104 @@ Syntax::Syntax(const QString &k, const QString &helpText, std::function").arg(arg.name); + argumentsUsage += QString(" <%1>: %2\n").arg(arg.name).arg(arg.help); + } else { + argumentsSummary += QString(" [%1]").arg(arg.name); + argumentsUsage += QString(" [%1]: %2\n").arg(arg.name).arg(arg.help); + } + if (arg.variadic) { + argumentsSummary += "..."; + } + } + } + + if (hasFlags) { + argumentsSummary += " [FLAGS]"; + } + + if (hasOptions) { + argumentsSummary += " [OPTIONS]"; + } + + if (hasSubcommand) { + if (hasArguments || hasFlags || hasOptions) { + argumentsSummary = QString(" [ |%1 ]").arg(argumentsSummary); + } else { + argumentsSummary = " "; + } + } + + argumentsSummary += '\n'; + + QString subcommandsUsage; + if (hasSubcommand) { + subcommandsUsage += "\nSUB-COMMANDS:\n" + " Use the 'help' command to find out more about a sub-command.\n\n"; + for (const auto &command : children) { + subcommandsUsage += QString(" %1: %2\n").arg(command.keyword).arg(command.help); + } + } + + QString flagsUsage; + if (hasFlags) { + flagsUsage += "\nFLAGS:\n"; + for (auto it = flags.constBegin(); it != flags.constEnd(); ++it) { + flagsUsage += QString(" [--%1]: %2\n").arg(it.key()).arg(it.value()); + } + } + + QString optionsUsage; + if (hasOptions) { + optionsUsage += "\nOPTIONS:\n"; + for (auto it = parameters.constBegin(); it != parameters.constEnd(); ++it) { + optionsUsage += " "; + if (!it.value().required) { + optionsUsage += QString("[--%1 $%2]").arg(it.key()).arg(it.value().name); + } else { + optionsUsage += QString("<--%1 $%2>").arg(it.key()).arg(it.value().name); + } + + optionsUsage += ": " + it.value().help + '\n'; + } + } + + // TODO: instead of just the keyword, we might want to have the whole + // command (e.g. if this is a sub-command) + return QString("USAGE:\n ") + keyword + argumentsSummary + subcommandsUsage + + argumentsUsage + flagsUsage + optionsUsage; +} + SyntaxTree::SyntaxTree() { } diff --git a/sinksh/syntaxtree.h b/sinksh/syntaxtree.h index ce28548..3d90288 100644 --- a/sinksh/syntaxtree.h +++ b/sinksh/syntaxtree.h @@ -42,10 +42,33 @@ public: Syntax(const QString &keyword, const QString &helpText = QString(), std::function lambda = std::function(), Interactivity interactivity = NotInteractive); + struct Argument { + QString name; + QString help; + bool required = true; + bool variadic = false; + }; + + struct ParameterOptions { + QString name; + QString help; + bool required = false; + }; + + // TODO: add examples? QString keyword; QString help; + QVector arguments; + QMap parameters; + QMap flags; Interactivity interactivity; + void addPositionalArgument(const Argument &); + void addParameter(const QString &name, const ParameterOptions &options); + void addFlag(const QString &name, const QString &help); + + QString usage() const; + /** * This function will be called to execute the command. * -- cgit v1.2.3