root/src/bin/setversion.cpp
/*
 * Copyright 2002, Ryan Fleet.
 * Copyright 2006-2007, Axel Dörfler, axeld@pinc-software.de.
 *
 * Distributed under the terms of the MIT license.
 */


#include <AppFileInfo.h>
#include <String.h>

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


extern const char *__progname;

const char *kProgramName = __progname;


enum arg_needed {
        switch_needed, major_version, middle_version, minor_version,
        variety_version, internal_version, long_string, short_string
};

enum app_error {
        e_base = B_ERRORS_END,
        e_unknown, e_app_sys_switch, e_specify_version, e_major_version,
        e_middle_version, e_minor_version, e_variety_version, e_internal_version,
        e_expecting, e_long_string, e_short_string,
        e_parameter, e_app_twice, e_sys_twice
};

enum processing_mode { no_switch, app_switch, sys_switch };


static void
usage()
{
        fprintf(stdout, "Usage: %s filename\n", kProgramName);
        fprintf(stdout, "   [ -system <major> <middle> <minor>\n");
        fprintf(stdout, "       [ [ d | a | b | g | gm | f ] [ <internal> ] ]\n");
        fprintf(stdout, "       [ -short <shortVersionString> ]\n");
        fprintf(stdout, "       [ -long <longVersionString> ] ] # system info\n");
        fprintf(stdout, "   [ -app <major> <middle> <minor>\n");
        fprintf(stdout, "       [ [ d | a | b | g | gm | f ] [ <internal> ] ]\n");
        fprintf(stdout, "       [ -short <shortVersionString> ]\n");
        fprintf(stdout, "       [ -long <longVersionString> ] ] # application info\n");
}


static int
convertVariety(const char *str)
{
        if (!strcmp(str, "d") || !strcmp(str, "development"))
                return 0;
        if (!strcmp(str, "a") || !strcmp(str, "alpha"))
                return 1;
        if (!strcmp(str, "b") || !strcmp(str, "beta"))
                return 2;
        if (!strcmp(str, "g") || !strcmp(str, "gamma"))
                return 3;
        if (!strcmp(str, "gm") || !strcmp(str, "goldenmaster"))
                return 4;
        if (!strcmp(str, "f") || !strcmp(str, "final"))
                return 5;

        return -1;
}


static void
errorToString(BString& output, status_t error, const char *appName = NULL)
{
        switch (error) {
                case e_app_sys_switch:
                        output = "-system or -app expected\n";
                        break;
                case e_specify_version:
                        output = "you did not specify any version\n";
                        break;
                case e_major_version:
                        output = "major version number error\n";
                        break;
                case e_middle_version:
                        output = "middle version number error\n";
                        break;
                case e_minor_version:
                        output = "minor version number error\n";
                        break;
                case e_variety_version:
                        output = "variety letter error\n";
                        break;
                case e_internal_version:
                        output = "internal version number error\n";
                        break;
                case e_expecting:
                        output = "expecting -short, -long, -app or -system\n";
                        break;
                case e_long_string:
                        output = "expecting long version string\n";
                        break;
                case e_short_string:
                        output = "expecting short version string\n";
                        break;
                case e_parameter:
                        output = "parameter error\n";
                        break;
                case e_app_twice:
                        output = "you cannot specify the app version twice\n";
                        break;
                case e_sys_twice:
                        output = "you cannot specify the system version twice\n";
                        break;
                case e_unknown:
                        output = "unknown internal error\n";
                        break;

                default:
                        output = strerror(error);

                        if (appName != NULL) {
                                output += ": ";
                                output += appName;
                        }
                        break;
        }
}


static void
errorOut(status_t error, const char *appName = NULL, bool showUsage = true)
{
        BString output;
        errorToString(output, error, appName);

        fprintf(stderr, "%s: %s", kProgramName, output.String());

        if (showUsage)
                usage();

        exit(1);
}


