poepod 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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