mkdocs-include-markdown-plugin 7.1.8__tar.gz → 7.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (16) hide show
  1. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/PKG-INFO +62 -1
  2. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/README.md +61 -0
  3. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/pyproject.toml +2 -2
  4. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/config.py +1 -0
  5. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/directive.py +91 -14
  6. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/event.py +64 -0
  7. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/plugin.py +12 -1
  8. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/process.py +55 -5
  9. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/.gitignore +0 -0
  10. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/LICENSE +0 -0
  11. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/__init__.py +0 -0
  12. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/cache.py +0 -0
  13. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/files_watcher.py +0 -0
  14. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/logger.py +0 -0
  15. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/placeholders.py +0 -0
  16. {mkdocs_include_markdown_plugin-7.1.8 → mkdocs_include_markdown_plugin-7.2.0}/src/mkdocs_include_markdown_plugin/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-include-markdown-plugin
3
- Version: 7.1.8
3
+ Version: 7.2.0
4
4
  Summary: Mkdocs Markdown includer plugin.
5
5
  Project-URL: Source, https://github.com/mondeja/mkdocs-include-markdown-plugin
6
6
  Project-URL: Documentation, https://github.com/mondeja/mkdocs-include-markdown-plugin#documentation
@@ -226,6 +226,32 @@ content to include.
226
226
  - <a name="include-markdown_recursive" href="#include-markdown_recursive">#</a>
227
227
  **recursive** (_true_): When this option is disabled, included files are not
228
228
  processed for recursive includes. Possible values are `true` and `false`.
229
+ - <a name="include-markdown_order" href="#include-markdown_order">#</a>
230
+ **order** (_'alpha-path'_): Define the order in which multiple files are included
231
+ when using globs. Possible values are:
232
+ - A combination of an optional order type and an optional order by separated
233
+ by a hyphen (`-`), and optionally prefixed by a hyphen (`-`) to indicate
234
+ ascending order. If an order type or an order by is not specified, the defaults
235
+ are used. It follows the form:
236
+ `[-]<order_type>-<order_by>` where:
237
+ - **Order type**:
238
+ - `'alpha'` (default): Alphabetical order.
239
+ - `'natural'`: Natural order, so that e.g. `file2.md` comes before `file10.md`.
240
+ - **Order by**:
241
+ - `'path'` (default): Order by full file path.
242
+ - `'name'`: Order by file name only.
243
+ - `'extension'`: Order by file extension.
244
+ - A combination of an optional prefix hyphen to denote ascending order and
245
+ one of the following values in the form `[-]<value>` where `<value>` is one of:
246
+ - `'size'`: Order by file size.
247
+ - `'mtime'`: Order by file modification time.
248
+ - `'ctime'`: Order by file creation time (or the last metadata change time
249
+ on Unix systems).
250
+ - `'atime'`: Order by file last access time.
251
+ - `'random'`: Random order.
252
+ - `'system'`: Order provided by the operating system. This is the same as not
253
+ specifying any order and relying on the default order of the filesystem. This
254
+ may be different between operating systems, so use it with care.
229
255
  - <a name="include-markdown_encoding" href="#include-markdown_encoding">#</a>
230
256
  **encoding** (_'utf-8'_): Specify the encoding of the included file.
231
257
  If not defined `'utf-8'` will be used.
@@ -284,6 +310,7 @@ content to include.
284
310
  {%
285
311
  include-markdown "**"
286
312
  exclude="./{index,LICENSE}.md"
313
+ order="name"
287
314
  %}
288
315
  ```
289
316
 
@@ -291,6 +318,13 @@ content to include.
291
318
  {% include-markdown '/escap\'ed/single-quotes/in/file\'/name.md' %}
292
319
  ```
293
320
 
321
+ ```jinja
322
+ {%
323
+ include-markdown "**"
324
+ order="-natural-extension"
325
+ %}
326
+ ```
327
+
294
328
  <!-- mdpo-disable-next-line -->
295
329
 
296
330
  #### **`include`**
@@ -317,6 +351,32 @@ Includes the content of a file or a group of files.
317
351
  - <a name="include_recursive" href="#include_recursive">#</a>
318
352
  **recursive** (_true_): When this option is disabled, included files are not
