1#!/usr/bin/env python3
  2
  3"""Web page to show algorithm constants."""
  4
  5import os
  6import ast
  7import logging
  8import operator
  9import runpy
 10import collections
 11
 12import numpy as np
 13
 14# import before Django modules
 15from chart.project import settings
 16
 17from django.shortcuts import render
 18
 19from chart.common.prettyprint import Table
 20from chart.common.prettyprint import show_time
 21from chart.backend.activity import Activity
 22from chart.common.decorators import memoized
 23from chart.browse.make_url import make_url
 24
 25
 26def islist(obj):
 27    """True if `obj` is a list-type object."""
 28    return isinstance(obj, (list, tuple, np.ndarray))
 29
 30
 31EXP_BIG_THRESHOLD = 1000
 32EXP_SMALL_THRESHOLD = 0.0001
 33
 34
 35def constants_in_file(filename):
 36    """Given a Python file name yield names of all constants used."""
 37    tree = ast.parse(filename.open('r').read())
 38    for elem in ast.walk(tree):
 39        if (isinstance(elem, ast.Call) and
 40            isinstance(elem.func, ast.Name) and
 41            elem.func.id == 'get_constant'):
 42
 43            constant = None
 44
 45            for e in elem.args:
 46                try:
 47                    constant = e.s
 48                except AttributeError:
 49                    # this can happen when calling get_constant with positional attributes
 50                    logging.error('Error parsing get_constants call in {filename}'.format(
 51                            filename=filename))
 52                    continue
 53
 54            for e in elem.keywords:
 55                if e.arg == 'name':
 56                    constant = e.value.s
 57
 58            if constant is None:
 59                logging.error('Could not decode get_constant call in ' + filename)
 60
 61            else:
 62                yield constant
 63
 64
 65@memoized
 66def usage():
 67    """For each constant, identify where it is used."""
 68    res = collections.defaultdict(list)
 69    for activity in Activity.all():
 70        if activity.executable is not None and activity.executable.suffix == '.py':
 71            # the html template allows each executable to have multiple activities
 72            # pointing to it
 73            for constant in constants_in_file(activity.abs_executable):
 74                res[constant].append({'exe': activity.executable.name,
 75                                      'activities': [activity]})
 76
 77    return res
 78
 79
 80def makehtml(obj, constant):
 81    """Convert a single constant into the inside of an html table cell.
 82    This is a recursive call since a constant may be a complex object.
 83    """
 84    # scalar
 85    if not islist(obj):
 86        # print 'obj ', obj, ' type ', type(obj)
 87        if isinstance(obj, int) or isinstance(obj, np.number):
 88            if np.abs(obj) > EXP_BIG_THRESHOLD or np.abs(obj) < EXP_SMALL_THRESHOLD:
 89                return '{0:e}'.format(obj)
 90
 91        elif isinstance(obj, dict):
 92            return ''.join('<p>{k}: {v}</p>'.format(k=k, v=v) for k, v in obj.items())
 93
 94        return str(obj)
 95
 96    # empty list
 97    if isinstance(obj, list) and len(obj) == 0:
 98        t = Table()
 99        if 'labels' in constant:
