linkedin2cv 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.buildpacks +2 -0
- data/.bundle/config +1 -0
- data/.gitignore +7 -0
- data/.node +1 -0
- data/.travis.yml +21 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +119 -0
- data/Procfile +3 -0
- data/README.md +32 -0
- data/Rakefile +6 -0
- data/Vagrantfile +16 -0
- data/app/routes/api.rb +101 -0
- data/app/routes/web.rb +132 -0
- data/app/views/hello.erb +36 -0
- data/app/views/home.erb +12 -0
- data/app/views/index.haml +11 -0
- data/app.rb +26 -0
- data/config.ru +6 -0
- data/config.yml +31 -0
- data/lib/linkedin2cv/cli/command.rb +46 -0
- data/lib/linkedin2cv/converter.rb +116 -0
- data/lib/linkedin2cv/logging.rb +35 -0
- data/lib/linkedin2cv/renderer/latex_renderer.rb +64 -0
- data/lib/linkedin2cv/version.rb +3 -0
- data/linkedin2cv.gemspec +49 -0
- data/public/.bowerrc +3 -0
- data/public/.editorconfig +21 -0
- data/public/.gitattributes +1 -0
- data/public/.gitignore +5 -0
- data/public/.jshintrc +24 -0
- data/public/.travis.yml +7 -0
- data/public/Gruntfile.js +487 -0
- data/public/app/.buildignore +1 -0
- data/public/app/.htaccess +543 -0
- data/public/app/404.html +157 -0
- data/public/app/favicon.ico +0 -0
- data/public/app/images/yeoman.png +0 -0
- data/public/app/index.html +73 -0
- data/public/app/robots.txt +3 -0
- data/public/app/scripts/app.js +20 -0
- data/public/app/scripts/controllers/main.js +21 -0
- data/public/app/scripts/services/linkedin2cv.js +93 -0
- data/public/app/styles/main.scss +95 -0
- data/public/app/views/main.html +23 -0
- data/public/bower.json +19 -0
- data/public/karma-e2e.conf.js +54 -0
- data/public/karma.conf.js +56 -0
- data/public/package.json +41 -0
- data/public/test/.jshintrc +36 -0
- data/public/test/runner.html +10 -0
- data/public/test/spec/controllers/main.js +22 -0
- data/public/test/spec/services/happyapi.js +18 -0
- data/public/test/spec/services/happyservice.js +18 -0
- data/spec/converter_spec.rb +83 -0
- data/spec/mocks/config.yml +31 -0
- data/spec/mocks/profile.json +866 -0
- data/spec/spec_helper.rb +13 -0
- data/templates/cv.erb +327 -0
- data/templates/foo.asciidoc +11 -0
- data/templates/foo.latex +230 -0
- data/test.rb +109 -0
- data/test.sh +6 -0
- data/teust.rb +75 -0
- metadata +434 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'linkedin-oauth2'
|
2
|
+
require 'asciidoctor'
|
3
|
+
require 'launchy'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'tilt'
|
6
|
+
require 'yaml'
|
7
|
+
require 'erb'
|
8
|
+
require 'net/http'
|
9
|
+
|
10
|
+
|
11
|
+
module Linkedin2CV
|
12
|
+
|
13
|
+
class Converter
|
14
|
+
include Logging
|
15
|
+
|
16
|
+
TOKEN_WAIT_TIME = 15
|
17
|
+
API_KEY = ENV['LINKEDIN_API_KEY']
|
18
|
+
API_SECRET = ENV["LINKEDIN_API_SECRET"]
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@display_fields = ["company", "publication", "patent", "language", "skills", "certification", "education", "course", "volunteer", "recommendations"]
|
22
|
+
@profile_fields = ["projects:(start-date,end-date,description,id,name,url),main-address,phone-numbers,email-address,first-name,last-name,maiden-name,formatted-name,phonetic-first-name,phonetic-last-name,formatted-phonetic-name,headline,location:(name,country:(code)),industry,current-status,current-share,num-connections,num-connections-capped,summary,specialties,positions,picture-url,api-standard-profile-request:(url,headers),public-profile-url,last-modified-timestamp,proposal-comments,associations,interests,publications:(id,title,publisher:(name),authors:(id,name,person),date,url,summary),patents,languages:(id,language:(name),proficiency:(level,name)),skills:(id,skill:(name)),certifications:(id,name,authority:(name),number,start-date,end-date),educations:(id,school-name,field-of-study,start-date,end-date,degree,activities,notes),courses:(id,name,number),three-current-positions:(id,title,summary,start-date,end-date,is-current,company),three-past-positions:(id,title,summary,start-date,end-date,is-current,company),num-recommenders,recommendations-received:(id,recommendation-type,recommendation-text,recommender),mfeed-rss-url,following,job-bookmarks,suggestions,date-of-birth,member-url-resources:(url,name),related-profile-views,honors-awards"]
|
23
|
+
|
24
|
+
# Confirm API keys setup
|
25
|
+
|
26
|
+
# Setup default options to be merged with supplied ones
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Create a resume given a LinkedIn Profile
|
30
|
+
#
|
31
|
+
# Takes
|
32
|
+
def create_resume(options = {})
|
33
|
+
|
34
|
+
# Get Profile
|
35
|
+
profile = get_profile
|
36
|
+
|
37
|
+
# Find stylist (LaTeX / Asciidoc etc.)
|
38
|
+
# Find output factory/plugin (fetch from Github?)
|
39
|
+
case options['format']
|
40
|
+
when 'latex'
|
41
|
+
require 'linkedin2cv/renderer/latex_renderer'
|
42
|
+
|
43
|
+
# Currently only supports one LaTeX template
|
44
|
+
renderer = Linkedin2CV::LatexRenderer.new
|
45
|
+
renderer.latex_pdf(profile, options)
|
46
|
+
else
|
47
|
+
puts "Sorry mate, I don't yet support the '#{options['format']}' format'"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def auth
|
52
|
+
thread = nil
|
53
|
+
if !File.exists?('.token')
|
54
|
+
auth_thread = prompt_user_authorisation
|
55
|
+
|
56
|
+
puts "Waiting to retrieve LinkedIn OAuth token"
|
57
|
+
sleep 1 until auth_thread.thread_variable?('access_token')
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get Token from environment
|
61
|
+
token = fetch_token_from_tempfile
|
62
|
+
puts "I have a token for you: #{token}"
|
63
|
+
token
|
64
|
+
end
|
65
|
+
|
66
|
+
# Public: Fetch Profile Data
|
67
|
+
#
|
68
|
+
#
|
69
|
+
def get_profile
|
70
|
+
|
71
|
+
# Auth
|
72
|
+
token = auth
|
73
|
+
|
74
|
+
# Get Client info based on fields provided
|
75
|
+
client = LinkedIn::Client.new(API_KEY, API_SECRET, token)
|
76
|
+
user = client.profile(:fields => @profile_fields)
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
# Public: Fetches a template for rendering via Asciidoctor
|
81
|
+
#
|
82
|
+
#
|
83
|
+
def lookup_template
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
# Public: Invoke the API and browser session for end user authentication.
|
88
|
+
#
|
89
|
+
# Returns a sub-process containing the API session.
|
90
|
+
#
|
91
|
+
def prompt_user_authorisation
|
92
|
+
|
93
|
+
require './app/routes/web'
|
94
|
+
|
95
|
+
# Start local API
|
96
|
+
Launchy.open("http://localhost:5000/cli/auth")
|
97
|
+
|
98
|
+
auth_thread = Thread.new do
|
99
|
+
Linkedin2CV::Routes::Web.run!
|
100
|
+
end
|
101
|
+
|
102
|
+
auth_thread
|
103
|
+
end
|
104
|
+
|
105
|
+
# Public: Fetch the token from a temporary oauth token file.
|
106
|
+
#
|
107
|
+
#
|
108
|
+
def fetch_token_from_tempfile
|
109
|
+
# Store in env for command line!
|
110
|
+
file = File.new('.token', 'r')
|
111
|
+
token = file.read
|
112
|
+
file.close
|
113
|
+
token
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
|
3
|
+
# Public: Logging module to mixin with classes.
|
4
|
+
#
|
5
|
+
#
|
6
|
+
module Logging
|
7
|
+
|
8
|
+
def log
|
9
|
+
@log ||= Logging.logger_for(self.class.name)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Use a hash class-ivar to cache a unique Logger per class:
|
13
|
+
@loggers = {}
|
14
|
+
|
15
|
+
class << self
|
16
|
+
include Log4r
|
17
|
+
|
18
|
+
def logger_for(classname)
|
19
|
+
@loggers[classname] ||= configure_logger_for(classname)
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure_logger_for(classname)
|
23
|
+
logger = Logger.new classname.to_s.gsub(/[^a-zA-Z0-9]/, '.').downcase.gsub(/\.+/, '.')
|
24
|
+
|
25
|
+
# Log to file or stdout?
|
26
|
+
if (ENV['LOG_OUTPUT_FILENAME'] && !ENV['LOG_OUTPUT_FILENAME'].empty?)
|
27
|
+
puts ENV['LOG_OUTPUT_FILENAME']
|
28
|
+
logger.outputters << Log4r::FileOutputter.new('linkedin2cvlog', :filename => ENV['LOG_OUTPUT_FILENAME'])
|
29
|
+
elsif
|
30
|
+
logger.outputters << Log4r::StdoutOutputter.new('linkedin2cvlog')
|
31
|
+
end
|
32
|
+
logger
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Linkedin2CV
|
2
|
+
class LatexRenderer
|
3
|
+
|
4
|
+
# Public: Produce a Latex PDF
|
5
|
+
#
|
6
|
+
#
|
7
|
+
def latex_pdf(profile, options)
|
8
|
+
|
9
|
+
def clean_latex(s)
|
10
|
+
# Clean &
|
11
|
+
s = s.gsub(/(?<!\\)\&(?!\\)/, '\\\&')
|
12
|
+
|
13
|
+
# Clean $
|
14
|
+
s = s.gsub(/(?<!\\)\$(?!\\)/, '\\\$')
|
15
|
+
|
16
|
+
# Clean %
|
17
|
+
s = s.gsub(/(?<!\\)%(?!\\)/, '\\\%')
|
18
|
+
|
19
|
+
# # Clean ~
|
20
|
+
s = s.gsub(/\~/, '\\\textasciitilde')
|
21
|
+
|
22
|
+
# # Clean >
|
23
|
+
s = s.gsub(/\>/, '\\\textgreater')
|
24
|
+
|
25
|
+
# # Clean <
|
26
|
+
s = s.gsub(/\</, '\\\textless')
|
27
|
+
|
28
|
+
s
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'tilt/erb'
|
32
|
+
output_filename = "#{options['output_file']}.latex"
|
33
|
+
template = Tilt.new('templates/cv.erb')
|
34
|
+
|
35
|
+
output = template.render(self, :profile => profile, :options => options)
|
36
|
+
|
37
|
+
output_file = File.new(output_filename, 'w')
|
38
|
+
output_file.write(output)
|
39
|
+
output_file.close
|
40
|
+
|
41
|
+
# Make sure this variable is escaped, clearly.....
|
42
|
+
exec("pdflatex #{output_filename}")
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Produce a Latex
|
46
|
+
#
|
47
|
+
#
|
48
|
+
def latex
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Monkey patching jiggery-pokery to escape special LaTeX chars
|
55
|
+
# automatically from ERB <%= %> statements
|
56
|
+
class ERB::Compiler
|
57
|
+
alias_method :old_add_insert_cmd, :add_insert_cmd
|
58
|
+
alias_method :old_add_put_cmd, :add_put_cmd
|
59
|
+
|
60
|
+
def add_insert_cmd(out, content)
|
61
|
+
out.push("#{@insert_cmd}(clean_latex( (#{content}).to_s ))")
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
data/linkedin2cv.gemspec
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'linkedin2cv/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "linkedin2cv"
|
8
|
+
spec.version = Linkedin2CV::VERSION
|
9
|
+
spec.authors = ["mefellows"]
|
10
|
+
spec.email = ["matt.fellows@onegeek.com.au"]
|
11
|
+
spec.summary = "Linkedin2CV"
|
12
|
+
spec.description = "Turn your LinkedIn Profile into a professional resume in many formats (PDF / HTML5 / LaTeX / Asciidoc)"
|
13
|
+
spec.homepage = "https://github.com/mefellows/linkedin2cv"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
|
25
|
+
# Needs to be runtime for deployment on Heroku
|
26
|
+
spec.add_runtime_dependency "rspec"
|
27
|
+
spec.add_runtime_dependency "rake"
|
28
|
+
spec.add_runtime_dependency "clamp"
|
29
|
+
spec.add_runtime_dependency "json"
|
30
|
+
spec.add_runtime_dependency "log4r"
|
31
|
+
spec.add_runtime_dependency "sinatra"
|
32
|
+
spec.add_runtime_dependency "sinatra-contrib"
|
33
|
+
spec.add_runtime_dependency "sinatra-param"
|
34
|
+
spec.add_runtime_dependency "eventmachine"
|
35
|
+
spec.add_runtime_dependency "em-websocket", '=0.3.6'
|
36
|
+
spec.add_runtime_dependency "thin"
|
37
|
+
spec.add_runtime_dependency "foreman"
|
38
|
+
spec.add_runtime_dependency "dotenv"
|
39
|
+
spec.add_runtime_dependency "sinatra-websocket"
|
40
|
+
|
41
|
+
# Specific App dependencies
|
42
|
+
spec.add_runtime_dependency "linkedin-oauth2"
|
43
|
+
spec.add_runtime_dependency "haml"
|
44
|
+
spec.add_runtime_dependency "asciidoctor"
|
45
|
+
spec.add_runtime_dependency "launchy"
|
46
|
+
spec.add_runtime_dependency "tilt"
|
47
|
+
# spec.add_development_dependency "pact"
|
48
|
+
spec.add_development_dependency "webmock"
|
49
|
+
end
|
data/public/.bowerrc
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# EditorConfig helps developers define and maintain consistent
|
2
|
+
# coding styles between different editors and IDEs
|
3
|
+
# editorconfig.org
|
4
|
+
|
5
|
+
root = true
|
6
|
+
|
7
|
+
|
8
|
+
[*]
|
9
|
+
|
10
|
+
# Change these settings to your own preference
|
11
|
+
indent_style = space
|
12
|
+
indent_size = 2
|
13
|
+
|
14
|
+
# We recommend you to keep these unchanged
|
15
|
+
end_of_line = lf
|
16
|
+
charset = utf-8
|
17
|
+
trim_trailing_whitespace = true
|
18
|
+
insert_final_newline = true
|
19
|
+
|
20
|
+
[*.md]
|
21
|
+
trim_trailing_whitespace = false
|
@@ -0,0 +1 @@
|
|
1
|
+
* text=auto
|
data/public/.gitignore
ADDED
data/public/.jshintrc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
"node": true,
|
3
|
+
"browser": true,
|
4
|
+
"esnext": true,
|
5
|
+
"bitwise": true,
|
6
|
+
"camelcase": true,
|
7
|
+
"curly": true,
|
8
|
+
"eqeqeq": true,
|
9
|
+
"immed": true,
|
10
|
+
"indent": 2,
|
11
|
+
"latedef": true,
|
12
|
+
"newcap": true,
|
13
|
+
"noarg": true,
|
14
|
+
"quotmark": "single",
|
15
|
+
"regexp": true,
|
16
|
+
"undef": true,
|
17
|
+
"unused": true,
|
18
|
+
"strict": true,
|
19
|
+
"trailing": true,
|
20
|
+
"smarttabs": true,
|
21
|
+
"globals": {
|
22
|
+
"angular": false
|
23
|
+
}
|
24
|
+
}
|