diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index adb9726a8..afa4e3187 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -4,16 +4,20 @@ * This file is part of the uutils coreutils package. * * (c) Anthony Deschamps + * (c) Sylvestre Ledru * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ extern crate chrono; -#[macro_use] + extern crate clap; +#[macro_use] extern crate uucore; +use clap::{App, Arg}; + use chrono::offset::Utc; use chrono::{DateTime, FixedOffset, Local, Offset}; use std::fs::File; @@ -25,8 +29,27 @@ const DATE: &str = "date"; const HOURS: &str = "hours"; const MINUTES: &str = "minutes"; const SECONDS: &str = "seconds"; +const HOUR: &str = "hour"; +const MINUTE: &str = "minute"; +const SECOND: &str = "second"; const NS: &str = "ns"; +const NAME: &str = "date"; +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const ABOUT: &str = "print or set the system date and time"; + +const OPT_DATE: &str = "date"; +const OPT_FORMAT: &str = "format"; +const OPT_FILE: &str = "file"; +const OPT_DEBUG: &str = "debug"; +const OPT_ISO_8601: &str = "iso-8601"; +const OPT_RFC_EMAIL: &str = "rfc-email"; +const OPT_RFC_3339: &str = "rfc-3339"; +const OPT_SET: &str = "set"; +const OPT_REFERENCE: &str = "reference"; +const OPT_UNIVERSAL: &str = "universal"; +const OPT_UNIVERSAL_2: &str = "utc"; + // Help strings static ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format. @@ -35,7 +58,7 @@ static ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format. for date and time to the indicated precision. Example: 2006-08-14T02:34:56-06:00"; -static RFC_2822_HELP_STRING: &str = "output date and time in RFC 2822 format. +static RFC_5322_HELP_STRING: &str = "output date and time in RFC 5322 format. Example: Mon, 14 Aug 2006 02:34:56 -0600"; static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format. @@ -54,7 +77,7 @@ struct Settings { /// Various ways of displaying the date enum Format { Iso8601(Iso8601Format), - Rfc2822, + Rfc5322, Rfc3339(Rfc3339Format), Custom(String), Default, @@ -78,9 +101,9 @@ enum Iso8601Format { impl<'a> From<&'a str> for Iso8601Format { fn from(s: &str) -> Self { match s { - HOURS => Iso8601Format::Hours, - MINUTES => Iso8601Format::Minutes, - SECONDS => Iso8601Format::Seconds, + HOURS | HOUR => Iso8601Format::Hours, + MINUTES | MINUTE => Iso8601Format::Minutes, + SECONDS | SECOND => Iso8601Format::Seconds, NS => Iso8601Format::Ns, DATE => Iso8601Format::Date, // Should be caught by clap @@ -99,7 +122,7 @@ impl<'a> From<&'a str> for Rfc3339Format { fn from(s: &str) -> Self { match s { DATE => Rfc3339Format::Date, - SECONDS => Rfc3339Format::Seconds, + SECONDS | SECOND => Rfc3339Format::Seconds, NS => Rfc3339Format::Ns, // Should be caught by clap _ => panic!("Invalid format: {}", s), @@ -108,7 +131,108 @@ impl<'a> From<&'a str> for Rfc3339Format { } pub fn uumain(args: Vec) -> i32 { - let settings = parse_cli(args); + let syntax = format!( + "{0} [OPTION]... [+FORMAT]... + {0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]", + NAME + ); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&syntax[..]) + .arg( + Arg::with_name(OPT_DATE) + .short("d") + .long(OPT_DATE) + .takes_value(true) + .help("display time described by STRING, not 'now'"), + ) + .arg( + Arg::with_name(OPT_FILE) + .short("f") + .long(OPT_FILE) + .takes_value(true) + .help("like --date; once for each line of DATEFILE"), + ) + .arg( + Arg::with_name(OPT_ISO_8601) + .short("I") + .long(OPT_ISO_8601) + .takes_value(true) + .help(ISO_8601_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_RFC_EMAIL) + .short("R") + .long(OPT_RFC_EMAIL) + .help(RFC_5322_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_RFC_3339) + .long(OPT_RFC_3339) + .takes_value(true) + .help(RFC_3339_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_DEBUG) + .long(OPT_DEBUG) + .help("annotate the parsed date, and warn about questionable usage to stderr"), + ) + .arg( + Arg::with_name(OPT_REFERENCE) + .short("r") + .long(OPT_REFERENCE) + .takes_value(true) + .help("display the last modification time of FILE"), + ) + .arg( + Arg::with_name(OPT_SET) + .short("s") + .long(OPT_SET) + .takes_value(true) + .help("set time described by STRING"), + ) + .arg( + Arg::with_name(OPT_UNIVERSAL) + .short("u") + .long(OPT_UNIVERSAL) + .alias(OPT_UNIVERSAL_2) + .help("print or set Coordinated Universal Time (UTC)"), + ) + .arg(Arg::with_name(OPT_FORMAT).multiple(true)) + .get_matches_from(&args); + + let format = if let Some(form) = matches.value_of(OPT_FORMAT) { + let form = form[1..].into(); + Format::Custom(form) + } else if let Some(fmt) = matches + .values_of(OPT_ISO_8601) + .map(|mut iter| iter.next().unwrap_or(DATE).into()) + { + Format::Iso8601(fmt) + } else if matches.is_present(OPT_RFC_EMAIL) { + Format::Rfc5322 + } else if let Some(fmt) = matches.value_of(OPT_RFC_3339).map(Into::into) { + Format::Rfc3339(fmt) + } else { + Format::Default + }; + + let date_source = if let Some(date) = matches.value_of(OPT_DATE) { + DateSource::Custom(date.into()) + } else if let Some(file) = matches.value_of(OPT_FILE) { + DateSource::File(file.into()) + } else { + DateSource::Now + }; + + let settings = Settings { + utc: matches.is_present(OPT_UNIVERSAL), + format, + date_source, + // TODO: Handle this option: + set_to: None, + }; if let Some(_time) = settings.set_to { unimplemented!(); @@ -174,81 +298,6 @@ pub fn uumain(args: Vec) -> i32 { 0 } -/// Handle command line arguments. -fn parse_cli(args: Vec) -> Settings { - let matches = clap_app!( - date => - (@group dates => - (@arg date: -d --date [STRING] - "display time described by STRING, not 'now'") - (@arg file: -f --file [DATEFILE] - "like --date; once for each line of DATEFILE")) - - (@group format => - (@arg iso_8601: -I --("iso-8601") - possible_value[date hours minutes seconds ns] - #{0, 1} - ISO_8601_HELP_STRING) - (@arg rfc_2822: -R --("rfc-2822") - RFC_2822_HELP_STRING) - (@arg rfc_3339: --("rfc-3339") - possible_value[date seconds ns] - RFC_3339_HELP_STRING) - (@arg custom_format: +takes_value { - |s| if s.starts_with('+') { - Ok(()) - } else { - Err(String::from("Date formats must start with a '+' character")) - } - })) - - (@arg debug: --debug - "annotate the parsed date, and warn about questionable usage to stderr") - (@arg reference: -r --reference [FILE] - "display the last modification time of FILE") - (@arg set: -s --set [STRING] - "set time described by STRING") - (@arg utc: -u --utc --universal - "print or set Coordinated Universal Time (UTC)")) - // TODO: Decide whether this is appropriate. - // The GNU date command has an explanation of all formatting options, - // but the `chrono` crate has a few differences (most notably, the %Z option) - // (after_help: include_str!("usage.txt"))) - .get_matches_from(args); - - let format = if let Some(form) = matches.value_of("custom_format") { - let form = form[1..].into(); - Format::Custom(form) - } else if let Some(fmt) = matches - .values_of("iso_8601") - .map(|mut iter| iter.next().unwrap_or(DATE).into()) - { - Format::Iso8601(fmt) - } else if matches.is_present("rfc_2822") { - Format::Rfc2822 - } else if let Some(fmt) = matches.value_of("rfc_3339").map(Into::into) { - Format::Rfc3339(fmt) - } else { - Format::Default - }; - - let date_source = if let Some(date) = matches.value_of("date") { - DateSource::Custom(date.into()) - } else if let Some(file) = matches.value_of("file") { - DateSource::File(file.into()) - } else { - DateSource::Now - }; - - Settings { - utc: matches.is_present("utc"), - format, - date_source, - // TODO: Handle this option: - set_to: None, - } -} - /// Return the appropriate format string for the given settings. fn make_format_string(settings: &Settings) -> &str { match settings.format { @@ -259,7 +308,7 @@ fn make_format_string(settings: &Settings) -> &str { Iso8601Format::Seconds => "%FT%T%:z", Iso8601Format::Ns => "%FT%T,%f%:z", }, - Format::Rfc2822 => "%a, %d %h %Y %T %z", + Format::Rfc5322 => "%a, %d %h %Y %T %z", Format::Rfc3339(ref fmt) => match *fmt { Rfc3339Format::Date => "%F", Rfc3339Format::Seconds => "%F %T%:z",