#!/usr/bin/env ruby # 秀丸エディタ用の tags ファイルを作成します。 # # カレントディレクトリ以下のファイルを元に tags ファイルを作成します。 # 作成される tags ファイルの内容は、prepare メソッドの内容を変更することで自由にカスタマイズできます。 # デフォルトでは PHP, Ruby, Python, Perl, JavaScript, C, C++, Java, C#, 秀丸マクロ に対応しています。 # # ■使い方 # 3つの動作モードがあります。 # --local モードを指定すると、カレントディレクトリ以下のファイルを元に、tags ファイルを作成します。 # --server を指定して実行された場合には サーバーとして動き続けます。 # --client を指定して実行することで、あらかじめ起動しておいたサーバーに tags ファイルを作成させることが出来ます。 # # ■想定される使用状況 # この --server と --client は、ワークスペースがリモートにある場合の利用を想定しています。 # 秀丸エディタ標準の tags ファイル作成機能では、tags ファイルを作成するために個々のファイルをダウンロード # する必要があり、ファイル数が増えるとその時間が無視できなくなります。 # このスクリプトをワークスペースがあるマシンの上でサーバーモードで起動しておき、--client オプションを指定して # 呼び出すことで、ファイルへのアクセスに伴う通信のオーバーヘッドがなくなり、高速に tags ファイルを作成できるようになります。 # # ■仕様 # サーバーモードで起動されたとき、このスクリプトファイルの末尾に # ・サーバーの IP アドレスとポート番号 # ・サーバーとの接続認証に使うパスワード # が書き込まれます。 # --client を指定して実行された場合には、その情報を元にサーバーに接続します。 # # Yasunori Miyamoto # http://tipszone.jp/20121103_create_tags_file/ # mailto: nori@tipszone.jp Version = '4.8' Release = '2017-02-14' require 'benchmark' require 'find' require 'gserver' require 'json' require 'nkf' require 'optparse' DIR_SEP = '\\' opts = OptionParser.new(nil, 20) opts.program_name = File.basename(__FILE__) opts.version = Version opts.release = Release opts.banner = "Usage: #{opts.program_name} (-a|-l|-s|-c) [-f] [-p]" opts.on('-a', '--auto', 'Use --client if available otherwise --local. (Default)') opts.on('-l', '--local', 'Make tags file in current directory.') opts.on('-s', '--server [port]', 'Start make tags file server.') opts.on('-c', '--client', 'Call the server.') opts.separator '' opts.on('-f', '--full-path', 'Output full path. (Available at --local mode.)') opts.on('-p', '--pause', 'Pause at exit') class TagsFile attr_accessor :sock # Param:: boolean tags ファイルの内容をフルパスで出力するかどうか def initialize(full_path = false) @full_path = full_path @sock = nil end # カレントフォルダ以下のファイルから指定されたパターンにマッチする行を検索し、 # tags ファイルの各行の形式に変換し、それらの配列を返す。 # Param:: Regexp ファイル名がこれにマッチしたファイルを検索対象とする(拡張子の指定などに利用する) # Param:: Regexp ファイルの各行からこれにマッチする行を検索する # Param:: string | array dirs ファイルやディレクトリのパスまたはその配列。 # ディレクトリを指定した場合はその中のディレクトリも再帰的に探索されます。 # Param:: Integer cons line_pattern とのマッチングをまとめて行う行数 # Param:: Regexp prune これにマッチしたフォルダは探索を省略する。 # Param:: nil|Integer max_line_size 抽出する行の最大サイズ # Return:: Array of String 見つかった各行を tags ファイルの形式に変換した文字列の配列 def grep_and_format(file_pattern, line_pattern, dirs: './', cons: 1, prune: %r{/\.}, max_line_size: nil) tags = [] file_count = 0 pwd = Dir.pwd.gsub('/'){DIR_SEP}.encode(Encoding::UTF_8) + DIR_SEP Find.find(*dirs) do |file| if FileTest.directory?(file) file.encode! Encoding::UTF_8 Find.prune if prune and file =~ prune next end next unless file =~ file_pattern path = file.sub(%r{^./}, '').gsub('/'){DIR_SEP}.encode! Encoding::UTF_8 full_path = pwd + path path = full_path if @full_path contents = File.read(file) contents.encode! Encoding::UTF_8, NKF.guess(contents) rescue next lineno = 0 (contents << "\n" * (cons - 1)).each_line.each_cons(cons) do |lines| lineno += 1 next if max_line_size and max_line_size < lines.first.size next unless lines.join =~ line_pattern if identifier = $~[1..-1].compact.first tags << "#{identifier}\t#{path}\t#{lineno}: #{lines[0].strip}" else tags << "#{path}(#{lineno}) : #{lines[0].strip}" end end file_count += 1 if file_count % 10 == 1 print '.' @sock.send '.', 0 if @sock end end return tags end # tags ファイルを作成する。 def prepare identifier_char = '[^\x00-\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\x7f]' open('tags', 'w') do |io| io.extend OUTPUT_SJIS # PHP用の設定 tags = grep_and_format(%r{\.php$}, /\b(?(?![=~])/) io.puts tags unless tags.empty? # JavaScript 用の設定 tags = grep_and_format(%r{(?