feat(bin/ltags): add ltags

Usage:

    ltags <ilean_files> # generate TAGS file for <ilean_files>

    ltags  # Find makefile upwards, generate TAGS file for all ilean files

close #50
This commit is contained in:
Soonho Kong 2014-08-15 20:49:24 -07:00
parent 991ce469e9
commit 6911cb0af0

185
bin/ltags Executable file
View file

@ -0,0 +1,185 @@
#!/usr/bin/env python2
#
# Copyright (c) 2014 Microsoft Corporation. All rights reserved.
# Released under Apache 2.0 license as described in the file LICENSE.
#
# Author: Soonho Kong
import argparse
import fnmatch
import os
import sys
import codecs
def get_ilean_files(directory):
"""Return ilean files under directory (recursively)"""
ilean_files = []
if not os.path.isdir(directory):
raise IOError("%s is not a directory" % directory)
for root, dirnames, filenames in os.walk(directory):
for filename in fnmatch.filter(filenames, '*.ilean'):
ilean_files.append(os.path.join(root, filename))
return ilean_files
def parse_arg(argv):
""" Parse arguments """
parser = argparse.ArgumentParser(description='Process arguments.')
parser.add_argument('--etags', '-e', action='store_true', default=True, help="Generate etags TAGS file.")
parser.add_argument('--gtags', '-g', action='store_true', default=False, help="Generate gtags files (GTAGS, GRTAGS, GPATH).")
parser.add_argument('files', nargs='*')
args = parser.parse_args(argv)
return args
def get_short_name(id):
last_pos_of_dot = id.rfind('.')
if last_pos_of_dot >= 0:
return id[last_pos_of_dot+1:]
return id
def build_line_to_byteoffset_map(ilean_filename):
"""Given a .ilean filename, return a mapping from a line number to the
byte offset of the beginning of the line. Note that line number
starts with 0 in our convention.
"""
map = [0, 0]
lines = [""]
pos = 0
with codecs.open(ilean_filename, "r", "utf-8") as f:
while True:
line = f.readline()
if not line:
break
pos = pos + len(line)
map.append(pos + 1)
lines.append(line)
return (map, lines)
def convert_position_to_etag_style(info):
"""Convert the position format from (line, col) to (line, byteoffset)"""
filename = info[0]['filename']
(line_to_byteoffset, contents) = build_line_to_byteoffset_map(filename)
for item in info:
linenum = item['linenum']
col = item['col']
item['offset'] = line_to_byteoffset[linenum] + col
item['prefix'] = contents[linenum][:col] + get_short_name(item['id'])
# item['prefix'] = ".*" + get_short_name(item['id'])
def extract_info_from_ilean(ilean_file):
info = []
with open(ilean_file) as f:
for line in f:
array = line[:-1].split("|")
item = {}
item['type'] = array[0]
item['filename'] = array[1]
item['linenum'] = int(array[2])
item['col'] = int(array[3])
item['id'] = array[4]
info.append(item)
return info
def get_etag_def_header(filename, len):
result = "\x0c\n%s,%d\n" % (filename, len)
return result
def get_etag_def_item(item):
result = "%s\x7f%s\x01%d,%d\n" \
% (item['prefix'], get_short_name(item['id']), item['linenum'], item['offset'])
return result
def get_etag_def_items(items):
result = ""
for item in items:
if item['type'] == 'd':
result += get_etag_def_item(item)
return result
def get_etag_def(info):
body_str = get_etag_def_items(info)
header = get_etag_def_header(info[0]['filename'], len(body_str))
return header + body_str
def print_item(item):
print "\t%s\t%-60s:%4d:%4d:%6d - %s" \
% (item['type'], item['filename'], item['linenum'], item['col'], item['offset'], item['id'])
def print_items(items):
for item in items:
print get_etag_def_entry(item),
def find_makefile(path, makefile_names):
""" Find makefile in a given directory.
Args:
path: a string of path to look up
makefile_names: a list of strings to search
Return:
When found, return the full path of a makefile
Otherwise, return False.
"""
for makefile in makefile_names:
makefile_pathname = os.path.join(path, makefile)
if os.path.isfile(makefile_pathname):
return makefile_pathname
return False
def find_makefile_upward(path, makefile_names):
""" Strating from a given directory, search upward to find
a makefile
Args:
path: a string of path to start the search
Return:
When found, return the full path of a makefile
Otherwise, return False.
"""
makefile = find_makefile(path, makefile_names)
if makefile:
return makefile
up = os.path.dirname(path)
if up != path:
return find_makefile_upward(up, makefile_names)
return False
def filter_ilean_files(ilean_files):
"""Remove .ilean file if a corresponding .lean file does not exist."""
result = []
for ilean_file in ilean_files:
lean_file = ilean_file[:-5] + "lean"
if os.path.isfile(lean_file) and os.path.isfile(ilean_file):
result.append(ilean_file)
return result
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
args = parse_arg(argv)
directory = os.getcwd()
if args.files:
ilean_files = fnmatch.filter(args.files, '*.ilean')
else:
makefile_names = ["GNUmakefile", "makefile", "Makefile"]
makefile = find_makefile_upward(directory, makefile_names)
if makefile:
directory = os.path.dirname(makefile)
ilean_files = get_ilean_files(directory)
ilean_files = filter_ilean_files(ilean_files)
if not ilean_files:
return 0
with codecs.open(os.path.join(directory, "TAGS"), 'w', 'utf-8') as tag_file:
for ilean_file in ilean_files:
info = extract_info_from_ilean(ilean_file)
if info:
convert_position_to_etag_style(info)
tag_file.write(get_etag_def(info))
if __name__ == "__main__":
sys.exit(main())