/* doctool.c

   Copyright 1998 Cygnus Solutions.

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>

/* Building native in a cross-built directory is tricky.  Be careful,
and beware that you don't have the full portability stuff available to
you (like libiberty) */

/*****************************************************************************/

/* The list of extensions that may contain SGML snippets.  We check
   both cases in case the file system isn't case sensitive enough. */

struct {
  char *upper;
  char *lower;
  int is_sgml;
} extensions[] = {
  { ".C",	".c",		0 },
  { ".CC",	".cc",		0 },
  { ".H",	".h",		0 },
  { ".SGML",	".sgml",	1 },
  { 0, 0, 0 }
};

/*****************************************************************************/

void
show_help()
{
  printf("Usage: doctool [-m] [-i] [-d dir] [-o outfile] [-s prefix] \\\n");
  printf("                [-b book_id] infile\n");
  printf("  -m   means to adjust Makefile to include new dependencies\n");
  printf("  -i   means to include internal snippets\n");
  printf("  -d   means to recursively scan directory for snippets\n");
  printf("  -o   means to output to file (else stdout)\n");
  printf("  -s   means to suppress source dir prefix\n");
  printf("  -b   means to change the <book id=\"book_id\">\n");
  printf("\n");
  printf("doctool looks for DOCTOOL-START and DOCTOOL-END lines in source,\n");
  printf("saves <foo id=\"bar\"> blocks, and looks for DOCTOOL-INSERT-bar\n");
  printf("commands to insert selected sections.  IDs starting with int-\n");
  printf("are internal only, add- are added at the end of relevant sections\n");
  printf("or add-int- for both.  Inserted sections are chosen by prefix,\n");
  printf("and sorted when inserted.\n");
  exit(1);
}

/*****************************************************************************/

typedef struct Section {
  struct Section *next;
  struct OneFile *file;
  char *name;
  char internal;
  char addend;
  char used;
  char **lines;
  int num_lines;
  int max_lines;
} Section;

typedef struct OneFile {
  struct OneFile *next;
  char *filename;
  int enable_scan;
  int used;
  Section *sections;
} OneFile;

OneFile *file_list = 0;

char *output_name = 0;
FILE *output_file = 0;

char *source_dir_prefix = "";
char *book_id = 0;

int internal_flag = 0;

/*****************************************************************************/

char *
has_string(char *line, char *string)
{
  int i;
  while (*line)
  {
    for (i=0; line[i]; i++)
    {
      if (!string[i])
	return line;
      if (line[i] != string[i])
	break;
    }
    line++;
  }
  return 0;
}

int
starts_with(char *line, char *string)
{
  int i=0;
  while (1)
  {
    if (!string[i])
      return 1;
    if (!line[i] || line[i] != string[i])
      return 0;
    i++;
  }
}

/*****************************************************************************/

#ifdef S_ISLNK
#define STAT lstat
#else
#define STAT stat
#endif

void
scan_directory(dirname)
  char *dirname;
{
  struct stat st;
  char *name;
  struct dirent *de;
  DIR *dir = opendir(dirname);
  if (!dir)
    return;
  while (de = readdir(dir))
  {
    if (strcmp(de->d_name, ".") == 0
	|| strcmp(de->d_name, "..") == 0)
      continue;

    name = (char *)malloc(strlen(dirname)+strlen(de->d_name)+3);
    strcpy(name, dirname);
    strcat(name, "/");
    strcat(name, de->d_name);

    STAT(name, &st);

    if (S_ISDIR(st.st_mode))
    {
      scan_directory(name);
    }

    else if (S_ISREG(st.st_mode))
    {
      char *dot = strrchr(de->d_name, '.');
      int i;

      if (dot)
      {
	for (i=0; extensions[i].upper; i++)
	  if (strcmp(dot, extensions[i].upper) == 0
	      || strcmp(dot, extensions[i].lower) == 0)
	  {
	    OneFile *one = (OneFile *)malloc(sizeof(OneFile));
	    one->next = file_list;
	    file_list = one;
	    one->filename = name;
	    one->enable_scan = ! extensions[i].is_sgml;
	    one->used = 0;
	    one->sections = 0;
	  }
      }
    }
  }
  closedir (dir);
}

/*****************************************************************************/

