polybar-dwm/src/components/parser.cpp

296 lines
7.0 KiB
C++
Raw Normal View History

#include <cassert>
2016-11-02 19:22:45 +00:00
#include "components/parser.hpp"
2016-11-21 14:07:00 +00:00
#include "components/types.hpp"
#include "events/signal.hpp"
#include "events/signal_emitter.hpp"
#include "settings.hpp"
2016-12-26 08:40:15 +00:00
#include "utils/color.hpp"
2017-01-11 02:07:28 +00:00
#include "utils/factory.hpp"
#include "utils/file.hpp"
2016-11-02 19:22:45 +00:00
#include "utils/math.hpp"
#include "utils/memory.hpp"
2016-11-02 19:22:45 +00:00
#include "utils/string.hpp"
2016-11-19 05:22:44 +00:00
POLYBAR_NS
2016-11-02 19:22:45 +00:00
using namespace signals::parser;
/**
* Create instance
*/
parser::make_type parser::make() {
return factory_util::unique<parser>(signal_emitter::make());
}
/**
* Construct parser instance
*/
parser::parser(signal_emitter& emitter) : m_sig(emitter) {}
2016-11-21 14:07:00 +00:00
2016-11-02 19:22:45 +00:00
/**
* Process input string
2016-11-02 19:22:45 +00:00
*/
void parser::parse(const bar_settings& bar, string data) {
2016-12-14 04:17:18 +00:00
while (!data.empty()) {
size_t pos{string::npos};
2016-11-25 12:55:15 +00:00
if (data.compare(0, 2, "%{") == 0 && (pos = data.find('}')) != string::npos) {
codeblock(data.substr(2, pos - 2), bar);
2016-11-02 19:22:45 +00:00
data.erase(0, pos + 1);
2016-12-14 04:17:18 +00:00
} else if ((pos = data.find("%{")) != string::npos) {
2016-11-02 19:22:45 +00:00
data.erase(0, text(data.substr(0, pos)));
2016-12-14 04:17:18 +00:00
} else {
data.erase(0, text(data.substr(0)));
2016-11-02 19:22:45 +00:00
}
}
2016-11-25 03:10:26 +00:00
if (!m_actions.empty()) {
throw unclosed_actionblocks(to_string(m_actions.size()) + " unclosed action block(s)");
}
2016-11-02 19:22:45 +00:00
}
/**
* Process contents within tag blocks, i.e: %{...}
2016-11-02 19:22:45 +00:00
*/
void parser::codeblock(string&& data, const bar_settings& bar) {
2016-11-02 19:22:45 +00:00
size_t pos;
while (data.length()) {
data = string_util::ltrim(move(data), ' ');
2016-11-02 19:22:45 +00:00
2016-11-25 12:55:15 +00:00
if (data.empty()) {
2016-11-02 19:22:45 +00:00
break;
2016-11-25 12:55:15 +00:00
}
2016-11-02 19:22:45 +00:00
char tag{data[0]};
/*
* Contains the string from the current position to the next space or
* closing curly bracket (})
*
* This may be unsuitable for some tags (e.g. action tag) to use
* These MUST set value to the actual string they parsed from the beginning
* of data (or a string with the same length). The length of value is used
* to progress the cursor further.
*
* example:
*
* data = A1:echo "test": ...}
*
* erase(0,1)
* -> data = 1:echo "test": ...}
*
* case 'A', parse_action_cmd
* -> value = echo "test"
*
* Padding value
* -> value = echo "test"0::
*
* erase(0, value.length())
* -> data = ...}
*
*/
2016-11-02 19:22:45 +00:00
string value;
// Remove the tag
data.erase(0, 1);
2016-11-25 12:55:15 +00:00
if ((pos = data.find_first_of(" }")) != string::npos) {
2016-11-02 19:22:45 +00:00
value = data.substr(0, pos);
2016-11-25 12:55:15 +00:00
} else {
2016-11-02 19:22:45 +00:00
value = data;
2016-11-25 12:55:15 +00:00
}
2016-11-02 19:22:45 +00:00
switch (tag) {
2017-09-07 03:06:13 +00:00
case 'B':
m_sig.emit(change_background{parse_color(value, bar.background)});
2016-11-02 19:22:45 +00:00
break;
2017-09-07 03:06:13 +00:00
case 'F':
m_sig.emit(change_foreground{parse_color(value, bar.foreground)});
break;
case 'T':
m_sig.emit(change_font{parse_fontindex(value)});
2016-11-02 19:22:45 +00:00
break;
case 'U':
2017-09-07 03:06:13 +00:00
m_sig.emit(change_underline{parse_color(value, bar.underline.color)});
m_sig.emit(change_overline{parse_color(value, bar.overline.color)});
2016-11-02 19:22:45 +00:00
break;
case 'u':
2017-09-07 03:06:13 +00:00
m_sig.emit(change_underline{parse_color(value, bar.underline.color)});
2016-11-02 19:22:45 +00:00
break;
case 'o':
2017-09-07 03:06:13 +00:00
m_sig.emit(change_overline{parse_color(value, bar.overline.color)});
break;
case 'R':
m_sig.emit(reverse_colors{});
2016-11-02 19:22:45 +00:00
break;
case 'O':
m_sig.emit(offset_pixel{static_cast<int>(std::strtol(value.c_str(), nullptr, 10))});
2016-11-02 19:22:45 +00:00
break;
case 'l':
m_sig.emit(change_alignment{alignment::LEFT});
2016-11-02 19:22:45 +00:00
break;
case 'c':
m_sig.emit(change_alignment{alignment::CENTER});
2016-11-02 19:22:45 +00:00
break;
case 'r':
m_sig.emit(change_alignment{alignment::RIGHT});
2016-11-02 19:22:45 +00:00
break;
case '+':
m_sig.emit(attribute_set{parse_attr(value[0])});
2016-11-02 19:22:45 +00:00
break;
case '-':
m_sig.emit(attribute_unset{parse_attr(value[0])});
break;
case '!':
m_sig.emit(attribute_toggle{parse_attr(value[0])});
2016-11-02 19:22:45 +00:00
break;
case 'A':
{
bool has_btn_id = (data[0] != ':');
if (isdigit(data[0]) || !has_btn_id) {
value = parse_action_cmd(data.substr(has_btn_id ? 1 : 0));
mousebtn btn = parse_action_btn(data);
m_actions.push_back(static_cast<int>(btn));
// Unescape colons inside command before sending it to the renderer
auto cmd = string_util::replace_all(value, "\\:", ":");
m_sig.emit(action_begin{action{btn, cmd}});
/*
* make sure value has the same length as the inside of the action
* tag which is btn_id + ':' + value + ':'
*/
if (has_btn_id) {
value += "0";
}
value += "::";
} else if (!m_actions.empty()) {
m_sig.emit(action_end{parse_action_btn(value)});
m_actions.pop_back();
2016-11-25 12:55:15 +00:00
}
break;
2016-11-02 19:22:45 +00:00
}
default:
2016-11-25 03:10:26 +00:00
throw unrecognized_token("Unrecognized token '" + string{tag} + "'");
2016-11-02 19:22:45 +00:00
}
2016-11-25 12:55:15 +00:00
if (!data.empty()) {
// Remove the parsed string from data
2016-11-02 19:22:45 +00:00
data.erase(0, !value.empty() ? value.length() : 1);
2016-11-25 12:55:15 +00:00
}
2016-11-02 19:22:45 +00:00
}
}
/**
* Process text contents
2016-11-02 19:22:45 +00:00
*/
2016-12-14 04:17:18 +00:00
size_t parser::text(string&& data) {
#ifdef DEBUG_WHITESPACE
string::size_type p;
while ((p = data.find(' ')) != string::npos) {
data.replace(p, 1, "-"s);
}
#endif
2017-01-19 04:38:42 +00:00
m_sig.emit(signals::parser::text{forward<string>(data)});
return data.size();
2016-11-02 19:22:45 +00:00
}
/**
* Process color hex string and convert it to the correct value
2016-11-02 19:22:45 +00:00
*/
2017-09-07 03:06:13 +00:00
unsigned int parser::parse_color(const string& s, unsigned int fallback) {
2017-01-27 12:46:27 +00:00
if (!s.empty() && s[0] != '-') {
return color_util::parse(s, fallback);
2016-11-25 12:55:15 +00:00
}
2017-01-27 12:46:27 +00:00
return fallback;
2016-11-02 19:22:45 +00:00
}
/**
* Process font index and convert it to the correct value
2016-11-02 19:22:45 +00:00
*/
2017-09-07 03:06:13 +00:00
int parser::parse_fontindex(const string& s) {
if (s.empty() || s[0] == '-') {
return 0;
2016-11-21 14:07:00 +00:00
}
2017-09-07 03:06:13 +00:00
try {
return std::stoul(s, nullptr, 10);
} catch (const std::invalid_argument& err) {
return 0;
2016-11-21 14:07:00 +00:00
}
2016-11-02 19:22:45 +00:00
}
/**
* Process attribute token and convert it to the correct value
2016-11-02 19:22:45 +00:00
*/
attribute parser::parse_attr(const char attr) {
switch (attr) {
2016-11-02 19:22:45 +00:00
case 'o':
return attribute::OVERLINE;
2016-11-02 19:22:45 +00:00
case 'u':
return attribute::UNDERLINE;
default:
2016-11-25 03:10:26 +00:00
throw unrecognized_token("Unrecognized attribute '" + string{attr} + "'");
2016-11-02 19:22:45 +00:00
}
}
/**
* Process action button token and convert it to the correct value
2016-11-02 19:22:45 +00:00
*/
2016-12-14 04:17:18 +00:00
mousebtn parser::parse_action_btn(const string& data) {
2016-11-25 12:55:15 +00:00
if (data[0] == ':') {
2016-11-02 19:22:45 +00:00
return mousebtn::LEFT;
2016-11-25 12:55:15 +00:00
} else if (isdigit(data[0])) {
2016-11-02 19:22:45 +00:00
return static_cast<mousebtn>(data[0] - '0');
2016-11-25 12:55:15 +00:00
} else if (!m_actions.empty()) {
2016-11-02 19:22:45 +00:00
return static_cast<mousebtn>(m_actions.back());
2016-11-25 12:55:15 +00:00
} else {
2016-11-02 19:22:45 +00:00
return mousebtn::NONE;
2016-11-25 12:55:15 +00:00
}
2016-11-02 19:22:45 +00:00
}
/**
* Process action command string
*
* data is the action cmd surrounded by unescaped colons followed by an
* arbitrary string
*
* Returns everything inside the unescaped colons as is
2016-11-02 19:22:45 +00:00
*/
2016-12-14 04:17:18 +00:00
string parser::parse_action_cmd(string&& data) {
if (data[0] != ':') {
2016-11-21 14:07:00 +00:00
return "";
2016-11-25 12:55:15 +00:00
}
2016-12-14 04:17:18 +00:00
size_t end{1};
while ((end = data.find(':', end)) != string::npos && data[end - 1] == '\\') {
end++;
}
2016-12-14 04:17:18 +00:00
if (end == string::npos) {
2016-11-21 14:07:00 +00:00
return "";
2016-11-25 12:55:15 +00:00
}
2016-12-14 04:17:18 +00:00
return data.substr(1, end - 1);
2016-11-02 19:22:45 +00:00
}
2016-11-19 05:22:44 +00:00
POLYBAR_NS_END