linkedin2cv 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|