CGI で実現した、ソースコードブラウザです。 手ものにあるソースコードを軽く確認したいような場合にどうぞ。 参照先がわかるキーワードは、参照先へ飛ぶことが可能です。 C を対象にして書かれています。 一部、Ruby も対応しています。
セキュリティについては、CGI が実行されるシステムからのアクセスしか許さないようになっています。 アクセスを許可するホストを登録するようにするか、公開していい特定のディレクトリ以下だけを公開するように修正が必要と思います。 Web サーバの機能に依存してセキュリティ機能をインプリメントすると、設定に依存してしまうことになります。 今回は Web サーバへ依存するセキュリティ設定を利用しない方法にしています。
CGI の設定で URL として http://localhost/cgi-bin/src.rb で呼びだしが可能という前提で進めさせてください。 このために cgi-bin ディレクトリへスクリプトを入れてください。
参照するソースコードが入っているディレクトリで、コマンド etags を実行します。
これで必要な「TAGS」というファイルが作成されます。
関連するソースコードが、複数のディレクトリに分散している場合は、そのディレクトリごとに TAGS を作成します。 このとき、関連するディレクトリのソースコードも参照するようにします。$ etags *.c *.h
$ etags *.c *.h lib/*.[ch] $ cd lib $ etags *.c *.h ../*.[ch] # *.c *.h ../*.h でもいい場合もありますね
呼びだす URL は、次のような形になります。
「ディレクトリ」を指定した場合は、ディレクトリに登録されているファイルの一覧が表示されます。 ファイル名をクリックすると、そのファイルが表示されます。
「ディレクトリ」「:」「キーワード」と、「ディレクトリ」のあとに「:」で区切り、「キーワード」を指定した場合、「キーワード」に関連したファイルの一覧が表示されます。
「ソースファイル」を指定した場合には、そのソースが表示されます。
ソースが表示されると、etags コマンドで有効になっているキーワードは、クリックでリンク先へ飛べるようになっています。
ちょっと便利なので試してみてください。
Web ブラウザでの表示上は Ruby のスクリプトですが、HTML の特殊文字をエスケープしています。 テキストとしてセーブしてご利用ください。
このスクリプトは、漢字コードを EUC Japan として使用してください。 一部漢字コードに依存している記述をしています。
#! /usr/local/bin/ruby -Ke
# /home/tetsu/src/ruby/cgi/src.rb
# Created: March 28,2002 Thursday 14:56:15
# Author: tetsu(WATANABE Tetsuya)
# $Id: src.rb,v 1.8 2002/04/03 09:06:39 tetsu Exp $
# usage:
class Etags
def initialize(dir)
@path = dir + '/TAGS'
@macro = {}
@define = {}
@typedef = {}
@func = {}
@ignore = ['switch', 'if', 'sizeof', 'while']
if File.directory?(dir) and File.exist?(@path)
f = File.open(@path)
parse(f)
f.close
end
end
attr_reader :macro, :define, :typedef, :func
def search(key, path = nil)
re = /#{key}/i
arr = []
[@func, @typedef, @define, @macro].each do |h|
h.keys.each do |k|
if k =~ re
arr.push([k, h[k]])
end
end
end
arr
end
def parse(f)
srcfile = ''
while f.gets
chop
if ~/\f/
srcfile = ''
elsif srcfile == ''
file, no = $_.split(/,/)
srcfile = file
elsif ~/^\s/ && srcfile =~ /\.(?:c|h)$/
elsif ~/^extern\s/i && srcfile =~ /\.(?:c|h)$/
else
case srcfile
when /\.(?:c|h)$/
if ~/^\#\s*define/
if ~/\001/
fn, l = $_.split(/\177/)
fn, l, b = l.split(/[\001,]/)
store(@macro, srcfile, fn, l, b)
else
fn, l, b = $_.sub(/^\#\s*define\s+/, '').split(/[\177\s,]+/)
store(@define, srcfile, fn, l, b)
end
elsif (~/typedef/ || ~/struct/ || ~/\}/) && ~/\001/
fn, l = $_.split(/\177/)
fn, l, b = l.split(/[\001,]/)
store(@typedef, srcfile, fn, l, b)
else
fn, l, b = $_.split(/[\177,]/)
store(@func, srcfile, fn, l, b)
end
when /\.rb$/
fn, l, b = $_.split(/[\177,]/)
fn, l, b = l.split(/[\001,]/)
store(@func, srcfile, fn, l, b)
end
end
end
end
private :parse
def store(h, src, fn, l, b)
fn.sub!(/\W+$/, '')
fn = fn.split(/\W+/)[-1]
return if (@ignore.grep fn).size == 1
if h.key? fn
h[fn].concat(" #{src},#{l},#{b}")
else
h[fn] = "#{src},#{l},#{b}"
end
end
private :store
end
require 'cgi-lib'
class SrcView
def initialize(path)
@src = []
@etag = nil
@path = path
@dir = File.dirname path
if File.exist? path
@etag = Etags.new(@dir)
@src = File.readlines(path) # .collect {|x| x.chop}
end
end
def pr_html
pre_str = ''
script = ENV['SCRIPT_NAME'] || '/cgi-bin/src.rb'
script.concat('?' + @dir + '/')
basename = File.basename @path
lineno = 0
name_flag = nil
@src.each do |l|
name_flag = false
lineno += 1
# escape char
l.tr!('<>&"', "\001\002\003\004") # "
# include
if l =~ /^\#\s*include\s+\004(.+?)\004/
inc_file = $1
l.sub!(inc_file, CGI::tag('a', 'href' => script + inc_file) { inc_file })
else
# 「(」ありの関数かマクロ
l = l.gsub(/(\w+)(\s*\(?)/) do |m| # l.gsub! するとだめみたい?
f_name = $1
last_char = $2
if basename =~ /\.rb$/ and f_name == 'new'
w = @etag.func['initialize']
else
w = @etag.func[f_name] || @etag.macro[f_name] || @etag.define[f_name] || @etag.typedef[f_name]
end
if w
f = l_no = b = nil
arr = w.split
arr.each do |a|
f, l_no, b = a.split(/,/)
break if f == basename
end
name_flag = true if f == basename and l_no.to_i == lineno
if l_no && (arr.size == 1 || f == basename) # 「宣言が一つ」または同じファイル? (これはどうかな?)
CGI::tag('a', 'href' => script + f + '#' + l_no) { f_name } + last_char
else
CGI::tag('a', 'href' => script + f) { f_name } + last_char
end
else
m
end
end
# 「(」なしのマクロ
end
# name tag
pre_str.concat CGI::tag('a', 'name' => lineno.to_s) if name_flag
# escape char を戻す
pre_str.concat l.gsub(/\001/, '<').gsub(/\002/, '>').gsub(/\003/, '&').gsub(/\004/, '"')
end
CGI::print("Content-Type: text/html") {
CGI::tag("html") {
CGI::tag("head") {
CGI::tag("title") { @path } + \
CGI::tag('link', 'href' => '/base.css', 'type' => 'text/css', 'rel' => 'stylesheet')
} +\
CGI::tag("body") { CGI::tag('pre') { pre_str } }
}
}
end
end
class SearchList
def initialize(dir, key)
@etag = nil
@dir = dir
@key = key
if File.directory? dir
@etag = Etags.new(dir)
end
end
def pr_html
script = ENV['SCRIPT_NAME'] || '/cgi-bin/src.rb'
script.concat('?' + @dir + '/')
arr = @etag.search(@key)
tr_str = ''
arr.sort.each do |a|
item = a[0]
a[1].split.sort.each do |i|
src, l, b = i.split(/,/)
tr_str.concat CGI::tag('tr') {
CGI::tag('td') { CGI::tag('a', 'href' => script + src + '#' + (l || '1') ) { item } } + \
CGI::tag('td') { src }
}
end
end
CGI::print("Content-Type: text/html") {
CGI::tag("html") {
CGI::tag("head") {
CGI::tag("title") { "serach: #{@key}" } + \
CGI::tag('link', 'href' => '/base.css', 'type' => 'text/css', 'rel' => 'stylesheet')
} +\
CGI::tag("body") { CGI::tag('table') { tr_str } }
}
}
end
end
class DirList
def initialize(dir)
@dir = dir
@list = []
if File.directory? dir
Dir.foreach(dir) do |f|
fst = File.stat(dir + '/' + f)
if f =~ /\.(c|h|txt|sh|rb|pl)$/ || f =~ /(makefile)/i
t =
case $1.downcase
when 'c', 'h'
'C'
when 'txt', 'makefile'
'T'
when 'sh', 'rb', 'pl'
'S'
else
'U'
end
@list.push([f, fst.size, t])
elsif f[0, 1] == '.'
elsif fst.directory?
@list.push([f, 0, 'D'])
end
end
end
@list.sort!
end
def pr_html
tr_str = ''
script = ENV['SCRIPT_NAME'] || '/cgi-bin/src.rb'
script.concat('?')
@list.each do |arr|
f, size, t = arr
size_str = if size == 0 then '' else size.to_s end
img_icon =
case t
when 'C'
'c'
when 'T'
'text'
when 'S'
'script'
when 'D'
'dir'
else
'unknown'
end
img_str = CGI::tag('img', 'src' => '/icons/' + img_icon + '.gif', 'alt' => img_icon)
tr_str.concat CGI::tag('tr') {
CGI::tag('td') { img_str } + \
CGI::tag('td', 'align' => 'right') { size_str } + \
CGI::tag('td') { CGI::tag('a', 'href' => script + @dir + '/' + f ) { f } }
}
end
CGI::print("Content-Type: text/html") {
CGI::tag("html") {
CGI::tag("head") {
CGI::tag("title") { @dir } + \
CGI::tag('link', 'href' => '/base.css', 'type' => 'text/css', 'rel' => 'stylesheet')
} +\
CGI::tag("body") { CGI::tag('table') { tr_str } }
}
}
end
end
def pr_welcome_page
script = ENV['SCRIPT_NAME'] || '/cgi-bin/src.rb'
script.concat('?')
body_str = ''
body_str.concat CGI::tag('h1') { '呼びだす場合の URL のサンプル' }
body_str.concat CGI::tag('ul') {
CGI::tag('li') { 'http://localhost' + script + 'ディレクトリの絶対パス' } + \
CGI::tag('li') { 'http://localhost' + script + 'ディレクトリの絶対パス:キーワード' } + \
CGI::tag('li') { 'http://localhost' + script + 'ソースファイルの絶対パス' }
}
CGI::print("Content-Type: text/html") {
CGI::tag("html") {
CGI::tag("head") {
CGI::tag("title") { 'CGI: source view' } + \
CGI::tag('link', 'href' => '/base.css', 'type' => 'text/css', 'rel' => 'stylesheet')
} +\
CGI::tag("body") { body_str }
}
}
end
require 'socket'
cgi = CGI.new
if ENV['REMOTE_ADDR'] == '127.0.0.1' || Socket.gethostname == Socket.gethostbyname(ENV['REMOTE_ADDR'])[0]
else
w = File.open('/tmp/cgi-src.txt', 'a')
w.puts Time.now.strftime('%x %X') +
' ' + ENV['SCRIPT_NAME'] +
' ' + ENV['REMOTE_ADDR'] +
' ' + ENV['HTTP_USER_AGENT'] +
' ' + cgi.keys.join(' ')
w.close
CGI::print("Content-Type: text/html") {
CGI::tag("html") {
CGI::tag("head") {
CGI::tag("title") { 'no' } + \
CGI::tag('link', 'href' => '/base.css', 'type' => 'text/css', 'rel' => 'stylesheet')
} +\
CGI::tag("body") { CGI::tag('p') { 'アクセスできまへん' } }
}
}
exit
end
case cgi.size
when 1
k = cgi.keys[0]
if k =~ /:/
dir, key = k.split(/:/)
sl = SearchList.new(dir, key)
sl.pr_html
elsif File.exist? k
fst = File.stat(k)
if fst.directory?
dirlist = DirList.new(k)
dirlist.pr_html
elsif fst.file?
src = SrcView.new(k)
src.pr_html
end
end
when 0
pr_welcome_page
end
とりかえず、必要だった C のソースコードを追いかけるのと、Ruby のスクリプトをちょっとだけ確認できるようにしています。 まだ、リファレンス先の特定方法など調整が必要と思います。
キーワードサーチは、そこそこ便利です。 関数名などで、あるキーワードに一致するものを一覧表示させて確認できたりします。
これでキーワード「rb_」とか「ruby_」を含むものを一覧表示します。 便利便利。$ w3m 'http://localhost/cgi-bin/src.rb?/usr/local/src/ruby-1.7.2:rb_' $ w3m 'http://localhost/cgi-bin/src.rb?/usr/local/src/ruby-1.7.2:ruby_'
私は Mozilla を使っているのですが、Mozilla は、name タグ(タグは a かな)がたくさんあると、うまく動かないようです。 w3m がきびきび動くので、ソースコードを参照するなら w3m がいいと思います。
$ w3m 'http://localhost/cgi-bin/src.rb?/usr/local/src/ruby-1.7.2'
いまだに cgi-lib.rb を使っていたりします。 手軽に HTML を生成するためだったりします。 いいのかな?
扱うソースコードを C だけに限定したほうが記述がすっきりするのですが、私がよく扱う C と Ruby にしました。 Ruby については、ブラウザとして使いやすいかちょっと疑問がありますが、とりあえず。
TAGS ファイル中の相対パスのファイルを含めていなかったので修正。
アクセス制御で exit していませんでした。
チェックインミス。 欠番。 コメントだけ書いたけど...
最初の公開 version です。