This commit is contained in:
Timur Gadiev 2025-04-01 21:21:06 +04:00 committed by GitHub
commit 6ffbebe90c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 873 additions and 0 deletions

6
.github/BOTMETA.yml vendored
View file

@ -15,6 +15,8 @@ files:
labels: action
$actions/iptables_state.py:
maintainers: quidame
$actions/prettytable.py:
maintainers: tgadiev
$actions/shutdown.py:
maintainers: nitzmahone samdoran aminvakil
$becomes/:
@ -208,6 +210,8 @@ files:
maintainers: resmo
$filters/to_months.yml:
maintainers: resmo
$filters/to_prettytable.py:
maintainers: tgadiev
$filters/to_seconds.yml:
maintainers: resmo
$filters/to_time_unit.yml:
@ -1133,6 +1137,8 @@ files:
keywords: doas dragonfly freebsd iocage jail netbsd openbsd opnsense pfsense
labels: bsd portinstall
maintainers: $team_bsd berenddeboer
$modules/prettytable.py:
maintainers: tgadiev
$modules/pritunl_:
maintainers: Lowess
$modules/profitbricks:

View file

@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, quidame <quidame@poivron.org>
# 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 annotations
from typing import Any, Dict, List, Optional
__metaclass__ = type # pylint: disable=C0103
from ansible.errors import AnsibleError, AnsibleUndefinedVariable
from ansible.module_utils._text import to_text
from ansible.plugins.action import ActionBase
try:
import prettytable
HAS_PRETTYTABLE = True
except ImportError:
HAS_PRETTYTABLE = False
class ActionModule(ActionBase):
"""Print prettytable from list of dicts."""
TRANSFERS_FILES = False
_VALID_ARGS = frozenset(
("data", "column_order", "header_names", "column_alignments")
)
_VALID_ALIGNMENTS = {"left", "center", "right", "l", "c", "r"}
def run(
self, tmp: Optional[str] = None, task_vars: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars) # pylint: disable=R1725
del tmp # tmp no longer has any effect
try:
self._handle_options()
if not HAS_PRETTYTABLE:
raise AnsibleError(
'You need to install "prettytable" python module before running this module'
)
data = self._task.args.get("data")
if data is None: # Only check for None, allow empty list
raise AnsibleError("The 'data' parameter is required")
if not isinstance(data, list):
raise AnsibleError(
"Expected a list of dictionaries, got a string"
if isinstance(data, (str, bytes))
else f"Expected a list of dictionaries, got {type(data).__name__}"
)
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))
raise AnsibleError(
"All items in the list must be dictionaries, got a string"
if isinstance(invalid_item, (str, bytes))
else f"All items in the list must be dictionaries, got {type(invalid_item).__name__}"
)
# Special handling for empty data
if not data:
result["pretty_table"] = "++\n++"
else:
table = self._create_table(data)
result["pretty_table"] = to_text(table)
result["_ansible_verbose_always"] = True
result["failed"] = False
except (AnsibleError, AnsibleUndefinedVariable) as e:
result["failed"] = True
result["msg"] = str(e)
return result
def _handle_options(self) -> None:
"""Validate module arguments."""
argument_spec = {
"data": {"type": "list", "required": True},
"column_order": {"type": "list", "elements": "str"},
"header_names": {"type": "list", "elements": "str"},
"column_alignments": {"type": "dict"},
}
self._options_context = self.validate_argument_spec(argument_spec)
def _create_table(self, data: List[Dict[str, Any]]) -> prettytable.PrettyTable:
"""Create and configure the prettytable instance.
Args:
data: List of dictionaries to format into a table
Returns:
Configured PrettyTable instance
"""
table = prettytable.PrettyTable()
# Determine field names from data or column_order
field_names = self._task.args.get("column_order") or list(data[0].keys())
# Set headers
header_names = self._task.args.get("header_names")
table.field_names = header_names if header_names else field_names
# Configure alignments
self._configure_alignments(table, field_names)
# Add rows - use add_row instead of add_rows for compatibility with older versions
rows = [[item.get(col, "") for col in field_names] for item in data]
for row in rows:
table.add_row(row)
return table
def _configure_alignments(
self, table: prettytable.PrettyTable, field_names: List[str]
) -> None:
"""Configure column alignments for the table.
Args:
table: The PrettyTable instance to configure
field_names: List of field names to align
"""
column_alignments = self._task.args.get("column_alignments", {})
if not isinstance(column_alignments, dict):
return
for col_name, alignment in column_alignments.items():
if col_name in field_names:
alignment = alignment.lower()
if alignment in self._VALID_ALIGNMENTS:
table.align[col_name] = alignment[0]
else:
self._display.warning(
f"Ignored invalid alignment '{alignment}' for column '{col_name}'. "
f"Valid alignments are {list(self._VALID_ALIGNMENTS)}"
)

