Skip to content

Commit

Permalink
docs(sphinx-conf): Fix CLI documentation process
Browse files Browse the repository at this point in the history
- correctly handles commands without groups
- Check for group names inside cli\__init__.py
  • Loading branch information
santiagogaray authored and Chris Mackey committed Apr 5, 2021
1 parent 047f617 commit afaaba3
Showing 1 changed file with 94 additions and 43 deletions.
137 changes: 94 additions & 43 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,9 @@
Note:
This process assumes that each CLI module represent a group of subcommands.
"""
# Activate this CLI documentation process
# Activate this CLI documentation process.
custom_cli_docs = True
# Override existing reSt files found in docs//cli folder
# Override existing reSt files found in docs//cli folder.
cli_overwrite = False

# Repository/library hash table.
Expand Down Expand Up @@ -272,40 +272,41 @@ def create_cli_files():
# Get CLI data from library
proj_folder = os.path.dirname(__file__)
result = get_cli_data(proj_folder)
if not result: # return if no CLI module found
if not result: # return if no CLI module found.
return 0

ht_cli, lib_name, tool_name = result

# Prepare docs folder and reST files to create
# Prepare docs folder and reST files to create.
doc_folder = os.path.join(proj_folder, 'cli')
if not os.path.isdir(doc_folder):
os.mkdir(doc_folder)

# Create new groups only list by excluding CLI groups that have a reST file
# Create new groups only list by excluding CLI groups that have a reST file.
# inside docs/cli
def get_new_groups_only():
def get_missing_groups():
ht_new_cli_groups = {}
for mod_name in ht_cli.keys():
if mod_name + ".rst" not in os.listdir(doc_folder):
ht_new_cli_groups[mod_name] = ht_cli[mod_name]
return ht_new_cli_groups

# Prepare all/new group list
# Prepare all/missing group list.
if not cli_overwrite:
ht_new_cli = get_new_groups_only()
if not ht_new_cli:
ht_missing_cli = get_missing_groups()
if not ht_missing_cli:
print("[CLI]: No new CLI files created.")
return 0
else:
ht_new_cli = ht_cli
ht_missing_cli = ht_cli

# Create CLI reST files for each module(command group) found.
if not write_cli_files(ht_new_cli, lib_name, tool_name, doc_folder):
if not write_cli_files(ht_missing_cli, lib_name, tool_name, doc_folder):
return -1

# Update/Create CLI index file with command group section included
if not update_cli_index(os.path.join(doc_folder, 'index.rst'), ht_cli.keys()):
# Update/Create CLI index file with command group section included.
if not update_cli_index(os.path.join(doc_folder, 'index.rst'),
list(ht_cli.keys())):
return -1

# Update package index file to include CLI docs section
Expand All @@ -324,8 +325,9 @@ def get_cli_data(project_folder):
Returns:
A tuple with three elements.
- module_names:
A list with the names of the CLI modules found in the repository.
- ht_mod_group:
A dictionary with module file names and the corresponding
group names found.
- lib_name: The name of the library found in the repository.
Expand All @@ -334,14 +336,15 @@ def get_cli_data(project_folder):
"""
print("[CLI data]: Retrieveing CLI data from {}".format(project_folder))

# Check in hash table for a library name based on repository name
# Check in hash table for a library name based on repository name.
repo_path = os.path.abspath(os.path.join(project_folder, os.pardir))
repo_name = os.path.split(repo_path)[1]

# Look up the library name in hashtable
# Look up the library name in hashtable.
if repo_name in ht_repo_lib:
lib_name = ht_repo_lib[repo_name]
# Otherwise exract library name from modules.rst file (from heading)
# Otherwise exract library name from modules.rst file heading and replace
# dash(-) to underscore(_).
else:
modules_file = os.path.join(os.path.join(project_folder, 'modules.rst'))
with open(modules_file, 'r') as mod_file:
Expand All @@ -364,7 +367,7 @@ def get_cli_data(project_folder):
print("[CLI data]: No CLI library found")
return None

# Get CLI module names and their corresponding group names as a dictionary
# Get CLI module names and their corresponding group names as a dictionary.
ht_mod_group = get_cli_groups(cli_path)

if ht_mod_group == {}:
Expand All @@ -389,18 +392,49 @@ def get_cli_groups(cli_path):
module_names = [os.path.splitext(file)[0] for file in os.listdir(cli_path)
if os.path.splitext(file)[1] == ".py"]

# Remove files that aren't a cli module
discard_names = ["__init__"]
module_names = [name for name in module_names if name not in discard_names]
# Look for group function names inside their CLI module. Assume (1) group
# and (1) function per module. Assumme possible multiple groups and
# functions in '__init.py'.

