From cf3c125e77a12386f2472d036044096b293775cd Mon Sep 17 00:00:00 2001 From: junior rantila Date: Sun, 3 Oct 2021 01:12:39 +0200 Subject: [PATCH] watch: Add ability to run command on file change --- Userland/Utilities/watch.cpp | 110 +++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 23 deletions(-) diff --git a/Userland/Utilities/watch.cpp b/Userland/Utilities/watch.cpp index 4d9f9ce6c32..558d8c9b4ec 100644 --- a/Userland/Utilities/watch.cpp +++ b/Userland/Utilities/watch.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -33,6 +35,15 @@ static String build_header_string(Vector const& command, struct tim return builder.build(); } +static String build_header_string(Vector const& command, Vector const& filenames) +{ + StringBuilder builder; + builder.appendff("Every time any of {} changes: \x1b[1m", filenames); + builder.join(' ', command); + builder.append("\x1b[0m"); + return builder.build(); +} + static struct timeval get_current_time() { struct timespec ts; @@ -97,11 +108,12 @@ static int run_command(Vector const& command) int main(int argc, char** argv) { signal(SIGINT, handle_signal); - if (pledge("stdio proc exec", nullptr) < 0) { + if (pledge("stdio proc exec rpath", nullptr) < 0) { perror("pledge"); return 1; } + Vector files_to_watch; Vector command; Core::ArgsParser args_parser; args_parser.set_stop_on_first_non_option(true); @@ -109,28 +121,26 @@ int main(int argc, char** argv) args_parser.add_option(opt_interval, "Amount of time between updates", "interval", 'n', "seconds"); args_parser.add_option(flag_noheader, "Turn off the header describing the command and interval", "no-title", 't'); args_parser.add_option(flag_beep_on_fail, "Beep if the command has a non-zero exit code", "beep", 'b'); + Core::ArgsParser::Option file_arg { + .requires_argument = true, + .help_string = "Run command whenever this file changes. Can be used multiple times.", + .long_name = "file", + .short_name = 'f', + .value_name = "file", + .accept_value = [&files_to_watch](auto filename) { + files_to_watch.append(filename); + return true; + } + }; + args_parser.add_option(move(file_arg)); args_parser.add_positional_argument(command, "Command to run", "command"); args_parser.parse(argc, argv); - struct timeval interval; - if (opt_interval <= 0) { - interval = { 0, 100000 }; - } else { - interval = { opt_interval, 0 }; - } - - auto header = build_header_string(command, interval); command.append(nullptr); - auto now = get_current_time(); - auto next_run_time = now; - while (true) { - int usecs_to_sleep = usecs_from(now, next_run_time); - while (usecs_to_sleep > 0) { - usleep(usecs_to_sleep); - now = get_current_time(); - usecs_to_sleep = usecs_from(now, next_run_time); - } + String header; + + auto watch_callback = [&] { // Clear the screen, then reset the cursor position to the top left. warn("\033[H\033[2J"); // Print the header. @@ -147,11 +157,65 @@ int main(int argc, char** argv) fflush(stderr); } } - now = get_current_time(); - timeval_add(next_run_time, interval, next_run_time); - if (usecs_from(now, next_run_time) < 0) { - // The next execution is overdue, so we set next_run_time to now to prevent drift. - next_run_time = now; + }; + + if (!files_to_watch.is_empty()) { + header = build_header_string(command, files_to_watch); + + auto file_watcher = Core::BlockingFileWatcher(); + for (auto const& file : files_to_watch) { + if (!Core::File::exists(file)) { + warnln("Cannot watch '{}', it does not exist.", file); + return 1; + } + if (!file_watcher.is_watching(file)) { + auto success_or_error = file_watcher.add_watch(file, Core::FileWatcherEvent::Type::MetadataModified); + if (success_or_error.is_error() && !success_or_error.value()) { + warnln("Could not add '{}' to watch list.", file); + return 1; + } + } + } + + watch_callback(); + while (true) { + auto maybe_event = file_watcher.wait_for_event(); + if (maybe_event.has_value()) { + watch_callback(); + } + } + } else { + if (pledge("stdio proc exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + struct timeval interval; + if (opt_interval <= 0) { + interval = { 0, 100000 }; + } else { + interval = { opt_interval, 0 }; + } + + auto now = get_current_time(); + auto next_run_time = now; + header = build_header_string(command, interval); + while (true) { + int usecs_to_sleep = usecs_from(now, next_run_time); + while (usecs_to_sleep > 0) { + usleep(usecs_to_sleep); + now = get_current_time(); + usecs_to_sleep = usecs_from(now, next_run_time); + } + + watch_callback(); + + now = get_current_time(); + timeval_add(next_run_time, interval, next_run_time); + if (usecs_from(now, next_run_time) < 0) { + // The next execution is overdue, so we set next_run_time to now to prevent drift. + next_run_time = now; + } } } }