Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

first stab

  • Loading branch information...
commit 885aecb444662005977efb4c5b950768ed38c8d4 0 parents
Trent Mick authored

Showing 49 changed files with 12,314 additions and 0 deletions. Show diff stats Hide diff stats

  1. +2 0  .gitignore
  2. +3 0  DEVNOTES.txt
  3. +36 0 README.md
  4. +315 0 bin/nodedoc.py
  5. +2 0  deps/README.md
  6. +346 0 deps/appdirs.py
  7. BIN  deps/appdirs.pyc
  8. +2,299 0 deps/markdown2.py
  9. BIN  deps/markdown2.pyc
  10. +37 0 doc/api/_toc.markdown
  11. +624 0 doc/api/addons.markdown
  12. +36 0 doc/api/all.markdown
  13. +44 0 doc/api/appendix_1.markdown
  14. +84 0 doc/api/assert.markdown
  15. +645 0 doc/api/buffer.markdown
  16. +407 0 doc/api/child_process.markdown
  17. +439 0 doc/api/cluster.markdown
  18. +363 0 doc/api/crypto.markdown
  19. +138 0 doc/api/debugger.markdown
  20. +209 0 doc/api/dgram.markdown
  21. +148 0 doc/api/dns.markdown
  22. +68 0 doc/api/documentation.markdown
  23. +261 0 doc/api/domain.markdown
  24. +95 0 doc/api/events.markdown
  25. +667 0 doc/api/fs.markdown
  26. +150 0 doc/api/globals.markdown
  27. +886 0 doc/api/http.markdown
  28. +173 0 doc/api/https.markdown
  29. +1 0  doc/api/index.markdown
  30. +461 0 doc/api/modules.markdown
  31. +467 0 doc/api/net.markdown
  32. +134 0 doc/api/os.markdown
  33. +156 0 doc/api/path.markdown
  34. +410 0 doc/api/process.markdown
  35. +49 0 doc/api/querystring.markdown
  36. +283 0 doc/api/readline.markdown
  37. +188 0 doc/api/repl.markdown
  38. +61 0 doc/api/stdio.markdown
  39. +184 0 doc/api/stream.markdown
  40. +24 0 doc/api/string_decoder.markdown
  41. +23 0 doc/api/synopsis.markdown
  42. +31 0 doc/api/timers.markdown
  43. +491 0 doc/api/tls.markdown
  44. +75 0 doc/api/tty.markdown
  45. +99 0 doc/api/url.markdown
  46. +190 0 doc/api/util.markdown
  47. +220 0 doc/api/vm.markdown
  48. +276 0 doc/api/zlib.markdown
  49. +14 0 package.json
2  .gitignore
... ... @@ -0,0 +1,2 @@
  1 +/tmp
  2 +/node_modules
3  DEVNOTES.txt
... ... @@ -0,0 +1,3 @@
  1 +# dev notes
  2 +
  3 + less '+10G' configure # how to call `less` to jump to particular line
