Skip to content

Commit

Permalink
Merge pull request #2280 from SasView/ticket-2237-cli-2
Browse files Browse the repository at this point in the history
In the absence of any contradictory response from @llimeht we have agreed to merge.
  • Loading branch information
butlerpd committed Mar 14, 2023
2 parents b956b61 + de26080 commit c31aeed
Show file tree
Hide file tree
Showing 17 changed files with 509 additions and 135 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ jobs:
python -m pip install wheel setuptools
python -m pip install -r build_tools/requirements.txt
- name: Install pywin32 (Windows)
if: ${{ startsWith(matrix.os, 'windows') }}
run: |
python -m pip install pywin32
- name: Install pyopencl (Windows)
if: ${{ startsWith(matrix.os, 'windows') }}
run: |
Expand Down
1 change: 1 addition & 0 deletions build_tools/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ importlib-resources
bumps
html2text
jsonschema
pywin32; platform_system == "Windows"
superqt
pyopengl
pyopengl_accelerate
6 changes: 5 additions & 1 deletion docs/sphinx-docs/source/user/working.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ Working with SasView

Writing a Plugin Model <qtgui/Perspectives/Fitting/plugin>

Scripting Interface to Sasmodels <qtgui/Perspectives/Fitting/scripting>

Command Line Interpreter Syntax <qtgui/Perspectives/Fitting/cli>

Environment Variables <environment>

Model marketplace <marketplace>
Model Marketplace <marketplace>

Preferences <qtgui/MainWindow/preferences_help.rst>
10 changes: 5 additions & 5 deletions installers/sasview.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
"""

import sys


sys.dont_write_bytecode = True

from sas.qtgui.MainWindow.MainWindow import run_sasview

run_sasview()
if __name__ == "__main__":
from multiprocessing import freeze_support
freeze_support()
from sas.cli import main
sys.exit(main())
7 changes: 7 additions & 0 deletions installers/sasview.spec
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ hiddenimports = [
'uncertainties',
]

if platform.system() == 'Windows':
# Need win32 to run sasview from the command line.
hiddenimports.extend([
'win32',
'win32.win32console',
])

a = Analysis(
['sasview.py'],
pathex=[],
Expand Down
77 changes: 29 additions & 48 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
import os
from os.path import abspath, dirname, realpath, join as joinpath
from contextlib import contextmanager

PLUGIN_MODEL_DIR = 'plugin_models'
from importlib import import_module

def addpath(path):
"""
Expand All @@ -35,7 +34,6 @@ def addpath(path):
os.environ['PYTHONPATH'] = PYTHONPATH
sys.path.insert(0, path)


