md_resume 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5e17377e32b45ab6b2127c36392a12d26ee7b0b3b0b6f078b3fdf07e4f9b2663
4
+ data.tar.gz: 946d865aae51c40a864f70112be7d04797b40292fd35c2f2deaf01c672d3b30d
5
+ SHA512:
6
+ metadata.gz: d7abc200a53f58087cdcb9bebc2a4f08ac9a6890a78f4c87b0ee132d4f6f354350bbb0e16c183f7db71d55aef067cc09d89465e12aaed8a25eb9a2e600c08543
7
+ data.tar.gz: c13a6605ec135d342b46bdf91eba7c8307be5e1f2c60cb32cabab4740a23f578dae9406534f7280647044f07262abf874c1ecec727c305d086ef84219b7b904a
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
3
+ ruby_version: 2.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-01-02
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in md_resume.gemspec
6
+ gemspec
7
+
8
+ gem 'minitest', '~> 5.0'
9
+ gem 'rake', '~> 13.0'
10
+ gem 'standard', '~> 1.3'
data/Gemfile.lock ADDED
@@ -0,0 +1,124 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ md_resume (0.1.0)
5
+ foreman (~> 0.87)
6
+ guard (~> 2.18)
7
+ guard-livereload (~> 2.5)
8
+ guard-process (~> 1.0)
9
+ guard-shell (~> 0.7)
10
+ kramdown (~> 2.4)
11
+ webrick (~> 1.8)
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ ast (2.4.2)
17
+ coderay (1.1.3)
18
+ em-websocket (0.5.3)
19
+ eventmachine (>= 0.12.9)
20
+ http_parser.rb (~> 0)
21
+ eventmachine (1.2.7)
22
+ ffi (1.16.3)
23
+ foreman (0.87.2)
24
+ formatador (1.1.0)
25
+ guard (2.18.1)
26
+ formatador (>= 0.2.4)
27
+ listen (>= 2.7, < 4.0)
28
+ lumberjack (>= 1.0.12, < 2.0)
29
+ nenv (~> 0.1)
30
+ notiffany (~> 0.0)
31
+ pry (>= 0.13.0)
32
+ shellany (~> 0.0)
33
+ thor (>= 0.18.1)
34
+ guard-compat (1.2.1)
35
+ guard-livereload (2.5.2)
36
+ em-websocket (~> 0.5)
37
+ guard (~> 2.8)
38
+ guard-compat (~> 1.0)
39
+ multi_json (~> 1.8)
40
+ guard-process (1.2.1)
41
+ guard-compat (~> 1.2, >= 1.2.1)
42
+ spoon (~> 0.0.1)
43
+ guard-shell (0.7.2)
44
+ guard (>= 2.0.0)
45
+ guard-compat (~> 1.0)
46
+ http_parser.rb (0.8.0)
47
+ json (2.7.1)
48
+ kramdown (2.4.0)
49
+ rexml
50
+ language_server-protocol (3.17.0.3)
51
+ lint_roller (1.1.0)
52
+ listen (3.8.0)
53
+ rb-fsevent (~> 0.10, >= 0.10.3)
54
+ rb-inotify (~> 0.9, >= 0.9.10)
55
+ lumberjack (1.2.10)
56
+ method_source (1.0.0)
57
+ minitest (5.20.0)
58
+ multi_json (1.15.0)
59
+ nenv (0.3.0)
60
+ notiffany (0.1.3)
61
+ nenv (~> 0.1)
62
+ shellany (~> 0.0)
63
+ parallel (1.24.0)
64
+ parser (3.2.2.4)
65
+ ast (~> 2.4.1)
66
+ racc
67
+ pry (0.14.2)
68
+ coderay (~> 1.1)
69
+ method_source (~> 1.0)
70
+ racc (1.7.3)
71
+ rainbow (3.1.1)
72
+ rake (13.1.0)
73
+ rb-fsevent (0.11.2)
74
+ rb-inotify (0.10.1)
75
+ ffi (~> 1.0)
76
+ regexp_parser (2.8.3)
77
+ rexml (3.2.6)
78
+ rubocop (1.59.0)
79
+ json (~> 2.3)
80
+ language_server-protocol (>= 3.17.0)
81
+ parallel (~> 1.10)
82
+ parser (>= 3.2.2.4)
83
+ rainbow (>= 2.2.2, < 4.0)
84
+ regexp_parser (>= 1.8, < 3.0)
85
+ rexml (>= 3.2.5, < 4.0)
86
+ rubocop-ast (>= 1.30.0, < 2.0)
87
+ ruby-progressbar (~> 1.7)
88
+ unicode-display_width (>= 2.4.0, < 3.0)
89
+ rubocop-ast (1.30.0)
90
+ parser (>= 3.2.1.0)
91
+ rubocop-performance (1.20.1)
92
+ rubocop (>= 1.48.1, < 2.0)
93
+ rubocop-ast (>= 1.30.0, < 2.0)
94
+ ruby-progressbar (1.13.0)
95
+ shellany (0.0.1)
96
+ spoon (0.0.6)
97
+ ffi
98
+ standard (1.33.0)
99
+ language_server-protocol (~> 3.17.0.2)
100
+ lint_roller (~> 1.0)
101
+ rubocop (~> 1.59.0)
102
+ standard-custom (~> 1.0.0)
103
+ standard-performance (~> 1.3)
104
+ standard-custom (1.0.2)
105
+ lint_roller (~> 1.0)
106
+ rubocop (~> 1.50)
107
+ standard-performance (1.3.0)
108
+ lint_roller (~> 1.1)
109
+ rubocop-performance (~> 1.20.1)
110
+ thor (1.3.0)
111
+ unicode-display_width (2.5.0)
112
+ webrick (1.8.1)
113
+
114
+ PLATFORMS
115
+ x86_64-linux
116
+
117
+ DEPENDENCIES
118
+ md_resume!
119
+ minitest (~> 5.0)
120
+ rake (~> 13.0)
121
+ standard (~> 1.3)
122
+
123
+ BUNDLED WITH
124
+ 2.4.22
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 YuriBocharov
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,67 @@
1
+ # md_resume
2
+
3
+ https://github.com/elasticspoon/md_resume/assets/14540596/d5034437-2842-4974-bffb-dbeacc742030
4
+
5
+ Write your resume in [Markdown](https://raw.githubusercontent.com/mikepqr/resume.md/main/resume.md), style it with [CSS](resume.css), output to [HTML](resume.html) and [PDF](resume.pdf). Open a your resume in the browser and watch it update live with changes made to the markdown.
6
+
7
+ ## Prerequisites
8
+
9
+ - Ruby ≥ 3.0
10
+ - Optional, required for PDF output: Google Chrome or Chromium
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ gem install md_resume
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```
21
+ Usage: md_resume command filename [options…]
22
+
23
+ Commands:
24
+ serve Start a local server to preview your resume
25
+ build Build your resume in html and pdf formats.
26
+ generate Generate a template with given name (defaults to markdown)
27
+
28
+ Specific options:
29
+ --chrome-path=PATH Path to Chrome executable
30
+ --no-pdf Do not write pdf output
31
+ --no-html Do not write html output
32
+ -p, --pdf-path=PATH Path of pdf output
33
+ -h, --html-path=PATH Path of html output
34
+ --css-path=PATH Path of css inputs.
35
+ --server-port=PORT Specify the localhost port number for the server
36
+ --serve-only
37
+ -v, --[no-]verbose Run verbosely
38
+
39
+ Common options:
40
+ --help Show this message
41
+ ```
42
+
43
+ ## Customization
44
+
45
+ You can generate the default style sheet with `md_resume generate-css FILENAME`. The default style is extremely generic, which is perhaps what you want in a resume,
46
+ but CSS gives you a lot of flexibility. See, e.g. [The Tech Resume Inside-Out](https://www.thetechinterview.com/) for good advice about what a resume should look like (and what it should say).
47
+
48
+ Change the appearance of the PDF version (without affecting the HTML version) by adding rules under the `@media print` CSS selector.
49
+ Change the margins and paper size of the PDF version by editing the [`@page` CSS rule](https://developer.mozilla.org/en-US/docs/Web/CSS/%40page/size).
50
+
51
+ ## Note
52
+
53
+ The idea for the project is based off of https://github.com/mikepqr/resume.md. I could not get python to play nice so I rewrote it in Ruby and added features.
54
+
55
+ ## Development
56
+
57
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
58
+
59
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
60
+
61
+ ## Contributing
62
+
63
+ Bug reports and pull requests are welcome on GitHub at https://github.com/elasticspoon/md_resume.
64
+
65
+ ## License
66
+
67
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ require "standard/rake"
13
+
14
+ task default: %i[test standard]
@@ -0,0 +1,167 @@
1
+ body {
2
+ color: #000000;
3
+ background: #eeeeee;
4
+ font-size: 1.1em;
5
+ font-family: "Roboto", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
6
+ line-height: 1.2;
7
+ margin: 40px 0;
8
+ }
9
+ #resume {
10
+ margin: 0 auto;
11
+ max-width: 800px;
12
+ padding: 40px 60px;
13
+ background: #ffffff;
14
+ border: 1px solid #cccccc;
15
+ box-shadow: 2px 2px 4px #aaaaaa;
16
+ -webkit-box-shadow: 2px 2px 4px #aaaaaa;
17
+ }
18
+ h1,
19
+ h2 {
20
+ font-family: "Archivo Black", sans-serif;
21
+ line-height: 1;
22
+ }
23
+ h1 {
24
+ text-transform: uppercase;
25
+ margin-top: 0;
26
+ margin-bottom: 0;
27
+ margin-left: auto;
28
+ margin-right: auto;
29
+ width: fit-content;
30
+ padding: 0;
31
+ color: #090079;
32
+ }
33
+ h2 {
34
+ border-bottom: 1px solid;
35
+ border-color: linear-gradient(to right, #fc5c7d, #6a82fb);
36
+ text-transform: uppercase;
37
+ font-size: 130%;
38
+ margin: 1em 0 0 0;
39
+ padding: 0 0 1px 0;
40
+ color: #015aa8;
41
+ }
42
+ h3 {
43
+ font-size: 100%;
44
+ margin: 0.8em 0 0.3em 0;
45
+ padding: 0;
46
+ display: flex;
47
+ justify-content: space-between;
48
+ }
49
+ p {
50
+ margin: 0 0 0.5em 0;
51
+ padding: 0;
52
+ }
53
+ ul {
54
+ padding: 0;
55
+ margin: 0 1.5em;
56
+ }
57
+ /* ul immediately after h1 = contact list */
58
+ h1 + ul {
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ margin: 0;
63
+ padding: 0;
64
+ }
65
+ h1 + ul > li {
66
+ display: inline-flex;
67
+ white-space: pre;
68
+ list-style-type: none;
69
+ & span {
70
+ margin-block: auto;
71
+ }
72
+ }
73
+ h1 + ul > li:after {
74
+ content: " \2022 ";
75
+ margin-block: auto;
76
+ }
77
+ h1 + ul > li:last-child:after {
78
+ content: "";
79
+ }
80
+ /* p immediately after contact list = summary */
81
+ h1 + ul + p {
82
+ margin: 1em 0;
83
+ }
84
+ img {
85
+ height: 20px;
86
+ }
87
+ .img_link {
88
+ display: inline-flex;
89
+ }
90
+ #skills {
91
+ margin-bottom: 8px;
92
+ }
93
+
94
+ @media print {
95
+ body {
96
+ font-size: 10pt;
97
+ margin: 0;
98
+ padding: 0;
99
+ background: none;
100
+ }
101
+ #resume {
102
+ margin: 0;
103
+ padding: 0;
104
+ border: 0px;
105
+ background: none;
106
+ box-shadow: none;
107
+ -webkit-box-shadow: none;
108
+ }
109
+ /* Do not underline abbr tags in PDF */
110
+ abbr {
111
+ text-decoration: none;
112
+ font-variant: none;
113
+ }
114
+ /* Make links black in PDF */
115
+ /* Move this outside the print block to apply this in HTML too */
116
+ a,
117
+ a:link,
118
+ a:visited,
119
+ a:hover {
120
+ color: #000000;
121
+ text-decoration: underline;
122
+ }
123
+ }
124
+ @page {
125
+ /* Change margins and paper size of PDF */
126
+ /* https://developer.mozilla.org/en-US/docs/Web/CSS/@page */
127
+ size: letter;
128
+ margin: 0.5in 0.8in;
129
+ }
130
+ @media screen and (max-width: 800px) {
131
+ body {
132
+ font-size: 16pt;
133
+ margin: 0;
134
+ padding: 0;
135
+ background: #ffffff !important;
136
+ }
137
+ #resume {
138
+ margin: 0;
139
+ padding: 1em;
140
+ border: 0px;
141
+ background: none;
142
+ box-shadow: none;
143
+ -webkit-box-shadow: none;
144
+ }
145
+ }
146
+
147
+ @media screen {
148
+ h1 {
149
+ background-color: #fc5c7d;
150
+ background-image: linear-gradient(to right, #fc5c7d, #6a82fb);
151
+ background-size: 100%;
152
+ -webkit-background-clip: text;
153
+ -webkit-text-fill-color: transparent;
154
+ -moz-background-clip: text;
155
+ -moz-text-fill-color: transparent;
156
+ }
157
+
158
+ h2 {
159
+ background-color: #314755;
160
+ background-image: linear-gradient(45deg, #003850, #154bdf 20%);
161
+ background-size: 100%;
162
+ -webkit-background-clip: text;
163
+ -webkit-text-fill-color: transparent;
164
+ -moz-background-clip: text;
165
+ -moz-text-fill-color: transparent;
166
+ }
167
+ }
data/assets/reload.js ADDED
@@ -0,0 +1,24 @@
1
+ function reload() {
2
+ fetch("/")
3
+ .then((response) => {
4
+ return response.text();
5
+ })
6
+ .then((html) => {
7
+ document.body.innerHTML = html;
8
+ console.log("Reloaded");
9
+ });
10
+ }
11
+
12
+ function pollServer() {
13
+ fetch("http://localhost:12345/")
14
+ .then(() => {
15
+ reload();
16
+ pollServer();
17
+ })
18
+ .catch((err) => {
19
+ console.log(err);
20
+ console.log("Server not ready. Reload the page with the server running.");
21
+ });
22
+ }
23
+
24
+ pollServer();
@@ -0,0 +1,64 @@
1
+ <!-- The (first) h1 will be used as the <title> of the HTML page -->
2
+
3
+ # Richard Hendricks
4
+
5
+ <!-- The unordered list immediately after the h1 will be formatted on a single
6
+ line. It is intended to be used for contact details -->
7
+
8
+ - <richard.hendriks@mail.com>
9
+ - (912) 555-4321
10
+ - [richardhendricks.example.com](http://richardhendricks.example.com)
11
+ - San Francisco, CA
12
+
13
+ <!-- The paragraph after the h1 and ul and before the first h2 is optional. It
14
+ is intended to be used for a short summary. -->
15
+
16
+ CEO and Software Engineer with knowledge of applied information theory,
17
+ including optimizing lossless compression schema of both the length-limited and
18
+ adaptive variants.
19
+
20
+ ## Experience
21
+
22
+ <!-- You have to wrap the "left" and "right" half of these headings in spans by
23
+ hand -->
24
+
25
+ ### <span>CEO/President, Pied Piper</span> <span>Dec 2013 -- Dec 2014</span>
26
+
27
+ Pied Piper is a multi-platform technology based on a proprietary universal
28
+ compression algorithm that has consistently fielded high Weisman Scores™ that
29
+ are not merely competitive, but approach the theoretical limit of lossless
30
+ compression.
31
+
32
+ - Build an algorithm for artist to detect if their music was violating
33
+ copyright infringement laws
34
+ - Successfully won Techcrunch Disrupt
35
+ - Optimized an algorithm that holds the current world record for Weisman Scores
36
+
37
+ ### <span>Teacher, CoderDojo</span> <span>July 2013 -- Dec 2013</span>
38
+
39
+ Global movement of free coding clubs for young people.
40
+
41
+ - Awarded 'Teacher of the Month'
42
+
43
+ ## Projects
44
+
45
+ ### <span>Miss Direction</span> <span>Aug 2016</span>
46
+
47
+ A mapping engine that misguides you:
48
+
49
+ - Won award at AIHacks 2016
50
+ - Built by all women team of newbie programmers
51
+ - Using modern technologies such as GoogleMaps, Chrome Extension and Javascript
52
+
53
+ ## Education
54
+
55
+ ### <span>University of Oklahoma, BA Information Technology</span> <span>2011 -- 2014</span>
56
+
57
+ - GPA 4.0
58
+ - DB1101 - Basic SQL
59
+ - CS2011 - Java Introduction
60
+
61
+ ## Skills
62
+
63
+ - Web development: HTML, CSS, JavaScript
64
+ - Compression: Mpeg, MP4, GIF
data/exe/md_resume ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'resume_generator'
4
+ require 'parser'
5
+ require 'local-server'
6
+
7
+ command = ARGV.shift
8
+ resume = ARGV.shift
9
+
10
+ parser = Parser.new
11
+ opts = parser.parse(command, ARGV)
12
+
13
+ if resume.nil?
14
+ puts parser.args
15
+ exit
16
+ end
17
+
18
+ resume = File.expand_path(resume)
19
+ opts.input = resume
20
+
21
+ case command
22
+ when 'serve'
23
+ generator = ResumeGenerator.new(opts)
24
+ server = Server.new(generator, opts)
25
+ server.start
26
+ when 'build'
27
+ generator = ResumeGenerator.new(opts)
28
+ generator.write
29
+ when 'generate'
30
+ generator = ResumeGenerator.new(opts)
31
+ generator.generate_template
32
+ else
33
+ puts parser.args
34
+ end
@@ -0,0 +1,97 @@
1
+ require 'webrick'
2
+ require 'socket'
3
+ require 'filewatcher'
4
+
5
+ class Server
6
+ attr_reader :opts, :generator
7
+
8
+ def initialize(generator, opts)
9
+ @opts = opts
10
+ @generator = generator
11
+ @needs_reload = false
12
+ end
13
+
14
+ def start
15
+ start_file_watcher
16
+ start_reload_server
17
+ open_browser
18
+ start_local_server
19
+ ensure
20
+ clean_build_dir
21
+ end
22
+
23
+ private
24
+
25
+ def filewatcher
26
+ puts "watching #{opts.input} and #{opts.css_path}" if opts.verbose
27
+ @filewatcher ||= Filewatcher.new([opts.input, opts.css_path])
28
+ end
29
+
30
+ def open_browser
31
+ link = "http://localhost:#{opts.port}"
32
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
33
+ system "start #{link}"
34
+ elsif RbConfig::CONFIG['host_os'] =~ /darwin/
35
+ system "open #{link}"
36
+ elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
37
+ system "xdg-open #{link}"
38
+ end
39
+ end
40
+
41
+ def start_file_watcher
42
+ generator.write
43
+ @thread = Thread.new(filewatcher) do |fw|
44
+ fw.watch do |change|
45
+ puts "Change detected: #{change}" if opts.verbose
46
+ generator.write
47
+ @needs_reload = true
48
+ end
49
+ end
50
+ end
51
+
52
+ def start_reload_server
53
+ puts 'Starting reload server on port 12345'
54
+ @reload_thread = Thread.new do
55
+ reload_server
56
+ end
57
+ end
58
+
59
+ def reload_server
60
+ server = TCPServer.new('localhost', 12_345)
61
+
62
+ loop do
63
+ socket = server.accept
64
+ request = socket.gets
65
+ warn request
66
+ sleep 0.5 until @needs_reload
67
+ @needs_reload = false
68
+ socket.print(headers)
69
+ socket.close
70
+ end
71
+ end
72
+
73
+ def start_local_server
74
+ puts "Starting local server on port #{opts.port}"
75
+ root = opts.html_path
76
+ server = WEBrick::HTTPServer.new(Port: opts.port, DocumentRoot: root)
77
+
78
+ trap 'INT' do server.shutdown end
79
+
80
+ server.start
81
+ end
82
+
83
+ def headers
84
+ headers = [
85
+ 'HTTP/1.1 200 OK',
86
+ 'Content-Type: text/html',
87
+ "Access-Control-Allow-Origin: http://localhost:#{opts.port}"
88
+ ]
89
+ @headers ||= "#{headers.join("\r\n")}\r\n\r\n"
90
+ end
91
+
92
+ def clean_build_dir
93
+ tmp_dir = opts.html_path.dirname
94
+ FileUtils.rm_rf(tmp_dir)
95
+ puts "Could not delete #{tmp_dir}" if File.directory?(tmp_dir)
96
+ end
97
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MdResume
4
+ VERSION = "0.1.0"
5
+ end
data/lib/parser.rb ADDED
@@ -0,0 +1,198 @@
1
+ require 'optparse'
2
+ require 'pathname'
3
+
4
+ class Parser
5
+ class ScriptOptions
6
+ attr_accessor :chrome_path, :html, :pdf, :css_path, :pdf_path, :html_path, :verbose, :input,
7
+ :serve_only, :port, :open_browser, :generate_md, :generate_css
8
+
9
+ def initialize
10
+ self.chrome_path = nil
11
+ self.html = true
12
+ self.pdf = true
13
+ self.open_browser = true
14
+ self.css_path = default_css_path
15
+ self.pdf_path = Pathname.new('resume.pdf').expand_path
16
+ self.html_path = Pathname.new('resume.html').expand_path
17
+ self.verbose = false
18
+ self.serve_only = false
19
+ self.input = nil
20
+ self.port = 3000
21
+ self.generate_md = true
22
+ self.generate_css = false
23
+ end
24
+
25
+ def define_options(parser)
26
+ parser.banner = 'Usage: md_resume command filename [options...]'
27
+ parser.separator ''
28
+ parser.separator 'Commands:'
29
+ parser.separator "serve\t\t\tStart a local server to preview your resume"
30
+ parser.separator "build\t\t\tBuild your resume in html and pdf formats."
31
+ parser.separator "generate\t\tGenerate a resume template with the given filename"
32
+ parser.separator ''
33
+ parser.separator 'Specific options:'
34
+
35
+ # add additional options
36
+ specify_chrome_path_option(parser)
37
+ boolean_pdf_option(parser)
38
+ boolean_html_option(parser)
39
+ specify_output_pdf_option(parser)
40
+ specify_output_html_option(parser)
41
+ specify_input_css_option(parser)
42
+ specify_server_port_option(parser)
43
+ # TODO: remove this?
44
+ boolean_serve_only_option(parser)
45
+ boolean_open_browser_option(parser)
46
+ boolean_verbosity_option(parser)
47
+ boolean_generate_md_option(parser)
48
+ boolean_generate_css_option(parser)
49
+ parser.separator ''
50
+ parser.separator 'Common options:'
51
+ # No argument, shows at tail. This will print an options summary.
52
+ # Try it and see!
53
+ parser.on_tail('-h', '--help', 'Show this message') do
54
+ puts parser
55
+ exit
56
+ end
57
+ end
58
+
59
+ def specify_chrome_path_option(parser)
60
+ # Specifies an optional option argument
61
+ parser.on('--chrome-path=PATH', 'Path to Chrome executable') do |path|
62
+ full_path = Pathname.new(path).expand_path
63
+ self.chrome_path = full_path
64
+ end
65
+ end
66
+
67
+ def specify_output_html_option(parser)
68
+ parser.on('-h PATH', '--html-path=PATH', 'Path of html output') do |path|
69
+ input_dir = Pathname.new(path).expand_path
70
+ self.html_path = input_dir
71
+ end
72
+ end
73
+
74
+ def specify_output_pdf_option(parser)
75
+ parser.on('-p PATH', '--pdf-path=PATH', 'Path of pdf output') do |path|
76
+ input_dir = Pathname.new(path).expand_path
77
+ self.pdf_path = input_dir
78
+ end
79
+ end
80
+
81
+ def specify_input_css_option(parser)
82
+ parser.on('--css-path=PATH', 'Path of css inputs.') do |path|
83
+ input_dir = Pathname.new(path).expand_path
84
+ self.css_path = input_dir
85
+ end
86
+ end
87
+
88
+ def specify_server_port_option(parser)
89
+ parser.on('--server-port=PORT', 'Specify the localhost port number for the server') do |port|
90
+ port = port.to_i
91
+ raise OptionParser::InvalidArgument unless (1..65_535).cover?(port)
92
+
93
+ self.port = port
94
+ end
95
+ end
96
+
97
+ def boolean_verbosity_option(parser)
98
+ parser.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
99
+ self.verbose = v
100
+ end
101
+ end
102
+
103
+ def boolean_pdf_option(parser)
104
+ # Boolean switch.
105
+ parser.on('--no-pdf', 'Do [not] write pdf output') do |v|
106
+ self.pdf = v
107
+ end
108
+ end
109
+
110
+ def boolean_html_option(parser)
111
+ # Boolean switch.
112
+ parser.on('--[no-]html', 'Do [not] write html output') do |v|
113
+ self.html = v
114
+ end
115
+ end
116
+
117
+ def boolean_serve_only_option(parser)
118
+ # Boolean switch.
119
+ parser.on('--serve-only') do |v|
120
+ self.serve_only = v
121
+ end
122
+ end
123
+
124
+ def boolean_open_browser_option(parser)
125
+ # Boolean switch.
126
+ parser.on('--no-open', 'Do not automatically open browser when starting server') do |v|
127
+ self.open_browser = v
128
+ end
129
+ end
130
+
131
+ def boolean_generate_md_option(parser)
132
+ parser.on('--no-generate-md', 'Do not generate markdown template.') do |v|
133
+ self.generate_md = v
134
+ end
135
+ end
136
+
137
+ def boolean_generate_css_option(parser)
138
+ parser.on('--[no-]generate-css', 'Generate CSS template.') do |v|
139
+ self.generate_css = v
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def default_css_path
146
+ curr = Pathname.new(__FILE__).dirname
147
+ relative = Pathname.new('../assets/defaults.css')
148
+ File.expand_path(relative, curr)
149
+ end
150
+ end
151
+
152
+ #
153
+ # Return a structure describing the options.
154
+ #
155
+ def parse(command, args)
156
+ @options = ScriptOptions.new
157
+ @args = OptionParser.new do |parser|
158
+ @options.define_options(parser)
159
+ parser.parse!(args)
160
+ set_command_defaults(command)
161
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
162
+ puts e
163
+ puts
164
+ puts parser
165
+ exit
166
+ end
167
+ @options
168
+ end
169
+
170
+ def set_command_defaults(command)
171
+ case command
172
+ when 'serve'
173
+ serve_defaults
174
+ when 'build'
175
+ build_defaults
176
+ when 'generate'
177
+ generate_defaults
178
+ end
179
+ end
180
+
181
+ def serve_defaults
182
+ options.pdf = false
183
+ options.html = true
184
+ options.html_path = internal_tmp_dir.join('resume.html')
185
+ end
186
+
187
+ def build_defaults(parser); end
188
+
189
+ def generate_defaults(parser); end
190
+
191
+ attr_reader :parser, :options, :args
192
+
193
+ private
194
+
195
+ def internal_tmp_dir
196
+ Pathname.new('../../tmp').expand_path(__FILE__)
197
+ end
198
+ end
@@ -0,0 +1,233 @@
1
+ require 'kramdown'
2
+ require 'base64'
3
+ require 'open3'
4
+ require 'fileutils'
5
+ require 'pathname'
6
+
7
+ class ResumeGenerator
8
+ attr_reader :opts
9
+
10
+ ValueError = Class.new(StandardError)
11
+
12
+ POSTAMBLE = <<~POSTAMBLE.freeze
13
+ </div>
14
+ </body>
15
+ <script>
16
+ #{File.read(File.expand_path('../assets/reload.js', __dir__))}
17
+ </script>
18
+ </html>
19
+ POSTAMBLE
20
+
21
+ CHROME_GUESSES_MACOS = [
22
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
23
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
24
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
25
+ ].freeze
26
+
27
+ LINUX_CHROME_DIRS = [
28
+ '/usr/local/sbin',
29
+ '/usr/local/bin',
30
+ '/usr/sbin',
31
+ '/usr/bin',
32
+ '/sbin',
33
+ '/bin',
34
+ '/opt/google/chrome',
35
+ "/etc/profiles/per-user/#{ENV['USER']}/bin" # NixOS
36
+ ].freeze
37
+
38
+ LINUX_CHROME_EXECUTABLES = %w[google-chrome chrome chromium chromium-browser].freeze
39
+
40
+ def initialize(opts)
41
+ @opts = opts
42
+ @opts.chrome_path = guess_chrome_path if @opts.pdf && @opts.chrome_path.nil?
43
+ end
44
+
45
+ def write
46
+ valid_input?
47
+ write_html
48
+ write_pdf
49
+ end
50
+
51
+ def valid_input?
52
+ puts "Input file: #{@opts.input}"
53
+ return if File.exist?(@opts.input)
54
+
55
+ puts "Resume not found at #{@opts.input}"
56
+ exit
57
+ end
58
+
59
+ def generate_template
60
+ curr = Pathname.new(__FILE__).dirname
61
+ if @opts.generate_md
62
+ relative = Pathname.new('../assets/sample-resume.md')
63
+ template = File.expand_path(relative, curr)
64
+ FileUtils.copy_file(template, "#{@opts.input}.md")
65
+ end
66
+
67
+ return unless @opts.generate_css
68
+
69
+ relative = Pathname.new('../assets/defaults.css')
70
+ template = File.expand_path(relative, curr)
71
+ FileUtils.copy_file(template, "#{@opts.input}.css")
72
+ end
73
+
74
+ private
75
+
76
+ def chrome_guesses_linux
77
+ LINUX_CHROME_DIRS.product(LINUX_CHROME_EXECUTABLES).map do |dir, exe|
78
+ "#{dir}/#{exe}"
79
+ end
80
+ end
81
+
82
+ def chrome_guesses_windows
83
+ [
84
+ # Windows 10
85
+ File.expand_path('Google/Chrome/Application/chrome.exe', ENV.fetch('ProgramFiles(x86)', nil)),
86
+ File.expand_path('Google/Chrome/Application/chrome.exe', ENV.fetch('ProgramFiles(x86)', nil)),
87
+ File.expand_path('Google/Chrome/Application/chrome.exe', ENV.fetch('ProgramFiles(x86)', nil)),
88
+ # Windows 7
89
+ 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
90
+ 'C:/Program Files/Google/Chrome/Application/chrome.exe',
91
+ # Vista
92
+ 'C:/Users/UserName/AppData/Local/Google/Chrome',
93
+ # XP
94
+ 'C:/Documents and Settings/UserName/Local Settings/Application Data/Google/Chrome'
95
+ ]
96
+ end
97
+
98
+ def guess_chrome_path
99
+ guesses = case RUBY_PLATFORM
100
+ when /darwin/
101
+ CHROME_GUESSES_MACOS
102
+ when /cygwin|mswin|mingw|bccwin|wince|emx/
103
+ chrome_guesses_windows
104
+ else
105
+ chrome_guesses_linux
106
+ end
107
+ guesses.each do |path|
108
+ if File.exist?(path)
109
+ puts "Guessed Chrome path: #{path}" if opts.verbose
110
+ return path
111
+ end
112
+ end
113
+
114
+ raise ValueError,
115
+ "Cannot guess Chrome path on platform #{RUBY_PLATFORM}.
116
+ Please set --chrome_path= manually."
117
+ end
118
+
119
+ def to_html
120
+ markdown = File.read(opts.input)
121
+ make_html(markdown, css_file: opts.css_path)
122
+ end
123
+
124
+ def to_pdf
125
+ make_pdf(to_html)
126
+ end
127
+
128
+ def make_pdf(html)
129
+ html64 = Base64.encode64(html.encode('utf-8')).chomp
130
+ prefix = opts.pdf_path.basename
131
+ tmp_dir = Pathname.new('../tmp').expand_path(__FILE__)
132
+ FileUtils.mkdir(tmp_dir) unless File.directory?(tmp_dir)
133
+ options = pdf_opts_string(tmp_dir)
134
+ create_output_dir(opts.pdf_path)
135
+
136
+ begin
137
+ cmd = "#{opts.chrome_path} #{options} --print-to-pdf=#{opts.pdf_path} 'data:text/html;base64,#{html64}'"
138
+ _stdout, stderr, status = Open3.capture3(cmd)
139
+ raise stderr unless status.success?
140
+
141
+ puts "Wrote #{prefix}" if opts.verbose
142
+ rescue StandardError => e
143
+ puts e.message
144
+ ensure
145
+ FileUtils.rm_rf(tmp_dir)
146
+ puts "Could not delete #{tmp_dir}" if File.directory?(tmp_dir)
147
+ end
148
+ end
149
+
150
+ def pdf_opts_string(tmp_dir)
151
+ options = [
152
+ '--no-sandbox',
153
+ '--headless',
154
+ '--no-pdf-header-footer',
155
+ '--enable-logging=stderr',
156
+ '--log-level=2',
157
+ '--in-process-gpu',
158
+ '--disable-gpu'
159
+ ]
160
+ options.push("--crash-dumps-dir=#{tmp_dir}")
161
+ options.push("--user-data-dir=#{tmp_dir}")
162
+ options.join(' ')
163
+ end
164
+
165
+ def preamable(title, css)
166
+ <<~PREAMBLE
167
+ <html lang="en">
168
+ <head>
169
+ <meta charset="UTF-8">
170
+ <meta property="time_built" content="#{Time.now.iso8601}">
171
+ <title>#{title}</title>
172
+ <style>
173
+ #{css}
174
+ </style>
175
+ </head>
176
+ <body>
177
+ <div id="resume">
178
+ PREAMBLE
179
+ end
180
+
181
+ def title(markdown)
182
+ # Return the contents of the first markdown heading in md, which we
183
+ # assume to be the title of the document.
184
+ markdown.each_line do |line|
185
+ return Regexp.last_match(1) if line =~ /^#([^#]*)$/
186
+ end
187
+
188
+ raise ValueError,
189
+ 'Cannot find any lines that look like markdown h1 headings to use as the title'
190
+ end
191
+
192
+ def reload_script
193
+ File.read(File.expand_path('assets/reload.js', __dir__))
194
+ end
195
+
196
+ def make_html(markdown, css_file: nil)
197
+ title = title(markdown)
198
+ html = Kramdown::Document.new(markdown).to_html
199
+ begin
200
+ css = File.read(css_file.to_s)
201
+ rescue Errno::ENOENT
202
+ warn "Cannot find CSS file #{css_file}"
203
+ warn 'Output will not be styled'
204
+ css = ''
205
+ end
206
+ preamable(title, css) + html + POSTAMBLE
207
+ end
208
+
209
+ def write_html
210
+ puts "Writing HTML to #{opts.html_path}"
211
+ File.write(opts.html_path, to_html, mode: 'w') if opts.html
212
+ rescue Errno::ENOENT => e
213
+ raise e unless e.message =~ /No such file or directory/
214
+
215
+ create_output_dir(opts.html_path)
216
+ retry
217
+ end
218
+
219
+ def write_pdf
220
+ to_pdf if opts.pdf
221
+ end
222
+
223
+ def create_output_dir(path)
224
+ FileUtils.mkdir_p(File.dirname(path))
225
+ end
226
+
227
+ def set_server_opts
228
+ opts.html_path = Pathname.new(Dir.pwd).join('tmp/resume.html')
229
+ opts.html = true
230
+ opts.pdf = false
231
+ opts.verbose = true
232
+ end
233
+ end
data/md_resume.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/md_resume/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'md_resume'
7
+ spec.version = MdResume::VERSION
8
+ spec.authors = ['YuriBocharov']
9
+ spec.email = ['quesadillaman@gmail.com']
10
+
11
+ spec.summary = 'Ruby gem for creating resume from markdown file.'
12
+ spec.description = 'Write a resume in markdown, style it with CSS, distribute it as either HTML or PDF. Now with even faster feedback cycles, make changes and preview them immediately!</br></br><code>md-resume</code> is a resume generator written in Ruby styled with CSS. Instead of stopping at exclusively generation of a resume in HTML or PDF format, the project goes further in letting you perfect your resume. Running <code>resume.rb</code> in development mode will spin up a local server that will watch your changes to the resume styles and content. The server will live update an HTML preview of your resume letting you move quickly with changes and updates. The project uses <code>kramdown</code> to translate markdown to HTML, bringing with it additional inline markdown customization options.'
13
+ spec.homepage = 'https://github.com/elasticspoon/markdown-resume'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.6.0'
16
+
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/elasticspoon/markdown-resume'
21
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
28
+ end
29
+ end
30
+ spec.bindir = 'exe'
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ['lib']
33
+
34
+ # Uncomment to register a new dependency of your gem
35
+ # spec.add_dependency "example-gem", "~> 1.0"
36
+ spec.add_dependency 'filewatcher', '~> 2.1'
37
+ spec.add_dependency 'kramdown', '~> 2.4'
38
+ spec.add_dependency 'webrick', '~> 1.8'
39
+
40
+ # For more information and examples about making a new gem, check out our
41
+ # guide at: https://bundler.io/guides/creating_gem.html
42
+ end
data/resume.png ADDED
Binary file
data/sig/md_resume.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module MdResume
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: md_resume
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - YuriBocharov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: filewatcher
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
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.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webrick
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
55
+ description: Write a resume in markdown, style it with CSS, distribute it as either
56
+ HTML or PDF. Now with even faster feedback cycles, make changes and preview them
57
+ immediately!</br></br><code>md-resume</code> is a resume generator written in Ruby
58
+ styled with CSS. Instead of stopping at exclusively generation of a resume in HTML
59
+ or PDF format, the project goes further in letting you perfect your resume. Running
60
+ <code>resume.rb</code> in development mode will spin up a local server that will
61
+ watch your changes to the resume styles and content. The server will live update
62
+ an HTML preview of your resume letting you move quickly with changes and updates.
63
+ The project uses <code>kramdown</code> to translate markdown to HTML, bringing with
64
+ it additional inline markdown customization options.
65
+ email:
66
+ - quesadillaman@gmail.com
67
+ executables:
68
+ - md_resume
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - ".standard.yml"
73
+ - CHANGELOG.md
74
+ - Gemfile
75
+ - Gemfile.lock
76
+ - LICENSE.txt
77
+ - README.md
78
+ - Rakefile
79
+ - assets/defaults.css
80
+ - assets/reload.js
81
+ - assets/sample-resume.md
82
+ - exe/md_resume
83
+ - lib/local-server.rb
84
+ - lib/md_resume/version.rb
85
+ - lib/parser.rb
86
+ - lib/resume_generator.rb
87
+ - md_resume.gemspec
88
+ - resume.png
89
+ - sig/md_resume.rbs
90
+ homepage: https://github.com/elasticspoon/markdown-resume
91
+ licenses:
92
+ - MIT
93
+ metadata:
94
+ homepage_uri: https://github.com/elasticspoon/markdown-resume
95
+ source_code_uri: https://github.com/elasticspoon/markdown-resume
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 2.6.0
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.4.10
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: Ruby gem for creating resume from markdown file.
115
+ test_files: []