require 'csv' # Responsible for running the `cloc` system command and handling any resulting errors class CLOCScanner class Exception < StandardError; end class ArgumentError < RuntimeError; end class Unavailable < RuntimeError; end class NoDirectory < RuntimeError; end class CSVError < RuntimeError; end def initialize(system_cloc = SystemCloc.new) @system_cloc = system_cloc 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 'cloc' command is unavailable. Is it installed and in your path?" unless cloc_available? fail NoDirectory, "No such directory: '#{directory}'" unless Dir.exist?(directory) result, status = @system_cloc.call(directory) if status.success? CLOC.new(cloc_csv(result)) else raise Exception, "CLOC raised an error we didn't recognise. Here's the output:\n#{result}" end end private def cloc_csv(cloc_result) CSV.parse(cloc_result.lstrip, headers: true) rescue CSV::MalformedCSVError => e raise CSVError, "The CSV generated by CLOC was malformed: #{e}" end def cloc_available? _result, status = @system_cloc.which status.success? end end