Created
April 3, 2025 04:35
-
-
Save yanshiyason/45326a617622c2b5bb3ce1bc5ed954e3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'yaml' | |
require 'json' | |
require 'pathname' | |
require 'open3' | |
require 'set' | |
require 'ripper' # For Ruby comment stripping | |
# Load configuration from .ziprc.local or .ziprc | |
config_path = Pathname.new(Dir.pwd).join('.ziprc.local') | |
config_path = Pathname.new(Dir.pwd).join('.ziprc') unless config_path.exist? | |
if File.exist?(config_path) | |
config = YAML.load_file(config_path) | |
EXCLUDED_FILES = config['excluded_files'] || [] | |
EXCLUDED_DIRECTORIES = config['excluded_directories'] || [] | |
EXCLUDED_PATTERNS = (config['excluded_patterns'] || []).map { |pattern| Regexp.new(pattern) } | |
RESPECT_GITIGNORE = config.dig('settings', 'respect_gitignore') == true | |
STRIP_RUBY_COMMENTS = config.dig('settings', 'strip_ruby_comments') == true | |
else | |
puts "Warning: .ziprc file not found. Using default exclusions." | |
EXCLUDED_FILES = [] | |
EXCLUDED_DIRECTORIES = [] | |
EXCLUDED_PATTERNS = [] | |
RESPECT_GITIGNORE = true | |
STRIP_RUBY_COMMENTS = true | |
end | |
# Utility method to convert bytes to human readable format | |
def human_readable_size(size) | |
units = ['B', 'KB', 'MB', 'GB', 'TB'] | |
index = 0 | |
while size >= 1024 && index < units.length - 1 | |
size /= 1024.0 | |
index += 1 | |
end | |
"%.2f %s" % [size, units[index]] | |
end | |
# Ruby comment stripper using Ripper | |
def strip_ruby_comments(content) | |
Ripper.lex(content) | |
.reject { |(_, type, _)| type == :on_comment } | |
.map { |(_, _, str)| str } | |
.join | |
rescue => e | |
# If there's any error, return the original content | |
puts "Warning: Error stripping comments from Ruby file: #{e.message}" | |
return content | |
end | |
# Track all files added to the zip and their sizes | |
TRACKED_SIZE_DATA = [] | |
# Get a set of all git tracked files | |
def get_git_tracked_files | |
return Set.new unless system('git rev-parse --is-inside-work-tree >/dev/null 2>&1') | |
stdout, status = Open3.capture2('git', 'ls-files') | |
if status.success? | |
Set.new(stdout.split("\n").map { |f| File.expand_path(f) }) | |
else | |
Set.new | |
end | |
end | |
# Initialize set of tracked files if git ignore is respected | |
TRACKED_FILES = RESPECT_GITIGNORE ? get_git_tracked_files : nil | |
# Get the current directory name | |
folder_name = File.basename(Dir.pwd) | |
zip_filename = "tmp/#{folder_name}.zip.txt" | |
# Create tmp directory if it doesn't exist | |
Dir.mkdir('tmp') unless Dir.exist?('tmp') | |
# Method to check if a file should be excluded | |
def should_exclude?(path, base_dir) | |
# Skip if not tracked by git and we respect gitignore | |
if RESPECT_GITIGNORE && !TRACKED_FILES.include?(File.expand_path(path)) && !File.directory?(path) | |
return [true, "not in git"] | |
end | |
relative_path = Pathname.new(path).relative_path_from(Pathname.new(base_dir)).to_s | |
# Exclude whole directories | |
if EXCLUDED_DIRECTORIES.any? { |dir| relative_path.split('/').include?(dir) } | |
return [true, "excluded directory"] | |
end | |
# Exclude specific files | |
if EXCLUDED_FILES.include?(File.basename(relative_path)) | |
return [true, "excluded file"] | |
end | |
# Exclude by file pattern | |
EXCLUDED_PATTERNS.each do |pattern| | |
if relative_path.match?(pattern) | |
return [true, "excluded pattern"] | |
end | |
end | |
[false, ""] | |
end | |
# Method to recursively add files to the zip archive | |
def add_files_to_zip(zipfile, base_dir, current_dir) | |
Dir.foreach(current_dir) do |file| | |
next if file == '.' || file == '..' # Skip system entries | |
path = File.join(current_dir, file) | |
# Skip excluded files and directories | |
excluded, reason = should_exclude?(path, base_dir) | |
if excluded | |
# puts "Skipping: #{path.sub("#{base_dir}/", '')} (#{reason})" | |
next | |
end | |
if File.directory?(path) | |
add_files_to_zip(zipfile, base_dir, path) # Recurse into directories | |
else | |
relative_path = Pathname.new(path).relative_path_from(Pathname.new(base_dir)).to_s | |
original_size = File.size(path) | |
# puts "Adding: #{relative_path}" | |
content = File.read(path) | |
# Strip Ruby comments if enabled and file is a Ruby file | |
if STRIP_RUBY_COMMENTS && File.extname(relative_path) == '.rb' | |
content = strip_ruby_comments(content) | |
end | |
zipfile << "<file filepath=#{relative_path}>\n#{content}\n</file>\n" | |
# Track file size data | |
stripped_size = content.bytesize | |
TRACKED_SIZE_DATA << { | |
path: relative_path, | |
size: stripped_size, | |
original_size: original_size, | |
size_reduction: original_size - stripped_size | |
} | |
end | |
end | |
end | |
# Create the ZIP file | |
File.open(zip_filename, 'w') do |zipfile| | |
# zipfile << '<project_trees>\n' | |
# zipfile << `rg --files | tree --fromfile` | |
# zipfile << '</project_trees>\n' | |
zipfile << "<project_files>\n" | |
add_files_to_zip(zipfile, Dir.pwd, Dir.pwd) | |
zipfile << "</project_files>\n" | |
end | |
puts "\n✅ ZIP TXT file created: #{zip_filename}" | |
# Separate Ruby files (which had comments stripped) from other files | |
ruby_files = TRACKED_SIZE_DATA.select { |item| File.extname(item[:path]) == '.rb' } | |
non_ruby_files = TRACKED_SIZE_DATA.select { |item| File.extname(item[:path]) != '.rb' } | |
# Print a simple list of all files included in the zip | |
puts "\n📄 Files included in the ZIP (#{TRACKED_SIZE_DATA.count} total):" | |
puts "=" * 85 | |
puts "%-70s %15s" % ["File Path", "Size"] | |
puts "-" * 85 | |
# Sort files by size in descending order | |
TRACKED_SIZE_DATA.sort_by { |item| -item[:size] }.each do |file_data| | |
puts "%-70s %15s" % [ | |
file_data[:path], | |
human_readable_size(file_data[:size]) | |
] | |
end | |
# Show summary statistics | |
puts "=" * 85 | |
puts "%-70s %15s" % ["RUBY FILES", "#{ruby_files.count} files"] | |
puts "%-70s %15s" % ["OTHER FILES", "#{non_ruby_files.count} files"] | |
puts "%-70s %15s" % ["TOTAL FILES", "#{TRACKED_SIZE_DATA.count} files"] | |
puts "%-70s %15s" % ["TOTAL SIZE", human_readable_size(TRACKED_SIZE_DATA.sum { |file_data| file_data[:size] })] | |
# Get token count | |
claude_token_count = IO.popen('claude_token_count --stdin', 'r+') do |pipe| | |
pipe.write(File.read(zip_filename)) | |
pipe.close_write | |
JSON.parse(pipe.read)['input_tokens'] | |
end | |
puts "Estimated Tokens: #{claude_token_count}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment