root/3rdparty/proj2make/proj2make.cpp
/*
 * Copyright 2012 Aleksas Pantechovskis, <alexp.frl@gmail.com>
 * All rights reserved. Distributed under the terms of the MIT License.
 */

#include <exception>
#include <fstream>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>

#include <ByteOrder.h>
#include <FindDirectory.h>
#include <Path.h>
#include <String.h>
#include <TypeConstants.h>


using namespace std;


const char* kUsageMessage = \
        "proj2make usage:\n"
        "# proj2make <projPath> [makePath]\n"
        "# if makePath parameter doesn't specified makefile will be created in\n"
        "#      the same directory as .proj file\n"
        "# example: proj2make /boot/home/myprog/myprog.proj\n";

fstream gProjFile;
uint32 gProjLength;
uint8* gProjData;

fstream gMakeFile;

fstream gTemplateFile;

string gSPthString;
string gPPthString;
string gFil1String;
string gLinkString;
string gPLnkString;

const char* gAppTypes[] = {
        "APP",
        "SHARED",
        "STATIC",
        "DRIVER"
};

uint8 gAppType;
string gAppName;

struct hdr
{
                        uint32  Id() { return static_cast<uint32>(B_BENDIAN_TO_HOST_INT32(fId)); }
                        uint32  Size() { return static_cast<uint32>(B_BENDIAN_TO_HOST_INT32(fSize)); }
                        const char* Data() { return (char*)(this + 1); }
private:
                        uint32 fId;
                        uint32 fSize;
};


class Error : public std::exception
{
                        BString         fWhat;
public:
                                                Error(const char* what, ...);
        virtual                         ~Error() throw() {}
        virtual const char*     what() const throw() { return fWhat.String(); }
};


Error::Error(const char* what, ...)
{
        const int size = 1024;
        va_list args;
        va_start(args, what);
        vsnprintf(fWhat.LockBuffer(size), size, what, args);
        fWhat.UnlockBuffer();
        va_end(args);
}


void
CheckFiles(const char* projPath, const char* makePath)
{
        gProjFile.open(projPath, fstream::in | fstream::binary);
        if (!gProjFile.is_open())
                throw Error("%s not found", projPath);

        gProjFile.seekg(0, ios::end);
        uint32 projFileLength = gProjFile.tellg();
        gProjFile.seekg(0, ios::beg);

        char* name = new char[5];
        gProjFile.read(name, 4);

        uint32 length;
        gProjFile.read((char*)&length, 4);
        name[4] = '\0';
        length = static_cast<uint32>(B_BENDIAN_TO_HOST_INT32(length));
        gProjLength = length + 8;

        if (strcmp(name, "MIDE") != 0 || gProjLength > projFileLength)
                throw Error("File corrupted or it is not BeIDE *.proj file");

        gMakeFile.open(makePath, fstream::in);
        if (gMakeFile.is_open())
                throw Error("%s already exists", makePath);

        gMakeFile.open(makePath, fstream::out);
        if (!gMakeFile.is_open())
                throw Error("Can not create makefile");

        BPath templateFileName;
        find_directory(B_SYSTEM_DEVELOP_DIRECTORY, &templateFileName);
        templateFileName.Append("etc/Makefile");

        gTemplateFile.open(templateFileName.Path(), fstream::in);
        if (!gTemplateFile.is_open())
                throw Error("Can not open template %s", templateFileName.Path());
}


void
ParseGenB(hdr* data)
{
        hdr* child = (hdr*)data->Data();
        char* name = (char*)(child + 1);
        int len = strlen(name) + 1;

        uint32 u = child->Id();
        char* c = (char*)&u;
        printf("\t%c%c%c%c:%d:%s\n", c[3], c[2], c[1], c[0], child->Size(), name);

        if (strncmp(name, "ProjectPrefsx86", len - 1) == 0) {
                const char* type = child->Data() + len + 8;
                if (*type <= 3)
                        gAppType = *type;
                type++;
                type += 64; // skip the mime type name
                gAppName = type;
        }
}


class _l {
        static string _s;

public:
        _l() { _s += " "; }
        ~_l() { _s.resize(_s.size() - 1); }

        const char* str() { return _s.c_str(); }
};

string _l::_s;