init_text = ""
ht_groups = {}
for name in module_names:
with open(os.path.join(cli_path, name + ".py"), 'r') as cli_file:
text = cli_file.read()
m = re.search(r'^@click\.group\(.*\ndef\s(\w*)\s*\(\):', text,
flags=re.MULTILINE)
if m:
ht_groups[name] = m.group(1)
# Use regex pattern to get function name after click group decorator
fnd = re.findall(r'^@click\.group\(.*\n(@click.*\n){0,2}def\s(\w*)\s*\(\):\n',
text, flags=re.MULTILINE)
# Add multiple commands found in each group inside '__init__.py'
if name == "__init__":
init_text = text
for result in fnd:
tr, cli_comm = result
comm_exp = r'^@' + cli_comm + r'.command\(\'([\w-]+)'
im = re.findall(comm_exp, text, flags=re.MULTILINE)
if im:
# add sub-group key with list of commands to the
# 'main' group dict. key
if 'main' not in ht_groups:
ht_groups['main'] = {}
ht_groups['main'][cli_comm] = list(im)

else:
# Store module function name and initial command name in hash table.
if fnd:
ht_groups[name] = [fnd[0][1], fnd[0][1]]

# Check if any group/function has a new command name specified inisde
# "add_command" function.
if init_text:
named_groups = re.findall(
r'add_command\( *([\w-]+) *, *(name=)? *\'([\w-]+)\' *\)',
init_text, flags=re.MULTILINE)
for group in named_groups:
cli_func, tr, cli_comm = group
for file in ht_groups:
# 'main' groups are excluded, assumes not used in CLI.
if file != 'main' and ht_groups[file][0] == cli_func:
ht_groups[file][1] = cli_comm

return ht_groups

Expand All @@ -420,23 +454,38 @@ def write_cli_files(ht_cli_data, lib_name, tool_name, doc_folder):
doc_folder: The path where the CLI documentation files will be saved.
"""

# Creating missing CLI reST files
# Creating missing CLI reST files.
group_filenames = ht_cli_data.keys()
print("[CLI files]: Creating ({}) CLI rst files: {}...".format(
len(group_filenames), list(group_filenames)))

# Write sphinx-click directive with options for each CLI group
# Write sphinx-click directive with options for each CLI group.
for file in group_filenames:
cli_content = ["{}\n".format(file),
"{}\n".format("=" * len(file)),
"\n",
".. click:: {}.cli.{}:{}\n".format(
lib_name, file, ht_cli_data[file]),
" :prog: {} {}\n".format(tool_name, ht_cli_data[file]),
" :show-nested:\n"
]

# Create CLI group reST file
"{}\n".format("=" * len(file))]
if file != "main":
# one command(with all its subcommands) per cli module.
cli_content += ["\n",
".. click:: {}.cli.{}:{}\n".format(
lib_name, file, ht_cli_data[file][0]),
" :prog: {} {}\n".format(
tool_name, ht_cli_data[file][1]),
" :show-nested:\n"]
else:
# multiple commands in the 'main' group (implicitly named to
# avoid commands from other modules to be included). Also specify
# commands as a root command.
for group in ht_cli_data[file].keys():
cli_content += ["\n",
".. click:: {}.cli.{}:{}\n".
format(lib_name, "__init__", group),
" :prog: {}\n".format(tool_name),
" :show-nested:\n",
" :commands: " + " ,".
join(ht_cli_data[file][group]) + "\n"
]

# Create CLI group reST file.
with open(os.path.join(doc_folder, file + ".rst"), 'w') as group_file:
group_file.writelines(cli_content)

Expand All @@ -452,8 +501,7 @@ def update_cli_index(index_path, group_filenames):
group_filenames: Name of the click groups to include in the
index \'Commands\' section.
"""

# Include exisitng cli/index.rst data if present
# Include exisitng cli/index.rst data if present.
cli_content = []
if os.path.isfile(index_path):
with open(index_path, 'r') as index_file:
Expand All @@ -462,11 +510,11 @@ def update_cli_index(index_path, group_filenames):
] if "Commands\n" in lines else lines
print("[CLI cli\\index]: Updating index.rst file...")
else:
# Otherwise create a "CLI" heading
# Otherwise create a "CLI" heading.
cli_content = ["CLI\n", "===\n", "\n"]
print("[CLI cli\\index]: Creating index.rst file...")

# Add 'Commands' section with directive and options
# Add 'Commands' section with directive and options.
cli_content += ["\n"
"Commands\n",
"--------\n",
Expand All @@ -475,11 +523,14 @@ def update_cli_index(index_path, group_filenames):
"\n"
]

# Add sub-command groups to content
# Add sub-command groups to content.
if "main" in group_filenames:
group_filenames.remove('main')
group_filenames.insert(0, 'main')
for file in group_filenames:
cli_content.append(" {}\n".format(file))

# Append section to existing file or create new file
# Append section to existing file or create new file.
with open(index_path, 'w') as index_file:
index_file.writelines(cli_content)

Expand Down

0 comments on commit afaaba3

Please sign in to comment.