poepod 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c54370c2df957654eb4ae1fea362455f862164a7f96fd1b8fd538b86464f57d1
4
- data.tar.gz: 98719e8e94e7da6010cd468c5dcb6c695ae0a4700f35a4436c99ba060265d2ae
3
+ metadata.gz: db687d7a65f167645bedbabce5082c8d254a70f891b493a7f900a4666b07dfc3
4
+ data.tar.gz: 159ed36950788d819b870a2f8d5e8d1e7c26103cdadc11702073bca959885711
5
5
  SHA512:
6
- metadata.gz: 3d81a22b7571bbb1212b43089c4ca8e6e60cfe0a32228fce9116cab97c0b347ac9d3a2b8ccfb752a4b409537f6ce4841f535266c9a7b69e25a27a82d5139691a
7
- data.tar.gz: a96111a90ab4583e043ba316dd08ccf1b3e5530e4ad68f883c9b4626a612adb0ff5e890ab5e688062ff84d4fa26c0fd7619d3210f2d3cea8ddcc9eba99f4ca5e
6
+ metadata.gz: 53e110b148f0e78e7f9c257f18619f4c5718d01d390c8a604fd9a928349a25ca8840a26ac6130dc793f7687783732d360100c96195b88f83a2af20b58bed7723
7
+ data.tar.gz: 007e29ab64c3c3984aaef34a700fe3384c97d3f784f93cda466d8335252b979829b0373d7802737990bf8e65c618fa2e00fd46970a2345e57c35a24a33ddc05d
@@ -1,5 +1,3 @@
1
- # Auto-generated by Cimas: Do not edit it manually!
2
- # See https://github.com/metanorma/cimas
3
1
  name: rake
4
2
 
5
3
  on:
@@ -7,9 +5,8 @@ on:
7
5
  branches: [ master, main ]
8
6
  tags: [ v* ]
9
7
  pull_request:
8
+ workflow_dispatch:
10
9
 
11
10
  jobs:
12
11
  rake:
13
12
  uses: metanorma/ci/.github/workflows/generic-rake.yml@main
14
- secrets:
15
- pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
@@ -1,5 +1,3 @@
1
- # Auto-generated by Cimas: Do not edit it manually!
2
- # See https://github.com/metanorma/cimas
3
1
  name: release
4
2
 
5
3
  on:
@@ -10,8 +8,8 @@ on:
10
8
  Next release version. Possible values: x.y.z, major, minor, patch or pre|rc|etc
11
9
  required: true
12
10
  default: 'skip'
13
- repository_dispatch:
14
- types: [ do-release ]
11
+ push:
12
+ tags: [ v* ]
15
13
 
16
14
  jobs:
17
15
  release:
@@ -19,5 +17,6 @@ jobs:
19
17
  with:
20
18
  next_version: ${{ github.event.inputs.next_version }}
21
19
  secrets:
22
- rubygems-api-key: ${{ secrets.METANORMA_CI_RUBYGEMS_API_KEY }}
23
- pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
20
+ rubygems-api-key: ${{ secrets.RIBOSE_RUBYGEMS_API_KEY }}
21
+ pat_token: ${{ secrets.RIBOSE_CI_PAT_TOKEN }}
22
+
data/README.adoc CHANGED
@@ -1,7 +1,10 @@
1
1
  = Poepod
2
2
 
3
- Poepod is a Ruby gem that provides functionality to concatenate code files from
4
- a directory into one text file for analysis by Poe.
3
+ Poepod is a Ruby gem that streamlines the process of preparing code for analysis
4
+ by Poe. It offers two main features: concatenating multiple files into a single
5
+ text file, and wrapping gem contents including unstaged files. These features
6
+ are particularly useful for developers who want to quickly gather code for
7
+ review, analysis, or submission to AI-powered coding assistants.
5
8
 
6
9
  == Installation
7
10
 
@@ -28,49 +31,102 @@ $ gem install poepod
28
31
 
29
32
  == Usage
30
33
 
31
- After installation, you can use the `poepod` command line tool to concatenate
32
- code files:
34
+ After installation, you can use the `poepod` command line tool:
33
35
 
34
36
  [source,shell]
35
37
  ----
36
38
  $ poepod help
37
39
  Commands:
