tesorotools-python 0.1.5__tar.gz → 0.1.6__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 (152) hide show
  1. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/PKG-INFO +1 -1
  2. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/_common.py +16 -8
  3. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/builder.py +86 -19
  4. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/lint.py +221 -31
  5. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/markdown.py +93 -21
  6. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/skill/SKILL.md +22 -9
  7. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/.gitignore +0 -0
  8. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/pyproject.toml +0 -0
  9. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/__init__.py +0 -0
  10. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/_build_context.py +0 -0
  11. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/_registry.py +0 -0
  12. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/__init__.py +0 -0
  13. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/bar_line.py +0 -0
  14. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/barh_plot.py +0 -0
  15. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/box_plot.py +0 -0
  16. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/compact.py +0 -0
  17. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/intraday_plot.py +0 -0
  18. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/line_plot.py +0 -0
  19. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/matrix.py +0 -0
  20. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/plotly_backend.py +0 -0
  21. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/sankey.py +0 -0
  22. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/scatter.py +0 -0
  23. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/sharing.py +0 -0
  24. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/shock_plot.py +0 -0
  25. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/stacked.py +0 -0
  26. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/tikz_backend.py +0 -0
  27. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/type_curve.py +0 -0
  28. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/vector_plot.py +0 -0
  29. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/artists/waterfall.py +0 -0
  30. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/README.md +0 -0
  31. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
  32. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
  33. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
  34. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
  35. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
  36. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
  37. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
  38. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
  39. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/fonts/README.md +0 -0
  40. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/nota_template.dotx +0 -0
  41. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/plots.yaml +0 -0
  42. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/template.docx +0 -0
  43. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/assets/tesoro.mplstyle +0 -0
  44. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/data_sources/__init__.py +0 -0
  45. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/data_sources/debug.py +0 -0
  46. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/database/__init__.py +0 -0
  47. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/database/local.py +0 -0
  48. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/database/push.py +0 -0
  49. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/database/shared.py +0 -0
  50. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/dependencies/__init__.py +0 -0
  51. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/dependencies/node.py +0 -0
  52. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/dependencies/resolution.py +0 -0
  53. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/driver.py +0 -0
  54. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/__init__.py +0 -0
  55. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/cli.py +0 -0
  56. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/scaffold.py +0 -0
  57. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/article/main.tex.tmpl +0 -0
  58. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/article/sections/01-introduccion.tex +0 -0
  59. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/article/sections/02-analisis.tex +0 -0
  60. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/article/sections/03-conclusiones.tex +0 -0
  61. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/beamer/main.tex.tmpl +0 -0
  62. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/beamer/slides/01-contexto.tex +0 -0
  63. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/beamer/slides/02-analisis.tex +0 -0
  64. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/beamer/slides/03-conclusiones.tex +0 -0
  65. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/beamer/style/beamerthemeTesoro.sty +0 -0
  66. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/book/appendices/a-fuentes.tex +0 -0
  67. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/book/chapters/01-introduccion.tex +0 -0
  68. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/book/chapters/02-analisis.tex +0 -0
  69. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/book/frontmatter/resumen.tex +0 -0
  70. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/book/main.tex.tmpl +0 -0
  71. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/README.md.tmpl +0 -0
  72. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/dot-gitignore +0 -0
  73. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/dot-vscode/settings.json +0 -0
  74. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/figures/dot-gitkeep +0 -0
  75. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/fonts/README.md +0 -0
  76. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/latexmkrc +0 -0
  77. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/refs.bib +0 -0
  78. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/style/tesoro.sty +0 -0
  79. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/style/tesorobrand.sty.tmpl +0 -0
  80. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/latex/templates/common/style/tesorolocal.sty.tmpl +0 -0
  81. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/manifest.py +0 -0
  82. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/__init__.py +0 -0
  83. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/charts.py +0 -0
  84. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/cli.py +0 -0
  85. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/inline.py +0 -0
  86. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/issues.py +0 -0
  87. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/pdf.py +0 -0
  88. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/skeleton.md +0 -0
  89. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/skill/reglas.md +0 -0
  90. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/note/template.py +0 -0
  91. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/offsets/__init__.py +0 -0
  92. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/offsets/offsets.py +0 -0
  93. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/offsets/outliers.py +0 -0
  94. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/orchestration.py +0 -0
  95. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/palette.py +0 -0
  96. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/pipeline/__init__.py +0 -0
  97. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/pipeline/diagnose.py +0 -0
  98. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/pipeline/engine.py +0 -0
  99. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/pipeline/rules.py +0 -0
  100. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/providers/__init__.py +0 -0
  101. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/providers/base.py +0 -0
  102. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/providers/bde.py +0 -0
  103. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/providers/ecb.py +0 -0
  104. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/providers/fred.py +0 -0
  105. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/providers/imf_irfcl.py +0 -0
  106. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/providers/lseg.py +0 -0
  107. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/py.typed +0 -0
  108. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/__init__.py +0 -0
  109. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/content/__init__.py +0 -0
  110. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/content/content.py +0 -0
  111. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/content/images.py +0 -0
  112. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/content/section.py +0 -0
  113. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/content/subtitle.py +0 -0
  114. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/content/table.py +0 -0
  115. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/content/text.py +0 -0
  116. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/content/title.py +0 -0
  117. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/document.py +0 -0
  118. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/render/report.py +0 -0
  119. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/scaffold.py +0 -0
  120. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/testing/__init__.py +0 -0
  121. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/testing/compare.py +0 -0
  122. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/utils/__init__.py +0 -0
  123. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/utils/config.py +0 -0
  124. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/utils/format.py +0 -0
  125. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/utils/globals.py +0 -0
  126. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/utils/matplotlib.py +0 -0
  127. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/utils/series.py +0 -0
  128. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/utils/shortcuts.py +0 -0
  129. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/utils/template.py +0 -0
  130. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/__init__.py +0 -0
  131. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/app.py +0 -0
  132. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/brand.py +0 -0
  133. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/cli.py +0 -0
  134. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/components.py +0 -0
  135. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/formatting.py +0 -0
  136. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/launcher.py +0 -0
  137. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/palette.py +0 -0
  138. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/plotting.py +0 -0
  139. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/scaffold.py +0 -0
  140. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/Dockerfile.tmpl +0 -0
  141. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/README.md.tmpl +0 -0
  142. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/dot-dockerignore.tmpl +0 -0
  143. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/dot-gitignore.tmpl +0 -0
  144. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/dot-streamlit/config.toml.tmpl +0 -0
  145. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/pyproject.toml.tmpl +0 -0
  146. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/src/$package/__init__.py.tmpl +0 -0
  147. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/src/$package/cli.py.tmpl +0 -0
  148. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/src/$package/main.py.tmpl +0 -0
  149. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/src/$package/sections.py.tmpl +0 -0
  150. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/tests/__init__.py.tmpl +0 -0
  151. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/templates/tests/test_app.py.tmpl +0 -0
  152. {tesorotools_python-0.1.5 → tesorotools_python-0.1.6}/src/tesorotools/web/theme.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tesorotools-python
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Requires-Python: >=3.13
5
5
  Requires-Dist: babel>=2.17
