diff --git a/addons21/fastwq/context.py b/addons21/fastwq/context.py index 85a0800..aed659b 100644 --- a/addons21/fastwq/context.py +++ b/addons21/fastwq/context.py @@ -19,16 +19,17 @@ import json import os -from aqt import mw + from anki.hooks import runHook +from aqt import mw + from .constants import VERSION from .utils import get_icon - __all__ = ['APP_ICON', 'config'] -APP_ICON = get_icon('wqicon.png') #Addon Icon +APP_ICON = get_icon('wqicon.png') # Addon Icon class Config(object): @@ -37,7 +38,7 @@ class Config(object): Addon Config """ - _CONFIG_FILENAME = 'fastwqcfg.json' #Config File Path + _CONFIG_FILENAME = 'fastwqcfg.json' # Config File Path def __init__(self, window): self.path = u'_' + self._CONFIG_FILENAME @@ -122,7 +123,7 @@ class Config(object): @property def last_folder(self): """ - last file dialog open path + last file dialog open path """ return self.data.get('last_folder', '') @@ -131,15 +132,12 @@ class Config(object): '''ignore accents of field in querying''' return self.data.get('ignore_accents', False) - @property - def auto_update(self): - '''auto check new version''' - return self.data.get('auto_update', True) - @property def cloze_str(self): '''cloze formater string''' - return self.data.get('cloze_str', '{{c1::%s}}') - + tmpstr = self.data.get('cloze_str', '{{c1::%s}}') + if len(tmpstr.split('%s')) != 2: + tmpstr = '{{c1::%s}}' + return tmpstr config = Config(mw) diff --git a/addons21/fastwq/gui/common.py b/addons21/fastwq/gui/common.py index eb7c37c..3a5d444 100644 --- a/addons21/fastwq/gui/common.py +++ b/addons21/fastwq/gui/common.py @@ -18,38 +18,22 @@ # along with this program. If not, see . import types + from aqt import mw from aqt.qt import * -from aqt.utils import showInfo -from .options import OptionsDialog -from .foldermanager import FoldersManageDialog -from .dictmanager import DictManageDialog -from ..libs import ankihub + +from ..constants import Template from ..context import config from ..lang import _ -from ..constants import Endpoint, Template from ..service import service_manager, service_pool +from .dictmanager import DictManageDialog +from .foldermanager import FoldersManageDialog +from .options import OptionsDialog + +__all__ = ['show_options', 'show_fm_dialog', 'show_about_dialog'] -__all__ = ['show_options', 'show_fm_dialog', 'show_about_dialog'] #'check_updates', - - -# def check_updates(background=False, parent=None): -# '''check add-on last version''' -# try: -# parent = mw if parent is None else parent -# state = ankihub.update([Endpoint.check_version], Endpoint.version, background, parent) -# if not background: -# if state == 0: -# showInfo(_('LATEST_VERSION')) -# elif state == -1: -# showInfo(_('CHECK_FAILURE')) -# except: -# if not background: -# showInfo(_('CHECK_FAILURE')) - - -def show_fm_dialog(browser = None): +def show_fm_dialog(browser=None): '''open dictionary folder manager window''' parent = mw if browser is None else browser fm_dialog = FoldersManageDialog(parent, u'Dictionary Folder Manager') @@ -64,7 +48,7 @@ def show_fm_dialog(browser = None): show_options(browser) -def show_dm_dialog(browser = None): +def show_dm_dialog(browser=None): parent = mw if browser is None else browser dm_dialog = DictManageDialog(parent, u'Dictionary Manager') dm_dialog.activateWindow() @@ -78,7 +62,7 @@ def show_dm_dialog(browser = None): show_options(browser) -def show_options(browser = None, model_id = -1, callback = None, *args, **kwargs): +def show_options(browser=None, model_id=-1, callback=None, *args, **kwargs): '''open options window''' parent = mw if browser is None else browser config.read() diff --git a/addons21/fastwq/gui/setting.py b/addons21/fastwq/gui/setting.py index 051c3d1..842454d 100644 --- a/addons21/fastwq/gui/setting.py +++ b/addons21/fastwq/gui/setting.py @@ -18,10 +18,10 @@ # along with this program. If not, see . from aqt.qt import * -from .base import Dialog, WIDGET_SIZE + from ..context import config from ..lang import _ - +from .base import WIDGET_SIZE, Dialog __all__ = ['SettingDialog'] @@ -36,7 +36,6 @@ class SettingDialog(Dialog): self.setFixedWidth(400) self.check_force_update = None self.check_ignore_accents = None - # self.check_auto_update = None self.input_thread_number = None self.build() @@ -53,11 +52,6 @@ class SettingDialog(Dialog): layout.addWidget(check_ignore_accents) layout.addSpacing(10) - # check_auto_update = QCheckBox(_("AUTO_UPDATE")) - # check_auto_update.setChecked(config.auto_update) - # layout.addWidget(check_auto_update) - # layout.addSpacing(10) - check_ighore_mdx_wordcase = QCheckBox(_("IGNORE_MDX_WORDCASE")) check_ighore_mdx_wordcase.setChecked(config.ignore_mdx_wordcase) layout.addWidget(check_ighore_mdx_wordcase) @@ -84,16 +78,23 @@ class SettingDialog(Dialog): hbox.setStretchFactor(input_cloze_str, 2) layout.addLayout(hbox) - buttonBox = QDialogButtonBox(parent=self) - buttonBox.setStandardButtons(QDialogButtonBox.Ok) - buttonBox.accepted.connect(self.accept) # 确定 + hbox = QHBoxLayout() + okbtn = QDialogButtonBox(parent=self) + okbtn.setStandardButtons(QDialogButtonBox.Ok) + okbtn.clicked.connect(self.accept) + resetbtn = QDialogButtonBox(parent=self) + resetbtn.setStandardButtons(QDialogButtonBox.Reset) + resetbtn.clicked.connect(self.reset) + hbox.setAlignment(Qt.AlignRight) + hbox.addSpacing(300) + hbox.addWidget(resetbtn) + hbox.addWidget(okbtn) layout.addSpacing(48) - layout.addWidget(buttonBox) + layout.addLayout(hbox) self.check_force_update = check_force_update self.check_ignore_accents = check_ignore_accents - # self.check_auto_update = check_auto_update self.check_ighore_mdx_wordcase = check_ighore_mdx_wordcase self.input_thread_number = input_thread_number self.input_cloze_str = input_cloze_str @@ -105,14 +106,27 @@ class SettingDialog(Dialog): self.save() super(SettingDialog, self).accept() + def reset(self): + data = { + 'force_update': False, + 'ignore_accents': False, + 'ignore_mdx_wordcase': False, + 'thread_number': 16, + 'cloze_str': '{{c1::%s}}' + } + config.update(data) + self.check_force_update.setChecked(config.force_update) + self.check_ignore_accents.setChecked(config.ignore_accents) + self.check_ighore_mdx_wordcase.setChecked(config.ignore_mdx_wordcase) + self.input_thread_number.setValue(config.thread_number) + self.input_cloze_str.setText(config.cloze_str) + def save(self): data = { 'force_update': self.check_force_update.isChecked(), 'ignore_accents': self.check_ignore_accents.isChecked(), - # 'auto_update': self.check_auto_update.isChecked(), 'ignore_mdx_wordcase': self.check_ighore_mdx_wordcase.isChecked(), 'thread_number': self.input_thread_number.value(), 'cloze_str': self.input_cloze_str.text() } config.update(data) - \ No newline at end of file diff --git a/addons21/fastwq/libs/AnkiHub/__init__.py b/addons21/fastwq/libs/AnkiHub/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/addons21/fastwq/libs/AnkiHub/markdown2.py b/addons21/fastwq/libs/AnkiHub/markdown2.py deleted file mode 100644 index 1926792..0000000 --- a/addons21/fastwq/libs/AnkiHub/markdown2.py +++ /dev/null @@ -1,2584 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 Trent Mick. -# Copyright (c) 2007-2008 ActiveState Corp. -# License: MIT (http://www.opensource.org/licenses/mit-license.php) - -from __future__ import generators - -r"""A fast and complete Python implementation of Markdown. - -[from http://daringfireball.net/projects/markdown/] -> Markdown is a text-to-HTML filter; it translates an easy-to-read / -> easy-to-write structured text format into HTML. Markdown's text -> format is most similar to that of plain text email, and supports -> features such as headers, *emphasis*, code blocks, blockquotes, and -> links. -> -> Markdown's syntax is designed not as a generic markup language, but -> specifically to serve as a front-end to (X)HTML. You can use span-level -> HTML tags anywhere in a Markdown document, and you can use block level -> HTML tags (like
and as well). - -Module usage: - - >>> import markdown2 - >>> markdown2.markdown("*boo!*") # or use `html = markdown_path(PATH)` - u'

boo!

\n' - - >>> markdowner = Markdown() - >>> markdowner.convert("*boo!*") - u'

boo!

\n' - >>> markdowner.convert("**boom!**") - u'

boom!

