poepod 0.1.3 → 0.1.6
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 +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
|