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 PyQt5.QtWebKit import QWebSettings
|
||||||
|
|
||||||
from qutebrowser.commands import cmdexc, argparser
|
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:
|
class Command:
|
||||||
@ -74,7 +74,7 @@ class Command:
|
|||||||
self.debug = is_debug
|
self.debug = is_debug
|
||||||
self.ignore_args = ignore_args
|
self.ignore_args = ignore_args
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
self.docparser = utils.DocstringParser(handler)
|
self.docparser = docutils.DocstringParser(handler)
|
||||||
self.parser = argparser.ArgumentParser(
|
self.parser = argparser.ArgumentParser(
|
||||||
name, description=self.docparser.short_desc,
|
name, description=self.docparser.short_desc,
|
||||||
epilog=self.docparser.long_desc)
|
epilog=self.docparser.long_desc)
|
||||||
|
@ -31,7 +31,7 @@ from PyQt5.QtNetwork import QNetworkReply
|
|||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.network import schemehandler
|
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"
|
pyeval_output = ":pyeval was never called"
|
||||||
@ -139,7 +139,7 @@ def qute_help(request):
|
|||||||
urlpath = 'index.html'
|
urlpath = 'index.html'
|
||||||
else:
|
else:
|
||||||
urlpath = urlpath.lstrip('/')
|
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/"
|
message.error("Your documentation is outdated! Please re-run scripts/"
|
||||||
"asciidoc2html.py.")
|
"asciidoc2html.py.")
|
||||||
path = 'html/doc/{}'.format(urlpath)
|
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 os
|
||||||
import io
|
import io
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import enum
|
import enum
|
||||||
import shlex
|
import shlex
|
||||||
import inspect
|
|
||||||
import os.path
|
import os.path
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@ -38,7 +36,7 @@ from PyQt5.QtGui import QKeySequence, QColor
|
|||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.utils import qtutils, log, usertypes
|
from qutebrowser.utils import qtutils, log
|
||||||
|
|
||||||
|
|
||||||
def elide(text, length):
|
def elide(text, length):
|
||||||
@ -580,122 +578,3 @@ def is_enum(obj):
|
|||||||
return issubclass(obj, enum.Enum)
|
return issubclass(obj, enum.Enum)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return False
|
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 import qutebrowser
|
||||||
from qutebrowser.commands import cmdutils
|
from qutebrowser.commands import cmdutils
|
||||||
from qutebrowser.config import configdata
|
from qutebrowser.config import configdata
|
||||||
from qutebrowser.utils import utils as quteutils
|
from qutebrowser.utils import docutils
|
||||||
|
|
||||||
|
|
||||||
class UsageFormatter(argparse.HelpFormatter):
|
class UsageFormatter(argparse.HelpFormatter):
|
||||||
@ -151,7 +151,7 @@ def _get_command_doc(name, cmd):
|
|||||||
if syntax != name:
|
if syntax != name:
|
||||||
output.append('Syntax: +:{}+'.format(syntax))
|
output.append('Syntax: +:{}+'.format(syntax))
|
||||||
output.append("")
|
output.append("")
|
||||||
parser = quteutils.DocstringParser(cmd.handler)
|
parser = docutils.DocstringParser(cmd.handler)
|
||||||
output.append(parser.short_desc)
|
output.append(parser.short_desc)
|
||||||
if parser.long_desc:
|
if parser.long_desc:
|
||||||
output.append("")
|
output.append("")
|
||||||
|
Loading…
Reference in New Issue
Block a user