Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Markdown #51

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ include tox.ini .coveragerc
recursive-include tests *.html
recursive-include tests *.py
recursive-include tests *.rst
recursive-include tests *.md
recursive-include tests *.txt
29 changes: 29 additions & 0 deletions readme_renderer/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2017 Julien Palard
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, division, print_function

import mdorrst

from .rst import render as rst_renderer
from .markdown import render as md_renderer
from .txt import render as txt_renderer

RENDERERS = {'rst': rst_renderer,
'md': md_renderer,
'txt': txt_renderer}


def render(raw):
sniffed_format = mdorrst.sniff(raw)
return RENDERERS[sniffed_format](raw) or txt_renderer(raw)
34 changes: 34 additions & 0 deletions readme_renderer/markdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2014 Donald Stufft
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, division, print_function

import markdown

from .clean import clean


def render(raw):
try:
rendered = markdown.markdown(
raw,
extensions=[
'markdown.extensions.codehilite',
'markdown.extensions.fenced_code',
'markdown.extensions.smart_strong',
])
except ValueError:
# Markdown failed to strip top-level tags.
return None
else:
return clean(rendered)
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@
],

install_requires=[
"markdown>=2.6.8",
"bleach>=2.0.0",
"docutils>=0.13.1",
"Pygments",
"six",
"mdorrst>=0.4.0"
],

