diff --git a/apps/cp.cpp b/apps/cp.cpp index 4ef5b633..a1692bb0 100644 --- a/apps/cp.cpp +++ b/apps/cp.cpp @@ -1,11 +1,13 @@ #include +#include #include #include #include +#include using os::File; -Result copy_file(StringView source, StringView destination, bool verbose) +Result copy_file(StringView source, StringView destination, bool verbose, bool interactive) { SharedPtr source_file = TRY(os::File::open(source, File::ReadOnly)); SharedPtr destination_file; @@ -19,10 +21,23 @@ Result copy_file(StringView source, StringView destination, bool verbose) auto basename = TRY(PathParser::basename(source)); auto path = TRY(PathParser::join_paths(destination, basename.view())); + if (interactive && os::FileSystem::exists(path.view(), false)) + { + auto prompt = TRY(String::format("cp: Overwrite %s with %s?"_sv, path.chars(), source.chars())); + if (!os::conditional_prompt(prompt.view(), os::DefaultNo)) return {}; + } + destination_file = TRY(File::open_or_create(path.view(), File::ReadWrite, st.st_mode & ~S_IFMT)); } else + { + if (interactive && os::FileSystem::exists(destination, false)) + { + auto prompt = TRY(String::format("cp: Overwrite %s with %s?"_sv, destination.chars(), source.chars())); + if (!os::conditional_prompt(prompt.view(), os::DefaultNo)) return {}; + } destination_file = TRY(File::open_or_create(destination, File::ReadWrite, st.st_mode & ~S_IFMT)); + } if (verbose) os::eprintln("copying %s to %s", source.chars(), destination.chars()); @@ -42,6 +57,7 @@ Result luna_main(int argc, char** argv) Vector files; StringView destination; bool verbose { false }; + bool interactive { false }; os::ArgumentParser parser; parser.add_description("Copy files or directories."_sv); @@ -49,9 +65,10 @@ Result luna_main(int argc, char** argv) parser.set_vector_argument(files, "files"_sv, true); parser.add_positional_argument(destination, "destination"_sv, true); parser.add_switch_argument(verbose, 'v', "verbose"_sv, "show more information"_sv); - parser.parse(argc, argv); + parser.add_switch_argument(interactive, 'i', "interactive"_sv, "prompt before overwriting existing files"_sv); + TRY(parser.parse(argc, argv)); - for (const auto& file : files) { TRY(copy_file(file, destination, verbose)); } + for (const auto& file : files) { TRY(copy_file(file, destination, verbose, interactive)); } return 0; } diff --git a/libos/CMakeLists.txt b/libos/CMakeLists.txt index 3ce41249..0771eb28 100644 --- a/libos/CMakeLists.txt +++ b/libos/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES src/Main.cpp src/Path.cpp src/Mode.cpp + src/Prompt.cpp ) add_library(os ${SOURCES}) diff --git a/libos/include/os/Prompt.h b/libos/include/os/Prompt.h new file mode 100644 index 00000000..7d5f1f83 --- /dev/null +++ b/libos/include/os/Prompt.h @@ -0,0 +1,35 @@ +/** + * @file Prompt.h + * @author apio (cloudapio.eu) + * @brief A few functions to get input from the user. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include + +namespace os +{ + /** + * @brief The default value for a conditional prompt. + * + */ + enum DefaultValue + { + DefaultNo = 0, + DefaultYes = 1 + }; + + /** + * @brief Show a prompt asking a yes or no question to the user. + * + * @param prompt The prompt that will be shown, without the "[y/N]" part. This will be added automatically by the + * function. + * @param fallback The value to return if the user's input was not a valid yes or no answer. + * @return true If the user entered 'y', 'Y', or the default value was yes and the input was invalid. + * @return false If the user entered 'n', 'N', or the default value was no and the input was invalid. + */ + bool conditional_prompt(StringView prompt, DefaultValue fallback); +} diff --git a/libos/src/Prompt.cpp b/libos/src/Prompt.cpp new file mode 100644 index 00000000..6ec13581 --- /dev/null +++ b/libos/src/Prompt.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +namespace os +{ + bool conditional_prompt(StringView prompt, DefaultValue fallback) + { + bool default_yes = fallback == DefaultYes; + + os::print("%s [%s] ", prompt.chars(), default_yes ? "Y/n" : "y/N"); + + auto line_or_error = os::File::standard_input()->read_line(); + if (line_or_error.has_error()) return default_yes; + + auto line = line_or_error.release_value(); + line.trim("\n"); + + if (line.view() == "y" || line.view() == "Y") return true; + if (line.view() == "n" || line.view() == "N") return false; + + return default_yes; + } +}