From 2f9f3d7b83806fd0defe5cbcb868a8180765a11c Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 10:35:34 +0000 Subject: [PATCH 01/11] add failing test for games(include_descriptions=True) which gets used in update script --- build_support/catalog/update.py | 2 +- tests/test_catalog.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index e42a18ad9..67065491a 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -66,7 +66,7 @@ def update_makefile(): args = parser.parse_args() # Create CSV used by RST docs page - gbt.catalog.games().to_csv(CATALOG_CSV, index=False) + gbt.catalog.games(include_descriptions=True).to_csv(CATALOG_CSV, index=False) print(f"Generated {CATALOG_CSV} for use in local docs build. DO NOT COMMIT.") # Update the Makefile.am with the current list of catalog files diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 6cc53d16a..b61ca6d1a 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -164,3 +164,10 @@ def test_catalog_games_filter_bad_filter(): """Test games() function raises error on invalid filter key""" with pytest.raises(TypeError): gbt.catalog.games(invalid_filter=123) + + +def test_catalog_games_include_descriptions(): + """Test games() function can include descriptions""" + games_with_desc = gbt.catalog.games(include_descriptions=True) + assert "Description" in games_with_desc.columns + assert len(games_with_desc.Description.iloc[0]) > 0 From 5ec788d8dc611808eafbb6dc5e6d4ad2cc34126b Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 10:47:55 +0000 Subject: [PATCH 02/11] Add description field to df when selected --- catalog/__init__.py | 53 +++++++++++++++++++++++++++++++------------ tests/test_catalog.py | 1 - 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/catalog/__init__.py b/catalog/__init__.py index e83ea540b..da7d29ae4 100644 --- a/catalog/__init__.py +++ b/catalog/__init__.py @@ -65,11 +65,12 @@ def games( n_outcomes: int | None = None, n_players: int | None = None, n_strategies: int | None = None, + include_descriptions: bool = False, ) -> pd.DataFrame: """ List games available in the package catalog. - Arguments are treated as filters on the + Most arguments are treated as filters on the attributes of the Game objects. Parameters @@ -98,11 +99,15 @@ def games( The number of players in the game. n_strategies: int, optional The number of pure strategies in the game. + include_descriptions: bool, optional + Whether to include the description of each game in the returned DataFrame. + Defaults to False. Returns ------- pd.DataFrame A DataFrame with columns "Game" and "Title", where "Game" is the slug to load the game. + If `include_descriptions=True`, the DataFrame will also include a "Description" column. """ records: list[dict[str, Any]] = [] @@ -153,12 +158,21 @@ def check_filters(game: gbt.Game) -> bool: with as_file(resource_path) as path: game = reader(str(path)) if check_filters(game): - records.append( - { - "Game": slug, - "Title": game.title, - } - ) + if include_descriptions: + records.append( + { + "Game": slug, + "Title": game.title, + "Description": game.description, + } + ) + else: + records.append( + { + "Game": slug, + "Title": game.title, + } + ) # Add all the games from families for slug, game in family_games().items(): @@ -168,13 +182,24 @@ def check_filters(game: gbt.Game) -> bool: f"Slug collision: {slug} is present in both file-based and family games." ) if check_filters(game): - records.append( - { - "Game": slug, - "Title": game.title, - } - ) - + if include_descriptions: + records.append( + { + "Game": slug, + "Title": game.title, + "Description": game.description, + } + ) + else: + records.append( + { + "Game": slug, + "Title": game.title, + } + ) + + if include_descriptions: + return pd.DataFrame.from_records(records, columns=["Game", "Title", "Description"]) return pd.DataFrame.from_records(records, columns=["Game", "Title"]) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index b61ca6d1a..4d0b39e96 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -170,4 +170,3 @@ def test_catalog_games_include_descriptions(): """Test games() function can include descriptions""" games_with_desc = gbt.catalog.games(include_descriptions=True) assert "Description" in games_with_desc.columns - assert len(games_with_desc.Description.iloc[0]) > 0 From b7806d3af9a3d7056c7855281973f9d40c801637 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 10:55:08 +0000 Subject: [PATCH 03/11] refactor to reduce code duplication --- catalog/__init__.py | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/catalog/__init__.py b/catalog/__init__.py index da7d29ae4..9e9c9fb53 100644 --- a/catalog/__init__.py +++ b/catalog/__init__.py @@ -145,6 +145,18 @@ def check_filters(game: gbt.Game) -> bool: return False return not (n_strategies is not None and len(game.strategies) != n_strategies) + def append_record( + slug: str, + game: gbt.Game, + ) -> None: + record = { + "Game": slug, + "Title": game.title, + } + if include_descriptions: + record["Description"] = game.description + records.append(record) + # Add all the games stored as EFG/NFG files for resource_path in sorted(_CATALOG_RESOURCE.rglob("*")): reader = READERS.get(resource_path.suffix) @@ -158,21 +170,7 @@ def check_filters(game: gbt.Game) -> bool: with as_file(resource_path) as path: game = reader(str(path)) if check_filters(game): - if include_descriptions: - records.append( - { - "Game": slug, - "Title": game.title, - "Description": game.description, - } - ) - else: - records.append( - { - "Game": slug, - "Title": game.title, - } - ) + append_record(slug, game) # Add all the games from families for slug, game in family_games().items(): @@ -182,21 +180,7 @@ def check_filters(game: gbt.Game) -> bool: f"Slug collision: {slug} is present in both file-based and family games." ) if check_filters(game): - if include_descriptions: - records.append( - { - "Game": slug, - "Title": game.title, - "Description": game.description, - } - ) - else: - records.append( - { - "Game": slug, - "Title": game.title, - } - ) + append_record(slug, game) if include_descriptions: return pd.DataFrame.from_records(records, columns=["Game", "Title", "Description"]) From cdb8285a3b401fada0579868e11d25c2037f0dca Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 11:35:09 +0000 Subject: [PATCH 04/11] correctly show descriptions on catalog page --- build_support/catalog/update.py | 6 ++++-- doc/catalog.rst | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index 67065491a..53a934f8b 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -1,4 +1,5 @@ import argparse +import csv from pathlib import Path import pygambit as gbt @@ -65,8 +66,9 @@ def update_makefile(): parser.add_argument("--build", action="store_true") args = parser.parse_args() - # Create CSV used by RST docs page - gbt.catalog.games(include_descriptions=True).to_csv(CATALOG_CSV, index=False) + # Create CSV used by doc/catalog.rst + df = gbt.catalog.games(include_descriptions=True) + df.to_csv(CATALOG_CSV, index=False, quoting=csv.QUOTE_NONNUMERIC) print(f"Generated {CATALOG_CSV} for use in local docs build. DO NOT COMMIT.") # Update the Makefile.am with the current list of catalog files diff --git a/doc/catalog.rst b/doc/catalog.rst index d50367996..fa2a79f14 100644 --- a/doc/catalog.rst +++ b/doc/catalog.rst @@ -7,5 +7,5 @@ Check out the :ref:`pygambit API reference ` for instructions .. csv-table:: :file: catalog.csv :header-rows: 1 - :widths: 20, 80 + :widths: 20, 80, 80 :class: tight-table From 1800cb1b5046946987c75b0859418c3ca9da839f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 11:50:37 +0000 Subject: [PATCH 05/11] Add biblio links to all games in catalog --- catalog/__init__.py | 2 +- catalog/myerson1991/fig4_2.efg | 4 ++-- catalog/selten1975/fig1.efg | 4 ++-- catalog/selten1975/fig2.efg | 4 ++-- catalog/selten1975/fig3.efg | 4 ++-- doc/biblio.rst | 8 ++++++++ 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/catalog/__init__.py b/catalog/__init__.py index 9e9c9fb53..6e6242574 100644 --- a/catalog/__init__.py +++ b/catalog/__init__.py @@ -227,7 +227,7 @@ def one_shot_trust(unique_NE_variant: bool = False) -> gbt.Game: The constructed extensive-form game. """ g = gbt.Game.new_tree(players=["Buyer", "Seller"]) - g.description = "One-shot trust game with binary actions, originally from Kreps (1990)." + g.description = "One-shot trust game with binary actions, originally from [Kre90]_." g.append_move(g.root, "Buyer", ["Trust", "Not trust"]) g.append_move(g.root.children[0], "Seller", ["Honor", "Abuse"]) g.set_outcome(g.root.children[0].children[0], g.add_outcome([1, 1], label="Trustworthy")) diff --git a/catalog/myerson1991/fig4_2.efg b/catalog/myerson1991/fig4_2.efg index 9631090f0..21f5b5907 100644 --- a/catalog/myerson1991/fig4_2.efg +++ b/catalog/myerson1991/fig4_2.efg @@ -1,5 +1,5 @@ EFG 2 R "Myerson (1991) Figure 4.2" { "Player 1" "Player 2" } -"An example from Myerson [^Mye1991] which illustrates the distinction between +"An example from Myerson [Mye1991]_ which illustrates the distinction between an equilibrium of an extensive form game and an equilibrium of its (multi)agent representation. The actions B1, Z1, and W2 form a behavior profile which is an equilibrium in the (multi)agent @@ -8,7 +8,7 @@ game, because Player 1 would prefer to switch from (B1, Z1) to (A1, Y1); the (multi)agent representation rules out such coordinated deviations across information sets. -[^Mye1991]: Myerson, Roger B. (1991) Game Theory: Analysis of Conflict. +[Mye1991]_: Myerson, Roger B. (1991) Game Theory: Analysis of Conflict. Cambridge: Harvard University Press. " diff --git a/catalog/selten1975/fig1.efg b/catalog/selten1975/fig1.efg index c966809b6..f85a10270 100644 --- a/catalog/selten1975/fig1.efg +++ b/catalog/selten1975/fig1.efg @@ -1,12 +1,12 @@ EFG 2 R "Selten's horse (Selten IJGT 1975, Figure 1)" { "Player 1" "Player 2" "Player 3" } -"This is a three-player game presented in Selten [^Sel1975], commonly referred +"This is a three-player game presented in Selten [Sel1975]_, commonly referred to as \"Selten's horse\" owing to the layout in which it can be drawn. It is the motivating example for his definition of (trembling-hand) perfect equilibrium, by showing a game that has an equilibrium which is \"unreasonable\", but which is not ruled out by subgame perfection because this game has no proper subgames. -[^Sel1975]: Selten, Reinhard (1975). A reexamination of the perfectness concept +[Sel1975]_: Selten, Reinhard (1975). A reexamination of the perfectness concept for equilibrium points in extensive games. International Journal of Game Theory 4(1): 25-55. " diff --git a/catalog/selten1975/fig2.efg b/catalog/selten1975/fig2.efg index 1d36cb21f..a44b11fd7 100644 --- a/catalog/selten1975/fig2.efg +++ b/catalog/selten1975/fig2.efg @@ -1,10 +1,10 @@ EFG 2 R "Selten (IJGT 1975) Figure 2" { "Player 1" "Player 2" } -"This is a counterexample presented in [^Sel1975], to show that extensive and +"This is a counterexample presented in [Sel1975]_, to show that extensive and normal form concepts of perfectness do not coincide. This game has one perfect equilibrium in the extensive from, but a distinct (pure) strategy equilibrium is also perfect in the normal form. -[^Sel75]: Selten, Reinhard (1975). A reexamination of the perfectness concept +[Sel1975]_: Selten, Reinhard (1975). A reexamination of the perfectness concept for equilibrium points in extensive games. International Journal of Game Theory 4(1): 25-55. " diff --git a/catalog/selten1975/fig3.efg b/catalog/selten1975/fig3.efg index 7df596906..e34321669 100644 --- a/catalog/selten1975/fig3.efg +++ b/catalog/selten1975/fig3.efg @@ -1,10 +1,10 @@ EFG 2 R "Selten (IJGT 1975) Figure 3" { "Player 1" "Player 2" "Player 3" } -"This is a counterexample presented in [^Sel1975], to show that extensive and +"This is a counterexample presented in [Sel1975]_, to show that extensive and normal form concepts of perfectness do not coincide. Specifically, there are two equilibria which are perfect in the normal form but not perfect in the extensive form. -[^Sel75]: Selten, Reinhard (1975). A reexamination of the perfectness concept +[Sel1975]_: Selten, Reinhard (1975). A reexamination of the perfectness concept for equilibrium points in extensive games. International Journal of Game Theory 4(1): 25-55. " diff --git a/doc/biblio.rst b/doc/biblio.rst index b84d341a8..8c12265be 100644 --- a/doc/biblio.rst +++ b/doc/biblio.rst @@ -1,6 +1,11 @@ Bibliography ============ +.. note:: + + To reference an entry in this bibliography, use the format ``[key]_``, for example, ``[Mye91]_`` will link to the Myerson (1991) textbook entry. + + Articles on computation of Nash equilibria ------------------------------------------ @@ -89,6 +94,9 @@ General game theory articles and texts .. [KreWil82] David Kreps and Robert Wilson, "Sequential Equilibria", 863-894, Econometrica , 50, 1982. +.. [Kre90] David Kreps, 1990, A Course in Microeconomic Theory, + Princeton University Press. + .. [McKPal95] Richard McKelvey and Tom Palfrey, "Quantal response equilibria for normal form games", 6-38, Games and Economic Behavior , 10, 1995. From 00594d8e1cacf7812a1e510d52b7ded5c9e82ccc Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 11:56:04 +0000 Subject: [PATCH 06/11] fix biblio links --- catalog/myerson1991/fig4_2.efg | 4 ++-- catalog/selten1975/fig1.efg | 4 ++-- catalog/selten1975/fig2.efg | 4 ++-- catalog/selten1975/fig3.efg | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/catalog/myerson1991/fig4_2.efg b/catalog/myerson1991/fig4_2.efg index 21f5b5907..c36cf1e13 100644 --- a/catalog/myerson1991/fig4_2.efg +++ b/catalog/myerson1991/fig4_2.efg @@ -1,5 +1,5 @@ EFG 2 R "Myerson (1991) Figure 4.2" { "Player 1" "Player 2" } -"An example from Myerson [Mye1991]_ which illustrates the distinction between +"An example from Myerson [Mye91]_ which illustrates the distinction between an equilibrium of an extensive form game and an equilibrium of its (multi)agent representation. The actions B1, Z1, and W2 form a behavior profile which is an equilibrium in the (multi)agent @@ -8,7 +8,7 @@ game, because Player 1 would prefer to switch from (B1, Z1) to (A1, Y1); the (multi)agent representation rules out such coordinated deviations across information sets. -[Mye1991]_: Myerson, Roger B. (1991) Game Theory: Analysis of Conflict. +[Mye91]_: Myerson, Roger B. (1991) Game Theory: Analysis of Conflict. Cambridge: Harvard University Press. " diff --git a/catalog/selten1975/fig1.efg b/catalog/selten1975/fig1.efg index f85a10270..ea47ae88f 100644 --- a/catalog/selten1975/fig1.efg +++ b/catalog/selten1975/fig1.efg @@ -1,12 +1,12 @@ EFG 2 R "Selten's horse (Selten IJGT 1975, Figure 1)" { "Player 1" "Player 2" "Player 3" } -"This is a three-player game presented in Selten [Sel1975]_, commonly referred +"This is a three-player game presented in Selten [Sel75]_, commonly referred to as \"Selten's horse\" owing to the layout in which it can be drawn. It is the motivating example for his definition of (trembling-hand) perfect equilibrium, by showing a game that has an equilibrium which is \"unreasonable\", but which is not ruled out by subgame perfection because this game has no proper subgames. -[Sel1975]_: Selten, Reinhard (1975). A reexamination of the perfectness concept +[Sel75]_: Selten, Reinhard (1975). A reexamination of the perfectness concept for equilibrium points in extensive games. International Journal of Game Theory 4(1): 25-55. " diff --git a/catalog/selten1975/fig2.efg b/catalog/selten1975/fig2.efg index a44b11fd7..ab52adaa5 100644 --- a/catalog/selten1975/fig2.efg +++ b/catalog/selten1975/fig2.efg @@ -1,10 +1,10 @@ EFG 2 R "Selten (IJGT 1975) Figure 2" { "Player 1" "Player 2" } -"This is a counterexample presented in [Sel1975]_, to show that extensive and +"This is a counterexample presented in [Sel75]_, to show that extensive and normal form concepts of perfectness do not coincide. This game has one perfect equilibrium in the extensive from, but a distinct (pure) strategy equilibrium is also perfect in the normal form. -[Sel1975]_: Selten, Reinhard (1975). A reexamination of the perfectness concept +[Sel75]_: Selten, Reinhard (1975). A reexamination of the perfectness concept for equilibrium points in extensive games. International Journal of Game Theory 4(1): 25-55. " diff --git a/catalog/selten1975/fig3.efg b/catalog/selten1975/fig3.efg index e34321669..25c123b05 100644 --- a/catalog/selten1975/fig3.efg +++ b/catalog/selten1975/fig3.efg @@ -1,10 +1,10 @@ EFG 2 R "Selten (IJGT 1975) Figure 3" { "Player 1" "Player 2" "Player 3" } -"This is a counterexample presented in [Sel1975]_, to show that extensive and +"This is a counterexample presented in [Sel75]_, to show that extensive and normal form concepts of perfectness do not coincide. Specifically, there are two equilibria which are perfect in the normal form but not perfect in the extensive form. -[Sel1975]_: Selten, Reinhard (1975). A reexamination of the perfectness concept +[Sel75]_: Selten, Reinhard (1975). A reexamination of the perfectness concept for equilibrium points in extensive games. International Journal of Game Theory 4(1): 25-55. " From 5e1893395d1669e2926c79ce80e0c15812ee59e7 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 13:10:48 +0000 Subject: [PATCH 07/11] update table row spacing --- doc/catalog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/catalog.rst b/doc/catalog.rst index fa2a79f14..66579e932 100644 --- a/doc/catalog.rst +++ b/doc/catalog.rst @@ -7,5 +7,5 @@ Check out the :ref:`pygambit API reference ` for instructions .. csv-table:: :file: catalog.csv :header-rows: 1 - :widths: 20, 80, 80 + :widths: 20, 20, 80 :class: tight-table From e2170dda800d2a1fb2c2b40509d69a8a1ebe2998 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 15:56:50 +0000 Subject: [PATCH 08/11] hide samples page --- doc/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/index.rst b/doc/index.rst index 0e1725265..017bdaf88 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -87,7 +87,6 @@ We recommended most new users install the PyGambit Python package and read the a tools gui catalog - samples developer formats biblio From 4571f7908e0aad4860ee7294fd4dc5e035ddbfb8 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 15:59:58 +0000 Subject: [PATCH 09/11] Add download links --- catalog/__init__.py | 6 +++++- doc/catalog.rst | 2 +- tests/test_catalog.py | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/catalog/__init__.py b/catalog/__init__.py index 6e6242574..d4ca2995f 100644 --- a/catalog/__init__.py +++ b/catalog/__init__.py @@ -155,6 +155,8 @@ def append_record( } if include_descriptions: record["Description"] = game.description + ext = "efg" if game.is_tree else "nfg" + record["Download"] = f":download:`{slug}.{ext} <../catalog/{slug}.{ext}>`" records.append(record) # Add all the games stored as EFG/NFG files @@ -183,7 +185,9 @@ def append_record( append_record(slug, game) if include_descriptions: - return pd.DataFrame.from_records(records, columns=["Game", "Title", "Description"]) + return pd.DataFrame.from_records( + records, columns=["Game", "Title", "Description", "Download"] + ) return pd.DataFrame.from_records(records, columns=["Game", "Title"]) diff --git a/doc/catalog.rst b/doc/catalog.rst index 66579e932..92c76d6aa 100644 --- a/doc/catalog.rst +++ b/doc/catalog.rst @@ -7,5 +7,5 @@ Check out the :ref:`pygambit API reference ` for instructions .. csv-table:: :file: catalog.csv :header-rows: 1 - :widths: 20, 20, 80 + :widths: 20, 20, 80, 20 :class: tight-table diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 4d0b39e96..a9188f1f8 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -170,3 +170,4 @@ def test_catalog_games_include_descriptions(): """Test games() function can include descriptions""" games_with_desc = gbt.catalog.games(include_descriptions=True) assert "Description" in games_with_desc.columns + assert "Download" in games_with_desc.columns From b1b653a50d8e54334a988289ee3432954a779ed2 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 16:29:17 +0000 Subject: [PATCH 10/11] generate rst table instead of CSV for Catalog of games page --- .gitignore | 2 +- build_support/catalog/update.py | 49 +++++++++++++++++++++++++++++---- doc/catalog.rst | 6 +--- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index f8be35fcf..643f8c565 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,4 @@ Gambit.app/* build_support/msw/gambit.wxs build_support/osx/Info.plist src/pygambit/catalog -doc/catalog.csv +doc/catalog_table.rst diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index 53a934f8b..9cd10d26c 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -1,14 +1,53 @@ import argparse -import csv from pathlib import Path +import pandas as pd + import pygambit as gbt -CATALOG_CSV = Path(__file__).parent.parent.parent / "doc" / "catalog.csv" +CATALOG_RST_TABLE = Path(__file__).parent.parent.parent / "doc" / "catalog_table.rst" CATALOG_DIR = Path(__file__).parent.parent.parent / "catalog" MAKEFILE_AM = Path(__file__).parent.parent.parent / "Makefile.am" +def generate_rst_table(df: pd.DataFrame, rst_path: Path): + """Generate a list-table RST file with dropdowns for long descriptions.""" + with open(rst_path, "w", encoding="utf-8") as f: + f.write(".. list-table::\n") + f.write(" :header-rows: 1\n") + f.write(" :widths: 20 20 80 20\n") + f.write(" :class: tight-table\n") + f.write("\n") + + f.write(" * - **Game**\n") + f.write(" - **Title**\n") + f.write(" - **Description**\n") + f.write(" - **Download**\n") + + for _, row in df.iterrows(): + f.write(f" * - {row['Game']}\n") + f.write(f" - {row['Title']}\n") + + description_cell_lines = [] + description = str(row.get("Description", "")).strip() + # Create a single-line summary for the dropdown title + temp_desc_for_summary = description.replace("\n", " ") + if len(description) > 80 and ". " in temp_desc_for_summary: + summary = temp_desc_for_summary.split(". ", 1)[0] + "." + description_cell_lines.append(f".. dropdown:: {summary}") + description_cell_lines.append(" ") # Indented blank line + for line in description.splitlines(): + description_cell_lines.append(f" {line}") + else: + description_cell_lines.append(description.replace("\n", " ")) + + f.write(f" - {description_cell_lines[0]}\n") + for line in description_cell_lines[1:]: + f.write(f" {line}\n") + + f.write(f" - {row['Download']}\n") + + def update_makefile(): """Update the Makefile.am with all games from the catalog.""" @@ -66,10 +105,10 @@ def update_makefile(): parser.add_argument("--build", action="store_true") args = parser.parse_args() - # Create CSV used by doc/catalog.rst + # Create RST list-table used by doc/catalog.rst df = gbt.catalog.games(include_descriptions=True) - df.to_csv(CATALOG_CSV, index=False, quoting=csv.QUOTE_NONNUMERIC) - print(f"Generated {CATALOG_CSV} for use in local docs build. DO NOT COMMIT.") + generate_rst_table(df, CATALOG_RST_TABLE) + print(f"Generated {CATALOG_RST_TABLE} for use in local docs build. DO NOT COMMIT.") # Update the Makefile.am with the current list of catalog files if args.build: diff --git a/doc/catalog.rst b/doc/catalog.rst index 92c76d6aa..a7d132c4a 100644 --- a/doc/catalog.rst +++ b/doc/catalog.rst @@ -4,8 +4,4 @@ Catalog of games Below is a complete list of games included in Gambit's catalog. Check out the :ref:`pygambit API reference ` for instructions on how to search and load these games in Python, and the :ref:`Updating the games catalog ` guide for instructions on how to contribute new games to the catalog. -.. csv-table:: - :file: catalog.csv - :header-rows: 1 - :widths: 20, 20, 80, 20 - :class: tight-table +.. include:: catalog_table.rst From c0cf1503fc2f06b13b6cfb1b37f296f9663de58a Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 3 Mar 2026 16:39:42 +0000 Subject: [PATCH 11/11] Refactor table such that descriptions are nested under titles --- build_support/catalog/update.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index 9cd10d26c..fd012ee65 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -15,31 +15,27 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path): with open(rst_path, "w", encoding="utf-8") as f: f.write(".. list-table::\n") f.write(" :header-rows: 1\n") - f.write(" :widths: 20 20 80 20\n") + f.write(" :widths: 20 80 20\n") f.write(" :class: tight-table\n") f.write("\n") f.write(" * - **Game**\n") - f.write(" - **Title**\n") f.write(" - **Description**\n") f.write(" - **Download**\n") for _, row in df.iterrows(): f.write(f" * - {row['Game']}\n") - f.write(f" - {row['Title']}\n") description_cell_lines = [] + title = str(row.get("Title", "")).strip() description = str(row.get("Description", "")).strip() - # Create a single-line summary for the dropdown title - temp_desc_for_summary = description.replace("\n", " ") - if len(description) > 80 and ". " in temp_desc_for_summary: - summary = temp_desc_for_summary.split(". ", 1)[0] + "." - description_cell_lines.append(f".. dropdown:: {summary}") + if description: + description_cell_lines.append(f".. dropdown:: {title}") description_cell_lines.append(" ") # Indented blank line for line in description.splitlines(): description_cell_lines.append(f" {line}") else: - description_cell_lines.append(description.replace("\n", " ")) + description_cell_lines.append(title) f.write(f" - {description_cell_lines[0]}\n") for line in description_cell_lines[1:]: