/*      Id: demandoc.c,v 1.33 2019/03/03 11:01:15 schwarze Exp  */
/*
* Copyright (c) 2011 Kristaps Dzonsons <[email protected]>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"

#include <sys/types.h>

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

#include "mandoc.h"
#include "roff.h"
#include "man.h"
#include "mdoc.h"
#include "mandoc_parse.h"

static  void     pline(int, int *, int *, int);
static  void     pman(const struct roff_node *, int *, int *, int);
static  void     pmandoc(struct mparse *, int, const char *, int);
static  void     pmdoc(const struct roff_node *, int *, int *, int);
static  void     pstring(const char *, int, int *, int);
static  void     usage(void);

static  const char       *progname;

int
main(int argc, char *argv[])
{
       struct mparse   *mp;
       int              ch, fd, i, list;
       extern int       optind;

       if (argc < 1)
               progname = "demandoc";
       else if ((progname = strrchr(argv[0], '/')) == NULL)
               progname = argv[0];
       else
               ++progname;

       mp = NULL;
       list = 0;

       while (-1 != (ch = getopt(argc, argv, "ikm:pw")))
               switch (ch) {
               case ('i'):
                       /* FALLTHROUGH */
               case ('k'):
                       /* FALLTHROUGH */
               case ('m'):
                       /* FALLTHROUGH */
               case ('p'):
                       break;
               case ('w'):
                       list = 1;
                       break;
               default:
                       usage();
                       return (int)MANDOCLEVEL_BADARG;
               }

       argc -= optind;
       argv += optind;

       mchars_alloc();
       mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
           MPARSE_VALIDATE, MANDOC_OS_OTHER, NULL);
       assert(mp);

       if (argc < 1)
               pmandoc(mp, STDIN_FILENO, "<stdin>", list);

       for (i = 0; i < argc; i++) {
               mparse_reset(mp);
               if ((fd = mparse_open(mp, argv[i])) == -1) {
                       perror(argv[i]);
                       continue;
               }
               pmandoc(mp, fd, argv[i], list);
       }

       mparse_free(mp);
       mchars_free();
       return (int)MANDOCLEVEL_OK;
}

static void
usage(void)
{

       fprintf(stderr, "usage: %s [-w] [files...]\n", progname);
}

static void
pmandoc(struct mparse *mp, int fd, const char *fn, int list)
{
       struct roff_meta        *meta;
       int              line, col;

       mparse_readfd(mp, fd, fn);
       close(fd);
       meta = mparse_result(mp);
       line = 1;
       col = 0;

       if (meta->macroset == MACROSET_MDOC)
               pmdoc(meta->first->child, &line, &col, list);
       else
               pman(meta->first->child, &line, &col, list);

       if ( ! list)
               putchar('\n');
}

/*
* Strip the escapes out of a string, emitting the results.
*/
static void
pstring(const char *p, int col, int *colp, int list)
{
       enum mandoc_esc  esc;
       const char      *start, *end;
       int              emit;

       /*
        * Print as many column spaces til we achieve parity with the
        * input document.
        */

again:
       if (list && '\0' != *p) {
               while (isspace((unsigned char)*p))
                       p++;

               while ('\'' == *p || '(' == *p || '"' == *p)
                       p++;

               emit = isalpha((unsigned char)p[0]) &&
                       isalpha((unsigned char)p[1]);

               for (start = p; '\0' != *p; p++)
                       if ('\\' == *p) {
                               p++;
                               esc = mandoc_escape(&p, NULL, NULL);
                               if (ESCAPE_ERROR == esc)
                                       return;
                               emit = 0;
                       } else if (isspace((unsigned char)*p))
                               break;

               end = p - 1;

               while (end > start)
                       if ('.' == *end || ',' == *end ||
                                       '\'' == *end || '"' == *end ||
                                       ')' == *end || '!' == *end ||
                                       '?' == *end || ':' == *end ||
                                       ';' == *end)
                               end--;
                       else
                               break;

               if (emit && end - start >= 1) {
                       for ( ; start <= end; start++)
                               if (ASCII_HYPH == *start)
                                       putchar('-');
                               else
                                       putchar((unsigned char)*start);
                       putchar('\n');
               }

               if (isspace((unsigned char)*p))
                       goto again;

               return;
       }

       while (*colp < col) {
               putchar(' ');
               (*colp)++;
       }

       /*
        * Print the input word, skipping any special characters.
        */
       while ('\0' != *p)
               if ('\\' == *p) {
                       p++;
                       esc = mandoc_escape(&p, NULL, NULL);
                       if (ESCAPE_ERROR == esc)
                               break;
               } else {
                       putchar((unsigned char )*p++);
                       (*colp)++;
               }
}

static void
pline(int line, int *linep, int *col, int list)
{

       if (list)
               return;

       /*
        * Print out as many lines as needed to reach parity with the
        * original input.
        */

       while (*linep < line) {
               putchar('\n');
               (*linep)++;
       }

       *col = 0;
}

static void
pmdoc(const struct roff_node *p, int *line, int *col, int list)
{

       for ( ; p; p = p->next) {
               if (NODE_LINE & p->flags)
                       pline(p->line, line, col, list);
               if (ROFFT_TEXT == p->type)
                       pstring(p->string, p->pos, col, list);
               if (p->child)
                       pmdoc(p->child, line, col, list);
       }
}

static void
pman(const struct roff_node *p, int *line, int *col, int list)
{

       for ( ; p; p = p->next) {
               if (NODE_LINE & p->flags)
                       pline(p->line, line, col, list);
               if (ROFFT_TEXT == p->type)
                       pstring(p->string, p->pos, col, list);
               if (p->child)
                       pman(p->child, line, col, list);
       }
}