diff --git a/bin/NormEZ.rb b/bin/NormEZ.rb new file mode 100755 index 0000000..641e620 --- /dev/null +++ b/bin/NormEZ.rb @@ -0,0 +1,856 @@ +#!/usr/bin/ruby +# NormEZ_v2.0.1 +# Changelog: Remove space between file name and line number in output to improve compatibility with IDEs + +require 'optparse' +require 'tmpdir' + +class String + def each_char + split('').each { |i| yield i } + end + + def add_style(color_code) + if $options.include? :colorless + self + else + "\e[#{color_code}m#{self}\e[0m" + end + end + + def black + add_style(31) + end + + def red + add_style(31) + end + + def green + add_style(32) + end + + def yellow + add_style(33) + end + + def blue + add_style(34) + end + + def magenta + add_style(35) + end + + def cyan + add_style(36) + end + + def grey + add_style(37) + end + + def bold + add_style(1) + end + + def italic + add_style(3) + end + + def underline + add_style(4) + end +end + +module FileType + UNKNOWN = 0 + DIRECTORY = 1 + MAKEFILE = 2 + HEADER = 3 + SOURCE = 4 + HSOURCE = 5 +end + +class FileManager + attr_accessor :path + attr_accessor :type + + def initialize(path, type) + @path = path + @type = type + @type = file_type if @type == FileType::UNKNOWN + end + + def file_type + @type = if @path =~ /Makefile$/ + FileType::MAKEFILE + elsif @path =~ /[.]h$/ + FileType::HEADER + elsif @path =~ /[.]c$/ + FileType::SOURCE + elsif @path =~ /[.]hs$/ + FileType::HSOURCE + else + FileType::UNKNOWN + end + end + + def get_content + file = File.open(@path) + content = file.read + file.close + content + end +end + +class FilesRetriever + @@ignore = nil + + def initialize + @files = ARGV.select { |f| File.file? f } + @files = Dir['**/*'].select { |f| File.file? f } if @files.count.zero? + + if File.file?('.gitignore') + gitignore = FileManager.new('.gitignore', FileType::UNKNOWN).get_content + gitignore.gsub!(/\r\n?/, "\n") + @@ignore = [] + gitignore.each_line do |line| + line.start_with?('#') && line !~ /^\s*$/ || @@ignore.push(line.chomp) + end + end + @nb_files = @files.size + @idx_files = 0 + + @dirs = Dir['**/*'].select { |d| File.directory? d } + @nb_dirs = @dirs.size + @idx_dirs = 0 + end + + def is_ignored_file(file) + @@ignore.each do |ignored_file| + if file.include?(ignored_file) || file.include?(ignored_file.tr('*', '')) + return true + end + end + false + end + + def get_next_file + if @idx_files < @nb_files + file = FileManager.new(@files[@idx_files], FileType::UNKNOWN) + @idx_files += 1 + file = get_next_file if !@@ignore.nil? && is_ignored_file(file.path) + return file + elsif @idx_dirs < @nb_dirs + file = FileManager.new(@dirs[@idx_dirs], FileType::DIRECTORY) + @idx_dirs += 1 + file = get_next_file if !@@ignore.nil? && is_ignored_file(file.path) + return file + end + nil + end +end + +class CodingStyleChecker + def initialize(file_manager) + @file_path = file_manager.path + @type = file_manager.type + @file = nil + if (@type != FileType::UNKNOWN) && (@type != FileType::DIRECTORY) + @file = file_manager.get_content + end + check_file + end + + def check_file + if @type == FileType::UNKNOWN + unless $options.include? :ignorefiles + msg_brackets = "[#{@file_path}]" + msg_error = ' This is probably a forbidden file. You might not want to commit it.' + puts(msg_brackets.bold.red + msg_error.bold + ' (O1)'.grey) + end + return + end + if @type == FileType::DIRECTORY + check_dirname + return + end + check_trailing_spaces_tabs + @type == FileType::HSOURCE || check_indentation + if @type != FileType::MAKEFILE + check_too_many_columns + check_comma_missing_space + if @type == FileType::SOURCE || @type == FileType::HEADER + check_filename_c + check_header + check_several_assignments + check_forbidden_keyword_func + check_too_many_else_if + check_empty_parenthesis + check_too_many_parameters + check_space_after_keywords + check_misplaced_pointer_symbol + check_operators_spaces + check_misplaced_comments + check_condition_assignment + end + if @type == FileType::SOURCE + check_too_broad_filename + check_functions_per_file + check_function_lines_c + check_empty_line_between_functions + end + if @type == FileType::HSOURCE + check_filename_hs + check_function_lines_hs + check_conditional_branching + check_mutable_variables + check_bindings + check_guards + check_useless_do + end + @type == FileType::HEADER && check_macro_used_as_constant + else + check_header_makefile + end + end + + def check_dirname + filename = File.basename(@file_path) + return if filename =~ /^[a-z0-9]+([a-z0-9_]+[a-z0-9]+)*$/ + + msg_brackets = "[#{@file_path}]" + msg_error = ' Directory names should respect the snake_case naming convention.' + puts(msg_brackets.bold.red + msg_error.bold + ' (O4)'.grey) + end + + def check_filename_c + filename = File.basename(@file_path) + return if filename =~ /^[a-z0-9]+([a-z0-9_]+[a-z0-9]+)*[.][ch]$/ + + msg_brackets = "[#{@file_path}]" + msg_error = ' Filenames should respect the snake_case naming convention.' + puts(msg_brackets.bold.red + msg_error.bold + ' (O4)'.grey) + end + + def check_filename_hs + filename = File.basename(@file_path) + return if filename =~ /^([A-Z][a-z0-9]+)+[.]hs$/ + + msg_brackets = "[#{@file_path}]" + msg_error = ' Filenames should respect the UpperCamelCase naming convention.' + puts(msg_brackets.bold.red + msg_error.bold + ' (O4)'.grey) + end + + def check_too_many_columns + line_nb = 1 + @file.each_line do |line| + length = 0 + line.each_char do |char| + length += char == "\t" ? 8 : 1 + end + if length - 1 > 80 + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " Too many columns (#{length - 1} > 80)." + puts(msg_brackets.bold.red + msg_error.bold + ' (F3)'.grey) + end + line_nb += 1 + end + end + + def check_too_broad_filename + unless @file_path =~ %r{(.*/|^)(string.c|str.c|my_string.c|my_str.c|algorithm.c|my_algorithm.c|algo.c|my_algo.c|program.c|my_program.c|prog.c|my_prog.c)$} + return + end + + msg_brackets = "[#{@file_path}]" + msg_error = ' Too broad filename. You should rename this file.' + puts(msg_brackets.bold.red + msg_error.bold + ' (O4)'.grey) + end + + def check_header + return unless @file !~ %r{/\*\n\*\* EPITECH PROJECT, [0-9]{4}\n\*\* .*\n\*\* File description:\n(\*\* .*\n)+\*/\n.*} + + msg_brackets = "[#{@file_path}]" + msg_error = ' Missing or corrupted header.' + puts(msg_brackets.bold.red + msg_error.bold + ' (G1)'.grey) + end + + def check_function_lines_c + count = level = 0 + line_nb = function_start = 1 + @file.each_line do |line| + case line + when /{[ \t]*$/ + if level.zero? + function_start = line_nb + count = -1 + end + level += 1 + when /^[ \t]*}[ \t]*$/ + level -= 1 + if level.zero? && (count > 20) + msg_brackets = "[#{@file_path}: #{function_start}]" + msg_error = " Function contains more than 20 lines (#{count} > 20)." + puts(msg_brackets.bold.red + msg_error.bold + ' (F4)'.grey) + end + end + count += 1 + line_nb += 1 + end + end + + def check_function_lines_hs + count = 0 + line_nb = function_start = 1 + @file.each_line do |line| + if (line =~ /^[\t ]*$/ || line =~ /^[A-Za-z]/) && count > 10 + msg_brackets = "[#{@file_path}: #{function_start}]" + msg_error = " Function contains more than 10 lines (#{count} > 10)." + puts(msg_brackets.bold.red + msg_error.bold + ' (F1)'.grey) + end + if line =~ /^[A-Za-z]/ + function_start = line_nb + count = 0 + end + line =~ /^[ \t]/ && count += 1 + line_nb += 1 + end + return unless count > 10 + + msg_brackets = "[#{@file_path}: #{function_start}]" + msg_error = " Function contains more than 10 lines (#{count} > 10)." + puts(msg_brackets.bold.red + msg_error.bold + ' (F1)'.grey) + end + + def check_conditional_branching + line_nb = 1 + @file.each_line do |line| + if line =~ /[\t ]if[\t ].+[\t ]if[\t ]/ || line =~ /[\t ]else[\t ].+[\t ]if[\t ]/ + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " Nested 'if' statements are strictly forbidden." + puts(msg_brackets.bold.red + msg_error.bold + ' (C1)'.grey) + end + line_nb += 1 + end + end + + def check_guards + line_nb = 1 + new_guard = true + @file.each_line do |line| + if line =~ /[\t ]\|[\t ].*==/ && new_guard + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Can this guard be expressed as a pattern matching?' + puts(msg_brackets.bold.yellow + msg_error.bold + ' (C2)'.grey) + new_guard = false + end + line =~ /^$/ && new_guard = true + line_nb += 1 + end + end + + def check_useless_do + line_nb = 1 + in_do_block = false + useless = true + @file.each_line do |line| + case line + when /\sdo\s/ + in_do_block = true + when /\s<-\s/ + in_do_block && useless = false + when /^$/ + if useless && in_do_block + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " the 'do' notation is forbidden unless it contains a generator: '<-'" + puts(msg_brackets.bold.red + msg_error.bold + ' (D1)'.grey) + end + in_do_block = false + useless = true + end + line_nb += 1 + end + return unless useless && in_do_block + + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " the 'do' notation is forbidden unless it contains a generator: '<-'" + puts(msg_brackets.bold.red + msg_error.bold + ' (D1)'.grey) + end + + def check_mutable_variables + line_nb = 1 + @file.each_line do |line| + if line =~ /(Data.IORef|Data.STRef|Control.Concurrent.STM.TVar)/ + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Mutable variables are strictly forbidden.' + puts(msg_brackets.bold.red + msg_error.bold + ' (M1)'.grey) + end + line_nb += 1 + end + end + + def check_bindings + line_nb = 1 + bound_functions = [] + @file.each_line do |line| + case line + when /^(type|class)/ + next + when /^[A-Za-z].*::/ + bound_functions.push(line.split(' ')[0]) + when /^[A-Za-z].*=[^:]/ + unless bound_functions.include? line.split(' ')[0] + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' All top level bindings must have an accompanying type signature.' + puts(msg_brackets.bold.red + msg_error.bold + ' (T1)'.grey) + end + end + line_nb += 1 + end + end + + def check_several_assignments + line_nb = 1 + @file.each_line do |line| + if line =~ /^[ \t]*for ?\(/ + line_nb += 1 + next + end + assignments = 0 + line.each_char do |char| + assignments += 1 if char == ';' + end + if assignments > 1 + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Several assignments on the same line.' + puts(msg_brackets.bold.red + msg_error.bold + ' (L5)'.grey) + end + line_nb += 1 + end + end + + def check_forbidden_keyword_func + line_nb = 1 + @file.each_line do |line| + line.scan(/(^|[^0-9a-zA-Z_])(printf|dprintf|fprintf|vprintf|sprintf|snprintf|vprintf|vfprintf|vsprintf|vsnprintf|asprintf|scranf|memcpy|memset|memmove|strcat|strchar|strcpy|atoi|strlen|strstr|strncat|strncpy|strcasestr|strncasestr|strcmp|strncmp|strtok|strnlen|strdup|realloc)[^0-9a-zA-Z]/) do + unless $options.include? :ignorefunctions + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " Are you sure that this function is allowed: '".bold + msg_error += Regexp.last_match(2).bold.red + msg_error += "'?".bold + puts(msg_brackets.bold.red + msg_error) + end + end + line.scan(/(^|[^0-9a-zA-Z_])(goto)[^0-9a-zA-Z]/) do + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " Are you sure that this keyword is allowed: '".bold + msg_error += Regexp.last_match(2).bold.red + msg_error += "'?".bold + puts(msg_brackets.bold.red + msg_error) + end + line_nb += 1 + end + end + + def check_too_many_else_if + line_nb = condition_start = previous_condition_start = 1 + count = 0 + @file.each_line do |line| + line[0] = '' while [' ', "\t"].include?(line[0]) + if line =~ /^if ?\(/ + condition_start = line_nb + count = 1 + elsif line =~ /^else if ?\(/ || line =~ /^else ?\(/ + count += 1 + if count > 3 && previous_condition_start != condition_start + msg_brackets = "[#{@file_path}: #{condition_start}]" + msg_error = ' Too many "else if" statements.' + puts(msg_brackets.bold.green + msg_error.bold + ' (C1)'.grey) + previous_condition_start = condition_start + end + end + line_nb += 1 + end + end + + def check_trailing_spaces_tabs + line_nb = 1 + @file.each_line do |line| + case line + when / $/ + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Trailing space(s) at the end of the line.' + puts(msg_brackets.bold.green + msg_error.bold) + when /\t$/ + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Trailing tabulation(s) at the end of the line.' + puts(msg_brackets.bold.green + msg_error.bold) + end + line_nb += 1 + end + end + + def check_indentation + line_nb = 1 + if @type == FileType::MAKEFILE + valid_indent = '\t' + bad_indent_regexp = /^ +.*$/ + bad_indent_name = 'space' + else + valid_indent = ' ' + bad_indent_regexp = /^\t+.*$/ + bad_indent_name = 'tabulation' + end + @file.each_line do |line| + indent = 0 + while line[indent] == valid_indent + indent += 1 + end + if line =~ bad_indent_regexp + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " Wrong indentation: #{bad_indent_name}s are not allowed." + puts(msg_brackets.bold.green + msg_error.bold + ' (L2)'.grey) + elsif indent % 4 != 0 + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Wrong indentation.' + puts(msg_brackets.bold.green + msg_error.bold + ' (L2)'.grey) + end + line_nb += 1 + end + end + + def check_functions_per_file + functions = 0 + @file.each_line do |line| + functions += 1 if line =~ /^{/ + end + return unless functions > 5 + + msg_brackets = "[#{@file_path}]" + msg_error = " More than 5 functions in the same file (#{functions} > 5)." + puts(msg_brackets.bold.red + msg_error.bold + ' (O3)'.grey) + end + + def check_empty_parenthesis + line_nb = 1 + missing_bracket = false + @file.each_line do |line| + if missing_bracket + if line =~ /^{$/ + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " This function takes no parameter, it should take 'void' as argument." + puts(msg_brackets.bold.red + msg_error.bold + ' (F5)'.grey) + elsif line !~ /^[\t ]*$/ + missing_bracket = false + end + elsif line =~ /\(\)[\t ]*{$/ + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " This function takes no parameter, it should take 'void' as argument." + puts(msg_brackets.bold.red + msg_error.bold + ' (F5)'.grey) + elsif line =~ /\(\)[ \t]*$/ + missing_bracket = true + end + line_nb += 1 + end + end + + def check_too_many_parameters + @file.scan(/\(([^(),]*,){4,}[^()]*\)[ \t\n]+{/).each do |_match| + msg_brackets = "[#{@file_path}]" + msg_error = " Function shouldn't take more than 4 arguments." + puts(msg_brackets.bold.red + msg_error.bold + ' (F5)'.grey) + end + end + + def check_space_after_keywords + line_nb = 1 + @file.each_line do |line| + line.scan(/(return|if|else if|else|while|for)\(/) do |match| + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " Missing space after keyword '#{match[0]}'." + puts(msg_brackets.bold.green + msg_error.bold + ' (L3)'.grey) + end + line_nb += 1 + end + end + + def check_misplaced_pointer_symbol + line_nb = 1 + @file.each_line do |line| + line.scan(/([^(\t ]+_t|int|signed|unsigned|char|long|short|float|double|void|const|struct [^ ]+)\*/) do |match| + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " Misplaced pointer symbol after '#{match[0]}'." + puts(msg_brackets.bold.green + msg_error.bold + ' (V3)'.grey) + end + line_nb += 1 + end + end + + def check_macro_used_as_constant + line_nb = 1 + @file.each_line do |line| + if line =~ /#define [^ ]+ [0-9]+([.][0-9]+)?/ + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Macros should not be used for constants.' + puts(msg_brackets.bold.green + msg_error.bold + ' (H3)'.grey) + end + line_nb += 1 + end + end + + def check_header_makefile + return if @file != /##\n## EPITECH PROJECT, [0-9]{4}\n## .*\n## File description:\n## .*\n##\n.*/ + + msg_brackets = "[#{@file_path}]" + msg_error = ' Missing or corrupted header.' + puts(msg_brackets.bold.red + msg_error.bold + ' (G1)'.grey) + end + + def check_misplaced_comments + level = 0 + line_nb = 1 + @file.each_line do |line| + level += line.count '{' + level -= line.count '}' + if (level != 0) && (line =~ %r{/\*} || line =~ %r{//}) + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Comment inside a function.' + puts(msg_brackets.bold.green + msg_error.bold + ' (F6)'.grey) + end + line_nb += 1 + end + end + + def check_comma_missing_space + line_nb = 1 + @file.each_line do |line| + line.scan(/,[^ \n]/) do + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Missing space after comma.' + puts(msg_brackets.bold.green + msg_error.bold + ' (L3)'.grey) + end + line_nb += 1 + end + end + + def put_error_sign(sign, line_nb) + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = " Misplaced space(s) around '#{sign}' sign." + puts(msg_brackets.bold.green + msg_error.bold + ' (L3)'.grey) + end + + def check_operators_spaces + line_nb = 1 + @file.each_line do |line| + # A space on both ends + line.scan(%r{([^\t&|=^><+\-*%/! ]=[^=]|[^&|=^><+\-*%/!]=[^= \n])}) do + put_error_sign('=', line_nb) + end + line.scan(/([^\t ]==|==[^ \n])/) do + put_error_sign('==', line_nb) + end + line.scan(/([^\t ]!=|!=[^ \n])/) do + put_error_sign('!=', line_nb) + end + line.scan(/([^\t <]<=|[^<]<=[^ \n])/) do + put_error_sign('<=', line_nb) + end + line.scan(/([^\t >]>=|[^>]>=[^ \n])/) do + put_error_sign('>=', line_nb) + end + line.scan(/([^\t ]&&|&&[^ \n])/) do + put_error_sign('&&', line_nb) + end + line.scan(/([^\t ]\|\||\|\|[^ \n])/) do + put_error_sign('||', line_nb) + end + line.scan(/([^\t ]\+=|\+=[^ \n])/) do + put_error_sign('+=', line_nb) + end + line.scan(/([^\t ]-=|-=[^ \n])/) do + put_error_sign('-=', line_nb) + end + line.scan(/([^\t ]\*=|\*=[^ \n])/) do + put_error_sign('*=', line_nb) + end + line.scan(%r{([^\t ]/=|/=[^ \n])}) do + put_error_sign('/=', line_nb) + end + line.scan(/([^\t ]%=|%=[^ \n])/) do + put_error_sign('%=', line_nb) + end + line.scan(/([^\t ]&=|&=[^ \n])/) do + put_error_sign('&=', line_nb) + end + line.scan(/([^\t ]\^=|\^=[^ \n])/) do + put_error_sign('^=', line_nb) + end + line.scan(/([^\t ]\|=|\|=[^ \n])/) do + put_error_sign('|=', line_nb) + end + line.scan(/([^\t |]\|[^|]|[^|]\|[^ =|\n])/) do + # Minifix for Matchstick + line.scan(/([^']\|[^'])/) do + put_error_sign('|', line_nb) + end + end + line.scan(/([^\t ]\^|\^[^ =\n])/) do + put_error_sign('^', line_nb) + end + line.scan(/([^\t ]>>[^=]|>>[^ =\n])/) do + put_error_sign('>>', line_nb) + end + line.scan(/([^\t ]<<[^=]|<<[^ =\n])/) do + put_error_sign('<<', line_nb) + end + line.scan(/([^\t ]>>=|>>=[^ \n])/) do + put_error_sign('>>=', line_nb) + end + line.scan(/([^\t ]<<=|<<=[^ \n])/) do + put_error_sign('<<=', line_nb) + end + # No space after + line.scan(/([^!]! )/) do + put_error_sign('!', line_nb) + end + line.scan(/([^a-zA-Z0-9]sizeof )/) do + put_error_sign('sizeof', line_nb) + end + line.scan(/([^a-zA-Z)\]]\+\+[^(\[*a-zA-Z])/) do + put_error_sign('++', line_nb) + end + line.scan(/([^a-zA-Z)\]]--[^\[(*a-zA-Z])/) do + put_error_sign('--', line_nb) + end + line_nb += 1 + end + end + + def check_condition_assignment + line_nb = 1 + @file.each_line do |line| + line.scan(%r{(if.*[^&|=^><+\-*%/!]=[^=].*==.*)|(if.*==.*[^&|=^><+\-*%/!]=[^=].*)}) do + msg_brackets = "[#{@file_path}:#{line_nb}]" + msg_error = ' Condition and assignment on the same line.' + puts(msg_brackets.bold.green + msg_error.bold + ' (L1)'.grey) + end + line_nb += 1 + end + end + + def check_empty_line_between_functions + @file.scan(/\n{3,}^[^ \n\t]+ [^ \n\t]+\([^\n\t]*\)/).each do |_match| + msg_brackets = "[#{@file_path}]" + msg_error = ' Too many empty lines between functions.' + puts(msg_brackets.bold.green + msg_error.bold + ' (G2)'.grey) + end + @file.scan(/[^\n]\n^[^ \n\t]+ [^ \n\t]+\([^\n\t]*\)/).each do |_match| + msg_brackets = "[#{@file_path}]" + msg_error = ' Missing empty line between functions.' + puts(msg_brackets.bold.green + msg_error.bold + ' (G2)'.grey) + end + end +end + +class UpdateManager + def initialize(script_path) + path = File.dirname(script_path) + tmp_dir = Dir.tmpdir + @script_path = script_path + @remote_path = "#{tmp_dir}/__normez_remote" + @backup_path = "#{tmp_dir}/__normez_backup" + @remote = system("curl -s https://raw.githubusercontent.com/ronanboiteau/NormEZ/master/NormEZ.rb > #{@remote_path}") + end + + def clean_update_files + system("rm -rf #{@backup_path}") + system("rm -rf #{@remote_path}") + end + + def can_update + unless @remote + clean_update_files + return false + end + @current = `cat #{@script_path} | grep 'NormEZ_v' | cut -c 11- | head -1 | tr -d '.'` + @latest = `cat #{@remote_path} | grep 'NormEZ_v' | cut -c 11- | head -1 | tr -d '.'` + @latest_disp = `cat #{@remote_path} | grep 'NormEZ_v' | cut -c 11- | head -1` + return true if @current < @latest + + clean_update_files + false + end + + def update + return unless @current < @latest + + update_msg = `cat #{@remote_path} | grep 'Changelog: ' | cut -c 14- | head -1 | tr -d '.'` + print("A new version is available: NormEZ v#{@latest_disp}".bold.yellow) + print(' => Changelog: '.bold) + print(update_msg.to_s.bold.blue) + response = nil + Kernel.loop do + print('Update NormEZ? [Y/n]: ') + response = gets.chomp + break if ['N', 'n', 'no', 'Y', 'y', 'yes', ''].include?(response) + end + if %w[N n no].include?(response) + puts('Update skipped. You can also use the --no-update (or -u) option to prevent auto-updating.'.bold.blue) + clean_update_files + return + end + puts('Downloading update...') + system("cat #{@script_path} > #{@backup_path}") + exit_code = system("cat #{@remote_path} > #{@script_path}") + unless exit_code + print('Error while updating! Cancelling...'.bold.red) + system("cat #{@backup_path} > #{@script_path}") + clean_update_files + Kernel.exit(false) + end + clean_update_files + puts('NormEZ has been successfully updated!'.bold.green) + Kernel.exit(true) + end +end + +$options = {} +opt_parser = OptionParser.new do |opts| + opts.banner = "Usage: `ruby #{$PROGRAM_NAME} [-ufmi]`" + opts.on('-u', '--no-update', "Don't check for updates") do |o| + $options[:noupdate] = o + end + opts.on('-f', '--ignore-files', 'Ignore forbidden files') do |o| + $options[:ignorefiles] = o + end + opts.on('-m', '--ignore-functions', 'Ignore forbidden functions') do |o| + $options[:ignorefunctions] = o + end + opts.on('-i', '--ignore-all', 'Ignore forbidden files & forbidden functions (same as `-fm`)') do |o| + $options[:ignorefiles] = o + $options[:ignorefunctions] = o + end + opts.on('-c', '--colorless', 'Disable output styling') do |o| + $options[:colorless] = o + end +end + +begin + opt_parser.parse! +rescue OptionParser::InvalidOption => e + puts("Error: #{e}") + puts(opt_parser.banner) + Kernel.exit(false) +end + +unless $options.include?(:noupdate) + updater = UpdateManager.new($PROGRAM_NAME) + updater.update if updater.can_update +end + +files_retriever = FilesRetriever.new +while (next_file = files_retriever.get_next_file) + CodingStyleChecker.new(next_file) +end diff --git a/bin/script b/bin/script index 01061f0..9852905 100755 --- a/bin/script +++ b/bin/script @@ -6,6 +6,8 @@ if [ ! -w "$1" ]; then fi ( +set -e + asciiquarium & cmatrix -ab & while [ 1 ]; do sl; done &