Doc.cpp

#include "Doc.hpp"

#include "DocTokenizer.hpp"
#include "Log.hpp"
#include "doxide.hpp"

#include <algorithm>
#include <cctype>
#include <ostream>
#include <string_view>
#include <unordered_set>
#include <utility>

Doc::Doc(const TextLineCursor &comment, const int init_indent,
    const std::string& style) :
    indent(init_indent),
    hide(false) {
  DocTokenizer tokenizer(comment);
  DocToken token = tokenizer.next();
  open = token;
  if (open.type == OPEN_BEFORE || open.type == OPEN_AFTER) {
    /* documentation comment, not just an ordinary comment */
    token = tokenizer.next();
    bool first = true;  // is this the first token in the comment?
    if (!token.type) {
      /* empty end-of-line comment, consider end of paragraph */
      if (indent && style == "pandoc") {
        docs.append("\n:::");
      }
      indent = std::max(indent - 4, 0);
    } else while (token.type) {
      if (token.type & COMMAND) {
        auto command_token = token.substr(1);
        std::string_view command = command_token.view();
        if (command == "param" ||
            command == "param[in]") {
          docs.append("\n");
          if (style == "mkdocs") {
            docs.append(":material-location-enter: `");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`\n:   ");
          } else if (style == "pandoc") {
            docs.append("`");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`\n:   ");
          } else {
            docs.append("  - **`");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`**\n\n    ");
          }
        } else if (command == "param[out]") {
          docs.append("\n");
          if (style == "mkdocs") {
            docs.append(":material-location-exit: `");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`\n:   ");
          } else if (style == "pandoc") {
            docs.append("`");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`\n:   ");
          } else {
            docs.append("  - **`");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`**\n\n    ");
          }
        } else if (command == "param[in,out]") {
          docs.append("\n");
          if (style == "mkdocs") {
            docs.append(":material-location-enter::material-location-exit: `");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`\n:   ");
          } else if (style == "pandoc") {
            docs.append("`");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`\n:   ");
          } else {
            docs.append("  - **`");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`**\n\n    ");
          }
        } else if (command == "tparam") {
          docs.append("\n");
          if (style == "mkdocs") {
            docs.append(":material-code-tags: `");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`\n:   ");
          } else if (style == "pandoc") {
            docs.append("**`");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`**\n:   ");
          } else {
            docs.append("  - **`");
            docs.append(tokenizer.consume(WORD).view());
            docs.append("`**\n\n    ");
          }
        } else if (command == "p") {
          docs.append("**`");
          docs.append(tokenizer.consume(WORD).view());
          docs.append("`**");
        } else if (command == "return" ||
            command == "returns" ||
            command == "result") {
          docs.append("\n");
          if (style == "mkdocs") {
            docs.append(":material-keyboard-return: **Return**\n:   ");
          } else if (style == "pandoc") {
            docs.append("Return\n:   ");
          } else {
            docs.append("\n  - **Return**\n");
            docs.append("\n\n    ");
          }
        } else if (command == "pre") {
          docs.append("\n");
          if (style == "mkdocs") {
            docs.append(":material-check-circle-outline: **Pre-condition**\n:   ");
          } else if (style == "pandoc") {
            docs.append("Pre-condition\n:   ");
          } else {
            docs.append("\n  - **Pre-condition**\n");
            docs.append("\n\n    ");
          }
        } else if (command == "post") {
          docs.append("\n");
          if (style == "mkdocs") {
            docs.append(":material-check-circle-outline: **Post-condition**\n:   ");
          } else if (style == "pandoc") {
            docs.append("Post-condition\n:   ");
          } else {
            docs.append("\n  - **Post-condition**\n");
            docs.append("\n\n    ");
          }
        } else if (command == "throw") {
          docs.append("\n");
          if (style == "mkdocs") {
            docs.append(":material-alert-circle-outline: **Throw**\n:   ");
          } else if (style == "pandoc") {
            docs.append("Throw\n:   ");
          } else {
            docs.append("\n  - **Throw**\n");
            docs.append("\n\n    ");
          }
        } else if (command == "see" ||
            command == "sa") {
          docs.append("\n");
          if (style == "mkdocs") {
            docs.append(":material-eye-outline: **See**\n:   ");
          } else if (style == "pandoc") {
            docs.append("See\n:   ");
          } else {
            docs.append("\n  - **See**\n");
            docs.append("\n\n    ");
          }
        } else if (command == "anchor") {
          docs.append("<a name=\"");
          docs.append(tokenizer.consume(WORD).view());
          docs.append("\"></a>");
        } else if (command == "note" ||
            command == "abstract" ||
            command == "info" ||
            command == "tip" ||
            command == "success" ||
            command == "question" ||
            command == "warning" ||
            command == "failure" ||
            command == "danger" ||
            command == "bug" ||
            command == "example" ||
            command == "quote") {
          if (style == "mkdocs") {
            docs.append("\n!!! ");
            docs.append(command);
            docs.append("\n");
          } else if (style == "pandoc") {
            docs.append("\n::: ");
            docs.append(command);
            docs.append("\n");
          } else {
            docs.append("> **");
            docs.append(capitalize(command));
            docs.append("**\n");
            docs.append("> ");
          }
          indent += 4;
        } else if (command == "ingroup") {
          ingroup = tokenizer.consume(WORD).get();

        /* legacy commands */
        } else if (command == "file" ||
            command == "internal") {
          hide = true;
        } else if (command == "e" ||
            command == "em" ||
            command == "a") {
          docs.append("*");
          docs.append(tokenizer.consume(WORD).view());
          docs.append("*");
        } else if (command == "b") {
          docs.append("**");
          docs.append(tokenizer.consume(WORD).view());
          docs.append("**");
        } else if (command == "c") {
          docs.append("`");
          docs.append(tokenizer.consume(WORD).view());
          docs.append("`");
        } else if (command == "f$") {
          docs.append("$");
        } else if (command == "f[" ||
            command == "f]") {
          docs.append("$$");
        } else if (command == "li" ||
            command == "arg") {
          docs.append("  - ");
        } else if (command == "ref") {
          auto href = tokenizer.consume(WORD);
          auto text = tokenizer.consume(WORD);
          docs.append("[");
          docs.append(text.view());
          docs.append("](#");
          docs.append(href.view());
          docs.append(")");
        } else if (command == "code" ||
            command == "endcode" ||
            command == "verbatim" ||
            command == "endverbatim") {
          docs.append("\n```");
        } else if (command == "attention") {
          if (style == "mkdocs") {
            docs.append("\n!!! warning \"Attention\"\n");
          } else if (style == "pandoc") {
            docs.append("\n::: warning\n");
          } else {
            docs.append("> **Attention**\n");
            docs.append("> ");
          }
          indent += 4;
        } else if (command == "todo") {
          if (style == "mkdocs") {
            docs.append("\n!!! example \"To-do\"\n");
          } else if (style == "pandoc") {
            docs.append("\n::: example\n");
          } else {
            docs.append("> **To-do**\n");
            docs.append("> ");
          }
          indent += 4;
        } else if (command == "remark") {
          if (style == "mkdocs") {
            docs.append("\n!!! quote \"Remark\"\n");
          } else if (style == "pandoc") {
            docs.append("\n::: quote\n");
          } else {
            docs.append("> **Remark**\n");
            docs.append("> ");
          }
          indent += 4;
        } else if (command == "def" ||
            command == "var" ||
            command == "fn" ||
            command == "class" ||
            command == "struct" ||
            command == "union" ||
            command == "enum" ||
            command == "typedef" ||
            command == "namespace" ||
            command == "interface" ||
            command == "protocol" ||
            command == "property") {
          /* ignore, including following name */
          tokenizer.consume(WORD);
        } else if (command == "@") {
          docs.append("@");
        } else if (command == "/") {
          docs.append("/");
        } else if (token.view().at(0) == '\\') {
          /* unrecognized command starting with legacy backslash, could just
           * be e.g. a LaTeX macro, output as is */
          docs.append(token.view());
        } else {
          /* keep track of warnings and don't repeat them */
          static std::unordered_set<std::string> warned;
          if (warned.insert(std::string(command)).second) {
            warn("line " << command_token.get_line_number()
              << ": unrecognized command '" << command << "'");
          }
          docs.append(token.view
            ());
        }
      } else if (token.type & PARA) {
        if (!first) {
          if (indent && style == "pandoc") {
            docs.append("\n:::");
          }
          docs.append("\n\n");
          indent = std::max(indent - 4, 0);
        }
      } else if (token.type & LINE) {
        if (!first) {
          docs.append("\n");
          add_indent(indent, style);
        }
      } else if (token.type & CLOSE) {
        if (style == "pandoc") {
          while (indent) {
            docs.append("\n:::");
            indent -= 4;
          }
        }
        indent = 0;
      } else {
        add_indent(first*indent, style);  // indent on first token
        docs.append(token.view());
      }
      token = tokenizer.next();
      first = false;
    }

    /* trim whitespace from the end and add single new line */
    while (docs.length() > 0 && std::isspace(docs.back())) {
      docs.resize(docs.length() - 1);
    }
    docs.append("\n");
  }
}

void Doc::add_indent(const int indent, const std::string& style) {
  if (style == "mkdocs") {
    docs.append(indent, ' ');
  } else if (style == "pandoc") {
    //
  } else {
    for (int i = 0; i < indent; i += 4) {
      docs.append("> ");
    }
  }
}