├── MIT-LICENSE.txt ├── README.md ├── __init__.py ├── apps ├── __init__.py ├── admin.py └── code.py ├── config.yaml ├── db.sql ├── index.wsgi ├── libs ├── __init__.py ├── markdown.py └── utils.py ├── settings.py ├── static ├── blog.css ├── bootstrap-modal.js ├── bootstrap.min.css ├── prettify.css └── prettify.js ├── templates ├── base.html ├── compose.html ├── entry.html ├── feed.xml ├── home.html ├── login.html ├── page.html ├── start.html └── update.html └── urls.py /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2012 SerhoLiu, http://serholiu.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the “Software”), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to 8 | do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 15 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 16 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 17 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 18 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 这个是一个运行在SAE(Sina App Engine)上,基于Tornado的简单的代码分享网站。见:http://tocode.sinaapp.com 2 | 3 | ## 使用方法 4 | 5 | > 1. 在 SAE 上新建 Python 应用,在`setting.py`中设置站点名,修改后上传 6 | > 2. 开启MySQL服务,进入数据库,导入db.sql 7 | > 3. 打开 http://youapp.sinaapp.com/admin/start 输入你的管理员帐号(为了安全,建议初始化后删除urls.py文件中的对应网址) 8 | 9 | ## 一些特征 10 | 11 | 用户可以自由发布代码,拥有一个自己设定的密码,用于修改和删除自己的代码,管理员可以删除代码,如果要修改只能去数据库修改。 12 | 13 | ## License 14 | 15 | See MIT-LICENSE.txt -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhoLiu/CodeShare/34a00f13606777962be88a8b61b178b0fdaebf4b/__init__.py -------------------------------------------------------------------------------- /apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhoLiu/CodeShare/34a00f13606777962be88a8b61b178b0fdaebf4b/apps/__init__.py -------------------------------------------------------------------------------- /apps/admin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import tornado.web 5 | from code import BaseHandler 6 | from libs.utils import hexpassword, checkpassword 7 | 8 | 9 | class SiteStartHandler(BaseHandler): 10 | 11 | def get(self): 12 | admin = self.db.query("SELECT * FROM users ORDER BY id DESC LIMIT 1") 13 | if not admin: 14 | self.render("start.html") 15 | else: 16 | self.redirect("/") 17 | 18 | def post(self): 19 | email = self.get_argument("email") 20 | pswd1 = self.get_argument("password1") 21 | pswd2 = self.get_argument("password2") 22 | 23 | if pswd1 != pswd2: 24 | self.redirect("/admin/start") 25 | return 26 | password = hexpassword(pswd1) 27 | self.db.execute( 28 | "INSERT INTO users (password,email) VALUES (%s,%s)", password, email) 29 | self.redirect("/auth/login") 30 | 31 | 32 | class LoginHandler(BaseHandler): 33 | 34 | def get(self): 35 | if self.current_user: 36 | self.redirect("/") 37 | return 38 | self.render("login.html", msg=0) 39 | 40 | def post(self): 41 | email = self.get_argument("email", None) 42 | password = self.get_argument("password") 43 | 44 | user = self.db.get("SELECT * FROM users WHERE email = %s", email) 45 | if user and checkpassword(password, user["password"]): 46 | self.set_secure_cookie("user", str(user["id"])) 47 | self.redirect("/") 48 | else: 49 | msg = "Error" 50 | self.render("login.html", msg=msg) 51 | 52 | 53 | class LogoutHandler(BaseHandler): 54 | 55 | def get(self): 56 | self.clear_cookie("user") 57 | self.redirect(self.get_argument("next", "/")) 58 | 59 | 60 | class DeleteHandler(BaseHandler): 61 | 62 | @tornado.web.authenticated 63 | def get(self, slug): 64 | code = self.db.get("SELECT * FROM entries WHERE slug = %s", str(slug)) 65 | if not code: 66 | raise tornado.web.HTTPError(404) 67 | else: 68 | self.db.execute("DELETE FROM entries WHERE slug=%s", str(slug)) 69 | self.redirect("/") 70 | -------------------------------------------------------------------------------- /apps/code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | code 发布和管理模块 6 | """ 7 | 8 | import datetime 9 | import tornado.web 10 | import tornado.database 11 | from settings import db, NAVNUM 12 | from libs import markdown 13 | from tornado.escape import xhtml_escape 14 | from libs.utils import hexuserpass, checkuserpass 15 | 16 | md = markdown.Markdown(safe_mode=True) 17 | 18 | 19 | class BaseHandler(tornado.web.RequestHandler): 20 | 21 | @property 22 | def db(self): 23 | blogdb = tornado.database.Connection( 24 | host=db["host"] + ":" + db["port"], database=db["db"], 25 | user=db["user"], password=db["password"]) 26 | return blogdb 27 | 28 | def get_current_user(self): 29 | user_id = self.get_secure_cookie("user") 30 | if not user_id: 31 | return None 32 | return self.db.get("SELECT * FROM users WHERE id = %s", int(user_id)) 33 | 34 | 35 | class HomeHandler(BaseHandler): 36 | 37 | def get(self): 38 | entries = self.db.query("SELECT * FROM entries ORDER BY published " 39 | "DESC LIMIT 8") 40 | if not entries: 41 | self.redirect("/newcode") 42 | return 43 | results = self.db.query("SELECT COUNT(*) As code FROM entries") 44 | count = results[0].code 45 | pages = (count - 1) / NAVNUM + 1 46 | self.render("home.html", entries=entries, pages=pages, counts=count) 47 | 48 | 49 | class PageHandler(BaseHandler): 50 | 51 | def get(self, id): 52 | results = self.db.query("SELECT COUNT(*) As code FROM entries") 53 | count = results[0].code 54 | pages = (count - 1) / NAVNUM + 1 55 | offset = (int(id) - 1) * NAVNUM 56 | entries = self.db.query(""" 57 | SELECT * FROM entries ORDER BY published DESC LIMIT 8 OFFSET %s 58 | """, offset) 59 | self.render("page.html", entries=entries, pages = pages, this=int(id), 60 | counts=count) 61 | 62 | 63 | class EntryHandler(BaseHandler): 64 | 65 | def get(self, slug): 66 | entry = self.db.get("SELECT * FROM entries WHERE slug = %s", slug) 67 | if not entry: 68 | raise tornado.web.HTTPError(404) 69 | self.render("entry.html", entry=entry) 70 | 71 | 72 | class FeedHandler(BaseHandler): 73 | 74 | def get(self): 75 | entries = self.db.query("SELECT * FROM entries ORDER BY published " 76 | "DESC LIMIT 10") 77 | self.set_header("Content-Type", "application/atom+xml") 78 | self.render("feed.xml", entries=entries) 79 | 80 | 81 | class ComposeHandler(BaseHandler): 82 | 83 | def get(self): 84 | self.render("compose.html") 85 | 86 | def post(self): 87 | #id = self.get_argument("id", None) 88 | title = xhtml_escape(self.get_argument("title")) 89 | tep = self.get_argument("info") 90 | code = xhtml_escape(self.get_argument("code")) 91 | pswd = self.get_argument("password") 92 | 93 | # 添加了一个丑陋的验证机制,预防无脑的机器发布垃圾信息 94 | check = self.get_argument("check", None) 95 | if check != "1984": 96 | self.redirect("/newcode") 97 | return 98 | 99 | info = md.convert(tep) 100 | password = hexuserpass(pswd) 101 | slug = "zzzzzzzz" # 丑陋的一笔 102 | self.db.execute(""" 103 | INSERT INTO entries (password,title,slug,code,info,markdown, 104 | published) VALUES (%s,%s,%s,%s,%s,%s,%s) 105 | """, password, title, slug, code, info, tep, datetime.datetime.now()) 106 | e = self.db.get("SELECT * FROM entries WHERE slug = %s", slug) 107 | eid = e.id 108 | slug = eid 109 | self.db.execute("UPDATE entries SET slug = %s WHERE id = %s", slug, int(eid)) 110 | self.redirect("/" + str(slug)) 111 | 112 | 113 | class DeleteHandler(BaseHandler): 114 | 115 | def post(self): 116 | password = self.get_argument("password") 117 | id = self.get_argument("id") 118 | e = self.db.get("SELECT * FROM entries WHERE id = %s", int(id)) 119 | 120 | if checkuserpass(password, e["password"]): 121 | self.db.execute("DELETE FROM entries WHERE id=%s", int(id)) 122 | self.redirect("/") 123 | else: 124 | self.redirect("/" + str(id)) 125 | 126 | 127 | class UserLoginHandler(BaseHandler): 128 | 129 | def post(self): 130 | password = self.get_argument("password") 131 | id = self.get_argument("id") 132 | e = self.db.get("SELECT * FROM entries WHERE id = %s", int(id)) 133 | if checkuserpass(password, e["password"]): 134 | self.set_secure_cookie("codeid", str(id)) 135 | self.redirect("/update/" + str(id)) 136 | else: 137 | self.redirect("/" + str(id)) 138 | 139 | 140 | class UpdateHandler(BaseHandler): 141 | 142 | def get(self, codeid): 143 | id = self.get_secure_cookie("codeid") 144 | if str(codeid) == str(id): 145 | code = self.db.get("SELECT * FROM entries WHERE id = %s", int(id)) 146 | self.render("update.html", code=code) 147 | else: 148 | self.redirect("/" + str(codeid)) 149 | 150 | def post(self, codeid): 151 | title = xhtml_escape(self.get_argument("title")) 152 | tep = self.get_argument("info") 153 | code = xhtml_escape(self.get_argument("code")) 154 | pswd = self.get_argument("password") 155 | info = md.convert(tep) 156 | codes = self.db.get("SELECT * FROM entries WHERE id = %s", int(codeid)) 157 | if checkuserpass(pswd, codes["password"]): 158 | self.db.execute(""" 159 | UPDATE entries SET title = %s, info = %s, code = %s, markdown = %s 160 | WHERE id = %s""", title, info, code, tep, int(codeid)) 161 | self.clear_cookie("codeid") 162 | self.redirect("/" + str(codeid)) 163 | else: 164 | self.redirect("/" + str(codeid)) 165 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tosae 3 | version: 1 4 | ... 5 | -------------------------------------------------------------------------------- /db.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 3.3.8.1 3 | -- http://www.phpmyadmin.net 4 | -- 5 | 6 | SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; 7 | 8 | 9 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 10 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 11 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 12 | /*!40101 SET NAMES utf8 */; 13 | 14 | -- -------------------------------------------------------- 15 | 16 | -- 17 | -- 表的结构 `entries` 18 | -- 19 | 20 | CREATE TABLE IF NOT EXISTS `entries` ( 21 | `id` int(11) NOT NULL AUTO_INCREMENT, 22 | `password` varchar(100) NOT NULL, 23 | `slug` varchar(100) NOT NULL, 24 | `title` varchar(512) NOT NULL, 25 | `info` mediumtext NOT NULL, 26 | `markdown` mediumtext NOT NULL, 27 | `code` mediumtext NOT NULL, 28 | `published` datetime NOT NULL, 29 | `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 30 | PRIMARY KEY (`id`), 31 | UNIQUE KEY `slug` (`slug`), 32 | KEY `published` (`published`) 33 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; 34 | 35 | -- 36 | -- 转存表中的数据 `entries` 37 | -- 38 | 39 | 40 | -- -------------------------------------------------------- 41 | 42 | -- 43 | -- 表的结构 `users` 44 | -- 45 | 46 | CREATE TABLE IF NOT EXISTS `users` ( 47 | `id` int(11) NOT NULL AUTO_INCREMENT, 48 | `email` varchar(100) NOT NULL, 49 | `password` varchar(100) NOT NULL, 50 | PRIMARY KEY (`id`), 51 | UNIQUE KEY `email` (`email`) 52 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; 53 | 54 | -- 55 | -- 转存表中的数据 `users` 56 | -- 57 | 58 | -------------------------------------------------------------------------------- /index.wsgi: -------------------------------------------------------------------------------- 1 | import sae 2 | import tornado.wsgi 3 | 4 | from urls import urls 5 | from settings import settings 6 | 7 | app = tornado.wsgi.WSGIApplication(urls,**settings) 8 | 9 | application = sae.create_wsgi_app(app) -------------------------------------------------------------------------------- /libs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhoLiu/CodeShare/34a00f13606777962be88a8b61b178b0fdaebf4b/libs/__init__.py -------------------------------------------------------------------------------- /libs/markdown.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2007-2008 ActiveState Corp. 3 | # License: MIT (http://www.opensource.org/licenses/mit-license.php) 4 | 5 | r"""A fast and complete Python implementation of Markdown. 6 | 7 | [from http://daringfireball.net/projects/markdown/] 8 | > Markdown is a text-to-HTML filter; it translates an easy-to-read / 9 | > easy-to-write structured text format into HTML. Markdown's text 10 | > format is most similar to that of plain text email, and supports 11 | > features such as headers, *emphasis*, code blocks, blockquotes, and 12 | > links. 13 | > 14 | > Markdown's syntax is designed not as a generic markup language, but 15 | > specifically to serve as a front-end to (X)HTML. You can use span-level 16 | > HTML tags anywhere in a Markdown document, and you can use block level 17 | > HTML tags (like
and as well). 18 | 19 | Module usage: 20 | 21 | >>> import markdown2 22 | >>> markdown2.markdown("*boo!*") # or use `html = markdown_path(PATH)` 23 | u'

boo!

\n' 24 | 25 | >>> markdowner = Markdown() 26 | >>> markdowner.convert("*boo!*") 27 | u'

boo!

\n' 28 | >>> markdowner.convert("**boom!**") 29 | u'

boom!

