rezy 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7374d2170bbaab6bcf908a10824b7661163e01c409cfc156825076d805edfecb
4
+ data.tar.gz: 40382ff2ec6dfbbccf111f142aa8eee52157af75d332c4ab291a78432ea3f0cd
5
+ SHA512:
6
+ metadata.gz: f3be052eac05aec22472beb1f27d8e7aecf5682accd961c5a8b40532fd9ede6cdca9856217ad039a3b6aae74ce3dbb8419877cf624a9cf7a2e8631fddd1b9fb0
7
+ data.tar.gz: 5cc010ed629ffc698dcd304995e4d0e2a5d199a043267634340de0d84f4e2f964e3280aff58feb3dc999de355e19eb97def2c44669d3866ad3091f930854360a
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
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
+ ## [0.1.0] - 2025-06-21
9
+ ### Added
10
+ - Initial release of the rezy gem
11
+ - Support for generating resumes from YAML data files
12
+ - Simple HTML template included
13
+ - Command-line interface (CLI) for resume generation
14
+
15
+ [0.1.0]: https://github.com/collindonnell/rezy/releases/tag/v0.1.0
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Rezy
2
+
3
+ A simple Ruby gem for generating resumes from YAML data files using customizable templates.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ $ gem install rezy
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ 1. Create a new resume project directory and use `rezy init` to populate resume data
14
+ 2. Update YAML file with your resume data (`data/resume.yaml`)
15
+ 3. Update the template ERB and CSS however you like (`template` directory)
16
+ 4. Generate your resume:
17
+
18
+ ```
19
+ $ rezy generate
20
+ ```
21
+
22
+ By default, this will use the simple template included with the gem. You can specify a custom template with:
23
+
24
+ ## Contributing
25
+
26
+ Bug reports and pull requests are welcome on GitHub at https://github.com/collindonnell/rezy.
27
+
28
+ ## License
29
+
30
+ The gem is available as open source under the terms of the MIT License.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+ task default: %i[test]
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "rezy"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other setup tasks needed for development
data/data/resume.yaml ADDED
File without changes
data/exe/rezy ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/rezy"
4
+
5
+ Rezy::CLI.new.run(ARGV)
data/lib/rezy/cli.rb ADDED
@@ -0,0 +1,143 @@
1
+ require "optparse"
2
+
3
+ class Rezy::CLI
4
+ def run(argv)
5
+ parse_global_options
6
+ end
7
+
8
+ private
9
+
10
+ def parse_global_options
11
+ global_parser = OptionParser.new do |opts|
12
+ opts.banner = "Usage: rezy <command> [options]"
13
+ opts.separator ""
14
+ opts.separator "Commands:"
15
+ opts.separator " init Initialize a new resume project"
16
+ opts.separator " generate Generate resume from existing project"
17
+ opts.separator ""
18
+ opts.on("-v", "--version", "Show version") do
19
+ puts "Rezy #{Rezy::VERSION}"
20
+ exit
21
+ end
22
+ opts.on("-h", "--help", "Show this help") do
23
+ puts opts
24
+ exit
25
+ end
26
+ end
27
+
28
+ # Parse global options and get subcommand
29
+ begin
30
+ global_parser.order!
31
+ rescue OptionParser::InvalidOption => e
32
+ puts "Error: #{e.message}"
33
+ puts global_parser
34
+ exit 1
35
+ end
36
+
37
+ command = ARGV.shift || "generate"
38
+ case command
39
+ when "init"
40
+ handle_init_command
41
+ when "generate"
42
+ handle_generate_command
43
+ else
44
+ puts "Error: Unknown command '#{command}'"
45
+ puts global_parser
46
+ exit 1
47
+ end
48
+ end
49
+
50
+ def handle_init_command
51
+ init_options = {
52
+ template: "simple",
53
+ directory: "."
54
+ }
55
+
56
+ OptionParser.new do |opts|
57
+ opts.banner = "Usage: resume init [options]"
58
+ opts.separator ""
59
+ opts.separator "Initialize a new resume project with sample data and template"
60
+ opts.separator ""
61
+
62
+ opts.on("-t", "--template NAME", "Template to use (default: simple)") do |template|
63
+ init_options[:template] = template
64
+ end
65
+
66
+ opts.on("-d", "--directory DIR", "Directory to create project in (default: current)") do |dir|
67
+ init_options[:directory] = dir
68
+ end
69
+
70
+ opts.on("-h", "--help", "Show this help") do
71
+ puts opts
72
+ exit
73
+ end
74
+ end.parse!
75
+
76
+ init_with_options(init_options)
77
+ end
78
+
79
+ def init_with_options(options)
80
+ puts "Initializing resume project with options: #{options.inspect}"
81
+ project_dir = options[:directory]
82
+ mkdir_p(project_dir) unless Dir.exist?(project_dir)
83
+
84
+ source_template = File.join(Rezy::TEMPLATES_DIR, options[:template])
85
+ dest_templates_path = File.join(project_dir, "templates")
86
+ FileUtils.cp_r(source_template, dest_templates_path) if Dir.exist?(source_template)
87
+
88
+ source_data_file = File.join(Rezy::GEM_ROOT, "data/resume.yaml")
89
+ FileUtils.cp(source_data_file, project_dir) if File.exist?(source_data_file)
90
+
91
+ puts "Project initialized with template '#{options[:template]}' in directory '#{project_dir}'"
92
+ rescue StandardError => e
93
+ puts "Error initializing project: #{e.message}"
94
+ exit 1
95
+ end
96
+
97
+ def handle_generate_command
98
+ # Generate command options (your existing code)
99
+ generate_options = {
100
+ data_file: "resume.yaml",
101
+ output_dir: "output",
102
+ formats: [:html, :pdf]
103
+ }
104
+
105
+ OptionParser.new do |opts|
106
+ opts.banner = "Usage: rezy generate [options]"
107
+ opts.separator ""
108
+ opts.separator "Generate resume HTML and PDF from YAML data"
109
+ opts.separator ""
110
+
111
+ opts.on("-d", "--data FILE", "YAML data file (default: resume.yaml)") do |file|
112
+ generate_options[:data_file] = file
113
+ end
114
+
115
+ opts.on("-o", "--output DIR", "Output directory (default: output)") do |dir|
116
+ generate_options[:output_dir] = dir
117
+ end
118
+
119
+ opts.on("-f", "--formats FORMATS", Array, "Output formats: html,pdf (default: html,pdf)") do |formats|
120
+ generate_options[:formats] = formats.map(&:to_sym)
121
+ end
122
+
123
+ opts.on("-h", "--help", "Show this help") do
124
+ puts opts
125
+ exit
126
+ end
127
+ end.parse!
128
+
129
+ generate_with_options(generate_options)
130
+ end
131
+
132
+ def generate_with_options(options)
133
+ generator = Rezy::Generator.new(
134
+ data_file: options[:data_file],
135
+ output_dir: options[:output_dir],
136
+ formats: options[:formats]
137
+ )
138
+ generator.generate
139
+ rescue StandardError => e
140
+ puts "Error generating resume: #{e.message}"
141
+ exit 1
142
+ end
143
+ end
@@ -0,0 +1,83 @@
1
+ require "yaml"
2
+ require "erb"
3
+ require "fileutils"
4
+
5
+ module Rezy
6
+ class Generator
7
+ def initialize(data_file: "resume.yaml", template_dir: "template", output_dir: "output", formats: [:html, :pdf])
8
+ @data_file = data_file
9
+ @template_dir = template_dir
10
+ @output_dir = output_dir
11
+ @formats = formats
12
+ end
13
+
14
+ def generate
15
+ FileUtils.mkdir_p(@output_dir)
16
+
17
+ generate_html if @formats.include?(:html)
18
+ generate_pdf if @formats.include?(:pdf) && playwright_available?
19
+ end
20
+
21
+ def generate_html
22
+ resume_data = load_data
23
+ html_output = render_template(resume_data)
24
+
25
+ File.write(File.join(@output_dir, "resume.html"), html_output)
26
+ FileUtils.cp(File.join(@template_dir, "style.css"), @output_dir)
27
+
28
+ puts "\u2705 HTML generated: #{@output_dir}/resume.html"
29
+ end
30
+
31
+ private
32
+
33
+ def load_data
34
+ if File.exist?(@data_file)
35
+ YAML.load_file(@data_file)
36
+ else
37
+ raise "Data file not found: #{@data_file}"
38
+ end
39
+ end
40
+
41
+ def render_template(resume_data)
42
+ template_path = File.join(@template_dir, "template.html.erb")
43
+ template = ERB.new(File.read(template_path))
44
+ result = template.result_with_hash(resume_data: resume_data)
45
+ result.gsub(/\n\s*\n/, "\n").strip.squeeze("\n")
46
+ end
47
+
48
+ def generate_pdf
49
+ generate_html unless File.exist?(File.join(@output_dir, "resume.html"))
50
+
51
+ begin
52
+ html_file = File.absolute_path(File.join(@output_dir, 'resume.html'))
53
+ pdf_file = File.join(@output_dir, 'resume.pdf')
54
+
55
+ # Try using Chrome directly (if available)
56
+ chrome_path = `which google-chrome`.strip
57
+ chrome_path = `which chromium-browser`.strip if chrome_path.empty?
58
+ chrome_path = `which chromium`.strip if chrome_path.empty?
59
+ chrome_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" if chrome_path.empty? && File.exist?("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")
60
+
61
+ if !chrome_path.empty? && File.exist?(chrome_path)
62
+ chrome_command = "\"#{chrome_path}\" --headless --disable-gpu --no-margins --run-all-compositor-stages-before-draw --virtual-time-budget=1000 --print-to-pdf=\"#{pdf_file}\" file://#{html_file}"
63
+ success = system(chrome_command)
64
+ if success
65
+ puts "\u2705 PDF generated: #{@output_dir}/resume.pdf"
66
+ else
67
+ puts "⚠️ Chrome PDF generation failed"
68
+ end
69
+ else
70
+ puts "⚠️ Chrome not found - PDF generation skipped"
71
+ puts " Install Google Chrome or use: brew install --cask google-chrome"
72
+ end
73
+ rescue => e
74
+ puts "⚠️ PDF generation failed: #{e.message}"
75
+ end
76
+ end
77
+
78
+ def playwright_available?
79
+ # For now, check if Chrome is available instead
80
+ system("test -f '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'")
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,3 @@
1
+ module Rezy
2
+ VERSION = "0.1.0"
3
+ end
data/lib/rezy.rb ADDED
@@ -0,0 +1,9 @@
1
+ require_relative "rezy/version"
2
+ require_relative "rezy/generator"
3
+ require_relative "rezy/cli"
4
+
5
+ module Rezy
6
+ class Error < StandardError; end
7
+ GEM_ROOT = File.expand_path("..", __dir__)
8
+ TEMPLATES_DIR = File.join(GEM_ROOT, "templates")
9
+ end
data/mise.toml ADDED
@@ -0,0 +1,3 @@
1
+ [tools]
2
+ node = "latest"
3
+ ruby = "latest"
@@ -0,0 +1,76 @@
1
+ body {
2
+ font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
3
+ padding: 24pt;
4
+ font-size: 0.8em;
5
+ }
6
+
7
+ h1 {
8
+ color: #3F8D57;
9
+ font-size: 2.5em;
10
+ margin-bottom: 8pt;
11
+ }
12
+
13
+ h2 {
14
+ font-size: 1.4em;
15
+ text-transform: uppercase;
16
+ margin-top: 1.4em;
17
+ padding-top: 8px;
18
+ }
19
+
20
+ .experience-header {
21
+ display: flex;
22
+ justify-content: space-between;
23
+ flex-wrap: wrap;
24
+ margin-bottom: 0.25em;
25
+ }
26
+
27
+ .experience-header .company, .dates {
28
+ font-weight: bold;
29
+ }
30
+
31
+ .experience-header .right {
32
+ white-space: nowrap;
33
+ color: #666;
34
+ }
35
+
36
+ .skills-table {
37
+ font-size: 1.0em;
38
+ border-collapse: collapse;
39
+ width: 100%;
40
+ }
41
+
42
+ .skills-table th {
43
+ text-align: left;
44
+ padding-right: 1rem;
45
+ vertical-align: top;
46
+ }
47
+
48
+ .skills-table td {
49
+ padding-bottom: 0.25rem;
50
+ }
51
+
52
+ /* Print-specific styles to control margins and remove headers/footers */
53
+ @page {
54
+ size: A4;
55
+ margin: 0; /* Remove all default margins */
56
+ @top-left { content: ""; }
57
+ @top-center { content: ""; }
58
+ @top-right { content: ""; }
59
+ @bottom-left { content: ""; }
60
+ @bottom-center { content: ""; }
61
+ @bottom-right { content: ""; }
62
+ }
63
+
64
+ @media print {
65
+ body {
66
+ -webkit-print-color-adjust: exact;
67
+ print-color-adjust: exact;
68
+ margin: 0 !important; /* Override any browser defaults */
69
+ padding: 0.5in !important; /* Set our own consistent margins */
70
+ }
71
+
72
+ * {
73
+ -webkit-box-sizing: border-box;
74
+ box-sizing: border-box;
75
+ }
76
+ }
@@ -0,0 +1,59 @@
1
+ <html>
2
+ <head>
3
+ <link rel="stylesheet" href="style.css">
4
+ </head>
5
+ <body>
6
+ <header>
7
+ <h1><%= resume_data["name"] %></h1>
8
+ <% unless resume_data["contact_info"].nil? %>
9
+ <div style="display: flex; gap: 0.5rem;">
10
+ <% resume_data["contact_info"].each do |value| %>
11
+ <span><%= value %></span>
12
+ <% unless resume_data["contact_info"].last == value %>
13
+ &#x2022;
14
+ <% end%>
15
+ <% end %>
16
+ </div>
17
+ <% end %>
18
+ </header>
19
+
20
+ <section>
21
+ <h2>Summary</h2>
22
+ <p><%= resume_data["summary"] %></p>
23
+ </section>
24
+
25
+ <h2>Skills</h2>
26
+ <table class="skills-table">
27
+ <% resume_data["skills"].each do |section_name, items| %>
28
+ <tr>
29
+ <th><%= section_name %></th>
30
+ <td><%= items.join(", ") %></td>
31
+ </tr>
32
+ <% end %>
33
+ </table>
34
+
35
+ <h2>Experience</h2>
36
+ <% resume_data["experience"].each do |job| %>
37
+ <div class="experience-header">
38
+ <div class="left">
39
+ <span class="company"><%= job["company"] %></span> |
40
+ <span class="role"><%= job["role"] %></span>
41
+ </div>
42
+ <div class="right">
43
+ <span class="location"><%= job["location"] %></span> |
44
+ <% start_date = job["start"]; end_date = job["end"] %>
45
+ <% if start_date == end_date %>
46
+ <span class="dates"><%= job["start"] %></span>
47
+ <% else %>
48
+ <span class="dates"><%= job["start"] %>-<%= job["end"] %></span>
49
+ <% end %>
50
+ </div>
51
+ </div>
52
+ <ul>
53
+ <% job["bullets"].each do |bullet| %>
54
+ <li><%= bullet %></li>
55
+ <% end %>
56
+ </ul>
57
+ <% end %>
58
+ </body>
59
+ </html>
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rezy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Collin Donnell
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: This gem allows you to create resumes by filling in templates with data
13
+ from a YAML file.
14
+ email:
15
+ - collin@waveform.fm
16
+ executables:
17
+ - rezy
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - README.md
23
+ - Rakefile
24
+ - bin/console
25
+ - bin/setup
26
+ - data/resume.yaml
27
+ - exe/rezy
28
+ - lib/rezy.rb
29
+ - lib/rezy/cli.rb
30
+ - lib/rezy/generator.rb
31
+ - lib/rezy/version.rb
32
+ - mise.toml
33
+ - templates/simple/style.css
34
+ - templates/simple/template.html.erb
35
+ homepage: https://github.com/collindonnell/rezy
36
+ licenses:
37
+ - MIT
38
+ metadata:
39
+ allowed_push_host: https://rubygems.org
40
+ homepage_uri: https://github.com/collindonnell/rezy
41
+ source_code_uri: https://github.com/collindonnell/rezy
42
+ changelog_uri: https://github.com/collindonnell/rezy/blob/main/CHANGELOG.md
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 3.1.0
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubygems_version: 3.6.7
58
+ specification_version: 4
59
+ summary: Generate resumes from templates using YAML data.
60
+ test_files: []