format: redesign how grouping behaves so it's more sensible

This commit is contained in:
Andrzej Rybczak
2015-02-11 16:28:21 +01:00
parent b874173c57
commit 809ffada40
4 changed files with 103 additions and 57 deletions

View File

@@ -133,7 +133,7 @@
# #
##### song format ##### ##### song format #####
## ##
## for song format you can use: ## For a song format you can use:
## ##
## %l - length ## %l - length
## %f - filename ## %f - filename
@@ -153,10 +153,14 @@
## %P - priority ## %P - priority
## $R - begin right alignment ## $R - begin right alignment
## ##
## You can also put them in { } and then it will be displayed ## If you want to make sure that a part of the format is displayed
## only if all requested values are available and/or define alternate ## only when certain tags are present, you can archieve it by
## value with { }|{ } eg. {%a - %t}|{%f}. It is worth noting that ## grouping them with brackets, e.g. '{%a - %t}' will be evaluated
## a single bracket {..} is equivalent to the alternative {..}|{}. ## to 'ARTIST - TITLE' if both tags are present or '' otherwise.
## It is also possible to define a list of alternatives by providing
## several groups and separating them with '|', e.g. '{%t}|{%f}'
## will be evaluated to 'TITLE' or 'FILENAME' if the former is not
## present.
## ##
## Note: If you want to set limit on maximal length of a tag, just ## Note: If you want to set limit on maximal length of a tag, just
## put the appropriate number between % and character that defines ## put the appropriate number between % and character that defines

View File

