Instantly share code, notes, and snippets.
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save alexanderadam/438008237fdd82f628b4e6486e5349af to your computer and use it in GitHub Desktop.
Generates ctags for a Crystal source - Crystal 0.17.4 [9d258f6] (2016-06-05)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Generates ctags for a Crystal source | |
# Based on asterite script https://github.com/crystal-lang/crystal/issues/577#issuecomment-97054600 | |
# This works on Crystal 0.17.4 [9d258f6] (2016-06-05) | |
# | |
require "compiler/crystal/**" | |
require "option_parser" | |
include Crystal | |
class CTagsVisitor < Visitor | |
record Tag, name : String, filename : String, regex : String, kind : String | |
getter tags | |
def initialize(@base_dir : String) | |
@files_to_lines = {} of String => Array(String) | |
@tags = [] of Tag | |
end | |
def process(filename) | |
contents = File.read(filename) | |
@files_to_lines[filename] = contents.lines | |
parser = Parser.new contents | |
parser.filename = filename | |
parser.parse.accept self | |
end | |
def visit(node : ClassDef) | |
process_node node, node.name.names.last, "c" | |
true | |
end | |
def visit(node : ModuleDef) | |
process_node node, node.name.names.last, "m" | |
true | |
end | |
def visit(node : Def) | |
process_node node, node.name, "d" | |
false | |
end | |
def visit(node : Macro) | |
process_node node, node.name, "x" | |
true | |
end | |
def visit(node : LibDef) | |
process_node node, node.name, "l" | |
true | |
end | |
def visit(node : StructOrUnionDef) | |
process_node node, node.name, "s" | |
true | |
end | |
def visit(node : FunDef) | |
process_node node, node.name, "l" | |
true | |
end | |
def visit(node : Expressions) | |
true | |
end | |
def visit(node : ASTNode) | |
true | |
end | |
def process_node(node, name, kind) | |
location = node.location | |
return true unless location | |
filename = location.filename | |
return true unless filename.is_a?(String) | |
line = @files_to_lines[filename][location.line_number - 1] | |
regex = escape_regex(line) | |
@tags << Tag.new(name, relativize(filename), regex, kind) | |
true | |
end | |
def relativize(filename) | |
if filename.starts_with?(@base_dir) | |
filename = filename[@base_dir.size .. -1] | |
filename = filename[1 .. -1] if filename.starts_with? '/' | |
filename | |
else | |
filename | |
end | |
end | |
REPLACEMENTS = { | |
"(": "\\(", | |
")": "\\)", | |
"[": "\\[", | |
"]": "\\]", | |
"{": "\\{", | |
"}": "\\}", | |
} | |
def escape_regex(string) | |
string.gsub(/\(\)\[\]\{\}/, REPLACEMENTS) | |
end | |
end | |
directory = "" | |
output = "" | |
def fetch_directory(args) | |
directory = args.empty? ? "." : args.shift | |
unless Dir.exists?(directory) || File.exists?(directory) | |
puts "file or directory #{directory} does not exists" | |
exit 1 | |
end | |
if Dir.exists?(directory) | |
directory = directory.try &.+ "/**/*.cr" | |
end | |
directory | |
end | |
OptionParser.parse! do |opts| | |
opts.banner = <<-USAGE | |
Usage: crystaltags -o [FILE] [DIR] | |
DIR - directory will be read to generate ctags, | |
default: NAME, eg: ./custom/path/example | |
USAGE | |
opts.on("-o OUTPUT", "--output=OUTPUT", "output to (- STDIN)") do |opt| | |
output = opt | |
end | |
opts.on("--help", "show this help") do | |
puts opts | |
exit | |
end | |
opts.unknown_args do |args, after_dash| | |
directory = fetch_directory(args) | |
end | |
end | |
output = output.empty? ? "tags" : output | |
visitor = CTagsVisitor.new(Dir.current) | |
Dir.glob(directory) do |file| | |
visitor.process File.expand_path(file) | |
end | |
tags = visitor.tags | |
tags.sort_by! &.name | |
tags_output = String.build do |io| | |
io.puts %(!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/) | |
io.puts %(!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/) | |
tags.each do |tag| | |
io << tag.name << "\t" << tag.filename << "\t/^" << tag.regex.chomp << "$/;\"\t" << tag.kind | |
io.puts | |
end | |
end | |
if output == "-" | |
puts tags_output | |
else | |
File.open(output, "w") do |file| | |
file.puts tags_output | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment