Skip to main content
edited tags; edited title
Link
Jamal
  • 34.9k
  • 13
  • 133
  • 237

pretty Pretty print braces - Review Request for my source code analyzer

Source Link
Murat
  • 243
  • 1
  • 6

pretty print braces - Review Request for my source code analyzer

I wrote a simple utility script to process source code files, extract and pretty print braces. It could be very useful for spotting the ugliest files in a code repository. So, I tidied it up a little bit and open-sourced it here.

I would very like to have your suggestions and comments on how to make this Python code better in any way.

__author__ = 'Murat Derya Ozen'

PROGRAM_DESCRIPTION = """
Spot the ugliest files in a code repository.
Given a path to a file or a directory, this program parses source files to extract,
pretty print and calculate the number of curly brackets, i.e braces.

*** Samples usages ***

> python pretty_print_braces file --path /root/repo/svn/trunk/SomeJavaClass.java

Assuming SomeJavaClass.java is a simple Java source file containing the following code:
    public class SomeJavaClass {
        public static void main(String[] args) {
            if (args != null && args.length > 2) {
                System.out.println("Hello");
            }
        }
    }

The program would output:
/root/repo/svn/trunk/SomeJavaClass.java
{
    {
        {
        }
    }
}
Depths: [1, 1, 1]
Max depth: 3

The output starts with the file name, followed by the braces in the file pretty-printed,
followed by the number of braces at each level, finally followed by the maximum depth.
This means that the source file contains at most 3 levels of nested braces.
Furthermore; we see that at there is 1 opening and closing brace for each level
(i.e 1 for the class declaration, 1 for the static main method declaration and 1 for the
if statement).


> python pretty_print_braces file --path /root/repo/svn/trunk/SomeJavaClass.java -q

Adding '-q' or '--quiet' switches to quite mode where pretty-printing of the braces are not
included in the output.


> python pretty_print_braces dir -p /root/repo/svn/trunk

This command process the whole directory (recursively) as opposed to a single file. It processes each
source file and prints the ugliest 5. Ugliness is defined by having the most depth of nested braces.

> python pretty_print_braces dir -p /root/repo/svn/trunk --nonrecursive --extensions=cpp,c,h -q -n 3

Process the source files with extensions cpp, c or h in the directory at /root/repo/svn/trunk. Output the
ugliest 3 source files in quiet mode. --nonrecursive indicates that subdirectories of trunk will not be traversed.
"""

import argparse
import heapq
import re
import os

INDENT = ' ' * 4
LINE_SEPARATOR = os.linesep


def extract_braces(source_code, filename, quiet):
    """ Parses and extracts the braces from source_code string.
    Returns a tuple (max_depth, depths, braces, filename).
    max_depth is the maximum depth of the braces; the deepest level found.
    depths is a list of integers such that depths[i] is the number of
    opening braces at depth i.
    braces is a string containing all braces in source_code pretty printed.
    """
    current_depth, depths, braces = 0, [0], []

    def increment_depth_occurence(d):
        if d < len(depths):
            depths[d] += 1
        else:
            depths.append(1)

    for char in source_code:
        if '{' == char:
            if not quiet:
                braces.append((INDENT * current_depth) + '{')
            current_depth += 1
            increment_depth_occurence(current_depth)
        elif '}' == char:
            current_depth -= 1
            if not quiet:
                braces.append((INDENT * current_depth) + '}')
    max_depth = len(depths) - 1
    return max_depth, depths[1:], LINE_SEPARATOR.join(braces), filename


def analyze_file(filename, quiet):
    """ Open the file named filename, parse it and
    return the extract_braces(file_content)'s result. """
    file = open(filename, 'r')
    content = file.read()
    result = extract_braces(content, filename, quiet)
    file.close()
    return result


