doc generation for commandline now extracted into an RST directly.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,4 +12,5 @@ client/venv/
|
|||||||
client/client/config.json
|
client/client/config.json
|
||||||
webservice/bam/config.py
|
webservice/bam/config.py
|
||||||
|
|
||||||
|
# generated
|
||||||
|
doc/source/reference/index.rst
|
||||||
|
2
Makefile
2
Makefile
@@ -15,6 +15,6 @@ doc: FORCE
|
|||||||
@echo "xdg-open doc/build/html/index.html"
|
@echo "xdg-open doc/build/html/index.html"
|
||||||
|
|
||||||
clean: FORCE
|
clean: FORCE
|
||||||
$(MAKE) -C docs clean
|
$(MAKE) -C doc clean
|
||||||
|
|
||||||
FORCE:
|
FORCE:
|
||||||
|
48
bam/cli.py
48
bam/cli.py
@@ -1128,9 +1128,20 @@ def create_argparse_deps(subparsers):
|
|||||||
|
|
||||||
|
|
||||||
def create_argparse_pack(subparsers):
|
def create_argparse_pack(subparsers):
|
||||||
|
import argparse
|
||||||
subparse = subparsers.add_parser(
|
subparse = subparsers.add_parser(
|
||||||
"pack", aliases=("pk",),
|
"pack", aliases=("pk",),
|
||||||
help="Pack a blend file and its dependencies into an archive",
|
help="Pack a blend file and its dependencies into an archive",
|
||||||
|
description=
|
||||||
|
"""
|
||||||
|
This command is used for packing a ``.blend`` file into a ``.zip`` file for redistribution.
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
# pack a blend with maximum compression for online downloads
|
||||||
|
bam pack /path/to/scene.blend --output scene.zip --compress
|
||||||
|
""",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
)
|
)
|
||||||
subparse.add_argument(
|
subparse.add_argument(
|
||||||
dest="paths", nargs="+",
|
dest="paths", nargs="+",
|
||||||
@@ -1155,9 +1166,41 @@ def create_argparse_pack(subparsers):
|
|||||||
|
|
||||||
|
|
||||||
def create_argparse_remap(subparsers):
|
def create_argparse_remap(subparsers):
|
||||||
|
import argparse
|
||||||
|
|
||||||
subparse = subparsers.add_parser(
|
subparse = subparsers.add_parser(
|
||||||
"remap",
|
"remap",
|
||||||
help="Remap blend file paths",
|
help="Remap blend file paths",
|
||||||
|
description=
|
||||||
|
"""
|
||||||
|
This command is a 3 step process:
|
||||||
|
|
||||||
|
- first run ``bam remap start .`` which stores the current state of your project (recursively).
|
||||||
|
- then re-arrange the files on the filesystem (rename, relocate).
|
||||||
|
- finally run ``bam remap finish`` to apply the changes, updating the ``.blend`` files internal paths.
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
cd /my/project
|
||||||
|
|
||||||
|
bam remap start .
|
||||||
|
mv photos textures
|
||||||
|
mv house_v14_library.blend house_libraray.blend
|
||||||
|
bam remap finish
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Remapping creates a file called ``bam_remap.data`` in the current directory.
|
||||||
|
You can relocate the entire project to a new location but on executing ``finish``,
|
||||||
|
this file must be accessible from the current directory.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This command depends on files unique contents,
|
||||||
|
take care not to modify the files once remap is started.
|
||||||
|
""",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
)
|
)
|
||||||
|
|
||||||
subparse_remap_commands = subparse.add_subparsers(
|
subparse_remap_commands = subparse.add_subparsers(
|
||||||
@@ -1234,7 +1277,10 @@ def create_argparse():
|
|||||||
__doc__
|
__doc__
|
||||||
)
|
)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=usage_text)
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="bam",
|
||||||
|
description=usage_text,
|
||||||
|
)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(
|
subparsers = parser.add_subparsers(
|
||||||
title='subcommands',
|
title='subcommands',
|
||||||
|
181
doc/bam_cli_argparse.py
Normal file
181
doc/bam_cli_argparse.py
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# ***** END GPL LICENCE BLOCK *****
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------
|
||||||
|
# Ensure module path
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
path = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "exts"))
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.append(path)
|
||||||
|
del os, sys, path
|
||||||
|
# --------
|
||||||
|
|
||||||
|
# ------------------
|
||||||
|
# Ensure module path
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
path = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.append(path)
|
||||||
|
del os, sys, path
|
||||||
|
# --------
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
CURRENT_DIR = os.path.normpath(os.path.dirname(__file__))
|
||||||
|
INDENT = " "
|
||||||
|
|
||||||
|
import bam.cli
|
||||||
|
|
||||||
|
# start at this level
|
||||||
|
title_level = 2
|
||||||
|
|
||||||
|
title_chars = (
|
||||||
|
'%', # not from python docs!
|
||||||
|
'#',
|
||||||
|
'*',
|
||||||
|
'=',
|
||||||
|
'-',
|
||||||
|
'^',
|
||||||
|
'"',
|
||||||
|
"'", # not from python docs!
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def write_title(title, ch, double=False):
|
||||||
|
ret = [""]
|
||||||
|
line = ch * len(title)
|
||||||
|
assert(len(ch) == 1)
|
||||||
|
if double:
|
||||||
|
ret.append(line)
|
||||||
|
ret.append(title)
|
||||||
|
ret.append(line)
|
||||||
|
ret.append("")
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def text_unintend(text):
|
||||||
|
text = text.lstrip("\n")
|
||||||
|
text_newlines = text.split("\n")
|
||||||
|
text_indent = len(text_newlines[0]) - len(text_newlines[0].lstrip(" \t"))
|
||||||
|
return "\n".join([t[text_indent:] for t in text_newlines])
|
||||||
|
|
||||||
|
|
||||||
|
def options_as_rst(data):
|
||||||
|
ret = []
|
||||||
|
ret.extend([
|
||||||
|
".. list-table::",
|
||||||
|
INDENT + ":widths: 2, 8",
|
||||||
|
"",
|
||||||
|
])
|
||||||
|
for c in data:
|
||||||
|
c_name = c["name"]
|
||||||
|
if isinstance(c_name, list):
|
||||||
|
c_name = ", ".join(["``%s``" % w for w in c["name"]])
|
||||||
|
c_metavar = c["metavar"]
|
||||||
|
c_choices = c.get("choices")
|
||||||
|
|
||||||
|
ret.extend([
|
||||||
|
INDENT + "* - " + c_name + (" ``<%s>``" % c_metavar if c_metavar else ""),
|
||||||
|
INDENT + " - " + c["help"],
|
||||||
|
])
|
||||||
|
if c_choices:
|
||||||
|
ret.extend([
|
||||||
|
"",
|
||||||
|
INDENT + " Possible choices: " + ", ".join(["``%s``" % w for w in c_choices])
|
||||||
|
])
|
||||||
|
|
||||||
|
# print(c.keys())
|
||||||
|
ret.append("")
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def subcommands_as_rst(data, commands, subcommands, level):
|
||||||
|
# import IPython; IPython.embed()
|
||||||
|
ret = []
|
||||||
|
for c in data:
|
||||||
|
name = c["name"]
|
||||||
|
name_abbr = name.split(" ", 1)[0]
|
||||||
|
if (subcommands is None) or (name_abbr in subcommands):
|
||||||
|
commands_sub = commands + [name_abbr]
|
||||||
|
ret.extend(write_title(" ".join(commands_sub[1:]), title_chars[level]))
|
||||||
|
|
||||||
|
# ret.extend([".. program:: " + " ".join(commands_sub), ""])
|
||||||
|
|
||||||
|
ret.extend([c["help"], ""])
|
||||||
|
ret.extend(["::", "", INDENT + c["usage"], ""])
|
||||||
|
ret.extend([text_unintend(c.get("description", "")), ""])
|
||||||
|
|
||||||
|
ls = c.get("args")
|
||||||
|
if ls:
|
||||||
|
ret.extend(write_title("Positional arguments:", title_chars[level + 1]))
|
||||||
|
ret.extend(options_as_rst(ls))
|
||||||
|
|
||||||
|
ls = c.get("options")
|
||||||
|
if ls:
|
||||||
|
ret.extend(write_title("Options:", title_chars[level + 1]))
|
||||||
|
# import IPython; IPython.embed()
|
||||||
|
ret.extend(options_as_rst(c["options"]))
|
||||||
|
|
||||||
|
ls = c.get("children")
|
||||||
|
if ls:
|
||||||
|
ret.extend(write_title("Subcommands:", title_chars[level + 1]))
|
||||||
|
ret.extend(subcommands_as_rst(ls, commands_sub, None, level + 2))
|
||||||
|
|
||||||
|
# ret.extend(["", "----", ""])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def write_argparse_rst(title_level):
|
||||||
|
program_root = "bam"
|
||||||
|
|
||||||
|
parser = bam.cli.create_argparse()
|
||||||
|
|
||||||
|
import sphinxarg.parser as sp
|
||||||
|
data = sp.parse_parser(parser)
|
||||||
|
|
||||||
|
# import pprint
|
||||||
|
# pprint.pprint(data)
|
||||||
|
|
||||||
|
with open(os.path.join(CURRENT_DIR, 'bam_cli_argparse.rst'), 'r', encoding='utf-8') as f:
|
||||||
|
main_doc_split = f.read().split("\n")
|
||||||
|
|
||||||
|
for i, l in enumerate(main_doc_split):
|
||||||
|
l_strip = l.lstrip()
|
||||||
|
if l_strip.startswith(".. %%"):
|
||||||
|
# ".. %%(foo, bar)%%" --> ("foo", "bar")
|
||||||
|
subcommands = tuple([w.strip() for w in l_strip[3:].strip("%()").split(",")])
|
||||||
|
l = subcommands_as_rst(data["children"], [program_root], subcommands, title_level)
|
||||||
|
main_doc_split[i] = "\n".join(l)
|
||||||
|
|
||||||
|
return main_doc_split
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
os.makedirs(os.path.join(CURRENT_DIR, "source", "reference"), exist_ok=True)
|
||||||
|
with open(os.path.join(CURRENT_DIR, "source", "reference", "index.rst"), 'w') as f:
|
||||||
|
ret = write_argparse_rst(3)
|
||||||
|
f.write("\n".join(ret))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
20
doc/bam_cli_argparse.rst
Normal file
20
doc/bam_cli_argparse.rst
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
****************
|
||||||
|
Bam Command Line
|
||||||
|
****************
|
||||||
|
|
||||||
|
|
||||||
|
Standalone Subcommands
|
||||||
|
======================
|
||||||
|
|
||||||
|
These commands can run standalone.
|
||||||
|
|
||||||
|
.. %%(pack,remap,deps)%%
|
||||||
|
|
||||||
|
|
||||||
|
Project Subcommands
|
||||||
|
===================
|
||||||
|
|
||||||
|
These commands relate to projects which use a BAM server.
|
||||||
|
|
||||||
|
.. %%(init,create,checkout,update,commit,revert,status,list,deps)%%
|
||||||
|
|
@@ -9,6 +9,29 @@ from sphinx.util.nodes import nested_parse_with_titles
|
|||||||
from sphinxarg.parser import parse_parser, parser_navigate
|
from sphinxarg.parser import parse_parser, parser_navigate
|
||||||
|
|
||||||
|
|
||||||
|
def text_from_rst(text, is_rst=False):
|
||||||
|
if not text:
|
||||||
|
return []
|
||||||
|
if not is_rst:
|
||||||
|
return [nodes.paragraph(text=text)]
|
||||||
|
else:
|
||||||
|
text = text.lstrip("\n")
|
||||||
|
text_newlines = text.split("\n")
|
||||||
|
text_indent = len(text_newlines[0]) - len(text_newlines[0].lstrip(" \t"))
|
||||||
|
text = "\n".join([t[text_indent:] for t in text_newlines])
|
||||||
|
del text_newlines, text_indent
|
||||||
|
|
||||||
|
import docutils.parsers.rst
|
||||||
|
parser = docutils.parsers.rst.Parser()
|
||||||
|
filepath = None
|
||||||
|
settings = docutils.frontend.OptionParser(
|
||||||
|
components=(docutils.parsers.rst.Parser,)
|
||||||
|
).get_default_values()
|
||||||
|
document = docutils.utils.new_document(filepath, settings)
|
||||||
|
parser.parse(text, document)
|
||||||
|
return document.children
|
||||||
|
|
||||||
|
|
||||||
def map_nested_definitions(nested_content):
|
def map_nested_definitions(nested_content):
|
||||||
if nested_content is None:
|
if nested_content is None:
|
||||||
raise Exception('Nested content should be iterable, not null')
|
raise Exception('Nested content should be iterable, not null')
|
||||||
@@ -91,7 +114,7 @@ def print_opt_list(data, nested_content):
|
|||||||
return nodes.option_list('', *items) if items else None
|
return nodes.option_list('', *items) if items else None
|
||||||
|
|
||||||
|
|
||||||
def print_command_args_and_opts(arg_list, opt_list, sub_list=None):
|
def print_command_args_and_opts(arg_list, opt_list, epilog_list=None, sub_list=None):
|
||||||
items = []
|
items = []
|
||||||
if arg_list:
|
if arg_list:
|
||||||
items.append(nodes.definition_list_item(
|
items.append(nodes.definition_list_item(
|
||||||
@@ -101,6 +124,8 @@ def print_command_args_and_opts(arg_list, opt_list, sub_list=None):
|
|||||||
items.append(nodes.definition_list_item(
|
items.append(nodes.definition_list_item(
|
||||||
'', nodes.term(text='Options:'),
|
'', nodes.term(text='Options:'),
|
||||||
nodes.definition('', opt_list)))
|
nodes.definition('', opt_list)))
|
||||||
|
if epilog_list:
|
||||||
|
items.extend(epilog_list)
|
||||||
if sub_list and len(sub_list):
|
if sub_list and len(sub_list):
|
||||||
items.append(nodes.definition_list_item(
|
items.append(nodes.definition_list_item(
|
||||||
'', nodes.term(text='Sub-commands:'),
|
'', nodes.term(text='Sub-commands:'),
|
||||||
@@ -137,7 +162,9 @@ def print_subcommand_list(data, nested_content):
|
|||||||
my_def.append(print_command_args_and_opts(
|
my_def.append(print_command_args_and_opts(
|
||||||
print_arg_list(child, nested_content),
|
print_arg_list(child, nested_content),
|
||||||
print_opt_list(child, nested_content),
|
print_opt_list(child, nested_content),
|
||||||
print_subcommand_list(child, nested_content)
|
text_from_rst(child.get('description', ""), is_rst=True),
|
||||||
|
print_subcommand_list(child, nested_content),
|
||||||
|
|
||||||
))
|
))
|
||||||
items.append(
|
items.append(
|
||||||
nodes.definition_list_item(
|
nodes.definition_list_item(
|
||||||
@@ -345,6 +372,7 @@ class ArgParseDirective(Directive):
|
|||||||
items.append(print_command_args_and_opts(
|
items.append(print_command_args_and_opts(
|
||||||
print_arg_list(result, nested_content),
|
print_arg_list(result, nested_content),
|
||||||
print_opt_list(result, nested_content),
|
print_opt_list(result, nested_content),
|
||||||
|
None,
|
||||||
print_subcommand_list(result, nested_content)
|
print_subcommand_list(result, nested_content)
|
||||||
))
|
))
|
||||||
if 'epilog' in result:
|
if 'epilog' in result:
|
||||||
|
@@ -116,7 +116,8 @@ def parse_parser(parser, data=None, **kwargs):
|
|||||||
option = {
|
option = {
|
||||||
'name': action.option_strings,
|
'name': action.option_strings,
|
||||||
'default': action.default if show_defaults else '==SUPPRESS==',
|
'default': action.default if show_defaults else '==SUPPRESS==',
|
||||||
'help': action.help or ''
|
'help': action.help or '',
|
||||||
|
'metavar': action.metavar
|
||||||
}
|
}
|
||||||
if action.choices:
|
if action.choices:
|
||||||
option['choices'] = action.choices
|
option['choices'] = action.choices
|
||||||
|
@@ -33,14 +33,11 @@ extensions = [
|
|||||||
'sphinx.ext.todo',
|
'sphinx.ext.todo',
|
||||||
]
|
]
|
||||||
|
|
||||||
if 1:
|
# create bam_cli_argparse.rst
|
||||||
extensions += ['sphinxarg.ext']
|
import os
|
||||||
sys.path.extend([
|
import sys
|
||||||
# to import 'bam.py'
|
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
os.path.join(os.path.dirname(__file__), "..", ".."),
|
__import__("bam_cli_argparse").main()
|
||||||
# to access the 'sphinxarg' extension
|
|
||||||
os.path.abspath(os.path.join("..", "exts"))
|
|
||||||
])
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
Client Reference
|
|
||||||
################
|
|
||||||
|
|
||||||
Here is a reference of all the BAM cli commands.
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: bam.cli
|
|
||||||
:func: create_argparse
|
|
||||||
:prog: bam
|
|
Reference in New Issue
Block a user