<commit_before>"""
Load snippets from source files to show violation lines
in HTML reports.
"""

import os
import pygments
from pygments.lexers import TextLexer, guess_lexer_for_filename
from pygments.formatters import HtmlFormatter
from pygments.util import ClassNotFound

from diff_cover.git_path import GitPathTool

class Snippet(object):
    """
    A source code snippet.
    """

    VIOLATION_COLOR = '#ffcccc'
    DIV_CSS_CLASS = 'snippet'

    # Number of extra lines to include before and after
    # each snippet to provide context.
    NUM_CONTEXT_LINES = 4

    # Maximum distance between two violations within
    # a snippet.  If violations are further apart,
    # should split into two snippets.
    MAX_GAP_IN_SNIPPET = 4

    PATH_TOOL = GitPathTool(os.getcwd())

    def __init__(self, src_tokens, src_filename,
                 start_line, violation_lines):
        """
        Create a source code snippet.

        `src_tokens` is a list of `(token_type, value)`
        tuples, parsed from the source file.
        NOTE: `value` must be `unicode`, not a `str`

        `src_filename` is the name of the source file,
        used to determine the source file language.

        `start_line` is the line number of first line
        in `src_str`.  The first line in the file is
        line number 1.

        `violation_lines` is a list of line numbers
        to highlight as violations.

        Raises a `ValueError` if `start_line` is less than 1
        """
        if start_line < 1:
            raise ValueError('Start line must be >= 1')

        self._src_tokens = src_tokens
        self._src_filename = src_filename
        self._start_line = start_line
        self._violation_lines = violation_lines

    @classmethod
    def style_defs(cls):
        """
        Return the CSS style definitions required
        by the formatted snippet.
        """
        formatter = HtmlFormatter()
        formatter.style.highlight_color = cls.VIOLATION_COLOR
        return formatter.get_style_defs()

    def html(self):
        """
        Return an HTML representation of the snippet.
        """
        formatter = HtmlFormatter(
            cssclass=self.DIV_CSS_CLASS,
            linenos=True,
            linenostart=self._start_line,
            hl_lines=self._shift_lines(
                self._violation_lines,
                self._start_line
            ),
            lineanchors=self._src_filename
        )

        return pygments.format(self.src_tokens(), formatter)

    def src_tokens(self):
        """
        Return a list of `(token_type, value)` tokens
        parsed from the source file.
        """
        return self._src_tokens

    def line_range(self):
        """
        Return a tuple of the form `(start_line, end_line)`
        indicating the start and end line number of the snippet.
        """
        num_lines = len(self.text().split('\n'))
        end_line = self._start_line + num_lines - 1
        return (self._start_line, end_line)

    def text(self):
        """
        Return the source text for the snippet.
        """
        return ''.join([val for _, val in self._src_tokens])

    @classmethod
    def load_snippets_html(cls, src_path, violation_lines):
        """
        Load snippets from the file at `src_path` and format
        them as HTML.

        See `load_snippets()` for details.
        """
        snippet_list = cls.load_snippets(src_path, violation_lines)
        return [snippet.html() for snippet in snippet_list]

    @classmethod
    def load_snippets(cls, src_path, violation_lines):
        """
        Load snippets from the file at `src_path` to show
        violations on lines in the list `violation_lines`
        (list of line numbers, starting at index 0).

        The file at `src_path` should be a text file (not binary).

        Returns a list of `Snippet` instances.

        Raises an `IOError` if the file could not be loaded.
        """
        # Load the contents of the file
        with open(cls.PATH_TOOL.absolute_path(src_path)) as src_file:
            contents = src_file.read()

        # Construct a list of snippet ranges
        src_lines = contents.split('\n')
        snippet_ranges = cls._snippet_ranges(len(src_lines), violation_lines)

        # Parse the source into tokens
        token_stream = cls._parse_src(contents, src_path)

        # Group the tokens by snippet
        token_groups = cls._group_tokens(token_stream, snippet_ranges)

        return [
            Snippet(tokens, src_path, start, violation_lines)
            for (start, _), tokens in token_groups.iteritems()
        ]

    @classmethod
    def _parse_src(cls, src_contents, src_filename):
        """
        Return a stream of `(token_type, value)` tuples
        parsed from `src_contents` (str)

        Uses `src_filename` to guess the type of file
        so it can highlight syntax correctly.
        """

        # Parse the source into tokens<commit_msg>Use relative path rather than absolute path<commit_after>"""