36 README.md
Source Rendered
... ... @@ -0,0 +1,36 @@
  1 +A fledgling `perldoc` for node.js.
  2 +
  3 +# Installation
  4 +
  5 +1. Get [node](http://nodejs.org).
  6 +
  7 +2. `npm install -g nodedoc`
  8 +
  9 +You should now have "nodedoc" on your PATH:
  10 +
  11 + $ nodedoc --version
  12 + nodedoc 1.0.0
  13 +
  14 +# Status
  15 +
  16 +This really is a quick hack. There are a number of limitations in the current
  17 +Markdown -> HTML -> ANSI escape-colored text. Among them:
  18 +
  19 +- nested lists aren't handled properly
  20 +- `<ol>` aren't handled properly
  21 +
  22 +The current version of the node.js docs is a snapshot of the
  23 +<https://github.com/joyent/node> master.
  24 +
  25 +
  26 +# Examples
  27 +
  28 +This will render and color the fs.markdown core docs and page through them
  29 +(using your `PAGER` environment setting, if any):
  30 +
  31 + $ nodedoc fs
  32 +
  33 +List all nodedoc sections:
  34 +
  35 + $ nodedoc -l
  36 +
315 bin/nodedoc.py
... ... @@ -0,0 +1,315 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +"""nodedoc -- fledgling perldoc for node.js
  5 +
  6 +Usage:
  7 + nodedoc SECTION
  8 +
  9 +See <https://github.com/trentm/nodedoc> for more info.
  10 +"""
  11 +
  12 +__version_info__ = (1, 0, 0)
  13 +__version__ = '.'.join(map(str, __version_info__))
  14 +
  15 +import re
  16 +import sys
  17 +import textwrap
  18 +import os
  19 +from os.path import dirname, abspath, join, exists
  20 +import logging
  21 +import codecs
  22 +import optparse
  23 +from glob import glob
  24 +
  25 +TOP = dirname(dirname(abspath(__file__)))
  26 +sys.path.insert(0, join(TOP, "deps"))
  27 +import markdown2
  28 +import appdirs
  29 +
  30 +
  31 +
  32 +#---- globals
  33 +
  34 +log = logging.getLogger("nodedoc")
  35 +CACHE_DIR = appdirs.user_cache_dir("nodedoc", "trentm")
  36 +
  37 +
  38 +
  39 +#---- exceptions
  40 +
  41 +class Error(Exception):
  42 + pass
  43 +
  44 +
  45 +
  46 +#---- stylers
  47 +
  48 +def red(text):
  49 + return '\033[31m' + text + '\033[39m'
  50 +def green(text):
  51 + return '\033[32m' + text + '\033[39m'
  52 +def cyan(text):
  53 + return '\033[36m' + text + '\033[39m'
  54 +def grey(text):
  55 + return '\033[90m' + text + '\033[39m'
  56 +
  57 +def bold(text):
  58 + return '\033[1m' + text + '\033[22m'
  59 +def italic(text):
  60 + return '\033[3m' + text + '\033[23m'
  61 +def inverse(text):
  62 + return '\033[7m' + text + '\033[27m'
  63 +
  64 +
  65 +
  66 +#---- re.sub transformers
  67 +
  68 +def indent(match):
  69 + INDENT = ' '
  70 + text = match.group(1)
  71 + after = INDENT + INDENT.join(text.splitlines(True))
  72 + #print "XXX hit: after=%r" % after
  73 + return after
  74 +
  75 +def code(match):
  76 + """green. Special case grey for "Stability: ..." pre-blocks."""
  77 + text = match.group(1)
  78 + styler = green
  79 + if text.startswith("Stability:"):
  80 + styler = grey
  81 + lines = [
  82 + styler(line)
  83 + for line in text.splitlines(False)
  84 + ]
  85 + return '\n'.join(lines)
  86 +
  87 +def wrap(match, width=80):
  88 + """XXX TODO: somehow make the ANSI escapes zero-length for width
  89 + calculation."""
  90 + text = match.group(1)
  91 + text = '\n'.join(textwrap.wrap(text, width=width))
  92 + return text
  93 +
  94 +def h1(match):
  95 + """bold red"""
  96 + text = match.group(1)
  97 + return bold(red('# ' + text))
  98 + return '\n'.join(lines)
  99 +
  100 +def h2(match):
  101 + """bold red, extra leading space"""
  102 + text = match.group(1)
  103 + text = '\n' + bold(red('## ' + text))
  104 + return text
  105 +
  106 +def h3(match):
  107 + """bold red"""
  108 + text = match.group(1)
  109 + text = '\n' + bold(red('### ' + text))
  110 + return text
  111 +
  112 +def a(match):
  113 + """blue"""
  114 + text = match.group(1)
  115 + lines = [
  116 + '\033[34m' + line + '\033[39m'
  117 + for line in text.splitlines(False)
  118 + ]
  119 + return '\n'.join(lines)
  120 +
  121 +def em(match):
  122 + """cyan"""
  123 + text = match.group(1)
  124 + lines = [cyan(line) for line in text.splitlines(False)]
  125 + return cyan('*') + '\n'.join(lines) + cyan('*')
  126 +
  127 +def strong(match):
  128 + """bold cyan"""
  129 + text = match.group(1)
  130 + lines = [bold(cyan(line)) for line in text.splitlines(False)]
  131 + return bold(cyan('**')) + '\n'.join(lines) + bold(cyan('**'))
  132 +
  133 +def li(match):
  134 + """bullet and indent and reflow"""
  135 + text = match.group(1)
  136 + text = '\n'.join(textwrap.wrap(text, width=78))
  137 + INDENT = ' '
  138 + text = INDENT + INDENT.join(text.splitlines(True))
  139 + text = '-' + text[1:]
  140 + return text
  141 +
  142 +def noop(match):
  143 + return match.group(1)
  144 +
  145 +
  146 +
  147 +#---- main nodedoc functionality
  148 +
  149 +def generate_html_path(markdown_path, html_path):
  150 + if not exists(dirname(html_path)):
  151 + os.makedirs(dirname(html_path))
  152 + html = markdown2.markdown_path(markdown_path)
  153 + codecs.open(html_path, 'w', 'utf-8').write(html)
  154 +
  155 +def generate_nodedoc_path(html_path, nodedoc_path):
  156 + if not exists(dirname(nodedoc_path)):
  157 + os.makedirs(dirname(nodedoc_path))
  158 +
  159 + content = codecs.open(html_path, 'r', 'utf-8').read()
  160 +
  161 + # html comments: drop
  162 + content = re.compile('\n?<!--(.*?)-->\n', re.S).sub('', content)
  163 +
  164 + # code:
  165 + content = re.compile('<code>(.*?)</code>', re.S).sub(code, content)
  166 +
  167 + # pre: indent
  168 + content = re.compile('<pre>(.*?)</pre>', re.S).sub(indent, content)
  169 +
  170 + # li: bullet
  171 + # XXX how to know in ol? AFAICT only one <ol> in node.js docs, so ignoring for now.
  172 + # XXX does this mess up multi-para li?
  173 + content = re.compile('<li>(?:<p>)?(.*?)(?:</p>)?</li>', re.S).sub(li, content)
  174 + # ol, ul: ignore
  175 + content = re.compile('\n?<ul>(.*?)</ul>\n', re.S).sub(noop, content)
  176 + content = re.compile('\n?<ol>(.*?)</ol>\n', re.S).sub(noop, content)
  177 +
  178 + # p: wrap content at 80 columns
  179 + content = re.compile('<p>(.*?)</p>', re.S).sub(wrap, content)
  180 +
  181 + # a: drop attrs (until/unless have a way to follow those links)
  182 + content = re.compile('<a[^>]*>(.*?)</a>', re.S).sub(a, content)
  183 +
  184 + content = re.compile('<em>(.*?)</em>', re.S).sub(em, content)
  185 + content = re.compile('<strong>(.*?)</strong>', re.S).sub(strong, content)
  186 +
  187 + # hN: highlight, but how to highlight different levels?
  188 + content = re.compile('<h1>(.*?)</h1>', re.S).sub(h1, content)
  189 + content = re.compile('<h2>(.*?)</h2>', re.S).sub(h2, content)
  190 + content = re.compile('<h3>(.*?)</h3>', re.S).sub(h3, content)
  191 +
  192 + #TODO:XXX special case two adjacent h2's, e.g.:
  193 + #
  194 + # <h2>fs.utimes(path, atime, mtime, [callback])</h2>
  195 + #
  196 + # <h2>fs.utimesSync(path, atime, mtime)</h2>
  197 + #
  198 + # <p>Change file timestamps of the file referenced by the supplied path.</p>
  199 +
  200 + codecs.open(nodedoc_path, 'w', 'utf-8').write(content)
  201 +
  202 +
  203 +def nodedoc(section):
  204 + markdown_path = join(TOP, "doc", "api", section + ".markdown")
  205 + if not exists(markdown_path):
  206 + raise Error("no such section: '%s'" % section)
  207 +
  208 + html_path = join(CACHE_DIR, section + ".html")
  209 + if not exists(html_path) or mtime(html_path) < mtime(markdown_path):
  210 + generate_html_path(markdown_path, html_path)
  211 +
  212 + nodedoc_path = join(CACHE_DIR, section + ".nodedoc")
  213 + if not exists(nodedoc_path) or mtime(nodedoc_path) < mtime(html_path):
  214 + generate_nodedoc_path(html_path, nodedoc_path)
  215 +
  216 + pager = os.environ.get("PAGER", "less")
  217 + cmd = '%s "%s"' % (pager, nodedoc_path)
  218 + return os.system(cmd)
  219 +
  220 +def nodedoc_sections():
  221 + markdown_paths = glob(join(TOP, "doc", "api", "*.markdown"))
  222 + for p in markdown_paths:
  223 + yield os.path.splitext(os.path.basename(p))[0]
  224 +
  225 +
  226 +
  227 +#---- other internal support stuff
  228 +
  229 +class _LowerLevelNameFormatter(logging.Formatter):
  230 + def format(self, record):
  231 + record.lowerlevelname = record.levelname.lower()
  232 + return logging.Formatter.format(self, record)
  233 +
  234 +def _setup_logging():
  235 + hdlr = logging.StreamHandler(sys.stdout)
  236 + fmt = "%(name)s: %(lowerlevelname)s: %(message)s"
  237 + fmtr = _LowerLevelNameFormatter(fmt=fmt)
  238 + hdlr.setFormatter(fmtr)
  239 + logging.root.addHandler(hdlr)
  240 +
  241 +class _NoReflowFormatter(optparse.IndentedHelpFormatter):
  242 + """An optparse formatter that does NOT reflow the description."""
  243 + def format_description(self, description):
  244 + return description or ""
  245 +
  246 +def mtime(path):
  247 + return os.stat(path).st_mtime
  248 +
  249 +
  250 +
  251 +#---- mainline
  252 +
  253 +def main(argv=sys.argv):
  254 + _setup_logging()
  255 + log.setLevel(logging.INFO)
  256 +
  257 + # Parse options.
  258 + parser = optparse.OptionParser(prog="nodedoc", usage='',
  259 + version="%prog " + __version__, description=__doc__,
  260 + formatter=_NoReflowFormatter())
  261 + parser.add_option("-v", "--verbose", dest="log_level",
  262 + action="store_const", const=logging.DEBUG,
  263 + help="more verbose output")
  264 + parser.add_option("-q", "--quiet", dest="log_level",
  265 + action="store_const", const=logging.WARNING,
  266 + help="quieter output (just warnings and errors)")
  267 + parser.add_option("-l", "--list", action="store_true",
  268 + help="list all nodedoc sections")
  269 + parser.set_defaults(log_level=logging.INFO)
  270 + opts, sections = parser.parse_args()
  271 + log.setLevel(opts.log_level)
  272 +
  273 + if opts.list:
  274 + print '\n'.join(nodedoc_sections())
  275 + elif len(sections) == 0:
  276 + parser.print_help()
  277 + elif len(sections) > 1:
  278 + log.error("too many arguments: %s", ' '.join(sections))
  279 + else:
  280 + return nodedoc(sections[0])
  281 +
  282 +
  283 +## {{{ http://code.activestate.com/recipes/577258/ (r4)
  284 +if __name__ == "__main__":
  285 + try:
  286 + retval = main(sys.argv)
  287 + except KeyboardInterrupt:
  288 + sys.exit(1)
  289 + except SystemExit:
  290 + raise
  291 + except:
  292 + import traceback, logging
  293 + if not log.handlers and not logging.root.handlers:
  294 + logging.basicConfig()
  295 + skip_it = False
  296 + exc_info = sys.exc_info()
  297 + if hasattr(exc_info[0], "__name__"):
  298 + exc_class, exc, tb = exc_info
  299 + if isinstance(exc, IOError) and exc.args[0] == 32:
  300 + # Skip 'IOError: [Errno 32] Broken pipe': often a cancelling of `less`.
  301 + skip_it = True
  302 + if not skip_it:
  303 + tb_path, tb_lineno, tb_func = traceback.extract_tb(tb)[-1][:3]
  304 + log.error("%s (%s:%s in %s)", exc_info[1], tb_path,
  305 + tb_lineno, tb_func)
  306 + else: # string exception
  307 + log.error(exc_info[0])
  308 + if not skip_it:
  309 + if log.isEnabledFor(logging.DEBUG):
  310 + print()
  311 + traceback.print_exception(*exc_info)
  312 + sys.exit(1)
  313 + else:
  314 + sys.exit(retval)
  315 +## end of http://code.activestate.com/recipes/577258/ }}}
2  deps/README.md
Source Rendered
... ... @@ -0,0 +1,2 @@
  1 +markdown2.py # from https://github.com/trentm/python-markdown2
  2 +appdirs.py # from https://github.com/ActiveState/appdirs
346 deps/appdirs.py
... ... @@ -0,0 +1,346 @@
  1 +#!/usr/bin/env python
  2 +# Copyright (c) 2005-2010 ActiveState Software Inc.
  3 +
  4 +"""Utilities for determining application-specific dirs.
  5 +
  6 +See <http://github.com/ActiveState/appdirs> for details and usage.
  7 +"""
  8 +# Dev Notes:
  9 +# - MSDN on where to store app data files:
  10 +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
  11 +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
  12 +# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
  13 +
  14 +__version_info__ = (1, 2, 0)
  15 +__version__ = '.'.join(map(str, __version_info__))
  16 +
  17 +
  18 +import sys
  19 +import os
  20 +
  21 +PY3 = sys.version_info[0] == 3
  22 +
  23 +if PY3:
  24 + unicode = str
  25 +
  26 +class AppDirsError(Exception):
  27 + pass
  28 +
  29 +
  30 +
  31 +def user_data_dir(appname, appauthor=None, version=None, roaming=False):
  32 + r"""Return full path to the user-specific data dir for this application.
  33 +
  34 + "appname" is the name of application.
  35 + "appauthor" (only required and used on Windows) is the name of the
  36 + appauthor or distributing body for this application. Typically
  37 + it is the owning company name.
  38 + "version" is an optional version path element to append to the
  39 + path. You might want to use this if you want multiple versions
  40 + of your app to be able to run independently. If used, this
  41 + would typically be "<major>.<minor>".
  42 + "roaming" (boolean, default False) can be set True to use the Windows
  43 + roaming appdata directory. That means that for users on a Windows
  44 + network setup for roaming profiles, this user data will be
  45 + sync'd on login. See
  46 + <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
  47 + for a discussion of issues.
  48 +
  49 + Typical user data directories are:
  50 + Mac OS X: ~/Library/Application Support/<AppName>
  51 + Unix: ~/.config/<appname> # or in $XDG_CONFIG_HOME if defined
  52 + Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
  53 + Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
  54 + Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
  55 + Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
  56 +
  57 + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. We don't
  58 + use $XDG_DATA_HOME as that data dir is mostly used at the time of
  59 + installation, instead of the application adding data during runtime.
  60 + Also, in practice, Linux apps tend to store their data in
  61 + "~/.config/<appname>" instead of "~/.local/share/<appname>".
  62 + """
  63 + if sys.platform.startswith("win"):
  64 + if appauthor is None:
  65 + raise AppDirsError("must specify 'appauthor' on Windows")
  66 + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
  67 + path = os.path.join(_get_win_folder(const), appauthor, appname)
  68 + elif sys.platform == 'darwin':
  69 + path = os.path.join(
  70 + os.path.expanduser('~/Library/Application Support/'),
  71 + appname)
  72 + else:
  73 + path = os.path.join(
  74 + os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")),
  75 + appname.lower())
  76 + if version:
  77 + path = os.path.join(path, version)
  78 + return path
  79 +
  80 +
  81 +def site_data_dir(appname, appauthor=None, version=None):
  82 + """Return full path to the user-shared data dir for this application.
  83 +
  84 + "appname" is the name of application.
  85 + "appauthor" (only required and used on Windows) is the name of the
  86 + appauthor or distributing body for this application. Typically
  87 + it is the owning company name.
  88 + "version" is an optional version path element to append to the
  89 + path. You might want to use this if you want multiple versions
  90 + of your app to be able to run independently. If used, this
  91 + would typically be "<major>.<minor>".
  92 +
  93 + Typical user data directories are:
  94 + Mac OS X: /Library/Application Support/<AppName>
  95 + Unix: /etc/xdg/<appname>
  96 + Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
  97 + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
  98 + Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
  99 +
  100 + For Unix, this is using the $XDG_CONFIG_DIRS[0] default.
  101 +
  102 + WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
  103 + """
  104 + if sys.platform.startswith("win"):
  105 + if appauthor is None:
  106 + raise AppDirsError("must specify 'appauthor' on Windows")
  107 + path = os.path.join(_get_win_folder("CSIDL_COMMON_APPDATA"),
  108 + appauthor, appname)
  109 + elif sys.platform == 'darwin':
  110 + path = os.path.join(
  111 + os.path.expanduser('/Library/Application Support'),
  112 + appname)
  113 + else:
  114 + # XDG default for $XDG_CONFIG_DIRS[0]. Perhaps should actually
  115 + # *use* that envvar, if defined.
  116 + path = "/etc/xdg/"+appname.lower()
  117 + if version:
  118 + path = os.path.join(path, version)
  119 + return path
  120 +
  121 +
  122 +def user_cache_dir(appname, appauthor=None, version=None, opinion=True):
  123 + r"""Return full path to the user-specific cache dir for this application.
  124 +
  125 + "appname" is the name of application.
  126 + "appauthor" (only required and used on Windows) is the name of the
  127 + appauthor or distributing body for this application. Typically
  128 + it is the owning company name.
  129 + "version" is an optional version path element to append to the
  130 + path. You might want to use this if you want multiple versions
  131 + of your app to be able to run independently. If used, this
  132 + would typically be "<major>.<minor>".
  133 + "opinion" (boolean) can be False to disable the appending of
  134 + "Cache" to the base app data dir for Windows. See
  135 + discussion below.
  136 +
  137 + Typical user cache directories are:
  138 + Mac OS X: ~/Library/Caches/<AppName>
  139 + Unix: ~/.cache/<appname> (XDG default)
  140 + Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
  141 + Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
  142 +
  143 + On Windows the only suggestion in the MSDN docs is that local settings go in
  144 + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
  145 + app data dir (the default returned by `user_data_dir` above). Apps typically
  146 + put cache data somewhere *under* the given dir here. Some examples:
  147 + ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
  148 + ...\Acme\SuperApp\Cache\1.0
  149 + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
  150 + This can be disabled with the `opinion=False` option.
  151 + """
  152 + if sys.platform.startswith("win"):
  153 + if appauthor is None:
  154 + raise AppDirsError("must specify 'appauthor' on Windows")
  155 + path = os.path.join(_get_win_folder("CSIDL_LOCAL_APPDATA"),
  156 + appauthor, appname)
  157 + if opinion:
  158 + path = os.path.join(path, "Cache")
  159 + elif sys.platform == 'darwin':
  160 + path = os.path.join(
  161 + os.path.expanduser('~/Library/Caches'),
  162 + appname)
  163 + else:
  164 + path = os.path.join(
  165 + os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')),
  166 + appname.lower())
  167 + if version:
  168 + path = os.path.join(path, version)
  169 + return path
  170 +
  171 +def user_log_dir(appname, appauthor=None, version=None, opinion=True):
  172 + r"""Return full path to the user-specific log dir for this application.
  173 +
  174 + "appname" is the name of application.
  175 + "appauthor" (only required and used on Windows) is the name of the
  176 + appauthor or distributing body for this application. Typically
  177 + it is the owning company name.
  178 + "version" is an optional version path element to append to the
  179 + path. You might want to use this if you want multiple versions
  180 + of your app to be able to run independently. If used, this
  181 + would typically be "<major>.<minor>".
  182 + "opinion" (boolean) can be False to disable the appending of
  183 + "Logs" to the base app data dir for Windows, and "log" to the
  184 + base cache dir for Unix. See discussion below.
  185 +
  186 + Typical user cache directories are:
  187 + Mac OS X: ~/Library/Logs/<AppName>
  188 + Unix: ~/.cache/<appname>/log # or under $XDG_CACHE_HOME if defined
  189 + Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
  190 + Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
  191 +
  192 + On Windows the only suggestion in the MSDN docs is that local settings
  193 + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
  194 + examples of what some windows apps use for a logs dir.)
  195 +
  196 + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
  197 + value for Windows and appends "log" to the user cache dir for Unix.
  198 + This can be disabled with the `opinion=False` option.
  199 + """
  200 + if sys.platform == "darwin":
  201 + path = os.path.join(
  202 + os.path.expanduser('~/Library/Logs'),
  203 + appname)
  204 + elif sys.platform == "win32":
  205 + path = user_data_dir(appname, appauthor, version); version=False
  206 + if opinion:
  207 + path = os.path.join(path, "Logs")
  208 + else:
  209 + path = user_cache_dir(appname, appauthor, version); version=False
  210 + if opinion:
  211 + path = os.path.join(path, "log")
  212 + if version:
  213 + path = os.path.join(path, version)
  214 + return path
  215 +
  216 +
  217 +class AppDirs(object):
  218 + """Convenience wrapper for getting application dirs."""
  219 + def __init__(self, appname, appauthor, version=None, roaming=False):
  220 + self.appname = appname
  221 + self.appauthor = appauthor
  222 + self.version = version
  223 + self.roaming = roaming
  224 + @property
  225 + def user_data_dir(self):
  226 + return user_data_dir(self.appname, self.appauthor,
  227 + version=self.version, roaming=self.roaming)
  228 + @property
  229 + def site_data_dir(self):
  230 + return site_data_dir(self.appname, self.appauthor,
  231 + version=self.version)
  232 + @property
  233 + def user_cache_dir(self):
  234 + return user_cache_dir(self.appname, self.appauthor,
  235 + version=self.version)
  236 + @property
  237 + def user_log_dir(self):
  238 + return user_log_dir(self.appname, self.appauthor,
  239 + version=self.version)
  240 +
  241 +
  242 +
  243 +
  244 +#---- internal support stuff
  245 +
  246 +def _get_win_folder_from_registry(csidl_name):
  247 + """This is a fallback technique at best. I'm not sure if using the
  248 + registry for this guarantees us the correct answer for all CSIDL_*
  249 + names.
  250 + """
  251 + import _winreg
  252 +
  253 + shell_folder_name = {
  254 + "CSIDL_APPDATA": "AppData",
  255 + "CSIDL_COMMON_APPDATA": "Common AppData",
  256 + "CSIDL_LOCAL_APPDATA": "Local AppData",
  257 + }[csidl_name]
  258 +
  259 + key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
  260 + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
  261 + dir, type = _winreg.QueryValueEx(key, shell_folder_name)
  262 + return dir
  263 +
  264 +def _get_win_folder_with_pywin32(csidl_name):
  265 + from win32com.shell import shellcon, shell
  266 + dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
  267 + # Try to make this a unicode path because SHGetFolderPath does
  268 + # not return unicode strings when there is unicode data in the
  269 + # path.
  270 + try:
  271 + dir = unicode(dir)
  272 +
  273 + # Downgrade to short path name if have highbit chars. See
  274 + # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
  275 + has_high_char = False
  276 + for c in dir:
  277 + if ord(c) > 255:
  278 + has_high_char = True
  279 + break
  280 + if has_high_char:
  281 + try:
  282 + import win32api
  283 + dir = win32api.GetShortPathName(dir)
  284 + except ImportError:
  285 + pass
  286 + except UnicodeError:
  287 + pass
  288 + return dir
  289 +
  290 +def _get_win_folder_with_ctypes(csidl_name):
  291 + import ctypes
  292 +
  293 + csidl_const = {
  294 + "CSIDL_APPDATA": 26,
  295 + "CSIDL_COMMON_APPDATA": 35,
  296 + "CSIDL_LOCAL_APPDATA": 28,
  297 + }[csidl_name]
  298 +
  299 + buf = ctypes.create_unicode_buffer(1024)
  300 + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
  301 +
  302 + # Downgrade to short path name if have highbit chars. See
  303 + # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
  304 + has_high_char = False
  305 + for c in buf:
  306 + if ord(c) > 255:
  307 + has_high_char = True
  308 + break
  309 + if has_high_char:
  310 + buf2 = ctypes.create_unicode_buffer(1024)
  311 + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
  312 + buf = buf2
  313 +
  314 + return buf.value
  315 +
  316 +if sys.platform == "win32":
  317 + try:
  318 + import win32com.shell
  319 + _get_win_folder = _get_win_folder_with_pywin32
  320 + except ImportError:
  321 + try:
  322 + import ctypes
  323 + _get_win_folder = _get_win_folder_with_ctypes
  324 + except ImportError:
  325 + _get_win_folder = _get_win_folder_from_registry
  326 +
  327 +
  328 +
  329 +#---- self test code
  330 +
  331 +if __name__ == "__main__":
  332 + appname = "MyApp"
  333 + appauthor = "MyCompany"
  334 +
  335 + props = ("user_data_dir", "site_data_dir", "user_cache_dir",
  336 + "user_log_dir")
  337 +
  338 + print("-- app dirs (without optional 'version')")
  339 + dirs = AppDirs(appname, appauthor, version="1.0")
  340 + for prop in props:
  341 + print("%s: %s" % (prop, getattr(dirs, prop)))
  342 +
  343 + print("\n-- app dirs (with optional 'version')")
  344 + dirs = AppDirs(appname, appauthor)
  345 + for prop in props:
  346 + print("%s: %s" % (prop, getattr(dirs, prop)))
BIN  deps/appdirs.pyc
Binary file not shown
2,299 deps/markdown2.py
... ... @@ -0,0 +1,2299 @@
  1 +#!/usr/bin/env python
  2 +# Copyright (c) 2012 Trent Mick.
  3 +# Copyright (c) 2007-2008 ActiveState Corp.
  4 +# License: MIT (http://www.opensource.org/licenses/mit-license.php)
  5 +
  6 +from __future__ import generators
  7 +
  8 +r"""A fast and complete Python implementation of Markdown.
  9 +
  10 +[from http://daringfireball.net/projects/markdown/]
  11 +> Markdown is a text-to-HTML filter; it translates an easy-to-read /
  12 +> easy-to-write structured text format into HTML. Markdown's text
  13 +> format is most similar to that of plain text email, and supports
  14 +> features such as headers, *emphasis*, code blocks, blockquotes, and
  15 +> links.
  16 +>
  17 +> Markdown's syntax is designed not as a generic markup language, but
  18 +> specifically to serve as a front-end to (X)HTML. You can use span-level
  19 +> HTML tags anywhere in a Markdown document, and you can use block level
  20 +> HTML tags (like <div> and <table> as well).
  21 +
  22 +Module usage:
  23 +
  24 + >>> import markdown2
  25 + >>> markdown2.markdown("*boo!*") # or use `html = markdown_path(PATH)`
  26 + u'<p><em>boo!</em></p>\n'
  27 +
  28 + >>> markdowner = Markdown()
  29 + >>> markdowner.convert("*boo!*")
  30 + u'<p><em>boo!</em></p>\n'
  31 + >>> markdowner.convert("**boom!**")
  32 + u'<p><strong>boom!</strong></p>\n'
  33 +
  34 +This implementation of Markdown implements the full "core" syntax plus a
  35 +number of extras (e.g., code syntax coloring, footnotes) as described on
  36 +<https://github.com/trentm/python-markdown2/wiki/Extras>.
  37 +"""
  38 +
  39 +cmdln_desc = """A fast and complete Python implementation of Markdown, a
  40 +text-to-HTML conversion tool for web writers.
  41 +
  42 +Supported extra syntax options (see -x|--extras option below and
  43 +see <https://github.com/trentm/python-markdown2/wiki/Extras> for details):
  44 +
  45 +* code-friendly: Disable _ and __ for em and strong.
  46 +* cuddled-lists: Allow lists to be cuddled to the preceding paragraph.
  47 +* fenced-code-blocks: Allows a code block to not have to be indented
  48 + by fencing it with '```' on a line before and after. Based on
  49 + <http://github.github.com/github-flavored-markdown/> with support for
  50 + syntax highlighting.
  51 +* footnotes: Support footnotes as in use on daringfireball.net and
  52 + implemented in other Markdown processors (tho not in Markdown.pl v1.0.1).
  53 +* header-ids: Adds "id" attributes to headers. The id value is a slug of
  54 + the header text.
  55 +* html-classes: Takes a dict mapping html tag names (lowercase) to a
  56 + string to use for a "class" tag attribute. Currently only supports
  57 + "pre" and "code" tags. Add an issue if you require this for other tags.
  58 +* markdown-in-html: Allow the use of `markdown="1"` in a block HTML tag to
  59 + have markdown processing be done on its contents. Similar to
  60 + <http://michelf.com/projects/php-markdown/extra/#markdown-attr> but with
  61 + some limitations.
  62 +* metadata: Extract metadata from a leading '---'-fenced block.
  63 + See <https://github.com/trentm/python-markdown2/issues/77> for details.
  64 +* pyshell: Treats unindented Python interactive shell sessions as <code>
  65 + blocks.
  66 +* link-patterns: Auto-link given regex patterns in text (e.g. bug number
  67 + references, revision number references).
  68 +* smarty-pants: Replaces ' and " with curly quotation marks or curly
  69 + apostrophes. Replaces --, ---, ..., and . . . with en dashes, em dashes,
  70 + and ellipses.
  71 +* toc: The returned HTML string gets a new "toc_html" attribute which is
  72 + a Table of Contents for the document. (experimental)
  73 +* xml: Passes one-liner processing instructions and namespaced XML tags.
  74 +* wiki-tables: Google Code Wiki-style tables. See
  75 + <http://code.google.com/p/support/wiki/WikiSyntax#Tables>.
  76 +"""
  77 +
  78 +# Dev Notes:
  79 +# - Python's regex syntax doesn't have '\z', so I'm using '\Z'. I'm
  80 +# not yet sure if there implications with this. Compare 'pydoc sre'
  81 +# and 'perldoc perlre'.
  82 +
  83 +__version_info__ = (1, 4, 3)
  84 +__version__ = '.'.join(map(str, __version_info__))
  85 +__author__ = "Trent Mick"
  86 +
  87 +import os
  88 +import sys
  89 +from pprint import pprint
  90 +import re
  91 +import logging
  92 +try:
  93 + from hashlib import md5
  94 +except ImportError:
  95 + from md5 import md5
  96 +import optparse
  97 +from random import random, randint
  98 +import codecs
  99 +
  100 +
  101 +#---- Python version compat
  102 +
  103 +try:
  104 + from urllib.parse import quote # python3
  105 +except ImportError:
  106 + from urllib import quote # python2
  107 +
  108 +if sys.version_info[:2] < (2,4):
  109 + from sets import Set as set
  110 + def reversed(sequence):
  111 + for i in sequence[::-1]:
  112 + yield i
  113 +
  114 +# Use `bytes` for byte strings and `unicode` for unicode strings (str in Py3).
  115 +if sys.version_info[0] <= 2:
  116 + py3 = False
  117 + try:
  118 + bytes
  119 + except NameError:
  120 + bytes = str
  121 + base_string_type = basestring
  122 +elif sys.version_info[0] >= 3:
  123 + py3 = True
  124 + unicode = str
  125 + base_string_type = str
  126 +
  127 +
  128 +
  129 +#---- globals
  130 +
  131 +DEBUG = False
  132 +log = logging.getLogger("markdown")
  133 +
  134 +DEFAULT_TAB_WIDTH = 4
  135 +
  136 +
  137 +SECRET_SALT = bytes(randint(0, 1000000))
  138 +def _hash_text(s):
  139 + return 'md5-' + md5(SECRET_SALT + s.encode("utf-8")).hexdigest()
  140 +
  141 +# Table of hash values for escaped characters:
  142 +g_escape_table = dict([(ch, _hash_text(ch))
  143 + for ch in '\\`*_{}[]()>#+-.!'])
  144 +
  145 +
  146 +
  147 +#---- exceptions
  148 +
  149 +class MarkdownError(Exception):
  150 + pass
  151 +
  152 +
  153 +
  154 +#---- public api
  155 +
  156 +def markdown_path(path, encoding="utf-8",
  157 + html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
  158 + safe_mode=None, extras=None, link_patterns=None,
  159 + use_file_vars=False):
  160 + fp = codecs.open(path, 'r', encoding)
  161 + text = fp.read()
  162 + fp.close()
  163 + return Markdown(html4tags=html4tags, tab_width=tab_width,
  164 + safe_mode=safe_mode, extras=extras,
  165 + link_patterns=link_patterns,
  166 + use_file_vars=use_file_vars).convert(text)
  167 +
  168 +def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
  169 + safe_mode=None, extras=None, link_patterns=None,
  170 + use_file_vars=False):
  171 + return Markdown(html4tags=html4tags, tab_width=tab_width,
  172 + safe_mode=safe_mode, extras=extras,
  173 + link_patterns=link_patterns,
  174 + use_file_vars=use_file_vars).convert(text)
  175 +
  176 +class Markdown(object):
  177 + # The dict of "extras" to enable in processing -- a mapping of
  178 + # extra name to argument for the extra. Most extras do not have an
  179 + # argument, in which case the value is None.
  180 + #
  181 + # This can be set via (a) subclassing and (b) the constructor
  182 + # "extras" argument.
  183 + extras = None
  184 +
  185 + urls = None
  186 + titles = None
  187 + html_blocks = None
  188 + html_spans = None
  189 + html_removed_text = "[HTML_REMOVED]" # for compat with markdown.py
  190 +
  191 + # Used to track when we're inside an ordered or unordered list
  192 + # (see _ProcessListItems() for details):
  193 + list_level = 0
  194 +
  195 + _ws_only_line_re = re.compile(r"^[ \t]+$", re.M)
  196 +
  197 + def __init__(self, html4tags=False, tab_width=4, safe_mode=None,
  198 + extras=None, link_patterns=None, use_file_vars=False):
  199 + if html4tags:
  200 + self.empty_element_suffix = ">"
  201 + else:
  202 + self.empty_element_suffix = " />"
  203 + self.tab_width = tab_width
  204 +
  205 + # For compatibility with earlier markdown2.py and with
  206 + # markdown.py's safe_mode being a boolean,
  207 + # safe_mode == True -> "replace"
  208 + if safe_mode is True:
  209 + self.safe_mode = "replace"
  210 + else:
  211 + self.safe_mode = safe_mode
  212 +
  213 + # Massaging and building the "extras" info.
  214 + if self.extras is None:
  215 + self.extras = {}
  216 + elif not isinstance(self.extras, dict):
  217 + self.extras = dict([(e, None) for e in self.extras])
  218 + if extras:
  219 + if not isinstance(extras, dict):
  220 + extras = dict([(e, None) for e in extras])
  221 + self.extras.update(extras)
  222 + assert isinstance(self.extras, dict)
  223 + if "toc" in self.extras and not "header-ids" in self.extras:
  224 + self.extras["header-ids"] = None # "toc" implies "header-ids"
  225 + self._instance_extras = self.extras.copy()
  226 +
  227 + self.link_patterns = link_patterns
  228 + self.use_file_vars = use_file_vars
  229 + self._outdent_re = re.compile(r'^(\t|[ ]{1,%d})' % tab_width, re.M)
  230 +
  231 + self._escape_table = g_escape_table.copy()
  232 + if "smarty-pants" in self.extras:
  233 + self._escape_table['"'] = _hash_text('"')
  234 + self._escape_table["'"] = _hash_text("'")
  235 +
  236 + def reset(self):
  237 + self.urls = {}
  238 + self.titles = {}
  239 + self.html_blocks = {}
  240 + self.html_spans = {}
  241 + self.list_level = 0
  242 + self.extras = self._instance_extras.copy()
  243 + if "footnotes" in self.extras:
  244 + self.footnotes = {}
  245 + self.footnote_ids = []
  246 + if "header-ids" in self.extras:
  247 + self._count_from_header_id = {} # no `defaultdict` in Python 2.4
  248 + if "metadata" in self.extras:
  249 + self.metadata = {}
  250 +
  251 + def convert(self, text):
  252 + """Convert the given text."""
  253 + # Main function. The order in which other subs are called here is
  254 + # essential. Link and image substitutions need to happen before
  255 + # _EscapeSpecialChars(), so that any *'s or _'s in the <a>
  256 + # and <img> tags get encoded.
  257 +
  258 + # Clear the global hashes. If we don't clear these, you get conflicts
  259 + # from other articles when generating a page which contains more than
  260 + # one article (e.g. an index page that shows the N most recent
  261 + # articles):
  262 + self.reset()
  263 +
  264 + if not isinstance(text, unicode):
  265 + #TODO: perhaps shouldn't presume UTF-8 for string input?
  266 + text = unicode(text, 'utf-8')
  267 +
  268 + if self.use_file_vars:
  269 + # Look for emacs-style file variable hints.
  270 + emacs_vars = self._get_emacs_vars(text)
  271 + if "markdown-extras" in emacs_vars:
  272 + splitter = re.compile("[ ,]+")
  273 + for e in splitter.split(emacs_vars["markdown-extras"]):
  274 + if '=' in e:
  275 + ename, earg = e.split('=', 1)
  276 + try:
  277 + earg = int(earg)
  278 + except ValueError:
  279 + pass
  280 + else:
  281 + ename, earg = e, None
  282 + self.extras[ename] = earg
  283 +
  284 + # Standardize line endings:
  285 + text = re.sub("\r\n|\r", "\n", text)
  286 +
  287 + # Make sure $text ends with a couple of newlines:
  288 + text += "\n\n"
  289 +
  290 + # Convert all tabs to spaces.
  291 + text = self._detab(text)
  292 +
  293 + # Strip any lines consisting only of spaces and tabs.
  294 + # This makes subsequent regexen easier to write, because we can
  295 + # match consecutive blank lines with /\n+/ instead of something
  296 + # contorted like /[ \t]*\n+/ .
  297 + text = self._ws_only_line_re.sub("", text)
  298 +
  299 + # strip metadata from head and extract
  300 + if "metadata" in self.extras:
  301 + text = self._extract_metadata(text)
  302 +
  303 + if self.safe_mode:
  304 + text = self._hash_html_spans(text)
  305 +
  306 + # Turn block-level HTML blocks into hash entries
  307 + text = self._hash_html_blocks(text, raw=True)
  308 +
  309 + # Strip link definitions, store in hashes.
  310 + if "footnotes" in self.extras:
  311 + # Must do footnotes first because an unlucky footnote defn
  312 + # looks like a link defn:
  313 + # [^4]: this "looks like a link defn"
  314 + text = self._strip_footnote_definitions(text)
  315 + text = self._strip_link_definitions(text)
  316 +
  317 + text = self._run_block_gamut(text)
  318 +
  319 + if "footnotes" in self.extras:
  320 + text = self._add_footnotes(text)
  321 +
  322 + text = self.postprocess(text)
  323 +
  324 + text = self._unescape_special_chars(text)
  325 +
  326 + if self.safe_mode:
  327 + text = self._unhash_html_spans(text)
  328 +
  329 + text += "\n"
  330 +
  331 + rv = UnicodeWithAttrs(text)
  332 + if "toc" in self.extras:
  333 + rv._toc = self._toc
  334 + if "metadata" in self.extras:
  335 + rv.metadata = self.metadata
  336 + return rv
  337 +
  338 + def postprocess(self, text):
  339 + """A hook for subclasses to do some postprocessing of the html, if
  340 + desired. This is called before unescaping of special chars and
  341 + unhashing of raw HTML spans.
  342 + """
  343 + return text
  344 +
  345 + # Is metadata if the content starts with '---'-fenced `key: value`
  346 + # pairs. E.g. (indented for presentation):
  347 + # ---
  348 + # foo: bar
  349 + # another-var: blah blah
  350 + # ---
  351 + _metadata_pat = re.compile("""^---[ \t]*\n((?:[ \t]*[^ \t:]+[ \t]*:[^\n]*\n)+)---[ \t]*\n""")
  352 +
  353 + def _extract_metadata(self, text):
  354 + # fast test
  355 + if not text.startswith("---"):
  356 + return text
  357 + match = self._metadata_pat.match(text)
  358 + if not match:
  359 + return text
  360 +
  361 + tail = text[len(match.group(0)):]
  362 + metadata_str = match.group(1).strip()
  363 + for line in metadata_str.split('\n'):
  364 + key, value = line.split(':', 1)
  365 + self.metadata[key.strip()] = value.strip()
  366 +
  367 + return tail
  368 +
  369 +
  370 + _emacs_oneliner_vars_pat = re.compile(r"-\*-\s*([^\r\n]*?)\s*-\*-", re.UNICODE)
  371 + # This regular expression is intended to match blocks like this:
  372 + # PREFIX Local Variables: SUFFIX
  373 + # PREFIX mode: Tcl SUFFIX
  374 + # PREFIX End: SUFFIX
  375 + # Some notes:
  376 + # - "[ \t]" is used instead of "\s" to specifically exclude newlines
  377 + # - "(\r\n|\n|\r)" is used instead of "$" because the sre engine does
  378 + # not like anything other than Unix-style line terminators.
  379 + _emacs_local_vars_pat = re.compile(r"""^
  380 + (?P<prefix>(?:[^\r\n|\n|\r])*?)
  381 + [\ \t]*Local\ Variables:[\ \t]*
  382 + (?P<suffix>.*?)(?:\r\n|\n|\r)
  383 + (?P<content>.*?\1End:)
  384 + """, re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE)
  385 +
  386 + def _get_emacs_vars(self, text):
  387 + """Return a dictionary of emacs-style local variables.
  388 +
  389 + Parsing is done loosely according to this spec (and according to
  390 + some in-practice deviations from this):
  391 + http://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html#Specifying-File-Variables
  392 + """
  393 + emacs_vars = {}
  394 + SIZE = pow(2, 13) # 8kB
  395 +
  396 + # Search near the start for a '-*-'-style one-liner of variables.
  397 + head = text[:SIZE]
  398 + if "-*-" in head:
  399 + match = self._emacs_oneliner_vars_pat.search(head)
  400 + if match:
  401 + emacs_vars_str = match.group(1)
  402 + assert '\n' not in emacs_vars_str
  403 + emacs_var_strs = [s.strip() for s in emacs_vars_str.split(';')
  404 + if s.strip()]
  405 + if len(emacs_var_strs) == 1 and ':' not in emacs_var_strs[0]:
  406 + # While not in the spec, this form is allowed by emacs:
  407 + # -*- Tcl -*-
  408 + # where the implied "variable" is "mode". This form
  409 + # is only allowed if there are no other variables.
  410 + emacs_vars["mode"] = emacs_var_strs[0].strip()
  411 + else:
  412 + for emacs_var_str in emacs_var_strs:
  413 + try:
  414 + variable, value = emacs_var_str.strip().split(':', 1)
  415 + except ValueError:
  416 + log.debug("emacs variables error: malformed -*- "
  417 + "line: %r", emacs_var_str)
  418 + continue
  419 + # Lowercase the variable name because Emacs allows "Mode"
  420 + # or "mode" or "MoDe", etc.
  421 + emacs_vars[variable.lower()] = value.strip()
  422 +
  423 + tail = text[-SIZE:]
  424 + if "Local Variables" in tail:
  425 + match = self._emacs_local_vars_pat.search(tail)
  426 + if match:
  427 + prefix = match.group("prefix")
  428 + suffix = match.group("suffix")
  429 + lines = match.group("content").splitlines(0)
  430 + #print "prefix=%r, suffix=%r, content=%r, lines: %s"\
  431 + # % (prefix, suffix, match.group("content"), lines)
  432 +
  433 + # Validate the Local Variables block: proper prefix and suffix
  434 + # usage.
  435 + for i, line in enumerate(lines):
  436 + if not line.startswith(prefix):
  437 + log.debug("emacs variables error: line '%s' "
  438 + "does not use proper prefix '%s'"
  439 + % (line, prefix))
  440 + return {}
  441 + # Don't validate suffix on last line. Emacs doesn't care,
  442 + # neither should we.
  443 + if i != len(lines)-1 and not line.endswith(suffix):
  444 + log.debug("emacs variables error: line '%s' "
  445 + "does not use proper suffix '%s'"
  446 + % (line, suffix))
  447 + return {}
  448 +
  449 + # Parse out one emacs var per line.
  450 + continued_for = None
  451 + for line in lines[:-1]: # no var on the last line ("PREFIX End:")
  452 + if prefix: line = line[len(prefix):] # strip prefix
  453 + if suffix: line = line[:-len(suffix)] # strip suffix
  454 + line = line.strip()
  455 + if continued_for:
  456 + variable = continued_for
  457 + if line.endswith('\\'):
  458 + line = line[:-1].rstrip()
  459 + else:
  460 + continued_for = None
  461 + emacs_vars[variable] += ' ' + line
  462 + else:
  463 + try:
  464 + variable, value = line.split(':', 1)
  465 + except ValueError:
  466 + log.debug("local variables error: missing colon "
  467 + "in local variables entry: '%s'" % line)
  468 + continue
  469 + # Do NOT lowercase the variable name, because Emacs only
  470 + # allows "mode" (and not "Mode", "MoDe", etc.) in this block.
  471 + value = value.strip()
  472 + if value.endswith('\\'):
  473 + value = value[:-1].rstrip()
  474 + continued_for = variable
  475 + else:
  476 + continued_for = None
  477 + emacs_vars[variable] = value
  478 +
  479 + # Unquote values.
  480 + for var, val in list(emacs_vars.items()):
  481 + if len(val) > 1 and (val.startswith('"') and val.endswith('"')
  482 + or val.startswith('"') and val.endswith('"')):
  483 + emacs_vars[var] = val[1:-1]
  484 +
  485 + return emacs_vars
  486 +
  487 + # Cribbed from a post by Bart Lateur:
  488 + # <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>