Skip to content
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

cli.put: add --strip to remove comments and docstrings before copying #108

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
35 changes: 23 additions & 12 deletions ampy/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ def ls(directory, long_format, recursive):
@cli.command()
@click.argument("local", type=click.Path(exists=True))
@click.argument("remote", required=False)
def put(local, remote):
@click.option("--strip", "-s", is_flag=True, help="Strip docstrings and comments")
def put(local, remote, strip):
"""Put a file or folder and its contents on the board.

Put will upload a local file or folder to the board. If the file already
Expand Down Expand Up @@ -247,12 +248,28 @@ def put(local, remote):
# Use the local filename if no remote filename is provided.
if remote is None:
remote = os.path.basename(os.path.abspath(local))
board_files = files.Files(_board)

def copy_file(local_filepath, remote_filepath):
with open(local_filepath, "rb") as infile:
contents = infile.read()

# try to strip the python file if requested
if strip and local_filepath.endswith(".py"):
try:
contents = files.strip_docstrings_and_comments(contents)
except:
# not a hard error, just push the old contents
print("Warning: could not strip", filepath)

# copy the (potentially stripped) contents to the board
board_files.put(remote_filepath, contents)

# Check if path is a folder and do recursive copy of everything inside it.
# Otherwise it's a file and should simply be copied over.
if os.path.isdir(local):
# Directory copy, create the directory and walk all children to copy
# over the files.
board_files = files.Files(_board)
for parent, child_dirs, child_files in os.walk(local, followlinks=True):
# Create board filesystem absolute path to parent directory.
remote_parent = posixpath.normpath(
Expand All @@ -266,17 +283,11 @@ def put(local, remote):
pass
# Loop through all the files and put them on the board too.
for filename in child_files:
with open(os.path.join(parent, filename), "rb") as infile:
remote_filename = posixpath.join(remote_parent, filename)
board_files.put(remote_filename, infile.read())


filepath = os.path.join(parent, filename)
remote_filepath = posixpath.join(remote_parent, filename)
copy_file(filepath, remote_filepath)
else:
# File copy, open the file and copy its contents to the board.
# Put the file on the board.
with open(local, "rb") as infile:
board_files = files.Files(_board)
board_files.put(remote, infile.read())
copy_file(local, remote)


@cli.command()
Expand Down
42 changes: 42 additions & 0 deletions ampy/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,48 @@
# bridges usually have very small buffers.


def strip_docstrings_and_comments(contents):
"""Removes docstrings and comments from python code.

The contents are converted to an AST, docstrings are removed, then it is
converted back into code.

The resultant code may look a little different afterwards, but should be
functionally equivalent.
"""
relevant_types = (
ast.FunctionDef,
ast.ClassDef,
ast.AsyncFunctionDef,
ast.Module,
)
parsed = ast.parse(contents)
for node in ast.walk(parsed):
# Narrow down the search until we know we have a node with a docstring
if not isinstance(node, relevant_types):
continue
if not node.body:
continue
first_child = node.body[0]
if not isinstance(first_child, ast.Expr):
continue
if not hasattr(first_child, "value"):
continue
if not isinstance(first_child.value, ast.Str):
continue

# If we got here, then first_child is a docstring. Let's remove it
del node.body[0]

# If that was the only element and this was not a module, then we need
# to add a pass to maintain valid python syntax
if not node.body and not isinstance(node, ast.Module):
node.body.append(ast.Pass())

# return the code generated by this pruned down AST
return ast.unparse(parsed)


class DirectoryExistsError(Exception):
pass

Expand Down