From ca33cb142f94d4f227fb46fea4e5ad13aa37b179 Mon Sep 17 00:00:00 2001
From: Marty Oehme <marty.oehme@gmail.com>
Date: Wed, 29 Jan 2025 11:52:17 +0100
Subject: [PATCH] Begin deduplicating code with a lib

Starting to move the separated efforts of functionality between CV and
resume into a library from which to import. Will take additional
simplification still.
---
 cv.typ     | 163 +++--------------------------------------------------
 lib.typ    | 139 +++++++++++++++++++++++++++++++++++++++++++++
 resume.typ | 141 +--------------------------------------------
 3 files changed, 150 insertions(+), 293 deletions(-)
 create mode 100644 lib.typ

diff --git a/cv.typ b/cv.typ
index 518cd71..a17b353 100644
--- a/cv.typ
+++ b/cv.typ
@@ -1,149 +1,4 @@
-#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]
-
-// Choose the compiled language through cli by doing
-//
-// $ typst compile --input lang=de cv.typ
-//
-#let lang = {
-  if "lang" in sys.inputs and sys.inputs.lang == "de" {
-    "de"
-  } else {
-    "en"
-  }
-}
-#let sel_word_lang(de: "", en: "") = {
-  if lang == "de" {
-    de
-  } else {
-    en
-  }
-}
-
-#let _columns_3(left_body, center_body, right_body) = {
-  block[
-    #box(width: 1fr)[
-      #align(left)[#left_body]
-    ]
-    #box(width: 1fr)[
-      #align(center)[#center_body]
-    ]
-    #box(width: 1fr)[
-      #align(right)[#right_body]
-    ]
-  ]
-}
-
-#let header(about, columns: (1.5fr, 1fr, 1fr)) = {
-  [= #about.fullname]
-  let contact_fields = (
-    for c in about.contact {
-      if "link" in c {
-        ([#c.icon ~ #link(c.link)[#c.text]],)
-      } else {
-        ([#c.icon ~ #c.text],)
-      }
-    }
-  )
-  grid(
-    columns: columns,
-    gutter: 5pt,
-    ..contact_fields
-  )
-}
-
-#let subdued(body) = {
-  block(inset: 5%, width: 85%, text(fill: luma(150), body))
-}
-
-#let entry(item: ()) = {
-  if "title" in item {
-    [*#item.title.at(lang)*]
-  }
-  if "place" in item {
-    if "title" in item {
-      [, ]
-    }
-    [_#item.place.at(lang)_]
-  }
-  [#h(1fr)]
-  if "date" in item {
-    [ _#item.date.at(lang)_ \ ]
-  }
-  if "bullets" in item {
-    for bullet in item.bullets {
-      [- #bullet.at(lang)]
-    }
-  }
-  if "publication" in item {
-    subdued[#item.publication.at(lang) \ ]
-  }
-  if "abstract" in item {
-    subdued[#item.abstract.at(lang) \ ]
-  }
-}
-
-#let horizon_line() = {
-  v(-3pt)
-  line(length: 100%)
-  v(-5pt)
-}
-
-#let section_header(title) = {
-  [== #title]
-  horizon_line()
-};
-
-#let section(title: "Section", entries: (), body) = {
-  section_header(title)
-  if body == none or body == [] {
-    for e in entries {
-      entry(item: e)
-    }
-  } else {
-    body
-  }
-};
-
-// Slightly re-styled entry with PLACE first and TITLE second
-#let education_entry(item: ()) = {
-  assert(
-    "place" in item and "title" in item and "date" in item,
-    message: "Education items require place, program and date.",
-  )
-  [*#item.place.at(lang)*, #item.title.at(lang) #h(1fr)]
-  [ _#item.date.at(lang)_ \ ]
-}
-
-// Restyled entry with PLACE not emphasized like usual, and no date but an abstract
-#let thesis_entry(item: ()) = {
-  assert("title" in item and "place" in item, message: "Thesis items require type and title.")
-  [*#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 [)]
-    })
-  }
-}
+#import "lib.typ": *
 
 #let cv(contents, use_sidebar: false) = {
   set text(lang: lang)
@@ -188,23 +43,23 @@
     }
 
     if "experience" in contents {
-      let title = sel_word_lang(en: "Professional Experience", de: "Berufserfahrung")
+      let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang)
       section(title: title, entries: contents.experience)[]
     }
 
     if "education" in contents {
-      let title = sel_word_lang(en: "Education", de: "Ausbildung")
+      let title = (en: "Education", de: "Ausbildung").at(lang)
       section(title: title, entries: contents.thesis + contents.education)[]
     }
 
     if not use_sidebar {
       if "volunteering" in contents {
-        let title = sel_word_lang(en: "Volunteer Work", de: "Ehrenamt")
+        let title = (en: "Volunteer Work", de: "Ehrenamt").at(lang)
         section(title: title, entries: contents.volunteering)[]
       }
 
       if "skills" in contents {
-        let title = sel_word_lang(en: "Qualifications", de: "Qualifikationen")
+        let title = (en: "Qualifications", de: "Qualifikationen").at(lang)
         section(
           title: title,
           {
@@ -214,7 +69,7 @@
       }
 
       if "languages" in contents {
-        let title = sel_word_lang(en: "Languages", de: "Sprachen")
+        let title = (en: "Languages", de: "Sprachen").at(lang)
         section(
           title: title,
           {
@@ -227,7 +82,7 @@
 
   let sidebar = {
     if "volunteering" in contents {
-      let title = sel_word_lang(en: "Volunteer Work", de: "Ehrenamt")
+      let title = (en: "Volunteer Work", de: "Ehrenamt").at(lang)
       [== #title]
       for e in contents.volunteering {
         [
@@ -238,14 +93,14 @@
     }
 
     if "languages" in contents {
-      let title = sel_word_lang(en: "Languages", de: "Sprachen")
+      let title = (en: "Languages", de: "Sprachen").at(lang)
       [== #title]
       skill_item(item: contents.languages, is_sidebar: true)
       [\ ]
     }
 
     if "skills" in contents {
-      let title = sel_word_lang(en: "Qualifications", de: "Kenntnisse")
+      let title = (en: "Qualifications", de: "Kenntnisse").at(lang)
       [== #title]
       skill_item(item: contents.skills, is_sidebar: true)
     }
diff --git a/lib.typ b/lib.typ
new file mode 100644
index 0000000..51862d7
--- /dev/null
+++ b/lib.typ
@@ -0,0 +1,139 @@
+#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]
+
+// Choose the compiled language through cli by doing
+//
+// $ typst compile --input lang=de cv.typ
+//
+#let lang = {
+  if "lang" in sys.inputs and sys.inputs.lang == "de" {
+    "de"
+  } else {
+    "en"
+  }
+}
+
+#let _columns_3(left_body, center_body, right_body) = {
+  block[
+    #box(width: 1fr)[
+      #align(left)[#left_body]
+    ]
+    #box(width: 1fr)[
+      #align(center)[#center_body]
+    ]
+    #box(width: 1fr)[
+      #align(right)[#right_body]
+    ]
+  ]
+}
+
+#let header(about, columns: (1.5fr, 1fr, 1fr)) = {
+  [= #about.fullname]
+  let contact_fields = (
+    for c in about.contact {
+      if "link" in c {
+        ([#c.icon ~ #link(c.link)[#c.text]],)
+      } else {
+        ([#c.icon ~ #c.text],)
+      }
+    }
+  )
+  grid(
+    columns: columns,
+    gutter: 5pt,
+    ..contact_fields
+  )
+}
+
+#let subdued(body) = {
+  block(inset: 5%, width: 85%, text(fill: luma(150), body))
+}
+
+#let entry(item: ()) = {
+  if "title" in item {
+    [*#item.title.at(lang)*]
+  }
+  if "place" in item {
+    if "title" in item {
+      [, ]
+    }
+    [_#item.place.at(lang)_]
+  }
+  [#h(1fr)]
+  if "date" in item {
+    [ _#item.date.at(lang)_ \ ]
+  }
+  if "bullets" in item {
+    for bullet in item.bullets {
+      [- #bullet.at(lang)]
+    }
+  }
+  if "publication" in item {
+    subdued[#item.publication.at(lang) \ ]
+  }
+  if "abstract" in item {
+    subdued[#item.abstract.at(lang) \ ]
+  }
+}
+
+#let horizon_line() = {
+  v(-3pt)
+  line(length: 100%)
+  v(-5pt)
+}
+
+#let section_header(title) = {
+  [== #title]
+  horizon_line()
+};
+
+#let section(title: "Section", entries: (), body) = {
+  section_header(title)
+  if body == none or body == [] {
+    for e in entries {
+      entry(item: e)
+    }
+  } else {
+    body
+  }
+};
+
+// Slightly re-styled entry with PLACE first and TITLE second
+#let education_entry(item: ()) = {
+  assert(
+    "place" in item and "title" in item and "date" in item,
+    message: "Education items require place, program and date.",
+  )
+  [*#item.place.at(lang)*, #item.title.at(lang) #h(1fr)]
+  [ _#item.date.at(lang)_ \ ]
+}
+
+// Restyled entry with PLACE not emphasized like usual, and no date but an abstract
+#let thesis_entry(item: ()) = {
+  assert("title" in item and "place" in item, message: "Thesis items require type and title.")
+  [*#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 2d10d7d..489d044 100644
--- a/resume.typ
+++ b/resume.typ
@@ -1,59 +1,6 @@
-#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]
-
-// Choose the compiled language through cli by doing
-//
-// $ typst compile --input lang=de cv.typ
-//
-#let lang = {
-  if "lang" in sys.inputs and sys.inputs.lang == "de" {
-    "de"
-  } else {
-    "en"
-  }
-}
-
-#let _columns_3(left_body, center_body, right_body) = {
-  block[
-    #box(width: 1fr)[
-      #align(left)[#left_body]
-    ]
-    #box(width: 1fr)[
-      #align(center)[#center_body]
-    ]
-    #box(width: 1fr)[
-      #align(right)[#right_body]
-    ]
-  ]
-}
-
-#let header(about, columns: (1.5fr, 1fr, 1fr)) = {
-  [= #about.fullname]
-  let contact_fields = (
-    for c in about.contact {
-      if "link" in c {
-        ([#c.icon ~ #link(c.link)[#c.text]],)
-      } else {
-        ([#c.icon ~ #c.text],)
-      }
-    }
-  )
-  grid(
-    columns: columns,
-    gutter: 5pt,
-    ..contact_fields
-  )
-}
-
-#let subdued(body) = {
-  block(inset: 5%, width: 85%, text(fill: luma(150), body))
-}
+#import "lib.typ": *
 
+// TODO: make it _return_ the data, not display it on its own
 #let by_client(experience: ()) = {
   let by_client = (:)
 
@@ -86,90 +33,6 @@
   }
 }
 
-#let entry(item: ()) = {
-  if "title" in item {
-    [*#item.title.at(lang)*]
-  }
-  if "place" in item {
-    if "title" in item {
-      [, ]
-    }
-    [_#item.place.at(lang)_]
-  }
-  [#h(1fr)]
-  if "date" in item {
-    [ _#item.date.at(lang)_ \ ]
-  }
-  if "bullets" in item {
-    for bullet in item.bullets {
-      [- #bullet.at(lang)]
-    }
-  }
-  if "publication" in item {
-    subdued[#item.publication.at(lang) \ ]
-  }
-  if "abstract" in item {
-    subdued[#item.abstract.at(lang) \ ]
-  }
-}
-
-#let horizon_line() = {
-  v(-3pt)
-  line(length: 100%)
-  v(-5pt)
-}
-
-#let section_header(title) = {
-  [== #title]
-  horizon_line()
-};
-
-#let section(title: "Section", entries: (), body) = {
-  section_header(title)
-  if body == none or body == [] {
-    for e in entries {
-      entry(item: e)
-    }
-  } else {
-    body
-  }
-};
-
-// Slightly re-styled entry with PLACE first and TITLE second
-#let education_entry(item: ()) = {
-  assert(
-    "place" in item and "title" in item and "date" in item,
-    message: "Education items require place, program and date.",
-  )
-  [*#item.place.at(lang)*, #item.title.at(lang) #h(1fr)]
-  [ _#item.date.at(lang)_ \ ]
-}
-
-// Restyled entry with PLACE not emphasized like usual, and no date but an abstract
-#let thesis_entry(item: ()) = {
-  assert("title" in item and "place" in item, message: "Thesis items require type and title.")
-  [*#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 [)]
-    })
-  }
-}
-
 #let resume(contents, main: ("experience", "education"), sidebar: ("volunteering", "languages", "skills")) = {
   set text(lang: lang)