⛏️ index : haiku.git

// hey
// a small scripting utility
// written by Attila Mezei (attila.mezei@mail.datanet.hu)
// contributions by Sander Stoks, Peter Folk, Chris Herborth, Marco Nelissen, Scott Lindsey and others
//
// public domain, use it at your own risk
//
// 1.2.8:	(Sander Stoks): Added command-line option -o which will output the "result" value
//		in the reply message to stdout, so you can use it in shell scripting more easily:
//		"hey Becasso get AspectRatio of Canvas 0"
//		outputs
//		Reply BMessage(B_REPLY):
//		   "result" (B_DOUBLE_TYPE) : 0.600
//		but "hey -o Becasso get AspectRatio of Canvas 0"
//		outputs 0.600000 directly.
//
// 1.2.7:	by Sander Stoks: Made a fork since I don't think Attila still supports "hey", and
//		because the latest version on BeBits seems to be 1.2.4.
//		Changes w.r.t. 1.2.6: When an application returns an error on a message, hey now
//		keeps iterating over applications with the same signature.  This is useful because,
//		for instance, Terminal starts as a new process for each instance, so it previously
//		wouldn't work to move a specific Terminal window using hey.  You can now say
//		"hey Terminal set Frame of Window foo to BRect[...]".
//
// 1.2.6:	syntax extended by Sander Stoks <sander@stoks.nl to allow:
//		"hey Application let Specifier do ..."
//		allowing you to send messages directly to other handlers than the app itself.
//		In cooperation with the new Application defined commands (note that some
//		Be classes, e.g. BWindow, publish commands as well) this allows, for example:
//		"hey NetPositive let Window 0 do MoveBy BPoint[10,10]"
//		Note that this is partly redundant, since
//		"hey NetPositive let Window 0 do get Title"
//		duplicates "hey NetPositive get Title of Window 0"
//		But with the old system,
//		"hey NetPositive MoveBy of Window 0 with data=BPoint[10,10]"
//		couldn't work ("MoveBy" is not defined by the Application itself).
//
// 1.2.5:   value_info is supported in BPropertyInfo. This means when you send GETSUITES (B_GET_SUPPORTED_SUITES)
//	the value info is printed after the property infos, like this:
//		   "messages" (B_PROPERTY_INFO_TYPE) :
//		        property   commands                            specifiers
//		--------------------------------------------------------------------------------
//		          Suites   B_GET_PROPERTY                      DIRECT
//		       Messenger   B_GET_PROPERTY                      DIRECT
//		    InternalName   B_GET_PROPERTY                      DIRECT
//
//		            name   value                               kind
//		--------------------------------------------------------------------------------
//		          Backup   0x6261636B ('back')                 COMMAND
//		                   Usage: This command backs up your hard drive.
//		           Abort   0x61626F72 ('abor')                 COMMAND
//		                   Usage: Stops the current operation...
//		       Type Code   0x74797065 ('type')                 TYPE CODE
//		                   Usage: Type code info...
//
//	You can also use the application defined commands (as the verb) with hey:
//		hey MyBackupApp Backup "Maui"
//
// 1.2.4:   the syntax is extended by Peter Folk <pfolk@uni.uiuc.edu> to contain:
//      do the x of y -3 of z '"1"'
//      I.e. "do" => B_EXECUTE_PROPERTY, optional "the" makes direct specifiers
//      more like english, bare reverse-index-specifiers are now handled, and
//      named specifiers can contain only digits by quoting it (but make sure the
//      shell passed the quotes through).
//
//      Hey(target,const char*,reply) was previously limited to 100 tokens.  It
//      now uses a vector<> so it's only limited by available memory.
//
//      Also, the archive name is now Y2K compliant =)
//
// 1.2.3:	new option: -s for silent processing (no reply or errors printed) AM
//
// 1.2.2:	Fixes by Marco Nelissen (marcone@xs4all.nl)
//      - fixed parsing of negative numbers
//		- fixed "with" syntax, which was broken (after a create, "with" would be taken as a specifier)
//
// 1.2.1:	compiled for x86, R4 with minor modifications at BPropertyInfo
//
// 1.2.0:	the syntax is extended by Sander Stoks (sander@adamation.com) to contain
//		with name=<value> [and name=<value> [...]]
//		at the end of the command which will add additional data to the scripting message. E.g:
//		hey Becasso create Canvas with name=MyCanvas and size=BRect(100,100,300,300)
//		Also a small interpreter is included.
//
//		Detailed printout of B_PROPERTY_INFO in BMessages. Better than BPropertyInfo::PrintToStream().
//		Also prints usage info for a property if defined.
//
// 1.1.1:	minor change from chrish@qnx.com to return -1 if an error is
//		sent back in the reply message; also added B_COUNT_PROPERTIES support
//
//		The range specifier sent to the target was 1 greater than it should've been. Fixed.
//
//		'hey' made the assumption that the first thread in a team will be the
//		application thread (and therefore have the application's name).
//		This was not always the case. Fix from Scott Lindsey <wombat@gobe.com>.
//
//v1.1.0:	Flattened BPropertyInfo is printed if found in the reply of B_GET_SUPPORTED_SUITES
//		1,2,3 and 4 character message constant is supported (e.g. '1', '12', '123', '1234')
//		Alpha is sent with rgb_color
//
//v1.0.0	First public release


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <AppKit.h>
#include <Path.h>
#include <SupportDefs.h>

int32 HeyInterpreterThreadHook(void* arg);

status_t Hey(BMessenger* target, const char* arg, BMessage* reply);
bool isSpace(char c);
status_t Hey(BMessenger* target, char* argv[], int32* argx, int32 argc, BMessage* reply);
status_t add_specifier(BMessage *to_message, char *argv[], int32 *argx, int32 argc);
status_t add_data(BMessage *to_message, char *argv[], int32 *argx);
status_t add_with(BMessage *to_message, char *argv[], int32 *argx, int32 argc);
void add_message_contents(BList *textlist, BMessage *msg, int32 level);
char *get_datatype_string(int32 type);
char *format_data(int32 type, char *ptr, long size);
void print_message(BMessage *message);
char *id_to_string(long ID, char *here);
bool is_valid_char(uint8 c);

const char VERSION[] = "v1.2.8";

#define MAX_INPUT_SIZE 1024
	// Maximum amount of input data that "hey" can process at a time

#define DEBUG_HEY 0		// 1: prints the script message to be sent to the target application, 0: prints only the reply


// test, these should be zero for normal operation
#define TEST_VALUEINFO 0


// flag for silent mode
bool silent;
// flag for stdout mode
bool output;

