summaryrefslogtreecommitdiff
path: root/api_coverage.py
blob: c975fb8690c1829e56e808be7a7ffad80793d3ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/python

import os
import sys
import re
import subprocess
import argparse

c_exclude_list = [
    "elm_app", # These are only useful for C apps
    "elm_widget", # Custom widgets, probably not feasible for us to provide
    "elm_prefs", # Intended for configuration dialogs
    "elm_route", # Useless API?
    "elm_glview", # Is there an OpenGL API for Python that can be used with this?
    "evas_gl_", # ditto
    "elm_quicklaunch", # Is quicklaunch relevant for us?
    "emotion_object_extension_may_play_fast_get", # this optimization does not work from py
    "edje_edit_", # edje edit is not there for users to use
    "ecore_thread_", # python has his own thread abstraction library
    "ecore_pipe_", # python has his own pipe abstraction library
    "ecore_getopt_", # python has his own getopt implementation
    "ecore_coroutine_", # python has someting similar...maybe
    "ecore_fork_", # low level stuff, not to be exposed
    "ecore_timer_dump", # this is just usefull for debugging
    "ecore_throttle_", # I don't know what this is :/  - davemds
    "elm_check_state_pointer_set", # Cannot be implemented in Python
]
c_excludes = "|".join(c_exclude_list)

py_exclude_list = [
    "elm_naviframe_item_simple_push", # macro
    "elm_object_item_content", # macro
    "elm_object_item_text", # macro
    "elm_object_content", # macro
    "elm_object_text", # macro
]
py_excludes = "|".join(py_exclude_list)

parser = argparse.ArgumentParser()
parser.add_argument("--python", action="store_true", default=False, help="Show Python API coverage")
parser.add_argument("--c", action="store_true", default=False, help="Show C API coverage")
parser.add_argument("libs", nargs="+", help="Possible values are eo, evas, ecore, ecore-file, edje, emotion, elementary and all.")
args = parser.parse_args()

libs = args.libs[:]

if libs == ["all"]:
    libs = ["eo", "evas", "ecore", "ecore-file", "edje", "emotion", "elementary"]

params = {
    "eo": ("include", "Eo", "eo"),
    "evas": ("include", "Evas", "evas"),
    "ecore": ("include", "Ecore", "ecore"),
    "ecore-file": ("include", "Ecore_File", "ecore_file"),
    "edje": ("include", "Edje", "edje"),
    "emotion": ("include", "Emotion", "emotion"),
    "elementary": ("efl/elementary", "Elementary", "elm"),
}

def pkg_config(require, min_vers=None):
    name = require.capitalize()
    try:
        sys.stdout.write("Checking for " + name + ": ")
        ver = subprocess.check_output(["pkg-config", "--modversion", require]).decode("utf-8").strip()
        if min_vers is not None:
            assert 0 == subprocess.call(["pkg-config", "--atleast-version", min_vers, require])
        cflags = subprocess.check_output(["pkg-config", "--cflags-only-I", require]).decode("utf-8").split()
        sys.stdout.write("OK, found " + ver + "\n")
        return cflags
    except (OSError, subprocess.CalledProcessError):
        raise SystemExit("Failed to find" + name + "with 'pkg-config'.  Please make sure that it is installed and available on your system path.")
    except (AssertionError):
        raise SystemExit("Failed to match version. Found: " + ver + "  Needed: " + min_vers)

def get_capis(inc_path, prefix):
    capis = []
    capi_pattern = re.compile("^ *EAPI [A-Za-z_ *\n]+ +\**\n?(?!" + c_excludes + ")(" + prefix + "_\w+) *\(", flags = re.S|re.M)

    for path, dirs, files in os.walk(inc_path):
        for f in files:
            open_args = (os.path.join(path, f),)
            open_kwargs = dict(mode="r")
            if sys.version_info[0] > 2: open_kwargs["encoding"] = "UTF-8"

            with open(*open_args, **open_kwargs) as header:
                capi = header.read()

                matches = re.finditer(capi_pattern, capi)
                for match in matches:
                    func = match.group(1)
                    capis.append(func)

    return capis

def get_pyapis(pxd_path, header_name, prefix):
    pyapis = []
    pyapi_pattern1 = re.compile('(cdef extern from "' + header_name + '\.h":\n)(.+)', flags = re.S)
    pyapi_pattern2 = re.compile("^ +[a-zA-Z _*]+?(?!" + py_excludes + ")(" + prefix + "_\w+)\(", flags = re.M)

    for path, dirs, files in os.walk(pxd_path):
        for f in files:
            if f.endswith(".pxd"):
                open_args = (os.path.join(path, f),)
                open_kwargs = dict(mode="r")
                if sys.version_info[0] > 2: open_kwargs["encoding"] = "UTF-8"

                with open(*open_args, **open_kwargs) as pxd:
                    pyapi = pxd.read()

                    cdef = re.search(pyapi_pattern1, pyapi)
                    if cdef:
                        matches = re.finditer(pyapi_pattern2, cdef.group(2))
                        for match in matches:
                            func = match.group(1)
                            pyapis.append(func)

    return pyapis


for lib in libs:
    inc_path = pkg_config(lib, "1.7.99")[0][2:]
    pxd_path, header_name, prefix = params[lib]

    capis = get_capis(inc_path, prefix)
    pyapis = get_pyapis(pxd_path, header_name, prefix)

    capis = set(capis)
    pyapis = set(pyapis)
    differences = capis.union(pyapis) - capis.intersection(pyapis)

    for d in sorted(differences):
        if args.python and d in capis:
            print("{0} is missing from Python API".format(d))
        if args.c and d in pyapis:
            print("{0} is missing from C API".format(d))

    if args.python or args.c: print("\n---")

    if args.python:
        print("Number of functions missing from Python API: {0}".format(len(capis - capis.intersection(pyapis))))
    if args.c:
        print("Number of functions missing from C API: {0}".format(len(pyapis - capis.intersection(pyapis))))

    if args.python or args.c: print("---")

    if args.python:
        print("Python API functions: {0}".format(len(pyapis)))
    if args.c:
        print("C API functions: {0}".format(len(capis)))

    if args.python and len(capis) > 0:
        percentage = float(len(capis.intersection(pyapis))) / float(len(capis)) * 100.0
        print("===")
        print("Bindings coverage {0:.2f}%".format(percentage))

    if args.python or args.c: print("---\n")