\n' - -This implementation of Markdown implements the full "core" syntax plus a -number of extras (e.g., code syntax coloring, footnotes) as described on -. -""" - -cmdln_desc = """A fast and complete Python implementation of Markdown, a -text-to-HTML conversion tool for web writers. - -Supported extra syntax options (see -x|--extras option below and -see for details): - -* code-friendly: Disable _ and __ for em and strong. -* cuddled-lists: Allow lists to be cuddled to the preceding paragraph. -* fenced-code-blocks: Allows a code block to not have to be indented - by fencing it with '```' on a line before and after. Based on - with support for - syntax highlighting. -* footnotes: Support footnotes as in use on daringfireball.net and - implemented in other Markdown processors (tho not in Markdown.pl v1.0.1). -* numbering: Support of generic counters. Non standard extension to - allow sequential numbering of figures, tables, equations, exhibits etc. -* header-ids: Adds "id" attributes to headers. The id value is a slug of - the header text. -* html-classes: Takes a dict mapping html tag names (lowercase) to a - string to use for a "class" tag attribute. Currently only supports "img", - "table", "pre" and "code" tags. Add an issue if you require this for other - tags. -* markdown-in-html: Allow the use of `markdown="1"` in a block HTML tag to - have markdown processing be done on its contents. Similar to - but with - some limitations. -* metadata: Extract metadata from a leading '---'-fenced block. - See for details. -* nofollow: Add `rel="nofollow"` to add `` tags with an href. See - . -* pyshell: Treats unindented Python interactive shell sessions as - blocks. -* link-patterns: Auto-link given regex patterns in text (e.g. bug number - references, revision number references). -* smarty-pants: Replaces ' and " with curly quotation marks or curly - apostrophes. Replaces --, ---, ..., and . . . with en dashes, em dashes, - and ellipses. -* spoiler: A special kind of blockquote commonly hidden behind a - click on SO. Syntax per . -* toc: The returned HTML string gets a new "toc_html" attribute which is - a Table of Contents for the document. (experimental) -* xml: Passes one-liner processing instructions and namespaced XML tags. -* tables: Tables using the same format as GFM - and - PHP-Markdown Extra . -* wiki-tables: Google Code Wiki-style tables. See - . -""" - -# Dev Notes: -# - Python's regex syntax doesn't have '\z', so I'm using '\Z'. I'm -# not yet sure if there implications with this. Compare 'pydoc sre' -# and 'perldoc perlre'. - -__version_info__ = (2, 3, 2) -__version__ = '.'.join(map(str, __version_info__)) -__author__ = "Trent Mick" - -import sys -import re -import logging -try: - from hashlib import md5 -except ImportError: - from md5 import md5 -import optparse -from random import random, randint -import codecs -from itertools import chain - - -# ---- Python version compat - -if sys.version_info[:2] < (2, 4): - def reversed(sequence): - for i in sequence[::-1]: - yield i - -# Use `bytes` for byte strings and `unicode` for unicode strings (str in Py3). -if sys.version_info[0] <= 2: - py3 = False - try: - bytes - except NameError: - bytes = str - base_string_type = basestring -elif sys.version_info[0] >= 3: - py3 = True - unicode = str - base_string_type = str - -# ---- globals - -DEBUG = False -log = logging.getLogger("markdown") - -DEFAULT_TAB_WIDTH = 4 - - -SECRET_SALT = bytes(randint(0, 1000000)) -def _hash_text(s): - return 'md5-' + md5(SECRET_SALT + s.encode("utf-8")).hexdigest() - -# Table of hash values for escaped characters: -g_escape_table = dict([(ch, _hash_text(ch)) - for ch in '\\`*_{}[]()>#+-.!']) - - -# ---- exceptions -class MarkdownError(Exception): - pass - - -# ---- public api - -def markdown_path(path, encoding="utf-8", - html4tags=False, tab_width=DEFAULT_TAB_WIDTH, - safe_mode=None, extras=None, link_patterns=None, - use_file_vars=False): - fp = codecs.open(path, 'r', encoding) - text = fp.read() - fp.close() - return Markdown(html4tags=html4tags, tab_width=tab_width, - safe_mode=safe_mode, extras=extras, - link_patterns=link_patterns, - use_file_vars=use_file_vars).convert(text) - - -def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH, - safe_mode=None, extras=None, link_patterns=None, - use_file_vars=False): - return Markdown(html4tags=html4tags, tab_width=tab_width, - safe_mode=safe_mode, extras=extras, - link_patterns=link_patterns, - use_file_vars=use_file_vars).convert(text) - - -class Markdown(object): - # The dict of "extras" to enable in processing -- a mapping of - # extra name to argument for the extra. Most extras do not have an - # argument, in which case the value is None. - # - # This can be set via (a) subclassing and (b) the constructor - # "extras" argument. - extras = None - - urls = None - titles = None - html_blocks = None - html_spans = None - html_removed_text = "[HTML_REMOVED]" # for compat with markdown.py - - # Used to track when we're inside an ordered or unordered list - # (see _ProcessListItems() for details): - list_level = 0 - - _ws_only_line_re = re.compile(r"^[ \t]+$", re.M) - - def __init__(self, html4tags=False, tab_width=4, safe_mode=None, - extras=None, link_patterns=None, use_file_vars=False): - if html4tags: - self.empty_element_suffix = ">" - else: - self.empty_element_suffix = " />" - self.tab_width = tab_width - - # For compatibility with earlier markdown2.py and with - # markdown.py's safe_mode being a boolean, - # safe_mode == True -> "replace" - if safe_mode is True: - self.safe_mode = "replace" - else: - self.safe_mode = safe_mode - - # Massaging and building the "extras" info. - if self.extras is None: - self.extras = {} - elif not isinstance(self.extras, dict): - self.extras = dict([(e, None) for e in self.extras]) - if extras: - if not isinstance(extras, dict): - extras = dict([(e, None) for e in extras]) - self.extras.update(extras) - assert isinstance(self.extras, dict) - if "toc" in self.extras and "header-ids" not in self.extras: - self.extras["header-ids"] = None # "toc" implies "header-ids" - self._instance_extras = self.extras.copy() - - self.link_patterns = link_patterns - self.use_file_vars = use_file_vars - self._outdent_re = re.compile(r'^(\t|[ ]{1,%d})' % tab_width, re.M) - - self._escape_table = g_escape_table.copy() - if "smarty-pants" in self.extras: - self._escape_table['"'] = _hash_text('"') - self._escape_table["'"] = _hash_text("'") - - def reset(self): - self.urls = {} - self.titles = {} - self.html_blocks = {} - self.html_spans = {} - self.list_level = 0 - self.extras = self._instance_extras.copy() - if "footnotes" in self.extras: - self.footnotes = {} - self.footnote_ids = [] - if "header-ids" in self.extras: - self._count_from_header_id = {} # no `defaultdict` in Python 2.4 - if "metadata" in self.extras: - self.metadata = {} - - # Per "rel" - # should only be used in tags with an "href" attribute. - _a_nofollow = re.compile(r"<(a)([^>]*href=)", re.IGNORECASE) - - # Opens the linked document in a new window or tab - # should only used in tags with an "target" attribute. - # same with _a_nofollow - _a_blank = _a_nofollow - - def convert(self, text): - """Convert the given text.""" - # Main function. The order in which other subs are called here is - # essential. Link and image substitutions need to happen before - # _EscapeSpecialChars(), so that any *'s or _'s in the - # and tags get encoded. - - # Clear the global hashes. If we don't clear these, you get conflicts - # from other articles when generating a page which contains more than - # one article (e.g. an index page that shows the N most recent - # articles): - self.reset() - - if not isinstance(text, unicode): - # TODO: perhaps shouldn't presume UTF-8 for string input? - text = unicode(text, 'utf-8') - - if self.use_file_vars: - # Look for emacs-style file variable hints. - emacs_vars = self._get_emacs_vars(text) - if "markdown-extras" in emacs_vars: - splitter = re.compile("[ ,]+") - for e in splitter.split(emacs_vars["markdown-extras"]): - if '=' in e: - ename, earg = e.split('=', 1) - try: - earg = int(earg) - except ValueError: - pass - else: - ename, earg = e, None - self.extras[ename] = earg - - # Standardize line endings: - text = text.replace("\r\n", "\n") - text = text.replace("\r", "\n") - - # Make sure $text ends with a couple of newlines: - text += "\n\n" - - # Convert all tabs to spaces. - text = self._detab(text) - - # Strip any lines consisting only of spaces and tabs. - # This makes subsequent regexen easier to write, because we can - # match consecutive blank lines with /\n+/ instead of something - # contorted like /[ \t]*\n+/ . - text = self._ws_only_line_re.sub("", text) - - # strip metadata from head and extract - if "metadata" in self.extras: - text = self._extract_metadata(text) - - text = self.preprocess(text) - - if "fenced-code-blocks" in self.extras and not self.safe_mode: - text = self._do_fenced_code_blocks(text) - - if self.safe_mode: - text = self._hash_html_spans(text) - - # Turn block-level HTML blocks into hash entries - text = self._hash_html_blocks(text, raw=True) - - if "fenced-code-blocks" in self.extras and self.safe_mode: - text = self._do_fenced_code_blocks(text) - - # Because numbering references aren't links (yet?) then we can do everything associated with counters - # before we get started - if "numbering" in self.extras: - text = self._do_numbering(text) - - # Strip link definitions, store in hashes. - if "footnotes" in self.extras: - # Must do footnotes first because an unlucky footnote defn - # looks like a link defn: - # [^4]: this "looks like a link defn" - text = self._strip_footnote_definitions(text) - text = self._strip_link_definitions(text) - - text = self._run_block_gamut(text) - - if "footnotes" in self.extras: - text = self._add_footnotes(text) - - text = self.postprocess(text) - - text = self._unescape_special_chars(text) - - if self.safe_mode: - text = self._unhash_html_spans(text) - - if "nofollow" in self.extras: - text = self._a_nofollow.sub(r'<\1 rel="nofollow"\2', text) - - if "target-blank-links" in self.extras: - text = self._a_blank.sub(r'<\1 target="_blank"\2', text) - - text += "\n" - - rv = UnicodeWithAttrs(text) - if "toc" in self.extras: - rv._toc = self._toc - if "metadata" in self.extras: - rv.metadata = self.metadata - return rv - - def postprocess(self, text): - """A hook for subclasses to do some postprocessing of the html, if - desired. This is called before unescaping of special chars and - unhashing of raw HTML spans. - """ - return text - - def preprocess(self, text): - """A hook for subclasses to do some preprocessing of the Markdown, if - desired. This is called after basic formatting of the text, but prior - to any extras, safe mode, etc. processing. - """ - return text - - # Is metadata if the content starts with optional '---'-fenced `key: value` - # pairs. E.g. (indented for presentation): - # --- - # foo: bar - # another-var: blah blah - # --- - # # header - # or: - # foo: bar - # another-var: blah blah - # - # # header - _meta_data_pattern = re.compile(r'^(?:---[\ \t]*\n)?(.*:\s+>\n\s+[\S\s]+?)(?=\n\w+\s*:\s*\w+\n|\Z)|([\S\w]+\s*:(?! >)[ \t]*.*\n?)(?:---[\ \t]*\n)?', re.MULTILINE) - _key_val_pat = re.compile("[\S\w]+\s*:(?! >)[ \t]*.*\n?", re.MULTILINE) - # this allows key: > - # value - # conutiues over multiple lines - _key_val_block_pat = re.compile( - "(.*:\s+>\n\s+[\S\s]+?)(?=\n\w+\s*:\s*\w+\n|\Z)", re.MULTILINE) - - def _extract_metadata(self, text): - match = re.findall(self._meta_data_pattern, text) - - if not match: - return text - - last_item = list(filter(None, match[-1]))[0] - end_of_metadata = text.index(last_item)+len(last_item) - if text.startswith("---"): - # add 8 charachters for opening and closing - # and since indexing starts at 0 we add a step - tail = text[end_of_metadata+4:] - else: - tail = text[end_of_metadata:] - - kv = re.findall(self._key_val_pat, text) - kvm = re.findall(self._key_val_block_pat, text) - kvm = [item.replace(": >\n", ":", 1) for item in kvm] - - for item in kv + kvm: - k, v = item.split(":", 1) - self.metadata[k.strip()] = v.strip() - - return tail - - _emacs_oneliner_vars_pat = re.compile(r"-\*-\s*([^\r\n]*?)\s*-\*-", re.UNICODE) - # This regular expression is intended to match blocks like this: - # PREFIX Local Variables: SUFFIX - # PREFIX mode: Tcl SUFFIX - # PREFIX End: SUFFIX - # Some notes: - # - "[ \t]" is used instead of "\s" to specifically exclude newlines - # - "(\r\n|\n|\r)" is used instead of "$" because the sre engine does - # not like anything other than Unix-style line terminators. - _emacs_local_vars_pat = re.compile(r"""^ - (?P(?:[^\r\n|\n|\r])*?) - [\ \t]*Local\ Variables:[\ \t]* - (?P.*?)(?:\r\n|\n|\r) - (?P.*?\1End:) - """, re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE) - - def _get_emacs_vars(self, text): - """Return a dictionary of emacs-style local variables. - - Parsing is done loosely according to this spec (and according to - some in-practice deviations from this): - http://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html#Specifying-File-Variables - """ - emacs_vars = {} - SIZE = pow(2, 13) # 8kB - - # Search near the start for a '-*-'-style one-liner of variables. - head = text[:SIZE] - if "-*-" in head: - match = self._emacs_oneliner_vars_pat.search(head) - if match: - emacs_vars_str = match.group(1) - assert '\n' not in emacs_vars_str - emacs_var_strs = [s.strip() for s in emacs_vars_str.split(';') - if s.strip()] - if len(emacs_var_strs) == 1 and ':' not in emacs_var_strs[0]: - # While not in the spec, this form is allowed by emacs: - # -*- Tcl -*- - # where the implied "variable" is "mode". This form - # is only allowed if there are no other variables. - emacs_vars["mode"] = emacs_var_strs[0].strip() - else: - for emacs_var_str in emacs_var_strs: - try: - variable, value = emacs_var_str.strip().split(':', 1) - except ValueError: - log.debug("emacs variables error: malformed -*- " - "line: %r", emacs_var_str) - continue - # Lowercase the variable name because Emacs allows "Mode" - # or "mode" or "MoDe", etc. - emacs_vars[variable.lower()] = value.strip() - - tail = text[-SIZE:] - if "Local Variables" in tail: - match = self._emacs_local_vars_pat.search(tail) - if match: - prefix = match.group("prefix") - suffix = match.group("suffix") - lines = match.group("content").splitlines(0) - # print "prefix=%r, suffix=%r, content=%r, lines: %s"\ - # % (prefix, suffix, match.group("content"), lines) - - # Validate the Local Variables block: proper prefix and suffix - # usage. - for i, line in enumerate(lines): - if not line.startswith(prefix): - log.debug("emacs variables error: line '%s' " - "does not use proper prefix '%s'" - % (line, prefix)) - return {} - # Don't validate suffix on last line. Emacs doesn't care, - # neither should we. - if i != len(lines)-1 and not line.endswith(suffix): - log.debug("emacs variables error: line '%s' " - "does not use proper suffix '%s'" - % (line, suffix)) - return {} - - # Parse out one emacs var per line. - continued_for = None - for line in lines[:-1]: # no var on the last line ("PREFIX End:") - if prefix: line = line[len(prefix):] # strip prefix - if suffix: line = line[:-len(suffix)] # strip suffix - line = line.strip() - if continued_for: - variable = continued_for - if line.endswith('\\'): - line = line[:-1].rstrip() - else: - continued_for = None - emacs_vars[variable] += ' ' + line - else: - try: - variable, value = line.split(':', 1) - except ValueError: - log.debug("local variables error: missing colon " - "in local variables entry: '%s'" % line) - continue - # Do NOT lowercase the variable name, because Emacs only - # allows "mode" (and not "Mode", "MoDe", etc.) in this block. - value = value.strip() - if value.endswith('\\'): - value = value[:-1].rstrip() - continued_for = variable - else: - continued_for = None - emacs_vars[variable] = value - - # Unquote values. - for var, val in list(emacs_vars.items()): - if len(val) > 1 and (val.startswith('"') and val.endswith('"') - or val.startswith('"') and val.endswith('"')): - emacs_vars[var] = val[1:-1] - - return emacs_vars - - def _detab_line(self, line): - r"""Recusively convert tabs to spaces in a single line. - - Called from _detab().""" - if '\t' not in line: - return line - chunk1, chunk2 = line.split('\t', 1) - chunk1 += (' ' * (self.tab_width - len(chunk1) % self.tab_width)) - output = chunk1 + chunk2 - return self._detab_line(output) - - def _detab(self, text): - r"""Iterate text line by line and convert tabs to spaces. - - >>> m = Markdown() - >>> m._detab("\tfoo") - ' foo' - >>> m._detab(" \tfoo") - ' foo' - >>> m._detab("\t foo") - ' foo' - >>> m._detab(" foo") - ' foo' - >>> m._detab(" foo\n\tbar\tblam") - ' foo\n bar blam' - """ - if '\t' not in text: - return text - output = [] - for line in text.splitlines(): - output.append(self._detab_line(line)) - return '\n'.join(output) - - # I broke out the html5 tags here and add them to _block_tags_a and - # _block_tags_b. This way html5 tags are easy to keep track of. - _html5tags = '|article|aside|header|hgroup|footer|nav|section|figure|figcaption' - - _block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del' - _block_tags_a += _html5tags - - _strict_tag_block_re = re.compile(r""" - ( # save in \1 - ^ # start of line (with re.M) - <(%s) # start tag = \2 - \b # word break - (.*\n)*? # any number of lines, minimally matching - # the matching end tag - [ \t]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - ) - """ % _block_tags_a, - re.X | re.M) - - _block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math' - _block_tags_b += _html5tags - - _liberal_tag_block_re = re.compile(r""" - ( # save in \1 - ^ # start of line (with re.M) - <(%s) # start tag = \2 - \b # word break - (.*\n)*? # any number of lines, minimally matching - .* # the matching end tag - [ \t]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - ) - """ % _block_tags_b, - re.X | re.M) - - _html_markdown_attr_re = re.compile( - r'''\s+markdown=("1"|'1')''') - def _hash_html_block_sub(self, match, raw=False): - html = match.group(1) - if raw and self.safe_mode: - html = self._sanitize_html(html) - elif 'markdown-in-html' in self.extras and 'markdown=' in html: - first_line = html.split('\n', 1)[0] - m = self._html_markdown_attr_re.search(first_line) - if m: - lines = html.split('\n') - middle = '\n'.join(lines[1:-1]) - last_line = lines[-1] - first_line = first_line[:m.start()] + first_line[m.end():] - f_key = _hash_text(first_line) - self.html_blocks[f_key] = first_line - l_key = _hash_text(last_line) - self.html_blocks[l_key] = last_line - return ''.join(["\n\n", f_key, - "\n\n", middle, "\n\n", - l_key, "\n\n"]) - key = _hash_text(html) - self.html_blocks[key] = html - return "\n\n" + key + "\n\n" - - def _hash_html_blocks(self, text, raw=False): - """Hashify HTML blocks - - We only want to do this for block-level HTML tags, such as headers, - lists, and tables. That's because we still want to wrap

