diff --git a/src/display.cpp b/src/display.cpp index 2bbd1ee7..83a1b3bf 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -110,9 +110,11 @@ void showSongs(NC::Menu &menu, const MPD::Song &s, bool separate_albums, is_now_playing, is_selected, discard_colors; setProperties(menu, s, pl, separate_albums, is_now_playing, is_selected, discard_colors); - size_t y = menu.getY(); + const size_t y = menu.getY(); NC::Buffer right_aligned; - Format::print(ast, menu, &s, &right_aligned); + Format::print(ast, menu, &s, &right_aligned, + discard_colors ? Format::Flags::Tag | Format::Flags::OutputSwitch : Format::Flags::All + ); if (!right_aligned.str().empty()) { size_t x_off = menu.getWidth() - wideLength(ToWString(right_aligned.str())); diff --git a/src/format.cpp b/src/format.cpp index 17ca5cd9..025cf2ae 100644 --- a/src/format.cpp +++ b/src/format.cpp @@ -25,6 +25,10 @@ namespace { +const unsigned properties = Format::Flags::Color + | Format::Flags::Format + | Format::Flags::OutputSwitch; + template using string = std::basic_string; template using iterator = typename std::basic_string::const_iterator; template using expressions = std::vector>; @@ -54,7 +58,8 @@ void rangeCheck(const string &s, iterator current, iterator template expressions parseBracket(const string &s, - iterator it, iterator end) + iterator it, iterator end, + const unsigned flags) { string token; expressions tmp, result; @@ -88,7 +93,7 @@ expressions parseBracket(const string &s, // skip the opening bracket ++it; // recursively parse the bracket - tmp = parseBracket(s, it, jt); + tmp = parseBracket(s, it, jt, flags); // if the inner bracket contains only one expression, // put it as is. otherwise require all of them. if (tmp.size() == 1) @@ -117,7 +122,7 @@ expressions parseBracket(const string &s, any.base().push_back(string()); result.push_back(std::move(any)); } - else if (*it == '%') + else if (flags & Format::Flags::Tag && *it == '%') { ++it; rangeCheck(s, it, end); @@ -144,7 +149,7 @@ expressions parseBracket(const string &s, throwError(s, it, invalidCharacter(*it)); result.push_back(Format::SongTag(f, delimiter)); } - else if (*it == '$') + else if (flags & properties && *it == '$') { ++it; rangeCheck(s, it, end); @@ -155,22 +160,43 @@ expressions parseBracket(const string &s, continue; } push_token(); - if (isdigit(*it)) + // legacy colors + if (flags & Format::Flags::Color && isdigit(*it)) { auto color = charToColor(*it); result.push_back(color); } - else if (*it == 'R') - result.push_back(Format::AlignRight()); - else if (*it == 'b') + // new colors + else if (flags & Format::Flags::Color && *it == '(') + { + ++it; + rangeCheck(s, it, end); + auto jt = it; + string scolor; + do + scolor += *it++; + while (it != end && *it != ')'); + rangeCheck(s, it, end); + auto value = convertString::apply(scolor); + try { + result.push_back(boost::lexical_cast(value)); + } catch (boost::bad_lexical_cast &) { + throwError(s, jt, "invalid color \"" + value + "\""); + } + } + // output switch + else if (flags & Format::Flags::OutputSwitch && *it == 'R') + result.push_back(Format::OutputSwitch()); + // format + else if (flags & Format::Flags::Format && *it == 'b') result.push_back(NC::Format::Bold); - else if (*it == 'u') + else if (flags & Format::Flags::Format && *it == 'u') result.push_back(NC::Format::Underline); - else if (*it == 'a') + else if (flags & Format::Flags::Format && *it == 'a') result.push_back(NC::Format::AltCharset); - else if (*it == 'r') + else if (flags & Format::Flags::Format && *it == 'r') result.push_back(NC::Format::Reverse); - else if (*it == '/') + else if (flags & Format::Flags::Format && *it == '/') { ++it; rangeCheck(s, it, end); @@ -185,23 +211,6 @@ expressions parseBracket(const string &s, else throwError(s, it, invalidCharacter(*it)); } - else if (*it == '(') - { - ++it; - rangeCheck(s, it, end); - auto jt = it; - string scolor; - do - scolor += *it++; - while (it != end && *it != ')'); - rangeCheck(s, it, end); - auto value = convertString::apply(scolor); - try { - result.push_back(boost::lexical_cast(value)); - } catch (boost::bad_lexical_cast &) { - throwError(s, jt, "invalid color \"" + value + "\""); - } - } else throwError(s, it, invalidCharacter(*it)); } @@ -216,14 +225,14 @@ expressions parseBracket(const string &s, namespace Format { -AST parse(const std::string &s) +AST parse(const std::string &s, const unsigned flags) { - return AST(parseBracket(s, s.begin(), s.end())); + return AST(parseBracket(s, s.begin(), s.end(), flags)); } -AST wparse(const std::wstring &s) +AST parse(const std::wstring &s, const unsigned flags) { - return AST(parseBracket(s, s.begin(), s.end())); + return AST(parseBracket(s, s.begin(), s.end(), flags)); } } diff --git a/src/format.h b/src/format.h index 30af58f6..a2762e69 100644 --- a/src/format.h +++ b/src/format.h @@ -31,6 +31,15 @@ namespace Format { +namespace Flags { +const unsigned None = 0; +const unsigned Color = 1; +const unsigned Format = 2; +const unsigned OutputSwitch = 4; +const unsigned Tag = 8; +const unsigned All = Color | Format | OutputSwitch | Tag; +} + enum class ListType { All, Any, AST }; template struct List; @@ -38,7 +47,7 @@ template using All = List; template using Any = List; template using AST = List; -struct AlignRight { }; +struct OutputSwitch { }; struct SongTag { @@ -59,7 +68,7 @@ using Expression = boost::variant< std::basic_string, NC::Color, NC::Format, - AlignRight, + OutputSwitch, SongTag, boost::recursive_wrapper>, boost::recursive_wrapper> @@ -82,17 +91,18 @@ private: Base m_base; }; -template +template struct Printer: boost::static_visitor { typedef std::basic_string StringT; - Printer(OutputT &os, const MPD::Song *song, OutputAfterAlignmentT *os_after_alignment) + Printer(OutputT &os, const MPD::Song *song, SecondOutputT *second_os, const unsigned flags) : m_output(os) , m_song(song) - , m_after_alignment(false) - , m_output_after_alignment(os_after_alignment) + , m_output_switched(false) + , m_second_os(second_os) , m_no_output(0) + , m_flags(flags) { } bool operator()(const StringT &s) @@ -103,27 +113,29 @@ struct Printer: boost::static_visitor bool operator()(const NC::Color &c) { - output(c); + if (m_flags & Flags::Color) + output(c); return true; } bool operator()(NC::Format fmt) { - output(fmt); + if (m_flags & Flags::Format) + output(fmt); return true; } - bool operator()(AlignRight) + bool operator()(OutputSwitch) { if (!m_no_output) - m_after_alignment = true; + m_output_switched = true; return true; } bool operator()(const SongTag &st) { StringT tags; - if (m_song != nullptr) + if (m_flags & Flags::Tag && m_song != nullptr) { tags = convertString::apply( m_song->getTags(st.function()) @@ -198,11 +210,13 @@ private: result += s; } }; - // when writing to a string, ignore all properties + // when writing to a string, we should ignore all other + // properties. if this code is reached, throw an exception. template - struct output_> - { - static void exec(std::basic_string &, const ValueT &) { } + struct output_> { + static void exec(std::basic_string &, const ValueT &) { + throw std::logic_error("non-string property can't be appended to the string"); + } }; template @@ -210,8 +224,8 @@ private: { if (!m_no_output) { - if (m_after_alignment && m_output_after_alignment != nullptr) - output_::exec(*m_output_after_alignment, value); + if (m_output_switched && m_second_os != nullptr) + output_::exec(*m_second_os, value); else output_::exec(m_output, value); } @@ -220,10 +234,11 @@ private: OutputT &m_output; const MPD::Song *m_song; - bool m_after_alignment; - OutputAfterAlignmentT *m_output_after_alignment; + bool m_output_switched; + SecondOutputT *m_second_os; unsigned m_no_output; + const unsigned m_flags; }; template @@ -234,17 +249,18 @@ void visit(VisitorT &visitor, const AST &ast) } template -void print(const AST &ast, NC::Menu &menu, - const MPD::Song *song, NC::BasicBuffer *buffer) +void print(const AST &ast, NC::Menu &menu, const MPD::Song *song, + NC::BasicBuffer *buffer, const unsigned flags = Flags::All) { - Printer, NC::Buffer> printer(menu, song, buffer); + Printer, NC::Buffer> printer(menu, song, buffer, flags); visit(printer, ast); } template -void print(const AST &ast, NC::BasicBuffer &buffer, const MPD::Song *song) +void print(const AST &ast, NC::BasicBuffer &buffer, + const MPD::Song *song, const unsigned flags = Flags::All) { - Printer> printer(buffer, song, &buffer); + Printer> printer(buffer, song, &buffer, flags); visit(printer, ast); } @@ -252,13 +268,13 @@ template std::basic_string stringify(const AST &ast, const MPD::Song *song) { std::basic_string result; - Printer> printer(result, song, &result); + Printer> printer(result, song, &result, Flags::Tag); visit(printer, ast); return result; } -AST parse(const std::string &s); -AST wparse(const std::wstring &ws); +AST parse(const std::string &s, const unsigned flags = Flags::All); +AST parse(const std::wstring &ws, const unsigned flags = Flags::All); } diff --git a/src/lyrics.cpp b/src/lyrics.cpp index 3677a72e..a35cde82 100644 --- a/src/lyrics.cpp +++ b/src/lyrics.cpp @@ -129,7 +129,7 @@ std::wstring Lyrics::title() std::wstring result = L"Lyrics: "; result += Scroller( - Format::stringify(Format::wparse(L"%a - %t"), &itsSong), + Format::stringify(Format::parse(L"%a - %t"), &itsSong), itsScrollBegin, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()) ); diff --git a/src/settings.cpp b/src/settings.cpp index 4347f98f..d360f846 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -165,7 +165,8 @@ option_parser::worker buffer(NC::Buffer &arg, ValueT &&value, TransformT &&map) { return option_parser::worker(assign(arg, [&arg, map](std::string s) { NC::Buffer result; - Format::print(Format::parse(s), result, nullptr); + auto ast = Format::parse(s, Format::Flags::Color | Format::Flags::Format); + Format::print(ast, result, nullptr); return map(std::move(result)); }), defaults_to(arg, map(std::forward(value)))); } @@ -271,30 +272,37 @@ bool Configuration::read(const std::string &config_path) message_delay_time, 5 )); p.add("song_list_format", assign_default( - song_list_format, "{%a - }{%t}|{$8%f$9}$R{$3(%l)$9}", Format::parse - )); + song_list_format, "{%a - }{%t}|{$8%f$9}$R{$3(%l)$9}", [](std::string v) { + return Format::parse(v); + })); p.add("song_status_format", assign_default( song_status_format, "{{%a{ \"%b\"{ (%y)}} - }{%t}}|{%f}", [this](std::string v) { // precompute wide format for status display - song_status_wformat = Format::wparse(ToWString(v)); + song_status_wformat = Format::parse(ToWString(v)); return Format::parse(v); })); p.add("song_library_format", assign_default( - song_library_format, "{%n - }{%t}|{%f}", Format::parse - )); + song_library_format, "{%n - }{%t}|{%f}", [](std::string v) { + return Format::parse(v); + })); p.add("browser_sort_mode", assign_default( browser_sort_mode, SortMode::Name )); p.add("browser_sort_format", assign_default( - browser_sort_format, "{%a - }{%t}|{%f} {(%l)}", Format::parse - )); + browser_sort_format, "{%a - }{%t}|{%f} {(%l)}", [](std::string v) { + return Format::parse(v, Format::Flags::Tag); + })); p.add("alternative_header_first_line_format", assign_default( - new_header_first_line, "$b$1$aqqu$/a$9 {%t}|{%f} $1$atqq$/a$9$/b", [this](std::string v) { - return Format::wparse(ToWString(std::move(v))); + new_header_first_line, "$b$1$aqqu$/a$9 {%t}|{%f} $1$atqq$/a$9$/b", [](std::string v) { + return Format::parse(ToWString(std::move(v)), + Format::Flags::All ^ Format::Flags::OutputSwitch + ); })); p.add("alternative_header_second_line_format", assign_default( - new_header_second_line, "{{$4$b%a$/b$9}{ - $7%b$9}{ ($4%y$9)}}|{%D}", [this](std::string v) { - return Format::wparse(ToWString(std::move(v))); + new_header_second_line, "{{$4$b%a$/b$9}{ - $7%b$9}{ ($4%y$9)}}|{%D}", [](std::string v) { + return Format::parse(ToWString(std::move(v)), + Format::Flags::All ^ Format::Flags::OutputSwitch + ); })); p.add("now_playing_prefix", buffer( now_playing_prefix, NC::Buffer::init(NC::Format::Bold), [this](NC::Buffer buf) { @@ -323,8 +331,9 @@ bool Configuration::read(const std::string &config_path) modified_item_prefix, NC::Buffer::init(NC::Color::Green, "> ", NC::Color::End), id_() )); p.add("song_window_title_format", assign_default( - song_window_title_format, "{%a - }{%t}|{%f}", Format::parse - )); + song_window_title_format, "{%a - }{%t}|{%f}", [](std::string v) { + return Format::parse(v, Format::Flags::Tag); + })); p.add("song_columns_list_format", assign_default( columns_format, "(20)[]{a} (6f)[green]{NE} (50)[white]{t|f:Title} (20)[cyan]{b} (7f)[magenta]{l}", [this](std::string v) {