Skip to content

Commit

Permalink
Merge branch 'donot-change-case'
Browse files Browse the repository at this point in the history
* donot-change-case: (34 commits)
  Modify README according to @jonasbn's proposal
  Update changes
  Add test for 'lowercase' param
  Cleanup
  Update README
  Add 'lowercase' parameter
  Prepare for 2.6.0
  Add 'Maintain the images in headings' section on README
  Change param name
  Recovering TestDefault
  Remove 'test_' prefix from test files
  Divide test files with each attributes
  Fix regex
  Cleanup
  - Fix same name function - Fix don't remove image in codeblock
  Revert "Allow list in args of log"
  Allow list in args of log
  Cleanup
  - Use uppercase in patterns - Cleanup global consts
  Unify to function escape_brackets
  ...
  • Loading branch information
naokazuterada committed Sep 26, 2017
2 parents f7a9767 + f57428a commit 09ec4d8
Show file tree
Hide file tree
Showing 20 changed files with 915 additions and 512 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changes in MarkdownTOC
===========================

## 2.6.0

- Add `remove_image` parameter. Ref: #43
- Add `lowercase` parameter. Ref: #40

## 2.5.0

- Add `Customizable list bullets` feature
Expand Down
124 changes: 90 additions & 34 deletions MarkdownTOC.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,13 @@
# for dbug
pp = pprint.PrettyPrinter(indent=4)

pattern_reference_link = re.compile(r'\[.+?\]$') # [Heading][my-id]
pattern_link = re.compile(r'\[(.+?)\]\(.+?\)') # [link](http://www.sample.com/)
pattern_ex_id = re.compile(r'\{#.+?\}$') # [Heading]{#my-id}
pattern_tag = re.compile(r'<.*?>')
pattern_anchor = re.compile(r'<a\s+name="[^"]+"\s*>\s*</a>')
pattern_toc_tag_start = re.compile(r'<!-- *')
pattern_toc_tag_end = re.compile(r'-->')
PATTERN_REFERENCE_LINK = re.compile(r'\[.+?\]$') # [Heading][my-id]
PATTERN_IMAGE = re.compile(r'!\[([^\]]+)\]\([^\)]+\)') # ![alt](path/to/image.png)
PATTERN_EX_ID = re.compile(r'\{#.+?\}$') # [Heading]{#my-id}
PATTERN_TAG = re.compile(r'<.*?>')
PATTERN_ANCHOR = re.compile(r'<a\s+name="[^"]+"\s*>\s*</a>')
PATTERN_TOC_TAG_START = re.compile(r'<!-- *')

pattern_h1_h2_equal_dash = "^.*?(?:(?:\r\n)|\n|\r)(?:-+|=+)$"

TOCTAG_START = "<!-- MarkdownTOC -->"
TOCTAG_END = "<!-- /MarkdownTOC -->"

class MarkdowntocInsert(sublime_plugin.TextCommand):
Expand All @@ -34,7 +30,7 @@ def run(self, edit):
attrs = self.get_settings()

# add TOCTAG
toc = TOCTAG_START + "\n"
toc = "<!-- MarkdownTOC -->\n"
toc += "\n"
toc += self.get_toc(attrs, sel.end(), edit)
toc += "\n"
Expand Down Expand Up @@ -105,16 +101,40 @@ def find_tag_and_insert(self, edit):
return False

def escape_brackets(self, _text):
is_in_code = False
text = ''
for char in _text:
if char in ['(', ')', '[', ']'] and not is_in_code:
text += '\\' + char
else:
text += char
if char == '`':
is_in_code = not is_in_code
return text
# Escape brackets which not in image and codeblock

def do_escape(_text, _pattern, _open, _close):
images = []
brackets = []
codes = []
for m in re.compile(r'`[^`]*`').finditer(_text):
codes.append([m.start(), m.end()])
def not_in_codeblock(target):
return not within_ranges(target, codes)
def not_in_image(target):
return not within_ranges(target, images)
# Collect images not in codeblock
for m in PATTERN_IMAGE.finditer(_text):
images.append([m.start(), m.end()])
images = list(filter(not_in_codeblock, images))
# Collect brackets not in image tags
for m in _pattern.finditer(_text):
brackets.append([m.start(), m.end()])
brackets = list(filter(not_in_image, brackets))
brackets = list(filter(not_in_codeblock, brackets))
brackets = list(map((lambda x: x[0]), brackets))
# Escape brackets
def replace_brackets(m):
if m.start() in brackets:
return _open+m.group(1)+_close
else:
return m.group(0)
return re.sub(_pattern, replace_brackets, _text)

