mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-26 14:41:23 -07:00
Add new filter plugin: 'to_prettytable' (#9954)
* Add new action plugin 'prettytable' * Add integration tests for 'prettytable' plugin * Added BOTMETA details * Add COPYRIGHT details * Add 'to_prettytable' filter plugin and tests * fix: 🐛 Fix add_rows method * Add changelog fragment * Remove changelog fragments * Apply code review suggestions * Correct BOTMETA and lint * refactor: 🔥 Remove unnecessary code parts * fix: Fix contact details * Correct kwargs.pop and column_alignments description * Remove 'trim' filter from conditionals in tests * Add additional validations and tests * fix: Apply corrections after review * refactor: Optimize code and make some refactoring * fix: Add some minor corrections * fix: add column_alignments validation and tests * Update version_added to "10.7.0" Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> * refactor: Use TypeValidationError class for type checking * refactor: Apply suggestions * fix: documentation indent * Apply suggestion Co-authored-by: Felix Fontein <felix@fontein.de> * style: Adjust indentation Co-authored-by: Felix Fontein <felix@fontein.de> * style: Correction of examples * fix: Commit suggestion Co-authored-by: Felix Fontein <felix@fontein.de> * fix: Commit suggestion Co-authored-by: Felix Fontein <felix@fontein.de> * feat: Add correct parameters validation for empty data --------- Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
f3ea40db3d
commit
7e4d6aa541
4 changed files with 1079 additions and 0 deletions
2
.github/BOTMETA.yml
vendored
2
.github/BOTMETA.yml
vendored
|
@ -210,6 +210,8 @@ files:
|
||||||
maintainers: resmo
|
maintainers: resmo
|
||||||
$filters/to_months.yml:
|
$filters/to_months.yml:
|
||||||
maintainers: resmo
|
maintainers: resmo
|
||||||
|
$filters/to_prettytable.py:
|
||||||
|
maintainers: tgadiev
|
||||||
$filters/to_seconds.yml:
|
$filters/to_seconds.yml:
|
||||||
maintainers: resmo
|
maintainers: resmo
|
||||||
$filters/to_time_unit.yml:
|
$filters/to_time_unit.yml:
|
||||||
|
|
414
plugins/filter/to_prettytable.py
Normal file
414
plugins/filter/to_prettytable.py
Normal file
|
@ -0,0 +1,414 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2025, Timur Gadiev <tgadiev@gmail.com>
|
||||||
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
name: to_prettytable
|
||||||
|
short_description: Format a list of dictionaries as an ASCII table
|
||||||
|
version_added: "10.7.0"
|
||||||
|
author: Timur Gadiev (@tgadiev)
|
||||||
|
description:
|
||||||
|
- This filter takes a list of dictionaries and formats it as an ASCII table using the I(prettytable) Python library.
|
||||||
|
requirements:
|
||||||
|
- prettytable
|
||||||
|
options:
|
||||||
|
_input:
|
||||||
|
description: A list of dictionaries to format.
|
||||||
|
type: list
|
||||||
|
elements: dictionary
|
||||||
|
required: true
|
||||||
|
column_order:
|
||||||
|
description: List of column names to specify the order of columns in the table.
|
||||||
|
type: list
|
||||||
|
elements: string
|
||||||
|
header_names:
|
||||||
|
description: List of custom header names to use instead of dictionary keys.
|
||||||
|
type: list
|
||||||
|
elements: string
|
||||||
|
column_alignments:
|
||||||
|
description:
|
||||||
|
- >-
|
||||||
|
Dictionary where keys are column names and values are alignment settings.
|
||||||
|
Valid alignment values are C(left), C(center), C(right), C(l), C(c), or C(r).
|
||||||
|
- >-
|
||||||
|
For example, V({'name': 'left', 'id': 'right'}) will align the C(name) column to the left
|
||||||
|
and the C(id) column to the right.
|
||||||
|
type: dictionary
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
---
|
||||||
|
- name: Set a list of users
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
users:
|
||||||
|
- name: Alice
|
||||||
|
age: 25
|
||||||
|
role: admin
|
||||||
|
- name: Bob
|
||||||
|
age: 30
|
||||||
|
role: user
|
||||||
|
|
||||||
|
- name: Display a list of users as a table
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{
|
||||||
|
users | community.general.to_prettytable
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Display a table with custom column ordering
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{
|
||||||
|
users | community.general.to_prettytable(
|
||||||
|
column_order=['role', 'name', 'age']
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Display a table with selective column output (only show name and role fields)
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{
|
||||||
|
users | community.general.to_prettytable(
|
||||||
|
column_order=['name', 'role']
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Display a table with custom headers
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{
|
||||||
|
users | community.general.to_prettytable(
|
||||||
|
header_names=['User Name', 'User Age', 'User Role']
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Display a table with custom alignments
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{
|
||||||
|
users | community.general.to_prettytable(
|
||||||
|
column_alignments={'name': 'center', 'age': 'right', 'role': 'left'}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Combine multiple options
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{
|
||||||
|
users | community.general.to_prettytable(
|
||||||
|
column_order=['role', 'name', 'age'],
|
||||||
|
header_names=['Position', 'Full Name', 'Years'],
|
||||||
|
column_alignments={'name': 'center', 'age': 'right', 'role': 'left'}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
_value:
|
||||||
|
description: The formatted ASCII table.
|
||||||
|
type: string
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
import prettytable
|
||||||
|
HAS_PRETTYTABLE = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_PRETTYTABLE = False
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible.module_utils.six import string_types
|
||||||
|
|
||||||
|
|
||||||
|
class TypeValidationError(AnsibleFilterError):
|
||||||
|
"""Custom exception for type validation errors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj: The object with incorrect type
|
||||||
|
expected: Description of expected type
|
||||||
|
"""
|
||||||
|
def __init__(self, obj, expected):
|
||||||
|
type_name = "string" if isinstance(obj, string_types) else type(obj).__name__
|
||||||
|
super().__init__(f"Expected {expected}, got a {type_name}")
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_list_param(param, param_name, ensure_strings=True):
|
||||||
|
"""Validate a parameter is a list and optionally ensure all elements are strings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: The parameter to validate
|
||||||
|
param_name: The name of the parameter for error messages
|
||||||
|
ensure_strings: Whether to check that all elements are strings
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AnsibleFilterError: If validation fails
|
||||||
|
"""
|
||||||
|
# Map parameter names to their original error message format
|
||||||
|
error_messages = {
|
||||||
|
"column_order": "a list of column names",
|
||||||
|
"header_names": "a list of header names"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use the specific error message if available, otherwise use a generic one
|
||||||
|
error_msg = error_messages.get(param_name, f"a list for {param_name}")
|
||||||
|
|
||||||
|
if not isinstance(param, list):
|
||||||
|
raise TypeValidationError(param, error_msg)
|
||||||
|
|
||||||
|
if ensure_strings:
|
||||||
|
for item in param:
|
||||||
|
if not isinstance(item, string_types):
|
||||||
|
# Maintain original error message format
|
||||||
|
if param_name == "column_order":
|
||||||
|
error_msg = "a string for column name"
|
||||||
|
elif param_name == "header_names":
|
||||||
|
error_msg = "a string for header name"
|
||||||
|
else:
|
||||||
|
error_msg = f"a string for {param_name} element"
|
||||||
|
raise TypeValidationError(item, error_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def _match_key(item_dict, lookup_key):
|
||||||
|
"""Find a matching key in a dictionary, handling type conversion.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_dict: Dictionary to search in
|
||||||
|
lookup_key: Key to look for, possibly needing type conversion
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The matching key or None if no match found
|
||||||
|
"""
|
||||||
|
# Direct key match
|
||||||
|
if lookup_key in item_dict:
|
||||||
|
return lookup_key
|
||||||
|
|
||||||
|
# Try boolean conversion for 'true'/'false' strings
|
||||||
|
if isinstance(lookup_key, string_types):
|
||||||
|
if lookup_key.lower() == 'true' and True in item_dict:
|
||||||
|
return True
|
||||||
|
if lookup_key.lower() == 'false' and False in item_dict:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Try numeric conversion for string numbers
|
||||||
|
if lookup_key.isdigit() and int(lookup_key) in item_dict:
|
||||||
|
return int(lookup_key)
|
||||||
|
|
||||||
|
# No match found
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_key_maps(data):
|
||||||
|
"""Build mappings between string keys and original keys.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: List of dictionaries with keys to map
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (key_map, reverse_key_map)
|
||||||
|
"""
|
||||||
|
key_map = {}
|
||||||
|
reverse_key_map = {}
|
||||||
|
|
||||||
|
# Check if the data list is not empty
|
||||||
|
if not data:
|
||||||
|
return key_map, reverse_key_map
|
||||||
|
|
||||||
|
first_dict = data[0]
|
||||||
|
for orig_key in first_dict.keys():
|
||||||
|
# Store string version of the key
|
||||||
|
str_key = to_text(orig_key)
|
||||||
|
key_map[str_key] = orig_key
|
||||||
|
# Also store lowercase version for case-insensitive lookups
|
||||||
|
reverse_key_map[str_key.lower()] = orig_key
|
||||||
|
|
||||||
|
return key_map, reverse_key_map
|
||||||
|
|
||||||
|
|
||||||
|
def _configure_alignments(table, field_names, column_alignments):
|
||||||
|
"""Configure column alignments for the table.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
table: The PrettyTable instance to configure
|
||||||
|
field_names: List of field names to align
|
||||||
|
column_alignments: Dict of column alignments
|
||||||
|
"""
|
||||||
|
valid_alignments = {"left", "center", "right", "l", "c", "r"}
|
||||||
|
|
||||||
|
if not isinstance(column_alignments, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
for col_name, alignment in column_alignments.items():
|
||||||
|
if col_name in field_names:
|
||||||
|
# We already validated alignment is a string and a valid value in the main function
|
||||||
|
# Just apply it here
|
||||||
|
alignment = alignment.lower()
|
||||||
|
table.align[col_name] = alignment[0]
|
||||||
|
|
||||||
|
|
||||||
|
def to_prettytable(data, *args, **kwargs):
|
||||||
|
"""Convert a list of dictionaries to an ASCII table.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: List of dictionaries to format
|
||||||
|
*args: Optional list of column names to specify column order
|
||||||
|
**kwargs: Optional keyword arguments:
|
||||||
|
- column_order: List of column names to specify the order
|
||||||
|
- header_names: List of custom header names
|
||||||
|
- column_alignments: Dict of column alignments (left, center, right)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String containing the ASCII table
|
||||||
|
"""
|
||||||
|
if not HAS_PRETTYTABLE:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
'You need to install "prettytable" Python module to use this filter'
|
||||||
|
)
|
||||||
|
|
||||||
|
# === Input validation ===
|
||||||
|
# Validate list type
|
||||||
|
if not isinstance(data, list):
|
||||||
|
raise TypeValidationError(data, "a list of dictionaries")
|
||||||
|
|
||||||
|
# Validate dictionary items if list is not empty
|
||||||
|
if data and not all(isinstance(item, dict) for item in data):
|
||||||
|
invalid_item = next((item for item in data if not isinstance(item, dict)), None)
|
||||||
|
raise TypeValidationError(invalid_item, "all items in the list to be dictionaries")
|
||||||
|
|
||||||
|
# Get sample dictionary to determine fields - empty if no data
|
||||||
|
sample_dict = data[0] if data else {}
|
||||||
|
max_fields = len(sample_dict)
|
||||||
|
|
||||||
|
# === Process column order ===
|
||||||
|
# Handle both positional and keyword column_order
|
||||||
|
column_order = kwargs.pop('column_order', None)
|
||||||
|
|
||||||
|
# Check for conflict between args and column_order
|
||||||
|
if args and column_order is not None:
|
||||||
|
raise AnsibleFilterError("Cannot use both positional arguments and the 'column_order' keyword argument")
|
||||||
|
|
||||||
|
# Use positional args if provided
|
||||||
|
if args:
|
||||||
|
column_order = list(args)
|
||||||
|
|
||||||
|
# Validate column_order
|
||||||
|
if column_order is not None:
|
||||||
|
_validate_list_param(column_order, "column_order")
|
||||||
|
|
||||||
|
# Validate column_order doesn't exceed the number of fields (skip if data is empty)
|
||||||
|
if data and len(column_order) > max_fields:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
f"'column_order' has more elements ({len(column_order)}) than available fields in data ({max_fields})")
|
||||||
|
|
||||||
|
# === Process headers ===
|
||||||
|
# Determine field names and ensure they are strings
|
||||||
|
if column_order:
|
||||||
|
field_names = column_order
|
||||||
|
else:
|
||||||
|
# Use field names from first dictionary, ensuring all are strings
|
||||||
|
field_names = [to_text(k) for k in sample_dict]
|
||||||
|
|
||||||
|
# Process custom headers
|
||||||
|
header_names = kwargs.pop('header_names', None)
|
||||||
|
if header_names is not None:
|
||||||
|
_validate_list_param(header_names, "header_names")
|
||||||
|
|
||||||
|
# Validate header_names doesn't exceed the number of fields (skip if data is empty)
|
||||||
|
if data and len(header_names) > max_fields:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
f"'header_names' has more elements ({len(header_names)}) than available fields in data ({max_fields})")
|
||||||
|
|
||||||
|
# Validate that column_order and header_names have the same size if both provided
|
||||||
|
if column_order is not None and len(column_order) != len(header_names):
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
f"'column_order' and 'header_names' must have the same number of elements. "
|
||||||
|
f"Got {len(column_order)} columns and {len(header_names)} headers.")
|
||||||
|
|
||||||
|
# === Process alignments ===
|
||||||
|
# Get column alignments and validate
|
||||||
|
column_alignments = kwargs.pop('column_alignments', {})
|
||||||
|
valid_alignments = {"left", "center", "right", "l", "c", "r"}
|
||||||
|
|
||||||
|
# Validate column_alignments is a dictionary
|
||||||
|
if not isinstance(column_alignments, dict):
|
||||||
|
raise TypeValidationError(column_alignments, "a dictionary for column_alignments")
|
||||||
|
|
||||||
|
# Validate column_alignments keys and values
|
||||||
|
for key, value in column_alignments.items():
|
||||||
|
# Check that keys are strings
|
||||||
|
if not isinstance(key, string_types):
|
||||||
|
raise TypeValidationError(key, "a string for column_alignments key")
|
||||||
|
|
||||||
|
# Check that values are strings
|
||||||
|
if not isinstance(value, string_types):
|
||||||
|
raise TypeValidationError(value, "a string for column_alignments value")
|
||||||
|
|
||||||
|
# Check that values are valid alignments
|
||||||
|
if value.lower() not in valid_alignments:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
f"Invalid alignment '{value}' in 'column_alignments'. "
|
||||||
|
f"Valid alignments are: {', '.join(sorted(valid_alignments))}")
|
||||||
|
|
||||||
|
# Validate column_alignments doesn't have more keys than fields (skip if data is empty)
|
||||||
|
if data and len(column_alignments) > max_fields:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
f"'column_alignments' has more elements ({len(column_alignments)}) than available fields in data ({max_fields})")
|
||||||
|
|
||||||
|
# Check for unknown parameters
|
||||||
|
if kwargs:
|
||||||
|
raise AnsibleFilterError(f"Unknown parameter(s) for to_prettytable filter: {', '.join(sorted(kwargs))}")
|
||||||
|
|
||||||
|
# === Build the table ===
|
||||||
|
table = prettytable.PrettyTable()
|
||||||
|
|
||||||
|
# Set the field names for display
|
||||||
|
display_names = header_names if header_names is not None else field_names
|
||||||
|
table.field_names = [to_text(name) for name in display_names]
|
||||||
|
|
||||||
|
# Configure alignments after setting field_names
|
||||||
|
_configure_alignments(table, display_names, column_alignments)
|
||||||
|
|
||||||
|
# Build key maps only if not using explicit column_order and we have data
|
||||||
|
key_map = {}
|
||||||
|
reverse_key_map = {}
|
||||||
|
if not column_order and data: # Only needed when using original dictionary keys and we have data
|
||||||
|
key_map, reverse_key_map = _build_key_maps(data)
|
||||||
|
|
||||||
|
# If we have an empty list with no custom parameters, return a simple empty table
|
||||||
|
if not data and not column_order and not header_names and not column_alignments:
|
||||||
|
return "++\n++"
|
||||||
|
|
||||||
|
# Process each row if we have data
|
||||||
|
for item in data:
|
||||||
|
row = []
|
||||||
|
for col in field_names:
|
||||||
|
# Try direct mapping first
|
||||||
|
if col in key_map:
|
||||||
|
row.append(item.get(key_map[col], ""))
|
||||||
|
else:
|
||||||
|
# Try to find a matching key in the item
|
||||||
|
matched_key = _match_key(item, col)
|
||||||
|
if matched_key is not None:
|
||||||
|
row.append(item.get(matched_key, ""))
|
||||||
|
else:
|
||||||
|
# Try case-insensitive lookup as last resort
|
||||||
|
lower_col = col.lower() if isinstance(col, string_types) else str(col).lower()
|
||||||
|
if lower_col in reverse_key_map:
|
||||||
|
row.append(item.get(reverse_key_map[lower_col], ""))
|
||||||
|
else:
|
||||||
|
# No match found
|
||||||
|
row.append("")
|
||||||
|
table.add_row(row)
|
||||||
|
|
||||||
|
return to_text(table)
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
"""Ansible core jinja2 filters."""
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
return {
|
||||||
|
'to_prettytable': to_prettytable
|
||||||
|
}
|
5
tests/integration/targets/filter_to_prettytable/aliases
Normal file
5
tests/integration/targets/filter_to_prettytable/aliases
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Copyright (c) Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
azp/posix/1
|
658
tests/integration/targets/filter_to_prettytable/tasks/main.yml
Normal file
658
tests/integration/targets/filter_to_prettytable/tasks/main.yml
Normal file
|
@ -0,0 +1,658 @@
|
||||||
|
---
|
||||||
|
####################################################################
|
||||||
|
# WARNING: These are designed specifically for Ansible tests #
|
||||||
|
# and should not be used as examples of how to write Ansible roles #
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
# Copyright (c) 2025, Timur Gadiev (tgadiev@gmail.com)
|
||||||
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
- name: Install required libs
|
||||||
|
pip:
|
||||||
|
name: prettytable
|
||||||
|
state: present
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
|
||||||
|
- name: Set test data
|
||||||
|
set_fact:
|
||||||
|
test_data:
|
||||||
|
- name: Alice
|
||||||
|
age: 25
|
||||||
|
role: admin
|
||||||
|
- name: Bob
|
||||||
|
age: 30
|
||||||
|
role: user
|
||||||
|
data_for_align:
|
||||||
|
- date: 2023-01-01
|
||||||
|
description: Office supplies
|
||||||
|
amount: 123.45
|
||||||
|
|
||||||
|
# Test basic functionality
|
||||||
|
- name: Test basic table creation
|
||||||
|
set_fact:
|
||||||
|
basic_table: '{{ test_data | community.general.to_prettytable }}'
|
||||||
|
expected_basic_table: |-
|
||||||
|
+-------+-----+-------+
|
||||||
|
| name | age | role |
|
||||||
|
+-------+-----+-------+
|
||||||
|
| Alice | 25 | admin |
|
||||||
|
| Bob | 30 | user |
|
||||||
|
+-------+-----+-------+
|
||||||
|
|
||||||
|
- name: Verify basic table output
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- basic_table == expected_basic_table
|
||||||
|
|
||||||
|
# Test column ordering
|
||||||
|
- name: Test column ordering
|
||||||
|
set_fact:
|
||||||
|
ordered_table: "{{ test_data | community.general.to_prettytable(column_order=['role', 'name', 'age']) }}"
|
||||||
|
expected_ordered_table: |-
|
||||||
|
+-------+-------+-----+
|
||||||
|
| role | name | age |
|
||||||
|
+-------+-------+-----+
|
||||||
|
| admin | Alice | 25 |
|
||||||
|
| user | Bob | 30 |
|
||||||
|
+-------+-------+-----+
|
||||||
|
|
||||||
|
- name: Verify ordered table output
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- ordered_table == expected_ordered_table
|
||||||
|
|
||||||
|
# Test selective column ordering (subset of keys)
|
||||||
|
- name: Test selective column ordering
|
||||||
|
set_fact:
|
||||||
|
selective_ordered_table: "{{ test_data | community.general.to_prettytable(column_order=['name', 'role']) }}"
|
||||||
|
expected_selective_table: |-
|
||||||
|
+-------+-------+
|
||||||
|
| name | role |
|
||||||
|
+-------+-------+
|
||||||
|
| Alice | admin |
|
||||||
|
| Bob | user |
|
||||||
|
+-------+-------+
|
||||||
|
|
||||||
|
- name: Verify selective column ordering
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- selective_ordered_table == expected_selective_table
|
||||||
|
|
||||||
|
# Test custom headers
|
||||||
|
- name: Test custom headers
|
||||||
|
set_fact:
|
||||||
|
headers_table: "{{ test_data | community.general.to_prettytable(header_names=['User Name', 'User Age', 'User Role']) }}"
|
||||||
|
expected_headers_table: |-
|
||||||
|
+-----------+----------+-----------+
|
||||||
|
| User Name | User Age | User Role |
|
||||||
|
+-----------+----------+-----------+
|
||||||
|
| Alice | 25 | admin |
|
||||||
|
| Bob | 30 | user |
|
||||||
|
+-----------+----------+-----------+
|
||||||
|
|
||||||
|
- name: Verify custom headers output
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- headers_table == expected_headers_table
|
||||||
|
|
||||||
|
# Test selective column ordering with custom headers (subset of keys)
|
||||||
|
- name: Test selective column ordering with custom headers
|
||||||
|
set_fact:
|
||||||
|
selective_ordered_headers_table: "{{ test_data | community.general.to_prettytable(column_order=['name', 'role'], header_names=['User Name', 'User Role']) }}"
|
||||||
|
expected_selective_headers_table: |-
|
||||||
|
+-----------+-----------+
|
||||||
|
| User Name | User Role |
|
||||||
|
+-----------+-----------+
|
||||||
|
| Alice | admin |
|
||||||
|
| Bob | user |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
- name: Verify selective column ordering with custom headers
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- selective_ordered_headers_table == expected_selective_headers_table
|
||||||
|
|
||||||
|
# Test alignments
|
||||||
|
- name: Test column alignments
|
||||||
|
set_fact:
|
||||||
|
aligned_table: "{{ data_for_align | community.general.to_prettytable(column_alignments={'amount': 'right', 'description': 'left', 'date': 'center'}) }}"
|
||||||
|
expected_aligned_table: |-
|
||||||
|
+------------+-----------------+--------+
|
||||||
|
| date | description | amount |
|
||||||
|
+------------+-----------------+--------+
|
||||||
|
| 2023-01-01 | Office supplies | 123.45 |
|
||||||
|
+------------+-----------------+--------+
|
||||||
|
|
||||||
|
- name: Verify aligned table output
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- aligned_table == expected_aligned_table
|
||||||
|
|
||||||
|
# Test combined options
|
||||||
|
- name: Test combined options
|
||||||
|
set_fact:
|
||||||
|
combined_table: "{{ test_data | community.general.to_prettytable(
|
||||||
|
column_order=['role', 'name', 'age'],
|
||||||
|
header_names=['Position', 'Full Name', 'Years'],
|
||||||
|
column_alignments={'role': 'left', 'name': 'center', 'age': 'right'}) }}"
|
||||||
|
expected_combined_table: |-
|
||||||
|
+----------+-----------+-------+
|
||||||
|
| Position | Full Name | Years |
|
||||||
|
+----------+-----------+-------+
|
||||||
|
| admin | Alice | 25 |
|
||||||
|
| user | Bob | 30 |
|
||||||
|
+----------+-----------+-------+
|
||||||
|
|
||||||
|
- name: Verify combined table output
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- combined_table == expected_combined_table
|
||||||
|
|
||||||
|
# Test empty data
|
||||||
|
- name: Test empty data list with no parameters
|
||||||
|
set_fact:
|
||||||
|
empty_table: "{{ [] | community.general.to_prettytable }}"
|
||||||
|
expected_empty_table: |-
|
||||||
|
++
|
||||||
|
++
|
||||||
|
|
||||||
|
- name: Verify empty table output
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- empty_table == expected_empty_table
|
||||||
|
|
||||||
|
# Test empty data with column_order
|
||||||
|
- name: Test empty data list with column_order
|
||||||
|
set_fact:
|
||||||
|
empty_with_columns: "{{ [] | community.general.to_prettytable(column_order=['name', 'age', 'role']) }}"
|
||||||
|
expected_empty_with_columns: |-
|
||||||
|
+------+-----+------+
|
||||||
|
| name | age | role |
|
||||||
|
+------+-----+------+
|
||||||
|
+------+-----+------+
|
||||||
|
|
||||||
|
- name: Verify empty table with column_order
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- empty_with_columns == expected_empty_with_columns
|
||||||
|
|
||||||
|
# Test empty data with header_names
|
||||||
|
- name: Test empty data list with header_names
|
||||||
|
set_fact:
|
||||||
|
empty_with_headers: "{{ [] | community.general.to_prettytable(header_names=['User Name', 'User Age', 'User Role']) }}"
|
||||||
|
expected_empty_with_headers: |-
|
||||||
|
+-----------+----------+-----------+
|
||||||
|
| User Name | User Age | User Role |
|
||||||
|
+-----------+----------+-----------+
|
||||||
|
+-----------+----------+-----------+
|
||||||
|
|
||||||
|
- name: Verify empty table with header_names
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- empty_with_headers == expected_empty_with_headers
|
||||||
|
|
||||||
|
# Test empty data with combined parameters
|
||||||
|
- name: Test empty data with combined parameters
|
||||||
|
set_fact:
|
||||||
|
empty_combined: "{{ [] | community.general.to_prettytable(
|
||||||
|
column_order=['role', 'name', 'age'],
|
||||||
|
header_names=['Position', 'Full Name', 'Years'],
|
||||||
|
column_alignments={'role': 'left', 'name': 'center', 'age': 'right'}) }}"
|
||||||
|
expected_empty_combined: |-
|
||||||
|
+----------+-----------+-------+
|
||||||
|
| Position | Full Name | Years |
|
||||||
|
+----------+-----------+-------+
|
||||||
|
+----------+-----------+-------+
|
||||||
|
|
||||||
|
- name: Verify empty table with combined parameters
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- empty_combined == expected_empty_combined
|
||||||
|
|
||||||
|
# Test validation with empty data
|
||||||
|
- name: Test empty data with non-list column_order (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ [] | community.general.to_prettytable(column_order=123) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for empty data with invalid column_order
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a list of column names, got a int" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test empty data with non-list header_names (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ [] | community.general.to_prettytable(header_names='invalid_headers') }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for empty data with invalid header_names
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a list of header names, got a string" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test empty data with non-dictionary column_alignments (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ [] | community.general.to_prettytable(column_alignments='invalid') }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for empty data with invalid column_alignments
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a dictionary for column_alignments, got a string" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test empty data with non-string values in column_alignments (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ [] | community.general.to_prettytable(column_alignments={'name': 123}) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for empty data with non-string values in column_alignments
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a string for column_alignments value, got a int" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test empty data with invalid alignment value in column_alignments (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ [] | community.general.to_prettytable(column_alignments={'name': 'invalid'}) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for empty data with invalid alignment value
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Invalid alignment 'invalid' in 'column_alignments'" in failure_result.msg
|
||||||
|
- >
|
||||||
|
"Valid alignments are" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test empty data with mismatched column_order and header_names (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ [] | community.general.to_prettytable(column_order=['a', 'b', 'c'], header_names=['X', 'Y']) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for empty data with mismatched lengths
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"'column_order' and 'header_names' must have the same number of elements" in failure_result.msg
|
||||||
|
|
||||||
|
# Test error conditions
|
||||||
|
- name: Test non-list input (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ 'not_a_list' | community.general.to_prettytable }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-list input
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a list of dictionaries, got a string" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test list with non-dictionary items (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ ['not_a_dict', 'also_not_a_dict'] | community.general.to_prettytable }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-dictionary items
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected all items in the list to be dictionaries, got a string" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test non-list column_order (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(column_order=123) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-list column_order
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a list of column names, got a int" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test non-list header_names (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(header_names='invalid_headers') }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-list header_names
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a list of header names, got a string" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test unknown parameters (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(unknown_param='value') }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for unknown parameters
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Unknown parameter(s) for to_prettytable filter: unknown_param" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test both positional args and column_order (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable('role', 'name', column_order=['name', 'age', 'role']) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for using both positional args and column_order
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Cannot use both positional arguments and the 'column_order' keyword argument" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test non-string values in positional args (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable('name', 123, 'role') }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-string values in positional args
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a string for column name, got a int" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test non-string values in column_order (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(column_order=['name', 123, 'role']) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-string values in column_order
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a string for column name, got a int" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test non-string values in header_names (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(header_names=['User Name', 456, 'User Role']) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-string values in header_names
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a string for header name, got a int" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test mismatched sizes of column_order and header_names (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(column_order=['name', 'age', 'role'], header_names=['User Name', 'User Age']) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for mismatched sizes
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"'column_order' and 'header_names' must have the same number of elements" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test column_order with more elements than available fields (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(column_order=['name', 'age', 'role', 'extra_field', 'another_extra']) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for column_order with too many elements
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"'column_order' has more elements" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test header_names with more elements than available fields (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(header_names=['User Name', 'User Age', 'User Role', 'Extra Field', 'Another Extra']) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for header_names with too many elements
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"'header_names' has more elements" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test column_alignments with more elements than available fields (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(column_alignments={'name': 'center', 'age': 'right', 'role': 'left', 'extra': 'center', 'another': 'left'}) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for column_alignments with too many elements
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"'column_alignments' has more elements" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test non-dictionary column_alignments (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(column_alignments='invalid') }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-dictionary column_alignments
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a dictionary for column_alignments, got a string" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test non-string keys in column_alignments (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(column_alignments={123: 'center'}) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-string keys in column_alignments
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a string for column_alignments key, got a int" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test non-string values in column_alignments (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(column_alignments={'name': 123}) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for non-string values in column_alignments
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Expected a string for column_alignments value, got a int" in failure_result.msg
|
||||||
|
|
||||||
|
- name: Test invalid alignment value in column_alignments (expect failure)
|
||||||
|
block:
|
||||||
|
- set_fact:
|
||||||
|
invalid_table: "{{ test_data | community.general.to_prettytable(column_alignments={'name': 'invalid'}) }}"
|
||||||
|
register: failure_result
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify error message for invalid alignment value in column_alignments
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failure_result is failed
|
||||||
|
- >
|
||||||
|
"Invalid alignment 'invalid' in 'column_alignments'" in failure_result.msg
|
||||||
|
- >
|
||||||
|
"Valid alignments are" in failure_result.msg
|
||||||
|
|
||||||
|
# Test using explicit python script to create dictionary with mixed key types
|
||||||
|
- name: Create test data with numeric keys
|
||||||
|
set_fact:
|
||||||
|
mixed_key_data:
|
||||||
|
- name: Alice
|
||||||
|
role: admin
|
||||||
|
1: ID001
|
||||||
|
- name: Bob
|
||||||
|
role: user
|
||||||
|
1: ID002
|
||||||
|
|
||||||
|
- name: Test prettytable with mixed key types
|
||||||
|
set_fact:
|
||||||
|
mixed_key_table: "{{ mixed_key_data | community.general.to_prettytable }}"
|
||||||
|
expected_mixed_key_table: |-
|
||||||
|
+-------+-------+-------+
|
||||||
|
| name | role | 1 |
|
||||||
|
+-------+-------+-------+
|
||||||
|
| Alice | admin | ID001 |
|
||||||
|
| Bob | user | ID002 |
|
||||||
|
+-------+-------+-------+
|
||||||
|
|
||||||
|
- name: Verify mixed key types were handled correctly
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- mixed_key_table == expected_mixed_key_table
|
||||||
|
|
||||||
|
# Test column ordering with numeric keys
|
||||||
|
- name: Test column ordering with numeric keys
|
||||||
|
set_fact:
|
||||||
|
mixed_ordered_table: "{{ mixed_key_data | community.general.to_prettytable(column_order=['1', 'name', 'role']) }}"
|
||||||
|
expected_ordered_numeric_table: |-
|
||||||
|
+-------+-------+-------+
|
||||||
|
| 1 | name | role |
|
||||||
|
+-------+-------+-------+
|
||||||
|
| ID001 | Alice | admin |
|
||||||
|
| ID002 | Bob | user |
|
||||||
|
+-------+-------+-------+
|
||||||
|
|
||||||
|
- name: Verify column ordering with numeric keys
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- mixed_ordered_table == expected_ordered_numeric_table
|
||||||
|
|
||||||
|
# Test custom headers with numeric keys
|
||||||
|
- name: Test custom headers with numeric keys
|
||||||
|
set_fact:
|
||||||
|
mixed_headers_table: "{{ mixed_key_data | community.general.to_prettytable(header_names=['Name', 'Role', 'ID']) }}"
|
||||||
|
expected_headers_numeric_table: |-
|
||||||
|
+-------+-------+-------+
|
||||||
|
| Name | Role | ID |
|
||||||
|
+-------+-------+-------+
|
||||||
|
| Alice | admin | ID001 |
|
||||||
|
| Bob | user | ID002 |
|
||||||
|
+-------+-------+-------+
|
||||||
|
|
||||||
|
- name: Verify custom headers with numeric keys
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- mixed_headers_table == expected_headers_numeric_table
|
||||||
|
|
||||||
|
# Test column alignments with numeric keys
|
||||||
|
- name: Test column alignments with numeric keys
|
||||||
|
set_fact:
|
||||||
|
mixed_aligned_table: "{{ mixed_key_data | community.general.to_prettytable(column_alignments={'1': 'right', 'name': 'left', 'role': 'center'}) }}"
|
||||||
|
expected_aligned_numeric_table: |-
|
||||||
|
+-------+-------+-------+
|
||||||
|
| name | role | 1 |
|
||||||
|
+-------+-------+-------+
|
||||||
|
| Alice | admin | ID001 |
|
||||||
|
| Bob | user | ID002 |
|
||||||
|
+-------+-------+-------+
|
||||||
|
|
||||||
|
- name: Verify column alignments with numeric keys
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- mixed_aligned_table == expected_aligned_numeric_table
|
||||||
|
|
||||||
|
# Test with boolean-like string keys
|
||||||
|
- name: Create test data with boolean-like string keys
|
||||||
|
set_fact:
|
||||||
|
boolean_data:
|
||||||
|
- name: Alice
|
||||||
|
role: admin
|
||||||
|
true: 'Yes'
|
||||||
|
false: 'No'
|
||||||
|
- name: Bob
|
||||||
|
role: user
|
||||||
|
true: 'No'
|
||||||
|
false: 'Yes'
|
||||||
|
|
||||||
|
- name: Test prettytable with boolean-like string keys
|
||||||
|
set_fact:
|
||||||
|
bool_table: "{{ boolean_data | community.general.to_prettytable }}"
|
||||||
|
expected_bool_table: |-
|
||||||
|
+-------+-------+------+-------+
|
||||||
|
| name | role | True | False |
|
||||||
|
+-------+-------+------+-------+
|
||||||
|
| Alice | admin | Yes | No |
|
||||||
|
| Bob | user | No | Yes |
|
||||||
|
+-------+-------+------+-------+
|
||||||
|
|
||||||
|
- name: Verify boolean-like keys were handled correctly
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- bool_table == expected_bool_table
|
||||||
|
|
||||||
|
# Test that column_order with capitalized boolean names works via case-insensitive matching
|
||||||
|
- name: Test column ordering with capitalized boolean names
|
||||||
|
set_fact:
|
||||||
|
bool_ordered_table: "{{ boolean_data | community.general.to_prettytable(column_order=['True', 'False', 'name', 'role']) }}"
|
||||||
|
expected_bool_ordered_table: |-
|
||||||
|
+------+-------+-------+-------+
|
||||||
|
| True | False | name | role |
|
||||||
|
+------+-------+-------+-------+
|
||||||
|
| Yes | No | Alice | admin |
|
||||||
|
| No | Yes | Bob | user |
|
||||||
|
+------+-------+-------+-------+
|
||||||
|
|
||||||
|
- name: Verify that 'True' in column_order works with 'true' keys
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- bool_ordered_table == expected_bool_ordered_table
|
||||||
|
|
||||||
|
# Test column alignments with boolean-like string keys
|
||||||
|
- name: Test column alignments with boolean-like string keys
|
||||||
|
set_fact:
|
||||||
|
bool_aligned_table: "{{ boolean_data | community.general.to_prettytable(column_alignments={'true': 'right', 'false': 'center', 'name': 'left'}) }}"
|
||||||
|
expected_bool_aligned_table: |-
|
||||||
|
+-------+-------+------+-------+
|
||||||
|
| name | role | True | False |
|
||||||
|
+-------+-------+------+-------+
|
||||||
|
| Alice | admin | Yes | No |
|
||||||
|
| Bob | user | No | Yes |
|
||||||
|
+-------+-------+------+-------+
|
||||||
|
|
||||||
|
- name: Verify column alignments with boolean-like string keys
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- bool_aligned_table == expected_bool_aligned_table
|
Loading…
Add table
Add a link
Reference in a new issue