зеркало из https://github.com/github/vitess-gh.git
807 строки
35 KiB
Python
Executable File
807 строки
35 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
# Copyright 2018 The Vitess Authors.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import optparse
|
|
import re
|
|
|
|
# TODO: Handle angle brackets that appear in command definitions --
|
|
# e.g. ChangeSlaveType
|
|
|
|
|
|
# Traverse directory to get list of all files in the directory.
|
|
def get_all_files(directory, filenames):
|
|
os.chdir(directory)
|
|
for path, dirs, files in os.walk('.'):
|
|
for filename in files:
|
|
filenames[os.path.join(path, filename)] = True
|
|
return filenames
|
|
|
|
# This needs to produce the same anchor ID as the Markdown processor.
|
|
def anchor_id(heading):
|
|
return heading.lower().replace(' ', '-').replace(',', '')
|
|
|
|
def write_header(doc, commands):
|
|
doc.write('This reference guide explains the commands that the ' +
|
|
'<b>vtctl</b> tool supports. **vtctl** is a command-line tool ' +
|
|
'used to administer a Vitess cluster, and it allows a human ' +
|
|
'or application to easily interact with a Vitess ' +
|
|
'implementation.\n\nCommands are listed in the ' +
|
|
'following groups:\n\n')
|
|
for group in sorted(commands):
|
|
group_link = anchor_id(group)
|
|
doc.write('* [' + group + '](#' + group_link + ')\n')
|
|
doc.write('\n\n')
|
|
|
|
def write_footer(doc):
|
|
doc.write(' </body>\n')
|
|
doc.write('</html>\n')
|
|
|
|
def create_reference_doc(root_directory, commands, arg_definitions):
|
|
doc = open(root_directory + '/doc/vtctlReference.md', 'w')
|
|
write_header(doc, commands)
|
|
|
|
not_found_arguments = {}
|
|
for group in sorted(commands):
|
|
doc.write('## ' + group + '\n\n');
|
|
for command in sorted(commands[group]):
|
|
if ('definition' in commands[group][command] and
|
|
commands[group][command]['definition'] != '' and
|
|
re.search(r'HIDDEN', commands[group][command]['definition'])):
|
|
print '\n\n****** ' + command + ' is hidden *******\n\n'
|
|
continue
|
|
command_link = anchor_id(command)
|
|
doc.write('* [' + command + '](#' + command_link + ')\n')
|
|
|
|
doc.write('\n')
|
|
|
|
for command in sorted(commands[group]):
|
|
if ('definition' in commands[group][command] and
|
|
commands[group][command]['definition'] != '' and
|
|
re.search(r'HIDDEN', commands[group][command]['definition'])):
|
|
continue
|
|
doc.write('### ' + command + '\n\n');
|
|
if ('definition' in commands[group][command] and
|
|
commands[group][command]['definition'] != ''):
|
|
commands[group][command]['definition'] = (
|
|
commands[group][command]['definition'].replace('<', '<'))
|
|
commands[group][command]['definition'] = (
|
|
commands[group][command]['definition'].replace('>', '>'))
|
|
commands[group][command]['definition'] = (
|
|
commands[group][command]['definition'].replace('</a>',
|
|
'</a>'))
|
|
commands[group][command]['definition'] = (
|
|
commands[group][command]['definition'].replace('<a href',
|
|
'<a href'))
|
|
commands[group][command]['definition'] = (
|
|
commands[group][command]['definition'].replace('">', '">'))
|
|
commands[group][command]['definition'] = (
|
|
commands[group][command]['definition'].replace('"<br>',
|
|
'<br>'))
|
|
commands[group][command]['definition'] = (
|
|
commands[group][command]['definition'].replace('\\n', '<br><br>'))
|
|
doc.write(commands[group][command]['definition'] + '\n\n')
|
|
|
|
if ('arguments' in commands[group][command] and
|
|
commands[group][command]['arguments']):
|
|
doc.write('#### Example\n\n')
|
|
arguments = commands[group][command]['arguments'].strip().strip(
|
|
'"').replace('<', '<')
|
|
arguments = arguments.strip().replace('>', '>')
|
|
doc.write('<pre class="command-example">%s %s</pre>\n\n' %
|
|
(command, arguments))
|
|
|
|
if ('argument_list' in commands[group][command] and
|
|
'flags' in commands[group][command]['argument_list']):
|
|
flag_text = ''
|
|
for command_flag in sorted(
|
|
commands[group][command]['argument_list']['flags']):
|
|
flag = (
|
|
commands[group][command]['argument_list']['flags'][command_flag])
|
|
flag_text += ('| %s | %s | %s |\n' % (command_flag, flag['type'],
|
|
flag['definition']))
|
|
|
|
if flag_text:
|
|
#flag_text = '<a name="' + command + '-flags"></a>\n\n' +
|
|
flag_text = ('| Name | Type | Definition |\n' +
|
|
'| :-------- | :--------- | :--------- |\n' +
|
|
flag_text + '\n')
|
|
doc.write('#### Flags\n\n' + flag_text + '\n')
|
|
|
|
if ('argument_list' in commands[group][command] and
|
|
'args' in commands[group][command]['argument_list']):
|
|
arg_text = ''
|
|
|
|
for arg in commands[group][command]['argument_list']['args']:
|
|
if 'name' in arg:
|
|
arg_name = arg['name']
|
|
new_arg_name = arg['name'].replace('<', '').replace('>', '')
|
|
if (new_arg_name[0:len(new_arg_name) - 1] in arg_definitions and
|
|
re.search(r'\d', new_arg_name[-1])):
|
|
arg_name = '<' + new_arg_name[0:len(new_arg_name) - 1] + '>'
|
|
arg_name = arg_name.strip().replace('<', 'START_CODE_TAG<')
|
|
arg_name = arg_name.strip().replace('>', '>END_CODE_TAG')
|
|
arg_text += '* ' + arg_name + ' – '
|
|
if 'required' in arg and arg['required']:
|
|
arg_text += 'Required.'
|
|
else:
|
|
arg_text += 'Optional.'
|
|
|
|
arg_values = None
|
|
temp_name = arg['name'].replace('<', '').replace('>', '')
|
|
if temp_name in arg_definitions:
|
|
arg_text += ' ' + arg_definitions[temp_name]['description']
|
|
if 'list_items' in arg_definitions[temp_name]:
|
|
arg_values = arg_definitions[temp_name]['list_items']
|
|
# Check if the argument name ends in a digit to catch things like
|
|
# keyspace1 being used to identify the first in a list of keyspaces.
|
|
elif (temp_name[0:len(temp_name) - 1] in arg_definitions and
|
|
re.search(r'\d', temp_name[-1])):
|
|
arg_length = len(temp_name) - 1
|
|
arg_text += (' ' +
|
|
arg_definitions[temp_name[0:arg_length]]['description'])
|
|
if 'list_items' in arg_definitions[temp_name[0:arg_length]]:
|
|
arg_values = (
|
|
arg_definitions[temp_name[0:arg_length]]['list_items'])
|
|
else:
|
|
not_found_arguments[arg['name']] = True
|
|
if arg_values:
|
|
arg_text += '\n\n'
|
|
for arg_value in sorted(arg_values, key=lambda k: k['value']):
|
|
arg_text += (' * <code>' + arg_value['value'] + '</code> ' +
|
|
'– ' + arg_value['definition'] + '\n')
|
|
arg_text += '\n\n'
|
|
|
|
if 'multiple' in arg:
|
|
separation = 'space'
|
|
if 'hasComma' in arg:
|
|
separation = 'comma'
|
|
arg_text += (' To specify multiple values for this argument, ' +
|
|
'separate individual values with a ' +
|
|
separation + '.')
|
|
arg_text += '\n'
|
|
if arg_text:
|
|
arg_text = arg_text.replace('START_CODE_TAG', '<code>')
|
|
arg_text = arg_text.replace('END_CODE_TAG', '</code>')
|
|
doc.write('#### Arguments\n\n' +
|
|
arg_text +
|
|
'\n')
|
|
|
|
if 'errors' in commands[group][command]:
|
|
errors_text = ''
|
|
if 'ARG_COUNT' in commands[group][command]['errors']:
|
|
error = commands[group][command]['errors']['ARG_COUNT']
|
|
message = re.sub(str(command), '<' + command + '>', error['message'])
|
|
#print 'message here'
|
|
#print message
|
|
#message = error['message'].replace(command, '<' + command + '>')
|
|
message = message.replace('<', 'START_CODE_TAG<')
|
|
message = message.replace('>', '>END_CODE_TAG')
|
|
errors_text += '* ' + message + ' '
|
|
if (error['exact_count'] and
|
|
len(error['exact_count']) == 1 and
|
|
error['exact_count'][0] == '1'):
|
|
errors_text += ('This error occurs if the command is not called ' +
|
|
'with exactly one argument.')
|
|
elif error['exact_count'] and len(error['exact_count']) == 1:
|
|
errors_text += ('This error occurs if the command is not called ' +
|
|
'with exactly ' + error['exact_count'][0] + ' ' +
|
|
'arguments.')
|
|
elif error['exact_count']:
|
|
allowed_error_counts = ' or '.join(error['exact_count'])
|
|
errors_text += ('This error occurs if the command is not called ' +
|
|
'with exactly ' + allowed_error_counts + ' ' +
|
|
'arguments.')
|
|
elif error['min_count'] and error['max_count']:
|
|
errors_text += ('This error occurs if the command is not called ' +
|
|
'with between ' + error['min_count'] + ' and ' +
|
|
error['max_count'] + ' arguments.')
|
|
elif error['min_count'] and error['min_count'] == '1':
|
|
errors_text += ('This error occurs if the command is not called ' +
|
|
'with at least one argument.')
|
|
elif error['min_count']:
|
|
errors_text += ('This error occurs if the command is not called ' +
|
|
'with at least ' + error['min_count'] + ' ' +
|
|
'arguments.')
|
|
elif error['max_count']:
|
|
errors_text += ('This error occurs if the command is not called ' +
|
|
'with more than ' + error['max_count'] + ' ' +
|
|
'arguments.')
|
|
errors_text += '\n'
|
|
|
|
if 'other' in commands[group][command]['errors']:
|
|
#print 'other errors'
|
|
#print commands[group][command]['errors']
|
|
for error in commands[group][command]['errors']['other']:
|
|
error = re.sub(str(command), '<' + command + '>', error)
|
|
if ('argument_list' in commands[group][command] and
|
|
'flags' in commands[group][command]['argument_list']):
|
|
for command_flag in sorted(
|
|
commands[group][command]['argument_list']['flags']):
|
|
error = re.sub(str(command_flag), '<' + command_flag + '>',
|
|
error)
|
|
|
|
error = error.replace('<', 'START_CODE_TAG<')
|
|
error = error.replace('>', '>END_CODE_TAG')
|
|
errors_text += '* ' + error + '\n'
|
|
|
|
if errors_text:
|
|
errors_text = errors_text.replace('START_CODE_TAG', '<code>')
|
|
errors_text = errors_text.replace('END_CODE_TAG', '</code>')
|
|
doc.write('#### Errors\n\n' + errors_text)
|
|
doc.write('\n\n')
|
|
#write_footer(doc)
|
|
doc.close()
|
|
#print json.dumps(not_found_arguments, sort_keys=True, indent=4)
|
|
return
|
|
|
|
def parse_arg_list(arguments, current_command):
|
|
last_char = ''
|
|
|
|
find_closing_square_bracket = False
|
|
has_comma = False
|
|
has_multiple = False
|
|
is_optional_argument = False
|
|
is_required_argument = False
|
|
current_argument = ''
|
|
|
|
new_arg_list = []
|
|
arg_count = 0
|
|
char_count = 1
|
|
for char in arguments:
|
|
if (last_char == '' or last_char == ' ') and char == '[':
|
|
find_closing_square_bracket = True
|
|
elif (last_char == '[' and
|
|
find_closing_square_bracket and
|
|
char == '<'):
|
|
is_optional_argument = True
|
|
current_argument += char
|
|
elif find_closing_square_bracket:
|
|
if char == ']':
|
|
if is_optional_argument:
|
|
new_arg_list.append({'name': current_argument,
|
|
'has_comma': has_comma,
|
|
'has_multiple': has_multiple,
|
|
'required': False})
|
|
arg_count += 1
|
|
find_closing_square_bracket = False
|
|
current_argument = ''
|
|
has_comma = False
|
|
has_multiple = False
|
|
elif is_optional_argument:
|
|
if char == ',':
|
|
has_comma = True
|
|
elif last_char == '.' and char == '.':
|
|
has_multiple = True
|
|
elif not has_comma:
|
|
current_argument += char
|
|
elif char == '<' and (last_char == '' or last_char == ' '):
|
|
is_required_argument = True
|
|
current_argument += char
|
|
elif char == ',':
|
|
has_comma = True
|
|
if last_char == '>':
|
|
new_arg_list[arg_count - 1]['hasComma'] = True
|
|
has_comma = False
|
|
elif is_required_argument:
|
|
current_argument += char
|
|
if char == '>' and current_argument:
|
|
if char_count == len(arguments[0]):
|
|
new_arg_list.append({'name': current_argument,
|
|
'required': True})
|
|
arg_count += 1
|
|
is_required_argument = False
|
|
current_argument = ''
|
|
else:
|
|
next_char = 'x'
|
|
if current_command == 'Resolve':
|
|
if char_count < len(arguments[0]):
|
|
next_char = arguments[0][char_count:char_count + 1]
|
|
|
|
if next_char and not next_char == '.' and not next_char == ':':
|
|
new_arg_list.append({'name': current_argument,
|
|
'required': True})
|
|
arg_count += 1
|
|
is_required_argument = False
|
|
current_argument = ''
|
|
elif (arg_count > 0 and
|
|
current_argument == '' and
|
|
last_char == '.' and
|
|
'multiple' in new_arg_list[arg_count - 1] and
|
|
char == ' '):
|
|
current_argument = ''
|
|
elif (arg_count > 0 and
|
|
current_argument == '' and
|
|
last_char == '.' and
|
|
char == '.'):
|
|
new_arg_list[arg_count - 1]['multiple'] = True
|
|
|
|
char_count += 1
|
|
last_char = char
|
|
return new_arg_list
|
|
|
|
def get_group_name_from_variable(file_path, variable_name):
|
|
vtctl_go_file = open(file_path, 'rU')
|
|
vtctl_go_data = vtctl_go_file.readlines()
|
|
vtctl_go_file.close()
|
|
|
|
for line in vtctl_go_data:
|
|
regex = r'const\s*' + re.escape(variable_name) + r'\s*=\s*\"([^\"]+)\"'
|
|
if re.search(regex, line):
|
|
return line.split('=')[1].strip().strip('"')
|
|
return variable_name
|
|
|
|
def main(root_directory):
|
|
arg_definitions = {}
|
|
commands = {}
|
|
command_groups = {}
|
|
error_counts = {}
|
|
functions = {}
|
|
|
|
# Read the .go files in the /vitess/go/vt/vtctl/ directory
|
|
vtctl_dir_path = root_directory + '/go/vt/vtctl/'
|
|
go_files = next(os.walk(vtctl_dir_path))[2]
|
|
for path in go_files:
|
|
if not path.endswith('.go'):
|
|
continue
|
|
vtctl_go_path = vtctl_dir_path + path
|
|
vtctl_go_file = open(vtctl_go_path, 'rU')
|
|
vtctl_go_data = vtctl_go_file.readlines()
|
|
vtctl_go_file.close()
|
|
|
|
add_command_syntax = False
|
|
get_commands = False
|
|
get_argument_definitions = False
|
|
get_wrong_arg_count_error = False
|
|
get_group_name = False
|
|
current_command_argument = ''
|
|
current_command_argument_value = ''
|
|
current_command = ''
|
|
current_function = ''
|
|
is_func_init = False
|
|
is_flag_section = False
|
|
|
|
# treat func init() same as var commands
|
|
# treat addCommand("Group Name"... same as command {... in vtctl.go group
|
|
# Reformat Generic Help command to same format as commands in backup.go
|
|
# and reparent.go.
|
|
# Add logic to capture command data from those commands.
|
|
for line in vtctl_go_data:
|
|
|
|
# skip comments and empty lines
|
|
if line.strip() == '' or line.strip().startswith('//'):
|
|
continue
|
|
|
|
if is_func_init and not is_flag_section and re.search(r'^if .+ {', line.strip()):
|
|
is_flag_section = True
|
|
elif is_func_init and not is_flag_section and line.strip() == 'servenv.OnRun(func() {':
|
|
pass
|
|
elif is_func_init and is_flag_section and line.strip() == 'return':
|
|
pass
|
|
elif is_func_init and is_flag_section and line.strip() == '}':
|
|
is_flag_section = False
|
|
elif is_func_init and (line.strip() == '}' or line.strip() == '})'):
|
|
is_func_init = False
|
|
elif get_commands:
|
|
# This line precedes a command group's name, e.g. "Tablets" or "Shards."
|
|
# Capture the group name on the next line.
|
|
if line.strip() == '{':
|
|
get_group_name = True
|
|
# Capture the name of a command group.
|
|
elif get_group_name:
|
|
# Regex to identify the group name. Line in code looks like:
|
|
# "Tablets", []command{
|
|
find_group = re.findall(r'\"([^\"]+)\", \[\]\s*command\s*\{', line)
|
|
if find_group:
|
|
current_group = find_group[0]
|
|
if current_group not in commands:
|
|
commands[current_group] = {}
|
|
get_group_name = False
|
|
|
|
# First line of a command in addCommand syntax. This contains the
|
|
# name of the group that the command is in. Line in code looks like:
|
|
# addCommand{"Shards", command{
|
|
elif re.search(r'^addCommand\(', line.strip()):
|
|
command_data = re.findall(r'addCommand\s*\(\s*([^\,]+),\s*command\{',
|
|
line)
|
|
if command_data:
|
|
current_group = command_data[0]
|
|
current_group_strip_quotes = current_group.strip('"')
|
|
if current_group == current_group_strip_quotes:
|
|
current_group = get_group_name_from_variable(vtctl_go_path,
|
|
current_group)
|
|
else:
|
|
current_group = current_group_strip_quotes
|
|
if current_group not in commands:
|
|
commands[current_group] = {}
|
|
add_command_syntax = True
|
|
|
|
elif add_command_syntax and is_func_init:
|
|
if not current_command:
|
|
current_command = line.strip().strip(',').strip('"')
|
|
commands[current_group][current_command] = {
|
|
'definition': '',
|
|
'argument_list': {
|
|
'flags': {},
|
|
'args': []
|
|
},
|
|
'errors': {'other': []}}
|
|
command_groups[current_command] = current_group
|
|
elif 'function' not in commands[current_group][current_command]:
|
|
function = line.strip().strip(',')
|
|
commands[current_group][current_command]['function'] = function
|
|
functions[function] = current_command
|
|
elif 'arguments' not in commands[current_group][current_command]:
|
|
arguments = line.strip().strip(',')
|
|
commands[current_group][current_command]['arguments'] = arguments
|
|
if arguments:
|
|
new_arg_list = parse_arg_list(arguments, current_command)
|
|
commands[current_group][current_command]['argument_list']['args'] = new_arg_list
|
|
else:
|
|
definition_list = line.strip().split(' +')
|
|
for definition_part in definition_list:
|
|
definition = definition_part.strip().strip('})')
|
|
definition = definition.replace('}},', '')
|
|
definition = definition.replace('"', '')
|
|
commands[current_group][current_command]['definition'] += (
|
|
definition)
|
|
if line.strip().endswith('})'):
|
|
current_command = ''
|
|
# Command definition ends with line ending in "},".
|
|
elif line.strip().endswith('})'):
|
|
current_command = ''
|
|
add_command_syntax = False
|
|
|
|
# First line of a command. This contains the command name and the
|
|
# function used to process the command. Line in code looks like:
|
|
# command{"ScrapTablet", commandScrapTablet,
|
|
elif re.search(r'^\{\s*\"[^\"]+\",\s*command[^\,]+\,', line.strip()):
|
|
command_data = re.findall(r'\{\s*\"([^\"]+)\",\s*([^\,]+)\,',
|
|
line)
|
|
if command_data:
|
|
# Capture the command name and associate it with its function.
|
|
# Create a data structure to contain information about the command
|
|
# and its processing rules.
|
|
current_command = command_data[0][0]
|
|
function = command_data[0][1]
|
|
commands[current_group][current_command] = {
|
|
'definition': '',
|
|
'argument_list': {
|
|
'flags': {},
|
|
'args': []
|
|
},
|
|
'errors': {'other': []}}
|
|
|
|
# Associate the function with the command and vice versa.
|
|
# Associate the command with its group and vice versa.
|
|
commands[current_group][current_command]['function'] = function
|
|
command_groups[current_command] = current_group
|
|
functions[function] = current_command
|
|
|
|
# If code has identified a command name but has not identified
|
|
# arguments for that command, capture the next line and store it
|
|
# as the command arguments.
|
|
elif (current_command and
|
|
'arguments' not in commands[current_group][current_command]):
|
|
arguments = re.findall(r'\"([^\"]+)\"', line)
|
|
if arguments:
|
|
commands[current_group][current_command]['arguments'] = arguments[0]
|
|
last_char = ''
|
|
|
|
find_closing_square_bracket = False
|
|
has_comma = False
|
|
has_multiple = False
|
|
is_optional_argument = False
|
|
is_required_argument = False
|
|
current_argument = ''
|
|
|
|
new_arg_list = []
|
|
arg_count = 0
|
|
char_count = 1
|
|
for char in arguments[0]:
|
|
if (last_char == '' or last_char == ' ') and char == '[':
|
|
find_closing_square_bracket = True
|
|
elif (last_char == '[' and
|
|
find_closing_square_bracket and
|
|
char == '<'):
|
|
is_optional_argument = True
|
|
current_argument += char
|
|
elif find_closing_square_bracket:
|
|
if char == ']':
|
|
if is_optional_argument:
|
|
new_arg_list.append({'name': current_argument,
|
|
'has_comma': has_comma,
|
|
'has_multiple': has_multiple,
|
|
'required': False})
|
|
arg_count += 1
|
|
find_closing_square_bracket = False
|
|
current_argument = ''
|
|
has_comma = False
|
|
has_multiple = False
|
|
elif is_optional_argument:
|
|
if char == ',':
|
|
has_comma = True
|
|
elif last_char == '.' and char == '.':
|
|
has_multiple = True
|
|
elif not has_comma:
|
|
current_argument += char
|
|
elif char == '<' and (last_char == '' or last_char == ' '):
|
|
is_required_argument = True
|
|
current_argument += char
|
|
elif char == ',':
|
|
has_comma = True
|
|
if last_char == '>':
|
|
new_arg_list[arg_count - 1]['hasComma'] = True
|
|
has_comma = False
|
|
elif is_required_argument:
|
|
current_argument += char
|
|
if char == '>' and current_argument:
|
|
if char_count == len(arguments[0]):
|
|
new_arg_list.append({'name': current_argument,
|
|
'required': True})
|
|
arg_count += 1
|
|
is_required_argument = False
|
|
current_argument = ''
|
|
else:
|
|
next_char = 'x'
|
|
if current_command == 'Resolve':
|
|
#print len(arguments[0])
|
|
#print char_count
|
|
if char_count < len(arguments[0]):
|
|
next_char = arguments[0][char_count:char_count + 1]
|
|
#print next_char
|
|
|
|
#if char_count < (len(arguments[0]) - 1):
|
|
# print current_argument
|
|
# next_char = arguments[0][char_count + 1:char_count + 2]
|
|
# print next_char
|
|
if next_char and not next_char == '.' and not next_char == ':':
|
|
new_arg_list.append({'name': current_argument,
|
|
'required': True})
|
|
arg_count += 1
|
|
is_required_argument = False
|
|
current_argument = ''
|
|
elif (arg_count > 0 and
|
|
current_argument == '' and
|
|
last_char == '.' and
|
|
'multiple' in new_arg_list[arg_count - 1] and
|
|
char == ' '):
|
|
current_argument = ''
|
|
elif (arg_count > 0 and
|
|
current_argument == '' and
|
|
last_char == '.' and
|
|
char == '.'):
|
|
new_arg_list[arg_count - 1]['multiple'] = True
|
|
#elif (arg_count > 0 and
|
|
# current_argument == '' and
|
|
# last_char == '.'):
|
|
|
|
char_count += 1
|
|
last_char = char
|
|
commands[current_group][current_command]['argument_list']['args'] = (
|
|
new_arg_list)
|
|
|
|
else:
|
|
commands[current_group][current_command]['arguments'] = ""
|
|
# If code has identified a command and arguments, capture remaining lines
|
|
# as the command description. Assume the description ends at the line
|
|
# of code ending with "},".
|
|
elif current_command:
|
|
definition_list = line.rstrip('},').split(' +')
|
|
for definition_part in definition_list:
|
|
definition = definition_part.strip().strip('"')
|
|
#definition = definition.replace('\\n', '<br><br>')
|
|
definition = definition.replace('"},', '')
|
|
commands[current_group][current_command]['definition'] += definition
|
|
if line.strip().endswith('},'):
|
|
current_command = ''
|
|
# Command definition ends with line ending in "},".
|
|
elif line.strip().endswith('},'):
|
|
current_command = ''
|
|
|
|
# Capture information about a function that processes a command.
|
|
# Here, identify the function name.
|
|
elif re.search(r'^func ([^\)]+)\(', line.strip()):
|
|
current_function = ''
|
|
function_data = re.findall(r'func ([^\)]+)\(', line.strip())
|
|
if function_data:
|
|
current_function = function_data[0]
|
|
|
|
elif current_function:
|
|
# Lines that contain this:
|
|
# = subFlags....
|
|
# generally seem to contain descriptions of flags for the function.
|
|
# Capture the content type of the argument, its name, default value,
|
|
# and description. Associate these with the command that calls this
|
|
# function.
|
|
if re.search(r'\=\s*subFlags\.([^\(]+)\(\s*\"([^\"]+)\"\,' +
|
|
'([^\,]+)\,\s*\"([^\"]+)\"', line):
|
|
argument_data = re.findall(r'\=\s*subFlags\.([^\(]+)' +
|
|
'\(\s*\"([^\"]+)\"\,([^\,]+)\,\s*\"([^\"]+)\"', line)
|
|
if argument_data and current_function in functions:
|
|
[arg_type, arg_name, arg_default, arg_definition] = argument_data[0]
|
|
if arg_type == 'Bool':
|
|
arg_type = 'Boolean'
|
|
if arg_type == 'String' or arg_type == 'int':
|
|
arg_type = arg_type.lower()
|
|
|
|
arg_default = arg_default.strip().strip('"')
|
|
|
|
fcommand = functions[current_function]
|
|
fgroup = command_groups[fcommand]
|
|
commands[fgroup][fcommand]['argument_list']['flags'][arg_name] = {
|
|
'type': arg_type,
|
|
'default': arg_default,
|
|
'definition': arg_definition
|
|
}
|
|
|
|
elif re.search(r'\s*subFlags\.Var\(\s*([^\,]+)\,\s*\"([^\"]+)\"\,' +
|
|
'\s*\"([^\"]+)\"', line):
|
|
#print 'found subFlags.Var'
|
|
argument_data = re.findall(r'\s*subFlags\.Var\(\s*([^\,]+)\,' +
|
|
'\s*\"([^\"]+)\"\,\s*\"([^\"]+)\"', line)
|
|
#print argument_data
|
|
if argument_data and current_function in functions:
|
|
[arg_type, arg_name, arg_definition] = argument_data[0]
|
|
arg_type = 'string' # Var?
|
|
|
|
fcommand = functions[current_function]
|
|
fgroup = command_groups[fcommand]
|
|
commands[fgroup][fcommand]['argument_list']['flags'][arg_name] = {
|
|
'type': arg_type,
|
|
'definition': arg_definition
|
|
}
|
|
|
|
# Capture information for errors that indicate that the command
|
|
# was called with the incorrect number of arguments. Use the
|
|
# code to determine whether the code is looking for an exact number
|
|
# of arguments, a minimum number, a maximum number, etc.
|
|
elif re.search(r'if subFlags.NArg', line):
|
|
wrong_arg_data = re.findall(
|
|
r'subFlags.NArg\(\)\s*([\<\>\!\=]+)\s*(\d+)', line)
|
|
error_counts[current_function] = {'min': None,
|
|
'max': None,
|
|
'exact': []}
|
|
if wrong_arg_data:
|
|
get_wrong_arg_count_error = True
|
|
for wrong_arg_info in wrong_arg_data:
|
|
if wrong_arg_info[0] == '!=':
|
|
error_counts[current_function]['exact'].append(
|
|
wrong_arg_info[1])
|
|
elif wrong_arg_info[0] == '<':
|
|
error_counts[current_function]['min'] = wrong_arg_info[1]
|
|
elif wrong_arg_info[0] == '>':
|
|
error_counts[current_function]['max'] = wrong_arg_info[1]
|
|
elif wrong_arg_info[0] == '==' and wrong_arg_info[1] == '0':
|
|
error_counts[current_function]['min'] = '1'
|
|
|
|
# Capture data about other errors that the command might yield.
|
|
# TODO: Capture other errors from other files, such as
|
|
# //depot/google3/third_party/golang/vitess/go/vt/topo/tablet.go
|
|
elif get_wrong_arg_count_error and re.search(r'fmt.Errorf', line):
|
|
get_wrong_arg_count_error = False
|
|
error_data = re.findall(r'fmt\.Errorf\(\"([^\"]+)\"\)', line)
|
|
if error_data and current_function in functions:
|
|
fcommand = functions[current_function]
|
|
fgroup = command_groups[fcommand]
|
|
commands[fgroup][fcommand]['errors']['ARG_COUNT'] = {
|
|
'exact_count': error_counts[current_function]['exact'],
|
|
'min_count': error_counts[current_function]['min'],
|
|
'max_count': error_counts[current_function]['max'],
|
|
'message': error_data[0]
|
|
}
|
|
elif line.strip().endswith('}') or line.strip().endswith('{'):
|
|
get_wrong_arg_count_error = False
|
|
elif current_function in functions and re.search(r'fmt.Errorf', line):
|
|
error_data = re.findall(r'fmt\.Errorf\(\"([^\"]+)\"', line)
|
|
if error_data:
|
|
fcommand = functions[current_function]
|
|
fgroup = command_groups[fcommand]
|
|
if (error_data[0] not in
|
|
commands[fgroup][fcommand]['errors']['other']):
|
|
if ('ARG_COUNT' in commands[fgroup][fcommand]['errors'] and
|
|
error_data[0] !=
|
|
commands[fgroup][fcommand]['errors']['ARG_COUNT']['message']):
|
|
commands[fgroup][fcommand]['errors']['other'].append(
|
|
error_data[0])
|
|
elif 'ARG_COUNT' not in commands[fgroup][fcommand]['errors']:
|
|
commands[fgroup][fcommand]['errors']['other'].append(
|
|
error_data[0])
|
|
|
|
|
|
# This line indicates that commands are starting. No need to capture
|
|
# stuff before here.
|
|
elif re.search(r'^var commands =', line.strip()):
|
|
get_commands = True
|
|
|
|
elif re.search(r'^func init\(\)', line.strip()):
|
|
get_commands = True
|
|
is_func_init = True
|
|
|
|
if get_argument_definitions:
|
|
if line.strip() == '*/':
|
|
get_argument_definitions = False
|
|
elif line.startswith('-'):
|
|
definition_data = re.findall(r'^\-\s*([^\:]+)\:\s*(.*)', line)
|
|
if definition_data:
|
|
arg_name_array = definition_data[0][0].split('(internal)')
|
|
current_command_argument = arg_name_array[0].strip()
|
|
arg_definitions[current_command_argument] = {}
|
|
if len(arg_name_array) > 1:
|
|
arg_definitions[current_command_argument]['internal'] = True
|
|
arg_definitions[current_command_argument]['description'] = (
|
|
definition_data[0][1].strip())
|
|
current_command_argument_value = ''
|
|
elif current_command_argument and line.lstrip()[0:2] == '--':
|
|
if 'list_items' not in arg_definitions[current_command_argument]:
|
|
arg_definitions[current_command_argument]['list_items'] = []
|
|
arg_value_data = re.findall(r'^\--\s*([^\:]+)\:\s*(.*)', line.strip())
|
|
if arg_value_data:
|
|
current_command_argument_value = arg_value_data[0][0]
|
|
argument_definition = arg_value_data[0][1]
|
|
arg_definitions[current_command_argument]['list_items'].append({
|
|
'value': current_command_argument_value,
|
|
'definition': argument_definition})
|
|
elif current_command_argument_value:
|
|
list_length = (len(
|
|
arg_definitions[current_command_argument]['list_items']) - 1)
|
|
arg_definitions[current_command_argument][
|
|
'list_items'][list_length]['definition'] += ' ' + line.strip()
|
|
elif line and current_command_argument:
|
|
arg_definitions[current_command_argument]['description'] += ' ' + line.strip()
|
|
|
|
elif re.search(r'^COMMAND ARGUMENT DEFINITIONS', line.strip()):
|
|
get_argument_definitions = True
|
|
|
|
# Handle arguments that have different names but same meaning
|
|
new_arg_definitions = {}
|
|
modifiers = ['destination', 'new master', 'original', 'parent', 'served',
|
|
'source', 'target']
|
|
|
|
for defined_argument in arg_definitions:
|
|
argument_list = defined_argument.split(',')
|
|
if len(argument_list) > 1:
|
|
for argument_list_item in argument_list:
|
|
new_arg_definitions[argument_list_item.strip()] = (
|
|
arg_definitions[defined_argument])
|
|
for modifier in modifiers:
|
|
new_arg_definitions[modifier + ' ' + argument_list_item.strip()] = (
|
|
arg_definitions[defined_argument])
|
|
else:
|
|
new_arg_definitions[defined_argument] = arg_definitions[defined_argument]
|
|
for modifier in modifiers:
|
|
new_arg_definitions[modifier + ' ' + defined_argument] = (
|
|
arg_definitions[defined_argument])
|
|
|
|
#print json.dumps(new_arg_definitions, sort_keys=True, indent=4)
|
|
#print json.dumps(commands["Generic"], sort_keys=True, indent=4)
|
|
|
|
create_reference_doc(root_directory, commands, new_arg_definitions)
|
|
|
|
return
|
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = optparse.OptionParser()
|
|
parser.add_option('-r', '--root-directory', default='..',
|
|
help='root directory for the vitess github tree')
|
|
(options, args) = parser.parse_args()
|
|
main(options.root_directory)
|