diff --git a/todo/CHANGELOG.md b/todo/CHANGELOG.md new file mode 100644 index 0000000..03acd03 --- /dev/null +++ b/todo/CHANGELOG.md @@ -0,0 +1,14 @@ +# Журнал изменений + +Все заметные изменения проекта будут документироваться в этом файле. + +Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/), +и этот проект придерживается [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0] - 2025-05-29 + +### Добавлено +- Базовая функциональность для управления задачами +- Команды: create, list, delete +- Консольный интерфейс для взаимодействия с приложением +- In-memory хранилище задач \ No newline at end of file diff --git a/todo/README.md b/todo/README.md new file mode 100644 index 0000000..6955372 --- /dev/null +++ b/todo/README.md @@ -0,0 +1,27 @@ +# TodoApp + +## Описание +TodoApp - это консольное приложение для управления списком задач, разработанное на Java в качестве учебного проекта. Приложение позволяет создавать, просматривать, отмечать как выполненные и удалять задачи через интерфейс командной строки. + +## Требования +- Java 24 или выше +- Maven 3.8.x или выше + +### Доступные команды +- `create <название_задачи>` - Создать новую задачу +- `list` - Показать список всех задач +- `complete ` - Отметить задачу как выполненную +- `delete ` - Удалить задачу +- `exit` - Выйти из приложения + +## Структура проекта +- `model` - Модели данных (Task, SimpleTask) +- `repo` - Репозитории для хранения задач +- `service` - Бизнес-логика +- `cli` - Компоненты интерфейса командной строки + +## Версии +Актуальная версия: 2.0 + +## Лицензия +[MIT](LICENSE) \ No newline at end of file diff --git a/todo/pom.xml b/todo/pom.xml index ad0e192..725addd 100644 --- a/todo/pom.xml +++ b/todo/pom.xml @@ -6,7 +6,7 @@ ru.kamask.pet todo - 1.0-SNAPSHOT + 2.0-SNAPSHOT 24 diff --git a/todo/src/main/java/ru/kamask/pet/todo/cli/CliEngine.java b/todo/src/main/java/ru/kamask/pet/todo/cli/CliEngine.java new file mode 100644 index 0000000..dd39b15 --- /dev/null +++ b/todo/src/main/java/ru/kamask/pet/todo/cli/CliEngine.java @@ -0,0 +1,69 @@ +package ru.kamask.pet.todo.cli; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Optional; + +import ru.kamask.pet.todo.service.TaskService; + +public class CliEngine { + private HashMap registry = new HashMap<>(); + private TaskService service; + private BufferedReader reader; + + public CliEngine(TaskService service) { + this.service = service; + reader = new BufferedReader(new InputStreamReader(System.in)); + initializeCommands(); + } + + public void start() throws IOException { + System.out.println("\nДобро пожаловать в Список задач 2.0!"); + System.out.println("Введите help для просмтора доступных команд или exit для выхода."); + while (true) { + System.out.print("\ntodo> "); + var input = reader.readLine().trim(); + System.out.println(); + switch (input) { + case "help" -> handleHelp(); + case "exit" -> System.exit(0); + default -> handleCommand(input).ifPresentOrElse(System.out::println, () -> { + System.out.println("Не известная команда. Повторите ввод.\n"); + handleHelp(); + }); + } + } + } + + void registerCommand(Command command) { + registry.put(command.name(), command); + } + + void initializeCommands() { + registerCommand(new CreateCommand()); + registerCommand(new ListCommand()); + registerCommand(new CompleteCommand()); + registerCommand(new DeleteCommand()); + } + + void handleHelp() { + registry.values().stream().map(handler -> handler.usage()).forEach(System.out::println); + System.out.printf(Command.templateUsage, "help", "Вот этот список команд\n"); + System.out.printf(Command.templateUsage, "exit", "Выход, но лучше не надо)\n"); + + } + + Optional handleCommand(String input) { + var parts = input.split("\\s+"); + var command = parts[0]; + var args = Arrays.copyOfRange(parts, 1, parts.length); + + var handlerOpt = Optional.ofNullable(registry.get(command)); + if (handlerOpt.isPresent()) + return handlerOpt.get().handle(args, service); + return Optional.empty(); + } +} diff --git a/todo/src/main/java/ru/kamask/pet/todo/cli/Command.java b/todo/src/main/java/ru/kamask/pet/todo/cli/Command.java new file mode 100644 index 0000000..52dde08 --- /dev/null +++ b/todo/src/main/java/ru/kamask/pet/todo/cli/Command.java @@ -0,0 +1,16 @@ +package ru.kamask.pet.todo.cli; + +import java.util.Optional; + +import ru.kamask.pet.todo.service.TaskService; + +public interface Command { + String templateUsage = " %-30s // %s"; + String errorMessage = "Не корректно введена команда. Введите help для спарвки."; + + Optional handle(String[] args, TaskService service); + + String name(); + + String usage(); +} diff --git a/todo/src/main/java/ru/kamask/pet/todo/cli/CompleteCommand.java b/todo/src/main/java/ru/kamask/pet/todo/cli/CompleteCommand.java new file mode 100644 index 0000000..a0ece27 --- /dev/null +++ b/todo/src/main/java/ru/kamask/pet/todo/cli/CompleteCommand.java @@ -0,0 +1,34 @@ +package ru.kamask.pet.todo.cli; + +import java.util.Optional; + +import ru.kamask.pet.todo.service.TaskService; + +public class CompleteCommand implements Command { + @Override + public String name() { + return "complete"; + } + + @Override + public String usage() { + return String.format(Command.templateUsage, name() + " ", "Отметить задачу как \"выполнена\"."); + } + + @Override + public Optional handle(String[] args, TaskService service) { + if (args.length != 1) + return Optional.of(Command.errorMessage); + + try { + int id = Integer.parseInt(args[0]); + + return Optional.of(service.complete(id) + ? "Задача ID-%d выполнена.".formatted(id) + : "Задача ID-%d не найдена.".formatted(id)); + + } catch (NumberFormatException e) { + return Optional.of("ID - должен быть числом."); + } + } +} diff --git a/todo/src/main/java/ru/kamask/pet/todo/cli/CreateCommand.java b/todo/src/main/java/ru/kamask/pet/todo/cli/CreateCommand.java new file mode 100644 index 0000000..4549625 --- /dev/null +++ b/todo/src/main/java/ru/kamask/pet/todo/cli/CreateCommand.java @@ -0,0 +1,29 @@ +package ru.kamask.pet.todo.cli; + +import java.util.Optional; + +import ru.kamask.pet.todo.service.TaskService; + +public class CreateCommand implements Command { + @Override + public String name() { + return "create"; + } + + @Override + public Optional handle(String[] args, TaskService service) { + if (args.length > 0) { + var title = String.join(" ", args); + if (title.length() > 30) + return Optional.of("Ошибка: максимальная длинна названия задачи 30 символов."); + service.create(title); + return Optional.of(String.format("Задача \"%s\" успешно добавлена!", title)); + } + return Optional.of(Command.errorMessage); + } + + @Override + public String usage() { + return String.format(templateUsage, name() + " <название_задачи>", "Добавление новой задачи"); + } +} diff --git a/todo/src/main/java/ru/kamask/pet/todo/cli/DeleteCommand.java b/todo/src/main/java/ru/kamask/pet/todo/cli/DeleteCommand.java new file mode 100644 index 0000000..b037776 --- /dev/null +++ b/todo/src/main/java/ru/kamask/pet/todo/cli/DeleteCommand.java @@ -0,0 +1,35 @@ +package ru.kamask.pet.todo.cli; + +import java.util.Optional; + +import ru.kamask.pet.todo.service.TaskService; + +public class DeleteCommand implements Command { + @Override + public String name() { + return "delete"; + } + + @Override + public String usage() { + return String.format(Command.templateUsage, name() + " ", "Удалить задачу."); + } + + @Override + public Optional handle(String[] args, TaskService service) { + if (args.length != 1) + return Optional.of(Command.errorMessage); + + try { + int id = Integer.parseInt(args[0]); + if (!service.has(id)) + return Optional.of("Задача ID-%d не найдена.".formatted(id)); + service.remove(id); + return Optional.of("Задача ID-%d удалена.".formatted(id)); + + } catch (NumberFormatException e) { + return Optional.of("ID - должен быть числом."); + } + } + +} diff --git a/todo/src/main/java/ru/kamask/pet/todo/cli/ListCommand.java b/todo/src/main/java/ru/kamask/pet/todo/cli/ListCommand.java new file mode 100644 index 0000000..102837c --- /dev/null +++ b/todo/src/main/java/ru/kamask/pet/todo/cli/ListCommand.java @@ -0,0 +1,39 @@ +package ru.kamask.pet.todo.cli; + +import java.util.Optional; + +import ru.kamask.pet.todo.model.SimpleTask; +import ru.kamask.pet.todo.model.Task; +import ru.kamask.pet.todo.service.TaskService; + +public class ListCommand implements Command { + @Override + public String name() { + return "list"; + } + + @Override + public Optional handle(String[] args, TaskService service) { + if (args.length > 0) + return Optional.of(Command.errorMessage); + + String template = "%-2s | %-30s | %s\n"; + String res = ""; + res += String.format(template, "ID", "Название задачи", "Статус"); + res += "-".repeat(50) + "\n"; + + if (service.list().size() == 0) + return Optional.of(res + "\nСписок задач пуст."); + + for (Task task : service.list()) { + SimpleTask.Data data = ((SimpleTask) task).data(); + res += String.format(template, data.id(), data.title(), data.done() ? "выполнено" : "не выполнено"); + } + return Optional.of(res); + } + + @Override + public String usage() { + return String.format(templateUsage, name(), "Список всех задач."); + } +} diff --git a/todo/src/main/java/ru/kamask/pet/todo/repo/InMemoryTaskRepository.java b/todo/src/main/java/ru/kamask/pet/todo/repo/InMemoryTaskRepository.java new file mode 100644 index 0000000..47bd259 --- /dev/null +++ b/todo/src/main/java/ru/kamask/pet/todo/repo/InMemoryTaskRepository.java @@ -0,0 +1,37 @@ +package ru.kamask.pet.todo.repo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import ru.kamask.pet.todo.model.Task; + +public class InMemoryTaskRepository implements TaskRepository { + private Map storage = new HashMap<>(); + + @Override + public void save(Task task) { + storage.put(task.id(), task); + } + + @Override + public Optional findById(int id) { + return Optional.ofNullable(storage.get(id)); + } + + @Override + public List findAll() { + return new ArrayList<>(storage.values()); + } + + @Override + public void delete(int id) { + storage.remove(id); + } + + public boolean has(int id) { + return storage.containsKey(id); + } +} diff --git a/todo/src/main/java/ru/kamask/pet/todo/repo/TaskRepository.java b/todo/src/main/java/ru/kamask/pet/todo/repo/TaskRepository.java new file mode 100644 index 0000000..3ae050d --- /dev/null +++ b/todo/src/main/java/ru/kamask/pet/todo/repo/TaskRepository.java @@ -0,0 +1,18 @@ +package ru.kamask.pet.todo.repo; + +import java.util.List; +import java.util.Optional; + +import ru.kamask.pet.todo.model.Task; + +public interface TaskRepository { + void save(Task task); + + Optional findById(int id); + + List findAll(); + + void delete(int id); + + boolean has(int id); +} diff --git a/todo/src/main/java/ru/kamask/pet/todo/service/TaskService.java b/todo/src/main/java/ru/kamask/pet/todo/service/TaskService.java new file mode 100644 index 0000000..29fd8dd --- /dev/null +++ b/todo/src/main/java/ru/kamask/pet/todo/service/TaskService.java @@ -0,0 +1,45 @@ +package ru.kamask.pet.todo.service; + +import java.util.List; +import java.util.Optional; + +import ru.kamask.pet.todo.model.Task; +import ru.kamask.pet.todo.model.SimpleTask; +import ru.kamask.pet.todo.repo.TaskRepository; + +public class TaskService { + private final TaskRepository repo; + + public TaskService(TaskRepository repo) { + this.repo = repo; + } + + public void create(String title) { + repo.save(new SimpleTask(title)); + } + + public Optional getById(int id) { + return repo.findById(id); + } + + public boolean complete(int id) { + var taskOpt = repo.findById(id); + if (taskOpt.isPresent()) { + taskOpt.get().markAsCompleted(); + return true; + } + return false; + } + + public List list() { + return repo.findAll(); + } + + public void remove(int id) { + repo.delete(id); + } + + public boolean has(int id) { + return repo.has(id); + } +}