entry_points={
Expand Down
16 changes: 16 additions & 0 deletions tests/fixtures/markdown/code.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<p>Here is some Python:</p>
<div><pre><span></span><span class="kn">import</span> <span class="nn">os</span>

<span class="k">def</span> <span class="nf">read</span><span class="p">(</span><span class="n">fn</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">open</span><span class="p">(</span><span class="n">fn</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</pre></div>


<p>And some more with the commonly used short <em>py</em> mark:</p>
<div><pre><span></span><span class="ch">#!/usr/bin/env python3</span>

<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">setuptools</span>

<span class="n">base_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span>
</pre></div>
19 changes: 19 additions & 0 deletions tests/fixtures/markdown/code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Here is some Python:

```python
Copy link

@AraHaan AraHaan Apr 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to ```py. This is a shortcut for ```python.

Copy link
Author

@JulienPalard JulienPalard Apr 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it's a test fixture I really don't see the point of changing this. If I touch it, maybe I can add a new block with py so the test ensure both are working?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, many people use py for shorthand anyway as it saves them some typing so I think adding that block anyway would be great.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's probably not really the responsibility of the readme_renderer tests to validate this, but it may not hurt to add it so we're also ensuring that this particular tag, probably the most used in this context, still work, so I added it.

import os

def read(fn):
return open(fn).read()
```

And some more with the commonly used short *py* mark:

```py
#!/usr/bin/env python3

import os
import setuptools

base_dir = os.path.dirname(__file__)
```
4 changes: 4 additions & 0 deletions tests/fixtures/markdown/headings_and_paragraphs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<h1>Required packages</h1>
<p>To run the PyPI software, you need Python 2.5+ and PostgreSQL</p>
<h1>Quick development setup</h1>
<p>Make sure you ...</p>
6 changes: 6 additions & 0 deletions tests/fixtures/markdown/headings_and_paragraphs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Required packages

To run the PyPI software, you need Python 2.5+ and PostgreSQL

# Quick development setup
Make sure you ...
14 changes: 14 additions & 0 deletions tests/fixtures/markdown/misc.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<p>Then, you can create a <em>development environment</em> like this,
if you have <strong>virtualenv</strong> installed:</p>
<div><pre><span></span>$ virtualenv --no-site-packages .
$ pip install -r requirements.txt
</pre></div>


<p>Then you can launch the server using the <code>pypi.wsgi</code> script:</p>
<div><pre><span></span>$ python pypi.wsgi
Serving on port <span class="m">8000</span>...
</pre></div>


<p>PyPI will be available in your browser at http://localhost:8000</p>
12 changes: 12 additions & 0 deletions tests/fixtures/markdown/misc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Then, you can create a *development environment* like this,
if you have **virtualenv** installed:

$ virtualenv --no-site-packages .
$ pip install -r requirements.txt

Then you can launch the server using the `pypi.wsgi` script:

$ python pypi.wsgi
Serving on port 8000...

PyPI will be available in your browser at http://localhost:8000
45 changes: 45 additions & 0 deletions tests/test_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import io
import glob
import os.path

import pytest

from readme_renderer.base import render


@pytest.mark.parametrize(
("src_filename", "html_filename"),
[
(fn, os.path.splitext(fn)[0] + ".html")
for fn in glob.glob(
os.path.join(os.path.dirname(__file__),
"fixtures", "test_*.rst")
) + glob.glob(
os.path.join(os.path.dirname(__file__),
"fixtures", "markdown", "*.md")
)
],
)
def test_base_fixtures(src_filename, html_filename):
too_short = {
'misc.md',
'test_rst_004.rst',
'test_rst_005.rst',
'test_rst_006.rst',
'test_rst_007.rst'}
if os.path.basename(src_filename) in too_short:
"""sniff won't match in any cases for those files being too short
tests to be sniffed correctly (like just an "<a href", or just
a <script>alert("Hello")</script>) """
return
# Get our Markup
with io.open(src_filename, encoding='utf-8') as f:
markup = f.read()

# Get our expected
with io.open(html_filename, encoding="utf-8") as f:
expected = f.read()

out = render(markup)

assert out.rstrip() == expected.rstrip()
86 changes: 86 additions & 0 deletions tests/test_markdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import io
import os
import textwrap

from readme_renderer.markdown import render


def test_simple():
markdown_markup = 'Hello'
out = render(markdown_markup)
assert out == '<p>Hello</p>'


def test_url_no_link():
markdown_markup = 'http://mymalicioussite.com/'
out = render(markdown_markup)
expected_html = '<p>http://mymalicioussite.com/</p>'
assert out == expected_html


def test_iframe():
markdown_markup = """\
<iframe src="http://mymalicioussite.com/">Click here</iframe>
""".strip()
out = render(markdown_markup)
expected_html = ''.join([
'&lt;iframe src="http://mymalicioussite.com/"&gt;'
'Click here&lt;/iframe&gt;'])
assert out == expected_html


def test_script():
markdown_markup = textwrap.dedent("""\
<script>
alert("Hello");
</script>""")
out = render(markdown_markup)
expected_html = textwrap.dedent("""\
&lt;script&gt;
alert("Hello");
&lt;/script&gt;""")
assert out == expected_html


def test_a_tag_gets_nofollow():
markdown_markup = '<a href="http://mymalicioussite.com/">Click here</a>'
out = render(markdown_markup)
expected_htmls = [
''.join(['<p><a rel="nofollow" href="http://mymalicioussite.com/">',
'Click here</a></p>']),
''.join(['<p><a href="http://mymalicioussite.com/" rel="nofollow">',
'Click here</a></p>']),
]
assert out in expected_htmls


def test_smart_strong():
markdown_markup = 'Text with double__underscore__words.'
out = render(markdown_markup)
expected_html = '<p>Text with double__underscore__words.</p>'
assert out == expected_html


def test_github_backtick_syntax_for_code():
_do_test_with_files('code')


def test_headings_and_paragraphs():
_do_test_with_files('headings_and_paragraphs')


def test_misc():
_do_test_with_files('misc')


def _do_test_with_files(test_name):
md_markup = read('{0}.md'.format(test_name))
expected_html = read('{0}.html'.format(test_name))
out = render(md_markup)
assert out == expected_html.rstrip()


def read(fn):
path = os.path.join(os.path.dirname(__file__), 'fixtures', 'markdown', fn)
with io.open(path, encoding='utf-8') as f:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for using io here either.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, Python 2 compatibility, to give the encoding.

Copy link

@AraHaan AraHaan Apr 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, so we can change it later when python 2 is decided to be fully dropped?
Or maybe I could figure out how to port the encoding parameter to the builtin open function for the next version of python 2.7 so then this can be changed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm OK to change it later when python2 compat is dropped.

Look like pypi-legacy has an indeterminate lifespan, but if we pin a legacy version of readme_renderer in pypi-lecacy requirements, we'll be able to drop python2 compatibility. For the moment, I think it's better to keep with python2 compatibility so pypi-legacy can at least get markdown.

Copy link

@AraHaan AraHaan Apr 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, or port python 2's open function to accept `encoding`` as an paramiter like how python 3 does. I would have to figure out a patch for python 2 then to allow such. (because I only use python 3 now)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure backporting the encoding parameter to Python 2's open is worth the effort since Python 2 is end of life. Let's just stick, like everyone else, to a simple and temporary "import io".

Also, it will be easier to port Python 2 using io.open than Python 2 using open, as Python 3's open is io.open.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment here might be useful, mentioning that this a Py2 compatibility thing.

return f.read()