diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1950c30..96199cc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: ruby: - - '3.1.2' + - '3.1.3' steps: - uses: actions/checkout@v2 diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..ff365e0 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.1.3 diff --git a/Gemfile b/Gemfile index b986411..ee851c3 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" -ruby "3.1.2" +ruby "3.1.3" # Specify your gem's dependencies in cv_builder.gemspec gemspec @@ -14,3 +14,6 @@ gem "minitest", "~> 5.0" gem "rubocop", "~> 1.57", ">= 1.57.2" gem "rubocop-rake", "~> 0.6.0" gem "rubocop-minitest", "~> 0.33.0" + +gem "prawn" +gem "pdfkit" diff --git a/Gemfile.lock b/Gemfile.lock index 661ffc5..34883c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,6 +14,11 @@ GEM parser (3.2.2.4) ast (~> 2.4.1) racc + pdf-core (0.9.0) + pdfkit (0.8.7.3) + prawn (2.4.0) + pdf-core (~> 0.9.0) + ttfunk (~> 1.7) racc (1.7.3) rainbow (3.1.1) rake (13.0.6) @@ -37,21 +42,25 @@ GEM rubocop-rake (0.6.0) rubocop (~> 1.0) ruby-progressbar (1.13.0) + ttfunk (1.7.0) unicode-display_width (2.5.0) PLATFORMS + arm64-darwin-22 x86_64-linux DEPENDENCIES cv_builder! minitest (~> 5.0) + pdfkit + prawn rake (~> 13.0) rubocop (~> 1.57, >= 1.57.2) rubocop-minitest (~> 0.33.0) rubocop-rake (~> 0.6.0) RUBY VERSION - ruby 3.1.2p20 + ruby 3.1.3p185 BUNDLED WITH 2.3.7 diff --git a/README.md b/README.md index 956ae21..1b07232 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ *Build a beautiful CV from a text file.* +Uses `wkhtmltopdf` to generate the PDF. + Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/cv_builder`. To experiment with that code, run `bin/console` for an interactive prompt. TODO: Delete this and the text above, and describe your gem @@ -24,7 +26,25 @@ Or install it yourself as: ## Usage -TODO: Write usage instructions here +First we need to have a yaml file with the CV data. The yaml file support following sections. + +```yaml +version: 1 +prorfile: + name: + title: + about: +contact: + github: + mobile: + email: + linkedin: + location: + country: + city: +skills: + - +``` ## Development diff --git a/bin/console b/bin/console index 7c605ae..88c61d1 100755 --- a/bin/console +++ b/bin/console @@ -12,4 +12,6 @@ require "cv_builder" # Pry.start require "irb" -IRB.start(__FILE__) + +builder = CvBuilder::Builder.new +builder.run diff --git a/examples/basic_cv.yml b/examples/basic_cv.yml new file mode 100644 index 0000000..eee263f --- /dev/null +++ b/examples/basic_cv.yml @@ -0,0 +1,30 @@ +version: 1 +profile: + name: Sinaru Gunawardena + title: Senior Software Engineer + about: "Versatile software developer with more than 6 years experience + in the field of web application development. Consistent learner, focus + on improving skills, collect and share new knowledge while contributing + the best to the organization. A team player with collaboration experience + in Scrum and Kanban teams." +contact: + github: sinaru + mobile: +353 838315349 + website: sinaru.com + email: sinarug@gmail.com + linkedin: sinaru + location: + country: IRE + city: Dublin +skills: + - area: Development + items: + - Ruby + - Ruby on Rails + - ES6 JavaScript + - Vue.js 2 + - React + - area: Database + items: + - PostgreSQL + - MongoDB diff --git a/lib/arg_parser.rb b/lib/arg_parser.rb new file mode 100644 index 0000000..306a6c3 --- /dev/null +++ b/lib/arg_parser.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module CvBuilder + class ArgParser + def initialize(args) + puts args + end + end +end diff --git a/lib/cv_builder.rb b/lib/cv_builder.rb index 66f0e7a..2662465 100644 --- a/lib/cv_builder.rb +++ b/lib/cv_builder.rb @@ -1,8 +1,22 @@ # frozen_string_literal: true require_relative "cv_builder/version" +require_relative "cv_builder/cv_data" +require_relative "cv_builder/cv_data_sections/skill" +require_relative "cv_builder/meta_file_parser" +require_relative "cv_builder/cv_generator" module CvBuilder class Error < StandardError; end - # Your code goes here... + + class Builder + def run + example_yaml = File.join(File.dirname(__FILE__), "/../examples/basic_cv.yml") + parser = MetaFileParser.new(example_yaml) + cv_data = parser.parse! + + output_location = File.join(File.dirname(__FILE__), "/../tmp/basic_cv.pdf") + CvGenerator.new(cv_data).generate(output_location) + end + end end diff --git a/lib/cv_builder/cv_data.rb b/lib/cv_builder/cv_data.rb new file mode 100644 index 0000000..6e3bcdd --- /dev/null +++ b/lib/cv_builder/cv_data.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module CvBuilder + class CvData + def initialize(hash) + @hash = hash + end + + def get_bindings + binding + end + + def dig(*path) + if path.first.instance_of?(CvDataSection::Skill) + return path.first.dig(*path[1..]) + end + + # TODO: if experiences, return CvData::Experiences + path_s = path.map(&:to_s) + case path_s.first + when "skills" + @hash[path_s.first].each do |skill_hash| + yield CvDataSection::Skill.new(skill_hash) + end + else + @hash.dig(*path_s) + end + end + end +end diff --git a/lib/cv_builder/cv_data_sections/skill.rb b/lib/cv_builder/cv_data_sections/skill.rb new file mode 100644 index 0000000..c606bd7 --- /dev/null +++ b/lib/cv_builder/cv_data_sections/skill.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module CvDataSection + class Skill + def initialize(hash) + @hash = hash + end + + def get_bindings + binding + end + + def dig(*path) + path_s = path.map(&:to_s) + data = @hash.dig(*path_s) + return [] if data.nil? && path.first == :items + data + end + end +end diff --git a/lib/cv_builder/cv_generator.rb b/lib/cv_builder/cv_generator.rb new file mode 100644 index 0000000..2f7513a --- /dev/null +++ b/lib/cv_builder/cv_generator.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "erb" +require "pdfkit" + +module CvBuilder + class CvGenerator + def initialize(cv_data) + @cv_data = cv_data + @template = "basic" + end + + ## Generate CV PDF file using PDFKit. In future if an alternative approach is needed (e.g. using Prawn gem), + # consider refactoring code to follow strategy pattern. + def generate(output_location) + pdf = PDFKit.new(cv_html, page_size: "A4") + pdf.stylesheets = template_stylesheets + pdf.to_file(output_location) + end + + private + + def cv_html + rhtml = ERB.new(template_html) + rhtml.result(@cv_data.get_bindings) + end + + def template_html + path = File.join(File.dirname(__FILE__), "/templates/#{@template}/index.html.erb") + File.read(path) + end + + def template_stylesheets + [File.join(File.dirname(__FILE__), "/templates/#{@template}/styles/index.css")] + end + end +end diff --git a/lib/cv_builder/meta_file_parser.rb b/lib/cv_builder/meta_file_parser.rb new file mode 100644 index 0000000..f7d96bd --- /dev/null +++ b/lib/cv_builder/meta_file_parser.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "yaml" + +module CvBuilder + class MetaFileParser + attr_reader :location + + def initialize(location) + @location = location + end + + def validate! + # TODO: Check if file contains correct fields + end + + def parse! + validate! + CvData.new(yaml) + end + + def yaml + @yaml ||= YAML.load_file(location) + end + end +end diff --git a/lib/cv_builder/templates/basic/Cambria-Font.ttf b/lib/cv_builder/templates/basic/Cambria-Font.ttf new file mode 100644 index 0000000..ab88fb4 Binary files /dev/null and b/lib/cv_builder/templates/basic/Cambria-Font.ttf differ diff --git a/lib/cv_builder/templates/basic/index.html.erb b/lib/cv_builder/templates/basic/index.html.erb new file mode 100644 index 0000000..7786348 --- /dev/null +++ b/lib/cv_builder/templates/basic/index.html.erb @@ -0,0 +1,61 @@ + + + + +
+
+
+
<%= dig :profile, :name %>
+
<%= dig :profile, :title %>
+
+
+ + +
+ +
+ + <%= dig :contact, :email %> +
+
+ + <%= dig :contact, :location, :city %>, <%= dig :contact, :location, :country %> +
+
+ +
+
+
+

