-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
539 additions
and
205 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,69 @@ | ||
# frozen_string_literal: true | ||
|
||
# lib/poepod/cli.rb | ||
require "thor" | ||
require_relative "processor" | ||
require_relative "file_processor" | ||
require_relative "gem_processor" | ||
|
||
module Poepod | ||
class Cli < Thor | ||
desc "concat DIRECTORY OUTPUT_FILE", "Concatenate code from a directory into one text file" | ||
option :exclude, type: :array, default: Poepod::Processor::EXCLUDE_DEFAULT, desc: "List of patterns to exclude" | ||
desc "concat FILES [OUTPUT_FILE]", "Concatenate specified files into one text file" | ||
option :exclude, type: :array, default: Poepod::FileProcessor::EXCLUDE_DEFAULT, desc: "List of patterns to exclude" | ||
option :config, type: :string, desc: "Path to configuration file" | ||
option :include_binary, type: :boolean, default: false, desc: "Include binary files (encoded in MIME format)" | ||
|
||
def concat(directory, output_file = nil) | ||
dir_path = Pathname.new(directory) | ||
|
||
# Check if the directory exists | ||
unless dir_path.directory? | ||
puts "Error: Directory '#{directory}' does not exist." | ||
def concat(*files, output_file: nil) | ||
if files.empty? | ||
puts "Error: No files specified." | ||
exit(1) | ||
end | ||
|
||
dir_path = dir_path.expand_path unless dir_path.absolute? | ||
output_file ||= default_output_file(files.first) | ||
output_path = Pathname.new(output_file).expand_path | ||
|
||
processor = Poepod::FileProcessor.new(files, output_path, options[:config], options[:include_binary]) | ||
total_files, copied_files = processor.process | ||
|
||
puts "-> #{total_files} files detected." | ||
puts "=> #{copied_files} files have been concatenated into #{output_path.relative_path_from(Dir.pwd)}." | ||
end | ||
|
||
desc "wrap GEMSPEC_PATH", "Wrap a gem based on its gemspec file" | ||
option :include_unstaged, type: :boolean, default: false, desc: "Include unstaged files from lib, spec, and test directories" | ||
|
||
output_file ||= "#{dir_path.basename}.txt" | ||
output_path = dir_path.dirname.join(output_file) | ||
processor = Poepod::Processor.new(options[:config]) | ||
total_files, copied_files = processor.write_directory_structure_to_file(directory, output_path, options[:exclude]) | ||
def wrap(gemspec_path) | ||
processor = Poepod::GemProcessor.new(gemspec_path, nil, options[:include_unstaged]) | ||
success, result, unstaged_files = processor.process | ||
|
||
puts "-> #{total_files} files detected in the #{dir_path.relative_path_from(Dir.pwd)} directory." | ||
puts "=> #{copied_files} files have been concatenated into #{Pathname.new(output_path).relative_path_from(Dir.pwd)}." | ||
if success | ||
puts "=> The gem has been wrapped into '#{result}'." | ||
if unstaged_files.any? | ||
puts "\nWarning: The following files are not staged in git:" | ||
puts unstaged_files | ||
puts "\nThese files are #{options[:include_unstaged] ? "included" : "not included"} in the wrap." | ||
puts "Use --include-unstaged option to include these files." unless options[:include_unstaged] | ||
end | ||
else | ||
puts result | ||
exit(1) | ||
end | ||
end | ||
|
||
def self.exit_on_failure? | ||
true | ||
end | ||
|
||
private | ||
|
||
def default_output_file(first_pattern) | ||
first_item = Dir.glob(first_pattern).first | ||
if first_item | ||
if File.directory?(first_item) | ||
"#{File.basename(first_item)}.txt" | ||
else | ||
"#{File.basename(first_item, ".*")}_concat.txt" | ||
end | ||
else | ||
"concatenated_output.txt" | ||
end | ||
end | ||
end | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
require_relative "processor" | ||
require "yaml" | ||
require "tqdm" | ||
require "pathname" | ||
require "open3" | ||
require "base64" | ||
require "mime/types" | ||
|
||
module Poepod | ||
class FileProcessor < Processor | ||
EXCLUDE_DEFAULT = [ | ||
/node_modules\//, /.git\//, /.gitignore$/, /.DS_Store$/, | ||
].freeze | ||
|
||
def initialize(files, output_file, config_file = nil, include_binary = false) | ||
super(config_file) | ||
@files = files | ||
@output_file = output_file | ||
@failed_files = [] | ||
@include_binary = include_binary | ||
end | ||
|
||
def process | ||
total_files = 0 | ||
copied_files = 0 | ||
|
||
File.open(@output_file, "w", encoding: "utf-8") do |output| | ||
@files.each do |file| | ||
Dir.glob(file).each do |matched_file| | ||
if File.file?(matched_file) | ||
total_files += 1 | ||
file_path, content, error = process_file(matched_file) | ||
if content | ||
output.puts "--- START FILE: #{file_path} ---" | ||
output.puts content | ||
output.puts "--- END FILE: #{file_path} ---" | ||
copied_files += 1 | ||
elsif error | ||
output.puts "#{file_path}\n#{error}" | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
[total_files, copied_files] | ||
end | ||
|
||
private | ||
|
||
def process_file(file_path) | ||
if text_file?(file_path) | ||
content = File.read(file_path, encoding: "utf-8") | ||
[file_path, content, nil] | ||
elsif @include_binary | ||
content = encode_binary_file(file_path) | ||
[file_path, content, nil] | ||
else | ||
[file_path, nil, "Skipped binary file"] | ||
end | ||
rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError | ||
@failed_files << file_path | ||
[file_path, nil, "Failed to decode the file, as it is not saved with UTF-8 encoding."] | ||
end | ||
|
||
def text_file?(file_path) | ||
stdout, status = Open3.capture2("file", "-b", "--mime-type", file_path) | ||
status.success? && stdout.strip.start_with?("text/") | ||
end | ||
|
||
def encode_binary_file(file_path) | ||
mime_type = MIME::Types.type_for(file_path).first.content_type | ||
encoded_content = Base64.strict_encode64(File.binread(file_path)) | ||
"Content-Type: #{mime_type}\nContent-Transfer-Encoding: base64\n\n#{encoded_content}" | ||
end | ||
end | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# lib/poepod/gem_processor.rb | ||
require_relative "processor" | ||
require "rubygems/specification" | ||
require "git" | ||
|
||
module Poepod | ||
class GemProcessor < Processor | ||
def initialize(gemspec_path, config_file = nil, include_unstaged = false) | ||
super(config_file) | ||
@gemspec_path = gemspec_path | ||
@include_unstaged = include_unstaged | ||
end | ||
|
||
def process | ||
unless File.exist?(@gemspec_path) | ||
return [false, "Error: The specified gemspec file '#{@gemspec_path}' does not exist."] | ||
end | ||
|
||
begin | ||
spec = Gem::Specification.load(@gemspec_path) | ||
rescue => e | ||
return [false, "Error loading gemspec: #{e.message}"] | ||
end | ||
|
||
gem_name = spec.name | ||
output_file = "#{gem_name}_wrapped.txt" | ||
unstaged_files = check_unstaged_files | ||
|
||
File.open(output_file, "w") do |file| | ||
file.puts "# Wrapped Gem: #{gem_name}" | ||
file.puts "## Gemspec: #{File.basename(@gemspec_path)}" | ||
|
||
if unstaged_files.any? | ||
file.puts "\n## Warning: Unstaged Files" | ||
file.puts unstaged_files.join("\n") | ||
file.puts "\nThese files are not included in the wrap unless --include-unstaged option is used." | ||
end | ||
|
||
file.puts "\n## Files:\n" | ||
|
||
files_to_include = (spec.files + spec.test_files + find_readme_files).uniq | ||
files_to_include += unstaged_files if @include_unstaged | ||
|
||
files_to_include.uniq.each do |relative_path| | ||
full_path = File.join(File.dirname(@gemspec_path), relative_path) | ||
if File.file?(full_path) | ||
file.puts "--- START FILE: #{relative_path} ---" | ||
file.puts File.read(full_path) | ||
file.puts "--- END FILE: #{relative_path} ---\n\n" | ||
end | ||
end | ||
end | ||
|
||
[true, output_file, unstaged_files] | ||
end | ||
|
||
private | ||
|
||
def find_readme_files | ||
Dir.glob(File.join(File.dirname(@gemspec_path), "README*")) | ||
.map { |path| Pathname.new(path).relative_path_from(Pathname.new(File.dirname(@gemspec_path))).to_s } | ||
end | ||
|
||
def check_unstaged_files | ||
gem_root = File.dirname(@gemspec_path) | ||
git = Git.open(gem_root) | ||
|
||
untracked_files = git.status.untracked.keys | ||
modified_files = git.status.changed.keys | ||
|
||
(untracked_files + modified_files).select do |file| | ||
file.start_with?("lib/", "spec/", "test/") | ||
end | ||
rescue Git::GitExecuteError => e | ||
warn "Git error: #{e.message}. Assuming no unstaged files." | ||
[] | ||
end | ||
end | ||
end |
Oops, something went wrong.