kamisaku 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3ec65c8d7b635ea047a547d4fdf3a90ef1e4ddaa2b271b2178e8b2bdf9bca0f
4
- data.tar.gz: c6fada80efb1876043306bc9c38842e627f5ee32a7ca18f1a3c97a1f1490a954
3
+ metadata.gz: '08311183150f22bf9897f33d7ecb93616573e99b90a49455efd27747316b3a5b'
4
+ data.tar.gz: b198531b11fdf5b409c1b9c39be5ca04c64a1b9174a867f38e93f7d928f534e9
5
5
  SHA512:
6
- metadata.gz: ea270d4a7e392a32ffb83fbe87a9f31ba483b1827f99f52547278875f92f87989b629dd901e4047d31f288cafeac8f722a6534ac1d88858cd6e9b3f6e06c055a
7
- data.tar.gz: '0877a0d602fe76e39cd955bd71557355d273b4d6f45b6bafd519757b1478d635da1c3c78e67488e1aa3cd23898d7eb3297f55870c328c811f506675847ca2c63'
6
+ metadata.gz: 621551dfa5fa5bf482ccccf5e8fb72a7a12b2736ed397f8a813f85274f8f33db09e1ffed4d1846dbd3dbb75f2ffc7abb53dd719229c1d00946945050949517d8
7
+ data.tar.gz: 6434717f692966c901becca7a7573ee69ed69b63516bdf356ea826c7d27302255dad520e72ac86519b87213ade8319d3a064de0f9991c5cf69f3242a3918aa20
data/CHANGELOG.md CHANGED
@@ -13,6 +13,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
13
13
 
14
14
  ### Removed
15
15
 
16
+ ## [0.3.1] - 2025-06-01
17
+
18
+ ### Changed
19
+ - Refactor code and update `Kamisaku::PDF` interface
20
+
21
+ ## [0.3.0] - 2025-05-28
22
+
23
+ ### Added
24
+ - Add `Kamisaku::PDF` interface that can be used externally to generate PDF
25
+
16
26
  ## [0.2.1] - 2025-05-28
17
27
 
