text: #ifdef WIN32
  /* don't build this for now */
  #else
  #include "transform.h"
  
  /*
   * debugging/dumping/error handling
   */
  
  char*
  event_type(yaml_event_t* ep) 
  {
      switch (ep->type) 
  	{
          case YAML_NO_EVENT:             return("YAML_NO_EVENT\n");              break;
          case YAML_STREAM_START_EVENT:   return("YAML_STREAM_START_EVENT\n");    break;
          case YAML_STREAM_END_EVENT:     return("YAML_STREAM_END_EVENT\n");      break;
          case YAML_DOCUMENT_START_EVENT: return("YAML_DOCUMENT_START_EVENT\n");  break;
          case YAML_DOCUMENT_END_EVENT:   return("YAML_DOCUMENT_END_EVENT\n");    break;
          case YAML_ALIAS_EVENT:          return("YAML_ALIAS_EVENT\n");           break;
          case YAML_SCALAR_EVENT:         return("YAML_SCALAR_EVENT\n");          break;
          case YAML_SEQUENCE_START_EVENT: return("YAML_SEQUENCE_START_EVENT\n");  break;
          case YAML_SEQUENCE_END_EVENT:   return("YAML_SEQUENCE_END_EVENT\n");    break;
          case YAML_MAPPING_START_EVENT:  return("YAML_MAPPING_START_EVENT\n");   break;
          case YAML_MAPPING_END_EVENT:    return("YAML_MAPPING_END_EVENT\n");     break;
          default:                        return("unknown event\n");              break;
      }
  }
  
  void
  debug_event_type(yaml_event_t* ep) 
  {
  	printf("%s", event_type(ep));
  }
  
  
  /* forward decl for debug_keyvalue */
  void 
  debug_mapping(struct mapping* map, int indent);
  
  void
  debug_keyvalue(struct keyvalue* kv, int indent)
  {
      printf("%*s%s: ", indent, "", kv->key);
      if (kv->type == KV_SCALAR)
          printf("%s\n", kv->scalar);
      else 
      {
          printf("\n");
          if (kv->map)
              debug_mapping(kv->map, indent+1);
      }
  }
  
  void
  debug_mapping(struct mapping* map, int indent)
  {
      struct keyvalue* kv;
      for (kv = map->kvlist; kv; kv=kv->nxt)
          debug_keyvalue(kv, indent);
  }
  
  
  /*
   * yaml structural parse errors
   */
  
  int
  unexpected_event(struct streamstate* stp) 
  {
      fprintf(stderr, "%s:%zu:%zu:unexpected event: %s\n",
  			stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1, 
  			event_type(&stp->event));
  	return 1;
  }
  
  int
  error_parser_initialize_failed(struct streamstate* stp) 
  {
      fprintf(stderr, "%s:0:0:yaml_parser_initialize failed\n", stp->filename);
  	return 1;
  }
  
  int
  error_parser_parse_failed(struct streamstate* stp) 
  {
      fprintf(stderr, "%s:%zu:%zu:yaml_parser_parse failed\n",
             stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1);
  	return 1;
  }
  
  int
  error_invalid_stream_start(struct streamstate* stp) 
  {
      fprintf(stderr, "%s:%zu:%zu:stream_start_event but stream has already been started\n",
  			stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1);
  	return 1;
  }
  
  int
  error_stream_not_started(struct streamstate* stp, char* what) 
  {
      fprintf(stderr, "%s:%zu:%zu:%s but stream has not been started\n",
  			stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1, what);
  	return 1;
  }
  
  int
  error_invalid_document_start(struct streamstate* stp) 
  {
      fprintf(stderr, "%s:%zu:%zu:document_start but already within a document\n",
  			stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1);
  	return 1;
  }
  
  int
  error_document_not_started(struct streamstate* stp, char* what) 
  {
      fprintf(stderr, "%s:%zu:%zu:%s but not within a document\n",
  			stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1, what);
  	return 1;
  }
  
  int
  error_no_current_mapping(struct streamstate* stp, char* what) 
  {
      fprintf(stderr, "%s:%zu:%zu:%s but not within a mapping\n",
  			stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1, what);
  	return 1;
  }
  
  
  /*
   * parse tree construction
   */
  
  char* 
  copy_scalar(struct streamstate* stp)
  {
      unsigned char* src  = stp->event.data.scalar.value;
      size_t         len  = stp->event.data.scalar.length;    
      char*          copy = malloc(len+1);
      if (copy) 
      {
          memcpy(copy, src, len);
          copy[len] = 0;
      } else
          snprintf(stp->errbuf, sizeof(stp->errbuf), 
  				 "%s:%zu:%zu:allocation failed for malloc(%zu)", 
                   stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1, len+1);
      return copy;
  }
  
  struct keyvalue*
  new_keyvalue(struct streamstate* stp, struct keyvalue* nxt)
  {
      struct keyvalue* kv = calloc(1, sizeof(struct keyvalue));
      if (kv)
          kv->nxt = nxt;
      else
          snprintf(stp->errbuf, sizeof(stp->errbuf), 
  				 "%s:%zu:%zu:allocation failed for calloc(1, sizeof(struct keyvalue))",
                   stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1);
      return kv;
  
  
  }
  
  struct mapping*
  new_mapping(struct streamstate* stp, struct mapping* nxt)
  {
      struct mapping* map = calloc(1, sizeof(struct mapping));
      if (map)
          map->nxt = nxt;
      else
          snprintf(stp->errbuf, sizeof(stp->errbuf), 
  				 "%s:%zu:%zu:allocation failed for calloc(1, sizeof(struct mapping))",
                   stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1);
      return map;
  }
  
  int
  handle_mapping_start(struct streamstate* stp)
  {
      stp->curmap = new_mapping(stp, stp->curmap);
      if (! stp->curmap)
          return 1;
  
      struct keyvalue* kv = stp->curkv;
      if (kv) 
      {
          /*
           * this mapping is the value for the current keyvalue
           */
          kv->type      = KV_MAPPING;
          kv->map       = stp->curmap;
          kv->valuemark = stp->event.start_mark;
          stp->curkv = NULL;
      } 
      else 
      {
          /*
           * this mapping is the top level document.
           */
          if (! stp->document)
              stp->document = stp->curmap;
      }
      return 0;
  }
  
  int
  handle_mapping_end(struct streamstate* stp)
  {
      stp->curmap = stp->curmap->nxt; /* pop mapping from stack */
      return 0;
  }
  
  int
  handle_scalar(struct streamstate* stp)
  {
      struct mapping*  map = stp->curmap;
      struct keyvalue* kv  = stp->curkv;
      if (kv) 
      {
          /*
           * this scalar is the value for current keyvalue
           */
          kv->type         = KV_SCALAR;
          kv->scalar       = copy_scalar(stp);
          if (! kv->scalar)
              return 1;
          kv->valuemark    = stp->event.start_mark;
          stp->curkv       = NULL;
      } 
      else 
      {
          /*
           * this scalar is the name of a new keyvalue pair
           */
          if (stp->event.data.scalar.length > MAX_KEYLEN) 
          {
              snprintf(stp->errbuf, sizeof(stp->errbuf), 
  					 "%s:%zu:%zu:key name exceeds maximum length %d",
                       stp->filename, stp->event.start_mark.line+1, stp->event.start_mark.column+1, MAX_KEYLEN);
              return 1;
          }
          kv = stp->curkv  = map->kvlist = new_keyvalue(stp, map->kvlist);
          if (! kv)
              return 1;
          kv->key          = copy_scalar(stp);
          if (! kv->key)
              return 1;
          kv->keymark      = stp->event.start_mark;
      }
      return 0;
  }
  
  
  int
  stage1_parse(struct streamstate* stp, FILE* file, int verbose)
  {
      int rc = 0;
      int done  = 0;
      int error = 0;
  
      rc = yaml_parser_initialize(&stp->parser);
  	if (! rc) 
  		return error_parser_initialize_failed(stp);
  
      yaml_parser_set_input_file(&stp->parser, file);
  
      while (!done) 
      {
          rc = yaml_parser_parse(&stp->parser, &stp->event);
          if (!rc) 
  		{
  			error = error_parser_parse_failed(stp);
  			break;
  		}
  
  		if (verbose)
  			debug_event_type(&stp->event);
  
          rc = 0;
          switch (stp->event.type) 
          {
              case YAML_STREAM_START_EVENT:
  				if (stp->stream_start)
  					rc = error_invalid_stream_start(stp);
  				else
  					stp->stream_start = 1;
                  break;
  
              case YAML_DOCUMENT_START_EVENT:
  				if (stp->document_start)
  					rc = error_invalid_document_start(stp);
  				else
  					stp->document_start = 1;
                  break;
  
              case YAML_MAPPING_START_EVENT:
  				if (! stp->stream_start)
  					rc = error_stream_not_started(stp, "mapping_start_event");
  				else if (! stp->document_start)
  					rc = error_document_not_started(stp, "mapping_start_event");
  				else
  					rc = handle_mapping_start(stp);
                  break;
  
  
              case YAML_MAPPING_END_EVENT:
  				if (! stp->stream_start)
  					rc = error_stream_not_started(stp, "mapping_end_event");
  				else if (! stp->document_start)
  					rc = error_document_not_started(stp, "mapping_end_event");
  				else if (stp->curmap == NULL) 
  					rc = error_no_current_mapping(stp, "mapping_end_event");
  				else
  					rc = handle_mapping_end(stp);
                  break;
  
              case YAML_SCALAR_EVENT:
  				if (! stp->stream_start)
  					rc = error_stream_not_started(stp, "scalar_event");
  				else if (! stp->document_start)
  					rc = error_document_not_started(stp, "scalar_event");
  				else if (stp->curmap == NULL) 
  					rc = error_no_current_mapping(stp, "scalar_event");
  				else
  					rc = handle_scalar(stp);
                  break;
  
              case YAML_DOCUMENT_END_EVENT:
  				if (! stp->stream_start)
  					rc = error_stream_not_started(stp, "document_end_event");
  				else if (! stp->document_start)
  					rc = error_document_not_started(stp, "document_end_event");
  				else
  					stp->document_start = 0;
                  break;
  
              case YAML_STREAM_END_EVENT:
  				if (! stp->stream_start)
  					rc = error_stream_not_started(stp, "stream_end_event");
  				else
  				{
  					stp->stream_start = 0;
  					done = 1;
  				}
                  break;
              default:
                  rc = unexpected_event(stp);
                  break;
          }
  
          yaml_event_delete(&stp->event);
          if (rc) 
          {
              error = 1;
              break;
          }
      }
  
      yaml_parser_delete(&stp->parser);
      return error;
  }
  
  /*
   * debugging/dumping/error handling
   */
  
  void
  dump_transform(struct transform* tr)
  {
  	printf(" %s:\n", tr->kv->key);
  	printf("  TYPE: %s\n", tr->type == TR_INPUT ? "input" : (tr->type == TR_OUTPUT ? "output" : "unknown")); 
  	printf("  COMMAND: %s\n", tr->command);
  	printf("  STDERR: %s\n", tr->errs == TR_ER_CONSOLE ? "console" : (tr->errs == TR_ER_SERVER ? "server" : "unknown")); 
  	printf("  CONTENTS: %s\n", tr->content == TR_FN_DATA ? "data" : (tr->content == TR_FN_PATHS ? "paths" : "unknown"));
  	if (tr->safe)
  		printf("  SAFE: %s\n", tr->safe);
  }
  
  void
  debug_transforms(struct transform* trlist)
  {
      struct transform* tr;
      printf("TRANSFORMATIONS:\n");
      for (tr = trlist; tr; tr=tr->nxt) 
  		dump_transform(tr);
  }
  
  int
  format_onlyfilename(struct parsestate* psp, char* fmt)
  {
      snprintf(psp->errbuf, sizeof(psp->errbuf), fmt, psp->filename);
      return 1;
  }
  
  int
  format_key1(struct parsestate* psp, char* fmt, char* key, yaml_mark_t mark)
  {
      snprintf(psp->errbuf, sizeof(psp->errbuf), fmt, psp->filename, mark.line+1, mark.column+1, key);
      return 1;
  }
  
  int
  format_key2(struct parsestate* psp, char* fmt, char* key, yaml_mark_t mark, char* value)
  {
      snprintf(psp->errbuf, sizeof(psp->errbuf), fmt, psp->filename, mark.line+1, mark.column+1, key, value);
      return 1;
  }
  
  int
  error_failed_to_find_transformations(struct parsestate* psp)
  {
      return format_onlyfilename(psp, "%s:0:0:failed to find TRANSFORMATIONS");
  }
  
  int
  error_transformations_not_proper_yaml_map(struct parsestate* psp, struct keyvalue* kv)
  {
      char* fmt = ERRFMT "key '%s' is not a proper YAML map\n";
      return format_key1(psp, fmt, kv->key, kv->keymark);
  }
  
  int
  error_transformation_not_proper_yaml_map(struct parsestate* psp, struct keyvalue* kv)
  {
      char* fmt = ERRFMT "transformation '%s': not a proper YAML map\n";
      return format_key1(psp, fmt, kv->key, kv->keymark);
  }
  
  int
  error_failed_to_find_type_for_transformation(struct parsestate* psp, struct keyvalue* kv)
  {
      char* fmt = ERRFMT "transformation '%s': failed to find TYPE\n";
      return format_key1(psp, fmt, kv->key, kv->keymark);
  }
  
  int
  error_transformation_type_not_scalar(struct parsestate* psp, struct keyvalue* trkv, struct keyvalue* kv)
  {
      char* fmt = ERRFMT "transformation '%s': TYPE not a scalar (expected TYPE:input or TYPE:output)\n";
      return format_key1(psp, fmt, trkv->key, kv->keymark);
  }
  
  int
  error_invalid_type_for_transformation(struct parsestate* psp, struct keyvalue* trkv, struct keyvalue* kv)
  {
      char* fmt = ERRFMT "transformation '%s': invalid TYPE: '%s' (expected TYPE:input or TYPE:output)\n";
      return format_key2(psp, fmt, trkv->key, kv->keymark, kv->scalar);
  }
  
  int
  error_failed_to_find_command_for_transformation(struct parsestate* psp, struct keyvalue* kv)
  {
      char* fmt = ERRFMT "transformation '%s': failed to find COMMAND\n";
      return format_key1(psp, fmt, kv->key, kv->keymark);
  }
  
  int
  error_invalid_command_for_transformation(struct parsestate* psp, struct keyvalue* trkv, struct keyvalue* kv)
  {
      char* fmt = ERRFMT "transformation '%s': invalid COMMAND (expected a simple non-empty string)\n";
      return format_key1(psp, fmt, trkv->key, kv->keymark);
  }
  
  int
  error_stderr_not_scalar(struct parsestate* psp, struct keyvalue* trkv, struct keyvalue* kv)
  {
  	char* fmt = ERRFMT "transformation '%s': STDERR not a scalar (expected a simple non-empty string)\n";
  	return format_key1(psp, fmt, trkv->key, kv->keymark);
  }
  
  int
  error_invalid_stderr_for_transformation(struct parsestate* psp, struct keyvalue* trkv, struct keyvalue* kv)
  {
  	char* fmt = ERRFMT "transformation '%s': invalid STDERR (expected STDERR:console or STDERR:server)\n";
  	return format_key1(psp, fmt, trkv->key, kv->keymark);
  }
  
  int
  error_content_not_scalar(struct parsestate* psp, struct keyvalue* trkv, struct keyvalue* kv)
  {
  	char* fmt = ERRFMT "transformation '%s': CONTENT not a scalar (expected CONTENT:data or CONTENT:paths)\n";
  	return format_key1(psp, fmt, trkv->key, kv->keymark);
  }
  
  int
  error_invalid_content_for_transformation(struct parsestate* psp, struct keyvalue* trkv, struct keyvalue* kv)
  {
      char* fmt = ERRFMT "transformation '%s': invalid CONTENT (expected CONTENT:data or CONTENT:paths)\n";
      return format_key1(psp, fmt, trkv->key, kv->keymark);
  }
  
  int
  error_safe_not_scalar(struct parsestate* psp, struct keyvalue* trkv, struct keyvalue* kv)
  {
      char* fmt = ERRFMT "transformation '%s': SAFE not a scalar (expected a simple non-empty string)\n";
      return format_key1(psp, fmt, trkv->key, kv->keymark);
  }
  
  int
  error_safe_not_valid_regex(struct parsestate* psp, struct keyvalue* trkv, struct keyvalue* kv, regex_t* r, int rc)
  {
  	char* fmt = ERRFMT "transformation '%s': could not compile SAFE regex: ";
  	int   len = snprintf(psp->errbuf, sizeof(psp->errbuf), fmt, psp->filename, kv->keymark.line+1, kv->keymark.column+1, trkv->key);
  	char* end = psp->errbuf + len;
  	char* lim = psp->errbuf + sizeof(psp->errbuf) - 2;
  	if (end < lim) {
  		regerror(rc, r, end, lim-end);
  		end += strlen(end);
  		*end++ = '\n';
  		*end++ = 0;
  	}
  	return 1;
  }
  
  
  /*
   * transform validation and construction
   */
  
  struct transform*
  new_transform(struct parsestate* psp, struct transform* nxt)
  {
      struct transform* tr = calloc(1, sizeof(struct transform));
      if (tr)
          tr->nxt = nxt;
      else
          snprintf(psp->errbuf, sizeof(psp->errbuf), 
  				 "%s:0:0:allocation failed for calloc(1, sizeof(struct transform))\n",
                   psp->filename);
      return tr;
  }
  
  struct keyvalue*
  find_keyvalue(struct keyvalue* kvlist, char* name)
  {
      struct keyvalue* kv;
      for (kv = kvlist; kv; kv=kv->nxt) 
      {
          if (strcmp(kv->key, name) == 0)
              return kv;
      }
      return NULL;
  }
  
  int
  validate_transform(struct parsestate* psp, struct transform* tr, struct mapping* map)
  {
      struct keyvalue* kv = NULL;
  
      kv = find_keyvalue(map->kvlist, "TYPE");
      if (! kv)
          return error_failed_to_find_type_for_transformation(psp, tr->kv);
  
      if (kv->type != KV_SCALAR)
          return error_transformation_type_not_scalar(psp, tr->kv, kv);
  
      if (0 == strcasecmp(kv->scalar, "input"))
          tr->type = TR_INPUT;
      else if (0 == strcasecmp(kv->scalar, "output"))
          tr->type = TR_OUTPUT;
      else
          return error_invalid_type_for_transformation(psp, tr->kv, kv);
      
      kv = find_keyvalue(map->kvlist, "COMMAND");
      if (! kv)
          return error_failed_to_find_command_for_transformation(psp, tr->kv);
  
      if (kv->type != KV_SCALAR || strlen(kv->scalar) < 1)
          return error_invalid_command_for_transformation(psp, tr->kv, kv);
  
      tr->command = kv->scalar;
  
  	kv = find_keyvalue(map->kvlist, "STDERR");
  	if (kv) 
  	{
  		if (kv->type != KV_SCALAR)
  			return error_stderr_not_scalar(psp, tr->kv, kv);
  
  		if (0 == strcasecmp(kv->scalar, "console"))
  			tr->errs = TR_ER_CONSOLE;
  		else if (0 == strcasecmp(kv->scalar, "server"))
  			tr->errs = TR_ER_SERVER;
  		else
  			return error_invalid_stderr_for_transformation(psp, tr->kv, kv);
  	}
  	else
  	{
  		/* 'server' is the default when stderr is not specified */
  		tr->errs = TR_ER_SERVER;
  	}
  
      kv = find_keyvalue(map->kvlist, "CONTENT");
      if (kv) 
  	{
  		if (kv->type != KV_SCALAR)
  			return error_content_not_scalar(psp, tr->kv, kv);
  
  		if (0 == strcasecmp(kv->scalar, "data"))
  			tr->content = TR_FN_DATA;
  		else if (0 == strcasecmp(kv->scalar, "paths"))
  			tr->content = TR_FN_PATHS;
  		else
  			return error_invalid_content_for_transformation(psp, tr->kv, kv);
  	}
  	else
  	{
  		/* 'data' is the default when filename_contents is not specified */
  		tr->content = TR_FN_DATA;
  	}
  
      kv = find_keyvalue(map->kvlist, "SAFE");
      if (kv) 
  	{
  		int rc;
  
  		if (kv->type != KV_SCALAR || strlen(kv->scalar) < 1)
  			return error_safe_not_scalar(psp, tr->kv, kv);
  
  		tr->safe = kv->scalar;
  
  		rc = regcomp( &(tr->saferegex), tr->safe, REG_EXTENDED|REG_NOSUB);
  		if (rc != 0)
  			return error_safe_not_valid_regex(psp, tr->kv, kv, &(tr->saferegex), rc);
  	}
  
      return 0;
  }
  
  
  int
  validate(struct parsestate* psp, struct streamstate* stp)
  {
      int rc;
      struct mapping*  document  = stp->document;
      struct mapping*  trmapping = NULL;
      struct keyvalue* kv;
  
      kv = find_keyvalue(document->kvlist, "TRANSFORMATIONS");
      if (! kv)
          return error_failed_to_find_transformations(psp);
      if (kv->type != KV_MAPPING)
          return error_transformations_not_proper_yaml_map(psp, kv);
  
      trmapping = kv->map;
      for (kv = trmapping->kvlist; kv; kv=kv->nxt) 
      {
          if (kv->type != KV_MAPPING)
              return error_transformation_not_proper_yaml_map(psp, kv);
      
          struct transform* tr = psp->trlist = new_transform(psp, psp->trlist);
          tr->kv = kv;
          rc = validate_transform(psp, tr, kv->map);
          if (rc)
              return 1;
      }
      return 0;
  }
  
  
  /*
   * configure transforms
   */
  
  int
  transform_config(const char* filename, struct transform** trlistp, int verbose)
  {
      struct parsestate ps;
      memset(&ps, 0, sizeof(ps));
      ps.filename = filename;
  
      FILE* file = fopen(ps.filename, "rb");
  	if (! file) 
  	{
  		fprintf(stderr, "unable to open file %s: %s\n", ps.filename, strerror(errno));
  		return 1;
  	}
  
      struct streamstate st;
      memset(&st, 0, sizeof(st));
      st.filename = ps.filename;
  
      int rc = stage1_parse(&st, file, verbose);
      fclose(file);
  
      if (rc) 
  		fprintf(stderr, "failed to parse file: %s\n", ps.filename);
  
  	if (rc || verbose) {
          if (st.document)
              debug_mapping(st.document, 0);
  	}
  
  	if (rc)
          return 1;
  
  	if (validate(&ps, &st))
      {
  		fprintf(stderr, "failed to validate file: %s\n", ps.filename);
          fprintf(stderr, "%s", ps.errbuf);
          return 1;
      }
  
      (*trlistp) = ps.trlist;
      return 0;
  }
  
  
  /*
   * lookup transformation
   */
  
  struct transform*
  transform_lookup(struct transform* trlist, const char* name, int for_write, int verbose)
  {
  	struct transform* tr;
  
  	for (tr = trlist; tr; tr = tr->nxt)
      {
  
          /* ignore transforms not of proper type */
          if (for_write) 
          {
              if (tr->type != TR_OUTPUT)
                  continue;
          } 
          else 
          {
              if (tr->type != TR_INPUT)
                  continue;
          }
  
          /* when we've found a match, return the corresponding command */
          if (0 == strcmp(tr->kv->key, name)) 
  		{
  			if (verbose)
  				dump_transform(tr);
  
              return tr;
  		}
      }
      return NULL;
  }
  
  
  /*
   * transformation accessors
   */
  
  char*
  transform_command(struct transform* tr)
  {
  	return tr->command;
  }
  
  int
  transform_stderr_server(struct transform* tr)
  {
  	return (tr->errs == TR_ER_SERVER);
  }
  
  int
  transform_content_paths(struct transform* tr)
  {
  	return (tr->content == TR_FN_PATHS);
  }
  
  char*
  transform_safe(struct transform* tr)
  {
  	return tr->safe;
  }
  
  regex_t*
  transform_saferegex(struct transform* tr)
  {
  	return &(tr->saferegex);
  }
  
  #endif
  