Skip to content
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
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: python
python:
- "3.7-dev"
script:
- pytest
50 changes: 50 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
GREP

Были рассмотрены ряд библиотек для парсинга аргументов:
* argparse
* optparse
* click
* docopt

В результате был выбран argparse, по ряду причин:
* встроен в стандартную библиотеку и не требует установки
* нет проблем с лицензией
* большая популярность и множество примеров работы
* поддержка опциональных аргументов
* автогенерируемый help по аргументам и использованию команды

Все остальные уступают в том или ином виде, в частности некоторые из них уже устарели (optparse) и считаются deprecated



Интерпретатор командной строки, поддерживающий следующие команды:
* cat [FILE] — вывести на экран содержимое файла;
* echo — вывести на экран свой аргумент (или аргументы);
* wc [FILE] — вывести количество строк, слов и байт в файле;
* pwd — распечатать текущую директорию;
* exit — выйти из интерпретатора.

Архитектура:

Класс IToken - представляет собой интерфейс для токены, поиск которых будет осуществляться в исходном выражении, от него наследуются TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe,
TokenAssignment, TokenWord, которые отвечают токенам в соответствии со своим названием.

В последствии список классов токенов, которые необходимо распарсить передаются в класс, наследованный от интерфейса IParser,
который в свою очередь должен преобразовать входную строчку в набор токенов

Класс IStorage представляет собой интерфейс для представления хранилища для переменных окружения,
его метод evaluate_variables, должен принимать строку и заменять вхождения всех переменных на их значения,
переменные ищутся по регулярному выражению, переданному в конструктор

Команды и интерфейс ICommand представляют собой логику работы отдельной команды, из ключевых методов -
name, который должен вернуть имя команды, по которому будет выполнена подстановка и execute логика самой команды. Команды
реализованы согласно списку в начале файла.

Интерпретатор ICommandInterpreter должен реализовывать преобразование потока токенов в поток команд с помощью
метода retrieve_commands, также при его создании необходимо указать разделитель команд (в нашем случае TokenPipe), а также
команду, которая будет выбрана по умолчанию, если все остальные команды не подошли

IExecutor - аккумулирует все вышеперечисленное и должен преобразовывать входное выражение в результат его исполнения, что делает его
единственный метод execute_expression

