cloc 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/cloc +92 -0
- data/lib/cloc.rb +14 -0
- data/lib/cloc/database.rb +63 -0
- data/lib/cloc/filesource.rb +115 -0
- data/lib/cloc/linker.rb +72 -0
- metadata +59 -0
data/bin/cloc
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
# If we were installed as a gem we can just:
|
5
|
+
require 'cloc'
|
6
|
+
rescue LoadError
|
7
|
+
# No, we weren't installed as a gem. Let's try to make Ruby aware
|
8
|
+
# of our library folder.
|
9
|
+
lib = File.join(File.dirname(__FILE__), '..', 'lib')
|
10
|
+
$: << lib
|
11
|
+
require 'cloc'
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'optparse'
|
15
|
+
|
16
|
+
SETTINGS = { :version => Cloc::Database.default_version }
|
17
|
+
|
18
|
+
def process_files
|
19
|
+
db = Cloc::Database.new SETTINGS[:version]
|
20
|
+
linker = Cloc::Linker.new
|
21
|
+
ARGV.each do |file|
|
22
|
+
if File.directory? file
|
23
|
+
Dir.glob(File.join(file, '**', '*.[cy]')).each do |file|
|
24
|
+
linker.process_file file
|
25
|
+
end
|
26
|
+
else
|
27
|
+
linker.process_file file
|
28
|
+
end
|
29
|
+
end
|
30
|
+
linker.resolve_non_static_functions
|
31
|
+
linker.remove_missing
|
32
|
+
db.merge linker.data
|
33
|
+
db.save
|
34
|
+
end
|
35
|
+
|
36
|
+
OptionParser.new do |opts|
|
37
|
+
opts.banner = 'Usage: cloc [options] files-or-folders ...'
|
38
|
+
opts.separator ''
|
39
|
+
opts.separator <<EOS
|
40
|
+
DESCRIPTION
|
41
|
+
|
42
|
+
'cloc' is a program that locates Ruby methods in C source files and
|
43
|
+
records their location (filepath + line number) in ~/.cloc-VERSION
|
44
|
+
|
45
|
+
VERSIONS
|
46
|
+
|
47
|
+
Since you may have several different versions of Ruby's source code on
|
48
|
+
your system, cloc's data file has a "version" embedded in it. E.g.,
|
49
|
+
~/.cloc-1.8.7 and ~/.cloc-1.9.1. It's up to you to decide on these version
|
50
|
+
strings (they have absolutely no meaning for cloc). You may specify the
|
51
|
+
version to work with with the '-v' switch.
|
52
|
+
|
53
|
+
Common options:
|
54
|
+
EOS
|
55
|
+
opts.on('-v', '--version VERSION', "Select the version (default: #{SETTINGS[:version]}).") do |v|
|
56
|
+
SETTINGS[:version] = v
|
57
|
+
end
|
58
|
+
opts.on('-l', '--list', 'List existing versions and exit.') do
|
59
|
+
p Cloc::Database.versions
|
60
|
+
exit
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.separator ''
|
64
|
+
opts.separator 'Debugging options:'
|
65
|
+
|
66
|
+
opts.on('-p', '--pp', 'Print out the database contents and exit.') do
|
67
|
+
require 'pp'
|
68
|
+
pp Cloc::Database.new(SETTINGS[:version]).data
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
opts.on('-w', '--warnings', "Show warnings (these are usually harmless).") do
|
72
|
+
Cloc.warn = true
|
73
|
+
end
|
74
|
+
opts.on('-b', 'Print out the process memory consumption (linux only).', 'and do some benchmarking.') do
|
75
|
+
system("ps -p #{$$} -o vsz,rss,cmd")
|
76
|
+
db = Cloc::Database.new(SETTINGS[:version])
|
77
|
+
system("ps -p #{$$} -o vsz,rss,cmd")
|
78
|
+
require 'benchmark'
|
79
|
+
Benchmark.bm do |x|
|
80
|
+
x.report { 10.times { db.load } }
|
81
|
+
end
|
82
|
+
exit
|
83
|
+
end
|
84
|
+
end.parse!
|
85
|
+
|
86
|
+
if ARGV.empty?
|
87
|
+
puts "No file(s) or folder(s) specified. Nothing to do. Exiting"
|
88
|
+
puts "Invoke with `--help' for help."
|
89
|
+
exit
|
90
|
+
else
|
91
|
+
process_files
|
92
|
+
end
|
data/lib/cloc.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
module Cloc
|
3
|
+
|
4
|
+
class << self
|
5
|
+
# Whether to warn of various things (parsing/linking problems).
|
6
|
+
attr_accessor :warn
|
7
|
+
alias warn? warn
|
8
|
+
end
|
9
|
+
|
10
|
+
autoload :Database, 'cloc/database'
|
11
|
+
autoload :Linker, 'cloc/linker'
|
12
|
+
autoload :FileSource, 'cloc/filesource'
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
module Cloc
|
3
|
+
|
4
|
+
HOME = ENV['HOME'] ? File.expand_path('~') : Dir.pwd
|
5
|
+
|
6
|
+
class Database
|
7
|
+
|
8
|
+
# Find all existing versions.
|
9
|
+
def self.versions
|
10
|
+
@versions ||= Dir.entries(HOME).grep(/^.cloc-(.*)/) { $1 }.sort
|
11
|
+
end
|
12
|
+
|
13
|
+
# Pick a default version.
|
14
|
+
#
|
15
|
+
# We give preference to RUBY_VERSION.
|
16
|
+
def self.default_version
|
17
|
+
versions.include?(RUBY_VERSION) ? RUBY_VERSION : (versions.first || RUBY_VERSION)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(version)
|
21
|
+
@version = version
|
22
|
+
@table = {}
|
23
|
+
load if File.exist? path
|
24
|
+
end
|
25
|
+
|
26
|
+
def version; @version; end
|
27
|
+
|
28
|
+
def merge(hash)
|
29
|
+
@table.merge!(hash) do |key, oldval, newval|
|
30
|
+
oldval.merge(newval)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def data
|
35
|
+
@table
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the source-code location where a method is defined.
|
39
|
+
#
|
40
|
+
# Returned value is either [ path_to_file, line_number ], or nil if
|
41
|
+
# the method isn't known.
|
42
|
+
def lookup(object_name, method_name)
|
43
|
+
if @table[object_name] and (loc = @table[object_name][method_name])
|
44
|
+
# Since the pathname is stored as a symbol, we convert
|
45
|
+
# it back to a string.
|
46
|
+
return [loc[0].to_s, loc[1]]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def path
|
51
|
+
File.join(HOME, ".cloc-#{@version}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def save
|
55
|
+
File.open(path, 'w') { |f| Marshal.dump(@table, f) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def load
|
59
|
+
@table = File.open(path) { |f| Marshal.load(f) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
|
2
|
+
module Cloc
|
3
|
+
|
4
|
+
class NmNtFnd < Exception
|
5
|
+
end
|
6
|
+
|
7
|
+
class FileSource
|
8
|
+
|
9
|
+
def initialize(pathname)
|
10
|
+
@pathname = pathname
|
11
|
+
@c2r_cache = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def raw_source
|
15
|
+
@raw_source ||= IO.read(@pathname).gsub!(/\r?\n|\r/, "\n").freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the source after processing it a bit to make it easier
|
19
|
+
# for other parsing functions to not care about edge cases.
|
20
|
+
def source
|
21
|
+
return @source if defined? @source
|
22
|
+
|
23
|
+
@source = raw_source.dup
|
24
|
+
@source.gsub!('rb_define_global_function(', 'rb_define_module_function(rb_mKernel,')
|
25
|
+
@source.gsub!('define_filetest_function(', 'rb_define_singleton_method(rb_cFile,')
|
26
|
+
|
27
|
+
# Add any special cases here. As an example, here are some
|
28
|
+
# "macro expansions" required to process DataObjects extensions.
|
29
|
+
@source.gsub!('CONST_GET(rb_mKernel,', 'rb_path2class(')
|
30
|
+
@source.gsub!('SQLITE3_CLASS(', 'rb_define_class_under(mSqlite3,');
|
31
|
+
@source.gsub!('DRIVER_CLASS(', 'rb_define_class_under(mDOMysql,');
|
32
|
+
|
33
|
+
@source.freeze
|
34
|
+
end
|
35
|
+
|
36
|
+
# Converts a character offset to a line number.
|
37
|
+
def offs_to_line(offs)
|
38
|
+
# The following isn't as inefficient as it seems: slice() returns
|
39
|
+
# a string pointing inside the original string's allocated space.
|
40
|
+
return source.slice(0, offs).count("\n") + 1
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a list of all the C functions that are callable from Ruby.
|
44
|
+
def cfunctions
|
45
|
+
@cfunction ||= begin
|
46
|
+
list = {}
|
47
|
+
source.scan( /\n (?:static\s+)? VALUE\s+ (\w+) \( /x ) do |func, |
|
48
|
+
list[func] = offs_to_line($~.begin(1))
|
49
|
+
end
|
50
|
+
list
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the fully-qualified Ruby class/module name behind a C variable.
|
55
|
+
#
|
56
|
+
# Examples:
|
57
|
+
# "rb_mKernel" -> "Kernel"
|
58
|
+
# "cResolver" -> "YAML::Syck::Resolver"
|
59
|
+
def c2r(cvar)
|
60
|
+
return @c2r_cache[cvar] if @c2r_cache[cvar]
|
61
|
+
@c2r_cache[cvar] = _c2r(cvar)
|
62
|
+
end
|
63
|
+
|
64
|
+
def _c2r(cvar)
|
65
|
+
if source =~ /#{cvar} \s* = \s* rb_define_(?:class|module) \( \s* "([^"]+)" /x
|
66
|
+
return $1
|
67
|
+
end
|
68
|
+
if source =~ /#{cvar} \s* = \s* rb_define_(?:class|module)_under \( \s* (\w+) \s* , \s* "([^"]+)" /x
|
69
|
+
return c2r($1) + '::' + $2
|
70
|
+
end
|
71
|
+
if source =~ /#{cvar} \s* = \s* rb_path2class \( \s* "([^"]+)" /x
|
72
|
+
return $1
|
73
|
+
end
|
74
|
+
# As a last resort we use C naming conventions:
|
75
|
+
if cvar =~ /rb_[cm](.*)/
|
76
|
+
return $1
|
77
|
+
end
|
78
|
+
raise NmNtFnd
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns a list of all the method definitions.
|
82
|
+
def refs
|
83
|
+
errors = {}
|
84
|
+
list = []
|
85
|
+
source.scan( / (rb_define_method|rb_define_private_method|rb_define_singleton_method|rb_define_module_function)
|
86
|
+
\(
|
87
|
+
\s* (\w+) \s* ,
|
88
|
+
\s* "([^"]+)" \s* , # "
|
89
|
+
\s* (\w+) /x ) do |definer, cvar, rfunc, cfunc |
|
90
|
+
begin
|
91
|
+
klass = c2r(cvar)
|
92
|
+
is_singleton = (definer == 'rb_define_singleton_method' or definer == 'rb_define_module_function')
|
93
|
+
is_instance = (definer != 'rb_define_singleton_method')
|
94
|
+
if is_singleton
|
95
|
+
list << [klass + '--singleton', rfunc, cfunc]
|
96
|
+
end
|
97
|
+
if is_instance
|
98
|
+
list << [klass, rfunc, cfunc]
|
99
|
+
end
|
100
|
+
rescue NmNtFnd
|
101
|
+
errors[cvar] = true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if Cloc.warn?
|
106
|
+
errors.keys.each do |cvar|
|
107
|
+
puts "-- Couldn't find variable '#{cvar}'"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
list
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
data/lib/cloc/linker.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
module Cloc
|
3
|
+
|
4
|
+
# Connects method declarations to location of c functions.
|
5
|
+
# The result is written to the database.
|
6
|
+
class Linker
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@table = {}
|
10
|
+
@non_static_functions = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_file(pathname)
|
14
|
+
puts "Processing #{pathname} ..."
|
15
|
+
# Since same pathnames appear many many times, we store them as
|
16
|
+
# symbols to conserve memory.
|
17
|
+
absolute_pathname = File.expand_path(pathname).to_sym
|
18
|
+
|
19
|
+
src = FileSource.new(pathname)
|
20
|
+
src.refs.each do |klass, rmethod, cfunc|
|
21
|
+
if @table[klass]
|
22
|
+
klass = @table[klass]
|
23
|
+
else
|
24
|
+
klass = @table[klass] = {}
|
25
|
+
end
|
26
|
+
klass[rmethod] = begin
|
27
|
+
if src.cfunctions[cfunc]
|
28
|
+
# Resolve static methods.
|
29
|
+
[ absolute_pathname, src.cfunctions[cfunc] ]
|
30
|
+
else
|
31
|
+
# Postpone to later.
|
32
|
+
cfunc
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Save non-static functions for later.
|
38
|
+
src.cfunctions.each_pair do |cfunc, lineno|
|
39
|
+
@non_static_functions[cfunc] = [ absolute_pathname, lineno ]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def resolve_non_static_functions
|
44
|
+
@table.each_pair do |klass, methods_table|
|
45
|
+
methods_table.each_pair do |method, location_or_cfunction|
|
46
|
+
if location_or_cfunction.is_a? String
|
47
|
+
if @non_static_functions[location_or_cfunction]
|
48
|
+
methods_table[method] = @non_static_functions[location_or_cfunction]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove_missing
|
56
|
+
@table.each_pair do |klass, methods_table|
|
57
|
+
methods_table.each_pair do |method, location_or_cfunction|
|
58
|
+
if location_or_cfunction.is_a? String
|
59
|
+
puts "-- Couldn't resolve #{klass}##{method}" if Cloc.warn?
|
60
|
+
methods_table.delete(method)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def data
|
67
|
+
@table
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mooffie
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-04-26 00:00:00 +03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: mooffie@gmail.com
|
18
|
+
executables:
|
19
|
+
- cloc
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- bin/cloc
|
26
|
+
- lib/cloc.rb
|
27
|
+
- lib/cloc/database.rb
|
28
|
+
- lib/cloc/filesource.rb
|
29
|
+
- lib/cloc/linker.rb
|
30
|
+
has_rdoc: true
|
31
|
+
homepage: http://github.com/mooffie/cloc
|
32
|
+
licenses: []
|
33
|
+
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.8.0
|
44
|
+
version:
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
requirements: []
|
52
|
+
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.3.5
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: Locates Ruby methods in C source files.
|
58
|
+
test_files: []
|
59
|
+
|