emmykit 0.3.2__tar.gz → 0.3.4__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 (53) hide show
  1. {emmykit-0.3.2 → emmykit-0.3.4}/PKG-INFO +247 -784
  2. {emmykit-0.3.2 → emmykit-0.3.4}/README.md +246 -783
  3. {emmykit-0.3.2 → emmykit-0.3.4}/scripts/smoke_pypi.sh +5 -4
  4. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/_version.py +1 -1
  5. emmykit-0.3.4/tests/test_readme_generator.py +99 -0
  6. {emmykit-0.3.2 → emmykit-0.3.4}/tools/generate_readme.py +196 -13
  7. emmykit-0.3.2/tests/test_readme_generator.py +0 -40
  8. {emmykit-0.3.2 → emmykit-0.3.4}/.gitignore +0 -0
  9. {emmykit-0.3.2 → emmykit-0.3.4}/CHANGELOG.md +0 -0
  10. {emmykit-0.3.2 → emmykit-0.3.4}/LICENSE +0 -0
  11. {emmykit-0.3.2 → emmykit-0.3.4}/docs/design.md +0 -0
  12. {emmykit-0.3.2 → emmykit-0.3.4}/pyproject.toml +0 -0
  13. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/__init__.py +0 -0
  14. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/constants.py +0 -0
  15. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/datetime_utils.py +0 -0
  16. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/diff_view.py +0 -0
  17. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/docker_utils.py +0 -0
  18. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/embedded_scripts.py +0 -0
  19. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/extensions.py +0 -0
  20. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/file_io.py +0 -0
  21. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/files.py +0 -0
  22. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/hosts.py +0 -0
  23. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/html_files.py +0 -0
  24. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/humanize.py +0 -0
  25. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/inflect_utils.py +0 -0
  26. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/introspection.py +0 -0
  27. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/io_subprocess.py +0 -0
  28. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/json_io.py +0 -0
  29. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/lint.py +0 -0
  30. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/llm.py +0 -0
  31. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/logging_utils.py +0 -0
  32. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/media.py +0 -0
  33. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/net_targets.py +0 -0
  34. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/network.py +0 -0
  35. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/numeric_helpers.py +0 -0
  36. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/options.py +0 -0
  37. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/paths_ensure.py +0 -0
  38. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/prompts.py +0 -0
  39. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/py.typed +0 -0
  40. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/python_env.py +0 -0
  41. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/safe_paths.py +0 -0
  42. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/system.py +0 -0
  43. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/text.py +0 -0
  44. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/text_constants.py +0 -0
  45. {emmykit-0.3.2 → emmykit-0.3.4}/src/emmykit/treeview.py +0 -0
  46. {emmykit-0.3.2 → emmykit-0.3.4}/tests/__init__.py +0 -0
  47. {emmykit-0.3.2 → emmykit-0.3.4}/tests/_baseline_pure_helpers.json +0 -0
  48. {emmykit-0.3.2 → emmykit-0.3.4}/tests/_baseline_signatures.json +0 -0
  49. {emmykit-0.3.2 → emmykit-0.3.4}/tests/test_public_api.py +0 -0
  50. {emmykit-0.3.2 → emmykit-0.3.4}/tests/test_pure_helpers.py +0 -0
  51. {emmykit-0.3.2 → emmykit-0.3.4}/tools/__init__.py +0 -0
  52. {emmykit-0.3.2 → emmykit-0.3.4}/tools/_layout.json +0 -0
  53. {emmykit-0.3.2 → emmykit-0.3.4}/tools/extract_symbols.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emmykit
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Layered Python utility package — datetime parsing, logging, safe filesystem ops, mojibake fixing, lint runners, media tools, LLM wrappers. Stdlib-only base.
5
5
  Project-URL: Homepage, https://github.com/killett/emmykit
6
6
  Project-URL: Repository, https://github.com/killett/emmykit
@@ -279,8 +279,10 @@ Description-Content-Type: text/markdown
279
279
 
280
280
  # emmykit
281
281
 
282
- Personal Python utility kit: 184 importable functions, classes, and constants spread across
283
- 32 submodules in 9 dependency layers. Base install is stdlib-only; heavier helpers
282
+ Personal Python utility kit: 184 importable functions, classes, and constants across 32 submodules
283
+ in 9 dependency layers (README highlights the user-facing surface — internal punctuation, frozenset
284
+ aliases, probe-target lists, and translation tables are referenced by section rather than enumerated).
285
+ Base install is stdlib-only; heavier helpers
284
286
  (datetime parsing via numpy/pandas/dateutil, mojibake fixing via ftfy, lint runners,
285
287
  LLM wrappers, ffmpeg/VLC controls) are gated behind optional extras so a bare
286
288
  `import emmykit` is fast and side-effect-free.
@@ -314,75 +316,31 @@ print(ek.my_capitalize("hello world")) # "Hello world"
314
316
  ## Table of contents
315
317
 