\n' 30 | 31 | This implementation of Markdown implements the full "core" syntax plus a 32 | number of extras (e.g., code syntax coloring, footnotes) as described on 33 | . 34 | """ 35 | 36 | cmdln_desc = """A fast and complete Python implementation of Markdown, a 37 | text-to-HTML conversion tool for web writers. 38 | """ 39 | 40 | # Dev Notes: 41 | # - There is already a Python markdown processor 42 | # (http://www.freewisdom.org/projects/python-markdown/). 43 | # - Python's regex syntax doesn't have '\z', so I'm using '\Z'. I'm 44 | # not yet sure if there implications with this. Compare 'pydoc sre' 45 | # and 'perldoc perlre'. 46 | 47 | __version_info__ = (1, 0, 1, 14) # first three nums match Markdown.pl 48 | __version__ = '1.0.1.14' 49 | __author__ = "Trent Mick" 50 | 51 | import os 52 | import sys 53 | from pprint import pprint 54 | import re 55 | import logging 56 | try: 57 | from hashlib import md5 58 | except ImportError: 59 | from md5 import md5 60 | import optparse 61 | from random import random 62 | import codecs 63 | 64 | 65 | 66 | #---- Python version compat 67 | 68 | if sys.version_info[:2] < (2,4): 69 | from sets import Set as set 70 | def reversed(sequence): 71 | for i in sequence[::-1]: 72 | yield i 73 | def _unicode_decode(s, encoding, errors='xmlcharrefreplace'): 74 | return unicode(s, encoding, errors) 75 | else: 76 | def _unicode_decode(s, encoding, errors='strict'): 77 | return s.decode(encoding, errors) 78 | 79 | 80 | #---- globals 81 | 82 | DEBUG = False 83 | log = logging.getLogger("markdown") 84 | 85 | DEFAULT_TAB_WIDTH = 4 86 | 87 | # Table of hash values for escaped characters: 88 | def _escape_hash(s): 89 | # Lame attempt to avoid possible collision with someone actually 90 | # using the MD5 hexdigest of one of these chars in there text. 91 | # Other ideas: random.random(), uuid.uuid() 92 | #return md5(s).hexdigest() # Markdown.pl effectively does this. 93 | return 'md5-'+md5(s).hexdigest() 94 | g_escape_table = dict([(ch, _escape_hash(ch)) 95 | for ch in '\\`*_{}[]()>#+-.!']) 96 | 97 | 98 | 99 | #---- exceptions 100 | 101 | class MarkdownError(Exception): 102 | pass 103 | 104 | 105 | 106 | #---- public api 107 | 108 | def markdown_path(path, encoding="utf-8", 109 | html4tags=False, tab_width=DEFAULT_TAB_WIDTH, 110 | safe_mode=None, extras=None, link_patterns=None, 111 | use_file_vars=False): 112 | text = codecs.open(path, 'r', encoding).read() 113 | return Markdown(html4tags=html4tags, tab_width=tab_width, 114 | safe_mode=safe_mode, extras=extras, 115 | link_patterns=link_patterns, 116 | use_file_vars=use_file_vars).convert(text) 117 | 118 | def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH, 119 | safe_mode=None, extras=None, link_patterns=None, 120 | use_file_vars=False): 121 | return Markdown(html4tags=html4tags, tab_width=tab_width, 122 | safe_mode=safe_mode, extras=extras, 123 | link_patterns=link_patterns, 124 | use_file_vars=use_file_vars).convert(text) 125 | 126 | class Markdown(object): 127 | # The dict of "extras" to enable in processing -- a mapping of 128 | # extra name to argument for the extra. Most extras do not have an 129 | # argument, in which case the value is None. 130 | # 131 | # This can be set via (a) subclassing and (b) the constructor 132 | # "extras" argument. 133 | extras = None 134 | 135 | urls = None 136 | titles = None 137 | html_blocks = None 138 | html_spans = None 139 | html_removed_text = "[HTML_REMOVED]" # for compat with markdown.py 140 | 141 | # Used to track when we're inside an ordered or unordered list 142 | # (see _ProcessListItems() for details): 143 | list_level = 0 144 | 145 | _ws_only_line_re = re.compile(r"^[ \t]+$", re.M) 146 | 147 | def __init__(self, html4tags=False, tab_width=4, safe_mode=None, 148 | extras=None, link_patterns=None, use_file_vars=False): 149 | if html4tags: 150 | self.empty_element_suffix = ">" 151 | else: 152 | self.empty_element_suffix = " />" 153 | self.tab_width = tab_width 154 | 155 | # For compatibility with earlier markdown2.py and with 156 | # markdown.py's safe_mode being a boolean, 157 | # safe_mode == True -> "replace" 158 | if safe_mode is True: 159 | self.safe_mode = "replace" 160 | else: 161 | self.safe_mode = safe_mode 162 | 163 | if self.extras is None: 164 | self.extras = {} 165 | elif not isinstance(self.extras, dict): 166 | self.extras = dict([(e, None) for e in self.extras]) 167 | if extras: 168 | if not isinstance(extras, dict): 169 | extras = dict([(e, None) for e in extras]) 170 | self.extras.update(extras) 171 | assert isinstance(self.extras, dict) 172 | self._instance_extras = self.extras.copy() 173 | self.link_patterns = link_patterns 174 | self.use_file_vars = use_file_vars 175 | self._outdent_re = re.compile(r'^(\t|[ ]{1,%d})' % tab_width, re.M) 176 | 177 | def reset(self): 178 | self.urls = {} 179 | self.titles = {} 180 | self.html_blocks = {} 181 | self.html_spans = {} 182 | self.list_level = 0 183 | self.extras = self._instance_extras.copy() 184 | if "footnotes" in self.extras: 185 | self.footnotes = {} 186 | self.footnote_ids = [] 187 | 188 | def convert(self, text): 189 | """Convert the given text.""" 190 | # Main function. The order in which other subs are called here is 191 | # essential. Link and image substitutions need to happen before 192 | # _EscapeSpecialChars(), so that any *'s or _'s in the 193 | # and tags get encoded. 194 | 195 | # Clear the global hashes. If we don't clear these, you get conflicts 196 | # from other articles when generating a page which contains more than 197 | # one article (e.g. an index page that shows the N most recent 198 | # articles): 199 | self.reset() 200 | 201 | if not isinstance(text, unicode): 202 | #TODO: perhaps shouldn't presume UTF-8 for string input? 203 | text = unicode(text, 'utf-8') 204 | 205 | if self.use_file_vars: 206 | # Look for emacs-style file variable hints. 207 | emacs_vars = self._get_emacs_vars(text) 208 | if "markdown-extras" in emacs_vars: 209 | splitter = re.compile("[ ,]+") 210 | for e in splitter.split(emacs_vars["markdown-extras"]): 211 | if '=' in e: 212 | ename, earg = e.split('=', 1) 213 | try: 214 | earg = int(earg) 215 | except ValueError: 216 | pass 217 | else: 218 | ename, earg = e, None 219 | self.extras[ename] = earg 220 | 221 | # Standardize line endings: 222 | text = re.sub("\r\n|\r", "\n", text) 223 | 224 | # Make sure $text ends with a couple of newlines: 225 | text += "\n\n" 226 | 227 | # Convert all tabs to spaces. 228 | text = self._detab(text) 229 | 230 | # Strip any lines consisting only of spaces and tabs. 231 | # This makes subsequent regexen easier to write, because we can 232 | # match consecutive blank lines with /\n+/ instead of something 233 | # contorted like /[ \t]*\n+/ . 234 | text = self._ws_only_line_re.sub("", text) 235 | 236 | if self.safe_mode: 237 | text = self._hash_html_spans(text) 238 | 239 | # Turn block-level HTML blocks into hash entries 240 | text = self._hash_html_blocks(text, raw=True) 241 | 242 | # Strip link definitions, store in hashes. 243 | if "footnotes" in self.extras: 244 | # Must do footnotes first because an unlucky footnote defn 245 | # looks like a link defn: 246 | # [^4]: this "looks like a link defn" 247 | text = self._strip_footnote_definitions(text) 248 | text = self._strip_link_definitions(text) 249 | 250 | text = self._run_block_gamut(text) 251 | 252 | if "footnotes" in self.extras: 253 | text = self._add_footnotes(text) 254 | 255 | text = self._unescape_special_chars(text) 256 | 257 | if self.safe_mode: 258 | text = self._unhash_html_spans(text) 259 | 260 | text += "\n" 261 | return text 262 | 263 | _emacs_oneliner_vars_pat = re.compile(r"-\*-\s*([^\r\n]*?)\s*-\*-", re.UNICODE) 264 | # This regular expression is intended to match blocks like this: 265 | # PREFIX Local Variables: SUFFIX 266 | # PREFIX mode: Tcl SUFFIX 267 | # PREFIX End: SUFFIX 268 | # Some notes: 269 | # - "[ \t]" is used instead of "\s" to specifically exclude newlines 270 | # - "(\r\n|\n|\r)" is used instead of "$" because the sre engine does 271 | # not like anything other than Unix-style line terminators. 272 | _emacs_local_vars_pat = re.compile(r"""^ 273 | (?P(?:[^\r\n|\n|\r])*?) 274 | [\ \t]*Local\ Variables:[\ \t]* 275 | (?P.*?)(?:\r\n|\n|\r) 276 | (?P.*?\1End:) 277 | """, re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE) 278 | 279 | def _get_emacs_vars(self, text): 280 | """Return a dictionary of emacs-style local variables. 281 | 282 | Parsing is done loosely according to this spec (and according to 283 | some in-practice deviations from this): 284 | http://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html#Specifying-File-Variables 285 | """ 286 | emacs_vars = {} 287 | SIZE = pow(2, 13) # 8kB 288 | 289 | # Search near the start for a '-*-'-style one-liner of variables. 290 | head = text[:SIZE] 291 | if "-*-" in head: 292 | match = self._emacs_oneliner_vars_pat.search(head) 293 | if match: 294 | emacs_vars_str = match.group(1) 295 | assert '\n' not in emacs_vars_str 296 | emacs_var_strs = [s.strip() for s in emacs_vars_str.split(';') 297 | if s.strip()] 298 | if len(emacs_var_strs) == 1 and ':' not in emacs_var_strs[0]: 299 | # While not in the spec, this form is allowed by emacs: 300 | # -*- Tcl -*- 301 | # where the implied "variable" is "mode". This form 302 | # is only allowed if there are no other variables. 303 | emacs_vars["mode"] = emacs_var_strs[0].strip() 304 | else: 305 | for emacs_var_str in emacs_var_strs: 306 | try: 307 | variable, value = emacs_var_str.strip().split(':', 1) 308 | except ValueError: 309 | log.debug("emacs variables error: malformed -*- " 310 | "line: %r", emacs_var_str) 311 | continue 312 | # Lowercase the variable name because Emacs allows "Mode" 313 | # or "mode" or "MoDe", etc. 314 | emacs_vars[variable.lower()] = value.strip() 315 | 316 | tail = text[-SIZE:] 317 | if "Local Variables" in tail: 318 | match = self._emacs_local_vars_pat.search(tail) 319 | if match: 320 | prefix = match.group("prefix") 321 | suffix = match.group("suffix") 322 | lines = match.group("content").splitlines(0) 323 | #print "prefix=%r, suffix=%r, content=%r, lines: %s"\ 324 | # % (prefix, suffix, match.group("content"), lines) 325 | 326 | # Validate the Local Variables block: proper prefix and suffix 327 | # usage. 328 | for i, line in enumerate(lines): 329 | if not line.startswith(prefix): 330 | log.debug("emacs variables error: line '%s' " 331 | "does not use proper prefix '%s'" 332 | % (line, prefix)) 333 | return {} 334 | # Don't validate suffix on last line. Emacs doesn't care, 335 | # neither should we. 336 | if i != len(lines)-1 and not line.endswith(suffix): 337 | log.debug("emacs variables error: line '%s' " 338 | "does not use proper suffix '%s'" 339 | % (line, suffix)) 340 | return {} 341 | 342 | # Parse out one emacs var per line. 343 | continued_for = None 344 | for line in lines[:-1]: # no var on the last line ("PREFIX End:") 345 | if prefix: line = line[len(prefix):] # strip prefix 346 | if suffix: line = line[:-len(suffix)] # strip suffix 347 | line = line.strip() 348 | if continued_for: 349 | variable = continued_for 350 | if line.endswith('\\'): 351 | line = line[:-1].rstrip() 352 | else: 353 | continued_for = None 354 | emacs_vars[variable] += ' ' + line 355 | else: 356 | try: 357 | variable, value = line.split(':', 1) 358 | except ValueError: 359 | log.debug("local variables error: missing colon " 360 | "in local variables entry: '%s'" % line) 361 | continue 362 | # Do NOT lowercase the variable name, because Emacs only 363 | # allows "mode" (and not "Mode", "MoDe", etc.) in this block. 364 | value = value.strip() 365 | if value.endswith('\\'): 366 | value = value[:-1].rstrip() 367 | continued_for = variable 368 | else: 369 | continued_for = None 370 | emacs_vars[variable] = value 371 | 372 | # Unquote values. 373 | for var, val in emacs_vars.items(): 374 | if len(val) > 1 and (val.startswith('"') and val.endswith('"') 375 | or val.startswith('"') and val.endswith('"')): 376 | emacs_vars[var] = val[1:-1] 377 | 378 | return emacs_vars 379 | 380 | # Cribbed from a post by Bart Lateur: 381 | # 382 | _detab_re = re.compile(r'(.*?)\t', re.M) 383 | def _detab_sub(self, match): 384 | g1 = match.group(1) 385 | return g1 + (' ' * (self.tab_width - len(g1) % self.tab_width)) 386 | def _detab(self, text): 387 | r"""Remove (leading?) tabs from a file. 388 | 389 | >>> m = Markdown() 390 | >>> m._detab("\tfoo") 391 | ' foo' 392 | >>> m._detab(" \tfoo") 393 | ' foo' 394 | >>> m._detab("\t foo") 395 | ' foo' 396 | >>> m._detab(" foo") 397 | ' foo' 398 | >>> m._detab(" foo\n\tbar\tblam") 399 | ' foo\n bar blam' 400 | """ 401 | if '\t' not in text: 402 | return text 403 | return self._detab_re.subn(self._detab_sub, text)[0] 404 | 405 | _block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del' 406 | _strict_tag_block_re = re.compile(r""" 407 | ( # save in \1 408 | ^ # start of line (with re.M) 409 | <(%s) # start tag = \2 410 | \b # word break 411 | (.*\n)*? # any number of lines, minimally matching 412 | # the matching end tag 413 | [ \t]* # trailing spaces/tabs 414 | (?=\n+|\Z) # followed by a newline or end of document 415 | ) 416 | """ % _block_tags_a, 417 | re.X | re.M) 418 | 419 | _block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math' 420 | _liberal_tag_block_re = re.compile(r""" 421 | ( # save in \1 422 | ^ # start of line (with re.M) 423 | <(%s) # start tag = \2 424 | \b # word break 425 | (.*\n)*? # any number of lines, minimally matching 426 | .* # the matching end tag 427 | [ \t]* # trailing spaces/tabs 428 | (?=\n+|\Z) # followed by a newline or end of document 429 | ) 430 | """ % _block_tags_b, 431 | re.X | re.M) 432 | 433 | def _hash_html_block_sub(self, match, raw=False): 434 | html = match.group(1) 435 | if raw and self.safe_mode: 436 | html = self._sanitize_html(html) 437 | key = _hash_text(html) 438 | self.html_blocks[key] = html 439 | return "\n\n" + key + "\n\n" 440 | 441 | def _hash_html_blocks(self, text, raw=False): 442 | """Hashify HTML blocks 443 | 444 | We only want to do this for block-level HTML tags, such as headers, 445 | lists, and tables. That's because we still want to wrap

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

461 | #
462 | # tags for inner block must be indented. 463 | #
464 | #
465 | # 466 | # The outermost tags must start at the left margin for this to match, and 467 | # the inner nested divs must be indented. 468 | # We need to do this before the next, more liberal match, because the next 469 | # match will start at the first `
` and stop at the first `
`. 470 | text = self._strict_tag_block_re.sub(hash_html_block_sub, text) 471 | 472 | # Now match more liberally, simply from `\n` to `\n` 473 | text = self._liberal_tag_block_re.sub(hash_html_block_sub, text) 474 | 475 | # Special case just for
. It was easier to make a special 476 | # case than to make the other regex more complicated. 477 | if "", start_idx) + 3 492 | except ValueError, ex: 493 | break 494 | 495 | # Start position for next comment block search. 496 | start = end_idx 497 | 498 | # Validate whitespace before comment. 499 | if start_idx: 500 | # - Up to `tab_width - 1` spaces before start_idx. 501 | for i in range(self.tab_width - 1): 502 | if text[start_idx - 1] != ' ': 503 | break 504 | start_idx -= 1 505 | if start_idx == 0: 506 | break 507 | # - Must be preceded by 2 newlines or hit the start of 508 | # the document. 509 | if start_idx == 0: 510 | pass 511 | elif start_idx == 1 and text[0] == '\n': 512 | start_idx = 0 # to match minute detail of Markdown.pl regex 513 | elif text[start_idx-2:start_idx] == '\n\n': 514 | pass 515 | else: 516 | break 517 | 518 | # Validate whitespace after comment. 519 | # - Any number of spaces and tabs. 520 | while end_idx < len(text): 521 | if text[end_idx] not in ' \t': 522 | break 523 | end_idx += 1 524 | # - Must be following by 2 newlines or hit end of text. 525 | if text[end_idx:end_idx+2] not in ('', '\n', '\n\n'): 526 | continue 527 | 528 | # Escape and hash (must match `_hash_html_block_sub`). 529 | html = text[start_idx:end_idx] 530 | if raw and self.safe_mode: 531 | html = self._sanitize_html(html) 532 | key = _hash_text(html) 533 | self.html_blocks[key] = html 534 | text = text[:start_idx] + "\n\n" + key + "\n\n" + text[end_idx:] 535 | 536 | if "xml" in self.extras: 537 | # Treat XML processing instructions and namespaced one-liner 538 | # tags as if they were block HTML tags. E.g., if standalone 539 | # (i.e. are their own paragraph), the following do not get 540 | # wrapped in a

tag: 541 | # 542 | # 543 | # 544 | _xml_oneliner_re = _xml_oneliner_re_from_tab_width(self.tab_width) 545 | text = _xml_oneliner_re.sub(hash_html_block_sub, text) 546 | 547 | return text 548 | 549 | def _strip_link_definitions(self, text): 550 | # Strips link definitions from text, stores the URLs and titles in 551 | # hash references. 552 | less_than_tab = self.tab_width - 1 553 | 554 | # Link defs are in the form: 555 | # [id]: url "optional title" 556 | _link_def_re = re.compile(r""" 557 | ^[ ]{0,%d}\[(.+)\]: # id = \1 558 | [ \t]* 559 | \n? # maybe *one* newline 560 | [ \t]* 561 | ? # url = \2 562 | [ \t]* 563 | (?: 564 | \n? # maybe one newline 565 | [ \t]* 566 | (?<=\s) # lookbehind for whitespace 567 | ['"(] 568 | ([^\n]*) # title = \3 569 | ['")] 570 | [ \t]* 571 | )? # title is optional 572 | (?:\n+|\Z) 573 | """ % less_than_tab, re.X | re.M | re.U) 574 | return _link_def_re.sub(self._extract_link_def_sub, text) 575 | 576 | def _extract_link_def_sub(self, match): 577 | id, url, title = match.groups() 578 | key = id.lower() # Link IDs are case-insensitive 579 | self.urls[key] = self._encode_amps_and_angles(url) 580 | if title: 581 | self.titles[key] = title.replace('"', '"') 582 | return "" 583 | 584 | def _extract_footnote_def_sub(self, match): 585 | id, text = match.groups() 586 | text = _dedent(text, skip_first_line=not text.startswith('\n')).strip() 587 | normed_id = re.sub(r'\W', '-', id) 588 | # Ensure footnote text ends with a couple newlines (for some 589 | # block gamut matches). 590 | self.footnotes[normed_id] = text + "\n\n" 591 | return "" 592 | 593 | def _strip_footnote_definitions(self, text): 594 | """A footnote definition looks like this: 595 | 596 | [^note-id]: Text of the note. 597 | 598 | May include one or more indented paragraphs. 599 | 600 | Where, 601 | - The 'note-id' can be pretty much anything, though typically it 602 | is the number of the footnote. 603 | - The first paragraph may start on the next line, like so: 604 | 605 | [^note-id]: 606 | Text of the note. 607 | """ 608 | less_than_tab = self.tab_width - 1 609 | footnote_def_re = re.compile(r''' 610 | ^[ ]{0,%d}\[\^(.+)\]: # id = \1 611 | [ \t]* 612 | ( # footnote text = \2 613 | # First line need not start with the spaces. 614 | (?:\s*.*\n+) 615 | (?: 616 | (?:[ ]{%d} | \t) # Subsequent lines must be indented. 617 | .*\n+ 618 | )* 619 | ) 620 | # Lookahead for non-space at line-start, or end of doc. 621 | (?:(?=^[ ]{0,%d}\S)|\Z) 622 | ''' % (less_than_tab, self.tab_width, self.tab_width), 623 | re.X | re.M) 624 | return footnote_def_re.sub(self._extract_footnote_def_sub, text) 625 | 626 | 627 | _hr_res = [ 628 | re.compile(r"^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$", re.M), 629 | re.compile(r"^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$", re.M), 630 | re.compile(r"^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$", re.M), 631 | ] 632 | 633 | def _run_block_gamut(self, text): 634 | # These are all the transformations that form block-level 635 | # tags like paragraphs, headers, and list items. 636 | 637 | text = self._do_headers(text) 638 | 639 | # Do Horizontal Rules: 640 | hr = "\n tags around block-level tags. 657 | text = self._hash_html_blocks(text) 658 | 659 | text = self._form_paragraphs(text) 660 | 661 | return text 662 | 663 | def _pyshell_block_sub(self, match): 664 | lines = match.group(0).splitlines(0) 665 | _dedentlines(lines) 666 | indent = ' ' * self.tab_width 667 | s = ('\n' # separate from possible cuddled paragraph 668 | + indent + ('\n'+indent).join(lines) 669 | + '\n\n') 670 | return s 671 | 672 | def _prepare_pyshell_blocks(self, text): 673 | """Ensure that Python interactive shell sessions are put in 674 | code blocks -- even if not properly indented. 675 | """ 676 | if ">>>" not in text: 677 | return text 678 | 679 | less_than_tab = self.tab_width - 1 680 | _pyshell_block_re = re.compile(r""" 681 | ^([ ]{0,%d})>>>[ ].*\n # first line 682 | ^(\1.*\S+.*\n)* # any number of subsequent lines 683 | ^\n # ends with a blank line 684 | """ % less_than_tab, re.M | re.X) 685 | 686 | return _pyshell_block_re.sub(self._pyshell_block_sub, text) 687 | 688 | def _run_span_gamut(self, text): 689 | # These are all the transformations that occur *within* block-level 690 | # tags like paragraphs, headers, and list items. 691 | 692 | text = self._do_code_spans(text) 693 | 694 | text = self._escape_special_chars(text) 695 | 696 | # Process anchor and image tags. 697 | text = self._do_links(text) 698 | 699 | # Make links out of things like `` 700 | # Must come after _do_links(), because you can use < and > 701 | # delimiters in inline links like [this](). 702 | text = self._do_auto_links(text) 703 | 704 | if "link-patterns" in self.extras: 705 | text = self._do_link_patterns(text) 706 | 707 | text = self._encode_amps_and_angles(text) 708 | 709 | text = self._do_italics_and_bold(text) 710 | 711 | # Do hard breaks: 712 | text = re.sub(r" {2,}\n", " 724 | | 725 | # auto-link (e.g., ) 726 | <\w+[^>]*> 727 | | 728 | # comment 729 | | 730 | <\?.*?\?> # processing instruction 731 | ) 732 | """, re.X) 733 | 734 | def _escape_special_chars(self, text): 735 | # Python markdown note: the HTML tokenization here differs from 736 | # that in Markdown.pl, hence the behaviour for subtle cases can 737 | # differ (I believe the tokenizer here does a better job because 738 | # it isn't susceptible to unmatched '<' and '>' in HTML tags). 739 | # Note, however, that '>' is not allowed in an auto-link URL 740 | # here. 741 | escaped = [] 742 | is_html_markup = False 743 | for token in self._sorta_html_tokenize_re.split(text): 744 | if is_html_markup: 745 | # Within tags/HTML-comments/auto-links, encode * and _ 746 | # so they don't conflict with their use in Markdown for 747 | # italics and strong. We're replacing each such 748 | # character with its corresponding MD5 checksum value; 749 | # this is likely overkill, but it should prevent us from 750 | # colliding with the escape values by accident. 751 | escaped.append(token.replace('*', g_escape_table['*']) 752 | .replace('_', g_escape_table['_'])) 753 | else: 754 | escaped.append(self._encode_backslash_escapes(token)) 755 | is_html_markup = not is_html_markup 756 | return ''.join(escaped) 757 | 758 | def _hash_html_spans(self, text): 759 | # Used for safe_mode. 760 | 761 | def _is_auto_link(s): 762 | if ':' in s and self._auto_link_re.match(s): 763 | return True 764 | elif '@' in s and self._auto_email_link_re.match(s): 765 | return True 766 | return False 767 | 768 | tokens = [] 769 | is_html_markup = False 770 | for token in self._sorta_html_tokenize_re.split(text): 771 | if is_html_markup and not _is_auto_link(token): 772 | sanitized = self._sanitize_html(token) 773 | key = _hash_text(sanitized) 774 | self.html_spans[key] = sanitized 775 | tokens.append(key) 776 | else: 777 | tokens.append(token) 778 | is_html_markup = not is_html_markup 779 | return ''.join(tokens) 780 | 781 | def _unhash_html_spans(self, text): 782 | for key, sanitized in self.html_spans.items(): 783 | text = text.replace(key, sanitized) 784 | return text 785 | 786 | def _sanitize_html(self, s): 787 | if self.safe_mode == "replace": 788 | return self.html_removed_text 789 | elif self.safe_mode == "escape": 790 | replacements = [ 791 | ('&', '&'), 792 | ('<', '<'), 793 | ('>', '>'), 794 | ] 795 | for before, after in replacements: 796 | s = s.replace(before, after) 797 | return s 798 | else: 799 | raise MarkdownError("invalid value for 'safe_mode': %r (must be " 800 | "'escape' or 'replace')" % self.safe_mode) 801 | 802 | _tail_of_inline_link_re = re.compile(r''' 803 | # Match tail of: [text](/url/) or [text](/url/ "title") 804 | \( # literal paren 805 | [ \t]* 806 | (?P # \1 807 | <.*?> 808 | | 809 | .*? 810 | ) 811 | [ \t]* 812 | ( # \2 813 | (['"]) # quote char = \3 814 | (?P.*?) 815 | \3 # matching quote 816 | )? # title is optional 817 | \) 818 | ''', re.X | re.S) 819 | _tail_of_reference_link_re = re.compile(r''' 820 | # Match tail of: [text][id] 821 | [ ]? # one optional space 822 | (?:\n[ ]*)? # one optional newline followed by spaces 823 | \[ 824 | (?P<id>.*?) 825 | \] 826 | ''', re.X | re.S) 827 | 828 | def _do_links(self, text): 829 | """Turn Markdown link shortcuts into XHTML <a> and <img> tags. 830 | 831 | This is a combination of Markdown.pl's _DoAnchors() and 832 | _DoImages(). They are done together because that simplified the 833 | approach. It was necessary to use a different approach than 834 | Markdown.pl because of the lack of atomic matching support in 835 | Python's regex engine used in $g_nested_brackets. 836 | """ 837 | MAX_LINK_TEXT_SENTINEL = 3000 # markdown2 issue 24 838 | 839 | # `anchor_allowed_pos` is used to support img links inside 840 | # anchors, but not anchors inside anchors. An anchor's start 841 | # pos must be `>= anchor_allowed_pos`. 842 | anchor_allowed_pos = 0 843 | 844 | curr_pos = 0 845 | while True: # Handle the next link. 846 | # The next '[' is the start of: 847 | # - an inline anchor: [text](url "title") 848 | # - a reference anchor: [text][id] 849 | # - an inline img: ![text](url "title") 850 | # - a reference img: ![text][id] 851 | # - a footnote ref: [^id] 852 | # (Only if 'footnotes' extra enabled) 853 | # - a footnote defn: [^id]: ... 854 | # (Only if 'footnotes' extra enabled) These have already 855 | # been stripped in _strip_footnote_definitions() so no 856 | # need to watch for them. 857 | # - a link definition: [id]: url "title" 858 | # These have already been stripped in 859 | # _strip_link_definitions() so no need to watch for them. 860 | # - not markup: [...anything else... 861 | try: 862 | start_idx = text.index('[', curr_pos) 863 | except ValueError: 864 | break 865 | text_length = len(text) 866 | 867 | # Find the matching closing ']'. 868 | # Markdown.pl allows *matching* brackets in link text so we 869 | # will here too. Markdown.pl *doesn't* currently allow 870 | # matching brackets in img alt text -- we'll differ in that 871 | # regard. 872 | bracket_depth = 0 873 | for p in range(start_idx+1, min(start_idx+MAX_LINK_TEXT_SENTINEL, 874 | text_length)): 875 | ch = text[p] 876 | if ch == ']': 877 | bracket_depth -= 1 878 | if bracket_depth < 0: 879 | break 880 | elif ch == '[': 881 | bracket_depth += 1 882 | else: 883 | # Closing bracket not found within sentinel length. 884 | # This isn't markup. 885 | curr_pos = start_idx + 1 886 | continue 887 | link_text = text[start_idx+1:p] 888 | 889 | # Possibly a footnote ref? 890 | if "footnotes" in self.extras and link_text.startswith("^"): 891 | normed_id = re.sub(r'\W', '-', link_text[1:]) 892 | if normed_id in self.footnotes: 893 | self.footnote_ids.append(normed_id) 894 | result = '<sup class="footnote-ref" id="fnref-%s">' \ 895 | '<a href="#fn-%s">%s</a></sup>' \ 896 | % (normed_id, normed_id, len(self.footnote_ids)) 897 | text = text[:start_idx] + result + text[p+1:] 898 | else: 899 | # This id isn't defined, leave the markup alone. 900 | curr_pos = p+1 901 | continue 902 | 903 | # Now determine what this is by the remainder. 904 | p += 1 905 | if p == text_length: 906 | return text 907 | 908 | # Inline anchor or img? 909 | if text[p] == '(': # attempt at perf improvement 910 | match = self._tail_of_inline_link_re.match(text, p) 911 | if match: 912 | # Handle an inline anchor or img. 913 | is_img = start_idx > 0 and text[start_idx-1] == "!" 914 | if is_img: 915 | start_idx -= 1 916 | 917 | url, title = match.group("url"), match.group("title") 918 | if url and url[0] == '<': 919 | url = url[1:-1] # '<url>' -> 'url' 920 | # We've got to encode these to avoid conflicting 921 | # with italics/bold. 922 | url = url.replace('*', g_escape_table['*']) \ 923 | .replace('_', g_escape_table['_']) 924 | if title: 925 | title_str = ' title="%s"' \ 926 | % title.replace('*', g_escape_table['*']) \ 927 | .replace('_', g_escape_table['_']) \ 928 | .replace('"', '"') 929 | else: 930 | title_str = '' 931 | if is_img: 932 | result = '<img src="%s" alt="%s"%s%s' \ 933 | % (url, link_text.replace('"', '"'), 934 | title_str, self.empty_element_suffix) 935 | curr_pos = start_idx + len(result) 936 | text = text[:start_idx] + result + text[match.end():] 937 | elif start_idx >= anchor_allowed_pos: 938 | result_head = '<a href="%s"%s>' % (url, title_str) 939 | result = '%s%s</a>' % (result_head, link_text) 940 | # <img> allowed from curr_pos on, <a> from 941 | # anchor_allowed_pos on. 942 | curr_pos = start_idx + len(result_head) 943 | anchor_allowed_pos = start_idx + len(result) 944 | text = text[:start_idx] + result + text[match.end():] 945 | else: 946 | # Anchor not allowed here. 947 | curr_pos = start_idx + 1 948 | continue 949 | 950 | # Reference anchor or img? 951 | else: 952 | match = self._tail_of_reference_link_re.match(text, p) 953 | if match: 954 | # Handle a reference-style anchor or img. 955 | is_img = start_idx > 0 and text[start_idx-1] == "!" 956 | if is_img: 957 | start_idx -= 1 958 | link_id = match.group("id").lower() 959 | if not link_id: 960 | link_id = link_text.lower() # for links like [this][] 961 | if link_id in self.urls: 962 | url = self.urls[link_id] 963 | # We've got to encode these to avoid conflicting 964 | # with italics/bold. 965 | url = url.replace('*', g_escape_table['*']) \ 966 | .replace('_', g_escape_table['_']) 967 | title = self.titles.get(link_id) 968 | if title: 969 | title = title.replace('*', g_escape_table['*']) \ 970 | .replace('_', g_escape_table['_']) 971 | title_str = ' title="%s"' % title 972 | else: 973 | title_str = '' 974 | if is_img: 975 | result = '<img src="%s" alt="%s"%s%s' \ 976 | % (url, link_text.replace('"', '"'), 977 | title_str, self.empty_element_suffix) 978 | curr_pos = start_idx + len(result) 979 | text = text[:start_idx] + result + text[match.end():] 980 | elif start_idx >= anchor_allowed_pos: 981 | result = '<a href="%s"%s>%s</a>' \ 982 | % (url, title_str, link_text) 983 | result_head = '<a href="%s"%s>' % (url, title_str) 984 | result = '%s%s</a>' % (result_head, link_text) 985 | # <img> allowed from curr_pos on, <a> from 986 | # anchor_allowed_pos on. 987 | curr_pos = start_idx + len(result_head) 988 | anchor_allowed_pos = start_idx + len(result) 989 | text = text[:start_idx] + result + text[match.end():] 990 | else: 991 | # Anchor not allowed here. 992 | curr_pos = start_idx + 1 993 | else: 994 | # This id isn't defined, leave the markup alone. 995 | curr_pos = match.end() 996 | continue 997 | 998 | # Otherwise, it isn't markup. 999 | curr_pos = start_idx + 1 1000 | 1001 | return text 1002 | 1003 | 1004 | _setext_h_re = re.compile(r'^(.+)[ \t]*\n(=+|-+)[ \t]*\n+', re.M) 1005 | def _setext_h_sub(self, match): 1006 | n = {"=": 1, "-": 2}[match.group(2)[0]] 1007 | demote_headers = self.extras.get("demote-headers") 1008 | if demote_headers: 1009 | n = min(n + demote_headers, 6) 1010 | return "<h%d>%s</h%d>\n\n" \ 1011 | % (n, self._run_span_gamut(match.group(1)), n) 1012 | 1013 | _atx_h_re = re.compile(r''' 1014 | ^(\#{1,6}) # \1 = string of #'s 1015 | [ \t]* 1016 | (.+?) # \2 = Header text 1017 | [ \t]* 1018 | (?<!\\) # ensure not an escaped trailing '#' 1019 | \#* # optional closing #'s (not counted) 1020 | \n+ 1021 | ''', re.X | re.M) 1022 | def _atx_h_sub(self, match): 1023 | n = len(match.group(1)) 1024 | demote_headers = self.extras.get("demote-headers") 1025 | if demote_headers: 1026 | n = min(n + demote_headers, 6) 1027 | return "<h%d>%s</h%d>\n\n" \ 1028 | % (n, self._run_span_gamut(match.group(2)), n) 1029 | 1030 | def _do_headers(self, text): 1031 | # Setext-style headers: 1032 | # Header 1 1033 | # ======== 1034 | # 1035 | # Header 2 1036 | # -------- 1037 | text = self._setext_h_re.sub(self._setext_h_sub, text) 1038 | 1039 | # atx-style headers: 1040 | # # Header 1 1041 | # ## Header 2 1042 | # ## Header 2 with closing hashes ## 1043 | # ... 1044 | # ###### Header 6 1045 | text = self._atx_h_re.sub(self._atx_h_sub, text) 1046 | 1047 | return text 1048 | 1049 | 1050 | _marker_ul_chars = '*+-' 1051 | _marker_any = r'(?:[%s]|\d+\.)' % _marker_ul_chars 1052 | _marker_ul = '(?:[%s])' % _marker_ul_chars 1053 | _marker_ol = r'(?:\d+\.)' 1054 | 1055 | def _list_sub(self, match): 1056 | lst = match.group(1) 1057 | lst_type = match.group(3) in self._marker_ul_chars and "ul" or "ol" 1058 | result = self._process_list_items(lst) 1059 | if self.list_level: 1060 | return "<%s>\n%s</%s>\n" % (lst_type, result, lst_type) 1061 | else: 1062 | return "<%s>\n%s</%s>\n\n" % (lst_type, result, lst_type) 1063 | 1064 | def _do_lists(self, text): 1065 | # Form HTML ordered (numbered) and unordered (bulleted) lists. 1066 | 1067 | for marker_pat in (self._marker_ul, self._marker_ol): 1068 | # Re-usable pattern to match any entire ul or ol list: 1069 | less_than_tab = self.tab_width - 1 1070 | whole_list = r''' 1071 | ( # \1 = whole list 1072 | ( # \2 1073 | [ ]{0,%d} 1074 | (%s) # \3 = first list item marker 1075 | [ \t]+ 1076 | ) 1077 | (?:.+?) 1078 | ( # \4 1079 | \Z 1080 | | 1081 | \n{2,} 1082 | (?=\S) 1083 | (?! # Negative lookahead for another list item marker 1084 | [ \t]* 1085 | %s[ \t]+ 1086 | ) 1087 | ) 1088 | ) 1089 | ''' % (less_than_tab, marker_pat, marker_pat) 1090 | 1091 | # We use a different prefix before nested lists than top-level lists. 1092 | # See extended comment in _process_list_items(). 1093 | # 1094 | # Note: There's a bit of duplication here. My original implementation 1095 | # created a scalar regex pattern as the conditional result of the test on 1096 | # $g_list_level, and then only ran the $text =~ s{...}{...}egmx 1097 | # substitution once, using the scalar as the pattern. This worked, 1098 | # everywhere except when running under MT on my hosting account at Pair 1099 | # Networks. There, this caused all rebuilds to be killed by the reaper (or 1100 | # perhaps they crashed, but that seems incredibly unlikely given that the 1101 | # same script on the same server ran fine *except* under MT. I've spent 1102 | # more time trying to figure out why this is happening than I'd like to 1103 | # admit. My only guess, backed up by the fact that this workaround works, 1104 | # is that Perl optimizes the substition when it can figure out that the 1105 | # pattern will never change, and when this optimization isn't on, we run 1106 | # afoul of the reaper. Thus, the slightly redundant code to that uses two 1107 | # static s/// patterns rather than one conditional pattern. 1108 | 1109 | if self.list_level: 1110 | sub_list_re = re.compile("^"+whole_list, re.X | re.M | re.S) 1111 | text = sub_list_re.sub(self._list_sub, text) 1112 | else: 1113 | list_re = re.compile(r"(?:(?<=\n\n)|\A\n?)"+whole_list, 1114 | re.X | re.M | re.S) 1115 | text = list_re.sub(self._list_sub, text) 1116 | 1117 | return text 1118 | 1119 | _list_item_re = re.compile(r''' 1120 | (\n)? # leading line = \1 1121 | (^[ \t]*) # leading whitespace = \2 1122 | (%s) [ \t]+ # list marker = \3 1123 | ((?:.+?) # list item text = \4 1124 | (\n{1,2})) # eols = \5 1125 | (?= \n* (\Z | \2 (%s) [ \t]+)) 1126 | ''' % (_marker_any, _marker_any), 1127 | re.M | re.X | re.S) 1128 | 1129 | _last_li_endswith_two_eols = False 1130 | def _list_item_sub(self, match): 1131 | item = match.group(4) 1132 | leading_line = match.group(1) 1133 | leading_space = match.group(2) 1134 | if leading_line or "\n\n" in item or self._last_li_endswith_two_eols: 1135 | item = self._run_block_gamut(self._outdent(item)) 1136 | else: 1137 | # Recursion for sub-lists: 1138 | item = self._do_lists(self._outdent(item)) 1139 | if item.endswith('\n'): 1140 | item = item[:-1] 1141 | item = self._run_span_gamut(item) 1142 | self._last_li_endswith_two_eols = (len(match.group(5)) == 2) 1143 | return "<li>%s</li>\n" % item 1144 | 1145 | def _process_list_items(self, list_str): 1146 | # Process the contents of a single ordered or unordered list, 1147 | # splitting it into individual list items. 1148 | 1149 | # The $g_list_level global keeps track of when we're inside a list. 1150 | # Each time we enter a list, we increment it; when we leave a list, 1151 | # we decrement. If it's zero, we're not in a list anymore. 1152 | # 1153 | # We do this because when we're not inside a list, we want to treat 1154 | # something like this: 1155 | # 1156 | # I recommend upgrading to version 1157 | # 8. Oops, now this line is treated 1158 | # as a sub-list. 1159 | # 1160 | # As a single paragraph, despite the fact that the second line starts 1161 | # with a digit-period-space sequence. 1162 | # 1163 | # Whereas when we're inside a list (or sub-list), that line will be 1164 | # treated as the start of a sub-list. What a kludge, huh? This is 1165 | # an aspect of Markdown's syntax that's hard to parse perfectly 1166 | # without resorting to mind-reading. Perhaps the solution is to 1167 | # change the syntax rules such that sub-lists must start with a 1168 | # starting cardinal number; e.g. "1." or "a.". 1169 | self.list_level += 1 1170 | self._last_li_endswith_two_eols = False 1171 | list_str = list_str.rstrip('\n') + '\n' 1172 | list_str = self._list_item_re.sub(self._list_item_sub, list_str) 1173 | self.list_level -= 1 1174 | return list_str 1175 | 1176 | def _get_pygments_lexer(self, lexer_name): 1177 | try: 1178 | from pygments import lexers, util 1179 | except ImportError: 1180 | return None 1181 | try: 1182 | return lexers.get_lexer_by_name(lexer_name) 1183 | except util.ClassNotFound: 1184 | return None 1185 | 1186 | def _color_with_pygments(self, codeblock, lexer, **formatter_opts): 1187 | import pygments 1188 | import pygments.formatters 1189 | 1190 | class HtmlCodeFormatter(pygments.formatters.HtmlFormatter): 1191 | def _wrap_code(self, inner): 1192 | """A function for use in a Pygments Formatter which 1193 | wraps in <code> tags. 1194 | """ 1195 | yield 0, "<code>" 1196 | for tup in inner: 1197 | yield tup 1198 | yield 0, "</code>" 1199 | 1200 | def wrap(self, source, outfile): 1201 | """Return the source with a code, pre, and div.""" 1202 | return self._wrap_div(self._wrap_pre(self._wrap_code(source))) 1203 | 1204 | formatter = HtmlCodeFormatter(cssclass="codehilite", **formatter_opts) 1205 | return pygments.highlight(codeblock, lexer, formatter) 1206 | 1207 | def _code_block_sub(self, match): 1208 | codeblock = match.group(1) 1209 | codeblock = self._outdent(codeblock) 1210 | codeblock = self._detab(codeblock) 1211 | codeblock = codeblock.lstrip('\n') # trim leading newlines 1212 | codeblock = codeblock.rstrip() # trim trailing whitespace 1213 | 1214 | if "code-color" in self.extras and codeblock.startswith(":::"): 1215 | lexer_name, rest = codeblock.split('\n', 1) 1216 | lexer_name = lexer_name[3:].strip() 1217 | lexer = self._get_pygments_lexer(lexer_name) 1218 | codeblock = rest.lstrip("\n") # Remove lexer declaration line. 1219 | if lexer: 1220 | formatter_opts = self.extras['code-color'] or {} 1221 | colored = self._color_with_pygments(codeblock, lexer, 1222 | **formatter_opts) 1223 | return "\n\n%s\n\n" % colored 1224 | 1225 | codeblock = self._encode_code(codeblock) 1226 | return "\n\n<pre><code>%s\n</code></pre>\n\n" % codeblock 1227 | 1228 | def _do_code_blocks(self, text): 1229 | """Process Markdown `<pre><code>` blocks.""" 1230 | code_block_re = re.compile(r''' 1231 | (?:\n\n|\A) 1232 | ( # $1 = the code block -- one or more lines, starting with a space/tab 1233 | (?: 1234 | (?:[ ]{%d} | \t) # Lines must start with a tab or a tab-width of spaces 1235 | .*\n+ 1236 | )+ 1237 | ) 1238 | ((?=^[ ]{0,%d}\S)|\Z) # Lookahead for non-space at line-start, or end of doc 1239 | ''' % (self.tab_width, self.tab_width), 1240 | re.M | re.X) 1241 | 1242 | return code_block_re.sub(self._code_block_sub, text) 1243 | 1244 | 1245 | # Rules for a code span: 1246 | # - backslash escapes are not interpreted in a code span 1247 | # - to include one or or a run of more backticks the delimiters must 1248 | # be a longer run of backticks 1249 | # - cannot start or end a code span with a backtick; pad with a 1250 | # space and that space will be removed in the emitted HTML 1251 | # See `test/tm-cases/escapes.text` for a number of edge-case 1252 | # examples. 1253 | _code_span_re = re.compile(r''' 1254 | (?<!\\) 1255 | (`+) # \1 = Opening run of ` 1256 | (?!`) # See Note A test/tm-cases/escapes.text 1257 | (.+?) # \2 = The code block 1258 | (?<!`) 1259 | \1 # Matching closer 1260 | (?!`) 1261 | ''', re.X | re.S) 1262 | 1263 | def _code_span_sub(self, match): 1264 | c = match.group(2).strip(" \t") 1265 | c = self._encode_code(c) 1266 | return "<code>%s</code>" % c 1267 | 1268 | def _do_code_spans(self, text): 1269 | # * Backtick quotes are used for <code></code> spans. 1270 | # 1271 | # * You can use multiple backticks as the delimiters if you want to 1272 | # include literal backticks in the code span. So, this input: 1273 | # 1274 | # Just type ``foo `bar` baz`` at the prompt. 1275 | # 1276 | # Will translate to: 1277 | # 1278 | # <p>Just type <code>foo `bar` baz</code> at the prompt.</p> 1279 | # 1280 | # There's no arbitrary limit to the number of backticks you 1281 | # can use as delimters. If you need three consecutive backticks 1282 | # in your code, use four for delimiters, etc. 1283 | # 1284 | # * You can use spaces to get literal backticks at the edges: 1285 | # 1286 | # ... type `` `bar` `` ... 1287 | # 1288 | # Turns to: 1289 | # 1290 | # ... type <code>`bar`</code> ... 1291 | return self._code_span_re.sub(self._code_span_sub, text) 1292 | 1293 | def _encode_code(self, text): 1294 | """Encode/escape certain characters inside Markdown code runs. 1295 | The point is that in code, these characters are literals, 1296 | and lose their special Markdown meanings. 1297 | """ 1298 | replacements = [ 1299 | # Encode all ampersands; HTML entities are not 1300 | # entities within a Markdown code span. 1301 | ('&', '&'), 1302 | # Do the angle bracket song and dance: 1303 | ('<', '<'), 1304 | ('>', '>'), 1305 | # Now, escape characters that are magic in Markdown: 1306 | ('*', g_escape_table['*']), 1307 | ('_', g_escape_table['_']), 1308 | ('{', g_escape_table['{']), 1309 | ('}', g_escape_table['}']), 1310 | ('[', g_escape_table['[']), 1311 | (']', g_escape_table[']']), 1312 | ('\\', g_escape_table['\\']), 1313 | ] 1314 | for before, after in replacements: 1315 | text = text.replace(before, after) 1316 | return text 1317 | 1318 | _strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S) 1319 | _em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S) 1320 | _code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S) 1321 | _code_friendly_em_re = re.compile(r"\*(?=\S)(.+?)(?<=\S)\*", re.S) 1322 | def _do_italics_and_bold(self, text): 1323 | # <strong> must go first: 1324 | if "code-friendly" in self.extras: 1325 | text = self._code_friendly_strong_re.sub(r"<strong>\1</strong>", text) 1326 | text = self._code_friendly_em_re.sub(r"<em>\1</em>", text) 1327 | else: 1328 | text = self._strong_re.sub(r"<strong>\2</strong>", text) 1329 | text = self._em_re.sub(r"<em>\2</em>", text) 1330 | return text 1331 | 1332 | 1333 | _block_quote_re = re.compile(r''' 1334 | ( # Wrap whole match in \1 1335 | ( 1336 | ^[ \t]*>[ \t]? # '>' at the start of a line 1337 | .+\n # rest of the first line 1338 | (.+\n)* # subsequent consecutive lines 1339 | \n* # blanks 1340 | )+ 1341 | ) 1342 | ''', re.M | re.X) 1343 | _bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M); 1344 | 1345 | _html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S) 1346 | def _dedent_two_spaces_sub(self, match): 1347 | return re.sub(r'(?m)^ ', '', match.group(1)) 1348 | 1349 | def _block_quote_sub(self, match): 1350 | bq = match.group(1) 1351 | bq = self._bq_one_level_re.sub('', bq) # trim one level of quoting 1352 | bq = self._ws_only_line_re.sub('', bq) # trim whitespace-only lines 1353 | bq = self._run_block_gamut(bq) # recurse 1354 | 1355 | bq = re.sub('(?m)^', ' ', bq) 1356 | # These leading spaces screw with <pre> content, so we need to fix that: 1357 | bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq) 1358 | 1359 | return "<blockquote>\n%s\n</blockquote>\n\n" % bq 1360 | 1361 | def _do_block_quotes(self, text): 1362 | if '>' not in text: 1363 | return text 1364 | return self._block_quote_re.sub(self._block_quote_sub, text) 1365 | 1366 | def _form_paragraphs(self, text): 1367 | # Strip leading and trailing lines: 1368 | text = text.strip('\n') 1369 | 1370 | # Wrap <p> tags. 1371 | grafs = re.split(r"\n{2,}", text) 1372 | for i, graf in enumerate(grafs): 1373 | if graf in self.html_blocks: 1374 | # Unhashify HTML blocks 1375 | grafs[i] = self.html_blocks[graf] 1376 | else: 1377 | # Wrap <p> tags. 1378 | graf = self._run_span_gamut(graf) 1379 | grafs[i] = "<p>" + graf.lstrip(" \t") + "</p>" 1380 | 1381 | return "\n\n".join(grafs) 1382 | 1383 | def _add_footnotes(self, text): 1384 | if self.footnotes: 1385 | footer = [ 1386 | '<div class="footnotes">', 1387 | '<hr' + self.empty_element_suffix, 1388 | '<ol>', 1389 | ] 1390 | for i, id in enumerate(self.footnote_ids): 1391 | if i != 0: 1392 | footer.append('') 1393 | footer.append('<li id="fn-%s">' % id) 1394 | footer.append(self._run_block_gamut(self.footnotes[id])) 1395 | backlink = ('<a href="#fnref-%s" ' 1396 | 'class="footnoteBackLink" ' 1397 | 'title="Jump back to footnote %d in the text.">' 1398 | '↩</a>' % (id, i+1)) 1399 | if footer[-1].endswith("</p>"): 1400 | footer[-1] = footer[-1][:-len("</p>")] \ 1401 | + ' ' + backlink + "</p>" 1402 | else: 1403 | footer.append("\n<p>%s</p>" % backlink) 1404 | footer.append('</li>') 1405 | footer.append('</ol>') 1406 | footer.append('</div>') 1407 | return text + '\n\n' + '\n'.join(footer) 1408 | else: 1409 | return text 1410 | 1411 | # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: 1412 | # http://bumppo.net/projects/amputator/ 1413 | _ampersand_re = re.compile(r'&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)') 1414 | _naked_lt_re = re.compile(r'<(?![a-z/?\$!])', re.I) 1415 | _naked_gt_re = re.compile(r'''(?<![a-z?!/'"-])>''', re.I) 1416 | 1417 | def _encode_amps_and_angles(self, text): 1418 | # Smart processing for ampersands and angle brackets that need 1419 | # to be encoded. 1420 | text = self._ampersand_re.sub('&', text) 1421 | 1422 | # Encode naked <'s 1423 | text = self._naked_lt_re.sub('<', text) 1424 | 1425 | # Encode naked >'s 1426 | # Note: Other markdown implementations (e.g. Markdown.pl, PHP 1427 | # Markdown) don't do this. 1428 | text = self._naked_gt_re.sub('>', text) 1429 | return text 1430 | 1431 | def _encode_backslash_escapes(self, text): 1432 | for ch, escape in g_escape_table.items(): 1433 | text = text.replace("\\"+ch, escape) 1434 | return text 1435 | 1436 | _auto_link_re = re.compile(r'<((https?|ftp):[^\'">\s]+)>', re.I) 1437 | def _auto_link_sub(self, match): 1438 | g1 = match.group(1) 1439 | return '<a href="%s">%s</a>' % (g1, g1) 1440 | 1441 | _auto_email_link_re = re.compile(r""" 1442 | < 1443 | (?:mailto:)? 1444 | ( 1445 | [-.\w]+ 1446 | \@ 1447 | [-\w]+(\.[-\w]+)*\.[a-z]+ 1448 | ) 1449 | > 1450 | """, re.I | re.X | re.U) 1451 | def _auto_email_link_sub(self, match): 1452 | return self._encode_email_address( 1453 | self._unescape_special_chars(match.group(1))) 1454 | 1455 | def _do_auto_links(self, text): 1456 | text = self._auto_link_re.sub(self._auto_link_sub, text) 1457 | text = self._auto_email_link_re.sub(self._auto_email_link_sub, text) 1458 | return text 1459 | 1460 | def _encode_email_address(self, addr): 1461 | # Input: an email address, e.g. "foo@example.com" 1462 | # 1463 | # Output: the email address as a mailto link, with each character 1464 | # of the address encoded as either a decimal or hex entity, in 1465 | # the hopes of foiling most address harvesting spam bots. E.g.: 1466 | # 1467 | # <a href="mailto:foo@e 1468 | # xample.com">foo 1469 | # @example.com</a> 1470 | # 1471 | # Based on a filter by Matthew Wickline, posted to the BBEdit-Talk 1472 | # mailing list: <http://tinyurl.com/yu7ue> 1473 | chars = [_xml_encode_email_char_at_random(ch) 1474 | for ch in "mailto:" + addr] 1475 | # Strip the mailto: from the visible part. 1476 | addr = '<a href="%s">%s</a>' \ 1477 | % (''.join(chars), ''.join(chars[7:])) 1478 | return addr 1479 | 1480 | def _do_link_patterns(self, text): 1481 | """Caveat emptor: there isn't much guarding against link 1482 | patterns being formed inside other standard Markdown links, e.g. 1483 | inside a [link def][like this]. 1484 | 1485 | Dev Notes: *Could* consider prefixing regexes with a negative 1486 | lookbehind assertion to attempt to guard against this. 1487 | """ 1488 | link_from_hash = {} 1489 | for regex, repl in self.link_patterns: 1490 | replacements = [] 1491 | for match in regex.finditer(text): 1492 | if hasattr(repl, "__call__"): 1493 | href = repl(match) 1494 | else: 1495 | href = match.expand(repl) 1496 | replacements.append((match.span(), href)) 1497 | for (start, end), href in reversed(replacements): 1498 | escaped_href = ( 1499 | href.replace('"', '"') # b/c of attr quote 1500 | # To avoid markdown <em> and <strong>: 1501 | .replace('*', g_escape_table['*']) 1502 | .replace('_', g_escape_table['_'])) 1503 | link = '<a href="%s">%s</a>' % (escaped_href, text[start:end]) 1504 | hash = md5(link).hexdigest() 1505 | link_from_hash[hash] = link 1506 | text = text[:start] + hash + text[end:] 1507 | for hash, link in link_from_hash.items(): 1508 | text = text.replace(hash, link) 1509 | return text 1510 | 1511 | def _unescape_special_chars(self, text): 1512 | # Swap back in all the special characters we've hidden. 1513 | for ch, hash in g_escape_table.items(): 1514 | text = text.replace(hash, ch) 1515 | return text 1516 | 1517 | def _outdent(self, text): 1518 | # Remove one level of line-leading tabs or spaces 1519 | return self._outdent_re.sub('', text) 1520 | 1521 | 1522 | class MarkdownWithExtras(Markdown): 1523 | """A markdowner class that enables most extras: 1524 | 1525 | - footnotes 1526 | - code-color (only has effect if 'pygments' Python module on path) 1527 | 1528 | These are not included: 1529 | - pyshell (specific to Python-related documenting) 1530 | - code-friendly (because it *disables* part of the syntax) 1531 | - link-patterns (because you need to specify some actual 1532 | link-patterns anyway) 1533 | """ 1534 | extras = ["footnotes", "code-color"] 1535 | 1536 | 1537 | #---- internal support functions 1538 | 1539 | # From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549 1540 | def _curry(*args, **kwargs): 1541 | function, args = args[0], args[1:] 1542 | def result(*rest, **kwrest): 1543 | combined = kwargs.copy() 1544 | combined.update(kwrest) 1545 | return function(*args + rest, **combined) 1546 | return result 1547 | 1548 | # Recipe: regex_from_encoded_pattern (1.0) 1549 | def _regex_from_encoded_pattern(s): 1550 | """'foo' -> re.compile(re.escape('foo')) 1551 | '/foo/' -> re.compile('foo') 1552 | '/foo/i' -> re.compile('foo', re.I) 1553 | """ 1554 | if s.startswith('/') and s.rfind('/') != 0: 1555 | # Parse it: /PATTERN/FLAGS 1556 | idx = s.rfind('/') 1557 | pattern, flags_str = s[1:idx], s[idx+1:] 1558 | flag_from_char = { 1559 | "i": re.IGNORECASE, 1560 | "l": re.LOCALE, 1561 | "s": re.DOTALL, 1562 | "m": re.MULTILINE, 1563 | "u": re.UNICODE, 1564 | } 1565 | flags = 0 1566 | for char in flags_str: 1567 | try: 1568 | flags |= flag_from_char[char] 1569 | except KeyError: 1570 | raise ValueError("unsupported regex flag: '%s' in '%s' " 1571 | "(must be one of '%s')" 1572 | % (char, s, ''.join(flag_from_char.keys()))) 1573 | return re.compile(s[1:idx], flags) 1574 | else: # not an encoded regex 1575 | return re.compile(re.escape(s)) 1576 | 1577 | # Recipe: dedent (0.1.2) 1578 | def _dedentlines(lines, tabsize=8, skip_first_line=False): 1579 | """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines 1580 | 1581 | "lines" is a list of lines to dedent. 1582 | "tabsize" is the tab width to use for indent width calculations. 1583 | "skip_first_line" is a boolean indicating if the first line should 1584 | be skipped for calculating the indent width and for dedenting. 1585 | This is sometimes useful for docstrings and similar. 1586 | 1587 | Same as dedent() except operates on a sequence of lines. Note: the 1588 | lines list is modified **in-place**. 1589 | """ 1590 | DEBUG = False 1591 | if DEBUG: 1592 | print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\ 1593 | % (tabsize, skip_first_line) 1594 | indents = [] 1595 | margin = None 1596 | for i, line in enumerate(lines): 1597 | if i == 0 and skip_first_line: continue 1598 | indent = 0 1599 | for ch in line: 1600 | if ch == ' ': 1601 | indent += 1 1602 | elif ch == '\t': 1603 | indent += tabsize - (indent % tabsize) 1604 | elif ch in '\r\n': 1605 | continue # skip all-whitespace lines 1606 | else: 1607 | break 1608 | else: 1609 | continue # skip all-whitespace lines 1610 | if DEBUG: print "dedent: indent=%d: %r" % (indent, line) 1611 | if margin is None: 1612 | margin = indent 1613 | else: 1614 | margin = min(margin, indent) 1615 | if DEBUG: print "dedent: margin=%r" % margin 1616 | 1617 | if margin is not None and margin > 0: 1618 | for i, line in enumerate(lines): 1619 | if i == 0 and skip_first_line: continue 1620 | removed = 0 1621 | for j, ch in enumerate(line): 1622 | if ch == ' ': 1623 | removed += 1 1624 | elif ch == '\t': 1625 | removed += tabsize - (removed % tabsize) 1626 | elif ch in '\r\n': 1627 | if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line 1628 | lines[i] = lines[i][j:] 1629 | break 1630 | else: 1631 | raise ValueError("unexpected non-whitespace char %r in " 1632 | "line %r while removing %d-space margin" 1633 | % (ch, line, margin)) 1634 | if DEBUG: 1635 | print "dedent: %r: %r -> removed %d/%d"\ 1636 | % (line, ch, removed, margin) 1637 | if removed == margin: 1638 | lines[i] = lines[i][j+1:] 1639 | break 1640 | elif removed > margin: 1641 | lines[i] = ' '*(removed-margin) + lines[i][j+1:] 1642 | break 1643 | else: 1644 | if removed: 1645 | lines[i] = lines[i][removed:] 1646 | return lines 1647 | 1648 | def _dedent(text, tabsize=8, skip_first_line=False): 1649 | """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text 1650 | 1651 | "text" is the text to dedent. 1652 | "tabsize" is the tab width to use for indent width calculations. 1653 | "skip_first_line" is a boolean indicating if the first line should 1654 | be skipped for calculating the indent width and for dedenting. 1655 | This is sometimes useful for docstrings and similar. 1656 | 1657 | textwrap.dedent(s), but don't expand tabs to spaces 1658 | """ 1659 | lines = text.splitlines(1) 1660 | _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line) 1661 | return ''.join(lines) 1662 | 1663 | 1664 | class _memoized(object): 1665 | """Decorator that caches a function's return value each time it is called. 1666 | If called later with the same arguments, the cached value is returned, and 1667 | not re-evaluated. 1668 | 1669 | http://wiki.python.org/moin/PythonDecoratorLibrary 1670 | """ 1671 | def __init__(self, func): 1672 | self.func = func 1673 | self.cache = {} 1674 | def __call__(self, *args): 1675 | try: 1676 | return self.cache[args] 1677 | except KeyError: 1678 | self.cache[args] = value = self.func(*args) 1679 | return value 1680 | except TypeError: 1681 | # uncachable -- for instance, passing a list as an argument. 1682 | # Better to not cache than to blow up entirely. 1683 | return self.func(*args) 1684 | def __repr__(self): 1685 | """Return the function's docstring.""" 1686 | return self.func.__doc__ 1687 | 1688 | 1689 | def _xml_oneliner_re_from_tab_width(tab_width): 1690 | """Standalone XML processing instruction regex.""" 1691 | return re.compile(r""" 1692 | (?: 1693 | (?<=\n\n) # Starting after a blank line 1694 | | # or 1695 | \A\n? # the beginning of the doc 1696 | ) 1697 | ( # save in $1 1698 | [ ]{0,%d} 1699 | (?: 1700 | <\?\w+\b\s+.*?\?> # XML processing instruction 1701 | | 1702 | <\w+:\w+\b\s+.*?/> # namespaced single tag 1703 | ) 1704 | [ \t]* 1705 | (?=\n{2,}|\Z) # followed by a blank line or end of document 1706 | ) 1707 | """ % (tab_width - 1), re.X) 1708 | _xml_oneliner_re_from_tab_width = _memoized(_xml_oneliner_re_from_tab_width) 1709 | 1710 | def _hr_tag_re_from_tab_width(tab_width): 1711 | return re.compile(r""" 1712 | (?: 1713 | (?<=\n\n) # Starting after a blank line 1714 | | # or 1715 | \A\n? # the beginning of the doc 1716 | ) 1717 | ( # save in \1 1718 | [ ]{0,%d} 1719 | <(hr) # start tag = \2 1720 | \b # word break 1721 | ([^<>])*? # 1722 | /?> # the matching end tag 1723 | [ \t]* 1724 | (?=\n{2,}|\Z) # followed by a blank line or end of document 1725 | ) 1726 | """ % (tab_width - 1), re.X) 1727 | _hr_tag_re_from_tab_width = _memoized(_hr_tag_re_from_tab_width) 1728 | 1729 | 1730 | def _xml_encode_email_char_at_random(ch): 1731 | r = random() 1732 | # Roughly 10% raw, 45% hex, 45% dec. 1733 | # '@' *must* be encoded. I [John Gruber] insist. 1734 | # Issue 26: '_' must be encoded. 1735 | if r > 0.9 and ch not in "@_": 1736 | return ch 1737 | elif r < 0.45: 1738 | # The [1:] is to drop leading '0': 0x63 -> x63 1739 | return '&#%s;' % hex(ord(ch))[1:] 1740 | else: 1741 | return '&#%s;' % ord(ch) 1742 | 1743 | def _hash_text(text): 1744 | return 'md5:'+md5(text.encode("utf-8")).hexdigest() 1745 | 1746 | 1747 | #---- mainline 1748 | 1749 | class _NoReflowFormatter(optparse.IndentedHelpFormatter): 1750 | """An optparse formatter that does NOT reflow the description.""" 1751 | def format_description(self, description): 1752 | return description or "" 1753 | 1754 | def _test(): 1755 | import doctest 1756 | doctest.testmod() 1757 | 1758 | def main(argv=None): 1759 | if argv is None: 1760 | argv = sys.argv 1761 | if not logging.root.handlers: 1762 | logging.basicConfig() 1763 | 1764 | usage = "usage: %prog [PATHS...]" 1765 | version = "%prog "+__version__ 1766 | parser = optparse.OptionParser(prog="markdown2", usage=usage, 1767 | version=version, description=cmdln_desc, 1768 | formatter=_NoReflowFormatter()) 1769 | parser.add_option("-v", "--verbose", dest="log_level", 1770 | action="store_const", const=logging.DEBUG, 1771 | help="more verbose output") 1772 | parser.add_option("--encoding", 1773 | help="specify encoding of text content") 1774 | parser.add_option("--html4tags", action="store_true", default=False, 1775 | help="use HTML 4 style for empty element tags") 1776 | parser.add_option("-s", "--safe", metavar="MODE", dest="safe_mode", 1777 | help="sanitize literal HTML: 'escape' escapes " 1778 | "HTML meta chars, 'replace' replaces with an " 1779 | "[HTML_REMOVED] note") 1780 | parser.add_option("-x", "--extras", action="append", 1781 | help="Turn on specific extra features (not part of " 1782 | "the core Markdown spec). Supported values: " 1783 | "'code-friendly' disables _/__ for emphasis; " 1784 | "'code-color' adds code-block syntax coloring; " 1785 | "'link-patterns' adds auto-linking based on patterns; " 1786 | "'footnotes' adds the footnotes syntax;" 1787 | "'xml' passes one-liner processing instructions and namespaced XML tags;" 1788 | "'pyshell' to put unindented Python interactive shell sessions in a <code> block.") 1789 | parser.add_option("--use-file-vars", 1790 | help="Look for and use Emacs-style 'markdown-extras' " 1791 | "file var to turn on extras. See " 1792 | "<http://code.google.com/p/python-markdown2/wiki/Extras>.") 1793 | parser.add_option("--link-patterns-file", 1794 | help="path to a link pattern file") 1795 | parser.add_option("--self-test", action="store_true", 1796 | help="run internal self-tests (some doctests)") 1797 | parser.add_option("--compare", action="store_true", 1798 | help="run against Markdown.pl as well (for testing)") 1799 | parser.set_defaults(log_level=logging.INFO, compare=False, 1800 | encoding="utf-8", safe_mode=None, use_file_vars=False) 1801 | opts, paths = parser.parse_args() 1802 | log.setLevel(opts.log_level) 1803 | 1804 | if opts.self_test: 1805 | return _test() 1806 | 1807 | if opts.extras: 1808 | extras = {} 1809 | for s in opts.extras: 1810 | splitter = re.compile("[,;: ]+") 1811 | for e in splitter.split(s): 1812 | if '=' in e: 1813 | ename, earg = e.split('=', 1) 1814 | try: 1815 | earg = int(earg) 1816 | except ValueError: 1817 | pass 1818 | else: 1819 | ename, earg = e, None 1820 | extras[ename] = earg 1821 | else: 1822 | extras = None 1823 | 1824 | if opts.link_patterns_file: 1825 | link_patterns = [] 1826 | f = open(opts.link_patterns_file) 1827 | try: 1828 | for i, line in enumerate(f.readlines()): 1829 | if not line.strip(): continue 1830 | if line.lstrip().startswith("#"): continue 1831 | try: 1832 | pat, href = line.rstrip().rsplit(None, 1) 1833 | except ValueError: 1834 | raise MarkdownError("%s:%d: invalid link pattern line: %r" 1835 | % (opts.link_patterns_file, i+1, line)) 1836 | link_patterns.append( 1837 | (_regex_from_encoded_pattern(pat), href)) 1838 | finally: 1839 | f.close() 1840 | else: 1841 | link_patterns = None 1842 | 1843 | from os.path import join, dirname, abspath, exists 1844 | markdown_pl = join(dirname(dirname(abspath(__file__))), "test", 1845 | "Markdown.pl") 1846 | for path in paths: 1847 | if opts.compare: 1848 | print "==== Markdown.pl ====" 1849 | perl_cmd = 'perl %s "%s"' % (markdown_pl, path) 1850 | o = os.popen(perl_cmd) 1851 | perl_html = o.read() 1852 | o.close() 1853 | sys.stdout.write(perl_html) 1854 | print "==== markdown2.py ====" 1855 | html = markdown_path(path, encoding=opts.encoding, 1856 | html4tags=opts.html4tags, 1857 | safe_mode=opts.safe_mode, 1858 | extras=extras, link_patterns=link_patterns, 1859 | use_file_vars=opts.use_file_vars) 1860 | sys.stdout.write( 1861 | html.encode(sys.stdout.encoding or "utf-8", 'xmlcharrefreplace')) 1862 | if opts.compare: 1863 | test_dir = join(dirname(dirname(abspath(__file__))), "test") 1864 | if exists(join(test_dir, "test_markdown2.py")): 1865 | sys.path.insert(0, test_dir) 1866 | from test_markdown2 import norm_html_from_html 1867 | norm_html = norm_html_from_html(html) 1868 | norm_perl_html = norm_html_from_html(perl_html) 1869 | else: 1870 | norm_html = html 1871 | norm_perl_html = perl_html 1872 | print "==== match? %r ====" % (norm_perl_html == norm_html) 1873 | 1874 | 1875 | if __name__ == "__main__": 1876 | sys.exit( main(sys.argv) ) 1877 | 1878 | -------------------------------------------------------------------------------- /libs/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from hashlib import sha1 5 | import random 6 | 7 | 8 | def hexuserpass(password): 9 | """ 10 | 加密使用者发布代码时的密码,所以使用最简单的加密方法 11 | """ 12 | enpass = sha1(password.encode('utf-8')).hexdigest() 13 | return enpass 14 | 15 | 16 | def checkuserpass(passwd, enpass): 17 | password = hexuserpass(passwd) 18 | return (password == enpass) 19 | 20 | 21 | def hexpassword(password): 22 | """ 23 | 加密管理员密码,就目前来说,这个加密强度也太弱了,可以考虑使用 `pbkdf2` 加密方法 24 | """ 25 | seed = "1234567890abcdefghijklmnopqrstuvwxyz \ 26 | ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+=-" 27 | sa = [] 28 | for i in range(8): 29 | sa.append(random.choice(seed)) 30 | salt = ''.join(sa) 31 | enpass = sha1(sha1((salt + password).encode('utf-8')).hexdigest().encode('utf-8')).hexdigest() 32 | return str(salt) + '$' + str(enpass) 33 | 34 | 35 | def checkpassword(passwd, enpass): 36 | salt = enpass[:8] 37 | password = sha1(sha1((salt + passwd).encode('utf-8')).hexdigest().encode('utf-8')).hexdigest() 38 | p = str(salt) + '$' + str(password) 39 | return (p == enpass) 40 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path 5 | import sae.const 6 | 7 | # 分页时每页的条目数 8 | NAVNUM = 8 9 | 10 | settings = { 11 | "sitename": "Code Share", # 设置为你的站点名 12 | "template_path": os.path.join(os.path.dirname(__file__), "templates"), 13 | "static_path": os.path.join(os.path.dirname(__file__), "static"), 14 | "xsrf_cookies": True, 15 | # 设置为随机的一串字符,千万不要使用现在这个 16 | "cookie_secret": "11oETzKXQAGaYdkL5gEmGeJJFuYh78768945$6P1o/Vo=", 17 | "login_url": "/auth/login", 18 | "autoescape": None, 19 | "debug": False, 20 | } 21 | 22 | #数据库设置 23 | db = { 24 | "host": sae.const.MYSQL_HOST, 25 | "db": sae.const.MYSQL_DB, 26 | "port": sae.const.MYSQL_PORT, 27 | "user": sae.const.MYSQL_USER, 28 | "password": sae.const.MYSQL_PASS, 29 | } 30 | -------------------------------------------------------------------------------- /static/blog.css: -------------------------------------------------------------------------------- 1 | 2 | body{ 3 | background-color: #eee; 4 | } 5 | 6 | div.container{ 7 | width:840px; 8 | } 9 | 10 | .header{ 11 | background-color: #fff; 12 | margin:0 10px 20px; 13 | } 14 | 15 | .header, 16 | .header a{ 17 | color: white; 18 | } 19 | .header h1{ 20 | font-size:20px; 21 | padding:5px; 22 | margin:0; 23 | } 24 | .header h1 a { 25 | text-decoration: none; 26 | } 27 | .hleft{ 28 | background-color: #0064cd; 29 | float:left; 30 | } 31 | .hleft:hover{ 32 | background-color: #db4937; 33 | } 34 | 35 | .hright{ 36 | float:right; 37 | padding:5px; 38 | } 39 | #content{ 40 | background-color: #fff; 41 | padding: 20px; 42 | margin: 0 10px 10px; 43 | -moz-border-radius: 6px; 44 | -webkit-border-top-left-radius: 6px; 45 | -webkit-border-top-right-radius: 6px; 46 | -webkit-border-bottom-left-radius: 6px; 47 | -webkit-border-bottom-right-radius: 6px; 48 | border-radius: 6px; 49 | -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15); 50 | -moz-box-shadow: 0 1px 2px rgba(0,0,0,.15); 51 | box-shadow: 0 1px 2px rgba(0,0,0,.15); 52 | } 53 | 54 | #footer p{ 55 | text-align: center; 56 | } 57 | 58 | .entry h1 a{ 59 | color: black; 60 | text-decoration: none; 61 | } 62 | 63 | .entry{ 64 | margin-bottom: 2em; 65 | } 66 | 67 | .entry p { 68 | margin: 0; 69 | margin-bottom: 1em; 70 | } 71 | 72 | .entry .body{ 73 | margin-top: 1em; 74 | line-height: 16pt; 75 | } 76 | .title{ 77 | float:left; 78 | } 79 | .title h2{ 80 | font-weight:normal; 81 | } 82 | .delete{ 83 | float:right; 84 | margin-left:20px; 85 | } 86 | .box{ 87 | -moz-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 88 | -webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 89 | box-shadow:0 1px 3px rgba(0, 0, 0, 0.2); 90 | -moz-border-radius: 5px; 91 | -webkit-border-top-left-radius: 5px; 92 | -webkit-border-top-right-radius: 5px; 93 | -webkit-border-bottom-left-radius: 5px; 94 | -webkit-border-bottom-right-radius: 5px; 95 | border-radius: 5px; 96 | } 97 | .clearfix:after{ 98 | content: "."; 99 | visibility: hidden; 100 | display: block; 101 | height: 0; 102 | clear: both; 103 | } -------------------------------------------------------------------------------- /static/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-modal.js v1.4.0 3 | * http://twitter.github.com/bootstrap/javascript.html#modal 4 | * ========================================================= 5 | * Copyright 2011 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================= */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) 26 | * ======================================================= */ 27 | 28 | var transitionEnd 29 | 30 | $(document).ready(function () { 31 | 32 | $.support.transition = (function () { 33 | var thisBody = document.body || document.documentElement 34 | , thisStyle = thisBody.style 35 | , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined 36 | return support 37 | })() 38 | 39 | // set CSS transition event type 40 | if ( $.support.transition ) { 41 | transitionEnd = "TransitionEnd" 42 | if ( $.browser.webkit ) { 43 | transitionEnd = "webkitTransitionEnd" 44 | } else if ( $.browser.mozilla ) { 45 | transitionEnd = "transitionend" 46 | } else if ( $.browser.opera ) { 47 | transitionEnd = "oTransitionEnd" 48 | } 49 | } 50 | 51 | }) 52 | 53 | 54 | /* MODAL PUBLIC CLASS DEFINITION 55 | * ============================= */ 56 | 57 | var Modal = function ( content, options ) { 58 | this.settings = $.extend({}, $.fn.modal.defaults, options) 59 | this.$element = $(content) 60 | .delegate('.close', 'click.modal', $.proxy(this.hide, this)) 61 | 62 | if ( this.settings.show ) { 63 | this.show() 64 | } 65 | 66 | return this 67 | } 68 | 69 | Modal.prototype = { 70 | 71 | toggle: function () { 72 | return this[!this.isShown ? 'show' : 'hide']() 73 | } 74 | 75 | , show: function () { 76 | var that = this 77 | this.isShown = true 78 | this.$element.trigger('show') 79 | 80 | escape.call(this) 81 | backdrop.call(this, function () { 82 | var transition = $.support.transition && that.$element.hasClass('fade') 83 | 84 | that.$element 85 | .appendTo(document.body) 86 | .show() 87 | 88 | if (transition) { 89 | that.$element[0].offsetWidth // force reflow 90 | } 91 | 92 | that.$element.addClass('in') 93 | 94 | transition ? 95 | that.$element.one(transitionEnd, function () { that.$element.trigger('shown') }) : 96 | that.$element.trigger('shown') 97 | 98 | }) 99 | 100 | return this 101 | } 102 | 103 | , hide: function (e) { 104 | e && e.preventDefault() 105 | 106 | if ( !this.isShown ) { 107 | return this 108 | } 109 | 110 | var that = this 111 | this.isShown = false 112 | 113 | escape.call(this) 114 | 115 | this.$element 116 | .trigger('hide') 117 | .removeClass('in') 118 | 119 | $.support.transition && this.$element.hasClass('fade') ? 120 | hideWithTransition.call(this) : 121 | hideModal.call(this) 122 | 123 | return this 124 | } 125 | 126 | } 127 | 128 | 129 | /* MODAL PRIVATE METHODS 130 | * ===================== */ 131 | 132 | function hideWithTransition() { 133 | // firefox drops transitionEnd events :{o 134 | var that = this 135 | , timeout = setTimeout(function () { 136 | that.$element.unbind(transitionEnd) 137 | hideModal.call(that) 138 | }, 500) 139 | 140 | this.$element.one(transitionEnd, function () { 141 | clearTimeout(timeout) 142 | hideModal.call(that) 143 | }) 144 | } 145 | 146 | function hideModal (that) { 147 | this.$element 148 | .hide() 149 | .trigger('hidden') 150 | 151 | backdrop.call(this) 152 | } 153 | 154 | function backdrop ( callback ) { 155 | var that = this 156 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 157 | if ( this.isShown && this.settings.backdrop ) { 158 | var doAnimate = $.support.transition && animate 159 | 160 | this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') 161 | .appendTo(document.body) 162 | 163 | if ( this.settings.backdrop != 'static' ) { 164 | this.$backdrop.click($.proxy(this.hide, this)) 165 | } 166 | 167 | if ( doAnimate ) { 168 | this.$backdrop[0].offsetWidth // force reflow 169 | } 170 | 171 | this.$backdrop.addClass('in') 172 | 173 | doAnimate ? 174 | this.$backdrop.one(transitionEnd, callback) : 175 | callback() 176 | 177 | } else if ( !this.isShown && this.$backdrop ) { 178 | this.$backdrop.removeClass('in') 179 | 180 | $.support.transition && this.$element.hasClass('fade')? 181 | this.$backdrop.one(transitionEnd, $.proxy(removeBackdrop, this)) : 182 | removeBackdrop.call(this) 183 | 184 | } else if ( callback ) { 185 | callback() 186 | } 187 | } 188 | 189 | function removeBackdrop() { 190 | this.$backdrop.remove() 191 | this.$backdrop = null 192 | } 193 | 194 | function escape() { 195 | var that = this 196 | if ( this.isShown && this.settings.keyboard ) { 197 | $(document).bind('keyup.modal', function ( e ) { 198 | if ( e.which == 27 ) { 199 | that.hide() 200 | } 201 | }) 202 | } else if ( !this.isShown ) { 203 | $(document).unbind('keyup.modal') 204 | } 205 | } 206 | 207 | 208 | /* MODAL PLUGIN DEFINITION 209 | * ======================= */ 210 | 211 | $.fn.modal = function ( options ) { 212 | var modal = this.data('modal') 213 | 214 | if (!modal) { 215 | 216 | if (typeof options == 'string') { 217 | options = { 218 | show: /show|toggle/.test(options) 219 | } 220 | } 221 | 222 | return this.each(function () { 223 | $(this).data('modal', new Modal(this, options)) 224 | }) 225 | } 226 | 227 | if ( options === true ) { 228 | return modal 229 | } 230 | 231 | if ( typeof options == 'string' ) { 232 | modal[options]() 233 | } else if ( modal ) { 234 | modal.toggle() 235 | } 236 | 237 | return this 238 | } 239 | 240 | $.fn.modal.Modal = Modal 241 | 242 | $.fn.modal.defaults = { 243 | backdrop: false 244 | , keyboard: false 245 | , show: false 246 | } 247 | 248 | 249 | /* MODAL DATA- IMPLEMENTATION 250 | * ========================== */ 251 | 252 | $(document).ready(function () { 253 | $('body').delegate('[data-controls-modal]', 'click', function (e) { 254 | e.preventDefault() 255 | var $this = $(this).data('show', true) 256 | $('#' + $this.attr('data-controls-modal')).modal( $this.data() ) 257 | }) 258 | }) 259 | 260 | }( window.jQuery || window.ender ); 261 | -------------------------------------------------------------------------------- /static/bootstrap.min.css: -------------------------------------------------------------------------------- 1 | html,body{margin:0;padding:0;} 2 | h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;} 3 | table{border-collapse:collapse;border-spacing:0;} 4 | ol,ul{list-style:none;} 5 | q:before,q:after,blockquote:before,blockquote:after{content:"";} 6 | html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} 7 | a:focus{outline:thin dotted;} 8 | a:hover,a:active{outline:0;} 9 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} 10 | audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} 11 | audio:not([controls]){display:none;} 12 | sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;} 13 | sup{top:-0.5em;} 14 | sub{bottom:-0.25em;} 15 | img{border:0;-ms-interpolation-mode:bicubic;} 16 | button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;} 17 | button,input{line-height:normal;*overflow:visible;} 18 | button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 19 | button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} 20 | input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} 21 | input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;} 22 | textarea{overflow:auto;vertical-align:top;} 23 | body{background-color:#ffffff;margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;} 24 | .container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;} 25 | .container:after{clear:both;} 26 | .container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;} 27 | .container-fluid:after{clear:both;} 28 | .container-fluid>.sidebar{position:absolute;top:0;left:20px;width:220px;} 29 | .container-fluid>.content{margin-left:240px;} 30 | a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;} 31 | .pull-right{float:right;} 32 | .pull-left{float:left;} 33 | .hide{display:none;} 34 | .show{display:block;} 35 | .row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;} 36 | .row:after{clear:both;} 37 | .row>[class*="span"]{display:inline;float:left;margin-left:20px;} 38 | 39 | .span4{width:220px;} 40 | 41 | .span10{width:580px;} 42 | 43 | .row>.offset1{margin-left:80px;} 44 | .row>.offset2{margin-left:140px;} 45 | .row>.offset3{margin-left:200px;} 46 | .row>.offset4{margin-left:260px;} 47 | .row>.offset5{margin-left:320px;} 48 | .row>.offset6{margin-left:380px;} 49 | .row>.offset7{margin-left:440px;} 50 | .row>.offset8{margin-left:500px;} 51 | .row>.offset9{margin-left:560px;} 52 | .row>.offset10{margin-left:620px;} 53 | .row>.offset11{margin-left:680px;} 54 | .row>.offset12{margin-left:740px;} 55 | .span-one-third{width:300px;} 56 | .span-two-thirds{width:620px;} 57 | .row>.offset-one-third{margin-left:340px;} 58 | .row>.offset-two-thirds{margin-left:660px;} 59 | p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;} 60 | h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;} 61 | h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;} 62 | h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;} 63 | h3,h4,h5,h6{line-height:36px;} 64 | h3{font-size:18px;}h3 small{font-size:14px;} 65 | h4{font-size:16px;}h4 small{font-size:12px;} 66 | h5{font-size:14px;} 67 | h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;} 68 | ul,ol{margin:0 0 18px 25px;} 69 | ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} 70 | ul{list-style:disc;} 71 | ol{list-style:decimal;} 72 | li{line-height:18px;color:#808080;} 73 | ul.unstyled{list-style:none;margin-left:0;} 74 | dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;} 75 | dl dt{font-weight:bold;} 76 | dl dd{margin-left:9px;} 77 | hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;} 78 | strong{font-style:inherit;font-weight:bold;} 79 | em{font-style:italic;font-weight:inherit;line-height:inherit;} 80 | .muted{color:#bfbfbf;} 81 | blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;} 82 | blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';} 83 | address{display:block;line-height:18px;margin-bottom:18px;} 84 | code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 85 | code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;} 86 | pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;} 87 | form{margin-bottom:18px;} 88 | fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;} 89 | form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;} 90 | form .clearfix:after{clear:both;} 91 | label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;} 92 | label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;} 93 | form .input{margin-left:150px;} 94 | input[type=checkbox],input[type=radio]{cursor:pointer;} 95 | input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 96 | select{padding:initial;} 97 | input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;} 98 | input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 99 | input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;} 100 | select,input[type=file]{height:27px;*height:auto;line-height:27px;*margin-top:4px;} 101 | select[multiple]{height:inherit;background-color:#ffffff;} 102 | textarea{height:auto;} 103 | .uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} 104 | :-moz-placeholder{color:#bfbfbf;} 105 | ::-webkit-input-placeholder{color:#bfbfbf;} 106 | input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);} 107 | input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);} 108 | input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;} 109 | form .clearfix.error>label,form .clearfix.error .help-block,form .clearfix.error .help-inline{color:#b94a48;} 110 | form .clearfix.error input,form .clearfix.error textarea{color:#b94a48;border-color:#ee5f5b;}form .clearfix.error input:focus,form .clearfix.error textarea:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} 111 | form .clearfix.error .input-prepend .add-on,form .clearfix.error .input-append .add-on{color:#b94a48;background-color:#fce6e6;border-color:#b94a48;} 112 | form .clearfix.warning>label,form .clearfix.warning .help-block,form .clearfix.warning .help-inline{color:#c09853;} 113 | form .clearfix.warning input,form .clearfix.warning textarea{color:#c09853;border-color:#ccae64;}form .clearfix.warning input:focus,form .clearfix.warning textarea:focus{border-color:#be9a3f;-webkit-box-shadow:0 0 6px #e5d6b1;-moz-box-shadow:0 0 6px #e5d6b1;box-shadow:0 0 6px #e5d6b1;} 114 | form .clearfix.warning .input-prepend .add-on,form .clearfix.warning .input-append .add-on{color:#c09853;background-color:#d2b877;border-color:#c09853;} 115 | form .clearfix.success>label,form .clearfix.success .help-block,form .clearfix.success .help-inline{color:#468847;} 116 | form .clearfix.success input,form .clearfix.success textarea{color:#468847;border-color:#57a957;}form .clearfix.success input:focus,form .clearfix.success textarea:focus{border-color:#458845;-webkit-box-shadow:0 0 6px #9acc9a;-moz-box-shadow:0 0 6px #9acc9a;box-shadow:0 0 6px #9acc9a;} 117 | form .clearfix.success .input-prepend .add-on,form .clearfix.success .input-append .add-on{color:#468847;background-color:#bcddbc;border-color:#468847;} 118 | .input-mini,input.mini,textarea.mini,select.mini{width:60px;} 119 | .input-small,input.small,textarea.small,select.small{width:90px;} 120 | .input-medium,input.medium,textarea.medium,select.medium{width:150px;} 121 | .input-large,input.large,textarea.large,select.large{width:210px;} 122 | .input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;} 123 | .input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;} 124 | textarea.xxlarge{overflow-y:auto;} 125 | input.span1,textarea.span1{display:inline-block;float:none;width:30px;margin-left:0;} 126 | input.span2,textarea.span2{display:inline-block;float:none;width:90px;margin-left:0;} 127 | input.span3,textarea.span3{display:inline-block;float:none;width:150px;margin-left:0;} 128 | input.span4,textarea.span4{display:inline-block;float:none;width:210px;margin-left:0;} 129 | input.span5,textarea.span5{display:inline-block;float:none;width:270px;margin-left:0;} 130 | input.span6,textarea.span6{display:inline-block;float:none;width:330px;margin-left:0;} 131 | input.span7,textarea.span7{display:inline-block;float:none;width:390px;margin-left:0;} 132 | input.span8,textarea.span8{display:inline-block;float:none;width:450px;margin-left:0;} 133 | input.span9,textarea.span9{display:inline-block;float:none;width:510px;margin-left:0;} 134 | input.span10,textarea.span10{display:inline-block;float:none;width:570px;margin-left:0;} 135 | input.span11,textarea.span11{display:inline-block;float:none;width:630px;margin-left:0;} 136 | input.span12,textarea.span12{display:inline-block;float:none;width:690px;margin-left:0;} 137 | input.span13,textarea.span13{display:inline-block;float:none;width:750px;margin-left:0;} 138 | input.span14,textarea.span14{display:inline-block;float:none;width:810px;margin-left:0;} 139 | input.span15,textarea.span15{display:inline-block;float:none;width:870px;margin-left:0;} 140 | input.span16,textarea.span16{display:inline-block;float:none;width:930px;margin-left:0;} 141 | input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;} 142 | .actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;} 143 | .help-inline,.help-block{font-size:13px;line-height:18px;color:#bfbfbf;} 144 | .help-inline{padding-left:5px;*position:relative;*top:-5px;} 145 | .help-block{display:block;max-width:600px;} 146 | .inline-inputs{color:#808080;}.inline-inputs span{padding:0 2px 0 1px;} 147 | .input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} 148 | .input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} 149 | .input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;} 150 | .input-prepend .add-on{*margin-top:1px;} 151 | .input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} 152 | .input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;} 153 | .inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;} 154 | .inputs-list label{display:block;float:none;width:auto;padding:0;margin-left:20px;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;} 155 | .inputs-list label small{font-size:11px;font-weight:normal;} 156 | .inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;} 157 | .inputs-list:first-child{padding-top:6px;} 158 | .inputs-list li+li{padding-top:2px;} 159 | .inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;margin-left:-20px;float:left;} 160 | .form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;} 161 | .form-stacked legend{padding-left:0;} 162 | .form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;} 163 | .form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;} 164 | .form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;} 165 | .form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;} 166 | .form-stacked .actions{margin-left:-20px;padding-left:20px;} 167 | table{width:100%;margin-bottom:18px;padding:0;font-size:13px;border-collapse:collapse;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;} 168 | table th{padding-top:9px;font-weight:bold;vertical-align:middle;} 169 | table td{vertical-align:top;border-top:1px solid #ddd;} 170 | table tbody th{border-top:1px solid #ddd;vertical-align:top;} 171 | .condensed-table th,.condensed-table td{padding:5px 5px 4px;} 172 | .bordered-table{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.bordered-table th+th,.bordered-table td+td,.bordered-table th+td{border-left:1px solid #ddd;} 173 | .bordered-table thead tr:first-child th:first-child,.bordered-table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;} 174 | .bordered-table thead tr:first-child th:last-child,.bordered-table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;} 175 | .bordered-table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;} 176 | .bordered-table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} 177 | .zebra-striped tbody tr:nth-child(odd) td,.zebra-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} 178 | .zebra-striped tbody tr:hover td,.zebra-striped tbody tr:hover th{background-color:#f5f5f5;} 179 | table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;} 180 | table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);} 181 | table .header:hover:after{visibility:visible;} 182 | table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} 183 | table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} 184 | table .blue{color:#049cdb;border-bottom-color:#049cdb;} 185 | table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;} 186 | table .green{color:#46a546;border-bottom-color:#46a546;} 187 | table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;} 188 | table .red{color:#9d261d;border-bottom-color:#9d261d;} 189 | table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;} 190 | table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;} 191 | table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;} 192 | table .orange{color:#f89406;border-bottom-color:#f89406;} 193 | table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;} 194 | table .purple{color:#7a43b6;border-bottom-color:#7a43b6;} 195 | table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;} 196 | .pills a{margin:5px 3px 5px 0;padding:0 15px;line-height:30px;text-shadow:0 1px 1px #ffffff;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#00438a;} 197 | .pills .active a{color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#0069d6;} 198 | .pills-vertical>li{float:none;} 199 | .tab-content>.tab-pane,.pill-content>.pill-pane,.tab-content>div,.pill-content>div{display:none;} 200 | .tab-content>.active,.pill-content>.active{display:block;} 201 | .hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;} 202 | .hero-unit p{font-size:18px;font-weight:200;line-height:27px;} 203 | footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;} 204 | .page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;} 205 | .btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;} 206 | .btn .close,.alert-message .close{font-family:Arial,sans-serif;line-height:18px;} 207 | .btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 208 | .btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 209 | .btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 210 | .btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;} 211 | .btn:focus{outline:1px dotted #666;} 212 | .btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 213 | .btn.active,.btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);} 214 | .btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 215 | .btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 216 | .btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} 217 | .btn.small{padding:7px 9px 7px;font-size:11px;} 218 | :root .alert-message,:root .btn{border-radius:0 \0;} 219 | button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;} 220 | .close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=25);-khtml-opacity:0.25;-moz-opacity:0.25;opacity:0.25;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;} 221 | .pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} 222 | .pagination li{display:inline;} 223 | .pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;} 224 | .pagination a:hover,.pagination .active a{background-color:#c7eefe;} 225 | .pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;} 226 | .pagination .next a{border:0;} 227 | .well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} 228 | .modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;} 229 | .modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} 230 | .modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;} 231 | .modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} 232 | .modal.fade.in{top:50%;} 233 | .modal-header{border-bottom:1px solid #eee;padding:5px 15px;} 234 | .modal-body{padding:15px;} 235 | .modal-body form{margin-bottom:0;} 236 | .modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;} 237 | .modal-footer:after{clear:both;} 238 | .modal-footer .btn{float:right;margin-left:5px;} 239 | .modal .popover,.modal .twipsy{z-index:12000;} 240 | .twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} 241 | .twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} 242 | .twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} 243 | .twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} 244 | .twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} 245 | .twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} 246 | .twipsy-arrow{position:absolute;width:0;height:0;} 247 | .fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;} 248 | .label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;white-space:nowrap;background-color:#bfbfbf;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;} 249 | .label.warning{background-color:#f89406;} 250 | .label.success{background-color:#46a546;} 251 | .label.notice{background-color:#62cffc;} -------------------------------------------------------------------------------- /static/prettify.css: -------------------------------------------------------------------------------- 1 | .com { color: #93a1a1; } 2 | .lit { color: #195f91; } 3 | .pun, .opn, .clo { color: #93a1a1; } 4 | .fun { color: #dc322f; } 5 | .str, .atv { color: #268bd2; } 6 | .kwd, .tag { color: #195f91; } 7 | .typ, .atn, .dec, .var { color: #CB4B16; } 8 | .pln { color: #93a1a1; } 9 | .prettyprint { 10 | background-color: #fefbf3; 11 | padding: 9px; 12 | border: 1px solid rgba(0,0,0,.2); 13 | -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1); 14 | -moz-box-shadow: 0 1px 2px rgba(0,0,0,.1); 15 | box-shadow: 0 1px 2px rgba(0,0,0,.1); 16 | } 17 | 18 | /* Specify class=linenums on a pre to get line numbering */ 19 | ol.linenums { 20 | margin: 0 0 0 40px; 21 | } 22 | /* IE indents via margin-left */ 23 | ol.linenums li { 24 | padding: 0 5px; 25 | color: rgba(0,0,0,.15); 26 | line-height: 20px; 27 | -webkit-border-radius: 2px; 28 | -moz-border-radius: 2px; 29 | border-radius: 2px; 30 | } 31 | /* Alternate shading for lines */ 32 | li.L1, li.L3, li.L5, li.L7, li.L9 { } 33 | 34 | /* 35 | $base03: #002b36; 36 | $base02: #073642; 37 | $base01: #586e75; 38 | $base00: #657b83; 39 | $base0: #839496; 40 | $base1: #93a1a1; 41 | $base2: #eee8d5; 42 | $base3: #fdf6e3; 43 | $yellow: #b58900; 44 | $orange: #cb4b16; 45 | $red: #dc322f; 46 | $magenta: #d33682; 47 | $violet: #6c71c4; 48 | $blue: #268bd2; 49 | $cyan: #2aa198; 50 | $green: #859900; 51 | */ 52 | 53 | 54 | /* 55 | #1d1f21 Background 56 | #282a2e Current Line 57 | #373b41 Selection 58 | #c5c8c6 Foreground 59 | #969896 Comment 60 | #cc6666 Red 61 | #de935f Orange 62 | #f0c674 Yellow 63 | #b5bd68 Green 64 | #8abeb7 Aqua 65 | #81a2be Blue 66 | #b294bb Purple 67 | */ 68 | 69 | 70 | /* DARK THEME */ 71 | /* ---------- */ 72 | 73 | .prettyprint-dark { 74 | background-color: #1d1f21; 75 | border: 0; 76 | padding: 10px; 77 | } 78 | .prettyprint-dark .linenums li { 79 | color: #444; 80 | } 81 | .prettyprint-dark .linenums li:hover { 82 | background-color: #282a2e; 83 | } 84 | /* tags in html */ 85 | .prettyprint-dark .kwd, 86 | .prettyprint-dark .tag { color: #cc6666; } 87 | /* html attr */ 88 | .prettyprint-dark .typ, 89 | .prettyprint-dark .atn, 90 | .prettyprint-dark .dec, 91 | .prettyprint-dark .var { color: #de935f; } 92 | /* html attr values */ 93 | .prettyprint-dark .str, 94 | .prettyprint-dark .atv { color: #b5bd68; } 95 | -------------------------------------------------------------------------------- /static/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c< 4 | f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&& 5 | (j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r= 6 | {b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length, 7 | t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b=== 8 | "string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value", 15 | m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m= 16 | a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue= 17 | j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m, 27 | 250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit", 28 | PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})(); 29 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8"> 5 | <title> {% block title %}{{ escape(handler.settings["sitename"]) }}{% end %} 6 | 7 | 8 | 9 | {% block head %}{% end %} 10 | 11 | 12 | 13 | 14 |

15 | 23 | 24 |
{% block body %}{% end %}
25 | 26 |
27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /templates/compose.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}发布代码——Code Share{% end %} 3 | {% block body %} 4 |
5 |
6 | 发布分享代码 7 |
8 | 9 |
10 | 11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 |
19 |
20 |
21 | 22 |
23 | 24 | 25 | 支持Markdown语法,请参考:# 26 | 27 |
28 |
29 |
30 | 31 |
32 | 33 | 34 | 在你修改和删除代码时有用。 35 | 36 |
37 |
38 |
39 | 40 |
41 | 42 | 43 | 最近垃圾信息泛滥,请输入 1984。 44 | 45 |
46 |
47 |
48 |   49 | 50 |
51 |
52 | 53 | {{ xsrf_form_html() }} 54 | 55 | {% end %} 56 | 57 | {% block bottom %} 58 | 59 | 60 | 80 | {% end %} 81 | 82 | -------------------------------------------------------------------------------- /templates/entry.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ entry.title }}——{{ escape(handler.settings["sitename"]) }}{% end %} 3 | {% block head %} 4 | 5 | 6 | {% end %} 7 | 8 | {% block body %} 9 |
10 |
11 |
12 |

Code:{{ entry.title }}

13 |
14 |
15 | DElete 16 |
17 |
18 | Update 19 |
20 |
21 |
{{ entry.code }}
22 | Code info 23 |
24 |

