text:
# These mappings are from https://github.com/python/cpython/blob/3.10/Lib/locale.py

const LOCALE_ALIAS = Dict(
    "a3" => "az_AZ.KOI8-C",
    "a3_az" => "az_AZ.KOI8-C",
    "a3_az.koic" => "az_AZ.KOI8-C",
    "aa_dj" => "aa_DJ.ISO8859-1",
    "aa_er" => "aa_ER.UTF-8",
    "aa_et" => "aa_ET.UTF-8",
    "af" => "af_ZA.ISO8859-1",
    "af_za" => "af_ZA.ISO8859-1",
    "agr_pe" => "agr_PE.UTF-8",
    "ak_gh" => "ak_GH.UTF-8",
    "am" => "am_ET.UTF-8",
    "am_et" => "am_ET.UTF-8",
    "american" => "en_US.ISO8859-1",
    "an_es" => "an_ES.ISO8859-15",
    "anp_in" => "anp_IN.UTF-8",
    "ar" => "ar_AA.ISO8859-6",
    "ar_aa" => "ar_AA.ISO8859-6",
    "ar_ae" => "ar_AE.ISO8859-6",
    "ar_bh" => "ar_BH.ISO8859-6",
    "ar_dz" => "ar_DZ.ISO8859-6",
    "ar_eg" => "ar_EG.ISO8859-6",
    "ar_in" => "ar_IN.UTF-8",
    "ar_iq" => "ar_IQ.ISO8859-6",
    "ar_jo" => "ar_JO.ISO8859-6",
    "ar_kw" => "ar_KW.ISO8859-6",
    "ar_lb" => "ar_LB.ISO8859-6",
    "ar_ly" => "ar_LY.ISO8859-6",
    "ar_ma" => "ar_MA.ISO8859-6",
    "ar_om" => "ar_OM.ISO8859-6",
    "ar_qa" => "ar_QA.ISO8859-6",
    "ar_sa" => "ar_SA.ISO8859-6",
    "ar_sd" => "ar_SD.ISO8859-6",
    "ar_ss" => "ar_SS.UTF-8",
    "ar_sy" => "ar_SY.ISO8859-6",
    "ar_tn" => "ar_TN.ISO8859-6",
    "ar_ye" => "ar_YE.ISO8859-6",
    "arabic" => "ar_AA.ISO8859-6",
    "as" => "as_IN.UTF-8",
    "as_in" => "as_IN.UTF-8",
    "ast_es" => "ast_ES.ISO8859-15",
    "ayc_pe" => "ayc_PE.UTF-8",
    "az" => "az_AZ.ISO8859-9E",
    "az_az" => "az_AZ.ISO8859-9E",
    "az_az.iso88599e" => "az_AZ.ISO8859-9E",
    "az_ir" => "az_IR.UTF-8",
    "be" => "be_BY.CP1251",
    "be@latin" => "be_BY.UTF-8@latin",
    "be_bg.utf8" => "bg_BG.UTF-8",
    "be_by" => "be_BY.CP1251",
    "be_by@latin" => "be_BY.UTF-8@latin",
    "bem_zm" => "bem_ZM.UTF-8",
    "ber_dz" => "ber_DZ.UTF-8",
    "ber_ma" => "ber_MA.UTF-8",
    "bg" => "bg_BG.CP1251",
    "bg_bg" => "bg_BG.CP1251",
    "bhb_in.utf8" => "bhb_IN.UTF-8",
    "bho_in" => "bho_IN.UTF-8",
    "bho_np" => "bho_NP.UTF-8",
    "bi_vu" => "bi_VU.UTF-8",
    "bn_bd" => "bn_BD.UTF-8",
    "bn_in" => "bn_IN.UTF-8",
    "bo_cn" => "bo_CN.UTF-8",
    "bo_in" => "bo_IN.UTF-8",
    "bokmal" => "nb_NO.ISO8859-1",
    "bokm\xe5l" => "nb_NO.ISO8859-1",
    "br" => "br_FR.ISO8859-1",
    "br_fr" => "br_FR.ISO8859-1",
    "brx_in" => "brx_IN.UTF-8",
    "bs" => "bs_BA.ISO8859-2",
    "bs_ba" => "bs_BA.ISO8859-2",
    "bulgarian" => "bg_BG.CP1251",
    "byn_er" => "byn_ER.UTF-8",
    "c" => "C",
    "c-french" => "fr_CA.ISO8859-1",
    "c.ascii" => "C",
    "c.en" => "C",
    "c.iso88591" => "en_US.ISO8859-1",
    "c.utf8" => "en_US.UTF-8",
    "c_c" => "C",
    "c_c.c" => "C",
    "ca" => "ca_ES.ISO8859-1",
    "ca_ad" => "ca_AD.ISO8859-1",
    "ca_es" => "ca_ES.ISO8859-1",
    "ca_es@valencia" => "ca_ES.UTF-8@valencia",
    "ca_fr" => "ca_FR.ISO8859-1",
    "ca_it" => "ca_IT.ISO8859-1",
    "catalan" => "ca_ES.ISO8859-1",
    "ce_ru" => "ce_RU.UTF-8",
    "cextend" => "en_US.ISO8859-1",
    "chinese-s" => "zh_CN.eucCN",
    "chinese-t" => "zh_TW.eucTW",
    "chr_us" => "chr_US.UTF-8",
    "ckb_iq" => "ckb_IQ.UTF-8",
    "cmn_tw" => "cmn_TW.UTF-8",
    "crh_ua" => "crh_UA.UTF-8",
    "croatian" => "hr_HR.ISO8859-2",
    "cs" => "cs_CZ.ISO8859-2",
    "cs_cs" => "cs_CZ.ISO8859-2",
    "cs_cz" => "cs_CZ.ISO8859-2",
    "csb_pl" => "csb_PL.UTF-8",
    "cv_ru" => "cv_RU.UTF-8",
    "cy" => "cy_GB.ISO8859-1",
    "cy_gb" => "cy_GB.ISO8859-1",
    "cz" => "cs_CZ.ISO8859-2",
    "cz_cz" => "cs_CZ.ISO8859-2",
    "czech" => "cs_CZ.ISO8859-2",
    "da" => "da_DK.ISO8859-1",
    "da_dk" => "da_DK.ISO8859-1",
    "danish" => "da_DK.ISO8859-1",
    "dansk" => "da_DK.ISO8859-1",
    "de" => "de_DE.ISO8859-1",
    "de_at" => "de_AT.ISO8859-1",
    "de_be" => "de_BE.ISO8859-1",
    "de_ch" => "de_CH.ISO8859-1",
    "de_de" => "de_DE.ISO8859-1",
    "de_it" => "de_IT.ISO8859-1",
    "de_li.utf8" => "de_LI.UTF-8",
    "de_lu" => "de_LU.ISO8859-1",
    "deutsch" => "de_DE.ISO8859-1",
    "doi_in" => "doi_IN.UTF-8",
    "dutch" => "nl_NL.ISO8859-1",
    "dutch.iso88591" => "nl_BE.ISO8859-1",
    "dv_mv" => "dv_MV.UTF-8",
    "dz_bt" => "dz_BT.UTF-8",
    "ee" => "ee_EE.ISO8859-4",
    "ee_ee" => "ee_EE.ISO8859-4",
    "eesti" => "et_EE.ISO8859-1",
    "el" => "el_GR.ISO8859-7",
    "el_cy" => "el_CY.ISO8859-7",
    "el_gr" => "el_GR.ISO8859-7",
    "el_gr@euro" => "el_GR.ISO8859-15",
    "en" => "en_US.ISO8859-1",
    "en_ag" => "en_AG.UTF-8",
    "en_au" => "en_AU.ISO8859-1",
    "en_be" => "en_BE.ISO8859-1",
    "en_bw" => "en_BW.ISO8859-1",
    "en_ca" => "en_CA.ISO8859-1",
    "en_dk" => "en_DK.ISO8859-1",
    "en_dl.utf8" => "en_DL.UTF-8",
    "en_gb" => "en_GB.ISO8859-1",
    "en_hk" => "en_HK.ISO8859-1",
    "en_ie" => "en_IE.ISO8859-1",
    "en_il" => "en_IL.UTF-8",
    "en_in" => "en_IN.ISO8859-1",
    "en_ng" => "en_NG.UTF-8",
    "en_nz" => "en_NZ.ISO8859-1",
    "en_ph" => "en_PH.ISO8859-1",
    "en_sc.utf8" => "en_SC.UTF-8",
    "en_sg" => "en_SG.ISO8859-1",
    "en_uk" => "en_GB.ISO8859-1",
    "en_us" => "en_US.ISO8859-1",
    "en_us@euro@euro" => "en_US.ISO8859-15",
    "en_za" => "en_ZA.ISO8859-1",
    "en_zm" => "en_ZM.UTF-8",
    "en_zw" => "en_ZW.ISO8859-1",
    "en_zw.utf8" => "en_ZS.UTF-8",
    "eng_gb" => "en_GB.ISO8859-1",
    "english" => "en_EN.ISO8859-1",
    "english.iso88591" => "en_US.ISO8859-1",
    "english_uk" => "en_GB.ISO8859-1",
    "english_united-states" => "en_US.ISO8859-1",
    "english_united-states.437" => "C",
    "english_us" => "en_US.ISO8859-1",
    "eo" => "eo_XX.ISO8859-3",
    "eo.utf8" => "eo.UTF-8",
    "eo_eo" => "eo_EO.ISO8859-3",
    "eo_us.utf8" => "eo_US.UTF-8",
    "eo_xx" => "eo_XX.ISO8859-3",
    "es" => "es_ES.ISO8859-1",
    "es_ar" => "es_AR.ISO8859-1",
    "es_bo" => "es_BO.ISO8859-1",
    "es_cl" => "es_CL.ISO8859-1",
    "es_co" => "es_CO.ISO8859-1",
    "es_cr" => "es_CR.ISO8859-1",
    "es_cu" => "es_CU.UTF-8",
    "es_do" => "es_DO.ISO8859-1",
    "es_ec" => "es_EC.ISO8859-1",
    "es_es" => "es_ES.ISO8859-1",
    "es_gt" => "es_GT.ISO8859-1",
    "es_hn" => "es_HN.ISO8859-1",
    "es_mx" => "es_MX.ISO8859-1",
    "es_ni" => "es_NI.ISO8859-1",
    "es_pa" => "es_PA.ISO8859-1",
    "es_pe" => "es_PE.ISO8859-1",
    "es_pr" => "es_PR.ISO8859-1",
    "es_py" => "es_PY.ISO8859-1",
    "es_sv" => "es_SV.ISO8859-1",
    "es_us" => "es_US.ISO8859-1",
    "es_uy" => "es_UY.ISO8859-1",
    "es_ve" => "es_VE.ISO8859-1",
    "estonian" => "et_EE.ISO8859-1",
    "et" => "et_EE.ISO8859-15",
    "et_ee" => "et_EE.ISO8859-15",
    "eu" => "eu_ES.ISO8859-1",
    "eu_es" => "eu_ES.ISO8859-1",
    "eu_fr" => "eu_FR.ISO8859-1",
    "fa" => "fa_IR.UTF-8",
    "fa_ir" => "fa_IR.UTF-8",
    "fa_ir.isiri3342" => "fa_IR.ISIRI-3342",
    "ff_sn" => "ff_SN.UTF-8",
    "fi" => "fi_FI.ISO8859-15",
    "fi_fi" => "fi_FI.ISO8859-15",
    "fil_ph" => "fil_PH.UTF-8",
    "finnish" => "fi_FI.ISO8859-1",
    "fo" => "fo_FO.ISO8859-1",
    "fo_fo" => "fo_FO.ISO8859-1",
    "fr" => "fr_FR.ISO8859-1",
    "fr_be" => "fr_BE.ISO8859-1",
    "fr_ca" => "fr_CA.ISO8859-1",
    "fr_ch" => "fr_CH.ISO8859-1",
    "fr_fr" => "fr_FR.ISO8859-1",
    "fr_lu" => "fr_LU.ISO8859-1",
    "fran\xe7ais" => "fr_FR.ISO8859-1",
    "fre_fr" => "fr_FR.ISO8859-1",
    "french" => "fr_FR.ISO8859-1",
    "french.iso88591" => "fr_CH.ISO8859-1",
    "french_france" => "fr_FR.ISO8859-1",
    "fur_it" => "fur_IT.UTF-8",
    "fy_de" => "fy_DE.UTF-8",
    "fy_nl" => "fy_NL.UTF-8",
    "ga" => "ga_IE.ISO8859-1",
    "ga_ie" => "ga_IE.ISO8859-1",
    "galego" => "gl_ES.ISO8859-1",
    "galician" => "gl_ES.ISO8859-1",
    "gd" => "gd_GB.ISO8859-1",
    "gd_gb" => "gd_GB.ISO8859-1",
    "ger_de" => "de_DE.ISO8859-1",
    "german" => "de_DE.ISO8859-1",
    "german.iso88591" => "de_CH.ISO8859-1",
    "german_germany" => "de_DE.ISO8859-1",
    "gez_er" => "gez_ER.UTF-8",
    "gez_et" => "gez_ET.UTF-8",
    "gl" => "gl_ES.ISO8859-1",
    "gl_es" => "gl_ES.ISO8859-1",
    "greek" => "el_GR.ISO8859-7",
    "gu_in" => "gu_IN.UTF-8",
    "gv" => "gv_GB.ISO8859-1",
    "gv_gb" => "gv_GB.ISO8859-1",
    "ha_ng" => "ha_NG.UTF-8",
    "hak_tw" => "hak_TW.UTF-8",
    "he" => "he_IL.ISO8859-8",
    "he_il" => "he_IL.ISO8859-8",
    "hebrew" => "he_IL.ISO8859-8",
    "hi" => "hi_IN.ISCII-DEV",
    "hi_in" => "hi_IN.ISCII-DEV",
    "hi_in.isciidev" => "hi_IN.ISCII-DEV",
    "hif_fj" => "hif_FJ.UTF-8",
    "hne" => "hne_IN.UTF-8",
    "hne_in" => "hne_IN.UTF-8",
    "hr" => "hr_HR.ISO8859-2",
    "hr_hr" => "hr_HR.ISO8859-2",
    "hrvatski" => "hr_HR.ISO8859-2",
    "hsb_de" => "hsb_DE.ISO8859-2",
    "ht_ht" => "ht_HT.UTF-8",
    "hu" => "hu_HU.ISO8859-2",
    "hu_hu" => "hu_HU.ISO8859-2",
    "hungarian" => "hu_HU.ISO8859-2",
    "hy_am" => "hy_AM.UTF-8",
    "hy_am.armscii8" => "hy_AM.ARMSCII_8",
    "ia" => "ia.UTF-8",
    "ia_fr" => "ia_FR.UTF-8",
    "icelandic" => "is_IS.ISO8859-1",
    "id" => "id_ID.ISO8859-1",
    "id_id" => "id_ID.ISO8859-1",
    "ig_ng" => "ig_NG.UTF-8",
    "ik_ca" => "ik_CA.UTF-8",
    "in" => "id_ID.ISO8859-1",
    "in_id" => "id_ID.ISO8859-1",
    "is" => "is_IS.ISO8859-1",
    "is_is" => "is_IS.ISO8859-1",
    "iso-8859-1" => "en_US.ISO8859-1",
    "iso-8859-15" => "en_US.ISO8859-15",
    "iso8859-1" => "en_US.ISO8859-1",
    "iso8859-15" => "en_US.ISO8859-15",
    "iso_8859_1" => "en_US.ISO8859-1",
    "iso_8859_15" => "en_US.ISO8859-15",
    "it" => "it_IT.ISO8859-1",
    "it_ch" => "it_CH.ISO8859-1",
    "it_it" => "it_IT.ISO8859-1",
    "italian" => "it_IT.ISO8859-1",
    "iu" => "iu_CA.NUNACOM-8",
    "iu_ca" => "iu_CA.NUNACOM-8",
    "iu_ca.nunacom8" => "iu_CA.NUNACOM-8",
    "iw" => "he_IL.ISO8859-8",
    "iw_il" => "he_IL.ISO8859-8",
    "iw_il.utf8" => "iw_IL.UTF-8",
    "ja" => "ja_JP.eucJP",
    "ja_jp" => "ja_JP.eucJP",
    "ja_jp.euc" => "ja_JP.eucJP",
    "ja_jp.mscode" => "ja_JP.SJIS",
    "ja_jp.pck" => "ja_JP.SJIS",
    "japan" => "ja_JP.eucJP",
    "japanese" => "ja_JP.eucJP",
    "japanese-euc" => "ja_JP.eucJP",
    "japanese.euc" => "ja_JP.eucJP",
    "jp_jp" => "ja_JP.eucJP",
    "ka" => "ka_GE.GEORGIAN-ACADEMY",
    "ka_ge" => "ka_GE.GEORGIAN-ACADEMY",
    "ka_ge.georgianacademy" => "ka_GE.GEORGIAN-ACADEMY",
    "ka_ge.georgianps" => "ka_GE.GEORGIAN-PS",
    "ka_ge.georgianrs" => "ka_GE.GEORGIAN-ACADEMY",
    "kab_dz" => "kab_DZ.UTF-8",
    "kk_kz" => "kk_KZ.ptcp154",
    "kl" => "kl_GL.ISO8859-1",
    "kl_gl" => "kl_GL.ISO8859-1",
    "km_kh" => "km_KH.UTF-8",
    "kn" => "kn_IN.UTF-8",
    "kn_in" => "kn_IN.UTF-8",
    "ko" => "ko_KR.eucKR",
    "ko_kr" => "ko_KR.eucKR",
    "ko_kr.euc" => "ko_KR.eucKR",
    "kok_in" => "kok_IN.UTF-8",
    "korean" => "ko_KR.eucKR",
    "korean.euc" => "ko_KR.eucKR",
    "ks" => "ks_IN.UTF-8",
    "ks_in" => "ks_IN.UTF-8",
    "ks_in@devanagari.utf8" => "ks_IN.UTF-8@devanagari",
    "ku_tr" => "ku_TR.ISO8859-9",
    "kw" => "kw_GB.ISO8859-1",
    "kw_gb" => "kw_GB.ISO8859-1",
    "ky" => "ky_KG.UTF-8",
    "ky_kg" => "ky_KG.UTF-8",
    "lb_lu" => "lb_LU.UTF-8",
    "lg_ug" => "lg_UG.ISO8859-10",
    "li_be" => "li_BE.UTF-8",
    "li_nl" => "li_NL.UTF-8",
    "lij_it" => "lij_IT.UTF-8",
    "lithuanian" => "lt_LT.ISO8859-13",
    "ln_cd" => "ln_CD.UTF-8",
    "lo" => "lo_LA.MULELAO-1",
    "lo_la" => "lo_LA.MULELAO-1",
    "lo_la.cp1133" => "lo_LA.IBM-CP1133",
    "lo_la.ibmcp1133" => "lo_LA.IBM-CP1133",
    "lo_la.mulelao1" => "lo_LA.MULELAO-1",
    "lt" => "lt_LT.ISO8859-13",
    "lt_lt" => "lt_LT.ISO8859-13",
    "lv" => "lv_LV.ISO8859-13",
    "lv_lv" => "lv_LV.ISO8859-13",
    "lzh_tw" => "lzh_TW.UTF-8",
    "mag_in" => "mag_IN.UTF-8",
    "mai" => "mai_IN.UTF-8",
    "mai_in" => "mai_IN.UTF-8",
    "mai_np" => "mai_NP.UTF-8",
    "mfe_mu" => "mfe_MU.UTF-8",
    "mg_mg" => "mg_MG.ISO8859-15",
    "mhr_ru" => "mhr_RU.UTF-8",
    "mi" => "mi_NZ.ISO8859-1",
    "mi_nz" => "mi_NZ.ISO8859-1",
    "miq_ni" => "miq_NI.UTF-8",
    "mjw_in" => "mjw_IN.UTF-8",
    "mk" => "mk_MK.ISO8859-5",
    "mk_mk" => "mk_MK.ISO8859-5",
    "ml" => "ml_IN.UTF-8",
    "ml_in" => "ml_IN.UTF-8",
    "mn_mn" => "mn_MN.UTF-8",
    "mni_in" => "mni_IN.UTF-8",
    "mr" => "mr_IN.UTF-8",
    "mr_in" => "mr_IN.UTF-8",
    "ms" => "ms_MY.ISO8859-1",
    "ms_my" => "ms_MY.ISO8859-1",
    "mt" => "mt_MT.ISO8859-3",
    "mt_mt" => "mt_MT.ISO8859-3",
    "my_mm" => "my_MM.UTF-8",
    "nan_tw" => "nan_TW.UTF-8",
    "nb" => "nb_NO.ISO8859-1",
    "nb_no" => "nb_NO.ISO8859-1",
    "nds_de" => "nds_DE.UTF-8",
    "nds_nl" => "nds_NL.UTF-8",
    "ne_np" => "ne_NP.UTF-8",
    "nhn_mx" => "nhn_MX.UTF-8",
    "niu_nu" => "niu_NU.UTF-8",
    "niu_nz" => "niu_NZ.UTF-8",
    "nl" => "nl_NL.ISO8859-1",
    "nl_aw" => "nl_AW.UTF-8",
    "nl_be" => "nl_BE.ISO8859-1",
    "nl_nl" => "nl_NL.ISO8859-1",
    "nn" => "nn_NO.ISO8859-1",
    "nn_no" => "nn_NO.ISO8859-1",
    "no" => "no_NO.ISO8859-1",
    "no@nynorsk" => "ny_NO.ISO8859-1",
    "no_no" => "no_NO.ISO8859-1",
    "no_no.iso88591@bokmal" => "no_NO.ISO8859-1",
    "no_no.iso88591@nynorsk" => "no_NO.ISO8859-1",
    "norwegian" => "no_NO.ISO8859-1",
    "nr" => "nr_ZA.ISO8859-1",
    "nr_za" => "nr_ZA.ISO8859-1",
    "nso" => "nso_ZA.ISO8859-15",
    "nso_za" => "nso_ZA.ISO8859-15",
    "ny" => "ny_NO.ISO8859-1",
    "ny_no" => "ny_NO.ISO8859-1",
    "nynorsk" => "nn_NO.ISO8859-1",
    "oc" => "oc_FR.ISO8859-1",
    "oc_fr" => "oc_FR.ISO8859-1",
    "om_et" => "om_ET.UTF-8",
    "om_ke" => "om_KE.ISO8859-1",
    "or" => "or_IN.UTF-8",
    "or_in" => "or_IN.UTF-8",
    "os_ru" => "os_RU.UTF-8",
    "pa" => "pa_IN.UTF-8",
    "pa_in" => "pa_IN.UTF-8",
    "pa_pk" => "pa_PK.UTF-8",
    "pap_an" => "pap_AN.UTF-8",
    "pap_aw" => "pap_AW.UTF-8",
    "pap_cw" => "pap_CW.UTF-8",
    "pd" => "pd_US.ISO8859-1",
    "pd_de" => "pd_DE.ISO8859-1",
    "pd_us" => "pd_US.ISO8859-1",
    "ph" => "ph_PH.ISO8859-1",
    "ph_ph" => "ph_PH.ISO8859-1",
    "pl" => "pl_PL.ISO8859-2",
    "pl_pl" => "pl_PL.ISO8859-2",
    "polish" => "pl_PL.ISO8859-2",
    "portuguese" => "pt_PT.ISO8859-1",
    "portuguese_brazil" => "pt_BR.ISO8859-1",
    "posix" => "C",
    "posix-utf2" => "C",
    "pp" => "pp_AN.ISO8859-1",
    "pp_an" => "pp_AN.ISO8859-1",
    "ps_af" => "ps_AF.UTF-8",
    "pt" => "pt_PT.ISO8859-1",
    "pt_br" => "pt_BR.ISO8859-1",
    "pt_pt" => "pt_PT.ISO8859-1",
    "quz_pe" => "quz_PE.UTF-8",
    "raj_in" => "raj_IN.UTF-8",
    "ro" => "ro_RO.ISO8859-2",
    "ro_ro" => "ro_RO.ISO8859-2",
    "romanian" => "ro_RO.ISO8859-2",
    "ru" => "ru_RU.UTF-8",
    "ru_ru" => "ru_RU.UTF-8",
    "ru_ua" => "ru_UA.KOI8-U",
    "rumanian" => "ro_RO.ISO8859-2",
    "russian" => "ru_RU.KOI8-R",
    "rw" => "rw_RW.ISO8859-1",
    "rw_rw" => "rw_RW.ISO8859-1",
    "sa_in" => "sa_IN.UTF-8",
    "sat_in" => "sat_IN.UTF-8",
    "sc_it" => "sc_IT.UTF-8",
    "sd" => "sd_IN.UTF-8",
    "sd_in" => "sd_IN.UTF-8",
    "sd_in@devanagari.utf8" => "sd_IN.UTF-8@devanagari",
    "sd_pk" => "sd_PK.UTF-8",
    "se_no" => "se_NO.UTF-8",
    "serbocroatian" => "sr_RS.UTF-8@latin",
    "sgs_lt" => "sgs_LT.UTF-8",
    "sh" => "sr_RS.UTF-8@latin",
    "sh_ba.iso88592@bosnia" => "sr_CS.ISO8859-2",
    "sh_hr" => "sh_HR.ISO8859-2",
    "sh_hr.iso88592" => "hr_HR.ISO8859-2",
    "sh_sp" => "sr_CS.ISO8859-2",
    "sh_yu" => "sr_RS.UTF-8@latin",
    "shn_mm" => "shn_MM.UTF-8",
    "shs_ca" => "shs_CA.UTF-8",
    "si" => "si_LK.UTF-8",
    "si_lk" => "si_LK.UTF-8",
    "sid_et" => "sid_ET.UTF-8",
    "sinhala" => "si_LK.UTF-8",
    "sk" => "sk_SK.ISO8859-2",
    "sk_sk" => "sk_SK.ISO8859-2",
    "sl" => "sl_SI.ISO8859-2",
    "sl_cs" => "sl_CS.ISO8859-2",
    "sl_si" => "sl_SI.ISO8859-2",
    "slovak" => "sk_SK.ISO8859-2",
    "slovene" => "sl_SI.ISO8859-2",
    "slovenian" => "sl_SI.ISO8859-2",
    "sm_ws" => "sm_WS.UTF-8",
    "so_dj" => "so_DJ.ISO8859-1",
    "so_et" => "so_ET.UTF-8",
    "so_ke" => "so_KE.ISO8859-1",
    "so_so" => "so_SO.ISO8859-1",
    "sp" => "sr_CS.ISO8859-5",
    "sp_yu" => "sr_CS.ISO8859-5",
    "spanish" => "es_ES.ISO8859-1",
    "spanish_spain" => "es_ES.ISO8859-1",
    "sq" => "sq_AL.ISO8859-2",
    "sq_al" => "sq_AL.ISO8859-2",
    "sq_mk" => "sq_MK.UTF-8",
    "sr" => "sr_RS.UTF-8",
    "sr@cyrillic" => "sr_RS.UTF-8",
    "sr@latn" => "sr_CS.UTF-8@latin",
    "sr_cs" => "sr_CS.UTF-8",
    "sr_cs.iso88592@latn" => "sr_CS.ISO8859-2",
    "sr_cs@latn" => "sr_CS.UTF-8@latin",
    "sr_me" => "sr_ME.UTF-8",
    "sr_rs" => "sr_RS.UTF-8",
    "sr_rs@latn" => "sr_RS.UTF-8@latin",
    "sr_sp" => "sr_CS.ISO8859-2",
    "sr_yu" => "sr_RS.UTF-8@latin",
    "sr_yu.cp1251@cyrillic" => "sr_CS.CP1251",
    "sr_yu.iso88592" => "sr_CS.ISO8859-2",
    "sr_yu.iso88595" => "sr_CS.ISO8859-5",
    "sr_yu.iso88595@cyrillic" => "sr_CS.ISO8859-5",
    "sr_yu.microsoftcp1251@cyrillic" => "sr_CS.CP1251",
    "sr_yu.utf8" => "sr_RS.UTF-8",
    "sr_yu.utf8@cyrillic" => "sr_RS.UTF-8",
    "sr_yu@cyrillic" => "sr_RS.UTF-8",
    "ss" => "ss_ZA.ISO8859-1",
    "ss_za" => "ss_ZA.ISO8859-1",
    "st" => "st_ZA.ISO8859-1",
    "st_za" => "st_ZA.ISO8859-1",
    "sv" => "sv_SE.ISO8859-1",
    "sv_fi" => "sv_FI.ISO8859-1",
    "sv_se" => "sv_SE.ISO8859-1",
    "sw_ke" => "sw_KE.UTF-8",
    "sw_tz" => "sw_TZ.UTF-8",
    "swedish" => "sv_SE.ISO8859-1",
    "szl_pl" => "szl_PL.UTF-8",
    "ta" => "ta_IN.TSCII-0",
    "ta_in" => "ta_IN.TSCII-0",
    "ta_in.tscii" => "ta_IN.TSCII-0",
    "ta_in.tscii0" => "ta_IN.TSCII-0",
    "ta_lk" => "ta_LK.UTF-8",
    "tcy_in.utf8" => "tcy_IN.UTF-8",
    "te" => "te_IN.UTF-8",
    "te_in" => "te_IN.UTF-8",
    "tg" => "tg_TJ.KOI8-C",
    "tg_tj" => "tg_TJ.KOI8-C",
    "th" => "th_TH.ISO8859-11",
    "th_th" => "th_TH.ISO8859-11",
    "th_th.tactis" => "th_TH.TIS620",
    "th_th.tis620" => "th_TH.TIS620",
    "thai" => "th_TH.ISO8859-11",
    "the_np" => "the_NP.UTF-8",
    "ti_er" => "ti_ER.UTF-8",
    "ti_et" => "ti_ET.UTF-8",
    "tig_er" => "tig_ER.UTF-8",
    "tk_tm" => "tk_TM.UTF-8",
    "tl" => "tl_PH.ISO8859-1",
    "tl_ph" => "tl_PH.ISO8859-1",
    "tn" => "tn_ZA.ISO8859-15",
    "tn_za" => "tn_ZA.ISO8859-15",
    "to_to" => "to_TO.UTF-8",
    "tpi_pg" => "tpi_PG.UTF-8",
    "tr" => "tr_TR.ISO8859-9",
    "tr_cy" => "tr_CY.ISO8859-9",
    "tr_tr" => "tr_TR.ISO8859-9",
    "ts" => "ts_ZA.ISO8859-1",
    "ts_za" => "ts_ZA.ISO8859-1",
    "tt" => "tt_RU.TATAR-CYR",
    "tt_ru" => "tt_RU.TATAR-CYR",
    "tt_ru.tatarcyr" => "tt_RU.TATAR-CYR",
    "tt_ru@iqtelif" => "tt_RU.UTF-8@iqtelif",
    "turkish" => "tr_TR.ISO8859-9",
    "ug_cn" => "ug_CN.UTF-8",
    "uk" => "uk_UA.KOI8-U",
    "uk_ua" => "uk_UA.KOI8-U",
    "univ" => "en_US.utf",
    "universal" => "en_US.utf",
    "universal.utf8@ucs4" => "en_US.UTF-8",
    "unm_us" => "unm_US.UTF-8",
    "ur" => "ur_PK.CP1256",
    "ur_in" => "ur_IN.UTF-8",
    "ur_pk" => "ur_PK.CP1256",
    "uz" => "uz_UZ.UTF-8",
    "uz_uz" => "uz_UZ.UTF-8",
    "uz_uz@cyrillic" => "uz_UZ.UTF-8",
    "ve" => "ve_ZA.UTF-8",
    "ve_za" => "ve_ZA.UTF-8",
    "vi" => "vi_VN.TCVN",
    "vi_vn" => "vi_VN.TCVN",
    "vi_vn.tcvn" => "vi_VN.TCVN",
    "vi_vn.tcvn5712" => "vi_VN.TCVN",
    "vi_vn.viscii" => "vi_VN.VISCII",
    "vi_vn.viscii111" => "vi_VN.VISCII",
    "wa" => "wa_BE.ISO8859-1",
    "wa_be" => "wa_BE.ISO8859-1",
    "wae_ch" => "wae_CH.UTF-8",
    "wal_et" => "wal_ET.UTF-8",
    "wo_sn" => "wo_SN.UTF-8",
    "xh" => "xh_ZA.ISO8859-1",
    "xh_za" => "xh_ZA.ISO8859-1",
    "yi" => "yi_US.CP1255",
    "yi_us" => "yi_US.CP1255",
    "yo_ng" => "yo_NG.UTF-8",
    "yue_hk" => "yue_HK.UTF-8",
    "yuw_pg" => "yuw_PG.UTF-8",
    "zh" => "zh_CN.eucCN",
    "zh_cn" => "zh_CN.gb2312",
    "zh_cn.big5" => "zh_TW.big5",
    "zh_cn.euc" => "zh_CN.eucCN",
    "zh_hk" => "zh_HK.big5hkscs",
    "zh_hk.big5hk" => "zh_HK.big5hkscs",
    "zh_sg" => "zh_SG.GB2312",
    "zh_sg.gbk" => "zh_SG.GBK",
    "zh_tw" => "zh_TW.big5",
    "zh_tw.euc" => "zh_TW.eucTW",
    "zh_tw.euctw" => "zh_TW.eucTW",
    "zu" => "zu_ZA.ISO8859-1",
    "zu_za" => "zu_ZA.ISO8859-1",
)

const WINDOWS_LOCALES = Dict(
    0x0436 => "af_ZA",  # Afrikaans
    0x041c => "sq_AL",  # Albanian
    0x0484 => "gsw_FR", # Alsatian - France
    0x045e => "am_ET",  # Amharic - Ethiopia
    0x0401 => "ar_SA",  # Arabic - Saudi Arabia
    0x0801 => "ar_IQ",  # Arabic - Iraq
    0x0c01 => "ar_EG",  # Arabic - Egypt
    0x1001 => "ar_LY",  # Arabic - Libya
    0x1401 => "ar_DZ",  # Arabic - Algeria
    0x1801 => "ar_MA",  # Arabic - Morocco
    0x1c01 => "ar_TN",  # Arabic - Tunisia
    0x2001 => "ar_OM",  # Arabic - Oman
    0x2401 => "ar_YE",  # Arabic - Yemen
    0x2801 => "ar_SY",  # Arabic - Syria
    0x2c01 => "ar_JO",  # Arabic - Jordan
    0x3001 => "ar_LB",  # Arabic - Lebanon
    0x3401 => "ar_KW",  # Arabic - Kuwait
    0x3801 => "ar_AE",  # Arabic - United Arab Emirates
    0x3c01 => "ar_BH",  # Arabic - Bahrain
    0x4001 => "ar_QA",  # Arabic - Qatar
    0x042b => "hy_AM",  # Armenian
    0x044d => "as_IN",  # Assamese - India
    0x042c => "az_AZ",  # Azeri - Latin
    0x082c => "az_AZ",  # Azeri - Cyrillic
    0x046d => "ba_RU",  # Bashkir
    0x042d => "eu_ES",  # Basque - Russia
    0x0423 => "be_BY",  # Belarusian
    0x0445 => "bn_IN",  # Begali
    0x201a => "bs_BA",  # Bosnian - Cyrillic
    0x141a => "bs_BA",  # Bosnian - Latin
    0x047e => "br_FR",  # Breton - France
    0x0402 => "bg_BG",  # Bulgarian
 #    0x0455 => "my_MM",  # Burmese - Not supported
    0x0403 => "ca_ES",  # Catalan
    0x0004 => "zh_CHS", # Chinese - Simplified
    0x0404 => "zh_TW",  # Chinese - Taiwan
    0x0804 => "zh_CN",  # Chinese - PRC
    0x0c04 => "zh_HK",  # Chinese - Hong Kong S.A.R.
    0x1004 => "zh_SG",  # Chinese - Singapore
    0x1404 => "zh_MO",  # Chinese - Macao S.A.R.
    0x7c04 => "zh_CHT", # Chinese - Traditional
    0x0483 => "co_FR",  # Corsican - France
    0x041a => "hr_HR",  # Croatian
    0x101a => "hr_BA",  # Croatian - Bosnia
    0x0405 => "cs_CZ",  # Czech
    0x0406 => "da_DK",  # Danish
    0x048c => "gbz_AF", # Dari - Afghanistan
    0x0465 => "div_MV", # Divehi - Maldives
    0x0413 => "nl_NL",  # Dutch - The Netherlands
    0x0813 => "nl_BE",  # Dutch - Belgium
    0x0409 => "en_US",  # English - United States
    0x0809 => "en_GB",  # English - United Kingdom
    0x0c09 => "en_AU",  # English - Australia
    0x1009 => "en_CA",  # English - Canada
    0x1409 => "en_NZ",  # English - New Zealand
    0x1809 => "en_IE",  # English - Ireland
    0x1c09 => "en_ZA",  # English - South Africa
    0x2009 => "en_JA",  # English - Jamaica
    0x2409 => "en_CB",  # English - Caribbean
    0x2809 => "en_BZ",  # English - Belize
    0x2c09 => "en_TT",  # English - Trinidad
    0x3009 => "en_ZW",  # English - Zimbabwe
    0x3409 => "en_PH",  # English - Philippines
    0x4009 => "en_IN",  # English - India
    0x4409 => "en_MY",  # English - Malaysia
    0x4809 => "en_IN",  # English - Singapore
    0x0425 => "et_EE",  # Estonian
    0x0438 => "fo_FO",  # Faroese
    0x0464 => "fil_PH", # Filipino
    0x040b => "fi_FI",  # Finnish
    0x040c => "fr_FR",  # French - France
    0x080c => "fr_BE",  # French - Belgium
    0x0c0c => "fr_CA",  # French - Canada
    0x100c => "fr_CH",  # French - Switzerland
    0x140c => "fr_LU",  # French - Luxembourg
    0x180c => "fr_MC",  # French - Monaco
    0x0462 => "fy_NL",  # Frisian - Netherlands
    0x0456 => "gl_ES",  # Galician
    0x0437 => "ka_GE",  # Georgian
    0x0407 => "de_DE",  # German - Germany
    0x0807 => "de_CH",  # German - Switzerland
    0x0c07 => "de_AT",  # German - Austria
    0x1007 => "de_LU",  # German - Luxembourg
    0x1407 => "de_LI",  # German - Liechtenstein
    0x0408 => "el_GR",  # Greek
    0x046f => "kl_GL",  # Greenlandic - Greenland
    0x0447 => "gu_IN",  # Gujarati
    0x0468 => "ha_NG",  # Hausa - Latin
    0x040d => "he_IL",  # Hebrew
    0x0439 => "hi_IN",  # Hindi
    0x040e => "hu_HU",  # Hungarian
    0x040f => "is_IS",  # Icelandic
    0x0421 => "id_ID",  # Indonesian
    0x045d => "iu_CA",  # Inuktitut - Syllabics
    0x085d => "iu_CA",  # Inuktitut - Latin
    0x083c => "ga_IE",  # Irish - Ireland
    0x0410 => "it_IT",  # Italian - Italy
    0x0810 => "it_CH",  # Italian - Switzerland
    0x0411 => "ja_JP",  # Japanese
    0x044b => "kn_IN",  # Kannada - India
    0x043f => "kk_KZ",  # Kazakh
    0x0453 => "kh_KH",  # Khmer - Cambodia
    0x0486 => "qut_GT", # K'iche - Guatemala
    0x0487 => "rw_RW",  # Kinyarwanda - Rwanda
    0x0457 => "kok_IN", # Konkani
    0x0412 => "ko_KR",  # Korean
    0x0440 => "ky_KG",  # Kyrgyz
    0x0454 => "lo_LA",  # Lao - Lao PDR
    0x0426 => "lv_LV",  # Latvian
    0x0427 => "lt_LT",  # Lithuanian
    0x082e => "dsb_DE", # Lower Sorbian - Germany
    0x046e => "lb_LU",  # Luxembourgish
    0x042f => "mk_MK",  # FYROM Macedonian
    0x043e => "ms_MY",  # Malay - Malaysia
    0x083e => "ms_BN",  # Malay - Brunei Darussalam
    0x044c => "ml_IN",  # Malayalam - India
    0x043a => "mt_MT",  # Maltese
    0x0481 => "mi_NZ",  # Maori
    0x047a => "arn_CL", # Mapudungun
    0x044e => "mr_IN",  # Marathi
    0x047c => "moh_CA", # Mohawk - Canada
    0x0450 => "mn_MN",  # Mongolian - Cyrillic
    0x0850 => "mn_CN",  # Mongolian - PRC
    0x0461 => "ne_NP",  # Nepali
    0x0414 => "nb_NO",  # Norwegian - Bokmal
    0x0814 => "nn_NO",  # Norwegian - Nynorsk
    0x0482 => "oc_FR",  # Occitan - France
    0x0448 => "or_IN",  # Oriya - India
    0x0463 => "ps_AF",  # Pashto - Afghanistan
    0x0429 => "fa_IR",  # Persian
    0x0415 => "pl_PL",  # Polish
    0x0416 => "pt_BR",  # Portuguese - Brazil
    0x0816 => "pt_PT",  # Portuguese - Portugal
    0x0446 => "pa_IN",  # Punjabi
    0x046b => "quz_BO", # Quechua (Bolivia)
    0x086b => "quz_EC", # Quechua (Ecuador)
    0x0c6b => "quz_PE", # Quechua (Peru)
    0x0418 => "ro_RO",  # Romanian - Romania
    0x0417 => "rm_CH",  # Romansh
    0x0419 => "ru_RU",  # Russian
    0x243b => "smn_FI", # Sami Finland
    0x103b => "smj_NO", # Sami Norway
    0x143b => "smj_SE", # Sami Sweden
    0x043b => "se_NO",  # Sami Northern Norway
    0x083b => "se_SE",  # Sami Northern Sweden
    0x0c3b => "se_FI",  # Sami Northern Finland
    0x203b => "sms_FI", # Sami Skolt
    0x183b => "sma_NO", # Sami Southern Norway
    0x1c3b => "sma_SE", # Sami Southern Sweden
    0x044f => "sa_IN",  # Sanskrit
    0x0c1a => "sr_SP",  # Serbian - Cyrillic
    0x1c1a => "sr_BA",  # Serbian - Bosnia Cyrillic
    0x081a => "sr_SP",  # Serbian - Latin
    0x181a => "sr_BA",  # Serbian - Bosnia Latin
    0x045b => "si_LK",  # Sinhala - Sri Lanka
    0x046c => "ns_ZA",  # Northern Sotho
    0x0432 => "tn_ZA",  # Setswana - Southern Africa
    0x041b => "sk_SK",  # Slovak
    0x0424 => "sl_SI",  # Slovenian
    0x040a => "es_ES",  # Spanish - Spain
    0x080a => "es_MX",  # Spanish - Mexico
    0x0c0a => "es_ES",  # Spanish - Spain (Modern)
    0x100a => "es_GT",  # Spanish - Guatemala
    0x140a => "es_CR",  # Spanish - Costa Rica
    0x180a => "es_PA",  # Spanish - Panama
    0x1c0a => "es_DO",  # Spanish - Dominican Republic
    0x200a => "es_VE",  # Spanish - Venezuela
    0x240a => "es_CO",  # Spanish - Colombia
    0x280a => "es_PE",  # Spanish - Peru
    0x2c0a => "es_AR",  # Spanish - Argentina
    0x300a => "es_EC",  # Spanish - Ecuador
    0x340a => "es_CL",  # Spanish - Chile
    0x380a => "es_UR",  # Spanish - Uruguay
    0x3c0a => "es_PY",  # Spanish - Paraguay
    0x400a => "es_BO",  # Spanish - Bolivia
    0x440a => "es_SV",  # Spanish - El Salvador
    0x480a => "es_HN",  # Spanish - Honduras
    0x4c0a => "es_NI",  # Spanish - Nicaragua
    0x500a => "es_PR",  # Spanish - Puerto Rico
    0x540a => "es_US",  # Spanish - United States
 #    0x0430 => "",  # Sutu - Not supported
    0x0441 => "sw_KE",  # Swahili
    0x041d => "sv_SE",  # Swedish - Sweden
    0x081d => "sv_FI",  # Swedish - Finland
    0x045a => "syr_SY", # Syriac
    0x0428 => "tg_TJ",  # Tajik - Cyrillic
    0x085f => "tmz_DZ", # Tamazight - Latin
    0x0449 => "ta_IN",  # Tamil
    0x0444 => "tt_RU",  # Tatar
    0x044a => "te_IN",  # Telugu
    0x041e => "th_TH",  # Thai
    0x0851 => "bo_BT",  # Tibetan - Bhutan
    0x0451 => "bo_CN",  # Tibetan - PRC
    0x041f => "tr_TR",  # Turkish
    0x0442 => "tk_TM",  # Turkmen - Cyrillic
    0x0480 => "ug_CN",  # Uighur - Arabic
    0x0422 => "uk_UA",  # Ukrainian
    0x042e => "wen_DE", # Upper Sorbian - Germany
    0x0420 => "ur_PK",  # Urdu
    0x0820 => "ur_IN",  # Urdu - India
    0x0443 => "uz_UZ",  # Uzbek - Latin
    0x0843 => "uz_UZ",  # Uzbek - Cyrillic
    0x042a => "vi_VN",  # Vietnamese
    0x0452 => "cy_GB",  # Welsh
    0x0488 => "wo_SN",  # Wolof - Senegal
    0x0434 => "xh_ZA",  # Xhosa - South Africa
    0x0485 => "sah_RU", # Yakut - Cyrillic
    0x0478 => "ii_CN",  # Yi - PRC
    0x046a => "yo_NG",  # Yoruba - Nigeria
    0x0435 => "zu_ZA",  # Zulu
)

function _replace_encoding(code, encoding)
    langname = split(code, '.')[1]
    # TODO: Normalize encoding
    encoding = uppercase(encoding)
    string(langname, '.', encoding)
end

function _append_modifier(code, modifier)
    if modifier == "euro"
        t = split(code, '.')
        length(t) > 1 || return string(code, ".ISO8859-15")
        encoding = t[1]
        encoding in ("ISO8859-15", "UTF-8") && return code
        encoding == "ISO8859-1" && return _replace_encoding(code, "ISO8859-15")
    end
    return string(code, '@', modifier)
end

function normalize_locale(locale)
    code = lowercase(locale)
    t = split(code, '@', limit=2)
    code, modifier = if length(t) > 1
        t[1], t[2]
    else
        code, ""
    end
    code = replace(code, ':' => '.')
    t = split(code, '.')
    langname, encoding = if length(t) > 1
        t[1], t[2]
    else
        code, ""
    end
    lang_enc = langname
    if length(encoding) > 0
        encoding = replace(encoding, '-' => "")
        encoding = replace(encoding, '_' => "")
        lang_enc = string(lang_enc, ".", encoding)
    end
    lookup_name = if length(modifier) > 0
        string(lang_enc, "@", modifier)
    else
        lang_enc
    end
    haskey(LOCALE_ALIAS, lookup_name) && return LOCALE_ALIAS[lookup_name]
    if length(modifier) > 0
        code = get(LOCALE_ALIAS, lang_enc, nothing)
        if !isnothing(code)
            t = split(code, '@', limit=2)
            length(t) > 1 || return _append_modifier(code, modifier)
            lowercase(t[2]) == modifier && return code
        end
    end
    if length(encoding) > 0
        lookup_name = if length(modifier) > 0
            string(langname, "@", modifier)
        else
            langname
        end
        code = get(LOCALE_ALIAS, lookup_name, nothing)
        if !isnothing(code)
            t = split(code, '@', limit=2)
            length(t) > 1 || return _replace_encoding(code, encoding)
            code, encoding = t
            return string(_replace_encoding(code, encoding), '@', modifier)
        end

        if length(modifier) > 0
            code = get(LOCALE_ALIAS, langname, nothing)
            if !isnothing(code)
                t = split(code, '@', limit=2)
                if length(t) == 1
                    code = _replace_encoding(code, encoding)
                    return _append_modifier(code, modifier)
                end
                code, defmod = t
                lower(defmod) == modifier && return string(_replace_encoding(code, encoding), '@', defmod)
            end
        end
    end
    locale
end

function parse_locale_name(locale)
    name = split(normalize_locale(locale), '.')[1]
    replace(name, '-' => '_')
end

@static if Sys.iswindows()

function get_system_language()
    language = ccall((:GetUserDefaultUILanguage, "kernel32"), stdcall, UInt16, ())
    WINDOWS_LOCALES[language]
end

else

function get_system_language()
    locale_name = ccall(:setlocale, Cstring, (Cint, Cstring), 0, C_NULL) |> unsafe_string
    parse_locale_name(split(locale_name, '.')[1])
end

end


text:
"""
    quasisteady_liftingline_model()

Construct a model by coupling a quasi-steady aerodynamic model based on thin airfoil theory
(see [`quasisteady_model`](@ref)) and a lifting line section model (see
[`liftingline_section_model`](@ref)).
"""
function quasisteady_liftingline_model()

    # aerodynamic model
    aero = quasisteady_model()

    # structural model
    stru = liftingline_section_model()

    # submodels
    submodels = (aero, stru)

    # construct coupling
    coupling = quasisteady_liftingline_coupling(aero, stru)

    # return the coupled model
    return CoupledModel(submodels, coupling)
end

# --- Internal Methods for this Coupling --- #

# coupling definition
function quasisteady_liftingline_coupling(aero, stru)

    # coupling function
    g = quasisteady_liftingline_inputs

    # number of states, inputs, and parameters (use Val(N) to use inferrable dimensions)
    nx = Val(6)
    ny = Val(6)
    np = Val(8)

    # number of parameters introduced by the coupling (use Val(N) to use inferrable dimensions)
    npc = Val(0)

    # jacobian definitions
    ratejac = Nonlinear() # TODO: define rate jacobian function
    statejac = Nonlinear() # TODO: define state jacobian function
    paramjac = Nonlinear() # TODO: define parameter jacobian function
    tgrad = Zeros()

    # return resulting coupling
    return Coupling{false}(g, nx, ny, np, npc;
        ratejac = ratejac,
        statejac = statejac,
        paramjac = paramjac,
        tgrad = tgrad)
end

# coupling function
function quasisteady_liftingline_inputs(dx, x, p, t)
    # rate variables
    dvx, dvy, dvz, dωx, dωy, dωz = dx
    # extract state variables
    vx, vy, vz, ωx, ωy, ωz = x
    # extract parameters
    a, b, a0, α0, cd0, cm0, ρ, c = p
    # freestream velocity components
    u, v, ω = liftingline_section_velocities(vx, vz, ωy)
    udot, vdot, ωdot = liftingline_section_accelerations(dvx, dvz, dωy)
    # calculate aerodynamic loads
    N, A, M = quasisteady_loads(a, b, ρ, c, a0, α0, cd0, cm0, u, v, ω, vdot, ωdot)
    # forces and moments per unit span
    f = SVector(A, 0, N)
    m = SVector(0, M, 0)
    # return inputs
    return vcat(f, m)
end

ext: jl


text:
module TimeDomain2D

using ..Common
using ..DivGradCurls
using Plots
using ProgressMeter
using RecipesBase

import ..Common.DEFAULT_CFL_NUMBER
import ..Common.AbstractComplexBL
import LinearAlgebra: mul!

import ..AbstractFDTD
import ..AbstractFields
import ..HelmholtzPointSource
import ..HelmholtzWaveFields
import ..HelmholtzFDTD
import ..propagate!
import .._αβ
import .._whichdimspml
import .._ndimpml

# HelmholtzField constructor from simulation
"""
    HelmholtzWaveFields(simulation) -> hf
"""
HelmholtzWaveFields

function HelmholtzWaveFields(sim::Simulation{2,Common.Symmetric})
    M = _ndimpml(sim)
    if M==0
        φ = (ScalarField(sim.x, 1, Float64), ScalarField(sim.x, 1, Float64))
        ∇Φₓ = (ScalarField(sim.x_half[1], 1, Float64), ScalarField(sim.x_half[1], 1, Float64))
        ∇Φᵤ = (ScalarField(sim.x_half[2], 1, Float64), ScalarField(sim.x_half[2], 1, Float64))
        ∇Φ = ((∇Φₓ[1], ∇Φᵤ[1]),(∇Φₓ[2], ∇Φᵤ[2]))
    elseif M==1
        φ = (ScalarField(sim.x, 1, Float64), ScalarField(sim.x, 1, Float64))
        ∇Φₓ = (ScalarField(sim.x_half[1], 1, Float64), ScalarField(sim.x_half[1], 1, Float64))
        ∇Φᵤ = (ScalarField(sim.x_half[2], 1, Float64), ScalarField(sim.x_half[2], 1, Float64))
        ∇Φ = (∇Φₓ, ∇Φᵤ)
    else
        φ = (ScalarField(sim.x, 1, Float64), ScalarField(sim.x, 1, Float64), ScalarField(sim.x, 1, Float64))
        ∇Φₓ = (ScalarField(sim.x_half[1], 1, Float64), ScalarField(sim.x_half[1], 1, Float64), ScalarField(sim.x_half[1], 1, Float64))
        ∇Φᵤ = (ScalarField(sim.x_half[2], 1, Float64), ScalarField(sim.x_half[2], 1, Float64), ScalarField(sim.x_half[2], 1, Float64))
        ∇Φ = (∇Φₓ, ∇Φᵤ)
    end
    return HelmholtzWaveFields(φ, ∇Φ)
end

"""
    HelmholtzFDTD(sim; source=0, dt=sim.dx*$DEFAULT_CFL_NUMBER, plotoptions...) -> fdtd
"""
function HelmholtzFDTD(
            sim::Simulation{2,Common.Symmetric},
            source = HelmholtzPointSource(sim, 1e100, 0);
            dt::Real = 1/(1/sim.dx+1/sim.dy)*DEFAULT_CFL_NUMBER,
            kwargs...)

    return HelmholtzFDTD(sim, source, dt; kwargs...)
end

function _ndimpml(sim::Simulation{2})
    bls = sim.boundary.bls
    if typeof(bls[1])<:AbstractComplexBL || typeof(bls[2])<:AbstractComplexBL
        if typeof(bls[3])<:AbstractComplexBL || typeof(bls[4])<:AbstractComplexBL
            ndim = 2
        else
            ndim = 1
        end
    else
        if typeof(bls[3])<:AbstractComplexBL || typeof(bls[4])<:AbstractComplexBL
            ndim = 1
        else
            ndim = 0
        end
    end
    return ndim
end

function _whichdimspml(sim::Simulation{2,Common.Symmetric})
    M = _ndimpml(sim)
    if M == 0
        return ()
    elseif M==1
        if typeof(sim.boundary.bls[1])<:AbstractComplexBL || typeof(sim.boundary.bls[2])<:AbstractComplexBL
            return (1,)
        else
            return (2,)
        end
    elseif M==2
        return (1,2)
    end
end

function _αβ(sim::Simulation{2,Common.Symmetric}, dt::Real)
    M = _ndimpml(sim)
    pmldims = _whichdimspml(sim)
    if M == 0
        α = [ones(Float64,length(sim)), dt./sim.ε]
        β = [ones(Float64,length(sim)), fill(dt,length(sim))]
    elseif M==1
    elseif M==2
    end
    return α, β
end

"""
    HelmholtzPointSource(sim, xoft) -> ps
"""
function HelmholtzPointSource(sim::Simulation{2,Common.Symmetric}, xoft, aoft, ωoft=1, ϕoft=0) where F
    σ = 4hypot(sim.dx,sim.dy)
    N = sqrt(2π)*σ
    return HelmholtzPointSource(xoft, aoft, ωoft, ϕoft, σ^2, N)
end

"""
    propagate!(fdtd, [n=1; animate=false, verbose=false])
"""
@inline function propagate!(fdtd::HelmholtzFDTD{2,2,0}, n::Integer=1; animate::Bool=false, verbose::Bool=false)
    dt = fdtd.dt

    φ = getfield(fdtd.fields,:φ)
    ∇Φ = fdtd.fields.∇Φ

    grad = fdtd.grad
    div = fdtd.div

    α = fdtd.α
    β = fdtd.β

    options = fdtd.options

    verbose ? pg = Progress(n) : nothing
    foreach(1:n) do i
        @fastmath mul!(φ[2], div, ∇Φ[1])
        @fastmath @inbounds @simd for j ∈ eachindex(φ[1])
            φ[1][j] = α[1][j]*φ[1][j] + α[2][j]*φ[2][j]
            iszero(fdtd.source.aoft(fdtd.t[1])) ? nothing : φ[1][j] -= dt*fdtd.source(φ[1].positions[j], fdtd.t[1])
        end
        @fastmath @inbounds fdtd.t[1] += dt/2

        @fastmath mul!(∇Φ[2], grad, φ[1])
        for k ∈ eachindex(∇Φ[1])
            @fastmath @inbounds @simd for j ∈ eachindex(∇Φ[1][k])
                ∇Φ[1][k][j] = β[1][j]*∇Φ[1][k][j] + β[2][j]*∇Φ[2][k][j]
            end
        end
        @fastmath @inbounds fdtd.t[1] += dt/2

        verbose ? next!(pg) : nothing

        if animate && iszero(mod(fdtd.n[1]+i-options.start,options.interval))
            if options.by ∈ (abs,abs2)
                options.ylims[1] = 0
                options.ylims[2] = max(options.ylims[2],maximum(options.by,φ[1]))
            else
                options.ylims[1] = min(options.ylims[1],minimum(options.by,φ[1]))
                options.ylims[2] = max(options.ylims[2],maximum(options.by,φ[1]))
                options.ylims[2] = max(abs(options.ylims[1]),abs(options.ylims[2]))
                options.ylims[1] = -options.ylims[2]
            end
            frame(options.animation, plot(fdtd, options.by; ylims=options.ylims, grid=false, options.plotoptions...))
        end
    end
    fdtd.n[1] += n
    return nothing
end

################################################################################
# Plotting

@recipe function f(field::HelmholtzWaveFields{2,2}, by::Function)
    @series begin field.φ, by end
end
@recipe function f(sim::Simulation{2}, field::HelmholtzWaveFields{1,2}, by::Function)
    @series begin sim, field.φ, by end
end

@recipe function f(fdtd::HelmholtzFDTD{2,2}, by::Function)
    @series begin fdtd.fields, by end
end
@recipe function f(sim::Simulation{2}, fdtd::HelmholtzFDTD{2,2}, by::Function)
    @series begin fdtd.simulation, fdtd.fields, by end
end

end # module

using .TimeDomain2D


text:
struct Filter{f,T} <: UnaryNodeOp{T} end

stateless_operator(::Filter) = true
time_agnostic(::Filter) = true
operator!(::Filter{f,T}, x) where {f,T} = f(x) ? Maybe(x) : Maybe{T}()

"""
    filter(f::Function, x::Node) -> Node

Obtain a node that removes knots for which `f(value)` is false.

The [`value_type`](@ref) of the returned node is the same as that for the input `x`.
"""
Base.filter(f::Function, x::Node) = obtain_node((x,), Filter{f,value_type(x)}())

struct SkipMissing{T} <: UnaryNodeOp{T} end

stateless_operator(::SkipMissing) = true
time_agnostic(::SkipMissing) = true

function operator!(::SkipMissing{T}, x::Union{Missing,T}) where {T}
    return ismissing(x) ? Maybe{T}() : Maybe(x)
end

"""
    skipmissing(x::Node{T}) -> Node{nonmissingtype(T)}

Obtain a node which ticks with the values of `x`, so long as that value is not `missing`.

The [`value_type`](@ref) of the node that is returned will always be the `nonmissingtype` of
the `value_type` of `x`.

In the case that `x` cannot tick with `missing` (based on its `value_type`), we just return
`x`.
"""
function Base.skipmissing(x::Node)
    if !(Missing <: value_type(x))
        # There are provably no missing values in the input, so return the same node.
        return x
    end
    return obtain_node((x,), SkipMissing{nonmissingtype(value_type(x))}())
end


text:
export sequence2data

function sequence2data(input,output,T)
  dimInput,nInput=size(input)
  dimOutput,nOutput=size(output)
  @assert nInput==nOutput "#input must be equal to #output"
  inputBase=zeros(dimInput,nInput-T+1,T);
  outputBase=zeros(dimOutput,nOutput-T+1,T)
  for t=0:T-1
     inputBase[:,:,t+1] =input[:,t+1:t+nInput-T+1]
    outputBase[:,:,t+1]=output[:,t+1:t+nOutput-T+1]
  end
  inputBase,outputBase
end


text:
# simple scan into a list of free buffers

## tunables

# how much larger a buf can be to fullfil an allocation request.
# lower values improve efficiency, but increase pressure on the underlying GC.
function max_oversize(sz)
    if sz <= 2^20       # 1 MiB
        # small buffers are fine no matter
        return typemax(Int)
    elseif sz <= 2^20   # 32 MiB
        return 2^20
    else
        return 2^22
    end
end

## pooling

const pool_lock = ReentrantLock()
const pool = PerDevice{Set{Block}}() do dev
    Set{Block}()
end

const freed_lock = NonReentrantLock()
const freed = PerDevice{Vector{Block}}() do dev
    Vector{Block}()
end

function scan(dev, sz)
    @lock pool_lock for block in pool[dev]
        if sz <= sizeof(block) <= max_oversize(sz)
            delete!(pool[dev], block)
            return block
        end
    end
    return
end

function repopulate(dev)
    blocks = @lock freed_lock begin
        isempty(freed[dev]) && return
        blocks = Set(freed[dev])
        empty!(freed[dev])
        blocks
    end

    @lock pool_lock begin
        for block in blocks
            @assert !in(block, pool[dev])
            push!(pool[dev], block)
        end
    end

    return
end

function pool_reclaim(dev, sz::Int=typemax(Int))
    repopulate(dev)

    @lock pool_lock begin
        freed_bytes = 0
        while freed_bytes < sz && !isempty(pool[dev])
            block = pop!(pool[dev])
            freed_bytes += sizeof(block)
            actual_free(dev, block)
        end
        return freed_bytes
    end
end

function pool_alloc(dev, sz)
    block = nothing
    for phase in 1:3
        if phase == 2
            @pool_timeit "$phase.0 gc (incremental)" GC.gc(false)
        elseif phase == 3
            @pool_timeit "$phase.0 gc (full)" GC.gc(true)
        end

        @pool_timeit "$phase.1 repopulate" repopulate(dev)

        @pool_timeit "$phase.2 scan" begin
            block = scan(dev, sz)
        end
        block === nothing || break

        @pool_timeit "$phase.3 alloc" begin
            block = actual_alloc(dev, sz)
        end
        block === nothing || break

        @pool_timeit "$phase.4 reclaim + alloc" begin
            pool_reclaim(dev, sz)
            block = actual_alloc(dev, sz)
        end
        block === nothing || break
    end

    return block
end

function pool_free(dev, block)
    # we don't do any work here to reduce pressure on the GC (spending time in finalizers)
    # and to simplify locking (preventing concurrent access during GC interventions)
    @safe_lock_spin freed_lock begin
        push!(freed[dev], block)
    end
end

function pool_init()
    initialize!(pool, ndevices())
    initialize!(freed, ndevices())
end

function cached_memory(dev=device())
    sz = @safe_lock freed_lock mapreduce(sizeof, +, freed[dev]; init=0)
    sz += @lock pool_lock mapreduce(sizeof, +, pool[dev]; init=0)
    return sz
end


text:
module DataSets

using UUIDs
using TOML
using SHA
using ResourceContexts
using Base: PkgId

export DataSet, dataset, @datafunc, @datarun
export Blob, BlobTree, newfile, newdir

"""
The current DataSets version number
"""
const PACKAGE_VERSION = let
    project = TOML.parsefile(joinpath(pkgdir(DataSets), "Project.toml"))
    VersionNumber(project["version"])
end

include("paths.jl")

#-------------------------------------------------------------------------------

"""
A `DataSet` is a metadata overlay for data held locally or remotely which is
unopinionated about the underlying storage mechanism.

The data in a `DataSet` has a type which implies an index; the index can be
used to partition the data for processing.
"""
struct DataSet
    # For now, the representation `conf` contains data read directly from the
    # TOML. Once the design has settled we might get some explicit fields and
    # do validation.
    uuid::UUID     # Unique identifier for the dataset. Use uuid4() to create these.
    conf

    function DataSet(conf)
        _check_keys(conf, DataSet, ["uuid"=>String, "storage"=>Dict, "name"=>String])
        _check_keys(conf["storage"], DataSet, ["driver"=>String])
        check_dataset_name(conf["name"])
        new(UUID(conf["uuid"]), conf)
    end

    #=
    name::String # Default name for convenience.
                         # The binding to an actual name is managed by the data
                         # project.
    storage        # Storage config and driver definition
    maps::Vector{DataMap}

    # Generic dictionary of other properties... for now. Required properties
    # will be moved
    _other::Dict{Symbol,Any}

    #storage_id     # unique identifier in storage backend, if it exists
    #owner          # Project or user who owns the data
    #description::String
    #type           # Some representation of the type of data?
    #               # An array, blob, table, tree, etc
    #cachable::Bool # Can the data be cached?  It might not for data governance
    #               # reasons or it might change commonly.
    ## A set of identifiers
    #tags::Set{String}
    =#
end

_key_match(config, (k,T)::Pair) = haskey(config, k) && config[k] isa T
_key_match(config, k::String) = haskey(config, k)

function _check_keys(config, context, keys)
    missed_keys = filter(k->!_key_match(config, k), keys)
    if !isempty(missed_keys)
        error("""
              Missing expected keys in $context:
              $missed_keys

              In TOML fragment:
              $(sprint(TOML.print,config))
              """)
    end
end

"""
    check_dataset_name(name)

Check whether a dataset name is valid. Valid names include start with a letter
and may contain letters, numbers or `_`. Names may be hieracicial, with pieces
separated with forward slashes. Examples:

    my_data
    my_data_1
    username/data
    organization/project/data
"""
function check_dataset_name(name::AbstractString)
    # DataSet names disallow most punctuation for now, as it may be needed as
    # delimiters in data-related syntax (eg, for the data REPL).
    dataset_name_pattern = r"
        ^
        [[:alpha:]]
        (?:
            [[:alnum:]_]      |
            / (?=[[:alpha:]])
        )*
        $
        "x
    if !occursin(dataset_name_pattern, name)
        error("DataSet name \"$name\" is invalid. DataSet names must start with a letter and can contain only letters, numbers, `_` or `/`.")
    end
end

# Hacky thing until we figure out which fields DataSet should actually have.
function Base.getproperty(d::DataSet, name::Symbol)
    if name in fieldnames(DataSet)
        return getfield(d, name)
    else
        getfield(d, :conf)[string(name)]
    end
end

function Base.show(io::IO, d::DataSet)
    print(io, DataSet, "(name=$(repr(d.name)), uuid=$(repr(d.uuid)), #= … =#)")
end

function Base.show(io::IO, ::MIME"text/plain", d::DataSet)
    TOML.print(io, d.conf)
end

#-------------------------------------------------------------------------------
"""
Subtypes of `AbstractDataProject` have the interface

Must implement:
  - `Base.get(project, dataset_name, default)` — search
  - `Base.keys(project)` - get dataset names

Optional:
  - `Base.iterate()`  — default implementation in terms of `keys` and `get`
  - `Base.pairs()`    — default implementation in terms of `keys` and `get`
  - `Base.haskey()`   — default implementation in terms of `get`
  - `Base.getindex()` — default implementation in terms of `get`
  - `DataSets.project_name()` — returns `nothing` by default.

Provided by AbstractDataProject (should not be overridden):
  - `DataSets.dataset()` - implemented in terms of `get`
"""
abstract type AbstractDataProject end

function Base.getindex(proj::AbstractDataProject, name::AbstractString)
    data = get(proj, name, nothing)
    data != nothing || error("DataSet $(repr(name)) not found")
    data
end

"""
    dataset(name)
    dataset(project, name)

Returns the [`DataSet`](@ref) with the given `name` from `project`. If omitted,
the global data environment [`DataSets.PROJECT`](@ref) will be used.

The `DataSet` is *metadata*, but to use the actual *data* in your program you
need to use the `open` function to access the `DataSet`'s content as a given
Julia type.

# Example

To open a dataset named `"a_text_file"` and read the whole content as a String,

```julia
content = open(String, dataset("a_text_file"))
```

To open the same dataset as an `IO` stream and read only the first line,

```julia
open(IO, dataset("a_text_file")) do io
    line = readline(io)
    @info "The first line is" line
end
```

To open a directory as a browsable tree object,

```julia
open(BlobTree, dataset("a_tree_example"))
```
"""
function dataset(proj::AbstractDataProject, name::AbstractString)
    # Non-fancy search... for now :)
    # In the future, we can consider parsing `name` into a dataset prefix and a
    # data selector / resource section. Eg a path for BlobTree which gives us a
    # SubDataSet
    #
    # The URN RFC8141 has some good design inspiration here, in particular the
    # distinction between r-component and q-component seems relevant:
    # * Some parameters may need to be passed to the "resolver" (ie, the data
    #   storage backend)
    # * Some parameters may need to be passed to the dataset itself (eg, a
    #   relative path within the dataset)
    #
    # See https://datatracker.ietf.org/doc/html/rfc8141#page-12
    return proj[name]
end

function Base.haskey(proj::AbstractDataProject, name::AbstractString)
    get(proj, name, nothing) !== nothing
end

function Base.iterate(project::AbstractDataProject, state=nothing)
    if isnothing(state)
        ks = keys(project)
        ks_itr = iterate(ks)
    else
        (ks, ks_state) = state
        ks_itr = iterate(ks, ks_state)
    end
    if isnothing(ks_itr)
        return nothing
    end
    (k, ks_state) = ks_itr
    val = get(project, k, nothing)
    if isnothing(val)
        # val could be `nothing` if entries in the project are updated
        # concurrently. (Eg, this might happen for data projects which are
        # backed by the filesystem.)
        return iterate(project, (ks, ks_state))
    end
    (val, (ks, ks_state))
end

# Unknown size by default, due to the above get-based implementation of
# iterate, coupled with possible concurrent modification.
Base.IteratorSize(::AbstractDataProject) = Base.SizeUnknown()

function Base.pairs(proj::AbstractDataProject)
    ks = keys(proj)
    (k=>d for (k,d) in (k=>get(proj, k, nothing) for k in ks) if !isnothing(d))
end

"""
    project_name(data_project)

Return the name of the given `data_project`. Ideally this can be used to
uniquely identify the project when modifying the project stack in
`DataSets.PROJECT`. For projects which were generated from
`JULIA_DATASETS_PATH`, this will be the expanded path component.

Other types of projects will have to return something else. For example, remote
data projects may want to return a URI. For projects which have no obvious
identifier, `nothing` is returned.
"""
project_name(data_project::AbstractDataProject) = nothing

data_drivers(proj::AbstractDataProject) = []

#-------------------------------------------------------------------------------
"""
    DataProject

A concrete data project is a collection of DataSets with associated names.
Names are unique within the project.
"""
struct DataProject <: AbstractDataProject
    datasets::Dict{String,DataSet}
    drivers::Vector{Dict{String,Any}}
end

DataProject() = DataProject(Dict{String,DataSet}(), Vector{Dict{String,Any}}())

DataProject(project::AbstractDataProject) = DataProject(Dict(pairs(project)),
                                                        Vector{Dict{String,Any}}())

data_drivers(project::DataProject) = project.drivers

function _fill_template(toml_path, toml_str)
    # Super hacky templating for paths relative to the toml file.
    # We really should have something a lot nicer here...
    if Sys.iswindows()
        toml_path = replace(toml_path, '\\'=>'/')
    end
    toml_str = replace(toml_str, "@__DIR__"=>toml_path)
end

"""
`CURRENT_DATA_CONFIG_VERSION` is the current version of the data configuration
format, as reflected in the Data.toml `data_config_version` key. This allows old
versions of DataSets.jl to detect when the Data.toml schema has changed.

New versions of DataSets.jl should always try to parse old versions of
Data.toml where possible.

### Version 0 (DataSets <= 0.2.3):

Required structure:

```toml
data_config_version = 0

[[datasets]]
name = "alphnumeric and underscore chars"
uuid = "a uuid"

[datasets.storage]
    driver = "StorageDriver"
```

### Version 1 (DataSets 0.2.4):

Same as version 0 with additions
* Allows the `/` character in dataset names to serve as a namespace separator.
* Adds a new `[[drivers]]` section with the format

```toml
[[drivers]]
type="storage"
name="<driver name>"

    [drivers.module]
    name="<module name>"
    uuid="<module uuid>"
```
"""
const CURRENT_DATA_CONFIG_VERSION = 1

"""
    load_project(path; auto_update=false)
    load_project(config_dict)

Load a data project from a system `path` referring to a TOML file. If
`auto_update` is true, the returned project will monitor the file for updates
and reload when necessary.

Alternatively, create a `DataProject` from a an existing dictionary
`config_dict`, which should be in the Data.toml format.

See also [`load_project!`](@ref).
"""
function load_project(path::AbstractString; auto_update=false)
    sys_path = abspath(path)
    auto_update ? TomlFileDataProject(sys_path) :
                  _load_project(read(sys_path,String), dirname(sys_path))
end

function load_project(config::AbstractDict; kws...)
    _check_keys(config, "Data.toml", ["data_config_version"=>Integer,
                                      "datasets"=>AbstractVector])
    format_ver = config["data_config_version"]
    if format_ver > CURRENT_DATA_CONFIG_VERSION
        error("""
              data_config_version=$format_ver is newer than supported.
              Consider upgrading to a newer version of DataSets.jl
              """)
    end
    proj = DataProject()
    for dataset_conf in config["datasets"]
        dataset = DataSet(dataset_conf)
        link_dataset(proj, dataset.name => dataset)
    end
    if haskey(config, "drivers")
        _check_keys(config, DataProject, ["drivers"=>AbstractVector])
        for driver_conf in config["drivers"]
            _check_keys(driver_conf, DataProject, ["type"=>String, "name"=>String, "module"=>Dict])
            _check_keys(driver_conf["module"], DataProject, ["name"=>String, "uuid"=>String])
            push!(proj.drivers, driver_conf)
        end
    end
    proj
end

# TODO: Deprecate this?
function load_project(path::AbstractPath; kws)
    load_project(sys_abspath(abspath(path)); kws...)
end

function link_dataset(proj::DataProject, (name,data)::Pair)
    proj.datasets[name] = data
end

link_dataset(proj::DataProject, d::DataSet) = link_dataset(proj, d.name=>d)

function unlink_dataset(proj::DataProject, name::AbstractString)
    if !haskey(proj.datasets, name)
        throw(ArgumentError("No dataset \"$name\" in data project"))
    end
    d = proj.datasets[name]
    delete!(proj.datasets, name)
    d
end

function Base.get(proj::DataProject, name::AbstractString, default)
    get(proj.datasets, name, default)
end

Base.keys(proj::DataProject) = keys(proj.datasets)

function Base.iterate(proj::DataProject, state=nothing)
    # proj.datasets iterates key=>value; need to rejig it to iterate values.
    itr = isnothing(state) ? iterate(proj.datasets) : iterate(proj.datasets, state)
    isnothing(itr) && return nothing
    (x, state) = itr
    (x.second, state)
end

function Base.show(io::IO, ::MIME"text/plain", project::AbstractDataProject)
    datasets = collect(pairs(project))
    summary(io, project)
    println(io, ":")
    if isempty(datasets)
        print(io, "  (empty)")
        return
    end
    sorted = sort(datasets, by=first)
    maxwidth = maximum(textwidth.(first.(sorted)))
    for (i, (name, data)) in enumerate(sorted)
        pad = maxwidth - textwidth(name)
        storagetype = get(data.storage, "type", nothing)
        icon = storagetype == "Blob"     ? '📄' :
               storagetype == "BlobTree" ? '📁' :
               '❓'
        print(io, "  ", icon, ' ', name, ' '^pad, " => ", data.uuid)
        if i < length(sorted)
            println(io)
        end
    end
end

function Base.summary(io::IO, project::AbstractDataProject)
    print(io, typeof(project))
    name = project_name(project)
    if !isnothing(name)
        print(io, " [", name, "]")
    end
end

#-------------------------------------------------------------------------------
"""
    StackedDataProject()
    StackedDataProject(projects)

Search stack of AbstractDataProjects, where projects are searched from the
first to last element of `projects`.

Additional projects may be added or removed from the stack with `pushfirst!`,
`push!` and `empty!`.

See also [`DataSets.PROJECT`](@ref).
"""
struct StackedDataProject <: AbstractDataProject
    projects::Vector
end

StackedDataProject() = StackedDataProject([])

data_drivers(stack::StackedDataProject) = vcat(data_drivers.(stack.projects)...)

function Base.keys(stack::StackedDataProject)
    names = []
    for project in stack.projects
        append!(names, keys(project))
    end
    unique(names)
end

function Base.get(stack::StackedDataProject, name::AbstractString, default)
    for project in stack.projects
        d = get(project, name, nothing)
        if !isnothing(d)
            return d
        end
    end
end

# API for manipulating the stack.
Base.push!(stack::StackedDataProject, project) = push!(stack.projects, project)
Base.pushfirst!(stack::StackedDataProject, project) = pushfirst!(stack.projects, project)
Base.popfirst!(stack::StackedDataProject) = popfirst!(stack.projects)
Base.pop!(stack::StackedDataProject) = pop!(stack.projects)
Base.empty!(stack::StackedDataProject) = empty!(stack.projects)

function Base.show(io::IO, mime::MIME"text/plain", stack::StackedDataProject)
    summary(io, stack)
    println(io, ":")
    for (i,project) in enumerate(stack.projects)
        # show(io, mime, project)
        # indent each project
        str = sprint(show, mime, project)
        print(io, join("  " .* split(str, "\n"), "\n"))
        i != length(stack.projects) && println(io)
    end
end

include("file_data_projects.jl")

function expand_project_path(path)
    if path == "@"
        return path
    elseif path == ""
        return joinpath(homedir(), ".julia", "datasets", "Data.toml")
    else
        path = abspath(expanduser(path))
        if isdir(path)
            path = joinpath(path, "Data.toml")
        end
    end
    path
end

function data_project_from_path(path)
    if path == "@"
        ActiveDataProject()
    else
        TomlFileDataProject(expand_project_path(path))
    end
end

function create_project_stack(env)
    stack = []
    env_search_path = get(env, "JULIA_DATASETS_PATH", nothing)
    if isnothing(env_search_path)
        paths = ["@", ""]
    else
        paths = isempty(env_search_path) ? String[] :
            split(env_search_path, Sys.iswindows() ? ';' : ':')
    end
    for path in paths
        project = data_project_from_path(path)
        push!(stack, project)
    end
    StackedDataProject(stack)
end

#-------------------------------------------------------------------------------
# Global datasets configuration for current Julia session

# Global stack of data projects, with the top of the stack being searched
# first.
"""
`DataSets.PROJECT` contains the default global data environment for the Julia
process. This is created from the `JULIA_DATASETS_PATH` environment variable at
initialization which is a list of paths (separated by `:` or `;` on windows).

In analogy to `Base.LOAD_PATH` and `Base.DEPOT_PATH`, the path components are
interpreted as follows:

 - `@` means the path of the current active project as returned by
   `Base.active_project(false)` This can be useful when you're "doing
   scripting" and you've got a project-specific Data.toml which resides
   next to the Project.toml. This only applies to projects which are explicitly
   set with `julia --project` or `Pkg.activate()`.
 - Explicit paths may be either directories or files in Data.toml format.
   For directories, the filename "Data.toml" is implicitly appended.
   `expanduser()` is used to expand the user's home directory.
 - As in `DEPOT_PATH`, an *empty* path component means the user's default
   Julia home directory, `joinpath(homedir(), ".julia", "datasets")`

This simplified version of the code loading rules (LOAD_PATH/DEPOT_PATH) is
used as it seems unlikely that we'll want data location to be version-
dependent in the same way that that code is.

Unlike `LOAD_PATH`, `JULIA_DATASETS_PATH` is represented inside the program as
a `StackedDataProject`, and users can add custom projects by defining their own
`AbstractDataProject` subtypes.

Additional projects may be added or removed from the stack with `pushfirst!`,
`push!` and `empty!`.
"""
PROJECT = StackedDataProject()

# deprecated. TODO: Remove dependency on this from JuliaHub
_current_project = DataProject()

_isprecompiling() = ccall(:jl_generating_output, Cint, ()) == 1

function __init__()
    # Triggering Base.require for storage drivers during precompilation should
    # be unnecessary and can cause problems if those driver modules use
    # Requires-like code loading.
    if !_isprecompiling()
        global PROJECT = create_project_stack(ENV)
        for proj in PROJECT.projects
            try
                add_storage_driver(proj)
            catch exc
                @error "Could not load storage drivers from data project" #=
                    =# project=proj exception=(exc,catch_backtrace())
            end
        end
    end
end

dataset(name) = dataset(PROJECT, name)

"""
    load_project!(path_or_config)

Prepends to the default global dataset search stack, [`DataSets.PROJECT`](@ref).

May be renamed in a future version.
"""
function load_project!(path_or_config)
    new_project = load_project(path_or_config, auto_update=true)
    add_storage_driver(new_project)
    pushfirst!(PROJECT, new_project)
    # deprecated: _current_project reflects only the initial version of the
    # project on *top* of the stack.
    _current_project = DataProject(new_project)
end

#-------------------------------------------------------------------------------
# Storage layer and interface

const _storage_drivers_lock = ReentrantLock()
const _storage_drivers = Dict{String,Any}()

"""
    add_storage_driver(driver_name=>storage_opener)

Associate DataSet storage driver named `driver_name` with `storage_opener`.
When a `dataset` with `storage.driver == driver_name` is opened,
`storage_opener(user_func, storage_config, dataset)` will be called. Any
existing storage driver registered to `driver_name` will be overwritten.

As a matter of convention, `storage_opener` should generally take configuration
from `storage_config` which is just `dataset.storage`. But to avoid config
duplication it may also use the content of `dataset`, (for example, dataset.uuid).

Packages which define new storage drivers should generally call
`add_storage_driver()` within their `__init__()` functions.
"""
function add_storage_driver((name,opener)::Pair)
    lock(_storage_drivers_lock) do
        _storage_drivers[name] = opener
    end
end

function add_storage_driver(project::AbstractDataProject)
    for conf in data_drivers(project)
        if conf["type"] != "storage"
            # Anticipate there might be layer drivers too
            continue
        end
        pkgid = PkgId(UUID(conf["module"]["uuid"]), conf["module"]["name"])
        if Base.haskey(Base.package_locks, pkgid)
            # Hack: Avoid triggering another call to require() for packages
            # which are already in the process of being loaded. (This would
            # result in a deadlock!)
            #
            # Obviously this depends on Base internals...
            continue
        end
        mod = Base.require(pkgid)
        driver_name = conf["name"]
        # Module itself does add_storage_driver() inside its __init__
        # TODO: Is this a good workflow?
        lock(_storage_drivers_lock) do
            get(_storage_drivers, driver_name) do
                error("Package $pkgid did not provide storage driver $driver_name")
            end
        end
    end
end

function _find_driver(dataset)
    storage_config = dataset.storage
    driver_name = get(storage_config, "driver") do
        error("`storage.driver` configuration not found for dataset $(dataset.name)")
    end
    driver = lock(_storage_drivers_lock) do
        get(_storage_drivers, driver_name) do
            error("""
                  Storage driver $(repr(driver_name)) not found for dataset $(dataset.name).
                  Current drivers are $(collect(keys(_storage_drivers)))
                  """)
        end
    end
end

#-------------------------------------------------------------------------------
# Functions for opening datasets

# do-block form of open()
function Base.open(f::Function, as_type, dataset::DataSet)
    storage_config = dataset.storage
    driver = _find_driver(dataset)
    driver(storage_config, dataset) do storage
        open(f, as_type, storage)
    end
end

# Contexts-based form of open()
@! function Base.open(dataset::DataSet)
    storage_config = dataset.storage
    driver = _find_driver(dataset)
    # Use `enter_do` because drivers don't yet use the ResourceContexts.jl mechanism
    (storage,) = @! enter_do(driver, storage_config, dataset)
    storage
end

@! function Base.open(as_type, dataset::DataSet)
    storage = @! open(dataset)
    @! open(as_type, storage)
end

# TODO:
#  Consider making a distinction between open() and load().

# Finalizer-based version of open()
function Base.open(dataset::DataSet)
    @context begin
        result = @! open(dataset)
        @! ResourceContexts.detach_context_cleanup(result)
    end
end

function Base.open(as_type, dataset::DataSet)
    @context begin
        result = @! open(as_type, dataset)
        @! ResourceContexts.detach_context_cleanup(result)
    end
end

#-------------------------------------------------------------------------------
# Application entry points
include("entrypoint.jl")

# Builtin Data models
include("BlobTree.jl")

# Builtin backends
include("filesystem.jl")
include("DataTomlStorage.jl")

# Backends
# include("ZipTree.jl")
# include("GitTree.jl")

# Application-level stuff
include("repl.jl")

end


text:
"""
    Clock{T}

Manages a simulation's time information.
"""
mutable struct Clock{T}
    time::T         # current simulation time
    dt::T           # simulation timestep
    stop_time::T    # simulation end time
end

tick!(clock::Clock) = (clock.time += clock.dt)

stop_time_exceeded(clock::Clock) = (clock.time >= clock.stop_time)

max_stars_repo_name: CliMA/CouplerMachine

max_issues_repo_name: CliMA/CouplerMachine


text:
module J4Org

using Random # for random string

export create_documented_item_array_dir, print_org_doc

if VERSION < v"0.7"
    const Nothing = Void
end

#+Error
# You must use this function to print error message,
# It generates a message of the form
# #+BEGIN_EXAMPLE
# # =WARNING:= message
# #+END_EXAMPLE
# which is an Org mode *comment*, hence it does not affect function output.
#
function warning_message(message::String)::Nothing
    println(stderr,"# =WARNING:= $(message)")
end

#+Error,Links
# Generate a org compatible link to file
# *Examples:*
#!J4Org.create_file_org_link("/path/file.txt")
#!J4Org.create_file_org_link("/path/file.txt",10)
function create_file_org_link(filename::String,line::Int=0)::String
    if line<=0
        link = "[[file:$(filename)][$(basename(filename))]]"
    else
        link = "[[file:$(filename)::$(line)][$(basename(filename)):$(line)]]"
    end
    return link
end

include("tokenizer.jl")
include("extract.jl")
include("find_tag.jl")
include("documented_item.jl")
include("links.jl")
include("evaluate.jl")
include("org_string_comment.jl")
include("org_string_code.jl")
include("main.jl")

end # module


text:
module BuildingTaoBindings

using Libdl

TAO_INCDIR = get(ENV, "TAO_INCDIR", "/home/narit/local/TAO/base")
TAO_LIBDIR = get(ENV, "TAO_LIBDIR", "/usr/local/lib")
TAO_DLL = joinpath(TAO_LIBDIR, "libtao.$(Libdl.dlext)")

run(`make TAO_INCDIR="$TAO_INCDIR" TAO_LIBDIR="$TAO_LIBDIR" TAO_DLL="$TAO_DLL" all`)

end # module


text:
#!/cvmfs/soft.computecanada.ca/easybuild/software/2017/avx512/Compiler/gcc7.3/julia/1.1.0/bin/julia
#SBATCH -o four_rooms_var_red.out # Standard output
#SBATCH -e four_rooms_var_red.err # Standard error
#SBATCH --mem-per-cpu=2000M # Memory request of 2 GB
#SBATCH --time=12:00:00 # Running time of 6 hours
#SBATCH --ntasks=64
#SBATCH --account=def-whitem

using Pkg
Pkg.activate(".")

using Reproduce
using Logging

const save_loc = "four_rooms_cont_exp_int_study_rmsprop"
const exp_file = "experiment/four_rooms_cont_int.jl"
const exp_module_name = :FourRoomsContIntExperiment
const exp_func_name = :main_experiment
const alphas = [0.0, 0.00001, 0.00005, 0.0001, 0.0005, 0.001, 0.005, 0.01]
const betas = collect(0.0:0.1:1.0)
const policies = ["random_state_variant", "random_state_weight_variant", "uniform"]
const gvfs = ["collide_down", "favored_down"]
const batchsizes = [16]
const train_gaps = [1, 2, 3, 4, 8, 16, 32, 48, 64, 128, 192, 256]
const warm_up = 1000
const buffersize = 15000
const numsteps = 250000

function make_arguments(args::Dict{String, String})
    new_args=["--policy", args["policy"],
              "--gvf", args["gvf"],
              "--train_gap", args["train_gap"],
              "--batchsize", args["batchsize"],
              "--beta", args["beta"],
              "--run", args["run"],
              ]
    return new_args
end

function main()

    as = ArgParseSettings()
    @add_arg_table as begin
        "numworkers"
        arg_type=Int64
        default=1
        "--numruns"
        arg_type=Int64
        default=10
        "--saveloc"
        arg_type=String
        default=save_loc
        "--numjobs"
        action=:store_true
    end
    parsed = parse_args(as)
    num_workers = parsed["numworkers"]

    arg_dict = Dict([
        "policy"=>policies,
        "gvf"=>gvfs,
        "train_gap"=>train_gaps,
        "batchsize"=>batchsizes,
        "beta"=>betas,
        "run"=>1:parsed["numruns"]
    ])
    arg_list = ["policy", "gvf", "beta", "train_gap", "batchsize", "run"]

    static_args = [["--exp_loc", parsed["saveloc"],
                    "--warm_up", string(warm_up),
                    "--buffersize", string(buffersize),
                    "--seed", "0",
                    "--numinter", string(numsteps),
                    "--eval_points", "100",
                    "--eval_steps", "100",
                    "--compress",
                    "--opt", "RMSProp"
                    "--alphas"];
                   string.(alphas)]
    args_iterator = ArgIterator(arg_dict, static_args; arg_list=arg_list, make_args=make_arguments)

    if parsed["numjobs"]
        @info "This experiment has $(length(collect(args_iterator))) jobs."
        println(collect(args_iterator)[num_workers])
        exit(0)
    end

    experiment = Experiment(save_loc,
                            exp_file,
                            exp_module_name,
                            exp_func_name,
                            args_iterator)

    create_experiment_dir(experiment)
    add_experiment(experiment; settings_dir="settings")
    ret = job(experiment; num_workers=num_workers)
    post_experiment(experiment, ret)
end

main()


text:
#
# traits.jl -
#
# Methods related to mapping traits.
#
#-------------------------------------------------------------------------------
#
# This file is part of LazyAlgebra (https://github.com/emmt/LazyAlgebra.jl)
# released under the MIT "Expat" license.
#
# Copyright (c) 2017-2021 Éric Thiébaut.
#

"""

```julia
LinearType(A)
```

yields the *linear* trait of mapping `A` indicating whether `A` is certainly
linear.  The returned value is one of the singletons `Linear()` for linear maps
or `NonLinear()` for other mappings.

See also: [`Trait`](@ref), [`is_linear`](@ref).

"""
LinearType(::Mapping) = NonLinear() # any mapping assumed non-linear by default
LinearType(::LinearMapping) = Linear()
LinearType(::Inverse{<:LinearMapping}) = Linear()
LinearType(::Scaled{<:LinearMapping}) = Linear()
LinearType(A::Inverse) = LinearType(unveil(A))
LinearType(A::Scaled) = LinearType(unscaled(A))
LinearType(A::Union{Sum,Composition}) =
    (allof(x -> LinearType(x) === Linear(), terms(A)...) ?
     Linear() : NonLinear())
LinearType(A::Scaled{T,S}) where {T,S} =
    # If the multiplier λ of a scaled mapping A = (λ⋅M) is zero, then A behaves
    # linearly even though M is not a linear mapping.  FIXME: But acknowledging
    # this as a linear mapping may give rise to troubles later.
    (multiplier(A) == zero(S) ? Linear() : LinearType(unscaled(A)))

@doc @doc(LinearType) Linear
@doc @doc(LinearType) NonLinear

"""

```julia
SelfAdjointType(A)
```

yields the *self-adjoint* trait of mapping `A` indicating whether `A` is
certainly a self-adjoint linear map.  The returned value is one of the
singletons `SelfAdjoint()` for self-adjoint linear maps and `NonSelfAdjoint()`
for other mappings.

See also: [`Trait`](@ref), [`is_selfadjoint`](@ref).

"""
SelfAdjointType(::Mapping) = NonSelfAdjoint()
SelfAdjointType(A::DecoratedMapping) = SelfAdjointType(unveil(A))
SelfAdjointType(A::Scaled) = SelfAdjointType(unscaled(A))
SelfAdjointType(A::Sum) =
    (allof(x -> SelfAdjointType(x) === SelfAdjoint(), terms(A)...) ?
     SelfAdjoint() : NonSelfAdjoint())
SelfAdjointType(A::Gram) = SelfAdjoint()

@doc @doc(SelfAdjointType) SelfAdjoint
@doc @doc(SelfAdjointType) NonSelfAdjoint

"""

```julia
MorphismType(A)
```

yields the *morphism* trait of mapping `A` indicating whether `A` is certainly
an endomorphism (its input and output spaces are the same).  The returned value
is one of the singletons `Endomorphism()` for mappings whose input and output
spaces are the same or `Morphism()` for other mappings.

See also: [`Trait`](@ref), [`is_endomorphism`](@ref).

"""
MorphismType(::Mapping) = Morphism()
MorphismType(A::DecoratedMapping) = MorphismType(unveil(A))
MorphismType(A::Gram) = Endomorphism()
MorphismType(A::Scaled) = MorphismType(unscaled(A))
MorphismType(A::Union{Sum,Composition}) =
    (allof(x -> MorphismType(x) === Endomorphism(), terms(A)...) ?
     Endomorphism() : Morphism())

@doc @doc(MorphismType) Morphism
@doc @doc(MorphismType) Endomorphism

"""

```julia
DiagonalType(A)
```

yields the *diagonal* trait of mapping `A` indicating whether `A` is certainly
a diagonal linear mapping.  The returned value is one of the singletons
`DiagonalMapping()` for diagonal linear maps or `NonDiagonalMapping()` for other
mappings.

See also: [`Trait`](@ref), [`is_diagonal`](@ref).

"""
DiagonalType(::Mapping) = NonDiagonalMapping()
DiagonalType(A::DecoratedMapping) = DiagonalType(unveil(A))
DiagonalType(A::Scaled) = DiagonalType(unscaled(A))
DiagonalType(A::Union{Sum,Composition}) =
    (allof(x -> DiagonalType(x) === DiagonalMapping(), terms(A)...) ?
     DiagonalMapping() : NonDiagonalMapping())

@doc @doc(DiagonalType) NonDiagonalMapping
@doc @doc(DiagonalType) DiagonalMapping

"""
```julia
is_linear(A)
```

yields whether `A` is certainly a linear mapping.

!!! note
    This method is intended to perform certain automatic simplifications or
    optimizations.  It is guaranted to return `true` when its argument is
    certainly a linear mapping but it may return `false` even though its
    argument behaves linearly because it is not always possible to figure out
    that a complex mapping assemblage has this property.

See also: [`LinearType`](@ref).

"""
is_linear(A::LinearMapping) = true
is_linear(A::Mapping) = _is_linear(LinearType(A))
_is_linear(::Linear) = true
_is_linear(::NonLinear) = false

"""
```julia
is_selfadjoint(A)
```

yields whether mapping `A` is certainly a self-adjoint linear mapping.

!!! note
    This method is intended to perform certain automatic simplifications or
    optimizations.  It is guaranted to return `true` when its argument is
    certainly a self-adjoint linear mapping but it may return `false` even
    though its argument behaves like a self-adjoint linear map because it is
    not always possible to figure out that a complex mapping construction has
    this property or because, for efficiency reasons, the coefficients of the
    mapping are not considered for this trait.

See also: [`SelfAdjointType`](@ref).

"""
is_selfadjoint(A::Mapping) = _is_selfadjoint(SelfAdjointType(A))
_is_selfadjoint(::SelfAdjoint) = true
_is_selfadjoint(::NonSelfAdjoint) = false

"""
```julia
is_endomorphism(A)
```

yields whether mapping `A` is certainly an endomorphism.

!!! note
    This method is intended to perform certain automatic simplifications or
    optimizations.  It is guaranted to return `true` when its argument is
    certainly an endomorphism but it may return `false` even though its
    argument behaves like an endomorphism because it is not always possible to
    figure out that a complex mapping assemblage has this property.

See also: [`MorphismType`](@ref).

"""
is_endomorphism(A::Mapping) = _is_endomorphism(MorphismType(A))
_is_endomorphism(::Endomorphism) = true
_is_endomorphism(::Morphism) = false

"""
```julia
is_diagonal(A)
```

yields whether mapping `A` is certainly a diagonal linear map.

!!! note
    This method is intended to perform certain automatic simplifications or
    optimizations.  It is guaranted to return `true` when its argument is
    certainly a diagonal linear map but it may return `false` even though its
    argument behaves like a diagonal linear map because it is not always
    possible to figure out that a complex mapping assemblage has this property.

See also: [`DiagonalType`](@ref).

"""
is_diagonal(A::Mapping) = _is_diagonal(DiagonalType(A))
_is_diagonal(::DiagonalMapping) = true
_is_diagonal(::NonDiagonalMapping) = false


text:
# Julia wrapper for header: /Users/dls/jl/openssl-1.0.1e/include/openssl/md4.h
# Automatically generated using Clang.jl wrap_c, version 0.0.0

@c Int32 MD4_Init (Ptr{MD4_CTX},) libcrypto
@c Int32 MD4_Update (Ptr{MD4_CTX}, Ptr{None}, Csize_t) libcrypto
@c Int32 MD4_Final (Ptr{Uint8}, Ptr{MD4_CTX}) libcrypto
@c Ptr{Uint8} MD4 (Ptr{Uint8}, Csize_t, Ptr{Uint8}) libcrypto
@c None MD4_Transform (Ptr{MD4_CTX}, Ptr{Uint8}) libcrypto