void
Parse(hdr* current, hdr* parent)
{
        _l l;

        uint32 u = current->Id();
        char* c = (char*)&u;
        printf("%#06x:%s%c%c%c%c:%d\n", (uint8*)current - gProjData, l.str(), c[3], c[2], c[1], c[0],
                current->Size());

        bool useGrandParent = false;
        size_t off = 0;
        BString data;
        switch (current->Id()) {
                case 'Fil1':
                case 'Link':
                case 'PLnk':
                        off = 24;
                        break;
                case 'MIDE':
                case 'DPrf':
                case 'GPrf':
                        break;
                case 'MSFl':
                        off = 8;
                        useGrandParent = true;
                        break;
                case 'SPth':
                        data = &current->Data()[5];
                        // Avoid adding these library paths as include dirs.
                        if ((data.FindFirst("/boot/develop/lib") > -1)
                                || (data.FindFirst("/boot/beos/system/lib") > -1)) {
                                return;
                        }
                        // Replace BeOS paths with Haiku's.
                        data.ReplaceFirst("/boot/develop/headers/be", "/boot/system/develop/os");
                        data.ReplaceFirst("/boot/develop/headers/cpp", "/boot/system/develop/c++");
                        data.ReplaceFirst("/boot/develop/headers/posix", "/boot/system/develop/headers/posix");

                        gSPthString += " \\\n\t";
                        gSPthString += data.String();
                        return;
                case 'PPth':
                        gPPthString += " \\\n\t";
                        gPPthString += &current->Data()[5];
                        return;
                case 'Name':
                        if (parent->Id() == 'Fil1') {
                                gFil1String += " \\\n\t";
                                gFil1String += &current->Data()[4];
                        } else if (parent->Id() == 'Link') {
                                gLinkString += " \\\n\t";
                                gLinkString += &current->Data()[4];
                        } else if (parent->Id() == 'PLnk') {
                                gPLnkString += " \\\n\t";
                                gPLnkString += &current->Data()[4];
                        }
                        return;
                case 'GenB':
                        ParseGenB(current);
                        return;
                default:
                        return;
        }

        hdr* child = (hdr*)(current->Data() + off);
        while (off < current->Size()) {
                Parse(child, useGrandParent ? parent : current);
                off += child->Size() + sizeof(hdr);
                child = (hdr*)(child->Data() + child->Size());
        }
}


void
ReadProj()
{
        gProjFile.seekg(0, ios::beg);
        gProjData = new uint8[gProjLength];
        gProjFile.read((char*)gProjData, gProjLength);
        gProjFile.close();

        Parse((hdr*)gProjData, NULL);
}


void
Proj2Make()
{
        gFil1String = "";
        gLinkString = "";
        gPLnkString = "";
        gSPthString = "";
        gPPthString = "";

        ReadProj();
        string str;
        while (gTemplateFile.good()) {
                getline(gTemplateFile, str);

                if (str.find("SRCS") == 0)
                        str = str + gFil1String;
                else if (str.find("LIBS") == 0)
                        str = str + gLinkString;
                else if (str.find("SYSTEM_INCLUDE_PATHS") == 0)
                        str = str + gSPthString;
                else if (str.find("LOCAL_INCLUDE_PATHS") == 0)
                        str = str + gPPthString;
                else if (str.find("TYPE") == 0)
                        str = str + " " + gAppTypes[gAppType];
                else if (str.find("NAME") == 0)
                        str = str + " " + gAppName;
                else if (str.find("RSRCS") == 0)
                        str = str + gPLnkString;

                gMakeFile << str << endl;
        }

        gMakeFile.close();
        gTemplateFile.close();
}


int
main(int argc, char** argv)
{
        try {
                if (argc <= 1 || (argc > 1 && strcmp(argv[1], "--help") == 0))
                        throw Error("");

                BString projPath = argv[1];

                BString makePath;
                // if makefile path specified
                if (argc > 2)
                        makePath = argv[2];
                // default makefile path
                else {
                        BPath path(argv[1]);
                        path.GetParent(&path);
                        path.Append("makefile");
                        makePath = path.Path();
                }

                CheckFiles(projPath.String(), makePath.String());

                Proj2Make();

        } catch (exception& exc) {
                cerr << argv[0] << " : " << exc.what() << endl;
                cerr << kUsageMessage;
                return B_ERROR;
        }

        return B_OK;
}