pylspci/pylspci/parsers/verbose.py

115 lines
3.7 KiB
Python
Raw Normal View History

2019-09-06 21:09:50 +00:00
from typing import Union, List, Dict, Iterable, NamedTuple, Callable, Any
2019-07-10 17:02:55 +00:00
from pylspci.parsers.base import Parser
from pylspci.device import Device
from pylspci.fields import hexstring, Slot, NameWithID
class FieldMapping(NamedTuple):
2019-07-10 18:54:15 +00:00
"""
Helper class to map verbose output field names such as ``SVendor`` to
:class:`Device` fields such as ``subsytem_vendor``.
"""
2019-07-10 17:02:55 +00:00
field_name: str
2019-07-10 18:54:15 +00:00
"""
Field name on the :class:`Device` named tuple.
:type: str
"""
2019-07-10 17:02:55 +00:00
field_type: Callable[[str], Any]
2019-07-10 18:54:15 +00:00
"""
Field type; a callable to use to parse the string value.
:type: Callable[[str], Any]
"""
2019-07-10 17:02:55 +00:00
many: bool = False
2019-07-10 18:54:15 +00:00
"""
Whether or not to use a List, if this field can be repeated multiple times
in the lspci output.
:type: bool
"""
2019-07-10 17:02:55 +00:00
class VerboseParser(Parser):
"""
2019-07-10 18:54:15 +00:00
A parser for lspci -vvvmmk
2019-07-10 17:02:55 +00:00
"""
default_lspci_args = {
'verbose': True,
'kernel_drivers': True,
}
# Maps lspci output fields to Device fields with a type
_field_mapping = {
'Slot': FieldMapping(field_name='slot', field_type=Slot),
'Class': FieldMapping(field_name='cls', field_type=NameWithID),
'Vendor': FieldMapping(field_name='vendor', field_type=NameWithID),
'Device': FieldMapping(field_name='device', field_type=NameWithID),
'SVendor': FieldMapping(
field_name='subsystem_vendor',
field_type=NameWithID,
),
'SDevice': FieldMapping(
field_name='subsystem_device',
field_type=NameWithID,
),
'Rev': FieldMapping(field_name='revision', field_type=hexstring),
'ProgIf': FieldMapping(field_name='progif', field_type=hexstring),
'Driver': FieldMapping(field_name='driver', field_type=str),
'Module': FieldMapping(
field_name='kernel_modules',
field_type=str,
many=True,
),
}
2019-08-03 22:53:16 +00:00
def _parse_device(self, device_data: Union[str, Iterable[str]]) -> Device:
2019-09-06 21:09:50 +00:00
devdict: Dict[str, Any] = {}
2019-07-10 17:02:55 +00:00
if isinstance(device_data, str):
device_data = device_data.splitlines()
for line in device_data:
key, _, value = map(str.strip, line.partition(':'))
assert key in self._field_mapping, \
'Unsupported key {!r}'.format(key)
field = self._field_mapping[key]
if field.many:
devdict.setdefault(field.field_name, []) \
.append(field.field_type(value))
else:
devdict[field.field_name] = field.field_type(value)
return Device(**devdict)
2019-09-06 21:09:50 +00:00
def parse(
self,
data: Union[str, Iterable[str], Iterable[Iterable[str]]],
) -> List[Device]:
2019-07-10 18:54:15 +00:00
"""
Parse an lspci -vvvmm[nnk] output, either as a single string holding
multiple devices separated by two newlines,
or as a list of multiline strings holding one device each.
2019-09-06 21:09:50 +00:00
:param data: A string holding multiple devices,
a list of strings, one for each device,
or a list of lists of strings, one list for each device, with
each list holding each part of the device output.
:type data: str or Iterable[str] or Iterable[Iterable[str]]
2019-07-10 18:54:15 +00:00
:return: A list of parsed devices.
:rtype: List[Device]
"""
2019-07-10 17:02:55 +00:00
if isinstance(data, str):
data = data.split('\n\n')
2019-09-06 21:09:50 +00:00
result: List[Device] = []
for line in data:
if isinstance(line, str):
line = str.strip(line)
if not line: # Ignore empty strings and lists
continue
result.append(self._parse_device(line))
return result