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,186 @@
1
+ require_relative '../config'
2
+
3
+ # Manage criteria and labels.
4
+
5
+ # @example Call a criterion (in the config File):
6
+ # criteria:
7
+ # - :count_lines: Number of lines
8
+ # - :list_files: List of all files
9
+ # - :<method>: <label of method>
10
+ #
11
+ # @author Yann Prono
12
+ # @author Renan Strauss
13
+ #
14
+ module Helpers
15
+ # This module enables to
16
+ # imports automaticlly all modules for the analysis
17
+ #
18
+ # Convention over configuration !
19
+ # A analysis module contains the word 'tool' in its filename.
20
+ # You also have the possibility to put code in the ext folder.
21
+ #
22
+ # @example Call a criterion during analysis (in the config File):
23
+ # criteria:
24
+ # - :count_lines
25
+ # - :list_files
26
+ # - :<method>: <custom label>
27
+ #
28
+ # In the last item of the list, the custom label will overwrite the label
29
+ # in labels.yml if it exist.
30
+ #
31
+ module Criteria
32
+
33
+ # Where I am ?
34
+ PWD = File.dirname __FILE__
35
+ # Path of extensions
36
+ EXT = File.join File.dirname(File.dirname(PWD)), 'ext'
37
+
38
+ attr_reader :criteria
39
+ attr_reader :output
40
+
41
+ # All criterias available.
42
+ # :criterion => Name of the module
43
+ @criteria
44
+
45
+ # :criterion => label of criterion
46
+ @output
47
+
48
+ # Enable to call a specified method.
49
+ # @param meth [String] Method to call.
50
+ # @return results of the method.
51
+ def call meth
52
+ if @criteria.key? meth
53
+ @logger.h1(Logger::INFO, "Include methods of #{@criteria[meth]}") unless respond_to? meth
54
+ self.class.send(:include, @criteria[meth]) unless respond_to? meth
55
+
56
+ send meth
57
+ else
58
+ nil
59
+ end
60
+ end
61
+
62
+
63
+ protected
64
+
65
+ # Get all criteria which can be used.
66
+ # @return [Array] all criteria
67
+ def all
68
+ @criteria.keys.map &:to_sym
69
+ end
70
+
71
+ # from Cfg file to symbols
72
+ # @param criterion The criteria
73
+ # @param module_name The name of the module.
74
+ def populate criterion, module_name
75
+ raise "Criterion '#{criterion}' is already defined in #{@criteria[criterion.to_sym]} (#{criterion}/#{module_name}).\nPlease change the name of the method in one of modules." if @criteria.has_key? criterion.to_sym
76
+ @criteria[criterion.to_sym] = module_name
77
+ end
78
+
79
+ # Load all of modules available for the analysis
80
+ # @param directory Directory where search of modules is done.
81
+ # @param regex regex to find name of modules.
82
+ def load directory, regex = '*'
83
+ @logger.h2 Logger::INFO, "Modules of #{directory}"
84
+ modules = Dir[File.join(directory, regex)]
85
+
86
+ namespace = directory == EXT && 'Extensions' || 'Helpers'
87
+ modules.each do |m|
88
+ next if m =~ /(criteria)/
89
+
90
+ require_relative m
91
+ ext = File.extname m
92
+ module_name = Object.const_get "#{namespace}::#{camelize(File.basename(m,ext))}", false
93
+ @logger.h3 Logger::INFO, "Load #{module_name}"
94
+ methods = module_name.instance_methods false
95
+ methods.each { |method| populate(method, module_name) }
96
+ end
97
+ end
98
+
99
+ # Gets the name of module
100
+ # @param file_module Name of the file module.
101
+ def camelize file_module
102
+ file_module.split('_').each {|s| s.capitalize! }.join('')
103
+ end
104
+
105
+ # Load labels given by the user.
106
+ # If the label doesn't exist, it will created with the name of the method.
107
+ # @param key Key of criteria in config file
108
+ def load_labels key
109
+ labels = Labels.load
110
+
111
+ if Cfg.has_key?(key) && Cfg[key].respond_to?('each')
112
+ Cfg[key].each do |meth|
113
+ if meth.respond_to?('each') && meth.respond_to?('[]')
114
+ label = meth[meth.keys[0]]
115
+ @logger.h2(Logger::ERROR, "Undefined label for #{meth.keys[0]}, check your config file") if label == nil
116
+ label = create_label(meth.keys[0]) if label == nil
117
+ @output[meth.keys[0].to_sym] = label
118
+ else
119
+ label = (labels.respond_to?('[]') && labels.key?(meth.to_sym)) ? labels[meth.to_sym] : create_label(meth)
120
+ @output[meth.to_sym] = label
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ # Create label for a method.
127
+ # @param meth [String] method linked to the label
128
+ # @return [String] Renamed Label inspired of the name of the method
129
+ def create_label meth
130
+ @logger.h2 Logger::ERROR, "Create label for #{meth}. You should custom your label (see 'reviser add')"
131
+ meth.to_s.split('_').each {|s| s.capitalize! }.join(' ')
132
+ end
133
+ end
134
+
135
+
136
+ # Manage all actions for adding, updating or getting labels of Reviser.
137
+ # A label is a a group of words, describing the associated criterion (method).
138
+ #
139
+ # @example
140
+ # criterion => label
141
+ # all_files => all files of project
142
+ #
143
+ # known Labels are in the labels.yml file.
144
+ #
145
+ # @author Yann Prono
146
+ class Labels
147
+
148
+ # Current directory of this file
149
+ PWD = File.dirname __FILE__
150
+
151
+ # Path of label.yml file
152
+ LABELS = File.join(File.dirname(File.dirname(PWD)), 'labels.yml')
153
+
154
+ #
155
+ # Enable to associate a label to a criterion (method).
156
+ # The label will be saved in the 'labels.yml' file
157
+ # @param meth Method to link.
158
+ # @param label Label to link with the method.
159
+ def self.add meth, label
160
+ res = "Create"
161
+ labels = YAML.load File.open(LABELS)
162
+ if labels.respond_to? '[]'
163
+ res = "Update" if labels.key? meth
164
+ labels[meth] = label
165
+ File.open(LABELS, 'w') { |f| f.write labels.to_yaml }
166
+ end
167
+ res
168
+ end
169
+
170
+ # @return Hash all known labels by reviser.
171
+ # :criterion => label
172
+ def self.load
173
+ Labels.populate(YAML.load(File.open(LABELS)))
174
+ end
175
+
176
+ def self.populate hash
177
+ labels = {}
178
+ if hash.respond_to?('each')
179
+ hash.each do |meth, label|
180
+ labels[meth.to_sym] = label
181
+ end
182
+ end
183
+ labels
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,83 @@
1
+ #
2
+ # @Author Renan Strauss
3
+ #
4
+ # Needed stuff for Checker
5
+ # when it comes to executing
6
+ # both compiled and interpreted
7
+ # languages
8
+ #
9
+
10
+ require 'timeout'
11
+
12
+ module Helpers
13
+ module Execution
14
+ #
15
+ # Determines how to execute the program
16
+ # thanks to Cfg, then returns its exec
17
+ # status(es)
18
+ #
19
+ def execute
20
+ outputs = []
21
+ if Cfg.has_key? :execution_value
22
+ if Cfg[:execution_value].respond_to? 'each'
23
+ Cfg[:execution_value].each do |v|
24
+ outputs << exec(v)
25
+ end
26
+ else
27
+ if Cfg.has_key? :execution_count
28
+ outputs[Cfg[:execution_value]] = []
29
+ Cfg[:execution_count].times do
30
+ outputs << exec(Cfg[:execution_value])
31
+ end
32
+ else
33
+ outputs << exec(Cfg[:execution_value])
34
+ end
35
+ end
36
+ else
37
+ if Cfg.has_key? :execution_count
38
+ Cfg[:execution_count].times do
39
+ outputs << exec
40
+ end
41
+ else
42
+ return exec
43
+ end
44
+ end
45
+
46
+ outputs.join("\r")
47
+ end
48
+
49
+ private
50
+
51
+ #
52
+ # The method that actually
53
+ # executes the program.
54
+ # If no program name is specified
55
+ # in the Cfg, it executes the
56
+ # first executable found.
57
+ # It helps with C (a.out) when no
58
+ # Makefile is avalaible, but it
59
+ # might not be a good idea regarding
60
+ # security
61
+ #
62
+ def exec(param = nil)
63
+ program = (Cfg.has_key? :program_name) && Cfg[:program_name] || find_executable
64
+
65
+ return 'Program not found' unless program != nil
66
+
67
+ program = "#{Cfg[:program_prefix]}#{program}"
68
+ argument = (param == nil) && '' || param
69
+
70
+ cmd = "#{(Cfg.has_key? :execute_command) && Cfg[:execute_command] || ''} #{program} #{argument}"
71
+ out = exec_with_timeout cmd
72
+
73
+ "$ #{cmd}\r#{out[:stdout]}\r#{out[:stderr]}"
74
+ end
75
+
76
+ #
77
+ # @return the first executable found
78
+ #
79
+ def find_executable
80
+ Dir.glob('*').select {|f| File.executable?(f) && !File.directory?(f)}.first
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,37 @@
1
+ # Class which organizes all directories for use them with git.
2
+ # Its another component of the project
3
+ #
4
+ # @author Romain Ruez
5
+ # @author Anthony Cerf
6
+ #
7
+ require 'git'
8
+
9
+ module Helpers
10
+ module Git
11
+ # method which initialize a git repository
12
+ def git_init
13
+ @git = ::Git.init
14
+ end
15
+
16
+ # method which allows the user to add something on the repository
17
+ def git_add
18
+ @git.add(:all=>true)
19
+ end
20
+
21
+ # method for displaying a message when the repository is configured
22
+ def git_commit
23
+ @git.commit_all('initialization of git repertory')
24
+ end
25
+
26
+ def git_push
27
+ @git.push
28
+ end
29
+
30
+ # method which allows the user to see the differences between two last commits
31
+ # I have to know the current commit and the last but how ?
32
+ # and do a diff between these 2 commits.
33
+ def git_diff
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Provide important methods
3
+ # for compilation or something like that.
4
+ #
5
+ # @author Renan Strauss
6
+ # @author Yann Prono
7
+ #
8
+ module Helpers
9
+ module Project
10
+ #
11
+ # For interpreted languages
12
+ # We only check for missing files
13
+ #
14
+ def prepare
15
+ missing_files.empty? && 'None' || res
16
+ end
17
+
18
+ # Check if the project has all files needed
19
+ def missing_files
20
+ return [] unless Cfg =~ :required_files
21
+
22
+ dir = Dir['*']
23
+
24
+ #
25
+ # Check if there is any regexp
26
+ # If it's the case, if any file
27
+ # matches, we delete the entry
28
+ # for diff to work properly
29
+ #
30
+ Cfg[:required_files].each_with_index do |e, i|
31
+ if dir.any? { |f| (e.respond_to?(:match)) && (e =~ f) }
32
+ Cfg[:required_files].delete_at i
33
+ end
34
+ end
35
+
36
+ Cfg[:required_files] - dir
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,56 @@
1
+ #
2
+ # @author Renan Strauss
3
+ #
4
+
5
+ module Helpers
6
+ module System
7
+ #
8
+ # Executes the given command
9
+ # and kills it if its execution
10
+ # time > timeout
11
+ # @returns stdout, stderr & process_status
12
+ #
13
+ def exec_with_timeout(cmd, timeout = Cfg[:timeout])
14
+ stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
15
+ process_status = -1
16
+
17
+ stdin.close
18
+ #
19
+ # We try to wait for the thread to join
20
+ # during the given timeout.
21
+ # When the thread has joined, process_status
22
+ # will be an object, so we can check and
23
+ # return at the end if it failed to complete
24
+ # before time runs out.
25
+ #
26
+ begin
27
+ Timeout.timeout(timeout) do
28
+ process_status = wait_thr.value
29
+ end
30
+ rescue Timeout::Error
31
+ #
32
+ # Then whether it suceeded or not,
33
+ # we kill the process
34
+ #
35
+ begin
36
+ Process.kill('KILL', wait_thr[:pid])
37
+ rescue Object => e
38
+ $stderr << "Unable to kill process : #{e.to_s}"
39
+ end
40
+ end
41
+
42
+ result = {
43
+ :stdout => process_status == -1 && 'Timeout' || stdout.read,
44
+ :stderr => process_status == -1 && 'Timeout' || stderr.read,
45
+ :process_status => process_status == -1 && 'Timeout' || process_status
46
+ }
47
+
48
+ result.delete :process_status unless process_status != -1
49
+
50
+ stdout.close
51
+ stderr.close
52
+
53
+ result
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,41 @@
1
+ require 'logger'
2
+ require_relative 'modes'
3
+
4
+ # Custom logger of Reviser.
5
+ # This class is a adapter.
6
+ # We used the standard Logger included in Ruby.
7
+ #
8
+ # @author Yann Prono
9
+ #
10
+ module Loggers
11
+ class Logger
12
+
13
+ # Create logger.
14
+ # The extension determines the mode to use (logger mode).
15
+ # @filename
16
+ def initialize filename
17
+
18
+ ext = File.extname(filename).delete '.'
19
+ # Include mode aksed by user (config file)
20
+ begin
21
+ self.class.send :include, Modes.const_get("#{ext.downcase.capitalize}")
22
+ rescue => e
23
+ self.class.send :include, Modes::Txt
24
+ end
25
+
26
+ @logger = ::Logger.new filename
27
+ @logger.level = ::Logger::DEBUG
28
+ end
29
+
30
+ # Close the logger
31
+ def close
32
+ @logger.close
33
+ end
34
+
35
+ # In case of someone want to use methods of standard Logger ...
36
+ def method_missing(m, *args, &block)
37
+ @logger.send(m,*args, &block)
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,74 @@
1
+ require 'logger'
2
+
3
+ #
4
+ # Modules containing all methods to custom logger.
5
+ #
6
+ # There are 3 main level of logger (as in HTML)
7
+ # => h1
8
+ # => h2
9
+ # => h3
10
+
11
+ # @author Yann Prono
12
+ # @author Anthony Cerf
13
+ module Loggers
14
+ module Modes
15
+
16
+ module Txt
17
+
18
+ include Modes
19
+
20
+ def h1 severity, msg
21
+ change_formatter ''
22
+ @logger.add severity, msg
23
+ end
24
+
25
+ def h2 severity, msg
26
+ change_formatter "\t\t"
27
+ @logger.add severity, msg
28
+ end
29
+
30
+ def h3 severity, msg
31
+ change_formatter "\t\t\t"
32
+ @logger.add severity, msg
33
+ end
34
+ end
35
+
36
+ module Org
37
+
38
+ include Modes
39
+
40
+ def h1 severity, msg
41
+ change_formatter '*'
42
+ @logger.add severity, msg
43
+ end
44
+
45
+ def h2 severity, msg
46
+ change_formatter "**"
47
+ @logger.add severity, msg
48
+ end
49
+
50
+ def h3 severity, msg
51
+ change_formatter "***"
52
+ @logger.add severity, msg
53
+ end
54
+
55
+ end
56
+
57
+ # Change formatter
58
+ # @param prefix Prefix to put before all content
59
+ def change_formatter prefix
60
+ @logger.formatter = proc do |severity, datetime, progname, msg|
61
+ "\n#{prefix} #{severity} #{msg}"
62
+ end
63
+ end
64
+
65
+ # Create new line
66
+ def newline
67
+ @logger.formatter = proc do |severity, datetime, progname, msg|
68
+ "\n#{msg}"
69
+ end
70
+ @logger.add(nil,"\n")
71
+ end
72
+
73
+ end
74
+ end
data/lib/project.rb ADDED
@@ -0,0 +1,155 @@
1
+ # This modules is used to scan the name of project
2
+ # in order to get all students who worked.
3
+ # This analysis uses regex of convention given by teachers (config file).
4
+ #
5
+ # @author Yann Prono
6
+ #
7
+ module Project
8
+
9
+ # Dictionnary for regex in config file
10
+ SYMBOLS = {
11
+ :group => 'GROUP',
12
+ :firstname => 'FIRSTN',
13
+ :name => 'NAME',
14
+ :user => 'USER',
15
+ :lambda => 'LAMBDA'
16
+ }
17
+
18
+ # Regex to associate, depending the used word in Cfg
19
+ REGEX = {
20
+ :group => '([A-Za-z0-9]+)',
21
+ :firstname => '([A-Za-z\-]+)',
22
+ :name => '([A-Za-z]+)',
23
+ :user => '([^_]*)',
24
+ :lambda => '[a-zA-Z0-9 _]*'
25
+ }
26
+
27
+
28
+ # Get formatter written in the config file
29
+ # and count occurences of each word in the dictionnary SYMBOLS.
30
+ # @return [Hash] sym => count.
31
+ #
32
+ def analyze_formatter
33
+ regex = Cfg[:projects_names]
34
+ # Foreach known symbols
35
+ SYMBOLS.each do |k, _|
36
+ # Get numbers of occurences of the word k in regex
37
+ matches = regex.scan(SYMBOLS[k]).size
38
+ # the word K => number of occurences
39
+ @count_patterns[k] = matches if matches > 0
40
+ end
41
+ end
42
+
43
+
44
+ # Analyze et get all informations
45
+ # that could be useful in the name of the
46
+ # directory project.
47
+ # @param entry [String] name of directory to analysis.
48
+ #
49
+ def format entry
50
+ ext = File.extname entry
51
+ entry = File.basename entry, ext
52
+
53
+ analyze_formatter if @count_patterns.empty?
54
+
55
+ group = check_entry_name entry
56
+ generate_label group
57
+ end
58
+
59
+
60
+ # Generate new name of project.
61
+ # @param infos [Hash] All informations used for generate a name for the directory.
62
+ # @return [String] the formatted name for directory project
63
+ #
64
+ def generate_label infos
65
+ unless infos.empty?
66
+ label = ''
67
+ infos.reject { |k| k == :group }.each { |_, v|
68
+ if v.respond_to?('each')
69
+ v.each { |data|
70
+ label += data +' ' }
71
+ else
72
+ label += v + ' '
73
+ end
74
+ }
75
+ # Inject group of project before name : group/name
76
+ label = infos.key?(:group) && File.join(infos[:group], label) || label
77
+ label
78
+ end
79
+ end
80
+
81
+ # I'm not pround of this method ...
82
+ # associate to a symbol, his position in the regex
83
+ # @example NAME_FIRSTN
84
+ # will give : {
85
+ # 1 => :name,
86
+ # 2 => :firstname
87
+ #}
88
+ def get_position regex
89
+ res = {}
90
+ SYMBOLS.each do |k,v|
91
+ regex.scan(v) do |_|
92
+ res[$~.offset(0)[0]] = k
93
+ end
94
+ end
95
+
96
+ res = (res.sort_by { |k, _| k }).to_h
97
+ tmp = {}
98
+
99
+ index = 1
100
+ res.each do |_,v|
101
+ tmp[index] = v
102
+ index += 1
103
+ end
104
+ tmp
105
+ end
106
+
107
+
108
+ # Apply regex of user on the entry name
109
+ # and try to get all interested matched values.
110
+ def check_entry_name entry
111
+ regex = Cfg[:projects_names]
112
+ # who work on the current project (entry) ?
113
+ position = get_position regex
114
+
115
+ @count_patterns.each do |k, _|
116
+ regex = regex.gsub SYMBOLS[k], REGEX[k]
117
+ end
118
+
119
+ # Apply created regex
120
+ entry.match Regexp.new(regex)
121
+ pos = 1
122
+ infos = {}
123
+
124
+ # Get matched values
125
+ begin
126
+ tmp = eval "$#{pos}"
127
+ if tmp != nil && tmp != ''
128
+ tmp = tmp.delete '_'
129
+ infos.has_key?(position[pos]) && infos[position[pos]] << tmp || infos[position[pos]] = [tmp]
130
+ else
131
+ ask entry
132
+ end
133
+ pos += 1
134
+ end while pos <= position.size
135
+
136
+ sort_infos infos
137
+ infos
138
+ end
139
+
140
+
141
+ # Put all datas found in respective variables (students, groups, teams ...).
142
+ # @param infos [Hash] Informations found by regex.
143
+ def sort_infos infos
144
+ infos[:name].respond_to?('each') && infos[:name].each { |n| @students << n } || @students << infos[:name]
145
+ infos[:group] = infos[:group][0].upcase if infos.key? :group
146
+ @groups << infos[:group] if infos.has_key?(:group) && !@groups.include?(infos[:group])
147
+ @binoms << infos[:name]
148
+ end
149
+
150
+
151
+ def ask entry
152
+ # TODO, ask to user if the entry is correctly written
153
+ end
154
+
155
+ end