Repository navigation
Add user_nl_blocks, for tools that rewrite their own user_nl entries - #52
Open
manishvenu wants to merge 3 commits into
Open
manishvenu wants to merge 3 commits into
manishvenu wants to merge 3 commits into
Conversation
… files Tools that configure a case after creation need to rewrite their own user_nl settings on every run without clobbering what the user wrote. Each call strips and rewrites the blocks carrying its tag, leaves out variables the user set outside them, warns about hand edits inside them, and takes the caseroot and instance count explicitly instead of reading global state.
alperaltuntas
requested changes
Oct 27, 2026
…variables - _parse matches any owner write() accepts (spaces, empty, trailing spaces), so strip() removes every block write() wrote. - snapshot() keys files by absolute path and restore() resolves its caseroot, so a relative or differently spelled caseroot, or a moved working directory, no longer deletes the user_nl files. - write() raises a ValueError, before writing any file, when another of the tag's blocks already sets one of its variables.
alperaltuntas
requested changes
Oct 29, 2026
| for path in _files(Path(caseroot), model, ninst): | ||
| lines = _read_lines(path) | ||
| outside, inside = _set_keys(lines, path, tag) | ||
| for _, pairs in groups: |
Member
There was a problem hiding this comment.
This only checks the tag's other blocks, so the same variable in two groups of one write call (e.g., X and x) is still written twice. We should check that too.
| ( | ||
| j | ||
| for j in range(i + 1, len(lines)) | ||
| if lines[j].rstrip("\n") == end_line |
Member
There was a problem hiding this comment.
I think my earlier rstrip("\n") suggestion made this stricter than it needs to be. An end line with a trailing space now raises a RuntimeError. Comparing lines[j].rstrip() with end_line.rstrip() should handle both trailing spaces and empty owners.
…d line - write() also raises a ValueError when its own groups set the same variable twice (e.g. X and x in two groups), before writing any file. - _parse ignores trailing whitespace on both the end line and the expected end line, so an editor's trailing space no longer raises, and empty owners or owners ending in a space still match.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
append_user_nlappends. That suits case creation, which runs once. A tool thatconfigures a case after creation is different. Forcing or boundary setup that writes
parameters into
user_nl_momoruser_nl_datm_streamsgets run again on the same case:a notebook cell is rerun, or the settings change. If each run appends:
Deleting lines by variable name would also delete the user's own edits. A tool like this
needs to own its entries: put them in a marked block it can find, remove and rewrite on
every run, and leave everything else alone.
append_user_nlalso reads CASEROOT and NINST from the globalcvars, which belong towhichever case was set up last. A tool working on a case read back from its caseroot,
or on two cases in one process, can't rely on that.
This module is the rewritable counterpart of
append_user_nl. It writes the same files(including the per-instance
user_nl_<model>_NNNNones) in the same layout. It takes thecaseroot and instance count as arguments and uses only the standard library: no
cvars,widgets or CIME. Keeping it in visualCaseGen means tools built on vCG share one
implementation and one marker format. CrocoDash, which creates its cases through vCG's
CaseCreator, is the first user.
What
New module
visualCaseGen/user_nl_blocks.py. Nothing in vCG calls it yet, so existingbehavior is unchanged.
A block looks like this.
tagnames the tool andownerthe part of it that wrote theblock:
The marker lines are
!comments, which MOM_interface's user_nl parser, CIME's namelistparser and CDEPS's stream-mod parser all skip. The sha records the block's contents as
written.
write(owner, model, groups, *, caseroot, ninst, tag)appendsowner's block touser_nl_<model>, or to each instance's file.groupsis[(comment or None, [(var, val), ...]), ...]. It is written and printedthe way consecutive
append_user_nlcalls would.says so. The comparison ignores case and whitespace, so CDEPS's
stream : keyandstream:keycount as the same variable.strip(caseroot, tag)removes every block with that tag from the case's user_nlfiles. It returns the blocks whose contents no longer match their sha, i.e. ones that
were edited by hand.
report_edits(caseroot, edited, tag)runs after the rewrite. For each hand-editedblock, it prints the lines that were discarded and the ones written back.
snapshot(caseroot)/restore(caseroot, contents)put the user_nl files back if arun fails part-way. That includes deleting any file the run created.
A run is:
snapshot,strip, onewriteper owner and file,report_edits; andrestoreon an exception. The result is the same as if only the latest run had happened.Tests
tests/1_unit/test_user_nl_blocks.py: five tests usingtmp_pathonly, with no CESM orCIME.
stripreports no hand edit. A rerun thatdrops a group removes its lines.
stripremoves only its own tag's blocks. Another tag's block and the creation linesstay. The tag's block is given as literal text, which pins the layout that existing
files hold.
ninst=2, each instance file gets its own block and keeps its own overrides.restoreundoeswrites, including deleting a file they created.Notes for reviewers
Moved from CrocoDash with the code unchanged. There are only two differences:
tagis required, so vCG holds no CrocoDash default.The block format and parsing are byte-for-byte the same, so blocks CrocoDash has already
written into existing cases keep parsing. CrocoDash will switch to this module in a
follow-up, passing its existing tag.
Hand edits inside a block are not kept. A block belongs to its tool.
stripspotsedits through the sha, and
report_editswarns about them. Users set their own valuesoutside the blocks, where they are kept and take precedence.
writealone appends. Replacing a block takes astripfirst. Onestripper runremoves all of the tag's blocks, so a block the tool no longer writes disappears too.
Malformed blocks raise. A block whose end line is missing raises a RuntimeError
instead of deleting to the end of the file.
writeparses every instance file before itwrites any, so a malformed file changes nothing.
Output differs a little from
append_user_nl. The log goes through plainprint,with no ANSI color and no
outwidget, since there is no GUI here.Placement. It sits at the top level of
visualCaseGen/rather than incustom_widget_types/case_tools.py, because it isn't a widget helper and doesn't touchcvars. Happy to move it.Untested here:
report_edits' wording and the malformed-block error.Possible follow-ups in visualCaseGen
CaseCreator._apply_user_nl_changesappends visualCaseGen's entries: the MOM6 gridand timestep parameters, the CICE grid file, and CLM's
fsurdat. Appended linesduplicate whenever the step runs again, and MOM6's buildnml exits on a duplicate
parameter.
user_nl_blocks.write(..., tag="visualCaseGen")would makereapplying idempotent.
so a later "what did the user change" view could compare against them.
🤖 Generated with Claude Code