Skip to content

Commit f7ad3fc

Browse files
Paul-Ilyinilya-khadykin
authored andcommitted
markdown: Add exercise to resolve exercism#729
1 parent e75cc4b commit f7ad3fc

5 files changed

Lines changed: 240 additions & 0 deletions

File tree

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,14 @@
869869
"conditionals"
870870
]
871871
},
872+
{
873+
"uuid": "1818f134-0ed9-e680-9b29-45ffd2b3344b0f841c7",
874+
"slug": "markdown",
875+
"difficulty": 3,
876+
"topics": [
877+
"refactoring"
878+
]
879+
},
872880
{
873881
"uuid": "e348a307-078c-5280-65af-a159283d4e79438b755",
874882
"slug": "forth",

exercises/markdown/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Markdown
2+
3+
The markdown exercise is a refactoring exercise. There is code that parses a
4+
given string with [Markdown
5+
syntax](https://guides.github.com/features/mastering-markdown/) and returns the
6+
associated HTML for that string. Even though this code is confusingly written
7+
and hard to follow, somehow it works and all the tests are passing! Your
8+
challenge is to re-write this code to make it easier to read and maintain
9+
while still making sure that all the tests keep passing.
10+
11+
It would be helpful if you made notes of what you did in your refactoring in
12+
comments so reviewers can see that, but it isn't strictly necessary. The most
13+
important thing is to make the code better!
14+
15+
### Submitting Exercises
16+
17+
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
18+
19+
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
20+
21+
22+
For more detailed information about running tests, code style and linting,
23+
please see the [help page](http://exercism.io/languages/python).
24+
25+
## Source
26+
27+
Syntax [https://guides.github.com/features/mastering-markdown/](https://guides.github.com/features/mastering-markdown/)
28+
29+
## Submitting Incomplete Solutions
30+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

exercises/markdown/example.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import re
2+
3+
4+
def parse_markdown(markdown):
5+
lines = markdown.split('\n')
6+
html = ''
7+
in_list = False
8+
for line in lines:
9+
res = parse_line(line, in_list)
10+
html += res['line']
11+
in_list = res['in_list']
12+
if in_list:
13+
html += '</ul>'
14+
return html
15+
16+
17+
def wrap(line, tag):
18+
return '<{tag}>{line}</{tag}>'.format(line=line, tag=tag)
19+
20+
21+
def check_headers(line):
22+
pattern = '# (.*)'
23+
for i in range(6):
24+
if re.match(pattern, line):
25+
return wrap(line[(i + 2):], 'h' + str(i + 1))
26+
pattern = '#' + pattern
27+
return line
28+
29+
30+
def check_bold(line):
31+
bold_pattern = '(.*)__(.*)__(.*)'
32+
bold_match = re.match(bold_pattern, line)
33+
if bold_match:
34+
return bold_match.group(1) + wrap(bold_match.group(2), 'strong')\
35+
+ bold_match.group(3)
36+
else:
37+
return None
38+
39+
40+
def check_italic(line):
41+
italic_pattern = '(.*)_(.*)_(.*)'
42+
italic_match = re.match(italic_pattern, line)
43+
if italic_match:
44+
return italic_match.group(1) + wrap(italic_match.group(2), 'em')\
45+
+ italic_match.group(3)
46+
else:
47+
return None
48+
49+
50+
def parse_line(line, in_list):
51+
res = check_headers(line)
52+
53+
list_match = re.match(r'\* (.*)', res)
54+
55+
if (list_match):
56+
if not in_list:
57+
res = '<ul>' + wrap(list_match.group(1), 'li')
58+
in_list = True
59+
else:
60+
res = wrap(list_match.group(1), 'li')
61+
else:
62+
if in_list:
63+
res += '</ul>'
64+
in_list = False
65+
66+
if not re.match('<h|<ul|<li', res):
67+
res = wrap(res, 'p')
68+
69+
if not re.match('(<ul>)?<li>_(.*)', res):
70+
res = re.sub('(.*)(<li>)(.*)(</li>)(.*)', r'\1\2<p>\3</p>\4\5', res)
71+
72+
while check_bold(res):
73+
res = check_bold(res)
74+
while check_italic(res):
75+
res = check_italic(res)
76+
77+
return {
78+
'line': res,
79+
'in_list': in_list
80+
}

exercises/markdown/markdown.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import re
2+
3+
4+
def parse_markdown(markdown):
5+
lines = markdown.split('\n')
6+
res = ''
7+
in_list = False
8+
for i in lines:
9+
if re.match('###### (.*)', i) is not None:
10+
i = '<h6>' + i[7:] + '</h6>'
11+
elif re.match('## (.*)', i) is not None:
12+
i = '<h2>' + i[3:] + '</h2>'
13+
elif re.match('# (.*)', i) is not None:
14+
i = '<h1>' + i[2:] + '</h1>'
15+
m = re.match(r'\* (.*)', i)
16+
if m:
17+
if not in_list:
18+
in_list = True
19+
is_bold = False
20+
is_italic = False
21+
curr = m.group(1)
22+
m1 = re.match('(.*)__(.*)__(.*)', curr)
23+
if m1:
24+
curr = m1.group(1) + '<strong>' + \
25+
m1.group(2) + '</strong>' + m1.group(3)
26+
is_bold = True
27+
m1 = re.match('(.*)_(.*)_(.*)', curr)
28+
if m1:
29+
curr = m1.group(1) + '<em>' + m1.group(2) + \
30+
'</em>' + m1.group(3)
31+
is_italic = True
32+
if is_italic or is_bold:
33+
i = '<ul><li>' + curr + '</li>'
34+
else:
35+
i = '<ul><li><p>' + curr + '</p></li>'
36+
else:
37+
is_bold = False
38+
is_italic = False
39+
curr = m.group(1)
40+
m1 = re.match('(.*)__(.*)__(.*)', curr)
41+
if m1:
42+
curr = m1.group(1) + '<strong>' + \
43+
m1.group(2) + '</strong>' + m1.group(3)
44+
is_bold = True
45+
m1 = re.match('(.*)_(.*)_(.*)', curr)
46+
if m1:
47+
curr = m1.group(1) + '<em>' + m1.group(2) + \
48+
'</em>' + m1.group(3)
49+
is_italic = True
50+
if is_italic or is_bold:
51+
i = '<li>' + curr + '</li>'
52+
else:
53+
i = '<li><p>' + curr + '</p></li>'
54+
else:
55+
if in_list:
56+
i = '</ul>+i'
57+
in_list = False
58+
59+
m = re.match('<h|<ul|<p|<li', i)
60+
if not m:
61+
i = '<p>' + i + '</p>'
62+
m = re.match('(.*)__(.*)__(.*)', i)
63+
if m:
64+
i = m.group(1) + '<strong>' + m.group(2) + '</strong>' + m.group(3)
65+
m = re.match('(.*)_(.*)_(.*)', i)
66+
if m:
67+
i = m.group(1) + '<em>' + m.group(2) + '</em>' + m.group(3)
68+
res += i
69+
if in_list:
70+
res += '</ul>'
71+
return res
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import unittest
2+
from markdown import parse_markdown
3+
4+
5+
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
6+
7+
8+
class TestMarkdown(unittest.TestCase):
9+
10+
def test_paragraph(self):
11+
self.assertEqual(parse_markdown('This will be a paragraph'),
12+
'<p>This will be a paragraph</p>')
13+
14+
def test_italics(self):
15+
self.assertEqual(parse_markdown('_This will be italic_'),
16+
'<p><em>This will be italic</em></p>')
17+
18+
def test_bold(self):
19+
self.assertEqual(parse_markdown('__This will be bold__'),
20+
'<p><strong>This will be bold</strong></p>')
21+
22+
def test_mixed(self):
23+
self.assertEqual(parse_markdown('This will _be_ __mixed__'),
24+
'<p>This will <em>be</em> <strong>mixed</strong></p>')
25+
26+
def test_h1(self):
27+
self.assertEqual(parse_markdown('# This will be an h1'),
28+
'<h1>This will be an h1</h1>')
29+
30+
def test_h2(self):
31+
self.assertEqual(parse_markdown('## This will be an h2'),
32+
'<h2>This will be an h2</h2>')
33+
34+
def test_h6(self):
35+
self.assertEqual(parse_markdown(
36+
'###### This will be an h6'), '<h6>This will be an h6</h6>')
37+
38+
def test_unordered_lists(self):
39+
self.assertEqual(parse_markdown('* Item 1\n* Item 2'),
40+
'<ul><li><p>Item 1</p></li>'
41+
'<li><p>Item 2</p></li></ul>')
42+
43+
def test_little_bit_of_everything(self):
44+
self.assertEqual(parse_markdown(
45+
'# Header!\n* __Bold Item__\n* _Italic Item_'),
46+
'<h1>Header!</h1><ul><li><strong>Bold Item</strong></li>'
47+
'<li><em>Italic Item</em></li></ul>')
48+
49+
50+
if __name__ == '__main__':
51+
unittest.main()

0 commit comments

Comments
 (0)