View file

@ -0,0 +1,183 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2025, Timur Gadiev <timur@example.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)
__metaclass__ = type
DOCUMENTATION = '''
name: to_prettytable
short_description: Format a list of dictionaries as an ASCII table
version_added: "8.0.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 of column alignments. Keys are column names, values are alignments.
type: dictionary
suboptions:
alignment:
description: Alignment for the column. Must be one of C(left), C(center), C(right), C(l), C(c), or C(r).
type: string
choices: [left, center, right, l, c, r]
'''
EXAMPLES = '''
- name: Display a list of users as a table
vars:
users:
- name: Alice
age: 25
role: admin
- name: Bob
age: 30
role: user
debug:
msg: "{{ users | community.general.to_prettytable }}"
- name: Display a table with custom column ordering
debug:
msg: "{{ users | community.general.to_prettytable('role', 'name', 'age') }}"
- name: Display a table with custom headers
debug:
msg: "{{ users | community.general.to_prettytable(header_names=['User Name', 'User Age', 'User Role']) }}"
- name: Display a table with custom alignments
debug:
msg: "{{ users | community.general.to_prettytable(column_alignments={'name': 'center', 'age': 'right', 'role': 'left'}) }}"
- name: Combine multiple options
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
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'
)
if not isinstance(data, list):
raise AnsibleFilterError(
"Expected a list of dictionaries, got a string"
if isinstance(data, string_types)
else f"Expected a list of dictionaries, got {type(data).__name__}"
)
# Handle empty data
if not data:
return "++\n++"
# Check that all items are dictionaries
if not all(isinstance(item, dict) for item in data):
invalid_item = next(item for item in data if not isinstance(item, dict))
raise AnsibleFilterError(
"All items in the list must be dictionaries, got a string"
if isinstance(invalid_item, string_types)
else f"All items in the list must be dictionaries, got {type(invalid_item).__name__}"
)
# Handle positional argument column order
column_order = kwargs.get('column_order', None)
if args and not column_order:
column_order = list(args)
# Create the table and configure it
table = prettytable.PrettyTable()
# Determine field names
field_names = column_order or list(data[0].keys())
# Set headers
header_names = kwargs.get('header_names', None)
table.field_names = header_names if header_names else field_names
# Configure alignments
_configure_alignments(table, field_names, kwargs.get('column_alignments', {}))
# Add rows - use add_row instead of add_rows for compatibility with older versions
rows = [[item.get(col, "") for col in field_names] for item in data]
for row in rows:
table.add_row(row)
return to_text(table)
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:
alignment = alignment.lower()
if alignment in valid_alignments:
table.align[col_name] = alignment[0]
class FilterModule(object):
"""Ansible core jinja2 filters."""
def filters(self):
return {
'to_prettytable': to_prettytable
}

View file

@ -0,0 +1,145 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2020, 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
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: prettytable
short_description: Format data into an ASCII table using prettytable
version_added: '10.6.0'
author:
- Timur Gadiev (@tgadiev)
extends_documentation_fragment:
- action_common_attributes
- action_common_attributes.conn
- action_common_attributes.flow
- community.general.attributes
- community.general.attributes.flow
description:
- This module formats a list of dictionaries into an ASCII table using the B(prettytable) Python library.
- Useful for creating human-readable output from structured data.
- Allows customization of column order, headers, and alignments.
- The table is created with a clean, readable format suitable for terminal output.
requirements:
- prettytable
attributes:
action:
support: full
async:
support: none
become:
support: none
bypass_host_loop:
support: none
check_mode:
support: full
connection:
support: none
delegation:
support: none
details: This is a pure action plugin that runs entirely on the controller. Delegation has no effect as no tasks are executed on remote hosts.
diff_mode:
support: none
platform:
support: full
platforms: all
options:
data:
description:
- List of dictionaries to format into a table.
- Each dictionary in the list represents a row in the table.
- Dictionary keys become column headers unless overridden by I(header_names).
- If the list is empty, an empty table will be created.
- All items in the list must be dictionaries.
type: list
elements: dict
required: true
column_order:
description:
- List of dictionary keys specifying the order of columns in the output table.
- If not specified, uses the keys from the first dictionary in the input list.
- Only the columns specified in this list will be included in the table.
- Keys must exist in the input dictionaries.
type: list
elements: str
required: false
header_names:
description:
- List of custom header names for the columns.
- Must match the length of columns being displayed.
- If not specified, uses the dictionary keys or I(column_order) values as headers.
- Use this to provide more readable or formatted column headers.
type: list
elements: str
required: false
column_alignments:
description:
- Dictionary mapping column names to their alignment.
- Keys should be column names (either from input data or I(column_order)).
- Values must be one of 'left', 'center', 'right' (or 'l', 'c', 'r').
- Invalid alignment values will be ignored with a warning.
- Columns not specified default to left alignment.
- Alignments for non-existent columns are ignored.
type: dict
required: false
notes:
- This is an action plugin, meaning the plugin executes on the controller, rather than on the target host.
- The prettytable Python library must be installed on the controller.
- Column alignments are case-insensitive.
- Missing values in input dictionaries are displayed as empty strings.
seealso:
- module: ansible.builtin.debug
'''
EXAMPLES = r'''
# Basic usage with a list of dictionaries
- name: Create a table from user data
community.general.prettytable:
data:
- name: Alice
age: 25
role: admin
- name: Bob
age: 30
role: user
# Specify column order and custom headers
- name: Create a formatted table with custom headers
community.general.prettytable:
data:
- name: Alice
age: 25
role: admin
- name: Bob
age: 30
role: user
column_order:
- name
- role
- age
header_names:
- "User Name"
- "User Role"
- "User Age"
# Set column alignments for better number and text formatting
- name: Create table with specific alignments
community.general.prettytable:
data:
- date: "2023-01-01"
description: "Office supplies"
amount: 123.45
- date: "2023-01-02"
description: "Software license"
amount: 500.00
column_alignments:
amount: right # Numbers right-aligned
description: left # Text left-aligned
date: center # Dates centered
'''

