# Copyright 2019 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This package provides various operations users can ask ARMI to do with their inputs.
An Entry Point might run a simulation, migrate inputs, build a suite of related inputs
and submit them in a parameter sweep, validate inputs, open the GUI, run a test suite,
or other similar things. There are built-in entry points, and additional ones may
be specified by custom plugins.
The full :doc:`docs for entry points are here </developer/entrypoints>`.
See Also
--------
armi.cases : Individual collections of tasks that may run one or more entry points.
These allow one entry point to create a sequence of events that may call one
or more additional entry points. For example, the ``submitSuite`` entry point builds
a case suite with many related cases that will all call the ``run`` entry point from
a HPC cluster.
armi.operators : Operations that ARMI will perform on a reactor model.
These may be created by ``Case`` objects created by certain entry points (e.g. ``run``).
armi : Fundamental entry point that calls this package.
"""
# importing each module causes the any EntryPoints defined in the module that
# are decorated with @armi.command to be added to the collection of registered
# classes
import argparse
import re
import textwrap
from typing import Optional
from armi import context
from armi import meta
from armi import plugins
from armi import runLog
[docs]class EntryPointsPlugin(plugins.ArmiPlugin):
[docs] @staticmethod
@plugins.HOOKIMPL
def defineEntryPoints():
from armi.cli import (
checkInputs,
clone,
compareCases,
migrateInputs,
modify,
run,
gridGui,
# testing
cleanTemps,
runSuite,
reportsEntryPoint,
)
entryPoints = []
entryPoints.append(checkInputs.CheckInputEntryPoint)
entryPoints.append(checkInputs.ExpandBlueprints)
entryPoints.append(clone.CloneArmiRunCommandBatch)
entryPoints.append(clone.CloneArmiRunCommandInteractive)
entryPoints.append(clone.CloneSuiteCommand)
entryPoints.append(compareCases.CompareCases)
entryPoints.append(compareCases.CompareSuites)
entryPoints.append(migrateInputs.MigrateInputs)
entryPoints.append(modify.ModifyCaseSettingsCommand)
entryPoints.append(run.RunEntryPoint)
entryPoints.append(runSuite.RunSuiteCommand)
entryPoints.append(gridGui.GridGuiEntryPoint)
# testing
entryPoints.append(cleanTemps.CleanTemps)
entryPoints.append(reportsEntryPoint.ReportsEntryPoint)
return entryPoints
[docs]class ArmiParser(argparse.ArgumentParser):
"""Subclass of default ArgumentParser to better handle application splash text."""
[docs] def print_help(self, file=None):
splash()
argparse.ArgumentParser.print_help(self, file)
[docs]class ArmiCLI:
"""
ARMI CLI -- The main entry point into ARMI. There are various commands available. To get help
for the individual commands, run again with `<command> --help`. Typically, the CLI implements
functions that already exist within ARMI.
.. impl:: The basic ARMI CLI, for running a simulation.
:id: I_ARMI_CLI_CS
:implements: R_ARMI_CLI_CS
Provides a basic command-line interface (CLI) for running an ARMI simulation. Available
commands can be listed with ``-l``. Information on individual commands can be obtained by
running with ``<command> --help``.
"""
def __init__(self):
from armi import getPluginManager
self._entryPoints = dict()
for pluginEntryPoints in getPluginManager().hook.defineEntryPoints():
for entryPoint in pluginEntryPoints:
if entryPoint.name in self._entryPoints:
raise KeyError(
"Duplicate entry points defined for `{}`: {} and {}".format(
entryPoint.name,
self._entryPoints[entryPoint.name],
entryPoint,
)
)
self._entryPoints[entryPoint.name] = entryPoint
parser = ArmiParser(
prog=context.APP_NAME,
description=self.__doc__.split(".. impl")[0],
usage="%(prog)s [-h] [-l | command [args]]",
)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"-v", "--version", action="store_true", help="display the version"
)
group.add_argument(
"-l", "--list-commands", action="store_true", help="list commands"
)
group.add_argument("command", nargs="?", default="help", help=argparse.SUPPRESS)
parser.add_argument("args", nargs=argparse.REMAINDER, help=argparse.SUPPRESS)
self.parser = parser
[docs] @staticmethod
def showVersion():
"""Print the App name and version on the command line."""
from armi import getApp
prog = context.APP_NAME
app = getApp()
if app is None or prog == "armi":
print("{0} {1}".format(prog, meta.__version__))
else:
print("{0} {1}".format(prog, app.version))
[docs] def listCommands(self):
"""List commands with a short description."""
splash()
indent = 22
initial_indent = " "
subsequent_indent = initial_indent + " " * indent
wrapper = textwrap.TextWrapper(
initial_indent=initial_indent, subsequent_indent=subsequent_indent, width=79
)
sub = re.compile(r"\s+").sub
# given a string, condense white space into a single space
condense = lambda s: sub(" ", s.strip())
commands = self._entryPoints.values()
formatter = "{name:<{width}}{desc}".format
print("\ncommands:")
for cmd in sorted(commands, key=lambda cmd: cmd.name):
"""Each command can optionally define a class attribute `description`
as documentation. If description is not defined (default=None since
it should inherit from EntryPoint), then the docstring is used.
If the docstring is also None, then fall back to an empty string."""
desc = condense(cmd.description or cmd.__doc__ or "")
print(wrapper.fill(formatter(width=indent, name=cmd.name, desc=desc)))
[docs] def run(self) -> Optional[int]:
args = self.parser.parse_args()
if args.list_commands:
self.listCommands()
return 0
elif args.version:
ArmiCLI.showVersion()
return 0
elif args.command == "help":
self.parser.print_help()
return 0
return self.executeCommand(args.command, args.args)
[docs] def executeCommand(self, command, args) -> Optional[int]:
"""Execute `command` with arguments `args`, return optional exit code."""
command = command.lower()
if command not in self._entryPoints:
print(
'Unrecognized command "{}". Valid commands are listed below.'.format(
command
)
)
self.listCommands()
return 1
commandClass = self._entryPoints[command]
cmd = commandClass()
if cmd.splash:
splash()
# parse the arguments... command can have their own
cmd.parse(args)
if cmd.args.batch:
context.Mode.setMode(context.Mode.BATCH)
elif cmd.mode is not None:
context.Mode.setMode(cmd.mode)
# do whatever there is to be done!
return cmd.invoke()
[docs]def splash():
"""Emit a the active App's splash text to the runLog for the primary node."""
from armi import getApp
app = getApp()
assert app is not None
if context.MPI_RANK == 0:
runLog.raw(app.splashText)