# SMELL I don't like the name gemfile_lock_file, but I don't know # what else to call it. I want to distinguish it from Gemfile. def extract_gems_from_gemfile_lock_file(contents) gems = [] # Extract all the non-empty lines between and excluding 'specs:' and 'PLATFORMS' contents.each_line do |line| line = line.strip # It takes a few seconds to understand this algorithm, # but I can't yet justify replacing it with a functional # approach. Again, it depends what the reader understands # better. # If we're going to let iterating and filtering stay # intertwined like this, I'm glad we've separated it from # the rest of the code. break if line.include?("PLATFORMS") next if line.include?("GEM") next if line.include?("remote:") next if line.include?("specs:") next if line.empty? gems.push(line.split(' ').first) end return gems end # Guess what? Now independent of "gem" concept. More reusable. MAGIC! # Guess what? Almost dead-simple wrapper around command line tool. Cohesive. SRP. Nice. def count_lines_in_file(file) output = `wc -l #{file}` # ASSERT: output is of the form # We could turn this comment into code by matching with a regex, which some # would find clearer and others less clear. I could do either. line_count_text = output.strip.split(' ').first return line_count_text.to_i end # SMELL Depends on globals like 'puts' and `` (execute process). def count_lines_for_gem(gem) puts "Processing #{gem}" gem_filenames = `gem contents #{gem}`.split # If you prefer to 'inject', then feel free to 'inject'. # Either way, the name duplication disappears. line_count_for_this_gem = gem_filenames.map { |each| count_lines_in_file(each) }.reduce(:+) puts " LOC: #{line_count_for_this_gem}" return line_count_for_this_gem end total = extract_gems_from_gemfile_lock_file(File.read("Gemfile.lock")) .map { |each| count_lines_for_gem(each) }.reduce(:+) puts "Total Lines: #{total}"