@contextmanager
def cd(path):
"""
Expand All @@ -46,62 +44,45 @@ def cd(path):
yield
os.chdir(old_dir)

def setup_sasmodels():
"""
Prepare sasmodels for running within sasview.
"""
# Set SAS_MODELPATH so sasmodels can find our custom models

from sas.system.user import get_user_dir
plugin_dir = os.path.join(get_user_dir(), PLUGIN_MODEL_DIR)
os.environ['SAS_MODELPATH'] = plugin_dir

def prepare():
# Don't create *.pyc files
sys.dont_write_bytecode = True

# find the directories for the source and build
from distutils.util import get_platform
root = abspath(dirname(realpath(__file__)))

platform = '%s-%s' % (get_platform(), sys.version[:3])
build_path = joinpath(root, 'build', 'lib.' + platform)
# Turn numpy warnings into errors
#import numpy; numpy.seterr(all='raise')

# Notify the help menu that the Sphinx documentation is in a different
# place than it otherwise would be.
os.environ['SASVIEW_DOC_PATH'] = joinpath(build_path, "doc")

try:
import periodictable
except ImportError:
addpath(joinpath(root, '..', 'periodictable'))
# Find the directories for the source and build
root = abspath(dirname(realpath(__file__)))

try:
import bumps
except ImportError:
addpath(joinpath(root, '..', 'bumps'))
# TODO: Do we prioritize the sibling repo or the installed package?
# TODO: Can we use sasview/run.py from a distributed sasview.exe?
# Put supporting packages on the path if they are not already available.
for sibling in ('periodictable', 'bumps', 'sasdata', 'sasmodels'):
try:
import_module(sibling)
except:
addpath(joinpath(root, '..', sibling))

# Put the source trees on the path
addpath(joinpath(root, 'src'))

# sasmodels on the path
addpath(joinpath(root, '../sasmodels/'))

# == no more C sources so no need to build project to run it ==
# Leave this snippet around in case we add a compile step later.
#from distutils.util import get_platform
#platform = '%s-%s' % (get_platform(), sys.version[:3])
#build_path = joinpath(root, 'build', 'lib.' + platform)
## Build project if the build directory does not already exist.
#if not os.path.exists(build_path):
# import subprocess
# with cd(root):
# subprocess.call((sys.executable, "setup.py", "build"), shell=False)

# Notify the help menu that the Sphinx documentation is in a different
# place than it otherwise would be.
docpath = joinpath(root, 'docs', 'sphinx-docs', '_build', 'html')
os.environ['SASVIEW_DOC_PATH'] = docpath

if __name__ == "__main__":
# Need to add absolute path before actual prepare call,
# so logging can be done during initialization process too
root = abspath(dirname(realpath(sys.argv[0])))

addpath(joinpath(root, 'src'))
prepare()

# Run the UI conversion tool when executed from script. This has to
# happen after prepare() so that sas.qtgui is on the path.
import sas.qtgui.convertUI
setup_sasmodels()

from sas.qtgui.MainWindow.MainWindow import run_sasview
run_sasview()
#logger.debug("Ending SASVIEW in debug mode.")
import sas.cli
sys.exit(sas.cli.main(logging="development"))
4 changes: 2 additions & 2 deletions src/sas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from sas.system.version import __version__

from sas.system import config, user
from sas.system import config

__all__ = ['config']

# TODO: fix logger-config circular dependency
# Load the config file
config.load()

184 changes: 184 additions & 0 deletions src/sas/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#
#Also see sasview\src\sas\qtgui\Perspectives\Fitting\media\cli.rst
#
"""
**SasView command line interface**
This functionality is under development. Interactive sessions do not yet
work in the Windows console.
**Usage:**
sasview [flags]
*Run SasView. If no flag is given, or -q or -V are given, this will start
the GUI.*
sasview [flags] script [args...]
*Run a python script using the installed SasView libraries [passing
optional arguments]*
sasview [flags] -m module [args...]
*Run a SasView/Sasmodels/Bumps module as main [passing optional arguments]*
sasview [flags] -c "python statements" [args...]
*Execute python statements using the installed SasView libraries*
sasview -V
*Print sasview version and exit.*
**Flags:**
-i, --interactive. *Enter an interactive session after command/module/script.*
-o, --console. *Open a console to show command output. (Windows only)*
-q, --quiet. *Suppress startup messages on interactive console.*
Note: On Windows any console output is ignored by default. You can either
open a console to show the output with the *-o* flag or redirect output to
a file using something like *sasview ... > output.txt*.
"""
import sys

# TODO: Support dropping datafiles onto .exe?
# TODO: Maybe use the bumps cli with project file as model?

import argparse

def parse_cli(argv):
"""
Parse the command argv returning an argparse.Namespace.
* version: bool - print version
* command: str - string to exec
* module: str - module to run as main
* interactive: bool - run interactive
* args: list[str] - additional arguments, or script + args
"""
parser = argparse.ArgumentParser()
parser.add_argument("-V", "--version", action='store_true',
help="Print sasview version and exit")
parser.add_argument("-m", "--module", type=str,
help="Run module with remaining arguments sent to main")
parser.add_argument("-c", "--command", type=str,
help="Execute command")
parser.add_argument("-i", "--interactive", action='store_true',
help="Start interactive interpreter after running command")
parser.add_argument("-o", "--console", action='store_true',
help="Open console to display output (windows only)")
parser.add_argument("-q", "--quiet", action='store_true',
help="Don't print banner when entering interactive mode")
parser.add_argument("args", nargs="*",
help="script followed by args")

# Special case: abort argument processing after -m or -c.
have_trigger = False
collect_rest = False
keep = []
rest = []
for arg in argv[1:]:
# Append argument to the parser argv or collect them as extra args.
if collect_rest:
rest.append(arg)
else:
keep.append(arg)
# For an action that needs an argument (e.g., -m module) we need
# to keep the next argument for the parser, but the remaining arguments
# get collected as extra args. Trigger and collect will happen in one
# step if the trigger requires no args or if the arg was provided
# with the trigger (e.g., -mmodule)
if have_trigger:
collect_rest = True
if arg.startswith('-m') or arg.startswith('--module'):
have_trigger = True
collect_rest = arg not in ('-m', '--module')
elif arg.startswith('-c') or arg.startswith('--command'):
have_trigger = True
collect_rest = arg not in ('-c', '--command')

opts = parser.parse_args(keep)
if collect_rest:
opts.args = rest
return opts

def main(logging="production"):
from sas.system import log
from sas.system import lib
from sas.system import console

# I/O redirection for the windows console. Need to do this early so that
# output will be displayed on the console. Presently not working for
# for production (it always opens the console even if it is not needed)
# so require "sasview con ..." to open the console. Not an infamous
# "temporary fix" I hope...
if "-i" in sys.argv[1:] or "-o" in sys.argv[1:]:
console.setup_console()

# Eventually argument processing might affect logger or config, so do it first
cli = parse_cli(sys.argv)

# Setup logger and sasmodels
if logging == "production":
log.production()
elif logging == "development":
log.development()
else:
raise ValueError(f"Unknown logging mode \"{logging}\"")
lib.setup_sasmodels()
lib.setup_qt_env() # Note: does not import any gui libraries

if cli.version: # -V
import sas
print(f"SasView {sas.__version__}")
# Exit immediately after -V.
return 0

context = {'exit': sys.exit}
if cli.module: # -m module [arg...]
import runpy
# TODO: argv[0] should be the path to the module file not the dotted name
sys.argv = [cli.module, *cli.args]
context = runpy.run_module(cli.module, run_name="__main__")
elif cli.command: # -c "command"
sys.argv = ["-c", *cli.args]
exec(cli.command, context)
elif cli.args: # script [arg...]
import runpy
sys.argv = cli.args
context = runpy.run_path(cli.args[0], run_name="__main__")
elif not cli.interactive: # no arguments so start the GUI
from sas.qtgui.MainWindow.MainWindow import run_sasview
# sys.argv is unchanged
# Maybe hand cli.quiet to run_sasview?
run_sasview()
return 0 # don't drop into the interactive interpreter

# TODO: Start interactive with ipython rather than normal python
# For ipython use:
# from IPython import start_ipython
# sys.argv = ["ipython", *args]
# sys.exit(start_ipython())
if cli.interactive:
import code
# The python banner is something like
# f"Python {sys.version} on {platform.system().lower()}"
# where sys.version contains "VERSION (HGTAG, DATE)\n[COMPILER]"
# We are replacing it with something that includes the sasview version.
if cli.quiet:
exitmsg = banner = ""
else:
import platform
import sas
# Form dotted python version number out of sys.version_info
major, minor, micro = sys.version_info[:3]
sasview_ver = f"SasView {sas.__version__}"
python_ver = f"Python {major}.{minor}.{micro}"
os_ver = platform.system()
banner = f"{sasview_ver} for {python_ver} on {os_ver}"
exitmsg = ""
code.interact(banner=banner, exitmsg=exitmsg, local=context)

return 0

if __name__ == "__main__":
sys.exit(main())

0 comments on commit c31aeed

Please sign in to comment.