我开始用 expl3 编写新包。由于与通常的 LaTeX2e 相比,学习资源和示例较少,我对我的代码质量(格式、可读性、约定等)真的不太了解。因此,我想用一个具体的例子来征求您的建议。
以下是我的包的当前版本crefthe
。我确信存在更好的实现,但这是我目前能做到的最好的。无论其目的是什么,您能否指出代码中存在的问题(例如命名约定、格式和可读性、用户和编程级宏的错误使用等)并提出一些可能的改进建议?我很感激任何建议。
\NeedsTeXFormat{LaTeX2e}[2020-10-01]
\RequirePackage{l3keys2e}
\ProvidesExplPackage
{crefthe}
{2022/02/02} {}
{Cross referencing with proper definite articles}
\keys_define:nn {crefthe}
{
unknown .code:n =
{
\PassOptionsToPackage{\CurrentOption}{cleveref}
}
}
\ProcessKeysOptions{crefthe}
\RequirePackage{cleveref}
\tl_gset:Nn \g_crefthe_prepf_tl {}
\tl_gset:Nn \g_crefthe_prep_tl {}
\tl_gset:Nn \g_crefthe_sep_tl {}
\cs_new:Nn \crefthe_case:n {#1}
\cs_new:Nn \crefthe_nochange:n {#1}
\cs_new:Nn \crefthe_atbegindocument:n {#1}
\cs_new:Nn \crefthe_atbegindocument_if_needed:n
{
\cs_if_eq:NNTF \@onlypreamble \@notprerr
{
\cs_gset_eq:NN \crefthe_atbegindocument:n \crefthe_nochange:n
}
{
\cs_gset_eq:NN \crefthe_atbegindocument:n \AtBeginDocument
}
\crefthe_atbegindocument:n {#1}
}
\NewDocumentCommand{\crefthe}{st-t+O{}m}
{
\IfBooleanTF{ #2 }
{
\tl_gset:Nn \g_crefthe_prepf_tl { #4 }
}
{
\IfBooleanTF{ #3 }
{
\tl_gset:Nn \g_crefthe_prep_tl { #4 }
}
{
\str_case_e:nn { \crefthe_prep_mode:nn }
{
{ - } { \tl_gset:Nn \g_crefthe_prepf_tl { #4 } }
{ + } { \tl_gset:Nn \g_crefthe_prep_tl { #4 } }
}
}
}
\IfBooleanTF{ #1 } { \cref*{#5} } { \cref{#5} }
\tl_gset:Nn \g_crefthe_prep_tl {}
}
\NewDocumentCommand{\Crefthe}{st-t+O{}m}
{
\cs_gset_eq:NN \crefthe_case:n \text_titlecase:n
\IfBooleanTF{ #2 }
{
\tl_gset:Nn \g_crefthe_prepf_tl { #4 }
}
{
\IfBooleanTF{ #3 }
{
\tl_gset:Nn \g_crefthe_prep_tl { #4 }
}
{
\str_case_e:nn { \crefthe_prep_mode:nn }
{
{ - } { \tl_gset:Nn \g_crefthe_prepf_tl { #4 } }
{ + } { \tl_gset:Nn \g_crefthe_prep_tl { #4 } }
}
}
}
\IfBooleanTF{ #1 } { \Cref*{#5} } { \Cref{#5} }
\tl_gset:Nn \g_crefthe_prep_tl {}
}
\NewDocumentCommand{\crefthename}{mO{}mO{}m}
{
\str_if_eq:eeTF {#2} {}
{
\crefname{#1}{#3}{#5}
\cs_set:cn {cref_#1_format:nnn} {##2#3~##1##3}
\cs_set:cn {cref_#1_format_first:nnn} {##2#5~##1##3}
}
{
\crefname{#1}{\crefthemark{#2} \g_crefthe_sep_tl #3}{\crefthemark{#4} \g_crefthe_sep_tl #5}
\cs_set:cn {cref_#1_format:nnn} {\crefthemark{#2} \g_crefthe_sep_tl ##2#3~##1##3}
\cs_set:cn {cref_#1_format_first:nnn} {\crefthemark{#4} \g_crefthe_sep_tl ##2#5~##1##3}
}
\crefthe_atbegindocument_if_needed:n
{
\cs_set_eq:cc {cref@#1@format} {cref_#1_format:nnn}
\cs_set_eq:cc {cref@#1@format@first} {cref_#1_format_first:nnn}
}
}
\NewDocumentCommand{\Crefthename}{mO{}mO{}m}
{
\str_if_eq:eeTF {#2} {}
{
\Crefname{#1}{#3}{#5}
\cs_set:cn {Cref_#1_format:nnn} {##2#3~##1##3}
\cs_set:cn {Cref_#1_format_first:nnn} {##2#5~##1##3}
}
{
\Crefname{#1}{\crefthemark{#2} \g_crefthe_sep_tl #3}{\crefthemark{#4} \g_crefthe_sep_tl #5}
\cs_set:cn {Cref_#1_format:nnn} {\crefthemark{#2} \g_crefthe_sep_tl ##2#3~##1##3}
\cs_set:cn {Cref_#1_format_first:nnn} {\crefthemark{#4} \g_crefthe_sep_tl ##2#5~##1##3}
}
\crefthe_atbegindocument_if_needed:n
{
\cs_set_eq:cc {Cref@#1@format} {Cref_#1_format:nnn}
\cs_set_eq:cc {Cref@#1@format@first} {Cref_#1_format_first:nnn}
}
}
\NewDocumentCommand{\crefthemark}{m}{
\crefthe_contraction:nn { \crefthe_case:n \g_crefthe_prep_tl }
{
\crefthe_contraction:nn { \crefthe_case:n \g_crefthe_prepf_tl }{#1}
}
\tl_gset:Nn \g_crefthe_prepf_tl {}
\tl_gset:Nx \g_crefthe_prep_tl { \text_lowercase:n \g_crefthe_prep_tl }
\str_case_e:nnF {\str_tail:n {#1}}
{
{'} { \tl_gset:Nn \g_crefthe_sep_tl {} }
}
{ \tl_gset:Nn \g_crefthe_sep_tl {~} }
\cs_gset_eq:NN \crefthe_case:n \crefthe_nochange:n
% \cs_gset_eq:NN \crefthe_case:n \text_lowercase:n
}
\cs_set:Npn \crefthe_prep_mode:nn {
\str_case_e:nn { \languagename }
{
{french} { + }
{italian} { + }
{spanish} { - }
{portuguese} { + }
{brazilian} { + }
}
}
\cs_set:Npn \crefthe_contraction:nn #1#2
{
\str_if_eq:eeTF {#1} {}
{#2}
{
\str_case_e:nn { \languagename }
{
{french}
{
\str_case_e:nnF {#1~\text_lowercase:n{#2}}
{
{à~le} {au}
{à~les} {aux}
{de~le} {du}
{de~les} {des}
{À~le} {Au}
{À~les} {Aux}
{De~le} {Du}
{De~les} {Des}
}
{#1~\text_lowercase:n{#2}}
}
{italian}
{
\str_case_e:nnF {#1~\text_lowercase:n{#2}}
{
{a~il} {al}
{a~lo} {allo}
{a~l'} {all'}
{a~la} {alla}
{di~il} {del}
{di~lo} {dello}
{di~l'} {dell'}
{di~la} {della}
{da~il} {dal}
{da~lo} {dallo}
{da~l'} {dall'}
{da~la} {dalla}
{in~il} {nel}
{in~lo} {nello}
{in~l'} {nell'}
{in~la} {nella}
{su~il} {sul}
{su~lo} {sullo}
{su~l'} {sull'}
{su~la} {sulla}
{a~i} {ai}
{a~gli} {agli}
{a~le} {alle}
{di~i} {dei}
{di~gli} {degli}
{di~le} {delle}
{da~i} {dai}
{da~gli} {dagli}
{da~le} {dalle}
{in~i} {nei}
{in~gli} {negli}
{in~le} {nelle}
{su~i} {sui}
{su~gli} {sugli}
{su~le} {sulle}
{A~il} {Al}
{A~lo} {Allo}
{A~l'} {All'}
{A~la} {Alla}
{Di~il} {Del}
{Di~lo} {Dello}
{Di~l'} {Dell'}
{Di~la} {Della}
{Da~il} {Dal}
{Da~lo} {Dallo}
{Da~l'} {Dall'}
{Da~la} {Dalla}
{In~il} {Nel}
{In~lo} {Nello}
{In~l'} {Nell'}
{In~la} {Nella}
{Su~il} {Sul}
{Su~lo} {Sullo}
{Su~l'} {Sull'}
{Su~la} {Sulla}
{A~i} {Ai}
{A~gli} {Agli}
{A~le} {Alle}
{Di~i} {Dei}
{Di~gli} {Degli}
{Di~le} {Delle}
{Da~i} {Dai}
{Da~gli} {Dagli}
{Da~le} {Dalle}
{In~i} {Nei}
{In~gli} {Negli}
{In~le} {Nelle}
{Su~i} {Sui}
{Su~gli} {Sugli}
{Su~le} {Sulle}
}
{#1~\text_lowercase:n{#2}}
}
{portuguese}
{
\str_case_e:nnF {#1~\text_lowercase:n{#2}}
{
{a~o} {ao}
{a~a} {à}
{a~os} {aos}
{a~as} {às}
{de~o} {do}
{de~a} {da}
{de~os} {dos}
{de~as} {das}
{em~o} {no}
{em~a} {na}
{em~os} {nos}
{em~as} {nas}
{A~o} {Ao}
{A~a} {À}
{A~os} {Aos}
{A~as} {Às}
{De~o} {Do}
{De~a} {Da}
{De~os} {Dos}
{De~as} {Das}
{Em~o} {No}
{Em~a} {Na}
{Em~os} {Nos}
{Em~as} {Nas}
}
{#1~\text_lowercase:n{#2}}
}
{brazilian}
{
\str_case_e:nnF {#1~\text_lowercase:n{#2}}
{
{a~o} {ao}
{a~a} {à}
{a~os} {aos}
{a~as} {às}
{de~o} {do}
{de~a} {da}
{de~os} {dos}
{de~as} {das}
{em~o} {no}
{em~a} {na}
{em~os} {nos}
{em~as} {nas}
{A~o} {Ao}
{A~a} {À}
{A~os} {Aos}
{A~as} {Às}
{De~o} {Do}
{De~a} {Da}
{De~os} {Dos}
{De~as} {Das}
{Em~o} {No}
{Em~a} {Na}
{Em~os} {Nos}
{Em~as} {Nas}
}
{#1~\text_lowercase:n{#2}}
}
{spanish}
{
\str_case_e:nnF {#1~\text_lowercase:n{#2}}
{
{a~el} {al}
{de~el} {del}
{A~el} {Al}
{De~el} {Del}
}
{#1~\text_lowercase:n{#2}}
}
}
}
}
答案1
TeX-sx 的格式不太适合复习,也很难制作一般的答案。在这些限制条件下工作:
一般来说,不要过度优化代码。除非你编写了非常紧密的循环,每页使用 100 次,否则你可以使用“更高级别”的抽象。
阅读
l3style-guide
有关间距代码,ETC。:虽然这不是很重要,但我们对内核文件有一个“固定”的样式,如果软件包使用相同的样式,那么会更容易一些。避免使用括号括住 N 类型参数。例如,您不需要在后面的命令名称
\NewDocumentCommand
或后面的布尔标记周围使用括号。\IfBooleanTF
变量应该在使用前声明,例如
\tl_gset:Nn \g_crefthe_prepf_tl {}
应该在前面加上\tl_new:N \g_crefthe_prepf_tl
。大多数情况下,在创建函数时也应该做类似的事情,所以\cs_new:Npn
在顶层使用 或其他任何形式,\cs_set:Npn
如果要在作用域内调整函数的含义,则使用 或类似形式。只有记录的材料才应被赋予公开名称:虽然您想要记录的内容不是 100% 清楚,但我认为例如
\g_crefthe_prepf_tl
应该是内部的\g__crefthe_prepf_tl
。仅在需要时使用全局范围:同样,我不是 100%确定,但我怀疑
\g_crefthe_prepf_tl
,ETC。可能是本地的。跟踪命令的扩展状态。如果您有一个使用任何不可扩展代码,它本身不可扩展,应该是
protected
。例如,\crefthe_atbegindocument_if_needed:n
使用\cs_gset_eq:NN
,它是不可扩展的,因此\crefthe_atbegindocument_if_needed:n
应该使用来定义\cs_new_protected:Nn
(或者\cs_new_protected:Npn
- 这里的选择在某种程度上是个人偏好)。正确指定函数的参数。例如,
\crefthe_prep_mode:nn
函数不带参数,但有:nn
签名。不要依赖实现的副作用,除非存在重大的性能问题,否则请使用最高级别的扩展控制。您有
\str_case_e:nn { \crefthe_prep_mode:nn }
,它滥用了您已定义\crefthe_prep_mode:nn
并且它实际上是一个字符串的事实。例如,最好有\str_case:Vn \l__crefthe_prep_mode_str
字符串创建的位置,就像您已经为所做的那样\crefthe_prep_mode:nn
。请注意,我使用了-type 扩展,因为它比这里的或类型V
更清晰、更可控。另一个例子是,您依赖于将扩展参数的事实:我赞成将其作为-type 传递。e
o
\tl_gset:Nx \g_crefthe_prep_tl { \text_lowercase:n \g_crefthe_prep_tl }
\text_lowercase:n
V
同样,使用正确的工具来完成工作:
\str_if_eq:eeTF {#2} {}
几乎肯定会更好,例如\tl_if_blank:nTF {#2}
(或者如果你真的需要该扩展,请创建变体并使用\tl_if_blank:eTF
)。将数据存储在函数之外。
\crefthe_contraction:nn
您已将所有语言数据放入函数内。这会使用户或您以后难以进行更改。更好的方法是创建数据结构,以便将信息存储在外部,然后您可以动态找到它,例如\tl_if_exist:cTF { g__crefthe_lang_ \languagename _tl }
,可能带有一个或多个辅助程序,以避免重复生成名称。(例如,您\str_case_e:nnF {#1~\text_lowercase:n{#2}}
为每种语言都生成一个,而我会预先生成一次,然后将整个内容传递给辅助程序。)当有其他方法可用时,请保持警惕
\cs_(g)set_eq:NN
。例如\cs_gset_eq:NN \crefthe_case:n \text_titlecase:n
,如果您需要跟踪代码,您将永远不会“看到”\text_titlecase:n
。另一方面,如果您有,\cs_gset:Npn \crefthe_case:n ##1 { \text_titlecase:n {##1} }
您会发现调试要容易得多。在非常紧密的循环之外,一次扩展的性能损失通常不是问题。注意不要过度使用
ltcmd
来创建复杂的接口。签名几乎总是类似于som
或sO{}m
。例如,您有\NewDocumentCommand{\Crefthe}{st-t+O{}m}
,它使用非标准标记(不是星号)来改变行为。编写公共代码级接口并使用它们来实现文档命令。文档命令代码中应该有最少的“编程”,足以根据签名的可选部分划分不同的路径。对于任何实质性的新
expl3
代码,应该有一个程序员 API,提供代码级方法来获取功能但不使用文档命令(例如在文档级别使用可选参数的地方使用单独的函数)。尽可能遵守 LaTeX2e 惯例;我真的不知道你在做什么
\crefthe_atbegindocument_if_needed:n
,但我立即想到你正试图颠覆声明必须在序言中的想法,而声明的材料只能在文档正文中使用