todonotifier.todo_notifier

This module contains the core logic of the application

 1"""This module contains the core logic of the application
 2"""
 3
 4import logging
 5import os
 6import re
 7from typing import Dict, List
 8
 9from todonotifier.constants import UNKNOWN_USER_NAME
10from todonotifier.models import POSITION, TODO, USER
11from todonotifier.utils import (
12    compute_file_line_no_to_chars_map,
13    compute_line_and_pos_given_span,
14)
15
16# logging configuration
17logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(process)d - %(name)s - %(levelname)s - %(message)s")
18logger = logging.getLogger(__name__)
19
20TODO_REGEX_PATTERN = r"TODO\s*(\{.*\})?\s*(@[^\s]*)?\s*(.*)?"
21
22
23def parse_files_for_todo_items(project_parent_dir: str, files: List[str], ignore_todo_case: bool) -> Dict[str, List[TODO]]:
24    """Parses the list of `files` one by one to collect all todo items
25
26    Args:
27        project_parent_dir (str): Parent directory of the project folder (required to get relative path of files and avoid exposing temporary paths)
28        files (List[str]): List of all files that need to be parsed
29        ignore_todo_case (bool): Boolean whether to look for case insensitive todo items like todo, Todo etc.
30
31    Returns:
32        Dict[str, List[TODO]]: Returns a key-value pair where key is relative path of file parsed and value is list of todo objects in that file
33    """
34    all_todos_objs = {}
35    for file in files:
36        try:
37            rel_file_path = os.path.relpath(file, project_parent_dir)
38            all_todos_objs[rel_file_path] = []
39            line_no_to_chars_map = compute_file_line_no_to_chars_map(file)
40            with open(file, "r") as f:
41                file_content = f.read()
42
43                flags = re.MULTILINE
44                if ignore_todo_case:
45                    flags |= re.IGNORECASE
46
47                todo_items = re.finditer(r"TODO.*", file_content, flags=flags)
48                for todo_item_idx, todo_item in enumerate(todo_items):
49                    try:
50                        todo_item_group = todo_item.group()
51                        todo_date_username = re.findall(TODO_REGEX_PATTERN, todo_item_group, flags=flags)
52
53                        if todo_date_username:
54                            todo_date_username = todo_date_username[0]
55
56                        msg = ""
57                        if len(todo_date_username) > 2:
58                            msg = todo_date_username[2]
59
60                        user = USER(UNKNOWN_USER_NAME)  # By default we assume an unknown user
61                        if len(todo_date_username) > 1:
62                            user = USER(todo_date_username[1][1:] or UNKNOWN_USER_NAME)  # handle empty string
63
64                        completion_date_str = ""
65                        if len(todo_date_username) > 0:
66                            completion_date_str = todo_date_username[0]
67                            if completion_date_str:
68                                completion_date_str = completion_date_str[1:-1]
69
70                        module = rel_file_path
71                        todo_position = todo_item.span()
72
73                        line = compute_line_and_pos_given_span(line_no_to_chars_map, todo_position)
74                        position = POSITION(line)
75
76                        todo = TODO(msg, user, completion_date_str, module, position)
77
78                        all_todos_objs[rel_file_path].append(todo)
79                    except Exception:
80                        logger.exception(f"Error in parsing todo item: {todo_item}, idx: {todo_item_idx}, all_todos: {all_todos_objs}")
81        except Exception:
82            logger.exception(f"Error in parsing todo items in file: {file}")
83
84    return all_todos_objs
logger = <Logger todonotifier.todo_notifier (INFO)>
TODO_REGEX_PATTERN = 'TODO\\s*(\\{.*\\})?\\s*(@[^\\s]*)?\\s*(.*)?'
def parse_files_for_todo_items( project_parent_dir: str, files: List[str], ignore_todo_case: bool) -> Dict[str, List[todonotifier.models.TODO]]:
24def parse_files_for_todo_items(project_parent_dir: str, files: List[str], ignore_todo_case: bool) -> Dict[str, List[TODO]]:
25    """Parses the list of `files` one by one to collect all todo items
26
27    Args:
28        project_parent_dir (str): Parent directory of the project folder (required to get relative path of files and avoid exposing temporary paths)
29        files (List[str]): List of all files that need to be parsed
30        ignore_todo_case (bool): Boolean whether to look for case insensitive todo items like todo, Todo etc.
31
32    Returns:
33        Dict[str, List[TODO]]: Returns a key-value pair where key is relative path of file parsed and value is list of todo objects in that file
34    """
35    all_todos_objs = {}
36    for file in files:
37        try:
38            rel_file_path = os.path.relpath(file, project_parent_dir)
39            all_todos_objs[rel_file_path] = []
40            line_no_to_chars_map = compute_file_line_no_to_chars_map(file)
41            with open(file, "r") as f:
42                file_content = f.read()
43
44                flags = re.MULTILINE
45                if ignore_todo_case:
46                    flags |= re.IGNORECASE
47
48                todo_items = re.finditer(r"TODO.*", file_content, flags=flags)
49                for todo_item_idx, todo_item in enumerate(todo_items):
50                    try:
51                        todo_item_group = todo_item.group()
52                        todo_date_username = re.findall(TODO_REGEX_PATTERN, todo_item_group, flags=flags)
53
54                        if todo_date_username:
55                            todo_date_username = todo_date_username[0]
56
57                        msg = ""
58                        if len(todo_date_username) > 2:
59                            msg = todo_date_username[2]
60
61                        user = USER(UNKNOWN_USER_NAME)  # By default we assume an unknown user
62                        if len(todo_date_username) > 1:
63                            user = USER(todo_date_username[1][1:] or UNKNOWN_USER_NAME)  # handle empty string
64
65                        completion_date_str = ""
66                        if len(todo_date_username) > 0:
67                            completion_date_str = todo_date_username[0]
68                            if completion_date_str:
69                                completion_date_str = completion_date_str[1:-1]
70
71                        module = rel_file_path
72                        todo_position = todo_item.span()
73
74                        line = compute_line_and_pos_given_span(line_no_to_chars_map, todo_position)
75                        position = POSITION(line)
76
77                        todo = TODO(msg, user, completion_date_str, module, position)
78
79                        all_todos_objs[rel_file_path].append(todo)
80                    except Exception:
81                        logger.exception(f"Error in parsing todo item: {todo_item}, idx: {todo_item_idx}, all_todos: {all_todos_objs}")
82        except Exception:
83            logger.exception(f"Error in parsing todo items in file: {file}")
84
85    return all_todos_objs

Parses the list of files one by one to collect all todo items

Arguments:
  • project_parent_dir (str): Parent directory of the project folder (required to get relative path of files and avoid exposing temporary paths)
  • files (List[str]): List of all files that need to be parsed
  • ignore_todo_case (bool): Boolean whether to look for case insensitive todo items like todo, Todo etc.
Returns:

Dict[str, List[TODO]]: Returns a key-value pair where key is relative path of file parsed and value is list of todo objects in that file