Implement proposal system

This commit is contained in:
str4d
2016-04-03 11:30:29 +00:00
parent a894ae84f0
commit 2d25e2a5f6
9 changed files with 197 additions and 26 deletions

48
create-proposal.sh Executable file
View File

@ -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"

View File

@ -106,6 +106,7 @@ GETTEXT_DOMAIN_MAPPING = {
TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'pages') TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'pages')
STATIC_DIR = os.path.join(os.path.dirname(__file__), 'static') STATIC_DIR = os.path.join(os.path.dirname(__file__), 'static')
SPEC_DIR = os.path.join(os.path.dirname(__file__), 'spec') 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') BLOG_DIR = os.path.join(os.path.dirname(__file__), 'blog')
MEETINGS_DIR = os.path.join(os.path.dirname(__file__), 'meetings/logs') MEETINGS_DIR = os.path.join(os.path.dirname(__file__), 'meetings/logs')
SITE_DIR = os.path.join(TEMPLATE_DIR, 'site') SITE_DIR = os.path.join(TEMPLATE_DIR, 'site')

View File

@ -1,6 +1,7 @@
{%- macro change_lang(lang) -%} {%- macro change_lang(lang) -%}
{%- if request.endpoint == 'site_show' -%}{{ url_for('site_show', lang=lang, page=page) }} {%- 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 == 'spec_show' -%}{{ url_for('spec_show', name=name) }}
{%- elif request.endpoint == 'proposal_show' -%}{{ url_for('proposal_show', name=name) }}
{%- elif request.endpoint == 'blog_index' -%} {%- elif request.endpoint == 'blog_index' -%}
{%- if category -%}{{ url_for('blog_index', lang=lang, category=category) }} {%- if category -%}{{ url_for('blog_index', lang=lang, category=category) }}
{%- else -%}{{ url_for('blog_index', lang=lang) }} {%- else -%}{{ url_for('blog_index', lang=lang) }}

View File

@ -30,6 +30,7 @@
</ul> </ul>
</li> </li>
<li><a href="{{ url_for('spec_index') }}"><div class="menuitem"><span>{{ _('Specifications') }}</span></div></a></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> <li class="has-sub"><div class="menuitem"><span>{{ _('API') }}</span></div>
<ul> <ul>
<li><a href="{{ site_url('docs/api/i2ptunnel') }}"><div class="menuitem"><span>I2PTunnel</span></div></a></li> <li><a href="{{ site_url('docs/api/i2ptunnel') }}"><div class="menuitem"><span>I2PTunnel</span></div></a></li>

View File

@ -1,9 +1,13 @@
{% extends "global/layout.html" %} {% extends "global/layout.html" %}
{% block title %}I2P Specification Documents{% endblock %} {% block title %}I2P Specification Documents{% endblock %}
{% block content %} {% block content %}
<p>
This page provides the specifications for various components of the I2P network This page provides the specifications for various components of the I2P network
and router software. These are living documents, and the specifications are 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> <ul><li>
"Last updated" is the last date when the specification given within a document "Last updated" is the last date when the specification given within a document

View File

@ -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 %}

View File

@ -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 %}

View File

@ -1,9 +1,12 @@
import codecs import codecs
from docutils import io
from docutils.core import ( from docutils.core import (
Publisher,
publish_doctree, publish_doctree,
publish_from_doctree, publish_from_doctree,
publish_parts, publish_parts,
) )
from docutils.readers.doctree import Reader
from flask import ( from flask import (
abort, abort,
g, g,
@ -17,7 +20,7 @@ from flask import (
) )
import os.path import os.path
from i2p2www import SPEC_DIR from i2p2www import PROPOSAL_DIR, SPEC_DIR
from i2p2www import helpers from i2p2www import helpers
@ -26,7 +29,6 @@ SPEC_METATAGS = {
'category': '', 'category': '',
'lastupdated': None, 'lastupdated': None,
} }
SPEC_LIST_METATAGS = [ SPEC_LIST_METATAGS = [
] ]
SPEC_CATEGORY_SORT = { SPEC_CATEGORY_SORT = {
@ -36,12 +38,36 @@ SPEC_CATEGORY_SORT = {
'': 999, '': 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(): METATAG_LABELS = {
specs = [] 'accuratefor': u'Accurate for',
for f in os.listdir(SPEC_DIR): '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'): if f.endswith('.rst'):
path = safe_join(SPEC_DIR, f) path = safe_join(directory, f)
# read file header # read file header
header = '' header = ''
with codecs.open(path, encoding='utf-8') as fd: with codecs.open(path, encoding='utf-8') as fd:
@ -49,22 +75,32 @@ def spec_index():
header += line header += line
if not line.strip(): if not line.strip():
break break
parts = publish_parts(source=header, source_path=SPEC_DIR, writer_name="html") parts = publish_parts(source=header, source_path=directory, writer_name="html")
meta = get_metadata_from_meta(parts['meta']) meta = meta_parser(parts['meta'])
spec = { rst = {
'name': f[:-4], 'name': f[:-4],
'title': parts['title'], 'title': parts['title'],
} }
spec.update(meta) rst.update(meta)
specs.append(spec) 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'])) specs.sort(key=lambda s: (SPEC_CATEGORY_SORT[s['category']], s['title']))
return render_template('spec/index.html', specs=specs) 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 # 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): if not os.path.exists(path):
abort(404) abort(404)
@ -72,7 +108,7 @@ def spec_show(name, txt=False):
with codecs.open(path, encoding='utf-8') as fd: with codecs.open(path, encoding='utf-8') as fd:
content = fd.read() content = fd.read()
if txt: if not template:
# Strip out RST # Strip out RST
content = content.replace('.. meta::\n', '') content = content.replace('.. meta::\n', '')
content = content.replace('.. contents::\n\n', '') content = content.replace('.. contents::\n\n', '')
@ -82,16 +118,15 @@ def spec_show(name, txt=False):
content = content.replace(']_', '] ') content = content.replace(']_', '] ')
# Change highlight formatter # Change highlight formatter
content = content.replace('{% highlight', "{% highlight formatter='textspec'") content = content.replace('{% highlight', "{% highlight formatter='textspec'")
# Other string changes # Metatags
content = content.replace(' :accuratefor', '- Accurate for') for (metatag, label) in METATAG_LABELS.items():
content = content.replace(' :category', '- Category') content = content.replace(' :%s' % metatag, label)
content = content.replace(' :lastupdated', '- Last updated')
# render the post with Jinja2 to handle URLs etc. # render the post with Jinja2 to handle URLs etc.
rendered_content = render_template_string(content) rendered_content = render_template_string(content)
rendered_content = rendered_content.replace('</pre></div>', ' </pre></div>') rendered_content = rendered_content.replace('</pre></div>', ' </pre></div>')
if txt: if not template:
# Send response # Send response
r = make_response(rendered_content) r = make_response(rendered_content)
r.mimetype = 'text/plain' r.mimetype = 'text/plain'
@ -102,19 +137,37 @@ def spec_show(name, txt=False):
bullet_list = doctree[1][1] bullet_list = doctree[1][1]
doctree.clear() doctree.clear()
doctree.append(bullet_list) 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 # Remove the ToC from the main document
rendered_content = rendered_content.replace('.. contents::\n', '') rendered_content = rendered_content.replace('.. contents::\n', '')
# publish the spec with docutils # publish the spec with docutils
parts = publish_parts(source=rendered_content, source_path=SPEC_DIR, writer_name="html") parts = publish_parts(source=rendered_content, source_path=directory, writer_name="html")
meta = get_metadata_from_meta(parts['meta']) 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): def spec_show_txt(name):
return spec_show(name, True) return render_rst(SPEC_DIR, name, spec_meta, None)
def get_metadata_from_meta(meta): def proposal_show(name):
return render_rst(PROPOSAL_DIR, name, proposal_meta, 'spec/proposal-show.html')
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) 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)

View File

@ -47,6 +47,9 @@ url('/<lang:lang>/<path:page>', 'views.site_show')
url('/spec', 'spec.views.spec_index') url('/spec', 'spec.views.spec_index')
url('/spec/<string:name>', 'spec.views.spec_show') url('/spec/<string:name>', 'spec.views.spec_show')
url('/spec/<string:name>.txt', 'spec.views.spec_show_txt') 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/', 'anonbib.views.papers_list')
url('/<lang:lang>/papers/bibtex', 'anonbib.views.papers_bibtex') url('/<lang:lang>/papers/bibtex', 'anonbib.views.papers_bibtex')