View 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

View file

@ -0,0 +1,130 @@
---
####################################################################
# 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)
# 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 }}'
- name: Verify basic table output
assert:
that:
- basic_table | trim is defined
- basic_table | trim is not none
# Test column ordering
- name: Test column ordering
set_fact:
ordered_table: "{{ test_data | community.general.to_prettytable(column_order=['role', 'name', 'age']) }}"
- name: Verify ordered table output
assert:
that:
- ordered_table | trim is defined
- ordered_table | trim is not none
- ordered_table | trim != basic_table | trim
# 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']) }}"
- name: Verify custom headers output
assert:
that:
- headers_table | trim is defined
- headers_table | trim is not none
- "headers_table | trim is search('User Name')"
- "headers_table | trim is search('User Age')"
- "headers_table | trim is search('User Role')"
# 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'}) }}"
- name: Verify aligned table output
assert:
that:
- aligned_table | trim is defined
- aligned_table | trim is not none
# 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={'name': 'center', 'age': 'right', 'role': 'left'}) }}"
- name: Verify combined table output
assert:
that:
- combined_table | trim is defined
- combined_table | trim is not none
- "combined_table | trim is search('Position')"
- "combined_table | trim is search('Full Name')"
- "combined_table | trim is search('Years')"
# Test empty data
- name: Test empty data list
set_fact:
empty_table: "{{ [] | community.general.to_prettytable }}"
- name: Verify empty table output
assert:
that:
- empty_table | trim == "++\n++" | trim
# 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
- "'All items in the list must be dictionaries' in failure_result.msg"

View 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

View file

@ -0,0 +1,9 @@
# 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
+------------+-----------------+--------+
| date | description | amount |
+------------+-----------------+--------+
| 2023-01-01 | Office supplies | 123.45 |
+------------+-----------------+--------+

View file

@ -0,0 +1,10 @@
# 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
+-------+-----+-------+
| name | age | role |
+-------+-----+-------+
| Alice | 25 | admin |
| Bob | 30 | user |
+-------+-----+-------+

View file

@ -0,0 +1,9 @@
# 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
+-----------+----------+-----------+
| User Name | User Age | User Role |
+-----------+----------+-----------+
| Alice | 25 | admin |
+-----------+----------+-----------+

View file

@ -0,0 +1,6 @@
# 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
++
++

View file

@ -0,0 +1,10 @@
# 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
+-------+-------+-----+
| role | name | age |
+-------+-------+-----+
| admin | Alice | 25 |
| user | Bob | 30 |
+-------+-------+-----+

View file

@ -0,0 +1,4 @@
---
# Copyright (c) 2020, Pavlo Bashynskyi (@levonet) <levonet@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