Load snippets from source files to show violation lines
in HTML reports.
"""

import os
import pygments
from pygments.lexers import TextLexer, guess_lexer_for_filename
from pygments.formatters import HtmlFormatter
from pygments.util import ClassNotFound

from diff_cover.git_path import GitPathTool

class Snippet(object):
    """
    A source code snippet.
    """

    VIOLATION_COLOR = '#ffcccc'
    DIV_CSS_CLASS = 'snippet'

    # Number of extra lines to include before and after
    # each snippet to provide context.
    NUM_CONTEXT_LINES = 4

    # Maximum distance between two violations within
    # a snippet.  If violations are further apart,
    # should split into two snippets.
    MAX_GAP_IN_SNIPPET = 4

    PATH_TOOL = GitPathTool(os.getcwd())

    def __init__(self, src_tokens, src_filename,
                 start_line, violation_lines):
        """
        Create a source code snippet.

        `src_tokens` is a list of `(token_type, value)`
        tuples, parsed from the source file.
        NOTE: `value` must be `unicode`, not a `str`

        `src_filename` is the name of the source file,
        used to determine the source file language.

        `start_line` is the line number of first line
        in `src_str`.  The first line in the file is
        line number 1.

        `violation_lines` is a list of line numbers
        to highlight as violations.

        Raises a `ValueError` if `start_line` is less than 1
        """
        if start_line < 1:
            raise ValueError('Start line must be >= 1')

        self._src_tokens = src_tokens
        self._src_filename = src_filename
        self._start_line = start_line
        self._violation_lines = violation_lines

    @classmethod
    def style_defs(cls):
        """
        Return the CSS style definitions required
        by the formatted snippet.
        """
        formatter = HtmlFormatter()
        formatter.style.highlight_color = cls.VIOLATION_COLOR
        return formatter.get_style_defs()

    def html(self):
        """
        Return an HTML representation of the snippet.
        """
        formatter = HtmlFormatter(
            cssclass=self.DIV_CSS_CLASS,
            linenos=True,
            linenostart=self._start_line,
            hl_lines=self._shift_lines(
                self._violation_lines,
                self._start_line
            ),
            lineanchors=self._src_filename
        )

        return pygments.format(self.src_tokens(), formatter)

    def src_tokens(self):
        """
        Return a list of `(token_type, value)` tokens
        parsed from the source file.
        """
        return self._src_tokens

    def line_range(self):
        """
        Return a tuple of the form `(start_line, end_line)`
        indicating the start and end line number of the snippet.
        """
        num_lines = len(self.text().split('\n'))
        end_line = self._start_line + num_lines - 1
        return (self._start_line, end_line)

    def text(self):
        """
        Return the source text for the snippet.
        """
        return ''.join([val for _, val in self._src_tokens])

    @classmethod
    def load_snippets_html(cls, src_path, violation_lines):
        """
        Load snippets from the file at `src_path` and format
        them as HTML.

        See `load_snippets()` for details.
        """
        snippet_list = cls.load_snippets(src_path, violation_lines)
        return [snippet.html() for snippet in snippet_list]

    @classmethod
    def load_snippets(cls, src_path, violation_lines):
        """
        Load snippets from the file at `src_path` to show
        violations on lines in the list `violation_lines`
        (list of line numbers, starting at index 0).

        The file at `src_path` should be a text file (not binary).

        Returns a list of `Snippet` instances.

        Raises an `IOError` if the file could not be loaded.
        """
        # Load the contents of the file
        with open(cls.PATH_TOOL.relative_path(src_path)) as src_file:
            contents = src_file.read()

        # Construct a list of snippet ranges
        src_lines = contents.split('\n')
        snippet_ranges = cls._snippet_ranges(len(src_lines), violation_lines)

        # Parse the source into tokens
        token_stream = cls._parse_src(contents, src_path)

        # Group the tokens by snippet
        token_groups = cls._group_tokens(token_stream, snippet_ranges)

        return [
            Snippet(tokens, src_path, start, violation_lines)
            for (start, _), tokens in token_groups.iteritems()
        ]

    @classmethod
    def _parse_src(cls, src_contents, src_filename):
        """
        Return a stream of `(token_type, value)` tuples
        parsed from `src_contents` (str)

        Uses `src_filename` to guess the type of file
        so it can highlight syntax correctly.
        """

        # Parse the source into tokens
{'fraction_non_alphanumeric': 0.07626100880704563, 'fraction_numerical': 0.0016012810248198558, 'mean_word_length': 2.2455342643715492, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, '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': '3062225', 'n_tokens_mistral': 2909, 'n_tokens_neox': 2758, 'n_words': 917}