Split docutils from utils

This commit is contained in:
Florian Bruhin 2014-09-23 04:22:51 +02:00
parent 9954a08ea2
commit f4876c7f4f
5 changed files with 155 additions and 128 deletions

View File

@ -26,7 +26,7 @@ from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWebKit import QWebSettings
from qutebrowser.commands import cmdexc, argparser
from qutebrowser.utils import log, utils, message, debug, usertypes
from qutebrowser.utils import log, utils, message, debug, usertypes, docutils
class Command:
@ -74,7 +74,7 @@ class Command:
self.debug = is_debug
self.ignore_args = ignore_args
self.handler = handler
self.docparser = utils.DocstringParser(handler)
self.docparser = docutils.DocstringParser(handler)
self.parser = argparser.ArgumentParser(
name, description=self.docparser.short_desc,
epilog=self.docparser.long_desc)

View File

@ -31,7 +31,7 @@ from PyQt5.QtNetwork import QNetworkReply
import qutebrowser
from qutebrowser.network import schemehandler
from qutebrowser.utils import version, utils, jinja, log, message
from qutebrowser.utils import version, utils, jinja, log, message, docutils
pyeval_output = ":pyeval was never called"
@ -139,7 +139,7 @@ def qute_help(request):
urlpath = 'index.html'
else:
urlpath = urlpath.lstrip('/')
if not utils.docs_up_to_date(urlpath):
if not docutils.docs_up_to_date(urlpath):
message.error("Your documentation is outdated! Please re-run scripts/"
"asciidoc2html.py.")
path = 'html/doc/{}'.format(urlpath)

View File

@ -0,0 +1,148 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Utilities used for the documentation and built-in help."""
import re
import sys
import inspect
import os.path
import collections
import qutebrowser
from qutebrowser.utils import usertypes
def is_git_repo():
"""Check if we're running from a git repository."""
gitfolder = os.path.join(qutebrowser.basedir, os.path.pardir, '.git')
return os.path.isdir(gitfolder)
def docs_up_to_date(path):
"""Check if the generated html documentation is up to date.
Args:
path: The path of the document to check.
Return:
True if they are up to date or we couldn't check.
False if they are outdated.
"""
if hasattr(sys, 'frozen') or not is_git_repo():
return True
html_path = os.path.join(qutebrowser.basedir, 'html', 'doc', path)
filename = os.path.splitext(path)[0]
asciidoc_path = os.path.join(qutebrowser.basedir, os.path.pardir,
'doc', 'help', filename + '.asciidoc')
try:
html_time = os.path.getmtime(html_path)
asciidoc_time = os.path.getmtime(asciidoc_path)
except FileNotFoundError:
return True
return asciidoc_time <= html_time
class DocstringParser:
"""Generate documentation based on a docstring of a command handler.
The docstring needs to follow the format described in HACKING.
"""
State = usertypes.enum('State', 'short', 'desc', 'desc_hidden',
'arg_start', 'arg_inside', 'misc')
def __init__(self, func):
"""Constructor.
Args:
func: The function to parse the docstring for.
"""
self.state = self.State.short
self.short_desc = []
self.long_desc = []
self.arg_descs = collections.OrderedDict()
self.cur_arg_name = None
self.handlers = {
self.State.short: self._parse_short,
self.State.desc: self._parse_desc,
self.State.desc_hidden: self._skip,
self.State.arg_start: self._parse_arg_start,
self.State.arg_inside: self._parse_arg_inside,
self.State.misc: self._skip,
}
doc = inspect.getdoc(func)
for line in doc.splitlines():
handler = self.handlers[self.state]
stop = handler(line)
if stop:
break
for k, v in self.arg_descs.items():
self.arg_descs[k] = ' '.join(v).replace(', or None', '')
self.long_desc = ' '.join(self.long_desc)
self.short_desc = ' '.join(self.short_desc)
def _process_arg(self, line):
"""Helper method to process a line like 'fooarg: Blah blub'."""
self.cur_arg_name, argdesc = line.split(':', maxsplit=1)
self.cur_arg_name = self.cur_arg_name.strip().lstrip('*')
self.arg_descs[self.cur_arg_name] = [argdesc.strip()]
def _skip(self, line):
"""Handler to ignore everything until we get 'Args:'."""
if line.startswith('Args:'):
self.state = self.State.arg_start
def _parse_short(self, line):
"""Parse the short description (first block) in the docstring."""
if not line:
self.state = self.State.desc
else:
self.short_desc.append(line.strip())
def _parse_desc(self, line):
"""Parse the long description in the docstring."""
if line.startswith('Args:'):
self.state = self.State.arg_start
elif line.startswith('Emit:') or line.startswith('Raise:'):
self.state = self.State.misc
elif line.strip() == '//':
self.state = self.State.desc_hidden
elif line.strip():
self.long_desc.append(line.strip())
def _parse_arg_start(self, line):
"""Parse first argument line."""
self._process_arg(line)
self.state = self.State.arg_inside
def _parse_arg_inside(self, line):
"""Parse subsequent argument lines."""
argname = self.cur_arg_name
if re.match(r'^[A-Z][a-z]+:$', line):
if not self.arg_descs[argname][-1].strip():
self.arg_descs[argname] = self.arg_descs[argname][:-1]
return True
elif not line.strip():
self.arg_descs[argname].append('\n\n')
elif line[4:].startswith(' '):
self.arg_descs[argname].append(line.strip() + '\n')
else:
self._process_arg(line)

