mirror of
https://github.com/silverqx/TinyORM.git
synced 2026-05-05 16:10:02 -05:00
tom added completion for about --only= sections
Added support for pwsh, zsh, and bash shells. Contains workaround for Register-ArgumentCompleter for array option values, currently, the wordArg is empty, the workaround fix fills the wordArg with the last option value.
This commit is contained in:
@@ -969,6 +969,10 @@ tom complete --word="mi" --commandline="tom mi" --position=6
|
||||
tom complete --word="--" --commandline="tom migrate --" --position=14
|
||||
tom complete --word="--p" --commandline="tom migrate --p" --position=15
|
||||
|
||||
tom complete --word="--only=env" --commandline="tom about --only=env" --position=20
|
||||
tom complete --word="" --commandline="tom about --only=environment," --position=29
|
||||
tom complete --word="m" --commandline="tom about --only=environment,m" --position=30
|
||||
|
||||
|
||||
cmake build commands:
|
||||
---------------------
|
||||
|
||||
@@ -65,6 +65,10 @@ namespace Orm::Utils
|
||||
/*! Split a string by the given width (not in the middle of a word). */
|
||||
static std::vector<QString>
|
||||
splitStringByWidth(const QString &string, int width);
|
||||
/*! Split a string at the first given character. */
|
||||
static QList<QStringView>
|
||||
splitAtFirst(QStringView string, QChar separator,
|
||||
Qt::SplitBehavior behavior = Qt::KeepEmptyParts);
|
||||
|
||||
/*! Count number of the given character before the given position. */
|
||||
static QString::size_type countBefore(QString string, QChar character,
|
||||
|
||||
@@ -405,6 +405,39 @@ std::vector<QString> String::splitStringByWidth(const QString &string, const int
|
||||
return lines;
|
||||
}
|
||||
|
||||
QList<QStringView> String::splitAtFirst(const QStringView string, const QChar separator,
|
||||
const Qt::SplitBehavior behavior)
|
||||
{
|
||||
// Nothing to do
|
||||
if (string.isEmpty())
|
||||
return {};
|
||||
|
||||
const auto index = string.indexOf(separator);
|
||||
|
||||
// Nothing to do, separator was not found
|
||||
if (index == -1)
|
||||
return {string};
|
||||
|
||||
const auto *const begin = string.constBegin();
|
||||
const auto *const end = string.constEnd();
|
||||
const auto *const itSeparator = string.constBegin() + index;
|
||||
const auto *const itAfterSeparator = string.constBegin() + index + 1; // +1 to skip the separator
|
||||
|
||||
// Currently, a value before the separator must contain at least one character
|
||||
Q_ASSERT(begin < itSeparator);
|
||||
// Standard development check, therefore is separated from the above
|
||||
Q_ASSERT(itAfterSeparator <= end);
|
||||
|
||||
if (behavior == Qt::SkipEmptyParts && itAfterSeparator == end)
|
||||
return {{begin, itSeparator}};
|
||||
|
||||
/* This is correct in all cases, overflow can't happen if there is nothing after
|
||||
the separator, eg. key=, in this case the beginIndex will point to the constEnd(),
|
||||
so the result will be like {string.constEnd(), string.constEnd()} what is an empty
|
||||
string view. */
|
||||
return {{begin, itSeparator}, {itAfterSeparator, end}};
|
||||
}
|
||||
|
||||
QString::size_type String::countBefore(QString string, const QChar character,
|
||||
const QString::size_type position)
|
||||
{
|
||||
|
||||
@@ -47,6 +47,10 @@ namespace Tom::Commands
|
||||
static std::optional<QString>
|
||||
getCurrentTomCommand(const QString &commandlineArg, QString::size_type cword);
|
||||
#endif
|
||||
/*! Get the command-line option value for --word= option (workaround for pwsh). */
|
||||
QString getWordOptionValue(
|
||||
const QStringList ¤tCommandSplitted,
|
||||
QString::size_type positionArg, QString::size_type commandlineArgSize);
|
||||
|
||||
/*! Print all guessed commands. */
|
||||
int printGuessedCommands(
|
||||
@@ -59,6 +63,8 @@ namespace Tom::Commands
|
||||
int printAndGuessConnectionNames(const QString &connectionName) const;
|
||||
/*! Print all or guessed environment names for the --env= option. */
|
||||
int printEnvironmentNames(const QString &environmentName) const;
|
||||
/*! Print all section names for the about command --only= option. */
|
||||
int printSectionNamesForAbout(QStringView sectionNamesValue) const;
|
||||
/*! Print all or guessed long option parameter names. */
|
||||
int printGuessedLongOptions(const std::optional<QString> ¤tCommand,
|
||||
const QString &word) const;
|
||||
@@ -89,6 +95,8 @@ namespace Tom::Commands
|
||||
inline static bool isLongOption(const QString &wordArg);
|
||||
/*! Determine whether the given word is a short option argument. */
|
||||
inline static bool isShortOption(const QString &wordArg);
|
||||
/*! Determine if the given word is a long option argument with an array value. */
|
||||
inline static bool isLongOptionWithArrayValue(const QString &wordArg);
|
||||
/*! Get the command-line option value (eg. --database=value). */
|
||||
static QString getOptionValue(const QString &wordArg);
|
||||
|
||||
|
||||
@@ -77,6 +77,10 @@ __tom_environments() {
|
||||
echo 'dev development local prod production test testing staging'
|
||||
}
|
||||
|
||||
__tom_about_sections() {
|
||||
echo 'environment macros versions connections'
|
||||
}
|
||||
|
||||
_tom()
|
||||
{
|
||||
local cur prev words cword split
|
||||
@@ -123,6 +127,12 @@ _tom()
|
||||
return
|
||||
fi
|
||||
|
||||
# Complete section names for about command --only= option
|
||||
if [[ -v tom_command ]] && [[ $tom_command == 'about' ]] && [[ $prev == '--only' ]]; then
|
||||
COMPREPLY=($(compgen -W "$(__tom_about_sections)" -- "$cur"))
|
||||
return
|
||||
fi
|
||||
|
||||
# Accurate completion using the tom complete command
|
||||
if _have tom; then
|
||||
# Completion for positional arguments and for long and short options
|
||||
@@ -234,6 +244,10 @@ __tom_namespaces() {
|
||||
_values namespace 'global' 'db' 'make' 'migrate' 'namespaced' 'all'
|
||||
}
|
||||
|
||||
__tom_about_sections() {
|
||||
_values -s , section 'connections' 'environment' 'macros' 'versions'
|
||||
}
|
||||
|
||||
# Try to infer database connection names if a user is in the right folder and have tagged
|
||||
# connection names with '// shell:connection' comment
|
||||
__tom_connections() {
|
||||
@@ -332,7 +346,7 @@ _tom() {
|
||||
$common_options \
|
||||
'--json[Output the information as JSON]' \
|
||||
'--pretty[Enable JSON human readable output]' \
|
||||
'--only=[Sections to display (partial match)]:section names'
|
||||
'--only=[Sections to display (partial match)]:section names:__tom_about_sections'
|
||||
;;
|
||||
|
||||
(env|inspire)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <range/v3/view/transform.hpp>
|
||||
|
||||
#include <orm/constants.hpp>
|
||||
#include <orm/macros/likely.hpp>
|
||||
#include <orm/utils/string.hpp>
|
||||
|
||||
#include "tom/application.hpp"
|
||||
@@ -24,7 +25,9 @@ TINYORM_BEGIN_COMMON_NAMESPACE
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using Orm::Constants::COMMA_C;
|
||||
using Orm::Constants::DASH;
|
||||
using Orm::Constants::EMPTY;
|
||||
using Orm::Constants::EQ_C;
|
||||
using Orm::Constants::NEWLINE;
|
||||
using Orm::Constants::NOSPACE;
|
||||
@@ -33,6 +36,7 @@ using Orm::Constants::database_;
|
||||
|
||||
using StringUtils = Orm::Utils::String;
|
||||
|
||||
using Tom::Constants::About;
|
||||
using Tom::Constants::DoubleDash;
|
||||
using Tom::Constants::Env;
|
||||
using Tom::Constants::Help;
|
||||
@@ -42,6 +46,7 @@ using Tom::Constants::LongOption;
|
||||
using Tom::Constants::ShPwsh;
|
||||
using Tom::Constants::commandline;
|
||||
using Tom::Constants::commandline_up;
|
||||
using Tom::Constants::only_;
|
||||
using Tom::Constants::word_;
|
||||
using Tom::Constants::word_up;
|
||||
|
||||
@@ -90,7 +95,6 @@ int CompleteCommand::run() // NOLINT(readability-function-cognitive-complexity)
|
||||
Command::run();
|
||||
|
||||
/* Initialization section */
|
||||
const auto wordArg = value(word_);
|
||||
const auto commandlineArg = value(commandline);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
@@ -100,15 +104,20 @@ int CompleteCommand::run() // NOLINT(readability-function-cognitive-complexity)
|
||||
const auto positionArg = value(position_).toInt();
|
||||
# endif
|
||||
|
||||
const auto commandlineArgSize = commandlineArg.size();
|
||||
|
||||
// Currently processed tom command
|
||||
const auto currentCommandSplitted = commandlineArg.split(SPACE);
|
||||
Q_ASSERT(!currentCommandSplitted.isEmpty());
|
||||
|
||||
const auto currentCommandArg = getCurrentTomCommand(currentCommandSplitted);
|
||||
const auto tomCommandSize = currentCommandSplitted.constFirst().size();
|
||||
const auto currentCommandArg = getCurrentTomCommand(currentCommandSplitted);
|
||||
const auto tomCommandSize = currentCommandSplitted.constFirst().size();
|
||||
|
||||
// Register-ArgumentCompleter --word with workaround for arrays
|
||||
const auto commandlineArgSize = commandlineArg.size();
|
||||
const auto wordArg = getWordOptionValue(currentCommandSplitted, positionArg,
|
||||
commandlineArgSize);
|
||||
#else
|
||||
const auto wordArg = value(word_);
|
||||
|
||||
# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
const auto cwordArg = value(cword_).toLongLong();
|
||||
# else
|
||||
@@ -178,6 +187,16 @@ int CompleteCommand::run() // NOLINT(readability-function-cognitive-complexity)
|
||||
#endif
|
||||
return printGuessedShells(wordArg);
|
||||
|
||||
/* Print all or guessed section names for the about command --only= option
|
||||
--- */
|
||||
// Bash has it's own guess logic in the tom.bash complete file
|
||||
#ifdef _MSC_VER
|
||||
if (currentCommandArg == About && wordArg.startsWith(LongOption.arg(only_)) &&
|
||||
positionArg >= commandlineArgSize
|
||||
)
|
||||
return printSectionNamesForAbout(getOptionValue(wordArg));
|
||||
#endif
|
||||
|
||||
/* Print inferred database connection names for the --database= option
|
||||
--- */
|
||||
// Bash has it's own guess logic in the tom.bash complete file
|
||||
@@ -259,6 +278,49 @@ CompleteCommand::getCurrentTomCommand(const QString &commandlineArg,
|
||||
}
|
||||
#endif
|
||||
|
||||
QString CompleteCommand::getWordOptionValue(
|
||||
const QStringList ¤tCommandSplitted,
|
||||
const QString::size_type positionArg, const QString::size_type commandlineArgSize)
|
||||
{
|
||||
/* This method contains a special handling (alternative to getOptionValue() method)
|
||||
with workaround for the --word= pwsh option from the Register-ArgumentCompleter.
|
||||
It fixes cases when the option value on the command-line to complete contains
|
||||
array value like --only=version,| in this case, pwsh provides/fills
|
||||
the $wordToComplete in the Register-ArgumentCompleter with an empty string so
|
||||
there is no way how to correctly complete these values.
|
||||
This method workarounds/fixes this behavior and instead of an empty string provides
|
||||
the correct value you would expect like --only=version,| or --only=version,ma|
|
||||
which enables to complete the given partial array values. */
|
||||
|
||||
auto wordArg = value(word_);
|
||||
|
||||
/* Nothing to do, cursor is already after an option, eg: --only=env | or --only=env, |
|
||||
or somewhere before. */
|
||||
if (positionArg != commandlineArgSize)
|
||||
return wordArg;
|
||||
|
||||
const auto &lastArg = currentCommandSplitted.constLast();
|
||||
|
||||
/* This condition can't be true with the current Register-ArgumentCompleter
|
||||
implementation, it ensures that our completion will work correctly if this will be
|
||||
by any chance fixed in future pwsh versions. 🙃 */
|
||||
if (wordArg == lastArg)
|
||||
return wordArg;
|
||||
|
||||
const auto isLongOption = CompleteCommand::isLongOption(lastArg);
|
||||
const auto isWordArgEmpty = wordArg.isEmpty();
|
||||
|
||||
// Targets --only=macros,| and returns --only=macros,
|
||||
if ((isLongOption && isWordArgEmpty && lastArg.endsWith(COMMA_C)) ||
|
||||
// Targets --only=macros,versions,en| and returns --only=macros,versions,en
|
||||
isLongOptionWithArrayValue(lastArg)
|
||||
) T_UNLIKELY
|
||||
return lastArg;
|
||||
|
||||
else T_LIKELY
|
||||
return wordArg;
|
||||
}
|
||||
|
||||
int CompleteCommand::printGuessedCommands(
|
||||
const std::vector<std::shared_ptr<Command>> &commands) const
|
||||
{
|
||||
@@ -400,6 +462,96 @@ int CompleteCommand::printEnvironmentNames(const QString &environmentName) const
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/*! Return type for the initializePrintSectionNamesForAbout() function. */
|
||||
struct PrintSectionNamesForAboutType
|
||||
{
|
||||
/*! Section name to complete/find (passed on command-line). */
|
||||
QString sectionArg;
|
||||
/*! All section names for completion (excluding already printed section names). */
|
||||
QList<QStringView> allSectionNamesFiltered;
|
||||
/*! Determine whether completing the first section name (need by pwsh). */
|
||||
bool isFirstValue;
|
||||
/*! Print all section names (if the section name input is empty). */
|
||||
bool printAllSectionNames;
|
||||
};
|
||||
|
||||
/*! Initialize local variables for the printSectionNamesForAbout() method. */
|
||||
PrintSectionNamesForAboutType
|
||||
initializePrintSectionNamesForAbout(const QStringView sectionNamesValue,
|
||||
const QStringList &allSectionNames)
|
||||
{
|
||||
// Nothing to do, the wordArg is empty, return right away as we know the resut
|
||||
if (sectionNamesValue.isEmpty())
|
||||
return {EMPTY, ranges::to<QList<QStringView>>(allSectionNames), true, true};
|
||||
|
||||
// Current wordArg, section names already displayed on the command-line
|
||||
auto sectionNamesSplitted = sectionNamesValue.split(COMMA_C, Qt::KeepEmptyParts);
|
||||
// Needed for pwsh, determines an output format
|
||||
const auto isFirstValue = sectionNamesSplitted.size() == 1;
|
||||
/* Currently completed section name, we need to take it out so that this section
|
||||
name is not filtered out in the ranges::views::filter() algorithm below. */
|
||||
const auto sectionArg = sectionNamesSplitted.takeLast();
|
||||
const auto printAllSectionNames = sectionArg.isEmpty();
|
||||
|
||||
// Remove all empty and null strings (it would print all section names w/o this)
|
||||
sectionNamesSplitted.removeAll({});
|
||||
|
||||
// Filter out section names that are already displayed on the command-line
|
||||
auto allSectionNamesFiltered =
|
||||
allSectionNames | ranges::views::filter([§ionNamesSplitted]
|
||||
(const QString &allSectionName)
|
||||
{
|
||||
// Include all of section names that aren't already on the command-line
|
||||
return std::ranges::all_of(sectionNamesSplitted,
|
||||
[&allSectionName](const QStringView sectionName)
|
||||
{
|
||||
return !allSectionName.startsWith(sectionName);
|
||||
});
|
||||
})
|
||||
| ranges::to<QList<QStringView>>();
|
||||
|
||||
return {sectionArg.toString(), std::move(allSectionNamesFiltered), isFirstValue,
|
||||
printAllSectionNames};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int CompleteCommand::printSectionNamesForAbout(const QStringView sectionNamesValue) const
|
||||
{
|
||||
static const QStringList allSectionNames {
|
||||
sl("environment"), sl("macros"), sl("versions"), sl("connections"),
|
||||
};
|
||||
|
||||
// Initialize local variables
|
||||
auto [sectionArg, allSectionNamesFiltered, isFirstValue, printAllSectionNames] =
|
||||
initializePrintSectionNamesForAbout(sectionNamesValue, allSectionNames);
|
||||
|
||||
QStringList sectionNames;
|
||||
sectionNames.reserve(allSectionNamesFiltered.size());
|
||||
|
||||
for (const auto section : allSectionNamesFiltered)
|
||||
/* It also evaluates to true if the given environmentName is an empty string "",
|
||||
so it prints all environment names in this case.
|
||||
Also --env= has to be prepended because pwsh overwrites whole option. */
|
||||
if (printAllSectionNames || section.startsWith(sectionArg))
|
||||
sectionNames << sl("%1;%2").arg(
|
||||
/* This is weird, but for the first section name we must
|
||||
print also --only= and for the next section we don't,
|
||||
reason is that for subsequent sections the wordArg is
|
||||
empty so pwsh doesn't rewrite the whole --only= option
|
||||
text so we must print the section name only. */
|
||||
isFirstValue
|
||||
? NOSPACE.arg(LongOption.arg(only_).append(EQ_C), section)
|
||||
: section,
|
||||
section);
|
||||
|
||||
// Print
|
||||
note(sectionNames.join(NEWLINE));
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int CompleteCommand::printGuessedLongOptions(
|
||||
const std::optional<QString> ¤tCommand, const QString &word) const
|
||||
{
|
||||
@@ -565,6 +717,19 @@ bool CompleteCommand::isOptionArgument(const QString &wordArg, const OptionType
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
bool CompleteCommand::isLongOptionWithArrayValue(const QString &wordArg)
|
||||
{
|
||||
// Nothing to check, not a long option
|
||||
if (!isLongOption(wordArg))
|
||||
return false;
|
||||
|
||||
const auto wordArgSplitted = StringUtils::splitAtFirst(wordArg, EQ_C,
|
||||
Qt::KeepEmptyParts);
|
||||
|
||||
// Checks --only=macros, or --only=macros,en
|
||||
return wordArgSplitted.size() == 2 && wordArgSplitted.constLast().contains(COMMA_C);
|
||||
}
|
||||
|
||||
QString CompleteCommand::getOptionValue(const QString &wordArg)
|
||||
{
|
||||
Q_ASSERT(wordArg.contains(EQ_C));
|
||||
|
||||
@@ -36,6 +36,10 @@ __tom_environments() {
|
||||
echo 'dev development local prod production test testing staging'
|
||||
}
|
||||
|
||||
__tom_about_sections() {
|
||||
echo 'environment macros versions connections'
|
||||
}
|
||||
|
||||
_tom()
|
||||
{
|
||||
local cur prev words cword split
|
||||
@@ -82,6 +86,12 @@ _tom()
|
||||
return
|
||||
fi
|
||||
|
||||
# Complete section names for about command --only= option
|
||||
if [[ -v tom_command ]] && [[ $tom_command == 'about' ]] && [[ $prev == '--only' ]]; then
|
||||
COMPREPLY=($(compgen -W "$(__tom_about_sections)" -- "$cur"))
|
||||
return
|
||||
fi
|
||||
|
||||
# Accurate completion using the tom complete command
|
||||
if _have tom; then
|
||||
# Completion for positional arguments and for long and short options
|
||||
|
||||
@@ -36,6 +36,10 @@ __tom_namespaces() {
|
||||
_values namespace 'global' 'db' 'make' 'migrate' 'namespaced' 'all'
|
||||
}
|
||||
|
||||
__tom_about_sections() {
|
||||
_values -s , section 'connections' 'environment' 'macros' 'versions'
|
||||
}
|
||||
|
||||
# Try to infer database connection names if a user is in the right folder and have tagged
|
||||
# connection names with '// shell:connection' comment
|
||||
__tom_connections() {
|
||||
@@ -134,7 +138,7 @@ _tom() {
|
||||
$common_options \
|
||||
'--json[Output the information as JSON]' \
|
||||
'--pretty[Enable JSON human readable output]' \
|
||||
'--only=[Sections to display (partial match)]:section names'
|
||||
'--only=[Sections to display (partial match)]:section names:__tom_about_sections'
|
||||
;;
|
||||
|
||||
(env|inspire)
|
||||
|
||||
Reference in New Issue
Block a user