Skip to content

Commit

Permalink
add verbose attribute param to CLI for usage message format setting. …
Browse files Browse the repository at this point in the history
…now can show short usage message
  • Loading branch information
sekiguchi-nagisa committed Sep 24, 2023
1 parent d628373 commit e75507e
Show file tree
Hide file tree
Showing 15 changed files with 265 additions and 65 deletions.
8 changes: 4 additions & 4 deletions doc/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
### ``CLI`` attribute
only allowed for user-defied type definition

| **param** | **type** | **default** | **description** |
|-------------|------------|--------------|------------------------------|
| name | ``String`` | empty string | default command name |
| short_usage | ``Bool`` | false | default usage message format |
| **param** | **type** | **default** | **description** |
|-----------|------------|--------------|------------------------------|
| name | ``String`` | empty string | default command name |
| verbose | ``Bool`` | true | default usage message format |

### ``Flag`` attribute
only allowed for field declaration that is ``Bool`` or ``Bool?`` type
Expand Down
2 changes: 1 addition & 1 deletion doc/std.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ function parse(args : [String]) : Int for CLI
function parseOrExit(args : [String]) : Int for CLI
function usage(message : String?) : String for CLI
function usage(message : String?, verbose : Bool?) : String for CLI
```

## Array type
Expand Down
14 changes: 10 additions & 4 deletions src/arg_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ void RequiredOptionSet::del(unsigned char n) {
}
}

static bool verboseUsage(const DSState &st, const BaseObject &out) {
auto &type = st.typePool.get(out.getTypeID());
return hasFlag(cast<CLIRecordType>(type).getAttr(), CLIRecordType::Attr::VERBOSE);
}

static bool checkAndSetArg(DSState &state, const ArgParser &parser, const ArgEntry &entry,
StringRef arg, bool shortOpt, BaseObject &out) {
std::string err;
Expand All @@ -58,7 +63,7 @@ static bool checkAndSetArg(DSState &state, const ArgParser &parser, const ArgEnt
}
return true;
} else {
err = parser.formatUsage(err, true);
err = parser.formatUsage(err, verboseUsage(state, out));
raiseError(state, TYPE::CLIError, std::move(err), 1);
return false;
}
Expand All @@ -67,6 +72,7 @@ static bool checkAndSetArg(DSState &state, const ArgParser &parser, const ArgEnt
static bool checkRequireOrPositionalArgs(DSState &state, const ArgParser &parser,
const RequiredOptionSet &requiredSet, StrArrayIter &begin,
const StrArrayIter end, BaseObject &out) {
const bool verbose = verboseUsage(state, out);
for (auto &i : requiredSet.getValues()) {
auto &e = parser.getEntries()[i];
if (!e.isPositional()) {
Expand All @@ -84,7 +90,7 @@ static bool checkRequireOrPositionalArgs(DSState &state, const ArgParser &parser
err += l;
}
err += " option";
err = parser.formatUsage(err, true);
err = parser.formatUsage(err, verbose);
raiseError(state, TYPE::CLIError, std::move(err), 1);
return false;
}
Expand Down Expand Up @@ -117,7 +123,7 @@ static bool checkRequireOrPositionalArgs(DSState &state, const ArgParser &parser
std::string err = "require `";
err += e.getArgName();
err += "' argument";
err = parser.formatUsage(err, true);
err = parser.formatUsage(err, verbose);
raiseError(state, TYPE::CLIError, std::move(err), 1);
return false;
}
Expand Down Expand Up @@ -172,7 +178,7 @@ CLIParseResult parseCommandLine(DSState &state, const ArrayObject &args, BaseObj
}
if (ret.isError()) {
auto v = ret.formatError();
v = instance.formatUsage(v, true);
v = instance.formatUsage(v, verboseUsage(state, out));
raiseError(state, TYPE::CLIError, std::move(v), 2);
goto END;
}
Expand Down
67 changes: 35 additions & 32 deletions src/arg_parser_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,46 +136,49 @@ std::string ArgParser::formatUsage(StringRef message, bool verbose) const {
out += '\n';
}

