From 2ef1fae73f15061a1207ec17e55ae55282adb601 Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Wed, 24 Jul 2024 18:08:28 +0800 Subject: [PATCH] feat: make gem work again --- lib/poepod/cli.rb | 16 +++---- lib/poepod/file_processor.rb | 20 +++------ lib/poepod/gem_processor.rb | 33 +++++++------- lib/poepod/processor.rb | 72 ++++++++++++++++++++---------- poepod.gemspec | 2 - spec/poepod/cli_spec.rb | 15 +++---- spec/poepod/file_processor_spec.rb | 10 ++--- spec/poepod/gem_processor_spec.rb | 12 ++--- 8 files changed, 98 insertions(+), 82 deletions(-) diff --git a/lib/poepod/cli.rb b/lib/poepod/cli.rb index f6b9f7d..0c3e2cc 100644 --- a/lib/poepod/cli.rb +++ b/lib/poepod/cli.rb @@ -1,6 +1,6 @@ +# lib/poepod/cli.rb # frozen_string_literal: true -# lib/poepod/cli.rb require "thor" require_relative "file_processor" require_relative "gem_processor" @@ -10,7 +10,7 @@ module Poepod class Cli < Thor # Define shared options def self.shared_options - option :exclude, type: :array, default: Poepod::FileProcessor::EXCLUDE_DEFAULT, + option :exclude, type: :array, default: nil, 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)" @@ -36,6 +36,7 @@ def concat(*files) def wrap(gemspec_path) base_dir = options[:base_dir] || File.dirname(gemspec_path) + output_file = options[:output_file] || File.join(base_dir, "#{File.basename(gemspec_path, ".*")}_wrapped.txt") processor = Poepod::GemProcessor.new( gemspec_path, include_unstaged: options[:include_unstaged], @@ -43,9 +44,9 @@ def wrap(gemspec_path) include_binary: options[:include_binary], include_dot_files: options[:include_dot_files], base_dir: base_dir, - config_file: options[:config] + config_file: options[:config], ) - success, result, unstaged_files = processor.process + success, result, unstaged_files = processor.process(output_file) if success handle_wrap_result(success, result, unstaged_files) else @@ -80,9 +81,9 @@ def process_files(files, output_file, base_dir) include_binary: options[:include_binary], include_dot_files: options[:include_dot_files], exclude: options[:exclude], - base_dir: base_dir + base_dir: base_dir, ) - total_files, copied_files = processor.process + total_files, copied_files = processor.process(output_path.to_s) print_result(total_files, copied_files, output_path) end @@ -114,8 +115,7 @@ def default_output_file(first_pattern) if File.directory?(first_item) "#{File.basename(first_item)}.txt" else - "#{File.basename(first_item, - ".*")}_concat.txt" + "#{File.basename(first_item, ".*")}_concat.txt" end else "concatenated_output.txt" diff --git a/lib/poepod/file_processor.rb b/lib/poepod/file_processor.rb index 79dd985..49f9315 100644 --- a/lib/poepod/file_processor.rb +++ b/lib/poepod/file_processor.rb @@ -1,3 +1,4 @@ +# lib/poepod/file_processor.rb # frozen_string_literal: true require_relative "processor" @@ -5,17 +6,13 @@ module Poepod # Processes files for concatenation, handling binary and dot files class FileProcessor < Processor - EXCLUDE_DEFAULT = [ - %r{node_modules/}, %r{.git/}, /.gitignore$/, /.DS_Store$/, /^\..+/ - ].freeze - def initialize( - files, + patterns, output_file, config_file: nil, include_binary: false, include_dot_files: false, - exclude: [], + exclude: nil, base_dir: nil ) super( @@ -25,20 +22,15 @@ def initialize( exclude: exclude, base_dir: base_dir, ) - @files = files + @patterns = patterns @output_file = output_file end private def collect_files_to_process - @files.flatten.each_with_object([]) do |file, files_to_process| - Dir.glob(file, File::FNM_DOTMATCH).each do |matched_file| - next unless File.file?(matched_file) - next if should_exclude?(matched_file) - - files_to_process << matched_file - end + @patterns.flatten.each_with_object([]) do |pattern, files_to_process| + files_to_process.concat(collect_files_from_pattern(pattern)) end end end diff --git a/lib/poepod/gem_processor.rb b/lib/poepod/gem_processor.rb index 649db00..cd92dc7 100644 --- a/lib/poepod/gem_processor.rb +++ b/lib/poepod/gem_processor.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# lib/poepod/gem_processor.rb require_relative "processor" require "rubygems/specification" require "git" @@ -11,7 +10,7 @@ class GemProcessor < Processor def initialize( gemspec_path, include_unstaged: false, - exclude: [], + exclude: nil, include_binary: false, include_dot_files: false, base_dir: nil, @@ -28,29 +27,23 @@ def initialize( @include_unstaged = include_unstaged end - def process + def process(output_file) return error_no_gemspec unless File.exist?(@gemspec_path) spec = load_gemspec return spec unless spec.is_a?(Gem::Specification) - gem_name = spec.name - @output_file = "#{gem_name}_wrapped.txt" unstaged_files = check_unstaged_files - super() + total_files, copied_files = super(output_file) - [true, @output_file, unstaged_files] + [true, output_file, unstaged_files] end private def collect_files_to_process - spec = load_gemspec - files_to_include = (spec.files + - spec.test_files + - find_readme_files).uniq - + files_to_include = find_gemspec_files files_to_include += check_unstaged_files if @include_unstaged files_to_include.sort.uniq.reject do |relative_path| @@ -60,6 +53,13 @@ def collect_files_to_process end end + def find_gemspec_files + spec = load_gemspec + executables = spec.bindir ? collect_files_from_pattern(File.join(@base_dir, spec.bindir, "*")) : [] + + (spec.files + spec.test_files + find_readme_files + executables).uniq + end + def error_no_gemspec [false, "Error: The specified gemspec file '#{@gemspec_path}' does not exist."] end @@ -71,10 +71,9 @@ def load_gemspec end def find_readme_files - Dir.glob(File.join(File.dirname(@gemspec_path), "README*")).map do |path| - Pathname.new(path).relative_path_from( - Pathname.new(File.dirname(@gemspec_path)) - ).to_s + gemspec_dir = Pathname.new(File.dirname(@gemspec_path)) + Dir.glob(gemspec_dir.join("README*")).map do |path| + Pathname.new(path).relative_path_from(gemspec_dir).to_s end end @@ -86,7 +85,7 @@ def check_unstaged_files modified_files = git.status.changed.keys (untracked_files + modified_files).select do |file| - file.start_with?("lib/", "spec/", "test/") + file.start_with?("bin/", "exe/", "lib/", "spec/", "test/") end rescue Git::GitExecuteError => e warn "Git error: #{e.message}. Assuming no unstaged files." diff --git a/lib/poepod/processor.rb b/lib/poepod/processor.rb index 84ed0a3..5245ae3 100644 --- a/lib/poepod/processor.rb +++ b/lib/poepod/processor.rb @@ -1,3 +1,4 @@ +# lib/poepod/processor.rb # frozen_string_literal: true require "yaml" @@ -8,33 +9,65 @@ module Poepod # Base processor class class Processor + EXCLUDE_DEFAULT = [ + %r{node_modules/}, %r{.git/}, /.gitignore$/, /.DS_Store$/, + ].freeze + def initialize( config_file = nil, include_binary: false, include_dot_files: false, - exclude: [], + exclude: nil, base_dir: nil ) @config = load_config(config_file) @include_binary = include_binary @include_dot_files = include_dot_files - @exclude = exclude || [] + @exclude = exclude || EXCLUDE_DEFAULT @base_dir = base_dir @failed_files = [] end - def process + def process(output_file) files_to_process = collect_files_to_process - total_files, copied_files = process_files(files_to_process) + total_files, copied_files = process_files(files_to_process, output_file) [total_files, copied_files] end private + def process_files(files, output_file) + total_files = files.size + copied_files = 0 + + File.open(output_file, "w", encoding: "utf-8") do |output| + files.sort.each do |file_path| + process_file(output, file_path) + copied_files += 1 + end + end + + [total_files, copied_files] + end + def collect_files_to_process raise NotImplementedError, "Subclasses must implement collect_files_to_process" end + def collect_files_from_pattern(pattern) + expanded_pattern = File.expand_path(pattern) + if File.directory?(expanded_pattern) + expanded_pattern = File.join(expanded_pattern, "**", "*") + end + + Dir.glob(expanded_pattern, File::FNM_DOTMATCH).each_with_object([]) do |file_path, acc| + next unless File.file?(file_path) + next if should_exclude?(file_path) + + acc << file_path + end + end + def load_config(config_file) return {} unless config_file && File.exist?(config_file) @@ -46,33 +79,26 @@ def binary_file?(file_path) File.open(file_path, "rb") do |file| content = file.read(8192) # Read first 8KB for magic byte detection - mime_type = Marcel::MimeType.for(content, name: File.basename(file_path), declared_type: "text/plain") - !mime_type.start_with?("text/") && mime_type != "application/json" - end - end + mime_type = Marcel::MimeType.for( + content, + name: File.basename(file_path), + declared_type: "text/plain", + ) - def process_files(files) - total_files = files.size - copied_files = 0 - - File.open(@output_file, "w", encoding: "utf-8") do |output| - files.sort.each do |file_path| - process_file(output, file_path) - copied_files += 1 - end + !mime_type.start_with?("text/") && mime_type != "application/json" end - - [total_files, copied_files] end def process_file(output = nil, file_path) output ||= StringIO.new relative_path = if @base_dir - Pathname.new(file_path).relative_path_from(Pathname.new(@base_dir)).to_s - else - file_path - end + Pathname.new(file_path).relative_path_from(@base_dir).to_s + else + file_path + end + + puts "Adding to bundle: #{relative_path}" output.puts "--- START FILE: #{relative_path} ---" diff --git a/poepod.gemspec b/poepod.gemspec index df05be3..3fbb152 100644 --- a/poepod.gemspec +++ b/poepod.gemspec @@ -18,8 +18,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0") - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. spec.files = Dir.chdir(File.expand_path(__dir__)) do `git ls-files -z`.split("\x0").reject do |f| f.match(%r{^(test|spec|features)/}) diff --git a/spec/poepod/cli_spec.rb b/spec/poepod/cli_spec.rb index b7829db..cb471a1 100644 --- a/spec/poepod/cli_spec.rb +++ b/spec/poepod/cli_spec.rb @@ -25,7 +25,7 @@ it "concatenates text files and excludes binary and dot files by default" do output_file = File.join(temp_dir, "output.txt") expect do - cli.invoke(:concat, [File.join(temp_dir, "*")], { output_file: output_file }) + cli.invoke(:concat, [text_file], { output_file: output_file }) end.to output(/1 files detected\.\n.*1 files have been concatenated/).to_stdout expect(File.exist?(output_file)).to be true content = File.read(output_file) @@ -36,7 +36,7 @@ it "includes binary files when specified" do output_file = File.join(temp_dir, "output.txt") expect do - cli.invoke(:concat, [File.join(temp_dir, "*")], { output_file: output_file, include_binary: true }) + cli.invoke(:concat, [text_file, binary_file], { output_file: output_file, include_binary: true }) end.to output(/2 files detected\.\n.*2 files have been concatenated/).to_stdout expect(File.exist?(output_file)).to be true content = File.read(output_file) @@ -47,7 +47,7 @@ it "includes dot files when specified" do output_file = File.join(temp_dir, "output.txt") expect do - cli.invoke(:concat, [File.join(temp_dir, "*")], { output_file: output_file, include_dot_files: true }) + cli.invoke(:concat, [text_file, dot_file], { output_file: output_file, include_dot_files: true }) end.to output(/2 files detected\.\n.*2 files have been concatenated/).to_stdout expect(File.exist?(output_file)).to be true content = File.read(output_file) @@ -57,9 +57,8 @@ it "uses the specified base directory for relative paths" do output_file = File.join(temp_dir, "output.txt") - base_dir = File.dirname(text_file) expect do - cli.invoke(:concat, [File.join(temp_dir, "*")], { output_file: output_file, base_dir: base_dir }) + cli.invoke(:concat, [text_file], { output_file: output_file, base_dir: temp_dir }) end.to output(/1 files detected\.\n.*1 files have been concatenated/).to_stdout expect(File.exist?(output_file)).to be true content = File.read(output_file) @@ -95,8 +94,8 @@ end it "wraps a gem" do + output_file = File.join(temp_dir, "test_gem_wrapped.txt") expect { cli.wrap(gemspec_file) }.to output(/The gem has been wrapped into/).to_stdout - output_file = File.join(Dir.pwd, "test_gem_wrapped.txt") expect(File.exist?(output_file)).to be true content = File.read(output_file) expect(content).to include("--- START FILE: lib/test_gem.rb ---") @@ -112,10 +111,10 @@ it "uses the specified base directory for relative paths" do base_dir = File.dirname(gemspec_file) + output_file = File.join(base_dir, "test_gem_wrapped.txt") expect do - cli.invoke(:wrap, [gemspec_file], { base_dir: base_dir }) + cli.invoke(:wrap, [gemspec_file], { base_dir: base_dir, output_file: output_file }) end.to output(/The gem has been wrapped into/).to_stdout - output_file = File.join(Dir.pwd, "test_gem_wrapped.txt") expect(File.exist?(output_file)).to be true content = File.read(output_file) expect(content).to include("--- START FILE: lib/test_gem.rb ---") diff --git a/spec/poepod/file_processor_spec.rb b/spec/poepod/file_processor_spec.rb index 487d477..d2b14a1 100644 --- a/spec/poepod/file_processor_spec.rb +++ b/spec/poepod/file_processor_spec.rb @@ -27,10 +27,10 @@ describe "#process" do context "with default options" do - let(:processor) { described_class.new([File.join(temp_dir, "*")], output_file.path) } + let(:processor) { described_class.new([text_file1, text_file2], output_file.path) } it "processes text files and excludes binary and dot files" do - total_files, copied_files = processor.process + total_files, copied_files = processor.process(output_file.path) expect(total_files).to eq(2) expect(copied_files).to eq(2) @@ -51,7 +51,7 @@ let(:processor) { described_class.new([File.join(temp_dir, "*")], output_file.path, include_binary: true) } it "includes binary files" do - total_files, copied_files = processor.process + total_files, copied_files = processor.process(output_file.path) expect(total_files).to eq(3) expect(copied_files).to eq(3) @@ -78,7 +78,7 @@ let(:processor) { described_class.new([File.join(temp_dir, "*")], output_file.path, include_dot_files: true) } it "includes dot files" do - total_files, copied_files = processor.process + total_files, copied_files = processor.process(output_file.path) expect(total_files).to eq(3) expect(copied_files).to eq(3) @@ -104,7 +104,7 @@ end it "includes all files in sorted order" do - total_files, copied_files = processor.process + total_files, copied_files = processor.process(output_file.path) expect(total_files).to eq(4) expect(copied_files).to eq(4) diff --git a/spec/poepod/gem_processor_spec.rb b/spec/poepod/gem_processor_spec.rb index 4a063fc..f4a85c9 100644 --- a/spec/poepod/gem_processor_spec.rb +++ b/spec/poepod/gem_processor_spec.rb @@ -34,6 +34,7 @@ describe "#process" do let(:processor) { described_class.new(gemspec_file) } + let(:output_file) { File.join(temp_dir, "test_gem_wrapped.txt") } before do # Mock Git operations @@ -41,7 +42,7 @@ end it "processes the gem files, includes README files, and spec files in sorted order" do - success, output_file = processor.process + success, result, _ = processor.process(output_file) expect(success).to be true expect(File.exist?(output_file)).to be true @@ -52,7 +53,7 @@ "README.md", "README.txt", "lib/test_gem.rb", - "spec/test_gem_spec.rb" + "spec/test_gem_spec.rb", ] expect(file_order).to eq(expected_order) @@ -82,7 +83,7 @@ let(:processor) { described_class.new("non_existent.gemspec") } it "returns an error" do - success, error_message = processor.process + success, error_message, _ = processor.process(output_file) expect(success).to be false expect(error_message).to include("Error: The specified gemspec file") end @@ -104,6 +105,7 @@ context "with include_unstaged option" do let(:processor) { described_class.new(gemspec_file, include_unstaged: true) } + let(:output_file) { File.join(temp_dir, "test_gem_wrapped.txt") } it "includes unstaged files" do allow(File).to receive(:file?).and_return(true) @@ -114,7 +116,7 @@ "spec/test_gem_spec.rb" => "RSpec.describe TestGem do\nend", "README.md" => "# Test Gem\n\nThis is a test gem.", "README.txt" => "Test Gem\n\nThis is a test gem in plain text.", - "lib/unstaged_file.rb" => "Unstaged content" + "lib/unstaged_file.rb" => "Unstaged content", } # Mock File.read @@ -136,7 +138,7 @@ end end - success, output_file, unstaged_files = processor.process + success, result, unstaged_files = processor.process(output_file) expect(success).to be true expect(unstaged_files).to eq(["lib/unstaged_file.rb"])