--- /dev/null
+Return-Path: <mpn@google.com>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id 4831C431FAF\r
+ for <notmuch@notmuchmail.org>; Mon, 17 Sep 2012 07:13:51 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.7\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
+ tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, RCVD_IN_DNSWL_LOW=-0.7]\r
+ autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id Fw+F4127W2uw for <notmuch@notmuchmail.org>;\r
+ Mon, 17 Sep 2012 07:13:48 -0700 (PDT)\r
+Received: from mail-bk0-f53.google.com (mail-bk0-f53.google.com\r
+ [209.85.214.53]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
+ (No client certificate requested)\r
+ by olra.theworths.org (Postfix) with ESMTPS id E5754431FAE\r
+ for <notmuch@notmuchmail.org>; Mon, 17 Sep 2012 07:13:47 -0700 (PDT)\r
+Received: by bkwj4 with SMTP id j4so2779763bkw.26\r
+ for <notmuch@notmuchmail.org>; Mon, 17 Sep 2012 07:13:45 -0700 (PDT)\r
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com;\r
+ s=20120113; h=sender:from:to:subject:in-reply-to:organization:references\r
+ :user-agent:x-face:face:x-pgp:x-pgp-fp:date:message-id:mime-version\r
+ :content-type; bh=SXKUsmwu/XziMg4brNjKTGDYW8B15ZqO0UtdkTxkpPg=;\r
+ b=pLAHGftO/0ZKngdQR98x5OteyD04pbkPE59nLF2fQ3h76SUlNmHX5sjZQJwyx6s2pF\r
+ ad35K6xDG+73CclcRlTNLsbFXiTJvW9hfGIPUugxCwnOTOqhdK2asJvo4roNfFl+quI6\r
+ 22rGZnbPVbY4xk6s3EodUJb1fp7cp307dxCWlhQq+FIFLfBF5sKriaD3Vov0K17mjym0\r
+ aXzird+Y149jm70qyev9OiWphi2TuSdkJNKX9FNqUBZE7IXwwXm6LqlBjbXNiweYQ5AL\r
+ RnxZiUGRnlaqEfNOVVK4PprlD61UTWBjO/LNnsOBvNVwk3qUpTmbHeual8eAX7QLq1PS /baw==\r
+X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r
+ d=google.com; s=20120113;\r
+ h=sender:from:to:subject:in-reply-to:organization:references\r
+ :user-agent:x-face:face:x-pgp:x-pgp-fp:date:message-id:mime-version\r
+ :content-type:x-gm-message-state;\r
+ bh=SXKUsmwu/XziMg4brNjKTGDYW8B15ZqO0UtdkTxkpPg=;\r
+ b=kGbgTWuWqje8sC096jaqF5rLNOd+lXV5wVegczAmw9cTMtOtfnOwT8AOqOf55IfC9M\r
+ d47g8Cvg7TGhhXa2/v9uY0v4hfon224VVfp23p17xX/AKEqXqvmOLmRBVTgvvCHIjkYB\r
+ ZGJwFW9yADj0MyM3aDq7mm2heFzuzelu6RLLGBtVjqHuivJnxbjQDs5N6V+/UV/WykXa\r
+ ygRbJii0hjpoMpLvww7EXUFS7/hY6+G12XQ59rQyJ1Bb6yII5ri4CaESxlFjWrcNKhCD\r
+ fe/ZvnLsdsbYglRrM35OlkaYlQC4bpYTDdt7VsnOsTmKRbxCr7tqtyXWRAxowvcvKWv6\r
+ YmLg==\r
+Received: by 10.204.148.83 with SMTP id o19mr4351950bkv.74.1347891225206;\r
+ Mon, 17 Sep 2012 07:13:45 -0700 (PDT)\r
+Received: by 10.204.148.83 with SMTP id o19mr4351935bkv.74.1347891224863;\r
+ Mon, 17 Sep 2012 07:13:44 -0700 (PDT)\r
+Received: from mpn-glaptop ([172.28.91.186])\r
+ by mx.google.com with ESMTPS id 14sm5629228bkw.15.2012.09.17.07.13.41\r
+ (version=TLSv1/SSLv3 cipher=OTHER);\r
+ Mon, 17 Sep 2012 07:13:43 -0700 (PDT)\r
+Sender: Michal Nazarewicz <mpn@google.com>\r
+From: Michal Nazarewicz <mina86@mina86.com>\r
+To: Jani Nikula <jani@nikula.org>, notmuch@notmuchmail.org,\r
+ David Bremner <david@tethera.net>\r
+Subject: Re: [PATCH v3 2/9] parse-time-string: add a date/time parser to\r
+ notmuch\r
+In-Reply-To: <878vcerurc.fsf@nikula.org>\r
+Organization: http://mina86.com/\r
+References: <cover.1347484177.git.jani@nikula.org>\r
+ <89741ec9a9687fca8b30aa1a4877392d355dd3ce.1347484177.git.jani@nikula.org>\r
+ <xa1tvcfidvpe.fsf@mina86.com> <878vcerurc.fsf@nikula.org>\r
+User-Agent: Notmuch/0.14+22~g8bdc16b (http://notmuchmail.org) Emacs/24.2.50.1\r
+ (x86_64-unknown-linux-gnu)\r
+X-Face: PbkBB1w#)bOqd`iCe"Ds{e+!C7`pkC9a|f)Qo^BMQvy\q5x3?vDQJeN(DS?|-^$uMti[3D*#^_Ts"pU$jBQLq~Ud6iNwAw_r_o_4]|JO?]}P_}Nc&"p#D(ZgUb4uCNPe7~a[DbPG0T~!&c.y$Ur,=N4RT>]dNpd; KFrfMCylc}gc??'U2j,!8%xdD\r
+Face: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAJFBMVEWbfGlUPDDHgE57V0jUupKjgIObY0PLrom9mH4dFRK4gmjPs41MxjOgAAACQElEQVQ4jW3TMWvbQBQHcBk1xE6WyALX1069oZBMlq+ouUwpEQQ6uRjttkWP4CmBgGM0BQLBdPFZYPsyFUo6uEtKDQ7oy/U96XR2Ux8ehH/89Z6enqxBcS7Lg81jmSuujrfCZcLI/TYYvbGj+jbgFpHJ/bqQAUISj8iLyu4LuFHJTosxsucO4jSDNE0Hq3hwK/ceQ5sx97b8LcUDsILfk+ovHkOIsMbBfg43VuQ5Ln9YAGCkUdKJoXR9EclFBhixy3EGVz1K6eEkhxCAkeMMnqoAhAKwhoUJkDrCqvbecaYINlFKSRS1i12VKH1XpUd4qxL876EkMcDvHj3s5RBajHHMlA5iK32e0C7VgG0RlzFPvoYHZLRmAC0BmNcBruhkE0KsMsbEc62ZwUJDxWUdMsMhVqovoT96i/DnX/ASvz/6hbCabELLk/6FF/8PNpPCGqcZTGFcBhhAaZZDbQPaAB3+KrWWy2XgbYDNIinkdWAFcCpraDE/knwe5DBqGmgzESl1p2E4MWAz0VUPgYYzmfWb9yS4vCvgsxJriNTHoIBz5YteBvg+VGISQWUqhMiByPIPpygeDBE6elD973xWwKkEiHZAHKjhuPsFnBuArrzxtakRcISv+XMIPl4aGBUJm8Emk7qBYU8IlgNEIpiJhk/No24jHwkKTFHDWfPniR4iw5vJaw2nzSjfq2zffcE/GDjRC2dn0J0XwPAbDL84TvaFCJEU4Oml9pRyEUhR3Cl2t01AoEjRbs0sYugp14/4X5n4pU4EHHnMAAAAAElFTkSuQmCC\r
+X-PGP: 50751FF4\r
+X-PGP-FP: AC1F 5F5C D418 88F8 CC84 5858 2060 4012 5075 1FF4\r
+Date: Mon, 17 Sep 2012 16:13:35 +0200\r
+Message-ID: <xa1tpq5kzqi8.fsf@mina86.com>\r
+MIME-Version: 1.0\r
+Content-Type: multipart/mixed; boundary="=-=-="\r
+X-Gm-Message-State: ALoCoQlLV3rFEX4IG9wEvoVab+ezDzAM8zmuD17sqgf1ybc6TCmgyQFx8l1ChUNjRZeaVXQNmaS1v0n/3G+F109dmqrBUKdT2ejBkVH2TpXKvY1vBYySU+bW2mZeImXtGJVDJ3juu6Uq5D48AlAQ7SQdi6oFC+2j+WWA37XDrT1V/VW7z7cUhG4KBRF47sAZV7ucsT2Xe/uF\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Mon, 17 Sep 2012 14:13:51 -0000\r
+\r
+--=-=-=\r
+Content-Type: text/plain; charset=utf-8\r
+Content-Transfer-Encoding: quoted-printable\r
+\r
+> On Thu, 13 Sep 2012, Michal Nazarewicz <mina86@mina86.com> wrote:\r
+>> Have you consider doing the same in bison? I consider the code totally\r
+>> unreadable and unmaintainable.\r
+\r
+On Thu, Sep 13 2012, Jani Nikula wrote:\r
+> I do not think you could easily do everything that this parser does in\r
+> bison. But then I'm not an expert in bison, and I have zero ambition to\r
+> become one. So I'm biased, and I'm open about it.\r
+\r
+Bison can do a lot of weird stuff including modifying how lexer\r
+interpretes tokens even while parsing given grammar rule.\r
+\r
+> Even so, if you're suggesting doing this in bison would make this\r
+> totally readable and maintainable, I urge you to have a good look at\r
+> [1]. Note that it also does less in more lines of code. (And using it\r
+> as-is in notmuch has pretty much been turned down in the past.)\r
+>\r
+> Finally, I also suggest you actually read and review the code, pointing\r
+> out concrete issues in readability or maintainability that you\r
+> see. Especially since an earlier version has received comment "[I]t\r
+> looks very nice to me. It is pleasantly nice to read." [2]. What you're\r
+> doing is worthless bikeshedding otherwise.\r
+\r
+I'm sorry. I sometime tend to go into extremes with my statements, so\r
+yes, the =E2=80=9Ctotally unreadable=E2=80=9D was a over statement on my pa=\r
+rt.\r
+\r
+My point was however that parsing is a solved problem, and for\r
+non-trivial parsers one needs to ask herself whether it's worth trying\r
+to implement the logic, or maybe using a parser generator is just\r
+simpler.\r
+\r
+And in this particular case, my feeling is that bison is easier to read\r
+and modify.\r
+\r
+To add some merit to my statement, I attach a bison parser.\r
+\r
+It supports ranges as so:\r
+ <date> the specific moment with duration dependent\r
+ on specification. How duration is figured out\r
+ is described in the next paragraph.\r
+ <from>..<to> dates >=3D <from> and < <to>, so for instance\r
+ =E2=80=9Cyesterday..0=E2=80=9D days yields results from yesterday.\r
+ ..<to> dates < <to>\r
+ <from>.. dates >=3D <from>\r
+ <from>++<dur> a shorthand of =E2=80=9C<from>..<from> + <dur>=E2=80=9D.\r
+ This is useful for things like: =E2=80=9C2012q1++2\r
+ quarters=E2=80=9D which is equivalent to\r
+ =E2=80=9C2012/01/01..2012/07/01=E2=80=9D, ie. the first two\r
+ quarters of 2012.\r
+\r
+It supports specifications as:\r
+ '@' <num>\r
+ Raw timestamp. It's duration is one second.\r
+\r
+ <num> (seconds | minutes | hours | days | weeks | fortnights) [ago]\r
+ moves the date by given number of units in the future or\r
+ in the past (if =E2=80=9Cago=E2=80=9D is given). <num> can be preceded\r
+ by sign.\r
+\r
+ This specification's duration is whatever unit was used,\r
+ ie. one second, one minute, one hour, one day, one week\r
+ or one fortnight. So =E2=80=9C7 days ago=E2=80=9D and =E2=80=9C1 week ag=\r
+o=E2=80=9D\r
+ specify the same moment, but they hay different\r
+ durations.\r
+\r
+ <num> (months | quarters | years) [ago]\r
+ Like above, but calendar months are used which do not\r
+ always have the same length. If applying the offset\r
+ ends up with a day of the month out of range, the day\r
+ is capped to the last day of the month.\r
+\r
+ yesterday\r
+ Moves one day back. [*] Note that because of [*] this\r
+ is not quivalent to =E2=80=9C-1day=E2=80=9D.\r
+ YYYY/MM/DD\r
+ YYYY-MM-DD\r
+ MM-DD-YYYY\r
+ DD Month YYYY\r
+ Month DD YYYY\r
+ Sets date accordingly. [*] =E2=80=9CMonth=E2=80=9D is a human readable\r
+ month name.\r
+ Month [DD] [YYYY]\r
+ If either day or year is missing, given component of the\r
+ date is not changed. [*] Also, if day is missing, the\r
+ duration is set to one month rather than one day (but\r
+ see caveats described in [*]).\r
+ YYYY q Q\r
+ Sets date to the beginning of quarter Q, ie. =E2=80=9C2012q2=E2=80=9D is\r
+ roughly the same as =E2=80=9C2012/04/01=E2=80=9D. [*] Sets duration to\r
+ three moths but see caveats described in [*].\r
+\r
+ midnight | noon\r
+ Sets time to 0:00:00 and 12:00:00 respectively. Has\r
+ duration of 1 hour.\r
+ HH:MM:SS [am | pm]\r
+ HH:MM [am | pm]\r
+ HH (am | pm)\r
+ Sets time accordingly with the part that is not\r
+ specified set to zero. Duration depends on how many\r
+ components are missing, ie. =E2=80=9CHH (am|pm)=E2=80=9D has a duration of\r
+ on hour, =E2=80=9CHH:MM=E2=80=9D has a duration of one minute and\r
+ =E2=80=9CHH:MM:SS=E2=80=9D has a duration of one second.\r
+\r
+[*] Formats specifying the date will zero the time to midnight unless\r
+ the time has already been specified (ie. =E2=80=9Cyesterday=E2=80=9D is=\r
+ roughly the\r
+ same as =E2=80=9Cyesterday midnight=E2=80=9D, but =E2=80=9Cnoon yesterd=\r
+ay=E2=80=9D still keeps time\r
+ as noon.\r
+\r
+ Also, if the time has not been specified, those formats will set\r
+ duration to one day (with two exception), so =E2=80=9Cyesterday=E2=80=\r
+=9D has\r
+ a duration of one day, but =E2=80=9Cyesterday midnight=E2=80=9D, even t=\r
+hough it\r
+ specifies the same moment's beginning, has a duration of one hour.\r
+\r
+Purposly, I have not added support for MM/DD/YYYY or DD/MM/YYYY as well\r
+as two-digit years. I feel this would only add confusion.\r
+\r
+---\r
+ .gitignore | 3 +\r
+ Makefile | 17 ++\r
+ date-parser-grammar.y | 173 ++++++++++++++++++\r
+ date-parser.c | 476 +++++++++++++++++++++++++++++++++++++++++++++=\r
+++++\r
+ date-parser.h | 59 ++++++\r
+ test.c | 44 +++++\r
+ 6 files changed, 772 insertions(+), 0 deletions(-)\r
+ create mode 100644 .gitignore\r
+ create mode 100644 Makefile\r
+ create mode 100644 date-parser-grammar.y\r
+ create mode 100644 date-parser.c\r
+ create mode 100644 date-parser.h\r
+ create mode 100644 test.c\r
+\r
+diff --git a/.gitignore b/.gitignore\r
+new file mode 100644\r
+index 0000000..b73c782\r
+--- /dev/null\r
++++ b/.gitignore\r
+@@ -0,0 +1,3 @@\r
++test\r
++*.o\r
++date-parser-grammar.tab.*\r
+diff --git a/Makefile b/Makefile\r
+new file mode 100644\r
+index 0000000..8d95f71\r
+--- /dev/null\r
++++ b/Makefile\r
+@@ -0,0 +1,17 @@\r
++CFLAGS +=3D -std=3Dc99 -Wextra -Werror -pedantic\r
++\r
++test: test.o date-parser.o date-parser-grammar.tab.o\r
++test.o: test.c date-parser.h\r
++date-parser.o: date-parser.c date-parser.h\r
++date-parser.o: date-parser-grammar.tab.h\r
++\r
++date-parser-grammar.tab.c: date-parser-grammar.y\r
++ bison $<\r
++\r
++date-parser-grammar.tab.h: date-parser-grammar.tab.c\r
++date-parser-grammar.tab.o: date-parser-grammar.tab.c date-parser.h\r
++date-parser-grammar.tab.o: CPPFLAGS +=3D -Wno-unreachable-code\r
++\r
++clean:\r
++ rm -f date-parser-grammar.output date-parser-grammar.tab.* \\r
++ *.o test\r
+diff --git a/date-parser-grammar.y b/date-parser-grammar.y\r
+new file mode 100644\r
+index 0000000..38ddfca\r
+--- /dev/null\r
++++ b/date-parser-grammar.y\r
+@@ -0,0 +1,173 @@\r
++/* Date parser bison grammar file\r
++ * Copyright (c) 2012 Google Inc.\r
++ * Written by Michal Nazarewicz <mina86@mina86.com>\r
++ *\r
++ * This program is free software: you can redistribute it and/or modify\r
++ * it under the terms of the GNU General Public License as published by\r
++ * the Free Software Foundation, either version 3 of the License, or\r
++ * (at your option) any later version.\r
++ *\r
++ * This program is distributed in the hope that it will be useful,\r
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
++ * GNU General Public License for more details.\r
++ *\r
++ * You should have received a copy of the GNU General Public License\r
++ * along with this program. If not, see http://www.gnu.org/licenses/ . */\r
++\r
++%code requires {\r
++\r
++#ifndef YYSTYPE\r
++# define YYSTYPE long\r
++#endif\r
++#ifndef YYLTYPE\r
++# define YYLTYPE struct yylocation\r
++#endif\r
++\r
++#ifdef YYLLOC_DEFAULT\r
++# undef YYLLOC_DEFAULT\r
++#endif\r
++#define YYLLOC_DEFAULT(Cur, Rhs, N) do { \\r
++ if (N) { \\r
++ (Cur).start =3D YYRHSLOC(Rhs, 1).start; \\r
++ (Cur).end =3D YYRHSLOC(Rhs, N).end; \\r
++ } else { \\r
++ (Cur) =3D YYRHSLOC(Rhs, 0); \\r
++ } \\r
++} while (0)\r
++}\r
++\r
++%code{\r
++#include "date-parser-grammar.tab.h"\r
++#include "date-parser.h"\r
++\r
++#define ASSERT(cond, loc, message) do { \\r
++ if (!(cond)) { \\r
++ parse_date_print_error(&loc, message); \\r
++ YYERROR; \\r
++ } \\r
++} while (0)\r
++}\r
++\r
++%locations\r
++%defines\r
++%error-verbose\r
++%define api.pure\r
++\r
++%parse-param {struct date *ret}\r
++%parse-param {const char **inputp}\r
++%lex-param {const char **inputp}\r
++\r
++%token T_NUMBER "<num>" /* Always positive. */\r
++%token T_NUMBER_4 "####" /* Four digit number. */\r
++%token T_AGO "ago"\r
++/* Also used for minutes, hours, days and weeks. */\r
++%token T_SECONDS "seconds"\r
++/* Also used for quarters and years */\r
++%token T_MONTHS "months"\r
++%token T_YESTERDAY "yesterday"\r
++%token T_AMPM "am/pm"\r
++%token T_HOUR "<hour>"\r
++%token T_MONTH "<month>"\r
++\r
++%expect 3 /* Two shift/reduce conflicts caused by year_maybe, and onde\r
++ * caused by day_maybe. */\r
++\r
++%%\r
++ /* For backwards compatibility, just a number and nothing else\r
++ * is treated as timestamp */\r
++input : number { date_set_from_stamp(ret, $1) }\r
++ | date\r
++ ;\r
++\r
++date : part\r
++ | date part\r
++ ;\r
++\r
++part : integer "seconds" ago_maybe {\r
++ ASSERT(date_add_seconds(ret, $1 * $3, $2), @$,\r
++ "offset ends up in date out of range")\r
++ }\r
++ | integer "months" ago_maybe {\r
++ ASSERT(date_add_months(ret, $1 * $3, $2), @$,\r
++ "offset ends up in date out of range")\r
++ }\r
++ | "yesterday" {\r
++ ASSERT(date_set_yesterday(ret), @$,\r
++ "offset ends up in date out of range")\r
++ }\r
++\r
++ | '@' number { date_set_from_stamp(ret, $2) }\r
++ | "<hour>" { date_set_time(ret, $1, -1, -1, -1) }\r
++\r
++ /* HH:MM, HH:MM:SS, HH:MM am/pm, HH:MM:SS am/pm */\r
++ | number ':' number seconds_maybe ampm_maybe {\r
++ ASSERT(date_set_time(ret, $1, $3, $4, $5), @$,\r
++ "invalid time")\r
++ }\r
++\r
++ | number "am/pm" { /* HH am/pm */\r
++ ASSERT(date_set_time(ret, $1, -1, -1, $2), @$, "invalid hour")\r
++ }\r
++\r
++ | "####" '/' "<num>" '/' "<num>" { /* YYYY/MM/DD */\r
++ ASSERT(date_set_date(ret, $1, $3, $5), @$, "invalid date")\r
++ }\r
++ | "####" '-' "<num>" '-' "<num>" { /* YYYY-MM-DD */\r
++ ASSERT(date_set_date(ret, $1, $3, $5), @$, "invalid date")\r
++ }\r
++ | "<num>" '-' "<num>" '-' "####" { /* DD-MM-YYYY */\r
++ ASSERT(date_set_date(ret, $5, $3, $1), @$, "invalid date")\r
++ }\r
++ /* No MM/DD/YYYY or DD/MM/YYYY because it's confusing. */\r
++\r
++ | "<num>" "<month>" year_maybe { /* 1 January 2012 */\r
++ ASSERT(date_set_date(ret, $3, $2, $1), @$, "invalid date")\r
++ }\r
++ | "<month>" day_maybe year_maybe { /* January 1 2012 */\r
++ ASSERT(date_set_date(ret, $3, $1, $2), @$, "invalid date")\r
++ }\r
++\r
++ | "####" 'q' "<num>" { /* Quarter, 2012q1 */\r
++ ASSERT(date_set_quarter(ret, $1, $3), @$, "invalid quarter");\r
++ }\r
++ ;\r
++\r
++number : "<num>" { $$ =3D $1 }\r
++ | "####" { $$ =3D $1 }\r
++ ;\r
++\r
++integer : number { $$ =3D $1 }\r
++ | '-' number { $$ =3D -$2 }\r
++ ;\r
++\r
++ago_maybe\r
++ : /* empty */ { $$ =3D 1 }\r
++ | "ago" { $$ =3D -1 }\r
++ ;\r
++\r
++seconds_maybe\r
++ : /* empty */ { $$ =3D -1 }\r
++ | ':' "<num>" { $$ =3D $2 }\r
++ ;\r
++\r
++ampm_maybe\r
++ : /* empty */ { $$ =3D -1 }\r
++ | "am/pm" { $$ =3D $$ }\r
++ /* For people who like writing "a.m." or "p.m." and since dot\r
++ * is ignored by the lexer (ie. it's treated just like white\r
++ * space), dot is lost. */\r
++ | 'a' 'm' { $$ =3D 0 }\r
++ | 'p' 'm' { $$ =3D 0 }\r
++ ;\r
++\r
++day_maybe\r
++ : /* empty */ { $$ =3D -1 }\r
++ | "<num>" { $$ =3D $1 }\r
++ ;\r
++\r
++year_maybe\r
++ : /* empty */ { $$ =3D -1 }\r
++ | "####" { $$ =3D $1 }\r
++ ;\r
++%%\r
+diff --git a/date-parser.c b/date-parser.c\r
+new file mode 100644\r
+index 0000000..c1701bd\r
+--- /dev/null\r
++++ b/date-parser.c\r
+@@ -0,0 +1,476 @@\r
++/* Date parser implementation file.\r
++ * Copyright (c) 2012 Google Inc.\r
++ * Written by Michal Nazarewicz <mina86@mina86.com>\r
++ *\r
++ * This program is free software: you can redistribute it and/or modify\r
++ * it under the terms of the GNU General Public License as published by\r
++ * the Free Software Foundation, either version 3 of the License, or\r
++ * (at your option) any later version.\r
++ *\r
++ * This program is distributed in the hope that it will be useful,\r
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
++ * GNU General Public License for more details.\r
++ *\r
++ * You should have received a copy of the GNU General Public License\r
++ * along with this program. If not, see http://www.gnu.org/licenses/ . */\r
++\r
++#define _POSIX_C_SOURCE 1\r
++\r
++#include "date-parser.h"\r
++#include "date-parser-grammar.tab.h"\r
++\r
++#include <ctype.h>\r
++#include <errno.h>\r
++#include <stdlib.h>\r
++#include <string.h>\r
++#include <strings.h>\r
++\r
++\r
++static bool is_valid_year(int year) {\r
++ /* TODO: Get the actual time_t range. */\r
++ return year >=3D 1970 && year < 2037;\r
++}\r
++\r
++\r
++/***************************** Basic date helpers ************************=\r
+***/\r
++\r
++static int days_in_months[] =3D {\r
++ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31\r
++};\r
++\r
++static inline int is_leap(int year) {\r
++ return year % 4 =3D=3D 0 && (year % 100 || year % 400 =3D=3D 0);\r
++}\r
++\r
++static inline int days_in_month(int year, int month) {\r
++ return days_in_months[month] + (month =3D=3D 1 ? is_leap(year) : 0);\r
++}\r
++\r
++static inline int days_in_year(int year) {\r
++ return 365 + is_leap(year);\r
++}\r
++\r
++static inline int min(int a, int b) {\r
++ return a < b ? a : b;\r
++}\r
++\r
++\r
++/****************************** Date manipulation ************************=\r
+***/\r
++\r
++struct date {\r
++ struct tm tm;\r
++ int dur_sec, dur_mon, has_time;\r
++};\r
++\r
++\r
++void date_set_from_stamp(struct date *ret, long stamp) {\r
++ time_t t =3D stamp;\r
++ localtime_r(&t, &ret->tm);\r
++ ret->dur_sec =3D 1;\r
++ ret->dur_mon =3D 0;\r
++ ret->has_time =3D 1;\r
++}\r
++\r
++static void date_set_to_now(struct date *ret) {\r
++ time_t t =3D time(NULL);\r
++ localtime_r(&t, &ret->tm);\r
++ ret->dur_sec =3D 1;\r
++ ret->dur_mon =3D 0;\r
++ ret->has_time =3D 0;\r
++}\r
++\r
++static void date_zero_time(struct date *ret, int dur_sec, int dur_mon) {\r
++ if (!ret->has_time) {\r
++ ret->tm.tm_hour =3D ret->tm.tm_min =3D ret->tm.tm_sec =3D 0;\r
++ ret->dur_sec =3D dur_sec;\r
++ ret->dur_mon =3D dur_mon;\r
++ }\r
++}\r
++\r
++bool date_add_seconds(struct date *ret, long num, long unit) {\r
++ time_t t =3D mktime(&ret->tm) + num * unit;\r
++ localtime_r(&t, &ret->tm);\r
++ ret->dur_sec =3D unit;\r
++ ret->dur_mon =3D 0;\r
++ return true; /* TODO add validation */\r
++}\r
++\r
++bool date_set_yesterday(struct date *ret) {\r
++ if (ret->tm.tm_mday !=3D 1) {\r
++ --ret->tm.tm_mday;\r
++ } else if (ret->tm.tm_mon) {\r
++ --ret->tm.tm_mon;\r
++ ret->tm.tm_mday =3D days_in_month(ret->tm.tm_year + 1900,\r
++ ret->tm.tm_mon);\r
++ } else if (is_valid_year(1900 + ret->tm.tm_year - 1)) {\r
++ --ret->tm.tm_year;\r
++ ret->tm.tm_mon =3D 11;\r
++ ret->tm.tm_mday =3D 31;\r
++ } else {\r
++ return false;\r
++ }\r
++ date_zero_time(ret, 24 * 3600, 0);\r
++ ret->tm.tm_isdst =3D -1;\r
++ return true;\r
++}\r
++\r
++bool date_add_months(struct date *ret, long num, long unit) {\r
++ long y;\r
++\r
++ y =3D ret->tm.tm_year + 1900;\r
++ num =3D num * unit + ret->tm.tm_mon;\r
++ if (num < 0) {\r
++ y -=3D -num / 12;\r
++ num =3D 11 - (-num % 12);\r
++ } else {\r
++ y +=3D num / 12;\r
++ num %=3D 12;\r
++ }\r
++ if (!is_valid_year(y)) {\r
++ return false;\r
++ }\r
++\r
++ ret->tm.tm_year =3D y - 1900;\r
++ ret->tm.tm_mon =3D num;\r
++ ret->tm.tm_mday =3D min(ret->tm.tm_mday,\r
++ days_in_month(ret->tm.tm_year + 1900,\r
++ ret->tm.tm_mon));\r
++ if (!ret->has_time) {\r
++ ret->dur_sec =3D 0;\r
++ ret->dur_mon =3D unit;\r
++ }\r
++ ret->tm.tm_isdst =3D -1;\r
++ return true;\r
++}\r
++\r
++bool date_set_time(struct date *ret, long h, long m, long s, int ampm) {\r
++ if (m > 60 || s > 60 || h > 23) {\r
++ return false;\r
++ }\r
++\r
++ if (ampm !=3D -1) {\r
++ if (!h || h > 12) {\r
++ return false;\r
++ }\r
++ if (h !=3D 12) {\r
++ h +=3D ampm * 12;\r
++ } else if (ampm) { /* 12 pm */\r
++ h =3D 12;\r
++ } else {\r
++ /* 12 am is 0 the next day, so adjust date */\r
++ date_add_seconds(ret, 1, 24 * 3600);\r
++ h =3D 0;\r
++ }\r
++ }\r
++\r
++ if (m =3D=3D -1) {\r
++ ret->dur_sec =3D 3600;\r
++ m =3D s =3D 0;\r
++ } else if (s =3D=3D -1) {\r
++ ret->dur_sec =3D 60;\r
++ s =3D 0;\r
++ } else {\r
++ ret->dur_sec =3D 1;\r
++ }\r
++ ret->dur_mon =3D 0;\r
++\r
++ ret->tm.tm_hour =3D h;\r
++ ret->tm.tm_min =3D m;\r
++ ret->tm.tm_sec =3D s;\r
++ ret->tm.tm_isdst =3D -1;\r
++\r
++ ret->has_time =3D 1;\r
++ return true;\r
++}\r
++\r
++bool date_set_date(struct date *ret, long y, long m, long d) {\r
++ if (y =3D=3D -1) {\r
++ y =3D ret->tm.tm_year + 1900;\r
++ } else if (!is_valid_year(y)) {\r
++ return false;\r
++ }\r
++ if (m < 1 || m > 12 ||\r
++ (d !=3D -1 && (d < 1 || d > days_in_month(y, m)))) {\r
++ return false;\r
++ }\r
++ ret->tm.tm_year =3D y - 1900;\r
++ ret->tm.tm_mon =3D m - 1;\r
++ ret->tm.tm_mday =3D d =3D=3D -1 ? 1 : d;\r
++ if (d =3D=3D -1) {\r
++ date_zero_time(ret, 0, 1);\r
++ } else {\r
++ date_zero_time(ret, 24 * 3600, 0);\r
++ }\r
++ ret->tm.tm_isdst =3D -1;\r
++ return true;\r
++}\r
++\r
++bool date_set_quarter(struct date *ret, long y, long q) {\r
++ if (!is_valid_year(y) || q < 1 || q > 4) {\r
++ return false;\r
++ }\r
++ ret->tm.tm_year =3D y - 1900;\r
++ ret->tm.tm_mon =3D (q - 1) * 3;\r
++ ret->tm.tm_mday =3D 1;\r
++ date_zero_time(ret, 0, 3);\r
++ ret->tm.tm_isdst =3D -1;\r
++ return true;\r
++}\r
++\r
++\r
++#define TOKEN(str, token, num) { str, sizeof(str) - 1, token, num }\r
++#define ABBR(len) { NULL, len, 0, 0 }\r
++\r
++static struct token {\r
++ const char *str;\r
++ size_t len;\r
++ int token;\r
++ long num;\r
++} tokens_array[] =3D {\r
++ TOKEN("ago", T_AGO, 0),\r
++\r
++ TOKEN("am", T_AMPM, 0),\r
++ TOKEN("pm", T_AMPM, 1),\r
++\r
++ TOKEN("seconds", T_SECONDS, 1), ABBR(3), ABBR(6),\r
++ TOKEN("minutes", T_SECONDS, 60), ABBR(3), ABBR(6),\r
++ TOKEN("hours", T_SECONDS, 3600), ABBR(4), ABBR(1),\r
++ TOKEN("days", T_SECONDS, 24 * 3600), ABBR(3), ABBR(1),\r
++ TOKEN("weeks", T_SECONDS, 7 * 24 * 3600), ABBR(4),\r
++ TOKEN("fortnights", T_SECONDS, 14 * 24 * 3600), ABBR(9),\r
++\r
++ TOKEN("months", T_MONTHS, 1), ABBR(5),\r
++ TOKEN("quarters", T_MONTHS, 3), ABBR(7),\r
++ TOKEN("years", T_MONTHS, 12), ABBR(4),\r
++\r
++ TOKEN("yesterday", T_YESTERDAY, 0),\r
++\r
++ TOKEN("midnight", T_HOUR, 0),\r
++ TOKEN("noon", T_HOUR, 12),\r
++\r
++ TOKEN("january", T_MONTH, 1), ABBR(3),\r
++ TOKEN("february", T_MONTH, 2), ABBR(3),\r
++ TOKEN("march", T_MONTH, 3), ABBR(3),\r
++ TOKEN("april", T_MONTH, 4), ABBR(3),\r
++ TOKEN("may", T_MONTH, 5),\r
++ TOKEN("june", T_MONTH, 6), ABBR(3),\r
++ TOKEN("july", T_MONTH, 7), ABBR(3),\r
++ TOKEN("august", T_MONTH, 8), ABBR(3),\r
++ TOKEN("september", T_MONTH, 9), ABBR(4), ABBR(3),\r
++ TOKEN("october", T_MONTH, 10), ABBR(3),\r
++ TOKEN("november", T_MONTH, 11), ABBR(3),\r
++ TOKEN("december", T_MONTH, 12), ABBR(3),\r
++\r
++ { NULL, 0, 0, 0 },\r
++};\r
++\r
++#undef TOKEN\r
++#undef ABBR\r
++\r
++\r
++static struct token locale_tokens_array[2*12 + 1];\r
++static bool locale_tokens_populated =3D false;\r
++\r
++static void populate_locale_tokens(void) {\r
++ static const char *mon_formats[] =3D { "%b", "%B" };\r
++ static char locale_buffer[1024];\r
++\r
++ char *buf =3D locale_buffer, *end =3D buf + sizeof(locale_buffer);\r
++ struct token *out =3D locale_tokens_array;\r
++ struct tm tm;\r
++ unsigned i;\r
++\r
++ tm.tm_sec =3D 0;\r
++ tm.tm_min =3D 0;\r
++ tm.tm_hour =3D 0;\r
++ tm.tm_mday =3D 10;\r
++ tm.tm_year =3D 100;\r
++ tm.tm_isdst =3D 0;\r
++\r
++ for (tm.tm_mon =3D 0; tm.tm_mon < 12; ++tm.tm_mon) {\r
++ for (i =3D 0; i < 2; ++i) {\r
++ out->len =3D strftime(buf, end - buf,\r
++ mon_formats[i], &tm);\r
++ if (!out->len) {\r
++ continue;\r
++ }\r
++ out->str =3D buf;\r
++ buf +=3D out->len;\r
++ out->token =3D T_MONTH;\r
++ out->num =3D tm.tm_mon + 1;\r
++ ++out;\r
++ }\r
++ }\r
++\r
++ out->len =3D 0;\r
++}\r
++\r
++static const struct token *find_token(const struct token *tk,\r
++ const char *str, size_t len) {\r
++ const struct token *ret;\r
++\r
++ for (; tk->len; ++tk) {\r
++ if (tk->str) {\r
++ ret =3D tk;\r
++ }\r
++ if (tk->len =3D=3D len && !strncasecmp(str, ret->str, len)) {\r
++ return ret;\r
++ }\r
++ }\r
++\r
++ return NULL;\r
++}\r
++\r
++\r
++/* Treat '_' and '.' as white space so that people don't have to quote\r
++ * the argument when specifying it on command line. */\r
++#define SKIP_WHITE_SPACE(ch) do { \\r
++ while (isspace(*ch) || *ch =3D=3D '_' || *ch =3D=3D '.') { \\r
++ ++ch; \\r
++ } \\r
++} while (0)\r
++\r
++\r
++int yylex(YYSTYPE *valp, struct yylocation *loc, const char **inputp) {\r
++ const char *ch =3D *inputp, *str;\r
++ const struct token *tk;\r
++\r
++ SKIP_WHITE_SPACE(ch);\r
++\r
++ /* End of data */\r
++ if (*ch =3D=3D 0) {\r
++ return EOF;\r
++ }\r
++\r
++ loc->start =3D ch;\r
++\r
++ /* Parse number */\r
++ if (isdigit(*ch)) {\r
++ errno =3D 0;\r
++ *valp =3D strtol(ch, (char**)&ch, 10);\r
++\r
++ loc->end =3D ch;\r
++ *inputp =3D ch;\r
++\r
++ if (errno) {\r
++ parse_date_print_error(loc, "number out of range");\r
++ return 256;\r
++ }\r
++ return ch - loc->start =3D=3D 4 ? T_NUMBER_4 : T_NUMBER;\r
++ }\r
++\r
++ if (!isalpha(*ch)) {\r
++ *inputp =3D ch + 1;\r
++ loc->end =3D ch + 1;\r
++ return *ch;\r
++ }\r
++\r
++ /* So it's a string token. */\r
++ str =3D ch;\r
++ while (isalpha(*ch)) {\r
++ ++ch;\r
++ }\r
++ loc->end =3D ch;\r
++ *inputp =3D ch;\r
++\r
++ tk =3D find_token(tokens_array, str, ch - str);\r
++ if (tk) {\r
++ *valp =3D tk->num;\r
++ return tk->token;\r
++ }\r
++\r
++ /* Let's try with locale strings. */\r
++ if (!locale_tokens_populated) {\r
++ populate_locale_tokens();\r
++ locale_tokens_populated =3D true;\r
++ }\r
++ tk =3D find_token(locale_tokens_array, str, ch - str);\r
++ if (tk) {\r
++ *valp =3D tk->num;\r
++ return tk->token;\r
++ }\r
++\r
++ /* If it's just one letter, return it converted to lower case */\r
++ if (ch - str =3D=3D 1) {\r
++ return tolower(*str);\r
++ }\r
++\r
++ parse_date_print_error(loc, "unrecognised token");\r
++ return 256;\r
++}\r
++\r
++\r
++/**************************** Parsing interface **************************=\r
+***/\r
++\r
++static bool parse_date(struct date *ret, const char *from, char *to) {\r
++ char tmp;\r
++ int res;\r
++ if (to) {\r
++ tmp =3D *to;\r
++ *to =3D '\0';\r
++ }\r
++ res =3D yyparse(ret, &from);\r
++ if (to) {\r
++ *to =3D tmp;\r
++ }\r
++ return res =3D=3D 0;\r
++}\r
++\r
++bool parse_range(char *arg, time_t *from, time_t *to) {\r
++ bool left =3D false, right =3D false;\r
++ struct date a, b;\r
++ char *dd, *pp;\r
++\r
++ SKIP_WHITE_SPACE(arg);\r
++ if (!*arg) {\r
++ fprintf(stderr, "empty range argument\n");\r
++ return false;\r
++ }\r
++\r
++ dd =3D strstr(arg, "..");\r
++ pp =3D strstr(arg, "++");\r
++ if ((dd && pp) ||\r
++ (dd && strstr(dd + 2, "..")) ||\r
++ (pp && strstr(pp + 2, "++"))) {\r
++ fprintf(stderr,\r
++ "%s: at most one of '..' or '++' can be used\n", arg);\r
++ return false;\r
++ }\r
++\r
++ if (dd || pp) {\r
++ char *ch =3D dd ? dd : pp;\r
++ left =3D ch !=3D arg;\r
++ SKIP_WHITE_SPACE(ch);\r
++ right =3D *ch;\r
++ }\r
++ if (pp && (!right || !left)) {\r
++ fprintf(stderr,\r
++ "%s: '++' requires expression on both sides\n", arg);\r
++ return false;\r
++ }\r
++\r
++ if (left || !right) { /* date:<date>.. or date:<date> */\r
++ date_set_to_now(&a);\r
++ if (!parse_date(&a, arg, dd ? dd : pp)) {\r
++ return false;\r
++ }\r
++ }\r
++ if (right) { /* date:<date>..<date> or date:..<date> */\r
++ if (pp) {\r
++ b =3D a;\r
++ } else {\r
++ date_set_to_now(&b);\r
++ }\r
++ if (!parse_date(&b, (dd ? dd : pp) + 2, NULL)) {\r
++ return false;\r
++ }\r
++ } else if (!left) { /* date:date */\r
++ left =3D right =3D true; /* convert to date:<date>..<date> */\r
++ b =3D a;\r
++ if ((b.dur_sec && !date_add_seconds(&b, b.dur_sec, 1)) ||\r
++ (b.dur_mon && !date_add_months(&b, b.dur_mon, 1))) {\r
++ right =3D false; /* convert to date:<date>.. */\r
++ }\r
++ }\r
++\r
++ *from =3D left ? mktime(&a.tm) : 0;\r
++ *to =3D right ? mktime(&b.tm) : (time_t)((unsigned long)~(time_t)0 >> 1);\r
++ return true;\r
++}\r
+diff --git a/date-parser.h b/date-parser.h\r
+new file mode 100644\r
+index 0000000..fb0f19b\r
+--- /dev/null\r
++++ b/date-parser.h\r
+@@ -0,0 +1,59 @@\r
++/* Date parser header file.\r
++ * Copyright (c) 2012 Google Inc.\r
++ * Written by Michal Nazarewicz <mina86@mina86.com>\r
++ *\r
++ * This program is free software: you can redistribute it and/or modify\r
++ * it under the terms of the GNU General Public License as published by\r
++ * the Free Software Foundation, either version 3 of the License, or\r
++ * (at your option) any later version.\r
++ *\r
++ * This program is distributed in the hope that it will be useful,\r
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
++ * GNU General Public License for more details.\r
++ *\r
++ * You should have received a copy of the GNU General Public License\r
++ * along with this program. If not, see http://www.gnu.org/licenses/ . */\r
++\r
++#ifndef H_DATE_PARSER_H\r
++#define H_DATE_PARSER_H\r
++\r
++#include <stdbool.h>\r
++#include <stdio.h>\r
++#include <time.h>\r
++\r
++bool parse_range(char *arg, time_t *from, time_t *to);\r
++\r
++/* For parser */\r
++struct date;\r
++\r
++struct yylocation {\r
++ const char *start, *end;\r
++};\r
++\r
++static inline void parse_date_print_error(struct yylocation *loc,\r
++ const char *message) {\r
++ fprintf(stderr, "%.*s: %s\n",\r
++ (int)(loc->end - loc->start), loc->start, message);\r
++}\r
++\r
++static inline int yyerror(struct yylocation *loc, struct date *ret,\r
++ const char **inputp, const char *message) {\r
++ ret =3D ret; /* make compiler happy */\r
++ inputp =3D inputp;\r
++ parse_date_print_error(loc, message);\r
++ return 0;\r
++}\r
++\r
++int yylex(long *valp, struct yylocation *loc, const char **inputp);\r
++int yyparse(struct date *ret, const char **inputp);\r
++\r
++void date_set_from_stamp(struct date *ret, long stamp);\r
++bool date_add_seconds(struct date *ret, long num, long unit);\r
++bool date_set_yesterday(struct date *ret);\r
++bool date_add_months(struct date *ret, long num, long unit);\r
++bool date_set_time(struct date *ret, long h, long m, long s, int ampm);\r
++bool date_set_date(struct date *ret, long y, long m, long d);\r
++bool date_set_quarter(struct date *ret, long y, long q);\r
++\r
++#endif\r
+diff --git a/test.c b/test.c\r
+new file mode 100644\r
+index 0000000..c4e2d9c\r
+--- /dev/null\r
++++ b/test.c\r
+@@ -0,0 +1,44 @@\r
++/* Date parser testing application.\r
++ * Copyright (c) 2012 Google Inc.\r
++ * Written by Michal Nazarewicz <mina86@mina86.com>\r
++ *\r
++ * This program is free software: you can redistribute it and/or modify\r
++ * it under the terms of the GNU General Public License as published by\r
++ * the Free Software Foundation, either version 3 of the License, or\r
++ * (at your option) any later version.\r
++ *\r
++ * This program is distributed in the hope that it will be useful,\r
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
++ * GNU General Public License for more details.\r
++ *\r
++ * You should have received a copy of the GNU General Public License\r
++ * along with this program. If not, see http://www.gnu.org/licenses/ . */\r
++\r
++#define _POSIX_C_SOURCE 1\r
++\r
++#include <locale.h>\r
++#include <stdio.h>\r
++#include <time.h>\r
++\r
++#include "date-parser.h"\r
++\r
++int main(void) {\r
++ char buf[1024], *ch;\r
++ time_t from, to;\r
++ struct tm tm;\r
++\r
++ setlocale(LC_ALL, "");\r
++\r
++ while (fgets(buf, sizeof buf, stdin)) {\r
++ if (parse_range(buf, &from, &to)) {\r
++ localtime_r(&from, &tm);\r
++ ch =3D buf + strftime(buf, sizeof buf / 2,\r
++ "[%Y/%m/%d %H:%M:%S %Z, ", &tm);\r
++ localtime_r(&to, &tm);\r
++ strftime(ch, sizeof buf / 2,\r
++ "%Y/%m/%d %H:%M:%S %Z)\n", &tm);\r
++ fputs(buf, stdout);\r
++ }\r
++ }\r
++}\r
+--=20\r
+1.7.7.3\r
+\r
+--=20\r
+Best regards, _ _\r
+.o. | Liege of Serenely Enlightened Majesty of o' \,=3D./ `o\r
+..o | Computer Science, Micha=C5=82 =E2=80=9Cmina86=E2=80=9D Nazarewicz =\r
+ (o o)\r
+ooo +----<email/xmpp: mpn@google.com>--------------ooO--(_)--Ooo--\r
+--=-=-=\r
+Content-Type: multipart/signed; boundary="==-=-=";\r
+ micalg=pgp-sha1; protocol="application/pgp-signature"\r
+\r
+--==-=-=\r
+Content-Type: text/plain\r
+\r
+\r
+--==-=-=\r
+Content-Type: application/pgp-signature\r
+\r
+-----BEGIN PGP SIGNATURE-----\r
+Version: GnuPG v1.4.10 (GNU/Linux)\r
+\r
+iQIcBAEBAgAGBQJQVzAPAAoJECBgQBJQdR/0e8UP/jA6s6ANgAHVEcfvd4nvCpaZ\r
+DEBV1hpm6HGJDQ/P1NPRafn6k4ZdaZokCK1qWMjgmYNOQ6jmfNLhieGuEXXaTsQs\r
+NRmroRQfWpDAIeQgsF/9dIAZgFoCigLKt/ge0p4e/rHBePmYOfxr3T9RBuGw9drv\r
+/zZ+fpv55P6iZP+xzl8TxeeVL9CDbzkuytnH3Kg1nFKgEFHlRoZxjeq/iELKXz/n\r
+reMF3cxoAZkYXwW5WntA/mWUqUFptWgBYZSi6bPhg6KEbJdr2lu6L3kxrYbuSUn9\r
+MBRIqG60OWVwguoCUbHE71mv39tnINb7uHTZumvdQkBlr2deeXPzho1xCwqMQH17\r
+sZ6IkSe2hhfaCxgBWhddzvHHr7TPIuAVZGHWucJuWOHCvBasn21ARtcOn/q0dhVT\r
+XGTvAw072lbo2l/ceaEQTGDrj1IV8Xo/1OeQU4y6n56hrVDknb4zwkdlk1BIcNu/\r
+Upi2vgewDETFl+NuLE7rr2/4UNcXAgBQIZnwlL5+vo/EV2/eTsTY0TemYxq/ppZC\r
+/YtV1x35sc1pN5LswSGU6tDeFp0+TNXaPXEW48IPrE5OhmaCrQT84DK62+UJDJ8r\r
+Ga4MmQh6GFQ3EemKoDHim59gp2hC3HOrHiQ9MWlf2GG0zYVOKto02qjPCjoeRjzc\r
+ZTefvm76BBIEpTfwa1fx\r
+=STYS\r
+-----END PGP SIGNATURE-----\r
+--==-=-=--\r
+\r
+--=-=-=--\r