void
scan_file(OneFile *one)
{
  FILE *f = fopen(one->filename, "r");
  int enabled = ! one->enable_scan;
  char line[1000], *tag=0, *id=0, *tmp;
  int taglen = 0;
  Section *section = 0;
  Section **prev_section_ptr = &(one->sections);

  if (!f)
  {
    perror(one->filename);
    return;
  }

  while (fgets(line, 1000, f))
  {
    if (one->enable_scan)
    {
      /* source files have comment-embedded docs, check for them */
      if (has_string(line, "DOCTOOL-START"))
	enabled = 1;
      if (has_string(line, "DOCTOOL-END"))
	enabled = 0;
    }
    if (!enabled)
      continue;

    /* DOCTOOL-START

<sect1 id="dt-tags">
this is the doctool tags section.
</sect1>

       DOCTOOL-END */

    if (!tag && line[0] == '<')
    {
      tag = (char *)malloc(strlen(line)+1);
      id = (char *)malloc(strlen(line)+1);
      if (sscanf(line, "<%s id=\"%[^\"]\">", tag, id) == 2)
      {
	if (strcmp(tag, "book") == 0 || strcmp(tag, "BOOK") == 0)
	{
	  /* Don't want to "scan" these */
	  return;
	}
	taglen = strlen(tag);
	section = (Section *)malloc(sizeof(Section));
	/* We want chunks within single files to appear in that order */
	section->next = 0;
	section->file = one;
	*prev_section_ptr = section;
	prev_section_ptr = &(section->next);
	section->internal = 0;
	section->addend = 0;
	section->used = 0;
	section->name = id;
	if (starts_with(section->name, "add-"))
	{
	  section->addend = 1;
	  section->name += 4;
	}
	if (starts_with(section->name, "int-"))
	{
	  section->internal = 1;
	  section->name += 4;
	}
	section->lines = (char **)malloc(10*sizeof(char *));
	section->num_lines = 0;
	section->max_lines = 10;
      }
      else
      {
	free(tag);
	free(id);
	tag = id = 0;
      }
    }

    if (tag && section)
    {
      if (section->num_lines >= section->max_lines)
      {
	section->max_lines += 10;
	section->lines = (char **)realloc(section->lines,
					  section->max_lines * sizeof (char *));
      }
      section->lines[section->num_lines] = (char *)malloc(strlen(line)+1);
      strcpy(section->lines[section->num_lines], line);
      section->num_lines++;

      if (line[0] == '<' && line[1] == '/'
	  && memcmp(line+2, tag, taglen) == 0
	  && (isspace(line[2+taglen]) || line[2+taglen] == '>'))
      {
	/* last line! */
	tag = 0;
      }
    }
  }
  fclose(f);
}

/*****************************************************************************/

Section **
enumerate_matching_sections(char *name_prefix, int internal, int addend, int *count_ret)
{
  Section **rv = (Section **)malloc(12*sizeof(Section *));
  int count = 0, max=10, prefix_len = strlen(name_prefix);
  OneFile *one;
  int wildcard = 0;

  if (name_prefix[strlen(name_prefix)-1] == '-')
    wildcard = 1;

  for (one=file_list; one; one=one->next)
  {
    Section *s;
    for (s=one->sections; s; s=s->next)
    {
      int matches = 0;
      if (wildcard)
      {
	if (starts_with(s->name, name_prefix))
	  matches = 1;
      }
      else
      {
	if (strcmp(s->name, name_prefix) == 0)
	  matches = 1;
      }
      if (s->internal <= internal
	  && s->addend == addend
	  && matches
	  && ! s->used)
      {
	s->used = 1;
	if (count >= max)
	{
	  max += 10;
	  rv = (Section **)realloc(rv, max*sizeof(Section *));
	}
	rv[count++] = s;
	rv[count] = 0;
      }
    }
  }
  if (count_ret)
    *count_ret = count;
  return rv;
}

/*****************************************************************************/

#define ID_CHARS "~@$%&()_-+[]{}:."

void include_section(char *name, int addend);

char *
unprefix(char *fn)
{
  int l = strlen(source_dir_prefix);
  if (memcmp(fn, source_dir_prefix, l) == 0)
  {
    fn += l;
    while (*fn == '/' || *fn == '\\')
      fn++;
    return fn;
  }
  return fn;
}

void
parse_line(char *line, char *filename)
{
  char *cmd = has_string(line, "DOCTOOL-INSERT-");
  char *sname, *send, *id, *save;
  if (!cmd)
  {
    if (book_id
	&& (starts_with(line, "<book") || starts_with(line, "<BOOK")))
    {
      cmd = strchr(line, '>');
      if (cmd)
      {
	cmd++;
	fprintf(output_file, "<book id=\"%s\">", book_id);
	fputs(cmd, output_file);
	return;
      }
    }
    fputs(line, output_file);
    return;
  }
  if (cmd != line)
    fwrite(line, cmd-line, 1, output_file);
  save = (char *)malloc(strlen(line)+1);
  strcpy(save, line);
  line = save;

  sname = cmd + 15; /* strlen("DOCTOOL-INSERT-") */
  for (send = sname;
       *send && isalnum(*send) || strchr(ID_CHARS, *send);
       send++);
  id = (char *)malloc(send-sname+2);
  memcpy(id, sname, send-sname);
  id[send-sname] = 0;
  include_section(id, 0);

  fprintf(output_file, "<!-- %s -->\n", unprefix(filename));

  fputs(send, output_file);
  free(save);
}

