|
4 | 4 | """ |
5 | 5 |
|
6 | 6 | import itertools |
| 7 | +from pathlib import Path |
| 8 | + |
7 | 9 | from sphinx_gallery.sorting import ExplicitOrder |
8 | 10 |
|
9 | 11 | # Gallery sections shall be displayed in the following order. |
@@ -64,70 +66,87 @@ def __call__(self, item): |
64 | 66 | else: |
65 | 67 | return f"{self.ordered_list.index(UNSORTED):04d}{item}" |
66 | 68 |
|
67 | | -# Subsection order: |
68 | | -# Subsections are ordered by filename, unless they appear in the following |
69 | | -# lists in which case the list order determines the order within the section. |
70 | | -# Examples/tutorials that do not appear in a list will be appended. |
71 | | - |
72 | | -list_all = [ |
73 | | - # **Tutorials** |
74 | | - # introductory |
75 | | - "quick_start", "pyplot", "images", "lifecycle", "customizing", |
76 | | - # intermediate |
77 | | - "artists", "legend_guide", "color_cycle", |
78 | | - "constrainedlayout_guide", "tight_layout_guide", |
79 | | - # advanced |
80 | | - # text |
81 | | - "text_intro", "text_props", |
82 | | - # colors |
83 | | - "colors", |
84 | | - |
85 | | - # **Examples** |
86 | | - # animation |
87 | | - "simple_anim", # Most basic example |
88 | | - # color |
89 | | - "color_demo", |
90 | | - # pies |
91 | | - "pie_features", "pie_demo2", |
92 | | - # scales |
93 | | - "scales", # Scales overview |
94 | | - |
95 | | - # **Plot Types |
96 | | - # Basic |
97 | | - "plot", "scatter_plot", "bar", "stem", "step", "fill_between", |
98 | | - # Arrays |
99 | | - "imshow", "pcolormesh", "contour", "contourf", |
100 | | - "barbs", "quiver", "streamplot", |
101 | | - # Stats |
102 | | - "hist_plot", "boxplot_plot", "errorbar_plot", "violin", |
103 | | - "eventplot", "hist2d", "hexbin", "pie", |
104 | | - # Unstructured |
105 | | - "tricontour", "tricontourf", "tripcolor", "triplot", |
106 | | - # Spines |
107 | | - "spines", "spine_placement_demo", "spines_dropped", |
108 | | - "multiple_yaxis_with_spines", "centered_spines_with_arrows", |
109 | | - ] |
110 | | -explicit_subsection_order = [item + ".py" for item in list_all] |
111 | | - |
112 | | - |
113 | | -class MplExplicitSubOrder(ExplicitOrder): |
114 | | - """For use within the 'within_subsection_order' key.""" |
| 69 | + |
| 70 | +class MplFileExplicitOrder(ExplicitOrder): |
| 71 | + """ |
| 72 | + An explicit order class that reads the order of examples from 'gallery_order.txt'. |
| 73 | +
|
| 74 | + For use with the sphinx_gallery 'within_subsection_order' key. |
| 75 | +
|
| 76 | + The file contains a list of example filenames (without the .py extension) in the |
| 77 | + desired order, with an optional '*' to indicate where not-listed examples should be |
| 78 | + placed. |
| 79 | +
|
| 80 | + If '*' is not present, all examples must be listed, or an error will be raised. |
| 81 | + Use this if you want to ensure that a full order is intentionally maintained. |
| 82 | + """ |
115 | 83 | def __init__(self, src_dir): |
116 | | - self.src_dir = src_dir # src_dir is unused here |
117 | | - self.ordered_list = explicit_subsection_order |
| 84 | + ordered_list = self.read_gallery_order(Path(src_dir)) or [] |
| 85 | + super().__init__(ordered_list) |
| 86 | + |
| 87 | + @staticmethod |
| 88 | + def read_gallery_order(src_dir: Path): |
| 89 | + """Return the list of examples to be sorted; read from 'gallery_order.txt'.""" |
| 90 | + gallery_order_txt = src_dir / "gallery_order.txt" |
| 91 | + if not gallery_order_txt.exists(): |
| 92 | + return None |
| 93 | + lines = [ |
| 94 | + line.strip() |
| 95 | + for line in gallery_order_txt.read_text().splitlines() |
| 96 | + if line.strip() and not line.startswith("#") |
| 97 | + ] |
| 98 | + |
| 99 | + try: |
| 100 | + placeholder_index = lines.index("*") |
| 101 | + except ValueError: |
| 102 | + placeholder_index = None |
| 103 | + |
| 104 | + lines = [line + ".py" for line in lines] |
| 105 | + |
| 106 | + if placeholder_index is None: |
| 107 | + front = lines |
| 108 | + back = [] |
| 109 | + else: |
| 110 | + front = lines[:placeholder_index] |
| 111 | + back = lines[placeholder_index+1:] |
| 112 | + |
| 113 | + listed_examples = {*front, *back} |
| 114 | + existing_examples = { |
| 115 | + file.name for file in src_dir.iterdir() if file.suffix == ".py" |
| 116 | + } |
| 117 | + |
| 118 | + non_existing_examples = listed_examples - existing_examples |
| 119 | + missing_examples = existing_examples - listed_examples |
| 120 | + print(f"non_existing_examples: {non_existing_examples}") |
| 121 | + print(f"missing_examples: {missing_examples}") |
| 122 | + |
| 123 | + if non_existing_examples: |
| 124 | + raise ValueError( |
| 125 | + f"The following examples listed in {gallery_order_txt} do not exist: " |
| 126 | + f"{', '.join(non_existing_examples)}") |
| 127 | + if placeholder_index is None and missing_examples: |
| 128 | + raise ValueError( |
| 129 | + f"The following examples are not listed in {gallery_order_txt}. " |
| 130 | + f"Either include them or add a '*' to indicate where not listed " |
| 131 | + f"examples should be placed: " |
| 132 | + f"{', '.join(missing_examples)}" |
| 133 | + ) |
| 134 | + |
| 135 | + mid = list(sorted(missing_examples)) |
| 136 | + return front + mid + back |
118 | 137 |
|
119 | 138 | def __call__(self, item): |
120 | 139 | """Return a string determining the sort order.""" |
121 | | - if item in self.ordered_list: |
122 | | - return f"{self.ordered_list.index(item):04d}" |
123 | | - else: |
124 | | - # ensure not explicitly listed items come last. |
125 | | - return "zzz" + item |
| 140 | + if not self.ordered_list: |
| 141 | + return item |
| 142 | + return f"{self.ordered_list.index(item):04d}" |
126 | 143 |
|
| 144 | + def __repr__(self): |
| 145 | + return '<%s: %s>' % (self.__class__.__name__, self.ordered_list) |
127 | 146 |
|
128 | 147 | # Provide the above classes for use in conf.py |
129 | 148 | sectionorder = MplExplicitOrder(explicit_order_folders) |
130 | | -subsectionorder = MplExplicitSubOrder |
| 149 | +subsectionorder = MplFileExplicitOrder |
131 | 150 |
|
132 | 151 | _preserve_count = itertools.count() |
133 | 152 |
|
|
0 commit comments