319
353
  processed for recursive includes. Possible values are `true` and `false`.
354
+ - <a name="include_order" href="#include_order">#</a>
355
+ **order** (_'alpha-path'_): Define the order in which multiple files are included
356
+ when using globs. Possible values are:
357
+ - A combination of an optional order type and an optional order by separated
358
+ by a hyphen (`-`), and optionally prefixed by a hyphen (`-`) to indicate
359
+ ascending order. If an order type or an order by is not specified, the defaults
360
+ are used. It follows the form:
361
+ `[-]<order_type>-<order_by>` where:
362
+ - **Order type**:
363
+ - `'alpha'` (default): Alphabetical order.
364
+ - `'natural'`: Natural order, so that e.g. `file2.md` comes before `file10.md`.
365
+ - **Order by**:
366
+ - `'path'` (default): Order by full file path.
367
+ - `'name'`: Order by file name only.
368
+ - `'extension'`: Order by file extension.
369
+ - A combination of an optional prefix hyphen to denote ascending order and
370
+ one of the following values in the form `[-]<value>` where `<value>` is one of:
371
+ - `'size'`: Order by file size.
372
+ - `'mtime'`: Order by file modification time.
373
+ - `'ctime'`: Order by file creation time (or the last metadata change time
374
+ on Unix systems).
375
+ - `'atime'`: Order by file last access time.
376
+ - `'random'`: Random order.
377
+ - `'system'`: Order provided by the operating system. This is the same as not
378
+ specifying any order and relying on the default order of the filesystem. This
379
+ may be different between operating systems, so use it with care.
320
380
  - <a name="include_encoding" href="#include_encoding">#</a>
321
381
  **encoding** (_'utf-8'_): Specify the encoding of the included file.
322
382
  If not defined `'utf-8'` will be used.
@@ -341,6 +401,7 @@ Includes the content of a file or a group of files.
341
401
  {%
342
402
  include '**'
343
403
  exclude='./*.md'
404
+ order='random'
344
405
  %}
345
406
  ```
346
407
 
@@ -191,6 +191,32 @@ content to include.
191
191
  - <a name="include-markdown_recursive" href="#include-markdown_recursive">#</a>
192
192
  **recursive** (_true_): When this option is disabled, included files are not
193
193
  processed for recursive includes. Possible values are `true` and `false`.
194
+ - <a name="include-markdown_order" href="#include-markdown_order">#</a>
195
+ **order** (_'alpha-path'_): Define the order in which multiple files are included
196
+ when using globs. Possible values are:
197
+ - A combination of an optional order type and an optional order by separated
198
+ by a hyphen (`-`), and optionally prefixed by a hyphen (`-`) to indicate
199
+ ascending order. If an order type or an order by is not specified, the defaults
200
+ are used. It follows the form:
201
+ `[-]<order_type>-<order_by>` where:
202
+ - **Order type**:
203
+ - `'alpha'` (default): Alphabetical order.
204
+ - `'natural'`: Natural order, so that e.g. `file2.md` comes before `file10.md`.
205
+ - **Order by**:
206
+ - `'path'` (default): Order by full file path.
207
+ - `'name'`: Order by file name only.
208
+ - `'extension'`: Order by file extension.
209
+ - A combination of an optional prefix hyphen to denote ascending order and
210
+ one of the following values in the form `[-]<value>` where `<value>` is one of:
211
+ - `'size'`: Order by file size.
212
+ - `'mtime'`: Order by file modification time.
213
+ - `'ctime'`: Order by file creation time (or the last metadata change time
214
+ on Unix systems).
215
+ - `'atime'`: Order by file last access time.
216
+ - `'random'`: Random order.
217
+ - `'system'`: Order provided by the operating system. This is the same as not
218
+ specifying any order and relying on the default order of the filesystem. This
219
+ may be different between operating systems, so use it with care.
194
220
  - <a name="include-markdown_encoding" href="#include-markdown_encoding">#</a>
195
221
  **encoding** (_'utf-8'_): Specify the encoding of the included file.
196
222
  If not defined `'utf-8'` will be used.
@@ -249,6 +275,7 @@ content to include.
249
275
  {%
250
276
  include-markdown "**"
251
277
  exclude="./{index,LICENSE}.md"
278
+ order="name"
252
279
  %}
253
280
  ```
