Skip to content

Commit

Permalink
libdeng2|Info: Added an include directive
Browse files Browse the repository at this point in the history
The @include directive instructs the Info parser to read the
contents of another Info document and insert its root's elements
into the root of the currently parsed document.
  • Loading branch information
skyjake committed Mar 26, 2014
1 parent 066a6d9 commit 556e9ef
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 21 deletions.
39 changes: 39 additions & 0 deletions doomsday/libdeng2/include/de/data/info.h
Expand Up @@ -223,27 +223,66 @@ class DENG2_PUBLIC Info
*/
Element *findByPath(String const &path) const;

/**
* Moves all elements in this block to the destination block. This block
* will be empty afterwards.
*
* @param destination Block.
*/
void moveContents(BlockElement &destination);

private:
Info &_info;
String _blockType;
Contents _contents;
ContentsInOrder _contentsInOrder;
};

/**
* Interface for objects that provide included document content. @ingroup data
*/
class DENG2_PUBLIC IIncludeFinder
{
public:
virtual ~IIncludeFinder() {}

/**
* Finds an Info document.
* @param includeName Name of the Info document as specified in an \@include
* directive.
* @param from Info document where the inclusion occurs.
* @return Content of the included document.
*/
virtual String findIncludedInfoSource(String const &includeName, Info const &from) const = 0;

/// The included document could not be found. @ingroup errors
DENG2_ERROR(NotFoundError);
};

public:
/// The parser encountered a syntax error in the source file. @ingroup errors
DENG2_ERROR(SyntaxError);

public:
Info();

/**
* Sets the finder for included documents. By default, attempts to locate Info files
* by treating the name of the included file as an absolute path.
*
* @param finder Include finder object. Info does not take ownership.
*/
void setFinder(IIncludeFinder const &finder);

/**
* Parses a string of text as Info source.
*
* @param source Info source text.
*/
Info(String const &source);

Info(String const &source, IIncludeFinder const &finder);

/**
* Sets all the block types whose content is parsed using a script parser.
* The blocks will contain a single KeyElement named "script".
Expand Down
95 changes: 74 additions & 21 deletions doomsday/libdeng2/src/data/info.cpp
Expand Up @@ -21,19 +21,39 @@
#include "de/ScriptLex"
#include "de/Log"
#include "de/LogBuffer"
#include "de/App"
#include <QFile>

using namespace de;

static QString const WHITESPACE = " \t\r\n";
static QString const WHITESPACE_OR_COMMENT = " \t\r\n#";
static QString const TOKEN_BREAKING_CHARS = "#:=$(){}<>,\"" + WHITESPACE;
static QString const INCLUDE_TOKEN = "@include";

DENG2_PIMPL(Info)
{
DENG2_ERROR(OutOfElements);
DENG2_ERROR(EndOfFile);

struct DefaultIncludeFinder : public IIncludeFinder
{
String findIncludedInfoSource(String const &includeName, Info const &) const
{
try
{
return String::fromUtf8(Block(App::rootFolder().locate<File const>(includeName)));
}
catch(Error const &er)
{
throw NotFoundError("Info::DefaultIncludeFinder",
QString("Cannot include '%1': %2")
.arg(includeName)
.arg(er.asText()));
}
}
};

QStringList scriptBlockTypes;
QStringList allowDuplicateBlocksOfType;
String content;
Expand All @@ -43,13 +63,18 @@ DENG2_PIMPL(Info)
int tokenStartOffset;
String currentToken;
BlockElement rootBlock;
DefaultIncludeFinder defaultFinder;
IIncludeFinder const *finder;

typedef Info::Element::Value InfoValue;

Instance(Public *i)
: Base(i),
currentLine(0), cursor(0), tokenStartOffset(0),
rootBlock("", "", *i)
: Base(i)
, currentLine(0)
, cursor(0)
, tokenStartOffset(0)
, rootBlock("", "", *i)
, finder(&defaultFinder)
{
scriptBlockTypes << "script";
}
Expand Down Expand Up @@ -546,13 +571,34 @@ success:;
return block.take();
}

void includeFrom(String const &includeName)
{
DENG2_ASSERT(finder != 0);

Info included(finder->findIncludedInfoSource(includeName, self), *finder);

// Move the contents of the resulting root block to our root block.
included.d->rootBlock.moveContents(rootBlock);
}

void parse(String const &source)
{
init(source);
forever
{
Element *e = parseElement();
if(!e) break;

// If this is an include directive, try to acquire the inclusion and parse it
// instead. Inclusions are only possible at the root level.
if(e->isList() && e->name() == INCLUDE_TOKEN)
{
foreach(Element::Value const &val, e->as<ListElement>().values())
{
includeFrom(val);
}
}

rootBlock.add(e);
}
}
Expand All @@ -577,24 +623,6 @@ void Info::BlockElement::add(Info::Element *elem)
{
DENG2_ASSERT(elem != 0);

/*
/// @todo This check is at the wrong level. Conditions may be applied
/// to resolve duplicates. Check when processing...
// Check for duplicate identifiers in this block.
if(elem->name() && _contents.contains(elem->name()))
{
if(!elem->isBlock() || !info().d->allowDuplicateBlocksOfType.contains(
elem->as<BlockElement>().blockType()))
{
LOG_AS("Info::BlockElement");
LOG_WARNING("Block '%s' already has an element named '%s'")
<< name() << elem->name();
}
}
*/

elem->setParent(this);
_contentsInOrder.append(elem); // owned
if(!elem->name().isEmpty())
Expand Down Expand Up @@ -645,6 +673,18 @@ Info::Element *Info::BlockElement::findByPath(String const &path) const
return e;
}

void Info::BlockElement::moveContents(BlockElement &destination)
{
foreach(Element *e, _contentsInOrder)
{
destination.add(e);
}
_contentsInOrder.clear();
_contents.clear();
}

//---------------------------------------------------------------------------------------

Info::Info() : d(new Instance(this))
{}

Expand All @@ -655,6 +695,19 @@ Info::Info(String const &source)
d.reset(inst.take());
}

Info::Info(String const &source, IIncludeFinder const &finder)
{
QScopedPointer<Instance> inst(new Instance(this)); // parsing may throw exception
inst->finder = &finder;
inst->parse(source);
d.reset(inst.take());
}

void Info::setFinder(IIncludeFinder const &finder)
{
d->finder = &finder;
}

void Info::setScriptBlocks(QStringList const &blocksToParseAsScript)
{
d->scriptBlockTypes = blocksToParseAsScript;
Expand Down

0 comments on commit 556e9ef

Please sign in to comment.