From 77693f17db8ed64667f935bb01a2c5040718e061 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 09:29:47 -0400 Subject: [PATCH 01/12] Created simple example of using rich tables --- examples/rich_tables.py | 160 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100755 examples/rich_tables.py diff --git a/examples/rich_tables.py b/examples/rich_tables.py new file mode 100755 index 000000000..2e7a63314 --- /dev/null +++ b/examples/rich_tables.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""An example of using Rich Tables within a cmd2 application for displaying tabular data. + +While you can use any Python library for displaying tabular data within a cmd2 application, +we recommend using rich since that is built into cmd2. + +Data comes from World Population Review: https://worldpopulationreview.com/ +""" + +from rich.table import Table + +import cmd2 + +CITY_HEADERS = ['Country Flag', 'City', 'Country', '2025 Population'] + +CITY_DATA = [ + [ + "🇯🇵", + "Tokyo (東京)", + "Japan", + 37_036_200, + ], + [ + "🇮🇳", + "Delhi (दिल्ली)", + "India", + 34_665_600, + ], + [ + "🇨🇳", + "Shanghai (上海)", + "China", + 30_482_100, + ], + [ + "🇧🇩", + "Dhaka (ঢাকা)", + "Bangladesh", + 24_652_900, + ], + [ + "🇪🇬", + "Cairo (القاهرة)", + "Egypt", + 23_074_200, + ], + [ + "🇪🇬", + "São Paulo", + "Brazil", + 22_990_000, + ], +] + +COUNTRY_HEADERS = ['Flag', 'Country', '2025 Population', 'Area (M km^2)', 'Density (/km^2)'] + +COUNTRY_DATA = [ + [ + "🇮🇳", + "India", + 1_463_870_000, + 3.3, + 492, + ], + [ + "🇨🇳", + "China", + 1_416_100_000, + 9.7, + 150, + ], + [ + "🇺🇸", + "United States", + 347_276_000, + 9.4, + 38, + ], + [ + "🇮🇩", + "Indonesia", + 285_721_000, + 1.9, + 152, + ], + [ + "🇵🇰", + "Pakistan", + 255_220_000, + 0.9, + 331, + ], + [ + "🇳🇬", + "Nigeria", + 237_528_000, + 0.9, + 261, + ], +] + + +class TableApp(cmd2.Cmd): + """Cmd2 application to demonstrate displaying tabular data using rich.""" + + TABLE_CATEGORY = 'Table Commands' + + def __init__(self) -> None: + """Initialize the cmd2 application.""" + super().__init__() + + # Prints an intro banner once upon application startup + self.intro = 'Are you curious which countries and cities on Earth have the largest populations?' + + # Set the default category name + self.default_category = 'cmd2 Built-in Commands' + + @cmd2.with_category(TABLE_CATEGORY) + def do_cities(self, _: cmd2.Statement) -> None: + """Display the cities with the largest population.""" + table = Table(show_footer=False) + table.title = "Largest Cities by Population 2025" + table.caption = "Data from https://worldpopulationreview.com/" + + for header in CITY_HEADERS: + table.add_column(header) + + for row in CITY_DATA: + # Convert integers or floats to strings, since rich tables can not render int/float + str_row = [f"{item:,}" if isinstance(item, int) else str(item) for item in row] + table.add_row(*str_row) + + self.poutput(table) + + @cmd2.with_category(TABLE_CATEGORY) + def do_countries(self, _: cmd2.Statement) -> None: + """Display the countries with the largest population.""" + table = Table(show_footer=False) + table.title = "Largest Countries by Population 2025" + table.caption = "Data from https://worldpopulationreview.com/" + + for header in COUNTRY_HEADERS: + justify = "left" + if 'Population' in header or 'Density' in header: + justify = "right" + if 'Area' in header: + justify = "center" + table.add_column(header, justify=justify) + + for row in COUNTRY_DATA: + # Convert integers or floats to strings, since rich tables can not render int/float + str_row = [f"{item:,}" if isinstance(item, int) else str(item) for item in row] + table.add_row(*str_row) + + self.poutput(table) + + +if __name__ == '__main__': + app = TableApp() + app.cmdloop() From 76dad364527ee45d68d6f530ad132a08250936d0 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 09:30:45 -0400 Subject: [PATCH 02/12] Remove use of Hindi and Bengali text because rich screws up the width --- examples/rich_tables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index 2e7a63314..569e6556c 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -22,7 +22,7 @@ ], [ "🇮🇳", - "Delhi (दिल्ली)", + "Delhi", "India", 34_665_600, ], @@ -34,7 +34,7 @@ ], [ "🇧🇩", - "Dhaka (ঢাকা)", + "Dhaka", "Bangladesh", 24_652_900, ], From e39153f7d8f224fd0dd21ce4a70ff05b6c1bc444 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 11:14:28 -0400 Subject: [PATCH 03/12] Improved country data --- examples/rich_tables.py | 115 +++++++++++----------------------------- 1 file changed, 30 insertions(+), 85 deletions(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index 569e6556c..b42c37954 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -5,98 +5,43 @@ we recommend using rich since that is built into cmd2. Data comes from World Population Review: https://worldpopulationreview.com/ +and https://en.wikipedia.org/wiki/List_of_countries_by_GDP_(nominal) """ from rich.table import Table import cmd2 -CITY_HEADERS = ['Country Flag', 'City', 'Country', '2025 Population'] - +CITY_HEADERS = ['Flag', 'City', 'Country', '2025 Population'] CITY_DATA = [ - [ - "🇯🇵", - "Tokyo (東京)", - "Japan", - 37_036_200, - ], - [ - "🇮🇳", - "Delhi", - "India", - 34_665_600, - ], - [ - "🇨🇳", - "Shanghai (上海)", - "China", - 30_482_100, - ], - [ - "🇧🇩", - "Dhaka", - "Bangladesh", - 24_652_900, - ], - [ - "🇪🇬", - "Cairo (القاهرة)", - "Egypt", - 23_074_200, - ], - [ - "🇪🇬", - "São Paulo", - "Brazil", - 22_990_000, - ], + ["🇯🇵", "Tokyo (東京)", "Japan", 37_036_200], + ["🇮🇳", "Delhi", "India", 34_665_600], + ["🇨🇳", "Shanghai (上海)", "China", 30_482_100], + ["🇧🇩", "Dhaka", "Bangladesh", 24_652_900], + ["🇪🇬", "Cairo (القاهرة)", "Egypt", 23_074_200], + ["🇪🇬", "São Paulo", "Brazil", 22_990_000], ] -COUNTRY_HEADERS = ['Flag', 'Country', '2025 Population', 'Area (M km^2)', 'Density (/km^2)'] - +COUNTRY_HEADERS = [ + 'Flag', + 'Country', + '2025 Population', + 'Area (M km^2)', + 'Density (/km^2)', + 'GDP (million US$)', + 'GDP per capita (US$)', +] COUNTRY_DATA = [ - [ - "🇮🇳", - "India", - 1_463_870_000, - 3.3, - 492, - ], - [ - "🇨🇳", - "China", - 1_416_100_000, - 9.7, - 150, - ], - [ - "🇺🇸", - "United States", - 347_276_000, - 9.4, - 38, - ], - [ - "🇮🇩", - "Indonesia", - 285_721_000, - 1.9, - 152, - ], - [ - "🇵🇰", - "Pakistan", - 255_220_000, - 0.9, - 331, - ], - [ - "🇳🇬", - "Nigeria", - 237_528_000, - 0.9, - 261, - ], + ["🇮🇳", "India", 1_463_870_000, 3.3, 492, 4_187_017, 2_878], + ["🇨🇳", "China (中国)", 1_416_100_000, 9.7, 150, 19_231_705, 13_687], + ["🇺🇸", "United States", 347_276_000, 9.4, 38, 30_507_217, 89_105], + ["🇮🇩", "Indonesia", 285_721_000, 1.9, 152, 1_429_743, 5_027], + ["🇵🇰", "Pakistan", 255_220_000, 0.9, 331, 373_072, 1_484], + ["🇳🇬", "Nigeria", 237_528_000, 0.9, 261, 188_271, 807], + ["🇧🇷", "Brazil", 212_812_000, 8.5, 25, 2_125_958, 9_964], + ["🇧🇩", "Bangladesh", 175_687_000, 0.1, 1_350, 467_218, 2_689], + ["🇷🇺", "Russia (россия)", 143_997_000, 17.1, 9, 2_076_396, 14_258], + ["🇪🇹", "Ethiopia (እትዮጵያ)", 135_472_000, 1.1, 120, 117_457, 1_066], ] @@ -136,12 +81,12 @@ def do_cities(self, _: cmd2.Statement) -> None: def do_countries(self, _: cmd2.Statement) -> None: """Display the countries with the largest population.""" table = Table(show_footer=False) - table.title = "Largest Countries by Population 2025" - table.caption = "Data from https://worldpopulationreview.com/" + table.title = "10 Largest Countries by Population 2025" + table.caption = "Data from https://worldpopulationreview.com/ and Wikipedia" for header in COUNTRY_HEADERS: justify = "left" - if 'Population' in header or 'Density' in header: + if any(term in header for term in ['Population', 'Density', 'GDP']): justify = "right" if 'Area' in header: justify = "center" From 8090ee4df59135be6d69252b48a0e50e0a0fd616 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 11:19:51 -0400 Subject: [PATCH 04/12] Improved city data --- examples/rich_tables.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index b42c37954..1e1e37d0d 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -20,6 +20,10 @@ ["🇧🇩", "Dhaka", "Bangladesh", 24_652_900], ["🇪🇬", "Cairo (القاهرة)", "Egypt", 23_074_200], ["🇪🇬", "São Paulo", "Brazil", 22_990_000], + ["🇲🇽", "Mexico City", "Mexico", 22_752_400], + ["🇨🇳", "Beijing (北京)", "China", 22_596_500], + ["🇮🇳", "Mumbai", "India", 22_089_000], + ["🇯🇵", "Osaka (大阪)", "Japan", 18_921_600], ] COUNTRY_HEADERS = [ @@ -64,7 +68,7 @@ def __init__(self) -> None: def do_cities(self, _: cmd2.Statement) -> None: """Display the cities with the largest population.""" table = Table(show_footer=False) - table.title = "Largest Cities by Population 2025" + table.title = "10 Largest Cities by Population 2025" table.caption = "Data from https://worldpopulationreview.com/" for header in CITY_HEADERS: From a821cb0fdf6aa803e43a686855848940f4bfa716 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 11:41:36 -0400 Subject: [PATCH 05/12] Added color to some columns for illustrative purposes --- examples/rich_tables.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index 1e1e37d0d..897e64a4e 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -31,7 +31,7 @@ 'Country', '2025 Population', 'Area (M km^2)', - 'Density (/km^2)', + 'Population Density (/km^2)', 'GDP (million US$)', 'GDP per capita (US$)', ] @@ -101,6 +101,18 @@ def do_countries(self, _: cmd2.Statement) -> None: str_row = [f"{item:,}" if isinstance(item, int) else str(item) for item in row] table.add_row(*str_row) + # Make Population column blue + table.columns[2].header_style = "bold blue" + table.columns[2].style = "blue" + + # Make Density column red + table.columns[4].header_style = "bold red" + table.columns[4].style = "red" + + # Make GDB per capita column green + table.columns[6].header_style = "bold green" + table.columns[6].style = "green" + self.poutput(table) From 1992ccc3ccc5926bbe07c3839332e3c65419bad2 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 11:43:58 -0400 Subject: [PATCH 06/12] Use colors from cmd2.colors.Color --- examples/rich_tables.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index 897e64a4e..c99fcbcfb 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -11,6 +11,7 @@ from rich.table import Table import cmd2 +from cmd2.colors import Color CITY_HEADERS = ['Flag', 'City', 'Country', '2025 Population'] CITY_DATA = [ @@ -102,16 +103,16 @@ def do_countries(self, _: cmd2.Statement) -> None: table.add_row(*str_row) # Make Population column blue - table.columns[2].header_style = "bold blue" - table.columns[2].style = "blue" + table.columns[2].header_style = Color.BRIGHT_BLUE + table.columns[2].style = Color.BLUE # Make Density column red - table.columns[4].header_style = "bold red" - table.columns[4].style = "red" + table.columns[4].header_style = Color.BRIGHT_RED + table.columns[4].style = Color.RED # Make GDB per capita column green - table.columns[6].header_style = "bold green" - table.columns[6].style = "green" + table.columns[6].header_style = Color.BRIGHT_GREEN + table.columns[6].style = Color.GREEN self.poutput(table) From 55fcbdadd40379d13ca6bf2e6164951eb995ff38 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 11:53:17 -0400 Subject: [PATCH 07/12] Use match/case for per-column table styling --- examples/rich_tables.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index c99fcbcfb..629cddb30 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -90,30 +90,31 @@ def do_countries(self, _: cmd2.Statement) -> None: table.caption = "Data from https://worldpopulationreview.com/ and Wikipedia" for header in COUNTRY_HEADERS: - justify = "left" - if any(term in header for term in ['Population', 'Density', 'GDP']): - justify = "right" - if 'Area' in header: - justify = "center" - table.add_column(header, justify=justify) + match header: + case s if "2025 Population" in s: + justify = "right" + header_style = Color.BRIGHT_BLUE + style = Color.BLUE + case s if "Density" in s: + justify = "right" + header_style = Color.BRIGHT_RED + style = Color.RED + case s if "per capita" in s: + justify = "right" + header_style = Color.BRIGHT_GREEN + style = Color.GREEN + case _: + justify = "left" + style = None + header_style = None + + table.add_column(header, justify=justify, header_style=header_style, style=style) for row in COUNTRY_DATA: # Convert integers or floats to strings, since rich tables can not render int/float str_row = [f"{item:,}" if isinstance(item, int) else str(item) for item in row] table.add_row(*str_row) - # Make Population column blue - table.columns[2].header_style = Color.BRIGHT_BLUE - table.columns[2].style = Color.BLUE - - # Make Density column red - table.columns[4].header_style = Color.BRIGHT_RED - table.columns[4].style = Color.RED - - # Make GDB per capita column green - table.columns[6].header_style = Color.BRIGHT_GREEN - table.columns[6].style = Color.GREEN - self.poutput(table) From 93fc6416c1d05b10d7e5eaa98dbaefaa37b413fd Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 12:01:10 -0400 Subject: [PATCH 08/12] Fix match-case so table formatting is as desired --- examples/rich_tables.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index 629cddb30..57377700f 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -90,23 +90,27 @@ def do_countries(self, _: cmd2.Statement) -> None: table.caption = "Data from https://worldpopulationreview.com/ and Wikipedia" for header in COUNTRY_HEADERS: + header_style = None + style = None match header: - case s if "2025 Population" in s: + case population if "2025 Population" in population: justify = "right" header_style = Color.BRIGHT_BLUE style = Color.BLUE - case s if "Density" in s: + case density if "Density" in density: justify = "right" header_style = Color.BRIGHT_RED style = Color.RED - case s if "per capita" in s: + case percap if "per capita" in percap: justify = "right" header_style = Color.BRIGHT_GREEN style = Color.GREEN + case area if 'Area' in area: + justify = "right" + case gdp if 'GDP' in gdp: + justify = "right" case _: justify = "left" - style = None - header_style = None table.add_column(header, justify=justify, header_style=header_style, style=style) From a52242d0f243601f906bb90f27ac9fe039079305 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 12:03:17 -0400 Subject: [PATCH 09/12] Add an example of center-justifying one column --- examples/rich_tables.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index 57377700f..4de8e4633 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -109,6 +109,8 @@ def do_countries(self, _: cmd2.Statement) -> None: justify = "right" case gdp if 'GDP' in gdp: justify = "right" + case flag if 'Flag' in flag: + justify = "center" case _: justify = "left" From 3e21e7a4bf3a6e159f5e26e181c573e829073cb4 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 14:07:22 -0400 Subject: [PATCH 10/12] Simplify logic in match/case --- examples/rich_tables.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index 4de8e4633..12b8271b9 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -90,28 +90,22 @@ def do_countries(self, _: cmd2.Statement) -> None: table.caption = "Data from https://worldpopulationreview.com/ and Wikipedia" for header in COUNTRY_HEADERS: + justify = "right" header_style = None style = None match header: case population if "2025 Population" in population: - justify = "right" header_style = Color.BRIGHT_BLUE style = Color.BLUE case density if "Density" in density: - justify = "right" header_style = Color.BRIGHT_RED style = Color.RED case percap if "per capita" in percap: - justify = "right" header_style = Color.BRIGHT_GREEN style = Color.GREEN - case area if 'Area' in area: - justify = "right" - case gdp if 'GDP' in gdp: - justify = "right" case flag if 'Flag' in flag: justify = "center" - case _: + case country if 'Country' in country: justify = "left" table.add_column(header, justify=justify, header_style=header_style, style=style) From 24d8d83679b69a7f722cdcd867cbaac009c5cad0 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 16:02:41 -0400 Subject: [PATCH 11/12] Stop setting show_footer=False when creating Table because that is the default --- examples/rich_tables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index 12b8271b9..3fcbc6d58 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -68,7 +68,7 @@ def __init__(self) -> None: @cmd2.with_category(TABLE_CATEGORY) def do_cities(self, _: cmd2.Statement) -> None: """Display the cities with the largest population.""" - table = Table(show_footer=False) + table = Table() table.title = "10 Largest Cities by Population 2025" table.caption = "Data from https://worldpopulationreview.com/" @@ -85,7 +85,7 @@ def do_cities(self, _: cmd2.Statement) -> None: @cmd2.with_category(TABLE_CATEGORY) def do_countries(self, _: cmd2.Statement) -> None: """Display the countries with the largest population.""" - table = Table(show_footer=False) + table = Table() table.title = "10 Largest Countries by Population 2025" table.caption = "Data from https://worldpopulationreview.com/ and Wikipedia" From 18fd253377ed97a28af394b984e863dc2cd46f65 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 24 Aug 2025 16:09:26 -0400 Subject: [PATCH 12/12] Define table titles and captions as constants and set when creating the tables --- examples/rich_tables.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/rich_tables.py b/examples/rich_tables.py index 3fcbc6d58..0d4a0900b 100755 --- a/examples/rich_tables.py +++ b/examples/rich_tables.py @@ -26,6 +26,8 @@ ["🇮🇳", "Mumbai", "India", 22_089_000], ["🇯🇵", "Osaka (大阪)", "Japan", 18_921_600], ] +CITY_TITLE = "10 Largest Cities by Population 2025" +CITY_CAPTION = "Data from https://worldpopulationreview.com/" COUNTRY_HEADERS = [ 'Flag', @@ -48,6 +50,8 @@ ["🇷🇺", "Russia (россия)", 143_997_000, 17.1, 9, 2_076_396, 14_258], ["🇪🇹", "Ethiopia (እትዮጵያ)", 135_472_000, 1.1, 120, 117_457, 1_066], ] +COUNTRY_TITLE = "10 Largest Countries by Population 2025" +COUNTRY_CAPTION = "Data from https://worldpopulationreview.com/ and Wikipedia" class TableApp(cmd2.Cmd): @@ -68,9 +72,7 @@ def __init__(self) -> None: @cmd2.with_category(TABLE_CATEGORY) def do_cities(self, _: cmd2.Statement) -> None: """Display the cities with the largest population.""" - table = Table() - table.title = "10 Largest Cities by Population 2025" - table.caption = "Data from https://worldpopulationreview.com/" + table = Table(title=CITY_TITLE, caption=CITY_CAPTION) for header in CITY_HEADERS: table.add_column(header) @@ -85,9 +87,7 @@ def do_cities(self, _: cmd2.Statement) -> None: @cmd2.with_category(TABLE_CATEGORY) def do_countries(self, _: cmd2.Statement) -> None: """Display the countries with the largest population.""" - table = Table() - table.title = "10 Largest Countries by Population 2025" - table.caption = "Data from https://worldpopulationreview.com/ and Wikipedia" + table = Table(title=COUNTRY_TITLE, caption=COUNTRY_CAPTION) for header in COUNTRY_HEADERS: justify = "right"