Enhanced BinaryBuilder

When using this BinaryBuilder with a zip archive, it will extract the entries and build a hierarchical namespace with constants declared for each zip entry. Very handy.


/*
  ==============================================================================
   Utility to turn a bunch of binary files into a .cpp file and .h file full of
   data so they can be built directly into an executable.
   Use this code at your own risk! It carries no warranty!
  ==============================================================================
*/
#include "../JuceLibraryCode/JuceHeader.h"
class StringStack : private StringArray {
public:
    int level() const 
    {
        return this->size();   
    }
    bool isEmpty() const
    {
        return (level() == 0);
    }
    void push(const String& value)
    {
        this->add(value);
    }
    void pop()
    {
        if (!isEmpty())
        {
            this->remove(this->size() - 1);
        }
    }
    const String& top() const {
        if (isEmpty())
            return String::empty;
        return this->operator [] (this->size() - 1);
    }
    const String& operator [] (const int index) const {
        return StringArray::operator [] (index);
    }
};
static String indentation(const int n) {
    return String::repeatedString("  ", n);
}

//==============================================================================
static int addFile (const File& file,
                    const String& classname,
                    OutputStream& headerStream,
                    OutputStream& cppStream)
{
    MemoryBlock mb;
    file.loadFileAsData (mb);
    const String name (file.getFileName().toLowerCase()
                           .replaceCharacter (' ', '_')
                           .replaceCharacter ('.', '_')
                           .retainCharacters ("abcdefghijklmnopqrstuvwxyz_0123456789"));
    std::cout << "Adding " << name << ": "
              << (int) mb.getSize() << " bytes" << std::endl;
    // Add a namespace for the file entry
    headerStream << indentation(1) << "namespace " << name << "\r\n"
                 << indentation(1) << "{\r\n";
    headerStream << indentation(2) << "extern const char* data;\r\n"
                 << indentation(2) << "const int          size = " << (int) mb.getSize() << ";\r\n\r\n";
    static int tempNum = 0;
    cppStream << "static const unsigned char temp" << ++tempNum << "[] = {";
    size_t i = 0;
    const uint8* const data = (const uint8*) mb.getData();
    while (i < mb.getSize() - 1)
    {
        if ((i % 40) != 39)
            cppStream << (int) data[i] << ",";
        else
            cppStream << (int) data[i] << ",\r\n  ";
        ++i;
    }
    cppStream << (int) data[i] << ",0,0};\r\n";
    cppStream << "const char* " << classname << "::" << name << "::data"
              << " = (const char*) temp" << tempNum << ";\r\n\r\n";
    // If zip file, add all entries as an enum, makes it easy (and "code safe") to use
    // with ZipArchive
    if (file.getFileExtension().endsWithIgnoreCase("zip"))
    {
        ZipFile zip(file);
        zip.sortEntriesByFilename();
        if (zip.getNumEntries() > 0)
        {
            headerStream << indentation(2) << "// Adding easy access constants for sorted zip file entries\r\n";
            StringStack stack;
            for (int i = 0; i < zip.getNumEntries(); ++i)
            {
                const ZipFile::ZipEntry* pEntry = zip.getEntry(i);
                const StringArray pathEntries = StringArray::fromTokens(pEntry->filename, "/", String::empty);
                for (int j = 0; j < pathEntries.size() - 1; ++j)
                {
                    const String entry (pathEntries[j]
                        .replaceCharacter (' ', '_')
                        .replaceCharacter ('.', '_')
                        .retainCharacters ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"));
stack_loop:
                    if (stack.level() < (j + 1))
                    {
                        // Add namespace
                        headerStream << indentation(stack.level() + 2) << "namespace " << entry << "\r\n";
                        headerStream << indentation(stack.level() + 2) << "{\r\n";
                        stack.push(entry);
                    }
                    else if (stack[j] != entry)
                    {
                        while (!stack.isEmpty() && stack.top() != entry)
                        {
                            // Remove namespace
                            stack.pop();
                            headerStream << indentation(stack.level() + 2) << "}\r\n";
                            goto stack_loop;
                        }
                    }
                }
                while (stack.level() > pathEntries.size() - 1)
                {
                    // Remove namespace
                    stack.pop();
                    headerStream << indentation(stack.level() + 2) << "}\r\n";
                }
                const String fileNameEntry = pathEntries[pathEntries.size()-1].replaceCharacters(" .-", "___");
                if (fileNameEntry.isNotEmpty())
                {
                    headerStream << indentation(stack.level() + 2) << "const unsigned " << fileNameEntry << " = " << i << ";\r\n";
                }
            }
            while (!stack.isEmpty())
            {
                // Remove namespace
                stack.pop();
                headerStream << indentation(stack.level() + 2) << "}\r\n";
            }
        }
    }
    // Exit namespace for the file entry
    headerStream << indentation(1) << "}\r\n";
    return mb.getSize();
}
static bool isHiddenFile (const File& f, const File& root)
{
    return f.getFileName().endsWithIgnoreCase (".scc")
         || f.getFileName() == ".svn"
         || f.getFileName().startsWithChar ('.')
         || (f.getSize() == 0 && ! f.isDirectory())
         || (f.getParentDirectory() != root && isHiddenFile (f.getParentDirectory(), root));
}

//==============================================================================
int main (int argc, char* argv[])
{
    std::cout << "\n BinaryBuilder! Copyright 2007 by Julian Storer - www.juce.com\n\n";
    if (argc < 4 || argc > 5)
    {
        std::cout << " Usage: BinaryBuilder  sourcedirectory targetdirectory targetclassname [optional wildcard pattern]\n\n"
                     " BinaryBuilder will find all files in the source directory, and encode them\n"
                     " into two files called (targetclassname).cpp and (targetclassname).h, which it\n"
                     " will write into the target directory supplied.\n\n"
                     " Any files in sub-directories of the source directory will be put into the\n"
                     " resultant class, but #ifdef'ed out using the name of the sub-directory (hard to\n"
                     " explain, but obvious when you try it...)\n";
        return 0;
    }
    const File sourceDirectory (File::getCurrentWorkingDirectory()
                                     .getChildFile (String (argv[1]).unquoted()));
    if (! sourceDirectory.isDirectory())
    {
        std::cout << "Source directory doesn't exist: "
                  << sourceDirectory.getFullPathName()
                  << std::endl << std::endl;
        return 0;
    }
    const File destDirectory (File::getCurrentWorkingDirectory()
                                   .getChildFile (String (argv[2]).unquoted()));
    if (! destDirectory.isDirectory())
    {
        // No need to fail because destination directory doesn't exist, only if creating it
        // fails.
        if (! descDirectory.createDirectory())
        {
            std::cout << "Destination directory doesn't exist: "
                    << destDirectory.getFullPathName() << std::endl << std::endl;
            return 0;
        }
    }
    String className (argv[3]);
    className = className.trim();
    const File headerFile (destDirectory.getChildFile (className).withFileExtension (".h"));
    const File cppFile    (destDirectory.getChildFile (className).withFileExtension (".cpp"));
    std::cout << "Creating " << headerFile.getFullPathName()
              << " and " << cppFile.getFullPathName()
              << " from files in " << sourceDirectory.getFullPathName()
              << "..." << std::endl << std::endl;
    Array <File> files;
    sourceDirectory.findChildFiles (files, File::findFiles, true,
                                    (argc > 4) ? argv[4] : "*");
    if (files.size() == 0)
    {
        std::cout << "Didn't find any source files in: "
                  << sourceDirectory.getFullPathName() << std::endl << std::endl;
        return 0;
    }
    headerFile.deleteFile();
    cppFile.deleteFile();
    ScopedPointer <OutputStream> header (headerFile.createOutputStream());
    if (header == 0)
    {
        std::cout << "Couldn't open "
                  << headerFile.getFullPathName() << " for writing" << std::endl << std::endl;
        return 0;
    }
    ScopedPointer <OutputStream> cpp (cppFile.createOutputStream());
    if (cpp == 0)
    {
        std::cout << "Couldn't open "
                  << cppFile.getFullPathName() << " for writing" << std::endl << std::endl;
        return 0;
    }
    *header << "/* (Auto-generated binary data file). */\r\n\r\n"
               "#ifndef BINARY_" << className.toUpperCase() << "_H\r\n"
               "#define BINARY_" << className.toUpperCase() << "_H\r\n\r\n"
               "namespace " << className << "\r\n"
               "{\r\n";
    *cpp << "/* (Auto-generated binary data file). */\r\n\r\n"
            "#include \"" << className << ".h\"\r\n\r\n";
    int totalBytes = 0;
    for (int i = 0; i < files.size(); ++i)
    {
        const File file (files[i]);
        // (avoid source control files and hidden files..)
        if (! isHiddenFile (file, sourceDirectory))
        {
            if (file.getParentDirectory() != sourceDirectory)
            {
                *header << "  #ifdef " << file.getParentDirectory().getFileName().toUpperCase() << "\r\n";
                *cpp << "#ifdef " << file.getParentDirectory().getFileName().toUpperCase() << "\r\n";
                totalBytes += addFile (file, className, *header, *cpp);
                *header << "  #endif\r\n";
                *cpp << "#endif\r\n";
            }
            else
            {
                totalBytes += addFile (file, className, *header, *cpp);
            }
        }
    }
    *header << "}\r\n\r\n"
               "#endif\r\n";
    header = 0;
    cpp = 0;
    std::cout << std::endl << " Total size of binary data: " << totalBytes << " bytes" << std::endl;
    return 0;
}

Nice!