38
- poepod concat DIRECTORY OUTPUT_FILE # Concatenate code from a directory into one text file
39
- poepod help [COMMAND] # Describe available commands or one specific command
40
+ poepod concat FILES [OUTPUT_FILE] # Concatenate specified files into one text file
41
+ poepod help [COMMAND] # Describe available commands or one specific command
42
+ poepod wrap GEMSPEC_PATH # Wrap a gem based on its gemspec file
43
+ ----
44
+
45
+ === Concatenating files
46
+
47
+ The `concat` command allows you to combine multiple files into a single text
48
+ file. This is particularly useful when you want to review or analyze code from
49
+ multiple files in one place, or when preparing code submissions for AI-powered
50
+ coding assistants.
51
+
52
+ [source,shell]
53
+ ----
54
+ $ poepod concat path/to/files/* output.txt
55
+ ----
56
+
57
+ This will concatenate all files from the specified path into `output.txt`.
58
+
59
+ ==== Excluding patterns
60
+
61
+ You can exclude certain patterns using the `--exclude` option:
62
+
63
+ [source,shell]
64
+ ----
65
+ $ poepod concat path/to/files/* output.txt --exclude node_modules .git build test
66
+ ----
67
+
68
+ This is helpful when you want to focus on specific parts of your codebase,
69
+ excluding irrelevant or large directories.
40
70
 
41
- $ poepod help concat
42
- Usage:
43
- poepod concat DIRECTORY OUTPUT_FILE
71
+ ==== Including binary files
44
72
 
45
- Options:
46
- [--exclude=one two three] # List of patterns to exclude
47
- # Default: "node_modules/" ".git/" "build" "test" ".gitignore" ".DS_Store" "*.jpg" "*.jpeg" "*.png" "*.svg" "*.gif" "*.exe" "*.dll" "*.so" "*.bin" "*.o" "*.a"
48
- [--config=CONFIG] # Path to configuration file
73
+ By default, binary files are excluded to keep the output focused on readable
74
+ code. However, you can include binary files (encoded in MIME format) using the
75
+ `--include-binary` option:
49
76
 
50
- Concatenate code from a directory into one text file
77
+ [source,shell]
78
+ ----
79
+ $ poepod concat path/to/files/* output.txt --include-binary
51
80
  ----
52
81
 
53
- For example:
82
+ This can be useful when you need to include binary assets or compiled files in
83
+ your analysis.
84
+
85
+ === Wrapping a gem
86
+
87
+ The `wrap` command creates a comprehensive snapshot of your gem, including all
88
+ files specified in the gemspec and README files. This is particularly useful for
89
+ gem developers who want to review their entire gem contents or prepare it for
90
+ submission to code review tools.
54
91
 
55
92
  [source,shell]
56
93
  ----
57
- $ poepod concat my_project
58
- # => concatenated into my_project.txt
94
+ $ poepod wrap path/to/your_gem.gemspec
59
95
  ----
60
96
 
61
- This will concatenate all code files from the specified directory into `output.txt`.
97
+ This will create a file named `your_gem_wrapped.txt` containing all the files
98
+ specified in the gemspec, including README files.
99
+
100
+ ==== Handling unstaged files
62
101
 
63
- You can also exclude certain directories or files by using the `--exclude` option:
102
+ By default, unstaged files in the `lib/`, `spec/`, and `test/` directories are
103
+ not included in the wrap, but they will be listed as a warning. This default
104
+ behavior ensures that the wrapped content matches what's currently tracked in
105
+ your version control system.
106
+
107
+ However, there are cases where including unstaged files can be beneficial:
108
+
109
+ . When you're actively developing and want to include recent changes that
110
+ haven't been committed yet.
111
+
112
+ . When you're seeking feedback on work-in-progress code.
113
+
114
+ . When you want to ensure you're not missing any important files in your commit.
115
+
116
+ To include these unstaged files in the wrap:
64
117
 
65
118
  [source,shell]
66
119
  ----
67
- $ poepod concat my_project output.txt --exclude node_modules .git build test .gitignore .DS_Store .jpg .png .svg
120
+ $ poepod wrap path/to/your_gem.gemspec --include-unstaged
68
121
  ----
69
122
 
123
+ This option allows you to capture a true snapshot of your gem's current state,
124
+ including any work in progress.
125
+
70
126
  == Development
71
127
 
72
128
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
73
- `rake test` to run the tests. You can also run `bin/console` for an interactive
129
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
74
130
  prompt that will allow you to experiment.
75
131
 
76
132
  To install this gem onto your local machine, run `bundle exec rake install`. To
@@ -81,4 +137,5 @@ https://rubygems.org.
81
137
 
82
138
  == Contributing
83
139
 
84
- Bug reports and pull requests are welcome on GitHub at https://github.com/riboseinc/poepod.
140
+ Bug reports and pull requests are welcome on GitHub at https://github.com/riboseinc/poepod.
141
+ Please adhere to the link:CODE_OF_CONDUCT.md[code of conduct].
data/lib/poepod/cli.rb CHANGED
@@ -1,36 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # lib/poepod/cli.rb
3
4
  require "thor"
4
- require_relative "processor"
5
+ require_relative "file_processor"
6
+ require_relative "gem_processor"
5
7
 
6
8
  module Poepod
7
9
  class Cli < Thor
8
- desc "concat DIRECTORY OUTPUT_FILE", "Concatenate code from a directory into one text file"
9
- option :exclude, type: :array, default: Poepod::Processor::EXCLUDE_DEFAULT, desc: "List of patterns to exclude"
10
+ desc "concat FILES [OUTPUT_FILE]", "Concatenate specified files into one text file"
11
+ option :exclude, type: :array, default: Poepod::FileProcessor::EXCLUDE_DEFAULT, desc: "List of patterns to exclude"
10
12
  option :config, type: :string, desc: "Path to configuration file"
13
+ option :include_binary, type: :boolean, default: false, desc: "Include binary files (encoded in MIME format)"
11
14
 
12
- def concat(directory, output_file = nil)
13
- dir_path = Pathname.new(directory)
14
-
15
- # Check if the directory exists
16
- unless dir_path.directory?
17
- puts "Error: Directory '#{directory}' does not exist."
15
+ def concat(*files, output_file: nil)
16
+ if files.empty?
17
+ puts "Error: No files specified."
18
18
  exit(1)
19
19
  end
20
20
 
21
- dir_path = dir_path.expand_path unless dir_path.absolute?
21
+ output_file ||= default_output_file(files.first)
22
+ output_path = Pathname.new(output_file).expand_path
22
23
 
23
- output_file ||= "#{dir_path.basename}.txt"
24
- output_path = dir_path.dirname.join(output_file)
25
- processor = Poepod::Processor.new(options[:config])
26
- total_files, copied_files = processor.write_directory_structure_to_file(directory, output_path, options[:exclude])
24
+ processor = Poepod::FileProcessor.new(files, output_path, options[:config], options[:include_binary])
25
+ total_files, copied_files = processor.process
27
26
 
28
- puts "-> #{total_files} files detected in the #{dir_path.relative_path_from(Dir.pwd)} directory."
29
- puts "=> #{copied_files} files have been concatenated into #{Pathname.new(output_path).relative_path_from(Dir.pwd)}."
27
+ puts "-> #{total_files} files detected."
28
+ puts "=> #{copied_files} files have been concatenated into #{output_path.relative_path_from(Dir.pwd)}."
29
+ end
30
+
31
+ desc "wrap GEMSPEC_PATH", "Wrap a gem based on its gemspec file"
32
+ option :include_unstaged, type: :boolean, default: false,
33
+ desc: "Include unstaged files from lib, spec, and test directories"
34
+
35
+ def wrap(gemspec_path)
36
+ processor = Poepod::GemProcessor.new(gemspec_path, nil, options[:include_unstaged])
37
+ success, result, unstaged_files = processor.process
38
+
39
+ if success
40
+ puts "=> The gem has been wrapped into '#{result}'."
41
+ if unstaged_files.any?
42
+ puts "\nWarning: The following files are not staged in git:"
43
+ puts unstaged_files
44
+ puts "\nThese files are #{options[:include_unstaged] ? "included" : "not included"} in the wrap."
45
+ puts "Use --include-unstaged option to include these files." unless options[:include_unstaged]
46
+ end
47
+ else
48
+ puts result
49
+ exit(1)
50
+ end
30
51
  end
31
52
 
32
53
  def self.exit_on_failure?
33
54
  true
34
55
  end
56
+
57
+ private
58
+
59
+ def default_output_file(first_pattern)
60
+ first_item = Dir.glob(first_pattern).first
61
+ if first_item
62
+ if File.directory?(first_item)
63
+ "#{File.basename(first_item)}.txt"
64
+ else
65
+ "#{File.basename(first_item, ".*")}_concat.txt"
66
+ end
67
+ else
68
+ "concatenated_output.txt"
69
+ end
70
+ end
35
71
  end
36
72
  end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "processor"
4
+ require "yaml"
5
+ require "tqdm"
6
+ require "pathname"
7
+ require "open3"
8
+ require "base64"
9
+ require "mime/types"
10
+
11
+ module Poepod
12
+ class FileProcessor < Processor
13
+ EXCLUDE_DEFAULT = [
14
+ %r{node_modules/}, %r{.git/}, /.gitignore$/, /.DS_Store$/
15
+ ].freeze
16
+
17
+ def initialize(files, output_file, config_file = nil, include_binary = false)
18
+ super(config_file)
19
+ @files = files
20
+ @output_file = output_file
21
+ @failed_files = []
22
+ @include_binary = include_binary
23
+ end
24
+
25
+ def process
26
+ total_files = 0
27
+ copied_files = 0
28
+
29
+ File.open(@output_file, "w", encoding: "utf-8") do |output|
30
+ @files.each do |file|
31
+ Dir.glob(file).each do |matched_file|
32
+ next unless File.file?(matched_file)
33
+
34
+ total_files += 1
35
+ file_path, content, error = process_file(matched_file)
36
+ if content
37
+ output.puts "--- START FILE: #{file_path} ---"
38
+ output.puts content
39
+ output.puts "--- END FILE: #{file_path} ---"
40
+ copied_files += 1
41
+ elsif error
42
+ output.puts "#{file_path}\n#{error}"
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ [total_files, copied_files]
49
+ end
50
+
51
+ private
52
+
53
+ def process_file(file_path)
54
+ if text_file?(file_path)
55
+ content = File.read(file_path, encoding: "utf-8")
56
+ [file_path, content, nil]
57
+ elsif @include_binary
58
+ content = encode_binary_file(file_path)
59
+ [file_path, content, nil]
60
+ else
61
+ [file_path, nil, "Skipped binary file"]
62
+ end
63
+ rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError
64
+ @failed_files << file_path
65
+ [file_path, nil, "Failed to decode the file, as it is not saved with UTF-8 encoding."]
66
+ end
67
+
68
+ def text_file?(file_path)
69
+ stdout, status = Open3.capture2("file", "-b", "--mime-type", file_path)
70
+ status.success? && stdout.strip.start_with?("text/")
71
+ end
72
+
73
+ def encode_binary_file(file_path)
74
+ mime_type = MIME::Types.type_for(file_path).first.content_type
75
+ encoded_content = Base64.strict_encode64(File.binread(file_path))
76
+ "Content-Type: #{mime_type}\nContent-Transfer-Encoding: base64\n\n#{encoded_content}"
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/poepod/gem_processor.rb
4
+ require_relative "processor"
5
+ require "rubygems/specification"
6
+ require "git"
7
+
8
+ module Poepod
9
+ class GemProcessor < Processor
10
+ def initialize(gemspec_path, config_file = nil, include_unstaged = false)
11
+ super(config_file)
12
+ @gemspec_path = gemspec_path
13
+ @include_unstaged = include_unstaged
14
+ end
15
+
16
+ def process
17
+ unless File.exist?(@gemspec_path)
18
+ return [false, "Error: The specified gemspec file '#{@gemspec_path}' does not exist."]
19
+ end
20
+
21
+ begin
22
+ spec = Gem::Specification.load(@gemspec_path)
23
+ rescue StandardError => e
24
+ return [false, "Error loading gemspec: #{e.message}"]
25
+ end
26
+
27
+ gem_name = spec.name
28
+ output_file = "#{gem_name}_wrapped.txt"
29
+ unstaged_files = check_unstaged_files
30
+
31
+ File.open(output_file, "w") do |file|
32
+ file.puts "# Wrapped Gem: #{gem_name}"
33
+ file.puts "## Gemspec: #{File.basename(@gemspec_path)}"
34
+
35
+ if unstaged_files.any?
36
+ file.puts "\n## Warning: Unstaged Files"
37
+ file.puts unstaged_files.join("\n")
38
+ file.puts "\nThese files are not included in the wrap unless --include-unstaged option is used."
39
+ end
40
+
41
+ file.puts "\n## Files:\n"
42
+
43
+ files_to_include = (spec.files + spec.test_files + find_readme_files).uniq
44
+ files_to_include += unstaged_files if @include_unstaged
45
+
46
+ files_to_include.uniq.each do |relative_path|
47
+ full_path = File.join(File.dirname(@gemspec_path), relative_path)
48
+ next unless File.file?(full_path)
49
+
50
+ file.puts "--- START FILE: #{relative_path} ---"
51
+ file.puts File.read(full_path)
52
+ file.puts "--- END FILE: #{relative_path} ---\n\n"
53
+ end
54
+ end
55
+
56
+ [true, output_file, unstaged_files]
57
+ end
58
+
59
+ private
60
+
61
+ def find_readme_files
62
+ Dir.glob(File.join(File.dirname(@gemspec_path), "README*"))
63
+ .map { |path| Pathname.new(path).relative_path_from(Pathname.new(File.dirname(@gemspec_path))).to_s }
64
+ end
65
+
66
+ def check_unstaged_files
67
+ gem_root = File.dirname(@gemspec_path)
68
+ git = Git.open(gem_root)
69
+
70
+ untracked_files = git.status.untracked.keys
71
+ modified_files = git.status.changed.keys
72
+
73
+ (untracked_files + modified_files).select do |file|
74
+ file.start_with?("lib/", "spec/", "test/")
75
+ end
76
+ rescue Git::GitExecuteError => e
77
+ warn "Git error: #{e.message}. Assuming no unstaged files."
78
+ []
79
+ end
80
+ end
81
+ end
@@ -1,20 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "yaml"
4
- require "tqdm"
5
- require "pathname"
6
-
3
+ # lib/poepod/processor.rb
7
4
  module Poepod
8
5
  class Processor
9
- EXCLUDE_DEFAULT = [
10
- /node_modules\//, /.git\//, /.gitignore$/, /.DS_Store$/,
11
- /.jpg$/, /.jpeg$/, /.png/, /.svg$/, /.gif$/,
12
- /.exe$/, /.dll$/, /.so$/, /.bin$/, /.o$/, /.a$/, /.gem$/, /.cap$/,
13
- /.zip$/,
14
- ].freeze
15
-
16
6
  def initialize(config_file = nil)
17
- @failed_files = []
18
7
  @config = load_config(config_file)
19
8
  end
20
9
 
@@ -26,61 +15,8 @@ module Poepod
26
15
  end
27
16
  end
28
17
 
29
- def process_file(file_path)
30
- content = File.read(file_path, encoding: "utf-8")
31
- [file_path, content, nil]
32
- rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError
33
- @failed_files << file_path
34
- [file_path, nil, "Failed to decode the file, as it is not saved with UTF-8 encoding."]
35
- end
36
-
37
- def gather_files(directory_path, exclude)
38
- exclude += @config["exclude"] if @config["exclude"]
39
- exclude_pattern = Regexp.union(exclude.map { |ex| Regexp.new(ex) })
40
-
41
- Dir.glob("#{directory_path}/**/*").reject do |file_path|
42
- File.directory?(file_path) || file_path.match?(exclude_pattern)
43
- end.map do |file_path|
44
- Pathname.new(file_path).expand_path.to_s
45
- end
46
- end
47
-
48
- def write_results_to_file(results, output_file)
49
- results.each_with_index do |(file_path, content, error), index|
50
- relative = relative_path(file_path)
51
- if content
52
- output_file.puts "--- START FILE: #{relative} ---"
53
- output_file.puts content
54
- output_file.puts "--- END FILE: #{relative} ---"
55
- elsif error
56
- output_file.puts "#{relative}\n#{error}"
57
- end
58
- output_file.puts if index < results.size - 1 # Add a newline between files
59
- end
60
- end
61
-
62
- def relative_path(file_path)
63
- Pathname.new(file_path).relative_path_from(Dir.pwd)
64
- end
65
-
66
- def write_directory_structure_to_file(directory_path, output_file_name, exclude = EXCLUDE_DEFAULT)
67
- dir_path = Pathname.new(directory_path)
68
-
69
- dir_path = dir_path.expand_path unless dir_path.absolute?
70
-
71
- file_list = gather_files(dir_path, exclude)
72
- total_files = file_list.size
73
-
74
- File.open(output_file_name, "w", encoding: "utf-8") do |output_file|
75
- results = file_list.tqdm(desc: "Progress", unit: " file").map do |file|
76
- process_file(file)
77
- end
78
- write_results_to_file(results, output_file)
79
- end
80
-
81
- copied_files = total_files - @failed_files.size
82
-
83
- [total_files, copied_files]
18
+ def process
19
+ raise NotImplementedError, "Subclasses must implement the 'process' method"
84
20
  end
