Skip to content

Commit 81a70e0

Browse files
authored
Merge pull request #334 from python-cmd2/alias_commands
Alias commands
2 parents ad76cec + 299c6ea commit 81a70e0

File tree

3 files changed

+93
-34
lines changed

3 files changed

+93
-34
lines changed

cmd2.py

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,9 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, persistent_histor
10521052

10531053
# If a startup script is provided, then add it in the queue to load
10541054
if startup_script is not None:
1055-
self.cmdqueue.append('load {}'.format(startup_script))
1055+
startup_script = os.path.expanduser(startup_script)
1056+
if os.path.exists(startup_script) and os.path.getsize(startup_script) > 0:
1057+
self.cmdqueue.append('load {}'.format(startup_script))
10561058

10571059
############################################################################################################
10581060
# The following variables are used by tab-completion functions. They are reset each time complete() is run
@@ -2105,9 +2107,9 @@ def complete(self, text, state):
21052107

21062108
else:
21072109
# Complete token against aliases and command names
2108-
alias_names = list(self.aliases.keys())
2109-
visible_commands = self.get_visible_commands()
2110-
strs_to_match = alias_names + visible_commands
2110+
alias_names = set(self.aliases.keys())
2111+
visible_commands = set(self.get_visible_commands())
2112+
strs_to_match = list(alias_names | visible_commands)
21112113
self.completion_matches = self.basic_complete(text, line, begidx, endidx, strs_to_match)
21122114

21132115
# Handle single result
@@ -2305,13 +2307,24 @@ def parseline(self, line):
23052307
# Deal with empty line or all whitespace line
23062308
return None, None, line
23072309

2308-
# Handle aliases
2309-
for cur_alias in self.aliases:
2310-
if line == cur_alias or line.startswith(cur_alias + ' '):
2311-
line = line.replace(cur_alias, self.aliases[cur_alias], 1)
2312-
break
2310+
# Make a copy of aliases so we can edit it
2311+
tmp_aliases = list(self.aliases.keys())
2312+
keep_expanding = len(tmp_aliases) > 0
2313+
2314+
# Expand aliases
2315+
while keep_expanding:
2316+
for cur_alias in tmp_aliases:
2317+
keep_expanding = False
23132318

2314-
# Expand command shortcuts to the full command name
2319+
if line == cur_alias or line.startswith(cur_alias + ' '):
2320+
line = line.replace(cur_alias, self.aliases[cur_alias], 1)
2321+
2322+
# Do not expand the same alias more than once
2323+
tmp_aliases.remove(cur_alias)
2324+
keep_expanding = len(tmp_aliases) > 0
2325+
break
2326+
2327+
# Expand command shortcut to its full command name
23152328
for (shortcut, expansion) in self.shortcuts:
23162329
if line.startswith(shortcut):
23172330
# If the next character after the shortcut isn't a space, then insert one
@@ -2734,33 +2747,48 @@ def _cmdloop(self):
27342747
def do_alias(self, arglist):
27352748
"""Define or display aliases
27362749
2737-
Usage: Usage: alias [<name> <value>]
2750+
Usage: Usage: alias [name] | [<name> <value>]
27382751
Where:
2739-
name - name of the alias being added or edited
2740-
value - what the alias will be resolved to
2752+
name - name of the alias being looked up, added, or replaced
2753+
value - what the alias will be resolved to (if adding or replacing)
27412754
this can contain spaces and does not need to be quoted
27422755
27432756
Without arguments, 'alias' prints a list of all aliases in a reusable form which
27442757
can be outputted to a startup_script to preserve aliases across sessions.
27452758
2759+
With one argument, 'alias' shows the value of the specified alias.
2760+
Example: alias ls (Prints the value of the alias called 'ls' if it exists)
2761+
2762+
With two or more arguments, 'alias' creates or replaces an alias.
2763+
27462764
Example: alias ls !ls -lF
2765+
2766+
If you want to use redirection or pipes in the alias, then either quote the tokens with these
2767+
characters or quote the entire alias value.
2768+
2769+
Examples:
2770+
alias save_results print_results ">" out.txt
2771+
alias save_results print_results "> out.txt"
2772+
alias save_results "print_results > out.txt"
27472773
"""
27482774
# If no args were given, then print a list of current aliases
27492775
if len(arglist) == 0:
27502776
for cur_alias in self.aliases:
27512777
self.poutput("alias {} {}".format(cur_alias, self.aliases[cur_alias]))
27522778

2779+
# The user is looking up an alias
2780+
elif len(arglist) == 1:
2781+
name = arglist[0]
2782+
if name in self.aliases:
2783+
self.poutput("alias {} {}".format(name, self.aliases[name]))
2784+
else:
2785+
self.perror("Alias {!r} not found".format(name), traceback_war=False)
2786+
27532787
# The user is creating an alias
2754-
elif len(arglist) >= 2:
2788+
else:
27552789
name = arglist[0]
27562790
value = ' '.join(arglist[1:])
27572791

