Skip to content

Commit

Permalink
Use AST for checking if a filename is valid or not
Browse files Browse the repository at this point in the history
  • Loading branch information
Bionus committed Jan 31, 2019
1 parent 4970f02 commit 9e5beb9
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 51 deletions.
49 changes: 49 additions & 0 deletions lib/src/filename/ast-filename.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "filename/ast-filename.h"
#include "filename/filename-parser.h"
#include "filename/filename-resolution-visitor.h"
#include "filename/ast/filename-node-root.h"


AstFilename::AstFilename(const QString &str)
: m_parser(str)
{}

void AstFilename::parse()
{
auto ast = m_parser.parseRoot();
if (m_parser.error().isEmpty()) {
m_ast = ast;

FilenameResolutionVisitor resolutionVisitor;
m_tokens = resolutionVisitor.run(*m_ast);
}

m_parsed = true;
}

const QString &AstFilename::error()
{
if (!m_parsed) {
parse();
}

return m_parser.error();
}

FilenameNodeRoot *AstFilename::ast()
{
if (!m_parsed) {
parse();
}

return m_ast;
}

const QSet<QString> &AstFilename::tokens()
{
if (!m_parsed) {
parse();
}

return m_tokens;
}
30 changes: 30 additions & 0 deletions lib/src/filename/ast-filename.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef AST_FILENAME_H
#define AST_FILENAME_H

#include <QSet>
#include <QString>
#include "filename/filename-parser.h"


struct FilenameNodeRoot;

class AstFilename
{
public:
explicit AstFilename(const QString &str);
const QString &error();
FilenameNodeRoot *ast();
const QSet<QString> &tokens();

protected:
void parse();

private:
FilenameParser m_parser;
bool m_parsed = false;

FilenameNodeRoot *m_ast = nullptr;
QSet<QString> m_tokens;
};

#endif // AST_FILENAME_H
33 changes: 3 additions & 30 deletions lib/src/filename/filename-cache.h
Original file line number Diff line number Diff line change
@@ -1,39 +1,12 @@
#ifndef FILENAME_CACHE_H
#define FILENAME_CACHE_H

#include <QMap>
#include <QSharedPointer>
#include <QString>
#include "filename/ast/filename-node-root.h"
#include "filename/filename-parser.h"
#include "flyweight-cache.h"
#include "filename/ast-filename.h"


class FilenameCache
class FilenameCache : public FlyweightCache<QString, AstFilename>
{
public:
static QSharedPointer<FilenameNodeRoot> Get(const QString &key);

private:
static QMap<QString, QWeakPointer<FilenameNodeRoot>> Map;
};


QMap<QString, QWeakPointer<FilenameNodeRoot>> FilenameCache::Map;

QSharedPointer<FilenameNodeRoot> FilenameCache::Get(const QString &key)
{
auto it = Map.find(key);
if (it != Map.end()) {
return QSharedPointer<FilenameNodeRoot>(it.value());
}

FilenameParser parser(key);
const QSharedPointer<FilenameNodeRoot> shared(parser.parseRoot());

const QWeakPointer<FilenameNodeRoot> weak(shared);
Map.insert(key, weak);

return shared;
}

#endif // FILENAME_CACHE_H
2 changes: 1 addition & 1 deletion lib/src/filename/filename-parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ FilenameParser::FilenameParser(QString str)
: m_str(std::move(str)), m_index(0)
{}