18
28
  ### Changed
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kamisaku (0.3.0)
4
+ kamisaku (0.3.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -12,21 +12,21 @@ module Kamisaku
12
12
  options[:html_output] = html_output
13
13
  end
14
14
 
15
- parser.on("-c", "--cv-info INFO", "Require the INFO") do |cv_info|
16
- options[:cv_info] = cv_info
15
+ parser.on("-c", "--yaml-file INFO", "Require the INFO") do |yaml_file|
16
+ options[:yaml_file] = yaml_file
17
17
  end
18
18
 
19
- parser.on("-o", "--output-path OUTPUT", "Require the OUTPUT") do |output_path|
20
- options[:output_path] = output_path
19
+ parser.on("-o", "--pdf-file OUTPUT", "Require the OUTPUT") do |pdf_file|
20
+ options[:pdf_file] = pdf_file
21
21
  end
22
22
 
23
- parser.on("-t", "--template-path TEMPLATE", "Provide the TEMPLATE name") do |template|
23
+ parser.on("-t", "--template TEMPLATE", "Provide the TEMPLATE name") do |template|
24
24
  options[:template] = template
25
25
  end
26
26
  end.parse!
27
27
 
28
- raise OptionParser::MissingArgument.new("Provide the location of CV yaml file") if options[:cv_info].nil?
29
- raise OptionParser::MissingArgument.new("Provide a valid path to generate the CV pdf file") if options[:output_path].nil?
28
+ raise OptionParser::MissingArgument.new("-c") if options[:yaml_file].nil?
29
+ raise OptionParser::MissingArgument.new("-o") if options[:pdf_file].nil?
30
30
 
31
31
  options
32
32
  end
@@ -0,0 +1,18 @@
1
+ module Kamisaku
2
+ class ChromeRunner
3
+ def initialize(sandbox: false, headless: true, gpu: false)
4
+ @base_args = ["--run-all-compositor-stages-before-draw"]
5
+ @base_args << "--no-sandbox" unless sandbox
6
+ @base_args << "--headless" if headless
7
+ @base_args << "--disable-gpu" unless gpu
8
+ end
9
+
10
+ def html_to_pdf(html_path, pdf_path, pdf_header_footer: false)
11
+ args = [*@base_args]
12
+ args << "--no-pdf-header-footer" unless pdf_header_footer
13
+ args << "--print-to-pdf=#{pdf_path}"
14
+ args << html_path
15
+ system("google-chrome", *args)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module Kamisaku
2
+ class CLIRunner
3
+ def self.run
4
+ options = Kamisaku::ArgParser.parse!
5
+ yaml_file = options[:yaml_file]
6
+ raise Kamisaku::Error.new "YAML file does not exist" unless File.exist?(yaml_file)
7
+
8
+ yaml_string = File.read(yaml_file)
9
+ pdf = PDF.new(content_hash: Helpers.yaml_str_to_content_hash(yaml_string), template: options[:template])
10
+ pdf.write_to(options[:pdf_file])
11
+ pdf.generate_html(options[:html_output]) if options[:html_output]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,173 @@
1
+ module Kamisaku
2
+ class ContentValidator
3
+ attr_reader :content_hash
4
+ alias_method :data, :content_hash
5
+
6
+ def initialize(content_hash:)
7
+ @content_hash = content_hash
8
+ end
9
+
10
+ def validate!
11
+ validate_version
12
+ validate_profile
13
+ validate_contact
14
+ validate_skills
15
+ validate_experiences
16
+ end
17
+
18
+ private
19
+
20
+ def validate_version
21
+ raise Error, "Invalid version" unless data[:version] && data[:version] == 1
22
+ end
23
+
24
+ def validate_profile
25
+ raise Error, "Missing profile" unless data[:profile]
26
+ raise Error, "Profile must be a hash" unless data[:profile].is_a?(Hash)
27
+
28
+ allowed_fields = %i[name title about]
29
+ profile_fields = data[:profile].keys
30
+
31
+ unless profile_fields.all? { |field| allowed_fields.include?(field) }
32
+ raise Error, "Profile contains invalid fields"
33
+ end
34
+
35
+ unless profile_fields.size == allowed_fields.size
36
+ raise Error, "Profile must contain exactly the fields: #{allowed_fields.join(", ")}"
37
+ end
38
+
39
+ data[:profile].each do |field, value|
40
+ unless value.is_a?(String)
41
+ raise Error, "Profile field '#{field}' must be a string"
42
+ end
43
+ end
44
+ end
45
+
46
+ def validate_contact
47
+ raise Error, "Missing contact" unless data[:contact]
48
+ raise Error, "Contact must be a hash" unless data[:contact].is_a?(Hash)
49
+
50
+ allowed_fields = %i[github mobile linkedin website email location]
51
+ contact_fields = data[:contact].keys
52
+
53
+ unless contact_fields.all? { |field| allowed_fields.include?(field) }
54
+ raise Error, "Contact contains invalid fields"
55
+ end
56
+
57
+ allowed_fields.each do |field|
58
+ raise Error, "Contact missing required field '#{field}'" unless contact_fields.include?(field)
59
+ end
60
+
61
+ data[:contact].each do |field, value|
62
+ if field == :location
63
+ validate_location(value, "Contact section")
64
+ else
65
+ raise Error, "Contact field '#{field}' must be a string" unless value.is_a?(String)
66
+ end
67
+ end
68
+ end
69
+
70
+ def validate_location(location, section)
71
+ raise Error, "#{section}: Location must be a hash" unless location.is_a?(Hash)
72
+
73
+ allowed_fields = %i[country city]
74
+ location_fields = location.keys
75
+
76
+ unless location_fields.all? { |field| allowed_fields.include?(field) }
77
+ raise Error, "#{section}: Location contains invalid fields"
78
+ end
79
+
80
+ allowed_fields.each do |field|
81
+ raise Error, "#{section}: Location missing required field '#{field}'" unless location_fields.include?(field)
82
+ raise Error, "#{section}: Location field '#{field}' must be a string" unless location[field].is_a?(String)
83
+ end
84
+ end
85
+
86
+ def validate_skills
87
+ return unless data[:skills]
88
+
89
+ raise Error, "Skills must be an array" unless data[:skills].is_a?(Array)
90
+
91
+ data[:skills].each do |skill|
92
+ raise Error, "Each skill must be a hash" unless skill.is_a?(Hash)
93
+
94
+ allowed_fields = %i[area items]
95
+ skill_fields = skill.keys
96
+
97
+ unless skill_fields.all? { |field| allowed_fields.include?(field) }
98
+ raise Error, "Skills section: Skill contains invalid fields"
99
+ end
100
+
101
+ allowed_fields.each do |field|
102
+ raise Error, "Skills section: Skill missing required field '#{field}'" unless skill_fields.include?(field)
103
+ end
104
+
105
+ raise Error, "Skills section: Skill field 'area' must be a string" unless skill[:area].is_a?(String)
106
+ raise Error, "Skills section: Skill field 'items' must be an array" unless skill[:items].is_a?(Array)
107
+
108
+ skill[:items].each do |item|
109
+ raise Error, "Skills section: Each skill item must be a string" unless item.is_a?(String)
110
+ end
111
+ end
112
+ end
113
+
114
+ def validate_experiences
115
+ return unless data[:experiences]
116
+
117
+ raise Error, "Experiences must be an array" unless data[:experiences].is_a?(Array)
118
+
119
+ data[:experiences].each do |experience|
120
+ raise Error, "Each experience must be a hash" unless experience.is_a?(Hash)
121
+
122
+ allowed_fields = %i[title organisation location from to skills achievements]
123
+ required_fields = %i[title organisation location from skills achievements]
124
+ experience_fields = experience.keys
125
+
126
+ unless experience_fields.all? { |field| allowed_fields.include?(field) }
127
+ raise Error, "Experiences section: Experience contains invalid fields"
128
+ end
129
+
130
+ required_fields.each do |field|
131
+ raise Error, "Experiences section: Experience missing required field '#{field}'" unless experience_fields.include?(field)
132
+ end
133
+
134
+ raise Error, "Experiences section: Experience field 'title' must be a string" unless experience[:title].is_a?(String)
135
+ raise Error, "Experiences section: Experience field 'organisation' must be a string" unless experience[:organisation].is_a?(String)
136
+
137
+ validate_location(experience[:location], "Experiences section")
138
+
139
+ raise Error, "Experiences section: Experience field 'from' must be a hash" unless experience[:from].is_a?(Hash)
140
+ validate_date_format(experience[:from], "Experiences section")
141
+
142
+ if experience.key?(:to)
143
+ raise Error, "Experiences section: Experience field 'to' must be a hash" unless experience[:to].is_a?(Hash)
144
+ validate_date_format(experience[:to], "Experiences section")
145
+ end
146
+
147
+ raise Error, "Experiences section: Experience field 'skills' must be an array" unless experience[:skills].is_a?(Array)
148
+ experience[:skills].each do |skill|
149
+ raise Error, "Experiences section: Each experience skill item must be a string" unless skill.is_a?(String)
150
+ end
151
+
152
+ raise Error, "Experiences section: Experience field 'achievements' must be an array" unless experience[:achievements].is_a?(Array)
153
+ experience[:achievements].each do |achievement|
154
+ raise Error, "Experiences section: Each experience achievement item must be a string" unless achievement.is_a?(String)
155
+ end
156
+ end
157
+ end
158
+
159
+ def validate_date_format(date, section)
160
+ allowed_fields = %i[month year]
161
+ date_fields = date.keys
162
+
163
+ unless date_fields.all? { |field| allowed_fields.include?(field) }
164
+ raise Error, "#{section}: Date contains invalid fields"
165
+ end
166
+
167
+ allowed_fields.each do |field|
168
+ raise Error, "#{section}: Date missing required field '#{field}'" unless date_fields.include?(field)
169
+ raise Error, "#{section}: Date field '#{field}' must be an integer" unless date[field].is_a?(Integer)
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,3 @@
1
+ module Kamisaku
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,15 @@
1
+ require "psych"
2
+
3
+ module Kamisaku
4
+ module Helpers
5
+ def self.yaml_str_to_content_hash(yaml_str)
6
+ Psych.safe_load(yaml_str, symbolize_names: true, aliases: false, freeze: true)
7
+ rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::AliasesNotEnabled => error
8
+ raise Kamisaku::Error.new error.message
9
+ end
10
+
11
+ def self.remove_metadata_from_pdf_file(file_path)
12
+ system("exiftool", "-all=", file_path, "-overwrite_original")
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ require "erb"
2
+
3
+ module Kamisaku
4
+ class HtmlBuilder
5
+ attr_reader :content_hash, :template
6
+ alias_method :data, :content_hash
7
+
8
+ include TemplateHelpers
9
+
10
+ def initialize(content_hash, template)
11
+ @content_hash = content_hash
12
+ @template = template
13
+ end
14
+
15
+ def html
16
+ rhtml = ::ERB.new(template_html)
17
+ rhtml.result(binding)
18
+ end
19
+
20
+ private
21
+
22
+ def template_html
23
+ path = File.join(File.dirname(__FILE__), "/../templates/#{template}/template.html.erb")
24
+ File.read(path)
25
+ end
26
+ end
27
+ end
data/lib/kamisaku/pdf.rb CHANGED
@@ -1,47 +1,34 @@
1
1
  module Kamisaku
2
2
  class PDF
3
- attr_reader :yaml_content, :pdf_location, :html_location, :cv_data, :template
3
+ attr_reader :content_hash, :template
4
4
 
5
- def initialize(yaml_content:, pdf_location:, html_location: nil, template: nil)
6
- @yaml_content = yaml_content
7
- @cv_data = CvData.new(YAML.load(yaml_content))
8
- @pdf_location = pdf_location
9
- @html_location = html_location
5
+ def initialize(content_hash:, template: nil)
6
+ @content_hash = content_hash
10
7
  @template = template || "sleek"
8
+ ContentValidator.new(content_hash:).validate!
11
9
  end
12
10
 
13
- def create
14
- generated_html_file do |file_path|
15
- FileUtils.cp(file_path, html_location) if html_location
11
+ def write_to(pdf_location)
12
+ html_file do |file_path|
16
13
  html_file_to_pdf_file(file_path, pdf_location)
17
- soft_remove_metadata_from_pdf_file(pdf_location)
14
+ Helpers.remove_metadata_from_pdf_file(pdf_location)
18
15
  end
19
16
  end
20
17
 
18
+ def generate_html(html_location)
19
+ html_file { |file_path| FileUtils.cp(file_path, html_location) }
20
+ end
21
+
21
22
  private
22
23
 
23
24
  def html_file_to_pdf_file(html_file_path, pdf_file_path)
24
- # Convert the HTML file to a PDF using Google Chrome in headless mode
25
- pdf_conversion_command = <<~CMD
26
- google-chrome --headless --disable-gpu --run-all-compositor-stages-before-draw \
27
- --print-to-pdf=#{pdf_file_path} --no-pdf-header-footer \
28
- #{html_file_path}
29
- CMD
30
-
31
- system(pdf_conversion_command)
32
- raise "PDF generation failed" unless File.exist?(pdf_file_path)
25
+ runner = ChromeRunner.new
26
+ runner.html_to_pdf(html_file_path, pdf_file_path)
33
27
  end
34
28
 
35
- def soft_remove_metadata_from_pdf_file(file_path)
36
- command = <<~CMD
37
- exiftool -all= #{file_path} -overwrite_original
38
- CMD
39
- system(command)
40
- end
41
-
42
- def generated_html_file
29
+ def html_file
43
30
  temp_html_file = Tempfile.new(%w[kamisaku .html])
44
- temp_html_file.write(cv_html)
31
+ temp_html_file.write(html)
45
32
  temp_html_file.close
46
33
  begin
47
34
  yield temp_html_file.path
@@ -50,9 +37,11 @@ module Kamisaku
50
37
  end
51
38
  end
52
39
 
53
- def cv_html
54
- rhtml = ERB.new(template_html)
55
- rhtml.result(@cv_data.get_bindings)
40
+ def html
41
+ return @html if defined? @html
42
+
43
+ builder = HtmlBuilder.new(content_hash, template)
44
+ @html = builder.html
56
45
  end
57
46
 
58
47
  def template_html
@@ -0,0 +1,11 @@
1
+ require "date"
2
+
3
+ module Kamisaku
4
+ module TemplateHelpers
5
+ def month_name(month_int)
6
+ return "" if month_int.nil?
7
+
8
+ ::Date::MONTHNAMES[month_int]
9
+ end
10
+ end
11
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kamisaku
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/kamisaku.rb CHANGED
@@ -1,29 +1,10 @@
1
- # frozen_string_literal: true
2
-
3
1
  require_relative "kamisaku/version"
4
- require_relative "kamisaku/cv_data_sections/parse_helpers/month_parser"
5
- require_relative "kamisaku/cv_data_sections/base"
6
- require_relative "kamisaku/cv_data_sections/skill"
7
- require_relative "kamisaku/cv_data_sections/interest"
8
- require_relative "kamisaku/cv_data_sections/experience"
9
- require_relative "kamisaku/cv_data_sections/education"
10
- require_relative "kamisaku/cv_data"
11
- require_relative "kamisaku/meta_file_parser"
12
- require_relative "kamisaku/cv_generator"
2
+ require_relative "kamisaku/errors"
3
+ require_relative "kamisaku/helpers"
13
4
  require_relative "kamisaku/arg_parser"
14
5
  require_relative "kamisaku/pdf"
15
-
16
- module Kamisaku
17
- class Error < StandardError; end
18
-
19
- class Builder
20
- def run
21
- options = Kamisaku::ArgParser.parse!
22
- raise Error.new "CV info file does not exist" unless File.exist?(options[:cv_info])
23
-
24
- parser = MetaFileParser.new(options[:cv_info])
25
- cv_data = parser.parse!
26
- CvGenerator.new(cv_data, pdf_location: options[:output_path], html_location: options[:html_output], template: options[:template]).generate
27
- end
28
- end
29
- end
6
+ require_relative "kamisaku/chrome_runner"
7
+ require_relative "kamisaku/cli_runner"
8
+ require_relative "kamisaku/template_helpers"
9
+ require_relative "kamisaku/html_builder"
10
+ require_relative "kamisaku/content_validator"
@@ -286,58 +286,66 @@
286
286
  </style>
287
287
  </head>
288
288
  <body>
289
- <div class="bottom-ruler contact-section">
290
- <div class="name bottom-ruler"><%= dig :profile, :name %></div>
291
- <div class="contact">
292
- <% if has? :contact, :email %>
293
- <a href="mailto:<%= dig :contact, :email %>"><%= dig :contact, :email %></a>
294
- <% end %>
295
- <% if has? :contact, :mobile %>
296
- <a href="tel:<%= dig :contact, :mobile %>"><%= dig :contact, :mobile %></a>
297
- <% end %>
298
- <% if has?(:contact, :location, :city) || has?(:contact, :location, :country) %>
299
- <%= dig :contact, :location, :city %>, <%= dig :contact, :location, :country %>
300
- <% end %>
301
- <% if has? :contact, :website %>
302
- <a href="<%= dig :contact, :website %>"><%= dig :contact, :website %></a>
303
- <% end %>
304
- <% if has? :contact, :github %>
305
- <a href="https://github.com/<%= dig :contact, :github %>">github.com/<%= dig :contact, :github %></a>
306
- <% end %>
307
- <% if has? :contact, :linkedin %>
308
- <a href="https://linkedin.com/in/<%= dig :contact, :linkedin %>">linkedin.com/in/<%= dig :contact, :linkedin %></a>
289
+ <% if (profile = data[:profile]) %>
290
+ <div class="bottom-ruler contact-section">
291
+ <div class="name bottom-ruler"><%= profile.dig(:name) %></div>
292
+ <% if (contact = data[:contact]) %>
293
+ <div class="contact">
294
+ <% if (email = contact[:email]) %>
295
+ <a href="mailto:<%= email %>"><%= email %></a>
296
+ <% end %>
297
+
298
+ <% if (mobile = contact[:mobile]) %>
299
+ <a href="tel:<%= mobile %>"><%= mobile %></a>
300
+ <% end %>
301
+
302
+ <% if (city = contact.dig(:location, :city)) || (country = contact.dig(:location, :country)) %>
303
+ <%= "#{city}, #{country}".strip %>
304
+ <% end %>
305
+
306
+ <% if (website = contact[:website]) %>
307
+ <a href="<%= website %>"><%= website %></a>
308
+ <% end %>
309
+
310
+ <% if (github = contact[:github]) %>
311
+ <a href="https://github.com/<%= github %>">github.com/<%= github %></a>
312
+ <% end %>
313
+
314
+ <% if (linkedin = contact[:linkedin]) %>
315
+ <a href="https://linkedin.com/in/<%= linkedin %>">linkedin.com/in/<%= linkedin %></a>
316
+ <% end %>
317
+ </div>
309
318
  <% end %>
310
319
  </div>
311
- </div>
312
-
313
- <div class="summary">
314
- <div class="upcase bottom-ruler section-title">Summary</div>
315
- <div class="about"><%= dig :profile, :about %></div>
316
- </div>
320
+ <div class="summary">
321
+ <div class="upcase bottom-ruler section-title">Summary</div>
322
+ <div class="about"><%= profile[:about] %></div>
323
+ </div>
324
+ <% end %>
317
325
 
318
- <% if has?(:experiences) %>
326
+ <% if (experiences = data[:experiences]) %>
319
327
  <div class="work-experience">
320
328
  <div class="upcase bottom-ruler section-title">Work Experience</div>
321
- <% dig :experiences do |experience| %>
329
+ <% experiences.each do |experience| %>
322
330
  <div class="experience">
323
331
  <div class="flex flex-grow sb">
324
- <div class="organisation-name"><%= dig experience, :organisation %></div>
332
+ <div class="organisation-name"><%= experience[:organisation] %></div>
325
333
  <div class="time-period">
326
- <%= "#{dig experience, :from, :month} #{dig experience, :from, :year}" %> -
327
- <%= has?(experience, :to) ? "#{dig experience, :to, :month} #{dig experience, :to, :year}" : "Present" %>
334
+ <%= "#{month_name experience.dig(:from, :month)} #{experience.dig(:from, :year)}" %> -
335
+ <%= experience[:to] ? "#{month_name experience.dig(:to, :month)} #{experience.dig(:to, :year)}" : "Present" %>
328
336
  </div>
329
337
  </div>
330
338
 
331
339
  <div class="flex flex-grow sb">
332
- <div class="job-title"><%= dig experience, :title %></div>
340
+ <div class="job-title"><%= experience[:title] %></div>
333
341
  <div class="job-location">
334
- <%= "#{dig(experience, :location, :city)}, #{dig(experience, :location, :country)}" %>
342
+ <%= "#{experience.dig(:location, :city)}, #{experience.dig(:location, :country)}" %>
335
343
  </div>
336
344
  </div>
337
345
 
338
- <% if has? experience, :achievements %>
346
+ <% if (achievements = experience[:achievements]) %>
339
347
  <ul class="achievements">
340
- <% dig(experience, :achievements).each do |achievement| %>
348
+ <% achievements.each do |achievement| %>
341
349
  <li class="achievement-item"><%= achievement %></li>
342
350
  <% end %>
343
351
  </ul>
@@ -347,30 +355,31 @@
347
355
  </div>
348
356
  <% end %>
349
357
 
350
- <% if has?(:education) %>
358
+ <% if (education_list = data[:education]) %>
351
359
  <div class="education">
352
360
  <div class="upcase bottom-ruler section-title">Education</div>
353
- <% dig :education do |education| %>
361
+ <% education_list.each do |education| %>
354
362
  <div class="education-item">
355
363
  <div class="flex flex-grow sb">
356
364
  <div class="institute-name">
357
- <%= dig education, :institute %>
365
+ <%= education[:institute] %>
358
366
  </div>
359
367
  <div class="time-period">
360
- <%= has?(education, :from) ? "#{dig education, :from, :month} #{dig education, :from, :year} - " : "" %>
361
- <%= has?(education, :to) ? "#{dig education, :to, :month} #{dig education, :to, :year}" : "Present" %>
368
+ <%= education.dig(:from) ? "#{month_name education.dig(:from, :month)} #{education.dig(:from, :year)} - " : "" %>
369
+ <%= education.dig(:to) ? "#{month_name education.dig(:to, :month)} #{education.dig(:to, :year)}" : "Present" %>
362
370
  </div>
363
371
  </div>
364
372
 
365
373
  <div class="flex flex-grow sb">
366
- <div class="qualification"><%= dig education, :qualification %></div>
374
+ <div class="qualification"><%= education[:qualification] %></div>
367
375
  <div class="education-location">
368
- <%= dig education, :location, :city %>, <%= dig education, :location, :country %>
376
+ <%= "#{education.dig(:location, :city)}, #{education.dig(:location, :country)}".strip %>
369
377
  </div>
370
378
  </div>
371
- <% if has?(education, :achievements) %>
379
+
380
+ <% if (achievements = education[:achievements]) %>
372
381
  <ul class="achievements">
373
- <% dig(education, :achievements).each do |achievement| %>
382
+ <% achievements.each do |achievement| %>
374
383
  <li class="achievement-item"><%= achievement %></li>
375
384
  <% end %>
376
385
  </ul>
@@ -380,37 +389,32 @@
380
389
  </div>
381
390
  <% end %>
382
391
 
383
- <% generic_sections = [has?(:skills) ? 'Skills' : nil, has?(:interests) ? 'Interests' : nil].compact %>
384
-
385
- <% unless generic_sections.empty? %>
386
392
 
393
+ <% if (generic_sections = data.slice(:skills, :interests).compact) %>
394
+ <% show_subtitle = generic_sections.keys.size > 1 %>
387
395
  <div class="generic-section">
388
- <div class="upcase bottom-ruler section-title"><%= generic_sections.join(', ') %></div>
396
+ <div class="upcase bottom-ruler section-title"><%= generic_sections.keys.map(&:capitalize).join(', ') %></div>
389
397
  <ul>
390
- <% if has?(:skills) %>
398
+ <% if (skills = generic_sections[:skills]) %>
391
399
  <li class="item">
392
- <% if generic_sections.length > 1 %>
400
+ <% if show_subtitle %>
393
401
  <b>Skills: </b>
394
402
  <% end %>
395
- <% skill_list = [] %>
396
- <% dig :skills do |skill| %>
397
- <% dig(skill, :items).each { |item| skill_list.push(item) } %>
398
- <% end %>
399
-
400
- <%= skill_list.join('; ') %>
403
+ <%= skills.flat_map { |skill| skill[:items] }.join('; ') %>
401
404
  </li>
402
405
  <% end %>
403
406
 
404
- <% if has?(:interests) %>
407
+ <% if (interests = generic_sections[:interests]) %>
405
408
  <li class="item">
406
- <% if generic_sections.length > 1 %>
409
+ <% if show_subtitle %>
407
410
  <b>Interests: </b>
408
411
  <% end %>
409
- <%= dig(:interests).join('; ') %>
412
+ <%= interests.join('; ') %>
410
413
  </li>
411
414
  <% end %>
412
415
  </ul>
413
416
  </div>
414
417
  <% end %>
418
+
415
419
  </body>
416
420
  </html>
@@ -133,39 +133,39 @@
133
133
  <div class="profile">
134
134
  <div class="flex">
135
135
  <div>
136
- <div class="name"> <%= dig :profile, :name %></div>
137
- <div class="title"> <%= dig :profile, :title %></div>
136
+ <div class="name"> <%= data.dig(:profile, :name) %></div>
137
+ <div class="title"> <%= data.dig(:profile, :title) %></div>
138
138
  </div>
139
139
  <div class="flex contact">
140
140
  <div style="margin-right: 10px;">
141
141
  <div class="contact-item">
142
142
  <span class="icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span>
143
- <span><a href="https://github.com/<%= dig :contact, :github %>">github.com/<%= dig :contact, :github %></a></span>
143
+ <span><a href="https://github.com/<%= data.dig(:contact, :github) %>">github.com/<%= data.dig(:contact,:github) %></a></span>
144
144
  </div>
145
145
 
146
146
  <div class="contact-item">
147
147
  <span class="icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/></svg></span>
148
- <span><a href="https://linkedin.com/in/<%= dig :contact, :linkedin %>">linkedin.com/in/<%= dig :contact, :linkedin %></a></span>
148
+ <span><a href="https://linkedin.com/in/<%= data.dig :contact, :linkedin %>">linkedin.com/in/<%= data.dig :contact, :linkedin %></a></span>
149
149
  </div>
150
150
 
151
151
  <div class="contact-item">
152
152
  <span class="icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z"/></svg></span>
153
- <span><a href="https://<%= dig :contact, :website %>"><%= dig :contact, :website %></a></span>
153
+ <span><a href="https://<%= data.dig :contact, :website %>"><%= data.dig :contact, :website %></a></span>
154
154
  </div>
155
155
  </div>
156
156
 
157
157
  <div>
158
158
  <div class="contact-item">
159
159
  <span class="icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M0 64C0 28.7 28.7 0 64 0H256c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64zm64 96v64c0 17.7 14.3 32 32 32H224c17.7 0 32-14.3 32-32V160c0-17.7-14.3-32-32-32H96c-17.7 0-32 14.3-32 32zM80 352a24 24 0 1 0 0-48 24 24 0 1 0 0 48zm24 56a24 24 0 1 0 -48 0 24 24 0 1 0 48 0zm56-56a24 24 0 1 0 0-48 24 24 0 1 0 0 48zm24 56a24 24 0 1 0 -48 0 24 24 0 1 0 48 0zm56-56a24 24 0 1 0 0-48 24 24 0 1 0 0 48zm24 56a24 24 0 1 0 -48 0 24 24 0 1 0 48 0zM128 48c-8.8 0-16 7.2-16 16s7.2 16 16 16h64c8.8 0 16-7.2 16-16s-7.2-16-16-16H128z"/></svg></span>
160
- <span><a href="tel:<%= dig :contact, :mobile %>"><%= dig :contact, :mobile %></a></span>
160
+ <span><a href="tel:<%= data.dig :contact, :mobile %>"><%= data.dig :contact, :mobile %></a></span>
161
161
  </div>
162
162
  <div class="contact-item">
163
163
  <span class="icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"/></svg></span>
164
- <span><%= dig :contact, :email %></span>
164
+ <span><%= data.dig :contact, :email %></span>
165
165
  </div>
166
166
  <div class="contact-item">
167
167
  <span class="icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/></svg></span>
168
- <span><%= dig :contact, :location, :city %>, <%= dig :contact, :location, :country %></span>
168
+ <span><%= data.dig :contact, :location, :city %>, <%= data.dig :contact, :location, :country %></span>
169
169
  </div>
170
170
  </div>
171
171
 
@@ -173,16 +173,16 @@
173
173
  </div>
174
174
  <div class="about">
175
175
  <h2 class="upcase heading">Professional Summary</h2>
176
- <p><%= dig :profile, :about %></p>
176
+ <p><%= data.dig :profile, :about %></p>
177
177
  </div>
178
178
  </div>
179
179
 
180
- <% if has? :skills %>
180
+ <% if data[:skills] %>
181
181
  <div class="skills">
182
- <% dig :skills do |skill| %>
182
+ <% data[:skills].each do |skill| %>
183
183
  <div>
184
- <b><%= dig skill, :area %> </b>
185
- <% dig(skill, :items).each do |item| %>
184
+ <b><%= skill.dig :area %> </b>
185
+ <% skill.dig(:items).each do |item| %>
186
186
  • <%= item %>
187
187
  <% end %>
188
188
  </div>
@@ -190,36 +190,36 @@
190
190
  </div>
191
191
  <% end %>
192
192
 
193
- <% if has? :experiences %>
193
+ <% if data[:experiences] %>
194
194
  <div class="experiences">
195
195
  <h2 class="upcase heading">Work Experience</h2>
196
- <% dig :experiences do |experience| %>
196
+ <% data[:experiences].each do |experience| %>
197
197
  <div class="experience">
198
- <h3 class="job-title"><%= dig(experience, :title) %></h3>
198
+ <h3 class="job-title"><%= experience.dig(:title) %></h3>
199
199
  <div class="flex">
200
200
  <div class="flex-grow">
201
201
  <span style="font-weight: bold">
202
- <%= dig(experience, :organisation) %>
202
+ <%= experience.dig(:organisation) %>
203
203
  </span> /
204
- <span><%= "#{dig(experience, :location, :city)}, #{dig(experience, :location, :country)}" %></span>
204
+ <span><%= "#{experience.dig(:location, :city)}, #{experience.dig(:location, :country)}" %></span>
205
205
  </div>
206
206
  <div class="time-period">
207
207
  <span class="time">
208
- <%= dig(experience, :from, :month) %> <%= dig(experience, :from, :year) %> -
209
- <%= has?(experience, :to) ? "#{dig(experience, :to, :month)} #{dig(experience, :to, :year)}" : 'Present' %>
208
+ <%= month_name experience.dig(:from, :month) %> <%= experience.dig(:from, :year) %> -
209
+ <%= experience[:to] ? "#{month_name experience.dig(:to, :month)} #{experience.dig(:to, :year)}" : 'Present' %>
210
210
  </span>
211
211
  </div>
212
212
  </div>
213
- <% if has? experience, :achievements %>
213
+ <% if experience[:achievements] %>
214
214
  <div class="achievements">
215
- <% dig(experience, :achievements).each do |achievement| %>
215
+ <% experience.dig(:achievements).each do |achievement| %>
216
216
  <div class="achievement-item">• <%= achievement %></div>
217
217
  <% end %>
218
218
  </div>
219
219
  <% end %>
220
220
  <div class="skills">
221
221
  <span class="label">Skills</span>
222
- <% dig(experience, :skills).each do |item| %>
222
+ <% experience.dig(:skills).each do |item| %>
223
223
  <span class="skill-item">• <%= item %></span>
224
224
  <% end %>
225
225
  </div>
@@ -228,28 +228,28 @@
228
228
  </div>
229
229
  <% end %>
230
230
 
231
- <% if has? :education %>
231
+ <% if data[:education] %>
232
232
  <div class="education">
233
233
  <h2 class="upcase heading">Education</h2>
234
- <% dig :education do |education| %>
234
+ <% data[:education].each do |education| %>
235
235
  <div class="education-item">
236
- <h3><%= dig(education, :qualification) %></h3>
236
+ <h3><%= education.dig(:qualification) %></h3>
237
237
  <div class="flex">
238
238
  <div class="flex-grow">
239
239
  <span style="font-weight: bold">
240
- <%= dig(education, :institute) %>
240
+ <%= education.dig(:institute) %>
241
241
  </span> /
242
- <span><%= "#{dig(education, :location, :city)}, #{dig(education, :location, :country)}" %></span>
242
+ <span><%= "#{education.dig(:location, :city)}, #{education.dig(:location, :country)}" %></span>
243
243
  </div>
244
244
  <div class="time-period">
245
245
  <span class="time">
246
- <%= has?(education, :from) ? "#{dig(education, :from, :month)} #{dig(education, :from, :year)} - " : '' %>
247
- <%= has?(education, :to) ? "#{dig(education, :to, :month)} #{dig(education, :to, :year)}" : 'Present' %>
246
+ <%= education[:from] ? "#{month_name education.dig(:from, :month)} #{education.dig(:from, :year)} - " : '' %>
247
+ <%= education[:to] ? "#{month_name education.dig(:to, :month)} #{education.dig(:to, :year)}" : 'Present' %>
248
248
  </span>
249
249
  </div>
250
250
  </div>
251
251
  <div class="achievements-section">
252
- <% dig(education, :achievements).each do |achievement| %>
252
+ <% education.dig(:achievements).each do |achievement| %>
253
253
  <div>
254
254
  • <%= achievement %>
255
255
  </div>
@@ -260,4 +260,4 @@
260
260
  </div>
261
261
  <% end %>
262
262
  </body>
263
- </html>
263
+ </html>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kamisaku
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sinaru Gunawardena
@@ -33,16 +33,14 @@ files:
33
33
  - kamisaku.png
34
34
  - lib/kamisaku.rb
35
35
  - lib/kamisaku/arg_parser.rb
36
- - lib/kamisaku/cv_data.rb
37
- - lib/kamisaku/cv_data_sections/base.rb
38
- - lib/kamisaku/cv_data_sections/education.rb
39
- - lib/kamisaku/cv_data_sections/experience.rb
40
- - lib/kamisaku/cv_data_sections/interest.rb
41
- - lib/kamisaku/cv_data_sections/parse_helpers/month_parser.rb
42
- - lib/kamisaku/cv_data_sections/skill.rb
43
- - lib/kamisaku/cv_generator.rb
44
- - lib/kamisaku/meta_file_parser.rb
36
+ - lib/kamisaku/chrome_runner.rb
37
+ - lib/kamisaku/cli_runner.rb
38
+ - lib/kamisaku/content_validator.rb
39
+ - lib/kamisaku/errors.rb
40
+ - lib/kamisaku/helpers.rb
41
+ - lib/kamisaku/html_builder.rb
45
42
  - lib/kamisaku/pdf.rb
43
+ - lib/kamisaku/template_helpers.rb
46
44
  - lib/kamisaku/version.rb
47
45
  - lib/templates/paper/template.html.erb
48
46
  - lib/templates/sleek/template.html.erb
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kamisaku
4
- class CvData
5
- DATA_SECTION_CLASSES = [
6
- CvDataSection::Skill,
7
- CvDataSection::Experience,
8
- CvDataSection::Education,
9
- CvDataSection::Interest
10
- ]
11
- def initialize(hash)
12
- @hash = hash
13
- end
14
-
15
- def get_bindings
16
- binding
17
- end
18
-
19
- def has?(*path)
20
- if DATA_SECTION_CLASSES.any? { |klass| path.first.instance_of? klass }
21
- return path.first.has?(*path[1..])
22
- end
23
-
24
- path_s = path.map(&:to_s)
25
- !@hash.dig(*path_s).nil?
26
- end
27
-
28
- def dig(*path)
29
- if DATA_SECTION_CLASSES.any? { |klass| path.first.instance_of? klass }
30
- return path.first.dig(*path[1..])
31
- end
32
-
33
- path_s = path.map(&:to_s)
34
- case path_s.first
35
- when "skills"
36
- @hash[path_s.first].each do |skill_hash|
37
- yield CvDataSection::Skill.new(skill_hash)
38
- end
39
- when "experiences"
40
- @hash[path_s.first].each do |skill_hash|
41
- yield CvDataSection::Experience.new(skill_hash)
42
- end
43
- when "education"
44
- @hash[path_s.first].each do |skill_hash|
45
- yield CvDataSection::Education.new(skill_hash)
46
- end
47
- when "interests"
48
- @hash[path_s.first].each do |skill_hash|
49
- CvDataSection::Interest.new(skill_hash)
50
- end
51
- else
52
- @hash.dig(*path_s)
53
- end
54
- end
55
- end
56
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CvDataSection
4
- class Base
5
- def initialize(hash)
6
- @hash = hash
7
- end
8
-
9
- def get_bindings
10
- binding
11
- end
12
-
13
- def has?(*path)
14
- path_s = path.map(&:to_s)
15
- data = @hash.dig(*path_s)
16
- !data.nil?
17
- end
18
-
19
- def dig(*path)
20
- path_s = path.map(&:to_s)
21
- @hash.dig(*path_s)
22
- end
23
- end
24
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CvDataSection
4
- class Education < Base
5
- include ParseHelper::Date
6
-
7
- def dig(*path)
8
- data = super
9
- return [] if data.nil? && path.first == :achievements
10
- return to_month(data) if path[-2..] == [:from, :month]
11
- return to_month(data) if path[-2..] == [:to, :month]
12
- data
13
- end
14
- end
15
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CvDataSection
4
- class Experience < Base
5
- include ParseHelper::Date
6
- def dig(*path)
7
- data = super
8
- return [] if data.nil? && path.first == :technologies
9
- return [] if data.nil? && path.first == :achievements
10
- return to_month(data) if path[-2..] == [:from, :month]
11
- return to_month(data) if path[-2..] == [:to, :month]
12
- data
13
- end
14
- end
15
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CvDataSection
4
- class Interest < Base
5
- def dig(*_path)
6
- super
7
- end
8
- end
9
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "date"
4
-
5
- module CvDataSection
6
- module ParseHelper
7
- module Date
8
- private
9
-
10
- def to_month(month_int)
11
- return "" if month_int.nil?
12
-
13
- ::Date::MONTHNAMES[month_int]
14
- end
15
- end
16
- end
17
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CvDataSection
4
- class Skill < Base
5
- def dig(*path)
6
- data = super
7
- return [] if data.nil? && path.first == :items
8
- data
9
- end
10
- end
11
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "erb"
4
-
5
- module Kamisaku
6
- class CvGenerator
7
- attr_reader :pdf_location, :html_location, :cv_data, :template
8
-
9
- def initialize(cv_data, pdf_location:, html_location: nil, template: nil)
10
- @cv_data = cv_data
11
- @pdf_location = pdf_location
12
- @html_location = html_location
13
- @template = template || "sleek"
14
- end
15
-
16
- def generate
17
- generated_html_file do |file_path|
18
- FileUtils.cp(file_path, html_location) if html_location
19
- html_file_to_pdf_file(file_path, pdf_location)
20
- soft_remove_metadata_from_pdf_file(pdf_location)
21
- end
22
- end
23
-
24
- private
25
-
26
- def html_file_to_pdf_file(html_file_path, pdf_file_path)
27
- # Convert the HTML file to a PDF using Google Chrome in headless mode
28
- pdf_conversion_command = <<~CMD
29
- google-chrome --headless --disable-gpu --run-all-compositor-stages-before-draw \
30
- --print-to-pdf=#{pdf_file_path} --no-pdf-header-footer \
31
- #{html_file_path}
32
- CMD
33
-
34
- system(pdf_conversion_command)
35
- raise "PDF generation failed" unless File.exist?(pdf_file_path)
36
- end
37
-
38
- def soft_remove_metadata_from_pdf_file(file_path)
39
- command = <<~CMD
40
- exiftool -all= #{file_path} -overwrite_original
41
- CMD
42
- system(command)
43
- end
44
-
45
- def generated_html_file
46
- temp_html_file = Tempfile.new(%w[kamisaku .html])
47
- temp_html_file.write(cv_html)
48
- temp_html_file.close
49
- begin
50
- yield temp_html_file.path
51
- ensure
52
- temp_html_file.unlink
53
- end
54
- end
55
-
56
- def cv_html
57
- rhtml = ERB.new(template_html)
58
- rhtml.result(@cv_data.get_bindings)
59
- end
60
-
61
- def template_html
62
- path = File.join(File.dirname(__FILE__), "/../templates/#{template}/template.html.erb")
63
- File.read(path)
64
- end
65
- end
66
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "yaml"
4
-
5
- module Kamisaku
6
- class MetaFileParser
7
- attr_reader :location
8
-
9
- def initialize(location)
10
- @location = location
11
- end
12
-
13
- def validate!
14
- # TODO: Check if file contains correct fields
15
- end
16
-
17
- def parse!
18
- validate!
19
- CvData.new(yaml)
20
- end
21
-
22
- def yaml
23
- @yaml ||= YAML.load_file(location)
24
- end
25
- end
26
- end