_text = do_escape(_text, re.compile(r'\[([^\]]*)\]'), '\[', '\]')
_text = do_escape(_text, re.compile(r'\(([^\)]*)\)'), '\(', '\)')

return _text

# TODO: add "end" parameter
def get_toc(self, attrs, begin, edit):
Expand Down Expand Up @@ -173,7 +193,9 @@ def heading_to_id(heading):
elif attrs['markdown_preview'] == 'markdown':
return slugify(heading, '-')
else:
if strtobool(attrs['lowercase_only_ascii']):
if not strtobool(attrs['lowercase']):
_id = heading
elif strtobool(attrs['lowercase_only_ascii']):
# only ascii
_id = ''.join(chr(ord(x)+('A'<=x<='Z')*32) for x in heading)
else:
Expand All @@ -192,8 +214,9 @@ def replace_strings_in_id(_str):

# Search headings in docment
pattern_hash = "^#+?[^#]"
headings = self.view.find_all(
"%s|%s" % (pattern_h1_h2_equal_dash, pattern_hash))
pattern_h1_h2_equal_dash = "^.*?(?:(?:\r\n)|\n|\r)(?:-+|=+)$"
pattern_heading = "%s|%s" % (pattern_h1_h2_equal_dash, pattern_hash)
headings = self.view.find_all(pattern_heading)

headings = self.remove_items_in_codeblock(headings)

Expand Down Expand Up @@ -238,19 +261,42 @@ def replace_strings_in_id(_str):
toc = ''
_ids = []
level_counters = [0]
remove_image = strtobool(attrs['remove_image'])
list_bullets = attrs['list_bullets']


for item in items:
_id = None
_indent = item[0] - 1
_text = item[1]
if remove_image:
# Remove markdown image which not in codeblock
images = []
codes = []
for m in re.compile(r'`[^`]*`').finditer(_text):
codes.append([m.start(), m.end()])
def not_in_codeblock(_target):
return not within_ranges(_target, codes)
# Collect images not in codeblock
for m in PATTERN_IMAGE.finditer(_text):
images.append([m.start(), m.end()])
images = list(filter(not_in_codeblock, images))
images = list(map((lambda x: x[0]), images))
def _replace(m):
if m.start() in images:
return ''
else:
return m.group(0)
_text = re.sub(PATTERN_IMAGE, _replace, _text)

_list_bullet = list_bullets[_indent%len(list_bullets)]
_text = pattern_tag.sub('', _text) # remove html tags
_text = _text.rstrip() # remove end space
_text = PATTERN_TAG.sub('', _text) # remove html tags
_text = _text.strip() # remove start and end spaces

# Ignore links: e.g. '[link](http://sample.com/)' -> 'link'
_text = pattern_link.sub('\\1', _text)
link = re.compile(r'([^!])\[([^\]]+)\]\([^\)]+\)') # this is [link](http://www.sample.com/)
_text = link.sub('\\1\\2', _text)
beginning_link = re.compile(r'^\[([^\]]+)\]\([^\)]+\)') # [link](http://www.sample.com/) link in the beginning of line
_text = beginning_link.sub('\\1', _text)

# Add indent
for i in range(_indent):
Expand All @@ -260,10 +306,10 @@ def replace_strings_in_id(_str):
toc += _prefix

# Reference-style links: e.g. '# heading [my-anchor]'
list_reference_link = list(pattern_reference_link.finditer(_text))
list_reference_link = list(PATTERN_REFERENCE_LINK.finditer(_text))

# Markdown-Extra special attribute style: e.g. '# heading {#my-anchor}'
match_ex_id = pattern_ex_id.search(_text)
match_ex_id = PATTERN_EX_ID.search(_text)

if len(list_reference_link):
match = list_reference_link[-1]
Expand Down Expand Up @@ -309,7 +355,7 @@ def update_anchors(self, edit, items, autoanchor):
# Iterate in reverse so that inserts don't affect the position
for item in reversed(items):
anchor_region = v.line(item[2] - 1) # -1 to get to previous line
is_update = pattern_anchor.match(v.substr(anchor_region))
is_update = PATTERN_ANCHOR.match(v.substr(anchor_region))
if autoanchor:
if is_update:
new_anchor = '<a name="{0}"></a>'.format(item[3])
Expand All @@ -333,8 +379,10 @@ def get_settings(self):
"autolink": self.get_setting('default_autolink'),
"bracket": self.get_setting('default_bracket'),
"depth": self.get_setting('default_depth'),
"remove_image": self.get_setting('default_remove_image'),
"indent": self.get_setting('default_indent'),
"list_bullets": self.get_setting('default_list_bullets'),
"lowercase": self.get_setting('default_lowercase'),
"lowercase_only_ascii": self.get_setting('default_lowercase_only_ascii'),
"style": self.get_setting('default_style'),
"uri_encoding": self.get_setting('default_uri_encoding'),
Expand All @@ -344,8 +392,8 @@ def get_settings(self):
def get_attibutes_from(self, tag_str):
"""return dict of settings from tag_str"""

