CoolFace
Apppublic

CintraAI/code-chunker

sourceHugging Facemitupdated 1y agoView on Hugging Face
5likes
CodeParser.py359 linesDownload Raw Back to root
1import os2import subprocess3from typing import List, Dict, Union, Tuple4from tree_sitter import Language, Parser, Node5import logging6 7class CodeParser:8    # Added a CACHE_DIR class attribute for caching9    CACHE_DIR = os.path.expanduser("~/.code_parser_cache")10 11    def __init__(self, file_extensions: Union[None, List[str], str] = None):12        if isinstance(file_extensions, str):13            file_extensions = [file_extensions]14        self.language_extension_map = {15            "py": "python",16            "js": "javascript",17            "jsx": "javascript",18            "css": "css",19            "ts": "typescript",20            "tsx": "typescript",21            "php": "php",22            "rb": "ruby",23            "go": "go"24        }25        if file_extensions is None:26            self.language_names = []27        else:28            self.language_names = [self.language_extension_map.get(ext) for ext in file_extensions if29                                   ext in self.language_extension_map]30        self.languages = {}31        self._install_parsers()32 33    def _install_parsers(self):34        logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')35 36        try:37            # Ensure cache directory exists38            if not os.path.exists(self.CACHE_DIR):39                os.makedirs(self.CACHE_DIR)40 41            for language in self.language_names:42                repo_path = os.path.join(self.CACHE_DIR, f"tree-sitter-{language}")43 44                # Check if the repository exists and contains necessary files45                if not os.path.exists(repo_path) or not self._is_repo_valid(repo_path, language):46                    try:47                        if os.path.exists(repo_path):48                            logging.info(f"Updating existing repository for {language}")49                            update_command = f"cd {repo_path} && git pull"50                            subprocess.run(update_command, shell=True, check=True)51                        else:52                            logging.info(f"Cloning repository for {language}")53                            clone_command = f"git clone https://github.com/tree-sitter/tree-sitter-{language} {repo_path}"54                            subprocess.run(clone_command, shell=True, check=True)55                    except subprocess.CalledProcessError as e:56                        logging.error(f"Failed to clone/update repository for {language}. Error: {e}")57                        continue58 59                try:60                    build_path = os.path.join(self.CACHE_DIR, f"build/{language}.so")61                    62                    # Special handling for TypeScript63                    if language == 'typescript':64                        ts_dir = os.path.join(repo_path, 'typescript')65                        tsx_dir = os.path.join(repo_path, 'tsx')66                        if os.path.exists(ts_dir) and os.path.exists(tsx_dir):67                            Language.build_library(build_path, [ts_dir, tsx_dir])68                        else:69                            raise FileNotFoundError(f"TypeScript or TSX directory not found in {repo_path}")70                    elif language == 'php':71                        php_dir = os.path.join(repo_path, 'php')72                        if os.path.exists(php_dir):73                            Language.build_library(build_path, [php_dir])74                        else:75                            raise FileNotFoundError(f"PHP directory not found in {repo_path}")76                    else:77                        Language.build_library(build_path, [repo_path])78                    79                    self.languages[language] = Language(build_path, language)80                    logging.info(f"Successfully built and loaded {language} parser")81                except Exception as e:82                    logging.error(f"Failed to build or load language {language}. Error: {str(e)}")83                    logging.error(f"Repository path: {repo_path}")84                    logging.error(f"Build path: {build_path}")85                    if language == 'typescript':86                        logging.error(f"TypeScript dir exists: {os.path.exists(ts_dir)}")87                        logging.error(f"TSX dir exists: {os.path.exists(tsx_dir)}")88                    elif language == 'php':89                        logging.error(f"PHP dir exists: {os.path.exists(php_dir)}")90 91        except Exception as e:92            logging.error(f"An unexpected error occurred during parser installation: {str(e)}")93 94    def _is_repo_valid(self, repo_path: str, language: str) -> bool:95        """Check if the repository contains necessary files."""96        if language == 'typescript':97            return (os.path.exists(os.path.join(repo_path, 'typescript', 'src', 'parser.c')) and98                     os.path.exists(os.path.join(repo_path, 'tsx', 'src', 'parser.c')))99        elif language == 'php':100            return os.path.exists(os.path.join(repo_path, 'php', 'src', 'parser.c'))101        else:102            return os.path.exists(os.path.join(repo_path, 'src', 'parser.c'))103 104    def parse_code(self, code: str, file_extension: str) -> Union[None, Node]:105        language_name = self.language_extension_map.get(file_extension)106        if language_name is None:107            print(f"Unsupported file type: {file_extension}")108            return None109 110        language = self.languages.get(language_name)111        if language is None:112            print("Language parser not found")113            return None114 115        parser = Parser()116        parser.set_language(language)117        tree = parser.parse(bytes(code, "utf8"))118 119        if tree is None:120            print("Failed to parse the code")121            return None122 123        return tree.root_node124 125    def extract_points_of_interest(self, node: Node, file_extension: str) -> List[Tuple[Node, str]]:126        node_types_of_interest = self._get_node_types_of_interest(file_extension)127 128        points_of_interest = []129        if node.type in node_types_of_interest.keys():130            points_of_interest.append((node, node_types_of_interest[node.type]))131 132        for child in node.children:133            points_of_interest.extend(self.extract_points_of_interest(child, file_extension))134 135        return points_of_interest136 137    def _get_node_types_of_interest(self, file_extension: str) -> Dict[str, str]:138        node_types = {139            'py': {140                'import_statement': 'Import',141                'export_statement': 'Export',142                'class_definition': 'Class',143                'function_definition': 'Function',144            },145            'css': {146                'tag_name': 'Tag',147                '@media': 'Media Query',148            },149            'js': {150                'import_statement': 'Import',151                'export_statement': 'Export',152                'class_declaration': 'Class',153                'function_declaration': 'Function',154                'arrow_function': 'Arrow Function',155                'statement_block': 'Block',156            },157            'ts': {158                'import_statement': 'Import',159                'export_statement': 'Export',160                'class_declaration': 'Class',161                'function_declaration': 'Function',162                'arrow_function': 'Arrow Function',163                'statement_block': 'Block',164                'interface_declaration': 'Interface',165                'type_alias_declaration': 'Type Alias',166            },167            'php': {168                'namespace_definition': 'Namespace',169                'class_declaration': 'Class',170                'method_declaration': 'Method',171                'function_definition': 'Function',172                'interface_declaration': 'Interface',173                'trait_declaration': 'Trait',174            },175            'rb': {176                'class': 'Class',177                'method': 'Method',178                'module': 'Module',179                'singleton_class': 'Singleton Class',180                'begin': 'Begin Block',181            },182            'go': {183                'import_declaration': 'Import',184                'function_declaration': 'Function',185                'method_declaration': 'Method',186                'type_declaration': 'Type',187                'struct_type': 'Struct',188                'interface_type': 'Interface',189                'package_clause': 'Package'190            }191        }192 193        if file_extension in node_types.keys():194            return node_types[file_extension]195        elif file_extension == "jsx":196            return node_types["js"]197        elif file_extension == "tsx":198            return node_types["ts"]199        else:200            raise ValueError("Unsupported file type")201        202 203    def _get_nodes_for_comments(self, file_extension: str) -> Dict[str, str]:204        node_types = {205            'py': {206                'comment': 'Comment',207                'decorator': 'Decorator',  # Broadened category208            },209            'css': {210                'comment': 'Comment'211            },212            'js': {213                'comment': 'Comment',214                'decorator': 'Decorator',  # Broadened category215            },216            'ts': {217                'comment': 'Comment',218                'decorator': 'Decorator',219            },220            'php': {221                'comment': 'Comment',222                'attribute': 'Attribute',223            },224            'rb': {225                'comment': 'Comment',226            },227            'go': {228                'comment': 'Comment',229            }230        }231 232        if file_extension in node_types.keys():233            return node_types[file_extension]234        elif file_extension == "jsx":235            return node_types["js"]236        elif file_extension == "tsx":237            return node_types["ts"]238        else:239            raise ValueError("Unsupported file type")240        241    def extract_comments(self, node: Node, file_extension: str) -> List[Tuple[Node, str]]:242        node_types_of_interest = self._get_nodes_for_comments(file_extension)243 244        comments = []245        if node.type in node_types_of_interest:246            comments.append((node, node_types_of_interest[node.type]))247 248        for child in node.children:249            comments.extend(self.extract_comments(child, file_extension))250 251        return comments252 253    def get_lines_for_points_of_interest(self, code: str, file_extension: str) -> List[int]:254        language_name = self.language_extension_map.get(file_extension)255        if language_name is None:256            raise ValueError("Unsupported file type")257 258        language = self.languages.get(language_name)259        if language is None:260            raise ValueError("Language parser not found")261 262        parser = Parser()263        parser.set_language(language)264 265        tree = parser.parse(bytes(code, "utf8"))266 267        root_node = tree.root_node268        points_of_interest = self.extract_points_of_interest(root_node, file_extension)269 270        line_numbers_with_type_of_interest = {}271 272        for node, type_of_interest in points_of_interest:273            start_line = node.start_point[0] 274            if type_of_interest not in line_numbers_with_type_of_interest:275                line_numbers_with_type_of_interest[type_of_interest] = []276 277            if start_line not in line_numbers_with_type_of_interest[type_of_interest]:278                line_numbers_with_type_of_interest[type_of_interest].append(start_line)279 280        lines_of_interest = []281        for _, line_numbers in line_numbers_with_type_of_interest.items():282            lines_of_interest.extend(line_numbers)283 284        return lines_of_interest285 286    def get_lines_for_comments(self, code: str, file_extension: str) -> List[int]:287        language_name = self.language_extension_map.get(file_extension)288        if language_name is None:289            raise ValueError("Unsupported file type")290 291        language = self.languages.get(language_name)292        if language is None:293            raise ValueError("Language parser not found")294 295        parser = Parser()296        parser.set_language(language)297 298        tree = parser.parse(bytes(code, "utf8"))299 300        root_node = tree.root_node301        comments = self.extract_comments(root_node, file_extension)302 303        line_numbers_with_comments = {}304 305        for node, type_of_interest in comments:306            start_line = node.start_point[0] 307            if type_of_interest not in line_numbers_with_comments:308                line_numbers_with_comments[type_of_interest] = []309 310            if start_line not in line_numbers_with_comments[type_of_interest]:311                line_numbers_with_comments[type_of_interest].append(start_line)312 313        lines_of_interest = []314        for _, line_numbers in line_numbers_with_comments.items():315            lines_of_interest.extend(line_numbers)316 317        return lines_of_interest318 319    def print_all_line_types(self, code: str, file_extension: str):320        language_name = self.language_extension_map.get(file_extension)321        if language_name is None:322            print(f"Unsupported file type: {file_extension}")323            return324 325        language = self.languages.get(language_name)326        if language is None:327            print("Language parser not found")328            return329 330        parser = Parser()331        parser.set_language(language)332        tree = parser.parse(bytes(code, "utf8"))333 334        root_node = tree.root_node335        line_to_node_type = self.map_line_to_node_type(root_node)336 337        code_lines = code.split('\n')338 339        for line_num, node_types in line_to_node_type.items():340            line_content = code_lines[line_num - 1]  # Adjusting index for zero-based indexing341            print(f"line {line_num}: {', '.join(node_types)} | Code: {line_content}")342 343 344    def map_line_to_node_type(self, node, line_to_node_type=None, depth=0):345        if line_to_node_type is None:346            line_to_node_type = {}347 348        start_line = node.start_point[0] + 1  # Tree-sitter lines are 0-indexed; converting to 1-indexed349 350        # Only add the node type if it's the start line of the node351        if start_line not in line_to_node_type:352            line_to_node_type[start_line] = []353        line_to_node_type[start_line].append(node.type)354 355        for child in node.children:356            self.map_line_to_node_type(child, line_to_node_type, depth + 1)357 358        return line_to_node_type359