init, complete rxresume converter

This commit is contained in:
Reorx 2023-02-01 23:23:17 +08:00
commit 9b7c0e9645
5 changed files with 3321 additions and 0 deletions

3
README.md Executable file
View File

@ -0,0 +1,3 @@
# jsoncv
A toolset to build your CV / Resume with JSON and generate elegant HTML and PDF documents.

View File

@ -0,0 +1,245 @@
import dayjs from 'dayjs';
import { readFileSync } from 'fs';
import objectPath from 'object-path';
// defines which property in source json (value) to map to which property in JSONResume json (key).
// the first level keys of conversionMap are the same keys in JSONResume.
const conversionMap = {
basics: {
name: 'name',
email: 'email',
phone: 'phone',
url: 'website',
// need to merge 'headline' in 'summary'
summary: 'summary',
image: 'photo.url',
location: {
key: 'location',
conversion: {
address: 'address',
postalCode: 'postalCode',
city: 'city',
countryCode: 'country',
region: 'region',
}
},
},
education: {
"institution": 'institution',
"url": 'url',
"area": 'area',
"studyType": 'degree',
"startDate": 'date.start',
"endDate": 'date.end',
"score": 'score',
"courses": 'courses',
/* droped properties: summary */
},
work: {
/* missing properties: location, description, highlights */
name: 'name',
position: 'position',
url: 'url',
// need to reformat to YYYY-MM-DD
startDate: 'date.start',
endDate: 'date.end',
summary: 'summary',
},
skills: {
"name": 'name',
// need to merge 'levelNum' in 'level'
"level": 'level',
"keywords": 'keywords',
},
projects: {
/* missing properties: roles, entity, type */
"name": 'name',
"description": 'description',
"keywords": 'keywords',
"startDate": 'date.start',
"endDate": 'date.end',
"url": 'url',
// need to convert 'summary' to 'highlights'
},
// sideProjects is grabed from custom section with name "open-source" or "side-projects"
languages: {
"language": 'name',
// need to merge 'levelNum' in 'fluency'
"fluency": 'level',
},
references: {
"name": 'name',
// need to merge 'relationship', 'phone', 'email' in 'reference'
"reference": 'summary',
},
awards: {
"title": 'title',
"date": 'date',
"awarder": 'awarder',
// need to merge 'url' in 'summary'
"summary": 'summary',
},
publications: {
"name": 'name',
"publisher": 'publisher',
"releaseDate": 'date',
"url": 'url',
"summary": 'summary',
},
interests: {
"name": 'name',
"keywords": 'keywords',
},
volunteer: {
/* missing properties: highlights */
"organization": 'organization',
"position": 'position',
"url": 'url',
"startDate": 'date.start',
"endDate": 'date.end',
"summary": 'summary',
},
}
// convert function
function convert(source) {
const result = {}
// basics
result.basics = convertObject(source.basics, conversionMap.basics, (item, result) => {
if (item.headline) {
result.summary = `${item.headline}\n${result.summary || ''}`
}
})
// basics.profiles
/* sections */
const sections = source.sections || {}
const sectionToResult = (sectionKey, itemCustomFunc, resultKey) => {
const section = sections[sectionKey]
if (!section) return
if (!section.items || section.items.length === 0) return
if (!resultKey) resultKey = sectionKey
result[resultKey] = section.items.map(item => convertObject(item, conversionMap[resultKey], itemCustomFunc))
}
// education
sectionToResult('education', (item, result) => {
if (item.summary) {
console.log(`warn: summary is dropped in education item: institution=${item.institution} summary=${item.summary}`)
}
formatDatesInResult(result)
})
// work
sectionToResult('work', (item, result) => {
formatDatesInResult(result)
})
// skills
sectionToResult('skills', (item, result) => {
if (item.levelNum) {
if (result.level) {
result.level = `${result.level}, ${item.levelNum}`
} else {
result.level = `${item.levelNum}`
}
}
})
// projects
sectionToResult('projects', (item, result) => {
if (item.summary) {
result.highlights = item.summary.split('\n')
}
formatDatesInResult(result)
})
// languages
sectionToResult('languages', (item, result) => {
if (item.levelNum) {
if (result.fluency) {
result.fluency = `${result.fluency}, ${item.levelNum}`
} else {
result.fluency = `${item.levelNum}`
}
}
})
// references
sectionToResult('references', (item, result) => {
const lines = []
if (item.relationship)
lines.push(`relationship: ${item.relationship}`)
if (item.phone)
lines.push(`phone: ${item.phone}`)
if (item.email)
lines.push(`email: ${item.email}`)
if (lines.length > 0)
result.reference = lines.join('\n') + '\n\n' + result.reference
})
// awards
sectionToResult('awards', (item, result) => {
if (item.url) {
result.summary = `${item.url}\n\n${result.summary}`
}
})
// publications
sectionToResult('publications')
// interests
sectionToResult('interests')
// volunteer
sectionToResult('volunteer', (item, result) => {
formatDatesInResult(result)
})
return result
}
function convertObject(obj, objMap, customFunc) {
// console.log(`convert ${JSON.stringify(obj)} by ${objMap}`)
const result = {}
for (const [key, conversion] of Object.entries(objMap)) {
const value = objectPath.get(obj, key)
if (value === undefined) continue
if (typeof conversion === 'string') {
result[conversion] = value
} else if (typeof conversion === 'object') {
result[conversion.key] = convertObject(value, conversion.conversion)
}
}
if (customFunc)
customFunc(obj, result)
return result
}
const dateKeys = ['startDate', 'endDate']
function formatDatesInResult(result) {
for (const key of dateKeys) {
const dateStr = result[key]
if (!dateStr) continue
result[key] = dayjs(dateStr).format('YYYY-MM-DD')
}
}
/* main */
// read json from file, which is the first argument
const sourceJSON = readFileSync(process.argv[2], 'utf8')
// parse json
const source = JSON.parse(sourceJSON)
const result = convert(source)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)