254
281
 
@@ -256,6 +283,13 @@ content to include.
256
283
  {% include-markdown '/escap\'ed/single-quotes/in/file\'/name.md' %}
257
284
  ```
258
285
 
286
+ ```jinja
287
+ {%
288
+ include-markdown "**"
289
+ order="-natural-extension"
290
+ %}
291
+ ```
292
+
259
293
  <!-- mdpo-disable-next-line -->
260
294
 
261
295
  #### **`include`**
@@ -282,6 +316,32 @@ Includes the content of a file or a group of files.
282
316
  - <a name="include_recursive" href="#include_recursive">#</a>
283
317
  **recursive** (_true_): When this option is disabled, included files are not
284
318
  processed for recursive includes. Possible values are `true` and `false`.
319
+ - <a name="include_order" href="#include_order">#</a>
320
+ **order** (_'alpha-path'_): Define the order in which multiple files are included
321
+ when using globs. Possible values are:
322
+ - A combination of an optional order type and an optional order by separated
323
+ by a hyphen (`-`), and optionally prefixed by a hyphen (`-`) to indicate
324
+ ascending order. If an order type or an order by is not specified, the defaults
325
+ are used. It follows the form:
326
+ `[-]<order_type>-<order_by>` where:
327
+ - **Order type**:
328
+ - `'alpha'` (default): Alphabetical order.
329
+ - `'natural'`: Natural order, so that e.g. `file2.md` comes before `file10.md`.
330
+ - **Order by**:
331
+ - `'path'` (default): Order by full file path.
332
+ - `'name'`: Order by file name only.
333
+ - `'extension'`: Order by file extension.
334
+ - A combination of an optional prefix hyphen to denote ascending order and
335
+ one of the following values in the form `[-]<value>` where `<value>` is one of:
336
+ - `'size'`: Order by file size.
337
+ - `'mtime'`: Order by file modification time.
338
+ - `'ctime'`: Order by file creation time (or the last metadata change time
339
+ on Unix systems).
340
+ - `'atime'`: Order by file last access time.
341
+ - `'random'`: Random order.
342
+ - `'system'`: Order provided by the operating system. This is the same as not
343
+ specifying any order and relying on the default order of the filesystem. This
344
+ may be different between operating systems, so use it with care.
285
345
  - <a name="include_encoding" href="#include_encoding">#</a>
286
346
  **encoding** (_'utf-8'_): Specify the encoding of the included file.
287
347
  If not defined `'utf-8'` will be used.
@@ -306,6 +366,7 @@ Includes the content of a file or a group of files.
306
366
  {%
307
367
  include '**'
308
368
  exclude='./*.md'
369
+ order='random'
309
370
  %}
310
371
  ```
311
372
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mkdocs-include-markdown-plugin"
3
- version = "7.1.8"
3
+ version = "7.2.0"
4
4
  description = "Mkdocs Markdown includer plugin."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -151,7 +151,7 @@ select = [
151
151
  "COM",
152
152
  "SLOT",
153
153
  ]
154
- ignore = ["G004"]
154
+ ignore = ["G004", "E731"]
155
155
 
156
156
  [tool.ruff.lint.pydocstyle]
157
157
  convention = "google"
@@ -20,6 +20,7 @@ class PluginConfig(Config): # noqa: D101
20
20
  comments = MkType(bool, default=False)
21
21
  rewrite_relative_urls = MkType(bool, default=True)
22
22
  heading_offset = MkType(int, default=0)
23
+ order = MkType(str, default='alpha-path')
23
24
  start = Optional(MkType(str))
24
25
  end = Optional(MkType(str))
25
26
  exclude = ListOfItems(MkType(str), default=[])
@@ -13,8 +13,15 @@ from typing import TYPE_CHECKING
13
13
  from mkdocs.exceptions import PluginError
14
14
  from wcmatch import glob
15
15
 
16
- from mkdocs_include_markdown_plugin import process
17
16
  from mkdocs_include_markdown_plugin.logger import logger
17
+ from mkdocs_include_markdown_plugin.process import (
18
+ file_lineno_message,
19
+ filter_paths,
20
+ is_absolute_path,
21
+ is_relative_path,
22
+ is_url,
23
+ sort_paths,
24
+ )
18
25
 
