commit
b03e9de8fa
|
@ -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;
|
||||
}
|
||||
|
14
src/main.d
14
src/main.d
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue