#!/usr/bin/ruby require 'find' require 'getoptlong' EXTS = Regexp.compile('^.*\.(' + %w[ h hh hpp H c cc cpp cxx C m mm M rb py pl sh lua scm hs lisp java cs js l ll y yy].join('|') + ')$') OPTS = [ [ '--exclude', '-x', GetoptLong::REQUIRED_ARGUMENT ], [ '--no-blank-lines', '-n', GetoptLong::NO_ARGUMENT ], [ '--help', '-h', GetoptLong::NO_ARGUMENT ]] HELP = { '--exclude' => { :argument_placeholder => 'pattern', :help_text => 'exclude files matching glob pattern ' }, '--no-blank-lines' => { :help_text => "don't count blank lines in source files" }, '--help' => { :help_text => "this help text" } } opts = GetoptLong.new(*OPTS) REGEXP_METACHARACTERS = '\\$^*(){}+.?' exclusions = nil blank_lines = true opts.each do |opt, arg| if opt == '--exclude' then re = '' arg.each_byte do |b| if b == ?* then re += '.*' elsif b == ?? then re += '.' else if REGEXP_METACHARACTERS.include?(b) then re += '\\' end re << b end end re += '$' if exclusions then exclusions += "|(#{re})" else exclusions = "(#{re})" end elsif opt == '--no-blank-lines' then blank_lines = false elsif opt == '--help' then puts "Usage: #{$0} [options] [paths to search]\n\nOptions:" puts OPTS.map { |option| if option[-1] == GetoptLong::REQUIRED_ARGUMENT then joiner = " <#{HELP[option[0]][:argument_placeholder]}>\n " elsif option[-1] == GetoptLong::OPTIONAL_ARGUMENT then joiner = " [#{HELP[option[0]][:argument_placeholder]}]\n " elsif option[-1] == GetoptLong::NO_ARGUMENT then joiner = "\n " end " " + option[0..-2].map { |o| o + joiner }.join + " " + HELP[option[0]][:help_text] + "\n" }.join("\n") puts exit end end if ARGV.size == 0 then ARGV << '.' end exclusions_re = if exclusions then Regexp.compile(exclusions) else nil end lc = 0 ARGV.each do |root| Find.find(root) do |path| if exclusions_re && exclusions_re =~ path then Find.prune next end if EXTS =~ path then begin File.read(path).each_line do |line| unless line =~ /^\s+$/ && !blank_lines then lc += 1 end end rescue => e $stderr.puts(e) end end end end puts lc