def walk_through_directory(rootpath, recursive, n, file_extensions, quiet):
    """ Taverse the directory rooted at rootpath. Analyze each file.
    Store the greatest n results inside a heap.
    Only files within file_extensions are processed, other are skipped.
    If recursive is True, traversal will include subdirectories of
    rootpath as well.
    Returns the heap containing at most n results. Each element
    is a tuple returned by extract_braces(source_code). Elements
    are compared according to the tuple' first element.
    """
    heap_max_size = n

    def heappush(h, element):
        """ Insert element into h iff heap's max size has not been reached
        or element is greater than the lowest element in the heap. """
        if len(h) < heap_max_size:
            heapq.heappush(h, element)
        else:
            heapq.heappushpop(h, element)

    file_extension_regex = '^.+\\.(' + '|'.join(file_extensions) + ')$'
    file_extension_supported = lambda filename: bool(re.match(file_extension_regex, filename, re.I))
    join = os.path.join
    h = [] # heap to keep track of the greatest n items.

    def process_directory(dirpath, filenames):
        """ Process the files rooted immediately in dirpath,
        without going into dirpath's subdirectories. """
        for filename in filenames:
            if not file_extension_supported(filename): continue
            absolute_file_path = join(dirpath, filename)
            result = analyze_file(absolute_file_path, quiet)
            heappush(h, result)

    if recursive:
        for dirpath, _, filenames in os.walk(rootpath):
            process_directory(dirpath, filenames)
    else:
        process_directory(rootpath, os.listdir(rootpath))
    return h


def get_argparser():
    """ Defines and returns the ArgumentParser for this program. """
    parser = argparse.ArgumentParser(description=PROGRAM_DESCRIPTION)
    sub_parsers = parser.add_subparsers()

    file_sub_parser = sub_parsers.add_parser('file')
    file_sub_parser.add_argument('-p', '--path', required=True,
                                 help='Parse this file to extract and pretty print braces.')
    file_sub_parser.add_argument('-q', '--quiet', action='store_true',
                                 help='Increase output quietness. By default, '
                                      + 'this program prints the file name, maximum depth (deepest level), '
                                      + 'number of opening braces for each depth and the braces pretty '
                                      + 'printed. Supplying \'-q\' will omit pretty printing of the braces')
    file_sub_parser.set_defaults(func=pretty_print_file)

    dir_sub_parser = sub_parsers.add_parser('dir')

    dir_sub_parser.add_argument('-p', '--path', required=True,
                                help='Traverse the files rooted in this directory and all its subdirectories '
                                     + 'and parse each file to extract and pretty print braces.')
    dir_sub_parser.add_argument('--nonrecursive', action='store_true',
                                help='Traverse only the files in dir but not in subdirectories of dir. By '
                                     + 'default, directories are traversed recursively.')
    dir_sub_parser.add_argument('--extensions', default='java,cpp,c,h',
                                help='Only the files with these file extensions are parsed. This optional '
                                     + 'argument can take a single extension or a comma separated list. '
                                     + 'Default is \'java,cpp,c,h\'.')
    dir_sub_parser.add_argument('-n', type=int, default=5,
                                help='Resulting output includes the ugliest \'n\' files. Ugliness is defined by '
                                     + 'the depth of braces. For instance, the Java class defined as '
                                     + '\'class A { void method() { if(true) {return;} } }\' would have a '
                                     + ' depth of 3 as it\'s curly braces go 3 levels deep. By default n=5.')

    dir_sub_parser.add_argument('-q', '--quiet', action='store_true',
                                help='Increase output quietness. By default, '
                                     + 'this program prints the file name, maximum depth (deepest level), '
                                     + 'number of opening braces for each depth and the braces pretty '
                                     + 'printed. Supplying \'-q\' will omit pretty printing of the braces')
    dir_sub_parser.set_defaults(func=pretty_print_dir)

    return parser


def print_result(analysis_result, quiet):
    """ Print analysis_result to STDOUT """
    max_depth, depths, braces, filename = analysis_result
    print filename
    if not quiet:
        print braces
    print 'Depths:', depths
    print 'Max depth:', max_depth


def pretty_print_dir(args):
    heap = walk_through_directory(args.path, not args.nonrecursive, args.n, args.extensions.split(','), args.quiet)
    for i in range(min(len(heap), args.n)):
        analysis_result = heapq.heappop(heap)
        print_result(analysis_result, args.quiet)
        print


def pretty_print_file(args):
    analysis_result = analyze_file(args.path, args.quiet)
    print_result(analysis_result, args.quiet)


def main():
    parser = get_argparser()
    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()