<commit_before>#pragma once

#include <cstdio>
#include <cstring>

#include "common.hpp"
#include "utils/string.hpp"

LEMONBUDDY_NS

enum class loglevel {
  NONE = 0,
  ERROR,
  WARNING,
  INFO,
  TRACE,
};

/**
 * Convert given loglevel name to its enum type counterpart
 */
auto parse_loglevel_name = [](string name) {
  if (string_util::compare(name, "error"))
    return loglevel::ERROR;
  else if (string_util::compare(name, "warning"))
    return loglevel::WARNING;
  else if (string_util::compare(name, "info"))
    return loglevel::INFO;
  else if (string_util::compare(name, "trace"))
    return loglevel::TRACE;
  else
    return loglevel::NONE;
};

class logger {
 public:
  /**
   * Construct logger
   */
  explicit logger(loglevel level) : m_level(level) {
    if (isatty(STDOUT_FILENO)) {
      // clang-format off
      m_prefixes[loglevel::TRACE]   = "\r\033[0;90m- ";
      m_prefixes[loglevel::INFO]    = "\r\033[1;32m* \033[0m";
      m_prefixes[loglevel::WARNING] = "\r\033[1;33mwarning: \033[0m";
      m_prefixes[loglevel::ERROR]   = "\r\033[1;31merror: \033[0m";

      m_suffixes[loglevel::TRACE]   = "\033[0m";
      m_suffixes[loglevel::INFO]    = "\033[0m";
      m_suffixes[loglevel::WARNING] = "\033[0m";
      m_suffixes[loglevel::ERROR]   = "\033[0m";
      // clang-format on
    }
  }

  /**
   * Construct logger
   */
  explicit logger(string level_name) : logger(parse_loglevel_name(level_name)) {}

  /**
   * Set output verbosity
   */
  void verbosity(loglevel level) {
#ifndef DEBUG
    if (level == loglevel::TRACE)
      throw application_error("not a debug build: trace disabled...");
#endif
    m_level = level;
  }

  /**
   * Set output verbosity by loglevel name
   */
  void verbosity(string level) {
    verbosity(parse_loglevel_name(level));
  }

  /**
   * Output a trace message
   */
  template <typename... Args>
  void trace(string message, Args... args) const {
#ifdef DEBUG
    output(loglevel::TRACE, message, args...);
#endif
  }

  /**
   * Output an info message
   */
  template <typename... Args>
  void info(string message, Args... args) const {
    output(loglevel::INFO, message, args...);
  }

  /**
   * Output a warning message
   */
  template <typename... Args>
  void warn(string message, Args... args) const {
    output(loglevel::WARNING, message, args...);
  }

  /**
   * Output an error message
   */
  template <typename... Args>
  void err(string message, Args... args) const {
    output(loglevel::ERROR, message, args...);
  }

  /**
   * Configure injection module
   */
  template <typename T = const logger&>
  static di::injector<T> configure(loglevel level = loglevel::NONE) {
    auto instance = factory::generic_singleton<logger>(level);
    return di::make_injector(di::bind<>().to(instance));
  }

 protected:
  template <typename T>
  decltype(auto) convert(T&& arg) const {
    return forward<T>(arg);
  };

  /**
   * Convert string to const char*
   */
  const char* convert(string arg) const {
    return arg.c_str();
  };

  /**
   * Write the log message to the output channel
   * if the defined verbosity level allows it
   */
  template <typename... Args>
  void output(loglevel level, string format, Args... values) const {
    if (level > m_level)
      return;

    auto prefix = m_prefixes.find(level)->second;
    auto suffix = m_suffixes.find(level)->second;

// silence the compiler
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
#endif
    dprintf(m_fd, (prefix + format + suffix + "\n").c_str(), convert(values)...);
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
  }

 private:
  /**
   * Logger verbosity level
   */
  loglevel m_level = loglevel::TRACE;

  /**
   * File descriptor used when writing the log messages
   */
  int m_fd = STDERR_FILENO;

