finalcif 177__tar.gz → 178__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 (206) hide show
  1. finalcif-178/LICENSE.md +5 -0
  2. {finalcif-177 → finalcif-178}/PKG-INFO +5 -8
  3. {finalcif-177 → finalcif-178}/README.md +2 -4
  4. finalcif-178/finalcif/__init__.py +3 -0
  5. {finalcif-177 → finalcif-178}/finalcif/appwindow.py +20 -17
  6. {finalcif-177 → finalcif-178}/finalcif/report/references.py +0 -117
  7. {finalcif-177 → finalcif-178}/finalcif/report/report_text.py +1 -50
  8. {finalcif-177 → finalcif-178}/finalcif/report/templated_report.py +35 -13
  9. {finalcif-177 → finalcif-178}/finalcif/template/report.tmpl +9 -4
  10. finalcif-178/finalcif/template/report_default.docx +0 -0
  11. {finalcif-177 → finalcif-178}/finalcif/template/report_fastmolwidget.tmpl +9 -4
  12. finalcif-178/finalcif/template/template_for_multitable.docx +0 -0
  13. finalcif-178/finalcif/template/template_text.docx +0 -0
  14. finalcif-178/finalcif/template/template_without_text.docx +0 -0
  15. finalcif-178/githooks/pre-push +76 -0
  16. {finalcif-177 → finalcif-178}/pyproject.toml +4 -3
  17. {finalcif-177 → finalcif-178}/uv.lock +198 -190
  18. finalcif-177/LICENSE +0 -7
  19. finalcif-177/finalcif/__init__.py +0 -3
  20. finalcif-177/finalcif/report/mtools.py +0 -43
  21. finalcif-177/finalcif/report/tables.py +0 -965
  22. finalcif-177/finalcif/template/report_default.docx +0 -0
  23. finalcif-177/finalcif/template/template_for_multitable.docx +0 -0
  24. finalcif-177/finalcif/template/template_text.docx +0 -0
  25. finalcif-177/finalcif/template/template_without_text.docx +0 -0
  26. {finalcif-177 → finalcif-178}/.gitattributes +0 -0
  27. {finalcif-177 → finalcif-178}/.githooks/pre-push +0 -0
  28. {finalcif-177 → finalcif-178}/.gitignore +0 -0
  29. {finalcif-177 → finalcif-178}/.python-version +0 -0
  30. {finalcif-177 → finalcif-178}/.ruff.toml +0 -0
  31. {finalcif-177 → finalcif-178}/.run/Make Documentation MAC.run.xml +0 -0
  32. {finalcif-177 → finalcif-178}/.run/compile_ui_files.run.xml +0 -0
  33. {finalcif-177 → finalcif-178}/.run/make documentation Windows.run.xml +0 -0
  34. {finalcif-177 → finalcif-178}/AGENTS.md +0 -0
  35. {finalcif-177 → finalcif-178}/CODE_OF_CONDUCT.md +0 -0
  36. {finalcif-177 → finalcif-178}/CONTRIBUTING.md +0 -0
  37. {finalcif-177 → finalcif-178}/Finalcif_installer_win.spec +0 -0
  38. {finalcif-177 → finalcif-178}/Finalcif_linux_onefile.spec +0 -0
  39. {finalcif-177 → finalcif-178}/Finalcif_mac.spec +0 -0
  40. {finalcif-177 → finalcif-178}/finalcif/app_path.py +0 -0
  41. {finalcif-177 → finalcif-178}/finalcif/cif/__init__.py +0 -0
  42. {finalcif-177 → finalcif-178}/finalcif/cif/all_cif_dicts.py +0 -0
  43. {finalcif-177 → finalcif-178}/finalcif/cif/atoms.py +0 -0
  44. {finalcif-177 → finalcif-178}/finalcif/cif/checkcif/__init__.py +0 -0
  45. {finalcif-177 → finalcif-178}/finalcif/cif/checkcif/checkcif.py +0 -0
  46. {finalcif-177 → finalcif-178}/finalcif/cif/cif_dict_foo.py +0 -0
  47. {finalcif-177 → finalcif-178}/finalcif/cif/cif_file_io.py +0 -0
  48. {finalcif-177 → finalcif-178}/finalcif/cif/cif_keyword_mapping.py +0 -0
  49. {finalcif-177 → finalcif-178}/finalcif/cif/cif_order.py +0 -0
  50. {finalcif-177 → finalcif-178}/finalcif/cif/cod/__init__.py +0 -0
  51. {finalcif-177 → finalcif-178}/finalcif/cif/cod/deposit.py +0 -0
  52. {finalcif-177 → finalcif-178}/finalcif/cif/cod/deposit_check.py +0 -0
  53. {finalcif-177 → finalcif-178}/finalcif/cif/cod/deposition_list.py +0 -0
  54. {finalcif-177 → finalcif-178}/finalcif/cif/cod/doi.py +0 -0
  55. {finalcif-177 → finalcif-178}/finalcif/cif/cod/upload.py +0 -0
  56. {finalcif-177 → finalcif-178}/finalcif/cif/cod/website_parser.py +0 -0
  57. {finalcif-177 → finalcif-178}/finalcif/cif/core_dict.py +0 -0
  58. {finalcif-177 → finalcif-178}/finalcif/cif/hkl.py +0 -0
  59. {finalcif-177 → finalcif-178}/finalcif/cif/modulation_dict.py +0 -0
  60. {finalcif-177 → finalcif-178}/finalcif/cif/powder_dict.py +0 -0
  61. {finalcif-177 → finalcif-178}/finalcif/cif/reference.py +0 -0
  62. {finalcif-177 → finalcif-178}/finalcif/cif/restraints_dict.py +0 -0
  63. {finalcif-177 → finalcif-178}/finalcif/cif/text.py +0 -0
  64. {finalcif-177 → finalcif-178}/finalcif/cif/twin_dict.py +0 -0
  65. {finalcif-177 → finalcif-178}/finalcif/cif/vrf_entry.py +0 -0
  66. {finalcif-177 → finalcif-178}/finalcif/ciforder/__init__.py +0 -0
  67. {finalcif-177 → finalcif-178}/finalcif/ciforder/order.py +0 -0
  68. {finalcif-177 → finalcif-178}/finalcif/ciforder/order_ui.py +0 -0
  69. {finalcif-177 → finalcif-178}/finalcif/ciforder/order_ui.ui +0 -0
  70. {finalcif-177 → finalcif-178}/finalcif/datafiles/__init__.py +0 -0
  71. {finalcif-177 → finalcif-178}/finalcif/datafiles/bruker_data.py +0 -0
  72. {finalcif-177 → finalcif-178}/finalcif/datafiles/bruker_frame.py +0 -0
  73. {finalcif-177 → finalcif-178}/finalcif/datafiles/ccdc_mail.py +0 -0
  74. {finalcif-177 → finalcif-178}/finalcif/datafiles/data.py +0 -0
  75. {finalcif-177 → finalcif-178}/finalcif/datafiles/p4p_reader.py +0 -0
  76. {finalcif-177 → finalcif-178}/finalcif/datafiles/sadabs.py +0 -0
  77. {finalcif-177 → finalcif-178}/finalcif/datafiles/saint.py +0 -0
  78. {finalcif-177 → finalcif-178}/finalcif/datafiles/shelx_lst.py +0 -0
  79. {finalcif-177 → finalcif-178}/finalcif/datafiles/utils.py +0 -0
  80. {finalcif-177 → finalcif-178}/finalcif/equip_property/__init__.py +0 -0
  81. {finalcif-177 → finalcif-178}/finalcif/equip_property/author_loop_templates.py +0 -0
  82. {finalcif-177 → finalcif-178}/finalcif/equip_property/equipment.py +0 -0
  83. {finalcif-177 → finalcif-178}/finalcif/equip_property/properties.py +0 -0
  84. {finalcif-177 → finalcif-178}/finalcif/equip_property/tools.py +0 -0
  85. {finalcif-177 → finalcif-178}/finalcif/finalcif_start.py +0 -0
  86. {finalcif-177 → finalcif-178}/finalcif/gui/__init__.py +0 -0
  87. {finalcif-177 → finalcif-178}/finalcif/gui/block_combobox.py +0 -0
  88. {finalcif-177 → finalcif-178}/finalcif/gui/checkcif_browser.py +0 -0
  89. {finalcif-177 → finalcif-178}/finalcif/gui/cif_table_model.py +0 -0
  90. {finalcif-177 → finalcif-178}/finalcif/gui/cif_table_view.py +0 -0
  91. {finalcif-177 → finalcif-178}/finalcif/gui/combobox.py +0 -0
  92. {finalcif-177 → finalcif-178}/finalcif/gui/custom_classes.py +0 -0
  93. {finalcif-177 → finalcif-178}/finalcif/gui/dialogs.py +0 -0
  94. {finalcif-177 → finalcif-178}/finalcif/gui/edit_button.py +0 -0
  95. {finalcif-177 → finalcif-178}/finalcif/gui/equipmenttable.py +0 -0
  96. {finalcif-177 → finalcif-178}/finalcif/gui/file_editor.py +0 -0
  97. {finalcif-177 → finalcif-178}/finalcif/gui/finalcif_gui_ui.py +0 -0
  98. {finalcif-177 → finalcif-178}/finalcif/gui/finalcif_gui_ui.ui +0 -0
  99. {finalcif-177 → finalcif-178}/finalcif/gui/import_selector.py +0 -0
  100. {finalcif-177 → finalcif-178}/finalcif/gui/import_selector_ui.py +0 -0
  101. {finalcif-177 → finalcif-178}/finalcif/gui/import_selector_ui.ui +0 -0
  102. {finalcif-177 → finalcif-178}/finalcif/gui/loop_creator.py +0 -0
  103. {finalcif-177 → finalcif-178}/finalcif/gui/loop_creator_ui.py +0 -0
  104. {finalcif-177 → finalcif-178}/finalcif/gui/loop_creator_ui.ui +0 -0
  105. {finalcif-177 → finalcif-178}/finalcif/gui/loops.py +0 -0
  106. {finalcif-177 → finalcif-178}/finalcif/gui/mainstackwidget.py +0 -0
  107. {finalcif-177 → finalcif-178}/finalcif/gui/mixins.py +0 -0
  108. {finalcif-177 → finalcif-178}/finalcif/gui/new_key_dialog.py +0 -0
  109. {finalcif-177 → finalcif-178}/finalcif/gui/new_key_dialog_ui.py +0 -0
  110. {finalcif-177 → finalcif-178}/finalcif/gui/new_key_dialog_ui.ui +0 -0
  111. {finalcif-177 → finalcif-178}/finalcif/gui/plaintextedit.py +0 -0
  112. {finalcif-177 → finalcif-178}/finalcif/gui/playground.py +0 -0
  113. {finalcif-177 → finalcif-178}/finalcif/gui/propertytable.py +0 -0
  114. {finalcif-177 → finalcif-178}/finalcif/gui/shelx_navigation.py +0 -0
  115. {finalcif-177 → finalcif-178}/finalcif/gui/single_line_textedit.py +0 -0
  116. {finalcif-177 → finalcif-178}/finalcif/gui/spell_check_edit.py +0 -0
  117. {finalcif-177 → finalcif-178}/finalcif/gui/squeeze_dialog.py +0 -0
  118. {finalcif-177 → finalcif-178}/finalcif/gui/syntax_highlighter.py +0 -0
  119. {finalcif-177 → finalcif-178}/finalcif/gui/text_templates_ui.py +0 -0
  120. {finalcif-177 → finalcif-178}/finalcif/gui/text_templates_ui.ui +0 -0
  121. {finalcif-177 → finalcif-178}/finalcif/gui/text_value_editor.py +0 -0
  122. {finalcif-177 → finalcif-178}/finalcif/gui/timers.py +0 -0
  123. {finalcif-177 → finalcif-178}/finalcif/gui/validators.py +0 -0
  124. {finalcif-177 → finalcif-178}/finalcif/gui/vrf_classes.py +0 -0
  125. {finalcif-177 → finalcif-178}/finalcif/gui/vzs_viewer.py +0 -0
  126. {finalcif-177 → finalcif-178}/finalcif/icon/__init__.py +0 -0
  127. {finalcif-177 → finalcif-178}/finalcif/icon/finalcif.png +0 -0
  128. {finalcif-177 → finalcif-178}/finalcif/icon/finalcif2.ico +0 -0
  129. {finalcif-177 → finalcif-178}/finalcif/icon/finalcif2.png +0 -0
  130. {finalcif-177 → finalcif-178}/finalcif/icon/multitable.ico +0 -0
  131. {finalcif-177 → finalcif-178}/finalcif/icon/multitable.png +0 -0
  132. {finalcif-177 → finalcif-178}/finalcif/icon/multitable.xcf +0 -0
  133. {finalcif-177 → finalcif-178}/finalcif/report/__init__.py +0 -0
  134. {finalcif-177 → finalcif-178}/finalcif/report/archive_report.py +0 -0
  135. {finalcif-177 → finalcif-178}/finalcif/report/gui/__init__.py +0 -0
  136. {finalcif-177 → finalcif-178}/finalcif/report/gui/mainwindow.py +0 -0
  137. {finalcif-177 → finalcif-178}/finalcif/report/gui/mainwindow.ui +0 -0
  138. {finalcif-177 → finalcif-178}/finalcif/report/symm.py +0 -0
  139. {finalcif-177 → finalcif-178}/finalcif/template/__init__.py +0 -0
  140. {finalcif-177 → finalcif-178}/finalcif/template/bootstrap/bootstrap.min.css +0 -0
  141. {finalcif-177 → finalcif-178}/finalcif/template/machines/D8_VENTURE.cif +0 -0
  142. {finalcif-177 → finalcif-178}/finalcif/template/mathml2omml.xsl +0 -0
  143. {finalcif-177 → finalcif-178}/finalcif/template/miew/LICENSES +0 -0
  144. {finalcif-177 → finalcif-178}/finalcif/template/miew/Miew.css +0 -0
  145. {finalcif-177 → finalcif-178}/finalcif/template/miew/Miew.min.js +0 -0
  146. {finalcif-177 → finalcif-178}/finalcif/template/miew/lodash.min.js +0 -0
  147. {finalcif-177 → finalcif-178}/finalcif/template/miew/three.js +0 -0
  148. {finalcif-177 → finalcif-178}/finalcif/template/report.tex +0 -0
  149. {finalcif-177 → finalcif-178}/finalcif/template/report2.tex +0 -0
  150. {finalcif-177 → finalcif-178}/finalcif/template/template1.docx +0 -0
  151. {finalcif-177 → finalcif-178}/finalcif/template/templates.py +0 -0
  152. {finalcif-177 → finalcif-178}/finalcif/template/unicode2latex/FakePlasTeX/FakeContext.py +0 -0
  153. {finalcif-177 → finalcif-178}/finalcif/template/unicode2latex/FakePlasTeX/FakeTokenizer.py +0 -0
  154. {finalcif-177 → finalcif-178}/finalcif/template/unicode2latex/FakePlasTeX/README.md +0 -0
  155. {finalcif-177 → finalcif-178}/finalcif/template/unicode2latex/FakePlasTeX/TokenizerPassThru.py +0 -0
  156. {finalcif-177 → finalcif-178}/finalcif/template/unicode2latex/LICENSE +0 -0
  157. {finalcif-177 → finalcif-178}/finalcif/template/unicode2latex/__init__.py +0 -0
  158. {finalcif-177 → finalcif-178}/finalcif/template/unicode2latex/u2l.py +0 -0
  159. {finalcif-177 → finalcif-178}/finalcif/template/unicode2latex/unicode-math-table.tex +0 -0
  160. {finalcif-177 → finalcif-178}/finalcif/template/unicode2latex/unicode-math-xetex.sty +0 -0
  161. {finalcif-177 → finalcif-178}/finalcif/template/xsl/convert.py +0 -0
  162. {finalcif-177 → finalcif-178}/finalcif/template/xsl/xmltohtml.xsl +0 -0
  163. {finalcif-177 → finalcif-178}/finalcif/template/xsl/xmltolatex.xsl +0 -0
  164. {finalcif-177 → finalcif-178}/finalcif/tools/__init__.py +0 -0
  165. {finalcif-177 → finalcif-178}/finalcif/tools/chemparse.py +0 -0
  166. {finalcif-177 → finalcif-178}/finalcif/tools/download.py +0 -0
  167. {finalcif-177 → finalcif-178}/finalcif/tools/dsrmath.py +0 -0
  168. {finalcif-177 → finalcif-178}/finalcif/tools/formal_charge.py +0 -0
  169. {finalcif-177 → finalcif-178}/finalcif/tools/misc.py +0 -0
  170. {finalcif-177 → finalcif-178}/finalcif/tools/mol_file_writer.py +0 -0
  171. {finalcif-177 → finalcif-178}/finalcif/tools/options.py +0 -0
  172. {finalcif-177 → finalcif-178}/finalcif/tools/platon.py +0 -0
  173. {finalcif-177 → finalcif-178}/finalcif/tools/pupdate.py +0 -0
  174. {finalcif-177 → finalcif-178}/finalcif/tools/selfupdate.py +0 -0
  175. {finalcif-177 → finalcif-178}/finalcif/tools/settings.py +0 -0
  176. {finalcif-177 → finalcif-178}/finalcif/tools/shred.py +0 -0
  177. {finalcif-177 → finalcif-178}/finalcif/tools/solvents.py +0 -0
  178. {finalcif-177 → finalcif-178}/finalcif/tools/space_groups.py +0 -0
  179. {finalcif-177 → finalcif-178}/finalcif/tools/spgr_format.py +0 -0
  180. {finalcif-177 → finalcif-178}/finalcif/tools/squeeze.py +0 -0
  181. {finalcif-177 → finalcif-178}/finalcif/tools/statusbar.py +0 -0
  182. {finalcif-177 → finalcif-178}/finalcif/tools/sumformula.py +0 -0
  183. {finalcif-177 → finalcif-178}/finalcif/tools/z_from_packing.py +0 -0
  184. {finalcif-177 → finalcif-178}/finalcif.exe +0 -0
  185. {finalcif-177 → finalcif-178}/platon/README.txt +0 -0
  186. {finalcif-177 → finalcif-178}/platon/compile_platon.bat +0 -0
  187. {finalcif-177 → finalcif-178}/platon/platon_special.exe +0 -0
  188. {finalcif-177 → finalcif-178}/platon/platon_special.f +0 -0
  189. {finalcif-177 → finalcif-178}/pylock.toml +0 -0
  190. {finalcif-177 → finalcif-178}/run_and_capture.py +0 -0
  191. {finalcif-177 → finalcif-178}/run_finalcif +0 -0
  192. {finalcif-177 → finalcif-178}/run_finalcif.bat +0 -0
  193. {finalcif-177 → finalcif-178}/screenshots/finalcif_checkcif.png +0 -0
  194. {finalcif-177 → finalcif-178}/screenshots/finalcif_details.png +0 -0
  195. {finalcif-177 → finalcif-178}/screenshots/finalcif_loops.png +0 -0
  196. {finalcif-177 → finalcif-178}/screenshots/finalcif_loops2.png +0 -0
  197. {finalcif-177 → finalcif-178}/screenshots/finalcif_main.png +0 -0
  198. {finalcif-177 → finalcif-178}/screenshots/finalcif_report.png +0 -0
  199. {finalcif-177 → finalcif-178}/screenshots/finalcif_responses.png +0 -0
  200. {finalcif-177 → finalcif-178}/screenshots/loops_column_buttons.png +0 -0
  201. {finalcif-177 → finalcif-178}/work/compare_z_platon.py +0 -0
  202. {finalcif-177 → finalcif-178}/work/diagnose_1519506.py +0 -0
  203. {finalcif-177 → finalcif-178}/work/diagnose_z.py +0 -0
  204. {finalcif-177 → finalcif-178}/work/investigate_failures.py +0 -0
  205. {finalcif-177 → finalcif-178}/work/validate_cod.py +0 -0
  206. {finalcif-177 → finalcif-178}/work/validate_zprime.py +0 -0
@@ -0,0 +1,5 @@
1
+ "THE BEER-WARE LICENSE" (Revision 42):
2
+ Daniel Kratzert, dkratzert@gmx.de wrote this file. As long as you retain
3
+ this notice you can do whatever you want with this stuff. If we meet some day,
4
+ and you think this stuff is worth it, you can buy me a beer in return.
5
+ Dr. Daniel Kratzert
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: finalcif
3
- Version: 177
3
+ Version: 178
4
4
  Summary: CIF file editor
5
5
  Project-URL: Homepage, https://dkratzert.de/finalcif.html
6
6
  Project-URL: Bug Tracker, https://github.com/dkratzert/FinalCif/issues
7
7
  Author-email: Daniel Kratzert <dkratzert@gmx.de>
8
- License-File: LICENSE
8
+ License-Expression: Beerware
9
+ License-File: LICENSE.md
9
10
  Classifier: Development Status :: 5 - Production/Stable
10
- Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Topic :: Scientific/Engineering :: Chemistry
@@ -33,16 +33,13 @@ Requires-Dist: qtpy
33
33
  Requires-Dist: requests
34
34
  Requires-Dist: shelxfile>=24
35
35
  Requires-Dist: urllib3==2.7.0
36
- Requires-Dist: wheel==0.47.0
37
36
  Description-Content-Type: text/markdown
38
37
 
39
38
  ![Latest Release](https://img.shields.io/github/v/tag/dkratzert/FinalCif?label=Release)
40
39
  [![Unit Tests](https://github.com/dkratzert/FinalCif/actions/workflows/python_tests.yml/badge.svg?branch=master)](https://github.com/dkratzert/FinalCif/actions/workflows/python_tests.yml)
41
40
  ![Contributions](https://img.shields.io/badge/contributions-welcome-blue)
42
- [![PyPI package](https://repology.org/badge/version-for-repo/pypi/python:finalcif.svg)](https://repology.org/project/python:finalcif/versions)
43
- <a href="https://repology.org/project/finalcif/versions">
44
- <img src="https://repology.org/badge/vertical-allrepos/finalcif.svg" alt="Packaging status" align="right">
45
- </a>
41
+ [![PyPI version](https://img.shields.io/pypi/v/finalcif.svg)](https://pypi.org/project/finalcif/)
42
+
46
43
 
47
44
 
48
45
  # FinalCif
@@ -1,10 +1,8 @@
1
1
  ![Latest Release](https://img.shields.io/github/v/tag/dkratzert/FinalCif?label=Release)
2
2
  [![Unit Tests](https://github.com/dkratzert/FinalCif/actions/workflows/python_tests.yml/badge.svg?branch=master)](https://github.com/dkratzert/FinalCif/actions/workflows/python_tests.yml)
3
3
  ![Contributions](https://img.shields.io/badge/contributions-welcome-blue)
4
- [![PyPI package](https://repology.org/badge/version-for-repo/pypi/python:finalcif.svg)](https://repology.org/project/python:finalcif/versions)
5
- <a href="https://repology.org/project/finalcif/versions">
6
- <img src="https://repology.org/badge/vertical-allrepos/finalcif.svg" alt="Packaging status" align="right">
7
- </a>
4
+ [![PyPI version](https://img.shields.io/pypi/v/finalcif.svg)](https://pypi.org/project/finalcif/)
5
+
8
6
 
9
7
 
10
8
  # FinalCif
@@ -0,0 +1,3 @@
1
+
2
+ VERSION = 178
3
+ __version__ = VERSION
@@ -21,7 +21,6 @@ from math import sin, radians
21
21
  from pathlib import Path, WindowsPath
22
22
  from typing import cast, TYPE_CHECKING
23
23
 
24
-
25
24
  if TYPE_CHECKING:
26
25
  from finalcif.cif.checkcif.checkcif import MyHTMLParser
27
26
 
@@ -1625,7 +1624,6 @@ class AppWindow(QMainWindow):
1625
1624
  """
1626
1625
  Generates a report document.
1627
1626
  """
1628
- from finalcif.report.tables import make_multi_tables
1629
1627
  from finalcif.report.templated_report import ReportFormat, TemplatedReport
1630
1628
  from docx.image.exceptions import UnrecognizedImageError
1631
1629
  current_block = self.ui.datanameComboBox.currentIndex()
@@ -1638,7 +1636,7 @@ class AppWindow(QMainWindow):
1638
1636
  return None
1639
1637
  self.load_cif_file(self.cif.finalcif_file, block=current_block, load_changes=False)
1640
1638
  report_filename = self.cif.finalcif_file_prefixed(prefix='report_', suffix='-finalcif.docx')
1641
- multi_table_document = self.cif.finalcif_file_prefixed(prefix='', suffix='-multitable.docx')
1639
+ multi_table_document: Path | None = None
1642
1640
  self.cif.picometer = self.options.use_picometers
1643
1641
  # The picture after the header:
1644
1642
  if self.report_picture_path:
@@ -1652,9 +1650,12 @@ class AppWindow(QMainWindow):
1652
1650
  if template_path.suffix in ('.docx',):
1653
1651
  t = TemplatedReport(format=ReportFormat.RICHTEXT, options=self.options, cif=self.cif)
1654
1652
  ok = t.make_templated_docx_report(output_filename=str(report_filename),
1655
- template_path=Path(self.get_checked_templates_list_text()))
1656
- if self.cif.is_multi_cif and self.cif.doc[0].name != 'global':
1657
- make_multi_tables(cif=self.cif, output_filename=str(multi_table_document))
1653
+ template_path=template_path)
1654
+ if (ok and self.cif.is_multi_cif and self.cif.doc[0].name != 'global'
1655
+ and not t.is_multi_cif_template):
1656
+ multi_table_document = self.cif.finalcif_file_prefixed(prefix='', suffix='-multitable.docx')
1657
+ ok = t.make_templated_docx_report(output_filename=str(multi_table_document),
1658
+ template_path=application_path / 'template' / 'template_for_multitable.docx')
1658
1659
  elif template_path.suffix in ('.html', '.tmpl'):
1659
1660
  t = TemplatedReport(format=ReportFormat.HTML, options=self.options, cif=self.cif)
1660
1661
  report_filename = report_filename.with_suffix('.html')
@@ -1687,16 +1688,16 @@ class AppWindow(QMainWindow):
1687
1688
  if DEBUG:
1688
1689
  print('dbg> Unrecognized image error')
1689
1690
  raise
1690
- show_general_warning(self,'The report contains an unrecognized image format.')
1691
+ show_general_warning(self, 'The report contains an unrecognized image format.')
1691
1692
  return None
1692
1693
  if not self.running_inside_unit_test:
1693
1694
  self.open_report_document(report_filename, multi_table_document)
1694
1695
  # Save report and other files to a zip file:
1695
- self.zip_report(report_filename)
1696
+ self.zip_report(report_filename, multi_table_document)
1696
1697
  return None
1697
1698
  return None
1698
1699
 
1699
- def zip_report(self, report_filename: Path) -> None:
1700
+ def zip_report(self, report_filename: Path, multi_table_document: Path | None = None) -> None:
1700
1701
  from finalcif.report.archive_report import ArchiveReport
1701
1702
  zipfile = self.cif.finalcif_file.with_suffix('.zip')
1702
1703
  if zipfile.exists():
@@ -1711,16 +1712,16 @@ class AppWindow(QMainWindow):
1711
1712
  with suppress(Exception):
1712
1713
  pdfname = self.cif.finalcif_file_prefixed(prefix='checkcif-', suffix='-finalcif.pdf')
1713
1714
  arc.zip.write(filename=pdfname, arcname=pdfname.name)
1714
- with suppress(Exception):
1715
- multitable = self.cif.finalcif_file_prefixed(prefix='', suffix='-multitable.docx')
1716
- arc.zip.write(filename=multitable, arcname=multitable.name)
1715
+ if multi_table_document is not None:
1716
+ with suppress(Exception):
1717
+ arc.zip.write(filename=multi_table_document, arcname=multi_table_document.name)
1717
1718
  with suppress(Exception):
1718
1719
  prp_list = self.cif.finalcif_file.parent.glob('*.prp')
1719
1720
  sorted_prp = sorted(prp_list, key=lambda x: x.stat().st_mtime)
1720
1721
  arc.zip.write(filename=sorted_prp[-1], arcname=sorted_prp[-1].name)
1721
1722
 
1722
- def open_report_document(self, report_filename: Path, multi_table_document: Path) -> None:
1723
- if self.cif.is_multi_cif:
1723
+ def open_report_document(self, report_filename: Path, multi_table_document: Path | None = None) -> None:
1724
+ if multi_table_document is not None:
1724
1725
  open_file(multi_table_document)
1725
1726
  open_file(report_filename)
1726
1727
 
@@ -2295,16 +2296,18 @@ class AppWindow(QMainWindow):
2295
2296
  d = 0.0
2296
2297
  self.ui.numRestraintsLineEdit.setText(self.cif['_refine_ls_number_restraints'])
2297
2298
  self.ui.thetaMaxLineEdit.setText(thetamax)
2298
- self.ui.thetaFullLineEdit.setText(self.cif['_diffrn_reflns_theta_full'])
2299
+ theta_full = self.cif['_diffrn_reflns_theta_full']
2300
+ self.ui.thetaFullLineEdit.setText(theta_full)
2299
2301
  self.ui.dLineEdit.setText(f"{d:5.3f}")
2300
2302
  try:
2301
- compl = float(self.cif['_diffrn_measured_fraction_theta_max']) * 100
2303
+ fraction = self.cif['_diffrn_measured_fraction_theta_full']
2304
+ compl = float(fraction) * 100
2302
2305
  if not compl:
2303
2306
  compl = 0.0
2304
2307
  except (TypeError, ValueError):
2305
2308
  compl = 0.0
2306
2309
  try:
2307
- self.ui.completeLineEdit.setText(f"{compl:<5.1f}")
2310
+ self.ui.completeLineEdit.setText(f"{compl:<5.1f} (at {theta_full}°)")
2308
2311
  except ValueError:
2309
2312
  pass
2310
2313
  self.ui.wavelengthLineEdit.setText(f"{wavelen}")
@@ -5,10 +5,6 @@
5
5
  # and you think this stuff is worth it, you can buy me a beer in return.
6
6
  # Dr. Daniel Kratzert
7
7
  # ----------------------------------------------------------------------------
8
- from contextlib import suppress
9
- from typing import Union
10
-
11
- from docx.text.paragraph import Paragraph
12
8
  from docxtpl import RichText
13
9
 
14
10
  from finalcif import VERSION
@@ -43,89 +39,6 @@ Missing: d*trek
43
39
  """
44
40
 
45
41
 
46
- class ReferenceList:
47
- """
48
- This reference list holds a list of all used references. During each self.append(Reference()), a new reference is
49
- appended to the list.
50
- At the end of the document, a numbered list of references can be generated with self.make_make_literature_list().
51
- """
52
-
53
- def __init__(self, paragraph: Paragraph):
54
- self.paragraph = paragraph
55
- self._references = []
56
-
57
- def append(self, ref: Union[list['Reference'], tuple['Reference'], 'Reference']) -> None:
58
- """Adds a superscript list of reference numbers in brackets to the document."""
59
- if isinstance(ref, list | tuple):
60
- if not ref:
61
- return None
62
- self._append_list(ref)
63
- else:
64
- if ref not in self._references:
65
- self._references.append(ref)
66
- self.paragraph.add_run(f'[{self._references.index(ref) + 1}]').font.superscript = True
67
- # better not here:
68
- # self.paragraph.add_run(' ')
69
-
70
- def _append_list(self, reflist: list) -> None:
71
- reflst_long = []
72
- self.paragraph.add_run('[').font.superscript = True
73
- reflist = [x for x in reflist if x]
74
- for n, ref in enumerate(reflist):
75
- if ref not in self._references:
76
- self._references.append(ref)
77
- refnum = self._references.index(ref) + 1
78
- reflst_long.append(refnum)
79
- reftxt = self.get_sequence(reflst_long)
80
- self.paragraph.add_run(reftxt).font.superscript = True
81
- self.paragraph.add_run(']').font.superscript = True
82
-
83
- @staticmethod
84
- def get_sequence(stringlist: list[int]) -> str:
85
- """
86
- Converts a list of numbers into a string of numbers where recurring sequences
87
- are described with a range.
88
-
89
- >>> ReferenceList.get_sequence([1, 3, 4, 5, 6, 8, 11])
90
- '1,3-6,8,11'
91
- """
92
- folg = []
93
- start = 0
94
- for n, val in enumerate(stringlist):
95
- val = int(val)
96
- nextval = 0
97
- with suppress(IndexError):
98
- nextval = int(stringlist[n + 1])
99
- nextnext = 0
100
- with suppress(IndexError):
101
- nextnext = int(stringlist[n + 2])
102
- # a sequence starts:
103
- if nextnext == val + 2 and not start:
104
- start = val
105
- # in a sequence and next value is not +1 -> squence ends with val:
106
- if start and nextval != val + 1:
107
- folg.append(f'{start}-{val}')
108
- start = 0
109
- continue
110
- # everything outside a sequence:
111
- if not start:
112
- folg.append(val)
113
- return ','.join([str(x) for x in folg])
114
-
115
- def make_literature_list(self, document):
116
- if len(self._references) < 10:
117
- template = 'references_ni'
118
- else:
119
- template = 'references'
120
- for num, ref in enumerate(self._references, 1):
121
- paragraph_reflist = document.add_paragraph('', template)
122
- paragraph_reflist.add_run(f'[{num!s}] \t')
123
- ref.add_reference(paragraph_reflist)
124
-
125
- def __repr__(self):
126
- return '\n'.join([f'[{num}] {x}' for num, x in enumerate(self._references)])
127
-
128
-
129
42
  class Reference:
130
43
  def __init__(self):
131
44
  self.authors = ''
@@ -137,31 +50,6 @@ class Reference:
137
50
  self.program = ''
138
51
  self.count = 0
139
52
 
140
- def add_reference(self, p: Paragraph) -> None:
141
- if self.authors:
142
- p.add_run(self.authors)
143
- p.add_run(', ')
144
- if self.journal:
145
- p.add_run(self.journal).italic = True
146
- if not self.journal.endswith('.'):
147
- p.add_run(', ')
148
- else:
149
- p.add_run(' ')
150
- if self.year:
151
- p.add_run(self.year).bold = True
152
- p.add_run(', ')
153
- if self.volume:
154
- p.add_run(self.volume).italic = True
155
- p.add_run(', ')
156
- if self.pages:
157
- p.add_run(self.pages)
158
- if self.doi:
159
- p.add_run(', ')
160
- if self.doi:
161
- p.add_run(self.doi)
162
- if any([self.journal, self.pages, self.year, self.volume, self.doi]):
163
- p.add_run('.')
164
-
165
53
  @property
166
54
  def richtext(self) -> RichText:
167
55
  r = RichText('')
@@ -589,8 +477,3 @@ class CrysalisProReference(Reference):
589
477
  self.journal = version
590
478
  self.year = year
591
479
  self.pages = pages
592
-
593
-
594
- if __name__ == '__main__':
595
- r = ReferenceList.get_sequence([1, 2, 3, 6])
596
- print(r)
@@ -1,18 +1,14 @@
1
1
  import os
2
2
 
3
3
  import gemmi
4
- from docx import Document
5
4
  from docx.oxml.xmlchemy import BaseOxmlElement
6
- from docx.text.paragraph import Paragraph
7
- from docx.text.run import Run
8
5
  from lxml import etree
9
6
  from lxml.etree import XSLTAccessControl
10
7
 
11
8
  from finalcif.app_path import application_path
12
9
  from finalcif.cif.cif_file_io import CifContainer
13
10
  from finalcif.cif.text import retranslate_delimiter, string_to_utf8
14
- from finalcif.report import references
15
- from finalcif.tools.misc import (protected_space, angstrom, remove_line_endings)
11
+ from finalcif.tools.misc import angstrom, remove_line_endings
16
12
 
17
13
 
18
14
  def math_to_word(eq: str) -> BaseOxmlElement:
@@ -57,41 +53,6 @@ def _get_cooling_device(cif: CifContainer) -> str:
57
53
  return ''
58
54
 
59
55
 
60
- class FormatMixin:
61
-
62
- def bold(self, run: Run):
63
- r = run.bold = True
64
- return r
65
-
66
-
67
- class SpaceChar:
68
- def __init__(self, paragraph: Paragraph):
69
- self.p = paragraph
70
-
71
- def regular(self) -> None:
72
- self.p.add_run(' ')
73
-
74
- def protected(self):
75
- self.p.add_run(protected_space)
76
-
77
-
78
- class RefinementDetails:
79
- def __init__(self, cif: CifContainer, document: Document):
80
- ph = document.add_paragraph(style='Heading 2')
81
- ph.add_run(text=fr"Refinement details for {cif.block.name}")
82
- p = document.add_paragraph()
83
- try:
84
- p.style = document.styles['fliesstext']
85
- except KeyError:
86
- print('DBG> Text style not found')
87
- text = ' '.join(cif['_refine_special_details'].splitlines(keepends=False))
88
- if cif['_olex2_refine_details']:
89
- text += ' '.join(cif['_olex2_refine_details'].splitlines(keepends=False))
90
- # Replacing semicolon, because it can damage the CIF:
91
- text = text.replace(';', '.')
92
- p.add_run(string_to_utf8(text).strip())
93
-
94
-
95
56
  def get_inf_article(next_word: str) -> str:
96
57
  if not next_word:
97
58
  return 'a'
@@ -133,13 +94,3 @@ def format_radiation(radiation_type: str) -> list:
133
94
  return radtype
134
95
  else:
135
96
  return radtype
136
-
137
-
138
- def make_report_text(cif, document: Document) -> references.ReferenceList:
139
- paragr = document.add_paragraph()
140
- try:
141
- paragr.style = document.styles['fliesstext']
142
- except KeyError:
143
- print('DBG> Text style not found')
144
- ref = references.ReferenceList(paragr)
145
- return ref
@@ -13,7 +13,7 @@ from collections.abc import Callable
13
13
  from contextlib import suppress
14
14
  from math import sin, radians
15
15
  from pathlib import Path
16
- from typing import Any
16
+ from typing import Any, Literal
17
17
 
18
18
  from qtpy.QtWidgets import QApplication
19
19
 
@@ -725,7 +725,7 @@ class Formatter(abc.ABC):
725
725
  if 'OLEX' in refined.upper():
726
726
  self.literature['refinement'] = ref.Olex2Reference()
727
727
  if ('NOSPHERA2' in refined.upper() or 'NOSPHERA2' in cif['_refine_special_details'].upper() or
728
- 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()):
728
+ 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()):
729
729
  self.literature['refinement'] = ref.Nosphera2Reference()
730
730
  return string_to_utf8(refined.split('(')[0]).strip()
731
731
 
@@ -779,11 +779,28 @@ class Formatter(abc.ABC):
779
779
  U23=u23.replace('-', minus_sign))
780
780
  for label, u11, u22, u33, u23, u13, u12 in cif.displacement_parameters())
781
781
 
782
- def get_completeness(self, cif: CifContainer) -> str:
782
+ def get_completeness(self, cif: CifContainer, theta_limit: Literal['full', 'max'] = 'full') -> str:
783
+ """
784
+ Completeness in % for the given theta limit ('full' or 'max').
785
+ The standard CIF values used to report the completeness are
786
+ _diffrn_measured_fraction_theta_max and _diffrn_measured_fraction_theta_full.
787
+
788
+ How they differ
789
+ _diffrn_measured_fraction_theta_full:
790
+ Reports the fraction of independent reflections measured up to a standardized
791
+ "full" resolution limit (e.g., theta = 25.2° for Mo-K⍺
792
+ or 67.5° for Cu-K⍺). This is the metric most rigorously checked for publication.
793
+
794
+ _diffrn_measured_fraction_theta_max:
795
+ Reports the fraction of independent reflections measured up to the absolute highest
796
+ angle collected in that specific experiment, even if it exceeds or falls short of
797
+ the standard resolution limit.
798
+ """
799
+ fraction = cif[f'_diffrn_measured_fraction_theta_{theta_limit}']
783
800
  try:
784
- completeness = f"{float(cif['_diffrn_measured_fraction_theta_max']) * 100:.1f}"
801
+ completeness = f"{float(fraction) * 100:.1f}"
785
802
  except ValueError:
786
- completeness = cif['_diffrn_measured_fraction_theta_max']
803
+ completeness = fraction
787
804
  return completeness
788
805
 
789
806
  def format_experiment_table(self, cif: CifContainer):
@@ -1122,6 +1139,7 @@ class TemplatedReport:
1122
1139
  self.options = options
1123
1140
  self.text_formatter = text_factory(options, cif)[self.format]
1124
1141
  self.references = {}
1142
+ self.is_multi_cif_template = False
1125
1143
 
1126
1144
  def count_reference(self, ref):
1127
1145
  if self.references:
@@ -1167,6 +1185,8 @@ class TemplatedReport:
1167
1185
  # plain text full reference:
1168
1186
  jinja_env.filters['ref_txt'] = self.reference_text
1169
1187
  try:
1188
+ variables = tpl_doc.get_undeclared_template_variables(jinja_env=jinja_env)
1189
+ self.is_multi_cif_template = bool({'blocklist', 'block'} & variables)
1170
1190
  tpl_doc.render(context, jinja_env=jinja_env, autoescape=True)
1171
1191
  tpl_doc.save(output_filename)
1172
1192
  return True
@@ -1180,7 +1200,6 @@ class TemplatedReport:
1180
1200
  show_general_warning(parent=None, window_title='Warning', warn_text='Document generation failed',
1181
1201
  info_text=str(e))
1182
1202
  print(e)
1183
- raise
1184
1203
  return False
1185
1204
 
1186
1205
  def make_templated_html_report(self,
@@ -1298,10 +1317,10 @@ class TemplatedReport:
1298
1317
  'space_group' : self.text_formatter.space_group_formatted(cif, tpl_doc),
1299
1318
  'structure_figure' : self.text_formatter.make_picture(options,
1300
1319
  options.structure_figure, tpl_doc) if (
1301
- options and options.report_text) else '',
1320
+ options and options.report_text) else '',
1302
1321
  'crystal_video' : self.text_formatter.make_picture(options,
1303
1322
  options.video_image, tpl_doc) if (
1304
- options and options.report_text) else '',
1323
+ options and options.report_text) else '',
1305
1324
  '3d_structure' : self.text_formatter.make_3d(cif, options) if options else '',
1306
1325
  'crystallization_method' : self.text_formatter.get_crystallization_method(cif),
1307
1326
  'sum_formula' : self.text_formatter.format_sum_formula(
@@ -1332,8 +1351,11 @@ class TemplatedReport:
1332
1351
  'indepentent_refl' : this_or_quest(cif['_reflns_number_total']),
1333
1352
  'r_int' : this_or_quest(cif['_diffrn_reflns_av_R_equivalents']),
1334
1353
  'r_sigma' : this_or_quest(cif['_diffrn_reflns_av_unetI/netI']),
1335
- 'completeness' : self.text_formatter.get_completeness(cif),
1354
+ 'completeness' : self.text_formatter.get_completeness(cif, 'full'),
1355
+ 'completeness_theta_full': self.text_formatter.get_completeness(cif, 'full'),
1356
+ 'completeness_theta_max' : self.text_formatter.get_completeness(cif, 'max'),
1336
1357
  'theta_full' : cif['_diffrn_reflns_theta_full'],
1358
+ 'theta_max' : cif['_diffrn_reflns_theta_max'],
1337
1359
  'resolution_angstrom' : self.text_formatter.get_angstrom_resolution(cif),
1338
1360
  'redundancy' : self.text_formatter.get_redundancy(cif),
1339
1361
  'data' : this_or_quest(cif['_refine_ls_number_reflns']),
@@ -1475,15 +1497,15 @@ if __name__ == '__main__':
1475
1497
 
1476
1498
  import subprocess
1477
1499
 
1478
- report_type = ReportFormat.LATEX
1500
+ report_type = ReportFormat.HTML
1479
1501
 
1480
1502
  data = Path('tests')
1481
1503
  testcif = Path(data / 'examples/1979688.cif').absolute()
1482
- testcif = Path(data / r'..\test-data\4060314.cif').absolute()
1504
+ # testcif = Path(data / r'..\test-data\4060314.cif').absolute()
1483
1505
 
1484
1506
  # testcif = Path(r'test-data/p31c.cif').absolute()
1485
- #testcif = Path(r'test-data/p31c.cif').absolute()
1486
- #testcif = Path(r"D:\Downloads\9008564.cif").absolute()
1507
+ # testcif = Path(r'test-data/p31c.cif').absolute()
1508
+ # testcif = Path(r"D:\Downloads\9008564.cif").absolute()
1487
1509
  cif = CifContainer(testcif)
1488
1510
 
1489
1511
  pic = pathlib.Path("screenshots/finalcif_checkcif.png")
@@ -250,11 +250,16 @@
250
250
  </td>
251
251
  </tr>
252
252
  <tr>
253
- <td>Completeness{% if cif._diffrn_reflns_theta_max %} to
254
- θ = {{ cif._diffrn_reflns_theta_max|float_num(2) }}°
255
- {% endif %}
253
+ <td>Completeness{% if theta_full %} to
254
+ θ = {{ theta_full|float_num(2) }}°{% endif %}
255
+ </td>
256
+ <td>{{ completeness_theta_full }}%</td>
257
+ </tr>
258
+ <tr>
259
+ <td>Completeness{% if theta_max %} to
260
+ θ = {{ theta_max|float_num(2) }}°{% endif %}
256
261
  </td>
257
- <td>{{ completeness }}%</td>
262
+ <td>{{ completeness_theta_max }}%</td>
258
263
  </tr>
259
264
  <tr>
260
265
  <td>Data / Restraints / Parameters</td>
@@ -289,11 +289,16 @@
289
289
  </td>
290
290
  </tr>
291
291
  <tr>
292
- <td>Completeness{% if cif._diffrn_reflns_theta_max %} to
293
- θ = {{ cif._diffrn_reflns_theta_max|float_num(2) }}°
294
- {% endif %}
292
+ <td>Completeness{% if theta_full %} to
293
+ θ = {{ theta_full|float_num(2) }}°{% endif %}
294
+ </td>
295
+ <td>{{ completeness_theta_full }}%</td>
296
+ </tr>
297
+ <tr>
298
+ <td>Completeness{% if theta_max %} to
299
+ θ = {{ theta_max|float_num(2) }}°{% endif %}
295
300
  </td>
296
- <td>{{ completeness }}%</td>
301
+ <td>{{ completeness_theta_max }}%</td>
297
302
  </tr>
298
303
  <tr>
299
304
  <td>Data / Restraints / Parameters</td>
@@ -0,0 +1,76 @@
1
+ #!/bin/sh
2
+ #
3
+ # pre-push hook: reject pushing version tags (e.g. "version-177") whose
4
+ # numeric version is newer than the VERSION defined in finalcif/__init__.py
5
+ # at the commit the tag points to.
6
+ #
7
+ # Git has no client-side hook that fires on local `git tag` creation, so
8
+ # this is enforced at push time instead, which is the last point before
9
+ # the tag becomes visible to others.
10
+ #
11
+ # Install (once per clone):
12
+ # git config core.hooksPath githooks
13
+ #
14
+ # Tag names are expected to look like "version-NNN" (matching VERSION = NN).
15
+ # Tags that don't match this pattern are ignored by this check.
16
+
17
+ VERSION_FILE="finalcif/__init__.py"
18
+ z40="0000000000000000000000000000000000000000"
19
+
20
+ reject=0
21
+
22
+ while read -r local_ref local_sha remote_ref remote_sha; do
23
+ case "$local_ref" in
24
+ refs/tags/*) ;;
25
+ *) continue ;;
26
+ esac
27
+
28
+ # Tag deletion (local_sha is all zeros) - nothing to check.
29
+ if [ "$local_sha" = "$z40" ]; then
30
+ continue
31
+ fi
32
+
33
+ tag_name=${local_ref#refs/tags/}
34
+
35
+ # Only enforce the check for tags named like "version-<number>".
36
+ case "$tag_name" in
37
+ version-[0-9]*)
38
+ tag_version=${tag_name#version-}
39
+ case "$tag_version" in
40
+ *[!0-9]*) continue ;; # not purely numeric, skip
41
+ esac
42
+ ;;
43
+ *) continue ;;
44
+ esac
45
+
46
+ # Resolve to the commit the tag points at (handles annotated tags).
47
+ commit_sha=$(git rev-list -n 1 "$local_sha" 2>/dev/null)
48
+ if [ -z "$commit_sha" ]; then
49
+ commit_sha="$local_sha"
50
+ fi
51
+
52
+ file_content=$(git show "$commit_sha:$VERSION_FILE" 2>/dev/null)
53
+ if [ -z "$file_content" ]; then
54
+ echo "pre-push: warning: could not read $VERSION_FILE at $commit_sha for tag $tag_name; skipping check" >&2
55
+ continue
56
+ fi
57
+
58
+ file_version=$(printf '%s\n' "$file_content" | sed -n "s/^VERSION *= *\\([0-9]\\+\\).*/\\1/p" | head -n 1)
59
+ if [ -z "$file_version" ]; then
60
+ echo "pre-push: warning: could not parse VERSION from $VERSION_FILE at $commit_sha; skipping check" >&2
61
+ continue
62
+ fi
63
+
64
+ if [ "$tag_version" -gt "$file_version" ]; then
65
+ echo "pre-push: rejected: tag '$tag_name' (version $tag_version) is newer than" >&2
66
+ echo " VERSION = $file_version in $VERSION_FILE at commit $commit_sha." >&2
67
+ echo " Update $VERSION_FILE to $tag_version before tagging/pushing." >&2
68
+ reject=1
69
+ fi
70
+ done
71
+
72
+ if [ "$reject" -eq 1 ]; then
73
+ exit 1
74
+ fi
75
+
76
+ exit 0