100            t.append_headers(constant['labels'])
101
102        t.append((makehtml(x, constant) for x in obj))
103        return t.to_html_str()
104
105    # 1d
106    if not islist(obj[0]):
107        t = Table()
108        if 'labels' in constant:
109            t.append_headers(constant['labels'])
110
111        t.append((makehtml(x, constant) for x in obj))
112        return t.to_html_str()
113
114    # 2d
115    if not islist(obj[0][0]):
116        t = Table()
117        row_labels = None
118        if 'labels' in constant:
119            if not islist(constant['labels']):
120                # labels might be a single array ...
121                t.append_headers(constant['labels'])
122
123            else:
124                # ... or an array of arrays, with the labels for each dimension in a separate list
125                if len(constant['labels']) > 1:
126                    # leave space for left hand column
127                    t.append_headers([''] + list(constant['labels'][0]))
128                    row_labels = constant['labels'][1]
129
130                else:
131                    t.append_headers(constant['labels'][0])
132
133        if row_labels:
134            for label, line in zip(row_labels, obj):
135                t.append([label + ':'] + [makehtml(x, constant) for x in line])
136
137        else:
138            for line in obj:
139                t.append((makehtml(x, constant) for x in line))
140
141        return t.to_html_str()
142
143    # more dimensions
144    return '<br>'.join(makehtml(x, constant) for x in obj)
145
146
147def constants(request):
148    """Compile a table of all constants from `chart.alg.constants`."""
149
150    users = usage()
151    res = []
152    # background_colours = ('#EEEEEE', '#DDDDFF')
153    # background_index = [0]
154    # prev_users = [None]
155
156    # def background_colour(users):
157    #     """Attempt to show alternating background colours, with constants for each algorithm
158    #     shown in a different colour...
159    #     This does not produce a particularly good effect.
160    #     """
161
162    #     if users != prev_users[0]:
163    #         background_index[0] = (background_index[0] + 1) % len(background_colours)
164
165    #     prev_users[0] = users
166
167    #     return background_colours[background_index[0]]
168
169    constants_file = settings.CONSTANTS_FILE
170
171    if constants_file is None:
172        return render(request,
173                  'alg/constants.html',
174                      dict(constants=res,
175                           url=None))
176
177    all_constants = runpy.run_path(settings.CONSTANTS_FILE)['data']
178
179    for k, v in all_constants.items():
180        unit = v.get('unit', '')
181        description = v.get('description', '')
182
183        if 'value' in v:
184            res.append({'name': k,
185                        'description': description,
186                        'unit': unit,
187                        'validity': '',
188                        'value': makehtml(v['value'], v),
189                        'users': users[k]})
190
191        else:
192            for value in v['values']:
193                # for each possible value of the constant (each one specified with
194                # scid and/or start validity time) append a row to the HTML output
195                if 'sensing_start' in value:
196                    validity = '{scid} / {time}'.format(scid=value.get('scid', ''),
197                                                        time=show_time(value['sensing_start']))
198
199                else:
200                    validity = value.get('scid', '')
201
202                res.append({'name': k,
203                            'description': description,
204                            'unit': unit,
205                            'validity': validity,
206                            'value': makehtml(value['value'], v),
207                            'users': users[k]})
208
209    res.sort(key=operator.itemgetter('name', 'validity'))
210
211    for r in res:
212        r['background'] = '#eeeeee'
213        # r['background'] = background_colour(r['users'])
214
215    # make browseable link to constants module file. Should probably patch make_url
216    # to accept modules instead
217    # rel = str(settings.CONSTANTS_FILE).replace('.', '/') + '.py'
218    url = make_url(settings.CONSTANTS_FILE)
219    return render(request,
220                  'alg/constants.html',
221                  dict(constants=res,
222                       url=url))
223
224
225def main():
226    """Dump representation of a single constant."""
227    # print makehtml(data['MHS.central_frequency'][0]['value'])
228
229    print(usage())
230
231    def dump(elem):
232        """Display a single constant."""
233        return ', '.join('{name}={value}'.format(
234                name=name,
235                value=elem.__getattribute__(name)) for name in elem._fields)
236
237    def debug():  # (unused variable) pylint: disable=W0612
238        """Scan the AST of a module and print sections relating to a call
239        to `get_constant`."""
240
241        tree = ast.parse(open(os.path.join(settings.ALGORITHM_DIR, 'mhs_nedt.py'), 'r').read())
242        for elem in ast.walk(tree):
243            # if 'name' in elem._fields:
244                # print elem.name, dump(elem)
245            # if type(elem) == ast.Name:
246                # print elem.id
247            if isinstance(elem, ast.Call):
248                if isinstance(elem.func, ast.Attribute):
249                    if 'id' in elem.func.value._fields:
250                        print('CALL ATTRIBUTE ' + elem.func.value.id + ' . ' + elem.func.attr)
251                    else:
252                        if 'value' in elem.func.value._fields:
253                            if 'id' in elem.func.value.value._fields:
254                                print('CALL ATTRIBUTE ' + elem.func.value.value.id + ' [] ' +
255                                      elem.func.attr)
256                            else:
257                                print('UNKNOWN2')
258
259                        elif 's' in elem.func.value._fields:
260                            print('MSG ' + elem.func.value.s)
261                        else:
262                            print('UNKNOWN')
263
264                elif isinstance(elem.func, ast.Name):
265                    print('CALL NAME ' + elem.func.id)
266                    if elem.func.id == 'get_constant':
267                        print(elem.func, elem.args, elem.keywords, elem.starargs, elem.kwargs)
268                        for e in elem.args:
269                            print('GET_CONSTANT , ' + e.s)
270
271                        for e in elem.keywords:
272                            print('keyword ' + dump(e))
273                            if e.arg == 'name':
274                                print('GET_CONSTANT = ' + e.value.s)
275                        # print len(elem.args)
276                        # print len(elem.keywords)
277                    # print elem.func.ctx
278                    # print dump(elem.func.ctx)
279
280if __name__ == '__main__':
281    main()