aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2020-06-09 18:29:31 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2020-06-11 10:25:02 +0300
commite1da4d682173d6adbc0e5c99ec1b3c8c9a948957 (patch)
tree5ca5df253b716d9a9672eee8dc9bdee10cb2d58e
parent8483129b6a4746247fd7fb3b5554e10181ad937a (diff)
Add date builtin
-rw-r--r--libbutl/builtin-options.cxx268
-rw-r--r--libbutl/builtin-options.hxx65
-rw-r--r--libbutl/builtin-options.ixx9
-rw-r--r--libbutl/builtin.cli5
-rw-r--r--libbutl/builtin.cxx97
-rw-r--r--tests/builtin/date.testscript48
6 files changed, 492 insertions, 0 deletions
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<std::string, void (*) (date_options&, ::butl::cli::scanner&)>
+ _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<char*> (co.c_str ()),
+ const_cast<char*> (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] [+<format>]
+ //
+ // 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<date_options> (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 <string>...
//
// 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