easydiffraction 0.5.4__tar.gz → 0.5.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 (227) hide show
  1. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.github/workflows/build-docs.yml +31 -23
  2. easydiffraction-0.5.6/.github/workflows/draft-release-notes.yml +27 -0
  3. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.github/workflows/test-tutorials.yaml +1 -1
  4. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/DEVELOPMENT.md +18 -9
  5. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/PKG-INFO +1 -1
  6. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/introduction/index.md +1 -1
  7. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/plotting/plotters/plotter_plotly.py +8 -9
  8. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/utils/utils.py +11 -0
  9. easydiffraction-0.5.6/tools/prepare_notebooks.py +255 -0
  10. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.ipynb +911 -468
  11. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.py +261 -179
  12. easydiffraction-0.5.4/.github/workflows/update-release-draft.yml +0 -22
  13. easydiffraction-0.5.4/tools/nb_uncomment_pip.py +0 -108
  14. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.github/release-drafter.yml +0 -0
  15. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.github/workflows/delete-old-runs.yml +0 -0
  16. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.github/workflows/publish-pypi.yml +0 -0
  17. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.github/workflows/scan-security.yml +0 -0
  18. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.github/workflows/test-code.yaml +0 -0
  19. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.github/workflows/test-tutorials-colab.yaml +0 -0
  20. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.github/workflows/verify-pr-labels.yml +0 -0
  21. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.gitignore +0 -0
  22. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/.prettierignore +0 -0
  23. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/CONTRIBUTING.md +0 -0
  24. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/LICENSE +0 -0
  25. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/README.md +0 -0
  26. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/deps/pycrysfml-0.1.6-py312-none-macosx_14_0_arm64.whl +0 -0
  27. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/deps/pycrysfml-0.1.6-py312-none-win_amd64.whl +0 -0
  28. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/analysis.md +0 -0
  29. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/core.md +0 -0
  30. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/crystallography.md +0 -0
  31. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/experiments.md +0 -0
  32. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/index.md +0 -0
  33. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/plotting.md +0 -0
  34. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/project.md +0 -0
  35. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/sample_models.md +0 -0
  36. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/summary.md +0 -0
  37. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/api-reference/utils.md +0 -0
  38. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/assets/images/user-guide/data-acquisition_2d-raw-data.jpg +0 -0
  39. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/assets/images/user-guide/data-acquisition_instrument.png +0 -0
  40. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/assets/images/user-guide/data-analysis_model.png +0 -0
  41. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/assets/images/user-guide/data-analysis_refinement.png +0 -0
  42. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/assets/images/user-guide/data-reduction_1d-pattern.png +0 -0
  43. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/index.md +0 -0
  44. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/installation-and-setup/index.md +0 -0
  45. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/mkdocs.yml +0 -0
  46. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/tutorials/index.md +0 -0
  47. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/analysis-workflow/analysis.md +0 -0
  48. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/analysis-workflow/experiment.md +0 -0
  49. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/analysis-workflow/index.md +0 -0
  50. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/analysis-workflow/model.md +0 -0
  51. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/analysis-workflow/project.md +0 -0
  52. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/analysis-workflow/summary.md +0 -0
  53. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/concept.md +0 -0
  54. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/data-format.md +0 -0
  55. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/first-steps.md +0 -0
  56. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/glossary.md +0 -0
  57. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/index.md +0 -0
  58. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/_diffrn_radiation.md +0 -0
  59. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/_diffrn_radiation_wavelength.md +0 -0
  60. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/_exptl_crystal.md +0 -0
  61. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/_extinction.md +0 -0
  62. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/_pd_calib.md +0 -0
  63. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/atom_site.md +0 -0
  64. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/background.md +0 -0
  65. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/cell.md +0 -0
  66. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/expt_type.md +0 -0
  67. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/instrument.md +0 -0
  68. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/linked_phases.md +0 -0
  69. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/pd_meas.md +0 -0
  70. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/peak.md +0 -0
  71. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters/space_group.md +0 -0
  72. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/docs/user-guide/parameters.md +0 -0
  73. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/prettierrc.toml +0 -0
  74. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/pyproject.toml +0 -0
  75. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/__init__.py +0 -0
  76. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/__init__.py +0 -0
  77. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/analysis.py +0 -0
  78. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/calculation.py +0 -0
  79. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/calculators/__init__.py +0 -0
  80. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/calculators/calculator_base.py +0 -0
  81. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/calculators/calculator_crysfml.py +0 -0
  82. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/calculators/calculator_cryspy.py +0 -0
  83. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/calculators/calculator_factory.py +0 -0
  84. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/calculators/calculator_pdffit.py +0 -0
  85. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/collections/__init__.py +0 -0
  86. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/collections/aliases.py +0 -0
  87. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/collections/constraints.py +0 -0
  88. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/collections/joint_fit_experiments.py +0 -0
  89. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/minimization.py +0 -0
  90. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/minimizers/__init__.py +0 -0
  91. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/minimizers/fitting_progress_tracker.py +0 -0
  92. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/minimizers/minimizer_base.py +0 -0
  93. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/minimizers/minimizer_dfols.py +0 -0
  94. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/minimizers/minimizer_factory.py +0 -0
  95. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/minimizers/minimizer_lmfit.py +0 -0
  96. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/analysis/reliability_factors.py +0 -0
  97. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/core/__init__.py +0 -0
  98. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/core/constants.py +0 -0
  99. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/core/objects.py +0 -0
  100. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/core/singletons.py +0 -0
  101. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/crystallography/__init__.py +0 -0
  102. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/crystallography/crystallography.py +0 -0
  103. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/crystallography/space_group_lookup_table.py +0 -0
  104. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/__init__.py +0 -0
  105. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/collections/__init__.py +0 -0
  106. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/collections/background.py +0 -0
  107. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/collections/datastore.py +0 -0
  108. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/collections/excluded_regions.py +0 -0
  109. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/collections/linked_phases.py +0 -0
  110. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/components/__init__.py +0 -0
  111. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/components/experiment_type.py +0 -0
  112. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/components/instrument.py +0 -0
  113. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/components/peak.py +0 -0
  114. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/experiment.py +0 -0
  115. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/experiments/experiments.py +0 -0
  116. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/plotting/__init__.py +0 -0
  117. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/plotting/plotters/__init__.py +0 -0
  118. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/plotting/plotters/plotter_ascii.py +0 -0
  119. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/plotting/plotters/plotter_base.py +0 -0
  120. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/plotting/plotting.py +0 -0
  121. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/project.py +0 -0
  122. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/sample_models/__init__.py +0 -0
  123. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/sample_models/collections/__init__.py +0 -0
  124. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/sample_models/collections/atom_sites.py +0 -0
  125. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/sample_models/components/__init__.py +0 -0
  126. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/sample_models/components/cell.py +0 -0
  127. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/sample_models/components/space_group.py +0 -0
  128. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/sample_models/sample_model.py +0 -0
  129. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/sample_models/sample_models.py +0 -0
  130. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/summary.py +0 -0
  131. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/utils/__init__.py +0 -0
  132. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/utils/decorators.py +0 -0
  133. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/src/easydiffraction/utils/formatting.py +0 -0
  134. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/functional_tests/fitting/test_pair-distribution-function.py +0 -0
  135. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/functional_tests/fitting/test_powder-diffraction_constant-wavelength.py +0 -0
  136. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/functional_tests/fitting/test_powder-diffraction_joint-fit.py +0 -0
  137. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/functional_tests/fitting/test_powder-diffraction_multiphase.py +0 -0
  138. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/functional_tests/fitting/test_powder-diffraction_time-of-flight.py +0 -0
  139. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/calculators/test_calculator_base.py +0 -0
  140. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/calculators/test_calculator_cryspy.py +0 -0
  141. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/calculators/test_calculator_factory.py +0 -0
  142. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/collections/test_joint_fit_experiment.py +0 -0
  143. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/minimizers/test_fitting_progress_tracker.py +0 -0
  144. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/minimizers/test_minimizer_base.py +0 -0
  145. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/minimizers/test_minimizer_dfols.py +0 -0
  146. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/minimizers/test_minimizer_factory.py +0 -0
  147. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/minimizers/test_minimizer_lmfit.py +0 -0
  148. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/test_analysis.py +0 -0
  149. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/test_minimization.py +0 -0
  150. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/analysis/test_reliability_factors.py +0 -0
  151. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/core/test_objects.py +0 -0
  152. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/core/test_singletons.py +0 -0
  153. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/experiments/collections/test_background.py +0 -0
  154. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/experiments/collections/test_datastore.py +0 -0
  155. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/experiments/collections/test_linked_phases.py +0 -0
  156. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/experiments/components/test_experiment_type.py +0 -0
  157. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/experiments/components/test_instrument.py +0 -0
  158. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/experiments/components/test_peak.py +0 -0
  159. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/experiments/test_experiment.py +0 -0
  160. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/experiments/test_experiments.py +0 -0
  161. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/sample_models/collections/test_atom_sites.py +0 -0
  162. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/sample_models/components/test_cell.py +0 -0
  163. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/sample_models/components/test_space_group.py +0 -0
  164. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/sample_models/test_sample_models.py +0 -0
  165. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/test_project.py +0 -0
  166. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tests/unit_tests/test_symmetry_lookup_table.py +0 -0
  167. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tools/build_docs.sh +0 -0
  168. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tools/cleanup_docs.sh +0 -0
  169. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tools/create_mkdocs-yml.py +0 -0
  170. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tools/install_deps.sh +0 -0
  171. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tools/prepare_docs.sh +0 -0
  172. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tools/run_notebooks.sh +0 -0
  173. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tools/update_spdx-headers.py +0 -0
  174. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.ipynb +0 -0
  175. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.py +0 -0
  176. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.ipynb +0 -0
  177. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py +0 -0
  178. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.ipynb +0 -0
  179. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.py +0 -0
  180. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.ipynb +0 -0
  181. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py +0 -0
  182. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.ipynb +0 -0
  183. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py +0 -0
  184. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.ipynb +0 -0
  185. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.py +0 -0
  186. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.ipynb +0 -0
  187. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.py +0 -0
  188. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/NOM_9999_Si_640g_PAC_50_ff_ftfrgr_up-to-50.gr +0 -0
  189. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/NaCl.gr +0 -0
  190. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/co2sio4_d20.xye +0 -0
  191. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/d1a_pbso4.dat +0 -0
  192. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/d1a_pbso4_first-half.dat +0 -0
  193. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/d1a_pbso4_second-half.dat +0 -0
  194. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/hrpt_hs.xye +0 -0
  195. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/hrpt_lbco.xye +0 -0
  196. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/lab_pbso4.dat +0 -0
  197. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/mcstas_lbco-si.xye +0 -0
  198. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/mcstas_lbco-si.xys +0 -0
  199. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/mcstas_lbco-si_up-to-108k.xys +0 -0
  200. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/ni-q27r100-neutron_from-2.gr +0 -0
  201. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/powder_reduced_Si_2large_bank.xye +0 -0
  202. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/reduced_LBCO.xye +0 -0
  203. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/reduced_Si.xye +0 -0
  204. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/sepd_si.xye +0 -0
  205. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/wish_ncaf.xye +0 -0
  206. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/wish_ncaf_2_9.xye +0 -0
  207. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/wish_ncaf_4_7.xye +0 -0
  208. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/wish_ncaf_4_7.xys +0 -0
  209. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/wish_ncaf_5_6.xye +0 -0
  210. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/wish_ncaf_5_6.xys +0 -0
  211. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/data/wish_ybcfo_5_6.xye +0 -0
  212. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/pdf_pd-neut-cwl_Ni.ipynb +0 -0
  213. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/pdf_pd-neut-cwl_Ni.py +0 -0
  214. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/pdf_pd-neut-tof_Si-NOMAD.ipynb +0 -0
  215. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/pdf_pd-neut-tof_Si-NOMAD.py +0 -0
  216. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/pdf_pd-xray_NaCl.ipynb +0 -0
  217. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/pdf_pd-xray_NaCl.py +0 -0
  218. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.ipynb +0 -0
  219. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py +0 -0
  220. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials-drafts/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py +0 -0
  221. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials-drafts/data/DREAM_mantle_bc240_nist_cif.xye +0 -0
  222. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials-drafts/data/DREAM_mantle_bc240_nist_cif_2.xye +0 -0
  223. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials-drafts/data/DREAM_mantle_bc240_nist_nc.xye +0 -0
  224. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials-drafts/data/DREAM_mantle_bc240_nist_nc_2.xye +0 -0
  225. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials-drafts/data/Si_mp-149_symmetrized_mcstas.cif +0 -0
  226. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials-drafts/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye +0 -0
  227. {easydiffraction-0.5.4 → easydiffraction-0.5.6}/tutorials-drafts/test_single-fit_pd-neut-tof_Si-DREAM_nc.py +0 -0
