File: primitives.py

package info (click to toggle)
lektor 3.3.12-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,856 kB
  • sloc: python: 14,376; javascript: 77; makefile: 37; sh: 7; xml: 1
file content (177 lines) | stat: -rw-r--r-- 5,411 bytes parent folder | download
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import re
from datetime import date
from datetime import datetime

from babel.dates import get_timezone
from markupsafe import Markup

from lektor.constants import PRIMARY_ALT
from lektor.i18n import get_i18n_block
from lektor.types.base import Type
from lektor.utils import bool_from_string


class SingleInputType(Type):
    widget = "singleline-text"

    def to_json(self, pad, record=None, alt=PRIMARY_ALT):
        rv = Type.to_json(self, pad, record, alt)
        rv["addon_label_i18n"] = get_i18n_block(self.options, "addon_label") or None
        return rv


class StringType(SingleInputType):
    def value_from_raw(self, raw):
        if raw.value is None:
            return raw.missing_value("Missing string")
        try:
            return raw.value.splitlines()[0].strip()
        except IndexError:
            return ""


class StringsType(Type):
    widget = "multiline-text"

    def value_from_raw(self, raw):
        return [x.strip() for x in (raw.value or "").splitlines()]


class TextType(Type):
    widget = "multiline-text"

    def value_from_raw(self, raw):
        if raw.value is None:
            return raw.missing_value("Missing text")
        return raw.value


class HtmlType(Type):
    widget = "multiline-text"

    def value_from_raw(self, raw):
        if raw.value is None:
            return raw.missing_value("Missing HTML")
        return Markup(raw.value)


class IntegerType(SingleInputType):
    widget = "integer"

    def value_from_raw(self, raw):
        if raw.value is None:
            return raw.missing_value("Missing integer value")
        try:
            return int(raw.value.strip())
        except ValueError:
            try:
                return int(float(raw.value.strip()))
            except ValueError:
                return raw.bad_value("Not an integer")


class FloatType(SingleInputType):
    widget = "float"

    def value_from_raw(self, raw):
        if raw.value is None:
            return raw.missing_value("Missing float value")
        try:
            return float(raw.value.strip())
        except ValueError:
            return raw.bad_value("Not an integer")


class BooleanType(Type):
    widget = "checkbox"

    def to_json(self, pad, record=None, alt=PRIMARY_ALT):
        rv = Type.to_json(self, pad, record, alt)
        rv["checkbox_label_i18n"] = get_i18n_block(self.options, "checkbox_label")
        return rv

    def value_from_raw(self, raw):
        if raw.value is None:
            return raw.missing_value("Missing boolean")
        val = bool_from_string(raw.value.strip().lower())
        if val is None:
            return raw.bad_value("Bad boolean value")
        return val


class DateType(SingleInputType):
    widget = "datepicker"

    def value_from_raw(self, raw):
        if raw.value is None:
            return raw.missing_value("Missing date")
        try:
            return date(*map(int, raw.value.split("-")))
        except Exception:
            return raw.bad_value("Bad date format")


class DateTimeType(SingleInputType):
    widget = "singleline-text"

    def value_from_raw(self, raw):
        if raw.value is None:
            return raw.missing_value("Missing datetime")

        # The previous version of this code allowed a timezone name, followed by a zone
        # offset. In that case the zone name would be ignored (unless the combined zone
        # name, including the offset, matched an IANA zone key). For the sake of
        # backwards compatibility we do the same here.
        m = re.match(
            r"""
            (?P<datetime>
                \d{4} - \d\d? - \d\d?  # YY-MM-DD
                \s+ \d\d? : \d\d (?P<seconds> :\d\d )? # HH:MM[:SS]
            )
            (?: \s+
                (?P<timezone>
                    # Long timezone keys, and — on Windows — names containing
                    # certain characters (those that are not allowed in filenames)
                    # give zoneinfo gas.
                    # https://github.com/python/cpython/issues/96463
                    [^<>:"|?*\x00-\x1f]{,100}?
                    (?P<zoneoffset> [-+] \d\d (?: :? \d\d ){1,2} )?  # ±HHMM[SS]
                )
            )?
            \Z""",
            raw.value.strip(),
            re.DOTALL | re.VERBOSE,
        )
        if m is None:
            return raw.bad_value("Bad datetime format")
        timezone, zoneoffset = m.group("timezone", "zoneoffset")
        tz = None
        if timezone is not None:
            try:
                tz = get_timezone(timezone)
                zoneoffset = None
            except LookupError:
                if zoneoffset is None:
                    return raw.bad_value(f"Unknown timezone {timezone!r}")

        dt = m["datetime"]
        fmt = "%Y-%m-%d %H:%M"
        if m["seconds"] is not None:
            fmt += ":%S"
        if zoneoffset is not None:
            dt += f" {zoneoffset}"
            fmt += " %z"
        try:
            result = datetime.strptime(dt, fmt)
        except ValueError:
            return raw.bad_value("Invalid datetime")

        if tz is None:
            return result

        # as of babel 2.12, get_timezone can return either a pytz timezone
        # or a zoneinfo timezone
        assert result.tzinfo is None
        if hasattr(tz, "localize"):  # pytz
            return tz.localize(result)
        return result.replace(tzinfo=tz)