[xiph-cvs] r6745 - in websites-ngen: . xiph.org xiph.org/JS xiph.org/speex/CSS xiph.org/speex/compare
comatoast at xiph.org
comatoast at xiph.org
Sun May 23 02:27:37 PDT 2004
Author: comatoast
Date: 2004-05-23 05:27:36 -0400 (Sun, 23 May 2004)
New Revision: 6745
Added:
websites-ngen/doc.html
websites-ngen/xiph.org/JS/
websites-ngen/xiph.org/JS/stripe.js
websites-ngen/xiph.org/inhead.inherit
websites-ngen/xiph.org/options.ini
websites-ngen/xiph.org/speex/compare/options.ini
Removed:
websites-ngen/readme.txt
Modified:
websites-ngen/style.txt
websites-ngen/todo.txt
websites-ngen/wrapup.py
websites-ngen/xiph.org/.htaccess
websites-ngen/xiph.org/speex/CSS/all.css
websites-ngen/xiph.org/speex/compare/index.markdown
Log:
SubEthaEdit made a mess of wrapup.py (it added in unix newlines without bothering to check if the file already had unix newlines in it or not; it didn't), so I made a bunch of not-quite-pointless changes like s/\.\.\./?\226?\128?\166/g. I also added in the zebra stripes thingy from http://www.alistapart.com/articles/zebratables/ for /speex/compare/ and added in that options.ini doodad that I've been meaning to do. Currently it lets me add in extra attributes to the body element. Yeehaw. Oh, and there's a new user doc.
Added: websites-ngen/doc.html
===================================================================
--- websites-ngen/doc.html 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/doc.html 2004-05-23 09:27:36 UTC (rev 6745)
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html lang='en-US'>
+<head>
+ <meta http-equiv='Content-type' content='text/html; charset=UTF-8'>
+ <title>Wrapup Documentation</title>
+ <style type='text/css'>
+ html {
+ font-family: 'Palatino Linotype', 'Hoefler Text', Georgia, sans-serif;
+ background: #132354;
+ }
+
+ body {
+ background: #eee;
+ border: thin solid gray;
+ padding: 0 1em;
+ max-width: 50em;
+ }
+ kbd, code, tt {
+ font-family: 'Courier New', Monaco, 'Lucida Console', monospace;
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ border-bottom: thin solid #999;
+ }
+ h2 { border-bottom-color: #ccc; }
+ h3 { border-bottom-color: #ccc; }
+ h4 { border-bottom-color: #ccc; }
+ h5 { border-bottom-color: #ccc; }
+ h6 { border-bottom-color: #ddd; }
+
+ ol, ul {
+ margin: 0;
+ padding: 0 0 0 2em;
+ }
+ li {
+ margin: .5em 0;
+ padding: 0;
+ }
+ </style>
+</head>
+<body>
+
+<h1>Wrapup Documentation</h1>
+<h2 id='requirements'>Requirements</h2>
+<ul>
+<li>
+ <a href='http://www.python.org'>Python</a> 2.3.2, to run wrapup.py
+ (use earlier versions at your own risk)
+</li>
+<li>
+ <a href='http://www.perl.org/'>Perl</a> 5.6.1, to run Markdown
+ (<a href='http://www.activestate.com/Products/ActivePerl/'>ActivePerl</a> works fine)</li>
+<li><a href='http://httpd.apache.org/'>Apache</a> 2, to check your changes</li>
+<li><a href='http://www.daringfireball.net/projects/markdown/'>Markdown</a> 1.0b4 (included)</li>
+</ul>
+
+
+<h2 id='usage'>Usage</h2>
+<p>
+Simply run <kbd>wrapup.py</kbd> in the directory itâs in. If nothing failed, you should see
+a newly generated .html file for every .markdown file that isnât in the news directory.
+</p>
+
+<h3 id='creation'>Creating</h3>
+<p>
+Suppose you want to create a new document. Hereâs how:
+</p>
+
+<ol>
+<li>
+Think of a good location to put it and what to call it. Donât gloss over this step; what you
+write will, ideally, be available at that location forever.
+</li>
+<li>Create a directory for that location.</li>
+<li>Create and edit a file called âindex.markdownâ in that directory.</li>
+<li>When you want to see your edits, run wrapup.py from its directory.</li>
+</ol>
+
+<h2 id='apacheSetup'>Apache Setup</h2>
+<p>
+If you want to view your changes on your own computer, youâll need to install
+<a href='http://httpd.apache.org/'>Apache</a>. In either the <code><Directory></code> or the
+<code><VirtualHost></code> blocks, put the following directives:
+</p>
+<pre><code>
+ Options Indexes Includes FollowSymLinks
+ AllowOverride All
+</code></pre>
+
+<p>
+Ensure that <tt>mod_rewrite</tt> is on. The non-English versions of the site wonât work without
+it.
+</p>
+
+
+<h2 id='inheritsAndIncludes'><kbd>.inherit</kbd> and <kbd>.include</kbd> files</h2>
+<p>
+Inherit and include files serve very</em> similar purposesâthey both reduce the amount of
+boilerplate that has to be typed in. Wrapup has five insertion points:
+</p>
+<ol>
+<li>
+ Inside the <code>head</code> element<br>
+ (<tt>inhead.inherit</tt> and <tt>inhead.include</tt>)
+</li>
+<li>
+ A CSS-specific inside-the-<code>head</code> element<br>
+ (<tt>css.inherit</tt> and <tt>css.include</tt>)
+</li>
+<li>
+ Before the main content <code>div</code>, suitable for menus<br>
+ (<tt>beforecontent.inherit</tt> and <tt>beforecontent.include</tt>)
+</li>
+<li>
+ After the text inside the main content <code>div</code><br>
+ (<tt>aftermaintext.inherit</tt> and <tt>aftermaintext.include</tt>)
+</li>
+<li>
+ After the main content <code>div</code><br>
+ (<tt>aftercontent.inherit</tt> and <tt>aftercontent.include</tt>)
+</li>
+</ol>
+
+<p>
+Inherited files came first when I developed Wrapup; I then realized that there would be many things
+that I would want on the front page but not on its subpages. I then implemented <tt>.include</tt>
+files for uninherited, page-specific things.
+</p>
+
+
+<h2 id='multilingual'>Multilingual Concerns</h2>
+<p>â¦<tt>index.es.html</tt>â¦<tt>/es</tt>â¦</p>
+
+<p>
+After working with wrapup and adding ways for multiple languages to be added without too much
+fuss, I discovered that Iâd need to have ways for non-English languages to override my CSS
+includes as Verdana is a poor first choice for text in Korean. Additionally, other languages like
+German may need tweaked widths to cope with its exceptionally long words. Further, each language
+would need its own sidebar include and perhaps its own header includes.
+</p>
+<p>
+Given these constraints, each .inherit and .include set has language-specific infixes, such as
+<tt>css.es.include</tt> and <tt>aftermaintext.ko.inherit</tt>.
+</body>
+</html>
Deleted: websites-ngen/readme.txt
===================================================================
--- websites-ngen/readme.txt 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/readme.txt 2004-05-23 09:27:36 UTC (rev 6745)
@@ -1,32 +0,0 @@
-About This Ball of Wax
-======================
-
-Requirements
-------------
-
-* Python 2.3.2, to run wrapup.py (use earlier versions at your own risk)
-* Perl 5.6.1, to run Markdown (ActivePerl works fine)
-* Apache2, if you want to view your changes (and you probably do)
-
-Trying It Out
--------------
-
-Run wrapup.py right in the directory it's in. If nothing failed, you should see a new .html
-file for every .markdown file.
-
-Apache Setup
-------------
-
-I try to keep as much configuration stuff in the .htaccess file as I possibly can
-(running apachectl is a pain), but there are a couple of things you need to have in the main
-httpd.conf or wherever you store your apache setup:
-
- <?>
- # The following may be okay without Includes. You also might be able to chuck Indexes,
- # but I never do that.
- # FollowSymLinks is an absolute must for the non-English versions, since mod_rewrite
- # needs it as a security check or something.
- Options Indexes Includes FollowSymLinks
-
- AllowOverride All
- </?>
Modified: websites-ngen/style.txt
===================================================================
--- websites-ngen/style.txt 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/style.txt 2004-05-23 09:27:36 UTC (rev 6745)
@@ -15,18 +15,23 @@
* use camelCase for id and class attribute values.
-* Close tags that need closing. <p>-as-paragraph-separator has gone the way of the dodo.
+* Try to stay as XHTML-compliant as possible; itâs simply good style. This doesnât mean that
+ you should include a closing slash in your img tags, but it does mean that you should
+ close your (for example) p, ul, and li tags.
-* Prefer the equals-sign-underline for h1 to the single #. The latter method doesn't offer
- a good way to get around BOMs that may sneak in from Notepad and TextEdit. However, prefer
- ## to ------ for marking up second-level headings.
+* Use the ===== style of heading for h1 elements; if you use the single # at the beginning of the
+ of the document, wrapup and Markdown wonât be able to cope with the UTF-8 BOM that some
+ editors add in.
+* Prefer ## to -------- for second-level headings. Unlike in this file and in readme.txt.
+
* Directories generally not shown to users should start with a capital letter.
Prime examples of this are the */CSS/ directories and /Error/.
Spelling, Grammar, and Punctuation
----------------------------------
+
* Do not write "CODEC" in all caps.
* "Copyright © 2004 Xiph.Org" and "Copyright (c) 2004 Xiph.Org" are redundant;
prefer "© 2004 Xiph.Org" to both. Failing that, use "Copyright 2004 Xiph.Org".
Modified: websites-ngen/todo.txt
===================================================================
--- websites-ngen/todo.txt 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/todo.txt 2004-05-23 09:27:36 UTC (rev 6745)
@@ -1,10 +1,8 @@
To Do
=====
-* Implement some of the ideas on <http://wiki.xiph.org/SXSW2004Ideas>.
-
* Make the front page look less like a blog page and more like a proper introduction.
- Communicate what we can do for the user.
+ Communicate what we can do for the user. Perhaps with even (gasp) images.
* Make a new essay (series) for /about/. Or do something other than an essay.
@@ -19,6 +17,9 @@
* We need a tagline. Lame starter: "Compressing your media into byte-size chunks"
+* `<link>` bits from one language version to the other. Consider using the information in
+ those options.ini files.
+
* Communicate that we want to attractâ**and retain**âWindows developers.
Also, since weâre mantaining python bindings, why not .NET ones, too? The little VB.NET shop
seems to be right up our alley.
Modified: websites-ngen/wrapup.py
===================================================================
--- websites-ngen/wrapup.py 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/wrapup.py 2004-05-23 09:27:36 UTC (rev 6745)
@@ -1,433 +1,465 @@
-# -*- coding: UTF-8 -*-
-# © 2004 Nathan Sharfi
-#
-# This software is provided 'as-is', without any express or implied warranty.
-# In no event will the authors be held liable for any damages arising from
-# the use of this software.
-#
-# Permission is granted to anyone to use this software for any purpose,
-# including commercial applications, and to alter it and redistribute it
-# freely, subject to the following restrictions:
-#
-# 1. The origin of this software must not be misrepresented; you must not
-# claim that you wrote the original software. If you use this software
-# in a product, an acknowledgment in the product documentation would be
-# appreciated but is not required.
-#
-# 2. Altered source versions must be plainly marked as such, and must not
-# be misrepresented as being the original software.
-#
-# 3. This notice may not be removed or altered from any source distribution.
-
-import os, re, sys, glob, os.path, time, datetime
-import pdb
-from ConfigParser import SafeConfigParser
-
-MARKDOWN_COMMAND = "perl Markdown.pl" # could also be "perl Markdown.pl"
-TITLE_REGEX = re.compile(r"<h1.*>(.*)</h1>")
-LANGUAGE_EXTRACTOR = re.compile(r"^.*\.(.{2,5})\..*$") # anything from â.es.â to â.zh-TW.â
-
-class MarkdownWrapper(object):
- """
- Important attributes:
-
- .sourceFilename -- the file containing markdown text
-
- .destFilename -- the output html file name
-
- .doctype -- self explanatory
-
- .inHeadInherit -- An inherited string from inhead.inherit
- ...to be included inside the head element
-
- .inHeadInclude -- An uninherited string from inhead.include
- ...to be included right after .inHeadInherit
-
- .cssInherit -- An inherited string from css.inherit
- ...to be included inside the head element
-
- .cssInclude -- An uninherited string from css.include
- ...to be included right after .cssInherit
-
- .beforeContentInherit -- An inherited string from beforecontent.inherit.
- ...to be included right before the content div
-
- .beforeContentInclude -- An uninherited string from beforecontent.include.
- ...to be included right before the content div
-
- .mainText -- the text from .sourceFileName after it's been turned into HTML
-
- .afterMainTextInherit -- An inherited string from aftermaintext.inherit
- ...included at the end of .mainText, but inside
- <div id='content'>
-
- .afterMainTextInclude -- Like the above, but not inherited, and above it. I think.
-
- .afterContentInherit -- stuff to include after the content div
-
- """
- def __init__(self, sourceFilename, destFilename=None):
- self.sourceFilename = sourceFilename
- if destFilename:
- self.destFilename = destFilename
- else:
- self.destFilename = os.path.splitext(sourceFilename)[0] + ".html"
- self.doctype = '<!DOCTYPE HTML PUBLIC ' + \
- '"-//W3C//DTD HTML 4.01//EN" ' + \
- '"http://www.w3.org/TR/html4/strict.dtd">'
-
- self.mainText = markdown(sourceFilename)
-
- self.title = "Untitled Document"
- match = TITLE_REGEX.match(self.mainText)
- if match: #TODO: needs decent error handling
- self.title = "Xiph.Org: " + match.group(1)
-
- self.lang = 'en-US'
- lm = LANGUAGE_EXTRACTOR.match(sourceFilename)
- if lm:
- self.lang = lm.group(1)
-
-
- self.inHeadInherit = ''
- self.inHeadInclude = ''
-
- self.cssInherit = ''
- self.cssInclude = ''
-
- self.langInclude = ''
-
- self.beforeContentInherit = ''
- self.beforeContentInclude = ''
-
- self.afterMainTextInherit = ''
- self.afterMainTextInclude = ''
-
- self.afterContentInherit = ''
- self.afterContentInclude = ''
-
- # Now we need to stuff these attributes from their respective files.
-
- prefixes = "inhead css beforecontent aftermaintext aftercontent".split()
- inheritables = prefixes[:]
- inheritables = [i + ".inherit" for i in inheritables]
-
- includables = prefixes[:]
- includables = [i + ".include" for i in includables]
-
- # this if cascade is ugly, ugly, ugly, ugly. Suggestions, please.
- def makeSearchers(baseList, ext):
- ret = []
- for base in baseList:
- s = r"^.*" + base + r".*" + ext + r"$"
- ret.append(re.compile(s))
- return tuple(ret)
-
- languagedPrefixes = prefixes[:]
- if self.lang != 'en-US':
- languagedPrefixes = [x + r'\.' + self.lang + r'\.' for x in languagedPrefixes]
- (INHEAD_INHERIT,
- CSS_INHERIT,
- BEFORECONTENT_INHERIT,
- AFTERMAINTEXT_INHERIT,
- AFTERCONTENT_INHERIT) = makeSearchers(languagedPrefixes, "inherit")
- (INHEAD_INCLUDE,
- CSS_INCLUDE,
- BEFORECONTENT_INCLUDE,
- AFTERMAINTEXT_INCLUDE,
- AFTERCONTENT_INCLUDE) = makeSearchers(languagedPrefixes, "include")
-
- for baseFilename in prefixes[:]:
- if self.lang != 'en-US':
- baseFilename += ".%s" % self.lang
- baseFilename += ".inherit"
- filename = getParallelFile(self.sourceFilename, baseFilename)
- filename = getParentedFile(filename)
- if not filename: continue
- if os.path.isfile(filename):
- contents = file(filename).read()
- if INHEAD_INHERIT.search(filename):
- self.inHeadInherit = contents
- if CSS_INHERIT.search(filename):
- self.cssInherit = contents
- if BEFORECONTENT_INHERIT.search(filename):
- self.beforeContentInherit = contents
- if AFTERMAINTEXT_INHERIT.search(filename):
- self.afterMainTextInherit = contents
- if AFTERCONTENT_INHERIT.search(filename):
- self.afterContentInherit = contents
- for baseFilename in prefixes[:]:
- if self.lang != 'en-US':
- baseFilename += ".%s" % self.lang
- baseFilename += ".include"
-
- filename = getParallelFile(self.sourceFilename, baseFilename)
- # notice the complete lack of getParentedFile()
- if not filename: continue
- if os.path.isfile(filename):
- contents = file(filename).read()
- if INHEAD_INCLUDE.search(filename):
- self.inHeadInclude = contents
- if CSS_INCLUDE.search(filename):
- self.cssInclude = contents
- if BEFORECONTENT_INCLUDE.search(filename):
- self.beforeContentInclude = contents
- if AFTERMAINTEXT_INCLUDE.search(filename):
- self.afterMainTextInclude = contents
- if AFTERCONTENT_INCLUDE.search(filename):
- self.afterMainTextInclude = contents
- langFilename = getParallelFile(self.sourceFilename, "lang.include")
- if langFilename and os.path.isfile(langFilename):
- self.langInclude = file(langFilename).read()
- def renderToFile(self):
- outfile = file(self.destFilename, "w")
- try:
- outfile.write(self.renderToString())
- finally:
- outfile.close()
-
-
- def renderToString(self):
- """dump self to string."""
- ret = ""
- ret += self.doctype + '\n'
- ret += "<html lang='" + self.lang + "'>\n"
- ret += "<head>\n"
- ret += "<meta http-equiv='Content-type' content='text/html; charset=UTF-8'>\n"
- ret += self.inHeadInherit
- ret += self.inHeadInclude
- ret += '<title>' + self.title + '</title>\n'
- ret += self.cssInherit
- ret += self.cssInclude
- ret += "</head>\n"
- ret += "<body>\n"
- ret += self.langInclude
- ret += self.beforeContentInherit
- ret += self.beforeContentInclude
- ret += "<div id='content'>\n"
- ret += self.mainText
- ret += self.afterMainTextInclude
- ret += self.afterMainTextInherit
- ret += "</div>\n"
- ret += self.afterContentInherit
- ret += "</body>\n"
- ret += "</html>\n"
- return ret
-
-
-def markdown(filename):
- """Returns an HTML snippet from a filename in Markdown format."""
- ret = ""
- fh = os.popen(MARKDOWN_COMMAND + ' ' + filename)
- try:
- ret = fh.read()
- finally:
- fh.close()
-
- return snipBom(ret)
-
-def snipBom(s):
- BOM = '\xef\xbb\xbf'
- ret = ''
- bomBegin = s.find(BOM)
- if bomBegin > -1:
- ret = s[:bomBegin] + s[bomBegin+3:]
- else:
- return s
- return ret
-
-
-def getParallelFile(where, what):
- """
- given f("/home/foo.txt", "bar.html"), returns "/home/bar.html".
- """
- return os.path.join(os.path.split(where)[0], what)
-
-def hasTwoDirseps(path):
- firstIndex = path.find(os.sep)
- if firstIndex == -1: return False
-
- secondIndex = path.find(os.sep, firstIndex+1)
- if secondIndex == -1: return False
-
- return True
-
-def getParentedFile(filename):
- r"""
- given a path to a file, say, /meals/breakfast/sausage/index.html, tries to return
- the first valid file of:
- /meals/breakfast/sausage/index.html
- /meals/breakfast/index.html
- /meals/index.html
- /index.html
- Or given .\meals\breakfast\eggs\index.html:
- .\meals\breakfast\eggs\index.html
- .\meals\breakfast\index.html
- .\meals\index.html
- .\index.html
- Or even given cheeses\france\brie.html:
- cheeses\france\brie.html
- cheeses\brie.html
- brie.html
-
- Returns "" if no such file is found.
- """
- while True:
- # okay, we need to abort out on two conditions:
- # 1) we found a valid path (return it)
- # 2) we've gone as far as we can, and there is no valid path (return "")
- if os.path.isfile(filename): return filename
-
- # There is no parented file if there's only one separator in the path
- if filename.find(os.sep) == -1:
- return ""
-
- # if the full string has two directory separators in it or more, then
- # there are still intermediate directories to back up through.
- # We need to chop out the thing just before the last dirsep,
- # but end just before the penultimate dirsep.
- if hasTwoDirseps(filename):
- indexOfLastDirsep = filename.rfind(os.sep)
- indexOfPenultimateDirsep = filename.rfind(os.sep, 0, indexOfLastDirsep)
- filename = filename[:indexOfPenultimateDirsep] + filename[indexOfLastDirsep:]
- if os.path.isfile(filename): return filename
- elif filename.find(os.sep) > -1:
- # If it has only one dirsep, then it's one of the following forms (modulo / vs. \)
- # * /foo.html
- # * ./foo.html
- if os.path.isfile(filename): return filename
- else: return ""
- else:
- if os.path.isfile(filename): return filename
- else: return ""
-
-class NewsItem:
- def __init__(self, post='', title='', author='', date=None, sections=None):
- self.post = post
- self.title = title
- self.author = author
- self.date = date
- self.sections = sections
-
- def __cmp__(self, other):
- return cmp(self.date)
-
-
-class NewsDispenser:
- def __init__(self, optionsFile):
- """Make a very simple list of news posts. This is not final code."""
- config = SafeConfigParser({'sections': 'general'})
- config.read(os.path.join("news", "options.ini"))
-
- FILENAME_DATE_FORMAT = "on%Y-%m-%dat%H%M"
-
- self.newsItems = []
-
- # okay, now we want to go through the sections and associate each with their newspost.
- for section in config.sections(): # section is string
- ni = NewsItem()
- ni.date = time.strptime(section, FILENAME_DATE_FORMAT)
- for key, value in config.items(section):
- if key == 'author':
- ni.author = value
- if key == 'sections':
- ni.sections = value.split()
- if key == 'title':
- ni.title = value
- filename = os.path.join('news', section + '.txt')
- s = markdown(filename)
- ni.post = s
- self.newsItems.append(ni)
-
- def dump(self):
- return self.newsItems[:]
-
- def getItems(startDate, endDate):
- """
- Return news items from
- """
-
-class NewsFormatter:
- def __init__(self, newsItems): pass
- def formatOne(self, newsItem): pass
- def formatAll(self): pass
-
-class HTMLNewsFormatter(NewsFormatter):
- """ This is just one HTML news formatter of possibly many, really."""
- def __init__(self, newsItems, dateFormat="%A, %B %d, %Y"):
- self.newsItems = newsItems
- self.dateFormat = dateFormat
-
- def formatOne(self, newsItem):
- ret = "<h1>" + newsItem.title + "</h1>\n"
- ret += newsItem.post[:]
- ret += "<p>Posted %s by %s</p>" % \
- (time.strftime(self.dateFormat, newsItem.date), newsItem.author)
- ret += "\n<hr>\n\n"
-
- return ret
-
- def formatAll(self):
- ret = ''
- for ni in self.newsItems:
- ret += self.formatOne(ni)
- return ret
-
-class AtomNewsFormatter(NewsFormatter):
-
- # Tag URIs: http://www.taguri.org/ (used for atom:id)
-
- def __init__(self, newsItems):
- self.newsItems = newsItems
- self.dateFormat = "%Y-%m-%dT%H:%M:%SZ" # lie and say it's at UTC (...Z)
-
- def formatOne(self, newsItem, index=0):
- ret = "<entry>\n"
- ret += "<title>" + newsItem.title + "</title>\n"
- # these next two lines are so wrong.
- ret += "<issued>%s</issued>\n" % time.strftime(self.dateFormat, newsItem.date)
- ret += "<modified>%s</modified>\n" % time.strftime(self.dateFormat, newsItem.date)
- ret += "<id>tag:xiph.org,%s:%s</id>" % (time.strftime("%Y", newsItem.date), index)
- ret += "<link rel='alternate' type='text/html' href='http://www.xiph.org/news/'/>\n"
- ret += "<content type='text/html' mode='escaped'><![CDATA["
- ret += newsItem.post
- ret += "]]></content>\n"
- ret += "</entry>"
- return ret
- def formatAll(self):
- ret = ''
- ret += "<?xml version='1.0' encoding='utf-8'?>\n" \
- "<feed version='0.3' xmlns='http://purl.org/atom/ns#'>\n" \
- " <title>Xiph.Org News</title>\n" \
- " <link rel='alternate' type='text/html' href='http://www.xiph.org/news/'/>\n" \
- " <modified>2003-12-13T18:30:02Z</modified>\n"
- ret += "<author><name>Xiph.Org Foundation</name></author>"
-
- for index, newsItem in enumerate(self.newsItems):
- ret += self.formatOne(newsItem, index) + "\n\n"
-
- ret += "</feed>"
- return ret
-
-def doSite():
- for dirpath, dirnames, filenames in os.walk('xiph.org'):
- for filename in filter(lambda x: x.endswith(".markdown"), filenames):
- srcPath = os.path.join(dirpath, filename)
- destFile = os.path.splitext(filename)[0] + ".html"
- destPath = getParallelFile(srcPath, destFile)
- print srcPath
- mw = MarkdownWrapper(srcPath, destPath)
- mw.renderToFile()
- if ".svn" in dirnames:
- dirnames.remove('.svn') # don't transform things in .svn directories
-
-def doNews():
- nd = NewsDispenser(os.path.join('news', 'options.ini'))
- #print HTMLNewsFormatter(nd.dump()).formatAll()
- s = AtomNewsFormatter(nd.dump()).formatAll()
- fh = open('xiph.org\\atom.xml', 'w')
- fh.write(s)
- fh.close()
-
-if __name__ == '__main__':
- doSite()
+# -*- coding: UTF-8 -*-
+# © 2004 Nathan Sharfi
+#
+# This software is provided 'as-is', without any express or implied warranty.
+# In no event will the authors be held liable for any damages arising from
+# the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+# claim that you wrote the original software. If you use this software
+# in a product, an acknowledgment in the product documentation would be
+# appreciated but is not required.
+#
+# 2. Altered source versions must be plainly marked as such, and must not
+# be misrepresented as being the original software.
+#
+# 3. This notice may not be removed or altered from any source distribution.
+
+import os, re, sys, glob, os.path, time, datetime
+import pdb
+from ConfigParser import SafeConfigParser
+
+MARKDOWN_COMMAND = "perl Markdown.pl" # could also be "perl Markdown.pl"
+TITLE_REGEX = re.compile(r"<h1.*>(.*)</h1>")
+LANGUAGE_EXTRACTOR = re.compile(r"^.*\.(.{2,5})\..*$") # anything from â.es.â to â.zh-TW.â
+
+class MarkdownWrapper(object):
+ """
+ Important attributes:
+
+ .sourceFilename -- the file containing markdown text
+
+ .destFilename -- the output html file name
+
+ .doctype -- self explanatory
+
+ .inHeadInherit -- An inherited string from inhead.inherit
+ â¦to be included inside the head element
+
+ .inHeadInclude -- An uninherited string from inhead.include
+ â¦to be included right after .inHeadInherit
+
+ .cssInherit -- An inherited string from css.inherit
+ â¦to be included inside the head element
+
+ .cssInclude -- An uninherited string from css.include
+ â¦to be included right after .cssInherit
+
+ .beforeContentInherit -- An inherited string from beforecontent.inherit.
+ â¦to be included right before the content div
+
+ .beforeContentInclude -- An uninherited string from beforecontent.include.
+ â¦to be included right before the content div
+
+ .mainText -- the text from .sourceFileName after it's been turned into HTML
+
+ .afterMainTextInherit -- An inherited string from aftermaintext.inherit
+ â¦included at the end of .mainText, but inside
+ <div id='content'>
+
+ .afterMainTextInclude -- Like the above, but not inherited, and above it. I think.
+
+ .afterContentInherit -- stuff to include after the content div
+
+ """
+ def __init__(self, sourceFilename, destFilename=None):
+ self.sourceFilename = sourceFilename
+ if destFilename:
+ self.destFilename = destFilename
+ else:
+ self.destFilename = os.path.splitext(sourceFilename)[0] + ".html"
+ self.doctype = '<!DOCTYPE HTML PUBLIC ' + \
+ '"-//W3C//DTD HTML 4.01//EN" ' + \
+ '"http://www.w3.org/TR/html4/strict.dtd">'
+
+ self.mainText = markdown(sourceFilename)
+
+ self.title = "Untitled Document"
+ match = TITLE_REGEX.match(self.mainText)
+ if match: #TODO: needs decent error handling
+ self.title = "Xiph.Org: " + match.group(1)
+
+ self.lang = 'en-US'
+ lm = LANGUAGE_EXTRACTOR.match(sourceFilename)
+ if lm:
+ self.lang = lm.group(1)
+
+
+ self.inHeadInherit = ''
+ self.inHeadInclude = ''
+
+ self.cssInherit = ''
+ self.cssInclude = ''
+
+ self.langInclude = ''
+
+ self.beforeContentInherit = ''
+ self.beforeContentInclude = ''
+
+ self.afterMainTextInherit = ''
+ self.afterMainTextInclude = ''
+
+ self.afterContentInherit = ''
+ self.afterContentInclude = ''
+
+ self.bodyAttributes = ''
+
+ self.__loadIncludes()
+ self.__loadOptions()
+
+ def __loadOptions(self):
+
+ def getBaseName(path):
+ """
+ 'index.html' -> 'index'
+ 'index.es.markdown' -> 'index.es'
+ '/foo/bar/baz.es.txt' -> 'baz.es'
+ """
+ filename = os.path.split(path)[1]
+ filenameWithoutExtension = os.path.splitext(filename)[0]
+ return filenameWithoutExtension
+
+ configFilename = getParallelFile(self.sourceFilename, 'options.ini')
+ if not os.path.isfile(configFilename):
+ return
+
+ config = SafeConfigParser()
+ config.read(configFilename)
+
+ for section in config.sections():
+ if section == getBaseName(self.sourceFilename):
+ for key, value in config.items(section):
+ if key == 'bodyattributes':
+ self.bodyAttributes = ' ' + value
+
+
+ def __loadIncludes(self):
+ """Loads .include and .inherit files into this object's attributes."""
+
+ prefixes = "inhead css beforecontent aftermaintext aftercontent".split()
+ inheritables = [i + ".inherit" for i in prefixes]
+ includables = [i + ".include" for i in prefixes]
+
+ # this if cascade is ugly, ugly, ugly, ugly. Suggestions, please.
+ def makeSearchers(baseList, ext):
+ ret = []
+ for base in baseList:
+ s = r"^.*" + base + r".*" + ext + r"$"
+ ret.append(re.compile(s))
+ return tuple(ret)
+
+ languagedPrefixes = prefixes[:]
+ if self.lang != 'en-US':
+ languagedPrefixes = [x + r'\.' + self.lang + r'\.' for x in languagedPrefixes]
+ (INHEAD_INHERIT,
+ CSS_INHERIT,
+ BEFORECONTENT_INHERIT,
+ AFTERMAINTEXT_INHERIT,
+ AFTERCONTENT_INHERIT) = makeSearchers(languagedPrefixes, "inherit")
+ (INHEAD_INCLUDE,
+ CSS_INCLUDE,
+ BEFORECONTENT_INCLUDE,
+ AFTERMAINTEXT_INCLUDE,
+ AFTERCONTENT_INCLUDE) = makeSearchers(languagedPrefixes, "include")
+
+ for baseFilename in prefixes[:]:
+ if self.lang != 'en-US':
+ baseFilename += ".%s" % self.lang
+ baseFilename += ".inherit"
+ filename = getParallelFile(self.sourceFilename, baseFilename)
+ filename = getParentedFile(filename)
+ if not filename: continue
+ if os.path.isfile(filename):
+ contents = file(filename).read()
+ if INHEAD_INHERIT.search(filename):
+ self.inHeadInherit = contents
+ if CSS_INHERIT.search(filename):
+ self.cssInherit = contents
+ if BEFORECONTENT_INHERIT.search(filename):
+ self.beforeContentInherit = contents
+ if AFTERMAINTEXT_INHERIT.search(filename):
+ self.afterMainTextInherit = contents
+ if AFTERCONTENT_INHERIT.search(filename):
+ self.afterContentInherit = contents
+ for baseFilename in prefixes:
+ if self.lang != 'en-US':
+ baseFilename += ".%s" % self.lang
+ baseFilename += ".include"
+
+ filename = getParallelFile(self.sourceFilename, baseFilename)
+ # notice the complete lack of getParentedFile()
+ if not filename: continue
+ if os.path.isfile(filename):
+ contents = file(filename).read()
+ if INHEAD_INCLUDE.search(filename):
+ self.inHeadInclude = contents
+ if CSS_INCLUDE.search(filename):
+ self.cssInclude = contents
+ if BEFORECONTENT_INCLUDE.search(filename):
+ self.beforeContentInclude = contents
+ if AFTERMAINTEXT_INCLUDE.search(filename):
+ self.afterMainTextInclude = contents
+ if AFTERCONTENT_INCLUDE.search(filename):
+ self.afterMainTextInclude = contents
+ langFilename = getParallelFile(self.sourceFilename, "lang.include")
+ if langFilename and os.path.isfile(langFilename):
+ self.langInclude = file(langFilename).read()
+
+ def renderToFile(self):
+ outfile = file(self.destFilename, "w")
+ try:
+ outfile.write(self.renderToString())
+ finally:
+ outfile.close()
+
+
+ def renderToString(self):
+ """dump self to string."""
+ ret = ""
+ ret += self.doctype + '\n'
+ ret += "<html lang='" + self.lang + "'>\n"
+ ret += "<head>\n"
+ ret += " <meta http-equiv='Content-type' content='text/html; charset=UTF-8'>\n"
+ ret += self.inHeadInherit
+ ret += self.inHeadInclude
+ ret += ' <title>' + self.title + '</title>\n'
+ ret += self.cssInherit
+ ret += self.cssInclude
+ ret += "</head>\n"
+ ret += "<body"
+ ret += self.bodyAttributes
+ ret += ">\n"
+ ret += self.langInclude
+ ret += self.beforeContentInherit
+ ret += self.beforeContentInclude
+ ret += "<div id='content'>\n"
+ ret += self.mainText
+ ret += self.afterMainTextInclude
+ ret += self.afterMainTextInherit
+ ret += "</div>\n"
+ ret += self.afterContentInherit
+ ret += "</body>\n"
+ ret += "</html>\n"
+ return ret
+
+
+def markdown(filename):
+ """Returns an HTML snippet from a filename in Markdown format."""
+ ret = ""
+ fh = os.popen(MARKDOWN_COMMAND + ' ' + filename)
+ try:
+ ret = fh.read()
+ finally:
+ fh.close()
+
+ return snipBom(ret)
+
+def snipBom(s):
+ BOM = '\xef\xbb\xbf'
+ ret = ''
+ bomBegin = s.find(BOM)
+ if bomBegin > -1:
+ ret = s[:bomBegin] + s[bomBegin+3:]
+ else:
+ return s
+ return ret
+
+
+def getParallelFile(where, what):
+ """
+ given f("/home/foo.txt", "bar.html"), returns "/home/bar.html".
+ """
+ return os.path.join(os.path.split(where)[0], what)
+
+def hasTwoDirseps(path):
+ firstIndex = path.find(os.sep)
+ if firstIndex == -1: return False
+
+ secondIndex = path.find(os.sep, firstIndex+1)
+ if secondIndex == -1: return False
+
+ return True
+
+def getParentedFile(filename):
+ r"""
+ given a path to a file, say, /meals/breakfast/sausage/index.html, tries to return
+ the first valid file of:
+ /meals/breakfast/sausage/index.html
+ /meals/breakfast/index.html
+ /meals/index.html
+ /index.html
+ Or given .\meals\breakfast\eggs\index.html:
+ .\meals\breakfast\eggs\index.html
+ .\meals\breakfast\index.html
+ .\meals\index.html
+ .\index.html
+ Or even given cheeses\france\brie.html:
+ cheeses\france\brie.html
+ cheeses\brie.html
+ brie.html
+
+ Returns "" if no such file is found.
+ """
+ while True:
+ # okay, we need to abort out on two conditions:
+ # 1) we found a valid path (return it)
+ # 2) we've gone as far as we can, and there is no valid path (return "")
+ if os.path.isfile(filename): return filename
+
+ # There is no parented file if there's only one separator in the path
+ if filename.find(os.sep) == -1:
+ return ""
+
+ # if the full string has two directory separators in it or more, then
+ # there are still intermediate directories to back up through.
+ # We need to chop out the thing just before the last dirsep,
+ # but end just before the penultimate dirsep.
+ if hasTwoDirseps(filename):
+ indexOfLastDirsep = filename.rfind(os.sep)
+ indexOfPenultimateDirsep = filename.rfind(os.sep, 0, indexOfLastDirsep)
+ filename = filename[:indexOfPenultimateDirsep] + filename[indexOfLastDirsep:]
+ if os.path.isfile(filename): return filename
+ elif filename.find(os.sep) > -1:
+ # If it has only one dirsep, then it's one of the following forms (modulo / vs. \)
+ # * /foo.html
+ # * ./foo.html
+ if os.path.isfile(filename): return filename
+ else: return ""
+ else:
+ if os.path.isfile(filename): return filename
+ else: return ""
+
+class NewsItem(object):
+ def __init__(self, post='', title='', author='', date=None, sections=None):
+ self.post = post
+ self.title = title
+ self.author = author
+ self.date = date
+ self.sections = sections
+
+ def __cmp__(self, other):
+ return cmp(self.date)
+
+
+class NewsDispenser(object):
+ def __init__(self, optionsFile):
+ """Make a very simple list of news posts. This is not final code."""
+ config = SafeConfigParser({'sections': 'general'})
+ config.read(os.path.join("news", "options.ini"))
+
+ FILENAME_DATE_FORMAT = "on%Y-%m-%dat%H%M"
+
+ self.newsItems = []
+
+ # okay, now we want to go through the sections and associate each with their newspost.
+ for section in config.sections(): # section is string
+ ni = NewsItem()
+ ni.date = time.strptime(section, FILENAME_DATE_FORMAT)
+ for key, value in config.items(section):
+ if key == 'author':
+ ni.author = value
+ if key == 'sections':
+ ni.sections = value.split()
+ if key == 'title':
+ ni.title = value
+ filename = os.path.join('news', section + '.txt')
+ s = markdown(filename)
+ ni.post = s
+ self.newsItems.append(ni)
+
+ def dump(self):
+ return self.newsItems[:]
+
+ def getItems(startDate, endDate):
+ """
+ Return news items from
+ """
+
+class NewsFormatter(object):
+ def __init__(self, newsItems): pass
+ def formatOne(self, newsItem): pass
+ def formatAll(self): pass
+
+class HTMLNewsFormatter(NewsFormatter):
+ """ This is just one HTML news formatter of possibly many, really."""
+ def __init__(self, newsItems, dateFormat="%A, %B %d, %Y"):
+ self.newsItems = newsItems
+ self.dateFormat = dateFormat
+
+ def formatOne(self, newsItem):
+ ret = "<h1>" + newsItem.title + "</h1>\n"
+ ret += newsItem.post[:]
+ ret += "<p>Posted %s by %s</p>" % \
+ (time.strftime(self.dateFormat, newsItem.date), newsItem.author)
+ ret += "\n<hr>\n\n"
+
+ return ret
+
+ def formatAll(self):
+ ret = ''
+ for ni in self.newsItems:
+ ret += self.formatOne(ni)
+ return ret
+
+class AtomNewsFormatter(NewsFormatter):
+
+ # Tag URIs: http://www.taguri.org/ (used for atom:id)
+
+ def __init__(self, newsItems):
+ self.newsItems = newsItems
+ self.dateFormat = "%Y-%m-%dT%H:%M:%SZ" # lie and say it's at UTC (...Z)
+
+ def formatOne(self, newsItem, index=0):
+ ret = "<entry>\n"
+ ret += "<title>" + newsItem.title + "</title>\n"
+ # these next two lines are so wrong.
+ ret += "<issued>%s</issued>\n" % time.strftime(self.dateFormat, newsItem.date)
+ ret += "<modified>%s</modified>\n" % time.strftime(self.dateFormat, newsItem.date)
+ ret += "<id>tag:xiph.org,%s:%s</id>" % (time.strftime("%Y", newsItem.date), index)
+ ret += "<link rel='alternate' type='text/html' href='http://www.xiph.org/news/'/>\n"
+ ret += "<content type='text/html' mode='escaped'><![CDATA["
+ ret += newsItem.post
+ ret += "]]></content>\n"
+ ret += "</entry>"
+ return ret
+ def formatAll(self):
+ ret = ''
+ ret += "<?xml version='1.0' encoding='utf-8'?>\n" \
+ "<feed version='0.3' xmlns='http://purl.org/atom/ns#'>\n" \
+ " <title>Xiph.Org News</title>\n" \
+ " <link rel='alternate' type='text/html' href='http://www.xiph.org/news/'/>\n" \
+ " <modified>2003-12-13T18:30:02Z</modified>\n"
+ ret += "<author><name>Xiph.Org Foundation</name></author>"
+
+ for index, newsItem in enumerate(self.newsItems):
+ ret += self.formatOne(newsItem, index) + "\n\n"
+
+ ret += "</feed>"
+ return ret
+
+def doSite():
+ for dirpath, dirnames, filenames in os.walk('xiph.org'):
+ for filename in filter(lambda x: x.endswith(".markdown"), filenames):
+ srcPath = os.path.join(dirpath, filename)
+ destFile = os.path.splitext(filename)[0] + ".html"
+ destPath = getParallelFile(srcPath, destFile)
+ print srcPath
+ mw = MarkdownWrapper(srcPath, destPath)
+ mw.renderToFile()
+ if ".svn" in dirnames:
+ dirnames.remove('.svn') # don't transform things in .svn directories
+
+def doNews():
+ nd = NewsDispenser(os.path.join('news', 'options.ini'))
+ #print HTMLNewsFormatter(nd.dump()).formatAll()
+ s = AtomNewsFormatter(nd.dump()).formatAll()
+ fh = open('xiph.org\\atom.xml', 'w')
+ fh.write(s)
+ fh.close()
+
+if __name__ == '__main__':
+ doSite()
Modified: websites-ngen/xiph.org/.htaccess
===================================================================
--- websites-ngen/xiph.org/.htaccess 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/xiph.org/.htaccess 2004-05-23 09:27:36 UTC (rev 6745)
@@ -1,5 +1,5 @@
AddDefaultCharset UTF-8
-DirectoryIndex index.html index.xhtml index.shtml index.markdown index.txt
+DirectoryIndex index.html index.markdown index.txt
# good guess, but not good enough
Redirect permanent /svn/ http://www.xiph.org/subversion/
Added: websites-ngen/xiph.org/JS/stripe.js
===================================================================
--- websites-ngen/xiph.org/JS/stripe.js 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/xiph.org/JS/stripe.js 2004-05-23 09:27:36 UTC (rev 6745)
@@ -0,0 +1,70 @@
+// Gleefully snagged from somewhere in http://www.alistapart.com/articles/zebratables/.
+
+// this function is needed to work around a bug in IE related to element attributes
+function hasClass(obj) {
+ var result = false;
+ if (obj.getAttributeNode("class") != null) {
+ result = obj.getAttributeNode("class").value;
+ }
+ return result;
+}
+
+function stripe(id) {
+
+// the flag we'll use to keep track of whether the current row is odd or even
+var even = false;
+
+// if arguments are provided to specify the colours
+// of the even & odd rows, then use the them;
+// otherwise use the following defaults:
+var evenColor = arguments[1] ? arguments[1] : "#fff";
+var oddColor = arguments[2] ? arguments[2] : "#eee";
+
+// obtain a reference to the desired table
+// if no such table exists, abort
+var table = document.getElementById(id);
+if (! table) { return; }
+
+// by definition, tables can have more than one tbody
+// element, so we'll have to get the list of child
+// <tbody>s
+var tbodies = table.getElementsByTagName("tbody");
+
+// and iterate through them...
+for (var h = 0; h < tbodies.length; h++) {
+
+ // find all the <tr> elements...
+ var trs = tbodies[h].getElementsByTagName("tr");
+
+ // ... and iterate through them
+ for (var i = 0; i < trs.length; i++) {
+
+ // avoid rows that have a class attribute
+ // or backgroundColor style
+ if (! hasClass(trs[i]) &&
+ ! trs[i].style.backgroundColor) {
+
+ // get all the cells in this row...
+ var tds = trs[i].getElementsByTagName("td");
+
+ // and iterate through them...
+ for (var j = 0; j < tds.length; j++) {
+
+ var mytd = tds[j];
+
+ // avoid cells that have a class attribute
+ // or backgroundColor style
+ if (! hasClass(mytd) &&
+ ! mytd.style.backgroundColor) {
+
+ mytd.style.backgroundColor =
+ even ? evenColor : oddColor;
+
+ }
+ }
+ }
+ // flip from odd to even, or vice-versa
+ even = ! even;
+ }
+}
+}
Added: websites-ngen/xiph.org/inhead.inherit
===================================================================
--- websites-ngen/xiph.org/inhead.inherit 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/xiph.org/inhead.inherit 2004-05-23 09:27:36 UTC (rev 6745)
@@ -0,0 +1 @@
+ <script type='text/javascript' src='/JS/stripe.js'></script>
Added: websites-ngen/xiph.org/options.ini
===================================================================
--- websites-ngen/xiph.org/options.ini 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/xiph.org/options.ini 2004-05-23 09:27:36 UTC (rev 6745)
@@ -0,0 +1,8 @@
+[index]
+; the 'title' key is not implemented; it would be used for when you want the title and h1 elements
+; to differ (maybe because you have markup in the latter)
+; Oh, and it hasn't been implemented yet.
+; title = Welcome to Xiph.Org!
+bodyattributes = onload="stripe('comparison', '#fff', '#edf3fe')"
+[index.es]
+; title = ¡Bienvenidos a Xiph.Org!
Modified: websites-ngen/xiph.org/speex/CSS/all.css
===================================================================
--- websites-ngen/xiph.org/speex/CSS/all.css 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/xiph.org/speex/CSS/all.css 2004-05-23 09:27:36 UTC (rev 6745)
@@ -46,3 +46,14 @@
div#sidebar a:hover {
background: #c1dfb1;
}
+
+table#comparison {
+ border-collapse: collapse;
+ font-size: 90%;
+ width: 100%;
+}
+
+table#comparison th,
+table#comparison td {
+ border: 1px solid black;
+}
Modified: websites-ngen/xiph.org/speex/compare/index.markdown
===================================================================
--- websites-ngen/xiph.org/speex/compare/index.markdown 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/xiph.org/speex/compare/index.markdown 2004-05-23 09:27:36 UTC (rev 6745)
@@ -1,6 +1,7 @@
Codec Comparison
================
-<table border="2" cellpadding="2" width="100%">
+
+<table id='comparison'>
<thead>
<tr>
<th>Codec</th>
@@ -8,7 +9,6 @@
<th>bitrate (kbps)</th>
<th>delay (ms)</th>
<th>multi-rate</th>
-
<th>embedded</th>
<th><a href="#vbr"><abbr title="variable bitrate">VBR</abbr></a></th>
<th><a href="#plc"><abbr title="packet loss concealment">PLC</abbr></a></th>
@@ -17,12 +17,11 @@
</tr>
</thead>
<tbody>
- <tr class="speex">
+ <tr>
<td>Speex</td>
<td>8, 16, 32</td>
<td>
2.15-24.6 (<abbr title="narrowband">NB</abbr>)<br>
-
4-44.2 (<abbr title="wideband">WB</abbr>)
</td>
<td>
Added: websites-ngen/xiph.org/speex/compare/options.ini
===================================================================
--- websites-ngen/xiph.org/speex/compare/options.ini 2004-05-21 18:50:13 UTC (rev 6744)
+++ websites-ngen/xiph.org/speex/compare/options.ini 2004-05-23 09:27:36 UTC (rev 6745)
@@ -0,0 +1,2 @@
+[index]
+bodyattributes = onload="stripe('comparison', '#fff', '#edf3fe')"
--- >8 ----
List archives: http://www.xiph.org/archives/
Ogg project homepage: http://www.xiph.org/ogg/
To unsubscribe from this list, send a message to 'cvs-request at xiph.org'
containing only the word 'unsubscribe' in the body. No subject is needed.
Unsubscribe messages sent to the list will be ignored/filtered.
More information about the commits
mailing list