diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index c4fc82f8..9d2be93e 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -11,13 +11,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: set up python 3.8
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.8
- run: pip install . montepy[develop]
- run: python -m pytest
build-pages:
+ needs: [last-minute-test, build-packages]
environment:
name: github-pages
runs-on: ubuntu-latest
@@ -27,7 +28,9 @@ jobs:
TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: git config --global url."https://${TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/"
- uses: actions/checkout@v4
- - uses: actions/setup-python@v4
+ with:
+ ref: main
+ - uses: actions/setup-python@v5
with:
python-version: 3.8
- run: pip install --user . montepy[doc]
@@ -53,13 +56,28 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: set up python 3.8
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.8
- run: pip install . montepy[build]
+ - uses: mathieudutour/github-tag-action@v6.2
+ name: Get next version number (dry)
+ id: version_num
+ with:
+ dry_run: True
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Update changelog version
+ run: |
+ sed -i "s/#Next Version#/${{steps.version_num.outputs.new_version}}/w changes" doc/source/changelog.rst
+ [[ -s changes ]] || exit 1
+ - name: Commit new changelog
+ uses: actions4git/add-commit-push@v1.0.0
+ with:
+ add-pathspec: doc/source
+ commit-message: Update Changelog version to ${{steps.version_num.outputs.new_version}}
- name: GitHub Actions Create Tag
id: tag_version
- uses: mathieudutour/github-tag-action@v6.1
+ uses: mathieudutour/github-tag-action@v6.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# ensure tags are up to date
@@ -85,7 +103,7 @@ jobs:
gh release upload
'${{ steps.tag_version.outputs.new_tag }}' dist/**
--repo '${{ github.repository }}'
- - uses: actions/upload-artifact@v4
+ - uses: actions/upload-artifact@v4.3.1
with:
name: build
path: |
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 10507e64..7c339d9e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -15,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: set up python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install --upgrade pip build
@@ -35,7 +35,8 @@ jobs:
- run: pip install --user . montepy[develop]
- run: pip freeze
- name: Upload build artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4.3.1
+ if: ${{ matrix.python-version == '3.9' }}
with:
name: build
path: dist/*
@@ -50,35 +51,38 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: set up python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install --user . montepy[test]
+ - run: pip install --user . montepy[build]
- run: coverage run -m pytest --junitxml=test_report.xml
- run: coverage report
+ if: ${{ success() || failure() }}
- run: coverage xml
+ if: ${{ success() || failure() }}
- name: Upload test report
- if: ${{ matrix.python-version == '3.9' }}
+ if: ${{ matrix.python-version == '3.9' && (success() || failure() )}}
uses: actions/upload-artifact@v4
with:
name: test
path: test_report.xml
- name: Upload coverage report
- if: ${{ matrix.python-version == '3.9' }}
+ if: ${{ matrix.python-version == '3.9' && (success() || failure() )}}
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage.xml
- name: Test Reporter
- if: ${{ matrix.python-version == '3.9' }}
+ if: ${{ matrix.python-version == '3.9' && (success() || failure() )}}
uses: dorny/test-reporter@v1.7.0
with:
name: CI-test-report
path: test_report.xml
reporter: java-junit
- name: Coveralls GitHub Action
- if: ${{ matrix.python-version == '3.9' }}
- uses: coverallsapp/github-action@v2.2.3
+ if: ${{ matrix.python-version == '3.9' && (success() || failure() )}}
+ uses: coverallsapp/github-action@v2
with:
file: coverage.xml
@@ -88,17 +92,30 @@ jobs:
steps:
- uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
- name: set up python 3.8
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.8
- - run: pip install . montepy[doc]
+ - run: pip install . montepy[doc,build]
+ - uses: mathieudutour/github-tag-action@v6.2
+ name: Get next version number
+ id: version_num
+ with:
+ dry_run: True
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Update changelog version
+ run: |
+ sed -i "s/#Next Version#/${{steps.version_num.outputs.new_version}}/w changes" doc/source/changelog.rst
+ [[ -s changes ]] || exit 1
- run: sphinx-build doc/source/ doc/build/ -W --keep-going -E
- - run: sphinx-build -b html doc/source/ doc/build/html
- - uses: actions/upload-artifact@v3
+ name: Build site strictly
+ - uses: actions/upload-artifact@v4
with:
name: website
- path: doc/build/html
+ path: doc/build
- name: Test for missing API documentation
run: |
cd doc/source
@@ -110,10 +127,27 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: set up python 3.8
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.8
- run: pip install . montepy[format]
- run: black --check montepy/ tests/
-
-
+
+
+ changelog-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Check for changes
+ uses: dorny/paths-filter@v3
+ id: changes
+ with:
+ filters: |
+ change:
+ - 'doc/source/changelog.rst'
+ - if: steps.changes.outputs.change == 'false'
+ run: |
+ echo "Changelog not updated"
+ exit 1
+
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 120000
index 00000000..e0e37937
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1 @@
+doc/source/changelog.rst
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..fba47e95
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,41 @@
+# Contributing to MontePy
+
+Welcome, and thank you for considering contributing to MontePy! We look forward
+to welcoming new members to the community and will do our best to help you get
+up to speed.
+
+## Code of Conduct
+
+Participants in the MontePy project are expected to follow and uphold the [Code
+of Conduct](CODE_OF_CONDUCT.md). Please report any unacceptable behavior to
+micah.gale@inl.gov.
+
+## Resources
+
+- [GitHub Repository](https://github.com/idaholab/montepy)
+- [Documentation](https://idaholab.github.io/MontePy/)
+- [Developer's Guide](https://idaholab.github.io/MontePy/developing.html)
+
+## How to Report Bugs
+
+MontePy is hosted on GitHub and all bugs are reported and tracked through the
+[Issues](https://github.com/idaholab/MontePy/issues) listed on GitHub.
+
+## How to Suggest Enhancements
+
+If you have suggestions for new features, feel free to suggest one as a new issue.
+Keep in mind that MontePy has a small development team,
+and in some cases MontePy might not be the right home for the feature.
+Please review the package philosophy to see if the feature would be a good fit.
+
+## How to Submit Changes
+
+All changes to OpenMC happen through pull requests. For a full overview of the
+process, see the developer's guide section on [Contributing to
+OpenMC](https://docs.openmc.org/en/latest/devguide/contributing.html).
+
+## Code Style
+
+Before you run off to make changes to the code, please review the [design
+philosophy](https://idaholab.github.io/MontePy/developing.html#design-philosophy),
+and make sure to also use [black](https://black.readthedocs.io/en/stable/index.html).
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000..7c325863
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+exclude tests/test_version.py
diff --git a/doc/source/_test_for_missing_docs.py b/doc/source/_test_for_missing_docs.py
index f0dbf9a7..c6ad78f1 100644
--- a/doc/source/_test_for_missing_docs.py
+++ b/doc/source/_test_for_missing_docs.py
@@ -3,7 +3,7 @@
import sys
import warnings
-ignored = {"__pycache__", "_version.py", "__main__.py", "_cell_data_control.py"}
+ignored = {"__pycache__", "_scripts", "_version.py", "__main__.py", "_cell_data_control.py"}
base = os.path.join("..", "..")
@@ -14,6 +14,8 @@ def crawl_path(rel_path):
f_name = os.path.join(rel_path, f)
if f in ignored:
continue
+ if f_name == "montepy/__init__.py":
+ continue
if os.path.isdir(os.path.join(base, f_name)):
crawl_path(f_name)
elif os.path.isfile(os.path.join(base, f_name)) and ".py" in f:
diff --git a/doc/source/api/modules.rst b/doc/source/api/modules.rst
index 7331f9c2..89af6576 100644
--- a/doc/source/api/modules.rst
+++ b/doc/source/api/modules.rst
@@ -1,8 +1,36 @@
-Python API
-============
+MontePy API
+===========
+Submodules
+----------
.. toctree::
- :maxdepth: 4
+ :maxdepth: 1
+
+ montepy.cell
+ montepy.cells
+ montepy.constants
+ montepy.errors
+ montepy.geometry_operators
+ montepy.materials
+ montepy.mcnp_object
+ montepy.mcnp_problem
+ montepy.numbered_mcnp_object
+ montepy.numbered_object_collection
+ montepy.particle
+ montepy.surface_collection
+ montepy.transforms
+ montepy.universe
+ montepy.universes
+ montepy.utilities
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 2
+
+ montepy.data_inputs
+ montepy.input_parser
+ montepy.surfaces
- montepy
diff --git a/doc/source/api/montepy.cell.rst b/doc/source/api/montepy.cell.rst
index f14acb8c..5b34b566 100644
--- a/doc/source/api/montepy.cell.rst
+++ b/doc/source/api/montepy.cell.rst
@@ -2,7 +2,7 @@ montepy.cell module
===================
-.. automodule:: montepy.cell
+.. autoclass:: montepy.cell.Cell
:members:
:undoc-members:
:show-inheritance:
diff --git a/doc/source/api/montepy.data_inputs.rst b/doc/source/api/montepy.data_inputs.rst
index 96bdae1e..3471a48c 100644
--- a/doc/source/api/montepy.data_inputs.rst
+++ b/doc/source/api/montepy.data_inputs.rst
@@ -7,8 +7,6 @@ montepy.data\_inputs package
:undoc-members:
:show-inheritance:
-Submodules
-----------
.. toctree::
:maxdepth: 4
diff --git a/doc/source/api/montepy.input_parser.rst b/doc/source/api/montepy.input_parser.rst
index fc837495..3c595b97 100644
--- a/doc/source/api/montepy.input_parser.rst
+++ b/doc/source/api/montepy.input_parser.rst
@@ -7,11 +7,8 @@ montepy.input\_parser package
:undoc-members:
:show-inheritance:
-Submodules
-----------
-
.. toctree::
- :maxdepth: 4
+ :maxdepth: 2
montepy.input_parser.block_type
montepy.input_parser.cell_parser
diff --git a/doc/source/api/montepy.rst b/doc/source/api/montepy.rst
deleted file mode 100644
index 7f788814..00000000
--- a/doc/source/api/montepy.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-montepy package
-===============
-
-
-.. automodule:: montepy
- :members:
- :undoc-members:
- :show-inheritance:
-
-Subpackages
------------
-
-.. toctree::
- :maxdepth: 4
-
- montepy.data_inputs
- montepy.input_parser
- montepy.surfaces
-
-Submodules
-----------
-
-.. toctree::
- :maxdepth: 4
-
- montepy.cell
- montepy.cells
- montepy.constants
- montepy.errors
- montepy.geometry_operators
- montepy.materials
- montepy.mcnp_object
- montepy.mcnp_problem
- montepy.numbered_mcnp_object
- montepy.numbered_object_collection
- montepy.particle
- montepy.surface_collection
- montepy.transforms
- montepy.universe
- montepy.universes
- montepy.utilities
diff --git a/doc/source/api/montepy.surfaces.rst b/doc/source/api/montepy.surfaces.rst
index e66b4112..6f07c96e 100644
--- a/doc/source/api/montepy.surfaces.rst
+++ b/doc/source/api/montepy.surfaces.rst
@@ -7,11 +7,9 @@ montepy.surfaces package
:undoc-members:
:show-inheritance:
-Submodules
-----------
.. toctree::
- :maxdepth: 4
+ :maxdepth: 2
montepy.surfaces.axis_plane
montepy.surfaces.cylinder_on_axis
diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst
new file mode 100644
index 00000000..9ed36f32
--- /dev/null
+++ b/doc/source/changelog.rst
@@ -0,0 +1,160 @@
+MontePy Changelog
+=================
+
+#Next Version#
+-----------------------
+
+**Documentation**
+
+* Added changelog
+* Added code of conduct
+* Added contribution guideline
+* Added pull request template
+
+
+0.2.5
+-------------------
+
+**Added**
+
+* Implemented Github actions
+* Added default github issue templates
+
+**Changed**
+
+* Improved readme and documentation hyperlinks
+
+**Fixed**
+
+* bug with comments in complex geometry.
+
+
+0.2.4
+-------------------
+**Added**
+
+* Public release
+
+0.2.3
+--------------------
+**Added**
+
+* A license
+* A logo
+
+**Changed**
+
+* Explicitly set file encoding for read/write. #159.
+
+**Fixed**
+
+* Bug with not detecting comments with no space e.g., `c\n`. #158.
+
+0.2.2
+--------------------
+**Fixed**
+
+* TODO
+
+0.2.1
+---------------------
+**Fixed**
+
+* A bug with the packaging process
+
+0.2.0
+----------------------
+**Added**
+
+* User formatting is preserved automatically
+* Cell geometry is now stored in `cell.geometry` and can be set with bitwise operators. e.g., `cell.geometry = + inner_sphere & - outer_sphere`. This was heavily influenced by OpenMC.
+* You can now check an input file for errors from the command line. `python -m montepy -c /path/to/inputs/*.imcnp`
+* The error reporting for syntax errors should be much more intuitive now, and easy to read.
+* Dollar sign comments are kept and are available in `obj.comments`
+* All comments are now in a generator `.comments`
+
+**Deprecated**
+
+* `montepy.data_cards` moved to `montepy.data_inputs`
+* `montepy.data_cards.data_card` is now `montepy.data_inputs.data_input`
+* `Montepy.Cell.geometry_logic_string` was completely removed.
+* Much of the internal functions with how objects are written to file were changed and/or deprecated.
+* `montepy.data_cards.data_card.DataCard.class_prefix` was moved to `_class_prefix` as the user usually shouldn't see this. Same goes for `has_classifier` and `has_number`.
+* Most of the data types inside `montepy.input_parser.mcnp_input` were deprecated or changed
+
+0.1.7
+-----------------
+
+**Added**
+
+* License information
+
+0.1.6
+-------------------
+
+**fixed**
+
+* Fixed bug that `+=` didn't work with Numbered object collections
+* Updated the Documentation URL for sphinx
+* Improved (and then removed) guidance on weird gitlab installation workflow.
+
+0.1.5
+--------------------
+
+**Fixed**
+
+* When a `PX` style surface was `1 PZ 0` this would cause a validation error.
+* Empty "cell modifiers" would be printed in the data block even if they had no useful information. E.g., `U 30J`
+* Volumes couldn't start with a jump e.g., `vol j 1.0`
+* "Cell modifiers" were printed both in the cell block and the data block.
+* Running `problem.cells = []` would make the problem impossible to write to file.
+* Support was added for tabs.
+
+0.1.0
+---------------------
+
+
+**Added**
+
+* Added infrastructure to support cell modifier inputs easily
+* Added support for importances, and particle modes: `imp`, `mode`.
+* Added support for cell volumes `vol`.
+* Add support for Universes, lattices, and fills `U`, `fill`, `lat`.
+* Created universal system for parsing parameters
+* If you create an object from scratch and write it out to a file while it is missing, it will gracefully fail with a helpful error message.
+* Added support for detecting metastable isotopes.
+* Improved the experience with densities in `Cell` instead of having `cell.density` now there is `cell.mass_density` and `cell.atom_density`.
+
+
+**Fixed**
+
+* Supported parameters that don't have equal signs. MCNP supports `1 0 -1 u 1`
+* Now doesn't try to expand shortcuts inside of `FC` and `SC` comments.
+
+**Code Quality**
+
+* Removed magic numbers for number of characters in a line.
+* Reduced the usage of regular expressions
+* Made error messages related to invalid user set attributes clearer.
+* Cleaned up documentation and docstrings
+* Improved CI backend
+
+
+0.0.5
+-----------------------
+
+**Added:**
+
+ * `NumberedObjectCollections` which is implemented for `cells`, `surfaces`, and `materials`. This changed these collections from being a list to acting like a dict. Objects are now retrievable by their number e.g., `cells[1005]` will retrieve cell 1005.
+ * Implemented "pass-through" of the original inputs. If an object is not edited or mutated, the original formatting from the input file will be copied out to the output.
+ * Support was added for most MCNP shortcuts: (`R`, `I`, `M`, `LOG`), `J` still needs some better support. MontePy will expand these shortcuts, but will not "recompress" them.
+ * Added sphinx documentation website. This documents the API, has a starting guide for the users, and a guide for developers.
+
+
+**Changed:**
+
+* Object numbers are now generalized: e.g., `cell.cell_number` has changed to `cell.number`. The `.number` property is standardized across all numbered objects.
+
+**Fixed:**
+
+* Comments in the middle of an input no longer breaks the input into two.
diff --git a/doc/source/dev_tree.rst b/doc/source/dev_tree.rst
new file mode 100644
index 00000000..df91c14d
--- /dev/null
+++ b/doc/source/dev_tree.rst
@@ -0,0 +1,9 @@
+Developer's Resources
+=====================
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Contents:
+
+ developing
+ scope
diff --git a/doc/source/developing.rst b/doc/source/developing.rst
index 5d9c2485..6f740325 100644
--- a/doc/source/developing.rst
+++ b/doc/source/developing.rst
@@ -15,6 +15,7 @@ Contributing
Here is a getting started guide to contributing.
If you have any questions Micah and Travis are available to give input and answer your questions.
+Before contributing you should review the :ref:`scope` and design philosophy.
Setting up and Typical Development Workflow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -154,27 +155,6 @@ All classes need to be children of :class:`~montepy.surfaces.surface.Surface`.
When possible new surface classes should combine similar planes.
For example :class:`~montepy.surfaces.axis_plane.AxisPlane` covers ``PX``, ``PY``, and ``PZ``.
-Design Philosophy
------------------
-#. **Do Not Repeat Yourself (DRY)**
-#. Use abstraction and inheritance smartly.
-#. Use ``_private`` fields mostly. Use ``__private`` for very private things that should never be touched.
-#. Use ``@property`` getters, and if needed setters. Setters must verify and clean user inputs. For the most part use :func:`~montepy.utilities.make_prop_val_node`, and :func:`~montepy.utilities.make_prop_pointer`.
-#. Fail early and politely. If there's something that might be bad: the user should get a helpful error as
- soon as the error is apparent.
-#. Test. test. test. The goal is to achieve 100% test coverage. Unit test first, then do integration testing. A new feature merge request will ideally have around a dozen new test cases.
-#. Do it right the first time.
-#. Document all functions.
-#. Expect everything to mutate at any time.
-#. Avoid relative imports when possible. Use top level ones instead: e.g., ``import montepy.cell.Cell``.
-#. Defer to vanilla python, and only use the standard library. Currently the only dependencies are `numpy `_ and `sly `_.
- There must be good justification for breaking from this convention and complicating things for the user.
-
-Style Guide
------------
-#. Use ``black`` to autoformat all code.
-#. Spaces for indentation, tabs for alignment. Use spaces to build python syntax (4 spaces per level), and tabs for aligning text inside of docstrings.
-
Introduction to SLY and Syntax Trees
------------------------------------
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 31077195..c5fb49e7 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -3,26 +3,30 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
-Welcome to MontePy's documentation!
-===================================
+MontePy: a Python library for MCNP input files.
+===============================================
-.. toctree::
- :maxdepth: 1
- :caption: Contents:
+MontePy is a Python library for reading, editing, and writing MCNP input files.
+MontePy provides an object-oriented interface for MCNP input files.
+This allows for easy automation of many different tasks for working with MCNP input files.
- starting
+Installing
+----------
- utilities
+MontePy can be installed with pip:
- tricks
+.. code-block:: shell
- faq
+ pip install montepy
- developing
- api/modules
+.. toctree::
+ :maxdepth: 2
+ :caption: Table of Contents:
- publications
+ api/modules
+ users
+ dev_tree
See Also
========
diff --git a/doc/source/scope.rst b/doc/source/scope.rst
new file mode 100644
index 00000000..acbc964a
--- /dev/null
+++ b/doc/source/scope.rst
@@ -0,0 +1,101 @@
+.. _scope:
+
+MontePy Scope
+=============
+
+This document defines the scope of MontePy so it easier to decide if a feature should live in MontePy or possibly another repository.
+This isn't meant to be fully static and is subject to change.
+This is less focused on defining a strict scope and more-so on providing guidelines.
+
+Mission
+-------
+
+MontePy's mission is to improve the user experience for working with MCNP input files through the power of python automation?
+
+Principles
+----------
+
+Here are the guiding principles of what MontePy should be, and what it aspires to be.
+
+MontePy should be:
+^^^^^^^^^^^^^^^^^^
+
+#. Easy to install.
+
+ #. It should be on PyPi.
+ #. Install with ``pip`` for most conceivable platforms.
+
+#. Lightweight. It should only include the code that all users need, and only the bare essentials for dependencies.
+#. Well documented. All public functions must have documentation.
+#. `Pythonic `_.
+#. Idiomatic.
+#. Consistently designed.
+#. General. New features shouldn't be only useful for a specific problem, or class of problems.
+#. Quick to fail and do so in a verbose helpful manner.
+#. Reliable.
+#. Thorough in its validation. If it MontePy finds more issues than MCNP does, that's ok.
+#. Easy to contribute to.
+#. Able to support the full (public) MCNP manual, except when it doesn't.
+
+ #. For features that aren't supported yet an UnsuportedFeature error should be raised.
+ #. The developers reserve the right to decide a feature is not worth ever supporting.
+
+
+MontePy shouldn't be:
+^^^^^^^^^^^^^^^^^^^^^
+
+#. A collection of scripts for every use case.
+#. A linking code to other software.
+#. Written in other languages*
+
+Design Philosophy
+-----------------
+
+#. **Do Not Repeat Yourself (DRY)**
+#. If it's worth doing, it's worth doing well.
+#. Use abstraction and inheritance smartly.
+#. Use ``_private`` fields mostly. Use ``__private`` for very private things that should never be touched.
+#. Use ``@property`` getters, and if needed setters. Setters must verify and clean user inputs. For the most part use :func:`~montepy.utilities.make_prop_val_node`, and :func:`~montepy.utilities.make_prop_pointer`.
+#. Fail early and politely. If there's something that might be bad: the user should get a helpful error as
+ soon as the error is apparent.
+#. Test. test. test. The goal is to achieve 100% test coverage. Unit test first, then do integration testing. A new feature merge request will ideally have around a dozen new test cases.
+#. Do it right the first time.
+#. Document all functions.
+#. Expect everything to mutate at any time.
+#. Avoid relative imports when possible. Use top level ones instead: e.g., ``import montepy.cell.Cell``.
+#. Defer to vanilla python, and only use the standard library. Currently the only dependencies are `numpy `_ and `sly `_.
+ There must be good justification for breaking from this convention and complicating things for the user.
+
+Style Guide
+-----------
+#. Use ``black`` to autoformat all code.
+#. Spaces for indentation, tabs for alignment. Use spaces to build python syntax (4 spaces per level), and tabs for aligning text inside of docstrings.
+#. Follow `PEP 8 `_.
+
+
+Support of MCNP Output Files
+----------------------------
+This is a common question: "Will MontePy support MCNP output files?"
+The short answer is no.
+This is due to a few reasons:
+
+#. MCNP is export controlled, and none of the public manuals document the formatting of the output files.
+ So out of an abundance of caution we treat the format of MCNP output files as being export controlled.
+#. The output format is not documented. This makes it hard to robustly handle, and also means that the format may
+ change.
+#. `MCNPtools `_ exists and may be a better tool than what we can implement.
+
+
+Note on use of other languages
+------------------------------
+
+The use of another language in the code base goes against the principle that it should be easy to contribute.
+However, there could be cases where the advantages of another language could justify this violation.
+Here are guidelines for when it could be appropriate:
+
+#. There needs to be a clear and significant benefit.
+#. The rewrite needs to be limited in scope to a module that few developers will modify.
+
+ #. Only the input parser seems to meet this criteria.
+
+#. There must be a dependable build system that allows deployment to PyPI.
diff --git a/doc/source/users.rst b/doc/source/users.rst
new file mode 100644
index 00000000..7a572473
--- /dev/null
+++ b/doc/source/users.rst
@@ -0,0 +1,13 @@
+User Guide
+==========
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+ starting
+ utilities
+ tricks
+ faq
+ changelog
+ publications
diff --git a/montepy/__init__.py b/montepy/__init__.py
index 93cf0e4f..c33e7e2c 100644
--- a/montepy/__init__.py
+++ b/montepy/__init__.py
@@ -33,7 +33,7 @@
from setuptools_scm import get_version
__version__ = get_version()
- except ImportError:
+ except (ImportError, LookupError):
__version__ = "Undefined"
diff --git a/montepy/data_inputs/isotope.py b/montepy/data_inputs/isotope.py
index 5a5272a5..4303198d 100644
--- a/montepy/data_inputs/isotope.py
+++ b/montepy/data_inputs/isotope.py
@@ -20,20 +20,22 @@ class Isotope:
def __init__(self, ZAID="", node=None):
if node is not None and isinstance(node, ValueNode):
+ if node.type == float:
+ node = ValueNode(node.token, str, node.padding)
self._tree = node
ZAID = node.value
- if "." in ZAID:
- parts = ZAID.split(".")
- try:
- assert len(parts) == 2
- int(parts[0])
- except (AssertionError, ValueError) as e:
- raise ValueError(f"ZAID: {ZAID} could not be parsed as a valid isotope")
- self._ZAID = parts[0]
- self.__parse_zaid()
+ parts = ZAID.split(".")
+ try:
+ assert len(parts) <= 2
+ int(parts[0])
+ except (AssertionError, ValueError) as e:
+ raise ValueError(f"ZAID: {ZAID} could not be parsed as a valid isotope")
+ self._ZAID = parts[0]
+ self.__parse_zaid()
+ if len(parts) == 2:
self._library = parts[1]
else:
- raise ValueError(f"ZAID: {ZAID} could not be parsed as a valid isotope")
+ self._library = ""
def __parse_zaid(self):
"""
diff --git a/montepy/data_inputs/material.py b/montepy/data_inputs/material.py
index 44516538..75cd3413 100644
--- a/montepy/data_inputs/material.py
+++ b/montepy/data_inputs/material.py
@@ -3,6 +3,8 @@
from montepy.data_inputs import data_input, thermal_scattering
from montepy.data_inputs.isotope import Isotope
from montepy.data_inputs.material_component import MaterialComponent
+from montepy.input_parser import syntax_node
+from montepy.input_parser.material_parser import MaterialParser
from montepy import mcnp_object
from montepy.numbered_mcnp_object import Numbered_MCNP_Object
from montepy.errors import *
@@ -26,6 +28,8 @@ class Material(data_input.DataInputAbstract, Numbered_MCNP_Object):
:type input: Input
"""
+ _parser = MaterialParser()
+
def __init__(self, input=None):
self._material_components = {}
self._thermal_scattering = None
@@ -37,7 +41,24 @@ def __init__(self, input=None):
self._number = num
set_atom_frac = False
isotope_fractions = self._tree["data"]
- for isotope_node, fraction in isotope_fractions:
+ if isinstance(isotope_fractions, syntax_node.ListNode):
+ # in python 3.12 this can be replaced with itertools.batched
+ def batch_gen():
+ it = iter(isotope_fractions)
+ while batch := tuple(itertools.islice(it, 2)):
+ yield batch
+
+ iterator = batch_gen()
+ elif isinstance(isotope_fractions, syntax_node.IsotopesNode):
+ iterator = iter(isotope_fractions)
+ else: # pragma: no cover
+ # this is a fall through error, that should never be raised,
+ # but is here just in case
+ raise MalformedInputError(
+ input,
+ f"Material definitions for material: {self.number} is not valid.",
+ )
+ for isotope_node, fraction in iterator:
isotope = Isotope(node=isotope_node)
fraction.is_negatable_float = True
if not set_atom_frac:
diff --git a/montepy/input_parser/__init__.py b/montepy/input_parser/__init__.py
index 487d3fcb..a80864bb 100644
--- a/montepy/input_parser/__init__.py
+++ b/montepy/input_parser/__init__.py
@@ -4,6 +4,7 @@
from . import cell_parser
from . import data_parser
from . import input_reader
+from . import material_parser
from . import mcnp_input
from . import parser_base
from . import read_parser
diff --git a/montepy/input_parser/input_syntax_reader.py b/montepy/input_parser/input_syntax_reader.py
index e6cf1b9e..ac202831 100644
--- a/montepy/input_parser/input_syntax_reader.py
+++ b/montepy/input_parser/input_syntax_reader.py
@@ -9,6 +9,7 @@
from montepy.input_parser.input_file import MCNP_InputFile
from montepy.input_parser.mcnp_input import Input, Message, ReadInput, Title
from montepy.input_parser.read_parser import ReadParser
+from montepy.utilities import is_comment
import os
import warnings
@@ -88,20 +89,6 @@ def read_front_matters(fh, mcnp_version):
break
-def is_comment(line):
- """"""
- upper_start = line[0 : BLANK_SPACE_CONTINUE + 1].upper()
- non_blank_comment = upper_start and line.lstrip().upper().startswith("C ")
- if non_blank_comment:
- return True
- blank_comment = (
- "C\n" == upper_start.lstrip()
- or "C\r\n" == upper_start.lstrip()
- or ("C" == upper_start and "\n" not in line)
- )
- return blank_comment or non_blank_comment
-
-
def read_data(fh, mcnp_version, block_type=None, recursion=False):
"""
Reads the bulk of an MCNP file for all of the MCNP data.
diff --git a/montepy/input_parser/material_parser.py b/montepy/input_parser/material_parser.py
new file mode 100644
index 00000000..f53e1b4b
--- /dev/null
+++ b/montepy/input_parser/material_parser.py
@@ -0,0 +1,31 @@
+# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved.
+from montepy.input_parser.data_parser import DataParser
+from montepy.input_parser import syntax_node
+
+
+class MaterialParser(DataParser):
+ debugfile = None
+
+ @_(
+ "introduction isotopes",
+ "introduction isotopes parameters",
+ )
+ def material(self, p):
+ ret = {}
+ for key, node in p.introduction.nodes.items():
+ ret[key] = node
+ ret["data"] = p.isotopes
+ if hasattr(p, "parameters"):
+ ret["parameters"] = p.parameters
+ return syntax_node.SyntaxNode("data", ret)
+
+ @_("isotope_fractions", "number_sequence", "isotope_hybrid_fractions")
+ def isotopes(self, p):
+ return p[0]
+
+ @_("number_sequence isotope_fraction", "isotope_hybrid_fractions isotope_fraction")
+ def isotope_hybrid_fractions(self, p):
+ ret = p[0]
+ for node in p.isotope_fraction[1:]:
+ ret.append(node)
+ return ret
diff --git a/montepy/input_parser/mcnp_input.py b/montepy/input_parser/mcnp_input.py
index fd533892..700c034e 100644
--- a/montepy/input_parser/mcnp_input.py
+++ b/montepy/input_parser/mcnp_input.py
@@ -295,16 +295,28 @@ class ReadInput(Input):
def __init__(self, input_lines, block_type, input_file=None, lineno=None):
super().__init__(input_lines, block_type, input_file, lineno)
+ if not self.is_read_input(input_lines):
+ raise ValueError("Not a valid Read Input")
parse_result = self._parser.parse(self.tokenize(), self)
- first_word = input_lines[0].split()[0].lower()
if not parse_result:
- if first_word != "read":
- raise ValueError("Not a valid Read Input")
- else:
- raise ParsingError(self, "", self._parser.log.clear_queue())
+ raise ParsingError(self, "", self._parser.log.clear_queue())
self._tree = parse_result
self._parameters = self._tree["parameters"]
+ @staticmethod
+ def is_read_input(input_lines):
+ first_non_comment = ""
+ for line in input_lines:
+ if not is_comment(line):
+ first_non_comment = line
+ break
+ words = first_non_comment.split()
+ if len(words) > 0:
+ first_word = words[0].lower()
+ return first_word == "read"
+ # this is a fall through catch that only happens for a blank input
+ return False # pragma: no cover
+
@property
def file_name(self):
"""
diff --git a/montepy/input_parser/parser_base.py b/montepy/input_parser/parser_base.py
index 900062f6..dcd844ce 100644
--- a/montepy/input_parser/parser_base.py
+++ b/montepy/input_parser/parser_base.py
@@ -374,6 +374,7 @@ def file_name(self, p):
"SURFACE_TYPE",
"THERMAL_LAW",
"ZAID",
+ "NUMBER_WORD",
)
def file_atom(self, p):
return p[0]
diff --git a/montepy/utilities.py b/montepy/utilities.py
index feb1a3db..e167dfef 100644
--- a/montepy/utilities.py
+++ b/montepy/utilities.py
@@ -1,4 +1,5 @@
# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved.
+from montepy.constants import BLANK_SPACE_CONTINUE
import functools
import re
@@ -31,6 +32,25 @@ def fortran_float(number_string):
raise ValueError(f"Value Not parsable as float: {number_string}") from e
+def is_comment(line):
+ """
+ Determines if the line is a ``C comment`` style comment.
+
+ :param line: the line to analyze
+ :type line: str
+ :returns: True if the line is a comment
+ :rtype: bool
+ """
+ upper_start = line[0 : BLANK_SPACE_CONTINUE + 1].upper()
+ non_blank_comment = upper_start and line.lstrip().upper().startswith("C ")
+ if non_blank_comment:
+ return True
+ blank_comment = ("C" == upper_start.strip() and "\n" in line) or (
+ "C" == upper_start and "\n" not in line
+ )
+ return blank_comment
+
+
def make_prop_val_node(
hidden_param, types=None, base_type=None, validator=None, deletable=False
):
diff --git a/pyproject.toml b/pyproject.toml
index 9fab1856..c2b13e9e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -70,7 +70,7 @@ version_file = "montepy/_version.py"
include = ["montepy*"]
[tool.coverage.run]
-omit = ["montepy/_version.py","tests/*"]
+omit = ["tests/*"]
[tool.coverage.report]
precision = 2
diff --git a/tests/constants.py b/tests/constants.py
index 7398f7e4..87ec8663 100644
--- a/tests/constants.py
+++ b/tests/constants.py
@@ -23,6 +23,7 @@
"testReadTarget.imcnp",
"bad_encoding.imcnp",
"unicode.imcnp",
+ "file2read.imcnp",
}
BAD_ENCODING_FILES = {
diff --git a/tests/inputs/file2read.imcnp b/tests/inputs/file2read.imcnp
new file mode 100644
index 00000000..8a2c2b33
--- /dev/null
+++ b/tests/inputs/file2read.imcnp
@@ -0,0 +1,2 @@
+1 0 -1
+c
diff --git a/tests/inputs/readEdgeCase.imcnp b/tests/inputs/readEdgeCase.imcnp
new file mode 100644
index 00000000..f06c3224
--- /dev/null
+++ b/tests/inputs/readEdgeCase.imcnp
@@ -0,0 +1,7 @@
+Testing read card
+C this is a comment
+read file=file2read.imcnp
+
+1 so 0.5
+
+mode n
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
index fabcf147..ef6ac566 100644
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -177,3 +177,8 @@ def test_geometry_comments(self):
# this step caused an error for #163
cell.comments
cell._tree.format()
+
+ def test_bad_read(self):
+ problem = montepy.read_input(
+ os.path.join("tests", "inputs", "readEdgeCase.imcnp")
+ )
diff --git a/tests/test_material.py b/tests/test_material.py
index 56adfed9..aec22336 100644
--- a/tests/test_material.py
+++ b/tests/test_material.py
@@ -32,6 +32,22 @@ def test_material_init(self):
for component in material.material_components:
self.assertEqual(material.material_components[component].fraction, 0.5)
+ # test implicit library with syntax tree errors
+ in_str = """m1 1001 0.33
+ 8016 0.666667"""
+ input_card = Input(in_str.split("\n"), BlockType.DATA)
+ material = Material(input_card)
+ # test implicit library
+ in_str = "M20 1001 0.5 2001 0.5 8016.710nc 0.5"
+ input_card = Input([in_str], BlockType.DATA)
+ material = Material(input_card)
+ self.assertEqual(material.number, 20)
+ self.assertEqual(material.old_number, 20)
+ self.assertTrue(material.is_atom_fraction)
+ for component in material.material_components:
+ self.assertEqual(material.material_components[component].fraction, 0.5)
+
+ # test weight fraction
in_str = "M20 1001.80c -0.5 8016.80c -0.5"
input_card = Input([in_str], BlockType.DATA)
material = Material(input_card)
@@ -164,8 +180,6 @@ def test_isotope_init(self):
Isotope("1001.80c.5")
with self.assertRaises(ValueError):
Isotope("hi.80c")
- with self.assertRaises(ValueError):
- Isotope("1001")
def test_isotope_metastable_init(self):
isotope = Isotope("13426.02c")
diff --git a/tests/test_syntax_parsing.py b/tests/test_syntax_parsing.py
index 73b6f34c..a59a4e05 100644
--- a/tests/test_syntax_parsing.py
+++ b/tests/test_syntax_parsing.py
@@ -1101,6 +1101,10 @@ def testReadCardConfusions(self):
input = ReadInput([f"Read FILE={file}"], BlockType.CELL)
self.assertEqual(input.file_name, file)
+ def testReadCardBadSyntax(self):
+ with self.assertRaises(ParsingError):
+ card = ReadInput(["Read 1"], BlockType.CELL)
+
def testTitleFinder(self):
test_title = "Richard Stallman writes GNU"
test_string = f"""{test_title}
diff --git a/tests/test_version.py b/tests/test_version.py
new file mode 100644
index 00000000..19d8114d
--- /dev/null
+++ b/tests/test_version.py
@@ -0,0 +1,38 @@
+import importlib
+import os
+import sys
+from unittest import TestCase
+
+
+class TestVersion(TestCase):
+ def test_version(self):
+ self.assertTrue(os.path.exists(os.path.join("montepy", "_version.py")))
+ # Test without version.py
+ try:
+ # try without setuptools_scm
+ old_file = os.path.join("montepy", "_version.py")
+ new_file = os.path.join("montepy", "_version.bak")
+ os.rename(old_file, new_file)
+ # clear out previous imports
+ to_delete = set()
+ for mod in sys.modules:
+ for bad_mod in ["setuptools_scm", "montepy"]:
+ if bad_mod in mod:
+ to_delete.add(mod)
+ for mod in to_delete:
+ del sys.modules[mod]
+ sys.modules["setuptools_scm"] = None
+ import montepy
+
+ self.assertEqual(montepy.__version__, "Undefined")
+ # try with setuptools_scm
+ del sys.modules["setuptools_scm"]
+ importlib.reload(montepy)
+ print(f"From setuptools_scm: {montepy.__version__}")
+ self.assertTrue(len(montepy.__version__.split(".")) >= 3)
+ finally:
+ os.rename(new_file, old_file)
+ # do base with _version
+ importlib.reload(montepy)
+ print(f"From _version.py: {montepy.__version__}")
+ self.assertTrue(len(montepy.__version__.split(".")) >= 3)