text: """
  Contains the implementation of the Ruby language parser and a relevant keyword enum.
  """
  
  # Authors: Grzegorz Lato <grzegorz.lato@gmail.com>
  # License: MIT
  
  from typing import Dict
  from enum import Enum, unique
  
  import logging
  from pathlib import PosixPath
  import os
  
  import pyparsing as pp
  import coloredlogs
  
  from emerge.languages.abstractparser import AbstractParser, ParsingMixin, Parser, CoreParsingKeyword, LanguageType
  from emerge.results import FileResult
  from emerge.abstractresult import AbstractResult, AbstractFileResult, AbstractEntityResult
  from emerge.stats import Statistics
  from emerge.log import Logger
  
  LOGGER = Logger(logging.getLogger('parser'))
  coloredlogs.install(level='E', logger=LOGGER.logger(), fmt=Logger.log_format)
  
  
  @unique
  class RubyParsingKeyword(Enum):
      CLASS = "class"
      CLOSE_SCOPE = "end"
      INLINE_COMMENT = "#"
      START_BLOCK_COMMENT = "=begin"
      STOP_BLOCK_COMMENT = "=end"
      REQUIRE = "require"
      REQUIRE_RELATIVE = "require_relative"  # Ruby >= 1.9
  
  
  class RubyParser(AbstractParser, ParsingMixin):
  
      def __init__(self):
          self._results: Dict[str, AbstractResult] = {}
          self._token_mappings: Dict[str, str] = {
              ':': ' : ',
              ';': ' ; ',
              '{': ' { ',
              '}': ' } ',
              '(': ' ( ',
              ')': ' ) ',
              '[': ' [ ',
              ']': ' ] ',
              '?': ' ? ',
              '!': ' ! ',
              ',': ' , ',
              '<': ' < ',
              '>': ' > ',
              '"': ' " ',
              "'": " ' "
          }
  
      @classmethod
      def parser_name(cls) -> str:
          return Parser.RUBY_PARSER.name
      
      @classmethod
      def language_type(cls) -> str:
          return LanguageType.RUBY.name
  
      @property
      def results(self) -> Dict[str, AbstractResult]:
          return self._results
  
      @results.setter
      def results(self, value):
          self._results = value
  
      def generate_file_result_from_analysis(self, analysis, *, file_name: str, full_file_path: str, file_content: str) -> None:
          LOGGER.debug(f'generating file results...')
          scanned_tokens = self.preprocess_file_content_and_generate_token_list(file_content)
  
          # make sure to create unique names by using the relative analysis path as a base for the result
          parent_analysis_source_path = f"{PosixPath(analysis.source_directory).parent}/"
          relative_file_path_to_analysis = full_file_path.replace(parent_analysis_source_path, "")
  
          file_result = FileResult.create_file_result(
              analysis=analysis,
              scanned_file_name=file_name,
              relative_file_path_to_analysis=relative_file_path_to_analysis,
              absolute_name=full_file_path,
              display_name=file_name,
              module_name="",
              scanned_by=self.parser_name(),
              scanned_language=LanguageType.RUBY,
              scanned_tokens=scanned_tokens
          )
  
          self._add_imports_to_result(file_result, analysis)
          self._results[file_result.unique_name] = file_result
  
      def after_generated_file_results(self, analysis) -> None:
          pass
  
      def create_unique_entity_name(self, entity: AbstractEntityResult) -> None:
          raise NotImplementedError(f'currently not implemented in {self.parser_name()}')
  
      def generate_entity_results_from_analysis(self, analysis):
          raise NotImplementedError(f'currently not implemented in {self.parser_name()}')
  
      def _add_imports_to_result(self, result: AbstractResult, analysis):
          LOGGER.debug(f'extracting imports from file result {result.scanned_file_name}...')
          list_of_words_with_newline_strings = result.scanned_tokens
          source_string_no_comments = self._filter_source_tokens_without_comments(
              list_of_words_with_newline_strings, RubyParsingKeyword.INLINE_COMMENT.value, RubyParsingKeyword.START_BLOCK_COMMENT.value, RubyParsingKeyword.STOP_BLOCK_COMMENT.value)
          filtered_list_no_comments = self.preprocess_file_content_and_generate_token_list_by_mapping(source_string_no_comments, self._token_mappings)
  
          for _, obj, following in self._gen_word_read_ahead(filtered_list_no_comments):
              if obj == RubyParsingKeyword.REQUIRE.value or obj == RubyParsingKeyword.REQUIRE_RELATIVE.value:
                  read_ahead_string = self.create_read_ahead_string(obj, following)
  
                  import_name = pp.Word(pp.alphanums + CoreParsingKeyword.UNDERSCORE.value + CoreParsingKeyword.SLASH.value + CoreParsingKeyword.DOT.value)
                  ignore_between_require_and_import_name = pp.Word(pp.alphanums + CoreParsingKeyword.UNDERSCORE.value +
                                                                   CoreParsingKeyword.DOT.value + CoreParsingKeyword.OPENING_ROUND_BRACKET.value)
  
                  expression_to_match = (pp.Keyword(RubyParsingKeyword.REQUIRE.value) | pp.Keyword(RubyParsingKeyword.REQUIRE_RELATIVE.value)) + (
  
                      (pp.Keyword(CoreParsingKeyword.SINGLE_QUOTE.value) +
                       import_name.setResultsName(CoreParsingKeyword.IMPORT_ENTITY_NAME.value) +
                       pp.Keyword(CoreParsingKeyword.SINGLE_QUOTE.value)) |
  
                      (pp.Keyword(CoreParsingKeyword.DOUBLE_QUOTE.value) +
                       import_name.setResultsName(CoreParsingKeyword.IMPORT_ENTITY_NAME.value) +
                       pp.Keyword(CoreParsingKeyword.DOUBLE_QUOTE.value)) |
  
                      pp.OneOrMore(ignore_between_require_and_import_name) + pp.Suppress(pp.Keyword(CoreParsingKeyword.SINGLE_QUOTE.value) | pp.Keyword(CoreParsingKeyword.DOUBLE_QUOTE.value)) +
                      import_name.setResultsName(CoreParsingKeyword.IMPORT_ENTITY_NAME.value)
  
                  )
  
                  try:
                      parsing_result = expression_to_match.parseString(read_ahead_string)
                  except Exception as some_exception:
                      result.analysis.statistics.increment(Statistics.Key.PARSING_MISSES)
                      LOGGER.warning(f'warning: could not parse result {result=}\n{some_exception}')
                      LOGGER.warning(f'next tokens: {[obj] + following[:ParsingMixin.Constants.MAX_DEBUG_TOKENS_READAHEAD.value]}')
                      continue
  
                  analysis.statistics.increment(Statistics.Key.PARSING_HITS)
  
                  # ignore any dependency substring from the config ignore list
                  dependency = getattr(parsing_result, CoreParsingKeyword.IMPORT_ENTITY_NAME.value)
  
                  # try to resolve the dependency
                  resolved_dependency = self.try_resolve_dependency(dependency, result, analysis)
  
                  if self._is_dependency_in_ignore_list(resolved_dependency, analysis):
                      LOGGER.debug(f'ignoring dependency from {result.unique_name} to {resolved_dependency}')
                  else:
                      result.scanned_import_dependencies.append(resolved_dependency)
                      LOGGER.debug(f'adding import: {resolved_dependency}')
  
      def try_resolve_dependency(self, dependency: str, result: AbstractFileResult, analysis) -> str:
  
          successfully_resolved_dependency = False
  
          # resolve in pure POSIX way
          resolved_posix_dependency = self.resolve_relative_dependency_path(dependency, result.absolute_dir_path, analysis.source_directory)
          if '.rb' not in resolved_posix_dependency:
              resolved_posix_dependency = f"{resolved_posix_dependency}.rb"
  
          check_dependency_path = f"{PosixPath(analysis.source_directory).parent}/{resolved_posix_dependency}"
          if os.path.exists(check_dependency_path):
              dependency = resolved_posix_dependency
              successfully_resolved_dependency = True
  
          if not successfully_resolved_dependency:
              # otherwise try to resolve it as a non-POSIX dependency, i.e. where "../" imports from the current directory "./"
              non_posix_dependency = ""
              resolved_non_posix_dependency = ""
  
              # resolve/check by reducing only the first ".." to "."
              if CoreParsingKeyword.POSIX_PARENT_DIRECTORY.value in dependency:
                  non_posix_dependency = dependency.replace(CoreParsingKeyword.POSIX_PARENT_DIRECTORY.value, CoreParsingKeyword.POSIX_CURRENT_DIRECTORY.value, 1)
                  resolved_non_posix_dependency = self.resolve_relative_dependency_path(non_posix_dependency, result.absolute_dir_path, analysis.source_directory)
                  if '.rb' not in resolved_non_posix_dependency:
                      resolved_non_posix_dependency = f"{resolved_non_posix_dependency}.rb"
  
                  check_dependency_path = f"{PosixPath(analysis.source_directory).parent}/{resolved_non_posix_dependency}"
                  if os.path.exists(check_dependency_path):
                      dependency = resolved_non_posix_dependency
                      successfully_resolved_dependency = True
  
          # as a last step, try to check if the dependency can be found in a local "lib" folder
          if not successfully_resolved_dependency:
  
              resolved_lib_dependency = self.resolve_relative_dependency_path(f"lib/{dependency}.rb", analysis.source_directory, analysis.source_directory)
              # f"{PosixPath(analysis.source_directory)}/lib/{dependency}.rb"
              check_resolved_lib_dependency_path = f"{PosixPath(analysis.source_directory).parent}/{resolved_lib_dependency}"
  
              if os.path.exists(check_resolved_lib_dependency_path):
                  dependency = resolved_lib_dependency
                  successfully_resolved_dependency = True
  
          return dependency
  
  
  if __name__ == "__main__":
      LEXER = RubyParser()
      print(f'{LEXER.results=}')
  