Make create_manpage_completions output to ~/.config/fish/completions by default, and also discover man pages from reading the manpath

This commit is contained in:
ridiculousfish 2012-04-11 18:26:26 -07:00
parent cccd1cefbe
commit 2ec0778d78

View File

@ -17,7 +17,7 @@ Redistributions in binary form must reproduce the above copyright notice, this l
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import string, sys, re, os.path, gzip, traceback
import string, sys, re, os.path, gzip, traceback, getopt, errno
from deroff import Deroffer
# This gets set to the name of the command that we are currently executing
@ -30,8 +30,8 @@ built_command_output = []
diagnostic_output = []
diagnostic_indent = 0
global VERBOSE
VERBOSE = False
global VERBOSE, WRITE_TO_STDOUT
VERBOSE, WRITE_TO_STDOUT = False, False
def add_diagnostic(dgn, always = False):
# Add a diagnostic message if VERBOSE is True or always is True
@ -520,6 +520,7 @@ class TypeDarwinManParser(ManParser):
# Extract the name
name = self.trim_groff(lines.pop(0)).strip()
name = name.split(None, 2)[0]
# Extract the description
desc = ''
@ -527,7 +528,7 @@ class TypeDarwinManParser(ManParser):
# print "*", lines[0]
desc = desc + lines.pop(0)
#print "name: ", name
# print "name: ", name
if name == '-':
# Skip double -- arguments
@ -601,7 +602,41 @@ class TypeDeroffManParser(ManParser):
def name(self):
return "Deroffing man parser"
def parse_manpage_at_path(manpage_path):
# 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 file_missing_or_overwritable(path):
try:
result = False
file = open(path, 'r')
for line in file:
# Skip leading empty lines
line = line.strip()
if not line:
continue
# We look in the initial run of lines that start with #
if not line.startswith('#'):
break
# See if this contains the magic word
if 'Autogenerated' in line:
result = True
break
file.close()
return result
except IOError as (err, strerror):
if err == errno.ENOENT:
# File does not exist, full steam ahead
return True
else:
# Something else happened
return False
def parse_manpage_at_path(manpage_path, output_directory):
filename = os.path.basename(manpage_path)
# Clear diagnostics
@ -639,7 +674,6 @@ def parse_manpage_at_path(manpage_path):
# Ignore the millions of links to BUILTIN(1)
if manpage.find('BUILTIN 1') != -1:
return
# Clear the output list
built_command_output[:] = []
@ -659,11 +693,31 @@ def parse_manpage_at_path(manpage_path):
if success: break
if success:
if WRITE_TO_STDOUT:
output_file = sys.stdout
else:
fullpath = output_directory + CMDNAME + '.fish'
try:
if file_missing_or_overwritable(fullpath):
output_file = open(fullpath, 'w')
else:
add_diagnostic("Not overwriting the file at '%s'" % fullpath)
except IOError as (errno, strerror):
add_diagnostic("Unable to open file '%s': error(%d): %s" % (fullpath, errno, strerror))
return False
built_command_output.insert(0, "# %s: %s" % (CMDNAME, parser.name()))
# Output the magic word Autogenerated so we can tell if we can overwrite this
built_command_output.insert(1, "# Autogenerated from man pages")
for line in built_command_output:
print line
print ''
output_file.write(line)
output_file.write('\n')
output_file.write('\n')
add_diagnostic(manpage_path + ' parsed successfully')
if output_file != sys.stdout:
output_file.close()
else:
parser_names = ', '.join(p.name() for p in parsersToTry)
#add_diagnostic('%s contains no options or is unparsable' % manpage_path, True)
@ -674,14 +728,14 @@ def compare_paths(a, b):
""" Compare two paths by their base name, case insensitive """
return cmp(os.path.basename(a).lower(), os.path.basename(b).lower())
def parse_and_output_man_pages(paths):
def parse_and_output_man_pages(paths, output_directory):
global diagnostic_indent
paths.sort(compare_paths)
total_count = len(paths)
successful_count = 0
for manpage_path in paths:
try:
if parse_manpage_at_path(manpage_path):
if parse_manpage_at_path(manpage_path, output_directory):
successful_count += 1
except IOError:
diagnostic_indent = 0
@ -695,19 +749,89 @@ def parse_and_output_man_pages(paths):
flush_diagnostics(sys.stderr)
add_diagnostic("Successfully parsed %d / %d pages" % (successful_count, total_count), True)
flush_diagnostics(sys.stderr)
def get_paths_from_manpath():
# Return all the paths to man(1) files in the manpath
import subprocess, os
manpath = subprocess.check_output(['man', '--path']).strip()
parent_paths = manpath.split(':')
if not parent_paths:
print >> sys.stderr, "Unable to get the manpath (tried man --path)"
sys.exit(-1)
result = []
for parent_path in parent_paths:
directory_path = os.path.join(parent_path, 'man1')
try:
names = os.listdir(directory_path)
except OSError, e:
names = []
names.sort()
for name in names:
result.append(os.path.join(directory_path, name))
return result
def usage(script_name):
print "Usage: %s [-v, --verbose] [-s, --stdout] [-d, --directory] files..." % script_name
print """Command options are:
-h, --help\t\tShow this help message
-v, --verbose\tShow debugging output to stderr
-s, --stdout\tWrite all completions to stdout (trumps the --directory option)
-d, --directory\tWrite all completions to the given directory, instead of to ~/.config/fish/completions
-m, --manpath\tProcess all man1 files available in the manpath (as determined by man --path)
"""
if __name__ == "__main__":
args = sys.argv[1:]
if args and (args[0] == '-v' or args[0] == '--verbose'):
VERBOSE = True
args.pop(0)
script_name = sys.argv[0]
try:
opts, file_paths = getopt.gnu_getopt(sys.argv[1:], 'vsd:hm', ['verbose', 'stdout', 'directory=', 'help', 'manpath'])
except getopt.GetoptError, err:
print str(err) # will print something like "option -a not recognized"
usage(script_name)
sys.exit(2)
use_manpath = False
custom_dir = False
output_directory = ''
for opt, value in opts:
if opt in ('-v', '--verbose'):
VERBOSE = True
elif opt in ('-s', '--stdout'):
WRITE_TO_STDOUT = True
elif opt in ('-d', '--directory'):
output_directory = value
elif opt in ('-h', '--help'):
usage(script_name)
sys.exit(0)
elif opt in ('-m', '--manpath'):
use_manpath = True
else:
assert False, "unhandled option"
if use_manpath:
# Fetch all man1 files from the manpath
file_paths.extend(get_paths_from_manpath())
if not file_paths:
print "No paths specified"
sys.exit(0)
if not WRITE_TO_STDOUT and not output_directory:
# Default to ~/.config/fish/completions/
# Create it if it doesn't exist
output_directory = os.path.expanduser('~/.config/fish/completions/')
try:
os.makedirs(output_directory)
except OSError, e:
if e.errno != errno.EEXIST:
raise
if True:
parse_and_output_man_pages(args)
parse_and_output_man_pages(file_paths, output_directory)
else:
# Profiling code
import cProfile, pstats
cProfile.run('parse_and_output_man_pages(args)', 'fooprof')
cProfile.run('parse_and_output_man_pages(file_paths, output_directory)', 'fooprof')
p = pstats.Stats('fooprof')
p.sort_stats('cumulative').print_stats(100)