diff --git a/pyproject.toml b/pyproject.toml index 47c125c4e26..d721451c763 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,6 +85,9 @@ R = [ file = "README.md" content-type = "text/markdown" +[project.scripts] +sage = "sage.cli:main" + [tool.conda-lock] platforms = [ 'osx-64', 'linux-64', 'linux-aarch64', 'osx-arm64' diff --git a/src/sage/cli/__init__.py b/src/sage/cli/__init__.py new file mode 100644 index 00000000000..adae8b92585 --- /dev/null +++ b/src/sage/cli/__init__.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import argparse +import logging +import sys + +from sage.cli.interactive_shell_cmd import InteractiveShellCmd +from sage.cli.options import CliOptions +from sage.cli.version_cmd import VersionCmd + + +def main() -> int: + input_args = sys.argv[1:] + parser = argparse.ArgumentParser( + prog="sage", + description="If no command is given, starts the interactive interpreter where you can enter statements and expressions, immediately execute them and see their results.", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + default=False, + help="print additional information", + ) + + VersionCmd.extend_parser(parser) + + if not input_args: + InteractiveShellCmd(CliOptions()).run() + + args = parser.parse_args(input_args) + options = CliOptions(**vars(args)) + + logging.basicConfig(level=logging.DEBUG if options.verbose else logging.INFO) + + return InteractiveShellCmd(options).run() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/sage/cli/interactive_shell_cmd.py b/src/sage/cli/interactive_shell_cmd.py new file mode 100644 index 00000000000..22f5468374c --- /dev/null +++ b/src/sage/cli/interactive_shell_cmd.py @@ -0,0 +1,25 @@ +from sage.cli.options import CliOptions + + +class InteractiveShellCmd: + def __init__(self, options: CliOptions): + r""" + Initialize the command. + """ + self.options = options + + def run(self) -> int: + r""" + Start the interactive shell. + """ + # Display startup banner. Do this before anything else to give the user + # early feedback that Sage is starting. + from sage.misc.banner import banner + + banner() + + from sage.repl.interpreter import SageTerminalApp + + app = SageTerminalApp.instance() + app.initialize([]) + return app.start() # type: ignore diff --git a/src/sage/cli/meson.build b/src/sage/cli/meson.build new file mode 100644 index 00000000000..e3ac31698e5 --- /dev/null +++ b/src/sage/cli/meson.build @@ -0,0 +1,8 @@ + +py.install_sources( + '__init__.py', + 'interactive_shell_cmd.py', + 'options.py', + 'version_cmd.py', + subdir: 'sage/cli', +) diff --git a/src/sage/cli/options.py b/src/sage/cli/options.py new file mode 100644 index 00000000000..9606b5b1988 --- /dev/null +++ b/src/sage/cli/options.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass + + +@dataclass +class CliOptions: + """ + A TypedDict for command-line interface options. + + Attributes: + verbose (bool): Indicates whether verbose output is enabled. + """ + + verbose: bool = False diff --git a/src/sage/cli/version_cmd.py b/src/sage/cli/version_cmd.py new file mode 100644 index 00000000000..e40d3f8f1d5 --- /dev/null +++ b/src/sage/cli/version_cmd.py @@ -0,0 +1,25 @@ +import argparse + +from sage.version import version + + +class VersionCmd: + @staticmethod + def extend_parser(parser: argparse.ArgumentParser): + r""" + Extend the parser with the version command. + + INPUT: + + - ``parsers`` -- the parsers to extend. + + OUTPUT: + + - the extended parser. + """ + parser.add_argument( + "--version", + action="version", + version=version, + help="print the version number and exit", + ) diff --git a/src/sage/meson.build b/src/sage/meson.build index 80b2a030518..72700de1ef1 100644 --- a/src/sage/meson.build +++ b/src/sage/meson.build @@ -142,3 +142,4 @@ subdir('symbolic') subdir('tests') subdir('dynamics') subdir('sat') +subdir('cli') diff --git a/src/sage/misc/banner.py b/src/sage/misc/banner.py index af46d711721..0a4395d3a2b 100644 --- a/src/sage/misc/banner.py +++ b/src/sage/misc/banner.py @@ -27,6 +27,9 @@ def version(): sage: version() 'SageMath version ..., Release Date: ...' """ + from sage.misc.superseded import deprecation + + deprecation(1, "Use sage.version instead.") return SAGE_VERSION_BANNER