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

new feature: Anchors #252

Open
wants to merge 3 commits into
base: main
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
7 changes: 7 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ unsets an environment variable.

Whether to echo comments or not. If enabled, non-magic comments will be echoed back in bold yellow before each prompt. This can be useful for providing some annotations for yourself and the audience.

#doitlive anchor: <name>
************************

anchors can be used to mark sections in the .sh file.
Command line arguments ``--from-anchor`` and ``--to-anchor`` can then be used to restrict the play to just the commands between those 2 anchors.
Each of those is optional, and if missing, will be the start and end of the file (as normal)


Python mode
-----------
Expand Down
54 changes: 47 additions & 7 deletions doitlive/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
OPTION_RE = re.compile(
r"^#\s?doitlive\s+"
r"(?P<option>prompt|shell|alias|env|speed"
r"|unalias|unset|commentecho):\s*(?P<arg>.+)$"
r"|unalias|unset|commentecho|anchor):\s*(?P<arg>.+)$"
)

TESTING = False
Expand Down Expand Up @@ -61,10 +61,13 @@ def __init__(
extra_commands=None,
test_mode=False,
commentecho=False,
boundaries=(None, None)
):
aliases = aliases or []
envvars = envvars or []
extra_commands = extra_commands or []
# Flag that defines if any command should be processed
do_process = True if boundaries[0] is None else False
dict.__init__(
self,
shell=shell,
Expand All @@ -75,8 +78,10 @@ def __init__(
extra_commands=extra_commands,
test_mode=test_mode,
commentecho=commentecho,
boundaries=boundaries,
do_process=do_process
)

def add_alias(self, alias):
self["aliases"].append(alias)

Expand All @@ -94,6 +99,12 @@ def set_template(self, template):

def set_shell(self, shell):
self["shell"] = shell

def passed_anchor(self, anchor):
if anchor == self["boundaries"][0]:
self["do_process"] = True
if anchor == self["boundaries"][1]:
self["do_process"] = False

def _remove_var(self, key, variable):
for each in self[key]:
Expand All @@ -115,7 +126,6 @@ def commentecho(self, doit=None):
self["commentecho"] = doit in self.TRUTHY
return self["commentecho"]


# Map of option names => function that modifies session state
OPTION_MAP = {
"prompt": lambda state, arg: state.set_template(arg),
Expand All @@ -126,6 +136,7 @@ def commentecho(self, doit=None):
"unalias": lambda state, arg: state.remove_alias(arg),
"unset": lambda state, arg: state.remove_envvar(arg),
"commentecho": lambda state, arg: state.commentecho(arg),
"anchor": lambda state, arg: state.passed_anchor(arg),
}

SHELL_RE = re.compile(r"```(python|ipython)")
Expand All @@ -149,6 +160,8 @@ def run(
quiet=False,
test_mode=False,
commentecho=False,
from_anchor=None,
to_anchor=None
):
"""Main function for "magic-running" a list of commands."""
if not quiet:
Expand All @@ -167,6 +180,7 @@ def run(
speed=speed,
test_mode=test_mode,
commentecho=commentecho,
boundaries=(from_anchor, to_anchor)
)

i = 0
Expand All @@ -175,6 +189,7 @@ def run(
i += 1
if not command:
continue

is_comment = command.startswith("#")
if not is_comment:
command_as_list = shlex.split(command)
Expand All @@ -189,8 +204,11 @@ def run(
func = OPTION_MAP[option]
func(state, arg)
elif state.commentecho():
comment = command.lstrip("#")
secho(comment, fg="yellow", bold=True)
if state["do_process"]:
comment = command.lstrip("#")
secho(comment, fg="yellow", bold=True)
continue
elif not state["do_process"]:
continue
# Handle 'export' and 'alias' commands by storing them in SessionState
elif command_as_list and command_as_list[0] in ["alias", "export"]:
Expand Down Expand Up @@ -240,6 +258,10 @@ def run(
i -= stealthmode(state, goto_stealthmode)
echo_prompt(state["prompt_template"])
wait_for(RETURNS)
if not state['do_process']:
secho("NO COMMAND WAS PROCESSED. Anchors provided were probably not found", fg="red", bold=True)
return

if not quiet:
secho("FINISHED SESSION", fg="yellow", bold=True)

Expand Down Expand Up @@ -391,6 +413,22 @@ def completion():
"--envvar", "-e", metavar="<envvar>", multiple=True, help="Adds a session variable."
)

ANCHOR_FROM_OPTION = click.option(
"--from-anchor",
"-f",
metavar="<anchor_name>",
type=str,
help="Start point for session, as marked in the script (otherwise, start of file)"
)

ANCHOR_TO_OPTION = click.option(
"--to-anchor",
"-t",
metavar="<anchor_name>",
type=str,
help="End point for session, as marked in the script (otherwise, end of file)"
)


def _compose(*functions):
def inner(func1, func2):
Expand All @@ -401,15 +439,15 @@ def inner(func1, func2):

# Compose the decorators into "bundled" decorators
player_command = _compose(
QUIET_OPTION, SHELL_OPTION, SPEED_OPTION, PROMPT_OPTION, ECHO_OPTION
QUIET_OPTION, SHELL_OPTION, SPEED_OPTION, PROMPT_OPTION, ECHO_OPTION, ANCHOR_FROM_OPTION, ANCHOR_TO_OPTION
)
recorder_command = _compose(SHELL_OPTION, PROMPT_OPTION, ALIAS_OPTION, ENVVAR_OPTION)


@player_command
@click.argument("session_file", type=click.File("r", encoding="utf-8"))
@cli.command()
def play(quiet, session_file, shell, speed, prompt, commentecho):
def play(quiet, session_file, shell, speed, prompt, commentecho, from_anchor, to_anchor):
"""Play a session file."""
run(
session_file.readlines(),
Expand All @@ -419,6 +457,8 @@ def play(quiet, session_file, shell, speed, prompt, commentecho):
test_mode=TESTING,
prompt_template=prompt,
commentecho=commentecho,
from_anchor=from_anchor,
to_anchor=to_anchor
)


Expand Down
1 change: 1 addition & 0 deletions doitlive/keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ def magicrun(
speed=1,
test_mode=False,
commentecho=False,
**kwargs
):
"""Echo out each character in ``text`` as keyboard characters are pressed,
wait for a RETURN keypress, then run the ``text`` in a shell context.
Expand Down
42 changes: 42 additions & 0 deletions examples/anchors.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#doitlive shell: /bin/bash
#doitlive prompt: default
#doitlive speed: 2
#doitlive commentecho: true

echo 'Hello there!'

echo "Let's get started. There is an anchor named 'anchor1 after this string."

#doitlive anchor: start

echo "There is an anchor 'start' before this line. Any commands before it will not have been executed, if --from_anchor was set to 'start' at the command line"

doitlive -h

# doitlive anchor: themes

doitlive themes -p

clear

# doitlive anchor: python

# And now for something completely different

echo 'We can even enter a Python console'

```python
list = [2, 4, 6, 8]
sum = 0
for num in list:
sum = sum + num

print("The sum is: {sum}".format(sum=sum))
```

#doitlive speed: 2
echo 'Pretty neat, eh?'

echo 'Did you notice that the speed changed?'

echo 'For full docs, check out:' $DOCS_URL