@@ -64,7 +64,8 @@ expressions<CharT> parseBracket(const string<CharT> &s,
string<CharT> token; string<CharT> token;
expressions<CharT> tmp, result; expressions<CharT> tmp, result;
auto push_token = [&] { auto push_token = [&] {
result.push_back(std::move(token)); if (!token.empty())
result.push_back(std::move(token));
}; };
for (; it != end; ++it) for (; it != end; ++it)
{ {
@@ -72,7 +73,7 @@ expressions<CharT> parseBracket(const string<CharT> &s,
{ {
push_token(); push_token();
bool done; bool done;
Format::Any<CharT> any; Format::FirstOf<CharT> first_of;
do do
{ {
auto jt = it; auto jt = it;
@@ -95,11 +96,11 @@ expressions<CharT> parseBracket(const string<CharT> &s,
// recursively parse the bracket // recursively parse the bracket
tmp = parseBracket(s, it, jt, flags); tmp = parseBracket(s, it, jt, flags);
// if the inner bracket contains only one expression, // if the inner bracket contains only one expression,
// put it as is. otherwise require all of them. // put it as is. otherwise make a group out of them.
if (tmp.size() == 1) if (tmp.size() == 1)
any.base().push_back(std::move(tmp[0])); first_of.base().push_back(std::move(tmp[0]));
else else
any.base().push_back(Format::All<CharT>(std::move(tmp))); first_of.base().push_back(Format::Group<CharT>(std::move(tmp)));
it = jt; it = jt;
// check for the alternative // check for the alternative
++jt; ++jt;
@@ -114,13 +115,8 @@ expressions<CharT> parseBracket(const string<CharT> &s,
} }
} }
while (!done); while (!done);
assert(!any.base().empty()); assert(!first_of.base().empty());
// if there was only one bracket, append empty branch result.push_back(std::move(first_of));
// so that it always evaluates to true. otherwise put
// it as is.
if (any.base().size() == 1)
any.base().push_back(string<CharT>());
result.push_back(std::move(any));
} }
else if (flags & Format::Flags::Tag && *it == '%') else if (flags & Format::Flags::Tag && *it == '%')
{ {

View File

@@ -40,11 +40,11 @@ const unsigned Tag = 8;
const unsigned All = Color | Format | OutputSwitch | Tag; const unsigned All = Color | Format | OutputSwitch | Tag;
} }
enum class ListType { All, Any, AST }; enum class ListType { Group, FirstOf, AST };
template <ListType, typename> struct List; template <ListType, typename> struct List;
template <typename CharT> using All = List<ListType::All, CharT>; template <typename CharT> using Group = List<ListType::Group, CharT>;
template <typename CharT> using Any = List<ListType::Any, CharT>; template <typename CharT> using FirstOf = List<ListType::FirstOf, CharT>;
template <typename CharT> using AST = List<ListType::AST, CharT>; template <typename CharT> using AST = List<ListType::AST, CharT>;
struct OutputSwitch { }; struct OutputSwitch { };
@@ -63,6 +63,41 @@ private:
unsigned m_delimiter; unsigned m_delimiter;
}; };
enum class Result { Empty, Missing, Ok };
// Commutative binary operation such that:
// - Empty + Empty = Empty
// - Empty + Missing = Missing
// - Empty + Ok = Ok
// - Missing + Missing = Missing
// - Missing + Ok = Missing
// - Ok + Ok = Ok
inline Result &operator+=(Result &base, Result result)
{
if (base == Result::Missing || result == Result::Missing)
base = Result::Missing;
else if (base == Result::Ok || result == Result::Ok)
base = Result::Ok;
return base;
}
/*inline std::ostream &operator<<(std::ostream &os, Result r)
{
switch (r)
{
case Result::Empty:
os << "empty";
break;
case Result::Missing:
os << "missing";
break;
case Result::Ok:
os << "ok";
break;
}
return os;
}*/
template <typename CharT> template <typename CharT>
using Expression = boost::variant< using Expression = boost::variant<
std::basic_string<CharT>, std::basic_string<CharT>,
@@ -70,8 +105,8 @@ using Expression = boost::variant<
NC::Format, NC::Format,
OutputSwitch, OutputSwitch,
SongTag, SongTag,
boost::recursive_wrapper<Any<CharT>>, boost::recursive_wrapper<FirstOf<CharT>>,
boost::recursive_wrapper<All<CharT>> boost::recursive_wrapper<Group<CharT>>
>; >;
template <ListType Type, typename CharT> template <ListType Type, typename CharT>
@@ -92,7 +127,7 @@ private:
}; };
template <typename CharT, typename OutputT, typename SecondOutputT = OutputT> template <typename CharT, typename OutputT, typename SecondOutputT = OutputT>
struct Printer: boost::static_visitor<bool> struct Printer: boost::static_visitor<Result>
{ {
typedef std::basic_string<CharT> StringT; typedef std::basic_string<CharT> StringT;
@@ -105,34 +140,39 @@ struct Printer: boost::static_visitor<bool>
, m_flags(flags) , m_flags(flags)
{ } { }
bool operator()(const StringT &s) Result operator()(const StringT &s)
{ {
output(s); if (!s.empty())
return true; {
output(s);
return Result::Ok;
}
else
return Result::Empty;
} }
bool operator()(const NC::Color &c) Result operator()(const NC::Color &c)
{ {
if (m_flags & Flags::Color) if (m_flags & Flags::Color)
output(c); output(c);
return true; return Result::Empty;
} }
bool operator()(NC::Format fmt) Result operator()(NC::Format fmt)
{ {
if (m_flags & Flags::Format) if (m_flags & Flags::Format)
output(fmt); output(fmt);
return true; return Result::Empty;
} }
bool operator()(OutputSwitch) Result operator()(OutputSwitch)
{ {
if (!m_no_output) if (!m_no_output)
m_output_switched = true; m_output_switched = true;
return true; return Result::Ok;
} }
bool operator()(const SongTag &st) Result operator()(const SongTag &st)
{ {
StringT tags; StringT tags;
if (m_flags & Flags::Tag && m_song != nullptr) if (m_flags & Flags::Tag && m_song != nullptr)
@@ -152,40 +192,46 @@ struct Printer: boost::static_visitor<bool>
tags = wideShorten(tags, st.delimiter()); tags = wideShorten(tags, st.delimiter());
} }
output(tags); output(tags);
return true; return Result::Ok;
} }
else else
return false; return Result::Missing;
} }
bool operator()(const All<CharT> &all) // If all Empty -> Empty, if any Ok -> continue with Ok, if any Missing -> stop with Empty.
Result operator()(const Group<CharT> &group)
{ {
auto visit = [this, &all] { auto visit = [this, &group] {
return std::all_of( Result result = Result::Empty;
all.base().begin(), for (const auto &ex : group.base())
all.base().end(), {
[this](const Expression<CharT> &ex) { result += boost::apply_visitor(*this, ex);
return boost::apply_visitor(*this, ex); if (result == Result::Missing)
{
result = Result::Empty;
break;
} }
); }
return result;
}; };
++m_no_output; ++m_no_output;
bool all_ok = visit(); Result result = visit();
--m_no_output; --m_no_output;
if (!m_no_output && all_ok) if (!m_no_output && result == Result::Ok)
visit(); visit();
return all_ok; return result;
} }
bool operator()(const Any<CharT> &any) // If all Empty or Missing -> Empty, if any Ok -> stop with Ok.
Result operator()(const FirstOf<CharT> &first_of)
{ {
return std::any_of( for (const auto &ex : first_of.base())
any.base().begin(), {
any.base().end(), if (boost::apply_visitor(*this, ex) == Result::Ok)
[this](const Expression<CharT> &ex) { return Result::Ok;
return boost::apply_visitor(*this, ex); }
} return Result::Empty;
);
} }
private: private:

View File

@@ -125,14 +125,14 @@ Format::AST<char> columns_to_format(const std::vector<Column> &columns)
auto column = columns.begin(); auto column = columns.begin();
while (true) while (true)
{ {
Format::Any<char> any; Format::FirstOf<char> first_of;
for (const auto &type : column->type) for (const auto &type : column->type)
{ {
auto f = charToGetFunction(type); auto f = charToGetFunction(type);
assert(f != nullptr); assert(f != nullptr);
any.base().push_back(f); first_of.base().push_back(f);
} }
result.push_back(std::move(any)); result.push_back(std::move(first_of));
if (++column != columns.end()) if (++column != columns.end())
result.push_back(" "); result.push_back(" ");