316
318
  - [`constants` — ANSI colors, unicode punctuation, default encoding, ignore-lists](#m-constants)
317
- - [`ANSI_CYAN`](#ansi_cyan)
318
- - [`ANSI_GREEN`](#ansi_green)
319
- - [`ANSI_RED`](#ansi_red)
320
- - [`ANSI_RESET`](#ansi_reset)
321
- - [`ANSI_YELLOW`](#ansi_yellow)
322
- - [`BACKTICK`](#backtick)
323
319
  - [`DEFAULT_ENCODING`](#default_encoding)
324
320
  - [`DEFAULT_EXCLUDE_DIRS`](#default_exclude_dirs)
325
- - [`EM_DASH`](#em_dash)
326
- - [`HORIZONTAL_ELLIPSIS`](#horizontal_ellipsis)
327
- - [`IGNORE_THESE_ERRORS`](#ignore_these_errors)
328
- - [`IGNORED_CODES`](#ignored_codes)
329
- - [`LDQUOTE`](#ldquote)
330
- - [`LSQUOTE`](#lsquote)
331
- - [`RDQUOTE`](#rdquote)
332
- - [`RSQUOTE`](#rsquote)
321
+ - [`ANSI color escapes`](#c-constants-ansi-color-escapes)
322
+ - [`IGNORED_CODES`](#c-constants-ignored-codes)
323
+ - [`IGNORE_THESE_ERRORS`](#c-constants-ignore-these-errors)
333
324
  - [`extensions` — File-extension lookup tables (audio / video / image / book / text / html / playlist / archive / subtitle)](#m-extensions)
334
325
  - [`ALL_KNOWN_EXTENSIONS`](#all_known_extensions)
335
- - [`ALL_KNOWN_EXTENSIONS_SET`](#all_known_extensions_set)
336
326
  - [`ARCHIVE_EXTENSIONS`](#archive_extensions)
337
- - [`ARCHIVE_EXTENSIONS_SET`](#archive_extensions_set)
338
327
  - [`AUDIO_EXTENSIONS`](#audio_extensions)
339
- - [`AUDIO_EXTENSIONS_SET`](#audio_extensions_set)
340
328
  - [`BOOK_EXTENSIONS`](#book_extensions)
341
- - [`BOOK_EXTENSIONS_SET`](#book_extensions_set)
342
329
  - [`HTML_EXTENSIONS`](#html_extensions)
343
- - [`HTML_EXTENSIONS_SET`](#html_extensions_set)
344
330
  - [`IMAGE_EXTENSIONS`](#image_extensions)
345
- - [`IMAGE_EXTENSIONS_SET`](#image_extensions_set)
346
331
  - [`PLAYLIST_EXTENSIONS`](#playlist_extensions)
347
- - [`PLAYLIST_EXTENSIONS_SET`](#playlist_extensions_set)
348
332
  - [`PYTHON_EXTENSIONS`](#python_extensions)
349
- - [`PYTHON_EXTENSIONS_SET`](#python_extensions_set)
350
333
  - [`SUBTITLE_EXTENSIONS`](#subtitle_extensions)
351
- - [`SUBTITLE_EXTENSIONS_SET`](#subtitle_extensions_set)
352
334
  - [`TEXT_ENCODINGS`](#text_encodings)
353
- - [`TEXT_ENCODINGS_SET`](#text_encodings_set)
354
335
  - [`TEXT_EXTENSIONS`](#text_extensions)
355
- - [`TEXT_EXTENSIONS_SET`](#text_extensions_set)
356
336
  - [`VIDEO_EXTENSIONS`](#video_extensions)
357
- - [`VIDEO_EXTENSIONS_SET`](#video_extensions_set)
358
- - [`net_targets` — Network-diagnostic probe targets](#m-net_targets)
359
- - [`DNS_TEST_NAMES`](#dns_test_names)
360
- - [`HTTP_PROBES`](#http_probes)
361
- - [`IPV4_TARGETS`](#ipv4_targets)
362
- - [`IPV6_TARGETS`](#ipv6_targets)
363
337
  - [`embedded_scripts` — Pre-packaged helper-script source-strings](#m-embedded_scripts)
364
- - [`MULTIREPLACE_SCRIPT`](#multireplace_script)
365
- - [`MYAUDIT_SCRIPT`](#myaudit_script)
366
- - [`MYDIFF_SCRIPT`](#mydiff_script)
367
- - [`PRINTALL_SCRIPT`](#printall_script)
368
- - [`SETUP_CARTOPY_SCRIPT`](#setup_cartopy_script)
369
- - [`TREEVIEW_SCRIPT`](#treeview_script)
370
- - [`UNIV_DEFS_SYS_PATH_SCRIPT`](#univ_defs_sys_path_script)
338
+ - [`7 embedded helper scripts`](#c-embedded_scripts-7-embedded-helper-scripts)
371
339
  - [`_version` — Package and Python version constants](#m-_version)
372
340
  - [`PY_VERSION`](#py_version)
373
341
  - [`options` — Options dataclasses for configuration](#m-options)
374
342
  - [`Options`](#options)
375
343
  - [`PlotOptions`](#plotoptions)
376
- - [`text_constants` — Translation tables for text normalization](#m-text_constants)
377
- - [`CHARACTERS_TO_SPACE`](#characters_to_space)
378
- - [`QUOTES_TO_DELETE`](#quotes_to_delete)
379
- - [`REPLACE_WITH_SPACE`](#replace_with_space)
380
- - [`TRANSLATION_TABLE`](#translation_table)
381
- - [`numeric_helpers` — Numeric parsing + unit-to-seconds conversion](#m-numeric_helpers)
382
- - [`is_float`](#is_float)
383
- - [`seconds_in_unit`](#seconds_in_unit)
384
- - [`paths_ensure` — Path normalization](#m-paths_ensure)
385
- - [`ensure_path`](#ensure_path)
386
344
  - [`inflect_utils` — Grammar + pluralization helpers](#m-inflect_utils)
387
345
  - [`InflectEngine`](#inflectengine)
388
346
  - [`my_plural`](#my_plural)
@@ -394,6 +352,8 @@ print(ek.my_capitalize("hello world")) # "Hello world"
394
352
  - [`MemoryHandler`](#memoryhandler)
395
353
  - [`print_all_errors`](#print_all_errors)
396
354
  - [`return_method_name`](#return_method_name)
355
+ - [`paths_ensure` — Path normalization](#m-paths_ensure)
356
+ - [`ensure_path`](#ensure_path)
397
357
  - [`safe_paths` — Exception-swallowing filesystem queries](#m-safe_paths)
398
358
  - [`ensure_dir`](#ensure_dir)
399
359
  - [`ensure_file`](#ensure_file)
@@ -424,9 +384,11 @@ print(ek.my_capitalize("hello world")) # "Hello world"
424
384
  - [`human_bytesize`](#human_bytesize)
425
385
  - [`round_out`](#round_out)
426
386
  - [`sci_exp`](#sci_exp)
387
+ - [`numeric_helpers` — Numeric parsing + unit-to-seconds conversion](#m-numeric_helpers)
388
+ - [`is_float`](#is_float)
389
+ - [`seconds_in_unit`](#seconds_in_unit)
427
390
  - [`datetime_utils` — Date / time parsing, formatting, timezone handling](#m-datetime_utils)
428
391
  - [`adaptive_date_labels`](#adaptive_date_labels)
429
- - [`ADAPTIVE_FORMAT_LEVELS`](#adaptive_format_levels)
430
392
  - [`AdaptiveDateFormatter`](#adaptivedateformatter)
431
393
  - [`AnyDateTimeType`](#anydatetimetype)
432
394
  - [`decimal_year_to_datetime`](#decimal_year_to_datetime)
@@ -466,8 +428,7 @@ print(ek.my_capitalize("hello world")) # "Hello world"
466
428
  - [`get_hostname_subprocess_hostname`](#get_hostname_subprocess_hostname)
467
429
  - [`get_hostname_subprocess_scutil`](#get_hostname_subprocess_scutil)
468
430
  - [`IS_NASA_COMPUTER`](#is_nasa_computer)
469
- - [`NASA_CASEFOLDED_COMPUTER_NAME_PREFIXES`](#nasa_casefolded_computer_name_prefixes)
470
- - [`NASA_COMPUTER_NAME_PREFIXES`](#nasa_computer_name_prefixes)
431
+ - [`NASA computer-name prefixes`](#c-hosts-nasa-computer-name-prefixes)
471
432
  - [`network` — Internet-connectivity probes](#m-network)
472
433
  - [`CheckResult`](#checkresult)
473
434
  - [`is_internet_available`](#is_internet_available)
@@ -539,78 +500,6 @@ _Layer 0._ `from emmykit.constants import …`
539
500
 
540
501
  Terminal escape codes, curly quotes, the em-dash, the package's UTF-8 default, the set of errno codes treated as benign by `safe_*`, and the flake8/autopep8 codes Emmy deliberately ignores.
541
502
 
542
- <a id="ansi_cyan"></a>
543
- <details>
544
- <summary><code>ANSI_CYAN</code> — this is blue on Linux but cyan on my Mac</summary>
545
-
546
- ```python
547
- ANSI_CYAN: str = '\x1b[94m'
548
- ```
549
-
550
- [source ↗](src/emmykit/constants.py#L16)
551
-
552
- </details>
553
-
554
- <a id="ansi_green"></a>
555
- <details>
556
- <summary><code>ANSI_GREEN</code> — this is bold/bright green on Linux but orange on my Mac</summary>
557
-
558
- ```python
559
- ANSI_GREEN: str = '\x1b[92m'
560
- ```
561
-
562
- [source ↗](src/emmykit/constants.py#L12)
563
-
564
- </details>
565
-
566
- <a id="ansi_red"></a>
567
- <details>
568
- <summary><code>ANSI_RED</code> — str = '\x1b[91m'</summary>
569
-
570
- ```python
571
- ANSI_RED: str = '\x1b[91m'
572
- ```
573
-
574
- [source ↗](src/emmykit/constants.py#L10)
575
-
576
- </details>
577
-
578
- <a id="ansi_reset"></a>
579
- <details>
580
- <summary><code>ANSI_RESET</code> — str = '\x1b[0m'</summary>
581
-
582
- ```python
583
- ANSI_RESET: str = '\x1b[0m'
584
- ```
585
-
586
- [source ↗](src/emmykit/constants.py#L18)
587
-
588
- </details>
589
-
590
- <a id="ansi_yellow"></a>
591
- <details>
592
- <summary><code>ANSI_YELLOW</code> — str = '\x1b[93m'</summary>
593
-
594
- ```python
595
- ANSI_YELLOW: str = '\x1b[93m'
596
- ```
597
-
598
- [source ↗](src/emmykit/constants.py#L14)
599
-
600
- </details>
601
-
602
- <a id="backtick"></a>
603
- <details>
604
- <summary><code>BACKTICK</code> — U+0060 "GRAVE ACCENT" (the backtick)</summary>
605
-
606
- ```python
607
- BACKTICK = '`'
608
- ```
609
-
610
- [source ↗](src/emmykit/constants.py#L44)
611
-
612
- </details>
613
-
614
503
  <a id="default_encoding"></a>
615
504
  <details>
616
505
  <summary><code>DEFAULT_ENCODING</code> — str = 'utf-8'</summary>
@@ -619,7 +508,7 @@ BACKTICK = '`'
619
508
  DEFAULT_ENCODING: str = 'utf-8'
620
509
  ```
621
510
 
622
- [source ↗](src/emmykit/constants.py#L8)
511
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/constants.py#L8)
623
512
 
624
513
  </details>
625
514
 
@@ -631,104 +520,37 @@ DEFAULT_ENCODING: str = 'utf-8'
631
520
  DEFAULT_EXCLUDE_DIRS: set[str] = {'.git', '.venv', '__pycache__', 'build', 'dist', 'venv'}
632
521
  ```
633
522
 
634
- [source ↗](src/emmykit/constants.py#L65)
523
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/constants.py#L65)
635
524
 
636
525
  </details>
637
526
 
638
- <a id="em_dash"></a>
527
+ <a id="c-constants-ansi-color-escapes"></a>
639
528
  <details>
640
- <summary><code>EM_DASH</code> — U+2014 "EM DASH"</summary>
529
+ <summary><code>ANSI color escapes</code> — 5 terminal-escape strings: ANSI_CYAN / GREEN / RED / RESET / YELLOW.</summary>
641
530
 
642
- ```python
643
- EM_DASH = '—'
644
- ```
531
+ **Includes:** `ANSI_CYAN`, `ANSI_GREEN`, `ANSI_RED`, `ANSI_RESET`, `ANSI_YELLOW`.
645
532
 
646
- [source ↗](src/emmykit/constants.py#L56)
533
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/constants.py#L16)
647
534
 
648
535
  </details>
649
536
 
650
- <a id="horizontal_ellipsis"></a>
537
+ <a id="c-constants-ignored-codes"></a>
651
538
  <details>
652
- <summary><code>HORIZONTAL_ELLIPSIS</code> — U+2026 "HORIZONTAL ELLIPSIS" (three closely spaced periods)</summary>
539
+ <summary><code>IGNORED_CODES</code> — flake8 + autopep8 codes Emmy deliberately ignores.</summary>
653
540
 
654
- ```python
655
- HORIZONTAL_ELLIPSIS = '…'
656
- ```
657
-
658
- [source ↗](src/emmykit/constants.py#L54)
659
-
660
- </details>
661
-
662
- <a id="ignore_these_errors"></a>
663
- <details>
664
- <summary><code>IGNORE_THESE_ERRORS</code> — Final[frozenset[int]] (6 items)</summary>
665
-
666
- ```python
667
- IGNORE_THESE_ERRORS: Final[frozenset[int]] = frozenset({1, 116, 13, 2, 20, 40})
668
- ```
669
-
670
- [source ↗](src/emmykit/constants.py#L58)
671
-
672
- </details>
673
-
674
- <a id="ignored_codes"></a>
675
- <details>
676
- <summary><code>IGNORED_CODES</code> — list[str] (21 items)</summary>
541
+ **Includes:** `IGNORED_CODES`.
677
542
 
678
- ```python
679
- IGNORED_CODES: list[str] = ['W503', 'W504', 'E117', 'E127', 'E122', 'E128', 'E201', 'E202', 'E203', 'E211', 'E221',
680
- 'E222', 'E226', 'E227', 'E241', 'E251', 'E262', 'E271', 'E272', 'E701', 'E702']
681
- ```
682
-
683
- [source ↗](src/emmykit/constants.py#L20)
543
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/constants.py#L20)
684
544
 
685
545
  </details>
686
546
 
687
- <a id="ldquote"></a>
547
+ <a id="c-constants-ignore-these-errors"></a>
688
548
  <details>
689
- <summary><code>LDQUOTE</code> — U+201C "LEFT DOUBLE QUOTATION MARK"</summary>
549
+ <summary><code>IGNORE_THESE_ERRORS</code> — errno codes treated as benign by safe_* helpers.</summary>
690
550
 
691
- ```python
692
- LDQUOTE = '“'
693
- ```
551
+ **Includes:** `IGNORE_THESE_ERRORS`.
694
552
 
695
- [source ↗](src/emmykit/constants.py#L50)
696
-
697
- </details>
698
-
699
- <a id="lsquote"></a>
700
- <details>
701
- <summary><code>LSQUOTE</code> — U+2018 "LEFT SINGLE QUOTATION MARK" (curly apostrophe)</summary>
702
-
703
- ```python
704
- LSQUOTE = '‘'
705
- ```
706
-
707
- [source ↗](src/emmykit/constants.py#L46)
708
-
709
- </details>
710
-
711
- <a id="rdquote"></a>
712
- <details>
713
- <summary><code>RDQUOTE</code> — U+201D "RIGHT DOUBLE QUOTATION MARK"</summary>
714
-
715
- ```python
716
- RDQUOTE = '”'
717
- ```
718
-
719
- [source ↗](src/emmykit/constants.py#L52)
720
-
721
- </details>
722
-
723
- <a id="rsquote"></a>
724
- <details>
725
- <summary><code>RSQUOTE</code> — U+2019 "RIGHT SINGLE QUOTATION MARK" (curly apostrophe)</summary>
726
-
727
- ```python
728
- RSQUOTE = '’'
729
- ```
730
-
731
- [source ↗](src/emmykit/constants.py#L48)
553
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/constants.py#L58)
732
554
 
733
555
  </details>
734
556
 
@@ -739,6 +561,8 @@ _Layer 0._ `from emmykit.extensions import …`
739
561
 
740
562
  Lists and frozensets of common file extensions per media kind, plus an `ALL_KNOWN_EXTENSIONS` umbrella and the canonical `TEXT_ENCODINGS` ordering used by `my_fopen` when sniffing.
741
563
 
564
+ Each `*_EXTENSIONS` list has a `*_EXTENSIONS_SET` frozenset alias for fast membership tests.
565
+
742
566
  <a id="all_known_extensions"></a>
743
567
  <details>
744
568
  <summary><code>ALL_KNOWN_EXTENSIONS</code> — Final[tuple[str, ...]] (979 items)</summary>
@@ -749,19 +573,7 @@ ALL_KNOWN_EXTENSIONS: Final[tuple[str, ...]] = ('.py', '.pyw', '.html', '.htm',
749
573
  '.properties', '.rtf', '.rst', ...)
750
574
  ```
751
575
 
752
- [source ↗](src/emmykit/extensions.py#L319)
753
-
754
- </details>
755
-
756
- <a id="all_known_extensions_set"></a>
757
- <details>
758
- <summary><code>ALL_KNOWN_EXTENSIONS_SET</code> — Final[frozenset[str]] (979 items)</summary>
759
-
760
- ```python
761
- ALL_KNOWN_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.001', '.002', '.003', '.004', '.005', '.006', '.007', '.008', '.009', '.010', '.011', '.012', '.013', '.014', '.015', '.016', '.017', '.018', '.019', '.020', '.021', '.022', '.023', '...
762
- ```
763
-
764
- [source ↗](src/emmykit/extensions.py#L323)
576
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L319)
765
577
 
766
578
  </details>
767
579
 
@@ -775,19 +587,7 @@ ARCHIVE_EXTENSIONS: Final[tuple[str, ...]] = ('.zip', '.rar', '.7z', '.tar', '.g
775
587
  '.ear', '.iso', ...)
776
588
  ```
777
589
 
778
- [source ↗](src/emmykit/extensions.py#L308)
779
-
780
- </details>
781
-
782
- <a id="archive_extensions_set"></a>
783
- <details>
784
- <summary><code>ARCHIVE_EXTENSIONS_SET</code> — Final[frozenset[str]] (376 items)</summary>
785
-
786
- ```python
787
- ARCHIVE_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.001', '.002', '.003', '.004', '.005', '.006', '.007', '.008', '.009', '.010', '.011', '.012', '.013', '.014', '.015', '.016', '.017', '.018', '.019', '.020', '.021', '.022', '.023', '...
788
- ```
789
-
790
- [source ↗](src/emmykit/extensions.py#L312)
590
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L308)
791
591
 
792
592
  </details>
793
593
 
@@ -801,19 +601,7 @@ AUDIO_EXTENSIONS: Final[tuple[str, ...]] = ('.mp3', '.wav', '.flac', '.aac', '.o
801
601
  '.ra', '.rm', '.oga', ...)
802
602
  ```
803
603
 
804
- [source ↗](src/emmykit/extensions.py#L206)
805
-
806
- </details>
807
-
808
- <a id="audio_extensions_set"></a>
809
- <details>
810
- <summary><code>AUDIO_EXTENSIONS_SET</code> — Final[frozenset[str]] (112 items)</summary>
811
-
812
- ```python
813
- AUDIO_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.3g2', '.3ga', '.3gp', '.669', '.aa', '.aac', '.aax', '.aaxc', '.abc', '.ac3', '.adts', '.adx', '.aif', '.aifc', '.aiff', '.alac', '.amr', '.ape', '.asf', '.ast', '.au', '.awb', '.bcst...
814
- ```
815
-
816
- [source ↗](src/emmykit/extensions.py#L228)
604
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L206)
817
605
 
818
606
  </details>
819
607
 
@@ -827,19 +615,7 @@ BOOK_EXTENSIONS: Final[tuple[str, ...]] = ('.epub', '.pdf', '.txt', '.rtf', '.ht
827
615
  '.fb2', '.fbz', ...)
828
616
  ```
829
617
 
830
- [source ↗](src/emmykit/extensions.py#L80)
831
-
832
- </details>
833
-
834
- <a id="book_extensions_set"></a>
835
- <details>
836
- <summary><code>BOOK_EXTENSIONS_SET</code> — Final[frozenset[str]] (63 items)</summary>
837
-
838
- ```python
839
- BOOK_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.azw', '.azw1', '.azw3', '.azw4', '.azw6', '.cb7', '.cba', '.cbr', '.cbt', '.cbz', '.ceb', '.chm', '.djv', '.djvu', '.doc', '.docx', '.dvi', '.epub', '.fb2', '.fbz', '.htm', '.html', '...
840
- ```
841
-
842
- [source ↗](src/emmykit/extensions.py#L175)
618
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L80)
843
619
 
844
620
  </details>
845
621
 
@@ -851,19 +627,7 @@ BOOK_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.azw', '.azw1', '.azw3'
851
627
  HTML_EXTENSIONS: Final[tuple[str, ...]] = ('.html', '.htm', '.xhtml')
852
628
  ```
853
629
 
854
- [source ↗](src/emmykit/extensions.py#L47)
855
-
856
- </details>
857
-
858
- <a id="html_extensions_set"></a>
859
- <details>
860
- <summary><code>HTML_EXTENSIONS_SET</code> — Final[frozenset[str]] (3 items)</summary>
861
-
862
- ```python
863
- HTML_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.htm', '.html', '.xhtml'})
864
- ```
865
-
866
- [source ↗](src/emmykit/extensions.py#L49)
630
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L47)
867
631
 
868
632
  </details>
869
633
 
@@ -877,19 +641,7 @@ IMAGE_EXTENSIONS: Final[tuple[str, ...]] = ('.bmp', '.dib', '.gif', '.jpeg', '.j
877
641
  '.hdr', '.exr', '.webp', ...)
878
642
  ```
879
643
 
880
- [source ↗](src/emmykit/extensions.py#L243)
881
-
882
- </details>
883
-
884
- <a id="image_extensions_set"></a>
885
- <details>
886
- <summary><code>IMAGE_EXTENSIONS_SET</code> — Final[frozenset[str]] (127 items)</summary>
887
-
888
- ```python
889
- IMAGE_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.3fr', '.afphoto', '.ai', '.apng', '.arw', '.ase', '.aseprite', '.avif', '.basis', '.bil', '.bip', '.bmp', '.bpg', '.bsq', '.cdr', '.cgm', '.clip', '.cr2', '.cr3', '.crw', '.dcm', '.dc...
890
- ```
891
-
892
- [source ↗](src/emmykit/extensions.py#L268)
644
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L243)
893
645
 
894
646
  </details>
895
647
 
@@ -903,19 +655,7 @@ PLAYLIST_EXTENSIONS: Final[tuple[str, ...]] = ('.m3u', '.m3u8', '.pls', '.xspf',
903
655
  '.aimppl4', '.pla', '.xml', ...)
904
656
  ```
905
657
 
906
- [source ↗](src/emmykit/extensions.py#L270)
907
-
908
- </details>
909
-
910
- <a id="playlist_extensions_set"></a>
911
- <details>
912
- <summary><code>PLAYLIST_EXTENSIONS_SET</code> — Final[frozenset[str]] (28 items)</summary>
913
-
914
- ```python
915
- PLAYLIST_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.aimppl', '.aimppl4', '.asx', '.b4s', '.cue', '.dpl', '.f4m', '.fpl', '.ism', '.ismc', '.isml', '.ismv', '.m3u', '.m3u8', '.mpcpl', '.mpd', '.pla', '.pls', '.ram', '.smi', '.smil', '.w...
916
- ```
917
-
918
- [source ↗](src/emmykit/extensions.py#L278)
658
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L270)
919
659
 
920
660
  </details>
921
661
 
@@ -927,19 +667,7 @@ PLAYLIST_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.aimppl', '.aimppl4
927
667
  PYTHON_EXTENSIONS: Final[tuple[str, ...]] = ('.py', '.pyw')
928
668
  ```
929
669
 
930
- [source ↗](src/emmykit/extensions.py#L43)
931
-
932
- </details>
933
-
934
- <a id="python_extensions_set"></a>
935
- <details>
936
- <summary><code>PYTHON_EXTENSIONS_SET</code> — Final[frozenset[str]] (2 items)</summary>
937
-
938
- ```python
939
- PYTHON_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.py', '.pyw'})
940
- ```
941
-
942
- [source ↗](src/emmykit/extensions.py#L45)
670
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L43)
943
671
 
944
672
  </details>
945
673
 
@@ -953,19 +681,7 @@ SUBTITLE_EXTENSIONS: Final[tuple[str, ...]] = ('.srt', '.sub', '.idx', '.ass', '
953
681
  '.mpl2', '.sbt', ...)
954
682
  ```
955
683
 
956
- [source ↗](src/emmykit/extensions.py#L230)
957
-
958
- </details>
959
-
960
- <a id="subtitle_extensions_set"></a>
961
- <details>
962
- <summary><code>SUBTITLE_EXTENSIONS_SET</code> — Final[frozenset[str]] (47 items)</summary>
963
-
964
- ```python
965
- SUBTITLE_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.890', '.aqt', '.asc', '.ass', '.cap', '.cin', '.dfxp', '.dks', '.ebu', '.gsub', '.idx', '.itt', '.jss', '.lrc', '.mcc', '.mks', '.mpl', '.mpl2', '.onl', '.pac', '.pjs', '.psb', '.rt',...
966
- ```
967
-
968
- [source ↗](src/emmykit/extensions.py#L241)
684
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L230)
969
685
 
970
686
  </details>
971
687
 
@@ -979,19 +695,7 @@ TEXT_ENCODINGS: Final[tuple[str, ...]] = ('utf-8', 'latin-1', 'ascii', 'iso-8859
979
695
  'cp1253', 'cp1254', ...)
980
696
  ```
981
697
 
982
- [source ↗](src/emmykit/extensions.py#L8)
983
-
984
- </details>
985
-
986
- <a id="text_encodings_set"></a>
987
- <details>
988
- <summary><code>TEXT_ENCODINGS_SET</code> — sets are faster</summary>
989
-
990
- ```python
991
- TEXT_ENCODINGS_SET: Final[frozenset[str]] = frozenset({'ascii', 'base64', 'big5', 'big5hkscs', 'bz2', 'charmap', 'cp037', 'cp1006', 'cp1026', 'cp1125', 'cp1140', 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255', 'cp1256', 'cp1257'...
992
- ```
993
-
994
- [source ↗](src/emmykit/extensions.py#L41)
698
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L8)
995
699
 
996
700
  </details>
997
701
 
@@ -1005,19 +709,7 @@ TEXT_EXTENSIONS: Final[tuple[str, ...]] = ('.txt', '.html', '.htm', '.csv', '.js
1005
709
  '.sgml', '.tex', ...)
1006
710
  ```
1007
711
 
1008
- [source ↗](src/emmykit/extensions.py#L51)
1009
-
1010
- </details>
1011
-
1012
- <a id="text_extensions_set"></a>
1013
- <details>
1014
- <summary><code>TEXT_EXTENSIONS_SET</code> — Final[frozenset[str]] (143 items)</summary>
1015
-
1016
- ```python
1017
- TEXT_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.adoc', '.asciidoc', '.ass', '.atom', '.aux', '.bash', '.bat', '.bib', '.c', '.cff', '.cfg', '.cjs', '.cls', '.cmd', '.conf', '.cpp', '.cs', '.css', '.csv', '.cue', '.desktop', '.diff'...
1018
- ```
1019
-
1020
- [source ↗](src/emmykit/extensions.py#L78)
712
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L51)
1021
713
 
1022
714
  </details>
1023
715
 
@@ -1031,74 +723,7 @@ VIDEO_EXTENSIONS: Final[tuple[str, ...]] = ('.mp4', '.mkv', '.mov', '.avi', '.mp
1031
723
  '.asf', '.f4v', '.mxf', ...)
1032
724
  ```
1033
725
 
1034
- [source ↗](src/emmykit/extensions.py#L177)
1035
-
1036
- </details>
1037
-
1038
- <a id="video_extensions_set"></a>
1039
- <details>
1040
- <summary><code>VIDEO_EXTENSIONS_SET</code> — Final[frozenset[str]] (133 items)</summary>
1041
-
1042
- ```python
1043
- VIDEO_EXTENSIONS_SET: Final[frozenset[str]] = frozenset({'.264', '.3g2', '.3gp', '.3gpp', '.amv', '.arf', '.asf', '.av1', '.avi', '.b5t', '.b6t', '.bik', '.bin', '.bk2', '.braw', '.bup', '.bvr', '.bwt', '.ccd', '.cine', '.cue', '.dav', '.dcr'...
1044
- ```
1045
-
1046
- [source ↗](src/emmykit/extensions.py#L204)
1047
-
1048
- </details>
1049
-
1050
- <a id="m-net_targets"></a>
1051
- ### `net_targets` — Network-diagnostic probe targets
1052
-
1053
- _Layer 0._ `from emmykit.net_targets import …`
1054
-
1055
- IPv4, IPv6, HTTP, and DNS endpoint lists used by `is_internet_available` to verify connectivity beyond DNS resolution.
1056
-
1057
- <a id="dns_test_names"></a>
1058
- <details>
1059
- <summary><code>DNS_TEST_NAMES</code> — list[str] (5 items)</summary>
1060
-
1061
- ```python
1062
- DNS_TEST_NAMES: list[str] = ['example.com', 'cloudflare.com', 'google.com', 'one.one.one.one', 'dns.google']
1063
- ```
1064
-
1065
- [source ↗](src/emmykit/net_targets.py#L40)
1066
-
1067
- </details>
1068
-
1069
- <a id="http_probes"></a>
1070
- <details>
1071
- <summary><code>HTTP_PROBES</code> — list[dict[str, Any]] (3 items)</summary>
1072
-
1073
- ```python
1074
- HTTP_PROBES: list[dict[str, Any]] = [{'url': 'https://www.gstatic.com/generate_204', 'method': 'GET', 'expect': {'status': 204}, 'note': 'Android/gstatic 204 probe'}, {'url': 'http://www.gstatic.com/generate_204', 'method': 'GET', '...
1075
- ```
1076
-
1077
- [source ↗](src/emmykit/net_targets.py#L19)
1078
-
1079
- </details>
1080
-
1081
- <a id="ipv4_targets"></a>
1082
- <details>
1083
- <summary><code>IPV4_TARGETS</code> — list[tuple[str, int]] (4 items)</summary>
1084
-
1085
- ```python
1086
- IPV4_TARGETS: list[tuple[str, int]] = [('1.1.1.1', 443), ('8.8.8.8', 853), ('9.9.9.9', 443), ('208.67.222.222', 443)]
1087
- ```
1088
-
1089
- [source ↗](src/emmykit/net_targets.py#L7)
1090
-
1091
- </details>
1092
-
1093
- <a id="ipv6_targets"></a>
1094
- <details>
1095
- <summary><code>IPV6_TARGETS</code> — list[tuple[str, int]] (2 items)</summary>
1096
-
1097
- ```python
1098
- IPV6_TARGETS: list[tuple[str, int]] = [('2606:4700:4700::1111', 443), ('2001:4860:4860::8888', 53)]
1099
- ```
1100
-
1101
- [source ↗](src/emmykit/net_targets.py#L14)
726
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/extensions.py#L177)
1102
727
 
1103
728
  </details>
1104
729
 
@@ -1109,87 +734,13 @@ _Layer 0._ `from emmykit.embedded_scripts import …`
1109
734
 
1110
735
  Multi-kilobyte Python script literals shipped as importable strings — used by Emmy's external automation to drop drop-in helpers into other projects.
1111
736
 
1112
- <a id="multireplace_script"></a>
737
+ <a id="c-embedded_scripts-7-embedded-helper-scripts"></a>
1113
738
  <details>
1114
- <summary><code>MULTIREPLACE_SCRIPT</code> — str (2742 chars)</summary>
739
+ <summary><code>7 embedded helper scripts</code> — Multi-KB Python script source strings shipped as importable constants.</summary>
1115
740
 
1116
- ```python
1117
- MULTIREPLACE_SCRIPT: str = '<2,742-char Python script source, 61 lines>'
1118
- ```
1119
-
1120
- [source ↗](src/emmykit/embedded_scripts.py#L156)
1121
-
1122
- </details>
1123
-
1124
- <a id="myaudit_script"></a>
1125
- <details>
1126
- <summary><code>MYAUDIT_SCRIPT</code> — str (3021 chars)</summary>
1127
-
1128
- ```python
1129
- MYAUDIT_SCRIPT: str = '<3,021-char Python script source, 64 lines>'
1130
- ```
1131
-
1132
- [source ↗](src/emmykit/embedded_scripts.py#L91)
1133
-
1134
- </details>
1135
-
1136
- <a id="mydiff_script"></a>
1137
- <details>
1138
- <summary><code>MYDIFF_SCRIPT</code> — str (3373 chars)</summary>
1139
-
1140
- ```python
1141
- MYDIFF_SCRIPT: str = '<3,373-char Python script source, 72 lines>'
1142
- ```
741
+ **Includes:** `MULTIREPLACE_SCRIPT`, `MYAUDIT_SCRIPT`, `MYDIFF_SCRIPT`, `PRINTALL_SCRIPT`, `SETUP_CARTOPY_SCRIPT`, `TREEVIEW_SCRIPT`, `UNIV_DEFS_SYS_PATH_SCRIPT`.
1143
742
 
1144
- [source ↗](src/emmykit/embedded_scripts.py#L18)
1145
-
1146
- </details>
1147
-
1148
- <a id="printall_script"></a>
1149
- <details>
1150
- <summary><code>PRINTALL_SCRIPT</code> — str (9957 chars)</summary>
1151
-
1152
- ```python
1153
- PRINTALL_SCRIPT: str = '<9,957-char Python script source, 261 lines>'
1154
- ```
1155
-
1156
- [source ↗](src/emmykit/embedded_scripts.py#L283)
1157
-
1158
- </details>
1159
-
1160
- <a id="setup_cartopy_script"></a>
1161
- <details>
1162
- <summary><code>SETUP_CARTOPY_SCRIPT</code> — str (532 chars)</summary>
1163
-
1164
- ```python
1165
- SETUP_CARTOPY_SCRIPT: str = '<532-char Python script source, 16 lines>'
1166
- ```
1167
-
1168
- [source ↗](src/emmykit/embedded_scripts.py#L545)
1169
-
1170
- </details>
1171
-
1172
- <a id="treeview_script"></a>
1173
- <details>
1174
- <summary><code>TREEVIEW_SCRIPT</code> — str (2913 chars)</summary>
1175
-
1176
- ```python
1177
- TREEVIEW_SCRIPT: str = '<2,913-char Python script source, 64 lines>'
1178
- ```
1179
-
1180
- [source ↗](src/emmykit/embedded_scripts.py#L218)
1181
-
1182
- </details>
1183
-
1184
- <a id="univ_defs_sys_path_script"></a>
1185
- <details>
1186
- <summary><code>UNIV_DEFS_SYS_PATH_SCRIPT</code> — str (373 chars)</summary>
1187
-
1188
- ```python
1189
- UNIV_DEFS_SYS_PATH_SCRIPT: str = '<373-char Python script source, 10 lines>'
1190
- ```
1191
-
1192
- [source ↗](src/emmykit/embedded_scripts.py#L7)
743
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/embedded_scripts.py#L156)
1193
744
 
1194
745
  </details>
1195
746
 
@@ -1208,7 +759,7 @@ Single source of truth for `emmykit.__version__` (read by hatchling at build-tim
1208
759
  PY_VERSION: Final[float] = 3.12
1209
760
  ```
1210
761
 
1211
- [source ↗](src/emmykit/_version.py#L13)
762
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/_version.py#L13)
1212
763
 
1213
764
  </details>
1214
765
 
@@ -1231,7 +782,7 @@ Options() -> 'None'
1231
782
  Class that has all global options in one place.
1232
783
  ```
1233
784
 
1234
- [source ↗](src/emmykit/options.py#L8)
785
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/options.py#L8)
1235
786
 
1236
787
  </details>
1237
788
 
@@ -1247,142 +798,7 @@ PlotOptions() -> 'None'
1247
798
  Global figure options.
1248
799
  ```
1249
800
 
1250
- [source ↗](src/emmykit/options.py#L25)
1251
-
1252
- </details>
1253
-
1254
- <a id="m-text_constants"></a>
1255
- ### `text_constants` — Translation tables for text normalization
1256
-
1257
- _Layer 1._ `from emmykit.text_constants import …`
1258
-
1259
- Character maps used by `normalize_for_search` to fold quotes, ellipses, and other punctuation into ASCII equivalents.
1260
-
1261
- <a id="characters_to_space"></a>
1262
- <details>
1263
- <summary><code>CHARACTERS_TO_SPACE</code> — str = '._-—…'</summary>
1264
-
1265
- ```python
1266
- CHARACTERS_TO_SPACE = '._-—…'
1267
- ```
1268
-
1269
- [source ↗](src/emmykit/text_constants.py#L15)
1270
-
1271
- </details>
1272
-
1273
- <a id="quotes_to_delete"></a>
1274
- <details>
1275
- <summary><code>QUOTES_TO_DELETE</code> — str = '"\'`‘’“”'</summary>
1276
-
1277
- ```python
1278
- QUOTES_TO_DELETE = '"\'`‘’“”'
1279
- ```
1280
-
1281
- [source ↗](src/emmykit/text_constants.py#L19)
1282
-
1283
- </details>
1284
-
1285
- <a id="replace_with_space"></a>
1286
- <details>
1287
- <summary><code>REPLACE_WITH_SPACE</code> — str = ' '</summary>
1288
-
1289
- ```python
1290
- REPLACE_WITH_SPACE = ' '
1291
- ```
1292
-
1293
- [source ↗](src/emmykit/text_constants.py#L17)
1294
-
1295
- </details>
1296
-
1297
- <a id="translation_table"></a>
1298
- <details>
1299
- <summary><code>TRANSLATION_TABLE</code> — dict (12 entries)</summary>
1300
-
1301
- ```python
1302
- TRANSLATION_TABLE = {34: None,
1303
- 39: None,
1304
- 45: 32,
1305
- 46: 32,
1306
- 95: 32,
1307
- 96: None,
1308
- 8212: 32,
1309
- 8216: None,
1310
- 8217: None,
1311
- 8220: None,
1312
- 8221: None,
1313
- 8230: 32}
1314
- ```
1315
-
1316
- [source ↗](src/emmykit/text_constants.py#L21)
1317
-
1318
- </details>
1319
-
1320
- <a id="m-numeric_helpers"></a>
1321
- ### `numeric_helpers` — Numeric parsing + unit-to-seconds conversion
1322
-
1323
- _Layer 1._ `from emmykit.numeric_helpers import …`
1324
-
1325
- Tiny helpers shared by `humanize` and `datetime_utils` so neither has to pull in the other.
1326
-
1327
- <a id="is_float"></a>
1328
- <details>
1329
- <summary><code>is_float</code> — Check if a string can be parsed as a float.</summary>
1330
-
1331
- ```python
1332
- is_float(s: 'str') -> 'bool'
1333
- ```
1334
-
1335
- ```text
1336
- Check if a string can be parsed as a float.
1337
- ```
1338
-
1339
- [source ↗](src/emmykit/numeric_helpers.py#L56)
1340
-
1341
- </details>
1342
-
1343
- <a id="seconds_in_unit"></a>
1344
- <details>
1345
- <summary><code>seconds_in_unit</code> — Return the number of seconds in a given time unit.</summary>
1346
-
1347
- ```python
1348
- seconds_in_unit(unit: 'str') -> 'float'
1349
- ```
1350
-
1351
- ```text
1352
- Return the number of seconds in a given time unit.
1353
- ```
1354
-
1355
- [source ↗](src/emmykit/numeric_helpers.py#L49)
1356
-
1357
- </details>
1358
-
1359
- <a id="m-paths_ensure"></a>
1360
- ### `paths_ensure` — Path normalization
1361
-
1362
- _Layer 1._ `from emmykit.paths_ensure import …`
1363
-
1364
- Leaf helper that coerces `os.PathLike` / `str` arguments into resolved `Path` objects.
1365
-
1366
- <a id="ensure_path"></a>
1367
- <details>
1368
- <summary><code>ensure_path</code> — Ensure that the path is a Path. If not, make it a Path.</summary>
1369
-
1370
- ```python
1371
- ensure_path(path: 'str | os.PathLike[str]', absolute: 'bool' = True) -> 'Path'
1372
- ```
1373
-
1374
- ```text
1375
- Ensure that the path is a Path. If not, make it a Path.
1376
-
1377
- Args:
1378
- path: The path to ensure is a Path object.
1379
- absolute: If True (default), return an absolute path without resolving symlinks.
1380
-
1381
- Returns:
1382
- A Path object (expanded for "~"). If absolute=True, it's absolute; otherwise it may be relative.
1383
- ```
1384
-
1385
- [source ↗](src/emmykit/paths_ensure.py#L10)
801
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/options.py#L25)
1386
802
 
1387
803
  </details>
1388
804
 
@@ -1407,7 +823,7 @@ Protocol for the 'inflect' library's engine interface.
1407
823
 
1408
824
  **Public methods:** `plural`, `plural_noun`.
1409
825
 
1410
- [source ↗](src/emmykit/inflect_utils.py#L8)
826
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/inflect_utils.py#L8)
1411
827
 
1412
828
  </details>
1413
829
 
@@ -1443,7 +859,7 @@ Raises:
1443
859
  None.
1444
860
  ```
1445
861
 
1446
- [source ↗](src/emmykit/inflect_utils.py#L42)
862
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/inflect_utils.py#L42)
1447
863
 
1448
864
  </details>
1449
865
 
@@ -1478,7 +894,7 @@ Raises:
1478
894
  None (file creation errors are caught and logged to stdout).
1479
895
  ```
1480
896
 
1481
- [source ↗](src/emmykit/logging_utils.py#L63)
897
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/logging_utils.py#L63)
1482
898
 
1483
899
  </details>
1484
900
 
@@ -1499,7 +915,7 @@ Args:
1499
915
  rawlog : If True, use a simple log format without timestamps or levels.
1500
916
  ```
1501
917
 
1502
- [source ↗](src/emmykit/logging_utils.py#L46)
918
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/logging_utils.py#L46)
1503
919
 
1504
920
  </details>
1505
921
 
@@ -1517,7 +933,7 @@ A logging handler that flushes the stream after emitting each log so the logs ar
1517
933
 
1518
934
  **Public methods:** `acquire`, `addFilter`, `close`, `createLock`, `emit`, `filter`, `flush`, `format`, `get_name`, `handle`, `handleError`, `release`, `removeFilter`, `setFormatter`, `setLevel`, `setStream`, `set_name`.
1519
935
 
1520
- [source ↗](src/emmykit/logging_utils.py#L25)
936
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/logging_utils.py#L25)
1521
937
 
1522
938
  </details>
1523
939
 
@@ -1535,7 +951,7 @@ A logging filter that only allows logs up to a certain level to pass through, so
1535
951
 
1536
952
  **Public methods:** `filter`.
1537
953
 
1538
- [source ↗](src/emmykit/logging_utils.py#L35)
954
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/logging_utils.py#L35)
1539
955
 
1540
956
  </details>
1541
957
 
@@ -1553,7 +969,7 @@ A logging handler that stores logs in memory so the errors can be printed at the
1553
969
 
1554
970
  **Public methods:** `acquire`, `addFilter`, `close`, `createLock`, `emit`, `filter`, `flush`, `format`, `get_name`, `handle`, `handleError`, `release`, `removeFilter`, `setFormatter`, `setLevel`, `set_name`.
1555
971
 
1556
- [source ↗](src/emmykit/logging_utils.py#L12)
972
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/logging_utils.py#L12)
1557
973
 
1558
974
  </details>
1559
975
 
@@ -1569,7 +985,7 @@ print_all_errors(memory_handler: 'MemoryHandler', rawlog: 'bool' = False) -> 'No
1569
985
  Print all the captured error messages.
1570
986
  ```
1571
987
 
1572
- [source ↗](src/emmykit/logging_utils.py#L147)
988
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/logging_utils.py#L147)
1573
989
 
1574
990
  </details>
1575
991
 
@@ -1602,7 +1018,37 @@ Raises:
1602
1018
  if sys._getframe or inspect fails.
1603
1019
  ```
1604
1020
 
1605
- [source ↗](src/emmykit/logging_utils.py#L157)
1021
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/logging_utils.py#L157)
1022
+
1023
+ </details>
1024
+
1025
+ <a id="m-paths_ensure"></a>
1026
+ ### `paths_ensure` — Path normalization
1027
+
1028
+ _Layer 1._ `from emmykit.paths_ensure import …`
1029
+
1030
+ Leaf helper that coerces `os.PathLike` / `str` arguments into resolved `Path` objects.
1031
+
1032
+ <a id="ensure_path"></a>
1033
+ <details>
1034
+ <summary><code>ensure_path</code> — Ensure that the path is a Path. If not, make it a Path.</summary>
1035
+
1036
+ ```python
1037
+ ensure_path(path: 'str | os.PathLike[str]', absolute: 'bool' = True) -> 'Path'
1038
+ ```
1039
+
1040
+ ```text
1041
+ Ensure that the path is a Path. If not, make it a Path.
1042
+
1043
+ Args:
1044
+ path: The path to ensure is a Path object.
1045
+ absolute: If True (default), return an absolute path without resolving symlinks.
1046
+
1047
+ Returns:
1048
+ A Path object (expanded for "~"). If absolute=True, it's absolute; otherwise it may be relative.
1049
+ ```
1050
+
1051
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/paths_ensure.py#L10)
1606
1052
 
1607
1053
  </details>
1608
1054
 
@@ -1640,7 +1086,7 @@ Raises:
1640
1086
  NotADirectoryError: If the path exists but is not a directory.
1641
1087
  ```
1642
1088
 
1643
- [source ↗](src/emmykit/safe_paths.py#L63)
1089
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/safe_paths.py#L63)
1644
1090
 
1645
1091
  </details>
1646
1092
 
@@ -1675,7 +1121,7 @@ Raises:
1675
1121
  ValueError: If raise_on_empty is True and the file is empty (or bad permissions, etc.)
1676
1122
  ```
1677
1123
 
1678
- [source ↗](src/emmykit/safe_paths.py#L14)
1124
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/safe_paths.py#L14)
1679
1125
 
1680
1126
  </details>
1681
1127
 
@@ -1702,7 +1148,7 @@ Returns:
1702
1148
  The ctime of the file in seconds or nanoseconds, or None if an error occurred.
1703
1149
  ```
1704
1150
 
1705
- [source ↗](src/emmykit/safe_paths.py#L296)
1151
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/safe_paths.py#L296)
1706
1152
 
1707
1153
  </details>
1708
1154
 
@@ -1726,7 +1172,7 @@ Returns:
1726
1172
  For certain access/loop issues, returns True to avoid misclassifying as 'missing'.
1727
1173
  ```
1728
1174
 
1729
- [source ↗](src/emmykit/safe_paths.py#L122)
1175
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/safe_paths.py#L122)
1730
1176
 
1731
1177
  </details>
1732
1178
 
@@ -1754,7 +1200,7 @@ Raises:
1754
1200
  some OSError variations. But not all.
1755
1201
  ```
1756
1202
 
1757
- [source ↗](src/emmykit/safe_paths.py#L194)
1203
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/safe_paths.py#L194)
1758
1204
 
1759
1205
  </details>
1760
1206
 
@@ -1782,7 +1228,7 @@ Raises:
1782
1228
  some OSError variations. But not all.
1783
1229
  ```
1784
1230
 
1785
- [source ↗](src/emmykit/safe_paths.py#L162)
1231
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/safe_paths.py#L162)
1786
1232
 
1787
1233
  </details>
1788
1234
 
@@ -1807,7 +1253,7 @@ Returns:
1807
1253
  The mtime of the file in seconds or nanoseconds, or None if an error occurred.
1808
1254
  ```
1809
1255
 
1810
- [source ↗](src/emmykit/safe_paths.py#L276)
1256
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/safe_paths.py#L276)
1811
1257
 
1812
1258
  </details>
1813
1259
 
@@ -1831,7 +1277,7 @@ Returns:
1831
1277
  The size of the file in bytes or None if an error occurred.
1832
1278
  ```
1833
1279
 
1834
- [source ↗](src/emmykit/safe_paths.py#L260)
1280
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/safe_paths.py#L260)
1835
1281
 
1836
1282
  </details>
1837
1283
 
@@ -1859,7 +1305,7 @@ Raises:
1859
1305
  some OSError variations. But not all.
1860
1306
  ```
1861
1307
 
1862
- [source ↗](src/emmykit/safe_paths.py#L226)
1308
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/safe_paths.py#L226)
1863
1309
 
1864
1310
  </details>
1865
1311
 
@@ -1899,7 +1345,7 @@ Raises:
1899
1345
  RuntimeError: If the lock cannot be acquired within the specified timeout.
1900
1346
  ```
1901
1347
 
1902
- [source ↗](src/emmykit/file_io.py#L13)
1348
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/file_io.py#L13)
1903
1349
 
1904
1350
  </details>
1905
1351
 
@@ -1922,7 +1368,7 @@ my_critical_error(message: 'str' = 'A critical error occurred.', choose_breakpoi
1922
1368
  Log a critical error message and either exit the program or enter a breakpoint.
1923
1369
  ```
1924
1370
 
1925
- [source ↗](src/emmykit/io_subprocess.py#L20)
1371
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/io_subprocess.py#L20)
1926
1372
 
1927
1373
  </details>
1928
1374
 
@@ -1953,7 +1399,7 @@ Returns:
1953
1399
  - cannot be read with any of the specified encodings
1954
1400
  ```
1955
1401
 
1956
- [source ↗](src/emmykit/io_subprocess.py#L124)
1402
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/io_subprocess.py#L124)
1957
1403
 
1958
1404
  </details>
1959
1405
 
@@ -1969,7 +1415,7 @@ my_popen(command_list: 'list', suppress_info: 'bool' = False, suppress_error: 'b
1969
1415
  Execute a command using subprocess.Popen and capture the output line by line using threads.
1970
1416
  ```
1971
1417
 
1972
- [source ↗](src/emmykit/io_subprocess.py#L49)
1418
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/io_subprocess.py#L49)
1973
1419
 
1974
1420
  </details>
1975
1421
 
@@ -1985,7 +1431,7 @@ MyPopenResult(stdout: 'str', stderr: 'str', returncode: 'int') -> 'None'
1985
1431
  A class to store the results of a customized subprocess.Popen call.
1986
1432
  ```
1987
1433
 
1988
- [source ↗](src/emmykit/io_subprocess.py#L39)
1434
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/io_subprocess.py#L39)
1989
1435
 
1990
1436
  </details>
1991
1437
 
@@ -2019,7 +1465,7 @@ Raises:
2019
1465
  None: If the user input is invalid, it will keep prompting until a valid choice is made.
2020
1466
  ```
2021
1467
 
2022
- [source ↗](src/emmykit/prompts.py#L15)
1468
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/prompts.py#L15)
2023
1469
 
2024
1470
  </details>
2025
1471
 
@@ -2035,7 +1481,7 @@ prompt_then_confirm(prompt: 'str') -> 'bool'
2035
1481
  Prompt the user with the given message and return True if the user enters 'yes', False otherwise.
2036
1482
  ```
2037
1483
 
2038
- [source ↗](src/emmykit/prompts.py#L10)
1484
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/prompts.py#L10)
2039
1485
 
2040
1486
  </details>
2041
1487
 
@@ -2070,7 +1516,7 @@ Raises:
2070
1516
  TypeError: If 'source_or_filepath' is not a string or a file path.
2071
1517
  ```
2072
1518
 
2073
- [source ↗](src/emmykit/introspection.py#L427)
1519
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/introspection.py#L427)
2074
1520
 
2075
1521
  </details>
2076
1522
 
@@ -2119,7 +1565,7 @@ Notes:
2119
1565
  result in an empty string.
2120
1566
  ```
2121
1567
 
2122
- [source ↗](src/emmykit/introspection.py#L321)
1568
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/introspection.py#L321)
2123
1569
 
2124
1570
  </details>
2125
1571
 
@@ -2148,7 +1594,7 @@ Raises:
2148
1594
  ValueError: If the value of the variable cannot be evaluated as a literal expression.
2149
1595
  ```
2150
1596
 
2151
- [source ↗](src/emmykit/introspection.py#L20)
1597
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/introspection.py#L20)
2152
1598
 
2153
1599
  </details>
2154
1600
 
@@ -2164,7 +1610,7 @@ normalize_to_dict(value: 'Any', var_name: 'str', script_path: 'str | os.PathLike
2164
1610
  Ensure that 'value' is a dict. If it's a JSON-style string, try to parse it. Otherwise, log a warning and return an empty dict.
2165
1611
  ```
2166
1612
 
2167
- [source ↗](src/emmykit/introspection.py#L300)
1613
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/introspection.py#L300)
2168
1614
 
2169
1615
  </details>
2170
1616
 
@@ -2212,7 +1658,7 @@ Raises:
2212
1658
  TypeError: If the resolved object isn't suitable for source extraction.
2213
1659
  ```
2214
1660
 
2215
- [source ↗](src/emmykit/introspection.py#L133)
1661
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/introspection.py#L133)
2216
1662
 
2217
1663
  </details>
2218
1664
 
@@ -2258,7 +1704,7 @@ Raises:
2258
1704
  None.
2259
1705
  ```
2260
1706
 
2261
- [source ↗](src/emmykit/humanize.py#L6)
1707
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/humanize.py#L6)
2262
1708
 
2263
1709
  </details>
2264
1710
 
@@ -2285,7 +1731,7 @@ Returns:
2285
1731
  float: The rounded number, or the original number if it is smaller than 10^(-max_digits).
2286
1732
  ```
2287
1733
 
2288
- [source ↗](src/emmykit/humanize.py#L124)
1734
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/humanize.py#L124)
2289
1735
 
2290
1736
  </details>
2291
1737
 
@@ -2302,7 +1748,46 @@ Return floor(log10(|x|)), clamped to -max_digits for very small |x|.
2302
1748
  For x == 0, returns -max_digits.
2303
1749
  ```
2304
1750
 
2305
- [source ↗](src/emmykit/humanize.py#L110)
1751
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/humanize.py#L110)
1752
+
1753
+ </details>
1754
+
1755
+ <a id="m-numeric_helpers"></a>
1756
+ ### `numeric_helpers` — Numeric parsing + unit-to-seconds conversion
1757
+
1758
+ _Layer 1._ `from emmykit.numeric_helpers import …`
1759
+
1760
+ Tiny helpers shared by `humanize` and `datetime_utils` so neither has to pull in the other.
1761
+
1762
+ <a id="is_float"></a>
1763
+ <details>
1764
+ <summary><code>is_float</code> — Check if a string can be parsed as a float.</summary>
1765
+
1766
+ ```python
1767
+ is_float(s: 'str') -> 'bool'
1768
+ ```
1769
+
1770
+ ```text
1771
+ Check if a string can be parsed as a float.
1772
+ ```
1773
+
1774
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/numeric_helpers.py#L56)
1775
+
1776
+ </details>
1777
+
1778
+ <a id="seconds_in_unit"></a>
1779
+ <details>
1780
+ <summary><code>seconds_in_unit</code> — Return the number of seconds in a given time unit.</summary>
1781
+
1782
+ ```python
1783
+ seconds_in_unit(unit: 'str') -> 'float'
1784
+ ```
1785
+
1786
+ ```text
1787
+ Return the number of seconds in a given time unit.
1788
+ ```
1789
+
1790
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/numeric_helpers.py#L49)
2306
1791
 
2307
1792
  </details>
2308
1793
 
@@ -2342,19 +1827,7 @@ Returns:
2342
1827
  for NaT/NaN values.
2343
1828
  ```
2344
1829
 
2345
- [source ↗](src/emmykit/datetime_utils.py#L667)
2346
-
2347
- </details>
2348
-
2349
- <a id="adaptive_format_levels"></a>
2350
- <details>
2351
- <summary><code>ADAPTIVE_FORMAT_LEVELS</code> — Final[list[str]] (5 items)</summary>
2352
-
2353
- ```python
2354
- ADAPTIVE_FORMAT_LEVELS: Final[list[str]] = ['%Y', '%Y-%m', '%Y-%m-%d', '%Y-%m-%d %H:%M', '%Y-%m-%d %H:%M:%S']
2355
- ```
2356
-
2357
- [source ↗](src/emmykit/datetime_utils.py#L629)
1830
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L667)
2358
1831
 
2359
1832
  </details>
2360
1833
 
@@ -2385,7 +1858,7 @@ Example:
2385
1858
 
2386
1859
  **Public methods:** `format_ticks`.
2387
1860
 
2388
- [source ↗](src/emmykit/datetime_utils.py#L723)
1861
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L723)
2389
1862
 
2390
1863
  </details>
2391
1864
 
@@ -2397,7 +1870,7 @@ Example:
2397
1870
  AnyDateTimeType: TypeAlias = 'str | float | int | np.datetime64 | pd.Timestamp | dt.datetime'
2398
1871
  ```
2399
1872
 
2400
- [source ↗](src/emmykit/datetime_utils.py#L324)
1873
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L324)
2401
1874
 
2402
1875
  </details>
2403
1876
 
@@ -2415,7 +1888,7 @@ If use_astropy is True, astropy.time is used for sub-second and leap-second–aw
2415
1888
  Usage: new_datetime_datetime_object = decimal_year_to_datetime(2002.291)
2416
1889
  ```
2417
1890
 
2418
- [source ↗](src/emmykit/datetime_utils.py#L282)
1891
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L282)
2419
1892
 
2420
1893
  </details>
2421
1894
 
@@ -2431,7 +1904,7 @@ extract_timestamp(the_string: 'str') -> 'str | None'
2431
1904
  Extract timestamp string (in format YYYYMMDD-HHMMSS) from the_string, or None if not found.
2432
1905
  ```
2433
1906
 
2434
- [source ↗](src/emmykit/datetime_utils.py#L122)
1907
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L122)
2435
1908
 
2436
1909
  </details>
2437
1910
 
@@ -2460,7 +1933,7 @@ Raises:
2460
1933
  ValueError: If either date1 or date2 is not a datetime.datetime object.
2461
1934
  ```
2462
1935
 
2463
- [source ↗](src/emmykit/datetime_utils.py#L64)
1936
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L64)
2464
1937
 
2465
1938
  </details>
2466
1939
 
@@ -2488,7 +1961,7 @@ Raises:
2488
1961
  None.
2489
1962
  ```
2490
1963
 
2491
- [source ↗](src/emmykit/datetime_utils.py#L15)
1964
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L15)
2492
1965
 
2493
1966
  </details>
2494
1967
 
@@ -2554,7 +2027,7 @@ Raises:
2554
2027
  TypeError: If the given_date is not a string, float, int, numpy.datetime64, pandas.Timestamp, or datetime.datetime object.
2555
2028
  ```
2556
2029
 
2557
- [source ↗](src/emmykit/datetime_utils.py#L393)
2030
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L393)
2558
2031
 
2559
2032
  </details>
2560
2033
 
@@ -2589,7 +2062,7 @@ Raises:
2589
2062
  ValueError if the string cannot be converted to a valid timezone.
2590
2063
  ```
2591
2064
 
2592
- [source ↗](src/emmykit/datetime_utils.py#L185)
2065
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L185)
2593
2066
 
2594
2067
  </details>
2595
2068
 
@@ -2607,7 +2080,7 @@ Integer constants representing date-formatting precision levels.
2607
2080
  Levels are ordered from coarsest (YEAR=0) to finest (SECOND=4).
2608
2081
  ```
2609
2082
 
2610
- [source ↗](src/emmykit/datetime_utils.py#L617)
2083
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/datetime_utils.py#L617)
2611
2084
 
2612
2085
  </details>
2613
2086
 
@@ -2631,7 +2104,7 @@ Reconstruct objects encoded with to_jsonable(..., roundtrip=True).
2631
2104
  If input was produced with roundtrip=False, this mostly passes values through.
2632
2105
  ```
2633
2106
 
2634
- [source ↗](src/emmykit/json_io.py#L146)
2107
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/json_io.py#L146)
2635
2108
 
2636
2109
  </details>
2637
2110
 
@@ -2658,7 +2131,7 @@ Raises:
2658
2131
  ValueError: If the JSON file is invalid or cannot be parsed.
2659
2132
  ```
2660
2133
 
2661
- [source ↗](src/emmykit/json_io.py#L303)
2134
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/json_io.py#L303)
2662
2135
 
2663
2136
  </details>
2664
2137
 
@@ -2688,7 +2161,7 @@ Raises:
2688
2161
  ValueError: If the options object is invalid.
2689
2162
  ```
2690
2163
 
2691
- [source ↗](src/emmykit/json_io.py#L271)
2164
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/json_io.py#L271)
2692
2165
 
2693
2166
  </details>
2694
2167
 
@@ -2705,7 +2178,7 @@ Convert arbitrary Python objects into JSON-serializable primitives.
2705
2178
  If roundtrip=True, non-JSON types are wrapped with a small type tag so they can be reconstructed.
2706
2179
  ```
2707
2180
 
2708
- [source ↗](src/emmykit/json_io.py#L17)
2181
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/json_io.py#L17)
2709
2182
 
2710
2183
  </details>
2711
2184
 
@@ -2751,7 +2224,7 @@ Raises:
2751
2224
  ValueError: If the specified path is not a file. The function which raises this exception is my_fopen().
2752
2225
  ```
2753
2226
 
2754
- [source ↗](src/emmykit/diff_view.py#L300)
2227
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/diff_view.py#L300)
2755
2228
 
2756
2229
  </details>
2757
2230
 
@@ -2784,7 +2257,7 @@ Raises:
2784
2257
  None.
2785
2258
  ```
2786
2259
 
2787
- [source ↗](src/emmykit/diff_view.py#L36)
2260
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/diff_view.py#L36)
2788
2261
 
2789
2262
  </details>
2790
2263
 
@@ -2813,7 +2286,7 @@ Raises:
2813
2286
  PermissionError: If the file is not accessible due to permission issues.
2814
2287
  ```
2815
2288
 
2816
- [source ↗](src/emmykit/diff_view.py#L255)
2289
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/diff_view.py#L255)
2817
2290
 
2818
2291
  </details>
2819
2292
 
@@ -2847,7 +2320,7 @@ Raises:
2847
2320
  None.
2848
2321
  ```
2849
2322
 
2850
- [source ↗](src/emmykit/diff_view.py#L82)
2323
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/diff_view.py#L82)
2851
2324
 
2852
2325
  </details>
2853
2326
 
@@ -2858,6 +2331,8 @@ _Layer 5._ `from emmykit.text import …`
2858
2331
 
2859
2332
  ftfy-based `fix_text`/`fix_mojibake` (with an atomic write-back), explicit UTF-8 / CP-1252 decoders, sentence-aware `my_capitalize`/`my_title_case`, and `normalize_for_search` for diacritic-folded comparisons.
2860
2333
 
2334
+ Translation tables live in `emmykit.text_constants` (`CHARACTERS_TO_SPACE` / `QUOTES_TO_DELETE` / `REPLACE_WITH_SPACE` / `TRANSLATION_TABLE`) and feed `normalize_for_search`.
2335
+
2861
2336
  <a id="contains_mojibake"></a>
2862
2337
  <details>
2863
2338
  <summary><code>contains_mojibake</code> — Use ftfy.badness.is_bad() to detect any likely mojibake in the text.</summary>
@@ -2870,7 +2345,7 @@ contains_mojibake(text: 'str') -> 'bool'
2870
2345
  Use ftfy.badness.is_bad() to detect any likely mojibake in the text.
2871
2346
  ```
2872
2347
 
2873
- [source ↗](src/emmykit/text.py#L63)
2348
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/text.py#L63)
2874
2349
 
2875
2350
  </details>
2876
2351
 
@@ -2887,7 +2362,7 @@ Attempt to decode CP1252 bytes and return as a string.
2887
2362
  If it fails, return None.
2888
2363
  ```
2889
2364
 
2890
- [source ↗](src/emmykit/text.py#L48)
2365
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/text.py#L48)
2891
2366
 
2892
2367
  </details>
2893
2368
 
@@ -2904,7 +2379,7 @@ If the file at 'path' is valid UTF-8 without lone C1 controls,
2904
2379
  return the decoded string. Otherwise, return None.
2905
2380
  ```
2906
2381
 
2907
- [source ↗](src/emmykit/text.py#L31)
2382
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/text.py#L31)
2908
2383
 
2909
2384
  </details>
2910
2385
 
@@ -2924,7 +2399,7 @@ as an http-equiv Content-Type declaration—normalize it to
2924
2399
  after the opening <head> tag.
2925
2400
  ```
2926
2401
 
2927
- [source ↗](src/emmykit/text.py#L103)
2402
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/text.py#L103)
2928
2403
 
2929
2404
  </details>
2930
2405
 
@@ -2941,7 +2416,7 @@ Fix mojibake in a text file, recoding from CP1252 to UTF-8 if necessary.
2941
2416
  If the file is already valid UTF-8, it will only fix mojibake.
2942
2417
  ```
2943
2418
 
2944
- [source ↗](src/emmykit/text.py#L141)
2419
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/text.py#L141)
2945
2420
 
2946
2421
  </details>
2947
2422
 
@@ -2957,7 +2432,7 @@ fix_text(current_text: 'str', path: 'str | os.PathLike[str]', raw_bytes: 'bytes'
2957
2432
  Fix mojibake in a string using ftfy.fix_encoding().
2958
2433
  ```
2959
2434
 
2960
- [source ↗](src/emmykit/text.py#L77)
2435
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/text.py#L77)
2961
2436
 
2962
2437
  </details>
2963
2438
 
@@ -2973,7 +2448,7 @@ my_capitalize(string_to_capitalize: 'str') -> 'str'
2973
2448
  Capitalize ONLY the first letter of a string and DON'T modify the rest of it.
2974
2449
  ```
2975
2450
 
2976
- [source ↗](src/emmykit/text.py#L18)
2451
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/text.py#L18)
2977
2452
 
2978
2453
  </details>
2979
2454
 
@@ -2989,7 +2464,7 @@ my_title_case(the_title: 'str') -> 'str'
2989
2464
  Capitalize the first letter of each word, but if a word already has ANY uppercase letters, leave it as is. This way, words like "WW2" or "iZombie" won't be modified.
2990
2465
  ```
2991
2466
 
2992
- [source ↗](src/emmykit/text.py#L24)
2467
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/text.py#L24)
2993
2468
 
2994
2469
  </details>
2995
2470
 
@@ -3005,7 +2480,7 @@ normalize_for_search(text: 'str') -> 'str'
3005
2480
  Convert text to ASCII and lowercase for case- and diacritic-insensitive comparison. Also treat some characters such as ._- the same as spaces. Remove quotes (', ", ' and their unicode variants).
3006
2481
  ```
3007
2482
 
3008
- [source ↗](src/emmykit/text.py#L196)
2483
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/text.py#L196)
3009
2484
 
3010
2485
  </details>
3011
2486
 
@@ -3040,7 +2515,7 @@ Raises:
3040
2515
  no names (or differing names) are retrieved.
3041
2516
  ```
3042
2517
 
3043
- [source ↗](src/emmykit/hosts.py#L100)
2518
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L100)
3044
2519
 
3045
2520
  </details>
3046
2521
 
@@ -3052,7 +2527,7 @@ Raises:
3052
2527
  COMPUTER_NAME: str = 'b98ed262ead6'
3053
2528
  ```
3054
2529
 
3055
- [source ↗](src/emmykit/hosts.py#L146)
2530
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L146)
3056
2531
 
3057
2532
  </details>
3058
2533
 
@@ -3078,7 +2553,7 @@ Raises:
3078
2553
  None: This function does not raise exceptions, but it may log warnings if no names are retrieved.
3079
2554
  ```
3080
2555
 
3081
- [source ↗](src/emmykit/hosts.py#L61)
2556
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L61)
3082
2557
 
3083
2558
  </details>
3084
2559
 
@@ -3094,7 +2569,7 @@ get_hostname_os_uname(rawlog: 'bool' = False) -> 'str | None'
3094
2569
  Retrieves the hostname using os.uname().nodename.
3095
2570
  ```
3096
2571
 
3097
- [source ↗](src/emmykit/hosts.py#L31)
2572
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L31)
3098
2573
 
3099
2574
  </details>
3100
2575
 
@@ -3110,7 +2585,7 @@ get_hostname_platform(rawlog: 'bool' = False) -> 'str | None'
3110
2585
  Retrieves the hostname using platform.node().
3111
2586
  ```
3112
2587
 
3113
- [source ↗](src/emmykit/hosts.py#L22)
2588
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L22)
3114
2589
 
3115
2590
  </details>
3116
2591
 
@@ -3126,7 +2601,7 @@ get_hostname_socket(rawlog: 'bool' = False) -> 'str | None'
3126
2601
  Retrieves the hostname using socket.gethostname().
3127
2602
  ```
3128
2603
 
3129
- [source ↗](src/emmykit/hosts.py#L13)
2604
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L13)
3130
2605
 
3131
2606
  </details>
3132
2607
 
@@ -3142,7 +2617,7 @@ get_hostname_subprocess_hostname(rawlog: 'bool' = False) -> 'str | None'
3142
2617
  Retrieves the hostname using the 'hostname' system command via subprocess.
3143
2618
  ```
3144
2619
 
3145
- [source ↗](src/emmykit/hosts.py#L39)
2620
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L39)
3146
2621
 
3147
2622
  </details>
3148
2623
 
@@ -3158,7 +2633,7 @@ get_hostname_subprocess_scutil(rawlog: 'bool' = False) -> 'str | None'
3158
2633
  Retrieves the hostname using the 'scutil --get ComputerName' command on macOS via subprocess.
3159
2634
  ```
3160
2635
 
3161
- [source ↗](src/emmykit/hosts.py#L49)
2636
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L49)
3162
2637
 
3163
2638
  </details>
3164
2639
 
@@ -3170,31 +2645,17 @@ Retrieves the hostname using the 'scutil --get ComputerName' command on macOS vi
3170
2645
  IS_NASA_COMPUTER: bool = False
3171
2646
  ```
3172
2647
 
3173
- [source ↗](src/emmykit/hosts.py#L153)
3174
-
3175
- </details>
3176
-
3177
- <a id="nasa_casefolded_computer_name_prefixes"></a>
3178
- <details>
3179
- <summary><code>NASA_CASEFOLDED_COMPUTER_NAME_PREFIXES</code> — Final[tuple[str, ...]] (4 items)</summary>
3180
-
3181
- ```python
3182
- NASA_CASEFOLDED_COMPUTER_NAME_PREFIXES: Final[tuple[str, ...]] = ('rayl', 'nasa', 'jpl', 'mt')
3183
- ```
3184
-
3185
- [source ↗](src/emmykit/hosts.py#L150)
2648
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L153)
3186
2649
 
3187
2650
  </details>
3188
2651
 
3189
- <a id="nasa_computer_name_prefixes"></a>
2652
+ <a id="c-hosts-nasa-computer-name-prefixes"></a>
3190
2653
  <details>
3191
- <summary><code>NASA_COMPUTER_NAME_PREFIXES</code> — Final[tuple[str, ...]] (4 items)</summary>
2654
+ <summary><code>NASA computer-name prefixes</code> — Prefix lists feeding `IS_NASA_COMPUTER` detection.</summary>
3192
2655
 
3193
- ```python
3194
- NASA_COMPUTER_NAME_PREFIXES: Final[tuple[str, ...]] = ('RAYL', 'NASA', 'JPL', 'MT')
3195
- ```
2656
+ **Includes:** `NASA_CASEFOLDED_COMPUTER_NAME_PREFIXES`, `NASA_COMPUTER_NAME_PREFIXES`.
3196
2657
 
3197
- [source ↗](src/emmykit/hosts.py#L148)
2658
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/hosts.py#L150)
3198
2659
 
3199
2660
  </details>
3200
2661
 
@@ -3205,6 +2666,8 @@ _Layer 5._ `from emmykit.network import …`
3205
2666
 
3206
2667
  `is_internet_available` runs a multi-strategy DNS + HTTP + TCP check against `net_targets` with a captive-portal sniff and a shared `ThreadPoolExecutor`.
3207
2668
 
2669
+ Probe targets live in `emmykit.net_targets` (`IPV4_TARGETS` / `IPV6_TARGETS` / `HTTP_PROBES` / `DNS_TEST_NAMES`) and feed `is_internet_available`.
2670
+
3208
2671
  <a id="checkresult"></a>
3209
2672
  <details>
3210
2673
  <summary><code>CheckResult</code> — Aggregate results from the multi-strategy connectivity check.</summary>
@@ -3219,7 +2682,7 @@ Aggregate results from the multi-strategy connectivity check.
3219
2682
 
3220
2683
  **Fields:** `tcp_ok`, `dns_ok`, `http_ok`, `captive_detected`.
3221
2684
 
3222
- [source ↗](src/emmykit/network.py#L425)
2685
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/network.py#L425)
3223
2686
 
3224
2687
  </details>
3225
2688
 
@@ -3263,7 +2726,7 @@ Raises:
3263
2726
  None.
3264
2727
  ```
3265
2728
 
3266
- [source ↗](src/emmykit/network.py#L509)
2729
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/network.py#L509)
3267
2730
 
3268
2731
  </details>
3269
2732
 
@@ -3286,7 +2749,7 @@ check_python_version(command: 'str') -> 'bool'
3286
2749
  Check if the given Python command is available and has a version of PY_VERSION or higher.
3287
2750
  ```
3288
2751
 
3289
- [source ↗](src/emmykit/python_env.py#L112)
2752
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/python_env.py#L112)
3290
2753
 
3291
2754
  </details>
3292
2755
 
@@ -3312,7 +2775,7 @@ Raises:
3312
2775
  subprocess.CalledProcessError or FileNotFoundError.
3313
2776
  ```
3314
2777
 
3315
- [source ↗](src/emmykit/python_env.py#L14)
2778
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/python_env.py#L14)
3316
2779
 
3317
2780
  </details>
3318
2781
 
@@ -3328,7 +2791,7 @@ find_additional_alias_files(options: 'Options') -> 'None'
3328
2791
  Find additional alias files for the shell.
3329
2792
  ```
3330
2793
 
3331
- [source ↗](src/emmykit/python_env.py#L89)
2794
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/python_env.py#L89)
3332
2795
 
3333
2796
  </details>
3334
2797
 
@@ -3344,7 +2807,7 @@ find_preferred_python_version() -> 'str | None'
3344
2807
  Find the command for the preferred version of python (stored here as PY_VERSION).
3345
2808
  ```
3346
2809
 
3347
- [source ↗](src/emmykit/python_env.py#L125)
2810
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/python_env.py#L125)
3348
2811
 
3349
2812
  </details>
3350
2813
 
@@ -3371,7 +2834,7 @@ Raises:
3371
2834
  for the specified shell.
3372
2835
  ```
3373
2836
 
3374
- [source ↗](src/emmykit/python_env.py#L45)
2837
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/python_env.py#L45)
3375
2838
 
3376
2839
  </details>
3377
2840
 
@@ -3394,7 +2857,7 @@ calculate_checksum(file_path: 'str | os.PathLike[str]') -> 'str'
3394
2857
  Calculate the SHA256 checksum of a file.
3395
2858
  ```
3396
2859
 
3397
- [source ↗](src/emmykit/files.py#L321)
2860
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/files.py#L321)
3398
2861
 
3399
2862
  </details>
3400
2863
 
@@ -3428,7 +2891,7 @@ Raises:
3428
2891
  SystemExit on failure after retries or if insufficient free space is detected.
3429
2892
  ```
3430
2893
 
3431
- [source ↗](src/emmykit/files.py#L19)
2894
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/files.py#L19)
3432
2895
 
3433
2896
  </details>
3434
2897
 
@@ -3469,7 +2932,7 @@ Raises:
3469
2932
  None: If the input text is None, it will return an empty string.
3470
2933
  ```
3471
2934
 
3472
- [source ↗](src/emmykit/files.py#L214)
2935
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/files.py#L214)
3473
2936
 
3474
2937
  </details>
3475
2938
 
@@ -3497,7 +2960,7 @@ Raises:
3497
2960
  OSError: If the filesystem information cannot be retrieved.
3498
2961
  ```
3499
2962
 
3500
- [source ↗](src/emmykit/files.py#L178)
2963
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/files.py#L178)
3501
2964
 
3502
2965
  </details>
3503
2966
 
@@ -3516,7 +2979,7 @@ Ensure that 'thepath' exists and contains exactly 'thescript'.
3516
2979
  - Otherwise, nothing happens.
3517
2980
  ```
3518
2981
 
3519
- [source ↗](src/emmykit/files.py#L292)
2982
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/files.py#L292)
3520
2983
 
3521
2984
  </details>
3522
2985
 
@@ -3558,7 +3021,7 @@ Raises:
3558
3021
  ValueError: If the specified path is not a file. The function which raises this exception is autopep8.fix_file().
3559
3022
  ```
3560
3023
 
3561
- [source ↗](src/emmykit/lint.py#L520)
3024
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/lint.py#L520)
3562
3025
 
3563
3026
  </details>
3564
3027
 
@@ -3595,7 +3058,7 @@ Raises:
3595
3058
  PermissionError: If the file is not accessible due to permission issues.
3596
3059
  ```
3597
3060
 
3598
- [source ↗](src/emmykit/lint.py#L587)
3061
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/lint.py#L587)
3599
3062
 
3600
3063
  </details>
3601
3064
 
@@ -3624,7 +3087,7 @@ Raises:
3624
3087
  FileNotFoundError: If the specified file does not exist.
3625
3088
  ```
3626
3089
 
3627
- [source ↗](src/emmykit/lint.py#L252)
3090
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/lint.py#L252)
3628
3091
 
3629
3092
  </details>
3630
3093
 
@@ -3644,7 +3107,7 @@ Walks a module AST and collects formatting violations:
3644
3107
 
3645
3108
  **Public methods:** `generic_visit`, `visit`, `visit_AsyncFunctionDef`, `visit_ClassDef`, `visit_Constant`, `visit_FunctionDef`.
3646
3109
 
3647
- [source ↗](src/emmykit/lint.py#L250)
3110
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/lint.py#L250)
3648
3111
 
3649
3112
  </details>
3650
3113
 
@@ -3662,7 +3125,7 @@ which Flake8 error‐codes autopep8 knows how to fix.
3662
3125
  Returns a set like {"E101","E111", ...}.
3663
3126
  ```
3664
3127
 
3665
- [source ↗](src/emmykit/lint.py#L492)
3128
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/lint.py#L492)
3666
3129
 
3667
3130
  </details>
3668
3131
 
@@ -3696,7 +3159,7 @@ Returns:
3696
3159
  False if the user chose to quit during any replacement prompts, True otherwise.
3697
3160
  ```
3698
3161
 
3699
- [source ↗](src/emmykit/lint.py#L734)
3162
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/lint.py#L734)
3700
3163
 
3701
3164
  </details>
3702
3165
 
@@ -3729,7 +3192,7 @@ Raises:
3729
3192
  NotADirectoryError: If the specified path is not a directory.
3730
3193
  ```
3731
3194
 
3732
- [source ↗](src/emmykit/lint.py#L667)
3195
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/lint.py#L667)
3733
3196
 
3734
3197
  </details>
3735
3198
 
@@ -3759,7 +3222,7 @@ Raises:
3759
3222
  FileNotFoundError: If the specified file does not exist.
3760
3223
  ```
3761
3224
 
3762
- [source ↗](src/emmykit/lint.py#L335)
3225
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/lint.py#L335)
3763
3226
 
3764
3227
  </details>
3765
3228
 
@@ -3782,7 +3245,7 @@ Returns:
3782
3245
  None.
3783
3246
  ```
3784
3247
 
3785
- [source ↗](src/emmykit/lint.py#L791)
3248
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/lint.py#L791)
3786
3249
 
3787
3250
  </details>
3788
3251
 
@@ -3829,7 +3292,7 @@ Raises:
3829
3292
  directory or does not exist.
3830
3293
  ```
3831
3294
 
3832
- [source ↗](src/emmykit/treeview.py#L14)
3295
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/treeview.py#L14)
3833
3296
 
3834
3297
  </details>
3835
3298
 
@@ -3852,7 +3315,7 @@ ensure_daemon_running() -> 'None'
3852
3315
  Check if the Docker daemon is running; if not, attempt to start it.
3853
3316
  ```
3854
3317
 
3855
- [source ↗](src/emmykit/docker_utils.py#L20)
3318
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/docker_utils.py#L20)
3856
3319
 
3857
3320
  </details>
3858
3321
 
@@ -3868,7 +3331,7 @@ ensure_docker_installed() -> 'None'
3868
3331
  Check if the Docker CLI is installed; if not, raise an error.
3869
3332
  ```
3870
3333
 
3871
- [source ↗](src/emmykit/docker_utils.py#L13)
3334
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/docker_utils.py#L13)
3872
3335
 
3873
3336
  </details>
3874
3337
 
@@ -3887,7 +3350,7 @@ or a build_cmd (and optionally a build_dir). If both dockerfile and build_cmd ar
3887
3350
  the function will raise an error.
3888
3351
  ```
3889
3352
 
3890
- [source ↗](src/emmykit/docker_utils.py#L48)
3353
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/docker_utils.py#L48)
3891
3354
 
3892
3355
  </details>
3893
3356
 
@@ -3916,7 +3379,7 @@ Raises:
3916
3379
  RuntimeError: If all fixes fail and the command still does not succeed.
3917
3380
  ```
3918
3381
 
3919
- [source ↗](src/emmykit/docker_utils.py#L82)
3382
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/docker_utils.py#L82)
3920
3383
 
3921
3384
  </details>
3922
3385
 
@@ -3945,7 +3408,7 @@ Returns:
3945
3408
  True if the command exists, False otherwise.
3946
3409
  ```
3947
3410
 
3948
- [source ↗](src/emmykit/system.py#L14)
3411
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/system.py#L14)
3949
3412
 
3950
3413
  </details>
3951
3414
 
@@ -3971,7 +3434,7 @@ Raises:
3971
3434
  ValueError: If the IPINFO_API_TOKEN environment variable is not set.
3972
3435
  ```
3973
3436
 
3974
- [source ↗](src/emmykit/system.py#L163)
3437
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/system.py#L163)
3975
3438
 
3976
3439
  </details>
3977
3440
 
@@ -3987,7 +3450,7 @@ get_effective_free_memory() -> 'float'
3987
3450
  Return the "effective" free memory in bytes: free memory plus buffers plus cache.
3988
3451
  ```
3989
3452
 
3990
- [source ↗](src/emmykit/system.py#L50)
3453
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/system.py#L50)
3991
3454
 
3992
3455
  </details>
3993
3456
 
@@ -4003,7 +3466,7 @@ is_process_running(process_name: 'str') -> 'bool'
4003
3466
  Check if a process with the given name is running.
4004
3467
  ```
4005
3468
 
4006
- [source ↗](src/emmykit/system.py#L108)
3469
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/system.py#L108)
4007
3470
 
4008
3471
  </details>
4009
3472
 
@@ -4019,7 +3482,7 @@ kill_process(pname: 'str') -> 'None'
4019
3482
  Kill a process by its name, then check if it is still running and retry if needed. Make sure the process name is unique to avoid killing unintended processes.
4020
3483
  ```
4021
3484
 
4022
- [source ↗](src/emmykit/system.py#L72)
3485
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/system.py#L72)
4023
3486
 
4024
3487
  </details>
4025
3488
 
@@ -4036,7 +3499,7 @@ Open the file manager with the specified directories.
4036
3499
  Note: Most file managers don't support multiple tabs via command line, so open separate windows.
4037
3500
  ```
4038
3501
 
4039
- [source ↗](src/emmykit/system.py#L135)
3502
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/system.py#L135)
4040
3503
 
4041
3504
  </details>
4042
3505
 
@@ -4053,7 +3516,7 @@ Open a GNOME terminal, source ~/.bashrc (via bash -i), run the_command,
4053
3516
  and optionally close or keep the window open. Optionally, maximize it.
4054
3517
  ```
4055
3518
 
4056
- [source ↗](src/emmykit/system.py#L27)
3519
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/system.py#L27)
4057
3520
 
4058
3521
  </details>
4059
3522
 
@@ -4069,7 +3532,7 @@ start_only_one_instance(process_name: 'str') -> 'None'
4069
3532
  Start a process, but only if it's not already running.
4070
3533
  ```
4071
3534
 
4072
- [source ↗](src/emmykit/system.py#L122)
3535
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/system.py#L122)
4073
3536
 
4074
3537
  </details>
4075
3538
 
@@ -4092,7 +3555,7 @@ ensure_even_dimensions(image_path: 'str | os.PathLike[str]') -> 'None'
4092
3555
  Ensure the image at 'image_path' has dimensions divisible by 2, by resizing if necessary.
4093
3556
  ```
4094
3557
 
4095
- [source ↗](src/emmykit/media.py#L21)
3558
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/media.py#L21)
4096
3559
 
4097
3560
  </details>
4098
3561
 
@@ -4108,7 +3571,7 @@ extract_and_concatenate_segments(input_file: 'str | os.PathLike[str]', timestamp
4108
3571
  Extracts segments from a video file and concatenates them into a new file.
4109
3572
  ```
4110
3573
 
4111
- [source ↗](src/emmykit/media.py#L474)
3574
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/media.py#L474)
4112
3575
 
4113
3576
  </details>
4114
3577
 
@@ -4135,7 +3598,7 @@ Raises:
4135
3598
  None
4136
3599
  ```
4137
3600
 
4138
- [source ↗](src/emmykit/media.py#L41)
3601
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/media.py#L41)
4139
3602
 
4140
3603
  </details>
4141
3604
 
@@ -4167,7 +3630,7 @@ Raises:
4167
3630
  ValueError: If a probe returns an invalid or non-positive duration.
4168
3631
  ```
4169
3632
 
4170
- [source ↗](src/emmykit/media.py#L315)
3633
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/media.py#L315)
4171
3634
 
4172
3635
  </details>
4173
3636
 
@@ -4183,7 +3646,7 @@ open_dir_in_VLC(the_dir: 'str | os.PathLike[str]', sort_choice: 'str' = 'sort_by
4183
3646
  Create a playlist of the files in the specified directory, then play that playlist in VLC. By default, don't search the directory recursively and sort the files by name. Optional arguments allow recursive loading or sorting by modification time. If no_start is True, don't start playback in VLC.
4184
3647
  ```
4185
3648
 
4186
- [source ↗](src/emmykit/media.py#L245)
3649
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/media.py#L245)
4187
3650
 
4188
3651
  </details>
4189
3652
 
@@ -4210,7 +3673,7 @@ Raises:
4210
3673
  FileNotFoundError: If the specified path does not exist.
4211
3674
  ```
4212
3675
 
4213
- [source ↗](src/emmykit/media.py#L292)
3676
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/media.py#L292)
4214
3677
 
4215
3678
  </details>
4216
3679
 
@@ -4226,7 +3689,7 @@ open_playlist_in_VLC(playlist: 'str | os.PathLike[str]', no_start: 'bool' = Fals
4226
3689
  Open a playlist in VLC. If no_start is True, don't start playback in VLC.
4227
3690
  ```
4228
3691
 
4229
- [source ↗](src/emmykit/media.py#L237)
3692
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/media.py#L237)
4230
3693
 
4231
3694
  </details>
4232
3695
 
@@ -4259,7 +3722,7 @@ Raises:
4259
3722
  RuntimeError: If the volume could not be set or verified.
4260
3723
  ```
4261
3724
 
4262
- [source ↗](src/emmykit/media.py#L111)
3725
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/media.py#L111)
4263
3726
 
4264
3727
  </details>
4265
3728
 
@@ -4298,7 +3761,7 @@ Raises:
4298
3761
  OSError: If there is an error during file operations.
4299
3762
  ```
4300
3763
 
4301
- [source ↗](src/emmykit/html_files.py#L88)
3764
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/html_files.py#L88)
4302
3765
 
4303
3766
  </details>
4304
3767
 
@@ -4328,7 +3791,7 @@ Raises:
4328
3791
  OSError: If the rename operation fails due to an OS error (e.g., permission denied).
4329
3792
  ```
4330
3793
 
4331
- [source ↗](src/emmykit/html_files.py#L15)
3794
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/html_files.py#L15)
4332
3795
 
4333
3796
  </details>
4334
3797
 
@@ -4344,7 +3807,7 @@ remove_prefix_from_html_title(filepath: 'str | os.PathLike[str]', prefix: 'str')
4344
3807
  If the given filepath is an HTML file and its title starts with the given prefix, remove the prefix from the title and save the file, then return True. Otherwise, return False.
4345
3808
  ```
4346
3809
 
4347
- [source ↗](src/emmykit/html_files.py#L59)
3810
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/html_files.py#L59)
4348
3811
 
4349
3812
  </details>
4350
3813
 
@@ -4366,7 +3829,7 @@ Configuration for LLM selection and usage. Data only.
4366
3829
 
4367
3830
  **Fields:** `only_cleared_models`, `only_local_models`, `allow_local_models`, `ollama_base_url`, `vllm_base_url`, `rate_throttle`, `rate_headroom`, `rate_retry_max_attempts`, `rate_retry_max_wait`, `rate_db_path`, `availability_probe`, `availability_probe_ttl_sec`, `availability_probe_timeout`, `availability_probe_allow_costly`, `selection_strategy`, `min_context_tokens`, `assumed_prompt_tokens`, `assumed_output_tokens`, `candidate_models`, `default_temperature`, `max_tokens`, `model_scores`, `prefer_code`, `prefer_low_TTFT`, `prefer_local`, `max_estimated_cost`, `speed_floor`, `model_filter`, `provider_filter`, `weight_price`, `weight_code_skill`, `weight_general_skill`, `weight_TTFT`, `weight_speed`, `weight_nonlocal_penalty`.
4368
3831
 
4369
- [source ↗](src/emmykit/llm.py#L31)
3832
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/llm.py#L31)
4370
3833
 
4371
3834
  </details>
4372
3835
 
@@ -4387,7 +3850,7 @@ LLMs() -> 'None'
4387
3850
 
4388
3851
  **Public methods:** `alternative_model`, `apply_config`, `describe_selection`, `get_config`, `list_candidates`, `refresh_selection`, `register_strategy`, `send_prompt`, `tokenize`.
4389
3852
 
4390
- [source ↗](src/emmykit/llm.py#L143)
3853
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/llm.py#L143)
4391
3854
 
4392
3855
  </details>
4393
3856
 
@@ -4404,7 +3867,7 @@ Information about a candidate Large Language Model (LLM).
4404
3867
 
4405
3868
  **Public methods:** `estimate_cost`.
4406
3869
 
4407
- [source ↗](src/emmykit/llm.py#L102)
3870
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/llm.py#L102)
4408
3871
 
4409
3872
  </details>
4410
3873
 
@@ -4422,7 +3885,7 @@ Context passed to strategy functions.
4422
3885
 
4423
3886
  **Fields:** `tokens_in`, `tokens_out`, `min_context_tokens`, `require_local`, `require_cleared`, `extras`.
4424
3887
 
4425
- [source ↗](src/emmykit/llm.py#L132)
3888
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/llm.py#L132)
4426
3889
 
4427
3890
  </details>
4428
3891
 
@@ -4440,7 +3903,7 @@ Enumeration of selection strategies for model selection.
4440
3903
 
4441
3904
  **Public methods:** `capitalize`, `casefold`, `center`, `count`, `encode`, `endswith`, `expandtabs`, `find`, `format`, `format_map`, `index`, `isalnum`, `isalpha`, `isascii`, `isdecimal`, `isdigit`, `isidentifier`, `islower`, `isnumeric`, `isprintable`, `isspace`, `istitle`, `isupper`, `join`, `ljust`, `lower`, `lstrip`, `maketrans`, `partition`, `removeprefix`, `removesuffix`, `replace`, `rfind`, `rindex`, `rjust`, `rpartition`, `rsplit`, `rstrip`, `split`, `splitlines`, `startswith`, `strip`, `swapcase`, `title`, `translate`, `upper`, `zfill`.
4442
3905
 
4443
- [source ↗](src/emmykit/llm.py#L20)
3906
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/llm.py#L20)
4444
3907
 
4445
3908
  </details>
4446
3909
 
@@ -4452,7 +3915,7 @@ Enumeration of selection strategies for model selection.
4452
3915
  StrategyFn: TypeAlias = collections.abc.Callable[[collections.abc.Sequence[emmykit.llm.ModelInfo], emmykit.llm.SelectionContext], emmykit.llm.ModelInfo]
4453
3916
  ```
4454
3917
 
4455
- [source ↗](src/emmykit/llm.py#L141)
3918
+ [source ↗](https://github.com/killett/emmykit/blob/main/src/emmykit/llm.py#L141)
4456
3919
 
4457
3920
  </details>
4458
3921