300 lines
8.3 KiB
C++
300 lines
8.3 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2008-2021 by Andrzej Rybczak *
|
|
* andrzej@rybczak.net *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
***************************************************************************/
|
|
|
|
#ifndef NCMPCPP_HAVE_FORMAT_IMPL_H
|
|
#define NCMPCPP_HAVE_FORMAT_IMPL_H
|
|
|
|
#include <boost/variant.hpp>
|
|
|
|
#include "curses/menu.h"
|
|
#include "curses/strbuffer.h"
|
|
#include "format.h"
|
|
#include "song.h"
|
|
#include "utility/functional.h"
|
|
#include "utility/wide_string.h"
|
|
|
|
namespace Format {
|
|
|
|
// 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, typename OutputT, typename SecondOutputT = OutputT>
|
|
struct Printer: boost::static_visitor<Result>
|
|
{
|
|
typedef std::basic_string<CharT> StringT;
|
|
|
|
Printer(OutputT &os, const MPD::Song *song, SecondOutputT *second_os, const unsigned flags)
|
|
: m_output(os)
|
|
, m_song(song)
|
|
, m_output_switched(false)
|
|
, m_second_os(second_os)
|
|
, m_no_output(0)
|
|
, m_flags(flags)
|
|
{ }
|
|
|
|
Result operator()(const StringT &s)
|
|
{
|
|
if (!s.empty())
|
|
{
|
|
output(s);
|
|
return Result::Ok;
|
|
}
|
|
else
|
|
return Result::Empty;
|
|
}
|
|
|
|
Result operator()(const NC::Color &c)
|
|
{
|
|
if (m_flags & Flags::Color)
|
|
output(c);
|
|
return Result::Empty;
|
|
}
|
|
|
|
Result operator()(NC::Format fmt)
|
|
{
|
|
if (m_flags & Flags::Format)
|
|
output(fmt);
|
|
return Result::Empty;
|
|
}
|
|
|
|
Result operator()(OutputSwitch)
|
|
{
|
|
if (!m_no_output)
|
|
m_output_switched = true;
|
|
return Result::Ok;
|
|
}
|
|
|
|
Result operator()(const SongTag &st)
|
|
{
|
|
StringT tags;
|
|
if (m_flags & Flags::Tag && m_song != nullptr)
|
|
{
|
|
tags = convertString<CharT, char>::apply(
|
|
m_song->getTags(st.function())
|
|
);
|
|
}
|
|
if (!tags.empty())
|
|
{
|
|
if (st.delimiter() > 0)
|
|
{
|
|
// shorten date/length by simple truncation
|
|
if (st.function() == &MPD::Song::getDate
|
|
|| st.function() == &MPD::Song::getLength)
|
|
tags.resize(st.delimiter());
|
|
else
|
|
tags = wideShorten(tags, st.delimiter());
|
|
}
|
|
output(tags, &st);
|
|
return Result::Ok;
|
|
}
|
|
else
|
|
return Result::Missing;
|
|
}
|
|
|
|
// 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, &group] {
|
|
Result result = Result::Empty;
|
|
for (const auto &ex : group.base())
|
|
{
|
|
result += boost::apply_visitor(*this, ex);
|
|
if (result == Result::Missing)
|
|
{
|
|
result = Result::Empty;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
++m_no_output;
|
|
Result result = visit();
|
|
--m_no_output;
|
|
if (!m_no_output && result == Result::Ok)
|
|
visit();
|
|
return result;
|
|
}
|
|
|
|
// If all Empty or Missing -> Empty, if any Ok -> stop with Ok.
|
|
Result operator()(const FirstOf<CharT> &first_of)
|
|
{
|
|
for (const auto &ex : first_of.base())
|
|
{
|
|
if (boost::apply_visitor(*this, ex) == Result::Ok)
|
|
return Result::Ok;
|
|
}
|
|
return Result::Empty;
|
|
}
|
|
|
|
private:
|
|
// generic version for streams (buffers, menus)
|
|
template <typename ValueT, typename OutputStreamT>
|
|
struct output_ {
|
|
static void exec(OutputStreamT &os, const ValueT &value, const SongTag *) {
|
|
os << value;
|
|
}
|
|
};
|
|
|
|
// specialization for strings (input/output)
|
|
template <typename SomeCharT, typename OtherCharT>
|
|
struct output_<std::basic_string<SomeCharT>, std::basic_string<OtherCharT>> {
|
|
typedef std::basic_string<SomeCharT> SomeString;
|
|
typedef std::basic_string<OtherCharT> OtherString;
|
|
|
|
// compile only if string types are the same
|
|
static typename std::enable_if<
|
|
std::is_same<SomeString, OtherString>::value,
|
|
void
|
|
>::type exec(SomeString &result, const OtherString &s, const SongTag *) {
|
|
result += s;
|
|
}
|
|
};
|
|
// When writing to a string, we should ignore all other properties. If this
|
|
// code is reached, throw an exception.
|
|
template <typename ValueT, typename SomeCharT>
|
|
struct output_<ValueT, std::basic_string<SomeCharT>> {
|
|
static void exec(std::basic_string<CharT> &, const ValueT &, const SongTag *) {
|
|
throw std::logic_error("non-string property can't be appended to the string");
|
|
}
|
|
};
|
|
|
|
// Specialization for TagVector.
|
|
template <typename SomeCharT, typename OtherCharT>
|
|
struct output_<std::basic_string<SomeCharT>, TagVector<OtherCharT> > {
|
|
// Compile only if string types are the same.
|
|
static typename std::enable_if<
|
|
std::is_same<SomeCharT, OtherCharT>::value,
|
|
void
|
|
>::type exec(TagVector<OtherCharT> &acc,
|
|
const std::basic_string<SomeCharT> &s, const SongTag *st) {
|
|
if (st != nullptr)
|
|
acc.emplace_back(*st, s);
|
|
else
|
|
acc.emplace_back(boost::none, s);
|
|
}
|
|
};
|
|
// When extracting tags from a song all the other properties should be
|
|
// ignored. If that's not the case, throw an exception.
|
|
template <typename ValueT, typename SomeCharT>
|
|
struct output_<ValueT, TagVector<SomeCharT> > {
|
|
static void exec(TagVector<SomeCharT> &, const ValueT &, const SongTag *) {
|
|
throw std::logic_error("Non-string property can't be inserted into the TagVector");
|
|
}
|
|
};
|
|
|
|
template <typename ValueT>
|
|
void output(const ValueT &value, const SongTag *st = nullptr) const
|
|
{
|
|
if (!m_no_output)
|
|
{
|
|
if (m_output_switched && m_second_os != nullptr)
|
|
output_<ValueT, SecondOutputT>::exec(*m_second_os, value, st);
|
|
else
|
|
output_<ValueT, OutputT>::exec(m_output, value, st);
|
|
}
|
|
}
|
|
|
|
OutputT &m_output;
|
|
const MPD::Song *m_song;
|
|
|
|
bool m_output_switched;
|
|
SecondOutputT *m_second_os;
|
|
|
|
unsigned m_no_output;
|
|
const unsigned m_flags;
|
|
};
|
|
|
|
template <typename CharT, typename VisitorT>
|
|
void visit(VisitorT &visitor, const AST<CharT> &ast)
|
|
{
|
|
for (const auto &ex : ast.base())
|
|
boost::apply_visitor(visitor, ex);
|
|
}
|
|
|
|
template <typename CharT, typename ItemT>
|
|
void print(const AST<CharT> &ast, NC::Menu<ItemT> &menu, const MPD::Song *song,
|
|
NC::BasicBuffer<CharT> *buffer, const unsigned flags)
|
|
{
|
|
Printer<CharT, NC::Menu<ItemT>, NC::Buffer> printer(menu, song, buffer, flags);
|
|
visit(printer, ast);
|
|
}
|
|
|
|
template <typename CharT>
|
|
void print(const AST<CharT> &ast, NC::BasicBuffer<CharT> &buffer,
|
|
const MPD::Song *song, const unsigned flags)
|
|
{
|
|
Printer<CharT, NC::BasicBuffer<CharT>> printer(buffer, song, &buffer, flags);
|
|
visit(printer, ast);
|
|
}
|
|
|
|
template <typename CharT>
|
|
std::basic_string<CharT> stringify(const AST<CharT> &ast, const MPD::Song *song)
|
|
{
|
|
std::basic_string<CharT> result;
|
|
Printer<CharT, std::basic_string<CharT>> printer(result, song, &result, Flags::Tag);
|
|
visit(printer, ast);
|
|
return result;
|
|
}
|
|
|
|
template <typename CharT>
|
|
TagVector<CharT> flatten(const AST<CharT> &ast, const MPD::Song &song)
|
|
{
|
|
TagVector<CharT> result;
|
|
Printer<CharT, TagVector<CharT>> printer(result, &song, &result, Flags::Tag);
|
|
visit(printer, ast);
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
#endif // NCMPCPP_HAVE_FORMAT__IMPL_H
|