Skip to content

Commit

Permalink
Add option transformation handler
Browse files Browse the repository at this point in the history
  • Loading branch information
salkinium committed Jul 10, 2021
1 parent b8dd3c7 commit 843ad2b
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 25 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ def add_option_dependencies(value):
This is the most generic option, allowing to input any string.
You may, however, provide your own validator that may raise a `ValueError`
if the input string does not match your expectations.
You may also pass a transformation function to convert the option value.
The string is passed unmodified from the configuration to the module and the
dependency handler.

Expand All @@ -719,10 +720,14 @@ def validate_string(string):
if "please" not in string:
raise ValueError("Input does not contain the magic word!")

def transform_string(string):
return string.lower()

option = StringOption(name="option-name",
description="inline", # or FileReader("file.md")
default="default string",
validate=validate_string,
transform=transform_string,
dependencies=add_option_dependencies)
```

Expand Down Expand Up @@ -759,13 +764,20 @@ option = PathOption(name="option-name",

#### BooleanOption

This option maps strings from `true`, `yes`, `1` to `bool(True)` and `false`,
`no`, `0` to `bool(False)`. The dependency handler is passed this `bool` value.
This option maps strings from `true`, `yes`, `1`, `enable` to `bool(True)` and
`false`, `no`, `0`, `disable` to `bool(False)`. You can extend this list with a
custom transform handler. The dependency handler is passed this `bool` value.

```python
def transform_boolean(string):
if string == 'y': return True;
if string == 'n': return False;
return string # hand over to built-in conversion