85
21
  end
86
22
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Poepod
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
data/poepod.gemspec CHANGED
@@ -16,10 +16,6 @@ Gem::Specification.new do |spec|
16
16
  spec.homepage = "https://github.com/riboseinc/poepod"
17
17
  spec.license = "BSD-2-Clause"
18
18
 
19
- spec.bindir = "bin"
20
- spec.require_paths = ["lib"]
21
- spec.files = `git ls-files`.split("\n")
22
- spec.test_files = `git ls-files -- {spec}/*`.split("\n")
23
19
  spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
24
20
 
25
21
  # Specify which files should be added to the gem when it is released.
@@ -32,10 +28,14 @@ Gem::Specification.new do |spec|
32
28
  spec.bindir = "exe"
33
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
30
  spec.require_paths = ["lib"]
31
+ spec.test_files = `git ls-files -- spec/*`.split("\n")
35
32
 
33
+ spec.add_runtime_dependency "git", "~> 1.11"
34
+ spec.add_runtime_dependency "mime-types", "~> 3.3"
36
35
  spec.add_runtime_dependency "parallel", "~> 1.20"
37
36
  spec.add_runtime_dependency "thor", "~> 1.0"
38
37
  spec.add_runtime_dependency "tqdm"
38
+
39
39
  spec.add_development_dependency "rake"