static void
parse(bool &systemModified, bool &appModified, arg_needed &argNeeded,
        processing_mode &mode, version_info &systemVersion, version_info &appVersion,
        int argc, char *argv[])
{
        systemModified = false;
        appModified = false;
        mode = no_switch;
        argNeeded = switch_needed;

        for (int i = 2; i < argc; ++i) {
                version_info &version = mode == app_switch ? appVersion : systemVersion;

                switch (argNeeded) {
                        case switch_needed:
                                if (strcmp(argv[i], "-app") == 0) {
                                        if (mode == app_switch)
                                                errorOut(e_app_twice);
                                        if (appModified)
                                                errorOut(e_parameter);

                                        mode = app_switch;
                                        argNeeded = major_version;
                                        appModified = true;
                                } else if (strcmp(argv[i], "-system") == 0) {
                                        if (mode == sys_switch)
                                                errorOut(e_sys_twice);
                                        if (systemModified)
                                                errorOut(e_parameter);

                                        mode = sys_switch;
                                        argNeeded = major_version;
                                        systemModified = true;
                                } else if (strcmp(argv[i], "-long") == 0) {
                                        if (mode == no_switch)
                                                errorOut(e_app_sys_switch);

                                        argNeeded = long_string;
                                } else if (strcmp(argv[i], "-short") == 0) {
                                        if (mode == no_switch)
                                                errorOut(e_app_sys_switch);

                                        argNeeded = short_string;
                                } else if (mode == no_switch)
                                        errorOut(e_app_sys_switch);
                                else if (strncmp(argv[i], "-", 1) == 0)
                                        errorOut(e_parameter);
                                else
                                        errorOut(e_expecting);
                                break;

                        case major_version:
                                if (isalpha(argv[i][0]))
                                        errorOut(e_major_version);

                                version.major = atoi(argv[i]);
                                argNeeded = middle_version;
                                break;

                        case middle_version:
                                if (isalpha(argv[i][0]))
                                        errorOut(e_middle_version);

                                version.middle = atoi(argv[i]);
                                argNeeded = minor_version;
                                break;

                        case minor_version:
                                if (isalpha(argv[i][0]))
                                        errorOut(e_minor_version);

                                version.minor = atoi(argv[i]);

                                if (i >= argc-1) {
                                        argNeeded = switch_needed;
                                        break;
                                }

                                argNeeded = variety_version;
                                break;

                        case variety_version:
                        {
                                if (!strncmp(argv[i], "-", 1)) {
                                        i--;
                                        argNeeded = switch_needed;
                                        break;
                                }

                                int variety = convertVariety(argv[i]);
                                if (variety < 0)
                                        errorOut(e_variety_version);

                                version.variety = variety;
                                argNeeded = internal_version;
                                break;
                        }

                        case internal_version:
                                if (isalpha(argv[i][0]))
                                        errorOut(e_expecting);

                                version.internal = atoi(argv[i]);
                                argNeeded = switch_needed;
                                break;

                        case long_string:
                                strcpy(version.long_info, argv[i]);
                                argNeeded = switch_needed;
                                break;

                        case short_string:
                                strcpy(version.short_info, argv[i]);
                                argNeeded = switch_needed;
                                break;
                }
        }

        if (mode == no_switch)
                errorOut(e_app_sys_switch);

        switch (argNeeded) {
                case major_version:
                        errorOut(e_major_version);
                        break;
                case middle_version:
                        errorOut(e_middle_version);
                        break;
                case minor_version:
                        errorOut(e_minor_version);
                        break;
                case variety_version:
                        errorOut(e_variety_version);
                        break;
                case internal_version:
                        errorOut(e_internal_version);
                        break;
                case long_string:
                        errorOut(e_long_string);
                        break;
                case short_string:
                        errorOut(e_short_string);
                        break;
                case switch_needed:
                        // all is well
                        break;
        }
}


int
main(int argc, char *argv[])
{
        if (argc < 3) {
                if (argc < 2)
                        errorOut(e_app_sys_switch);

                errorOut(e_specify_version);
        }

        // reset version infos

        version_info systemVersion, appVersion;
        memset(&systemVersion, 0, sizeof(version_info));
        memset(&appVersion, 0, sizeof(version_info));

        // process arguments

        processing_mode mode;
        arg_needed argNeeded;
        bool systemModified, appModified;

        parse(systemModified, appModified, argNeeded, mode, systemVersion,
                appVersion, argc, argv);

        // write back changes

        BFile file;
        status_t status = file.SetTo(argv[1], B_READ_WRITE);
        if (status != B_OK)
                errorOut(status, argv[1], false);

        BAppFileInfo info;
        status = info.SetTo(&file);
        if (status != B_OK)
                errorOut(status, argv[1], false);

        if (systemModified ^ appModified) {
                // clear out other app info if not present - this works around a
                // bug in BeOS, see bug #681.
                version_kind kind = systemModified ? B_APP_VERSION_KIND : B_SYSTEM_VERSION_KIND;
                version_info clean;

                if (info.GetVersionInfo(&clean, kind) != B_OK) {
                        memset(&clean, 0, sizeof(version_info));
                        info.SetVersionInfo(&clean, kind);
                }
        }

        if (appModified) {
                status = info.SetVersionInfo(&appVersion, B_APP_VERSION_KIND);
                if (status < B_OK)
                        errorOut(status, NULL, false);
        }

        if (systemModified) {
                status = info.SetVersionInfo(&systemVersion, B_SYSTEM_VERSION_KIND);
                if (status < B_OK)
                        errorOut(status, NULL, false);
        }

        return 0;
}