837 lines
28 KiB
Python
837 lines
28 KiB
Python
"""Commands are the building blocks of the filling out of the form.
|
|
|
|
When filling out the form the user can get things asked and some data
|
|
is automagically created. Both can be modelled by commands.
|
|
|
|
"""
|
|
|
|
import datetime
|
|
import formfield as ff
|
|
from constants import (SUBINFO_SEP, VARIABLE_BEGIN_SEP, VARIABLE_END_SEP,
|
|
VARIABLE_PARTS_SEP, VARIABLE_DEFAULT_SEP,
|
|
DATE_END_SEP, DATE_BEGIN_SEP,
|
|
ConfigError, ON, OFF)
|
|
|
|
|
|
def dateAfter(ref=datetime.date.today(), day=None, month=None):
|
|
"""Find first date after ref with the given infos.
|
|
|
|
Attributes:
|
|
ref (datetime.date):
|
|
dateAfter returns a date after (or equal to) ref.
|
|
day (int):
|
|
day in month of the return value.
|
|
None (default): use day of ref.
|
|
month (int):
|
|
month of the return value
|
|
None (default): use first possible month.
|
|
|
|
Returns:
|
|
First date after ref with day as day of month, month as month.
|
|
|
|
"""
|
|
if day is None:
|
|
day = ref.day
|
|
if month is None:
|
|
if day < ref.day:
|
|
if ref.month == 12:
|
|
return datetime.date(ref.year + 1, 1, day)
|
|
else:
|
|
return datetime.date(ref.year, ref.month + 1, day)
|
|
else:
|
|
return datetime.date(ref.year, ref.month, day)
|
|
return datetime.date(ref.year, ref.month, day)
|
|
else: # month is given
|
|
if month < ref.month:
|
|
return datetime.date(ref.year + 1, month, day)
|
|
else:
|
|
return datetime.date(ref.year, month, day)
|
|
|
|
|
|
class Command():
|
|
"""A command is one action used to create some data for a form.
|
|
|
|
Information about this command can be accessed via [..] notation.
|
|
This access the reason for the necessary information.
|
|
|
|
Attributes:
|
|
name (str):
|
|
the name of the command as used in the
|
|
info of reason (reason must have an info "<command>-name"
|
|
where <command> is the specific type of command used).
|
|
reason (FormField):
|
|
all information for a command is saved in a FormField.
|
|
This FormField is the reason for this Command.
|
|
Usually this command populates the reason with information.
|
|
fieldList ([FormField]):
|
|
list with all fields of the current form
|
|
data ({str: [FormField]}:
|
|
dictionary with lists of FormFields from
|
|
other forms that contain helpful data
|
|
|
|
"""
|
|
|
|
def __init__(self, name, reason, fieldList, data):
|
|
"""Create a command.
|
|
|
|
Attributes:
|
|
name (str): the name of the command as used in the
|
|
info of reason (reason must have an info "<command>: name"
|
|
where <command> is the specific type of command used).
|
|
reason, fieldList, data: see class-Docstring
|
|
|
|
# Raises:
|
|
# NotImplementedError: Command cannot be instantiated. Use a
|
|
# subclass.
|
|
"""
|
|
self.name = name
|
|
self.reason = reason
|
|
self.fieldList = fieldList
|
|
self.data = data
|
|
# raise NotImplementedError("Command cannot be instantiated.")
|
|
|
|
@classmethod
|
|
def extractCommands(cls, reason, fieldList, data):
|
|
"""Find all commands saved in reason.
|
|
|
|
The information used must have the key equal to the subclass.
|
|
|
|
Attributes:
|
|
reason: the field where the commands get extracted.
|
|
fieldList, data: see class-docstring
|
|
Returns:
|
|
a list of commands.
|
|
|
|
"""
|
|
commands = []
|
|
for key in reason.iterkeys():
|
|
for clss in cls.__subclasses__():
|
|
if key == clss.__name__:
|
|
commands.extend([clss(commandname, reason,
|
|
fieldList, data)
|
|
for commandname in reason[key]])
|
|
# reason[key] should be a list
|
|
# see MobileRegex in readformdata
|
|
return commands
|
|
|
|
def __getitem__(self, key):
|
|
"""Get some information.
|
|
|
|
self.reason must have this information in the form
|
|
self["name" + "-" + key].
|
|
|
|
"""
|
|
return self.reason[self.name + SUBINFO_SEP + key]
|
|
|
|
def __setitem__(self, key, value):
|
|
"""Set some information.
|
|
|
|
Similar as __getitem__.
|
|
|
|
"""
|
|
self.reason[self.name + SUBINFO_SEP + key] = value
|
|
|
|
def __contains__(self, key):
|
|
"""Give it makes sense to ask for this key.
|
|
|
|
Returns:
|
|
self.name + SUBINFO_SEP + key in self.reason
|
|
|
|
"""
|
|
return self.name + SUBINFO_SEP + key in self.reason
|
|
|
|
def __lt__(self, other):
|
|
"""Has self a lower priority than other.
|
|
|
|
Returns:
|
|
If only one has a priority, it is "less".
|
|
If both have no priority, True is returned.
|
|
|
|
"""
|
|
try:
|
|
myprio = float(self["Priority"])
|
|
except KeyError:
|
|
return False
|
|
except ValueError:
|
|
raise ConfigError("Priority in Command " + self.name +
|
|
" of field " + self.reason.name +
|
|
" is not a float.")
|
|
try:
|
|
otherprio = float(other["Priority"])
|
|
except KeyError:
|
|
return True
|
|
except ValueError:
|
|
raise ConfigError("Priority in Command " + other.name +
|
|
" of field " + other.reason.name +
|
|
" is not a float.")
|
|
return myprio < otherprio
|
|
|
|
def interpretVariables(self, value):
|
|
"""Interpret variables in values.
|
|
|
|
Replace occurences of {FieldName[#Default]} by the value saved in
|
|
field FieldName.
|
|
Replace occurences of {REF|FieldName[#Default]} by the value saved
|
|
in field FieldName in the data self.data[REF].
|
|
[] means that this part is optional.
|
|
|
|
If nothing is saved raise ConfigError except if a #Default is
|
|
given. Then use the default
|
|
|
|
Variables are replaced as long as there are any.
|
|
Hence if there are variables in the new strings they get replaced
|
|
as well. Hope no one uses this for recursion. ^^^
|
|
|
|
Attributes:
|
|
value (str): the string in which variables are replaced.
|
|
default (str): if some Field has no value saved, use default.
|
|
If default is None (default for this attribute),
|
|
raise a ConfigError.
|
|
|
|
Raises:
|
|
ConfigError:
|
|
if some FieldName does not exist and has no default
|
|
|
|
"""
|
|
while True:
|
|
start = value.find(VARIABLE_BEGIN_SEP)
|
|
end = value.find(VARIABLE_END_SEP, start)
|
|
if start == -1 or end == -1: # not found
|
|
return value
|
|
sep = value.find(VARIABLE_PARTS_SEP, start)
|
|
replaced = value[start:end + len(VARIABLE_END_SEP)]
|
|
if sep == -1: # {FieldName}
|
|
fieldList = self.fieldList
|
|
fieldname = value[start + len(VARIABLE_BEGIN_SEP):end]
|
|
else: # {spec|FieldName}
|
|
fieldList = self.data[value[
|
|
start + len(VARIABLE_BEGIN_SEP):sep]]
|
|
fieldname = value[sep + len(VARIABLE_PARTS_SEP):end]
|
|
fieldnameparts = fieldname.split(VARIABLE_DEFAULT_SEP, maxsplit=1)
|
|
if len(fieldnameparts) == 2:
|
|
# fieldname#default
|
|
fieldname = fieldnameparts[0]
|
|
default = fieldnameparts[1]
|
|
else: # len(fieldnameparts) == 1:
|
|
fieldname = fieldnameparts[0]
|
|
default = None
|
|
try:
|
|
field = ff.FormField.findByFieldName(fieldList, fieldname,
|
|
include=lambda x: True)
|
|
except KeyError as e:
|
|
raise ConfigError("Variable cannot be replaced due to " +
|
|
"missing FormField: " + fieldname
|
|
+ " (" + str(e) + ")")
|
|
else: # normal case: Field found
|
|
try:
|
|
value = value.replace(replaced, field["Value"])
|
|
except KeyError as e:
|
|
if default is None:
|
|
raise ConfigError("Variable cannot be replaced" +
|
|
" due to missing value: "
|
|
+ fieldname + " (" + str(e) + ")")
|
|
else:
|
|
value = value.replace(replaced, default)
|
|
|
|
def interpretDates(self, value):
|
|
"""Interpret dates in value.
|
|
|
|
Replace occurences of {TODAY|format} with the current day
|
|
in the format format.
|
|
format is described in
|
|
https://docs.python.org/3.5/library/datetime.html#strftime-strptime-behavior
|
|
|
|
"""
|
|
while True:
|
|
start = value.find(DATE_BEGIN_SEP)
|
|
end = value.find(DATE_END_SEP, start)
|
|
if start == -1 or end == -1:
|
|
return value
|
|
replaced = value[start: end + len(DATE_END_SEP)]
|
|
formatstr = value[start + len(DATE_BEGIN_SEP): end]
|
|
formatted = datetime.datetime.now().strftime(formatstr)
|
|
value = value.replace(replaced, formatted)
|
|
|
|
def interpret(self, value):
|
|
"""Replace variables and dates with their correct values.
|
|
|
|
See description of interpretDates and interpretVariables.
|
|
|
|
"""
|
|
value = self.interpretVariables(value)
|
|
value = self.interpretDates(value)
|
|
return value
|
|
|
|
def __call__(self): # todo: refactor, make smaller, externalise conditions
|
|
"""Run the command.
|
|
|
|
The subclasses must implement do() because __call__ calls do.
|
|
|
|
There can be conditions on doing actions. Conditions are and-connected.
|
|
That means that all must say yes.
|
|
1) even if exist:
|
|
Usually commands are not run if there is already a value.
|
|
If Ifexist exists as an info, the command is also run if there is
|
|
already a value.
|
|
2) if unequal:
|
|
Do not execute if two values are equal. Syntax:
|
|
name-Ifunequal: x
|
|
name-x-A: Blub
|
|
name-x-B: Bla
|
|
Usually Blub and/or Bla contain variables.
|
|
3) if equal:
|
|
Similarly to Ifunequal
|
|
|
|
"""
|
|
if "Value" in self.reason and "Ifexist" not in self:
|
|
# Do not do anything
|
|
return
|
|
for key, test in [("Ifunequal", lambda a, b: a == b),
|
|
("Ifequal", lambda a, b: a != b)]:
|
|
try:
|
|
for comp in self[key]:
|
|
# there can be sevaral tests of the same kind
|
|
# hence stored in a list self[key]
|
|
comps = {}
|
|
# the values to be compared are to be stored in a dict
|
|
# with the keys "A" and "B":
|
|
for comparevalue in ("A", "B"):
|
|
try:
|
|
# saved in infos of the kind y-a-A, y-a-B
|
|
comps[comparevalue] = self[comp + SUBINFO_SEP +
|
|
comparevalue]
|
|
except KeyError as e:
|
|
# values y-a-A are missing
|
|
raise ConfigError("A " + key +
|
|
" is missing a value " +
|
|
comparevalue + ".(" + str(e) +
|
|
" at " +
|
|
self.name + " at " +
|
|
self.reason.name + ")")
|
|
if test(self.interpret(comps["A"]),
|
|
self.interpret(comps["B"])):
|
|
# do not do anything
|
|
return
|
|
except KeyError:
|
|
# do nothing, apparently key does not exist
|
|
assert key not in self # there should be no other KeyError
|
|
# pass # necessary without assert statement
|
|
|
|
self.do()
|
|
|
|
def userinput(self, prompt=None):
|
|
"""Ask the user a question.
|
|
|
|
Use Prompt if existent or otherwise reason.name for informing the user.
|
|
|
|
Attributes:
|
|
prompt: Text shown to the user to ask him to type something.
|
|
Default (None)= Take "Prompt" info or name.
|
|
": " is added.
|
|
|
|
Returns:
|
|
If user presses Ctrl+D (EOFError), return None.
|
|
Otherwise return what user typed. (Can be "").
|
|
|
|
Raises:
|
|
KeyboardInterrupt:
|
|
if user presses Ctrl+C, wanting to cancel the program.
|
|
"""
|
|
if prompt is None:
|
|
try:
|
|
prompt = self["Prompt"]
|
|
except KeyError:
|
|
prompt = self.reason.name
|
|
else:
|
|
# take the argument
|
|
pass
|
|
prompt = self.interpret(prompt)
|
|
prompt += ": "
|
|
try:
|
|
return input(prompt)
|
|
except EOFError:
|
|
return None
|
|
|
|
def showHelp(self, extrainfo='(Ctrl+D = save nothing)'):
|
|
"""Show the help for this question.
|
|
|
|
Show "Help" or if not existing, the Description or if not existing
|
|
telling the user that.
|
|
|
|
Attributes:
|
|
extrainfo: Text that is shown additionally to the help.
|
|
|
|
"""
|
|
print("")
|
|
try:
|
|
message = self["Help"]
|
|
except KeyError:
|
|
# no help supplied, use field description
|
|
try:
|
|
message = self.reason["Description"]
|
|
except KeyError:
|
|
message = "No help available."
|
|
message = self.interpret(message)
|
|
print(message)
|
|
print(extrainfo)
|
|
|
|
|
|
class Question(Command):
|
|
"""The user gets asked a question.
|
|
|
|
No __init__ method since the Command.__init__ is sufficiant.
|
|
|
|
Uses infos:
|
|
Prompt: what the user sees while typing.
|
|
Help: what the user sees if asking for further information
|
|
|
|
"""
|
|
|
|
def do(self):
|
|
"""Ask the user a question and save the result in the field.
|
|
|
|
If the user writes '?', a help is shown and the user is asked again.
|
|
|
|
If the user only writes nothing (aka an empty string) nothing (not even
|
|
an empty string) is saved. This sounds like a plausible default.
|
|
|
|
If the user writes self.EMPTY, an empty string is saved.
|
|
|
|
Uses the name of self.reason if no other question was supplied.
|
|
|
|
"""
|
|
while True: # while no usable input
|
|
print("")
|
|
text = self.userinput()
|
|
if text == "?":
|
|
self.showHelp()
|
|
# ask again
|
|
else:
|
|
# None deletes what is there
|
|
self.reason["Value"] = text
|
|
break
|
|
|
|
|
|
class Choice(Command):
|
|
"""The user gets asked a question with a limited number of answer options.
|
|
|
|
One possibility is a on-off Button.
|
|
Another is a choice of several buttons from which one can be chosen.
|
|
|
|
|
|
Uses infos:
|
|
Prompt: what the user sees before typing.
|
|
Help: what the user sees if asking for further information
|
|
reason[FieldStateOption]: For the options that can be used.
|
|
Option-1/2/3/On/Off/...: for each option a description what it means.
|
|
Enter: if present tells what should be used if the user just presses
|
|
Enter.
|
|
|
|
"""
|
|
|
|
class SavedException(Exception):
|
|
"""Raise if a value is saved and asking the user should stop.
|
|
|
|
To be used in the check methods.
|
|
|
|
"""
|
|
|
|
class AgainException(Exception):
|
|
"""Raise if no value is saved and the user should be asked again.
|
|
|
|
To be used in the check methods.
|
|
|
|
"""
|
|
|
|
@property
|
|
def options(self):
|
|
"""Return the list of options of the field of this choice.
|
|
|
|
Raises:
|
|
ConfigError: if this info does not exist.
|
|
|
|
"""
|
|
try:
|
|
return self.reason["FieldStateOption"]
|
|
except KeyError:
|
|
raise ConfigError(
|
|
"A Choice question was asked but there are no options" +
|
|
" specified. Field: " + self.reason.name +
|
|
" Choice: " + self.name)
|
|
|
|
def getEnterDefault(self):
|
|
"""Get the default value.
|
|
|
|
The one that should be saved on pressing Enter without input.
|
|
|
|
Interprets variables.
|
|
|
|
Raises:
|
|
ConfigError: If it is not a valid option.
|
|
|
|
Returns:
|
|
None if no default was specified.
|
|
The default if it was specified.
|
|
|
|
"""
|
|
try:
|
|
default = self["Enter"]
|
|
except KeyError:
|
|
return None
|
|
default = self.interpret(default)
|
|
if default in self.options:
|
|
return default
|
|
else:
|
|
raise ConfigError(
|
|
"Default on Enter - value '" + default
|
|
+ "' not a possible option " +
|
|
"(" + self.name + " in " + self.reason.name + ")")
|
|
|
|
def sortOptions(self):
|
|
"""Sort the options in a reasonable way.
|
|
|
|
Change the order of the elements of self.reason["FieldStateOption"].
|
|
|
|
If default (self["Enter"]) exists, use this as first.
|
|
If Yes/On/Ja/ja exists, take this.
|
|
If natural numbers exist, take them in the natural order.
|
|
If No/Off/Nein/nein exists, take this.
|
|
Sort rest alphabetically.
|
|
|
|
"""
|
|
oldorder = self.options[:]
|
|
neworder = []
|
|
enter = self.getEnterDefault()
|
|
if enter is not None:
|
|
# check that it is indeed an option already included
|
|
neworder.append(enter)
|
|
oldorder.remove(enter)
|
|
for yes in ON:
|
|
if yes in oldorder:
|
|
neworder.append(yes)
|
|
oldorder.remove(yes)
|
|
numbers = sorted([number for number in oldorder if number.isdigit()])
|
|
neworder.extend(numbers)
|
|
for number in numbers:
|
|
oldorder.remove(number)
|
|
for no in OFF:
|
|
if no in oldorder:
|
|
neworder.append(no)
|
|
oldorder.remove(no)
|
|
neworder.extend(sorted(oldorder)) # add what remained
|
|
assert set(neworder) == set(self.options) # check that we have not
|
|
# lost anything
|
|
self.reason["FieldStateOption"] = neworder
|
|
|
|
def optionDescription(self, option):
|
|
"""Build descriptive option.
|
|
|
|
Return:
|
|
if option is default
|
|
option = description (Default on Enter)
|
|
or if not default:
|
|
option = description
|
|
or if no description is existing:
|
|
option (No help)
|
|
|
|
"""
|
|
default = ("; " if self.getEnterDefault() != option
|
|
else " (Default on Enter); ")
|
|
try:
|
|
return (option + " = " +
|
|
self["Option" + SUBINFO_SEP + option] +
|
|
default)
|
|
except KeyError:
|
|
return option + " (No help) " + default
|
|
|
|
def optionDescriptionInterpreted(self, option):
|
|
"""Build descriptive option.
|
|
|
|
Interpret variables.
|
|
|
|
Return:
|
|
if option is default
|
|
option = description (Default on Enter)
|
|
or if not default:
|
|
option = description
|
|
or if no description is existing:
|
|
option (No help)
|
|
|
|
"""
|
|
return self.interpret(self.optionDescription(option))
|
|
|
|
def showPrompt(self):
|
|
"""Show the prompt/ name specified and the options."""
|
|
print("")
|
|
try:
|
|
print(self["Prompt"], "Options: ")
|
|
# other version:, sep="\n", end="")
|
|
except KeyError:
|
|
# no question supplied, hence use field name
|
|
print(self.reason.name, "Options:", sep="\n")
|
|
# show Default as first option -> included in sorting
|
|
self.sortOptions()
|
|
for i in range(len(self.options)):
|
|
# for option in self.options:
|
|
print(i + 1, ") ",
|
|
self.optionDescriptionInterpreted(self.options[i]),
|
|
sep="")
|
|
# print(self.optionDescriptionInterpreted(option))
|
|
|
|
def usedefault(self):
|
|
"""Save the default value.
|
|
|
|
The default value is saved in "Enter".
|
|
|
|
Show help if no default is given.
|
|
|
|
Raises:
|
|
ConfigError: if the default is not a valid option.
|
|
AgainException: if a help was shown.
|
|
SavedException: if the default was shown.
|
|
|
|
"""
|
|
default = self.getEnterDefault()
|
|
if default is None:
|
|
self.showHelp()
|
|
raise self.AgainException()
|
|
else:
|
|
self.saveValue(default)
|
|
|
|
def saveValue(self, value):
|
|
"""Save value in self.reason["Value"].
|
|
|
|
Print message what was chosen.
|
|
|
|
Raises:
|
|
SavedException: always
|
|
|
|
"""
|
|
self.reason["Value"] = value
|
|
print("Chose", self.optionDescription(value))
|
|
raise self.SavedException()
|
|
|
|
def checkAbort(self, text):
|
|
"""Save nothing if the user gave no input (Ctrl + D).
|
|
|
|
Raises:
|
|
SavedException: if nothing is saved.
|
|
|
|
"""
|
|
if text is None:
|
|
# user aborts, nothing to be saved
|
|
self.reason["Value"] = text # = None = delete if existent
|
|
print("Chose nothing.")
|
|
raise self.SavedException()
|
|
|
|
def checkDefault(self, text):
|
|
"""Save value if the user wants to use the default.
|
|
|
|
Raises:
|
|
SavedException: if a value got saved, asking the user should stop.
|
|
AgainException: if the user should be asked again.
|
|
|
|
"""
|
|
if text == "":
|
|
self.usedefault()
|
|
# SavedException or AgainException or ConfigError raised
|
|
|
|
def checkHelp(self, text):
|
|
"""Show help if text is '?'.
|
|
|
|
Raises:
|
|
AgainException: if help is shown
|
|
|
|
"""
|
|
if text == "?":
|
|
self.showHelp()
|
|
raise self.AgainException()
|
|
|
|
def checkOptionNumber(self, text):
|
|
"""Save value which number is entered.
|
|
|
|
Raises:
|
|
SavedException: if input was a valid index.
|
|
|
|
"""
|
|
try:
|
|
number = int(text) - 1
|
|
# -1 because shown number start with 1, indeces with 0
|
|
if number < 0:
|
|
raise ValueError
|
|
except ValueError:
|
|
pass
|
|
# is not non-negative int, continue with next try
|
|
else:
|
|
try:
|
|
option = self.options[number]
|
|
except IndexError:
|
|
pass
|
|
# is not valid index, continue with next try
|
|
else:
|
|
self.saveValue(option)
|
|
|
|
def checkOptionDescription(self, text):
|
|
"""Save value if user input matches beginning of option description.
|
|
|
|
Ignore case.
|
|
|
|
Raises:
|
|
SavedException: if a value is saved.
|
|
AgainException: if two options are possible.
|
|
|
|
"""
|
|
choice = None
|
|
for option in self.options:
|
|
if ("Option" + SUBINFO_SEP + option in self
|
|
# option help exists
|
|
and self["Option" + SUBINFO_SEP + option].lower(
|
|
).startswith(text.lower())):
|
|
if choice is None:
|
|
choice = option
|
|
else:
|
|
# found second match
|
|
print("Choice not well-defined.")
|
|
raise self.AgainException()
|
|
if choice is not None:
|
|
self.saveValue(choice)
|
|
|
|
def notValidInput(self, text):
|
|
"""Assume that text is not possible to understand.
|
|
|
|
Print explaining message.
|
|
|
|
Raise:
|
|
AgainException
|
|
|
|
"""
|
|
print(text, "could not be understood as a valid choice.")
|
|
self.showHelp()
|
|
raise self.AgainException()
|
|
|
|
def do(self):
|
|
"""Ask the user a question and save the result in the field.
|
|
|
|
Tell the user of the different options.
|
|
|
|
If the user writes '?', a help is shown and the user is asked again.
|
|
|
|
The answer is interpreted in the following manner:
|
|
if the answer is empty:
|
|
if "Enter" is present, take this value
|
|
otherwise show help and reask
|
|
if the answer is ?, show help and reask
|
|
if the answer is number and a valid index of the option list take it
|
|
if one of the descriptions start with the answer, (ignore case):
|
|
take this one.
|
|
if none of the above, reask
|
|
if the user aborts with Ctrl+D (EOFError), do not save anything
|
|
|
|
Uses the name of self.reason if no other question was supplied.
|
|
Uses the description of self.reason if no other help was supplied.
|
|
|
|
"""
|
|
while True: # while no usable input
|
|
self.showPrompt()
|
|
text = self.userinput(prompt="Choice")
|
|
try:
|
|
self.checkAbort(text)
|
|
except self.SavedException:
|
|
break
|
|
text = text.strip()
|
|
try:
|
|
for check in [self.checkDefault,
|
|
self.checkHelp,
|
|
self.checkOptionNumber,
|
|
self.checkOptionDescription,
|
|
self.notValidInput]:
|
|
check(text)
|
|
# Exceptions stop the inner for loop
|
|
except self.SavedException:
|
|
break
|
|
except self.AgainException:
|
|
continue
|
|
|
|
|
|
class Default(Command):
|
|
"""A default value is saved.
|
|
|
|
No __init__ method since the Command.__init__ is sufficiant.
|
|
|
|
Uses infos:
|
|
Value: what should be saved. Can include variables.
|
|
Addhours, Addmin: see addTime(time)
|
|
|
|
"""
|
|
|
|
def do(self):
|
|
"""Save the default value.
|
|
|
|
Understand variables.
|
|
|
|
If it boils down to an empty string, save nothing (and do not
|
|
overwrite any existing value).
|
|
TODO: Check if this behavior is useful.
|
|
|
|
Raises:
|
|
ConfigError: if understanding variables failed.
|
|
|
|
"""
|
|
if "Value" not in self:
|
|
raise ConfigError("A Default needs a default Value. " +
|
|
"Add a line " + self.name + "-Value: " +
|
|
"<your Default value> to the field " +
|
|
self.reason.name +
|
|
" (" + self.name + " in " +
|
|
self.reason.name + ")")
|
|
value = self.interpret(self["Value"])
|
|
if value != "" and value is not None:
|
|
# is not None should not happen but who knows
|
|
self.reason["Value"] = self.addTime(value)
|
|
|
|
def addTime(self, time):
|
|
"""Add a specified timeshift to time.
|
|
|
|
If there is a timeshift specified, add this to time.
|
|
|
|
Attributes:
|
|
time (str):
|
|
If time is of the form hh:mm, all right.
|
|
Otherwise print error message if a time shift is specified.
|
|
|
|
Uses infos:
|
|
Addhours:
|
|
must be integer, can be negative. Add this number of hours.
|
|
Addmin:
|
|
must be integer, can be negative. Add this number of minutes.
|
|
|
|
Returns:
|
|
if time is of the form hh:mm, return (hh+Addhours):(mm+Addmin),
|
|
otherwise return time.
|
|
"""
|
|
try:
|
|
split = time.split(":", maxsplit=1)
|
|
hours = int(split[0])
|
|
minutes = int(split[1])
|
|
except (IndexError, ValueError):
|
|
if "Addhours" in self or "Addmin" in self:
|
|
print("Adding a timeshift for field " +
|
|
self.reason.name + " to time '" + time + "' failed" +
|
|
" because the original time is not of the form hh:mm.")
|
|
return time
|
|
else:
|
|
try:
|
|
hours += int(self["Addhours"])
|
|
except ValueError:
|
|
raise ConfigError("Option Addhours of field " +
|
|
self.reason.name + " is not an integer.")
|
|
except KeyError:
|
|
pass # do not change hours apparently
|
|
try:
|
|
minutes += int(self["Addmin"])
|
|
except ValueError:
|
|
raise ConfigError("Option Addmin of field " +
|
|
self.reason.name + " is not an integer.")
|
|
except KeyError:
|
|
pass # do not change minutes apparently
|
|
return str(hours).zfill(2) + ":" + str(minutes).zfill(2)
|