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)
|