code-stats 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ require 'rake_ext'
2
+
3
+ project(
4
+ name: "code_stats",
5
+ official_name: 'code-stats',
6
+ gem: true,
7
+ summary: "Language-agnostic Code Statistics",
8
+
9
+ bin: 'bin',
10
+ executables: ['code_stats'],
11
+ dirs: %w(bin),
12
+ version: '0.1.1',
13
+
14
+ author: "Alexey Petrushin",
15
+ homepage: "http://github.com/alexeypetrushin/misc/code_stats"
16
+ )
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib_dir = %(#{File.expand_path "#{__FILE__}/../.."}/lib)
4
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include? lib_dir
5
+
6
+ require 'code_stats'
7
+
8
+ if ARGV.empty? or %w(-h --help help).any?{|k| ARGV.include? k}
9
+ puts <<-TEXT
10
+ Language-agnostic Code Statistics
11
+
12
+ Usage:
13
+ $ code_stats /projects/wordpress /projects/drupal
14
+ $ code_stats /projects/*
15
+ $ code_stats /projects/wordpress /projects/drupal except: JavaScript skip_filter: /tmp/
16
+
17
+ Options:
18
+ except: JavaScript - analyze all languages except JavaScript
19
+ only: Ruby - analyze Ruby language only
20
+ specs_filter: regex, specifies should be path threated as specs, default: #{CodeStats::Project::DEFAUL_SPEC_FILTER.source}
21
+ skip_filter: regex, specifies paths that should be skipped, default: #{CodeStats::Project::DEFAUL_SKIP_FILTER.source}
22
+ TEXT
23
+ exit
24
+ end
25
+
26
+ args = RubyExt.argv
27
+ options = args.extract_options!
28
+ options2 = {}; options.each{|k, v| options2[k] = v ? v.to_sym : v}
29
+ args << options2
30
+
31
+ CodeStats.analyze_and_report *args
@@ -0,0 +1,28 @@
1
+ raise 'ruby 1.9.2 or higher required!' if RUBY_VERSION < '1.9.2'
2
+
3
+ require 'code_stats/gems'
4
+
5
+ require 'ruby_ext'
6
+ require 'vfs'
7
+ require 'haml'
8
+ require 'tilt'
9
+
10
+ class CodeStats
11
+ class << self
12
+ def extensions; @extensions ||= {} end
13
+
14
+ attr_accessor :file_size_limit
15
+ attr_required :file_size_limit
16
+ end
17
+
18
+ self.file_size_limit = 500 * 1024
19
+ end
20
+
21
+ %w(
22
+ support
23
+ languages
24
+ file_set
25
+ project
26
+ report
27
+ code_stats
28
+ ).each{|f| require "code_stats/#{f}"}
@@ -0,0 +1,51 @@
1
+ class CodeStats
2
+ class << self
3
+ def know? extension
4
+ extensions.include? extension
5
+ end
6
+
7
+ def parse text, extension
8
+ language = extensions[extension] || raise("no language for :#{extension} extension!")
9
+ language.new text
10
+ end
11
+
12
+ def analyze *args
13
+ options = args.extract_options!
14
+ paths = args
15
+ paths.collect!{|path| Dir[path]}.flatten!
16
+
17
+ puts <<-TEXT
18
+ please wait, analyzing:
19
+ #{paths.join("\n ")}
20
+ TEXT
21
+
22
+ projects = paths.collect do |path|
23
+ project = Project.new path, options
24
+ project.analyze!
25
+ project
26
+ end
27
+ projects.sort{|a, b| b.characters_count <=> a.characters_count}
28
+ end
29
+
30
+ def analyze_and_report *args
31
+ # parsing options
32
+ options = args.extract_options!
33
+ paths = args
34
+
35
+ options.validate_options! *(FileSet::AVAILIABLE_OPTIONS + Project::AVAILIABLE_OPTIONS)
36
+ project_options = options.select{|k, v| Project::AVAILIABLE_OPTIONS.include? k}
37
+ lang_options = options.select{|k, v| FileSet::AVAILIABLE_OPTIONS.include? k}
38
+
39
+ # analyzing & reporting
40
+ projects = analyze *(paths << project_options)
41
+ report = Report.new(*(projects << lang_options)).render
42
+
43
+ report_file = "./projects_statistics.html".to_file
44
+ report_file.write! report
45
+
46
+ puts "done, statistics are in #{report_file}"
47
+
48
+ Kernel.exec "open #{report_file}"
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,43 @@
1
+ class CodeStats::FileSet
2
+ def initialize
3
+ @lines_count_by_language, @characters_count_by_language = Hash.new(0), Hash.new(0)
4
+ end
5
+
6
+ def lines_count options = {}
7
+ total_count = 0
8
+ lines_count_by_language(options).each{|lang, count| total_count += count}
9
+ total_count
10
+ end
11
+
12
+ def characters_count options = {}
13
+ total_count = 0
14
+ characters_count_by_language(options).each{|lang, count| total_count += count}
15
+ total_count
16
+ end
17
+
18
+ def lines_count_by_language options = {}
19
+ filter_by_lang(@lines_count_by_language, options)
20
+ end
21
+
22
+ def characters_count_by_language options = {}
23
+ filter_by_lang(@characters_count_by_language, options)
24
+ end
25
+
26
+ def add script
27
+ @lines_count_by_language[script.class.alias.to_sym] += script.lines_count
28
+ @characters_count_by_language[script.class.alias.to_sym] += script.characters_count
29
+ end
30
+
31
+ AVAILIABLE_OPTIONS = [:only, :except]
32
+
33
+ protected
34
+ def filter_by_lang languages, options
35
+ options.validate_options! *AVAILIABLE_OPTIONS
36
+ only = options[:only] && Array.wrap(options[:only]).collect{|v| v.to_sym}
37
+ except = options[:except] && Array.wrap(options[:except]).collect{|v| v.to_sym}
38
+
39
+ languages.reject do |lang, count|
40
+ (only && !only.include?(lang)) or (except and except.include?(lang))
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,6 @@
1
+ if respond_to? :fake_gem
2
+ fake_gem 'ruby_ext'
3
+ fake_gem 'vfs'
4
+ fake_gem 'haml'
5
+ fake_gem 'tilt'
6
+ end
@@ -0,0 +1,134 @@
1
+ module CodeStats::Languages
2
+ class Abstract
3
+ attr_reader :text
4
+
5
+ def initialize text
6
+ @text = text
7
+ end
8
+
9
+ def self.extensions *args
10
+ if args.size > 0
11
+ @extensions = args
12
+
13
+ @extensions.each{|ext| CodeStats.extensions[ext] = self}
14
+ else
15
+ @extensions ||= []
16
+ end
17
+ end
18
+
19
+ def lines_count
20
+ calculate_basic_statistics
21
+ @lines_count
22
+ end
23
+
24
+ def characters_count
25
+ calculate_basic_statistics
26
+ @characters_count
27
+ end
28
+
29
+ def analyze
30
+ calculate_basic_statistics
31
+ end
32
+
33
+ protected
34
+ def calculate_basic_statistics
35
+ return if @basic_statistics_calculated
36
+ @basic_statistics_calculated = true
37
+
38
+ @lines_count = code.blank? ? 0 : 1
39
+ @characters_count = 0
40
+ code.chars do |c|
41
+ @lines_count += 1 if c == "\n"
42
+ @characters_count += 1 unless c =~ /\s/
43
+ end
44
+ end
45
+ end
46
+
47
+ module CComments
48
+ def code
49
+ @code ||= text.substitute(/\/\*.+?\*\/\n?/m, '').substitute(/^\/\/.+?[\n\z]/m, '')
50
+ end
51
+ end
52
+
53
+ module SharpComments
54
+ def code
55
+ @code ||= text.substitute(/^#.+?[\n\z]/m, '')
56
+ end
57
+ end
58
+
59
+ class Java < Abstract
60
+ include CComments
61
+
62
+ extensions :java
63
+ end
64
+
65
+ class JavaScript < Abstract
66
+ include CComments
67
+
68
+ extensions :js
69
+ end
70
+
71
+ class Ruby < Abstract
72
+ include SharpComments
73
+
74
+ extensions :rb
75
+ end
76
+
77
+ class Cpp < Abstract
78
+ include CComments
79
+
80
+ extensions :c, :cpp, :h
81
+ end
82
+
83
+ class Yaml < Abstract
84
+ include SharpComments
85
+
86
+ extensions :yml
87
+ end
88
+
89
+ class CoffeeScript < Abstract
90
+ include SharpComments
91
+
92
+ extensions :coffee
93
+
94
+ def code
95
+ @code ||= text.substitute(/###.+?###\n?/m, '').substitute(/^#.+?[\n\z]/m, '')
96
+ end
97
+ end
98
+
99
+ class Haml < Abstract
100
+ extensions :haml
101
+
102
+ def code
103
+ @code ||= text.substitute(/^\/.+?[\n\z]/m, '')
104
+ end
105
+ end
106
+
107
+ class Erb < Abstract
108
+ extensions :erb
109
+
110
+ def code
111
+ @code ||= text
112
+ end
113
+ end
114
+
115
+ class Rjs < Abstract
116
+ extensions :rjs
117
+
118
+ def code
119
+ @code ||= text
120
+ end
121
+ end
122
+
123
+ class Php < Abstract
124
+ include CComments
125
+
126
+ extensions :php
127
+ end
128
+
129
+ class Python < Abstract
130
+ include SharpComments
131
+
132
+ extensions :py
133
+ end
134
+ end
@@ -0,0 +1,55 @@
1
+ class CodeStats::Project
2
+ attr_reader :path, :name, :specs_filter, :skip_filter, :unknown_extensions
3
+ attr_reader :sources, :specs
4
+
5
+ AVAILIABLE_OPTIONS = [:specs_filter, :skip_filter]
6
+ DEFAUL_SPEC_FILTER = /\/tests?\/|\/specs?\//
7
+ DEFAUL_SKIP_FILTER = /\/docs?\/|\/(s|ex)amples?\/|\/guides?\/|\/.git\/|\/tmp\/|database\.php/
8
+
9
+ def initialize path, options = {}
10
+ @path, @name = path, path.to_entry.name
11
+ options.validate_options! *AVAILIABLE_OPTIONS
12
+ @specs_filter = options.include?(:specs_filter) ? options[:specs_filter] : DEFAUL_SPEC_FILTER
13
+ @skip_filter = options.include?(:skip_filter) ? options[:skip_filter] : DEFAUL_SKIP_FILTER
14
+ clear
15
+ end
16
+
17
+ def files &b
18
+ files = []
19
+ path.to_dir.files '**/*.*' do |file|
20
+ next if skip?(file)
21
+ b ? b.call(file) : files.push(file)
22
+ end
23
+ b ? nil : files
24
+ end
25
+
26
+ delegate :lines_count, :characters_count, :lines_count_by_language, :characters_count_by_language, to: :sources
27
+
28
+ def analyze!
29
+ clear
30
+ files do |file|
31
+ ext = file.extension.to_sym
32
+ if CodeStats.know?(ext)
33
+ warn("file #{file} is too big, skipping") and next if file.size > CodeStats.file_size_limit
34
+ script = CodeStats.parse file.read.force_utf8_encoding, ext
35
+ source?(file) ? sources.add(script) : specs.add(script)
36
+ else
37
+ unknown_extensions << ext unless unknown_extensions.include? ext
38
+ end
39
+ end
40
+ end
41
+
42
+ protected
43
+ def clear
44
+ @unknown_extensions = []
45
+ @sources, @specs = CodeStats::FileSet.new, CodeStats::FileSet.new
46
+ end
47
+
48
+ def source? file
49
+ !(specs_filter && (file.path.sub(path, '') =~ specs_filter))
50
+ end
51
+
52
+ def skip? file
53
+ skip_filter && (file.path.sub(path, '') =~ skip_filter)
54
+ end
55
+ end
@@ -0,0 +1,59 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %style{type: 'text/css'}
5
+ = css
6
+
7
+ / Ignore all this JS stuff, it's just some JS libraries explicitly embedded into single HTML file
8
+ %script{type: 'text/javascript'}
9
+ = js
10
+ %body
11
+ - print_by_lang = -> counts {counts.to_a.sort{|a, b| b[1] <=> a[1]}.collect{|lang, count| "#{lang}: #{count.to_s_with_delimiter}"}.join('<br/>')}
12
+
13
+ %h1 Projects Statistics
14
+
15
+ #plot.plot
16
+
17
+ %h1 Details
18
+
19
+ %script{type: 'text/javascript'}
20
+ - projects_js = projects.collect{|p| "['#{p.name}', #{p.characters_count}, #{p.specs.characters_count}]"}.join(",\n")
21
+ Report.plot('#chart', [
22
+ = projects_js
23
+ ]);
24
+
25
+ %table.report
26
+ %thead
27
+ %tr
28
+ %th Name
29
+
30
+ %th Chars
31
+ %th By Lang
32
+ - unless filters.empty?
33
+ %th Ignored
34
+
35
+ %th Spec Chars
36
+ %th Spec by Lang
37
+ - unless filters.empty?
38
+ %th Spec Ignored
39
+
40
+ %th Unknown
41
+ %tbody
42
+ - projects.each do |p|
43
+ %tr
44
+ %td= p.name
45
+
46
+ %td.nowrap.bold= p.characters_count.to_s_with_delimiter
47
+ %td.nowrap= print_by_lang.call p.characters_count_by_language
48
+ - unless filters.empty?
49
+ %td.nowrap= print_by_lang.call p.ignored_characters_count_by_language
50
+
51
+ %td.nowrap.bold= p.specs.characters_count.to_s_with_delimiter
52
+ %td.nowrap= print_by_lang.call p.specs.characters_count_by_language
53
+ - unless filters.empty?
54
+ %td.nowrap= print_by_lang.call p.specs.ignored_characters_count_by_language
55
+
56
+ %td= p.unknown_extensions.sort.join(', ')
57
+
58
+ %p.powered
59
+ Powered by <a href='https://github.com/alexeypetrushin/code_stats'>code_stat</a>
@@ -0,0 +1,42 @@
1
+ class CodeStats::Report
2
+ attr_reader :filters, :projects
3
+ def initialize *args
4
+ @filters = args.extract_options!
5
+ @projects = args
6
+ end
7
+
8
+ def render
9
+ # preparing data
10
+ data = projects.clone
11
+ data.sort!{|a, b| b.characters_count(filters) <=> a.characters_count(filters)}
12
+ data.collect! do |p|
13
+ o = OpenObject.new
14
+ o.name = p.name
15
+
16
+ o.characters_count = p.characters_count(filters)
17
+ o.characters_count_by_language = p.characters_count_by_language(filters)
18
+ o.ignored_characters_count_by_language = p.characters_count_by_language.reject do |lang, count|
19
+ o.characters_count_by_language.include?(lang)
20
+ end
21
+
22
+ o.specs = OpenObject.new
23
+ o.specs.characters_count = p.specs.characters_count(filters)
24
+ o.specs.characters_count_by_language = p.specs.characters_count_by_language(filters)
25
+ o.specs.ignored_characters_count_by_language = p.specs.characters_count_by_language.reject do |lang, count|
26
+ o.specs.characters_count_by_language.include?(lang)
27
+ end
28
+
29
+ o.unknown_extensions = p.unknown_extensions
30
+
31
+ o
32
+ end
33
+
34
+ # rendering
35
+ dir = __FILE__.dirname.to_dir
36
+ css = [dir['report/style.css'].read].join("\n")
37
+ js = %w(jquery.js highchart.js report.js).collect{|n| dir["report/#{n}"].read}.join("\n")
38
+
39
+ report = dir / 'report.html.haml'
40
+ report.render(projects: data, filters: filters, css: css, js: js)
41
+ end
42
+ end