reviser 0.0.1.1.pre.beta

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.
@@ -0,0 +1,47 @@
1
+ require_relative 'generators'
2
+
3
+
4
+ module Components
5
+
6
+ # Generator is used to create a file result after the analysis.
7
+ # Currently, Generator supports HTML, XLS and CSV format.
8
+ #
9
+ # @author Renan Strauss
10
+ # @author Yann Prono
11
+ #
12
+ class Generator < Component
13
+
14
+ # Include all supported formats
15
+ include Generators
16
+
17
+ def initialize(data)
18
+ super data
19
+ end
20
+
21
+ # Run the generation of results file in all asked formats by user.
22
+ def run
23
+ begin
24
+ if Cfg[:out_format].respond_to? 'each'
25
+ Cfg[:out_format].each { |f| send f.to_sym }
26
+ else
27
+ send Cfg[:out_format].to_sym
28
+ end
29
+ rescue Object => e
30
+ @logger.h1 Logger::FATAL, "Wrong format : #{e.to_s}"
31
+ end
32
+ end
33
+
34
+ # Get all criterias of marking
35
+ # used to display informations in documents.
36
+ # @return [Array] Array with all criterias.
37
+ def criterias
38
+ @data.values.first.keys.unshift.map! { |cri| Generator.titleize(cri.to_s) }
39
+ end
40
+
41
+ # Quite handy
42
+ def self.titleize(str)
43
+ str.split(/_/).join(' ').capitalize
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,130 @@
1
+
2
+ # Module containing all methods for writing results.
3
+ #
4
+ # Convention over configuration !
5
+ #
6
+ # To add a new format, you need maybe to install a gem.
7
+ # Find a gem which supports a specified format on rubygems.org.
8
+ # Add the line "gem <gem>" in the Gemfile and execute "bundle install"
9
+ #
10
+ # Now, you can write the method corresponding to the format.
11
+ # The name of the method corresponds to the format.
12
+ # For example, if you want to generate a word file (.doc), the name of the method will be: "doc"
13
+ # Don't forget to require the gem: "require <gem>" at the beginning of the method !
14
+ # the header of method looks like the following block:
15
+ #
16
+ # def <format> (ext = '<format>')
17
+ # require <gem>
18
+ # ...
19
+ # end
20
+ #
21
+ # To write results, you have to go through in the data instance variable.
22
+ # data is a [Hash]:
23
+ # - key: The person's name.
24
+ # - value: Results of analysis
25
+ #
26
+ # Each value of data is also a [Hash]:
27
+ # - key: the name of criterion checked.
28
+ # - value: The result of criterion.
29
+ #
30
+ #
31
+ # @author Renan Strauss
32
+ # @author Yann Prono
33
+ #
34
+ module Components
35
+ module Generators
36
+
37
+ # Generates the CSV file
38
+ def csv(ext = '.csv')
39
+ require 'csv'
40
+ CSV.open(out(ext), 'wb') do |f|
41
+ # Criterias as columns
42
+ f << (criterias).unshift('projet')
43
+
44
+ # Values for each project as rows
45
+ @data.keys.each do |proj|
46
+ f << @data[proj].values.unshift(proj)
47
+ end
48
+ end
49
+ end
50
+
51
+ # Generates a Excel file
52
+ def xls(ext = '.xls')
53
+ require 'spreadsheet'
54
+ Spreadsheet.client_encoding = 'UTF-8'
55
+ book = Spreadsheet::Workbook.new
56
+ sheet = book.create_worksheet :name => 'Results'
57
+
58
+ # header
59
+ format = Spreadsheet::Format.new :weight => :bold, :size => 14 ,
60
+ :horizontal_align => :center
61
+
62
+ (criterias.unshift('Projets')).each_with_index do |crit, i|
63
+ sheet[0,i] = crit
64
+ sheet.column(i).width = (crit.size * format.font.size/10) + 5
65
+ end
66
+ sheet.row(0).default_format = format
67
+ sheet.row(0).height = 18
68
+
69
+ # Values for each project as rows
70
+ @data.keys.each_with_index do |proj, i|
71
+ sheet.insert_row(i+1,@data[proj].values.unshift(proj))
72
+ end
73
+
74
+ book.write out(ext)
75
+ end
76
+
77
+ # Generates an HTML file
78
+ def html(ext = '.html')
79
+ out = '<!DOCTYPE html><html><head>'
80
+ out += '<meta charset= "UTF-8">'
81
+ out += "<link rel=\"stylesheet\" href=\"#{Cfg[:res_dir]}/css/component.css\" />"
82
+ out += "<link rel=\"stylesheet\" href=\"#{Cfg[:res_dir]}/css/normalize.css\" />"
83
+ out += '<script src="res/js/component.css"></script>'
84
+ out += '<title>Results</title>'
85
+ out += "</head>\n<body><table><thead>"
86
+ out += ' <tr>'
87
+
88
+ criterias.unshift('Projet').each { |crit| out += "<th>#{crit}</th>" }
89
+
90
+ out += '</tr></thead><tbody>'
91
+ # Values for each project as rows
92
+ @data.keys.each do |proj|
93
+ out += "<tr><th>#{proj}</th>"
94
+ @data[proj].each do |k, v|
95
+ if k.to_s[/(compilation|execution)/]
96
+ out += '<td class="console">'
97
+ else
98
+ out += '<td>'
99
+ end
100
+
101
+ # If file, generate a link, else do nothing !
102
+ out += file?(v) && "<pre><a href=\"#{v.gsub(' ','%20')}\" target=\"_blank\">#{v}</a></pre></td>" ||"<pre>#{v}</pre></td>"
103
+
104
+ end
105
+ out += '</tr>'
106
+ end
107
+
108
+ out += '</tbody></table>'
109
+
110
+ out += '<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>'
111
+ out += '<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-throttle-debounce/1.1/jquery.ba-throttle-debounce.min.js"></script>'
112
+ #out += "<script src=\"#{Cfg[:res_dir]}/js/jquery.stickyheader.js\"></script>"
113
+
114
+ out += '</body></html>'
115
+
116
+ File.open(out(ext), 'w') { |f| f.write(out) }
117
+ end
118
+
119
+ private
120
+
121
+ def out(ext)
122
+ Cfg[:out] + ext
123
+ end
124
+
125
+ def file? path
126
+ File.exist? path.to_s
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,181 @@
1
+ require 'fileutils'
2
+ require_relative '../helpers/git'
3
+ require_relative '../project'
4
+
5
+
6
+ module Components
7
+
8
+ # Class which organizes all directories to simplify projects' analysis.
9
+ # Organiser renames projects folders and organises the whole of projects
10
+ # in order to have a structured folder (files at the root of folder)
11
+ # During this step, a git repository will be created, with an initial commit.
12
+ #
13
+ # @author Yann Prono
14
+ # @author Renan Strauss
15
+ #
16
+ class Organiser < Component
17
+
18
+ # Include all tools
19
+ include Helpers::Git
20
+ include Project
21
+
22
+ # All entries to ignore during sort and organization
23
+ $rejected_entries = ['.', '..', '__MACOSX']
24
+
25
+ # initialize tool.
26
+ # Organiser has to :
27
+ # - get all students
28
+ # - get all groups (classes)
29
+ # - get all teams (binoms)
30
+ # - get all unknown soldiers ...
31
+ def initialize(data)
32
+ raise ArgumentError if data == nil || !data.respond_to?('each')
33
+
34
+ super data
35
+
36
+ @directory = Cfg[:dest]
37
+ @path = @directory
38
+ @git = nil
39
+ @students = []
40
+ @binoms = []
41
+ @groups = []
42
+ @unknown = []
43
+
44
+ # How many patterns are in the pseudo-regex?
45
+ @count_patterns = {}
46
+ end
47
+
48
+ # Rename directories more clearly.
49
+ # @param entry [String] path of the entry to rename.
50
+ def rename(entry)
51
+ name = format entry
52
+ if name != nil
53
+ if name != entry
54
+ new_path = File.join(@directory, name)
55
+ FileUtils.mkdir_p new_path.split(File.basename(new_path))[0]
56
+ FileUtils.mv(File.join(@directory, entry), new_path, :force => true)
57
+
58
+ @logger.h2 Logger::INFO, "renaming #{File.basename(entry)} to #{File.basename(name)}"
59
+ else
60
+ @logger.h2 Logger::INFO, "#{entry} has not been renamed}, already formatted"
61
+ end
62
+ else
63
+ @logger.h2 Logger::ERROR, "Can't rename #{File.basename(entry)} - Datas not found in name"
64
+ end
65
+ end
66
+
67
+ # Method which moves project's directories in order to
68
+ # have the same hierarchy for all project.
69
+ # @param entry [String] path of the entry to structure.
70
+ def structure(entry)
71
+ chdir entry
72
+ @logger.h2 Logger::INFO, "#{entry} => #{@path}"
73
+ level = 0
74
+ @logger.h2 Logger::INFO, "Files in #{@path}"
75
+ @logger.h3 Logger::INFO, "#{all}"
76
+
77
+ @logger.h2 Logger::INFO, "Dirs in #{@path}"
78
+ @logger.h3 Logger::INFO, "#{directories}"
79
+ # directory to delete if the project directory is not structured
80
+ rm = directories.first
81
+
82
+ # Loop to find the core of project
83
+ #
84
+ # Basically running through
85
+ # each level of directories
86
+ # while there are only directories
87
+ # in the current directory
88
+ #
89
+ while all == directories
90
+ level += 1
91
+ @logger.h2 Logger::DEBUG, "Level += 1\nPath = #{@path}"
92
+ chdir directories.first
93
+ @logger.h2 Logger::DEBUG, "New path = #{@path}"
94
+ end
95
+
96
+ # If the core of project is not at the root of directory ...
97
+ if level >= 1
98
+ Dir.glob(File.join(@path,'*')).each do |file|
99
+ FileUtils.mv(file,File.join(@directory, entry))
100
+ end
101
+ @logger.h2 Logger::INFO, "Structuring #{File.join(@path)}"
102
+ @logger.h2 Logger::INFO, "Removing #{File.join(@directory, entry, rm)}"
103
+ FileUtils.rm_rf(File.join(@directory, entry, rm))
104
+ end
105
+
106
+ @path = @directory
107
+ end
108
+
109
+ # Initialize a git repo.
110
+ # @param entry [String] Directory to process.
111
+ def git(entry)
112
+ Dir.chdir File.join(@directory, entry) do
113
+ git_init
114
+ git_add
115
+ git_commit
116
+ end
117
+ end
118
+
119
+
120
+ # Method which run the organiser.
121
+ # It will apply all importants methods of this class for each project.
122
+ def run
123
+ @data.each do |entry|
124
+ @logger.h1 Logger::INFO, "Work on #{entry}"
125
+ @logger.h1 Logger::INFO, "Structure project"
126
+ structure entry
127
+
128
+ @logger.h1 Logger::INFO, "Initializing git repo"
129
+ git entry
130
+
131
+ @logger.h1 Logger::INFO, "Renaming directory"
132
+ rename entry
133
+ @logger.newline
134
+ end
135
+
136
+ @logger.h1 Logger::INFO, "#{@groups.size} group#{'s' if @groups.size > 1} have been detected"
137
+ @logger.h1 Logger::INFO, "#{@students.size} student#{'s' if @students.size > 1} have been detected"
138
+ @logger.h1 Logger::INFO, "#{@binoms.size} binom#{'s' if @binoms.size > 1} have been detected"
139
+
140
+ log_resume(@groups, Logger::INFO, "Groups:")
141
+ log_resume(@students, Logger::INFO, "Students:")
142
+ log_resume(@binoms, Logger::INFO, "Binoms:")
143
+
144
+ log_resume(@unknown, Logger::ERROR, "\n#{@unknown.size} projects didn't matched with regex")
145
+ end
146
+
147
+ private
148
+ #
149
+ # Take attention : these accessors
150
+ # are meant to be used by structure
151
+ # only ! Because @path is initialized
152
+ # for that, and used only in this def
153
+ #
154
+ def all
155
+ Dir.entries(@path) - $rejected_entries
156
+ end
157
+
158
+ def directories
159
+ all.select { |e| File.directory? File.join(@path, e) }
160
+ end
161
+
162
+ def chdir(dir)
163
+ base = @path
164
+ @path = File.join(base, dir)
165
+ end
166
+
167
+
168
+ # Shortuct for logs ...
169
+ # @param data [Array] to loop.
170
+ # @param severity [Integer] Severity of the log.
171
+ # @param message [String] Message to log before writing data..
172
+ def log_resume(data ,severity, message)
173
+ unless data.empty?
174
+ @logger.newline
175
+ @logger.h1 severity, message
176
+ data.each {|d| @logger.h2 severity, "#{d}" }
177
+ end
178
+ end
179
+
180
+ end
181
+ end
data/lib/config.rb ADDED
@@ -0,0 +1,48 @@
1
+ #
2
+ # @author Renan Strauss
3
+ # Externalises the configuration
4
+ #
5
+ require 'yaml'
6
+
7
+ class Cfg
8
+ # Path for specialized config files for projects
9
+ ROOT = File.join(File.dirname(File.dirname(__FILE__)))
10
+
11
+ # Is the config is loaded ?
12
+ @@loaded = false
13
+
14
+ def self.[](key)
15
+ @@mem[key] if @@loaded
16
+ end
17
+
18
+ # @return true if there is the key in the config
19
+ def self.has_key?(key)
20
+ @@mem.has_key? key
21
+ end
22
+
23
+ # Method class alias
24
+ # might remove this at some point ( sorry Yannou I know u worked hard :( )
25
+ self.singleton_class.send(:alias_method, :=~, :has_key?)
26
+
27
+ def self.load(cfg_file)
28
+ @@loaded = true
29
+ @@mem = {}
30
+
31
+ populate YAML.load(File.read(cfg_file))
32
+ type_file = File.join(ROOT,'type',"#{@@mem[:type]}.yml")
33
+ type_cfg = YAML.load(File.read(type_file))
34
+ populate YAML.load(File.read(File.join(ROOT,'lang',"#{type_cfg['language']}.yml")))
35
+ # So that project's type Cfg overrides
36
+ # lang Cfg
37
+ populate type_cfg
38
+ end
39
+
40
+ private
41
+ #
42
+ # Handy method to convert string keys
43
+ # read from Cfg file to symbols
44
+ #
45
+ def self.populate(hash)
46
+ hash.each { |k, v| @@mem[k.to_sym] = v}
47
+ end
48
+ end
data/lib/exec.rb ADDED
@@ -0,0 +1,136 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+
4
+ require_relative 'reviser'
5
+ require_relative 'helpers/criteria'
6
+
7
+ #
8
+ # Module used for managing all actions in command line
9
+ # This module enables to user the programm in command line.
10
+ # It use the powerful toolkit Thor for building command line interfaces
11
+ #
12
+ # @author Yann Prono
13
+ #
14
+ class Exec < Thor
15
+
16
+ @@setup = false
17
+
18
+ # path of config template file.
19
+ $template_path = File.join(File.dirname(File.dirname(__FILE__)),'config.yml')
20
+
21
+ def initialize(*args)
22
+ super
23
+ # If config.yml already exists in the working
24
+ # directory, then we setup reviser here
25
+ config_file = File.expand_path('config.yml')
26
+ setup config_file if File.exist? config_file
27
+ end
28
+
29
+
30
+ # Say hello to the user !
31
+ desc 'hello','Say hello to the user !'
32
+ def hello
33
+ puts 'Hello, I am reviser'
34
+ end
35
+
36
+
37
+ # Create a environnment for checking projects
38
+ # This method only copies the config file into the current directory.
39
+ desc 'init DIRECTORY', 'Create a new App project. By default,DIRECTORY is the current.'
40
+ def init(dir = '.')
41
+ pwd = FileUtils.pwd
42
+ msg = File.exist?(File.join(pwd,dir,File.basename($template_path))) && 'Recreate' || 'Create'
43
+ FileUtils.mkdir_p dir unless Dir.exist?(File.join(pwd, dir))
44
+ FileUtils.cp($template_path, dir)
45
+ message(msg, File.basename($template_path))
46
+
47
+ setup File.expand_path(File.join(dir, File.basename($template_path))) unless @@setup
48
+ end
49
+
50
+
51
+ # Clean the directory of logs, projects and results.
52
+ desc 'clean', 'Delete datas creating by the App (logs, projects, results files ...).'
53
+ def clean
54
+ if File.exist? 'config.yml'
55
+ FileUtils.rm_rf(Cfg[:dest], :verbose => true)
56
+ if Cfg.has_key?(:options) && Cfg[:options].has_key?(:log_dir)
57
+ FileUtils.rm_rf(Cfg[:options][:log_dir], :verbose => true)
58
+ else
59
+ FileUtils.rm_f(Dir['*.txt'], :verbose => true)
60
+ end
61
+
62
+ if Cfg[:out_format].respond_to? 'each'
63
+ Cfg[:out_format].each { |format| FileUtils.rm_f(Dir["*.#{format}"], :verbose => true) }
64
+ else
65
+ FileUtils.rm_f(Dir["*.#{Cfg[:out_format]}"], :verbose => true)
66
+ end
67
+
68
+ FileUtils.rm_rf(Cfg[:res_dir], :verbose => true)
69
+ else
70
+ message("Error", "'config.yml' doesn't exist! Check if you are in the good directory.")
71
+ end
72
+
73
+ end
74
+
75
+
76
+ # Let do it for analysis.
77
+ # @param current_dir [String] the directory where the programm has to be launched.
78
+ desc 'work', 'Run components to analysis computing projects.'
79
+ def work
80
+ Reviser::load :component => 'archiver'
81
+ Reviser::load :component => 'organiser', :inputFrom => 'archiver'
82
+ Reviser::load :component => 'checker', :inputFrom => 'organiser'
83
+ Reviser::load :component => 'generator', :inputFrom => 'checker'
84
+
85
+ Reviser::run
86
+ end
87
+
88
+ # Launch archiver !
89
+ desc 'extract', 'Launch archiver and extract all projects.'
90
+ def extract
91
+ Reviser::load :component => 'archiver'
92
+ Reviser::run
93
+ end
94
+
95
+ # Launch organiser !
96
+ desc 'organise', 'Launch organiser and prepare all projects for analysis'
97
+ def organise
98
+ Reviser::load :component => 'organiser'
99
+ Reviser::run
100
+ end
101
+
102
+ # Launch checker and generator as well !
103
+ desc 'check', 'Launch checker for analysis and generate results.'
104
+ def check
105
+ Reviser::load :component => 'checker'
106
+ Reviser::load :component => 'generator', :inputFrom => 'checker'
107
+ Reviser::run
108
+ end
109
+
110
+ # For the moment, associate a label to a criterion (method).
111
+ desc 'add METH "LABEL" ', 'Add to the method METH a associated LABEL'
112
+ def add meth, label
113
+ res = Criteria::Labels.add meth, label
114
+ message "#{res} label",meth + " => " + label
115
+ end
116
+
117
+
118
+ no_tasks do
119
+ # A Formatter message for command line
120
+ def message(keyword, desc)
121
+ puts "\t#{keyword}\t\t#{desc}"
122
+ end
123
+
124
+ def setup(config_file)
125
+ Reviser::setup config_file
126
+
127
+ path_res = File.join(File.dirname(File.dirname(__FILE__)),"#{Cfg[:res_dir]}")
128
+ FileUtils.cp_r(path_res, FileUtils.pwd)
129
+
130
+ @@setup = true
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ Exec.start(ARGV)
@@ -0,0 +1,64 @@
1
+ #
2
+ # @Author Renan Strauss
3
+ #
4
+ # Basic stuff needed for Checker
5
+ #
6
+
7
+ module Helpers
8
+ module CodeAnalysis
9
+
10
+ def all_files
11
+ files.join("\r")
12
+ end
13
+
14
+ #
15
+ # @return all files matching the
16
+ # extenstion language list (note that Cfg[:extension] must be an array)
17
+ #
18
+ def src_files
19
+ sources.join("\r")
20
+ end
21
+
22
+ #
23
+ # @return the total amount of lines of code
24
+ #
25
+ def lines_count
26
+ count = sources.inject(0) { |sum, f|
27
+ sum + File.open(f).readlines.select { |l| !l.chomp.empty? }.size
28
+ }
29
+
30
+ count - comments_count # FIXME
31
+ end
32
+
33
+ #
34
+ # @return the number of lines of comments
35
+ #
36
+ def comments_count
37
+ tab_comments = sources.inject([]) { |t, f| t << IO.read(f).scrub.scan(Cfg[:regex_comments]) }
38
+ lines = tab_comments.inject('') { |s, comm| s << find_comments(comm) }.split "\n"
39
+
40
+ lines.size
41
+ end
42
+
43
+ private
44
+
45
+ #
46
+ # @return all the files in the project's folder
47
+ #
48
+ def files
49
+ Dir.glob("**/*").select { |f| (File.file?(f)) }
50
+ end
51
+
52
+ def sources
53
+ files.select { |f| Cfg[:extension].include? File.extname(f) }
54
+ end
55
+
56
+ #
57
+ # Translates a sub-match returned by scan
58
+ # into raw comments string
59
+ #
60
+ def find_comments(comm)
61
+ comm.inject('') { |t, l| t << l.detect { |a| (a != nil) && !a.strip.empty? } + "\n" }
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # @Author Yann Prono
3
+ # @Author Renan Strauss
4
+ #
5
+ # Needed stuff for compiled languages
6
+ # such as C, Java, and so on.
7
+ #
8
+
9
+ module Helpers
10
+ module Compilation
11
+ include Project
12
+ include System
13
+
14
+ #
15
+ # Only here for compiled language,
16
+ #
17
+ def compile
18
+ #
19
+ # For now, we compile only if there's
20
+ # no missing file
21
+ # We should maybe make it more
22
+ # understandable in the Cfg
23
+ #
24
+ if missing_files.empty?
25
+ cmd = "#{Cfg[(Cfg.has_key? :preferred_build_command) && :preferred_build_command || :default_build_command]}"
26
+ out = exec_with_timeout cmd
27
+
28
+ if out.has_key? :process_status
29
+ return "Exit status: 0\r#{out[:stdout]}" unless out[:process_status].exitstatus != 0
30
+ end
31
+
32
+ if Cfg.has_key? :preferred_build_command
33
+ out = exec_with_timeout Cfg[:default_build_command]
34
+ end
35
+
36
+ (out[:process_status].exitstatus == 0) ? "Exit status: 0\r#{out[:stdout]}" : "#{out[:stdout]}\r#{out[:stderr]}"
37
+ end
38
+ end
39
+ end
40
+ end