option = BooleanOption(name="option-name",
description="boolean",
default=True,
transform=transform_boolean,
dependencies=add_option_dependencies)
```

Expand Down
2 changes: 1 addition & 1 deletion lbuild/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from lbuild.api import Builder

__version__ = '1.17.0'
__version__ = '1.18.0'


class InitAction:
Expand Down
52 changes: 33 additions & 19 deletions lbuild/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@

class Option(BaseNode):

def __init__(self, name, description, default=None, dependencies=None, validate=None,
def __init__(self, name, description, default=None,
dependencies=None, validate=None, transform=None,
convert_input=None, convert_output=None):
BaseNode.__init__(self, name, BaseNode.Type.OPTION)
self._dependency_handler = dependencies
Expand All @@ -36,6 +37,7 @@ def __init__(self, name, description, default=None, dependencies=None, validate=
self._output = None
self._default = None
self._validate = validate
self._transform = transform
self._filename = os.path.join(os.getcwd(), "dummy")
self._set_default(default)

Expand Down Expand Up @@ -93,9 +95,10 @@ def format_values(self):

class StringOption(Option):

def __init__(self, name, description, default=None, dependencies=None, validate=None):
Option.__init__(self, name, description, None, dependencies, validate,
convert_input=self._validate_string)
def __init__(self, name, description, default=None, dependencies=None, validate=None, transform=None):
Option.__init__(self, name, description, None, dependencies, validate, transform,
convert_input=self._validate_string,
convert_output=self._transform_string)
self._set_default(default)

def _validate_string(self, value):
Expand All @@ -104,6 +107,12 @@ def _validate_string(self, value):
self._validate(value)
return value

def _transform_string(self, value):
value = str(value)
if self._transform is not None:
value = self._transform(value)
return value


class PathOption(Option):

Expand Down Expand Up @@ -163,29 +172,36 @@ def format_values(self):

class BooleanOption(Option):

def __init__(self, name, description, default=False, dependencies=None):
Option.__init__(self, name, description, default, dependencies,
convert_input=self.as_boolean,
convert_output=self.as_boolean)
def __init__(self, name, description, default=False, dependencies=None, transform=None):
self._transform = transform
Option.__init__(self, name, description, default, dependencies, transform=transform,
convert_input=self.input_boolean,
convert_output=self.output_boolean)

@property
def values(self):
return ["True", "False"]
return ["yes", "no"]

def format_values(self):
if self._default:
return _cw("True").wrap("underlined") + _cw(", False")
return _cw("True, ") + _cw("False").wrap("underlined")
if self.output_boolean(self._default):
return _cw("yes").wrap("underlined") + _cw(", no")
return _cw("yes, ") + _cw("no").wrap("underlined")

@staticmethod
def as_boolean(value):
def input_boolean(self, value):
if isinstance(value, bool):
value = "yes" if value else "no"
return str(value).lower()

def output_boolean(self, value):
if value is None:
return value
if self._transform is not None:
value = self._transform(value)
if isinstance(value, bool):
return value
if str(value).lower() in ['true', 'yes', '1']:
if str(value).strip().lower() in ['true', 'yes', '1', 'enable', 'enabled']:
return True
if str(value).lower() in ['false', 'no', '0']:
if str(value).strip().lower() in ['false', 'no', '0', 'disable', 'disabled']:
return False

raise TypeError("Input must be boolean!")
Expand All @@ -196,7 +212,6 @@ class NumericOption(Option):
def __init__(self, name, description, minimum=None, maximum=None,
default=None, dependencies=None, validate=None):
Option.__init__(self, name, description, default, dependencies, validate,
convert_input=str,
convert_output=self.as_numeric_value)
self.minimum_input = str(minimum)
self.maximum_input = str(maximum)
Expand Down Expand Up @@ -263,8 +278,7 @@ def format_values(self):

return default

@staticmethod
def as_numeric_value(value):
def as_numeric_value(self, value):
if value is None:
return value
if isinstance(value, (int, float)):
Expand Down
30 changes: 27 additions & 3 deletions test/option_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ def test_should_be_constructable_from_string(self):
option.value = False
self.assertEqual("False", option.value)

option = StringOption("test", "description", default="hello",
transform=lambda v: v.lower())
option.value = "HELLO"
self.assertEqual("hello", option.value)
option.value = False
self.assertEqual("false", option.value)

def validate_string(value):
if not isinstance(value, str):
raise TypeError("must be of type str")
Expand Down Expand Up @@ -178,7 +185,8 @@ def validate_path(path):
self.assertEqual("/absolute/filename.txt", option.value)

def test_should_be_constructable_from_boolean(self):
option = BooleanOption("test", "description", False)
option = BooleanOption("test", "description", False,
transform=lambda v: True if v == "world" else v)
self.assertIn("test [BooleanOption]", option.description)
self.assertEqual(False, option.value)
option.value = 1
Expand All @@ -191,6 +199,8 @@ def test_should_be_constructable_from_boolean(self):
self.assertEqual(True, option.value)
option.value = False
self.assertEqual(False, option.value)
option.value = "world"
self.assertEqual(True, option.value)

with self.assertRaises(le.LbuildOptionInputException):
option.value = "hello"
Expand Down Expand Up @@ -378,10 +388,24 @@ def test_should_be_constructable_from_set_set(self):
str(lbuild.format.format_option_value_description(option)))

def test_should_format_boolean_option(self):
option = BooleanOption("test", "description", default=True)
option = BooleanOption("test", "description", default=True,
transform=lambda v: True if v == "hello" else v)

output = str(lbuild.format.format_option_value_description(option))
self.assertIn("yes in [yes, no]", output, "Output")

option.value = "hello"
self.assertEqual(True, option.value)
output = str(lbuild.format.format_option_value_description(option))
self.assertIn("hello in [yes, no]", output, "Output")

option.value = "1"
output = str(lbuild.format.format_option_value_description(option))
self.assertIn("1 in [yes, no]", output, "Output")

option.value = "TRUE"
output = str(lbuild.format.format_option_value_description(option))
self.assertIn("True in [True, False]", output, "Output")
self.assertIn("true in [yes, no]", output, "Output")

def test_should_format_numeric_option(self):

Expand Down

0 comments on commit 843ad2b

Please sign in to comment.