{{ entry.info }}

25 | {{ entry.published.strftime('%Y-%m-%d %H:%M') }} 26 |

27 | {% if current_user %} 28 | 29 | {% end %} 30 |
31 | 32 | 33 | 57 | 58 | 82 | 83 | {% end %} 84 | {% block bottom %} 85 | 86 | 87 | 88 | 89 | {% end %} -------------------------------------------------------------------------------- /templates/feed.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | {{ escape(handler.settings["sitename"]) }} 5 | 6 | http://{{ request.host }}/ 7 | A simple code share site runing SAE. 8 | {% set date_format = "%Y-%m-%dT%H:%M:%SZ" %} 9 | {% if len(entries) > 0 %} 10 | {{ max(e.updated for e in entries).strftime(date_format) }} 11 | {% else %} 12 | {{ datetime.datetime.utcnow().strftime(date_format) }} 13 | {% end %} 14 | cn 15 | hourly 16 | 1 17 | http://wordpress.org/?v=3.2.1 18 | {{ escape(handler.settings["sitename"]) }} 19 | {% for entry in entries %} 20 | 21 | {{ escape(entry.title) }} 22 | http://{{ request.host }}/{{ entry.slug }} 23 | {{ entry.published.strftime(date_format) }} 24 | 25 | 26 | 27 | 28 | 29 | {% end %} 30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for i,entry in enumerate(entries) %} 14 | 15 | 16 | 17 | 18 | 19 | {% end %} 20 | 21 |
序号标题介绍
#{{counts-i}}{{entry.title}}{{entry.info[ :100]}}
22 | 57 | {% end %} 58 | -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}管理员登录{% end %} 3 | {% block body %} 4 |
5 |
6 | {{ msg if msg else ' '}} 7 | 管理员登录 8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 | {{ xsrf_form_html() }} 27 | 28 | {% end %} 29 | 30 | {% block bottom %} 31 | 32 | 33 | 53 | {% end %} 54 | 55 | -------------------------------------------------------------------------------- /templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}第{{ this }}页——{{ escape(handler.settings["sitename"]) }}{% end %} 3 | {% block body %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for i,entry in enumerate(entries) %} 15 | 16 | 17 | 18 | 19 | 20 | {% end %} 21 | 22 |
序号标题介绍
#{{counts-(this-1)*8-i}}{{entry.title}}{{entry.info[ :100]}}
23 |
24 | 93 | {% end %} 94 | -------------------------------------------------------------------------------- /templates/start.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}基本设置——准备开始{% end %} 3 | {% block body %} 4 |
5 |
6 | 管理员注册 7 |
8 | 9 |
10 | 11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 |
19 |
20 |
21 | 22 |
23 | 24 | 25 |
26 |
27 |
28 | 29 |
30 |
31 | 32 | {{ xsrf_form_html() }} 33 |
34 | {% end %} 35 | 36 | {% block bottom %} 37 | 38 | 39 | 59 | {% end %} 60 | 61 | -------------------------------------------------------------------------------- /templates/update.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}修改代码——Code Share{% end %} 3 | {% block body %} 4 |
5 |
6 | 修改分享代码 7 |
8 | 9 |
10 | 11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 |
19 |
20 |
21 | 22 |
23 | 24 | 25 | 支持Markdown语法,请参考:# 26 | 27 |
28 |
29 |
30 | 31 |
32 | 33 | 34 | 为了保证安全,麻烦你输入你的验证密码,如果密码错误,将不会修改。 35 | 36 |
37 |
38 |
39 |   40 | 41 |
42 |
43 | 44 | {{ xsrf_form_html() }} 45 |
46 | {% end %} 47 | 48 | {% block bottom %} 49 | 50 | 51 | 71 | {% end %} 72 | -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from apps import code, admin 5 | 6 | urls = [(r"/", code.HomeHandler), 7 | (r"/(\d+)", code.EntryHandler), 8 | (r"/newcode", code.ComposeHandler), 9 | (r"/user/login", code.UserLoginHandler), 10 | (r"/update/(\d+)", code.UpdateHandler), 11 | (r"/delete", code.DeleteHandler), 12 | (r"/page/(\d+)", code.PageHandler), 13 | (r"/auth/login", admin.LoginHandler), 14 | (r"/auth/logout", admin.LogoutHandler), 15 | (r"/admin/start", admin.SiteStartHandler), 16 | (r"/admin/delete/(\d+)", admin.DeleteHandler), 17 | (r"/feed", code.FeedHandler), ] 18 | --------------------------------------------------------------------------------