40
40
  spec.add_development_dependency "rspec"
41
41
  spec.add_development_dependency "rubocop"
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "poepod/cli"
5
+
6
+ RSpec.describe Poepod::Cli do
7
+ let(:cli) { described_class.new }
8
+
9
+ describe "#concat" do
10
+ let(:temp_dir) { Dir.mktmpdir }
11
+ let(:text_file) { File.join(temp_dir, "text_file.txt") }
12
+ let(:binary_file) { File.join(temp_dir, "binary_file.bin") }
13
+
14
+ before do
15
+ File.write(text_file, "Hello, World!")
16
+ File.write(binary_file, [0xFF, 0xD8, 0xFF, 0xE0].pack("C*"))
17
+ end
18
+
19
+ after do
20
+ FileUtils.remove_entry(temp_dir)
21
+ end
22
+
23
+ it "concatenates text files" do
24
+ output_file = File.join(temp_dir, "output.txt")
25
+ expect { cli.concat(text_file, output_file: output_file) }.to output(/1 files detected/).to_stdout
26
+ expect(File.exist?(output_file)).to be true
27
+ expect(File.read(output_file)).to include("Hello, World!")
28
+ end
29
+
30
+ it "excludes binary files by default" do
31
+ output_file = File.join(temp_dir, "output.txt")
32
+ expect do
33
+ cli.concat(text_file, binary_file,
34
+ output_file: output_file)
35
+ end.to output(/-> 2 files detected\.\n=> 1 files have been concatenated into.*\.txt/).to_stdout
36
+ end
37
+
38
+ it "includes binary files when specified" do
39
+ output_file = File.join(temp_dir, "output.txt")
40
+ expect do
41
+ cli.invoke(:concat, [text_file, binary_file], output_file: output_file,
42
+ include_binary: true)
43
+ end.to output(/-> 2 files detected\.\n=> 2 files have been concatenated into.*\.txt/).to_stdout
44
+ end
45
+ end
46
+
47
+ describe "#wrap" do
48
+ let(:temp_dir) { Dir.mktmpdir }
49
+ let(:gemspec_file) { File.join(temp_dir, "test_gem.gemspec") }
50
+
51
+ before do
52
+ File.write(gemspec_file, <<~GEMSPEC)
53
+ Gem::Specification.new do |spec|
54
+ spec.name = "test_gem"
55
+ spec.version = "0.1.0"
56
+ spec.authors = ["Test Author"]
57
+ spec.files = ["lib/test_gem.rb"]
58
+ end
59
+ GEMSPEC
60
+
61
+ FileUtils.mkdir_p(File.join(temp_dir, "lib"))
62
+ File.write(File.join(temp_dir, "lib/test_gem.rb"), "puts 'Hello from test_gem'")
63
+
64
+ # Mock Git operations
65
+ allow(Git).to receive(:open).and_return(double(status: double(untracked: {}, changed: {})))
66
+ end
67
+
68
+ after do
69
+ FileUtils.remove_entry(temp_dir)
70
+ end
71
+
72
+ it "wraps a gem" do
73
+ expect { cli.wrap(gemspec_file) }.to output(/The gem has been wrapped into/).to_stdout
74
+ output_file = File.join(Dir.pwd, "test_gem_wrapped.txt")
75
+ expect(File.exist?(output_file)).to be true
76
+ content = File.read(output_file)
77
+ expect(content).to include("# Wrapped Gem: test_gem")
78
+ expect(content).to include("## Gemspec: test_gem.gemspec")
79
+ expect(content).to include("--- START FILE: lib/test_gem.rb ---")
80
+ expect(content).to include("puts 'Hello from test_gem'")
81
+ expect(content).to include("--- END FILE: lib/test_gem.rb ---")
82
+ end
83
+
84
+ it "handles non-existent gemspec" do
85
+ expect do
86
+ cli.wrap("non_existent.gemspec")
87
+ end.to output(/Error: The specified gemspec file/).to_stdout.and raise_error(SystemExit)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # spec/poepod/file_processor_spec.rb
4
+ require "spec_helper"
5
+ require "poepod/file_processor"
6
+ require "tempfile"
7
+
8
+ RSpec.describe Poepod::FileProcessor do
9
+ let(:temp_dir) { Dir.mktmpdir }
10
+ let(:output_file) { Tempfile.new("output.txt") }
11
+ let(:text_file1) { File.join(temp_dir, "file1.txt") }
12
+ let(:text_file2) { File.join(temp_dir, "file2.txt") }
13
+ let(:binary_file) { File.join(temp_dir, "binary_file.bin") }
14
+
15
+ before do
16
+ File.write(text_file1, "Content of file1.\n")
17
+ File.write(text_file2, "Content of file2.\n")
18
+ File.write(binary_file, [0xFF, 0xD8, 0xFF, 0xE0].pack("C*"))
19
+ end
20
+
21
+ after do
22
+ FileUtils.remove_entry(temp_dir)
23
+ output_file.unlink
24
+ end
25
+
26
+ let(:processor) { described_class.new([text_file1, text_file2], output_file.path) }
27
+
28
+ describe "#process" do
29
+ it "processes text files and writes them to the output file" do
30
+ total_files, copied_files = processor.process
31
+ expect(total_files).to eq(2)
32
+ expect(copied_files).to eq(2)
33
+
34
+ output_content = File.read(output_file.path, encoding: "utf-8")
35
+ expected_content = <<~TEXT
36
+ --- START FILE: #{text_file1} ---
37
+ Content of file1.
38
+ --- END FILE: #{text_file1} ---
39
+ --- START FILE: #{text_file2} ---
40
+ Content of file2.
41
+ --- END FILE: #{text_file2} ---
42
+ TEXT
43
+ expect(output_content).to eq(expected_content)
44
+ end
45
+ end
46
+
47
+ describe "#process_file" do
48
+ it "reads the content of a file" do
49
+ file_path, content, error = processor.send(:process_file, text_file1)
50
+ expect(file_path).to eq(text_file1)
51
+ expect(content).to eq("Content of file1.\n")
52
+ expect(error).to be_nil
53
+ end
54
+
55
+ it "handles encoding errors gracefully" do
56
+ allow(File).to receive(:read).and_raise(Encoding::InvalidByteSequenceError)
57
+ file_path, content, error = processor.send(:process_file, text_file1)
58
+ expect(file_path).to eq(text_file1)
59
+ expect(content).to be_nil
60
+ expect(error).to eq("Failed to decode the file, as it is not saved with UTF-8 encoding.")
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ # spec/poepod/gem_processor_spec.rb
4
+ require "spec_helper"
5
+ require "poepod/gem_processor"
6
+ require "tempfile"
7
+
8
+ RSpec.describe Poepod::GemProcessor do
9
+ let(:temp_dir) { Dir.mktmpdir }
10
+ let(:gemspec_file) { File.join(temp_dir, "test_gem.gemspec") }
11
+
12
+ before do
13
+ File.write(gemspec_file, <<~GEMSPEC)
14
+ Gem::Specification.new do |spec|
15
+ spec.name = "test_gem"
16
+ spec.version = "0.1.0"
17
+ spec.authors = ["Test Author"]
18
+ spec.files = ["lib/test_gem.rb"]
19
+ spec.test_files = ["spec/test_gem_spec.rb"]
20
+ end
21
+ GEMSPEC
22
+
23
+ FileUtils.mkdir_p(File.join(temp_dir, "lib"))
24
+ FileUtils.mkdir_p(File.join(temp_dir, "spec"))
25
+ File.write(File.join(temp_dir, "lib/test_gem.rb"), "puts 'Hello from test_gem'")
26
+ File.write(File.join(temp_dir, "spec/test_gem_spec.rb"), "RSpec.describe TestGem do\nend")
27
+ File.write(File.join(temp_dir, "README.md"), "# Test Gem\n\nThis is a test gem.")
28
+ File.write(File.join(temp_dir, "README.txt"), "Test Gem\n\nThis is a test gem in plain text.")
29
+ end
30
+
31
+ after do
32
+ FileUtils.remove_entry(temp_dir)
33
+ end
34
+
35
+ describe "#process" do
36
+ let(:processor) { described_class.new(gemspec_file) }
37
+
38
+ before do
39
+ # Mock Git operations
40
+ allow(Git).to receive(:open).and_return(double(status: double(untracked: {}, changed: {})))
41
+ end
42
+
43
+ it "processes the gem files, includes README files, and spec files" do
44
+ success, output_file = processor.process
45
+ expect(success).to be true
46
+ expect(File.exist?(output_file)).to be true
47
+
48
+ content = File.read(output_file)
49
+ expect(content).to include("# Wrapped Gem: test_gem")
50
+ expect(content).to include("## Gemspec: test_gem.gemspec")
51
+ expect(content).to include("--- START FILE: lib/test_gem.rb ---")
52
+ expect(content).to include("puts 'Hello from test_gem'")
53
+ expect(content).to include("--- END FILE: lib/test_gem.rb ---")
54
+ expect(content).to include("--- START FILE: spec/test_gem_spec.rb ---")
55
+ expect(content).to include("RSpec.describe TestGem do")
56
+ expect(content).to include("--- END FILE: spec/test_gem_spec.rb ---")
57
+ expect(content).to include("--- START FILE: README.md ---")
58
+ expect(content).to include("# Test Gem\n\nThis is a test gem.")
59
+ expect(content).to include("--- END FILE: README.md ---")
60
+ expect(content).to include("--- START FILE: README.txt ---")
61
+ expect(content).to include("Test Gem\n\nThis is a test gem in plain text.")
62
+ expect(content).to include("--- END FILE: README.txt ---")
63
+ end
64
+
65
+ context "with non-existent gemspec" do
66
+ let(:processor) { described_class.new("non_existent.gemspec") }
67
+
68
+ it "returns an error" do
69
+ success, error_message = processor.process
70
+ expect(success).to be false
71
+ expect(error_message).to include("Error: The specified gemspec file")
72
+ end
73
+ end
74
+
75
+ context "with unstaged files" do
76
+ let(:mock_git) { instance_double(Git::Base) }
77
+ let(:mock_status) { instance_double(Git::Status) }
78
+
79
+ before do
80
+ allow(Git).to receive(:open).and_return(mock_git)
81
+ allow(mock_git).to receive(:status).and_return(mock_status)
82
+ allow(mock_status).to receive(:untracked).and_return({ "lib/unstaged_file.rb" => "??" })
83
+ allow(mock_status).to receive(:changed).and_return({})
84
+ end
85
+
86
+ it "warns about unstaged files" do
87
+ success, output_file, unstaged_files = processor.process
88
+ expect(success).to be true
89
+ expect(unstaged_files).to eq(["lib/unstaged_file.rb"])
90
+
91
+ content = File.read(output_file)
92
+ expect(content).to include("## Warning: Unstaged Files")
93
+ expect(content).to include("lib/unstaged_file.rb")
94
+ end
95
+
96
+ context "with include_unstaged option" do
97
+ let(:processor) { described_class.new(gemspec_file, nil, true) }
98
+
99
+ it "includes unstaged files" do
100
+ allow(File).to receive(:file?).and_return(true)
101
+
102
+ # Create a hash to store file contents
103
+ file_contents = {
104
+ "lib/test_gem.rb" => "puts 'Hello from test_gem'",
105
+ "spec/test_gem_spec.rb" => "RSpec.describe TestGem do\nend",
106
+ "README.md" => "# Test Gem\n\nThis is a test gem.",
107
+ "README.txt" => "Test Gem\n\nThis is a test gem in plain text.",
108
+ "lib/unstaged_file.rb" => "Unstaged content"
109
+ }
110
+
111
+ # Mock File.read
112
+ allow(File).to receive(:read) do |path|
113
+ file_name = File.basename(path)
114
+ if file_contents.key?(file_name)
115
+ file_contents[file_name]
116
+ elsif path.end_with?("_wrapped.txt")
117
+ # This is the output file, so we'll construct its content here
118
+ wrapped_content = "# Wrapped Gem: test_gem\n"
119
+ wrapped_content += "## Gemspec: test_gem.gemspec\n\n"
120
+ wrapped_content += "## Warning: Unstaged Files\n"
121
+ wrapped_content += "lib/unstaged_file.rb\n\n"
122
+ wrapped_content += "## Files:\n\n"
123
+ file_contents.each do |file, content|
124
+ wrapped_content += "--- START FILE: #{file} ---\n"
125
+ wrapped_content += "#{content}\n"
126
+ wrapped_content += "--- END FILE: #{file} ---\n\n"
127
+ end
128
+ wrapped_content
129
+ else
130
+ "Default content for #{path}"
131
+ end
132
+ end
133
+
134
+ success, output_file, unstaged_files = processor.process
135
+ expect(success).to be true
136
+ expect(unstaged_files).to eq(["lib/unstaged_file.rb"])
137
+
138
+ content = File.read(output_file)
139
+ expect(content).to include("--- START FILE: lib/test_gem.rb ---")
140
+ expect(content).to include("puts 'Hello from test_gem'")
141
+ expect(content).to include("--- END FILE: lib/test_gem.rb ---")
142
+ expect(content).to include("--- START FILE: spec/test_gem_spec.rb ---")
143
+ expect(content).to include("RSpec.describe TestGem do")
144
+ expect(content).to include("--- END FILE: spec/test_gem_spec.rb ---")
145
+ expect(content).to include("--- START FILE: README.md ---")
146
+ expect(content).to include("# Test Gem\n\nThis is a test gem.")
147
+ expect(content).to include("--- END FILE: README.md ---")
148
+ expect(content).to include("--- START FILE: README.txt ---")
149
+ expect(content).to include("Test Gem\n\nThis is a test gem in plain text.")
150
+ expect(content).to include("--- END FILE: README.txt ---")
151
+ expect(content).to include("--- START FILE: lib/unstaged_file.rb ---")
152
+ expect(content).to include("Unstaged content")
153
+ expect(content).to include("--- END FILE: lib/unstaged_file.rb ---")
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Poepod do
4
+ it "has a version number" do
5
+ expect(Poepod::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "poepod"
4
+ require "fileutils"
5
+
6
+ RSpec.configure do |config|
7
+ # Enable flags like --only-failures and --next-failure
8
+ config.example_status_persistence_file_path = ".rspec_status"
9
+
10
+ # Disable RSpec exposing methods globally on `Module` and `main`
11
+ config.disable_monkey_patching!
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+ end
@@ -0,0 +1 @@
1
+ Content of file1.
@@ -0,0 +1 @@
1
+ Content of file2.
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poepod
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-12 00:00:00.000000000 Z
11
+ date: 2024-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: git
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mime-types
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.3'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: parallel
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -146,15 +174,24 @@ files:
146
174
  - exe/poepod
147
175
  - lib/poepod.rb
148
176
  - lib/poepod/cli.rb
177
+ - lib/poepod/file_processor.rb
178
+ - lib/poepod/gem_processor.rb
149
179
  - lib/poepod/processor.rb
150
180
  - lib/poepod/version.rb
151
181
  - poepod.gemspec
152
182
  - sig/poepod.rbs
183
+ - spec/poepod/cli_spec.rb
184
+ - spec/poepod/file_processor_spec.rb
185
+ - spec/poepod/gem_processor_spec.rb
186
+ - spec/poepod_spec.rb
187
+ - spec/spec_helper.rb
188
+ - spec/support/test_files/file1.txt
189
+ - spec/support/test_files/file2.txt
153
190
  homepage: https://github.com/riboseinc/poepod
154
191
  licenses:
155
192
  - BSD-2-Clause
156
193
  metadata: {}
157
- post_install_message:
194
+ post_install_message:
158
195
  rdoc_options: []
159
196
  require_paths:
160
197
  - lib
@@ -169,8 +206,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
206
  - !ruby/object:Gem::Version
170
207
  version: '0'
171
208
  requirements: []
172
- rubygems_version: 3.5.11
173
- signing_key:
209
+ rubygems_version: 3.3.27
210
+ signing_key:
174
211
  specification_version: 4
175
212
  summary: Utilities for uploading code to Poe
176
- test_files: []
213
+ test_files:
214
+ - spec/poepod/cli_spec.rb
215
+ - spec/poepod/file_processor_spec.rb
216
+ - spec/poepod/gem_processor_spec.rb
217
+ - spec/poepod_spec.rb
218
+ - spec/spec_helper.rb
219
+ - spec/support/test_files/file1.txt
220
+ - spec/support/test_files/file2.txt