status_t
parse(BMessenger& the_application, int argc, char *argv[], int32 argapp)
{
	if (!the_application.IsValid()) {
		if (!silent)
			fprintf(stderr, "Cannot find the application (%s)\n", argv[argapp]);
		return B_ERROR;
	}

	if (argc < 3) {
		if (!silent)
			fprintf(stderr, "Cannot find the verb!\n");
		return B_ERROR;
	}


	BMessage the_reply;
	int32 argx = argapp+1;
	status_t err = Hey(&the_application, argv, &argx, argc, &the_reply);

	if (err != B_OK) {
		if (!silent) {
			fprintf(stderr, "Error when sending message to %s!\n",
				argv[argapp]);
		}
		return B_ERROR;
	} else {
		if (the_reply.what == (uint32)B_MESSAGE_NOT_UNDERSTOOD
			|| the_reply.what == (uint32)B_ERROR) {	// I do it myself
			if (the_reply.HasString("message")) {
				if (!silent) {
					printf("%s (error 0x%8" B_PRIx32 ")\n",
						the_reply.FindString("message"),
						the_reply.FindInt32("error"));
				}
			} else {
				if (!silent) {
					printf("error 0x%8" B_PRIx32 "\n",
						the_reply.FindInt32("error"));
				}
			}
			return 1;
		} else {
			if (!silent) {
				if (output) {
					type_code tc;
					if (the_reply.GetInfo("result", &tc) == B_OK) {
						if (tc == B_INT8_TYPE) {
							int8 v;
							the_reply.FindInt8("result", &v);
							printf("%d\n", v);
						} else if (tc == B_INT16_TYPE) {
							int16 v;
							the_reply.FindInt16("result", &v);
							printf("%d\n", v);
						} else if (tc == B_INT32_TYPE) {
							int32 v;
							the_reply.FindInt32("result", &v);
							printf("%" B_PRId32 "\n", v);
						} else if (tc == B_UINT8_TYPE) {
							uint8 v;
							the_reply.FindInt8("result", (int8*)&v);
							printf("%u\n", v);
						} else if (tc == B_UINT16_TYPE) {
							uint16 v;
							the_reply.FindInt16("result", (int16*)&v);
							printf("%u\n", v);
						} else if (tc == B_UINT32_TYPE) {
							uint32 v;
							the_reply.FindInt32("result", (int32*)&v);
							printf("%" B_PRIu32 "\n", v);
						} else if (tc == B_STRING_TYPE) {
							const char* v;
							the_reply.FindString("result", &v);
							printf("%s\n", v);
						} else if (tc == B_FLOAT_TYPE) {
							float v;
							the_reply.FindFloat("result", &v);
							printf("%f\n", v);
						} else if (tc == B_DOUBLE_TYPE) {
							double v;
							the_reply.FindDouble("result", &v);
							printf("%f\n", v);
						} else if (tc == B_BOOL_TYPE) {
							bool v;
							the_reply.FindBool("result", &v);
							printf("%s\n", v ? "true" : "false");
						} else
							printf("Unsupported type\n");
					}
				} else {
					printf("Reply ");
					print_message(&the_reply);
					printf("\n");
				}
			}
		}
	}
	return B_OK;
}


void
usage(int exitCode)
{
	fprintf(exitCode == EXIT_SUCCESS ? stdout : stderr,
		"hey %s, written by Attila Mezei (attila.mezei@mail.datanet.hu)\n"
		"usage: hey [-s][-o] <app|signature|teamid> [let <specifier> do] <verb> <specifier_1> <of\n"
		"           <specifier_n>>* [to <value>] [with name=<value> [and name=<value>]*]\n"
		"where  <verb> : DO|GET|SET|COUNT|CREATE|DELETE|GETSUITES|QUIT|SAVE|LOAD|'what'\n"
		"  <specifier> : [the] <property_name> [ <index> | name | \"name\" | '\"name\"' ]\n"
		"      <index> : int | -int | '['int']' | '['-int']' | '['startint to end']'\n"
		"      <value> : \"string\" | <integer> | <float> | bool(value) | int8(value)\n"
		"                | int16(value) | int32(value) | float(value) | double(value)\n"
		"                | BPoint(x,y) | BRect(l,t,r,b) | rgb_color(r,g,b,a) | file(path)\n"
		"options: -s: silent\n"
		"         -o: output result to stdout for easy parsing\n\n", VERSION);
	exit(exitCode);
}


int
main(int argc, char *argv[])
{
	BApplication app("application/x-amezei-hey");

	if (argc < 2)
		usage(1);

	int32 argapp = 1;
	silent = false;
	output = false;

	// Updated option mechanism
	for (int i = 0; i < argc; i++) {
		if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "-S") == 0) {
			silent = true;
			argapp++;
		} else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "-O") == 0) {
			output = true;
			argapp++;
		} else  if (strcmp(argv[1], "-h") == 0
			|| strcmp(argv[1], "--help") == 0)
			usage(0);
	}

	// find the application
	BMessenger the_application;
	BList team_list;
	team_id teamid;
	app_info appinfo;

	teamid = atoi(argv[argapp]);
	if (teamid > 0) {
		if (be_roster->GetRunningAppInfo(teamid, &appinfo) != B_OK)
			return 1;
		the_application=BMessenger(NULL, teamid);
		if (!parse(the_application, argc, argv, argapp))
			return 0;
		return 1;
	}

	be_roster->GetAppList(&team_list);

	for (int32 i = 0; i < team_list.CountItems(); i++) {
		teamid = (team_id)(addr_t)team_list.ItemAt(i);
		be_roster->GetRunningAppInfo(teamid, &appinfo);
		if (strcmp(appinfo.signature, argv[argapp]) == 0) {
			the_application=BMessenger(appinfo.signature);
			if (!parse(the_application, argc, argv, argapp))
				return 0;
		} else {
			if (strcmp(appinfo.ref.name, argv[argapp]) == 0) {
				the_application = BMessenger(0, teamid);
				if (!parse(the_application, argc, argv, argapp))
					return 0;
			}
		}
	}

	return 1;
}


int32
HeyInterpreterThreadHook(void* arg)
{
	if (!arg)
		return 1;

	BMessage environment(*(BMessage*) arg);
	const char* prompt = "Hey";
	if (environment.HasString("prompt"))
		environment.FindString("prompt", &prompt);
	printf("%s> ", prompt);

	BMessenger target;
	if (environment.HasMessenger("Target"))
		environment.FindMessenger("Target", &target);

	char command[MAX_INPUT_SIZE];
	status_t err;
	BMessage reply;
	while (fgets(command, sizeof(command), stdin)) {
		reply.MakeEmpty();
		err = Hey(&target, command, &reply);
		if (!err) {
			print_message(&reply);
		} else {
			printf("Error!\n");
		}
		printf("%s> ", prompt);
	}

	return 0;
}

status_t
Hey(BMessenger* target, const char* arg, BMessage* reply)
{
	BList argv;
	char* tokens = new char[strlen(arg) * 2];
		// number of tokens is limited only by memory
	char* currentToken = tokens;
	int32 tokenNdex = 0;
	int32 argNdex = 0;
	bool inquotes = false;

	while (arg[argNdex] != 0) { // for each character in arg
		if (arg[argNdex] == '\"')
			inquotes = !inquotes;
		if (!inquotes && isSpace(arg[argNdex])) { // if the character is white space
			if (tokenNdex != 0) { //  close off currentToken token
				currentToken[tokenNdex] = 0;
				argv.AddItem(currentToken);
				currentToken += tokenNdex + 1;
				tokenNdex = 0;
				argNdex++;
			} else { // just skip the whitespace
				argNdex++;
			}
		} else { // copy char into current token
			currentToken[tokenNdex] = arg[argNdex];
			tokenNdex++;
			argNdex++;
		}
	}

	if (tokenNdex!=0) { //  close off currentToken token
		currentToken[tokenNdex] = 0;
		argv.AddItem(currentToken);
	}
	argv.AddItem(NULL);

	int32 argx = 0;
	status_t ret = Hey(target, (char **)argv.Items(), &argx, argv.CountItems() - 1, reply);
	  // This used to be "return Hey(...);"---so tokens wasn't delete'd.
	delete[] tokens;
	return ret;
}


