resume-stylist 0.1.0 → 0.2.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 +4 -4
- data/README.md +61 -17
- data/data/resume-stylist/templates/theme.html.liquid +509 -7
- data/lib/resume-stylist/resume/json.rb +22 -12
- data/lib/resume-stylist/resume.rb +10 -8
- data/lib/resume-stylist/theme/normalize_css.rb +31 -0
- data/lib/resume-stylist/theme/ordinal_date.rb +46 -0
- data/lib/resume-stylist/theme.rb +14 -1
- data/lib/resume-stylist/version.rb +1 -1
- data/lib/resume-stylist.rb +3 -0
- metadata +4 -4
- data/bin/console +0 -14
- data/bin/setup +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2298b819f3fe882758cdc3db17a55c2ac89496c0
|
|
4
|
+
data.tar.gz: 047b6d97566a5373defcc80209bca8efbde744e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9fa53127f95fb8cde42b0f56a6b29eeb2e23809b46ddd9f911ef7580d6504fa8b6cd87da507419cea93df4ab7ee04d5a9964e8589bbf215eb154d32ef348d7ed
|
|
7
|
+
data.tar.gz: d1da79c170544948681f7c05c69ab4fd8c8e229bb0e498148a3da57e91c59b6d9622e393a6b966bb49dd36f157e0f6eb8930249b3b458d5280a1c51881ed54e7
|
data/README.md
CHANGED
|
@@ -1,39 +1,83 @@
|
|
|
1
1
|
# Resume Stylist
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Note**: Currently in very early development.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Small framework for making CV/resume themes using [Liquid](https://github.com/Shopify/liquid/wiki/liquid-for-designers) and [Sass](http://sass-lang.com/).
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
~~~sh
|
|
10
|
+
$ gem install resume-stylist
|
|
11
|
+
~~~
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
gem 'resume-stylist'
|
|
13
|
-
```
|
|
13
|
+
Also, since this uses PDFKit, you'll need to make sure you have `wkhtmltopdf` installed. On Arch it's as simple as running `sudo pacman -S wkhtmltopdf`, but consult their documentation for recommendations regarding other platforms: https://github.com/pdfkit/pdfkit#wkhtmltopdf
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Features
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Here are some cool features:
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
- Uses Liquid as the templating language
|
|
20
|
+
- Support for inline Sass and SCSS by using `<style type="text/sass">` and `<style type="text/scss">`
|
|
21
|
+
- Support for multiple Resume formats (JSON and XML currently), but you can BYOF (Bring Your Own Format)!
|
|
22
|
+
- Export to PDF
|
|
22
23
|
|
|
23
24
|
## Usage
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
### Tool usage
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
Create a new theme and a JSON resume template:
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
~~~sh
|
|
31
|
+
$ resume-stylist --new-theme fancy.html.liquid --new-resume john_doe.json
|
|
32
|
+
~~~
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
Build your theme as a PDF:
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
~~~sh
|
|
37
|
+
$ resume-stylist build john_doe.json john_doe.pdf
|
|
38
|
+
~~~
|
|
39
|
+
|
|
40
|
+
~~~sh
|
|
41
|
+
$ resume-stylist build john_doe -I json john_doe.pdf
|
|
42
|
+
~~~
|
|
43
|
+
|
|
44
|
+
Run `resume-stylist --help` for more options!
|
|
45
|
+
|
|
46
|
+
### Library usage
|
|
47
|
+
|
|
48
|
+
#### Registering a custom resume format
|
|
49
|
+
|
|
50
|
+
~~~rb
|
|
51
|
+
module MyResumeFormat
|
|
52
|
+
def self.handles?(resume_format)
|
|
53
|
+
# Handle
|
|
54
|
+
resume_format.to_s =~ /(myformat|myf)/i
|
|
55
|
+
end
|
|
34
56
|
|
|
35
|
-
|
|
57
|
+
def load!(input)
|
|
58
|
+
# input is the resume data
|
|
59
|
+
data = My::Parser.parse(input)
|
|
60
|
+
@data = data.to_h
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
ResumeStylist::Resume.register_handler MyResumeFormat
|
|
65
|
+
~~~
|
|
66
|
+
|
|
67
|
+
**NOTE**: The theme expects `@data` to be a `Hash` with `String` keys.
|
|
68
|
+
|
|
69
|
+
#### Creating a resume (HTML) programmatically:
|
|
70
|
+
|
|
71
|
+
~~~rb
|
|
72
|
+
resume = ResumeStylist::Resume.new(resume_source, :myformat)
|
|
73
|
+
theme = ResumeStylist::Theme.new(theme_source)
|
|
74
|
+
|
|
75
|
+
html = theme.render(resume.data)
|
|
76
|
+
~~~
|
|
77
|
+
|
|
78
|
+
## Contributing
|
|
36
79
|
|
|
80
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/omninonsense/resume-stylist. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
|
37
81
|
|
|
38
82
|
## License
|
|
39
83
|
|
|
@@ -1,20 +1,522 @@
|
|
|
1
|
+
---
|
|
2
|
+
theme:
|
|
3
|
+
author:
|
|
4
|
+
name: Nino Miletich
|
|
5
|
+
url: https://github.com/omninonsense
|
|
6
|
+
---
|
|
1
7
|
<!DOCTYPE html>
|
|
2
8
|
<html>
|
|
3
9
|
<head>
|
|
4
10
|
<meta charset="utf-8">
|
|
5
|
-
<title>{{ name }}'s Resume</title>
|
|
6
|
-
|
|
11
|
+
<title>{{ basics.name }}'s Resume</title>
|
|
12
|
+
<meta name="description" content="{{ basics.summary }}">
|
|
13
|
+
<meta name="generator" content="{{ _generator.name }}/{{ _generator.version }}">
|
|
14
|
+
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,800&subset=latin-ext" rel="stylesheet">
|
|
15
|
+
<link href="https://fonts.googleapis.com/css?family=Passion+One" rel="stylesheet">
|
|
7
16
|
<!-- or type="text/sass" -->
|
|
8
17
|
<style type="text/scss">
|
|
9
|
-
|
|
18
|
+
{% normalize_css inline %}
|
|
19
|
+
|
|
20
|
+
$max_width: 1200px;
|
|
21
|
+
#resume {
|
|
22
|
+
font-family: 'Open Sans', sans-serif;
|
|
23
|
+
color: #222;
|
|
24
|
+
margin: 3ex 1em;
|
|
25
|
+
@media screen and (min-width: $max_width){
|
|
26
|
+
width: 900px;
|
|
27
|
+
margin: 3ex auto;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
small {
|
|
32
|
+
display: block;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#basics {
|
|
36
|
+
.picture {
|
|
37
|
+
$size: 128px;
|
|
38
|
+
display: block;
|
|
39
|
+
width: $size;
|
|
40
|
+
height: $size;
|
|
41
|
+
border-radius: 50%;
|
|
42
|
+
margin: auto;
|
|
43
|
+
box-shadow: 0 0 0 5px #aaa;
|
|
44
|
+
border: 4px solid #fff;
|
|
45
|
+
}
|
|
46
|
+
|
|
10
47
|
h1 {
|
|
11
|
-
|
|
48
|
+
text-align: center;
|
|
49
|
+
font-weight: 300;
|
|
50
|
+
|
|
51
|
+
small {
|
|
52
|
+
color: #aaa;
|
|
53
|
+
font-weight: 100;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
ul.contact {
|
|
58
|
+
text-align: center;
|
|
59
|
+
list-style-type: none;
|
|
60
|
+
padding: 0;
|
|
61
|
+
margin-top: 0;
|
|
62
|
+
|
|
63
|
+
li {
|
|
64
|
+
display: inline-block;
|
|
65
|
+
margin: 0 .5em;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
ul.profiles {
|
|
70
|
+
list-style-type: none;
|
|
71
|
+
padding: 0;
|
|
72
|
+
text-align: center;
|
|
73
|
+
|
|
74
|
+
li {
|
|
75
|
+
display: inline-block;
|
|
76
|
+
margin: 0 1em;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.network-logo {
|
|
80
|
+
width: 22px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
a.profile {
|
|
84
|
+
text-decoration: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.network-logo {
|
|
88
|
+
vertical-align: middle;
|
|
89
|
+
margin-bottom: 3px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.profile-facebook { color: #3C5A99; }
|
|
93
|
+
.profile-github { color: #000000; }
|
|
94
|
+
.profile-twitter { color: #66757f; }
|
|
95
|
+
|
|
96
|
+
.profile-username::before {
|
|
97
|
+
content: "@";
|
|
98
|
+
font-size: 125%;
|
|
99
|
+
position: relative;
|
|
100
|
+
bottom: -1px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
} /* .profiles */
|
|
104
|
+
} /* #basics */
|
|
105
|
+
|
|
106
|
+
/* WKHTML doesn't support flexbox properly */
|
|
107
|
+
|
|
108
|
+
.category {
|
|
109
|
+
/*display: flex;*/
|
|
110
|
+
display: table;
|
|
111
|
+
margin-top: 3ex;
|
|
112
|
+
page-break-before: auto;
|
|
113
|
+
page-break-inside: avoid;
|
|
114
|
+
page-break-after: auto;
|
|
115
|
+
|
|
116
|
+
& > div {
|
|
117
|
+
display: table-cell;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.title {
|
|
121
|
+
/*flex: 0 0 160px;*/
|
|
122
|
+
width: 160px;
|
|
123
|
+
text-align: right;
|
|
124
|
+
border-right: 1px solid #ccc;
|
|
125
|
+
padding: 1em;
|
|
126
|
+
|
|
127
|
+
h2 { font-weight: 100; }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
ul.no-list {
|
|
131
|
+
padding: 0;
|
|
132
|
+
list-style-type: none;
|
|
133
|
+
|
|
134
|
+
& > li {
|
|
135
|
+
margin: 2em;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
ul {
|
|
139
|
+
list-style-type: square;
|
|
140
|
+
li { margin-top: 0.75ex; }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.timespan { color: #999; }
|
|
145
|
+
|
|
146
|
+
.content {
|
|
147
|
+
h3 {
|
|
148
|
+
font-weight: 400;
|
|
149
|
+
margin-top: 0;
|
|
150
|
+
a { color: inherit; }
|
|
151
|
+
.company, .organisation { font-style: italic; }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} /* .category */
|
|
155
|
+
|
|
156
|
+
#skills, #interests {
|
|
157
|
+
h3 {
|
|
158
|
+
margin-bottom: 0;
|
|
159
|
+
margin-top: 1em;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#languages ul { margin: 2em; }
|
|
164
|
+
|
|
165
|
+
.courses, .keywords, .languages {
|
|
166
|
+
list-style-type: none;
|
|
167
|
+
padding: 0;
|
|
168
|
+
|
|
169
|
+
li {
|
|
170
|
+
margin: 0.75ex 0.25em;
|
|
171
|
+
display: inline-block;
|
|
172
|
+
background-color: #eee;
|
|
173
|
+
padding: .5ex .5em;
|
|
174
|
+
border-radius: 10%;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
blockquote
|
|
180
|
+
{
|
|
181
|
+
border-left: 5px solid #ccc;
|
|
182
|
+
background-color: #eee;
|
|
183
|
+
margin: 0;
|
|
184
|
+
width: 100%;
|
|
185
|
+
padding: 1em;
|
|
186
|
+
position: relative;
|
|
187
|
+
|
|
188
|
+
p {
|
|
189
|
+
margin: 0;
|
|
190
|
+
margin-left: 1em;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
cite {
|
|
194
|
+
display: block;
|
|
195
|
+
text-align: right;
|
|
196
|
+
&::before {
|
|
197
|
+
content: "— ";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
&::before {
|
|
202
|
+
font-family: "Passion One", cursive;
|
|
203
|
+
display: block;
|
|
204
|
+
font-size: 500%;
|
|
205
|
+
color: #ccc;
|
|
206
|
+
height: 15px;
|
|
207
|
+
width: 60px;
|
|
208
|
+
float: left;
|
|
209
|
+
content: "“";
|
|
210
|
+
position: relative;
|
|
211
|
+
left: 20px;
|
|
212
|
+
top: -17px;
|
|
12
213
|
}
|
|
13
214
|
}
|
|
215
|
+
|
|
216
|
+
.flair {
|
|
217
|
+
color: #ccc;
|
|
218
|
+
text-align: right;
|
|
219
|
+
|
|
220
|
+
a {
|
|
221
|
+
color: #99b9d4;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
14
225
|
</style>
|
|
15
226
|
</head>
|
|
16
|
-
<body
|
|
17
|
-
<
|
|
18
|
-
|
|
227
|
+
<body id="resume">
|
|
228
|
+
<section id="basics">
|
|
229
|
+
{% unless basics.picture == "" %}
|
|
230
|
+
<div class="group">
|
|
231
|
+
<img class="picture" src="{{ basics.picture }}" alt="Picture of {{ basics.name }}" />
|
|
232
|
+
</div>
|
|
233
|
+
{% endunless %}
|
|
234
|
+
<div class="group">
|
|
235
|
+
<h1>{{ basics.name }}<small>{{ basics.label }}</small></h1>
|
|
236
|
+
<ul class="contact">
|
|
237
|
+
<li><a href="mailto:{{ basics.email }}" class="contact-info email">{{ basics.email }}</a></li>
|
|
238
|
+
<li><span class="contact-info phone">{{ basics.phone }}</span></li>
|
|
239
|
+
</ul>
|
|
240
|
+
<ul class="profiles">
|
|
241
|
+
{% unless basics.profiles == empty %}
|
|
242
|
+
{% for profile in basics.profiles %}
|
|
243
|
+
<li>
|
|
244
|
+
<a class="profile profile-{{ profile.network | downcase }}" href="{{ profile.url }}">
|
|
245
|
+
|
|
246
|
+
{% case profile.network %}
|
|
247
|
+
{% when "Facebook" %}
|
|
248
|
+
<!-- The below SVG was taken from Wikipedia. -->
|
|
249
|
+
<svg version="1.1" id="Facebook" class="network-logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 266.893 266.895" viewBox="0 0 266.893 266.895" xml:space="preserve">
|
|
250
|
+
<path id="Blue_1_" fill="#3C5A99" d="M248.082,262.307c7.854,0,14.223-6.369,14.223-14.225V18.812 c0-7.857-6.368-14.224-14.223-14.224H18.812c-7.857,0-14.224,6.367-14.224,14.224v229.27c0,7.855,6.366,14.225,14.224,14.225 H248.082z"/>
|
|
251
|
+
<path id="f" fill="#FFFFFF" d="M182.409,262.307v-99.803h33.499l5.016-38.895h-38.515V98.777c0-11.261,3.127-18.935,19.275-18.935 l20.596-0.009V45.045c-3.562-0.474-15.788-1.533-30.012-1.533c-29.695,0-50.025,18.126-50.025,51.413v28.684h-33.585v38.895h33.585 v99.803H182.409z"/>
|
|
252
|
+
</svg>
|
|
253
|
+
{% when "GitHub" %}
|
|
254
|
+
<!-- The below SVG was taken from Wikipedia. -->
|
|
255
|
+
<svg version="1.1" id="GitHub" class="network-logo" viewbox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" xml:space="preserve">
|
|
256
|
+
<path d="M512 0C229.25 0 0 229.25 0 512c0 226.25 146.688 418.125 350.156 485.812 25.594 4.688 34.938-11.125 34.938-24.625 0-12.188-0.469-52.562-0.719-95.312C242 908.812 211.906 817.5 211.906 817.5c-23.312-59.125-56.844-74.875-56.844-74.875-46.531-31.75 3.53-31.125 3.53-31.125 51.406 3.562 78.47 52.75 78.47 52.75 45.688 78.25 119.875 55.625 149 42.5 4.654-33 17.904-55.625 32.5-68.375C304.906 725.438 185.344 681.5 185.344 485.312c0-55.938 19.969-101.562 52.656-137.406-5.219-13-22.844-65.094 5.062-135.562 0 0 42.938-13.75 140.812 52.5 40.812-11.406 84.594-17.031 128.125-17.219 43.5 0.188 87.312 5.875 128.188 17.281 97.688-66.312 140.688-52.5 140.688-52.5 28 70.531 10.375 122.562 5.125 135.5 32.812 35.844 52.625 81.469 52.625 137.406 0 196.688-119.75 240-233.812 252.688 18.438 15.875 34.75 47 34.75 94.75 0 68.438-0.688 123.625-0.688 140.5 0 13.625 9.312 29.562 35.25 24.562C877.438 930 1024 738.125 1024 512 1024 229.25 794.75 0 512 0z" />
|
|
257
|
+
</svg>
|
|
258
|
+
{% when "Twitter" %}
|
|
259
|
+
<!-- The below SVG was taken from Wikipedia. -->
|
|
260
|
+
<svg id="Twitter" class="network-logo" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewbox="0 0 182.26 148.12" version="1.1" xmlns:cc="http://creativecommons.org/ns#" viewBox="0 0 182.66667 150.66667" xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata id="metadata8"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><defs id="defs6"><clipPath id="clipPath20" clipPathUnits="userSpaceOnUse"><path id="path18" d="m0 10.012h1366.9v1110.9h-1366.9z"/></clipPath></defs><g id="g10" transform="matrix(1.3333 0 0 -1.3333 0 150.67)"><g id="g12" transform="scale(.1)"><g id="g14"><g id="g16" clip-path="url(#clipPath20)"><path id="path22" d="m1366.9 989.39c-50.27-22.309-104.33-37.387-161.05-44.18 57.89 34.723 102.34 89.679 123.28 155.15-54.18-32.15-114.18-55.47-178.09-68.04-51.13 54.49-124.02 88.55-204.68 88.55-154.89 0-280.43-125.55-280.43-280.43 0-21.988 2.457-43.398 7.258-63.91-233.08 11.68-439.72 123.36-578.04 293.01-24.141-41.4-37.969-89.567-37.969-140.97 0-97.308 49.489-183.13 124.76-233.44-45.969 1.437-89.218 14.058-127.03 35.078-0.043-1.18-0.043-2.348-0.043-3.52 0-135.9 96.68-249.22 224.96-275-23.512-6.41-48.281-9.8-73.86-9.8-18.089 0-35.628 1.711-52.781 5 35.711-111.41 139.26-192.5 262-194.77-96.058-75.23-216.96-120.04-348.36-120.04-22.621 0-44.961 1.332-66.918 3.91 124.16-79.568 271.55-125.98 429.94-125.98 515.82 0 797.86 427.31 797.86 797.93 0 12.153-0.28 24.223-0.79 36.25 54.77 39.571 102.31 88.95 139.93 145.2" fill="#55ACEE"/></g></g></g></g></svg>
|
|
261
|
+
{% endcase %}
|
|
262
|
+
|
|
263
|
+
<span class="profile-username">{{ profile.username }}</span>
|
|
264
|
+
</a>
|
|
265
|
+
</li>
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
{% endfor %}
|
|
269
|
+
{% endunless %}
|
|
270
|
+
</ul>
|
|
271
|
+
</div>
|
|
272
|
+
</section>
|
|
273
|
+
|
|
274
|
+
{% unless work == empty %}
|
|
275
|
+
<section id="work" class="category">
|
|
276
|
+
<div class="title">
|
|
277
|
+
<h2>Work Experience</h2>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="content">
|
|
280
|
+
<ul class="no-list">
|
|
281
|
+
{% for experience in work %}
|
|
282
|
+
<li>
|
|
283
|
+
<div class="meta">
|
|
284
|
+
<span class="timespan">
|
|
285
|
+
<time datetime="{{experience.startdate}}">{{ experience.startdate | date: "%Y" }}</time>
|
|
286
|
+
–
|
|
287
|
+
{% if experience.enddate == nil %}
|
|
288
|
+
current
|
|
289
|
+
{% else %}
|
|
290
|
+
<time datetime="{{ experience.enddate }}">{{ experience.enddate | date: "%Y" }}</time>
|
|
291
|
+
{% endif %}
|
|
292
|
+
</span>
|
|
293
|
+
<h3>{{ experience.position }} at <span class="company">{% if experience.website == "" %}{{ experience.company }}{% else %}<a href="{{experience.website}}">{{ experience.company }}</a>{% endif %}</span></h3>
|
|
294
|
+
</div>
|
|
295
|
+
<p>{{ experience.summary }}</p>
|
|
296
|
+
{% unless experience.highlights == empty %}
|
|
297
|
+
<ul class="highlights">
|
|
298
|
+
{% for highlight in experience.highlights %}
|
|
299
|
+
<li>{{ highlight }}</li>
|
|
300
|
+
{% endfor %}
|
|
301
|
+
</ul>
|
|
302
|
+
{%endunless%}
|
|
303
|
+
</li>
|
|
304
|
+
{% endfor %}
|
|
305
|
+
</ul>
|
|
306
|
+
</div>
|
|
307
|
+
</section>
|
|
308
|
+
{% endunless %}
|
|
309
|
+
|
|
310
|
+
{% unless volunteer == empty %}
|
|
311
|
+
<section id="volunteer" class="category">
|
|
312
|
+
<div class="title">
|
|
313
|
+
<h2>Volunteering</h2>
|
|
314
|
+
</div>
|
|
315
|
+
<div class="content">
|
|
316
|
+
<ul class="no-list">
|
|
317
|
+
{% for experience in volunteer %}
|
|
318
|
+
<li>
|
|
319
|
+
<div class="meta">
|
|
320
|
+
<span class="timespan">
|
|
321
|
+
<time datetime="{{experience.startdate}}">{{ experience.startdate | date: "%Y" }}</time>
|
|
322
|
+
–
|
|
323
|
+
{% if experience.enddate == nil %}
|
|
324
|
+
current
|
|
325
|
+
{% else %}
|
|
326
|
+
<time datetime="{{ experience.enddate }}">{{ experience.enddate | date: "%Y" }}</time>
|
|
327
|
+
{% endif %}
|
|
328
|
+
</span>
|
|
329
|
+
<h3>{{ experience.position }} at <span class="organisation">{% if experience.website == "" %}{{ experience.organization }}{% else %}<a href="{{experience.website}}">{{ experience.organization }}</a>{% endif %}</span></h3>
|
|
330
|
+
</div>
|
|
331
|
+
<p>{{ experience.summary }}</p>
|
|
332
|
+
{% unless experience.highlights == empty %}
|
|
333
|
+
<ul class="highlights">
|
|
334
|
+
{% for highlight in experience.highlights %}
|
|
335
|
+
<li>{{ highlight }}</li>
|
|
336
|
+
{% endfor %}
|
|
337
|
+
</ul>
|
|
338
|
+
{%endunless%}
|
|
339
|
+
</li>
|
|
340
|
+
{% endfor %}
|
|
341
|
+
</ul>
|
|
342
|
+
</div>
|
|
343
|
+
</section>
|
|
344
|
+
{% endunless %}
|
|
345
|
+
|
|
346
|
+
{% unless education == empty %}
|
|
347
|
+
<section id="education" class="category">
|
|
348
|
+
<div class="title">
|
|
349
|
+
<h2>Education</h2>
|
|
350
|
+
</div>
|
|
351
|
+
<div class="content">
|
|
352
|
+
<ul class="no-list">
|
|
353
|
+
{% for edu in education %}
|
|
354
|
+
<li>
|
|
355
|
+
<span class="timespan">
|
|
356
|
+
<time datetime="{{edu.startdate}}">{{ edu.startdate | date: "%Y" }}</time>
|
|
357
|
+
–
|
|
358
|
+
{% if edu.enddate == nil %}
|
|
359
|
+
current
|
|
360
|
+
{% else %}
|
|
361
|
+
<time datetime="{{ edu.enddate }}">{{ edu.enddate | date: "%Y" }}</time>
|
|
362
|
+
{% endif %}
|
|
363
|
+
</span>
|
|
364
|
+
<h3>{{ edu.area }}
|
|
365
|
+
<small>
|
|
366
|
+
{{ edu.institution }}
|
|
367
|
+
</small>
|
|
368
|
+
</h3>
|
|
369
|
+
{% unless edu.gpa == empty %} <p><b>GPA: </b> {{ edu.gpa }}</p> {% endunless %}
|
|
370
|
+
{% unless edu.courses == empty %}
|
|
371
|
+
<ul class="courses">
|
|
372
|
+
{% for course in edu.courses %}
|
|
373
|
+
<li>{{ course }}</li>
|
|
374
|
+
{% endfor %}
|
|
375
|
+
</ul>
|
|
376
|
+
{%endunless%}
|
|
377
|
+
</li>
|
|
378
|
+
{% endfor %}
|
|
379
|
+
</ul>
|
|
380
|
+
</div>
|
|
381
|
+
</section>
|
|
382
|
+
{% endunless %}
|
|
383
|
+
|
|
384
|
+
{% unless skills == empty %}
|
|
385
|
+
<section id="skills" class="category">
|
|
386
|
+
<div class="title">
|
|
387
|
+
<h2>Skills</h2>
|
|
388
|
+
</div>
|
|
389
|
+
<div class="content">
|
|
390
|
+
<ul class="no-list">
|
|
391
|
+
{% for skill in skills %}
|
|
392
|
+
<li>
|
|
393
|
+
<h3>{{ skill.name }}<small>{{ skill.level }}</small></h3>
|
|
394
|
+
{% unless skill.keywords == empty %}
|
|
395
|
+
<ul class="keywords">
|
|
396
|
+
{% for keyword in skill.keywords %}
|
|
397
|
+
<li>{{ keyword }}</li>
|
|
398
|
+
{% endfor %}
|
|
399
|
+
</ul>
|
|
400
|
+
{%endunless%}
|
|
401
|
+
</li>
|
|
402
|
+
{% endfor %}
|
|
403
|
+
</ul>
|
|
404
|
+
</div>
|
|
405
|
+
</section>
|
|
406
|
+
{% endunless %}
|
|
407
|
+
|
|
408
|
+
{% unless awards == empty %}
|
|
409
|
+
<section id="awards" class="category">
|
|
410
|
+
<div class="title">
|
|
411
|
+
<h2>Awards</h2>
|
|
412
|
+
</div>
|
|
413
|
+
<div class="content">
|
|
414
|
+
<ul class="no-list">
|
|
415
|
+
{% for award in awards %}
|
|
416
|
+
<li>
|
|
417
|
+
<div class="meta">
|
|
418
|
+
<span class="timespan"><time datetime="{{ award.date }}">{{ award.date | ordinal_date }}</time></span>
|
|
419
|
+
<h3>{{ award.title }}<small>Awarded by <span class="awarder">{{ award.awarder }}</span></small></h3>
|
|
420
|
+
</div>
|
|
421
|
+
<p class="summary">{{ award.summary }}</p>
|
|
422
|
+
</li>
|
|
423
|
+
{% endfor %}
|
|
424
|
+
</ul>
|
|
425
|
+
</div>
|
|
426
|
+
</section>
|
|
427
|
+
{% endunless %}
|
|
428
|
+
|
|
429
|
+
{% unless publications == empty %}
|
|
430
|
+
<section id="publications" class="category">
|
|
431
|
+
<div class="title">
|
|
432
|
+
<h2>Publications</h2>
|
|
433
|
+
</div>
|
|
434
|
+
<div class="content">
|
|
435
|
+
<ul class="no-list">
|
|
436
|
+
{% for pub in publications %}
|
|
437
|
+
<li>
|
|
438
|
+
<div class="meta">
|
|
439
|
+
<span class="timespan"><time datetime="{{ pub.releasedate }}">{{ pub.releasedate | ordinal_date }}</time></span>
|
|
440
|
+
<h3>
|
|
441
|
+
{% if pub.website == empty %}
|
|
442
|
+
{{ pub.name }}
|
|
443
|
+
{% else %}
|
|
444
|
+
<a href="{{ pub.website }}">{{ pub.name }}</a>
|
|
445
|
+
{% endif %}
|
|
446
|
+
<small>Published by <span class="publisher">{{ pub.publisher }}</span></small>
|
|
447
|
+
</h3>
|
|
448
|
+
</div>
|
|
449
|
+
<p class="summary">{{ pub.summary }}</p>
|
|
450
|
+
</li>
|
|
451
|
+
{% endfor %}
|
|
452
|
+
</ul>
|
|
453
|
+
</div>
|
|
454
|
+
</section>
|
|
455
|
+
{% endunless %}
|
|
456
|
+
|
|
457
|
+
{% unless interests == empty %}
|
|
458
|
+
<section id="interests" class="category">
|
|
459
|
+
<div class="title">
|
|
460
|
+
<h2>Interests</h2>
|
|
461
|
+
</div>
|
|
462
|
+
<div class="content">
|
|
463
|
+
<ul class="no-list">
|
|
464
|
+
{% for interest in interests %}
|
|
465
|
+
<li>
|
|
466
|
+
<h3>{{ interest.name }}</h3>
|
|
467
|
+
{% unless interest.keywords == empty %}
|
|
468
|
+
<ul class="keywords">
|
|
469
|
+
{% for keyword in interest.keywords %}
|
|
470
|
+
<li>{{ keyword }}</li>
|
|
471
|
+
{% endfor %}
|
|
472
|
+
</ul>
|
|
473
|
+
{%endunless%}
|
|
474
|
+
</li>
|
|
475
|
+
{% endfor %}
|
|
476
|
+
</ul>
|
|
477
|
+
</div>
|
|
478
|
+
</section>
|
|
479
|
+
{% endunless %}
|
|
480
|
+
|
|
481
|
+
{% unless references == empty %}
|
|
482
|
+
<section id="references" class="category">
|
|
483
|
+
<div class="title">
|
|
484
|
+
<h2>References</h2>
|
|
485
|
+
</div>
|
|
486
|
+
<div class="content">
|
|
487
|
+
<ul class="no-list">
|
|
488
|
+
{% for reference in references %}
|
|
489
|
+
<li>
|
|
490
|
+
<blockquote>
|
|
491
|
+
<div class="fancy-quote"></div>
|
|
492
|
+
<p>{{ reference.reference }}</p>
|
|
493
|
+
<cite>{{ reference.name }}</cite>
|
|
494
|
+
</blockquote>
|
|
495
|
+
</li>
|
|
496
|
+
{% endfor %}
|
|
497
|
+
</ul>
|
|
498
|
+
</div>
|
|
499
|
+
</section>
|
|
500
|
+
{% endunless %}
|
|
501
|
+
|
|
502
|
+
{% unless languages == empty %}
|
|
503
|
+
<section id="languages" class="category">
|
|
504
|
+
<div class="title">
|
|
505
|
+
<h2>Languages</h2>
|
|
506
|
+
</div>
|
|
507
|
+
<div class="content">
|
|
508
|
+
<ul class="languages">
|
|
509
|
+
{% for language in languages %}
|
|
510
|
+
<li>{{ language.name }}<small>{{ language.level }}</small></li>
|
|
511
|
+
{% endfor %}
|
|
512
|
+
</ul>
|
|
513
|
+
</div>
|
|
514
|
+
</section>
|
|
515
|
+
{% endunless %}
|
|
516
|
+
|
|
517
|
+
<div class="flair">
|
|
518
|
+
CV generated using <a href="https://github.com/omninonsense/resume-stylist">{{ _generator.name }} v{{ _generator.version }}</a>, resume theme by <a href="{{ frontmatter.theme.author.url }}">{{ frontmatter.theme.author.name }}</a>.
|
|
519
|
+
</div>
|
|
520
|
+
|
|
19
521
|
</body>
|
|
20
522
|
</html>
|
|
@@ -11,31 +11,41 @@ module ResumeStylist
|
|
|
11
11
|
return date
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
def self.downcase_keys_helper(h)
|
|
15
|
+
case h
|
|
16
|
+
when Hash
|
|
17
|
+
h = h.dup
|
|
18
|
+
h.keys.each do |key|
|
|
19
|
+
new_key = key.downcase
|
|
20
|
+
val = h.delete(key)
|
|
21
|
+
h[new_key] = downcase_keys_helper(val)
|
|
22
|
+
end
|
|
23
|
+
when Array
|
|
24
|
+
return h.map {|e| downcase_keys_helper(e) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
return h
|
|
28
|
+
end
|
|
29
|
+
|
|
14
30
|
def self.handles?(resume_format)
|
|
15
31
|
resume_format.to_s.downcase == "json"
|
|
16
32
|
end
|
|
17
33
|
|
|
18
34
|
def load!(input)
|
|
19
|
-
data = Yajl::Parser.parse(input
|
|
20
|
-
# data = Yajl::Parser.parse(input, symbolize_keys: true)
|
|
21
|
-
|
|
22
|
-
# Copy the data data from the "basics" field into top level
|
|
23
|
-
# and remove the basics group from the hash
|
|
24
|
-
data["basics"].each_pair {|k, v| data[k] = v }
|
|
25
|
-
data.delete "basics"
|
|
26
|
-
|
|
27
|
-
@data = data
|
|
35
|
+
data = Yajl::Parser.parse(input)
|
|
28
36
|
|
|
29
37
|
# Fix dates, since they're just strings now
|
|
30
|
-
[
|
|
38
|
+
[ data["work"], data["volunteer"], data["education"] ].each do |set|
|
|
31
39
|
set.each do |e|
|
|
32
40
|
e["startDate"] = JSONResume.date_helper(e["startDate"])
|
|
33
41
|
e["endDate"] = JSONResume.date_helper(e["endDate"])
|
|
34
42
|
end
|
|
35
43
|
end
|
|
36
44
|
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
data["publications"].each {|e| e["releaseDate"] = JSONResume.date_helper(e["releaseDate"]) }
|
|
46
|
+
data["awards"].each {|e| e["date"] = JSONResume.date_helper(e["date"]) }
|
|
47
|
+
|
|
48
|
+
@data = JSONResume.downcase_keys_helper(data)
|
|
39
49
|
end
|
|
40
50
|
end
|
|
41
51
|
end
|
|
@@ -14,13 +14,15 @@ module ResumeStylist
|
|
|
14
14
|
|
|
15
15
|
def initialize(input, resume_format)
|
|
16
16
|
@data = {
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
"basics" => {
|
|
18
|
+
"name" => nil,
|
|
19
|
+
"label" => nil,
|
|
20
|
+
"picture" => nil,
|
|
21
|
+
"email" => nil,
|
|
22
|
+
"phone" => nil,
|
|
23
|
+
"website" => nil,
|
|
24
|
+
"summary" => nil
|
|
25
|
+
},
|
|
24
26
|
|
|
25
27
|
"location" => {
|
|
26
28
|
"address" => nil,
|
|
@@ -30,7 +32,7 @@ module ResumeStylist
|
|
|
30
32
|
"region" => nil
|
|
31
33
|
},
|
|
32
34
|
|
|
33
|
-
"profiles" => [], # {
|
|
35
|
+
"profiles" => [], # { network, username, url }
|
|
34
36
|
"work" => [], # { organisation, position, website, summary, highlights => [], startDate, endDate }
|
|
35
37
|
"volunteer" => [], # { organisation, position, website, summary, highlights => [], startDate, endDate }
|
|
36
38
|
"education" => [], # { institution, area, studyType, grade, courses => [], startDate, endDate }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module ResumeStylist
|
|
2
|
+
class NormalizeCSS < Liquid::Tag
|
|
3
|
+
|
|
4
|
+
NormalizeCSS_URI = URI("https://necolas.github.io/normalize.css/latest/normalize.css")
|
|
5
|
+
|
|
6
|
+
def initialize(tag_name, tokens, liq)
|
|
7
|
+
if tokens.include? "inline"
|
|
8
|
+
req = Net::HTTP::Get.new(NormalizeCSS_URI.request_uri)
|
|
9
|
+
|
|
10
|
+
http = Net::HTTP.new(NormalizeCSS_URI.host, NormalizeCSS_URI.port)
|
|
11
|
+
http.use_ssl = (NormalizeCSS_URI.scheme == "https")
|
|
12
|
+
|
|
13
|
+
response = http.request(req)
|
|
14
|
+
|
|
15
|
+
if response.code == "200"
|
|
16
|
+
@content = response.body
|
|
17
|
+
else
|
|
18
|
+
@content = "/*! ERROR: Request for `#{NormalizeCSS_URI}` returned #{response.code}! Please report this bug at https://github.com/omninonsense/resume-stylist/issues/new */"
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
@content = %Q{<link rel="stylesheet" href="#{NormalizeCSS_URI}" media="screen">}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render(context)
|
|
26
|
+
@content
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Liquid::Template.register_tag('normalize_css', ResumeStylist::NormalizeCSS)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module ResumeStylist
|
|
2
|
+
module OrdinalDateFilter
|
|
3
|
+
# safe true
|
|
4
|
+
|
|
5
|
+
# # For languages with more diverse ordinals (if such even exist)
|
|
6
|
+
# Ordinals = %w{
|
|
7
|
+
# st nd rd th th th th th th th
|
|
8
|
+
# th th th th th th th th th th
|
|
9
|
+
# st nd rd th th th th th th th
|
|
10
|
+
# st
|
|
11
|
+
# }
|
|
12
|
+
|
|
13
|
+
# For English
|
|
14
|
+
Ordinals = Hash.new do |h, k|
|
|
15
|
+
if (11..13).cover? k
|
|
16
|
+
h[k] = "th"
|
|
17
|
+
elsif 1 == (k % 10)
|
|
18
|
+
h[k] = "st"
|
|
19
|
+
elsif 2 == (k % 10)
|
|
20
|
+
h[k] = "nd"
|
|
21
|
+
elsif 3 == (k % 10)
|
|
22
|
+
h[k] = "rd"
|
|
23
|
+
else
|
|
24
|
+
h[k] = "th"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ordinal_date(input)
|
|
29
|
+
_day = input.mday
|
|
30
|
+
_ordinal = Ordinals[_day]
|
|
31
|
+
_month = Date::MONTHNAMES[input.month]
|
|
32
|
+
_year = input.year
|
|
33
|
+
|
|
34
|
+
day = %Q|<span class="day">#{_day}</span>|
|
|
35
|
+
ordinal = %Q|<span class="ordinal">#{_ordinal}</span>|
|
|
36
|
+
month = %Q|<span class="month">#{_month}</span>|
|
|
37
|
+
year = %Q|<span class="year">#{_year}</span>|
|
|
38
|
+
|
|
39
|
+
html = "#{day}#{ordinal} #{month} #{year}"
|
|
40
|
+
|
|
41
|
+
return html
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Liquid::Template.register_filter(ResumeStylist::OrdinalDateFilter)
|
data/lib/resume-stylist/theme.rb
CHANGED
|
@@ -13,7 +13,17 @@ module ResumeStylist
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def render(resume_data)
|
|
16
|
-
|
|
16
|
+
meta_data = {
|
|
17
|
+
"frontmatter" => @frontmatter,
|
|
18
|
+
|
|
19
|
+
"_generator" => {
|
|
20
|
+
"name" => "resume-stylist",
|
|
21
|
+
"version" => ResumeStylist::VERSION
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
ctx = resume_data.merge(meta_data)
|
|
26
|
+
@resume = @template.render(ctx)
|
|
17
27
|
post_process!
|
|
18
28
|
|
|
19
29
|
@resume
|
|
@@ -47,3 +57,6 @@ module ResumeStylist
|
|
|
47
57
|
end
|
|
48
58
|
end
|
|
49
59
|
end
|
|
60
|
+
|
|
61
|
+
require "resume-stylist/theme/normalize_css"
|
|
62
|
+
require "resume-stylist/theme/ordinal_date"
|
data/lib/resume-stylist.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resume-stylist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nino Miletich
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2016-
|
|
11
|
+
date: 2016-07-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -137,8 +137,6 @@ files:
|
|
|
137
137
|
- LICENSE.txt
|
|
138
138
|
- README.md
|
|
139
139
|
- Rakefile
|
|
140
|
-
- bin/console
|
|
141
|
-
- bin/setup
|
|
142
140
|
- data/resume-stylist/templates/resume.json
|
|
143
141
|
- data/resume-stylist/templates/theme.html.liquid
|
|
144
142
|
- exe/resume-stylist
|
|
@@ -147,6 +145,8 @@ files:
|
|
|
147
145
|
- lib/resume-stylist/resume/json.rb
|
|
148
146
|
- lib/resume-stylist/resume/xml.rb
|
|
149
147
|
- lib/resume-stylist/theme.rb
|
|
148
|
+
- lib/resume-stylist/theme/normalize_css.rb
|
|
149
|
+
- lib/resume-stylist/theme/ordinal_date.rb
|
|
150
150
|
- lib/resume-stylist/version.rb
|
|
151
151
|
- resume-stylist.gemspec
|
|
152
152
|
homepage: https://github.com/omninonsense/resume-stylist
|
data/bin/console
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "resume-stylist"
|
|
5
|
-
|
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
-
|
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
-
# require "pry"
|
|
11
|
-
# Pry.start
|
|
12
|
-
|
|
13
|
-
require "irb"
|
|
14
|
-
IRB.start
|