meta-tags: cleanup support for metatags.

- fixes problem of html-escape + supported link
- no longer extract url from description, you have to use new url => tag
- tags are extracted to the beginning of the description during rendering
- show helpers in proper places in groups/repos/forks
This commit is contained in:
Marcin Kuzminski 2017-09-14 17:31:52 +02:00
parent 77ccbcae5e
commit 6dada9f346
13 changed files with 293 additions and 110 deletions

View file

@ -928,52 +928,67 @@ def gravatar_with_user(request, author, show_disabled=False):
return _render('gravatar_with_user', author, show_disabled=show_disabled)
def desc_stylize(value):
tags_paterns = OrderedDict((
('lang', (re.compile(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+\.]*)\]'),
'<div class="metatag" tag="lang">\\2</div>')),
('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
'<div class="metatag" tag="see">see =&gt; \\1 </div>')),
('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((.*?)\)\]'),
'<div class="metatag" tag="url"> <a href="\\2">\\1</a> </div>')),
('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
'<div class="metatag" tag="ref \\1">\\1 =&gt; <a href="/\\2">\\2</a></div>')),
('state', (re.compile(r'\[(stable|featured|stale|dead|dev)\]'),
'<div class="metatag" tag="state \\1">\\1</div>')),
# label in grey
('label', (re.compile(r'\[([a-z]+)\]'),
'<div class="metatag" tag="label">\\1</div>')),
# generic catch all in grey
('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
'<div class="metatag" tag="generic">\\1</div>')),
))
def extract_metatags(value):
"""
Extract supported meta-tags from given text value
"""
if not value:
return ''
tags = []
for key, val in tags_paterns.items():
pat, replace_html = val
tags.extend([(key, x.group()) for x in pat.finditer(value)])
value = pat.sub('', value)
return tags, value
def style_metatag(tag_type, value):
"""
converts tags from value into html equivalent
:param value:
"""
if not value:
return ''
value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
'<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
'<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
'<div class="metatag" tag="lang">\\2</div>', value)
value = re.sub(r'\[([a-z]+)\]',
'<div class="metatag" tag="\\1">\\1</div>', value)
html_value = value
tag_data = tags_paterns.get(tag_type)
if tag_data:
pat, replace_html = tag_data
# convert to plain `unicode` instead of a markup tag to be used in
# regex expressions. safe_unicode doesn't work here
html_value = pat.sub(replace_html, unicode(value))
return value
def escaped_stylize(value):
"""
converts tags from value into html equivalent, but escaping its value first
"""
if not value:
return ''
# Using default webhelper escape method, but has to force it as a
# plain unicode instead of a markup tag to be used in regex expressions
value = unicode(escape(safe_unicode(value)))
value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
'<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
'<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
'<div class="metatag" tag="lang">\\2</div>', value)
value = re.sub(r'\[([a-z]+)\]',
'<div class="metatag" tag="\\1">\\1</div>', value)
return value
return html_value
def bool2icon(value):

View file

@ -220,12 +220,7 @@ class RepoModel(BaseModel):
cs_cache.get('message'))
def desc(desc):
if c.visual.stylify_metatags:
desc = h.urlify_text(h.escaped_stylize(desc))
else:
desc = h.urlify_text(h.html_escape(desc))
return _render('repo_desc', desc)
return _render('repo_desc', desc, c.visual.stylify_metatags)
def state(repo_state):
return _render("repo_state", repo_state)

View file