6
6
  Requires-Dist: matplotlib>=3.10
@@ -24,6 +24,7 @@ overrides only need to update ``plots.yaml``.
24
24
  from __future__ import annotations
25
25
 
26
26
  import math
27
+ import warnings
27
28
  from collections.abc import Sequence
28
29
  from pathlib import Path
29
30
  from typing import Any, Literal, Protocol, Self, cast, runtime_checkable
@@ -698,14 +699,21 @@ def date_span_ticks(
698
699
  lo: float = float(cast(Any, date2num(data_min)))
699
700
  hi: float = float(cast(Any, date2num(data_max)))
700
701
  locator = AutoDateLocator(minticks=2, maxticks=maxticks)
701
- candidates: list[float] = cast(
702
- "list[float]", locator.tick_values(data_min, data_max)
703
- )
704
- if not pin_start:
705
- return [t for t in candidates if lo <= t <= hi] or [lo, hi]
706
- chosen: Any = locator.get_locator( # type: ignore[reportUnknownMemberType]
707
- data_min, data_max
708
- )
702
+ # With few ticks over a long span the locator finds no interval in its
703
+ # table, warns and falls back to a sensible default: the fallback is
704
+ # the wanted result, the warning only noise for whoever draws a chart.
705
+ with warnings.catch_warnings():
706
+ warnings.filterwarnings(
707
+ "ignore", "AutoDateLocator was unable", UserWarning
708
+ )
709
+ candidates: list[float] = cast(
710
+ "list[float]", locator.tick_values(data_min, data_max)
711
+ )
712
+ if not pin_start:
713
+ return [t for t in candidates if lo <= t <= hi] or [lo, hi]
714
+ chosen: Any = locator.get_locator( # type: ignore[reportUnknownMemberType]
715
+ data_min, data_max
716
+ )
709
717
  if isinstance(chosen, YearLocator):
710
718
  step = int(cast(Any, chosen.base).step)
711
719
  if step > 1:
@@ -55,6 +55,7 @@ from docx.oxml import OxmlElement, parse_xml # type: ignore[reportUnknownVariab
55
55
  from docx.oxml.ns import qn
56
56
  from docx.shared import Cm, Emu, Pt, Twips
57
57
  from docx.table import Table as DocxTable
58
+ from docx.text.run import Run
58
59
  from docx.table import _Cell # pyright: ignore[reportPrivateUsage]
59
60
  from docx.text.paragraph import Paragraph
60
61
 
@@ -99,6 +100,8 @@ _MIN_PAIR_SCALE = 0.9
99
100
 
100
101
  #: Space after a chart or table block: that of the ``Nota`` style.
101
102
  _BLOCK_AFTER = 160
103
+ #: Left indent (twips) of the text of a list item («Bullet Nivel 1»).
104
+ _ITEM_INDENT = 714
102
105
 
103
106
  _CHART = "Gráfico"
104
107
  _TABLE = "Tabla"
@@ -261,7 +264,26 @@ def format_number(
261
264
 
262
265
 
263
266
  #: Cells that do not make a numeric column textual.
264
- _PLACEHOLDERS = {"", "-", "\u2013", "\u2014", "..", "...", "n.d.", "nd", "s.d."}
267
+ _PLACEHOLDERS = {
268
+ "",
269
+ "-",
270
+ "\u2013",
271
+ "\u2014",
272
+ "\u2212",
273
+ "\u2026",
274
+ "..",
275
+ "...",
276
+ "n.d.",
277
+ "n.d",
278
+ "nd",
279
+ "n/d",
280
+ "s.d.",
281
+ "s.d",
282
+ "sd",
283
+ "n.a.",
284
+ "n.a",
285
+ "na",
286
+ }
265
287
 
266
288
 
267
289
  def _is_numeric(text: str) -> bool:
@@ -282,8 +304,13 @@ def _year_column(values: Sequence[object]) -> bool:
282
304
  )
283
305
 
284
306
 
307
+ def _placeholder(cell: str) -> str:
308
+ """*cell* lowered and without spaces («n. d.» -> «n.d.»)."""
309
+ return "".join(cell.split()).lower()
310
+
311
+
285
312
  def _numeric_column(cells: Sequence[str]) -> bool:
286
- values = [c for c in cells if c.strip().lower() not in _PLACEHOLDERS]
313
+ values = [c for c in cells if _placeholder(c) not in _PLACEHOLDERS]
287
314
  return bool(values) and all(_is_numeric(c) for c in values)
288
315
 
289
316
 
@@ -467,6 +494,18 @@ def _footnotes_root(document: Document) -> tuple[Any, Any]:
467
494
  return part, _parse(part.blob)
468
495
 
469
496
 
497
+ def _es(value: float, spec: str = "g") -> str:
498
+ """*value* with a decimal comma, for the messages («7,2 cm»)."""
499
+ return format(value, spec).replace(".", ",")
500
+
501
+
502
+ def _end_block(table: DocxTable) -> None:
503
+ """The last row of *table* does not keep with what follows it."""
504
+ for cell in table.rows[-1].cells:
505
+ for paragraph in cell.paragraphs:
506
+ paragraph.paragraph_format.keep_with_next = False
507
+
508
+
470
509
  def _cm(emu: float) -> float:
471
510
  return emu / 360000
472
511
 
@@ -644,8 +683,17 @@ class Note:
644
683
  self._block()
645
684
  if level == 1:
646
685
  self._appendix = is_appendix(text)
647
- self._references = _REFERENCES.match(text.strip()) is not None
686
+ self._references = _REFERENCES.search(text) is not None
648
687
  paragraph = self._add(f"Heading{level}")
688
+ if self._appendix and level < 3:
689
+ # Annexes, references and bibliography are not numbered
690
+ # sections («3. Anexo I» reads as a section of the body): the
691
+ # numbering of the style is switched off (numId 0) and so is
692
+ # the hanging indent left for the number.
693
+ pPr = _xml(paragraph)._p.get_or_add_pPr()
694
+ pPr.get_or_add_numPr().get_or_add_numId().val = 0
695
+ paragraph.paragraph_format.left_indent = Twips(0)
696
+ paragraph.paragraph_format.first_line_indent = Twips(0)
649
697
  self._write(paragraph, spans)
650
698
  return paragraph
651
699
 
@@ -661,12 +709,20 @@ class Note:
661
709
  if self._references or long_link:
662
710
  paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
663
711
 
664
- def paragraph(self, text: str) -> Paragraph:
665
- """Body paragraph; start it with its ``**lead sentence.**``."""
712
+ def paragraph(self, text: str, *, continuation: bool = False) -> Paragraph:
713
+ """Body paragraph; start it with its ``**lead sentence.**``.
714
+
715
+ ``continuation`` is a further paragraph of a list item: indented
716
+ like the item's text, and the list goes on after it (the next
717
+ item keeps its number).
718
+ """
666
719
  spans = parse_inline(text)
667
- self._block()
720
+ if not continuation:
721
+ self._block()
668
722
  style = self._appendix_style() if self._appendix else "Normal"
669
723
  paragraph = self._add(style)
724
+ if continuation:
725
+ paragraph.paragraph_format.left_indent = Twips(_ITEM_INDENT)
670
726
  self._align(paragraph, spans)
671
727
  self._write(paragraph, spans)
672
728
  return paragraph
@@ -778,8 +834,9 @@ class Note:
778
834
  def _shrink(self, paragraph: Paragraph) -> None:
779
835
  """Annex bullets and numbered items: the annex font size."""
780
836
  if self._appendix:
781
- for run in paragraph.runs:
782
- run.font.size = Pt(_APPENDIX_PT)
837
+ # every run, also those inside hyperlinks (not in .runs)
838
+ for r in _xml(paragraph)._p.iter(qn("w:r")):
839
+ Run(r, paragraph).font.size = Pt(_APPENDIX_PT)
783
840
  paragraph.paragraph_format.space_after = Pt(2)
784
841
 
785
842
  # -- captions, sources and notes ---------------------------------------
@@ -846,13 +903,20 @@ class Note:
846
903
  The block ends with the spacing of the ``Nota`` style even when
847
904
  there is no note, so the next paragraph does not stick to it
848
905
  (*last* is ``None`` after a table, whose end already separates).
906
+ Its last paragraph does not keep with the next one: the ``Fuente``
907
+ style (and the image) keep with the next paragraph so that the
908
+ block holds together, and on the last paragraph that would chain
909
+ the block to the following chart or heading, which would then jump
910
+ to the next page together and leave half a page blank.
849
911
  """
850
912
  if source:
851
913
  last = self._attribution("Fuente", source)
852
914
  if note:
853
- self._attribution("Nota", note)
915
+ last = self._attribution("Nota", note)
854
916
  elif last is not None:
855
917
  last.paragraph_format.space_after = Twips(_BLOCK_AFTER)
918
+ if last is not None:
919
+ last.paragraph_format.keep_with_next = False
856
920
 
857
921
  # -- charts -------------------------------------------------------------
858
922
 
@@ -875,8 +939,8 @@ class Note:
875
939
  width = float(Cm(chart.width_cm))
876
940
  if chart.width_cm > TEXT_WIDTH_CM:
877
941
  self.warnings.append(
878
- f"«{chart.title}»: ancho de {chart.width_cm:g} cm, más que"
879
- f" el texto; se reduce a {TEXT_WIDTH_CM:g} cm"
942
+ f"«{chart.title}»: ancho de {_es(chart.width_cm)} cm, más que"
943
+ f" el texto; se reduce a {_es(TEXT_WIDTH_CM)} cm"
880
944
  )
881
945
  width = min(width, float(Cm(TEXT_WIDTH_CM)))
882
946
  height = width * native_h / native_w
@@ -885,7 +949,7 @@ class Note:
885
949
  width, height = width * scale, height * scale
886
950
  self.warnings.append(
887
951
  f"«{chart.title}»: la imagen no cabe en una página; se reduce"
888
- f" a {_cm(width):.1f} x {_cm(height):.1f} cm"
952
+ f" a {_es(_cm(width), '.1f')} x {_es(_cm(height), '.1f')} cm"
889
953
  )
890
954
  return round(width), round(height)
891
955
 
@@ -904,8 +968,8 @@ class Note:
904
968
  and _cm(width) < _SMALL_CHART_CM
905
969
  ):
906
970
  self.warnings.append(
907
- f"«{chart.title}» mide {_cm(width):.1f} cm de ancho en un texto"
908
- f" de {TEXT_WIDTH_CM:g}: genéralo con chart_size() o ponlo en"
971
+ f"«{chart.title}» mide {_es(_cm(width), '.1f')} cm de ancho en un texto"
972
+ f" de {_es(TEXT_WIDTH_CM)}: genéralo con chart_size() o ponlo en"
909
973
  " un bloque graficos con otro"
910
974
  )
911
975
  self._block()
@@ -949,7 +1013,7 @@ class Note:
949
1013
  if chart.width_cm > PAIR_WIDTH_CM:
950
1014
  self.warnings.append(
951
1015
  f"«{chart.title}»: en una pareja el ancho máximo es"
952
- f" {PAIR_WIDTH_CM:g} cm"
1016
+ f" {_es(PAIR_WIDTH_CM)} cm"
953
1017
  )
954
1018
  cap = min(cap, chart.width_cm)
955
1019
  caps.append(cap)
@@ -960,7 +1024,7 @@ class Note:
960
1024
  if scale < _MIN_PAIR_SCALE:
961
1025
  self.warnings.append(
962
1026
  f"«{chart.title}»: en la pareja se reduce al"
963
- f" {scale * 100:.0f} % de su tamaño (la letra queda por debajo"
1027
+ f" {_es(scale * 100, '.0f')} % de su tamaño (la letra queda por debajo"
964
1028
  " de 7 puntos); genera las imágenes con"
965
1029
  " chart_size(pair=True) y la misma proporción"
966
1030
  )
@@ -1006,6 +1070,7 @@ class Note:
1006
1070
  # image itself it would lift this chart above the other one
1007
1071
  if not (one_note and col) and (last is not image or rows == 2):
1008
1072
  last.paragraph_format.space_after = Twips(_BLOCK_AFTER)
1073
+ _end_block(table)
1009
1074
 
1010
1075
  def _separate_tables(self) -> None:
1011
1076
  """Keep a new table from merging with one that ends the body.
@@ -1152,6 +1217,8 @@ class Note:
1152
1217
  _element("w:noWrap")
1153
1218
  )
1154
1219
  self._fit_table(grid, table, rows, size or _TABLE_PT)
1220
+ if not (table.source or table.note):
1221
+ _end_block(grid)
1155
1222
  self._close(None, table.source, table.note)
1156
1223
 
1157
1224
  def _fit_table(
@@ -1166,8 +1233,8 @@ class Note:
1166
1233
  total = sum(table.widths)
1167
1234
  if total > TEXT_WIDTH_CM + 0.01:
1168
1235
  self.warnings.append(
1169
- f"la tabla «{table.title}» mide {total:g} cm de ancho;"
1170
- f" el texto, {TEXT_WIDTH_CM:g} cm"
1236
+ f"la tabla «{table.title}» mide {_es(total)} cm de ancho;"
1237
+ f" el texto, {_es(TEXT_WIDTH_CM)} cm"
1171
1238
  )
1172
1239
  props = _xml(grid)._tbl.tblPr
1173
1240
  props.find(qn("w:tblLook")).addprevious(
@@ -1185,7 +1252,7 @@ class Note:
1185
1252
  if needed > TEXT_WIDTH_CM:
1186
1253
  self.warnings.append(
1187
1254
  f"la tabla «{table.title}» parece no caber en el ancho de la"
1188
- f" página (unos {needed:.0f} cm de {TEXT_WIDTH_CM:g}):"
1255
+ f" página (unos {_es(needed, '.0f')} cm de {_es(TEXT_WIDTH_CM)}):"
1189
1256
  " abrevia las cabeceras, quita columnas o fija «anchos»"
1190
1257
  )
1191
1258