bool
isSpace(char c)
{
	switch (c) {
		case ' ':
		case '\t':
			return true;

		default:
			return false;
	}
}


status_t
Hey(BMessenger* target, char* argv[], int32* argx, int32 argc, BMessage* reply)
{
	bool direct_what = false;
	BMessage the_message;
	if (strcasecmp(argv[*argx], "let") == 0) {
		BMessage get_target (B_GET_PROPERTY);
		get_target.AddSpecifier ("Messenger");
			// parse the specifiers
		(*argx)++;
		status_t result = B_OK;
		while ((result = add_specifier(&get_target, argv, argx, argc)) == B_OK)
			;

		if (result != B_ERROR) {
			if (!silent)
				fprintf(stderr, "Bad specifier syntax!\n");
			return result;
		}
		BMessage msgr;
		if (target && target->IsValid()) {
			result = target->SendMessage(&get_target, &msgr);
			if (result != B_OK)
				return result;
			result = msgr.FindMessenger ("result", target);
			if (result != B_OK) {
				if (!silent)
					fprintf(stderr, "Couldn't retrieve the BMessenger!\n");
				return result;
			}
		}
		if (!argv[*argx]) {
			if (!silent)
				fprintf(stderr, "Syntax error - forgot \"do\"?\n");
			return B_ERROR;
		}
	}
	if (strcasecmp(argv[*argx], "do") == 0)
		the_message.what = B_EXECUTE_PROPERTY;
	else if (strcasecmp(argv[*argx], "get") == 0)
		the_message.what = B_GET_PROPERTY;
	else if (strcasecmp(argv[*argx], "set") == 0)
		the_message.what = B_SET_PROPERTY;
	else if (strcasecmp(argv[*argx], "create") == 0)
		the_message.what = B_CREATE_PROPERTY;
	else if (strcasecmp(argv[*argx], "delete") == 0)
		the_message.what = B_DELETE_PROPERTY;
	else if (strcasecmp(argv[*argx], "quit") == 0)
		the_message.what = B_QUIT_REQUESTED;
	else if (strcasecmp(argv[*argx], "save") == 0)
		the_message.what = B_SAVE_REQUESTED;
	else if (strcasecmp(argv[*argx], "load") == 0)
		the_message.what = B_REFS_RECEIVED;
	else if (strcasecmp(argv[*argx], "count") == 0)
		the_message.what = B_COUNT_PROPERTIES;
	else if (strcasecmp(argv[*argx], "getsuites") == 0)
		the_message.what = B_GET_SUPPORTED_SUITES;
	else {
		switch(strlen(argv[*argx])) {
				// can be a message constant if 1,2,3 or 4 chars
			case 1:
				the_message.what = (int32)argv[*argx][0];
				break;
			case 2:
				the_message.what = (((int32)argv[*argx][0]) << 8)
					| (((int32)argv[*argx][1]));
				break;
			case 3:
				the_message.what = (((int32)argv[*argx][0]) << 16)
					| (((int32)argv[*argx][1]) << 8)
					| (((int32)argv[*argx][2]));
				break;
			case 4:
				the_message.what = (((int32)argv[*argx][0]) << 24)
					| (((int32)argv[*argx][1]) << 16)
					| (((int32)argv[*argx][2]) << 8)
					| (((int32)argv[*argx][3]));
				break;
			default:
				// maybe this is a user defined command, ask for the supported suites
				bool found = false;
				if (target && target->IsValid()) {
					BMessage reply;
					if (target->SendMessage(B_GET_SUPPORTED_SUITES, &reply)
						== B_OK) {
						// if all goes well, reply contains all kinds of
						// property infos
						int32 j = 0;
						void *voidptr;
						ssize_t sizefound;
						BPropertyInfo propinfo;
						const value_info *vinfo;
						int32 vinfo_index, vinfo_count;

//						const char *str;
//						while (rply.FindString("suites", j++, &str) == B_OK)
//							printf ("Suite %ld: %s\n", j, str);
//
//						j = 0;
						while (reply.FindData("messages", B_PROPERTY_INFO_TYPE,
								j++, (const void **)&voidptr, &sizefound)
							== B_OK && !found) {
							if (propinfo.Unflatten(B_PROPERTY_INFO_TYPE,
									(const void *)voidptr, sizefound) == B_OK) {
								vinfo = propinfo.Values();
								vinfo_index = 0;
								vinfo_count = propinfo.CountValues();
#if TEST_VALUEINFO>0
								value_info vinfo[10] = {
									{"Backup", 'back', B_COMMAND_KIND,
										"This command backs up your hard"
										" drive."},
									{"Abort", 'abor', B_COMMAND_KIND,
										"Stops the current operation..."},
									{"Type Code", 'type', B_TYPE_CODE_KIND,
										"Type code info..."}
								};
								vinfo_count = 3;
#endif

								while (vinfo_index < vinfo_count) {
									if (strcmp(vinfo[vinfo_index].name,
											argv[*argx]) == 0) {
										found = true;
										the_message.what =
											vinfo[vinfo_index].value;
#if TEST_VALUEINFO>0
										printf("FOUND COMMAND \"%s\" = %lX\n",
											vinfo[vinfo_index].name,
											the_message.what);
#endif
										break;
									}
									vinfo_index++;
								}
							}
						}
					}
				}

				if (!found) {
					if (!silent)
						fprintf(stderr, "Bad verb (\"%s\")\n", argv[*argx]);
					return -1;
				}
		}
		direct_what = true;
	}

	status_t result = B_OK;
	(*argx)++;

	// One exception: Single data item at end of line.
	if (direct_what && *argx == argc - 1 && argv[*argx] != NULL)
		add_data(&the_message, argv, argx);
	else {
		// parse the specifiers
		if (the_message.what != B_REFS_RECEIVED) {
				// LOAD has no specifier
			while ((result = add_specifier(&the_message, argv, argx, argc))
				== B_OK)
				;

			if (result != B_ERROR) {
				if (!silent)
					fprintf(stderr, "Bad specifier syntax!\n");
				return result;
			}
		}
	}

	// if verb is SET or LOAD, there should be a to <value>
	if ((the_message.what == B_SET_PROPERTY || the_message.what == B_REFS_RECEIVED) && argv[*argx] != NULL) {
		if (strcasecmp(argv[*argx], "to") == 0)
			(*argx)++;
		result = add_data(&the_message, argv, argx);
		if (result != B_OK) {
			if (result == B_ENTRY_NOT_FOUND) {
				if (!silent)
					fprintf(stderr, "File not found!\n");
			} else if (!silent)
				fprintf(stderr, "Invalid 'to...' value format!\n");
			return result;
		}
	}

	add_with(&the_message, argv, argx, argc);

#if DEBUG_HEY>0
	fprintf(stderr, "Send ");
	print_message(&the_message);
	fprintf(stderr, "\n");
#endif

	if (target && target->IsValid()) {
		if (reply)
			result = target->SendMessage(&the_message, reply);
		else
			result = target->SendMessage(&the_message);
	}
	return result;
}