<%= dig :profile, :about %>

+
+
+
+ +
+ <% dig :skills do |skill| %> +

<%= dig skill, :area %>

+ <% dig(skill, :items).each do |item| %> + * <%= item %> + <% end %> + <% end %> +
+ + \ No newline at end of file diff --git a/lib/cv_builder/templates/basic/styles/index.css b/lib/cv_builder/templates/basic/styles/index.css new file mode 100644 index 0000000..a06c657 --- /dev/null +++ b/lib/cv_builder/templates/basic/styles/index.css @@ -0,0 +1,62 @@ +@font-face { + font-family: "Cambria"; +url("./Cambria-Font.ttf"); +} + +/* FIXME: font not loading */ +body { + font-family: "Cambria", serif; + color: #545454; +} + +a { + color: #545454; +} + +a:link { + text-decoration: none; +} + +a:visited { + text-decoration: none; +} + +a:hover { + text-decoration: none; +} + +a:active { + text-decoration: none; +} + +.flex { + display: flex; + +} + +.profile { + padding-bottom: 10px; +} + +.profile .name { + font-size: 2em; +} + +.profile .title { + font-size: 1.3em; +} + +.contact { + flex: 1 0 auto; + justify-content: space-between; + padding: 5px 0 0 1em; +} + +.contact .icon { + fill: #b8b8b8; +} + +.about { + flex: 0 0 100%; + font-size: small; +} diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 0000000..e69de29