vnote/src/utils/vmetawordmanager.cpp
2018-05-23 20:08:58 +08:00

675 lines
19 KiB
C++

#include "vmetawordmanager.h"
#include <QDebug>
#include <QWidget>
#include <QApplication>
#include <QToolTip>
#include <QFileInfo>
#include "vconfigmanager.h"
#include "vmainwindow.h"
extern VConfigManager *g_config;
extern VMainWindow *g_mainWin;
// Used as the function template for some date/time related meta words.
static QString formattedDateTime(const VMetaWord *p_metaWord,
const QString &p_format)
{
return p_metaWord->getManager()->getDateTime().toString(p_format);
}
static QString allMetaWordsInfo(const VMetaWord *p_metaWord)
{
QString msg = QObject::tr("All magic words:");
const VMetaWordManager *mgr = p_metaWord->getManager();
QList<QString> keys = mgr->getAllMetaWords().keys();
keys.sort(Qt::CaseInsensitive);
for (auto const & key : keys) {
const VMetaWord *word = mgr->findMetaWord(key);
Q_ASSERT(word);
msg += QString("\n%1:\t%2").arg(word->getWord()).arg(word->getDefinition());
}
QWidget *focusWid = QApplication::focusWidget();
if (focusWid) {
QPoint pos = focusWid->mapToGlobal(QPoint(0, focusWid->height()));
QToolTip::showText(pos, msg, focusWid);
}
// Just return the same word.
return QString("%1help%1").arg(VMetaWordManager::c_delimiter);
}
const QChar VMetaWordManager::c_delimiter = '%';
VMetaWordManager::VMetaWordManager(QObject *p_parent)
: QObject(p_parent),
m_initialized(false)
{
}
void VMetaWordManager::init()
{
if (m_initialized) {
return;
}
m_initialized = true;
using namespace std::placeholders;
// %d%.
addMetaWord(MetaWordType::FunctionBased,
"d",
tr("the day as number without a leading zero (`1` to `31`)"),
std::bind(formattedDateTime, _1, "d"));
// %dd%.
addMetaWord(MetaWordType::FunctionBased,
"dd",
tr("the day as number with a leading zero (`01` to `31`)"),
std::bind(formattedDateTime, _1, "dd"));
// %ddd%.
addMetaWord(MetaWordType::FunctionBased,
"ddd",
tr("the abbreviated localized day name (e.g. `Mon` to `Sun`)"),
std::bind(formattedDateTime, _1, "ddd"));
// %dddd%.
addMetaWord(MetaWordType::FunctionBased,
"dddd",
tr("the long localized day name (e.g. `Monday` to `Sunday`)"),
std::bind(formattedDateTime, _1, "dddd"));
// %M%.
addMetaWord(MetaWordType::FunctionBased,
"M",
tr("the month as number without a leading zero (`1` to `12`)"),
std::bind(formattedDateTime, _1, "M"));
// %MM%.
addMetaWord(MetaWordType::FunctionBased,
"MM",
tr("the month as number with a leading zero (`01` to `12`)"),
std::bind(formattedDateTime, _1, "MM"));
// %MMM%.
addMetaWord(MetaWordType::FunctionBased,
"MMM",
tr("the abbreviated localized month name (e.g. `Jan` to `Dec`)"),
std::bind(formattedDateTime, _1, "MMM"));
// %MMMM%.
addMetaWord(MetaWordType::FunctionBased,
"MMMM",
tr("the long localized month name (e.g. `January` to `December`)"),
std::bind(formattedDateTime, _1, "MMMM"));
// %yy%.
addMetaWord(MetaWordType::FunctionBased,
"yy",
tr("the year as two digit number (`00` to `99`)"),
std::bind(formattedDateTime, _1, "yy"));
// %yyyy%.
addMetaWord(MetaWordType::FunctionBased,
"yyyy",
tr("the year as four digit number"),
std::bind(formattedDateTime, _1, "yyyy"));
// %h%.
addMetaWord(MetaWordType::FunctionBased,
"h",
tr("the hour without a leading zero (`0` to `23` or `1` to `12` if AM/PM display)"),
std::bind(formattedDateTime, _1, "h"));
// %hh%.
addMetaWord(MetaWordType::FunctionBased,
"hh",
tr("the hour with a leading zero (`00` to `23` or `01` to `12` if AM/PM display)"),
std::bind(formattedDateTime, _1, "hh"));
// %H%.
addMetaWord(MetaWordType::FunctionBased,
"H",
tr("the hour without a leading zero (`0` to `23` even with AM/PM display)"),
std::bind(formattedDateTime, _1, "H"));
// %HH%.
addMetaWord(MetaWordType::FunctionBased,
"HH",
tr("the hour with a leading zero (`00` to `23` even with AM/PM display)"),
std::bind(formattedDateTime, _1, "HH"));
// %m%.
addMetaWord(MetaWordType::FunctionBased,
"m",
tr("the minute without a leading zero (`0` to `59`)"),
std::bind(formattedDateTime, _1, "m"));
// %mm%.
addMetaWord(MetaWordType::FunctionBased,
"mm",
tr("the minute with a leading zero (`00` to `59`)"),
std::bind(formattedDateTime, _1, "mm"));
// %s%.
addMetaWord(MetaWordType::FunctionBased,
"s",
tr("the second without a leading zero (`0` to `59`)"),
std::bind(formattedDateTime, _1, "s"));
// %ss%.
addMetaWord(MetaWordType::FunctionBased,
"ss",
tr("the second with a leading zero (`00` to `59`)"),
std::bind(formattedDateTime, _1, "ss"));
// %z%.
addMetaWord(MetaWordType::FunctionBased,
"z",
tr("the milliseconds without leading zeroes (`0` to `999`)"),
std::bind(formattedDateTime, _1, "z"));
// %zzz%.
addMetaWord(MetaWordType::FunctionBased,
"zzz",
tr("the milliseconds with leading zeroes (`000` to `999`)"),
std::bind(formattedDateTime, _1, "zzz"));
// %AP%.
addMetaWord(MetaWordType::FunctionBased,
"AP",
tr("use AM/PM display (`AM` or `PM`)"),
std::bind(formattedDateTime, _1, "AP"));
// %A%.
addMetaWord(MetaWordType::FunctionBased,
"A",
tr("use AM/PM display (`AM` or `PM`)"),
std::bind(formattedDateTime, _1, "A"));
// %ap%.
addMetaWord(MetaWordType::FunctionBased,
"ap",
tr("use am/pm display (`am` or `pm`)"),
std::bind(formattedDateTime, _1, "ap"));
// %a%.
addMetaWord(MetaWordType::FunctionBased,
"a",
tr("use am/pm display (`am` or `pm`)"),
std::bind(formattedDateTime, _1, "a"));
// %t%.
addMetaWord(MetaWordType::FunctionBased,
"t",
tr("the timezone (e.g. `CEST`)"),
std::bind(formattedDateTime, _1, "t"));
// %random%.
addMetaWord(MetaWordType::FunctionBased,
"random",
tr("a random number"),
[](const VMetaWord *) {
return QString::number(qrand());
});
// %random_d%.
addMetaWord(MetaWordType::Dynamic,
"random_d",
tr("dynamic version of `random`"),
[](const VMetaWord *) {
return QString::number(qrand());
});
// %date%.
addMetaWord(MetaWordType::Compound,
"date",
QString("%1yyyy%1-%1MM%1-%1dd%1").arg(c_delimiter));
// %da%.
addMetaWord(MetaWordType::Compound,
"da",
QString("%1yyyy%1%1MM%1%1dd%1").arg(c_delimiter));
// %time%.
addMetaWord(MetaWordType::Compound,
"time",
QString("%1hh%1:%1mm%1:%1ss%1").arg(c_delimiter));
// %datetime%.
addMetaWord(MetaWordType::Compound,
"datetime",
QString("%1date%1 %1time%1").arg(c_delimiter));
// %dt%.
addMetaWord(MetaWordType::Compound,
"dt",
QString("%1da%1-%1time%1").arg(c_delimiter));
// %note%.
addMetaWord(MetaWordType::FunctionBased,
"note",
tr("name of current note"),
[](const VMetaWord *) {
const VFile *file = g_mainWin->getCurrentFile();
if (file) {
return file->getName();
}
return QString();
});
// %no%.
addMetaWord(MetaWordType::FunctionBased,
"no",
tr("complete base name of current note"),
[](const VMetaWord *) {
const VFile *file = g_mainWin->getCurrentFile();
if (file) {
return QFileInfo(file->getName()).completeBaseName();
}
return QString();
});
// Custom meta words.
initCustomMetaWords();
// %help% to print all metaword info.
addMetaWord(MetaWordType::FunctionBased,
"help",
tr("information about all defined magic words"),
allMetaWordsInfo);
}
void VMetaWordManager::initCustomMetaWords()
{
QVector<VMagicWord> words = g_config->getCustomMagicWords();
for (auto const & item : words) {
addMetaWord(MetaWordType::Compound,
item.m_name,
item.m_definition);
}
}
QString VMetaWordManager::evaluate(const QString &p_text,
const QHash<QString, QString> &p_overriddenWords) const
{
if (p_text.isEmpty()) {
return p_text;
}
const_cast<VMetaWordManager *>(this)->init();
// Update datetime for later parse.
const_cast<VMetaWordManager *>(this)->m_dateTime = QDateTime::currentDateTime();
// Update overriden table.
const_cast<VMetaWordManager *>(this)->m_overriddenWords = p_overriddenWords;
// Treat the text as a Compound meta word.
const QString tmpWord("vnote_tmp_metaword");
Q_ASSERT(!contains(tmpWord));
VMetaWord metaWord(this,
MetaWordType::Compound,
tmpWord,
p_text,
nullptr,
true);
QString val;
if (metaWord.isValid()) {
val = metaWord.evaluate();
} else {
val = p_text;
}
const_cast<VMetaWordManager *>(this)->m_overriddenWords.clear();
return val;
}
bool VMetaWordManager::contains(const QString &p_word) const
{
const_cast<VMetaWordManager *>(this)->init();
return m_metaWords.contains(p_word);
}
const VMetaWord *VMetaWordManager::findMetaWord(const QString &p_word) const
{
const_cast<VMetaWordManager *>(this)->init();
auto it = m_metaWords.find(p_word);
if (it != m_metaWords.end()) {
return &it.value();
}
return NULL;
}
void VMetaWordManager::addMetaWord(MetaWordType p_type,
const QString &p_word,
const QString &p_definition,
MetaWordFunc p_function)
{
if (p_word.isEmpty() || contains(p_word)) {
return;
}
VMetaWord metaWord(this,
p_type,
p_word,
p_definition,
p_function);
if (metaWord.isValid()) {
m_metaWords.insert(p_word, metaWord);
qDebug() << QString("MetaWord %1%2%1[%3] added")
.arg(c_delimiter).arg(p_word).arg(p_definition);
}
}
bool VMetaWordManager::findOverriddenValue(const QString &p_word,
QString &p_value) const
{
const_cast<VMetaWordManager *>(this)->init();
auto it = m_overriddenWords.find(p_word);
if (it != m_overriddenWords.end()) {
p_value = it.value();
return true;
}
return false;
}
VMetaWord::VMetaWord(const VMetaWordManager *p_manager,
MetaWordType p_type,
const QString &p_word,
const QString &p_definition,
MetaWordFunc p_function,
bool p_allowAllSpaces)
: m_manager(p_manager),
m_type(p_type),
m_word(p_word),
m_definition(p_definition),
m_valid(false)
{
m_function = p_function;
if (checkType(MetaWordType::Simple)
|| checkType(MetaWordType::Compound)) {
Q_ASSERT(!m_function);
} else {
Q_ASSERT(m_function);
}
checkAndParseDefinition(p_allowAllSpaces);
}
bool VMetaWord::checkType(MetaWordType p_type)
{
return m_type == p_type;
}
void VMetaWord::checkAndParseDefinition(bool p_allowAllSpaces)
{
if (m_word.contains(VMetaWordManager::c_delimiter)) {
m_valid = false;
return;
}
m_valid = true;
m_tokens.clear();
// We do not accept \n and \t in the definition.
QRegExp defReg("[\\S ]*");
if (!defReg.exactMatch(m_definition) && !p_allowAllSpaces) {
m_valid = false;
return;
}
if (checkType(MetaWordType::FunctionBased)
|| checkType(MetaWordType::Dynamic)) {
if (!m_function) {
m_valid = false;
}
} else if (checkType(MetaWordType::Compound)) {
m_tokens = parseToTokens(m_definition);
if (m_tokens.isEmpty()) {
m_valid = false;
return;
}
for (auto const & to : m_tokens) {
if (to.isMetaWord()) {
if (!m_manager->contains(to.m_value)) {
// Dependency not defined.
m_valid = false;
break;
}
}
}
}
}
bool VMetaWord::isValid() const
{
return m_valid;
}
QString VMetaWord::evaluate() const
{
Q_ASSERT(m_valid);
// Check overridden table first.
QString overriddenVal;
if (m_manager->findOverriddenValue(m_word, overriddenVal)) {
return overriddenVal;
}
switch (m_type) {
case MetaWordType::Simple:
return m_definition;
case MetaWordType::FunctionBased:
case MetaWordType::Dynamic:
return m_function(this);
case MetaWordType::Compound:
{
QHash<QString, QString> cache;
return evaluateTokens(m_tokens, cache);
}
default:
return "";
}
}
MetaWordType VMetaWord::getType() const
{
return m_type;
}
const QString &VMetaWord::getWord() const
{
return m_word;
}
const QString &VMetaWord::getDefinition() const
{
return m_definition;
}
const VMetaWordManager *VMetaWord::getManager() const
{
return m_manager;
}
QString VMetaWord::toString() const
{
QChar typeChar('U');
switch (m_type) {
case MetaWordType::Simple:
typeChar = 'S';
break;
case MetaWordType::FunctionBased:
typeChar = 'F';
break;
case MetaWordType::Dynamic:
typeChar = 'D';
break;
case MetaWordType::Compound:
typeChar = 'C';
break;
default:
break;
}
return QString("%1%2%1[%3]: %4").arg(VMetaWordManager::c_delimiter)
.arg(m_word)
.arg(typeChar)
.arg(m_definition);
}
QVector<VMetaWord::Token> VMetaWord::parseToTokens(const QString &p_text)
{
QVector<Token> tokens;
TokenType type = TokenType::Raw;
QString value;
value.reserve(p_text.size());
for (int idx = 0; idx < p_text.size(); ++idx) {
const QChar &ch = p_text[idx];
if (ch == VMetaWordManager::c_delimiter) {
// Check if it is single or double.
int next = idx + 1;
if (next == p_text.size()
|| p_text[next] != VMetaWordManager::c_delimiter) {
// Single delimiter.
if (type == TokenType::Raw) {
// End of a raw token, begin of a MetaWord token.
if (!value.isEmpty()) {
tokens.push_back(Token(type, value));
}
type = TokenType::MetaWord;
} else {
// End of a MetaWord token, begin of a Raw token.
Q_ASSERT(!value.isEmpty());
tokens.push_back(Token(type, value));
type = TokenType::Raw;
}
value.clear();
} else {
// Double delimiter.
// If now is parsing a MetaWord token, treat the first delimiter
// as the end of a token.
// Otherwise, store one single delimiter in value and skip next char.
if (type == TokenType::MetaWord) {
Q_ASSERT(!value.isEmpty());
tokens.push_back(Token(type, value));
type = TokenType::Raw;
value.clear();
} else {
value.push_back(ch);
++idx;
}
}
} else {
// Push ch in value.
value.push_back(ch);
}
}
if (!value.isEmpty()) {
if (type == TokenType::Raw) {
tokens.push_back(Token(type, value));
} else {
// An imcomplete metaword token.
// Treat it as raw.
tokens.push_back(Token(TokenType::Raw, "%" + value));
}
value.clear();
}
return tokens;
}
QString VMetaWord::evaluateTokens(const QVector<VMetaWord::Token> &p_tokens,
QHash<QString, QString> &p_cache) const
{
QString val;
for (auto const & to : p_tokens) {
switch (to.m_type) {
case TokenType::Raw:
val += to.m_value;
break;
case TokenType::MetaWord:
{
const VMetaWord *metaWord = m_manager->findMetaWord(to.m_value);
if (!metaWord) {
// Invalid meta word. Treat it as literal value.
val += VMetaWordManager::c_delimiter + to.m_value + VMetaWordManager::c_delimiter;
break;
}
QString wordVal;
switch (metaWord->getType()) {
case MetaWordType::FunctionBased:
{
auto it = p_cache.find(metaWord->getWord());
if (it != p_cache.end()) {
// Find it in the cache.
wordVal = it.value();
} else {
// First evaluate this meta word.
wordVal = metaWord->evaluate();
p_cache.insert(metaWord->getWord(), wordVal);
}
break;
}
case MetaWordType::Compound:
wordVal = evaluateTokens(metaWord->m_tokens, p_cache);
break;
default:
wordVal = metaWord->evaluate();
break;
}
val += wordVal;
break;
}
default:
Q_ASSERT(false);
break;
}
}
return val;
}