rdist 0.0.1

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/ChangeLog ADDED
@@ -0,0 +1,5 @@
1
+ 2008-02-21 Yoshifumi Shimono <yoshifumi.shimono@gmail.com>
2
+
3
+ * 0.0.1 / 2008-02-21
4
+ * initial release
5
+
data/README ADDED
@@ -0,0 +1,71 @@
1
+
2
+ = RDist - method length distribution reporter for Ruby.
3
+
4
+
5
+ == Description
6
+
7
+ RDist reports the distribution of method length in your Ruby codes.
8
+ It helps you to find the longest methods by reporting the rankings together.
9
+
10
+ RDist can also report the distribution of number of method definitions (or that of lines) in Class/Module.
11
+
12
+ == Installation
13
+
14
+ === Archive Installation
15
+
16
+ rake install
17
+
18
+ === Gem Installation
19
+
20
+ gem install rdist
21
+
22
+
23
+ == Features/Problems
24
+
25
+ RDist simply check the indent level during the analysis, so it can't correctly analyze the code with incomplete indent.
26
+
27
+ == Synopsis
28
+
29
+ See the method length distribution.
30
+ $ rdist $GEM_HOME/gems/rdist-0.0.1/lib
31
+ Method length Distribution:
32
+ 0- 4: 77% 71 ***************************************
33
+ 5- 9: 96% 18 **********
34
+ 10- 14: 100% 3 **
35
+ Total: 100% 92
36
+
37
+ Ranking Top 5:
38
+ 11: analyze (at lib/rdist/analyzer/base.rb:34)
39
+ 10: find (at lib/rdist/targetfilefinder.rb:6)
40
+ 10: analyze (at lib/rdist/analyzer/state/inblock.rb:13)
41
+ 9: initialize (at lib/rdist/analyzer/base.rb:8)
42
+ 8: initialize (at lib/rdist/commandlineinterface.rb:16)
43
+
44
+ You can also see the distribution of number of method definitions in Class/Module (<code>-C</code>, <code>--num-methods-in-class</code> option).
45
+ $ rdist -C $GEM_HOME/gems/rdist-0.0.1/lib
46
+ Distribution of number of method definitons in Class/Module:
47
+ 0- 4: 60% 9 ******************************
48
+ 5- 9: 73% 2 *******
49
+ 10- 14: 93% 3 **********
50
+ 15- 19: 93% 0
51
+ 20- 24: 100% 1 ****
52
+ Total: 100% 15
53
+
54
+ Ranking Top 5:
55
+ 23: Histogram (at lib/rdist/histogram.rb:2)
56
+ 13: Base (at lib/rdist/analyzer/base.rb:5)
57
+ 12: InBlock (at lib/rdist/analyzer/state/inblock.rb:4)
58
+ 10: CommandLineInterface (at lib/rdist/commandlineinterface.rb:2)
59
+ 8: Setting (at lib/rdist/setting.rb:5)
60
+
61
+ See <b>$ rdist -h</b> for more information.
62
+
63
+ == See also
64
+
65
+ * Flog - Seattle.rb (http://seattlerb.rubyforge.org)
66
+
67
+ == Copyright
68
+
69
+ Author:: Yoshifumi Shimono <yoshifumi.shimono@gmail.com>
70
+ Copyright:: Copyright (c) 2008 Yoshifumi Shimono
71
+ License:: Ruby's
data/Rakefile ADDED
@@ -0,0 +1,135 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'rake/contrib/sshpublisher'
10
+ require 'spec/rake/spectask'
11
+ require 'rubyforge'
12
+ require 'fileutils'
13
+ include FileUtils
14
+
15
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
16
+ require 'rdist'
17
+
18
+ NAME = "rdist"
19
+ AUTHOR = "Yoshifumi Shimono"
20
+ EMAIL = "yoshifumi.shimono@gmail.com"
21
+ DESCRIPTION = "reports the distribution of method length in your Ruby codes."
22
+ RUBYFORGE_PROJECT = "rdist"
23
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
24
+ BIN_FILES = %w( rdist )
25
+ VERS = RDist::VERSION
26
+
27
+ REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
28
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
29
+ RDOC_OPTS = [
30
+ '--title', "#{NAME} documentation",
31
+ "--charset", "utf-8",
32
+ "--opname", "index.html",
33
+ "--line-numbers",
34
+ "--main", "README",
35
+ "--inline-source",
36
+ ]
37
+
38
+ task :default => [:spec]
39
+ task :package => [:clean]
40
+
41
+ Spec::Rake::SpecTask.new do |t|
42
+ t.spec_opts = ['--options', "spec/spec.opts"]
43
+ t.spec_files = FileList['spec/*_spec.rb']
44
+ t.rcov = false
45
+ t.verbose = true
46
+ end
47
+
48
+ spec = Gem::Specification.new do |s|
49
+ s.name = NAME
50
+ s.version = VERS
51
+ s.platform = Gem::Platform::RUBY
52
+ s.has_rdoc = true
53
+ s.extra_rdoc_files = ["README", "ChangeLog"]
54
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
55
+ s.summary = DESCRIPTION
56
+ s.description = DESCRIPTION
57
+ s.author = AUTHOR
58
+ s.email = EMAIL
59
+ s.homepage = HOMEPATH
60
+ s.executables = BIN_FILES
61
+ s.rubyforge_project = RUBYFORGE_PROJECT
62
+ s.bindir = "bin"
63
+ s.require_path = "lib"
64
+ s.test_files = Dir["test/test_*.rb"]
65
+
66
+ #s.add_dependency('activesupport', '>=1.3.1')
67
+ #s.required_ruby_version = '>= 1.8.2'
68
+
69
+ s.files = %w(README ChangeLog Rakefile) +
70
+ Dir.glob("{bin,doc,spec,test,lib,templates,generator,extras,website,script}/**/*") +
71
+ Dir.glob("ext/**/*.{h,c,rb}") +
72
+ Dir.glob("examples/**/*.rb") +
73
+ Dir.glob("tools/*.rb")
74
+
75
+ s.extensions = FileList["ext/**/extconf.rb"].to_a
76
+ end
77
+
78
+ Rake::GemPackageTask.new(spec) do |p|
79
+ p.need_tar = true
80
+ p.gem_spec = spec
81
+ end
82
+
83
+ task :install do
84
+ name = "#{NAME}-#{VERS}.gem"
85
+ sh %{rake package}
86
+ sh %{sudo gem install pkg/#{name}}
87
+ end
88
+
89
+ task :uninstall => [:clean] do
90
+ sh %{sudo gem uninstall #{NAME}}
91
+ end
92
+
93
+
94
+ Rake::RDocTask.new do |rdoc|
95
+ rdoc.rdoc_dir = 'html'
96
+ rdoc.options += RDOC_OPTS
97
+ rdoc.template = "resh"
98
+ #rdoc.template = "#{ENV['template']}.rb" if ENV['template']
99
+ if ENV['DOC_FILES']
100
+ rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
101
+ else
102
+ rdoc.rdoc_files.include('README', 'ChangeLog')
103
+ rdoc.rdoc_files.include('lib/**/*.rb')
104
+ #rdoc.rdoc_files.include('ext/**/*.c')
105
+ end
106
+ end
107
+
108
+ desc "Publish to RubyForge"
109
+ task :rubyforge => [:rdoc, :package] do
110
+ Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, 'shimono').upload
111
+ end
112
+
113
+ desc 'Package and upload the release to rubyforge.'
114
+ task :release => [:clean, :package] do |t|
115
+ v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
116
+ abort "Versions don't match #{v} vs #{VERS}" unless v == VERS
117
+ pkg = "pkg/#{NAME}-#{VERS}"
118
+
119
+ rf = RubyForge.new
120
+ puts "Logging in"
121
+ rf.login
122
+
123
+ c = rf.userconfig
124
+ # c["release_notes"] = description if description
125
+ # c["release_changes"] = changes if changes
126
+ c["preformatted"] = true
127
+
128
+ files = [
129
+ "#{pkg}.tgz",
130
+ "#{pkg}.gem"
131
+ ].compact
132
+
133
+ puts "Releasing #{NAME} v. #{VERS}"
134
+ rf.add_release RUBYFORGE_PROJECT, NAME, VERS, *files
135
+ end
data/bin/rdist ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.dirname(__FILE__) + '/../lib'
4
+
5
+ require 'rdist'
6
+
7
+ RDist::CommandLineInterface.analyze(ARGV, $stdout)
@@ -0,0 +1,109 @@
1
+ require 'rdist/analyzer/macros'
2
+
3
+ module RDist
4
+ module Analyzer
5
+ class Base
6
+ extend Macros
7
+
8
+ def initialize(pattern_open_token, pattern_count_target,
9
+ pattern_close_token, allow_nesting,
10
+ histogram_interval=5)
11
+ @pattern_open_token = pattern_open_token
12
+ @pattern_count_target = pattern_count_target
13
+ @pattern_close_token = pattern_close_token
14
+ @allow_nesting = allow_nesting
15
+ @histogram_interval = histogram_interval
16
+ init_count()
17
+ init_states()
18
+ end
19
+
20
+ attr_reader :pattern_open_token
21
+ attr_reader :pattern_count_target
22
+ attr_reader :pattern_close_token
23
+ attr_writer :histogram_interval
24
+
25
+ public
26
+ def analyze_all(pathes)
27
+ init_count()
28
+ pathes.each do |path|
29
+ analyze(path)
30
+ end
31
+ end
32
+
33
+ def analyze(path)
34
+ @path = path
35
+ DebugLogger.debug "Analyzing ``#{@path}''"
36
+ set_state_waiting_block()
37
+ open(path, 'r') do |input|
38
+ input.each_line do |line|
39
+ @current_line_id = input.lineno
40
+ analyze_line(line)
41
+ end
42
+ end
43
+ @current_line_id = nil
44
+ reset_states()
45
+ end
46
+
47
+ def histogram
48
+ Histogram.new(@count_of, @histogram_interval)
49
+ end
50
+
51
+ def ranking
52
+ Ranking.new(@count_of)
53
+ end
54
+
55
+ def reset
56
+ init_count()
57
+ end
58
+
59
+ def add_count_of(block_name, line_id)
60
+ block_id = "#{block_name} (at #{@path}:#{line_id})"
61
+ @count_of[block_id] ||= 0
62
+ @count_of[block_id] += 1
63
+ end
64
+
65
+ def allow_nesting?
66
+ @allow_nesting
67
+ end
68
+
69
+ def deny_nesting?
70
+ not @allow_nesting
71
+ end
72
+
73
+ def_state_setter :waiting_block
74
+ def_state_setter :in_multi_line_comment
75
+ def_state_setter :in_block
76
+ alias __set_state_in_block__ set_state_in_block
77
+
78
+ def set_state_in_block(indent, block_name)
79
+ __set_state_in_block__()
80
+ @state.push_to_stacks(indent, block_name,
81
+ @current_line_id)
82
+ end
83
+
84
+ private
85
+ def analyze_line(line)
86
+ @state.analyze(line)
87
+ end
88
+
89
+ def init_count
90
+ @count_of = {}
91
+ end
92
+
93
+ def init_states
94
+ @state_for = {
95
+ :waiting_block => State::WaitingBlock.new(self),
96
+ :in_block => State::InBlock.new(self),
97
+ :in_multi_line_comment \
98
+ => State::InMultiLineComment.new(self),
99
+ }
100
+ end
101
+
102
+ def reset_states
103
+ @state_for.each do |symbol, state|
104
+ state.reset
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,18 @@
1
+ module RDist
2
+ module Analyzer
3
+ module Macros
4
+ private
5
+ def def_state_setter(state_symbol)
6
+ method_name = get_state_setter_name(state_symbol)
7
+ define_method(method_name) do
8
+ next_state = instance_variable_get(:@state_for)[state_symbol]
9
+ instance_variable_set(:@state, next_state)
10
+ end
11
+ end
12
+
13
+ def get_state_setter_name(state_symbol)
14
+ "set_state_#{state_symbol}"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module RDist
2
+ module Analyzer
3
+ class MethodLength < Base
4
+ def initialize
5
+ super(/def/, /$/, /end/, RDist::DENY_NESTING)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module RDist
2
+ module Analyzer
3
+ class NumLinesInClass < Base
4
+ def initialize
5
+ open_tokens = %w{module class}
6
+ pattern_open_tokens = Regexp.union(*open_tokens)
7
+ super(pattern_open_tokens, /$/,
8
+ /end/, RDist::ALLOW_NESTING, 30)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module RDist
2
+ module Analyzer
3
+ class NumMethodsInClass < Base
4
+ def initialize
5
+ open_tokens = %w{module class}
6
+ pattern_open_tokens = Regexp.union(*open_tokens)
7
+ super(pattern_open_tokens, /(?:^|[^\w]+) def [^\w]+/nxm,
8
+ /end/, RDist::ALLOW_NESTING)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module RDist
2
+ module Analyzer
3
+ module State
4
+ PATTERN_MULTI_LINE_COMMENT_BEGIN = /\A \=begin/nxm
5
+ PATTERN_MULTI_LINE_COMMENT_END = /\A \=end/nxm
6
+
7
+ class Base
8
+ def initialize(analyzer)
9
+ @analyzer = analyzer
10
+ end
11
+
12
+ public
13
+ def analyze(line)
14
+ raise 'Must be overridden in sub-class'
15
+ end
16
+
17
+ def reset
18
+ # do nothing
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,85 @@
1
+ module RDist
2
+ module Analyzer
3
+ module State
4
+ class InBlock < WaitingBlock
5
+ def initialize(analyzer)
6
+ super(analyzer)
7
+ @pattern_count_target = analyzer.pattern_count_target
8
+ @pattern_close_token = analyzer.pattern_close_token
9
+ init_stacks()
10
+ end
11
+
12
+ public
13
+ def analyze(line)
14
+ if have_close_token?(line)
15
+ return accept_close_token()
16
+ end
17
+ if have_open_token?(line)
18
+ return super(line) # call WaitingBlock#analyze(line)
19
+ end
20
+ if have_count_target?(line)
21
+ @analyzer.add_count_of(@block_names.last,
22
+ @line_ids.last)
23
+ end
24
+ end
25
+
26
+ def push_to_stacks(indent, block_name, line_id)
27
+ @indents << indent
28
+ @block_names << block_name
29
+ @line_ids << line_id
30
+ end
31
+
32
+ def reset
33
+ init_stacks()
34
+ end
35
+
36
+ private
37
+ def init_stacks
38
+ @indents = []
39
+ @block_names = []
40
+ @line_ids = []
41
+ end
42
+
43
+ def have_close_token?(line)
44
+ pattern_have_close_token.match(line)
45
+ end
46
+
47
+ PATTERN_SECOND_TOKEN = /[^\s]+/nxm
48
+ PATTERN_TOKEN_GAP = /[^\w]+/nxm
49
+ def pattern_have_close_token
50
+ %r/
51
+ \A
52
+ #{Regexp.escape(@indents.last)}
53
+ #{@pattern_close_token}
54
+ #{PATTERN_TOKEN_GAP}
55
+ /nxm
56
+ end
57
+
58
+ def have_count_target?(line)
59
+ pattern_have_count_target.match(line)
60
+ end
61
+
62
+ def pattern_have_count_target
63
+ @pattern_count_target
64
+ end
65
+
66
+ def accept_close_token
67
+ pop_stacks()
68
+ if analyzing_toplevel?
69
+ @analyzer.set_state_waiting_block
70
+ end
71
+ end
72
+
73
+ def pop_stacks
74
+ @indents.pop
75
+ @block_names.pop
76
+ @line_ids.pop
77
+ end
78
+
79
+ def analyzing_toplevel?
80
+ @analyzer.deny_nesting? || @indents.empty?
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,18 @@
1
+ module RDist
2
+ module Analyzer
3
+ module State
4
+ class InMultiLineComment < Base
5
+ public
6
+ def analyze(line)
7
+ return unless multi_line_comment_end?(line)
8
+ @analyzer.set_state_waiting_block
9
+ end
10
+
11
+ private
12
+ def multi_line_comment_end?(line)
13
+ PATTERN_MULTI_LINE_COMMENT_END =~ line
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,52 @@
1
+ module RDist
2
+ module Analyzer
3
+ module State
4
+ class WaitingBlock < Base
5
+ def initialize(analyzer)
6
+ super(analyzer)
7
+ @pattern_open_token = analyzer.pattern_open_token
8
+ end
9
+
10
+ public
11
+ def analyze(line)
12
+ if multi_line_comment_begin?(line)
13
+ return @analyzer.set_state_in_multi_line_comment
14
+ end
15
+ return unless (match_data = have_open_token?(line))
16
+ indent = get_indent_from(match_data)
17
+ block_name = get_block_name_from(match_data)
18
+ @analyzer.set_state_in_block(indent, block_name)
19
+ end
20
+
21
+ private
22
+ def multi_line_comment_begin?(line)
23
+ PATTERN_MULTI_LINE_COMMENT_BEGIN =~ line
24
+ end
25
+
26
+ def have_open_token?(line)
27
+ pattern_have_open_token.match(line)
28
+ end
29
+
30
+ PATTERN_INDENT = /\s*/nxm
31
+ PATTERN_SECOND_TOKEN = /[\w\.\:\=\?]+/nxm
32
+ def pattern_have_open_token
33
+ %r/
34
+ \A
35
+ (#{PATTERN_INDENT})
36
+ #{@pattern_open_token}
37
+ \s+
38
+ (#{PATTERN_SECOND_TOKEN})
39
+ /nxm
40
+ end
41
+
42
+ def get_indent_from(match_data)
43
+ match_data[1]
44
+ end
45
+
46
+ def get_block_name_from(match_data)
47
+ match_data[2]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ require 'rdist/analyzer/state/base'
2
+ require 'rdist/analyzer/state/waitingblock'
3
+ require 'rdist/analyzer/state/inblock'
4
+ require 'rdist/analyzer/state/inmultilinecomment'
@@ -0,0 +1,6 @@
1
+ require 'rdist/analyzer/state'
2
+
3
+ require 'rdist/analyzer/base'
4
+ require 'rdist/analyzer/methodlength'
5
+ require 'rdist/analyzer/numlinesinclass'
6
+ require 'rdist/analyzer/nummethodsinclass'
@@ -0,0 +1,76 @@
1
+ module RDist
2
+ # CommandLineInterface is the main class used for the rdist command.
3
+ class CommandLineInterface
4
+ DEFAULT_OUTPUT = $stdout
5
+
6
+ private_class_method :new
7
+
8
+ # Parses +argv+ and print analysis result to +output+.
9
+ # Analysis result is the distribution histogram and the rankings.
10
+ # If +argv+ includes '--verbose' option,
11
+ # then print all analyzing file names too.
12
+ def self.analyze(argv, output=DEFAULT_OUTPUT)
13
+ begin
14
+ new(argv, output)
15
+ rescue OptionParser::ParseError => err
16
+ puts err
17
+ exit 1
18
+ end
19
+ end
20
+
21
+ def initialize(argv, output) #:nodoc:
22
+ @output = output
23
+ @setting = Setting.for_argv(argv)
24
+ @analyzer = @setting.analyzer
25
+ @num_ranking = @setting.num_ranking
26
+
27
+ analyze_all_targets(argv)
28
+ print_banner()
29
+ print_result()
30
+ end
31
+
32
+ private
33
+ def print_banner
34
+ DebugLogger.debug ''
35
+ @output.puts @setting.banner
36
+ end
37
+
38
+ def analyze_all_targets(argv)
39
+ TargetFileFinder.find(argv) do |path|
40
+ @analyzer.analyze(path)
41
+ end
42
+ @histogram = @analyzer.histogram
43
+ @ranking = @analyzer.ranking
44
+ end
45
+
46
+ def print_result
47
+ print_histogram()
48
+ print_ranking()
49
+ end
50
+
51
+ def print_histogram
52
+ @output.puts @histogram
53
+ @output.puts
54
+ end
55
+
56
+ def print_ranking
57
+ @output.puts ranking_banner()
58
+ if rank_all?
59
+ @output.puts @ranking.to_s
60
+ else
61
+ @output.puts @ranking.string_top(@num_ranking)
62
+ end
63
+ end
64
+
65
+ def ranking_banner
66
+ if rank_all?
67
+ return 'Entire Ranking:'
68
+ end
69
+ "Ranking Top #{@num_ranking}:"
70
+ end
71
+
72
+ def rank_all?
73
+ :all == @num_ranking
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,30 @@
1
+ require 'logger'
2
+ require 'singleton'
3
+ require 'forwardable'
4
+
5
+ module RDist
6
+ class DebugLogger
7
+ include Singleton
8
+ extend Forwardable
9
+
10
+ def self.debug(*s)
11
+ s.each{|x| instance.debug(x) }
12
+ end
13
+
14
+ def self.fatal(*s)
15
+ s.each{|x| instance.fatal(x) }
16
+ end
17
+
18
+ def initialize
19
+ @logger = Logger.new($stdout)
20
+ @logger.level = $DEBUG ? Logger::DEBUG : Logger::FATAL
21
+ @logger.progname = 'RDist'
22
+ @logger.formatter = Proc.new do
23
+ |severity, datetime, progname, msg| "#{msg}\n"
24
+ end
25
+ end
26
+
27
+ def_delegator :@logger, :debug, :debug
28
+ def_delegator :@logger, :fatal, :fatal
29
+ end
30
+ end
@@ -0,0 +1,136 @@
1
+ module RDist
2
+ class Histogram
3
+ DEFAULT_RANGE_UNIT_SIZE = 5
4
+ # Creates a new histogram from +count_of+.
5
+ # The keys of +count_of+ are divided into some ranges by its count.
6
+ # The size of each range is specified with +range_unit_size+.
7
+ def initialize(count_of,
8
+ range_unit_size=DEFAULT_RANGE_UNIT_SIZE)
9
+ raise 'Initialized with illegal object' unless count_of
10
+ @count_of = count_of
11
+ @range_unit_size = range_unit_size
12
+ init_count_in_range()
13
+ init_cumulative_percentages()
14
+ end
15
+
16
+ public
17
+ WHITESPACE = ' '.freeze
18
+ PERCENTAGE_MAX = 100
19
+ def to_s
20
+ str = ''
21
+ @count_in_range.each_with_index do |count, range_id|
22
+ str << heading_of(range_id)
23
+ str << string_cumulative_percentage_at(range_id)
24
+ str << string_count(count) << WHITESPACE
25
+ str << bar(count) << NEWLINE
26
+ end
27
+ str << footer()
28
+ end
29
+
30
+ private
31
+ def init_count_in_range
32
+ @count_in_range = []
33
+ @count_of.each{|block_name, count|
34
+ range_id = range_id_of(count)
35
+ @count_in_range[range_id] ||= 0
36
+ @count_in_range[range_id] += 1
37
+ }
38
+ replace_nil_by_zero(@count_in_range)
39
+ @count_in_range
40
+ end
41
+
42
+ def range_id_of(count)
43
+ (count / @range_unit_size).to_i
44
+ end
45
+
46
+ def init_cumulative_percentages
47
+ @cumulative_percentages = get_cumulative_percentages()
48
+ end
49
+
50
+ def get_cumulative_percentages
51
+ cumulative_counts \
52
+ = get_cumulative_counts_from(@count_in_range)
53
+ cumulative_counts.collect do |cumulative_count|
54
+ percentage_of(cumulative_count)
55
+ end
56
+ end
57
+
58
+ def get_cumulative_counts_from(ary)
59
+ cumulative_counts = []
60
+ prev = 0
61
+ ary.each_with_index do |item, index|
62
+ cumulative_counts[index] = prev += item
63
+ end
64
+ cumulative_counts
65
+ end
66
+
67
+ def replace_nil_by_zero(ary)
68
+ ary.each_index do |index|
69
+ ary[index] ||= 0
70
+ end
71
+ end
72
+
73
+ def sum_of_count
74
+ get_sum_of(@count_in_range)
75
+ end
76
+
77
+ def get_sum_of(ary)
78
+ sum = 0
79
+ ary.each do |item|
80
+ sum += item
81
+ end
82
+ sum
83
+ end
84
+
85
+ LENGTH_RANGE_MIN = 3
86
+ LENGTH_RANGE_MAX = LENGTH_RANGE_MIN
87
+ FORMAT_RANGE_HEADING \
88
+ = "%#{LENGTH_RANGE_MIN}d-%#{LENGTH_RANGE_MAX}d: ".freeze
89
+ def heading_of(range_id)
90
+ sprintf(FORMAT_RANGE_HEADING,
91
+ min_of(range_id), max_of(range_id))
92
+ end
93
+
94
+ def min_of(range_id)
95
+ range_id * @range_unit_size
96
+ end
97
+
98
+ def max_of(range_id)
99
+ (range_id + 1) * @range_unit_size - 1
100
+ end
101
+
102
+ def string_cumulative_percentage_at(range_id)
103
+ string_percentage(@cumulative_percentages[range_id])
104
+ end
105
+
106
+ FORMAT_PERCENTAGE = '%3d%% '.freeze
107
+ def string_percentage(percentage)
108
+ sprintf(FORMAT_PERCENTAGE, percentage)
109
+ end
110
+
111
+ FORMAT_COUNT = '%5d'.freeze
112
+ def string_count(count)
113
+ sprintf(FORMAT_COUNT, count)
114
+ end
115
+
116
+ UNIT_CHAR_FOR_BAR = '*'.freeze
117
+ def bar(count)
118
+ UNIT_CHAR_FOR_BAR * bar_length_for(count)
119
+ end
120
+
121
+ def bar_length_for(count)
122
+ (percentage_of(count) / 2).ceil
123
+ end
124
+
125
+ def percentage_of(count)
126
+ (count.to_f / sum_of_count) * PERCENTAGE_MAX
127
+ end
128
+
129
+ def footer
130
+ str = ' Total: '
131
+ str << string_percentage(PERCENTAGE_MAX)
132
+ str << string_count(sum_of_count())
133
+ str << NEWLINE
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,34 @@
1
+ module RDist
2
+ class Ranking
3
+ # Creates a new ranking from +score_of+.
4
+ # The items are sorted by ascending order.
5
+ def initialize(score_of)
6
+ @rankings = score_of.sort_by{|key, score| [-score, key] }
7
+ end
8
+
9
+ public
10
+ # Returns items from 1st to +n+-th.
11
+ # Each item is an Array as [key, score].
12
+ def top(n)
13
+ @rankings.first(n)
14
+ end
15
+
16
+ FORMAT = '%7d: %s'.freeze
17
+ FORMATTER = Proc.new{|key, score| sprintf(FORMAT, score, key) }
18
+ # Returns formatted string of top +n+ items.
19
+ def string_top(n)
20
+ str = ''
21
+ n.times do |i|
22
+ key, score = @rankings[i]
23
+ str << FORMATTER.call(key, score)
24
+ str << NEWLINE
25
+ end
26
+ str
27
+ end
28
+
29
+ # Returns formatted string of all items.
30
+ def to_s
31
+ string_top(@rankings.size)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ module RDist
2
+ class Setting
3
+ module Marcos
4
+ private
5
+ def def_option(*args, &block)
6
+ method_symbol = method_symbol_of(args)
7
+ block = Proc.new{} unless block
8
+ define_method(method_symbol, &block)
9
+ const_get(:OPTION_SEEDS) << [args, method_symbol]
10
+ end
11
+
12
+ def method_symbol_of(option_args)
13
+ str_option = option_args.first
14
+ str_converted = str_option.split(/\s/).first
15
+ str_converted.gsub!(/-/, '_')
16
+ :"__option_switch#{str_converted}__"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,115 @@
1
+ require 'optparse'
2
+ require 'rdist/setting/macros'
3
+
4
+ module RDist
5
+ class Setting
6
+ extend Marcos
7
+
8
+ private_class_method :new
9
+
10
+ def self.for_argv(argv)
11
+ new(argv)
12
+ end
13
+
14
+ def initialize(argv)
15
+ @argv = argv
16
+ init_attributes()
17
+ configure_option_parser()
18
+ parse_options()
19
+ init_analyzer()
20
+ end
21
+
22
+ attr_reader :analyzer
23
+ attr_reader :banner
24
+ attr_reader :num_ranking
25
+
26
+ private
27
+ DEFAULT_NUM_RANKING = 5
28
+ def init_attributes
29
+ @analyzer_class = Analyzer::MethodLength
30
+ @banner = 'Method length Distribution:'
31
+ @num_ranking = DEFAULT_NUM_RANKING
32
+ @interval = nil
33
+ init_parser()
34
+ end
35
+
36
+ def init_parser
37
+ @option_parser = OptionParser.new
38
+ @option_parser.banner = 'Usage: rdist [options] FILE...'
39
+ @option_parser.summary_width = 30
40
+ @option_parser.summary_indent = ' '
41
+ end
42
+
43
+ def parse_options
44
+ @option_parser.parse!(@argv)
45
+ @argv << '.' if @argv.empty?
46
+ end
47
+
48
+ def init_analyzer
49
+ @analyzer = @analyzer_class.new
50
+ @analyzer.histogram_interval = @interval if @interval
51
+ end
52
+
53
+ OPTION_SEEDS = [] # Do NOT freeze!!
54
+ def configure_option_parser
55
+ OPTION_SEEDS.each do |args, method_symbol|
56
+ method_call = method_call(method_symbol)
57
+ @option_parser.on(*args, &method_call)
58
+ end
59
+ end
60
+
61
+ def method_call(symbol)
62
+ Proc.new do |*args|
63
+ __send__(symbol, *args)
64
+ end
65
+ end
66
+
67
+ def_option('-m',
68
+ '--num-lines-in-method',
69
+ '--method-length',
70
+ 'count number of lines in a method',
71
+ ' (default)')
72
+
73
+ def_option('-c',
74
+ '--num-lines-in-class',
75
+ 'count number of lines in Class/Module') do
76
+ @analyzer_class = Analyzer::NumLinesInClass
77
+ @banner = 'Distribution of number of lines in Class/Module:'
78
+ end
79
+
80
+ def_option('-C',
81
+ '--num-methods-in-class',
82
+ 'ount number of method definitions',
83
+ ' in Class/Module') do
84
+ @analyzer_class = Analyzer::NumMethodsInClass
85
+ @banner = 'Distribution of number of method definitons in Class/Module:'
86
+ end
87
+
88
+ def_option('-i', '--interval [N]', Integer,
89
+ 'set histogram interval') do |n|
90
+ @interval = n if n
91
+ end
92
+
93
+ def_option('-r', '--rank [N]', Integer,
94
+ 'show ranking up to Nth item (default 5)') do |n|
95
+ @num_ranking = n if n
96
+ end
97
+
98
+ def_option('--verbose',
99
+ 'print verbose information to STDOUT',
100
+ ' (analysis process, entire ranking)') do
101
+ $DEBUG = true
102
+ @num_ranking = :all
103
+ end
104
+
105
+ def_option('-h', '--help', 'show this message') do
106
+ puts @option_parser.help
107
+ exit
108
+ end
109
+
110
+ def_option('-v', '--version', 'show version') do
111
+ puts "RDist-#{VERSION} - method length reporter for Ruby"
112
+ exit
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,43 @@
1
+ require 'find'
2
+
3
+ module RDist
4
+ module TargetFileFinder
5
+ module_function
6
+ def find(argv)
7
+ Find.find(*argv) do |path|
8
+ if FileTest.directory?(path)
9
+ Find.prune if hidden?(path)
10
+ next
11
+ end
12
+ next if hidden?(path)
13
+ next unless ruby_code?(path)
14
+ next if test_code?(path)
15
+ yield path
16
+ end
17
+ end
18
+
19
+ PATTERN_HIDDEN = %r/
20
+ \A
21
+ \.
22
+ [^\.] # pattern to accept parent directory: ``..''
23
+ /nxm
24
+ def hidden?(path)
25
+ PATTERN_HIDDEN =~ File.basename(path)
26
+ end
27
+
28
+ EXTNAME_RUBY_CODE = '.rb'.freeze
29
+ EXTNAME_RUBY_EXEC_CODE = '.rbw'.freeze
30
+ def ruby_code?(path)
31
+ case File.extname(path)
32
+ when EXTNAME_RUBY_CODE, EXTNAME_RUBY_EXEC_CODE then true
33
+ else false
34
+ end
35
+ end
36
+
37
+ PATTERN_TEST_CODE \
38
+ = /(?:\A test_ | _spec \z)/nxm.freeze
39
+ def test_code?(path)
40
+ PATTERN_TEST_CODE =~ File.basename(path)
41
+ end
42
+ end
43
+ end
data/lib/rdist.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'rdist/analyzer'
2
+ require 'rdist/commandlineinterface'
3
+ require 'rdist/debuglogger'
4
+ require 'rdist/histogram'
5
+ require 'rdist/ranking'
6
+ require 'rdist/setting'
7
+ require 'rdist/targetfilefinder'
8
+
9
+ module RDist
10
+ VERSION = '0.0.1'
11
+
12
+ ALLOW_NESTING = true
13
+ DENY_NESTING = false
14
+
15
+ NEWLINE = "\n".freeze
16
+ end
@@ -0,0 +1,43 @@
1
+ require 'stringio'
2
+
3
+ describe CommandLineInterface, %q[when ``normal_code.rb'' given] do
4
+ def check_output(string_io, count_of,
5
+ histogram_interval, num_ranking,
6
+ banner='Method length Distribution:',
7
+ ranking_banner='Ranking Top 5:')
8
+ histogram = Histogram.new(count_of, histogram_interval)
9
+ ranking = Ranking.new(count_of)
10
+ expected_string = "#{banner}\n"
11
+ expected_string << histogram.to_s
12
+ expected_string << "\n#{ranking_banner}\n"
13
+ expected_string << ranking.string_top(num_ranking)
14
+ string_io.string.should == expected_string
15
+ end
16
+
17
+ before do
18
+ @fixture_path = FIXTURE_DIR + '/normal_code.rb'
19
+ @string_io = StringIO.new
20
+ @expected_count_of = {
21
+ 'initialize (at ./spec/../spec/fixtures/normal_code.rb:3)' => 4,
22
+ 'each_vertex (at ./spec/../spec/fixtures/normal_code.rb:14)' => 3,
23
+ 'surrounded? (at ./spec/../spec/fixtures/normal_code.rb:20)' => 1,
24
+ 'concat (at ./spec/../spec/fixtures/normal_code.rb:24)' => 6,
25
+ 'init_edge_vertices (at ./spec/../spec/fixtures/normal_code.rb:34)' => 10,
26
+ 'dame_vertices (at ./spec/../spec/fixtures/normal_code.rb:47)' => 3,
27
+ 'space? (at ./spec/../spec/fixtures/normal_code.rb:53)' => 1,
28
+ }
29
+ end
30
+
31
+ it 'should count number of method length correctly' do
32
+ CommandLineInterface.analyze(['--interval', '1', @fixture_path],
33
+ @string_io)
34
+ check_output(@string_io, @expected_count_of, 1, 5)
35
+ end
36
+
37
+ it %q[should show entire ranking if '--verbose' option is given] do
38
+ CommandLineInterface.analyze(['--verbose', @fixture_path], @string_io)
39
+ check_output(@string_io, @expected_count_of, 5, 7,
40
+ 'Method length Distribution:',
41
+ 'Entire Ranking:')
42
+ end
43
+ end
@@ -0,0 +1,57 @@
1
+ module Go
2
+ class Ren
3
+ def initialize(board, color, vertices)
4
+ @board = board
5
+ @color = color
6
+ @vertices = vertices
7
+ init_edge_vertices()
8
+ end
9
+
10
+ attr_reader :color
11
+ attr_reader :vertices
12
+
13
+ public
14
+ def each_vertex
15
+ @vertices.each do |vertex|
16
+ yield vertex
17
+ end
18
+ end
19
+
20
+ def surrounded?
21
+ dame_vertices.empty?
22
+ end
23
+
24
+ def concat(other)
25
+ if other.color != @color
26
+ raise %q[Can't contat enemy's ren]
27
+ end
28
+ @vertices.concat(other.vertices)
29
+ init_edge_vertices()
30
+ self
31
+ end
32
+
33
+ private
34
+ def init_edge_vertices
35
+ @edge_vertices = []
36
+ each_vertex do |vertex|
37
+ vertex.each_neighbor do |neighbor|
38
+ next unless @board.include?(neighbor)
39
+ next if @vertices.include?(neighbor)
40
+ next if @edge_vertices.include?(neighbor)
41
+ @edge_vertices << neighbor
42
+ end
43
+ end
44
+ @edge_vertices
45
+ end
46
+
47
+ def dame_vertices
48
+ @edge_vertices.select do |vertex|
49
+ space?(vertex)
50
+ end
51
+ end
52
+
53
+ def space?(vertex)
54
+ @board.have_space_on?(vertex)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,41 @@
1
+ describe Ranking, 'when initialized with scores of 5 people' do
2
+ before do
3
+ score_of = {
4
+ 'George' => 0,
5
+ 'John' => 100,
6
+ 'Bob' => 50,
7
+ 'Peter' => 0,
8
+ 'Ben' => 100,
9
+ }
10
+ @ranking = Ranking.new(score_of)
11
+ end
12
+
13
+ it 'should show top 3' do
14
+ ary = [
15
+ ['Ben', 100],
16
+ ['John', 100],
17
+ ['Bob', 50],
18
+ ]
19
+ @ranking.top(3).should == ary
20
+ end
21
+
22
+ it 'should serialize top 2' do
23
+ expected_string = <<-'END_STRING'
24
+ 100: Ben
25
+ 100: John
26
+ 50: Bob
27
+ END_STRING
28
+ @ranking.string_top(3).should == expected_string
29
+ end
30
+
31
+ it 'should serialize entire rankings' do
32
+ expected_string = <<-'END_STRING'
33
+ 100: Ben
34
+ 100: John
35
+ 50: Bob
36
+ 0: George
37
+ 0: Peter
38
+ END_STRING
39
+ @ranking.to_s.should == expected_string
40
+ end
41
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color --require spec/spec_helper
@@ -0,0 +1,10 @@
1
+ PROJECT_ROOT = File.dirname(__FILE__) + '/..'
2
+
3
+ $LOAD_PATH.unshift PROJECT_ROOT + '/lib'
4
+
5
+ SPEC_DIR = PROJECT_ROOT + '/spec'
6
+ FIXTURE_DIR = SPEC_DIR + '/fixtures'
7
+
8
+ require 'rdist'
9
+
10
+ include RDist
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rdist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Yoshifumi Shimono
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-02-26 00:00:00 +09:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: reports the distribution of method length in your Ruby codes.
17
+ email: yoshifumi.shimono@gmail.com
18
+ executables:
19
+ - rdist
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - ChangeLog
25
+ files:
26
+ - README
27
+ - ChangeLog
28
+ - Rakefile
29
+ - bin/rdist
30
+ - spec/spec.opts
31
+ - spec/spec_helper.rb
32
+ - spec/commandlineinterface_spec.rb
33
+ - spec/fixtures
34
+ - spec/ranking_spec.rb
35
+ - spec/fixtures/normal_code.rb
36
+ - lib/rdist
37
+ - lib/rdist.rb
38
+ - lib/rdist/histogram.rb
39
+ - lib/rdist/targetfilefinder.rb
40
+ - lib/rdist/analyzer.rb
41
+ - lib/rdist/setting.rb
42
+ - lib/rdist/setting
43
+ - lib/rdist/debuglogger.rb
44
+ - lib/rdist/analyzer
45
+ - lib/rdist/ranking.rb
46
+ - lib/rdist/commandlineinterface.rb
47
+ - lib/rdist/setting/macros.rb
48
+ - lib/rdist/analyzer/macros.rb
49
+ - lib/rdist/analyzer/methodlength.rb
50
+ - lib/rdist/analyzer/state.rb
51
+ - lib/rdist/analyzer/numlinesinclass.rb
52
+ - lib/rdist/analyzer/base.rb
53
+ - lib/rdist/analyzer/state
54
+ - lib/rdist/analyzer/nummethodsinclass.rb
55
+ - lib/rdist/analyzer/state/inmultilinecomment.rb
56
+ - lib/rdist/analyzer/state/inblock.rb
57
+ - lib/rdist/analyzer/state/waitingblock.rb
58
+ - lib/rdist/analyzer/state/base.rb
59
+ has_rdoc: true
60
+ homepage: http://rdist.rubyforge.org
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --title
64
+ - rdist documentation
65
+ - --charset
66
+ - utf-8
67
+ - --opname
68
+ - index.html
69
+ - --line-numbers
70
+ - --main
71
+ - README
72
+ - --inline-source
73
+ - --exclude
74
+ - ^(examples|extras)/
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ version:
89
+ requirements: []
90
+
91
+ rubyforge_project: rdist
92
+ rubygems_version: 1.0.1
93
+ signing_key:
94
+ specification_version: 2
95
+ summary: reports the distribution of method length in your Ruby codes.
96
+ test_files: []
97
+