mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-03-04 07:44:26 +08:00
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:
parent
cccd1cefbe
commit
2ec0778d78
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user