1
0
Fork 0

The rest of the script?

Because two commits are sufficient :-)
master
LEdoian 5 years ago
parent cd1952bedc
commit 7a1b7f4dc1

@ -10,14 +10,25 @@ import sys
# - Let the user decide, what to run / edit / split / skip # - Let the user decide, what to run / edit / split / skip
# - Run the commands remotely # - Run the commands remotely
# Usage: $0 remote.host.name md1 md2 ... # Usage: $0 remote.host.name md1 ...
# TODO: implement it
host: str
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} hostname file1.md ...")
exit(1)
# Here I would use Haskell's pointfree functions, so that host is implicitly passed...
# Never mind, it will be a global variable NOTE THIS!!!
global host
host = sys.argv[1]
for file in sys.argv[2:]:
process_file(file)
@dataclass @dataclass
class Command: class Command:
user: str user: str
directory: str directory: Any[str, None]
command: str command: str
context: str context: str
@ -40,54 +51,136 @@ def extract_snippets(pd: t.Pandoc) -> Sequence[Tuple[str, str]]:
last_block = stringify(block) last_block = stringify(block)
return result return result
def parse_snippet(s: Tuple[str, str]) -> Sequence[Sequence[Command]]: def parse_snippet(s: Tuple[str, str]) -> Sequence[Command]:
# We need to preserve the groups of commands, so we return a list of groups (lists) of commands.
# Will return always a list, even if it has only one element. # Will return always a list, even if it has only one element.
# This is where we handle blank lines, comments, cd's and sudo's, so that the Commands in the output are "runnable" # This is where we handle blank lines, comments, cd's and sudo's, so that the Commands in the output are "runnable"
context_prefix = s[1]
# TODO
def user_action(hunk: Sequence[Command]) -> Sequence[Command]: # The original context should be mentioned for all hunks
context = s[0]
# We now check the separate lines and possibly tweak them (e.g. by removing sudo-s)
lines = s[1].split('\n')
directory = None # Don't cd by default
user = 'root' # Connect as root by default (it does not really matter, since the commands are run with sudo's)
result = []
for line in lines:
line = line.strip()
if line.startswith('cd'):
parts = line.split()
assert len(parts) == 2
directory = parts[1]
continue
elif line.startswith('sudo -u'):
parts = line.split(maxsplit=5)
assert parts[3] == '-H', "Insecure sudo?"
user = parts[2]
directory = f'~{user}' # This actually works with SSH. That is nice, since we don't need to know the mapping of
cmd = parts[4]
elif line.startswith('sudo'):
parts = line.split(maxsplit=2)
assert not parts[1].startswith('-'), "Weird sudo"
user = 'root'
cmd = parts[1]
elif line == '':
# Only add it to context for reference
context = f'{context}\n'
continue
else:
# No sudo or cd, just a command.
user = 'root' # Reset to default
cmd = line
result.append(Command(user=user,directory=directory,command=cmd,context=context))
# Let's have the previously processed commands in the context
context = f"{context}\nCMD: {user}:{directory if directory else '??'} $ {cmd}"
return result # I miss the right feature of Nim lang...
def user_action(hunk: Sequence[Command], context=None) -> Sequence[Command]:
# Maybe modify the commands to run. # Maybe modify the commands to run.
# This should have a simple UI: show the hunk with context (ideally in grey), ask what to do, return the wanted command # This should have a simple UI: show the hunk with context (ideally in grey), ask what to do, return the wanted command
# Actions: yes(y): accept input; no(n): skip this command; edit(e): launch $EDITOR or vim on the hunk; quit(q): terminate; split(s): split hunks to individual lines and ask for each one. # Actions: yes(y): accept input; no(n): skip this command; edit(e): launch $EDITOR or vim on the hunk; quit(q): terminate; split(s): split hunks to individual lines and ask for each one.
# TODO
pass # FIXME: We don't implement splitting yet, because it's simple to comment / delete in the editor
# FIXME: We only show given context and don't care for the command's one; that's wrong.
def stringify_hunk(hunk):
result = ''
for cmd in hunk:
global host
result += f'SSH: {cmd.user}@{host} {cmd.directory} $ {cmd.command}\n'
# TODO: Print context in color (gray?)
print('-------------------------------------------')
print(context)
print()
print(stringify_hunk(hunk))
# TODO: Print prompt in color (green?)
answer = input("What to do: edit(e), run(y), skip(n)?")
if answer.startswith(('e', 'E')):
import tempfile
with tempfile.NamedTemporaryFile() as tmpfile:
# Fill the file with data
for cmd in hunk:
tmpfile.file.write(f"{cmd.user}\t{cmd.directory}\t{cmd.command}\n")
tmpfile.file.flush() # Should not be needed, but better safe than sorry
# Let the user edit the file
import os
editor = os.getenv('EDITOR')
if editor is None:
# Fallback to vim
editor = 'vim'
import subprocess
subprocess.run([editor, tmpfile.name])
# Read the file back
new_hunk = []
for line in tmpfile.file.readlines():
parts = line.split('\t', maxsplit=3)
new_hunk.append(Command(user=parts[0],directory=parts[1],command=parts[2],context=None)
# Python has no goto for restarting, but we can recurse
return user_action(new_hunk, context)
elif answer.startswith(('y', 'Y')):
return hunk
elif answer.startswith(('n', 'N')):
return []
def run_command(cmd: Command): def run_command(cmd: Command):
# Generate and run the SSH command. # Generate and run the SSH command.
global host
local_command = ['ssh', host]
if cmd.user is not None:
local_command.extend(['-l', cmd.user])
if cmd.directory is not None:
cmd.command += f'cd {cmd.directory}; {cmd.command}'
local_command.append(cmd.command)
# Run
import subprocess
process = subprocess.run(local_command)
if process.returncode != 0:
# TODO: Print in color (red?)
answer = input(f"Command {process.args} failed with return code {process.returncode}. Continue? ")
if not answer.startswith(('Y', 'y', 'a', 'A')): # We are Czech, so "ano" is a valid answer :-)
print("Bailing out")
sys.exit(1)
def process_file(fn: str): def process_file(fn: str):
""" """
To be called upon each file on the command line. This function should perform all the steps needed in order to remotely run a parsed markdown, id est the three parts above. To be called upon each file on the command line. This function should perform all the steps needed in order to remotely run a parsed markdown, id est the three parts above.
""" """
# TODO pd = pandoc.read(open(fn))
pass snippets = extract_snippets(pd)
for snip in snippets:
cmds = parse_snippet(snip)
# FIXME: tohle má být parametr cmds = user_action(cmds)
TARGET="nothing.nowhere.invalid" for cmd in cmds:
run_command(cmd)
def format_command(command, user=None, directory=None, target=None): -> str
cmd = "ssh " if __name__ == '__main__':
main()
if user is not None:
cmd += f"{user}@"
if target is None:
global TARGET
cmd += TARGET
else:
cmd += target
# Be safe:
cmd += " set -e ';' "
if directory is not None
cmd += f"cd '{directory}' ';' "
cmd += command
return cmd

Loading…
Cancel
Save