summaryrefslogblamecommitdiffstats
path: root/mdl_style.py
blob: eb6ed125391b1cd1e450dcf39c3c79aaabc32434 (plain) (tree)
1
2
3

                       
                                                       
















                                                                      
               
         
 

                                                                     



                                                    
 



                                    

                



                                                                     































                                                                        



                               
                                  

































                                                                      


                         
















                                                            




                                                                    














































                                                                
                           








                                  

                                                              








                                                         

                                                                 

                          




                                        

                                                 
                   




















                                              











                                                                 

                                    
                                       
 




                                                              
 

                         
                                      

                                                      
                                                                     
                                                    
                                                                 
                                                      







                                                                    
                                  

 
           

                      
# -*- coding: utf-8 -*-
#
#   Copyright © 2017 markdown-link-style contributors.
#
#    This file is part of markdown-link-style.
#
#   markdown-link-style is free software: you can redistribute it
#   and/or modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation, either version 3 of
#   the License, or (at your option) any later version.
#
#   markdown-link-style is distributed in the hope that it will be
#   useful, but WITHOUT ANY WARRANTY; without even the implied
#   warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#   See the GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with markdown-link-style (see COPYING).  If not, see
#   <http://www.gnu.org/licenses/>.

import argparse
import re

from mistune import (BlockGrammar, BlockLexer, InlineLexer, Renderer,
                     Markdown)

from markdown_link_style.logging import MDLSLogger
from markdown_link_style._version import __version__


# Initialize logger for this module.
logger = MDLSLogger(__name__)


# from mistune
_inline_tags = [
    'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr',
    'data', 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i',
    'b', 'u', 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br',
    'wbr', 'ins', 'del', 'img', 'font',
]
_valid_end = r'(?!:/|[^\w\s@]*@)\b'
_block_tag = r'(?!(?:%s)\b)\w+%s' % ('|'.join(_inline_tags), _valid_end)


def _pure_pattern(regex):
    """Function from mistune."""
    pattern = regex.pattern
    if pattern.startswith('^'):
        pattern = pattern[1:]
    return pattern


class LSBlockGrammar(BlockGrammar):

    def __init__(self):
        # remove list_block and block_quote from paragraph
        self.paragraph = re.compile(
            r'^((?:[^\n]+\n?(?!'
            r'%s|%s|%s|%s|%s|%s|%s'
            r'))+)\n*' % (
                _pure_pattern(self.fences).replace(r'\1', r'\2'),
                _pure_pattern(self.hrule),
                _pure_pattern(self.heading),
                _pure_pattern(self.lheading),
                _pure_pattern(self.def_links),
                _pure_pattern(self.def_footnotes),
                '<' + _block_tag,
            )
        )


class LSBlockLexer(BlockLexer):
    """Link Style Block Lexer.

    """
    grammar_class = LSBlockGrammar

    def __init__(self, rules=None, **kwargs):
        super(LSBlockLexer, self).__init__(rules, **kwargs)

        # Only parse these block rules.
        self.default_rules = ['def_links', 'paragraph', 'text']


class LSInlineLexer(InlineLexer):
    """Link Style Inline Lexer.

    """

    def __init__(self, renderer, rules=None, **kwargs):
        super(LSInlineLexer, self).__init__(renderer, rules, **kwargs)

        # Only parse these inline rules
        self.default_rules = ['autolink', 'link', 'reflink', 'text']