// There can be a with <name>=<type>() [and <name>=<type> ...]
// I treat "and" just the same as "with", it's just to make the script syntax more English-like.
status_t
add_with(BMessage *to_message, char *argv[], int32 *argx, int32 argc)
{
	status_t result = B_OK;
	if (*argx < argc - 1 && argv[++(*argx)] != NULL) {
		// printf ("argv[%ld] = %s\n", *argx, argv[*argx]);
		if (strcasecmp(argv[*argx], "with") == 0) {
			// printf ("\"with\" detected!\n");
			(*argx)++;
			bool done = false;
			do {
				result = add_data(to_message, argv, argx);
				if (result != B_OK) {
					if (result == B_ENTRY_NOT_FOUND) {
						if (!silent)
							fprintf(stderr, "File not found!\n");
					} else {
						if (!silent)
							fprintf(stderr, "Invalid 'with...' value format!\n");
					}
					return result;
				}
				(*argx)++;
				// printf ("argc = %d, argv[%d] = %s\n", argc, *argx, argv[*argx]);
				if (*argx < argc - 1 && strcasecmp(argv[*argx], "and") == 0)
					(*argx)++;
				else
					done = true;
			} while (!done);
		}
	}
	return result;
}

// returns B_OK if successful
//         B_ERROR if no more specifiers
//         B_BAD_SCRIPT_SYNTAX if syntax error
status_t
add_specifier(BMessage *to_message, char *argv[], int32 *argx, int32 argc)
{
	char *property = argv[*argx];

	if (property == NULL)
		return B_ERROR;		// no more specifiers

	(*argx)++;

	if (strcasecmp(property, "do") == 0) {
			// Part of the "hey App let Specifier do Verb".
		return B_ERROR;	// no more specifiers
	}

	if (strcasecmp(property, "to") == 0) {
		return B_ERROR;
			// no more specifiers
	}

	if (strcasecmp(property, "with") == 0) {
		*argx -= 2;
		add_with(to_message, argv, argx, argc);
		return B_ERROR;
			// no more specifiers
	}

	if (strcasecmp(property, "of") == 0) {
			// skip "of", read real property
		property = argv[*argx];
		if (property == NULL)
			return B_BAD_SCRIPT_SYNTAX;
		(*argx)++;
	}

	if (strcasecmp(property, "the") == 0) {
			// skip "the", read real property
		property = argv[*argx];
		if (property == NULL)
			return B_BAD_SCRIPT_SYNTAX;
		(*argx)++;
	}

	// decide the specifier

	char *specifier = NULL;
	if (to_message->what == B_CREATE_PROPERTY) {
			// create is always direct. without this, a "with" would be
			// taken as a specifier
		(*argx)--;
	} else
		specifier = argv[*argx];
	if (specifier == NULL) {
			// direct specifier
		to_message->AddSpecifier(property);
		return B_ERROR;
			// no more specifiers
	}

	(*argx)++;

	if (strcasecmp(specifier, "of") == 0) {
			// direct specifier
		to_message->AddSpecifier(property);
		return B_OK;
	}

	if (strcasecmp(specifier, "to") == 0) {
			// direct specifier
		to_message->AddSpecifier(property);
		return B_ERROR;
			// no more specifiers
	}


	if (specifier[0] == '[') {
			// index, reverse index or range
		char *end;
		int32 ix1, ix2;
		if (specifier[1] == '-') {
				// reverse index
			ix1 = strtoul(specifier + 2, &end, 10);
			BMessage revspec(B_REVERSE_INDEX_SPECIFIER);
			revspec.AddString("property", property);
			revspec.AddInt32("index", ix1);
			to_message->AddSpecifier(&revspec);
		} else {
				// index or range
			ix1 = strtoul(specifier + 1, &end, 10);
			if (end[0] == ']') {
					// it was an index
				to_message->AddSpecifier(property, ix1);
				return B_OK;
			} else {
				specifier = argv[*argx];
				if (specifier == NULL) {
						// I was wrong, it was just an index
					to_message->AddSpecifier(property, ix1);
					return B_OK;
				}
				(*argx)++;
				if (strcasecmp(specifier, "to") == 0) {
					specifier = argv[*argx];
					if (specifier == NULL)
						return B_BAD_SCRIPT_SYNTAX;
					(*argx)++;
					ix2 = strtoul(specifier, &end, 10);
					to_message->AddSpecifier(property, ix1, ix2 - ix1 > 0
							? ix2 - ix1 : 1);
					return B_OK;
				} else
					return B_BAD_SCRIPT_SYNTAX;
			}
		}
	} else {
		// name specifier
		// if it contains only digits, it will be an index...
		bool index_spec = true;
		bool reverse = specifier[0] == '-';
		// accept bare reverse-index-specs
		size_t speclen = strlen(specifier);
		for (int32 i = (reverse ? 1 : 0); i < (int32)speclen; ++i) {
			if (specifier[i] < '0' || specifier[i] > '9') {
				index_spec = false;
				break;
			}
		}

		if (index_spec) {
			if (reverse) {
				// Copied from above
				BMessage revspec(B_REVERSE_INDEX_SPECIFIER);
				revspec.AddString("property", property);
				revspec.AddInt32("index", atol(specifier + 1));
				to_message->AddSpecifier(&revspec);
			} else
				to_message->AddSpecifier(property, atol(specifier));
		} else {
			// Allow any name by counting an initial " as a literal-string
			// indicator
			if (specifier[0] == '\"') {
				if (specifier[speclen - 1] == '\"')
					specifier[speclen - 1] = '\0';
				++specifier;
				--speclen;
			}
			to_message->AddSpecifier(property, specifier);
		}
	}

	return B_OK;
}


status_t
add_data(BMessage *to_message, char *argv[], int32 *argx)
{
	char *valuestring = argv[*argx];

	if (valuestring == NULL)
		return B_ERROR;

	// try to interpret it as an integer or float
	bool contains_only_digits = true;
	bool is_floating_point = false;
	for (int32 i = 0; i < (int32)strlen(valuestring); i++) {
		if (i != 0 || valuestring[i] != '-') {
			if (valuestring[i] < '0' || valuestring[i] > '9') {
				if (valuestring[i] == '.') {
					is_floating_point = true;
				} else {
					contains_only_digits = false;
					break;
				}
			}
		}
	}
	//printf("%d %d\n",	contains_only_digits,is_floating_point);
	if (contains_only_digits) {
		if (is_floating_point) {
			to_message->AddFloat("data", atof(valuestring));
			return B_OK;
		} else {
			to_message->AddInt32("data", atol(valuestring));
			return B_OK;
		}
	}

	// if true or false, it is bool
	if (strcasecmp(valuestring, "true") == 0) {
		to_message->AddBool("data", true);
		return B_OK;
	} else if (strcasecmp(valuestring, "false") == 0) {
		to_message->AddBool("data", false);
		return B_OK;
	}

	// Add support for "<name>=<type>()" here:
	// The type is then added under the name "name".

	#define MAX_NAME_LENGTH 128
	char curname[MAX_NAME_LENGTH];
	strcpy (curname, "data");	// This is the default.

	char *s = valuestring;
	while (*++s && *s != '=')
		// Look for a '=' character...
		;
	if (*s == '=') {	// We found a <name>=
		*s = 0;
		strcpy (curname, valuestring);	// Use the new <name>
		valuestring = s + 1;			// Reposition the valuestring ptr.
	}

	// must begin with a type( value )
	if (strncasecmp(valuestring, "int8", strlen("int8")) == 0)
		to_message->AddInt8(curname, atol(valuestring + strlen("int8(")));
	else if (strncasecmp(valuestring, "int16", strlen("int16")) == 0)
		to_message->AddInt16(curname, atol(valuestring + strlen("int16(")));
	else if (strncasecmp(valuestring, "int32", strlen("int32")) == 0)
		to_message->AddInt32(curname, atol(valuestring + strlen("int32(")));
	else if (strncasecmp(valuestring, "int64", strlen("int64")) == 0)
		to_message->AddInt64(curname, atol(valuestring + strlen("int64(")));
	else if (strncasecmp(valuestring, "bool", strlen("bool")) == 0) {
		if (strncasecmp(valuestring + strlen("bool("), "true", 4) == 0)
			to_message->AddBool(curname, true);
		else if (strncasecmp(valuestring + strlen("bool("), "false", 5) == 0)
			to_message->AddBool(curname, false);
		else
			to_message->AddBool(curname, atol(valuestring + strlen("bool(")) == 0 ? false : true);
	} else if (strncasecmp(valuestring, "float", strlen("float")) == 0)
		to_message->AddFloat(curname, atof(valuestring + strlen("float(")));
	else if (strncasecmp(valuestring, "double", strlen("double")) == 0)
		to_message->AddDouble(curname, atof(valuestring + strlen("double(")));
	else if (strncasecmp(valuestring, "BPoint", strlen("BPoint")) == 0) {
		float x, y;
		x = atof(valuestring + strlen("BPoint("));
		if (strchr(valuestring, ','))
			y = atof(strchr(valuestring, ',') + 1);
		else if (strchr(valuestring, ' '))
			y = atof(strchr(valuestring, ' ') + 1);
		else	// bad syntax
			y = 0.0f;
		to_message->AddPoint(curname, BPoint(x,y));
	} else if (strncasecmp(valuestring, "BRect", strlen("BRect")) == 0) {
		float l = 0.0f, t = 0.0f, r = 0.0f, b = 0.0f;
		char *ptr;
		l = atof(valuestring + strlen("BRect("));
		ptr = strchr(valuestring, ',');
		if (ptr) {
			t = atof(ptr + 1);
			ptr = strchr(ptr + 1, ',');
			if (ptr) {
				r = atof(ptr + 1);
				ptr = strchr(ptr + 1, ',');
				if (ptr)
					b = atof(ptr + 1);
			}
		}

		to_message->AddRect(curname, BRect(l,t,r,b));
	} else if (strncasecmp(valuestring, "rgb_color", strlen("rgb_color")) == 0) {
		rgb_color clr;
		char *ptr;
		clr.red = atol(valuestring + strlen("rgb_color("));
		ptr = strchr(valuestring, ',');
		if (ptr) {
			clr.green = atol(ptr + 1);
			ptr = strchr(ptr + 1, ',');
			if (ptr) {
				clr.blue = atol(ptr + 1);
				ptr = strchr(ptr + 1, ',');
				if (ptr)
					clr.alpha = atol(ptr + 1);
			}
		}

		to_message->AddData(curname, B_RGB_COLOR_TYPE, &clr, sizeof(rgb_color));
	} else if (strncasecmp(valuestring, "file", strlen("file")) == 0) {
		entry_ref file_ref;

		// remove the last ] or )
		if (valuestring[strlen(valuestring) - 1] == ')'
			|| valuestring[strlen(valuestring) - 1] == ']')
			valuestring[strlen(valuestring) - 1] = 0;

		if (get_ref_for_path(valuestring + 5, &file_ref) != B_OK)
			return B_ENTRY_NOT_FOUND;

		// check if the ref is valid
		BEntry entry;
		if (entry.SetTo(&file_ref) != B_OK)
			return B_ENTRY_NOT_FOUND;
		//if(!entry.Exists())  return B_ENTRY_NOT_FOUND;

		// add both ways, refsreceived needs it as "refs" while scripting needs "data"
		to_message->AddRef("refs", &file_ref);
		to_message->AddRef(curname, &file_ref);
	} else {
		// it is string
		// does it begin with a quote?
		if (valuestring[0] == '\"') {
			if (valuestring[strlen(valuestring) - 1] == '\"')
				valuestring[strlen(valuestring) - 1] = 0;
			to_message->AddString(curname, valuestring + 1);
		} else
			to_message->AddString(curname, valuestring);
	}

	return B_OK;
}


void
print_message(BMessage *message)
{
	BList textlist;
	add_message_contents(&textlist, message, 0);

	char *whatString = get_datatype_string(message->what);
	printf("BMessage(%s):\n", whatString);
	delete[] whatString;
	for (int32 i = 0; i < textlist.CountItems(); i++) {
		printf("   %s\n", (char*)textlist.ItemAt(i));
		free(textlist.ItemAt(i));
	}
}


void
add_message_contents(BList *textlist, BMessage *msg, int32 level)
{
	int32 count;
	int32 i, j;
	type_code typefound;
	ssize_t sizefound;
#ifdef HAIKU_TARGET_PLATFORM_DANO
	const char *namefound;
#else
	char *namefound;
#endif
	void *voidptr;
	BMessage a_message;
	char *textline, *datatype, *content;

	// go though all message data
	count = msg->CountNames(B_ANY_TYPE);
	for (i=0; i < count; i++) {
		msg->GetInfo(B_ANY_TYPE, i, &namefound, &typefound);
		j = 0;

		while (msg->FindData(namefound, typefound, j++, (const void **)&voidptr,
				&sizefound) == B_OK) {
			datatype = get_datatype_string(typefound);
			content = format_data(typefound, (char*)voidptr, sizefound);
			textline = (char*)malloc(20 + level * 4 + strlen(namefound)
					+ strlen(datatype) + strlen(content));
			memset(textline, 32, 20 + level * 4);
			sprintf(textline + level * 4, "\"%s\" (%s) : %s", namefound,
				datatype, content);
			textlist->AddItem(textline);
			delete[] datatype;
			delete[] content;

			if (typefound == B_MESSAGE_TYPE) {
				msg->FindMessage(namefound, j - 1, &a_message);
				add_message_contents(textlist, &a_message, level + 1);
			} else if (typefound == B_RAW_TYPE && strcmp(namefound,
					"_previous_") == 0) {
				if (a_message.Unflatten((const char *)voidptr) == B_OK)
					add_message_contents(textlist, &a_message, level + 1);
			}
		}
	}
}


