1#!/usr/bin/env python3
2
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, see <https://www.gnu.org/licenses/>.
15#
16#
17# Utility script to re-implement gobj2dot.rb in python with extra
18# option to send the output to a new file.
19#
20# Copyright (C) 2009 Henrik Akesson
21# Copyright (C) 2021 John Marshall
22
23from __future__ import print_function
24
25import os
26import sys
27import argparse
28import glob
29import re
30
31header = '''digraph G {
32        rankdir = RL
33
34        fontname = "Bitstream Vera Sans"
35        fontsize = 8
36
37        node [ shape = "record" ]
38
39        edge [ arrowhead = empty ]'''
40
41footer = '}'
42
43re_def_type = re.compile(
44    r'G_DEFINE_TYPE\s*\(\s*\w+,\s*(\w+),\s*(\w+)\s*\)',
45    re.DOTALL
46)
47re_def_type_code = re.compile(
48    r'G_DEFINE_TYPE_WITH_CODE\s*\(\s*(\w+).*G_IMPLEMENT_INTERFACE\s*\s*\(\s*(\w+)',
49    re.DOTALL
50)
51re_interface = re.compile(
52    r'G_TYPE_INTERFACE\s*,\s*\"([^\"]+)\"',
53    re.DOTALL
54)
55
56
57class Args():
58    def __init__(self):
59        parser = argparse.ArgumentParser()
60        parser.add_argument(
61            '--output',
62            metavar='OUTPUT_FILE',
63            help='output file - otherwise output to STDOUT'
64        )
65        parser.add_argument(
66            'PATH',
67            help='search path'
68        )
69
70        self.path = os.path.realpath(parser.parse_args().PATH)
71        if parser.parse_args().output:
72            self.output = os.path.realpath(parser.parse_args().output)
73        else:
74            self.output = None
75
76def snake_to_camel(name):
77    return re.sub(r'(?:^|_)([a-z])', lambda x: x.group(1).upper(), name)
78
79def gegl_type_to_camel(name):
80    return snake_to_camel('_'.join(name.split('_TYPE_')).lower())
81
82
83def main():
84    args = Args()
85
86    if args.output:
87        try:
88            out_file = open(os.path.realpath(args.output), 'w')
89        except IOError:
90            print('cannot open output file %s' % args.output,
91                  sys.stderr)
92            sys.exit(1)
93    else:
94        out_file = sys.stdout
95
96    print(header, file = out_file)
97
98    for filename in glob.iglob(os.path.join(args.path, '**', '*.[ch]'),
99                               recursive = True):
100        f = open(filename, mode='r', encoding='utf-8')
101        content = f.read()
102        f.close()
103        match = re.search(re_def_type, content)
104        if match:
105            if match.groups()[1] != 'G_TYPE_OBJECT':
106                print(
107                    '\t' + snake_to_camel(match.groups()[0]) +
108                    ' -> ' + gegl_type_to_camel(match.groups()[1]),
109                    file = out_file
110                )
111        else:
112            match = re.search(re_def_type_code, content)
113            if match:
114                print(
115                    '\t' + match.groups()[0] +
116                    ' -> ' + gegl_type_to_camel(match.groups()[1]) +
117                    ' [ style = dashed ]',
118                    file = out_file
119                )
120            else:
121                match = re.search(re_interface, content)
122                if match:
123                    print(
124                    '\t' + match.groups()[0] +
125                    ' [ label = \"{\\<\\<interface\\>\\>\\l' +
126                    match.groups()[0] + '}\" ]',
127                    file = out_file
128                )
129
130
131    print(footer, file = out_file)
132
133    sys.exit(0)
134
135
136if __name__ == "__main__":
137  main()