2758-
# Make sure the alias does not match an existing command
2759-
cmd_func = self._func_named(name)
2760-
if cmd_func is not None:
2761-
self.perror("Alias names cannot match an existing command: {!r}".format(name), traceback_war=False)
2762-
return
2763-
27642792
# Check for a valid name
27652793
for cur_char in name:
27662794
if cur_char not in self.identchars:
@@ -2770,10 +2798,7 @@ def do_alias(self, arglist):
27702798

27712799
# Set the alias
27722800
self.aliases[name] = value
2773-
self.poutput("Alias created")
2774-
2775-
else:
2776-
self.do_help('alias')
2801+
self.poutput("Alias {!r} created".format(name))
27772802

27782803
def complete_alias(self, text, line, begidx, endidx):
27792804
""" Tab completion for alias """
@@ -3698,16 +3723,35 @@ def parsed(self, raw):
36983723
s = self.input_source_parser.transformString(s.lstrip())
36993724
s = self.commentGrammars.transformString(s)
37003725

3701-
# Handle aliases
3702-
for cur_alias in self.aliases:
3703-
if s == cur_alias or s.startswith(cur_alias + ' '):
3704-
s = s.replace(cur_alias, self.aliases[cur_alias], 1)
3705-
break
3726+
# Make a copy of aliases so we can edit it
3727+
tmp_aliases = list(self.aliases.keys())
3728+
keep_expanding = len(tmp_aliases) > 0
37063729

3730+
# Expand aliases
3731+
while keep_expanding:
3732+
for cur_alias in tmp_aliases:
3733+
keep_expanding = False
3734+
3735+
if s == cur_alias or s.startswith(cur_alias + ' '):
3736+
s = s.replace(cur_alias, self.aliases[cur_alias], 1)
3737+
3738+
# Do not expand the same alias more than once
3739+
tmp_aliases.remove(cur_alias)
3740+
keep_expanding = len(tmp_aliases) > 0
3741+
break
3742+
3743+
# Expand command shortcut to its full command name
37073744
for (shortcut, expansion) in self.shortcuts:
37083745
if s.startswith(shortcut):
3709-
s = s.replace(shortcut, expansion + ' ', 1)
3746+
# If the next character after the shortcut isn't a space, then insert one
3747+
shortcut_len = len(shortcut)
3748+
if len(s) == shortcut_len or s[shortcut_len] != ' ':
3749+
expansion += ' '
3750+
3751+
# Expand the shortcut
3752+
s = s.replace(shortcut, expansion, 1)
37103753
break
3754+
37113755
try:
37123756
result = self.main_parser.parseString(s)
37133757
except pyparsing.ParseException:

tests/test_cmd2.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,7 +1549,7 @@ def test_poutput_none(base_app):
15491549
def test_alias(base_app, capsys):
15501550
# Create the alias
15511551
out = run_cmd(base_app, 'alias fake pyscript')
1552-
assert out == normalize("Alias created")
1552+
assert out == normalize("Alias 'fake' created")
15531553

15541554
# Use the alias
15551555
run_cmd(base_app, 'fake')
@@ -1560,10 +1560,15 @@ def test_alias(base_app, capsys):
15601560
out = run_cmd(base_app, 'alias')
15611561
assert out == normalize('alias fake pyscript')
15621562

1563-
def test_alias_with_cmd_name(base_app, capsys):
1564-
run_cmd(base_app, 'alias help eos')
1563+
# Lookup the new alias
1564+
out = run_cmd(base_app, 'alias fake')
1565+
assert out == normalize('alias fake pyscript')
1566+
1567+
def test_alias_lookup_invalid_alias(base_app, capsys):
1568+
# Lookup invalid alias
1569+
out = run_cmd(base_app, 'alias invalid')
15651570
out, err = capsys.readouterr()
1566-
assert "cannot match an existing command" in err
1571+
assert "not found" in err
15671572

15681573
def test_alias_with_invalid_name(base_app, capsys):
15691574
run_cmd(base_app, 'alias @ help')

tests/test_completion.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,16 @@ def test_parseline_strips_line(cmd2_app):
627627
assert args == 'history'
628628
assert line.strip() == out_line
629629

630+
def test_parseline_expands_alias(cmd2_app):
631+
# Create the alias
632+
cmd2_app.do_alias(['fake', 'pyscript'])
633+
634+
line = 'fake foobar.py'
635+
command, args, out_line = cmd2_app.parseline(line)
636+
assert command == 'pyscript'
637+
assert args == 'foobar.py'
638+
assert line.replace('fake', 'pyscript') == out_line
639+
630640
def test_parseline_expands_shortcuts(cmd2_app):
631641
line = '!cat foobar.txt'
632642
command, args, out_line = cmd2_app.parseline(line)

0 commit comments

Comments
 (0)