Generate etags

Generate Emacs style etags with -e.  Uses similar pattern as ctags.
This verison scopes all tags with module name and does not generate tags
for function or unittest scoped declarations.  May want to make some of
this configurable and add other stuff like tags for aliases.
This commit is contained in:
Dan Olson 2015-04-28 00:25:16 -07:00
parent 7b115caf3f
commit 7eedcef77b
2 changed files with 248 additions and 1 deletions

235
src/etags.d Normal file
View File

@ -0,0 +1,235 @@
// Emacs tags based on ctags.d with this header:
// Copyright Brian Schott (Hackerpilot) 2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
module etags;
import std.d.parser;
import std.d.lexer;
import std.d.ast;
import std.algorithm;
import std.range;
import std.stdio;
import std.path;
import std.array;
import std.conv;
/**
* Prints ETAGS information to the given file.
* Params:
* outpt = the file that ETAGS info is written to
* fileNames = tags will be generated from these files
*/
void printEtags(File output, string[] fileNames)
{
LexerConfig config;
StringCache cache = StringCache(StringCache.defaultBucketCount);
foreach (fileName; fileNames)
{
File f = File(fileName);
if (f.size == 0) continue;
auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size));
f.rawRead(bytes);
auto tokens = getTokensForParser(bytes, config, &cache);
Module m = parseModule(tokens.array, fileName, null, &doNothing);
auto printer = new EtagsPrinter;
// I think I like this
enum useModuleContext = true;
if (useModuleContext)
printer.context = m.moduleFullName(fileName) ~ ".";
printer.bytes = bytes.sansBOM;
printer.visit(m);
output.writef("\f\n%s,%u\n", fileName, printer.tags.length);
printer.tags.copy(output.lockingTextWriter);
}
}
private:
void doNothing(string, size_t, size_t, string, bool) {}
ubyte[] sansBOM(ubyte[] bytes)
{
// At least handle UTF-8 since there is some in druntime/phobos
return (bytes.length >= 3 &&
bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf)
? bytes[3 .. $] : bytes;
}
string moduleFullName(Module m, string fileName)
{
// When no module declaration, just use filename and hope its valid
if (!m.moduleDeclaration)
return fileName.baseName.stripExtension;
// reconstruct module full name
return m.moduleDeclaration.moduleName.identifiers.map!(i=>i.text).join(".");
}
class EtagsPrinter : ASTVisitor
{
override void visit(const ClassDeclaration dec)
{
maketag(dec.name);
acceptInContext(dec, dec.name.text);
}
override void visit(const StructDeclaration dec)
{
maketag(dec.name);
acceptInContext(dec, dec.name.text);
}
override void visit(const InterfaceDeclaration dec)
{
maketag(dec.name);
acceptInContext(dec, dec.name.text);
}
override void visit(const TemplateDeclaration dec)
{
maketag(dec.name);
acceptInContext(dec, dec.name.text);
}
override void visit(const FunctionDeclaration dec)
{
maketag(dec.name);
bool was = inFunc;
inFunc = true;
auto c = context;
acceptInContext(dec, dec.name.text);
inFunc = was;
}
override void visit(const Constructor dec)
{
maketag("this", dec.location, dec.line);
bool was = inFunc;
inFunc = true;
auto c = context;
acceptInContext(dec, "this");
inFunc = was;
}
override void visit(const Destructor dec)
{
maketag("~this", dec.index, dec.line);
bool was = inFunc;
inFunc = true;
auto c = context;
acceptInContext(dec, "~this");
inFunc = was;
}
override void visit(const EnumDeclaration dec)
{
maketag(dec.name);
acceptInContext(dec, dec.name.text);
}
override void visit(const UnionDeclaration dec)
{
if (dec.name == tok!"")
{
dec.accept(this);
return;
}
maketag(dec.name);
acceptInContext(dec, dec.name.text);
}
override void visit(const AnonymousEnumMember mem)
{
maketag(mem.name);
}
override void visit(const EnumMember mem)
{
maketag(mem.name);
}
override void visit(const Unittest dec)
{
bool was = inUnittest;
inUnittest = true;
dec.accept(this);
inUnittest = was;
}
override void visit(const VariableDeclaration dec)
{
foreach (d; dec.declarators)
{
maketag(d.name);
}
dec.accept(this);
}
override void visit(const AutoDeclaration dec)
{
foreach (i; dec.identifiers)
{
maketag(i);
}
dec.accept(this);
}
override void visit(const Invariant dec)
{
maketag("invariant", dec.index, dec.line);
}
void acceptInContext(Dec)(Dec dec, string name)
{
// nest context before journeying on
auto c = context;
context ~= name ~ ".";
dec.accept(this);
context = c;
}
void maketag(Token name)
{
maketag(name.text, name.index, name.line);
}
void maketag(string text, ulong index, ulong line)
{
// skip declaration in unittests and funcs
if (inUnittest || inFunc) return;
// tag is a searchable string from beginning of line
ulong b = index;
while (b > 0 && bytes[--b] != '\n') {}
++b;
// tag end is one char beyond tag name
ulong e = index + text.length;
if (e < bytes.length && bytes[e] != '\n') ++e;
auto tag = cast(char[])bytes[b..e];
auto tagname = context.empty ? text : context~text;
// drum roll... the etags tag format
tags ~= format("%s\x7f%s\x01%u,%u\n",
tag,
tagname,
line,
b);
}
alias visit = ASTVisitor.visit;
bool inUnittest;
bool inFunc;
ubyte[] bytes;
string tags;
string context;
}

View File

@ -20,6 +20,7 @@ import std.d.parser;
import highlighter;
import stats;
import ctags;
import etags;
import astprinter;
import imports;
import outliner;
@ -47,6 +48,7 @@ int run(string[] args)
bool sloc;
bool highlight;
bool ctags;
bool etags;
bool help;
bool tokenCount;
bool syntaxCheck;
@ -67,6 +69,7 @@ int run(string[] args)
{
getopt(args, std.getopt.config.caseSensitive, "sloc|l", &sloc,
"highlight", &highlight, "ctags|c", &ctags, "help|h", &help,
"etags|e", &etags,
"tokenCount|t", &tokenCount, "syntaxCheck|s", &syntaxCheck,
"ast|xml", &ast, "imports|i", &imports, "outline|o", &outline,
"tokenDump", &tokenDump, "styleCheck|S", &styleCheck,
@ -117,7 +120,7 @@ int run(string[] args)
return 0;
}
auto optionCount = count!"a"([sloc, highlight, ctags, tokenCount,
auto optionCount = count!"a"([sloc, highlight, ctags, etags, tokenCount,
syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig,
report, symbolName !is null]);
if (optionCount > 1)
@ -174,6 +177,10 @@ int run(string[] args)
{
stdout.printCtags(expandArgs(args));
}
else if (etags)
{
stdout.printEtags(expandArgs(args));
}
else if (styleCheck)
{
StaticAnalysisConfig config = defaultStaticAnalysisConfig();
@ -357,6 +364,11 @@ options:
ctags information requires a filename, so stdin cannot be used in place
of a filename.
--etags | -e sourceFile
Generates etags information from the given source code file. Note that
etags information requires a filename, so stdin cannot be used in place
of a filename.
--ast | --xml sourceFile
Generates an XML representation of the source files abstract syntax
tree. If no files are specified, input is read from stdin.