Python: markdown
This commit is contained in:
parent
399d240948
commit
a0535502eb
4 changed files with 178 additions and 0 deletions
1
python/markdown/.exercism/metadata.json
Normal file
1
python/markdown/.exercism/metadata.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"track":"python","exercise":"markdown","id":"4e53556c7e0a4cd4b08107bc26fdd6a3","url":"https://exercism.io/my/solutions/4e53556c7e0a4cd4b08107bc26fdd6a3","handle":"DmitryKokorin","is_requester":true,"auto_approve":false}
|
||||||
58
python/markdown/README.md
Normal file
58
python/markdown/README.md
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Markdown
|
||||||
|
|
||||||
|
Refactor a Markdown parser.
|
||||||
|
|
||||||
|
The markdown exercise is a refactoring exercise. There is code that parses a
|
||||||
|
given string with [Markdown
|
||||||
|
syntax](https://guides.github.com/features/mastering-markdown/) and returns the
|
||||||
|
associated HTML for that string. Even though this code is confusingly written
|
||||||
|
and hard to follow, somehow it works and all the tests are passing! Your
|
||||||
|
challenge is to re-write this code to make it easier to read and maintain
|
||||||
|
while still making sure that all the tests keep passing.
|
||||||
|
|
||||||
|
It would be helpful if you made notes of what you did in your refactoring in
|
||||||
|
comments so reviewers can see that, but it isn't strictly necessary. The most
|
||||||
|
important thing is to make the code better!
|
||||||
|
|
||||||
|
|
||||||
|
## Exception messages
|
||||||
|
|
||||||
|
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||||
|
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||||
|
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||||
|
a message.
|
||||||
|
|
||||||
|
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||||
|
`raise Exception`, you should write:
|
||||||
|
|
||||||
|
```python
|
||||||
|
raise Exception("Meaningful message indicating the source of the error")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
To run the tests, run `pytest markdown_test.py`
|
||||||
|
|
||||||
|
Alternatively, you can tell Python to run the pytest module:
|
||||||
|
`python -m pytest markdown_test.py`
|
||||||
|
|
||||||
|
### Common `pytest` options
|
||||||
|
|
||||||
|
- `-v` : enable verbose output
|
||||||
|
- `-x` : stop running tests on first failure
|
||||||
|
- `--ff` : run failures from previous test before running other test cases
|
||||||
|
|
||||||
|
For other options, see `python -m pytest -h`
|
||||||
|
|
||||||
|
## Submitting Exercises
|
||||||
|
|
||||||
|
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/markdown` directory.
|
||||||
|
|
||||||
|
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||||
|
|
||||||
|
For more detailed information about running tests, code style and linting,
|
||||||
|
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||||
|
|
||||||
|
## Submitting Incomplete Solutions
|
||||||
|
|
||||||
|
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||||
35
python/markdown/markdown.py
Normal file
35
python/markdown/markdown.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def parse(markdown):
|
||||||
|
res = ''
|
||||||
|
in_list = False
|
||||||
|
for line in markdown.splitlines():
|
||||||
|
|
||||||
|
match = re.match(r'^(#{1,6}) (.*)', line)
|
||||||
|
if match:
|
||||||
|
level = len(match.group(1))
|
||||||
|
res += f'<h{level}>{match.group(2)}</h{level}>'
|
||||||
|
continue
|
||||||
|
|
||||||
|
line = re.sub(r'__(.*)__', r'<strong>\1</strong>', line)
|
||||||
|
line = re.sub(r'_(.*)_', r'<em>\1</em>', line)
|
||||||
|
|
||||||
|
match = re.match(r'\* (.*)', line)
|
||||||
|
if match:
|
||||||
|
if not in_list:
|
||||||
|
in_list = True
|
||||||
|
res += '<ul>'
|
||||||
|
res += '<li>' + match.group(1) + '</li>'
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_list:
|
||||||
|
in_list = False
|
||||||
|
res += '</ul>'
|
||||||
|
|
||||||
|
res += '<p>' + line + '</p>'
|
||||||
|
|
||||||
|
if in_list:
|
||||||
|
res += '</ul>'
|
||||||
|
|
||||||
|
return res
|
||||||
84
python/markdown/markdown_test.py
Normal file
84
python/markdown/markdown_test.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from markdown import parse
|
||||||
|
|
||||||
|
# Tests adapted from `problem-specifications//canonical-data.json`
|
||||||
|
|
||||||
|
|
||||||
|
class MarkdownTest(unittest.TestCase):
|
||||||
|
def test_parses_normal_text_as_a_paragraph(self):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("This will be a paragraph"), "<p>This will be a paragraph</p>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parsing_italics(self):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("_This will be italic_"), "<p><em>This will be italic</em></p>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parsing_bold_text(self):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("__This will be bold__"), "<p><strong>This will be bold</strong></p>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_mixed_normal_italics_and_bold_text(self):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("This will _be_ __mixed__"),
|
||||||
|
"<p>This will <em>be</em> <strong>mixed</strong></p>",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_h1_header_level(self):
|
||||||
|
self.assertEqual(parse("# This will be an h1"), "<h1>This will be an h1</h1>")
|
||||||
|
|
||||||
|
def test_with_h2_header_level(self):
|
||||||
|
self.assertEqual(parse("## This will be an h2"), "<h2>This will be an h2</h2>")
|
||||||
|
|
||||||
|
def test_with_h6_header_level(self):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("###### This will be an h6"), "<h6>This will be an h6</h6>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_unordered_lists(self):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("* Item 1\n* Item 2"), "<ul><li>Item 1</li><li>Item 2</li></ul>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_a_little_bit_of_everything(self):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("# Header!\n* __Bold Item__\n* _Italic Item_"),
|
||||||
|
"<h1>Header!</h1><ul><li><strong>Bold Item</strong></li><li><em>Italic Item</em></li></ul>",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_markdown_symbols_in_the_header_text_that_should_not_be_interpreted(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("# This is a header with # and * in the text"),
|
||||||
|
"<h1>This is a header with # and * in the text</h1>",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_markdown_symbols_in_the_list_item_text_that_should_not_be_interpreted(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("* Item 1 with a # in the text\n* Item 2 with * in the text"),
|
||||||
|
"<ul><li>Item 1 with a # in the text</li><li>Item 2 with * in the text</li></ul>",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_markdown_symbols_in_the_paragraph_text_that_should_not_be_interpreted(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("This is a paragraph with # and * in the text"),
|
||||||
|
"<p>This is a paragraph with # and * in the text</p>",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_unordered_lists_close_properly_with_preceding_and_following_lines(self):
|
||||||
|
self.assertEqual(
|
||||||
|
parse("# Start a list\n* Item 1\n* Item 2\nEnd a list"),
|
||||||
|
"<h1>Start a list</h1><ul><li>Item 1</li><li>Item 2</li></ul><p>End a list</p>",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue