Implement proposal system
This commit is contained in:
48
create-proposal.sh
Executable file
48
create-proposal.sh
Executable 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"
|
@ -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')
|
||||||
|
@ -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) }}
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
36
i2p2www/pages/spec/proposal-index.html
Normal file
36
i2p2www/pages/spec/proposal-index.html
Normal 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 %}
|
24
i2p2www/pages/spec/proposal-show.html
Normal file
24
i2p2www/pages/spec/proposal-show.html
Normal 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 %}
|
@ -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)
|
||||||
|
@ -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')
|
||||||
|
Reference in New Issue
Block a user