poepod 0.1.3 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +1 -4
- data/.github/workflows/release.yml +5 -6
- data/README.adoc +116 -22
- data/lib/poepod/cli.rb +107 -18
- data/lib/poepod/file_processor.rb +45 -0
- data/lib/poepod/gem_processor.rb +96 -0
- data/lib/poepod/processor.rb +92 -55
- data/lib/poepod/version.rb +1 -1
- data/poepod.gemspec +4 -4
- data/spec/poepod/cli_spec.rb +126 -0
- data/spec/poepod/file_processor_spec.rb +150 -0
- data/spec/poepod/gem_processor_spec.rb +171 -0
- data/spec/poepod_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/test_files/file1.txt +1 -0
- data/spec/support/test_files/file2.txt +1 -0
- metadata +51 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b205fe50c5830d75893fbfa378b81984bf8a2b73fa8d104509fbbf698e0001fc
|
4
|
+
data.tar.gz: d4e87cef72c92cdcfaadd6bcf5a452bc1a05b13c4927cce10b6c81250ff99c9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c32f1ffb23fcf86f8cd1625c1deab4dc3599a0baf7b4ad80618f1e37a23f7b8e815b6065d628bf138f0e8ca9c2c849d1a08c31599737bee85711a6b4cd3e88f0
|
7
|
+
data.tar.gz: bdc4de92b51c89cfbeb484a119ce829e8a5c0a7768a032f4ec4299b06f2b35724038b13a2093827fbe839c650c0a18902c544ddd85f842e47ab4e633089edb38
|
data/.github/workflows/rake.yml
CHANGED
@@ -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
|
-
|
14
|
-
|
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.
|
23
|
-
pat_token: ${{ secrets.
|
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
|
4
|
-
|
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,139 @@ $ gem install poepod
|
|
28
31
|
|
29
32
|
== Usage
|
30
33
|
|
31
|
-
After installation, you can use the `poepod` command line tool
|
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
|
39
|
-
poepod help [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
|
+
=== Global options
|
46
|
+
|
47
|
+
All options can be used for both `wrap` and `concat` commands:
|
40
48
|
|
41
|
-
$
|
42
|
-
|
43
|
-
|
49
|
+
* `--exclude`: List of patterns to exclude (default: `["node_modules/", ".git/", ".gitignore$", ".DS_Store$", "^\\..+"]`)
|
50
|
+
* `--config`: Path to configuration file
|
51
|
+
* `--include-binary`: Include binary files (encoded in MIME format)
|
52
|
+
* `--include-dot-files`: Include dot files
|
53
|
+
* `--output-file`: Output path
|
54
|
+
* `--base-dir`: Base directory for relative file paths in output
|
55
|
+
* `--include-unstaged`: Include unstaged files from `lib`, `spec`, and `test` directories (for `wrap` command only)
|
44
56
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
57
|
+
[source,shell]
|
58
|
+
----
|
59
|
+
$ poepod concat FILES [OUTPUT_FILE] --exclude PATTERNS --config PATH --include-binary --include-dot-files --output-file PATH --base-dir PATH
|
60
|
+
$ poepod wrap GEMSPEC_PATH --exclude PATTERNS --config PATH --include-binary --include-dot-files --output-file PATH --base-dir PATH --include-unstaged
|
61
|
+
----
|
49
62
|
|
50
|
-
|
63
|
+
=== Concatenating files
|
64
|
+
|
65
|
+
The `concat` command allows you to combine multiple files into a single text
|
66
|
+
file.
|
67
|
+
|
68
|
+
This is particularly useful when you want to review or analyze code from
|
69
|
+
multiple files in one place, or when preparing code submissions for AI-powered
|
70
|
+
coding assistants.
|
71
|
+
|
72
|
+
By default, it excludes binary files, dot files, and certain patterns like
|
73
|
+
`node_modules/` and `.git/`.
|
74
|
+
|
75
|
+
[source,shell]
|
51
76
|
----
|
77
|
+
$ poepod concat path/to/files/* output.txt
|
78
|
+
----
|
79
|
+
|
80
|
+
This will concatenate all non-binary, non-dot files from the specified path into
|
81
|
+
`output.txt`.
|
82
|
+
|
83
|
+
==== Including dot files
|
52
84
|
|
53
|
-
|
85
|
+
By default, dot files (hidden files starting with a dot) are excluded.
|
86
|
+
|
87
|
+
To include them, use the `--include-dot-files` option:
|
54
88
|
|
55
89
|
[source,shell]
|
56
90
|
----
|
57
|
-
$ poepod concat
|
58
|
-
# => concatenated into my_project.txt
|
91
|
+
$ poepod concat path/to/files/* output.txt --include-dot-files
|
59
92
|
----
|
60
93
|
|
61
|
-
|
94
|
+
==== Including binary files
|
95
|
+
|
96
|
+
By default, binary files are excluded to keep the output focused on readable
|
97
|
+
code.
|
62
98
|
|
63
|
-
|
99
|
+
To include binary files (encoded in MIME format), use the `--include-binary`
|
100
|
+
option:
|
64
101
|
|
65
102
|
[source,shell]
|
66
103
|
----
|
67
|
-
$ poepod concat
|
104
|
+
$ poepod concat path/to/files/* output.txt --include-binary
|
105
|
+
----
|
106
|
+
|
107
|
+
This can be useful when you need to include binary assets or compiled files in
|
108
|
+
your analysis.
|
109
|
+
|
110
|
+
==== Excluding patterns
|
111
|
+
|
112
|
+
You can exclude certain patterns using the `--exclude` option:
|
113
|
+
|
114
|
+
[source,shell]
|
68
115
|
----
|
116
|
+
$ poepod concat path/to/files/* output.txt --exclude node_modules .git build test
|
117
|
+
----
|
118
|
+
|
119
|
+
This is helpful when you want to focus on specific parts of your codebase,
|
120
|
+
excluding irrelevant or large directories.
|
121
|
+
|
122
|
+
=== Wrapping a gem
|
123
|
+
|
124
|
+
The `wrap` command creates a comprehensive snapshot of your gem, including all
|
125
|
+
files specified in the gemspec and README files. This is particularly useful for
|
126
|
+
gem developers who want to review their entire gem contents or prepare it for
|
127
|
+
submission to code review tools.
|
128
|
+
|
129
|
+
[source,shell]
|
130
|
+
----
|
131
|
+
$ poepod wrap path/to/your_gem.gemspec
|
132
|
+
----
|
133
|
+
|
134
|
+
This will create a file named `your_gem_wrapped.txt` containing all the files
|
135
|
+
specified in the gemspec, including README files.
|
136
|
+
|
137
|
+
==== Handling unstaged files
|
138
|
+
|
139
|
+
By default, unstaged files in the `lib/`, `spec/`, and `test/` directories are
|
140
|
+
not included in the wrap, but they will be listed as a warning. This default
|
141
|
+
behavior ensures that the wrapped content matches what's currently tracked in
|
142
|
+
your version control system.
|
143
|
+
|
144
|
+
However, there are cases where including unstaged files can be beneficial:
|
145
|
+
|
146
|
+
. When you're actively developing and want to include recent changes that
|
147
|
+
haven't been committed yet.
|
148
|
+
|
149
|
+
. When you're seeking feedback on work-in-progress code.
|
150
|
+
|
151
|
+
. When you want to ensure you're not missing any important files in your commit.
|
152
|
+
|
153
|
+
To include these unstaged files in the wrap:
|
154
|
+
|
155
|
+
[source,shell]
|
156
|
+
----
|
157
|
+
$ poepod wrap path/to/your_gem.gemspec --include-unstaged
|
158
|
+
----
|
159
|
+
|
160
|
+
This option allows you to capture a true snapshot of your gem's current state,
|
161
|
+
including any work in progress.
|
69
162
|
|
70
163
|
== Development
|
71
164
|
|
72
165
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
73
|
-
`rake
|
166
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
74
167
|
prompt that will allow you to experiment.
|
75
168
|
|
76
169
|
To install this gem onto your local machine, run `bundle exec rake install`. To
|
@@ -81,4 +174,5 @@ https://rubygems.org.
|
|
81
174
|
|
82
175
|
== Contributing
|
83
176
|
|
84
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/riboseinc/poepod.
|
177
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/riboseinc/poepod.
|
178
|
+
Please adhere to the link:CODE_OF_CONDUCT.md[code of conduct].
|
data/lib/poepod/cli.rb
CHANGED
@@ -1,36 +1,125 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# lib/poepod/cli.rb
|
3
4
|
require "thor"
|
4
|
-
require_relative "
|
5
|
+
require_relative "file_processor"
|
6
|
+
require_relative "gem_processor"
|
5
7
|
|
6
8
|
module Poepod
|
9
|
+
# Command-line interface for Poepod
|
7
10
|
class Cli < Thor
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
+
# Define shared options
|
12
|
+
def self.shared_options
|
13
|
+
option :exclude, type: :array, default: Poepod::FileProcessor::EXCLUDE_DEFAULT,
|
14
|
+
desc: "List of patterns to exclude"
|
15
|
+
option :config, type: :string, desc: "Path to configuration file"
|
16
|
+
option :include_binary, type: :boolean, default: false, desc: "Include binary files (encoded in MIME format)"
|
17
|
+
option :include_dot_files, type: :boolean, default: false, desc: "Include dot files"
|
18
|
+
option :output_file, type: :string, desc: "Output path"
|
19
|
+
option :base_dir, type: :string, desc: "Base directory for relative file paths in output"
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "concat FILES [OUTPUT_FILE]", "Concatenate specified files into one text file"
|
23
|
+
shared_options
|
24
|
+
|
25
|
+
def concat(*files)
|
26
|
+
check_files(files)
|
27
|
+
output_file = determine_output_file(files)
|
28
|
+
base_dir = options[:base_dir] || Dir.pwd
|
29
|
+
process_files(files, output_file, base_dir)
|
30
|
+
end
|
11
31
|
|
12
|
-
|
13
|
-
|
32
|
+
desc "wrap GEMSPEC_PATH", "Wrap a gem based on its gemspec file"
|
33
|
+
shared_options
|
34
|
+
option :include_unstaged, type: :boolean, default: false,
|
35
|
+
desc: "Include unstaged files from lib, spec, and test directories"
|
14
36
|
|
15
|
-
|
16
|
-
|
17
|
-
|
37
|
+
def wrap(gemspec_path)
|
38
|
+
base_dir = options[:base_dir] || File.dirname(gemspec_path)
|
39
|
+
processor = Poepod::GemProcessor.new(
|
40
|
+
gemspec_path,
|
41
|
+
include_unstaged: options[:include_unstaged],
|
42
|
+
exclude: options[:exclude],
|
43
|
+
include_binary: options[:include_binary],
|
44
|
+
include_dot_files: options[:include_dot_files],
|
45
|
+
base_dir: base_dir,
|
46
|
+
config_file: options[:config]
|
47
|
+
)
|
48
|
+
success, result, unstaged_files = processor.process
|
49
|
+
if success
|
50
|
+
handle_wrap_result(success, result, unstaged_files)
|
51
|
+
else
|
52
|
+
puts result
|
18
53
|
exit(1)
|
19
54
|
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.exit_on_failure?
|
58
|
+
true
|
59
|
+
end
|
20
60
|
|
21
|
-
|
61
|
+
private
|
22
62
|
|
23
|
-
|
24
|
-
|
25
|
-
processor = Poepod::Processor.new(options[:config])
|
26
|
-
total_files, copied_files = processor.write_directory_structure_to_file(directory, output_path, options[:exclude])
|
63
|
+
def check_files(files)
|
64
|
+
return unless files.empty?
|
27
65
|
|
28
|
-
puts "
|
29
|
-
|
66
|
+
puts "Error: No files specified."
|
67
|
+
exit(1)
|
30
68
|
end
|
31
69
|
|
32
|
-
def
|
33
|
-
|
70
|
+
def determine_output_file(files)
|
71
|
+
options[:output_file] || default_output_file(files.first)
|
72
|
+
end
|
73
|
+
|
74
|
+
def process_files(files, output_file, base_dir)
|
75
|
+
output_path = Pathname.new(output_file).expand_path
|
76
|
+
processor = Poepod::FileProcessor.new(
|
77
|
+
files,
|
78
|
+
output_path,
|
79
|
+
config_file: options[:config],
|
80
|
+
include_binary: options[:include_binary],
|
81
|
+
include_dot_files: options[:include_dot_files],
|
82
|
+
exclude: options[:exclude],
|
83
|
+
base_dir: base_dir
|
84
|
+
)
|
85
|
+
total_files, copied_files = processor.process
|
86
|
+
print_result(total_files, copied_files, output_path)
|
87
|
+
end
|
88
|
+
|
89
|
+
def print_result(total_files, copied_files, output_path)
|
90
|
+
puts "-> #{total_files} files detected."
|
91
|
+
puts "=> #{copied_files} files have been concatenated into #{output_path.relative_path_from(Dir.pwd)}."
|
92
|
+
end
|
93
|
+
|
94
|
+
def handle_wrap_result(success, result, unstaged_files)
|
95
|
+
if success
|
96
|
+
puts "=> The gem has been wrapped into '#{result}'."
|
97
|
+
print_unstaged_files_warning(unstaged_files) if unstaged_files.any?
|
98
|
+
else
|
99
|
+
puts result
|
100
|
+
exit(1)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def print_unstaged_files_warning(unstaged_files)
|
105
|
+
puts "\nWarning: The following files are not staged in git:"
|
106
|
+
puts unstaged_files
|
107
|
+
puts "\nThese files are #{options[:include_unstaged] ? "included" : "not included"} in the wrap."
|
108
|
+
puts "Use --include-unstaged option to include these files." unless options[:include_unstaged]
|
109
|
+
end
|
110
|
+
|
111
|
+
def default_output_file(first_pattern)
|
112
|
+
first_item = Dir.glob(first_pattern).first
|
113
|
+
if first_item
|
114
|
+
if File.directory?(first_item)
|
115
|
+
"#{File.basename(first_item)}.txt"
|
116
|
+
else
|
117
|
+
"#{File.basename(first_item,
|
118
|
+
".*")}_concat.txt"
|
119
|
+
end
|
120
|
+
else
|
121
|
+
"concatenated_output.txt"
|
122
|
+
end
|
34
123
|
end
|
35
124
|
end
|
36
125
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "processor"
|
4
|
+
|
5
|
+
module Poepod
|
6
|
+
# Processes files for concatenation, handling binary and dot files
|
7
|
+
class FileProcessor < Processor
|
8
|
+
EXCLUDE_DEFAULT = [
|
9
|
+
%r{node_modules/}, %r{.git/}, /.gitignore$/, /.DS_Store$/, /^\..+/
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
files,
|
14
|
+
output_file,
|
15
|
+
config_file: nil,
|
16
|
+
include_binary: false,
|
17
|
+
include_dot_files: false,
|
18
|
+
exclude: [],
|
19
|
+
base_dir: nil
|
20
|
+
)
|
21
|
+
super(
|
22
|
+
config_file,
|
23
|
+
include_binary: include_binary,
|
24
|
+
include_dot_files: include_dot_files,
|
25
|
+
exclude: exclude,
|
26
|
+
base_dir: base_dir,
|
27
|
+
)
|
28
|
+
@files = files
|
29
|
+
@output_file = output_file
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def collect_files_to_process
|
35
|
+
@files.flatten.each_with_object([]) do |file, files_to_process|
|
36
|
+
Dir.glob(file, File::FNM_DOTMATCH).each do |matched_file|
|
37
|
+
next unless File.file?(matched_file)
|
38
|
+
next if should_exclude?(matched_file)
|
39
|
+
|
40
|
+
files_to_process << matched_file
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,96 @@
|
|
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
|
+
# Processes gem files for wrapping, handling unstaged files
|
10
|
+
class GemProcessor < Processor
|
11
|
+
def initialize(
|
12
|
+
gemspec_path,
|
13
|
+
include_unstaged: false,
|
14
|
+
exclude: [],
|
15
|
+
include_binary: false,
|
16
|
+
include_dot_files: false,
|
17
|
+
base_dir: nil,
|
18
|
+
config_file: nil
|
19
|
+
)
|
20
|
+
super(
|
21
|
+
config_file,
|
22
|
+
include_binary: include_binary,
|
23
|
+
include_dot_files: include_dot_files,
|
24
|
+
exclude: exclude,
|
25
|
+
base_dir: base_dir || File.dirname(gemspec_path),
|
26
|
+
)
|
27
|
+
@gemspec_path = gemspec_path
|
28
|
+
@include_unstaged = include_unstaged
|
29
|
+
end
|
30
|
+
|
31
|
+
def process
|
32
|
+
return error_no_gemspec unless File.exist?(@gemspec_path)
|
33
|
+
|
34
|
+
spec = load_gemspec
|
35
|
+
return spec unless spec.is_a?(Gem::Specification)
|
36
|
+
|
37
|
+
gem_name = spec.name
|
38
|
+
@output_file = "#{gem_name}_wrapped.txt"
|
39
|
+
unstaged_files = check_unstaged_files
|
40
|
+
|
41
|
+
super()
|
42
|
+
|
43
|
+
[true, @output_file, unstaged_files]
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def collect_files_to_process
|
49
|
+
spec = load_gemspec
|
50
|
+
files_to_include = (spec.files +
|
51
|
+
spec.test_files +
|
52
|
+
find_readme_files).uniq
|
53
|
+
|
54
|
+
files_to_include += check_unstaged_files if @include_unstaged
|
55
|
+
|
56
|
+
files_to_include.sort.uniq.reject do |relative_path|
|
57
|
+
should_exclude?(File.join(@base_dir, relative_path))
|
58
|
+
end.map do |relative_path|
|
59
|
+
File.join(@base_dir, relative_path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def error_no_gemspec
|
64
|
+
[false, "Error: The specified gemspec file '#{@gemspec_path}' does not exist."]
|
65
|
+
end
|
66
|
+
|
67
|
+
def load_gemspec
|
68
|
+
Gem::Specification.load(@gemspec_path)
|
69
|
+
rescue StandardError => e
|
70
|
+
[false, "Error loading gemspec: #{e.message}"]
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_readme_files
|
74
|
+
Dir.glob(File.join(File.dirname(@gemspec_path), "README*")).map do |path|
|
75
|
+
Pathname.new(path).relative_path_from(
|
76
|
+
Pathname.new(File.dirname(@gemspec_path))
|
77
|
+
).to_s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_unstaged_files
|
82
|
+
gem_root = File.dirname(@gemspec_path)
|
83
|
+
git = Git.open(gem_root)
|
84
|
+
|
85
|
+
untracked_files = git.status.untracked.keys
|
86
|
+
modified_files = git.status.changed.keys
|
87
|
+
|
88
|
+
(untracked_files + modified_files).select do |file|
|
89
|
+
file.start_with?("lib/", "spec/", "test/")
|
90
|
+
end
|
91
|
+
rescue Git::GitExecuteError => e
|
92
|
+
warn "Git error: #{e.message}. Assuming no unstaged files."
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/poepod/processor.rb
CHANGED
@@ -1,86 +1,123 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "yaml"
|
4
|
-
require "
|
5
|
-
require "
|
4
|
+
require "base64"
|
5
|
+
require "marcel"
|
6
|
+
require "stringio"
|
6
7
|
|
7
8
|
module Poepod
|
9
|
+
# Base processor class
|
8
10
|
class Processor
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def initialize(config_file = nil)
|
17
|
-
@failed_files = []
|
11
|
+
def initialize(
|
12
|
+
config_file = nil,
|
13
|
+
include_binary: false,
|
14
|
+
include_dot_files: false,
|
15
|
+
exclude: [],
|
16
|
+
base_dir: nil
|
17
|
+
)
|
18
18
|
@config = load_config(config_file)
|
19
|
+
@include_binary = include_binary
|
20
|
+
@include_dot_files = include_dot_files
|
21
|
+
@exclude = exclude || []
|
22
|
+
@base_dir = base_dir
|
23
|
+
@failed_files = []
|
19
24
|
end
|
20
25
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
{}
|
26
|
-
end
|
26
|
+
def process
|
27
|
+
files_to_process = collect_files_to_process
|
28
|
+
total_files, copied_files = process_files(files_to_process)
|
29
|
+
[total_files, copied_files]
|
27
30
|
end
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@failed_files << file_path
|
34
|
-
[file_path, nil, "Failed to decode the file, as it is not saved with UTF-8 encoding."]
|
32
|
+
private
|
33
|
+
|
34
|
+
def collect_files_to_process
|
35
|
+
raise NotImplementedError, "Subclasses must implement collect_files_to_process"
|
35
36
|
end
|
36
37
|
|
37
|
-
def
|
38
|
-
|
39
|
-
exclude_pattern = Regexp.union(exclude.map { |ex| Regexp.new(ex) })
|
38
|
+
def load_config(config_file)
|
39
|
+
return {} unless config_file && File.exist?(config_file)
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
YAML.load_file(config_file)
|
42
|
+
end
|
43
|
+
|
44
|
+
def binary_file?(file_path)
|
45
|
+
return false unless File.exist?(file_path) && File.file?(file_path)
|
46
|
+
|
47
|
+
File.open(file_path, "rb") do |file|
|
48
|
+
content = file.read(8192) # Read first 8KB for magic byte detection
|
49
|
+
mime_type = Marcel::MimeType.for(content, name: File.basename(file_path), declared_type: "text/plain")
|
50
|
+
!mime_type.start_with?("text/") && mime_type != "application/json"
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
output_file.puts "#{relative}\n#{error}"
|
54
|
+
def process_files(files)
|
55
|
+
total_files = files.size
|
56
|
+
copied_files = 0
|
57
|
+
|
58
|
+
File.open(@output_file, "w", encoding: "utf-8") do |output|
|
59
|
+
files.sort.each do |file_path|
|
60
|
+
process_file(output, file_path)
|
61
|
+
copied_files += 1
|
57
62
|
end
|
58
|
-
output_file.puts if index < results.size - 1 # Add a newline between files
|
59
63
|
end
|
60
|
-
end
|
61
64
|
|
62
|
-
|
63
|
-
Pathname.new(file_path).relative_path_from(Dir.pwd)
|
65
|
+
[total_files, copied_files]
|
64
66
|
end
|
65
67
|
|
66
|
-
def
|
67
|
-
|
68
|
+
def process_file(output = nil, file_path)
|
69
|
+
output ||= StringIO.new
|
68
70
|
|
69
|
-
|
71
|
+
relative_path = if @base_dir
|
72
|
+
Pathname.new(file_path).relative_path_from(Pathname.new(@base_dir)).to_s
|
73
|
+
else
|
74
|
+
file_path
|
75
|
+
end
|
70
76
|
|
71
|
-
|
72
|
-
total_files = file_list.size
|
77
|
+
output.puts "--- START FILE: #{relative_path} ---"
|
73
78
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
write_results_to_file(results, output_file)
|
79
|
+
if binary_file?(file_path) && @include_binary
|
80
|
+
output.puts encode_binary_file(file_path)
|
81
|
+
else
|
82
|
+
output.puts File.read(file_path)
|
79
83
|
end
|
80
84
|
|
81
|
-
|
85
|
+
output.puts "--- END FILE: #{relative_path} ---\n"
|
82
86
|
|
83
|
-
|
87
|
+
output.string if output.is_a?(StringIO) # Return the string if using StringIO
|
88
|
+
end
|
89
|
+
|
90
|
+
def encode_binary_file(file_path)
|
91
|
+
content = File.binread(file_path)
|
92
|
+
mime_type = Marcel::MimeType.for(content, name: File.basename(file_path))
|
93
|
+
encoded_content = Base64.strict_encode64(content)
|
94
|
+
<<~HERE
|
95
|
+
Content-Type: #{mime_type}
|
96
|
+
Content-Transfer-Encoding: base64
|
97
|
+
|
98
|
+
#{encoded_content}
|
99
|
+
HERE
|
100
|
+
end
|
101
|
+
|
102
|
+
def dot_file?(file_path)
|
103
|
+
File.basename(file_path).start_with?(".")
|
104
|
+
end
|
105
|
+
|
106
|
+
def should_exclude?(file_path)
|
107
|
+
return true if !@include_dot_files && dot_file?(file_path)
|
108
|
+
return true if !@include_binary && binary_file?(file_path)
|
109
|
+
|
110
|
+
exclude_file?(file_path)
|
111
|
+
end
|
112
|
+
|
113
|
+
def exclude_file?(file_path)
|
114
|
+
@exclude.any? do |pattern|
|
115
|
+
if pattern.is_a?(Regexp)
|
116
|
+
file_path.match?(pattern)
|
117
|
+
else
|
118
|
+
File.fnmatch?(pattern, file_path)
|
119
|
+
end
|
120
|
+
end
|
84
121
|
end
|
85
122
|
end
|
86
123
|
end
|
data/lib/poepod/version.rb
CHANGED
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 "marcel", "~> 1.0"
|
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,126 @@
|
|
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
|
+
let(:dot_file) { File.join(temp_dir, ".hidden_file") }
|
14
|
+
|
15
|
+
before do
|
16
|
+
File.write(text_file, "Hello, World!")
|
17
|
+
File.write(binary_file, [0xFF, 0xD8, 0xFF, 0xE0].pack("C*"))
|
18
|
+
File.write(dot_file, "Hidden content")
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
FileUtils.remove_entry(temp_dir)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "concatenates text files and excludes binary and dot files by default" do
|
26
|
+
output_file = File.join(temp_dir, "output.txt")
|
27
|
+
expect do
|
28
|
+
cli.invoke(:concat, [File.join(temp_dir, "*")], { output_file: output_file })
|
29
|
+
end.to output(/1 files detected\.\n.*1 files have been concatenated/).to_stdout
|
30
|
+
expect(File.exist?(output_file)).to be true
|
31
|
+
content = File.read(output_file)
|
32
|
+
expect(content).to include("Hello, World!")
|
33
|
+
expect(content).not_to include("Hidden content")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "includes binary files when specified" do
|
37
|
+
output_file = File.join(temp_dir, "output.txt")
|
38
|
+
expect do
|
39
|
+
cli.invoke(:concat, [File.join(temp_dir, "*")], { output_file: output_file, include_binary: true })
|
40
|
+
end.to output(/2 files detected\.\n.*2 files have been concatenated/).to_stdout
|
41
|
+
expect(File.exist?(output_file)).to be true
|
42
|
+
content = File.read(output_file)
|
43
|
+
expect(content).to include("Hello, World!")
|
44
|
+
expect(content).to include("Content-Type: image/jpeg")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "includes dot files when specified" do
|
48
|
+
output_file = File.join(temp_dir, "output.txt")
|
49
|
+
expect do
|
50
|
+
cli.invoke(:concat, [File.join(temp_dir, "*")], { output_file: output_file, include_dot_files: true })
|
51
|
+
end.to output(/2 files detected\.\n.*2 files have been concatenated/).to_stdout
|
52
|
+
expect(File.exist?(output_file)).to be true
|
53
|
+
content = File.read(output_file)
|
54
|
+
expect(content).to include("Hello, World!")
|
55
|
+
expect(content).to include("Hidden content")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "uses the specified base directory for relative paths" do
|
59
|
+
output_file = File.join(temp_dir, "output.txt")
|
60
|
+
base_dir = File.dirname(text_file)
|
61
|
+
expect do
|
62
|
+
cli.invoke(:concat, [File.join(temp_dir, "*")], { output_file: output_file, base_dir: base_dir })
|
63
|
+
end.to output(/1 files detected\.\n.*1 files have been concatenated/).to_stdout
|
64
|
+
expect(File.exist?(output_file)).to be true
|
65
|
+
content = File.read(output_file)
|
66
|
+
expect(content).to include("--- START FILE: text_file.txt ---")
|
67
|
+
expect(content).to include("Hello, World!")
|
68
|
+
expect(content).to include("--- END FILE: text_file.txt ---")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#wrap" do
|
73
|
+
let(:temp_dir) { Dir.mktmpdir }
|
74
|
+
let(:gemspec_file) { File.join(temp_dir, "test_gem.gemspec") }
|
75
|
+
|
76
|
+
before do
|
77
|
+
File.write(gemspec_file, <<~GEMSPEC)
|
78
|
+
Gem::Specification.new do |spec|
|
79
|
+
spec.name = "test_gem"
|
80
|
+
spec.version = "0.1.0"
|
81
|
+
spec.authors = ["Test Author"]
|
82
|
+
spec.files = ["lib/test_gem.rb"]
|
83
|
+
end
|
84
|
+
GEMSPEC
|
85
|
+
|
86
|
+
FileUtils.mkdir_p(File.join(temp_dir, "lib"))
|
87
|
+
File.write(File.join(temp_dir, "lib/test_gem.rb"), "puts 'Hello from test_gem'")
|
88
|
+
|
89
|
+
# Mock Git operations
|
90
|
+
allow(Git).to receive(:open).and_return(double(status: double(untracked: {}, changed: {})))
|
91
|
+
end
|
92
|
+
|
93
|
+
after do
|
94
|
+
FileUtils.remove_entry(temp_dir)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "wraps a gem" do
|
98
|
+
expect { cli.wrap(gemspec_file) }.to output(/The gem has been wrapped into/).to_stdout
|
99
|
+
output_file = File.join(Dir.pwd, "test_gem_wrapped.txt")
|
100
|
+
expect(File.exist?(output_file)).to be true
|
101
|
+
content = File.read(output_file)
|
102
|
+
expect(content).to include("--- START FILE: lib/test_gem.rb ---")
|
103
|
+
expect(content).to include("puts 'Hello from test_gem'")
|
104
|
+
expect(content).to include("--- END FILE: lib/test_gem.rb ---")
|
105
|
+
end
|
106
|
+
|
107
|
+
it "handles non-existent gemspec" do
|
108
|
+
expect do
|
109
|
+
cli.wrap("non_existent.gemspec")
|
110
|
+
end.to output(/Error: The specified gemspec file/).to_stdout.and raise_error(SystemExit)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "uses the specified base directory for relative paths" do
|
114
|
+
base_dir = File.dirname(gemspec_file)
|
115
|
+
expect do
|
116
|
+
cli.invoke(:wrap, [gemspec_file], { base_dir: base_dir })
|
117
|
+
end.to output(/The gem has been wrapped into/).to_stdout
|
118
|
+
output_file = File.join(Dir.pwd, "test_gem_wrapped.txt")
|
119
|
+
expect(File.exist?(output_file)).to be true
|
120
|
+
content = File.read(output_file)
|
121
|
+
expect(content).to include("--- START FILE: lib/test_gem.rb ---")
|
122
|
+
expect(content).to include("puts 'Hello from test_gem'")
|
123
|
+
expect(content).to include("--- END FILE: lib/test_gem.rb ---")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,150 @@
|
|
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
|
+
let(:dot_file) { File.join(temp_dir, ".hidden_file") }
|
15
|
+
|
16
|
+
before do
|
17
|
+
File.write(text_file1, "Content of file1.\n")
|
18
|
+
File.write(text_file2, "Content of file2.\n")
|
19
|
+
File.write(binary_file, [0xFF, 0xD8, 0xFF, 0xE0].pack("C*"))
|
20
|
+
File.write(dot_file, "Content of hidden file.\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
after do
|
24
|
+
FileUtils.remove_entry(temp_dir)
|
25
|
+
output_file.unlink
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#process" do
|
29
|
+
context "with default options" do
|
30
|
+
let(:processor) { described_class.new([File.join(temp_dir, "*")], output_file.path) }
|
31
|
+
|
32
|
+
it "processes text files and excludes binary and dot files" do
|
33
|
+
total_files, copied_files = processor.process
|
34
|
+
expect(total_files).to eq(2)
|
35
|
+
expect(copied_files).to eq(2)
|
36
|
+
|
37
|
+
output_content = File.read(output_file.path, encoding: "utf-8")
|
38
|
+
expected_content = <<~TEXT
|
39
|
+
--- START FILE: #{text_file1} ---
|
40
|
+
Content of file1.
|
41
|
+
--- END FILE: #{text_file1} ---
|
42
|
+
--- START FILE: #{text_file2} ---
|
43
|
+
Content of file2.
|
44
|
+
--- END FILE: #{text_file2} ---
|
45
|
+
TEXT
|
46
|
+
expect(output_content).to eq(expected_content)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "with include_binary option" do
|
51
|
+
let(:processor) { described_class.new([File.join(temp_dir, "*")], output_file.path, include_binary: true) }
|
52
|
+
|
53
|
+
it "includes binary files" do
|
54
|
+
total_files, copied_files = processor.process
|
55
|
+
expect(total_files).to eq(3)
|
56
|
+
expect(copied_files).to eq(3)
|
57
|
+
|
58
|
+
output_content = File.read(output_file.path, encoding: "utf-8")
|
59
|
+
expected_content = <<~TEXT
|
60
|
+
--- START FILE: #{binary_file} ---
|
61
|
+
Content-Type: image/jpeg
|
62
|
+
Content-Transfer-Encoding: base64
|
63
|
+
|
64
|
+
/9j/4A==
|
65
|
+
--- END FILE: #{binary_file} ---
|
66
|
+
--- START FILE: #{text_file1} ---
|
67
|
+
Content of file1.
|
68
|
+
--- END FILE: #{text_file1} ---
|
69
|
+
--- START FILE: #{text_file2} ---
|
70
|
+
Content of file2.
|
71
|
+
--- END FILE: #{text_file2} ---
|
72
|
+
TEXT
|
73
|
+
expect(output_content).to eq(expected_content)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with include_dot_files option" do
|
78
|
+
let(:processor) { described_class.new([File.join(temp_dir, "*")], output_file.path, include_dot_files: true) }
|
79
|
+
|
80
|
+
it "includes dot files" do
|
81
|
+
total_files, copied_files = processor.process
|
82
|
+
expect(total_files).to eq(3)
|
83
|
+
expect(copied_files).to eq(3)
|
84
|
+
|
85
|
+
output_content = File.read(output_file.path, encoding: "utf-8")
|
86
|
+
expected_content = <<~TEXT
|
87
|
+
--- START FILE: #{dot_file} ---
|
88
|
+
Content of hidden file.
|
89
|
+
--- END FILE: #{dot_file} ---
|
90
|
+
--- START FILE: #{text_file1} ---
|
91
|
+
Content of file1.
|
92
|
+
--- END FILE: #{text_file1} ---
|
93
|
+
--- START FILE: #{text_file2} ---
|
94
|
+
Content of file2.
|
95
|
+
--- END FILE: #{text_file2} ---
|
96
|
+
TEXT
|
97
|
+
expect(output_content).to eq(expected_content)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "with both include_binary and include_dot_files options" do
|
102
|
+
let(:processor) do
|
103
|
+
described_class.new([File.join(temp_dir, "*")], output_file.path, include_binary: true, include_dot_files: true)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "includes all files in sorted order" do
|
107
|
+
total_files, copied_files = processor.process
|
108
|
+
expect(total_files).to eq(4)
|
109
|
+
expect(copied_files).to eq(4)
|
110
|
+
|
111
|
+
output_content = File.read(output_file.path, encoding: "utf-8")
|
112
|
+
expected_content = <<~HERE
|
113
|
+
--- START FILE: #{dot_file} ---
|
114
|
+
Content of hidden file.
|
115
|
+
--- END FILE: #{dot_file} ---
|
116
|
+
--- START FILE: #{binary_file} ---
|
117
|
+
Content-Type: image/jpeg
|
118
|
+
Content-Transfer-Encoding: base64
|
119
|
+
|
120
|
+
/9j/4A==
|
121
|
+
--- END FILE: #{binary_file} ---
|
122
|
+
--- START FILE: #{text_file1} ---
|
123
|
+
Content of file1.
|
124
|
+
--- END FILE: #{text_file1} ---
|
125
|
+
--- START FILE: #{text_file2} ---
|
126
|
+
Content of file2.
|
127
|
+
--- END FILE: #{text_file2} ---
|
128
|
+
HERE
|
129
|
+
|
130
|
+
expect(output_content).to eq(expected_content)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#process_file" do
|
136
|
+
let(:processor) { described_class.new([text_file1], output_file.path) }
|
137
|
+
|
138
|
+
it "reads the content of a file" do
|
139
|
+
content = processor.send(:process_file, nil, text_file1)
|
140
|
+
expect(content).to include("Content of file1.\n")
|
141
|
+
end
|
142
|
+
|
143
|
+
it "handles encoding errors gracefully" do
|
144
|
+
allow(File).to receive(:read).and_raise(Encoding::InvalidByteSequenceError)
|
145
|
+
expect do
|
146
|
+
processor.send(:process_file, nil, text_file1)
|
147
|
+
end.to raise_error(Encoding::InvalidByteSequenceError)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,171 @@
|
|
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 in sorted order" 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
|
+
|
50
|
+
file_order = content.scan(/--- START FILE: (.+) ---/).flatten
|
51
|
+
expected_order = [
|
52
|
+
"README.md",
|
53
|
+
"README.txt",
|
54
|
+
"lib/test_gem.rb",
|
55
|
+
"spec/test_gem_spec.rb"
|
56
|
+
]
|
57
|
+
expect(file_order).to eq(expected_order)
|
58
|
+
|
59
|
+
expected = <<~HERE
|
60
|
+
--- START FILE: README.md ---
|
61
|
+
# Test Gem
|
62
|
+
|
63
|
+
This is a test gem.
|
64
|
+
--- END FILE: README.md ---
|
65
|
+
--- START FILE: README.txt ---
|
66
|
+
Test Gem
|
67
|
+
|
68
|
+
This is a test gem in plain text.
|
69
|
+
--- END FILE: README.txt ---
|
70
|
+
--- START FILE: lib/test_gem.rb ---
|
71
|
+
puts 'Hello from test_gem'
|
72
|
+
--- END FILE: lib/test_gem.rb ---
|
73
|
+
--- START FILE: spec/test_gem_spec.rb ---
|
74
|
+
RSpec.describe TestGem do
|
75
|
+
end
|
76
|
+
--- END FILE: spec/test_gem_spec.rb ---
|
77
|
+
HERE
|
78
|
+
expect(content).to eq(expected)
|
79
|
+
end
|
80
|
+
|
81
|
+
context "with non-existent gemspec" do
|
82
|
+
let(:processor) { described_class.new("non_existent.gemspec") }
|
83
|
+
|
84
|
+
it "returns an error" do
|
85
|
+
success, error_message = processor.process
|
86
|
+
expect(success).to be false
|
87
|
+
expect(error_message).to include("Error: The specified gemspec file")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "with unstaged files" do
|
92
|
+
let(:processor) { described_class.new(gemspec_file, include_unstaged: false) }
|
93
|
+
let(:mock_git) { instance_double(Git::Base) }
|
94
|
+
let(:mock_status) { instance_double(Git::Status) }
|
95
|
+
|
96
|
+
before do
|
97
|
+
allow(Git).to receive(:open).and_return(mock_git)
|
98
|
+
allow(mock_git).to receive(:status).and_return(mock_status)
|
99
|
+
allow(mock_status).to receive(:untracked).and_return(
|
100
|
+
{ "lib/unstaged_file.rb" => "??" }
|
101
|
+
)
|
102
|
+
allow(mock_status).to receive(:changed).and_return({})
|
103
|
+
end
|
104
|
+
|
105
|
+
context "with include_unstaged option" do
|
106
|
+
let(:processor) { described_class.new(gemspec_file, include_unstaged: true) }
|
107
|
+
|
108
|
+
it "includes unstaged files" do
|
109
|
+
allow(File).to receive(:file?).and_return(true)
|
110
|
+
|
111
|
+
# Create a hash to store file contents
|
112
|
+
file_contents = {
|
113
|
+
"lib/test_gem.rb" => "puts 'Hello from test_gem'",
|
114
|
+
"spec/test_gem_spec.rb" => "RSpec.describe TestGem do\nend",
|
115
|
+
"README.md" => "# Test Gem\n\nThis is a test gem.",
|
116
|
+
"README.txt" => "Test Gem\n\nThis is a test gem in plain text.",
|
117
|
+
"lib/unstaged_file.rb" => "Unstaged content"
|
118
|
+
}
|
119
|
+
|
120
|
+
# Mock File.read
|
121
|
+
allow(File).to receive(:read) do |path|
|
122
|
+
file_name = File.basename(path)
|
123
|
+
if file_contents.key?(file_name)
|
124
|
+
file_contents[file_name]
|
125
|
+
elsif path.end_with?("_wrapped.txt")
|
126
|
+
# This is the output file, so we'll construct its content here
|
127
|
+
file_contents.map do |file, content|
|
128
|
+
<<~HERE
|
129
|
+
--- START FILE: #{file} ---
|
130
|
+
#{content}
|
131
|
+
--- END FILE: #{file} ---
|
132
|
+
HERE
|
133
|
+
end.join("")
|
134
|
+
else
|
135
|
+
"Default content for #{path}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
success, output_file, unstaged_files = processor.process
|
140
|
+
expect(success).to be true
|
141
|
+
expect(unstaged_files).to eq(["lib/unstaged_file.rb"])
|
142
|
+
|
143
|
+
content = File.read(output_file)
|
144
|
+
expected = <<~HERE
|
145
|
+
--- START FILE: lib/test_gem.rb ---
|
146
|
+
puts 'Hello from test_gem'
|
147
|
+
--- END FILE: lib/test_gem.rb ---
|
148
|
+
--- START FILE: spec/test_gem_spec.rb ---
|
149
|
+
RSpec.describe TestGem do
|
150
|
+
end
|
151
|
+
--- END FILE: spec/test_gem_spec.rb ---
|
152
|
+
--- START FILE: README.md ---
|
153
|
+
# Test Gem
|
154
|
+
|
155
|
+
This is a test gem.
|
156
|
+
--- END FILE: README.md ---
|
157
|
+
--- START FILE: README.txt ---
|
158
|
+
Test Gem
|
159
|
+
|
160
|
+
This is a test gem in plain text.
|
161
|
+
--- END FILE: README.txt ---
|
162
|
+
--- START FILE: lib/unstaged_file.rb ---
|
163
|
+
Unstaged content
|
164
|
+
--- END FILE: lib/unstaged_file.rb ---
|
165
|
+
HERE
|
166
|
+
expect(content).to eq(expected)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/spec/poepod_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -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.
|
4
|
+
version: 0.1.6
|
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-
|
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: marcel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
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.
|
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
|