describe
trestle.core.commands.describe
¤
Trestle Describe Command.
logger
¤
Classes¤
DescribeCmd (CommandPlusDocs)
¤
Describe contents of a model file including optional element path.
Source code in trestle/core/commands/describe.py
class DescribeCmd(CommandPlusDocs):
"""Describe contents of a model file including optional element path."""
# The only output is via log lines. No other results or side-effects.
name = 'describe'
def _init_arguments(self) -> None:
logger.debug('Init arguments')
self.add_argument('-f', '--file', help='OSCAL file to import.', type=str, required=True)
self.add_argument(
'-e', '--element', help='Optional name of element in file to describe.', type=str, required=False
)
def _run(self, args: argparse.Namespace) -> int:
try:
logger.debug('Entering trestle describe.')
log.set_log_level_from_args(args)
if args.file:
model_file = pathlib.Path(args.file)
element = '' if not args.element else args.element.strip("'")
results = self.describe(model_file.resolve(), element, args.trestle_root)
return CmdReturnCodes.SUCCESS.value if len(results) > 0 else CmdReturnCodes.COMMAND_ERROR.value
else:
raise TrestleIncorrectArgsError('No file specified for command describe.')
except Exception as e: # pragma: no cover
return handle_generic_command_exception(e, logger, 'Error while describing contents of a model')
@classmethod
def _clean_type_string(cls, text: str) -> str:
text = text.replace("<class '", '').replace("'>", '')
text = text.replace('trestle.oscal.', '')
text = text.replace('pydantic.main.', 'stripped.')
return text
@classmethod
def _description_text(cls, sub_model: Optional[Union[OscalBaseModel, List[OscalBaseModel]]]) -> str:
clip_string = 100
if sub_model is None:
return 'None'
if type(sub_model) is list:
n_items = len(sub_model)
type_text = 'Unknown' if not n_items else f'{cls._clean_type_string(str(type(sub_model[0])))}'
text = f'list of {n_items} items of type {type_text}'
return text
if type(sub_model) is str:
return sub_model if len(sub_model
) < clip_string else sub_model[:clip_string] + '[truncated]' # type: ignore
if hasattr(sub_model, 'type_'):
return cls._clean_type_string(str(sub_model.type_))
return cls._clean_type_string(str(type(sub_model)))
@classmethod
def describe(cls, file_path: pathlib.Path, element_path_str: str, trestle_root: pathlib.Path) -> List[str]:
"""Describe the contents of the file.
Args:
file_path: pathlib.Path Path for model file to describe.
element_path_str: Element path of element in model to describe. Can be ''.
Returns:
The list of lines of text in the description, or an empty list on failure
"""
# figure out the model type so we can read it
try:
model_type, _ = ModelUtils.get_stripped_model_type(file_path, trestle_root)
model: OscalBaseModel = model_type.oscal_read(file_path)
except TrestleError as e:
logger.warning(f'Error loading model {file_path} to describe: {e}')
return []
sub_model = model
# if an element path was provided, follow the path chain to the desired sub_model
if element_path_str:
if '*' in element_path_str or ',' in element_path_str:
logger.warning('Wildcards and commas are not allowed in the element path for describe.')
return []
if '.' not in element_path_str:
logger.warning('The element path for describe must either be omitted or contain at least 2 parts.')
return []
element_paths = utils.parse_element_arg(model, element_path_str)
sub_model_element = Element(model)
for element_path in element_paths:
sub_model = sub_model_element.get_at(element_path, False)
sub_model_element = Element(sub_model)
# now that we have the desired sub_model we can describe it
text_out: List[str] = []
# create top level text depending on whether an element path was used
element_text = '' if not element_path_str else f' at element path {element_path_str}'
if type(sub_model) is list:
text = f'Model file {file_path}{element_text} is a {cls._description_text(sub_model)}'
text_out.append(text)
logger.info(text)
else:
text = f'Model file {file_path}{element_text} is of type '
text += f'{cls._clean_type_string(str(type(sub_model)))} and contains:'
text_out.append(text)
logger.info(text)
for key in sub_model.__fields__.keys():
value = getattr(sub_model, key, None)
text = f' {key}: {cls._description_text(value)}'
text_out.append(text)
logger.info(text)
return text_out
name
¤
Methods¤
describe(file_path, element_path_str, trestle_root)
classmethod
¤
Describe the contents of the file.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
file_path |
Path |
pathlib.Path Path for model file to describe. |
required |
element_path_str |
str |
Element path of element in model to describe. Can be ''. |
required |
Returns:
Type | Description |
---|---|
List[str] |
The list of lines of text in the description, or an empty list on failure |
Source code in trestle/core/commands/describe.py
@classmethod
def describe(cls, file_path: pathlib.Path, element_path_str: str, trestle_root: pathlib.Path) -> List[str]:
"""Describe the contents of the file.
Args:
file_path: pathlib.Path Path for model file to describe.
element_path_str: Element path of element in model to describe. Can be ''.
Returns:
The list of lines of text in the description, or an empty list on failure
"""
# figure out the model type so we can read it
try:
model_type, _ = ModelUtils.get_stripped_model_type(file_path, trestle_root)
model: OscalBaseModel = model_type.oscal_read(file_path)
except TrestleError as e:
logger.warning(f'Error loading model {file_path} to describe: {e}')
return []
sub_model = model
# if an element path was provided, follow the path chain to the desired sub_model
if element_path_str:
if '*' in element_path_str or ',' in element_path_str:
logger.warning('Wildcards and commas are not allowed in the element path for describe.')
return []
if '.' not in element_path_str:
logger.warning('The element path for describe must either be omitted or contain at least 2 parts.')
return []
element_paths = utils.parse_element_arg(model, element_path_str)
sub_model_element = Element(model)
for element_path in element_paths:
sub_model = sub_model_element.get_at(element_path, False)
sub_model_element = Element(sub_model)
# now that we have the desired sub_model we can describe it
text_out: List[str] = []
# create top level text depending on whether an element path was used
element_text = '' if not element_path_str else f' at element path {element_path_str}'
if type(sub_model) is list:
text = f'Model file {file_path}{element_text} is a {cls._description_text(sub_model)}'
text_out.append(text)
logger.info(text)
else:
text = f'Model file {file_path}{element_text} is of type '
text += f'{cls._clean_type_string(str(type(sub_model)))} and contains:'
text_out.append(text)
logger.info(text)
for key in sub_model.__fields__.keys():
value = getattr(sub_model, key, None)
text = f' {key}: {cls._description_text(value)}'
text_out.append(text)
logger.info(text)
return text_out
handler: python