resumer 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 +7 -0
- data/CHANGELOG.md +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +57 -0
- data/bin/resumer +6 -0
- data/lib/resumer/commands/export.rb +109 -0
- data/lib/resumer/commands/init.rb +47 -0
- data/lib/resumer/commands/usage.rb +20 -0
- data/lib/resumer/commands/validate.rb +20 -0
- data/lib/resumer/error.rb +5 -0
- data/lib/resumer/export.rb +135 -0
- data/lib/resumer/info.rb +10 -0
- data/lib/resumer.rb +49 -0
- data/templates/default.yml +86 -0
- data/themes/default/css/pdf.styles.css +34 -0
- data/themes/default/css/styles.css +213 -0
- data/themes/default/index.html +89 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5bfc062725a0c56a2f661476ae695ce31ee65ee5f0017352fda23e28b43e00e3
|
4
|
+
data.tar.gz: 4fe0cd3ceef03949bcf84a557230a264faa1be4963db308ce31308c9fb5f3346
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a792f865b82b176899b54f0a1debdae2a1094a37524c34df6dec50b142303cdcb3e1b14c25e35124f5e59f830848f68f0d82ace12454c197ba1ef273c921d315
|
7
|
+
data.tar.gz: 907b6d433ce95c2e5b4fdcf88411e3ce2398a2195e4946e8d49c8c08c29ead79f6199a4816bc13ed66ad2927552646d53a1261828d262aa5a55d2e887ec34e03
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [0.1.0] - 2021-12-28
|
8
|
+
### Added
|
9
|
+
- An "init" command to create a basic resume YAML template
|
10
|
+
- A "usage" command to display the quick help
|
11
|
+
- An "export" command to convert the YAML resume to PDF or HTML
|
12
|
+
- Use a nice default HTML/PDF theme
|
13
|
+
- This CHANGELOG file
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Vito Tardia
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Resumer
|
2
|
+
|
3
|
+
Resumer is a Ruby tool that processes resumes written in machine-readable YAML format and produces a well-formatted version in either HTML or PDF.
|
4
|
+
|
5
|
+
It was initially inspired by [JSON Resume][jsonresume] but I though YAML would be a more human-readable format for this purpose.
|
6
|
+
|
7
|
+
Under the hood, Resumer uses the [commander gem][commander], and produces PDF files using [wkhtmltopdf][wkhtmltopdf], which must be already installed in the system.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'resumer'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
```console
|
20
|
+
$ bundle install
|
21
|
+
```
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
```console
|
26
|
+
$ gem install resumer
|
27
|
+
```
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
Create a new YAML resume file:
|
32
|
+
|
33
|
+
```console
|
34
|
+
$ resumer init [path/to/new/resume.yml]
|
35
|
+
```
|
36
|
+
|
37
|
+
Edit the YAML file with your data, then export it:
|
38
|
+
|
39
|
+
```console
|
40
|
+
$ resumer <sourceFile> [destFile/defaults to sourceFile.pdf/html]
|
41
|
+
```
|
42
|
+
|
43
|
+
## Contributing
|
44
|
+
|
45
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/resumer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
46
|
+
|
47
|
+
## License
|
48
|
+
|
49
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
50
|
+
|
51
|
+
## Code of Conduct
|
52
|
+
|
53
|
+
Everyone interacting in the Resumer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/resumer/blob/master/CODE_OF_CONDUCT.md).
|
54
|
+
|
55
|
+
[jsonresume]: https://jsonresume.org/
|
56
|
+
[commander]: https://github.com/commander-rb/commander
|
57
|
+
[wkhtmltopdf]: http://wkhtmltopdf.org/
|
data/bin/resumer
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'commander'
|
4
|
+
require 'resumer/export'
|
5
|
+
|
6
|
+
module Resumer
|
7
|
+
module Command
|
8
|
+
# Export a YAML CV to an HTML or PDF file
|
9
|
+
class Export
|
10
|
+
def initialize(args = [], options = nil)
|
11
|
+
raise Error, 'No source file given, please provide a YAML source file' \
|
12
|
+
unless args.count.positive?
|
13
|
+
|
14
|
+
@export = Resumer::Export.new
|
15
|
+
@settings = parse_options(options)
|
16
|
+
@src, @dest = parse_args(args)
|
17
|
+
@settings[:format] = define_format(@dest) if options.format.nil?
|
18
|
+
@settings[:override] = (ask_override if File.exist? @dest) || false
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_args(args)
|
22
|
+
source = define_source(args.first)
|
23
|
+
destination = define_destination(args[1], source)
|
24
|
+
[source, destination]
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_options(options)
|
28
|
+
settings = config = {}
|
29
|
+
unless options.format.nil?
|
30
|
+
settings[:format] = options.format.downcase.to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
config = parse_custom_config(options.config) unless options.config.nil?
|
34
|
+
|
35
|
+
@export.defaults.merge(config, settings)
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_custom_config(config_file)
|
39
|
+
config_file_path = File.expand_path(config_file)
|
40
|
+
config = load(config_file_path)
|
41
|
+
return config if config[:theme].nil?
|
42
|
+
|
43
|
+
config[:theme] = parse_custom_theme(config[:theme], config_file_path)
|
44
|
+
config
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_custom_theme(path, base_path)
|
48
|
+
theme_path = File.expand_path(path, File.dirname(base_path))
|
49
|
+
return theme_path if File.exist?(theme_path)
|
50
|
+
|
51
|
+
raise ArgumentError, "Cannot find custom theme '#{theme_path}'"
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_source(arg)
|
55
|
+
source = File.absolute_path arg
|
56
|
+
raise Error, "Source file does not exist: #{source}" \
|
57
|
+
unless File.exist? source
|
58
|
+
|
59
|
+
source
|
60
|
+
end
|
61
|
+
|
62
|
+
def define_destination(arg, source)
|
63
|
+
return File.absolute_path arg if arg
|
64
|
+
|
65
|
+
# Default to source path with export format extension
|
66
|
+
File.join(
|
67
|
+
File.absolute_path(File.dirname(source)),
|
68
|
+
File.basename(source, '.*')
|
69
|
+
) + '.' + @settings[:format].to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
def define_format(destination)
|
73
|
+
# File extension without leading dot
|
74
|
+
File.extname(destination)[/^\.(.*)/, 1].to_sym
|
75
|
+
end
|
76
|
+
|
77
|
+
def ask_override
|
78
|
+
agree('Destination exists, do you want to override? [y/N]', true) do |q|
|
79
|
+
q.responses[:not_valid] = 'Please enter "[y]es" or "[n]o".'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def load(file)
|
84
|
+
YAML.safe_load(File.read(file), [Symbol], symbolize_names: true)
|
85
|
+
rescue StandardError => e
|
86
|
+
raise Error, "Failed to read #{file}: #{e.message}"
|
87
|
+
end
|
88
|
+
|
89
|
+
# TODO: check the override+file-exists options and throw errors if
|
90
|
+
# false+true
|
91
|
+
def run
|
92
|
+
# Ensure source file is valid YAML/CV
|
93
|
+
data = load(@src)
|
94
|
+
|
95
|
+
# Ensure destination format is supported
|
96
|
+
unless @export.formats.include? @settings[:format]
|
97
|
+
raise Error, "Unsupported format: '#{@settings[:format].to_s.upcase}'"
|
98
|
+
end
|
99
|
+
|
100
|
+
say(
|
101
|
+
"Exporting #{@src} to #{@dest} in #{@settings[:format].to_s.upcase}" \
|
102
|
+
" format (override: #{@settings[:override]})"
|
103
|
+
)
|
104
|
+
|
105
|
+
@export.run(data, @dest, @settings)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'commander'
|
4
|
+
|
5
|
+
module Resumer
|
6
|
+
module Command
|
7
|
+
# Initialize a resume YAML file
|
8
|
+
class Init < Commander::Command
|
9
|
+
def initialize
|
10
|
+
super(:init)
|
11
|
+
@summary = 'Initialize a resume.yml file'
|
12
|
+
@syntax = "#{Resumer::BIN} init [/path/to/resume.yml]"
|
13
|
+
@template = File.expand_path(
|
14
|
+
"#{File.dirname(__FILE__)}/../../../templates/default.yml"
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ask_override
|
19
|
+
agree('Destination exists, do you want to override? [y/n]', true) do |q|
|
20
|
+
q.responses[:not_valid] = 'Please enter "[y]es" or "[n]o".'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_dest(path)
|
25
|
+
return File.join(Dir.pwd, 'resume.yml') if path.nil?
|
26
|
+
|
27
|
+
dest = File.expand_path(path)
|
28
|
+
return File.join(dest, 'resume.yml') if File.directory? dest
|
29
|
+
|
30
|
+
dest
|
31
|
+
end
|
32
|
+
|
33
|
+
def run(*args)
|
34
|
+
dest = parse_dest(args.first)
|
35
|
+
override = (ask_override if File.exist? dest) || false
|
36
|
+
return if File.exist?(dest) && !override
|
37
|
+
|
38
|
+
FileUtils.cp @template, dest
|
39
|
+
say "Your new resume is ready at '#{dest}'"
|
40
|
+
say 'Good luck!'
|
41
|
+
rescue Errno => e
|
42
|
+
say e.message
|
43
|
+
exit e.class::Errno
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'commander'
|
4
|
+
|
5
|
+
module Resumer
|
6
|
+
module Command
|
7
|
+
# Display usage info
|
8
|
+
class Usage < Commander::Command
|
9
|
+
def initialize
|
10
|
+
super(:usage)
|
11
|
+
@summary = 'Display usage info'
|
12
|
+
@syntax = "#{Resumer::BIN} <command> [options] <args>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(*_args)
|
16
|
+
say("usage: #{@syntax}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'commander'
|
4
|
+
|
5
|
+
module Resumer
|
6
|
+
module Command
|
7
|
+
# Validate a YAML resume
|
8
|
+
class Validate < Commander::Command
|
9
|
+
def initialize
|
10
|
+
super(:validate)
|
11
|
+
@summary = 'Validate your YAML resume (not implemented)'
|
12
|
+
@syntax = "#{Resumer::BIN} validate [/path/to/resume.yml]"
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(*_args)
|
16
|
+
say "[#{self.class.name}] Feature not yet implemented"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'pdfkit'
|
6
|
+
require 'kramdown'
|
7
|
+
|
8
|
+
module Resumer
|
9
|
+
# Export a YAML CV to an HTML or PDF file
|
10
|
+
class Export
|
11
|
+
attr_reader :defaults
|
12
|
+
attr_reader :formats
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@formats = %i[html pdf]
|
16
|
+
@defaults = {
|
17
|
+
format: @formats.first,
|
18
|
+
theme: File.expand_path(
|
19
|
+
"#{File.dirname(__FILE__)}/../../themes/default"
|
20
|
+
),
|
21
|
+
pdf: pdf_defaults
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def pdf_defaults
|
26
|
+
{
|
27
|
+
page_size: 'A4',
|
28
|
+
margin_top: '1.5cm',
|
29
|
+
margin_bottom: '1.4cm',
|
30
|
+
margin_left: '1.5cm',
|
31
|
+
margin_right: '1.5cm',
|
32
|
+
print_media_type: true
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_format
|
37
|
+
@defaults[:format]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Transform the YAML object in an OpenStruct with symbol keys
|
41
|
+
def normalize(data)
|
42
|
+
return normalize_hash(data) if data.is_a? Hash
|
43
|
+
|
44
|
+
return normalize_array(data) if data.is_a? Array
|
45
|
+
|
46
|
+
data
|
47
|
+
end
|
48
|
+
|
49
|
+
def normalize_hash(data)
|
50
|
+
result = OpenStruct.new data
|
51
|
+
data.each do |k, v|
|
52
|
+
result[k] = normalize(v)
|
53
|
+
end
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
def normalize_array(data)
|
58
|
+
result = []
|
59
|
+
data.each do |i|
|
60
|
+
result.push normalize(i)
|
61
|
+
end
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
# Markdown helper for tempaltes
|
66
|
+
def markdown(text)
|
67
|
+
Kramdown::Document.new(text).to_html
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_html(data, theme = @defaults[:theme], with_style = false)
|
71
|
+
# Load the theme
|
72
|
+
erb = ERB.new(
|
73
|
+
File.read(File.absolute_path("#{theme}/index.html"))
|
74
|
+
)
|
75
|
+
if with_style
|
76
|
+
style = File.read(
|
77
|
+
File.absolute_path("#{theme}/css/styles.css")
|
78
|
+
)
|
79
|
+
end
|
80
|
+
data = normalize(data)
|
81
|
+
# Generate the HTML
|
82
|
+
erb.result(binding)
|
83
|
+
end
|
84
|
+
|
85
|
+
def save_html(html, dest)
|
86
|
+
File.open(dest, 'w+') do |file|
|
87
|
+
file.puts html
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def save_pdf(html, dest, theme = @defaults[:theme])
|
92
|
+
pdf_config
|
93
|
+
kit = PDFKit.new(html)
|
94
|
+
|
95
|
+
# Load generic styles
|
96
|
+
kit.stylesheets << "#{theme}/css/styles.css"
|
97
|
+
|
98
|
+
# Load PDF-specific styles
|
99
|
+
pdfstyles = "#{theme}/css/pdf.styles.css"
|
100
|
+
kit.stylesheets << pdfstyles if File.exist? pdfstyles
|
101
|
+
|
102
|
+
# Compile and save
|
103
|
+
kit.to_file(dest)
|
104
|
+
end
|
105
|
+
|
106
|
+
def run(data, dest, settings = @defaults)
|
107
|
+
raise Error, 'Invalid or empty source data' if data.nil?
|
108
|
+
|
109
|
+
html = create_html(data, settings[:theme], (settings[:format] == :html))
|
110
|
+
case settings[:format]
|
111
|
+
# Process HTML export
|
112
|
+
when :html
|
113
|
+
save_html(html, dest)
|
114
|
+
# Process PDF export
|
115
|
+
when :pdf
|
116
|
+
save_pdf(html, dest, settings[:theme])
|
117
|
+
else
|
118
|
+
raise Error, "Invalid format #{settings[:format].to_s.upcase}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def pdf_config
|
125
|
+
PDFKit.configure do |config|
|
126
|
+
# config.wkhtmltopdf = '/path/to/wkhtmltopdf'
|
127
|
+
config.default_options = pdf_defaults
|
128
|
+
# Use only if your external hostname is unavailable on the server.
|
129
|
+
# config.root_url = "file://#{File.dirname('../')}/themes/default"
|
130
|
+
# config.protocol = 'file'
|
131
|
+
config.verbose = false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/lib/resumer/info.rb
ADDED
data/lib/resumer.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'resumer/error'
|
4
|
+
require 'resumer/info'
|
5
|
+
require 'commander'
|
6
|
+
|
7
|
+
# Require all the available commands
|
8
|
+
Dir.glob(File.expand_path('resumer/commands/*.rb', __dir__), &method(:require))
|
9
|
+
|
10
|
+
module Resumer
|
11
|
+
# Commander CLI interface
|
12
|
+
class CLI
|
13
|
+
include Commander::Methods
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
program :name, Resumer::PKG
|
17
|
+
program :version, Resumer::VERSION
|
18
|
+
program :description, Resumer::DESCRIPTION
|
19
|
+
program :help_formatter, :compact
|
20
|
+
program :help, *Resumer::AUTHOR
|
21
|
+
end
|
22
|
+
|
23
|
+
# rubocop:disable Metrics/MethodLength
|
24
|
+
def add_export_command
|
25
|
+
command :export do |c|
|
26
|
+
c.option '-c', '--config FILE', 'Load a custom configuration file'
|
27
|
+
c.option '--format STRING', 'Destination format (HTML or PDF)'
|
28
|
+
c.summary = 'Export a YAML resume to HTML or PDF format'
|
29
|
+
c.syntax = "#{Resumer::BIN} export <source.yml> [destination.html|.pdf]"
|
30
|
+
c.action do |args, options|
|
31
|
+
Resumer::Command::Export.new(args, options).run
|
32
|
+
rescue StandardError => e
|
33
|
+
say "Error (#{e.class.name}): #{e.message}"
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
# rubocop:enable Metrics/MethodLength
|
39
|
+
|
40
|
+
def run
|
41
|
+
default_command :usage
|
42
|
+
add_command(Resumer::Command::Usage.new)
|
43
|
+
add_command(Resumer::Command::Init.new)
|
44
|
+
add_command(Resumer::Command::Validate.new)
|
45
|
+
add_export_command
|
46
|
+
run!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
---
|
2
|
+
basics:
|
3
|
+
name: Joe Bloggs
|
4
|
+
label: coder / nerd
|
5
|
+
picture: ''
|
6
|
+
email: me@example.com
|
7
|
+
phone: "+44 (0) 1234 567890"
|
8
|
+
website: http://mywebsite.com
|
9
|
+
|
10
|
+
summary: "I'm Joe. Lorem ipsum **dolor sit amet**, consectetur adipisicing elit,
|
11
|
+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
12
|
+
Ut enim ad minim veniam, quis nostrud _exercitation ullamco_ laboris nisi ut
|
13
|
+
aliquip ex ea commodo consequat."
|
14
|
+
|
15
|
+
profiles:
|
16
|
+
|
17
|
+
- network: Your GitHub
|
18
|
+
username: jbloggs
|
19
|
+
url: https://github.com/jbloggs
|
20
|
+
|
21
|
+
- network: You on LinkedIn
|
22
|
+
username: joe
|
23
|
+
url: https://linkedin.com/in/joe
|
24
|
+
|
25
|
+
#...other profiles
|
26
|
+
|
27
|
+
work:
|
28
|
+
|
29
|
+
- company: Great Company 1, London - UK
|
30
|
+
position: Frontend Engineer
|
31
|
+
startDate: august 2019
|
32
|
+
summary: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
|
33
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
34
|
+
|
35
|
+
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
36
|
+
aliquip ex ea commodo consequat."
|
37
|
+
|
38
|
+
- company: Great Company 2, NYC - US
|
39
|
+
position: Great Well paid role
|
40
|
+
startDate: february 2017
|
41
|
+
endDate: august 2019
|
42
|
+
summary: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
|
43
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
44
|
+
|
45
|
+
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
46
|
+
aliquip ex ea commodo consequat."
|
47
|
+
|
48
|
+
- company: <Company>, <City> - <Country>
|
49
|
+
position: <Role>
|
50
|
+
website: http://somesite.com
|
51
|
+
startDate: <Month Year>
|
52
|
+
endDate: <Month Year>
|
53
|
+
summary: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
|
54
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
55
|
+
|
56
|
+
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
57
|
+
aliquip ex ea commodo consequat."
|
58
|
+
|
59
|
+
education:
|
60
|
+
|
61
|
+
- institution: <School One>, <City> - Country
|
62
|
+
area: <Topic or degree>
|
63
|
+
startDate: <Month Year>
|
64
|
+
endDate: <Month Year>
|
65
|
+
|
66
|
+
- institution: <School Two or Great Mentor>
|
67
|
+
studyType: <Freeform description of the experience>
|
68
|
+
startDate: <Month Year>
|
69
|
+
endDate: <Month Year>
|
70
|
+
|
71
|
+
# more education here
|
72
|
+
|
73
|
+
skills:
|
74
|
+
|
75
|
+
- name: "**Website design**, with standard compliant **HTML/CSS**"
|
76
|
+
keywords:
|
77
|
+
- HTML
|
78
|
+
- CSS
|
79
|
+
- Javascript
|
80
|
+
|
81
|
+
- name: "**User experience** (UX) and product design"
|
82
|
+
keywords:
|
83
|
+
- UI
|
84
|
+
- UX
|
85
|
+
|
86
|
+
- name: "**Email marketing** services and consulting"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
/* csslint ignore:start */
|
2
|
+
header {
|
3
|
+
display: -webkit-box;
|
4
|
+
|
5
|
+
-webkit-box-pack: justify;
|
6
|
+
}
|
7
|
+
|
8
|
+
header .contact {
|
9
|
+
-webkit-box-flex: 1;
|
10
|
+
}
|
11
|
+
|
12
|
+
main {
|
13
|
+
overflow: hidden;
|
14
|
+
}
|
15
|
+
|
16
|
+
.experience {
|
17
|
+
float: left;
|
18
|
+
}
|
19
|
+
|
20
|
+
aside {
|
21
|
+
display: -webkit-box;
|
22
|
+
-webkit-box-flex: 1;
|
23
|
+
-webkit-box-orient: vertical;
|
24
|
+
float: right;
|
25
|
+
}
|
26
|
+
|
27
|
+
aside section {
|
28
|
+
-webkit-box-flex: 1;
|
29
|
+
}
|
30
|
+
|
31
|
+
.profiles {
|
32
|
+
clear: both;
|
33
|
+
}
|
34
|
+
/* csslint ignore:end */
|
@@ -0,0 +1,213 @@
|
|
1
|
+
/*
|
2
|
+
SCREEN 16px/1.4 => 22,4px baseline
|
3
|
+
PRINT 10pt/14pt => 14pt baseline
|
4
|
+
*/
|
5
|
+
|
6
|
+
html {
|
7
|
+
background-color: #fff;
|
8
|
+
font-family: Calibri, 'Helvetica Neue', Arial, sans-serif;
|
9
|
+
font-size: 100%;
|
10
|
+
line-height: 1.4;
|
11
|
+
color: #333;
|
12
|
+
}
|
13
|
+
|
14
|
+
.page {
|
15
|
+
width: 100%;
|
16
|
+
margin: 0;
|
17
|
+
padding: 0;
|
18
|
+
}
|
19
|
+
|
20
|
+
a, a:link, a:hover, a:visited {
|
21
|
+
color: #000099;
|
22
|
+
}
|
23
|
+
|
24
|
+
p {
|
25
|
+
margin-top: .5em;
|
26
|
+
margin-bottom: .5em;
|
27
|
+
}
|
28
|
+
|
29
|
+
/** Typography **/
|
30
|
+
header .contact, .profiles, .license, .skills ul, .education ul {
|
31
|
+
font-size: 90%;
|
32
|
+
line-height: 1.55;
|
33
|
+
}
|
34
|
+
|
35
|
+
header {
|
36
|
+
display: flex;
|
37
|
+
justify-content: space-between;
|
38
|
+
border-top: .2em solid #AAAAAA;
|
39
|
+
}
|
40
|
+
|
41
|
+
header .author-name {
|
42
|
+
letter-spacing: .1rem;
|
43
|
+
margin: 0;
|
44
|
+
padding: 1em 0 .7em 0;
|
45
|
+
flex-grow: 1;
|
46
|
+
max-width: 32.88em;
|
47
|
+
}
|
48
|
+
|
49
|
+
header svg {
|
50
|
+
display: block;
|
51
|
+
margin-bottom: 0.54em; /* Total height 67,2px (22,4*3) */
|
52
|
+
}
|
53
|
+
|
54
|
+
header .contact {
|
55
|
+
flex:1;
|
56
|
+
|
57
|
+
flex-grow: 1;
|
58
|
+
max-width: 15.86em; /* 14.28em = 90% */
|
59
|
+
|
60
|
+
padding: 1em 0 0 0;
|
61
|
+
font-style: italic;
|
62
|
+
color: #444;
|
63
|
+
}
|
64
|
+
|
65
|
+
.contact ul {
|
66
|
+
margin: 0;
|
67
|
+
padding: 0;
|
68
|
+
list-style: none;
|
69
|
+
}
|
70
|
+
|
71
|
+
main {
|
72
|
+
display: flex;
|
73
|
+
flex-direction: row;
|
74
|
+
justify-content: space-between;
|
75
|
+
flex-wrap: wrap;
|
76
|
+
align-items: stretch;
|
77
|
+
align-content: space-between;
|
78
|
+
}
|
79
|
+
|
80
|
+
.experience {
|
81
|
+
flex-grow: 2;
|
82
|
+
max-width: 31.88em;
|
83
|
+
}
|
84
|
+
|
85
|
+
.summary {
|
86
|
+
line-height: 1.8;
|
87
|
+
margin: 1.6em 0;
|
88
|
+
font-size: 110%;
|
89
|
+
color: #666;
|
90
|
+
}
|
91
|
+
|
92
|
+
aside {
|
93
|
+
display: flex;
|
94
|
+
flex:1;
|
95
|
+
flex-grow: 1;
|
96
|
+
max-width: 14.28em;
|
97
|
+
flex-direction: column;
|
98
|
+
justify-content: flex-start;
|
99
|
+
align-items: flex-start;
|
100
|
+
}
|
101
|
+
|
102
|
+
aside section {
|
103
|
+
flex: 1;
|
104
|
+
}
|
105
|
+
|
106
|
+
aside ul {
|
107
|
+
padding: 0;
|
108
|
+
list-style: none;
|
109
|
+
}
|
110
|
+
|
111
|
+
aside li {
|
112
|
+
margin: .775em 0;
|
113
|
+
}
|
114
|
+
|
115
|
+
.experience .headline {
|
116
|
+
font-size: 160%;
|
117
|
+
font-weight: normal;
|
118
|
+
line-height: 1;
|
119
|
+
margin-top: .2em;
|
120
|
+
margin-bottom: .5em;
|
121
|
+
color: #444;
|
122
|
+
}
|
123
|
+
|
124
|
+
.experience .position {
|
125
|
+
font-size: 130%;
|
126
|
+
font-weight: normal;
|
127
|
+
line-height: 1.2;
|
128
|
+
margin-top: 0.82em;
|
129
|
+
margin-bottom: 0.33em;
|
130
|
+
color: #232323;
|
131
|
+
}
|
132
|
+
|
133
|
+
.experience .dates {
|
134
|
+
margin-top: 0;
|
135
|
+
margin-bottom: 0;
|
136
|
+
font-style: italic;
|
137
|
+
color: #444;
|
138
|
+
}
|
139
|
+
|
140
|
+
.experience dd {
|
141
|
+
margin: .5em 0;
|
142
|
+
color: #424242;
|
143
|
+
}
|
144
|
+
|
145
|
+
.skills .headline, .education .headline {
|
146
|
+
font-size: 110%;
|
147
|
+
font-weight: normal;
|
148
|
+
line-height: 1.25;
|
149
|
+
margin-top: .63em;
|
150
|
+
margin-bottom: .63em;
|
151
|
+
color: #232323;
|
152
|
+
border-bottom: .1em solid #515151;
|
153
|
+
}
|
154
|
+
|
155
|
+
.education .headline {
|
156
|
+
font-style: italic;
|
157
|
+
}
|
158
|
+
|
159
|
+
.skills ul, .education ul {
|
160
|
+
color: #424242;
|
161
|
+
}
|
162
|
+
|
163
|
+
.profiles {
|
164
|
+
color: #424242;
|
165
|
+
}
|
166
|
+
|
167
|
+
.profiles ul {
|
168
|
+
padding: 0 2em;
|
169
|
+
}
|
170
|
+
|
171
|
+
footer {
|
172
|
+
margin-top: 4em;
|
173
|
+
}
|
174
|
+
|
175
|
+
.license {
|
176
|
+
font-style: italic;
|
177
|
+
color: #444;
|
178
|
+
}
|
179
|
+
|
180
|
+
@media screen {
|
181
|
+
html {
|
182
|
+
background-color: #ddd;
|
183
|
+
}
|
184
|
+
.page {
|
185
|
+
background-color: #fff;
|
186
|
+
max-width: 48.57em;
|
187
|
+
margin: 1.4em auto;
|
188
|
+
padding: 5.014em 4.285em;
|
189
|
+
box-shadow: .2em .2em .5em #666;
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
@media print {
|
194
|
+
html {
|
195
|
+
font-size: 10pt;
|
196
|
+
}
|
197
|
+
header svg {
|
198
|
+
width: 5.2cm;
|
199
|
+
}
|
200
|
+
p {
|
201
|
+
orphans:3;
|
202
|
+
widows:2;
|
203
|
+
}
|
204
|
+
|
205
|
+
footer {
|
206
|
+
margin-top: 0;
|
207
|
+
margin-bottom: 0;
|
208
|
+
}
|
209
|
+
|
210
|
+
.experience {
|
211
|
+
max-width: 44em;
|
212
|
+
}
|
213
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
|
3
|
+
<html lang="en">
|
4
|
+
<head>
|
5
|
+
<meta charset="utf-8">
|
6
|
+
|
7
|
+
<title><%=data.basics.name%> | Resume</title>
|
8
|
+
<% if style then %><style><%="#{style}"%></style><% end %>
|
9
|
+
|
10
|
+
</head>
|
11
|
+
|
12
|
+
<body>
|
13
|
+
<div class="page">
|
14
|
+
<!-- Page Header: Curriculum Vitae -->
|
15
|
+
<header role="banner">
|
16
|
+
<h1 class="author-name"><%=data.basics.name%></h1>
|
17
|
+
|
18
|
+
<!-- Contact box -->
|
19
|
+
<div class="contact h-card">
|
20
|
+
<ul>
|
21
|
+
<li><strong>w</strong>: <span class="p-name u-url"><%=data.basics.website%></span></li>
|
22
|
+
<li><strong>@</strong>: <span class="u-email"><%=data.basics.email%></span></li>
|
23
|
+
<li><strong>m</strong>: <span class="u-phone"><%=data.basics.phone%></span></li>
|
24
|
+
</ul>
|
25
|
+
</div>
|
26
|
+
</header>
|
27
|
+
|
28
|
+
<main>
|
29
|
+
<% if data.basics.summary %>
|
30
|
+
<section id="summary" class="summary">
|
31
|
+
<%=markdown(data.basics.summary)%>
|
32
|
+
</section>
|
33
|
+
<% end %>
|
34
|
+
|
35
|
+
<section id="experience" class="experience">
|
36
|
+
<h1 class="headline">Professional Experience</h1>
|
37
|
+
<dl>
|
38
|
+
<%data.basics.work.each do |w| %>
|
39
|
+
<dt class="position"><%=(w.position) ? w.position + " /": "" %> <%=w.company%></dt>
|
40
|
+
<dt class="dates"><%=w.startDate%> - <%=w.endDate || "present"%></dt>
|
41
|
+
<dd><%=markdown(w.summary)%></dd>
|
42
|
+
<%end%>
|
43
|
+
</dl>
|
44
|
+
</section>
|
45
|
+
|
46
|
+
<aside>
|
47
|
+
<% if data.skills.count > 0 %>
|
48
|
+
<section id="skills" class="skills">
|
49
|
+
<h1 class="headline">Skills</h1>
|
50
|
+
<ul>
|
51
|
+
<% data.skills.each do |s| %>
|
52
|
+
<li><%=markdown(s.name)%></li>
|
53
|
+
<% end %>
|
54
|
+
</ul>
|
55
|
+
</section>
|
56
|
+
<% end %>
|
57
|
+
|
58
|
+
<% if data.education.count > 0 %>
|
59
|
+
<section id="education" class="education">
|
60
|
+
<h1 class="headline">Education</h1>
|
61
|
+
<ul>
|
62
|
+
<% data.education.each do |e| %>
|
63
|
+
<li>
|
64
|
+
<strong><%=e.institution%></strong><br/>
|
65
|
+
<%=e.area || e.studyType || ""%> <%=e.startDate || ""%> <%=e.endDate || ""%>
|
66
|
+
</li>
|
67
|
+
<% end %>
|
68
|
+
</ul>
|
69
|
+
</section>
|
70
|
+
<% end %>
|
71
|
+
</aside>
|
72
|
+
</main>
|
73
|
+
|
74
|
+
<% if data.basics.profiles.count > 0 %>
|
75
|
+
<section id="profiles" class="profiles">
|
76
|
+
<ul>
|
77
|
+
<% data.basics.profiles.each do |p| %>
|
78
|
+
<li><%=p.network%> <a href="<%=p.url%>"><%=p.url%></a></li>
|
79
|
+
<% end %>
|
80
|
+
</ul>
|
81
|
+
</section>
|
82
|
+
<% end %>
|
83
|
+
|
84
|
+
<footer>
|
85
|
+
<p><small class="license">This document is licensed using a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License.</small></p>
|
86
|
+
</footer>
|
87
|
+
</div>
|
88
|
+
</body>
|
89
|
+
</html>
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resumer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vito Tardia
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-12-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: commander
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: kramdown
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pdfkit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '13.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '13.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
description: |
|
98
|
+
Write your resume once in machine-readable YAML format and export it
|
99
|
+
to HTML or PDF using custom themes.
|
100
|
+
email:
|
101
|
+
- vito@tardia.me
|
102
|
+
executables:
|
103
|
+
- resumer
|
104
|
+
extensions: []
|
105
|
+
extra_rdoc_files:
|
106
|
+
- README.md
|
107
|
+
- CHANGELOG.md
|
108
|
+
- LICENSE.txt
|
109
|
+
files:
|
110
|
+
- CHANGELOG.md
|
111
|
+
- LICENSE.txt
|
112
|
+
- README.md
|
113
|
+
- bin/resumer
|
114
|
+
- lib/resumer.rb
|
115
|
+
- lib/resumer/commands/export.rb
|
116
|
+
- lib/resumer/commands/init.rb
|
117
|
+
- lib/resumer/commands/usage.rb
|
118
|
+
- lib/resumer/commands/validate.rb
|
119
|
+
- lib/resumer/error.rb
|
120
|
+
- lib/resumer/export.rb
|
121
|
+
- lib/resumer/info.rb
|
122
|
+
- templates/default.yml
|
123
|
+
- themes/default/css/pdf.styles.css
|
124
|
+
- themes/default/css/styles.css
|
125
|
+
- themes/default/index.html
|
126
|
+
homepage: https://github.com/vtardia/resumer
|
127
|
+
licenses:
|
128
|
+
- MIT
|
129
|
+
metadata:
|
130
|
+
allowed_push_host: https://rubygems.org
|
131
|
+
homepage_uri: https://github.com/vtardia/resumer
|
132
|
+
source_code_uri: https://github.com/vtardia/resumer
|
133
|
+
changelog_uri: https://github.com/vtardia/resumer/blob/main/CHANGELOG.md
|
134
|
+
post_install_message:
|
135
|
+
rdoc_options:
|
136
|
+
- "--title"
|
137
|
+
- Resumer - beautiful CVs from YAML files
|
138
|
+
- "--main"
|
139
|
+
- README.md
|
140
|
+
- "--line-numbers"
|
141
|
+
- "--inline-source"
|
142
|
+
- "--quiet"
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - "~>"
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '2.7'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubygems_version: 3.1.6
|
157
|
+
signing_key:
|
158
|
+
specification_version: 4
|
159
|
+
summary: Resumer creates beautiful CVs from YAML files
|
160
|
+
test_files: []
|