View file

@ -0,0 +1,205 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
# Copyright (c) 2020, Pavlo Bashynskyi (@levonet) <levonet@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
# Test basic functionality
- name: Test basic table creation
prettytable:
data:
- name: Alice
age: 25
role: admin
- name: Bob
age: 30
role: user
register: basic_result
- name: Load expected basic table output
set_fact:
expected_basic_table_raw: "{{ lookup('file', 'basic_table.txt') }}"
- name: Remove copyright header from expected output
set_fact:
expected_basic_table: "{{ expected_basic_table_raw.split('\n')[4:] | join('\n') }}"
- name: Verify basic table output
assert:
that:
- basic_result.pretty_table | trim == expected_basic_table | trim
- not basic_result.failed
# Test column ordering
- name: Test table with custom column order
prettytable:
data:
- name: Alice
age: 25
role: admin
- name: Bob
age: 30
role: user
column_order:
- role
- name
- age
register: ordered_result
- name: Load expected ordered table output
set_fact:
expected_ordered_table_raw: "{{ lookup('file', 'ordered_table.txt') }}"
- name: Remove copyright header from expected output
set_fact:
expected_ordered_table: "{{ expected_ordered_table_raw.split('\n')[4:] | join('\n') }}"
- name: Verify ordered table output
assert:
that:
- ordered_result.pretty_table | trim == expected_ordered_table | trim
- not ordered_result.failed
# Test custom headers
- name: Test table with custom headers
prettytable:
data:
- name: Alice
age: 25
role: admin
header_names:
- "User Name"
- "User Age"
- "User Role"
register: headers_result
- name: Load expected headers table output
set_fact:
expected_headers_table_raw: "{{ lookup('file', 'custom_headers.txt') }}"
- name: Remove copyright header from expected output
set_fact:
expected_headers_table: "{{ expected_headers_table_raw.split('\n')[4:] | join('\n') }}"
- name: Verify custom headers output
assert:
that:
- headers_result.pretty_table | trim == expected_headers_table | trim
- not headers_result.failed
# Test column alignments
- name: Test table with alignments
prettytable:
data:
- date: "2023-01-01"
description: "Office supplies"
amount: 123.45
column_alignments:
amount: right
description: left
date: center
register: aligned_result
- name: Load expected aligned table output
set_fact:
expected_aligned_table_raw: "{{ lookup('file', 'aligned_table.txt') }}"
- name: Remove copyright header from expected output
set_fact:
expected_aligned_table: "{{ expected_aligned_table_raw.split('\n')[4:] | join('\n') }}"
- name: Verify aligned table output
assert:
that:
- aligned_result.pretty_table | trim == expected_aligned_table | trim
- not aligned_result.failed
# Test error conditions
- name: Test missing data parameter (should fail)
prettytable: {} # Empty dict to test missing required parameter
register: missing_data_result
ignore_errors: true
- name: Verify error for missing data
vars:
expected_msg: 'missing required arguments: data'
assert:
that:
- missing_data_result.failed
- expected_msg in missing_data_result.msg
- name: Test invalid data type (should fail)
prettytable:
data: "this is a string"
register: invalid_data_result
ignore_errors: true
- name: Verify error for invalid data
vars:
expected_msg: "Expected a list of dictionaries, got a string"
assert:
that:
- invalid_data_result.failed
- expected_msg == invalid_data_result.msg
- name: Test list with invalid items (should fail)
prettytable:
data:
- {"valid": "dict"}
- "invalid string item"
register: invalid_items_result
ignore_errors: true
- name: Verify error for invalid items
vars:
expected_msg: "All items in the list must be dictionaries, got a string"
assert:
that:
- invalid_items_result.failed
- expected_msg == invalid_items_result.msg
# Test empty data
- name: Test empty data list
prettytable:
data: []
register: empty_result
- name: Load expected empty table output
set_fact:
expected_empty_table_raw: "{{ lookup('file', 'empty_table.txt') }}"
- name: Remove copyright header from expected output
set_fact:
expected_empty_table: "{{ expected_empty_table_raw.split('\n')[4:] | join('\n') }}"
- name: Verify empty table output
assert:
that:
- empty_result.pretty_table | trim == expected_empty_table | trim
- not empty_result.failed
# Test invalid alignments
- name: Test invalid alignment value
prettytable:
data:
- col1: value1
col2: value2
column_alignments:
col1: invalid
register: invalid_alignment_result
- name: Verify invalid alignment handling
assert:
that:
- not invalid_alignment_result.failed # Should not fail, just warn