From c4993a7f0ce4b553a9a1a58b9cf580c32903bfe1 Mon Sep 17 00:00:00 2001 From: "Yury V. Zaytsev" Date: Wed, 24 Feb 2021 08:43:12 +0100 Subject: [PATCH 01/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 339af0b..9347a3d 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ convert("[Winston Smith|~accountid:internal-id] woke up with the word 'Shakespea |`{{monospaced}}`|`` `monospaced` ``| |`bq. Some block quoted text`|`> Some block quoted text`| |`{quote}Content to be quoted{quote}`|`> Content to be quoted`| -|`{color:red}red text!{color}`|`red text!`| +|`{color:red}red text!{color}`|`red text!`| ## Text Breaks From cde083471245c65f189fab91a0f9101c74c25684 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Wed, 24 Feb 2021 18:18:44 +0300 Subject: [PATCH 02/13] Add citation conversion Close #4 --- README.md | 2 +- jira2markdown/markup/text_effects.py | 19 +++++++++++++++++++ jira2markdown/parser.py | 5 +++-- tests/markup/test_mixed_content.py | 6 ++++++ tests/markup/test_text_effects.py | 24 ++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9347a3d..7b998b7 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ convert("[Winston Smith|~accountid:internal-id] woke up with the word 'Shakespea |------|----------| |`*strong*`|`**strong**`| |`_emphasis_`|Not converted (the same syntax)| -|`??citation??`|Not converted| +|`??citation??`|`citation`| |`-deleted-`|`~~deleted~~`| |`+inserted+`|`inserted`| |`^superscript^`|Not converted| diff --git a/jira2markdown/markup/text_effects.py b/jira2markdown/markup/text_effects.py index 782495e..a546ef0 100644 --- a/jira2markdown/markup/text_effects.py +++ b/jira2markdown/markup/text_effects.py @@ -63,6 +63,25 @@ def expr(self) -> ParserElement: ).setParseAction(self.action) + WordEnd() +class InlineQuote: + def __init__(self, markup: Forward): + self.markup = markup + + def action(self, tokens: ParseResults) -> str: + return "" + self.markup.transformString(tokens[0]) + "" + + @property + def expr(self) -> ParserElement: + TOKEN = Suppress("??") + IGNORE = White() + TOKEN | Color(self.markup).expr + return WordStart() + Combine( + TOKEN + + ~White() + + SkipTo(TOKEN, ignore=IGNORE, failOn="\n") + + TOKEN, + ).setParseAction(self.action) + WordEnd() + + class Color: def __init__(self, markup: Forward): self.markup = markup diff --git a/jira2markdown/parser.py b/jira2markdown/parser.py index 1ee9197..4bd176f 100644 --- a/jira2markdown/parser.py +++ b/jira2markdown/parser.py @@ -9,8 +9,8 @@ from jira2markdown.markup.lists import OrderedList, UnorderedList from jira2markdown.markup.tables import Table from jira2markdown.markup.text_breaks import LineBreak, Mdash, Ndash, Ruler -from jira2markdown.markup.text_effects import BlockQuote, Bold, Color, EscSpecialChars, Monospaced, Quote, \ - Strikethrough, Underline +from jira2markdown.markup.text_effects import BlockQuote, Bold, Color, EscSpecialChars, InlineQuote, Monospaced, \ + Quote, Strikethrough, Underline ParserElement.setDefaultWhitespaceChars(" \t") @@ -39,6 +39,7 @@ def convert(text: str, usernames: Optional[dict] = None) -> str: Ruler().expr | \ Strikethrough(markup).expr | \ Underline(markup).expr | \ + InlineQuote(markup).expr | \ Color(markup).expr | \ LineBreak().expr | \ EscSpecialChars().expr diff --git a/tests/markup/test_mixed_content.py b/tests/markup/test_mixed_content.py index 34e58a2..67242c6 100644 --- a/tests/markup/test_mixed_content.py +++ b/tests/markup/test_mixed_content.py @@ -25,6 +25,12 @@ def test_underline_color_underline(self): assert convert("+text {color:blue}contains+ token{color} outside+") == \ 'text contains+ token outside' + def test_inlinequote_color_inlinequote(self): + assert convert("??text {color:blue}??text inside??{color} outside??") == \ + 'text text inside outside' + assert convert("??text {color:blue}contains?? token{color} outside??") == \ + 'text contains?? token outside' + class TestTableContent: def test_basic_markup(self): diff --git a/tests/markup/test_text_effects.py b/tests/markup/test_text_effects.py index 37dbb0d..a41585c 100644 --- a/tests/markup/test_text_effects.py +++ b/tests/markup/test_text_effects.py @@ -83,6 +83,30 @@ def test_single_token(self): assert convert("single +char") == "single +char" +class TestInlineQuote: + def test_basic_conversion(self): + assert convert("inside ??some long?? text") == "inside some long text" + + def test_line_endings(self): + assert convert("??start string end??") == "start string end" + assert convert("\n??start line end??\n") == "\nstart line end\n" + + def test_match_start_conditions(self): + assert convert("no ?? space after start??") == "no ?? space after start??" + assert convert("word??connector?? markup") == "word??connector?? markup" + + def test_match_end_conditions(self): + assert convert("??underline ??") == "??underline ??" + assert convert("??word??connector") == "??word??connector" + assert convert("??skip ??spacing ?? char??") == "skip ??spacing ?? char" + + def test_multiline(self): + assert convert("??multiline\nunderline??") == "??multiline\nunderline??" + + def test_single_token(self): + assert convert("single ??char") == "single ??char" + + class TestColor: def test_color_value(self): assert convert("start {color:#0077ff}hex color{color} text") == \ From 6b86dc846af2f14cb1bb3dd641a2ed8350f87696 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Wed, 24 Feb 2021 18:37:23 +0300 Subject: [PATCH 03/13] Update dependencies --- poetry.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 55abcce..b311c73 100644 --- a/poetry.lock +++ b/poetry.lock @@ -88,7 +88,7 @@ flake8 = "*" [[package]] name = "importlib-metadata" -version = "3.4.0" +version = "3.6.0" description = "Read metadata from Python packages" category = "dev" optional = false @@ -120,7 +120,7 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.8" +version = "20.9" description = "Core utilities for Python packages" category = "dev" optional = false @@ -263,8 +263,8 @@ flake8-quotes = [ {file = "flake8-quotes-3.2.0.tar.gz", hash = "sha256:3f1116e985ef437c130431ac92f9b3155f8f652fda7405ac22ffdfd7a9d1055e"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"}, - {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"}, + {file = "importlib_metadata-3.6.0-py3-none-any.whl", hash = "sha256:144acbe6ec34f170c4a362f52c2d03e82db7d012668ba57ff8f1e668905889b0"}, + {file = "importlib_metadata-3.6.0.tar.gz", hash = "sha256:080d666daa2faedcc2e96c0c87d3cdb76325261c51650016784834733faa7897"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -275,8 +275,8 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] packaging = [ - {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, - {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, From 925141f2f184d5eb132e9c7f4428a54c69421dfe Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Thu, 25 Feb 2021 21:28:58 +0300 Subject: [PATCH 04/13] #2 Add superscript conversion --- README.md | 2 +- jira2markdown/markup/text_effects.py | 20 ++++++++++++++++++++ jira2markdown/parser.py | 3 ++- tests/markup/test_mixed_content.py | 18 ++++++++++++++---- tests/markup/test_text_effects.py | 28 ++++++++++++++++++++-------- 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7b998b7..f0ebde9 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ convert("[Winston Smith|~accountid:internal-id] woke up with the word 'Shakespea |`??citation??`|`citation`| |`-deleted-`|`~~deleted~~`| |`+inserted+`|`inserted`| -|`^superscript^`|Not converted| +|`^superscript^`|`superscript`| |`~subscript~`|Not converted| |`{{monospaced}}`|`` `monospaced` ``| |`bq. Some block quoted text`|`> Some block quoted text`| diff --git a/jira2markdown/markup/text_effects.py b/jira2markdown/markup/text_effects.py index a546ef0..01f0015 100644 --- a/jira2markdown/markup/text_effects.py +++ b/jira2markdown/markup/text_effects.py @@ -2,6 +2,7 @@ ParserElement, QuotedString, SkipTo, StringStart, Suppress, White, Word, WordEnd, WordStart, alphanums, alphas, \ hexnums, nums, replaceWith +from jira2markdown.markup.links import Attachment from jira2markdown.tokens import NotUnicodeAlphaNum @@ -82,6 +83,25 @@ def expr(self) -> ParserElement: ).setParseAction(self.action) + WordEnd() +class Superscript: + def __init__(self, markup: Forward): + self.markup = markup + + def action(self, tokens: ParseResults) -> str: + return "" + self.markup.transformString(tokens[0]) + "" + + @property + def expr(self) -> ParserElement: + TOKEN = Suppress("^") + IGNORE = White() + TOKEN | Color(self.markup).expr | Attachment().expr + return WordStart() + Combine( + TOKEN + + ~White() + + SkipTo(TOKEN, ignore=IGNORE, failOn="\n") + + TOKEN, + ).setParseAction(self.action) + WordEnd() + + class Color: def __init__(self, markup: Forward): self.markup = markup diff --git a/jira2markdown/parser.py b/jira2markdown/parser.py index 4bd176f..18dc0b2 100644 --- a/jira2markdown/parser.py +++ b/jira2markdown/parser.py @@ -10,7 +10,7 @@ from jira2markdown.markup.tables import Table from jira2markdown.markup.text_breaks import LineBreak, Mdash, Ndash, Ruler from jira2markdown.markup.text_effects import BlockQuote, Bold, Color, EscSpecialChars, InlineQuote, Monospaced, \ - Quote, Strikethrough, Underline + Quote, Strikethrough, Superscript, Underline ParserElement.setDefaultWhitespaceChars(" \t") @@ -40,6 +40,7 @@ def convert(text: str, usernames: Optional[dict] = None) -> str: Strikethrough(markup).expr | \ Underline(markup).expr | \ InlineQuote(markup).expr | \ + Superscript(markup).expr | \ Color(markup).expr | \ LineBreak().expr | \ EscSpecialChars().expr diff --git a/tests/markup/test_mixed_content.py b/tests/markup/test_mixed_content.py index 67242c6..b89a5af 100644 --- a/tests/markup/test_mixed_content.py +++ b/tests/markup/test_mixed_content.py @@ -7,30 +7,40 @@ def test_ruler(self): class TestRecursiveContent: - def test_bold_color_bold(self): + def test_bold_color(self): assert convert("*text {color:red}*text inside*{color} outside*") == \ '**text **text inside** outside**' assert convert("*text {color:red}contains* token{color} outside*") == \ r'**text contains\* token outside**' - def test_strikethrough_color_strikethrough(self): + def test_strikethrough_color(self): assert convert("-text {color:green}-text inside-{color} outside-") == \ '~~text ~~text inside~~ outside~~' assert convert("-text {color:green}contains- token{color} outside-") == \ '~~text contains- token outside~~' - def test_underline_color_underline(self): + def test_underline_color(self): assert convert("+text {color:blue}+text inside+{color} outside+") == \ 'text text inside outside' assert convert("+text {color:blue}contains+ token{color} outside+") == \ 'text contains+ token outside' - def test_inlinequote_color_inlinequote(self): + def test_inlinequote_color(self): assert convert("??text {color:blue}??text inside??{color} outside??") == \ 'text text inside outside' assert convert("??text {color:blue}contains?? token{color} outside??") == \ 'text contains?? token outside' + def test_superscript_color(self): + assert convert("^text {color:blue}^text inside^{color} outside^") == \ + 'text text inside outside' + assert convert("^text {color:blue}contains^ token{color} outside^") == \ + 'text contains^ token outside' + + def test_superscript_attachment(self): + assert convert("^text [^attachment.ext] outside^") == \ + "text [attachment.ext](attachment.ext) outside" + class TestTableContent: def test_basic_markup(self): diff --git a/tests/markup/test_text_effects.py b/tests/markup/test_text_effects.py index a41585c..966d7bb 100644 --- a/tests/markup/test_text_effects.py +++ b/tests/markup/test_text_effects.py @@ -55,9 +55,6 @@ def test_match_end_conditions(self): def test_multiline(self): assert convert("-multiline\nstrikethrough-") == "-multiline\nstrikethrough-" - def test_single_token(self): - assert convert("single -char") == "single -char" - class TestUnderline: def test_basic_conversion(self): @@ -79,9 +76,6 @@ def test_match_end_conditions(self): def test_multiline(self): assert convert("+multiline\nunderline+") == "+multiline\nunderline+" - def test_single_token(self): - assert convert("single +char") == "single +char" - class TestInlineQuote: def test_basic_conversion(self): @@ -103,8 +97,26 @@ def test_match_end_conditions(self): def test_multiline(self): assert convert("??multiline\nunderline??") == "??multiline\nunderline??" - def test_single_token(self): - assert convert("single ??char") == "single ??char" + +class TestSuperscript: + def test_basic_conversion(self): + assert convert("inside ^some long^ text") == "inside some long text" + + def test_line_endings(self): + assert convert("^start string end^") == "start string end" + assert convert("\n^start line end^\n") == "\nstart line end\n" + + def test_match_start_conditions(self): + assert convert("no ^ space after start^") == "no ^ space after start^" + assert convert("word^connector^ markup") == "word^connector^ markup" + + def test_match_end_conditions(self): + assert convert("^underline ^") == "^underline ^" + assert convert("^word^connector") == "^word^connector" + assert convert("^skip ^spacing ^ char^") == "skip ^spacing ^ char" + + def test_multiline(self): + assert convert("^multiline\nunderline^") == "^multiline\nunderline^" class TestColor: From 72958892a7c9b3de0ff91b785b327c53c68972c8 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Thu, 25 Feb 2021 21:37:17 +0300 Subject: [PATCH 05/13] #2 Add subscript conversion --- README.md | 2 +- jira2markdown/markup/text_effects.py | 21 +++++++++++++++++++- jira2markdown/parser.py | 3 ++- tests/markup/test_mixed_content.py | 9 +++++++++ tests/markup/test_text_effects.py | 29 ++++++++++++++++++++++++---- 5 files changed, 57 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f0ebde9..35c2482 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ convert("[Winston Smith|~accountid:internal-id] woke up with the word 'Shakespea |`-deleted-`|`~~deleted~~`| |`+inserted+`|`inserted`| |`^superscript^`|`superscript`| -|`~subscript~`|Not converted| +|`~subscript~`|`subscript`| |`{{monospaced}}`|`` `monospaced` ``| |`bq. Some block quoted text`|`> Some block quoted text`| |`{quote}Content to be quoted{quote}`|`> Content to be quoted`| diff --git a/jira2markdown/markup/text_effects.py b/jira2markdown/markup/text_effects.py index 01f0015..5e4fed1 100644 --- a/jira2markdown/markup/text_effects.py +++ b/jira2markdown/markup/text_effects.py @@ -2,7 +2,7 @@ ParserElement, QuotedString, SkipTo, StringStart, Suppress, White, Word, WordEnd, WordStart, alphanums, alphas, \ hexnums, nums, replaceWith -from jira2markdown.markup.links import Attachment +from jira2markdown.markup.links import Attachment, Mention from jira2markdown.tokens import NotUnicodeAlphaNum @@ -102,6 +102,25 @@ def expr(self) -> ParserElement: ).setParseAction(self.action) + WordEnd() +class Subscript: + def __init__(self, markup: Forward): + self.markup = markup + + def action(self, tokens: ParseResults) -> str: + return "" + self.markup.transformString(tokens[0]) + "" + + @property + def expr(self) -> ParserElement: + TOKEN = Suppress("~") + IGNORE = White() + TOKEN | Color(self.markup).expr | Mention({}).expr + return WordStart() + Combine( + TOKEN + + ~White() + + SkipTo(TOKEN, ignore=IGNORE, failOn="\n") + + TOKEN, + ).setParseAction(self.action) + WordEnd() + + class Color: def __init__(self, markup: Forward): self.markup = markup diff --git a/jira2markdown/parser.py b/jira2markdown/parser.py index 18dc0b2..a87d161 100644 --- a/jira2markdown/parser.py +++ b/jira2markdown/parser.py @@ -10,7 +10,7 @@ from jira2markdown.markup.tables import Table from jira2markdown.markup.text_breaks import LineBreak, Mdash, Ndash, Ruler from jira2markdown.markup.text_effects import BlockQuote, Bold, Color, EscSpecialChars, InlineQuote, Monospaced, \ - Quote, Strikethrough, Superscript, Underline + Quote, Strikethrough, Subscript, Superscript, Underline ParserElement.setDefaultWhitespaceChars(" \t") @@ -41,6 +41,7 @@ def convert(text: str, usernames: Optional[dict] = None) -> str: Underline(markup).expr | \ InlineQuote(markup).expr | \ Superscript(markup).expr | \ + Subscript(markup).expr | \ Color(markup).expr | \ LineBreak().expr | \ EscSpecialChars().expr diff --git a/tests/markup/test_mixed_content.py b/tests/markup/test_mixed_content.py index b89a5af..8c8a9e6 100644 --- a/tests/markup/test_mixed_content.py +++ b/tests/markup/test_mixed_content.py @@ -41,6 +41,15 @@ def test_superscript_attachment(self): assert convert("^text [^attachment.ext] outside^") == \ "text [attachment.ext](attachment.ext) outside" + def test_subscript_color(self): + assert convert("~text {color:blue}~text inside~{color} outside~") == \ + 'text text inside outside' + assert convert("~text {color:blue}contains~ token{color} outside~") == \ + 'text contains~ token outside' + + def test_subscript_mention(self): + assert convert("~text [~username] outside~") == "text @username outside" + class TestTableContent: def test_basic_markup(self): diff --git a/tests/markup/test_text_effects.py b/tests/markup/test_text_effects.py index 966d7bb..661f834 100644 --- a/tests/markup/test_text_effects.py +++ b/tests/markup/test_text_effects.py @@ -48,7 +48,7 @@ def test_match_start_conditions(self): assert convert("word-connector- markup") == "word-connector- markup" def test_match_end_conditions(self): - assert convert("-strikethrough -") == "-strikethrough -" + assert convert("-text -") == "-text -" assert convert("-word-connector") == "-word-connector" assert convert("-skip -spacing - chars-") == "~~skip -spacing - chars~~" @@ -69,7 +69,7 @@ def test_match_start_conditions(self): assert convert("word+connector+ markup") == "word+connector+ markup" def test_match_end_conditions(self): - assert convert("+underline +") == "+underline +" + assert convert("+text +") == "+text +" assert convert("+word+connector") == "+word+connector" assert convert("+skip +spacing + char+") == "skip +spacing + char" @@ -90,7 +90,7 @@ def test_match_start_conditions(self): assert convert("word??connector?? markup") == "word??connector?? markup" def test_match_end_conditions(self): - assert convert("??underline ??") == "??underline ??" + assert convert("??text ??") == "??text ??" assert convert("??word??connector") == "??word??connector" assert convert("??skip ??spacing ?? char??") == "skip ??spacing ?? char" @@ -111,7 +111,7 @@ def test_match_start_conditions(self): assert convert("word^connector^ markup") == "word^connector^ markup" def test_match_end_conditions(self): - assert convert("^underline ^") == "^underline ^" + assert convert("^text ^") == "^text ^" assert convert("^word^connector") == "^word^connector" assert convert("^skip ^spacing ^ char^") == "skip ^spacing ^ char" @@ -119,6 +119,27 @@ def test_multiline(self): assert convert("^multiline\nunderline^") == "^multiline\nunderline^" +class TestSubscript: + def test_basic_conversion(self): + assert convert("inside ~some long~ text") == "inside some long text" + + def test_line_endings(self): + assert convert("~start string end~") == "start string end" + assert convert("\n~start line end~\n") == "\nstart line end\n" + + def test_match_start_conditions(self): + assert convert("no ~ space after start~") == "no ~ space after start~" + assert convert("word~connector~ markup") == "word~connector~ markup" + + def test_match_end_conditions(self): + assert convert("~text ~") == "~text ~" + assert convert("~word~connector") == "~word~connector" + assert convert("~skip ~spacing ~ char~") == "skip ~spacing ~ char" + + def test_multiline(self): + assert convert("~multiline\nunderline~") == "~multiline\nunderline~" + + class TestColor: def test_color_value(self): assert convert("start {color:#0077ff}hex color{color} text") == \ From 06d7498b71af0830c9145c9d644394aa8bc3d1f4 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Fri, 26 Feb 2021 18:33:03 +0300 Subject: [PATCH 06/13] Replace NotUnicodeAlphaNum with PrecededBy element --- jira2markdown/markup/images.py | 7 ++++--- jira2markdown/markup/text_effects.py | 9 +++++---- jira2markdown/tokens.py | 25 ------------------------- 3 files changed, 9 insertions(+), 32 deletions(-) delete mode 100644 jira2markdown/tokens.py diff --git a/jira2markdown/markup/images.py b/jira2markdown/markup/images.py index 5b91bc4..e155a87 100644 --- a/jira2markdown/markup/images.py +++ b/jira2markdown/markup/images.py @@ -1,6 +1,7 @@ -from pyparsing import Combine, Optional, ParseResults, ParserElement, SkipTo, Word, printables +import re -from jira2markdown.tokens import NotUnicodeAlphaNum +from pyparsing import Combine, Optional, ParseResults, ParserElement, PrecededBy, Regex, SkipTo, StringStart, Word, \ + printables class Image: @@ -9,7 +10,7 @@ def action(self, tokens: ParseResults) -> str: @property def expr(self) -> ParserElement: - return NotUnicodeAlphaNum() + Combine( + return (StringStart() | PrecededBy(Regex(r"\W", flags=re.UNICODE), retreat=1)) + Combine( "!" + Word(printables + " ", min=3, excludeChars="|!").setResultsName("url") + Optional("|") diff --git a/jira2markdown/markup/text_effects.py b/jira2markdown/markup/text_effects.py index 5e4fed1..525d9f2 100644 --- a/jira2markdown/markup/text_effects.py +++ b/jira2markdown/markup/text_effects.py @@ -1,9 +1,10 @@ +import re + from pyparsing import CaselessLiteral, Char, Combine, Forward, LineEnd, Literal, Optional, ParseResults, \ - ParserElement, QuotedString, SkipTo, StringStart, Suppress, White, Word, WordEnd, WordStart, alphanums, alphas, \ - hexnums, nums, replaceWith + ParserElement, PrecededBy, QuotedString, Regex, SkipTo, StringStart, Suppress, White, Word, WordEnd, WordStart, \ + alphanums, alphas, hexnums, nums, replaceWith from jira2markdown.markup.links import Attachment, Mention -from jira2markdown.tokens import NotUnicodeAlphaNum class Bold: @@ -17,7 +18,7 @@ def action(self, tokens: ParseResults) -> str: def expr(self) -> ParserElement: TOKEN = Suppress("*") IGNORE = White() + TOKEN | Color(self.markup).expr - return NotUnicodeAlphaNum() + Combine( + return (StringStart() | PrecededBy(Regex(r"\W", flags=re.UNICODE), retreat=1)) + Combine( TOKEN + (~White() & ~TOKEN) + SkipTo(TOKEN, ignore=IGNORE, failOn=LineEnd()) diff --git a/jira2markdown/tokens.py b/jira2markdown/tokens.py deleted file mode 100644 index 6fc5cfc..0000000 --- a/jira2markdown/tokens.py +++ /dev/null @@ -1,25 +0,0 @@ -import re - -from pyparsing import ParseException, Token - - -class NotUnicodeAlphaNum(Token): - """ - Matches if current position is at the beginning of a line or - not preceded by unicode alpha numeric characters. - """ - - def __init__(self): - super().__init__() - - self.name = self.__class__.__name__ - self.mayReturnEmpty = True - self.mayIndexError = False - - self.pattern = re.compile(r"\w", re.UNICODE) - self.errmsg = "Not at the start of a line or preceded by alpha numeric characters" - - def parseImpl(self, instring, loc, doActions=True): - if (loc != 0) and self.pattern.match(instring[loc - 1]): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] From bcc0e01fc5ec424e4a3ac43ee693734334685181 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Fri, 26 Feb 2021 19:23:42 +0300 Subject: [PATCH 07/13] Remove StepBack expression by fixing StepTo expression for tables --- jira2markdown/expressions.py | 7 ------- jira2markdown/markup/tables.py | 5 ++--- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 jira2markdown/expressions.py diff --git a/jira2markdown/expressions.py b/jira2markdown/expressions.py deleted file mode 100644 index a2cf0cd..0000000 --- a/jira2markdown/expressions.py +++ /dev/null @@ -1,7 +0,0 @@ -from pyparsing import MatchFirst - - -class StepBack(MatchFirst): - def parseImpl(self, instring, loc, doActions=True): - loc, tokens = super().parseImpl(instring, loc, doActions) - return loc - 1, tokens diff --git a/jira2markdown/markup/tables.py b/jira2markdown/markup/tables.py index c91cd32..9598a86 100644 --- a/jira2markdown/markup/tables.py +++ b/jira2markdown/markup/tables.py @@ -1,7 +1,6 @@ from pyparsing import Forward, Group, LineEnd, LineStart, Literal, OneOrMore, Optional, ParseResults, ParserElement, \ SkipTo, StringEnd, ZeroOrMore -from jira2markdown.expressions import StepBack from jira2markdown.markup.images import Image from jira2markdown.markup.links import Link, MailTo, Mention @@ -39,10 +38,10 @@ def expr(self) -> ParserElement: NL = LineEnd().suppress() SEP = (Literal("||") | Literal("|")).suppress() ROW_BREAK = NL + SEP | NL + NL | StringEnd() - IGNORE = StepBack([Link(self.markup).expr, MailTo().expr, Image().expr, Mention({}).expr]) + IGNORE = Link(self.markup).expr | MailTo().expr | Image().expr | Mention({}).expr ROW = SEP + ZeroOrMore( - SkipTo(SEP, ignore=IGNORE, failOn=ROW_BREAK) + Optional(SEP), + SkipTo(SEP | ROW_BREAK, ignore=IGNORE) + Optional(SEP), stopOn=ROW_BREAK | NL + ~SEP, ) From 56a8fe6a5faf99e54981e12b120086c33c961a96 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Fri, 26 Feb 2021 19:54:28 +0300 Subject: [PATCH 08/13] Add a space before user mention if it adjacent to the previous text --- jira2markdown/markup/links.py | 6 +++--- tests/markup/test_links.py | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/jira2markdown/markup/links.py b/jira2markdown/markup/links.py index 30c274c..a243973 100644 --- a/jira2markdown/markup/links.py +++ b/jira2markdown/markup/links.py @@ -1,5 +1,5 @@ -from pyparsing import CaselessLiteral, Combine, Forward, Optional, ParseResults, ParserElement, SkipTo, Suppress,\ - Word, alphanums +from pyparsing import CaselessLiteral, Combine, Forward, Optional, ParseResults, ParserElement, PrecededBy, SkipTo, \ + StringStart, Suppress, White, Word, alphanums class MailTo: @@ -59,7 +59,7 @@ def action(self, tokens: ParseResults) -> str: @property def expr(self) -> ParserElement: - return Combine( + return (StringStart() | Optional(PrecededBy(White(), retreat=1), default=" ")) + Combine( "[" + Optional( SkipTo("|", failOn="]") + Suppress("|"), diff --git a/tests/markup/test_links.py b/tests/markup/test_links.py index e081456..2acdfd8 100644 --- a/tests/markup/test_links.py +++ b/tests/markup/test_links.py @@ -41,3 +41,6 @@ def test_prefix(self): def test_alias(self): assert convert("[Firstname Lastname|~accountid:100:internal-id]") == "@100:internal-id" assert convert("[Firstname Lastname|~accountid:100:internal-id]", self.USERNAMES) == "@elliot" + + def test_start_spacing(self): + assert convert("[~userA][~userB] [~userC]\t[~userD]\n[~userE]") == "@userA @userB @userC\t@userD\n@userE" From 92d66cc4796b4e296ccaf08dba770a7f094c371a Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Sat, 27 Feb 2021 11:14:41 +0300 Subject: [PATCH 09/13] Add a line break before and after a table if only it adjacent to a text --- jira2markdown/markup/tables.py | 11 ++++++----- tests/markup/test_mixed_content.py | 12 ++++++------ tests/markup/test_tables.py | 19 ++++++------------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/jira2markdown/markup/tables.py b/jira2markdown/markup/tables.py index 9598a86..8199086 100644 --- a/jira2markdown/markup/tables.py +++ b/jira2markdown/markup/tables.py @@ -1,5 +1,5 @@ -from pyparsing import Forward, Group, LineEnd, LineStart, Literal, OneOrMore, Optional, ParseResults, ParserElement, \ - SkipTo, StringEnd, ZeroOrMore +from pyparsing import Combine, Forward, Group, LineEnd, LineStart, Literal, OneOrMore, Optional, ParseResults, \ + ParserElement, SkipTo, StringEnd, StringStart, White, ZeroOrMore from jira2markdown.markup.images import Image from jira2markdown.markup.links import Link, MailTo, Mention @@ -31,7 +31,7 @@ def action(self, tokens: ParseResults) -> str: # Insert header delimiter after the first row output.insert(1, "|" + "-|" * max(max_columns_count, 1)) - return "\n" + "\n".join(output) + "\n" + return "\n".join(output) + "\n" @property def expr(self) -> ParserElement: @@ -45,6 +45,7 @@ def expr(self) -> ParserElement: stopOn=ROW_BREAK | NL + ~SEP, ) - return Optional(LineEnd(), default="\n") \ + EMPTY_LINE = Combine("\n" + White(" \t", min=0) + "\n") + return ((StringStart() + Optional("\n")) ^ Optional(EMPTY_LINE, default="\n")) \ + OneOrMore(LineStart() + Group(ROW) + NL).setParseAction(self.action) \ - + Optional(LineEnd(), default="\n") + + (StringEnd() | Optional(LineEnd(), default="\n")) diff --git a/tests/markup/test_mixed_content.py b/tests/markup/test_mixed_content.py index 8c8a9e6..5106b3b 100644 --- a/tests/markup/test_mixed_content.py +++ b/tests/markup/test_mixed_content.py @@ -54,17 +54,17 @@ def test_subscript_mention(self): class TestTableContent: def test_basic_markup(self): assert convert("| Table *bold header* and {color:red}colored title{color} |") == \ - '\n\n|Table **bold header** and colored title|\n|-|\n\n' + '|Table **bold header** and colored title|\n|-|\n' def test_cell_image(self): - assert convert("|!image.png|width=300!") == "\n\n|![image.png](image.png)|\n|-|\n\n" + assert convert("|!image.png|width=300!") == "|![image.png](image.png)|\n|-|\n" def test_cell_link(self): - assert convert("|[link|http://example.com]|") == "\n\n|[link](http://example.com)|\n|-|\n\n" + assert convert("|[link|http://example.com]|") == "|[link](http://example.com)|\n|-|\n" def test_cell_mailto(self): - assert convert("|[mailto:user@example.com]|") == "\n\n||\n|-|\n\n" - assert convert("|[alias|mailto:user@example.com]|") == "\n\n||\n|-|\n\n" + assert convert("|[mailto:user@example.com]|") == "||\n|-|\n" + assert convert("|[alias|mailto:user@example.com]|") == "||\n|-|\n" def test_cell_mention(self): - assert convert("|[user|~uuid]|", {"uuid": "elliot"}) == "\n\n|@elliot|\n|-|\n\n" + assert convert("|[user|~uuid]|", {"uuid": "elliot"}) == "|@elliot|\n|-|\n" diff --git a/tests/markup/test_tables.py b/tests/markup/test_tables.py index dced661..22c5128 100644 --- a/tests/markup/test_tables.py +++ b/tests/markup/test_tables.py @@ -8,7 +8,6 @@ def test_basic_conversion(self): |cell 1-1|cell 1-2|cell 1-3| |cell 2-1|cell 2-2|cell 2-3| """) == """ - |header 1|header 2|header 3| |-|-|-| |cell 1-1|cell 1-2|cell 1-3| @@ -21,7 +20,6 @@ def test_mixed_column_separator(self): |cell 1-1|cell 1-2||cell 1-3| ||cell 2-1|cell 2-2|cell 2-3| """) == """ - |header 1|header 2|header 3| |-|-|-| |cell 1-1|cell 1-2|cell 1-3| @@ -34,7 +32,6 @@ def test_uneven_columns_count(self): |cell 1-1|cell 1-2|cell 1-3| |cell 2-1| """) == """ - |header 1|header 2|| |-|-|-| |cell 1-1|cell 1-2|cell 1-3| @@ -47,7 +44,6 @@ def test_open_end_row(self): |cell 1-1|cell 1-2 |cell 2-1 """) == """ - |header 1|header 2|header 3| |-|-|-| |cell 1-1|cell 1-2| @@ -55,13 +51,7 @@ def test_open_end_row(self): """ def test_smallest_table(self): - assert convert(""" -|header -""") == """ - -|header| -|-| -""" + assert convert("|header") == "|header|\n|-|\n" def test_multiline_text(self): assert convert(""" @@ -75,7 +65,6 @@ def test_multiline_text(self): end row """) == """ - |multi
line
header|| |-|-| |multi
line
row|sibling row| @@ -106,8 +95,12 @@ def test_empty_rows(self): | |end| """) == """ - |text| |-| |end| """ + + def test_empty_start_lines(self): + assert convert(" \n|header") == " \n|header|\n|-|\n" + assert convert(" \n \t \n|header") == " \n \t \n|header|\n|-|\n" + assert convert(" \n text \n|header") == " \n text \n\n|header|\n|-|\n" From d34e2b2bc31b2f6254b4e7ddfdfe52de00f15654 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Sat, 27 Feb 2021 15:37:50 +0300 Subject: [PATCH 10/13] Add a space after user mention if it adjacent to the text --- jira2markdown/markup/links.py | 11 +++++++---- tests/markup/test_links.py | 9 +++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/jira2markdown/markup/links.py b/jira2markdown/markup/links.py index a243973..4dd3d9b 100644 --- a/jira2markdown/markup/links.py +++ b/jira2markdown/markup/links.py @@ -1,5 +1,5 @@ -from pyparsing import CaselessLiteral, Combine, Forward, Optional, ParseResults, ParserElement, PrecededBy, SkipTo, \ - StringStart, Suppress, White, Word, alphanums +from pyparsing import CaselessLiteral, Combine, FollowedBy, Forward, Optional, ParseResults, ParserElement, \ + PrecededBy, SkipTo, StringEnd, StringStart, Suppress, White, Word, alphanums class MailTo: @@ -59,7 +59,7 @@ def action(self, tokens: ParseResults) -> str: @property def expr(self) -> ParserElement: - return (StringStart() | Optional(PrecededBy(White(), retreat=1), default=" ")) + Combine( + MENTION = Combine( "[" + Optional( SkipTo("|", failOn="]") + Suppress("|"), @@ -69,4 +69,7 @@ def expr(self) -> ParserElement: + Optional(CaselessLiteral("accountid:")) + Word(alphanums + ":-").setResultsName("accountid") + "]", - ).setParseAction(self.action) + ) + return (StringStart() | Optional(PrecededBy(White(), retreat=1), default=" ")) \ + + MENTION.setParseAction(self.action) \ + + (StringEnd() | Optional(FollowedBy(White() | MENTION), default=" ")) diff --git a/tests/markup/test_links.py b/tests/markup/test_links.py index 2acdfd8..1513934 100644 --- a/tests/markup/test_links.py +++ b/tests/markup/test_links.py @@ -42,5 +42,10 @@ def test_alias(self): assert convert("[Firstname Lastname|~accountid:100:internal-id]") == "@100:internal-id" assert convert("[Firstname Lastname|~accountid:100:internal-id]", self.USERNAMES) == "@elliot" - def test_start_spacing(self): - assert convert("[~userA][~userB] [~userC]\t[~userD]\n[~userE]") == "@userA @userB @userC\t@userD\n@userE" + def test_spacing(self): + assert convert("text[~userA]") == "text @userA" + assert convert("[~userA]text") == "@userA text" + assert convert("[~userA][~userB]") == "@userA @userB" + assert convert("[~userA] [~userB]") == "@userA @userB" + assert convert("[~userA]\t[~userB]") == "@userA\t@userB" + assert convert("[~userA]\n[~userB]") == "@userA\n@userB" From 49e499eb76a26ad7771ccc00952af59d11ed63c3 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Sat, 27 Feb 2021 17:58:19 +0300 Subject: [PATCH 11/13] Allow punctuation marks come after the user mention without spaces --- jira2markdown/markup/links.py | 7 +++++-- tests/markup/test_links.py | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/jira2markdown/markup/links.py b/jira2markdown/markup/links.py index 4dd3d9b..2b78ce6 100644 --- a/jira2markdown/markup/links.py +++ b/jira2markdown/markup/links.py @@ -1,4 +1,6 @@ -from pyparsing import CaselessLiteral, Combine, FollowedBy, Forward, Optional, ParseResults, ParserElement, \ +from string import punctuation + +from pyparsing import CaselessLiteral, Char, Combine, FollowedBy, Forward, Optional, ParseResults, ParserElement, \ PrecededBy, SkipTo, StringEnd, StringStart, Suppress, White, Word, alphanums @@ -72,4 +74,5 @@ def expr(self) -> ParserElement: ) return (StringStart() | Optional(PrecededBy(White(), retreat=1), default=" ")) \ + MENTION.setParseAction(self.action) \ - + (StringEnd() | Optional(FollowedBy(White() | MENTION), default=" ")) + + (StringEnd() + | Optional(FollowedBy(White() | Char(punctuation, excludeChars="[") | MENTION), default=" ")) diff --git a/tests/markup/test_links.py b/tests/markup/test_links.py index 1513934..316780d 100644 --- a/tests/markup/test_links.py +++ b/tests/markup/test_links.py @@ -49,3 +49,8 @@ def test_spacing(self): assert convert("[~userA] [~userB]") == "@userA @userB" assert convert("[~userA]\t[~userB]") == "@userA\t@userB" assert convert("[~userA]\n[~userB]") == "@userA\n@userB" + + def test_punctuation(self): + assert convert("[~userA].") == "@userA." + assert convert("[~userA]:") == "@userA:" + assert convert("[~userA]?") == "@userA?" From 57e926a658c2138a9350ca495651d6e6482134e7 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Sun, 28 Feb 2021 20:42:57 +0300 Subject: [PATCH 12/13] #3 Add panel conversion --- README.md | 12 ++++++++--- jira2markdown/markup/advanced.py | 34 +++++++++++++++++++++++++++++- jira2markdown/parser.py | 3 ++- tests/markup/test_advanced.py | 33 +++++++++++++++++++++++++++++ tests/markup/test_mixed_content.py | 14 ++++++++++++ 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 35c2482..4115c0f 100644 --- a/README.md +++ b/README.md @@ -260,12 +260,18 @@ preformatted piece of text ``` +{panel:title=My Title} +Some text with a title {panel} -Some text -{panel} ``` -Not supported + + +``` +> **My Title** +> Some text with a title +``` + diff --git a/jira2markdown/markup/advanced.py b/jira2markdown/markup/advanced.py index 4212d51..8232d8a 100644 --- a/jira2markdown/markup/advanced.py +++ b/jira2markdown/markup/advanced.py @@ -1,4 +1,5 @@ -from pyparsing import Combine, FollowedBy, Optional, ParseResults, ParserElement, QuotedString, SkipTo, Word, alphanums +from pyparsing import Combine, FollowedBy, Forward, Group, Literal, OneOrMore, Optional, ParseResults, ParserElement, \ + QuotedString, SkipTo, Suppress, Word, alphanums, alphas class Noformat: @@ -30,3 +31,34 @@ def expr(self) -> ParserElement: + SkipTo("{code}").setResultsName("text") + "{code}", ).setParseAction(self.action) + + +class Panel: + def __init__(self, markup: Forward): + self.markup = markup + + def action(self, tokens: ParseResults) -> str: + text = self.markup.transformString(tokens.text.strip()) + + for param, value in tokens.get("params", []): + if param.lower() == "title": + text = f"**{value}**\n{text}" + + return "\n".join([f"> {line.lstrip()}" for line in text.splitlines()]) + + @property + def expr(self) -> ParserElement: + PARAM = Word(alphas) \ + + Suppress("=") \ + + SkipTo(Literal("|") | Literal("}")) \ + + Optional("|").suppress() + + return Combine( + "{panel" + + Optional( + ":" + OneOrMore(Group(PARAM), stopOn="}").setResultsName("params"), + ) + + "}" + + SkipTo("{panel}").setResultsName("text") + + "{panel}", + ).setParseAction(self.action) diff --git a/jira2markdown/parser.py b/jira2markdown/parser.py index a87d161..f106c17 100644 --- a/jira2markdown/parser.py +++ b/jira2markdown/parser.py @@ -2,7 +2,7 @@ from pyparsing import Forward, ParserElement -from jira2markdown.markup.advanced import Code, Noformat +from jira2markdown.markup.advanced import Code, Noformat, Panel from jira2markdown.markup.headings import Headings from jira2markdown.markup.images import Image from jira2markdown.markup.links import Attachment, Link, MailTo, Mention @@ -33,6 +33,7 @@ def convert(text: str, usernames: Optional[dict] = None) -> str: Headings().expr | \ Quote().expr | \ BlockQuote(markup).expr | \ + Panel(markup).expr | \ Bold(markup).expr | \ Ndash().expr | \ Mdash().expr | \ diff --git a/tests/markup/test_advanced.py b/tests/markup/test_advanced.py index 7d9ffa9..1c6bd78 100644 --- a/tests/markup/test_advanced.py +++ b/tests/markup/test_advanced.py @@ -61,3 +61,36 @@ def test_decorations(self): } ``` """ + + +class TestPanel: + def test_basic_conversion(self): + assert convert(""" +{panel} +Some text +{panel} +""") == """ +> Some text +""" + + def test_title(self): + assert convert(""" +{panel:title=My Title} +Some text with a title +{panel} +""") == """ +> **My Title** +> Some text with a title +""" + + def test_multiple_parameters(self): + assert convert(""" +{panel:borderStyle=dashed|borderColor=#ccc|title=My Title|titleBGColor=#F7D6C1|bgColor=#FFFFCE} +a block of text +surrounded with a panel +{panel} +""") == """ +> **My Title** +> a block of text +> surrounded with a panel +""" diff --git a/tests/markup/test_mixed_content.py b/tests/markup/test_mixed_content.py index 5106b3b..d0516c9 100644 --- a/tests/markup/test_mixed_content.py +++ b/tests/markup/test_mixed_content.py @@ -68,3 +68,17 @@ def test_cell_mailto(self): def test_cell_mention(self): assert convert("|[user|~uuid]|", {"uuid": "elliot"}) == "|@elliot|\n|-|\n" + + +class TestPanelContent: + def test_text_formatting(self): + assert convert(""" +{panel:title=My Title|borderStyle=dashed|borderColor=#ccc|titleBGColor=#F7D6C1|bgColor=#FFFFCE} +a block of text surrounded with a *panel* +line with !image.png|width=300! +{panel} +""") == """ +> **My Title** +> a block of text surrounded with a **panel** +> line with ![image.png](image.png) +""" From c0f2e0cf19d8255a7aac3c80aba478f761826b42 Mon Sep 17 00:00:00 2001 From: Evgeniy Krysanov Date: Sun, 28 Feb 2021 21:17:20 +0300 Subject: [PATCH 13/13] Bump version to 0.1.8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2423374..ce740a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jira2markdown" -version = "0.1.7" +version = "0.1.8" description = "Convert text from JIRA markup to Markdown using parsing expression grammars" authors = ["Evgeniy Krysanov "] readme = "README.md"