Skip to content

Commit 5a8d54f

Browse files
committed
Add option transformation handler
1 parent eb704a0 commit 5a8d54f

4 files changed

Lines changed: 75 additions & 25 deletions

File tree

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,7 @@ def add_option_dependencies(value):
711711
This is the most generic option, allowing to input any string.
712712
You may, however, provide your own validator that may raise a `ValueError`
713713
if the input string does not match your expectations.
714+
You may also pass a transformation function to convert the option value.
714715
The string is passed unmodified from the configuration to the module and the
715716
dependency handler.
716717

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

723+
def transform_string(string):
724+
return string.lower()
725+
722726
option = StringOption(name="option-name",
723727
description="inline", # or FileReader("file.md")
724728
default="default string",
725729
validate=validate_string,
730+
transform=transform_string,
726731
dependencies=add_option_dependencies)
727732
```
728733

@@ -759,13 +764,20 @@ option = PathOption(name="option-name",
759764

760765
#### BooleanOption
761766

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

765771
```python
772+
def transform_boolean(string):
773+
if string == 'y': return True;
774+
if string == 'n': return False;
775+
return string # hand over to built-in conversion
776+
766777
option = BooleanOption(name="option-name",
767778
description="boolean",
768779
default=True,
780+
transform=transform_boolean,
769781
dependencies=add_option_dependencies)
770782
```
771783

lbuild/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from lbuild.api import Builder
2424

25-
__version__ = '1.17.0'
25+
__version__ = '1.18.0'
2626

2727

2828
class InitAction:

lbuild/option.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525

2626
class Option(BaseNode):
2727

28-
def __init__(self, name, description, default=None, dependencies=None, validate=None,
28+
def __init__(self, name, description, default=None,
29+
dependencies=None, validate=None, transform=None,
2930
convert_input=None, convert_output=None):
3031
BaseNode.__init__(self, name, BaseNode.Type.OPTION)
3132
self._dependency_handler = dependencies
@@ -36,6 +37,7 @@ def __init__(self, name, description, default=None, dependencies=None, validate=
3637
self._output = None
3738
self._default = None
3839
self._validate = validate
40+
self._transform = transform
3941
self._filename = os.path.join(os.getcwd(), "dummy")
4042
self._set_default(default)
4143

@@ -93,9 +95,10 @@ def format_values(self):
9395

9496
class StringOption(Option):
9597

96-
def __init__(self, name, description, default=None, dependencies=None, validate=None):
97-
Option.__init__(self, name, description, None, dependencies, validate,
98-
convert_input=self._validate_string)
98+
def __init__(self, name, description, default=None, dependencies=None, validate=None, transform=None):
99+
Option.__init__(self, name, description, None, dependencies, validate, transform,
100+
convert_input=self._validate_string,
101+
convert_output=self._transform_string)
99102
self._set_default(default)
100103

101104
def _validate_string(self, value):
@@ -104,6 +107,12 @@ def _validate_string(self, value):
104107
self._validate(value)
105108
return value
106109

110+
def _transform_string(self, value):
111+
value = str(value)
112+
if self._transform is not None:
113+
value = self._transform(value)
114+
return value
115+
107116

108117
class PathOption(Option):
109118

@@ -163,29 +172,36 @@ def format_values(self):
163172

164173
class BooleanOption(Option):
165174

166-
def __init__(self, name, description, default=False, dependencies=None):
167-
Option.__init__(self, name, description, default, dependencies,
168-
convert_input=self.as_boolean,
169-
convert_output=self.as_boolean)
175+
def __init__(self, name, description, default=False, dependencies=None, transform=None):
176+
self._transform = transform
177+
Option.__init__(self, name, description, default, dependencies, transform=transform,
178+
convert_input=self.input_boolean,
179+
convert_output=self.output_boolean)
170180

171181
@property
172182
def values(self):
173-
return ["True", "False"]
183+
return ["yes", "no"]
174184

175185
def format_values(self):
176-
if self._default:
177-
return _cw("True").wrap("underlined") + _cw(", False")
178-
return _cw("True, ") + _cw("False").wrap("underlined")
186+
if self.output_boolean(self._default):
187+
return _cw("yes").wrap("underlined") + _cw(", no")
188+
return _cw("yes, ") + _cw("no").wrap("underlined")
179189

180-
@staticmethod
181-
def as_boolean(value):
190+
def input_boolean(self, value):
191+
if isinstance(value, bool):
192+
value = "yes" if value else "no"
193+
return str(value).lower()
194+
195+
def output_boolean(self, value):
182196
if value is None:
183197
return value
198+
if self._transform is not None:
199+
value = self._transform(value)
184200
if isinstance(value, bool):
185201
return value
186-
if str(value).lower() in ['true', 'yes', '1']:
202+
if str(value).strip().lower() in ['true', 'yes', '1', 'enable', 'enabled']:
187203
return True
188-
if str(value).lower() in ['false', 'no', '0']:
204+
if str(value).strip().lower() in ['false', 'no', '0', 'disable', 'disabled']:
189205
return False
190206

191207
raise TypeError("Input must be boolean!")
@@ -196,7 +212,6 @@ class NumericOption(Option):
196212
def __init__(self, name, description, minimum=None, maximum=None,
197213
default=None, dependencies=None, validate=None):
198214
Option.__init__(self, name, description, default, dependencies, validate,
199-
convert_input=str,
200215
convert_output=self.as_numeric_value)
201216
self.minimum_input = str(minimum)
202217
self.maximum_input = str(maximum)
@@ -263,8 +278,7 @@ def format_values(self):
263278

264279
return default
265280

266-
@staticmethod
267-
def as_numeric_value(value):
281+
def as_numeric_value(self, value):
268282
if value is None:
269283
return value
270284
if isinstance(value, (int, float)):

test/option_test.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ def test_should_be_constructable_from_string(self):
9898
option.value = False
9999
self.assertEqual("False", option.value)
100100

101+
option = StringOption("test", "description", default="hello",
102+
transform=lambda v: v.lower())
103+
option.value = "HELLO"
104+
self.assertEqual("hello", option.value)
105+
option.value = False
106+
self.assertEqual("false", option.value)
107+
101108
def validate_string(value):
102109
if not isinstance(value, str):
103110
raise TypeError("must be of type str")
@@ -178,7 +185,8 @@ def validate_path(path):
178185
self.assertEqual("/absolute/filename.txt", option.value)
179186

180187
def test_should_be_constructable_from_boolean(self):
181-
option = BooleanOption("test", "description", False)
188+
option = BooleanOption("test", "description", False,
189+
transform=lambda v: True if v == "world" else v)
182190
self.assertIn("test [BooleanOption]", option.description)
183191
self.assertEqual(False, option.value)
184192
option.value = 1
@@ -191,6 +199,8 @@ def test_should_be_constructable_from_boolean(self):
191199
self.assertEqual(True, option.value)
192200
option.value = False
193201
self.assertEqual(False, option.value)
202+
option.value = "world"
203+
self.assertEqual(True, option.value)
194204

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

380390
def test_should_format_boolean_option(self):
381-
option = BooleanOption("test", "description", default=True)
391+
option = BooleanOption("test", "description", default=True,
392+
transform=lambda v: True if v == "hello" else v)
393+
394+
output = str(lbuild.format.format_option_value_description(option))
395+
self.assertIn("yes in [yes, no]", output, "Output")
396+
397+
option.value = "hello"
398+
self.assertEqual(True, option.value)
399+
output = str(lbuild.format.format_option_value_description(option))
400+
self.assertIn("hello in [yes, no]", output, "Output")
401+
402+
option.value = "1"
403+
output = str(lbuild.format.format_option_value_description(option))
404+
self.assertIn("1 in [yes, no]", output, "Output")
382405

406+
option.value = "TRUE"
383407
output = str(lbuild.format.format_option_value_description(option))
384-
self.assertIn("True in [True, False]", output, "Output")
408+
self.assertIn("true in [yes, no]", output, "Output")
385409

386410
def test_should_format_numeric_option(self):
387411

0 commit comments

Comments
 (0)