Skip to content

Added tab completion for mbed cli on bash #450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ To uninstall mbed CLI, simply run:
pip uninstall mbed-cli
```

### Adding Bash tab completion

To install mbed-cli bash tab completion navigate to the `tools/bash_completion` directory. Then copy the `mbed` script into your `/etc/bash_completion.d/` or `/usr/local/etc/bash_completion.d` directory and reload your terminal.

[Full documentation here](tools/bash_completion/install.md)

## Quickstart video

<span class="images">[![Video tutorial](http://img.youtube.com/vi/PI1Kq9RSN_Y/0.jpg)](https://www.youtube.com/watch?v=PI1Kq9RSN_Y)</span>
Expand Down
218 changes: 218 additions & 0 deletions tools/bash_completion/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#!/usr/bin/env python
# Michael Bartling ([email protected])

from collections import defaultdict
import pystache
import re
import subprocess

# Top level --version is a pain to deal with so ignoring for now
# This one extracts single commands and the help txt
commandRegex = r"^\s+(?P<command>\w+)\s+(?P<helptxt>[a-zA-Z ]*)$"

# Why the hell do spaces get regexed in command1 ?
subcommandRegex = r"^\s+(?P<command1>-+[a-zA-Z_\-]+(?P<modifier1>\s+[A-Z_\-]+)?)"\
r"(?P<command2>,\s+-+[a-zA-Z_-]+(?P<modifier2>\s+[A-Z_-]+)?)?"\
r"\s+(?P<helptxt>.*)$"


def getHelpTxt(command=None):
if command:
p = subprocess.Popen(["mbed", command, "-h"], stdout=subprocess.PIPE)
else:
p = subprocess.Popen(["mbed", "-h"], stdout=subprocess.PIPE)
out, err = p.communicate()
return out

def getTargetCode():
txt = ''
with open("templates/target.tmplt") as fp:
txt = fp.read()
return txt

def getToolchainCode():
txt = ''
with open("templates/toolchain.tmplt") as fp:
txt = fp.read()
return txt

def getSCMCode():
txt = ''
with open("templates/scm.tmplt") as fp:
txt = fp.read()
return txt

def getIDECode():
txt = ''
with open("templates/ide.tmplt") as fp:
txt = fp.read()
return txt

def getProtocolCode():
txt = ''
with open("templates/protocol.tmplt") as fp:
txt = fp.read()
return txt

def parseCommands():
commands = defaultdict(defaultdict)
commands["COMMAND"] = []
helpTxt = getHelpTxt()
# print helpTxt
for line in helpTxt.split('\n'):
match = re.search(commandRegex, line)
if match:
g = match.groupdict()
commands[g["command"]]["helptxt"] = g["helptxt"]
commands[g["command"]]["subcommands"] = []

# Subcommand mustache generation
commands[g["command"]]["DDASH_COMMANDS"] = []
commands[g["command"]]["DASH_COMMANDS"] = []
commands[g["command"]]["COMMAND"] = g["command"]

commands[g["command"]]["HAVE_PREV"] = {"PREV_CASE": []}

# Main function generation
commands["COMMAND"].append({"name": g["command"]})

for commandKey in commands:
# Skip
if commandKey == "COMMAND":
continue

helpTxt = getHelpTxt(commandKey)
for line in helpTxt.split('\n'):
match = re.search(subcommandRegex, line)
if match:
commandMatch = match.groupdict()

# Clean up the subcommands
command1 = commandMatch["command1"]
command2 = commandMatch["command2"]

if command1:
command1 = re.sub(",", "", command1)
command1.strip()
command1 = command1.split()[0]
if command2:
command2 = re.sub(",", "", command2)
command2.strip()
command2 = command2.split()[0]

# Not sure why the cleaning is even necessary,
# the regex looks correct
commandMatch["command1"] = command1
commandMatch["command2"] = command2

commands[commandKey]["subcommands"].append(commandMatch)

# Push format for mustache
if command1 and '--' in command1:
commands[commandKey]["DDASH_COMMANDS"].append(
{"name": command1})
if command2 and '--' in command2:
commands[commandKey]["DDASH_COMMANDS"].append(
{"name": command2})

if command1:
m = re.match("^-[a-zA-Z]{1,2}", command1)
if m:
commands[commandKey]["DASH_COMMANDS"].append(
{"name": command1})
else:
command1 = ""

if command2:
m = re.match("^-[a-zA-Z]{1,2}", command2)
if m:
commands[commandKey]["DASH_COMMANDS"].append(
{"name": command2})
else:
command2 = ""

# Adding the dependent command handlers
if "target" in command1 or "target" in command2:
commands[commandKey]["HAVE_PREV"]["PREV_CASE"].append({"case": "|".join(filter(None, [command1, command2])), "code": getTargetCode()})

if "toolchain" in command1 or "toolchain" in command2:
commands[commandKey]["HAVE_PREV"]["PREV_CASE"].append({"case": "|".join(filter(None, [command1, command2])), "code": getToolchainCode()})


if "--ide" in command1 or "--ide" in command2:
commands[commandKey]["HAVE_PREV"]["PREV_CASE"].append({"case": "|".join(filter(None, [command1, command2])), "code": getIDECode()})

if "scm" in command1 or "scm" in command2:
commands[commandKey]["HAVE_PREV"]["PREV_CASE"].append({"case": "|".join(filter(None, [command1, command2])), "code": getSCMCode()})

if "protocol" in command1 or "protocol" in command2:
commands[commandKey]["HAVE_PREV"]["PREV_CASE"].append({"case": "|".join(filter(None, [command1, command2])), "code": getProtocolCode()})

# Adding the dependent command handlers for target and toolchain
if "target" in commandKey:
commands[commandKey]["HAVE_PREV"]["PREV_CASE"].append({"case": commandKey, "code": getTargetCode()})

if "toolchain" in commandKey:
commands[commandKey]["HAVE_PREV"]["PREV_CASE"].append({"case": commandKey, "code": getToolchainCode()})

return commands


def generateMain(commands):
tmplt = ""

txt = []

with open("templates/mbed.tmplt") as fp:
tmplt = fp.read()

txt.append(pystache.render(tmplt, commands))

return txt


def generateCompleters(commands):
tmplt = ""
txt = []

renderer = pystache.Renderer(escape=lambda u: u)

with open("templates/command.tmplt") as fp:
tmplt = fp.read()

for commandKey in commands:
txt.append(renderer.render(tmplt, commands[commandKey]))

# if need to add hacks add them here

return txt


def generateBoilerPlate(commands):
txt = []

with open("templates/boilerplate.tmplt") as fp:
txt.append(fp.read())

return txt


def generateScript(commands):
txt = []

txt.extend(generateBoilerPlate(commands))
txt.extend(generateCompleters(commands))
txt.extend(generateMain(commands))

with open("mbed-completion", "w") as fp:
for x in txt:
fp.write("%s\n" % x)


if __name__ == '__main__':
commands = parseCommands()

# At this point we have a list of all the commands and sub commands
# for each command create a Bash function
# register each subcommand
generateScript(commands)
13 changes: 13 additions & 0 deletions tools/bash_completion/install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Install Guide

## System-wide Installation (root)

- Copy or link the bash completion script, `mbed`, into your `/etc/bash_completion.d` or `/usr/local/etc/bash_completion.d` directory.
- Reopen terminal

## Local Installation

- `mkdir ~/.bash_completion.d && cp mbed ~/.bash_completion.d/`
- `echo "source ~/.bash_completion.d/mbed" >> ~/.bash_profile`
- logout and login

Loading