config_tools: support dynamic enum names in XML schema

The dynamic enum mechanism today only allows specifying the enum values
using XPATH. While this is sufficient from functionality point of view, it
may not provide the best experience as users have to understand the raw
data used internally. The typical way to present more informational labels
of enum values to users is enum names which cannot be supported by the
current XML schema to JSONSchema converter.

This patch allows the XML schema to specify dynamic enum names by adding an
`acrn:option-names` attribute to an element. The attribute is interpreted
as an XPATH which evaluates to a sequence of the same length of
`acrn-options`. The element at index i in that sequence is considered the
enum name of the enum value at index i of the results of `acrn:options`.

This mechanism is first applied to the `pcpu_id` element to indicate
whether a physical CPU is P-core or E-core.

Tracked-On: #8050
Signed-off-by: Junjie Mao <junjie.mao@intel.com>
This commit is contained in:
Junjie Mao 2022-08-24 22:04:32 +08:00 committed by acrnsi-robot
parent 7ad100b1a9
commit f78c8f4660
3 changed files with 28 additions and 17 deletions

View File

@ -22,15 +22,16 @@ def get_dynamic_scenario(board):
"""
board_xml = etree.fromstring(board)
def get_enum(source, options, obj_type):
def get_enum(source, options, option_names, obj_type):
elements = [str(x) for x in elementpath.select(source, options) if x]
elements = list(set(elements))
element_names = [str(x) for x in elementpath.select(source, option_names) if x]
elements = list(set(zip(elements, element_names)))
if not elements:
elements = ['']
elements = [('', '')]
# TODO: Add more converters if needed
enum_type_convert = {'integer': lambda x: int(x) if x else 0}
if obj_type in enum_type_convert.keys():
elements = [enum_type_convert[obj_type](x) for x in elements]
elements = [(enum_type_convert[obj_type](x[0]), x[1]) for x in elements]
return elements
def dynamic_enum(**enum_setting):
@ -40,13 +41,14 @@ def get_dynamic_scenario(board):
for key in ['function', 'source']
]
# value from given
selector, sorted_func, obj_type = [enum_setting[key] for key in ['selector', 'sorted', 'type']]
selector, name_selector, sorted_func, obj_type = [enum_setting[key] for key in ['selector', 'name-selector', 'sorted', 'type']]
# get enum data
enum = function(source, selector, obj_type)
enum = function(source, selector, name_selector, obj_type)
if sorted_func:
enum = sorted(enum, key=eval(sorted_func))
return enum
fn = eval(sorted_func)
enum = sorted(enum, key=lambda x: fn(x[0]))
return zip(*enum)
def dynamic_enum_apply(obj):
# get json schema enum obj
@ -56,7 +58,9 @@ def get_dynamic_scenario(board):
if enum_setting['type'] == 'dynamicEnum':
enum_setting['type'] = obj.get('type', '')
# replace json schema obj enum field data
obj['enum'] = dynamic_enum(**enum_setting)
enum, enum_names = dynamic_enum(**enum_setting)
obj['enum'] = enum
obj['enumNames'] = enum_names
return obj
data = json.loads(scenario_json_schema, object_hook=dynamic_enum_apply)

View File

@ -340,11 +340,13 @@ class XS2JS:
# get description
if 'xs:annotation' in element:
annotation = element['xs:annotation']
# title
js_ele['title'] = element['xs:annotation'].get('@acrn:title', name)
js_ele['title'] = annotation.get('@acrn:title', name)
# documentation
documentation: str = element['xs:annotation'].get('xs:documentation', None)
documentation: str = annotation.get('xs:documentation', None)
if documentation is None or documentation.strip() == '':
documentation = ''
if documentation:
@ -352,13 +354,14 @@ class XS2JS:
js_ele['description'] = documentation
# dynamic enum
if '@acrn:options' in element['xs:annotation'] and 'dynamicEnum' in self.features:
if '@acrn:options' in annotation and 'dynamicEnum' in self.features:
dynamic_enum = {
'type': 'dynamicEnum',
'function': 'get_enum',
'source': 'board_xml',
'selector': element['xs:annotation']['@acrn:options'],
'sorted': element['xs:annotation'].get('@acrn:options-sorted-by', None)
'selector': annotation['@acrn:options'],
'name-selector': annotation['@acrn:option-names'] if '@acrn:option-names' in annotation else annotation['@acrn:options'],
'sorted': annotation.get('@acrn:options-sorted-by', None)
}
# enum should be applied to array items instead of array itself
if 'items' in js_ele:
@ -367,10 +370,10 @@ class XS2JS:
js_ele['enum'] = dynamic_enum
# widget and its options
self.convert_widget_config(element['xs:annotation'], js_ele)
self.convert_widget_config(annotation, js_ele)
# Error messages
self.convert_errormsg_config(element['xs:annotation'], js_ele)
self.convert_errormsg_config(annotation, js_ele)
properties[name] = js_ele

View File

@ -46,7 +46,11 @@
<xs:all>
<xs:element name="pcpu_id" type="xs:integer">
<xs:annotation acrn:title="pCPU ID"
acrn:options="//processors//thread/cpu_id/text()" acrn:options-sorted-by="int">
acrn:options="//processors//thread/cpu_id/text()"
acrn:option-names="if (count(distinct-values(//processors//thread/core_type)) &gt; 1)
then (for $thread in //processors//thread return concat($thread/cpu_id, ' (', if ($thread/core_type = 'Core') then 'P-Core' else 'E-Core', ')'))
else //processors//thread/cpu_id/text()"
acrn:options-sorted-by="int">
<xs:documentation>ID of the pCPU that this VM's vCPU is allowed to pin to.</xs:documentation>
</xs:annotation>
</xs:element>