forked from ospalh/anki-addons
-
Notifications
You must be signed in to change notification settings - Fork 1
/
scalar.py
123 lines (100 loc) · 3.63 KB
/
scalar.py
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
# -*- mode: Python ; coding: utf-8 -*-
# Copyright © 2012–2013 Roland Sieker <ospalh@gmail.com>
# Based in part on code by Damien Elmes <anki@ichi2.net>
#
# License: GNU GPL, version 3 or later;
# http://www.gnu.org/copyleft/gpl.html
"""Add-on for Anki 2 to colour a typed in numeric answer."""
import re
from aqt.reviewer import Reviewer
from anki.cards import Card
from anki.hooks import wrap
__version__ = "3.0.0"
# Code word to look for in the field name to decide whether to do the
# number comparison:
scalar_field = 'scalar'
# Factor to decide what counts as ‘good enough’. It should be > 1.0
# (or at least >= 1.0). What you use here depends on how precisely you
# want to remember your numbers.
pass_factor = 1.5
# And the classes that are added.
fail_class = 'typeBad'
pass_class = 'typePass'
exact_class = 'typeGood'
scalar_css = u"""
<style scoped>
.{tp} {{background-color: #ff0; }}
</style>
""".format(tp=pass_class)
exact_format_string = u"""\
<div id=typeans class="typedscalar">
<span class="{cl} allGood">{num}</span>
</div>
"""
two_num_format_string = u"""\
<div id=typeans class="typedscalar corrected">
<span class="{cl} given">{g}</span>
<span class="arrow">→</span>
<span class="{ccl} correct">{c}</span>
</div>
"""
def scalar_card_css(self):
u"""Add the colors for this to the css."""
return scalar_css + old_css(self)
def correct_scalar(reviewer, given, correct, showBad=True, _old=None):
u"""
Return numeric answer with red, yellow or green background.
When certain conditions are met, return the typed-in answer with
CSS classes added that give them a red, yellow or green
background, depending how close the given answer was to the
correct one.
"""
try:
fld = re.search(r'\[\[type:([^\]]+)\]\]', reviewer.card.a()).group(1)
except AttributeError:
# No typed answer to show at all.
return _old(reviewer, given, correct, showBad)
if fld.startswith("cq:") or scalar_field not in fld.lower():
return _old(reviewer, given, correct, showBad)
try:
class_string = scalar_color_class(given, correct)
except ValueError:
return _old(reviewer, given, correct, showBad)
else:
if class_string == exact_class:
return exact_format_string.format(cl=class_string, num=given)
else:
return two_num_format_string.format(
cl=class_string, g=given, c=correct, ccl=exact_class)
def scalar_color_class(g, t):
"""
Return a color string and a class string.
Return a string that can be used to color an html text and a
string that can be used as a css class. The color is red, yellow
or green depending on how close the two numbers t and g are to
each other, the class describes this fact.
When t and g can't be converted to numbers, the ValueError of that
conversion is not caught.
"""
try:
target_value = int(t)
given_value = int(g)
except ValueError:
# No try here. Catch that case higher up.
target_value = float(t)
given_value = float(g)
# One of the two conversions worked: we have two valid numbers, two
# ints or two floats. We don’t really care which.
if target_value == given_value:
return exact_class
# Now we know that they are not the same, so either red or yellow.
try:
factor = 1.0 * given_value / target_value
except ZeroDivisionError:
return fail_class
if factor < 1.0 / pass_factor or factor > pass_factor:
return fail_class
return pass_class
Reviewer.correct = wrap(Reviewer.correct, correct_scalar, "around")
old_css = Card.css
Card.css = scalar_card_css