diff options
author | Olivier Gayot <olivier.gayot@sigexec.com> | 2020-03-21 00:06:56 +0100 |
---|---|---|
committer | Olivier Gayot <olivier.gayot@sigexec.com> | 2020-03-21 12:42:21 +0100 |
commit | 3da7ccbf94fc9dfeead62b0091b0f365bf89c0ef (patch) | |
tree | 72e501822223198f83360c3ad3096ba73fc60217 | |
parent | 609fad38b5818806323757d816d9fac5ba3c8a4c (diff) |
Handle quoting, escapements and IFS correctly
Signed-off-by: Olivier Gayot <olivier.gayot@sigexec.com>
-rwxr-xr-x | vish.py | 109 |
1 files changed, 98 insertions, 11 deletions
@@ -4,28 +4,115 @@ import os import re import argparse +IFS = (" ", "\t", "\n") + + class EOFException(Exception): pass +class Argument(): + def __init__(self, value: str, allow_expansion=True): + pass + + def expand(): + pass + + +class Pipeline(): + def __init__(self, instructions: list=[]): + self.instructions = instructions + + def execute(self): + for instruction in self.instructions: + instruction.execute() + + class Instruction(): - def __init__(self, string: str): - # TODO handle quoting and escapments - keywords = string.split() - self.prog = keywords[0] - self.arguments = keywords[1:] + def __init__(self, tokens: list=[]): + self.prog = tokens[0] + self.args = tokens[1:] def execute(self): cpid = os.fork() if cpid == 0: - os.execvp(self.prog, [self.prog] + self.arguments) + os.execvp(self.prog, [self.prog] + self.args) else: os.waitpid(cpid, 0) -def read_instruction(fh): - # TODO We should be reading an instruction, not a line. +class PipelineParser(): + def read_escaped(self, string_iterator): + return next(string_iterator) + + def read_literal(self, string_iterator): + literal = "" + while True: + c = next(string_iterator) + if c != "'": + literal += c + else: + return literal + + def read_quoted(self, string_iterator): + # TODO handle substitutions + quoted = "" + while True: + c = next(string_iterator) + if c == "\\": + quoted += self.read_escaped(string_iterator) + elif c != '"': + quoted += c + else: + return quoted + + + def get_next_token(self, string_iterator): + token = None + while True: + try: + c = next(string_iterator) + except StopIteration: + break + + # Skip leading whitespaces + if token is None and c in IFS: + continue + elif token is None: + token = "" + + if c == "'": + token += self.read_literal(string_iterator) + elif c == "\\": + token += self.read_escaped(string_iterator) + elif c == "\"": + token += self.read_quoted(string_iterator) + elif c in IFS: + return token + else: + token += c + + return token + + def tokenize(self, string_iterator): + while True: + token = self.get_next_token(string_iterator) + if token is None: + break + yield token + + def parse(self, line): + tokens = list(self.tokenize(iter(line))) + return Pipeline([Instruction(tokens)]) + + +def read_next_pipeline(fh): + # TODO Support multiple pipelines per line + # TODO Support instructions spawning multiple lines + + parser = PipelineParser() + while True: line = fh.readline() if line == "": @@ -36,7 +123,7 @@ def read_instruction(fh): if not line.startswith("#") and not line == "": break - return Instruction(line) + return parser.parse(line) def main(arguments): @@ -44,8 +131,8 @@ def main(arguments): with open(script, mode="r", encoding="utf-8") as fh: while True: try: - instruction = read_instruction(fh) - instruction.execute() + pipeline = read_next_pipeline(fh) + pipeline.execute() except EOFException: break |