Split docutils from utils
This commit is contained in:
parent
9954a08ea2
commit
f4876c7f4f
@ -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)
|
||||
|
@ -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)
|
||||
|
148
qutebrowser/utils/docutils.py
Normal file
148
qutebrowser/utils/docutils.py
Normal 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)
|
@ -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)
|
||||
|
@ -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("")
|
||||
|
Loading…
Reference in New Issue
Block a user