1515from gitevo .utils import is_git_dir , stdout_msg , stdout_link , as_str , aggregate_stat , ensure_file_extension
1616from gitevo .exceptions import *
1717
18+ """
19+ This module contains the main GitEvo classes: GitEvo, ParsedCommit, and ParsedFile.
20+ It provides functionality to analyze Git repositories, compute metrics, and generate reports.
21+
22+ See: https://github.com/andrehora/gitevo/tree/main/examples
23+
24+ Example:
25+
26+ from gitevo import GitEvo, ParsedCommit
27+
28+ git_url = 'https://github.com/pallets/flask'
29+ evo = GitEvo(repo=git_url, extension='.py')
30+
31+ @evo.metric('Lines of code (LOC)')
32+ def loc(commit: ParsedCommit):
33+ return commit.loc
34+
35+ @evo.metric('Python files')
36+ def python_files(commit: ParsedCommit):
37+ return len(commit.parsed_files)
38+
39+ @evo.metric('Test files')
40+ def test_files(commit: ParsedCommit):
41+ test_files = [f for f in commit.parsed_files if 'test_' in f.name.lower()]
42+ return len(test_files)
43+
44+ @evo.metric('LOC / Python files')
45+ def loc_per_file(commit: ParsedCommit):
46+ python_files = len(commit.parsed_files)
47+ if python_files == 0: return 0
48+ return commit.loc / python_files
49+
50+ evo.run()
51+
52+ """
1853
1954class GitEvo :
2055
2156 """
22- GitEvo app class, the main entrypoint to use GitEvo.
57+ GitEvo main class, the entrypoint to use GitEvo.
2358
2459 Args:
2560 repo (str): Git repository URL or local path
@@ -182,7 +217,7 @@ def _compute_metrics(self, git_repo: str) -> GitEvoResult:
182217 project_commits .add (selected_date )
183218
184219 # Chache parsed commits for each file extension, eg, .py, .js, .java, etc
185- parsed_commits = ParsedCommitCache (commit , self ._all_file_extensions ())
220+ parsed_commits = _ParsedCommitCache (commit , self ._all_file_extensions ())
186221 print (f'- Date: { selected_date } , commit: { commit .hash [0 :10 ]} , files: { parsed_commits .file_stats ()} ' )
187222
188223 # Iterate on each metric
@@ -298,14 +333,23 @@ def _export_csv(self, result: GitEvoResult):
298333
299334class ParsedFile :
300335
336+ """
337+ Represents a parsed file in a commit, containing its name, path, tree-sitter nodes, and lines of code (LOC).
338+ """
339+
301340 def __init__ (self , name : str , path : str , nodes : list [Node ], loc : int ):
302341 self .name = name
303342 self .path = path
304343 self .nodes = nodes
305344 self .loc = loc
306345
307346class ParsedCommit :
308-
347+
348+ """
349+ Represents a parsed commit in a repository, containing its hash, date, file extension, parsed files,
350+ tree-sitter nodes, and lines of code (LOC).
351+ """
352+
309353 def __init__ (self , hash : str , date : datetime , file_extension : str , parsed_files : list [ParsedFile ]):
310354 self .hash = hash
311355 self .date = date
@@ -316,27 +360,63 @@ def __init__(self, hash: str, date: datetime, file_extension: str, parsed_files:
316360
317361 @property
318362 def parsed_files (self ) -> list [ParsedFile ]:
363+ """
364+ Returns the list of parsed files in the commit.
365+ Returns:
366+ list[ParsedFile]: The list of parsed files.
367+ """
319368 return self ._parsed_files
320369
321370 @property
322371 def nodes (self ) -> list [Node ]:
372+ """
373+ Returns the list of tree-sitter nodes in the commit.
374+ Returns:
375+ list[Node]: The list of nodes.
376+ """
323377 if self ._nodes is None :
324378 self ._nodes = [node for file in self .parsed_files for node in file .nodes ]
325379 return self ._nodes
326380
327- def count_nodes (self , node_types : str | list [str ] | None = None ) -> int :
328- if node_types is None :
329- return len (self .nodes )
330- return len (self .find_nodes_by_type (node_types ))
331-
332381 @property
333382 def loc (self ) -> int :
383+ """
384+ Returns the total lines of code (LOC) in the commit.
385+ Returns:
386+ int: The total LOC.
387+ """
334388 if self ._loc is None :
335389 self ._loc = sum ([file .loc for file in self .parsed_files ])
336390 return self ._loc
337-
338- def loc_by_type (self , node_type : str , aggregate : str = 'median' ) -> int | float | list [int ]:
339391
392+ def count_nodes (self , node_types : str | list [str ] | None = None ) -> int :
393+ """
394+ Counts the number of tree-sitter nodes in the commit, optionally filtered by node types.
395+
396+ See node types examples at:
397+ https://github.com/tree-sitter/tree-sitter-python/blob/master/src/node-types.json
398+ https://github.com/tree-sitter/tree-sitter-javascript/blob/master/src/node-types.json
399+ https://github.com/tree-sitter/tree-sitter-typescript/blob/master/typescript/src/node-types.json
400+ https://github.com/tree-sitter/tree-sitter-java/blob/master/src/node-types.json
401+
402+ Returns:
403+ int: The count of nodes.
404+ """
405+ if node_types is None :
406+ return len (self .nodes )
407+ return len (self .find_nodes_by_type (node_types ))
408+
409+ def loc_by_type (self , node_type : str , aggregate : str = 'median' ) -> int | float :
410+ """
411+ Calculates the lines of code (LOC) for nodes of a specific type, using the specified aggregation method.
412+ Args:
413+ node_type (str): The type of node to calculate LOC for.
414+ aggregate (str): The aggregation method to use ('median', 'mean', 'mode').
415+ Returns:
416+ int | float: The aggregated LOC value.
417+ Raises:
418+ BadLOCAggregate: If the aggregate method is invalid.
419+ """
340420 if aggregate is not None and aggregate not in ['median' , 'mean' , 'mode' ]:
341421 raise BadLOCAggregate (f'LOC aggregate should be median, mean, or mode' )
342422
@@ -348,6 +428,13 @@ def loc_by_type(self, node_type: str, aggregate: str = 'median') -> int | float
348428 return aggregate_stat (locs , aggregate )
349429
350430 def find_node_types (self , node_types : str | list [str ] = None ) -> list [str ]:
431+ """
432+ Finds the types of tree-sitter nodes in the commit, optionally filtered by node types.
433+ Args:
434+ node_types (str | list[str], optional): The node types to filter by.
435+ Returns:
436+ list[str]: The list of node types.
437+ """
351438 if node_types is None :
352439 return [node .type for node in self .nodes ]
353440
@@ -357,16 +444,36 @@ def find_node_types(self, node_types: str | list[str] = None) -> list[str]:
357444 return [node .type for node in self .nodes if node .type in node_types ]
358445
359446 def find_nodes_by_type (self , node_types : str | list [str ]) -> list [Node ]:
360-
447+ """
448+ Finds the tree-sitter nodes in the commit by their types.
449+ Args:
450+ node_types (str | list[str]): The node types to filter by.
451+ Returns:
452+ list[Node]: The list of nodes.
453+ """
361454 if isinstance (node_types , str ):
362455 node_types = [node_types ]
363456
364457 return [node for node in self .nodes if node .type in node_types ]
365458
366459 def named_children_for (self , node : Node ) -> list [Node ]:
460+ """
461+ Returns the named children of a given tree-sitter node.
462+ Args:
463+ node (Node): The tree-sitter node.
464+ Returns:
465+ list[Node]: The list of named child nodes.
466+ """
367467 return [each for each in node .children if each .is_named ]
368468
369469 def descendant_nodes_for (self , node : Node ) -> list [Node ]:
470+ """
471+ Returns the descendant nodes of a given tree-sitter node.
472+ Args:
473+ node (Node): The tree-sitter node.
474+ Returns:
475+ list[Node]: The list of descendant nodes.
476+ """
370477 descendants = []
371478 def traverse_node (current_node ):
372479 descendants .append (current_node )
@@ -377,13 +484,21 @@ def traverse_node(current_node):
377484 return descendants
378485
379486 def descendant_node_by_field_name (self , node : Node , name : str ) -> Node | None :
487+ """
488+ Finds a descendant node of a given tree-sitter node by its field name.
489+ Args:
490+ node (Node): The tree-sitter node.
491+ name (str): The field name to search for.
492+ Returns:
493+ Node | None: The descendant node with the specified field name, or None if not found.
494+ """
380495 for desc_node in self .descendant_nodes_for (node ):
381496 target_node = desc_node .child_by_field_name (name )
382497 if target_node is not None :
383498 return target_node
384499 return None
385500
386- class ParsedCommitCache :
501+ class _ParsedCommitCache :
387502
388503 def __init__ (self , commit : Commit , file_extensions : list [str ]):
389504 self .commit = commit
0 commit comments