char *
get_datatype_string(int32 type)
{
	char *str = new char[128];

	switch (type) {
		case B_ANY_TYPE:	strcpy(str, "B_ANY_TYPE"); break;
		case B_ASCII_TYPE:	strcpy(str, "B_ASCII_TYPE"); break;
		case B_BOOL_TYPE:	strcpy(str, "B_BOOL_TYPE"); break;
		case B_CHAR_TYPE:	strcpy(str, "B_CHAR_TYPE"); break;
		case B_COLOR_8_BIT_TYPE:	strcpy(str, "B_COLOR_8_BIT_TYPE"); break;
		case B_DOUBLE_TYPE:	strcpy(str, "B_DOUBLE_TYPE"); break;
		case B_FLOAT_TYPE:	strcpy(str, "B_FLOAT_TYPE"); break;
		case B_GRAYSCALE_8_BIT_TYPE:	strcpy(str, "B_GRAYSCALE_8_BIT_TYPE"); break;
		case B_INT64_TYPE:	strcpy(str, "B_INT64_TYPE"); break;
		case B_INT32_TYPE:	strcpy(str, "B_INT32_TYPE"); break;
		case B_INT16_TYPE:	strcpy(str, "B_INT16_TYPE"); break;
		case B_INT8_TYPE:	strcpy(str, "B_INT8_TYPE"); break;
		case B_MESSAGE_TYPE:	strcpy(str, "B_MESSAGE_TYPE"); break;
		case B_MESSENGER_TYPE:	strcpy(str, "B_MESSENGER_TYPE"); break;
		case B_MIME_TYPE:	strcpy(str, "B_MIME_TYPE"); break;
		case B_MONOCHROME_1_BIT_TYPE:	strcpy(str, "B_MONOCHROME_1_BIT_TYPE"); break;
		case B_OBJECT_TYPE:	strcpy(str, "B_OBJECT_TYPE"); break;
		case B_OFF_T_TYPE:	strcpy(str, "B_OFF_T_TYPE"); break;
		case B_PATTERN_TYPE:	strcpy(str, "B_PATTERN_TYPE"); break;
		case B_POINTER_TYPE:	strcpy(str, "B_POINTER_TYPE"); break;
		case B_POINT_TYPE:	strcpy(str, "B_POINT_TYPE"); break;
		case B_RAW_TYPE:	strcpy(str, "B_RAW_TYPE"); break;
		case B_RECT_TYPE:	strcpy(str, "B_RECT_TYPE"); break;
		case B_REF_TYPE:	strcpy(str, "B_REF_TYPE"); break;
		case B_RGB_32_BIT_TYPE:	strcpy(str, "B_RGB_32_BIT_TYPE"); break;
		case B_RGB_COLOR_TYPE:	strcpy(str, "B_RGB_COLOR_TYPE"); break;
		case B_SIZE_T_TYPE:	strcpy(str, "B_SIZE_T_TYPE"); break;
		case B_SSIZE_T_TYPE:	strcpy(str, "B_SSIZE_T_TYPE"); break;
		case B_STRING_TYPE:	strcpy(str, "B_STRING_TYPE"); break;
		case B_TIME_TYPE :	strcpy(str, "B_TIME_TYPE"); break;
		case B_UINT64_TYPE:	strcpy(str, "B_UINT64_TYPE"); break;
		case B_UINT32_TYPE:	strcpy(str, "B_UINT32_TYPE"); break;
		case B_UINT16_TYPE:	strcpy(str, "B_UINT16_TYPE"); break;
		case B_UINT8_TYPE:	strcpy(str, "B_UINT8_TYPE"); break;
		case B_PROPERTY_INFO_TYPE: strcpy(str, "B_PROPERTY_INFO_TYPE"); break;
		// message constants:
		case B_ABOUT_REQUESTED:	strcpy(str, "B_ABOUT_REQUESTED"); break;
		case B_WINDOW_ACTIVATED:	strcpy(str, "B_WINDOW_ACTIVATED"); break;
		case B_ARGV_RECEIVED:	strcpy(str, "B_ARGV_RECEIVED"); break;
		case B_QUIT_REQUESTED:	strcpy(str, "B_QUIT_REQUESTED"); break;
		case B_CANCEL:	strcpy(str, "B_CANCEL"); break;
		case B_KEY_DOWN:	strcpy(str, "B_KEY_DOWN"); break;
		case B_KEY_UP:	strcpy(str, "B_KEY_UP"); break;
		case B_MINIMIZE:	strcpy(str, "B_MINIMIZE"); break;
		case B_MOUSE_DOWN:	strcpy(str, "B_MOUSE_DOWN"); break;
		case B_MOUSE_MOVED:	strcpy(str, "B_MOUSE_MOVED"); break;
		case B_MOUSE_ENTER_EXIT:	strcpy(str, "B_MOUSE_ENTER_EXIT"); break;
		case B_MOUSE_UP:	strcpy(str, "B_MOUSE_UP"); break;
		case B_PULSE:	strcpy(str, "B_PULSE"); break;
		case B_READY_TO_RUN:	strcpy(str, "B_READY_TO_RUN"); break;
		case B_REFS_RECEIVED:	strcpy(str, "B_REFS_RECEIVED"); break;
		case B_SCREEN_CHANGED:	strcpy(str, "B_SCREEN_CHANGED"); break;
		case B_VALUE_CHANGED:	strcpy(str, "B_VALUE_CHANGED"); break;
		case B_VIEW_MOVED:	strcpy(str, "B_VIEW_MOVED"); break;
		case B_VIEW_RESIZED:	strcpy(str, "B_VIEW_RESIZED"); break;
		case B_WINDOW_MOVED:	strcpy(str, "B_WINDOW_MOVED"); break;
		case B_WINDOW_RESIZED:	strcpy(str, "B_WINDOW_RESIZED"); break;
		case B_WORKSPACES_CHANGED:	strcpy(str, "B_WORKSPACES_CHANGED"); break;
		case B_WORKSPACE_ACTIVATED:	strcpy(str, "B_WORKSPACE_ACTIVATED"); break;
		case B_ZOOM:	strcpy(str, "B_ZOOM"); break;
		case _APP_MENU_:	strcpy(str, "_APP_MENU_"); break;
		case _BROWSER_MENUS_:	strcpy(str, "_BROWSER_MENUS_"); break;
		case _MENU_EVENT_:	strcpy(str, "_MENU_EVENT_"); break;
		case _QUIT_:	strcpy(str, "_QUIT_"); break;
		case _VOLUME_MOUNTED_:	strcpy(str, "_VOLUME_MOUNTED_"); break;
		case _VOLUME_UNMOUNTED_:	strcpy(str, "_VOLUME_UNMOUNTED_"); break;
		case _MESSAGE_DROPPED_:	strcpy(str, "_MESSAGE_DROPPED_"); break;
		case _MENUS_DONE_:	strcpy(str, "_MENUS_DONE_"); break;
		case _SHOW_DRAG_HANDLES_:	strcpy(str, "_SHOW_DRAG_HANDLES_"); break;
		case B_SET_PROPERTY:	strcpy(str, "B_SET_PROPERTY"); break;
		case B_GET_PROPERTY:	strcpy(str, "B_GET_PROPERTY"); break;
		case B_CREATE_PROPERTY:	strcpy(str, "B_CREATE_PROPERTY"); break;
		case B_DELETE_PROPERTY:	strcpy(str, "B_DELETE_PROPERTY"); break;
		case B_COUNT_PROPERTIES:	strcpy(str, "B_COUNT_PROPERTIES"); break;
		case B_EXECUTE_PROPERTY:      strcpy(str, "B_EXECUTE_PROPERTY"); break;
		case B_GET_SUPPORTED_SUITES:	strcpy(str, "B_GET_SUPPORTED_SUITES"); break;
		case B_CUT:	strcpy(str, "B_CUT"); break;
		case B_COPY:	strcpy(str, "B_COPY"); break;
		case B_PASTE:	strcpy(str, "B_PASTE"); break;
		case B_SELECT_ALL:	strcpy(str, "B_SELECT_ALL"); break;
		case B_SAVE_REQUESTED:	strcpy(str, "B_SAVE_REQUESTED"); break;
		case B_MESSAGE_NOT_UNDERSTOOD:	strcpy(str, "B_MESSAGE_NOT_UNDERSTOOD"); break;
		case B_NO_REPLY:	strcpy(str, "B_NO_REPLY"); break;
		case B_REPLY:	strcpy(str, "B_REPLY"); break;
		case B_SIMPLE_DATA:	strcpy(str, "B_SIMPLE_DATA"); break;
		//case B_MIME_DATA	 :	strcpy(str, "B_MIME_DATA"); break;
		case B_ARCHIVED_OBJECT:	strcpy(str, "B_ARCHIVED_OBJECT"); break;
		case B_UPDATE_STATUS_BAR:	strcpy(str, "B_UPDATE_STATUS_BAR"); break;
		case B_RESET_STATUS_BAR:	strcpy(str, "B_RESET_STATUS_BAR"); break;
		case B_NODE_MONITOR:	strcpy(str, "B_NODE_MONITOR"); break;
		case B_QUERY_UPDATE:	strcpy(str, "B_QUERY_UPDATE"); break;
		case B_BAD_SCRIPT_SYNTAX: strcpy(str, "B_BAD_SCRIPT_SYNTAX"); break;

		// specifiers:
		case B_NO_SPECIFIER:	strcpy(str, "B_NO_SPECIFIER"); break;
		case B_DIRECT_SPECIFIER:	strcpy(str, "B_DIRECT_SPECIFIER"); break;
		case B_INDEX_SPECIFIER:		strcpy(str, "B_INDEX_SPECIFIER"); break;
		case B_REVERSE_INDEX_SPECIFIER:	strcpy(str, "B_REVERSE_INDEX_SPECIFIER"); break;
		case B_RANGE_SPECIFIER:	strcpy(str, "B_RANGE_SPECIFIER"); break;
		case B_REVERSE_RANGE_SPECIFIER:	strcpy(str, "B_REVERSE_RANGE_SPECIFIER"); break;
		case B_NAME_SPECIFIER:	strcpy(str, "B_NAME_SPECIFIER"); break;

		case B_ERROR:	strcpy(str, "B_ERROR"); break;

		default:	// unknown
			id_to_string(type, str);
			break;
	}
	return str;
}


