poepod 0.1.2 → 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: 74aa4bb662884ffcb18e91baf9e8b25d892fd863218556e80a78ed30e603835d
4
- data.tar.gz: 0d8c004bac28b64d0b3cb47732a118b28bca2fdcabb2c7f31ed5d8a574d554b2
3
+ metadata.gz: db687d7a65f167645bedbabce5082c8d254a70f891b493a7f900a4666b07dfc3
4
+ data.tar.gz: 159ed36950788d819b870a2f8d5e8d1e7c26103cdadc11702073bca959885711
5
5
  SHA512:
6
- metadata.gz: b21d65bed30d5eaf4ae15f29434d94cd25546ba69f72c00c01c89aacbe7e9c17b0cf38560a5e9620bfa6b3c1b59eb0ec0caa4e9b74f23290cb7fd6e050803957
7
- data.tar.gz: 9b1313031ed40252c18d8db8f17b72bb67deefd114964eec56beb52cb7493e5a794746f54eeb086d9774ec1777d6fdb5cf5462badff62bc72bbe71a51c17358d
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,30 +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)
15
+ def concat(*files, output_file: nil)
16
+ if files.empty?
17
+ puts "Error: No files specified."
18
+ exit(1)
19
+ end
14
20
 
15
- 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
16
23
 
17
- output_file ||= "#{dir_path.basename}.txt"
18
- output_path = dir_path.dirname.join(output_file)
19
- processor = Poepod::Processor.new(options[:config])
20
- 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
21
26
 
22
- puts "-> #{total_files} files detected in the #{dir_path.relative_path_from(Dir.pwd)} directory."
23
- 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
24
51
  end
25
52
 
26
53
  def self.exit_on_failure?
27
54
  true
28
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
29
71
  end
30
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.2"
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.2
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
@@ -139,7 +167,6 @@ files:
139
167
  - ".rubocop.yml"
140
168
  - CODE_OF_CONDUCT.md
141
169
  - Gemfile
142
- - Gemfile.lock
143
170
  - README.adoc
144
171
  - Rakefile
145
172
  - bin/console
@@ -147,15 +174,24 @@ files:
147
174
  - exe/poepod
148
175
  - lib/poepod.rb
149
176
  - lib/poepod/cli.rb
177
+ - lib/poepod/file_processor.rb
178
+ - lib/poepod/gem_processor.rb
150
179
  - lib/poepod/processor.rb
151
180
  - lib/poepod/version.rb
152
181
  - poepod.gemspec
153
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
154
190
  homepage: https://github.com/riboseinc/poepod
155
191
  licenses:
156
192
  - BSD-2-Clause
157
193
  metadata: {}
158
- post_install_message:
194
+ post_install_message:
159
195
  rdoc_options: []
160
196
  require_paths:
161
197
  - lib
@@ -170,8 +206,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
206
  - !ruby/object:Gem::Version
171
207
  version: '0'
172
208
  requirements: []
173
- rubygems_version: 3.5.11
174
- signing_key:
209
+ rubygems_version: 3.3.27
210
+ signing_key:
175
211
  specification_version: 4
176
212
  summary: Utilities for uploading code to Poe
177
- 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
data/Gemfile.lock DELETED
@@ -1,101 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- poepod (0.1.0)
5
- parallel (~> 1.20)
6
- thor (~> 1.0)
7
- tqdm
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- activesupport (7.1.3.4)
13
- base64
14
- bigdecimal
15
- concurrent-ruby (~> 1.0, >= 1.0.2)
16
- connection_pool (>= 2.2.5)
17
- drb
18
- i18n (>= 1.6, < 2)
19
- minitest (>= 5.1)
20
- mutex_m
21
- tzinfo (~> 2.0)
22
- ast (2.4.2)
23
- base64 (0.2.0)
24
- bigdecimal (3.1.8)
25
- concurrent-ruby (1.3.3)
26
- connection_pool (2.4.1)
27
- diff-lcs (1.5.1)
28
- drb (2.2.1)
29
- i18n (1.14.5)
30
- concurrent-ruby (~> 1.0)
31
- json (2.7.2)
32
- language_server-protocol (3.17.0.3)
33
- minitest (5.23.1)
34
- mutex_m (0.2.0)
35
- parallel (1.25.1)
36
- parser (3.3.2.0)
37
- ast (~> 2.4.1)
38
- racc
39
- racc (1.8.0)
40
- rack (3.1.3)
41
- rainbow (3.1.1)
42
- rake (13.2.1)
43
- regexp_parser (2.9.2)
44
- rexml (3.3.0)
45
- strscan
46
- rspec (3.13.0)
47
- rspec-core (~> 3.13.0)
48
- rspec-expectations (~> 3.13.0)
49
- rspec-mocks (~> 3.13.0)
50
- rspec-core (3.13.0)
51
- rspec-support (~> 3.13.0)
52
- rspec-expectations (3.13.0)
53
- diff-lcs (>= 1.2.0, < 2.0)
54
- rspec-support (~> 3.13.0)
55
- rspec-mocks (3.13.1)
56
- diff-lcs (>= 1.2.0, < 2.0)
57
- rspec-support (~> 3.13.0)
58
- rspec-support (3.13.1)
59
- rubocop (1.64.1)
60
- json (~> 2.3)
61
- language_server-protocol (>= 3.17.0)
62
- parallel (~> 1.10)
63
- parser (>= 3.3.0.2)
64
- rainbow (>= 2.2.2, < 4.0)
65
- regexp_parser (>= 1.8, < 3.0)
66
- rexml (>= 3.2.5, < 4.0)
67
- rubocop-ast (>= 1.31.1, < 2.0)
68
- ruby-progressbar (~> 1.7)
69
- unicode-display_width (>= 2.4.0, < 3.0)
70
- rubocop-ast (1.31.3)
71
- parser (>= 3.3.1.0)
72
- rubocop-performance (1.21.0)
73
- rubocop (>= 1.48.1, < 2.0)
74
- rubocop-ast (>= 1.31.1, < 2.0)
75
- rubocop-rails (2.25.0)
76
- activesupport (>= 4.2.0)
77
- rack (>= 1.1)
78
- rubocop (>= 1.33.0, < 2.0)
79
- rubocop-ast (>= 1.31.1, < 2.0)
80
- ruby-progressbar (1.13.0)
81
- strscan (3.1.0)
82
- thor (1.3.1)
83
- tqdm (0.4.1)
84
- tzinfo (2.0.6)
85
- concurrent-ruby (~> 1.0)
86
- unicode-display_width (2.5.0)
87
-
88
- PLATFORMS
89
- arm64-darwin-23
90
- ruby
91
-
92
- DEPENDENCIES
93
- poepod!
94
- rake
95
- rspec
96
- rubocop
97
- rubocop-performance
98
- rubocop-rails
99
-
100
- BUNDLED WITH
101
- 2.5.11