@@ -5,6 +5,9 @@ on:
5
5
  push:
6
6
  # Selected branches
7
7
  branches: [develop, master, docs, patch]
8
+ # Runs on creating a new tag starting with 'v', e.g. 'v1.0.3'
9
+ tags:
10
+ - 'v*'
8
11
  # Allows you to run this workflow manually from the Actions tab
9
12
  workflow_dispatch:
10
13
 
@@ -33,18 +36,22 @@ jobs:
33
36
  runs-on: ${{ matrix.os }}
34
37
 
35
38
  steps:
36
- # Without this step, GITHUB_REPOSITORY is not accessible from mkdocs.yml
37
- - name: Get GitHub repository
38
- run: echo "GITHUB_REPOSITORY=$GITHUB_REPOSITORY" >> $GITHUB_ENV
39
+ - name: Check-out repository
40
+ uses: actions/checkout@v4
41
+ with:
42
+ fetch-depth: 0 # full history + tags. needed to get the latest release version
39
43
 
40
44
  # Save the latest release version of easyscience/diffraction-lib to RELEASE_VERSION
41
45
  # RELEASE_VERSION is used in the mkdocs.yml file to set release_version.
42
46
  # The release_version is then needed to display the latest release version in the index.md file
43
- - name: Get the latest release version of easydiffraction library
47
+ - name: Set RELEASE_VERSION env variable (latest easydiffraction release)
44
48
  run: |
45
- git clone --depth 1 https://github.com/easyscience/${{ github.event.repository.name }} .
46
- git fetch --tags
47
- echo "RELEASE_VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
49
+ git fetch --tags --force
50
+ echo "RELEASE_VERSION=$(git describe --tags --abbrev=0)" >> "$GITHUB_ENV"
51
+
52
+ # Without this step, GITHUB_REPOSITORY is not accessible from mkdocs.yml
53
+ - name: Set GITHUB_REPOSITORY env variable
54
+ run: echo "GITHUB_REPOSITORY=$GITHUB_REPOSITORY" >> "$GITHUB_ENV"
48
55
 
49
56
  # Activate dark mode to create documentation with Plotly charts in dark mode
50
57
  # Need a better solution to automatically switch the chart colour theme based on the mkdocs material switcher
@@ -57,9 +64,6 @@ jobs:
57
64
  # dark-mode on
58
65
  # dark-mode status
59
66
 
60
- - name: Check-out repository
61
- uses: actions/checkout@v4
62
-
63
67
  - name: Set up Python ${{ matrix.python-version }}
64
68
  uses: actions/setup-python@v5
65
69
  with:
@@ -113,7 +117,7 @@ jobs:
113
117
  cp -R ${{ env.NOTEBOOKS_DIR }}/data docs/${{ env.NOTEBOOKS_DIR }}/
114
118
  jupytext ${{ env.NOTEBOOKS_DIR }}/*.py --from py:percent --to ipynb
115
119
  nbstripout ${{ env.NOTEBOOKS_DIR }}/*.ipynb
116
- python tools/nb_uncomment_pip.py ${{ env.NOTEBOOKS_DIR }}/
120
+ python tools/prepare_notebooks.py ${{ env.NOTEBOOKS_DIR }}/
117
121
  mv ${{ env.NOTEBOOKS_DIR }}/*.ipynb docs/${{ env.NOTEBOOKS_DIR }}/
118
122
 
119
123
  # The following step is needed to avoid the following message during the build:
@@ -159,10 +163,10 @@ jobs:
159
163
  path: site/
160
164
 
161
165
  # Upload the static files from the site/ directory to be used in the next job
162
- # This extra step is needed to allow the download of the artifact in the next job
163
- # for pushing its content to the branch named 'gh_pages'
164
- - name: Upload built site as artifact for gh_pages (master branch)
165
- if: ${{ env.CI_BRANCH == 'master' }}
166
+ # This artifact is only uploaded on tagged releases (tags starting with 'v', e.g., v1.0.3)
167
+ # and is used to push content to gh_pages for custom domain deployment.
168
+ - name: Upload built site as artifact for gh_pages (tagged release)
169
+ if: startsWith(github.ref, 'refs/tags/v')
166
170
  uses: actions/upload-artifact@v4
167
171
  with:
168
172
  name: artifact # name of the artifact (without the extension zip)
@@ -205,26 +209,30 @@ jobs:
205
209
  (all branches)
206
210
  uses: actions/deploy-pages@v4
207
211
 
208
- # Download built site as artifact from a previous job for gh_pages (master branch)
209
- - name: Download built site from previous job (master branch)
210
- if: ${{ env.CI_BRANCH == 'master' }}
212
+ # Download built site as artifact from a previous job for gh_pages (tagged release)
213
+ # This artifact is only downloaded on tagged releases (tags starting with 'v', e.g., v1.0.3)
214
+ # and is used to push content to gh_pages for custom domain deployment.
215
+ - name: Download built site from previous job (tagged release)
216
+ if: startsWith(github.ref, 'refs/tags/v')
211
217
  uses: actions/download-artifact@v4
212
218
  with: # name or path are taken from the upload step of the previous job
213
219
  name: artifact
214
220
  path: site/ # directory to extract downloaded zipped artifacts
215
221
 
216
222
  # Push the site files created in the previous job to the gh_pages branch
223
+ # This push happens only for tagged releases (tags starting with 'v'),
224
+ # which triggers deployment to the custom domain via webhook.
225
+ #
217
226
  # To be able to push to the gh_pages branch, the personal GitHub API access
218
227
  # token GH_API_PERSONAL_ACCSESS_TOKEN must be set for this repository via
219
228
  # https://github.com/easyscience/diffraction-lib/settings/secrets/actions
220
- # This branch is used to deploy the site to the custom domain.
221
- # Deploying is done with a webhook:
229
+ # Then the gh_pages branch is used to deploy the site to the custom domain.
230
+ # Deploying is done with a webhook added via:
222
231
  # https://github.com/easyscience/diffraction-lib/settings/hooks
223
- # This is done for the gh_pages branch when the site is tested with a step above
224
232
  - name:
225
233
  Deploy to gh_pages branch to trigger deployment to custom domain
226
- (master branch)
227
- if: ${{ env.CI_BRANCH == 'master' }}
234
+ (tagged release)
235
+ if: startsWith(github.ref, 'refs/tags/v')
228
236
  uses: s0/git-publish-subdir-action@develop
229
237
  env:
230
238
  GITHUB_TOKEN: ${{ secrets.GH_API_PERSONAL_ACCSESS_TOKEN }}
@@ -0,0 +1,27 @@
1
+ # Drafts your next Release notes as pull requests are merged into
2
+ # default branch
3
+
4
+ name: Update release draft
5
+
6
+ on:
7
+ # Runs on pushes targeting the default branch
8
+ push:
9
+ branches:
10
+ - main
11
+ - master
12
+
13
+ jobs:
14
+ draft-release-notes:
15
+ permissions:
16
+ # write permission is required to create a github release
17
+ contents: write
18
+
19
+ runs-on: ubuntu-latest
20
+
21
+ steps:
22
+ - name: Drafts the next Release notes
23
+ uses: release-drafter/release-drafter@v6
24
+ with:
25
+ disable-autolabeler: true
26
+ env:
27
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -151,7 +151,7 @@ jobs:
151
151
  run: |
152
152
  jupytext ${{ env.NOTEBOOKS_DIR }}/*.py --from py:percent --to ipynb
153
153
  nbstripout ${{ env.NOTEBOOKS_DIR }}/*.ipynb
154
- python tools/nb_uncomment_pip.py ${{ env.NOTEBOOKS_DIR }}/
154
+ python tools/prepare_notebooks.py ${{ env.NOTEBOOKS_DIR }}/
155
155
 
156
156
  - name: Run tutorials as Jupyter Notebooks (using src/ as the source dir)
157
157
  shell: bash
@@ -106,22 +106,31 @@ This is an example of a workflow that describes the development process.
106
106
  ```
107
107
  - Add extra files to build documentation (from `../assets-docs/` and
108
108
  `../assets-branding/` directories)
109
+
109
110
  ```bash
110
111
  cp -R ../assets-docs/docs/assets/ docs/assets/
111
112
  cp -R ../assets-docs/includes/ includes/
112
113
  cp -R ../assets-docs/overrides/ overrides/
114
+
113
115
  mkdir -p docs/assets/images/
114
- cp ../assets-branding/EasyDiffraction/logos/ed-logo_dark.svg docs/assets/images/
115
- cp ../assets-branding/EasyDiffraction/logos/ed-logo_light.svg docs/assets/images/
116
- cp ../assets-branding/EasyDiffraction/logos/edl-logo_dark.svg docs/assets/images/logo_dark.svg
117
- cp ../assets-branding/EasyDiffraction/logos/edl-logo_light.svg docs/assets/images/logo_light.svg
118
- cp ../assets-branding/EasyDiffraction/icons/ed-icon_256x256.png docs/assets/images/favicon.png
116
+ cp ../assets-branding/easydiffraction/hero/dark.png docs/assets/images/hero_dark.png
117
+ cp ../assets-branding/easydiffraction/hero/light.png docs/assets/images/hero_light.png
118
+ cp ../assets-branding/easydiffraction/logos/dark.svg docs/assets/images/logo_dark.svg
119
+ cp ../assets-branding/easydiffraction/logos/light.svg docs/assets/images/logo_light.svg
120
+ cp ../assets-branding/easydiffraction/icons/color.png docs/assets/images/favicon.png
121
+
119
122
  mkdir -p overrides/.icons/
120
- cp ../assets-branding/EasyDiffraction/icons/ed-icon_bw.svg overrides/.icons/easydiffraction.svg
121
- cp ../assets-branding/EasyScienceOrg/icons/eso-icon_bw.svg overrides/.icons/easyscience.svg
122
- cp -R examples/ docs/examples/
123
- cat ../assets-docs/mkdocs.yml docs/mkdocs.yml > mkdocs.yml
123
+ cp ../assets-branding/easydiffraction/icons/bw.svg overrides/.icons/easydiffraction.svg
124
+ cp ../assets-branding/easyscience-org/icons/eso-icon_bw.svg overrides/.icons/easyscience.svg
125
+
126
+ jupytext tutorials/*.py --from py:percent --to ipynb
127
+ nbstripout tutorials/*.ipynb
128
+ cp tutorials/*.ipynb docs/tutorials/
129
+ cp -R tutorials/data docs/tutorials/
130
+
131
+ python tools/create_mkdocs-yml.py
124
132
  ```
133
+
125
134
  - Build documentation with MkDocs - static site generator
126
135
  ```bash
127
136
  export JUPYTER_PLATFORM_DIRS=1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easydiffraction
3
- Version: 0.5.4
3
+ Version: 0.5.6
4
4
  Summary: Diffraction data analysis
5
5
  Project-URL: homepage, https://easydiffraction.org
6
6
  Project-URL: documentation, https://docs.easydiffraction.org/lib
@@ -49,7 +49,7 @@ Zenodo, each with a version-specific Digital Object Identifier (DOI).
49
49
  Citation details in various styles (e.g., APA, MLA) and formats (e.g., BibTeX,
50
50
  JSON)
51
51
  are available on the
52
- [Zenodo archive page](https://doi.org/10.5281/zenodo.16806522).
52
+ [Zenodo archive page](https://doi.org/10.5281/zenodo.16806521).
53
53
 
54
54
  ## Contributing
55
55
 
@@ -12,7 +12,7 @@ except ImportError:
12
12
  display = None
13
13
  HTML = None
14
14
 
15
- from easydiffraction.utils.utils import is_pycharm
15
+ from easydiffraction.utils.utils import is_github_ci
16
16
 
17
17
  from .plotter_base import SERIES_CONFIG
18
18
  from .plotter_base import PlotterBase
@@ -106,14 +106,13 @@ class PlotlyPlotter(PlotterBase):
106
106
 
107
107
  # Show the figure
108
108
 
109
- # The standard `fig.show()` method method triggers the following
110
- # warning during the DMSC Summer School 2025 book build process:
111
- # WARNING: skipping unknown output mime type:
112
- # application/vnd.plotly.v1+json [mystnb.unknown_mime_type]
113
- # So, instead, we will use `pio.to_html()` to convert the figure
114
- # to HTML and keep `fig.show()` in PyCharm only.
115
-
116
- if is_pycharm() or display is None or HTML is None:
109
+ # In GitHub CI builds (e.g., during Jupyter Book generation), avoid
110
+ #
111
+ # calling `fig.show()`
112
+ # because it can emit `application/vnd.plotly.v1+json` outputs that some toolchains warn about.
113
+ # Instead, convert the figure to HTML and display it directly.
114
+ # Use a regular Figure and show it
115
+ if not is_github_ci() or display is None or HTML is None:
117
116
  fig.show(config=config)
118
117
  else:
119
118
  html_fig = pio.to_html(
@@ -101,6 +101,17 @@ def is_pycharm() -> bool:
101
101
  return os.environ.get('PYCHARM_HOSTED') == '1'
102
102
 
103
103
 
104
+ def is_github_ci() -> bool:
105
+ """
106
+ Determines if the current process is running in GitHub Actions CI.
107
+
108
+ Returns:
109
+ bool: True if the environment variable ``GITHUB_ACTIONS`` is set
110
+ (Always "true" on GitHub Actions), False otherwise.
111
+ """
112
+ return os.environ.get('GITHUB_ACTIONS') is not None
113
+
114
+
104
115
  def render_table(
105
116
  columns_data,
106
117
  columns_alignment,
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Uncomment `# !pip ...` and `# ed.download_from_repository ...` lines in Jupyter
4
+ notebooks so they become `!pip ...` and `ed.download_from_repository ...` respectively.
5
+
6
+ Additionally, adjust cell metadata and tags according to DMSC Summer School rules:
7
+ - If a cell has the `dmsc-school-hint` or `solution` tag, add
8
+ `{"jupyter": {"source_hidden": true}}` so it is collapsed by default.
9
+ - If a cell has the `remove-cell` tag, replace it with `hide_in_docs`.
10
+ - If a cell has the `non-editable` tag, set `editable: false` in metadata.
11
+ - Remove all tags except `hide_in_docs`.
12
+
13
+ Notes:
14
+ - Operates only on code cells for uncommenting (does not touch markdown sources),
15
+ but processes metadata/tags for ALL cells.
16
+ - Matches lines that start with optional whitespace, then `# !pip`
17
+ (e.g., " # !pip install ...").
18
+ - Also matches lines that start with optional whitespace, then
19
+ `# ed.download_from_repository` (e.g., " # ed.download_from_repository(...)").
20
+ - Rewrites to keep the original indentation and replace the leading
21
+ "# !pip" with "!pip", and "# ed.download_from_repository" with "ed.download_from_repository".
22
+ - Processes one or more paths (files or directories) given as CLI args,
23
+ recursively for directories.
24
+
25
+ New CLI options:
26
+ - `--remove-cell true|false` to control how cells tagged `remove-cell` are handled:
27
+ remove entirely (true) or map tag to `hide_in_docs` (false, default).
28
+ - `--strip-solution true|false` to control handling of cells tagged `solution`:
29
+ replace content with placeholder (true) or collapse via source_hidden (false, default).
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import argparse
35
+ import re
36
+ import sys
37
+ from pathlib import Path
38
+
39
+ import nbformat
40
+ from nbformat.validator import normalize
41
+
42
+ # Regex: beginning-of-line, capture leading whitespace, then "#", spaces, then "!pip"
43
+ _PIP_PATTERN = re.compile(r'^(\s*)#\s*!pip\b')
44
+ # Regex: beginning-of-line, capture leading whitespace, then "#", spaces, then "ed.download_from_repository"
45
+ _ED_PATTERN = re.compile(r'^(\s*)#\s*ed\.download_from_repository\b')
46
+
47
+
48
+ def fix_cell_source(src: str) -> tuple[str, int]:
49
+ """
50
+ Replace lines starting with optional whitespace + '# !pip' with '!pip',
51
+ and lines starting with optional whitespace + '# ed.download_from_repository'
52
+ with 'ed.download_from_repository'.
53
+ Returns the updated source and number of replacements performed.
54
+ """
55
+ changed = 0
56
+ new_lines: list[str] = []
57
+ for line in src.splitlines(keepends=False):
58
+ orig_line = line
59
+ # Replace # !pip
60
+ if _PIP_PATTERN.match(line):
61
+ line = _PIP_PATTERN.sub(r'\1!pip', line, count=1)
62
+ # Replace # ed.download_from_repository
63
+ if _ED_PATTERN.match(line):
64
+ line = _ED_PATTERN.sub(r'\1ed.download_from_repository', line, count=1)
65
+ if line != orig_line:
66
+ changed += 1
67
+ new_lines.append(line)
68
+ return ('\n'.join(new_lines), changed)
69
+
70
+
71
+ def fix_cell_metadata(cell, *, remove_cell: bool, strip_solution: bool) -> tuple[int, bool]:
72
+ """
73
+ Apply tag/metadata rules:
74
+ - 'dmsc-school-hint' or 'solution' -> jupyter.source_hidden = True (unless strip_solution is True)
75
+ - 'remove-cell' -> either remove cell (if remove_cell=True) or rename to 'hide_in_docs'
76
+ - 'non-editable' -> editable = False
77
+ - keep only 'hide_in_docs' tag, drop all others (unless cell removed)
78
+ - if strip_solution=True and 'solution' tag present, replace source with placeholder instead of collapsing
79
+ Returns (number of changes applied, remove_this_cell flag).
80
+ """
81
+ changed = 0
82
+ remove_flag = False
83
+ md = cell.metadata
84
+
85
+ # Normalize tags list
86
+ tags = list(md.get('tags', []))
87
+
88
+ # Check for remove-cell tag and remove_cell flag
89
+ if 'remove-cell' in tags:
90
+ if remove_cell:
91
+ # Mark cell for removal
92
+ remove_flag = True
93
+ # Count change if tags existed (since removal is a change)
94
+ if tags:
95
+ changed += 1
96
+ # Skip further processing for this cell
97
+ return changed, remove_flag
98
+ else:
99
+ # rename remove-cell -> hide_in_docs (without duplicating)
100
+ new_tags = []
101
+ seen_hide = 'hide_in_docs' in tags
102
+ for t in tags:
103
+ if t == 'remove-cell':
104
+ if not seen_hide:
105
+ new_tags.append('hide_in_docs')
106
+ seen_hide = True
107
+ else:
108
+ new_tags.append(t)
109
+ if new_tags != tags:
110
+ tags = new_tags
111
+ changed += 1
112
+
113
+ # Handle 'solution' tag with strip_solution flag
114
+ if strip_solution and ('solution' in tags):
115
+ if getattr(cell, 'source', None) != '# Insert your solution:':
116
+ cell.source = '# Insert your solution:'
117
+ changed += 1
118
+ # Do not add jupyter.source_hidden in this case
119
+ else:
120
+ # Add jupyter.source_hidden when hint or solution tag present
121
+ if ('dmsc-school-hint' in tags) or ('solution' in tags):
122
+ jup = md.get('jupyter', {})
123
+ if jup.get('source_hidden') is not True:
124
+ jup['source_hidden'] = True
125
+ md['jupyter'] = jup
126
+ changed += 1
127
+
128
+ # 3) add editable: false for non-editable tag
129
+ if 'non-editable' in tags:
130
+ if md.get('editable') is not False:
131
+ md['editable'] = False
132
+ changed += 1
133
+
134
+ # 4) keep only 'hide_in_docs' and drop empty tag arrays from metadata, unless removing cell
135
+ if not remove_flag:
136
+ keep = ['hide_in_docs'] if 'hide_in_docs' in tags else []
137
+
138
+ if keep: # we want to keep only 'hide_in_docs'
139
+ if md.get('tags') != keep:
140
+ md['tags'] = keep
141
+ changed += 1
142
+ else:
143
+ # remove empty tags key if present
144
+ if 'tags' in md:
145
+ # count as change if previously non-empty
146
+ if md['tags']:
147
+ changed += 1
148
+ md.pop('tags', None)
149
+
150
+ return changed, remove_flag
151
+
152
+
153
+ def process_notebook(path: Path, *, remove_cell: bool, strip_solution: bool) -> int:
154
+ """
155
+ Process a single .ipynb file.
156
+ - Uncomment magic/commented lines in code cells.
157
+ - Adjust cell metadata/tags as per school rules.
158
+ Returns number of changes made.
159
+ """
160
+ nb = nbformat.read(path, as_version=4)
161
+ total_changes = 0
162
+ new_cells = []
163
+ for cell in nb.cells:
164
+ # Source-only changes for code cells
165
+ if cell.cell_type == 'code':
166
+ new_src, changes = fix_cell_source(cell.source or '')
167
+ if changes:
168
+ cell.source = new_src
169
+ total_changes += changes
170
+ # Metadata/tag changes for all cells
171
+ md_changes, remove_it = fix_cell_metadata(cell, remove_cell=remove_cell, strip_solution=strip_solution)
172
+ total_changes += md_changes
173
+ if not remove_it:
174
+ new_cells.append(cell)
175
+ nb.cells = new_cells
176
+
177
+ # Normalize/add missing IDs if needed, even when no source/metadata changes were made
178
+ need_normalize = any('id' not in cell for cell in nb.cells)
179
+ if total_changes or need_normalize:
180
+ normalize(nb)
181
+ nbformat.write(nb, path)
182
+ return total_changes + (1 if need_normalize and not total_changes else 0)
183
+
184
+
185
+ def iter_notebooks(paths: list[Path]):
186
+ for p in paths:
187
+ if p.is_dir():
188
+ yield from (q for q in p.rglob('*.ipynb') if q.is_file())
189
+ elif p.is_file() and p.suffix == '.ipynb':
190
+ yield p
191
+
192
+
193
+ def main(argv: list[str]) -> int:
194
+ ap = argparse.ArgumentParser(
195
+ description=(
196
+ "Uncomment '# !pip ...' and '# ed.download_from_repository ...' in code cells, "
197
+ "and adjust cell metadata/tags (collapse hints/solutions, map 'remove-cell' "
198
+ "to 'hide_in_docs' or remove cells, keep only 'hide_in_docs', and mark 'non-editable' cells as not editable)."
199
+ )
200
+ )
201
+ ap.add_argument('paths', nargs='+', help='Notebook files or directories to process')
202
+ ap.add_argument('--dry-run', action='store_true', help='Report changes without writing files')
203
+ ap.add_argument(
204
+ '--remove-cell',
205
+ dest='remove_cell',
206
+ type=lambda s: str(s).lower() == 'true',
207
+ default=False,
208
+ help='true: remove cells tagged "remove-cell"; false (default): map tag to hide_in_docs',
209
+ )
210
+ ap.add_argument(
211
+ '--strip-solution',
212
+ dest='strip_solution',
213
+ type=lambda s: str(s).lower() == 'true',
214
+ default=False,
215
+ help='true: replace content of cells tagged "solution" with placeholder; false (default): collapse via source_hidden',
216
+ )
217
+ args = ap.parse_args(argv)
218
+
219
+ targets = list(iter_notebooks([Path(p) for p in args.paths]))
220
+ if not targets:
221
+ print('No .ipynb files found.', file=sys.stderr)
222
+ return 1
223
+
224
+ total_files = 0
225
+ total_changes = 0
226
+ for nb_path in targets:
227
+ changes = 0
228
+ if args.dry_run:
229
+ nb = nbformat.read(nb_path, as_version=4)
230
+ for cell in nb.cells:
231
+ if cell.cell_type == 'code':
232
+ _, c = fix_cell_source(cell.source or '')
233
+ changes += c
234
+ md_c, remove_it = fix_cell_metadata(cell, remove_cell=args.remove_cell, strip_solution=args.strip_solution)
235
+ changes += md_c
236
+ if remove_it:
237
+ changes += 1 # count removal as a change
238
+ else:
239
+ changes = process_notebook(nb_path, remove_cell=args.remove_cell, strip_solution=args.strip_solution)
240
+
241
+ if changes:
242
+ action = 'WOULD UPDATE' if args.dry_run else 'UPDATED'
243
+ print(f'{action}: {nb_path} ({changes} change(s))')
244
+ total_files += 1
245
+ total_changes += changes
246
+
247
+ if total_files == 0:
248
+ print('No changes needed.')
249
+ else:
250
+ print(f'Done. Files changed: {total_files}, total changes: {total_changes}')
251
+ return 0
252
+
253
+
254
+ if __name__ == '__main__':
255
+ raise SystemExit(main(sys.argv[1:]))