unsigned int optCount = 0;
unsigned int argCount = 0;
for (auto &e : this->entries) {
if (e.isHelp()) {
continue;
} else if (e.isOption()) {
optCount++;
} else {
argCount++;
if (verbose) {
unsigned int optCount = 0;
unsigned int argCount = 0;
for (auto &e : this->entries) {
if (e.isHelp()) {
continue;
} else if (e.isOption()) {
optCount++;
} else {
argCount++;
}
}
}

out += "Usage: ";
out += this->cmdName;
if (optCount) {
out += " [OPTIONS]";
}
out += "Usage: ";
out += this->cmdName;
if (optCount) {
out += " [OPTIONS]";
}

if (argCount) {
for (auto &e : this->entries) {
if (e.isPositional()) {
out += ' ';
if (!e.isRequire()) {
out += '[';
}
assert(!e.getArgName().empty());
out += e.getArgName();
if (e.isRemainArg()) {
out += "...";
}
if (!e.isRequire()) {
out += ']';
if (argCount) {
for (auto &e : this->entries) {
if (e.isPositional()) {
out += ' ';
if (!e.isRequire()) {
out += '[';
}
assert(!e.getArgName().empty());
out += e.getArgName();
if (e.isRemainArg()) {
out += "...";
}
if (!e.isRequire()) {
out += ']';
}
}
}
}
}

if (verbose) {
out += "\n\n";
this->formatOptions(out);
} else {
out += "See `";
out += this->cmdName;
out += " --help' for more information.";
}
return out;
}
Expand Down
1 change: 1 addition & 0 deletions src/attribute.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#define EACH_ATTRIBUTE_PARAM(OP) \
OP(NAME, "name", TYPE::String) \
OP(VERBOSE, "verbose", TYPE::Bool) \
OP(HELP, "help", TYPE::String) \
OP(SHORT, "short", TYPE::String) \
OP(LONG, "long", TYPE::String) \
Expand Down
6 changes: 4 additions & 2 deletions src/builtin.h
Original file line number Diff line number Diff line change
Expand Up @@ -2338,13 +2338,15 @@ YDSH_METHOD cli_parseOrExit(RuntimeContext &ctx) {
}
}

//!bind: function usage($this : CLI, $message : Option<String>) : String
//!bind: function usage($this : CLI, $message : Option<String>, $verbose : Option<Bool>) : String
YDSH_METHOD cli_usage(RuntimeContext &ctx) {
SUPPRESS_WARNING(cli_usage);
auto &obj = typeAs<BaseObject>(LOCAL(0));
StringRef message = LOCAL(1).isInvalid() ? "" : LOCAL(1).asStrRef();
bool verbose = LOCAL(2).isInvalid() ? true : LOCAL(2).asBool();
auto &type = cast<CLIRecordType>(ctx.typePool.get(obj.getTypeID()));
auto value = ArgParser::create(obj[0].asStrRef(), type.getEntries()).formatUsage(message, true);
auto value =
ArgParser::create(obj[0].asStrRef(), type.getEntries()).formatUsage(message, verbose);
RET(DSValue::createStr(std::move(value)));
}

Expand Down
46 changes: 34 additions & 12 deletions src/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,21 @@ void VarDeclNode::dump(NodeDumper &dumper) const {
// ## AttributeNode ##
// ###########################

int AttributeNode::findValidAttrParamIndex(Attribute::Param p) const {
if (this->getAttrKind() == AttributeKind::NONE || !this->isValidType() ||
!this->getResolvedParamSet().has(p)) {
return -1;
}
StringRef paramName = toString(p);
const unsigned int size = this->getKeys().size();
for (unsigned int i = 0; i < size; i++) {
if (this->getKeys()[i].getName() == paramName) {
return static_cast<int>(i);
}
}
return -1;
}

void AttributeNode::dump(NodeDumper &dumper) const {
#define EACH_ENUM(OP) \
OP(Attribute::Loc::NONE) \
Expand All @@ -859,6 +874,20 @@ void AttributeNode::dump(NodeDumper &dumper) const {
DUMP(constNodes);
}

std::pair<int, int>
lookupValidAttributeParamFromList(const std::vector<std::unique_ptr<AttributeNode>> &attrNodes,
AttributeKind kind, Attribute::Param p) {
unsigned int size = attrNodes.size();
for (unsigned int i = 0; i < size; i++) {
auto &attrNode = attrNodes[i];
if (attrNode->getAttrKind() == kind) {
int index = attrNode->findValidAttrParamIndex(p);
return {static_cast<int>(i), index};
}
}
return {-1, -1};
}

// ########################
// ## AssignNode ##
// ########################
Expand Down Expand Up @@ -945,18 +974,11 @@ void FunctionNode::setFuncBody(std::unique_ptr<Node> &&node) {

StringRef FunctionNode::getCLIName() const {
StringRef name;
for (auto &e : this->attrNodes) {
if (e->getAttrKind() != AttributeKind::CLI || !e->isValidType()) {
continue;
}
const unsigned int size = e->getKeys().size();
for (unsigned int i = 0; i < size; i++) {
if (e->getKeys()[i].getName() == "name") {
name = cast<StringNode>(*e->getConstNodes()[i]).getValue();
break;
}
}
break;
auto pair = lookupValidAttributeParamFromList(this->attrNodes, AttributeKind::CLI,
Attribute::Param::NAME);
if (pair.first > -1 && pair.second > -1) {
auto &node = this->attrNodes[pair.first]->getConstNodes()[pair.second];
name = cast<StringNode>(*node).getValue();
}
return name;
}
Expand Down
20 changes: 20 additions & 0 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -2198,9 +2198,29 @@ class AttributeNode : public WithRtti<Node, NodeKind::Attribute> {

AttributeKind getAttrKind() const { return this->attrKind; }

/**
*
* @param p
* @return
* if not found or invalid attribute, return -1
*/
int findValidAttrParamIndex(Attribute::Param p) const;

void dump(NodeDumper &dumper) const override;
};

/**
* get first occurred attribute parameter
* @param attrNodes
* @param kind
* @param p
* @return
* if not found , (-1,-1)
*/
std::pair<int, int>
lookupValidAttributeParamFromList(const std::vector<std::unique_ptr<AttributeNode>> &attrNodes,
AttributeKind kind, Attribute::Param p);

/**
* for assignment, self assignment or named parameter
* assignment is statement.
Expand Down
2 changes: 1 addition & 1 deletion src/type.h
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ class ArgEntry;
class CLIRecordType : public RecordType {
public:
enum class Attr : unsigned char {
SHORT_USAGE = 1u << 0u,
VERBOSE = 1u << 0u, // verbose usage message
};

private:
Expand Down
14 changes: 11 additions & 3 deletions src/type_checker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2005,14 +2005,23 @@ void TypeChecker::registerRecordType(FunctionNode &node) {

// check CLI attribute
bool cli = false;
auto attr = CLIRecordType::Attr::VERBOSE;
if (!node.getAttrNodes().empty()) {
for (auto &e : node.getAttrNodes()) {
if (e->getAttrKind() == AttributeKind::CLI) {
cli = true;
auto index = e->findValidAttrParamIndex(Attribute::Param::VERBOSE);
if (index != -1) {
if (cast<NumberNode>(*e->getConstNodes()[index]).getIntValue()) {
setFlag(attr, CLIRecordType::Attr::VERBOSE);
} else {
unsetFlag(attr, CLIRecordType::Attr::VERBOSE);
}
}
} else {
cli = false;
break;
}
break;
}
}
if (cli && !node.getParamNodes().empty()) {
Expand All @@ -2021,8 +2030,7 @@ void TypeChecker::registerRecordType(FunctionNode &node) {
}

auto typeOrError =
cli ? this->typePool().createCLIRecordType(node.getFuncName(), this->curScope->modId,
CLIRecordType::Attr{})
cli ? this->typePool().createCLIRecordType(node.getFuncName(), this->curScope->modId, attr)
: this->typePool().createRecordType(node.getFuncName(), this->curScope->modId);
if (typeOrError) {
auto &recordType = *typeOrError.asOk();
Expand Down
2 changes: 2 additions & 0 deletions src/type_checker_attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ AttributeMap AttributeMap::create(const TypePool &pool) {
defineAttribute(values, AttributeKind::CLI, Attribute::Loc::CONSTRUCTOR,
{
Attribute::Param::NAME,
Attribute::Param::VERBOSE,
},
{});
defineAttribute(values, AttributeKind::FLAG, Attribute::Loc::FIELD,
Expand Down Expand Up @@ -357,6 +358,7 @@ void TypeChecker::resolveArgEntry(std::unordered_set<std::string> &foundOptionSe
auto &constNode = *attrNode.getConstNodes()[i];
switch (*param) {
case Attribute::Param::NAME:
case Attribute::Param::VERBOSE:
continue; // unreachable
case Attribute::Param::HELP: {
StringRef ref = cast<StringNode>(constNode).getValue();
Expand Down
4 changes: 2 additions & 2 deletions test/analyzer/archive_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@ TEST_F(ArchiveTest, argEntry) {
});

auto &recordType = createRecordType(ctx.getPool(), "type1", std::move(builder), ctx.getModId(),
CLIRecordType::Attr::SHORT_USAGE);
CLIRecordType::Attr::VERBOSE);
ASSERT_EQ(6, recordType.getEntries().size());
auto ret = ctx.getScope()->defineTypeAlias(ctx.getPool(), "type1", recordType);
ASSERT_TRUE(ret);
Expand All @@ -840,7 +840,7 @@ TEST_F(ArchiveTest, argEntry) {
auto &recordType = cast<CLIRecordType>(this->pool().get(ret.asOk()->getTypeId()));
auto &entries = recordType.getEntries();
ASSERT_EQ(6, entries.size());
ASSERT_EQ(CLIRecordType::Attr::SHORT_USAGE, recordType.getAttr());
ASSERT_EQ(CLIRecordType::Attr::VERBOSE, recordType.getAttr());

// -e --enable
ASSERT_EQ(1, entries[0].getFieldOffset());
Expand Down
Loading

0 comments on commit e75507e

Please sign in to comment.