aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/gen_api_vimdoc.py563
-rw-r--r--scripts/gen_help_html.py74
-rwxr-xr-xscripts/gen_vimdoc.py763
-rwxr-xr-xscripts/genappimage.sh52
-rw-r--r--scripts/lua2dox.lua666
-rwxr-xr-xscripts/lua2dox_filter87
-rwxr-xr-xscripts/vim-patch.sh47
7 files changed, 1610 insertions, 642 deletions
diff --git a/scripts/gen_api_vimdoc.py b/scripts/gen_api_vimdoc.py
deleted file mode 100755
index 515964bfe8..0000000000
--- a/scripts/gen_api_vimdoc.py
+++ /dev/null
@@ -1,563 +0,0 @@
-#!/usr/bin/env python3
-"""Parses Doxygen XML output to generate Neovim's API documentation.
-
-This would be easier using lxml and XSLT, but:
-
- 1. This should avoid needing Python dependencies, especially ones that are
- C modules that have library dependencies (lxml requires libxml and
- libxslt).
- 2. I wouldn't know how to deal with nested indentation in <para> tags using
- XSLT.
-
-Each function documentation is formatted with the following rules:
-
- - Maximum width of 78 characters (`text_width`).
- - Spaces for indentation.
- - Function signature and helptag are on the same line.
- - Helptag is right aligned.
- - Signature and helptag must have a minimum of 8 spaces between them.
- - If the signature is too long, it is placed on the line after the
- helptag. The signature wraps at `text_width - 8` characters with
- subsequent lines indented to the open parenthesis.
- - Documentation body will be indented by 16 spaces.
- - Subsection bodies are indented an additional 4 spaces.
- - Documentation body consists of the function description, parameter details,
- return description, and C declaration.
- - Parameters are omitted for the `void` and `Error *` types, or if the
- parameter is marked as [out].
- - Each function documentation is separated by a single line.
-
-The C declaration is added to the end to show actual argument types.
-"""
-import os
-import re
-import sys
-import shutil
-import textwrap
-import subprocess
-
-from xml.dom import minidom
-
-if sys.version_info[0] < 3:
- print("use Python 3")
- sys.exit(1)
-
-doc_filename = 'api.txt'
-# String used to find the start of the generated part of the doc.
-section_start_token = '*api-global*'
-# Required prefix for API function names.
-api_func_name_prefix = 'nvim_'
-
-# Section name overrides.
-section_name = {
- 'vim.c': 'Global',
-}
-
-# Section ordering.
-section_order = (
- 'vim.c',
- 'buffer.c',
- 'window.c',
- 'tabpage.c',
- 'ui.c',
-)
-
-param_exclude = (
- 'channel_id',
-)
-
-# Annotations are displayed as line items after API function descriptions.
-annotation_map = {
- 'FUNC_API_ASYNC': '{async}',
-}
-
-text_width = 78
-script_path = os.path.abspath(__file__)
-base_dir = os.path.dirname(os.path.dirname(script_path))
-src_dir = os.path.join(base_dir, 'src/nvim/api')
-out_dir = os.path.join(base_dir, 'tmp-api-doc')
-filter_cmd = '%s %s' % (sys.executable, script_path)
-seen_funcs = set()
-
-# Tracks `xrefsect` titles. As of this writing, used only for separating
-# deprecated functions.
-xrefs = set()
-
-
-# XML Parsing Utilities {{{
-def find_first(parent, name):
- """Finds the first matching node within parent."""
- sub = parent.getElementsByTagName(name)
- if not sub:
- return None
- return sub[0]
-
-
-def get_children(parent, name):
- """Yield matching child nodes within parent."""
- for child in parent.childNodes:
- if child.nodeType == child.ELEMENT_NODE and child.nodeName == name:
- yield child
-
-
-def get_child(parent, name):
- """Get the first matching child node."""
- for child in get_children(parent, name):
- return child
- return None
-
-
-def clean_text(text):
- """Cleans text.
-
- Only cleans superfluous whitespace at the moment.
- """
- return ' '.join(text.split()).strip()
-
-
-def clean_lines(text):
- """Removes superfluous lines.
-
- The beginning and end of the string is trimmed. Empty lines are collapsed.
- """
- return re.sub(r'\A\n\s*\n*|\n\s*\n*\Z', '', re.sub(r'(\n\s*\n+)+', '\n\n', text))
-
-
-def get_text(parent):
- """Combine all text in a node."""
- if parent.nodeType == parent.TEXT_NODE:
- return parent.data
-
- out = ''
- for node in parent.childNodes:
- if node.nodeType == node.TEXT_NODE:
- out += clean_text(node.data)
- elif node.nodeType == node.ELEMENT_NODE:
- out += ' ' + get_text(node)
- return out
-
-
-def doc_wrap(text, prefix='', width=70, func=False):
- """Wraps text to `width`.
-
- The first line is prefixed with `prefix`, and subsequent lines are aligned.
- If `func` is True, only wrap at commas.
- """
- if not width:
- return text
-
- indent_space = ' ' * len(prefix)
-
- if func:
- lines = [prefix]
- for part in text.split(', '):
- if part[-1] not in ');':
- part += ', '
- if len(lines[-1]) + len(part) > width:
- lines.append(indent_space)
- lines[-1] += part
- return '\n'.join(x.rstrip() for x in lines).rstrip()
-
- tw = textwrap.TextWrapper(break_long_words = False,
- break_on_hyphens = False,
- width=width,
- initial_indent=prefix,
- subsequent_indent=indent_space)
- return '\n'.join(tw.wrap(text.strip()))
-
-
-def parse_params(parent, width=62):
- """Parse Doxygen `parameterlist`."""
- name_length = 0
- items = []
- for child in parent.childNodes:
- if child.nodeType == child.TEXT_NODE:
- continue
-
- name_node = find_first(child, 'parametername')
- if name_node.getAttribute('direction') == 'out':
- continue
-
- name = get_text(name_node)
- if name in param_exclude:
- continue
-
- name = '{%s}' % name
- name_length = max(name_length, len(name) + 2)
-
- desc = ''
- desc_node = get_child(child, 'parameterdescription')
- if desc_node:
- desc = parse_parblock(desc_node, width=None)
- items.append((name.strip(), desc.strip()))
-
- out = 'Parameters: ~\n'
- for name, desc in items:
- name = ' %s' % name.ljust(name_length)
- out += doc_wrap(desc, prefix=name, width=width) + '\n'
- return out.strip()
-
-
-def parse_para(parent, width=62):
- """Parse doxygen `para` tag.
-
- I assume <para> is a paragraph block or "a block of text". It can contain
- text nodes, or other tags.
- """
- line = ''
- lines = []
- for child in parent.childNodes:
- if child.nodeType == child.TEXT_NODE:
- line += child.data
- elif child.nodeName == 'computeroutput':
- line += '`%s`' % get_text(child)
- else:
- if line:
- lines.append(doc_wrap(line, width=width))
- line = ''
-
- if child.nodeName == 'parameterlist':
- lines.append(parse_params(child, width=width))
- elif child.nodeName == 'xrefsect':
- title = get_text(get_child(child, 'xreftitle'))
- xrefs.add(title)
- xrefdesc = parse_para(get_child(child, 'xrefdescription'))
- lines.append(doc_wrap(xrefdesc, prefix='%s: ' % title,
- width=width) + '\n')
- elif child.nodeName == 'simplesect':
- kind = child.getAttribute('kind')
- if kind == 'note':
- lines.append('Note:')
- lines.append(doc_wrap(parse_para(child),
- prefix=' ',
- width=width))
- elif kind == 'return':
- lines.append('%s: ~' % kind.title())
- lines.append(doc_wrap(parse_para(child),
- prefix=' ',
- width=width))
- else:
- lines.append(get_text(child))
-
- if line:
- lines.append(doc_wrap(line, width=width))
- return clean_lines('\n'.join(lines).strip())
-
-
-def parse_parblock(parent, width=62):
- """Parses a nested block of `para` tags.
-
- Named after the \parblock command, but not directly related.
- """
- paragraphs = []
- for child in parent.childNodes:
- if child.nodeType == child.TEXT_NODE:
- paragraphs.append(doc_wrap(child.data, width=width))
- elif child.nodeName == 'para':
- paragraphs.append(parse_para(child, width=width))
- else:
- paragraphs.append(doc_wrap(get_text(child), width=width))
- paragraphs.append('')
- return clean_lines('\n'.join(paragraphs).strip())
-# }}}
-
-
-def parse_source_xml(filename):
- """Collects API functions.
-
- Returns two strings:
- 1. API functions
- 2. Deprecated API functions
-
- Caller decides what to do with the deprecated documentation.
- """
- global xrefs
- xrefs = set()
- functions = []
- deprecated_functions = []
-
- dom = minidom.parse(filename)
- for member in dom.getElementsByTagName('memberdef'):
- if member.getAttribute('static') == 'yes' or \
- member.getAttribute('kind') != 'function':
- continue
-
- loc = find_first(member, 'location')
- if 'private' in loc.getAttribute('file'):
- continue
-
- return_type = get_text(get_child(member, 'type'))
- if return_type == '':
- continue
-
- if return_type.startswith(('ArrayOf', 'DictionaryOf')):
- parts = return_type.strip('_').split('_')
- return_type = '%s(%s)' % (parts[0], ', '.join(parts[1:]))
-
- name = get_text(get_child(member, 'name'))
-
- annotations = get_text(get_child(member, 'argsstring'))
- if annotations and ')' in annotations:
- annotations = annotations.rsplit(')', 1)[-1].strip()
- # XXX: (doxygen 1.8.11) 'argsstring' only includes attributes of
- # non-void functions. Special-case void functions here.
- if name == 'nvim_get_mode' and len(annotations) == 0:
- annotations += 'FUNC_API_ASYNC'
- annotations = filter(None, map(lambda x: annotation_map.get(x),
- annotations.split()))
-
- vimtag = '*%s()*' % name
- args = []
- type_length = 0
-
- for param in get_children(member, 'param'):
- arg_type = get_text(get_child(param, 'type')).strip()
- arg_name = ''
- declname = get_child(param, 'declname')
- if declname:
- arg_name = get_text(declname).strip()
-
- if arg_name in param_exclude:
- continue
-
- if arg_type.endswith('*'):
- arg_type = arg_type.strip('* ')
- arg_name = '*' + arg_name
- type_length = max(type_length, len(arg_type))
- args.append((arg_type, arg_name))
-
- c_args = []
- for arg_type, arg_name in args:
- c_args.append(' ' + (
- '%s %s' % (arg_type.ljust(type_length), arg_name)).strip())
-
- c_decl = textwrap.indent('%s %s(\n%s\n);' % (return_type, name,
- ',\n'.join(c_args)),
- ' ')
-
- prefix = '%s(' % name
- suffix = '%s)' % ', '.join('{%s}' % a[1] for a in args
- if a[0] not in ('void', 'Error'))
-
- # Minimum 8 chars between signature and vimtag
- lhs = (text_width - 8) - len(prefix)
-
- if len(prefix) + len(suffix) > lhs:
- signature = vimtag.rjust(text_width) + '\n'
- signature += doc_wrap(suffix, width=text_width-8, prefix=prefix,
- func=True)
- else:
- signature = prefix + suffix
- signature += vimtag.rjust(text_width - len(signature))
-
- doc = ''
- desc = find_first(member, 'detaileddescription')
- if desc:
- doc = parse_parblock(desc)
- if 'DEBUG' in os.environ:
- print(textwrap.indent(
- re.sub(r'\n\s*\n+', '\n',
- desc.toprettyxml(indent=' ', newl='\n')), ' ' * 16))
-
- if not doc:
- doc = 'TODO: Documentation'
-
- annotations = '\n'.join(annotations)
- if annotations:
- annotations = ('\n\nAttributes: ~\n' +
- textwrap.indent(annotations, ' '))
- i = doc.rfind('Parameters: ~')
- if i == -1:
- doc += annotations
- else:
- doc = doc[:i] + annotations + '\n\n' + doc[i:]
-
- if 'INCLUDE_C_DECL' in os.environ:
- doc += '\n\nC Declaration: ~\n>\n'
- doc += c_decl
- doc += '\n<'
-
- func_doc = signature + '\n'
- func_doc += textwrap.indent(clean_lines(doc), ' ' * 16)
- func_doc = re.sub(r'^\s+([<>])$', r'\1', func_doc, flags=re.M)
-
- if 'Deprecated' in xrefs:
- deprecated_functions.append(func_doc)
- elif name.startswith(api_func_name_prefix):
- functions.append(func_doc)
-
- xrefs.clear()
-
- return '\n\n'.join(functions), '\n\n'.join(deprecated_functions)
-
-
-def delete_lines_below(filename, tokenstr):
- """Deletes all lines below the line containing `tokenstr`, the line itself,
- and one line above it.
- """
- lines = open(filename).readlines()
- i = 0
- for i, line in enumerate(lines, 1):
- if tokenstr in line:
- break
- i = max(0, i - 2)
- with open(filename, 'wt') as fp:
- fp.writelines(lines[0:i])
-
-def gen_docs(config):
- """Generate documentation.
-
- Doxygen is called and configured through stdin.
- """
- p = subprocess.Popen(['doxygen', '-'], stdin=subprocess.PIPE)
- p.communicate(config.format(input=src_dir, output=out_dir,
- filter=filter_cmd).encode('utf8'))
- if p.returncode:
- sys.exit(p.returncode)
-
- sections = {}
- intros = {}
- sep = '=' * text_width
-
- base = os.path.join(out_dir, 'xml')
- dom = minidom.parse(os.path.join(base, 'index.xml'))
-
- # generate docs for section intros
- for compound in dom.getElementsByTagName('compound'):
- if compound.getAttribute('kind') != 'group':
- continue
-
- groupname = get_text(find_first(compound, 'name'))
- groupxml = os.path.join(base, '%s.xml' % compound.getAttribute('refid'))
-
- desc = find_first(minidom.parse(groupxml), 'detaileddescription')
- if desc:
- doc = parse_parblock(desc)
- if doc:
- intros[groupname] = doc
-
- for compound in dom.getElementsByTagName('compound'):
- if compound.getAttribute('kind') != 'file':
- continue
-
- filename = get_text(find_first(compound, 'name'))
- if filename.endswith('.c'):
- functions, deprecated = parse_source_xml(
- os.path.join(base, '%s.xml' % compound.getAttribute('refid')))
-
- if not functions and not deprecated:
- continue
-
- if functions or deprecated:
- name = os.path.splitext(os.path.basename(filename))[0]
- if name == 'ui':
- name = name.upper()
- else:
- name = name.title()
-
- doc = ''
-
- intro = intros.get('api-%s' % name.lower())
- if intro:
- doc += '\n\n' + intro
-
- if functions:
- doc += '\n\n' + functions
-
- if 'INCLUDE_DEPRECATED' in os.environ and deprecated:
- doc += '\n\n\nDeprecated %s Functions: ~\n\n' % name
- doc += deprecated
-
- if doc:
- filename = os.path.basename(filename)
- name = section_name.get(filename, name)
- title = '%s Functions' % name
- helptag = '*api-%s*' % name.lower()
- sections[filename] = (title, helptag, doc)
-
- if not sections:
- return
-
- docs = ''
-
- i = 0
- for filename in section_order:
- if filename not in sections:
- continue
- title, helptag, section_doc = sections.pop(filename)
-
- i += 1
- docs += sep
- docs += '\n%s%s' % (title, helptag.rjust(text_width - len(title)))
- docs += section_doc
- docs += '\n\n\n'
-
- if sections:
- # In case new API sources are added without updating the order dict.
- for title, helptag, section_doc in sections.values():
- i += 1
- docs += sep
- docs += '\n%s%s' % (title, helptag.rjust(text_width - len(title)))
- docs += section_doc
- docs += '\n\n\n'
-
- docs = docs.rstrip() + '\n\n'
- docs += ' vim:tw=78:ts=8:ft=help:norl:\n'
-
- doc_file = os.path.join(base_dir, 'runtime/doc', doc_filename)
- delete_lines_below(doc_file, section_start_token)
- with open(doc_file, 'ab') as fp:
- fp.write(docs.encode('utf8'))
- shutil.rmtree(out_dir)
-
-
-def filter_source(filename):
- """Filters the source to fix macros that confuse Doxygen."""
- with open(filename, 'rt') as fp:
- print(re.sub(r'^(ArrayOf|DictionaryOf)(\(.*?\))',
- lambda m: m.group(1)+'_'.join(
- re.split(r'[^\w]+', m.group(2))),
- fp.read(), flags=re.M))
-
-
-# Doxygen Config {{{
-Doxyfile = '''
-OUTPUT_DIRECTORY = {output}
-INPUT = {input}
-INPUT_ENCODING = UTF-8
-FILE_PATTERNS = *.h *.c
-RECURSIVE = YES
-INPUT_FILTER = "{filter}"
-EXCLUDE =
-EXCLUDE_SYMLINKS = NO
-EXCLUDE_PATTERNS = */private/*
-EXCLUDE_SYMBOLS =
-
-GENERATE_HTML = NO
-GENERATE_DOCSET = NO
-GENERATE_HTMLHELP = NO
-GENERATE_QHP = NO
-GENERATE_TREEVIEW = NO
-GENERATE_LATEX = NO
-GENERATE_RTF = NO
-GENERATE_MAN = NO
-GENERATE_DOCBOOK = NO
-GENERATE_AUTOGEN_DEF = NO
-
-GENERATE_XML = YES
-XML_OUTPUT = xml
-XML_PROGRAMLISTING = NO
-
-ENABLE_PREPROCESSING = YES
-MACRO_EXPANSION = YES
-EXPAND_ONLY_PREDEF = NO
-'''
-# }}}
-
-if __name__ == "__main__":
- if len(sys.argv) > 1:
- filter_source(sys.argv[1])
- else:
- gen_docs(Doxyfile)
-
-# vim: set ft=python ts=4 sw=4 tw=79 et fdm=marker :
diff --git a/scripts/gen_help_html.py b/scripts/gen_help_html.py
index 6cca81d250..dbdeb3c162 100644
--- a/scripts/gen_help_html.py
+++ b/scripts/gen_help_html.py
@@ -1,5 +1,10 @@
# Converts Vim/Nvim documentation to HTML.
#
+# USAGE:
+# 1. python3 scripts/gen_help_html.py runtime/doc/ ~/neovim.github.io/t/
+# 3. cd ~/neovim.github.io/ && jekyll serve --host 0.0.0.0
+# 2. Visit http://localhost:4000/t/help.txt.html
+#
# Adapted from https://github.com/c4rlo/vimhelp/
# License: MIT
#
@@ -23,7 +28,11 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
-import re, urllib.parse
+import os
+import re
+import urllib.parse
+import datetime
+import sys
from itertools import chain
HEAD = """\
@@ -32,6 +41,17 @@ HEAD = """\
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset={encoding}"/>
+<style>
+.h {{
+ font-weight: bold;
+}}
+h1 {{
+ font-family: sans-serif;
+}}
+pre {{
+ font-family: sans-serif;
+}}
+</style>
<title>Nvim: {filename}</title>
"""
@@ -39,22 +59,19 @@ HEAD_END = '</head>\n<body>\n'
INTRO = """
<h1>Nvim help files</h1>
-<p>HTML export of the <a href="https://neovim.io/">Nvim</a> help pages{vers-note}.
-Updated <a href="https://github.com/neovim/bot-ci" class="d">automatically</a> from the <a
-href="https://github.com/vim/vim/tree/master/runtime/doc" class="d">Nvim source repository</a>.
-Also includes the <a href="vim_faq.txt.html">Vim FAQ</a>, pulled from its
-<a href="https://github.com/chrisbra/vim_faq" class="d">source repository</a>.</p>
+<p>
+<a href="https://neovim.io/">Nvim</a> help pages{vers-note}.
+Updated <a href="https://github.com/neovim/bot-ci" class="d">automatically</a>
+from the <a href="https://github.com/neovim/neovim" class="d">Nvim source</a>.
+</p>
"""
-VERSION_NOTE = ", current as of Vim {version}"
+VERSION_NOTE = ", current as of Nvim {version}"
SITENAVI_LINKS = """
-Quick links:
-<a href="/">help overview</a> &middot;
-<a href="quickref.txt.html">quick reference</a> &middot;
-<a href="usr_toc.txt.html">user manual toc</a> &middot;
-<a href="{helptxt}#reference_toc">reference manual toc</a> &middot;
-<a href="vim_faq.txt.html">faq</a>
+<a href="quickref.txt.html">Quick reference</a> &middot;
+<a href="usr_toc.txt.html">User manual</a> &middot;
+<a href="{helptxt}#reference_toc">Reference manual</a> &middot;
"""
SITENAVI_LINKS_PLAIN = SITENAVI_LINKS.format(helptxt='help.txt.html')
@@ -77,15 +94,14 @@ TEXTSTART = """
FOOTER = '</pre>'
FOOTER2 = """
-<p id="footer">This site is maintained by Carlo Teubner (<i>(my first name) dot (my last name) at gmail dot com</i>).</p>
+<p id="footer">Generated {generated_date} from <code>{commit}</code></p>
</div>
</div>
</body>
</html>
-"""
-
-VIM_FAQ_LINE = '<a href="vim_faq.txt.html#vim_faq.txt" class="l">' \
- 'vim_faq.txt</a> Frequently Asked Questions\n'
+""".format(
+ generated_date='{0:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()),
+ commit='?')
RE_TAGLINE = re.compile(r'(\S+)\s+(\S+)')
@@ -127,6 +143,8 @@ RE_TAGWORD = re.compile(
PAT_URL + '|' +
PAT_WORD)
RE_NEWLINE = re.compile(r'[\r\n]')
+# H1 header "=====…"
+# H2 header "-----…"
RE_HRULE = re.compile(r'[-=]{3,}.*[-=]{3,3}$')
RE_EG_START = re.compile(r'(?:.* )?>$')
RE_EG_END = re.compile(r'\S')
@@ -211,13 +229,20 @@ class VimH2H(object):
inexample = 0
filename = str(filename)
is_help_txt = (filename == 'help.txt')
- faq_line = False
+ last = ''
for line in RE_NEWLINE.split(contents):
line = line.rstrip('\r\n')
line_tabs = line
line = line.expandtabs()
+ if last == 'h1':
+ out.extend(('</pre>')) # XXX
+ out.extend(('<h1>', line.rstrip(), '</h1>\n'))
+ out.extend(('<pre>'))
+ last = ''
+ continue
if RE_HRULE.match(line):
- out.extend(('<span class="h">', line, '</span>\n'))
+ # out.extend(('<span class="h">', line, '</span>\n'))
+ last = 'h1'
continue
if inexample == 2:
if RE_EG_END.match(line):
@@ -234,8 +259,6 @@ class VimH2H(object):
m = RE_SECTION.match(line)
out.extend((r'<span class="c">', m.group(0), r'</span>'))
line = line[m.end():]
- if is_help_txt and RE_LOCAL_ADD.match(line_tabs):
- faq_line = True
lastpos = 0
for match in RE_TAGWORD.finditer(line):
pos = match.start()
@@ -278,9 +301,6 @@ class VimH2H(object):
out.append(html_escape[line[lastpos:]])
out.append('\n')
if inexample == 1: inexample = 2
- if faq_line:
- out.append(VIM_FAQ_LINE)
- faq_line = False
header = []
header.append(HEAD.format(encoding=encoding, filename=filename))
@@ -310,9 +330,6 @@ html_escape = HtmlEscCache()
-import sys, os, os.path
-#import cProfile
-sys.path.append('.')
def slurp(filename):
try:
@@ -352,4 +369,3 @@ def main():
of.close()
main()
-#cProfile.run('main()')
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
new file mode 100755
index 0000000000..e491ff59b0
--- /dev/null
+++ b/scripts/gen_vimdoc.py
@@ -0,0 +1,763 @@
+#!/usr/bin/env python3
+"""Generates Nvim help docs from C docstrings, by parsing Doxygen XML.
+
+This would be easier using lxml and XSLT, but:
+
+ 1. This should avoid needing Python dependencies, especially ones that are
+ C modules that have library dependencies (lxml requires libxml and
+ libxslt).
+ 2. I wouldn't know how to deal with nested indentation in <para> tags using
+ XSLT.
+
+Each function documentation is formatted with the following rules:
+
+ - Maximum width of 78 characters (`text_width`).
+ - Spaces for indentation.
+ - Function signature and helptag are on the same line.
+ - Helptag is right aligned.
+ - Signature and helptag must have a minimum of 8 spaces between them.
+ - If the signature is too long, it is placed on the line after the
+ helptag. The signature wraps at `text_width - 8` characters with
+ subsequent lines indented to the open parenthesis.
+ - Documentation body will be indented by 16 spaces.
+ - Subsection bodies are indented an additional 4 spaces.
+ - Documentation body consists of the function description, parameter details,
+ return description, and C declaration.
+ - Parameters are omitted for the `void` and `Error *` types, or if the
+ parameter is marked as [out].
+ - Each function documentation is separated by a single line.
+
+The C declaration is added to the end to show actual argument types.
+"""
+import os
+import re
+import sys
+import shutil
+import textwrap
+import subprocess
+import collections
+import pprint
+
+from xml.dom import minidom
+
+if sys.version_info[0] < 3:
+ print("use Python 3")
+ sys.exit(1)
+
+DEBUG = ('DEBUG' in os.environ)
+INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ)
+INCLUDE_DEPRECATED = ('INCLUDE_DEPRECATED' in os.environ)
+
+text_width = 78
+script_path = os.path.abspath(__file__)
+base_dir = os.path.dirname(os.path.dirname(script_path))
+out_dir = os.path.join(base_dir, 'tmp-{mode}-doc')
+filter_cmd = '%s %s' % (sys.executable, script_path)
+seen_funcs = set()
+lua2dox_filter = os.path.join(base_dir, 'scripts', 'lua2dox_filter')
+
+CONFIG = {
+ 'api': {
+ 'filename': 'api.txt',
+ # String used to find the start of the generated part of the doc.
+ 'section_start_token': '*api-global*',
+ # Section ordering.
+ 'section_order' : [
+ 'vim.c',
+ 'buffer.c',
+ 'window.c',
+ 'tabpage.c',
+ 'ui.c',
+ ],
+ # List of files/directories for doxygen to read, separated by blanks
+ 'files': os.path.join(base_dir, 'src/nvim/api'),
+ # file patterns used by doxygen
+ 'file_patterns': '*.h *.c',
+ # Only function with this prefix are considered
+ 'func_name_prefix': 'nvim_',
+ # Section name overrides.
+ 'section_name': {
+ 'vim.c': 'Global',
+ },
+ # Module name overrides (for Lua).
+ 'module_override': {},
+ # Append the docs for these modules, do not start a new section.
+ 'append_only' : [],
+ },
+ 'lua': {
+ 'filename': 'if_lua.txt',
+ 'section_start_token': '*lua-vim*',
+ 'section_order' : [
+ 'vim.lua',
+ 'shared.lua',
+ ],
+ 'files': ' '.join([
+ os.path.join(base_dir, 'src/nvim/lua/vim.lua'),
+ os.path.join(base_dir, 'runtime/lua/vim/shared.lua'),
+ ]),
+ 'file_patterns': '*.lua',
+ 'func_name_prefix': '',
+ 'section_name': {},
+ 'module_override': {
+ 'shared': 'vim', # `shared` functions are exposed on the `vim` module.
+ },
+ 'append_only' : [
+ 'shared.lua',
+ ],
+ },
+}
+
+param_exclude = (
+ 'channel_id',
+)
+
+# Annotations are displayed as line items after API function descriptions.
+annotation_map = {
+ 'FUNC_API_ASYNC': '{async}',
+}
+
+
+# Tracks `xrefsect` titles. As of this writing, used only for separating
+# deprecated functions.
+xrefs = set()
+
+def debug_this(s, n):
+ o = n if isinstance(n, str) else n.toprettyxml(indent=' ', newl='\n')
+ name = '' if isinstance(n, str) else n.nodeName
+ if s in o:
+ raise RuntimeError('xxx: {}\n{}'.format(name, o))
+
+
+# XML Parsing Utilities {{{
+def find_first(parent, name):
+ """Finds the first matching node within parent."""
+ sub = parent.getElementsByTagName(name)
+ if not sub:
+ return None
+ return sub[0]
+
+
+def get_children(parent, name):
+ """Yield matching child nodes within parent."""
+ for child in parent.childNodes:
+ if child.nodeType == child.ELEMENT_NODE and child.nodeName == name:
+ yield child
+
+
+def get_child(parent, name):
+ """Get the first matching child node."""
+ for child in get_children(parent, name):
+ return child
+ return None
+
+
+def clean_text(text):
+ """Cleans text.
+
+ Only cleans superfluous whitespace at the moment.
+ """
+ return ' '.join(text.split()).strip()
+
+
+def clean_lines(text):
+ """Removes superfluous lines.
+
+ The beginning and end of the string is trimmed. Empty lines are collapsed.
+ """
+ return re.sub(r'\A\n\s*\n*|\n\s*\n*\Z', '', re.sub(r'(\n\s*\n+)+', '\n\n', text))
+
+
+def is_blank(text):
+ return '' == clean_lines(text)
+
+
+def get_text(parent, preformatted=False):
+ """Combine all text in a node."""
+ if parent.nodeType == parent.TEXT_NODE:
+ return parent.data
+
+ out = ''
+ for node in parent.childNodes:
+ if node.nodeType == node.TEXT_NODE:
+ out += node.data if preformatted else clean_text(node.data)
+ elif node.nodeType == node.ELEMENT_NODE:
+ out += ' ' + get_text(node, preformatted)
+ return out
+
+
+# Gets the length of the last line in `text`, excluding newline ("\n") char.
+def len_lastline(text):
+ lastnl = text.rfind('\n')
+ if -1 == lastnl:
+ return len(text)
+ if '\n' == text[-1]:
+ return lastnl - (1+ text.rfind('\n', 0, lastnl))
+ return len(text) - (1 + lastnl)
+
+
+def len_lastline_withoutindent(text, indent):
+ n = len_lastline(text)
+ return (n - len(indent)) if n > len(indent) else 0
+
+
+# Returns True if node `n` contains only inline (not block-level) elements.
+def is_inline(n):
+ for c in n.childNodes:
+ if c.nodeType != c.TEXT_NODE and c.nodeName != 'computeroutput':
+ return False
+ if not is_inline(c):
+ return False
+ return True
+
+def doc_wrap(text, prefix='', width=70, func=False, indent=None):
+ """Wraps text to `width`.
+
+ First line is prefixed with `prefix`, subsequent lines are aligned.
+ If `func` is True, only wrap at commas.
+ """
+ if not width:
+ # return prefix + text
+ return text
+
+ # Whitespace used to indent all lines except the first line.
+ indent = ' ' * len(prefix) if indent is None else indent
+ indent_only = (prefix == '' and indent is not None)
+
+ if func:
+ lines = [prefix]
+ for part in text.split(', '):
+ if part[-1] not in ');':
+ part += ', '
+ if len(lines[-1]) + len(part) > width:
+ lines.append(indent)
+ lines[-1] += part
+ return '\n'.join(x.rstrip() for x in lines).rstrip()
+
+ # XXX: Dummy prefix to force TextWrapper() to wrap the first line.
+ if indent_only:
+ prefix = indent
+
+ tw = textwrap.TextWrapper(break_long_words = False,
+ break_on_hyphens = False,
+ width=width,
+ initial_indent=prefix,
+ subsequent_indent=indent)
+ result = '\n'.join(tw.wrap(text.strip()))
+
+ # XXX: Remove the dummy prefix.
+ if indent_only:
+ result = result[len(indent):]
+
+ return result
+
+
+def has_nonexcluded_params(nodes):
+ """Returns true if any of the given <parameterlist> elements has at least
+ one non-excluded item."""
+ for n in nodes:
+ if render_params(n) != '':
+ return True
+
+
+def render_params(parent, width=62):
+ """Renders Doxygen <parameterlist> tag as Vim help text."""
+ name_length = 0
+ items = []
+ for node in parent.childNodes:
+ if node.nodeType == node.TEXT_NODE:
+ continue
+
+ name_node = find_first(node, 'parametername')
+ if name_node.getAttribute('direction') == 'out':
+ continue
+
+ name = get_text(name_node)
+ if name in param_exclude:
+ continue
+
+ name = '{%s}' % name
+ name_length = max(name_length, len(name) + 2)
+ items.append((name.strip(), node))
+
+ out = ''
+ for name, node in items:
+ name = ' {}'.format(name.ljust(name_length))
+
+ desc = ''
+ desc_node = get_child(node, 'parameterdescription')
+ if desc_node:
+ desc = parse_parblock(desc_node, width=width,
+ indent=(' ' * len(name)))
+
+ out += '{}{}\n'.format(name, desc)
+ return out.rstrip()
+
+# Renders a node as Vim help text, recursively traversing all descendants.
+def render_node(n, text, prefix='', indent='', width=62):
+ text = ''
+ # space_preceding = (len(text) > 0 and ' ' == text[-1][-1])
+ # text += (int(not space_preceding) * ' ')
+
+ if n.nodeType == n.TEXT_NODE:
+ # `prefix` is NOT sent to doc_wrap, it was already handled by now.
+ text += doc_wrap(n.data, indent=indent, width=width)
+ elif n.nodeName == 'computeroutput':
+ text += ' `{}` '.format(get_text(n))
+ elif n.nodeName == 'preformatted':
+ o = get_text(n, preformatted=True)
+ ensure_nl = '' if o[-1] == '\n' else '\n'
+ text += ' >{}{}\n<'.format(ensure_nl, o)
+ elif is_inline(n):
+ for c in n.childNodes:
+ text += render_node(c, text)
+ text = doc_wrap(text, indent=indent, width=width)
+ elif n.nodeName == 'verbatim':
+ # TODO: currently we don't use this. The "[verbatim]" hint is there as
+ # a reminder that we must decide how to format this if we do use it.
+ text += ' [verbatim] {}'.format(get_text(n))
+ elif n.nodeName == 'listitem':
+ for c in n.childNodes:
+ text += indent + prefix + render_node(c, text, indent=indent+(' ' * len(prefix)), width=width)
+ elif n.nodeName in ('para', 'heading'):
+ for c in n.childNodes:
+ text += render_node(c, text, indent=indent, width=width)
+ if is_inline(n):
+ text = doc_wrap(text, indent=indent, width=width)
+ elif n.nodeName == 'itemizedlist':
+ for c in n.childNodes:
+ text += '{}\n'.format(render_node(c, text, prefix='- ',
+ indent=indent, width=width))
+ elif n.nodeName == 'orderedlist':
+ i = 1
+ for c in n.childNodes:
+ if is_blank(get_text(c)):
+ text += '\n'
+ continue
+ text += '{}\n'.format(render_node(c, text, prefix='{}. '.format(i),
+ indent=indent, width=width))
+ i = i + 1
+ elif n.nodeName == 'simplesect' and 'note' == n.getAttribute('kind'):
+ text += 'Note:\n '
+ for c in n.childNodes:
+ text += render_node(c, text, indent=' ', width=width)
+ text += '\n'
+ elif n.nodeName == 'simplesect' and 'warning' == n.getAttribute('kind'):
+ text += 'Warning:\n '
+ for c in n.childNodes:
+ text += render_node(c, text, indent=' ', width=width)
+ text += '\n'
+ elif (n.nodeName == 'simplesect'
+ and n.getAttribute('kind') in ('return', 'see')):
+ text += ' '
+ for c in n.childNodes:
+ text += render_node(c, text, indent=' ', width=width)
+ else:
+ raise RuntimeError('unhandled node type: {}\n{}'.format(
+ n.nodeName, n.toprettyxml(indent=' ', newl='\n')))
+ return text
+
+def render_para(parent, indent='', width=62):
+ """Renders Doxygen <para> containing arbitrary nodes.
+
+ NB: Blank lines in a docstring manifest as <para> tags.
+ """
+ if is_inline(parent):
+ return clean_lines(doc_wrap(render_node(parent, ''),
+ indent=indent, width=width).strip())
+
+ # Ordered dict of ordered lists.
+ groups = collections.OrderedDict([
+ ('params', []),
+ ('return', []),
+ ('seealso', []),
+ ('xrefs', []),
+ ])
+
+ # Gather nodes into groups. Mostly this is because we want "parameterlist"
+ # nodes to appear together.
+ text = ''
+ kind = ''
+ last = ''
+ for child in parent.childNodes:
+ if child.nodeName == 'parameterlist':
+ groups['params'].append(child)
+ elif child.nodeName == 'xrefsect':
+ groups['xrefs'].append(child)
+ elif child.nodeName == 'simplesect':
+ last = kind
+ kind = child.getAttribute('kind')
+ if kind == 'return' or (kind == 'note' and last == 'return'):
+ groups['return'].append(child)
+ elif kind == 'see':
+ groups['seealso'].append(child)
+ elif kind in ('note', 'warning'):
+ text += render_node(child, text, indent=indent, width=width)
+ else:
+ raise RuntimeError('unhandled simplesect: {}\n{}'.format(
+ child.nodeName, child.toprettyxml(indent=' ', newl='\n')))
+ else:
+ text += render_node(child, text, indent=indent, width=width)
+
+ chunks = [text]
+ # Generate text from the gathered items.
+ if len(groups['params']) > 0 and has_nonexcluded_params(groups['params']):
+ chunks.append('\nParameters: ~')
+ for child in groups['params']:
+ chunks.append(render_params(child, width=width))
+ if len(groups['return']) > 0:
+ chunks.append('\nReturn: ~')
+ for child in groups['return']:
+ chunks.append(render_node(child, chunks[-1][-1], indent=indent, width=width))
+ if len(groups['seealso']) > 0:
+ chunks.append('\nSee also: ~')
+ for child in groups['seealso']:
+ chunks.append(render_node(child, chunks[-1][-1], indent=indent, width=width))
+ for child in groups['xrefs']:
+ title = get_text(get_child(child, 'xreftitle'))
+ xrefs.add(title)
+ xrefdesc = render_para(get_child(child, 'xrefdescription'), width=width)
+ chunks.append(doc_wrap(xrefdesc, prefix='{}: '.format(title),
+ width=width) + '\n')
+
+ return clean_lines('\n'.join(chunks).strip())
+
+
+def parse_parblock(parent, prefix='', width=62, indent=''):
+ """Renders a nested block of <para> tags as Vim help text."""
+ paragraphs = []
+ for child in parent.childNodes:
+ paragraphs.append(render_para(child, width=width, indent=indent))
+ paragraphs.append('')
+ return clean_lines('\n'.join(paragraphs).strip())
+# }}}
+
+
+def parse_source_xml(filename, mode):
+ """Collects API functions.
+
+ Returns two strings:
+ 1. API functions
+ 2. Deprecated API functions
+
+ Caller decides what to do with the deprecated documentation.
+ """
+ global xrefs
+ xrefs = set()
+ functions = []
+ deprecated_functions = []
+
+ dom = minidom.parse(filename)
+ compoundname = get_text(dom.getElementsByTagName('compoundname')[0])
+ for member in dom.getElementsByTagName('memberdef'):
+ if member.getAttribute('static') == 'yes' or \
+ member.getAttribute('kind') != 'function' or \
+ member.getAttribute('prot') == 'private' or \
+ get_text(get_child(member, 'name')).startswith('_'):
+ continue
+
+ loc = find_first(member, 'location')
+ if 'private' in loc.getAttribute('file'):
+ continue
+
+ return_type = get_text(get_child(member, 'type'))
+ if return_type == '':
+ continue
+
+ if return_type.startswith(('ArrayOf', 'DictionaryOf')):
+ parts = return_type.strip('_').split('_')
+ return_type = '{}({})'.format(parts[0], ', '.join(parts[1:]))
+
+ name = get_text(get_child(member, 'name'))
+
+ annotations = get_text(get_child(member, 'argsstring'))
+ if annotations and ')' in annotations:
+ annotations = annotations.rsplit(')', 1)[-1].strip()
+ # XXX: (doxygen 1.8.11) 'argsstring' only includes attributes of
+ # non-void functions. Special-case void functions here.
+ if name == 'nvim_get_mode' and len(annotations) == 0:
+ annotations += 'FUNC_API_ASYNC'
+ annotations = filter(None, map(lambda x: annotation_map.get(x),
+ annotations.split()))
+
+ if mode == 'lua':
+ fstem = compoundname.split('.')[0]
+ fstem = CONFIG[mode]['module_override'].get(fstem, fstem)
+ vimtag = '*{}.{}()*'.format(fstem, name)
+ else:
+ vimtag = '*{}()*'.format(name)
+
+ params = []
+ type_length = 0
+
+ for param in get_children(member, 'param'):
+ param_type = get_text(get_child(param, 'type')).strip()
+ param_name = ''
+ declname = get_child(param, 'declname')
+ if declname:
+ param_name = get_text(declname).strip()
+ elif mode == 'lua':
+ # that's how it comes out of lua2dox
+ param_name = param_type
+ param_type = ''
+
+ if param_name in param_exclude:
+ continue
+
+ if param_type.endswith('*'):
+ param_type = param_type.strip('* ')
+ param_name = '*' + param_name
+ type_length = max(type_length, len(param_type))
+ params.append((param_type, param_name))
+
+ c_args = []
+ for param_type, param_name in params:
+ c_args.append(' ' + (
+ '%s %s' % (param_type.ljust(type_length), param_name)).strip())
+
+ c_decl = textwrap.indent('%s %s(\n%s\n);' % (return_type, name,
+ ',\n'.join(c_args)),
+ ' ')
+
+ prefix = '%s(' % name
+ suffix = '%s)' % ', '.join('{%s}' % a[1] for a in params
+ if a[0] not in ('void', 'Error'))
+
+ # Minimum 8 chars between signature and vimtag
+ lhs = (text_width - 8) - len(prefix)
+
+ if len(prefix) + len(suffix) > lhs:
+ signature = vimtag.rjust(text_width) + '\n'
+ signature += doc_wrap(suffix, width=text_width-8, prefix=prefix,
+ func=True)
+ else:
+ signature = prefix + suffix
+ signature += vimtag.rjust(text_width - len(signature))
+
+ doc = ''
+ desc = find_first(member, 'detaileddescription')
+ if desc:
+ doc = parse_parblock(desc)
+ if DEBUG:
+ print(textwrap.indent(
+ re.sub(r'\n\s*\n+', '\n',
+ desc.toprettyxml(indent=' ', newl='\n')), ' ' * 16))
+
+ if not doc:
+ doc = 'TODO: Documentation'
+
+ annotations = '\n'.join(annotations)
+ if annotations:
+ annotations = ('\n\nAttributes: ~\n' +
+ textwrap.indent(annotations, ' '))
+ i = doc.rfind('Parameters: ~')
+ if i == -1:
+ doc += annotations
+ else:
+ doc = doc[:i] + annotations + '\n\n' + doc[i:]
+
+ if INCLUDE_C_DECL:
+ doc += '\n\nC Declaration: ~\n>\n'
+ doc += c_decl
+ doc += '\n<'
+
+ func_doc = signature + '\n'
+ func_doc += textwrap.indent(clean_lines(doc), ' ' * 16)
+ func_doc = re.sub(r'^\s+([<>])$', r'\1', func_doc, flags=re.M)
+
+ if 'Deprecated' in xrefs:
+ deprecated_functions.append(func_doc)
+ elif name.startswith(CONFIG[mode]['func_name_prefix']):
+ functions.append(func_doc)
+
+ xrefs.clear()
+
+ return '\n\n'.join(functions), '\n\n'.join(deprecated_functions)
+
+
+def delete_lines_below(filename, tokenstr):
+ """Deletes all lines below the line containing `tokenstr`, the line itself,
+ and one line above it.
+ """
+ lines = open(filename).readlines()
+ i = 0
+ for i, line in enumerate(lines, 1):
+ if tokenstr in line:
+ break
+ i = max(0, i - 2)
+ with open(filename, 'wt') as fp:
+ fp.writelines(lines[0:i])
+
+def gen_docs(config):
+ """Generate documentation.
+
+ Doxygen is called and configured through stdin.
+ """
+ for mode in CONFIG:
+ output_dir = out_dir.format(mode=mode)
+ p = subprocess.Popen(['doxygen', '-'], stdin=subprocess.PIPE)
+ p.communicate(
+ config.format(
+ input=CONFIG[mode]['files'],
+ output=output_dir,
+ filter=filter_cmd,
+ file_patterns=CONFIG[mode]['file_patterns'])
+ .encode('utf8')
+ )
+ if p.returncode:
+ sys.exit(p.returncode)
+
+ sections = {}
+ intros = {}
+ sep = '=' * text_width
+
+ base = os.path.join(output_dir, 'xml')
+ dom = minidom.parse(os.path.join(base, 'index.xml'))
+
+ # generate docs for section intros
+ for compound in dom.getElementsByTagName('compound'):
+ if compound.getAttribute('kind') != 'group':
+ continue
+
+ groupname = get_text(find_first(compound, 'name'))
+ groupxml = os.path.join(base, '%s.xml' % compound.getAttribute('refid'))
+
+ desc = find_first(minidom.parse(groupxml), 'detaileddescription')
+ if desc:
+ doc = parse_parblock(desc)
+ if doc:
+ intros[groupname] = doc
+
+ for compound in dom.getElementsByTagName('compound'):
+ if compound.getAttribute('kind') != 'file':
+ continue
+
+ filename = get_text(find_first(compound, 'name'))
+ if filename.endswith('.c') or filename.endswith('.lua'):
+ functions, deprecated = parse_source_xml(
+ os.path.join(base, '%s.xml' %
+ compound.getAttribute('refid')), mode)
+
+ if not functions and not deprecated:
+ continue
+
+ if functions or deprecated:
+ name = os.path.splitext(os.path.basename(filename))[0]
+ if name == 'ui':
+ name = name.upper()
+ else:
+ name = name.title()
+
+ doc = ''
+
+ intro = intros.get('api-%s' % name.lower())
+ if intro:
+ doc += '\n\n' + intro
+
+ if functions:
+ doc += '\n\n' + functions
+
+ if INCLUDE_DEPRECATED and deprecated:
+ doc += '\n\n\nDeprecated %s Functions: ~\n\n' % name
+ doc += deprecated
+
+ if doc:
+ filename = os.path.basename(filename)
+ name = CONFIG[mode]['section_name'].get(filename, name)
+
+ if mode == 'lua':
+ title = 'Lua module: {}'.format(name.lower())
+ helptag = '*lua-{}*'.format(name.lower())
+ else:
+ title = '{} Functions'.format(name)
+ helptag = '*api-{}*'.format(name.lower())
+ sections[filename] = (title, helptag, doc)
+
+ if not sections:
+ return
+
+ docs = ''
+
+ i = 0
+ for filename in CONFIG[mode]['section_order']:
+ if filename not in sections:
+ raise RuntimeError('found new module "{}"; update the "section_order" map'.format(filename))
+ title, helptag, section_doc = sections.pop(filename)
+ i += 1
+ if filename not in CONFIG[mode]['append_only']:
+ docs += sep
+ docs += '\n%s%s' % (title, helptag.rjust(text_width - len(title)))
+ docs += section_doc
+ docs += '\n\n\n'
+
+ docs = docs.rstrip() + '\n\n'
+ docs += ' vim:tw=78:ts=8:ft=help:norl:\n'
+
+ doc_file = os.path.join(base_dir, 'runtime', 'doc',
+ CONFIG[mode]['filename'])
+
+ delete_lines_below(doc_file, CONFIG[mode]['section_start_token'])
+ with open(doc_file, 'ab') as fp:
+ fp.write(docs.encode('utf8'))
+
+ shutil.rmtree(output_dir)
+
+
+def filter_source(filename):
+ name, extension = os.path.splitext(filename)
+ if extension == '.lua':
+ p = subprocess.run([lua2dox_filter, filename], stdout=subprocess.PIPE)
+ op = ('?' if 0 != p.returncode else p.stdout.decode('utf-8'))
+ print(op)
+ else:
+ """Filters the source to fix macros that confuse Doxygen."""
+ with open(filename, 'rt') as fp:
+ print(re.sub(r'^(ArrayOf|DictionaryOf)(\(.*?\))',
+ lambda m: m.group(1)+'_'.join(
+ re.split(r'[^\w]+', m.group(2))),
+ fp.read(), flags=re.M))
+
+
+# Doxygen Config {{{
+Doxyfile = '''
+OUTPUT_DIRECTORY = {output}
+INPUT = {input}
+INPUT_ENCODING = UTF-8
+FILE_PATTERNS = {file_patterns}
+RECURSIVE = YES
+INPUT_FILTER = "{filter}"
+EXCLUDE =
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS = */private/*
+EXCLUDE_SYMBOLS =
+EXTENSION_MAPPING = lua=C
+EXTRACT_PRIVATE = NO
+
+GENERATE_HTML = NO
+GENERATE_DOCSET = NO
+GENERATE_HTMLHELP = NO
+GENERATE_QHP = NO
+GENERATE_TREEVIEW = NO
+GENERATE_LATEX = NO
+GENERATE_RTF = NO
+GENERATE_MAN = NO
+GENERATE_DOCBOOK = NO
+GENERATE_AUTOGEN_DEF = NO
+
+GENERATE_XML = YES
+XML_OUTPUT = xml
+XML_PROGRAMLISTING = NO
+
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = NO
+MARKDOWN_SUPPORT = YES
+'''
+# }}}
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1:
+ filter_source(sys.argv[1])
+ else:
+ gen_docs(Doxyfile)
+
+# vim: set ft=python ts=4 sw=4 tw=79 et fdm=marker :
diff --git a/scripts/genappimage.sh b/scripts/genappimage.sh
index c0ef2a8591..cc88ab5559 100755
--- a/scripts/genappimage.sh
+++ b/scripts/genappimage.sh
@@ -37,29 +37,24 @@ VERSION=$("$ROOT_DIR"/build/bin/nvim --version | head -n 1 | grep -o 'v.*')
cd "$APP_BUILD_DIR"
-curl -Lo "$APP_BUILD_DIR"/appimage_functions.sh https://github.com/AppImage/AppImages/raw/master/functions.sh
-. ./appimage_functions.sh
-
-# Copy desktop and icon file to AppDir for AppRun to pick them up.
-# get_apprun
-# get_desktop
-cp "$ROOT_DIR/runtime/nvim.desktop" "$APP_DIR/"
-cp "$ROOT_DIR/runtime/nvim.png" "$APP_DIR/"
+# Only downloads linuxdeploy if the remote file is different from local
+if [ -e "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage ]; then
+ curl -Lo "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage \
+ -z "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage \
+ https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
+else
+ curl -Lo "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage \
+ https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
+fi
+
+chmod +x "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage
+
+# metainfo is not packaged automatically by linuxdeploy
mkdir "$APP_DIR/usr/share/metainfo/"
cp "$ROOT_DIR/runtime/nvim.appdata.xml" "$APP_DIR/usr/share/metainfo/"
cd "$APP_DIR"
-# copy dependencies
-copy_deps
-# Move the libraries to usr/bin
-move_lib
-
-# Delete stuff that should not go into the AppImage.
-# Delete dangerous libraries; see
-# https://github.com/AppImage/AppImages/blob/master/excludelist
-delete_blacklisted
-
########################################################################
# AppDir complete. Now package it as an AppImage.
########################################################################
@@ -78,21 +73,24 @@ chmod 755 AppRun
cd "$APP_BUILD_DIR" # Get out of AppImage directory.
+# Set the name of the file generated by appimage
+export OUTPUT=nvim.appimage
+
+# If it's a release generate the zsync file
+if [ -n "$TAG" ]; then
+ export UPDATE_INFORMATION="gh-releases-zsync|neovim|neovim|$TAG|nvim.appimage.zsync"
+fi
+
# Generate AppImage.
# - Expects: $ARCH, $APP, $VERSION env vars
# - Expects: ./$APP.AppDir/ directory
-# - Produces: ../out/$APP-$VERSION.glibc$GLIBC_NEEDED-$ARCH.AppImage
-if [ -n "$TAG" ]; then
- generate_type2_appimage -u "gh-releases-zsync|neovim|neovim|$TAG|nvim.appimage.zsync"
-else
- generate_type2_appimage
-fi
+# - Produces: ./nvim.appimage
+./linuxdeploy-x86_64.AppImage --appdir $APP.AppDir -d $ROOT_DIR/runtime/nvim.desktop -i \
+"$ROOT_DIR/runtime/nvim.png" --output appimage
# Moving the final executable to a different folder so it isn't in the
# way for a subsequent build.
-mv "$ROOT_DIR"/out/*.AppImage* "$ROOT_DIR"/build/bin
-# Remove the (now empty) folder the AppImage was built in
-rmdir "$ROOT_DIR"/out
+mv "$ROOT_DIR"/build/nvim.appimage* "$ROOT_DIR"/build/bin
echo 'genappimage.sh: finished'
diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua
new file mode 100644
index 0000000000..ae76a36d50
--- /dev/null
+++ b/scripts/lua2dox.lua
@@ -0,0 +1,666 @@
+--[[--------------------------------------------------------------------------
+-- Copyright (C) 2012 by Simon Dales --
+-- simon@purrsoft.co.uk --
+-- --
+-- This program 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 2 of the License, or --
+-- (at your option) any later version. --
+-- --
+-- This program 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 this program; if not, write to the --
+-- Free Software Foundation, Inc., --
+-- 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. --
+----------------------------------------------------------------------------]]
+--[[!
+\file
+\brief a hack lua2dox converter
+]]
+
+--[[!
+\mainpage
+
+Introduction
+------------
+
+A hack lua2dox converter
+Version 0.2
+
+This lets us make Doxygen output some documentation to let
+us develop this code.
+
+It is partially cribbed from the functionality of lua2dox
+(http://search.cpan.org/~alec/Doxygen-Lua-0.02/lib/Doxygen/Lua.pm).
+Found on CPAN when looking for something else; kinda handy.
+
+Improved from lua2dox to make the doxygen output more friendly.
+Also it runs faster in lua rather than Perl.
+
+Because this Perl based system is called "lua2dox"., I have decided to add ".lua" to the name
+to keep the two separate.
+
+Running
+-------
+
+<ol>
+<li> Ensure doxygen is installed on your system and that you are familiar with its use.
+Best is to try to make and document some simple C/C++/PHP to see what it produces.
+You can experiment with the enclosed example code.
+
+<li> Run "doxygen -g" to create a default Doxyfile.
+
+Then alter it to let it recognise lua. Add the two following lines:
+
+\code{.bash}
+FILE_PATTERNS = *.lua
+
+FILTER_PATTERNS = *.lua=lua2dox_filter
+\endcode
+
+
+Either add them to the end or find the appropriate entry in Doxyfile.
+
+There are other lines that you might like to alter, but see futher documentation for details.
+
+<li> When Doxyfile is edited run "doxygen"
+
+The core function reads the input file (filename or stdin) and outputs some pseudo C-ish language.
+It only has to be good enough for doxygen to see it as legal.
+Therefore our lua interpreter is fairly limited, but "good enough".
+
+One limitation is that each line is treated separately (except for long comments).
+The implication is that class and function declarations must be on the same line.
+Some functions can have their parameter lists extended over multiple lines to make it look neat.
+Managing this where there are also some comments is a bit more coding than I want to do at this stage,
+so it will probably not document accurately if we do do this.
+
+However I have put in a hack that will insert the "missing" close paren.
+The effect is that you will get the function documented, but not with the parameter list you might expect.
+</ol>
+
+Installation
+------------
+
+Here for linux or unix-like, for any other OS you need to refer to other documentation.
+
+This file is "lua2dox.lua". It gets called by "lua2dox_filter"(bash).
+Somewhere in your path (e.g. "~/bin" or "/usr/local/bin") put a link to "lua2dox_filter".
+
+Documentation
+-------------
+
+Read the external documentation that should be part of this package.
+For example look for the "README" and some .PDFs.
+
+]]
+
+-- we won't use our library code, so this becomes more portable
+
+-- require 'elijah_fix_require'
+-- require 'elijah_class'
+--
+--! \brief ``declare'' as class
+--!
+--! use as:
+--! \code{.lua}
+--! TWibble = class()
+--! function TWibble.init(this,Str)
+--! this.str = Str
+--! -- more stuff here
+--! end
+--! \endcode
+--!
+function class(BaseClass, ClassInitialiser)
+ local newClass = {} -- a new class newClass
+ if not ClassInitialiser and type(BaseClass) == 'function' then
+ ClassInitialiser = BaseClass
+ BaseClass = nil
+ elseif type(BaseClass) == 'table' then
+ -- our new class is a shallow copy of the base class!
+ for i,v in pairs(BaseClass) do
+ newClass[i] = v
+ end
+ newClass._base = BaseClass
+ end
+ -- the class will be the metatable for all its newInstanceects,
+ -- and they will look up their methods in it.
+ newClass.__index = newClass
+
+ -- expose a constructor which can be called by <classname>(<args>)
+ local classMetatable = {}
+ classMetatable.__call =
+ function(class_tbl, ...)
+ local newInstance = {}
+ setmetatable(newInstance,newClass)
+ --if init then
+ -- init(newInstance,...)
+ if class_tbl.init then
+ class_tbl.init(newInstance,...)
+ else
+ -- make sure that any stuff from the base class is initialized!
+ if BaseClass and BaseClass.init then
+ BaseClass.init(newInstance, ...)
+ end
+ end
+ return newInstance
+ end
+ newClass.init = ClassInitialiser
+ newClass.is_a =
+ function(this, klass)
+ local thisMetatable = getmetatable(this)
+ while thisMetatable do
+ if thisMetatable == klass then
+ return true
+ end
+ thisMetatable = thisMetatable._base
+ end
+ return false
+ end
+ setmetatable(newClass, classMetatable)
+ return newClass
+end
+
+-- require 'elijah_clock'
+
+--! \class TCore_Clock
+--! \brief a clock
+TCore_Clock = class()
+
+--! \brief get the current time
+function TCore_Clock.GetTimeNow()
+ if os.gettimeofday then
+ return os.gettimeofday()
+ else
+ return os.time()
+ end
+end
+
+--! \brief constructor
+function TCore_Clock.init(this,T0)
+ if T0 then
+ this.t0 = T0
+ else
+ this.t0 = TCore_Clock.GetTimeNow()
+ end
+end
+
+--! \brief get time string
+function TCore_Clock.getTimeStamp(this,T0)
+ local t0
+ if T0 then
+ t0 = T0
+ else
+ t0 = this.t0
+ end
+ return os.date('%c %Z',t0)
+end
+
+
+--require 'elijah_io'
+
+--! \class TCore_IO
+--! \brief io to console
+--!
+--! pseudo class (no methods, just to keep documentation tidy)
+TCore_IO = class()
+--
+--! \brief write to stdout
+function TCore_IO_write(Str)
+ if (Str) then
+ io.write(Str)
+ end
+end
+
+--! \brief write to stdout
+function TCore_IO_writeln(Str)
+ if (Str) then
+ io.write(Str)
+ end
+ io.write("\n")
+end
+
+
+--require 'elijah_string'
+
+--! \brief trims a string
+function string_trim(Str)
+ return Str:match("^%s*(.-)%s*$")
+end
+
+--! \brief split a string
+--!
+--! \param Str
+--! \param Pattern
+--! \returns table of string fragments
+function string_split(Str, Pattern)
+ local splitStr = {}
+ local fpat = "(.-)" .. Pattern
+ local last_end = 1
+ local str, e, cap = string.find(Str,fpat, 1)
+ while str do
+ if str ~= 1 or cap ~= "" then
+ table.insert(splitStr,cap)
+ end
+ last_end = e+1
+ str, e, cap = string.find(Str,fpat, last_end)
+ end
+ if last_end <= #Str then
+ cap = string.sub(Str,last_end)
+ table.insert(splitStr, cap)
+ end
+ return splitStr
+end
+
+
+--require 'elijah_commandline'
+
+--! \class TCore_Commandline
+--! \brief reads/parses commandline
+TCore_Commandline = class()
+
+--! \brief constructor
+function TCore_Commandline.init(this)
+ this.argv = arg
+ this.parsed = {}
+ this.params = {}
+end
+
+--! \brief get value
+function TCore_Commandline.getRaw(this,Key,Default)
+ local val = this.argv[Key]
+ if not val then
+ val = Default
+ end
+ return val
+end
+
+
+--require 'elijah_debug'
+
+-------------------------------
+--! \brief file buffer
+--!
+--! an input file buffer
+TStream_Read = class()
+
+--! \brief get contents of file
+--!
+--! \param Filename name of file to read (or nil == stdin)
+function TStream_Read.getContents(this,Filename)
+ -- get lines from file
+ local filecontents
+ if Filename then
+ -- syphon lines to our table
+ --TCore_Debug_show_var('Filename',Filename)
+ filecontents={}
+ for line in io.lines(Filename) do
+ table.insert(filecontents,line)
+ end
+ else
+ -- get stuff from stdin as a long string (with crlfs etc)
+ filecontents=io.read('*a')
+ -- make it a table of lines
+ filecontents = TString_split(filecontents,'[\n]') -- note this only works for unix files.
+ Filename = 'stdin'
+ end
+
+ if filecontents then
+ this.filecontents = filecontents
+ this.contentsLen = #filecontents
+ this.currentLineNo = 1
+ end
+
+ return filecontents
+end
+
+--! \brief get lineno
+function TStream_Read.getLineNo(this)
+ return this.currentLineNo
+end
+
+--! \brief get a line
+function TStream_Read.getLine(this)
+ local line
+ if this.currentLine then
+ line = this.currentLine
+ this.currentLine = nil
+ else
+ -- get line
+ if this.currentLineNo<=this.contentsLen then
+ line = this.filecontents[this.currentLineNo]
+ this.currentLineNo = this.currentLineNo + 1
+ else
+ line = ''
+ end
+ end
+ return line
+end
+
+--! \brief save line fragment
+function TStream_Read.ungetLine(this,LineFrag)
+ this.currentLine = LineFrag
+end
+
+--! \brief is it eof?
+function TStream_Read.eof(this)
+ if this.currentLine or this.currentLineNo<=this.contentsLen then
+ return false
+ end
+ return true
+end
+
+--! \brief output stream
+TStream_Write = class()
+
+--! \brief constructor
+function TStream_Write.init(this)
+ this.tailLine = {}
+end
+
+--! \brief write immediately
+function TStream_Write.write(this,Str)
+ TCore_IO_write(Str)
+end
+
+--! \brief write immediately
+function TStream_Write.writeln(this,Str)
+ TCore_IO_writeln(Str)
+end
+
+--! \brief write immediately
+function TStream_Write.writelnComment(this,Str)
+ TCore_IO_write('// ZZ: ')
+ TCore_IO_writeln(Str)
+end
+
+--! \brief write to tail
+function TStream_Write.writelnTail(this,Line)
+ if not Line then
+ Line = ''
+ end
+ table.insert(this.tailLine,Line)
+end
+
+--! \brief outout tail lines
+function TStream_Write.write_tailLines(this)
+ for k,line in ipairs(this.tailLine) do
+ TCore_IO_writeln(line)
+ end
+ TCore_IO_write('// Lua2DoX new eof')
+end
+
+--! \brief input filter
+TLua2DoX_filter = class()
+
+--! \brief allow us to do errormessages
+function TLua2DoX_filter.warning(this,Line,LineNo,Legend)
+ this.outStream:writelnTail(
+ '//! \todo warning! ' .. Legend .. ' (@' .. LineNo .. ')"' .. Line .. '"'
+ )
+end
+
+--! \brief trim comment off end of string
+--!
+--! If the string has a comment on the end, this trims it off.
+--!
+local function TString_removeCommentFromLine(Line)
+ local pos_comment = string.find(Line,'%-%-')
+ local tailComment
+ if pos_comment then
+ Line = string.sub(Line,1,pos_comment-1)
+ tailComment = string.sub(Line,pos_comment)
+ end
+ return Line,tailComment
+end
+
+--! \brief get directive from magic
+local function getMagicDirective(Line)
+ local macro,tail
+ local macroStr = '[\\@]'
+ local pos_macro = string.find(Line,macroStr)
+ if pos_macro then
+ --! ....\\ macro...stuff
+ --! ....\@ macro...stuff
+ local line = string.sub(Line,pos_macro+1)
+ local space = string.find(line,'%s+')
+ if space then
+ macro = string.sub(line,1,space-1)
+ tail = string_trim(string.sub(line,space+1))
+ else
+ macro = line
+ tail = ''
+ end
+ end
+ return macro,tail
+end
+
+--! \brief check comment for fn
+local function checkComment4fn(Fn_magic,MagicLines)
+ local fn_magic = Fn_magic
+ -- TCore_IO_writeln('// checkComment4fn "' .. MagicLines .. '"')
+
+ local magicLines = string_split(MagicLines,'\n')
+
+ local macro,tail
+
+ for k,line in ipairs(magicLines) do
+ macro,tail = getMagicDirective(line)
+ if macro == 'fn' then
+ fn_magic = tail
+ -- TCore_IO_writeln('// found fn "' .. fn_magic .. '"')
+ else
+ --TCore_IO_writeln('// not found fn "' .. line .. '"')
+ end
+ end
+
+ return fn_magic
+end
+--! \brief run the filter
+function TLua2DoX_filter.readfile(this,AppStamp,Filename)
+ local err
+
+ local inStream = TStream_Read()
+ local outStream = TStream_Write()
+ this.outStream = outStream -- save to this obj
+
+ if (inStream:getContents(Filename)) then
+ -- output the file
+ local line
+ local fn_magic -- function name/def from magic comment
+
+ outStream:writelnTail('// #######################')
+ outStream:writelnTail('// app run:' .. AppStamp)
+ outStream:writelnTail('// #######################')
+ outStream:writelnTail()
+
+ while not (err or inStream:eof()) do
+ line = string_trim(inStream:getLine())
+ -- TCore_Debug_show_var('inStream',inStream)
+ -- TCore_Debug_show_var('line',line )
+ if string.sub(line,1,2)=='--' then -- its a comment
+ if string.sub(line,3,3)=='@' then -- it's a magic comment
+ local magic = string.sub(line,4)
+ outStream:writeln('/// @' .. magic)
+ fn_magic = checkComment4fn(fn_magic,magic)
+ elseif string.sub(line,3,3)=='-' then -- it's a nonmagic doc comment
+ local comment = string.sub(line,4)
+ outStream:writeln('/// '.. comment)
+ elseif string.sub(line,3,4)=='[[' then -- it's a long comment
+ line = string.sub(line,5) -- nibble head
+ local comment = ''
+ local closeSquare,hitend,thisComment
+ while (not err) and (not hitend) and (not inStream:eof()) do
+ closeSquare = string.find(line,']]')
+ if not closeSquare then -- need to look on another line
+ thisComment = line .. '\n'
+ line = inStream:getLine()
+ else
+ thisComment = string.sub(line,1,closeSquare-1)
+ hitend = true
+
+ -- unget the tail of the line
+ -- in most cases it's empty. This may make us less efficient but
+ -- easier to program
+ inStream:ungetLine(string_trim(string.sub(line,closeSquare+2)))
+ end
+ comment = comment .. thisComment
+ end
+ if string.sub(comment,1,1)=='@' then -- it's a long magic comment
+ outStream:write('/*' .. comment .. '*/ ')
+ fn_magic = checkComment4fn(fn_magic,comment)
+ else -- discard
+ outStream:write('/* zz:' .. comment .. '*/ ')
+ fn_magic = nil
+ end
+ else
+ outStream:writeln('// zz:"' .. line .. '"')
+ fn_magic = nil
+ end
+ elseif string.find(line,'^function') or string.find(line,'^local%s+function') then
+ -- it's a function
+ local pos_fn = string.find(line,'function')
+ -- function
+ -- ....v...
+ if pos_fn then
+ -- we've got a function
+ local fn_type
+ if string.find(line,'^local%s+') then
+ fn_type = ''--'static ' -- static functions seem to be excluded
+ else
+ fn_type = ''
+ end
+ local fn = TString_removeCommentFromLine(string_trim(string.sub(line,pos_fn+8)))
+ if fn_magic then
+ fn = fn_magic
+ fn_magic = nil
+ end
+
+ if string.sub(fn,1,1)=='(' then
+ -- it's an anonymous function
+ outStream:writelnComment(line)
+ else
+ -- fn has a name, so is interesting
+
+ -- want to fix for iffy declarations
+ local open_paren = string.find(fn,'[%({]')
+ local fn0 = fn
+ if open_paren then
+ fn0 = string.sub(fn,1,open_paren-1)
+ -- we might have a missing close paren
+ if not string.find(fn,'%)') then
+ fn = fn .. ' ___MissingCloseParenHere___)'
+ end
+ end
+
+ local dot = string.find(fn0,'[%.:]')
+ if dot then -- it's a method
+ local klass = string.sub(fn,1,dot-1)
+ local method = string.sub(fn,dot+1)
+ --TCore_IO_writeln('function ' .. klass .. '::' .. method .. ftail .. '{}')
+ --TCore_IO_writeln(klass .. '::' .. method .. ftail .. '{}')
+ outStream:writeln(
+ '/*! \\memberof ' .. klass .. ' */ '
+ .. method .. '{}'
+ )
+ else
+ -- add vanilla function
+
+ outStream:writeln(fn_type .. 'function ' .. fn .. '{}')
+ end
+ end
+ else
+ this:warning(inStream:getLineNo(),'something weird here')
+ end
+ fn_magic = nil -- mustn't indavertently use it again
+ elseif string.find(line,'=%s*class%(') then
+ -- it's a class declaration
+ local tailComment
+ line,tailComment = TString_removeCommentFromLine(line)
+ local equals = string.find(line,'=')
+ local klass = string_trim(string.sub(line,1,equals-1))
+ local tail = string_trim(string.sub(line,equals+1))
+ -- class(wibble wibble)
+ -- ....v.
+ local parent = string.sub(tail,7,-2)
+ if #parent>0 then
+ parent = ' :public ' .. parent
+ end
+ outStream:writeln('class ' .. klass .. parent .. '{};')
+ else
+ -- we don't know what this line means, so we can probably just comment it out
+ if #line>0 then
+ outStream:writeln('// zz: ' .. line)
+ else
+ outStream:writeln() -- keep this line blank
+ end
+ end
+ end
+
+ -- output the tail
+ outStream:write_tailLines()
+ else
+ outStream:writeln('!empty file')
+ end
+end
+
+--! \brief this application
+TApp = class()
+
+--! \brief constructor
+function TApp.init(this)
+ local t0 = TCore_Clock()
+ this.timestamp = t0:getTimeStamp()
+ this.name = 'Lua2DoX'
+ this.version = '0.2 20130128'
+ this.copyright = 'Copyright (c) Simon Dales 2012-13'
+end
+
+function TApp.getRunStamp(this)
+ return this.name .. ' (' .. this.version .. ') '
+ .. this.timestamp
+end
+
+function TApp.getVersion(this)
+ return this.name .. ' (' .. this.version .. ') '
+end
+
+function TApp.getCopyright(this)
+ return this.copyright
+end
+
+local This_app = TApp()
+
+--main
+local cl = TCore_Commandline()
+
+local argv1 = cl:getRaw(2)
+if argv1 == '--help' then
+ TCore_IO_writeln(This_app:getVersion())
+ TCore_IO_writeln(This_app:getCopyright())
+ TCore_IO_writeln([[
+ run as:
+ lua2dox_filter <param>
+ --------------
+ Param:
+ <filename> : interprets filename
+ --version : show version/copyright info
+ --help : this help text]])
+elseif argv1 == '--version' then
+ TCore_IO_writeln(This_app:getVersion())
+ TCore_IO_writeln(This_app:getCopyright())
+else
+ -- it's a filter
+ local appStamp = This_app:getRunStamp()
+ local filename = argv1
+
+ local filter = TLua2DoX_filter()
+ filter:readfile(appStamp,filename)
+end
+
+
+--eof
diff --git a/scripts/lua2dox_filter b/scripts/lua2dox_filter
new file mode 100755
index 0000000000..6cb16ef060
--- /dev/null
+++ b/scripts/lua2dox_filter
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+###########################################################################
+# Copyright (C) 2012 by Simon Dales #
+# simon@purrsoft.co.uk #
+# #
+# This program 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 2 of the License, or #
+# (at your option) any later version. #
+# #
+# This program 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 this program; if not, write to the #
+# Free Software Foundation, Inc., #
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #
+###########################################################################
+LANG=""
+
+##! \brief test executable to see if it exists
+test_executable(){
+ P_EXE="$1"
+ #########
+ WHICH=`which ${P_EXE}`
+ if test -z "${WHICH}"
+ then
+ echo "not found \"${P_EXE}\""
+ else
+ EXE="${P_EXE}"
+ fi
+ }
+
+##! \brief sets the lua interpreter
+set_lua(){
+ test_executable 'texlua'
+ if test -z "${EXE}"
+ then
+ test_executable 'lua'
+ fi
+ #echo "final EXE=\"${EXE}\""
+ }
+
+##! \brief makes canonical name of file
+##!
+##! Note that "readlink -f" doesn't work in MacOSX
+##!
+do_readlink(){
+ pushd . > /dev/null
+ TARGET_FILE=$1
+
+ cd `dirname $TARGET_FILE`
+ TARGET_FILE=`basename $TARGET_FILE`
+
+ # Iterate down a (possible) chain of symlinks
+ while [ -L "$TARGET_FILE" ]
+ do
+ TARGET_FILE=`readlink $TARGET_FILE`
+ cd `dirname $TARGET_FILE`
+ TARGET_FILE=`basename $TARGET_FILE`
+ done
+
+ PHYS_DIR=`pwd -P`
+ RESULT=$PHYS_DIR
+ popd > /dev/null
+ }
+
+##main
+set_lua
+if test -z "${EXE}"
+then
+ echo "no lua interpreter available"
+else
+ BASENAME=`basename "$0"`
+ do_readlink "$0"
+ DIRNAME="${RESULT}"
+
+ LUASCRIPT="${DIRNAME}/lua2dox.lua ${BASENAME}"
+ #echo "lua[${LUASCRIPT}]"
+
+ ${EXE} ${LUASCRIPT} $@
+fi
+#
+##eof
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index 7339fdc691..eeb77dbbdc 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -357,13 +357,12 @@ list_vim_commits() { (
# Prints all (sorted) "vim-patch:xxx" tokens found in the Nvim git log.
list_vimpatch_tokens() {
- local tokens
- # Find all "vim-patch:xxx" tokens in the Nvim git log.
- tokens="$(cd "${NVIM_SOURCE_DIR}" && git log -E --grep='vim-patch:[^ ]+' | grep 'vim-patch')"
- echo "$tokens" | grep -E 'vim-patch:[^ ,{]{7,}' \
- | sed 's/.*\(vim-patch:[.0-9a-z]\+\).*/\1/' \
+ # Use sed…{7,7} to normalize (internal) Git hashes (for tokens caches).
+ git -C "${NVIM_SOURCE_DIR}" log -E --grep='vim-patch:[^ ,{]{7,}' \
+ | grep -oE 'vim-patch:[^ ,{:]{7,}' \
| sort \
- | uniq
+ | uniq \
+ | sed -nE 's/^(vim-patch:([0-9]+\.[^ ]+|[0-9a-z]{7,7})).*/\1/p'
}
# Prints all patch-numbers (for the current v:version) for which there is
@@ -377,29 +376,31 @@ list_vimpatch_numbers() {
# Prints a newline-delimited list of Vim commits, for use by scripts.
list_missing_vimpatches() {
- local tokens vim_commit vim_commits is_missing vim_tag patch_number
+ local token vim_commit vim_tag patch_number
+ declare -A tokens
# Find all "vim-patch:xxx" tokens in the Nvim git log.
- tokens="$(list_vimpatch_tokens)"
+ for token in $(list_vimpatch_tokens); do
+ tokens[$token]=1
+ done
# Get missing Vim commits
- vim_commits="$(list_vim_commits)"
- for vim_commit in ${vim_commits}; do
+ for vim_commit in $(list_vim_commits); do
# Check for vim-patch:<commit_hash> (usually runtime updates).
- is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${vim_commit:0:7}" && echo false || echo true)"
-
- if ! [ "$is_missing" = "false" ] \
- && vim_tag="$(cd "${VIM_SOURCE_DIR}" && git describe --tags --exact-match "${vim_commit}" 2>/dev/null)"
- then
- # Vim version number (not commit hash).
- # Check for vim-patch:<tag> (not commit hash).
- patch_number="${vim_tag:1}" # "v7.4.0001" => "7.4.0001"
- is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${patch_number}" && echo false || echo true)"
- vim_commit="${vim_tag#v}"
+ token="vim-patch:${vim_commit:0:7}"
+ if [[ "${tokens[$token]-}" ]]; then
+ continue
fi
- if ! [ "$is_missing" = "false" ]; then
- echo "${vim_commit}"
+ if vim_tag="$(git -C "${VIM_SOURCE_DIR}" describe --tags --exact-match "${vim_commit}" 2>/dev/null)"; then
+ # Check for vim-patch:<tag> (not commit hash).
+ patch_number="vim-patch:${vim_tag:1}" # "v7.4.0001" => "7.4.0001"
+ if [[ "${tokens[$patch_number]-}" ]]; then
+ continue
+ fi
+ echo "$vim_tag"
+ else
+ echo "$vim_commit"
fi
done
}
@@ -410,7 +411,7 @@ show_vimpatches() {
printf "\nVim patches missing from Neovim:\n"
list_missing_vimpatches | while read vim_commit; do
- if (cd "${VIM_SOURCE_DIR}" && git --no-pager show --color=never --name-only "v${vim_commit}" 2>/dev/null) | grep -q ^runtime; then
+ if [ -n "$(git -C "$VIM_SOURCE_DIR" diff-tree --no-commit-id --name-only "${vim_commit}" -- runtime)" ]; then
printf ' • %s (+runtime)\n' "${vim_commit}"
else
printf ' • %s\n' "${vim_commit}"