Skip to content

Commit

Permalink
Merge branch 'any-key'
Browse files Browse the repository at this point in the history
* Add _any pseudo key for conditions
* Document use of $PAGER in the manual page
  • Loading branch information
xaizek committed Nov 1, 2018
2 parents e297f71 + 5e9f3a3 commit 4a9c617
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 31 deletions.
3 changes: 3 additions & 0 deletions docs/04-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ key op value

Extra spaces are allowed, but don't forget to escape them (with \\ or quotes).

Key in a condition can be a pseudo value "\_any" which matches with any existing
field of an item.

Command composition
-------------------

Expand Down
3 changes: 3 additions & 0 deletions docs/10-environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ configuration/data.
**HOME** -- checked second to determine location of configuration/data.

**EDITOR** -- used to perform external editing of values.

**PAGER** -- used as a default for displaying output which doesn't fit on the
screen.
60 changes: 33 additions & 27 deletions src/ItemFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,43 @@ ItemFilter::~ItemFilter()
bool
ItemFilter::passes(Item &item) const
{
return passes([&item](const std::string &f) { return item.getValue(f); });
return passes([&item](const std::string &key) {
std::vector<std::string> values;
if (key == "_any") {
for (const std::string &key : item.listRecordNames()) {
values.push_back(item.getValue(key));
}
} else {
values.push_back(item.getValue(key));
}
return values;
});
}

bool
ItemFilter::passes(std::function<std::string(const std::string &)> accessor)
const
ItemFilter::passes(const std::function<accessor_f> &accessor) const
{
std::string error;
return passes(accessor, error);
}

bool
ItemFilter::passes(std::function<std::string(const std::string &)> accessor,
ItemFilter::passes(const std::function<accessor_f> &accessor,
std::string &error) const
{
error.clear();

auto test = [](const Cond &cond, const std::string &val) {
switch (cond.op) {
case Op::eq: return (val == cond.value);
case Op::ne: return (val != cond.value);
case Op::iccontains: return boost::icontains(val, cond.value);
case Op::icnotcontain: return !boost::icontains(val, cond.value);
}
assert(false && "Unhandled operation type.");
return false;
};

auto err = [&error](const Cond &cond) {
if (!error.empty()) {
error += '\n';
Expand All @@ -78,30 +98,16 @@ ItemFilter::passes(std::function<std::string(const std::string &)> accessor,
};

for (const Cond &cond : conds) {
const std::string &val = accessor(cond.key);
switch (cond.op) {
case Op::eq:
if (val != cond.value) {
err(cond);
}
continue;
case Op::ne:
if (val == cond.value) {
err(cond);
}
continue;
case Op::iccontains:
if (!boost::icontains(val, cond.value)) {
err(cond);
}
continue;
case Op::icnotcontain:
if (boost::icontains(val, cond.value)) {
err(cond);
}
continue;
bool matched = false;
for (const std::string &val : accessor(cond.key)) {
if (test(cond, val)) {
matched = true;
break;
}
}
if (!matched) {
err(cond);
}
assert(false && "Unhandled operation type.");
}

return error.empty();
Expand Down
12 changes: 10 additions & 2 deletions src/ItemFilter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ class Item;
*/
class ItemFilter
{
/**
* @brief Type of function that is used to query field value.
*
* A field can be expanded to zero, one or multiple values and each value
* will be matched individually.
*/
using accessor_f = std::vector<std::string>(const std::string &key);

public:
/**
* @brief Constructs the filter out of conditions in textual form.
Expand Down Expand Up @@ -72,7 +80,7 @@ class ItemFilter
*
* @returns @c true if it passes, and @c false otherwise.
*/
bool passes(std::function<std::string(const std::string &)> accessor) const;
bool passes(const std::function<accessor_f> &accessor) const;

/**
* @brief Checks whether item represented by its fields passes the filter.
Expand All @@ -82,7 +90,7 @@ class ItemFilter
*
* @returns @c true if it passes, and @c false otherwise.
*/
bool passes(std::function<std::string(const std::string &)> accessor,
bool passes(const std::function<accessor_f> &accessor,
std::string &error) const;

private:
Expand Down
3 changes: 2 additions & 1 deletion src/cmds/AddCmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@ AddCmd::run(Project &project, const std::vector<std::string> &args)

std::string guard = cfg.get("guards.newitem", std::string());
auto accessor = [&fields](const std::string &f) {
using return_t = std::vector<std::string>;
auto it = fields.find(f);
return (it == fields.cend()) ? std::string() : it->second;
return (it == fields.cend()) ? return_t{} : return_t{ it->second };
};
std::string error;
if (!ItemFilter(breakIntoArgs(guard)).passes(accessor, error)) {
Expand Down
13 changes: 12 additions & 1 deletion tests/ItemFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,19 @@ TEST_CASE("Error messages add up", "[item-filter]")
ItemFilter filter({ "_id==notid", "title!=title", "_id!/ID", "title/xy" });

std::string error;
auto accessor = [&item](const std::string &f) { return item.getValue(f); };
auto accessor = [&item](const std::string &f) {
return std::vector<std::string>{ item.getValue(f) };
};
REQUIRE(!filter.passes(accessor, error));

REQUIRE(split(error, '\n').size() == 4);
}

TEST_CASE("_any pseudo field", "[item-filter]")
{
Item item = Tests::makeItem("id");
item.setValue("title", "title");

ItemFilter filter({ "_any==title" });
REQUIRE(filter.passes(item));
}

0 comments on commit 4a9c617

Please sign in to comment.