@ -694,14 +694,8 @@ class RepoGroupModel(BaseModel):
return _render("last_change", last_change)
def desc(desc, personal):
prefix = h.escaped_stylize(u'[personal] ') if personal else ''
if c.visual.stylify_metatags:
desc = h.urlify_text(prefix + h.escaped_stylize(desc))
else:
desc = h.urlify_text(prefix + h.html_escape(desc))
return _render('repo_group_desc', desc)
return _render(
'repo_group_desc', desc, personal, c.visual.stylify_metatags)
def repo_group_actions(repo_group_id, repo_group_name, gr_count):
return _render(

View file

@ -71,14 +71,31 @@
}
}
[tag="featured"] { &:extend(.tag1); }
[tag="stale"] { &:extend(.tag2); }
[tag="dead"] { &:extend(.tag3); }
[tag="lang"] { &:extend(.tag4); }
[tag="license"] { &:extend(.tag5); }
[tag="requires"] { &:extend(.tag6); }
[tag="recommends"] { &:extend(.tag7); }
[tag="generic"] { &:extend(.tag0); }
[tag="label"] { &:extend(.tag0); }
[tag="state featured"] { &:extend(.tag1); }
[tag="state dev"] { &:extend(.tag1); }
[tag="ref base"] { &:extend(.tag1); }
[tag="state stable"] { &:extend(.tag2); }
[tag="state stale"] { &:extend(.tag2); }
[tag="ref requires"] { &:extend(.tag3); }
[tag="state dead"] { &:extend(.tag4); }
[tag="ref conflicts"] { &:extend(.tag4); }
[tag="license"] { &:extend(.tag6); }
[tag="lang"] { &:extend(.tag7); }
[tag="language"] { &:extend(.tag7); }
[tag="ref recommends"] { &:extend(.tag7); }
[tag="see"] { &:extend(.tag8); }
[tag="url"] { &:extend(.tag8); }
.perm_overriden {
text-decoration: line-through;

View file

@ -46,6 +46,12 @@
</div>
<div class="textarea editor">
${h.textarea('group_description',cols=23,rows=5,class_="medium")}
<% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
<span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
<span id="meta-tags-desc" style="display: none">
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
${dt.metatags_help()}
</span>
</div>
</div>

View file

@ -43,6 +43,12 @@
</div>
<div class="textarea text-area editor">
${h.textarea('group_description',cols=23,rows=5,class_="medium")}
<% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
<span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
<span id="meta-tags-desc" style="display: none">
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
${dt.metatags_help()}
</span>
</div>
</div>

View file

@ -44,7 +44,12 @@ ${h.secure_form(h.route_path('repo_create'), method='POST', request=request)}
</div>
<div class="textarea editor">
${h.textarea('repo_description')}
<span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
<% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
<span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
<span id="meta-tags-desc" style="display: none">
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
${dt.metatags_help()}
</span>
</div>
</div>
<div class="field">

View file

@ -129,7 +129,13 @@
<div class="textarea text-area editor">
${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n}
${c.form.render_error(request, c.form['repo_description'])|n}
<p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
<% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
<span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
<span id="meta-tags-desc" style="display: none">
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
${dt.metatags_help()}
</span>
</div>
</div>

View file

@ -63,21 +63,10 @@ ${h.secure_form(h.url('admin_settings_visual'), method='post')}
${h.checkbox('rhodecode_stylify_metatags','True')}
<label for="rhodecode_stylify_metatags">${_('Stylify recognised meta tags')}</label>
</div>
<span class="help-block">${_('Parses meta tags from repository description field and turns them into colored tags.')}</span>
<span class="help-block">${_('Parses meta tags from repository or repository group description fields and turns them into colored tags.')}</span>
<div>
<table>
<tr><td>[featured] </td><td><span class="metatag" tag="featured">featured</span></td></tr>
<tr><td>[stale] </td><td><span class="metatag" tag="stale">stale</span></td></tr>
<tr><td>[dead] </td><td><span class="metatag" tag="dead">dead</span></td></tr>
<tr><td>[personal] </td><td><span class="metatag" tag="personal">personal</span></td></tr>
<tr><td>[lang =&gt; lang] </td><td><span class="metatag" tag="lang" >lang</span></td></tr>
<tr><td>[license =&gt; License] </td><td><span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></td></tr>
<tr><td>[requires =&gt; Repo] </td><td><span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></td></tr>
<tr><td>[recommends =&gt; Repo] </td><td><span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></td></tr>
<tr><td>[see =&gt; URI] </td><td><span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></td></tr>
</table>
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
${dt.metatags_help()}
</div>
</div>
</div>

View file

@ -3,6 +3,39 @@
## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
<%namespace name="base" file="/base/base.mako"/>
<%def name="metatags_help()">
<table>
<%
example_tags = [
('state','[stable]'),
('state','[stale]'),
('state','[featured]'),
('state','[dev]'),
('state','[dead]'),
('label','[personal]'),
('generic','[v2.0.0]'),
('lang','[lang =&gt; JavaScript]'),
('license','[license =&gt; LicenseName]'),
('ref','[requires =&gt; RepoName]'),
('ref','[recommends =&gt; GroupName]'),
('ref','[conflicts =&gt; SomeName]'),
('ref','[base =&gt; SomeName]'),
('url','[url =&gt; [linkName](https://rhodecode.com)]'),
('see','[see =&gt; http://rhodecode.com]'),
]
%>
% for tag_type, tag in example_tags:
<tr>
<td>${tag|n}</td>
<td>${h.style_metatag(tag_type, tag)|n}</td>
</tr>
% endfor
</table>
</%def>
## REPOSITORY RENDERERS
<%def name="quick_menu(repo_name)">
<i class="icon-more"></i>
@ -74,8 +107,20 @@
</div>
</%def>
<%def name="repo_desc(description)">
<div class="truncate-wrap">${description}</div>
<%def name="repo_desc(description, stylify_metatags)">
<%
tags, description = h.extract_metatags(description)
%>
<div class="truncate-wrap">
% if stylify_metatags:
% for tag_type, tag in tags:
${h.style_metatag(tag_type, tag)|n}
% endfor
% endif
${description}
</div>
</%def>
<%def name="last_change(last_change)">
@ -168,8 +213,25 @@
</div>
</%def>
<%def name="repo_group_desc(description)">
<div class="truncate-wrap">${description}</div>
<%def name="repo_group_desc(description, personal, stylify_metatags)">
<%
tags, description = h.extract_metatags(description)
%>
<div class="truncate-wrap">
% if personal:
<div class="metatag" tag="personal">${_('personal')}</div>
% endif
% if stylify_metatags:
% for tag_type, tag in tags:
${h.style_metatag(tag_type, tag)|n}
% endfor
% endif
${description}
</div>
</%def>
<%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">

View file

@ -49,7 +49,12 @@
</div>
<div class="textarea-repo textarea text-area editor">
${h.textarea('description')}
<span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
<% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
<span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
<span id="meta-tags-desc" style="display: none">
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
${dt.metatags_help()}
</span>
</div>
</div>

View file

@ -82,11 +82,10 @@
${_('Description')}:
</div>
<div class="right-content">
%if c.visual.stylify_metatags:
<div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div>
%else:
<div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div>
%endif
<div class="input ${summary(c.show_stats)}">
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
</div>
</div>
</div>

View file

@ -186,31 +186,115 @@ def test_age_in_future(age_args, expected, kw, pylonsapp):
assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
def test_tag_exctrator():
sample = (
"hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
"[requires] [stale] [see<>=>] [see => http://url.com]"
"[requires => url] [lang => python] [just a tag] <html_tag first='abc' attr=\"my.url?attr=&another=\"></html_tag>"
"[,d] [ => ULR ] [obsolete] [desc]]"
)
from rhodecode.lib.helpers import desc_stylize, escaped_stylize
res = desc_stylize(sample)
assert '<div class="metatag" tag="tag">tag</div>' in res
assert '<div class="metatag" tag="obsolete">obsolete</div>' in res
assert '<div class="metatag" tag="stale">stale</div>' in res
assert '<div class="metatag" tag="lang">python</div>' in res
assert '<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res
assert '<div class="metatag" tag="tag">tag</div>' in res
assert '<html_tag first=\'abc\' attr=\"my.url?attr=&another=\"></html_tag>' in res
@pytest.mark.parametrize("sample, expected_tags", [
((
"hello world [stale]"
),
[
('state', '[stale]'),
]),
# entry
((
"hello world [v2.0.0] [v1.0.0]"
),
[
('generic', '[v2.0.0]'),
('generic', '[v1.0.0]'),
]),
# entry
((
"he[ll]o wo[rl]d"
),
[
('label', '[ll]'),
('label', '[rl]'),
]),
# entry
((
"hello world [stale]\n[featured]\n[stale] [dead] [dev]"
),
[
('state', '[stale]'),
('state', '[featured]'),
('state', '[stale]'),
('state', '[dead]'),
('state', '[dev]'),
]),
# entry
((
"hello world \n\n [stale] \n [url =&gt; [name](http://rc.com)]"
),
[
('state', '[stale]'),
('url', '[url =&gt; [name](http://rc.com)]'),
]),
# entry
((
"hello pta[tag] gog [[]] [[] sda ero[or]d [me =&gt;>< sa]"
"[requires] [stale] [see<>=&gt;] [see =&gt; http://url.com]"
"[requires =&gt; url] [lang =&gt; python] [just a tag] "
"<html_tag first='abc' attr=\"my.url?attr=&another=\"></html_tag>"
"[,d] [ =&gt; ULR ] [obsolete] [desc]]"
),
[
('label', '[desc]'),
('label', '[obsolete]'),
('label', '[or]'),
('label', '[requires]'),
('label', '[tag]'),
('state', '[stale]'),
('lang', '[lang =&gt; python]'),
('ref', '[requires =&gt; url]'),
('see', '[see =&gt; http://url.com]'),
res_encoded = escaped_stylize(sample)
assert '<div class="metatag" tag="tag">tag</div>' in res_encoded
assert '<div class="metatag" tag="obsolete">obsolete</div>' in res_encoded
assert '<div class="metatag" tag="stale">stale</div>' in res_encoded
assert '<div class="metatag" tag="lang">python</div>' in res_encoded
assert '<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res_encoded
assert '<div class="metatag" tag="tag">tag</div>' in res_encoded
assert '&lt;html_tag first=&#39;abc&#39; attr=&#34;my.url?attr=&amp;another=&#34;&gt;&lt;/html_tag&gt;' in res_encoded
]),
], ids=no_newline_id_generator)
def test_metatag_extraction(sample, expected_tags):
from rhodecode.lib.helpers import extract_metatags
tags, value = extract_metatags(sample)
assert sorted(tags) == sorted(expected_tags)
@pytest.mark.parametrize("tag_data, expected_html", [
(('state', '[stable]'), '<div class="metatag" tag="state stable">stable</div>'),
(('state', '[stale]'), '<div class="metatag" tag="state stale">stale</div>'),
(('state', '[featured]'), '<div class="metatag" tag="state featured">featured</div>'),
(('state', '[dev]'), '<div class="metatag" tag="state dev">dev</div>'),
(('state', '[dead]'), '<div class="metatag" tag="state dead">dead</div>'),
(('label', '[personal]'), '<div class="metatag" tag="label">personal</div>'),
(('generic', '[v2.0.0]'), '<div class="metatag" tag="generic">v2.0.0</div>'),
(('lang', '[lang =&gt; JavaScript]'), '<div class="metatag" tag="lang">JavaScript</div>'),
(('lang', '[lang =&gt; C++]'), '<div class="metatag" tag="lang">C++</div>'),
(('lang', '[lang =&gt; C#]'), '<div class="metatag" tag="lang">C#</div>'),
(('lang', '[lang =&gt; Delphi/Object]'), '<div class="metatag" tag="lang">Delphi/Object</div>'),
(('lang', '[lang =&gt; Objective-C]'), '<div class="metatag" tag="lang">Objective-C</div>'),
(('lang', '[lang =&gt; .NET]'), '<div class="metatag" tag="lang">.NET</div>'),
(('license', '[license =&gt; BSD 3-clause]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/BSD 3-clause">BSD 3-clause</a></div>'),
(('license', '[license =&gt; GPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/GPLv3">GPLv3</a></div>'),
(('license', '[license =&gt; MIT]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/MIT">MIT</a></div>'),
(('license', '[license =&gt; AGPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/AGPLv3">AGPLv3</a></div>'),
(('ref', '[requires =&gt; RepoName]'), '<div class="metatag" tag="ref requires">requires =&gt; <a href="/RepoName">RepoName</a></div>'),
(('ref', '[recommends =&gt; GroupName]'), '<div class="metatag" tag="ref recommends">recommends =&gt; <a href="/GroupName">GroupName</a></div>'),
(('ref', '[conflicts =&gt; SomeName]'), '<div class="metatag" tag="ref conflicts">conflicts =&gt; <a href="/SomeName">SomeName</a></div>'),
(('ref', '[base =&gt; SomeName]'), '<div class="metatag" tag="ref base">base =&gt; <a href="/SomeName">SomeName</a></div>'),
(('see', '[see =&gt; http://rhodecode.com]'), '<div class="metatag" tag="see">see =&gt; http://rhodecode.com </div>'),
(('url', '[url =&gt; [linkName](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">linkName</a> </div>'),
(('url', '[url =&gt; [example link](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">example link</a> </div>'),
(('url', '[url =&gt; [v1.0.0](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">v1.0.0</a> </div>'),
])
def test_metatags_stylize(tag_data, expected_html):
from rhodecode.lib.helpers import style_metatag
tag_type,value = tag_data
assert style_metatag(tag_type, value) == expected_html
@pytest.mark.parametrize("tmpl_url, email, expected", [