require 'open3' # Responsible for running the `sloccount` system command and handling any resulting errors # The SLOCCount project is at http://www.dwheeler.com/sloccount/ and can be installed on OSX using homebrew: # brew install sloccount class SLOCCountScanner class Exception < StandardError; end class ArgumentError < RuntimeError; end class Unavailable < RuntimeError; end class NoInput < RuntimeError; end class NoDirectory < RuntimeError; end def initialize(system_sloccount = SystemSLOCCount.instance) @system_sloccount = system_sloccount end def scan(directory) fail ArgumentError, "directory must must be a string (or quack like a string)" unless directory.respond_to?(:to_str) fail Unavailable, "The 'sloccount' command is unavailable. Is it installed and in your path?" unless @system_sloccount.available? fail NoDirectory, "No such directory: '#{directory}'" unless Dir.exist?(directory) result, status = @system_sloccount.call(directory) if status.success? SLOCCount.new(result) else check_for_errors(result) { NullSLOCCount.new } end end private def check_for_errors(result) case result when /SLOC total is zero, no further analysis performed/ yield when /Error: You must provide a directory or directories of source code/ # Because of the check above, it shouldn't be possible to get here raise NoInput, "sloccount requires a directory or directories of source code" else raise Exception, "sloccount raised an error we didn't recognise. Here's the output:\n#{result}" end end # Responsible for isolating the system calls to run SLOCCount class SystemSLOCCount require 'singleton' include Singleton COMMAND_NAME = "sloccount".freeze def available? _result, status = system_which_sloccount status.success? end def call(directory) # DANGER!!! calling system command # also: danger! not covered by specs # Should be safe from injection because of the `Dir.exist?` check. Open3.capture2("#{COMMAND_NAME} #{directory} 2> #{error_log_name}") ensure delete_empty_error_log end private def system_which_sloccount # DANGER!!! calling system command # also: danger! not covered by specs Open3.capture2("which #{COMMAND_NAME}") end def error_log_name "#{COMMAND_NAME}_error.log" end def delete_empty_error_log File.delete(error_log_name) if File.zero?(error_log_name) end end end