From e1de01ac57a21095aee00a8d9df6de03b8c0b5f7 Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Tue, 3 Mar 2020 17:46:28 +0300 Subject: [PATCH 01/10] Rework wc command. Rework shell. Add pipes command. --- .gitignore | 6 +- shell/src/main/java/Application.java | 2 +- .../main/java/commands/CommandExecutor.java | 2 + .../main/java/commands/impl/CommandExit.java | 2 +- .../java/commands/impl/CommandNotExist.java | 22 +++ .../main/java/commands/impl/CommandWc.java | 63 +++++--- shell/src/main/java/parser/Parser.java | 147 +++++++++--------- shell/src/main/java/parser/Substitutor.java | 25 ++- shell/src/main/java/service/Environment.java | 3 +- shell/src/main/java/service/Executor.java | 73 +++++++++ shell/src/main/java/service/Shell.java | 29 +--- 11 files changed, 245 insertions(+), 129 deletions(-) create mode 100644 shell/src/main/java/commands/impl/CommandNotExist.java create mode 100644 shell/src/main/java/service/Executor.java diff --git a/.gitignore b/.gitignore index 6253652..c587fb3 100644 --- a/.gitignore +++ b/.gitignore @@ -52,8 +52,10 @@ buildNumber.properties ## Gradle ############################## bin/ -build/ -.gradle +*build/ +*gradle +*gradlew* +*.gradle .gradletasknamecache gradle-app.setting !gradle-wrapper.jar diff --git a/shell/src/main/java/Application.java b/shell/src/main/java/Application.java index 250f5f5..a9c9187 100644 --- a/shell/src/main/java/Application.java +++ b/shell/src/main/java/Application.java @@ -3,6 +3,6 @@ public class Application { public static void main(String[] args) { - new Shell().run(); + Shell.run(); } } diff --git a/shell/src/main/java/commands/CommandExecutor.java b/shell/src/main/java/commands/CommandExecutor.java index b2c703d..43f45ff 100644 --- a/shell/src/main/java/commands/CommandExecutor.java +++ b/shell/src/main/java/commands/CommandExecutor.java @@ -8,6 +8,8 @@ public class CommandExecutor { public String execute(String commandName, String args) { Command command = environment.getCommand(commandName); + command.setName(commandName); + return command.run(args, " "); } } diff --git a/shell/src/main/java/commands/impl/CommandExit.java b/shell/src/main/java/commands/impl/CommandExit.java index 0789950..f7d1596 100644 --- a/shell/src/main/java/commands/impl/CommandExit.java +++ b/shell/src/main/java/commands/impl/CommandExit.java @@ -3,7 +3,7 @@ import commands.Command; public class CommandExit implements Command { - private String name = ""; + private String name; @Override public String name() { diff --git a/shell/src/main/java/commands/impl/CommandNotExist.java b/shell/src/main/java/commands/impl/CommandNotExist.java new file mode 100644 index 0000000..26c7127 --- /dev/null +++ b/shell/src/main/java/commands/impl/CommandNotExist.java @@ -0,0 +1,22 @@ +package commands.impl; + +import commands.Command; + +public class CommandNotExist implements Command { + private String name = ""; + + @Override + public String name() { + return ""; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String run(String arguments, String options) { + return "command not found: " + name; + } +} diff --git a/shell/src/main/java/commands/impl/CommandWc.java b/shell/src/main/java/commands/impl/CommandWc.java index 0382cea..eff2306 100644 --- a/shell/src/main/java/commands/impl/CommandWc.java +++ b/shell/src/main/java/commands/impl/CommandWc.java @@ -4,10 +4,7 @@ import java.io.File; import java.io.FileNotFoundException; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Scanner; +import java.util.*; public class CommandWc implements Command { private String name = ""; @@ -22,34 +19,50 @@ public void setName(String name) { this.name = name; } + // TODO: добавить на строку @Override public String run(String arguments, String options) { - File file = new File(arguments); + int totalWords = 0; + int totalLines = 0; + int totalBytes = 0; - if (file.isDirectory()) - return "This path is directory"; + List output = new ArrayList<>(); + String[] files = arguments.split(" "); + for (String fileName : files) { + File file = new File(fileName); - // TODO: добавить на строку - Scanner scanner = null; - try { - scanner = new Scanner(file); - } catch (FileNotFoundException e) { - return "No such file"; - } + if (file.isDirectory()) + return "This path is directory"; + + Scanner scanner; + try { + scanner = new Scanner(file); + } catch (FileNotFoundException e) { + return "No such file"; + } + + int words = 0; + int lines = 0; + int bytes = 0; - int words = 0; - int lines = 0; - int bytes = 0; + while (Objects.requireNonNull(scanner).hasNextLine()) { + List line = Arrays.asList(scanner.nextLine().split(" ")); + bytes += line.stream().mapToInt(String::length).sum(); + words += line.size(); + lines++; + } + totalBytes += bytes; + totalLines += lines; + totalWords += words; + scanner.close(); + + output.add(lines + " " + words + " " + bytes + " " + file.getPath() + "\n"); + } - while (Objects.requireNonNull(scanner).hasNextLine()) { - List line = Arrays.asList(scanner.nextLine().split(" ")); - bytes += line.stream().mapToInt(String::length).sum(); - words += line.size(); - lines++; + if (files.length > 1) { + output.add(totalLines + " " + totalWords + " " + totalBytes + " " + "total" + "\n"); } - scanner.close(); -// System.out.println(lines + " " + words + " " + bytes); - return lines + " " + words + " " + bytes; + return String.join("", output); } } diff --git a/shell/src/main/java/parser/Parser.java b/shell/src/main/java/parser/Parser.java index bea6527..b2652c3 100644 --- a/shell/src/main/java/parser/Parser.java +++ b/shell/src/main/java/parser/Parser.java @@ -1,92 +1,89 @@ package parser; -import org.jetbrains.annotations.NotNull; import commands.CommandExecutor; -import commands.CommandType; import service.Environment; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class Parser { Environment environment = Environment.getInstance(); CommandExecutor commandExecutor = new CommandExecutor(); - public CommandType getType(@NotNull String input) { - if (input.contains("=")) { - return CommandType.Assign; - } - - if (input.contains("|")) { - return CommandType.Pipes; - } - - return CommandType.Command; - } - - public String parse(String input) { - switch (getType(input)) { - case Assign: { - if (input.contains(" ")) { - return " "; - } - - String[] attrAndValue = input.split("="); - environment.putVariable(attrAndValue[0], attrAndValue[1]); - break; - } - case Pipes: { - input = substitution(input); - List commands = Arrays.asList(input.split("\\|")); - String[] cmdAndArgs = commands.get(0).split(" ", 2); - String output = cmdAndArgs[1]; - for (String command : commands) { - String[] cm = command.trim().split(" ", 2); - output = commandExecutor.execute(cm[0], output); - } - - return output; - } - case Command: { - input = substitution(input); - String[] cmdAndArgs = input.split(" ", 2); - - if (cmdAndArgs.length < 2) { - return commandExecutor.execute(cmdAndArgs[0], ""); - } - - return commandExecutor.execute(cmdAndArgs[0], cmdAndArgs[1]); - } - default: { - break; - } - } - - return " "; - } - - String substitution(String input) { - String[] line = input.split(" "); - boolean isReplace = true; - - StringBuilder subsInput = new StringBuilder(); - for (String word : line) { - if (word.contains("'")) { - isReplace = !isReplace; - } +// public CommandType getType(@NotNull String input) { +// if (input.contains("=")) { +// return CommandType.Assign; +// } +// +// if (input.contains("|")) { +// return CommandType.Pipes; +// } +// +// return CommandType.Command; +// } - if (word.startsWith("$")) { - if (isReplace) { - String repl = word.substring(word.indexOf("$") + 1); - word = word.replace("$" + repl, environment.getVariable(repl)); - } - } - subsInput.append(word).append(" "); - } +// public String parse(String input) { +// switch (getType(input)) { +// case Assign: { +// if (input.contains(" ")) { +// return " "; +// } +// +// String[] attrAndValue = input.split("="); +// environment.putVariable(attrAndValue[0], attrAndValue[1]); +// break; +// } +// case Pipes: { +// input = substitution(input); +// List commands = Arrays.asList(input.split("\\|")); +// String[] cmdAndArgs = commands.get(0).split(" ", 2); +// String output = cmdAndArgs[1]; +// for (String command : commands) { +// String[] cm = command.trim().split(" ", 2); +// output = commandExecutor.execute(cm[0], output); +// } +// +// return output; +// } +// case Command: { +// input = substitution(input); +// String[] cmdAndArgs = input.split(" ", 2); +// +// if (cmdAndArgs.length < 2) { +// return commandExecutor.execute(cmdAndArgs[0], ""); +// } +// +// return commandExecutor.execute(cmdAndArgs[0], cmdAndArgs[1]); +// } +// default: { +// break; +// } +// } +// +// return " "; +// } - return subsInput.deleteCharAt(subsInput.length() - 1).toString(); - } +// String substitution(String input) { +// String[] line = input.split(" "); +// boolean isReplace = true; +// +// StringBuilder subsInput = new StringBuilder(); +// for (String word : line) { +// if (word.contains("'")) { +// isReplace = !isReplace; +// } +// +// if (word.startsWith("$")) { +// if (isReplace) { +// String repl = word.substring(word.indexOf("$") + 1); +// word = word.replace("$" + repl, environment.getVariable(repl)); +// } +// } +// subsInput.append(word).append(" "); +// } +// +// return subsInput.deleteCharAt(subsInput.length() - 1).toString(); +// } // String substitution2(List lexems) { // for (Lexem lexem : lexems) { diff --git a/shell/src/main/java/parser/Substitutor.java b/shell/src/main/java/parser/Substitutor.java index 22abe0c..88823ee 100644 --- a/shell/src/main/java/parser/Substitutor.java +++ b/shell/src/main/java/parser/Substitutor.java @@ -1,12 +1,35 @@ package parser; +import service.Environment; + import java.util.List; public class Substitutor { + Environment environment = Environment.getInstance(); + + public void substitution(List lexems) { for (Lexem lexem : lexems) { +// if (lexem.type == LexemType.WORD_ASSIGMENT) { +// String[] attrAndValue = lexem.word.split("="); +// environment.putVariable(attrAndValue[0], attrAndValue[1]); +// } if (lexem.type == LexemType.WORD_VARIABLE) { - lexem.word = + String subs = lexem.word.substring(lexem.word.indexOf("$") + 1); + lexem.word = lexem.word.replace("$" + subs, environment.getVariable(subs)); + } + + if (lexem.type == LexemType.WORD || lexem.type == LexemType.WORD_IN_DOUBLE_QUOTE) { + String[] line = lexem.word.split(" "); + StringBuilder newWord = new StringBuilder(); + for (String word : line) { + if (word.startsWith("$")) { + String subs = word.substring(word.indexOf("$") + 1); + word = word.replace("$" + subs, environment.getVariable(subs)); + } + newWord.append(word).append(" "); + } + lexem.word = newWord.deleteCharAt(newWord.length() - 1).toString(); } } } diff --git a/shell/src/main/java/service/Environment.java b/shell/src/main/java/service/Environment.java index 0e6bb11..512ffc8 100644 --- a/shell/src/main/java/service/Environment.java +++ b/shell/src/main/java/service/Environment.java @@ -29,7 +29,8 @@ public static Environment getInstance() { } public Command getCommand(String commandName) { - return commands.getOrDefault(commandName, new CommandDefault()); +// return commands.getOrDefault(commandName, new CommandDefault()); + return commands.getOrDefault(commandName, new CommandNotExist()); } public void putVariable(String argumentName, String argumentValue) { diff --git a/shell/src/main/java/service/Executor.java b/shell/src/main/java/service/Executor.java new file mode 100644 index 0000000..15946fd --- /dev/null +++ b/shell/src/main/java/service/Executor.java @@ -0,0 +1,73 @@ +package service; + +import commands.CommandEntity; +import commands.CommandExecutor; +import parser.Lexem; +import parser.LexemType; +import parser.Parser; +import parser.Substitutor; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class Executor { + private Parser parser = new Parser(); + private Environment environment = new Environment(); + private Substitutor substitutor = new Substitutor(); + private CommandExecutor commandExecutor = new CommandExecutor(); + + public String execute(String input) { + if (input.contains("|")) { + List commands = new ArrayList<>(); + + String[] cmds = input.split("\\|"); + + for (String cmd : cmds) { + List lexems = parser.parseLexem(cmd); + substitutor.substitution(lexems); + + commands.add( + CommandEntity.builder() + .name(lexems.get(0).getWord()) + .arguments(lexems.stream() + .filter(lexem -> lexems.indexOf(lexem) != 0) + .map(Lexem::getWord) + .collect(Collectors.joining(" "))) + .build()); + } + + String output = commandExecutor.execute(commands.get(0).getName(), + commands.get(0).getArguments()); + commands.remove(0); + for (CommandEntity command : commands) { + output = commandExecutor.execute(command.getName(), output); + } + + return output; + } + + List lexems = parser.parseLexem(input); + substitutor.substitution(lexems); + + for (Lexem lexem : lexems) { + if (lexem.getType() == LexemType.WORD_ASSIGMENT) { + String[] attrAndValue = input.split("="); + environment.putVariable(attrAndValue[0], attrAndValue[1]); + return attrAndValue[0] + attrAndValue[1]; + } + + CommandEntity command = + CommandEntity.builder() + .name(lexems.get(0).getWord()) + .arguments(lexems.stream() + .filter(l -> lexems.indexOf(l) != 0) + .map(Lexem::getWord) + .collect(Collectors.joining(" "))) + .build(); + return commandExecutor.execute(command.getName(), command.getArguments()); + } + + return ""; + } +} diff --git a/shell/src/main/java/service/Shell.java b/shell/src/main/java/service/Shell.java index dcc3dbb..38494a2 100644 --- a/shell/src/main/java/service/Shell.java +++ b/shell/src/main/java/service/Shell.java @@ -1,39 +1,22 @@ package service; -import parser.Lexem; -import parser.LexemType; -import parser.Parser; - -import java.util.ArrayList; -import java.util.List; import java.util.Scanner; public class Shell { static Scanner scanner = new Scanner(System.in); + static Executor executor = new Executor(); public static void run() { String input; - Parser parser = new Parser(); + String output; - System.out.print("SHELL >> "); while (scanner.hasNextLine()) { -// System.out.print("SHELL >> "); -// -// input = scanner.nextLine(); -// -// System.out.println("DEBUG --- " + "IN > " + input); // TODO -// -// String output = parser.parse(input); -// -// System.out.println("DEBUG --- " + "OUT > " + output); // TODO -// -// System.out.println(output); - ///////////////////////////// + System.out.print("SHELL >> "); - System.out.print("SHELL-TEST >> "); input = scanner.nextLine(); - List lexems = parser.parseLexem(input); - System.out.println(lexems); + output = executor.execute(input); + + System.out.println(output); } } } From 4c46e14ab0954883bf6fe9cc3aa83291480e52fc Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Wed, 4 Mar 2020 00:10:28 +0300 Subject: [PATCH 02/10] Complete commands. Add tests --- shell/src/main/java/commands/Command.java | 4 +- .../src/main/java/commands/CommandEntity.java | 5 +- .../main/java/commands/CommandExecutor.java | 9 +- shell/src/main/java/commands/CommandType.java | 8 -- .../main/java/commands/impl/CommandCat.java | 7 +- .../java/commands/impl/CommandDefault.java | 11 +- .../main/java/commands/impl/CommandEcho.java | 16 ++- .../main/java/commands/impl/CommandExit.java | 4 +- .../java/commands/impl/CommandNotExist.java | 4 +- .../main/java/commands/impl/CommandPwd.java | 5 +- .../main/java/commands/impl/CommandWc.java | 28 +++- shell/src/main/java/parser/LexemType.java | 2 - shell/src/main/java/parser/Parser.java | 120 ++---------------- shell/src/main/java/parser/Substitutor.java | 32 +++-- shell/src/main/java/service/Environment.java | 14 +- shell/src/main/java/service/Executor.java | 17 +-- shell/src/test/java/parser/ParserTest.java | 14 ++ .../test/java/service/EnvironmentTest.java | 40 ++++++ shell/src/test/java/service/ExecutorTest.java | 12 ++ 19 files changed, 184 insertions(+), 168 deletions(-) delete mode 100644 shell/src/main/java/commands/CommandType.java create mode 100644 shell/src/test/java/parser/ParserTest.java create mode 100644 shell/src/test/java/service/EnvironmentTest.java create mode 100644 shell/src/test/java/service/ExecutorTest.java diff --git a/shell/src/main/java/commands/Command.java b/shell/src/main/java/commands/Command.java index 6a83eca..f1100a3 100644 --- a/shell/src/main/java/commands/Command.java +++ b/shell/src/main/java/commands/Command.java @@ -1,9 +1,11 @@ package commands; +import java.util.List; + public interface Command { String name(); - String run(String arguments, String options); + String run(List arguments); void setName(String name); } diff --git a/shell/src/main/java/commands/CommandEntity.java b/shell/src/main/java/commands/CommandEntity.java index 4277185..ee25a9b 100644 --- a/shell/src/main/java/commands/CommandEntity.java +++ b/shell/src/main/java/commands/CommandEntity.java @@ -3,9 +3,12 @@ import lombok.Builder; import lombok.Data; +import java.util.List; + @Data @Builder public class CommandEntity { private String name; - private String arguments; + // private String arguments; + private List arguments; } diff --git a/shell/src/main/java/commands/CommandExecutor.java b/shell/src/main/java/commands/CommandExecutor.java index 43f45ff..023830d 100644 --- a/shell/src/main/java/commands/CommandExecutor.java +++ b/shell/src/main/java/commands/CommandExecutor.java @@ -3,13 +3,12 @@ import service.Environment; public class CommandExecutor { - Environment environment = Environment.getInstance(); - public String execute(String commandName, String args) { - Command command = environment.getCommand(commandName); - command.setName(commandName); + public String execute(CommandEntity commandEntity) { + Command command = environment.getCommand(commandEntity.getName()); + command.setName(commandEntity.getName()); - return command.run(args, " "); + return command.run(commandEntity.getArguments()); } } diff --git a/shell/src/main/java/commands/CommandType.java b/shell/src/main/java/commands/CommandType.java deleted file mode 100644 index 4a41c32..0000000 --- a/shell/src/main/java/commands/CommandType.java +++ /dev/null @@ -1,8 +0,0 @@ -package commands; - -public enum CommandType { - Assign, - Command, - Pipes, - Variable, -} diff --git a/shell/src/main/java/commands/impl/CommandCat.java b/shell/src/main/java/commands/impl/CommandCat.java index c56ec2e..77d455c 100644 --- a/shell/src/main/java/commands/impl/CommandCat.java +++ b/shell/src/main/java/commands/impl/CommandCat.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.List; import java.util.stream.Stream; public class CommandCat implements Command { @@ -22,17 +23,17 @@ public void setName(String name) { } @Override - public String run(String arguments, String options) { + public String run(List arguments) { + String args = String.join(" ", arguments); StringBuilder contentBuilder = new StringBuilder(); try (Stream stream = Files.lines( - Paths.get(Environment.getCurrentPath(), arguments.trim()))) { + Paths.get(Environment.getCurrentPath(), args.trim()))) { stream.forEach(s -> contentBuilder.append(s).append("\n")); } catch (IOException e) { e.getMessage(); } - System.out.println(contentBuilder.toString()); return contentBuilder.toString(); } } diff --git a/shell/src/main/java/commands/impl/CommandDefault.java b/shell/src/main/java/commands/impl/CommandDefault.java index 565a732..d6bb298 100644 --- a/shell/src/main/java/commands/impl/CommandDefault.java +++ b/shell/src/main/java/commands/impl/CommandDefault.java @@ -5,6 +5,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.List; public class CommandDefault implements Command { private String name = ""; @@ -20,9 +21,11 @@ public void setName(String name) { } @Override - public String run(String arguments, String options) { + public String run(List arguments) { + String args = String.join(" ", arguments); + ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command(name, arguments, options); + processBuilder.command(name, args); try { Process process = processBuilder.start(); @@ -37,8 +40,8 @@ public String run(String arguments, String options) { int exitVal = process.waitFor(); if (exitVal == 0) { - System.out.println("Success!"); - System.out.println(output); +// System.out.println("Success!"); +// System.out.println(output); System.exit(0); } diff --git a/shell/src/main/java/commands/impl/CommandEcho.java b/shell/src/main/java/commands/impl/CommandEcho.java index 2f1b386..fffd623 100644 --- a/shell/src/main/java/commands/impl/CommandEcho.java +++ b/shell/src/main/java/commands/impl/CommandEcho.java @@ -2,6 +2,8 @@ import commands.Command; +import java.util.List; + public class CommandEcho implements Command { private String name = ""; @@ -16,7 +18,17 @@ public void setName(String name) { } @Override - public String run(String arguments, String options) { - return arguments; + public String run(List arguments) { + StringBuilder result = new StringBuilder(); + + for (String word : arguments) { + if ((word.startsWith("\"") && word.endsWith("\"")) || + (word.startsWith("'") && word.endsWith("'"))) { + word = word.substring(1, word.length() - 1); + } + result.append(word).append(" "); + } + + return result.toString(); } } diff --git a/shell/src/main/java/commands/impl/CommandExit.java b/shell/src/main/java/commands/impl/CommandExit.java index f7d1596..fc36d9f 100644 --- a/shell/src/main/java/commands/impl/CommandExit.java +++ b/shell/src/main/java/commands/impl/CommandExit.java @@ -2,6 +2,8 @@ import commands.Command; +import java.util.List; + public class CommandExit implements Command { private String name; @@ -16,7 +18,7 @@ public void setName(String name) { } @Override - public String run(String arguments, String options) { + public String run(List arguments) { System.exit(0); return null; } diff --git a/shell/src/main/java/commands/impl/CommandNotExist.java b/shell/src/main/java/commands/impl/CommandNotExist.java index 26c7127..82fe586 100644 --- a/shell/src/main/java/commands/impl/CommandNotExist.java +++ b/shell/src/main/java/commands/impl/CommandNotExist.java @@ -2,6 +2,8 @@ import commands.Command; +import java.util.List; + public class CommandNotExist implements Command { private String name = ""; @@ -16,7 +18,7 @@ public void setName(String name) { } @Override - public String run(String arguments, String options) { + public String run(List arguments) { return "command not found: " + name; } } diff --git a/shell/src/main/java/commands/impl/CommandPwd.java b/shell/src/main/java/commands/impl/CommandPwd.java index 714a73c..a54843a 100644 --- a/shell/src/main/java/commands/impl/CommandPwd.java +++ b/shell/src/main/java/commands/impl/CommandPwd.java @@ -2,8 +2,9 @@ import commands.Command; -public class CommandPwd implements Command { +import java.util.List; +public class CommandPwd implements Command { private String name = ""; @Override @@ -17,7 +18,7 @@ public void setName(String name) { } @Override - public String run(String arguments, String options) { + public String run(List arguments) { return System.getProperty("user.dir"); } } diff --git a/shell/src/main/java/commands/impl/CommandWc.java b/shell/src/main/java/commands/impl/CommandWc.java index eff2306..cf09d99 100644 --- a/shell/src/main/java/commands/impl/CommandWc.java +++ b/shell/src/main/java/commands/impl/CommandWc.java @@ -19,18 +19,36 @@ public void setName(String name) { this.name = name; } - // TODO: добавить на строку @Override - public String run(String arguments, String options) { + public String run(List arguments) { + String args = String.join(" ", arguments); + int totalWords = 0; int totalLines = 0; int totalBytes = 0; List output = new ArrayList<>(); - String[] files = arguments.split(" "); + String[] files = args.split(" "); for (String fileName : files) { + int words = 0; + int lines = 0; + int bytes = 0; + File file = new File(fileName); + if (!file.isFile()) { + Scanner scanner = new Scanner(args); + while (Objects.requireNonNull(scanner).hasNextLine()) { + List line = Arrays.asList(scanner.nextLine().split(" ")); + bytes += line.stream().mapToInt(String::length).sum(); + words += line.size(); + lines++; + } + output.add(lines + " " + words + " " + bytes + "\n"); + + return String.join("", output); + } + if (file.isDirectory()) return "This path is directory"; @@ -41,10 +59,6 @@ public String run(String arguments, String options) { return "No such file"; } - int words = 0; - int lines = 0; - int bytes = 0; - while (Objects.requireNonNull(scanner).hasNextLine()) { List line = Arrays.asList(scanner.nextLine().split(" ")); bytes += line.stream().mapToInt(String::length).sum(); diff --git a/shell/src/main/java/parser/LexemType.java b/shell/src/main/java/parser/LexemType.java index 1549e38..f8934d8 100644 --- a/shell/src/main/java/parser/LexemType.java +++ b/shell/src/main/java/parser/LexemType.java @@ -6,6 +6,4 @@ public enum LexemType { WORD_ASSIGMENT, WORD_VARIABLE, WORD, - - SPACES, } diff --git a/shell/src/main/java/parser/Parser.java b/shell/src/main/java/parser/Parser.java index b2652c3..be6a07c 100644 --- a/shell/src/main/java/parser/Parser.java +++ b/shell/src/main/java/parser/Parser.java @@ -1,118 +1,9 @@ package parser; -import commands.CommandExecutor; -import service.Environment; - import java.util.ArrayList; import java.util.List; public class Parser { - Environment environment = Environment.getInstance(); - CommandExecutor commandExecutor = new CommandExecutor(); - -// public CommandType getType(@NotNull String input) { -// if (input.contains("=")) { -// return CommandType.Assign; -// } -// -// if (input.contains("|")) { -// return CommandType.Pipes; -// } -// -// return CommandType.Command; -// } - -// public String parse(String input) { -// switch (getType(input)) { -// case Assign: { -// if (input.contains(" ")) { -// return " "; -// } -// -// String[] attrAndValue = input.split("="); -// environment.putVariable(attrAndValue[0], attrAndValue[1]); -// break; -// } -// case Pipes: { -// input = substitution(input); -// List commands = Arrays.asList(input.split("\\|")); -// String[] cmdAndArgs = commands.get(0).split(" ", 2); -// String output = cmdAndArgs[1]; -// for (String command : commands) { -// String[] cm = command.trim().split(" ", 2); -// output = commandExecutor.execute(cm[0], output); -// } -// -// return output; -// } -// case Command: { -// input = substitution(input); -// String[] cmdAndArgs = input.split(" ", 2); -// -// if (cmdAndArgs.length < 2) { -// return commandExecutor.execute(cmdAndArgs[0], ""); -// } -// -// return commandExecutor.execute(cmdAndArgs[0], cmdAndArgs[1]); -// } -// default: { -// break; -// } -// } -// -// return " "; -// } - -// String substitution(String input) { -// String[] line = input.split(" "); -// boolean isReplace = true; -// -// StringBuilder subsInput = new StringBuilder(); -// for (String word : line) { -// if (word.contains("'")) { -// isReplace = !isReplace; -// } -// -// if (word.startsWith("$")) { -// if (isReplace) { -// String repl = word.substring(word.indexOf("$") + 1); -// word = word.replace("$" + repl, environment.getVariable(repl)); -// } -// } -// subsInput.append(word).append(" "); -// } -// -// return subsInput.deleteCharAt(subsInput.length() - 1).toString(); -// } - -// String substitution2(List lexems) { -// for (Lexem lexem : lexems) { -// if (lexem.type == LexemType.WORD_ASSIGMENT) { -// String[] attrAndValue = lexem.word.split("="); -// environment.putVariable(attrAndValue[0], attrAndValue[1]); -// } else if (lexem.type == LexemType.WORD_VARIABLE) { -// -// String repl = lexem.word.substring(lexem.word.indexOf("$") + 1); -// word = word.replace("$" + repl, environment.getVariable(repl)); -// } - - -// for (String word : line) { -// if (word.contains("'")) { -// isReplace = !isReplace; -// } -// -// if (word.startsWith("$")) { -// if (isReplace) { -// String repl = word.substring(word.indexOf("$") + 1); -// word = word.replace("$" + repl, environment.getVariable(repl)); -// } -// } -// subsInput.append(word).append(" "); -// } -// -// return subsInput.deleteCharAt(subsInput.length() - 1).toString(); -// } public List parseLexem(String in) { int idx = 0; @@ -165,6 +56,17 @@ public List parseLexem(String in) { .build()); while (idx < in.length() && in.charAt(idx) == ' ') idx++; continue; + } else if (in.charAt(idx) == '=') { + while (idx < in.length() && in.charAt(idx) != ' ') { + word.append(in.charAt(idx)); + idx++; + } + lexems.add(Lexem.builder() + .word(word.toString()) + .type(LexemType.WORD_ASSIGMENT) + .build()); + word = new StringBuilder(); + continue; } else if (in.charAt(idx) == ' ') { if (word.length() > 0) { lexems.add(Lexem.builder() diff --git a/shell/src/main/java/parser/Substitutor.java b/shell/src/main/java/parser/Substitutor.java index 88823ee..756b9c2 100644 --- a/shell/src/main/java/parser/Substitutor.java +++ b/shell/src/main/java/parser/Substitutor.java @@ -19,18 +19,30 @@ public void substitution(List lexems) { lexem.word = lexem.word.replace("$" + subs, environment.getVariable(subs)); } - if (lexem.type == LexemType.WORD || lexem.type == LexemType.WORD_IN_DOUBLE_QUOTE) { - String[] line = lexem.word.split(" "); - StringBuilder newWord = new StringBuilder(); - for (String word : line) { - if (word.startsWith("$")) { - String subs = word.substring(word.indexOf("$") + 1); - word = word.replace("$" + subs, environment.getVariable(subs)); - } - newWord.append(word).append(" "); + if (lexem.type == LexemType.WORD_IN_DOUBLE_QUOTE) { + if (lexem.word.contains("$")) { + StringBuilder subs = new StringBuilder(); + subs.append(lexem.word.substring(lexem.word.indexOf("$") + 1)); + subs.deleteCharAt(lexem.word.length() - 3); +// String subs = lexem.word.substring(lexem.word.indexOf("$") + 1); + lexem.word = lexem.word.replace("$" + subs.toString(), + environment.getVariable(subs.toString())); + // lexem.word = newWord.deleteCharAt(newWord.length() - 1).toString(); } - lexem.word = newWord.deleteCharAt(newWord.length() - 1).toString(); } + +// if (lexem.type == LexemType.WORD || lexem.type == LexemType.WORD_IN_DOUBLE_QUOTE) { +// String[] line = lexem.word.split(" "); +// StringBuilder newWord = new StringBuilder(); +// for (String word : line) { +// if (word.startsWith("$")) { +// String subs = word.substring(word.indexOf("$") + 1); +// word = word.replace("$" + subs, environment.getVariable(subs)); +// } +// newWord.append(word).append(" "); +// } +// lexem.word = newWord.deleteCharAt(newWord.length() - 1).toString(); +// } } } } diff --git a/shell/src/main/java/service/Environment.java b/shell/src/main/java/service/Environment.java index 512ffc8..6f2da8a 100644 --- a/shell/src/main/java/service/Environment.java +++ b/shell/src/main/java/service/Environment.java @@ -7,7 +7,6 @@ import java.util.Map; public class Environment { - private static String currentPath = System.getProperty("user.dir"); private static Map commands = new HashMap<>(); private static Map variables = new HashMap<>(); @@ -20,6 +19,15 @@ public class Environment { commands.put("wc", new CommandWc()); } + static { + variables.put("PWD", "/home/pagrom"); + variables.put("HOME", "/home/pagrom/"); + variables.put("PATH", "/home/pagrom/.local/bin"); + variables.put("USER", "pagrom"); + variables.put("SHELL", "/usr/bin/zsh"); + variables.put("LOGNAME", "pagrom"); + } + public static class SingletonHolder { public static final Environment HOLDER_INSTANCE = new Environment(); } @@ -35,12 +43,10 @@ public Command getCommand(String commandName) { public void putVariable(String argumentName, String argumentValue) { variables.put(argumentName, argumentValue); - System.out.println("DEBUG --- " + "VARS > " + variables); } public String getVariable(String variableName) { - System.out.println("DEBUG --- " + "VARS > " + variables); - return variables.get(variableName); + return variables.getOrDefault(variableName, "Variable not exist!"); } public static String getCurrentPath() { diff --git a/shell/src/main/java/service/Executor.java b/shell/src/main/java/service/Executor.java index 15946fd..cd4f8c9 100644 --- a/shell/src/main/java/service/Executor.java +++ b/shell/src/main/java/service/Executor.java @@ -8,6 +8,7 @@ import parser.Substitutor; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -22,7 +23,6 @@ public String execute(String input) { List commands = new ArrayList<>(); String[] cmds = input.split("\\|"); - for (String cmd : cmds) { List lexems = parser.parseLexem(cmd); substitutor.substitution(lexems); @@ -33,15 +33,15 @@ public String execute(String input) { .arguments(lexems.stream() .filter(lexem -> lexems.indexOf(lexem) != 0) .map(Lexem::getWord) - .collect(Collectors.joining(" "))) + .collect(Collectors.toList())) .build()); } - String output = commandExecutor.execute(commands.get(0).getName(), - commands.get(0).getArguments()); + String output = commandExecutor.execute(commands.get(0)); commands.remove(0); for (CommandEntity command : commands) { - output = commandExecutor.execute(command.getName(), output); + command.setArguments(Collections.singletonList(output)); + output = commandExecutor.execute(command); } return output; @@ -54,7 +54,7 @@ public String execute(String input) { if (lexem.getType() == LexemType.WORD_ASSIGMENT) { String[] attrAndValue = input.split("="); environment.putVariable(attrAndValue[0], attrAndValue[1]); - return attrAndValue[0] + attrAndValue[1]; + return attrAndValue[0] + " = " + attrAndValue[1]; } CommandEntity command = @@ -63,9 +63,10 @@ public String execute(String input) { .arguments(lexems.stream() .filter(l -> lexems.indexOf(l) != 0) .map(Lexem::getWord) - .collect(Collectors.joining(" "))) + .collect(Collectors.toList())) .build(); - return commandExecutor.execute(command.getName(), command.getArguments()); + + return commandExecutor.execute(command); } return ""; diff --git a/shell/src/test/java/parser/ParserTest.java b/shell/src/test/java/parser/ParserTest.java new file mode 100644 index 0000000..ffe0f34 --- /dev/null +++ b/shell/src/test/java/parser/ParserTest.java @@ -0,0 +1,14 @@ +package parser; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ParserTest { +// private final +// +// @Test +// public void testParseLexemWithWordInDoubleQuote() { +// +// } +} \ No newline at end of file diff --git a/shell/src/test/java/service/EnvironmentTest.java b/shell/src/test/java/service/EnvironmentTest.java new file mode 100644 index 0000000..041cee4 --- /dev/null +++ b/shell/src/test/java/service/EnvironmentTest.java @@ -0,0 +1,40 @@ +package service; + +import commands.impl.CommandExit; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + +public class EnvironmentTest { + private final Environment environment = Environment.getInstance(); + + @Test + public void testGetInstance() { + // Arrange + Environment env1 = Environment.getInstance(); + Environment env2 = Environment.getInstance(); + + // Assert + assertEquals(env1, env2); + } + + @Test + public void testGetCommand() { + assertThat(environment.getCommand("exit").getClass()).isEqualTo(CommandExit.class); + } + + @Test + public void testPutAndGetVariable() { + // Act + environment.putVariable("Hello", "World"); + + // Assert + assertThat(environment.getVariable("Hello")).isEqualTo("World"); + } + + @Test + public void getCurrentPath() { + assertThat(Environment.getCurrentPath()).isEqualTo(System.getProperty("user.dir")); + } +} diff --git a/shell/src/test/java/service/ExecutorTest.java b/shell/src/test/java/service/ExecutorTest.java new file mode 100644 index 0000000..9125966 --- /dev/null +++ b/shell/src/test/java/service/ExecutorTest.java @@ -0,0 +1,12 @@ +package service; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ExecutorTest { + + @Test + public void execute() { + } +} \ No newline at end of file From 9c74b3f43edbcb7420a3d84e5fefc93b9e116d9e Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Wed, 4 Mar 2020 02:02:38 +0300 Subject: [PATCH 03/10] Add tests. --- .../main/java/commands/impl/CommandCat.java | 1 + .../main/java/commands/impl/CommandEcho.java | 2 +- .../java/commands/impl/CommandNotExist.java | 2 +- .../main/java/commands/impl/CommandWc.java | 6 +- shell/src/main/java/parser/Substitutor.java | 20 ---- .../java/commands/CommandExecutorTest.java | 27 +++++ .../java/commands/impl/CommandCatTest.java | 50 +++++++++ .../commands/impl/CommandNotExistTest.java | 18 ++++ .../java/commands/impl/CommandPwdTest.java | 16 +++ .../java/commands/impl/CommandWcTest.java | 60 +++++++++++ shell/src/test/java/parser/ParserTest.java | 102 ++++++++++++++++-- .../src/test/java/parser/SubstitutorTest.java | 37 +++++++ .../test/java/service/EnvironmentTest.java | 2 +- shell/src/test/java/service/ExecutorTest.java | 44 +++++++- shell/src/test/resources/Empty.txt | 0 shell/src/test/resources/Example.txt | 1 + 16 files changed, 351 insertions(+), 37 deletions(-) create mode 100644 shell/src/test/java/commands/CommandExecutorTest.java create mode 100644 shell/src/test/java/commands/impl/CommandCatTest.java create mode 100644 shell/src/test/java/commands/impl/CommandNotExistTest.java create mode 100644 shell/src/test/java/commands/impl/CommandPwdTest.java create mode 100644 shell/src/test/java/commands/impl/CommandWcTest.java create mode 100644 shell/src/test/java/parser/SubstitutorTest.java create mode 100644 shell/src/test/resources/Empty.txt create mode 100644 shell/src/test/resources/Example.txt diff --git a/shell/src/main/java/commands/impl/CommandCat.java b/shell/src/main/java/commands/impl/CommandCat.java index 77d455c..f057eaf 100644 --- a/shell/src/main/java/commands/impl/CommandCat.java +++ b/shell/src/main/java/commands/impl/CommandCat.java @@ -32,6 +32,7 @@ public String run(List arguments) { stream.forEach(s -> contentBuilder.append(s).append("\n")); } catch (IOException e) { e.getMessage(); + contentBuilder.append("File is not exist!"); } return contentBuilder.toString(); diff --git a/shell/src/main/java/commands/impl/CommandEcho.java b/shell/src/main/java/commands/impl/CommandEcho.java index fffd623..677f550 100644 --- a/shell/src/main/java/commands/impl/CommandEcho.java +++ b/shell/src/main/java/commands/impl/CommandEcho.java @@ -29,6 +29,6 @@ public String run(List arguments) { result.append(word).append(" "); } - return result.toString(); + return result.deleteCharAt(result.length() - 1).toString(); } } diff --git a/shell/src/main/java/commands/impl/CommandNotExist.java b/shell/src/main/java/commands/impl/CommandNotExist.java index 82fe586..0a846f9 100644 --- a/shell/src/main/java/commands/impl/CommandNotExist.java +++ b/shell/src/main/java/commands/impl/CommandNotExist.java @@ -19,6 +19,6 @@ public void setName(String name) { @Override public String run(List arguments) { - return "command not found: " + name; + return "Command not found: " + name; } } diff --git a/shell/src/main/java/commands/impl/CommandWc.java b/shell/src/main/java/commands/impl/CommandWc.java index cf09d99..6df5ffb 100644 --- a/shell/src/main/java/commands/impl/CommandWc.java +++ b/shell/src/main/java/commands/impl/CommandWc.java @@ -36,6 +36,9 @@ public String run(List arguments) { File file = new File(fileName); + if (file.isDirectory()) + return "This path is directory!"; + if (!file.isFile()) { Scanner scanner = new Scanner(args); while (Objects.requireNonNull(scanner).hasNextLine()) { @@ -49,9 +52,6 @@ public String run(List arguments) { return String.join("", output); } - if (file.isDirectory()) - return "This path is directory"; - Scanner scanner; try { scanner = new Scanner(file); diff --git a/shell/src/main/java/parser/Substitutor.java b/shell/src/main/java/parser/Substitutor.java index 756b9c2..42d7d16 100644 --- a/shell/src/main/java/parser/Substitutor.java +++ b/shell/src/main/java/parser/Substitutor.java @@ -7,13 +7,8 @@ public class Substitutor { Environment environment = Environment.getInstance(); - public void substitution(List lexems) { for (Lexem lexem : lexems) { -// if (lexem.type == LexemType.WORD_ASSIGMENT) { -// String[] attrAndValue = lexem.word.split("="); -// environment.putVariable(attrAndValue[0], attrAndValue[1]); -// } if (lexem.type == LexemType.WORD_VARIABLE) { String subs = lexem.word.substring(lexem.word.indexOf("$") + 1); lexem.word = lexem.word.replace("$" + subs, environment.getVariable(subs)); @@ -24,25 +19,10 @@ public void substitution(List lexems) { StringBuilder subs = new StringBuilder(); subs.append(lexem.word.substring(lexem.word.indexOf("$") + 1)); subs.deleteCharAt(lexem.word.length() - 3); -// String subs = lexem.word.substring(lexem.word.indexOf("$") + 1); lexem.word = lexem.word.replace("$" + subs.toString(), environment.getVariable(subs.toString())); - // lexem.word = newWord.deleteCharAt(newWord.length() - 1).toString(); } } - -// if (lexem.type == LexemType.WORD || lexem.type == LexemType.WORD_IN_DOUBLE_QUOTE) { -// String[] line = lexem.word.split(" "); -// StringBuilder newWord = new StringBuilder(); -// for (String word : line) { -// if (word.startsWith("$")) { -// String subs = word.substring(word.indexOf("$") + 1); -// word = word.replace("$" + subs, environment.getVariable(subs)); -// } -// newWord.append(word).append(" "); -// } -// lexem.word = newWord.deleteCharAt(newWord.length() - 1).toString(); -// } } } } diff --git a/shell/src/test/java/commands/CommandExecutorTest.java b/shell/src/test/java/commands/CommandExecutorTest.java new file mode 100644 index 0000000..3102462 --- /dev/null +++ b/shell/src/test/java/commands/CommandExecutorTest.java @@ -0,0 +1,27 @@ +package commands; + +import org.junit.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class CommandExecutorTest { + private final CommandExecutor commandExecutor = new CommandExecutor(); + + @Test + public void testExecuteCommandEcho() { + // Arrange + CommandEntity commandEntity = + CommandEntity.builder().name("echo") + .arguments(Collections.singletonList("hello world")) + .build(); + + // Act + String actual = commandExecutor.execute(commandEntity); + + // Arrange + assertThat(actual).isEqualTo("hello world"); + } +} diff --git a/shell/src/test/java/commands/impl/CommandCatTest.java b/shell/src/test/java/commands/impl/CommandCatTest.java new file mode 100644 index 0000000..3c36d8e --- /dev/null +++ b/shell/src/test/java/commands/impl/CommandCatTest.java @@ -0,0 +1,50 @@ +package commands.impl; + +import commands.Command; +import org.junit.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CommandCatTest { + + private final Command commandCat = new CommandCat(); + private final String pathToTestResources = "src/test/"; + + @Test + public void testRunCommandCat() { + // Arrange + String pathToFile = "resources/Example.txt"; + + // Act + String actual = commandCat.run(Collections.singletonList(pathToTestResources + pathToFile)); + + // Assert + assertThat(actual).isEqualTo("Hello, World!\n"); + } + + @Test + public void testRunCommandCatWithEmptyFile() { + // Arrange + String pathToFile = "resources/Empty.txt"; + + // Act + String actual = commandCat.run(Collections.singletonList(pathToTestResources + pathToFile)); + + // Assert + assertThat(actual).isEqualTo(""); + } + + @Test + public void testRunCommandCatWithNotExistFile() { + // Arrange + String pathToFile = "resources/NotExist.bash"; + + // Act + String actual = commandCat.run(Collections.singletonList(pathToTestResources + pathToFile)); + + // Assert + assertThat(actual).isEqualTo("File is not exist!"); + } +} \ No newline at end of file diff --git a/shell/src/test/java/commands/impl/CommandNotExistTest.java b/shell/src/test/java/commands/impl/CommandNotExistTest.java new file mode 100644 index 0000000..2bf415b --- /dev/null +++ b/shell/src/test/java/commands/impl/CommandNotExistTest.java @@ -0,0 +1,18 @@ +package commands.impl; + +import commands.Command; +import org.junit.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CommandNotExistTest { + private final Command command = new CommandNotExist(); + + @Test + public void testRunCommandNotExist() { + String actual = command.run(Collections.emptyList()); + assertThat(actual).isEqualTo("Command not found: "); + } +} diff --git a/shell/src/test/java/commands/impl/CommandPwdTest.java b/shell/src/test/java/commands/impl/CommandPwdTest.java new file mode 100644 index 0000000..c8ffd49 --- /dev/null +++ b/shell/src/test/java/commands/impl/CommandPwdTest.java @@ -0,0 +1,16 @@ +package commands.impl; + +import commands.Command; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CommandPwdTest { + private final Command commandPwd = new CommandPwd(); + + @Test + public void testRunCommandPwd() { + String actual = commandPwd.run(null); + assertThat(actual).isEqualTo(System.getProperty("user.dir")); + } +} diff --git a/shell/src/test/java/commands/impl/CommandWcTest.java b/shell/src/test/java/commands/impl/CommandWcTest.java new file mode 100644 index 0000000..48e9ad4 --- /dev/null +++ b/shell/src/test/java/commands/impl/CommandWcTest.java @@ -0,0 +1,60 @@ +package commands.impl; + +import commands.Command; +import org.junit.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class CommandWcTest { + + private final Command command = new CommandWc(); + private final String pathToTestResources = "src/test/"; + + @Test + public void testRunCommandWc() { + // Arrange + String pathToFile = "resources/Example.txt"; + + // Act + String actual = command.run(Collections.singletonList(pathToTestResources + pathToFile)); + + // Assert + assertThat(actual).isEqualTo("1 2 12 src/test/resources/Example.txt\n"); + } + + @Test + public void testRunCommandWcWithEmptyFile() { + // Arrange + String pathToFile = "resources/Empty.txt"; + + // Act + String actual = command.run(Collections.singletonList(pathToTestResources + pathToFile)); + + // Assert + assertThat(actual).isEqualTo("0 0 0 src/test/resources/Empty.txt\n"); + } + + @Test + public void testRunCommandWcWithPipe() { + // Act + String actual = command.run(Collections.singletonList("hello world")); + + // Assert + assertThat(actual).isEqualTo("1 2 10\n"); + } + + @Test + public void testRunCommandWcWithPathToDirectory() { + // Arrange + String pathToFile = "resources/"; + + // Act + String actual = command.run(Collections.singletonList(pathToTestResources + pathToFile)); + + // Assert + assertThat(actual).isEqualTo("This path is directory!"); + } +} diff --git a/shell/src/test/java/parser/ParserTest.java b/shell/src/test/java/parser/ParserTest.java index ffe0f34..54daa52 100644 --- a/shell/src/test/java/parser/ParserTest.java +++ b/shell/src/test/java/parser/ParserTest.java @@ -2,13 +2,99 @@ import org.junit.Test; -import static org.junit.Assert.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; public class ParserTest { -// private final -// -// @Test -// public void testParseLexemWithWordInDoubleQuote() { -// -// } -} \ No newline at end of file + private final Parser parser = new Parser(); + + @Test + public void testParseLexemWithWordInDoubleQuote() { + // Arrange + String input = "\" hello world \""; + List excpected = Collections.singletonList( + Lexem.builder() + .word(input) + .type(LexemType.WORD_IN_DOUBLE_QUOTE) + .build()); + + // Act + List actual = parser.parseLexem(input); + + // Assert + assertThat(actual).isEqualTo(excpected); + } + + + @Test + public void testParseLexemWithWordInSingleQuote() { + // Arrange + String input = "' hello world '"; + List excpected = Collections.singletonList( + Lexem.builder() + .word(input) + .type(LexemType.WORD_IN_SINGLE_QUOTE) + .build()); + + // Act + List actual = parser.parseLexem(input); + + // Assert + assertThat(actual).isEqualTo(excpected); + } + + @Test + public void testParseLexemWithWordVariable() { + // Arrange + String input = "$task"; + List excpected = Collections.singletonList( + Lexem.builder() + .word(input) + .type(LexemType.WORD_VARIABLE) + .build()); + + // Act + List actual = parser.parseLexem(input); + + // Assert + assertThat(actual).isEqualTo(excpected); + } + + @Test + public void testParseLexemWithWordAssigment() { + // Arrange + String input = "y=10"; + List excpected = Collections.singletonList( + Lexem.builder() + .word(input) + .type(LexemType.WORD_ASSIGMENT) + .build()); + + // Act + List actual = parser.parseLexem(input); + + // Assert + assertThat(actual).isEqualTo(excpected); + } + + @Test + public void testParseLexem() { + // Arrange + String input = "Hello World \" Software Design\" $bash 'hi' "; + Lexem lexem1 = Lexem.builder().word("Hello").type(LexemType.WORD).build(); + Lexem lexem2 = Lexem.builder().word("World").type(LexemType.WORD).build(); + Lexem lexem3 = Lexem.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); + Lexem lexem4 = Lexem.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); + Lexem lexem5 = Lexem.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); + List excpected = Arrays.asList(lexem1, lexem2, lexem3, lexem4, lexem5); + + // Act + List actual = parser.parseLexem(input); + + // Assert + assertThat(actual).isEqualTo(excpected); + } +} diff --git a/shell/src/test/java/parser/SubstitutorTest.java b/shell/src/test/java/parser/SubstitutorTest.java new file mode 100644 index 0000000..91478a8 --- /dev/null +++ b/shell/src/test/java/parser/SubstitutorTest.java @@ -0,0 +1,37 @@ +package parser; + +import org.junit.Test; +import service.Environment; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SubstitutorTest { + private final Substitutor substitutor = new Substitutor(); + private final Environment environment = Environment.getInstance(); + + @Test + public void testSubstitution() { + // Arrange + String input = "Hello World \" Software Design\" $bash 'hi' "; + Lexem lexem1 = Lexem.builder().word("Hello").type(LexemType.WORD).build(); + Lexem lexem2 = Lexem.builder().word("World").type(LexemType.WORD).build(); + Lexem lexem3 = Lexem.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); + Lexem lexem4 = Lexem.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); + Lexem lexem5 = Lexem.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); + Lexem lexem6 = Lexem.builder().word("value").type(LexemType.WORD_VARIABLE).build(); + + List actual = Arrays.asList(lexem1, lexem2, lexem3, lexem4, lexem5); + List expected = Arrays.asList(lexem1, lexem2, lexem3, lexem6, lexem5); + + environment.putVariable("bash", "value"); + + // Act + substitutor.substitution(actual); + + // Assert + assertThat(actual).isEqualTo(expected); + } +} diff --git a/shell/src/test/java/service/EnvironmentTest.java b/shell/src/test/java/service/EnvironmentTest.java index 041cee4..0d7f13c 100644 --- a/shell/src/test/java/service/EnvironmentTest.java +++ b/shell/src/test/java/service/EnvironmentTest.java @@ -34,7 +34,7 @@ public void testPutAndGetVariable() { } @Test - public void getCurrentPath() { + public void testGetCurrentPath() { assertThat(Environment.getCurrentPath()).isEqualTo(System.getProperty("user.dir")); } } diff --git a/shell/src/test/java/service/ExecutorTest.java b/shell/src/test/java/service/ExecutorTest.java index 9125966..231e1a1 100644 --- a/shell/src/test/java/service/ExecutorTest.java +++ b/shell/src/test/java/service/ExecutorTest.java @@ -2,11 +2,49 @@ import org.junit.Test; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; public class ExecutorTest { + private final Executor executor = new Executor(); + private final Environment environment = Environment.getInstance(); @Test - public void execute() { + public void testExecuteSingleCommand() { + // Arrange + String input = "echo hello world"; + String expected = "hello world"; + + // Act + String actual = executor.execute(input); + + // Assert + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testExecuteCommands() { + // Arrange + String input = "echo hello world | wc"; + String expected = "1 2 10\n"; + + // Act + String actual = executor.execute(input); + + // Assert + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testExecuteAssigment() { + // Arrange + String input = "x=2"; + String expected = "x = 2"; + + // Act + String actual = executor.execute(input); + + // Assert + assertThat(actual).isEqualTo(expected); + assertThat(environment.getVariable("x")).isEqualTo("2"); } -} \ No newline at end of file +} diff --git a/shell/src/test/resources/Empty.txt b/shell/src/test/resources/Empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/shell/src/test/resources/Example.txt b/shell/src/test/resources/Example.txt new file mode 100644 index 0000000..8ab686e --- /dev/null +++ b/shell/src/test/resources/Example.txt @@ -0,0 +1 @@ +Hello, World! From e32c43cb854d524d9b7ea294453653ea6645f84a Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Wed, 4 Mar 2020 02:36:19 +0300 Subject: [PATCH 04/10] Gradle settings. --- shell/build.gradle | 29 +++++++ shell/gradlew | 172 +++++++++++++++++++++++++++++++++++++++++ shell/gradlew.bat | 84 ++++++++++++++++++++ shell/local.properties | 8 ++ shell/settings.gradle | 2 + shell/shell.iml | 2 + 6 files changed, 297 insertions(+) create mode 100644 shell/build.gradle create mode 100755 shell/gradlew create mode 100644 shell/gradlew.bat create mode 100644 shell/local.properties create mode 100644 shell/settings.gradle create mode 100644 shell/shell.iml diff --git a/shell/build.gradle b/shell/build.gradle new file mode 100644 index 0000000..b1dbef6 --- /dev/null +++ b/shell/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'java' +} + +group 'org.jetbrains' +version '1.0-SNAPSHOT' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() + jcenter() +} + +dependencies { + implementation 'org.jetbrains:annotations:15.0' + + compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.0' + compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.0' + + compileOnly 'org.projectlombok:lombok:1.18.12' + annotationProcessor 'org.projectlombok:lombok:1.18.12' + + testCompileOnly 'org.projectlombok:lombok:1.18.12' + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile "org.mockito:mockito-core:2.+" + testCompile "org.assertj:assertj-core:3.11.1" + testAnnotationProcessor 'org.projectlombok:lombok:1.18.12' +} diff --git a/shell/gradlew b/shell/gradlew new file mode 100755 index 0000000..af6708f --- /dev/null +++ b/shell/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/shell/gradlew.bat b/shell/gradlew.bat new file mode 100644 index 0000000..0f8d593 --- /dev/null +++ b/shell/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/shell/local.properties b/shell/local.properties new file mode 100644 index 0000000..8bade8c --- /dev/null +++ b/shell/local.properties @@ -0,0 +1,8 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Mon Mar 02 23:59:06 MSK 2020 +sdk.dir=/home/pagrom/Android/Sdk diff --git a/shell/settings.gradle b/shell/settings.gradle new file mode 100644 index 0000000..c0a3f2f --- /dev/null +++ b/shell/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'shell' + diff --git a/shell/shell.iml b/shell/shell.iml new file mode 100644 index 0000000..8d81231 --- /dev/null +++ b/shell/shell.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 9ff5e53d6110fc8749aad1d3e20dad89508a1e31 Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Sun, 31 May 2020 23:37:17 +0300 Subject: [PATCH 05/10] Rework parser --- shell/docs/01-intro-cli-text.pdf | Bin 41346 -> 0 bytes shell/docs/Plan.txt | 11 -- shell/src/main/java/Application.java | 1 - .../java/parser/{Lexem.java => Lexeme.java} | 2 +- shell/src/main/java/parser/Parser.java | 177 ++++++++++-------- shell/src/main/java/parser/ParserSymbols.java | 11 ++ shell/src/main/java/parser/Substitutor.java | 20 +- shell/src/main/java/service/Executor.java | 85 +++++---- shell/src/main/java/service/Shell.java | 4 +- shell/src/test/java/parser/ParserTest.java | 38 ++-- .../src/test/java/parser/SubstitutorTest.java | 18 +- 11 files changed, 197 insertions(+), 170 deletions(-) delete mode 100644 shell/docs/01-intro-cli-text.pdf delete mode 100644 shell/docs/Plan.txt rename shell/src/main/java/parser/{Lexem.java => Lexeme.java} (83%) create mode 100644 shell/src/main/java/parser/ParserSymbols.java diff --git a/shell/docs/01-intro-cli-text.pdf b/shell/docs/01-intro-cli-text.pdf deleted file mode 100644 index 6211335caebe7dd62bb66f5b319375926e6e57f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41346 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 z%0D`$Yo)053EX){>QZrl!JvigUT3#nGNuhGBZ4L=18k!`z&2+Bm9V)o!bBZY?}b=f z$FTUUqNc&}Jj=C$2t&4^l`GTb^~i1moP0RX$9aNOBOuL^Ps{0TI1sj#zMLbD@jN}j z?-SXk@-Pq_1N4R?%1J=q&MI0@D^$xm14Sm_;CNw)prLb!UDn#8K^zcej_+tAQDAx3 zrbPN;mWagJ@wJPhii4@`N*OOCN@#b^jzE{Sp4EUD-pV{j8ZSZ4_XYXl$k>?qSK{3( z)XGAVX-E{Be}k41S3lOUH}iBRk^BKm3ALv5Hl@tDiAA)|B@m#eXr1eQ--P zj*&R<^F^wsZxcO02)~2hQC(@H`zD5JHf3ew~H~rhC3!Y^5_x*)(5jhrWC)s=`CQ_nXMOqQ-OVEtZ4#2K*y4X9p zRFnX;n&8pu;mH6f*6igaR1bt_d1sW;ISG1UAysCJTrSTGeX*B> zgLcP`)7tg*A*Z3fI$*GVlrQuy$Y_7K6MUlZY4K?G;n-a5{yDt+E;zm6m~;8?)lJw zk)A%aS1vwJ{hG6rHC)48*s|D*QM^H0HE{Qa{g66LfMLN-FMhm-5tW<6DSL8U$5$Bg zBRrofshcK9{jnlm9k0JpHDL)U?s3L5BbsZYrPU=8#8DPQO4#L_>TI8$2rWv$1w0z^ za2P8&_!t{O-RHq*M#1}JCEG4ZJhSZz2i9Aog`UwV3Kae$T-A@AWXT$%{mxQ%=$F2T zsrNP*&kxd)A|x5u%|?G%VJDyO{z>gS$4cjq4B-KR%%sj7XE=1FK+Had$2&|9$TGeD zp+SFW(MbxzT8yJ29*Gt8XbXnIEPhpkE_$zxb6VYAsa>A?*Vv zgZ+Z8D&a^X3cNh#G(NhI!bv`#h?9euZE2XaP5d;!pZ2e(sNN$QaAaD(*r~9oES49; zDKQWG-+ykhh$*{vCr|G)>h4xj=8C*4=im=FmU_PGfcrjA8`9WxSJ*1PJ}T|NJVYCZ z(#T(Dqs5yjucpi>^8_e-KEGb$^yv70)x)iZQV&GNW;m8cR5nt7FBl>mvX%tqpGZaH zSJ(hs!0%%}sJ$&m{JbzO*P6&!&ky^b58r0nx+jW3Z0dxya$h;1On$o)5+CS+B-h%7 z8--Ppd8nr=meu?e-C4nEznJL(hN=3Z5tdpY{~CuJx-DFMH>kQ$UQCO0Wl5n?D-_Gu zF7P&to;V^Izs%>QqJ&9ClqK)Vgj z!v3xO*o=;5mg$674Z|0b`zcypur7=|eoE~D-_F~P{2Geg-f$Vnj(Jzmf<3eR{a}_E zZ*A%*Yw@RV*3<#n^}Q$UPsCw&Uv*p+d_BZd)@M{iry&+JpSYYa6iuGw@2E?i6k}aL zHh<&|t0_^9>o5ERP0GKKVb#Q_?>G6ZU{v<^5zn?D__Fuiq6{uyWz}a&Cm~9}N3D!r zC#aD{Q9eO6j{Di*mS1<8uHlCBtRGLGeP`~8A|iWp z`a6%HbyB0JGl84c?xZA4O(fOb=p!4u_#tN{9+btnm3g4ozBrGMG6vr2NOw&7LTf$> z%yojg@VRrYOs%y8uF_80aK1F8|4DhD9`zbj0e%TwLlBW$Et&$a^t9Tz%3DlnzDNt3 zjkDsG1;Yx~ny`y`CaYP;<7W6mif+fe z72<6H*o3^Ba_+TFjj*=IrPYXIzxfJP3Yn!bl)5FxYZAD?P4WI|7P%c|*d(>h8Ip|j*#9;Bj~hQ$l)Di8NlDUG#PSbYsKb$*ZSPBW_>ZAv*0F2$l>YKTtU+yJ!v@`sV#q@(g)&!CGe7U zH8pNjC7!L?gXprW#Qfor?;%iJTAii8Rd<&*&mgC5SHaNluiD|ru1%u~D^7WaHga++ z?LHN~_EW>8om2ZErXU3gMSsYfyu~Ry;rm7w5vaDq7Mgl_w#p4U+O`I)+uD$N;Cg)p z3tw+ZJ!OMOw!(XM3pdm+=`R-S7i^XCbLdLtMxb<~f@Rbe?8}sfa120z6CUdcV+xfc zmwjJ8QfGKhHV5`2^ifkRB+$sDsMGhld6+QC1k9MrAj=`}i+kIxC-qcW%pY;u&RDQ` zx-Fb#pBuZIDqH$;4cI)Cdr^=Rt+}jRG=r3j@K_+P_e#~&T}*TzCCp-#8pl=h*4p-A zu)aI_ERb7RlfCLh7M@ew3dvL0*Buxt=C1dI!1GyOXo9el+Vm&(?s}T+Y0Q-(~S{$ zb*C|8YoZw0_y-ifkEC;h+f~8pX9Mb@N7#qK#L{-b)iH%|PErYXVwke-RP8IlxuQYS zkU2RE7k^}q0i+0RE9u2B5y;R_`=_S0@*P~2{psz^{A<$`o!8_Fk02EjgY~r@iW`Bu zmIH|VCWa}o=2Tt+EOyJKcS7BYwzb3kfcK7r7ZI6keqH#kmL9wT2}h{=CBU#-8fhk7 zi(2wx_ODp4_qQS1EIPu97JQFT^>fgxyF}D_H7t^6{H3cF&_A}g#%4L7$&9TWQ^|hO zWe&zQ@Q%qA#R$}~GcFS9P2w?BK=|#I0K9tRPS2Pri0mPh{lx(bW^jQKLH3iff3kVX zea$wvWyZBJ))NOQY;F75HBd|e=at00t51-5-BqRALLl%`LyXsl8s3;dss8LAB_P*` zsV-&ud+>AbmMc`pYE^{Pi%Q(C{0WroTwMO0r3pudU~OrNScw82&G1|`U7Pi<^Y-8F zS9hx!8t+@H%r0X;gA@yzJLOd_o9E;@*#kg^^5=VdU%qhYnZPz>7oKk^j*-DprSu!H zJZJ(#pgdlq1m`m)w@^;2=dr_3!@a&Zvtvb)JJ|XFsfApPBRkFHdPo9LAO5@pMU@Sj z3qZ;(B)2zC^s2JrI zsy8GW$6=iYH@ot_TjB1hf_Vw9L)>KtOoW-=5>ca`rB)C&RwC^2MdFc%gIR5su1Fmd z>%iZzS?`z-E5QhfxxbR{&%C<`1fSlSz)RM+OC18X7WlllYC-C_tN_A!4o7sj;$P6B zfi@&QYM5k1JcgT-W5P51OOR{Z`F)N))R@y^Iz^G<4!{{oy!{DV8E)wdkVADhZ#OW1 z9Xl(3qP_R0@aq>;8e7Q5QV9@{X2_YIHrrJI@)C<<>y7a74h@YjcOWu{AiBG!NqqUO z&xuzE_%kNvgQRz5X7PY&-nqIYaQi7XOx<3``>}nkD{O)Mu6G`AR;ww;vZfzWNAfgu zn9`$IT22wUhqF*RNS0oTSr?M$E0{5a_3TZ;!XIn>W35k}!Uw%>UJ&Hd=}lrL(UfPZmH6j*y7)(hRtIU1sT< zd54_>iEx5H55qd4C!S%F@$@)_nAkIU;dg92uED5V*6r@EV_dAGe0}FzZy@>S>@~>i zw^EeT*mQg!hAB1bs)tzH4Jhi1hfe6bqg?SG%C2K~`i?925ic;xn|%NfkDb+bQ5JWz zG6slwQU{T@P#O*_b;zs3`1>9;N&VDis}ZAc8q`7{ZwhXOaPwOsbfBWQhea*Z%(y*# zk?XhVXEg9Fn_;SKn@A_F*w~9Xs`{K?%y{~m#Fi$9bCax|96{@u(&$Po>4o9%>^9_h zLqIiNwRr&lC5Z9nCCF=xmnPqD`7&F| ziZ(+s+;q5x;zG>dIg+WYHqCkCu94*3cxpR_izH)pudgpC$BrKh1vpjM0$<#<)N%JqXE32~VVvf8V#o&{s_>it# za_Ox5`-pflG%FE-!DJr=e_DPPi z;4R>V^NqUGH>5f82gSGkDF1gavCDe6)R}i&iu*ALx!cd0fp*2rOXec!D@t=uP3xDN z3YM9+qE&sV?2B~$&k~3l4#vpO*_a<^;NNz@k8jqCSty*Zsz?Q_4YJwoK=mb;w-;B+ zlj8)3GPAUv;S^K^OoRDYY-*}nv$UB_!>6jp8HR&_3Pzat7SY=U=aWBG3TF8-+RDgL zwtj?wQeAObYBN}=JJi3%5Yn(PDP4dVH5k)Zca)@EO-*%ZO&en1G@k-_QQ$y{6VTZ+CORsjBXtCyG+I$k)e1S}h$YXR{cJUoUaLfYFiX$p4#S7#aQ>ieY4BV*8&I z)ACP49&y*d8uC5@65g-K{?(A@1#AY!%7k;aki&8JOK#dTPL!3oFg;>OSY0ZD78Ul) zu5z{XEBj(R$>*iHO~L8sdg%>iYrlewD-b{;=!#@%;5V zf`J{)@cB%P<;3u_U)S;V>aC6Y)q(lZ_jSGhyM6cykFLM$N|(oo)VKRq;UJRll{$C* zk*D`dO}VDTdpbgsFQ7mcX8&P-bA_flz&Er~7~N7`pCU&;K%##AVUQ+$33fQCR~6Ei z=)K9B8IpN-Wm9(u#9iY@)84`}xcZ=R{`vCfce5(yPAl~MqHWsHN#mc2jF{|0w#X=$ z(6Vl#8L&3KJSM$cYS62S5h)tl3*Ll+2#*NZ88FK}xgZEIq^-Bucx^7%9t~UAIw15B z@6_;;ZFJ-`z!S_0SfH83|HY*ya_@yQP^B)h9$EFr0}m4dI9b+m{7iUE9<{YRj6n+% zQ3jrK(A^AFz2kZ!-B^?ktKNP?x-Xiz9lv@?1|F@z?y69o3=aX|eT>eL%jOY(C(q{r zbnCdgHlu&r$d_jz#Hn72Zy@-fpg>(odw7Z4U4t^u>)raeDbq=Ri%Ed(;^;Sf%dQonNx$M7;-Sbf>>JQpgczq-DIxyqJM)bngBl>HScUnoWlJa)umNJpfn!)HOkva7ILJ?y7Mc@#0qiPT)j^+d9=S>jK$%F7wW*Sj^H zPb&?HL~-HBNA%@(T@EmJ1|SFKT~9Q5Gca|DeOP4}8CgLHnx>=`48}uS7o25N!-#Nj zqcsRV?ig7{7Pz1{Q^9aFgn&m-+*csSO#H$UvU5O+FXY@R$cV5UP!TAwfe@fV#=VY^ z8_?abUguh1?7`|Atsu#LjLs7Oak*6y*0dD;TfC?exy&|1)db+Sv*CneEEQ4ghA0@_&FX%`o$js11%tksvb%h)b)0U2FJV{ zFcr*NCV4U0R?-oiC9Y?%^&`$$oNJ8-uJviXnp5+heCpOFvP`*W+V79{Dn+Mp8Mw5T%H4iOKlzPN=r1 zNJDEWE#IW$oIvvFNHs__4TDZucZa#vO?h5LoS&m64XvKB$k_{hKh0T>X9>4F-pTr= ziP)res I8^JFEaV)n76h=;EthY;9e1ggO=0e1N!~{+;yapg}|BNQ$--P;({7R3A z*U=QDfop9{OjE9UWdcPY*8rd?C##J1e%V#JU}6$La43??_9JRk?7llydF+@6?K-G| z4bgA@E<0;&w5xeKVf)r%SjKVMXDyAvoxVkTOI3CjOM~4N!OO=Eu=>qe5}op zdY`{SAwb3{KqO3U@;n&TMjS~W$*~Kmc#{0D6mc%57acdHksg+ONQc&jsv}{{pI+nR z()Tf(gj7k6N4&F2p{c2`Dn2zaz`G01Kx$w_@bKt)gVDnsJo2`$F>0_fp!=4(4>m=s z2=LDC`0<=*B!a#S)|D^9Y6U_(ZDa!vJBHoYiWcshHR>Hk=o?qXDeymI7QZ`jW(d|M9wRzf(yIh(N_Q%wP}hs0RLJ=e~mC*dnOzR zl_<~KS$}>3C zy5Pchw^0R4yp}eMZJ^D6$>CR4sq8gSOQU9zT4SE7qcKtV0L-BPFjV`GCGjnS%u<_!v6hdw`D9+M{0nqRtd zqW3;3MXCV?3^gIO)F~C-cICMKU7_|J9_V{IvN$h{HM>~nhcB}23Q;jSU!KWDd15cD ziumB@L-y15dv9#5=B&7~Ka*+t^PrEFOZFO#}-}0~$0uBCJBW1d{Q#J%~!;}WET7qgj zJRrH`?B{G{x5c*0OQ9@en-r}rv8?(g!;)>_-}j`Fp?IZSB z{n`OnMGbomZv@_s6jH6JM!T9$#8^nx>x?k;q6XWi&Yyp}J@)iYQY;G`y(X8wPA~k) zt4yNL^BFq2?!X;idshU~nB8YHmW^+&7vq4UL-t(&PY_dGrT*{6466pP5aM@l%PN_N ziUGKbeV$YmatlU2X`6m|mo&=w8n7-e~_fi|CJSYmn{o(u{u8`oVe0?n3G91aYW0bq+WFHUiIsSAC$3nt+X431eKm zq*qwiwLcI%rXvOg_fdDCi2{UK5h)fivRF;B{(xN@*++e*Csh!VTQUKhH|y45!(wcAX%sOV+d|DXBXs{*vW) zu!7M@wB{?@_h%6dQ?)aADOo9*I)x76U;yUBi}Dv1AzFX%VFbi`YlQ(BD5xOjDBp|v z4?fcp1h;qEkd6xPJ!-fxvrX%aL=mG%TcT(6u4j*D31q;N8qJR16Nx%qqo}K95w)h* za}bdjs43dX&2#xfPHJ)eX#itK{_bOt9ftejh7bVziOfBguCy(A`!%5b>xvMV_hd9d zm~#f|7LlD zVB3iVeo@%1tYm#Zl|U#tMq?LcKFA2E`0cO>eyDAz6)CXm-?_%MiAQ%$wy_OfB0KXbRNyF)L?@%Y5ta3b?90>GK&M`08 zY%rkOdKh@0YTD2rul($Y>^dG%Afq%Z?af28X7(aAgx$!E@Y((>wCYdMQ8JYL_6;T< zb(`a+=)m-vi|EvuQ_M61a(Hc_|6!AG2NixHKcRE#{_FAuxa2W~$D&uQ0%dcn!07y` z-b@2QH;XGlkH9`Tkr|627KH?Z!);Q(;ZpTUP^X_@?1-r4Lq9K7Q6QtNWe`Gt8Maoq zmz-faSQK7iByDB*BACIQBZ88U@N1jL(u99X|^;V!b1R1|T5A^AC=h>8W0vd7pgJ1S( zEgN>C<$m5lP7@5B7GhIU5kkkO%n1~kQ`Ue~5*WK_N2-!Glye}Pz_N$0`NBY=hLaqW ztiPG{QO0NinG)lwZ%+y$%#6`&JO@Wpp_OAL=bW?5EADgmR~1XIO=Ew_0O=rA?9Z@d z%UcyXpOW_D{3ED?BBIBauEBS&l?A%I?-rtcUJ?Mcv;V*2l#tBzIhw=eCH zw1V>0csBp=c=3@GkZW+V0{c4-SNgC0K}=Nlpd{(p{_gPrp3R-2N1)vf6&XoqW^0J) zM%Y3z_>zH-UE`_zVu9T$xVOP&dqP06kNyp50yA*cWHOd|W?k`Af@F(soJg|1Of`@) zI^H*yb)NNE2qP??1lh>zzPi@jz^__^ffUb4k&FvV0ga}YQMG{%8Lc69#y|;j z+fg1U77rz|08=Y%sb7X26h$p)Rj9F@EWSTdT)A%S+6V|fGX6WbR@Ep|NP5x)FHrY& zJ^c%@X?QmIvbS zeRYhfmfF388OSS~jv#v5u!_>d5SC0>y;4lej>b$X#dl8hIIUc_uI{|7iLtXa!+T(v zuT~4Hk`L0`!|6m9CgPjD0*d=rAMRf>o}$qdTXza=8tj39f8^>~2oeF)X-WeJz%eVt zSWn!EjvJCU1Q-rA66M?Dyz?1roB%^JfU?uZYs`GwxN7-r3`c_fnO_>|=3mg@)*Tqj+2l~b;a}JHg6We`!ibGf^U8vTHZ@T>U zKzc~`#?5HbhuuMO1!|)n4S_Vy@<8!R?UjY%n6KJyW6z!#76@vNq<{ilwaDQxe9gTq zu+%Y;#wRUjJ6;lWS?$C`9V>9VOTi|Z1qs@NVf#j^2v;)xXN=Ar8C7@cjR(|K7|W>p zGJ%&*?vwab0Vp0Vw(S-aj*kL;BXu1ZTdbP<(lyR$l?~M4`VkcE!nTY5VF=+Xdy_9i z76r+LDF$w5*IvrrV-7=Bda2O?HY=%eS&}bCEr)pecW`yyh*JDBZP>Pl6s4Wsao`Ys zK=oK=h~H)WYSWesT?6-li%+Rtm`<}Gsxi}0!xY0n+PE`)1-7Gfh4jf77L5fmWSy>i zxdY?kXk*v)W$k^Ja!iknEppeNBWGydKNT&)S8MmiGc`Bt9Todl4fdVlp7Q){G8))_ z3UzBAe8YYDRszA~x(vEuzxA}OJGzjPYIf7ojJ8MNyOQm+9Va5~74LCqYayC_5KksIM&bAvNI^3Y!|`O|#7E{0vPArXIZMCydvqspppVxJ5mec+$}tmKX? zRZ+1kZhic~>y+aPBb880Ni?gtu;_d?7RtApMYc}3ml$4-QN@(kpwAs+)#1vm+%npC z;w~i#pC?;@GUmfP!E6MuN-r{;!z2f}62MPpB!hUwn=&R!P?2T%!-xmWXNZ`2!Zzl^ zn!^gZL|tI5&{S@sm$n#jopj9bq5pJ&LB2<078IM>(N_u(Lj`gQ%sb5MQ$i1^I?{r9 zblqulrP3#MtWJpx73Zh3C z+x|HlsLyNvSY zz+=}zhUB4yX9FhDX|LGWWFA^8*G=M%gNS&YbL)IKL<^*h2G7Eng&Ivr)${_+G#ecw5&GEcnTF*a`%^|VSahGi3 z#}wKi>LFASH@VZnIm_Ga;gJc7E<`Sh!g{<6d#N_oO_KRzz86GG@Hy)Gy2Cx)XMi4~?OCtWv_*`f+;m5a>I=sJn)3mpof z*Wr~EVGAlV}4!vGWsOO?NBSQ?c?__;X-*hd-q6U}>!;Y-o zDLNuTddbDmE@Z^brN|NVIkLv_l|MdiB#W{x?>gpiuch`=j#uFm;2ik?M*)iR)5e?m z=<&dk0$OBlppyL9WMe?21m*4W4yw|*@R~s+5Se()ba!Mz}7QE~qo`T#)*-eC5Y#(o3S!y25oduH}{nXt)h*xk_U z9#k1NH77MT{BtK)3ZYB>c{!iDlh)jA&SL{x$9Q|#%^6ep+4`!(XLkPxE`>jB{2$`T z^#9J6l8d3UhrJ1%kc6%SSZTCa(2Q=YDOh~29*3PkJ5CrcnJXz)YJ9y2qPSM=7FxmQPL z?US<>m!_{djYR+pf~R>&%@7jGlArD3A4&=O_pHjtJ%E`sznoIMa``{C&xUfxD?7lR zH5Ux&J=xs_WAg8O_%9fx@>D6?UOk-S*J(;-b`NWN?eVyQj8oyQj8o+qP}n=C`&^ZQHi(cJ9f%n0Ye4OrFd?aP1d6 zJ3DLTv)0A^pQ>Y>+49-SesK0#bv9aEEV|?p_3O3WdB)}V&sanYK2qlJZudsq7{k}o z=6Lh)_OK?88sP28TBcZDa9$(Ddj2k?sAO+jS3I_ z28jlZ`@jLE29X98!_s;+T4h@G8ih4VHEJ6)wo zKn0)*Pzk8EtKe7MR@qkCR@+wHR`vya2tAixBwo-g8!xX`QB(<3_bU2Uf53bl%JC??5p~+DP>Z~w`Ekw$;quyW1-JmDpniyCcW1y|46;x zaEAhLywU2;6h^4xsr|23>atzirnieak6k~d*ksVktT?bNlT02|__XlKW>%l(+O2OK znmUw+f^dDtzsS@al4| z_1XB(sk=SCtn+(0={?UpWiz-?hSpa zzuurV5IzEP?t7~C;c+^58iPvdQUtoOC!r#Lcyzimg`e);L0 zc(L*s`RzeC3^zkk8l0=$zD2F7YOGkREiJiy_8Fp`?0nak27X=s;PB9fw4L}GG%$^t zt2@fcH9&RY!WIUoT-VufInS8|qOXL|?)GkZroDdP9ZJNJe=Eri-hfi@TXDhXCs3fu z#V(M$>tVe0;kiEU+#KkgDQl{#XlS0R9?%;phEuwzeYwb0&DC6tVfevZ=j z4j4A{J~~Cwn!J}E8QQ>I`vIMw@6BS##(B17G7*|C$9m=xf^f6U}SIwblPd>D7 z3uUXD%@m2LSg@hfH_yoto3YoOnok!_qYobkJB3H5m3vG5=f|n1{OE{YSt<|qGeA-e zBL4=2&TX3qASr8p+yg~pU-T3bYxT0Y4B`o(3gDsN_VJ1jF|RIPo1=OnD;O7q=L|;Z z=vCVZdO=j_Hez^GwMh6l=SeREkp=EF?vE)%CuNiHeWjG^PX|f{hIfHL$lRV{hn||s z!$`$+R}6L3U{Ht;J^RbYP(2h7mta!=9|- z|KHO(X>tg|-B9Xtn{>Oar5F8i-N2QQk_e!WwV9#0bS$sJUkn*;f8-sVStyqB#8Rcc z`mWtOX_;Q`8LBo?x0D zEN9mW53GU|A_puw06reYpY#9Gy}jgS3kS%7QGAs7wfML>;N?vZEwGq*JhvXI{wQA3 z^=o5Xb(!YRn)<+c`^5>13YMBK2Eh#YzupHXZ9BkeVdQ$iR#2_U-{pwL%wP1ZW>wD) zf?K!+3^NY+9M;&9h8z-Xiz^dwJ2XCjHVuo`9}iUlx)^umzZD{<+fmdTRpY03LwKNtujhhIFnbBC|8x9Dgm9f)#t29O2fGnT#oENsnx#14`OR(LTkVj%)CcmVHi(a2yDd`MAbUpR zHpY-@z}}cWT8CA}#vn>=S{I*<%=8Zm{KZoH*>^9o(L(UFXj9KRE5E8AM_m~SFXP@N zWR>gOgLF|gulNAvCe|?T3w}GS;+@%HXwnIZd8-+cX(Z4WBDun=ow<|Qf#}~u!!&G4 zrjy?17RH1m3_q5z=-61|X_x%?he3xkWtAwg0^TqBh1lXDU2y3LbRFKRr83CzIop~P zp!0my0lQ6$sUmY0fXD=a!EYoK7D@S!>hXa?61l>Y3fI^7uk9T?%rhv(nuctu%{K~n z5<-z&ktNmDP7Vd%_I1AbOSA4mL6U!yF}ff1UOiQNgx<H zHZx1qo3jtw&C1?-w#6i8C^ZTAh1h4Wbh;g!Z0&+n+1|<72N^(5m$FGFx<&)aAS!wa4MnPHnG0BJ^r*xPV^quV@XL2K7R(b@OEw0LSNcV%jcK++7sS@8@i^)g+j8Lm538LBnJp4y zsGq-~DMceFKRDy*q8zA`p>ptZvZhAi<3^~a4WK`NuX=VPb4LS1dnYk9a!02npxA|I z40X6;tV3~iM|g&siQ9o^PmOA?Bx706#><3sk$+WCc=V9#8F2B{SxP5G1w;yx5uFqL z1?w4X0$Y|zGVrUjW4x&nPL@(Y?sOBF&@527 zLme7kGlj|G1rT&CYt`WC1-UED0lJ-~(C(*H`K^bMZfF2;bQyMB9a;IlOqqu>djA_U zlTQ2-7hMY{jsFS|gt{KuH6@8M9YXgYc>k*Rp5a-ZN6Qtm(<2r-TK#UiYS@uBZKTBX~Hy5jQ=<8jv|(-{V`@U0>;`Qz3 zTfKGV35{w}Z)l^mTmw8kt&p}>_vd$68!+#Z<>GX-6!va5%Oz}@jh}hLEZHZa8Q7h; z8;keU3qIOODAiJSI69EgT)Gi&>R!%%M-l8iVt_3>LEh^!D16qg^T*8?hVL)rGc1xG zw^nnPNu+~wQE7#Q$#~y4GjnVq<5a(TzuUB(puRl?OJ3qG+^F{`LKr$h*Y z_|(Q+8&SYw4HUh@n=B(?EPND1mp)Oo>^;}5nmiccxc|Yc=GH@du%KkACt?5!Ln=Vz z0UytwNr~Tv)I^4_mOO=I;CBnivQMMsK|BJI5ViW3ZiO;S&P38Tq@cJK5PnquTZR{` zu_loCBEe64b-u=3HTO`Ms;LkfI+MhpG)|v1mf)5Ow2>Pz=-`D;0diD{clf=qWev3*AbHV4#aRKQAnovn zjlox#Cr%ITNa|72(@P5Ug}=ovCXK8+QqRQ%uB%PdFK4M?_39~DGIZ?$&tI8&_-6p5 zi|y8GwEy7H%|fcXFv*Sz%+*FuEDhOhaHlpO#Odzc)&s^pUB94Cm*}gwzkG$G;p4#L zFwfZmp1+~4u#PeAuD>?MqPXYzL(n0#g{{dDSH_P(&m3PnUOhQrhP3QBW|QY3MWFdm zUY2?*g4SC9GOCEj>Cf`Jb6Y-9mfqgk)e03SXf93z2g1A&I^;Fhy8Gf9bFP58+_D!h_%X#lcLq*I{oNWt{P?0D)dzlafEP;o zRexdij-~~`-wftd&`x=+LCeFjo6pn1LQ-^rr^IK9R9h-sihnV-b%rYuK2UQscyBX~ z;2(#FrtwJLkCw|OHnP2-k~7D(40Z9Dk*XFa#hc(x%J*!MrE{qCm=w2!a%`SoaR=qA zno;41Fj0xdL+;SU%u?NqC|$8AUp_DScqx_h15LAN&Fuw?$hy29s6CWV?f@VnqTgui znX5cxOH#;8Ck0GBZ^N=FBq(-;r#J=n z7b-^(W$DbZ7isq^$S~$&S>10 zY*-Y)``?s+b&3akh1(##Lz;?W{Ev<`9K=Evy)Wy{lXa-VR3#bmN5|JEclMQ`X|Qi(HfX4 z1r*$mRtnFTQmU>nWHmclD?-q`91YdUTtgd=$N`=;)cv&}*WTW=Rn2R7azs>X(m~40 zFONB`+MrDOE|4ANqjk+ut_hX$n=(m0OH}wBz~F*Xu#Zu4Q#_2BRTW0Gc#}zmvTMkd z`W-cHsTw2`Kup}N)*uqI zoa0pU&T+8L+8b)JF=altG6J@TQ0poBPJlFrVy%&B({}syD(pIb#^=+P*@@wgKy+6$ z-M24{UFw7Ya+*%6&tNp*yw&rD^4ZS9>yCV*@kRCu*UOP(k@5Q@=VB@e1tu-uQ?57B zd%hV<2LIkk0a6lus!gA?IiW`JQ_xe$tN$otD+ZpAC&aI~Z5Lz-z zjolOyWgI)C87%j7ea~_%R%T7GHQ5)+2jPR#=bEElga&daHe1BKQHag)Qa*w7FZ@u# zcYF1NW}YLD%|<3~UdwX-Zf2Vv`;5-BjDp=wLv3G*zs(dot9x+|ni)Ay?lZZK_7nJ) zhdNi|n2(}TlUA-9Wpmc4jD>w&Y&51k<+jY9J3#Vt!Rrf)swg_3b*Kvn8$1Qe<9w|gF z@+Ja|Oi<8`Obyc3jgHhxu$1NE!`-b_@wy)yH?+gsv@h>6%! z`?HrQ!s}3ZbT#nIpxUAgK6PVrRFyO!HtjdK@}T|JJ+=__?K!%Z`72|8_j6bkDCL&9 zB2-3UCK$hc*0Bu5>uP_c@a7#y+PBtJ2r(S<|4HZV?BL^v?i{0u zhN2zsh}IC6`x<>w-8arGw%2i~K2tV5;?UG={TYwZ{AFZw6`{6d*{D4smO| zcKt-bnE~tr-!-FTKblYqe8sJRq+io>Cmw`eA1yOJu)aR=X+Pj-=_koGeuAu)y^UyHCEn5kajnB2JkB(P7EIz!<_e{`_9J_2JQ6 z%4DQ$)C{Cj3RaKK&F;`Z)M4y^vMgk0j7|~P# z(!p7in5wniX9E7rm}oz&e&Wk7)dh#!jjM*#ZfSrmpt;{gX5vXm6Kd8_RrMgU{c}mj zLRDC$+H_LW3aB>WUQ=kJ)p1l~rb+h{h-mmHzu;mho2h+4KGNMI-X!Kk$=1brLv#NJ zm%)}>g0k8&9sKM>j4AXJ05OjuI0auRNyYkM6H| zU)g{qD5VsP$uZ-9L5xofTdjGJ06SG&g=b6G7u#NXX8 zjs!xhyh=$q>4?by6bopJuv_(B zD*PWQR)o2x6FTk<1A`BjIMc`$akN7XG&2eI0SD5o=G=P-Ta$JoUS=EI>qAv2KP1#H z5fU!jNF+8LyH(L_RK+K!NRtyl4sm7Mg1qUpNFMMu^|ixezqre60}a;Ho$d0rc@ zodAfrkdf~_pnKx)=ykNz;;JH9R~>3_3Hai807dAYPm8#*AiQ=~YNig>WSZTQ<1$Yy z&#Ojaod&JN*`JnAubzIcuzrV(#78LEsXs(i>lvBycE)uKXN0anL^D7TP!Tb1Y2hW- zF*!7EnA_RV*TJo?_2JxgHOyU7{9ZY6)mw>X23t3WH#Zi;a|2Vw-7bo*R2uDTO#X!< ztgq`X=g&T-;mf~ME|JfE540yg{BR#FpW2F3@>LY4KHVF;@PNTz*AQSoBChp9|`f9A(IUO14(s z{FgnLP$RiTO)Wm~wJ)p9eA%dDEcxq1MCeS7INeE2{~3v?3QnmW4SE^9NQy?Q%&;;OFusJF0~=gQ=g?WL=fQu zIBz;}6VRSJ5VKKtndsj&@Oe15t8Alp2XEP>k>|Rg#S8^h%;ua-y1?Eg-VdIZWSnLM z0NXyL0fLd9#ID1SLp1GrE30X!9EOi!CnQquEoY6ty}x6$fScW{;rDMp^zu2j{laaH4Wuh`w*sjAuVGy8yZD9zp$vrL z7ur4<5SNQ8Q^eiBbbCtepHTHU!63fR0k*V}=G(r`+|*h7;)19j6Mv-)_+{Lm%|muB zAQUYX$A=J!&pmCtGFy5!M0oMjd+F=(fUBVp6yRa2Z4hqN;K!V6u#@k^=pyGxi!SkB4;!SV^w~u*RjnnW(K~F^qzW-Q_Ww5 zd4B&h1iw`)oM&B^HvT2K<^}hR*7@3FBVjZz+;RA0d0eM`{=_8YkCJ|vKbCLs+I8xT z=#ZT04C4iF(Z#|-Bb4Y_5FtvdjeLmAh{I#W4=&-$(v!tWk_OVqa=j?t@U2Xl<7!bQ&CgbpvVdf$JzPdqhhW^xLx}^w-JFv!E@sL=T&9XNXyZfsv-_* zg>>@?0b1SS)**BPol= znv8Q;w4Uh2jg*M88lo9^ja}9S+phqaK7-e-)&=)!5~S7on6IxpBMyw7-xv7Lt=VW> z1z)pW61B@iV^?AA!xqHj@%C(iRh5!gl)sRj1#}@~O^D~!8WnCol*|OwL_9Q^Xz0TY z3F6hJ_4*+`T?4X1&6qEoZnBA3AaMdcN)v4o&TA7p$Gc|N{ zhiV5b20M%;&kLhO(dgie>^;Q&wE3W-Xywn`5b8EWOKr^r0&*LmEaJND_ z%Ad*sF1`+Gx_@*C>5N5(@o3WoY%*PP!4(miNawg>GV=u5fp8IJ!r2V2PtK*7zm~bM zhAp43VtLXHZ!dTpfU+i~q5{np_9w%Fz-eo8d_%#&DrS2hr?I*8{e$~Q9T4#gCB@4k zs{84UR1;mJk>6nI zp%E(au^3_H+V+f1a|yLUtzDZtaALYwS<9IZvp2=%Y*-q|NsPr*m@D6FKA0TiC;pdO zm6YyF6nfAQD?!U*db*x=DS>LH=smetVb0Oy3ueV%yzqV9V6_JofJ$=f9cb?C``CoOk-2T&1 zr%+pa zwG-3P4_%~o73(0%ce)Lp)xfJUw?(=Y<8#d-fl#xdCt+3oS!3}j4LsxQMdq+f_-%gT zLDr&3)1asqdbT-<)Z&!mZ?rBk)9U@h_Jj(IzUDO(O@za`NmQpWM5N=Ugl+=-Zg*RT zM%*&A0}{9QCOJ|8$~$zjf7>#}vrmROEUlPsjH zNqE4o7IZ>UirJEHH4`vT@vNIPiL{0r+oRcY3zVuP@cu7G=SnYFcoX5|;kABOwa~(U zzs18R`s(x{d41=Wb)*DJAo1n^J^W+C$`VmFgNP{jnY*D<@|+a58mz#$r`*f-VdtuP zw?27ju0clXqGf`_+=SRXiALQ`GOoYDsw*VH!_y&g`aXPLL`+PYn91#B*)hC`1=Ns< z#c#vk59A7FDtno8uPA>W4rlXCd@Ujm3+;*&PPvx3}>X8G-`_JIk-$r^7f>5p(_-51D7j_s|LQYCrR!T-fDja64@B$qX zWl-}Bjzn$B%{gjkpO7O*>xM?BaEA8Uszk=pSp*(H)Bj&YjLZy849+6^5!@f=G!15@ zp4eR(6?&!mu{E#F7wQc=rvplTe`Tqv_MVhoZ@O)T0)H2rVt*%PJNX~B&zCI`wws^` z%zpk=#G)`O9-%yoR`d=2mNWENq?Y*SxwPvG@ui)ezkSXH4ZZGZy@It=xxi&E7N_l| ziaS55fo8>z`6fVisdT6rNhgv?FnGDoH#(CKPY#Lp(9gR3gwj~#i&{v10aLJb|CRhp zb1&GJ7GEuYq@&z+^NoW^yb2w#Q}J0p!9(+pY=K57#1>o<2?$?rJ`V|osKIMacx*=D z4y+E0GQ2xTf|86Xk&GUAr=LFBj#GE}LH+Dt=J@yVqwWiy`j%1Q2P6C2=gwM(DCKV$ zLH~}+vjE7#F%_r@u6rx}xh1&CSJ0>pR{T2|njc*Jr9rfAL9E?#S*mtm`!6y+7Dh;Z ztG65jXDd2n>pj3jsu77<>3B?FyytDgXJ$6s^+6zMl2+CR z5f;v&60YX6Ga<35#Uu7mx65LDiFutv+O2p!SL;hL&yX%%To&XzXW2e~(GrOJ+1adM zy^osd9fJ&r;UGf}b;L50Ua~VqpImm3lx!~AYUelqGwL!Z@ha@~7pSwZ9oY6afRVWa zLyWUUlAajC9Nw{k!vh#H^g*PD%q=}DGs2!?!uV*8=5C{g4=BFL$dKNM-QL*vzgi4xCZGQ z@r-TL;ssYWei4l1_xI)JWzJT!?UQlgQYC)@@L2k*2HymsW#B~HP;5PwK{hIEhto;J z=?~0-A1p9;+NN3?l{-m78?6RdhGLVq%6sV=&~rzrYsie(ytzW3oUZWCY&n3oOyhV} zRNP6X!Al~2Tm9mF6UHn-r~inz+CcBmD{Z&d7aTG-9bWZ3mePYLx&wojI(eJY?7&Ql z78632MkKJ&huBEn=fZQt`2RzJ&%m7B)!Fm0=W6U^!kera15LQC` z1bg4@o=u<5f2_>9Vg|*7y`p?&`dtixN8c%2;$x2M>vx%Jg|IdJOodPVZ!y~F8R0xW zT9!V`(pSDH18dfHT4|oep>WCb^*~>K48r1mpQ_Rk;-r^V{-vZiGu`jr$=AMr88+LG zSR0Qn`uw+|_Q1bK`8X{RVLgM9>jeFOnA|iqz^AwDONE}Hn!U(0y=GKLylF6rhK0b| z8mDUqUKx$XW8}mY3bfe$o{#V zX4iki88nP8p5cuxAT;wG8(vF-MIB^`GbeJ@yt%~gjqJn`Ey$~m1)T>Lc1mOk8TJ4)3E*$;Sj^?^%EI<3fcT(oB-W ztXWu>_!1Z0)JqXRz2no9yj;TZ88}?)WF)1eC$8p;zmK)Y$1SNM`MiV>7k?qMUX%s@ z2W;+tt6rP+e@T8>v;6Mj{CL+^fBpW(W1c}_`mYKc{`~&__CbUx3L+8`?gIhw%pq+3 z52tbb|A+Kgn3(<-NH131^8bYN00&yG@fv9%5A=(*D|D!&lonvI5VjyjTH4z4L&#|G zgisS2!@Tm;X2ML+P^>{Js*?o@aMZy8wv3StcqoT+VmwU6!bNz=GrcDXH||kKnKN2$ zJD-lPor7H7`H#N4?=PKB>lKR39Xj+iT3;8p-|8LoE8!tE2=n3LA7bJ*O}=+&h}|Q*`nQjMW_*XX5BN{`kNHn~_w1bA+q<^-ZlrH$ZunmVKEskZ zGq>3FX%;scx?t8F*WH~2Jo|E2W_E7~PY2-Rv=}dvj53V`v1)MlF2}(ZSiL^Zw7kVX z;a|!}JYuQvoR!fEJGtEahQC_Tp9bPl9Xz$gpaS1JcYGKz}Z70dCE%fV_H#SihkO_^_pBM~1_QJgv0-Vl^{r1DF_TS^H zJlMX8L3@sL&xZGvIi;bX3?DayygxT;`Fe=(vPD}UGw?geCgu@!IdkZBk*Cn^vxf}> zA!mpFq_0N)#Wp@LM=MhqWFyX%-?wB+6|N~Nd(+4ThsuuYOH?aRilcBuS*0a|4o{gj zuL=JFNiXyUrwv}uKAor4k~t{h^5L-K2q3mnJONY+R?zI}0PN}28f z^IxhdtEX}F=yy5{B5%&EL#Ad$zv0dNK@SD1S)vcl)%OI3j${g{^Y#A3Xa~v{Bjb*C zpg$?vWGZ?;vl$H(^FXzGhnE4zxVzUs6uDqQyS68=!whjRAi+!JLCQ7njBPiI67o-M+w67j+Q5i`$VM`AG>qFfwoY)m4= zT*)^uK>f$o7vTtePh6lC2cJ1saFhr7 zJ#xwCEgB6s+v(!0gn@`m85%V3oi-(5S`7F4S-AONT@zedEqqU$qWvwmJrKwO?<@Hp z6`=|Wr&0YBpf?ikTY1EISt@HR9Hi|Ji=o}cElLO6>1JSpM``pKa3|UsNWWPxAmv^8 zB?MG&;_X@v)0~Nw?+H!6u5HlM24MMKG9B>r!z&nrX`8@`$g?_y?vkyw&Z~ib*yit#m-lWISO7OmWwl z3`dyTOKp7Q3%u~z%St{^m;rX#@1mbduY-}ufR9Tn`adinc9-w&?yIFi0*1zeMZ$|K z!0(4M#oNU6_*hQ>+c|TupD(}$>n5Rq-+qTF5C5x*oQj7k_xJSZ!vNB8YLtbny&cUtx zon;k@9D#{bUWXB|MY#BYIhK+}j6`+U&X zHP9HNko?~s*FA}aYUh;``jsm9IbvDMNNWdPgTK>`IWY%B(Pn$q%0ebi zH<$a~68O(80RujxTj9xtKrri~b~OIohW1n0Q!EkS4|D3sdMS#aYC)r(oLaqogA&+b zr$hJRFIVz`3Lq|?oMio8POVaB-z9&_`>{ZlVWxMdM?Tc9Hz(ALgYqi7)RD6iF#)Cj zshg<-YUC(bsVJ$)4VrHhK=T2v7&N!z8}F4!^k9^5Pd*GPIiw&=pmKX`s))oR%T7xc%bdGtu$*0qGCs1OSf}ZLmxVv$bMuFVgR9n zo8156gN!Q^>zqjt?yO(mJYng-9+)Y?i?cEBw)<>p_jEx9Wg$HKu!W@! z4X50XBFF$qWKb+suGCA0s~-7LwnSn-3&PtO?lne_%pg@j(F5G4WH@Hkvm!w^C3W6v?Upg*WRk3G) zC(MWnnel`O3p{(75|;847-C=tm$uWImV4&g+`*I;PBk|zkXAv!T#QN?45(Swo9)Pz z6sMF!f`1PA=0EqNbaNp>#nEp_K)P+cUTfjTpa60NL@yS#jiAp&z}**e#8lPQ#PX?| zzcj^HCa@!*`m8V&82H{a^SBa{84<{mTV-|ezg8DjxX{?hO+Mwe+dI8`Yr&8s`1kA* zOFD>TW8^r`*Ei7CDoPzgWG^qN8z4KP=}=i)t>i{LViJ*!aRJY|EYRPs&3#9KV707s zyp&|^R(V*_5oSt~*s#vKxs0`hfEhgD4U#zx%~_jcH%p<^SlH3a>#-~0WYW-WBtq8f zQR;Phk){0d;)>LXd8uF^xG0MH67MOLJ#QFU4ljf*ZJ<*L?$B7Dt*s#G-(-I^7HR>0 zgH`|9cb4d-z>B%S-`~k2&87Q`qx>t>d|~%&e>aIlkmYt>VTyV4Tkh`#zTu#V--9sr z?YM;J#R;}m#*46kTp#DZ$*SIInj>`!OWzq~QNPCX7ed$?@jKpZlNl|vBqh=7L#N9cx z7gTOtkJU)A!h^?6nsH}De<{>59)?aq^((ygHh6FTCIBm!RQ|&zPBW2h%k0Q@H~OM> z5(wP*a&mMwj?!s{3{W6NsK-nBt9s1uOjJn)gvz!-THCRfpr53lWE^s1=?_C%QRF** z`G@MKf#;AA(rMCw;Qd^s@>yoaE*H}}sZW47Y8=;Z@~E30-fuXC*+1>k1RurdOPH8C zySc)zsK)ebp*r!_YF6;oPa3bGMvW_>W@QcLrsxN=|G3^+>e2#s%fH? zO`z1b0BT<$RBMA~gFOYq0v;rTjDN2meS)C>(Z*m1|L@_f|67qV zCZ_*q(y#hA9tcRh7uNNE7{~m7i>Ci;Un>(K6FVyx*Z)_diJ6h*|2cl!8`eiC=;5AkSwj}f!V7Pnov!O zPR;7OLMtW+*;55+iC9GYTwSUZF-1urdQHfw-TKzq>;b8vZMUcIvx_5~%UobZ@3Jq? zJKtEEl4wX~13ICmaboH!*7pX((L~_qWSRDL5~K5pC}fUOD5PfNq;teO)YKe0P$2o;&yz`((Utt&m_--z(ZE|gT1-2*tWw8xu6P3j6j z^2IGYko>?*gi3TOsf}R%>pKu$q81@IWR{BzS40@$Pi$P*C;#IqOK8IDM1I`jM$v%$ z{^6>df+E^>j&nXL4aMjELYqyteZ={DT^4`XloroRTSG2%cky5Mo!*|y_7kVBxc=Y^ z{X+OF+mW+XP}`Y`&GX&{xDiU6Tf()EDdbmX^FKeQC|nZr`e*Yj*!HwMV#z03Cv6Tw zaIboIs9TysNc1_84T-!$2Ex%)%0*!{*tb8_36DGnlDNL%yfLe04{-Me*+kmGZ@W5`3r35==`*JSDmi+1}6h3MyTgl)DaIJ&}Kk@BQVAt|RNdWOVK zzF2I8C0RD+ll)f-l{a3BgH|5D8N=`7Vb)>3ybnS>Ub1gK-UapEI!udknE-lTnIsUsVKLF)ughQ zqOV41bT`0q$MUsE>)(0b%h z{uYqRwq7ACMdy0kxHBZg)5}Q7`GaNy}&J_6pvVF}(=I*7`~kA*j*+Bfs2djN62L$4%}Omm2mTmVOyydw%em{7A99 zqyt!Gt#}t`Uot@90RD zIC<}l)?^)pp)#0`ltUgmoEM1U_tUw!?vwXS>ip@-!h^2e|L0zRyvzPW zRC@Pax?+H#2~oe(Y24{NU7cQ&#HqGpB?9y7m^g#yJZ0Q?RCCb6Whr9p&sf1>8R;CR zH3Y&gx2u#KoLiCOTvh-Wch8;EBItBqwY??pbxF%;Rqm*DT(9p1JO$mRCfjTH@$JvY zfM1~k6?mVs;{C&={ZHWEBl;*->FqAeTiBj54gPAy$; zVBdFfZs=Nzb!j)4cA4gSiLoPqbRova#~JA{pO~~&-D$LPN1Z~PAU?7s2r`P`k>cQ% z3+C$FAB)TLuBWi2&si}Y=TM{usMU-y4edWJkwWQD5qU0H@Kl{(gj7p0(TJ~(f$Qm- zcZPeC%1X{PlJk=a!1;_S_+MjVZ=-N`9x1AeZ*@q)rYPevJ~v_mjHv|i@dsAh_$uV3+P0p632VObd5 z_Lti|-ml16j;B$8M%DNejGT-V-~DGY$S+TSBacy}JL@NBR51~h2<%M8l_a3?NX3yM=m-qp{N9MYxn7J}lD%kj`~=J(+Qy-j zqXybLr$&3x8r1VYElgmnnC$lI{NF`4!JQlWYPVkZ!*rXiwrf9c!m!>Kn|cgC5mJ|g zj$7LD-Jj=cOrvEoH(O8JwOwkgA8K&~zge{T@554RXY+^FNd8m@9LayC8bGwIY@p7u z%P*xQZllg**0^sv{^R?Y+DJv>C80RTMMlYlg*=pu&=2||erciBvTzgvpPi8Z`z4gc zhqEUZ$tk?Sni|ShF>xc|kP3u^z6Z1OH9p5qC{o%8e7+v_bHBdsA$juEQXWrJFXd+~ z?X~B7y-c;c97?ReKWcSxR&6V#ygZ+yDdc%d`&S0C!x=_Qv8O@S=IioMo_b@u)5e~_>!hubwHkd%Hptx2V8oFO zbNT1410IPni`w2;$Al{(VSM5R9)GJ26o2dO$M&jKy0AXpN60EIHHe6 zx%SVIec?{_N``lrNQ%+YXi_WP1f8EYB%(!<1#$u3QeDQuqQ(*2{w7?LZGxXJM-FIz zcTmq5kExHomu(QsZh;tKe7I1Po{g&*LwN}sPc8`XK)Jx|#P{P(XtTE6B2BCA2sN*& z-R{uwS3G%|*QB>=ls)o!<65wOVy>ON4=~Ys$2Y5PToJ?~* z@NME15(#B+u@N!#8~k}HTN)Y)@k!<)QY@Kg5aq+!G%aOFG#>^m$z878|L0#C<>bZ0 zPQqvZDV>#_!Hy5AN6#7ENS#!s(txYytPrJ@p)>kwV0;%17Ca};mXv=|VF=JGh#n)3 z@>YV8?;gTgikF#qaDY1@()n4bj^=S=<8OHS2AvT2*eXwd->KZ~>^i;=)4wh+G(UZo z;Mwi){n+@lx==Qv==qJ_*)1>k*U)LVGktXOG%=fvEB<9ea5kp9$O|&(i4k8g=>kuT zNfI_1d^WMK@iP`#fOwC6i#7HlZXF3*&~P-#ki<};jP5++vF}$9S8ZzYoF3!#`ElAc ztqs#~CVBg@j-Hj8N@_}KOma`aa|y2>Y#!`(9*WDrYv2*P!9;77P)1qK;x?^fGnK+b zh#3xY;>jI=FHjprZkLATbN{*%r@?OR7S#8Beo5c(_d3|v?JGgF)$`fg+3T-2@ZFfXNThCs8;|%F6D*}*6mf8LX&d-6O zHyR!VBIEQo&aZ)Li;IedAnDFiZjm%GRLwL|rZZ^vwx^^S-ar%+fQ!O-AuU?Ujb~_8(wdS@0BRr%|>TapDu1TFl(MKy+^$V$NPDA zzD|h)^TN*2&qs{f;Yf=C4d>b?+YRYeZDLC8;&w1KQn*<2LKV}EAag76at{<4@=Ouy zvMqa__)H#4UxB5G7ALcQia^q)C*u{WZ&TBseZIwFpyOJ&5Ok_{l=+Ap&+@-DYWsX8 zw#;gW48NY~ad~Oq&{%ZqU`q;D$i8nU=FVQP&wPcy<8nP z$c+9Fd;kV5uY$2{X@5{?dFLOgv+(AIx=42FQwt;a|4bQ%ree^CP>|N1#MKl}gTGvb zLsE84!_seVZS#V!LB|ej$qsW4lmj+MF+%MXuq4PnvphZ;EIb%xBcUdy2SqLy??D77 z7?j~>7!2PU???D>(Ufn-qb;%Fa<=-VpZDNw1a1X>&mq0=C{z5Sv{j# zuD3z|M`33b6bG<%>i`)ncyM=_;O-vWHMq;*?oNy7oh_?y7IEr9WD6g5qMdEYJ-e-9PL*o}2zggrR5bVZhcrIf3`B zgw-TNd_37^g4-s`E+G?_ht6mC2T8H*;3TmaU5K2tNK^c8t^ zu%(EcPr|R*6XJE;&6H$mB27vhy5jocRvx=+6uL* zd^q+(IySb4CxaM6g(k`~} z)&{mX{2#yYBqdayQ*@m6o0vq!1E ziD2N-JWCPNq0W+6S-05Dsp)n>-yrMp+H9kCT}dL)@3+j!l0?(pw(9eUS6R~7;}}P} zdRi~b$iWZuBcq%D_`sg_4AvPQF5|;Y<6{tiK1iz0v~3QRBXM%j58fV4j2FPwZP7FQ(?_N;U4wHp(98O z*}fD+8D2j{(EgUm4?=eE@T6<_I}P6STk|4pOfqWXC8pB!$pn0ok zrJ&HGh}Rc(eugp`jq`2PUZdA3^OpWv7GoOcGohf3Lg<1K1{~a6YPGx=GRr0E^EsQ@ zz>}covS2j54}~Xy~8*-$s!zlg%Q2FKTcUqLY-Pkb` z&eUeFt%RX5Ka?6%@3FQ+o=L@h>z`5DY1sALGmT&LiwcvrzzuL+Y3M}<@6CXfJgREh z+$F3W<*krk&B%#=NcV>z@drc_+wJ`riYK_l177+19z1f%2Gnsw+?%8n+_8G$@k@Lr zxQMFCb-0SGZhFOL*%D)}wgC$MYVMz@kg;u48I~UM;ktx3-|!MsVQ>YPm-VRE6@p}# zZTWF>366++nJD9oloYD&7{46y(NXzR*h@jPH@yn#0vy?rz{zk`WhY9$*{8zY^9EEy zA;-wn1J0?mpQruktgScQ+#-q5q~(Z|r0?jAyQ*&Q98_E^-`tXvP!>7|M^0r;ScKbh zt5*Dk7p7Q69X9(ASE^St0S0BDie#5~0+ruU@7`x6F!4Ps(m|XeMBa_|>+xy*pr-;o z4aP`txZ|P>Ym+7sTII|qV>s=nAa_}7;qLA4iV=yMwAl)6qcFImgYD`@_p$IeL0- zr^bCDpv${9fssg!2vZs-Hbt^JgpYrPJYIwdY(^xP9MeS07C-+gb~!@3;w3W*6ChtE zO?a$ox1~LPO^jE3?M>;u68JZsT+Mv#j{**tb6KzvJdm0WeNR|sAauC&qaAawd8{*U zx}CTCS6dn*WxCo_dB$`{#H)l9~)yT9N7V^5F4IE8KmO8Xx^>`s53ijH~v46KVA zPkfo{fp8r@QE4dO83I;<;ZUtyYJ7PfY5!tr`w5my}Y_177crGPOPnfo?H=z+RTY-`b=$gc13#I<|`Gwi)O zZ2j`!L`sm97>~b6s*oKYmClCCBuu6|)UyUDLwklFg$^#k`U{!YVgJtl=_~!)#=?xH zc}7*4CF(C~A$Hue4-f-O7Oi==5a8sNpvzB~b)O8MaSMdb$5EmD^0azZPbj%KMn4*X z3ZwQT65)dbgL!t6**Nkh4qUR9ce5TD!<5N0mjYT2i}V)3afI>BE1k7{kq`^QPUYJa zxGFyh?O#nOOWdv5*A2UzBDSo|uCd_$Gy?C_Hf4(_UeuUABiJSP1dd=_6e9TgC( zqsPA7?#=!tZYqWpcqh3C;2!J0Z|x%)tg0a%Ysiwn;qF=jQEJ~0F}kd7AA(=s6P%nh z!xlXT>wd>KODRv%`mmQLq*;PTS-zVRWPuMy(Z%%>thnpp;;<}a%MGX3EmQ#!v%B#1!4!7*l#D9}g z7-@#lc{WVLc^*MJ>H2Y1Fu${UZ;bf3CiD@M60DE96AZ}7ll`WEtNhZ*1DbQoDe$)X zZieOWS?VcTOb}%&?(T#iA$1YnhE=iWr9qNc`dXg9u^oXk%Tgigp{~g(dH63h0!;+* zaZUc>%r9*)L3GASrDBy`Qtn{%#{foeek$FRbdYK7mManhI|-Ccmwe*oIwJK$`wN5L zsPQY1ymF^!ROSVtCww_;RA>Tic*eNajIvdR(h1Sh1ww<%GPxaws{PT2b3lavE=ggcgQPFeSb@#zV9M>*mpJWbgL!~+wBi^ z{MVM!r9R+aKIInEun|?=ZEAz<<#Z@g8Q|M?gMp|KDhpEhM>*WeJr-w{Se(v5E#>~ z8w|ll>&z0rcZuT!C#*Kr<;XlgS&N3P zRr(u0rw&^~QCW7Vc9-u}?o@dn*mNk*J=^yj^drl?rK6uXf}SThvycg=J^d*f$Algx z7t%uJBp}C|ufiX&A*XV&jhiQK9i$W+HjU*zf<%zYwS8x=zYncyCAGf(Cd^e+C-zsJ zIUtW3qVy4)iu%^6bjH78jm4F!ERe^77LO z(OoRj`SPY#jm99W9WW~9g-zr%em}gPZ=+zwX8SZZd*pB>@`RyY(~00Ln&-avZcj`le~ph&N_^;aRG%%1NCd4{Coq05sf+-c z7XwLqJHpxvTZUFYBbo#i&e8o91vS={22@rJPs{0$379KzM>&Y9stL?}7nrn$4Y0^J z+qK6#7RceE;9x{Bf~nTGwB@t(p(wPHxsc#nEFBoU)Bw}MJXs|A=omE6b}^PnS$7ic zF#ghFt6zfzu&F4Bp5hmV6TixV=S)s)!w>8)td1;@vV?QgYTF%zGt96!b{s%%e(lud zC7D^y#4InYVk+XfvpOcymw^0YQ-PALyM$J--rEx7;K>qXfzx!gelvm z?E>;}2!q5drd#WN&-PzD1es?|nv9U|CzE{w^ar8g@QXNP2>sIFniyhKuMc@T3Fni= zCuIA4+9x2r-Q^VXsXa|1fqyKm>oczt^R7%2Ymj9baXeK3S8R zQbb$VT8HEzO>%ZK~xo(y%idk=haJ8gq24xFBWxf~AZCX9WP+6~`@0iwYZGT3tgTvS zsPf0sX?rjAY9MaWw1irI(DR@83mOE=r0J z>My^82{OPb*)#{X3DeT`;OBYqND4$a;aFKVhqM~uqEGsLDekw*nkK{Lu<9Zps!fP{ zshH|i6?3gMP8@Iyhy9wV)?9e%V<7iox`SU9czIdB7^!mELA75x&=ifk+M2URI)28c zw|98q)UKh1q1BMmpGEh?judRzjB1WL?KCi`JL30#mxL$&T+|_oOo6?Hp&YdTH61&_ zWi4_3>cseL0C;3o`u6(!_c+C>)e#m9eoHAm4pP0M|QRXn}2$auHUKDxYY zYVa7XkFRC9u*Tfm@h%D2Iq+pYtK*pxz(F_hn`ZR;iDfe-kT)BvkFQHg{XX=)^2w-O zM*gb<=iAm~8{hpzZidwDl>94s)2atdQ5AZ!kdPq*#F^rRO4s#yll7!J|X&dSNAEa`OTn7 zU`LAKUQnTT6)gYRwnG=K>rQ5?ZN}x=J_z%kBA_K|V3CbvRu9l(ElD(X{_FD; z)16{2x@dUA?Vz|{k;&7GRpsnGQUAU3jxFFN1`%xadKrB23FuY8C49A|Ie$09ylfqv zC|Ey(M)uR5Ov|F1V3Fk_77_bjK&WBWO){cm&4n0Xk@>>JvFB!8$xkEv?ytqIubyHR z!NU~rQ~Dch=6Y@nJ?5XVJ`aH56OER#V3U`&WQgfOOZ!w#C%vR`@8a&l#R$>e3*3PCdq$-Z zsLV%%PXFGE`9$pdMl`)f)XU@Fq7?nNlb2Mn&8PY4WE-QxYqDFcT;*alp}2@V9hH*@ z_~$xXSfAZSbTe!Fe(zd95!>yzjG5N>L1|vAPMNU2nq$q`8g!K`OvpDmMTngYmbeH0 z54iy+0OR&eiDoh%I(I`l$vcu^T&-bo1@-u}*<$K$BXi3+Ry`IryjkKkHTNC1%2`=} zi`1BrmPG`oDzu=_4=~Ka$xp5&zo%U3L;N0#1)V-7MtKySe)L#t7MMIoCy2pP*%4g4lpQNF9?&ON^V#p74B_J&UW!$Us~)dhGKadQgSMA5hZ;4vz(116!_$kJONjG-VT!nT`8fZRDH_r@QYRC_ z57{)^Ze@h^-5crB;LbYHDUBmK`|{<+@N!vnSTHI@6TUo$*fD-Kf=6^zedKN#ISlFX zJXPp_3xO)`XL+f7>9e*5qL$&$>2cX)%|N%8Wk-0w4RC`pG2;}ca~ifRD4mpe&KW)# z{ETK9k$x)>PZ^PgugM*hF)KwWSYtzqS2SREp+rX9=b4G@GH>@IYX9!fyuO)TgaVrPeqSYKGVkf%CiUSmoRzkpnjdqsYuycGK-tU%<+KTRvqzem0*S21%FV>rJe-d(~rY%#{ zI-g1^>?-LxKWB{&39<=)h13lNc?5)vihHLeYkn$}@b0Y1ECT*57WV$bk)|Qd%wmnB z)p;l_@qWjkb>YD!Z)}|lw>9HhO7k$$3}u1-@_zj0tSFCwVa+M0J4t!>Wut;}62=lf zXFK1@>A}LCSoS4FIoH6hqI9Mut1`TXSs>9ks+#5-?NVx+XAO@P=gP!~EXRByhp0ZN zj8w88_LkpUW2AZE<+Zj6#Zl>G!NzuitXuY0pyw1%m8FZ3=Azxq!}IZ)s*I3J#fyOC+_N1&UpVtkst)miwnym3&k!YD9!c_x zsC0TvE@zUEY`U>~hx-TchBg1Ew$g^^aW6tWaj6=VSdfb!E*2B{Sz+b~4$XUgNDMSq zQ1C|_jVCf}JL5|W7eK6w@15AXP*gm!k0c|eN6HlWS%pupFx`?p*p1BChn>{J&Ko(f z+7a0#HR-5nP$R)fKofz;kf!B_bsFI$2F*`Rns)luby@h{V@FqoNv&&3w?+P)7qy>j zlgi|A%uDw3#^`lFZRu_kv)Y%_2M2@lYYTrvK1@qP){`M!J729}HItX5+To%cpXm2# z{+edqg%;xZrJk^W@C+4?zaSw~e9w7WlZCerFY>Ym7ho?)ozWqDj4Yt1lN?g=#`2HS z3EN&a)%P-NKMdxy$tQ1MnH}5A+0yZdWrUJ1H*aIWJWUItMzakYyE8WV$KM{}vdsj$ z>mI_&Di{4uye8Zz?fT!|8ez%XG^=n#$r2UB`AS>aKywLg?eaipT*^AF z&YiNv+KKu<#|&tjWa-Du9#-LPZ>&!y43PFZSOuZsN`EN(5=`wY zu@V8?1VHj3xdot8b_@7-oRG&B*KzAQ3@}VaXdC-k~@%SRwPl?)dJGx82DurghZu=eBJTGD8aXkKw~R$6R&9w<q`AU*y+Dg)<~3_&sxAn(27gYnva{`g8yI5D`3sV%PGXg zXTdKhz%Bm&Utz+b4GOS9=jG+lH{$xYHMhzCrZoR=_%XwT2>O5q{^#wiLEoaw8bvlk zpkdsRM`L(rM4F4_TN-6NWqnJcX*VeNaeM7u0f?6V$Do4OkZkpk@t!3MI)b<-5=lYH z5m#`aT+#H-2}wMHZ?l~;y=b23swJ~9?f#>Sgc;@V?G}^vMo7;v>wOO)t67gNhLPEa q=&`&gu%>I61TQ--%76FK)62rc%g@8c7X4o@g!s`J7(S}Yq5mJiFQR|| diff --git a/shell/docs/Plan.txt b/shell/docs/Plan.txt deleted file mode 100644 index d38b852..0000000 --- a/shell/docs/Plan.txt +++ /dev/null @@ -1,11 +0,0 @@ -service.Environment: - - currentPath - - stdIn - - stdOut - - stdErr - -Command: -1) cat [OPTION]... [FILE]... -2) echo [SHORT-OPTION]... [STRING]... -3) wc [OPTION]... [FILE]... - diff --git a/shell/src/main/java/Application.java b/shell/src/main/java/Application.java index a9c9187..62c0b4d 100644 --- a/shell/src/main/java/Application.java +++ b/shell/src/main/java/Application.java @@ -1,7 +1,6 @@ import service.Shell; public class Application { - public static void main(String[] args) { Shell.run(); } diff --git a/shell/src/main/java/parser/Lexem.java b/shell/src/main/java/parser/Lexeme.java similarity index 83% rename from shell/src/main/java/parser/Lexem.java rename to shell/src/main/java/parser/Lexeme.java index be67e50..4e7ba93 100644 --- a/shell/src/main/java/parser/Lexem.java +++ b/shell/src/main/java/parser/Lexeme.java @@ -5,7 +5,7 @@ @Data @Builder -public class Lexem { +public class Lexeme { String word; LexemType type; } diff --git a/shell/src/main/java/parser/Parser.java b/shell/src/main/java/parser/Parser.java index be6a07c..a629b8f 100644 --- a/shell/src/main/java/parser/Parser.java +++ b/shell/src/main/java/parser/Parser.java @@ -1,94 +1,123 @@ package parser; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class Parser { + private int position; + private StringBuilder word; + + public Parser() { + this.position = 0; + this.word = new StringBuilder(); + } - public List parseLexem(String in) { - int idx = 0; - - List lexems = new ArrayList<>(); - StringBuilder word = new StringBuilder(); - while (idx < in.length()) { - if (in.charAt(idx) == '"') { - StringBuilder wordInDoubleQuote = new StringBuilder(); - wordInDoubleQuote.append("\""); - idx++; - while (in.charAt(idx) != '"') { - wordInDoubleQuote.append(in.charAt(idx)); - idx++; + public List parse(@NotNull String input) { + List lexemes = new ArrayList<>(); + + while (position < input.length()) { + switch (input.charAt(position)) { + case ParserSymbols.DOUBLE_QUOTE: { + lexemes.add(parseWordInQuote(input, ParserSymbols.DOUBLE_QUOTE)); + break; + } + case ParserSymbols.SINGLE_QUOTE: { + lexemes.add(parseWordInQuote(input, ParserSymbols.SINGLE_QUOTE)); + break; } - idx++; - wordInDoubleQuote.append("\""); - lexems.add(Lexem.builder() - .word(wordInDoubleQuote.toString()) - .type(LexemType.WORD_IN_DOUBLE_QUOTE) - .build()); - while (idx < in.length() && (in.charAt(idx) == ' ')) idx++; - continue; - } else if (in.charAt(idx) == '\'') { - StringBuilder wordInSingleQuote = new StringBuilder(); - wordInSingleQuote.append("'"); - idx++; - while (in.charAt(idx) != '\'') { - wordInSingleQuote.append(in.charAt(idx)); - idx++; + case ParserSymbols.SUBSTITUTE: { + lexemes.add(parseSubstitute(input)); + break; } - idx++; - wordInSingleQuote.append("'"); - lexems.add(Lexem.builder() - .word(wordInSingleQuote.toString()) - .type(LexemType.WORD_IN_SINGLE_QUOTE) - .build()); - while (idx < in.length() && in.charAt(idx) == ' ') idx++; - continue; - } else if (in.charAt(idx) == '$') { - StringBuilder wordVariable = new StringBuilder(); - while (idx < in.length() && in.charAt(idx) != ' ') { - wordVariable.append(in.charAt(idx)); - idx++; + case ParserSymbols.ASSIGNMENT: { + lexemes.add(parseAssignment(input)); + break; } - idx++; - lexems.add(Lexem.builder() - .word(wordVariable.toString()) - .type(LexemType.WORD_VARIABLE) - .build()); - while (idx < in.length() && in.charAt(idx) == ' ') idx++; - continue; - } else if (in.charAt(idx) == '=') { - while (idx < in.length() && in.charAt(idx) != ' ') { - word.append(in.charAt(idx)); - idx++; + case ParserSymbols.SPACE: { + parseWord(input).ifPresent(lexemes::add); + break; } - lexems.add(Lexem.builder() - .word(word.toString()) - .type(LexemType.WORD_ASSIGMENT) - .build()); - word = new StringBuilder(); - continue; - } else if (in.charAt(idx) == ' ') { - if (word.length() > 0) { - lexems.add(Lexem.builder() - .word(word.toString()) - .type(LexemType.WORD) - .build()); - word = new StringBuilder(); + default: { + word.append(input.charAt(position)); + position++; + break; } - while (idx < in.length() && in.charAt(idx) == ' ') idx++; - continue; } - word.append(in.charAt(idx)); - idx++; } + parseWord(input).ifPresent(lexemes::add); + + return lexemes; + } + + private void skipSpaces(@NotNull String input) { + while (position < input.length() && input.charAt(position) == ParserSymbols.SPACE) { + position++; + } + } + + private @NotNull Lexeme parseWordInQuote(@NotNull String input, char quoteType) { + StringBuilder wordInQuote = new StringBuilder(); + wordInQuote.append(quoteType); + position++; + while (input.charAt(position) != quoteType) { + wordInQuote.append(input.charAt(position)); + position++; + } + position++; + wordInQuote.append(quoteType); + + skipSpaces(input); + return Lexeme.builder() + .word(wordInQuote.toString()) + .type(quoteType == ParserSymbols.DOUBLE_QUOTE ? LexemType.WORD_IN_DOUBLE_QUOTE + : LexemType.WORD_IN_SINGLE_QUOTE) + .build(); + } + + private @NotNull Lexeme parseSubstitute(@NotNull String input) { + StringBuilder variable = new StringBuilder(); + while (position < input.length() && input.charAt(position) != ParserSymbols.SPACE) { + variable.append(input.charAt(position)); + position++; + } + position++; + + skipSpaces(input); + return Lexeme.builder() + .word(variable.toString()) + .type(LexemType.WORD_VARIABLE) + .build(); + } + + private @NotNull Lexeme parseAssignment(@NotNull String input) { + while (position < input.length() && input.charAt(position) != ParserSymbols.SPACE) { + word.append(input.charAt(position)); + position++; + } + + Lexeme lexeme = Lexeme.builder() + .word(word.toString()) + .type(LexemType.WORD_ASSIGMENT) + .build(); + word = new StringBuilder(); + return lexeme; + } + + private @NotNull Optional parseWord(@NotNull String input) { + Lexeme lexeme = null; if (word.length() > 0) { - lexems.add(Lexem.builder() - .word(word.toString()) - .type(LexemType.WORD) - .build()); + lexeme = Lexeme.builder() + .word(word.toString()) + .type(LexemType.WORD) + .build(); + word = new StringBuilder(); } + skipSpaces(input); - return lexems; + return Optional.ofNullable(lexeme); } } diff --git a/shell/src/main/java/parser/ParserSymbols.java b/shell/src/main/java/parser/ParserSymbols.java new file mode 100644 index 0000000..6fa06d8 --- /dev/null +++ b/shell/src/main/java/parser/ParserSymbols.java @@ -0,0 +1,11 @@ +package parser; + +public class ParserSymbols { + private ParserSymbols() {} + + public static final char DOUBLE_QUOTE = '\"'; + public static final char SINGLE_QUOTE = '\''; + public static final char SUBSTITUTE = '$'; + public static final char ASSIGNMENT = '='; + public static final char SPACE = ' '; +} diff --git a/shell/src/main/java/parser/Substitutor.java b/shell/src/main/java/parser/Substitutor.java index 42d7d16..2c0c240 100644 --- a/shell/src/main/java/parser/Substitutor.java +++ b/shell/src/main/java/parser/Substitutor.java @@ -7,19 +7,19 @@ public class Substitutor { Environment environment = Environment.getInstance(); - public void substitution(List lexems) { - for (Lexem lexem : lexems) { - if (lexem.type == LexemType.WORD_VARIABLE) { - String subs = lexem.word.substring(lexem.word.indexOf("$") + 1); - lexem.word = lexem.word.replace("$" + subs, environment.getVariable(subs)); + public void substitution(List lexemes) { + for (Lexeme lexeme : lexemes) { + if (lexeme.type == LexemType.WORD_VARIABLE) { + String subs = lexeme.word.substring(lexeme.word.indexOf("$") + 1); + lexeme.word = lexeme.word.replace("$" + subs, environment.getVariable(subs)); } - if (lexem.type == LexemType.WORD_IN_DOUBLE_QUOTE) { - if (lexem.word.contains("$")) { + if (lexeme.type == LexemType.WORD_IN_DOUBLE_QUOTE) { + if (lexeme.word.contains("$")) { StringBuilder subs = new StringBuilder(); - subs.append(lexem.word.substring(lexem.word.indexOf("$") + 1)); - subs.deleteCharAt(lexem.word.length() - 3); - lexem.word = lexem.word.replace("$" + subs.toString(), + subs.append(lexeme.word.substring(lexeme.word.indexOf("$") + 1)); + subs.deleteCharAt(lexeme.word.length() - 3); + lexeme.word = lexeme.word.replace("$" + subs.toString(), environment.getVariable(subs.toString())); } } diff --git a/shell/src/main/java/service/Executor.java b/shell/src/main/java/service/Executor.java index cd4f8c9..f985a47 100644 --- a/shell/src/main/java/service/Executor.java +++ b/shell/src/main/java/service/Executor.java @@ -2,56 +2,55 @@ import commands.CommandEntity; import commands.CommandExecutor; -import parser.Lexem; +import org.jetbrains.annotations.NotNull; +import parser.Lexeme; import parser.LexemType; import parser.Parser; import parser.Substitutor; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; public class Executor { - private Parser parser = new Parser(); - private Environment environment = new Environment(); - private Substitutor substitutor = new Substitutor(); - private CommandExecutor commandExecutor = new CommandExecutor(); + private final Parser parser = new Parser(); + private final Environment environment = new Environment(); + private final Substitutor substitutor = new Substitutor(); + private final CommandExecutor commandExecutor = new CommandExecutor(); - public String execute(String input) { - if (input.contains("|")) { - List commands = new ArrayList<>(); + public String execute(@NotNull String input) { +// if (input.contains("|")) { +// List commands = new ArrayList<>(); +// +// String[] cmds = input.split("\\|"); +// for (String cmd : cmds) { +// List lexems = parser.parseLexem(cmd); +// substitutor.substitution(lexems); +// +// commands.add( +// CommandEntity.builder() +// .name(lexems.get(0).getWord()) +// .arguments(lexems.stream() +// .filter(lexem -> lexems.indexOf(lexem) != 0) +// .map(Lexem::getWord) +// .collect(Collectors.toList())) +// .build()); +// } +// +// String output = commandExecutor.execute(commands.get(0)); +// commands.remove(0); +// for (CommandEntity command : commands) { +// command.setArguments(Collections.singletonList(output)); +// output = commandExecutor.execute(command); +// } +// +// return output; +// } - String[] cmds = input.split("\\|"); - for (String cmd : cmds) { - List lexems = parser.parseLexem(cmd); - substitutor.substitution(lexems); + List lexemes = parser.parse(input); + substitutor.substitution(lexemes); - commands.add( - CommandEntity.builder() - .name(lexems.get(0).getWord()) - .arguments(lexems.stream() - .filter(lexem -> lexems.indexOf(lexem) != 0) - .map(Lexem::getWord) - .collect(Collectors.toList())) - .build()); - } - - String output = commandExecutor.execute(commands.get(0)); - commands.remove(0); - for (CommandEntity command : commands) { - command.setArguments(Collections.singletonList(output)); - output = commandExecutor.execute(command); - } - - return output; - } - - List lexems = parser.parseLexem(input); - substitutor.substitution(lexems); - - for (Lexem lexem : lexems) { - if (lexem.getType() == LexemType.WORD_ASSIGMENT) { + for (Lexeme lexeme : lexemes) { + if (lexeme.getType() == LexemType.WORD_ASSIGMENT) { String[] attrAndValue = input.split("="); environment.putVariable(attrAndValue[0], attrAndValue[1]); return attrAndValue[0] + " = " + attrAndValue[1]; @@ -59,10 +58,10 @@ public String execute(String input) { CommandEntity command = CommandEntity.builder() - .name(lexems.get(0).getWord()) - .arguments(lexems.stream() - .filter(l -> lexems.indexOf(l) != 0) - .map(Lexem::getWord) + .name(lexemes.get(0).getWord()) + .arguments(lexemes.stream() + .filter(l -> lexemes.indexOf(l) != 0) + .map(Lexeme::getWord) .collect(Collectors.toList())) .build(); diff --git a/shell/src/main/java/service/Shell.java b/shell/src/main/java/service/Shell.java index 38494a2..36d2968 100644 --- a/shell/src/main/java/service/Shell.java +++ b/shell/src/main/java/service/Shell.java @@ -10,13 +10,13 @@ public static void run() { String input; String output; + System.out.print("SHELL >> "); while (scanner.hasNextLine()) { - System.out.print("SHELL >> "); - input = scanner.nextLine(); output = executor.execute(input); System.out.println(output); + System.out.print("SHELL >> "); } } } diff --git a/shell/src/test/java/parser/ParserTest.java b/shell/src/test/java/parser/ParserTest.java index 54daa52..47f0347 100644 --- a/shell/src/test/java/parser/ParserTest.java +++ b/shell/src/test/java/parser/ParserTest.java @@ -15,14 +15,14 @@ public class ParserTest { public void testParseLexemWithWordInDoubleQuote() { // Arrange String input = "\" hello world \""; - List excpected = Collections.singletonList( - Lexem.builder() + List excpected = Collections.singletonList( + Lexeme.builder() .word(input) .type(LexemType.WORD_IN_DOUBLE_QUOTE) .build()); // Act - List actual = parser.parseLexem(input); + List actual = parser.parse(input); // Assert assertThat(actual).isEqualTo(excpected); @@ -33,14 +33,14 @@ public void testParseLexemWithWordInDoubleQuote() { public void testParseLexemWithWordInSingleQuote() { // Arrange String input = "' hello world '"; - List excpected = Collections.singletonList( - Lexem.builder() + List excpected = Collections.singletonList( + Lexeme.builder() .word(input) .type(LexemType.WORD_IN_SINGLE_QUOTE) .build()); // Act - List actual = parser.parseLexem(input); + List actual = parser.parse(input); // Assert assertThat(actual).isEqualTo(excpected); @@ -50,14 +50,14 @@ public void testParseLexemWithWordInSingleQuote() { public void testParseLexemWithWordVariable() { // Arrange String input = "$task"; - List excpected = Collections.singletonList( - Lexem.builder() + List excpected = Collections.singletonList( + Lexeme.builder() .word(input) .type(LexemType.WORD_VARIABLE) .build()); // Act - List actual = parser.parseLexem(input); + List actual = parser.parse(input); // Assert assertThat(actual).isEqualTo(excpected); @@ -67,14 +67,14 @@ public void testParseLexemWithWordVariable() { public void testParseLexemWithWordAssigment() { // Arrange String input = "y=10"; - List excpected = Collections.singletonList( - Lexem.builder() + List excpected = Collections.singletonList( + Lexeme.builder() .word(input) .type(LexemType.WORD_ASSIGMENT) .build()); // Act - List actual = parser.parseLexem(input); + List actual = parser.parse(input); // Assert assertThat(actual).isEqualTo(excpected); @@ -84,15 +84,15 @@ public void testParseLexemWithWordAssigment() { public void testParseLexem() { // Arrange String input = "Hello World \" Software Design\" $bash 'hi' "; - Lexem lexem1 = Lexem.builder().word("Hello").type(LexemType.WORD).build(); - Lexem lexem2 = Lexem.builder().word("World").type(LexemType.WORD).build(); - Lexem lexem3 = Lexem.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); - Lexem lexem4 = Lexem.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); - Lexem lexem5 = Lexem.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); - List excpected = Arrays.asList(lexem1, lexem2, lexem3, lexem4, lexem5); + Lexeme lexeme1 = Lexeme.builder().word("Hello").type(LexemType.WORD).build(); + Lexeme lexeme2 = Lexeme.builder().word("World").type(LexemType.WORD).build(); + Lexeme lexeme3 = Lexeme.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); + Lexeme lexeme4 = Lexeme.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexeme5 = Lexeme.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); + List excpected = Arrays.asList(lexeme1, lexeme2, lexeme3, lexeme4, lexeme5); // Act - List actual = parser.parseLexem(input); + List actual = parser.parse(input); // Assert assertThat(actual).isEqualTo(excpected); diff --git a/shell/src/test/java/parser/SubstitutorTest.java b/shell/src/test/java/parser/SubstitutorTest.java index 91478a8..9139801 100644 --- a/shell/src/test/java/parser/SubstitutorTest.java +++ b/shell/src/test/java/parser/SubstitutorTest.java @@ -16,15 +16,15 @@ public class SubstitutorTest { public void testSubstitution() { // Arrange String input = "Hello World \" Software Design\" $bash 'hi' "; - Lexem lexem1 = Lexem.builder().word("Hello").type(LexemType.WORD).build(); - Lexem lexem2 = Lexem.builder().word("World").type(LexemType.WORD).build(); - Lexem lexem3 = Lexem.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); - Lexem lexem4 = Lexem.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); - Lexem lexem5 = Lexem.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); - Lexem lexem6 = Lexem.builder().word("value").type(LexemType.WORD_VARIABLE).build(); - - List actual = Arrays.asList(lexem1, lexem2, lexem3, lexem4, lexem5); - List expected = Arrays.asList(lexem1, lexem2, lexem3, lexem6, lexem5); + Lexeme lexeme1 = Lexeme.builder().word("Hello").type(LexemType.WORD).build(); + Lexeme lexeme2 = Lexeme.builder().word("World").type(LexemType.WORD).build(); + Lexeme lexeme3 = Lexeme.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); + Lexeme lexeme4 = Lexeme.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexeme5 = Lexeme.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); + Lexeme lexeme6 = Lexeme.builder().word("value").type(LexemType.WORD_VARIABLE).build(); + + List actual = Arrays.asList(lexeme1, lexeme2, lexeme3, lexeme4, lexeme5); + List expected = Arrays.asList(lexeme1, lexeme2, lexeme3, lexeme6, lexeme5); environment.putVariable("bash", "value"); From 58e4a695da6fcf806c4da17809c896765b3d5d31 Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Mon, 1 Jun 2020 03:42:25 +0300 Subject: [PATCH 06/10] Rework parser and substitutor. Rework Command interface. Fix pipe command. --- shell/src/main/java/commands/Command.java | 4 - .../main/java/commands/CommandConstants.java | 11 ++ .../src/main/java/commands/CommandEntity.java | 3 +- .../main/java/commands/CommandException.java | 11 ++ .../main/java/commands/CommandExecutor.java | 10 +- .../main/java/commands/impl/CommandCat.java | 11 -- .../java/commands/impl/CommandDefault.java | 66 +++++------ .../main/java/commands/impl/CommandEcho.java | 14 +-- .../main/java/commands/impl/CommandExit.java | 11 -- .../java/commands/impl/CommandNotExist.java | 13 +-- .../main/java/commands/impl/CommandPwd.java | 11 -- .../main/java/commands/impl/CommandWc.java | 11 -- shell/src/main/java/parser/LexemType.java | 2 + shell/src/main/java/parser/Parser.java | 31 ++++- shell/src/main/java/parser/Substitutor.java | 35 ++++-- shell/src/main/java/service/Environment.java | 18 +-- shell/src/main/java/service/Executor.java | 107 +++++++++++------- shell/src/main/java/service/Shell.java | 5 +- .../java/commands/CommandExecutorTest.java | 4 +- shell/src/test/java/parser/ParserTest.java | 84 ++++++++------ .../src/test/java/parser/SubstitutorTest.java | 84 ++++++++++++-- shell/src/test/java/service/ExecutorTest.java | 71 +++++------- 22 files changed, 341 insertions(+), 276 deletions(-) create mode 100644 shell/src/main/java/commands/CommandConstants.java create mode 100644 shell/src/main/java/commands/CommandException.java diff --git a/shell/src/main/java/commands/Command.java b/shell/src/main/java/commands/Command.java index f1100a3..95edcf5 100644 --- a/shell/src/main/java/commands/Command.java +++ b/shell/src/main/java/commands/Command.java @@ -3,9 +3,5 @@ import java.util.List; public interface Command { - String name(); - String run(List arguments); - - void setName(String name); } diff --git a/shell/src/main/java/commands/CommandConstants.java b/shell/src/main/java/commands/CommandConstants.java new file mode 100644 index 0000000..19a81ee --- /dev/null +++ b/shell/src/main/java/commands/CommandConstants.java @@ -0,0 +1,11 @@ +package commands; + +public class CommandConstants { + private CommandConstants() {} + + public static final String COMMAND_ECHO = "echo"; + public static final String COMMAND_EXIT = "exit"; + public static final String COMMAND_CAT = "cat"; + public static final String COMMAND_PWD = "pwd"; + public static final String COMMAND_WC = "wc"; +} diff --git a/shell/src/main/java/commands/CommandEntity.java b/shell/src/main/java/commands/CommandEntity.java index ee25a9b..aa0e1cc 100644 --- a/shell/src/main/java/commands/CommandEntity.java +++ b/shell/src/main/java/commands/CommandEntity.java @@ -8,7 +8,6 @@ @Data @Builder public class CommandEntity { - private String name; - // private String arguments; + private Command command; private List arguments; } diff --git a/shell/src/main/java/commands/CommandException.java b/shell/src/main/java/commands/CommandException.java new file mode 100644 index 0000000..257e8f0 --- /dev/null +++ b/shell/src/main/java/commands/CommandException.java @@ -0,0 +1,11 @@ +package commands; + +public class CommandException extends RuntimeException { + public CommandException() { + super(); + } + + public CommandException(String s) { + super(s); + } +} diff --git a/shell/src/main/java/commands/CommandExecutor.java b/shell/src/main/java/commands/CommandExecutor.java index 023830d..4765a96 100644 --- a/shell/src/main/java/commands/CommandExecutor.java +++ b/shell/src/main/java/commands/CommandExecutor.java @@ -1,14 +1,10 @@ package commands; -import service.Environment; +import org.jetbrains.annotations.NotNull; public class CommandExecutor { - Environment environment = Environment.getInstance(); - - public String execute(CommandEntity commandEntity) { - Command command = environment.getCommand(commandEntity.getName()); - command.setName(commandEntity.getName()); - + public String execute(@NotNull CommandEntity commandEntity) { + Command command = commandEntity.getCommand(); return command.run(commandEntity.getArguments()); } } diff --git a/shell/src/main/java/commands/impl/CommandCat.java b/shell/src/main/java/commands/impl/CommandCat.java index f057eaf..8e28bf1 100644 --- a/shell/src/main/java/commands/impl/CommandCat.java +++ b/shell/src/main/java/commands/impl/CommandCat.java @@ -10,17 +10,6 @@ import java.util.stream.Stream; public class CommandCat implements Command { - private String name = ""; - - @Override - public String name() { - return "cat"; - } - - @Override - public void setName(String name) { - this.name = name; - } @Override public String run(List arguments) { diff --git a/shell/src/main/java/commands/impl/CommandDefault.java b/shell/src/main/java/commands/impl/CommandDefault.java index d6bb298..d93c5c4 100644 --- a/shell/src/main/java/commands/impl/CommandDefault.java +++ b/shell/src/main/java/commands/impl/CommandDefault.java @@ -8,47 +8,37 @@ import java.util.List; public class CommandDefault implements Command { - private String name = ""; - - @Override - public String name() { - return "default"; - } - - @Override - public void setName(String name) { - this.name = name; - } @Override public String run(List arguments) { - String args = String.join(" ", arguments); - - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command(name, args); - - try { - Process process = processBuilder.start(); - StringBuilder output = new StringBuilder(); - BufferedReader reader = new BufferedReader( - new InputStreamReader(process.getInputStream())); - - String line; - while ((line = reader.readLine()) != null) { - output.append(line).append("\n"); - } - - int exitVal = process.waitFor(); - if (exitVal == 0) { -// System.out.println("Success!"); -// System.out.println(output); - System.exit(0); - } - - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - +// String args = String.join(" ", arguments); +// +// ProcessBuilder processBuilder = new ProcessBuilder(); +// processBuilder.command(name, args); +// +// try { +// Process process = processBuilder.start(); +// StringBuilder output = new StringBuilder(); +// BufferedReader reader = new BufferedReader( +// new InputStreamReader(process.getInputStream())); +// +// String line; +// while ((line = reader.readLine()) != null) { +// output.append(line).append("\n"); +// } +// +// int exitVal = process.waitFor(); +// if (exitVal == 0) { +//// System.out.println("Success!"); +//// System.out.println(output); +// System.exit(0); +// } +// +// } catch (IOException | InterruptedException e) { +// e.printStackTrace(); +// } +// return ""; +// } } } diff --git a/shell/src/main/java/commands/impl/CommandEcho.java b/shell/src/main/java/commands/impl/CommandEcho.java index 677f550..e6cb776 100644 --- a/shell/src/main/java/commands/impl/CommandEcho.java +++ b/shell/src/main/java/commands/impl/CommandEcho.java @@ -1,24 +1,14 @@ package commands.impl; import commands.Command; +import org.jetbrains.annotations.NotNull; import java.util.List; public class CommandEcho implements Command { - private String name = ""; @Override - public String name() { - return "echo"; - } - - @Override - public void setName(String name) { - this.name = name; - } - - @Override - public String run(List arguments) { + public String run(@NotNull List arguments) { StringBuilder result = new StringBuilder(); for (String word : arguments) { diff --git a/shell/src/main/java/commands/impl/CommandExit.java b/shell/src/main/java/commands/impl/CommandExit.java index fc36d9f..14947f3 100644 --- a/shell/src/main/java/commands/impl/CommandExit.java +++ b/shell/src/main/java/commands/impl/CommandExit.java @@ -5,17 +5,6 @@ import java.util.List; public class CommandExit implements Command { - private String name; - - @Override - public String name() { - return "exit"; - } - - @Override - public void setName(String name) { - this.name = name; - } @Override public String run(List arguments) { diff --git a/shell/src/main/java/commands/impl/CommandNotExist.java b/shell/src/main/java/commands/impl/CommandNotExist.java index 0a846f9..913712f 100644 --- a/shell/src/main/java/commands/impl/CommandNotExist.java +++ b/shell/src/main/java/commands/impl/CommandNotExist.java @@ -5,20 +5,9 @@ import java.util.List; public class CommandNotExist implements Command { - private String name = ""; - - @Override - public String name() { - return ""; - } - - @Override - public void setName(String name) { - this.name = name; - } @Override public String run(List arguments) { - return "Command not found: " + name; + return "Command not found"; } } diff --git a/shell/src/main/java/commands/impl/CommandPwd.java b/shell/src/main/java/commands/impl/CommandPwd.java index a54843a..8f4d0e2 100644 --- a/shell/src/main/java/commands/impl/CommandPwd.java +++ b/shell/src/main/java/commands/impl/CommandPwd.java @@ -5,17 +5,6 @@ import java.util.List; public class CommandPwd implements Command { - private String name = ""; - - @Override - public String name() { - return "pwd"; - } - - @Override - public void setName(String name) { - this.name = name; - } @Override public String run(List arguments) { diff --git a/shell/src/main/java/commands/impl/CommandWc.java b/shell/src/main/java/commands/impl/CommandWc.java index 6df5ffb..9e00b53 100644 --- a/shell/src/main/java/commands/impl/CommandWc.java +++ b/shell/src/main/java/commands/impl/CommandWc.java @@ -7,17 +7,6 @@ import java.util.*; public class CommandWc implements Command { - private String name = ""; - - @Override - public String name() { - return "wc"; - } - - @Override - public void setName(String name) { - this.name = name; - } @Override public String run(List arguments) { diff --git a/shell/src/main/java/parser/LexemType.java b/shell/src/main/java/parser/LexemType.java index f8934d8..0496358 100644 --- a/shell/src/main/java/parser/LexemType.java +++ b/shell/src/main/java/parser/LexemType.java @@ -6,4 +6,6 @@ public enum LexemType { WORD_ASSIGMENT, WORD_VARIABLE, WORD, + SPACE, + ; } diff --git a/shell/src/main/java/parser/Parser.java b/shell/src/main/java/parser/Parser.java index a629b8f..a75c162 100644 --- a/shell/src/main/java/parser/Parser.java +++ b/shell/src/main/java/parser/Parser.java @@ -22,14 +22,18 @@ public List parse(@NotNull String input) { switch (input.charAt(position)) { case ParserSymbols.DOUBLE_QUOTE: { lexemes.add(parseWordInQuote(input, ParserSymbols.DOUBLE_QUOTE)); + skipSpaces(input).ifPresent(lexemes::add); break; } case ParserSymbols.SINGLE_QUOTE: { lexemes.add(parseWordInQuote(input, ParserSymbols.SINGLE_QUOTE)); + skipSpaces(input).ifPresent(lexemes::add); + break; } case ParserSymbols.SUBSTITUTE: { lexemes.add(parseSubstitute(input)); + skipSpaces(input).ifPresent(lexemes::add); break; } case ParserSymbols.ASSIGNMENT: { @@ -38,6 +42,7 @@ public List parse(@NotNull String input) { } case ParserSymbols.SPACE: { parseWord(input).ifPresent(lexemes::add); + skipSpaces(input).ifPresent(lexemes::add); break; } default: { @@ -49,14 +54,25 @@ public List parse(@NotNull String input) { } parseWord(input).ifPresent(lexemes::add); + position = 0; return lexemes; } - private void skipSpaces(@NotNull String input) { + private @NotNull Optional skipSpaces(@NotNull String input) { + Lexeme lexeme = null; + if (position < input.length() && input.charAt(position) == ParserSymbols.SPACE) { + lexeme = Lexeme.builder() + .word(" ") + .type(LexemType.SPACE) + .build(); + } + while (position < input.length() && input.charAt(position) == ParserSymbols.SPACE) { position++; } + + return Optional.ofNullable(lexeme); } private @NotNull Lexeme parseWordInQuote(@NotNull String input, char quoteType) { @@ -70,7 +86,7 @@ private void skipSpaces(@NotNull String input) { position++; wordInQuote.append(quoteType); - skipSpaces(input); +// skipSpaces(input); return Lexeme.builder() .word(wordInQuote.toString()) .type(quoteType == ParserSymbols.DOUBLE_QUOTE ? LexemType.WORD_IN_DOUBLE_QUOTE @@ -80,13 +96,16 @@ private void skipSpaces(@NotNull String input) { private @NotNull Lexeme parseSubstitute(@NotNull String input) { StringBuilder variable = new StringBuilder(); - while (position < input.length() && input.charAt(position) != ParserSymbols.SPACE) { + variable.append(ParserSymbols.SUBSTITUTE); + position++; + while (position < input.length() && + input.charAt(position) != ParserSymbols.SPACE && + input.charAt(position) != ParserSymbols.SUBSTITUTE) { variable.append(input.charAt(position)); position++; } - position++; - skipSpaces(input); +// skipSpaces(input); return Lexeme.builder() .word(variable.toString()) .type(LexemType.WORD_VARIABLE) @@ -116,7 +135,7 @@ private void skipSpaces(@NotNull String input) { .build(); word = new StringBuilder(); } - skipSpaces(input); +// skipSpaces(input); return Optional.ofNullable(lexeme); } diff --git a/shell/src/main/java/parser/Substitutor.java b/shell/src/main/java/parser/Substitutor.java index 2c0c240..120394a 100644 --- a/shell/src/main/java/parser/Substitutor.java +++ b/shell/src/main/java/parser/Substitutor.java @@ -1,26 +1,41 @@ package parser; +import org.jetbrains.annotations.NotNull; import service.Environment; import java.util.List; +//TODO: разнести логику public class Substitutor { - Environment environment = Environment.getInstance(); + private final Environment environment; - public void substitution(List lexemes) { + public Substitutor(Environment environment) { + this.environment = environment; + } + + public void substitution(@NotNull List lexemes) { for (Lexeme lexeme : lexemes) { if (lexeme.type == LexemType.WORD_VARIABLE) { - String subs = lexeme.word.substring(lexeme.word.indexOf("$") + 1); - lexeme.word = lexeme.word.replace("$" + subs, environment.getVariable(subs)); + String variable = lexeme.word.substring(lexeme.word.indexOf(ParserSymbols.SUBSTITUTE) + 1); + lexeme.word = lexeme.word.replace(ParserSymbols.SUBSTITUTE + variable, + environment.getVariable(variable)); + lexeme.type = LexemType.WORD; } if (lexeme.type == LexemType.WORD_IN_DOUBLE_QUOTE) { - if (lexeme.word.contains("$")) { - StringBuilder subs = new StringBuilder(); - subs.append(lexeme.word.substring(lexeme.word.indexOf("$") + 1)); - subs.deleteCharAt(lexeme.word.length() - 3); - lexeme.word = lexeme.word.replace("$" + subs.toString(), - environment.getVariable(subs.toString())); + if (lexeme.word.contains(String.valueOf(ParserSymbols.SUBSTITUTE))) { + String partLeft = lexeme.word.substring(0, lexeme.word.indexOf(ParserSymbols.SUBSTITUTE)); + int idx = lexeme.word.indexOf(ParserSymbols.SUBSTITUTE); + while (lexeme.word.charAt(idx) != ParserSymbols.SPACE && + lexeme.word.charAt(idx) != ParserSymbols.DOUBLE_QUOTE && + lexeme.word.charAt(idx) != ParserSymbols.SINGLE_QUOTE) { + idx++; + } + + String centralPart = lexeme.word.substring(lexeme.word.indexOf(ParserSymbols.SUBSTITUTE) + 1, + idx); + lexeme.word = partLeft + environment.getVariable(centralPart) + lexeme.word.substring(idx); + lexeme.type = LexemType.WORD; } } } diff --git a/shell/src/main/java/service/Environment.java b/shell/src/main/java/service/Environment.java index 6f2da8a..930d43e 100644 --- a/shell/src/main/java/service/Environment.java +++ b/shell/src/main/java/service/Environment.java @@ -1,22 +1,24 @@ package service; import commands.Command; +import commands.CommandConstants; import commands.impl.*; import java.util.HashMap; import java.util.Map; +// TODO: убрать Singleton. Добавить дефолтный public class Environment { - private static String currentPath = System.getProperty("user.dir"); - private static Map commands = new HashMap<>(); - private static Map variables = new HashMap<>(); + private static final String currentPath = System.getProperty("user.dir"); + private static final Map commands = new HashMap<>(); + private static final Map variables = new HashMap<>(); static { - commands.put("echo", new CommandEcho()); - commands.put("exit", new CommandExit()); - commands.put("cat", new CommandCat()); - commands.put("pwd", new CommandPwd()); - commands.put("wc", new CommandWc()); + commands.put(CommandConstants.COMMAND_ECHO, new CommandEcho()); + commands.put(CommandConstants.COMMAND_EXIT, new CommandExit()); + commands.put(CommandConstants.COMMAND_CAT, new CommandCat()); + commands.put(CommandConstants.COMMAND_PWD, new CommandPwd()); + commands.put(CommandConstants.COMMAND_WC, new CommandWc()); } static { diff --git a/shell/src/main/java/service/Executor.java b/shell/src/main/java/service/Executor.java index f985a47..1aaab4c 100644 --- a/shell/src/main/java/service/Executor.java +++ b/shell/src/main/java/service/Executor.java @@ -3,71 +3,90 @@ import commands.CommandEntity; import commands.CommandExecutor; import org.jetbrains.annotations.NotNull; -import parser.Lexeme; import parser.LexemType; +import parser.Lexeme; import parser.Parser; import parser.Substitutor; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; public class Executor { private final Parser parser = new Parser(); private final Environment environment = new Environment(); - private final Substitutor substitutor = new Substitutor(); + private final Substitutor substitutor = new Substitutor(environment); private final CommandExecutor commandExecutor = new CommandExecutor(); public String execute(@NotNull String input) { -// if (input.contains("|")) { -// List commands = new ArrayList<>(); -// -// String[] cmds = input.split("\\|"); -// for (String cmd : cmds) { -// List lexems = parser.parseLexem(cmd); -// substitutor.substitution(lexems); -// -// commands.add( -// CommandEntity.builder() -// .name(lexems.get(0).getWord()) -// .arguments(lexems.stream() -// .filter(lexem -> lexems.indexOf(lexem) != 0) -// .map(Lexem::getWord) -// .collect(Collectors.toList())) -// .build()); -// } -// -// String output = commandExecutor.execute(commands.get(0)); -// commands.remove(0); -// for (CommandEntity command : commands) { -// command.setArguments(Collections.singletonList(output)); -// output = commandExecutor.execute(command); -// } -// -// return output; -// } + if (input.contains("|")) { + return executePipesCommand(createCommandList(input)); + } List lexemes = parser.parse(input); substitutor.substitution(lexemes); + if (lexemes.stream().anyMatch(lexeme -> lexeme.getType() == LexemType.WORD_ASSIGMENT)) { + assigmentList(lexemes); + return ""; + } + + return commandExecutor.execute(createCommand(lexemes)); + } + + private @NotNull List createCommandList(@NotNull String input) { + List commandsList = new ArrayList<>(); + + String[] commands = input.split("\\|"); + for (String cmd : commands) { + List lexemes = parser.parse(cmd.trim()); + substitutor.substitution(lexemes); + commandsList.add(createCommand(lexemes)); + } + + return commandsList; + } + + private void assigmentList(@NotNull List lexemes) { + lexemes.stream() + .filter(lexeme -> lexeme.getType() == LexemType.WORD_ASSIGMENT) + .forEach(lexeme -> { + String[] attrAndValue = lexeme.getWord().split("="); + environment.putVariable(attrAndValue[0], attrAndValue[1]); + }); + } + + private CommandEntity createCommand(@NotNull List lexemes) { + boolean flag = true; + StringBuilder stringBuilder = new StringBuilder(); + List args = new ArrayList<>(); for (Lexeme lexeme : lexemes) { - if (lexeme.getType() == LexemType.WORD_ASSIGMENT) { - String[] attrAndValue = input.split("="); - environment.putVariable(attrAndValue[0], attrAndValue[1]); - return attrAndValue[0] + " = " + attrAndValue[1]; + if (lexeme.getType() == LexemType.SPACE) { + flag = false; } - CommandEntity command = - CommandEntity.builder() - .name(lexemes.get(0).getWord()) - .arguments(lexemes.stream() - .filter(l -> lexemes.indexOf(l) != 0) - .map(Lexeme::getWord) - .collect(Collectors.toList())) - .build(); + if (lexeme.getType() != LexemType.SPACE && flag) { + stringBuilder.append(lexeme.getWord()); + } else { + args.add(lexeme.getWord()); + } + } + + return CommandEntity.builder() + .command(environment.getCommand(stringBuilder.toString())) + .arguments(args) + .build(); + } + + private String executePipesCommand(@NotNull List commandsList) { + String output = commandExecutor.execute(commandsList.get(0)); - return commandExecutor.execute(command); + commandsList.remove(0); + for (CommandEntity command : commandsList) { + command.setArguments(Collections.singletonList(output)); + output = commandExecutor.execute(command); } - return ""; + return output; } } diff --git a/shell/src/main/java/service/Shell.java b/shell/src/main/java/service/Shell.java index 36d2968..f5aee37 100644 --- a/shell/src/main/java/service/Shell.java +++ b/shell/src/main/java/service/Shell.java @@ -2,9 +2,10 @@ import java.util.Scanner; +// TODO: exception public class Shell { - static Scanner scanner = new Scanner(System.in); - static Executor executor = new Executor(); + private static final Scanner scanner = new Scanner(System.in); + private static final Executor executor = new Executor(); public static void run() { String input; diff --git a/shell/src/test/java/commands/CommandExecutorTest.java b/shell/src/test/java/commands/CommandExecutorTest.java index 3102462..88e7b24 100644 --- a/shell/src/test/java/commands/CommandExecutorTest.java +++ b/shell/src/test/java/commands/CommandExecutorTest.java @@ -1,5 +1,6 @@ package commands; +import commands.impl.CommandEcho; import org.junit.Test; import java.util.Collections; @@ -14,7 +15,8 @@ public class CommandExecutorTest { public void testExecuteCommandEcho() { // Arrange CommandEntity commandEntity = - CommandEntity.builder().name("echo") + CommandEntity.builder() + .command(new CommandEcho()) .arguments(Collections.singletonList("hello world")) .build(); diff --git a/shell/src/test/java/parser/ParserTest.java b/shell/src/test/java/parser/ParserTest.java index 47f0347..ea24e92 100644 --- a/shell/src/test/java/parser/ParserTest.java +++ b/shell/src/test/java/parser/ParserTest.java @@ -12,89 +12,109 @@ public class ParserTest { private final Parser parser = new Parser(); @Test - public void testParseLexemWithWordInDoubleQuote() { + public void testParseLexemeWithWordInDoubleQuote() { // Arrange String input = "\" hello world \""; - List excpected = Collections.singletonList( + List expected = Collections.singletonList( Lexeme.builder() - .word(input) - .type(LexemType.WORD_IN_DOUBLE_QUOTE) - .build()); + .word(input) + .type(LexemType.WORD_IN_DOUBLE_QUOTE) + .build()); // Act List actual = parser.parse(input); // Assert - assertThat(actual).isEqualTo(excpected); + assertThat(actual).isEqualTo(expected); } - @Test - public void testParseLexemWithWordInSingleQuote() { + public void testParseLexemeWithWordInSingleQuote() { // Arrange String input = "' hello world '"; - List excpected = Collections.singletonList( + List expected = Collections.singletonList( Lexeme.builder() - .word(input) - .type(LexemType.WORD_IN_SINGLE_QUOTE) - .build()); + .word(input) + .type(LexemType.WORD_IN_SINGLE_QUOTE) + .build()); // Act List actual = parser.parse(input); // Assert - assertThat(actual).isEqualTo(excpected); + assertThat(actual).isEqualTo(expected); } @Test - public void testParseLexemWithWordVariable() { + public void testParseLexemeWithWordVariable() { // Arrange String input = "$task"; - List excpected = Collections.singletonList( + List expected = Collections.singletonList( Lexeme.builder() - .word(input) - .type(LexemType.WORD_VARIABLE) - .build()); + .word(input) + .type(LexemType.WORD_VARIABLE) + .build()); // Act List actual = parser.parse(input); // Assert - assertThat(actual).isEqualTo(excpected); + assertThat(actual).isEqualTo(expected); } @Test - public void testParseLexemWithWordAssigment() { + public void testParseLexemeWithWordAssigment() { // Arrange String input = "y=10"; - List excpected = Collections.singletonList( + List expected = Collections.singletonList( Lexeme.builder() - .word(input) - .type(LexemType.WORD_ASSIGMENT) - .build()); + .word(input) + .type(LexemType.WORD_ASSIGMENT) + .build()); // Act List actual = parser.parse(input); // Assert - assertThat(actual).isEqualTo(excpected); + assertThat(actual).isEqualTo(expected); } @Test - public void testParseLexem() { + public void testParseLexeme() { // Arrange String input = "Hello World \" Software Design\" $bash 'hi' "; Lexeme lexeme1 = Lexeme.builder().word("Hello").type(LexemType.WORD).build(); - Lexeme lexeme2 = Lexeme.builder().word("World").type(LexemType.WORD).build(); - Lexeme lexeme3 = Lexeme.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); - Lexeme lexeme4 = Lexeme.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); - Lexeme lexeme5 = Lexeme.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); - List excpected = Arrays.asList(lexeme1, lexeme2, lexeme3, lexeme4, lexeme5); + Lexeme lexeme2 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); + Lexeme lexeme3 = Lexeme.builder().word("World").type(LexemType.WORD).build(); + Lexeme lexeme4 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); + Lexeme lexeme5 = Lexeme.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); + Lexeme lexeme6 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); + Lexeme lexeme7 = Lexeme.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexeme8 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); + Lexeme lexeme9 = Lexeme.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); + Lexeme lexeme10 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); + List expected = Arrays.asList(lexeme1, lexeme2, lexeme3, lexeme4, lexeme5, + lexeme6, lexeme7, lexeme8, lexeme9, lexeme10); + + // Act + List actual = parser.parse(input); + + // Assert + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testParseLexeme2() { + // Arrange + String input = "$x$y"; + Lexeme lexeme1 = Lexeme.builder().word("$x").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexeme2 = Lexeme.builder().word("$y").type(LexemType.WORD_VARIABLE).build(); + List expected = Arrays.asList(lexeme1, lexeme2); // Act List actual = parser.parse(input); // Assert - assertThat(actual).isEqualTo(excpected); + assertThat(actual).isEqualTo(expected); } } diff --git a/shell/src/test/java/parser/SubstitutorTest.java b/shell/src/test/java/parser/SubstitutorTest.java index 9139801..5968f5b 100644 --- a/shell/src/test/java/parser/SubstitutorTest.java +++ b/shell/src/test/java/parser/SubstitutorTest.java @@ -4,30 +4,90 @@ import service.Environment; import java.util.Arrays; +import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; public class SubstitutorTest { - private final Substitutor substitutor = new Substitutor(); - private final Environment environment = Environment.getInstance(); + private final Environment environment = new Environment(); + private final Substitutor substitutor = new Substitutor(environment); @Test public void testSubstitution() { // Arrange - String input = "Hello World \" Software Design\" $bash 'hi' "; - Lexeme lexeme1 = Lexeme.builder().word("Hello").type(LexemType.WORD).build(); - Lexeme lexeme2 = Lexeme.builder().word("World").type(LexemType.WORD).build(); - Lexeme lexeme3 = Lexeme.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); - Lexeme lexeme4 = Lexeme.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); - Lexeme lexeme5 = Lexeme.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); - Lexeme lexeme6 = Lexeme.builder().word("value").type(LexemType.WORD_VARIABLE).build(); - - List actual = Arrays.asList(lexeme1, lexeme2, lexeme3, lexeme4, lexeme5); - List expected = Arrays.asList(lexeme1, lexeme2, lexeme3, lexeme6, lexeme5); + Lexeme lexemeIn = Lexeme.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexemeOut = Lexeme.builder().word("value").type(LexemType.WORD).build(); environment.putVariable("bash", "value"); + List actual = Collections.singletonList(lexemeIn); + List expected = Collections.singletonList(lexemeOut); + + // Act + substitutor.substitution(actual); + + // Assert + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testSubstitution2() { + // Arrange + Lexeme lexemeIn1 = Lexeme.builder().word("$x").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexemeIn2 = Lexeme.builder().word("$y").type(LexemType.WORD_VARIABLE).build(); + + environment.putVariable("x", "ex"); + environment.putVariable("y", "it"); + + Lexeme lexemeOut1 = Lexeme.builder().word("ex").type(LexemType.WORD).build(); + Lexeme lexemeOut2 = Lexeme.builder().word("it").type(LexemType.WORD).build(); + + List actual = Arrays.asList(lexemeIn1, lexemeIn2); + List expected = Arrays.asList(lexemeOut1, lexemeOut2); + + // Act + substitutor.substitution(actual); + + // Assert + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testSubstitution3() { + // Arrange + Lexeme lexemeIn1 = Lexeme.builder().word("$x").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexemeIn2 = Lexeme.builder().word("x").type(LexemType.WORD).build(); + Lexeme lexemeIn3 = Lexeme.builder().word("$y").type(LexemType.WORD_VARIABLE).build(); + + environment.putVariable("x", "e"); + environment.putVariable("y", "it"); + + Lexeme lexemeOut1 = Lexeme.builder().word("e").type(LexemType.WORD).build(); + Lexeme lexemeOut2 = Lexeme.builder().word("x").type(LexemType.WORD).build(); + Lexeme lexemeOut3 = Lexeme.builder().word("it").type(LexemType.WORD).build(); + + List actual = Arrays.asList(lexemeIn1, lexemeIn2, lexemeIn3); + List expected = Arrays.asList(lexemeOut1, lexemeOut2, lexemeOut3); + + // Act + substitutor.substitution(actual); + + // Assert + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testSubstitution4() { + // Arrange + Lexeme lexemeIn1 = Lexeme.builder().word("\"$x\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); + Lexeme lexemeOut1 = Lexeme.builder().word("\"42\"").type(LexemType.WORD).build(); + + environment.putVariable("x", "42"); + + List actual = Collections.singletonList(lexemeIn1); + List expected = Collections.singletonList(lexemeOut1); + // Act substitutor.substitution(actual); diff --git a/shell/src/test/java/service/ExecutorTest.java b/shell/src/test/java/service/ExecutorTest.java index 231e1a1..c93234b 100644 --- a/shell/src/test/java/service/ExecutorTest.java +++ b/shell/src/test/java/service/ExecutorTest.java @@ -4,47 +4,34 @@ import static org.assertj.core.api.Assertions.assertThat; +// TODO: удалить public class ExecutorTest { - private final Executor executor = new Executor(); - private final Environment environment = Environment.getInstance(); - - @Test - public void testExecuteSingleCommand() { - // Arrange - String input = "echo hello world"; - String expected = "hello world"; - - // Act - String actual = executor.execute(input); - - // Assert - assertThat(actual).isEqualTo(expected); - } - - @Test - public void testExecuteCommands() { - // Arrange - String input = "echo hello world | wc"; - String expected = "1 2 10\n"; - - // Act - String actual = executor.execute(input); - - // Assert - assertThat(actual).isEqualTo(expected); - } - - @Test - public void testExecuteAssigment() { - // Arrange - String input = "x=2"; - String expected = "x = 2"; - - // Act - String actual = executor.execute(input); - - // Assert - assertThat(actual).isEqualTo(expected); - assertThat(environment.getVariable("x")).isEqualTo("2"); - } +// private final Executor executor = new Executor(); +// private final Environment environment = Environment.getInstance(); + +// @Test +// public void testExecuteSingleCommand() { +// // Arrange +// String input = "echo hello world"; +// String expected = "hello world"; +// +// // Act +// String actual = executor.execute(input); +// +// // Assert +// assertThat(actual).isEqualTo(expected); +// } +// +// @Test +// public void testExecuteCommands() { +// // Arrange +// String input = "echo hello world | wc"; +// String expected = "1 2 10\n"; +// +// // Act +// String actual = executor.execute(input); +// +// // Assert +// assertThat(actual).isEqualTo(expected); +// } } From 2977d2a14d443a0a8f5fb6db128d55ac53c9587c Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Mon, 1 Jun 2020 21:30:48 +0300 Subject: [PATCH 07/10] Complete task 1 (shell) --- shell/docs/01-intro-cli-text.pdf | Bin 0 -> 41346 bytes shell/src/main/java/Application.java | 3 + shell/src/main/java/commands/Command.java | 13 ++ .../main/java/commands/CommandException.java | 19 ++- .../main/java/commands/CommandExecutor.java | 10 +- .../main/java/commands/impl/CommandCat.java | 29 ++-- .../java/commands/impl/CommandDefault.java | 63 ++++---- .../main/java/commands/impl/CommandEcho.java | 32 ++-- .../main/java/commands/impl/CommandExit.java | 9 ++ .../java/commands/impl/CommandNotExist.java | 13 -- .../main/java/commands/impl/CommandPwd.java | 9 ++ .../main/java/commands/impl/CommandWc.java | 146 ++++++++++++------ shell/src/main/java/parser/Lexeme.java | 2 +- .../{LexemType.java => LexemeType.java} | 4 +- shell/src/main/java/parser/Parser.java | 127 +++++++++------ shell/src/main/java/parser/ParserSymbols.java | 3 + shell/src/main/java/parser/Substitutor.java | 37 +++-- shell/src/main/java/service/Environment.java | 19 +-- shell/src/main/java/service/Executor.java | 60 +++++-- shell/src/main/java/service/Shell.java | 28 +++- .../java/commands/impl/CommandCatTest.java | 3 +- .../java/commands/impl/CommandLsTest.java | 18 +++ .../commands/impl/CommandNotExistTest.java | 18 --- .../java/commands/impl/CommandWcTest.java | 3 +- shell/src/test/java/parser/ParserTest.java | 32 ++-- .../src/test/java/parser/SubstitutorTest.java | 28 ++-- .../test/java/service/EnvironmentTest.java | 13 +- shell/src/test/java/service/ExecutorTest.java | 57 ++++--- 28 files changed, 507 insertions(+), 291 deletions(-) create mode 100644 shell/docs/01-intro-cli-text.pdf delete mode 100644 shell/src/main/java/commands/impl/CommandNotExist.java rename shell/src/main/java/parser/{LexemType.java => LexemeType.java} (84%) create mode 100644 shell/src/test/java/commands/impl/CommandLsTest.java delete mode 100644 shell/src/test/java/commands/impl/CommandNotExistTest.java diff --git a/shell/docs/01-intro-cli-text.pdf b/shell/docs/01-intro-cli-text.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6211335caebe7dd62bb66f5b319375926e6e57f4 GIT binary patch literal 41346 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 z%0D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 literal 0 HcmV?d00001 diff --git a/shell/src/main/java/Application.java b/shell/src/main/java/Application.java index 62c0b4d..7985bc2 100644 --- a/shell/src/main/java/Application.java +++ b/shell/src/main/java/Application.java @@ -1,5 +1,8 @@ import service.Shell; +/** + * Shell application + */ public class Application { public static void main(String[] args) { Shell.run(); diff --git a/shell/src/main/java/commands/Command.java b/shell/src/main/java/commands/Command.java index 95edcf5..0b7a5e0 100644 --- a/shell/src/main/java/commands/Command.java +++ b/shell/src/main/java/commands/Command.java @@ -1,7 +1,20 @@ package commands; +import org.jetbrains.annotations.NotNull; + import java.util.List; +import java.util.stream.Collectors; +/** + * Интерфейс для команды + */ public interface Command { String run(List arguments); + + default String joinArguments(@NotNull List arguments) { + return arguments.stream() + .filter(str -> !str.equals("")) + .map(String::trim) + .collect(Collectors.joining(" ")); + } } diff --git a/shell/src/main/java/commands/CommandException.java b/shell/src/main/java/commands/CommandException.java index 257e8f0..571f11c 100644 --- a/shell/src/main/java/commands/CommandException.java +++ b/shell/src/main/java/commands/CommandException.java @@ -5,7 +5,22 @@ public CommandException() { super(); } - public CommandException(String s) { - super(s); + public CommandException(String message) { + super(message); + } + + public CommandException(String message, Throwable throwable) { + super(message, throwable); + } + + public CommandException(Throwable throwable) { + super(throwable); + } + + protected CommandException(String message, + Throwable throwable, + boolean enableSuppression, + boolean writableStackTrace) { + super(message, throwable, enableSuppression, writableStackTrace); } } diff --git a/shell/src/main/java/commands/CommandExecutor.java b/shell/src/main/java/commands/CommandExecutor.java index 4765a96..e1dfdf5 100644 --- a/shell/src/main/java/commands/CommandExecutor.java +++ b/shell/src/main/java/commands/CommandExecutor.java @@ -2,9 +2,17 @@ import org.jetbrains.annotations.NotNull; +import java.util.stream.Collectors; + +/** + * Класс, определяющий класс команды и передающий аргументы в него + */ public class CommandExecutor { public String execute(@NotNull CommandEntity commandEntity) { Command command = commandEntity.getCommand(); - return command.run(commandEntity.getArguments()); + return command.run(commandEntity.getArguments() + .stream() + .map(String::trim) + .collect(Collectors.toList())); } } diff --git a/shell/src/main/java/commands/impl/CommandCat.java b/shell/src/main/java/commands/impl/CommandCat.java index 8e28bf1..8e0ae4c 100644 --- a/shell/src/main/java/commands/impl/CommandCat.java +++ b/shell/src/main/java/commands/impl/CommandCat.java @@ -1,29 +1,36 @@ package commands.impl; import commands.Command; +import commands.CommandException; +import org.jetbrains.annotations.NotNull; import service.Environment; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.stream.Collectors; import java.util.stream.Stream; +/** + * Команда соответствующая bash cat + */ public class CommandCat implements Command { + /** + * Запуск команды + * + * @param arguments аргументы для команды + * @return возвращаемое значение команды + */ @Override - public String run(List arguments) { - String args = String.join(" ", arguments); - StringBuilder contentBuilder = new StringBuilder(); - - try (Stream stream = Files.lines( - Paths.get(Environment.getCurrentPath(), args.trim()))) { - stream.forEach(s -> contentBuilder.append(s).append("\n")); + public String run(@NotNull List arguments) { + Path path = Paths.get(Environment.getCurrentPath(), joinArguments(arguments)); + try (Stream stream = Files.lines(path)) { + return stream.collect(Collectors.joining("\n")); } catch (IOException e) { - e.getMessage(); - contentBuilder.append("File is not exist!"); + throw new CommandException("File is not exist!"); } - - return contentBuilder.toString(); } } diff --git a/shell/src/main/java/commands/impl/CommandDefault.java b/shell/src/main/java/commands/impl/CommandDefault.java index d93c5c4..95a372e 100644 --- a/shell/src/main/java/commands/impl/CommandDefault.java +++ b/shell/src/main/java/commands/impl/CommandDefault.java @@ -1,44 +1,47 @@ package commands.impl; import commands.Command; +import commands.CommandException; +import org.jetbrains.annotations.NotNull; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.List; +/** + * Команда которой нет в данном shell, но можно вызвать из bash + */ public class CommandDefault implements Command { + private final String commandName; + public CommandDefault(String commandName) { + this.commandName = commandName; + } + + /** + * Запуск команды при помощи Process + * + * @param arguments аргументы для команды + * @return возвращаемое значение команды + */ @Override - public String run(List arguments) { -// String args = String.join(" ", arguments); -// -// ProcessBuilder processBuilder = new ProcessBuilder(); -// processBuilder.command(name, args); -// -// try { -// Process process = processBuilder.start(); -// StringBuilder output = new StringBuilder(); -// BufferedReader reader = new BufferedReader( -// new InputStreamReader(process.getInputStream())); -// -// String line; -// while ((line = reader.readLine()) != null) { -// output.append(line).append("\n"); -// } -// -// int exitVal = process.waitFor(); -// if (exitVal == 0) { -//// System.out.println("Success!"); -//// System.out.println(output); -// System.exit(0); -// } -// -// } catch (IOException | InterruptedException e) { -// e.printStackTrace(); -// } -// - return ""; -// } + public String run(@NotNull List arguments) { + String line; + StringBuilder result = new StringBuilder(); + try { + Process process = Runtime.getRuntime().exec(commandName + " " + joinArguments(arguments)); + BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream())); + + while ((line = input.readLine()) != null) { + result.append(line).append("\n"); + } + } catch (IOException e) { + return "Command not found: " + commandName; + } catch (ArrayIndexOutOfBoundsException e) { + throw new CommandException("Wrong input"); + } + + return result.toString(); } } diff --git a/shell/src/main/java/commands/impl/CommandEcho.java b/shell/src/main/java/commands/impl/CommandEcho.java index e6cb776..695575a 100644 --- a/shell/src/main/java/commands/impl/CommandEcho.java +++ b/shell/src/main/java/commands/impl/CommandEcho.java @@ -4,21 +4,31 @@ import org.jetbrains.annotations.NotNull; import java.util.List; +import java.util.stream.Collectors; +/** + * Команда соответствующая bash echo + */ public class CommandEcho implements Command { + /** + * Запуск команды + * + * @param arguments аргументы для команды + * @return возвращаемое значение команды + */ @Override public String run(@NotNull List arguments) { - StringBuilder result = new StringBuilder(); - - for (String word : arguments) { - if ((word.startsWith("\"") && word.endsWith("\"")) || - (word.startsWith("'") && word.endsWith("'"))) { - word = word.substring(1, word.length() - 1); - } - result.append(word).append(" "); - } - - return result.deleteCharAt(result.length() - 1).toString(); + return arguments.stream() + .map(String::trim) + .filter(str -> !str.equals("")) + .map(arg -> { + if ((arg.startsWith("\"") && arg.endsWith("\"")) || + (arg.startsWith("'") && arg.endsWith("'"))) { + arg = arg.substring(1, arg.length() - 1); + } + return arg; + }) + .collect(Collectors.joining(" ")); } } diff --git a/shell/src/main/java/commands/impl/CommandExit.java b/shell/src/main/java/commands/impl/CommandExit.java index 14947f3..007d62f 100644 --- a/shell/src/main/java/commands/impl/CommandExit.java +++ b/shell/src/main/java/commands/impl/CommandExit.java @@ -4,8 +4,17 @@ import java.util.List; +/** + * Команда соответствующая bash exit + */ public class CommandExit implements Command { + /** + * Запуск команды + * + * @param arguments аргументы для команды + * @return возвращаемое значение команды + */ @Override public String run(List arguments) { System.exit(0); diff --git a/shell/src/main/java/commands/impl/CommandNotExist.java b/shell/src/main/java/commands/impl/CommandNotExist.java deleted file mode 100644 index 913712f..0000000 --- a/shell/src/main/java/commands/impl/CommandNotExist.java +++ /dev/null @@ -1,13 +0,0 @@ -package commands.impl; - -import commands.Command; - -import java.util.List; - -public class CommandNotExist implements Command { - - @Override - public String run(List arguments) { - return "Command not found"; - } -} diff --git a/shell/src/main/java/commands/impl/CommandPwd.java b/shell/src/main/java/commands/impl/CommandPwd.java index 8f4d0e2..2e77466 100644 --- a/shell/src/main/java/commands/impl/CommandPwd.java +++ b/shell/src/main/java/commands/impl/CommandPwd.java @@ -4,8 +4,17 @@ import java.util.List; +/** + * Команда соответствующая bash pwd + */ public class CommandPwd implements Command { + /** + * Запуск команды + * + * @param arguments аргументы для команды + * @return возвращаемое значение команды + */ @Override public String run(List arguments) { return System.getProperty("user.dir"); diff --git a/shell/src/main/java/commands/impl/CommandWc.java b/shell/src/main/java/commands/impl/CommandWc.java index 9e00b53..2abe4b5 100644 --- a/shell/src/main/java/commands/impl/CommandWc.java +++ b/shell/src/main/java/commands/impl/CommandWc.java @@ -1,71 +1,127 @@ package commands.impl; import commands.Command; +import commands.CommandException; +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.FileNotFoundException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; +/** + * Команда соответствующая bash wc + */ public class CommandWc implements Command { + /** + * Запуск команды + * + * @param arguments аргументы для команды + * @return возвращаемое значение команды + */ @Override - public String run(List arguments) { - String args = String.join(" ", arguments); + public String run(@NotNull List arguments) { + List statistics = new ArrayList<>(); + for (String argument : joinArguments(arguments).split(" ")) { + File file = new File(argument); - int totalWords = 0; - int totalLines = 0; - int totalBytes = 0; - - List output = new ArrayList<>(); - String[] files = args.split(" "); - for (String fileName : files) { - int words = 0; - int lines = 0; - int bytes = 0; - - File file = new File(fileName); - - if (file.isDirectory()) + if (file.isDirectory()) { return "This path is directory!"; + } if (!file.isFile()) { - Scanner scanner = new Scanner(args); - while (Objects.requireNonNull(scanner).hasNextLine()) { - List line = Arrays.asList(scanner.nextLine().split(" ")); - bytes += line.stream().mapToInt(String::length).sum(); - words += line.size(); - lines++; - } - output.add(lines + " " + words + " " + bytes + "\n"); - - return String.join("", output); + return countStatistics(new Scanner(joinArguments(arguments))).toString(); } - Scanner scanner; - try { - scanner = new Scanner(file); + try (Scanner scanner = new Scanner(file)) { + FileStatistics fileStatistics = countStatistics(scanner); + fileStatistics.setDescription(file.getPath()); + statistics.add(fileStatistics); } catch (FileNotFoundException e) { - return "No such file"; + throw new CommandException("No such file"); } + } - while (Objects.requireNonNull(scanner).hasNextLine()) { - List line = Arrays.asList(scanner.nextLine().split(" ")); - bytes += line.stream().mapToInt(String::length).sum(); - words += line.size(); - lines++; - } - totalBytes += bytes; - totalLines += lines; - totalWords += words; - scanner.close(); + addTotalStatistic(statistics); + + return statistics.stream().map(FileStatistics::toString).collect(Collectors.joining("\n")); + } + + /** + * Добавление общей статистики, если файлов больше чем 1 + * + * @param statistics список статистик по файлам + */ + private void addTotalStatistic(@NotNull List statistics) { + if (statistics.size() > 1) { + statistics.add(new FileStatistics( + statistics.stream().mapToInt(FileStatistics::getLines).sum(), + statistics.stream().mapToInt(FileStatistics::getWords).sum(), + statistics.stream().mapToInt(FileStatistics::getBytes).sum(), + "total")); + } + } + + /** + * Подсчет статистики + * + * @param scanner сканер файла или строки + * @return статистика файла или строки + */ + private @NotNull FileStatistics countStatistics(@NotNull Scanner scanner) { + FileStatistics fileStatistics = new FileStatistics(); - output.add(lines + " " + words + " " + bytes + " " + file.getPath() + "\n"); + while (scanner.hasNextLine()) { + List line = Arrays.asList(scanner.nextLine().split(" ")); + fileStatistics.bytes += line.stream().mapToInt(String::length).sum(); + fileStatistics.words += line.size(); + fileStatistics.lines++; } - if (files.length > 1) { - output.add(totalLines + " " + totalWords + " " + totalBytes + " " + "total" + "\n"); + return fileStatistics; + } + + /** + * Класс статистики в котором хранится количество: + *
  • строк
  • + *
  • слов
  • + *
  • байт
  • + *
  • название файла (опционально)
  • + */ + @Getter + @Setter + private static class FileStatistics { + private int lines; + private int words; + private int bytes; + private String description; + + public FileStatistics() { + this.lines = 0; + this.words = 0; + this.bytes = 0; + this.description = ""; + } + + public FileStatistics(int lines, int words, int bytes, String description) { + this.lines = lines; + this.words = words; + this.bytes = bytes; + this.description = description; } - return String.join("", output); + @Override + public String toString() { + return lines + " " + + words + " " + + bytes + " " + + description; + } } } diff --git a/shell/src/main/java/parser/Lexeme.java b/shell/src/main/java/parser/Lexeme.java index 4e7ba93..0273fc7 100644 --- a/shell/src/main/java/parser/Lexeme.java +++ b/shell/src/main/java/parser/Lexeme.java @@ -7,5 +7,5 @@ @Builder public class Lexeme { String word; - LexemType type; + LexemeType type; } diff --git a/shell/src/main/java/parser/LexemType.java b/shell/src/main/java/parser/LexemeType.java similarity index 84% rename from shell/src/main/java/parser/LexemType.java rename to shell/src/main/java/parser/LexemeType.java index 0496358..5f8e2c8 100644 --- a/shell/src/main/java/parser/LexemType.java +++ b/shell/src/main/java/parser/LexemeType.java @@ -1,8 +1,8 @@ package parser; -public enum LexemType { - WORD_IN_SINGLE_QUOTE, +public enum LexemeType { WORD_IN_DOUBLE_QUOTE, + WORD_IN_SINGLE_QUOTE, WORD_ASSIGMENT, WORD_VARIABLE, WORD, diff --git a/shell/src/main/java/parser/Parser.java b/shell/src/main/java/parser/Parser.java index a75c162..bb19aaa 100644 --- a/shell/src/main/java/parser/Parser.java +++ b/shell/src/main/java/parser/Parser.java @@ -1,11 +1,15 @@ package parser; +import commands.CommandException; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import java.util.Optional; +/** + * Парсер строки разбивающий символы на лексемы + */ public class Parser { private int position; private StringBuilder word; @@ -15,56 +19,71 @@ public Parser() { this.word = new StringBuilder(); } + /** + * Парсер строки + * + * @param input входящая строка + * @return список созданных лексем + */ public List parse(@NotNull String input) { List lexemes = new ArrayList<>(); - while (position < input.length()) { - switch (input.charAt(position)) { - case ParserSymbols.DOUBLE_QUOTE: { - lexemes.add(parseWordInQuote(input, ParserSymbols.DOUBLE_QUOTE)); - skipSpaces(input).ifPresent(lexemes::add); - break; - } - case ParserSymbols.SINGLE_QUOTE: { - lexemes.add(parseWordInQuote(input, ParserSymbols.SINGLE_QUOTE)); - skipSpaces(input).ifPresent(lexemes::add); - - break; - } - case ParserSymbols.SUBSTITUTE: { - lexemes.add(parseSubstitute(input)); - skipSpaces(input).ifPresent(lexemes::add); - break; - } - case ParserSymbols.ASSIGNMENT: { - lexemes.add(parseAssignment(input)); - break; - } - case ParserSymbols.SPACE: { - parseWord(input).ifPresent(lexemes::add); - skipSpaces(input).ifPresent(lexemes::add); - break; - } - default: { - word.append(input.charAt(position)); - position++; - break; + try { + while (position < input.length()) { + switch (input.charAt(position)) { + case ParserSymbols.DOUBLE_QUOTE: { + lexemes.add(parseWordInQuote(input, ParserSymbols.DOUBLE_QUOTE)); + skipSpaces(input).ifPresent(lexemes::add); + break; + } + case ParserSymbols.SINGLE_QUOTE: { + lexemes.add(parseWordInQuote(input, ParserSymbols.SINGLE_QUOTE)); + skipSpaces(input).ifPresent(lexemes::add); + break; + } + case ParserSymbols.SUBSTITUTE: { + lexemes.add(parseSubstitute(input)); + skipSpaces(input).ifPresent(lexemes::add); + break; + } + case ParserSymbols.ASSIGNMENT: { + lexemes.add(parseAssignment(input)); + break; + } + case ParserSymbols.SPACE: { + parseWord().ifPresent(lexemes::add); + skipSpaces(input).ifPresent(lexemes::add); + break; + } + default: { + word.append(input.charAt(position)); + position++; + break; + } } } - } - parseWord(input).ifPresent(lexemes::add); - position = 0; + parseWord().ifPresent(lexemes::add); + position = 0; + } catch (IndexOutOfBoundsException e) { + throw new CommandException("Wrong input"); + } return lexemes; } + /** + * Пропуск все пробельных символов + * + * @param input входящая строка + * @return лексема соответствующая пробельному символу или ничего + */ private @NotNull Optional skipSpaces(@NotNull String input) { Lexeme lexeme = null; if (position < input.length() && input.charAt(position) == ParserSymbols.SPACE) { lexeme = Lexeme.builder() .word(" ") - .type(LexemType.SPACE) + .type(LexemeType.SPACE) .build(); } @@ -75,6 +94,13 @@ public List parse(@NotNull String input) { return Optional.ofNullable(lexeme); } + /** + * Парсинг строки в двойных кавычках + * + * @param input входящая строка + * @param quoteType тип кавычки ('', "") + * @return лексема соответствующая двойным кавычкам + */ private @NotNull Lexeme parseWordInQuote(@NotNull String input, char quoteType) { StringBuilder wordInQuote = new StringBuilder(); wordInQuote.append(quoteType); @@ -86,14 +112,19 @@ public List parse(@NotNull String input) { position++; wordInQuote.append(quoteType); -// skipSpaces(input); return Lexeme.builder() .word(wordInQuote.toString()) - .type(quoteType == ParserSymbols.DOUBLE_QUOTE ? LexemType.WORD_IN_DOUBLE_QUOTE - : LexemType.WORD_IN_SINGLE_QUOTE) + .type(quoteType == ParserSymbols.DOUBLE_QUOTE ? LexemeType.WORD_IN_DOUBLE_QUOTE + : LexemeType.WORD_IN_SINGLE_QUOTE) .build(); } + /** + * Парсинг строки с найденным символом подстановки ($) + * + * @param input входящая строка + * @return лексема соответствующая подстановке + */ private @NotNull Lexeme parseSubstitute(@NotNull String input) { StringBuilder variable = new StringBuilder(); variable.append(ParserSymbols.SUBSTITUTE); @@ -108,10 +139,16 @@ public List parse(@NotNull String input) { // skipSpaces(input); return Lexeme.builder() .word(variable.toString()) - .type(LexemType.WORD_VARIABLE) + .type(LexemeType.WORD_VARIABLE) .build(); } + /** + * Парсинг строки с присвоением (=) + * + * @param input входящая строка + * @return лексема соответствующая присвоению + */ private @NotNull Lexeme parseAssignment(@NotNull String input) { while (position < input.length() && input.charAt(position) != ParserSymbols.SPACE) { word.append(input.charAt(position)); @@ -120,22 +157,26 @@ public List parse(@NotNull String input) { Lexeme lexeme = Lexeme.builder() .word(word.toString()) - .type(LexemType.WORD_ASSIGMENT) + .type(LexemeType.WORD_ASSIGMENT) .build(); word = new StringBuilder(); return lexeme; } - private @NotNull Optional parseWord(@NotNull String input) { + /** + * Парсинг обычного слова + * + * @return лексема соответствующая слову или ничего, если слова не было + */ + private @NotNull Optional parseWord() { Lexeme lexeme = null; if (word.length() > 0) { lexeme = Lexeme.builder() .word(word.toString()) - .type(LexemType.WORD) + .type(LexemeType.WORD) .build(); word = new StringBuilder(); } -// skipSpaces(input); return Optional.ofNullable(lexeme); } diff --git a/shell/src/main/java/parser/ParserSymbols.java b/shell/src/main/java/parser/ParserSymbols.java index 6fa06d8..a1df2fd 100644 --- a/shell/src/main/java/parser/ParserSymbols.java +++ b/shell/src/main/java/parser/ParserSymbols.java @@ -1,5 +1,8 @@ package parser; +/** + * Символы используемые в парсинге + */ public class ParserSymbols { private ParserSymbols() {} diff --git a/shell/src/main/java/parser/Substitutor.java b/shell/src/main/java/parser/Substitutor.java index 120394a..7b4edd4 100644 --- a/shell/src/main/java/parser/Substitutor.java +++ b/shell/src/main/java/parser/Substitutor.java @@ -5,7 +5,9 @@ import java.util.List; -//TODO: разнести логику +/** + * Подстановщик, который подставляет переменные на все места символа ($) + */ public class Substitutor { private final Environment environment; @@ -13,18 +15,26 @@ public Substitutor(Environment environment) { this.environment = environment; } + /** + * Поиск подстановочного символа, для замены на переменной на значение + * + * @param lexemes список лексем + */ public void substitution(@NotNull List lexemes) { for (Lexeme lexeme : lexemes) { - if (lexeme.type == LexemType.WORD_VARIABLE) { - String variable = lexeme.word.substring(lexeme.word.indexOf(ParserSymbols.SUBSTITUTE) + 1); + + // Поиск символа ($) в лексеме слова + if (lexeme.type == LexemeType.WORD_VARIABLE) { + String variable = lexeme.word.substring(findSubstitutionSymbol(lexeme) + 1); lexeme.word = lexeme.word.replace(ParserSymbols.SUBSTITUTE + variable, environment.getVariable(variable)); - lexeme.type = LexemType.WORD; + lexeme.type = LexemeType.WORD; } - if (lexeme.type == LexemType.WORD_IN_DOUBLE_QUOTE) { + // Поиск символа ($) в лексеме двойных кавычек + if (lexeme.type == LexemeType.WORD_IN_DOUBLE_QUOTE) { if (lexeme.word.contains(String.valueOf(ParserSymbols.SUBSTITUTE))) { - String partLeft = lexeme.word.substring(0, lexeme.word.indexOf(ParserSymbols.SUBSTITUTE)); + String partLeft = lexeme.word.substring(0, findSubstitutionSymbol(lexeme)); int idx = lexeme.word.indexOf(ParserSymbols.SUBSTITUTE); while (lexeme.word.charAt(idx) != ParserSymbols.SPACE && lexeme.word.charAt(idx) != ParserSymbols.DOUBLE_QUOTE && @@ -32,12 +42,21 @@ public void substitution(@NotNull List lexemes) { idx++; } - String centralPart = lexeme.word.substring(lexeme.word.indexOf(ParserSymbols.SUBSTITUTE) + 1, - idx); + String centralPart = lexeme.word.substring(findSubstitutionSymbol(lexeme) + 1, idx); lexeme.word = partLeft + environment.getVariable(centralPart) + lexeme.word.substring(idx); - lexeme.type = LexemType.WORD; + lexeme.type = LexemeType.WORD; } } } } + + /** + * Поиск индекса символа подстановки ($) + * + * @param lexeme лексема + * @return индекс символа + */ + private int findSubstitutionSymbol(@NotNull Lexeme lexeme) { + return lexeme.word.indexOf(ParserSymbols.SUBSTITUTE); + } } diff --git a/shell/src/main/java/service/Environment.java b/shell/src/main/java/service/Environment.java index 930d43e..38e4c15 100644 --- a/shell/src/main/java/service/Environment.java +++ b/shell/src/main/java/service/Environment.java @@ -7,12 +7,17 @@ import java.util.HashMap; import java.util.Map; -// TODO: убрать Singleton. Добавить дефолтный +/** + * Класс окружения, который хранит все все доступные + * команды и переменные окружения + *

    + */ public class Environment { private static final String currentPath = System.getProperty("user.dir"); private static final Map commands = new HashMap<>(); private static final Map variables = new HashMap<>(); + // Список доступных команд static { commands.put(CommandConstants.COMMAND_ECHO, new CommandEcho()); commands.put(CommandConstants.COMMAND_EXIT, new CommandExit()); @@ -21,6 +26,7 @@ public class Environment { commands.put(CommandConstants.COMMAND_WC, new CommandWc()); } + // Список переменных окружения static { variables.put("PWD", "/home/pagrom"); variables.put("HOME", "/home/pagrom/"); @@ -30,17 +36,8 @@ public class Environment { variables.put("LOGNAME", "pagrom"); } - public static class SingletonHolder { - public static final Environment HOLDER_INSTANCE = new Environment(); - } - - public static Environment getInstance() { - return SingletonHolder.HOLDER_INSTANCE; - } - public Command getCommand(String commandName) { -// return commands.getOrDefault(commandName, new CommandDefault()); - return commands.getOrDefault(commandName, new CommandNotExist()); + return commands.getOrDefault(commandName, new CommandDefault(commandName)); } public void putVariable(String argumentName, String argumentValue) { diff --git a/shell/src/main/java/service/Executor.java b/shell/src/main/java/service/Executor.java index 1aaab4c..98feb7c 100644 --- a/shell/src/main/java/service/Executor.java +++ b/shell/src/main/java/service/Executor.java @@ -3,21 +3,31 @@ import commands.CommandEntity; import commands.CommandExecutor; import org.jetbrains.annotations.NotNull; -import parser.LexemType; import parser.Lexeme; +import parser.LexemeType; import parser.Parser; import parser.Substitutor; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +/** + * Класс, который выполняет операции из командной строки + */ public class Executor { private final Parser parser = new Parser(); private final Environment environment = new Environment(); private final Substitutor substitutor = new Substitutor(environment); private final CommandExecutor commandExecutor = new CommandExecutor(); + /** + * Запуск команды из консоли + * + * @param input входящая строка + * @return результат выполнения команды + */ public String execute(@NotNull String input) { if (input.contains("|")) { return executePipesCommand(createCommandList(input)); @@ -26,14 +36,19 @@ public String execute(@NotNull String input) { List lexemes = parser.parse(input); substitutor.substitution(lexemes); - if (lexemes.stream().anyMatch(lexeme -> lexeme.getType() == LexemType.WORD_ASSIGMENT)) { - assigmentList(lexemes); - return ""; + if (lexemes.stream().anyMatch(lexeme -> lexeme.getType() == LexemeType.WORD_ASSIGMENT)) { + return assigmentList(lexemes); } return commandExecutor.execute(createCommand(lexemes)); } + /** + * Создание списка команд из пайпов (|) + * + * @param input входящая строка + * @return список разделенных команд + */ private @NotNull List createCommandList(@NotNull String input) { List commandsList = new ArrayList<>(); @@ -47,25 +62,37 @@ public String execute(@NotNull String input) { return commandsList; } - private void assigmentList(@NotNull List lexemes) { - lexemes.stream() - .filter(lexeme -> lexeme.getType() == LexemType.WORD_ASSIGMENT) - .forEach(lexeme -> { - String[] attrAndValue = lexeme.getWord().split("="); - environment.putVariable(attrAndValue[0], attrAndValue[1]); - }); + /** + * Поиск всех присваивающих лексем и добавление в окружение + * + * @param lexemes список лексем + * @return строковое представление списка присваиваний + */ + private String assigmentList(@NotNull List lexemes) { + return lexemes.stream() + .filter(lexeme -> lexeme.getType() == LexemeType.WORD_ASSIGMENT) + .map(lexeme -> lexeme.getWord().split("=")) + .peek(varAndValue -> environment.putVariable(varAndValue[0], varAndValue[1])) + .map(varAndValue -> varAndValue[0] + " = " + varAndValue[1]) + .collect(Collectors.joining("\n")); } + /** + * Создание сущности команды + * + * @param lexemes список лексем + * @return сущность команды + */ private CommandEntity createCommand(@NotNull List lexemes) { boolean flag = true; StringBuilder stringBuilder = new StringBuilder(); List args = new ArrayList<>(); for (Lexeme lexeme : lexemes) { - if (lexeme.getType() == LexemType.SPACE) { + if (lexeme.getType() == LexemeType.SPACE) { flag = false; } - if (lexeme.getType() != LexemType.SPACE && flag) { + if (lexeme.getType() != LexemeType.SPACE && flag) { stringBuilder.append(lexeme.getWord()); } else { args.add(lexeme.getWord()); @@ -78,6 +105,13 @@ private CommandEntity createCommand(@NotNull List lexemes) { .build(); } + /** + * Поочередное выполнение команд и пайпа (|) + * Результат выполнения первой команды передается как аргументы для второй и так далее + * + * @param commandsList список команд + * @return результат выполнения всех команд + */ private String executePipesCommand(@NotNull List commandsList) { String output = commandExecutor.execute(commandsList.get(0)); diff --git a/shell/src/main/java/service/Shell.java b/shell/src/main/java/service/Shell.java index f5aee37..6921508 100644 --- a/shell/src/main/java/service/Shell.java +++ b/shell/src/main/java/service/Shell.java @@ -1,23 +1,39 @@ package service; +import commands.CommandException; + import java.util.Scanner; -// TODO: exception +/** + * Обработчик командной строки + */ public class Shell { private static final Scanner scanner = new Scanner(System.in); private static final Executor executor = new Executor(); + private static final String repl = "SHELL >> "; + /** + * Запуск приложения + * + *

    input считанная строка

    + *

    output вывод программы

    + */ public static void run() { String input; String output; - System.out.print("SHELL >> "); + System.out.print(repl); while (scanner.hasNextLine()) { - input = scanner.nextLine(); - output = executor.execute(input); + try { + input = scanner.nextLine(); + output = executor.execute(input); - System.out.println(output); - System.out.print("SHELL >> "); + System.out.println(output); + } catch (CommandException exception) { + System.out.println(exception.getMessage()); + } finally { + System.out.print(repl); + } } } } diff --git a/shell/src/test/java/commands/impl/CommandCatTest.java b/shell/src/test/java/commands/impl/CommandCatTest.java index 3c36d8e..889479e 100644 --- a/shell/src/test/java/commands/impl/CommandCatTest.java +++ b/shell/src/test/java/commands/impl/CommandCatTest.java @@ -8,7 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; public class CommandCatTest { - private final Command commandCat = new CommandCat(); private final String pathToTestResources = "src/test/"; @@ -21,7 +20,7 @@ public void testRunCommandCat() { String actual = commandCat.run(Collections.singletonList(pathToTestResources + pathToFile)); // Assert - assertThat(actual).isEqualTo("Hello, World!\n"); + assertThat(actual).isEqualTo("Hello, World!"); } @Test diff --git a/shell/src/test/java/commands/impl/CommandLsTest.java b/shell/src/test/java/commands/impl/CommandLsTest.java new file mode 100644 index 0000000..8182ced --- /dev/null +++ b/shell/src/test/java/commands/impl/CommandLsTest.java @@ -0,0 +1,18 @@ +package commands.impl; + +import commands.Command; +import org.junit.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CommandLsTest { + private final Command commandLs = new CommandDefault("ls"); + + @Test + public void testRunCommandLs() { + String actual = commandLs.run(Collections.singletonList("resources/empty")); + assertThat(actual).isEqualTo(""); + } +} diff --git a/shell/src/test/java/commands/impl/CommandNotExistTest.java b/shell/src/test/java/commands/impl/CommandNotExistTest.java deleted file mode 100644 index 2bf415b..0000000 --- a/shell/src/test/java/commands/impl/CommandNotExistTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package commands.impl; - -import commands.Command; -import org.junit.Test; - -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CommandNotExistTest { - private final Command command = new CommandNotExist(); - - @Test - public void testRunCommandNotExist() { - String actual = command.run(Collections.emptyList()); - assertThat(actual).isEqualTo("Command not found: "); - } -} diff --git a/shell/src/test/java/commands/impl/CommandWcTest.java b/shell/src/test/java/commands/impl/CommandWcTest.java index 48e9ad4..2f0713e 100644 --- a/shell/src/test/java/commands/impl/CommandWcTest.java +++ b/shell/src/test/java/commands/impl/CommandWcTest.java @@ -9,7 +9,6 @@ public class CommandWcTest { - private final Command command = new CommandWc(); private final String pathToTestResources = "src/test/"; @@ -43,7 +42,7 @@ public void testRunCommandWcWithPipe() { String actual = command.run(Collections.singletonList("hello world")); // Assert - assertThat(actual).isEqualTo("1 2 10\n"); + assertThat(actual).isEqualTo("1 2 10 \n"); } @Test diff --git a/shell/src/test/java/parser/ParserTest.java b/shell/src/test/java/parser/ParserTest.java index ea24e92..3de0b9e 100644 --- a/shell/src/test/java/parser/ParserTest.java +++ b/shell/src/test/java/parser/ParserTest.java @@ -18,7 +18,7 @@ public void testParseLexemeWithWordInDoubleQuote() { List expected = Collections.singletonList( Lexeme.builder() .word(input) - .type(LexemType.WORD_IN_DOUBLE_QUOTE) + .type(LexemeType.WORD_IN_DOUBLE_QUOTE) .build()); // Act @@ -35,7 +35,7 @@ public void testParseLexemeWithWordInSingleQuote() { List expected = Collections.singletonList( Lexeme.builder() .word(input) - .type(LexemType.WORD_IN_SINGLE_QUOTE) + .type(LexemeType.WORD_IN_SINGLE_QUOTE) .build()); // Act @@ -52,7 +52,7 @@ public void testParseLexemeWithWordVariable() { List expected = Collections.singletonList( Lexeme.builder() .word(input) - .type(LexemType.WORD_VARIABLE) + .type(LexemeType.WORD_VARIABLE) .build()); // Act @@ -69,7 +69,7 @@ public void testParseLexemeWithWordAssigment() { List expected = Collections.singletonList( Lexeme.builder() .word(input) - .type(LexemType.WORD_ASSIGMENT) + .type(LexemeType.WORD_ASSIGMENT) .build()); // Act @@ -83,16 +83,16 @@ public void testParseLexemeWithWordAssigment() { public void testParseLexeme() { // Arrange String input = "Hello World \" Software Design\" $bash 'hi' "; - Lexeme lexeme1 = Lexeme.builder().word("Hello").type(LexemType.WORD).build(); - Lexeme lexeme2 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); - Lexeme lexeme3 = Lexeme.builder().word("World").type(LexemType.WORD).build(); - Lexeme lexeme4 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); - Lexeme lexeme5 = Lexeme.builder().word("\" Software Design\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); - Lexeme lexeme6 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); - Lexeme lexeme7 = Lexeme.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); - Lexeme lexeme8 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); - Lexeme lexeme9 = Lexeme.builder().word("'hi'").type(LexemType.WORD_IN_SINGLE_QUOTE).build(); - Lexeme lexeme10 = Lexeme.builder().word(" ").type(LexemType.SPACE).build(); + Lexeme lexeme1 = Lexeme.builder().word("Hello").type(LexemeType.WORD).build(); + Lexeme lexeme2 = Lexeme.builder().word(" ").type(LexemeType.SPACE).build(); + Lexeme lexeme3 = Lexeme.builder().word("World").type(LexemeType.WORD).build(); + Lexeme lexeme4 = Lexeme.builder().word(" ").type(LexemeType.SPACE).build(); + Lexeme lexeme5 = Lexeme.builder().word("\" Software Design\"").type(LexemeType.WORD_IN_DOUBLE_QUOTE).build(); + Lexeme lexeme6 = Lexeme.builder().word(" ").type(LexemeType.SPACE).build(); + Lexeme lexeme7 = Lexeme.builder().word("$bash").type(LexemeType.WORD_VARIABLE).build(); + Lexeme lexeme8 = Lexeme.builder().word(" ").type(LexemeType.SPACE).build(); + Lexeme lexeme9 = Lexeme.builder().word("'hi'").type(LexemeType.WORD_IN_SINGLE_QUOTE).build(); + Lexeme lexeme10 = Lexeme.builder().word(" ").type(LexemeType.SPACE).build(); List expected = Arrays.asList(lexeme1, lexeme2, lexeme3, lexeme4, lexeme5, lexeme6, lexeme7, lexeme8, lexeme9, lexeme10); @@ -107,8 +107,8 @@ public void testParseLexeme() { public void testParseLexeme2() { // Arrange String input = "$x$y"; - Lexeme lexeme1 = Lexeme.builder().word("$x").type(LexemType.WORD_VARIABLE).build(); - Lexeme lexeme2 = Lexeme.builder().word("$y").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexeme1 = Lexeme.builder().word("$x").type(LexemeType.WORD_VARIABLE).build(); + Lexeme lexeme2 = Lexeme.builder().word("$y").type(LexemeType.WORD_VARIABLE).build(); List expected = Arrays.asList(lexeme1, lexeme2); // Act diff --git a/shell/src/test/java/parser/SubstitutorTest.java b/shell/src/test/java/parser/SubstitutorTest.java index 5968f5b..3bfffa5 100644 --- a/shell/src/test/java/parser/SubstitutorTest.java +++ b/shell/src/test/java/parser/SubstitutorTest.java @@ -16,8 +16,8 @@ public class SubstitutorTest { @Test public void testSubstitution() { // Arrange - Lexeme lexemeIn = Lexeme.builder().word("$bash").type(LexemType.WORD_VARIABLE).build(); - Lexeme lexemeOut = Lexeme.builder().word("value").type(LexemType.WORD).build(); + Lexeme lexemeIn = Lexeme.builder().word("$bash").type(LexemeType.WORD_VARIABLE).build(); + Lexeme lexemeOut = Lexeme.builder().word("value").type(LexemeType.WORD).build(); environment.putVariable("bash", "value"); @@ -34,14 +34,14 @@ public void testSubstitution() { @Test public void testSubstitution2() { // Arrange - Lexeme lexemeIn1 = Lexeme.builder().word("$x").type(LexemType.WORD_VARIABLE).build(); - Lexeme lexemeIn2 = Lexeme.builder().word("$y").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexemeIn1 = Lexeme.builder().word("$x").type(LexemeType.WORD_VARIABLE).build(); + Lexeme lexemeIn2 = Lexeme.builder().word("$y").type(LexemeType.WORD_VARIABLE).build(); environment.putVariable("x", "ex"); environment.putVariable("y", "it"); - Lexeme lexemeOut1 = Lexeme.builder().word("ex").type(LexemType.WORD).build(); - Lexeme lexemeOut2 = Lexeme.builder().word("it").type(LexemType.WORD).build(); + Lexeme lexemeOut1 = Lexeme.builder().word("ex").type(LexemeType.WORD).build(); + Lexeme lexemeOut2 = Lexeme.builder().word("it").type(LexemeType.WORD).build(); List actual = Arrays.asList(lexemeIn1, lexemeIn2); List expected = Arrays.asList(lexemeOut1, lexemeOut2); @@ -56,16 +56,16 @@ public void testSubstitution2() { @Test public void testSubstitution3() { // Arrange - Lexeme lexemeIn1 = Lexeme.builder().word("$x").type(LexemType.WORD_VARIABLE).build(); - Lexeme lexemeIn2 = Lexeme.builder().word("x").type(LexemType.WORD).build(); - Lexeme lexemeIn3 = Lexeme.builder().word("$y").type(LexemType.WORD_VARIABLE).build(); + Lexeme lexemeIn1 = Lexeme.builder().word("$x").type(LexemeType.WORD_VARIABLE).build(); + Lexeme lexemeIn2 = Lexeme.builder().word("x").type(LexemeType.WORD).build(); + Lexeme lexemeIn3 = Lexeme.builder().word("$y").type(LexemeType.WORD_VARIABLE).build(); environment.putVariable("x", "e"); environment.putVariable("y", "it"); - Lexeme lexemeOut1 = Lexeme.builder().word("e").type(LexemType.WORD).build(); - Lexeme lexemeOut2 = Lexeme.builder().word("x").type(LexemType.WORD).build(); - Lexeme lexemeOut3 = Lexeme.builder().word("it").type(LexemType.WORD).build(); + Lexeme lexemeOut1 = Lexeme.builder().word("e").type(LexemeType.WORD).build(); + Lexeme lexemeOut2 = Lexeme.builder().word("x").type(LexemeType.WORD).build(); + Lexeme lexemeOut3 = Lexeme.builder().word("it").type(LexemeType.WORD).build(); List actual = Arrays.asList(lexemeIn1, lexemeIn2, lexemeIn3); List expected = Arrays.asList(lexemeOut1, lexemeOut2, lexemeOut3); @@ -80,8 +80,8 @@ public void testSubstitution3() { @Test public void testSubstitution4() { // Arrange - Lexeme lexemeIn1 = Lexeme.builder().word("\"$x\"").type(LexemType.WORD_IN_DOUBLE_QUOTE).build(); - Lexeme lexemeOut1 = Lexeme.builder().word("\"42\"").type(LexemType.WORD).build(); + Lexeme lexemeIn1 = Lexeme.builder().word("\"$x\"").type(LexemeType.WORD_IN_DOUBLE_QUOTE).build(); + Lexeme lexemeOut1 = Lexeme.builder().word("\"42\"").type(LexemeType.WORD).build(); environment.putVariable("x", "42"); diff --git a/shell/src/test/java/service/EnvironmentTest.java b/shell/src/test/java/service/EnvironmentTest.java index 0d7f13c..660e992 100644 --- a/shell/src/test/java/service/EnvironmentTest.java +++ b/shell/src/test/java/service/EnvironmentTest.java @@ -4,20 +4,9 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; public class EnvironmentTest { - private final Environment environment = Environment.getInstance(); - - @Test - public void testGetInstance() { - // Arrange - Environment env1 = Environment.getInstance(); - Environment env2 = Environment.getInstance(); - - // Assert - assertEquals(env1, env2); - } + private final Environment environment = new Environment(); @Test public void testGetCommand() { diff --git a/shell/src/test/java/service/ExecutorTest.java b/shell/src/test/java/service/ExecutorTest.java index c93234b..6419349 100644 --- a/shell/src/test/java/service/ExecutorTest.java +++ b/shell/src/test/java/service/ExecutorTest.java @@ -4,34 +4,33 @@ import static org.assertj.core.api.Assertions.assertThat; -// TODO: удалить public class ExecutorTest { -// private final Executor executor = new Executor(); -// private final Environment environment = Environment.getInstance(); - -// @Test -// public void testExecuteSingleCommand() { -// // Arrange -// String input = "echo hello world"; -// String expected = "hello world"; -// -// // Act -// String actual = executor.execute(input); -// -// // Assert -// assertThat(actual).isEqualTo(expected); -// } -// -// @Test -// public void testExecuteCommands() { -// // Arrange -// String input = "echo hello world | wc"; -// String expected = "1 2 10\n"; -// -// // Act -// String actual = executor.execute(input); -// -// // Assert -// assertThat(actual).isEqualTo(expected); -// } + private final Executor executor = new Executor(); + private final Environment environment = new Environment(); + + @Test + public void testExecuteSingleCommand() { + // Arrange + String input = "echo hello world"; + String expected = "hello world"; + + // Act + String actual = executor.execute(input); + + // Assert + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testExecuteCommands() { + // Arrange + String input = "echo hello world | wc"; + String expected = "1 2 10\n"; + + // Act + String actual = executor.execute(input); + + // Assert + assertThat(actual).isEqualTo(expected); + } } From d9269263cafe98ba1dc0855a7134b5ed46593f01 Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Mon, 1 Jun 2020 23:09:13 +0300 Subject: [PATCH 08/10] Add class diagram --- shell/docs/UML-Class-Diagram.png | Bin 0 -> 124091 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 shell/docs/UML-Class-Diagram.png diff --git a/shell/docs/UML-Class-Diagram.png b/shell/docs/UML-Class-Diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..e9876710c2b95c64bf24a92173be0aaa94e67992 GIT binary patch literal 124091 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

    |kI9bUC{Q_`Al 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⪚8_1Mh$6dPrV{QkaOm2q1R5`r zYJ&c&Gsp(=09}Sb5eUBSgaz}?3@a)|UTU4QPayunEJhN$&jk^?tfufQUyR?9O_L3} zId3Nq)J6%(n;g?f?g@pt26cZQo5#;YEXW>Ueji3%xH}Vf@VH>lA#c!ZH(z1XYPa|l z-6nF@y`cABCwX{k?fjFl?0%(y7y2${d0!+@-9)Wzua~XUpfmWr^@4A`&Q)#Cj-d>X zK2RV(@Z7<1@YUWeUxBS(zkkNpYwI7#U;mgs;k$3t9PIFA`$1>&vjuqpR4Bv8?RVRt z@wNPyRIG--vwV<4R8)M{3PuTtcViv^1l+Jy0xDLq0}K-nN_dnMuS3Lh4>VJ=EJPv0 z8$Q@uFfcI12BguJ#E0{c$>_2$@zPv}uXOGS!nW|70Gn;(#`EhqW8TJzv_!P6yMa0? zT_sVj=WxA6vD38UVi2oX4=b1rB0IQa$Bse$RwvcC z4CJY96S9fafjLxq5sW=&)xn<)rrd}=7gD;l`xhu#CJ{>)&{%ab3z~lISVrh6e&tS> zBrRZ*<52MxP@_yYU>`z zi-FpX7$p3K2{WNKBn%9rysRwH6*Ym3e5^(3q=$@rc`&jAq$II)Ww2W0LHx8uN7_pW zTxj<3j#bT#Za-od<>aJKDz>-^WeKR43WQMeVmXV$ZPIjY(?289!075aro z{AUh9BO(^864AE6ugW{=5qg08QyX32&MevM;sJXgE5H}A@+j|fWO zhv_NkHvhhr?i*Dkh--Od_?)k&)Wl1nopg+%{b$+~>q#RFyt?P=4HcgG!diuiyrM)> z-kls+37+%?UO!;ZoefR#;uD2zu!^jIXWPsI`=K8<$P52*a|yI@c{)FjO&u?V^X~$? zP3enqoN3&!5A^TA7m_jZ>03f4`&%#{@tsnXlY0wJn%%$x>#-+0le^B18iCY-%E|&J z(T)_=6T)m9UcJ8!PvC~%?RiSM3-+ZEVMyXRT~|^`Eh+f^i%$HojF~Ihd~r3z`CP1)H01(a9@pJc4G_rHdd<+c&NG z^gO4B<|;q$cOP4_-eNaY77{vsbYHR2`30tluOkBtCYz5rP%ldAt~)!eY?N*FQ?|(S zi5(%flz0Qjy?mg1|B^7>+x+ZbNomNl*vtMHTf=vE{+UwvpHFvxfAod=c6O(OP?zpZ zjQ%A`I`u6f>b?dI@;>kgO+9w}co$@snC@l|^jx>dkJjLqpn1IG?~=zZmul01W3^vT zbQ0>cgu`^_ILnG)}r7w@vkevzj>X{l)I6G(t7vQ3;F6b@kmY* z`N!6S{r4K8CB?&z{Dgh=pYa3#&8LT4kN^6FSu-9*p-?;Yk&J+?14P!yzfb-|>rUjS zUjJhq{5NOs>xDdF>+x?F+rLNO|LXYvyw&ex9zK?|2KWkc-2?ySy8ll~K5qA!X?Ee*jV5 B8aDs{ literal 0 HcmV?d00001 From 03e2db56a5ce3aa1fa989fd7addc3781d94fb7b6 Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Fri, 5 Jun 2020 02:58:18 +0300 Subject: [PATCH 09/10] Rework cat command --- shell/src/main/java/commands/impl/CommandCat.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/shell/src/main/java/commands/impl/CommandCat.java b/shell/src/main/java/commands/impl/CommandCat.java index 8e0ae4c..7d0b147 100644 --- a/shell/src/main/java/commands/impl/CommandCat.java +++ b/shell/src/main/java/commands/impl/CommandCat.java @@ -10,6 +10,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -26,7 +27,17 @@ public class CommandCat implements Command { */ @Override public String run(@NotNull List arguments) { - Path path = Paths.get(Environment.getCurrentPath(), joinArguments(arguments)); + String args = joinArguments(arguments); + + Path path; + if (Paths.get(Environment.getCurrentPath(), args).toFile().exists()) { + path = Paths.get(Environment.getCurrentPath(), args); + } else if (Paths.get(args).toFile().exists()) { + path = Paths.get(args); + } else { + return args; + } + try (Stream stream = Files.lines(path)) { return stream.collect(Collectors.joining("\n")); } catch (IOException e) { From 16add6b7c06b2ee9397593391ff771208d99e2fb Mon Sep 17 00:00:00 2001 From: Pavel Gromov Date: Sun, 7 Jun 2020 20:28:52 +0300 Subject: [PATCH 10/10] Create readme file --- Readme.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Readme.md diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..79a978e --- /dev/null +++ b/Readme.md @@ -0,0 +1,26 @@ +# CLI + +Данный проект - это интерпретатор командной строки, поддерживающий следующие команды: +- `cat [FILE]` - вывести на экран содержимое файла; +- `echo` - вывести на экран свой аргумент (или аргументы); +- `wc [FILE]` - вывести количество строк, слов и байт в файле; +- `pwd` - распечатать текущую директорию; +- `exit` - выйти из интерпретатора. + +Интерпретатор распознает строку при помощи парсера, который разделяет текст на +лексемы ([`Lexemes`](https://github.com/PaGr0m/ifmo-software-design/blob/master/shell/src/main/java/parser/LexemType.java)), +которые поддерживают одинарные и двойные кавычки, а также оператор подстановки переменных окружения (`$`). + +Все остальные команды, которые не перечислены в задании ("внешние команды") вызываются из Shell системы. + +У `CLI` имеется свое окружение ([`Environment`](https://github.com/PaGr0m/ifmo-software-design/blob/master/shell/src/main/java/service/Environment.java)), +которое хранит в себе все переменные окружения, а также доступные команды. +Окружение является единственным для всего проекта. +Было принято решение избавиться от шаблона одиночки ([`Singleton`](https://refactoring.guru/ru/design-patterns/singleton)) +для него, а использовать внедрение зависимостей ([`Dependency Injection`](https://en.wikipedia.org/wiki/Dependency_injection#:~:text=In%20software%20engineering%2C%20dependency%20injection,object%20is%20called%20a%20service.)). + +Для имплементации команд CLI было решено использовать паттерн "Команда" ([`Command`](https://refactoring.guru/ru/design-patterns/command)), +так как очень удобно задать единый интерфейс для функционала команды и реализовать метод для каждой команды. +Для создания команд отказался от использования шаблона "Фабрика" ([`Factory Method`](https://refactoring.guru/ru/design-patterns/factory-method)), +так как в данном случае посчитал это излишнем, поскольку обращение в `HashMap` будет намного быстрее и +по названию команды можно выдавать объект (в данном случае масштабируемость шаблона "Фабрика" никак не использовалась бы).