char *
format_data(int32 type, char *ptr, long size)
{
	char idtext[32];
	char *str;
	float *fptr;
	double *dptr;
	entry_ref aref;
	BEntry entry;
	BPath path;
	int64 i64;
	int32 i32;
	int16 i16;
	int8 i8;
	uint64 ui64;
	uint32 ui32;
	uint16 ui16;
	uint8 ui8;
	BMessage anothermsg;
	char *tempstr;

	if (size <= 0L) {
		str = new char[1];
		*str = 0;
		return str;
	}

	switch (type) {
		case B_MIME_TYPE:
		case B_ASCII_TYPE:
		case B_STRING_TYPE:
			if (size > 512)
				size = 512;
			str = new char[size + 4];
			*str='\"';
			strncpy(str + 1, ptr, size);
			strcat(str, "\"");
			break;

		case B_POINTER_TYPE:
			str = new char[64];
			sprintf(str, "%p", *(void**)ptr);
			break;

		case B_REF_TYPE:
			str = new char[1024];
			anothermsg.AddData("myref", B_REF_TYPE, ptr, size);
			anothermsg.FindRef("myref", &aref);
			if (entry.SetTo(&aref)==B_OK){
				entry.GetPath(&path);
				strcpy(str, path.Path());
			} else
				strcpy(str, "invalid entry_ref");
			break;

		case B_SSIZE_T_TYPE:
		case B_INT64_TYPE:
			str = new char[64];
			i64 = *(int64*)ptr;
			sprintf(str, "%" B_PRId64 " (0x%" B_PRIx64 ")", i64, i64);
			break;

		case B_SIZE_T_TYPE:
		case B_INT32_TYPE:
			str = new char[64];
			i32 = *(int32*)ptr;
			sprintf(str, "%" B_PRId32 " (0x%08" B_PRId32 ")", i32, i32);
			break;

		case B_INT16_TYPE:
			str = new char[64];
			i16 = *(int16*)ptr;
			sprintf(str, "%d (0x%04X)", i16, i16);
			break;

		case B_CHAR_TYPE:
		case B_INT8_TYPE:
			str = new char[64];
			i8 = *(int8*)ptr;
			sprintf(str, "%d (0x%02X)", i8, i8);
			break;

		case B_UINT64_TYPE:
			str = new char[64];
			ui64 = *(uint64*)ptr;
			sprintf(str, "%" B_PRIu64 " (0x%" B_PRIx64 ")", ui64, ui64);
			break;

		case B_UINT32_TYPE:
			str = new char[64];
			ui32 = *(uint32*)ptr;
			sprintf(str, "%" B_PRIu32 " (0x%08" B_PRIx32 ")", ui32, ui32);
			break;

		case B_UINT16_TYPE:
			str = new char[64];
			ui16 = *(uint16*)ptr;
			sprintf(str, "%u (0x%04X)", ui16, ui16);
			break;

		case B_UINT8_TYPE:
			str = new char[64];
			ui8 = *(uint8*)ptr;
			sprintf(str, "%u (0x%02X)", ui8, ui8);
			break;

		case B_BOOL_TYPE:
			str = new char[10];
			if (*ptr)
				strcpy(str, "TRUE");
			else
				strcpy(str, "FALSE");
			break;

		case B_FLOAT_TYPE:
			str = new char[40];
			fptr = (float*)ptr;
			sprintf(str, "%.3f", *fptr);
			break;

		case B_DOUBLE_TYPE:
			str = new char[40];
			dptr = (double*)ptr;
			sprintf(str, "%.3f", *dptr);
			break;

		case B_RECT_TYPE:
			str = new char[200];
			fptr = (float*)ptr;
			sprintf(str, "BRect(%.1f, %.1f, %.1f, %.1f)", fptr[0], fptr[1],
				fptr[2], fptr[3]);
			break;

		case B_POINT_TYPE:
			str = new char[200];
			fptr = (float*)ptr;
			sprintf(str, "BPoint(%.1f, %.1f)", fptr[0], fptr[1]);
			break;

		case B_RGB_COLOR_TYPE:
			str = new char[64];
			sprintf(str, "Red=%u  Green=%u  Blue=%u  Alpha=%u",
				((uint8*)ptr)[0], ((uint8*)ptr)[1], ((uint8*)ptr)[2],
				((uint8*)ptr)[3]);
			break;

		case B_COLOR_8_BIT_TYPE:
			str = new char[size * 6 + 4];
			*str = 0;
			for (int32 i = 0; i < min_c(256, size); i++) {
				sprintf(idtext, "%u ", ((unsigned char*)ptr)[i]);
				strcat(str,idtext);
			}
			*(str+strlen(str)-2) = 0;
			break;

		case B_MESSAGE_TYPE:
			str = new char[64];
			if (anothermsg.Unflatten((const char *)ptr) == B_OK) {
				char *whatString = get_datatype_string(anothermsg.what);
				sprintf(str, "what=%s", whatString);
				delete[] whatString;
			} else
				strcpy(str, "error when unflattening");
			break;

		case B_PROPERTY_INFO_TYPE:
		{
			BPropertyInfo propinfo;
			if (propinfo.Unflatten(B_PROPERTY_INFO_TYPE, (const void *)ptr,
					size) == B_OK) {
				str = new char[size * 32];	// an approximation

				const property_info *pinfo = propinfo.Properties();

				sprintf(str, "\n        property   commands                            "
					"specifiers              types\n-----------------------------------"
					"----------------------------------------------------------------\n");
				for (int32 pinfo_index = 0; pinfo_index < propinfo.CountProperties(); pinfo_index++) {
					strcat(str,  "                "
						+ (strlen(pinfo[pinfo_index].name) < 16 ?
							strlen(pinfo[pinfo_index].name) : 16));
					strcat(str, pinfo[pinfo_index].name);
					strcat(str, "   ");
					char *start = str + strlen(str);

					for (int32 i = 0; i < 10 && pinfo[pinfo_index].commands[i];
							i++) {
						tempstr = get_datatype_string(
							pinfo[pinfo_index].commands[i]);
						strcat(str, tempstr);
						strcat(str, " ");
						delete[] tempstr;
					}

					// pad the rest with spaces
					if (strlen(start) < 36) {
						strcat(str, "                                    "
							+ strlen(start));
					} else
						strcat(str, "  " );

					for (int32 i = 0; i < 10 && pinfo[pinfo_index].specifiers[i]; i++) {
						switch (pinfo[pinfo_index].specifiers[i]) {
							case B_NO_SPECIFIER:
								strcat(str, "NONE ");
								break;
							case B_DIRECT_SPECIFIER:
								strcat(str, "DIRECT ");
								break;
							case B_INDEX_SPECIFIER:
								strcat(str, "INDEX ");
								break;
							case B_REVERSE_INDEX_SPECIFIER:
								strcat(str, "REV.INDEX ");
								break;
							case B_RANGE_SPECIFIER:
								strcat(str, "RANGE ");
								break;
							case B_REVERSE_RANGE_SPECIFIER:
								strcat(str, "REV.RANGE ");
								break;
							case B_NAME_SPECIFIER:
								strcat(str, "NAME ");
								break;
							case B_ID_SPECIFIER:
								strcat(str, "ID ");
								break;
							default:
								strcat(str, "<NONE> ");
								break;
							}
						}

						// pad the rest with spaces
						if (strlen(start) < 60) {
							strcat(str, "                                      "
								"                      " + strlen(start));
						} else
							strcat(str, "  ");
						for (int32 i = 0; i < 10
								&& pinfo[pinfo_index].types[i] != 0; i++) {
							uint32 type = pinfo[pinfo_index].types[i];
							char str2[6];
							snprintf(str2, sizeof(str2), "%c%c%c%c ",
								int(type & 0xFF000000) >> 24,
								int(type & 0xFF0000) >> 16,
								int(type & 0xFF00) >> 8,
								(int)type & 0xFF);
							strcat(str, str2);
						}

						for (int32 i = 0; i < 3; i++) {
							for (int32 j = 0; j < 5
									&& pinfo[pinfo_index].ctypes[i].pairs[j].type
										!= 0; j++) {
								uint32 type = pinfo[pinfo_index].ctypes[i].pairs[j].type;
								char str2[strlen(pinfo[pinfo_index].ctypes[i].pairs[j].name) + 8];
								snprintf(str2, sizeof(str2),
									"(%s %c%c%c%c)",
									pinfo[pinfo_index].ctypes[i].pairs[j].name,
									int(type & 0xFF000000) >> 24,
									int(type & 0xFF0000) >> 16,
									int(type & 0xFF00) >> 8,
									(int)type & 0xFF);
								strcat(str, str2);
							}
						}
						strcat(str, "\n");

						// is there usage info?
						if (pinfo[pinfo_index].usage) {
							strcat(str, "                   Usage: ");
							strcat(str, pinfo[pinfo_index].usage);
							strcat(str, "\n");
						}
					}


					// handle value infos....
				const value_info *vinfo = propinfo.Values();
				int32 vinfo_count = propinfo.CountValues();
#if TEST_VALUEINFO>0
				value_info vinfo[10] = {
					{"Backup", 'back', B_COMMAND_KIND,
						"This command backs up your hard drive."},
					{"Abort", 'abor', B_COMMAND_KIND,
						"Stops the current operation..."},
					{"Type Code", 'type', B_TYPE_CODE_KIND,
						"Type code info..."}
				};
				vinfo_count = 3;
#endif

				if (vinfo && vinfo_count > 0) {
					sprintf(str + strlen(str),
						"\n            name   value                            "
						"   kind\n---------------------------------------------"
						"-----------------------------------\n");

					for (int32 vinfo_index = 0; vinfo_index < vinfo_count;
						vinfo_index++) {
						char *start = str + strlen(str);
						strcat(str, "                " + (strlen(vinfo[vinfo_index].name) < 16 ? strlen(vinfo[vinfo_index].name) : 16));
						strcat(str, vinfo[vinfo_index].name);
						strcat(str, "   ");

						sprintf(str + strlen(str), "0x%8" B_PRIx32 " (",
							vinfo[vinfo_index].value);
						id_to_string(vinfo[vinfo_index].value, str + strlen(str));
						strcat(str, ")");

							// pad the rest with spaces
						if (strlen(start) < 36 + 19) {
							strcat(str, "                                      "
								"                 " + strlen(start));
						} else
							strcat(str, "  ");

						switch (vinfo[vinfo_index].kind) {
							case B_COMMAND_KIND:
								strcat(str, "COMMAND         ");
								break;

							case B_TYPE_CODE_KIND:
								strcat(str, "TYPE CODE       ");
								break;

							default:
								strcat(str, "unknown         ");
								break;
						}

						strcat(str, "\n");

							// is there usage info?
						if (vinfo[vinfo_index].usage) {
							strcat(str, "                   Usage: ");
							strcat(str, vinfo[vinfo_index].usage);
							strcat(str, "\n");
						}
					}
				}
			} else {
				str = new char[64];
				strcpy(str, "error when unflattening");
			}
			break;
		}

		default:
			str = new char[min_c(256, size) * 20 + 4];
			*str = 0;
			for (int32 i = 0; i < min_c(256, size); i++) {
				sprintf(idtext, "0x%02X, ", (uint16)ptr[i]);
				strcat(str, idtext);
			}
			*(str + strlen(str) - 2) = 0;
			break;
	}

	return str;
}


