diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -928,52 +928,67 @@ def gravatar_with_user(request, author, 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\-\/\#\+\.]*)\]'), + '
\\2
')), + + ('see', (re.compile(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]'), + '
see => \\1
')), + + ('url', (re.compile(r'\[url\ \=\>\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((.*?)\)\]'), + '
\\1
')), + + ('license', (re.compile(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]'), + '
\\1
')), + + ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]'), + '
\\1 => \\2
')), + + ('state', (re.compile(r'\[(stable|featured|stale|dead|dev)\]'), + '
\\1
')), + + # label in grey + ('label', (re.compile(r'\[([a-z]+)\]'), + '
\\1
')), + + # generic catch all in grey + ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'), + '
\\1
')), +)) + + +def extract_metatags(value): """ - converts tags from value into html equivalent - - :param value: + Extract supported meta-tags from given text value """ if not value: return '' - value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', - '
see => \\1
', value) - value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', - '
\\1
', value) - value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]', - '
\\1 => \\2
', value) - value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]', - '
\\2
', value) - value = re.sub(r'\[([a-z]+)\]', - '
\\1
', value) + 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 value + return tags, value -def escaped_stylize(value): +def style_metatag(tag_type, value): """ - converts tags from value into html equivalent, but escaping its value first + converts tags from value into html equivalent """ 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))) + 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)) - value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', - '
see => \\1
', value) - value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', - '
\\1
', value) - value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]', - '
\\1 => \\2
', value) - value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]', - '
\\2
', value) - value = re.sub(r'\[([a-z]+)\]', - '
\\1
', value) - - return value + return html_value def bool2icon(value): diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -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) diff --git a/rhodecode/model/repo_group.py b/rhodecode/model/repo_group.py --- a/rhodecode/model/repo_group.py +++ b/rhodecode/model/repo_group.py @@ -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( diff --git a/rhodecode/public/css/tags.less b/rhodecode/public/css/tags.less --- a/rhodecode/public/css/tags.less +++ b/rhodecode/public/css/tags.less @@ -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; diff --git a/rhodecode/templates/admin/repo_groups/repo_group_add.mako b/rhodecode/templates/admin/repo_groups/repo_group_add.mako --- a/rhodecode/templates/admin/repo_groups/repo_group_add.mako +++ b/rhodecode/templates/admin/repo_groups/repo_group_add.mako @@ -46,6 +46,12 @@
${h.textarea('group_description',cols=23,rows=5,class_="medium")} + <% metatags_url = h.literal('''meta-tags''') %> + ${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n} +
diff --git a/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako b/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako --- a/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako +++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako @@ -43,6 +43,12 @@
${h.textarea('group_description',cols=23,rows=5,class_="medium")} + <% metatags_url = h.literal('''meta-tags''') %> + ${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n} +
diff --git a/rhodecode/templates/admin/repos/repo_add_base.mako b/rhodecode/templates/admin/repos/repo_add_base.mako --- a/rhodecode/templates/admin/repos/repo_add_base.mako +++ b/rhodecode/templates/admin/repos/repo_add_base.mako @@ -44,7 +44,12 @@
${h.textarea('repo_description')} - ${_('Keep it short and to the point. Use a README file for longer descriptions.')} + <% metatags_url = h.literal('''meta-tags''') %> + ${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n} +
diff --git a/rhodecode/templates/admin/repos/repo_edit_settings.mako b/rhodecode/templates/admin/repos/repo_edit_settings.mako --- a/rhodecode/templates/admin/repos/repo_edit_settings.mako +++ b/rhodecode/templates/admin/repos/repo_edit_settings.mako @@ -129,7 +129,13 @@
${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n} ${c.form.render_error(request, c.form['repo_description'])|n} -

${_('Keep it short and to the point. Use a README file for longer descriptions.')}

+ + <% metatags_url = h.literal('''meta-tags''') %> + ${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n} +
diff --git a/rhodecode/templates/admin/settings/settings_visual.mako b/rhodecode/templates/admin/settings/settings_visual.mako --- a/rhodecode/templates/admin/settings/settings_visual.mako +++ b/rhodecode/templates/admin/settings/settings_visual.mako @@ -63,21 +63,10 @@ ${h.checkbox('rhodecode_stylify_metatags','True')} - ${_('Parses meta tags from repository description field and turns them into colored tags.')} + ${_('Parses meta tags from repository or repository group description fields and turns them into colored tags.')}
- - - - - - - - - - - - -
[featured] featured
[stale] stale
[dead] dead
[personal] personal
[lang => lang] lang
[license => License] License
[requires => Repo] requires => Repo
[recommends => Repo] recommends => Repo
[see => URI] see => URI
+ <%namespace name="dt" file="/data_table/_dt_elements.mako"/> + ${dt.metatags_help()}
diff --git a/rhodecode/templates/data_table/_dt_elements.mako b/rhodecode/templates/data_table/_dt_elements.mako --- a/rhodecode/templates/data_table/_dt_elements.mako +++ b/rhodecode/templates/data_table/_dt_elements.mako @@ -3,6 +3,39 @@ ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/> <%namespace name="base" file="/base/base.mako"/> +<%def name="metatags_help()"> + + <% + example_tags = [ + ('state','[stable]'), + ('state','[stale]'), + ('state','[featured]'), + ('state','[dev]'), + ('state','[dead]'), + + ('label','[personal]'), + ('generic','[v2.0.0]'), + + ('lang','[lang => JavaScript]'), + ('license','[license => LicenseName]'), + + ('ref','[requires => RepoName]'), + ('ref','[recommends => GroupName]'), + ('ref','[conflicts => SomeName]'), + ('ref','[base => SomeName]'), + ('url','[url => [linkName](https://rhodecode.com)]'), + ('see','[see => http://rhodecode.com]'), + ] + %> + % for tag_type, tag in example_tags: + + + + + % endfor +
${tag|n}${h.style_metatag(tag_type, tag)|n}
+ + ## REPOSITORY RENDERERS <%def name="quick_menu(repo_name)"> @@ -74,8 +107,20 @@ -<%def name="repo_desc(description)"> -
${description}
+<%def name="repo_desc(description, stylify_metatags)"> + <% + tags, description = h.extract_metatags(description) + %> + +
+ % if stylify_metatags: + % for tag_type, tag in tags: + ${h.style_metatag(tag_type, tag)|n} + % endfor + % endif + ${description} +
+ <%def name="last_change(last_change)"> @@ -168,8 +213,25 @@ -<%def name="repo_group_desc(description)"> -
${description}
+<%def name="repo_group_desc(description, personal, stylify_metatags)"> + + <% + tags, description = h.extract_metatags(description) + %> + +
+ % if personal: +
${_('personal')}
+ % endif + + % if stylify_metatags: + % for tag_type, tag in tags: + ${h.style_metatag(tag_type, tag)|n} + % endfor + % endif + ${description} +
+ <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)"> diff --git a/rhodecode/templates/forks/fork.mako b/rhodecode/templates/forks/fork.mako --- a/rhodecode/templates/forks/fork.mako +++ b/rhodecode/templates/forks/fork.mako @@ -49,7 +49,12 @@
${h.textarea('description')} - ${_('Keep it short and to the point. Use a README file for longer descriptions.')} + <% metatags_url = h.literal('''meta-tags''') %> + ${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n} +
diff --git a/rhodecode/templates/summary/components.mako b/rhodecode/templates/summary/components.mako --- a/rhodecode/templates/summary/components.mako +++ b/rhodecode/templates/summary/components.mako @@ -82,11 +82,10 @@ ${_('Description')}:
- %if c.visual.stylify_metatags: -
${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}
- %else: -
${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}
- %endif +
+ <%namespace name="dt" file="/data_table/_dt_elements.mako"/> + ${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)} +
diff --git a/rhodecode/tests/lib/test_libs.py b/rhodecode/tests/lib/test_libs.py --- a/rhodecode/tests/lib/test_libs.py +++ b/rhodecode/tests/lib/test_libs.py @@ -186,31 +186,115 @@ def test_age_in_future(age_args, expecte 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] " - "[,d] [ => ULR ] [obsolete] [desc]]" - ) - from rhodecode.lib.helpers import desc_stylize, escaped_stylize - res = desc_stylize(sample) - assert '
tag
' in res - assert '
obsolete
' in res - assert '
stale
' in res - assert '
python
' in res - assert '
requires => url
' in res - assert '
tag
' in res - assert '' 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 => [name](http://rc.com)]" + ), + [ + ('state', '[stale]'), + ('url', '[url => [name](http://rc.com)]'), + ]), + # entry + (( + "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]" + "[requires] [stale] [see<>=>] [see => http://url.com]" + "[requires => url] [lang => python] [just a tag] " + "" + "[,d] [ => ULR ] [obsolete] [desc]]" + ), + [ + ('label', '[desc]'), + ('label', '[obsolete]'), + ('label', '[or]'), + ('label', '[requires]'), + ('label', '[tag]'), + ('state', '[stale]'), + ('lang', '[lang => python]'), + ('ref', '[requires => url]'), + ('see', '[see => http://url.com]'), - res_encoded = escaped_stylize(sample) - assert '
tag
' in res_encoded - assert '
obsolete
' in res_encoded - assert '
stale
' in res_encoded - assert '
python
' in res_encoded - assert '
requires => url
' in res_encoded - assert '
tag
' in res_encoded - assert '<html_tag first='abc' attr="my.url?attr=&another="></html_tag>' 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]'), '
stable
'), + (('state', '[stale]'), '
stale
'), + (('state', '[featured]'), '
featured
'), + (('state', '[dev]'), '
dev
'), + (('state', '[dead]'), '
dead
'), + + (('label', '[personal]'), '
personal
'), + (('generic', '[v2.0.0]'), '
v2.0.0
'), + + (('lang', '[lang => JavaScript]'), '
JavaScript
'), + (('lang', '[lang => C++]'), '
C++
'), + (('lang', '[lang => C#]'), '
C#
'), + (('lang', '[lang => Delphi/Object]'), '
Delphi/Object
'), + (('lang', '[lang => Objective-C]'), '
Objective-C
'), + (('lang', '[lang => .NET]'), '
.NET
'), + + (('license', '[license => BSD 3-clause]'), '
BSD 3-clause
'), + (('license', '[license => GPLv3]'), '
GPLv3
'), + (('license', '[license => MIT]'), '
MIT
'), + (('license', '[license => AGPLv3]'), '
AGPLv3
'), + + (('ref', '[requires => RepoName]'), '
requires => RepoName
'), + (('ref', '[recommends => GroupName]'), '
recommends => GroupName
'), + (('ref', '[conflicts => SomeName]'), '
conflicts => SomeName
'), + (('ref', '[base => SomeName]'), '
base => SomeName
'), + + (('see', '[see => http://rhodecode.com]'), '
see => http://rhodecode.com
'), + + (('url', '[url => [linkName](https://rhodecode.com)]'), '
linkName
'), + (('url', '[url => [example link](https://rhodecode.com)]'), '
example link
'), + (('url', '[url => [v1.0.0](https://rhodecode.com)]'), '
v1.0.0
'), + +]) +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", [