From b333e2f2db0a890a7cd935992f806e2a7fa3c68d Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 17 Mar 2025 11:27:45 +0100 Subject: [PATCH 1/9] ref(letter): Remove quarto letter --- letter.qmd | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 letter.qmd diff --git a/letter.qmd b/letter.qmd deleted file mode 100644 index 6b4078d..0000000 --- a/letter.qmd +++ /dev/null @@ -1,53 +0,0 @@ ---- -backaddress: Marty Oehme, Körnerstraße 54, 04107 Leipzig -fromname: Marty Oehme -fromaddress: | - Körnerstraße 54 - 04107 Leipzig -fromphone: "0177 / 377 49 49" -place: Leipzig, DAY MONTH YEAR -placeseparator: "‎ " -sendto: | - NAME OF COMPANY - ITS ADDRESS STREET AND NUMBER - LOCATION ZIPCODE -lang: en -subject: Bewerbung für JOBSTELLE -signature: Marty Oehme -opening: Dear CONTACTNAME -closing: Sincerely -format: - pdf: - template: templates/letter.latex ---- - -After two years of cooperation, I recently finished the final in a row of projects undertaken as consultant for UNU-WIDER and the ILO. -I am glad to have happened upon your job posting for the position of project officer as part-time worker with minimum 32 hours, as for me it may allow a more medium term stability than the previous short-term freelance positions did. - -Over the previous two years, I have primarily provided research assistance for three major projects under the helm of the UN, the ILO and Roskilde University: -In addition to the descriptive analysis undertaken for all of these projects, -my consultancy for UNU-WIDER required the creation of a time-series visualization on the basis of empirical analysis over roughly 200.000 observations within the UN's WIID dataset. -The previous work for the ILO and Roskilde University instead consisted of deep research for the creation of wide-ranging scoping reviews, -which necessitated collecting, organizing and cleaning datasets of around 2000 source potentials. -I accomplished both of these tasks more efficiently with the help of Python and its data analysis modules which is also where my primary statistical programming focus lies. - -Additionally, I have long been helping publish academic, particularly empirical, works, -first in the role of academic assistant and later on as editorial consultant. -Perhaps most personally beneficial for the current position was the publication of a large academic anthology under the SPIWORK project: -During this time I worked with clients located internationally, -collaborating directly to create clear and concise manuscripts for each individual topic, -before merging the divergent aspects into a final coherent whole while also fulfilling publisher's formal requirements. -I would love to bring both the analytical and editorial skillsets together in my new work, -to be able to push them forward in tandem. - -Lastly, my skills of communication, especially international communication skills, have been honed throughout this time as I was both participant and leader of small (from 3 people) to medium-sized (up to 8 people) teams. -Collaboration in this way was both exciting and straightforward for me, -and I firmly believe concise and clear communication both engenders easier productivity flows -and is fundamental to the ability to precisely frame topics as complex as energy transition. - -My interactions with processes of both long-term energy planning and renewables-based electrification of end-use sectors primarily stem from research undertaken in Vietnam and Benin under the helm of the Agence française de développement, -where the eventual distributional impact of access to clean water and new energy grids provided the project's focus. -I have, however, both the willingness and curiosity to dive deeper into these topics and their related issues of regulatory frameworks, financing and policy framing. - -I will be happy to hear back from you and am confident that I could be a good match. -Should you agree that my profile is a good fit for the offered position then do not hesitate to contact me and I would be delighted to arrange a meeting. From 948510fb13f3e0e2536c96152727b12eb1d0e67e Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 5 Feb 2025 15:15:55 +0100 Subject: [PATCH 2/9] Move element styling and smartypants to lib --- cv.typ | 2 ++ lib.typ | 21 +++++++++++++++------ resume.typ | 2 ++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cv.typ b/cv.typ index a17b353..227584a 100644 --- a/cv.typ +++ b/cv.typ @@ -1,6 +1,8 @@ #import "lib.typ": * #let cv(contents, use_sidebar: false) = { + show: style + show: smartypants set text(lang: lang) let date_formatting = { diff --git a/lib.typ b/lib.typ index 51862d7..09b33e1 100644 --- a/lib.typ +++ b/lib.typ @@ -1,10 +1,19 @@ -#show heading: set text(font: "New Computer Modern") -#show link: underline -// smartypants and latex compatibility -#show "--": [#sym.dash.en] -#show "---": [#sym.dash.em] -#show "\&": [#sym.amp] +// set some styles +#let style(it) = { + show heading: set text(font: "New Computer Modern") + show link: underline + it +} + +// transform md-similes to actual symbols +#let smartypants(it) = { + // smartypants and latex compatibility + show "--": [#sym.dash.en] + show "---": [#sym.dash.em] + show "\&": [#sym.amp] + it +} // Choose the compiled language through cli by doing // diff --git a/resume.typ b/resume.typ index 489d044..789e029 100644 --- a/resume.typ +++ b/resume.typ @@ -34,6 +34,8 @@ } #let resume(contents, main: ("experience", "education"), sidebar: ("volunteering", "languages", "skills")) = { + show: style + show: smartypants set text(lang: lang) let date_formatting = { From 0dd715d8069e33b3298c713e69d1cbc9541d8267 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Mar 2025 14:43:43 +0100 Subject: [PATCH 3/9] fix(content): Rephrase ZeitRaum support consultations --- content.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content.yml b/content.yml index 2afb449..0cd575d 100644 --- a/content.yml +++ b/content.yml @@ -386,8 +386,8 @@ volunteering: en: Transferring Digital Competence in Aging - de: Wöchentlicher Workshop zur Entwicklung von Selbstvertrauen und Kompetenz mit Smartphones en: Weekly workshop on developing confidence and competence with smartphones - - de: Personalisierte technische Hilfsmeetings und technische Support-Beratungen - en: Personalized tech assistance appointments and technical support consultations + - de: Personalisierte technische Unterstützung und personalisierte Einzelberatungen + en: Personalized tech assistance appointments and individual technical support consultations - title: de: Verpixelt en: Verpixelt From f60991344db7d8212db2d926bb18d9e2017aaf18 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Mar 2025 14:43:43 +0100 Subject: [PATCH 4/9] feat(resume): Choose experience display by client,type,chronological Can be set by setting the point in 'main' array to 'experience_by_client', 'experience_by_type' or just 'experience' respectively. --- resume.typ | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/resume.typ b/resume.typ index 789e029..16119f4 100644 --- a/resume.typ +++ b/resume.typ @@ -33,7 +33,7 @@ } } -#let resume(contents, main: ("experience", "education"), sidebar: ("volunteering", "languages", "skills")) = { +#let resume(contents, main: ("experience_by_type", "education"), sidebar: ("volunteering", "languages", "skills")) = { show: style show: smartypants set text(lang: lang) @@ -77,11 +77,20 @@ ) } - if "experience" in main and "experience" in contents { + if "experience_by_type" in main and "experience" in contents { let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) section(title: title)[] by_experience_type(experience: contents.experience, type: contents.experience_types) } + if "experience_by_client" in main and "experience" in contents { + let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) + section(title: title)[] + by_client(experience: contents.experience) + } + if "experience" in main and "experience" in contents { + let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) + section(title: title, entries: contents.experience)[] + } if "education" in main and "education" in contents { let title = (en: "Education", de: "Ausbildung").at(lang) From 1a62b90df77a38ba20d5409f97482777baaa4002 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Mar 2025 14:57:57 +0100 Subject: [PATCH 5/9] feat(resume): Add ability to reorder sections Both in main and in sidebar, sections can be reordered simply by changing the order they appar in the array adding them. `#resume.with(main:("education", "experience"))` displays education before experience items. --- resume.typ | 136 +++++++++++++++++++++++++++-------------------------- 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/resume.typ b/resume.typ index 16119f4..8309611 100644 --- a/resume.typ +++ b/resume.typ @@ -68,84 +68,88 @@ header(contents.about) let body = { - if "summary" in main and "summary" in contents { - section( - title: "", - { - contents.summary.at(lang) - }, - ) - } + for item in main { + if item == "summary" and "summary" in contents { + section( + title: "", + { + contents.summary.at(lang) + }, + ) + } - if "experience_by_type" in main and "experience" in contents { - let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) - section(title: title)[] - by_experience_type(experience: contents.experience, type: contents.experience_types) - } - if "experience_by_client" in main and "experience" in contents { - let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) - section(title: title)[] - by_client(experience: contents.experience) - } - if "experience" in main and "experience" in contents { - let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) - section(title: title, entries: contents.experience)[] - } + if item == "experience_by_type" and "experience" in contents { + let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) + section(title: title)[] + by_experience_type(experience: contents.experience, type: contents.experience_types) + } + if item == "experience_by_client" and "experience" in contents { + let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) + section(title: title)[] + by_client(experience: contents.experience) + } + if item == "experience" and "experience" in contents { + let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) + section(title: title, entries: contents.experience)[] + } - if "education" in main and "education" in contents { - let title = (en: "Education", de: "Ausbildung").at(lang) - section(title: title, entries: contents.thesis + contents.education)[] - } + if item == "education" and "education" in contents { + let title = (en: "Education", de: "Ausbildung").at(lang) + section(title: title, entries: contents.thesis + contents.education)[] + } - if "volunteering" in main and "volunteering" in contents { - let title = (en: "Volunteer Work", de: "Ehrenamt").at(lang) - section(title: title, entries: contents.volunteering)[] - } + if item == "volunteering" and "volunteering" in contents { + let title = (en: "Volunteer Work", de: "Ehrenamt").at(lang) + section(title: title, entries: contents.volunteering)[] + } - if "skills" in main and "skills" in contents { - let title = (en: "Qualifications", de: "Qualifikationen").at(lang) - section( - title: title, - { - skill_item(item: contents.skills) - }, - ) - } + if item == "skills" and "skills" in contents { + let title = (en: "Qualifications", de: "Qualifikationen").at(lang) + section( + title: title, + { + skill_item(item: contents.skills) + }, + ) + } - if "languages" in main and "languages" in contents { - let title = (en: "Languages", de: "Sprachen").at(lang) - section( - title: title, - { - skill_item(item: contents.languages) - }, - ) + if item == "languages" and "languages" in contents { + let title = (en: "Languages", de: "Sprachen").at(lang) + section( + title: title, + { + skill_item(item: contents.languages) + }, + ) + } } } let sidebar = { - if "volunteering" in sidebar and "volunteering" in contents { - let title = (en: "Volunteer Work", de: "Ehrenamt").at(lang) - [== #title] - for e in contents.at("volunteering") { - [ - - *#e.title.at(lang)* (#e.date.at(lang)) - #par(e.bullets.at(0).at(lang)) \ - ] + for item in sidebar { + if item == "volunteering" and "volunteering" in contents { + let title = (en: "Volunteer Work", de: "Ehrenamt").at(lang) + [== #title] + for e in contents.at("volunteering") { + [ + - *#e.title.at(lang)* (#e.date.at(lang)) + #par(e.bullets.at(0).at(lang)) \ + ] + } } - } - if "languages" in sidebar and "languages" in contents { - let title = (en: "Languages", de: "Sprachen").at(lang) - [== #title] - skill_item(item: contents.languages, is_sidebar: true) - [\ ] - } + if item == "languages" and "languages" in contents { + let title = (en: "Languages", de: "Sprachen").at(lang) + [== #title] + skill_item(item: contents.languages, is_sidebar: true) + [\ ] + } - if "skills" in sidebar and "skills" in contents { - let title = (en: "Qualifications", de: "Kenntnisse").at(lang) - [== #title] - skill_item(item: contents.skills, is_sidebar: true) + if item == "skills" and "skills" in contents { + let title = (en: "Qualifications", de: "Kenntnisse").at(lang) + [== #title] + skill_item(item: contents.skills, is_sidebar: true) + } } } From 37c59e71db0e32849c0dc07616a04ce06ab09d67 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Mar 2025 14:57:57 +0100 Subject: [PATCH 6/9] docs: Update README Give light hints to how the section arrays work. --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae62ba2..29a97f8 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,28 @@ Is called like the following: ``` This is the default invocation, though sidebar and main body sections can be exchanged at will. +The following sections currently exist: + +- education +- experience, subdivided in "experience", "experience_by_client" and "experience_by_type" +- languages +- skills +- summary +- volunteering + +Sections in the main body or sidebar can be reordered at will. + +## Advanced experience settings + +The experience section has 3 forms: + +Purely chronological ("experience"), which is the default; +separated by client worked for ("experience_by_client"); +separated by the type of work undertaken, then further separated by client worked for ("experience_by_client"). + +These options are intended especially for self-employed / entrepreneurial CVs. +They let you subdivide your work experience whichever way works best, +and additionally divide work undertaken for your own employ or salaried positions for example. TODO: @@ -42,4 +64,4 @@ TODO: - [x] Can be switched between Ger/En - [x] Try sidebar version - [ ] Generalize sidebar version through abstraction/extractions -- [ ] unify items: experience/education/(thesis?) +- [x] unify items: experience/education/(thesis?) From c831b008b93b9875646c7dd7c22b253eb4bc9b51 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Mar 2025 14:57:57 +0100 Subject: [PATCH 7/9] ref(resume): Move by_client and by_experience_type to lib In the continuous process of moving everything away from the main resume body, move these experience functions away. --- lib.typ | 33 +++++++++++++++++++++++++++++++++ resume.typ | 33 --------------------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lib.typ b/lib.typ index 09b33e1..c27d018 100644 --- a/lib.typ +++ b/lib.typ @@ -112,6 +112,39 @@ } }; +// TODO: make it _return_ the data, not display it on its own +#let by_client(experience: ()) = { + let by_client = (:) + + for item in experience { + let client = item.place.at(lang) + if client not in by_client { + by_client.insert(client, ()) + } + by_client.at(client).push((title: item.title.at(lang), date: item.date.at(lang))) + } + + for (client, jobs) in by_client { + [*#client*:] + for j in jobs { + [- #j.title #h(1fr) #j.date] + } + } +} + +#let by_experience_type(type: (), experience: ()) = { + let by_ty = (:) + for (id, desc) in type { + let matching_exp_items = experience.filter(item => int(item.typeid) == int(id)) + if matching_exp_items.len() == 0 { + return + } + + [=== _#desc.at(lang)_] + by_client(experience: matching_exp_items) + } +} + // Slightly re-styled entry with PLACE first and TITLE second #let education_entry(item: ()) = { assert( diff --git a/resume.typ b/resume.typ index 8309611..df2715a 100644 --- a/resume.typ +++ b/resume.typ @@ -1,38 +1,5 @@ #import "lib.typ": * -// TODO: make it _return_ the data, not display it on its own -#let by_client(experience: ()) = { - let by_client = (:) - - for item in experience { - let client = item.place.at(lang) - if client not in by_client { - by_client.insert(client, ()) - } - by_client.at(client).push((item.title.at(lang), item.date.at(lang))) - } - - for (client, jobs) in by_client { - [*#client*:] - for j in jobs { - [- #j.at(0) #h(1fr) #j.at(1)] - } - } -} - -#let by_experience_type(type: (), experience: ()) = { - let by_ty = (:) - for (id, desc) in type { - let matching_exp_items = experience.filter(item => int(item.typeid) == int(id)) - if matching_exp_items.len() == 0 { - return - } - - [=== _#desc.at(lang)_] - by_client(experience: matching_exp_items) - } -} - #let resume(contents, main: ("experience_by_type", "education"), sidebar: ("volunteering", "languages", "skills")) = { show: style show: smartypants From df480875df8eb088ff4c1e2a17d7d803a3b978bc Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Mar 2025 14:57:57 +0100 Subject: [PATCH 8/9] ref(lib): Rename skill_item to sidebar_entry We use the function for all entries in the sidebar (volunteering, languages, etc) not just skills. --- cv.typ | 8 ++++---- lib.typ | 36 ++++++++++++++++++------------------ resume.typ | 9 ++++----- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/cv.typ b/cv.typ index 227584a..19a5459 100644 --- a/cv.typ +++ b/cv.typ @@ -65,7 +65,7 @@ section( title: title, { - skill_item(item: contents.skills) + sidebar_entry(item: contents.skills) }, ) } @@ -75,7 +75,7 @@ section( title: title, { - skill_item(item: contents.languages) + sidebar_entry(item: contents.languages) }, ) } @@ -97,14 +97,14 @@ if "languages" in contents { let title = (en: "Languages", de: "Sprachen").at(lang) [== #title] - skill_item(item: contents.languages, is_sidebar: true) + sidebar_entry(item: contents.languages, is_sidebar: true) [\ ] } if "skills" in contents { let title = (en: "Qualifications", de: "Kenntnisse").at(lang) [== #title] - skill_item(item: contents.skills, is_sidebar: true) + sidebar_entry(item: contents.skills, is_sidebar: true) } } diff --git a/lib.typ b/lib.typ index c27d018..d82a0a1 100644 --- a/lib.typ +++ b/lib.typ @@ -90,6 +90,24 @@ } } + +#let sidebar_entry(item: (), is_sidebar: false) = { + let side_list(body) = if is_sidebar { list(body) } else { par(body) } + for skill in item { + side_list({ + [*#skill.name.at(lang)*] + if is_sidebar [\ ] else [ (] + for (i, v) in skill.items.enumerate() { + [#v.at(lang)] + if i < skill.items.len() - 1 { + [, ] + } + } + if not is_sidebar [)] + }) + } +} + #let horizon_line() = { v(-3pt) line(length: 100%) @@ -161,21 +179,3 @@ [*#item.title.at(lang)* #item.place.at(lang) #h(1fr)] [#par(item.abstract.at(lang))] } - -// skill-specific entry, changing its style for sidebar -#let skill_item(item: (), is_sidebar: false) = { - let side_list(body) = if is_sidebar { list(body) } else { par(body) } - for skill in item { - side_list({ - [*#skill.name.at(lang)*] - if is_sidebar [\ ] else [ (] - for (i, v) in skill.items.enumerate() { - [#v.at(lang)] - if i < skill.items.len() - 1 { - [, ] - } - } - if not is_sidebar [)] - }) - } -} diff --git a/resume.typ b/resume.typ index df2715a..42764df 100644 --- a/resume.typ +++ b/resume.typ @@ -75,7 +75,7 @@ section( title: title, { - skill_item(item: contents.skills) + sidebar_entry(item: contents.skills) }, ) } @@ -85,7 +85,7 @@ section( title: title, { - skill_item(item: contents.languages) + sidebar_entry(item: contents.languages) }, ) } @@ -108,14 +108,14 @@ if item == "languages" and "languages" in contents { let title = (en: "Languages", de: "Sprachen").at(lang) [== #title] - skill_item(item: contents.languages, is_sidebar: true) + sidebar_entry(item: contents.languages, is_sidebar: true) [\ ] } if item == "skills" and "skills" in contents { let title = (en: "Qualifications", de: "Kenntnisse").at(lang) [== #title] - skill_item(item: contents.skills, is_sidebar: true) + sidebar_entry(item: contents.skills, is_sidebar: true) } } } @@ -150,7 +150,6 @@ align( right, block( - fill: luma(250), width: 90%, { v(15pt) From 72d40e509447c8ebd8f8db5859b5b10221cf9e2a Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Mar 2025 14:57:57 +0100 Subject: [PATCH 9/9] ref(smartypants): Move smartypants application to lib --- lib.typ | 15 ++++++++------- resume.typ | 1 - 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib.typ b/lib.typ index d82a0a1..ff4c0ed 100644 --- a/lib.typ +++ b/lib.typ @@ -1,11 +1,4 @@ -// set some styles -#let style(it) = { - show heading: set text(font: "New Computer Modern") - show link: underline - it -} - // transform md-similes to actual symbols #let smartypants(it) = { // smartypants and latex compatibility @@ -15,6 +8,14 @@ it } +// set some styles +#let style(it) = { + show heading: set text(font: "New Computer Modern") + show link: underline + show: smartypants + it +} + // Choose the compiled language through cli by doing // // $ typst compile --input lang=de cv.typ diff --git a/resume.typ b/resume.typ index 42764df..2930ed1 100644 --- a/resume.typ +++ b/resume.typ @@ -2,7 +2,6 @@ #let resume(contents, main: ("experience_by_type", "education"), sidebar: ("volunteering", "languages", "skills")) = { show: style - show: smartypants set text(lang: lang) let date_formatting = {