char *
id_to_string(long ID, char *here)
{
	uint8 digit0 = (ID>>24)& 255;
	uint8 digit1 = (ID>>16)& 255;
	uint8 digit2 = (ID>>8) & 255;
	uint8 digit3 = (ID) & 255;
	bool itsvalid = false;

	if (digit0 == 0) {
		if (digit1 == 0) {
			if (digit2 == 0) {
				// 1 digits
				itsvalid = is_valid_char(digit3);
				sprintf(here, "'%c'", digit3);
			} else {
				// 2 digits
				itsvalid = is_valid_char(digit2) && is_valid_char(digit3);
				sprintf(here, "'%c%c'", digit2, digit3);
			}
		} else {
			// 3 digits
			itsvalid = is_valid_char(digit1) && is_valid_char(digit2)
				&& is_valid_char(digit3);
			sprintf(here, "'%c%c%c'", digit1, digit2, digit3);
		}
	} else {
		// 4 digits
		itsvalid = is_valid_char(digit0) && is_valid_char(digit1)
			&& is_valid_char(digit2) && is_valid_char(digit3);
		sprintf(here, "'%c%c%c%c'", digit0, digit1, digit2, digit3);
	}

	if (!itsvalid)
		sprintf(here, "%ldL", ID);

	return here;
}


bool
is_valid_char(uint8 c)
{
	return c >= 32 && c < 128;
}