View File

@ -0,0 +1,443 @@
{
"basics": {
"name": "Cat Bird",
"email": "catbird@example.com",
"phone": "(929) 266 8017",
"photo": {
"url": "https://cdn.rxresu.me/uploads/295357/364529/1675231730085.png",
"filters": {
"size": 128,
"shape": "square",
"border": false,
"grayscale": false
},
"visible": true
},
"summary": "This is my summary",
"website": "https://example.com",
"headline": "This is my headline",
"location": {
"city": "New York",
"region": "Brooklyn",
"address": "Wallstreet 110",
"country": "US",
"postalCode": "97200"
},
"profiles": [
{
"network": "Twitter",
"username": "foo",
"url": "https://twitter.com/foo",
"id": "9b619722-b458-4f53-97ae-a9b4041ccdeb"
},
{
"network": "LinkedIn",
"username": "foo1",
"url": "https://linkedin.com/foo1",
"id": "a771f04a-1fe0-482e-94d5-7ec1608920b4"
}
],
"birthdate": "1990-01-01"
},
"sections": {
"work": {
"id": "work",
"name": "Work Experience",
"type": "work",
"items": [
{
"id": "4fc76d7c-985d-4608-b270-a3fa158f1442",
"url": "https://example.com",
"date": {
"end": "2021-08-04",
"start": "2020-05-31T16:00:00.000Z"
},
"name": "Dodo",
"summary": "- Worked with design and development teams to make custom software solutions.\n- Deployed and delivered the website to CDN and dedicated servers.\n- Troubleshot and resolved any issues that arose from the customers.",
"position": "Technical Director"
}
],
"columns": 1,
"visible": true
},
"awards": {
"id": "awards",
"name": "Awards",
"type": "basic",
"items": [
{
"title": "Award 1",
"awarder": "SCP",
"date": "2022-02-01",
"url": "https://scp-wiki.wikidot.com/",
"summary": "Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License",
"id": "2526e842-3883-4057-81ca-9fcdfa925cb2"
}
],
"columns": 2,
"visible": false
},
"skills": {
"id": "skills",
"name": "Skills",
"type": "basic",
"items": [
{
"id": "8da94824-4545-4842-961a-dd5bdb9ef303",
"name": "Back-end Dev",
"level": "Expert - 10 years",
"keywords": [
"Python",
"Django",
"Go",
"gRPC",
"Docker",
"Linux"
],
"levelNum": 0
},
{
"id": "c549c120-fd05-4145-9094-a715d15997c4",
"name": "Front-end Dev",
"level": "Advanced - 6 years",
"keywords": [
"JavaScript",
"TypeScript",
"Webpack",
"CSS",
"SCSS"
],
"levelNum": 0
}
],
"columns": 2,
"visible": true
},
"projects": {
"id": "projects",
"name": "Projects",
"type": "basic",
"items": [
{
"id": "d714c2ed-9f84-40b3-ade6-8ecd78d4afc6",
"url": "",
"date": {
"end": "2021-10-29T16:00:00.000Z",
"start": "2021-05-31T16:00:00.000Z"
},
"name": "Dodo Cloud for Business",
"summary": "Collected and analyzed customer requirements, designed the on-premises system, managed the developer team, and delivered the product in a limited time.",
"keywords": [
"Go",
"Kubernetes",
"HPC",
"WebRTC"
],
"description": "A hybrid cloud platform for computer-aided engineering in HPC clusters."
},
{
"id": "51be9a1f-5930-4ccc-a2d3-dcfc1d88d840",
"url": "https://www.example.com.cn/",
"date": {
"end": "2020-09-30T16:00:00.000Z",
"start": "2020-04-30T16:00:00.000Z"
},
"name": "Example.com.cn",
"summary": "Rebuilt front-end and back-end of the website with the latest technology and systematic modules.",
"keywords": [
"Gatsby",
"React",
"Chart.js",
"Netlify CMS"
],
"description": "The official website of Example."
}
],
"columns": 1,
"visible": true
},
"education": {
"id": "education",
"name": "Education",
"type": "basic",
"items": [
{
"id": "ec3a46c0-d6a3-458c-82fc-2c30dacaa8f8",
"url": "https://www.big.edu.cn/",
"area": "Computer Science and Technology",
"date": {
"end": "2013-01-01T00:00:00.000Z",
"start": "2009-01-01T00:00:00.000Z"
},
"degree": "Bachelor of Engineering",
"courses": [
"Course 1"
],
"institution": "Big University, School of Computer Science",
"score": "7",
"summary": "Just learned some casual CS skills here."
}
],
"columns": 1,
"visible": true
},
"interests": {
"id": "interests",
"name": "Interests",
"type": "basic",
"items": [
{
"name": "Astrology",
"keywords": [
"Zodiac signs",
"horoscopes",
"planetary alignment"
],
"id": "d2e5b39e-f6c8-404e-bb51-47790a42e568"
}
],
"columns": 2,
"visible": false
},
"languages": {
"id": "languages",
"name": "Languages",
"type": "basic",
"items": [
{
"name": "English",
"level": "Fluent",
"levelNum": 10,
"id": "da5f850f-2628-4dab-9c9c-70a1f8b68b7d"
}
],
"columns": 2,
"visible": false
},
"volunteer": {
"id": "volunteer",
"name": "Volunteer Experience",
"type": "basic",
"items": [
{
"organization": "Vol Org",
"position": "Worker",
"date": {
"start": "2018-05-01",
"end": "2021-06-01"
},
"url": "https://example.com",
"summary": "Vol summary",
"id": "ced94fd5-f134-425f-803b-8a5a9dd7ced9"
}
],
"columns": 2,
"visible": false
},
"references": {
"id": "references",
"name": "References",
"type": "basic",
"items": [
{
"name": "John Smith",
"relationship": "Friend",
"phone": "9293334444",
"email": "john@example.com",
"summary": "Ref summary",
"id": "b25da525-a0c3-4612-99f2-62e9e46a5ac2"
}
],
"columns": 1,
"visible": false
},
"publications": {
"id": "publications",
"name": "Publications",
"type": "basic",
"items": [
{
"name": "Pub 1",
"publisher": "Amazon",
"date": "2018-06-01",
"url": "https://amazon.com",
"summary": "Pub summary",
"id": "705d2bbd-5186-43e8-a5b3-907585802e4d"
}
],
"columns": 2,
"visible": false
},
"certifications": {
"id": "certifications",
"name": "Certifications",
"type": "basic",
"items": [
{
"name": "Cert 1",
"issuer": "Duolingo",
"date": "2017-01-05",
"url": "https://www.duolingo.com/",
"summary": "Cert summary",
"id": "a0a3279b-412e-4133-8da5-e8ea40299239"
}
],
"columns": 2,
"visible": false
},
"596aa615-b3cb-48a2-8d9c-d70e92e36d20": {
"name": "Open-source",
"type": "custom",
"items": [
{
"id": "1f4f92aa-cb12-493a-8f48-df6e26a3c033",
"url": "",
"date": {
"end": "",
"start": ""
},
"level": "",
"title": "httpstat",
"summary": "A CLI tool to show HTTP statistics in a beautiful and clear way.\n\n[Link](https://github.com/reorx/httpstat)",
"keywords": [],
"levelNum": 0,
"subtitle": ""
},
{
"id": "67bf3677-9332-4ef6-804f-e7554538f38a",
"url": "",
"date": {
"end": "",
"start": ""
},
"level": "",
"title": "sui2",
"summary": "A self-hosted Startpage app that supports keyboard navigation and PWA.\n\n[Link](https://github.com/reorx/sui2), [Demo](https://reorx.github.io/sui2/)",
"keywords": [],
"levelNum": 0,
"subtitle": ""
},
{
"id": "7bb0ba5d-3beb-453d-af3e-2730afe76279",
"url": "",
"date": {
"end": "",
"start": ""
},
"level": "",
"title": "python-terminal-color",
"summary": "A drop-in single file Python library for printing colors in terminal.\n\n[Link](https://github.com/reorx/python-terminal-color)",
"keywords": [],
"levelNum": 0,
"subtitle": ""
},
{
"id": "312306bb-067b-4c31-aa64-8d9dd6f02d33",
"url": "",
"date": {
"end": "",
"start": ""
},
"level": "",
"title": "Deptest",
"summary": "A Python testing framework that can control the execution order of the test units.\n\n[Link](https://github.com/reorx/deptest)",
"keywords": [],
"levelNum": 0,
"subtitle": ""
},
{
"id": "fa356d82-31da-43ce-bc92-2dddee46f920",
"url": "",
"date": {
"end": "",
"start": ""
},
"level": "",
"title": "Obsidian paste image rename",
"summary": "An Obsidian plugin that provides an intuitive interface to rename pasted images and other attachments.\n\n[Link](https://github.com/reorx/obsidian-paste-image-rename)\n\n",
"keywords": [],
"levelNum": 0,
"subtitle": ""
}
],
"columns": 2,
"visible": true
},
"cc9952c7-4ce3-44ab-82b6-995a6a3f2f9e": {
"name": "Custom Section",
"type": "custom",
"visible": true,
"columns": 2,
"items": [
{
"title": "Custom 1",
"subtitle": "sub title",
"date": {
"start": "2022-05-01",
"end": "2023-01-01"
},
"url": "https://example.com",
"level": "Whatever",
"levelNum": 10,
"summary": "Custom summary",
"keywords": [
"key1",
"key2"
],
"id": "a4392fc9-08f3-4865-b653-02344fef5ec1"
}
]
}
},
"metadata": {
"css": {
"value": "/* Enter custom CSS here */\n\nh4 {\n margin: 15px 0;\n}\nh4.font-bold {\n font-size: 17px !important;\n}\n.markdown p {\n line-height: normal !important;\n}",
"visible": true
},
"date": {
"format": "MMM YYYY"
},
"page": {
"format": "A4"
},
"theme": {
"text": "#000000",
"primary": "#f44336",
"background": "#ffffff"
},
"layout": [
[
[
"education",
"work",
"projects",
"skills",
"596aa615-b3cb-48a2-8d9c-d70e92e36d20",
"cc9952c7-4ce3-44ab-82b6-995a6a3f2f9e"
],
[
"interests",
"languages",
"awards",
"references",
"certifications",
"volunteer",
"publications"
]
]
],
"locale": "en",
"template": "leafish",
"typography": {
"size": {
"body": 13,
"heading": 28
},
"family": {
"body": "Open Sans",
"heading": "Open Sans"
}
}
},
"public": true
}

2607
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "jsoncv",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@iconify-json/mdi": "^1.1.33",
"@iconify/utils": "^2.0.0",
"sass": "^1.54.8",
"vite": "^3.0.7",
"vite-plugin-handlebars": "^1.6.0"
},
"dependencies": {
"dayjs": "^1.11.7",
"iconify-icon": "^1.0.0",
"object-path": "^0.11.8"
}
}