![alt text](./class_diagram.png)
Binary file added cli/class_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added cli/src/__init__.py
Empty file.
50 changes: 50 additions & 0 deletions cli/src/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
интерпретатор командной строки, поддерживающий следующие команды:
• cat [FILE] — вывести на экран содержимое файла;
• echo — вывести на экран свой аргумент (или аргументы);
• wc [FILE] — вывести количество строк, слов и байт в файле;
• pwd — распечатать текущую директорию;
• exit — выйти из интерпретатора.
"""

import os
from subprocess import run, PIPE
from src.storage import Storage
from src.commands import CommandCat, CommandEcho, CommandWC, CommandPwd, \
CommandExit, CommandDefault, CommandGrep
from src.tokens import TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, \
TokenAssignment, TokenWord
from src.interpreter import CommandInterpreterWithStorage
from src.pparser import Parser
from src.executor import Executor


def main_loop():
"""
Главный цикл интерпретатора
"""

if os.name == 'nt': # установка кодировки utf-8 для windows
run(['chcp', '65001'], stdout=PIPE, shell=True)

storage = Storage(r'\$[^ \'\"$]+')
commands = [CommandCat, CommandEcho, CommandWC,
CommandPwd, CommandExit, CommandGrep]
token_types = [TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe,
TokenAssignment, TokenWord]

executor = Executor(CommandInterpreterWithStorage
(storage, commands, TokenPipe, CommandDefault),
Parser(token_types), storage)

while True:
try:
result = executor.execute_expression(input("> "))
if result:
print(result)
except Exception as error:
print(str(error))


if __name__ == "__main__":
main_loop()
206 changes: 206 additions & 0 deletions cli/src/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
"""
Модуль с командами с которыми работает интерпретатор
"""
import os
import re
from abc import ABCMeta, abstractmethod
from typing import List
from subprocess import run, PIPE
from src.storage import IStorage
import argparse


class ICommand(metaclass=ABCMeta):
""" Интерфейс для команды """

def __init__(self, args: List[str]):
self._args = args

@staticmethod
@abstractmethod
def name() -> str:
""" Вернуть имя команды
по нему будет происходить разбор команд"""
pass

@abstractmethod
def execute(self, pipe: str, storage: IStorage) -> str:
""" Выполнить команду с переданным pipeом
вернуть результат выполнения """
pass

def prepend_arg(self, arg: str):
""" Добавить аргумент перед всеми аргументами """
self._args = [arg] + self._args

def append_arg(self, arg: str):
""" Добавить аргумент после всех аргументов """
self._args.append(arg)


class CommandCat(ICommand):
""" Вывести на экран содержимое файла """

@staticmethod
def name() -> str:
return "cat"

def execute(self, pipe: str, storage: IStorage) -> str:
if not self._args:
raise RuntimeError("cat: must specify file names!")

result = ""
for filename in self._args:
try:
with open(filename) as f:
result += f.read() + '\n'
except IOError as error:
result += "cat: '%s' No such file or directory\n" % filename

return result[:-1]


class CommandEcho(ICommand):
""" Вывести на экран свой аргумент (или аргументы) """

@staticmethod
def name() -> str:
return "echo"

def execute(self, pipe: str, storage: IStorage) -> str:
return ' '.join(map(str, self._args))


class CommandWC(ICommand):
""" Вывести количество строк, слов и байт в файле """

@staticmethod
def name() -> str:
return "wc"

def execute(self, pipe: str, storage: IStorage) -> str:
result = ""
if pipe:
result += "%d %d %d\n" % (pipe.count('\n') + 1,
len(pipe.split()),
len(pipe))
else:
if not self._args:
raise RuntimeError("wc: must specify file names!")

for filename in self._args:
try:
with open(filename) as f:
data = f.read()
result += "%d %d %d\n" % (data.count('\n') + 1,
len(data.split()),
len(data))
except IOError as error:
result += "wc: '%s' No such file or directory\n" % filename

return result[:-1]


class CommandPwd(ICommand):
""" Распечатать текущую директорию """

@staticmethod
def name() -> str:
return "pwd"

def execute(self, pipe: str, storage: IStorage) -> str:
return os.getcwd()


class CommandExit(ICommand):
""" Выйти из интерпретатора """

@staticmethod
def name() -> str:
return "exit"

def execute(self, pipe: str, storage: IStorage) -> str:
quit(0)
return ""


class CommandDefault(ICommand):
""" Что будет выполнено, если не одна команда не подойдет """

@staticmethod
def name() -> str:
return ""

def execute(self, pipe: str, storage: IStorage) -> str:
process = run(self._args, stdout=PIPE, input=pipe,
shell=True, encoding="utf8", errors='ignore')
return process.stdout


class CommandGrep(ICommand):
""" Команда grep, ищет паттерн в файле или во входном потоке"""

@staticmethod
def name() -> str:
return "grep"

@staticmethod
def read_from_file(files) -> (str, str):
pipe = ""
result = ""
for filename in files:
try:
with open(filename, encoding="utf8") as f:
pipe += f.read() + '\n'
except IOError as error:
result += "grep: '%s' No such file or directory\n" % filename

return result, pipe

def execute(self, pipe: str, storage: IStorage) -> str:
parser = argparse.ArgumentParser(
description='Search for PATTERN in each FILE')

parser.add_argument("-i", "--ignore-case", action="store_true",
help="ignore case distinctions")
parser.add_argument("-w", "--word-regexp", action="store_true",
help="force PATTERN to match only whole words")
parser.add_argument("-A", "--after-context", type=int,
help="print NUM lines of output context")
parser.add_argument("PATTERN",
help="PATTERN is an extended regular expression")
parser.add_argument('FILE', nargs='*',
help='FILE is path to file for search')

try:
args = parser.parse_args(self._args)
pattern = args.PATTERN
result = ""

if args.after_context and args.after_context < 1:
raise argparse.ArgumentTypeError(
"lines NUM after context must be a positive integer")

if args.word_regexp:
pattern = r"\b" + pattern + r"\b"

if args.FILE:
res, pipe = self.read_from_file(args.FILE)
result += res

after_lines_count = 0
for line in pipe.splitlines(True):
if re.findall(pattern, line,
flags=re.IGNORECASE if args.ignore_case else 0):
after_lines_count = args.after_context if args.after_context else 0
result += line
elif after_lines_count > 0:
result += line
after_lines_count -= 1

return result[:-1]

except SystemExit:
pass

return ""
37 changes: 37 additions & 0 deletions cli/src/executor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Модуль исполняющий команды интерпретатора
"""

from abc import ABCMeta, abstractmethod
from src.interpreter import ICommandInterpreter
from src.pparser import IParser
from src.storage import IStorage


class IExecutor(metaclass=ABCMeta):
""" Интерфейс исполнителя выражений """

def __init__(self, command_interpreter: ICommandInterpreter,
parser: IParser, storage: IStorage):
self._command_interpreter = command_interpreter
self._parser = parser
self._storage = storage

@abstractmethod
def execute_expression(self, expr: str) -> str:
""" Исполнить выражение и вернуть результат """
pass


class Executor(IExecutor):
""" Реализация исполнителя выражений """

def execute_expression(self, expr: str) -> str:
tokens = self._parser.tokenize(expr)
commands = self._command_interpreter.retrieve_commands(iter(tokens))

result = ""
for command in commands:
result = command.execute(result, self._storage)

return result
Loading