From e1da4d682173d6adbc0e5c99ec1b3c8c9a948957 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 9 Jun 2020 18:29:31 +0300 Subject: Add date builtin --- libbutl/builtin-options.cxx | 268 ++++++++++++++++++++++++++++++++++++++++++ libbutl/builtin-options.hxx | 65 ++++++++++ libbutl/builtin-options.ixx | 9 ++ libbutl/builtin.cli | 5 + libbutl/builtin.cxx | 97 +++++++++++++++ tests/builtin/date.testscript | 48 ++++++++ 6 files changed, 492 insertions(+) create mode 100644 tests/builtin/date.testscript diff --git a/libbutl/builtin-options.cxx b/libbutl/builtin-options.cxx index 0e66a21..2848eea 100644 --- a/libbutl/builtin-options.cxx +++ b/libbutl/builtin-options.cxx @@ -895,6 +895,274 @@ namespace butl return r; } + // date_options + // + + date_options:: + date_options () + : utc_ () + { + } + + bool date_options:: + parse (int& argc, + char** argv, + bool erase, + ::butl::cli::unknown_mode opt, + ::butl::cli::unknown_mode arg) + { + ::butl::cli::argv_scanner s (argc, argv, erase); + bool r = _parse (s, opt, arg); + return r; + } + + bool date_options:: + parse (int start, + int& argc, + char** argv, + bool erase, + ::butl::cli::unknown_mode opt, + ::butl::cli::unknown_mode arg) + { + ::butl::cli::argv_scanner s (start, argc, argv, erase); + bool r = _parse (s, opt, arg); + return r; + } + + bool date_options:: + parse (int& argc, + char** argv, + int& end, + bool erase, + ::butl::cli::unknown_mode opt, + ::butl::cli::unknown_mode arg) + { + ::butl::cli::argv_scanner s (argc, argv, erase); + bool r = _parse (s, opt, arg); + end = s.end (); + return r; + } + + bool date_options:: + parse (int start, + int& argc, + char** argv, + int& end, + bool erase, + ::butl::cli::unknown_mode opt, + ::butl::cli::unknown_mode arg) + { + ::butl::cli::argv_scanner s (start, argc, argv, erase); + bool r = _parse (s, opt, arg); + end = s.end (); + return r; + } + + bool date_options:: + parse (::butl::cli::scanner& s, + ::butl::cli::unknown_mode opt, + ::butl::cli::unknown_mode arg) + { + bool r = _parse (s, opt, arg); + return r; + } + + typedef + std::map + _cli_date_options_map; + + static _cli_date_options_map _cli_date_options_map_; + + struct _cli_date_options_map_init + { + _cli_date_options_map_init () + { + _cli_date_options_map_["--utc"] = + &::butl::cli::thunk< date_options, bool, &date_options::utc_ >; + _cli_date_options_map_["-u"] = + &::butl::cli::thunk< date_options, bool, &date_options::utc_ >; + } + }; + + static _cli_date_options_map_init _cli_date_options_map_init_; + + bool date_options:: + _parse (const char* o, ::butl::cli::scanner& s) + { + _cli_date_options_map::const_iterator i (_cli_date_options_map_.find (o)); + + if (i != _cli_date_options_map_.end ()) + { + (*(i->second)) (*this, s); + return true; + } + + return false; + } + + bool date_options:: + _parse (::butl::cli::scanner& s, + ::butl::cli::unknown_mode opt_mode, + ::butl::cli::unknown_mode arg_mode) + { + // Can't skip combined flags (--no-combined-flags). + // + assert (opt_mode != ::butl::cli::unknown_mode::skip); + + bool r = false; + bool opt = true; + + while (s.more ()) + { + const char* o = s.peek (); + + if (std::strcmp (o, "--") == 0) + { + opt = false; + } + + if (opt) + { + if (_parse (o, s)) + { + r = true; + continue; + } + + if (std::strncmp (o, "-", 1) == 0 && o[1] != '\0') + { + // Handle combined option values. + // + std::string co; + if (const char* v = std::strchr (o, '=')) + { + co.assign (o, 0, v - o); + ++v; + + int ac (2); + char* av[] = + { + const_cast (co.c_str ()), + const_cast (v) + }; + + ::butl::cli::argv_scanner ns (0, ac, av); + + if (_parse (co.c_str (), ns)) + { + // Parsed the option but not its value? + // + if (ns.end () != 2) + throw ::butl::cli::invalid_value (co, v); + + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = co.c_str (); + } + } + + // Handle combined flags. + // + char cf[3]; + { + const char* p = o + 1; + for (; *p != '\0'; ++p) + { + if (!((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9'))) + break; + } + + if (*p == '\0') + { + for (p = o + 1; *p != '\0'; ++p) + { + std::strcpy (cf, "-"); + cf[1] = *p; + cf[2] = '\0'; + + int ac (1); + char* av[] = + { + cf + }; + + ::butl::cli::argv_scanner ns (0, ac, av); + + if (!_parse (cf, ns)) + break; + } + + if (*p == '\0') + { + // All handled. + // + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = cf; + } + } + } + + switch (opt_mode) + { + case ::butl::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::butl::cli::unknown_mode::stop: + { + break; + } + case ::butl::cli::unknown_mode::fail: + { + throw ::butl::cli::unknown_option (o); + } + } + + break; + } + } + + switch (arg_mode) + { + case ::butl::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::butl::cli::unknown_mode::stop: + { + break; + } + case ::butl::cli::unknown_mode::fail: + { + throw ::butl::cli::unknown_argument (o); + } + } + + break; + } + + return r; + } + // ln_options // diff --git a/libbutl/builtin-options.hxx b/libbutl/builtin-options.hxx index 99d955d..b389298 100644 --- a/libbutl/builtin-options.hxx +++ b/libbutl/builtin-options.hxx @@ -390,6 +390,71 @@ namespace butl bool preserve_; }; + class date_options + { + public: + date_options (); + + // Return true if anything has been parsed. + // + bool + parse (int& argc, + char** argv, + bool erase = false, + ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail, + ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop); + + bool + parse (int start, + int& argc, + char** argv, + bool erase = false, + ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail, + ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop); + + bool + parse (int& argc, + char** argv, + int& end, + bool erase = false, + ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail, + ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop); + + bool + parse (int start, + int& argc, + char** argv, + int& end, + bool erase = false, + ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail, + ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop); + + bool + parse (::butl::cli::scanner&, + ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail, + ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop); + + // Option accessors. + // + const bool& + utc () const; + + // Implementation details. + // + protected: + bool + _parse (const char*, ::butl::cli::scanner&); + + private: + bool + _parse (::butl::cli::scanner&, + ::butl::cli::unknown_mode option, + ::butl::cli::unknown_mode argument); + + public: + bool utc_; + }; + class ln_options { public: diff --git a/libbutl/builtin-options.ixx b/libbutl/builtin-options.ixx index b2275c6..f10f82d 100644 --- a/libbutl/builtin-options.ixx +++ b/libbutl/builtin-options.ixx @@ -166,6 +166,15 @@ namespace butl return this->preserve_; } + // date_options + // + + inline const bool& date_options:: + utc () const + { + return this->utc_; + } + // ln_options // diff --git a/libbutl/builtin.cli b/libbutl/builtin.cli index 5aeb675..adc47fa 100644 --- a/libbutl/builtin.cli +++ b/libbutl/builtin.cli @@ -29,6 +29,11 @@ namespace butl bool --preserve|-p; }; + class date_options + { + bool --utc|-u; + }; + class ln_options { bool --symbolic|-s; diff --git a/libbutl/builtin.cxx b/libbutl/builtin.cxx index fe74b05..940e5a0 100644 --- a/libbutl/builtin.cxx +++ b/libbutl/builtin.cxx @@ -680,6 +680,102 @@ namespace butl return 1; } + // date [-u|--utc] [+] + // + // Note: must be executed asynchronously. + // + static uint8_t + date (const strings& args, + auto_fd in, auto_fd out, auto_fd err, + const dir_path&, + const builtin_callbacks& cbs) noexcept + try + { + uint8_t r (1); + ofdstream cerr (err != nullfd ? move (err) : fddup (stderr_fd ())); + + auto error = [&cerr] (bool fail = false) + { + return error_record (cerr, fail, "date"); + }; + + auto fail = [&error] () {return error (true /* fail */);}; + + try + { + in.close (); + ofdstream cout (out != nullfd ? move (out) : fddup (stdout_fd ())); + + // Parse arguments. + // + cli::vector_scanner scan (args); + + date_options ops ( + parse (scan, args, cbs.parse_option, fail)); + + const char* format; + + string fs; // Storage. + if (scan.more ()) + { + fs = scan.next (); + if (fs[0] != '+') + fail () << "date format argument must start with '+'"; + + format = fs.c_str () + 1; + } + else + format = "%a %b %e %H:%M:%S %Z %Y"; + + if (scan.more ()) + fail () << "unexpected argument '" << scan.next () << "'"; + + // Print the current time. + // + try + { + to_stream (cout, + system_clock::now (), + format, + false /* special */, + !ops.utc ()); + } + catch (const system_error& e) + { + fail () << "unable to print time in format '" << format << "': " << e; + } + + cout << '\n'; + cout.close (); + r = 0; + } + // Can be thrown while closing cin or creating, writing to, or closing + // cout. + // + catch (const io_error& e) + { + error () << e; + } + catch (const failed&) + { + // Diagnostics has already been issued. + } + catch (const cli::exception& e) + { + error () << e; + } + + cerr.close (); + return r; + } + // In particular, handles io_error exception potentially thrown while + // creating, writing to, or closing cerr. + // + catch (const std::exception&) + { + return 1; + } + // echo ... // // Note: must be executed asynchronously. @@ -2111,6 +2207,7 @@ namespace butl { {"cat", {&async_impl<&cat>, 2}}, {"cp", {&sync_impl<&cp>, 2}}, + {"date", {&async_impl<&date>, 2}}, {"diff", {nullptr, 2}}, {"echo", {&async_impl<&echo>, 2}}, {"false", {&false_, 0}}, diff --git a/tests/builtin/date.testscript b/tests/builtin/date.testscript new file mode 100644 index 0000000..0b466c3 --- /dev/null +++ b/tests/builtin/date.testscript @@ -0,0 +1,48 @@ +# file : tests/builtin/date.testscript +# license : MIT; see accompanying LICENSE file + +test.arguments = "date" + +: timezone +: +: Here we also make sure that the dates are not only verified but can also be +: seen if running with the verbosity level 2 and up, which helps with +: troubleshooting. +: +{ + $* | set local; + $* --utc | set utc; + + echo "local: $local" >~'%local: \S+ \S+ .+ \d{2}:\d{2}:\d{2} .+ \d+%'; + echo "utc: $utc" >~'%utc: \S+ \S+ .+ \d{2}:\d{2}:\d{2} .+ \d+%' +} + +: format +: +{ + : non-empty + : + $* '+%Y-%m-%d %H:%M:%S%[.N]' >~'%\d+-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{9}%' + + : empty + : + $* '+' >'' + + : no-leading-plus + : + $* '%Y-%m-%d %H:%M:%S' 2>>EOE!= 0 + date: date format argument must start with '+' + EOE +} + +: unknown-option +: +$* -d '+%Y-%m-%d %H:%M:%S' 2>>EOE!= 0 + date: unknown option '-d' + EOE + +: unexpected-arg +: +$* '+%Y-%m-%d %H:%M:%S' '%Y' 2>>EOE!= 0 + date: unexpected argument '%Y' + EOE -- cgit v1.1