QString FilenameParser::error() const
const QString &FilenameParser::error() const
{
return m_error;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/filename/filename-parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class FilenameParser
{
public:
explicit FilenameParser(QString str);
QString error() const;
const QString &error() const;

FilenameNodeRoot *parseRoot();
FilenameNodeCondition *parseCondition();
Expand Down
3 changes: 2 additions & 1 deletion lib/src/flyweight-cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <QMap>
#include <QSharedPointer>
#include <QWeakPointer>


template <class K, class T>
Expand All @@ -23,7 +24,7 @@ template <class K, class T>
QSharedPointer<T> FlyweightCache<K, T>::Get(const K &key)
{
auto it = Map.find(key);
if (it != Map.end()) {
if (it != Map.end() && !it.value().isNull()) {
return QSharedPointer<T>(it.value());
}

Expand Down
43 changes: 28 additions & 15 deletions lib/src/models/filename.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <QRegularExpression>
#include <QSettings>
#include <algorithm>
#include "filename/ast-filename.h"
#include "filename/filename-cache.h"
#include "functions.h"
#include "loader/token.h"
#include "models/api/api.h"
Expand All @@ -13,9 +15,15 @@
#include "models/site.h"


Filename::Filename()
: Filename("")
{}

Filename::Filename(QString format)
: m_format(std::move(format))
{}
{
m_ast = FilenameCache::Get(m_format);
}

QString Filename::expandConditionals(const QString &text, const QStringList &tags, const QMap<QString, Token> &tokens, QSettings *settings, int depth) const
{
Expand Down Expand Up @@ -211,7 +219,7 @@ QList<QMap<QString, Token>> Filename::expandTokens(const QString &filename, QMap
continue;
}

const bool hasToken = !isJavascript && filename.contains(QRegularExpression("%" + key + "(?::[^%]+)?%"));
const bool hasToken = !isJavascript && m_ast->tokens().contains(key);
const bool hasVar = isJavascript && filename.contains(key);
if (!hasToken && !hasVar) {
continue;
Expand Down Expand Up @@ -583,13 +591,15 @@ bool Filename::isValid(Profile *profile, QString *error) const
return true;
}

const auto &toks = m_ast->tokens();

// Field must end by an extension
if (!m_format.endsWith(".%ext%")) {
return returnError(orange.arg(QObject::tr("Your filename doesn't ends by an extension, symbolized by %ext%! You may not be able to open saved files.")), error);
}

// Field must contain an unique token
if (!m_format.contains("%md5%") && !m_format.contains("%id%") && !m_format.contains("%num%")) {
if (!toks.contains("md5") && !toks.contains("id") && !toks.contains("num")) {
return returnError(orange.arg(QObject::tr("Your filename is not unique to each image and an image may overwrite a previous one at saving! You should use%md5%, which is unique to each image, to avoid this inconvenience.")), error);
}

Expand Down Expand Up @@ -623,7 +633,7 @@ bool Filename::isValid(Profile *profile, QString *error) const
#endif

// Check if code is unique
if (!m_format.contains("%md5%") && !m_format.contains("%website%") && !m_format.contains("%websitename%") && !m_format.contains("%count%") && m_format.contains("%id%")) {
if (!toks.contains("md5") && !toks.contains("website") && !toks.contains("websitename") && !toks.contains("count") && toks.contains("id")) {
return returnError(green.arg(QObject::tr("You have chosen to use the %id% token. Know that it is only unique for a selected site. The same ID can identify different images depending on the site.")), error);
}

Expand All @@ -634,16 +644,17 @@ bool Filename::isValid(Profile *profile, QString *error) const

bool Filename::needTemporaryFile(const QMap<QString, Token> &tokens) const
{
static const QRegularExpression regexMd5("%md5(?::([^%]+))?%");
static const QRegularExpression regexFilesize("%filesize(?::([^%]+))?%");
static const QRegularExpression regexWidth("%width(?::([^%]+))?%");
static const QRegularExpression regexHeight("%height(?::([^%]+))?%");
if (m_format.startsWith("javascript:")) {
return false;
}

const auto &toks = m_ast->tokens();

return (
(m_format.contains(regexMd5) && (!tokens.contains("md5") || tokens["md5"].value().toString().isEmpty())) ||
(m_format.contains(regexFilesize) && (!tokens.contains("filesize") || tokens["filesize"].value().toInt() <= 0)) ||
(m_format.contains(regexWidth) && (!tokens.contains("width") || tokens["width"].value().toInt() <= 0)) ||
(m_format.contains(regexHeight) && (!tokens.contains("height") || tokens["height"].value().toInt() <= 0))
(toks.contains("md5") && (!tokens.contains("md5") || tokens["md5"].value().toString().isEmpty())) ||
(toks.contains("filesize") && (!tokens.contains("filesize") || tokens["filesize"].value().toInt() <= 0)) ||
(toks.contains("width") && (!tokens.contains("width") || tokens["width"].value().toInt() <= 0)) ||
(toks.contains("height") && (!tokens.contains("height") || tokens["height"].value().toInt() <= 0))
);
}

Expand All @@ -668,20 +679,22 @@ int Filename::needExactTags(const QStringList &forcedTokens) const
return 2;
}

const auto &toks = m_ast->tokens();

// If we need the filename and it is returned from the details page
if (m_format.contains(QRegularExpression("%filename(?::([^%]+))?%")) && forcedTokens.contains("filename")) {
if (toks.contains("filename") && forcedTokens.contains("filename")) {
return 2;
}

// If we need the date and it is returned from the details page
if (m_format.contains(QRegularExpression("%date(?::([^%]+))?%")) && forcedTokens.contains("date")) {
if (toks.contains("date") && forcedTokens.contains("date")) {
return 2;
}

// The filename contains one of the special tags
QStringList forbidden = QStringList() << "artist" << "copyright" << "character" << "model" << "photo_set" << "species" << "meta" << "general";
for (const QString &token : forbidden) {
if (m_format.contains(QRegularExpression("%" + token + "(?::([^%]+))?%"))) {
if (toks.contains(token)) {
return 1;
}
}
Expand Down
9 changes: 6 additions & 3 deletions lib/src/models/filename.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@

#include <QList>
#include <QMap>
#include <QSharedPointer>
#include <QString>
#include <QStringList>
#include <QVariant>


class Site;
class AstFilename;
class Image;
class Profile;
class QJSEngine;
class QJSValue;
class QSettings;
class Profile;
class Site;
class Token;

class Filename
Expand All @@ -36,7 +38,7 @@ class Filename
};
Q_DECLARE_FLAGS(PathFlags, PathFlag)

Filename() = default;
Filename();
explicit Filename(QString format);
QString format() const;
void setFormat(const QString &format);
Expand Down Expand Up @@ -65,6 +67,7 @@ class Filename

private:
QString m_format;
QSharedPointer<AstFilename> m_ast;
QString (*m_escapeMethod)(const QVariant &) = nullptr;
};

Expand Down
9 changes: 9 additions & 0 deletions tests/src/filename/filename-parser-test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
#include "filename/ast/filename-node-variable.h"


void FilenameParserTest::testParseEmpty()
{
FilenameParser parser("");
auto filename = parser.parseRoot();
QVERIFY(parser.error().isEmpty());

QVERIFY(filename->exprs.isEmpty());
}

void FilenameParserTest::testParseText()
{
FilenameParser parser("image.png");
Expand Down
1 change: 1 addition & 0 deletions tests/src/filename/filename-parser-test.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class FilenameParserTest : public TestSuite

private slots:
// Basic
void testParseEmpty();
void testParseText();
void testParseVariable();
void testParseVariableWithOptions();
Expand Down

0 comments on commit 9e5beb9

Please sign in to comment.