#!/usr/bin/awk
#
# Takes a compact issues list in plain text and expands it to an HTML
# document. The issues list is line-based. Lines that start neither
# with "Issue" nor with a keyword and a colon are ignored, unless they
# are continuation lines (see below). The lines are grouped into issues: each
# occurrence of "Issue:" starts a new issue. The following keywords
# are recognized:
#
# Draft
# Must only occur once. The draft that these issues apply to. The
# value must be a URL that ends with --
# and an optional slash.
#
# Audience
# Must occur at most once. Set to "WG" (the default) for an HTML
# output with the "Edit" fields included, or to "Director" for an
# HTML output without those fields.
#
# Issue
# The issue number. The colon is optional after "Issue". Typically a
# number, but may be anything. Must be unique.
#
# Summary
# A short summary of the issue. May occur multiple times per
# issue. Each occurrence adds a paragraph to the summary.
#
# From
# The person who raised the issue. May occur multiple times per
# issue.
#
# Comment
# Typically a URL pointing to (a part of) the comment. May occur
# multiple times per issue. (Usually a pointer to a message on
# www-style.)
#
# Response
# Typically a URL pointing to an answer that the WG sent to the
# commenter. (Usually a pointer to a message on www-style.) May
# occur multiple times per issue. Comment and Response lines should
# occur in date order: for each issue, older comments and responses
# should be listed before newer ones.
#
# Closed
# The WG's resolution. Can be "Accepted," "Rejected," "OutOfScope,"
# "Retracted" or "Invalid." May occur only once per issue.
#
# Verified
# URL pointing to a message in which the commenter accepts the WG's
# resolution. (Typically omitted for issues that are "Accepted.")
# Should only occur multiple times if there are multiple From lines.
#
# Objection
# URL pointing to a message in which the commenter rejects the WG's
# resolution. Should only occur multiple times if there are
# multiple From lines.
#
# Edit
# Any text destined at the working group: edits to do or done,
# actions for people or for the WG, other comments...
#
# Resolution:
# Like Edit, only destined at the working group, but used to
# summarize a resolution.
#
# "Summary:", "Comment:", "Response:", "From:" and "Edit:" may have
# continuation lines (in the case of Comment and Response only if the
# first line contains text and not a URL), which are lines that start
# with white space.
#
# Author: Bert Bos
# Created: 13 March 2012
# Copyright: © 2012 World Wide Web Consortium
# See http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
BEGIN {nerrors = 0; n = 0; prev = ""; IGNORECASE = 1}
# Lines that start with a field name:
/^draft[ \t]*:/ {
draft = val($0);
prev = "";
next;
}
/^audience[ \t]*:[ \t]*wg\>/ {
next;
}
/^audience[ \t]*:[ \t]*director\>/ {
audience = "Director";
next;
}
/^audience[ \t]*:/ {
err("Audience must be \"WG\" (default) or \"Director\".");
next;
}
/^issue\>/ {
h = val($0);
if (h in seqno) err("Duplicate issue number: " h);
id[++n] = h;
seqno[h] = n;
prev = "";
next;
}
n && /^summary[ \t]*:/ {
summary[n] = summary[n] "" val($0);
prev = "summary";
next;
}
n && /^comment[ \t]*:[ \t]*http:/ {
link[n] = link[n] "
comment\n";
prev = "";
next;
}
n && /^comment[ \t]*:/ {
link[n] = link[n] "" val($0) "\n";
prev = "comment";
next;
}
n && /^response[ \t]*:[ \t]*http:/ {
link[n] = link[n] "reply\n";
prev = "";
next;
}
n && /^response[ \t]*:/ {
link[n] = link[n] "" val($0) "\n";
prev = "response";
next;
}
n && /^from[ \t]*:/ {
from[n] = (from[n] ? from[n] "
" : "") val($0);
prev = "from";
next;
}
n && /^closed[ \t]*:[ \t]* accepted\>/ {
status[n] = "Accepted";
prev = "";
next;
}
n && /^closed[ \t]*:[ \t]* outofscope\>/ {
status[n] = "Out of scope";
prev = "";
next;
}
n && /^closed[ \t]*:[ \t]* invalid\>/ {
status[n] = "Invalid";
prev = "";
next;
}
n && /^closed[ \t]*:[ \t]* rejected\>/ {
status[n] = "Rejected";
prev = "";
next;
}
n && /^closed[ \t]*:[ \t]* retracted\>/ {
status[n] = "Retracted";
prev = "";
next;
}
n && /^closed[ \t]*:/ {
err("Unrecognized resolution \"" val($0) "\".");
prev = "";
next;
}
n && /^verified[ \t]*:[ \t]*http:/ {
verif[n] = verif[n] "verified ";
prev = "";
next;
}
n && /^verified[ \t]*:/ {
verif[n] = verif[n] "verified ";
prev = "";
next;
}
n && /^objection[ \t]*:/ {
obj[n] = obj[n] "objection ";
prev = "";
next;
}
n && /^edit[ \t]*:/ {
edit[n] = edit[n] "" val($0);
prev = "edit";
next;
}
n && /^resolution[ \t]*:/ {
edit[n] = edit[n] "
Resolution: " val($0);
prev = "edit";
next;
}
# Continuation lines start with white space:
/^[ \t]+[^ \t]/ && prev == "summary" {
summary[n] = summary[n] val2($0);
next;
}
/^[ \t]+[^ \t]/ && prev == "comment" {
link[n] = link[n] val2($0);
next;
}
/^[ \t]+[^ \t]/ && prev == "response" {
link[n] = link[n] val2($0);
next;
}
/^[ \t]+[^ \t]/ && prev == "from" {
from[n] = from[n] val2($0);
next;
}
/^[ \t]+[^ \t]/ && prev == "edit" {
edit[n] = edit[n] val2($0);
next;
}
# Any other line is ignored, any other field name is an error:
{prev = ""}
n && /^[a-z]+[ \t]*:/ {err("Unrecognized keyword \"" $1 "\"."); next}
/^[a-z]+[ \t]*:/ {err("Incorrect keyword \"" $1 "\" before first issue."); next}
END {generate(); exit nerrors}
# arraylength -- return length of an array
function arraylength(x, i, n)
{
n = 0;
for (i in x) n++;
return n;
}
# generate -- generate the HTML file with all the issues
function generate( command, title, date, class, nobjections, i)
{
if (draft) {
command = "hxnormalize -l 10000 -x " draft " | hxselect -c -s '\n' title";
command | getline title;
date = gensub("^.*-([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])/?$", \
"\\1-\\2-\\3", 1, draft);
}
if (!title) title = draft;
if (!title) title = "[unknown]";
if (!date) date = "[unknown]";
print "";
print "";
print "
Disposition of Comments for “" title "” of " date "";
print "\n";
print "\n";
print "Disposition of comments
\n";
print "";
print "- Title
- " title;
print "
- Date
- " date;
print "
- URL
- " draft "";
print "
\n";
i = arraylength(obj);
if (i == 0)
print "There are no objections.\n";
else if (i == 1)
print "
There is 1 objection.\n";
else
print "
There are " i " objections.\n";
print "
";
print "";
print "";
print "| #"
print " | Author";
print " | Summary and discussion";
if (audience != "Director") print " | Actions for WG";
print " | Result";
print " |
";
for (i = 1; i <= n; i++) {
printf "\n";
else if (verif[i] || status[i] ~ "Accepted|Retracted") print "ok>";
else if (status[i]) print "unverified>";
else print "incomplete>";
print "| " id[i] "";
print " | " from[i];
print " | " linkify(summary[i]);
if (link[i]) printf "\n%s \n", link[i];
if (audience != "Director") printf " | %s\n", linkify(edit[i]);
if (status[i]) printf " | %s", status[i];
else printf " | [OPEN]";
if (obj[i]) printf " but %s", obj[i];
else if (verif[i]) printf " and %s", verif[i];
else if (status[i] && status[i] !~ "Accepted|Retracted") printf " but unverified";
printf "\n";
}
print " |
\n";
print "Legend:\n";
print "
";
print "";
print "| Status | Meaning";
print " |
";
print "\n| Retracted";
print " | Commenter has withdrawn the comment.";
print " |
\n| Accepted";
print " | The WG accepted and applied the comment.";
print " |
\n| Out of scope and verified";
print " | Commenter accepts that the comment is out of scope.";
print " |
\n| Invalid and verified";
print " | Commenter accepts that the comment is invalid.";
print " |
\n| Rejected and verified";
print " | Commenter accepts that the WG did not apply the comment.";
print " |
\n| Out of scope but unverified";
print " | Comment out of scope, but commenter did not yet react."
print " |
\n| Invalid but unverified";
print " | Comment invalid, but commenter did not yet react.";
print " |
\n| Rejected but unverified";
print " | Comment rejected, but commenter did not yet react.";
print " |
\n| Out of scope with objection";
print " | Comment out of scope, but commenter disagrees.";
print " |
\n| Invalid with objection";
print " | Comment invalid, but commenter disagrees.";
print " |
\n| Rejected with objection";
print " | Comment rejected, but commenter objects.";
print " |
\n";
print "This file was generated from";
print "" FILENAME "";
print "on " strftime("%e %B %Y", systime(), 1) ".";
}
# linkify -- make words that look like URLs into links
function linkify(s)
{
s = gensub(/[a-z]+:[^ )<]+/, "&", "g", s);
# s = gensub(/(>[a-z]+:)[^ )<]*([^ )<][^ )<][^ )<][^ )<][^ )<][^ )<][^ )<][^ )<][^ )<]<)/, "\\1\\…\\2", "g", s);
# s = gensub(/(>[a-z]+:)[^ )<]*\/([^ /)<]+<)/, "\\1\\…\\2", "g", s);
s = gensub(/>[^ )<]*\/([^ /)<]+<)/, ">\\1", "g", s);
return s;
}
# esc -- escape HTML delimiters
function esc(s)
{
gsub("&", "\\&", s);
gsub("<", "\\<", s);
gsub(">", "\\>", s);
gsub("\"", "\\"", s);
return s;
}
# val -- return the value part of the line s, as an HTML string
function val(s)
{
return esc(gensub("^[a-z]+[ \t]*(:[ \t]*)?", "", 1, s));
}
# val2 -- return the line s with initial white space collapsed
function val2(s)
{
return esc(gensub("^[ \t]*", " ", 1, s));
}
# err -- print an error message and increment the error count
function err(msg)
{
print FILENAME ":" FNR ": " msg > "/dev/stderr";
nerrors++;
}