parent
25708093e6
commit
8d24c42261
31 changed files with 636 additions and 404 deletions
@ -0,0 +1,209 @@ |
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2014 by Andrzej Rybczak * |
||||
* electricityispower@gmail.com * |
||||
* * |
||||
* 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. * |
||||
***************************************************************************/ |
||||
|
||||
#include <stdexcept> |
||||
|
||||
#include "format.h" |
||||
#include "utility/type_conversions.h" |
||||
|
||||
namespace { |
||||
|
||||
template <typename CharT> using string = std::basic_string<CharT>; |
||||
template <typename CharT> using iterator = typename std::basic_string<CharT>::const_iterator; |
||||
template <typename CharT> using expressions = std::vector<Format::Expression<CharT>>; |
||||
|
||||
std::string invalidCharacter(char c) |
||||
{ |
||||
return "invalid character '" + boost::lexical_cast<std::string>(c) + "'"; |
||||
} |
||||
|
||||
template <typename CharT> |
||||
void throwError(const string<CharT> &s, iterator<CharT> current, std::string msg) |
||||
{ |
||||
throw std::runtime_error( |
||||
std::move(msg) + " at position " + boost::lexical_cast<std::string>(current - s.begin()) |
||||
); |
||||
} |
||||
|
||||
template <typename CharT> |
||||
void rangeCheck(const string<CharT> &s, iterator<CharT> current, iterator<CharT> end) |
||||
{ |
||||
if (current >= end) |
||||
throwError(s, current, "unexpected end"); |
||||
} |
||||
|
||||
template <typename CharT> |
||||
expressions<CharT> parseBracket(const string<CharT> &s, |
||||
iterator<CharT> it, iterator<CharT> end) |
||||
{ |
||||
string<CharT> token; |
||||
expressions<CharT> tmp, result; |
||||
auto push_token = [&] { |
||||
result.push_back(std::move(token)); |
||||
}; |
||||
for (; it != end; ++it) |
||||
{ |
||||
if (*it == '{') |
||||
{ |
||||
push_token(); |
||||
bool done; |
||||
Format::Any<CharT> any; |
||||
do |
||||
{ |
||||
auto jt = it; |
||||
done = true; |
||||
// get to the corresponding closing bracket
|
||||
unsigned brackets = 1; |
||||
while (brackets > 0) |
||||
{ |
||||
if (++jt == end) |
||||
break; |
||||
if (*jt == '{') |
||||
++brackets; |
||||
else if (*jt == '}') |
||||
--brackets; |
||||
} |
||||
// check if we're still in range
|
||||
rangeCheck(s, jt, end); |
||||
// skip the opening bracket
|
||||
++it; |
||||
// recursively parse the bracket
|
||||
tmp = parseBracket(s, it, jt); |
||||
// if the inner bracket contains only one expression,
|
||||
// put it as is. otherwise require all of them.
|
||||
if (tmp.size() == 1) |
||||
any.base().push_back(std::move(tmp[0])); |
||||
else |
||||
any.base().push_back(Format::All<CharT>(std::move(tmp))); |
||||
it = jt; |
||||
// check for the alternative
|
||||
++jt; |
||||
if (jt != end && *jt == '|') |
||||
{ |
||||
++jt; |
||||
rangeCheck(s, jt, end); |
||||
if (*jt != '{') |
||||
throwError(s, jt, invalidCharacter(*jt) + ", expected '{'"); |
||||
it = jt; |
||||
done = false; |
||||
} |
||||
} |
||||
while (!done); |
||||
assert(!any.base().empty()); |
||||
// if there was only one bracket, append empty branch
|
||||
// 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 (*it == '%') |
||||
{ |
||||
++it; |
||||
rangeCheck(s, it, end); |
||||
// %% is escaped %
|
||||
if (*it == '%') |
||||
{ |
||||
token += '%'; |
||||
continue; |
||||
} |
||||
push_token(); |
||||
// check for tag delimiter
|
||||
unsigned delimiter = 0; |
||||
if (isdigit(*it)) |
||||
{ |
||||
std::string sdelimiter; |
||||
do |
||||
sdelimiter += *it++; |
||||
while (it != end && isdigit(*it)); |
||||
rangeCheck(s, it, end); |
||||
delimiter = boost::lexical_cast<unsigned>(sdelimiter); |
||||
} |
||||
auto f = charToGetFunction(*it); |
||||
if (f == nullptr) |
||||
throwError(s, it, invalidCharacter(*it)); |
||||
result.push_back(Format::SongTag(f, delimiter)); |
||||
} |
||||
else if (*it == '$') |
||||
{ |
||||
++it; |
||||
rangeCheck(s, it, end); |
||||
// $$ is escaped $
|
||||
if (*it == '$') |
||||
{ |
||||
token += '$'; |
||||
continue; |
||||
} |
||||
push_token(); |
||||
if (isdigit(*it)) |
||||
{ |
||||
auto color = charToColor(*it); |
||||
result.push_back(color); |
||||
} |
||||
else if (*it == 'R') |
||||
result.push_back(Format::AlignRight()); |
||||
else if (*it == 'b') |
||||
result.push_back(NC::Format::Bold); |
||||
else if (*it == 'u') |
||||
result.push_back(NC::Format::Underline); |
||||
else if (*it == 'a') |
||||
result.push_back(NC::Format::AltCharset); |
||||
else if (*it == 'r') |
||||
result.push_back(NC::Format::Reverse); |
||||
else if (*it == '/') |
||||
{ |
||||
++it; |
||||
rangeCheck(s, it, end); |
||||
if (*it == 'b') |
||||
result.push_back(NC::Format::NoBold); |
||||
else if (*it == 'u') |
||||
result.push_back(NC::Format::NoUnderline); |
||||
else if (*it == 'a') |
||||
result.push_back(NC::Format::NoAltCharset); |
||||
else if (*it == 'r') |
||||
result.push_back(NC::Format::NoReverse); |
||||
else |
||||
throwError(s, it, invalidCharacter(*it)); |
||||
} |
||||
else |
||||
throwError(s, it, invalidCharacter(*it)); |
||||
} |
||||
else |
||||
token += *it; |
||||
} |
||||
push_token(); |
||||
return result; |
||||
} |
||||
|
||||
} |
||||
|
||||
namespace Format { |
||||
|
||||
AST<char> parse(const std::string &s) |
||||
{ |
||||
return AST<char>(parseBracket(s, s.begin(), s.end())); |
||||
} |
||||
|
||||
AST<wchar_t> wparse(const std::wstring &s) |
||||
{ |
||||
return AST<wchar_t>(parseBracket(s, s.begin(), s.end())); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,279 @@ |
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2014 by Andrzej Rybczak * |
||||
* electricityispower@gmail.com * |
||||
* * |
||||
* 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_H |
||||
#define NCMPCPP_HAVE_FORMAT_H |
||||
|
||||
#include <boost/locale/encoding_utf.hpp> |
||||
#include <boost/variant.hpp> |
||||
|
||||
#include "menu.h" |
||||
#include "song.h" |
||||
#include "strbuffer.h" |
||||
#include "utility/wide_string.h" |
||||
|
||||
namespace Format { |
||||
|
||||
enum class ListType { All, Any, AST }; |
||||
|
||||
template <ListType, typename> struct List; |
||||
template <typename CharT> using All = List<ListType::All, CharT>; |
||||
template <typename CharT> using Any = List<ListType::Any, CharT>; |
||||
template <typename CharT> using AST = List<ListType::AST, CharT>; |
||||
|
||||
struct AlignRight { }; |
||||
|
||||
struct SongTag |
||||
{ |
||||
SongTag(MPD::Song::GetFunction function_, unsigned delimiter_ = 0) |
||||
: m_function(function_), m_delimiter(delimiter_) |
||||
{ } |
||||
|
||||
MPD::Song::GetFunction function() const { return m_function; } |
||||
unsigned delimiter() const { return m_delimiter; } |
||||
|
||||
private: |
||||
MPD::Song::GetFunction m_function; |
||||
unsigned m_delimiter; |
||||
}; |
||||
|
||||
template <typename CharT> |
||||
using Expression = boost::variant< |
||||
std::basic_string<CharT>, |
||||
NC::Color, |
||||
NC::Format, |
||||
AlignRight, |
||||
SongTag, |
||||
boost::recursive_wrapper<Any<CharT>>, |
||||
boost::recursive_wrapper<All<CharT>> |
||||
>; |
||||
|
||||
template <ListType Type, typename CharT> |
||||
struct List |
||||
{ |
||||
typedef std::vector<Expression<CharT>> Base; |
||||
|
||||
List() { } |
||||
List(Base &&base_) |
||||
: m_base(std::move(base_)) |
||||
{ } |
||||
|
||||
Base &base() { return m_base; } |
||||
const Base &base() const { return m_base; } |
||||
|
||||
private: |
||||
Base m_base; |
||||
}; |
||||
|
||||
template <typename CharT, typename OutputT, typename OutputAfterAlignmentT = OutputT> |
||||
struct Printer: boost::static_visitor<bool> |
||||
{ |
||||
typedef std::basic_string<CharT> StringT; |
||||
|
||||
Printer(OutputT &os, const MPD::Song *song, OutputAfterAlignmentT *os_after_alignment) |
||||
: m_output(os) |
||||
, m_song(song) |
||||
, m_after_alignment(false) |
||||
, m_output_after_alignment(os_after_alignment) |
||||
, m_no_output(0) |
||||
{ } |
||||
|
||||
bool operator()(const StringT &s) |
||||
{ |
||||
output(s); |
||||
return true; |
||||
} |
||||
|
||||
bool operator()(const NC::Color &c) |
||||
{ |
||||
output(c); |
||||
return true; |
||||
} |
||||
|
||||
bool operator()(NC::Format fmt) |
||||
{ |
||||
output(fmt); |
||||
return true; |
||||
} |
||||
|
||||
bool operator()(AlignRight) |
||||
{ |
||||
if (!m_no_output) |
||||
m_after_alignment = true; |
||||
return true; |
||||
} |
||||
|
||||
bool operator()(const SongTag &st) |
||||
{ |
||||
StringT tags; |
||||
if (m_song != nullptr) |
||||
{ |
||||
tags = convertString<std::is_same<char, CharT>::value, std::string>::apply( |
||||
m_song->getTags(st.function()) |
||||
); |
||||
} |
||||
if (!tags.empty()) |
||||
{ |
||||
if (st.delimiter() > 0) |
||||
{ |
||||
// shorten length by chopping off the tail
|
||||
if (st.function() == &MPD::Song::getLength) |
||||
tags.resize(st.delimiter()); |
||||
else |
||||
tags = wideShorten(tags, st.delimiter()); |
||||
} |
||||
output(tags); |
||||
return true; |
||||
} |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
bool operator()(const All<CharT> &all) |
||||
{ |
||||
auto visit = [this, &all] { |
||||
return std::all_of( |
||||
all.base().begin(), |
||||
all.base().end(), |
||||
[this](const Expression<CharT> &ex) { |
||||
return boost::apply_visitor(*this, ex); |
||||
} |
||||
); |
||||
}; |
||||
++m_no_output; |
||||
bool all_ok = visit(); |
||||
--m_no_output; |
||||
if (!m_no_output && all_ok) |
||||
visit(); |
||||
return all_ok; |
||||
} |
||||
|
||||
bool operator()(const Any<CharT> &any) |
||||
{ |
||||
return std::any_of( |
||||
any.base().begin(), |
||||
any.base().end(), |
||||
[this](const Expression<CharT> &ex) { |
||||
return boost::apply_visitor(*this, ex); |
||||
} |
||||
); |
||||
} |
||||
|
||||
private: |
||||
// convert string to appropriate type
|
||||
template <bool AreSame, typename ValueT> |
||||
struct convertString { |
||||
static const StringT &apply(const ValueT &s) { |
||||
return s; |
||||
} |
||||
}; |
||||
template <typename ValueT> |
||||
struct convertString<false, ValueT> { |
||||
static StringT apply(const ValueT &s) { |
||||
return boost::locale::conv::utf_to_utf<CharT>(s); |
||||
} |
||||
}; |
||||
|
||||
// generic version for streams (buffers, menus)
|
||||
template <typename ValueT, typename OutputStreamT> |
||||
struct output_ { |
||||
static void exec(OutputStreamT &os, const ValueT &value) { |
||||
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) { |
||||
result += s; |
||||
} |
||||
}; |
||||
// when writing to a string, ignore all properties
|
||||
template <typename ValueT, typename SomeCharT> |
||||
struct output_<ValueT, std::basic_string<SomeCharT>> |
||||
{ |
||||
static void exec(std::basic_string<CharT> &, const ValueT &) { } |
||||
}; |
||||
|
||||
template <typename ValueT> |
||||
void output(const ValueT &value) const |
||||
{ |
||||
if (!m_no_output) |
||||
{ |
||||
if (m_after_alignment && m_output_after_alignment != nullptr) |
||||
output_<ValueT, OutputAfterAlignmentT>::exec(*m_output_after_alignment, value); |
||||
else |
||||
output_<ValueT, OutputT>::exec(m_output, value); |
||||
} |
||||
} |
||||
|
||||
OutputT &m_output; |
||||
const MPD::Song *m_song; |
||||
|
||||
bool m_after_alignment; |
||||
OutputAfterAlignmentT *m_output_after_alignment; |
||||
|
||||
unsigned m_no_output; |
||||
}; |
||||
|
||||
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) |
||||
{ |
||||
Printer<CharT, NC::Menu<ItemT>, NC::Buffer> printer(menu, song, buffer); |
||||
visit(printer, ast); |
||||
} |
||||
|
||||
template <typename CharT> |
||||
void print(const AST<CharT> &ast, NC::BasicBuffer<CharT> &buffer, const MPD::Song *song) |
||||
{ |
||||
Printer<CharT, NC::BasicBuffer<CharT>> printer(buffer, song, &buffer); |
||||
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); |
||||
visit(printer, ast); |
||||
return result; |
||||
} |
||||
|
||||
AST<char> parse(const std::string &s); |
||||
AST<wchar_t> wparse(const std::wstring &ws); |
||||
|
||||
} |
||||
|
||||
#endif // NCMPCPP_HAVE_FORMAT_H
|
||||
Loading…
Reference in new issue