format: redesign how grouping behaves so it's more sensible
This commit is contained in:
14
doc/config
14
doc/config
@@ -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
|
||||||
|
|||||||
@@ -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 == '%')
|
||||||
{
|
{
|
||||||
|
|||||||
120
src/format.h
120
src/format.h
@@ -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:
|
||||||
|
|||||||
@@ -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(" ");
|
||||||
|
|||||||
Reference in New Issue
Block a user