Move autogenerated completions to ~/.config/fish/generated_completions/

https://github.com/fish-shell/fish-shell/issues/576
This commit is contained in:
ridiculousfish
2013-02-17 16:14:36 -08:00
parent 911c37e63e
commit e7c1cb7185
4 changed files with 55 additions and 74 deletions

View File

@@ -429,10 +429,7 @@ void completion_autoload_t::command_removed(const wcstring &cmd)
} }
/** /** Create a new completion entry */
Create a new completion entry
*/
void append_completion(std::vector<completion_t> &completions, const wcstring &comp, const wcstring &desc, complete_flags_t flags) void append_completion(std::vector<completion_t> &completions, const wcstring &comp, const wcstring &desc, complete_flags_t flags)
{ {
completions.push_back(completion_t(comp, desc, flags)); completions.push_back(completion_t(comp, desc, flags));

View File

@@ -36,7 +36,7 @@ if not contains $__fish_datadir/functions $fish_function_path
end end
if not set -q fish_complete_path if not set -q fish_complete_path
set fish_complete_path $configdir/fish/completions $__fish_sysconfdir/completions $__fish_datadir/completions set fish_complete_path $configdir/fish/completions $__fish_sysconfdir/completions $__fish_datadir/completions $configdir/fish/generated_completions
end end
if not contains $__fish_datadir/completions $fish_complete_path if not contains $__fish_datadir/completions $fish_complete_path

View File

@@ -1,3 +1,3 @@
function fish_update_completions --description "Update man-page based completions" function fish_update_completions --description "Update man-page based completions"
eval $__fish_datadir/tools/create_manpage_completions.py --manpath --progress --yield-to $__fish_datadir/completions/ eval \"$__fish_datadir/tools/create_manpage_completions.py\" --manpath --progress --cleanup-in '~/.config/fish/completions'
end end

View File

@@ -29,7 +29,7 @@ CMDNAME = ""
# Information used to track which of our parsers were successful # Information used to track which of our parsers were successful
PARSER_INFO = {} PARSER_INFO = {}
# builtcommand writes into this global variable, yuck # built_command writes into this global variable, yuck
built_command_output = [] built_command_output = []
# Diagnostic output # Diagnostic output
@@ -101,7 +101,7 @@ def output_complete_command(cmdname, args, description, output_list):
comps.append(description) comps.append(description)
output_list.append(' '.join(comps)) output_list.append(' '.join(comps))
def builtcommand(options, description): def built_command(options, description):
# print "Options are: ", options # print "Options are: ", options
man_optionlist = re.split(" |,|\"|=|[|]", options) man_optionlist = re.split(" |,|\"|=|[|]", options)
fish_options = [] fish_options = []
@@ -258,7 +258,7 @@ class Type1ManParser(ManParser):
optionName = unquoteSingleQuotes(optionName) optionName = unquoteSingleQuotes(optionName)
optionDescription = data[1].strip().replace("\n"," ") optionDescription = data[1].strip().replace("\n"," ")
# print >> sys.stderr, "Option: ", optionName," Description: ", optionDescription , '\n' # print >> sys.stderr, "Option: ", optionName," Description: ", optionDescription , '\n'
builtcommand(optionName, optionDescription) built_command(optionName, optionDescription)
else: else:
add_diagnostic('Unable to split option from description') add_diagnostic('Unable to split option from description')
@@ -288,7 +288,7 @@ class Type1ManParser(ManParser):
optionName = unquoteSingleQuotes(optionName) optionName = unquoteSingleQuotes(optionName)
optionDescription = data[1].strip().replace("\n"," ") optionDescription = data[1].strip().replace("\n"," ")
# print "Option: ", optionName," Description: ", optionDescription , '\n' # print "Option: ", optionName," Description: ", optionDescription , '\n'
builtcommand(optionName, optionDescription) built_command(optionName, optionDescription)
else: else:
add_diagnostic('Unable to split option from description') add_diagnostic('Unable to split option from description')
return False return False
@@ -329,7 +329,7 @@ class Type1ManParser(ManParser):
optionName = unquoteSingleQuotes(optionName) optionName = unquoteSingleQuotes(optionName)
optionDescription = data[1].strip().replace("\n"," ") optionDescription = data[1].strip().replace("\n"," ")
# print "Option: ", optionName," Description: ", optionDescription , '\n' # print "Option: ", optionName," Description: ", optionDescription , '\n'
builtcommand(optionName, optionDescription) built_command(optionName, optionDescription)
else: else:
# print data # print data
add_diagnostic('Unable to split option from description') add_diagnostic('Unable to split option from description')
@@ -395,7 +395,7 @@ class Type2ManParser(ManParser):
optionName = unquoteSingleQuotes(optionName) optionName = unquoteSingleQuotes(optionName)
optionDescription = data[1].strip().replace("\n"," ") optionDescription = data[1].strip().replace("\n"," ")
# print "Option: ", optionName," Description: ", optionDescription , '\n' # print "Option: ", optionName," Description: ", optionDescription , '\n'
builtcommand(optionName, optionDescription) built_command(optionName, optionDescription)
else: else:
# print >> sys.stderr, data # print >> sys.stderr, data
add_diagnostic('Unable to split option from description') add_diagnostic('Unable to split option from description')
@@ -452,7 +452,7 @@ class Type3ManParser(ManParser):
optionName = unquoteSingleQuotes(optionName) optionName = unquoteSingleQuotes(optionName)
optionDescription = data[1].strip().replace("\n"," ") optionDescription = data[1].strip().replace("\n"," ")
# print >> sys.stderr, "Option: ", optionName," Description: ", optionDescription , '\n' # print >> sys.stderr, "Option: ", optionName," Description: ", optionDescription , '\n'
builtcommand(optionName, optionDescription) built_command(optionName, optionDescription)
else: else:
add_diagnostic('Unable to split option from description') add_diagnostic('Unable to split option from description')
@@ -508,7 +508,7 @@ class Type4ManParser(ManParser):
optionName = unquoteSingleQuotes(optionName) optionName = unquoteSingleQuotes(optionName)
optionDescription = data[1].strip().replace("\n"," ") optionDescription = data[1].strip().replace("\n"," ")
# print "Option: ", optionName," Description: ", optionDescription , '\n' # print "Option: ", optionName," Description: ", optionDescription , '\n'
builtcommand(optionName, optionDescription) built_command(optionName, optionDescription)
else: else:
add_diagnostic('Unable to split option from description') add_diagnostic('Unable to split option from description')
@@ -615,10 +615,10 @@ class TypeDarwinManParser(ManParser):
continue continue
elif len(name) > 1: elif len(name) > 1:
# Output the command # Output the command
builtcommand(('-' * dash_count) + name, desc) built_command(('-' * dash_count) + name, desc)
got_something = True got_something = True
elif len(name) == 1: elif len(name) == 1:
builtcommand('-' + name, desc) built_command('-' + name, desc)
got_something = True got_something = True
return got_something return got_something
@@ -673,7 +673,7 @@ class TypeDeroffManParser(ManParser):
if description: description += ' ' if description: description += ' '
description += lines.pop(0) description += lines.pop(0)
builtcommand(options, description) built_command(options, description)
got_something = True got_something = True
return got_something return got_something
@@ -704,19 +704,25 @@ def file_is_overwritable(path):
file.close() file.close()
return result return result
# Remove any and all autogenerated completions in the given directory
# Return whether the file at the given path either does not exist, or exists but appears to be a file we output (and hence can overwrite) def cleanup_autogenerated_completions_in_directory(dir):
def file_missing_or_overwritable(path):
try: try:
return file_is_overwritable(path) for filename in os.listdir(dir):
except IOError as err: # Skip non .fish files
if err.errno == 2: if not filename.endswith('.fish'): continue
# File does not exist, full steam ahead path = os.path.join(dir, filename)
return True try:
else: if file_is_overwritable(path):
# Something else happened os.unlink(path)
return False except IOError:
pass
except OSError:
pass
except OSError as err:
return False
# Delete the file if it is autogenerated # Delete the file if it is autogenerated
def cleanup_autogenerated_file(path): def cleanup_autogenerated_file(path):
@@ -726,7 +732,7 @@ def cleanup_autogenerated_file(path):
except (OSError, IOError): except (OSError, IOError):
pass pass
def parse_manpage_at_path(manpage_path, yield_to_dirs, output_directory): def parse_manpage_at_path(manpage_path, output_directory):
filename = os.path.basename(manpage_path) filename = os.path.basename(manpage_path)
# Clear diagnostics # Clear diagnostics
@@ -800,11 +806,7 @@ def parse_manpage_at_path(manpage_path, yield_to_dirs, output_directory):
else: else:
fullpath = os.path.join(output_directory, CMDNAME + '.fish') fullpath = os.path.join(output_directory, CMDNAME + '.fish')
try: try:
if file_missing_or_overwritable(fullpath): output_file = codecs.open(fullpath, "w", encoding="utf-8")
output_file = codecs.open(fullpath, "w", encoding="utf-8");
else:
add_diagnostic("Not overwriting the file at '%s'" % fullpath)
except IOError as err: except IOError as err:
add_diagnostic("Unable to open file '%s': error(%d): %s" % (fullpath, err.errno, err.strerror)) add_diagnostic("Unable to open file '%s': error(%d): %s" % (fullpath, err.errno, err.strerror))
return False return False
@@ -836,17 +838,7 @@ def parse_manpage_at_path(manpage_path, yield_to_dirs, output_directory):
return success return success
# Indicates whether the given filename has a presence in one of the yield-to directories def parse_and_output_man_pages(paths, output_directory, show_progress):
# If so, there's a bespoke completion and we should not generate one
def file_in_yield_directory(filename, yield_to_dirs):
for yield_dir in yield_to_dirs:
test_path = os.path.join(yield_dir, filename)
if os.path.isfile(test_path):
# Yield to the existing file
return True
return False
def parse_and_output_man_pages(paths, output_directory, yield_to_dirs, show_progress):
global diagnostic_indent, CMDNAME global diagnostic_indent, CMDNAME
paths.sort() paths.sort()
total_count = len(paths) total_count = len(paths)
@@ -878,20 +870,12 @@ def parse_and_output_man_pages(paths, output_directory, yield_to_dirs, show_prog
# Compute the path that we would write to # Compute the path that we would write to
output_path = os.path.join(output_directory, output_file_name) output_path = os.path.join(output_directory, output_file_name)
if file_in_yield_directory(output_file_name, yield_to_dirs):
# We're duplicating a bespoke completion - delete any existing completion
skip = True
cleanup_autogenerated_file(output_path)
elif not file_missing_or_overwritable(output_path):
# Don't overwrite a user-created completion
skip = True
# Now skip if requested # Now skip if requested
if skip: if skip:
continue continue
try: try:
if parse_manpage_at_path(manpage_path, yield_to_dirs, output_directory): if parse_manpage_at_path(manpage_path, output_directory):
successful_count += 1 successful_count += 1
except IOError: except IOError:
diagnostic_indent = 0 diagnostic_indent = 0
@@ -928,16 +912,13 @@ def get_paths_from_manpath():
result.append(os.path.join(directory_path, name)) result.append(os.path.join(directory_path, name))
return result return result
def usage(script_name): def usage(script_name):
print("Usage: {0} [-v, --verbose] [-s, --stdout] [-d, --directory] [-p, --progress] files...".format(script_name)) print("Usage: {0} [-v, --verbose] [-s, --stdout] [-d, --directory] [-p, --progress] files...".format(script_name))
print("""Command options are: print("""Command options are:
-h, --help\t\tShow this help message -h, --help\t\tShow this help message
-v, --verbose [0, 1, 2]\tShow debugging output to stderr. Larger is more verbose. -v, --verbose [0, 1, 2]\tShow debugging output to stderr. Larger is more verbose.
-s, --stdout\tWrite all completions to stdout (trumps the --directory option) -s, --stdout\tWrite all completions to stdout (trumps the --directory option)
-d, --directory [dir]\tWrite all completions to the given directory, instead of to ~/.config/fish/completions -d, --directory [dir]\tWrite all completions to the given directory, instead of to ~/.config/fish/generated_completions
-y, --yield-to [dir]\tSkip completions that are already present in the given directory
-m, --manpath\tProcess all man1 files available in the manpath (as determined by manpath) -m, --manpath\tProcess all man1 files available in the manpath (as determined by manpath)
-p, --progress\tShow progress -p, --progress\tShow progress
""") """)
@@ -945,15 +926,16 @@ def usage(script_name):
if __name__ == "__main__": if __name__ == "__main__":
script_name = sys.argv[0] script_name = sys.argv[0]
try: try:
opts, file_paths = getopt.gnu_getopt(sys.argv[1:], 'v:sd:hmpy:z', ['verbose=', 'stdout', 'directory=', 'help', 'manpath', 'progress', 'yield-to=']) opts, file_paths = getopt.gnu_getopt(sys.argv[1:], 'v:sd:hmpc:z', ['verbose=', 'stdout', 'directory=', 'cleanup-in=', 'help', 'manpath', 'progress'])
except getopt.GetoptError as err: except getopt.GetoptError as err:
print(err.msg) # will print something like "option -a not recognized" print(err.msg) # will print something like "option -a not recognized"
usage(script_name) usage(script_name)
sys.exit(2) sys.exit(2)
# If a completion already exists in one of the yield-to directories, then don't overwrite it # Directories within which we will clean up autogenerated completions
# And even delete an existing autogenerated one # This script originally wrote completions into ~/.config/fish/completions
yield_to_dirs = [] # Now it writes them into a separate directory
cleanup_directories = []
use_manpath, show_progress, custom_dir = False, False, False use_manpath, show_progress, custom_dir = False, False, False
output_directory = '' output_directory = ''
@@ -971,10 +953,8 @@ if __name__ == "__main__":
use_manpath = True use_manpath = True
elif opt in ('-p', '--progress'): elif opt in ('-p', '--progress'):
show_progress = True show_progress = True
elif opt in ('-y', '--yield-to'): elif opt in ('-c', '--cleanup-in'):
yield_to_dirs.append(value) cleanup_directories.append(value)
if not os.path.isdir(value):
sys.stderr.write("Warning: yield-to directory does not exist: '{0}'\n".format(value))
elif opt in ('-z'): elif opt in ('-z'):
DEROFF_ONLY = True DEROFF_ONLY = True
else: else:
@@ -984,14 +964,18 @@ if __name__ == "__main__":
# Fetch all man1 files from the manpath # Fetch all man1 files from the manpath
file_paths.extend(get_paths_from_manpath()) file_paths.extend(get_paths_from_manpath())
if cleanup_directories:
for cleanup_dir in cleanup_directories:
cleanup_autogenerated_completions_in_directory(cleanup_dir)
if not file_paths: if not file_paths:
print("No paths specified") print("No paths specified")
sys.exit(0) sys.exit(0)
if not WRITE_TO_STDOUT and not output_directory: if not WRITE_TO_STDOUT and not output_directory:
# Default to ~/.config/fish/completions/ # Default to ~/.config/fish/generated_completions/
# Create it if it doesn't exist # Create it if it doesn't exist
output_directory = os.path.expanduser('~/.config/fish/completions/') output_directory = os.path.expanduser('~/.config/fish/generated_completions/')
try: try:
os.makedirs(output_directory) os.makedirs(output_directory)
except OSError as e: except OSError as e:
@@ -999,11 +983,11 @@ if __name__ == "__main__":
raise raise
if True: if True:
parse_and_output_man_pages(file_paths, output_directory, yield_to_dirs, show_progress) parse_and_output_man_pages(file_paths, output_directory, show_progress)
else: else:
# Profiling code # Profiling code
import cProfile, pstats import cProfile, pstats
cProfile.run('parse_and_output_man_pages(file_paths, output_directory, yield_to_dirs, show_progress)', 'fooprof') cProfile.run('parse_and_output_man_pages(file_paths, output_directory, show_progress)', 'fooprof')
p = pstats.Stats('fooprof') p = pstats.Stats('fooprof')
p.sort_stats('cumulative').print_stats(100) p.sort_stats('cumulative').print_stats(100)
@@ -1012,4 +996,4 @@ if __name__ == "__main__":
for name in PARSER_INFO: for name in PARSER_INFO:
print('Parser ' + name + ':') print('Parser ' + name + ':')
print('\t' + ', '.join(PARSER_INFO[name])) print('\t' + ', '.join(PARSER_INFO[name]))
print('') print('')