  /**<commit_msg>fix(logger): Ignore GCC warning<commit_after>#pragma once

#include <cstdio>
#include <cstring>

#include "common.hpp"
#include "utils/string.hpp"

LEMONBUDDY_NS

enum class loglevel {
  NONE = 0,
  ERROR,
  WARNING,
  INFO,
  TRACE,
};

/**
 * Convert given loglevel name to its enum type counterpart
 */
auto parse_loglevel_name = [](string name) {
  if (string_util::compare(name, "error"))
    return loglevel::ERROR;
  else if (string_util::compare(name, "warning"))
    return loglevel::WARNING;
  else if (string_util::compare(name, "info"))
    return loglevel::INFO;
  else if (string_util::compare(name, "trace"))
    return loglevel::TRACE;
  else
    return loglevel::NONE;
};

class logger {
 public:
  /**
   * Construct logger
   */
  explicit logger(loglevel level) : m_level(level) {
    if (isatty(STDOUT_FILENO)) {
      // clang-format off
      m_prefixes[loglevel::TRACE]   = "\r\033[0;90m- ";
      m_prefixes[loglevel::INFO]    = "\r\033[1;32m* \033[0m";
      m_prefixes[loglevel::WARNING] = "\r\033[1;33mwarning: \033[0m";
      m_prefixes[loglevel::ERROR]   = "\r\033[1;31merror: \033[0m";

      m_suffixes[loglevel::TRACE]   = "\033[0m";
      m_suffixes[loglevel::INFO]    = "\033[0m";
      m_suffixes[loglevel::WARNING] = "\033[0m";
      m_suffixes[loglevel::ERROR]   = "\033[0m";
      // clang-format on
    }
  }

  /**
   * Construct logger
   */
  explicit logger(string level_name) : logger(parse_loglevel_name(level_name)) {}

  /**
   * Set output verbosity
   */
  void verbosity(loglevel level) {
#ifndef DEBUG
    if (level == loglevel::TRACE)
      throw application_error("not a debug build: trace disabled...");
#endif
    m_level = level;
  }

  /**
   * Set output verbosity by loglevel name
   */
  void verbosity(string level) {
    verbosity(parse_loglevel_name(level));
  }

  /**
   * Output a trace message
   */
  template <typename... Args>
  void trace(string message, Args... args) const {
#ifdef DEBUG
    output(loglevel::TRACE, message, args...);
#endif
  }

  /**
   * Output an info message
   */
  template <typename... Args>
  void info(string message, Args... args) const {
    output(loglevel::INFO, message, args...);
  }

  /**
   * Output a warning message
   */
  template <typename... Args>
  void warn(string message, Args... args) const {
    output(loglevel::WARNING, message, args...);
  }

  /**
   * Output an error message
   */
  template <typename... Args>
  void err(string message, Args... args) const {
    output(loglevel::ERROR, message, args...);
  }

  /**
   * Configure injection module
   */
  template <typename T = const logger&>
  static di::injector<T> configure(loglevel level = loglevel::NONE) {
    auto instance = factory::generic_singleton<logger>(level);
    return di::make_injector(di::bind<>().to(instance));
  }

 protected:
  template <typename T>
  decltype(auto) convert(T&& arg) const {
    return forward<T>(arg);
  };

  /**
   * Convert string to const char*
   */
  const char* convert(string arg) const {
    return arg.c_str();
  };

  /**
   * Write the log message to the output channel
   * if the defined verbosity level allows it
   */
  template <typename... Args>
  void output(loglevel level, string format, Args... values) const {
    if (level > m_level)
      return;

    auto prefix = m_prefixes.find(level)->second;
    auto suffix = m_suffixes.find(level)->second;

// silence the compiler
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
#elif defined(__GCC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-security"
#endif
    dprintf(m_fd, (prefix + format + suffix + "\n").c_str(), convert(values)...);
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GCC__)
#pragma GCC diagnostic pop
#endif
  }

 private:
  /**
   * Logger verbosity level
   */
  loglevel m_level = loglevel::TRACE;

  /**
   * File descriptor used when writing the log messages
   */
  int m_fd = STDERR_FILENO;

  /**
{'fraction_non_alphanumeric': 0.14702894870492636, 'fraction_numerical': 0.01345860843067547, 'mean_word_length': 3.1832182687201276, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4712956', 'n_tokens_mistral': 2694, 'n_tokens_neox': 2601, 'n_words': 658}