Skip to content

Instantly share code, notes, and snippets.

@koute
Last active September 8, 2023 11:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koute/943d0c18402b7b729a5d14ebd3821499 to your computer and use it in GitHub Desktop.
Save koute/943d0c18402b7b729a5d14ebd3821499 to your computer and use it in GitHub Desktop.
Calculate total lines of code for a Rust crate, local and external (requires `cargo-tree` and `tokei` to be installed)
#!/usr/bin/ruby
require "shellwords"
require "json"
if ARGV.length == 0
extra_args = ""
elsif ARGV.length == 1
extra_args = "-p #{ARGV[0]}"
else
raise "invalid arguments"
end
puts "Gathering crates..."
warnings = []
paths = {}
crate_is_local = {}
`cargo tree -e no-dev --prefix none #{extra_args}`.strip.each_line do |line|
line = line.strip
next if line.end_with?("(*)") || line.empty?
raise "failed to parse line: '#{line}'" unless line =~ /\A(\S+) v(\S+)( \((.+?)\))?\Z/
name, version, path = $1, $2, $4
is_local = true
if path == nil || !File.exist?(path)
is_local = false
path = ["github.com-1ecc6299db9ec823", "index.crates.io-6f17d22bba15001f"].map do |source|
File.join(ENV["HOME"], ".cargo/registry/src/#{source}/#{name}-#{version}")
end.find { |path| File.exist?(path) }
end
if path == nil
warnings << "path not found for '#{name}-#{version}'"
next
end
key = [name, version]
paths[key] = path
crate_is_local[key] = is_local
end
puts "Found crates: #{paths.length}"
puts "Processing..."
puts
total = 0
total_external = 0
total_local = 0
crates_local = 0
crates_external = 0
counts = {}
paths.each do |key, path|
is_local = crate_is_local[key]
src_path = File.join(path, "src")
unless File.exist? src_path
warnings << "path doesn't exist: '#{src_path}'"
excluded = [
File.join(path, "tests"),
File.join(path, "benches"),
File.join(path, "vendor"),
].map { |p| "-e #{p.shellescape}" }.join(" ")
data = JSON.parse `tokei -C -t Rust -o json #{excluded} #{path.shellescape}`
else
data = JSON.parse `tokei -C -t Rust -o json #{src_path.shellescape}`
end
count = data["Rust"]["code"]
counts[key] = count
total += count
if is_local
crates_local += 1
total_local += count
else
crates_external += 1
total_external += count
end
end
width_name = 0
width_version = 0
width_count = 0
counts.each do |key, count|
name, version = key
width_name = [width_name, name.length].max
width_version = [width_version, version.length].max
width_count = [width_count, count.to_s.length].max
end
counts.sort_by { |key, count| count }.each do |key, count|
name, version = key
is_local = crate_is_local[key]
l =
if is_local
"l"
else
"e"
end
puts " (#{l}) #{name.ljust(width_name)} #{version.ljust(width_version)} #{count.to_s.rjust(width_count)}"
end
puts
puts "Total crates: #{paths.length}"
puts " Local: #{crates_local}"
puts " External: #{crates_external}"
puts
puts "Total lines of code: #{total}"
puts " Local: #{total_local}"
puts " External: #{total_external}"
puts
warnings.each do |warn|
puts "WARN: #{warn}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment