source: pyromaths/trunk/fuentes/src/pyromaths/ex/__init__.py @ 1666

Last change on this file since 1666 was 1666, checked in by jrpelegrina, 3 years ago

Fix script to tooltip ex

File size: 6.1 KB
Line 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4import inspect
5import os
6import pkgutil
7import types
8import sys
9import locale
10
11class Exercise(object):
12    ''' Base class for all exercise types. '''
13
14    def __str__(self):
15        return self.description
16
17
18class TexExercise(Exercise):
19    ''' Exercise with TeX support. '''
20
21    @classmethod
22    def name(cls):
23        return cls.__name__
24
25    @classmethod
26    def thumb(cls):
27        from pyromaths.Values import data_dir
28        langue = locale.getdefaultlocale()[0][0:2]
29
30        pathimgs = os.path.join(data_dir(), 'ex', cls.dirlevel, 'img')
31        if os.path.join(pathimgs,langue):
32            folder = os.path.join(pathimgs,langue)
33        else:
34            folder = os.path.join(pathimgs,'default')
35
36        return os.path.join(folder, '%s.png' % (cls.name())) 
37
38    @classmethod
39    def id(cls):
40        return "{}.{}".format(
41            cls.dirlevel,
42            cls.name(),
43            )
44
45    def tex_statement(self):
46        ''' Return problem statement in TeX format. '''
47        raise NotImplementedError()
48        return ["\\exercice TODO"]
49
50    def tex_answer(self):
51        ''' Return full answer in TeX format. '''
52        return ["\\exercice* TODO"]
53
54
55class LegacyExercise(TexExercise):
56    ''' Base class for legacy format exercise proxies. '''
57
58    function = []
59
60    def __init__(self):
61        self.stat, self.ans = self.function[0]()
62
63    @classmethod
64    def name(cls):
65        return cls.function[0].__name__
66
67    def tex_statement(self):
68        return self.stat
69
70    def tex_answer(self):
71        return self.ans
72
73def __module(filename):
74    """Expect an absolute path, subpath of this module's path. Return a relative path."""
75    # Get root of this application
76    root = '/'.join(__file__.split('/')[:-len(__name__.split('.')) - 1])
77    # Get filename, relative to said root
78    relative = filename[len(root)+1:]
79    # Remove extension
80    relative = relative[:-len('.py')]
81    # Turn file system path into python package
82    relative = relative.replace('/', '.')
83
84    return relative
85
86def __legacy(function, dirlevel):
87    ''' Create a new class proxying for a legacy exercise 'function'. '''
88    # Create a proxy class inheriting from LegacyExercise for this function
89    module = __module(function.func_code.co_filename)
90    name = function.func_name.title().replace('_', '')
91    return type("{}.{}".format(module, name),
92                (LegacyExercise,),
93                dict(description=function.description,
94                     level=function.level,
95                     module=module,
96                     function=(function,),
97                     dirlevel=dirlevel,
98                     )
99                )
100
101def __hasdescription(obj):
102    ''' Has 'obj' a legit description? '''
103    if 'description' not in dir(obj): return False
104    description = obj.__dict__['description']
105    # description must be some kind of string (preferably unicode)
106    if not isinstance(description, basestring): return False
107    return True
108
109def __islegacy(obj):
110    ''' Is target object an exercise in legacy format? '''
111    return inspect.isfunction(obj) and __hasdescription(obj)
112
113def __isexercise(obj):
114    ''' Is target object an exercise (in new format)? '''
115    return inspect.isclass(obj) and issubclass(obj, Exercise) and __hasdescription(obj)
116
117def __level(level):
118    ''' Format academic level(s). '''
119    # level may be a string or a list (default)
120    if not isinstance(level, list): level = [level]
121    level.sort()
122    return level
123
124def __import(name=__name__, parent=None):
125    ''' Import 'name' from 'parent' package. '''
126    if not isinstance(name, basestring):
127        name = name.__name__
128    # parent is None: assume 'name' is a package name
129    # hack tout moche pour l'import des exercices dans la version Windows de Pyromaths :
130    # Les modules sixiemes, quatriemes doivent être appelés avec le chemin complet,
131    # alors que les exercices cinquiemes.aires ne doivent être appelés qu'ainsi.
132    if "." not in name and hasattr(sys, "frozen"): name = "pyromaths.ex." + name
133    if parent is None: parent = name
134    elif not isinstance(parent, basestring):
135        # assume 'parent' is a package instance
136        parent = parent.__name__
137    return __import__(name, fromlist=parent)
138
139def _exercises(pkg):
140    ''' List exercises in 'pkg' modules. '''
141    # level defaults to description, then unknown
142    if 'level' not in dir(pkg): pkg.level = u"Inconnu"
143    for _, name, ispkg in pkgutil.iter_modules(pkg.__path__, pkg.__name__ + '.'):
144        # skip packages
145        if ispkg: continue;
146        # import module
147        mod = __import(name, pkg)
148        if 'level' not in dir(mod): mod.level = pkg.level
149        # search exercises in module
150        for element in dir(mod):
151            element = mod.__dict__[element]
152            level = __level(element.level if 'level' in dir(element)
153                              else mod.level)
154
155            if __isexercise(element) or __islegacy(element):
156                dirlevel = os.path.split(pkg.__path__[0])[1]
157                element.level = level
158            if __isexercise(element):
159                element.dirlevel = dirlevel
160                yield element
161            elif __islegacy(element):
162                yield __legacy(element, dirlevel)
163
164def _subpackages(pkg):
165    ''' List 'pkg' sub-packages. '''
166    for _, name, ispkg in pkgutil.iter_modules(pkg.__path__, pkg.__name__ + '.'):
167        # skip modules
168        if not ispkg: continue;
169        yield __import(name, pkg)
170
171def load_levels(pkg=None, recursive=True):
172    ''' Discover exercises. '''
173    levels = {}
174    # target package defaults to this package (pyromaths.ex)
175    if pkg is None: pkg = __import()
176    # load package exercises
177    for ex in _exercises(pkg):
178        for lvl in ex.level:
179            # new level? create its exercise list
180            if lvl not in levels.keys():
181                levels[lvl] = []
182            levels[lvl].append(ex)
183
184    if recursive:
185        # load sub-packages
186        for pk in _subpackages(pkg):
187            sublevels = load_levels(pk)
188            for lvl in sublevels:
189                if lvl in levels:
190                    levels[lvl].extend(sublevels[lvl])
191                else:
192                    levels[lvl] = sublevels[lvl]
193
194    return levels
Note: See TracBrowser for help on using the repository browser.