View File

@ -21,11 +21,9 @@
import os
import io
import re
import sys
import enum
import shlex
import inspect
import os.path
import urllib.request
import urllib.parse
@ -38,7 +36,7 @@ from PyQt5.QtGui import QKeySequence, QColor
import pkg_resources
import qutebrowser
from qutebrowser.utils import qtutils, log, usertypes
from qutebrowser.utils import qtutils, log
def elide(text, length):
@ -580,122 +578,3 @@ def is_enum(obj):
return issubclass(obj, enum.Enum)
except TypeError:
return False
def is_git_repo():
"""Check if we're running from a git repository."""
gitfolder = os.path.join(qutebrowser.basedir, os.path.pardir, '.git')
return os.path.isdir(gitfolder)
def docs_up_to_date(path):
"""Check if the generated html documentation is up to date.
Args:
path: The path of the document to check.
Return:
True if they are up to date or we couldn't check.
False if they are outdated.
"""
if hasattr(sys, 'frozen') or not is_git_repo():
return True
html_path = os.path.join(qutebrowser.basedir, 'html', 'doc', path)
filename = os.path.splitext(path)[0]
asciidoc_path = os.path.join(qutebrowser.basedir, os.path.pardir,
'doc', 'help', filename + '.asciidoc')
try:
html_time = os.path.getmtime(html_path)
asciidoc_time = os.path.getmtime(asciidoc_path)
except FileNotFoundError:
return True
return asciidoc_time <= html_time
class DocstringParser:
"""Generate documentation based on a docstring of a command handler.
The docstring needs to follow the format described in HACKING.
"""
State = usertypes.enum('State', 'short', 'desc', 'desc_hidden',
'arg_start', 'arg_inside', 'misc')
def __init__(self, func):
"""Constructor.
Args:
func: The function to parse the docstring for.
"""
self.state = self.State.short
self.short_desc = []
self.long_desc = []
self.arg_descs = collections.OrderedDict()
self.cur_arg_name = None
self.handlers = {
self.State.short: self._parse_short,
self.State.desc: self._parse_desc,
self.State.desc_hidden: self._skip,
self.State.arg_start: self._parse_arg_start,
self.State.arg_inside: self._parse_arg_inside,
self.State.misc: self._skip,
}
doc = inspect.getdoc(func)
for line in doc.splitlines():
handler = self.handlers[self.state]
stop = handler(line)
if stop:
break
for k, v in self.arg_descs.items():
self.arg_descs[k] = ' '.join(v).replace(', or None', '')
self.long_desc = ' '.join(self.long_desc)
self.short_desc = ' '.join(self.short_desc)
def _process_arg(self, line):
"""Helper method to process a line like 'fooarg: Blah blub'."""
self.cur_arg_name, argdesc = line.split(':', maxsplit=1)
self.cur_arg_name = self.cur_arg_name.strip().lstrip('*')
self.arg_descs[self.cur_arg_name] = [argdesc.strip()]
def _skip(self, line):
"""Handler to ignore everything until we get 'Args:'."""
if line.startswith('Args:'):
self.state = self.State.arg_start
def _parse_short(self, line):
"""Parse the short description (first block) in the docstring."""
if not line:
self.state = self.State.desc
else:
self.short_desc.append(line.strip())
def _parse_desc(self, line):
"""Parse the long description in the docstring."""
if line.startswith('Args:'):
self.state = self.State.arg_start
elif line.startswith('Emit:') or line.startswith('Raise:'):
self.state = self.State.misc
elif line.strip() == '//':
self.state = self.State.desc_hidden
elif line.strip():
self.long_desc.append(line.strip())
def _parse_arg_start(self, line):
"""Parse first argument line."""
self._process_arg(line)
self.state = self.State.arg_inside
def _parse_arg_inside(self, line):
"""Parse subsequent argument lines."""
argname = self.cur_arg_name
if re.match(r'^[A-Z][a-z]+:$', line):
if not self.arg_descs[argname][-1].strip():
self.arg_descs[argname] = self.arg_descs[argname][:-1]
return True
elif not line.strip():
self.arg_descs[argname].append('\n\n')
elif line[4:].startswith(' '):
self.arg_descs[argname].append(line.strip() + '\n')
else:
self._process_arg(line)

View File

@ -39,7 +39,7 @@ from scripts import asciidoc2html, utils
from qutebrowser import qutebrowser
from qutebrowser.commands import cmdutils
from qutebrowser.config import configdata
from qutebrowser.utils import utils as quteutils
from qutebrowser.utils import docutils
class UsageFormatter(argparse.HelpFormatter):
@ -151,7 +151,7 @@ def _get_command_doc(name, cmd):
if syntax != name:
output.append('Syntax: +:{}+'.format(syntax))
output.append("")
parser = quteutils.DocstringParser(cmd.handler)
parser = docutils.DocstringParser(cmd.handler)
output.append(parser.short_desc)
if parser.long_desc:
output.append("")