tags.
1208 | """
1209 | yield 0, ""
1210 | for tup in inner:
1211 | yield tup
1212 | yield 0, "
"
1213 |
1214 | def wrap(self, source, outfile):
1215 | """Return the source with a code, pre, and div."""
1216 | return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
1217 |
1218 | formatter = HtmlCodeFormatter(cssclass="codehilite", **formatter_opts)
1219 | return pygments.highlight(codeblock, lexer, formatter)
1220 |
1221 | def _code_block_sub(self, match):
1222 | codeblock = match.group(1)
1223 | codeblock = self._outdent(codeblock)
1224 | codeblock = self._detab(codeblock)
1225 | codeblock = codeblock.lstrip('\n') # trim leading newlines
1226 | codeblock = codeblock.rstrip() # trim trailing whitespace
1227 |
1228 | if "code-color" in self.extras and codeblock.startswith(":::"):
1229 | lexer_name, rest = codeblock.split('\n', 1)
1230 | lexer_name = lexer_name[3:].strip()
1231 | lexer = self._get_pygments_lexer(lexer_name)
1232 | codeblock = rest.lstrip("\n") # Remove lexer declaration line.
1233 | if lexer:
1234 | formatter_opts = self.extras['code-color'] or {}
1235 | colored = self._color_with_pygments(codeblock, lexer,
1236 | **formatter_opts)
1237 | return "\n\n%s\n\n" % colored
1238 |
1239 | codeblock = self._encode_code(codeblock)
1240 | return "\n\n%s\n
\n\n" % codeblock
1241 |
1242 | def _do_code_blocks(self, text):
1243 | """Process Markdown `` blocks."""
1244 | code_block_re = re.compile(r'''
1245 | (?:\n\n|\A)
1246 | ( # $1 = the code block -- one or more lines, starting with a space/tab
1247 | (?:
1248 | (?:[ ]{%d} | \t) # Lines must start with a tab or a tab-width of spaces
1249 | .*\n+
1250 | )+
1251 | )
1252 | ((?=^[ ]{0,%d}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
1253 | ''' % (self.tab_width, self.tab_width),
1254 | re.M | re.X)
1255 |
1256 | return code_block_re.sub(self._code_block_sub, text)
1257 |
1258 | # Rules for a code span:
1259 | # - backslash escapes are not interpreted in a code span
1260 | # - to include one or or a run of more backticks the delimiters must
1261 | # be a longer run of backticks
1262 | # - cannot start or end a code span with a backtick; pad with a
1263 | # space and that space will be removed in the emitted HTML
1264 | # See `test/tm-cases/escapes.text` for a number of edge-case
1265 | # examples.
1266 | _code_span_re = re.compile(r'''
1267 | (?%s
" % c
1280 |
1281 | def _do_code_spans(self, text):
1282 | # * Backtick quotes are used for
spans.
1283 | #
1284 | # * You can use multiple backticks as the delimiters if you want to
1285 | # include literal backticks in the code span. So, this input:
1286 | #
1287 | # Just type ``foo `bar` baz`` at the prompt.
1288 | #
1289 | # Will translate to:
1290 | #
1291 | # Just type foo `bar` baz
at the prompt.
1292 | #
1293 | # There's no arbitrary limit to the number of backticks you
1294 | # can use as delimters. If you need three consecutive backticks
1295 | # in your code, use four for delimiters, etc.
1296 | #
1297 | # * You can use spaces to get literal backticks at the edges:
1298 | #
1299 | # ... type `` `bar` `` ...
1300 | #
1301 | # Turns to:
1302 | #
1303 | # ... type `bar`
...
1304 | return self._code_span_re.sub(self._code_span_sub, text)
1305 |
1306 | def _encode_code(self, text):
1307 | """Encode/escape certain characters inside Markdown code runs.
1308 | The point is that in code, these characters are literals,
1309 | and lose their special Markdown meanings.
1310 | """
1311 | replacements = [
1312 | # Encode all ampersands; HTML entities are not
1313 | # entities within a Markdown code span.
1314 | ('&', '&'),
1315 | # Do the angle bracket song and dance:
1316 | ('<', '<'),
1317 | ('>', '>'),
1318 | # Now, escape characters that are magic in Markdown:
1319 | ('*', g_escape_table['*']),
1320 | ('_', g_escape_table['_']),
1321 | ('{', g_escape_table['{']),
1322 | ('}', g_escape_table['}']),
1323 | ('[', g_escape_table['[']),
1324 | (']', g_escape_table[']']),
1325 | ('\\', g_escape_table['\\']),
1326 | ]
1327 | for before, after in replacements:
1328 | text = text.replace(before, after)
1329 | return text
1330 |
1331 | _strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S)
1332 | _em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S)
1333 | _code_friendly_strong_re = re.compile(
1334 | r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S)
1335 | _code_friendly_em_re = re.compile(r"\*(?=\S)(.+?)(?<=\S)\*", re.S)
1336 |
1337 | def _do_italics_and_bold(self, text):
1338 | # must go first:
1339 | if "code-friendly" in self.extras:
1340 | text = self._code_friendly_strong_re.sub(
1341 | r"\1", text)
1342 | text = self._code_friendly_em_re.sub(r"\1", text)
1343 | else:
1344 | text = self._strong_re.sub(r"\2", text)
1345 | text = self._em_re.sub(r"\2", text)
1346 | return text
1347 |
1348 | _block_quote_re = re.compile(r'''
1349 | ( # Wrap whole match in \1
1350 | (
1351 | ^[ \t]*>[ \t]? # '>' at the start of a line
1352 | .+\n # rest of the first line
1353 | (.+\n)* # subsequent consecutive lines
1354 | \n* # blanks
1355 | )+
1356 | )
1357 | ''', re.M | re.X)
1358 | _bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M)
1359 |
1360 | _html_pre_block_re = re.compile(r'(\s*.+?
)', re.S)
1361 |
1362 | def _dedent_two_spaces_sub(self, match):
1363 | return re.sub(r'(?m)^ ', '', match.group(1))
1364 |
1365 | def _block_quote_sub(self, match):
1366 | bq = match.group(1)
1367 | bq = self._bq_one_level_re.sub('', bq) # trim one level of quoting
1368 | bq = self._ws_only_line_re.sub('', bq) # trim whitespace-only lines
1369 | bq = self._run_block_gamut(bq) # recurse
1370 |
1371 | bq = re.sub('(?m)^', ' ', bq)
1372 | # These leading spaces screw with content, so we need to fix
1373 | # that:
1374 | bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq)
1375 |
1376 | return "\n%s\n
\n\n" % bq
1377 |
1378 | def _do_block_quotes(self, text):
1379 | if '>' not in text:
1380 | return text
1381 | return self._block_quote_re.sub(self._block_quote_sub, text)
1382 |
1383 | def _form_paragraphs(self, text):
1384 | # Strip leading and trailing lines:
1385 | text = text.strip('\n')
1386 |
1387 | # Wrap tags.
1388 | grafs = re.split(r"\n{2,}", text)
1389 | for i, graf in enumerate(grafs):
1390 | if graf in self.html_blocks:
1391 | # Unhashify HTML blocks
1392 | grafs[i] = self.html_blocks[graf]
1393 | else:
1394 | # Wrap
tags.
1395 | graf = self._run_span_gamut(graf)
1396 | grafs[i] = "
" + graf.lstrip(" \t") + "
"
1397 |
1398 | return "\n\n".join(grafs)
1399 |
1400 | def _add_footnotes(self, text):
1401 | if self.footnotes:
1402 | footer = [
1403 | '')
1424 | return text + '\n\n' + '\n'.join(footer)
1425 | else:
1426 | return text
1427 |
1428 | # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
1429 | # http://bumppo.net/projects/amputator/
1430 | _ampersand_re = re.compile(r'&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)')
1431 | _naked_lt_re = re.compile(r'<(?![a-z/?\$!])', re.I)
1432 | _naked_gt_re = re.compile(r'''(?''', re.I)
1433 |
1434 | def _encode_amps_and_angles(self, text):
1435 | # Smart processing for ampersands and angle brackets that need
1436 | # to be encoded.
1437 | text = self._ampersand_re.sub('&', text)
1438 |
1439 | # Encode naked <'s
1440 | text = self._naked_lt_re.sub('<', text)
1441 |
1442 | # Encode naked >'s
1443 | # Note: Other markdown implementations (e.g. Markdown.pl, PHP
1444 | # Markdown) don't do this.
1445 | text = self._naked_gt_re.sub('>', text)
1446 | return text
1447 |
1448 | def _encode_backslash_escapes(self, text):
1449 | for ch, escape in g_escape_table.items():
1450 | text = text.replace("\\" + ch, escape)
1451 | return text
1452 |
1453 | _auto_link_re = re.compile(r'<((https?|ftp):[^\'">\s]+)>', re.I)
1454 |
1455 | def _auto_link_sub(self, match):
1456 | g1 = match.group(1)
1457 | return '%s' % (g1, g1)
1458 |
1459 | _auto_email_link_re = re.compile(r"""
1460 | <
1461 | (?:mailto:)?
1462 | (
1463 | [-.\w]+
1464 | \@
1465 | [-\w]+(\.[-\w]+)*\.[a-z]+
1466 | )
1467 | >
1468 | """, re.I | re.X | re.U)
1469 |
1470 | def _auto_email_link_sub(self, match):
1471 | return self._encode_email_address(
1472 | self._unescape_special_chars(match.group(1)))
1473 |
1474 | def _do_auto_links(self, text):
1475 | text = self._auto_link_re.sub(self._auto_link_sub, text)
1476 | text = self._auto_email_link_re.sub(self._auto_email_link_sub, text)
1477 | return text
1478 |
1479 | def _encode_email_address(self, addr):
1480 | # Input: an email address, e.g. "foo@example.com"
1481 | #
1482 | # Output: the email address as a mailto link, with each character
1483 | # of the address encoded as either a decimal or hex entity, in
1484 | # the hopes of foiling most address harvesting spam bots. E.g.:
1485 | #
1486 | # foo
1488 | # @example.com
1489 | #
1490 | # Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
1491 | # mailing list:
1492 | chars = [_xml_encode_email_char_at_random(ch)
1493 | for ch in "mailto:" + addr]
1494 | # Strip the mailto: from the visible part.
1495 | addr = '%s' \
1496 | % (''.join(chars), ''.join(chars[7:]))
1497 | return addr
1498 |
1499 | def _do_link_patterns(self, text):
1500 | """Caveat emptor: there isn't much guarding against link
1501 | patterns being formed inside other standard Markdown links, e.g.
1502 | inside a [link def][like this].
1503 |
1504 | Dev Notes: *Could* consider prefixing regexes with a negative
1505 | lookbehind assertion to attempt to guard against this.
1506 | """
1507 | link_from_hash = {}
1508 | for regex, repl in self.link_patterns:
1509 | replacements = []
1510 | for match in regex.finditer(text):
1511 | if hasattr(repl, "__call__"):
1512 | href = repl(match)
1513 | else:
1514 | href = match.expand(repl)
1515 | replacements.append((match.span(), href))
1516 | for (start, end), href in reversed(replacements):
1517 | escaped_href = (
1518 | href.replace('"', '"') # b/c of attr quote
1519 | # To avoid markdown and :
1520 | .replace('*', g_escape_table['*'])
1521 | .replace('_', g_escape_table['_']))
1522 | link = '%s' % (escaped_href, text[start:end])
1523 | hash = md5(link).hexdigest()
1524 | link_from_hash[hash] = link
1525 | text = text[:start] + hash + text[end:]
1526 | for hash, link in link_from_hash.items():
1527 | text = text.replace(hash, link)
1528 | return text
1529 |
1530 | def _unescape_special_chars(self, text):
1531 | # Swap back in all the special characters we've hidden.
1532 | for ch, hash in g_escape_table.items():
1533 | text = text.replace(hash, ch)
1534 | return text
1535 |
1536 | def _outdent(self, text):
1537 | # Remove one level of line-leading tabs or spaces
1538 | return self._outdent_re.sub('', text)
1539 |
1540 |
1541 | class MarkdownWithExtras(Markdown):
1542 | """A markdowner class that enables most extras:
1543 |
1544 | - footnotes
1545 | - code-color (only has effect if 'pygments' Python module on path)
1546 |
1547 | These are not included:
1548 | - pyshell (specific to Python-related documenting)
1549 | - code-friendly (because it *disables* part of the syntax)
1550 | - link-patterns (because you need to specify some actual
1551 | link-patterns anyway)
1552 | """
1553 | extras = ["footnotes", "code-color"]
1554 |
1555 |
1556 | #---- internal support functions
1557 |
1558 | # From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549
1559 | def _curry(*args, **kwargs):
1560 | function, args = args[0], args[1:]
1561 |
1562 | def result(*rest, **kwrest):
1563 | combined = kwargs.copy()
1564 | combined.update(kwrest)
1565 | return function(*args + rest, **combined)
1566 | return result
1567 |
1568 | # Recipe: regex_from_encoded_pattern (1.0)
1569 |
1570 |
1571 | def _regex_from_encoded_pattern(s):
1572 | """'foo' -> re.compile(re.escape('foo'))
1573 | '/foo/' -> re.compile('foo')
1574 | '/foo/i' -> re.compile('foo', re.I)
1575 | """
1576 | if s.startswith('/') and s.rfind('/') != 0:
1577 | # Parse it: /PATTERN/FLAGS
1578 | idx = s.rfind('/')
1579 | pattern, flags_str = s[1:idx], s[idx + 1:]
1580 | flag_from_char = {
1581 | "i": re.IGNORECASE,
1582 | "l": re.LOCALE,
1583 | "s": re.DOTALL,
1584 | "m": re.MULTILINE,
1585 | "u": re.UNICODE,
1586 | }
1587 | flags = 0
1588 | for char in flags_str:
1589 | try:
1590 | flags |= flag_from_char[char]
1591 | except KeyError:
1592 | raise ValueError("unsupported regex flag: '%s' in '%s' "
1593 | "(must be one of '%s')"
1594 | % (char, s, ''.join(flag_from_char.keys())))
1595 | return re.compile(s[1:idx], flags)
1596 | else: # not an encoded regex
1597 | return re.compile(re.escape(s))
1598 |
1599 | # Recipe: dedent (0.1.2)
1600 |
1601 |
1602 | def _dedentlines(lines, tabsize=8, skip_first_line=False):
1603 | """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines
1604 |
1605 | "lines" is a list of lines to dedent.
1606 | "tabsize" is the tab width to use for indent width calculations.
1607 | "skip_first_line" is a boolean indicating if the first line should
1608 | be skipped for calculating the indent width and for dedenting.
1609 | This is sometimes useful for docstrings and similar.
1610 |
1611 | Same as dedent() except operates on a sequence of lines. Note: the
1612 | lines list is modified **in-place**.
1613 | """
1614 | DEBUG = False
1615 | if DEBUG:
1616 | print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
1617 | % (tabsize, skip_first_line)
1618 | indents = []
1619 | margin = None
1620 | for i, line in enumerate(lines):
1621 | if i == 0 and skip_first_line:
1622 | continue
1623 | indent = 0
1624 | for ch in line:
1625 | if ch == ' ':
1626 | indent += 1
1627 | elif ch == '\t':
1628 | indent += tabsize - (indent % tabsize)
1629 | elif ch in '\r\n':
1630 | continue # skip all-whitespace lines
1631 | else:
1632 | break
1633 | else:
1634 | continue # skip all-whitespace lines
1635 | if DEBUG:
1636 | print "dedent: indent=%d: %r" % (indent, line)
1637 | if margin is None:
1638 | margin = indent
1639 | else:
1640 | margin = min(margin, indent)
1641 | if DEBUG:
1642 | print "dedent: margin=%r" % margin
1643 |
1644 | if margin is not None and margin > 0:
1645 | for i, line in enumerate(lines):
1646 | if i == 0 and skip_first_line:
1647 | continue
1648 | removed = 0
1649 | for j, ch in enumerate(line):
1650 | if ch == ' ':
1651 | removed += 1
1652 | elif ch == '\t':
1653 | removed += tabsize - (removed % tabsize)
1654 | elif ch in '\r\n':
1655 | if DEBUG:
1656 | print "dedent: %r: EOL -> strip up to EOL" % line
1657 | lines[i] = lines[i][j:]
1658 | break
1659 | else:
1660 | raise ValueError("unexpected non-whitespace char %r in "
1661 | "line %r while removing %d-space margin"
1662 | % (ch, line, margin))
1663 | if DEBUG:
1664 | print "dedent: %r: %r -> removed %d/%d"\
1665 | % (line, ch, removed, margin)
1666 | if removed == margin:
1667 | lines[i] = lines[i][j + 1:]
1668 | break
1669 | elif removed > margin:
1670 | lines[i] = ' ' * (removed - margin) + lines[i][j + 1:]
1671 | break
1672 | else:
1673 | if removed:
1674 | lines[i] = lines[i][removed:]
1675 | return lines
1676 |
1677 |
1678 | def _dedent(text, tabsize=8, skip_first_line=False):
1679 | """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text
1680 |
1681 | "text" is the text to dedent.
1682 | "tabsize" is the tab width to use for indent width calculations.
1683 | "skip_first_line" is a boolean indicating if the first line should
1684 | be skipped for calculating the indent width and for dedenting.
1685 | This is sometimes useful for docstrings and similar.
1686 |
1687 | textwrap.dedent(s), but don't expand tabs to spaces
1688 | """
1689 | lines = text.splitlines(1)
1690 | _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line)
1691 | return ''.join(lines)
1692 |
1693 |
1694 | class _memoized(object):
1695 | """Decorator that caches a function's return value each time it is called.
1696 | If called later with the same arguments, the cached value is returned, and
1697 | not re-evaluated.
1698 |
1699 | http://wiki.python.org/moin/PythonDecoratorLibrary
1700 | """
1701 |
1702 | def __init__(self, func):
1703 | self.func = func
1704 | self.cache = {}
1705 |
1706 | def __call__(self, *args):
1707 | try:
1708 | return self.cache[args]
1709 | except KeyError:
1710 | self.cache[args] = value = self.func(*args)
1711 | return value
1712 | except TypeError:
1713 | # uncachable -- for instance, passing a list as an argument.
1714 | # Better to not cache than to blow up entirely.
1715 | return self.func(*args)
1716 |
1717 | def __repr__(self):
1718 | """Return the function's docstring."""
1719 | return self.func.__doc__
1720 |
1721 |
1722 | def _xml_oneliner_re_from_tab_width(tab_width):
1723 | """Standalone XML processing instruction regex."""
1724 | return re.compile(r"""
1725 | (?:
1726 | (?<=\n\n) # Starting after a blank line
1727 | | # or
1728 | \A\n? # the beginning of the doc
1729 | )
1730 | ( # save in $1
1731 | [ ]{0,%d}
1732 | (?:
1733 | <\?\w+\b\s+.*?\?> # XML processing instruction
1734 | |
1735 | <\w+:\w+\b\s+.*?/> # namespaced single tag
1736 | )
1737 | [ \t]*
1738 | (?=\n{2,}|\Z) # followed by a blank line or end of document
1739 | )
1740 | """ % (tab_width - 1), re.X)
1741 | _xml_oneliner_re_from_tab_width = _memoized(_xml_oneliner_re_from_tab_width)
1742 |
1743 |
1744 | def _hr_tag_re_from_tab_width(tab_width):
1745 | return re.compile(r"""
1746 | (?:
1747 | (?<=\n\n) # Starting after a blank line
1748 | | # or
1749 | \A\n? # the beginning of the doc
1750 | )
1751 | ( # save in \1
1752 | [ ]{0,%d}
1753 | <(hr) # start tag = \2
1754 | \b # word break
1755 | ([^<>])*? #
1756 | /?> # the matching end tag
1757 | [ \t]*
1758 | (?=\n{2,}|\Z) # followed by a blank line or end of document
1759 | )
1760 | """ % (tab_width - 1), re.X)
1761 | _hr_tag_re_from_tab_width = _memoized(_hr_tag_re_from_tab_width)
1762 |
1763 |
1764 | def _xml_encode_email_char_at_random(ch):
1765 | r = random()
1766 | # Roughly 10% raw, 45% hex, 45% dec.
1767 | # '@' *must* be encoded. I [John Gruber] insist.
1768 | # Issue 26: '_' must be encoded.
1769 | if r > 0.9 and ch not in "@_":
1770 | return ch
1771 | elif r < 0.45:
1772 | # The [1:] is to drop leading '0': 0x63 -> x63
1773 | return '%s;' % hex(ord(ch))[1:]
1774 | else:
1775 | return '%s;' % ord(ch)
1776 |
1777 |
1778 | def _hash_text(text):
1779 | return 'md5:' + md5(text.encode("utf-8")).hexdigest()
1780 |
1781 |
1782 | #---- mainline
1783 |
1784 | class _NoReflowFormatter(optparse.IndentedHelpFormatter):
1785 | """An optparse formatter that does NOT reflow the description."""
1786 |
1787 | def format_description(self, description):
1788 | return description or ""
1789 |
1790 |
1791 | def _test():
1792 | import doctest
1793 | doctest.testmod()
1794 |
1795 |
1796 | def main(argv=None):
1797 | if argv is None:
1798 | argv = sys.argv
1799 | if not logging.root.handlers:
1800 | logging.basicConfig()
1801 |
1802 | usage = "usage: %prog [PATHS...]"
1803 | version = "%prog " + __version__
1804 | parser = optparse.OptionParser(prog="markdown2", usage=usage,
1805 | version=version, description=cmdln_desc,
1806 | formatter=_NoReflowFormatter())
1807 | parser.add_option("-v", "--verbose", dest="log_level",
1808 | action="store_const", const=logging.DEBUG,
1809 | help="more verbose output")
1810 | parser.add_option("--encoding",
1811 | help="specify encoding of text content")
1812 | parser.add_option("--html4tags", action="store_true", default=False,
1813 | help="use HTML 4 style for empty element tags")
1814 | parser.add_option("-s", "--safe", metavar="MODE", dest="safe_mode",
1815 | help="sanitize literal HTML: 'escape' escapes "
1816 | "HTML meta chars, 'replace' replaces with an "
1817 | "[HTML_REMOVED] note")
1818 | parser.add_option("-x", "--extras", action="append",
1819 | help="Turn on specific extra features (not part of "
1820 | "the core Markdown spec). Supported values: "
1821 | "'code-friendly' disables _/__ for emphasis; "
1822 | "'code-color' adds code-block syntax coloring; "
1823 | "'link-patterns' adds auto-linking based on patterns; "
1824 | "'footnotes' adds the footnotes syntax;"
1825 | "'xml' passes one-liner processing instructions and namespaced XML tags;"
1826 | "'pyshell' to put unindented Python interactive shell sessions in a block.")
1827 | parser.add_option("--use-file-vars",
1828 | help="Look for and use Emacs-style 'markdown-extras' "
1829 | "file var to turn on extras. See "
1830 | ".")
1831 | parser.add_option("--link-patterns-file",
1832 | help="path to a link pattern file")
1833 | parser.add_option("--self-test", action="store_true",
1834 | help="run internal self-tests (some doctests)")
1835 | parser.add_option("--compare", action="store_true",
1836 | help="run against Markdown.pl as well (for testing)")
1837 | parser.set_defaults(log_level=logging.INFO, compare=False,
1838 | encoding="utf-8", safe_mode=None, use_file_vars=False)
1839 | opts, paths = parser.parse_args()
1840 | log.setLevel(opts.log_level)
1841 |
1842 | if opts.self_test:
1843 | return _test()
1844 |
1845 | if opts.extras:
1846 | extras = {}
1847 | for s in opts.extras:
1848 | splitter = re.compile("[,;: ]+")
1849 | for e in splitter.split(s):
1850 | if '=' in e:
1851 | ename, earg = e.split('=', 1)
1852 | try:
1853 | earg = int(earg)
1854 | except ValueError:
1855 | pass
1856 | else:
1857 | ename, earg = e, None
1858 | extras[ename] = earg
1859 | else:
1860 | extras = None
1861 |
1862 | if opts.link_patterns_file:
1863 | link_patterns = []
1864 | f = open(opts.link_patterns_file)
1865 | try:
1866 | for i, line in enumerate(f.readlines()):
1867 | if not line.strip():
1868 | continue
1869 | if line.lstrip().startswith("#"):
1870 | continue
1871 | try:
1872 | pat, href = line.rstrip().rsplit(None, 1)
1873 | except ValueError:
1874 | raise MarkdownError("%s:%d: invalid link pattern line: %r"
1875 | % (opts.link_patterns_file, i + 1, line))
1876 | link_patterns.append(
1877 | (_regex_from_encoded_pattern(pat), href))
1878 | finally:
1879 | f.close()
1880 | else:
1881 | link_patterns = None
1882 |
1883 | from os.path import join, dirname, abspath, exists
1884 | markdown_pl = join(dirname(dirname(abspath(__file__))), "test",
1885 | "Markdown.pl")
1886 | for path in paths:
1887 | if opts.compare:
1888 | print "==== Markdown.pl ===="
1889 | perl_cmd = 'perl %s "%s"' % (markdown_pl, path)
1890 | o = os.popen(perl_cmd)
1891 | perl_html = o.read()
1892 | o.close()
1893 | sys.stdout.write(perl_html)
1894 | print "==== markdown2.py ===="
1895 | html = markdown_path(path, encoding=opts.encoding,
1896 | html4tags=opts.html4tags,
1897 | safe_mode=opts.safe_mode,
1898 | extras=extras, link_patterns=link_patterns,
1899 | use_file_vars=opts.use_file_vars)
1900 | sys.stdout.write(
1901 | html.encode(sys.stdout.encoding or "utf-8", 'xmlcharrefreplace'))
1902 | if opts.compare:
1903 | test_dir = join(dirname(dirname(abspath(__file__))), "test")
1904 | if exists(join(test_dir, "test_markdown2.py")):
1905 | sys.path.insert(0, test_dir)
1906 | from test_markdown2 import norm_html_from_html
1907 | norm_html = norm_html_from_html(html)
1908 | norm_perl_html = norm_html_from_html(perl_html)
1909 | else:
1910 | norm_html = html
1911 | norm_perl_html = perl_html
1912 | print "==== match? %r ====" % (norm_perl_html == norm_html)
1913 |
1914 |
1915 | if __name__ == "__main__":
1916 | sys.exit(main(sys.argv))
1917 |
--------------------------------------------------------------------------------
/lib/pagination.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding=utf-8
3 | try:
4 | import psyco
5 | psyco.full()
6 | except:
7 | pass
8 | from math import ceil
9 |
10 |
11 | class Pagination(object):
12 |
13 | def __init__(self, query, page, per_page=20):
14 | #: pagination object.
15 | self.query = query
16 | #: the current page number (1 indexed)
17 | self.page = page
18 | #: the number of items to be displayed on a page.
19 | self.per_page = per_page
20 | #: the total number of items matching the query
21 | self.total = self.query.count()
22 | self.items = self.query.paginate(page, per_page)
23 |
24 | @property
25 | def pages(self):
26 | """The total number of pages"""
27 | return int(ceil(self.total / float(self.per_page)))
28 |
29 | @property
30 | def has_prev(self):
31 | """True if a previous page exists"""
32 | return self.page > 1
33 |
34 | @property
35 | def has_next(self):
36 | """True if a next page exists."""
37 | return self.page < self.pages
38 |
39 | def prev(self):
40 | assert self.query is not None
41 | return self.query.paginate(self.page - 1, self.per_page)
42 |
43 | def next(self):
44 | assert self.query is not None
45 | return self.query.paginate(self.page + 1, self.per_page)
46 |
47 | def iter_pages(self, left_edge=2, left_current=2,
48 | right_current=5, right_edge=2):
49 | """Iterates over the page numbers in the pagination. The four
50 | parameters control the thresholds how many numbers should be produced
51 | from the sides. Skipped page numbers are represented as `None`.
52 | This is how you could render such a pagination in the templates:
53 |
54 | .. sourcecode:: html+jinja
55 |
56 | {% macro render_pagination(pagination, endpoint) %}
57 |
70 | {% endmacro %}
71 | """
72 | last = 0
73 | for num in xrange(1, self.pages + 1):
74 | if num <= left_edge or \
75 | (num > self.page - left_current - 1 and
76 | num < self.page + right_current) or \
77 | num > self.pages - right_edge:
78 | if last + 1 != num:
79 | yield None
80 | yield num
81 | last = num
82 |
--------------------------------------------------------------------------------
/lib/session.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding=utf-8
3 |
4 | try:
5 | import psyco
6 | psyco.full()
7 | except:
8 | pass
9 | import cPickle as pickle
10 | from uuid import uuid4
11 | import time
12 | import logging
13 |
14 |
15 | class RedisSessionStore(object):
16 |
17 | def __init__(self, redis_connection, **options):
18 | self.options = {
19 | 'key_prefix': 'session',
20 | 'expire': 7200,
21 | }
22 | self.options.update(options)
23 | self.redis = redis_connection
24 |
25 | def prefixed(self, sid):
26 | return '%s:%s' % (self.options['key_prefix'], sid)
27 |
28 | def generate_sid(self):
29 | return uuid4().get_hex()
30 |
31 | def get_session(self, sid, name):
32 | data = self.redis.hget(self.prefixed(sid), name)
33 | session = pickle.loads(data) if data else dict()
34 | return session
35 |
36 | def set_session(self, sid, session_data, name, expiry=None):
37 | self.redis.hset(self.prefixed(sid), name, pickle.dumps(session_data))
38 | expiry = expiry or self.options['expire']
39 | if expiry:
40 | self.redis.expire(self.prefixed(sid), expiry)
41 |
42 | def delete_session(self, sid):
43 | self.redis.delete(self.prefixed(sid))
44 |
45 |
46 | class Session(object):
47 |
48 | def __init__(self, session_store, session_id=None, expires_days=None):
49 | self._store = session_store
50 | self._sid = session_id if session_id else self._store.generate_sid()
51 | self._dirty = False
52 | self.set_expires(expires_days)
53 | try:
54 | self._data = self._store.get_session(self._sid, 'data')
55 | except:
56 | logging.error('Can not connect Redis server.')
57 | self._data = {}
58 |
59 | def clear(self):
60 | self._store.delete_session(self._sid)
61 |
62 | @property
63 | def id(self):
64 | return self._sid
65 |
66 | def access(self, remote_ip):
67 | access_info = {'remote_ip': remote_ip, 'time': '%.6f' % time.time()}
68 | self._store.set_session(
69 | self._sid,
70 | 'last_access',
71 | pickle.dumps(access_info))
72 |
73 | def last_access(self):
74 | access_info = self._store.get_session(self._sid, 'last_access')
75 | return pickle.loads(access_info)
76 |
77 | def set_expires(self, days):
78 | self._expiry = days * 86400 if days else None
79 |
80 | def __getitem__(self, key):
81 | return self._data[key]
82 |
83 | def __setitem__(self, key, value):
84 | self._data[key] = value
85 | self._dirty = True
86 |
87 | def __delitem__(self, key):
88 | del self._data[key]
89 | self._dirty = True
90 |
91 | def __len__(self):
92 | return len(self._data)
93 |
94 | def __contains__(self, key):
95 | return key in self._data
96 |
97 | def __iter__(self):
98 | for key in self._data:
99 | yield key
100 |
101 | def __repr__(self):
102 | return self._data.__repr__()
103 |
104 | def __del__(self):
105 | self.save()
106 |
107 | def save(self):
108 | if self._dirty:
109 | self._store.set_session(
110 | self._sid, self._data, 'data', self._expiry)
111 | self._dirty = False
112 |
--------------------------------------------------------------------------------
/manager.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf8
3 | try:
4 | import psyco
5 | psyco.full()
6 | except:
7 | pass
8 | import tornado
9 | import tornado.web
10 | from tornado.httpserver import HTTPServer
11 | from tornado.options import define, options
12 | from tornado.web import url
13 | import sys
14 |
15 | from lib import filters, session
16 |
17 | from core import jinja_environment, smtp_server
18 | from core import settings
19 | from core import redis_server
20 |
21 | define("cmd", default='runserver', metavar="runserver|createuser")
22 | define("port", default=9000, type=int)
23 | define("autoreload", default=False, type=bool)
24 |
25 |
26 | class Application(tornado.web.Application):
27 |
28 | def __init__(self):
29 | from urls import routes as handlers
30 |
31 | # init jiaja2 environment
32 | self.jinja_env = jinja_environment
33 |
34 | # register filters for jinja2
35 | self.jinja_env.filters.update(filters.register_filters())
36 | self.jinja_env.tests.update({})
37 |
38 | self.jinja_env.globals['settings'] = settings
39 | tornado.web.Application.__init__(self, handlers, **settings)
40 | self.session_store = session.RedisSessionStore(redis_server)
41 | self.email_backend = smtp_server
42 |
43 |
44 | def runserver():
45 | http_server = HTTPServer(Application(), xheaders=True)
46 | http_server.listen(options.port)
47 | loop = tornado.ioloop.IOLoop.instance()
48 | print 'Server running on http://0.0.0.0:%d' % (options.port)
49 | loop.start()
50 |
51 |
52 | def createuser():
53 | username = raw_input('input username: ')
54 | if username:
55 | from models import User
56 | q = User.select().where(User.username == username.strip())
57 | if q.count() > 0:
58 | print 'username [ %s ] exists! please choice another one and try it again!' % (username)
59 | sys.exit(0)
60 | email = raw_input('input your Email: ')
61 | password = raw_input('input password: ')
62 | User.create(username=username, email=email.strip(),
63 | password=User.create_password(password))
64 | print '%s created!' % (username)
65 | else:
66 | print 'username is null,exit!'
67 | sys.exit(0)
68 |
69 |
70 | def syncdb():
71 | from lib.helpers import find_subclasses
72 | from models import db
73 | models = find_subclasses(db.Model)
74 | for model in models:
75 | if model.table_exists():
76 | model.drop_table()
77 | model.create_table()
78 | print 'created table:', model._meta.db_table
79 |
80 | if __name__ == '__main__':
81 | tornado.options.parse_command_line()
82 | if options.cmd == 'runserver':
83 | runserver()
84 | elif options.cmd == 'createuser':
85 | createuser()
86 | elif options.cmd == 'syncdb':
87 | syncdb()
88 |
--------------------------------------------------------------------------------
/models.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf-8
3 | try:
4 | import psyco
5 | psyco.full()
6 | except:
7 | pass
8 | import peewee
9 | import datetime
10 | import hashlib
11 | import urllib
12 | from core import db
13 | from lib.helpers import create_token, cached_property
14 | from core import smtp_server, settings
15 | from config import DOMAIN as domain
16 |
17 |
18 | class User(db.Model):
19 | username = peewee.CharField()
20 | password = peewee.CharField()
21 | email = peewee.CharField()
22 |
23 | @staticmethod
24 | def create_password(raw):
25 | salt = create_token(8)
26 | passwd = '%s%s%s' % (salt, raw, 'blog_engine')
27 | hsh = hashlib.sha1(passwd).hexdigest()
28 | return "%s$%s" % (salt, hsh)
29 |
30 | def check_password(self, raw):
31 | if '$' not in self.password:
32 | return False
33 | salt, hsh = self.password.split('$')
34 | passwd = '%s%s%s' % (salt, raw, 'blog_engine')
35 | verify = hashlib.sha1(passwd).hexdigest()
36 | return verify == hsh
37 |
38 | class Meta:
39 | db_table = 'users'
40 |
41 |
42 | class Category(db.Model):
43 | name = peewee.CharField()
44 | slug = peewee.CharField()
45 |
46 | @property
47 | def url(self):
48 | return '/category/%s' % (urllib.quote(self.name.encode('utf8')))
49 |
50 | class Meta:
51 | db_table = 'category'
52 |
53 |
54 | class Post(db.Model):
55 | title = peewee.CharField()
56 | slug = peewee.CharField(index=True, max_length=100)
57 | category = peewee.ForeignKeyField(Category, related_name='posts')
58 | content = peewee.TextField()
59 | readnum = peewee.IntegerField(default=0)
60 | tags = peewee.CharField(null=True)
61 | slug = peewee.CharField(null=True)
62 | created = peewee.DateTimeField(default=datetime.datetime.now)
63 |
64 | @property
65 | def url(self):
66 | return '/post/post-%d.html' % (self.id)
67 |
68 | @property
69 | def absolute_url(self):
70 | return '%s%s' % (domain, self.url)
71 |
72 | @property
73 | def comment_feed(self):
74 | return '%s/archive/%s/feed'(domain, self.id)
75 |
76 | @cached_property
77 | def prev(self):
78 | posts = Post.select().where(Post.created < self.created)\
79 | .order_by(Post.created)
80 | return posts.get() if posts.exists() else None
81 |
82 | @cached_property
83 | def next(self):
84 | posts = Post.select().where(Post.created > self.created)\
85 | .order_by(Post.created)
86 | return posts.get() if posts.exists() else None
87 |
88 | @property
89 | def summary(self):
90 | return self.content.split('')[0] if self.content else self.content
91 |
92 | def taglist(self):
93 | if self.tags:
94 | tags = [tag.strip() for tag in self.tags.split(",")]
95 | return set(tags)
96 | else:
97 | return None
98 |
99 | class Meta:
100 | db_table = "posts"
101 | order_by = ('-created',)
102 |
103 |
104 | class Tag(db.Model):
105 | name = peewee.CharField(max_length=50)
106 | post = peewee.IntegerField()
107 |
108 | @property
109 | def url(self):
110 | return '/tag/%s' % (urllib.quote(self.name.encode('utf8')))
111 |
112 |
113 | class Comment(db.Model):
114 | post = peewee.ForeignKeyField(Post, related_name='comments')
115 | author = peewee.CharField()
116 | website = peewee.CharField(null=True)
117 | email = peewee.CharField()
118 | content = peewee.TextField()
119 | ip = peewee.TextField()
120 | parent_id = peewee.IntegerField(null=True)
121 | created = peewee.DateTimeField(default=datetime.datetime.now)
122 |
123 | @property
124 | def parent(self):
125 | p = Comment.select().where(Comment.parent_id == self.parent_id, Comment.id == self.id)
126 | return p.get() if p.exists() else None
127 |
128 | @property
129 | def url(self):
130 | return '%s/post/post-%s.html#comment-%s' % (domain, self.post.id, self.id)
131 |
132 | def gravatar_url(self, size=80):
133 | return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
134 | (hashlib.md5(self.email.strip().lower().encode('utf-8')).hexdigest(),
135 | size)
136 |
137 | class Meta:
138 | db_table = 'comments'
139 |
140 |
141 | class Link(db.Model):
142 | name = peewee.CharField()
143 | url = peewee.CharField()
144 |
145 | class Meta:
146 | db_table = 'links'
147 |
148 |
149 | from playhouse.signals import post_save
150 | from lib.mail.message import TemplateEmailMessage
151 |
152 |
153 | @post_save(sender=Comment)
154 | def send_email(model_class, instance, created):
155 | if instance.parent_id == '0':
156 | message = TemplateEmailMessage(u"收到新的评论", 'mail/new_comment.html',
157 | settings['smtp_user'], to=[settings['admin_email']], connection=smtp_server, params={'comment': instance})
158 | else:
159 | message = TemplateEmailMessage(u"评论有新的回复", 'mail/reply_comment.html',
160 | settings['smtp_user'], to=[instance.email], connection=smtp_server, params={'comment': instance})
161 | message.send()
162 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | tornado
2 | jinja2
3 | redis
4 | peewee
5 | markdown
6 | pygments
--------------------------------------------------------------------------------
/static/default/images/app-systempref.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/app-systempref.png
--------------------------------------------------------------------------------
/static/default/images/arrow2-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/arrow2-left.png
--------------------------------------------------------------------------------
/static/default/images/arrow2-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/arrow2-right.png
--------------------------------------------------------------------------------
/static/default/images/calendar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/calendar2.png
--------------------------------------------------------------------------------
/static/default/images/chat_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/chat_512.png
--------------------------------------------------------------------------------
/static/default/images/comment_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/comment_white.png
--------------------------------------------------------------------------------
/static/default/images/folder-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/folder-black.png
--------------------------------------------------------------------------------
/static/default/images/folder-open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/folder-open.png
--------------------------------------------------------------------------------
/static/default/images/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/folder.png
--------------------------------------------------------------------------------
/static/default/images/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/link.png
--------------------------------------------------------------------------------
/static/default/images/tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/tag.png
--------------------------------------------------------------------------------
/static/default/images/text-x-generic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/text-x-generic.png
--------------------------------------------------------------------------------
/static/default/images/text_list_bullets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/default/images/text_list_bullets.png
--------------------------------------------------------------------------------
/static/default/style.css:
--------------------------------------------------------------------------------
1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{border:0;font-family:inherit;font-size:100%;font-style:inherit;font-weight:inherit;margin:0;outline:0;padding:0;vertical-align:baseline;}body{font-size:12px;font-family:Arial,Helvetica,Tahoma,sans-serif;color:#333;line-height:160%;}h1,h2,h3,h4,h5,h6,b,strong{font-weight:bold;}img{border:none;}em{font-style:italic;}.cb{clear:both;}li{list-style-type:none;}a{text-decoration:none;color:#360;}a:hover{color:#3C0;}#header-wrap{height:115px;width:auto;background-color:#004600;color:white;}#header{width:960px;margin:0 auto;}#header a{color:#CCC;}#header h1,#header h2{font-size:45px;padding:25px 0 0;}#header h1 a,#header h2 a{color:white;}.h_top p{color:#ccc;margin-top:15px;}ul.main-menu{margin-top:10px;float:right;font-size:14px;}ul.main-menu li{float:left;margin-right:18px;position:relative;}#header ul.main-menu a:hover{color:white;}#header .current-menu-item a{color:white;}.main-submenu{position:absolute;width:105px;background-color:#004600;padding-top:8px;display:none;z-index:9;left:0;}.main-submenu li{width:100px;padding-left:5px;line-height:200%;}#header .main-submenu li a{font-size:12px;}.main-submenu ul.main-submenu{display:none;}#wrap{width:960px;margin:20px auto 0;}#main{margin-bottom:20px;}#content{width:670px;float:left;}.post-list .post-meta{width:160px;float:left;margin-right:20px;overflow:hidden;}.post-list .post-wrap{float:left;width:480px;overflow:hidden;}.post-list .post-content img{max-width:480px;width:auto;height:auto;-width:expression( width>480 ? '480px':true );display:block;margin:0 auto;}.post-list .post-meta p{padding-left:20px;border-bottom:1px dotted #ccc;margin-bottom:5px;line-height:180%;color:#777;}.post-list .post-meta a{color:#777;}.post-list p.time{background:url(images/calendar2.png) no-repeat left 3px;}.post-list .comment{background:url(images/chat_512.png) no-repeat left 3px;}.post-list .category{background:url(images/folder.png) no-repeat left 3px;}.post-list .tags{background:url(images/tag.png) no-repeat left 3px;}.post-list p.edit-link{border-bottom:none;}li.post-list{border-bottom:2px solid #BBCBB6;margin-bottom:20px;padding-bottom:20px;}.post-wrap h2{font-size:18px;margin-bottom:10px;}.post-content img{margin-bottom:5px;}.post-content{line-height:180%;}.paging{text-align:center;color:gray;font-family:"Times New Roman",Georgia,Times,serif;}.paging span,.paging a{margin-right:10px;}ul#sidebar{width:250px;float:right;overflow:hidden;line-height:180%;}li.widget{margin-bottom:20px;}h3.widgettitle{font-size:14px;padding-5px;border-bottom:2px solid #BBCBB6;margin-bottom:5px;padding-bottom:8px;}.widget ul li{background:url(images/text_list_bullets.png) no-repeat left 5px;padding-left:18px;}.widget_recent_entries ul li{background:url(images/text-x-generic.png) no-repeat left 5px;}.widget_archive ul li{background:url(images/folder-black.png) no-repeat left 5px;}.widget_categories ul li{background:url(images/folder-open.png) no-repeat left 5px;}.widget_meta ul li{background:url(images/app-systempref.png) no-repeat left 5px;}.widget_recent_comments ul li{background:url(images/comment_white.png) no-repeat left 5px;}.widget_links ul li{background:url(images/link.png) no-repeat left 5px;}table#wp-calendar{width:100%;text-align:center;}table#wp-calendar a,table#wp-calendar caption{font-weight:bold;}.widget_tag_cloud a{margin-right:5px;}.widget_tag_cloud a span{vertical-align:super;font-size:.8em;}#footer{border-top:1px dotted #BBCBB6;padding-top:20px;padding-bottom:20px;}#copyright{float:left;width:670px;}.daxiawp{float:right;width:300px;color:gray;font-style:italic;}.daxiawp a{color:gray;}.archive #content h1,.search #content h1{font-size:18px;margin-bottom:20px;padding-bottom:10px;border-bottom:2px solid #BBCBB6;}.singular h1{font-size:22px;border-bottom:2px solid #BBCBB6;margin-bottom:15px;padding-bottom:10px;}.singular img{max-width:650px;height:auto;width:auto;-width:expression( width>650 ? '650px':true );}.singular .post-content p{margin-bottom:10px;text-indent:2em;}img.aligncenter{margin:10px auto;display:block;}img.alignleft{display:block;float:left;margin-right:10px;}img.alignright{display:block;float:right;margin-left:10px;}.link-pages{text-align:center;margin:10px 0;}.singular div.post-meta{margin:10px 0;border-top:1px dotted #BBCBB6;padding-top:10px;line-height:220%;}.singular .post-meta div{padding-left:18px;}.singular div.time{background:url(images/calendar2.png) no-repeat left 5px;}.singular div.category{background:url(images/folder.png) no-repeat left 5px;}.singular div.tags{background:url(images/tag.png) no-repeat left 5px;}.singular div.previous{background:url(images/arrow2-left.png) no-repeat left 5px;}.singular div.next{background:url(images/arrow2-right.png) no-repeat left 5px;}#comments{border-top:2px solid #BBCBB6;}.comments-list h4,h3#reply-title{margin:10px 0;}.comments-list a{color:gray;}.comments-list ul.children{margin-left:40px;}.comments-list li.depth-1{margin-bottom:10px;}.form-allowed-tags{color:gray;}#respond .required,.comment-awaiting-moderation{color:red;}#commentform p{margin-bottom:10px;}#commentform label{display:inline-block;width:70px;line-height:18px;}#commentform .comment-form-author label,#commentform .comment-form-email label{width:62px;}input#author,input#email,input#url{height:18px;line-height:18px;width:250px;}textarea#comment{width:90%;}.error404 #content h1{font-size:16px;margin:20px auto;width:350px;}#ie7 #commentform .comment-form-author label,#ie7 #commentform .comment-form-email label,#ie6 #commentform .comment-form-author label,#ie6 #commentform .comment-form-email label{width:59px;}#ie6 .main-submenu,#ie7 .main-submenu{top:18px;}.label-success{padding:3px 3px 2px;font-size:11.25px;font-weight:bold;color:white;background-color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;background-color:#468847;}
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengmin/logpress-tornado/7bc22ba144bfd00956f0eb8c2adfb1b3992b549d/static/favicon.ico
--------------------------------------------------------------------------------
/static/fluid/print.css:
--------------------------------------------------------------------------------
1 | body{font-family:'Lucida Grande',Verdana,Arial,Sans-Serif;}#header{border-bottom:1px solid #aaa;}a{background:transparent;color:black;text-decoration:none;}.search{display:none;}#hmenu,#nav{display:none;}#sidebar{display:none;}#footer{display:none;}.post blockquote{padding:0 0 0 1em;border-left:0.4em solid #ccc;font-size:small;}.postmetadata,.post-meta{clear:both;font-size:small;}.navigation{display:none;}#respond,#commentform,#comments .reply{display:none;}.commentlist img.avatar{float:right;margin:0 0 0 10px;padding:3px;border:1px solid #eee;}.aligncenter,div.aligncenter{display:block;margin-left:auto;margin-right:auto;}.alignleft{float:left;margin:5px 5px 5px 0;}.alignright{float:right;margin:5px 0 5px 5px;}
--------------------------------------------------------------------------------
/static/fluid/style.css:
--------------------------------------------------------------------------------
1 | body{margin:0;background:#fff;color:#444;font-size:62.5%;font-family:'Lucida Grande',Verdana,Arial,Sans-Serif;text-align:center;}a{color:#258;text-decoration:none;}a:hover{text-decoration:underline;}h1,h1 a,h2,h2 a,h3,h4,h5,h6{margin-bottom:0;color:#27a;text-align:left;}h1{font-size:2.6em;}h2{font-size:2em;}h3{font-size:1.6em;}h4{font-size:1.2em;}h5{font-size:1em;}h6{font-size:0.8em;}img{border:0;}input{font-size:1.2em;}input[type=text],textarea{background:#fff;border:1px inset #aaa;}input[type=submit]{background:#eee;border:1px outset #aaa;}textarea{font-size:12px;}pre{font-size:12px;overflow:auto;}code{font-size:12px;background-color:#f8f8f8;color:#111;}#page{min-width:760px;margin:0 auto;text-align:left;}#wrapper{margin:0 5%;padding-right:230px;}#content{float:left;width:96%;border-right:230px solid #eee;margin-right:-230px;padding:20px 4% 20px 0;}#sidebar{float:left;width:190px;margin:0 -230px 0 0;padding:20px;background:#eee;}#footer{clear:both;padding:10px;border-top:0.2em solid #555;}#header{margin:0;padding:2em 0;height:4.8em;background:#237ab2;}#headertitle{float:left;position:absolute;top:2.4em;left:5%;}#headertitle h1{margin:0;}#headertitle h1 a{background:transparent;color:#fff;}#headertitle h1 a:hover{text-decoration:none;}#headertitle p{margin:0;background:transparent;color:#fff;font-size:1.1em;}.search{float:right;padding:1.5em 5% 0 0;}.search form{margin:0;padding:0;}.search input{display:inline;width:218px;border:1px solid #69b;margin:0;padding:0.2em 5px;background:#38b;color:#ddd;font-size:1.1em;}#navbar{border-top:0.1em solid #555;border-bottom:0.1em solid #555;background:#165279;height:2.3em;margin:0px;padding:0px;}#nav{margin:0 5%;padding:0;list-style:none;}#nav ul{padding:0.1em 0 0 0;margin:0;list-style:none;background:transparent;}#nav a{color:#c6c8c9;display:block;font-weight:bold;padding:0.5em;}#nav a:hover{background:#237ab2;color:#fff;display:block;text-decoration:none;padding:0.5em;}#nav li{float:left;margin:0;text-transform:uppercase;padding:0 2em 0 0;}#nav li li{float:left;margin:0;padding:0;width:14em;}#nav li li a,#nav li li a:link,#nav li li a:visited{background:#165279;color:#c6c8c9;width:14em;float:none;margin:0;padding:0.5em;border-bottom:1px solid #aaa;}#nav li li a:hover,#nav li li a:active{background:#237ab2;color:#fff;}#nav li ul{position:absolute;width:10em;left:-999em;}#nav li:hover ul{left:auto;display:block;}#nav li:hover ul,#nav li.sfhover ul{left:auto;}#sidebar ul{padding:0;margin:0;list-style:none;font-size:1.1em;}#sidebar ul ul{font-size:1em;}#sidebar ul li{margin:0 0 2em 0;}#sidebar ul ul{margin:0;padding:0;}#sidebar li li{margin:0.1em 0;}#sidebar li li li{padding-left:10px;}#sidebar ul h2{margin:0;padding:0;color:#4588c4;font-size:1.2em;text-transform:uppercase;}#footer{text-align:center;font-size:1em;background:#165279;color:#eee;}#footer a{color:#aac;}.post{margin:0 0 4em 0;clear:both;}.post p,.post ol li,.post ul li{margin-top:0;font-size:1.2em;line-height:1.5em;text-align:justify;}.post li li{font-size:1em;}.post blockquote{padding:0 0 0 2em;border-left:0.4em solid #ccc;font-size:0.9em;}.post blockquote blockquote{margin-left:0;font-size:1em;}.postentry a{border-bottom:1px solid #ddd;}.postentry a:hover{border-bottom:1px solid #258;text-decoration:none;}.postmetadata{clear:both;margin:1em 0;font-size:1.1em;color:#888;text-align:justify;}div.navigation{font-size:1.1em;}.postentry table{border-width:0 1px 1px 0;border-style:solid;border-color:#ccc;font-size:0.9em;}.postentry table tr td{padding:5px 10px;border-width:1px 0 0 1px;border-style:solid;border-color:#ccc;}.postentry table tr th{border-width:1px 0 0 1px;border-style:solid;border-color:#ccc;padding:5px 10px;background:#f4f4f4;color:#666;font-weight:bold;text-transform:uppercase;text-align:center;}#comments{font-size:1.2em;}.commentlist{margin:20px 0;padding:0;border-width:0 0.1em 0.1em 0;border-color:#eee;border-style:solid;}.commentlist li{list-style:none;margin:0;padding:0;border-width:0.1em 0 0 0.1em;border-color:#eee;border-style:solid;}li.comment div,li.pingback div{padding:20px;overflow:auto;}li.comment div div,li.pingback div div{padding:0;overflow:visible;}.commentlist li.even{background-color:#fafafa;}.commentlist li.odd{background-color:#f6f6f6;}ul.children li{list-style:none;}img.avatar{float:right;border:1px solid #eee;padding:2px;margin:0;background:#fff;}.comment-meta,.reply{margin:0;padding:0;font-size:0.8em;}.comment-author cite{font-style:normal;font-weight:bold;font-size:1.2em;}textarea#comment{width:100%;}#comments div.navigation{font-size:0.9em;}#wp-calendar caption{text-transform:uppercase;font-weight:bold;color:#aaa;text-align:left;}#wp-calendar thead th{font-weight:normal;color:#27a;text-align:center;}#wp-calendar tbody td{text-align:center;}#wp-calendar tbody td a{font-weight:bold;}#wp-calendar tbody td.pad{border:none;}abbr{cursor:help;border-bottom:0.1em dotted;}.aligncenter,div.aligncenter{display:block;margin-left:auto;margin-right:auto;}.alignleft{float:left;margin:5px 5px 5px 0;}.alignright{float:right;margin:5px 0 5px 5px;}.wp-caption{border:1px solid #ddd;text-align:center;background-color:#f3f3f3;padding-top:4px;margin:10px;}.wp-caption img{margin:0;padding:0;border:0 none;}.wp-caption p.wp-caption-text{font-size:11px;line-height:17px;padding:0 4px 5px;margin:0;}.tagcloud a span{vertical-align:super;}
--------------------------------------------------------------------------------
/static/robots.txt:
--------------------------------------------------------------------------------
1 | User-Agent: *
2 | Crawl-delay: 10
3 |
4 | Disallow: /static/
5 | Disallow: /admin/
6 | Disallow: /templates/
7 | Sitemap: {{settings.domain}}/sitemap.xml
8 | Sitemap: {{settings.domain}}/baidu.xml
--------------------------------------------------------------------------------
/static/sitemap.xsl:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 | XML Sitemap
11 |
12 |
58 |
59 |
60 | XML Sitemap
61 |
68 |
69 |
70 |
71 | URL |
72 | Priority |
73 | Change Frequency |
74 | LastChange (GMT) |
75 |
76 |
77 |
78 |
79 |
80 |
81 | high
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | |
91 |
92 |
93 | |
94 |
95 |
96 | |
97 |
98 |
99 | |
100 |
101 |
102 |
103 |
104 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/templates/admin/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Blog Administrator
7 |
8 |
9 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
43 |
44 |
45 |
46 |
47 |
56 |
57 |
58 | {%block main%}{%endblock%}
59 |
60 |
61 |
62 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/templates/admin/category/index.html:
--------------------------------------------------------------------------------
1 | {%extends "admin/base.html" %}
2 | {% block main %}
3 |
4 |
10 |
11 |
12 | Category list
13 |
14 |
15 | Name |
16 | Slug |
17 |
18 |
19 |
20 | {% for c in categories%}
21 |
22 | {{c.name}} |
23 | {{c.slug}} |
24 |
25 | {%endfor%}
26 |
27 |
28 | {% endblock %}
--------------------------------------------------------------------------------
/templates/admin/index.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base.html" %}
2 | {% block main %}
3 | {% endblock %}
--------------------------------------------------------------------------------
/templates/admin/link/index.html:
--------------------------------------------------------------------------------
1 | {%extends "admin/base.html" %}
2 | {% block main %}
3 |
4 |
10 |
11 |
12 | Link list
13 |
14 |
15 | Name |
16 | URL |
17 |
18 |
19 |
20 | {% for o in pagination.items%}
21 |
22 | {{o.name}} |
23 | {{o.url}} |
24 |
25 | {%endfor%}
26 |
27 |
28 | {% from "macros/pagination.html" import admin_pagination%}
29 | {{admin_pagination(pagination,'/admin/links')}}
30 | {% endblock %}
--------------------------------------------------------------------------------
/templates/admin/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Admin Login
6 |
7 |
8 |
9 |
43 |
44 |
45 |
46 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/templates/admin/post/add.html:
--------------------------------------------------------------------------------
1 | {%extends "admin/base.html"%}
2 | {%block main%}
3 |
30 | {%endblock%}
--------------------------------------------------------------------------------
/templates/admin/post/index.html:
--------------------------------------------------------------------------------
1 | {%extends "admin/base.html" %}
2 | {% block main %}
3 |
6 |
7 | Post list
8 |
9 |
10 | Title |
11 | Category |
12 | Time |
13 | Action |
14 |
15 |
16 |
17 | {% for post in pagination.items %}
18 |
19 | {{post.title}} |
20 | {{post.category.name}} |
21 | {{post.created|datetimeformat}} |
22 | Update
23 | Delete |
24 |
25 | {%endfor%}
26 |
27 |
28 | {% from "macros/pagination.html" import admin_pagination%}
29 | {{admin_pagination(pagination,'/admin/posts')}}
30 | {% endblock %}
--------------------------------------------------------------------------------
/templates/admin/post/update.html:
--------------------------------------------------------------------------------
1 | {%extends "admin/base.html"%}
2 | {%block main%}
3 |
30 | {%endblock%}
--------------------------------------------------------------------------------
/templates/admin/user/index.html:
--------------------------------------------------------------------------------
1 | {%extends "admin/base.html" %}
2 | {% block main %}
3 |
4 |
5 | User list
6 |
7 |
8 | ID |
9 | Name |
10 | Email |
11 |
12 |
13 |
14 | {% for user in users%}
15 |
16 | {{user.id}} |
17 | {{user.username}} |
18 | {{user.email}} |
19 | |
20 |
21 | {%endfor%}
22 |
23 |
24 | {% endblock %}
--------------------------------------------------------------------------------
/templates/baidu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{settings.domain}}
4 | {{settings.site_name}}
5 | 1800
6 | {% for post in posts%}
7 | -
8 | {{post.title}}
9 | {{settings.domain}}{{post.url}}
10 | {{post.summary|markdown}}
11 |
12 | {{post.tags}}
13 | {{settings.site_name}}
14 | {{settings.site_name}}
15 | {{post.created|datetimeformat}}
16 |
17 | {% endfor %}
18 |
19 |
--------------------------------------------------------------------------------
/templates/comment_feed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{settings.domain}}
8 | zh-cn
9 | Wed, 19 Dec 2012 01:26:06 +0000
10 | hourly
11 | 1
12 | Rss Powered By {{settings.site_name}}
13 |
14 | {% for comment in post.comments %}
15 | -
16 | By: {{comment.author}}
17 | {{comment.url}}
18 | {{comment.author}}
19 | {{comment.created}}
20 | {{settings.domain}}/?p={{post.id}}#comment-{{comment.id}}
21 |
22 | {{comment.content}}]]>
23 |
24 | {% endfor %}
25 |
26 |
--------------------------------------------------------------------------------
/templates/default/archive.html:
--------------------------------------------------------------------------------
1 | {% extends "default/base.html" %}
2 | {%block main%}
3 |
4 |
5 |
6 |
7 | {% for post in pagination.items %}
8 |
32 | {% endfor %}
33 |
34 | {% from "macros/pagination.html" import render_pagination%}
35 | {{render_pagination(pagination,obj_url)}}
36 |
37 |
38 | {% include "default/sidebar.html" %}
39 |
40 |
41 |
42 |
43 | {%endblock%}
--------------------------------------------------------------------------------
/templates/default/base.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
11 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{settings.site_name}}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
47 |
48 |
49 | {% block main %}
50 | {% endblock %}
51 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/templates/default/comment.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | 发表评论
7 |
8 |
9 |
34 |
--------------------------------------------------------------------------------
/templates/default/index.html:
--------------------------------------------------------------------------------
1 | {% extends "default/base.html" %}
2 | {%block main%}
3 |
4 |
5 |
6 | {% for post in pagination.items %}
7 |
31 | {% endfor %}
32 |
33 | {% from "macros/pagination.html" import render_pagination%}
34 | {{render_pagination(pagination,'/page')}}
35 |
36 | {% include "default/sidebar.html" %}
37 |
38 |
39 | {%endblock%}
--------------------------------------------------------------------------------
/templates/default/post.html:
--------------------------------------------------------------------------------
1 | {% extends "default/base.html" %}
2 | {% block main %}
3 |
4 |
5 |
{{post.title}}
6 |
7 | {{post.content|markdown}}
8 |
9 |
10 |
11 |
发表于:{{post.created|datetimeformat}}
12 |
分类:{{post.category.name}}
13 | {%if post.tags%}
14 |
标签:
15 | {%for tag in post.taglist() %}
16 | {%if loop.last%}{{tag}}.{%else%}{{tag}},{%endif%}
17 | {% endfor %}
18 |
19 | {%endif%}
20 | {%if post.prev%}
21 |
24 | {%endif%}
25 | {%if post.next%}
26 |
29 | {%endif%}
30 |
31 |
34 |
35 | {% include "default/sidebar.html" %}
36 |
37 |
38 | {% endblock %}
--------------------------------------------------------------------------------
/templates/default/sidebar.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/errors/404.html:
--------------------------------------------------------------------------------
1 | 404 not found
--------------------------------------------------------------------------------
/templates/errors/exception.html:
--------------------------------------------------------------------------------
1 | {% set type, value, tback = sys.exc_info() %}
2 |
3 |
4 |
5 |
6 | HTTP Status {{ status_code }}
7 |
76 |
77 |
78 |
79 |
82 |
83 | {% if exception %}
84 | {% set traceback_list = traceback.extract_tb(tback) %}
85 | {% set filepath, line, method, code = traceback_list[-1] %}
86 |
87 |
88 |
Application raised {{ exception.__class__.__name__ }}: {{ exception }}
89 |
90 |
96 |
97 |
98 | {% set extension = os.path.splitext(filepath)[1][1:] %}
99 | {% if extension in ['py', 'html', 'htm'] %}
100 | {{ get_snippet(filepath, line, 10) }}
101 | {% else %}
102 | Cannot load file, type not supported.
103 | {% endif %}
104 | |
105 |
106 |
107 |
108 |
Full Traceback
109 |
110 | {% for filepath, line, method, code in traceback_list %}
111 |
112 |
118 |
119 |
120 | {% set extension = os.path.splitext(filepath)[1][1:] %}
121 | {% if extension in ['py', 'html', 'htm'] %}
122 | {{ get_snippet(filepath, line, 10) }}
123 | {% else %}
124 | Cannot load file, type not supported.
125 | {% endif %}
126 | |
127 |
128 |
129 |
130 | {% endfor %}
131 |
132 |
133 |
Request Headers
134 |
135 |
136 | {% for hk, hv in handler.request.headers.iteritems() %}
137 |
138 | {{ hk }} |
139 | {{ hv }} |
140 |
141 | {% endfor %}
142 |
143 |
144 |
145 |
Response Headers
146 |
147 |
148 | {% for hk, hv in handler._headers.iteritems() %}
149 |
150 | {{ hk }} |
151 | {{ hv }} |
152 |
153 | {% endfor %}
154 |
155 |
156 |
157 | {% endif %}
158 |
159 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/templates/feed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{settings.domain}}
7 | zh-cn
8 | {{settings.site_desc}}
9 | hourly
10 | 1
11 | Rss Powered By {{settings.site_name}}
12 | {% for post in posts%}
13 | -
14 |
15 | {{post.absolute_url}}
16 | {{post.absolute_url}}#comments
17 | {{post.created|datetimeformat}}
18 |
19 |
20 | {{post.comment_feed}}
21 | {{post.comments.count()}}
22 |
23 | {% endfor %}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/templates/fluid-blue/archive.html:
--------------------------------------------------------------------------------
1 | {%extends "fluid-blue/base.html"%}
2 | {%block title%}
3 | {% if flag == 'category'%}
4 | Archive for the ‘{{name}}’ Category.
5 | {%elif flag=='tag'%}
6 | Posts tagged ‘{{name}}’
7 | {%elif flag=='archives'%}
8 | Archive for the ‘{{year}}-{{month}}’ Archive.
9 | {% endif %}
10 | {%endblock%}
11 | {%block main%}
12 |
13 |
14 |
15 | {% if pagination.total %}
16 |
17 | {% if flag == 'category'%}
18 |
Archive for the ‘{{name}}’ Category.
19 | {%elif flag=='tag'%}
20 |
Posts tagged ‘{{name}}’
21 | {%elif flag=='archives'%}
22 |
Archive for the ‘{{year}}-{{month}}’ Archive.
23 | {% endif %}
24 |
25 |
29 |
30 | {%for post in pagination.items %}
31 |
32 |
33 |
34 |
{{post.created|datetimeformat}}
35 |
36 | {{post.summary|markdown}}
37 |
38 |
39 |
53 |
54 |
55 | {%endfor %}
56 |
57 |
61 |
62 | {%else%}
63 |
64 |
Not Found
65 |
Sorry, no posts matched your criteria.
66 |
67 |
68 | {%endif%}
69 |
70 |
71 | {%include "fluid-blue/sidebar.html"%}
72 | {%endblock%}
--------------------------------------------------------------------------------
/templates/fluid-blue/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {%block title%}{%endblock%} {{settings.site_name}}
7 |
8 |
9 |
10 |
11 |
12 |
13 | {%block header%}{%endblock%}
14 |
15 |
16 |
17 |
18 |
30 |
31 |
39 |
40 | {%block main%}
41 | {%endblock%}
42 |
43 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/templates/fluid-blue/comment.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/fluid-blue/index.html:
--------------------------------------------------------------------------------
1 | {%extends "fluid-blue/base.html"%}
2 | {%block main%}
3 |
4 |
5 | {% if pagination.total%}
6 | {% for post in pagination.items %}
7 |
8 |
12 |
{{post.created|datetimeformat}}
13 |
14 | {{post.summary|markdown}}
15 |
16 |
17 |
31 |
32 |
33 | {%endfor%}
34 |
35 |
39 |
40 | {%else%}
41 |
42 |
Not Found
43 |
Sorry, no posts matched your criteria.
44 |
45 | {%endif%}
46 |
47 |
48 | {%include "fluid-blue/sidebar.html"%}
49 | {%endblock%}
--------------------------------------------------------------------------------
/templates/fluid-blue/post.html:
--------------------------------------------------------------------------------
1 | {%extends "fluid-blue/base.html"%}
2 | {%block title%}{{post.title}}{%endblock%}
3 | {%block header%}
4 | {%if post%}
5 |
6 |
7 | {%endif%}
8 | {%endblock%}
9 | {%block main%}
10 |
11 | {%if post%}
12 |
13 | {%if post.prev%}
14 |
15 | {%endif%}
16 | {%if post.next%}
17 |
18 | {%endif%}
19 |
20 |
21 |
22 |
24 |
{{post.created|datetimeformat}}
25 |
26 | {{post.content|markdown}}
27 |
28 |
29 |
30 |
31 | {%if post.tags %}
32 | Tags:
33 | {%for tag in post.taglist() %}
34 | {%if loop.last%}
35 |
{{tag}}.
36 | {%else%}
37 |
{{tag}},
38 | {%endif%}
39 | {% endfor %}
40 | {%endif%}
41 | Category:
{{post.category.name}} |
Comment
42 |
43 |
44 |
45 |
46 | {%include "fluid-blue/comment.html"%}
47 |
48 | {%else%}
49 |
50 |
'Not Found
51 |
Sorry, no posts matched your criteria.
52 |
53 | {%endif%}
54 |
55 |
56 | {%include "fluid-blue/sidebar.html"%}
57 | {%endblock%}
--------------------------------------------------------------------------------
/templates/fluid-blue/sidebar.html:
--------------------------------------------------------------------------------
1 |
79 |
--------------------------------------------------------------------------------
/templates/macros/pagination.html:
--------------------------------------------------------------------------------
1 | {% macro render_pagination(pagination, endpoint) %}
2 |
3 | {% if pagination.has_prev %}
4 |
上一页
5 | {% endif %}
6 | {%- for page in pagination.iter_pages() %}
7 | {% if page %}
8 | {% if page != pagination.page %}
9 |
{{ page }}
10 | {% else %}
11 |
{{ page }}
12 | {% endif %}
13 | {% else %}
14 |
…
15 | {% endif %}
16 | {%- endfor %}
17 | {% if pagination.has_next %}
18 |
下一页
19 | {% endif %}
20 |
共 {{pagination.total}} 篇文章; {{pagination.page}}/{{pagination.pages}} 页
21 |
22 | {% endmacro %}
23 |
24 |
25 | {% macro admin_pagination(pagination, endpoint) %}
26 |
47 | {% endmacro %}
--------------------------------------------------------------------------------
/templates/mail/new_comment.html:
--------------------------------------------------------------------------------
1 | 文章{{comment.post.title}} 有了新的评论
2 |
3 | 作者:{{comment.author}} (IP: {{comment.ip}})
4 | 电子邮件: {{comment.email}}
5 | URL: {{comment.weburl}}
6 | 评论:
7 | {{comment.content}}
8 |
9 |
10 | 点击回复 {{comment.url}}
11 |
--------------------------------------------------------------------------------
/templates/mail/reply_comment.html:
--------------------------------------------------------------------------------
1 | {{comment.parent.author}} 您好,您之前在文章 "{{comment.post.title}}" 上的评论现在有了新的回复
2 |
3 | 您之前的评论是:
4 | {{comment.parent.content}}
5 |
6 | {{comment.author}}给您的新的回复如下:
7 | {{comment.content}}
8 |
9 | 您可以点击以下链接查看具体内容:
10 | {{comment.url}}
11 |
12 | 感谢您对 Logpress 的关注
13 |
14 | 该信件由系统自动发出, 请勿回复, 谢谢.
--------------------------------------------------------------------------------
/templates/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{settings.domain}}
10 | {{today|datetimeformat("%Y-%m-%dT%H:%M:%S+00:00")}}
11 | daily
12 | 1.0
13 |
14 |
15 | {%for post in posts%}
16 |
17 | {{settings.domain}}{{post.url}}
18 | {{post.created|datetimeformat("%Y-%m-%dT%H:%M:%S+00:00") }}
19 | monthly
20 | 0.5
21 |
22 | {%endfor%}
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/urls.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf8
3 | try:
4 | import psyco
5 | psyco.full()
6 | except:
7 | pass
8 | from tornado.web import url
9 |
10 | from handlers import account, admin, blog
11 | from handlers import ErrorHandler
12 |
13 | routes = []
14 | routes.extend(blog.routes)
15 | routes.extend(account.routes)
16 | routes.extend(admin.routes)
17 | routes.append((r"/(.*)", ErrorHandler))
18 |
--------------------------------------------------------------------------------
{{comment.content}} 17 |
18 | 19 |