s around - "paragraphs" that are wrapped in non-block-level tags, such as anchors, - phrase emphasis, and spans. The list of tags we're looking for is - hard-coded. - - @param raw {boolean} indicates if these are raw HTML blocks in - the original source. It makes a difference in "safe" mode. - """ - if '<' not in text: - return text - - # Pass `raw` value into our calls to self._hash_html_block_sub. - hash_html_block_sub = _curry(self._hash_html_block_sub, raw=raw) - - # First, look for nested blocks, e.g.: - #

- #
- # tags for inner block must be indented. - #
- #
- # - # The outermost tags must start at the left margin for this to match, and - # the inner nested divs must be indented. - # We need to do this before the next, more liberal match, because the next - # match will start at the first `
` and stop at the first `
`. - text = self._strict_tag_block_re.sub(hash_html_block_sub, text) - - # Now match more liberally, simply from `\n` to `\n` - text = self._liberal_tag_block_re.sub(hash_html_block_sub, text) - - # Special case just for
. It was easier to make a special - # case than to make the other regex more complicated. - if "", start_idx) + 3 - except ValueError: - break - - # Start position for next comment block search. - start = end_idx - - # Validate whitespace before comment. - if start_idx: - # - Up to `tab_width - 1` spaces before start_idx. - for i in range(self.tab_width - 1): - if text[start_idx - 1] != ' ': - break - start_idx -= 1 - if start_idx == 0: - break - # - Must be preceded by 2 newlines or hit the start of - # the document. - if start_idx == 0: - pass - elif start_idx == 1 and text[0] == '\n': - start_idx = 0 # to match minute detail of Markdown.pl regex - elif text[start_idx-2:start_idx] == '\n\n': - pass - else: - break - - # Validate whitespace after comment. - # - Any number of spaces and tabs. - while end_idx < len(text): - if text[end_idx] not in ' \t': - break - end_idx += 1 - # - Must be following by 2 newlines or hit end of text. - if text[end_idx:end_idx+2] not in ('', '\n', '\n\n'): - continue - - # Escape and hash (must match `_hash_html_block_sub`). - html = text[start_idx:end_idx] - if raw and self.safe_mode: - html = self._sanitize_html(html) - key = _hash_text(html) - self.html_blocks[key] = html - text = text[:start_idx] + "\n\n" + key + "\n\n" + text[end_idx:] - - if "xml" in self.extras: - # Treat XML processing instructions and namespaced one-liner - # tags as if they were block HTML tags. E.g., if standalone - # (i.e. are their own paragraph), the following do not get - # wrapped in a

tag: - # - # - # - _xml_oneliner_re = _xml_oneliner_re_from_tab_width(self.tab_width) - text = _xml_oneliner_re.sub(hash_html_block_sub, text) - - return text - - def _strip_link_definitions(self, text): - # Strips link definitions from text, stores the URLs and titles in - # hash references. - less_than_tab = self.tab_width - 1 - - # Link defs are in the form: - # [id]: url "optional title" - _link_def_re = re.compile(r""" - ^[ ]{0,%d}\[(.+)\]: # id = \1 - [ \t]* - \n? # maybe *one* newline - [ \t]* - ? # url = \2 - [ \t]* - (?: - \n? # maybe one newline - [ \t]* - (?<=\s) # lookbehind for whitespace - ['"(] - ([^\n]*) # title = \3 - ['")] - [ \t]* - )? # title is optional - (?:\n+|\Z) - """ % less_than_tab, re.X | re.M | re.U) - return _link_def_re.sub(self._extract_link_def_sub, text) - - def _extract_link_def_sub(self, match): - id, url, title = match.groups() - key = id.lower() # Link IDs are case-insensitive - self.urls[key] = self._encode_amps_and_angles(url) - if title: - self.titles[key] = title - return "" - - def _do_numbering(self, text): - ''' We handle the special extension for generic numbering for - tables, figures etc. - ''' - # First pass to define all the references - self.regex_defns = re.compile(r''' - \[\#(\w+)\s* # the counter. Open square plus hash plus a word \1 - ([^@]*)\s* # Some optional characters, that aren't an @. \2 - @(\w+) # the id. Should this be normed? \3 - ([^\]]*)\] # The rest of the text up to the terminating ] \4 - ''', re.VERBOSE) - self.regex_subs = re.compile(r"\[@(\w+)\s*\]") # [@ref_id] - counters = {} - references = {} - replacements = [] - definition_html = '

{}{}{}
' - reference_html = '
{}' - for match in self.regex_defns.finditer(text): - # We must have four match groups otherwise this isn't a numbering reference - if len(match.groups()) != 4: - continue - counter = match.group(1) - text_before = match.group(2) - ref_id = match.group(3) - text_after = match.group(4) - number = counters.get(counter, 1) - references[ref_id] = (number, counter) - replacements.append((match.start(0), - definition_html.format(counter, - ref_id, - text_before, - number, - text_after), - match.end(0))) - counters[counter] = number + 1 - for repl in reversed(replacements): - text = text[:repl[0]] + repl[1] + text[repl[2]:] - - # Second pass to replace the references with the right - # value of the counter - # Fwiw, it's vaguely annoying to have to turn the iterator into - # a list and then reverse it but I can't think of a better thing to do. - for match in reversed(list(self.regex_subs.finditer(text))): - number, counter = references.get(match.group(1), (None, None)) - if number is not None: - repl = reference_html.format(counter, - match.group(1), - number) - else: - repl = reference_html.format(match.group(1), - 'countererror', - '?' + match.group(1) + '?') - if "smarty-pants" in self.extras: - repl = repl.replace('"', self._escape_table['"']) - - text = text[:match.start()] + repl + text[match.end():] - return text - - def _extract_footnote_def_sub(self, match): - id, text = match.groups() - text = _dedent(text, skip_first_line=not text.startswith('\n')).strip() - normed_id = re.sub(r'\W', '-', id) - # Ensure footnote text ends with a couple newlines (for some - # block gamut matches). - self.footnotes[normed_id] = text + "\n\n" - return "" - - def _strip_footnote_definitions(self, text): - """A footnote definition looks like this: - - [^note-id]: Text of the note. - - May include one or more indented paragraphs. - - Where, - - The 'note-id' can be pretty much anything, though typically it - is the number of the footnote. - - The first paragraph may start on the next line, like so: - - [^note-id]: - Text of the note. - """ - less_than_tab = self.tab_width - 1 - footnote_def_re = re.compile(r''' - ^[ ]{0,%d}\[\^(.+)\]: # id = \1 - [ \t]* - ( # footnote text = \2 - # First line need not start with the spaces. - (?:\s*.*\n+) - (?: - (?:[ ]{%d} | \t) # Subsequent lines must be indented. - .*\n+ - )* - ) - # Lookahead for non-space at line-start, or end of doc. - (?:(?=^[ ]{0,%d}\S)|\Z) - ''' % (less_than_tab, self.tab_width, self.tab_width), - re.X | re.M) - return footnote_def_re.sub(self._extract_footnote_def_sub, text) - - _hr_re = re.compile(r'^[ ]{0,3}([-_*][ ]{0,2}){3,}$', re.M) - - def _run_block_gamut(self, text): - # These are all the transformations that form block-level - # tags like paragraphs, headers, and list items. - - if "fenced-code-blocks" in self.extras: - text = self._do_fenced_code_blocks(text) - - text = self._do_headers(text) - - # Do Horizontal Rules: - # On the number of spaces in horizontal rules: The spec is fuzzy: "If - # you wish, you may use spaces between the hyphens or asterisks." - # Markdown.pl 1.0.1's hr regexes limit the number of spaces between the - # hr chars to one or two. We'll reproduce that limit here. - hr = "\n tags around block-level tags. - text = self._hash_html_blocks(text) - - text = self._form_paragraphs(text) - - return text - - def _pyshell_block_sub(self, match): - lines = match.group(0).splitlines(0) - _dedentlines(lines) - indent = ' ' * self.tab_width - s = ('\n' # separate from possible cuddled paragraph - + indent + ('\n'+indent).join(lines) - + '\n\n') - return s - - def _prepare_pyshell_blocks(self, text): - """Ensure that Python interactive shell sessions are put in - code blocks -- even if not properly indented. - """ - if ">>>" not in text: - return text - - less_than_tab = self.tab_width - 1 - _pyshell_block_re = re.compile(r""" - ^([ ]{0,%d})>>>[ ].*\n # first line - ^(\1.*\S+.*\n)* # any number of subsequent lines - ^\n # ends with a blank line - """ % less_than_tab, re.M | re.X) - - return _pyshell_block_re.sub(self._pyshell_block_sub, text) - - def _table_sub(self, match): - trim_space_re = '^[ \t\n]+|[ \t\n]+$' - trim_bar_re = '^\||\|$' - - head, underline, body = match.groups() - - # Determine aligns for columns. - cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", underline)).split('|')] - align_from_col_idx = {} - for col_idx, col in enumerate(cols): - if col[0] == ':' and col[-1] == ':': - align_from_col_idx[col_idx] = ' align="center"' - elif col[0] == ':': - align_from_col_idx[col_idx] = ' align="left"' - elif col[-1] == ':': - align_from_col_idx[col_idx] = ' align="right"' - - # thead - hlines = ['' % self._html_class_str_from_tag('table'), '
', ''] - cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", head)).split('|')] - for col_idx, col in enumerate(cols): - hlines.append(' %s' % ( - align_from_col_idx.get(col_idx, ''), - self._run_span_gamut(col) - )) - hlines.append('') - hlines.append('') - - # tbody - hlines.append('') - for line in body.strip('\n').split('\n'): - hlines.append('') - cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", line)).split('|')] - for col_idx, col in enumerate(cols): - hlines.append(' %s' % ( - align_from_col_idx.get(col_idx, ''), - self._run_span_gamut(col) - )) - hlines.append('') - hlines.append('') - hlines.append('
') - - return '\n'.join(hlines) + '\n' - - def _do_tables(self, text): - """Copying PHP-Markdown and GFM table syntax. Some regex borrowed from - https://github.com/michelf/php-markdown/blob/lib/Michelf/Markdown.php#L2538 - """ - less_than_tab = self.tab_width - 1 - table_re = re.compile(r''' - (?:(?<=\n\n)|\A\n?) # leading blank line - - ^[ ]{0,%d} # allowed whitespace - (.*[|].*) \n # $1: header row (at least one pipe) - - ^[ ]{0,%d} # allowed whitespace - ( # $2: underline row - # underline row with leading bar - (?: \|\ *:?-+:?\ * )+ \|? \n - | - # or, underline row without leading bar - (?: \ *:?-+:?\ *\| )+ (?: \ *:?-+:?\ * )? \n - ) - - ( # $3: data rows - (?: - ^[ ]{0,%d}(?!\ ) # ensure line begins with 0 to less_than_tab spaces - .*\|.* \n - )+ - ) - ''' % (less_than_tab, less_than_tab, less_than_tab), re.M | re.X) - return table_re.sub(self._table_sub, text) - - def _wiki_table_sub(self, match): - ttext = match.group(0).strip() - # print 'wiki table: %r' % match.group(0) - rows = [] - for line in ttext.splitlines(0): - line = line.strip()[2:-2].strip() - row = [c.strip() for c in re.split(r'(?' % self._html_class_str_from_tag('table'), ''] - for row in rows: - hrow = [''] - for cell in row: - hrow.append('') - hrow.append(self._run_span_gamut(cell)) - hrow.append('') - hrow.append('') - hlines.append(''.join(hrow)) - hlines += ['', ''] - return '\n'.join(hlines) + '\n' - - def _do_wiki_tables(self, text): - # Optimization. - if "||" not in text: - return text - - less_than_tab = self.tab_width - 1 - wiki_table_re = re.compile(r''' - (?:(?<=\n\n)|\A\n?) # leading blank line - ^([ ]{0,%d})\|\|.+?\|\|[ ]*\n # first line - (^\1\|\|.+?\|\|\n)* # any number of subsequent lines - ''' % less_than_tab, re.M | re.X) - return wiki_table_re.sub(self._wiki_table_sub, text) - - def _run_span_gamut(self, text): - # These are all the transformations that occur *within* block-level - # tags like paragraphs, headers, and list items. - - text = self._do_code_spans(text) - - text = self._escape_special_chars(text) - - # Process anchor and image tags. - text = self._do_links(text) - - # Make links out of things like `` - # Must come after _do_links(), because you can use < and > - # delimiters in inline links like [this](). - text = self._do_auto_links(text) - - if "link-patterns" in self.extras: - text = self._do_link_patterns(text) - - text = self._encode_amps_and_angles(text) - - if "strike" in self.extras: - text = self._do_strike(text) - - text = self._do_italics_and_bold(text) - - if "smarty-pants" in self.extras: - text = self._do_smart_punctuation(text) - - # Do hard breaks: - if "break-on-newline" in self.extras: - text = re.sub(r" *\n", " - | - # auto-link (e.g., ) - <\w+[^>]*> - | - # comment - | - <\?.*?\?> # processing instruction - ) - """, re.X) - - def _escape_special_chars(self, text): - # Python markdown note: the HTML tokenization here differs from - # that in Markdown.pl, hence the behaviour for subtle cases can - # differ (I believe the tokenizer here does a better job because - # it isn't susceptible to unmatched '<' and '>' in HTML tags). - # Note, however, that '>' is not allowed in an auto-link URL - # here. - escaped = [] - is_html_markup = False - for token in self._sorta_html_tokenize_re.split(text): - if is_html_markup: - # Within tags/HTML-comments/auto-links, encode * and _ - # so they don't conflict with their use in Markdown for - # italics and strong. We're replacing each such - # character with its corresponding MD5 checksum value; - # this is likely overkill, but it should prevent us from - # colliding with the escape values by accident. - escaped.append(token.replace('*', self._escape_table['*']) - .replace('_', self._escape_table['_'])) - else: - escaped.append(self._encode_backslash_escapes(token)) - is_html_markup = not is_html_markup - return ''.join(escaped) - - def _hash_html_spans(self, text): - # Used for safe_mode. - - def _is_auto_link(s): - if ':' in s and self._auto_link_re.match(s): - return True - elif '@' in s and self._auto_email_link_re.match(s): - return True - return False - - tokens = [] - is_html_markup = False - for token in self._sorta_html_tokenize_re.split(text): - if is_html_markup and not _is_auto_link(token): - sanitized = self._sanitize_html(token) - key = _hash_text(sanitized) - self.html_spans[key] = sanitized - tokens.append(key) - else: - tokens.append(token) - is_html_markup = not is_html_markup - return ''.join(tokens) - - def _unhash_html_spans(self, text): - for key, sanitized in list(self.html_spans.items()): - text = text.replace(key, sanitized) - return text - - def _sanitize_html(self, s): - if self.safe_mode == "replace": - return self.html_removed_text - elif self.safe_mode == "escape": - replacements = [ - ('&', '&'), - ('<', '<'), - ('>', '>'), - ] - for before, after in replacements: - s = s.replace(before, after) - return s - else: - raise MarkdownError("invalid value for 'safe_mode': %r (must be " - "'escape' or 'replace')" % self.safe_mode) - - _inline_link_title = re.compile(r''' - ( # \1 - [ \t]+ - (['"]) # quote char = \2 - (?P.*?) - \2 - )? # title is optional - \)$ - ''', re.X | re.S) - _tail_of_reference_link_re = re.compile(r''' - # Match tail of: [text][id] - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - \[ - (?P<id>.*?) - \] - ''', re.X | re.S) - - _whitespace = re.compile(r'\s*') - - _strip_anglebrackets = re.compile(r'<(.*)>.*') - - def _find_non_whitespace(self, text, start): - """Returns the index of the first non-whitespace character in text - after (and including) start - """ - match = self._whitespace.match(text, start) - return match.end() - - def _find_balanced(self, text, start, open_c, close_c): - """Returns the index where the open_c and close_c characters balance - out - the same number of open_c and close_c are encountered - or the - end of string if it's reached before the balance point is found. - """ - i = start - l = len(text) - count = 1 - while count > 0 and i < l: - if text[i] == open_c: - count += 1 - elif text[i] == close_c: - count -= 1 - i += 1 - return i - - def _extract_url_and_title(self, text, start): - """Extracts the url and (optional) title from the tail of a link""" - # text[start] equals the opening parenthesis - idx = self._find_non_whitespace(text, start+1) - if idx == len(text): - return None, None, None - end_idx = idx - has_anglebrackets = text[idx] == "<" - if has_anglebrackets: - end_idx = self._find_balanced(text, end_idx+1, "<", ">") - end_idx = self._find_balanced(text, end_idx, "(", ")") - match = self._inline_link_title.search(text, idx, end_idx) - if not match: - return None, None, None - url, title = text[idx:match.start()], match.group("title") - if has_anglebrackets: - url = self._strip_anglebrackets.sub(r'\1', url) - return url, title, end_idx - - def _do_links(self, text): - """Turn Markdown link shortcuts into XHTML <a> and <img> tags. - - This is a combination of Markdown.pl's _DoAnchors() and - _DoImages(). They are done together because that simplified the - approach. It was necessary to use a different approach than - Markdown.pl because of the lack of atomic matching support in - Python's regex engine used in $g_nested_brackets. - """ - MAX_LINK_TEXT_SENTINEL = 3000 # markdown2 issue 24 - - # `anchor_allowed_pos` is used to support img links inside - # anchors, but not anchors inside anchors. An anchor's start - # pos must be `>= anchor_allowed_pos`. - anchor_allowed_pos = 0 - - curr_pos = 0 - while True: # Handle the next link. - # The next '[' is the start of: - # - an inline anchor: [text](url "title") - # - a reference anchor: [text][id] - # - an inline img: ![text](url "title") - # - a reference img: ![text][id] - # - a footnote ref: [^id] - # (Only if 'footnotes' extra enabled) - # - a footnote defn: [^id]: ... - # (Only if 'footnotes' extra enabled) These have already - # been stripped in _strip_footnote_definitions() so no - # need to watch for them. - # - a link definition: [id]: url "title" - # These have already been stripped in - # _strip_link_definitions() so no need to watch for them. - # - not markup: [...anything else... - try: - start_idx = text.index('[', curr_pos) - except ValueError: - break - text_length = len(text) - - # Find the matching closing ']'. - # Markdown.pl allows *matching* brackets in link text so we - # will here too. Markdown.pl *doesn't* currently allow - # matching brackets in img alt text -- we'll differ in that - # regard. - bracket_depth = 0 - for p in range(start_idx+1, min(start_idx+MAX_LINK_TEXT_SENTINEL, - text_length)): - ch = text[p] - if ch == ']': - bracket_depth -= 1 - if bracket_depth < 0: - break - elif ch == '[': - bracket_depth += 1 - else: - # Closing bracket not found within sentinel length. - # This isn't markup. - curr_pos = start_idx + 1 - continue - link_text = text[start_idx+1:p] - - # Possibly a footnote ref? - if "footnotes" in self.extras and link_text.startswith("^"): - normed_id = re.sub(r'\W', '-', link_text[1:]) - if normed_id in self.footnotes: - self.footnote_ids.append(normed_id) - result = '<sup class="footnote-ref" id="fnref-%s">' \ - '<a href="#fn-%s">%s</a></sup>' \ - % (normed_id, normed_id, len(self.footnote_ids)) - text = text[:start_idx] + result + text[p+1:] - else: - # This id isn't defined, leave the markup alone. - curr_pos = p+1 - continue - - # Now determine what this is by the remainder. - p += 1 - if p == text_length: - return text - - # Inline anchor or img? - if text[p] == '(': # attempt at perf improvement - url, title, url_end_idx = self._extract_url_and_title(text, p) - if url is not None: - # Handle an inline anchor or img. - is_img = start_idx > 0 and text[start_idx-1] == "!" - if is_img: - start_idx -= 1 - - # We've got to encode these to avoid conflicting - # with italics/bold. - url = url.replace('*', self._escape_table['*']) \ - .replace('_', self._escape_table['_']) - if title: - title_str = ' title="%s"' % ( - _xml_escape_attr(title) - .replace('*', self._escape_table['*']) - .replace('_', self._escape_table['_'])) - else: - title_str = '' - if is_img: - img_class_str = self._html_class_str_from_tag("img") - result = '<img src="%s" alt="%s"%s%s%s' \ - % (url.replace('"', '"'), - _xml_escape_attr(link_text), - title_str, img_class_str, self.empty_element_suffix) - if "smarty-pants" in self.extras: - result = result.replace('"', self._escape_table['"']) - curr_pos = start_idx + len(result) - text = text[:start_idx] + result + text[url_end_idx:] - elif start_idx >= anchor_allowed_pos: - result_head = '<a href="%s"%s>' % (url, title_str) - result = '%s%s</a>' % (result_head, link_text) - if "smarty-pants" in self.extras: - result = result.replace('"', self._escape_table['"']) - # <img> allowed from curr_pos on, <a> from - # anchor_allowed_pos on. - curr_pos = start_idx + len(result_head) - anchor_allowed_pos = start_idx + len(result) - text = text[:start_idx] + result + text[url_end_idx:] - else: - # Anchor not allowed here. - curr_pos = start_idx + 1 - continue - - # Reference anchor or img? - else: - match = self._tail_of_reference_link_re.match(text, p) - if match: - # Handle a reference-style anchor or img. - is_img = start_idx > 0 and text[start_idx-1] == "!" - if is_img: - start_idx -= 1 - link_id = match.group("id").lower() - if not link_id: - link_id = link_text.lower() # for links like [this][] - if link_id in self.urls: - url = self.urls[link_id] - # We've got to encode these to avoid conflicting - # with italics/bold. - url = url.replace('*', self._escape_table['*']) \ - .replace('_', self._escape_table['_']) - title = self.titles.get(link_id) - if title: - title = _xml_escape_attr(title) \ - .replace('*', self._escape_table['*']) \ - .replace('_', self._escape_table['_']) - title_str = ' title="%s"' % title - else: - title_str = '' - if is_img: - img_class_str = self._html_class_str_from_tag("img") - result = '<img src="%s" alt="%s"%s%s%s' \ - % (url.replace('"', '"'), - link_text.replace('"', '"'), - title_str, img_class_str, self.empty_element_suffix) - if "smarty-pants" in self.extras: - result = result.replace('"', self._escape_table['"']) - curr_pos = start_idx + len(result) - text = text[:start_idx] + result + text[match.end():] - elif start_idx >= anchor_allowed_pos: - result = '<a href="%s"%s>%s</a>' \ - % (url, title_str, link_text) - result_head = '<a href="%s"%s>' % (url, title_str) - result = '%s%s</a>' % (result_head, link_text) - if "smarty-pants" in self.extras: - result = result.replace('"', self._escape_table['"']) - # <img> allowed from curr_pos on, <a> from - # anchor_allowed_pos on. - curr_pos = start_idx + len(result_head) - anchor_allowed_pos = start_idx + len(result) - text = text[:start_idx] + result + text[match.end():] - else: - # Anchor not allowed here. - curr_pos = start_idx + 1 - else: - # This id isn't defined, leave the markup alone. - curr_pos = match.end() - continue - - # Otherwise, it isn't markup. - curr_pos = start_idx + 1 - - return text - - def header_id_from_text(self, text, prefix, n): - """Generate a header id attribute value from the given header - HTML content. - - This is only called if the "header-ids" extra is enabled. - Subclasses may override this for different header ids. - - @param text {str} The text of the header tag - @param prefix {str} The requested prefix for header ids. This is the - value of the "header-ids" extra key, if any. Otherwise, None. - @param n {int} The <hN> tag number, i.e. `1` for an <h1> tag. - @returns {str} The value for the header tag's "id" attribute. Return - None to not have an id attribute and to exclude this header from - the TOC (if the "toc" extra is specified). - """ - header_id = _slugify(text) - if prefix and isinstance(prefix, base_string_type): - header_id = prefix + '-' + header_id - if header_id in self._count_from_header_id: - self._count_from_header_id[header_id] += 1 - header_id += '-%s' % self._count_from_header_id[header_id] - else: - self._count_from_header_id[header_id] = 1 - return header_id - - _toc = None - def _toc_add_entry(self, level, id, name): - if self._toc is None: - self._toc = [] - self._toc.append((level, id, self._unescape_special_chars(name))) - - _h_re_base = r''' - (^(.+)[ \t]*\n(=+|-+)[ \t]*\n+) - | - (^(\#{1,6}) # \1 = string of #'s - [ \t]%s - (.+?) # \2 = Header text - [ \t]* - (?<!\\) # ensure not an escaped trailing '#' - \#* # optional closing #'s (not counted) - \n+ - ) - ''' - - _h_re = re.compile(_h_re_base % '*', re.X | re.M) - _h_re_tag_friendly = re.compile(_h_re_base % '+', re.X | re.M) - - def _h_sub(self, match): - if match.group(1) is not None: - # Setext header - n = {"=": 1, "-": 2}[match.group(3)[0]] - header_group = match.group(2) - else: - # atx header - n = len(match.group(5)) - header_group = match.group(6) - - demote_headers = self.extras.get("demote-headers") - if demote_headers: - n = min(n + demote_headers, 6) - header_id_attr = "" - if "header-ids" in self.extras: - header_id = self.header_id_from_text(header_group, - self.extras["header-ids"], n) - if header_id: - header_id_attr = ' id="%s"' % header_id - html = self._run_span_gamut(header_group) - if "toc" in self.extras and header_id: - self._toc_add_entry(n, header_id, html) - return "<h%d%s>%s</h%d>\n\n" % (n, header_id_attr, html, n) - - def _do_headers(self, text): - # Setext-style headers: - # Header 1 - # ======== - # - # Header 2 - # -------- - - # atx-style headers: - # # Header 1 - # ## Header 2 - # ## Header 2 with closing hashes ## - # ... - # ###### Header 6 - - if 'tag-friendly' in self.extras: - return self._h_re_tag_friendly.sub(self._h_sub, text) - return self._h_re.sub(self._h_sub, text) - - _marker_ul_chars = '*+-' - _marker_any = r'(?:[%s]|\d+\.)' % _marker_ul_chars - _marker_ul = '(?:[%s])' % _marker_ul_chars - _marker_ol = r'(?:\d+\.)' - - def _list_sub(self, match): - lst = match.group(1) - lst_type = match.group(3) in self._marker_ul_chars and "ul" or "ol" - result = self._process_list_items(lst) - if self.list_level: - return "<%s>\n%s</%s>\n" % (lst_type, result, lst_type) - else: - return "<%s>\n%s</%s>\n\n" % (lst_type, result, lst_type) - - def _do_lists(self, text): - # Form HTML ordered (numbered) and unordered (bulleted) lists. - - # Iterate over each *non-overlapping* list match. - pos = 0 - while True: - # Find the *first* hit for either list style (ul or ol). We - # match ul and ol separately to avoid adjacent lists of different - # types running into each other (see issue #16). - hits = [] - for marker_pat in (self._marker_ul, self._marker_ol): - less_than_tab = self.tab_width - 1 - whole_list = r''' - ( # \1 = whole list - ( # \2 - [ ]{0,%d} - (%s) # \3 = first list item marker - [ \t]+ - (?!\ *\3\ ) # '- - - ...' isn't a list. See 'not_quite_a_list' test case. - ) - (?:.+?) - ( # \4 - \Z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another list item marker - [ \t]* - %s[ \t]+ - ) - ) - ) - ''' % (less_than_tab, marker_pat, marker_pat) - if self.list_level: # sub-list - list_re = re.compile("^"+whole_list, re.X | re.M | re.S) - else: - list_re = re.compile(r"(?:(?<=\n\n)|\A\n?)"+whole_list, - re.X | re.M | re.S) - match = list_re.search(text, pos) - if match: - hits.append((match.start(), match)) - if not hits: - break - hits.sort() - match = hits[0][1] - start, end = match.span() - middle = self._list_sub(match) - text = text[:start] + middle + text[end:] - pos = start + len(middle) # start pos for next attempted match - - return text - - _list_item_re = re.compile(r''' - (\n)? # leading line = \1 - (^[ \t]*) # leading whitespace = \2 - (?P<marker>%s) [ \t]+ # list marker = \3 - ((?:.+?) # list item text = \4 - (\n{1,2})) # eols = \5 - (?= \n* (\Z | \2 (?P<next_marker>%s) [ \t]+)) - ''' % (_marker_any, _marker_any), - re.M | re.X | re.S) - - _task_list_item_re = re.compile(r''' - (\[[\ x]\])[ \t]+ # tasklist marker = \1 - (.*) # list item text = \2 - ''', re.M | re.X | re.S) - - _task_list_warpper_str = r'<p><input type="checkbox" class="task-list-item-checkbox" %sdisabled>%s</p>' - - def _task_list_item_sub(self, match): - marker = match.group(1) - item_text = match.group(2) - if marker == '[x]': - return self._task_list_warpper_str % ('checked ', item_text) - elif marker == '[ ]': - return self._task_list_warpper_str % ('', item_text) - - _last_li_endswith_two_eols = False - def _list_item_sub(self, match): - item = match.group(4) - leading_line = match.group(1) - if leading_line or "\n\n" in item or self._last_li_endswith_two_eols: - item = self._run_block_gamut(self._outdent(item)) - else: - # Recursion for sub-lists: - item = self._do_lists(self._outdent(item)) - if item.endswith('\n'): - item = item[:-1] - item = self._run_span_gamut(item) - self._last_li_endswith_two_eols = (len(match.group(5)) == 2) - - if "task_list" in self.extras: - item = self._task_list_item_re.sub(self._task_list_item_sub, item) - - return "<li>%s</li>\n" % item - - def _process_list_items(self, list_str): - # Process the contents of a single ordered or unordered list, - # splitting it into individual list items. - - # The $g_list_level global keeps track of when we're inside a list. - # Each time we enter a list, we increment it; when we leave a list, - # we decrement. If it's zero, we're not in a list anymore. - # - # We do this because when we're not inside a list, we want to treat - # something like this: - # - # I recommend upgrading to version - # 8. Oops, now this line is treated - # as a sub-list. - # - # As a single paragraph, despite the fact that the second line starts - # with a digit-period-space sequence. - # - # Whereas when we're inside a list (or sub-list), that line will be - # treated as the start of a sub-list. What a kludge, huh? This is - # an aspect of Markdown's syntax that's hard to parse perfectly - # without resorting to mind-reading. Perhaps the solution is to - # change the syntax rules such that sub-lists must start with a - # starting cardinal number; e.g. "1." or "a.". - self.list_level += 1 - self._last_li_endswith_two_eols = False - list_str = list_str.rstrip('\n') + '\n' - list_str = self._list_item_re.sub(self._list_item_sub, list_str) - self.list_level -= 1 - return list_str - - def _get_pygments_lexer(self, lexer_name): - try: - from pygments import lexers, util - except ImportError: - return None - try: - return lexers.get_lexer_by_name(lexer_name) - except util.ClassNotFound: - return None - - def _color_with_pygments(self, codeblock, lexer, **formatter_opts): - import pygments - import pygments.formatters - - class HtmlCodeFormatter(pygments.formatters.HtmlFormatter): - def _wrap_code(self, inner): - """A function for use in a Pygments Formatter which - wraps in <code> tags. - """ - yield 0, "<code>" - for tup in inner: - yield tup - yield 0, "</code>" - - def wrap(self, source, outfile): - """Return the source with a code, pre, and div.""" - return self._wrap_div(self._wrap_pre(self._wrap_code(source))) - - formatter_opts.setdefault("cssclass", "codehilite") - formatter = HtmlCodeFormatter(**formatter_opts) - return pygments.highlight(codeblock, lexer, formatter) - - def _code_block_sub(self, match, is_fenced_code_block=False): - lexer_name = None - if is_fenced_code_block: - lexer_name = match.group(1) - if lexer_name: - formatter_opts = self.extras['fenced-code-blocks'] or {} - codeblock = match.group(2) - codeblock = codeblock[:-1] # drop one trailing newline - else: - codeblock = match.group(1) - codeblock = self._outdent(codeblock) - codeblock = self._detab(codeblock) - codeblock = codeblock.lstrip('\n') # trim leading newlines - codeblock = codeblock.rstrip() # trim trailing whitespace - - # Note: "code-color" extra is DEPRECATED. - if "code-color" in self.extras and codeblock.startswith(":::"): - lexer_name, rest = codeblock.split('\n', 1) - lexer_name = lexer_name[3:].strip() - codeblock = rest.lstrip("\n") # Remove lexer declaration line. - formatter_opts = self.extras['code-color'] or {} - - if lexer_name: - def unhash_code(codeblock): - for key, sanitized in list(self.html_spans.items()): - codeblock = codeblock.replace(key, sanitized) - replacements = [ - ("&", "&"), - ("<", "<"), - (">", ">") - ] - for old, new in replacements: - codeblock = codeblock.replace(old, new) - return codeblock - lexer = self._get_pygments_lexer(lexer_name) - if lexer: - codeblock = unhash_code( codeblock ) - colored = self._color_with_pygments(codeblock, lexer, - **formatter_opts) - return "\n\n%s\n\n" % colored - - codeblock = self._encode_code(codeblock) - pre_class_str = self._html_class_str_from_tag("pre") - code_class_str = self._html_class_str_from_tag("code") - return "\n\n<pre%s><code%s>%s\n</code></pre>\n\n" % ( - pre_class_str, code_class_str, codeblock) - - def _html_class_str_from_tag(self, tag): - """Get the appropriate ' class="..."' string (note the leading - space), if any, for the given tag. - """ - if "html-classes" not in self.extras: - return "" - try: - html_classes_from_tag = self.extras["html-classes"] - except TypeError: - return "" - else: - if tag in html_classes_from_tag: - return ' class="%s"' % html_classes_from_tag[tag] - return "" - - def _do_code_blocks(self, text): - """Process Markdown `<pre><code>` blocks.""" - code_block_re = re.compile(r''' - (?:\n\n|\A\n?) - ( # $1 = the code block -- one or more lines, starting with a space/tab - (?: - (?:[ ]{%d} | \t) # Lines must start with a tab or a tab-width of spaces - .*\n+ - )+ - ) - ((?=^[ ]{0,%d}\S)|\Z) # Lookahead for non-space at line-start, or end of doc - # Lookahead to make sure this block isn't already in a code block. - # Needed when syntax highlighting is being used. - (?![^<]*\</code\>) - ''' % (self.tab_width, self.tab_width), - re.M | re.X) - return code_block_re.sub(self._code_block_sub, text) - - _fenced_code_block_re = re.compile(r''' - (?:\n+|\A\n?) - ^```([\w+-]+)?[ \t]*\n # opening fence, $1 = optional lang - (.*?) # $2 = code block content - ^```[ \t]*\n # closing fence - ''', re.M | re.X | re.S) - - def _fenced_code_block_sub(self, match): - return self._code_block_sub(match, is_fenced_code_block=True) - - def _do_fenced_code_blocks(self, text): - """Process ```-fenced unindented code blocks ('fenced-code-blocks' extra).""" - return self._fenced_code_block_re.sub(self._fenced_code_block_sub, text) - - # Rules for a code span: - # - backslash escapes are not interpreted in a code span - # - to include one or or a run of more backticks the delimiters must - # be a longer run of backticks - # - cannot start or end a code span with a backtick; pad with a - # space and that space will be removed in the emitted HTML - # See `test/tm-cases/escapes.text` for a number of edge-case - # examples. - _code_span_re = re.compile(r''' - (?<!\\) - (`+) # \1 = Opening run of ` - (?!`) # See Note A test/tm-cases/escapes.text - (.+?) # \2 = The code block - (?<!`) - \1 # Matching closer - (?!`) - ''', re.X | re.S) - - def _code_span_sub(self, match): - c = match.group(2).strip(" \t") - c = self._encode_code(c) - return "<code>%s</code>" % c - - def _do_code_spans(self, text): - # * Backtick quotes are used for <code></code> spans. - # - # * You can use multiple backticks as the delimiters if you want to - # include literal backticks in the code span. So, this input: - # - # Just type ``foo `bar` baz`` at the prompt. - # - # Will translate to: - # - # <p>Just type <code>foo `bar` baz</code> at the prompt.</p> - # - # There's no arbitrary limit to the number of backticks you - # can use as delimters. If you need three consecutive backticks - # in your code, use four for delimiters, etc. - # - # * You can use spaces to get literal backticks at the edges: - # - # ... type `` `bar` `` ... - # - # Turns to: - # - # ... type <code>`bar`</code> ... - return self._code_span_re.sub(self._code_span_sub, text) - - def _encode_code(self, text): - """Encode/escape certain characters inside Markdown code runs. - The point is that in code, these characters are literals, - and lose their special Markdown meanings. - """ - replacements = [ - # Encode all ampersands; HTML entities are not - # entities within a Markdown code span. - ('&', '&'), - # Do the angle bracket song and dance: - ('<', '<'), - ('>', '>'), - ] - for before, after in replacements: - text = text.replace(before, after) - hashed = _hash_text(text) - self._escape_table[text] = hashed - return hashed - - _strike_re = re.compile(r"~~(?=\S)(.+?)(?<=\S)~~", re.S) - def _do_strike(self, text): - text = self._strike_re.sub(r"<strike>\1</strike>", text) - return text - - _strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S) - _em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S) - _code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S) - _code_friendly_em_re = re.compile(r"\*(?=\S)(.+?)(?<=\S)\*", re.S) - def _do_italics_and_bold(self, text): - # <strong> must go first: - if "code-friendly" in self.extras: - text = self._code_friendly_strong_re.sub(r"<strong>\1</strong>", text) - text = self._code_friendly_em_re.sub(r"<em>\1</em>", text) - else: - text = self._strong_re.sub(r"<strong>\2</strong>", text) - text = self._em_re.sub(r"<em>\2</em>", text) - return text - - # "smarty-pants" extra: Very liberal in interpreting a single prime as an - # apostrophe; e.g. ignores the fact that "round", "bout", "twer", and - # "twixt" can be written without an initial apostrophe. This is fine because - # using scare quotes (single quotation marks) is rare. - _apostrophe_year_re = re.compile(r"'(\d\d)(?=(\s|,|;|\.|\?|!|$))") - _contractions = ["tis", "twas", "twer", "neath", "o", "n", - "round", "bout", "twixt", "nuff", "fraid", "sup"] - def _do_smart_contractions(self, text): - text = self._apostrophe_year_re.sub(r"’\1", text) - for c in self._contractions: - text = text.replace("'%s" % c, "’%s" % c) - text = text.replace("'%s" % c.capitalize(), - "’%s" % c.capitalize()) - return text - - # Substitute double-quotes before single-quotes. - _opening_single_quote_re = re.compile(r"(?<!\S)'(?=\S)") - _opening_double_quote_re = re.compile(r'(?<!\S)"(?=\S)') - _closing_single_quote_re = re.compile(r"(?<=\S)'") - _closing_double_quote_re = re.compile(r'(?<=\S)"(?=(\s|,|;|\.|\?|!|$))') - def _do_smart_punctuation(self, text): - """Fancifies 'single quotes', "double quotes", and apostrophes. - Converts --, ---, and ... into en dashes, em dashes, and ellipses. - - Inspiration is: <http://daringfireball.net/projects/smartypants/> - See "test/tm-cases/smarty_pants.text" for a full discussion of the - support here and - <http://code.google.com/p/python-markdown2/issues/detail?id=42> for a - discussion of some diversion from the original SmartyPants. - """ - if "'" in text: # guard for perf - text = self._do_smart_contractions(text) - text = self._opening_single_quote_re.sub("‘", text) - text = self._closing_single_quote_re.sub("’", text) - - if '"' in text: # guard for perf - text = self._opening_double_quote_re.sub("“", text) - text = self._closing_double_quote_re.sub("”", text) - - text = text.replace("---", "—") - text = text.replace("--", "–") - text = text.replace("...", "…") - text = text.replace(" . . . ", "…") - text = text.replace(". . .", "…") - return text - - _block_quote_base = r''' - ( # Wrap whole match in \1 - ( - ^[ \t]*>%s[ \t]? # '>' at the start of a line - .+\n # rest of the first line - (.+\n)* # subsequent consecutive lines - \n* # blanks - )+ - ) - ''' - _block_quote_re = re.compile(_block_quote_base % '', re.M | re.X) - _block_quote_re_spoiler = re.compile(_block_quote_base % '[ \t]*?!?', re.M | re.X) - _bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M) - _bq_one_level_re_spoiler = re.compile('^[ \t]*>[ \t]*?![ \t]?', re.M) - _bq_all_lines_spoilers = re.compile(r'\A(?:^[ \t]*>[ \t]*?!.*[\n\r]*)+\Z', re.M) - _html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S) - def _dedent_two_spaces_sub(self, match): - return re.sub(r'(?m)^ ', '', match.group(1)) - - def _block_quote_sub(self, match): - bq = match.group(1) - is_spoiler = 'spoiler' in self.extras and self._bq_all_lines_spoilers.match(bq) - # trim one level of quoting - if is_spoiler: - bq = self._bq_one_level_re_spoiler.sub('', bq) - else: - bq = self._bq_one_level_re.sub('', bq) - # trim whitespace-only lines - bq = self._ws_only_line_re.sub('', bq) - bq = self._run_block_gamut(bq) # recurse - - bq = re.sub('(?m)^', ' ', bq) - # These leading spaces screw with <pre> content, so we need to fix that: - bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq) - - if is_spoiler: - return '<blockquote class="spoiler">\n%s\n</blockquote>\n\n' % bq - else: - return '<blockquote>\n%s\n</blockquote>\n\n' % bq - - def _do_block_quotes(self, text): - if '>' not in text: - return text - if 'spoiler' in self.extras: - return self._block_quote_re_spoiler.sub(self._block_quote_sub, text) - else: - return self._block_quote_re.sub(self._block_quote_sub, text) - - def _form_paragraphs(self, text): - # Strip leading and trailing lines: - text = text.strip('\n') - - # Wrap <p> tags. - grafs = [] - for i, graf in enumerate(re.split(r"\n{2,}", text)): - if graf in self.html_blocks: - # Unhashify HTML blocks - grafs.append(self.html_blocks[graf]) - else: - cuddled_list = None - if "cuddled-lists" in self.extras: - # Need to put back trailing '\n' for `_list_item_re` - # match at the end of the paragraph. - li = self._list_item_re.search(graf + '\n') - # Two of the same list marker in this paragraph: a likely - # candidate for a list cuddled to preceding paragraph - # text (issue 33). Note the `[-1]` is a quick way to - # consider numeric bullets (e.g. "1." and "2.") to be - # equal. - if (li and len(li.group(2)) <= 3 and li.group("next_marker") - and li.group("marker")[-1] == li.group("next_marker")[-1]): - start = li.start() - cuddled_list = self._do_lists(graf[start:]).rstrip("\n") - assert cuddled_list.startswith("<ul>") or cuddled_list.startswith("<ol>") - graf = graf[:start] - - # Wrap <p> tags. - graf = self._run_span_gamut(graf) - grafs.append("<p>" + graf.lstrip(" \t") + "</p>") - - if cuddled_list: - grafs.append(cuddled_list) - - return "\n\n".join(grafs) - - def _add_footnotes(self, text): - if self.footnotes: - footer = [ - '<div class="footnotes">', - '<hr' + self.empty_element_suffix, - '<ol>', - ] - for i, id in enumerate(self.footnote_ids): - if i != 0: - footer.append('') - footer.append('<li id="fn-%s">' % id) - footer.append(self._run_block_gamut(self.footnotes[id])) - backlink = ('<a href="#fnref-%s" ' - 'class="footnoteBackLink" ' - 'title="Jump back to footnote %d in the text.">' - '↩</a>' % (id, i+1)) - if footer[-1].endswith("</p>"): - footer[-1] = footer[-1][:-len("</p>")] \ - + ' ' + backlink + "</p>" - else: - footer.append("\n<p>%s</p>" % backlink) - footer.append('</li>') - footer.append('</ol>') - footer.append('</div>') - return text + '\n\n' + '\n'.join(footer) - else: - return text - - # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: - # http://bumppo.net/projects/amputator/ - _ampersand_re = re.compile(r'&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)') - _naked_lt_re = re.compile(r'<(?![a-z/?\$!])', re.I) - _naked_gt_re = re.compile(r'''(?<![a-z0-9?!/'"-])>''', re.I) - - def _encode_amps_and_angles(self, text): - # Smart processing for ampersands and angle brackets that need - # to be encoded. - text = self._ampersand_re.sub('&', text) - - # Encode naked <'s - text = self._naked_lt_re.sub('<', text) - - # Encode naked >'s - # Note: Other markdown implementations (e.g. Markdown.pl, PHP - # Markdown) don't do this. - text = self._naked_gt_re.sub('>', text) - return text - - def _encode_backslash_escapes(self, text): - for ch, escape in list(self._escape_table.items()): - text = text.replace("\\"+ch, escape) - return text - - _auto_link_re = re.compile(r'<((https?|ftp):[^\'">\s]+)>', re.I) - def _auto_link_sub(self, match): - g1 = match.group(1) - return '<a href="%s">%s</a>' % (g1, g1) - - _auto_email_link_re = re.compile(r""" - < - (?:mailto:)? - ( - [-.\w]+ - \@ - [-\w]+(\.[-\w]+)*\.[a-z]+ - ) - > - """, re.I | re.X | re.U) - def _auto_email_link_sub(self, match): - return self._encode_email_address( - self._unescape_special_chars(match.group(1))) - - def _do_auto_links(self, text): - text = self._auto_link_re.sub(self._auto_link_sub, text) - text = self._auto_email_link_re.sub(self._auto_email_link_sub, text) - return text - - def _encode_email_address(self, addr): - # Input: an email address, e.g. "foo@example.com" - # - # Output: the email address as a mailto link, with each character - # of the address encoded as either a decimal or hex entity, in - # the hopes of foiling most address harvesting spam bots. E.g.: - # - # <a href="mailto:foo@e - # xample.com">foo - # @example.com</a> - # - # Based on a filter by Matthew Wickline, posted to the BBEdit-Talk - # mailing list: <http://tinyurl.com/yu7ue> - chars = [_xml_encode_email_char_at_random(ch) - for ch in "mailto:" + addr] - # Strip the mailto: from the visible part. - addr = '<a href="%s">%s</a>' \ - % (''.join(chars), ''.join(chars[7:])) - return addr - - def _do_link_patterns(self, text): - """Caveat emptor: there isn't much guarding against link - patterns being formed inside other standard Markdown links, e.g. - inside a [link def][like this]. - - Dev Notes: *Could* consider prefixing regexes with a negative - lookbehind assertion to attempt to guard against this. - """ - link_from_hash = {} - for regex, repl in self.link_patterns: - replacements = [] - for match in regex.finditer(text): - if hasattr(repl, "__call__"): - href = repl(match) - else: - href = match.expand(repl) - replacements.append((match.span(), href)) - for (start, end), href in reversed(replacements): - escaped_href = ( - href.replace('"', '"') # b/c of attr quote - # To avoid markdown <em> and <strong>: - .replace('*', self._escape_table['*']) - .replace('_', self._escape_table['_'])) - link = '<a href="%s">%s</a>' % (escaped_href, text[start:end]) - hash = _hash_text(link) - link_from_hash[hash] = link - text = text[:start] + hash + text[end:] - for hash, link in list(link_from_hash.items()): - text = text.replace(hash, link) - return text - - def _unescape_special_chars(self, text): - # Swap back in all the special characters we've hidden. - for ch, hash in list(self._escape_table.items()): - text = text.replace(hash, ch) - return text - - def _outdent(self, text): - # Remove one level of line-leading tabs or spaces - return self._outdent_re.sub('', text) - - -class MarkdownWithExtras(Markdown): - """A markdowner class that enables most extras: - - - footnotes - - code-color (only has effect if 'pygments' Python module on path) - - These are not included: - - pyshell (specific to Python-related documenting) - - code-friendly (because it *disables* part of the syntax) - - link-patterns (because you need to specify some actual - link-patterns anyway) - """ - extras = ["footnotes", "code-color"] - - -# ---- internal support functions - -class UnicodeWithAttrs(unicode): - """A subclass of unicode used for the return value of conversion to - possibly attach some attributes. E.g. the "toc_html" attribute when - the "toc" extra is used. - """ - metadata = None - _toc = None - def toc_html(self): - """Return the HTML for the current TOC. - - This expects the `_toc` attribute to have been set on this instance. - """ - if self._toc is None: - return None - - def indent(): - return ' ' * (len(h_stack) - 1) - lines = [] - h_stack = [0] # stack of header-level numbers - for level, id, name in self._toc: - if level > h_stack[-1]: - lines.append("%s<ul>" % indent()) - h_stack.append(level) - elif level == h_stack[-1]: - lines[-1] += "</li>" - else: - while level < h_stack[-1]: - h_stack.pop() - if not lines[-1].endswith("</li>"): - lines[-1] += "</li>" - lines.append("%s</ul></li>" % indent()) - lines.append('%s<li><a href="#%s">%s</a>' % ( - indent(), id, name)) - while len(h_stack) > 1: - h_stack.pop() - if not lines[-1].endswith("</li>"): - lines[-1] += "</li>" - lines.append("%s</ul>" % indent()) - return '\n'.join(lines) + '\n' - toc_html = property(toc_html) - -## {{{ http://code.activestate.com/recipes/577257/ (r1) -_slugify_strip_re = re.compile(r'[^\w\s-]') -_slugify_hyphenate_re = re.compile(r'[-\s]+') -def _slugify(value): - """ - Normalizes string, converts to lowercase, removes non-alpha characters, - and converts spaces to hyphens. - - From Django's "django/template/defaultfilters.py". - """ - import unicodedata - value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode() - value = _slugify_strip_re.sub('', value).strip().lower() - return _slugify_hyphenate_re.sub('-', value) -## end of http://code.activestate.com/recipes/577257/ }}} - - -# From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549 -def _curry(*args, **kwargs): - function, args = args[0], args[1:] - def result(*rest, **kwrest): - combined = kwargs.copy() - combined.update(kwrest) - return function(*args + rest, **combined) - return result - - -# Recipe: regex_from_encoded_pattern (1.0) -def _regex_from_encoded_pattern(s): - """'foo' -> re.compile(re.escape('foo')) - '/foo/' -> re.compile('foo') - '/foo/i' -> re.compile('foo', re.I) - """ - if s.startswith('/') and s.rfind('/') != 0: - # Parse it: /PATTERN/FLAGS - idx = s.rfind('/') - pattern, flags_str = s[1:idx], s[idx+1:] - flag_from_char = { - "i": re.IGNORECASE, - "l": re.LOCALE, - "s": re.DOTALL, - "m": re.MULTILINE, - "u": re.UNICODE, - } - flags = 0 - for char in flags_str: - try: - flags |= flag_from_char[char] - except KeyError: - raise ValueError("unsupported regex flag: '%s' in '%s' " - "(must be one of '%s')" - % (char, s, ''.join(list(flag_from_char.keys())))) - return re.compile(s[1:idx], flags) - else: # not an encoded regex - return re.compile(re.escape(s)) - - -# Recipe: dedent (0.1.2) -def _dedentlines(lines, tabsize=8, skip_first_line=False): - """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines - - "lines" is a list of lines to dedent. - "tabsize" is the tab width to use for indent width calculations. - "skip_first_line" is a boolean indicating if the first line should - be skipped for calculating the indent width and for dedenting. - This is sometimes useful for docstrings and similar. - - Same as dedent() except operates on a sequence of lines. Note: the - lines list is modified **in-place**. - """ - DEBUG = False - if DEBUG: - print("dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\ - % (tabsize, skip_first_line)) - margin = None - for i, line in enumerate(lines): - if i == 0 and skip_first_line: continue - indent = 0 - for ch in line: - if ch == ' ': - indent += 1 - elif ch == '\t': - indent += tabsize - (indent % tabsize) - elif ch in '\r\n': - continue # skip all-whitespace lines - else: - break - else: - continue # skip all-whitespace lines - if DEBUG: print("dedent: indent=%d: %r" % (indent, line)) - if margin is None: - margin = indent - else: - margin = min(margin, indent) - if DEBUG: print("dedent: margin=%r" % margin) - - if margin is not None and margin > 0: - for i, line in enumerate(lines): - if i == 0 and skip_first_line: continue - removed = 0 - for j, ch in enumerate(line): - if ch == ' ': - removed += 1 - elif ch == '\t': - removed += tabsize - (removed % tabsize) - elif ch in '\r\n': - if DEBUG: print("dedent: %r: EOL -> strip up to EOL" % line) - lines[i] = lines[i][j:] - break - else: - raise ValueError("unexpected non-whitespace char %r in " - "line %r while removing %d-space margin" - % (ch, line, margin)) - if DEBUG: - print("dedent: %r: %r -> removed %d/%d"\ - % (line, ch, removed, margin)) - if removed == margin: - lines[i] = lines[i][j+1:] - break - elif removed > margin: - lines[i] = ' '*(removed-margin) + lines[i][j+1:] - break - else: - if removed: - lines[i] = lines[i][removed:] - return lines - - -def _dedent(text, tabsize=8, skip_first_line=False): - """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text - - "text" is the text to dedent. - "tabsize" is the tab width to use for indent width calculations. - "skip_first_line" is a boolean indicating if the first line should - be skipped for calculating the indent width and for dedenting. - This is sometimes useful for docstrings and similar. - - textwrap.dedent(s), but don't expand tabs to spaces - """ - lines = text.splitlines(1) - _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line) - return ''.join(lines) - - -class _memoized(object): - """Decorator that caches a function's return value each time it is called. - If called later with the same arguments, the cached value is returned, and - not re-evaluated. - - http://wiki.python.org/moin/PythonDecoratorLibrary - """ - def __init__(self, func): - self.func = func - self.cache = {} - - def __call__(self, *args): - try: - return self.cache[args] - except KeyError: - self.cache[args] = value = self.func(*args) - return value - except TypeError: - # uncachable -- for instance, passing a list as an argument. - # Better to not cache than to blow up entirely. - return self.func(*args) - - def __repr__(self): - """Return the function's docstring.""" - return self.func.__doc__ - - -def _xml_oneliner_re_from_tab_width(tab_width): - """Standalone XML processing instruction regex.""" - return re.compile(r""" - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - [ ]{0,%d} - (?: - <\?\w+\b\s+.*?\?> # XML processing instruction - | - <\w+:\w+\b\s+.*?/> # namespaced single tag - ) - [ \t]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - """ % (tab_width - 1), re.X) -_xml_oneliner_re_from_tab_width = _memoized(_xml_oneliner_re_from_tab_width) - - -def _hr_tag_re_from_tab_width(tab_width): - return re.compile(r""" - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in \1 - [ ]{0,%d} - <(hr) # start tag = \2 - \b # word break - ([^<>])*? # - /?> # the matching end tag - [ \t]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - """ % (tab_width - 1), re.X) -_hr_tag_re_from_tab_width = _memoized(_hr_tag_re_from_tab_width) - - -def _xml_escape_attr(attr, skip_single_quote=True): - """Escape the given string for use in an HTML/XML tag attribute. - - By default this doesn't bother with escaping `'` to `'`, presuming that - the tag attribute is surrounded by double quotes. - """ - escaped = (attr - .replace('&', '&') - .replace('"', '"') - .replace('<', '<') - .replace('>', '>')) - if not skip_single_quote: - escaped = escaped.replace("'", "'") - return escaped - - -def _xml_encode_email_char_at_random(ch): - r = random() - # Roughly 10% raw, 45% hex, 45% dec. - # '@' *must* be encoded. I [John Gruber] insist. - # Issue 26: '_' must be encoded. - if r > 0.9 and ch not in "@_": - return ch - elif r < 0.45: - # The [1:] is to drop leading '0': 0x63 -> x63 - return '&#%s;' % hex(ord(ch))[1:] - else: - return '&#%s;' % ord(ch) - - -# ---- mainline - -class _NoReflowFormatter(optparse.IndentedHelpFormatter): - """An optparse formatter that does NOT reflow the description.""" - def format_description(self, description): - return description or "" - - -def _test(): - import doctest - doctest.testmod() - - -def main(argv=None): - if argv is None: - argv = sys.argv - if not logging.root.handlers: - logging.basicConfig() - - usage = "usage: %prog [PATHS...]" - version = "%prog "+__version__ - parser = optparse.OptionParser(prog="markdown2", usage=usage, - version=version, description=cmdln_desc, - formatter=_NoReflowFormatter()) - parser.add_option("-v", "--verbose", dest="log_level", - action="store_const", const=logging.DEBUG, - help="more verbose output") - parser.add_option("--encoding", - help="specify encoding of text content") - parser.add_option("--html4tags", action="store_true", default=False, - help="use HTML 4 style for empty element tags") - parser.add_option("-s", "--safe", metavar="MODE", dest="safe_mode", - help="sanitize literal HTML: 'escape' escapes " - "HTML meta chars, 'replace' replaces with an " - "[HTML_REMOVED] note") - parser.add_option("-x", "--extras", action="append", - help="Turn on specific extra features (not part of " - "the core Markdown spec). See above.") - parser.add_option("--use-file-vars", - help="Look for and use Emacs-style 'markdown-extras' " - "file var to turn on extras. See " - "<https://github.com/trentm/python-markdown2/wiki/Extras>") - parser.add_option("--link-patterns-file", - help="path to a link pattern file") - parser.add_option("--self-test", action="store_true", - help="run internal self-tests (some doctests)") - parser.add_option("--compare", action="store_true", - help="run against Markdown.pl as well (for testing)") - parser.set_defaults(log_level=logging.INFO, compare=False, - encoding="utf-8", safe_mode=None, use_file_vars=False) - opts, paths = parser.parse_args() - log.setLevel(opts.log_level) - - if opts.self_test: - return _test() - - if opts.extras: - extras = {} - for s in opts.extras: - splitter = re.compile("[,;: ]+") - for e in splitter.split(s): - if '=' in e: - ename, earg = e.split('=', 1) - try: - earg = int(earg) - except ValueError: - pass - else: - ename, earg = e, None - extras[ename] = earg - else: - extras = None - - if opts.link_patterns_file: - link_patterns = [] - f = open(opts.link_patterns_file) - try: - for i, line in enumerate(f.readlines()): - if not line.strip(): continue - if line.lstrip().startswith("#"): continue - try: - pat, href = line.rstrip().rsplit(None, 1) - except ValueError: - raise MarkdownError("%s:%d: invalid link pattern line: %r" - % (opts.link_patterns_file, i+1, line)) - link_patterns.append( - (_regex_from_encoded_pattern(pat), href)) - finally: - f.close() - else: - link_patterns = None - - from os.path import join, dirname, abspath, exists - markdown_pl = join(dirname(dirname(abspath(__file__))), "test", - "Markdown.pl") - if not paths: - paths = ['-'] - for path in paths: - if path == '-': - text = sys.stdin.read() - else: - fp = codecs.open(path, 'r', opts.encoding) - text = fp.read() - fp.close() - if opts.compare: - from subprocess import Popen, PIPE - print("==== Markdown.pl ====") - p = Popen('perl %s' % markdown_pl, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True) - p.stdin.write(text.encode('utf-8')) - p.stdin.close() - perl_html = p.stdout.read().decode('utf-8') - if py3: - sys.stdout.write(perl_html) - else: - sys.stdout.write(perl_html.encode( - sys.stdout.encoding or "utf-8", 'xmlcharrefreplace')) - print("==== markdown2.py ====") - html = markdown(text, - html4tags=opts.html4tags, - safe_mode=opts.safe_mode, - extras=extras, link_patterns=link_patterns, - use_file_vars=opts.use_file_vars) - if py3: - sys.stdout.write(html) - else: - sys.stdout.write(html.encode( - sys.stdout.encoding or "utf-8", 'xmlcharrefreplace')) - if extras and "toc" in extras: - log.debug("toc_html: " + - str(html.toc_html.encode(sys.stdout.encoding or "utf-8", 'xmlcharrefreplace'))) - if opts.compare: - test_dir = join(dirname(dirname(abspath(__file__))), "test") - if exists(join(test_dir, "test_markdown2.py")): - sys.path.insert(0, test_dir) - from test_markdown2 import norm_html_from_html - norm_html = norm_html_from_html(html) - norm_perl_html = norm_html_from_html(perl_html) - else: - norm_html = html - norm_perl_html = perl_html - print("==== match? %r ====" % (norm_perl_html == norm_html)) - - -if __name__ == "__main__": - sys.exit(main(sys.argv)) diff --git a/addons21/fastwq/libs/AnkiHub/updates.py b/addons21/fastwq/libs/AnkiHub/updates.py deleted file mode 100644 index 38e7d8c..0000000 --- a/addons21/fastwq/libs/AnkiHub/updates.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- - -from aqt.qt import * - -try: - _encoding = QApplication.UnicodeUTF8 - def _translate(context, text, disambig): - return QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QApplication.translate(context, text, disambig) - -class Ui_DialogUpdates(object): - def setupUi(self, DialogUpdates): - DialogUpdates.setObjectName(u"DialogUpdates") - DialogUpdates.resize(500, 400) - self.verticalLayout = QVBoxLayout(DialogUpdates) - self.verticalLayout.setObjectName(u"verticalLayout") - self.labelUpdates = QLabel(DialogUpdates) - self.labelUpdates.setWordWrap(True) - self.labelUpdates.setOpenExternalLinks(True) - self.labelUpdates.setObjectName(u"labelUpdates") - self.verticalLayout.addWidget(self.labelUpdates) - self.textBrowser = QTextBrowser(DialogUpdates) - self.textBrowser.setObjectName(u"textBrowser") - self.verticalLayout.addWidget(self.textBrowser) - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.update = QPushButton(DialogUpdates) - self.update.setObjectName(u"update") - self.horizontalLayout.addWidget(self.update, 0, Qt.AlignCenter) - self.verticalLayout.addLayout(self.horizontalLayout) - - self.retranslateUi(DialogUpdates) - QMetaObject.connectSlotsByName(DialogUpdates) - - def retranslateUi(self, DialogUpdates): - DialogUpdates.setWindowTitle(_translate("DialogUpdates", "FastWQ - Updater", None)) - self.labelUpdates.setText(_translate( - "DialogUpdates", - "<html><head/><body>\ - <p>A new version of {0} is available for download! </p>\ - <p>Do you want to update {1}to version {2}?</p>\ - <p>Changes from your version are listed below:</p>\ - </body></html>", - None - )) - self.update.setText(_translate("DialogUpdates", "Update", None)) diff --git a/addons21/fastwq/libs/ankihub.py b/addons21/fastwq/libs/ankihub.py deleted file mode 100644 index b009b84..0000000 --- a/addons21/fastwq/libs/ankihub.py +++ /dev/null @@ -1,311 +0,0 @@ -try: - import httplib -except: - import http.client as httplib -try: - import urllib2 -except: - import urllib.request as urllib2 -import json -import os -import sys -import zipfile -import traceback -import io -import aqt -from aqt import mw -from aqt.qt import * -from aqt.utils import showInfo -from anki.hooks import addHook -from anki.utils import isMac, isWin -from ..context import APP_ICON -from .AnkiHub.updates import Ui_DialogUpdates -from .AnkiHub.markdown2 import markdown - - -__all__ = ['update'] - - -# taken from Anki's aqt/profiles.py -def defaultBase(): - path = mw.pm.addonFolder() - return os.path.dirname(os.path.abspath(path)) - - -headers = {"User-Agent": "AnkiHub"} -dataPath = os.path.join(defaultBase(),'.fastwq_2.1.x_ankihub.json') - - -class DialogUpdates(QDialog, Ui_DialogUpdates): - - def __init__(self, parent, data, oldData, callback): - parent = parent if parent else mw - QDialog.__init__(self, parent) - self.setModal(True) - self.setWindowFlags( - self.windowFlags() & - ~Qt.WindowContextHelpButtonHint - ) - self.setWindowIcon(APP_ICON) - self.setupUi(self) - totalSize = sum(map(lambda x:x['size'],data['assets'])) - def answer(): - self.update.setEnabled(False) - callback(self.appendHtml, self.finish) - - self.html = u'' - self.appendHtml(markdown(data['body'])) - - #if not automaticAnswer: - self.update.clicked.connect(lambda:answer()) - - fromVersion = '' - if 'tag_name' in oldData: - fromVersion = u'from {0} '.format(oldData['tag_name']) - self.labelUpdates.setText( - str(self.labelUpdates.text()).format( - data['name'], - fromVersion, - data['tag_name'])) - - def appendHtml(self,html='',temp=''): - self.html += html - self.textBrowser.setHtml(u'<html><body>{0}{1}{2}</body></html>'.format(self.html, temp, u'<div id="text_bottom"></div>')) - self.textBrowser.scrollToAnchor('text_bottom') - - def finish(self): - self.hide() - self.destroy() - showInfo('Updated. Please restart Anki.') - pass - - -def installZipFile(data, fname): - #base = os.path.join(mw.pm.addonFolder(), 'fastwq') - base = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../')) - if fname.endswith(".py"): - path = os.path.join(base, fname) - with open(path, "wb") as file: - file.write(data) - file.close() - return True - # .zip file - try: - z = zipfile.ZipFile(io.BytesIO(data)) - except zipfile.BadZipfile: - return False - for n in z.namelist(): - if n.endswith("/"): - # folder; ignore - continue - # write - try: - z.extract(n, base) - except: - print(n) - return True - - -def asset(a): - return { - 'url': a['browser_download_url'], - 'size': a['size'] - } - - -def updateSingle(repositories, path, data): - def callback(appendHtml, onReady): - for asset in data['assets']: - code = asset['url'] - p, fname = os.path.split(code) - appendHtml(temp='<br />Downloading {1}: {0}%<br/>'.format(0,fname)) - try: - urlthread = UrlThread(code) - urlthread.start() - urlthread.join() - response = urlthread.response#urllib2.urlopen(code) - meta = response.info() - file_size = int(meta.get("Content-Length")) - except: - appendHtml('Downloading file error!<br/>') - return - d = b'' - dl = 0 - i = 0 - lastPercent = None - while True: - dkb = response.read(1024) - if not dkb: - break - dl += len(dkb) - d += dkb - if dl*100/file_size>i: - lastPercent = int(dl*100/file_size) - i = lastPercent+1 - appendHtml(temp='<br />Downloading {1}: {0}%<br/>'.format(lastPercent,fname)) - QApplication.instance().processEvents() - appendHtml('<br />Downloading {1}: 100%<br/>'.format(int(dl*100/file_size),fname)) - appendHtml('Installing ...<br/>') - if not installZipFile(d, fname): - appendHtml('Corrupt file<br/>') - else: - repositories[path] = data - repositories[path]['update'] = 'ask' - with open(dataPath,'w') as f: - json.dump(repositories,f,indent=2) - f.close() - appendHtml('Done.<br/>') - onReady() # close the AnkiHub update window - - return callback - - -def update(add=[], VERSION='v0.0.0', background=False, parent=None): - parent = parent if parent else mw - # progress win - if not background: - progresswin = QProgressDialog('Update Checking...', '', 0, 0, parent) - progresswin.setWindowModality(Qt.ApplicationModal) - progresswin.setCancelButton(None) - progresswin.setWindowFlags( - progresswin.windowFlags() & - ~Qt.WindowContextHelpButtonHint - ) - progresswin.setWindowTitle('FastWQ - Updater') - progresswin.setWindowIcon(APP_ICON) - progresswin.resize(280, 60) - progresswin.show() - else: - progresswin = None - # - conn = httplib.HTTPSConnection("api.github.com") - try: - with open(dataPath,'r') as f: - repositories = json.load(f) - f.close() - except: - repositories = {} - - for a in add: - if a not in repositories: - repositories[a] = { - 'id': 0, - 'update': 'ask' - } - - for path,repository in repositories.items(): - username,repositoryName = path.split('/') - try: - urlthread = UrlThread("https://api.github.com/repos/{0}/releases/latest".format(path)) - urlthread.start() - urlthread.join() - release = json.loads(urlthread.response.read()) - except Exception as e: - release = {} - - if 'id' in release: - if release['id'] != repository['id']: - data = { - 'id': release['id'], - 'name': repositoryName, - 'tag_name': release['tag_name'], - 'body': '### {0}\n'.format(release['name']) + release['body'], - 'assets': [asset(release['assets'][1])], - 'update': 'ask' - } - if 'tag_name' in repository: - oldVersion = map(int,repository['tag_name'][1:].split('.')) - oldVersion = [x for x in oldVersion] - while len(oldVersion)<3: - oldVersion.append(0) - else: - oldVersion = map(int,VERSION[1:].split('.'))#[0,0,0] - oldVersion = [x for x in oldVersion] - newVersion = map(int,data['tag_name'][1:].split('.')) - newVersion = [x for x in newVersion] - isMinor = len(newVersion)>2 and newVersion[2]>0 - while len(newVersion)<3: - newVersion.append(0) - i = oldVersion[2]+1 - if oldVersion[0]<newVersion[0] or oldVersion[1]<newVersion[1]: - if isMinor: - i = 1 - while i<newVersion[2]: - if progresswin and progresswin.wasCanceled(): - break - try: - minorTagName = 'v{0}.{1}.{2}'.format(newVersion[0],oldVersion[1],i) - urlthread = UrlThread("https://api.github.com/repos/{0}/releases/tags/{1}".format(path,minorTagName)) - urlthread.start() - urlthread.join() - responseData = urlthread.response.read() - minor = json.loads(responseData) - data['body'] += '\n\n### {0}\n'.format(minor['name']) + minor['body'] - except: - pass - i += 1 - if oldVersion[0]<newVersion[0] or oldVersion[1]<newVersion[1]: - # new major release necessary! - # if the newest version is minor, fetch the additional assets from the major - if isMinor and (background or not progresswin.wasCanceled()): - try: - majorTagName = 'v{0}.{1}'.format(newVersion[0],newVersion[1]) - urlthread = UrlThread( - "https://api.github.com/repos/{0}/releases/tags/{1}".format(path,majorTagName), - "https://api.github.com/repos/{0}/releases/tags/{1}.0".format(path,majorTagName) - ) - urlthread.start() - urlthread.join() - responseData = urlthread.response.read() - major = json.loads(responseData) - data['body'] += '\n\n### {0}\n'.format(major['name']) + major['body'] - except: - pass - - if background or not progresswin.wasCanceled(): - if progresswin: - progresswin.hide() - progresswin.destroy() - dialog = DialogUpdates(parent, data, repository, updateSingle(repositories, path, data)) - dialog.exec_() - dialog.destroy() - else: - if progresswin: - progresswin.hide() - progresswin.destroy() - return 1 - else: - if progresswin: - progresswin.hide() - progresswin.destroy() - return 0 - if progresswin: - progresswin.hide() - progresswin.destroy() - return -1 - - -class UrlThread(QThread): - - def __init__(self, url, backurl=None): - super(UrlThread, self).__init__() - self.response = None - self.url = url - self.backurl = backurl - self.finished = False - - def run(self): - try: - self.response = urllib2.urlopen(self.url) - except: - if self.backurl: - try: - self.response = urllib2.urlopen(self.backurl) - except: - pass - self.finished = True - - def join(self): - while not self.finished: - QApplication.instance().processEvents() - self.wait(30)