Defining a decoder¶
The goal of this guide is to bootstrap you into making your own color decoder using tools from thcolor. For clarity, you should first read Color expressions, especially the Decoders section.
For this guide, we will want to do the following:
Create our
TutorialColorDecoder
with extended hexadecimal color support.Create a
sum
function which can take 2 to 5 numbers and sum them.Create a
diff
function which takes 2 numbers and return the first one minus the second one.Create a
reverse_diff
which takes 2 numbers and return the second one minus the first one.Create an
transparent
function which returns the alpha value as a byte between 0 and 255, and returns the transparent color when used as a value.Create a
awesome-color
constant which evaluates as blue.
Setting up the framework¶
First of, the elements you will want to import are the following:
thcolor.decoders.MetaColorDecoder
, which will be the base class your decoder will inherit from.thcolor.decoders.alias
, which will be useful when you want to define an alias for an existing function without doing it manually.thcolor.decoders.fallback
, which will be useful when you want to define a fallback value for a given function.Colors we might want to use from
thcolor.colors
.Angles we might want to use from
thcolor.angles
.
For our exercise, we are going to use thcolor.colors.SRGBColor
and no angles.
Our base code is the following:
from thcolor.decoders import MetaColorDecoder, alias, fallback
from thcolor.colors import Color, SRGBColor
__all__ = ['TutorialColorDecoder']
class TutorialColorDecoder(MetaColorDecoder):
pass
As we want extended hexadecimal colors to be read as well, we need to define
the corresponding option. As described in
thcolor.decoders.ColorDecoder
, the option for doing this is
__extended_hex_support__
, which gives the following class definition:
class TutorialColorDecoder(MetaColorDecoder):
__extended_hex_support__ = True
Defining the sum
function¶
As described in the constraints, the sum
function will sum 2 to 5
numbers; the type of these numbers is not given, so we have three options:
If the hint on these numbers is
int
, only integers will be accepted by the function, and floats with decimal parts will throw an error.If the hint on these numbers is
float
, integers will be converted tofloat
before being passed on to the function.If the hint on these numbers is
typing.Union[int, float]
, the function will receive both depending on what the syntax was in the original expression.
Here, for simplicity, we choose to use float
. The resulting code is
the following:
class TutorialColorDecoder(MetaColorDecoder):
# ...
def sum(
a: float, b: float, c: float = 0,
d: float = 0, e: float = 0,
) -> float:
return a + b + c + d + e
Defining the diff
and reverse_diff
functions¶
As described in the constraints, the diff
function will compute the
difference between two numbers. Based on what we’ve learned in the previous
function, we can do the following:
class TutorialColorDecoder(MetaColorDecoder):
# ...
def diff(a: float, b: float) -> float:
return a - b
Now for the reverse_diff
function, we could define it independently,
but in order to save time, we will use thcolor.decoders.alias
.
This function takes the following arguments:
The name of the function to alias.
The new order of the arguments, by name.
Based on this information, the thcolor.decoders.MetaColorDecoder
will create the function out of the previous function (by calling it) and
take all related annotations and default values (if possible and available).
For our current case, we can define our function the following way:
class TutorialColorDecoder(MetaColorDecoder):
# ...
reverse_diff = alias('diff', args=('b', 'a'))
Note that aliases can be defined anywhere within the class, even before the aliased function.
Defining the transparent
function with a fallback value¶
For the transparent
function, we will get the alpha value, multiply it
by 255 and round it to the nearest integer. However, how do we add a fallback
value for the function? thcolor.decoders.fallback
comes to
our rescue!
Once our function is defined, we use thcolor.decoders.fallback
as a decorator by giving it the value the function should fall back on when
used as a constant.
The resulting code is the following:
class TutorialColorDecoder(MetaColorDecoder):
# ...
@fallback(SRGBColor(0, 0, 0, 0.0))
def transparent(color: Color) -> int:
return int(round(color.alpha * 255))
Defining the awesome-color
constant¶
For defining a constant, we can use affectations, as for aliases.
But how do we define names with carets when they are illegal in Python names?
We can replace it with _
; the color decoders in thcolor are not only
case-insensitive, they also treat carets and underscores the same.
We can thus do the following:
class TutorialColorDecoder(MetaColorDecoder):
# ...
awesome_color = SRGBColor.frombytes(0, 0, 255)
Testing the resulting class¶
Putting all of our efforts together, we should have the following code:
from thcolor.decoders import MetaColorDecoder, alias, fallback
from thcolor.colors import Color, SRGBColor
__all__ = ['TutorialColorDecoder']
class TutorialColorDecoder(MetaColorDecoder):
__extended_hex_support__ = True
def sum(
a: float, b: float, c: float = 0,
d: float = 0, e: float = 0,
) -> float:
return a + b + c + d + e
def diff(a: float, b: float) -> float:
return a - b
reverse_diff = alias('diff', args=('b', 'a'))
@fallback(SRGBColor(0, 0, 0, 0.0))
def transparent(color: Color) -> int:
return int(round(color.alpha * 255))
awesome_color = SRGBColor.frombytes(0, 0, 255)
Now that our class is defined, we can instanciate and test our decoder:
decoder = TutorialColorDecoder()
results = decoder.decode(
'awesome-color '
'sum(sum(1, 2, 3, 4), reverse_diff(transparent(transparent), 4))',
)
print(results)
And the result we obtain are the following:
(SRGBColor(red=0.0, green=0.0, blue=1.0, alpha=1.0), 14.0)