Skip to content

Commit

Permalink
Merge pull request #450 from mbartling/bash-completion
Browse files Browse the repository at this point in the history
Added tab completion for mbed cli on bash
  • Loading branch information
screamerbg authored Apr 18, 2017
2 parents ae86dc3 + 08febbf commit c45e406
Show file tree
Hide file tree
Showing 14 changed files with 1,129 additions and 0 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ To uninstall mbed CLI, 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

0 comments on commit c45e406

Please sign in to comment.