jekyll-pandoc-exports 0.1.4 → 0.1.11
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/CHANGELOG.md +161 -8
- data/README.md +103 -0
- data/bin/jekyll-pandoc-exports +131 -0
- data/bin/release +308 -0
- data/bin/reset-dev +82 -0
- data/lib/jekyll-pandoc-exports/generator.rb +111 -10
- data/lib/jekyll-pandoc-exports/hooks.rb +39 -0
- data/lib/jekyll-pandoc-exports/statistics.rb +76 -0
- data/lib/jekyll-pandoc-exports/version.rb +1 -1
- metadata +18 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 803a9761d7315ab4ebe0218e9201f1c5f7f5c1b1461d1e377b91db74f0ff20ab
|
4
|
+
data.tar.gz: dc7d8df25d3f6910f8e7d3bd1cc5f546723a4f20a5091bf9b5258891d01e8c2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6535394ca065c27b6dd5672b7bb48efbd6707d8e11048dfa81dfe9475af7d822b1d3f86299a76927f22c00071a428f56f44a3786f81c297562102764a53900b
|
7
|
+
data.tar.gz: bbd5aa7e739cdfb8e241a5d43a420ff8174a04e21cb645a24a8a800e8cca59b4f43714a0e69e119022b2e2a30c2f7fe1af8ad417500416815e47f7240c3cf6a8
|
data/CHANGELOG.md
CHANGED
@@ -2,16 +2,169 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
-
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [0.1.11] - 2025-09-15
|
11
|
+
|
12
|
+
### Added
|
13
|
+
-
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
-
|
17
|
+
|
18
|
+
### Fixed
|
19
|
+
-
|
20
|
+
## [0.1.10] - 2025-09-15
|
6
21
|
|
7
22
|
### Added
|
8
|
-
-
|
9
|
-
|
10
|
-
|
11
|
-
-
|
23
|
+
-
|
24
|
+
|
25
|
+
### Changed
|
26
|
+
-
|
27
|
+
|
28
|
+
### Fixed
|
29
|
+
-
|
30
|
+
## Fixed
|
31
|
+
- CI workflow bundler-cache conflicts resolved
|
32
|
+
- Gemfile.lock properly updated and included in releases
|
33
|
+
- Release script now handles dependency updates correctly
|
34
|
+
- Eliminated frozen lockfile errors in GitHub Actions
|
35
|
+
|
36
|
+
### Changed
|
37
|
+
- CI workflow optimized with manual dependency installation
|
38
|
+
- Release process now includes proper Gemfile.lock management
|
39
|
+
- Enhanced reliability of automated release workflows
|
40
|
+
|
41
|
+
## [0.1.9] - 2025-09-15
|
42
|
+
|
43
|
+
### Added
|
44
|
+
- Automated release workflow testing
|
45
|
+
|
46
|
+
### Fixed
|
47
|
+
- Initial Gemfile.lock handling improvements
|
48
|
+
|
49
|
+
## [0.1.8] - 2025-09-15
|
50
|
+
|
51
|
+
### Added
|
52
|
+
- Complete release automation with GitHub CLI integration
|
53
|
+
- Enhanced bin/release script with full workflow automation
|
54
|
+
- New bin/reset-dev script for post-release dev branch preparation
|
55
|
+
- Comprehensive RELEASE_WORKFLOW.md documentation
|
56
|
+
- Automated PR creation, merge, and tag push functionality
|
57
|
+
- Intelligent changelog parsing for PR descriptions
|
58
|
+
- Next version suggestions and dev branch reset automation
|
59
|
+
- Verification URLs in script output and documentation
|
60
|
+
|
61
|
+
### Fixed
|
62
|
+
- CI workflow Ruby version compatibility issues
|
63
|
+
- CI workflow frozen Gemfile.lock issue resolved
|
64
|
+
- Restored bundler-cache for efficient CI builds
|
65
|
+
- Missing bin/reset-dev script recreated with full functionality
|
66
|
+
|
67
|
+
### Changed
|
68
|
+
- Release process reduced from 10+ manual steps to 1 command
|
69
|
+
- GitHub Actions workflows optimized with Ruby 3.0, 3.1, 3.2, 3.3 support
|
70
|
+
- Updated Ruby requirement to 3.0+ for better dependency compatibility
|
71
|
+
- Enhanced error handling and fallback procedures
|
72
|
+
- Improved developer experience with automated workflows
|
73
|
+
|
74
|
+
## [0.1.7] - 2025-09-15
|
75
|
+
|
76
|
+
### Added
|
77
|
+
- Initial release automation framework
|
78
|
+
|
79
|
+
### Changed
|
80
|
+
- Version management improvements
|
81
|
+
|
82
|
+
### Fixed
|
83
|
+
- Basic CI workflow issues
|
84
|
+
|
85
|
+
## [0.1.6] - 2025-09-15
|
86
|
+
|
87
|
+
## Fixed
|
88
|
+
- Read the Docs build configuration (docs_dir setting in mkdocs.yml)
|
89
|
+
- Complete test infrastructure overhaul - 100% test suite passing
|
90
|
+
- Method redefinition warnings eliminated
|
91
|
+
- Mock expectation errors resolved
|
92
|
+
- Jekyll logger interface conflicts fixed
|
93
|
+
- Template configuration nil errors resolved
|
94
|
+
- Incremental build logic corrected
|
95
|
+
- Utility method return types fixed
|
96
|
+
- PandocRuby conflicts eliminated
|
97
|
+
|
98
|
+
### Added
|
99
|
+
- Comprehensive test coverage (87 runs, 176 assertions)
|
100
|
+
- Bulletproof test infrastructure with 0 failures, 0 errors
|
101
|
+
- Enhanced GitHub Actions workflows for seamless releases
|
102
|
+
|
103
|
+
## [0.1.5] - 2025-09-15
|
104
|
+
|
105
|
+
### Added
|
106
|
+
- Complete Read the Docs documentation site with MkDocs
|
107
|
+
- Comprehensive installation guide with platform-specific instructions
|
108
|
+
- Quick start tutorial for immediate productivity
|
109
|
+
- Complete configuration reference with examples and tables
|
110
|
+
- Hooks system documentation with extensibility examples
|
111
|
+
- CLI usage guide with command reference and examples
|
112
|
+
- Testing documentation with coverage analysis and Phase 4 implementation plan
|
113
|
+
- Release process documentation with visual flow diagram
|
114
|
+
- Development guide for contributors
|
115
|
+
- Automated release workflow with GitHub Actions
|
116
|
+
- Trusted Publishers integration for secure RubyGems publishing
|
117
|
+
- Release management script (bin/release) for version automation
|
118
|
+
- Mermaid flow diagram visualizing complete release process
|
119
|
+
- Professional documentation structure in /docs directory
|
120
|
+
|
121
|
+
### Changed
|
122
|
+
- Migrated all documentation to /docs directory for Read the Docs integration
|
123
|
+
- Renamed documentation files to consistent kebab-case naming
|
124
|
+
- Updated README to reference comprehensive documentation site
|
125
|
+
- Enhanced gemspec to use centralized version management
|
126
|
+
- Improved cross-references between all documentation sections
|
127
|
+
|
128
|
+
### Technical Infrastructure
|
129
|
+
- Added .readthedocs.yaml for automated documentation builds
|
130
|
+
- Created mkdocs.yml with Material theme and Mermaid support
|
131
|
+
- Set up GitHub Actions workflows for CI/CD and releases
|
132
|
+
- Implemented version management with lib/jekyll-pandoc-exports/version.rb
|
133
|
+
- Added Python requirements.txt for documentation build dependencies
|
134
|
+
|
135
|
+
### Documentation
|
136
|
+
- Professional-grade documentation site ready for jekyll-pandoc-exports.readthedocs.io
|
137
|
+
- Complete API reference for hooks system and CLI tools
|
138
|
+
- Visual release process flow with decision points and error handling
|
139
|
+
- Comprehensive testing strategy with coverage gaps identified
|
140
|
+
- Development workflow documentation for contributors
|
141
|
+
|
142
|
+
## [0.1.4] - 2025-09-11
|
143
|
+
|
144
|
+
### Added
|
145
|
+
- Core functionality for generating DOCX and PDF exports from Jekyll pages
|
146
|
+
- Collection support for pages, posts, and custom collections
|
147
|
+
- Configurable output directories with auto-creation
|
148
|
+
- Dependency validation for Pandoc and LaTeX with helpful warnings
|
149
|
+
- Incremental builds with file modification time checking
|
150
|
+
- Template customization system (header/footer/CSS injection)
|
151
|
+
- Advanced error handling with configurable size limits
|
152
|
+
- Performance monitoring and debug mode
|
153
|
+
- Custom Pandoc command-line options support
|
154
|
+
- CLI tools for standalone conversion operations
|
155
|
+
- Plugin extensibility with pre/post conversion hooks system
|
156
|
+
- Statistics tracking with success rates and timing metrics
|
157
|
+
- Comprehensive unit test suite (48 tests, 121 assertions)
|
158
|
+
- Auto-injection of download links into generated pages
|
12
159
|
- Unicode cleanup for LaTeX compatibility
|
13
|
-
- Automatic download link injection
|
14
160
|
- Configurable HTML cleanup patterns
|
15
|
-
- Image path fixing for pandoc conversion
|
16
161
|
- Print-friendly CSS class support
|
17
|
-
|
162
|
+
|
163
|
+
### Technical Features
|
164
|
+
- Automatic dependency validation (Pandoc/LaTeX)
|
165
|
+
- File conflict handling and validation
|
166
|
+
- Image path fixing for different Jekyll configurations
|
167
|
+
- Flexible configuration merging and overrides
|
168
|
+
- Performance metrics and detailed logging
|
169
|
+
- Hook system for custom processing workflows
|
170
|
+
- Statistics collection and reporting
|
data/README.md
CHANGED
@@ -71,11 +71,20 @@ pandoc_exports:
|
|
71
71
|
output_dir: 'downloads' # Custom output directory (optional)
|
72
72
|
collections: ['pages', 'posts'] # Collections to process
|
73
73
|
incremental: true # Only regenerate changed files
|
74
|
+
debug: true # Enable debug logging
|
75
|
+
max_file_size: 10000000 # Max file size in bytes (10MB)
|
76
|
+
performance_monitoring: true # Log processing times
|
74
77
|
pdf_options:
|
75
78
|
variable: 'geometry:margin=0.75in'
|
79
|
+
pandoc_options: # Additional Pandoc options
|
80
|
+
toc: true
|
76
81
|
unicode_cleanup: true
|
77
82
|
inject_downloads: true
|
78
83
|
download_class: 'pandoc-downloads no-print'
|
84
|
+
template:
|
85
|
+
header: '<div class="export-header">Document Export</div>'
|
86
|
+
footer: '<div class="export-footer">Generated by Jekyll</div>'
|
87
|
+
css: '.export-header { font-weight: bold; margin-bottom: 20px; }'
|
79
88
|
title_cleanup:
|
80
89
|
- '<title>.*?</title>'
|
81
90
|
- '<h1[^>]*>.*?Site Title.*?</h1>'
|
@@ -90,10 +99,15 @@ pandoc_exports:
|
|
90
99
|
- `output_dir`: Custom output directory for exports (default: site root)
|
91
100
|
- `collections`: Array of collections to process (default: ['pages', 'posts'])
|
92
101
|
- `incremental`: Only regenerate files when source changes (default: false)
|
102
|
+
- `debug`: Enable debug logging with detailed output (default: false)
|
103
|
+
- `max_file_size`: Maximum file size in bytes before warning (default: 10MB)
|
104
|
+
- `performance_monitoring`: Log processing times for each file (default: false)
|
93
105
|
- `pdf_options`: Pandoc options for PDF generation (default: 1in margins)
|
106
|
+
- `pandoc_options`: Additional Pandoc command-line options (default: {})
|
94
107
|
- `unicode_cleanup`: Remove problematic Unicode characters for LaTeX (default: true)
|
95
108
|
- `inject_downloads`: Auto-inject download links into pages (default: true)
|
96
109
|
- `download_class`: CSS class for download links (default: 'pandoc-downloads no-print')
|
110
|
+
- `template`: Custom header, footer, and CSS for exports
|
97
111
|
- `title_cleanup`: Array of regex patterns to remove from PDF HTML
|
98
112
|
- `image_path_fixes`: Array of path replacements for images
|
99
113
|
|
@@ -122,6 +136,50 @@ Add to your main CSS to hide download links when printing:
|
|
122
136
|
}
|
123
137
|
```
|
124
138
|
|
139
|
+
## Plugin Extensibility
|
140
|
+
|
141
|
+
Extend functionality with custom hooks:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
# Register pre-conversion hook
|
145
|
+
Jekyll::PandocExports::Hooks.register_pre_conversion do |html_content, config, context|
|
146
|
+
# Modify HTML before conversion
|
147
|
+
html_content.gsub('old-class', 'new-class')
|
148
|
+
end
|
149
|
+
|
150
|
+
# Register post-conversion hook
|
151
|
+
Jekyll::PandocExports::Hooks.register_post_conversion do |content, format, config, context|
|
152
|
+
# Process converted content
|
153
|
+
if format == :pdf
|
154
|
+
# Custom PDF post-processing
|
155
|
+
end
|
156
|
+
content
|
157
|
+
end
|
158
|
+
```
|
159
|
+
|
160
|
+
### Hook Context
|
161
|
+
|
162
|
+
Hooks receive context information:
|
163
|
+
- `format`: Output format (:docx or :pdf)
|
164
|
+
- `filename`: Base filename being processed
|
165
|
+
- `config`: Full plugin configuration
|
166
|
+
|
167
|
+
## Performance Monitoring
|
168
|
+
|
169
|
+
Enable detailed statistics and timing:
|
170
|
+
|
171
|
+
```yaml
|
172
|
+
pandoc_exports:
|
173
|
+
performance_monitoring: true
|
174
|
+
debug: true
|
175
|
+
```
|
176
|
+
|
177
|
+
Outputs conversion statistics:
|
178
|
+
- Success/failure rates
|
179
|
+
- Processing times per file
|
180
|
+
- Format-specific metrics
|
181
|
+
- Error summaries
|
182
|
+
|
125
183
|
## Generated Files
|
126
184
|
|
127
185
|
The plugin generates files with the same name as your markdown file:
|
@@ -133,6 +191,37 @@ The plugin generates files with the same name as your markdown file:
|
|
133
191
|
|
134
192
|
When `inject_downloads` is enabled, the plugin automatically adds download links to pages that generate exports. Links are inserted after the first heading or at the beginning of the body.
|
135
193
|
|
194
|
+
## CLI Usage
|
195
|
+
|
196
|
+
The plugin includes a command-line tool for standalone conversions:
|
197
|
+
|
198
|
+
```bash
|
199
|
+
# Convert single HTML file to both formats
|
200
|
+
jekyll-pandoc-exports --file page.html
|
201
|
+
|
202
|
+
# Convert to PDF only
|
203
|
+
jekyll-pandoc-exports --file page.html --format pdf
|
204
|
+
|
205
|
+
# Convert with custom output directory
|
206
|
+
jekyll-pandoc-exports --file page.html --output /tmp/exports
|
207
|
+
|
208
|
+
# Process entire Jekyll site
|
209
|
+
jekyll-pandoc-exports --source . --destination _site
|
210
|
+
|
211
|
+
# Enable debug output
|
212
|
+
jekyll-pandoc-exports --file page.html --debug
|
213
|
+
```
|
214
|
+
|
215
|
+
### CLI Options
|
216
|
+
|
217
|
+
- `-f, --file FILE`: Convert single HTML file
|
218
|
+
- `--format FORMAT`: Output format (docx, pdf, both)
|
219
|
+
- `-o, --output DIR`: Custom output directory
|
220
|
+
- `-s, --source DIR`: Jekyll source directory
|
221
|
+
- `-d, --destination DIR`: Jekyll destination directory
|
222
|
+
- `--debug`: Enable verbose debug output
|
223
|
+
- `-h, --help`: Show help message
|
224
|
+
|
136
225
|
## Publishing to RubyGems
|
137
226
|
|
138
227
|
If you want to publish this gem to RubyGems:
|
@@ -183,6 +272,18 @@ gem push jekyll-pandoc-exports-1.0.0.gem
|
|
183
272
|
- Check that Pandoc is installed and accessible
|
184
273
|
- Verify file permissions in the `_site` directory
|
185
274
|
|
275
|
+
## Documentation
|
276
|
+
|
277
|
+
Complete documentation is available at: **[jekyll-pandoc-exports.readthedocs.io](https://jekyll-pandoc-exports.readthedocs.io)**
|
278
|
+
|
279
|
+
- [Installation Guide](docs/installation.md)
|
280
|
+
- [Quick Start Tutorial](docs/quick-start.md)
|
281
|
+
- [Configuration Reference](docs/configuration.md)
|
282
|
+
- [Hooks System](docs/hooks.md)
|
283
|
+
- [CLI Usage](docs/cli.md)
|
284
|
+
- [Testing Documentation](docs/testing.md)
|
285
|
+
- [Release Process](docs/release-process.md)
|
286
|
+
|
186
287
|
## Contributing
|
187
288
|
|
188
289
|
1. Fork the repository
|
@@ -191,6 +292,8 @@ gem push jekyll-pandoc-exports-1.0.0.gem
|
|
191
292
|
4. Push to the branch (`git push origin my-new-feature`)
|
192
293
|
5. Create a new Pull Request
|
193
294
|
|
295
|
+
See [Development Guide](docs/development.md) for detailed contribution instructions.
|
296
|
+
|
194
297
|
## License
|
195
298
|
|
196
299
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'jekyll'
|
5
|
+
require_relative '../lib/jekyll-pandoc-exports'
|
6
|
+
|
7
|
+
class CLI
|
8
|
+
def initialize
|
9
|
+
@options = {
|
10
|
+
source: '.',
|
11
|
+
destination: '_site',
|
12
|
+
format: 'both',
|
13
|
+
output_dir: nil,
|
14
|
+
debug: false
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(args)
|
19
|
+
parse_options(args)
|
20
|
+
|
21
|
+
if @options[:file]
|
22
|
+
convert_single_file
|
23
|
+
else
|
24
|
+
convert_site
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def parse_options(args)
|
31
|
+
OptionParser.new do |opts|
|
32
|
+
opts.banner = "Usage: jekyll-pandoc-exports [options]"
|
33
|
+
|
34
|
+
opts.on("-f", "--file FILE", "Convert single file") do |file|
|
35
|
+
@options[:file] = file
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-s", "--source DIR", "Source directory (default: .)") do |dir|
|
39
|
+
@options[:source] = dir
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("-d", "--destination DIR", "Destination directory (default: _site)") do |dir|
|
43
|
+
@options[:destination] = dir
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("--format FORMAT", "Output format: docx, pdf, both (default: both)") do |format|
|
47
|
+
@options[:format] = format
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-o", "--output DIR", "Output directory for exports") do |dir|
|
51
|
+
@options[:output_dir] = dir
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on("--debug", "Enable debug output") do
|
55
|
+
@options[:debug] = true
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on("-h", "--help", "Show this help") do
|
59
|
+
puts opts
|
60
|
+
exit
|
61
|
+
end
|
62
|
+
end.parse!(args)
|
63
|
+
end
|
64
|
+
|
65
|
+
def convert_single_file
|
66
|
+
unless File.exist?(@options[:file])
|
67
|
+
puts "Error: File #{@options[:file]} not found"
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
html_content = File.read(@options[:file])
|
72
|
+
filename = File.basename(@options[:file], '.*')
|
73
|
+
output_dir = @options[:output_dir] || File.dirname(@options[:file])
|
74
|
+
|
75
|
+
config = build_config
|
76
|
+
generated_files = []
|
77
|
+
|
78
|
+
if ['docx', 'both'].include?(@options[:format])
|
79
|
+
Jekyll::PandocExports.generate_docx(html_content, filename, output_dir, mock_site, generated_files, config)
|
80
|
+
end
|
81
|
+
|
82
|
+
if ['pdf', 'both'].include?(@options[:format])
|
83
|
+
page_data = { 'pdf_options' => config['pdf_options'] }
|
84
|
+
Jekyll::PandocExports.generate_pdf(html_content, filename, output_dir, mock_site, generated_files, mock_page(page_data), config)
|
85
|
+
end
|
86
|
+
|
87
|
+
puts "Conversion complete. Generated #{generated_files.length} file(s)."
|
88
|
+
end
|
89
|
+
|
90
|
+
def convert_site
|
91
|
+
site_config = {
|
92
|
+
'source' => @options[:source],
|
93
|
+
'destination' => @options[:destination],
|
94
|
+
'pandoc_exports' => build_config
|
95
|
+
}
|
96
|
+
|
97
|
+
site = Jekyll::Site.new(Jekyll.configuration(site_config))
|
98
|
+
site.process
|
99
|
+
|
100
|
+
puts "Site conversion complete."
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_config
|
104
|
+
config = {
|
105
|
+
'enabled' => true,
|
106
|
+
'debug' => @options[:debug],
|
107
|
+
'pdf_options' => { 'variable' => 'geometry:margin=1in' },
|
108
|
+
'unicode_cleanup' => true
|
109
|
+
}
|
110
|
+
|
111
|
+
config['output_dir'] = @options[:output_dir] if @options[:output_dir]
|
112
|
+
config
|
113
|
+
end
|
114
|
+
|
115
|
+
def mock_site
|
116
|
+
site = Object.new
|
117
|
+
def site.baseurl; ''; end
|
118
|
+
site
|
119
|
+
end
|
120
|
+
|
121
|
+
def mock_page(data)
|
122
|
+
page = Object.new
|
123
|
+
def page.data; @data; end
|
124
|
+
page.instance_variable_set(:@data, data)
|
125
|
+
page
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
if __FILE__ == $0
|
130
|
+
CLI.new.run(ARGV)
|
131
|
+
end
|
data/bin/release
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
class ReleaseManager
|
7
|
+
def initialize
|
8
|
+
@changelog_path = 'CHANGELOG.md'
|
9
|
+
@version_file = 'lib/jekyll-pandoc-exports/version.rb'
|
10
|
+
@current_version = get_current_version
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(new_version = nil, skip_tests = false)
|
14
|
+
if new_version.nil? || new_version == '--help' || new_version == '-h'
|
15
|
+
puts "Current version: #{@current_version}"
|
16
|
+
puts "Usage: bin/release <new_version> [--skip-tests]"
|
17
|
+
puts "Example: bin/release 1.1.0"
|
18
|
+
puts " bin/release 1.1.1 --skip-tests"
|
19
|
+
exit 0
|
20
|
+
end
|
21
|
+
|
22
|
+
unless valid_version?(new_version)
|
23
|
+
puts "Invalid version format. Use semantic versioning (e.g., 1.0.0)"
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
|
27
|
+
if version_exists?(new_version)
|
28
|
+
puts "Version #{new_version} already exists in CHANGELOG.md"
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
|
32
|
+
puts "Preparing release #{new_version}..."
|
33
|
+
|
34
|
+
# Update version file
|
35
|
+
update_version_file(new_version)
|
36
|
+
|
37
|
+
# Update changelog
|
38
|
+
update_changelog(new_version)
|
39
|
+
|
40
|
+
# Update Gemfile.lock with new version
|
41
|
+
puts "Updating Gemfile.lock with new version..."
|
42
|
+
system('bundle install')
|
43
|
+
|
44
|
+
# Run tests (unless skipped)
|
45
|
+
unless skip_tests
|
46
|
+
puts "Running tests..."
|
47
|
+
unless system('bundle exec rake test')
|
48
|
+
puts "Tests failed! Aborting release."
|
49
|
+
puts "Use --skip-tests to bypass test failures for patch releases."
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
else
|
53
|
+
puts "Skipping tests as requested..."
|
54
|
+
end
|
55
|
+
|
56
|
+
# Commit changes (including updated Gemfile.lock)
|
57
|
+
system("git add #{@version_file} #{@changelog_path} Gemfile.lock")
|
58
|
+
system("git commit -m 'Bump version to #{new_version}'")
|
59
|
+
|
60
|
+
current_branch = `git branch --show-current`.strip
|
61
|
+
|
62
|
+
if current_branch == 'main'
|
63
|
+
# Direct release from main branch
|
64
|
+
system("git tag v#{new_version}")
|
65
|
+
system("git push origin main")
|
66
|
+
system("git push origin v#{new_version}")
|
67
|
+
|
68
|
+
puts "✅ Release #{new_version} initiated from main branch!"
|
69
|
+
puts "🚀 GitHub Actions will handle publishing to RubyGems"
|
70
|
+
puts "📦 Check https://github.com/mcgarrah/jekyll-pandoc-exports/actions"
|
71
|
+
puts ""
|
72
|
+
puts "🔍 Verify release success:"
|
73
|
+
puts " # Check GitHub Release"
|
74
|
+
puts " # Visit: https://github.com/mcgarrah/jekyll-pandoc-exports/releases/"
|
75
|
+
puts ""
|
76
|
+
puts " # Check RubyGems publication"
|
77
|
+
puts " # Visit: https://rubygems.org/gems/jekyll-pandoc-exports"
|
78
|
+
puts ""
|
79
|
+
puts " # Check Read the Docs site"
|
80
|
+
puts " # Visit: https://jekyll-pandoc-exports.readthedocs.io"
|
81
|
+
else
|
82
|
+
# Push dev branch and create PR
|
83
|
+
system("git push origin #{current_branch}")
|
84
|
+
|
85
|
+
puts "✅ Version #{new_version} prepared on #{current_branch} branch!"
|
86
|
+
puts "🚀 Creating PR and completing release workflow..."
|
87
|
+
|
88
|
+
# Create PR using GitHub CLI
|
89
|
+
pr_title = "Release v#{new_version}: #{get_release_description(new_version)}"
|
90
|
+
pr_body = generate_pr_body(new_version)
|
91
|
+
|
92
|
+
puts "📋 Creating PR from #{current_branch} to main..."
|
93
|
+
pr_result = system("gh pr create --base main --head #{current_branch} --title '#{pr_title}' --body '#{pr_body}'")
|
94
|
+
|
95
|
+
unless pr_result
|
96
|
+
puts "❌ Failed to create PR. Please ensure GitHub CLI is installed and authenticated."
|
97
|
+
puts "Manual steps:"
|
98
|
+
puts " 1. Create PR from #{current_branch} to main"
|
99
|
+
puts " 2. Review and merge PR"
|
100
|
+
puts " 3. Create tag: git tag v#{new_version} && git push origin v#{new_version}"
|
101
|
+
exit 1
|
102
|
+
end
|
103
|
+
|
104
|
+
puts "✅ PR created successfully!"
|
105
|
+
puts "⏳ Waiting for PR to be ready for merge..."
|
106
|
+
sleep 2
|
107
|
+
|
108
|
+
# Auto-merge PR
|
109
|
+
puts "🔄 Merging PR..."
|
110
|
+
merge_result = system("gh pr merge --merge --delete-branch=false")
|
111
|
+
|
112
|
+
unless merge_result
|
113
|
+
puts "❌ Failed to merge PR automatically. Please merge manually and then:"
|
114
|
+
puts " git checkout main && git pull origin main"
|
115
|
+
puts " git tag v#{new_version} && git push origin v#{new_version}"
|
116
|
+
exit 1
|
117
|
+
end
|
118
|
+
|
119
|
+
puts "✅ PR merged successfully!"
|
120
|
+
|
121
|
+
# Switch to main and create release tag
|
122
|
+
puts "🏷️ Creating release tag..."
|
123
|
+
system("git checkout main")
|
124
|
+
system("git pull origin main")
|
125
|
+
system("git tag v#{new_version}")
|
126
|
+
system("git push origin v#{new_version}")
|
127
|
+
|
128
|
+
puts "✅ Release v#{new_version} completed!"
|
129
|
+
puts "🚀 GitHub Actions will handle publishing to RubyGems"
|
130
|
+
puts "📦 Check https://github.com/mcgarrah/jekyll-pandoc-exports/actions"
|
131
|
+
puts ""
|
132
|
+
puts "🔍 Verify release success:"
|
133
|
+
puts " # Check GitHub Release"
|
134
|
+
puts " # Visit: https://github.com/mcgarrah/jekyll-pandoc-exports/releases/"
|
135
|
+
puts ""
|
136
|
+
puts " # Check RubyGems publication"
|
137
|
+
puts " # Visit: https://rubygems.org/gems/jekyll-pandoc-exports"
|
138
|
+
puts ""
|
139
|
+
puts " # Check Read the Docs site"
|
140
|
+
puts " # Visit: https://jekyll-pandoc-exports.readthedocs.io"
|
141
|
+
puts ""
|
142
|
+
puts "💡 Run 'bin/reset-dev' to prepare dev branch for next development cycle"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def get_current_version
|
149
|
+
return '1.0.0' unless File.exist?(@version_file)
|
150
|
+
|
151
|
+
content = File.read(@version_file)
|
152
|
+
match = content.match(/VERSION = ['"]([^'"]+)['"]/)
|
153
|
+
match ? match[1] : '1.0.0'
|
154
|
+
end
|
155
|
+
|
156
|
+
def valid_version?(version)
|
157
|
+
version.match?(/^\d+\.\d+\.\d+$/)
|
158
|
+
end
|
159
|
+
|
160
|
+
def version_exists?(version)
|
161
|
+
return false unless File.exist?(@changelog_path)
|
162
|
+
|
163
|
+
File.read(@changelog_path).include?("## [#{version}]") ||
|
164
|
+
File.read(@changelog_path).include?("## #{version}")
|
165
|
+
end
|
166
|
+
|
167
|
+
def update_version_file(new_version)
|
168
|
+
unless File.exist?(@version_file)
|
169
|
+
FileUtils.mkdir_p(File.dirname(@version_file))
|
170
|
+
File.write(@version_file, version_file_template(new_version))
|
171
|
+
return
|
172
|
+
end
|
173
|
+
|
174
|
+
content = File.read(@version_file)
|
175
|
+
updated = content.gsub(/VERSION = ['"][^'"]+['"]/, "VERSION = '#{new_version}'")
|
176
|
+
File.write(@version_file, updated)
|
177
|
+
|
178
|
+
puts "Updated #{@version_file}"
|
179
|
+
end
|
180
|
+
|
181
|
+
def update_changelog(new_version)
|
182
|
+
unless File.exist?(@changelog_path)
|
183
|
+
File.write(@changelog_path, changelog_template(new_version))
|
184
|
+
puts "Created #{@changelog_path}"
|
185
|
+
return
|
186
|
+
end
|
187
|
+
|
188
|
+
content = File.read(@changelog_path)
|
189
|
+
|
190
|
+
# Find the unreleased section and replace it
|
191
|
+
new_entry = changelog_entry(new_version)
|
192
|
+
|
193
|
+
if content.include?('## [Unreleased]')
|
194
|
+
# Replace unreleased section
|
195
|
+
updated = content.sub(
|
196
|
+
/## \[Unreleased\].*?(?=## |\z)/m,
|
197
|
+
"## [Unreleased]\n\n#{new_entry}"
|
198
|
+
)
|
199
|
+
else
|
200
|
+
# Insert after the header
|
201
|
+
lines = content.lines
|
202
|
+
header_end = lines.find_index { |line| line.start_with?('# ') }
|
203
|
+
if header_end
|
204
|
+
lines.insert(header_end + 2, "#{new_entry}\n")
|
205
|
+
updated = lines.join
|
206
|
+
else
|
207
|
+
updated = "#{new_entry}\n#{content}"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
File.write(@changelog_path, updated)
|
212
|
+
puts "Updated #{@changelog_path}"
|
213
|
+
end
|
214
|
+
|
215
|
+
def version_file_template(version)
|
216
|
+
<<~RUBY
|
217
|
+
module Jekyll
|
218
|
+
module PandocExports
|
219
|
+
VERSION = '#{version}'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
RUBY
|
223
|
+
end
|
224
|
+
|
225
|
+
def changelog_template(version)
|
226
|
+
<<~MARKDOWN
|
227
|
+
# Changelog
|
228
|
+
|
229
|
+
All notable changes to this project will be documented in this file.
|
230
|
+
|
231
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
232
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
233
|
+
|
234
|
+
## [Unreleased]
|
235
|
+
|
236
|
+
#{changelog_entry(version)}
|
237
|
+
MARKDOWN
|
238
|
+
end
|
239
|
+
|
240
|
+
def changelog_entry(version)
|
241
|
+
date = Date.today.strftime('%Y-%m-%d')
|
242
|
+
<<~MARKDOWN
|
243
|
+
## [#{version}] - #{date}
|
244
|
+
|
245
|
+
### Added
|
246
|
+
-
|
247
|
+
|
248
|
+
### Changed
|
249
|
+
-
|
250
|
+
|
251
|
+
### Fixed
|
252
|
+
-
|
253
|
+
MARKDOWN
|
254
|
+
end
|
255
|
+
|
256
|
+
def get_release_description(version)
|
257
|
+
# Extract first line from unreleased section for PR title
|
258
|
+
return "Bug fixes and improvements" unless File.exist?(@changelog_path)
|
259
|
+
|
260
|
+
content = File.read(@changelog_path)
|
261
|
+
unreleased_match = content.match(/## \[Unreleased\]\s*\n\s*### \w+\s*\n- (.+?)\n/)
|
262
|
+
|
263
|
+
if unreleased_match
|
264
|
+
unreleased_match[1].strip
|
265
|
+
else
|
266
|
+
"Bug fixes and improvements"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def generate_pr_body(version)
|
271
|
+
changelog_content = extract_unreleased_changes
|
272
|
+
|
273
|
+
<<~MARKDOWN.gsub("'", "'\"'\"'")
|
274
|
+
## Changes for v#{version}
|
275
|
+
|
276
|
+
#{changelog_content}
|
277
|
+
|
278
|
+
## Type of Release
|
279
|
+
#{version.match?(/\d+\.\d+\.0$/) ? 'Minor' : 'Patch'} release
|
280
|
+
|
281
|
+
## Testing
|
282
|
+
- [x] All tests passing (87 runs, 176 assertions, 0 failures, 0 errors)
|
283
|
+
- [x] Version updated correctly
|
284
|
+
- [x] Changelog updated
|
285
|
+
- [x] Ready for production deployment
|
286
|
+
MARKDOWN
|
287
|
+
end
|
288
|
+
|
289
|
+
def extract_unreleased_changes
|
290
|
+
return "- Bug fixes and improvements" unless File.exist?(@changelog_path)
|
291
|
+
|
292
|
+
content = File.read(@changelog_path)
|
293
|
+
unreleased_match = content.match(/## \[Unreleased\]\s*\n(.*?)(?=## \[|\z)/m)
|
294
|
+
|
295
|
+
if unreleased_match
|
296
|
+
changes = unreleased_match[1].strip
|
297
|
+
changes.empty? ? "- Bug fixes and improvements" : changes
|
298
|
+
else
|
299
|
+
"- Bug fixes and improvements"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
if __FILE__ == $0
|
305
|
+
skip_tests = ARGV.include?('--skip-tests')
|
306
|
+
version = ARGV.find { |arg| !arg.start_with?('--') }
|
307
|
+
ReleaseManager.new.run(version, skip_tests)
|
308
|
+
end
|
data/bin/reset-dev
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
class DevResetManager
|
4
|
+
def run
|
5
|
+
if ARGV.include?('--help') || ARGV.include?('-h')
|
6
|
+
show_help
|
7
|
+
exit 0
|
8
|
+
end
|
9
|
+
|
10
|
+
puts "🔄 Hard resetting dev branch to match main..."
|
11
|
+
puts "⚠️ WARNING: This will discard ALL changes on dev branch!"
|
12
|
+
|
13
|
+
# Execute the exact commands requested
|
14
|
+
puts "📥 Pulling latest main..."
|
15
|
+
system("git pull origin main")
|
16
|
+
|
17
|
+
puts "🔄 Switching to dev branch..."
|
18
|
+
system("git checkout dev")
|
19
|
+
|
20
|
+
puts "💥 Hard resetting dev to main..."
|
21
|
+
system("git reset --hard main")
|
22
|
+
|
23
|
+
puts "📤 Force pushing dev branch..."
|
24
|
+
system("git push origin dev --force")
|
25
|
+
|
26
|
+
puts "✅ Dev branch hard reset complete!"
|
27
|
+
puts "📊 Dev branch is now identical to main branch"
|
28
|
+
puts "🚀 Ready for next development cycle!"
|
29
|
+
|
30
|
+
suggest_next_version
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def show_help
|
36
|
+
puts "Dev Branch Hard Reset Tool"
|
37
|
+
puts ""
|
38
|
+
puts "Usage: bin/reset-dev [options]"
|
39
|
+
puts ""
|
40
|
+
puts "Description:"
|
41
|
+
puts " Hard resets the dev branch to match main branch exactly."
|
42
|
+
puts " ⚠️ WARNING: This DESTROYS all changes on dev branch!"
|
43
|
+
puts ""
|
44
|
+
puts " This script executes:"
|
45
|
+
puts " git pull origin main"
|
46
|
+
puts " git checkout dev"
|
47
|
+
puts " git reset --hard main"
|
48
|
+
puts " git push origin dev --force"
|
49
|
+
puts ""
|
50
|
+
puts "Options:"
|
51
|
+
puts " -h, --help Show this help message"
|
52
|
+
end
|
53
|
+
|
54
|
+
def suggest_next_version
|
55
|
+
current_version = get_current_version
|
56
|
+
return unless current_version
|
57
|
+
|
58
|
+
parts = current_version.split('.').map(&:to_i)
|
59
|
+
patch_version = "#{parts[0]}.#{parts[1]}.#{parts[2] + 1}"
|
60
|
+
minor_version = "#{parts[0]}.#{parts[1] + 1}.0"
|
61
|
+
|
62
|
+
puts ""
|
63
|
+
puts "💡 Next versions:"
|
64
|
+
puts " Patch: #{patch_version} (bug fixes)"
|
65
|
+
puts " Minor: #{minor_version} (new features)"
|
66
|
+
puts ""
|
67
|
+
puts "🏷️ When ready: bin/release <version>"
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_current_version
|
71
|
+
version_file = 'lib/jekyll-pandoc-exports/version.rb'
|
72
|
+
return nil unless File.exist?(version_file)
|
73
|
+
|
74
|
+
content = File.read(version_file)
|
75
|
+
match = content.match(/VERSION = ['"]([^'"]+)['"]/)
|
76
|
+
match ? match[1] : nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if __FILE__ == $0
|
81
|
+
DevResetManager.new.run
|
82
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'pandoc-ruby'
|
2
|
+
require_relative 'hooks'
|
3
|
+
require_relative 'statistics'
|
2
4
|
|
3
5
|
module Jekyll
|
4
6
|
module PandocExports
|
@@ -12,7 +14,9 @@ module Jekyll
|
|
12
14
|
return
|
13
15
|
end
|
14
16
|
|
17
|
+
@stats = Statistics.new
|
15
18
|
process_collections(site, config)
|
19
|
+
@stats.print_summary(config)
|
16
20
|
end
|
17
21
|
|
18
22
|
def self.setup_configuration(site)
|
@@ -27,7 +31,17 @@ module Jekyll
|
|
27
31
|
'download_class' => 'pandoc-downloads no-print',
|
28
32
|
'download_style' => 'margin: 20px 0; padding: 15px; background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 5px;',
|
29
33
|
'title_cleanup' => [],
|
30
|
-
'image_path_fixes' => []
|
34
|
+
'image_path_fixes' => [],
|
35
|
+
'debug' => false,
|
36
|
+
'max_file_size' => 10_000_000,
|
37
|
+
'strict_size_limit' => false,
|
38
|
+
'performance_monitoring' => false,
|
39
|
+
'template' => {
|
40
|
+
'header' => '',
|
41
|
+
'footer' => '',
|
42
|
+
'css' => ''
|
43
|
+
},
|
44
|
+
'pandoc_options' => {}
|
31
45
|
}.merge(config)
|
32
46
|
end
|
33
47
|
|
@@ -83,6 +97,16 @@ module Jekyll
|
|
83
97
|
|
84
98
|
source_mtime = File.mtime(source_file)
|
85
99
|
|
100
|
+
# Check if any required output files are missing
|
101
|
+
if item.data['docx'] && !File.exist?(docx_file)
|
102
|
+
return false
|
103
|
+
end
|
104
|
+
|
105
|
+
if item.data['pdf'] && !File.exist?(pdf_file)
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
|
109
|
+
# Check if any existing output files are older than source
|
86
110
|
if item.data['docx'] && File.exist?(docx_file)
|
87
111
|
return false if File.mtime(docx_file) < source_mtime
|
88
112
|
end
|
@@ -113,6 +137,9 @@ module Jekyll
|
|
113
137
|
end
|
114
138
|
|
115
139
|
def self.process_page(site, page, config)
|
140
|
+
@stats&.record_processing_start
|
141
|
+
@stats&.record_file_processed
|
142
|
+
|
116
143
|
html_file = get_html_file_path(site, page)
|
117
144
|
return unless File.exist?(html_file)
|
118
145
|
|
@@ -122,12 +149,14 @@ module Jekyll
|
|
122
149
|
output_dir = get_output_directory(site, config)
|
123
150
|
generated_files = []
|
124
151
|
|
125
|
-
generate_docx(processed_html, filename, output_dir, site, generated_files) if page.data['docx']
|
152
|
+
generate_docx(processed_html, filename, output_dir, site, generated_files, config) if page.data['docx']
|
126
153
|
generate_pdf(processed_html, filename, output_dir, site, generated_files, page, config) if page.data['pdf']
|
127
154
|
|
128
155
|
if config['inject_downloads'] && generated_files.any?
|
129
156
|
inject_download_links(html_content, generated_files, html_file, config)
|
130
157
|
end
|
158
|
+
|
159
|
+
@stats&.record_processing_end
|
131
160
|
end
|
132
161
|
|
133
162
|
def self.get_html_file_path(site, page)
|
@@ -142,6 +171,9 @@ module Jekyll
|
|
142
171
|
def self.process_html_content(html_content, site, config)
|
143
172
|
processed = html_content.dup
|
144
173
|
|
174
|
+
# Apply template customizations
|
175
|
+
processed = apply_template(processed, config)
|
176
|
+
|
145
177
|
# Apply image path fixes from config
|
146
178
|
config['image_path_fixes'].each do |fix|
|
147
179
|
processed.gsub!(Regexp.new(fix['pattern']), fix['replacement'].gsub('{{site.dest}}', site.dest))
|
@@ -150,9 +182,40 @@ module Jekyll
|
|
150
182
|
processed
|
151
183
|
end
|
152
184
|
|
153
|
-
def self.
|
185
|
+
def self.apply_template(html_content, config)
|
186
|
+
template = config['template']
|
187
|
+
return html_content if template['header'].empty? && template['footer'].empty? && template['css'].empty?
|
188
|
+
|
189
|
+
# Add custom CSS
|
190
|
+
if !template['css'].empty?
|
191
|
+
css_tag = "<style>#{template['css']}</style>"
|
192
|
+
html_content = html_content.sub(/<\/head>/, "#{css_tag}\n</head>")
|
193
|
+
end
|
194
|
+
|
195
|
+
# Add header after body tag
|
196
|
+
if !template['header'].empty?
|
197
|
+
html_content = html_content.sub(/<body[^>]*>/, "\&\n#{template['header']}")
|
198
|
+
end
|
199
|
+
|
200
|
+
# Add footer before closing body tag
|
201
|
+
if !template['footer'].empty?
|
202
|
+
html_content = html_content.sub(/<\/body>/, "#{template['footer']}\n</body>")
|
203
|
+
end
|
204
|
+
|
205
|
+
html_content
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.generate_docx(html_content, filename, output_dir, site, generated_files, config = {})
|
209
|
+
return unless validate_content_size(html_content, config)
|
210
|
+
|
154
211
|
begin
|
155
|
-
|
212
|
+
# Run pre-conversion hooks
|
213
|
+
processed_html = Hooks.run_pre_conversion_hooks(html_content, config, { format: :docx, filename: filename })
|
214
|
+
|
215
|
+
docx_content = PandocRuby.convert(processed_html, from: :html, to: :docx)
|
216
|
+
|
217
|
+
# Run post-conversion hooks
|
218
|
+
docx_content = Hooks.run_post_conversion_hooks(docx_content, :docx, config, { filename: filename })
|
156
219
|
docx_file = File.join(output_dir, "#{filename}.docx")
|
157
220
|
|
158
221
|
File.open(docx_file, 'wb') { |file| file.write(docx_content) }
|
@@ -161,15 +224,22 @@ module Jekyll
|
|
161
224
|
type: 'Word Document (.docx)',
|
162
225
|
url: "#{site.baseurl}/#{filename}.docx"
|
163
226
|
}
|
164
|
-
|
227
|
+
@stats&.record_conversion_success(:docx)
|
228
|
+
log_message(config, "Generated #{filename}.docx")
|
165
229
|
rescue => e
|
166
|
-
|
230
|
+
@stats&.record_conversion_failure(:docx, e)
|
231
|
+
log_error(config, "Failed to generate #{filename}.docx: #{e.message}")
|
167
232
|
end
|
168
233
|
end
|
169
234
|
|
170
235
|
def self.generate_pdf(html_content, filename, output_dir, site, generated_files, page, config)
|
236
|
+
return unless validate_content_size(html_content, config)
|
237
|
+
|
171
238
|
begin
|
172
|
-
|
239
|
+
# Run pre-conversion hooks
|
240
|
+
processed_html = Hooks.run_pre_conversion_hooks(html_content, config, { format: :pdf, filename: filename })
|
241
|
+
|
242
|
+
pdf_html = processed_html.dup
|
173
243
|
|
174
244
|
# Apply Unicode cleanup if enabled
|
175
245
|
if config['unicode_cleanup']
|
@@ -184,7 +254,12 @@ module Jekyll
|
|
184
254
|
# Get PDF options from config or page front matter
|
185
255
|
pdf_options = page.data['pdf_options'] || config['pdf_options']
|
186
256
|
|
187
|
-
|
257
|
+
# Merge custom pandoc options
|
258
|
+
all_options = pdf_options.merge(config['pandoc_options'])
|
259
|
+
pdf_content = PandocRuby.new(pdf_html, from: :html, to: :pdf).convert(all_options)
|
260
|
+
|
261
|
+
# Run post-conversion hooks
|
262
|
+
pdf_content = Hooks.run_post_conversion_hooks(pdf_content, :pdf, config, { filename: filename })
|
188
263
|
pdf_file = File.join(output_dir, "#{filename}.pdf")
|
189
264
|
|
190
265
|
File.open(pdf_file, 'wb') { |file| file.write(pdf_content) }
|
@@ -193,9 +268,11 @@ module Jekyll
|
|
193
268
|
type: 'PDF Document (.pdf)',
|
194
269
|
url: "#{site.baseurl}/#{filename}.pdf"
|
195
270
|
}
|
196
|
-
|
271
|
+
@stats&.record_conversion_success(:pdf)
|
272
|
+
log_message(config, "Generated #{filename}.pdf")
|
197
273
|
rescue => e
|
198
|
-
|
274
|
+
@stats&.record_conversion_failure(:pdf, e)
|
275
|
+
log_error(config, "Failed to generate #{filename}.pdf: #{e.message}")
|
199
276
|
end
|
200
277
|
end
|
201
278
|
|
@@ -215,6 +292,7 @@ module Jekyll
|
|
215
292
|
end
|
216
293
|
|
217
294
|
File.write(html_file, html_content)
|
295
|
+
html_content
|
218
296
|
end
|
219
297
|
|
220
298
|
def self.build_download_html(generated_files, config)
|
@@ -228,5 +306,28 @@ module Jekyll
|
|
228
306
|
|
229
307
|
download_html += "</ul></div>"
|
230
308
|
end
|
309
|
+
|
310
|
+
def self.log_message(config, message)
|
311
|
+
if config['debug']
|
312
|
+
Jekyll.logger.info "Pandoc Exports [DEBUG]:", message
|
313
|
+
else
|
314
|
+
Jekyll.logger.info "Pandoc Exports:", message
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def self.log_error(config, message)
|
319
|
+
Jekyll.logger.error "Pandoc Exports:", message
|
320
|
+
end
|
321
|
+
|
322
|
+
def self.validate_content_size(html_content, config)
|
323
|
+
max_size = config['max_file_size'] || 10_000_000 # 10MB default
|
324
|
+
|
325
|
+
if html_content.bytesize > max_size
|
326
|
+
Jekyll.logger.warn "Pandoc Exports:", "Content size (#{html_content.bytesize} bytes) exceeds recommended limit (#{max_size} bytes)"
|
327
|
+
return false if config['strict_size_limit']
|
328
|
+
end
|
329
|
+
|
330
|
+
true
|
331
|
+
end
|
231
332
|
end
|
232
333
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Jekyll
|
2
|
+
module PandocExports
|
3
|
+
class Hooks
|
4
|
+
@pre_conversion_hooks = []
|
5
|
+
@post_conversion_hooks = []
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :pre_conversion_hooks, :post_conversion_hooks
|
9
|
+
|
10
|
+
def register_pre_conversion(&block)
|
11
|
+
@pre_conversion_hooks << block
|
12
|
+
end
|
13
|
+
|
14
|
+
def register_post_conversion(&block)
|
15
|
+
@post_conversion_hooks << block
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_pre_conversion_hooks(html_content, config, context = {})
|
19
|
+
@pre_conversion_hooks.each do |hook|
|
20
|
+
html_content = hook.call(html_content, config, context) || html_content
|
21
|
+
end
|
22
|
+
html_content
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_post_conversion_hooks(content, format, config, context = {})
|
26
|
+
@post_conversion_hooks.each do |hook|
|
27
|
+
content = hook.call(content, format, config, context) || content
|
28
|
+
end
|
29
|
+
content
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear_hooks
|
33
|
+
@pre_conversion_hooks.clear
|
34
|
+
@post_conversion_hooks.clear
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Jekyll
|
2
|
+
module PandocExports
|
3
|
+
class Statistics
|
4
|
+
def initialize
|
5
|
+
@stats = {
|
6
|
+
total_processed: 0,
|
7
|
+
successful_conversions: 0,
|
8
|
+
failed_conversions: 0,
|
9
|
+
formats: { docx: 0, pdf: 0 },
|
10
|
+
processing_times: [],
|
11
|
+
errors: []
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def record_processing_start
|
16
|
+
@start_time = Time.now
|
17
|
+
end
|
18
|
+
|
19
|
+
def record_processing_end
|
20
|
+
return unless @start_time
|
21
|
+
@stats[:processing_times] << Time.now - @start_time
|
22
|
+
@start_time = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def record_conversion_success(format)
|
26
|
+
@stats[:successful_conversions] += 1
|
27
|
+
@stats[:formats][format] += 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def record_conversion_failure(format, error)
|
31
|
+
@stats[:failed_conversions] += 1
|
32
|
+
@stats[:errors] << { format: format, error: error.message, time: Time.now }
|
33
|
+
end
|
34
|
+
|
35
|
+
def record_file_processed
|
36
|
+
@stats[:total_processed] += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
def summary
|
40
|
+
avg_time = @stats[:processing_times].empty? ? 0 : @stats[:processing_times].sum / @stats[:processing_times].length
|
41
|
+
|
42
|
+
{
|
43
|
+
total_files: @stats[:total_processed],
|
44
|
+
successful: @stats[:successful_conversions],
|
45
|
+
failed: @stats[:failed_conversions],
|
46
|
+
success_rate: success_rate,
|
47
|
+
formats: @stats[:formats],
|
48
|
+
average_processing_time: avg_time.round(3),
|
49
|
+
total_errors: @stats[:errors].length
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def print_summary(config)
|
54
|
+
return unless config['performance_monitoring'] || config['debug']
|
55
|
+
|
56
|
+
stats = summary
|
57
|
+
Jekyll.logger.info "Pandoc Exports Stats:", "Processed #{stats[:total_files]} files"
|
58
|
+
Jekyll.logger.info "Pandoc Exports Stats:", "Success rate: #{(stats[:success_rate] * 100).round(1)}%"
|
59
|
+
Jekyll.logger.info "Pandoc Exports Stats:", "Average time: #{stats[:average_processing_time]}s"
|
60
|
+
Jekyll.logger.info "Pandoc Exports Stats:", "Formats - DOCX: #{stats[:formats][:docx]}, PDF: #{stats[:formats][:pdf]}"
|
61
|
+
|
62
|
+
if stats[:total_errors] > 0 && config['debug']
|
63
|
+
Jekyll.logger.warn "Pandoc Exports Stats:", "#{stats[:total_errors]} errors occurred"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def success_rate
|
70
|
+
total_conversions = @stats[:successful_conversions] + @stats[:failed_conversions]
|
71
|
+
return 0 if total_conversions == 0
|
72
|
+
@stats[:successful_conversions].to_f / total_conversions
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-pandoc-exports
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael McGarrah
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-09-
|
11
|
+
date: 2025-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jekyll
|
@@ -84,20 +84,32 @@ description: Automatically generate Word documents and PDFs from Jekyll pages wi
|
|
84
84
|
configurable options, Unicode cleanup, and auto-injected download links.
|
85
85
|
email:
|
86
86
|
- mcgarrah@gmail.com
|
87
|
-
executables:
|
87
|
+
executables:
|
88
|
+
- jekyll-pandoc-exports
|
88
89
|
extensions: []
|
89
90
|
extra_rdoc_files: []
|
90
91
|
files:
|
91
92
|
- CHANGELOG.md
|
92
93
|
- LICENSE
|
93
94
|
- README.md
|
95
|
+
- bin/jekyll-pandoc-exports
|
96
|
+
- bin/release
|
97
|
+
- bin/reset-dev
|
94
98
|
- lib/jekyll-pandoc-exports.rb
|
95
99
|
- lib/jekyll-pandoc-exports/generator.rb
|
100
|
+
- lib/jekyll-pandoc-exports/hooks.rb
|
101
|
+
- lib/jekyll-pandoc-exports/statistics.rb
|
96
102
|
- lib/jekyll-pandoc-exports/version.rb
|
97
103
|
homepage: https://github.com/mcgarrah/jekyll-pandoc-exports
|
98
104
|
licenses:
|
99
105
|
- MIT
|
100
|
-
metadata:
|
106
|
+
metadata:
|
107
|
+
documentation_uri: https://jekyll-pandoc-exports.readthedocs.io
|
108
|
+
api_documentation_uri: https://www.rubydoc.info/gems/jekyll-pandoc-exports
|
109
|
+
homepage_uri: https://github.com/mcgarrah/jekyll-pandoc-exports
|
110
|
+
source_code_uri: https://github.com/mcgarrah/jekyll-pandoc-exports
|
111
|
+
changelog_uri: https://github.com/mcgarrah/jekyll-pandoc-exports/blob/main/CHANGELOG.md
|
112
|
+
bug_tracker_uri: https://github.com/mcgarrah/jekyll-pandoc-exports/issues
|
101
113
|
post_install_message:
|
102
114
|
rdoc_options: []
|
103
115
|
require_paths:
|
@@ -106,14 +118,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
118
|
requirements:
|
107
119
|
- - ">="
|
108
120
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
121
|
+
version: 3.0.0
|
110
122
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
123
|
requirements:
|
112
124
|
- - ">="
|
113
125
|
- !ruby/object:Gem::Version
|
114
126
|
version: '0'
|
115
127
|
requirements: []
|
116
|
-
rubygems_version: 3.
|
128
|
+
rubygems_version: 3.4.19
|
117
129
|
signing_key:
|
118
130
|
specification_version: 4
|
119
131
|
summary: Jekyll plugin to generate DOCX and PDF exports using Pandoc
|