19
26
 
20
27
  @dataclass
@@ -28,6 +35,7 @@ if TYPE_CHECKING: # pragma: no cover
28
35
  from typing import Callable, Literal, TypedDict
29
36
 
30
37
  DirectiveBoolArgumentsDict = dict[str, DirectiveBoolArgument]
38
+ OrderOption = tuple[bool, str, str]
31
39
 
32
40
  DefaultValues = TypedDict(
33
41
  'DefaultValues', {
@@ -41,6 +49,7 @@ if TYPE_CHECKING: # pragma: no cover
41
49
  'recursive': bool,
42
50
  'start': str | None,
43
51
  'end': str | None,
52
+ 'order': str,
44
53
  },
45
54
  )
46
55
 
@@ -92,10 +101,12 @@ def str_arg(arg: str) -> re.Pattern[str]:
92
101
 
93
102
 
94
103
  ARGUMENT_REGEXES = {
104
+ # str
95
105
  'start': functools.partial(str_arg, 'start'),
96
106
  'end': functools.partial(str_arg, 'end'),
97
107
  'exclude': functools.partial(str_arg, 'exclude'),
98
108
  'encoding': functools.partial(str_arg, 'encoding'),
109
+ 'order': functools.partial(str_arg, 'order'),
99
110
 
100
111
  # bool
101
112
  'comments': functools.partial(arg, 'comments'),
@@ -178,7 +189,7 @@ def warn_invalid_directive_arguments(
178
189
  )
179
190
  for maybe_arg in _maybe_arguments_iter(arguments_string):
180
191
  if maybe_arg not in valid_args:
181
- location = process.file_lineno_message(
192
+ location = file_lineno_message(
182
193
  page_src_path, docs_dir, directive_lineno(),
183
194
  )
184
195
  logger.warning(
@@ -283,12 +294,13 @@ def resolve_file_paths_to_include( # noqa: PLR0912
283
294
  includer_page_src_path: str | None,
284
295
  docs_dir: str,
285
296
  ignore_paths: list[str],
297
+ order: str,
286
298
  ) -> tuple[list[str], bool]:
287
299
  """Resolve the file paths to include for a directive."""
288
- if process.is_url(include_string):
300
+ if is_url(include_string):
289
301
  return [include_string], True
290
302
 
291
- if process.is_absolute_path(include_string):
303
+ if is_absolute_path(include_string):
292
304
  if os.name == 'nt': # pragma: no cover
293
305
  # Windows
294
306
  fpath = os.path.normpath(include_string)
@@ -299,21 +311,25 @@ def resolve_file_paths_to_include( # noqa: PLR0912
299
311
  if not is_file:
300
312
  return [], False
301
313
 
302
- return process.filter_paths(
303
- [fpath], ignore_paths,
304
- ), False
314
+ paths = filter_paths([fpath], ignore_paths)
315
+ is_url_ = False
316
+ return sort_paths(paths, parse_order_option(order)), is_url_
305
317
 
306
318
  try:
307
319
  is_file = stat.S_ISREG(os.stat(include_string).st_mode)
308
320
  except (FileNotFoundError, OSError):
309
321
  is_file = False
310
- return process.filter_paths(
322
+ paths = filter_paths(
311
323
  [include_string] if is_file else glob.iglob(
312
324
  include_string, flags=GLOB_FLAGS,
313
325
  ),
314
- ignore_paths), False
326
+ ignore_paths,
327
+ )
328
+ is_url_ = False
329
+ sort_paths(paths, parse_order_option(order))
330
+ return paths, is_url_
315
331
 
316
- if process.is_relative_path(include_string):
332
+ if is_relative_path(include_string):
317
333
  if includer_page_src_path is None: # pragma: no cover
318
334
  raise PluginError(
319
335
  'Relative paths are not allowed when the includer page'
@@ -338,7 +354,10 @@ def resolve_file_paths_to_include( # noqa: PLR0912
338
354
  root_dir=root_dir,
339
355
  ):
340
356
  paths.append(os.path.join(root_dir, fp))
341
- return process.filter_paths(paths, ignore_paths), False
357
+ paths = filter_paths(paths, ignore_paths)
358
+ is_url_ = False
359
+ sort_paths(paths, parse_order_option(order))
360
+ return paths, is_url_
342
361
 
343
362
  # relative to docs_dir
344
363
  paths = []
@@ -357,7 +376,10 @@ def resolve_file_paths_to_include( # noqa: PLR0912
357
376
  root_dir=root_dir,
358
377
  ):
359
378
  paths.append(os.path.join(root_dir, fp))
360
- return process.filter_paths(paths, ignore_paths), False
379
+ paths = filter_paths(paths, ignore_paths)
380
+ is_url_ = False
381
+ sort_paths(paths, parse_order_option(order))
382
+ return paths, is_url_
361
383
 
362
384
 
363
385
  def resolve_file_paths_to_exclude(
@@ -366,10 +388,10 @@ def resolve_file_paths_to_exclude(
366
388
  docs_dir: str,
367
389
  ) -> list[str]:
368
390
  """Resolve the file paths to exclude for a directive."""
369
- if process.is_absolute_path(exclude_string):
391
+ if is_absolute_path(exclude_string):
370
392
  return glob.glob(exclude_string, flags=GLOB_FLAGS)
371
393
 
372
- if process.is_relative_path(exclude_string):
394
+ if is_relative_path(exclude_string):
373
395
  if includer_page_src_path is None: # pragma: no cover
374
396
  raise PluginError(
375
397
  'Relative paths are not allowed when the includer page'
@@ -394,3 +416,58 @@ def resolve_file_paths_to_exclude(
394
416
  flags=GLOB_FLAGS,
395
417
  root_dir=docs_dir,
396
418
  )
419
+
420
+
421
+ def validate_order_option(
422
+ order: str,
423
+ page_src_path: str | None,
424
+ docs_dir: str,
425
+ directive_lineno: Callable[[], int],
426
+ directive: str,
427
+ ) -> None:
428
+ """Validate the 'order' option."""
429
+ regex = get_order_option_regex()
430
+ match = regex.match(order)
431
+ if not match:
432
+ location = file_lineno_message(
433
+ page_src_path, docs_dir, directive_lineno(),
434
+ )
435
+ raise PluginError(
436
+ f"Invalid value '{order}' for the 'order' argument in"
437
+ f" '{directive}' directive at {location}. The argument"
438
+ " 'order' must be a string that matches the regex"
439
+ f" '{regex.pattern}'.",
440
+ )
441
+
442
+
443
+ @functools.cache
444
+ def get_order_option_regex() -> re.Pattern[str]:
445
+ """Return the compiled regex to validate the 'order' option."""
446
+ return re.compile(
447
+ r'^-?'
448
+ r'(?:'
449
+ r'(?:alpha|natural)?(?:-?(?:path|name|extension))?'
450
+ r'|system|random|size|mtime|ctime|atime'
451
+ r')?$',
452
+ )
453
+
454
+
455
+ def parse_order_option(order: str) -> OrderOption:
456
+ """Parse the 'order' option into a tuple."""
457
+ ascending = False
458
+ order_type = 'alpha'
459
+ order_by = 'path'
460
+ if order.startswith('-'):
461
+ ascending = True
462
+ order = order[1:]
463
+ order_split = order.split('-', 1)
464
+ if len(order_split) == 2: # noqa: PLR2004
465
+ order_type, order_by = order_split
466
+ elif order_split[0] in (
467
+ 'alpha', 'random', 'natural', 'system',
468
+ 'size', 'mtime', 'ctime', 'atime',
469
+ ):
470
+ order_type = order_split[0]
471
+ elif order_split[0] in ('name', 'path', 'extension'):
472
+ order_by = order_split[0]
473
+ return ascending, order_type, order_by
@@ -24,6 +24,7 @@ from mkdocs_include_markdown_plugin.directive import (
24
24
  parse_string_argument,
25
25
  resolve_file_paths_to_exclude,
26
26
  resolve_file_paths_to_include,
27
+ validate_order_option,
27
28
  warn_invalid_directive_arguments,
28
29
  )
29
30
  from mkdocs_include_markdown_plugin.files_watcher import FilesWatcher
@@ -141,13 +142,42 @@ def get_file_content( # noqa: PLR0913, PLR0915
141
142
  ):
142
143
  ignore_paths.append(path)
143
144
 
145
+ order = defaults['order']
146
+ if 'order' in used_arguments:
147
+ order_match = ARGUMENT_REGEXES['order']().search(
148
+ arguments_string,
149
+ )
150
+ order_ = parse_string_argument(order_match)
151
+ if order_ is None:
152
+ location = process.file_lineno_message(
153
+ page_src_path, docs_dir, directive_lineno(),
154
+ )
155
+ raise PluginError(
156
+ "Invalid empty 'order' argument in 'include'"
157
+ f' directive at {location}',
158
+ )
159
+ validate_order_option(
160
+ order_, page_src_path, docs_dir, directive_lineno, 'include',
161
+ )
162
+ order = order_
163
+
144
164
  file_paths_to_include, is_url = resolve_file_paths_to_include(
145
165
  filename,
146
166
  page_src_path,
147
167
  docs_dir,
148
168
  ignore_paths,
169
+ order,
149
170
  )
150
171
 
172
+ if is_url and 'order' in used_arguments: # pragma: no cover
173
+ location = process.file_lineno_message(
174
+ page_src_path, docs_dir, directive_lineno(),
175
+ )
176
+ logger.warning(
177
+ f"Ignoring 'order' argument of 'include' directive"
178
+ f" at {location} because the included path is a URL",
179
+ )
180
+
151
181
  if not file_paths_to_include:
152
182
  location = process.file_lineno_message(
153
183
  page_src_path, docs_dir, directive_lineno(),
@@ -353,13 +383,46 @@ def get_file_content( # noqa: PLR0913, PLR0915
353
383
  ):
354
384
  ignore_paths.append(path)
355
385
 
386
+ order = defaults['order']
387
+ if 'order' in used_arguments:
388
+ order_match = ARGUMENT_REGEXES['order']().search(
389
+ arguments_string,
390
+ )
391
+ order_ = parse_string_argument(order_match)
392
+ if order_ is None:
393
+ location = process.file_lineno_message(
394
+ page_src_path, docs_dir, directive_lineno(),
395
+ )
396
+ raise PluginError(
397
+ "Invalid empty 'order' argument in 'include-markdown'"
398
+ f' directive at {location}',
399
+ )
400
+ validate_order_option(
401
+ order_,
402
+ page_src_path,
403
+ docs_dir,
404
+ directive_lineno,
405
+ 'include-markdown',
406
+ )
407
+ order = order_
408
+
356
409
  file_paths_to_include, is_url = resolve_file_paths_to_include(
357
410
  filename,
358
411
  page_src_path,
359
412
  docs_dir,
360
413
  ignore_paths,
414
+ order,
361
415
  )
362
416
 
417
+ if is_url and 'order' in used_arguments: # pragma: no cover
418
+ location = process.file_lineno_message(
419
+ page_src_path, docs_dir, directive_lineno(),
420
+ )
421
+ logger.warning(
422
+ f"Ignoring 'order' argument of 'include-markdown' directive"
423
+ f" at {location} because the included path is a URL",
424
+ )
425
+
363
426
  if not file_paths_to_include:
364
427
  location = process.file_lineno_message(
365
428
  page_src_path, docs_dir, directive_lineno(),
@@ -645,6 +708,7 @@ def on_page_markdown(
645
708
  'recursive': config.recursive,
646
709
  'start': config.start,
647
710
  'end': config.end,
711
+ 'order': config.order,
648
712
  },
649
713
  Settings(
650
714
  exclude=config.exclude,
@@ -11,7 +11,6 @@ from mkdocs.plugins import BasePlugin, event_priority
11
11
 
12
12
 
13
13
  if TYPE_CHECKING: # pragma: no cover
14
-
15
14
  from mkdocs.config.defaults import MkDocsConfig
16
15
  from mkdocs.livereload import LiveReloadServer
17
16
  from mkdocs.structure.files import Files
@@ -19,6 +18,9 @@ if TYPE_CHECKING: # pragma: no cover
19
18
 
20
19
  from mkdocs_include_markdown_plugin.cache import Cache, initialize_cache
21
20
  from mkdocs_include_markdown_plugin.config import PluginConfig
21
+ from mkdocs_include_markdown_plugin.directive import (
22
+ get_order_option_regex,
23
+ )
22
24
  from mkdocs_include_markdown_plugin.event import (
23
25
  on_page_markdown as _on_page_markdown,
24
26
  )
@@ -50,6 +52,15 @@ class IncludeMarkdownPlugin(BasePlugin[PluginConfig]):
50
52
  ' "include-markdown".',
51
53
  )
52
54
 
55
+ if self.config.order != 'alpha-path':
56
+ regex = get_order_option_regex()
57
+ if not regex.match(self.config.order):
58
+ raise PluginError(
59
+ f"Invalid value '{self.config.order}' for the 'order'"
60
+ ' global setting. Order must be a string'
61
+ f" that matches the regex '{regex.pattern}'.",
62
+ )
63
+
53
64
  return config
54
65
 
55
66
  @cached_property
@@ -15,6 +15,7 @@ if TYPE_CHECKING: # pragma: no cover
15
15
  from typing import Any
16
16
 
17
17
  from mkdocs_include_markdown_plugin.cache import Cache
18
+ from mkdocs_include_markdown_plugin.directive import OrderOption
18
19
 
19
20
 
20
21
  # Markdown regular expressions. Taken from the original Markdown.pl by John
@@ -494,12 +495,12 @@ def filter_paths(
494
495
 
495
496
  Args:
496
497
  filepaths (list): Set of source paths to filter.
497
- ignore_paths (list): Paths that must not be included in the response.
498
+ ignore_paths (list): Paths that are ignored.
498
499
 
499
500
  Returns:
500
501
  list: Non filtered paths ordered alphabetically.
501
502
  """
502
- response = []
503
+ result = []
503
504
  for filepath in filepaths:
504
505
  # ignore by filepath
505
506
  if filepath in ignore_paths:
@@ -514,11 +515,60 @@ def filter_paths(
514
515
  # ignore if is a directory
515
516
  try:
516
517
  if not stat.S_ISDIR(os.stat(filepath).st_mode):
517
- response.append(filepath)
518
+ result.append(filepath)
518
519
  except (FileNotFoundError, OSError): # pragma: no cover
519
520
  continue
520
- response.sort()
521
- return response
521
+ return result
522
+
523
+
524
+ def natural_sort_key(s: str) -> list[Any]:
525
+ """Key function for natural sorting of strings."""
526
+ return [int(text) if text.isdigit() else text.lower()
527
+ for text in re.split(r'(\d+)', s)]
528
+
529
+
530
+ def sort_paths(paths: list[str], order: OrderOption) -> list[str]:
531
+ """Sort a list of paths in-place according to an order option."""
532
+ ascending, order_type, order_by = order
533
+
534
+ if order_type == 'random':
535
+ import random # noqa: PLC0415
536
+
537
+ random.shuffle(paths)
538
+ return paths
539
+
540
+ key = None
541
+ if order_type == 'alpha':
542
+ if order_by == 'name':
543
+ def key(p: str) -> str:
544
+ return os.path.basename(p)
545
+ elif order_by == 'extension':
546
+ def key(p: str) -> str:
547
+ return os.path.splitext(p)[1]
548
+ elif order_type == 'natural':
549
+ if order_by == 'extension':
550
+ def key(p: str) -> str:
551
+ return natural_sort_key(os.path.splitext(p)[1]) # type: ignore
552
+ elif order_by == 'name':
553
+ def key(p: str) -> str:
554
+ return natural_sort_key(os.path.basename(p)) # type: ignore
555
+ else:
556
+ key = natural_sort_key # type: ignore
557
+ elif order_type == 'size':
558
+ def key(p: str) -> int: # type: ignore
559
+ return os.path.getsize(p)
560
+ ascending = not ascending # larger files first
561
+ elif order_type == 'mtime':
562
+ def key(p: str) -> float: # type: ignore
563
+ return os.path.getmtime(p)
564
+ elif order_type == 'ctime':
565
+ def key(p: str) -> float: # type: ignore
566
+ return os.path.getctime(p)
567
+ elif order_type == 'atime':
568
+ def key(p: str) -> float: # type: ignore
569
+ return os.path.getatime(p)
570
+ paths.sort(key=key, reverse=ascending)
571
+ return paths
522
572
 
523
573
 
524
574
  def _is_valid_url_scheme_char(c: str) -> bool: