docs: def _construct_category(glyph_name, data):
      """Derive (sub)category of a glyph name."""
      # Glyphs creates glyphs that start with an underscore as "non-exportable" glyphs or
      # construction helpers without a category.
      if glyph_name.startswith("_"):
          return None, None
  
      # Glyph variants (e.g. "fi.alt") don't have their own entry, so we strip e.g. the
      # ".alt" and try a second lookup with just the base name. A variant is hopefully in
      # the same category as its base glyph.
      base_name = glyph_name.split(".", 1)[0]
      base_attribute = data.names.get(base_name) or {}
      if base_attribute:
          category = base_attribute.get("category")
          sub_category = base_attribute.get("subCategory")
          return category, sub_category
  
      # Detect ligatures.
      if "_" in base_name:
          base_names = base_name.split("_")
          base_names_attributes = [_lookup_attributes(name, data) for name in base_names]
          first_attribute = base_names_attributes[0]
  
          # If the first part is a Mark, Glyphs 2.6 declares the entire glyph a Mark
          if first_attribute.get("category") == "Mark":
              category = first_attribute.get("category")
              sub_category = first_attribute.get("subCategory")
              return category, sub_category
  
          # If the first part is a Letter...
          if first_attribute.get("category") == "Letter":
              # ... and the rest are only marks or separators or don't exist, the
              # sub_category is that of the first part ...
              if all(
                  a.get("category") in (None, "Mark", "Separator")
                  for a in base_names_attributes[1:]
              ):
                  category = first_attribute.get("category")
                  sub_category = first_attribute.get("subCategory")
                  return category, sub_category
              # ... otherwise, a ligature.
              category = first_attribute.get("category")
              sub_category = "Ligature"
              return category, sub_category
  
          # TODO: Cover more cases. E.g. "one_one" -> ("Number", "Ligature") but
          # "one_onee" -> ("Number", "Composition").
  
      # Still nothing? Maybe we're looking at something like "uni1234.alt", try
      # using fontTools' AGL module to convert the base name to something meaningful.
      # Corner case: when looking at ligatures, names that don't exist in the AGLFN
      # are skipped, so len("acutecomb_o") == 2 but len("dotaccentcomb_o") == 1.
      character = fontTools.agl.toUnicode(base_name)
      if character:
          category, sub_category = _translate_category(
              glyph_name, unicodedata.category(character[0])
          )
          return category, sub_category
  
      return None, None