class LSRenderer(Renderer):
    """Link Style Renderer.

    """

    def __init__(self, **kwargs):
        super(LSRenderer, self).__init__(**kwargs)

        # Link style is either 'inline' or 'footnote'.
        self.link_style = self.options.get('link_style')

        self.fn_lnk_num = 0 # footnote style link number
        self.fn_lnk_refs = [] # footnote style link refs

    def text(self, text):
        return text

    def autolink(self, link, is_email=False):
        return '<{}>'.format(link)

    def paragraph(self, text):
        p = text
        fn_refs = self._pop_fn_refs()

        if fn_refs:
            # Insert footnote refs, if any, after paragraph.
            return '\n{}\n\n{}'.format(p, fn_refs)

        return '\n{}\n'.format(p)

    def link(self, link, title, text):
        link_text = self._stylize_link(link, title, text)
        return link_text

    def image(self, src, title, text):
        # Markup for images are same as links, except it is prefixed
        # with a bang (!).
        return '{}{}'.format('!', self.link(src, title, text))

    def _stylize_link(self, link, title, text):
        if self.link_style == 'inline':
            return self._gen_inline_link(link, title, text)
        else:
            return self._gen_footnote_link(link, title, text)

    def _gen_inline_link(self, link, title, text):
        if title:
            return '[{}]({} "{}")'.format(text, link, title)
        else:
            return '[{}]({})'.format(text, link)

    def _gen_footnote_link(self, link, title, text):
        fn_num = self._st_fn_ref(link, title)
        return '[{}][{}]'.format(text, fn_num)

    def _st_fn_ref(self, link, title):
        """Store footnote link reference.

        """
        fn_num = self._get_fn_lnk_num()

        if title:
            fn_ref = '[{}]: {} ({})'.format(fn_num, link, title)
        else:
            fn_ref = '[{}]: {}'.format(fn_num, link)

        self.fn_lnk_refs.append(fn_ref)
        return fn_num

    def _get_fn_lnk_num(self):
        """Get footnote link number.

        """
        fn_num = self.fn_lnk_num
        self.fn_lnk_num = self.fn_lnk_num + 1
        return fn_num

    def _pop_fn_refs(self):
        """Pop all footnote refs and return them as a string.

        """
        refs = ''

        for ref in self.fn_lnk_refs:
            refs += '{}\n'.format(ref)

        # Empty fn_lnk_refs
        self.fn_lnk_refs = []

        return refs


class LSMarkdown(Markdown):
    """Link Style Markdown parser.
    """

    def __init__(self, renderer=None, inline=None, block=None,
                     **kwargs):
        link_style = kwargs.get('link_style') or 'inline'

        if not renderer:
            renderer = LSRenderer(link_style=link_style)
        if not inline:
            inline = LSInlineLexer(renderer)
        if not block:
            block = LSBlockLexer()

        super(LSMarkdown, self).__init__(renderer, inline, block,
                                             **kwargs)

    def parse(self, text):
        # Reset footnote link variables.
        self.renderer.fn_lnk_num = 0
        self.renderer.fn_lnk_refs = []

        # Parse text.
        out = super(LSMarkdown, self).parse(text)

        # Spit out.
        return out.lstrip('\n')


class LinkStyler(object):
    """Markdown Link Styler.

    """

    def __init__(self, link_style='inline'):
        self.style = link_style

    def __call__(self, file_):
        return self._link_stylize(file_)

    def _link_stylize(self, file_):
        text = file_.read()
        md = LSMarkdown(link_style=self.style)

        return md(text)


def _write_to(file_, content):
        """Write `content` to `file_`.

        `file_` is expected to be a sub-class of `io.TextIOBase`.
        """
        file_.truncate(0)
        file_.seek(0)
        file_.write(content)
        file_.flush()
        file_.close()


def _mdl_stylize(args):
    ls = LinkStyler(args.link_style)
    stylized_content = ls(args.in_file)

    if args.out_file:
        args.in_file.close()
        _write_to(open(args.out_file, 'wt'), stylized_content)
    else:
        _write_to(args.in_file, stylized_content)


def _get_args(args=None):
    parser = argparse.ArgumentParser()
    parser.add_argument('--version', action='version',
                            version=__version__)
    parser.add_argument('link_style', choices=['inline', 'footnote'],
                        help='markdown link style.')
    parser.add_argument('in_file', type=argparse.FileType('rt+'),
                        help='path to markdown file.')
    parser.add_argument('out_file', nargs='?',
                        type=str,
                        default=None,
                        help= ' '.join(['path to output file.',
                                'if it is not given, the output is',
                                'directly written to the original',
                                'in_file.']))

    return parser.parse_args(args)


def main():
    args = _get_args()
    _mdl_stylize(args)