Skip to content

Commit 39c42bf

Browse files
authored
feat(cli): Adding a new firewheel config edit command (#128)
This PR adds a new command which enables users to easily edit the FIREWHEEL configuration. The `firewheel config edit` command will automatically use the `VISUAL` or `EDITOR` environment variables. Alternatively, users can pass in their own editor of choice (or override the environment variables). This PR also updates the associated documentation.
1 parent 851dc57 commit 39c42bf

File tree

4 files changed

+115
-0
lines changed

4 files changed

+115
-0
lines changed

docs/source/cli/commands.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ Users can interact with the :ref:`command_config` command (i.e. ``firewheel conf
3535
series of sub-commands which enable easily getting/setting various configuration options.
3636

3737

38+
.. _command_config_edit:
39+
40+
config edit
41+
^^^^^^^^^^^
42+
43+
usage: firewheel config edit [-e EDITOR]
44+
45+
Edit the FIREWHEEL configuration with a text editor. The user must set either the VISUAL or EDITOR
46+
environment variable or use the provided flag to override these environment variables.
47+
48+
options:
49+
-e EDITOR, --editor EDITOR
50+
Use the specified text editor.
51+
52+
53+
3854
.. _command_config_get:
3955

4056
config get
@@ -339,3 +355,4 @@ Example:
339355
340356
$ firewheel version
341357
2.6.0
358+

docs/source/cli/helper_docs.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,3 +1508,4 @@ Example
15081508
``firewheel vm resume host.root.net bgp.root.net``
15091509

15101510
``firewheel vm resume --all``
1511+

src/firewheel/cli/configure_firewheel.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import os
12
import cmd
23
import pprint
34
import argparse
45
import operator
6+
import subprocess
57
from inspect import cleandoc
68
from pathlib import Path
79

810
import yaml
11+
from rich.console import Console
912

1013
from firewheel.config import Config
1114
from firewheel.lib.log import Log
@@ -64,6 +67,64 @@ def define_reset_parser(self) -> argparse.ArgumentParser:
6467
)
6568
return parser
6669

70+
def define_edit_parser(self) -> argparse.ArgumentParser:
71+
"""Create an :py:class:`argparse.ArgumentParser` for :ref:`command_config_edit`.
72+
73+
Returns:
74+
argparse.ArgumentParser: The parser needed for :ref:`command_config_edit`.
75+
"""
76+
parser = argparse.ArgumentParser(
77+
description=str(
78+
"Edit the FIREWHEEL configuration with a text editor. "
79+
"The user must set either the VISUAL or EDITOR environment variable "
80+
"or use the provided flag to override these environment variables."
81+
),
82+
prog="firewheel config edit",
83+
add_help=False,
84+
)
85+
parser.add_argument(
86+
"-e",
87+
"--editor",
88+
required=False,
89+
default="",
90+
help="Use the specified text editor.",
91+
)
92+
return parser
93+
94+
def do_edit(self, args: str = "") -> None:
95+
"""
96+
Edit the FIREWHEEL config with the default text editor as determined by
97+
the ``VISUAL`` or ``EDITOR`` environment variables.
98+
If no editor is found an error message is output.
99+
100+
Args:
101+
args (str): A string of arguments passed in by the user.
102+
"""
103+
# Create a Console object for colored output
104+
console = Console()
105+
106+
# Get the parser for the reset command
107+
parser = self.define_edit_parser()
108+
cmd_args = self._handle_parsing(parser, args)
109+
110+
# Check for VISUAL, then EDITOR
111+
editor = cmd_args.editor or os.environ.get("VISUAL") or os.environ.get("EDITOR")
112+
113+
if not editor:
114+
self.help_edit()
115+
return
116+
117+
# Attempt to open the file with the chosen editor
118+
try:
119+
subprocess.run([editor, Config(writable=True).config_path], check=True)
120+
except (FileNotFoundError, subprocess.CalledProcessError):
121+
console.print(
122+
f"Error: Failed to open FIREWHEEL configuration with '{editor}'.\n",
123+
style="bold red",
124+
)
125+
self.help_edit()
126+
return
127+
67128
def do_reset(self, args: str = "") -> None:
68129
"""
69130
Reset the FIREWHEEL configuration to the defaults.
@@ -262,6 +323,18 @@ def get_docs(self) -> str:
262323
command_string += "\n\n"
263324
return command_string
264325

326+
def _help_edit(self) -> str:
327+
"""Help message for the :py:meth:`do_edit` sub-command.
328+
329+
Returns:
330+
str: The help message.
331+
"""
332+
return self.define_edit_parser().format_help()
333+
334+
def help_edit(self) -> None:
335+
"""Print help for the :py:meth:`do_edit` sub-command."""
336+
print(self._help_edit())
337+
265338
def _help_reset(self) -> str:
266339
"""Help message for the :py:meth:`do_reset` sub-command.
267340

src/firewheel/tests/unit/cli/test_cli_configure.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,23 @@ def test_do_get_all(self, mock_stdout):
127127

128128
self.assertEqual(self.old_config, test_config)
129129

130+
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
131+
def test_do_edit_param_invalid(self, mock_stdout):
132+
args = "-e asdf"
133+
self.cli.do_edit(args)
134+
135+
msg = "Error: Failed to open FIREWHEEL configuration with"
136+
self.assertIn(msg, mock_stdout.getvalue())
137+
138+
@unittest.mock.patch.dict(os.environ, {'EDITOR': '', 'VISUAL': ''})
139+
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
140+
def test_do_edit_none(self, mock_stdout):
141+
args = ""
142+
self.cli.do_edit(args)
143+
144+
msg = "Edit the FIREWHEEL configuration"
145+
self.assertIn(msg, mock_stdout.getvalue())
146+
130147
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
131148
def test_emptyline(self, mock_stdout):
132149
# Test the configuration from initialization
@@ -160,6 +177,13 @@ def test_help_set(self, mock_stdout):
160177
msg = "Set a FIREWHEEL configuration."
161178
self.assertIn(msg, mock_stdout.getvalue())
162179

180+
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
181+
def test_help_edit(self, mock_stdout):
182+
self.cli.help_edit()
183+
184+
msg = "Edit the FIREWHEEL configuration"
185+
self.assertIn(msg, mock_stdout.getvalue())
186+
163187
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
164188
def test_help_help(self, mock_stdout):
165189
self.cli.help_help()

0 commit comments

Comments
 (0)