tag_str_html = pattern_toc_tag_start.sub("<", tag_str)
tag_str_html = pattern_toc_tag_start.sub(">", tag_str_html)
tag_str_html = PATTERN_TOC_TAG_START.sub("<", tag_str)
tag_str_html = PATTERN_TOC_TAG_START.sub(">", tag_str_html)

soup = BeautifulSoup(tag_str_html, "html.parser")

Expand Down Expand Up @@ -413,7 +461,15 @@ def strtobool(val):
else:
return bool(val)


def within_ranges(target, ranges):
tb = target[0]
te = target[1]
for _range in ranges:
rb = _range[0]
re = _range[1]
if (rb <= tb and tb <= re) and (rb <= tb and tb <= re):
return True
return False
# Search and refresh if it's exist


Expand Down
2 changes: 2 additions & 0 deletions MarkdownTOC.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"default_bracket": "square",
"default_depth": 2,
"default_indent": "\t",
"default_remove_image": true,
"default_list_bullets": "-",
"default_lowercase": true,
"default_lowercase_only_ascii": true,
"default_style": "unordered",
"default_uri_encoding": true,
Expand Down
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ Sublime Text 3 plugin for generating a Table of Contents (TOC) in a Markdown doc
- [Auto anchoring when heading has anchor defined](#auto-anchoring-when-heading-has-anchor-defined)
- [Auto linking for _clickable_ TOC](#auto-linking-for-clickable-toc)
- [Lowercase only ASCII characters in auto link ids](#lowercase-only-ascii-characters-in-auto-link-ids)
- [Preserve case](#preserve-case)
- [Manipulation of auto link ids](#manipulation-of-auto-link-ids)
- [URI encoding](#uri-encoding)
- [Markdown Preview compatible](#markdown-preview-compatible)
- [Control of depth listed in TOC](#control-of-depth-listed-in-toc)
- [Ordered or unordered style for TOC elements](#ordered-or-unordered-style-for-toc-elements)
- [Customizable list bullets in TOC](#customizable-list-bullets-in-toc)
- [Specify custom indentation prefix](#specify-custom-indentation-prefix)
- [Preserve images in headings](#preserve-images-in-headings)
- [Usage](#usage)
- [Tips](#tips)
- [How to remove anchors added by MarkdownTOC](#how-to-remove-anchors-added-by-markdowntoc)
Expand Down Expand Up @@ -335,6 +337,22 @@ You can expand the lowercasing capability by setting the `lowecase_only_ascii` a
# ПРИМЕР EXAMPLE
```

#### Preserve case

You can disable the lowercasing capability by setting the `lowecase` attribute to `false`.

```markdown
<!-- MarkdownTOC autolink="true" lowercase="false" -->

- [One Two Three][One-Two-Three]

<!-- /MarkdownTOC -->

# One Two Three
```

You can also specify this in your [configuration](#configuration) with key `default_lowercase`.

#### Manipulation of auto link ids

You can manipulate your link ids in your [configuration](#configuration) using the key `id_replacements`.
Expand Down Expand Up @@ -616,6 +634,34 @@ Please note that the default for the [attribute](#attributes) is: `'\t'`.

You can set your default indentation in your [configuration](#configuration) with the key `default_indent`.

### Preserve images in headings

If you want to preserve images in headings, set `remove_image` to `false`.

```
<!-- MarkdownTOC remove_image="false" -->
- ![check](check.png) Everything is OK
<!-- /MarkdownTOC -->
# ![check](check.png) Everything is OK
```

Please note that the default for the [attribute](#attributes) is: `false`.

```
<!-- MarkdownTOC -->
- Everything is OK
<!-- /MarkdownTOC -->
# ![check](check.png) Everything is OK
```

You can change your default setting in your [configuration](#configuration) with the key `default_remove_image`.

## Usage

1. Open your [Markdown] file
Expand Down Expand Up @@ -697,7 +743,9 @@ The following attributes can be used to control the generation of the TOC.
| `depth` | integer (`0` means _no limit_) | `2` | `default_depth` |
| `indent` | string | `\t` | `default_indent` |
| `list_bullets` | string | `-` | `default_list_bullets` |
| `lowercase` | `true`or`false` | `true` | `default_lowercase` |
| `lowercase_only_ascii` | `true`or`false` | `true` | `default_lowercase_only_ascii` |
| `remove_image` | `true`or`false` | `true` | `default_remove_image` |
| `style` | `ordered` or `unordered` | `unordered` | `default_style` |
| `uri_encoding` | `true`or`false` | `true` | `default_uri_encoding` |
| `markdown_preview` | `false`or`github`or`markdown` | `false` | `default_markdown_preview` |
Expand Down Expand Up @@ -743,7 +791,9 @@ Example: `MarkdownTOC.sublime-settings`
"default_depth": 2,
"default_indent": "\t",
"default_list_bullets": "-",
"default_lowercase": true,
"default_lowercase_only_ascii": true,
"default_remove_image": true,
"default_style": "unordered",
"default_uri_encoding": true,
"default_markdown_preview": false,
Expand All @@ -769,7 +819,10 @@ For an overview of the specific behaviour behind an attribute, please refer to t
- `default_bracket`, (see: [Auto linking for _clickable_ TOC](#auto-linking-for-clickable-toc))
- `default_depth`, (see: [Control of depth listed in TOC](#control-of-depth-listed-in-toc))
- `default_indent`, (see: [Specify custom indentation prefix](#specify-custom-indentation-prefix))
- `default_list_bullets`, (see: [Customizable list bullets in TOC](#customizable-list-bullets-in-toc))
- `default_lowercase`, (see: [Preserve case](#preserve-case))
- `default_lowercase_only_ascii`, (see: [Lowercase only ASCII characters in auto link ids](#lowercase-only-ascii-characters-in-auto-link-ids))
- `remove_image`, (see: [Preserve images in headings](#maintain-the-images-in-headings))
- `default_style`, (see: [Ordered or unordered style for TOC elements](#ordered-or-unordered-style-for-toc-elements))
- `default_uri_encoding`, (see: [URI encoding](#uri-encoding))
- `default_markdown_preview`, (see: [Markdown Preview compatible](#markdown-preview-compatible))
Expand All @@ -783,6 +836,7 @@ A configuration for writing Markdown primaily for use on [Github] _could_ look l
{
"default_autolink": true,
"default_bracket": "round",
"default_lowercase": true,
"default_lowercase_only_ascii": true
}
```
Expand Down
6 changes: 6 additions & 0 deletions messages/2.6.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
MarkdownTOC - 2.6.0

CHANGES

- Add `remove_image` parameter. Ref: #43
- Add `lowercase` parameter. Ref: #40
41 changes: 41 additions & 0 deletions tests/autoanchor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# coding:utf-8
from base import TestBase
import sublime
import sys

class TestAutoanchor(TestBase):
"""Test of attributes 'autoanchor'"""

# for debug
# def tearDown(self):
# pass

autoanchor_text = \
"""
<!-- MarkdownTOC autolink=true {0} -->
<!-- /MarkdownTOC -->
# Changelog
# Glossary
# API Specification
"""
def test_autoanchor_false(self):
"""Default Auto Anchor is false"""
body_txt = self.commonSetupAndUpdateGetBody(self.autoanchor_text.format(''))
self.assert_NotIn('<a name="changelog"></a>', body_txt)
self.assert_NotIn('<a name="glossary"></a>', body_txt)
self.assert_NotIn('<a name="api-specification"></a>', body_txt)

def test_autoanchor_true(self):
body_txt = self.commonSetupAndUpdateGetBody(self.autoanchor_text.format('autoanchor=true'))
self.assert_In('<a name="changelog"></a>\n# Changelog', body_txt)
self.assert_In('<a name="glossary"></a>\n# Glossary', body_txt)
self.assert_In('<a name="api-specification"></a>\n# API Specification', body_txt)

def test_autoanchor_false(self):
body_txt = self.commonSetupAndUpdateGetBody(self.autoanchor_text.format('autoanchor=false'))
self.assert_NotIn('<a name="changelog"></a>', body_txt)
self.assert_NotIn('<a name="glossary"></a>', body_txt)
self.assert_NotIn('<a name="api-specification"></a>', body_txt)
Loading

0 comments on commit 09ec4d8

Please sign in to comment.