diff --git a/create-proposal.sh b/create-proposal.sh new file mode 100755 index 0000000000000000000000000000000000000000..f05814644a74ef0543f452368e76c4a45c229f77 --- /dev/null +++ b/create-proposal.sh @@ -0,0 +1,48 @@ +#!/bin/sh +PROPOSAL_DIR="i2p2www/spec/proposals" + +if [ $# -lt 4 ] +then + echo "Usage: ./create-proposal.sh name-in-url \"Title of proposal\" author forum-url [file]" + exit +fi + +name=$1 +title=$2 +author=$3 +thread=$4 +file=$5 + +date=`date +%Y-%m-%d` +num=`expr $(expr substr $(ls -r "$PROPOSAL_DIR" | head -n1) 1 3) + 1` +titleline=`printf '%*s' "$(expr length "$title")" | tr ' ' =` + +proposal="$PROPOSAL_DIR/$num-$name.rst" + +cat >"$proposal" <<EOF +$titleline +$title +$titleline +.. meta:: + :author: $author + :created: $date + :thread: $thread + :lastupdated: $date + :status: Draft + +.. contents:: + + +Introduction +============ + +EOF + +if [ -f "$file" ] +then + cat "$file" >>"$proposal" +else + echo >>"$proposal" +fi + +echo "Proposal created: $proposal" diff --git a/i2p2www/__init__.py b/i2p2www/__init__.py index 5018c59b43eeec9220f0fa8ccc3de7aa0e8c3744..34c5ff9fe4fa902e0afd703339dc9ada794ef22b 100644 --- a/i2p2www/__init__.py +++ b/i2p2www/__init__.py @@ -110,6 +110,7 @@ GETTEXT_DOMAIN_MAPPING = { TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'pages') STATIC_DIR = os.path.join(os.path.dirname(__file__), 'static') SPEC_DIR = os.path.join(os.path.dirname(__file__), 'spec') +PROPOSAL_DIR = os.path.join(SPEC_DIR, 'proposals') BLOG_DIR = os.path.join(os.path.dirname(__file__), 'blog') MEETINGS_DIR = os.path.join(os.path.dirname(__file__), 'meetings/logs') SITE_DIR = os.path.join(TEMPLATE_DIR, 'site') diff --git a/i2p2www/pages/global/macros b/i2p2www/pages/global/macros index b50e5541054610b63608608f69612ad7b74f7caa..7876f3323fdf40a790e03ce28579f88f07e409d8 100644 --- a/i2p2www/pages/global/macros +++ b/i2p2www/pages/global/macros @@ -1,6 +1,7 @@ {%- macro change_lang(lang) -%} {%- if request.endpoint == 'site_show' -%}{{ url_for('site_show', lang=lang, page=page) }} {%- elif request.endpoint == 'spec_show' -%}{{ url_for('spec_show', name=name) }} +{%- elif request.endpoint == 'proposal_show' -%}{{ url_for('proposal_show', name=name) }} {%- elif request.endpoint == 'blog_index' -%} {%- if category -%}{{ url_for('blog_index', lang=lang, category=category) }} {%- else -%}{{ url_for('blog_index', lang=lang) }} diff --git a/i2p2www/pages/global/nav.html b/i2p2www/pages/global/nav.html index 14d3f0cee98e24cbeed1572794d5872d4be11f15..974be8de89ede6832a64b0e479c050e3da3bfc1d 100644 --- a/i2p2www/pages/global/nav.html +++ b/i2p2www/pages/global/nav.html @@ -30,6 +30,7 @@ </ul> </li> <li><a href="{{ url_for('spec_index') }}"><div class="menuitem"><span>{{ _('Specifications') }}</span></div></a></li> + <li><a href="{{ url_for('proposal_index') }}"><div class="menuitem"><span>{{ _('Proposals') }}</span></div></a></li> <li class="has-sub"><div class="menuitem"><span>{{ _('API') }}</span></div> <ul> <li><a href="{{ site_url('docs/api/i2ptunnel') }}"><div class="menuitem"><span>I2PTunnel</span></div></a></li> diff --git a/i2p2www/pages/spec/index.html b/i2p2www/pages/spec/index.html index db397f3415ffd4ef27a0210723125731868b2633..f56ce0945446399dd8121a68b4234687f50c905d 100644 --- a/i2p2www/pages/spec/index.html +++ b/i2p2www/pages/spec/index.html @@ -1,9 +1,13 @@ {% extends "global/layout.html" %} {% block title %}I2P Specification Documents{% endblock %} {% block content %} +<p> This page provides the specifications for various components of the I2P network and router software. These are living documents, and the specifications are -updated as modifications are made to the network and software. +updated as modifications are made to the network and software. The proposal +documents that track changes to these specifications can be viewed +<a href="{{ url_for('proposal_index') }}">here</a>. +</p> <ul><li> "Last updated" is the last date when the specification given within a document diff --git a/i2p2www/pages/spec/proposal-index.html b/i2p2www/pages/spec/proposal-index.html new file mode 100644 index 0000000000000000000000000000000000000000..b701d42b0cf2f596b498ec667b6951805bbbb8d1 --- /dev/null +++ b/i2p2www/pages/spec/proposal-index.html @@ -0,0 +1,36 @@ +{% extends "global/layout.html" %} +{% block title %}I2P Proposal Documents{% endblock %} +{% block content %} +<p> +This page is the central index of proposed changes to the +<a href="{{ url_for('spec_index') }}">I2P specifications</a>. +</p> + +<p>{% trans dev='http://'+i2pconv('zzz.i2p'), +trac='https://trac.i2p2.de/report/1' -%} +To submit a proposal, post it on the <a href="{{ dev }}">development forum</a> +or <a href="{{ trac }}">enter a ticket with the proposal attached</a>. +{%- endtrans %}</p> + +<table> + <tr> + <th>Number</th> + <th>Title</th> + <th>Last updated</th> + <th>Status</th> + <th>Link</th> + </tr> + {% for proposal in proposals %} + <tr> + <td>{{ proposal.num }}</td> + <td>{{ proposal.title }}</td> + <td><time>{{ proposal.lastupdated }}</time></td> + <td>{{ proposal.status }}</td> + <td> + <a href="{{ url_for('proposal_show', name=proposal.name) }}">HTML</a> | + <a href="{{ url_for('proposal_show_txt', name=proposal.name) }}">TXT</a> + </td> + </tr> +{% endfor %} +</table> +{% endblock %} diff --git a/i2p2www/pages/spec/proposal-show.html b/i2p2www/pages/spec/proposal-show.html new file mode 100644 index 0000000000000000000000000000000000000000..4b3d8c9c9ef1d1899cc09b515b57b703c621d469 --- /dev/null +++ b/i2p2www/pages/spec/proposal-show.html @@ -0,0 +1,24 @@ +{% extends "global/layout.html" %} +{%- from "global/macros" import render_categories with context -%} +{% block title %}{{ title }}{% endblock %} +{% block content_nav %} +{% autoescape false %} +{{ toc }} +{% endautoescape %} +{% endblock %} +{% block content %} +<dl class="meta"> + <dt>Author</dt> + <dd>{{ meta.author }}</dd> + <dt>Created</dt> + <dd><time datetime="{{ meta.created }}">{{ meta.created }}</time></dd> + <dt>Thread</dt> + <dd><a href="{{ meta.thread }}">{{ meta.thread }}</a></dd> + <dt>Last updated</dt> + <dd><time datetime="{{ meta.lastupdated }}">{{ meta.lastupdated }}</time></dd> + <dt>Status</dt><dd>{{ meta.status }}</dd> +</dl> +{% autoescape false %} +{{ body }} +{% endautoescape %} +{% endblock %} diff --git a/i2p2www/spec/views.py b/i2p2www/spec/views.py index c43f4ddce18a1c70a214558286d6f5ab5bbae9f3..318638553450d156e5a3620a6eeb798fcc6645ad 100644 --- a/i2p2www/spec/views.py +++ b/i2p2www/spec/views.py @@ -1,9 +1,12 @@ import codecs +from docutils import io from docutils.core import ( + Publisher, publish_doctree, publish_from_doctree, publish_parts, ) +from docutils.readers.doctree import Reader from flask import ( abort, g, @@ -17,7 +20,7 @@ from flask import ( ) import os.path -from i2p2www import SPEC_DIR +from i2p2www import PROPOSAL_DIR, SPEC_DIR from i2p2www import helpers @@ -26,7 +29,6 @@ SPEC_METATAGS = { 'category': '', 'lastupdated': None, } - SPEC_LIST_METATAGS = [ ] SPEC_CATEGORY_SORT = { @@ -36,12 +38,36 @@ SPEC_CATEGORY_SORT = { '': 999, } +PROPOSAL_METATAGS = { + 'author': u'I2P devs', + 'created': None, + 'lastupdated': None, + 'status': u'Draft', + 'thread': None, + } +PROPOSAL_LIST_METATAGS = [ + ] +PROPOSAL_STATUS_SORT = { + 'Draft': 1, + '': 999, + } -def spec_index(): - specs = [] - for f in os.listdir(SPEC_DIR): +METATAG_LABELS = { + 'accuratefor': u'Accurate for', + 'author': u'Author', + 'category': u'Category', + 'created': u'Created', + 'lastupdated': u'Last updated', + 'status': u'Status', + 'thread': u'Thread', + } + + +def get_rsts(directory, meta_parser): + rsts = [] + for f in os.listdir(directory): if f.endswith('.rst'): - path = safe_join(SPEC_DIR, f) + path = safe_join(directory, f) # read file header header = '' with codecs.open(path, encoding='utf-8') as fd: @@ -49,22 +75,32 @@ def spec_index(): header += line if not line.strip(): break - parts = publish_parts(source=header, source_path=SPEC_DIR, writer_name="html") - meta = get_metadata_from_meta(parts['meta']) + parts = publish_parts(source=header, source_path=directory, writer_name="html") + meta = meta_parser(parts['meta']) - spec = { + rst = { 'name': f[:-4], 'title': parts['title'], } - spec.update(meta) - specs.append(spec) + rst.update(meta) + rsts.append(rst) + return rsts +def spec_index(): + specs = get_rsts(SPEC_DIR, spec_meta) specs.sort(key=lambda s: (SPEC_CATEGORY_SORT[s['category']], s['title'])) return render_template('spec/index.html', specs=specs) -def spec_show(name, txt=False): +def proposal_index(): + proposals = get_rsts(PROPOSAL_DIR, proposal_meta) + for i in range(0, len(proposals)): + proposals[i]['num'] = int(proposals[i]['name'][:3]) + proposals.sort(key=lambda s: (PROPOSAL_STATUS_SORT[s['status']], s['num'])) + return render_template('spec/proposal-index.html', proposals=proposals) + +def render_rst(directory, name, meta_parser, template): # check if that file actually exists - path = safe_join(SPEC_DIR, name + '.rst') + path = safe_join(directory, name + '.rst') if not os.path.exists(path): abort(404) @@ -72,7 +108,7 @@ def spec_show(name, txt=False): with codecs.open(path, encoding='utf-8') as fd: content = fd.read() - if txt: + if not template: # Strip out RST content = content.replace('.. meta::\n', '') content = content.replace('.. contents::\n\n', '') @@ -82,16 +118,15 @@ def spec_show(name, txt=False): content = content.replace(']_', '] ') # Change highlight formatter content = content.replace('{% highlight', "{% highlight formatter='textspec'") - # Other string changes - content = content.replace(' :accuratefor', '- Accurate for') - content = content.replace(' :category', '- Category') - content = content.replace(' :lastupdated', '- Last updated') + # Metatags + for (metatag, label) in METATAG_LABELS.items(): + content = content.replace(' :%s' % metatag, label) # render the post with Jinja2 to handle URLs etc. rendered_content = render_template_string(content) rendered_content = rendered_content.replace('</pre></div>', ' </pre></div>') - if txt: + if not template: # Send response r = make_response(rendered_content) r.mimetype = 'text/plain' @@ -102,19 +137,37 @@ def spec_show(name, txt=False): bullet_list = doctree[1][1] doctree.clear() doctree.append(bullet_list) - toc = publish_from_doctree(doctree, writer_name='html') + reader = Reader(parser_name='null') + pub = Publisher(reader, None, None, + source=io.DocTreeInput(doctree), + destination_class=io.StringOutput) + pub.set_writer('html') + pub.publish() + toc = pub.writer.parts['fragment'] # Remove the ToC from the main document rendered_content = rendered_content.replace('.. contents::\n', '') # publish the spec with docutils - parts = publish_parts(source=rendered_content, source_path=SPEC_DIR, writer_name="html") - meta = get_metadata_from_meta(parts['meta']) + parts = publish_parts(source=rendered_content, source_path=directory, writer_name="html") + meta = meta_parser(parts['meta']) - return render_template('spec/show.html', title=parts['title'], toc=toc, body=parts['fragment'], name=name, meta=meta) + return render_template(template, title=parts['title'], toc=toc, body=parts['fragment'], name=name, meta=meta) + +def spec_show(name): + return render_rst(SPEC_DIR, name, spec_meta, 'spec/show.html') def spec_show_txt(name): - return spec_show(name, True) + return render_rst(SPEC_DIR, name, spec_meta, None) + +def proposal_show(name): + return render_rst(PROPOSAL_DIR, name, proposal_meta, 'spec/proposal-show.html') -def get_metadata_from_meta(meta): +def proposal_show_txt(name): + return render_rst(PROPOSAL_DIR, name, proposal_meta, None) + +def spec_meta(meta): return helpers.get_metadata_from_meta(meta, SPEC_METATAGS, SPEC_LIST_METATAGS) + +def proposal_meta(meta): + return helpers.get_metadata_from_meta(meta, PROPOSAL_METATAGS, PROPOSAL_LIST_METATAGS) diff --git a/i2p2www/urls.py b/i2p2www/urls.py index 63faa463b6ed7b08fe240f65ba2b4ad00b791f91..41d6b58287324bcc12d8e7946552d823d0b2a162 100644 --- a/i2p2www/urls.py +++ b/i2p2www/urls.py @@ -47,6 +47,9 @@ url('/<lang:lang>/<path:page>', 'views.site_show') url('/spec', 'spec.views.spec_index') url('/spec/<string:name>', 'spec.views.spec_show') url('/spec/<string:name>.txt', 'spec.views.spec_show_txt') +url('/spec/proposals', 'spec.views.proposal_index') +url('/spec/proposals/<string:name>', 'spec.views.proposal_show') +url('/spec/proposals/<string:name>.txt', 'spec.views.proposal_show_txt') url('/<lang:lang>/papers/', 'anonbib.views.papers_list') url('/<lang:lang>/papers/bibtex', 'anonbib.views.papers_bibtex')