int
section_sort(const void *va, const void *vb)
{
  Section *a = *(Section **)va;
  Section *b = *(Section **)vb;
  int rv = strcmp(a->name, b->name);
  if (rv)
    return rv;
  return a->internal - b->internal;
}

void
include_section(char *name, int addend)
{
  Section **sections, *s;
  int count, i, l;

  sections = enumerate_matching_sections(name, internal_flag, addend, &count);

  qsort(sections, count, sizeof(sections[0]), section_sort);
  for (i=0; i<count; i++)
  {
    s = sections[i];
    s->file->used = 1;
    fprintf(output_file, "<!-- %s -->\n", unprefix(s->file->filename));
    for (l=addend; l<s->num_lines-1; l++)
      parse_line(s->lines[l], s->file->filename);
    if (!addend)
    {
      include_section(s->name, 1);
      parse_line(s->lines[l], s->file->filename);
    }
  }

  free(sections);
}

void
parse_sgml(FILE *in, char *input_name)
{
  static char line[1000];
  while (fgets(line, 1000, in))
  {
    parse_line(line, input_name);
  }
}

/*****************************************************************************/

void
fix_makefile(char *output_name)
{
  FILE *in, *out;
  char line[1000];
  int oname_len = strlen(output_name);
  OneFile *one;
  int used_something = 0;
  struct stat st;
  struct utimbuf times;

  stat("Makefile", &st);

  in = fopen("Makefile", "r");
  if (!in)
  {
    perror("Makefile");
    return;
  }

  out = fopen("Makefile.new", "w");
  if (!out)
  {
    perror("Makefile.new");
    return;
  }

  while (fgets(line, 1000, in))
  {
    if (starts_with(line, output_name)
	&& strcmp(line+oname_len, ": \\\n") == 0)
    {
      /* this is the old dependency */
      while (fgets(line, 1000, in))
      {
	if (strcmp(line+strlen(line)-2, "\\\n"))
	  break;
      }
    }
    else
      fputs(line, out);
  }
  fclose(in);

  for (one=file_list; one; one=one->next)
    if (one->used)
    {
      used_something = 1;
      break;
    }

  if (used_something)
  {
    fprintf(out, "%s:", output_name);
    for (one=file_list; one; one=one->next)
      if (one->used)
	fprintf(out, " \\\n\t%s", one->filename);
    fprintf(out, "\n");
  }

  fclose(out);

  times.actime = st.st_atime;
  times.modtime = st.st_mtime;
  utime("Makefile.new", &times);

  if (rename("Makefile", "Makefile.old"))
    return;
  if (rename("Makefile.new", "Makefile"))
    rename("Makefile.old", "Makefile");
}

/*****************************************************************************/

int
main(argc, argv)
  int argc;
  char **argv;
{
  int i;
  OneFile *one;
  FILE *input_file;
  int fix_makefile_flag = 0;

  while (argc > 1 && argv[1][0] == '-')
  {
    if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)
    {
      show_help();
    }
    else if (strcmp(argv[1], "-i") == 0)
    {
      internal_flag = 1;
    }
    else if (strcmp(argv[1], "-m") == 0)
    {
      fix_makefile_flag = 1;
    }
    else if (strcmp(argv[1], "-d") == 0 && argc > 2)
    {
      scan_directory(argv[2]);
      argc--;
      argv++;
    }
    else if (strcmp(argv[1], "-o") == 0 && argc > 2)
    {
      output_name = argv[2];
      argc--;
      argv++;
    }
    else if (strcmp(argv[1], "-s") == 0 && argc > 2)
    {
      source_dir_prefix = argv[2];
      argc--;
      argv++;
    }
    else if (strcmp(argv[1], "-b") == 0 && argc > 2)
    {
      book_id = argv[2];
      argc--;
      argv++;
    }

    argc--;
    argv++;
  }

  for (one=file_list; one; one=one->next)
  {
    scan_file(one);
  }

  input_file = fopen(argv[1], "r");
  if (!input_file)
  {
    perror(argv[1]);
    return 1;
  }

  if (output_name)
  {
    output_file = fopen(output_name, "w");
    if (!output_file)
    {
      perror(output_name);
      return 1;
    }
  }
  else
  {
    output_file = stdout;
    output_name = "<stdout>";
  }

  parse_sgml(input_file, argv[1]);

  if (output_file != stdout)
    fclose(output_file);

  if (fix_makefile_flag)
    fix_makefile(output_name);

  return 0;
}
